弊社の開発プロジェクトのうち、比較的新しめなものについては、開発環境が最初から Docker ベースになってきています。
ところが、それより古いプロジェクトだと、「Vagrant で起動した Linux 仮想環境に Ansible で色々インストールして使う」スタイルのものが多いです。
「Docker だったらオーバーヘッドほぼゼロなのに…」(Linux), 「Docker だったら HyperKit 上で動作する Linux カーネルが常駐してるから起動が速いのに…」(macOS)と、今のご時世ならこう思うところですが、かと言って今になってから開発環境を Docker ベースに切り替えるのもなかなか(主に工数的な面で)難しい。
そこで、開発環境の大幅な構成変更をすることなく(これ大事)、 Docker の効率的な処理系の恩恵だけつまみ食いできるか、試してみました。
TL;DR
- Vagrant に vagrant-docker というそのものズバリな provider がある!
- Linux as a container; Docker の哲学に真っ向から喧嘩を売っていくスタイル
- PID 1 となる systemd の建て方が難しい: 特権
CAP_SYS_ADMIN
と/sys/fs/cgroup
の扱いがミソ
とりあえず建ててみる
作戦の根幹は、
-
/sbin/init
の他はsshd
が動いている程度の、 Linux のシステムとしてはミニマムな状態のコンテナを作れる Dockerfile を用意 - Vagrant を介して管理
- あとは provisioner を使って煮るなり焼くなり
となります。
ということで、動く例
こちらを参考にしつつ、 sshd の起動を systemd で行うようにしてみました。
FROM ubuntu:18.04
RUN \
export DEBIAN_FRONTEND=noninteractive && \
apt-get update && apt-get -y upgrade && \
apt-get install -y --no-install-recommends \
dbus \
systemd-sysv \
openssh-server \
sudo && \
useradd -m -s /bin/bash -G adm,sudo vagrant && \
echo -n vagrant:vagrant | chpasswd && \
echo 'vagrant ALL = (ALL) NOPASSWD: ALL' >/etc/sudoers.d/vagrant && \
chmod 0440 /etc/sudoers.d/vagrant
#VOLUME [ "/sys/fs/cgroup" ]
CMD [ "/sbin/init", "--show-status" ]
Vagrant.configure("2") do |config|
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
# NB: no use for vagrant-docker
#config.vm.box = "base"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
config.vm.provider 'docker' do |docker, override|
# https://www.vagrantup.com/docs/docker/configuration.html
docker.build_dir = '.'
docker.has_ssh = true
override.ssh.username = 'vagrant'
override.ssh.password = 'vagrant'
# docker.cmd = [ '/bin/sh', '-c', 'mkdir /run/sshd && exec /usr/sbin/sshd -D' ]
docker.create_args = %w[
--cap-add SYS_ADMIN
--tmpfs /sys/fs/cgroup:rw
]
override.vm.synced_folder '.', '/vagrant', docker_consistency: 'cached'
end
end
以上を用意した上で、 vagrant up
するわけですが、 config.vm.provider
を複数記述している場合は、 --provider
オプションを使って docker を明示的に指定します。
$ vagrant up --provider docker
所々の謎記述について
Dockerfile
dbus
要らなくね?
事実上必須です。
systemctl
コマンドが systemd
との通信に dbus を使っているらしく、これが入っていないと systemctl
を使った様々なトリックが使えません。
python
は apt-get install
しなくていいの?
provisioner に Ansible を使っている場合は、入れておくと便利かも。
Vagrantfile
config.vm.box
は設定しなくていいの?
結論から言うと、設定不要あるいは逆にあると邪魔、でした。
うまく使えば Vagrantfile
の記述共通化に使えるかもしれません。
docker.create_args = %w[ --cap-add SYS_ADMIN --tmpfs /sys/fs/cgroup:rw ]
systemd は、デーモンやら何やらのリソース管理のために cgroups と言う仕組みを使いますが、そのためには /sys/fs/cgroup
ディレクトリ以下に cgroup ファイルシステムをマウントできるだけの特権が必要です。
ところが、 Docker はデフォルトで特権を放棄した状態でコンテナ内のプロセスを立ち上げるため、立ち上げられた systemd は身動きが取れなくなってしまいます。
cgroup ファイルシステムを /sys/fs/cgroup
にマウントするには、 CAP_SYS_ADMIN
特権があれば十分なので、 docker create
のオプション --cap-add
で与えています。
他に、マウントポイント用の tmpfs も必要なので、同様に --tmpfs
オプションで用意してやります。
override.vm.synced_folder '.', '/vagrant', docker_consistency: 'cached'
素の Docker for macOS でもあった、ホスト側のディレクトリをボリュームとしてコンテナ内にマウントすると遅い問題を回避する設定です。 vagrant-docker の synced_folder も同じメカニズムを使って実現されているので、回避策も似ています。
素の Docker for macOS でこのような設定にすることと等価です。
細かな点
気になるネットワークまわりは…
残念ながら、 Vagrant の枠組みだけだと、実質的に forwarded_port
しか使い物になりませんでした。
host_ip:
指定付きの forwarded_port
をうまく使って private_network
に似た構成を取ることや、 config.vm.provider 'docker' { |docker| docker.link }
を使ってコンテナ間ネットワークを構成することは可能なので、凝った構成でなければどうにかできるとは思います。
OK: config.vm.network 'forwarded_port'
(host_ip:
なし・ありどちらも)
NG: config.vm.network 'private_network'
(エラーにはならない)
NG: config.vm.network 'public_network'
(エラーにはならない)
synced_folder を NFS にできる?
残念ながらエラーとなりました。
==> default: Machine booted and ready!
No host IP was given to the Vagrant core NFS helper. This is
an internal error that should be reported as a bug.
そもそも、 NFS にする動機が「vboxsf が遅すぎるのを何とかしたい」というものだったはずなので、母艦が Linux ならむしろやらない方がいいです。母艦が macOS の場合は、「osxfs と nfs どっちが速いの?」という話になると思います。
xxxx したら何が起こる? シリーズ
vagrant halt
したら何が起こる?
Docker 的には、コンテナが stop 状態になります。
後で vagrant up
すると、コンテナの内容が保たれた状態で起動します。
vagrant reload
したら何が起こる?
コンテナが破棄されてビルドから作り直しになります。
素の Docker と同様、キャッシュが残っていれば活用されて素早く起動します。
なお、 provisioner は、 reload のたびに毎回実行されます。
provisioner を実行すると言うことは、とどのつまりコンテナ起動後にコンテナ内部へ変更を加えることなので、当然コンテナが破棄されたら失われますから、 vagrant-docker 側で配慮してくれるのはありがたいと言えなくもないのですが、 provisioner の処理が多いと辛いところ。
特に、 vagrant reload
を仮想マシン再起動のイディオムとして使っている場合は悲しいことになるので、多少面倒でも vagrant halt && vagrant up
とするといいと思います。
コンテナの中で shutdown -h
or shutdown -r
したら何が起こる?
Docker 的には、コンテナは running のままですが、 init 以外のプロセスが sshd を含めて全部終了した状態になります。
この状態だと、 vagrant halt && vagrant up
で復帰させるか、 docker exec
でコンテナ内に別のプロセスを立ち上げてあれこれすることが可能です。
ごめんなさい、あんまりいいユースケースが思いつきませんでした…
vagrant-lxc ってあったような…
macOS のことを考えなくていいなら、同じ基礎技術 cgroups を使う vagrant-lxc
provider も選択肢になると思います。
一時期開発が停滞していましたが、久しぶりに github を見たところ、また開発が再開しているようです。