えいのうにっき

a-knowの日記です

Kubernetes の Pod / ReplicaSet / Deployment について、ようやく整理できた (入門k8s 読書メモ)

入門 Kubernetes の読書メモ、第4弾。

入門 Kubernetes

入門 Kubernetes

今までにいくつかの k8s チュートリアルをやってきて、Pod や ReplicaSet、Deployment についても言われるがままに扱ってきた(そもそもこれが良くない......)けど、その関係性がようやく整理できた気がしている。この本を手にとったときの一番の問題意識であるところでもあったので、よかった。

Pod

Pod とは

  • 複数のコンテナを1つのアトミックな単位にまとめるためのもの
  • 同じ実行環境上で動くアプリケーションコンテナとストレージボリュームの集まりのこと
  • Kubernetesクラスタ上での最小のデプロイ単位
    • コンテナ単位ではない
  • Pod とはクジラの群のこと

Podの特徴

  • 1つのPod内のコンテナは、すべて同じマシン上に配置される
  • Pod内の各コンテナはそれぞれの cgroups 上で動作するが、Linuxネームスペースの多くを共有もしている
  • 同じPod内のアプリケーションの特徴
    • 同じIPアドレスとポートを使用する
    • 同じホスト名(UTSネームスペース)を持つ
    • System VIPC や POSIX メッセージキューを経由したネイティブなプロセス間通信チャネル(IPCネームスペース)を使って通信可能
    • 別のPod内アプリケーションからは分離されている
  • 「同じPodにまとめるべきか否か」を考えるとき
    • 対象プロセス(コンテナ)のスケール戦略の違いについて考える。
    • 「このコンテナは、それぞれ違うマシンに配置されても正常に動作するかどうか」と考えてみる
      • 「動作しない」なら同一Podで良い場合が多い
      • 「動作する」なら、Podを分ける方が良い場合が多い

Kubernetes クラスタ上に Pod が生成されるまで

  1. Podの設定をPodマニフェストに記述する
    • YAML, JSON 等
  2. Kubernetes API サーバに Pod マニフェストを送信する。
    • kubectl apply コマンドなど
  3. Kubernetes API サーバが Pod マニフェストを受け入れ、永続化ストレージ(etcd)に保存する
  4. Kubernetes スケジューラが、ノードへ未割り当ての Pod があることを見つけたら、Pod マニフェストに書かれたリソースなどの制約を満たしたノードに対して、Pod を割り当てる
    • 複数 Pod を割り当てる必要があるときには、スケジューラはできるだけ違うノードに・分散して割り当てようとする
      • ノードの障害などに対する信頼性の観点から。
  5. Podがノードに割り当てられると、マシン上の kubelet デーモンが Pod に対応したコンテナを作成し、Pod マニフェストに定義されたヘルスチェックを実行する。
  6. ノードに割り当てられた Pod は、明示的に削除したり割り当て直したりしない限り、同じノード上で動き続ける。その監視も kubelet デーモンプロセスがおこなう。

Pod マニフェスト

以下は、Podマニフェストの一例。

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containres:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP
  • Podとそのラベルについて書かれた metadata セクション
  • Volume について書かれた spec セクション
  • Pod内で動くコンテナの一覧

から構成されている。

ヘルスチェック

  • Pod起動時、稼働し始めたコンテナプロセスが正常かどうか・再起動すべきかどうかを確認する必要がある
    • そのことを確認するためのヘルスチェックの定義を Liveness Probe によりおこなうことができる。
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containres:
    - image: gcr.io/kuar-demo/kurad-amd64:1
      name: kuard
      livenessProbe:
        httpGet:                  # HTTP Get リクエストを送ることでヘルスチェックを行う。この他に tcpSocket / exec(コンテナ内で任意のスクリプト等を実行)がある
          path: /healthy          # HTTP Get リクエストを送る先のエンドポイント
          port: 8080
        initialDelaySeconds: 1    # Pod 内の全コンテナが生成されて5秒経過するまではリクエストを送らない
        periodSeconds: 10         # 10秒おきに監視をおこなう
        failuerThreshold: 3       # 監視に3回失敗したら、コンテナは障害を起こしていると判断され再起動される
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP
  • Liveness Probe では「起動しているかどうか」という観点での監視。
  • それに加えて、「応答できるかどうか」という観点での監視を定義できる「Readiness Probe」もある。
  • この2つを組み合わせることで、常にクラスタ内のコンテナが正常に動作していることを保証できる。

リソース管理

Kubernetes では、「リソース要求」と「リソース制限」の2つのリソース指標を指定できる。

リソース要求

  • アプリケーションを動作させるのに必要最低限のリソースを指定するためのもの
    • Kubernetes は、Podかrあ要求されたリソースが使用可能なことを保証する。
  • リソースの要求は、Podごとではなくコンテナごとにおこなう。
    • CPUに対して要求するリソースがコンテナごとに大きく異なるため。
  • CPUのリソース要求は、Linuxカーネルの cpu-shares 機能を使って実装されている。

リソース制限

  • Podの使用リソース量の上限を設定する。これを設定すると、コンテナのリソース使用量が制限を超えないよう、カーネルが調整をおこなう。
  • メモリ制限があるコンテナでそれを超えて使用すると、コンテナが終了される。

Pod と Volume

Podが削除されたりコンテナが再起動されると、コンテナ内ファイルシステム上のあらゆるデータも一緒に削除される。Volumeを使うことで、データの永続化をおこなうことができる。

Pod への Volume の設定方法

  • Pod に Volume を設定するには、マニフェストに2つのセクションを追加する必要がある
  • spec.volumes
    • Podマニフェスト内のコンテナからアクセスされる可能性のあるすべての Volume の一覧の配列。
    • すべてのコンテナは、Pod内で定義されたすべての Volume をマウントできる必要がある。
  • コンテナ定義内の volumeMounts
    • 特定のコンテナのどのパスに Volume をマウントするかの設定の配列。
      • 同じ Pod 内の別々のコンテナが、同じ volume を違うパスにマウントすることもできる。
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  volumes:
    - name: "kuard-data"
      hostPath:    # Podが配置されたノード(ワーカーノード)の任意の場所をコンテナにマウントする
        path: "/var/lib/kuard"
  # - name: "kuard-data"
    # emptyDir: {}    # emptyDir を使うことで、Podが停止するまでの間だけデータが保持されるようにすることもできる。マウント先はワーカーノード上に自動で作成される( `/var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/` )
  containres:
    - image: gcr.io/kuar-demo/kurad-amd64:1
      name: kuard
      volumeMounts:
        - mountPath: "/data"
          name: "kuard-data"
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP

Kubernetes Pod オブジェクトの特徴、機能

  • kubectl delete コマンドなどで Pod の削除をおこなった場合でも、すぐには削除されない。
    • 削除オペレーションを受けた Pod は Terminating という状態に以降する
    • デフォルトで30秒の、削除の猶予期間(grace period)が設けられている
    • Terminating 状態の Pod は、新しいリクエストは受け付けない。
      • この猶予期間により、処理中の可能性のあるリクエストを、Podが削除される前に終わらせることができ、信頼性を高めることができる
  • kubectl logs コマンドにより、ログを取得することができる
    • --previous フラグをつけることで、コンテナの1世代前のログを取得することが可能。

ReplicaSet

ReplicaSet とは

  • 指定したテンプレートに従った Pod が、常に正しい数だけ動いているようにする、クラスタ全体の Pod マネージャ。
    • Podのレプリカ群を1つのまとまりとして考えて管理するために利用できるもの
  • ReplicaSetで管理されたPodはノード障害やネットワーク分断などの障害時、自動的に他のノードに再割当てされる。

ReplicaSet の必要性

  • ひとつの Pod マニフェストから作成できるのは1つのPodのみであるが、実際の多くの場面では、冗長性・スケールなどを考慮し同じPodを複数稼働させたい場面が多い。
  • ReplicaSet から Pod を作成することで、自動フェイルオーバ機能を持った堅牢なアプリケーションを構築することができる
  • そのため、稼働させたい Pod の数にかかわらず、ReplicaSet を用いて Pod の管理をおこなうべき

調整ループ

  • Podのレプリカを管理する仕組みは、調整ループ(reconcillation loop)の一例。
  • 調整ループのベースにある概念、「望ましい状態(desire state)」と「現在の状態(observed state もしくは current state)」
    • 望ましい状態:宣言されたレプリカの数や複製する Pod の定義
    • 現在の状態:今現在、どんな Pod がいくつ動いているか。
  • 調整ループは連続して動き続け、システムの現在の状態を観察し、システムの現在の状態が望ましい状態に一致するようにアクションを起こす。
    • ゴール駆動型であり、自己回復システムである
  • ReplicaSetの調整ループは、1つのループで ReplicaSet のスケールアップもスケールダウンも、ノード障害や復活も扱える利点がある

Pod と ReplicaSet の関連

  • Pod と ReplicaSet の関係は疎結合になっている。
  • 疎結合にしておくことで、障害発生時の切り離しやデバッグなどもしやすくなる。
    • ある ReplicaSet 内のある Pod に不具合が生じた場合は、その Pod の Label だけを変更することで ReplicaSet(および Service)から Pod を切り離すことができる
    • その後 ReplicaSet は「調整ループ」により、Pod がなくなったと判断して新しいコピーを作成する
  • ReplicaSet コントローラが作った各 Pod は完全に同じものである。
    • ReplicaSet が作成した要素は交換可能であることになる

Pod から ReplicaSet の特定

  • ReplicaSet コントローラは、作成した各 Pod に Annotation を追加する。
    • キーは kubernetes.io/created-by
    • ReplicaSet が Pod を作成するときに付与される、これはユーザーが消そうと思えばいつでも消せるため、ベストエフォートなものであることに注意する
      • 【疑問】kubernetes.io/created-by をキーに、ユーザーが Annotation を追加することもできるはず?その点でもベストエフォート?

ReplicaSet に対応する Pod の集合の特定

  • ReplicaSet に管理されている Pod の集合の特定には、Label セレクタを用いる。
    • kubectl get pods -l app=hoge,version=2
    • これは、ReplicaSet が Pod の数を取得する際に実行するのと全く同じクエリとなる。
    • 【疑問】ReplicaSet 以外の手段によって同じ Label を持つような Pod が作成されていない限り、か?
      • 同じ Label を持つ Pod はその気になればいくらでも作れそうな気がする......
      • これもベストエフォート?(ベストエフォートだと困る気もするが)

ReplicaSet の定義

  • ReplicaSet も他のオブジェクトと同様、宣言的設定を記述することで定義する
    • metadata セクション
    • spec セクション
      • クラスタ内で同時に動いているべき Pod(レプリカ)の数
      • ReplicaSet の調整ループにより作成する Pod のテンプレート
    • 以下が ReplicaSet の最小限の定義例。
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: kuard
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: kuard
        version: "2"
    spec:
      containers:
        - name: kuard
          image: "gcr.io/kuar-demo/kuard-amd64:2"

Podテンプレート

  • ReplicaSet マニフェスト内、spec.template が該当。
  • Pod は、ReplicaSet の定義内に書かれた Pod テンプレートをもとに作成される。
  • ReplicaSet が管理下の Pod の監視に Label を用いていることは前述の通り。フィルタリングに使用する Label は、ReplicaSet の spec セクション(Podテンプレート内)に定義される。

ReplicaSet による Pod のスケール

Kubernetes 上の ReplicaSet オブジェクトの spec.replicas キーを更新することで、ReplicaSet が管理している Pod のスケールアップやスケールダウンが可能。スケールアップの際には、ReplicaSet で定義された Pod テンプレートが使われる。

命令的スケール

  • kubectl scale コマンドを使うことでスケールを変更できる。
  • これを実施した際には、宣言的設定の方の追随も必ずおこなうようにする。

宣言的スケール

  • マニフェスト内 replicas の数を変更し kubectl apply するだけ。
    • 「望ましい Pod の数が変更されたこと」「望ましい状態を実現するのに何らかの処理が必要なこと」を、ReplicaSet コントローラが検知する。

オートスケール

  • 「十分な数の Pod のレプリカがあればよい」というケースに対し、水平Podオートスケーリング(HPA)という仕組みで対応できる。
    • これに対し、ある Pod に必要なリソースを増やすことを垂直スケールと呼び、HPA とは明確に区別している(Kubernetes では未実装)
      • リソースの需要に応じてクラスタ内のマシンの数をスケールさせるクラスタスケールという仕組みもある
    • HPA を使うには、heapster という Pod がクラスタ内に存在している必要がある。
      • heapster : メトリクスを追跡し、HPA がスケーリングの判断をおこなうときに使用するメトリクスを取得する API を提供する
      • Kubernetes のほとんどの環境では、デフォルトで作られる。(Namespace : kube-system)
  • 設定のためのコマンド例としては kubectl autoscale rs kuard --min=2 --max=5 --cpu-percent=80
    • 水平 Pod オートスケーラと ReplicaSet との間に、明確な関連付けはない
    • そのため、オートスケールとレプリカ数指定を組み合わせて使うこともできてしまうが、ユーザー操作とオートスケーラによるレプリカ数の変更が重なったりすると想定外の動作を引き起こしたりする可能性があるため、実施しないようにする・

Deployment

Deployment とは

  • Deployment オブジェクトは、新しいバージョンのリリースを管理するための仕組み。
  • 前述の Pod や ReplicaSet は、変更(バージョンアップ)されるコンテナイメージを扱うために作られてはいない。
  • Deployment は、「デプロイされたアプリケーション」を、バージョンをまたいで表現する。信頼性の高いロールアウトとサービスのロールアウトの両立を図るための重要な機能。

Deployment の仕組み

  • ReplicaSet が Pod を管理するのと同様、Deployment は ReplicaSet を管理する。
    • その関係性も、Label と Label セレクタによるものとなる。

Deployment の作成

Deployment マニフェストの一例を以下に示す。この設定を用いて kubectl apply することで Deployment を作成できる。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  labels:
    run: nginx
    name: nginx
    namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      run: nginx
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
          run: nginx
    spec:
      containers:
        - image: nginx:1.7.12
          imagePullPolicy: Always
      dnsPolicy: ClusterFirst
      restartPolicy: Always

ReplicaSet の設定とよく似ている。Pod の設定に加えて、新しいバージョンのロールアウトをどのような方法でおこなうかを指示する strategy オブジェクトもある。

コンテナイメージの更新(ロールアウト)

例えば、上記一例のコンテナイメージを更新したいとする。そのときの手順としては、まずマニフェスト内のコンテナイメージ指定を更新する。

    spec:
      containers:
        - image: nginx:1.9.10
          imagePullPolicy: Always

そして、Deployment のテンプレートに Annotation も追加する(Deployment そのものへの追加ではない点に注意)。

spec:
  replicas: 2
  selector:
    matchLabels:
      run: nginx
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      annotations:
        kubernetes.io/change-cause: "Update nginx to 1.9.10"

追加する Annotation のキー kubernetes.io/change-cause はお決まり・ルールのようなものっぽい。仮にこの kubernetes.io/change-cause を持つ Annotation だけの更新でもロールアウトは起こってしまうため、例えばレプリカ数だけの変更などのときには kubernetes.io/change-cause は更新しないように気をつける。

この状態で kubectl apply するとロールアウトが始まる。kubectl rollout コマンドでその状況を確認することもできる。ロールバックされる場合に備えて、新旧の ReplicaSet は両方とも保持される。

ロールアウト履歴

  • kubectl rollout history でロールアウト履歴を確認することもできる。
$ kubectl rollout history deployments hoge-prod
deployments "hoge-prod"
REVISION  CHANGE-CAUSE
1         <none>
  • 履歴は、古い方から新しい方に向かって並ぶ。
  • ロールアウトされるたびに、一意なリビジョン番号がインクリメントされていく。
  • kubectl rollout undo コマンドでロールバックすることもできる
    • ロールバックは単に「ロールアウトが逆に実行される」だけなので、そのリビジョン番号がなくなり、最新のリビジョンとして登録されなおされるような形になる
      • 例:現在リビジョン 3 のときにロールバックした場合、以前リビジョン 2 だったものはリビジョン 4 となる
    • kubectl rollout undo は命令的オペレーションであり、緊急時以外にはおこなうべきではない。
      • ロールバックをおこなう望ましい方法は、「YAMLファイルを前の状態に戻す」「それを kubectl apply で適用する」こと。
  • ロールアウトのたびにDeployment オブジェクトがおこなっていることは、配下の ReplicaSet の望ましいレプリカ数を変更しているだけである。
$ kubectl get replicasets -o wide
NAME                     DESIRED   CURRENT   READY     AGE       CONTAINERS    IMAGES                                           SELECTOR
hoge-prod-1128242162     0         0         0         31d       hoge-prod   asia.gcr.io/hoge-prod/hoge-prod:1.0.0   pod-template-hash=1532469172,run=hoge-prod
hoge-prod-59768bf5c6     4         4         4         33d       hoge-prod   asia.gcr.io/hoge-prod/hoge-prod:2.0.0   pod-template-hash=1532469172,run=hoge-prod
  • Deployment の完全な履歴は、デフォルトでは Deployment オブジェクト自体が保持している
    • 時間の経過とともに肥大化していくので、履歴の最大サイズを指定しておくことが望ましい。
      • revisionHistoryLimit を使って残しておく世代数を指定可能

Deployment 戦略

Recreate 戦略

  • 管理している ReplicaSet を新しいイメージを使うように更新、一度関連している Pod をすべて停止させ、ReplicaSet の調整ループにより Pod の再作成をおこなう方法。
  • 高速かつシンプルであるが、ダウンタイムの発生を避けられない

RollingUpdate 戦略

  • すべての Pod がソフトウェアの新しいバージョンで動くまで、少数の Pod をまとめてアップデートしていくように動作する。
    • 一定時間内は、新旧両方のアプリケーションバージョンがユーザーからのリクエストを受け付け、処理することに注意する(ソフトウェア開発方針・設計に織り込む)。
  • Deployment によるローリングアップデートの振る舞いを調整するパラメータには、maxUnavailable と maxSurge の2つがある
  • maxUnavailable
    • ローリングアップデート中に使用不可能になってもいい Pod の最大数。
    • 「Pod の数の絶対値」「その割合(パーセンテージ)」のいずれかを指定する
    • ローリングアップデートをどのくらいの速さで進めるか、をチューニングするためのパラメータでもある
      • Recreate 戦略は、maxUnavailable を 100% にした RollingUpdate 戦略と実質的に同じ。
  • maxSurge
    • ロールアウトを実行する際にどのくらいの追加リソースを作れるか、を制御するパラメータ。
    • maxUnavailable と maxSurge との組み合わせで、キャパシティを100%から下げることなくロールアウトを実行させることも可能。
      • 例:レプリカ数10、maxUnavailable が 0、maxSurge を20%に設定しておくと、以下のような流れでロールアウトがおこなわれる
        • 新しいバージョンの Pod のレプリカを 2つ持つ Replicaset を作成する
        • 古いバージョンの ReplicaSet のレプリカサイズを 8 にする
        • 新しいバージョンの ReplicaSet のレプリカサイズを 4 にする
        • 古いバージョンの ReplicaSet のレプリカサイズを 6 にする
        • 以降、古いバージョンの ReplicaSet のレプリカサイズが 0 になるまで繰り返す
      • maxSurge を 100% に設定することで、Blue-Green Deployment にもなる

サービスの正常性を確保するロールアウト

  • 上述のような方法を用いて段階的にロールアウトをおこなう場合、Deployment コントローラは、次の Pod のアップデートをおこなう前に、Pod が起動済みであると報告してくるまで、常に待ち続ける。
    • コントローラは、Readiness probe の結果で Pod の状態を判断するため、Pod テンプレートにおいて Readiness probe を必ず指定しておく必要がある。
  • Pod が起動済み ≠ 正常に動作する、という場合もある。Deployment の minReadySeconds パラメータにて、ある程度の時間内の間正常な動作が確認できたかどうか、を指定することができる。
spec:
  minReadySeconds: 60
  • システムが待つ時間を制限するためのタイムアウト(一定時間以上経過するとロールアウトを失敗させる)も progressDeadlineSeconds で設定できる
    • デッドロックを起こしてしまったような場合に、タイムアウトがなければ Deployment コントローラはずっと待ち続けることになってしまう
    • Deployment 全体のタイムアウトではない点に注意。
spec:
  progressDeadlineSeconds: 60

入門 Kubernetes

入門 Kubernetes