Packerを使ってDockerのイメージを作成する（ansible-remote編）

以前、[Packerを使ってDockerのイメージを作成する](http://qiita.com/hirohero/items/9d306c0ff6b2b6a58f7a)というエントリを書きました。
この場合のプロビジョナは ansible では ansible でも、`ansible-local` という ansible を作成するイメージ内にインストールして、イメージ内で自分自身にプロビジョニングするものでした。

でも、ansibleといえばやっぱりリモートから操作するのが本来の姿というものです。
Packer には `ansible-remote` という、通常のansibleと同様にsshで接続してプロビジョニングするプロビジョナがあります。

`ansible-remote`を使ったPackerによるDockerのイメージ作成方法を紹介します。
今回もディストリビューションは Docker 向けの Alpine Linux でやってみます。


# 前準備
`ansible-remote`を使うためにはsshでリモート接続できることが必要ですが、Alpine Linuxの標準イメージにはOpenSSHがインストールされていません。

まずは前準備としてOpenSSHインストール済みのイメージを作成します。（ついでにAnsible実行のために必要なPython2 もインストールしておきます。）
packer.jsonファイルは次の通りです。

```json:packer.json
{
    "builders":[{
        "type": "docker",
        "image": "alpine:edge",
        "export_path": "image.tar",
        "run_command": ["-dit", "{{.Image}}", "/bin/sh"]
    }],
    
    "provisioners":[{
        "type": "shell",
        "inline": [
            "echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories",
            "apk update",
            "apk upgrade",
            "apk add openssh python",
            "/usr/bin/ssh-keygen -A"
        ]
    }],
    
    "post-processors": [{
        "type": "docker-import",
        "repository": "ansible-ready/alpine",
        "tag": "edge"
    }]
}
```

ポイントは2つ。

- bashが入っていないので`run_command`に/bin/shを指定する。
- sshdのホストキーを`ssh-kengen -A`で生成しておく。

これを実行すると、`ansible-ready/alpine:edge`というイメージができます。これで準備は完了です。

```shell
$ packer build packer.json
docker output will be in this color.

==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: alpine:edge
    docker: edge: Pulling from library/alpine
    docker: Digest: sha256:cd9c03c2d382fcf00c31dc1635445163ec185dfffb51242d9e097892b3b0d5b4
    docker: Status: Image is up to date for alpine:edge
==> docker: Starting docker container...
    docker: Run command: docker run -v /Users/XXXXX/.packer.d/tmp/packer-docker868740031:/packer-files -dit alpine:edge /bin/sh
    docker: Container ID: ef9896493e31be45724050746edadc59a64a32eedad94e2794b62333356df26c
==> docker: Provisioning with shell script: /var/folders/d1/c3mwd9kn20vdng53mywr181xm5hflj/T/packer-shell340896860
    docker: fetch http://dl-cdn.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
    docker: fetch http://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/APKINDEX.tar.gz
    docker: v3.4.0-4735-g0d6386a [http://dl-cdn.alpinelinux.org/alpine/edge/main]
    docker: v3.4.0-4715-ga71ef75 [http://dl-cdn.alpinelinux.org/alpine/edge/community]
    docker: OK: 7699 distinct packages available
    docker: Upgrading critical system libraries and apk-tools:
    docker: (1/1) Upgrading apk-tools (2.6.7-r2 -> 2.6.8-r1)
    docker: Executing busybox-1.25.1-r0.trigger
    docker: Continuing the upgrade transaction with new apk-tools:
    docker: (1/5) Upgrading musl (1.1.15-r3 -> 1.1.15-r5)
    docker: (2/5) Upgrading alpine-keys (1.2-r0 -> 1.3-r0)
    docker: (3/5) Upgrading libressl2.4-libcrypto (2.4.3-r1 -> 2.4.4-r0)
    docker: (4/5) Upgrading libressl2.4-libssl (2.4.3-r1 -> 2.4.4-r0)
    docker: (5/5) Upgrading musl-utils (1.1.15-r3 -> 1.1.15-r5)
    docker: Executing busybox-1.25.1-r0.trigger
    docker: OK: 4 MiB in 11 packages
    docker: (1/14) Installing ncurses-terminfo-base (6.0-r7)
    docker: (2/14) Installing ncurses-terminfo (6.0-r7)
    docker: (3/14) Installing ncurses-libs (6.0-r7)
    docker: (4/14) Installing readline (6.3.008-r4)
    docker: (5/14) Installing bash (4.3.46-r3)
    docker: Executing bash-4.3.46-r3.post-install
    docker: (6/14) Installing openssh-client (7.3_p1-r2)
    docker: (7/14) Installing openssh-sftp-server (7.3_p1-r2)
    docker: (8/14) Installing openssh (7.3_p1-r2)
    docker: (9/14) Installing libbz2 (1.0.6-r5)
    docker: (10/14) Installing expat (2.2.0-r0)
    docker: (11/14) Installing libffi (3.2.1-r2)
    docker: (12/14) Installing gdbm (1.12-r0)
    docker: (13/14) Installing sqlite-libs (3.15.0-r0)
    docker: (14/14) Installing python2 (2.7.12-r7)
    docker: Executing busybox-1.25.1-r0.trigger
    docker: OK: 55 MiB in 25 packages
==> docker: Provisioning with shell script: /var/folders/d1/c3mwd9kn20vdng53mywr181xm5hflj/T/packer-shell052823265
    docker: ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
==> docker: Exporting the container
==> docker: Killing the container: ef9896493e31be45724050746edadc59a64a32eedad94e2794b62333356df26c
==> docker: Running post-processor: docker-import
    docker (docker-import): Importing image: Container
    docker (docker-import): Repository: ansible-ready/alpine:edge
    docker (docker-import): Imported ID: sha256:6c3afddb8b1f4594bc0c52a6b3b4604fe17e32285db24d968d6672e2d4102fa1
Build 'docker' finished.

==> Builds finished. The artifacts of successful builds are:
--> docker: Imported Docker image: ansible-ready/alpine:edge
```

# ansible-remoteでプロビジョニングする
あとは、この`ansible-ready/alpine:edge` を元にして `ansible-remote`でのプロビジョニングを行うpacker.jsonを書きます。

```json:packer.json
{
    "builders":[{
        "type": "docker",
        "image": "ansible-ready/alpine:edge",
        "export_path": "image.tar",
        "pull": false,
        "run_command": ["-dit", "{{.Image}}", "/usr/sbin/sshd", "-D"]
    }],
    
    "provisioners":[{
        "type": "ansible",
        "playbook_file": "playbook.yml",
        "use_sftp": true,
        "sftp_command": "/usr/lib/ssh/sftp-server -e",
        "user": "root"
    }],
    
    "post-processors": [{
        "type": "docker-import",
        "repository": "ansible-test",
        "tag": "0.1"
    }]
}
```
こちらもポイントは2点。

- `entrypoint`を`sshd`としてフォアグラウンドで起動すること。
- `use_sftp`を有効にすること。
 - 現時点で`use_sftp`を無効（scpモード）にするとsshの認証エラーでコケます。
 - Alpineの sftp-server がデフォルトの `/usr/lib/sftp-server -e` ではないので、`sftp_command`で指定する。

```shell
$ packer build packer.json
docker output will be in this color.

==> docker: Creating a temporary directory for sharing data...
==> docker: Starting docker container...
    docker: Run command: docker run -v /Users/XXXXX/.packer.d/tmp/packer-docker232779775:/packer-files -dit base/alpine:edge /usr/sbin/sshd -D
    docker: Container ID: ade80cbfc9a14d383639d9f787fd54746bd0192027c2293d2b41ed0d5545997d
==> docker: Provisioning with Ansible...
==> docker: SSH proxy: serving on 127.0.0.1:61555
==> docker: Executing Ansible: ansible-playbook /Users/XXXXXX/Documents/src/ansible-test/playbook.yml -i /var/folders/d1/c3mwd9kn20vdng53mywr181xm5hflj/T/packer-provisioner-ansible058564979 --private-key /var/folders/d1/c3mwd9kn20vdng53mywr181xm5hflj/T/ansible-key671615844
    docker:
    docker: PLAY [all] *********************************************************************
    docker:
    docker: TASK [setup] *******************************************************************
    docker: SSH proxy: accepted connection
==> docker: authentication attempt from 127.0.0.1:61557 to 127.0.0.1:61555 as root using none
==> docker: authentication attempt from 127.0.0.1:61557 to 127.0.0.1:61555 as root using publickey
==> docker: starting sftp subsystem
    docker: ok: [default]
    docker:
    docker: TASK [ping] ********************************************************************
==> docker: starting sftp subsystem
    docker: ok: [default]
    docker:
    docker: PLAY RECAP *********************************************************************
    docker: default                    : ok=2    changed=0    unreachable=0    failed=0
    docker:
==> docker: shutting down the SSH proxy
==> docker: Exporting the container
==> docker: Killing the container: ade80cbfc9a14d383639d9f787fd54746bd0192027c2293d2b41ed0d5545997d
==> docker: Running post-processor: docker-import
    docker (docker-import): Importing image: Container
    docker (docker-import): Repository: ansible-test:0.1
    docker (docker-import): Imported ID: sha256:75912d41ae885c503c5c6597fa530feefbdb12a10682f2ba456785ff6255cfbe
Build 'docker' finished.
```

これでイメージができました。
`ansible-local`の場合はイメージ内に Ansible がゴミとして残ってしまいますが、この方法ならそのような心配がありません。キレイですね。