はじめに
Deploymentを適用すると、作ったのはDeploymentだけなのに、ReplicaSetとPodも一覧に並びます。
それぞれが何を担当していて、どうつながっているのか、改めて説明できますか?
この記事では、Podを作って管理するリソースを、どのリソースにも共通する仕組みから整理します。
あわせて、Podの数を負荷に応じて自動で変える仕組みが、これらのリソースとどうつながるのかも扱います。
クラスタを構成する部品は、Kubernetes入門:Podが動くまでに中で何が起きているのか、クラスタの構成要素を整理する(以下、クラスタの構成要素の記事)で整理した内容を前提にしています。
後半では、OCI(Oracle Cloud Infrastructure。Oracleのクラウドサービス)上でKubernetesを動かすサービスであるOKEのクラスタで、関係を確かめます。
この記事は、学習の過程で調べた内容を自分なりの解釈も含めて整理したもので、所属する組織の公式な見解ではありません。
正確な定義が必要な場合は、参考に載せている公式ドキュメントをご確認ください。
ざっくりまとめ
この記事で扱うリソースは、どれもPod(コンテナを動かす単位)を作って管理するためのものです。
どのリソースも、Podのひな形を持ち、担当のコントローラーがひな形からPodを作る、という同じ仕組みで動きます。
違うのは、Podの数の決め方と、Podの扱い方です。
| リソース | Podの数の決め方 | Podの扱い | 使う場面の例 |
|---|---|---|---|
| Deployment | replicas(Podの数)を書く | ReplicaSetを作り、新しいひな形のPodへの入れ替えを受け持つ | どのPodも同じ役割のアプリ(Webのフロントエンドなど) |
| ReplicaSet | replicasを書く | 同じPodを指定した数だけ保つ | 通常はDeploymentが作るので、直接は使わない |
| StatefulSet | replicasを書く | Podに番号付きの名前を付け、作り直しても同じ名前にする | Podごとにデータを区別するアプリ(データベースなど) |
| DaemonSet | 条件に合うノードの数で決まる | ノード1台に1つずつ置く | ログの収集、ノードの監視 |
| Job | 成功させる回数を書く | 処理が成功したら終わる | 一度きりの処理 |
| CronJob | スケジュールと、作るJobの設定を書く | 決まった時刻にJobを作る | 定期的なバックアップ |
Podと、Podを作って管理するリソース
まず、管理される側のPodと、Podを作って管理するリソースの関係です。
Podは、1つ以上のコンテナをまとめたもので、Kubernetesで作成、管理できる最小の単位です。
Podは、使い捨てを前提に作られています。
自分で直接作ったPodは、削除されたり、動いていたノードが故障したりしても、作り直されません。
そのため、通常Podを直接作らず、Podを作って管理するリソースを通して作ります。
こうしたリソースはワークロードリソースと呼ばれます。
例えばDeploymentを通して作ったPodは、消えても、代わりのPodが自動で作られます。
Kubernetesに組み込まれているワークロードリソースは、Deployment、ReplicaSet、StatefulSet、DaemonSet、Job、CronJobです。
1つのDeploymentから作られるもの
冒頭の「作ったのはDeploymentだけなのに、ReplicaSetとPodも一覧に並ぶ」理由を、Deploymentを1つ作ったときの動きから見ていきます。
例えば、kubectl create deployment コマンドで、nginxのPodを3つ動かす frontend という名前のDeploymentを作ると、次の順にReplicaSetとPodが作られます。
- 人が、Deploymentを作る
- Deploymentのコントローラーが、ReplicaSetを作る
- ReplicaSetのコントローラーが、Podを3つ作る
人が作ったのは、図の上にあるDeploymentだけです。
ReplicaSetとPodは、それぞれを担当するコントローラーが作ります。
そのため、kubectl get deployments,replicasets,pods を実行すると、自分では作っていないReplicaSetとPodも一覧に並びます。
コントローラーが繰り返す処理
ReplicaSetとPodを作っているコントローラーの動きを、ReplicaSetのコントローラーを例に見ていきます。
コントローラーは、「あるべき状態と実際の状態を比べて、違いを埋める処理を繰り返すプログラム」です。
組み込みのコントローラーは、kube-controller-managerの中で、リソースの種類ごとに動いています。
ReplicaSetのコントローラーにとっての「あるべき状態」は、ReplicaSetに書かれたPodの数で、この数をreplicasと呼びます。
例えば、replicasが3のReplicaSetのPodが1つ削除されると、ReplicaSetのコントローラーは次の処理を行います。
- 数える:自分が管理しているPodを数える(2つ)
- 比べる:replicasの3と比べる(1つ足りない)
- 作る:足りない1つを、kube-apiserverに頼んで作る
数が合っていれば何もせず、差ができたときだけ埋めます。Podが多すぎるときは、余分なPodを削除します。
コントローラーの仕事は、Podの作成や削除をkube-apiserverに頼むところまでです。Podを置くノードはkube-schedulerが決め、コンテナはノードのkubeletが起動します。
コントローラーが使う情報
上記の図のように数えて、比べて、作るために、コントローラーは次の情報を使います。
- 何個作るか — Podの数(replicas)
- 何を作るか — Podのひな形
- どれが自分のPodか — ラベルと、持ち主の記録
このうち、Podの数、ひな形、ラベルは、マニフェスト(あるべき状態を書いたYAMLのファイル)に書きます。
frontend のDeploymentのマニフェストでは、次の場所です。
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3 # 何個作るか:動かしておくPodの数
selector:
matchLabels:
app: frontend # どれが自分のPodか:このラベルを持つPodを、管理対象の候補にする
template: # 何を作るか:ここから下が、作るPodのひな形
metadata:
labels:
app: frontend # 作るPodに付けるラベル
spec:
containers:
- name: nginx
image: docker.io/library/nginx:1.27
resources:
requests:
cpu: 100m # Podが確保しておきたいCPUの量(0.1個分)
memory: 128Mi # Podが確保しておきたいメモリの量
template: から下がPodのひな形で、コントローラーはどのPodもこのひな形から作ります。
ひな形の中の resources.requests は、Podが確保しておきたいCPUとメモリの量で、kube-schedulerはこの値を見てPodを置くノードを決めます。
ラベルは、Podに付ける app: frontend のような「キー = 値」の形の目印です。
ひな形に書いたラベルは、作られるPodのすべてに付きます。
selector: に書くのは 「セレクタ」と呼ばれる条件 で、コントローラーは、この条件に合うラベルを持つPodを、自分の管理対象の候補にします。
DeploymentのPodを実際に作るのはReplicaSetなので、Deploymentに書いたreplicas、セレクタ、ひな形は、Deploymentのコントローラーが作るReplicaSetにも書き写されます。
ReplicaSetのコントローラーは、Podを作ると、Podの metadata.ownerReferences という欄に、そのReplicaSetを持ち主として記録します。この記事では、これを持ち主の記録と呼びます。
ReplicaSetのコントローラーが数えるのは、ラベルがセレクタに合い、持ち主としてそのReplicaSetが記録されているPodです。
ReplicaSetとPodの関係を図にすると、次のようになります。
左のReplicaSetに書かれたひな形から右のPodが作られ、Podには持ち主としてReplicaSetが記録されます。
持ち主の記録は、削除のときにも使われます。
Deploymentを削除すると、既定では、持ち主の記録をたどって、ReplicaSetとPodも自動で削除されます。
ReplicaSetとDeploymentの役割分担
ReplicaSetとDeploymentは、どちらもreplicasを持ちますが、受け持つことが違います。
ReplicaSetが受け持つのは、同じPodを指定した数だけ保つことです。
イメージの版を上げるときなどにひな形を書き換えても、すでに動いているPodは書き換わりません。ReplicaSetには、動いているPodを、新しいひな形から作ったPodへ入れ替える仕組みがありません。
この入れ替えを受け持つのが、Deploymentです。そのためドキュメントでは、独自の更新手順が必要な場合などを除いて、ReplicaSetを直接使わず、Deploymentを使うことを勧めています。
ReplicaSetとPodの名前
Deploymentが作るReplicaSetとPodの名前は、次の形です。
- ReplicaSetの名前 — 「Deployment名-ハッシュ」。ハッシュはひな形の内容から計算した値で、ひな形が変わると値も変わる
- Podの名前 — 「ReplicaSet名-ランダムな5文字」
つまり、Podの名前は「Deployment名-ハッシュ-ランダムな5文字」になります。
ハッシュは、ReplicaSetとPodに付く pod-template-hash というラベルの値にもなっています。
replicasの変更と、ひな形の変更
Deploymentでは、replicasを変えたときと、ひな形を変えたときとで、次のように動きが分かれます。
- スケール — replicasだけを変える。今のReplicaSetのPodの数が増えたり減ったりする
- ロールアウト — イメージなど、ひな形を変える。新しいひな形のReplicaSetが作られ、既定では古いReplicaSetのPodと少しずつ入れ替わる
左はreplicasを3から5に変えたときで、ReplicaSetは1つのまま、Podが5つに増えます。
右はイメージのタグを1.27から1.28に変えたときで、新しいReplicaSetのPodが3つになり、古いReplicaSetのPodは0になります。
古いReplicaSetは、ひな形を前の内容に戻すロールバックに備えて、Podが0のまま残されます(既定では10個まで)。
StatefulSetとPodごとの名前
ここからは、Deploymentとは違う扱いでPodを管理するStatefulSetです。
DeploymentのPodは、どれも同じ役割で、どのPodが消えても、同じひな形から作った新しいPodが代わりを務められます。こうしたアプリは、ステートレス(状態を持たない)と呼ばれます。
これに対して、データベースのように、Podごとに自分のデータを持ち、Podどうしを区別する必要があるアプリは、ステートフルと呼ばれます。
StatefulSetは、ステートフルなアプリのためのリソースです。
StatefulSetのPodには、web-0、web-1 のような「StatefulSet名-番号」の名前が付きます。
Podを削除すると、同じ名前のPodが作り直されます(Deploymentでは、作り直されたPodの名前は元と違います)。Podは、既定では番号順に1つずつ作られます。
また、DeploymentのようにReplicaSetを挟まず、StatefulSetのコントローラーが直接Podを作ります。
ほかのアプリからPodごとの名前でつなぐには、Headless Serviceを自分で作り、StatefulSetの spec.serviceName にその名前を書きます。
Serviceは、セレクタで選んだPodの集まりに、変わらない接続先を与えるリソースです。
Headless Serviceは、clusterIP: None を指定したServiceで、StatefulSetと組み合わせると、Podごとに接続先の名前が付きます。
例えば、nginx という名前のHeadless Serviceと組み合わせると、web-0 には web-0.nginx から始まる名前が付きます。
Podごとのストレージには、PersistentVolumeClaim(PVC)というリソースを使います。
PVCは、「このくらいの容量のストレージがほしい」という要求を表すリソースです。
StatefulSetの volumeClaimTemplates にPVCのひな形を書くと、Podごとに別のPVCが作られます。
web-0 が作り直されても、新しい web-0 は同じPVCにつながるので、データを引き継げます。
PVCは、StatefulSetを削除しても既定では残ります。
注意点:
OKE(Kubernetes 1.24以降で作ったクラスタ)の既定の設定では、PVCに1Giと書いても、最小サイズの50 GBのブロック・ボリューム(OCIのディスク)が作られ、PVCと同じく既定では残ります。
このため、この記事のOKEでの確認では、volumeClaimTemplatesを書かずにStatefulSetを作ります。
StatefulSetが用意するのは、Podごとの名前とストレージといった土台までです。
データの複製や障害時の切り替えは、データベース自身などで用意します。
そのほかのワークロードリソース
Deployment、ReplicaSet、StatefulSetのほかにも、同じ仕組みでPodを管理するワークロードリソースがあります。
- DaemonSet — すべてのノード(または条件に合うノード)に、1台に1つずつPodを置く。replicasは無く、ノードが増えればPodも増える。ログの収集やノードの監視のように、どのノードでも動かしたい処理に使う
- Job — 処理が終わったら止まるPodを扱う。失敗したら再試行し、決めた回数だけ成功したら完了になる
- CronJob — 決まった時刻にJobを作る。Podを作るのは、CronJobが作ったJob
ここまでのリソースごとに、誰が誰を作るのかを並べると、次のようになります。
どのリソースも、上の段が下の段を作り、下の段には上の段が持ち主として記録されます。
DeploymentとCronJobはPodとの間にもう1段(ReplicaSet、Job)を挟み、StatefulSet、DaemonSet、JobはPodを直接作ります。
Podの数を負荷に応じて変える仕組み
Podの数は、kubectl scale コマンドで手で変えるほかに、負荷に応じて自動で変えることもできます。
これを受け持つのが HorizontalPodAutoscaler(HPA) で、HPAもリソースとコントローラーの組み合わせです。
HPAのコントローラーは、HPAに書いた最小と最大の数の範囲で、CPU使用率などを見て、DeploymentやStatefulSetのreplicasを書き換えます。
HPAが書き換えるのはreplicasだけで、Podを作るのは、ここまでに見たコントローラーです。
Deploymentが対象で、Podの数を3から5に増やす場合は、次の順に進みます。
- HPAのコントローラーが、Deploymentのreplicasを3から5に書き換える
- Deploymentのコントローラーが、ReplicaSetのreplicasを5に合わせる
- ReplicaSetのコントローラーが、足りない2つのPodを作る
- kube-schedulerが、新しいPodを置くノードを決める
kube-schedulerが置けるノードを見つけられなければ、Podは作られても置き場所が決まらず、Pendingのまま待ちます。
Cluster AutoscalerやKarpenterのようにノードを自動で増やす仕組みは、この置き場所の決まらないPodをきっかけに動きます。
OKEでリソースの関係を確かめる
ここまでの関係を、OKEのクラスタで確かめます。
DeploymentとStatefulSetを作り、作られたReplicaSetやPodと、その持ち主を見ていきます。
前提:動かした環境
- OKE(Oracle Kubernetes Engine。OCI上でKubernetesのクラスタを作成、運用できるサービス)のBasicクラスタ。Kubernetesバージョンv1.36、ワーカーノードはVM.Standard.A1.Flex(1 OCPU / 6 GB)が2台
- 操作は、OCIコンソールのCloud Shell(ブラウザ上で使えるターミナル)から、kubectlで行う
- この確認では、ロードバランサーやブロック・ボリュームのように、クラスタのほかに料金がかかるリソースは作らない
クラスタの作成と接続の手順は、こちらの記事にまとめています。
イメージ名は、OKEのKubernetes 1.34.1以降の要件に合わせて、docker.io/library/nginx:1.27 のようにレジストリの場所から書きます。
nginxの公式イメージは、ワーカーノードのArm(CPUの種類の1つ)に対応しています。
では早速、default Namespace(Namespaceを指定しないときに使われる区画)に、リソースが残っていないかを確かめます。
# 残っていなければ No resources found in default namespace. と表示される
kubectl get deployments,statefulsets,pods
クラスタの構成要素の記事で作った nginx というDeploymentが表示された場合は、先に削除します。
そのPodには、後で作るStatefulSetと同じ app=nginx のラベルが付いていて、StatefulSetのPodを表示する一覧に混ざるためです。
# nginx という名前のDeploymentが表示された場合だけ実行する
# Deploymentを削除すると、ReplicaSetとPodも一緒に削除される
kubectl delete deployment nginx
Deploymentを作り、ReplicaSetとPodの関係を見る
まず、前半のマニフェストとほぼ同じDeployment(resources の行を除いたもの)を作ります。
# create deployment は、マニフェストを書かずにDeploymentを作るサブコマンド
# app=frontend のラベルとセレクタが付き、コンテナ名はイメージ名から nginx になる
kubectl create deployment frontend --image=docker.io/library/nginx:1.27 --replicas=3
# rollout status は、ロールアウトが終わるまで待って結果を表示するサブコマンド
# Deploymentを作ったときも、1回目のロールアウトとして扱われる
kubectl rollout status deployment/frontend
# -l で、指定したラベルを持つものに絞り込む
# --show-labels で、それぞれに付いているラベルを表示する
kubectl get deployments,replicasets,pods -l app=frontend --show-labels
ReplicaSetの名前の末尾、Pod名の真ん中の文字列、pod-template-hash ラベルの値が、同じハッシュになっています。
続けて、PodとReplicaSetの持ち主を表示します。
持ち主を表示する列の指定は長いので、シェルの変数(値に名前を付けて、後から使い回せるようにしたもの)に入れておきます。
# -o custom-columns は、表示する列を自分で決める指定
# ownerReferences[0] は、持ち主の記録の1件目
OWNER_COLS='custom-columns=NAME:.metadata.name,OWNER_KIND:.metadata.ownerReferences[0].kind,OWNER:.metadata.ownerReferences[0].name'
kubectl get pods -l app=frontend -o "$OWNER_COLS"
kubectl get replicasets -l app=frontend -o "$OWNER_COLS"
(Cloud Shellに接続し直した場合は、変数が消えています。そのまま実行するとエラーにはならず、持ち主の列が無い一覧が表示されるので、OWNER_COLS= の行をもう一度実行します。)
Podの持ち主(OWNER_KIND、OWNER)はReplicaSet、ReplicaSetの持ち主はDeploymentの frontend です。
Podを消したときと数を変えたときの違いを見る
Deploymentが管理するPodを1つ削除します。
# Pod名は、kubectl get pods で表示された名前に置き換える
kubectl delete pod frontend-xxxxxxxxxx-xxxxx
kubectl get pods -l app=frontend
削除したPodの代わりに、最後の5文字が違う新しいPodが作られ、Podの数は3のままです。
前半で見たとおり、ReplicaSetのコントローラーが、足りなくなった1つを作っています。
次に、kubectl scale でDeploymentのreplicasを5に増やします。
# scale は、replicas だけを変えるサブコマンド
kubectl scale deployment/frontend --replicas=5
kubectl get replicasets -l app=frontend
# rollout history は、ロールアウトの履歴を表示するサブコマンド
# REVISION 列は、ロールアウトごとに付く番号
kubectl rollout history deployment/frontend
ReplicaSetは1つのままで、DESIRED(あるべき数)が5になっています。
履歴の REVISION は、Deploymentを作ったときの1だけで、スケールではロールアウトが起きていないことが分かります。
(実行した直後は、新しく作られた2つのPodの起動が終わっておらず、READY が5より小さく表示されることがあります。)
確認できたら、replicasを3に戻します。
kubectl scale deployment/frontend --replicas=3
ひな形を変えて、ReplicaSetの入れ替えを見る
今度は、ひな形のイメージのタグを1.27から1.28に変えます。
# set image は、ひな形のコンテナのイメージを書き換えるサブコマンド
# = の左の nginx はコンテナ名
kubectl set image deployment/frontend nginx=docker.io/library/nginx:1.28
kubectl rollout status deployment/frontend
kubectl get replicasets -l app=frontend
kubectl rollout history deployment/frontend
ReplicaSetが2行になり、新しいReplicaSetの DESIRED が3、古いReplicaSetの DESIRED が0になっています。
REVISION は1と2の2つです。
(CHANGE-CAUSE 列が <none> なのは、変更の理由を記録していないためです。)
最後に、Deploymentを削除して、ReplicaSetとPodも消えることを確かめます。
kubectl delete deployment frontend
# 数秒待ってから実行する。Terminating のPodが表示された場合は、もう一度実行する
# No resources found と表示されれば、ReplicaSetとPodも消えている
kubectl get replicasets,pods -l app=frontend
StatefulSetのPodの名前と持ち主を見る
続いて、Headless ServiceとStatefulSetを作ります。
今回は、Podを2つ作るStatefulSetにします。
# 次の行から EOF と打つまでの内容を、web-statefulset.yaml に書き込む
cat > web-statefulset.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nginx # Headless Serviceの名前(イメージのnginxとは別)
spec:
clusterIP: None # Service自身のIPアドレスを持たないHeadless Serviceにする
selector:
app: nginx
ports:
- port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx # 上のHeadless Serviceの名前
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/library/nginx:1.28
ports:
- containerPort: 80
EOF
# 1つのファイルに --- で区切って書いた、ServiceとStatefulSetをまとめて作る
kubectl apply -f web-statefulset.yaml
# rollout status は、StatefulSetでもPodがそろうまで待つのに使える
# 最後に partitioned roll out complete と表示されれば完了(partitioned は更新の設定に由来する言葉)
kubectl rollout status statefulset/web
kubectl get pods -l app=nginx
kubectl get pods -l app=nginx -o "$OWNER_COLS"
Podの名前は web-0 と web-1 です。
Podの持ち主はStatefulSetの web で、Deploymentのときと違って、間にReplicaSetがありません。
続けて、web-0 を削除します。
kubectl delete pod web-0
# web-0 が表示されない場合は、数秒待ってからもう一度実行する
kubectl get pods -l app=nginx
削除した web-0 が同じ名前で作り直され、AGE が数秒になっています。
片付け
確認が終わったら、作ったものを削除します。
# delete -f は、ファイルに書かれたもの(ServiceとStatefulSet)を削除する
kubectl delete -f web-statefulset.yaml
# 数秒待ってから実行し、残っていないことを確かめる
# Podが表示された場合は、少し待ってからもう一度実行する
kubectl get statefulsets,pods,services
services には、クラスタに最初からある kubernetes という名前のServiceだけが残ります。
(Cloud Shellに作ったファイルは、rm web-statefulset.yaml で削除できます。)
まとめ
- 直接作ったPodは作り直されないので、PodはDeploymentなどのワークロードリソースを通して作る
- ワークロードリソースを担当するコントローラーが、Podを数えて、あるべき数と比べ、足りなければひな形から作る
- Deploymentを作ると、DeploymentのコントローラーがReplicaSetを、ReplicaSetのコントローラーがPodを作る
- Deploymentでは、replicasを変えると今のReplicaSetのPodが増減し、ひな形を変えると新しいReplicaSetに入れ替わる
- StatefulSetは、Podに番号付きの名前を付け、作り直しても同じ名前にする
- HPAが書き換えるのはreplicasまでで、そこから先は、ここまでと同じコントローラーがPodを作る
参考
- Kubernetes公式ドキュメント:Workload Management
- Kubernetes公式ドキュメント:Pods
- Kubernetes公式ドキュメント:Labels and Selectors
- Kubernetes公式ドキュメント:Owners and Dependents
- Kubernetes公式ドキュメント:Garbage Collection
- Kubernetes公式ドキュメント:ReplicaSet
- Kubernetes公式ドキュメント:Deployments
- Kubernetes公式ドキュメント:StatefulSets
- Kubernetes公式ドキュメント:DaemonSet
- Kubernetes公式ドキュメント:Jobs
- Kubernetes公式ドキュメント:CronJob
- Kubernetes公式ドキュメント:Service
- Kubernetes公式ドキュメント:Persistent Volumes
- Kubernetes公式ドキュメント:Horizontal Pod Autoscaling
- OCIドキュメント:Provisioning PVCs on the Block Volume Service
- OCIリリースノート:Support for Kubernetes version 1.34.1

















