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

2022.06.04
2024.03.24
Kubernetes
DeploymeServiceカナリアデプロイ

本ページはAmazonアフィリエイトのリンクを含みます。

はじめに

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

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

unknown link

カナリアデプロイとは

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

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

実際に試してみる

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

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

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

作成するマニフェスト

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

1.
2├── configmap-v1.yml # v1のhtml用
3├── configmap-v2.yml # v2のhtml用
4├── deployment-v1.yml # v1のDeployment
5├── deployment-v2.yml # v2のDeployment
6└── service.yml # アプリのService

v1アプリのデプロイ

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

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

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

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: index-html-v1
5data:
6  index.html: |-
7    <!DOCTYPE html>
8    <html lang="ja">
9    <head>
10      <meta charset="UTF-8">
11    </head>
12    <body style="background-color: blue">
13      <h2>
14        v1 app.
15      </h2>
16    </body>
17    </html>

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

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

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: myapp-v1
5spec:
6  replicas: 3
7  selector:
8    matchLabels:
9      app: myapp
10  template:
11    metadata:
12      labels:
13        app: myapp # アプリ共通のラベル
14        version: v1 # バージョンを表すラベル
15    spec:
16      containers:
17      - name: web
18        image: nginx
19        ports:
20          - containerPort: 80
21        volumeMounts:
22        - name: html-volume
23          mountPath: /usr/share/nginx/html
24      volumes:
25        - name: html-volume
26          configMap:
27            name: index-html-v1
28

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

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

1apiVersion: v1
2kind: Service
3metadata:
4  name: myapp-service
5spec:
6  ports:
7  - name: http
8    port: 80
9  selector:
10    app: myapp # アプリ共通のラベル
11  type: NodePort

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

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

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

1❯ kubectl get pod,svc,cm
2NAME                           READY   STATUS    RESTARTS   AGE
3pod/myapp-v1-556b75687-65ckg   1/1     Running   0          11s
4pod/myapp-v1-556b75687-6mf28   1/1     Running   0          11s
5pod/myapp-v1-556b75687-h8s26   1/1     Running   0          11s
6
7NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
8service/myapp-service   NodePort    10.100.240.251   <none>        80:32207/TCP   11s
9
10NAME                         DATA   AGE
11configmap/index-html-v1      1      11s

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

1curl localhost:32207
2<!DOCTYPE html>
3<html lang="ja">
4<head>
5  <meta charset="UTF-8">
6</head>
7<body style="background-color: blue">
8  <h2>
9    v1 app.
10  </h2>
11</body>
12</html>

v2アプリのデプロイ

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

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

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: index-html-v2
5data:
6  index.html: |-
7    <!DOCTYPE html>
8    <html lang="ja">
9    <head>
10      <meta charset="UTF-8">
11    </head>
12    <body style="background-color: green">
13      <h2>
14        v2 app.
15      </h2>
16    </body>
17    </html>

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

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

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: myapp-v2
5spec:
6  replicas: 1 # 最初はレプリカ数を少なく
7  selector:
8    matchLabels:
9      app: myapp
10  template:
11    metadata:
12      labels:
13        app: myapp # アプリ共通のラベル
14        version: v2 # バージョンを表すラベル
15    spec:
16      containers:
17      - name: web
18        image: nginx
19        ports:
20          - containerPort: 80
21        volumeMounts:
22        - name: html-volume
23          mountPath: /usr/share/nginx/html
24      volumes:
25        - name: html-volume
26          configMap:
27            name: index-html-v2

ConfigMapとDeploymentを作成します。

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

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

1❯ kubectl get pod,svc,cm
2NAME                            READY   STATUS    RESTARTS   AGE
3pod/myapp-v1-556b75687-65ckg    1/1     Running   0          2m39s
4pod/myapp-v1-556b75687-6mf28    1/1     Running   0          2m39s
5pod/myapp-v1-556b75687-h8s26    1/1     Running   0          2m39s
6pod/myapp-v2-6d55d79f9f-2hg7q   1/1     Running   0          9s
7
8NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
9service/myapp-service   NodePort    10.100.240.251   <none>        80:32207/TCP   2m39s
10
11NAME                         DATA   AGE
12configmap/index-html-v1      1      2m39s
13configmap/index-html-v2      1      9s

動作確認

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

1❯ curl localhost:32207
2<!DOCTYPE html>
3<html lang="ja">
4<head>
5  <meta charset="UTF-8">
6</head>
7<body style="background-color: blue">
8  <h2>
9    v1 app.
10  </h2>
11</body>
12</html>
13
14❯ curl localhost:32207
15<!DOCTYPE html>
16<html lang="ja">
17<head>
18  <meta charset="UTF-8">
19</head>
20<body style="background-color: blue">
21  <h2>
22    v1 app.
23  </h2>
24</body>
25</html>
26
27❯ curl localhost:32207
28<!DOCTYPE html>
29<html lang="ja">
30<head>
31  <meta charset="UTF-8">
32</head>
33<body style="background-color: blue">
34  <h2>
35    v1 app.
36  </h2>
37</body>
38</html>
39
40❯ curl localhost:32207
41<!DOCTYPE html>
42<html lang="ja">
43<head>
44  <meta charset="UTF-8">
45</head>
46<body style="background-color: green">
47  <h2>
48    v2 app.
49  </h2>
50</body>
51</html>

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

バージョンの入れ替え

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

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

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

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

1❯ kubectl get deploy,pod
2NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
3deployment.apps/myapp-v1   0/0     0            0           9m49s
4deployment.apps/myapp-v2   3/3     3            3           7m19s
5
6NAME                            READY   STATUS    RESTARTS   AGE
7pod/myapp-v2-6d55d79f9f-2hg7q   1/1     Running   0          7m19s
8pod/myapp-v2-6d55d79f9f-blrv5   1/1     Running   0          64s
9pod/myapp-v2-6d55d79f9f-xp544   1/1     Running   0          64s

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

1curl localhost:32207
2<!DOCTYPE html>
3<html lang="ja">
4<head>
5  <meta charset="UTF-8">
6</head>
7<body style="background-color: green">
8  <h2>
9    v2 app.
10  </h2>
11</body>
12</html>
13
14curl localhost:32207
15<!DOCTYPE html>
16<html lang="ja">
17<head>
18  <meta charset="UTF-8">
19</head>
20<body style="background-color: green">
21  <h2>
22    v2 app.
23  </h2>
24</body>
25</html>%

v1アプリの削除

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

1kubectl delete deploy myapp-v1

参考

Support

\ この記事が役に立ったと思ったら、サポートお願いします! /

buy me a coffee
Share

Profile

author

Masa

都内のIT企業で働くエンジニア
自分が学んだことをブログでわかりやすく発信していきながらスキルアップを目指していきます!

buy me a coffee