containerdネットワーキングとストレージ
containerdはネットワーキングとストレージを直接実装せず、標準インターフェースを通じて外部プラグインと統合します。この記事ではCNIによるネットワーク構成、ネームスペース管理、ボリュームマウント、デバイスアクセス、セキュリティモジュール統合を分析します。
1. CNI統合
1.1 CNI概要
Container Network Interface(CNI)はコンテナネットワーキングの標準インターフェースです。containerdはCNIプラグインを呼び出してネットワークを構成します。
CNI呼び出しフロー:
kubelet -> containerd(CRI RunPodSandbox)
|
v
ネットワークネームスペース作成
|
v
CNIプラグイン呼び出し
(ADDコマンド)
|
v
IP割り当て、ルーティング設定、インターフェース作成
|
v
結果をcontainerdに返す
1.2 CNI設定
CNI設定ファイル位置:
設定ディレクトリ:/etc/cni/net.d/
バイナリディレクトリ:/opt/cni/bin/
containerd CNI設定(config.toml):
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
max_conf_num = 1
1.3 CNIプラグインチェーン
CNI設定例(10-calico.conflist):
ネットワーク構成はプラグインチェーンで定義:
1. メインプラグイン(calico、cilium、flannelなど):
- ネットワークインターフェース作成
- IP割り当て(IPAM)
- ルーティングルール設定
2. メタプラグイン(bandwidth、portmapなど):
- 帯域幅制限
- ポートマッピング
- ファイアウォールルール
実行順序:
ADD:メイン -> メタプラグイン(順方向)
DEL:メタ -> メインプラグイン(逆方向)
1.4 CNI呼び出し詳細
CNI ADD実行詳細:
1. containerdがネットワークネームスペースパスを決定
/var/run/netns/cni-abc123
2. CNI環境変数設定:
CNI_COMMAND=ADD
CNI_CONTAINERID=abc123
CNI_NETNS=/var/run/netns/cni-abc123
CNI_IFNAME=eth0
CNI_PATH=/opt/cni/bin
3. CNIプラグインバイナリ実行
stdinで設定JSONを渡す
4. プラグインがstdoutで結果を返す:
- 割り当てられたIPアドレス
- ゲートウェイアドレス
- DNS設定
- ルーティング情報
5. containerdが結果を保存
2. ネットワークネームスペース
2.1 ネームスペース作成
Podネットワークネームスペース:
Pod Sandbox作成時:
1. unshare(CLONE_NEWNET)で新しいネットワークネームスペース作成
2. /var/run/netns/にバインドマウントで永続化
3. そのネームスペースでCNIプラグイン実行
4. PodのすべてのコンテナがこのネームスペースをShare
ネームスペース共有:
PauseコンテナがネットワークネームスペースをProperty
Appコンテナが同じネームスペースに参加
-> Pod内コンテナがlocalhostで通信可能
2.2 ネームスペースクリーンアップ
ネームスペースクリーンアップ:
Pod削除時:
1. CNI DELコマンドでネットワークリソース解放
- IPアドレス返却
- インターフェース削除
- ルーティングルール削除
2. /var/run/netns/のバインドマウント解除
3. ネットワークネームスペース自動削除
3. ボリュームマウント
3.1 マウントタイプ
containerdはOCIスペックのマウント構成を通じてボリュームを管理します:
マウントタイプ:
1. bindマウント:
- ホストファイル/ディレクトリをコンテナにマウント
- ホストとコンテナが同一データを共有
- ConfigMap、Secret、emptyDirなどに使用
2. tmpfsマウント:
- メモリベースのファイルシステム
- コンテナ終了時にデータ消滅
- /dev/shm、/runなどに使用
3. 特殊ファイルシステム:
- proc:/proc
- sysfs:/sys
- cgroup:/sys/fs/cgroup
- devpts:/dev/pts
3.2 マウント伝播
マウント伝播(Propagation)オプション:
1. private:
- マウントイベント伝播なし
- デフォルト
2. rprivate:
- 再帰的private
3. shared:
- マウントイベントを双方向伝播
- ホストでマウント -> コンテナでも見える
- コンテナでマウント -> ホストでも見える
4. rshared:
- 再帰的shared
5. slave:
- ホスト -> コンテナ片方向伝播
- ボリュームプラグインに有用
6. rslave:
- 再帰的slave
Kubernetesでの使用:
- MountPropagationフィールドで制御
- CSIドライバーは主にBidirectional(shared)を使用
3.3 CRIボリューム処理
CRIによるボリューム処理:
kubeletがOCIスペックにマウントを追加:
1. emptyDir:
- kubeletがホストにディレクトリ作成
- bindマウントでコンテナに渡す
2. hostPath:
- ホストパスを直接bindマウント
3. ConfigMap/Secret:
- kubeletがtmpfsにデータ作成
- bindマウントでコンテナに渡す
4. PersistentVolumeClaim:
- kubeletがCSIドライバーでボリュームマウント
- マウントされたパスをbindマウントで渡す
containerdの役割:
- kubeletが準備したマウント情報をOCIスペックに反映
- runcが実際のマウントを実行
4. デバイスアクセス
4.1 デバイスマッピング
デバイスアクセスメカニズム:
OCIスペックのdevicesセクション:
linux:
devices:
- path: "/dev/nvidia0"
type: "c"
major: 195
minor: 0
fileMode: 438
uid: 0
gid: 0
cgroupデバイスアクセス制御:
linux:
resources:
devices:
- allow: true
type: "c"
major: 195
access: "rwm"
4.2 GPUサポート
GPUアクセス(NVIDIA):
NVIDIA Container Toolkit統合:
1. nvidia-container-runtime-hook:
- OCIランタイムフックとして動作
- コンテナ起動前に実行
- NVIDIAドライバーライブラリをコンテナにマウント
- GPUデバイスノードをコンテナに追加
2. CDI(Container Device Interface):
- デバイスベンダー中立的標準
- /etc/cdi/にデバイススペック定義
- containerdがCDIスペックを読んでOCIスペックに反映
CDIスペック例:
cdiVersion: "0.5.0"
kind: "nvidia.com/gpu"
devices:
- name: "0"
containerEdits:
deviceNodes:
- path: "/dev/nvidia0"
mounts:
- hostPath: "/usr/lib/x86_64-linux-gnu/libnvidia-ml.so"
containerPath: "/usr/lib/x86_64-linux-gnu/libnvidia-ml.so"
4.3 その他のデバイス
その他のデバイスアクセス:
1. FPGA:
- CDIスペックでFPGAデバイスを公開
- ベンダー別デバイスプラグイン
2. InfiniBand/RDMA:
- /dev/infiniband/*デバイスマッピング
- ネットワークデバイスネームスペース共有
3. シリアル/USB:
- ホストデバイス直接マッピング
- privilegedモードまたは明示的デバイス許可
5. SELinux統合
5.1 SELinuxコンテキスト
SELinuxコンテナセキュリティ:
OCIスペックのSELinux設定:
linux:
mountLabel: "system_u:object_r:container_file_t:s0:c1,c2"
processLabel: "system_u:system_r:container_t:s0:c1,c2"
構成要素:
- user:system_u
- role:system_r(プロセス)/ object_r(ファイル)
- type:container_t(プロセス)/ container_file_t(ファイル)
- level:s0:c1,c2(MCSカテゴリ)
MCS(Multi-Category Security):
- 各コンテナに一意のカテゴリを割り当て
- 他のコンテナのファイルにアクセス不可
- ホストとコンテナ間の分離
5.2 SELinux処理フロー
SELinux適用:
1. kubeletがPodのSELinuxオプションを決定
- securityContext.seLinuxOptions
- 自動MCSラベル割り当て
2. CRIを通じてcontainerdに伝達
- processLabel:プロセスセキュリティコンテキスト
- mountLabel:ファイルセキュリティコンテキスト
3. containerdがOCIスペックに反映
4. runcが実行時に:
- プロセスにSELinuxラベル適用
- rootfsにSELinuxラベル適用
- マウントにSELinuxラベル適用
6. AppArmor統合
6.1 AppArmorプロファイル
AppArmorコンテナセキュリティ:
デフォルトプロファイル:cri-containerd.apparmor.d
主要ルール:
- ファイルシステムアクセス制限
deny /proc/kcore r,
deny /sys/firmware/** r,
- ネットワークアクセス制御
- 能力(capability)制限
- マウント操作制限
プロファイル適用:
OCIスペック:
process:
apparmorProfile: "cri-containerd.apparmor.d"
6.2 カスタムプロファイル
カスタムAppArmorプロファイル:
1. ホストにプロファイルインストール:
/etc/apparmor.d/にプロファイルファイル配置
apparmor_parser -r /etc/apparmor.d/my-profile
2. Podで指定:
annotations:
container.apparmor.security.beta.kubernetes.io/app: localhost/my-profile
3. containerdがOCIスペックに反映:
process:
apparmorProfile: "my-profile"
7. Seccomp統合
7.1 Seccompプロファイル
Seccomp(Secure Computing):
許可/ブロックするシステムコールを定義:
デフォルトアクション:SCMP_ACT_ERRNO(拒否)
許可システムコール例:
- read、write、open、close
- mmap、mprotect、munmap
- socket、connect、accept
- ...
ブロックシステムコール例:
- mount、umount(コンテナ脱出防止)
- reboot
- kexec_load
- ptrace(一部環境)
7.2 Seccomp適用
Seccompプロファイル適用:
1. Kubernetes SecurityContext:
securityContext:
seccompProfile:
type: RuntimeDefault
2. RuntimeDefaultプロファイル:
- containerd/runcデフォルトSeccompプロファイル
- 危険なシステムコールをブロック
- 大半のワークロードに適合
3. カスタムプロファイル:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: "profiles/my-seccomp.json"
8. ノードで直接確認する
ノードで使う道具は二つあり、層が違います。crictlはCRI APIを呼ぶのでkubeletが見ているのと同じビューを返し、ctrはcontainerdのネイティブAPIを呼ぶので、スナップショットキーやランタイムタイプのようにCRIには存在しないフィールドまで見せてくれます。
8.1 crictlの接続設定
crictlを初めて使うとソケットが見つからず数秒止まるのが普通です。cri-toolsのドキュメントは、デフォルトの接続試行がタイムアウトするまで時間がかかるためエンドポイントを明示するよう勧めています。設定ファイルに書いておくか、CONTAINER_RUNTIME_ENDPOINTとIMAGE_SERVICE_ENDPOINT環境変数で指定します。
# /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
8.2 サンドボックスを探しネットワークネームスペースに入る
Podのネットワークはアプリコンテナではなくサンドボックスが持っているので、必ずサンドボックスから探します。
crictl pods
crictl pods --name nginx-65899c769f-wv2gp
crictl inspectp 4dccb216c4adb
crictl podsはPOD ID、CREATED、STATE、NAME、NAMESPACE、ATTEMPTの六つのカラムを出力します。STATEがReadyでなければその下のコンテナを見る必要はありません。サンドボックスが立っていないという意味であり、原因はほぼ常にCNIです。ATTEMPTが0でなければkubeletが同じPodのサンドボックスをすでに何度も作り直したという意味なので、その数字自体が繰り返し失敗のシグナルです。
crictl inspectpが返すのはCRIのPodSandboxStatusResponseです。status配下にはid、metadata、state、created_at、network、linux、labels、annotations、runtime_handlerが入り、networkの中にipとadditional_ipsが入っています。ここでipが空ならCNI ADDが成功しなかったか、その結果が保存されなかったかです。runtime_handlerはこのサンドボックスがどのランタイムハンドラで立ち上がったかを教えてくれるので、RuntimeClassを使うクラスタで意図したランタイムに行ったかをここで確認します。
レスポンスの残り半分であるinfoはマップ型で、CRIの定義はキーが任意の文字列であり値がJSON形式であるべきとだけ規定しています。中身はランタイムが決め、containerdはランタイムスペックとサンドボックス設定をここに入れます。ネットワークネームスペースのパスもこの中に載ってきますが、キー名と入れ子構造はバージョンによって変わるため、正確なフィールドは使用中のバージョンのドキュメントで確認してください。
パスが分かったら中に入って実際のインターフェースを見ます。
ip netns list
ip netns exec cni-1f2e3d4c-5a6b-7c8d-9e0f-a1b2c3d4e5f6 ip addr
ip netns exec cni-1f2e3d4c-5a6b-7c8d-9e0f-a1b2c3d4e5f6 ip route
ip addrで見るのは二つです。eth0が存在するか、そこにPod CIDR範囲のアドレスが付いているか。eth0がなければCNIメインプラグインがインターフェースを作れなかったのであり、インターフェースはあるがアドレスがなければIPAMの段階で終わったのです。この区別ひとつで原因がプラグイン側かIPAM側かが分かれます。ip routeではデフォルト経路がCNIの渡したゲートウェイを指しているかを見ます。空なら外に出ていくトラフィックが全部死にます。
netnsファイルはすでに消えているがプロセスは生きている場合、PIDで入る方法だけが通ります。
nsenter -t 12345 -n ip addr
lsns -t net
8.3 CNI設定が実際にロードされたか
CNI設定ファイルがディスクにあることと、containerdがそれを読んだことは別の話です。containerdのCRI Statusレスポンスには RuntimeReady と NetworkReady の二つの条件があり、ネットワークが準備できていないときのreasonはNetworkPluginNotReady、メッセージはNetwork plugin returns errorの後ろに実際の原因が付く形式です。レスポンスのinfoマップにはconfig、golang、cniconfig、lastCNILoadStatus、そしてネットワーク名の付いたlastCNILoadStatusキーが載ってきます。
crictl info
cniconfigを見ればcontainerdが実際にパースして保持しているconflistが出てきます。ディスクにあるファイルとこの内容が違うなら、ファイルを直した後にcontainerdが読み直していないか、JSONが壊れてパースに失敗しています。lastCNILoadStatusにエラーが残っていれば、その文字列が事実上の答えです。
8.4 コンテナが受け取ったスナップショットとマウント
ストレージ側は逆にアプリコンテナを見ます。
crictl ps
crictl ps -a
crictl inspect 1f73f2d81bf98
ctr -n k8s.io containers list
ctr -n k8s.io containers info 1f73f2d81bf98
crictl psはCONTAINER ID、IMAGE、CREATED、STATE、NAME、ATTEMPTを出力し、-aを付けると終了したコンテナまで出ます。ctrはネームスペースを必ず指定しなければなりません。CRIプラグインはk8s.io containerdネームスペースを使うので、フラグを外すと何も出ず、対象がないという誤った結論を出してしまいます。ctr containers listはCONTAINER、IMAGE、RUNTIMEの三カラムを出力し、RUNTIMEカラムがどのshimで立ち上がったかを教えるのでRuntimeClassの問題もここで捕まえられます。
ctr containers infoはコンテナレコード全体をJSONで吐きます。ここにスナップショッター名とスナップショットキーが入っており、specフラグを付けるとOCIランタイムスペックだけを見られます。このコンテナにSELinuxラベルが付いたか、このマウントの伝播オプションは何かといった問いは、mounts配列とlinux配下のdevices、mountLabel、processLabelから読めば済みます。使えるスナップショッターの一覧はプラグイン一覧で確認します。
ホスト側のマウントはfindmntが最も速いです。伝播属性まで一緒に出してくれるので、OCIスペックに書かれたものとカーネルが実際に作ったもののズレを目で確認できます。
ctr plugins ls
findmnt -o TARGET,SOURCE,FSTYPE,PROPAGATION
findmnt -T /var/lib/kubelet/pods
9. config.tomlのバージョン2とバージョン3
どこかで拾ったスニペットをconfig.tomlに貼ったのに何の効果もない、ということがよくあります。多くはセクションヘッダがcontainerdのメジャーバージョンによって丸ごと変わるためです。バージョン2は1.3で導入され、CRI設定が全部ひとつのセクションにまとまっています。バージョン3は2.0で導入され、そのセクションをランタイムとイメージの二つに割りました。バージョン2のファイルは2.xでもサポートされ自動的にバージョン3へ変換されますが、バージョン1は2.0からサポートされません。
# containerd 1.x — config version 2
version = 2
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
max_conf_num = 1
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
# containerd 2.x — config version 3
version = 3
[plugins.'io.containerd.cri.v1.runtime'.cni]
bin_dir = '/opt/cni/bin'
conf_dir = '/etc/cni/net.d'
max_conf_num = 1
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins.'io.containerd.cri.v1.images']
snapshotter = 'overlayfs'
ヘッダを手で組み立てず、デフォルト設定を出力して始めてください。このコマンドは今インストールされているcontainerdバイナリが実際に理解するスキーマをそのまま出すので、バージョンを間違える余地がありません。
containerd config default > /etc/containerd/config.toml
systemctl daemon-reload
systemctl restart containerd
CNI側のキーの意味はドキュメントにこう書かれています。conf_dirは管理者がCNI confを置くディレクトリ、bin_dirはプラグインバイナリが置かれるディレクトリで、bin_dirは2.1でdeprecatedと表示されbin_dirsに置き換わりました。max_conf_numはCNI設定ディレクトリから読み込む設定ファイルの最大数で、デフォルトは1です。conf_templateはCNI設定を生成するgolangテンプレートファイルのパスで、設定されるとcontainerdがテンプレートからCNI設定ファイルを生成します。ip_prefはPodの代表IPを選ぶ戦略で、デフォルトは最初のipv4アドレスです。
スナップショッターのデフォルトはoverlayfsで、ドキュメントはこれをDockerのoverlay2ストレージドライバに相当すると説明します。SystemdCgroupのデフォルトはバージョン2でもバージョン3でもfalseですが、systemdベースのホストではtrueが推奨されます。デフォルトがfalseである点が重要で、kubeletだけsystemdに変えてcontainerdをそのままにすると、二つの管理者が異なるcgroupビューを持つことになるからです。
10. ウォークスルー: ContainerCreatingで止まったPod
PodがContainerCreatingから出ず、イベントにCNI関連のエラーが出ています。順序を守れば大抵は数分で終わり、守らなければconfig.tomlから読み始めて一時間を使います。
第一に、kubeletのイベントを読みます。ここに出た文字列が以降の調査の方向を決めます。containerdがネットワークプラグインは準備できていないと報告するとき、reasonはNetworkPluginNotReadyでメッセージはNetwork plugin returns errorで始まります。この文字列が見えたら個別のPodの問題ではなくノード全体のCNIが死んでいるので、同じノードのPodを併せて見れば影響範囲が即座に分かれます。
kubectl describe pod my-app-7d9f6c8b4-xk2wp
kubectl get pods -A -o wide --field-selector spec.nodeName=node-3
第二に、サンドボックスがそもそも作られなかったのか、作られたがIPがないのかを分けます。一覧になければRunPodSandbox自体が失敗したのであり、一覧にあってnetwork配下のipが空ならCNI ADDが失敗したのです。前者はランタイムの問題かもしれず、後者はほぼ確実にネットワークの問題です。
crictl pods --name my-app-7d9f6c8b4-xk2wp
crictl inspectp 4dccb216c4adb
第三に、containerdがCNI設定をロードしたかを見ます。8.3節のcniconfigとlastCNILoadStatusを読み、空ならディスクのconf_dirを確認します。ファイルが確かにあるのに空ならJSONが壊れているので、自分でパースさせてみます。conflistを手で直していてカンマをひとつ残した場合がここで捕まります。
crictl info
ls -l /etc/cni/net.d/
python3 -m json.tool /etc/cni/net.d/10-calico.conflist > /dev/null
第四に、conflistが参照するプラグインバイナリがbin_dirにあるかを見ます。plugins配列のtype値がそのまま実行されるバイナリ名であり、ひとつでも欠けるとCNIの実行自体ができません。ノードイメージを替えたりCNIをアップグレードした直後にこの形で壊れます。bandwidthやportmapのようなメタプラグインは別のパッケージから来るため、静かに抜けやすいです。
ls -l /opt/cni/bin/
grep -o '"type"[^,]*' /etc/cni/net.d/10-calico.conflist
第五に、ここまで全部正常ならIPAMの枯渇を疑います。ノードに割り当てられた帯域を使い切るとCNI ADDは呼ばれるがアドレスを受け取れず失敗します。症状が特定のノードで、Podを新しく立てるときだけ出るのでノード単位で固まって見えます。確認方法はCNI実装ごとに違うので、使っているプラグインのIPAMストアを見る必要があります。
第六に、それでも解けなければ死んだネームスペースが残っていないかを見ます。kubeletやノードが異常終了するとCNI DELが呼ばれないままバインドマウントが残り、IPAMから見るとそのアドレスは使用中のままです。ネームスペース数とReadyなサンドボックス数を比べればギャップがそのまま出ます。
ip netns list | wc -l
crictl pods
11. 失敗事例と診断の順序
11.1 kubeletクラッシュ後に残ったネットワークネームスペース
症状は特定のノードでだけ新しいPodがIPを受け取れないことです。ノードを再起動すると消え、数日後にまた現れます。診断はネームスペース数とサンドボックス数の比較から始まります。ギャップがあればCNI DELが呼ばれないまま消えたサンドボックスがあり、その分のIPが回収されていないので、次はノードのジャーナルからkubeletやcontainerdがクリーンアップ経路を通らずに死んだ時点を探します。手で片づけないでください。生きているPodのネームスペースを誤って消すとネットワークが即座に切れます。ノードを空にして入れ替える方が速く安全です。
11.2 max_conf_numが違うconflistを選ぶ場合
症状はCNIを入れ替えたのに以前のネットワークに繋がる、あるいはconflistを新しく置いたのに反映されないことです。原因はmax_conf_numのデフォルト値1です。conf_dirにファイルが複数あってもひとつしかロードされず、名前順が勝敗を決めるので、インストーラが残した古いファイルが先に来ればそのまま勝ちます。診断は8.3節のcniconfigがどのファイルの内容かを確認するところで終わります。正解はディレクトリを掃除することであり、max_conf_numを増やすことではありません。
11.3 マウント伝播がprivateでCSIドライバーが壊れる場合
症状はCSIドライバーPodは正常なのに、そのドライバーを使うワークロードでボリュームが空に見えることです。ドライバーはマウントしたと報告するのに、アプリコンテナの中には何もありません。3.2節のデフォルトprivateが原因で、ドライバーコンテナの中で新しく作ったマウントがホストへ上がれず、他のコンテナへも下りていけません。この値を決めるのはMountPropagationフィールドで、CSIドライバーは主にBidirectionalを使います。診断はホストでfindmntを使って伝播属性を読むことであり、アプリではなくドライバーPodのマニフェストを見る必要がある点が罠です。
11.4 SELinuxのMCS拒否が権限エラーに見える場合
症状はコンテナの中でファイルを開くとpermission deniedが出ることです。ファイルの所有者とモードを何度確認しても正しいです。5.1節のMCSが原因で、ラベルの異なるコンテナが作ったファイルはモードがどれだけ開いていても拒否されます。hostPathを二つのPodで共有するとき、再起動でカテゴリが変わったときに特によく出ます。決定的な手がかりはホストの監査ログです。ラベルが原因なら拒否がホスト側に記録され、そうでなければ何も残らないので、この一回の確認で権限の問題かラベルの問題かが分かれます。
11.5 seccomp RuntimeDefaultが生む説明のつかないEPERM
症状は特定のライブラリやランタイムがコンテナの中でだけ失敗することです。エラーはEPERMなのに、どの権限も足りていないようには見えません。7.1節のデフォルト動作がSCMP_ACT_ERRNO、つまり拒否である点が答えです。ブロックされたコールはカーネルに届かずエラーで返ってくるのですが、アプリケーションはこれを権限の問題として報告するのでログだけでは区別できません。診断はプロファイルを一時的にUnconfinedへ変えて症状が消えるかを見ることであり、消えるならどのコールがブロックされたかを探してカスタムプロファイルでそれだけを許可します。Unconfinedは診断の道具であって解決策ではありません。
12. この層を直接触らない場合
第一に、この記事に出てきた動作の大半はcontainerdではなくCNIプラグインとkubeletの仕事です。IPを渡すのも、ルーティングを入れるのも、ネットワークポリシーを掛けるのも全部プラグインです。PodにIPが付かないという問題の解決策はほぼ常にCalicoやCilium側の設定であって、containerd側ではありません。
第二に、マネージドノードグループでconfig.tomlを手で直すと元に戻ります。ノードを交換可能な資源として扱う環境では、直接入れた変更は次のスケールアウトやアップグレードで消えます。生き残る変更はノードイメージ、ブートストラップスクリプト、あるいはそのプラットフォームが提供するノード設定の仕組みを通したものだけです。ノードに入って直したものは診断であって、デプロイではありません。
第三に、この層を読むことと書くことは違います。crictlとctrで状態を読むのは安全ですが、コンテナやサンドボックスをこれらの道具で消すのはkubeletの知らないところで世界を変える行為であり、kubeletが自分の知る状態とのズレに気づいて予期しない復旧を始めることがあります。コンテナを消したいならkubectl側からPodを削除してください。
第四に、標準のプラグイン組み合わせで足りるのにconf_templateを使い始めると、デバッグの難易度が一段上がります。テンプレートはノードごとに異なる設定を生成する必要があるときのための道具であり、その要求がないなら静的なconflistひとつの方が良いです。問題が起きたときにファイルを開いて読めば終わるからです。
13. 参考資料
- CRIプラグイン設定: https://github.com/containerd/containerd/blob/main/docs/cri/config.md (2026-08-16 確認)
- Getting Started: https://github.com/containerd/containerd/blob/main/docs/getting-started.md (2026-08-16 確認)
- スナップショッター一覧: https://github.com/containerd/containerd/blob/main/docs/snapshotters/README.md (2026-08-16 確認)
- crictlドキュメント: https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md (2026-08-16 確認)
- crictlでノードをデバッグ: https://kubernetes.io/docs/tasks/debug/debug-cluster/crictl/ (2026-08-16 確認)
- コンテナランタイムの設定: https://kubernetes.io/docs/setup/production-environment/container-runtimes/ (2026-08-16 確認)
- CRI API定義: https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1/api.proto (2026-08-16 確認)
14. まとめ
containerdのネットワーキングとストレージは標準インターフェースによる委譲モデルに従います。CNIによるネットワーク構成、OCIスペックによるマウント管理、CDIによるデバイスアクセス、SELinux/AppArmor/Seccompによるセキュリティ分離が核心です。この標準ベースの設計によりcontainerdは多様なネットワーキングソリューションとセキュリティモジュールを柔軟に統合します。