StatefulSet を動かして理解する
v1.37 時点(2026年10月25日 確認)
目次
この記事では、Pod に番号付きの固定の名前を与え、番号の順に作ったり止めたりするワークロードのリソースである StatefulSet の仕組みを、図で説明します。StatefulSet の Pod が web-0、web-1 のような名前になり、消しても同じ名前で作り直される理由がわかります。Pod を番号の小さい順に 1 つずつ作り、番号の大きい順に 1 つずつ止める決まりと、Deployment との違いもわかります。後半では kind のクラスタで StatefulSet を作り、Pod の名前が固定されていることと、起動と停止の順番を確かめる手順を示します。
Deployment と、Deployment が ReplicaSet を通して Pod を入れ替える仕組みを扱った第12回の内容を前提にしています。まだの方は先にこちらをどうぞ。
Deployment とローリングアップデートの仕組みReplicaSet を持ち、Pod を宣言的に更新する Deployment の仕組みを図で説明します。ひな形を変えたときに新しい ReplicaSet へ Pod を少しずつ移すローリングアップデート、pod-template-hash の役割、リビジョンと切り戻しを押さえ、kind のクラスタでイメージを更新して rollout undo で戻す手順を紹介します。以下の図は web という名前の StatefulSet と、その Pod と、Pod のネットワーク上の名前を受け持つ nginx という Service の関係を表しています。
StatefulSet の Pod の名前は、StatefulSet の名前と番号をつないだものです。Deployment の Pod の名前とは違い、名前の後ろに generateName の仕組みで付く一意な文字列(第6回)がありません。公式ドキュメントの説明では、StatefulSet の Pod を作るのは StatefulSet のコントローラー(statefulset-controller)で、Deployment のように間に ReplicaSet は出てきません。Pod のネットワーク上の名前は、StatefulSet の serviceName に書いた headless Service が受け持ちます。図では StatefulSet と Service を線で結んでいませんが、StatefulSet は添え書きの serviceName: nginx のとおり、Service を名前で指しています。破線の「持つ」と実線の「選ぶ」は、第6回と第11回の図と同じ意味です。
参考:StatefulSets | Kubernetes、kube-controller-manager | Kubernetes
StatefulSet とは
StatefulSet は 状態を持つ(stateful)アプリケーションを管理するためのワークロードのリソース です。公式ドキュメントによると、StatefulSet は Pod の集まりを作ったり数を変えたりし、それらの Pod の順番と一意性(ほかと重ならないこと)を保証します。状態を持つアプリケーションとは、一般にはデータベースのように、動きながらデータなどを覚えておくアプリケーションのことです。
公式ドキュメントは StatefulSet を Deployment と比べて説明しています。Deployment と同じく、StatefulSet は同じコンテナの設定から作った Pod を管理します。Deployment と違い、StatefulSet はそれぞれの Pod に固定の素性(sticky identity)を持たせます。Pod は同じひな形から作られますが、入れ替えのきく Pod ではありません。それぞれの Pod はどのノードに置き直されても変わらない識別子を持ち続けます。
「置き直される」は公式ドキュメントの “rescheduling” の訳です。正確には、Pod が別のノードへ移るのではありません。第6回・第8回で見たとおり、Pod は置かれたノードから動かず、消されて新しい Pod に置き換えられます。StatefulSet では、作り直された Pod が前の Pod と同じ名前、番号、ホスト名を受け継ぎます。公式のチュートリアルでも、Pod を消して作り直したあと、名前とホスト名が前と同じになっています。
参考:StatefulSets | Kubernetes、Pod Lifecycle | Kubernetes、StatefulSet Basics | Kubernetes
公式ドキュメントが挙げる、StatefulSet が役立つアプリケーションの条件は、次の 4 つです。StatefulSet はどれか 1 つ以上が要るアプリケーションに向いています。この回で扱うかどうかも添えます。
| 公式ドキュメントが挙げる条件 | この回での扱い |
|---|---|
| ネットワーク上の、固定された一意な識別子が要る | 名前の決まりと headless Service の役目を扱う。DNS の仕組みは第23回 |
| 固定された、データが残るストレージが要る | 触れるだけ。第28回で扱う |
| 順番どおりで穏やかな(graceful)配置と数の変更が要る | 扱う |
| 順番どおりに自動で進むローリングアップデートが要る | 決まりだけ扱う |
ここでの「固定された(stable)」は、Pod がスケジュールし直されても(上で説明したとおり、消されて作り直されても)変わらない、という意味です。公式ドキュメントは、固定された識別子も、順番どおりの配置・削除・数の変更も要らないアプリケーションなら、状態を持たない Pod の集まりを扱うワークロードのリソースを使うよう勧めています。Deployment や ReplicaSet のほうが合う場合がある、としています。
なぜ StatefulSet が必要なのか
第6回と第11回で見たとおり、Deployment の Pod を消すと、ReplicaSet が同じひな形から代わりの Pod を作ります。代わりの Pod の名前は generateName の仕組みで付けられるので、ふつうは消した Pod と違う名前になります。数を減らすときにどの Pod を消すかも、ReplicaSet が決まりに沿って選び、差がつかなければ無作為です(第11回)。Pod がどれも同じ役目なら、これで困りません。
参考:ReplicaSet | Kubernetes、Object Names and IDs | Kubernetes
一方で、Pod を 1 つずつ見分けたいアプリケーションがあります。公式ドキュメントによると、StatefulSet の Pod は失敗することもありますが、Pod の識別子が変わらないので、失敗した Pod の代わりの Pod に、もとのボリューム(データを置く場所)を結び付けやすくなります。また、StatefulSet の数を変える手順のページは、分散データベースの中には、メンバーが同時に加わったり抜けたりすると問題が起きるものがある、と書いています。StatefulSet が Pod を 1 つずつ順に作って止めるのは、こうしたアプリケーションのためだと考えられます。
StatefulSet の Pod を強制削除するときの注意を書いたページも、こうしたアプリケーションは、決まった数のメンバーが決まった素性を持つことを前提にした設定を持つことが多い、と説明しています。たとえば、データベースを 3 つの Pod で動かし、web-0 のような名前でほかのメンバーを指す設定を書く使い方が考えられます。このとき、Pod を作り直すたびに名前が変わると、その設定が使えなくなると考えられます。
参考:StatefulSets | Kubernetes、Scale a StatefulSet | Kubernetes、Force Delete StatefulSet Pods | Kubernetes
Deployment と StatefulSet の違いを、v1.37 時点の公式ドキュメントに沿って次の表にまとめます。
| 項目 | Deployment | StatefulSet |
|---|---|---|
| Pod の名前 | ReplicaSet の名前の後ろに一意な文字列が付く。作り直すと、ふつうは変わる | StatefulSet の名前の後ろに番号が付く(web-0)。作り直しても同じ |
| Pod を作るもの | Deployment が作った ReplicaSet(replicaset-controller) | statefulset-controller |
| 数を減らすときに消す Pod | ReplicaSet が決まりに沿って選ぶ | 既定では、番号のいちばん大きい Pod から 1 つずつ |
| 更新の進め方 | 新しい ReplicaSet を増やし、古い ReplicaSet を減らす。新しい Pod を先に作ることもできる(第13回) | 既定では、番号の大きい Pod から 1 つずつ、消してから作り直す |
| リビジョンの履歴の置き場所 | 古い ReplicaSet | ControllerRevision というリソース(この回では名前だけ知っておけば十分) |
| 必要な Service | 要らない(第12回の手順も Service なしで動かした) | Pod のネットワーク上の名前を受け持つ headless Service を、利用者が作る |
参考:StatefulSets | Kubernetes、Deployments | Kubernetes、ReplicaSet | Kubernetes
StatefulSet の仕組み
マニフェストに書くフィールド
以下は Pod を 3 つ動かす StatefulSet と、それが使う headless Service のマニフェストです。公式のチュートリアル(StatefulSet Basics)の web.yaml から、replicas を 2 から 3 に変え、ストレージにかかわる volumeMounts と volumeClaimTemplates を外しています。ストレージは第27回と第28回で扱うので、この回では Pod の名前と順番だけを見ます。後半の「手元で試す」でも、このマニフェストを使います。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.21
ports:
- containerPort: 80
name: web参考:StatefulSet Basics | Kubernetes、StatefulSet | Kubernetes API Reference
イメージの registry.k8s.io/nginx-slim:0.21 は公式のチュートリアルと同じものです。チュートリアルによると、この Pod は NGINX の Web サーバーを動かします。イメージの名前の先頭の registry.k8s.io はイメージを置いているレジストリ(イメージの置き場所)のホスト名です。第12回までの nginx:1.14.2 のようにホスト名を書かなければ、既定では、Kubernetes は Docker の公開レジストリを指すものとして扱います。
1 つのファイルに --- で区切って 2 つのリソースを書いています。公式ドキュメントによると、リソースはマニフェストに書いた順に作られます。Service を先に書いたのは、API リファレンスが serviceName の Service を StatefulSet より先に作っておくよう求めているからです。
参考:Managing Workloads | Kubernetes、StatefulSet | Kubernetes API Reference
StatefulSet の spec の形は、replicas、selector、template の 3 つが中心で、Deployment とほとんど同じです。この回にかかわるフィールドを、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.serviceName | Pod のネットワーク上の名前を受け持つ Service の名前。この Service は StatefulSet より先にある必要がある | なし |
spec.replicas | 望ましい Pod の数 | 1 |
spec.selector | Pod を選ぶラベルセレクタ。必須。ひな形のラベルに合っていなければ、作るときにエラーになる。apps/v1 では作ったあとで変えられない | なし |
spec.template | Pod のひな形。必須。restartPolicy に書けるのは Always だけ | なし |
spec.podManagementPolicy | Pod を作る・止める順番の決まり。OrderedReady か Parallel | OrderedReady |
spec.updateStrategy.type | ひな形を変えたときの Pod の入れ替え方。RollingUpdate か OnDelete(ほかにアルファの Recreate) | RollingUpdate |
spec.minReadySeconds | 新しい Pod が、コンテナが落ちずに Ready のまま、この秒数がたつと「使える(available)」とみなされる | 0 |
spec.ordinals.start | 番号の始まり | 0 |
spec.volumeClaimTemplates | Pod ごとにストレージの要求(PersistentVolumeClaim)を作るためのひな形。第28回で扱う | なし |
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference、Deprecated API Migration Guide | Kubernetes
公式ドキュメントによると、StatefulSet の名前は DNS のラベル(英小文字、数字、- で作る短い名前)として正しい形でなければなりません。Pod の名前とネットワーク上の名前のもとになるからだと考えられます。
参考:StatefulSets | Kubernetes、Object Names and IDs | Kubernetes
Pod の名前と番号
公式ドキュメントによると、StatefulSet の Pod の素性は、番号(ordinal)、固定されたネットワーク上の名前、固定されたストレージの 3 つでできています。この素性は、Pod がどのノードに置かれても Pod についていきます。
replicas が N の StatefulSet では、それぞれの Pod に、その StatefulSet の中で重ならない整数の番号が割り当てられます。既定では 0 から N−1 までです。Pod の名前は <StatefulSet の名前>-<番号> の形になります。上のマニフェストなら、Pod は web-0、web-1、web-2 の 3 つです。API リファレンスにも、web という StatefulSet の番号 3 の Pod は web-3 という名前になる、という例があります。
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference
statefulset-controller は、Pod を作るときに次の 2 つのラベルを付けます。
| ラベル | 値 | 使い道(公式ドキュメントの説明) |
|---|---|---|
statefulset.kubernetes.io/pod-name | Pod の名前(例:web-0) | StatefulSet の特定の Pod に Service を付ける |
apps.kubernetes.io/pod-index | Pod の番号(例:0) | 特定の番号の Pod へ通信を送る、ログやメトリクス(CPU の使用量のような、測った値)を番号で絞り込む |
番号の始まりは spec.ordinals.start で変えられます。たとえば 1 にすると、番号は 1 から始まります。公式ドキュメントによると、この機能は v1.31 から stable です。この回では既定の 0 から始まる番号を使います。
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference
名前が同じでも、作り直された Pod は前とは別のオブジェクトです。公式ドキュメントによると、同じ種類のオブジェクトで、ある名前を持てるのは同時に 1 つだけです。ただし、オブジェクトを消せば、同じ名前で新しいオブジェクトを作れます。一方、UID(第6回)はクラスタができてから作られたオブジェクトごとに違う値です。そのため、作り直された web-0 は、名前は同じでも UID は前の web-0 と違うはずです。
StatefulSet の Pod を強制削除するときの注意を書いたページによると、StatefulSet は、同じ素性の Pod がクラスタで同時に動くのは多くても 1 つ(at most one)であることを保証します。Pod をふつうに(穏やかに)消すと、Pod が止まったあとで kubelet が API から Pod の名前を消します。そのページは、強制削除すると名前がすぐに空き、StatefulSet のコントローラーが同じ素性の代わりの Pod を作れるようになる、と説明しています。つまり、ふつうに消したときは、前の web-0 が止まって名前が空いたあとで、新しい web-0 が作られます。第6回の ReplicaSet は、消した Pod が終了中のあいだに、別の名前で代わりの Pod を作ることがありました。StatefulSet はこの点が違います。
参考:Object Names and IDs | Kubernetes、Force Delete StatefulSet Pods | Kubernetes
ネットワーク上の名前と headless Service
公式ドキュメントによると、StatefulSet の Pod のホスト名(Pod の中から見たコンピューターの名前)は、StatefulSet の名前と Pod の番号から作られ、<StatefulSet の名前>-<番号> の形になります。つまり Pod の名前と同じです。DNS のページにも、Pod のホスト名は、ふだんは Pod の metadata.name の値になると書かれています。
参考:StatefulSets | Kubernetes、DNS for Services and Pods | Kubernetes
前の段落に出てきた DNS は、名前を問い合わせると IP アドレスを返す仕組みです。公式ドキュメントによると、Kubernetes は Service と Pod の DNS のレコード(名前と IP アドレスの対応)を作るので、IP アドレスの代わりに、変わらない名前で相手を指せます。クラスタの中の DNS は第23回で詳しく扱います。
Pod のネットワーク上の名前には Service を使います。Service は 1 つ以上の Pod で動くアプリケーションを、ネットワークに公開する方法です(詳しくは第21回で扱います)。公式ドキュメントによると、Service の通信を Pod に振り分ける仕組み(負荷分散)も、Service の 1 つの IP アドレスも要らないときには、headless Service を作れます。headless Service は spec.clusterIP に None を書いた Service です。headless Service にはクラスタの中で使う IP アドレス(cluster IP)が割り当てられず、振り分けも行われません。その代わりに、クラスタの DNS が、Service が選んだ一つ一つの Pod の IP アドレスを返します。ふつうの Service が 1 つの IP アドレスで受けて Pod に振り分けるのに対し、headless Service では、接続する側(クライアント)が Pod を選んで直接つなげます。StatefulSet の Pod が 1 つずつ名前で引けるのは、この仕組みを使うからです。
StatefulSet の Pod は serviceName に書いた headless Service の名前の下に、自分の DNS の名前を持ちます。公式ドキュメントの例のうち、この回のマニフェストに当てはまるもの(Namespace が default、クラスタのドメインが、設定を変えていないときの cluster.local)を次の表にまとめます。
| 項目 | 名前 |
|---|---|
| Service が受け持つドメイン | nginx.default.svc.cluster.local |
| Pod の DNS の名前 | web-0.nginx.default.svc.cluster.local から web-{N-1}.nginx.default.svc.cluster.local |
| Pod のホスト名 | web-0 から web-{N-1} |
Pod の DNS の名前 web-0.nginx.default.svc.cluster.local を、公式ドキュメントの形(<Pod の名前>.<Service の名前>.<Namespace>.svc.<クラスタのドメイン>)に沿って分けると、次のようになります。
| 部分 | 意味 |
|---|---|
web-0 | Pod の名前 |
nginx | serviceName に書いた Service の名前 |
default | Namespace |
svc | 形の中で決まっている部分 |
cluster.local | クラスタのドメイン。公式ドキュメントによると、ほかの値に設定していなければ cluster.local |
公式ドキュメントによると、StatefulSet は今のところ、Pod のネットワーク上の名前を受け持つ headless Service を必要とし、その Service は利用者が作ります。StatefulSet を作っても、Service は自動では作られません。
公式のチュートリアルでは、Pod を消して作り直したあとも、Pod の番号、ホスト名、DNS の名前は変わっていません。ただし Pod の IP アドレスは変わることがあります。そのためチュートリアルはほかのアプリケーションから StatefulSet の Pod に、特定の IP アドレスで接続しないよう注意しています。ホスト名を名前解決して接続するのはかまいません。DNS で名前を IP アドレスに変える仕組みは第23回で扱います。この回の手順では、DNS の名前は引かずに、Pod の名前とホスト名だけを確かめます。
作る順番と止める順番
既定の podManagementPolicy(OrderedReady)では、StatefulSet は Pod を作る順番と止める順番を守ります。公式ドキュメントの説明は次の 4 つです。
- N 個の Pod を配置するときは、番号 0 から N−1 の順に、1 つずつ作る
- Pod を消すときは、逆の順番(N−1 から 0)で終了させる
- 数を変えるために、ある Pod を作ったり消したりする前に、それより番号の小さい Pod がすべて Running で Ready になっている必要がある
- ある Pod を終了させる前に、それより番号の大きい Pod がすべて完全に止まっている必要がある
Running は Pod のフェーズの 1 つで、Pod がノードに割り当てられ、すべてのコンテナが作られ、少なくとも 1 つのコンテナが動いているか、起動・再起動の途中である状態です(第9回)。Ready は Pod の conditions の 1 つで、Pod がリクエストに応えられる状態を表します(第5回と第9回)。spec.minReadySeconds を設定したときは、前の Pod が Ready になってから、その秒数がたって「使える」状態になっている必要があります。既定の minReadySeconds は 0 なので、既定では Ready になった時点で使える Pod とみなされます。
以下の図は上のマニフェストの StatefulSet を作ったときに、Pod が作られていく様子をコマ送りで表しています。公式ドキュメントの、web という StatefulSet を作る例の説明に沿っています。kube-apiserver、kube-scheduler、kubelet は省き、statefulset-controller が Pod を作る操作だけを描いています。statefulset-controller は StatefulSet の replicas を見て、番号 0 から N−1 までの Pod がそろって Ready で動いているように保とうとします。図の Pod はそのために作られます。図の「作られた」は作られたばかりで、まだ Running で Ready になっていない Pod を、「動いている」は Running で Ready になった Pod を指します。
公式ドキュメントによると、web-1 は web-0 が Running で Ready になるまで作られず、web-2 は web-1 が Running で Ready になるまで作られません。もし web-1 が Running で Ready になったあと、web-2 を作る前に web-0 が失敗したら、web-2 は web-0 が作り直されて Running で Ready になるまで作られません。番号の小さい Pod が「すべて」そろっていることが条件だからです。
参考:StatefulSets | Kubernetes、Force Delete StatefulSet Pods | Kubernetes
数を減らすときは逆の順番です。公式ドキュメントには、この例の StatefulSet を replicas=1 に書き換えた場合の説明があります。以下の図はその説明に沿って、Pod が止まっていく様子をコマ送りで表しています。「終了中」は削除の時刻が書き込まれ、止まるのを待っている Pod を指します。
公式ドキュメントによると、web-2 が最初に終了させられ、web-1 は web-2 が完全に止まって消されるまで終了させられません。もし web-2 が消えたあと、web-1 を終了させる前に web-0 が失敗したら、web-1 は web-0 が Running で Ready になるまで終了させられません。数を増やすときと同じく、番号の小さい Pod がそろっていないと、次の操作に進まないということです。StatefulSet の数を変える手順のページも、管理する Pod のどれかの調子が悪いあいだは数を減らせず、それらの Pod が Running で Ready になってから数が減る、と書いています(「注意点」の表に載せています)。
参考:StatefulSets | Kubernetes、Scale a StatefulSet | Kubernetes
順番をゆるめる Parallel
spec.podManagementPolicy を Parallel にすると、順番の保証をゆるめられます。公式ドキュメントによると、ゆるむのは順番の保証だけで、一意性と素性の保証(番号付きの名前など)はそのまま保たれます。2 つの値を、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| 値 | 作るときと止めるとき |
|---|---|
OrderedReady(既定) | 作るときは番号の小さい順、止めるときは大きい順。前の Pod が Ready になるか止まりきるのを待ってから次に進む。一度に変わる Pod は多くても 1 つ |
Parallel | 数を変えると、Pod が Running で Ready になるのも、止まりきるのも待たずに、すべての Pod を一度に作ったり止めたりする |
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference
この回の後半では既定の OrderedReady だけを使います。
更新の順番
Pod のひな形(spec.template)を変えたときの入れ替え方は、spec.updateStrategy.type で決まります。v1.37 時点の公式ドキュメントと API リファレンスに沿って、次の表にまとめます。
| 値 | 入れ替え方 |
|---|---|
RollingUpdate(既定) | statefulset-controller が Pod を 1 つずつ消して作り直す。順番は止めるときと同じで、番号の大きい Pod から |
OnDelete | statefulset-controller は Pod を自動では入れ替えない。利用者が Pod を消すと、新しいひな形で作り直される |
Recreate | すべての Pod を消し、止まりきってから新しいひな形で作る。v1.37 時点ではアルファで、既定では使えない |
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference
RollingUpdate では、コントロールプレーンは更新した Pod が Running で Ready になるのを待ってから、その 1 つ小さい番号の Pod を更新します。minReadySeconds を設定していれば、Ready になってからその秒数も待ちます。第12回の Deployment は新しい Pod を先に作れましたが、公式ドキュメントによると、StatefulSet は Pod を 1 つずつ消してから作り直します。同じ素性の Pod は同時に多くても 1 つ(at most one)という保証があるので、先に作ることはできないのだと考えられます。
参考:StatefulSets | Kubernetes、Force Delete StatefulSet Pods | Kubernetes
RollingUpdate の進め方は次の 2 つのフィールドで変えられます。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.updateStrategy.rollingUpdate.partition | ひな形を変えたとき、この番号以上の Pod だけを更新する。これより小さい番号の Pod は更新されず、消されても前のひな形で作り直される | 0 |
spec.updateStrategy.rollingUpdate.maxUnavailable | 更新中に使えなくてもよい Pod の数の上限。数か割合で書き、割合は切り上げて数に直す。0 にはできない。v1.37 時点ではベータで、既定で有効(ベータになったのは v1.35) | 1 |
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference、Feature Gates | Kubernetes
maxUnavailable は第13回の Deployment のフィールドと同じ名前ですが、割合を数に直すときの丸め方が違います。第13回の Deployment の maxUnavailable は切り捨てでした。API リファレンスは、OrderedReady では Pod が 1 つずつ作られて Ready になるので、この設定が効かないことがある、と書いています。公式ドキュメントによると、Parallel で maxUnavailable を 1 より大きくすると、statefulset-controller は maxUnavailable の数まで Pod を同時に止めて作ります。既定の OrderedReady と、既定の maxUnavailable(1)のままなら、Pod は 1 つずつ入れ替わります(Rolling Updates の節の説明のとおり)。
参考:StatefulSet | Kubernetes API Reference、StatefulSets | Kubernetes、StatefulSets | Kubernetes
リビジョンの履歴は ReplicaSet ではなく ControllerRevision というリソースに残ります(この回では名前だけ知っておけば十分です)。公式ドキュメントによると、ひな形を変えると、statefulset-controller は新しい ControllerRevision を用意し、ひな形などの写しを保存して、増えていくリビジョンの番号を割り当てます。残す数は spec.revisionHistoryLimit(既定は 10)で決まります。第12回と同じく、kubectl rollout history と kubectl rollout undo で履歴を見たり前のリビジョンに戻したりできます。
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。StatefulSet と headless Service を作り、Pod が番号の小さい順に作られる様子を見ます。続けて、Pod を消しても同じ名前で作り直されることと、数を減らすと番号の大きい順に止まることを確かめます。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。この回ではシングルノードのクラスタを使います。この回で見る Pod の名前と、作る順番・止める順番は、ノードが 1 つでも確かめられるからです。公式のチュートリアルは、コントロールプレーンを兼ねないノードを 2 つ以上持つクラスタを勧めていますが、ストレージや Pod がどのノードに置かれるかを見ないこの回の手順では、1 つで足りると考えられます。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。シングルノードのクラスタを用意する
まず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、そのまま使えるので、次のクラスタを作るコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
前の回のクラスタを使い続けている場合は、app: nginx のラベルを持つ Pod が残っていないことを確かめます。
$ kubectl get deployments,pods -l app=nginx --context kind-kind
見つからないことを知らせる 1 行が出れば、そのまま進めます。前の回の nginx-deployment が残っていたら、kubectl delete deployment nginx-deployment --context kind-kind で消してから進みます。app: nginx の Pod が残ったままだと、この回の nginx の Service がその Pod も選んでしまい、StatefulSet のセレクタとも重なるからです。
StatefulSet を作る
「マニフェストに書くフィールド」のマニフェストを、web.yaml という名前で、コマンドを実行するディレクトリに保存します。このファイルはあとでエディタで書き換えます。
ここからは端末を 2 つ使います。第11回・第13回と同じく、1 つ目の端末で Pod の変化を追い続け、2 つ目の端末でマニフェストを渡したり Pod を消したりします。公式のチュートリアルも端末を 2 つ使って同じように進めています。
1 つ目の端末で、app: nginx の Pod を監視します。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch -l app=nginx --context kind-kind
Pod がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f web.yaml --context kind-kind
公式のチュートリアルの例では、出力は次のとおりです。
手元でも、Service と StatefulSet がそれぞれ created になったことを知らせる 2 行が出るはずです。
1 つ目の端末を見ると、行が書き足されていくはずです。公式のチュートリアルの例では、表示は次のとおりです。この例は Pod が 2 つの StatefulSet のもので、web-0 と web-1 の行だけがあります。
チュートリアルは、web-0 が Running で Ready になるまで web-1 が起動していないことに注目するよう書いています。手元では Pod が 3 つなので、例の最後の web-1 1/1 Running の行のあとに、web-2 の行が Pending、ContainerCreating、Running と、例の web-1 と同じ形で続くはずです。見るところを次の表にまとめます。
| 見るところ | 起きていること |
|---|---|
最初に現れるのは web-0 の行だけ | statefulset-controller が番号 0 の Pod だけを作った |
web-0 の行の READY 列が 1/1、STATUS 列が Running になってから、web-1 の行が現れる | web-0 が Running で Ready になるのを待ってから、web-1 を作った |
web-1 の行が 1/1 と Running になってから、web-2 の行が現れる | 同じく、web-1 を待ってから web-2 を作った |
READY 列の 1/1 は第9回と同じく、Pod の 1 つのコンテナのうち、準備ができているものが 1 つであることを表すと考えられます。同じ Pod の行が同じ値で 2 回出ることもあります。クラスタを作り直したばかりなら、web-0 はイメージの取得に時間がかかるので、ContainerCreating のまましばらく止まって見えることがあります(第9回)。公式ドキュメントによると、イメージをレジストリから取得しているあいだ、コンテナは起動に要る作業の途中の状態(Waiting)です。
2 つ目の端末で、Service と StatefulSet を一覧します。
$ kubectl get service nginx --context kind-kind
$ kubectl get statefulset web --context kind-kind
公式のチュートリアルの例では、出力は次のとおりです。
nginx の Service の CLUSTER-IP 列は、cluster IP が割り当てられていないので None になるはずです。TYPE 列が ClusterIP なのは、headless Service も Service の種類(spec.type)は既定の ClusterIP のままで、そのうえで spec.clusterIP に None を書いたものだからです。
StatefulSet の READY 列は、チュートリアルの例では Pod が 2 つなので 2/2 です。手元では Pod が 3 つなので、3 つとも Ready になれば 3/3 になると考えられます。
Pod の名前とラベルを確かめる
2 つ目の端末で、Pod をラベルの列つきで一覧します。--show-labels はラベルを最後の列に表示する指定です(第6回)。
$ kubectl get pods -l app=nginx --show-labels --context kind-kind
公式のチュートリアルの例(--show-labels を付けずに一覧したもの)では、出力は次のとおりです。この例も Pod は 2 つです。
手元では、web-0、web-1、web-2 の 3 つが並ぶはずです。Deployment の Pod と違い、名前の後ろに一意な文字列は付きません。LABELS 列にはひな形に書いた app=nginx のほかに、apps.kubernetes.io/pod-index=0 のような番号のラベルと、statefulset.kubernetes.io/pod-name=web-0 のような名前のラベルが並ぶはずです。これら以外のラベルが並ぶこともあると考えられますが、この回では扱いません。
続けて、それぞれの Pod の中で、ホスト名を表示する hostname のコマンドを動かします。第10回で見たとおり、kubectl exec はコンテナの中でコマンドを動かすコマンドで、-- の後ろがコンテナの中で動かすコマンドです。公式のチュートリアルと同じく、sh -c でシェルを通して hostname を動かします。
$ kubectl exec web-0 --context kind-kind -- sh -c 'hostname'
$ kubectl exec web-1 --context kind-kind -- sh -c 'hostname'
$ kubectl exec web-2 --context kind-kind -- sh -c 'hostname'
参考:StatefulSet Basics | Kubernetes、kubectl exec | Kubernetes
公式のチュートリアルの例(Pod 2 つ)では、出力は次のとおりです。
手元では、3 つのコマンドがそれぞれ web-0、web-1、web-2 を表示するはずです。Pod のホスト名が、Pod の名前と同じになっていることがわかります。
Pod を消して、同じ名前で作り直されるのを見る
まず 2 つ目の端末で、作り直す前の Pod の UID を表示して控えておきます。第6回と同じく、custom-columns で名前と UID の列だけを表示します。
$ kubectl get pods -l app=nginx -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid' --context kind-kind
2 つ目の端末で、公式のチュートリアルと同じく、StatefulSet の Pod をラベルセレクタでまとめて消します。
$ kubectl delete pod -l app=nginx --context kind-kind
公式のチュートリアルの例(Pod 2 つ)では、出力は次のとおりです。
手元では web-2 の行も加わり、3 行になるはずです。第6回で見たとおり、kubectl delete は既定では Pod が消えるまで待ってから終わるので、終わるまで少しかかることがあります。
1 つ目の端末を見ると、行が書き足されていくはずです。公式のチュートリアルの例では、Pod を消したあとの表示は次のとおりです。
この例には、消された Pod が終了中であることを表す Terminating の行がありません。例は作り直されたあとの行だけを示しているのだと考えられます。公式ドキュメントによると、消されている途中の Pod は、kubectl の表示の STATUS 列に Terminating と出ることがあります。チュートリアルの数を減らす例(後で引用します)にも Terminating の行があります。手元では、消した Pod の Terminating の行も出るはずです(第8回)。
参考:StatefulSet Basics | Kubernetes、Pod Lifecycle | Kubernetes、StatefulSet Basics | Kubernetes
この手順では利用者が 3 つの Pod をまとめて消すので、web-0、web-1、web-2 の行がほぼ同時に Terminating になることがあると考えられます。「番号の大きい順に止める」決まりが破られたように見えます。この決まりは公式ドキュメントでは「配置と数の変更の保証」(Deployment and Scaling Guarantees)として書かれています。利用者が kubectl delete で直接消した Pod には当てはまらないと考えられます(公式ドキュメントにはこの場合の説明がありません)。
一方、作り直す順番には決まりが表れます。作り直された Pod の行は web-0、web-1、web-2 の順に、同じ名前で現れるはずです。例でも、web-1 の行は web-0 が Running になってから現れています。作り直すときも、番号の小さい順に 1 つずつ作る決まりが守られていると読めます。手元では Pod が 3 つなので、例の最後の web-1 の Running の行のあとに、web-2 の行が同じ形で続くはずです。
参考:StatefulSet Basics | Kubernetes、StatefulSets | Kubernetes
監視を続けているので、画面には消す前の Pod の行と、作り直された Pod の行が、同じ名前で並びます。kubectl delete のあとに書き足された行の見分け方を、次の表にまとめます。
| 行 | 見分け方 |
|---|---|
| 消す前の Pod の行 | STATUS 列が Running か Terminating。AGE 列が大きい |
| 作り直された Pod の行 | STATUS 列が Pending か ContainerCreating から始まり、やがて Running になる。AGE 列が 0s のような小さい値から始まる |
1 つ目の端末で 3 つの Pod が Running になったら、2 つ目の端末で、名前と UID をもう一度表示します。
$ kubectl get pods -l app=nginx -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid' --context kind-kind
NAME 列は消す前と同じ web-0、web-1、web-2 のはずです。一方、UID 列の値は消す前に控えた値のどれとも違うはずです。名前は同じでも、新しく作られた別の Pod だということです。「Pod の名前とラベルを確かめる」の hostname のコマンドをもう一度実行すると、ホスト名も前と同じ web-0、web-1、web-2 になるはずです。
参考:Object Names and IDs | Kubernetes、StatefulSet Basics | Kubernetes
数を減らして、逆の順番で止まるのを見る
2 つ目の端末で、web.yaml をテキストエディタで開き、StatefulSet の replicas を 3 から 1 に書き換えて保存します。書き換えたあとの spec の始めの部分は次のとおりです。ほかの行は変えません。
spec:
serviceName: "nginx"
replicas: 1公式のチュートリアルでは kubectl scale や kubectl patch で数を変えています。この回では第12回・第13回と同じく、マニフェストを書き換えて kubectl apply します。公式ドキュメントの StatefulSet の数を変える手順のページも、kubectl apply で作った StatefulSet なら、マニフェストの replicas を書き換えて kubectl apply するやり方を挙げています。
参考:Scale a StatefulSet | Kubernetes、StatefulSets | Kubernetes
書き換えたマニフェストを渡します。
$ kubectl apply -f web.yaml --context kind-kind
1 つ目の端末を見ると、行が書き足されていくはずです。公式のチュートリアルの例では、表示は次のとおりです。この例は Pod を 5 つから 3 つに減らしたもので、止まるのは web-4 と web-3 です。監視を始めた時点では、web-4 はまだ起動の途中(ContainerCreating)でした。
チュートリアルは、コントロールプレーンが Pod を番号の大きい順に 1 つずつ消し、それぞれの Pod が完全に止まるのを待ってから次の Pod を消した、と説明しています。手元の手順(3 つから 1 つ)では、例の web-4 にあたるのが web-2、web-3 にあたるのが web-1 です。先に web-2 の行の STATUS 列が Terminating になり、そのあとで web-1 の行が Terminating になるはずです。web-0 の行には変化が無いはずです。
例の始めの 6 行(見出しの行と 5 つの Pod の行)は、チュートリアルがこの節で監視を新しく始めたときに表示された一覧です。この記事では監視を続けたままなので、このひとまとまりの一覧は出ず、変化した Pod の行だけが書き足されると考えられます。
参考:StatefulSet Basics | Kubernetes、StatefulSets | Kubernetes
第8回で見たとおり、--watch の表示は Pod が API から消えても行を消さないので、web-2 が消えた時点は表示からは読み取れません。web-1 の Terminating の行が web-2 の行より後に出ることで、順番を確かめます。Pod が止まりきるまでの時間は、Pod の終了の猶予(既定では 30 秒)の範囲で変わると考えられます。
1 つ目の端末の表示が落ち着いたら、2 つ目の端末で Pod と StatefulSet を一覧します。
$ kubectl get pods -l app=nginx --context kind-kind
$ kubectl get statefulset web --context kind-kind
Pod は web-0 の 1 つだけが表示されるはずです。web-1 か web-2 が Terminating で表示されていたら、少し待ってからもう一度実行します。StatefulSet の READY 列は 1/1 になると考えられます。
片付け
1 つ目の端末で Ctrl+C を押して、監視を止めます。
2 つ目の端末で、作った Service と StatefulSet を、マニフェストを指定して削除します。web.yaml には 2 つのリソースが書いてあるので、両方が消えます。
$ kubectl delete -f web.yaml --context kind-kind
参考:Delete a StatefulSet | Kubernetes、kubectl delete | Kubernetes
公式ドキュメントによると、kubectl で StatefulSet を消すと、StatefulSet の数は 0 になり、StatefulSet の Pod も消されます。ただし、StatefulSet を消すときは、Pod が止まる順番は保証されません。順番どおりに止めたいときは、消す前に StatefulSet の数を 0 に減らします。この手順では Pod は web-0 の 1 つだけなので、順番は気にしなくてかまいません。少し待ってから、残っていないかを確かめます。
$ kubectl get statefulsets,pods --context kind-kind
$ kubectl get service nginx --context kind-kind
参考:Delete a StatefulSet | Kubernetes、StatefulSets | Kubernetes
web-0 が Terminating で表示されていたら、少し待ってからもう一度実行します。1 つ目のコマンドで見つからないことを知らせる 1 行が出て、2 つ目のコマンドで nginx が見つからないことを知らせるエラーが出れば、削除できています。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster
$ kind get clusters
kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
StatefulSet でつまずきやすい点と、後から変えられない設定を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| headless Service は自分で作る | StatefulSet は Pod のネットワーク上の名前を受け持つ headless Service を必要とするが、自動では作らない |
| 消すときは順番が保証されない | StatefulSet を消すと Pod は消えるが、止まる順番は保証されない。順番どおりに止めたいときは、先に数を 0 に減らす |
| 1 つでも Pod の調子が悪いと、数を減らせない | StatefulSet の Pod のどれかが Running で Ready でないあいだは、数を減らす操作が進まない |
| 壊れたひな形で止まった更新は、ひな形を戻すだけでは直らない | 既定の OrderedReady で、Running で Ready にならないひな形(たとえば第12回のイメージのタグの打ち間違い nginx:1.161 のようなもの)に更新すると、更新は止まる。ひな形を戻したあと、壊れたひな形で作られた Pod を消す必要がある(既知の問題) |
| ボリュームは既定では自動で消えない | 既定では、StatefulSet を消しても、数を減らしても、Pod に結び付いたボリュームは消えない(第28回) |
terminationGracePeriodSeconds を 0 にしない | Pod の終了の猶予を 0 にするのは安全ではないとして、公式ドキュメントは 0 にしないよう強く求めている。0 にすると穏やかな削除(Pod が止まってから名前が消される)にならない、と公式ドキュメントは説明しており、同じ素性の Pod が 2 つ同時に動くおそれにつながると考えられる |
| セレクタは作ったあとで変えられない | apps/v1 の StatefulSet の spec.selector は必須で、作ったあとは変えられない |
手で変えた数は kubectl apply で戻る | kubectl scale で数を変えたあと、マニフェストを kubectl apply すると、マニフェストの replicas に戻る |
| 作ったばかりの Pod の DNS の名前は、すぐ引けないことがある | クラスタの DNS の設定によっては、Pod ができる前に名前を問い合わせたクライアントがいると、その失敗した結果が、Pod が動き出したあとも少なくとも数秒は使い回される(DNS は「ネットワーク上の名前と headless Service」と第23回) |
参考:StatefulSets | Kubernetes、Scale a StatefulSet | Kubernetes、Deprecated API Migration Guide | Kubernetes、Force Delete StatefulSet Pods | Kubernetes
壊れたひな形で止まった更新について補足します。公式ドキュメントによると、StatefulSet は、壊れた Pod が Ready になるのを待ち続けてから、前のひな形に戻そうとします。その Pod は Ready にならないので、ひな形を戻しても先に進みません。そのため、ひな形を戻したあと、壊れたひな形で作られた Pod を利用者が消します。消すと、StatefulSet は戻したひな形で Pod を作り直し始めます。第12回の Deployment では、kubectl rollout undo で切り戻せば古い ReplicaSet の Pod がそのまま使われました。StatefulSet はこの点が違います。
関連記事
ノードごとに Pod を 1 つずつ置くワークロード DaemonSet については、以下の記事で解説しています。
DaemonSet の仕組み対象のノードごとに Pod を 1 つずつ置く DaemonSet の仕組みを図で説明します。ノードの数で Pod の数が決まる理由、コントロールプレーンのノードの Taint と Toleration、更新の進め方を押さえ、kind のマルチノードのクラスタで各ノードに Pod が 1 つずつ置かれることを確かめる手順を紹介します。Service や Pod の DNS の名前を IP アドレスに変える、クラスタの中の DNS については、以下の記事で解説しています。
なぜ Service 名で通信できるのかService の名前を IP アドレスに変えるクラスタ DNS の仕組みを図で説明します。CoreDNS と kube-dns の Service、kubelet が Pod に書き込む /etc/resolv.conf の search と ndots、Namespace をまたぐ名前の書き方を押さえ、kind のクラスタで名前を引いて確かめる手順を紹介します。StatefulSet と PersistentVolumeClaim を組み合わせて、Pod を作り直してもデータを残す仕組みについては、以下の記事で解説しています。
StatefulSet と PVC でデータを残すStatefulSet の volumeClaimTemplates が Pod ごとに PersistentVolumeClaim を作り、Pod を作り直しても同じボリュームにつなぎ直す仕組みを図で説明します。PVC の名前の付き方、消したり数を減らしたりしても PVC が残る既定の動き、persistentVolumeClaimRetentionPolicy を押さえ、kind のクラスタでデータが残ることを確かめる手順を示します。