ocipkg: OCI Registry for package distribution
ocipkg 0.1.0をリリースしました:
この記事ではこのプロジェクトを作り始めたモチベーションについて説明します。
配布機構としてのコンテナ
この記事ではDocker等のコンテナシステムをユーザーとして使ったことがある人を想定読者とします。
「コンテナ」という単語が想起させるイメージとして、大雑把に次の2つのものがあります:
- 「箱庭」つまり外から隔絶された内部で閉じた世界
- 「(輸送における)コンテナ」つまり規格が決まっており船やトラックに積めてさらに重ねたりも出来る
コンテナ技術の話をするときは前者の隔離の機能について、例えばDockerのようにプロセスレベルで隔離するのかあるいは仮想マシンレベルで隔離するのかといった事を議論することが多いですが、今回注目するのは後者の性質です。
Dockerが成功した理由の1つとして、コンテナの配布機構を整備した事が大きいでしょう。プロセスレベルの隔離機能は本来Linuxの機能でDockerはクライアントとしてそれを応用しているに過ぎません。
配布機構としてのコンテナに注目したとき、それはどのように実行するかがメタデータに書いてあるtarやzipのようなアーカイブ形式と見ることが出来ます。このアーカイブの仕様は現在ではLinux Foundation配下のプロジェクトのOpen Container Initiative (OCI)によってOCI Image Formatとして制定されています。OCI Image Formatはディレクトリ構造として定義されるので、これを扱い易いように1ファイルのtarにつなげたものをoci-archive形式呼び、ocipkgではこのフォーマットを多用します。
OCI Registry As Storage (ORAS)
OCI Registryとは標準化されたコンテナレジストリ、つまりコンテナをPushしたりPullするREST APIが定められており(OCI Distribution Specification)、これに則って実装されたコンテナレジストリの事です。例えばGitHub Container Registry (ghcr.io)やDockerHub (docker.io)があります。
OCI Registryはコンテナを配布するシステムなので、サーバーでコンテナを保持してくれますが、コンテナの実行機能を忘れるとこれはリモートのファイルシステムのように見えます。OCI Image Formatは何かしらのバイナリを保存できるblobs/
(個々のファイルは内容のSHA256ハッシュで名前付けされる)とそこに何を保存したかを示すインデックスから構成されており、さらにOCI Distribution APIではblobs
以下のファイルを直接HTTPで取得できます。しかもこれはコンテナを配布するためのシステムなので、大容量のファイルをやりとり出来、HTTPでアクセスできるので内容が分かっている場合に部分的に取得する等も可能です。
この点に着目して様々なものをOCI Registryに保持しようと試みたプロジェクトがOCI Registry As Storage (ORAS)です。また最近ではsigstoreプロジェクトの一部のcosign
とsget
コマンドも同じような機能を有し、これらはさらにコンテナへの署名をOCI Registryに保存します。
重要なのはこれらのプロジェクトがコンテナランタイムとは独立に実装出きることです。コンテナの作成と配布機構を使うだけならばコンテナの実行環境は必要ありません。そもそもDockerがDockerfile
による仮想マシン上でのスクリプトの実行によってコンテナを作ろうとするのは従来のシステムに置けるセットアップ機構をそのまま使ってコンテナを作成するためで、新しく一からコンテナを作る場合にはそのような機構は必要ありません。必要なのはファイルを操作する事とHTTPSで通信する事だけです。
ocipkg
以上の知識の元でocipkgについて説明していきましょう。ocipkgはOCI Registryのクライアントとして動作するように設計されています:
- OCI RegistryへのコンテナのPushとPull
- ローカルに保存されたoci-archive形式のコンテナの読み書き
が実装されています。これらに加えて、パッケージ管理機構の一部としてOCI Registryを使うためのユーティティとして:
- ファイルやディレクトリ、あるいはRustプロジェクトからのコンテナの作成
-
build.rs
ヘルパーによるコンテナの取得とその中のライブラリのリンク
が可能です。これらは全てDocker等の外部のコンテナランタイムを使うこと無く独立に実装されています。
ORASやsigstoreを使わなかったのはこれらの機能が単純なRust crateとして欲しかったからです。実際これらはファイルの操作と単純なREST APIへのアクセスだけからなるので、わざわざ別言語で実装されたもの(両者ともGo実装)を導入するコストを払う必要が感じられません。
FFIするライブラリをどうやって手に入れるか
さてようやく本題です。これは以前書いたRustのFFIの記事のある意味続編です:
問題となるのは、RustでFFIによってC APIしか提供されない既存のライブラリを使うcrate (以下*-sys
crate)を作る際にそのライブラリをどのように用意するべきかです。
システムに存在しているライブラリを使う
まず検討するのはシステムに既に存在しているライブラリをリンクする事です。ほとんどの*-sys
crateはこの方法をとります。
実際これは開発者本人にとっては良く動きます。*-sys
crateの開発者は基本的にライブラリのリンク方法と実行ファイルが実行時にどうやって共有ライブラリを探すのか良く知っているため、問題が発生しても簡単に対処できます。自分のシステムにおいてライブラリを用意する方法についても良く知っているでしょう。
つまり別の言い方をするとこれはライブラリを用意することが*-sys
crateの開発者ではなく、ユーザーの責任になっているということです。これはcrateを公開して一般的なRustユーザーにこのcrateを使ってもらおうと思った時に問題がおきます。Rustではcargo
でライブラリが取得できて使えるようになっている事は開発者の責任です。しかし外部ライブラリを用意する事をユーザーの責任にしたままではこれが満たせません。
ソースコードを同梱してビルド時に一緒にビルドする
crateの開発者はシステムを操作するわけにいかないので、システムに上手くライブラリがインストールされるようにシステムに手を入れる、例えばbuild.rs
の中でOSを検出してUbuntuならapt
を実行するようなわけにはいきません。
そこで開発者の管理下でライブラリを作成する事を考えましょう。まず検討するのは対象のライブラリのソースコードをRustのコードと一緒にcrateに含めて配布しビルドしてリンクしてしまう方法です。*-src
という名前のcrateの多くでそのような方法を採用しています。これで開発者の責任でユーザーのシステム上に正しくライブラリを用意できます。これを補助するためにcmake crateやvcpkg crateが存在しています。これはシステムにcmake
等が存在している事が必要になりますが、そもそもRustはld
を外部に依存していたりするのでこれは許容できる場合も多いでしょう。
ビルド時にバイナリを取得する
上の方法にも問題が2つあります。1つはLLVMの様に依存するライブラリのビルドが重い場合、もう1つはIntel MKLやCUDAの様にソースコードが公開されていない場合、crateは容量制限があるのでバイナリを含めるわけにはいかない点です。
これを解決するためにバイナリをビルド時に取得する方法が必要になります。しかしここで例えばAWS S3にバイナリを開発者が置いてユーザーがそれを使うようにすると、AWSの費用を開発者が持つ事になってしまいます。
そこでそのバイナリ配布機構の為にOCI Registryを使おうというのがocipkgの目的です。OCI Registryは標準化されたAPIに基づいているので複数のサービスで使うことが出来、またAWS等のクラウドには自費で立てる為のサービスが用意されています。
正直まだocipkgに基本的な機能が出来上がったところなので、これから私の管理下のプロジェクトで試しに使ってみるところです。続報をお待ちください(´・ω・`)!
Discussion