DaemonSet の仕組み
v1.37 時点(2026年10月25日 確認)
目次
この記事では対象のノードごとに Pod を 1 つずつ置くワークロードのリソースである DaemonSet の仕組みを、図で説明します。DaemonSet に replicas が無く、ノードの数で Pod の数が決まる理由がわかります。どのノードが対象になるのか、コントロールプレーンのノードに Pod を置くには何が要るのかもわかります。後半では kind のマルチノードのクラスタで DaemonSet を作り、各ノードに Pod が 1 つずつ置かれることを確かめる手順を示します。
Deployment と、Deployment が ReplicaSet を通して Pod を入れ替える仕組みを扱った第12回の内容を前提にしています。まだの方は先にこちらをどうぞ。
Deployment とローリングアップデートの仕組みReplicaSet を持ち、Pod を宣言的に更新する Deployment の仕組みを図で説明します。ひな形を変えたときに新しい ReplicaSet へ Pod を少しずつ移すローリングアップデート、pod-template-hash の役割、リビジョンと切り戻しを押さえ、kind のクラスタでイメージを更新して rollout undo で戻す手順を紹介します。以下の図は nginx-daemonset という名前の DaemonSet が、kind のマルチノードのクラスタ(コントロールプレーン 1、ワーカー 2)の各ノードに Pod を 1 つずつ置いた様子を表しています。後半の「手元で試す」の最後の状態にあたります。
Pod の数を決めているのは replicas ではなく Pod を置く対象になったノードの数です。公式ドキュメントの説明では、DaemonSet の Pod を作るのは DaemonSet のコントローラー(daemonset-controller)で、Deployment のときのように間に ReplicaSet が出てくることはありません。コントロールプレーンのノードにも Pod があるのは、DaemonSet の Pod のひな形に、コントロールプレーンのノードの Taint を許容する Toleration を書いたからです。Taint はノードに付けて Pod を寄せ付けないようにする印で、Toleration は Pod の側に書いて Taint を許容する設定です。どちらも後で説明します。破線の「持つ」は第6回と第11回の図と同じ意味です。図では矢印がクラスタの枠を指していますが、DaemonSet が持つのは枠の中の各ノードの Pod です(DaemonSet を消すと、その Pod も消えます)。
参考:DaemonSet | Kubernetes、kube-controller-manager | Kubernetes、DaemonSet | Kubernetes API Reference
DaemonSet とは
DaemonSet は すべての(または一部の)ノードで、Pod の写しを 1 つずつ動かすためのワークロードのリソース です。公式ドキュメントによると、ノードがクラスタに加わると、そのノードにも Pod が足されます。ノードがクラスタから外されると、そのノードの Pod はガベージコレクション(要らなくなったオブジェクトを自動で片付ける仕組み)で片付けられます。DaemonSet を消すと、DaemonSet が作った Pod も消されます。
名前の「デーモン(daemon)」は一般に、裏で動き続けるプログラムのことです。第4回では kubelet を各ノードで常に裏で動き続けるデーモンとして紹介しました。公式ドキュメントは DaemonSet を、ノードの中で使う機能を提供する Pod を定義するもの、と説明しています。そうした機能にはクラスタの動作の土台になるもの(ネットワークを助けるツールなど)も、アドオンの一部になるものもあります。公式ドキュメントが挙げる典型的な使い方は、次の 3 つです。
- すべてのノードで、クラスタのストレージのデーモンを動かす
- すべてのノードで、ログを集めるデーモンを動かす
- すべてのノードで、ノードを監視するデーモンを動かす
DaemonSet はすでに第4回で見ています。kind のクラスタの kube-system では、kube-proxy と、ネットワークプラグインの kindnetd(DaemonSet の名前は kindnet)が DaemonSet として動いていました。どちらもノードごとに 1 つずつ要る機能です。公式ドキュメントも、ネットワークプラグインには DaemonSet として動く部品が含まれることが多い、と書いています。その部品は、自分が動いているノードでクラスタのネットワークが働くようにします。
参考:DaemonSet | Kubernetes、Implementation details | Kubernetes、kind のソースコード(kindnet)、Configuration | kind
なぜ DaemonSet が必要なのか
第12回の Deployment では、Pod の数は spec.replicas に書いた数で決まり、どのノードに置くかは kube-scheduler が選びました(第4回)。そのため Deployment では、ノードごとに Pod が 1 つずつ置かれるとは限らず、ノードが増えても Pod の数は replicas のままだと考えられます。公式ドキュメントも、Deployment は Pod をどのホストで動かすかを正確に決めることより、数の増減や更新の展開のほうが大事な、フロントエンドのような状態を持たないサービス(stateless services)に使うよう勧めています。一方 DaemonSet は、すべての(または特定の)ホストで Pod の写しがいつも動いていることが大事なときに使う、としています。ここでのホストはノードのこと、サービスは Service のリソースではなくアプリケーションのことだと読めます。
参考:DaemonSet | Kubernetes、Deployments | Kubernetes、Kubernetes Scheduler | Kubernetes
ノードごとに Pod を動かす方法はほかにもあります。公式ドキュメントが DaemonSet の代わりとして挙げる方法のうち、これまでの回に出てきたものを DaemonSet と比べると、次の表のとおりです。
| 方法 | 公式ドキュメントの説明 |
|---|---|
ノードを指定した Pod を直接作る(spec.nodeName にノードの名前を書くなど。spec.nodeName は第5回) | 作れる。ただし DaemonSet は、ノードの故障や、カーネルの更新のようなノードを止める保守作業など、どんな理由で消されたり止まったりした Pod でも作り直す。そのため、Pod を 1 つずつ作るより DaemonSet を使うよう勧めている |
| static Pod(第4回) | kubelet が見ているディレクトリにファイルを置いて作る。DaemonSet と違い、kubectl などの API のクライアントで管理できない。kube-apiserver に頼らないので、クラスタの立ち上げに向く。将来、非推奨になるかもしれない |
| Deployment(第12回) | Pod を作り、その Pod が止まらずに動き続けることを前提にする点は DaemonSet と同じ。Pod をどのノードで動かすかより、数の増減や更新が大事なときに使う |
参考:DaemonSet | Kubernetes、Assigning Pods to Nodes | Kubernetes
Deployment と DaemonSet の違いを、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| 項目 | Deployment | DaemonSet |
|---|---|---|
| Pod の数 | spec.replicas に書く | replicas のフィールドが無い。Pod を置く対象のノードの数で決まる |
| Pod を置くノード | kube-scheduler が選ぶ | daemonset-controller が対象のノードを決め、ノードごとに 1 つ作る。ノードへの割り当て(binding)は、ふつうは kube-scheduler(第4回)が行う |
| Pod を作るもの | Deployment が作った ReplicaSet(replicaset-controller) | daemonset-controller |
| 更新の進め方の既定 | 新しい Pod を先に作ることがある(maxSurge が 25%。第13回) | maxSurge が 0。古い Pod を止めてから、その代わりに新しい Pod を作る(「更新の進め方」で説明) |
| リビジョンの履歴の置き場所 | 古い ReplicaSet | ControllerRevision というリソース(第14回でも名前だけ出てきた) |
参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference、Deployments | Kubernetes、Perform a Rollback on a DaemonSet | Kubernetes
DaemonSet の仕組み
マニフェストに書くフィールド
以下は Pod の写しを各ノードで 1 つずつ動かす DaemonSet のマニフェストです。公式ドキュメントの DaemonSet の例(controllers/daemonset.yaml)は fluentd-elasticsearch のイメージを動かすもので、ノードの /var/log をコンテナから見えるようにする設定(hostPath。第26回)も入っています。この回では Pod が置かれるノードだけを見るので、フィールドの形は公式ドキュメントの例に沿いつつ、コンテナは第12回までと同じ nginx:1.14.2 にしています。volumes と volumeMounts、resources などは外しました。後半の「手元で試す」でも、このマニフェストを使います。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-daemonset
labels:
app: nginx-daemonset
spec:
selector:
matchLabels:
app: nginx-daemonset
template:
metadata:
labels:
app: nginx-daemonset
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference、Deployments | Kubernetes
公式ドキュメントの例にはこのほかに tolerations のフィールドがあります。これはコントロールプレーンのノードでも Pod を動かすための設定で、「Taint と Toleration」で説明します。このマニフェストにはまだ書いていません。
spec は selector と template が中心で、Deployment から replicas を除いたものに近い形です。この回にかかわるフィールドを、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.selector | DaemonSet が管理する Pod を選ぶラベルセレクタ。必須。ひな形のラベルに合っていなければ API が受け付けない。作ったあとは変えられない | なし |
spec.template | Pod のひな形。必須。DaemonSet は、ひな形のノードセレクタに合うすべてのノード(ノードセレクタが無ければすべてのノード)に、この Pod の写しをちょうど 1 つずつ作る。restartPolicy に書けるのは Always だけ | なし |
spec.updateStrategy.type | ひな形を変えたときの Pod の入れ替え方。RollingUpdate か OnDelete | RollingUpdate |
spec.minReadySeconds | 新しく作った Pod が、コンテナが落ちずに Ready のまま、この秒数がたつと「使える(available)」とみなされる | 0 |
spec.revisionHistoryLimit | 切り戻しのために残す古い履歴の数 | 10 |
参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference
表の spec.template の説明に出てくる「ノードセレクタ」は、Pod のひな形の spec.nodeSelector のことです。次の「Pod を置くノードの決まり」で説明します。Taint があるノードの扱いは「Taint と Toleration」で説明します。
公式ドキュメントによると、spec.selector を作ったあとで変えられないのは、変えると Pod が意図せず持ち主を失う(孤立する)おそれがあり、利用者にもわかりにくかったからです。DaemonSet の名前は DNS のサブドメイン名(英小文字、数字、-、. で作る、253 文字までの名前)として正しい形でなければなりません。
Pod を置くノードの決まり
公式ドキュメントによると、DaemonSet は「対象になる(eligible)」すべてのノードで Pod の写しを動かすために使えます。どのノードが対象になるかは、まず Pod のひな形に書いたノードの条件で決まります。後で説明する Taint と Toleration もかかわります。
その条件に使うのが、ノードのラベルです。第4回で見たとおり、ラベルはリソースに付けるキーと値の組で、Pod だけでなくノードにも付けられます。たとえば kubeadm はコントロールプレーンのノードに node-role.kubernetes.io/control-plane というラベルを付けます。ラベルの仕組みは第42回で扱います。
参考:DaemonSet | Kubernetes、Labels and Selectors | Kubernetes、Implementation details | Kubernetes
ノードのラベルで対象を絞るフィールドは、次の 2 つです。
| フィールド | 書くと |
|---|---|
spec.template.spec.nodeSelector | daemonset-controller はこのノードセレクタに合う(ラベルが合う)ノードに Pod を作る |
spec.template.spec.affinity | daemonset-controller はこのノードアフィニティ(ノードの条件をより細かく書ける仕組み)に合うノードに Pod を作る |
公式ドキュメントによると、どちらも書かなければ、daemonset-controller はすべてのノードに Pod を作ります。ただし、この「すべてのノード」はラベルで絞らないという意味で、後で説明する、許容しない NoSchedule の Taint があるノードは別だと考えられます。公式ドキュメントの例のマニフェストにも、コントロールプレーンのノードで動かすために Toleration を書いている、というコメントがあるからです。この回のマニフェストはどちらも書いていないので、Taint の無いノード(この回のクラスタではワーカーの 2 つ)が対象になるはずです。nodeSelector と Node Affinity の違いは第31回で扱います。
対象のノードが決まったあと、Pod がノードに置かれるまでの流れは次のとおりです。公式ドキュメントによると、daemonset-controller は対象のノードごとに Pod を 1 つ作り、その Pod の spec.affinity.nodeAffinity に、そのノードを指す条件を書き込みます。Pod ができると、ふつうは既定のスケジューラー(kube-scheduler)が引き継ぎ、spec.nodeName にそのノードの名前を入れて、Pod をノードに割り当てます(binding。第5回)。daemonset-controller が書き込む条件の形は、公式ドキュメントでは次のとおりです。
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: In
values:
- target-host-namerequiredDuringSchedulingIgnoredDuringExecution は Pod をノードに割り当てるときに必ず満たす必要がある条件、という意味のフィールドです。matchFields はノードのラベルではなくノードのフィールドで条件を書く指定です。詳しくは第31回で扱います。metadata.name がノードの名前、target-host-name のところに対象のノードの名前が入ります。つまり「名前がこのノードであること」という条件で、Pod を置けるノードを 1 つに決めています。ひな形にノードアフィニティを書いていた場合、daemonset-controller は対象のノードを決めるときにはそれを使い、作った Pod ではこのノードの名前の条件に置き換えます。
公式ドキュメントによると、新しい Pod がノードに収まらないときは、既定のスケジューラーが新しい Pod の優先度(priority)をもとに、すでにあるほかの Pod を追い出す(preempt)ことがあります。各ノードで動くことが大事な DaemonSet では、ひな形の spec.priorityClassName で高い優先度を設定しておくとよい、とも書かれています。優先度の仕組みはこのシリーズでは扱いません。
参考:DaemonSet | Kubernetes、Pod Priority and Preemption | Kubernetes
DaemonSet の Pod の名前は、第4回で予告したとおり、DaemonSet の名前の後ろに文字列が付いたものになります。v1.37 時点のソースコードでは、コントローラーが Pod を作るとき、generateName(第6回)に <DaemonSet の名前>- を入れています。そのため、この回の Pod は nginx-daemonset- の後ろに一意な文字列が付いた名前になるはずです。Deployment の Pod と違い、間に ReplicaSet のハッシュは入りません。
参考:Object Names and IDs | Kubernetes、Kubernetes のソースコード(controller_utils.go)
ノードやラベルが変わったとき
DaemonSet の Pod の数はノードの増減やラベルの変化に合わせて変わります。公式ドキュメントの説明を次の表にまとめます。
| 起きたこと | DaemonSet がすること |
|---|---|
| ノードがクラスタに加わった | そのノードにも Pod を足す |
| ノードがクラスタから外された | そのノードの Pod はガベージコレクションで片付けられる |
| ノードのラベルが変わり、条件に合うようになった | そのノードにすぐ Pod を足す |
| ノードのラベルが変わり、条件に合わなくなった | そのノードの Pod をすぐ消す |
| DaemonSet の Pod が消されたり止まったりした | 作り直す |
参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes、DaemonSet | Kubernetes
作り直された Pod は消された Pod と同じノードに置かれると考えられます。daemonset-controller は、Pod が無い対象のノードごとに Pod を作り、そのノードの名前を条件に書き込むからです。名前は generateName で付くので、前の Pod とは違う名前になるはずです。
DaemonSet の status には、ノードの数を表すフィールドが並びます。Pod の数ではない点に注意します。API リファレンスの説明を次の表にまとめます。表の説明は API リファレンスの定義の訳です。
| フィールド | 数えるもの |
|---|---|
status.desiredNumberScheduled | DaemonSet の Pod を動かすべきノードの数(正しく動かしているノードも含む) |
status.currentNumberScheduled | DaemonSet の Pod を動かすべきノードのうち、少なくとも 1 つの Pod を動かしているノードの数 |
status.numberReady | DaemonSet の Pod を動かすべきノードのうち、Ready の状態の Pod が 1 つ以上動いているノードの数 |
status.numberMisscheduled | DaemonSet の Pod を動かしているが、動かすべきではないノードの数 |
Taint と Toleration
Taint(読み:テイント)は、ノードが Pod を寄せ付けないようにする印です(第4回)。Toleration(読み:トレレーション)は Pod に書く設定で、合う Taint があるノードにも、スケジューラーが Pod を置けるようにします。公式ドキュメントによると、ノードに 1 つ以上の Taint があると、そのノードは、その Taint を許容しない Pod を受け入れないよう印が付いたことになります。Taint と Toleration を組み合わせて、Pod が合わないノードに置かれないようにします。ただし、Toleration は置くことを許すだけで、そのノードに置かれることを保証するものではありません。
Taint にはキー、値、効果(effect)があります。効果は次の 3 つです。
| 効果 | 公式ドキュメントの説明 |
|---|---|
NoSchedule | 合う Toleration を持たない新しい Pod は、そのノードに置かれない。すでに動いている Pod は追い出されない |
PreferNoSchedule | NoSchedule のゆるい版。許容しない Pod をそのノードに置くのを避けようとするが、保証はしない |
NoExecute | すでに動いている Pod にも効く。許容しない Pod は、すぐに追い出される |
第4回で見たとおり、kubeadm はコントロールプレーンのノードに node-role.kubernetes.io/control-plane:NoSchedule の Taint を付けます。キーが node-role.kubernetes.io/control-plane、効果が NoSchedule の Taint です。kind のマルチノードのクラスタも kubeadm で作られるので、コントロールプレーンのノードにはこの Taint があるはずです。そのため、Toleration を書かない DaemonSet の Pod は、マルチノードのクラスタのコントロールプレーンのノードでは動かない と考えられます。公式ドキュメントの例のマニフェストのコメントにも、この Toleration はコントロールプレーンのノードで DaemonSet を動かすためのもので、コントロールプレーンのノードで Pod を動かすべきでないなら外すように、と書かれています。
参考:Implementation details | Kubernetes、DaemonSet | Kubernetes、Configuration | kind
Pod をノードに置く場面で Taint を見ることが公式ドキュメントにはっきり書かれているのは、kube-scheduler です。API リファレンスによると、NoSchedule の Taint はスケジューラーが守らせます。一方、daemonset-controller も Pod を作る前に Taint を見ていると考えられます。v1.37 時点のソースコードでは、daemonset-controller はノードの Taint を Pod の Toleration と照らし合わせ、許容されない NoSchedule や NoExecute の Taint があるノードを、Pod を作る対象から外しています。そのため、コントロールプレーンのノードの分の Pod は、作られたまま Pending で止まるのではなくそもそも作られないと考えられます。
参考:Toleration | Kubernetes API Reference、Taints and Tolerations | Kubernetes、Kubernetes のソースコード(daemon_controller.go)
コントロールプレーンのノードでも動かすには、ひな形の spec に次の tolerations を足します。公式ドキュメントの例と同じ書き方です。
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule公式ドキュメントの例には、キーが node-role.kubernetes.io/master の項目もあります。v1.37 時点の kubeadm がコントロールプレーンのノードに付けるのは node-role.kubernetes.io/control-plane の Taint なので、この回では 1 つだけにしています。Toleration の各フィールドを、API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 |
|---|---|
key | 許容する Taint のキー |
operator | キーと値の関係。主に使うのは Equal(値が同じ)と Exists(値を問わない)。既定は Equal。ほかに数で比べる Lt と Gt もあるが、v1.37 時点ではアルファの機能で、既定では使えない(機能ゲート TaintTolerationComparisonOperators を有効にする必要がある) |
value | 許容する Taint の値。operator が Exists なら空にする |
effect | 許容する Taint の効果。空ならすべての効果に合う |
参考:Toleration | Kubernetes API Reference、Taints and Tolerations | Kubernetes、Feature Gates | Kubernetes
公式ドキュメントによると、Toleration が Taint に「合う」のは、キーと効果が同じで、さらに operator が Exists であるか、operator が Equal で値も同じときです。上の Toleration は Exists なので、キーが node-role.kubernetes.io/control-plane で効果が NoSchedule の Taint なら、値を問わず許容します。
以下の図は Toleration の有無で、DaemonSet の Pod が置かれるノードがどう変わるかを表しています。上下の段はそれぞれ別のマニフェストを渡した場合です。
ワーカーのノードには Taint が無いので、どちらの場合も Pod が置かれます。違いが出るのは Taint のあるコントロールプレーンのノードだけです。図の Taint は異常な状態ではなく kubeadm が意図して付けた印です。Taint と Toleration の仕組みは第32回で詳しく扱います。
DaemonSet の Pod には、利用者が書かなくても daemonset-controller が自動で足す Toleration があります。v1.37 時点の公式ドキュメントの表を次にまとめます。この回では、コントロールプレーンのノードの Taint がこの表に無いことだけを押さえれば十分です。表の各行の Taint は、Taint と Toleration を扱う第32回と、ノードの drain を扱う第36回で関係してきます。
| Taint のキー | 効果 | 許容することで起きること |
|---|---|---|
node.kubernetes.io/not-ready | NoExecute | 調子が悪いか、Pod を受け入れる準備ができていないノードにも置ける。そのノードで動いている Pod は追い出されない |
node.kubernetes.io/unreachable | NoExecute | ノードのコントローラーから届かないノードにも置ける。そのノードで動いている Pod は追い出されない |
node.kubernetes.io/disk-pressure | NoSchedule | ディスクが足りなくなっているノードにも置ける |
node.kubernetes.io/memory-pressure | NoSchedule | メモリが足りなくなっているノードにも置ける |
node.kubernetes.io/pid-pressure | NoSchedule | プロセスの数が足りなくなっているノードにも置ける |
node.kubernetes.io/unschedulable | NoSchedule | Pod を新しく置かないよう印を付けたノードにも置ける |
node.kubernetes.io/network-unavailable | NoSchedule | ネットワークが使えないノードにも置ける。ノードのネットワークをそのまま使う Pod(spec.hostNetwork: true。第4回)にだけ足される |
参考:DaemonSet | Kubernetes、Taints and Tolerations | Kubernetes
公式ドキュメントは、node.kubernetes.io/unschedulable の Toleration が自動で付くので、Pod を新しく置かないよう印を付けたノードでも DaemonSet の Pod が動く、と説明しています。続けて、クラスタのネットワークのような大事な機能を DaemonSet で動かすなら、ノードの準備ができる前に Pod が置かれるほうが助かる、とも書いています。この特別な Toleration が無いと、ネットワークプラグインが動いていないのでノードの準備ができず、ノードの準備ができていないのでネットワークプラグインが動かない、という行き詰まり(デッドロック)になりかねない、という例です。コントロールプレーンのノードの Taint は、この表に入っていません。そのため利用者が自分で Toleration を書く必要があります。
更新の進め方
Pod のひな形(spec.template)を変えたときの入れ替え方は、spec.updateStrategy.type で決まります。公式ドキュメントによると、RollingUpdate の DaemonSet では、spec.template をどう変えても、ローリングアップデートが始まります。2 つの値を、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| 値 | 入れ替え方 |
|---|---|
RollingUpdate(既定) | ひな形を変えると、古い Pod を止め、新しい Pod を自動で作る。制御されたやり方で、ノードごとに順に入れ替えていく |
OnDelete | ひな形を変えても、自動では入れ替えない。利用者が古い Pod を消したときだけ、新しい Pod が作られる |
参考:Perform a Rolling Update on a DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference
RollingUpdate の進め方は次の 2 つのフィールドで変えられます。API リファレンスの説明では、maxUnavailable は Pod の数、maxSurge はノードの数で書かれています。例ではどちらも Pod を動かすべきノードの数(status.desiredNumberScheduled)に対する割合で説明しています。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.updateStrategy.rollingUpdate.maxUnavailable | 更新のあいだに使えなくてもよい DaemonSet の Pod の数の上限。数か割合で書き、割合は切り上げて数に直す。maxSurge が 0 なら 0 にはできない | 1 |
spec.updateStrategy.rollingUpdate.maxSurge | 更新のあいだに、古い Pod が使える状態のまま、新しい Pod も作ってよいノードの数の上限。数か割合で書き、割合は切り上げて数に直す。切り上げた結果は少なくとも 1 になる(API リファレンスの「rounding up to a minimum of 1」)。maxUnavailable が 0 なら 0 にはできない | 0 |
参考:DaemonSet | Kubernetes API Reference、Perform a Rolling Update on a DaemonSet | Kubernetes
第13回の Deployment と同じ名前のフィールドですが、既定値が違います。Deployment は maxSurge が 25% で、新しい Pod を先に作ることがありました。DaemonSet の既定の maxSurge は 0 です。更新の手順のページは、RollingUpdate では更新のあいだ、各ノードで動く DaemonSet の Pod は多くても 1 つだと書いています。これは maxSurge が 0 の既定のときに当てはまります。API リファレンスの maxUnavailable の説明によると、更新はまず古い Pod を上限の数まで止め、その代わりに新しい Pod を作るところから始まります。新しい Pod が使えるようになると、ほかの Pod に進みます。既定のままなら、使えない Pod は多くても 1 つ(maxUnavailable が 1)です。API リファレンスによると、maxSurge を 1 以上にすると、新しい Pod が使えるようになってから、そのノードの古い Pod が消されます。API リファレンスは、そのとき新しい Pod の準備ができたかの確認が失敗すると、そのノードで DaemonSet が使う資源が 2 倍になり得る、とも注意しています。
参考:Perform a Rolling Update on a DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference、Deployments | Kubernetes
リビジョンの履歴は第14回の StatefulSet と同じく、ControllerRevision というリソースに残ります。公式ドキュメントによると、ControllerRevision は DaemonSet のリビジョンごとのアノテーションとひな形を保存しています。第12回と同じく、kubectl rollout history で履歴を見て、kubectl rollout undo で前のリビジョンに戻せます。kubectl rollout undo は ControllerRevision に保存されたひな形で DaemonSet のひな形を置き換えます。kubectl apply などで前のひな形に戻すのと同じことです。
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。DaemonSet を作り、Pod がワーカーのノード 2 つに 1 つずつ置かれることを見ます。続けて Toleration を足し、コントロールプレーンのノードにも Pod が置かれて、3 つのノードに 1 つずつになることを確かめます。最後に Pod を 1 つ消し、同じノードに作り直されることを見ます。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | マルチノード(クラスタ名 multi-node):コントロールプレーン 1、ワーカー 2 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。第5回から第14回まではシングルノードのクラスタを使いましたが、この回は第2回〜第4回と同じマルチノードのクラスタを使います。ノードごとに Pod が置かれる様子は、ノードが 1 つでは見分けられないからです。公式ドキュメントの DaemonSet の更新の手順も、コントロールプレーンを兼ねないノードを 2 つ以上持つクラスタで試すよう勧めています。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。マルチノードのクラスタを用意する
第2回と同じ次の設定ファイルを使います。公式ドキュメントの例をそのまま引用しています。kind-multi-node.yaml という名前で、コマンドを実行するディレクトリに保存します。第2回〜第4回で保存したファイルが残っていれば、それを使えます。
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerまず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
multi-node が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の kind が表示されても、この回では使いません。手元のマシンの資源(メモリなど)に余裕がなければ、kind delete cluster --name kind で消してから進めてください。
$ kind create cluster --name multi-node --config kind-multi-node.yaml
$ kind get clusters
kind get clusters に multi-node が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-multi-node を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-multi-node
ノードが 3 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。NAME 列のノードの名前は、あとで Pod が置かれたノードと見比べるので控えておきます。第4回では、kind の公式ドキュメントの例に倣うと、コントロールプレーンのノードのコンテナは multi-node-control-plane という名前になると考えられる、と書きました。kubelet は既定ではホスト名でノードを登録する(第4回)ので、たとえばコントロールプレーンのノードは multi-node-control-plane のような名前になると考えられます。ワーカーのノードの名前は公式ドキュメントでは確かめられなかったので、この表示で確かめてください。また第4回で見たとおり、kubectl get nodes -l node-role.kubernetes.io/control-plane --context kind-multi-node を実行すると、コントロールプレーンのノードだけが表示されます。
参考:kubectl get | Kubernetes、Node Status | Kubernetes、Implementation details | Kubernetes、kubelet | Kubernetes
次に、各ノードの Taint のキーを表示します。Taint はノードの spec.taints に入っています。第5回で見た -o jsonpath を使います。{range .items[*]} から {end} までは、一覧の項目(ここではノード)ごとに繰り返す部分で、[*] は並んだ項目のすべてを表します。ノードごとに、名前、タブ、Taint のキーを 1 行に表示します。公式ドキュメントの JSONPath のページにある例と同じ形です。
$ kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.taints[*].key}{"\n"}{end}' --context kind-multi-node
参考:Node | Kubernetes API Reference、JSONPath Support | Kubernetes
コントロールプレーンのノードの行には、名前の後ろに node-role.kubernetes.io/control-plane が表示されるはずです。ワーカーの 2 つの行には Taint が無いので、名前だけが表示されると考えられます。
DaemonSet を作り、ワーカーのノードに置かれるのを見る
「マニフェストに書くフィールド」のマニフェストを、nginx-daemonset.yaml という名前で、コマンドを実行するディレクトリに保存します。tolerations はまだ書きません。このファイルはあとでエディタで書き換えます。
ここからは端末を 2 つ使います。第11回・第13回・第14回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でマニフェストを渡したり Pod を消したりします。この回で 1 つ目の端末が追うのは、第14回と同じく Pod です。
1 つ目の端末で、app: nginx-daemonset の Pod を監視します。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。-o wide を付けて、Pod が置かれたノードの名前(NODE 列)も表示します(第3回・第4回)。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch -l app=nginx-daemonset -o wide --context kind-multi-node
Pod がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-daemonset.yaml --context kind-multi-node
daemonset.apps/nginx-daemonset が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には、nginx-daemonset- で始まる名前の Pod の行が書き足されていくはずです。行は Pending、ContainerCreating、Running と移っていくと考えられます。クラスタを作ったばかりなら、イメージの取得に時間がかかるので、ContainerCreating のまましばらく止まって見えることがあります(第9回)。
1 つ目の端末の表示が落ち着いたら、2 つ目の端末で、Pod が置かれたノードの名前と一緒に一覧します。公式ドキュメントの DaemonSet の更新の手順でも、DaemonSet の Pod が置かれていないノードを探すときに、kubectl get nodes の出力と、この形のコマンドの出力を見比べています。
$ kubectl get pods -l app=nginx-daemonset -o wide --context kind-multi-node
Pod は 2 つ表示されるはずです。NODE 列を「マルチノードのクラスタを用意する」で控えたノードの名前と見比べると、2 つのワーカーのノードに 1 つずつ置かれ、コントロールプレーンのノードには置かれていないはずです。コントロールプレーンのノードには NoSchedule の Taint があり、この DaemonSet の Pod はそれを許容していないからです。Pod の名前は nginx-daemonset- の後ろに文字列が付いた形で、Deployment の Pod と違い、ReplicaSet のハッシュの部分は無いはずです。
参考:Taints and Tolerations | Kubernetes、DaemonSet | Kubernetes
続けて、DaemonSet の status から、ノードの数を表すフィールドを取り出します。Taint を表示したときと同じく -o jsonpath を使い、desiredNumberScheduled、currentNumberScheduled、numberReady を 1 行ずつ表示します。
$ kubectl get daemonset nginx-daemonset -o jsonpath='{.status.desiredNumberScheduled}{"\n"}{.status.currentNumberScheduled}{"\n"}{.status.numberReady}{"\n"}' --context kind-multi-node
参考:DaemonSet | Kubernetes API Reference、JSONPath Support | Kubernetes
3 行とも 2 になるはずです。Pod を動かすべきノードが 2 つ、Pod を動かしているノードが 2 つ、Ready の Pod があるノードが 2 つ、という意味です。マニフェストに数を書いていないのに 2 になるのは、Pod の数が対象のノードの数で決まるからです。3 行目が 2 より小さければ、Pod がまだ Ready になっていないので、少し待ってからもう一度実行します。kubectl get daemonset nginx-daemonset --context kind-multi-node でも数の列が並びますが、各列の意味は公式ドキュメントでは確かめられなかったため、この回では status のフィールドで確かめます。
Toleration を足し、3 つのノードに置かれるのを見る
2 つ目の端末で、nginx-daemonset.yaml をテキストエディタで開き、ひな形の spec に tolerations を足して保存します。書き換えたあとの template: の部分は次のとおりです。tolerations の 4 行を、containers: と同じ深さに足します。ほかの行は変えません。
template:
metadata:
labels:
app: nginx-daemonset
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80参考:DaemonSet | Kubernetes、Toleration | Kubernetes API Reference
書き換えたマニフェストを渡します。
$ kubectl apply -f nginx-daemonset.yaml --context kind-multi-node
ひな形(spec.template)を変えたので、DaemonSet のローリングアップデートが始まります。2 つ目の端末で、すぐに展開の進み具合を見ます。ds は DaemonSet の短い名前で、ds/nginx-daemonset は nginx-daemonset という DaemonSet を指します。
$ kubectl rollout status ds/nginx-daemonset --context kind-multi-node
参考:Perform a Rolling Update on a DaemonSet | Kubernetes、kubectl rollout status | Kubernetes、Command line tool (kubectl) | Kubernetes
公式ドキュメントの DaemonSet の更新の手順の例では、展開が終わったときの出力は次のとおりです。例の DaemonSet の名前は fluentd-elasticsearch です。
手元では名前の部分が nginx-daemonset になり、最後に successfully rolled out で終わる行が出るはずです。途中で、展開を待っていることを知らせる行が先に出ることもあると考えられます。
1 つ目の端末を見ると、行が書き足されていくはずです。起きると考えられることは、次の 2 つです。
| 起きること | 1 つ目の端末の見え方 |
|---|---|
| コントロールプレーンのノードが対象に加わり、Pod が作られる | NODE 列がコントロールプレーンのノードの、新しい名前の Pod の行が現れ、やがて Running になる |
| ワーカーのノードの Pod が、新しいひな形の Pod に入れ替わる | 前の 2 つの Pod の行が 1 つずつ Terminating になり、そのたびに、同じ NODE 列の値を持つ新しい名前の Pod の行が現れる |
参考:Perform a Rolling Update on a DaemonSet | Kubernetes、Pod Lifecycle | Kubernetes
ワーカーのノードの Pod も入れ替わるのは、ひな形が変わったので、前の Pod が古いひな形の Pod になったからです。既定の maxUnavailable は 1、maxSurge は 0 なので、API リファレンスの説明のとおり、ワーカーのノードの Pod は 1 つずつ、古い Pod を止めてから新しい Pod を作る順で入れ替わります。コントロールプレーンのノードの Pod が作られるのと、ワーカーのノードの Pod が入れ替わるのと、どちらが先になるかは公式ドキュメントには書かれていません。
参考:DaemonSet | Kubernetes API Reference、Perform a Rolling Update on a DaemonSet | Kubernetes
successfully rolled out の行が出たら、2 つ目の端末で、もう一度ノードの名前と一緒に一覧します。
$ kubectl get pods -l app=nginx-daemonset -o wide --context kind-multi-node
Pod は 3 つ表示され、NODE 列の値がすべて違うはずです。控えておいた 3 つのノードの名前と見比べると、コントロールプレーンのノードとワーカーのノード 2 つに、1 つずつ置かれていることがわかります。前の Pod が Terminating で残っていたら、少し待ってからもう一度実行します。
続けて、「DaemonSet を作り、ワーカーのノードに置かれるのを見る」と同じ -o jsonpath のコマンドで、status を表示します。
$ kubectl get daemonset nginx-daemonset -o jsonpath='{.status.desiredNumberScheduled}{"\n"}{.status.currentNumberScheduled}{"\n"}{.status.numberReady}{"\n"}' --context kind-multi-node
3 行とも 3 になるはずです。そうなれば、Toleration を足したことで、Pod を動かすべきノードが 2 つから 3 つに増えたことになります。
最後に、リビジョンの履歴を表示します。
$ kubectl rollout history ds/nginx-daemonset --context kind-multi-node
公式ドキュメントの DaemonSet の切り戻しの手順では、kubectl rollout history daemonset <daemonset-name> と書かれています。daemonset <名前> と ds/<名前> はどちらも同じ DaemonSet を指します。その手順での出力の形は次のとおりです(<daemonset-name> と ... は、手元の値に置き換わる部分です)。
参考:Perform a Rollback on a DaemonSet | Kubernetes、Command line tool (kubectl) | Kubernetes
手元では、REVISION 列に 1 と 2 の 2 行が並ぶはずです。1 が最初に作ったときのひな形、2 が Toleration を足したひな形です。公式ドキュメントによると、CHANGE-CAUSE 列の値は、リビジョンが作られるときに DaemonSet の kubernetes.io/change-cause のアノテーションから写されます。この回の手順ではこのアノテーションを付けていないので、CHANGE-CAUSE 列は第12回と同じく <none> になると考えられます。
Pod を消して、同じノードに作り直されるのを見る
2 つ目の端末で、ワーカーのノードに置かれた Pod を 1 つ消します。<Pod の名前> には、直前の一覧で NODE 列がワーカーのノードになっている Pod の名前を 1 つ入れます。どのノードの Pod だったかを控えておきます。
$ kubectl delete pod <Pod の名前> --context kind-multi-node
第6回で見たとおり、kubectl delete は既定では Pod が消えるまで待ってから終わるので、終わるまで少しかかることがあります。
1 つ目の端末には、消した Pod の行が Terminating になり、新しい名前の Pod の行が現れるはずです。公式ドキュメントによると、DaemonSet は消された Pod を作り直します。daemonset-controller は Pod が無くなったノードのために Pod を作るので、新しい Pod の NODE 列は、消した Pod と同じノードの名前になるはずです。名前は generateName で付くので、消した Pod とは違う名前になるはずです。新しい Pod が、消した Pod が止まりきる前に作られるかどうかは、公式ドキュメントには書かれていません。
参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes、Object Names and IDs | Kubernetes
監視を続けているので、画面には消す前の Pod の行と、作り直された Pod の行が並びます。kubectl delete のあとに書き足された行の見分け方を、次の表にまとめます。
| 行 | 見分け方 |
|---|---|
| 消した Pod の行 | 名前が消した Pod と同じ。STATUS 列が Terminating |
| 作り直された Pod の行 | 名前が新しい。STATUS 列が Pending か ContainerCreating から始まり、やがて Running になる。NODE 列は消した Pod と同じ |
1 つ目の端末で新しい Pod が Running になったら、2 つ目の端末で、もう一度一覧します。
$ kubectl get pods -l app=nginx-daemonset -o wide --context kind-multi-node
Pod は 3 つで、NODE 列の値がすべて違うはずです。3 つのノードに 1 つずつ、という状態に戻っています。消した Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。
片付け
1 つ目の端末で Ctrl+C を押して、監視を止めます。
2 つ目の端末で、作った DaemonSet を、マニフェストを指定して削除します。公式ドキュメントによると、DaemonSet を消すと、DaemonSet が作った Pod も消されます。
$ kubectl delete -f nginx-daemonset.yaml --context kind-multi-node
少し待ってから、残っていないかを確かめます。
$ kubectl get daemonsets,pods --context kind-multi-node
Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。第8回と同じく、見つからないことを知らせる 1 行が出れば、削除できています。第8回では、この 1 行は No resources found in default namespace. のような表示になると推測しました。この一覧は default の Namespace だけが対象なので、kube-system の kube-proxy や kindnet の DaemonSet は表示されません。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster --name multi-node
$ kind get clusters
kind delete cluster は、存在しないクラスタを指定してもエラーを返しません。kind get clusters に multi-node が表示されなくなったことを確かめます。kubeconfig に kind-multi-node のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-multi-node で消します。
注意点
DaemonSet でつまずきやすい点と、後から変えられない設定を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
replicas は書けない | DaemonSet に replicas のフィールドは無い。Pod の数は、対象のノードの数で決まる |
| コントロールプレーンのノードには、そのままでは置かれない | kubeadm で作ったマルチノードのクラスタでは、コントロールプレーンのノードに NoSchedule の Taint がある。置くには、その Taint を許容する Toleration をひな形に書く |
| 自動で許容される Taint がある | node.kubernetes.io/unschedulable などは daemonset-controller が自動で許容する。Pod を新しく置かないよう印を付けたノード(第36回)でも、DaemonSet の Pod は置かれる |
| セレクタは作ったあとで変えられない | spec.selector は必須で、ひな形のラベルに合っている必要があり、作ったあとは変えられない |
| ノードの資源が足りないと更新が止まる | 新しい Pod を置けないノードがあると、ローリングアップデートが止まる。公式ドキュメントは、そのノードの DaemonSet 以外の Pod を消して場所を空ける方法を挙げている。消した Pod がコントローラーに管理されていなかったり、複製が無かったりすると、サービスが止まる点に注意 |
| 壊れたひな形で止まった更新は、もう一度ひな形を更新して直す | イメージの名前の打ち間違いなどで更新が進まないときは、ひな形をもう一度更新する。前の調子の悪い更新に、新しい更新は妨げられない |
| Pod を直接変えても、新しいノードには元のひな形が使われる | DaemonSet が作った Pod を直接変えても、次にノードがクラスタに加わったとき(消したノードを同じ名前で加え直したときも含む)、daemonset-controller はもとのひな形で Pod を作る |
minReadySeconds と時計のずれ | spec.minReadySeconds を設定していると、コントロールプレーンとノードの時計がずれたとき、更新の進み具合を正しく判断できなくなる |
参考:DaemonSet | Kubernetes、DaemonSet | Kubernetes API Reference、Implementation details | Kubernetes、Perform a Rolling Update on a DaemonSet | Kubernetes
壊れたひな形からの戻し方は第14回の StatefulSet と違います。StatefulSet では、ひな形を戻したあと、壊れたひな形で作られた Pod を利用者が消す必要がありました。DaemonSet の更新の手順のページは、ひな形をもう一度更新すれば、新しい更新は前の調子の悪い更新に妨げられない、と説明しています。
参考:Perform a Rolling Update on a DaemonSet | Kubernetes、StatefulSets | Kubernetes
関連記事
一度だけ動いて終わる処理を Pod で動かす Job と、決まった時刻に Job を作る CronJob については、以下の記事で解説しています。
Job と CronJob の仕組み終わりまで動いて止まる処理を Pod で動かす Job と、決まった時刻に Job を作る CronJob の仕組みを図で説明します。完了の数え方、失敗したときの再試行と backoffLimit、スケジュールとタイムゾーンの決まりを押さえ、kind のクラスタで成功・失敗・再試行と、指定した時刻に Job が作られることを確かめる手順を紹介します。この回で使った Taint と Toleration で、Pod を置くノードを制御する仕組みについては、以下の記事で解説しています。
Taint と Toleration で Pod の配置を制御するノードの側から Pod を遠ざける Taint と、それを許す Pod の Toleration の仕組みを図で説明します。3 つの効果、Toleration が Taint に合う条件、組み込みの Taint と自動で足される Toleration を押さえ、kind のマルチノードのクラスタで Taint を付けたノードに Pod が置かれなくなる様子と、NoExecute で Pod が追い出される様子を確かめる手順を紹介します。ノードのラベルで Pod を置くノードを選ぶ nodeSelector と Node Affinity については、以下の記事で解説しています。