在 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。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 应用,因此有必要创建一个集群。使用以下命令在 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 上预览”(或网页预览设置为使用的任何端口)。

Cloud Shell

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

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

使用网页预览功能查看 .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 资源,并确认新部署是否存在。

$ 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

在 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