Kubernetes Patterns
本番環境対応のKubernetesパターンで、ワークロードを確実にデプロイ、管理、デバッグします。
アクティブにするタイミング
- Kubernetesマニフェスト(Deployments、Services、Ingress、Jobs)を作成するとき
- リソース要求/制限、liveness/readinessプローブを設定するとき
- RBAC、名前空間、ServiceAccountsを設定するとき
- K8sで設定とシークレットを管理するとき
- CrashLoopBackOff、OOMKilled、保留中Pod、イメージプルエラーをデバッグするとき
- HPA(Horizontal Pod Autoscaler)またはPodDisruptionBudgetsを設定するとき
- K8s YAMLのセキュリティや正確性をレビューするとき
使用するタイミング
上記のアクティブにするタイミングと同じです。このエイリアスはリポジトリのスキルフォーマット規約に準拠します。このスキルは、KubernetesのYAMLとワークロードを作成、レビュー、デバッグするときはいつでも使用してください。
仕組み
このスキルは、コピー&ペースト可能な本番環境対応のYAMLパターンとkubectlデバッグコマンドをタスク別に整理して提供します。
- Deploymentテンプレート — セキュリティコンテキスト、ローリングアップデート戦略、3種類のプローブ、リソース制限、ConfigMap/Secretからの環境変数注入を備えた、完全に設定された本番用Deployment。
- プローブ — startup/liveness/readinessの使い分け表と、
failureThreshold × periodSecondsの正しい計算。 - Services & Ingress — ClusterIP、LoadBalancer、TLS Ingressのパターン(cert-managerアノテーション付き)。
- ConfigMaps & Secrets —
envFrom、ファイルマウント、外部シークレットのガイダンス。 - リソース管理 — ワークロードタイプ(Web API、JVM、ワーカー、サイドカー)ごとの要求/制限のルール。
- RBAC — 最小権限のServiceAccount → Role → RoleBindingチェーン。
- HPA & PDB — オートスケーリングとノードドレイン安全設定。
- Jobs & CronJobs — 一回限りおよびスケジュールされたワークロードパターン(正しい
restartPolicy付き)。 - kubectlチートシート — ログ、exec、ロールバック、ポートフォワード、dry-run、一般的なエラー診断コマンド。
- アンチパターン & チェックリスト — やってはいけないことと、セキュリティ/信頼性/可観測性のチェックリスト。
例
以下のセクションで、完全で実行可能な例を参照してください。クイックリファレンス:
| タスク | ジャンプ先 |
|---|---|
| 完全な本番用Deployment YAML | コアワークロードパターン |
| プローブ設定 | プローブ |
| RBAC最小権限設定 | RBAC |
| CrashLoopBackOffのデバッグ | kubectlデバッグチートシート |
| オートスケーリング | HPA |
コアワークロードパターン
Deployment — 本番用テンプレート
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新中に1つの追加Podを許可
maxUnavailable: 0 # 希望数を下回らない
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Podレベルのセキュリティコンテキスト
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# グレースフルシャットダウン
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # :latestは絶対に使用しない
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# リソース要求と制限の両方が必要
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# コンテナセキュリティコンテキスト
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# プローブ(以下のプローブセクションを参照)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# ConfigMapとSecretからの環境変数
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# readOnlyRootFilesystem: trueの場合の書き込み可能なtmpディレクトリ
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
プローブ — Liveness、Readiness、Startup
各プローブを使用するタイミングを理解することは重要です:
| プローブ | 失敗時の動作 | 使用目的 |
|---|---|---|
startupProbe | 起動が遅い場合にコンテナを強制終了 | 起動の遅いアプリ(JVM、Python) |
livenessProbe | コンテナを再起動 | デッドロック/ハングしたプロセスの検出 |
readinessProbe | Serviceエンドポイントから削除 | 一時的な利用不可(DB再接続) |
# 正しいパターン:startupProbeが遅い起動をカバーし、
# その後liveness/readinessが引き継ぐ
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 最大起動時間150秒
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 再起動まで90秒
readinessProbe:
httpGet:
path: /ready # 別のエンドポイント:DB、キャッシュなどをチェック
port: 8080
periodSeconds: 10
failureThreshold: 2
# 間違い:startupProbeなしでinitialDelaySecondsを使用
# アプリの起動に60秒かかる場合は、代わりにstartupProbeを設定
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 悪い例:任意の待機時間、競合状態を引き起こす
ServicesとIngress
サービスタイプ
# ClusterIP(デフォルト)— 内部のみ
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — 外部トラフィック(クラウドプロバイダー)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
TLS付きIngress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMapsとSecrets
ConfigMap — 機密性の低い設定
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# 複雑な設定はファイルとしてマウント
app.yaml: |
server:
port: 8080
timeout: 30s
# ConfigMapをファイルとしてマウント
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secrets — 機密データ
# リテラルからSecretを作成(CLI、その後Vault/SOPSに保存)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# 値はbase64エンコードされています(暗号化されていません — 実際の暗号化にはSealed SecretsまたはESOを使用)
data:
db-password: czNjcjN0 # 's3cr3t'のbase64
重要: 生のKubernetes Secretsはbase64エンコードされているだけで、クラスターで暗号化が設定されていない限り、保存時に暗号化されません。本番環境ではSealed SecretsまたはExternal Secrets Operatorを使用してください。
リソース要求と制限
resources:
requests: # スケジューラーがPodの配置に使用
cpu: "100m" # 100ミリコア = 0.1 CPU
memory: "128Mi"
limits: # これを超えるとコンテナが強制終了/スロットリングされる
cpu: "500m"
memory: "256Mi"
経験則:
| ワークロードタイプ | CPU要求 | メモリ要求 | 注意事項 |
|---|---|---|---|
| Web API | 100–250m | 128–256Mi | 制限は要求の2〜4倍に設定 |
| ワーカー/コンシューマー | 250–500m | 256–512Mi | 予測可能性のためにメモリ制限 = 要求 |
| JVMアプリ | 500m–1 | 512Mi–2Gi | JVMオーバーヘッドのために-Xmx以上の余裕を確保 |
| サイドカー | 10–50m | 32–64Mi | 最小限に抑える |
# 間違い:要求も制限もなし — スケジューリングが予測不能、OOMによる強制終了
containers:
- name: app
image: myapp:latest
# resources: {} が欠落 — 本番環境では危険
# 間違い:要求なしで制限のみ — 要求が制限値にデフォルト設定され、容量を過剰予約
resources:
limits:
cpu: "2"
memory: "1Gi"
# 要求が欠落 — 制限値にデフォルト設定される
RBAC — ロールとサービスアカウント
最小権限の原則
アプリがKubernetes APIを呼び出すかどうかによって2つのパターン:
パターンA — アプリがKubernetes APIを必要としない(ほとんどのアプリ)
ServiceAccountでトークンの自動マウントを無効にします。Role/RoleBindingは不要です。
# トークンが無効なServiceAccount — 最も安全なデフォルト
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # PodにK8s APIトークンが注入されない
# Deploymentで参照 — トークンなし、APIアクセスなし
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # 二重の安全策:Podレベルでも設定
パターンB — アプリがKubernetes APIを必要とする(オペレーター、コントローラー、設定監視)
トークンを有効にし、実際に必要な権限のみを付与します。
# 1. ServiceAccount — このSAのトークンを有効にする
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # トークンが必要:アプリがK8s APIを呼び出す
# 2. Role — アプリが必要なものだけを付与(名前空間スコープ)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # 読み取り専用、特定のリソース
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # 特定のシークレット名に制限
verbs: ["get"]
# 3. RoleをServiceAccountにバインド
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. DeploymentでSAを参照
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountTokenはSAからデフォルトでtrue — トークンが注入される
Horizontal Pod Autoscaler (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # 高可用性のために常に少なくとも2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 平均CPU使用率が70%を超えたらスケールアップ
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPAはすべてのコンテナに
resources.requestsが設定されている必要があります。使用率はcurrent / requestとして計算されます。
PodDisruptionBudget (PDB)
ノードドレインやローリング更新中に、同時にダウンするPodが多くなりすぎるのを防ぎます:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # またはmaxUnavailable: 1を使用
selector:
matchLabels:
app: my-app
名前空間とマルチテナンシー
# リソースクォータ付きの名前空間を作成
kubectl create namespace my-namespace
# ResourceQuotaを適用して名前空間の消費を制限
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
JobsとCronJobs
# 一回限りのJob(DBマイグレーション、データ処理)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # 失敗時に最大3回リトライ
ttlSecondsAfterFinished: 3600 # 1時間後に自動削除
template:
spec:
restartPolicy: OnFailure # Jobの場合はNever(Alwaysではない)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# スケジュールされたCronJob(夜間バッチ、クリーンアップ)
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-cleanup
namespace: my-namespace
spec:
schedule: "0 2 * * *" # 毎日午前2時(UTC)
startingDeadlineSeconds: 300 # ジョブが開始されなかった場合の猶予時間
successfulJobsHistoryLimit: 3 # 保持する成功履歴数
failedJobsHistoryLimit: 1 # 保持する失敗履歴数
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: ghcr.io/org/cleanup-tool:1.0.0
command: ["/cleanup.sh"]
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
CronJobのタイムゾーン(Kubernetes 1.25+)
spec:
schedule: "0 9 * * 1-5" # 月曜〜金曜の午前9時
timeZone: "America/New_York" # 明示的にタイムゾーンを指定
kubectlデバッグチートシート
基本的なデバッグコマンド
# イベントの確認 — トラブルシューティングの最初のステップ
kubectl get events --sort-by='.lastTimestamp' -n my-namespace
# Podの詳細確認
kubectl describe pod <pod-name> -n my-namespace
# ログを確認
kubectl logs <pod-name> -n my-namespace
kubectl logs <pod-name> -n my-namespace --previous # 以前のインスタンスのログ
kubectl logs -l app=my-app -n my-namespace --tail=50 # ラベルセレクタ付き
# 実行中のコンテナでシェルを実行
kubectl exec -it <pod-name> -n my-namespace -- /bin/sh
kubectl exec -it <pod-name> -n my-namespace -c <container-name> -- /bin/sh # 特定のコンテナ
一般的なエラーの診断
# CrashLoopBackOff — 終了コードとログを確認
kubectl get pod <pod-name> -n my-namespace -o json | jq '.status.containerStatuses[0].lastState.terminated.exitCode'
kubectl logs <pod-name> -n my-namespace --previous | tail -30
# OOMKilled — メモリ制限を増やす必要があるか確認
kubectl describe pod <pod-name> -n my-namespace | grep -A5 "State:.*Terminated"
# 終了理由: OOMKilled を探す
# ImagePullBackOff — イメージ名とタグを確認
kubectl describe pod <pod-name> -n my-namespace | grep "Failed to pull image"
# スペルミス、存在しないイメージ、レジストリ認証の問題を確認
# Podが保留中 — リソース不足
kubectl describe pod <pod-name> -n my-namespace | grep -A5 "Events:"
# "0/3 nodes are available" を探す — 要求がクラスター容量を超えている
# PodPending (コンテナの作成に失敗) — 一時的なストレージの問題
kubectl get events -n my-namespace | grep "FailedCreatePodContainer"
# ノード上のディスク容量不足が原因の可能性
ネットワークデバッグ
# Serviceがエンドポイントを持っているか確認
kubectl get endpoints -n my-namespace
# Service DNSを確認(クラスター内の別のPodから)
kubectl run tmp-shell --rm -it --image=nicolaka/netshoot -- /bin/sh
# その後: nslookup my-app.my-namespace.svc.cluster.local
# ポートフォワード(直接テスト用)
kubectl port-forward pod/<pod-name> 8080:8080 -n my-namespace
# その後: curl http://localhost:8080/health
設定とロールバック
# デプロイメントの変更を確認
kubectl describe deployment my-app -n my-namespace | grep "Change Cause"
kubectl rollout history deployment my-app -n my-namespace
# ロールバック
kubectl rollout undo deployment my-app -n my-namespace
# 特定のリビジョンにロールバック
kubectl rollout undo deployment my-app -n my-namespace --to-revision=3
# 実際に適用せずにYAMLを検証
kubectl apply -f manifest.yaml --dry-run=client -o yaml
リソース消費の監視
# ノードのリソース使用量
kubectl top nodes
# Podのリソース使用量(metrics-serverが必要)
kubectl top pods -n my-namespace
# すべてのネームスペースの使用量を要約
kubectl top pods -A | awk '{sum+=$3} END {print "総CPU使用量: " sum "m"}'
kubectl top pods -A | awk '{sum+=$4} END {print "総メモリ使用量: " sum "Mi"}'
アンチパターンとチェックリスト
やってはいけないこと
| アンチパターン | 問題 | 代わりにすべきこと |
|---|---|---|
:latest タグを使用 | 再現不可能なデプロイ、デバッグが困難 | セマンティックバージョンまたはコミットSHAを使用 |
| リソース要求を省略 | スケジューリングの問題、OOM強制終了 | 常に要求と制限の両方を設定 |
| プローブなし | K8sはPodが死んでいるか応答不能かを検出できない | 常にliveness + readiness(必要に応じてstartup)を追加 |
initialDelaySeconds を代用 | 競合状態、不必要な再起動 | 遅い起動にはstartupProbeを使用 |
| デフォルトのServiceAccountを使用 | 権限過多、監査証跡がない | アプリごとに専用のServiceAccountを作成 |
| Secretをプレーンテキストでコミット | 資格情報が漏洩 | 外部シークレットストアまたはSealed Secretsを使用 |
Podに直接nodePortを公開 | セキュリティリスク、K8s抽象化の損失 | Service (ClusterIP -> Ingress) を使用 |
privileged: true を使用 | セキュリティ大幅低下 | 特権が必要な場合はPodSecurityPolicy/Admission Controllerを使用 |
プロダクション準備チェックリスト
セキュリティ
- すべてのコンテナで
runAsNonRoot: trueが設定されている -
allowPrivilegeEscalation: falseが設定されている - 不必要な機能が
capabilities.drop: ["ALL"]で削除されている -
readOnlyRootFilesystem: trueが設定されている(tmp用のemptyDirあり) - デフォルトのServiceAccountに
automountServiceAccountToken: falseが設定されている - Secretがコミットされていない(外部ストアを使用)
信頼性
- startup/liveness/readinessプローブが設定されている
- 適切な
terminationGracePeriodSeconds(デフォルトの30秒より長い場合は調整) - 高可用性のために
replicas >= 2 - 中断に対する保護のための
PodDisruptionBudget - 自動スケーリングのための
HorizontalPodAutoscaler - アップデートが中断を引き起こさないためのローリングアップデート戦略
可観測性
- すべてのコンテナにリソース要求と制限が設定されている
- Podに意味のある
labelsがある - アプリケーションが/healthと/readyエンドポイントを公開している
- 構造化ログ(JSON)がstdout/stderrに出力されている
- メトリクス(Prometheusなど)が公開されている