DockerコンテナでAnsibleをテストする
Ansible 2.0になり、Docker connection pluginが標準で入りました。これにより、Docker内にsshdを立てることなくAnsibleを直接実行できるようになりました。
すでに導入されている方も多く、かなり今更ではありますが、Dockerコンテナに対してAnsibleを実行してテストする方法についてここに記します。
Dockerに対する場合の制限
まず最初にAnsibleをDockerコンテナに対して実行する際の制限についてです。
基本的にはすべての機能が使えます。ただ、以下の制限があります。
/etc/hosts, /etc/resolv.conf, /etc/hostnameは書き換えできない
これらのファイルはDockerがbind mountしており、書き換えられるが、置き換えることは出来ないため。/etc/hostnameが変更できないため、hostnameモジュールでの変更もできない。
また、実行するイメージによっては少なくとも以下の問題があります。他にもいろいろあるかもしれません。このあたりはDocker自体に関する問題で、Ansible特有の問題ではないので、なんとか解決して下さい。
systemdのserviceが起動できない
D-busがないため、Failed to connect to bus: No such file or directoryと言われる。upstartやrc initは起動できる。CAP_SYS_ADMINcapabilityが必要
sudoがない場合がある
付け加えるならば、まっさらなイメージからテストをしていくとダウンロードなどに時間がかかってしまいますので、適宜設定を施したイメージを事前に用意しておくとテストの時間が減ると思います。
Inventory
さて、本題です。Ansibleの docker connection pluginを使うには
web ansible_connection=docker ansible_host=<コンテナID>
というように、ansible_connection=dockerとするだけですぐに使えます。しかし、ansible_hostにはコンテナIDを指定しなければいけません。DockerのコンテナIDは本当に一時的なものなのでここに書くのはデバッグ時だけです。
これを回避するためにはdockermoduleを使ってコンテナを立ち上げ、add_hostでグループを生成することも可能ですが、playbookをテスト用に編集する必要が出てきます。それでもいい場合もありますが、せっかくですからDocker dynamic inventoryを使いましょう。
Docker dynamic inventory
GitHubのansibleのリポジトリからdocker.pyを取得し実行権限を付与しておきます。docker.ymlはなくても構いません。
# docker containerを立ち上げる
$ docker run --name <hostsでの指定対象> ubuntu:16.04 /bin/sleep 3600
# 立ち上げたdocker containerに対してansibleを実行する
$ ansible-playbook -i docker.py something.yml
で現在起動しているコンテナのnameからコンテナIDを取得して、使ってくれます。docker.pyで取得できる情報の一部を以下に示します。name以外にもimageやコンテナIDでグループが作成されていることが分かります。しかし、今回はテストなので、通常のグループと同じ名前を使いたいためnameを使います。
"web": [
"web"
],
"image_ubuntu:16.04": [
"web"
],
"zzzzzd5bed36e033fac72d52ae115a2da12f3e08b995325182398aae2a95a": [
"web"
],
"zzzzz114d5bed": [
"web"
],
"running": [
"web"
],
"docker_hosts": [
"unix://var/run/docker.sock"
],
"unix://var/run/docker.sock": [
"web"
],
Inventory Directory
dynamic inventoryをつかうと、inventoryファイルで指定してあるgroup_varsが使えなくなってしまうのではないか、と思うかもしれません。
その場合ディレクトリを分けてdocker.pyと静的なファイルを入れておきます。そうしておいてinventoryファイルとしてディレクトリを指定すると、静的なファイルとdynamic inventoryの両方から情報をとってくれます。CIでのみ使う場合はCI用のInventoryとして、ディレクトリを分けておくと扱いやすくなると思います。
CircleCI
では、CIを通してみましょう。CircleCIを試します。circle.ymlはこんな感じです。
machine:
services:
- docker
environment:
DOCKER_API_VERSION: "1.20"
dependencies:
pre:
- docker pull ubuntu:16.04
- sudo pip install ansible ansible-lint docker-py
test:
override:
- docker run -d --name web ubuntu:16.04 /bin/sleep 3600
- ansible-playbook -i inventory_docker web.yml test.yml --syntax-check
- ansible-lint test.yml
- ansible-playbook -i inventory_docker web.yml test.yml -l running
--syntax-checkやansible-lintもついでに行っています。DOCKER_API_VERSIONはCircleCIのdockerが古いために設定しています。また、docker runで--name webとしています。これは、通常使うplaybookではweb グループに対して実行しており、そのplaybookを変えたくないからです。
これでpushすると、
fatal: [web]: FAILED! => {"changed": false, "failed": true, "rc": 1, "stderr": "Error response from daemon: Unsupported: Exec is not supported by the lxc driver\n", "stdout": "", "stdout_lines": []}
と怒られてしまいました。そうです。CircleCIはlxc driverを使っており、Docker connection pluginが使うdocker execが使えないのです。
ということで諦めました。
他のCIサービスはwerckerとかdrone.ioありますが、これらはそもそもCIでDockerを使っており、Docker in Dockerになってしまいいろいろ大変です。
別解: 自前Dockerホストを用意する
あるいは、circle.ymlのenvironmentでDOCKER_HOSTを設定することで、CircleCI外に立てたDockerホストに対して実行することもできます。次に説明するGitLabを使うよりも手軽かもしれませんが、セキュリティ設定をしっかりする点は特に気をつけて下さい。
GitLab
GitLabが最近流行りですね。最近CIも付いたのでこれを使う場合も紹介します。
gitlabやgitlab CI runner自体のインストールは省略します。Docker内で実行するCI Runnerもありますが、それではDocker in Dockerになってしまいますので、今回の用途ではshell runnerにすることを忘れないで下さい。
結論からいうと、runnerの設定がしっかりしてあれば、このような.gitlab-ci.ymlで動きます。ほとんどCircleCIと変わらず、after_scriptによるコンテナの削除が入っているぐらいです。
before_script:
- pip install ansible ansible-lint docker-py
stages:
- build
build_job:
stage: build
script:
- docker run -d --name web ubuntu:16.04 /bin/sleep 3600
- ansible-playbook -i inventory_docker web.yml test.yml --syntax-check
- ansible-lint test.yml
- ansible-playbook -i inventory_docker web.yml test.yml -l running
after_script:
- docker kill `docker ps -aq`
- docker rm `docker ps -aq`
runnerの設定としてはsudo gpasswd -a $USER dockerをしてsudoをしなくてもdockerを使えるようにしておくとよいと思います。
追記: Travis CI
@auchidaさんから`Travis CI<https://travis-ci.org/>`_ならば使える、ということをお聞きしました。auchidaさんのリポジトリを参考にしました。
ポイントはsudo: requiredを入れることのようです。
しかし、たぶんvirtualenvとsystemとがなにかおかしいようで、docker dynamic inventoryを実行時に以下のエラーが出ました。そのうち直したいと思います。
class AnsibleDockerClient(Client):
NameError: name 'Client' is not defined
ありがとうございました。
まとめ
この記事では、Dockerコンテナを利用してAnsibleのテストを行う方法についてご紹介しました。
- Ansible2.0からDockerコンテナに対して直接ansibleを実行できる
- 一部の制限はあるが、Docker上でも問題なく動く
- CircleCIでは動かないので以下の三つの方法を紹介
- 自前でDockerホストを用意する
- GitLabなどを立てる
- Travis CIを使う
おまけ: ansible-lintのルール
最近弊社内でPlaybookの書き方を統一するためにansible-lintのルールの制定を始めました。ansible-lint-rulesにて公開しています。
Long descriptionがないなど、まだ途中ではありますが、使ってみてIssueやPRをいただけると大変ありがたく思います。