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。Cloud Shell 可通过 Google Cloud 控制台使用,并预配置了许多实用工具,例如 gcloud 和 Docker。以下内容介绍了如何访问 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 集群
由于本实验会在 Kubernetes 上部署 .NET Core 应用,因此有必要创建一个集群。使用以下命令在 Google Cloud 中通过 GKE 创建新的 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建立工作负载身份,以便 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 应用,因此需要在 Web 浏览器中查看和验证。下一部分将介绍如何使用网页预览功能在 Cloud Shell 中执行此操作。
4. 使用“网页预览”从 Cloud Shell 访问服务
Cloud Shell 提供网页预览功能,让您可以使用浏览器与在 Cloud Shell 实例中运行的进程进行交互。
使用“网页预览”在 Cloud Shell 中查看应用
在 Cloud Shell 中,点击网页预览按钮,然后选择“在端口 8080 上预览”(或网页预览设置为使用的任何端口)。

系统会打开一个浏览器窗口,其中包含如下所示的地址:
https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0
使用网页预览功能查看 .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 资源,并确认新部署是否存在。
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dotnet-deployment 3/3 3 3 80s
接下来,我们来看看 ReplicaSet。上述部署应会创建一个 ReplicaSet。
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
dotnet-deployment-5c9d4cc4b9 3 3 3 111s
最后,我们来看看 Pod。部署表明应该有三个实例。以下命令应显示有三个实例。添加了 -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 资源。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dotnet-service ClusterIP 10.20.9.124 <none> 8080/TCP 2m50s
...
您可以使用以下命令查看有关服务的更多详细信息。
$ 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>
请注意,该服务的类型为 ClusterIP。这意味着,集群中的任何 Pod 都可以将服务名称 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 应用。
将以下内容添加到网页预览生成的网址中:/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 是否与上面显示的 Host 值一致。
$ 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。使用上面的映像 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 应用。
将以下内容添加到网页预览生成的网址中:/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 服务正在分发负载
多次刷新此网址,并注意当服务在不同 Pod 之间进行负载均衡时,主机也会随之变化。将“主机”值与上述 Pod 列表进行比较,以查看所有 Pod 是否都在接收流量。
纵向扩容实例
在 Kubernetes 中扩缩应用非常简单。以下命令会将部署扩缩到最多 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