Ansible などのプロビジョニングスクリプトを同じホストに何度も適用していると、そのスクリプトが正しく動くのか怪しくなってきます。
新規環境で流すと実は動かなかったりなんて。
この記事ではコンテナを使って高速に環境を再作成することで、いつも正しく動くプロビジョニングスクリプトを記述する方法を紹介します。
Vagrant 用が生成するssh_config
を Ansible に渡すので、Ansible の inventory ファイルを編集せずにデプロイ先をコンテナに切り替えることができます。
Vagrantfile
および Ansible playbooks は GitHub で公開しています。
この記事では、例として Ansible で Elasticsearch クラスタを構築します。
また高速化のために apt キャッシュサーバも配置します。
Vagrantfile
によって以下のホストを作成します。
apt-cacher
: apt キャッシュサーバelasticsearch-client
Elasticsearch のクライアントノードで、クラスタのエンドポイントとなるelasticsearch-master-{n}
Elasticsearch のマスターノードelasticsearch-data-{n}
Elasticsearch のデータノード
apt-cacher
の構築は Ansible の対象ではないので、Dockerfile
で環境を作ります。
各 Elasticsearch ノードは、SSH ができる環境をDockerfile
で作ります。
コンテナを定義する
ますはじめに、Docker のEmbedded DNS serverを使うために、ネットワークを定義します。
docker network create elasticsearch
このネットワークに接続したコンテナは、コンテナ名で他のコンテナの名前解決できるようになります。
Vagrantfile を記述
Vagrantfile の全貌は以下のとおりです。
# Vagrantfile
def vanilla_container(config, name, &proc)
config.vm.define name do |node|
node.vm.provider "docker" do |docker|
docker.name = name
docker.create_args = ["--hostname=#{name}", "--network=elasticsearch"]
docker.build_dir = "vanilla"
docker.has_ssh = true
proc.call(docker) if block_given?
end
end
end
Vagrant.configure("2") do |config|
config.ssh.username = "vagrant"
config.ssh.password = "vagrant"
config.vm.define "apt-cacher" do |node|
node.vm.provider "docker" do |docker|
docker.name = "apt-cacher"
docker.create_args = ["--hostname=apt-cacher", "--network=elasticsearch"]
docker.build_dir = "apt-cacher"
end
end
(1..4).each do |i|
vanilla_container config, "elasticsearch-master-#{i}"
end
(1..6).each do |i|
vanilla_container config, "elasticsearch-data-#{i}"
end
vanilla_container config, "elasticsearch-client" do |docker|
docker.expose = [9200]
end
end
さきほど作成したネットワークにコンテナを接続するために、docker.create_args
に--network=elasticsearch
を指定します。
そしてコンテナ名をdocker.name
で指定して、ホスト名をdocker.create_args
に--hostname=apt-cacher
を追加することで設定します。
今回は Elasticsearch ノードのコンテナをヘルパメソッドでガッと定義します。
エンドポイントとなるelasticsearch-client
ノードは、ポート 9200 を expose します。
vanilla
コンテナの定義
vanilla
の Dockerfile は、実際の Ansible のターゲットホストに近い状態を作るため、必要最低限の環境を構築します。
sudo や ssh サーバの設定については、Vagrant Docker provider で SSH ができるまでをどうぞ。
またapt-cacher
を apt のキャッシュサーバとしてプロキシに設定します。
# vanilla/Dockerfile
FROM ubuntu:16.04
RUN apt update && apt install -y --no-install-recommends \
openssh-server \
sudo \
ca-certificates \
apt-transport-https \
python \
curl
# vagrantユーザを追加
RUN useradd --create-home --user-group vagrant && \
echo -n 'vagrant:vagrant' | chpasswd && \
echo 'vagrant ALL=NOPASSWD: ALL' >/etc/sudoers.d/vagrant
# apt-cacherをプロキシに設定
RUN echo 'Acquire::http::Proxy "http://apt-cacher:3142/";' >/etc/apt/apt.conf.d/02proxy
RUN mkdir -p /var/run/sshd
CMD /usr/sbin/sshd -D
apt-cacher
コンテナの定義
apt-cacher
コンテナは、apt キャッシュのみを行うので、ssh サーバや sudo すら必要ありません。
CMD
でapt-cacher-ng
を起動する、シンプルなコンテナです。
# apt-cacher/Dockerfile
FROM ubuntu:16.04
RUN apt update && apt install -y --no-install-recommends \
ca-certificates \
apt-cacher-ng
VOLUME "/var/cache/apt-cacher-ng"
RUN mkdir -p /var/run/apt-cacher-ng
CMD /usr/sbin/apt-cacher-ng -c /etc/apt-cacher-ng foreground=1
コンテナを起動する
この状態でコンテナを立ち上げてみましょう
vagrant up
vagrant ssh
で各ホストにログインできるので、他のホストの名前が引けるか、apt-cacher が正常に動作しているかを確認してみましょう。
vagrant ssh elasticsearch-client -- getent hosts elasticsearch-master-1
vagrant ssh elasticsearch-client -- sudo apt update
Ansible を流す
Ansible playbook は平凡な Elasticsearch クラスタを構築するのみです。 詳しくはをリポジトリを参照してください。
次に Ansible を流すために、ssh_config
を作ります。
引数なしのvagrant ssh-config
だと、apt-cacher
の設定も作ろうとして失敗するため、apt-cacher
を除いたホストを指定して ssh_config を作ります。
vagrant status | \
awk '/running/ { print $1 }' | \
grep -v 'apt-cacher' | \
xargs -n1 vagrant ssh-config >ssh_config
そしてssh_config
を Ansible に渡すために、ansible.cfg
を作ります。
cat >ansible.cfg <<EOF
[ssh_connection]
ssh_args = -F ssh_config
EOF
そして流します。
ansible-playbook --inventory-file=ansible/inventories/hosts --sudo ansible/site.yml
出来上がったらクライアントノードからクラスタの状態を見てみましょう。number_of_nodes
が 11、number_of_data_nodes
が 6 となっていれば正常にクラスタが作成できています。
client_ip=$(docker inspect elasticsearch-client | \
jq -r '.[].NetworkSettings.Networks.elasticsearch.IPAddress')
curl http://${client_ip}:9200/_cluster/health | jq '.'