在 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 帳戶和專案,才能完成這個研究室。如需進一步瞭解如何建立新專案的詳細操作說明,請參閱這個程式碼研究室

本研究室將運用在 Cloud Shell 中執行的 Docker,該工具可透過 Google Cloud 控制台取得,並已預先設定許多實用工具,例如 gcloud 和 Docker。以下顯示如何存取 Cloud Shell。點選右上角的 Cloud Shell 圖示,控制台視窗的底部窗格就會顯示該圖示。

Cloud Shell

GKE 叢集的替代設定選項 (選用)

本研究室需要 Kubernetes 叢集。在下一節中,您將建立設定簡單的 GKE 叢集。本節介紹了一些 gcloud 指令,提供替代設定選項,以便在透過 GKE 建構 Kubernetes 叢集時使用。舉例來說,您可以使用下列指令來識別不同的機器類型、可用區,甚至是 GPU (加速器)。

  • 使用這個指令 gcloud compute machine-types list 列出機器類型
  • 使用這項指令「gcloud compute accelerator-types list」列出 GPU
  • 使用這個指令 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 會建立 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

確認網頁應用程式功能

中的範例網頁應用程式也可以透過 Cloud Shell 進行驗證。以下 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

由於這是網頁應用程式,因此需要在網路瀏覽器中查看及驗證。下一節將說明如何在 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 檔案

下一步需要說明兩個 Kubernetes 資源 (一個 Deployment 和一個 Service) 的 YAML 檔案。在 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>

查看服務資源

Kubernetes 中的 Service 資源是負載平衡器。端點是由 Pod 上的標籤決定。這樣一來,只要透過上述 kubectl scale deployment 作業將新的 Pod 加到 Deployment 中,產生的 Pod 就能立即用於該服務處理的要求。

下列指令應會顯示服務資源。

$ 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 都能將服務名稱 dotnet-service 解析為 IP 位址。傳送至服務的要求將在所有執行個體 (Pod) 之間進行負載平衡。上述 Endpoints 值顯示了這項服務目前可用的 Pod IP。請將這些內容與上方輸出的 Pod IP 進行比較。

驗證執行中的應用程式

此時應用程式已上線,已準備好回應使用者要求。請使用 Proxy 存取,下列指令會建立本機 Proxy,用來接受通訊埠 8080 上的要求,並傳送至 Kubernetes 叢集。

$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080

現在使用 Cloud Shell 中的網頁預覽功能來存取網頁應用程式。

將下列程式碼新增至網頁預覽產生的網址:/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

和先前一樣,您可以透過網頁預覽功能存取這個應用程式。這次應該會顯示主機參數,如下所示:

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

上述輸出內容會顯示當下發生的滾動式更新。首先,新的容器會啟動,而舊容器執行時則會終止。

驗證執行中的應用程式

此時應用程式已更新完成,已準備好回應使用者要求。和之前一樣,您可以透過 Proxy 存取。

$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080

現在使用 Cloud Shell 中的網頁預覽功能來存取網頁應用程式。

將下列程式碼新增至網頁預覽產生的網址:/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 範例應用程式已在開發人員環境中通過驗證,隨後使用 GKE 部署至 Kubernetes。接著,應用程式已經過修改,以顯示其執行所在容器的主機名稱。接著,Kubernetes Deployment 更新至新版本,並向上擴充應用程式,讓 Kubernetes 瞭解負載分配至其他執行個體的方式。

如要進一步瞭解 .NET 和 Kubernetes,請參考這些教學課程。這些架構奠基於本研究室中學到的知識,透過介紹 Istio 服務網格,以及更精細的轉送和彈性模式。

9. 清除所用資源

請使用下列指令,刪除在這個研究室中建立的叢集和容器映像檔,以免產生非預期的費用。

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