Kubernetes入門|コンテナオーケストレーションの基礎から本番運用まで
Kubernetes,Docker,コンテナ,DevOps,インフラ
はじめに
Dockerでコンテナ化したアプリケーションを、本番環境でどう運用すればいいのか?複数のコンテナを管理し、スケーリングし、障害に対応する...。これらの課題を解決するのがKubernetes(K8s)です。
この記事では、Kubernetesの基本概念から実践的な運用方法まで、詳しく解説します。
ただし、Kubernetesは「用語を覚えた」だけだと本番で詰まります。
現場でつまずきやすいのは概ね次です。
- Podは動くのに外から繋がらない(Service Ingress NetworkPolicy)
- 起動はするがすぐ落ちる(Probe ResourceLimit OOM)
- 設定や機密情報の扱いが統一されず破綻(ConfigMap Secret)
- ロールバックやデバッグの手順がなく、障害時に手が止まる
本記事はリソースの説明に加えて
何から固めるべきか、どこを設計で固定すべきか、運用で事故らないチェックリストまで含めて整理します。
まず学ぶ順番(最短ルート)
Kubernetesは要素が多いので、順番を固定すると理解が早いです。
- Deploymentで Pod を増減できる
- Serviceで Pod の前に安定した入口を作る
- ConfigMap Secretで設定を外出しする
- Ingressで外部公開をする
- HPAでオートスケールする
- 監視とログ、ロールバック手順を整える
まず決める設計判断の軸
コンテナに何を入れて 何を入れないか
- コンテナイメージは不変で再現可能にする
- 設定値は環境ごとに外出しする
- ローカルファイルに状態を持たない(永続化はPVなどに逃がす)
リソース要求と制限をどう置くか
リソースは置かないより「最初から置く」方が安全です。
無制限だとノード全体を巻き込んで落ちます。
- requestsはスケジューリングの前提
- limitsは暴走防止
監視とデバッグの入口をどこに置くか
障害時に見る場所を決めておくと、復旧が早くなります。
- まず events
- 次に describe
- 次に logs
- 最後に exec
事故防止チェックリスト(最小版)
- readiness と liveness と startup の役割を理解して分けている
- requests と limits を設定している
- Secretを平文でコミットしていない
- ロールバック手順(Deploymentの履歴)が確認できる
- 外部公開経路を図で説明できる(Ingress Service Pod)
Kubernetesとは
コンテナオーケストレーションの必要性
Docker単体では以下のような課題があります。
| 課題 | 説明 |
|---|---|
| スケーリング | コンテナの数を動的に増減させたい |
| 負荷分散 | 複数コンテナへのトラフィック分散 |
| 自動復旧 | 障害時に自動で再起動したい |
| ローリングアップデート | ダウンタイムなしでデプロイしたい |
| サービスディスカバリ | コンテナ間の通信を管理したい |
Kubernetesはこれらすべてを解決します。
アーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Control Plane │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ API │ │ etcd │ │Scheduler │ │Controller │ │ │
│ │ │ Server │ │ │ │ │ │ Manager │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Node 1 │ │ Node 2 │ ... │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ kubelet │ │ │ │ kubelet │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ kube-proxy │ │ │ │ kube-proxy │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │ │
│ │ │ Pod │ │ Pod │ │ │ │ Pod │ │ Pod │ │ │
│ │ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
コンポーネントの役割
| コンポーネント | 役割 |
|---|---|
| API Server | クラスタへのすべてのリクエストを受け付ける |
| etcd | クラスタの状態を保存するKVS |
| Scheduler | Podをどのノードに配置するか決定 |
| Controller Manager | 各種コントローラーを実行 |
| kubelet | ノード上でPodを管理 |
| kube-proxy | ネットワークプロキシ |
基本リソース
Pod
Kubernetesの最小デプロイ単位です。1つ以上のコンテナを含みます。
まず押さえるポイント
- Podは「Kubernetesが管理する実行単位」で、Node上に実体として配置される
- Podは落ちたり再作成される前提なので、IPやローカルディスクなどは永続・固定を期待しない
- 本番では通常、Podを直接作るより Deployment / Job など上位リソースから作る(再現性と運用性が高い)
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
version: v1
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 8080
env:
- name: NODE_ENV
value: "production"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# Podの作成
kubectl apply -f pod.yaml
# Podの一覧
kubectl get pods
# Podの詳細
kubectl describe pod my-app
# Podのログ
kubectl logs my-app
# Podに入る
kubectl exec -it my-app -- /bin/sh
# Podの削除
kubectl delete pod my-app
Deployment
ステートレスなPodを「望む数だけ」「落ちても戻る」ように維持しつつ、
**ダウンタイムを抑えて更新(ローリングアップデート)**するための入口がDeploymentです。
厳密には、
- Deployment は「更新戦略」と「ReplicaSetの世代管理(ロールアウト/ロールバック)」を管理
- ReplicaSet が「指定数のPodレプリカ維持」を管理
- Pod は実行単位
という責務分担になっています。
何が嬉しい?
- Podを直接触らずに「更新」「ロールバック」「レプリカ数調整」を一貫して扱える
- ロールアウト中の不具合を
rollout undoで戻しやすい(運用の手戻りが小さい)
いつ使う?
- Web API / フロント / ワーカーなど、基本は「常に動いていて欲しい」ステートレス系
よくある事故
-
selectorとtemplate.labelsがズレて、意図しないPod集合を掴む -
maxUnavailableを大きくしすぎて、更新時に一気に落ちてしまう
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# Deploymentの作成
kubectl apply -f deployment.yaml
# レプリカ数の変更
kubectl scale deployment my-app --replicas=5
# イメージの更新(ローリングアップデート)
kubectl set image deployment/my-app app=my-app:2.0.0
# ロールアウト状況の確認
kubectl rollout status deployment/my-app
# ロールアウト履歴
kubectl rollout history deployment/my-app
# ロールバック
kubectl rollout undo deployment/my-app
kubectl rollout undo deployment/my-app --to-revision=2
Service
Podへのネットワークアクセスの“入口”を作り、到達先Podが入れ替わっても安定して接続できるようにします。
注意点として、PodにはPod IPが付くため同一クラスタ内からPodへ直接アクセス自体は可能です。
ただしPod IPは再作成で変わり、レプリカが増減すると到達先選択も困るため、通常はアプリへの入口をServiceに寄せます。
Serviceが提供するもの(代表)
- 安定した仮想IP(ClusterIP)
-
DNS名(例:
my-app-service.<namespace>.svc.cluster.local) - ラベルセレクタでPod集合を束ねる(Service Discovery)
- (typeによって)ノード外/クラウドLB経由の公開
いつ使う?
- 「このアプリ(Pod群)にリクエストを届けたい」と思ったら基本はService
- PodのIPが変わってもクライアント側を変えたくないとき
よくある事故
-
selectorが間違っていて「ServiceはあるのにEndpointsが空」になる -
targetPortの設定ミスで疎通できない - readinessが落ちてEndpointsから外れているのに、ネットワークのせいだと勘違いする
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP # ClusterIP, NodePort, LoadBalancer
Serviceの種類
まず結論(迷ったときの指針)
- ClusterIP: クラスタ内向けの基本形。まずはこれ。
- NodePort: Ingress ControllerやLBがない環境での暫定公開・検証用。常用は慎重に。
- LoadBalancer: クラウド/MetalLBなどがあるなら、外部公開の標準ルート。
外部公開の全体像(よくある構成)
- L7(HTTP/HTTPS)で公開したい: Ingress →(Ingress Controller)→ Service(ClusterIP)→ Pod
- L4(TCP)で単体公開したい: Service(LoadBalancer)→ Pod
# ClusterIP(デフォルト): クラスタ内部のみ
apiVersion: v1
kind: Service
metadata:
name: internal-service
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
# いつ使う?
# - ほぼ常に。Pod群への安定した入口(DNS/ClusterIP)が欲しいとき
# 注意
# - クラスタ外から到達できない(別途IngressやLBが必要)
# NodePort: 各ノードのポートを開放
apiVersion: v1
kind: Service
metadata:
name: nodeport-service
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 30000-32767
# いつ使う?
# - 手元検証・簡易デモ・Ingress Controllerの入口(前段にLBを置けない場合)など
# 避けたい/注意
# - ノードの全台でポートが開くため攻撃面が増える
# - スケール/ノード入れ替えで到達先や許可設定がブレやすい(本番常用は慎重に)
# LoadBalancer: クラウドのロードバランサーを使用
apiVersion: v1
kind: Service
metadata:
name: lb-service
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
# 補足
# - 多くのクラウド環境では Service type=LoadBalancer が「外部IP(External IP)を払い出して公開する」標準ルート
# - ただし外部IPの取り扱いは環境/実装(クラウドコントローラ、MetalLB等)に依存します
# - ExternalIPs を手動で入れる運用も可能ですが、ネットワーク設計・セキュリティの影響が大きいので原則は管理された方式を推奨
# いつ使う?
# - 外部へ“単体で”公開したい(L4) / Ingress Controllerの前段にLBを置きたい
# 注意
# - LBの払い出しはコストやクォータに効く(作りすぎ注意)
Ingress
主にHTTP/HTTPSの公開・ルーティング・TLS終端など、**L7(アプリ層)**のトラフィック管理を担当します。
Ingress Controllerが“実体”(ここを知らないとハマる)
Ingressは「ルールを表現するリソース」で、実際に通信をさばくのは Ingress Controller(nginx-ingress、Traefik など)です。
つまり
- Ingressリソースだけ作っても、Controllerがいなければ何も起きない
-
ingressClassNameがControllerの対象と一致しないと、拾われずに無視される
という前提があります。
補足
- “Ingress” というリソース自体はHTTPルールを表現するのが中心です
- 一方で Ingress Controllerの実装次第では TCP/UDP(例: gRPC以外の生TCP)を扱える拡張機能を持つものもあります
- そのため「HTTPだけ」と断言せず「基本はHTTP/HTTPS、拡張で別プロトコルもあり得る」と理解すると事故りにくいです
何が嬉しい?
-
ホスト名/パスベースのルーティング(
api.example.comとexample.comを分ける等) - TLS終端や証明書(cert-manager等)との連携がしやすい
いつ使う?
- 外部公開がHTTP/HTTPSで、複数サービスを1つの入口でさばきたいとき
よくある事故
- Ingress Controllerが未導入/未設定で、Ingressリソースを作っても何も起きない
-
ingressClassNameの指定ミスでコントローラに拾われない -
pathとアプリ側ルーティングの認識ずれ(rewriteの有無)で404になる
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- api.example.com
secretName: tls-secret
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
ConfigMap と Secret
ここは運用で差が出ます。
「イメージは不変、設定は外出し」を徹底すると、環境差分(dev/stg/prod)を安全に扱えます。
ざっくり
- ConfigMap: 機密ではない設定(フラグ、接続先ホスト名、ログレベルなど)
- Secret: パスワードやAPIキーなどの機密(ただし“暗号化されて安全”と誤解しない。保護は運用設計も必要)
ConfigMap
何が嬉しい?
- 同じコンテナイメージを使い回しつつ、設定だけ差し替えられる
- キー/値を環境変数にもファイルにもできる(アプリ側事情に合わせられる)
注意
- ConfigMap更新を即時反映したいか(Pod再起動が必要なケース)を最初に決める
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "db.example.com"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
config.json: |
{
"debug": false,
"maxConnections": 100
}
# Podでの使用
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
# 環境変数として使用
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
# すべてのキーを環境変数として使用
envFrom:
- configMapRef:
name: app-config
# ファイルとしてマウント
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
Secret
注意(入門で勘違いしがち)
-
data:はbase64であって暗号化ではありません - etcd暗号化、RBAC、Secretの配布方式(External Secrets等)まで含めると「漏れにくさ」が上がります
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
# base64エンコードされた値
DATABASE_PASSWORD: cGFzc3dvcmQxMjM=
API_KEY: c2VjcmV0LWFwaS1rZXk=
stringData:
# プレーンテキストで記述可能(適用時にエンコードされる)
ANOTHER_SECRET: "plain-text-secret"
# base64エンコード
echo -n 'password123' | base64
# Secretの作成(コマンドライン)
kubectl create secret generic app-secrets \
--from-literal=DATABASE_PASSWORD=password123 \
--from-literal=API_KEY=secret-api-key
PersistentVolume と PersistentVolumeClaim
永続化が必要なワークロード(DB、ファイルアップロード、キャッシュの永続化など)で使います。
逆に、ステートレスなWeb APIはまず「永続化しない設計」を優先した方が運用が楽です。
ざっくり
- PV(PersistentVolume): クラスタ側が提供するストレージの“実体”
- PVC(PersistentVolumeClaim): Pod側が要求するストレージの“リクエスト”
# persistent-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: standard
hostPath:
path: /data/my-pv
# persistent-volume-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard
# Podでの使用
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
実践的な構成例
Webアプリケーション + データベース
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: my-app
# database-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: my-app
type: Opaque
stringData:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
# database-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: my-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# database-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: postgres-secret
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
# database-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: my-app
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
# app-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: my-app
data:
DATABASE_HOST: postgres
DATABASE_PORT: "5432"
NODE_ENV: production
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: my-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: app
image: my-web-app:1.0.0
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: postgres-secret
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
# app-service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: my-app
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 3000
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
namespace: my-app
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app
port:
number: 80
水平スケーリング(HPA)
HPA(HorizontalPodAutoscaler)は、負荷に応じてDeployment等のレプリカ数を自動調整します。
注意
- requests が未設定だとCPU使用率ベースのスケールが破綻しがちです(計算の前提が崩れる)
- 急なスケールは外部依存(DB、外部API)を先に詰まらせるため、伸び方/縮み方(behavior)も設計します
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: my-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
# HPAの確認
kubectl get hpa -n my-app
# 詳細表示
kubectl describe hpa web-app-hpa -n my-app
Job と CronJob
アプリ本体とは別に「バッチ」や「定期処理」をKubernetes上で回したいときに使います。
- Job: 1回きりの処理(マイグレーション、ワンショット集計など)
- CronJob: スケジュール実行(毎日バックアップなど)
注意
- 冪等性(同じ処理が2回走っても壊れない)を前提に設計する
- 実行時間が長い場合、concurrencyPolicyや履歴保持を調整する
Job(一回限りのタスク)
# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: database-migration
spec:
ttlSecondsAfterFinished: 3600
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: my-app:1.0.0
command: ["npm", "run", "migrate"]
envFrom:
- secretRef:
name: postgres-secret
CronJob(定期実行)
# cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *" # 毎日午前2時
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: backup-tool:1.0.0
command: ["/bin/sh", "-c", "./backup.sh"]
envFrom:
- secretRef:
name: backup-credentials
Helmによるパッケージ管理
HelmはKubernetesマニフェストの「テンプレート + パッケージ管理」です。
複数リソース(Deployment/Service/Ingress/ConfigMap…)を一塊として配布・更新できます。
注意
- 「values.yamlに何を寄せるか」を決めないと、環境差分がカオスになります
- アプリのバージョン(image tag)とChartのバージョン(
version/appVersion)を混同しない
Helmの基本
# Helmのインストール
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# リポジトリの追加
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# チャートの検索
helm search repo nginx
# チャートのインストール
helm install my-nginx bitnami/nginx
# リリースの一覧
helm list
# リリースの削除
helm uninstall my-nginx
Chartの作成
# 新しいチャートを作成
helm create my-app
my-app/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── hpa.yaml
│ └── _helpers.tpl
└── charts/
# Chart.yaml
apiVersion: v2
name: my-app
description: My application Helm chart
version: 1.0.0
appVersion: "1.0.0"
# values.yaml
replicaCount: 3
image:
repository: my-app
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
host: myapp.example.com
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
env:
NODE_ENV: production
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
# テンプレートのレンダリング確認
helm template my-app ./my-app
# インストール(dry-run)
helm install my-app ./my-app --dry-run
# インストール
helm install my-app ./my-app
# 値を上書きしてインストール
helm install my-app ./my-app --set replicaCount=5
# アップグレード
helm upgrade my-app ./my-app --set image.tag=2.0.0
運用のベストプラクティス
リソース制限
requests/limitsは「後で入れる」より、最初から置いた方が事故りません。
理由は、無制限だとノード全体を巻き込んで落ちたり、逆に厳しすぎるとスケジュールできず Pending で止まるからです。
最小テンプレ(まずはここから)
resources:
requests:
memory: "128Mi" # 保証されるリソース
cpu: "100m"
limits:
memory: "256Mi" # 上限
cpu: "500m"
置き方の指針
-
requests: 「平常時これくらいは必要」を置く(スケジューリングの前提) -
limits.memory: 暴走対策として置く(超えるとOOMKilled) -
limits.cpu: 置くなら“絞りすぎ注意”(超えるとスロットリングで遅くなる)
よくある事故
-
requestsが高すぎてPending(空きノードがなく配置できない) -
limits.memoryが低すぎてOOMKilled→ 再起動ループ -
limits.cpuが低くてスロットリング → レイテンシ悪化 → readiness/livenessがタイムアウトして落ちる
補足:Troubleshootingとの対応
-
Pending/OOMKilled/Evictedが出たら、まずこの節の設計を疑うと早いです
ヘルスチェック
livenessProbe: # 失敗するとコンテナ再起動
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe: # 失敗するとサービスから除外
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe: # 起動完了の確認
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30
readiness / liveness / startup の違い(ここで詰まりがち)
-
readinessProbe(準備できた?)
- 目的: 「今このPodにトラフィックを流していいか」を判定
- 失敗したら: Serviceのエンドポイントから外れる(再起動はしない)
- 例: 起動直後のウォームアップ中、DB接続不可、依存サービス未準備
-
livenessProbe(生きてる?)
- 目的: 「プロセスが詰んでいて、自力では戻らない」状態を検知
- 失敗したら: コンテナ再起動
- 例: デッドロック、メモリ破壊で応答不能、イベントループ停止
-
startupProbe(起動完了した?)
- 目的: 起動が遅いアプリで、起動中にlivenessが誤判定して再起動ループになるのを防ぐ
- ポイント: startupが成功するまで、liveness/readinessの判定を遅らせられる(扱いは設定次第)
よくある事故
- readinessを「/health」1本にしてしまい、依存DBが落ちた瞬間に全Podがreadyから外れて全滅する
- livenessに重い依存チェック(DB疎通など)を入れてしまい、外部要因で再起動ループする
Pod Disruption Budget
PDBは「計画停止(ノード入れ替えやアップグレード)でも、落ちるPod数を制限する」ための安全装置です。
使いどころ
- 本番運用でノードメンテやオートスケールがある
- 1台落ちたら即死するサービスを避けたい
注意
-
minAvailableを強くしすぎると、メンテが進まず詰まることがあります
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # 最低2つは稼働を保証
# または maxUnavailable: 1
selector:
matchLabels:
app: my-app
Network Policy
NetworkPolicyは「どのPodからどのPodへ通信を許可するか」を宣言します。
ゼロトラスト寄りに寄せたい場合や、マルチテナント環境で重要です。
注意
- NetworkPolicyはCNI依存です(対応していない環境では効きません)
- まずはNamespace単位の方針→重要通信だけ絞る、の順で段階導入すると安全です
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
トラブルシューティング
障害対応は「見る順番」を固定すると速いです。
-
events(何が起きたか) -
describe(状態と理由) -
logs(アプリの出力) -
exec(最後の手段)
まずは「Podが落ちているのか / readyじゃないのか / そもそもルーティングされてないのか」を切り分けます。
外から繋がらない時の最短チェック
- DNS/証明書: そもそも名前解決とTLSが合っているか
- Ingress: Ingress Controllerがいるか、
ingressClassNameが合っているか - Service: Endpointsが空ではないか(readinessが落ちていないか)
- NetworkPolicy: 追加した途端に遮断していないか(CNI対応も含めて)
外部公開の疎通チェック手順(これだけで7割解ける)
前提
-
<ns>は対象Namespace(例:default) -
<app>は対象アプリ名(Deployment/Service/Ingressの名前など)
- IngressがControllerに拾われているか
# Ingress一覧とアドレス(ADDRESS)が付いているか
kubectl get ingress -n <ns>
# どのclassで扱う想定か / イベントにエラーが出てないか
kubectl describe ingress <ingress-name> -n <ns>
見るポイント
-
ingressClassName(またはannotation)が想定Controllerと一致 -
Eventsに "no IngressClass" / "service not found" 的なものが出ていない
- Serviceが正しいPod集合を掴んでいるか(Endpoints)
# Serviceのselectorとportの確認
kubectl describe svc <service-name> -n <ns>
# Endpointsが空じゃないか(空なら大抵selectorかreadiness)
kubectl get endpoints <service-name> -n <ns>
kubectl describe endpoints <service-name> -n <ns>
見るポイント
-
Endpoints:が空なら、まずselectorとPod側labelsを疑う -
Endpoints:が空でないのに繋がらないなら、次はtargetPortとアプリの待受ポートを疑う
- PodがReadyか、落ちてないか(readiness/liveness)
kubectl get pods -n <ns> -o wide
kubectl describe pod <pod-name> -n <ns>
# readinessの失敗理由をログと合わせて見る
kubectl logs <pod-name> -n <ns>
見るポイント
-
READYが0/1なら、ServiceのEndpointsに入りません(=繋がらない) -
EventsにReadiness probe failed/Liveness probe failedが出ていないか
- NetworkPolicyで遮断していないか
kubectl get networkpolicy -n <ns>
kubectl describe networkpolicy <np-name> -n <ns>
見るポイント
- まず「PolicyがあるNamespaceか」を確認(あるだけでデフォルト拒否になる実装もある)
- ingress/egress の
podSelector/portsの指定が意図通りか
- それでもダメなら「アプリ自体は応答できるか」を最短で確認
IngressやServiceの外側から到達できない原因が、実は
- アプリが想定ポートでlistenしていない
- ルーティング/パスが違っていて外からは404
- アプリが内部的に依存(DB等)で詰まっていてタイムアウト
のように “中身” 側にあることも多いです。
# Service越しにローカルへ転送して叩く(Ingressを介さず切り分け)
kubectl port-forward -n <ns> svc/<service-name> 18080:80
# 例: /health を確認(PowerShellでもcurlは使えます)
curl http://127.0.0.1:18080/health
さらに一歩(Pod内から疎通確認)
# コンテナに入って、待受ポートやHTTP応答を確認
kubectl exec -n <ns> -it <pod-name> -- /bin/sh
# (コンテナ内)
# 例: ポート確認やHTTP疎通(busybox/alpine等で利用可な範囲で)
# wget -qO- http://127.0.0.1:8080/health
見るポイント
- port-forwardで健康チェックが通るなら「Ingress/LB/DNS側」を疑う
- port-forwardでもダメなら「アプリ設定/待受ポート/Probe/依存先」を疑う
Probe起因で詰まるとき(再起動ループ / readyにならない)
症状と原因の当たり
-
Podが再起動を繰り返す(
RESTARTSが増える /CrashLoopBackOff)- まず疑う:
livenessProbeが厳しすぎる(起動が遅い、タイムアウトが短い、失敗許容が少ない)
- まず疑う:
-
Podは落ちないが、ずっとReadyにならない(
READY 0/1のまま)- まず疑う:
readinessProbeが厳しすぎる / 依存先(DB等)が未準備
- まず疑う:
最初に見る場所
kubectl describe pod <pod-name> -n <ns>
kubectl logs <pod-name> -n <ns>
kubectl logs <pod-name> -n <ns> --previous
調整の基本(“どれをいじるか”)
-
initialDelaySeconds: 起動直後は見ない(まず時間を稼ぐ) -
timeoutSeconds: たまに遅いだけで落ちるのを防ぐ -
failureThreshold×periodSeconds: 何秒連続で失敗したらNGにするか
startupProbe を足す判断
- 起動が重い/ウォームアップが必要なアプリは、startupProbeで「起動完了までlivenessを待たせる」
- これがないと、起動途中をlivenessが殺して 再起動ループになりがち
読み違え注意
- readiness失敗は「トラフィックを止める」だけ(再起動しない)
- liveness失敗は「再起動」なので、外部依存(DB等)の不調チェックを入れると自爆しやすい
Podの典型ステータス別:原因の目星と最短対処
まずはこれだけ
kubectl get pods -n <ns> -o wide
kubectl describe pod <pod-name> -n <ns>
kubectl logs <pod-name> -n <ns>
kubectl get events -n <ns> --sort-by='.lastTimestamp'
| 症状/ステータス | ありがちな原因 | 最初に見る場所 | まずやる対処 |
|---|---|---|---|
Pending のまま |
リソース不足(CPU/mem)/ nodeSelector・tolerations・affinity・PVC未バインド |
kubectl describe pod の Events / kubectl get nodes / kubectl describe node
|
requests/制約を見直す、ノード増強、PVC/StorageClass確認 |
ErrImagePull / ImagePullBackOff
|
イメージ名/タグ誤り、レジストリ認証(imagePullSecret)、Pull制限 |
kubectl describe pod の Events
|
イメージ名修正、imagePullSecrets 追加、レジストリ疎通確認 |
CrashLoopBackOff |
起動直後にアプリが落ちる、設定不足、probeが厳しすぎ、依存先未準備 |
kubectl logs --previous / describe pod
|
まずログで原因特定、設定(env/secret)確認、必要ならstartupProbe導入 |
OOMKilled(再起動する) |
limits.memory が低い / メモリリーク / JVM等の設定不一致 |
kubectl describe pod の Last State / kubectl top pod
|
memory limit引き上げ、アプリのメモリ設定見直し、HPAや垂直スケール検討 |
Evicted |
ノードが逼迫(メモリ/ディスク)で追い出された |
kubectl describe pod の Status/Reason / describe node
|
ノード資源を増やす、requests/limits適正化、ログ/一時領域の削減 |
READY 0/1(落ちない) |
readinessが失敗、依存先未準備、path/port違い |
kubectl describe pod の Readiness probe failed / アプリログ |
readinessの条件を見直す、依存を切り離す、timeoutや閾値調整 |
# Podの状態確認
kubectl get pods -o wide
# Podの詳細(イベント確認)
kubectl describe pod <pod-name>
# ログの確認
kubectl logs <pod-name>
kubectl logs <pod-name> -f # フォロー
kubectl logs <pod-name> --previous # 前のコンテナ
kubectl logs <pod-name> -c <container> # 特定コンテナ
# Podに入る
kubectl exec -it <pod-name> -- /bin/sh
# リソース使用量
kubectl top pods
kubectl top nodes
# イベントの確認
kubectl get events --sort-by='.lastTimestamp'
# すべてのリソースを確認
kubectl get all -n <namespace>
まとめ
| リソース | 役割 |
|---|---|
| Pod | コンテナの実行単位 |
| Deployment | ロールアウト/ロールバック(ReplicaSetの世代管理) |
| Service | 安定した入口(ClusterIP/DNS/負荷分散・Service Discovery) |
| Ingress | 主にHTTP/HTTPSの外部公開・L7ルーティング・TLS |
| ConfigMap | 設定情報 |
| Secret | 機密情報 |
| PVC | 永続ストレージ |
| HPA | 自動スケーリング |
Kubernetesを活用して、スケーラブルで信頼性の高いコンテナ運用を実現しましょう!