このエントリーをはてなブックマークに追加

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の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をいただけると大変ありがたく思います。