Kubernetes 基礎:Pod、Deployment、Service

1. 概要

Kubernetesには数十種類のリソースがありますが、日々の業務の80%は3つの概念に集中しています:Pod、Deployment、Service。このレッスンを読むことで、各リソースの本質を理解し、それらがどのように相互に関連しているか、そして実際にクラスタ上で動作するアプリケーションをデプロイするマニフェスト YAMLを書けるようになります。

このレッスンを終えた後、Spring Boot アプリケーションを Kubernetes にデプロイし、複数のレプリカにスケーリングし、Service 経由で外部に公開できるようになります —— すべては kubectl といくつかの YAML ファイルだけです。

注意: このレッスンは、すでにクラスタ(Minikube、Kind、またはクラウド)があり、kubectl が正しいコンテキストを指しているものと仮定しています。

2. Pod —— 最小のデプロイメント単位

2.1. Pod とは

Pod は、ネットワーク名前空間(同じ IP、同じポート空間)とボリュームを共有する 1 つ以上のコンテナのグループです。実際には、ほとんどの Pod には1 つのアプリケーションコンテナのみが含まれています。

apiVersion: v1
kind: Pod
metadata:
  name: order-service-pod
  labels:
    app: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      ports:
        - containerPort: 8080
      resources:
        requests:
          cpu: "250m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"

説明:

  • metadata.labels は app: order-service ラベルを付与します —— これが Kubernetes が Pod を「識別」する方法であり、Service と Deployment はこのラベルを使用して関連するオブジェクトを選択します。
  • resources.requests は、scheduler が Pod をノードに配置するために必要な最小リソースです。limits は上限で、超過するとスロットル(CPU)または終了(メモリ —— OOMKilled)されます。
  • containerPort: 8080 はあくまで情報の宣言です。外側へのポート公開は自動では行われません。

適用して確認:

kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
order-service-pod    1/1     Running   0          5s

2.2. 直接 Pod を作成すべきでない理由

上記のようにベアな Pod を作成した場合、大きな問題があります:Pod が含まれているノードが停止するか Pod がクラッシュすると、自動的に再作成されることがありません。Pod には self-healing やスケーリングメカニズムがないのです。

注意: 本番環境では、直接 Pod を作成することはほぼありません。常に Deployment(または StatefulSet、Job)を使用して Pod を管理してください。

3. Deployment —— Pod のライフサイクル管理

3.1. Deployment の宣言

Deployment は ReplicaSet 経由で Pod のセットを作成・管理し、常に正確な数のレプリカが実行されることを保証し、ダウンタイムなしのローリングアップデートをサポートします。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10

説明:

  • replicas: 3 は、Deployment が常に 3 つの Pod が実行されていることを維持することを要求します —— 1 つの Pod が停止すると、Deployment は自動的に新しい Pod を作成して置き換えます。
  • selector.matchLabels は template.metadata.labels と一致する必要があります。これは Deployment とそれが管理する Pod の間の必須のバインディングです。
  • readinessProbe は Pod がトラフィックを受け取るかどうかを決定します(Service によって使用されます)。livenessProbe は Pod が再起動する必要があるかどうかを決定します。この 2 つのプローブを混同することは、Kubernetes を学習するときの非常に一般的なエラーです。

適用して観察:

kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
order-service   3/3     3            3           12s

NAME                             READY   STATUS    RESTARTS   AGE
order-service-7d4f9c8b6d-2kx9p   1/1     Running   0          12s
order-service-7d4f9c8b6d-7h2mn   1/1     Running   0          12s
order-service-7d4f9c8b6d-xq1lw   1/1     Running   0          12s

3.2. スケーリングとローリングアップデート

YAML を編集せずにレプリカをスケールアップ/ダウン:

kubectl scale deployment order-service --replicas=5

イメージを変更する際のローリングアップデート —— Deployment は古い Pod を新しい Pod に置き換え、ローリング戦略に従ってダウンタイムなしで実行します:

kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out

新しいバージョンにバグがある場合、すぐに前のバージョンにロールバックできます:

kubectl rollout undo deployment/order-service

3.3. ローリングアップデート戦略の設定

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

maxUnavailable: 1 はアップデート中に最大 1 つの Pod を「失う」ことを許可します。maxSurge: 1 は、宣言されたレプリカ数を超える最大 1 つの新しい Pod を作成することを許可します。replicas: 3 の場合、クラスタの遷移中に 2-4 の Pod が実行されることがありますが、2 未満になることはありません。

4. Service —— Pod の安定した公開

4.1. Service が必要な理由

Pod には固有の IP がありますが、この IP は Pod が再作成されるたびに変わります(クラッシュ、ローリングアップデート、スケーリング)。Service は Pod グループの前に安定したアドレス(ClusterIP + DNS 名)を提供し、ラベルセレクタに基づいてトラフィックを自動的にロードバランスします ——基盤となる Pod がいくら変わってもかまいません。

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

説明:

  • selector.app: order-service は Pod 上のラベルと一致します(Deployment が template.metadata.labels に付与します)—— Service はこの Pod を自動的に検出・追跡し、Endpoints という中間オブジェクトを通じて管理します。
  • port: 80 は Service がクラスタ内部でリッスンするポートです。targetPort: 8080 はコンテナが実際に公開するポートです。この 2 つの数値が同じである必要はありません。
  • type: ClusterIP(デフォルト)はクラスタ内からのみアクセス可能です ——マイクロサービス間の内部 Service に適しています。

Service と Endpoints を確認:

kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
order-service   ClusterIP   10.96.142.201   <none>        80/TCP    4s

NAME            ENDPOINTS                                   AGE
order-service   10.244.1.5:8080,10.244.1.6:8080,+1 more     4s

4.2. Service type の種類

Type アクセス範囲 用途
ClusterIP クラスタ内のみ マイクロサービス間の内部通信
NodePort すべてのノード上の固定ポート(30000-32767) クイックテスト、dev/オンプレ環境
LoadBalancer クラウドプロバイダーを通じて公開 IP を割り当て クラウド上の Service を Internet に公開

Minikube 上でクイックテストするために外部に公開する例:

apiVersion: v1
kind: Service
metadata:
  name: order-service-external
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
  type: NodePort
minikube service order-service-external --url

注意: 本番環境で NodePort を使用するのはアンチパターンです —— ポート範囲が制限され、Ingress のような TLS termination やヘルスベースのルーティングがありません。クラウド上の本番環境では、LoadBalancer または Ingress Controller を優先してください。

5. 実際のアプリケーションで3つすべてを組み合わせる

完全なパイプライン:クライアントが Service を呼び出す → Service は Deployment が管理する Pod にロードバランスします。

クライアント
  │
  ▼
Service (order-service, ClusterIP 10.96.142.201:80)
  │  (label selector: app=order-service)
  ▼
┌─────────────┬─────────────┬─────────────┐
│  Pod #1     │  Pod #2     │  Pod #3     │
│  :8080      │  :8080      │  :8080      │
└─────────────┴─────────────┴─────────────┘
  ▲ 下記で管理
  Deployment (order-service, replicas: 3)

デプロイを 1 回で完了するために、Deployment と Service を 1 つのファイルに結合し、--- で区切ることができます:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
kubectl apply -f order-service-all.yaml

6. デプロイ前に readiness エンドポイントのテストを書く

Kubernetes のプローブが /actuator/health/readiness に依存する前に、Spring Boot のコードでこのエンドポイントが正しい状態を返すことを確認しておく必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void readinessProbe_shouldReturnUp() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/actuator/health/readiness", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("\"status\":\"UP\"");
    }
}

このテストは、TestRestTemplate を使用して実際の readiness エンドポイントを呼び出し、HTTP 200 とボディに UP が含まれていることを確認します。これはまさに Deployment の readinessProbe が定期的に呼び出すエンドポイントです ——このテストに失敗した場合、Pod はクラスタ上で決して「Ready」状態にはなりません。

7. よくあるエラー

  • Selector がラベルと一致しない:selector と labels の記述が間違っているか異なっているため、Service または Deployment が Pod を見つけられません(Endpoints が空)。常に kubectl get endpoints <svc-name> で確認してください。
  • readinessProbe がない:Service がまだアプリケーション起動中の Pod にトラフィックをルーティングし、デプロイ直後に 502/503 エラーが発生します。
  • Resource requests/limits がない:Pod がスケジューラーによって誤ったノードに配置されたり、トラフィックが増加すると予期せず OOMKilled されたりする可能性があります。
  • 本番環境でベア Pod を使用する:self-healing がないため、Pod が失われると完全に失われ、手動で再作成する必要があります。
  • イメージタグ latest:ローリングアップデートが変更を認識せず、Kubernetes はイメージが変わらないと思い、再度プルしません。常に特定のバージョンタグを使用してください。

8. 結論

Pod は最小のコンテナ実行単位であり、Deployment は Pod セットのライフサイクルとスケーリングを管理し、Service は基盤となる Pod が絶えず変わってもそれらにアクセスするための安定したアドレスを提供します。この 3 つのビルディングブロックを確実に理解することは、後の Ingress、ConfigMap/Secret、Helm チャートに進むための基礎です。次のステップとしては、実際の Spring Boot アプリケーションをデプロイし、ローリングアップデートを観察し、Pod を手動で削除して Deployment がどのように自動回復するかを見てみてください。

kubernetesdevopscontainerorchestration