|
| 1 | +--- |
| 2 | +title: "実験して入門するKubernetes:Pod起動の裏側を追っていたら、どうしてもSchedulerを止めてみたくなった件" |
| 3 | +date: 2026/03/25 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - Kubernetes |
| 7 | + - minikube |
| 8 | + - 初心者向け |
| 9 | +category: |
| 10 | + - Infrastructure |
| 11 | +thumbnail: /images/2026/20260325a/thumbnail.jpg |
| 12 | +author: 内堀航輝 |
| 13 | +lede: "Kubernetesの基礎的な挙動を追ってみることにしました。今回は、どういう流れでPodが作られているのかを実験し、そのアウトプットとしてこの記事にまとめています。" |
| 14 | +--- |
| 15 | + |
| 16 | +<img src="/images/2026/20260325a/top.jpg" alt="" width="1024" height="572"> |
| 17 | + |
| 18 | +# はじめに |
| 19 | + |
| 20 | +最近、コンテナオーケストレーションの技術領域に興味をもち、Kubernetesを触り始めました。早速自宅で飼ってるMiniPC(ホームサーバ)にArgoCDやLonghornを入れてみたものの、Deployment、Pod、Serviceなど初めましての概念ばかり。気づいたら、AIの指示通りにただコマンド打つマンになっていました。 |
| 21 | + |
| 22 | +このままではまずいと思い、Kubernetesの基礎的な挙動を追うことにしました。今回は、どのような流れでPodが作られているのかを実験し、そのアウトプットとしてこの記事にまとめています。 |
| 23 | + |
| 24 | +※実験にはMinikubeを使っています。初学者が調べながらまとめたものなので、誤りがあればご指摘ください。 |
| 25 | + |
| 26 | +# Pod起動までの流れの概要 |
| 27 | + |
| 28 | +Podが起動するまでの流れを紹介します。複数のKubernetesコンポーネントが登場しますが、この記事のメインテーマではないため、用語は簡単な説明に留めます。詳細が気になる方は、既存の解説記事や[公式ドキュメント](https://kubernetes.io/ja/docs/concepts/overview/components/)をご参照ください。 |
| 29 | + |
| 30 | +```txt |
| 31 | +kubectl run |
| 32 | + ↓ |
| 33 | +API Server → etcd(Podデータ保存、ノード未割り当て) |
| 34 | + ↓ |
| 35 | +Scheduler(最適なノードを割り当て) |
| 36 | + ↓ |
| 37 | +kubelet(コンテナイメージのPull・起動) |
| 38 | + ↓ |
| 39 | +podが起動 |
| 40 | +``` |
| 41 | + |
| 42 | +- **etcd**:Kubernetesの全状態を保存するデータベース |
| 43 | +- **API Server**:全コンポーネントからのリクエスト受付と、状態変更の通知する窓口 |
| 44 | +- **Scheduler**:Podをどのノードで動かすかを決定するスケジューラ |
| 45 | +- **kubelet**:各ノード上で実際にコンテナを起動・管理する実行エンジン |
| 46 | + |
| 47 | +> 補足:`kubectl run`はPodを直接作成します。Deployment経由の場合は、Controller ManagerがDeploymentデータ → ReplicaSetデータ → Podデータ と各種データを作成するステップが加わります。この記事では、簡略化のため`kubectl run`による直接的な作成を追いかけます。 |
| 48 | +
|
| 49 | +# 実験1:イベントログでPod作成の流れを確認する |
| 50 | + |
| 51 | +最初の実験として、実際に`kubectl run`を実行し、出力されるイベントログを確認してみます。 |
| 52 | + |
| 53 | +```sh |
| 54 | +% kubectl run log-test --image=nginx |
| 55 | +pod/log-test created |
| 56 | +% kubectl get events -w |
| 57 | +LAST SEEN TYPE REASON OBJECT MESSAGE |
| 58 | +23m Normal Scheduled pod/log-test Successfully assigned default/log-test to minikube |
| 59 | +22m Normal Pulling pod/log-test Pulling image "nginx" |
| 60 | +22m Normal Pulled pod/log-test Successfully pulled image "nginx" in 1.481s (1.481s including waiting). Image size: 180545980 bytes. |
| 61 | +22m Normal Created pod/log-test Container created |
| 62 | +22m Normal Started pod/log-test Container started |
| 63 | +``` |
| 64 | + |
| 65 | +REASON列とMESSAGE列を見ると、まずScheduledでPodがノード(ここではminikube)に割り当てられ、その後にコンテナの立ち上げ処理が始まっていることがわかります。 |
| 66 | + |
| 67 | +さらに`kubectl get events -o yaml` とオプションを追加すると、各イベントがどのコンポーネントから出力されたのかを詳しく確認できます。 |
| 68 | + |
| 69 | +<details> |
| 70 | +<summary> kubectl get events -o yaml の出力</summary> |
| 71 | + |
| 72 | +```yaml |
| 73 | +% kubectl get events -o yaml |
| 74 | +apiVersion: v1 |
| 75 | +items: |
| 76 | +- action: Binding |
| 77 | + apiVersion: v1 |
| 78 | + eventTime: "2026-03-17T08:45:27.742811Z" |
| 79 | + firstTimestamp: null |
| 80 | + involvedObject: |
| 81 | + apiVersion: v1 |
| 82 | + kind: Pod |
| 83 | + name: log-test |
| 84 | + namespace: default |
| 85 | + resourceVersion: "62245" |
| 86 | + uid: c79a534d-9c74-45bf-acf4-5a9985c39c14 |
| 87 | + kind: Event |
| 88 | + lastTimestamp: null |
| 89 | + message: Successfully assigned default/log-test to minikube |
| 90 | + metadata: |
| 91 | + creationTimestamp: "2026-03-17T08:45:27Z" |
| 92 | + name: log-test.189d9485200842b1 |
| 93 | + namespace: default |
| 94 | + resourceVersion: "62247" |
| 95 | + uid: 822856bb-0e26-4942-8d78-ac1eff975cb0 |
| 96 | + reason: Scheduled |
| 97 | + reportingComponent: default-scheduler |
| 98 | + reportingInstance: default-scheduler-minikube |
| 99 | + source: {} |
| 100 | + type: Normal |
| 101 | +- apiVersion: v1 |
| 102 | + count: 1 |
| 103 | + eventTime: null |
| 104 | + firstTimestamp: "2026-03-17T08:45:28Z" |
| 105 | + involvedObject: |
| 106 | + apiVersion: v1 |
| 107 | + fieldPath: spec.containers{log-test} |
| 108 | + kind: Pod |
| 109 | + name: log-test |
| 110 | + namespace: default |
| 111 | + resourceVersion: "62246" |
| 112 | + uid: c79a534d-9c74-45bf-acf4-5a9985c39c14 |
| 113 | + kind: Event |
| 114 | + lastTimestamp: "2026-03-17T08:45:28Z" |
| 115 | + message: Pulling image "nginx" |
| 116 | + metadata: |
| 117 | + creationTimestamp: "2026-03-17T08:45:28Z" |
| 118 | + name: log-test.189d94853f8ec93d |
| 119 | + namespace: default |
| 120 | + resourceVersion: "62249" |
| 121 | + uid: 212f6090-34de-46dd-82eb-d721ace1b32a |
| 122 | + reason: Pulling |
| 123 | + reportingComponent: kubelet |
| 124 | + reportingInstance: minikube |
| 125 | + source: |
| 126 | + component: kubelet |
| 127 | + host: minikube |
| 128 | + type: Normal |
| 129 | +- apiVersion: v1 |
| 130 | + count: 1 |
| 131 | + eventTime: null |
| 132 | + firstTimestamp: "2026-03-17T08:45:30Z" |
| 133 | + involvedObject: |
| 134 | + apiVersion: v1 |
| 135 | + fieldPath: spec.containers{log-test} |
| 136 | + kind: Pod |
| 137 | + name: log-test |
| 138 | + namespace: default |
| 139 | + resourceVersion: "62246" |
| 140 | + uid: c79a534d-9c74-45bf-acf4-5a9985c39c14 |
| 141 | + kind: Event |
| 142 | + lastTimestamp: "2026-03-17T08:45:30Z" |
| 143 | + message: 'Successfully pulled image "nginx" in 2.135s (2.135s including waiting). |
| 144 | + Image size: 180542930 bytes.' |
| 145 | + metadata: |
| 146 | + creationTimestamp: "2026-03-17T08:45:30Z" |
| 147 | + name: log-test.189d9485beda4a95 |
| 148 | + namespace: default |
| 149 | + resourceVersion: "62252" |
| 150 | + uid: 3eb2fd61-7b0b-4f62-b7c3-e5a4be893399 |
| 151 | + reason: Pulled |
| 152 | + reportingComponent: kubelet |
| 153 | + reportingInstance: minikube |
| 154 | + source: |
| 155 | + component: kubelet |
| 156 | + host: minikube |
| 157 | + type: Normal |
| 158 | +- apiVersion: v1 |
| 159 | + count: 1 |
| 160 | + eventTime: null |
| 161 | + firstTimestamp: "2026-03-17T08:45:30Z" |
| 162 | + involvedObject: |
| 163 | + apiVersion: v1 |
| 164 | + fieldPath: spec.containers{log-test} |
| 165 | + kind: Pod |
| 166 | + name: log-test |
| 167 | + namespace: default |
| 168 | + resourceVersion: "62246" |
| 169 | + uid: c79a534d-9c74-45bf-acf4-5a9985c39c14 |
| 170 | + kind: Event |
| 171 | + lastTimestamp: "2026-03-17T08:45:30Z" |
| 172 | + message: Container created |
| 173 | + metadata: |
| 174 | + creationTimestamp: "2026-03-17T08:45:30Z" |
| 175 | + name: log-test.189d9485c0e87bde |
| 176 | + namespace: default |
| 177 | + resourceVersion: "62253" |
| 178 | + uid: df68a670-7684-4573-beff-b92e5a6bbdec |
| 179 | + reason: Created |
| 180 | + reportingComponent: kubelet |
| 181 | + reportingInstance: minikube |
| 182 | + source: |
| 183 | + component: kubelet |
| 184 | + host: minikube |
| 185 | + type: Normal |
| 186 | +- apiVersion: v1 |
| 187 | + count: 1 |
| 188 | + eventTime: null |
| 189 | + firstTimestamp: "2026-03-17T08:45:30Z" |
| 190 | + involvedObject: |
| 191 | + apiVersion: v1 |
| 192 | + fieldPath: spec.containers{log-test} |
| 193 | + kind: Pod |
| 194 | + name: log-test |
| 195 | + namespace: default |
| 196 | + resourceVersion: "62246" |
| 197 | + uid: c79a534d-9c74-45bf-acf4-5a9985c39c14 |
| 198 | + kind: Event |
| 199 | + lastTimestamp: "2026-03-17T08:45:30Z" |
| 200 | + message: Container started |
| 201 | + metadata: |
| 202 | + creationTimestamp: "2026-03-17T08:45:30Z" |
| 203 | + name: log-test.189d9485c5d61a22 |
| 204 | + namespace: default |
| 205 | + resourceVersion: "62254" |
| 206 | + uid: edf2dbb3-11e1-49e6-bb91-6dbca2d51edb |
| 207 | + reason: Started |
| 208 | + reportingComponent: kubelet |
| 209 | + reportingInstance: minikube |
| 210 | + source: |
| 211 | + component: kubelet |
| 212 | + host: minikube |
| 213 | + type: Normal |
| 214 | +kind: List |
| 215 | +metadata: |
| 216 | + resourceVersion: "" |
| 217 | +``` |
| 218 | +
|
| 219 | +</details> |
| 220 | +
|
| 221 | +出力されたイベントログの `reportingComponent` フィールドを確認すると、各イベントの出力元は以下です。 |
| 222 | + |
| 223 | +- **Scheduled** → `reportingComponent: default-scheduler` |
| 224 | +- **Pulling / Pulled / Created / Started** → `reportingComponent: kubelet` |
| 225 | + |
| 226 | +概要で確認した通り、まずSchedulerがノードを割り当てた後、そのノードのkubeletがコンテナの起動処理を行っていることが、実際のログからも確認できました。 |
| 227 | + |
| 228 | +# Static PodとMirror Pod |
| 229 | + |
| 230 | +ここまででpodが起動するまでの流れはわかったのですが、ここで1つ疑問が湧きました。 |
| 231 | + |
| 232 | +先ほど確認した通り、Podが起動するにはSchedulerによるノード割り当てが必要です。しかし、そのScheduler自身もPodとして動いています。となると、Schedulerを起動するためのSchedulerが必要で、そのSchedulerを起動するためにさらにSchedulerが必要で、、、と無限ループに陥ってしまいます。最初のSchedulerは一体どのように起動されているのでしょうか? |
| 233 | + |
| 234 | +結論から言うと、 SchedulerはAPI Serverや別のSchedulerを経由せず、kubeletがマニフェストファイルから直接起動していました。 |
| 235 | + |
| 236 | +実際のSchedulerのマニフェストファイルを確認すると、以下のようになっています。kindが `Deployment` ではなく `Pod` となっており、Podデータそのものが定義されていることがわかります。 |
| 237 | + |
| 238 | +```yaml |
| 239 | +% minikube ssh |
| 240 | +$ sudo cat /etc/kubernetes/manifests/kube-scheduler.yaml |
| 241 | +apiVersion: v1 |
| 242 | +kind: Pod |
| 243 | +metadata: |
| 244 | + labels: |
| 245 | + component: kube-scheduler |
| 246 | + tier: control-plane |
| 247 | + name: kube-scheduler |
| 248 | + namespace: kube-system |
| 249 | +spec: |
| 250 | + containers: |
| 251 | + - command: |
| 252 | + - kube-scheduler |
| 253 | + ... |
| 254 | +``` |
| 255 | + |
| 256 | +このように、特定のディレクトリにマニフェストを配置し、API Serverを経由せずにkubeletが直接起動・管理するPodを**Static Pod**と呼びます。Kubernetesの起動時には、コントロールプレーンのkubeletがこの仕組みを利用して、API Serverやetcd、Schedulerなどの主要なコンポーネントをStatic Podとして起動しています。 |
| 257 | + |
| 258 | +ただ、このStatic PodはAPI Serverを経由しないため、etcd上に実体がありません。そのままでは、`kubectl get pods`で確認できず、クラスタ全体の状態を一元管理する上で不便です。そこでkubeletは、**Mirror Pod**と呼ばれる読み取り専用のPodを作成します。`kubectl get pods -n kube-system`で見える`kube-scheduler-minikube`は、実はこのMirror Podでした。 |
| 259 | + |
| 260 | +# 実験2:Schedulerを止めてみる |
| 261 | + |
| 262 | +ここまでの話が本当なら、以下が成り立つはずです。 |
| 263 | + |
| 264 | +- Mirror Podを削除しても、Schedulerは止まらない(本体はマニフェストファイルだから) |
| 265 | +- マニフェストファイルを削除(移動)すると、Schedulerが止まる |
| 266 | +- Schedulerが止まると、新しいPodはノードに割り当てられずPendingのままになる |
| 267 | + |
| 268 | +というわけで、さっそく試してみましょう。 |
| 269 | + |
| 270 | +## 1. Mirror Podを削除する |
| 271 | + |
| 272 | +`kubectl delete pod`で`kube-scheduler-minikube`を削除してみます。しかし、何度削除しても、即座に復活し、`kubectl get pods`の結果に出てきます。 |
| 273 | + |
| 274 | +```sh |
| 275 | +% kubectl delete pod kube-scheduler-minikube -n kube-system |
| 276 | +pod "kube-scheduler-minikube" deleted from kube-system namespace |
| 277 | +% kubectl get pods -n kube-system |
| 278 | +NAME READY STATUS RESTARTS AGE |
| 279 | +... |
| 280 | +kube-scheduler-minikube 1/1 Running 0 5s |
| 281 | +... |
| 282 | +``` |
| 283 | + |
| 284 | +## 2. マニフェストファイルを移動する |
| 285 | + |
| 286 | +次に`minikube ssh`でMinikubeのノード内入り、マニフェストファイルを他の場所に動かしてみます(削除すると戻すのが面倒なので`mv`にしました)。 |
| 287 | + |
| 288 | +すると、あれだけ何度削除しても復活していた`kube-scheduler-minikube`が、`kubectl get pods`から消えました。 |
| 289 | + |
| 290 | +```sh |
| 291 | +% minikube ssh |
| 292 | +$ sudo mv /etc/kubernetes/manifests/kube-scheduler.yaml /tmp/ |
| 293 | +$ exit |
| 294 | +% kubectl get pods -n kube-system |
| 295 | +NAME READY STATUS RESTARTS AGE |
| 296 | +coredns-7d764666f9-bbkvl 1/1 Running 0 11h |
| 297 | +etcd-minikube 1/1 Running 0 11h |
| 298 | +kube-apiserver-minikube 1/1 Running 0 11h |
| 299 | +kube-controller-manager-minikube 1/1 Running 0 11h |
| 300 | +kube-proxy-fj8zg 1/1 Running 0 11h |
| 301 | +storage-provisioner 1/1 Running 0 11h |
| 302 | +``` |
| 303 | + |
| 304 | +## 3. Schedulerなしで新しいPodを作成する |
| 305 | + |
| 306 | +このSchedulerがいない状態で、新しいPodを作成してみます。Podデータ自体は作成できましたが、STATUSが`Pending`のまま動かず、NODEも`<none>`のままです(必要な情報を出力するため`custom-columns`オプションをつけています)。 |
| 307 | + |
| 308 | +```sh |
| 309 | +% kubectl run new-pod --image=nginx |
| 310 | +pod/new-pod created |
| 311 | +% kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName |
| 312 | +NAME STATUS NODE |
| 313 | +hello-node-64fc5894d-57r8s Running minikube |
| 314 | +hello-node-64fc5894d-7cqrw Running minikube |
| 315 | +hello-node-64fc5894d-jl4pb Running minikube |
| 316 | +new-pod Pending <none> |
| 317 | +``` |
| 318 | + |
| 319 | +どれだけ待っても`Running`になることはありませんでした。 |
| 320 | + |
| 321 | +Schedulerがいないと、Podデータは作成されても、ノードが割り当てられないため、冒頭で確認したフローのSchedulerのステップで処理が止まってしまうことが実際の挙動でも確認できました。 |
| 322 | + |
| 323 | +# おわりに |
| 324 | + |
| 325 | +解説動画や記事を見ても、Kubernetesの各概念やその関係性がピンとこず、「デプロイするとPodが作れてその中にコンテナがいるらしい」くらいの認識でした。そんな状態だったので、当然`kubectl`コマンドの意味がわかるはずもなく、結果としてAIの指示通りにコマンド打つマンと化していました。 |
| 326 | + |
| 327 | +しかし今回、小さな実験を通して手を動かしてみたことで、裏側で各コンポーネントがどのように連携して動いているのかを具体的に追うことができ、一気に理解が深まりました。特にSchedulerは、こうして実験してみなければ存在すら知らなかった概念だったため、非常に学びになりました。 |
| 328 | + |
| 329 | +今回の実験で、何度も消されたりファイルを移動させられたりと散々な目に遭わせてしまったSchedulerには、この場を借りて感謝と謝罪を述べて締めくくりたいと思います。 |
0 commit comments