Category
Zigでトイコンテナランタイム開発
ZigでrunCやyoukiのようなLinuxのコンテナランタイムを実装します。Zigは汎用のLinux syscallラッパーが若干弱いためライブラリ的に補完しています。
11件の記事
exec fifoでcreate/startを同期し、コンテナのライフサイクル管理(create/start/kill/state/delete)を実装する
OCI Runtime Spec準拠のcreate/start/kill/state/deleteサブコマンドを実装し、名前付きパイプ(exec fifo)でcreateとstartの実行タイミングを分離しました。実装の過程で、pivot_root後の空の/devによるstdio切り離し処理の失敗、clone(2)がファイルディスクリプタテーブルを複製することによる同期用パイプの検知漏れ、kill(2)のEPERM/ESRCHの取り違え、PID namespaceのinitプロセスに対するSIGTERMの無視、という4つのはまりどころに遭遇したため、それぞれの原因と対処をまとめています。
続きを読むpullしたlayerを展開してrootfs化し、OCI Runtime Spec形式のbundleを生成する
OCI Distribution APIでmanifestを取得する処理に続き、実際にlayerのblob(tar+gzip)をダウンロードして展開し、rootfsを構築する処理を実装しました。あわせてmanifest indexからdigestで解決したmanifest本体のハッシュ検証を追加し、pullしたimage configから、既存のrunコマンドがそのまま読み込めるOCI Runtime Spec形式のconfig.jsonを生成するところまで実装しています。Docker Hubからalpineイメージをpullし、生成したbundleをrunコマンドで実際に起動して動作確認しました。
続きを読むOCI Distribution APIで認証しmanifestを取得する——TLSは自作せずstd.http.Clientに任せる
config.jsonからのコンテナ起動に続き、OCI Distribution APIを使ってDocker Hubからイメージのmanifestを取得する処理を実装しました。これまでのフェーズと違い、HTTP(S)通信自体はstd.http.Clientに任せ、Bearerトークンによる認証チャレンジの解釈とmanifestのcontent negotiationという、OCI Distribution API固有のプロトコル部分を実装しています。実際にDocker Hubへ問い合わせて、hello-world・busyboxイメージのmanifest取得まで確認しました。
続きを読むOCI Runtime Specのバンドル形式に合わせてconfig.jsonからコンテナを起動する
capabilities/seccompに続き、これまで独自のCLI引数(rootfsのパスを直接指定)で起動していたコンテナを、OCI Runtime Specのバンドル形式(config.json + rootfsディレクトリ)から起動できるように変更しました。実行コマンド・環境変数・作業ディレクトリ・ホスト名・cgroupのリソース上限を、すべてconfig.jsonから読み取れるようにしています。std.jsonでの構造体パース時に気をつけた、パース元バッファの寿命に関する注意点もまとめます。
続きを読むcapabilitiesの削減とseccomp(BPF)によるsyscallフィルタでコンテナの権限を絞る
Network namespaceとvethに続き、capabilitiesとseccompによる権限制御を実装しました。capabilitiesはDocker/runcのデフォルト許可リストに合わせて14個まで絞り込み、seccompは自前で組み立てたBPF(Berkeley Packet Filter)プログラムでmount(2)だけを明示的に拒否します。std.os.linuxにはcapset/capget・seccomp()・関連する構造体や定数がほぼ揃っており、自分で定義する必要があったのはBPF命令を表すsock_filter/sock_fprogとオペコード定数だけでした。
続きを読む自前netlinkクライアントでNetwork namespaceとvethペアを構築する——RTM_SETLINKは通るのに/sys/class/netには見えない現象
cgroup v2に続き、Network namespaceとvethペアによるホスト・コンテナ間の通信を実装しました。vethの作成やnetns間の移動にはrtnetlink(netlinkプロトコル)が標準的な手段で、std.os.linuxにはnlmsghdr等のデータ構造はあるもののクライアント実装はないため、TLV形式のメッセージを自前で組み立てるnetlinkクライアントを実装しています。実装の過程で、RTM_SETLINKでは名前解決できたインターフェースが/sys/class/net経由では見えないという現象に遭遇したので、その対処もあわせてまとめます。
続きを読むcgroup v2でCPU・メモリ使用量を制限する——syscallではなくファイル操作だけで実現する仕組み
PID/UTS/IPC/Mount namespaceに続き、cgroup v2を使ってコンテナのCPU・メモリ使用量を制限する実装を行いました。cgroup v2はnamespaceと違って専用のsyscallを持たず、疑似ファイルシステム(cgroupfs)へのディレクトリ作成とファイル書き込みだけで操作できる点が特徴です。実際にmemory.max/cpu.maxへ書き込んだ値がcgroupfs側にそのまま反映されていることを確認します。
続きを読むMount namespaceと`pivot_root`でrootfsを差し替える——busyboxの`--install`が生成する絶対パスシンボリックリンクが壊れる理由
busyboxで最小限のrootfsを用意し、Mount namespace(CLONE_NEWNS)とpivot_root(2)によって、コンテナ専用のルートファイルシステムに切り替える実装を行いました。busybox --installが作るシンボリックリンクがpivot_root後に壊れる問題と、その原因になったコード上のミスについてもまとめます。
続きを読むclone(2)にnamespaceフラグをまとめて渡し、PID/UTS/IPCを分離する——同じプロセスなのに親と子で見える世界が違う理由
fork(2)では実現できないnamespaceの分離を、clone(2)にCLONE_NEWPID/CLONE_NEWUTS/CLONE_NEWIPCフラグをまとめて渡すことで実装します。同じ子プロセスでも、親から見える値と、新しいnamespace内で自分自身が見る値(PID・ホスト名・IPCオブジェクトの有無)が異なることを、実際の動作確認で確かめます。
続きを読むstd.os.linuxの薄いラッパーを自作する——sethostname(2)に高レベル関数が存在しない問題
Zigでコンテナランタイムを開発するにあたり、std.os.linuxを直接使う代わりに自前のsyscallラッパーモジュールを用意しました。syscallごとに高レベル関数の有無がばらつくというstd.os.linuxの現状と、errnoをZigのエラーユニオン型に変換する設計方針についてまとめます。
続きを読むZigでトイコンテナランタイム開発 - 用語集
本カテゴリの記事に登場した専門用語を、Linuxネームスペース・プロセス生成/syscall・マウント/ファイルシステム・cgroup・ネットワーク・セキュリティ/権限・OCI仕様・開発環境の8カテゴリに分けて整理します。記事の追加にあわせて随時更新します。
続きを読む