デプロイメントをもう少し深く#

デプロイメントリソースは、ワークロードの中で利用回数がかなり多めのものです。 そのためもう少し深く観ておく必要も出てきます。

デプロイメントの定義#

デプロイメントは、以下のAPIで定義されています。

このマニフェストも、metadataの扱いは基本的に同じです。 spec部分が少しわかりにくいと思いますので、そこを見ていきましょう。

Listing 27 deploy1.ymlのセレクタ部分(6〜8行目)#
  # レプリカ数は初期値1なので実は書かなくてもOK(明示)
  replicas: 1
  selector:

セレクタ部(selector)は、デプロイメントが管理対象とするPodの選択方法を指示するために使うものとなっています。 ここでは特定のラベルが付与されている(matchlabels)ことを条件としており、該当するラベルのKey-Valueを辞書として記述しています。 今回のケースであれば、appキーに対する値frontendが付与されたものということになります。

そのうえで、起動させたいポッドの定義を template以下に記載します。 この部分は、実はポッドの定義と同じになっています。

Listing 28 deploy1.ymlのPod定義テンプレート(9〜22行目)#
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: nginx
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"

このテンプレート部分で、メタデータとしてラベル(appfrontend)としているため、起動したPodにはそのラベルが付加され、それをデプロイメントが見つけて管理するということになるのです。 冗長さを感じるかもしれませんが、テンプレート側とセレクタ側で「ひっかける」ための条件をきちんと同じになるよう設定しましょう。 もちろんこの方法以外のセレクタ設定は存在しますが、今は概念としてセレクタを条件として合致するポッドをハイかとするというルールを意識しておけば十分です。

バージョン管理#

実際にアプリケーションを書いていると、途中でバージョンアップということになります。 仮にバージョンアップまで行かなくても、バグ取り等のマイナーフィックスも出てくると思います(ここではこれもバージョンとして扱います)。 そのため、新しいバージョンに上げることも出てくると思います。

デプロイメントリソースはこの部分を管理する能力を持っています。

そこで、ちょっとこんなWebサーバー(nginxベース)考えてみます。

Listing 29 バージョンチェックのWebサイト(v1)#
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vcheck
spec:
  selector:
    matchLabels:
      app: vcheck
  template:
    metadata:
      labels:
        app: vcheck
    spec:
      containers:
      - name: vcheck
        image: densukest/vcheck:v1
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

こちらを適用し、ポートフォワードで実際にアクセスしてみます。

PS> kubectl apply -f vcheck-v1.yml
# 別端末でポートフォワード
PS> kubectl port-forward deploy/vcheck 8080:80

ポッドの起動後に実際に http://127.0.0.1:8080/ にアクセスすると、v1である旨が得られます。

_images/vcheck-v1.png

Fig. 7 バージョンチェックの出力(v1)#

ところがコードの開発が進み、提供バージョンがv2に変わったとしましょう。 このとき、提供中のサービスを差し替えるために、イメージ名を”v2”にしてみましょう。

Listing 30 提供バージョンをv1からv2へ(差分)#
--- /work/codes/vcheck-v1.yml
+++ /work/codes/vcheck-v2.yml
@@ -13,7 +13,7 @@
     spec:
       containers:
       - name: vcheck
-        image: densukest/vcheck:v1
+        image: densukest/vcheck:v2
         resources:
           limits:
             memory: "128Mi"

変更したマニフェストを適用すると、従来のポッドはそのままで新しいポッド(vⅡベース)が起動し、起動後に差し替わります。 端末をもうひとつ立ち上げておいて、 kubectl get pods -w して追いかけるとわかると思います。

Listing 31 v2への差し替え操作#
PS> kubectl apply -f vcheck-v2.yml
deployment.apps/vcheck configured
_images/replace-v1-v2.png

Fig. 8 v1からv2への差し替えの様子#

ポートフォワードは接続してたポッドが終了したため停止していると思います。 再度起動してからブラウザ上でリロードすると、v2に変更されていることがわかります。

_images/vcheck-v2.png

Fig. 9 バージョンチェックの出力(v2に変更)#

Caution

ポートフォワードが終了してしまったのは 対象となるポッドが消滅したから ですが、 これはデプロイメントに対してポートフォワードをしたことにより直接ポッドと繋いだからです。

この機能が特に効果を発揮するのは、レプリケーションを併用した場合で、バックエンドのポッドが4つあるのであれば、最終的に全てが置き換わるようにポッドを追加、差し替え(+削除)を行ってくれます。

v1          v1        v1                  v2
v1     →    v1    →   v1     →  ...  →    v2
v1          v1        v1                  v2
v1          v1        v2                  v2
            v2

また、ロールアウト機能(kubectl rollout)を用いることでうまく行かなかったときの巻き戻し(ロールバック・アンドゥ)を行うことも可能です。

レプリカセット#

続いてレプリカセットです。実はデプロイメントはあくまでセレクタでひっかけたポッドを見張るにすぎません。 ポッドの増減自体を管理するのは、こちらのレプリカセットとなります。 レプリカセットは、テンプレートで設定されたポッドが指定された数だけ存在するかを管理し、何らかの要因でポッドが減った場合には新たにポッドを生成しますし、要求数が減ったときに適当にポッドを間引きます。

実はデプロイメントで書いていたPodテンプレートはこのレプリカセットが利用するためのものだったりします。 関係性をツリー状にすれば、ざっくりこのようなかたちです。

_images/deployment.drawio.png

Fig. 10 デプロイ・レプリカセット・ポッドの関係図(レプリカ数3での例)#

レプリケーション(スケーリング)#

ダッシュボードを使ったデプロイメントを試した際に、スケーリングを試しましたが、マニフェスト上でも可能です。 実際の所はスケーリングの値(いくつ同じ内容のPodを生成するか)です。

実際にスケーリングのパラメータを追加してみます。記述する位置が、セレクタ指示などと同じ階層なので注意してください。

Listing 32 レプリカ数2に設定#
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
      # 以下略…

これを再適用(kubectl apply -f deploy1.yml)すると、ポッド数が2になります。

PS> kubectl apply -f deploy1.yml
deployment.apps/frontend configured
_images/deployment-replica2.png

Fig. 11 レプリカ数2に設定したときのダッシュボード(ポッドが増えている)#

どうしてこんなことをする必要があるかですが、いわゆるスケールアウトを実現するためのものです。 1つのサーバーでは、同時に引き受けられるのは実際1つだけです(すごいスピードで処理しているから並列で処理しているように見えますが違います)。 だったら複数のサーバーを待ち受けにして、両方で仕事できればいいよねというかなり安直な考え方だったりします。

こうやって複数のポッドが並行して動けるようになっているのですが、ポッドそのものはテンプレートから生成されているため、どちらも同じラベルを持つことになります。そのためデプロイメントは複数のポッドを検出するようになり、配下として管理するわけですね。

Caution

ちょっと待って! 誰が2つに分かれたサーバーにリクエストを分配するの? となると思います。この部分については今は横に置いておきましょう(モヤッとしますが)。