Upgrade to Pro — share decks privately, control downloads, hide ads and more …

詳説 OCIコンテナランタイム youki@第15回 コンテナ技術の情報交換会

詳説 OCIコンテナランタイム youki@第15回 コンテナ技術の情報交換会

D0acab281cd715f03af84f51ebd71faa?s=128

うたもく

October 09, 2021
Tweet

Transcript

  1. 詳説 OCIコンテナランタイム youki
 第15回 コンテナ技術の情報交換会
 うたもく(@utam0k)
 1

  2. 自己紹介
 • うたもく(@utam0k)
 • 普段はWeb企業のバックエンドエンジニア(Scala)
 • 2021年からコンテナ真面目に入門
 • containersのメンバー
 2

  3. 知ってもらいたいこと
 3 youkiの面白いポイント
 • Rustとコンテナランタイムってどうなの?
 • runCとの違いは?
 • 処理の流れは?
 


    コンテナを作るまでの流れがコードレベルでわかる
 • youkiの詳細な処理の流れ
 • runCの処理の流れ
 

  4. 目次
 1. youkiの紹介
 2. youkiの大まかな処理の流れ
 3. コンテナランタイムのコードを追っていく!
 4

  5. 目次
 1. youkiの紹介
 2. youkiの大まかな処理の流れ
 3. コンテナランタイムのコードを追っていく!
 5

  6. • Rust製の低レベルコンテナランタイム
 • podmanなどを開発しているcontainersで開発中
 ◦ https://github.com/containers/youki • うたもくを中心にリリースに向けて開発を進めている
 • githubで⭐数が2.2K


    • youki = 容器 = コンテナ
 youkiとは?
 6
  7. Kubelet(K8s)
 Linuxなど
 High-Level Runtime
 CRI
 Low-Level Runtime
 OCI
 7 CRI

    − Container Runtime Interface
 OCI − Open Container Initiative
 Container Runtime

  8. Kubelet(K8s)
 Linuxなど
 High-Level Runtime
 CRI
 Low-Level Runtime
 OCI
 • runc


    • youki
 • crun
 
 8
  9. youkiの存在意義
 • コンテナランタイムの多様性
 • Rustコンテナ界隈でのライブラリへの貢献
 ◦ https://github.com/containers/oci-spec-rs • 既存の低レイヤのソフトウェアをRustで書き換える 一例


    9
  10. 目次
 1. youkiの紹介
 2. youkiの大まかな処理の流れ
 3. コンテナランタイムのコードを追っていく!
 10

  11. コンテナ技術の軽い復習
 • コンテナを支える技術
 ◦ chrootpace - ルートディレクトリを変更
 ◦ namespace -

    操作可能なリソースの隔離
 ◦ cgroup ace - 使用可能なリソースの設定
 
 • コンテナ界隈を支える仕様
 ◦ CRI - Container Runtime Interface
 ◦ OCI - Open Container Initiative
 11
  12. None
  13. None
  14. • コンテナのプロセスができるまでには double-fork が必要
 ◦ 1st fork 
 ▪ 中間プロセス


    ▪ initプロセスを作るための間のプロセス
 ◦ 2nd fork
 ▪ initプロセス
 ▪ 実際にコンテナのinitプロセスになる 
 ◦ 理由は後ほど...

  15. None
  16. • $ docker create --name test busybox hostname
 • 高レベルランタイムからコンテナを作る命令を受付


    ◦ 命令を受け付ける...? 
 ▪ 単なるコマンド
 ▪ $ youki create -b $(bundle_path) $(container_name)
 • fork(2) - Intermediate Processを生成する

  17. None
  18. • unshare(CLONE_NEWUSER) - ユーザー名前空間を分離
 ◦ 新しい名前空間で特権ユーザーになれる
 • host ↔ containerのユーザーとグループのマッピング


    ◦ 例) host(uid 1000) ↔ container(uid 0)
 中間 create
  19. None
  20. • 中間プロセスでPID名前空間だけを先に分離
 ◦ PID名前空間は発動した次のプロセスから適用されるため
 • fork(2) - 中間プロセスからinitプロセスを生成
 • initプロセスで残りの名前空間を分離


    ◦ unshare(2)を発動した瞬間から分離される
 init 中間
  21. なぜdouble-forkが必要なのか?
 • PID名前空間の分離は発動した次のプロセスから適用される ◦ fork回数 += 1 • PID名前空間の分離にはCAP_SYS_ADMINが必要 ◦

    ホストが特権ユーザーではない場合は...? ◦ ユーザー名前空間の分離(一般ユーザーで実行可能) ▪ 中間Process内で特権ユーザーになる
 ▪ fork回数 += 1
 • ユーザー名前空間 → PID名前空間 → 残りの名前空間 21
  22. None
  23. • 諸々コンテナになるのに必要な処理をinitプロセスに施す
 ◦ pivot_root(2) - `/` の切り替え
 ◦ setup capability

    - HCRから受け取ったケーパビリティにする
 • 諸々準備が終わったことを最初のプロセス(youki create)に通知
 • initプロセスはstartのシグナルが来るまで「待て」
 create 中間 init
  24. None
  25. • cgroupを諸々適用する
 • 受け取ったinitプロセスのPIDをpidファイルに書き込む
 ◦ HCRはこのpidファイルのPIDを元に色々管理していたりするっぽい
 create 中間

  26. None
  27. • initプロセスはデーモンプロセスとなり、コンテナのエントリポイントを実行 するのを待つ
 • 高レベルコンテナランタイムからスタートの合図
 ◦ $ docker start test


    init
  28. 処理の流れのまとめ
 • プロセスに対して細かく色々している
 ◦ 名前空間の分離
 ◦ pivot_root
 ◦ capability
 ◦

    cgroups
 
 • double-forkが必要
 28
  29. 処理の流れのまとめ
 • プロセスに対して細かく色々している
 ◦ 名前空間の分離
 ◦ pivot_root
 ◦ capability
 ◦

    cgroups
 
 • double-forkが必要
 29 Rust 得意分野っぽい!

  30. 目次
 1. youkiの紹介
 2. youkiの大まかな処理の流れ
 3. コンテナランタイムのコードを追っていく!
 30

  31. Let’s dive into youki

  32. • 流れ自体は大きく変わらない
 • 使用言語
 ◦ runC - GoとCのハイブリッド
 ◦ youki

    - Rust(一部FFI)
 runCではどうなっているのか
 32
  33. GoとCのハイブリッドだと...?
 • Goの言語ランタイムの制約
 ◦ fork(2) / clone(2) が難しい
 ▪ おそらくGoroutineの関係


    ◦ 言語ランタイムがマルチスレッド
 
 • setns(2) 😢 A multithreaded process may not change user namespace with setns().

  34. • Goで扱えない部分はCで扱う
 ◦ サブコマンドのinitがCを扱う
 ◦ 言語ランタイムが起動する前にCの処理を呼び出す
 GoとCのハイブリッドだと...?


  35. • 大雑把に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のハイブリッドだと...?

  36. Let’s dive into runC

  37. runCのコード読み
 • startContainer() - create.go
 ◦ createContainer()
 ▪ loadFactory()
 •

    libcontainer.New()
 ◦ /proc/self/exec init(= runc init)をコマンドとして container にセットしている
 
 ◦ runner.run()
 ▪ container をスタートする

  38. コードリーディングのまとめ
 • runCはGoとCのハイブリッド
 ◦ マルチスレッドでは処理ができない部分があるのでCを 使う
 ◦ createサブコマンド(Go) → initサブコマンド(C)


    • youki
 ◦ Rustのみでよりシンプルにコードがかける

  39. Thanks to all the people who already contributed to youki

    :)
 39
  40. Thanks you! Any questions?
 40