Skip to content
Published on

[containerd] CRI実装:Kubernetesランタイム統合

Authors

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の安定したコンテナランタイムとしての地位を確立しています。