コンテナと Kubernetes
読了目安 55 分
- コンテナと VM の違いを説明できる
- Pod が起動しない原因を kubectl で特定できる
- Service に繋がらない時の調べ方を知っている
書いたコードは、最終的にどこかで動きます。その「どこか」が Kubernetes です。
新人が Kubernetes に求められるのは、クラスタを構築することではありません。 自分のサービスが動かない時に、原因を特定できることです。 この章はそこに絞ります。
コンテナとは何か
仮想マシンとの違い
仮想マシン コンテナ
┌──────────┬──────────┐ ┌──────────┬──────────┐
│ アプリ │ アプリ │ │ アプリ │ アプリ │
│ ライブラリ│ ライブラリ│ │ ライブラリ │ ライブラリ │
│ ゲストOS │ ゲストOS │ ← 重い ├──────────┴──────────┤
├──────────┴──────────┤ │ コンテナランタイム │
│ ハイパーバイザ │ ├─────────────────────┤
├─────────────────────┤ │ ホストOS │
│ ホストOS │ └─────────────────────┘
└─────────────────────┘
コンテナはOS を丸ごと持ちません。ホストのカーネルを共有し、 プロセスから見える世界だけを分離します。だから軽く、起動が速いのです。
第1章で触れた話に戻ります。あなたが macOS で開発していても、 コンテナの中は Linuxです。
そのため次の差が出ます。
- シェルスクリプトの挙動(BSD と GNU)
- ファイルパスの大文字小文字の扱い(macOS は区別しないことが多い)
- ネイティブモジュールのビルド対象
「ローカルで動くのにコンテナで動かない」の大半がこれです。
イメージとレイヤー
Dockerfile の各命令が1つのレイヤーになり、積み重なってイメージができます。
FROM node:22-alpine # レイヤー1
WORKDIR /app
COPY package.json pnpm-lock.yaml ./ # レイヤー2
RUN pnpm install --frozen-lockfile # レイヤー3
COPY . . # レイヤー4
RUN pnpm build # レイヤー5
CMD ["node", "dist/main.js"]変わっていないレイヤーはキャッシュが使われます。 だから「変わりにくいものを先に、変わりやすいものを後に」書きます。
上の例で COPY . . を先頭に持ってくると、
コードを1文字直すたびに pnpm install からやり直しになります。
node:22 は約1GB、node:22-alpine は約150MB です。
イメージが小さいと、デプロイが速くなり、 攻撃対象になりうるパッケージも減ります。
本番用にはマルチステージビルドで、ビルド用と実行用を分けるのが定石です。
なぜオーケストレータが必要か
コンテナを1つ動かすだけなら docker run で足ります。
しかし実際には、次のことが必要になります。
- 落ちたら自動で再起動する
- 負荷に応じて数を増やす
- どのマシンに配置するか決める
- 新バージョンに順次入れ替える(無停止で)
- アクセス先を1つの窓口にまとめる
これを自動でやるのが Kubernetes です。
宣言的であること
Kubernetes の考え方の核心です。
命令的: 「コンテナを3つ起動しろ」
宣言的: 「常に3つ動いている状態にしろ」 ← Kubernetes はこちら
1つ落ちたら、Kubernetes が勝手に1つ起動して3つに戻します。 あなたは「あるべき状態」を書くだけで、そこへ近づけるのは Kubernetes の仕事です。
主要なリソース
| リソース | 役割 |
|---|---|
| Pod | コンテナを動かす最小単位。通常1 Pod に1コンテナ |
| Deployment | 「Pod を何個動かすか」を宣言する。更新も担当 |
| Service | Pod への安定したアクセス口。ラベルで対象を選ぶ |
| Ingress | 外部からの HTTP を Service に振り分ける |
| ConfigMap | 設定値(非機密) |
| Secret | 認証情報など(機密) |
関係
Ingress(外からの入口)
↓
Service(安定した窓口。Pod が入れ替わっても変わらない)
↓ セレクタでラベルが一致する Pod を選ぶ
Pod Pod Pod
↑ Deployment が「3つ」を維持している
Pod は使い捨てです。落ちれば新しいものが作られ、IP も変わります。 だから直接 Pod を指定せず、Service を通します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api # ← このラベルの Pod を管理する
template:
metadata:
labels:
app: api # ← Pod に付けるラベル
spec:
containers:
- name: api
image: asia-northeast1-docker.pkg.dev/proj/api:v1.2.3
ports:
- containerPort: 8080Probe — 生きているかの確認
Kubernetes は、Pod が正常かどうかを自分で判断できません。 アプリ側が答える必要があります。
| Probe | 質問 | 失敗すると |
|---|---|---|
| readinessProbe | 「今リクエストを受けられる?」 | Service から外される(再起動はしない) |
| livenessProbe | 「生きてる?」 | コンテナが再起動される |
| startupProbe | 「起動終わった?」 | 起動が遅いアプリ用 |
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10「DB に繋がるか」を livenessProbe に入れると、 DB が一時的に不調になった時に、全 Pod が一斉に再起動します。
アプリは生きているのに再起動を繰り返し、障害が拡大します。
- liveness — 自分自身が壊れているか(デッドロックなど)だけを見る
- readiness — 依存先を含めて、今リクエストを処理できるかを見る
障害の読み方
この節が実務で最も使います。
kubectl get pods # まず全体を見る
kubectl describe pod <pod-name> # イベントと状態の詳細
kubectl logs <pod-name> # アプリのログ
kubectl logs <pod-name> --previous # 再起動前のログ(クラッシュの原因)よくある症状と原因
| 状態 | 意味 | 見るところ |
|---|---|---|
ImagePullBackOff | イメージを取得できない | タグの typo、レジストリの権限 |
CrashLoopBackOff | 起動しては落ちるの繰り返し | logs --previous |
OOMKilled | メモリ上限を超えて強制終了 | resources.limits.memory |
Pending | どのノードにも置けない | リソース不足、describe のイベント |
Running だが繋がらない | Service のセレクタ不一致など | kubectl get endpoints |
診断の順番
# 1. 状態を見る
kubectl get pods
# api-7d9f8-x2k4l 0/1 CrashLoopBackOff 5 3m
# 2. イベントを見る(describe の一番下)
kubectl describe pod api-7d9f8-x2k4l
# 3. 落ちる前のログを見る(ここに答えがあることが多い)
kubectl logs api-7d9f8-x2k4l --previousCrashLoopBackOff の Pod に kubectl logs を実行しても、
再起動直後の新しいログしか出ません。
落ちた理由は前のコンテナのログにあります。
--previous(-p)を付けてください。これを知っているかどうかで
調査時間が10分と1時間くらい変わります。
Service に繋がらない — Endpoints が空
最も気づきにくい障害です。
kubectl get endpoints api
# NAME ENDPOINTS AGE
# api <none> 5m ← 空!Service はラベルのセレクタで Pod を選びます。 これが一致していないと、Pod は元気に動いているのにどこにも繋がりません。
# Service
spec:
selector:
app: api-server # ← こちらは api-server
# Deployment の Pod
metadata:
labels:
app: api # ← こちらは api。一致していないエラーもログも出ません。Endpoints が空かどうかを見るのが唯一の手がかりです。
Pod が CrashLoopBackOff になっています。原因を調べる最初の一手は?
リソース制限
resources:
requests: # 最低これだけ確保してほしい(配置の判断に使う)
cpu: 100m # 0.1 コア
memory: 128Mi
limits: # これを超えたら制限する
cpu: 500m
memory: 512Mi| 超えるとどうなるか | |
|---|---|
| CPU の limit | 遅くなる(スロットリング)。落ちはしない |
| memory の limit | 強制終了(OOMKilled)。落ちる |
requests は「配置を決めるための申告」です。書かないと、
Kubernetes は「リソースを使わない」と見なして詰め込みます。
結果、1つのノードに Pod が集まりすぎてメモリ不足になり、 巻き込まれて他の Pod まで落ちます。
新人が触ることは少ない設定ですが、 「OOMKilled が出たら limit を疑う」 ことだけ覚えておいてください。
ConfigMap と Secret
設定をイメージから切り離すための仕組みです。
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef: { name: api-config, key: log_level }
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: api-secret, key: db_password }Kubernetes の Secret は、Base64 エンコードされているだけです。 暗号化ではありません。
kubectl get secret api-secret -o jsonpath='{.data.db_password}' | base64 -dこれで誰でも読めます。本番では Secret Manager などの 外部の仕組みと連携させるのが一般的です。
少なくとも、Secret の YAML を Git にコミットしてはいけません。
デプロイの流れ
1. コードを push
2. CI がイメージをビルドしてレジストリへ push
3. マニフェストの image タグを新しいものに更新
4. kubectl apply(または Argo CD などが自動で適用)
5. Kubernetes が Pod を順次入れ替える(ローリングアップデート)
6. readinessProbe が OK になった Pod から Service に入る
問題があれば戻します。
kubectl rollout undo deployment/api # 直前のバージョンに戻す
kubectl rollout status deployment/api # 進行状況を見る第3章でも触れましたが、重要なので再掲します。
kubectl edit や kubectl scale での変更は、クラスタの状態だけを変えます。
Git のマニフェストは変わらないので、次のデプロイで上書きされて消えます。
調査のための一時的な変更には便利ですが、 恒久的な変更は必ずマニフェストを直して PR を出してください。
実務の落とし穴まとめ
logsだけ見て--previousを忘れる — CrashLoopBackOff の原因が見えない- Service のセレクタ不一致 — Endpoints が空。エラーが出ないので気づきにくい
- livenessProbe に依存先チェック — DB 不調で全 Pod が再起動する
- Dockerfile の COPY の順序 — キャッシュが効かずビルドが毎回遅い
- Secret を暗号化だと思う — Base64 なので誰でも読める
kubectl editで直す — 次のデプロイで消える
まとめ
- コンテナは OS を持たない。中身は Linux
- Dockerfile は変わりにくいものを先に書く(キャッシュ)
- Kubernetes は宣言的。「あるべき状態」を書けば、そこへ近づけてくれる
- Pod は使い捨て。アクセスは Service を通す
- readiness は Service から外れる、liveness は再起動。依存先チェックは readiness へ
- 障害調査は
get pods→describe→logs --previous - 繋がらない時は
kubectl get endpointsが空でないか OOMKilledは memory limit 超過
章末問題
Pod は Running なのに、Service 経由でアクセスすると繋がりません。最初に確認することは?
DB が数分間不調になった際、全ての Pod が再起動を繰り返して障害が拡大しました。設定上の問題は?
Dockerfile で COPY . . を pnpm install より前に書くと何が起きますか。
次の章では、こうして動かした複数のサービスを、 どう安全に繋ぐかを扱います。