Kubernetes トラブルシューティング実践:診断から修復まで
Kubernetes トラブルシューティング方法論
Kubernetes クラスタのトラブルシューティングは「当てずっぽう」ではなく、階層的に進む体系的な診断アプローチです。アプリケーション層からインフラ層まで段階的に調査することで、根本原因を効率的に特定できます。
階層別診断モデル
| 階層 | 範囲 | 主要コマンド |
|---|---|---|
| L1 アプリケーション層 | Pod ステータス、コンテナログ | kubectl logs, kubectl describe pod |
| L2 サービス層 | Service、Endpoint、DNS | kubectl get endpoints, nslookup |
| L3 スケジューリング層 | Deployment、ReplicaSet、HPA | kubectl rollout status, kubectl get hpa |
| L4 ストレージ層 | PVC、PV、StorageClass | kubectl describe pvc, kubectl get pv |
| L5 ノード層 | Node ステータス、リソース圧迫 | kubectl describe node, kubectl top nodes |
| L6 インフラ層 | ネットワークプラグイン、API Server、etcd | systemctl status kubelet, crictl ps |
診断のゴールデンワークフロー
# ステップ1:クラスタ全体の健全性を確認
kubectl get nodes
kubectl get componentstatuses
# ステップ2:問題のあるネームスペースを特定
kubectl get all -n <namespace>
# ステップ3:異常リソースに焦点を当てる
kubectl describe <resource> <name> -n <namespace>
# ステップ4:イベントとログを確認
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
kubectl logs <pod-name> -n <namespace> --previous
# ステップ5:コンテナに入って調査
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
Pod トラブルシューティング
Pod は Kubernetes の最小スケジューリング単位であり、最も障害が集中するリソースです。各異常状態の診断方法を習得することが重要です。
CrashLoopBackOff
コンテナが起動後すぐに終了し、Kubelet が繰り返し再起動してクラッシュループが発生します。
# 現在のログを確認
kubectl logs <pod-name> -n <namespace>
# 前回クラッシュ時のログを確認(重要!)
kubectl logs <pod-name> -n <namespace> --previous
# コンテナの終了コードを確認
kubectl describe pod <pod-name> -n <namespace> | grep -A5 "Last State"
一般的な原因と修正:
| 終了コード | 意味 | 修正方法 |
|---|---|---|
| 0 | 正常終了だが長期実行モード未設定 | エントリコマンドを確認、フォアグラウンド実行を確保 |
| 1 | アプリケーションエラー(未キャッチ例外) | アプリログを確認、コードロジックを修正 |
| 137 | OOMKilled(SIGKILL 受信) | resources.limits.memory を増加 |
| 139 | Segmentation Fault | 依存ライブラリの互換性を確認 |
| 143 | SIGTERM グレースフル終了 | preStop フックまたは終了シグナル処理を確認 |
# 修正例:スタートアッププローブを追加してスロー起動時の強制終了を防止
spec:
containers:
- name: app
image: my-app:v1
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15
failureThreshold: 3
ImagePullBackOff
イメージのプルに失敗します。通常、イメージアドレス、認証、またはネットワークの問題が原因です。
# 詳細なエラー情報を確認
kubectl describe pod <pod-name> | grep -A10 "Events"
# 一般的なエラーメッセージ
# ErrImagePull: registry.k8s.io/pause:3.9 - 接続タイムアウト
# ImagePullBackOff: unauthorized: authentication required
トラブルシューティングチェックリスト:
- イメージアドレスの誤り:image フィールドのスペルを確認、タグの存在を確認
- プライベートレジストリ認証:imagePullSecrets が設定されていることを確認
- ネットワーク到達不能:ノードがイメージレジストリにアクセスできない(ファイアウォール/プロキシ)
- イメージが存在しない:タグのスペルミスまたはイメージがプッシュされていない
# 修正:プライベートレジストリ認証を設定
spec:
imagePullSecrets:
- name: registry-credentials
containers:
- name: app
image: harbor.company.com/project/app:v1.2.3
# imagePullSecret を作成
kubectl create secret docker-registry registry-credentials \
--docker-server=harbor.company.com \
--docker-username=admin \
--docker-password=Harbor12345 \
-n <namespace>
OOMKilled
コンテナのメモリが制限を超え、カーネルの OOM Killer によって強制終了されます。
# OOMKilled を確認
kubectl describe pod <pod-name> | grep -A5 "Last State"
# Last State: Terminated Reason: OOMKilled Exit Code: 137
# コンテナのメモリ使用傾向を確認
kubectl top pod <pod-name> -n <namespace>
# ノードの cgroup ログを確認
dmesg | grep -i oom
修正戦略:
# 戦略1:メモリ制限を増加
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi" # 512Mi から 1Gi に増加
cpu: "500m"
# 戦略2:アプリケーションのメモリ使用を最適化(JVM の例)
env:
- name: JAVA_OPTS
value: "-Xms256m -Xmx512m -XX:+UseG1GC"
# 戦略3:QoS を Guaranteed に設定(requests == limits)
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "500m"
Pod Pending
Pod がどのノードにもスケジュールできません。通常、リソース不足または制約が満たされていないことが原因です。
# スケジューリング失敗の理由を確認
kubectl describe pod <pod-name> | grep -A20 "Events"
# 一般的なメッセージ:
# 0/3 nodes are available: 3 Insufficient cpu.
# 0/3 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
一般的な原因と解決策:
| 原因 | イベントメッセージ | 解決策 |
|---|---|---|
| CPU 不足 | Insufficient cpu | requests を下げるかノードを追加 |
| メモリ不足 | Insufficient memory | requests を下げるかノードを追加 |
| PVC 未バインド | pod has unbound immediate PersistentVolumeClaims | PVC ステータスを先に調査 |
| ノードセレクタ不一致 | node(s) didn't match node selector | nodeSelector / nodeAffinity を確認 |
| ティント非許容 | node(s) had taints that the pod didn't tolerate | tolerations を追加 |
| PodDisruptionBudget | Cannot evict pod | PDB 設定を確認 |
Service とネットワークのトラブルシューティング
DNS 解決失敗
# DNS デバッグ Pod をデプロイ
kubectl run dnsutils --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \
--restart=Never -- sleep infinity
# クラスタ内 DNS 解決をテスト
kubectl exec -it dnsutils -- nslookup kubernetes.default.svc.cluster.local
kubectl exec -it dnsutils -- nslookup <service-name>.<namespace>.svc.cluster.local
# CoreDNS ステータスを確認
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
CoreDNS の一般的な問題:
- CoreDNS Pod が実行されていない:kube-system の coredns ステータスを確認
- ConfigMap 設定ミス:
corednsConfigMap のプラグイン設定を確認 - ndots によるタイムアウト:
/etc/resolv.confの ndots=5 が複数クエリを引き起こす
# Pod DNS 設定を最適化
spec:
dnsConfig:
options:
- name: ndots
value: "2"
- name: single-request-reopen
- name: timeout
value: "3"
dnsPolicy: ClusterFirst
Endpoint が空
Service の Endpoint リストが空で、トラフィックがどの Pod にもルーティングされません。
# Endpoint を確認
kubectl get endpoints <service-name> -n <namespace>
# 一般的な原因の調査
# 1. ラベルセレクタの不一致
kubectl get pods -n <namespace> --show-labels
kubectl get svc <service-name> -n <namespace> -o yaml | grep -A5 selector
# 2. Pod が Ready でない
kubectl get pods -n <namespace> # READY 列が 1/1 か確認
# 3. ポートの不一致
kubectl describe svc <service-name> -n <namespace>
# Service セレクタが Pod ラベルと一致することを確認
# Service
spec:
selector:
app: my-app # Pod ラベルと一致する必要がある
ports:
- port: 80
targetPort: 8080 # コンテナポートと一致する必要がある
# Pod
metadata:
labels:
app: my-app # Service セレクタに対応
spec:
containers:
- name: app
ports:
- containerPort: 8080
接続拒否
# デバッグ Pod から接続性をテスト
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
# デバッグ Pod 内で
wget -qO- http://<service-name>.<namespace>:<port>/healthz
curl -v telnet://<service-name>.<namespace>:<port>
# NetworkPolicy がブロックしていないか確認
kubectl get networkpolicy -n <namespace>
# kube-proxy モードと iptables/ipvs ルールを確認
kubectl logs -n kube-system -l k8s-app=kube-proxy
iptables-save | grep <service-cluster-ip>
Deployment トラブルシューティング
ロールバック操作
# rollout 履歴を確認
kubectl rollout history deployment/<name> -n <namespace>
# 前バージョンにロールバック
kubectl rollout undo deployment/<name> -n <namespace>
# 特定リビジョンにロールバック
kubectl rollout undo deployment/<name> -n <namespace> --to-revision=2
# rollout の一時停止と再開(カナリアデプロイ用)
kubectl rollout pause deployment/<name> -n <namespace>
kubectl set image deployment/<name> app=my-app:v2 -n <namespace>
kubectl rollout resume deployment/<name> -n <namespace>
スケーリング失敗
# 手動スケール
kubectl scale deployment/<name> --replicas=5 -n <namespace>
# HPA ステータスを確認
kubectl get hpa -n <namespace>
kubectl describe hpa <hpa-name> -n <namespace>
# HPA の一般的な失敗原因
# 1. metrics-server がデプロイされていない
kubectl get pods -n kube-system -l k8s-app=metrics-server
# 2. リソース requests が未設定(HPA が利用率を計算できない)
kubectl describe pod <pod-name> | grep -A5 "Requests"
# 3. スケーリングが最大レプリカ数制限に到達
kubectl get hpa <hpa-name> -o yaml | grep maxReplicas
# 正しい HPA 設定
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-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
PVC/PV ストレージトラブルシューティング
PVC Pending
# PVC ステータスとイベントを確認
kubectl describe pvc <pvc-name> -n <namespace>
# 一般的な原因
# 1. 一致する PV がない(静的プロビジョニング)
kubectl get pv | grep <storage-class>
# 2. StorageClass が存在しない(動的プロビジョニング)
kubectl get storageclass
# 3. 動的プロビジョナーが実行されていない
kubectl get pods -n <namespace> | grep provisioner
PV Lost
# PV ステータスを確認
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
# pv-data 50Gi RWO Delete Lost default/pvc-data
# 修正:バックエンドストレージが復旧したか確認
# ストレージが復旧した場合、Lost ステータスを手動でクリア
kubectl patch pv <pv-name> -p '{"spec":{"claimRef": null}}'
# PVC を再バインド
AccessMode の不一致
# PVC が ReadWriteMany を要求しているが PV は ReadWriteOnce のみサポート
# PVC
spec:
accessModes:
- ReadWriteMany # RWX が必要
resources:
requests:
storage: 10Gi
storageClassName: nfs
# StorageClass がサポートする accessMode を確認
kubectl describe storageclass nfs
| AccessMode | 略語 | 説明 | 代表的なバックエンド |
|---|---|---|---|
| ReadWriteOnce | RWO | 単一ノード読み書き | AWS EBS, Ceph RBD |
| ReadOnlyMany | ROX | 複数ノード読み取り専用 | NFS |
| ReadWriteMany | RWX | 複数ノード読み書き | NFS, CephFS |
| ReadWriteOncePod | RWOP | 単一 Pod 排他読み書き | CSI サポート |
Node トラブルシューティング
NotReady ステータス
# ノードステータスを確認
kubectl get nodes
kubectl describe node <node-name>
# kubelet サービスを確認
systemctl status kubelet
journalctl -u kubelet -n 100 --no-pager
# コンテナランタイムを確認
crictl ps
systemctl status containerd
# 一般的な原因
# 1. kubelet 証明書の期限切れ
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates
# 2. ディスク容量不足
df -h /var/lib/kubelet
# 3. ネットワークプラグインの異常
kubectl get pods -n kube-system -o wide | grep <node-name>
リソース圧迫条件
# ノード条件を確認
kubectl describe node <node-name> | grep -A20 "Conditions"
# DiskPressure:ノードのディスク使用率が閾値を超過
# MemoryPressure:ノードの利用可能メモリが不足
# PIDPressure:プロセス数が多すぎる
# kubelet 駆除閾値を調整(本番では下げることを推奨しない)
kind: KubeletConfiguration
evictionHard:
memory.available: "100Mi"
nodefs.available: "10%"
imagefs.available: "15%"
evictionSoft:
memory.available: "200Mi"
nodefs.available: "20%"
evictionSoftGracePeriod:
memory.available: "1m30s"
nodefs.available: "2m"
kubectl デバッグコマンド早見表
リソース確認
| コマンド | 用途 |
|---|---|
kubectl get all -n <ns> |
ネームスペースの全リソースを確認 |
kubectl get pods -o wide |
ノード情報付きで Pod を確認 |
kubectl get pods --show-labels |
Pod ラベルを確認 |
kubectl top pods --sort-by=memory |
メモリ順でソート |
kubectl api-resources |
全 API リソースタイプを確認 |
デバッグ・トラブルシューティング
| コマンド | 用途 |
|---|---|
kubectl logs <pod> -c <container> --previous |
前回のコンテナログ |
kubectl logs <pod> --since=1h |
過去1時間のログ |
kubectl logs <pod> -f --tail=100 |
直近100行をリアルタイム追跡 |
kubectl exec -it <pod> -- /bin/sh |
コンテナターミナルに入る |
kubectl debug <pod> -it --image=busybox |
一時デバッグコンテナ |
kubectl port-forward svc/<svc> 8080:80 |
ローカルデバッグ用ポートフォワード |
クラスタ管理
| コマンド | 用途 |
|---|---|
kubectl cordon <node> |
ノードをスケジュール不可にマーク |
kubectl drain <node> --ignore-daemonsets |
ノードから全 Pod を駆除 |
kubectl uncordon <node> |
ノードのスケジューリングを復元 |
kubectl taint nodes <node> key=value:NoSchedule |
ティントを追加 |
kubectl label node <node> key=value |
ラベルを追加 |
ログとイベント分析
集中ログ収集
# マルチコンテナ Pod のログを集約
kubectl logs <pod-name> --all-containers=true
# ラベルセレクタで一括確認
kubectl logs -l app=my-app -n <namespace> --since=5m
# ログをファイルにエクスポートして分析
kubectl logs <pod-name> -n <namespace> > pod-debug.log
イベント分析
# ネームスペースの全イベントを確認(時系列ソート)
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
# タイプでフィルタ
kubectl get events -n <namespace> --field-selector type=Warning
# イベントを継続監視
kubectl get events -n <namespace> --watch
# 特定リソースのイベントを確認
kubectl events --for pod/<pod-name> -n <namespace>
ログレベルと形式
# API Server 監査ログ
cat /var/log/kubernetes/audit.log | jq '. | select(.responseStatus.code >= 400)'
# kubelet ログ
journalctl -u kubelet -f
# etcd ログ
kubectl logs -n kube-system etcd-<node-name> --since=10m
リソース制限とリクエストのベストプラクティス
QoS クラス
| QoS クラス | 条件 | 駆除優先度 | ユースケース |
|---|---|---|---|
| Guaranteed | requests == limits(CPU+メモリ) | 最低(最後に強制終了) | コアサービス |
| Burstable | requests < limits | 中 | 一般サービス |
| BestEffort | requests/limits 未設定 | 最高(最初に強制終了) | バッチジョブ |
# 本番環境推奨設定テンプレート
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# LimitRange でネームスペースのデフォルト値を設定
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
一般的なリソース問題
# ノードのリソース使用状況を確認
kubectl top nodes
kubectl describe node <node-name> | grep -A10 "Allocated resources"
# ネームスペースのリソースクォータを確認
kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
NetworkPolicy デバッグ
ポリシーブロックの調査
# ネームスペースの全 NetworkPolicy を確認
kubectl get networkpolicy -n <namespace>
# ポリシーの詳細を確認
kubectl describe networkpolicy <policy-name> -n <namespace>
# 一般的な NetworkPolicy 設定ミス
# エラー:DNS トラフィックの許可を忘れている
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
ネットワーク接続性テスト
# ネットワークデバッグツールをデプロイ
kubectl run netshoot --image=nicolaka/netshoot --rm -it --restart=Never
# デバッグ Pod 内でテスト
# 同一ネームスペース
curl http://<service-name>:<port>/healthz
# クロスネームスペース
curl http://<service-name>.<namespace>.svc.cluster.local:<port>/healthz
# 外部接続性をテスト
curl -I https://external-api.example.com
Helm Chart の一般的な問題
Release インストール失敗
# Release ステータスを確認
helm status <release-name> -n <namespace>
helm history <release-name> -n <namespace>
# レンダリング後の YAML を確認(インストールせずに)
helm template <chart> --debug
# 失敗した Release の値を確認
helm get values <release-name> -n <namespace>
# Helm Release をロールバック
helm rollback <release-name> <revision> -n <namespace>
一般的な Helm エラー
| エラー | 原因 | 修正 |
|---|---|---|
rendered manifests contain a resource that already exists |
リソースの競合 | --replace を使用または旧リソースをクリーンアップ |
Release "xxx" has not been installed yet |
Release が存在しない | namespace と release name を確認 |
timed out waiting for the condition |
Pod が Ready でない | --timeout 値と Pod ステータスを確認 |
YAML parse error |
values.yaml の構文エラー | /encode/yaml で検証 |
# 失敗した Release をクリーンアップ
helm uninstall <release-name> -n <namespace>
# 強制インストール(開発環境)
helm install <release-name> <chart> --replace -n <namespace>
Prometheus 監視アラート
主要アラートルール
groups:
- name: kubernetes-alerts
rules:
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
- alert: NodeNotReady
expr: kube_node_status_condition{condition="Ready",status="unknown"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Node {{ $labels.node }} is not ready"
- alert: PVCAlmostFull
expr: kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} is >85% full"
- alert: DeploymentReplicasMismatch
expr: kube_deployment_spec_replicas != kube_deployment_status_available_replicas
for: 10m
labels:
severity: warning
一般的な PromQL クエリ
# Pod メモリ使用率
container_memory_working_set_bytes / container_spec_memory_limit_bytes * 100
# ノード CPU 使用率
rate(node_cpu_seconds_total{mode!="idle"}[5m]) * 100
# リクエストエラー率
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100
# PVC 使用率
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes
緊急対応プレイブック
P0:クラスタ利用不可
# 1. API Server を確認
kubectl get componentstatuses
curl -k https://<api-server>:6443/healthz
# 2. etcd を確認
kubectl logs -n kube-system etcd-<node> --tail=50
ETCDCTL_API=3 etcdctl endpoint health --cluster
# 3. kubelet を確認
systemctl status kubelet
journalctl -u kubelet --since "10 minutes ago"
# 4. 緊急復旧:コントロールプレーンコンポーネントを再起動
systemctl restart kubelet
systemctl restart containerd
P1:サービス全面利用不可
# 1. クイックロールバック
kubectl rollout undo deployment/<name> -n <namespace>
# 2. 緊急スケールアップ
kubectl scale deployment/<name> --replicas=10 -n <namespace>
# 3. ノードの緊急メンテナンス
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
P2:ストレージ障害
# 1. PV ステータスを確認
kubectl get pv | grep -v Bound
# 2. CSI Driver を確認
kubectl get pods -n <csi-namespace>
# 3. 緊急処理:スタックした Pod を強制削除
kubectl delete pod <pod-name> --force --grace-period=0 -n <namespace>
FAQ
Q: Pod が ContainerCreating ステータスのまま怎么办?
A: 通常、イメージプルが遅いか StorageClass のマウント失敗が原因です。kubectl describe pod で Events を確認し、イメージプルとボリュームマウントのイベントに焦点を当てます。
Q: 削除された Pod のログを確認するには?
A: ログ集約(EFK/PLG)が設定されていれば、ログプラットフォームから検索できます。それ以外の場合、削除された Pod のログは復元できません。本番環境では必ずログ収集ソリューションをデプロイしてください。
Q: kubectl コマンドがタイムアウトする場合の調査方法は?
A: 通常、API Server の過負荷またはネットワーク問題が原因です。~/.kube/config のサーバーアドレスを確認し、kubectl get --request-timeout=5s nodes で接続性をテストします。
Q: どのノードのリソースが不足しているかを素早く特定するには?
A: kubectl top nodes でリアルタイムリソースを確認するか、kubectl describe node | grep -A5 "Allocated resources" で割り当て量を確認します。
Q: Service ClusterIP にアクセスできない場合の調査方法は?
A: 以下の順序で確認:① kube-proxy が正常に実行されているか ② iptables/ipvs ルールが正しいか ③ Endpoint が空でないか ④ NetworkPolicy がブロックしていないか ⑤ CNI プラグインのステータス。
Q: Pod が予期せず駆除されるのを防ぐには?
A: PodDisruptionBudget を設定し、重要サービスの最小利用可能レプリカ数を保証します。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-app
Q: Helm upgrade が失敗し Release ステータスがスタックした場合の対処法は?
A: helm rollback でロールバックするか、helm history で過去のバージョンを確認します。Release がロックされている場合は、helm rollback <release> <last-good-revision> で復旧します。
ブラウザローカルツールを無料で試す →