Kubernetes入門 第2回:Podのライフサイクルと「使い捨て」の哲学

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.logtail していましたが、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: を確認(nginxngixn の違いなど)
存在しないタグ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