AnsibleでDocker Image作るときのあれこれ

最近はDocker Imageを作る時Dockerfile以外の方法としてPacker + MItamaeとかでDocker Imageを作ることがある。

そのためにPacker用のMItamaeプラグインとかも作ったりもした。

hatappi.hateblo.jp

最近プロビジョニング周りでAnsibleを使うことになったので、改めてDocker Imageを作る方法を調べた。

実現したいこと

  • 任意のミドルウェアなどをいれたDocker Imageを作ってECRにPUSHまでしたい
  • Docker ImageもだけどAMIも作りたい

どう実現するか

まずDocker Imageを作るところにフォーカスをあてると
どうやらAnsibleには Ansible Containerと呼ばれるDocker Imageを作ったりしてくれるものが提供されているらしい。
任意のレジストリへログインしていれば、pushだって出来てしまう。

これを使えばAnsibleだけで完結することが出来そう!!!

Ansible Container

Ansible Container自体はpipを使って簡単にインストールすることが出来る。

$ pip install ansible-container[docker]

インストールが完了するとansible-containerコマンドが使えるようになって、始めに必要なものを作ってくれる ansible-container initコマンドラインで実行する。
すると下記のようなファイルが生成される。

.
├── ansible-requirements.txt # Ansibleを実行する時に必要なPythonモジュールをpip形式で記載する
├── ansible.cfg # ansible コマンドを実行する時の設定を記載する
├── container.yml # コンテナの設定
├── meta.yml # ライセンスとか諸々をかいていく。Galaxyとかのリポジトリで共有される時に使用される
└── requirements.yml # Ansibleを実行する時に外部リポジトリからダウンロードするもの

今回はサンプルとしてnginxをいれてCMDにたいしてnginxを起動させるようなものを作る。

先程生成したファイル群と同じディレクトリに下記のようにファイルを配置する。
Ansibleのディレクトリ構造については公式で Best Practiceが出ているのでこれを参照する。

└── roles
    └── nginx
        └── tasks
            └── main.yml

main.ymlには下記のように記載する。
やっていることはシンプルで、nginxのyumリポジトリを追加してあげて、インストール。
そして起動設定をして、サービスを起動するだけ。
ここで1つポイントしては今回docker上ではserviceで起動に関してコンテナを立ち絵上げる時にするので、スキップするように when: ansible_connection != 'docker' と記載してあげる。

- name: Add nginx repository
  yum_repository:
    name: nginx_repo
    description: nginx repo
    baseurl: http://nginx.org/packages/centos/$releasever/$basearch/
    gpgcheck: 0
    enabled: 1
- name: install nginx
  yum: name=nginx state=present
- name: set auto start nginx
  command: chkconfig nginx on
- service:
    name: nginx
    state: restarted
  when: ansible_connection != 'docker'

これでroleは完成したので最後にconatiner.ymlを作っていく。
container.ymlの書き方や何を意味するかは公式のdocにのっている。
ポイントだけ紹介するとservices配下に作成したいDocker Imageの設定を記載していく。
今回は起動した時にnginxのプロセスを立てたいので、CMDには["nginx", "-g", "daemon off;"]を設定している。
registriesではどこにpushするのかの情報を記載する今回はECR上にtestというネームスペースのtest-nginxがあるとする。
registries配下のnamespace欄がECRのネームスペースにあたる。またrepository_prefixとあるがこれを設定することでAnsible Containerはserivice配下のkeyと組み合わせて test-nginxというものを作成する。

version: "2"
settings:
  conductor:
    base: centos:7
    roles_path:
      - roles
  project_name: hoge
services:
  nginx:
    from: "centos:7"
    roles:
      - nginx
    command: ["nginx", "-g", "daemon off;"]
registries:
  ecr:
    url: https://0000.dkr.ecr.ap-northeast-1.amazonaws.com
    namespace: test
    repository_prefix: test

これで一通りできたのでまずはローカルにdocker imageを作ってみる。

$ 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=hoge
Building service...     project=hoge service=nginx

PLAY [nginx] *******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [nginx]

TASK [nginx : Add nginx repository] ********************************************
changed: [nginx]

TASK [nginx : install nginx] ***************************************************
changed: [nginx]

TASK [nginx : set auto start nginx] ********************************************
changed: [nginx]

TASK [nginx : service] *********************************************************
skipping: [nginx]

PLAY RECAP *********************************************************************
nginx                      : ok=4    changed=3    unreachable=0    failed=0

Applied role to service role=nginx service=nginx
Committed layer as image        image=sha256:612fd9e04b1ce2b1b0ecf2bf8cdc198cb59646f0726c8bd41c6c572e6cf service=nginx
Build complete. service=nginx
All images successfully built.
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=4285231fc80e8625b2e155496c5e033486e767146816f912a8e69 save_container=False

docker images で見るとちゃんと作られている!!

f:id:hatappi1225:20170915090224p:plain

あとはECRにpushするだけで

# --tagで任意のタグをうつことが出来る
$ ansible-container deploy --push-to ecr --username AWS --password xxx --tag latest

これで無事ECRにnginxのDocker Imageがpushされる。

後は docker run -d -p 8080:80 0000.dkr.ecr.ap-northeast-1.amazonaws.com/test/test-nginx とかで80ポートを8080にポーティングしてあげれば、ブラウザでいつもの画面をみることが出来る。

f:id:hatappi1225:20170915091317p:plain

やってみて

Ansible完結できた!
ただやってみて個人的に2つ問題があって、
1つ目が今回はしなかったがAMIを作ることを考えた時にAnsibleにはec2 moduleec2_ami moduleがあるので、これらを組み合わせてec2でインスタンスをたてて => sshで接続できるまで待ってあげて => Ansibleでプロビジョニングしてあげて => ec2インスタンスからAMIを作ってあげてみたいなことをしないといけない。
2つ目がpushする際の認証で、このAnsible Containerの仕組みとしてはdocker loginなどした時に~/.docker/config.jsonなどに認証情報が記載されているのだが、このファイルを読み込んで対象レジストリの認証情報をBase64でデコードしてユーザー情報とパスワードを取り出している。コードだとこのへん この実装だとECRの認証をよしなにやってくれる公式のamazon-ecr-credential-helperも使えない。
これはわりと面倒。

この2つはPackerが解決してくれる。 1つ目のAMIの部分もPackerにはプラグインが提供されており、面倒なインスタンスの制御とかをよしなにやってくれる。
2つ目の認証部分に関してはPackerは裏側でgoのaws sdkを使用するようにしているので、環境変数とか~/.aws配下とかをよしなに見に行ってくれる。

結論

Packer最高じゃん。