Ansible controller/target を Docker コンテナで構築する

はじめまして。食べログの DevOps / データサイエンスチームの爲岡 (ためおか) です。
Twitter やら GitHub やら、たいてい @zettaittenani というアカウント名で活動しています。
どうでもいいですが今のところかぶったことはないです。

今回は DevOps にフォーカスして、Ansible の実行環境を Docker を使って構築した話をします。

# 背景
食べログの開発環境構築には Ansible を利用していますが、諸々の事情で開発環境構築用の Ansible playbook を整理するタスクが発生しました。<br>
Ansible は基本的には冪等性を担保していますが、command モジュールなどはその限りではなく、新しい開発環境のための Ansible playbook を冪等性が担保される、されないを意識しながら書いていくのは面倒な作業です。
そのため、下記のような requirements を満たすテスト環境を構築しよう、という話になりました。

1. 開発中の playbook がバグを含んでいて、それによって冪等性が担保できない状態に陥った場合、playbook の実行対象となる新しい環境を簡単に立ち上げられる。
2. まっさらな環境に対して初めて playbook を実行した際の結果を簡単に確認できる。

そこで、検証用の Ansible target 環境を Docker コンテナで構築し、環境を壊してしまったらまたコンテナを作り直せばよい状態ができれば、上記2つを満たすことができ、ハッピーなのでは? と考えました。
で、手元の環境を汚すのも嫌なので、どうせなら Ansible controller も含めて Docker で作ってしまおう! というのが今回の話です。

**2019/03/13 追記**
本内容を GitHub リポジトリ ([ansible_tester](https://github.com/zettaittenani/ansible_tester)) として公開しておりますので、
コピペが面倒な方はぜひご利用ください。

# 前提
手元のホストに Docker がインストールされていることを確認します。
無ければインストールします。

``` shell
$ docker -v
Docker version 18.09.0, ...
```

# controller 構築
Ansible は ssh 経由で実行されますが、official に配布されているまっさらな`centos`には`openssh-server`, `openssh-clients`いずれもインストールされていないため、下記のような`Dockerfile`を用意します。

controller に関しては、`openssh-clients`がインストールされた`centos`ベースの image を用意します。
Ansible は、ホストが CentOS であれば EPEL リポジトリからもインストールできるそうですが、公式では`pip`でインストールしているのでそちらに合わせました。

**※2018/12/14(金) 追記**
`Dockerfile`の最後に`CMD /sbin/init`が抜けていたため、修正しました。
@ikemo さん、ありがとうございましたmm

``` dockerfile:/some_dir/controller/Dockerfile
FROM centos:7.5.1804
MAINTAINER "zettaittenani"

# infra settings
RUN yum check
RUN yum -y update
RUN yum -y install openssh-clients

# application settings
RUN curl -L https://bootstrap.pypa.io/get-pip.py | python
RUN pip install ansible

CMD /sbin/init
```

# target 構築
target には`openssh-server`がインストールされた`centos`ベースの image を用意します。
今回は開発環境での playbook の実行検証が目的なので、UsePAM は no にして root login を許可しています。
また、`EXPOSE 22`で controller からの ssh アクセスを受けられるよう設定しています。

``` dockerfile:/some_dir/target/Dockerfile
FROM centos:7.5.1804
MAINTAINER "zettaittenani"

# infra settings
RUN yum check
RUN yum -y update
RUN yum -y install openssh-server
RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config
RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config
RUN passwd -d root

# start sshd when booting
RUN /bin/systemctl enable sshd
EXPOSE 22

CMD /sbin/init
```

# docker-compose にまとめる
2つの image の build からコンテナ立ち上げまでを一挙に行いたいので、
そのための`docker-compose.yml`を用意します。

まず、テスト実行用の簡素な Ansible playbook を用意します。
`hosts`に関しては、`docker-compose`でコンテナを立ち上げる際に名前解決できるように自動で設定してくれるようで、下記のように`target`と書くだけでOKです。
なんて楽なんだ…。

``` ansible:/some_dir/ansible_dir/hosts
[managed_node]
target
```

``` ansible:/some_dir/ansible_dir/setup.yml
---
- name: Test
  hosts: managed_node
  user: root

  tasks:
    - name: Hello
      shell: date >> now.txt
```

次に下記のような`docker-compose.yml`を用意して、この Ansible playbook を controller にマウントするよう設定します。

``` dockerfile:/some_dir/docker-compose.yml
version: '3'

services:
  controller:
    build: ./controller
    privileged: true
    volumes:
      - "./ansible_dir:/ansible"
  target:
    build: ./target
    privileged: true
```

# コンテナを立ち上げる
ホストで下記コマンドを実行して Docker コンテナを立ち上げます。

``` shell
$ pwd
/some_dir
$ docker-compose up -d --build
```

これで controller と target、どちらのコンテナも立ち上がったはずです。
別々の shell から`docker-compose`コマンドで各コンテナが立ち上がったことを確認します。

``` shell
# @shell 1 (controller 用)
$ docker-compose exec controller /bin/bash
$ [root@xxxxxxxxxxxx ~]#

# @shell 2 (target 用)
$ docker-compose exec target /bin/bash
$ [root@xxxxxxxxxxxx ~]#
```

# 疎通確認
コンテナは用意できたので最後に疎通確認です。
controller のコンテナから target に対して playbook をテスト実行します。

``` shell
# @shell 1 (controller 用)
[root@xxxxxxxxxxxx ~]# cd ansible
[root@xxxxxxxxxxxx ~]# ansible-playbook -k ./setup.yml -vvv -i ./hosts
...

PLAY RECAP *********************************************************************************************************************************************************************************************************************************************************************
xxx.xxx.xxx.xxx               : ok=2    changed=1    unreachable=0    failed=0
```

実行できました。
次に target の Docker コンテナでテスト結果を確認してみます。

``` shell
# @shell 2 (target 用)
[root@xxxxxxxxxxxx ~]# cat now.txt
xxxx年 xx月 xx日 x曜日 xx時xx分xx秒 JST
```

はい、確かに controller から target に対して playbook が実行できていますね。

playbook 自体はマウントしているものなので、手元のホストの shell 上で編集するなりなんなりできますし、実行する際は controller のコンテナで`ansible-playbook`コマンドを叩けば良いです。
実行結果は target のコンテナ上で確認できますし、target が壊れたら`docker-compose down/up`するだけでOK。
これはかなり幸せな環境ではないでしょうか!


最後までお読みいただきありがとうございました。
Happy Ansible Life!!