kubectl と最初の Pod
kubectl のメンタルモデルをつかみ、最初の Pod を起動します。kubectl run の命令型の一連の流れから YAML マニフェストの宣言型、get / describe / logs / exec の日常コマンド、Pod ライフサイクルと ImagePullBackOff・CrashLoopBackOff のよくある失敗パターンまで扱います。
第2章 ローカル環境 でクラスタ1台をノートパソコンの上に乗せました。本章ではそのクラスタに初めて Pod を1つ乗せ、中に入ってみて、ログを読み、再びきれいに消す一連の流れを扱います。その過程で kubectl の基本コマンドと Pod の役割も併せて身につけます。
本章の終わりには kubectl の日常コマンド群が手に馴染み、Pod という単位が何なのかを一度直接作ってみた 状態になります。続いて 第4章 Deployment と ReplicaSet からは、この Pod を人が直接作らずにコントローラに任せる話へ進みます。
Pod とは何か #
第1章 Kubernetes とは のリソース表で一行で見た定義がありました — Pod はコンテナ1個または一緒に動作する数個をまとめた K8s の最小実行単位 です。これをもう少しかみ砕いてみます。
Docker まで触れてきた読者なら コンテナ という単位はすでに慣れています。K8s はその上にもう一層を置きます — コンテナ1個または複数個をまとめて一緒にスケジュール・実行する単位が Pod です。同じ Pod の中のコンテナたちは次を共有します。
- ネットワークネームスペース — 同じ IP、同じポート空間を共有します。1つの Pod の中では
localhostで互いを呼びます。 - 一部のボリューム — Pod に定義されたボリュームを複数のコンテナが一緒にマウントできます。
- 寿命 — 一緒に起動し、一緒に死にます (同じノードの上で)。
docker のコンテナと最も混乱する点がここです。docker では「コンテナ1個 = 実行単位1個」でしたが、K8s では「Pod 1個 = 実行単位1個」であり、その Pod の中にコンテナが1個のこともあれば、2個のこともあります。頭の中の公式としては Pod ≈「1つの IP を共有するコンテナグループ」 程度が無難です。
ただし実務の99%以上は 1 Pod 1 コンテナ です。同じ Pod にコンテナ2つを一緒に置くのは、サイドカー (ログ収集器、プロキシなど) のように、そのコンテナがメインコンテナにくっついて動かなければ自然でない場合に限られます。最初は「Pod = コンテナ1個を K8s が転がすために一層包んだもの」と理解しても十分です。
もう1つ — 運用で直接 kind: Pod をマニフェストに書くことはほとんどありません。 普通は 第4章 Deployment が代わりに作ってくれます。本章で Pod を直接扱うのは、K8s を理解する土台を用意するためです。人がマニフェストに書く単位はほぼ常に Deployment・StatefulSet・Job といった コントローラ であり、Pod はそのコントローラが作り出す産物です。
kubectl コマンドを一表で #
手に馴染ませておく日常コマンドを一表に整理します。本書全体で繰り返し出会う形です。
| コマンド | 何をするか |
|---|---|
kubectl get <res> | リソース一覧を見ます。例: kubectl get pods、kubectl get nodes |
kubectl get <res> <name> | 特定のリソース1つの要約を見ます |
kubectl get <res> -o wide | デフォルトのカラム以外に IP・ノードなどの追加情報まで表示します |
kubectl get <res> <name> -o yaml | そのリソースの全定義を YAML で出力します |
kubectl describe <res> <name> | そのリソースの詳細と最近のイベント (Events) を併せて出力します |
kubectl logs <pod> | Pod コンテナの stdout / stderr を見ます |
kubectl logs -f <pod> | follow — tail -f のように動作します |
kubectl exec -it <pod> -- <cmd> | 起動中の Pod コンテナの中でコマンドを実行します |
kubectl apply -f file.yaml | マニフェストをクラスタに反映します (宣言型) |
kubectl delete -f file.yaml | 同じマニフェストで作られたリソースを削除します |
kubectl delete <res> <name> | 名前でリソースを削除します |
kubectl run <name> --image=<img> | Pod 1個を命令型でその場で生成します |
この表で <res> の部分には pods、pod、po、deployments、deploy、services、svc といった名前が入ります。よく使われるリソースごとに短縮形があり、入力量が素早く減ります。
命令型 vs 宣言型 #
kubectl でクラスタを扱う道には2つの筋があります。
- 命令型 (imperative) —
kubectl run、kubectl create deploymentのように「今これを作れ」と直接命令します。速くて手軽ですが、そのコマンドを再現するにはコマンドそのものがどこかに書かれていなければなりません。 - 宣言型 (declarative) — YAML マニフェストに「こういう形であるべきだ」を書いて
kubectl apply -f file.yamlを呼びます。マニフェストファイルがそのままクラスタ状態の記録になるので Git に上げやすく、同じファイルを再びapplyするだけで同一の状態になります。
実務はほぼすべてが宣言型です。クラスタの状態がコードとして記録される という点が決定的です。本書も最初の一度だけ命令型で Pod を起動してみて、すぐにマニフェストへ移ります。このモデルは 第20章 GitOps で ArgoCD / Flux でもう一度本格的に扱います。
命令型で最初の Pod — kubectl run #
まず最も短い道で Pod を1個起動してみましょう。イメージはどこでも取得できる nginx:1.27 を使います。
kubectl run hello --image=nginx:1.27 --port=80pod/hello created
--port=80は情報用のフラグです。外部からこのポートで入ってこられるようにする作業は 第5章 Service で扱います。今の段階では「Pod の中の nginx が80番を待ち受ける」というメモ程度に見てください。
作られた結果を確認します。
kubectl get podsNAME READY STATUS RESTARTS AGE
hello 1/1 Running 0 12sREADY 1/1 は Pod の中のコンテナ1個のうち1個が準備できたという意味で、STATUS Running なら私たちが期待した形です。カラム名を一行で押さえておくと — NAME / READY / STATUS / RESTARTS / AGE です。本書の最後までよく見ることになります。
もう少し詳しい情報が必要なときは -o wide を付けます。
kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello 1/1 Running 0 25s 10.244.0.5 kind-control-plane <none> <none>IP は Pod に割り当てられたクラスタ内部 IP、NODE はどの worker node に起動しているかを見せてくれます。単一ノードの kind クラスタなので kind-control-plane 1か所だけに集まりますが、マルチノードクラスタならここでどの Pod がどこへ散らばったかが見えます。NOMINATED NODE と READINESS GATES は普通は空で、高度なスケジューリング / 条件を使うときに埋まります。
describe — その中で何が起きたのか #
一覧だけでは Pod がどうやってそこまで来たのか分かりません。describe がその隙間を埋めてくれます。
kubectl describe pod helloName: hello
Namespace: default
Priority: 0
Node: kind-control-plane/172.18.0.2
Start Time: ...
Labels: run=hello
...
Containers:
hello:
Image: nginx:1.27
Port: 80/TCP
State: Running
Started: ...
Ready: True
Restart Count: 0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 30s default-scheduler Successfully assigned default/hello to kind-control-plane
Normal Pulling 29s kubelet Pulling image "nginx:1.27"
Normal Pulled 25s kubelet Successfully pulled image "nginx:1.27"
Normal Created 25s kubelet Created container hello
Normal Started 25s kubelet Started container hello上のほうには Pod の定義 (ノード、ラベル、コンテナ、状態) が、下のほうには Events が時間順に書かれています。この Events セクションが K8s デバッグの一次出発点になります — Scheduled → Pulling → Pulled → Created → Started。第1章 で図でしか見なかった流れがそのまま書かれています。scheduler がノードを決め、そのノードの kubelet がイメージを取得してコンテナを作り、起動する順序です。
何かが間違っているときも、答えはほぼ常にこの Events の中にあります。イメージ名を間違って書いたなら Failed to pull image が、コンテナが起動直後に死ぬなら Back-off restarting failed container がここに上がってきます。この診断の流れの完成版は 第27章 kubectl デバッグパターン で扱います。
logs — Pod が何を言っているのか #
Pod コンテナの stdout / stderr は kubectl logs で見ます。
kubectl logs hello/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
...
/docker-entrypoint.sh: Configuration complete; ready for start up
... [notice] start worker processesdocker の docker logs と1:1で対応するコマンドです。よく使う変形も同じです。
kubectl logs -f hello # follow
kubectl logs --tail=100 hello # 最後の100行
kubectl logs --since=10m hello # 直近10分
kubectl logs --previous hello # 以前に死んだコンテナのログ最後の --previous は K8s でだけ出会うオプションなので慣れておく必要があります。コンテナが一度死んで再起動したなら、死ぬ直前のログは現在のコンテナの stdout ではなく 以前のコンテナインスタンス の出力です。クラッシュ原因を追跡するときによく使います。
exec — Pod の中に入る #
docker の docker exec に対応するコマンドです。
kubectl exec -it hello -- bash-it はインタラクティブ + TTY、その後の -- は ここから先は Pod の中で実行するコマンド という区切り記号です。-- を抜かすと kubectl が後ろのオプションを自分のものとして解釈してしまい、よく失敗が起きます。
入って nginx の設定を一度確認して出ます。
root@hello:/# curl -s localhost:80 | head -5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
root@hello:/# exit単発コマンドだけ実行したいなら -it なしでも大丈夫です。
kubectl exec hello -- nginx -v
# nginx version: nginx/1.27.x最初のクリーンアップ #
ここまでが命令型の一連の流れです。きれいに片付けます。
kubectl delete pod hellopod "hello" deletedkubectl get pods で空になっていることまで確認すれば出発点に戻ります。
宣言型でもう一度 — YAML マニフェスト #
同じ Pod を今度はマニフェストで書いてみましょう。ほぼすべての K8s マニフェストは4つの最上位フィールドで始まります。
| フィールド | 意味 |
|---|---|
apiVersion | このリソースを定義する K8s API のバージョンです。Pod は安定グループの v1 なので v1 です |
kind | リソースの種類です。ここでは Pod です |
metadata | 名前・ラベル・ネームスペースといった識別情報です |
spec | 「このリソースがどうあるべきか」の本体です。リソースの種類ごとに形が異なります |
この4フィールドの形は Pod でも Deployment でも Service でも同じです。覚えておけばすべてのマニフェストの背骨になります。
hello-pod.yaml ファイルを次のように書きます。
apiVersion: v1
kind: Pod
metadata:
name: hello
labels:
app: hello
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80同じディレクトリで apply を呼びます。
kubectl apply が意図と違うエラーを吐き、原因をクラスタ側から逆にたどっていくことになります。マニフェストを適用する前に utilrepo の YAML 検証ツール に一度貼り付ければ、構文エラーを行・列番号で指摘してくれます。utilrepo はブラウザで動作する軽量な Web ユーティリティ集で、秘密情報が外部に出ることなく、--- でまとめた複数ドキュメントのマニフェストやタブ・スペース混在といったよく出会う罠まで併せて捕まえてくれます。本書のすべてのマニフェスト実習で同じやり方で活用できます。kubectl apply -f hello-pod.yamlpod/hello createdkubectl get pods で結果が同じか確認します — 出てくる形は kubectl run とまったく同じです。違いはクラスタの外にあります。
マニフェストの長所 #
- Git に上がります。 クラスタの状態がコードとして記録されるので、「この環境の形」を PR でレビューし、履歴として残せます。
- 再び
applyするだけで同一の状態になります。 命令型は頭の中やターミナルの履歴に依存しますが、マニフェストはファイルそのものが記録です。 applyは冪等 (idempotent) です。 すでに存在すれば差分だけ反映し、なければ作ります。この性質が CI / CD とよく合います。- コントローラへ自然に拡張されます。 同じマニフェストの
kindをDeploymentに変えるだけで 第4章 へまっすぐ進みます。
K8s が埋めてくれるフィールドたち #
マニフェストには私たちが書いたものだけが入ったのに、実際にクラスタに入ったオブジェクトにはもっと多くのフィールドが付いています。一度覗き込んでみましょう。
kubectl get pod hello -o yaml | head -40apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "..."
labels:
app: hello
name: hello
namespace: default
resourceVersion: "..."
uid: ...-...-...-...-...
spec:
containers:
- image: nginx:1.27
imagePullPolicy: IfNotPresent
name: web
ports:
- containerPort: 80
protocol: TCP
...
nodeName: kind-control-plane
...
status:
conditions:
- ...
phase: Running
podIP: 10.244.0.5
...私たちが書いた部分 (名前・ラベル・イメージ・ポート) はそのまま生きていて、そこに K8s が次のようなフィールドたちを埋め込みました。
metadata.uid、metadata.resourceVersion、metadata.creationTimestamp— このオブジェクトの素性spec.nodeName— scheduler が決めたノードspec.containers[].imagePullPolicy: IfNotPresentのようなデフォルト値statusブロック全体 — Phase、Pod IP、コンディションなど 現在の状態
第1章 で見た desired state vs actual state が1つのオブジェクトの中に一緒に入っているのが、まさにこの形です。spec が desired、status が actual です。コントローラは絶えず両者を比較します。
Pod ライフサイクルを一度 #
Pod は Phase という概念を持ちます。kubectl get pod 出力の STATUS カラムが映しているのがおおむねこの Phase です。正確な値は5つです。
| Phase | 意味 |
|---|---|
Pending | オブジェクトは作られたが、まだコンテナが起動していない状態です。イメージのダウンロード中だったり、ノード割り当て待ちだったりすることがあります |
Running | ノードに割り当てられ、コンテナのうち少なくとも1つが実行中です |
Succeeded | すべてのコンテナが成功裏に終了しました (exit 0)。再起動されません |
Failed | すべてのコンテナが終了し、そのうち少なくとも1つが異常終了した状態です |
Unknown | apiserver がノードとの通信が切れて状態が分からない状態です |
Web サーバーのようにずっと起動していなければならない Pod は一生を Running で過ごします。バッチ作業のように一度回って終わるべき Pod は Succeeded か Failed へ入ります。
kubectl describe の Events セクションでこの流れをもう一度押さえてみましょう — Scheduled → Pulling → Pulled → Created → Started。上で見た出力そのままの順序です。この5つの単語のどこで止まるかがデバッグの最初の分かれ道になります。
よくある失敗2つ #
最初に最もよく出会う失敗は2つです。
ImagePullBackOff / ErrImagePull — イメージを取得できなかった状態です。名前のタイプミスだったり、非公開レジストリの認証を抜かしていたり、そのタグが本当にないことがあります。kubectl describe pod <name> の Events に Failed to pull image "..." というメッセージと、その下に理由が書かれています。そこから始めればよいです。
CrashLoopBackOff — コンテナが起動直後に死に、K8s が再起動し、また死ぬ、を繰り返す状態です。一次の手がかりは kubectl logs <pod> で、直前に死んだコンテナのログが必要なら kubectl logs --previous <pod> を使います。RESTARTS カラムが速く上がっていく形でも見えます。
この2つのパターンは本書を通じて繰り返し出会います。最初の手がかりが describe の Events、2番目の手がかりが logs という順序だけ手に持っておけば9割は解けます。診断ツリーの完成版は 第27章 kubectl デバッグパターン で整理します。
Pod 1個ではなぜ足りないのか #
ここまでついてきたなら、自然に1つのことが引っかかり始めます — この Pod は死んだらどうなるのか? 試しに強制的に一度消してみましょう。
kubectl delete pod hellopod "hello" deletedkubectl get podsNo resources found in default namespace.ただ消えます。 自動で再び起動しません。K8s からすれば、私たちは「この Pod があるべきだ」とどこにも書いておかなかったからです。apply で一度作ったオブジェクトを人が消したので、その人の意図だと見たわけです。
第1章 で見た reconcile loop が働くには「これが N 個起動していなければならない」という 上位の宣言 がなければなりません。それを持っているのが次の章の主役である Deployment です。Deployment が ReplicaSet を作り、ReplicaSet が「この Pod テンプレートで N 個維持」という責任を負います。だから同じ実験を Deployment で行うと、Pod を消した直後に新しい Pod が自動で再び起動します。
K8s 公式ドキュメントが Pod についてよく使う表現が mortal です — いつか死ぬ存在という意味です。ノードが死んだり、コンテナが OOM で死んだり、誰かが誤って消したりすると Pod はただ消えてしまい、新しく起動してくれる誰かも別途いません。自動再起動・再配置は K8s がただでくれるわけではありません — コントローラを通じて提供します。
だから最初に言った一行が再び戻ってきます — 運用で kind: Pod を直接使うことはほとんどありません。 デバッグ用の一時 Pod、Job が作り出す使い捨ての Pod、DaemonSet がノードごとに起動する Pod といった特殊なケースを除けば、私たちが書くマニフェストはほぼ常にコントローラです。本章の Pod は、そのコントローラたちが中で何を作り出すのかを見るための土台です。
後片付け・クリーンアップ #
今日の実験の跡をきれいに消します。マニフェストで作ったリソースは同じマニフェストで消すのが最も確実です。
kubectl delete -f hello-pod.yamlpod "hello" deleted名前で直接消す道もあります。
kubectl delete pod hello最後に default ネームスペースが空になっているかだけ確認しておきます。
kubectl get podsNo resources found in default namespace.第2章 で見た kube-system のシステム Pod たちはそのまま起動しています。そちらはクラスタが自分の運用のために持っている Pod なので、私たちが作ったワークロードとは筋が異なります。両者が混ざらないようにネームスペースで分けておく話は 第7章 Namespace とラベル で扱います。
練習問題 #
- 上の本文どおり
kubectl run hello --image=nginx:1.27で命令型に Pod を起動したあと、kubectl get pod hello -o yaml出力を自分が書いたhello-pod.yamlと比較してみてください。K8s が埋め込んだフィールド (例:metadata.uid、spec.nodeName、spec.containers[].imagePullPolicy、statusブロック) を5つ選んで表に整理し、それぞれがdesiredなのかactualなのかを表示します。 - イメージ名をわざと間違って書いてみましょう (例:
nginx:9.99-no-such-tag)。作られた Pod のkubectl describe podEvents とkubectl get podsのSTATUSがどう変わるかを時間順に記録してみてください。ImagePullBackOffとErrImagePullがどの段階で登場するかを §「よくある失敗2つ」と比較します。 - マニフェストで作った
hello-pod.yamlの Pod をkubectl delete pod helloで消したあとkubectl get podsが空になっていることを確認してみてください。その次に同じファイルでkubectl apply -f hello-pod.yamlを再度実行して同一の Pod が戻ってくるかを確認します。「マニフェストは冪等」という §「マニフェストの長所」の意味を一段落で自分の言葉で整理します。
一行まとめ: Pod は K8s の最小実行単位であり、日常コマンドは get / describe / logs / exec / apply / delete の6つが事実上すべてである。命令型 (
kubectl run) は速いが再現が難しく、宣言型 (YAML + apply) はクラスタの状態をコードとして記録する。Pod は mortal なので死ぬとただ消え、だから次の章の Deployment のようなコントローラが必要である。
次の章 #
本章の最後で見た1つのこと — Pod を消すとただ消える — が次の章の出発点になります。人がいちいち見守らなくても「この Pod が N 個起動していなければならない」を K8s が絶えず維持してくれる抽象が必要です。
第4章 Deployment と ReplicaSet では、ReplicaSet がどう Pod の個数を保証するのか、Deployment がその上で何をさらにしてくれるのか (ローリングアップデート、ロールバック)、同じ nginx Pod を Deployment マニフェストで書き直して replicas: 3 で起動し、1個を消したときどう自動復旧されるのかを扱います。本章の Pod 1個がそこで本物のワークロードのように振る舞い始めます。