【注意書き】本記事は、Kubernetes におけるコンテナランタイム(containerd / CRI-O)の基礎を、一般的な情報として整理するものです。実際の最適解は、クラスタ規模、OS/カーネル、cgroup v1/v2、利用する CNI/CSI、イメージレジストリ、セキュリティ要件、運用体制、監査要件などの条件で大きく変わります。障害対応・移行・設計判断はリスクが高いため、必要に応じて株式会社情報工学研究所のような専門家へご相談ください。
もくじ
- 第1章: “Dockerで動くのに…”が通じない瞬間:Kubernetes運用のモヤモヤから始めよう
- 第2章: コンテナ実行の全体像:kubelet → CRI → runtime → OCI を一枚でつなぐ
- 第3章: containerdとCRI-Oの立ち位置:汎用ランタイムとKubernetes特化の違い
- 第4章: CRIの「呼び出し口」:gRPC APIと“どこで詰まるか”の観察ポイント
- 第5章: OCIとrunc:最後にプロセスを起動するのは誰か(責務の分解)
- 第6章: namespace / cgroup復習:リソース制御が“効かない”の正体をほどく
- 第7章: イメージとストレージ:pullの遅さ・容量逼迫・スナップショットの関係
- 第8章: ネットワークは誰の責任?CNIとruntimeの境界で切り分ける
- 第9章: ログと可観測性:containerd/CRI-Oのログで障害の手掛かりを掴む
- 第10章: 帰結:runtimeを理解すると「夜間対応の不安」が減る—選定・移行・運用の最短ルート
第1章: “Dockerで動くのに…”が通じない瞬間:Kubernetes運用のモヤモヤから始めよう
「ローカルのDockerでは動くのに、クラスタに入れると落ちる」「Podが起動しないのに、アプリ側のログが出ない」「ImagePullBackOff の裏で何が起きているのか分からない」——Kubernetes運用で一番つらいのは、“現象は出ているのに、原因の位置が見えない”瞬間です。
このモヤモヤは、担当者の腕が足りないからではありません。Kubernetesは、責務をレイヤーで分割しているため、問題が「アプリ」ではなく「実行基盤(ランタイム)」や「境界(CRI/CNI/CSI)」にあると、見えるログやメトリクスの取り方が変わるからです。
「心の会話」:現場でよく起きる独り言
「また新しい概念? どうせ運用が増えるだけじゃないのって、正直思いますよね。」
「“それ、誰がメンテするの?”ってまず考えるのが現場です。」
こう感じるのは自然です。Kubernetesの運用負荷は、クラスタの成長とともに確実に上がります。だからこそ、containerd/CRI-Oの役割を“最低限”理解しておくと、切り分けが速くなり、不要な疑い(アプリのせい/ネットワークのせい/ノードのせい)を減らせます。
このブログの狙い:ランタイムを「仕組み」として腹落ちさせる
本記事では、containerd/CRI-Oを「インストール手順」ではなく、何を担当し、どこまでが責任範囲かという観点で整理します。狙いは次の3つです。
- 障害時に「まずどのログを見るべきか」を判断できる
- 移行や選定の場面で、説明責任(上司・監査・チーム)を果たせる
- “一般論”と“個別案件”の境界を理解し、必要なら専門家に相談できる
まず押さえる前提:Kubernetesは「Docker」を直接呼ばない
昔は Docker Engine(dockerd)をKubernetesが利用する構成も広く使われましたが、現在の一般的な構成では、kubelet はCRI(Container Runtime Interface)というインターフェースを介して、containerd や CRI-O のようなランタイムと連携します。つまり「Dockerで動く」は、Kubernetesの実行パスを必ずしも保証しません。
この差分が、エンジニアの“腹落ちしないトラブル”の温床になります。次章では、実行パスを一枚の絵としてつなぎ、どこで何が起きるのかを整理します。
第2章: コンテナ実行の全体像:kubelet → CRI → runtime → OCI を一枚でつなぐ
Kubernetesでコンテナが動くまでの流れは、細部を省けば次の鎖で説明できます。
| 層 | 主役 | ざっくり責務 |
|---|---|---|
| Kubernetesノード | kubelet | Pod仕様を満たすようにノード上の実体(コンテナ)を維持する |
| ランタイムIF | CRI | kubeletがランタイムに要求を出すための共通API(gRPC) |
| コンテナ管理 | containerd / CRI-O | イメージ管理、コンテナライフサイクル、ストレージ/ネットワーク連携の土台 |
| 実行仕様 | OCI | コンテナを「どう起動するか」の標準(イメージ仕様/ランタイム仕様) |
| 最終実行 | runc など | Linuxのnamespace/cgroup等を使って実プロセスを起動する |
ポイントは、kubeletは「コンテナを起動して」とランタイムに依頼するだけで、実際にイメージを解決し、ファイルシステムを用意し、プロセスを起動するのはランタイム側(containerd/CRI-O)と、その配下(OCIランタイム)です。
“どこで詰まるか”が分かると、切り分けが変わる
同じ「Podが起動しない」でも、詰まり箇所で見るべき情報が変わります。代表例を整理します。
- スケジューリング前後:Podがノードに割り当てられていない → scheduler / taint・resource・affinity を疑う
- kubelet〜CRI:kubeletイベントにエラーが出る → CRI疎通、認証、ノード状態(disk pressure等)を疑う
- イメージ取得:ImagePullBackOff → レジストリ到達性、認証、イメージ名/タグ、プロキシ設定を疑う
- 起動直後に終了:CrashLoopBackOff → entrypoint/command、環境変数、依存サービス、ファイル権限などアプリ要因が多い
- リソース制限:OOMKilled → cgroup制限、requests/limits、JVM/Goのメモリ挙動を疑う
ここで重要なのが「ランタイム視点」です。たとえば ImagePullBackOff は“アプリのバグ”ではありませんし、OOMKilled は“ノードが弱い”だけとも限りません。どの層の責務かを押さえることで、説明も修正も速くなります。
最低限覚える用語:Pod sandbox と container
CRIの世界では、Pod(正確にはPodのネットワーク名前空間などの基盤)を作るための概念としてPod sandboxが出てきます。Pod内の複数コンテナ(app/sidecar)は、そのsandbox上で動く、という整理になります。ログに “sandbox” が出たら「Podの土台(ネットワーク/名前空間)側」の話だ、と当たりを付けられます。
次章では、containerd と CRI-O を「思想と責務」の観点で比べ、なぜ2つが並立するのかを整理します。
第3章: containerdとCRI-Oの立ち位置:汎用ランタイムとKubernetes特化の違い
結論から言うと、containerd と CRI-O はどちらも Kubernetes で使われる主要なコンテナランタイムですが、成り立ちと設計の重心が少し違います。ここを理解すると、選定やトラブルシュートの“迷い”が減ります。
まず大枠:どちらも「CRIを話せる」ランタイム
kubelet は CRI を通じてランタイムと話します。したがって、Kubernetes運用の観点では、どちらも「kubeletの要求に応えてコンテナを動かす役」と捉えて問題ありません。一方で、周辺エコシステムや管理対象の範囲が異なります。
| 観点 | containerd | CRI-O |
|---|---|---|
| 設計の重心 | 汎用のコンテナ実行基盤(Kubernetes以外の利用も想定) | Kubernetes向けに絞った実装(OCI/CRI中心) |
| 統合の考え方 | プラグイン/拡張を含む構造で組み合わせやすい | Kubernetes運用で必要な範囲に焦点を当てやすい |
| 見るべき観測点 | containerd本体ログ、snapshotter、image store、shim/runc周辺 | CRI-O本体ログ、OCIランタイム、Kubernetes連携の境界 |
“心の会話”に技術で答える:「結局どっちが正解なの?」
「また比較表? 正直、正解だけ教えてほしい…」という気持ち、分かります。ただ、ランタイム選定は“機能の多寡”ではなく、自社の運用要件にどれだけ素直に沿うかで決まります。
- 標準化された運用(手順・自動化・監査)が強い現場:ディストリビューション/サポート体系が前提になることが多い
- 性能/ストレージ/イメージ管理に課題がある現場:snapshotterやレジストリ、ノードのディスク設計まで含めて考える必要がある
- セキュリティ要件が強い現場:rootless/権限分離、ポリシー、監査ログ、脆弱性対応フローが重要になる
つまり「ランタイムだけで決める」と失敗しやすい、というのが現実です。次章以降で、CRI(呼び出し口)・OCI(起動仕様)・cgroup/namespace(実体)・ストレージ/ネットワーク(境界)を順に整理し、最終的に“一般論の限界”まで踏み込みます。
この章のまとめ:違いは“現場の見え方”に出る
containerd と CRI-O は、どちらもKubernetesで使えます。しかし、障害対応で辿るログ、統合のされ方、周辺コンポーネントの意識の置き方が少しずつ異なります。だからこそ「基礎」を押さえる価値があります。次章では CRI の中身(gRPC)に踏み込み、“どこで詰まるか”をログ観点で具体化します。
第4章: CRIの「呼び出し口」:gRPC APIと“どこで詰まるか”の観察ポイント
CRI(Container Runtime Interface)は、kubelet がコンテナランタイムに対して「Pod(正確には sandbox)を作れ」「コンテナを起動しろ」「ログを取れ」といった要求を出すための共通インターフェースです。実装としては gRPC で、kubelet は “runtime endpoint(UNIXソケット)” に接続し、RuntimeService / ImageService といったAPIを呼び出します。
ここが腹落ちすると、障害対応の初手が変わります。つまり、「kubelet → CRI で詰まっているのか」「CRIの先(ランタイム内部)で詰まっているのか」を分けて考えられるようになります。
“心の会話”:「Podが上がらないのに、何を見ればいいか分からない」
「イベントを見る? ログを見る? どっち? ていうか、どのログ?」となりがちですよね。これは“見る場所”が多いのが原因です。だからこそ、まずは観測点を固定します。
- kubelet のイベント:Pod/Nodeイベント(kubectl describe)に、CRI呼び出し失敗の痕跡が出る
- kubelet のログ:kubelet 自体が endpoint に接続できているか、認証/権限/タイムアウトはないか
- ランタイムのログ:containerd / CRI-O が要求を受け取り、どこで落ちたか
- CRIクライアント(crictl):kubelet と同じ CRI を叩き、切り分けを短絡化する
実務で強い切り分け:crictl で “CRI疎通” を確認する
kubelet が CRI を叩けない状況では、k8s の上位から見ても情報が薄くなります。そこで役に立つのが crictl です。crictl は CRI に直接接続し、イメージ一覧やコンテナ状態を取れます。kubelet が絡む前に「CRIが生きているか」を判断できます。
たとえば、次のような観点で“当たり”が付けられます(コマンドは一例です。環境によりパス・権限は異なります)。
- CRIに接続できない:ソケットパス不一致、権限不足、ランタイム自体が停止
- イメージ一覧が取れない/遅い:ストレージ逼迫、GC不全、レジストリ到達性の問題
- Pod sandbox が作れない:CNI、名前空間、iptables/nftables、SELinux 等の境界問題
“どこで詰まるか”を整理するチェック表
| 症状(見え方) | 疑う層 | 観察ポイント |
|---|---|---|
| kubeletがruntime endpointに接続できない | kubelet ⇄ CRI | kubeletログ、endpoint設定、ソケット権限、ランタイム起動状態 |
| ImagePullBackOff が続く | CRI ⇄ イメージ管理 | レジストリ到達性、認証、プロキシ、DNS、ノードディスク/GC |
| sandbox 作成で止まる | CRI ⇄ CNI | CNIログ、プラグイン設定、iptables/nftables、SELinux/権限 |
| コンテナ起動直後に失敗(詳細が薄い) | OCI起動周辺 | runc/shimログ、seccomp/AppArmor/SELinux、マウント/権限 |
この章のまとめ:CRIは「診断の分岐点」
CRI は単なる仕様ではなく、運用者にとっての“観測の分岐点”です。kubelet の見えている世界と、ランタイムの実体世界をつなぐ場所だからこそ、ここが理解できると「何を見ればよいか」が定まります。次章では、CRIの先にある OCI と runc(実際にプロセスを起動する層)へ進み、「起動そのものの失敗」を解体します。
第5章: OCIとrunc:最後にプロセスを起動するのは誰か(責務の分解)
containerd/CRI-O の理解で最後に必ず出てくるのが OCI(Open Container Initiative)と runc です。ここが曖昧だと「ランタイムが悪いのか、OSが悪いのか、アプリが悪いのか」の境界がぼやけます。逆に言えば、ここが腹落ちすると、“起動に失敗する系”のトラブルが怖くなくなります。
OCIは「標準化された約束事」
OCI には大きく2つの標準(仕様)が関係します。
- OCI Image Spec:イメージ(レイヤー、設定、メタデータ)をどう表現するか
- OCI Runtime Spec:コンテナを起動するための実行設定(プロセス、マウント、名前空間、cgroup等)をどう表現するか
重要なのは、OCI は「コンテナという概念」を魔法にしないことです。実態は Linux の機能(namespace/cgroup/マウント/ケーパビリティ等)を組み合わせ、決められた形式の設定(代表例が config.json)に基づいてプロセスを起動する、という構造です。
runcは「OCI Runtime Spec の参照実装」
runc は OCI Runtime Spec に沿ってコンテナを起動する代表的な実装です。多くの環境で、最終的に「実プロセスを起動する役割」を担います。ここでのポイントは、containerd/CRI-O が直接すべてをやるのではなく、“最後の起動”を OCI ランタイムに委譲するという責務分割があることです。
実務でのイメージとしては、次のような流れを押さえると十分です。
- kubelet が CRI 経由で「コンテナ起動」を要求
- containerd/CRI-O が必要な rootfs(レイヤー/スナップショット)を準備
- ランタイムが OCI 形式の実行設定を整え、runc 等へ起動を依頼
- runc が namespace/cgroup 等を設定し、プロセスを exec して起動
“心の会話”:「それ、アプリの設定ミスじゃないの?」
もちろん、起動に失敗する原因がアプリ側(entrypointや環境変数)のことは多いです。ただし、OCI層で失敗するケースは、アプリが正しくても起きます。たとえば次のようなものです。
- 権限・セキュリティポリシー:seccomp/AppArmor/SELinux の制約で特定のsyscallや操作が拒否される
- マウントやパス:volume の mount 失敗、パスの不整合、ファイル権限の不足
- ユーザー/グループ:rootless、runAsUser、fsGroup 等の整合が取れずに起動時に失敗
- cgroup制約:起動直後に OOM、pids制限、CPUスロットリングでタイムアウトに見える
起動失敗を“構造で”見るための表
| 失敗の種類 | 起きやすい層 | 確認の方向性 |
|---|---|---|
| 起動直後に終了(exit 1 など) | アプリ/entrypoint | コンテナログ、コマンド/引数、環境変数、依存サービス |
| 権限拒否・操作拒否 | OCI/OSセキュリティ | seccomp/AppArmor/SELinux、capabilities、runAsUser |
| マウント失敗 | ストレージ/CSI/ノード | volume設定、ノードのパス、権限、CSIログ、ディスク残量 |
この章のまとめ:起動の最終責任者を知ると、説明が強くなる
「コンテナが落ちる」をアプリだけで説明しようとすると、運用の会話が噛み合いません。OCI と runc の位置づけが分かると、「どこまでがアプリで、どこからが基盤か」を筋道立てて説明できます。次章では、基盤の根っこである namespace/cgroup を復習し、Kubernetes の requests/limits が“なぜ効いたり効かなかったり見えるのか”を現場目線で整理します。
第6章: namespace / cgroup復習:リソース制御が“効かない”の正体をほどく
Kubernetes運用のトラブルで、実は頻出なのに説明が難しいのが「リソース制御の話」です。たとえば、
- 「limit 入れたのにメモリが跳ねる」
- 「CPUが足りないのに落ちない(ただ遅い)」
- 「急に OOMKilled が増えた」
こういう現象は、アプリの“コード品質”だけでは語れません。裏にあるのは Linux の namespace と cgroup(control groups)です。
namespace:見える世界を分離する(名前空間の隔離)
namespace は「プロセスに見える世界」を分けます。PID、ネットワーク、マウント、ユーザー等が代表例です。コンテナは “軽量VM” と誤解されがちですが、実際は 同じカーネルの上で、見える範囲を分けているに過ぎません。だから、カーネル側の制約やセキュリティ設定が効きやすい、という特徴があります。
cgroup:使っていい資源を制限する(リソース管理)
cgroup は CPU・メモリ・I/O・PID数などの資源を「どこまで使っていいか」制御します。Kubernetes の requests/limits は、この cgroup 設定に反映されます。ここでの重要点は、CPU とメモリで“効き方”が違うことです。
| 資源 | 制御の効き方(一般的な傾向) | 現象として見えるもの |
|---|---|---|
| CPU | 上限を超えるとスロットリング(遅くなる) | レスポンス悪化、タイムアウト、スパイク時だけ詰まる |
| メモリ | 上限を超えるとOOM(落ちる) | OOMKilled、再起動ループ、突然の切断 |
| PID数 | 上限に達すると fork/exec が失敗 | 新規プロセスが作れず異常終了、監視エージェントも動かない |
“心の会話”:「limit ちゃんと入れてるのに、なんで?」
この疑問は健全です。運用現場での“あるある”として、次の要因が絡み合います。
- requests と limits の意味の違い:requests はスケジューリングの基準、limits は上限。requests が小さいと、混雑時にCPU奪い合いが起きやすい
- Kubernetes の QoS クラス:Guaranteed / Burstable / BestEffort で、ノード逼迫時の扱い(追い出されやすさ)が変わる
- cgroup v1/v2 の違い:OS・ディストリビューション・設定で挙動が変わることがある
- アプリのメモリモデル:JVM/Node.js/Go などは“コンテナ内のメモリ上限”を前提にしない設定だと暴れやすい
現場で役立つ「症状 → 解釈」の整理
ここでは、よくある現象を“決めつけずに”解釈するための目安を置きます。
| 現象 | ありがちな誤解 | 現実的な見立て |
|---|---|---|
| CPUが足りないのに落ちない | 「壊れてないからOK」 | スロットリングで“遅いだけ”。SLO/タイムアウトに直結しやすい |
| OOMKilled が断続的に増える | 「メモリリーク確定」 | リークの可能性はあるが、スパイク、キャッシュ、GC設定、limit過小でも起きる |
| 起動だけ遅い/たまに失敗 | 「ネットワークが弱い」 | CPU不足、ディスクI/O、イメージ展開、cgroup制約が絡むことが多い |
この章のまとめ:namespace/cgroupは「基盤の物理法則」
containerd/CRI-O の話に戻すと、OCI ランタイムは最終的に namespace と cgroup を使ってプロセスを起動します。だから、リソース制御のトラブルは “アプリの外側” で起きます。ここを押さえると、requests/limits の調整や、ノード設計・監視設計の議論が現実的になります。次章では、イメージとストレージに踏み込み、pullの遅さ・容量逼迫・GC不全が、なぜランタイムの問題として現れるのかを整理します。
第7章: イメージとストレージ:pullの遅さ・容量逼迫・スナップショットの関係
Kubernetes運用で「地味につらい」のが、イメージ取得(pull)とノードストレージの問題です。アプリは変えていないのに起動が遅くなったり、突然 ImagePullBackOff が増えたり、ノードが DiskPressure になって Pod が追い出されたりします。これらは“アプリが遅い”のではなく、ランタイムとノードのディスク設計が原因で起きることが多いです。
イメージ取得の実体:レイヤーと展開(unpack)
一般にコンテナイメージは複数のレイヤー(差分)で構成され、ノード側はそれをダウンロードし、ローカルに保存し、実行可能な形(rootfs)として展開します。この「ダウンロード」と「展開」は別のボトルネックになりえます。
- ネットワーク要因:レジストリ到達性、DNS、プロキシ、認証失敗、帯域不足
- ディスクI/O要因:レイヤーの展開でCPU/ディスクを使う、同時起動数が多いほど詰まりやすい
- キャッシュ要因:ノードに既にレイヤーがあれば速いが、ノード入れ替えやスケールアウトでキャッシュが効かない
運用上は「どれが遅いのか」を見分けるのが重要です。ネットワークが遅いのか、ディスクが遅いのかで対策が違うためです。
スナップショットの考え方:rootfsを“差分”で組み立てる
多くの環境では、イメージのレイヤーを重ね合わせて rootfs を作る際に、スナップショット(差分ファイルシステムの仕組み)を利用します。これにより、同じベースレイヤーを複数コンテナで共有でき、ディスク使用量や展開の効率が改善します。
ただし、ノードのファイルシステム種類、空き容量、inode枯渇、断片化、I/O性能の低下などがあると、スナップショット運用が不安定になります。結果として「pullが遅い」「起動が遅い」「GC(不要データ削除)が追いつかない」という現象に見えます。
“心の会話”:「ディスク空きはあるのに、なんでDiskPressure?」
これは本当に起きがちです。Kubernetesでは、ノードの状態(ディスク逼迫など)を検知してスケジューリングや追い出しに反映しますが、「どの領域を見て逼迫と判断しているか」は環境依存になりやすいです。たとえば、以下のようなケースがあります。
- イメージ領域は空きがあるが、ログ領域が肥大化している(/var/log/containers 等)
- 空き容量はあるが、inodeが枯渇している(小さなファイルが大量)
- 一時領域(ephemeral storage)をアプリが大量消費している(tmp、キャッシュ、ワークディレクトリ)
このため、ディスクの“総量”だけでなく、ログ・一時領域・イメージ領域がどこに載っているか(パーティション設計)と、監視指標(空き容量/inode/書き込み遅延)をセットで考える必要があります。
現場で役立つ整理:何を増やすか、何を減らすか
| 課題 | よくある原因 | 対策の方向性(一般論) |
|---|---|---|
| pullが遅い | レジストリ到達性、帯域不足、同時起動で展開が詰まる | レジストリ/プロキシ設計、ノードI/O改善、同時起動数の見直し |
| 容量逼迫(DiskPressure) | ログ肥大、イメージGC不全、一時領域の増加 | ログローテ、不要イメージ削除、ephemeral storage 設計/制限 |
| 起動がムラになる | ディスクI/O待ち、CPU不足、キャッシュヒット率低下 | ノード性能の底上げ、配置戦略、イメージサイズ最適化 |
この章の結論は「イメージはネットワークだけの問題ではない」です。ランタイムが扱うストレージ(レイヤー、展開、キャッシュ、GC)を理解すると、対策が“筋の良いところ”に当たるようになります。次章では、もう一つの境界であるネットワーク(CNI)に進み、「ネットワークは誰の責任か」を切り分けられるようにします。
第8章: ネットワークは誰の責任?CNIとruntimeの境界で切り分ける
Kubernetesのネットワークトラブルは、切り分けが難しい代表格です。「Serviceに繋がらない」「DNSが引けない」「Pod間通信だけ落ちる」「外部へのegressが不安定」など、現象が多様で、原因が複数層に散らばります。ここで重要なのは、ランタイム自身がネットワークを“全部作る”わけではないという点です。
CNIとは:ネットワーク設定を行うための標準インターフェース
CNI(Container Network Interface)は、コンテナ(Pod sandbox)に対してネットワークを設定するための仕組み(仕様)です。実体は、ノード上の CNI プラグイン(実行ファイル)と設定ファイル群で、Pod作成のタイミングで「ネットワーク名前空間にIFを追加し、IPを割り当て、ルーティングやフィルタを整える」といった処理を行います。
kubelet は CRI を介して「sandbox を作る」要求を出し、その過程でランタイムが CNI を呼び出してネットワークをセットアップする、という形になるのが一般的です。つまり、ネットワークの失敗は「kubelet」ではなく、CRI〜ランタイム〜CNIのどこかで起きます。
“心の会話”:「ネットワークって、結局どこを直せばいいの?」
この問いに対する実務的な答えは、「現象を“責務の境界”で切る」です。以下のように当たりを付けると、調査の順序が安定します。
- Podが作れない/止まる:sandbox 作成で失敗 → CNI呼び出し失敗の可能性が高い
- Podは上がるが通信できない:IP付与は成功、ルーティング/フィルタ/ポリシーの問題の可能性
- DNSだけ不安定:CoreDNS/名前解決経路、ノード側のresolv.conf、ネットワークポリシーの影響を疑う
- 特定ノードだけおかしい:ノード固有のiptables/nftables、カーネル設定、NIC、MTU不整合などを疑う
よくある落とし穴:iptables/nftables、MTU、ネットワークポリシー
ネットワークは“正しさ”よりも“整合”が大事です。特に次の3つは、環境差が出やすいポイントです。
- iptables / nftables:OSや設定によりフィルタの実装・ルールの解釈が変わり、CNIやServiceの挙動に影響することがある
- MTU不整合:オーバーレイや経路で実効MTUが変わり、特定サイズのパケットだけ落ちる(“疎通はするが遅い/タイムアウトする”)に見える
- NetworkPolicy:意図せずDNSやメトリクス収集経路を遮断し、障害時に観測不能になる
ここでのポイントは、「CNIが悪い」と決めつけないことです。CNIは“設定する側”で、背後にあるOSのパケット処理や、クラスタのポリシー設計が原因のことも多いからです。
切り分けの道具立て:ログと観測点を固定する
ネットワークの問題は、観測点が散るほど迷子になります。最低限、以下の観測点を固定しておくと、調査が前に進みます。
| 観測点 | 見る理由 |
|---|---|
| Podイベント(kubectl describe) | sandbox 作成失敗や CNI 関連の失敗が露出しやすい |
| ランタイムログ(containerd/CRI-O) | CNI呼び出しの成否やタイムアウトの痕跡が出ることがある |
| CNIプラグインのログ | IP付与、IF作成、ルール設定のどこで失敗したかを特定しやすい |
この章の結論は「ネットワークは“境界”で切り分ける」です。CNIは万能ではありませんが、責務を押さえると“どこを疑うべきか”が決まります。次章では、ログと可観測性にフォーカスし、containerd/CRI-Oのログや、Kubernetesの標準ログ配置から、障害の手掛かりを掴む方法を整理します。
第9章: ログと可観測性:containerd/CRI-Oのログで障害の手掛かりを掴む
運用の現場で本当に効くのは「ログを見る順番」を決めておくことです。containerd/CRI-O の基礎は、インストール知識よりも、障害時に“どこから掘るか”で価値が出ます。ここでは、一般的なKubernetesノードでのログ配置と、調査の導線を整理します(ディストリビューションや設定により差はあります)。
まず押さえる:Kubernetesのコンテナログは“ファイル”としても残る
多くの構成で、コンテナ標準出力/標準エラー(いわゆる kubectl logs で見えるもの)は、ノード上にログファイルとして保存されます。代表的には次のようなディレクトリ配下です。
- /var/log/containers/:コンテナ単位のログ(多くの環境でシンボリックリンク)
- /var/log/pods/:Pod単位のログ格納(コンテナごとのファイルが並ぶ)
「kubectl logs が取れない」場合でも、ノードに入れる状況ならログファイルから復旧できることがあります。一方で、ログローテーションやディスク逼迫で消えることもあるため、長期保存が必要なら外部収集(ログ基盤)が別途必要です。
“心の会話”:「イベントはあるけど、決定打がない…」
これは典型的な状態です。イベントは“要約”で、ランタイム内部の失敗(タイムアウト、権限、展開失敗など)の詳細は薄くなりがちです。だから、次の順で深掘りします。
- Podイベントで失敗の種類を分類(pull / sandbox / start / probe / OOM など)
- kubeletログで CRI 呼び出しの失敗(接続、タイムアウト)を確認
- ランタイムログで “どこで落ちたか” を追う(イメージ、スナップショット、CNI、OCI)
- 周辺ログ(CNI/CSI/OSセキュリティ)で境界の失敗を確定する
観測点の対応表:迷子にならないための地図
| 見たいこと | 主な観測点 | 狙い |
|---|---|---|
| Podが上がらない理由 | kubectl describe(イベント) | pull失敗、sandbox失敗、起動失敗の分類 |
| CRI疎通の問題か | kubeletログ、crictl | endpoint接続、タイムアウト、権限の切り分け |
| イメージ/展開/GCの問題か | ランタイムログ、ノードディスク状況 | ダウンロード・展開・容量逼迫の特定 |
| ネットワーク境界の問題か | ランタイムログ、CNIログ | sandbox/CNI呼び出しの失敗点を確定 |
この章のまとめ:可観測性は“設計”で決まる
障害時にログが取れないのは、担当者の努力不足ではなく、可観測性が設計されていないサインであることが多いです。containerd/CRI-O を理解すると、「どの層のログが必要か」を運用要件として言語化できます。次章では、ここまでの基礎を“判断軸”に落とし込み、ランタイム理解がなぜ夜間対応の不安を減らすのか、そして一般論の限界と専門家相談が必要なポイントを整理します。
第10章: 帰結:runtimeを理解すると「夜間対応の不安」が減る—選定・移行・運用の最短ルート
ここまで、kubelet→CRI→containerd/CRI-O→OCI→runc、さらに cgroup/namespace、ストレージ、CNI、ログの導線を整理してきました。最後に一番大事な帰結を言うと、containerd/CRI-O を学ぶ価値は「ツールの暗記」ではなく、障害や変更の場面で“迷いが減る”ことにあります。
“心の会話”:「結局、何を分かっていれば夜が楽になるの?」
現場の本音としてはこれですよね。夜間対応がつらいのは、障害そのものより、
- どこから調べればいいか分からない
- 説明ができずに会話が空回りする(アプリ?基盤?ネットワーク?)
- 復旧を急ぐほど“当てずっぽう”になり、再発する
この3つが重なるからです。だから、ランタイム理解のゴールは「設計の正しさ」ではなく、切り分けの順番を固定することです。
最短ルートの実務フレーム:3つの問いで切り分ける
夜間の一次対応で強いのは、細部の知識よりも “問いの型” です。以下の3つを順番に当てはめると、判断がブレにくくなります。
| 問い | 意図 | 次のアクション例 |
|---|---|---|
| ① そもそもノードに割り当てられているか? | Kubernetes制御面(スケジューリング)か、ノード実行面かを分離 | Podイベント、Node状態、taint/リソース、配置条件を確認 |
| ② CRIは動いているか?(kubelet→runtimeの疎通) | 「kubeletが呼べない」問題を早期に切る | kubeletログ、runtimeの稼働、crictl相当で確認 |
| ③ 失敗は “pull/sandbox/start” のどれか? | ストレージ・CNI・OCI起動・アプリを分類して調査の順番を固定 | イベント分類→ランタイムログ→CNI/CSI/OSセキュリティへ進む |
この型を守るだけで、「アプリが悪いはず」「ネットワークが怪しい気がする」といった推測の暴走を抑えられます。復旧が速くなるだけでなく、再発防止の議論が“層”で整理できるので、会議も楽になります。
選定・移行の現実:一般論では決まらない理由
containerd と CRI-O の比較はよく話題になりますが、実際の現場では「ランタイム単体」では決まりません。理由はシンプルで、ランタイムは他の境界(CNI/CSI/OSセキュリティ/監視/レジストリ/ディスク設計)と強く結びつくからです。
たとえば、次のような条件が絡むと、一般的なおすすめは簡単にひっくり返ります。
- ノードOS/カーネル差:cgroup v2 前提か、SELinux運用か、既存のセキュリティ設計を崩せないか
- ストレージ制約:イメージサイズが大きい、ノードディスクが小さい、ログが多い、GCが追いつかない
- ネットワーク条件:MTU、プロキシ、閉域、レジストリ到達性、名前解決要件
- 運用体制:夜間対応の分担、権限分離、監査ログ要求、変更管理の厳しさ
つまり「どっちが良いか」ではなく、“あなたの現場にとって、どの失敗が一番痛いか”を先に決めないと、最適化はできません。ここが、一般論の限界です。
導入・変更で失敗しやすいポイント(運用目線)
最後に、実務で“事故になりやすい”ポイントを、あえて現場の言葉でまとめます。
- ログが取れない設計:障害時に kubectl logs が取れず、原因不明のまま復旧優先になって再発する
- ディスク設計の軽視:イメージ/ログ/一時領域が同居してDiskPressure、起動遅延が慢性化する
- ネットワーク境界の不明瞭:CNI/iptables/MTU/ポリシーが絡んで「繋がらない」を説明できない
- リソース制御の誤解:CPUは遅くなり、メモリは落ちる。ここを混同するとSLOもコストも崩れる
これらは「頑張れば何とかなる」種類の課題ではなく、最初に設計しておくべき運用要件です。設計できている現場は、夜間対応が劇的に減ります。
この章のまとめ:困ったときに“相談できる設計”が最強
containerd/CRI-O の基礎を押さえると、現場で起きていることを「層」で説明できるようになります。結果として、障害対応が速くなり、運用の心理的負担も下がります。
一方で、ここまで見てきた通り、ランタイム周りは条件依存が強く、一般論のまま進めると「部分最適の改善が、別の層の事故を呼ぶ」こともあります。特に、閉域ネットワーク、医療・公共などの監査要件、BCP上の復旧手順、厳格な変更管理がある現場では、判断を誤るとリスクが大きいです。
もし、
- ランタイムの選定・移行(Docker由来構成の整理、ノード更改)で悩んでいる
- 起動遅延やDiskPressure、ネットワーク不安定が慢性化している
- 夜間対応の属人化を減らし、手順と観測点を標準化したい
といった状況なら、株式会社情報工学研究所のような専門家に相談する価値があります。単に「こう直せば良い」を言うのではなく、現場の制約(止められない、変えられない、監査がある)を前提に、再発しない運用設計として落とし込むことが重要だからです。
付録:主要プログラミング言語ごとの注意点(コンテナ/Kubernetes運用の観点)
最後に、「いま主流のプログラミング言語」ごとに、Kubernetes上で運用する際にトラブルになりやすい注意点をまとめます。ここも一般論であり、実際の最適設定はワークロードやSLO、セキュリティ要件で変わります。
Python
- 依存関係(pip/OSパッケージ)が増えやすく、イメージ肥大→pull遅延に繋がりやすい
- ワーカー並列(gunicorn等)を増やしすぎるとメモリ上限に当たりやすい(OOMの原因)
- ネイティブ依存(cryptography等)のビルド差分が出ると、ノード差・ベースイメージ差で事故になりやすい
Java
- コンテナのメモリ上限に合わせたJVM設定が不適切だと、OOMKilledやGCスパイクが出やすい
- 起動時間が長いアプリは readiness/liveness 設計を誤ると再起動ループになりやすい
- スレッド数・ファイルディスクリプタ上限をcgroup/pids/ulimitと整合させないと、負荷時に突然落ちる
C
- メモリ安全性の問題が致命障害に直結しやすい(セキュリティ面も含む)
- シグナル/プロセス管理を雑にすると、PID1問題(子プロセス回収など)で運用事故になりやすい
- libc/ランタイム依存でベースイメージ差が出やすく、移植性の検証が重要
C++
- 依存ライブラリのABI差、ビルドオプション差が事故の原因になりやすい(環境を固定する設計が重要)
- メモリ管理・スレッド設計次第で、負荷時の遅延やメモリ跳ねが発生しやすい
- 観測性(メトリクス/トレース)を後付けすると難しいため、先に設計しておくと運用が安定する
C#(.NET)
- ランタイム・GCの挙動がワークロードにより大きく変わるため、requests/limitsと実測のすり合わせが必須
- 起動時間・JITの影響で、ローリング更新時に一時的な性能劣化が出ることがある
- 証明書/暗号スイートなど基盤依存のトラブルが、コンテナ化で表面化することがある
JavaScript(Node.js)
- イベントループ遅延がSLOに直結しやすい。CPU制限(スロットリング)でタイムアウトに見えることがある
- ヒープ上限・メモリ設定が不適切だと OOMKilled が起きやすい
- 依存(npm)が増えてイメージサイズが膨らみやすく、配布・脆弱性対応の負担が増える
TypeScript
- ビルド工程(トランスパイル)をコンテナビルドに組み込むと時間・サイズが増えやすい(段階ビルド等の設計が重要)
- 実行時はNode.jsの注意点を引き継ぐ(CPU制限・メモリ・依存管理)
- 型だけで安全にならないため、入力検証・権限設計・秘密情報管理は別途必要
PHP
- 拡張モジュールやOSライブラリ依存があり、イメージ差分や脆弱性対応が運用負債になりやすい
- FPMのプロセス数調整を誤ると、メモリを食い尽くしやすい(OOM)
- ファイルI/O(アップロード/セッション/キャッシュ)と永続化(ボリューム設計)の整合が重要
Go
- CPU制限下でスケジューリングやGCが想定通りに見えず、遅延が出ることがある(実測とチューニングが必要)
- メモリ使用量が“じわじわ増える”ケースがあり、limit設計と監視が重要
- 静的リンクの利点は大きいが、証明書やDNSなど周辺環境差は残るため、運用検証が必要
Rust
- メモリ安全性の強みはあるが、非同期/スレッド設計次第で負荷時の遅延が出る(運用指標を先に決めると安定)
- ビルド時間が長くなりがちで、CI/CDとイメージ配布戦略が重要
- 低レイヤ連携が多いほど、OS/カーネル差・権限差が表面化しやすい
Swift
- サーバ用途では環境差・依存差の影響を受けやすく、コンテナでビルド/実行を安定させる設計が必要
- ビルド・配布のパイプラインが複雑化しやすいので、再現性を最優先にする
- 観測性(ログ/メトリクス/トレース)を先に整えると、運用負担が大きく下がる
付録のまとめ:言語ごとの“癖”は、Kubernetes上では「CPU制限」「メモリ上限」「ディスク/ログ」「ネットワーク境界」「セキュリティポリシー」という形で表面化します。一般論としての注意点は押さえられますが、実際の最適化は、SLO・コスト・監査要件・既存資産に左右されます。具体的な案件(移行計画、クラスタ構成、ログ基盤、BCP/復旧手順)で悩んだ場合は、株式会社情報工学研究所のような専門家に相談し、現場制約込みで“壊れにくい運用設計”として落とし込むのが安全です。




