AnsibleでDockerコンテナを作成する「Ansible Container」を使ってみる

現在、ansible-containerプロジェクトは開発終了しています。詳細は同プロジェクトのGitHubページをご覧ください。

昨今利用者が増えている構成管理ツール「Ansible」については以前にも紹介したが、このAnsibleをDockerコンテナのビルドに使用するツール「Ansible Container」が登場した。Ansible Containerを利用することで、Dockerコンテナイメージをより簡潔に管理できるようになる。今回はこのAnsible Containerの概要と基本的な設定方法を紹介する。

Ansible Containerとは

Dockerコンテナを作成するための方法は複数ある。たとえばdockerコマンドには、「Dockerfile」という設定ファイルを元にコンテナを作成する「docker build」サブコマンドが用意されているが、これ以外にもDockerコンテナを作成するツールは複数存在する。今回紹介するAnsible Containerもその1つだ。

図1 Ansible ContainerのWebサイト
図1 Ansible ContainerのWebサイト

「docker build」コマンドではビルド設定を記述したDockerfileファイルに従ってコンテナの設定を行うのだが、Dockerfileによる設定はコンテナ内で実行するシェルコマンドを記述していくというスタイルで、複雑な設定を行おうとするとやや面倒だ。

いっぽう、コンテナではない実サーバーや仮想マシン上のサーバーにおいては、PuppetやChef、Ansibleといった構成管理ツールを利用してサーバーの設定やソフトウェアのインストールを行うことが一般的になりつつある。これらのツールでは独自に提供するDSL(Domain Specific Language、ドメイン固有言語)を使い、少ない記述量でさまざまな設定を行えるようになっている。今回紹介するAnsible Containerは、構成管理ツールであるAnsibleを使ってコンテナ内の環境設定を行えるというのが特徴となっている。

それに加えてAnsible Containerでは、Docker Composeのようにあらかじめ設定ファイルに記述しておいたコンテナの起動設定に応じてコンテナを起動したり、複数のコンテナを設定しておいた依存関係に従って起動する、といった機能も備えている。また、設定に従ってKuberneteやOpenShiftのようなコンテナクラスタ環境へのデプロイを行う機能もある。

Ansible Containerを利用する利点

Ansible Containerでは、Ansibleの設定ファイルである「Playbook」やPlaybook向けモジュールである「Role」を使ってコンテナ作成のための設定を記述できるのが特徴だ。Ansibleについて詳しくはエージェントレスでシンプルな構成管理ツール「Ansible」入門記事を参照してほしいが、Ansibleでは「タスク」や「ハンドラ」といった小さく分割した単位で実行する処理を記述できる。これによってメンテナンス性を向上させられるほか、ベアメタルサーバーや仮想サーバー向けに作成されたPlaybookやRoleを流用できるという利点もある。たとえばAnsibleではGalaxyというRole共有サービスがあり、ここではさまざまなアプリケーション向けの設定ファイルが公開されており、これをコンテナ作成用に流用できる。

Ansible Containerインストールと初歩的な使い方

それでは、実際にAnsible Containerを使ってコンテナを作成し起動する流れを紹介しよう。なお、本稿では執筆時の最新版であるAnsible Containerバージョン0.9.1を使用している。バージョン0.9では、それよりも前のバージョンと大きく構成が変わっている点には注意したい。

Ansible Containerのインストール

Ansible ContainerはPythonで実装されており、利用にはPython 2.7以降もしくは3.5以降が必要となる。インストール方法は複数あるが、最も簡単なのはPythonのパッケージ管理ツールであるpipを利用するものだ。pipはDebian/Ubuntu系であれば「python-pip」パッケージをインストールすれば利用できるようになる。Red Hat Enterprise Linux(RHEL)7系の場合、「epel-release」パッケージをインストールしてEPELリポジトリを有効にした後、次のように実行すればインストールできる。

# yum install --enablerepo=epel python2-pip

また、pip経由でAnsible Containerをインストールする場合、setuptools 20.0.0以降が必要となる。「pip list」コマンドでsetuptoolsのバージョンを確認し、もし古いバージョンのものがインストールされていた場合は次のようにしてアップデートしておこう。

# pip install --upgrade setuptools

Ansible Containerは使用するコンテナエンジンとしてDockerおよびKubernetes、Red Hat OpenShiftに対応しており、それぞれに応じたパッケージをインストールする必要がある。Dockerと組み合わせて利用する場合は、次のようにpipコマンドを実行すれば良い。

# pip install ansible-container[docker]

また、KubernetesやOpenShiftと組み合わせて利用する場合は次のように実行する。ここでは「,」の後に空白を入れるとエラーになるので注意したい。

↓Kubernetesに対応するパッケージをインストールする場合
# pip install ansible-container[docker,k8s]
↓OpenShiftに対応するパッケージをインストールする場合
# pip install ansible-container[docker,openshift]

これでpipおよび依存関係にあるパッケージのダウンロードとインストールが行われ、ansible-containerコマンドが利用できるようになる。

設定ファイルの作成

Ansible Containerでは「ansible-container」というコマンドを用いてコンテナの作成や管理を行う。たとえば設定ファイルからコンテナを作成するには「ansible-container build」コマンド、作成したコンテナを実行するには「ansible-container run」コマンドを実行すれば良い。

また、設定ファイルのひな形を作成する「ansible-container init」も用意されている。Ansible Containerでコンテナを作成する場合、まずは適当なディレクトリでこのコマンドを実行し設定ファイルを作成する。

$ mkdir ansible-container-test
$ cd ansible-container-test/
$ ansible-container init
Ansible Container initialized.

デフォルトではこのansible-container initコマンドを実行して設定ファイルを作成したディレクトリ名が「プロジェクト名」として使用される。プロジェクト名はコンテナの実行時などに使われるので、分かりやすい名前にしておこう。また、プロジェクト名にアルファベット大文字小文字および数字、「-」(ハイフン)以外を使用するとKubernetesとの連携時などにトラブルが発生するので注意しよう。

さて、ansible-container initコマンドを実行すると表1のファイルが作成される。コンテナの設定を記述するcontainer.ymlファイル以外については基本的には編集は不要で、必要に応じて設定を行えば良い。

表1 「ansible-container init」コマンドを実行すると作成されるファイル
ファイル名 説明
ansible-requirements.txt conductorコンテナ内でAnsibleによる設定処理を実行する際に必要なPythonモジュールをpipフォーマットで記述する
ansible.cfg conductorコンテナ内でansibleコマンドを実行する際の設定を記述する
container.yml コンテナの設定を記述する
meta.yml 設定ファイルの著者やライセンスといったメタデータを記述する。このメタデータは設定ファイルをAnsible Galaxyなどのリポジトリで共有する際に使われる
requirements.yml conductorコンテナ内でAnsibleによる設定を行う際に外部リポジトリからダウンロードする必要のあるモジュールの情報を記述する

それではまずは簡単なサンプルとして、Apache HTTP Server(httpd)を起動するコンテナを作成してみよう。この場合、container.ymlファイルは以下のようになる。

version: "2"
settings:
  conductor_base: debian:jessie-backports

services:
  httpd:
    from: "httpd:2.4"
    ports:
      - "8888:80"
    environment:
      PATH: "/usr/local/apache2/bin:/bin:/usr/bin:/usr/local/bin"
    command: ["httpd-foreground"]
registries: {}

このファイルの詳細なフォーマットは公式ドキュメントを参照して欲しいが、最初の「version:」は設定ファイルのバージョンを示すもので、現時点では「2」を指定しておく。次の「settings:」はビルドの際の設定を記述するものだが、ここで重要なのが「conductor_base」パラメータだ。

Ansible Containerでは、コンテナをビルドする際にこのconductor_baseパラメータで指定したコンテナイメージからコンテナを起動し、作成するコンテナのファイルシステムをこのコンテナ内にマウントして設定を行うようになっている。このコンテナは「conductorコンテナ」と呼ばれているが、こういった構成を取ることで、目的のコンテナ内にAnsibleやAnsibleの実行に必要なPython環境などをインストールすること無しに各種処理を実行できるようになっている。

このconductor_base:には、作成対象とするコンテナと同系統のディストリビューションを指定する必要がある。やや面倒だが、DockerHub等で公開されているコンテナではそのDockerfileを確認することでベースとするコンテナを確認できる。今回使用する「httpd」コンテナイメージの場合、そのDockerfileには「FROM debian:jessie-backports」と記載されているので、ここでは「debian:jessie-backports」を指定している。

続いての「services:」以下では、作成するコンテナについての設定を記述していく。ここでは複数のコンテナが指定できるが、今回は「httpd:」という名称で1つのコンテナのみを記述した。ここで指定できるパラメータは基本的にはDocker Composeの設定ファイルと同一となるが、その一部のみがサポートされている点に注意したい。サポートされているディレクティブはAnsible Containerのドキュメントに記載されている(表の「Supported?」欄にチェックが入っているものが利用可能)。

今回は、以下のパラメータを指定している。

表2 今回指定したパラメータ
パラメータ 説明
from: ベースとするコンテナイメージ
ports: 解放するポート。docker runコマンドの「-p」オプションに相当
environment: 設定する環境変数を指定する。docker runコマンドの「-e」オプションやDockerfileの「ENV」ディレクティブに相当する
command: コンテナ起動時に実行するコマンドを指定する。Dockerfileの「CMD」ディレクティブに相当する

コンテナに対して変更を加えた場合、ベースとするコンテナイメージのDockerfile内で指定されている設定内容の一部は引き継がれない点に注意が必要だ。具体的には、ENVディレクティブで指定されている環境変数や、CMDディレクティブで指定されている実行するコマンドなどは引き継がれない。今回は指定したコンテナイメージをそのまま起動するだけなのでこれらの設定は不要だが、後でコンテナに変更を加える可能性を考慮して明示的にenvironment:やcommand:パラメータを指定している。

なお、Docker Composeについては過去記事(複数のDockerコンテナを自動で立ち上げる構成管理ツール「Docker Compose」)でも紹介しているので、そちらも参照して欲しい。

コンテナの作成

設定ファイルを作成したら、続いて「ansible-container build」コマンドを実行するとコンテナを作成できる。このコマンドはDockerへのアクセスを行うので、Dockerへのアクセス権を持つユーザーで実行する必要がある。

$ ansible-container build

このコマンドを実行すると、必要なコンテナイメージのダウンロードが行われてビルドおよび設定処理が実行される。問題なく完了すると、以下のようなメッセージが表示される。

Building Docker Engine context...
Starting Docker build of Ansible Container Conductor image (please be patient)...
Parsing conductor CLI args.
Docker? daemon integration engine loaded. Build starting.       project=ansible-container-test
Building service...     project=ansible-container-test service=httpd
Service had no roles specified. Nothing to do.  service=httpd
All images successfully built.
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=528dee51cc0e8f60b56a5e168ed5dc02b188a7994d379160f75950b5649ab028 save_container=False

なお、筆者が確認したところ以下のようなメッセージが表示されてコンテナ作成に失敗するケースが見られた。

ImageNotFound: 404 Client Error: Not Found ("No such image: a")

これは、Python向けのDockerライブラリ(docker-py)バージョン2.3とAnsible Containerバージョン0.9系の組み合わせで生じる問題のようだ。この問題は、以下のようにpipコマンドを実行してdocker-pyのバージョンを2.2.1にダウングレードすることで解決できる。

# pip install docker==2.2.1

また、ホスト上のDockerのバージョンが古い場合、コンテナ作成時に次のようなエラーが出ることがある。

failed: Error response from daemon: client is newer than server (client API version: 1.24, server API version: 1.22)\n"}

この場合、ansible-containerに「--with-variables DOCKER_API_VERSION=<サーバーのAPIバージョン>」というオプションを追加すれば良い。「--with-variables」オプションはconductorコンテナ内での環境変数を設定するもので、「DOCKER_API_VERSION」環境変数は使用するDocker APIのバージョンを指定するものだ。

この例の場合、サーバーのAPIバージョンは「1.22」(「server API version:」に続くバージョン)であることが出力されたメッセージから確認できるので、次のようにコマンドを実行すれば良い。

$ ansible-container build --with-variables DOCKER_API_VERSION=1.22

コンテナの実行

作成したコンテナは、「ansible-container run」コマンドで実行できる。

$ ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.       engine=Docker? daemon
WARNING Image httpd:2.4 for service httpd not found. An attempt will be made to pull it.
PLAY [localhost] ***************************************************************
TASK [docker_service] **********************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0
All services running.   playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=af3af861d3d24d88c61a5ec84f8c36af366bd39c2d2d492b5c733af86f605147 save_container=False

設定に問題がなければ、ここでコンテナイメージのダウンロードやAnsibleによる設定処理が行われてコンテナが起動する。「docker ps」コマンドで確認すれば、設定通りコンテナが実行されていることが確認できるはずだ。

$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                  NAMES
3173b36cb9b1        httpd:2.4           "httpd-foreground"   31 seconds ago      Up 30 seconds       0.0.0.0:8888->80/tcp   ansiblecontainertest_httpd_1

もし設定の不備などでコンテナが実行できなかった場合、コンテナは実行されずに停止される。その場合、「docker logs <コンテナID>」コマンドでコンテナのログを確認してみると良いだろう。たとえば次の例では、Ansible Containerがconductorコンテナ内で実行する「docker_service」タスクでエラーが発生しているのだが、具体的なエラー内容はここでは確認できない。

$ ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.	engine=Docker™ daemon
Verifying service image	service=httpd
PLAY [localhost] ***************************************************************
TASK [docker_service] **********************************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "module_stderr": "", "module_stdout": "", "msg": "Error starting project "}
	to retry, use: --limit @/home/hylom/ansible-con/httpd_test/ansible-deployment/playbook.retry
PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1
All services running.	playbook_rc=2
Conductor terminated. Cleaning up.	command_rc=0 conductor_id=8821bab3e51b7417979403cfd08455132a918a7642b9516ea70c4670752fec1b save_container=False

docker psコマンドで確認するとコンテナが稼働していないことが分かるので、docker ps -aコマンドでコンテナのID(ここでは「bf0ecd84a7ae」)を調べ、それを引数にdocker logsコマンドを実行する。ログには「starting container process caused "exec: \"\": executable file not found in $PATH"」と記録されており、コンテナ内で実行すべきプロセスが見つからないためにコンテナが実行できなかったことが確認できる。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

$ docker ps -a
CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS                      PORTS               NAMES
bf0ecd84a7ae        httpd_test-httpd:20170614123812   ""                       23 seconds ago      Created                                         httpdtest_httpd_1

$ docker logs bf0ecd84a7ae
container_linux.go:247: starting container process caused "exec: \"\": executable file not found in $PATH"

コンテナの停止と破棄

Ansible Containerで作成したコンテナを停止させるには、「ansible-container stop」コマンドを実行する。

$ ansible-container stop
Parsing conductor CLI args.
Engine integration loaded. Preparing to stop all containers.    engine=Docker? daemon
PLAY [localhost] ***************************************************************
TASK [docker_service] **********************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0
All services stopped.   playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=da5569757f49ed33c1347e6826dfff495a73fe580347b243f89142cf6a680732 save_container=False

また、「ansible-container restart」コマンドで停止させたコンテナの再起動が、「ansible-container destroy」コマンドでコンテナの破棄が行える。

$ ansible-container destroy
Parsing conductor CLI args.
Engine integration loaded. Preparing to stop+delete all containers and built images.    engine=Docker? daemon
PLAY [localhost] ***************************************************************
TASK [docker_service] **********************************************************
changed: [localhost]
TASK [docker_image] ************************************************************
changed: [localhost]
TASK [docker_image] ************************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=3    unreachable=0    failed=0
All services destroyed. playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=aafe6c351ffed6781877eba2960bb57fbe2bc59dc4afc86ed01e4f4b61e90d40 save_container=False

複数のコンテナをAnsible Containerで管理する

Ansible Containerの設定ファイルであるcontainer.ymlには、それぞれ依存関係にある複数のコンテナの設定を記述できる。その場合、Ansible Containerでは設定された依存関係に応じてコンテナを起動する。たとえば次のcontainer.ymlファイルは、WordPressを実行するコンテナ(「wordpress」コンテナ)と、MySQLデータベースを実行するコンテナ(「wordpress-db」コンテナ)の2つのコンテナについての設定が記述されている。

version: "2"
settings:
  conductor_base: debian:jessie

services:
  wordpress:
    from: "wordpress:latest"
    ports:
      - "8888:80"
    environment:
      WORDPRESS_VERSION: 4.8
      WORDPRESS_SHA1: 3738189a1f37a03fb9cb087160b457d7a641ccb4
      WORDPRESS_DB_HOST: wordpress-db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: foobar
      WORDPRESS_DB_NAME: wordpress
    links:
      - wordpress-db:wordpress-db
    depends_on:
      - wordpress-db
    volume: ["/var/www/html"]

  wordpress-db:
    from: "mysql:latest"
    expose: [3306]
    environment:
      MYSQL_MAJOR: 5.7
      MYSQL_VERSION: 5.7.18-1debian8
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: foobar
      MYSQL_ROOT_PASSWORD: foobar

registries: {}

ここで設定しているパラメータは表3のとおりだ。

表3 今回指定したパラメータ
パラメータ 説明
environment: コンテナ実行時に設定する環境変数
links: そのコンテナにリンクするコンテナ。docker runコマンドの--linkオプションに相当
depends_on: そのコンテナが依存しているコンテナ
volume: volumeの設定。docker runコマンドの-vオプションに相当
expose: ほかのコンテナに公開するポート。docker runコマンドの--exposeコマンドに相当

wordpressコンテナの実行にはMySQLが稼動するwordpress-dbコンテナが必要なので、wordpressコンテナの「depends_on:」パラメータでそのように依存関係を設定している。また、wordpressコンテナやmysqlコンテナは環境変数で使用するデータベースの情報や作成するデータベース/テーブルを指定できるので、「environment:」パラメータでこれらを設定している。これら環境変数について詳しくはDockerHubのwordpressコンテナmysqlコンテナのリポジトリを参照して欲しい。

このような内容のcontainer.ymlファイルを用意して「ansible-container build」コマンドおよび「ansible-container run」コマンドを実行すると、次のように「mysql:latest」コンテナと「wordpress:latest」コンテナの2つが立ち上がる。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
6161a2be68cf        wordpress:latest    "docker-entrypoint.sh"   44 seconds ago      Up 42 seconds       0.0.0.0:8888->80/tcp   wordpresstest_wordpress_1
d7f002f59ca1        mysql:latest        "docker-entrypoint.sh"   45 seconds ago      Up 44 seconds       3306/tcp               wordpresstest_wordpress-db_1

また、docker logsでそれぞれのログを確認してみると、正しく各種設定が行われ、WordPressからMySQLデータベースへの接続が行えていることも確認できる。

$ docker logs d7f002f59ca1
Initializing database
  
  データベースの初期化作業が行われる
  
2017-06-13T12:20:36.217273Z 0 [Note] End of list of non-natively partitioned tables
$ docker logs 6161a2be68cf
WordPress not found in /var/www/html - copying now...
Complete! WordPress has been successfully copied to /var/www/html
Warning: mysqli::mysqli(): (HY000/2002): Connection refused in - on line 22
MySQL Connection Error: (2002) Connection refused
MySQL Connection Error: (2002) Connection refused
Warning: mysqli::mysqli(): (HY000/2002): Connection refused in - on line 22
MySQL Connection Error: (2002) Connection refused
Warning: mysqli::mysqli(): (HY000/2002): Connection refused in - on line 22
MySQL Connection Error: (2002) Connection refused
Warning: mysqli::mysqli(): (HY000/2002): Connection refused in - on line 22
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.19.0.3. Set the 'ServerName' directive globally to suppress this message
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.19.0.3. Set the 'ServerName' directive globally to suppress this message
[Tue Jun 13 12:20:37.693340 2017] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.10 (Debian) PHP/5.6.30 configured -- resuming normal operations
[Tue Jun 13 12:20:37.709003 2017] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'

Ansibleの機能を使ったコンテナの設定

さて、ここまでで紹介したコンテナの作成や起動と言った機能は、Ansible Containerを利用しなくとも、Docker Composerで実現できるものだ。Ansible Containerの本当の利点は、コンテナの実行時などに自動的にAnsibleによる設定処理を実行できる点にある。続く記事後編では、Ansibleのroleをコンテナに適用する方法について紹介する。