はじめに
前回は、Podを実際に動かすコンピューターであるNodeについて学びました。
ここまでの関係は、次のようになります。
Node
└── Pod
└── Container
しかし、実際の運用ではPodを1つずつ直接作成し、手作業で管理することはあまりありません。
Podが停止したときに作り直したり、アクセス増加に合わせて個数を増やしたり、新しいバージョンへ安全に入れ替えたりする仕組みが必要だからです。
そこで登場するのが Deployment(デプロイメント) です。
この記事では、次の内容を整理します。
- Deploymentとは何か
- Podを直接作成する場合との違い
- Deployment・ReplicaSet・Podの関係
- Podの個数が自動的に維持される仕組み
- Podを増減する方法
- ローリングアップデートとロールバック
- DeploymentのYAMLの読み方
- DeploymentとServiceの役割の違い
結論
Deploymentとは、どのようなPodを何個動かし、どのように更新するかを管理するKubernetesのリソースです。
たとえば、Rails Podを3個動かしたい場合は、Deploymentに次のような希望状態を定義します。
Deployment
「Rails Podを3個動かしたい」
↓
現在の状態を確認
↓
Rails Podを3個維持する
1個のPodが停止して2個になると、Deploymentの管理下では新しいPodが作られ、再び3個へ戻ります。
希望する状態:3個
現在の状態 :2個
↓
新しいPodを1個作成
↓
現在の状態 :3個
さらに、Deploymentは新しいコンテナイメージへのローリングアップデートや、問題発生時のロールバックも管理します。
Podを直接作成する場合の問題
PodはYAMLから直接作成できます。
apiVersion: v1
kind: Pod
metadata:
name: rails-pod
spec:
containers:
- name: rails
image: example/rails-app:v1
しかし、このPodを直接作成した場合、Podが削除されても同じPodを維持してくれる上位のコントローラーがありません。
Podを直接作成
↓
Podが削除される
↓
Podは0個のまま
また、Podを3個動かしたい場合に、ほぼ同じYAMLを3個用意して個別に管理するのは大変です。
rails-pod-1.yaml
rails-pod-2.yaml
rails-pod-3.yaml
Deploymentを使えば、「この定義のPodを3個動かす」という希望状態を1つのYAMLで宣言できます。
Deployment
└── 同じ定義のRails Podを3個維持する
Deploymentとは
Deploymentは、アプリケーションのPodを宣言的に管理するためのリソースです。
「この処理を1回実行してください」という命令ではなく、「この状態を維持してください」という希望状態をKubernetesへ伝えます。
命令的な考え方
「Podを3個作ってください」
宣言的な考え方
「Podが常に3個存在する状態にしてください」
Deploymentは、主に次のことを管理します。
- Podの作成
- 指定したPod数の維持
- Pod数の増減
- コンテナイメージやPod設定の更新
- ローリングアップデート
- 更新状況の記録
- 問題発生時のロールバック
Deployment・ReplicaSet・Podの関係
DeploymentがPodを管理すると説明されることが多いですが、厳密にはDeploymentとPodの間に ReplicaSet があります。
Deployment
└── ReplicaSet
├── Pod 1
├── Pod 2
└── Pod 3
それぞれの役割は次のとおりです。
| 要素 | 役割 |
|---|---|
| Deployment | ReplicaSetの作成・更新履歴・切り替えを管理する |
| ReplicaSet | 指定された個数のPodを維持する |
| Pod | コンテナを実際に動かす単位 |
| Container | RailsやSidekiqなどのアプリケーションを実行する |
普段はReplicaSetを直接作成・更新するのではなく、Deploymentを操作します。
Deploymentを作成すると、DeploymentがReplicaSetを作り、そのReplicaSetがPodを作成します。
Deploymentを作成
↓
ReplicaSetを作成
↓
指定された個数のPodを作成
↓
Pod内でContainerを起動
なぜReplicaSetが間にあるのか
ReplicaSetの基本的な役割は、指定された個数のPodを維持することです。
ReplicaSet
「Podを3個維持する」
↓
Pod 1
Pod 2
Pod 3
一方、DeploymentはReplicaSetを利用して、アプリケーションのバージョンを管理します。
コンテナイメージをv1からv2へ変更すると、Deploymentは新しいPod定義のためのReplicaSetを作ります。
Deployment
├── 古いReplicaSet
│ └── image: v1のPod
│
└── 新しいReplicaSet
└── image: v2のPod
新しいReplicaSetのPodを増やしながら、古いReplicaSetのPodを減らすことで、段階的な更新が可能になります。
この仕組みがローリングアップデートです。
希望する状態と現在の状態
Kubernetesでは、YAMLに書いた希望する状態と、実際の現在の状態を比較し続けます。
Deploymentで次のように指定したとします。
spec:
replicas: 3
これは、「Podを作る処理を3回実行する」という意味ではありません。
「対象のPodが3個存在する状態を維持する」という意味です。
希望する状態:Pod 3個
現在の状態:Pod 3個
→ 何もしない
現在の状態:Pod 2個
→ Podを1個作る
現在の状態:Pod 4個
→ Podを1個減らす
このように、現在の状態を希望する状態へ近づけ続ける処理を Reconciliation(調整ループ) と呼びます。
Podが停止したときの自己修復
Deploymentの管理下にあるPodが失われると、ReplicaSetがPod数の不足を検知し、新しいPodを作成します。
障害発生前
├── Rails Pod A
├── Rails Pod B
└── Rails Pod C
Rails Pod Bが削除される
↓
現在は2個しかない
↓
新しいRails Pod Dを作成
↓
再び3個になる
ここで重要なのは、消えたPod Bが復活するわけではないことです。
別の名前やIPアドレスを持つ、新しいPod Dが作成されます。
削除前:rails-abc-123
削除後:rails-abc-789
そのため、Podは同じ個体を長期間使い続けるサーバーではなく、必要に応じて作り直される一時的な存在として扱います。
Deploymentを定義するYAML
以下は、Railsの代わりにNginxを3個動かす基本的なDeploymentです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-nginx
spec:
replicas: 3
selector:
matchLabels:
app: sample-nginx
template:
metadata:
labels:
app: sample-nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
大きく分けると、次の構造になっています。
Deploymentの設定
├── Podを何個動かすか
├── どのPodを管理対象にするか
└── どのようなPodを作るか
└── どのContainerを動かすか
YAMLの各項目
apiVersionとkind
apiVersion: apps/v1
kind: Deployment
| 項目 | 意味 |
|---|---|
apiVersion: apps/v1 |
Deploymentを扱うKubernetes APIのバージョン |
kind: Deployment |
作成するリソースがDeploymentであることを示す |
metadata
metadata:
name: sample-nginx
Deployment自体の名前です。
作成後は、sample-nginxという名前でDeploymentを指定できます。
replicas
replicas: 3
動かしたいPodの個数です。
この値を5へ変更して適用すると、Podが5個になるように調整されます。
selector
selector:
matchLabels:
app: sample-nginx
Deploymentが管理対象を識別するための条件です。
この例では、app: sample-nginxというラベルを持つPodを対象にします。
template
template:
metadata:
labels:
app: sample-nginx
templateは、Deploymentが作成するPodの設計図です。
template.metadata.labelsには、作成されるPodへ付けるラベルを定義します。
selector.matchLabelsとtemplate.metadata.labelsは対応している必要があります。
selector.matchLabels
app: sample-nginx
↕ 一致させる
template.metadata.labels
app: sample-nginx
一致していない場合、Deploymentを正しく作成できません。
template内のspec
template:
spec:
containers:
- name: nginx
image: nginx:1.27
これは、作成するPodの中で動かすコンテナの定義です。
Podを直接定義するYAMLのspecに相当する部分が、Deploymentではspec.template.specの中に入ります。
Deployment
└── spec
└── template
└── spec
└── containers
Deploymentを作成する
YAMLをdeployment.yamlとして保存した場合、次のコマンドで適用できます。
kubectl apply -f deployment.yaml
Deploymentの状態を確認します。
kubectl get deployments
作成されたReplicaSetを確認します。
kubectl get replicasets
作成されたPodを確認します。
kubectl get pods
関係をまとめると、次のようになります。
kubectl apply -f deployment.yaml
↓
Deploymentを作成
↓
ReplicaSetを作成
↓
Podを3個作成
Deploymentの詳細やイベントを確認する場合は、次のコマンドを使用します。
kubectl describe deployment sample-nginx
Podを増減する
Pod数を3個から5個へ増やす場合は、YAMLのreplicasを変更します。
spec:
replicas: 5
その後、再び適用します。
kubectl apply -f deployment.yaml
一時的な操作であれば、次のコマンドでもPod数を変更できます。
kubectl scale deployment sample-nginx --replicas=5
ただし、GitなどでYAMLを管理している場合は、コマンドだけで変更するとYAMLに書かれた値とクラスターの状態が食い違います。
そのため、継続して使う設定はYAML側も更新します。
PodはどのNodeへ配置されるのか
Deploymentは「Podを何個作るか」を管理しますが、PodをどのNodeへ配置するかは直接決めません。
作成された各Podの配置先は、基本的にkube-schedulerが決定します。
Deployment
「Rails Podを3個必要としています」
↓
ReplicaSetがPodを作成
↓
kube-schedulerが配置先を決定
↓
Node 1:Rails Pod A
Node 2:Rails Pod B
Node 3:Rails Pod C
ただし、Podが必ず異なるNodeへ1個ずつ分散されるとは限りません。
何も条件を設定していなければ、複数のPodが同じNodeへ配置されることもあります。
厳密に分散させたい場合は、Pod anti-affinityやtopology spread constraintsなどの配置条件を使用します。
ローリングアップデートとは
DeploymentでコンテナイメージなどのPodテンプレートを変更すると、DeploymentはPodを段階的に新しいバージョンへ入れ替えます。
たとえば、Railsアプリケーションをv1からv2へ更新するとします。
containers:
- name: rails
image: example/rails-app:v2
Deploymentは、新しいReplicaSetを作成します。
更新前
Deployment
└── ReplicaSet v1
├── Pod v1
├── Pod v1
└── Pod v1
更新開始
Deployment
├── ReplicaSet v1
│ ├── Pod v1
│ └── Pod v1
│
└── ReplicaSet v2
└── Pod v2
その後、新しいPodを増やしながら古いPodを減らします。
途中
├── Pod v1
├── Pod v2
└── Pod v2
更新完了
├── Pod v2
├── Pod v2
└── Pod v2
すべてのPodを一度に停止してから新しいPodを作るのではないため、アプリケーションを提供し続けながら更新しやすくなります。
ただし、無停止を実現するには、アプリケーション側の互換性、Readiness Probe、十分なNodeリソースなども必要です。
Deploymentを使うだけで、どのようなアプリケーションでも必ず無停止になるわけではありません。
RollingUpdateの設定
Deploymentの標準的な更新方式はRollingUpdateです。
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
各項目の意味は次のとおりです。
| 項目 | 意味 |
|---|---|
maxUnavailable |
更新中に利用できなくてもよいPodの最大数 |
maxSurge |
希望するPod数を超えて一時的に作成できるPodの最大数 |
Podを3個動かしている場合、上記の設定ではおおむね次のように更新できます。
通常:3個
↓
新しいPodを一時的に1個追加:最大4個
↓
古いPodを1個ずつ減らす
↓
最終的に新しいPodが3個
値には個数だけでなく、25%のような割合も指定できます。
更新状況を確認する
Deploymentの更新状況は、次のコマンドで確認できます。
kubectl rollout status deployment/sample-nginx
更新履歴は次のコマンドで確認できます。
kubectl rollout history deployment/sample-nginx
PodやReplicaSetも合わせて確認すると、古いPodから新しいPodへ切り替わる様子を把握できます。
kubectl get replicasets
kubectl get pods
ロールバックとは
新しいバージョンで問題が起きた場合、Deploymentを以前のリビジョンへ戻せます。
v1:正常
↓
v2へ更新
↓
不具合を検知
↓
v1相当のPodテンプレートへロールバック
直前のリビジョンへ戻す場合は、次のコマンドを使用します。
kubectl rollout undo deployment/sample-nginx
特定のリビジョンへ戻す場合は、次のように指定できます。
kubectl rollout undo deployment/sample-nginx --to-revision=2
ロールバックは、以前のPodそのものを復活させる処理ではありません。
保存されている以前のPodテンプレートを使い、再びローリングアップデートを行う処理です。
また、データベース変更など、Deploymentの外で行われた変更まで自動的に元へ戻すわけではありません。
Podを再起動したい場合
Deploymentに「同じPodをその場で再起動する」という操作はありません。
管理下のPodを順番に作り直したい場合は、次のコマンドを利用できます。
kubectl rollout restart deployment/sample-nginx
これは既存コンテナ内のプロセスだけを再起動するのではなく、ローリングアップデートの仕組みを使ってPodを新しく作り直します。
DeploymentとServiceの関係
DeploymentとServiceは、どちらも複数のPodに関係しますが、役割が異なります。
Deployment
└── Podを3個作成・維持する
Service
└── 対象のPodへ通信を届ける
| 要素 | 役割 |
|---|---|
| Deployment | Podの個数・定義・更新を管理する |
| Service | Podに対する安定した接続先を提供する |
全体の関係は次のようになります。
Deployment
└── ReplicaSet
├── Rails Pod 1 ←┐
├── Rails Pod 2 ←┼─ Serviceから通信
└── Rails Pod 3 ←┘
DeploymentとServiceは、Podへ付けられたラベルをそれぞれの目的で利用します。
labels:
app: rails
ただし、DeploymentがServiceへ通信を渡すわけではありません。
DeploymentはPodの管理、Serviceは通信の入口を担当します。
RailsとSidekiqは別のDeploymentにする
RailsとSidekiqは、同じコンテナイメージを使う場合でも役割や増減のタイミングが異なります。
Rails Deployment
└── Rails Podを3個維持
Sidekiq Deployment
└── Sidekiq Podを2個維持
RailsへのHTTPリクエストが増えた場合はRails Podを増やし、バックグラウンドジョブが増えた場合はSidekiq Podを増やせます。
Webアクセス増加
→ Rails Deploymentをスケール
ジョブ増加
→ Sidekiq Deploymentをスケール
同じDeploymentへまとめると、Railsだけ増やしたい場合でもSidekiqまで同じ単位で増えてしまいます。
そのため、独立して更新・拡張したいアプリケーションは別のDeploymentにします。
EKSにおけるDeployment
DeploymentはKubernetesのリソースであり、EKS専用の機能ではありません。
EKS上でも、基本的な関係は同じです。
Amazon EKSクラスター
└── Deployment
└── ReplicaSet
└── Pod
└── Container
EC2をNodeとして使う構成では、作成されたPodが各EC2 Nodeへ配置されます。
EKSクラスター
├── Node 1(EC2)
│ ├── Rails Pod 1
│ └── Sidekiq Pod 1
│
└── Node 2(EC2)
├── Rails Pod 2
└── Rails Pod 3
DeploymentのYAMLはKubernetes APIへ送られ、EKSのControl Planeによって処理されます。
EKSがDeploymentそのものの代わりになるわけではありません。
Deploymentが向いているアプリケーション
Deploymentは、基本的に個々のPodの名前や順番を固定する必要がないアプリケーションに向いています。
代表例は次のとおりです。
- RailsなどのWebアプリケーション
- APIサーバー
- NginxやEnvoyなどのプロキシ
- Sidekiqなどのワーカー
一方、Podごとに固定した名前や永続ストレージとの対応関係が必要なアプリケーションでは、StatefulSetが適する場合があります。
Deployment
└── 入れ替わってもよい同一役割のPodを管理
StatefulSet
└── 固定した識別子や順序が必要なPodを管理
たとえば、データベースをKubernetes上で構築する場合は、DeploymentだけでなくStatefulSetや永続ストレージについて検討する必要があります。
よくある勘違い
DeploymentはPodそのもの?
違います。
DeploymentはPodを実行する場所ではなく、Podの個数や更新状態を管理するリソースです。
実際にコンテナを動かす単位はPodです。
DeploymentがPodを直接管理する?
説明を簡略化するとそのように表現できますが、実際にはDeploymentがReplicaSetを管理し、ReplicaSetがPod数を維持します。
Deployment → ReplicaSet → Pod
replicasを3にすると、必ず3台のNodeへ分散される?
必ずしも分散されません。
replicas: 3はPod数を3個にする指定であり、配置先Nodeを3台に分ける指定ではありません。
Podが落ちると、同じPodが再起動する?
コンテナだけが異常終了した場合は、Pod内でコンテナが再起動されることがあります。
一方、Pod自体が削除・消失した場合は、同じPodが戻るのではなく、新しいPodが作成されます。
Deploymentを作れば外部からアクセスできる?
Deploymentを作成しただけでは、外部からアクセスするための入口は提供されません。
通信の入口には、ServiceやIngressなど別のリソースが必要です。
Deploymentを使えば必ず無停止で更新できる?
必ずしも無停止になるわけではありません。
ローリングアップデートに加えて、Readiness Probe、十分なリソース、アプリケーションやDBの後方互換性などが必要です。
Deploymentはデータベースにも使える?
技術的にPodを起動することはできますが、固定された識別子や永続ストレージが重要なデータベースでは、StatefulSetやRDSなどを検討します。
まとめ
Deploymentについて、重要な点をまとめます。
- Deploymentは、どのようなPodを何個動かすか管理する
- 希望する状態と現在の状態を比較し、Pod数を維持する
- DeploymentとPodの間にはReplicaSetがある
- ReplicaSetが指定された個数のPodを維持する
- Podが失われると、同じPodではなく新しいPodが作成される
-
replicasでPod数を指定する -
templateに作成するPodの設計図を書く -
selector.matchLabelsとtemplate.metadata.labelsを対応させる - Podテンプレートの変更時は、新しいReplicaSetが作成される
- ローリングアップデートでは、Podを段階的に新しいバージョンへ入れ替える
- 問題があれば、以前のリビジョンへロールバックできる
- DeploymentはPodを管理し、ServiceはPodへの通信を担当する
- RailsとSidekiqのように別々に増減・更新したい処理は、別のDeploymentにする
- EKSでもDeploymentはKubernetesリソースとして動作する
最も簡単に覚えるなら、次の関係です。
Deployment
└── ReplicaSet
└── Pod
└── Container
それぞれの役割を一言で表すと、次のようになります。
Deployment:Podの定義・個数・更新を管理する
ReplicaSet:指定された個数のPodを維持する
Pod :Containerを動かす
Node :Podを動かす
Service :Podへの通信を受け付ける
PodとNodeを理解したあとにDeploymentを学ぶと、Kubernetesが「Podを作るだけでなく、必要な個数を維持し、安全に更新する仕組み」が見えやすくなります。