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_ADMIN capabilityが必要

  • sudoがない場合がある

付け加えるならば、まっさらなイメージからテストをしていくとダウンロードなどに時間がかかってしまいますので、適宜設定を施したイメージを事前に用意しておくとテストの時間が減ると思います。

Inventory

さて、本題です。Ansibleの docker connection pluginを使うには

web ansible_connection=docker ansible_host=<コンテナID>

というように、 ansible_connection=docker とするだけですぐに使えます。しかし、ansible_hostにはコンテナIDを指定しなければいけません。DockerのコンテナIDは本当に一時的なものなのでここに書くのはデバッグ時だけです。

これを回避するためには docker moduleを使ってコンテナを立ち上げ、 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の environmentDOCKER_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 ならば使える、ということをお聞きしました。 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をいただけると大変ありがたく思います。

Comments

comments powered by Disqus