mruby
container
haconiwa
OSS紹介Day 20

Haconiwa を改めて紹介する

More than 1 year has passed since last update.

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コマンドを使うので事前インストールしておいてください。

screen-qiita.png

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利用率が上がらないことがわかります。

スクリーンショット 2017-12-20 18.36.34.png

また、プロセス数の上限も制限をしているので、このコンテナで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には他にも様々な機能(特に、便利なフック機能など...)がありますが、無限に紹介記事が長くなってしまいそうなので、この辺りにしておきます。

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