Packer + Docker + Ansibleでハマる
Packer + Ansible + Dockerの組み合わせは茨の道
Scalaが動くDockerコンテナを作りたくてトライしてみました。
rebuild.fm 167でもDockerfileを使いたくないという話がポロっと出ていましたが、
私もインフラのセットアップは愛用しているAnsibleに統一したく、Dockerfileをあまり使いたくなかったのでPackerを使ってみました。
(後で書きますがDockerfileでもAnsibleは使用出来ます。この時はまだ知らなかった...)
PackerはHashiCorp製のプロビジョナと出力されるビルドイメージを自由に組み合わせられるツールとして有名ですよね。
ぶっちゃけDocker For Mac入れてPackerのjsonファイルにbuilders: docker
みたいな事書いてplaybookのパス指定すれば楽勝だと思っていたんですよ。
(そもそもPackerってそのためのツールですよねw)
個人的にPackerを利用するモチベーションとして、
- VagrantBox、AMI、Docker等ビルド対象固有の技術的マネジメントをせずとも、プロビジョナの記述に注力していれば目的のイメージをうまいことビルドしてくれる。
- ビルド対象によって構成管理の形式やツール(プロビジョナ側)が左右されない。
という利点を期待していたのですが、
実際にはエラーに躓いた場合、そのツールの知識を用いて問題を解決して行かなければならず、
最低限のplaybookを流すだけでも苦労したので備忘録として残しておきます。
(長々とログ出力を貼っており全体としてかなり読みにくくなっております。)
環境
- Docker for Mac 1.12.3
- Packer 0.10.0
- Ansible 2.0.1.0
前提
packer build
コマンドでのdockerコンテナイメージ作成で躓いている- packerの設定ファイルは
packer-scala.json
DockerコンテナへのSSH Connectionが上手くいかない
AnsibleのPlaybook実行時、Dockerホスト → DockerコンテナへのSSHが失敗するようです。
{ "variables": { "ansible_host": "default" }, "builders": [{ "type": "docker", "image": "ubuntu", "commit": "true" }], "provisioners": [ { "type": "ansible", "playbook_file": "playbook.yml" }] }
packer build packer-scala.json
のコンソール出力
ryota.murakami@murakami ~/r/a/otsukimi> packer build packer-scala.json docker output will be in this color. ==> docker: Creating a temporary directory for sharing data... ==> docker: Pulling Docker image: ubuntu docker: Using default tag: latest docker: latest: Pulling from library/ubuntu docker: Digest: sha256:7a64bc9c8843b0a8c8b8a7e4715b7615e4e1b0d8ca3c7e7a76ec8250899c397a docker: Status: Image is up to date for ubuntu:latest ==> docker: Starting docker container... docker: Run command: docker run -v /Users/ryota.murakami/.packer.d/tmp/packer-docker968054136:/packer-files -d -i -t ubuntu /bin/bash docker: Container ID: ef33e308d6bfde09f42c42665f0d1d4fd816e8fcd5d56866f73a5c0b4a608a2d ==> docker: Provisioning with Ansible... ==> docker: SSH proxy: serving on 127.0.0.1:64026 ==> docker: Executing Ansible: ansible-playbook /Users/ryota.murakami/repository/ansible/otsukimi/playbook.yml -i /var/folders/b0/_7l4w13s7vl3vl9h107skyvh0000gp/T/packer-provisioner-ansible048561848 --private-key /var/folders/b0/_7l4w13s7vl3vl9h107skyvh0000gp/T/ansible-key800601981 docker: docker: PLAY *************************************************************************** docker: docker: TASK [setup] ******************************************************************* docker: SSH proxy: accepted connection ==> docker: authentication attempt from 127.0.0.1:64027 to 127.0.0.1:64026 as ryota.murakami using none ==> docker: unauthorized key ==> docker: authentication attempt from 127.0.0.1:64027 to 127.0.0.1:64026 as ryota.murakami using publickey ==> docker: unauthorized key ==> docker: authentication attempt from 127.0.0.1:64027 to 127.0.0.1:64026 as ryota.murakami using publickey ==> docker: authentication attempt from 127.0.0.1:64027 to 127.0.0.1:64026 as ryota.murakami using publickey ==> docker: starting sftp subsystem docker: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh", "unreachable": true} docker: to retry, use: --limit @/Users/ryota.murakami/repository/ansible/otsukimi/playbook.retry docker: docker: PLAY RECAP ********************************************************************* docker: default : ok=0 changed=0 unreachable=1 failed=0 docker: ==> docker: shutting down the SSH proxy ==> docker: Killing the container: ef33e308d6bfde09f42c42665f0d1d4fd816e8fcd5d56866f73a5c0b4a608a2d Build 'docker' errored: Error executing Ansible: Non-zero exit status: exit status 3 ==> Some builds didn't complete successfully and had errors: --> docker: Error executing Ansible: Non-zero exit status: exit status 3 ==> Builds finished but no artifacts were created.
unauthorized key
、SSH Error: data could not be sent to the remote host.
といったフレーズでSSH Connectionが上手く行っていない事が分かります。
ログをより詳細に出力する-vvvv
オプションをAnsibleに与えて再度実行してみましょう。
{ "variables": { "ansible_host": "default" }, "builders": [{ "type": "docker", "image": "ubuntu", "commit": "true" }], "provisioners": [ { "type": "ansible", "playbook_file": "playbook.yml", "extra_arguments" : "-vvvv" }] }
packer build packer-scala.json
を実行すると-vvvv
により新たに以下のメッセージが出力されました。
実行されたsshコマンドの詳細です。(横長)
docker: <127.0.0.1> SSH: EXEC ssh -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=65453 -o 'IdentityFile="/var/folders/b0/_7l4w13s7vl3vl9h107skyvh0000gp/T/ansible-key210798578"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ryota.murakami -o ConnectTimeout=10 -o ControlPath=/Users/ryota.murakami/.ansible/cp/ansible-ssh-%h-%p-%r -tt 127.0.0.1 '/bin/sh -c '"'"'( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1482417280.49-114181904662247 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1482417280.49-114181904662247 `" )'"'"''
使用されていたsshコマンドのオプションは以下です。
オプション | 効果 |
---|---|
-C | データを圧縮して通信 |
-vvv | 冗長表示(verbose) |
-o | ssh_configで設定可能なオプションを指定できる |
-o ControlMaster=auto | Master接続が存在すれば使用、なければ作成 |
-o ControlPersist=60s | Master接続の切断後の保持秒数 |
-o StrictHostKeyChecking=no | 使用した鍵が~/.ssh/known_hostsに自動登録される |
-o Port=65453 | 使用するポート |
-o 'IdentityFile="filepath"' | 使用する鍵ファイル |
-o KbdInteractiveAuthentication=no | キーボード対話的認証を使用しない |
-o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey | 認証方法の優先順位を定義 |
-o PasswordAuthentication=no | パスワード認証を禁止 |
-o User=ryota.murakami | ログインユーザー |
-o ConnectTimeout=10 | ssh接続時のタイムアウト秒数 |
-o ControlPath=filepath | ssh接続のセッション制御用ソケットのファイルパス |
これを見る限りsshコマンドには特に問題がある訳ではなさそうです。
Dockerコンテナでsshdが起動していない
コンソール出力のdocker run
コマンドを見れば直ぐに分かった話なのですがDockerコンテナでsshdが起動していないのでssh接続出来るはずがありません。
docker: Run command: docker run -v /Users/ryota.murakami/.packer.d/tmp/packer-docker685009806:/packer-files -d -i -t ubuntu /bin/bash
重要なところは/bin/bash
の部分で、docker run
コマンドで立ち上がるコンテナはここで指定した1つのプロセスのみ動いている状態となります。
コンテナの中でtopコマンドを叩いた結果がこちらです。
ご覧のようにbashしか起動していない事が分かります。
top - 08:39:38 up 3 days, 21:02, 0 users, load average: 0.00, 0.00, 0.00 Tasks: 2 total, 1 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni, 99.3 id, 0.7 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 2045844 total, 1514688 free, 77800 used, 453356 buff/cache KiB Swap: 987960 total, 987960 free, 0 used. 1664680 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 18240 3240 2760 S 0.0 0.2 0:00.07 bash 9 root 20 0 36692 3120 2640 R 0.0 0.2 0:00.01 top
公式ドキュメントではbuildersにAmazon AMIを用いたサンプルのみが掲載されていますが、
Amazon AMIの部分をdockerに変更しただけでは動かない仕組みのようです。
不親切な事に公式ドキュメントにはこの点についての説明が一切ありません。
AnsibleをDockerに適用するにはdocker-connection-driverが必要
この件についてググったところ以下のスレッドにたどり着きました。
コメントを読んでいくとdockerコンテナに対してansibleを実行する場合はdocker-connection-driverが必要であるというヒントが。
http://blog.oddbit.com/2015/10/13/ansible-20-the-docker-connection-driver/
なるほど、Ansible設定の問題だったわけですね。
Packerでエラーが出た、という捉え方をしていたのでこの情報にたどり着くまでに時間が掛かりました。
playbookにconnection: docker
の設定を追加して再度packer build packer-scala.json
コマンドを実行してみます。
- playbook.yml
- hosts: all connection: docker roles: - { role: scala }
dockerコンテナにsshコマンドを実行する事は無くなりましたが、ディレクトリ作成時にパーミッションエラーが発生しました。
- コンソール出力
docker: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "Authentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in \"/tmp\". Failed command was: ( umask 22 && mkdir -p \"` echo $HOME/.ansible/tmp/ansible-tmp-1482486284.49-101790217276259 `\" && echo \"` echo $HOME/.ansible/tmp/ansible-tmp-1482486284.49-101790217276259 `\" ), exited with result 1", "unreachable": true}
docker exec
コマンドがrootで実行されていないからでしょうか...
良く分からないので上に貼ったgithub issueのコメントに投下されていたサンプル通りにファイルを編集してみました。(最初からやっとけ感)
{ "variables": { "ansible_host": "default", "ansible_connection": "docker" }, "builders":[ { "type": "docker", "image": "centos:7", "commit": true, "run_command": [ "-d", "-i", "-t", "--name", "{{user `ansible_host`}}", "{{.Image}}", "/bin/bash" ] } ], "provisioners": [ { "type": "ansible", "groups": [ "webserver" ], "playbook_file": "./webserver.yml", "extra_arguments": [ "--extra-vars", "ansible_host={{user `ansible_host`}} ansible_connection={{user `ansible_connection`}}" ] } ] }
packer build packer-scala.json
を実行するとumask 22~
の下りは無事成功したようです。
変更前とは--name
オプションが追加されただけに見えますが、それでパーミッション関連のエラーが解決する理由は分からんですね。
ubuntuのDockerコンテナにpythonが入っていない
ご存知の通りansibleはリモートホストでpythonを使用してタスクを実行するのでpythonが必要です。
ubuntuは最初から/usr/bin/python
にpythonが入っているはずなのですが、packerがfetchしてきたubuntuのdockerコンテナには入っていないようです。
- コンソール出力
docker: fatal: [default]: FAILED! => {"changed": false, "failed": true, "invocation": {"module_name": "setup"}, "module_stderr": "/bin/sh: 1: /usr/bin/python: not found\n", "module_stdout": "", "msg": "MODULE FAILURE", "parsed": false}
"type": "shell"
プロビジョナでpythonをインストールします。
{ "variables": { "ansible_host": "default", "ansible_connection": "docker" }, "builders": [{ "type": "docker", "image": "ubuntu", "commit": "true", "run_command": [ "-d", "-i", "-t", "--name", "{{user `ansible_host`}}", "{{.Image}}", "/bin/bash" ] }], "provisioners": [{ "type": "shell", "inline": [ "apt-get -y update", "apt-get -y install python-dev" ]}, { "type": "ansible", "groups": ["scala"], "playbook_file": "playbook.yml", "extra_arguments" : [ "-vvv", "--extra-vars", "ansible_host={{user `ansible_host`}} ansible_connection={{user `ansible_connection`}}" ] }] }
もう一度packer build packer-scala.json
を実行...
やっとこさ成功
だいぶ苦戦しましたがdockerにplaybookを流す事が出来ました!
(1個failedしているのはjdkが入っていなくsbtインストールtaskがコケている様子です)
docker: PLAY RECAP ********************************************************************* docker: default : ok=12 changed=9 unreachable=0 failed=1
今更ですがDockerfileでもAnsibleを使えるみたいです
Dockerfileを使うとAnsibleを使えないと思い込んでいたのですが、
RUN
でansible-playbookコマンドを実行すれば普通に使えるんですね...
最初にこちらを選択していればここまでハマらなかったかもしれませんね。
まとめ
Ansilbeプロビジョナの公式ドキュメントを見てAmazon AMIをdockerに替えただけだと同じような現象でハマる方が多いのでは、と思い書きました。
高レベルなインフラツールを使ってもエラーにぶち当たった時は結局Linux周りの自力と英語力が要求されるという事を実感しました。