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 をご覧ください。

このラボでは、Google Cloud Console から使用できる Cloud Shell で実行されている Docker を使用します。Cloud Shell には、gcloud や Docker など、多くの便利なツールが事前に構成されています。Cloud Shell にアクセスする方法は次のとおりです。右上にある Cloud Shell アイコンをクリックして、コンソール ウィンドウの下部ペインに表示します。

Cloud Shell

GKE クラスタの代替構成オプション(省略可)

このラボには Kubernetes クラスタが必要です。次のセクションでは、シンプルな構成の GKE クラスタを作成します。このセクションでは、GKE を使用して Kubernetes クラスタを構築するときに使用する代替構成オプションを提供する gcloud コマンドの例を示します。たとえば、次のコマンドを使用すると、さまざまなマシンタイプ、ゾーン、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 クラスタを作成する

このラボでは .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 は、GKE ワークロードが Google Cloud サービスにアクセスできるようにワークロード ID を確立します

クラスタの構築中は、次の内容が表示されます。

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 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

これはウェブアプリであるため、ウェブブラウザで表示して検証する必要があります。次のセクションでは、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 サンプル アプリケーションを表示する

前の手順で起動したサンプルアプリは、ウェブ プレビューを起動して提供された URL を読み込むことで確認できます。次のようになります。

.NET アプリ V1 のスクリーンショット

5. Kubernetes にデプロイする

YAML ファイルをビルドして適用する

次の手順では、Deployment と Service の 2 つの Kubernetes リソースを記述する 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 リソースを確認し、新しいデプロイがあることを確認します。

$ 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 は 3 つのインスタンスが必要であることを示しています。次のコマンドを実行すると、3 つのインスタンスが表示されます。-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 がデプロイに追加されると、その Service で処理されるリクエストに、結果として得られた 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
...

次のコマンドを使用すると、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 アドレスに解決できます。Service に送信されたリクエストは、すべてのインスタンス(Pod)間でロードバランスされます。上記の Endpoints 値は、このサービスで現在使用可能な Pod の IP を示しています。これらを、上記で出力された Pod の IP と比較します。

実行中のアプリを確認する

この時点で、アプリケーションは稼働しており、ユーザー リクエストに対応する準備が整っています。アクセスするには、プロキシを使用します。次のコマンドは、ポート 8080 でリクエストを受け取り、kubernetes クラスタに渡すローカル プロキシを作成します。

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

次に、Cloud Shell のウェブ プレビューを使用してウェブ アプリケーションにアクセスします。

ウェブ プレビューで生成された URL に /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 で使用できるようにイメージにタグを付けて push する

Kubernetes がイメージを pull できるように、イメージにタグを付けて push する必要があります。まず、コンテナ イメージを一覧表示して、目的のイメージを特定します。

$ docker image list
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
aspnetapp                                          alpine              95b4267bb6d0        6 days ago          110MB

次に、そのイメージにタグを付けて Google Container Registry に push します。上記の画像 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 に push したコンテナ イメージを参照するように変更する必要があります。

        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 リソースは変更されていないことがわかります。更新された Pod は、以前と同様に kubectl get 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 のウェブ プレビューを使用してウェブ アプリケーションにアクセスします。

ウェブ プレビューで生成された URL に /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 がロードを分散していることを確認する

この URL を数回更新すると、リクエストが Service によって異なる Pod 間でロードバランスされるため、ホストが変化します。ホストの値を上記の 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 サンプル ウェブ アプリケーションを開発環境で検証し、その後 GKE を使用して Kubernetes にデプロイしました。次に、実行中のコンテナのホスト名を表示するようにアプリを変更しました。その後、Kubernetes Deployment が新しいバージョンに更新され、アプリがスケールアップされて、追加のインスタンスに負荷が分散される様子が示されました。

.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