シリーズ記事一覧
- 第1回:全体像と環境構築
- 第2回:Pod・ReplicaSet・Deployment
- 第3回:Service・Ingress
- 第4回:ConfigMap・Secret・Volume
- 第5回:Namespace・RBAC
- 第6回:トラブルシュートと運用
- 第7回:設計総合演習
📑 目次
- この記事について
- この記事のゴール
- 前提条件
- Docker Composeではこうだった(Before)
- k8sではこうなる(After)
- ハンズオン:壊して直す3つのシナリオ
- 実務で使うkubectlコマンド集
- つまずきポイント(体験談)
- まとめ
- 次回予告
1. この記事について
ここまで5回にわたって、k8sの主要リソースを学んできました。
Pod、Service、ConfigMap、Secret、Namespace、RBAC... 「作る」ことはできるようになりました。
でも、実務で最も時間を使うのは作ることではなく、直すことです。
🍽️ レストランを開店するのは1回。
でも毎日の営業(トラブル対応・品質管理)はずっと続く。
「コンロの火がつかない!」「食材が届かない!」「お客さんから料理が出てこないとクレーム!」— これらに対応するスキルが、実務では最も求められます。
今回は意図的にPodを壊して、調査→特定→修正の流れを体験します。
2. この記事のゴール
この記事で書いていること:
- logs / describe / exec の使い分けの整理
- 「Podが起動しない!」の原因を3パターン体験してみた話
- トラブルシュートの思考フロー
- 実務でよく使うkubectlコマンド集
3. 前提条件
| 項目 | 要件 |
|---|---|
| 前回の完了 | 第5回でNamespace/RBACを理解済み |
| クラスタ状態 | kindクラスタが起動中 |
# WSL2 Ubuntu で実行
kubectl get nodes
4. Docker Composeではこうだった(Before)
4-1. Docker Composeの調査コマンド
# ログを見る
docker-compose logs web
# コンテナに入る
docker-compose exec web bash
# コンテナの状態を見る
docker-compose ps
シンプルで分かりやすい。
コンテナが1〜3個なら十分です。
4-2. でもこの壁がある
しかし、コンテナの数が増えると以下の限界が見えてきます。
| # | 壁 | Docker Composeの現実 |
|---|---|---|
| 壁1 | 複数コンテナの横断調査が辛い | 1つずつログを見て回る |
| 壁2 | 「なぜ起動しないか」の情報が少ない |
docker-compose ps は Exit Code くらい |
| 壁3 | コンテナが消えるとログも消える | ログの永続化は自分で設定 |
5. k8sではこうなる(After)
5-1. 調査コマンド3兄弟
k8sのトラブルシュートは、主に3つのコマンドで行います。
🍽️ 比喩:本部のトラブル対応ツール
コマンド 比喩 いつ使う kubectl logs防犯カメラの映像 「何が起きたか」を時系列で確認 kubectl describe店舗の点検報告書 「設備の状態」「最近のイベント」を確認 kubectl exec店舗に直接行って確認 現場で実際に触って調査
5-2. 3兄弟の使い分け
3つのコマンドの違いを整理しましょう。
「どんな情報が欲しいか」で使い分けます。
| コマンド | 得られる情報 | 対応するDocker | 最初に見る場面 |
|---|---|---|---|
kubectl logs <pod> |
アプリの標準出力/エラー | docker logs |
アプリがエラーを吐いている |
kubectl describe <resource> |
リソースの設定・状態・イベント | (該当なし) | Podが起動しない・Pendingのまま |
kubectl exec -it <pod> -- bash |
コンテナ内のリアルタイム状態 | docker exec |
アプリは動いてるが応答がおかしい |
🔰 Note: 迷ったら describe から始める のが鉄則だと実感しました。
Eventsセクションに「なぜ」が書いてあることが多いです。
🔰 Memo: describe の出力は長いですが、全部読む必要はありません。最後の Events セクションだけ見れば、たいていの原因がわかります:
kubectl describe pod <Pod名> | grep -A 20 "Events:"
5-3. トラブルシュートの思考フロー
🍽️ これは「本部のトラブル対応マニュアル」です。
電話がかかってきたら、まず「店舗の状態は?」を聞いて、症状に応じて調査方法を変える。
6. ハンズオン:壊して直す3つのシナリオ
6-1. 作業ディレクトリ
# WSL2 Ubuntu で実行
mkdir -p ~/k8s-handson/troubleshoot
cd ~/k8s-handson/troubleshoot
6-2. シナリオ1:ImagePullBackOff — 食材が届かない
🍽️ 「特上マグロを注文したけど、そんな食材は存在しない!」→ 食材が届かず開店できない
6-2-1. 壊れたPodを作成
~/k8s-handson/troubleshoot/
└── broken-image.yaml # ここに作成
# broken-image.yaml
# ❌ 意図的に壊れたPod(存在しないイメージ名)
apiVersion: v1
kind: Pod
metadata:
name: broken-image
spec:
containers:
- name: app
image: nginx:99.99.99 # ← 存在しないバージョン!
ports:
- containerPort: 80
# ~/k8s-handson/troubleshoot/ で実行
kubectl apply -f broken-image.yaml
# 少し待ってから状態を確認
kubectl get pods
期待される出力:
NAME READY STATUS RESTARTS AGE
broken-image 0/1 ImagePullBackOff 0 30s
❌ ImagePullBackOff — イメージの取得に失敗しています。
6-2-2. 調査:kubectl describe
kubectl describe pod broken-image
注目するのは最下部の Events セクション:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 30s default-scheduler Successfully assigned default/broken-image to ...
Normal Pulling 28s kubelet Pulling image "nginx:99.99.99"
Warning Failed 25s kubelet Failed to pull image "nginx:99.99.99": ... not found
Warning Failed 25s kubelet Error: ErrImagePull
Normal BackOff 10s kubelet Back-off pulling image "nginx:99.99.99"
Warning Failed 10s kubelet Error: ImagePullBackOff
6-2-3. Eventsの読み方
| 列 | 意味 |
|---|---|
| Type | Normal(正常)/ Warning(警告) |
| Reason | イベントの種類 |
| Message | 具体的な内容 ← ここが最重要 |
🍽️ 点検報告書(describe)の「最近のイベント」欄に「食材 nginx:99.99.99 は見つかりませんでした」と書いてある。
原因は明確:イメージ名の間違い。
6-2-4. 修正
⚠️ 重要: kubectl set image は Deployment / StatefulSet 等のコントローラ配下のPod にしか使えません。
今回のように Pod単体 で作った場合は、削除して作り直すのが正しい手順です。
# ① YAMLのイメージ名を修正してから再作成
kubectl delete pod broken-image
kubectl apply -f broken-image.yaml
# ② 確認
kubectl get pods --watch
🍽️ 仕込み済みの食材(Pod)が腐っていたら、捨てて(delete)新しく仕込み直す(apply)しかない。
シフト表(Deployment)経由なら、店長が自動で差し替えてくれる(set image)。
6-2-5. 学び
| 覚えること | 内容 |
|---|---|
| 症状 |
ImagePullBackOff / ErrImagePull
|
| 調査 |
kubectl describe pod → Events |
| 原因 | イメージ名・タグの間違い、レジストリへの接続失敗 |
| 修正 | 正しいイメージ名に変更 |
6-3. シナリオ2:CrashLoopBackOff — スタッフが出勤直後に倒れる
🍽️ 「新人スタッフが出勤するたびに即倒れる。
何度送り出してもダメ」→ アプリのエラーで起動直後にクラッシュ
6-3-1. 壊れたPodを作成
~/k8s-handson/troubleshoot/
├── broken-image.yaml
└── broken-crash.yaml # ここに作成
# broken-crash.yaml
# ❌ 意図的に壊れたPod(起動直後にエラー終了)
apiVersion: v1
kind: Pod
metadata:
name: broken-crash
spec:
containers:
- name: app
image: busybox
# 存在しないコマンドを実行 → エラー終了
command: ["this-command-does-not-exist"]
# ~/k8s-handson/troubleshoot/ で実行
kubectl apply -f broken-crash.yaml
# 少し待ってから状態を確認
kubectl get pods --watch
期待される出力(時間経過とともに):
NAME READY STATUS RESTARTS AGE
broken-crash 0/1 CrashLoopBackOff 3 (20s ago) 1m
❌ CrashLoopBackOff — 起動→クラッシュ→再起動を繰り返しています。
Ctrl + C でwatchを終了。
6-3-2. 調査:kubectl logs
# ログを確認
kubectl logs broken-crash
期待される出力:
exec /bin/this-command-does-not-exist: no such file or directory
原因が一発で分かりました。
「存在しないコマンド」を実行しようとしている。
6-3-3. --previous フラグ
CrashLoopBackOff中は、コンテナが起動→クラッシュを繰り返すため、現在のログが取れないことがあります。
# 前回クラッシュ時のログを見る
kubectl logs broken-crash --previous
🍽️ 防犯カメラ(logs)を再生。
スタッフが倒れた瞬間の映像を確認 → 「存在しない道具を使おうとして倒れた」ことが判明。
6-3-4. describe でも確認
kubectl describe pod broken-crash | tail -20
Eventsセクション:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned ...
Normal Pulled 30s (x4 over 1m) kubelet Container image "busybox" already present
Normal Created 30s (x4 over 1m) kubelet Created container app
Normal Started 30s (x4 over 1m) kubelet Started container app
Warning BackOff 5s (x6 over 55s) kubelet Back-off restarting failed container
(x4 over 1m) は「1分間で4回繰り返した」という意味。
k8sが何度も再起動を試みていることがわかります。
6-3-5. 修正
# 壊れたPodを削除
kubectl delete pod broken-crash
正しいコマンドでYAMLを修正して再作成します。
6-3-6. 学び
| 覚えること | 内容 |
|---|---|
| 症状 |
CrashLoopBackOff / RESTARTS が増え続ける |
| 調査 |
kubectl logs(--previous も) |
| 原因 | アプリのエラー、コマンドミス、設定不足 |
| 修正 | アプリのコード/設定を修正 |
6-4. シナリオ3:Runningなのに応答しない — 設定ミス
🍽️ 「スタッフは出勤してるのに、お客さんの注文に応えない。
何か伝達ミスがある?」
6-4-1. ConfigMap + Pod + Serviceを作成
~/k8s-handson/troubleshoot/
├── broken-image.yaml
├── broken-crash.yaml
├── broken-config.yaml # ここに作成(ConfigMap + Deployment + Service)
# broken-config.yaml
# ❌ ConfigMapの設定ミスでヘルスチェックが失敗するシナリオ
---
# ConfigMap(nginx設定)
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-broken-config
data:
default.conf: |
server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: broken-config-app
spec:
replicas: 1
selector:
matchLabels:
app: broken-config
template:
metadata:
labels:
app: broken-config
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80 # ← ここは80を期待
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume
configMap:
name: nginx-broken-config
---
# Service
apiVersion: v1
kind: Service
metadata:
name: broken-config-svc
spec:
type: ClusterIP
selector:
app: broken-config
ports:
- port: 80 # ← Serviceは80で受ける
targetPort: 80 # ← Podの80に転送するが...
🔰 Note: ConfigMapでnginxを listen 8080 にしているのに、ServiceのtargetPortは 80 です。
ポートがずれています。
# ~/k8s-handson/troubleshoot/ で実行
kubectl apply -f broken-config.yaml
# Pod は Running
kubectl get pods
期待される出力:
NAME READY STATUS RESTARTS AGE
broken-config-app-xxxxxxxxx-xxxxx 1/1 Running 0 10s
✅ Running... 見た目は正常です。
6-4-2. 問題を発見する
# Service経由でアクセスしてみる
kubectl run curl-test \
--image=curlimages/curl \
--rm -it \
--restart=Never \
-- curl -s --max-time 5 broken-config-svc:80
期待される出力:
curl: (28) Connection timed out after 5000 milliseconds
❌ タイムアウト! Podは Running なのに応答しません。
6-4-3. 調査:kubectl exec
# Podの中に入って直接確認
kubectl exec -it deploy/broken-config-app -- bash
Pod内で調査:
# Pod の中で実行
# nginx が何番ポートでlistenしているか確認
cat /etc/nginx/conf.d/default.conf
出力:
server {
listen 8080; # ← 8080で待っている!
...
}
# 80番ポートにアクセスしてみる
curl -s localhost:80
# → 応答なし(Connection refused)
# 8080番ポートにアクセスしてみる
curl -s localhost:8080
# → Welcome to nginx! が返る
# Pod から出る
exit
🍽️ 店舗に直接行って確認(exec)したら、スタッフは「8080番窓口」で待っていた。
でも本部(Service)は「80番窓口に案内してね」と言っていた。
お客さんが80番に行っても誰もいない!
6-4-4. 修正方法
原因がわかりました。
修正方法は2つ:
| 方法 | 修正内容 |
|---|---|
| A | ConfigMapの listen 8080 を listen 80 に変更 |
| B | ServiceのtargetPortを 8080 に変更 |
今回はBで修正します:
# Serviceを修正(targetPortを8080に)
kubectl patch service broken-config-svc --type='json' \
-p='[{"op": "replace", "path": "/spec/ports/0/targetPort", "value": 8080}]'
# 再度アクセステスト
kubectl run curl-test2 \
--image=curlimages/curl \
--rm -it \
--restart=Never \
-- curl -s broken-config-svc:80
期待される出力:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
修正完了。
Service経由でnginxにアクセスできるようになりました。
6-4-5. 学び
| 覚えること | 内容 |
|---|---|
| 症状 | Pod は Running だがアプリが応答しない |
| 調査 |
kubectl exec でコンテナ内に入って直接確認 |
| 原因 | ポート設定のミスマッチ、設定ファイルの誤り |
| 修正 | ConfigMap または Service の設定を修正 |
6-5. シナリオのまとめ
| シナリオ | STATUS | 最初に使うコマンド | 比喩 |
|---|---|---|---|
| 1. ImagePullBackOff | ImagePullBackOff |
describe → Events |
食材が届かない |
| 2. CrashLoopBackOff | CrashLoopBackOff |
logs(+ --previous) |
スタッフが即倒れる |
| 3. 応答しない | Running |
exec で中に入る |
伝達ミス |
6-6. 後片付け
# 全てのリソースを削除
kubectl delete pod broken-image --ignore-not-found
kubectl delete pod broken-crash --ignore-not-found
kubectl delete -f broken-config.yaml --ignore-not-found
kubectl get all
7. 実務で使うkubectlコマンド集
7-1. 基本操作
| コマンド | 用途 | 頻度 |
|---|---|---|
kubectl get pods |
Pod一覧 | ⭐⭐⭐ |
kubectl get pods -o wide |
Pod一覧(IP・ノード情報付き) | ⭐⭐⭐ |
kubectl get all |
全リソース一覧 | ⭐⭐ |
kubectl get pods --all-namespaces |
全NS横断 | ⭐⭐ |
7-2. 調査系
| コマンド | 用途 | 頻度 |
|---|---|---|
kubectl describe pod <name> |
詳細情報 + Events | ⭐⭐⭐ |
kubectl logs <pod> |
ログ確認 | ⭐⭐⭐ |
kubectl logs <pod> --previous |
前回クラッシュ時のログ | ⭐⭐ |
kubectl logs <pod> -f |
ログをリアルタイム追跡 | ⭐⭐ |
kubectl logs <pod> -c <container> |
特定コンテナのログ | ⭐⭐ |
kubectl exec -it <pod> -- bash |
コンテナに入る | ⭐⭐⭐ |
kubectl exec -it <pod> -- sh |
bashがない場合 | ⭐⭐ |
7-3. リソース管理
| コマンド | 用途 | 頻度 |
|---|---|---|
kubectl apply -f <file> |
作成・更新(宣言的) | ⭐⭐⭐ |
kubectl delete -f <file> |
ファイルで指定して削除 | ⭐⭐ |
kubectl scale deployment <name> --replicas=N |
スケール変更 | ⭐⭐ |
kubectl rollout status deployment <name> |
デプロイ状況確認 | ⭐⭐ |
kubectl rollout undo deployment <name> |
ロールバック | ⭐⭐ |
7-4. 情報取得
| コマンド | 用途 | 頻度 |
|---|---|---|
kubectl get events --sort-by='.lastTimestamp' |
イベントを時系列で | ⭐⭐ |
kubectl top pods |
CPU/メモリ使用量 | ⭐⭐ |
kubectl top nodes |
ノードのリソース状況 | ⭐⭐ |
kubectl auth can-i <verb> <resource> |
権限確認 | ⭐ |
7-5. 便利なショートカット
| 省略形 | 正式名 |
|---|---|
po |
pods |
svc |
services |
deploy |
deployments |
cm |
configmaps |
ns |
namespaces |
sa |
serviceaccounts |
# これらは同じ
kubectl get pods
kubectl get po
kubectl get services
kubectl get svc
kubectl get deployments
kubectl get deploy
8. つまずきポイント(体験談)
8-1. logsが空っぽ — コンテナが起動する前に失敗している
症状: kubectl logs <pod> が何も返さない。
原因: コンテナが起動すらしていない(ImagePullBackOff等)。
ログはコンテナが起動してから出力される。
対策: kubectl describe pod <pod> のEventsを見る。
起動前の情報はここにある。
8-2. exec できない — CrashLoop中のコンテナ
症状:
error: unable to upgrade connection: container not found ("app")
原因: コンテナがクラッシュ→再起動の合間で、接続先がない。
対策:
# デバッグ用コンテナで入る(k8s 1.25+)
kubectl debug -it broken-crash \
--image=busybox \
--target=app
8-3. describe の出力が長すぎて迷子になる
対策: Events セクションだけを見る。
# Events だけ抽出
kubectl describe pod <pod> | grep -A 20 "Events:"
🍽️ 点検報告書(describe)は全ページ読む必要はありません。
最後の「最近のイベント」欄だけ見れば、たいていの原因がわかります。
9. まとめ
9-1. この記事でやったこと
| # | 内容 | 状態 |
|---|---|---|
| 1 | 調査コマンド3兄弟(logs / describe / exec)を使い分けた | ✅ |
| 2 | ImagePullBackOff を調査・修正した | ✅ |
| 3 | CrashLoopBackOff を調査・修正した | ✅ |
| 4 | Running だが応答しない問題を調査・修正した | ✅ |
| 5 | 実務で使うkubectlコマンド集を確認した | ✅ |
9-2. トラブルシュート早見表
| STATUS | 最初に見る | よくある原因 |
|---|---|---|
Pending |
describe → Events |
リソース不足、ノード不足 |
ImagePullBackOff |
describe → Events |
イメージ名/タグの間違い |
CrashLoopBackOff |
logs + --previous
|
アプリのエラー、コマンドミス |
Running だが異常 |
exec で中に入る |
設定ミス、ポートずれ |
Terminating が長い |
describe → Events |
Graceful shutdown のタイムアウト |
9-3. Docker Compose → k8s 対応表
| Docker Compose | Kubernetes | 備考 |
|---|---|---|
docker-compose logs |
kubectl logs |
k8sは --previous -f が便利 |
docker-compose exec |
kubectl exec |
同じ感覚で使える |
docker-compose ps |
kubectl get pods + kubectl describe
|
k8sは情報量が圧倒的に多い |
| (該当なし) | kubectl get events |
クラスタ全体のイベント一覧 |
10. 次回予告
第7回:設計総合演習 — 「このアプリをk8sで設計して」に答える
6回にわたって学んだ知識を総動員する最終回です。
🍽️ これまで調理技術(各リソース)を個別に学んできました。
最終回は「コース料理を設計してフルで提供する」総合演習です。
次回は:
- 「Webアプリをk8sで動かして」という要件に対して、設計から構成図まで一気通貫
- 第1〜6回の知識を組み合わせた実践的な設計