Haconiwa を改めて紹介する


OSS紹介 Advent Calendar 2017 にかこつけて、自作オーエスエスを紹介していくスタンス。ということで20日の記事です。

Haconiwaは、いわゆるLinuxコンテナランタイムです。コンテナといえばクジラっぽいロゴのやつが有名ですが、Haconiwaはmrubyを組み込んでおり、コンテナに関するLinuxカーネルの機能を自分で組み合わせて使える点、フック機構など様々なAPIにより自分自身を拡張できる点、に特徴があります。

リリースして RubyKaigi 2016 でおしゃべりしてから大体1年(雑...)経っているのですが、改めて紹介してみます。みるぞ!

インストール

手元にUbuntu Zestyの環境があるので雑にインストールします(Xenialならリポジトリ追加もできますがLTS以外は用意していなかった...)

$ wget --content-disposition https://packagecloud.io/udzura/haconiwa/packages/ubuntu/xenial/haconiwa_0.8.9-1_amd64.deb/download.deb
$ sudo apt install ./haconiwa_0.8.9-1_amd64.deb
$ haconiwa version
haconiwa: v0.8.9

今日(!!)リリースした0.8.9が最新です。

試しに動かす

Haconiwaの設定ファイル兼Ruby DSLを Hacofile と呼んでいます。[^1]: 一部でしか呼んでいない雰囲気があるので呼んでいきたい。

$ haconiwa new qiita.haco                                                                                                    
assign  new haconiwa name = haconiwa-9bdfc517                                                                                                     
assign  rootfs location = /var/lib/haconiwa/9bdfc517                                                                                              
create  qiita.haco

作られた qiita.haco は文法としては普通のRubyのスクリプトです。bootstrap/provisionのブロックを以下のように変更してみます。

qiita.haco
  config.bootstrap do |b|
    b.strategy = "debootstrap"
    b.variant = "minbase"
    b.debian_release = "stretch"
  end

  config.provision do |p|
    p.run_shell <<-SHELL
apt -y update
apt -y install procps iproute2 ruby
    SHELL
  end

この状態で sudo haconiwa create qiita.haco と打つと、DSLに沿ってコンテナのrootfsの作成とプロビジョニングをしてくれます。

なおこの設定の場合は、debootstrapコマンドを使うので事前インストールしておいてください。

rootfsの作成が完了したらコンテナに入ってみます。

$ sudo haconiwa run qiita.haco -T -- /bin/bash
Create lock: #<Lockfile path=/var/lock/.haconiwa-9bdfc517.hacolock>
Container fork success and going to wait: pid=11736
root@haconiwa-9bdfc517:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.3  18164  3180 ?        S    09:32   0:00 /bin/bash
root         2  0.0  0.2  36632  2836 ?        R+   09:32   0:00 ps auxf
root@haconiwa-9bdfc517:/#

PIDも 1 から開始しているし、コンテナですね。やった!

コンテナ機能を試す

コンテナの機能を「個別に」有効にして検証してみるということをします。

cgroup

以下のような設定を追記します。

  config.cgroup["cpu.cfs_period_us"] = 100000
  config.cgroup["cpu.cfs_quota_us"]  =  30000
  config.cgroup["pids.max"] = 128

この設定を入れてコンテナに入って、Rubyなどで無限にフィボナッチ数を計算させてみます。

$ sudo haconiwa run qiita.haco -T -- /bin/bash
Create lock: #<Lockfile path=/var/lock/.haconiwa-9bdfc517.hacolock>
Container fork success and going to wait: pid=11910
root@haconiwa-9bdfc517:/# ruby -e \
  'def fib(n);n<2?1:fib(n-2)+fib(n-1);end;loop{fib rand(32)}'

しかし、cgroupの制限が効いているので、 30000us/100000us の30%までしかCPU利用率が上がらないことがわかります。

また、プロセス数の上限も制限をしているので、このコンテナでfork bomb攻撃(危ないのでコードの掲載はしません:( )を実行してみても、無事に攻撃が収まることがわかります。 pids.max の制限をしていない場合 ホストごと落ちる ので気をつけましょう。

## fork bomb コマンドを発行
...
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable

root@haconiwa-9bdfc517:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.3  18300  3316 ?        S    09:38   0:00 /bin/bash
root       828  0.0  0.2  36632  2904 ?        R+   09:40   0:00 ps auxf

seccomp

seccompも試せます。以下のようなDSLを書いて、またコンテナを立ち上げてみます。

  config.seccomp.filter(default: :allow) do |rule|
    rule.kill :mkdir
    rule.kill :fchownat
  end

すると、見た目は普通のbashセッションですが、このコンテナの中では、rootであるにもかかわらず mkdir の操作や chown の操作などが一切できなくなっていることがわかります。

root@haconiwa-9bdfc517:/# touch hoge.txt                                 
root@haconiwa-9bdfc517:/# chown www-data hoge.txt 
Bad system call
root@haconiwa-9bdfc517:/# mkdir /tmp/test
Bad system call

このように、Haconiwaでは特定のシステムコール呼び出しを制限するサンドボックス環境を作ることができ、そのホワイトリスト/ブラックリストをRubyで記述可能です。

ネットワーク機能

今日のリリースで入った機能です。ブリッジとveth/network namespaceを用いいてコンテナに仮想的なネットワークを割り振ることができます。

最初にブリッジが必要なので、以下のコマンドで生成しましょう。

$ sudo haconiwa init --bridge --bridge-ip=10.254.254.1/24
Command success: ip link add haconiwa0 type bridge exited 0
Command success: ip addr add 10.254.254.1/24 dev haconiwa0 exited 0
Command success: ip link set dev haconiwa0 up exited 0

デフォルトでは haconiwa0 というブリッジができます。

$ ip a s haconiwa0
5: haconiwa0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 56:98:fb:4e:2f:3b brd ff:ff:ff:ff:ff:ff
    inet 10.254.254.1/24 scope global haconiwa0
       valid_lft forever preferred_lft forever
    inet6 fe80::5498:fbff:fe4e:2f3b/64 scope link 
       valid_lft forever preferred_lft forever

このブリッジの 10.254.254.1/24 の範囲で一つIPを決め、Hacofileで設定してみましょう。

  config.network.container_ip = "10.254.254.2"
  config.network.namespace = config.name

このHacofileでコンテナを立ち上げると、ちゃんと 10.254.254.2 が割り当てられたコンテナになり、またホストのネットワークとちゃんと分離されることがわかります。

root@haconiwa-9bdfc517:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
10: e6d4d690_g@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 02:6b:df:0e:d9:38 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.254.254.2/24 scope global e6d4d690_g
       valid_lft forever preferred_lft forever
    inet6 fe80::6b:dfff:fe0e:d938/64 scope link 
       valid_lft forever preferred_lft forever

Haconiwaには他にも様々な機能(特に、便利なフック機能など...)がありますが、無限に紹介記事が長くなってしまいそうなので、この辺りにしておきます。

ぜひいろいろ試してみてください!