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 で検証され、新しいバージョンを使用するように Deployment が更新されます。次の図は、このラボの一連のアクティビティを示しています。

デモのシーケンス図

料金

このラボを記載どおりに実行すると、次のサービスに対して通常の費用が適用されます。

2. 設定と要件

前提条件

このラボを完了するには、Google Cloud アカウントとプロジェクトが必要です。新しいプロジェクトを作成する方法について詳しくは、こちらの Codelab をご覧ください。

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

Cloud Shell

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

このラボには Kubernetes クラスタが必要です。次のセクションでは、シンプルな構成の GKE クラスタを作成します。このセクションでは、GKE を使用して Kubernetes クラスタを構築する際に使用できる代替構成オプションを提供する gcloud コマンドをいくつか紹介します。たとえば、以下のコマンドを使用すると、さまざまなマシンタイプ、ゾーン、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 は、GKE ワークロードが Google Cloud サービスにアクセスできるように Workload Identity を確立します。

クラスタのビルド中は、次のように表示されます。

Creating cluster dotnet-cluster in us-central1-b... Cluster is being deployed...⠼

kubectl を構成する

kubectl CLI は、Kubernetes クラスタを操作する主な方法です。作成したばかりの新しいクラスタで kubectl を使用するには、クラスタに対して認証を行うように kubectl を構成する必要があります。これを行うには、次のコマンドを実行します。

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

ウェブ プレビューを使用して .NET サンプル アプリケーションを表示する

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

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

5. Kubernetes にデプロイする

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

次のステップでは、2 つの 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 を確認します。上記の Deployment によって作成された 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 が 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 のウェブ プレビューを使用してウェブ アプリケーションにアクセスします。

ウェブ プレビューで生成された 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

イメージにタグを付けて push し、Kubernetes で使用できるようにする

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

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

次に、そのイメージにタグを付けて Google Container Registry に push します。上記の 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 に 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 間でロード バランシングされるため、Host が変化することがわかります。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 サンプル ウェブ アプリケーションを開発環境で検証し、その後 GKE を使用して Kubernetes にデプロイしました。その後、アプリは、実行されているコンテナのホスト名を表示するように変更されました。次に、Kubernetes Deployment を新しいバージョンに更新し、アプリをスケールアップして、追加のインスタンスに負荷がどのように分散されるかを確認しました。

.NET と Kubernetes の詳細については、次のチュートリアルをご覧ください。これらのラボは、このラボで学んだことを基に、より高度なルーティングと復元パターンのために Istio サービス メッシュを導入します。

9. クリーンアップ

意図しない費用が発生しないように、次のコマンドを使用して、このラボで作成したクラスタとコンテナ イメージを削除します。

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