https://github.com/containers/youki/tree/f1acccb65452252556713683557e587379399246 https://github.com/opencontainers/runc/tree/v1.0.2
詳説 OCIコンテナランタイムyouki 第15回 コンテナ技術の情報交換会 うたもく(@utam0k) 1
View Slide
自己紹介 ● うたもく(@utam0k) ● 普段はWeb企業のバックエンドエンジニア(Scala) ● 2021年からコンテナ真面目に入門 ● containersのメンバー 2
知ってもらいたいこと 3youkiの面白いポイント ● Rustとコンテナランタイムってどうなの? ● runCとの違いは? ● 処理の流れは? コンテナを作るまでの流れがコードレベルでわかる ● youkiの詳細な処理の流れ ● runCの処理の流れ
目次 1. youkiの紹介 2. youkiの大まかな処理の流れ 3. コンテナランタイムのコードを追っていく! 4
目次 1. youkiの紹介 2. youkiの大まかな処理の流れ 3. コンテナランタイムのコードを追っていく! 5
● Rust製の低レベルコンテナランタイム ● podmanなどを開発しているcontainersで開発中 ○ https://github.com/containers/youki● うたもくを中心にリリースに向けて開発を進めている ● githubで⭐数が2.2K ● youki = 容器 = コンテナ youkiとは? 6
Kubelet(K8s) Linuxなど High-LevelRuntime CRI Low-LevelRuntime OCI 7CRI − Container Runtime Interface OCI − Open Container Initiative Container Runtime
Kubelet(K8s) Linuxなど High-LevelRuntime CRI Low-LevelRuntime OCI ● runc ● youki ● crun 8
youkiの存在意義 ● コンテナランタイムの多様性 ● Rustコンテナ界隈でのライブラリへの貢献 ○ https://github.com/containers/oci-spec-rs● 既存の低レイヤのソフトウェアをRustで書き換える一例 9
目次 1. youkiの紹介 2. youkiの大まかな処理の流れ 3. コンテナランタイムのコードを追っていく! 10
コンテナ技術の軽い復習 ● コンテナを支える技術 ○ chrootpace - ルートディレクトリを変更 ○ namespace - 操作可能なリソースの隔離 ○ cgroup ace - 使用可能なリソースの設定 ● コンテナ界隈を支える仕様 ○ CRI - Container Runtime Interface ○ OCI - Open Container Initiative 11
● コンテナのプロセスができるまでには double-fork が必要 ○ 1st fork ■ 中間プロセス ■ initプロセスを作るための間のプロセス ○ 2nd fork ■ initプロセス ■ 実際にコンテナのinitプロセスになる ○ 理由は後ほど...
● $ docker create --name test busybox hostname ● 高レベルランタイムからコンテナを作る命令を受付 ○ 命令を受け付ける...? ■ 単なるコマンド ■ $ youki create -b $(bundle_path) $(container_name) ● fork(2) - Intermediate Processを生成する
● unshare(CLONE_NEWUSER) - ユーザー名前空間を分離 ○ 新しい名前空間で特権ユーザーになれる ● host ↔ containerのユーザーとグループのマッピング ○ 例) host(uid 1000) ↔ container(uid 0) 中間create
● 中間プロセスでPID名前空間だけを先に分離 ○ PID名前空間は発動した次のプロセスから適用されるため ● fork(2) - 中間プロセスからinitプロセスを生成 ● initプロセスで残りの名前空間を分離 ○ unshare(2)を発動した瞬間から分離される init中間
なぜdouble-forkが必要なのか? ● PID名前空間の分離は発動した次のプロセスから適用される○ fork回数 += 1● PID名前空間の分離にはCAP_SYS_ADMINが必要○ ホストが特権ユーザーではない場合は...?○ ユーザー名前空間の分離(一般ユーザーで実行可能)■ 中間Process内で特権ユーザーになる ■ fork回数 += 1 ● ユーザー名前空間 → PID名前空間 → 残りの名前空間21
● 諸々コンテナになるのに必要な処理をinitプロセスに施す ○ pivot_root(2) - `/` の切り替え ○ setup capability - HCRから受け取ったケーパビリティにする ● 諸々準備が終わったことを最初のプロセス(youki create)に通知 ● initプロセスはstartのシグナルが来るまで「待て」 create 中間 init
● cgroupを諸々適用する ● 受け取ったinitプロセスのPIDをpidファイルに書き込む ○ HCRはこのpidファイルのPIDを元に色々管理していたりするっぽい create 中間
● initプロセスはデーモンプロセスとなり、コンテナのエントリポイントを実行するのを待つ ● 高レベルコンテナランタイムからスタートの合図 ○ $ docker start test init
処理の流れのまとめ ● プロセスに対して細かく色々している ○ 名前空間の分離 ○ pivot_root ○ capability ○ cgroups ● double-forkが必要 28
処理の流れのまとめ ● プロセスに対して細かく色々している ○ 名前空間の分離 ○ pivot_root ○ capability ○ cgroups ● double-forkが必要 29Rust 得意分野っぽい!
目次 1. youkiの紹介 2. youkiの大まかな処理の流れ 3. コンテナランタイムのコードを追っていく! 30
Let’s dive into youki
● 流れ自体は大きく変わらない ● 使用言語 ○ runC - GoとCのハイブリッド ○ youki - Rust(一部FFI) runCではどうなっているのか 32
GoとCのハイブリッドだと...? ● Goの言語ランタイムの制約 ○ fork(2) / clone(2) が難しい ■ おそらくGoroutineの関係 ○ 言語ランタイムがマルチスレッド ● setns(2) 😢A multithreaded process may not change usernamespace with setns().
● Goで扱えない部分はCで扱う ○ サブコマンドのinitがCを扱う ○ 言語ランタイムが起動する前にCの処理を呼び出す GoとCのハイブリッドだと...?
● 大雑把にrunCがコンテナを作る流れ 1. runC create(Golang) ● Goでも可能な処理を行いinitサブコマンドを実行 2. runC init(Golang & C) ● Goの言語ランタイムが起動する前にCの処理(double-fork)を行う 3. double-fork(C言語) ● fork(2)、setns(2)、uid/gidのマッピングなどなど... GoとCのハイブリッドだと...?
Let’s dive into runC
runCのコード読み ● startContainer() - create.go ○ createContainer() ■ loadFactory() ● libcontainer.New() ○ /proc/self/exec init(= runc init)をコマンドとしてcontainer にセットしている ○ runner.run() ■ container をスタートする
コードリーディングのまとめ ● runCはGoとCのハイブリッド ○ マルチスレッドでは処理ができない部分があるのでCを使う ○ createサブコマンド(Go) → initサブコマンド(C) ● youki ○ Rustのみでよりシンプルにコードがかける
Thanks to all the people whoalready contributed to youki :) 39
Thanks you! Any questions? 40