原文はこちら。
https://blogs.oracle.com/developers/building-a-container-runtime-in-rust
コンテナエコシステムは拡大を続けており、プロプライエタリな実装が徐々にオープンスタンダードなものに取って代わっています。オープンコンテナイニシアティブ(OCI)から出ている最も重要な標準の一つが、OCI Runtime Specificationで、代替コンテナ実行環境を開発することを許容するものです。
言語はまだ枯れてはいませんし、もっと多くのライブラリのサポートを使うこともできますが、やりたいことを実現するにあたって必要なものの大部分はかなり簡単に実現できました。安全でないコードの利用を最小限に抑え、コードのメモリ安全性に関して信頼を得ました。
Off-CPUプロファイリング手法を使って、ロック待ちの主犯はcgroupの作成と名前空間の作成であることがわかりました。これはつまり、本当に高速な起動のためには、名前空間とcgroupをあらかじめ作成しておき、起動時にそれらを特化するだけにとどめる必要があります。
一般に、railarはまだ幅広いシナリオで使われてはいませんので、railcarを試し、発生する問題解決の支援をしてくださるコミュニティの皆様に感謝しています。railcarがcri-oを介してKubernatesのバックエンドとしても利用されることになればすばらしいことでしょう。
https://blogs.oracle.com/developers/building-a-container-runtime-in-rust
コンテナエコシステムは拡大を続けており、プロプライエタリな実装が徐々にオープンスタンダードなものに取って代わっています。オープンコンテナイニシアティブ(OCI)から出ている最も重要な標準の一つが、OCI Runtime Specificationで、代替コンテナ実行環境を開発することを許容するものです。
OCI Runtime Specificationランタイムの選択は、イノベーションとより良い実装につながります。Oracleのコンテナ開発の一環として、railcarと呼ばれるrustで動作するランタイムを実装することにしました。
https://github.com/opencontainers/runtime-spec
RailCar: Rust implementation of oci-runtime以下でランタイムの開発、直面した課題、および教訓について詳しく説明します。
https://github.com/oracle/railcar
Related Content
- Containers, Containers Everywhere But Not a Drop to Drink!
http://blogs.oracle.com/developers/containers-everywhere - Getting Started with Microservices, Part 2: Containers and Microservices
http://blogs.oracle.com/developers/getting-started-with-microservices-part-two - Running Docker Store images on Container Cloud Service
http://blogs.oracle.com/developers/running-docker-store-images-on-container-cloud-service
Why Rust?
今日では、ほぼすべてのコンテナユーティリティがCまたはGoで記述されています。CはLinuxカーネルとやりとりするのに最適ですが、セキュリティ上の問題があります。Goは、開発スピードやメモリの安全性の面で優れていますが、名前空間との相互作用に問題を生じるいくつかの制限があります。Linux Namespaces And Go Don't MixRustはこれらの2つの言語の長所を兼ね備えています。つまり、メモリの安全性とより高いレベルのプリミティブを備えていますが、スレッド管理に対する低レベルの制御を犠牲にしないため、名前空間を適切に処理できます。これはコンテナユーティリティにとってよい選択であり、我々はRustコミュニティとコンテナコミュニティが今後もっと協力し合うことを期待しています。
https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix
Implementation Challenges
Forking
Linuxの名前空間と正確に相互作用することは極めて困難です。ユーザーおよびpidの名前空間を適切に入力するためには、大量のforkや同期が必要で、非常にいらだたしいものです。組み合わせ可能なlimited isolation primitivesにメリットがあるとはいえ、確実にjailやzoneのようなよりシンプルなモデルを待望されることでしょう。Namespaces in operation, part 5: User namespaces
https://lwn.net/Articles/532593/
Namespaces in operation, part 3: PID namespaces
https://lwn.net/Articles/531419/
Setting the Record Straight: containers vs. Zones vs. Jails vs. VMs
https://blog.jessfraz.com/post/containers-zones-jails-vms/
Complexity and Debugability
仕様の実装はかなり簡単ですが、バックエンドをDockerの背後で動作させることは、かなり大変です。具体的には、様々なプロセスが関わって、問題になっていることを見ています。`docker run`のようなコマンドでは、docker client - > dockerd - > containerd - > containerd-shim - > runtimeのように呼び出されます。事態がうまくいかないとき、何がうまくいかなかったのか、その理由は不明です。containerdによるランタイムに対するShell呼び出しで、containerdがクラッシュすることがあり、その場合dockerdがcontainerdを再起動させるため、実際のエラーが記録されないまま無限ループに陥ります。
Incomplete Spec
最も不満なことの一つが、仕様に記載の通りに実装するだけでは不十分で、Dockerの代替バックエンドとはなり得ない、ということでした。その他にいくつか作業が必要でした。例えば、containerdが<runtime> psもしくは<runtime> statsを呼び出そうとした場合、これらの操作はruncがサポートしますが、仕様に言及はありません。Bugs
containerd/runcが正しく仕様に従っていないことがあります。例えば、仕様ではコンテナ削除後にdeleteを呼び出すとエラーを返すはずですが、テストでは、containerdがdeleteを2回呼び出し、runcは2回のdeleteを受け付けることを明確に示していました。runcはインスタンスが完全に削除された後エラーを返すため、最初のdeleteは実際に何も削除しないと想定し、2度目の呼び出しをします。この振る舞いは、適切に最初の呼び出しで全てを削除する別のランタイムでは問題を引き起こします。Misfeatures
仕様のいくつかの部分は少し過剰のように思えます。おそらくまだ仕様策定後時間が経っていないからでしょう。困難な実装の1つとして、起動前(prestart)、起動後(poststart)のイベントフックでした。これらは、run(実行)コマンドが使用されていた場合には非常に便利でしたが、仕様が変わって create(作成)、start(開始)、stop(停止)コマンドになったため、この手の作業をランタイムの外部で行うことができます。ランタイムは単にコンテナ化に集中することになるため、ランタイムが実行するフックは、関心の分離(Separation of Concerns)に違反します。Differences From Runc
Railcarの動きは、デフォルトのランタイムとは少し異なり、常にinitプロセスを作成します。CLI tool for spawning and running containers according to the OCI specificationpid名前空間にinitプロセスがないと、奇妙な問題が発生します。
https://github.com/opencontainers/runc
The Curious Case of Pid Namespacesそのため、新しいランタイムが提供する機会を利用して、別のオプションを試すことにしました。この機能が提供する一貫性を気に入っていますが、擬似端末が使用されていないときに標準出力と標準エラー出力の周りで予期しない動作が発生することがあります。
https://hackernoon.com/the-curious-case-of-pid-namespaces-1ce86b6bc900
Lessons Learned
The Rust Language
このようなプロジェクトでは、Rustは優れた選択であることがわかりました。Goランタイムが起動する前のruncにとって必要な不快なCコードの注入は、常に迷惑なものでした。Rustは、1つの言語でRailcarの全体を実装するのに十分な柔軟性がありました。また、必要に応じて静的なバイナリを構築することもできるのです。言語はまだ枯れてはいませんし、もっと多くのライブラリのサポートを使うこともできますが、やりたいことを実現するにあたって必要なものの大部分はかなり簡単に実現できました。安全でないコードの利用を最小限に抑え、コードのメモリ安全性に関して信頼を得ました。
Fast Container Startup
代替ランタイムに対する我々のゴールの一つが、コンテナ起動時間を短縮する方法を確認する、というものでした。Rust実装はわずかにGo実装よりも速いのですが、コンテナ起動時間(およそロードしないシステムで150msec)の半分以上は実はカーネルのロック待ちということに驚きました。Off-CPUプロファイリング手法を使って、ロック待ちの主犯はcgroupの作成と名前空間の作成であることがわかりました。これはつまり、本当に高速な起動のためには、名前空間とcgroupをあらかじめ作成しておき、起動時にそれらを特化するだけにとどめる必要があります。
Off-CPU Flame Graphsこのような方法をサポートするためには、ランタイム仕様の作成/開始フローへのいくつかの変更が必要ですが、検討する価値はあります。ネットワーク名前空間とcgroupがあらかじめ作成されている場合、起動時間は10ms以下です。
http://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html
Future Improvements
railcar startでinitプロセスの標準出力および標準エラー出力へのアクセスが可能になれば、runcとの互換性が向上します。現時点では、ターミナル無しでDocker内でrailcarを実行すると、ユーザープロセスからの標準出力と標準エラー出力へのアクセスはできませんので、railcarが実装していないstatsコマンドのようなruncの他の機能が非常に有用です。仕様のより新しいバージョンに対する自動テストはもう一つの価値の追加になることでしょう。一般に、railarはまだ幅広いシナリオで使われてはいませんので、railcarを試し、発生する問題解決の支援をしてくださるコミュニティの皆様に感謝しています。railcarがcri-oを介してKubernatesのバックエンドとしても利用されることになればすばらしいことでしょう。
Kubernetes | Production-Grade Container Orchestration
https://kubernetes.io/
Open Container Initiative-based implementation of Kubernetes Container Runtime Interface
https://github.com/kubernetes-incubator/cri-o
Conclusion
全体として、別のランタイムの実装は非常に価値ある作業でした。ランタイムがコミュニティから注目を集め、有用なコンテナ化の選択肢が存在し続け、実験と相互交流が促進されることを願っています。自由にGitHubリポジトリをクローンし、問題がありましたらお知らせください。railcar Issue Tracking
https://github.com/oracle/railcar/issues