- Authors

- Name
- Youngju Kim
- @fjvbn20031
containerd CRI実装:Kubernetesランタイム統合
containerdはKubernetes CRI(Container Runtime Interface)を内蔵プラグインとして実装しています。この記事ではCRI gRPCサービスの実装詳細、Pod Sandbox管理、コンテナスペック変換、ストリーミングAPI、RuntimeClass、NRIを分析します。
1. CRI gRPCサービス
1.1 サービス構造
CRIは2つのgRPCサービスで構成されます:
CRI gRPCサービス:
RuntimeService:
+-- PodSandbox管理
| RunPodSandbox
| StopPodSandbox
| RemovePodSandbox
| PodSandboxStatus
| ListPodSandbox
|
+-- Container管理
| CreateContainer
| StartContainer
| StopContainer
| RemoveContainer
| ListContainers
| ContainerStatus
| UpdateContainerResources
|
+-- ストリーミング
| ExecSync
| Exec
| Attach
| PortForward
|
+-- ランタイム情報
Status
Version
ImageService:
+-- PullImage
+-- ListImages
+-- ImageStatus
+-- RemoveImage
+-- ImageFsInfo
1.2 ソケット構成
CRIソケット:
containerdは同一のgRPCソケットでCRIを提供:
/run/containerd/containerd.sock
kubelet設定:
--container-runtime-endpoint=unix:///run/containerd/containerd.sock
CRIプラグインがcontainerdサーバーにCRIサービスを登録:
プラグインID:io.containerd.grpc.v1.cri
2. Pod Sandbox
2.1 Pod Sandbox概念
Pod SandboxはPodの分離環境を表します:
Pod Sandbox構成:
Pod Sandbox = Pauseコンテナ + 共有ネームスペース
共有リソース:
- ネットワークネームスペース(同じIP、ポート空間)
- IPCネームスペース(プロセス間通信)
- UTSネームスペース(ホスト名)
- PIDネームスペース(オプション)
分離リソース:
- マウントネームスペース(コンテナ別)
- cgroup(コンテナ別リソース制限)
2.2 RunPodSandboxフロー
RunPodSandbox処理:
1. Sandboxメタデータ作成
- ID生成
- ログディレクトリ作成
|
v
2. Pauseイメージプル
- sandbox_image設定からイメージ決定
- デフォルト値:registry.k8s.io/pause:3.9
|
v
3. Pauseコンテナスナップショット準備
|
v
4. OCIスペック生成
- Pauseコンテナ用最小スペック
- ホスト名、DNS設定を含む
|
v
5. ネットワークネームスペース作成
- /var/run/netns/にネームスペースファイル作成
|
v
6. CNIプラグイン呼び出し
- ネットワークインターフェース作成
- IP割り当て
|
v
7. PauseコンテナTask作成と起動
|
v
8. Sandbox状態をSANDBOX_READYに設定
2.3 Pauseコンテナ
Pauseコンテナの役割:
1. ネームスペース保持者:
- ネットワークネームスペースの最初のプロセス
- Appコンテナが終了してもネームスペースを維持
- ネームスペースのライフサイクルをPodにバインド
2. PID 1の役割:
- Pod PIDネームスペースのinitプロセス
- ゾンビプロセス回収(reap)
- 最小リソース使用(約1MB)
3. 動作:
- pause()システムコールで無限待機
- SIGTERM受信で終了
3. コンテナスペック変換
3.1 CRIリクエストからOCIスペックへ
スペック変換過程:
CRI ContainerConfig:
- Image
- Command、Args
- Envs
- Mounts
- Devices
- SecurityContext
- Resources
|
v
containerd CRIプラグインが変換
|
v
OCI Runtime Spec:
- root(イメージスナップショットパス)
- process(コマンド、env、capabilities)
- mounts(ボリューム、特殊ファイルシステム)
- linux.resources(cgroup設定)
- linux.namespaces(Sandboxと共有)
- hooks(OCIフック)
3.2 リソース変換
Kubernetesリソース -> OCIリソース変換:
CPU:
requests.cpu: 250m
-> linux.resources.cpu.shares = 256
(1000m = 1024 shares基準)
limits.cpu: 500m
-> linux.resources.cpu.quota = 50000
linux.resources.cpu.period = 100000
(500m/1000m * 100000us)
Memory:
limits.memory: 512Mi
-> linux.resources.memory.limit = 536870912
(バイト単位)
requests.memory:
-> スケジューリングにのみ使用、OCIスペックには反映しない
Hugepages:
limits.hugepages-2Mi: 100Mi
-> linux.resources.hugepageLimits:
pageSize: "2MB"
limit: 104857600
3.3 セキュリティコンテキスト変換
SecurityContext -> OCIスペック変換:
runAsUser: 1000
-> process.user.uid = 1000
runAsGroup: 1000
-> process.user.gid = 1000
readOnlyRootFilesystem: true
-> root.readonly = true
privileged: true
-> すべてのcapabilitiesを付与
-> すべてのデバイスアクセスを許可
-> AppArmor/SELinux/Seccompを無効化
capabilities:
add: ["NET_ADMIN"]
drop: ["ALL"]
-> process.capabilities設定
seccompProfile:
type: RuntimeDefault
-> linux.seccompプロファイル適用
4. ストリーミングAPI
4.1 ExecSync
ExecSync動作:
同期的にコンテナでコマンド実行:
1. kubeletがExecSync(containerID, cmd, timeout)を呼び出し
|
v
2. containerdがshimにExecリクエスト
|
v
3. shimがrunc execを実行
- コンテナネームスペースに新しいプロセス作成
|
v
4. stdout/stderrをキャプチャ
|
v
5. プロセス終了待機
|
v
6. exit code + stdout + stderrを返す
使用事例:liveness/readiness probe、kubectl exec(同期)
4.2 Exec(非同期ストリーミング)
Execストリーミング動作:
1. kubeletがExec(containerID, cmd, stdin, stdout, stderr)を呼び出し
|
v
2. containerdがストリーミングURLを返す
- ストリーミングサーバーアドレス:https://node:10250/exec/...
|
v
3. kubeletがクライアントにURLを転送
|
v
4. クライアントがWebSocket/SPDYでストリーミングサーバーに接続
|
v
5. ストリーミングサーバーがcontainerdに実際のExecを実行
|
v
6. stdin/stdout/stderr双方向ストリーミング
ストリーミングプロトコル:
- SPDY(レガシー)
- WebSocket(最新)
4.3 Attach
Attach動作:
実行中のコンテナのメインプロセスに接続:
1. ストリーミングURL生成(Execと類似)
|
v
2. コンテナのstdin/stdout/stderrに接続
- 新しいプロセスを作成しない
- 既存プロセスのI/Oに直接接続
|
v
3. 双方向ストリーミング
使用事例:kubectl attach
4.4 PortForward
PortForward動作:
Podのポートにローカルトラフィックを転送:
1. ストリーミングURL生成
|
v
2. Podのネットワークネームスペースでsocat/nsenterを実行
- 指定ポートにTCP接続
|
v
3. ローカルポートとPodポート間の双方向データ転送
実装:
containerdがPodのネットワークネームスペースに参入し
対象ポートにTCP接続を作成します。
使用事例:kubectl port-forward
5. RuntimeClass
5.1 RuntimeClassマッピング
RuntimeClass処理:
1. Kubernetes RuntimeClassリソース:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
2. kubeletがCRI RunPodSandbox呼び出し時
runtime_handler = "kata"を渡す
3. containerdがhandlerをランタイム設定にマッピング:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
4. 対応するshimバイナリでTask作成:
containerd-shim-kata-v2
5.2 デフォルトランタイム
デフォルトランタイム設定:
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
PodにruntimeClassNameがなければデフォルトランタイム(runc)を使用
5.3 RuntimeClassオーバーヘッド
RuntimeClassリソースオーバーヘッド:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "160Mi"
cpu: "250m"
オーバーヘッド処理:
- kubeletがPodリソースにオーバーヘッドを追加
- スケジューラーがオーバーヘッドを含めてノード選択
- VMベースランタイムの固定コストを反映
6. NRI(Node Resource Interface)
6.1 NRI概要
NRIはcontainerdのプラグイン拡張メカニズムで、コンテナライフサイクルイベントにフックを登録できます:
NRIアーキテクチャ:
kubelet -> containerd
|
+-- NRI Plugin 1(リソース割り当て)
+-- NRI Plugin 2(トポロジー認識)
+-- NRI Plugin 3(モニタリング)
NRIプラグインはコンテナライフサイクルイベントを受信し
OCIスペックを修正できます。
6.2 NRIフックポイント
NRIフックポイント:
1. RunPodSandbox:
- Pod作成時に呼び出し
- Podレベルリソース割り当て
2. CreateContainer:
- コンテナ作成時に呼び出し
- OCIスペック修正可能
- CPUピンニング、メモリNUMA割り当てなど
3. StartContainer:
- コンテナ起動時に呼び出し
4. UpdateContainer:
- リソース更新時に呼び出し
5. StopContainer:
- コンテナ停止時に呼び出し
- リソース解放
6. RemoveContainer:
- コンテナ削除時に呼び出し
6.3 NRI使用事例
NRI活用事例:
1. CPU/メモリトポロジー認識割り当て:
- NUMA認識CPUピンニング
- メモリを特定NUMAノードに割り当て
- トポロジーマネージャーとの連携
2. デバイスリソース管理:
- GPU割り当て最適化
- RDMAリソース管理
- デバイスプラグイン補完
3. セキュリティポリシー適用:
- 動的Seccompプロファイル
- ランタイムセキュリティルール注入
4. モニタリング/監査:
- コンテナ起動/停止イベントロギング
- リソース使用追跡
7. イメージサービス
7.1 イメージPull
CRI PullImage処理:
1. kubeletがPullImage(imageSpec, authConfig)を呼び出し
|
v
2. containerdがイメージ参照を解決
- タグまたはダイジェスト
- レジストリ認証情報を適用
|
v
3. イメージダウンロード
- Manifest、Config、Layers
- k8s.ioネームスペースに保存
|
v
4. レイヤーアンパッキング
- Snapshotterでスナップショットチェーン作成
|
v
5. イメージ参照(imageRef)を返す
7.2 イメージキャッシング
イメージキャッシング:
containerdのイメージキャッシング:
- Content Storeにレイヤーが既にある場合はダウンロードスキップ
- Snapshotterにスナップショットが既にある場合はアンパッキングスキップ
- ダイジェストベースの正確な重複排除
kubeletのイメージポリシー:
imagePullPolicy: Always
-> 常にレジストリマニフェストを確認(レイヤーはキャッシュ活用)
imagePullPolicy: IfNotPresent
-> ローカルにない場合のみPull
imagePullPolicy: Never
-> ローカルイメージのみ使用
8. モニタリングとデバッグ
8.1 CRIメトリクス
containerd CRI関連メトリクス:
container_runtime_cri_operations_total: CRI操作数
container_runtime_cri_operations_errors_total:CRI操作エラー数
container_runtime_cri_operations_latency_seconds:CRI操作レイテンシ
containerd内部メトリクス:
containerd_task_count: 実行中Task数
containerd_container_count: コンテナ数
containerd_image_pull_duration_seconds: イメージPull所要時間
8.2 デバッグツール
デバッグツール:
1. crictl(CRI CLI):
crictl ps # コンテナ一覧
crictl pods # Pod一覧
crictl images # イメージ一覧
crictl inspect CONTAINER_ID # コンテナ詳細
crictl logs CONTAINER_ID # コンテナログ
crictl exec -it CONTAINER_ID /bin/sh # exec
2. ctr(containerd CLI):
ctr -n k8s.io containers list
ctr -n k8s.io tasks list
ctr -n k8s.io images list
3. containerdログ:
journalctl -u containerd -f
9. まとめ
containerdのCRI実装はKubernetesとコンテナランタイム間の核心インターフェースです。Pod Sandboxを通じたPodレベルの分離、CRIリクエストからOCIスペックへの正確な変換、WebSocket/SPDYベースのストリーミング、RuntimeClassによるマルチランタイムサポート、NRIによる柔軟な拡張が主要な特徴です。この階層化された設計によりcontainerdはKubernetesの安定したコンテナランタイムとしての地位を確立しています。