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 がどのように自動回復するかを見てみてください。