1. 概览
Microsoft .NET Core 是 .NET 的开源和跨平台版本,可在容器中原生运行。.NET Core 可在 GitHub 上获取,由 Microsoft 和 .NET 社区维护。本实验会将一个容器化 .NET Core 应用部署到 Google Kubernetes Engine (GKE) 中。
本实验遵循典型的开发模式,即在开发者的本地环境中开发应用,然后将其部署到生产环境。在本实验的第一部分,您将使用 Cloud Shell 中运行的容器来验证一个示例 .NET Core 应用。验证后,应用会使用 GKE 部署在 Kubernetes 上。本实验包含创建 GKE 集群的步骤。
在实验的第二部分,您将对应用进行细微更改,以显示运行该应用实例的容器的主机名。然后,在 Cloud Shell 中验证更新后的应用,并更新部署以使用新版本。下图展示了本实验中的活动顺序:

费用
如果您完全按照说明运行此实验,则需要支付以下服务的正常费用
2. 设置和要求
前提条件
如需完成本实验,您需要一个 Google Cloud 账号和项目。如需详细了解如何创建新项目,请参阅此 Codelab。
本实验使用在 Cloud Shell 中运行的 Docker,您可以通过 Google Cloud 控制台访问 Cloud Shell,其中预配置了许多实用工具,例如 gcloud 和 Docker。下面展示了如何访问 Cloud Shell。点击右上角的 Cloud Shell 图标,在控制台窗口的底部窗格中显示 Cloud Shell。

GKE 集群的其他配置选项(可选)
本实验需要一个 Kubernetes 集群。在下一部分中,您将创建一个具有简单配置的 GKE 集群。本部分介绍了一些 gcloud 命令,这些命令提供了在使用 GKE 构建 Kubernetes 集群时可供使用的替代配置选项。例如,使用以下命令可以识别不同的机器类型、可用区,甚至 GPU(加速器)。
- 使用以下命令列出机器类型:
gcloud compute machine-types list - 使用以下命令列出 GPU:
gcloud compute accelerator-types list - 使用以下命令列出计算可用区:
gcloud compute zones list - 获取有关任何 gcloud 命令的帮助
gcloud container clusters --help- 例如,此命令会提供有关创建 Kubernetes 集群
gcloud container clusters create --help的详细信息
- 例如,此命令会提供有关创建 Kubernetes 集群
如需查看 GKE 配置选项的完整列表,请参阅此文档
准备创建 Kubernetes 集群
在 Cloud Shell 中,需要设置一些环境变量并配置 gcloud 客户端。这可以通过以下命令完成。
export PROJECT_ID=YOUR_PROJECT_ID
export DEFAULT_ZONE=us-central1-c
gcloud config set project ${PROJECT_ID}
gcloud config set compute/zone ${DEFAULT_ZONE}
创建 GKE 集群
由于本实验会将 .NET Core 应用部署到 Kubernetes 上,因此需要创建一个集群。使用以下命令,通过 GKE 在 Google Cloud 中创建新的 Kubernetes 集群。
gcloud container clusters create dotnet-cluster \
--zone ${DEFAULT_ZONE} \
--num-nodes=1 \
--node-locations=${DEFAULT_ZONE},us-central1-b \
--enable-stackdriver-kubernetes \
--machine-type=n1-standard-1 \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--enable-ip-alias
--num-nodes是每个可用区要添加的节点数,稍后可以扩缩--node-locations是以英文逗号分隔的可用区列表。在这种情况下,系统会使用您在上述环境变量中指定的可用区和us-central1-b- 注意:此列表不能包含重复项
--workload-pool建立 Workload Identity,以便 GKE 工作负载可以访问 Google Cloud 服务
在构建集群时,系统会显示以下内容
Creating cluster dotnet-cluster in us-central1-b... Cluster is being deployed...⠼
配置 kubectl
kubectl CLI 是与 Kubernetes 集群交互的主要方式。为了将其与刚刚创建的新集群搭配使用,需要将其配置为针对该集群进行身份验证。为此,请运行以下命令。
$ gcloud container clusters get-credentials dotnet-cluster --zone ${DEFAULT_ZONE}
Fetching cluster endpoint and auth data.
kubeconfig entry generated for dotnet-cluster.
现在应该可以使用 kubectl 与集群进行交互。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-dotnet-cluster-default-pool-02c9dcb9-fgxj Ready <none> 2m15s v1.16.13-gke.401
gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 Ready <none> 2m24s v1.16.13-gke.401
3. 在本地进行测试并确认所需功能
本实验使用 Docker Hub 上官方 .NET 代码库中的以下容器映像。
在本地运行容器以验证功能
在 Cloud Shell 中,运行以下 Docker 命令,验证 Docker 是否已启动并正常运行,以及 .NET 容器是否按预期运行:
$ docker run --rm mcr.microsoft.com/dotnet/samples
Hello from .NET!
__________________
\
\
....
....'
....
..........
.............'..'..
................'..'.....
.......'..........'..'..'....
........'..........'..'..'.....
.'....'..'..........'..'.......'.
.'..................'... ......
. ......'......... .....
. ......
.. . .. ......
.... . .......
...... ....... ............
................ ......................
........................'................
......................'..'...... .......
.........................'..'..... .......
........ ..'.............'..'.... ..........
..'..'... ...............'....... ..........
...'...... ...... .......... ...... .......
........... ....... ........ ......
....... '...'.'. '.'.'.' ....
....... .....'.. ..'.....
.. .......... ..'........
............ ..............
............. '..............
...........'.. .'.'............
............... .'.'.............
.............'.. ..'..'...........
............... .'..............
......... ..............
.....
Environment:
.NET 5.0.1-servicing.20575.16
Linux 5.4.58-07649-ge120df5deade #1 SMP PREEMPT Wed Aug 26 04:56:33 PDT 2020
确认 Web 应用功能
您还可以在 Cloud Shell 中验证示例 Web 应用。下面的 Docker run 命令会创建一个新容器,该容器会公开端口 80 并将其映射到 localhost 端口 8080。请注意,在本例中,localhost 位于 Cloud Shell 中。
$ docker run -it --rm -p 8080:80 --name aspnetcore_sample mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {64a3ed06-35f7-4d95-9554-8efd38f8b5d3} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
由于这是一个 Web 应用,因此需要在网络浏览器中查看和验证。下一部分将介绍如何在 Cloud Shell 中使用网页预览功能执行此操作。
4. 使用“网页预览”从 Cloud Shell 访问服务
Cloud Shell 提供网页预览功能,让您可以使用浏览器与 Cloud Shell 实例中运行的进程进行交互。
使用“网页预览”在 Cloud Shell 中查看应用
在 Cloud Shell 中,点击网页预览按钮,然后选择“在端口 8080 上预览”(或网页预览设置为使用的任何端口)。

系统会打开一个浏览器窗口,其中包含如下地址:
https://8080-cs-754738286554-default.us-central1.cloudshell.dev/
使用网页预览查看 .NET 示例应用
现在,您可以通过启动 Web 预览并加载提供的网址来查看上一步中启动的示例应用。输出应如下所示:

5. 部署到 Kubernetes
构建 YAML 文件并应用
下一步需要一个 YAML 文件,其中描述了两个 Kubernetes 资源:一个 Deployment 和一个 Service。在 Cloud Shell 中创建一个名为 dotnet-app.yaml 的文件,并向其中添加以下内容。
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-deployment
labels:
app: dotnetapp
spec:
replicas: 3
selector:
matchLabels:
app: dotnetapp
template:
metadata:
labels:
app: dotnetapp
spec:
containers:
- name: dotnet
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: dotnet-service
spec:
selector:
app: dotnetapp
ports:
- protocol: TCP
port: 8080
targetPort: 80
现在,使用 kubectl 将此文件应用于 Kubernetes。
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment created
service/dotnet-service created
请注意,系统会显示消息,指明已创建所需资源。
探索生成的资源
我们可以使用 kubectl CLI 检查上面创建的资源。首先,我们来看看 Deployment 资源,确认新 Deployment 是否存在。
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dotnet-deployment 3/3 3 3 80s
接下来,查看 ReplicaSet。上述 Deployment 应该创建了一个 ReplicaSet。
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
dotnet-deployment-5c9d4cc4b9 3 3 3 111s
最后,查看 Pod。Deployment 指示应有三个实例。以下命令应显示有三个实例。添加了 -o wide 选项,以便显示运行这些实例的节点。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 2m25s 10.16.0.8 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 2m25s 10.16.1.7 gke-dotnet-cluster-default-pool-02c9dcb9-fgxj <none> <none>
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 2m25s 10.16.0.7 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
查看 Service 资源
Kubernetes 中的 Service 资源是一个负载均衡器。端点由 Pod 上的标签决定。这样,通过上述 kubectl scale deployment 操作将新 Pod 添加到部署后,生成的 Pod 即可立即用于处理该 Service 的请求。
以下命令应显示 Service 资源。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dotnet-service ClusterIP 10.20.9.124 <none> 8080/TCP 2m50s
...
您可以使用以下命令查看有关该 Service 的更多详细信息。
$ kubectl describe svc dotnet-service
Name: dotnet-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=dotnetapp
Type: ClusterIP
IP: 10.20.9.124
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.16.0.7:80,10.16.0.8:80,10.16.1.7:80
Session Affinity: None
Events: <none>
请注意,Service 的类型为 ClusterIP。这意味着集群内的任何 Pod 都可以将 Service 名称 dotnet-service 解析为其 IP 地址。发送到该服务的请求将在所有实例 (Pod) 之间进行负载均衡。上面的 Endpoints 值显示了当前可用于此服务的 Pod 的 IP。将这些 IP 与上面输出的 Pod IP 进行比较。
验证正在运行的应用
此时,应用已上线,可以处理用户请求。如需访问该网站,请使用代理。以下命令会创建一个本地代理,该代理接受端口 8080 上的请求并将其传递给 Kubernetes 集群。
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
现在,使用 Cloud Shell 中的网页预览功能访问 Web 应用。
在 Web 预览生成的网址中添加以下内容:/api/v1/namespaces/default/services/dotnet-service:8080/proxy/。最终效果大致如下:
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
恭喜!您已成功在 Google Kubernetes Engine 上部署 .NET Core 应用。接下来,我们将对应用进行更改并重新部署。
6. 修改应用
在本部分中,您将修改应用,以显示实例运行所在的主机。这样就可以确认负载均衡是否正常运行,以及可用 Pod 是否按预期响应。
获取源代码
git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/aspnetapp/
更新应用以包含主机名
vi aspnetapp/Pages/Index.cshtml
<tr>
<td>Host</td>
<td>@Environment.MachineName</td>
</tr>
构建新的容器映像并在本地进行测试
使用更新后的代码构建新的容器映像。
docker build --pull -t aspnetapp:alpine -f Dockerfile.alpine-x64 .
和之前一样,在本地测试新应用
$ docker run --rm -it -p 8080:80 aspnetapp:alpine
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {f71feb13-8eae-4552-b4f2-654435fff7f8} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
和之前一样,您可以使用“网页预览”访问该应用。这次应该可以看到“Host”参数,如下所示:

在 Cloud Shell 中打开一个新标签页,然后运行 docker ps,查看容器 ID 是否与上面显示的主机值一致。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ab85ce11aecd aspnetapp:alpine "./aspnetapp" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp relaxed_northcutt
为映像添加标记并推送映像,以便 Kubernetes 可以使用
您需要为该映像添加标记并将其推送,以便 Kubernetes 能够拉取它。首先列出容器映像,然后确定所需的映像。
$ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp alpine 95b4267bb6d0 6 days ago 110MB
接下来,标记该映像并将其推送到 Google Container Registry。使用上面的 IMAGE ID,命令将如下所示
docker tag 95b4267bb6d0 gcr.io/${PROJECT_ID}/aspnetapp:alpine
docker push gcr.io/${PROJECT_ID}/aspnetapp:alpine
7. 重新部署更新后的应用
修改 YAML 文件
切换回 dotnet-app.yaml 文件保存到的目录。在 YAML 文件中找到以下行
image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
需要将其更改为引用上面创建并推送到 gcr.io 的容器映像。
image: gcr.io/PROJECT_ID/aspnetapp:alpine
别忘了修改它,以便使用您的 PROJECT_ID。完成后,它应该如下所示
image: gcr.io/myproject/aspnetapp:alpine
应用更新后的 YAML 文件
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment configured
service/dotnet-service unchanged
请注意,Deployment 资源显示为已更新,而 Service 资源显示为未更改。您可以使用命令 kubectl get pod 查看更新后的 Pod,但这次我们将添加 -w,它会监视所有发生的变化。
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 34m
dotnet-deployment-85f6446977-tmbdq 0/1 ContainerCreating 0 4s
dotnet-deployment-85f6446977-tmbdq 1/1 Running 0 5s
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 ContainerCreating 0 0s
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 1/1 Running 0 6s
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 2s
dotnet-deployment-85f6446977-hw24v 0/1 ContainerCreating 0 2s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 1/1 Running 0 3s
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 0/1 Terminating 0 34m
上述输出显示了滚动更新的执行过程。首先,系统会启动新容器,当新容器运行后,旧容器会被终止。
验证正在运行的应用
此时,应用已更新,可以处理用户请求。和以前一样,您可以使用代理访问该服务。
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
现在,使用 Cloud Shell 中的网页预览功能访问 Web 应用。
在 Web 预览生成的网址中添加以下内容:/api/v1/namespaces/default/services/dotnet-service:8080/proxy/。最终效果大致如下:
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
确认 Kubernetes Service 正在分配负载
多次刷新此网址,您会发现主机发生了变化,因为 Service 会在不同的 Pod 之间对请求进行负载均衡。将 Host 值与上面的 Pod 列表进行比较,可以看到所有 Pod 都在接收流量。
纵向扩容实例
在 Kubernetes 中扩缩应用非常简单。以下命令会将 Deployment 扩容到 6 个应用实例。
$ kubectl scale deployment dotnet-deployment --replicas 6
deployment.apps/dotnet-deployment scaled
您可以使用以下命令查看新 Pod 及其当前状态
kubectl get pod -w
请注意,刷新同一浏览器窗口后,流量现在会在所有新 Pod 之间进行均衡。
8. 恭喜!
在本实验中,您在开发环境中验证了一个 .NET Core 示例 Web 应用,随后使用 GKE 将其部署到了 Kubernetes。然后,我们修改了该应用,使其显示运行该应用的容器的主机名。然后,Kubernetes 部署更新到了新版本,应用也进行了扩容,以演示负载如何在更多实例之间分配。
如需详细了解 .NET 和 Kubernetes,请参阅以下教程。这些实验以本实验所学内容为基础,介绍了 Istio 服务网格,以实现更复杂的路由和弹性模式。
9. 清理
为避免产生不必要的费用,请使用以下命令删除在本实验中创建的集群和容器映像。
gcloud container clusters delete dotnet-cluster --zone ${DEFAULT_ZONE}
gcloud container images delete gcr.io/${PROJECT_ID}/aspnetapp:alpine