VagrantでAnsibleを加速させる

    Vagrant, Docker and Ansible

    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 すら必要ありません。 CMDapt-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 '.'

    Profile picture

    Shin'ya Ueoka

    B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。