Ansible 2.0の新機能 Docker Connection Pluginを使ってDockerコンテナの構成管理をしてみた

ANSIBLE

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

藤本です。

※ 本ブログ執筆時点ではAnsible 2.0はrc3です。GAリリース時にこのブログの通りにいかないことがあるかもしれませんのでご注意ください。

概要

Ansible 2.0からDockerのConnection Pluginが追加されました。Ansible 1.x系では標準モジュールだけでDockerコンテナをリモートから構成管理したい場合、コンテナ内でsshdを起動して、SSHポートをDockerホストと紐付けたりする必要があり、エージェントレス(ホストの構成を変えずに)が特徴のAnsibleとしては微妙な感じでした。Ansible 2.0で追加されたDockerのConnection Pluginを利用すれば、Docker Remote APIを利用したコンテナのプロビジョニングが可能となりました。Ansible実行環境からDockerホストへRemote APIを実行できる環境であれば、Dockerコンテナでsshdを起動しておく必要がありません。

今回はOSX上のVirtualBox上のDockerホスト(Docker Toolbox)に対して、ansible-playbookでWEBサーバをプロビジョンすることを目標します。

Docker Connection Plugin

Docker Connection Pluginはソースコードをザッと目を通した限り、ファイル転送にdocker cp、コマンド実行をdocker execで実行しているようです。インベントリファイルではDockerホストを指定するのではなく、コンテナ名を指定します。そのためデフォルトではローカルホスト上のDockerコンテナに対して、処理を実行します。リモートホスト上のDockerコンテナに対して処理を実行したい場合、環境変数のDOCKER_HOSTでリモートホストを指定する必要があります。

環境

  • Ansible実行環境
    • OS : OSX
    • Python : 2.7.10
    • Ansible : v2.0.0-0.8.rc3
  • Dockerホスト
    • IPアドレス : 192.168.99.100
    • on VirtualBox on OSX(Docker Toolboxにより構築)

やってみた

ansibleインストール

今回は2.0系を利用したいのでpip install ansibleではなく、githubから最新版のリリースをインストールします。

$ pip install git+https://github.com/ansible/ansible.git
Collecting git+https://github.com/ansible/ansible.git
  Cloning https://github.com/ansible/ansible.git to /var/folders/vp/2b51_dy91r3b5bbjsv1lbj4c0000gq/T/pip-nNfLOk-build
Collecting paramiko (from ansible==2.1.0)
  Using cached paramiko-1.16.0-py2.py3-none-any.whl
Collecting jinja2 (from ansible==2.1.0)
  Using cached Jinja2-2.8-py2.py3-none-any.whl
Collecting PyYAML (from ansible==2.1.0)
Requirement already satisfied (use --upgrade to upgrade): setuptools in /Users/fujimoto.shinji/.pyenv/versions/ansible/lib/python2.7/site-packages (from ansible==2.1.0)
Collecting pycrypto>=2.6 (from ansible==2.1.0)
Collecting ecdsa>=0.11 (from paramiko->ansible==2.1.0)
  Using cached ecdsa-0.13-py2.py3-none-any.whl
Collecting MarkupSafe (from jinja2->ansible==2.1.0)
Installing collected packages: ecdsa, pycrypto, paramiko, MarkupSafe, jinja2, PyYAML, ansible
  Running setup.py install for ansible
Successfully installed MarkupSafe-0.23 PyYAML-3.11 ansible-2.1.0 ecdsa-0.13 jinja2-2.8 paramiko-1.16.0 pycrypto-2.6.1

$ pip list
ansible (2.1.0)
ecdsa (0.13)
Jinja2 (2.8)
MarkupSafe (0.23)
paramiko (1.16.0)
pip (7.1.0)
pycrypto (2.6.1)
PyYAML (3.11)
setuptools (18.0.1)
wheel (0.24.0)

ansibleがインストールされました。(なぜか2.1.0と表示される。。。)

環境変数設定

Docker Remote APIを外部ホストに実行したい場合、dockerコマンドのオプションで指定可能ですが、Docker Connection Pluginではオプション指定することができません。その代わりに環境変数に設定することで指定が可能です。Dockerホスト、TLSの有効化、およびSSH鍵関連パスを環境変数に設定します。

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/fujimoto.shinji/.docker/machine/machines/docker01"

ちなみに以下のコマンドでも同様の設定が可能です。

$ eval "$(docker-machine env docker01)"

Remote APIの接続確認を実施します。

$ docker info
Containers: 1
Images: 5
Server Version: 1.9.1
Storage Driver: aufs
 Root Dir: /mnt/sda1/var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 7
 Dirperm1 Supported: true
Execution Driver: native-0.2
Logging Driver: json-file
Kernel Version: 4.1.13-boot2docker
Operating System: Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015
CPUs: 1
Total Memory: 1.956 GiB
Name: default
ID: 6E4I:7UKE:4RFA:HPFR:NJSN:ASG4:IRZI:J7DY:JMDZ:52XW:CCZH:UFJ2
Debug mode (server): true
 File Descriptors: 12
 Goroutines: 18
 System Time: 2015-12-24T06:45:56.410777786Z
 EventsListeners: 0
 Init SHA1:
 Init Path: /usr/local/bin/docker
 Docker Root Dir: /mnt/sda1/var/lib/docker
Labels:
 provider=virtualbox

情報取れました。接続できています。

インベントリファイルの作成

インベントリファイルにはDockerコンテナ作成用にDockerホスト、Dockerコンテナのプロビジョニング用にコンテナID、もしくはコンテナ名を指定します。今回はコンテナ名で指定します。

$ vi hosts
---
[docker_host]
docker01

[container]
web01
---

$ vi ssh_config
---
Host docker01
  HostName 192.168.99.100
  User docker
  UserKnownHostsFile /dev/null
  IdentityFile ~/.docker/machine/machines/docker01/id_rsa
  StrictHostKeyChecking no
---

$ vi ansible.cfg
---
[defaults]
inventory = hosts

[ssh_connection]
ssh_args = -F ssh_config
scp_if_ssh = True
---

Dockerホストへ接続確認します。

$ ansible -i hosts docker01 -m ping
docker01 | FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "",
    "module_stdout": "sh: /usr/bin/python: not found\r\n",
    "msg": "MODULE FAILURE",
    "parsed": false
}

boot2dockerのOSイメージはpythonがインストールされていない!!というかこのOSは何者?

・・・調べたら、Tiny Core LinuxというOSらしいです。エクステンションと呼ばれるパッケージをtce-loadコマンドでインストール出来るようです。

$ ssh -F ssh_config docker01
Warning: Permanently added '192.168.99.100' (RSA) to the list of known hosts.
                        ##         .
                  ## ## ##        ==
               ## ## ## ## ##    ===
           /"""""""""""""""""\___/ ===
      ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
           \______ o           __/
             \    \         __/
              \____\_______/
 _                 _   ____     _            _
| |__   ___   ___ | |_|___ \ __| | ___   ___| | _____ _ __
| '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
| |_) | (_) | (_) | |_ / __/ (_| | (_) | (__|   <  __/ |
|_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
Boot2Docker version 1.9.1, build master : cef800b - Fri Nov 20 19:33:59 UTC 2015
Docker version 1.9.1, build a34a1d5

docker@docker01:~$ tce-load -wi python
python.tcz.dep OK
tk.tcz.dep OK
readline.tcz.dep OK
Downloading: libffi.tcz
Connecting to repo.tinycorelinux.net (89.22.99.37:80)
libffi.tcz           100% |**************************************************************************************************************************************| 16384   0:00:00 ETA
libffi.tcz: OK

(略)

Downloading: python.tcz
Connecting to repo.tinycorelinux.net (89.22.99.37:80)
python.tcz           100% |*******************************************************************************************************************************************************************************************************************************|  7980k  0:00:00 ETA
python.tcz: OK

docker@docker01:~$ python --version
Python 2.7.10

docker@docker01:~$ curl https://bootstrap.pypa.io/get-pip.py | sudo python -
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1379k  100 1379k    0     0  1831k      0 --:--:-- --:--:-- --:--:-- 1829k
Collecting pip
  Downloading pip-7.1.2-py2.py3-none-any.whl (1.1MB)
    100% |################################| 1.1MB 401kB/s
Collecting setuptools
  Downloading setuptools-19.1.1-py2.py3-none-any.whl (463kB)
    100% |################################| 466kB 774kB/s
Collecting wheel
  Downloading wheel-0.26.0-py2.py3-none-any.whl (63kB)
    100% |################################| 65kB 4.9MB/s
Installing collected packages: pip, setuptools, wheel
Successfully installed pip-7.1.2 setuptools-19.1.1 wheel-0.26.0

docker@docker01:~$ sudo pip install docker-py
Collecting docker-py
  Downloading docker-py-1.6.0.tar.gz (63kB)
    100% |################################| 65kB 2.2MB/s
Collecting requests>=2.5.2 (from docker-py)
  Downloading requests-2.9.1-py2.py3-none-any.whl (501kB)
    100% |################################| 503kB 748kB/s
Collecting six>=1.4.0 (from docker-py)
  Downloading six-1.10.0-py2.py3-none-any.whl
Collecting websocket-client>=0.32.0 (from docker-py)
  Downloading websocket_client-0.34.0.tar.gz (193kB)
    100% |################################| 196kB 1.8MB/s
Building wheels for collected packages: docker-py, websocket-client
  Running setup.py bdist_wheel for docker-py
  Stored in directory: /root/.cache/pip/wheels/e6/d9/a1/fe57c3e479387975813b221e7f01dcaa0f73e9149744472c71
  Running setup.py bdist_wheel for websocket-client
  Stored in directory: /root/.cache/pip/wheels/c2/af/03/d3899019a2100d8b39625e578e8680f444096278d16b7dc4a4
Successfully built docker-py websocket-client
Installing collected packages: requests, six, websocket-client, docker-py
Successfully installed docker-py-1.6.0 requests-2.9.1 six-1.10.0 websocket-client-0.34.0

docker@docker01:~$ sudo ln -s /usr/local/bin/python /usr/bin/python

Pythonがインストールされました。ついでにpip、docker-pyをインストールして、pythonコマンドのパスにシンボリックリンク張りました。

もう一度、確認します。

$ ansible -i hosts docker01 -m ping
docker01 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

接続できました。ちなみにこの接続はDockerホストへの接続なので、まだ今回の主役のDocker Connection Pluginは関係ありません。SSH Connection Pluginによる接続です。

Playbook作成

Dockerコンテナの作成とプロビジョニングを一つのPlaybookにまとめて記述します。
まずはDockerコンテナの作成部分です。コンテナのプロビジョニングはAnsibleに任せたいのでただ起動させるだけです。

$ vi site.yml
---
- hosts: docker_hosts
  become: yes
  remote_user: docker
  tasks:
    - name: deploy centos container
      docker: image=centos:centos6 name=web01 ports=80:80 expose=80 tty=yes

実行してみます。

$ ansible-playbook site.yml

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [docker01]

TASK [deploy centos container] *************************************************
changed: [docker01]

PLAY RECAP *********************************************************************
docker01                   : ok=2    changed=1    unreachable=0    failed=0

コンテナが起動していることを確認してみます。

docker@docker01:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                NAMES
6636521e80f0        centos:centos6      "/bin/bash"         3 minutes ago       Up 3 minutes        0.0.0.0:80->80/tcp   web01

起動していますね。

ここからが主題です。

先ほどのPlaybookにコンテナへの設定内容を追記します。Docker Connection Pluginを利用する設定としてはconnection: dockerを指定するだけです。コンテナの設定内容はシンプルでhttpd、filebeatをインストールして、index.html、filebeatの設定ファイルを置き換えて、サービスを起動するだけです。

$ vi site.yml
---
- hosts: containers
  connection: docker # ここだけ
  tasks:
    - name: install packages
      yum: name={{item}} state=installed
      with_items:
        - httpd
        - "https://download.elastic.co/beats/filebeat/filebeat-1.0.1-x86_64.rpm"
    - name: copy filebeat.yml
      copy: src=filebeat.yml dest=/etc/filebeat/filebeat.yml backup=yes
    - name: start services
      service: name={{item}} enabled=yes state=started
      with_items:
        - httpd
        - filebeat

それではPlaybookを実行してみましょう。

$ ansible-playbook site.yml

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [docker01]

TASK [deploy centos container] *************************************************
ok: [docker01]

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [web01]

TASK [install packages] ********************************************************
changed: [web01] => (item=[u'httpd', u'https://download.elastic.co/beats/filebeat/filebeat-1.0.1-x86_64.rpm'])

TASK [copy index.html] *********************************************************
changed: [web01]

TASK [copy filebeat.yml] *******************************************************
changed: [web01]

TASK [start services] **********************************************************
changed: [web01] => (item=httpd)
changed: [web01] => (item=filebeat)

PLAY RECAP *********************************************************************
docker01                   : ok=2    changed=0    unreachable=0    failed=0
web01                      : ok=4    changed=4    unreachable=0    failed=0

Webアクセスしてみます。

$ cat index.html
index page on docker container

$ curl 192.168.99.100
index page on docker container

コピーしたindex.htmlの内容が返ってきました。

まとめ

いかがでしたでしょうか。
今回の主題ではないところで躓いてしまって、伝えたい内容がブレブレになった気もしますが、、、今まではDockerコンテナの構築はDockerfile、ツールを利用したい人はPackerといったところが主流だったでしょうか。ただプロビジョニングツールとしてはAnsibleを利用している方も多いと思います。Docker Connection Pluginの導入により、DockerコンテナのプロビジョニングにAnsibleを採用するケールも増えるのではないでしょうか。