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のブロックを以下のように変更してみます。
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には他にも様々な機能(特に、便利なフック機能など...)がありますが、無限に紹介記事が長くなってしまいそうなので、この辺りにしておきます。
ぜひいろいろ試してみてください!