Open9

RustでDockerを操作する練習

ozaki-rozaki-r

Dockerを操作するためのcrateはいくつかあるが、一番使われてそうなのがbollardらしいのでこれを使う。

いつものようにCargo.tomlで指定する。

[dependencies]
tokio = { version = "0.2", features = ["full"] }
bollard = "0.9"

bollardは非同期I/OフレームワークのTokioも使うのでここで指定している(が、多分書き方によっては不要だと思われる)。

ozaki-rozaki-r

Tokioを使うのでチュートリアルAsynchronous Programming in Rustは読んでおいた方が良いと思う。

Rustの非同期I/Oはランタイムが勝手に何かやることが少ないので理解はしやすいかもしれない。他の言語の非同期処理は詳しくないので使いやすいかはよくわからない。

Rustのasync/awaitに関してはとりあえず以下のような適当な理解しかしていない。

  • async
    • asyncは関数やブロックに指定でき、awaitなど非同期処理はその中でのみ実行可能。
    • 非同期処理はできることがない時に処理を中断して別のタスクに処理を譲る必要があり、その時に状態を退避しなければならない。asyncはその退避すべき状態の場所をコンパイラに教えている。
  • await
    • 上記の通りランタイムは自動的には何もしてくれないので、明示的に非同期処理が完了しているか確認するためのもの。
    • awaitの他にjoin!とかselect!のマクロで確認することも可能。
ozaki-rozaki-r

bollardのドキュメントやサンプルプログラムやテストを見ながら以下のようなプログラムを書いた。Dockerのversionコマンド相当を呼んで結果を表示するだけ。

#[tokio::main]
pub async fn main() {
    let docker = Docker::connect_with_unix_defaults().unwrap();
    let version = docker.version().await.unwrap();
    println!("{:?}", version);
}

#[tokio::main]はmain関数で非同期I/Oを使うおまじない。mainでなければ要らない。bollardのサンプルにあるようにtokio::runtime::Runtimeを使っても良い。

API呼び出し(上記コードでは.version())が非同期I/Oになっているので.awaitでI/O完了まで待ち合わせている。

ozaki-rozaki-r

話が前後するが、UbuntuでDockerを使う場合にやることメモ。Ubuntuのレポジトリにはないので、Docker本家のレポジトリを登録してCommunity Editionをインストールする。

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt install docker-ce
sudo usermod -aG docker ${USER}
ozaki-rozaki-r

Dockerfileからイメージをビルドする方法。

bollardのbuild_imageメソッドを使う。渡すのはDockerfileではなくDockerfileを含めた必要なファイル一式のtaball。圧縮しなくても良い。

Rustでtarballを作る方法はbollardのテストを参考にすると良い。

ozaki-rozaki-r

std::default::Default便利。

ConfigBuildImageOptionsのようにフィールドが多い構造体を生成する時にデフォルト値を与えてくれる。

    let config = Config {
        image: Some(image.to_owned()),
        ..Default::default()
    };

このように一部のフィールドを指定しつつ残りはデフォルトを使うことができる。

Rust Design Patternsでイディオムとして紹介されているし、Rust API Guidelinesでも実装することが推奨されている。

ozaki-rozaki-r

Bind mountする方法。

docker run -v /tmp:/mnt相当は以下で実現できる。

        let config = Config {
            image: Some(imagename),
            host_config: Some(HostConfig {
                binds: Some(vec![String::from("/tmp:/mnt")]),
                ..Default::default()
            }),
            ..Default::default()
        };

Bind mountはホストに依存する機能なので、ConfigではなくHostConfigで指定する。

ozaki-rozaki-r

ネットワーク関連のサンプルを探すと network_test.rs というテストを見つけた。

ただこれはネットワーク(bridge)を作るもので、やりたい事とは違うか。やりたいのはコンテナを既存のbridgeに繋ぐ方法。

裏技的だが、netnsでコンテナに無理矢理vethを生やすというやり方もありかも。(Rustはもはや関係ないが)

ozaki-rozaki-r

コンテナ(内のbash)のnetnsにvethを生やす。

docker run -it ubuntu bash
pid=$(pidof bash)
sudo ip netns attach hoge $pid
sudo ip link add name veth0 type veth peer name veth1
sudo ip link set veth1 netns hoge