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 が表示されます。

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 を構成する

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

$ 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 でプレビュー] を選択します。(または [Web Preview] に設定されている任意のポート)。

Cloud Shell

これにより、次のようなアドレスのブラウザ ウィンドウが開きます。

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

ウェブでプレビューを使用して .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 が上記の [ホスト] の値と一致していることを確認します。

$ 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 に負荷分散されるため、ホストが変わることがわかります。ホストの値と上記の 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