Kubernetes入門
第2回:Podのライフサイクルと「使い捨て」の哲学
2.1 Podの本質:VMとプロセスの間
2.1.1 Podは「VM」ではない。なぜIPアドレスを固定してはいけないのか
前回、私たちは kind を使ってKubernetesクラスターを構築し、Nginx Podが自己修復する様子を体験しました。VMの世界では「サーバーが落ちたら、手順書を片手に復旧作業」が常識でしたが、K8sは何事もなかったかのようにPodを蘇らせました。
しかし、ここで一つの疑問が湧いてきます。
「復活したPodは、本当に”同じ”サーバーなのか?」
結論から言えば、まったく別物です。そして、ここにK8sの哲学の核心があります。
VMの世界では、サーバーは「ペット」でした。名前をつけ(web-server-01)、固定IPを割り当て(192.168.1.10)、何年も大切に育てる。病気になれば看病し、調子が悪ければSSHでログインして治療する。私たちは、そのサーバーの「個性」を熟知していました。
一方、K8sの世界では、Podは「畜産」です。個体識別よりも群れ全体の健康が重要であり、1頭が倒れれば、すぐに新しい1頭が補充されます。名前は自動生成され(nginx-deployment-7d9f8c6b5-x2k9p)、IPアドレスは起動のたびに変わります。
この「畜産モデル」を受け入れることが、K8sマスターへの第一歩です。
では、なぜIPアドレスを固定してはいけないのでしょうか?
VMの世界では、IPアドレスは「住所」でした。アプリケーションの設定ファイルに 192.168.1.10 とハードコードし、ファイアウォールのルールもそのIPに紐づけていました。しかし、K8sでは:
- Podは頻繁に作り直される(デプロイ、スケール、障害復旧)
- 作り直されるたびに、新しいIPアドレスが割り当てられる
- 古いIPアドレスは、もう誰も住んでいない空き家になる
もし設定ファイルに旧IPをハードコードしていたら?——接続先は「空き家」を叩き続け、アプリケーションは沈黙します。
K8sでは、IPアドレスを「一時的な識別子」として扱い、決して永続的な宛先としては使いません。 では、どうやって通信するのか?それは第3回で解説する「Service」の出番ですが、今回はまず「Podは使い捨て」という前提を、身体で覚えていきましょう。
2.1.2 コンテナの中身は「不変(Immutable)」であるべき理由
VMの運用で、こんな経験はありませんか?
「本番サーバーで直接
httpd.confを編集して、systemctl reload httpdしたら動いた。あとで手順書に追記しておこう……」
3ヶ月後、そのサーバーが故障。新しいVMを手順書通りに構築したら、なぜか動かない。調べてみると、あの夜の「ちょっとした修正」が手順書に反映されていなかった——。
「本番環境だけに存在する謎の設定」、これはVMエンジニアなら誰もが経験する悪夢です。
K8sは、この悪夢を構造的に排除します。その武器が「Immutable Infrastructure(不変のインフラ)」という思想です。
Immutable Infrastructureでは、一度デプロイしたコンテナの中身は、絶対に変更しないというルールを守ります。設定を変えたいなら、コンテナの中を編集するのではなく、新しいコンテナイメージを作り、古いものと入れ替えるのです。
これにより、以下が保証されます:
| 保証されること | VM運用での悩み |
|---|---|
| 本番と開発で同一のイメージが動く | 「本番だけ動かない」が消える |
| 設定は全てコード化(GitOps) | 「誰がいつ何を変えた?」が追跡可能 |
| ロールバックが一瞬 | 古いイメージに戻すだけ |
| 再現性100% | 「おま環(お前の環境だけ)」が消滅 |
「サーバーを治す」のではなく、「正しいサーバーに置き換える」——これがK8sの流儀です。
2.1.3 コンテナ内で行った「一時的な変更」が消える瞬間を実験する
理屈はわかった。でも、本当にそうなのか?
VMエンジニアの血が騒ぐ方もいるでしょう。「いやいや、ファイルを書き換えたら、書き換わったままでしょう?」と。
では、実験してみましょう。あえてK8sの作法を破り、コンテナ内部を直接編集するという「禁じ手」を試します。そして、その変更がどう消えるのかを、自分の目で確かめてください。
この体験が、あなたの「VMエンジニアとしての常識」を、優しく、しかし確実に書き換えてくれるはずです。
2.2 実践:不変(Immutable)インフラの洗礼
2.2.1 kubectl exec でコンテナ内部に潜入し、設定ファイルを書き換える
まず、前回作成したkindクラスターが起動していることを確認します。
[Execution User: developer]
kind get clusters
my-cluster が表示されれば準備完了です。もし表示されない場合は、前回の手順でクラスターを再作成してください。
次に、実験用のNginx Deploymentを作成します。
[Execution User: developer]
cat << 'EOF' > ~/nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-experiment
labels:
app: nginx-experiment
spec:
replicas: 1
selector:
matchLabels:
app: nginx-experiment
template:
metadata:
labels:
app: nginx-experiment
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
EOF
このYAMLファイルをクラスターに適用します。
[Execution User: developer]
kubectl apply -f ~/nginx-deployment.yaml
Podが Running になるまで待ちます。
[Execution User: developer]
kubectl get pods -l app=nginx-experiment -w
Running と表示されたら Ctrl+C で監視を終了します。
では、いよいよコンテナ内部に潜入します。VMエンジニアにとっては、SSHでサーバーにログインするのと同じ感覚です。
[Execution User: developer]
# Pod名を変数に格納
POD_NAME=$(kubectl get pods -l app=nginx-experiment -o jsonpath='{.items[0].metadata.name}')
# コンテナ内でシェルを起動
kubectl exec -it $POD_NAME -- /bin/bash
プロンプトが root@nginx-experiment-xxxxx:/# のように変わりましたか? 今、あなたはNginxコンテナの「中」にいます。
まず、現在のNginxのデフォルトページを確認しましょう。
[Inside Container]
cat /usr/share/nginx/html/index.html
「Welcome to nginx!」という見慣れたHTMLが表示されるはずです。
さあ、ここからが禁断の実験です。このファイルを、直接書き換えます。
[Inside Container]
echo '<html><body><h1>I hacked this server!</h1></body></html>' > /usr/share/nginx/html/index.html
変更を確認します。
[Inside Container]
cat /usr/share/nginx/html/index.html
「I hacked this server!」と表示されましたか?
コンテナから抜けて、外部からも確認してみましょう。
[Inside Container]
exit
[Execution User: developer]
# Pod内のNginxにポートフォワードで接続
kubectl port-forward $POD_NAME 8080:80 &
sleep 2
# curlでアクセス
curl http://localhost:8080
# ポートフォワードを停止
kill %1
ブラウザを開くまでもなく、ターミナルに「I hacked this server!」が表示されます。
成功です。コンテナの中身を書き換えました。
VMエンジニアとしては、ここで満足感を覚えるかもしれません。「やはり、サーバーは直接触れば変えられる」と。
しかし、K8sの世界では、この変更は砂上の楼閣です。次のセクションで、その「崩壊」を目撃してください。
2.2.2 Podを再起動(削除・再作成)し、変更が消失することを確認する
Podを削除します。Deploymentが管理しているので、削除すると自動的に新しいPodが作られます。
[Execution User: developer]
kubectl delete pod $POD_NAME
新しいPodが起動するのを待ちます。
[Execution User: developer]
kubectl get pods -l app=nginx-experiment -w
Running になったら Ctrl+C で終了し、新しいPod名を取得します。
[Execution User: developer]
NEW_POD_NAME=$(kubectl get pods -l app=nginx-experiment -o jsonpath='{.items[0].metadata.name}')
echo "旧Pod: $POD_NAME"
echo "新Pod: $NEW_POD_NAME"
Pod名が変わっていることに注目してください。これは「修復された同じサーバー」ではなく、「新しく生まれた別のサーバー」です。
では、例の index.html はどうなっているでしょうか?
[Execution User: developer]
kubectl exec $NEW_POD_NAME -- cat /usr/share/nginx/html/index.html
……「Welcome to nginx!」。
あなたが深夜に書き換えた「I hacked this server!」は、跡形もなく消え去りました。
これが、Immutable Infrastructureの洗礼です。
何が起きたのか?
Deploymentは、YAMLに記述された「あるべき姿」を常に維持しようとします。その「あるべき姿」には、nginx:1.27 というコンテナイメージを使うことが明記されています。
Podが削除されると、Deploymentは新しいPodを作ります。その際、常にオリジナルのコンテナイメージから作り直すのです。あなたがコンテナ内で行った変更は、イメージには記録されていません。だから、消えるのです。
第1回で見た「自己修復」の正体は、これでした。「壊れたサーバーを直す」のではなく、「設計図から新品を作り直す」——それがK8sの自己修復です。
2.2.3 「設定は外から与える」というK8s流儀へのマインドセット変更
「じゃあ、設定を変えたい時はどうするんだ?」
ごもっともな疑問です。VMの世界では、設定ファイルはサーバー内部に存在し、変更もサーバー内部で行うのが当然でした。しかしK8sでは、設定はコンテナの「外」から注入するのが作法です。
K8sが用意する「外から設定を与える」仕組みには、主に以下があります:
| 仕組み | 用途 | VMでの類似概念 |
|---|---|---|
| ConfigMap | 設定ファイル、環境変数 | /etc/ 配下の設定ファイル |
| Secret | パスワード、APIキー | 暗号化された認証情報ファイル |
| 環境変数 | アプリケーションパラメータ | シェルのexport |
| PersistentVolume | 永続データ | 外付けディスク、NFS |
例えば、Nginxの index.html を変更したいなら、ConfigMapで外部から注入します。
[Execution User: developer]
# ConfigMapを作成
kubectl create configmap nginx-content --from-literal=index.html='<html><body><h1>Hello from ConfigMap!</h1></body></html>'
# 確認
kubectl get configmap nginx-content -o yaml
このConfigMapをPodにマウントするようにDeploymentを書き換えます。
[Execution User: developer]
cat << 'EOF' > ~/nginx-deployment-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-experiment
labels:
app: nginx-experiment
spec:
replicas: 1
selector:
matchLabels:
app: nginx-experiment
template:
metadata:
labels:
app: nginx-experiment
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: nginx-content
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-content
configMap:
name: nginx-content
EOF
kubectl apply -f ~/nginx-deployment-v2.yaml
新しいPodが起動したら確認します。
[Execution User: developer]
# 新Podの起動を待つ
kubectl rollout status deployment/nginx-experiment
# 新Pod名を取得
CONFIGMAP_POD=$(kubectl get pods -l app=nginx-experiment -o jsonpath='{.items[0].metadata.name}')
# コンテンツを確認
kubectl exec $CONFIGMAP_POD -- cat /usr/share/nginx/html/index.html
「Hello from ConfigMap!」と表示されます。
さらに、このPodを削除してみましょう。
[Execution User: developer]
kubectl delete pod $CONFIGMAP_POD
sleep 5
RECREATED_POD=$(kubectl get pods -l app=nginx-experiment -o jsonpath='{.items[0].metadata.name}')
kubectl exec $RECREATED_POD -- cat /usr/share/nginx/html/index.html
今度は、「Hello from ConfigMap!」が残っています。
なぜか? ConfigMapは「Podの外」に存在するからです。Podが何度作り直されても、ConfigMapから設定が注入され、常に同じ状態が再現されます。
設定をコンテナの中に閉じ込めるのではなく、外部化する——これがK8sにおける「設定変更」の正しい作法です。
2.3 運用設計:SSHログイン不要のデバッグ手法
2.3.1 なぜK8sでは「サーバにログインして直す」のがNGなのか
VMの世界では、トラブル対応といえば「SSHでログインして調査・修正」が定石でした。/var/log/ を漁り、設定ファイルを vi で開き、原因を特定したら systemctl restart で復旧——このフローは、エンジニアの身体に染み付いています。
しかしK8sでは、この「ログインして直す」がアンチパターンとされます。その理由は3つあります。
理由1:直しても、すぐ消える
先ほど実験した通りです。コンテナ内で何を変更しても、Podが再起動すれば元に戻ります。深夜に1時間かけて直した修正も、翌朝のデプロイで上書きされます。徒労です。
理由2:再現性がなくなる
「このサーバーだけ、手で直した」という状態は、Immutable Infrastructureの原則に反します。同じDeploymentから作られたPodなのに、挙動が違う——これは、VMの「謎の本番環境」問題の再発です。
理由3:スケールしない
VMが10台なら、10回SSHすればいい。しかしK8sでは、Podは数十、数百、時には数千に達します。全部にログインして直す? 現実的ではありません。
「壊れたら直す」のではなく、「正しい状態を宣言し、システムに維持させる」——これがK8sの運用哲学です。あなたの仕事は、サーバーを「治療」することではなく、「処方箋(マニフェスト)」を正しく書くことです。
2.3.2 代替手段としてのログ確認(kubectl logs)とマニフェスト管理の重要性
では、トラブル時にはどうすればいいのか? K8sが提供する「SSHなし」のデバッグ手法を紹介します。
ログの確認:kubectl logs
[Execution User: developer]
# 現在のPodのログを表示
kubectl logs $RECREATED_POD
# リアルタイムでログを追跡(tail -f 相当)
kubectl logs -f $RECREATED_POD
# 過去のPod(再起動前)のログを確認
kubectl logs $RECREATED_POD --previous
VMでは /var/log/nginx/access.log を tail していましたが、K8sでは kubectl logs がその役割を担います。重要なのは、コンテナに入らなくてもログが見えるという点です。
Podの状態確認:kubectl describe
[Execution User: developer]
kubectl describe pod $RECREATED_POD
このコマンドは、Podの「カルテ」を表示します。Events欄には、Podに何が起きたかの履歴が記録されています。「なぜ起動しないのか」「なぜ再起動したのか」のヒントは、たいていここにあります。
マニフェスト(YAML)のバージョン管理
K8sでは、インフラの「あるべき姿」はYAMLファイルに記述されます。このYAMLをGitで管理することで、以下が実現します:
- いつ、誰が、何を変えたかが追跡可能(git log)
- 問題が起きたら、前のバージョンに戻せる(git revert → kubectl apply)
- 本番環境と開発環境で、同じYAMLを使える(環境差異の排除)
これが「GitOps」と呼ばれる運用スタイルの基本です。「サーバーを直接触る」のではなく、「Gitにコミットする」ことで、インフラを変更する。レビューも、承認も、ロールバックも、Gitのワークフローに乗せられます。
VMの世界では「手順書」と「作業ログ」を別々に管理していましたが、K8sではYAMLファイル自体が手順書であり、作業ログであり、現在の状態の証拠になります。
2.3.3 【次回予告】IP管理を過去にする「Service」の魔法
今回、私たちは「Podは使い捨て」という哲学を、身をもって体験しました。Podは消え、IPアドレスは変わり、コンテナ内の変更は跡形もなく失われる。
しかし、ここで新たな疑問が浮かびます。
「IPアドレスが毎回変わるなら、どうやってアプリケーション同士を通信させるんだ?」
VMの世界では、WebサーバーがDBサーバーに接続するとき、DBサーバーの固定IPを設定ファイルに書いていました。しかしK8sでは、DBのIPが起動のたびに変わるなら、Webサーバーはどこに接続すればいいのでしょうか?
第3回では、この問題を解決する「Service」という仕組みを解説します。Serviceは、変動するPodのIPを抽象化し、安定した接続先を提供してくれます。もうIPアドレスを追いかける必要はありません。
お楽しみに。
2.4 トラブルシューティングのTips
実験を進める中で、思わぬトラブルに遭遇することがあります。VMエンジニアとして培った「勘」が通用しない場面も多いでしょう。ここでは、よくあるトラブルと「K8s流」の対処法を紹介します。
Podがいつまでも Terminating から消えない
VMでは、shutdown を実行すれば、いずれサーバーは停止しました。しかしK8sでは、Podが Terminating 状態のまま固まることがあります。
まず見るべきポイント:
[Execution User: developer]
# Podの状態を詳しく確認
kubectl describe pod <pod-name>
Eventsセクションで、何が起きているかを確認します。多くの場合、ファイナライザー(finalizer)が残っているか、PersistentVolumeのアンマウントに失敗しているかのどちらかです。
強制削除(最終手段):
[Execution User: developer]
kubectl delete pod <pod-name> --force --grace-period=0
ただし、これは「電源ボタン長押し」相当の荒技です。データ破損のリスクがあるため、原因を特定してから使いましょう。
Podが ImagePullBackOff になる
これは、コンテナイメージの取得に失敗している状態です。VMでいえば「ISOファイルが見つからない」に相当します。
まず見るべきポイント:
[Execution User: developer]
kubectl describe pod <pod-name>
Eventsセクションに、以下のようなメッセージが出ていませんか?
Failed to pull image "nginx:1.27": rpc error: ...unauthorized: authentication required
よくある原因と対処:
| 原因 | 対処 |
|---|---|
| イメージ名のタイポ | YAMLの image: を確認(nginx と ngixn の違いなど) |
| 存在しないタグ | Docker Hubでタグの存在を確認 |
| プライベートレジストリの認証不足 | imagePullSecrets の設定を確認 |
| ネットワーク不通 | kindノードからインターネットへの疫通を確認 |
[Execution User: developer]
# kindノードに入ってイメージをpullできるか確認
docker exec -it my-cluster-control-plane crictl pull nginx:1.27
kubectl exec が error: unable to upgrade connection で失敗する
kindを使っていると、稀にこのエラーに遭遇します。
対処法:
[Execution User: developer]
# kindクラスターの状態を確認
kind get clusters
docker ps -a | grep kindest
# コントロールプレーンが起動しているか確認
docker logs my-cluster-control-plane 2>&1 | tail -20
多くの場合、Dockerデーモンの再起動やkindクラスターの再作成で解決します。
[Execution User: developer]
# 最終手段:クラスターの再作成
kind delete cluster --name my-cluster
kind create cluster --name my-cluster --config ~/k8s-lab/kind-config.yaml
まとめ:今回学んだこと
| VMの常識 | K8sの新常識 |
|---|---|
| サーバーは「ペット」、大切に育てる | Podは「家畜」、いつでも交換可能 |
| SSHでログインして設定変更 | 設定は外部(ConfigMap/Secret)から注入 |
| IPアドレスは固定し、設定ファイルに記載 | IPは一時的、Serviceで抽象化(次回) |
| トラブル時は「直す」 | トラブル時は「正しい状態を再宣言する」 |
Podは消えるもの。だからこそ、「あるべき姿」をコードで定義し、システムに維持させる。
この哲学を腹落ちさせることが、K8sエンジニアへの第一歩です。次回は、変動するPodに「安定した窓口」を与える「Service」の仕組みを学びます。
実験後のクリーンアップ:
[Execution User: developer]
kubectl delete deployment nginx-experiment
kubectl delete configmap nginx-content
