LoginSignup
17
11

More than 5 years have passed since last update.

自作コンテナランタイムの開発で使用しているcrate

Last updated at Posted at 2018-12-22

この記事はRustその2 Advent Calendar 2018の22日目の記事です。

現在個人でRustを使ってコンテナランタイムの開発に挑戦してみているのですが、その中で利用しているcrateを知見として紹介したいと思います。

作ろうと思ったきっかけ

コンテナ型仮想化の情報交換会に参加させて頂き、自作コンテナランタイムの話を聞いて興味をもったのがきっかけです。

もともと当時はDockerひいてはコンテナに興味をもっていた時期だったのですが、コンテナの事をもっと詳しく知りたいと思い上記の勉強会に参加させてもらったところ、自作コンテナランタイムを開発している方の話を聞き「自分も開発してみたい!」と思うようになり開発に乗り出しました。

何故Rustなのか

DockerはGoで開発されているので最初はGoを使おうかと思っていたのですが、上記の勉強会でRustという選択肢もあると教えて頂きました。

当時はRustを知らなかったのですが調べて行くうちに興味が出たのと、Goと同じく低レイヤーのプログラミングも可能だったのでRustで作る事を決定しました。

自作コンテナランタイムについて

smiyaguchi/miniconという名で開発しています。

コンテナランタイムの標準仕様のOCI(Open Container Initiative)とkubernetesのインタフェースであるCRI(Container Runtime Interface)の仕様を満たした実装にできればと思い開発しています。

使用しているcrate

前置きが長くなってしまいましたが、開発で使用しているcrateを紹介していきます。

nix

nix-rust/nix
自作コンテナランタイムの開発で一番使用しているcrateです。

nixは一言でいうとlibcのラッパーです。
コンテナを作る際はkernelの機能を利用するのでシステムコールを発行する事になります。
システムコールの発行はlibc等を使っても可能なのですがlibcを使う場合、自分でunsafeを使用してコンパイル時の安全を保証しないと行けなくなってしまいます。

nixを使用するとlibcをラップしているのでRustの強力な型システムに乗せることができ、コンパイル時に安全性を保証できるようになる為、開発でもlibcではなくnixを使っています。

nix_example
use nix::sys::socket::{connect, socket};
use nix::sys::socket::{AddressFamily, SockAddr, SockType, SockFlag, UnixAddr};

#socketの作成・接続
fn create_socket() -> Result<(), Error> {
    let esocket = "example-socket";
    let mut esocket = socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
    esocket = match connect(esocket, &SockAddr::Unix(UnixAddr::new(&*csocket)?)) {
            Ok(()) => esocket,
            Err(_) => -1,
        };

    Ok()
}

nixで対応していないシステムコールもありますので、その場合は自身で対応する必要があります。

clap

clap-rs/clap
有名なcrateなのでご存知の方も多いかと思います。cliを作る際に便利なコマンドライン解析ライブラリですね。

OCIではランタイムからコンテナを操作する際の仕様を定義しています。
(コンテナを作る時はcreatestartで操作できる様にする等)

自作コンテナランタイムではDockerと同じくcliとして上記コンテナの操作の仕様を満たせる様にしたいと思っていますのでclapを利用しています。

cliの定義を記載してyamlファイルを用意して読み込む事も可能です。

cli.yml_example
name: example 
version: "0.1.0"
args:
    - version:
        short: v
        long: version
        help: Show version
subcommands:
    - create:
        about: create a container
        args:
            - id:
                required: true
                help: container id
            - bundle:
                short: b
                long: bundle
                required: true
                default_value: .
                help: path to bundle
clap_example
#[macro_use]
extern crate clap;
use clap::App;

fn main() {
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // 以下処理
}

serde, serde_derive,serde_json

serde-rs/serde
serde-rs/serde-derive
serde-rs/serde-json
こちらも超有名crateなので皆さんご存知かと思います。シリアライズ/デシリアライズのフレームワークですね。

OCIではconfig.jsonというファイルにコンテナの構成情報が記載される事になっており、ランタイムはその情報を元にコンテナを作成するという仕様になっています。
なので開発ではconfig.jsonのシリアライズ/デシリアライズに利用しています。

serde_example
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

#[derive(Serialize, Deserialize, Debug)]
pub struct Spec {
    #[serde(default, rename = "ociVersion")]
    pub oci_version: String,
}

impl Spec {
    pub fn load() -> Result<Spec, Error> {
        let file = File::open("config.json")?;
        let buf_reader = BufReader::new(file);
        let spec: Spec = from_reader(buf_reader)?;
        Ok(spec)
    }        
}

fn load_spec() -> Result<Spec, Error> {
    let spec = Spec::load()?;
    Ok(spec)
}

failure

rust-lang-nursery/failure
ライブラリのエラーを定義するのに便利なcrateです。

私も使い初めて日が浅いので使い方を学んでいる最中ですが、自身のライブラリ用に新しくエラーを定義するのに便利なcrateだという認識です。
(このあたりもしっかり理解したい...)

自作コンテナランタイムの中で独自のエラー定義を行えるように利用しています。

failure_example
extern crate serde;
extern crate toml;

#[macro_use] extern crate failure;
#[macro_use] extern crate serde_derive;

use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;

use failure::Error;

// This is a new error type that you've created. It represents the ways a
// toolchain could be invalid.
//
// The custom derive for Fail derives an impl of both Fail and Display.
// We don't do any other magic like creating new types.
#[derive(Debug, Fail)]
enum ToolchainError {
    #[fail(display = "invalid toolchain name: {}", name)]
    InvalidToolchainName {
        name: String,
    },
    #[fail(display = "unknown toolchain version: {}", version)]
    UnknownToolchainVersion {
        version: String,
    }
}

pub struct ToolchainId {
    // ... etc
}

impl FromStr for ToolchainId {
    type Err = ToolchainError;

    fn from_str(s: &str) -> Result<ToolchainId, ToolchainError> {
        // ... etc
    }
}

pub type Toolchains = HashMap<ToolchainId, PathBuf>;

// This opens a toml file containing associations between ToolchainIds and
// Paths (the roots of those toolchains).
//
// This could encounter an io Error, a toml parsing error, or a ToolchainError,
// all of them will be thrown into the special Error type
pub fn read_toolchains(path: PathBuf) -> Result<Toolchains, Error>
{
    use std::fs::File;
    use std::io::Read;

    let mut string = String::new();
    File::open(path)?.read_to_string(&mut string)?;

    let toml: HashMap<String, PathBuf> = toml::from_str(&string)?;

    let toolchains = toml.iter().map(|(key, path)| {
        let toolchain_id = key.parse()?;
        Ok((toolchain_id, path))
    }).collect::<Result<Toolchains, ToolchainError>>()?;

    Ok(toolchains)
}

※公式より引用

終わりに

以上が現在開発で使用している主なcrateになります。
いろいろ調べながら進めており、まだまだ未完成な為あまり数がないですが現在利用しているcrateを紹介させてもらいました。

また、便利なcrateがあれば紹介したいと思います。

17
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
11