Kubernetes入門 第5回:レプリケーションと負荷分散

Kubernetes入門
第5回:レプリケーションと負荷分散

前回、私たちはYAMLマニフェストという「設計図」でインフラを定義する方法を学びました。今回は、その設計図に書かれた「たった1つの数字」を変えるだけで、Webサーバが瞬時に増殖し、負荷を分散し、そして障害から自動回復する様子を目撃します。VMの世界で何時間もかけていたクローン作成とHA構成が、K8sではどれほど「あっけなく」実現されるのか——その衝撃を、ぜひ体感してください。


5.1 スケールアウト:VMのクローン作成との違い

5.1.1 VMのコピー&IP変更作業が、K8sではなぜ「数字を変えるだけ」で済むのか

VMware vSphereでWebサーバを3台構成にする作業を思い出してください。

まず、マスターとなるVMのテンプレートを作成します。次に、そのテンプレートからクローンを3台デプロイ。ここまでで、ストレージの空き容量を確認し、データストアを選択し、クローン完了まで数分から数十分待つことになります。

しかし、本当の作業はここからです。

各VMに順番にSSHでログインし、ホスト名を変更します。/etc/hostname を編集し、/etc/hosts も書き換え、ネットワーク設定ファイルでIPアドレスを静的に割り当て——あるいはDHCP予約を設定し——最後にサービスを再起動。この一連の作業を、3台それぞれに対して行います。手順書は10ページを超え、深夜メンテナンスの疲れた目には、IPアドレスの打ち間違いという罠が待ち構えています。

Kubernetesでは、この作業が以下の1行に集約されます。

replicas: 3

冗談ではありません。YAMLファイルのこの数字を 1 から 3 に書き換え、kubectl apply を実行する。それだけで、K8sは自動的に2つの新しいPodを起動し、それぞれに一意のホスト名とIPアドレスを割り当て、ネットワークに接続し、ヘルスチェックを通過したものからサービスに組み込みます。

所要時間は、数秒から数十秒。

なぜこれが可能なのか? 答えは「コンテナの軽量性」と「K8sの宣言的モデル」の組み合わせにあります。

VMは完全なOSを含むため、起動に時間がかかり、ディスク容量も大きい。一方、コンテナはホストOSのカーネルを共有し、アプリケーションとその依存関係だけをパッケージ化しています。だから軽く、だから速い。

そして、K8sの宣言的モデルは「あるべき状態」を定義するだけで、「そこに至る手順」はシステムが自動で判断します。「3台あるべき」と宣言すれば、K8sは現在1台しかないことを認識し、差分の2台を自動で作成するのです。

VMとK8sのスケールアウト作業比較

作業項目VMware vSphereKubernetes
テンプレート/イメージ準備30分〜1時間初回のみ(以降は不要)
クローン/Pod作成(3台)10〜30分5〜30秒
ホスト名設定手動×3台自動(一意の名前が付与)
IPアドレス設定手動またはDHCP予約×3台自動(CNIが割り当て)
ロードバランサー登録手動でプール追加自動(Serviceが検出)
合計作業時間1〜3時間1分未満

この差は、1回のスケールアウトでは「便利だな」程度かもしれません。しかし、トラフィックの増減に応じて日に何度もスケールする現代のWebサービスでは、この差が運用チームの睡眠時間を左右します。

5.1.2 2026年のインフラ事情:予測不能なトラフィックへの「秒速」対応

2026年現在、Webサービスのトラフィックパターンは、かつてないほど予測困難になっています。

SNSでのバズ、インフルエンサーの一言、予期せぬニュース——これらが引き起こすトラフィックスパイクは、従来の「事前にキャパシティプランニングを行い、余裕を持ったリソースを確保する」というアプローチでは対応しきれません。

VMベースのインフラでは、急なトラフィック増加に対して「まず起きている人間が状況を認識し、判断し、手を動かす」というプロセスが必要でした。深夜3時にアラートが鳴り、眠い目をこすりながらVPNに接続し、vSphere Clientを開き、クローンを作成し、ロードバランサーに登録する——その間にも、ユーザーは503エラーを見続けています。

Kubernetesの世界では、この対応を「人間抜き」で行えます。

Horizontal Pod Autoscaler(HPA)という機能を使えば、CPU使用率やメモリ使用率、あるいはカスタムメトリクスに基づいて、Podの数を自動的に増減させることができます。トラフィックが増えればPodが増え、落ち着けばPodが減る。人間は翌朝、ログを確認して「ああ、昨夜スパイクがあったのか」と知るだけです。

03:00 トラフィック急増を検知
03:00 HPAがreplicas: 3 → 10 に自動変更
03:01 7つの新しいPodが起動完了、サービス投入
03:02 レスポンスタイム正常化
06:00 トラフィック沈静化
06:05 HPAがreplicas: 10 → 3 に自動変更
06:06 余剰Podが終了、リソース解放

この一連の流れに、人間の介入は一切ありません。

今回の記事では、まずこの「スケールアウト」の基礎となる手動でのレプリカ数変更を体験します。自動スケーリング(HPA)は、この基礎を理解した上で次のステップとして学ぶべきトピックです。まずは「数字を変えるとPodが増える」という魔法の仕組みを、手を動かして体感しましょう。

5.1.3 レプリカセット(ReplicaSet):裏側で働く「増殖の魔法」の仕組み

第4回で作成したDeploymentのマニフェストを思い出してください。replicas: 1 という指定がありました。この「レプリカ」という概念を管理しているのが、ReplicaSet というK8sのリソースです。

ReplicaSetの役割は、シンプルかつ強力です。

「指定された数のPodが、常に稼働していることを保証する」

たとえば replicas: 3 と指定されていれば、ReplicaSetは常にPodの数を監視し、3台を維持しようとします。1台が何らかの理由で消えれば、即座に新しい1台を起動します。逆に、何かの間違いで4台になっていれば、1台を終了させます。

この「あるべき状態を維持し続ける」動作を、K8sではReconciliation Loop(調整ループ)と呼びます。

[あるべき状態]     [現在の状態]      [アクション]
replicas: 3   →   Pods: 2      →   1台追加起動
replicas: 3   →   Pods: 4      →   1台終了
replicas: 3   →   Pods: 3      →   何もしない

VMの世界でいえば、vSphere HAの「VMが落ちたら別ホストで再起動」に似ていますが、決定的な違いがあります。

vSphere HAは「VMが完全に停止したこと」を検知してから動作を開始します。検知には数十秒から数分かかり、その後のVM起動にもさらに時間がかかります。合計で数分間のダウンタイムは避けられません。

ReplicaSetは「Podが期待される状態でなくなった瞬間」に新しいPodの起動を開始します。しかも、コンテナの起動は数秒で完了します。結果として、ユーザーから見たサービス断の時間は、VMベースのHAと比較して桁違いに短くなります。

Deployment、ReplicaSet、Podの関係

ここで重要なのは、私たちが直接ReplicaSetを操作することは稀だということです。

実際の運用では、Deployment というより高レベルのリソースを使います。Deploymentは内部的にReplicaSetを作成・管理し、さらに「ローリングアップデート」や「ロールバック」といった高度な機能を提供します。

Deployment(私たちが操作する)
    └── ReplicaSet(自動で作成される)
            ├── Pod-1
            ├── Pod-2
            └── Pod-3

この階層構造により、私たちはDeploymentのマニフェストで replicas: 3 と書くだけで、ReplicaSetが自動的に3つのPodを管理してくれるのです。

第6回で扱う「ローリングアップデート」では、このDeployment → ReplicaSet → Podの関係がさらに重要になります。新しいバージョンをデプロイする際、Deploymentは新しいReplicaSetを作成し、古いReplicaSetのPodを徐々に減らしながら、新しいReplicaSetのPodを徐々に増やします。この優雅な切り替えが、無停止デプロイを実現するのです。


5.2 実践:レプリカ数の変更とトラフィックの分散

理論はここまでにして、実際に手を動かしましょう。第4回で作成したkindクラスタとDeploymentがある前提で進めます。もしクラスタがない場合は、第3回の手順でkindクラスタを再作成してください。

5.2.1 マニフェストの replicas を1から3へ。Podが分身する様子を観察する

現在の状態を確認する

まず、現在のPodの状態を確認します。

[Execution User: developer]

kubectl get pods -o wide

出力例:

NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE
web-server-5d6b77d8c4-x7k2m   1/1     Running   0          24h   10.244.0.5   kind-control-plane

現在、Podは1台だけ稼働しています。このPodの名前、IPアドレス、配置されているノードを覚えておいてください。

マニフェストを編集する

第4回で作成した nginx-deployment.yaml を編集します。まだファイルがない場合は、以下の内容で作成してください。

[Execution User: developer]

cat << 'EOF' > ~/k8s-practice/04-declarative/web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
  labels:
    app: web
    environment: practice
spec:
  replicas: 3    # ← ここを2から3に変更
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
EOF

変更点は replicas: 3 の1箇所だけです。この「3」という数字が、「Podを3台維持せよ」というK8sへの宣言になります。

変更を適用する

kubectl apply で変更を適用します。

[Execution User: developer]

kubectl apply -f ~/k8s-practice/04-declarative/web-deployment.yaml

出力:

deployment.apps/web-server configured

configured と表示されました。これは「既存のリソースが更新された」ことを意味します。

Podの増殖を観察する

さあ、ここからが見どころです。Podがリアルタイムで増えていく様子を観察しましょう。

[Execution User: developer]

kubectl get pods -w

-w オプションは「watch」の略で、状態が変化するたびにリアルタイムで表示を更新します。

NAME                        READY   STATUS              RESTARTS   AGE
web-server-5d6b77d8c4-x7k2m   1/1     Running             0          24h
web-server-5d6b77d8c4-abc12   0/1     ContainerCreating   0          2s
web-server-5d6b77d8c4-def34   0/1     ContainerCreating   0          2s
web-server-5d6b77d8c4-abc12   1/1     Running             0          5s
web-server-5d6b77d8c4-def34   1/1     Running             0          6s

わずか数秒で、2つの新しいPodが ContainerCreating から Running へと遷移しました。

Ctrl+C で監視を終了し、最終状態を確認しましょう。

[Execution User: developer]

kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE
web-server-5d6b77d8c4-x7k2m   1/1     Running   0          24h   10.244.0.5    kind-control-plane
web-server-5d6b77d8c4-abc12   1/1     Running   0          30s   10.244.0.6    kind-control-plane
web-server-5d6b77d8c4-def34   1/1     Running   0          30s   10.244.0.7    kind-control-plane

3台のPodが稼働しています。それぞれに異なる名前(x7k2m, abc12, def34)と異なるIPアドレスが自動的に割り当てられていることに注目してください。

VMの世界では、この「ホスト名とIPアドレスの割り当て」こそが、手動作業の大部分を占めていました。K8sでは、それが完全に自動化されています。

ReplicaSetの状態を確認する

裏側で動いているReplicaSetも見てみましょう。

[Execution User: developer]

kubectl get replicaset
NAME                  DESIRED   CURRENT   READY   AGE
web-server-5d6b77d8c4   3         3         3       24h

DESIRED: 3(あるべき数)、CURRENT: 3(現在の数)、READY: 3(準備完了の数)がすべて一致しています。ReplicaSetが「あるべき状態」を維持していることがわかります。

5.2.2 Serviceによる負荷分散:一つの窓口から複数のPodへリクエストが流れる確認

3台のPodが起動しました。しかし、それぞれが異なるIPアドレスを持っているとすると、クライアントはどのIPに接続すればよいのでしょうか?

ここで登場するのが、第3回で作成した Service です。

ServiceはPodの「窓口」として機能し、クライアントからのリクエストを背後の複数Podに振り分けます。VMの世界でいえば、ロードバランサー(F5 BIG-IPやNSX ALBなど)に相当しますが、K8sではこれも宣言的に管理されます。

Serviceの状態を確認する

まず、Serviceが存在することを確認します。

[Execution User: developer]

kubectl get service web-service

もしServiceが存在しない場合は、以下のマニフェストで作成してください。

[Execution User: developer]

cat << 'EOF' > ~/k8s-practice/04-declarative/web-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web    # このラベルを持つPodに振り分け
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP
EOF

kubectl apply -f ~/k8s-practice/04-declarative/web-service.yaml

ServiceがPodを認識していることを確認する

ServiceがどのPodにトラフィックを振り分けるかは、Endpoints というリソースで管理されています。

[Execution User: developer]

kubectl get endpoints web-service
NAME               ENDPOINTS                                   AGE
web-service   10.244.0.5:80,10.244.0.6:80,10.244.0.7:80   24h

3つのPodのIPアドレスがすべて ENDPOINTS に含まれています。Serviceは app: web というラベルを持つPodを自動的に検出し、Endpointsに登録しているのです。

VMのロードバランサーでは、新しいサーバを追加するたびに「プールメンバーの追加」という手動作業が必要でした。K8sでは、ラベルが一致するPodは自動的にServiceに組み込まれます。

負荷分散の動作を確認する

では、実際にリクエストが複数のPodに分散されることを確認しましょう。

そのために、各Podが「自分のホスト名」を返すように設定します。Nginxのデフォルトページを、Podごとに異なる内容に書き換えます。

[Execution User: developer]

# 各Podに対してホスト名を表示するindex.htmlを設定
for pod in $(kubectl get pods -l app=web -o jsonpath='{.items[*].metadata.name}'); do
  kubectl exec "$pod" -- sh -c "echo 'Hello from $pod' > /usr/share/nginx/html/index.html"
done

これで、各Podにアクセスすると、そのPod自身の名前が返ってくるようになりました。

curlループで負荷分散を可視化する

ServiceのClusterIPに対して、繰り返しリクエストを送信します。

[Execution User: developer]

# ServiceのClusterIPを取得
SERVICE_IP=$(kubectl get service web-service -o jsonpath='{.spec.clusterIP}')
echo "Service IP: $SERVICE_IP"

# クラスタ内から10回アクセスし、どのPodが応答するか確認
kubectl run curl-test --image=curlimages/curl:8.5.0 --rm -it --restart=Never -- \
  sh -c "for i in \$(seq 1 10); do curl -s http://$SERVICE_IP && echo ''; sleep 0.5; done"

出力例:

Hello from web-server-5d6b77d8c4-x7k2m
Hello from web-server-5d6b77d8c4-def34
Hello from web-server-5d6b77d8c4-abc12
Hello from web-server-5d6b77d8c4-x7k2m
Hello from web-server-5d6b77d8c4-abc12
Hello from web-server-5d6b77d8c4-def34
Hello from web-server-5d6b77d8c4-abc12
Hello from web-server-5d6b77d8c4-x7k2m
Hello from web-server-5d6b77d8c4-def34
Hello from web-server-5d6b77d8c4-abc12

3つの異なるPod名が、ほぼランダムに返ってきています。これが、Serviceによる負荷分散の実際の動作です。

クライアントは web-service という単一のエンドポイントにアクセスするだけで、K8sが自動的にリクエストを3台のPodに分散しています。新しいPodが追加されれば自動的に振り分け先に含まれ、Podが減れば自動的に除外される——これがK8sの「宣言的負荷分散」です。

5.2.3 【Debug Tips】Podが Pending で止まったら? リソース不足の切り分け

レプリカ数を増やした際、Podが Pending 状態のまま Running にならないことがあります。これは多くの場合、クラスタのリソース不足が原因です。

Pendingの原因を調査する

[Execution User: developer]

# Pending状態のPodがあるか確認
kubectl get pods | grep Pending

# 特定のPodの詳細を確認(Pod名は適宜置き換え)
kubectl describe pod <pending-pod-name>

describe の出力の最後にある Events セクションに、原因が記載されています。

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  30s   default-scheduler  0/1 nodes are available:
           1 Insufficient cpu. preemption: 0/1 nodes are available:
           1 No preemption victims found for incoming pod.

この例では「CPUが不足している」と明確に示されています。

kindクラスタでの対処法

kindはローカル開発用の軽量クラスタであり、リソースに制限があります。以下の対処法を検討してください。

方法1: Podのリソースリクエストを下げる

[Execution User: developer]

cat << 'EOF' > ~/k8s-practice/04-declarative/web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "32Mi"     # 64Mi → 32Mi に削減
            cpu: "50m"         # 100m → 50m に削減
          limits:
            memory: "64Mi"
            cpu: "100m"
EOF

kubectl apply -f ~/k8s-practice/04-declarative/web-deployment.yaml

方法2: マルチノードクラスタを使用する

kindでマルチノードクラスタを作成すれば、より多くのPodをスケジュールできます。
※今回は、趣旨の範囲外になるため割愛します。

本番環境での考慮事項

本番環境では、クラスタのキャパシティプランニングが重要です。

  • Cluster Autoscaler: ノード自体を自動で増減させる機能
  • Resource Quotas: 名前空間ごとのリソース上限を設定
  • Limit Ranges: Podごとのデフォルトリソース制限を設定

これらは本連載の範囲を超えますが、「K8sにはリソース管理の仕組みが豊富に用意されている」ということを覚えておいてください。


5.3 検証:一部のサーバが故障した際の負荷分散挙動

ここまでで、Podのスケールアウトと負荷分散を確認しました。しかし、本当に重要なのは「障害時の挙動」です。

VMの世界では、サーバ障害はアラート対応と手動復旧を意味しました。K8sでは、それがどう変わるのか——実際にPodを「殺して」確認しましょう。

5.3.1 3台中1台のPodを削除。K8sが「何食わぬ顔で」サービスを継続させる様子

障害をシミュレートする

3台のPodのうち1台を、意図的に削除します。

[Execution User: developer]

# 現在のPod一覧を確認
kubectl get pods

# 1台を削除(最初のPodを選択)
POD_TO_DELETE=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
echo "Target pod: $POD_TO_DELETE"

kubectl delete pod "$POD_TO_DELETE"

復旧の様子をリアルタイムで観察する

削除コマンドを実行したら、すぐに別のターミナルでPodの状態を監視します。

[Execution User: developer]

kubectl get pods -w
NAME                        READY   STATUS        RESTARTS   AGE
web-server-5d6b77d8c4-x7k2m   1/1     Terminating   0          1h
web-server-5d6b77d8c4-abc12   1/1     Running       0          30m
web-server-5d6b77d8c4-def34   1/1     Running       0          30m
web-server-5d6b77d8c4-ghi56   0/1     Pending       0          1s
web-server-5d6b77d8c4-ghi56   0/1     ContainerCreating   0   2s
web-server-5d6b77d8c4-ghi56   1/1     Running       0          4s
web-server-5d6b77d8c4-x7k2m   0/1     Terminating   0          1h
web-server-5d6b77d8c4-x7k2m   0/1     Terminating   0          1h

注目すべきは、x7k2m がまだ Terminating(終了処理中)の段階で、すでに ghi56 という新しいPodが ContainerCreating を経て Running になっていることです。

ReplicaSetは「Podが消えそう」と認識した瞬間に、新しいPodの作成を開始します。削除が完了するのを待ちません。この「先行起動」により、実質的なサービス断の時間を最小化しているのです。

サービスの継続性を確認する

Podの入れ替わりの最中でも、Serviceは正常に機能しています。

[Execution User: developer]

# Podの削除中にもリクエストが処理されることを確認
SERVICE_IP=$(kubectl get service web-service -o jsonpath='{.spec.clusterIP}')

kubectl run curl-test --image=curlimages/curl:8.5.0 --rm -it --restart=Never -- \
  sh -c "for i in \$(seq 1 10); do curl -s --max-time 2 http://$SERVICE_IP || echo 'timeout'; sleep 0.3; done"

10回のリクエストすべてが正常に処理されるはずです。1台が消えても、残り2台がリクエストを処理し続け、新しいPodが起動すればそこにもリクエストが振り分けられます。

5.3.2 障害検知から再起動までのスピード:VMのHA機能(再起動)との圧倒的な差

今見た復旧プロセスを、時系列で整理してみましょう。

00:00.000  kubectl delete pod 実行
00:00.100  ReplicaSetが「Podが3台未満になりそう」と検知
00:00.200  新しいPodの作成をスケジューラに依頼
00:00.500  ノードが決定、コンテナ起動開始
00:03.000  コンテナ起動完了、READY状態に
00:05.000  古いPodの終了完了

わずか3秒で新しいPodが稼働状態になりました。

同じ状況をVMware vSphere HA で対応した場合を想像してください。

00:00     VMがクラッシュ
00:30     vCenter がホストの死活監視でVM停止を検知(デフォルト30秒間隔)
00:45     HAが発動、別ホストでのVM再起動を決定
01:00     VMのブートプロセス開始
02:00     OSの起動完了
02:30     アプリケーション(Nginx)の起動完了
03:00     ロードバランサーのヘルスチェック通過、サービス復帰

検知に30秒、VM起動に1〜2分、合計で約3分のダウンタイム。

K8sの3秒と、VMware HAの3分。60倍の差です。

この差は、技術的には「コンテナの起動速度」と「検知の粒度」によるものですが、ビジネス的には「SLA の達成可能性」と「ユーザー体験」に直結します。

もちろん、vSphere HAには「アプリケーションレベルの障害には対応できない」という限界もあります。VMは動いているがNginxプロセスがクラッシュした場合、vSphere HAは何もしません。K8sでは、Liveness Probeによりプロセスレベルの障害も検知し、Podを再起動できます(これは今後の回で扱います)。

5.3.3 【次回予告】新旧バージョンを無停止で入れ替える「ローリングアップデート」

今回は「同じバージョンのPodを増減させる」スケールアウト/インを学びました。

次回は、「異なるバージョンのPodを、サービスを止めずに入れ替える」ローリングアップデートを実践します。

VMの世界では、アプリケーションの更新は「メンテナンス時間を設けてサービスを停止し、全台を一斉に更新する」か、「ロードバランサーから1台ずつ切り離して更新し、戻す」という手順が必要でした。後者は安全ですが、手動作業が多く、深夜メンテナンスの定番でした。

K8sのローリングアップデートは、この「1台ずつ入れ替える」作業を完全に自動化します。しかも、問題が発生すれば即座にロールバック(巻き戻し)できます。

次回予告として、キーとなるパラメータを紹介しておきます。

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 一時的に何台まで増やしてよいか
      maxUnavailable: 0  # 何台まで同時に停止してよいか

この設定により、「常に3台以上が稼働している状態を維持しながら、1台ずつ新バージョンに入れ替える」という動作が実現します。


5.4 トラブルシューティングのTips

本章の締めくくりとして、スケールアウトと負荷分散に関連するトラブルシューティングのTipsをまとめます。

Podの配置状況を一覧で確認する

kubectl get pods -o wide は、Podのトラブルシューティングにおいて最も頻繁に使うコマンドの一つです。

[Execution User: developer]

# 「web」というタグを付けたアプリケーションPodを一覧表示する
kubectl get pods -o wide -l app=web
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE                 NOMINATED NODE   READINESS GATES
web-server-5d6b77d8c4-abc12   1/1     Running   0          1h    10.244.0.6    kind-control-plane   <none>           <none>
web-server-5d6b77d8c4-def34   1/1     Running   0          1h    10.244.0.7    kind-control-plane   <none>           <none>
web-server-5d6b77d8c4-ghi56   1/1     Running   0          30m   10.244.0.8    kind-control-plane   <none>           <none>

この出力から読み取れる情報:

カラム意味トラブルシュート時の着目点
NAMEPod名一意の識別子。ログ調査やexec時に使用
READY準備状態0/1 なら起動途中か異常
STATUS状態Running 以外なら要調査
RESTARTS再起動回数数値が大きいとアプリに問題あり
IPPodのIPアドレスネットワーク疎通確認に使用
NODE配置ノード特定ノードに偏っていないか確認

よくある問題と対処法

問題1: すべてのPodが同じノードに配置される

kindのシングルノード構成では必然ですが、本番環境でこれが起きると可用性が下がります。

対処: Pod Anti-Affinity を設定し、同じDeploymentのPodが異なるノードに分散されるようにする。

問題2: Serviceに接続できない

確認手順:

[Execution User: developer]

# 1. Serviceが存在するか
kubectl get service web-service

# 2. Endpointsが設定されているか
kubectl get endpoints web-service

# 3. Endpointsが空なら、ラベルセレクタを確認
kubectl get pods --show-labels
kubectl describe service web-service | grep Selector

多くの場合、ServiceのSelector と PodのLabels の不一致が原因です。

問題3: 一部のPodにリクエストが偏る

K8sのService(kube-proxy)はデフォルトでランダムまたはラウンドロビンで振り分けますが、長寿命のTCPコネクションでは偏りが生じることがあります。

確認:

[Execution User: developer]

# 各Podへのリクエスト数を簡易的に確認
for pod in $(kubectl get pods -l app=web -o jsonpath='{.items[*].metadata.name}'); do
  count=$(kubectl logs "$pod" 2>/dev/null | wc -l)
  echo "$pod: $count requests (approx)"
done

コマンドチートシート

今回学んだ主要コマンドをまとめます。

## スケール操作
# imperative(即時変更)
kubectl scale deployment <deployment-name> --replicas=<変更後のレプリカ数>

# declarative(マニフェスト適用)
kubectl apply -f <deployment.yamlのパス>               

## 状態確認
kubectl get pods -o wide                         # Pod一覧(詳細)
kubectl get pods -w                              # リアルタイム監視
kubectl get replicaset                           # ReplicaSet確認
kubectl get endpoints <service-name>             # Service の振り分け先確認

# トラブルシュート
kubectl describe pod <pod-name>                  # Pod詳細(Events確認)
kubectl logs <pod-name>                          # コンテナログ
kubectl exec -it <pod-name> -- /bin/sh          # コンテナ内にシェル接続

まとめ:今日の学びと次回への架け橋

今回、私たちは「レプリケーションと負荷分散」という、K8sの真価を体感するテーマに取り組みました。

VMの世界の常識:

  • サーバを増やすには、クローン作成、ホスト名変更、IP設定、ロードバランサー登録という一連の手作業が必要
  • 障害復旧には、検知から再起動完了まで数分のダウンタイムが避けられない
  • これらの作業は、深夜メンテナンスや緊急対応で人間が行う

K8sの新しい常識:

  • replicas: 3 という1行で、Podは秒速で増殖する
  • Serviceが自動的にPodを検出し、負荷を分散する
  • Podが消えても、ReplicaSetが即座に新しいPodを起動し、サービスは継続する
  • これらはすべて、人間の介入なしに自動で行われる

この「自動化」と「宣言的管理」の組み合わせが、K8sがVM時代のインフラ運用を過去のものにする理由です。

次回は、この自動化をさらに一歩進め、アプリケーションの更新を無停止で行う「ローリングアップデート」に挑戦します。nginx:1.27 から nginx:1.28 へ、サービスを止めずに、しかも問題があれば即座に巻き戻せる——その魔法の仕組みを、一緒に体験しましょう。


今回のキーワード

  • ReplicaSet: 指定された数のPodを維持するK8sリソース
  • Reconciliation Loop: 「あるべき状態」と「現在の状態」の差分を埋め続ける自動調整機構
  • Endpoints: ServiceがどのPodにトラフィックを振り分けるかを管理するリソース
  • スケールアウト/イン: Podの数を増減させること(水平スケーリング)