在 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 中验证更新后的应用,并更新部署以使用新版本。下图展示了本实验中的活动顺序:

演示序列图

费用

如果您完全按照说明运行此实验,则需要支付以下服务的正常费用

2. 设置和要求

前提条件

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

本实验使用在 Cloud Shell 中运行的 Docker,您可以通过 Google Cloud 控制台访问 Cloud Shell,其中预配置了许多实用工具,例如 gcloud 和 Docker。下面展示了如何访问 Cloud Shell。点击右上角的 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 集群

由于本实验会将 .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 上预览”(或网页预览设置为使用的任何端口)。

Cloud Shell

系统会打开一个浏览器窗口,其中包含如下地址:

https://8080-cs-754738286554-default.us-central1.cloudshell.dev/

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

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

.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。上述 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

在 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