【Kubernetes】DeploymentとServiceでカナリアデプロイやってみる

スポンサーリンク

はじめに

Kubernetesのデプロイ戦略として、DeploymentとServiceだけでカナリアデプロイを動かしてみます。

Kubernetesでの代表的なデプロイ戦略については、下記でざっくりまとめています。

【Kubernetes】デプロイ戦略についてざっくり理解する
はじめにKubernetesにおけるデプロイ戦略をざっくりまとめてみたいと思います。デプロイ戦略ここで紹介するデプロイ戦略は下記になります。ローリングアップデートRecreateブルーグリーンデプロイレインボーデプロイカナリアデプロイKub...

カナリアデプロイとは

カナリアデプロイは、少数の新しいバージョンをデプロイし、問題がなければ徐々に新しいバージョンを増やすデプロイ方法です。

メリットデメリット
・ダウンタイムがゼロ
・すぐに旧バージョンに戻せる
・新しいバージョンを本番環境で試せる
・様子を見ながら新しいバージョンに移行できる
・ロールアウトまで時間がかかる
・モニタリングの手間がかかる

実際に試してみる

実際にカナリアデプロイをやってみます。

カナリアデプロイを実現する方法はいくつかあると思いますが、今回はDeploymentとServiceで実装します。

現行のバージョンと新しいバージョンの共有のラベルに対して、Serviceからトラフィックを流し、新しいバージョンのレプリカ数を増やしていくことで実装します。

作成するマニフェスト

今回、作成するマニフェストは下記の通りです。

.
├── configmap-v1.yml # v1のhtml用
├── configmap-v2.yml # v2のhtml用
├── deployment-v1.yml # v1のDeployment
├── deployment-v2.yml # v2のDeployment
└── service.yml # アプリのService

v1アプリのデプロイ

まずは、v1のアプリをデプロイします。

nginxで表示するHTMLファイルを、ConfigMapを使ってマウントします。

configmap-v1.ymlは下記の通りです。

apiVersion: v1
kind: ConfigMap
metadata:
  name: index-html-v1
data:
  index.html: |-
    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
    </head>
    <body style="background-color: blue">
      <h2>
        v1 app.
      </h2>
    </body>
    </html>

v1アプリは、v1用のConfigMapをマウントしたものになります。

deployment-v1.ymlは、下記の通りです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp # アプリ共通のラベル
        version: v1 # バージョンを表すラベル
    spec:
      containers:
      - name: web
        image: nginx
        ports:
          - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      volumes:
        - name: html-volume
          configMap:
            name: index-html-v1

アプリのServiceは、共通のラベルをセレクタとして使用します。バージョンのラベルはセレクタとして使用しません

service.ymlは下記の通りです。

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: myapp # アプリ共通のラベル
  type: NodePort

ConfigMap、Deployment、Serviceそれぞれ作成します。

kubectl apply -f configmap-v1.yml,deployment-v1.yml,service.yml

それぞれ作成されたのが確認できます。

❯ kubectl get pod,svc,cm
NAME                           READY   STATUS    RESTARTS   AGE
pod/myapp-v1-556b75687-65ckg   1/1     Running   0          11s
pod/myapp-v1-556b75687-6mf28   1/1     Running   0          11s
pod/myapp-v1-556b75687-h8s26   1/1     Running   0          11s

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/myapp-service   NodePort    10.100.240.251   <none>        80:32207/TCP   11s

NAME                         DATA   AGE
configmap/index-html-v1      1      11s

curlでNodePortを指定して確認してみると、v1のアプリが動作していることが確認できます。何度実行してもv1のアプリからしかレスポンスは返ってきません。

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: blue">
  <h2>
    v1 app.
  </h2>
</body>
</html>

v2アプリのデプロイ

v2のアプリの準備をしていきます。

v2のConfigMapのマニフェストconfigmap-v1.ymlは下記の通りです。

apiVersion: v1
kind: ConfigMap
metadata:
  name: index-html-v2
data:
  index.html: |-
    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
    </head>
    <body style="background-color: green">
      <h2>
        v2 app.
      </h2>
    </body>
    </html>

v2のDeploymentのマニフェストdeployment-v2.yamlは下記の通りです。

v2のアプリはv2のConfigMapをマウントしており、レプリカ数は少なく、v2を表すラベルが追加されています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-v2
spec:
  replicas: 1 # 最初はレプリカ数を少なく
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp # アプリ共通のラベル
        version: v2 # バージョンを表すラベル
    spec:
      containers:
      - name: web
        image: nginx
        ports:
          - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      volumes:
        - name: html-volume
          configMap:
            name: index-html-v2

ConfigMapとDeploymentを作成します。

kubectl apply -f configmap-v2.yml,deployment-v2.yml

現行のバージョン(v1)のPodが3つ、新しいバージョン(v2)のPodが1つで、同じServiceからトラフィックが流れてくるようになりました。

❯ kubectl get pod,svc,cm
NAME                            READY   STATUS    RESTARTS   AGE
pod/myapp-v1-556b75687-65ckg    1/1     Running   0          2m39s
pod/myapp-v1-556b75687-6mf28    1/1     Running   0          2m39s
pod/myapp-v1-556b75687-h8s26    1/1     Running   0          2m39s
pod/myapp-v2-6d55d79f9f-2hg7q   1/1     Running   0          9s

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/myapp-service   NodePort    10.100.240.251   <none>        80:32207/TCP   2m39s

NAME                         DATA   AGE
configmap/index-html-v1      1      2m39s
configmap/index-html-v2      1      9s

動作確認

curlで動作確認してみると、数回に一度ぐらいの頻度でv2アプリからのレスポンスが返ってきます。

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: blue">
  <h2>
    v1 app.
  </h2>
</body>
</html>

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: blue">
  <h2>
    v1 app.
  </h2>
</body>
</html>

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: blue">
  <h2>
    v1 app.
  </h2>
</body>
</html>

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: green">
  <h2>
    v2 app.
  </h2>
</body>
</html>

この状況で新しいバージョンの動作を確認します。

バージョンの入れ替え

少ないレプリカ数で新しいバージョンの動作に自信が持てたら、レプリカ数を増やして、新しいバージョンのみにしていきます。

徐々に増やしていくこともできますが、今回は一気にレプリカ数を逆転させていきます。

kubectl scale --replicas=3 deploy myapp-v2
kubectl scale --replicas=0 deploy myapp-v1

新しいバージョンのみになりました。

❯ kubectl get deploy,pod
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/myapp-v1   0/0     0            0           9m49s
deployment.apps/myapp-v2   3/3     3            3           7m19s

NAME                            READY   STATUS    RESTARTS   AGE
pod/myapp-v2-6d55d79f9f-2hg7q   1/1     Running   0          7m19s
pod/myapp-v2-6d55d79f9f-blrv5   1/1     Running   0          64s
pod/myapp-v2-6d55d79f9f-xp544   1/1     Running   0          64s

curlで動作確認しても、v1からのレスポンスは返ってこなくなっています。

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: green">
  <h2>
    v2 app.
  </h2>
</body>
</html>

❯ curl localhost:32207
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
</head>
<body style="background-color: green">
  <h2>
    v2 app.
  </h2>
</body>
</html>%

v1アプリの削除

最後に、古いバージョン(v1)のアプリはもう必要ないので、削除して完了です。

kubectl delete deploy myapp-v1

参考

タイトルとURLをコピーしました