在 Google Kubernetes Engine 中部署和更新 .NET Core 应用

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 中验证更新后的应用,并将部署更新为使用新版本。下图显示了本实验中的 activity 的顺序:

演示序列图

费用

如果您完全按照所写的方式运行本实验,则需要支付以下服务的正常费用

2. 设置和要求

前提条件

为完成本实验,您需要拥有 Google Cloud 账号和项目。如需详细了解如何创建新项目,请参阅此 Codelab

本实验使用在 Cloud Shell 中运行的 Docker。Cloud Shell 可通过 Google Cloud 控制台使用,并预先配置了许多实用工具,例如 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

如需查看 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 应用,因此必须创建集群。通过以下命令,使用 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 可建立工作负载身份,以便 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 运行命令会创建一个新容器,用于公开端口 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 上预览(或网络预览设置为使用的任何端口)。

Cloud Shell

这将打开一个浏览器窗口,地址如下所示:

https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0

查看使用网页预览的 .NET 示例应用

现在,您可以通过启动网页预览并加载提供的网址来查看上一步中启动的示例应用。代码应如下所示:

.NET 应用 V1 的屏幕截图

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应该有一个由上述部署创建的 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 添加到 Deployment 中,生成的 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。将这些信息与上面输出的 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

在 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。如果使用上面的图片 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 之间由 Service 进行负载均衡,主机会发生变化。将 Host 值与上述 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 Service Mesh,旨在实现更复杂的路由和弹性模式,以此进一步巩固您在本实验中学到的知识。

9. 清理

为避免产生意外费用,请使用以下命令删除在本实验中创建的集群和容器映像。

gcloud container clusters delete dotnet-cluster --zone ${DEFAULT_ZONE}
gcloud container images delete gcr.io/${PROJECT_ID}/aspnetapp:alpine