Edited at

PackerのAnsibleProvisionerでインベントリ毎のgroup_varsを使い分ける


はじめに

この記事は、エーピーコミュニケーションズ Advent Calendar 2018 の22日目のエントリです。

今回もAnsible絡みですが、Packerを使ってAWS EC2にAMIをデプロイします。

PackerにはProvisionerというVM起動後にインストール作業や設定変更ができる機能があり、

その中にAnsible Provisionerがあるのでそちらを使います。

PackerのProvisionerにはAnsibleの他にもShellやPowerShell, Chef, Puppetなどなど各種あるのでお好みのツールを使えると思います。

ちなみにもう一つAnsible Local Provisionerというものがありますが、そちらは試していません。


やりたいこと

Packerは基本的に1台のマシンイメージ(AMI)を作成するツールですが、

Ansibleは複数のマシンをホスト名やグループ名によってインストールするパッケージなどの処理を変えながら構築していくことを多いと思われます。

こういった使い方をPackerで実現するため、所属するグループをサーバの役割毎に動的に変更させることによって複数種類のAMIを作成させます。

また、Packerではインベントリを使用せずにPlaybookのみで動作させることができます。

クラウドでは基本的にIPやホスト名が動的に割り当てられるため、あらかじめインベントリを作成しておくのが困難なので、作成する必要がないのは楽ですね。

インベントリを使用しなくてもPlaybook側のgroup_varsを使用すれば上記の挙動は実現可能ですが、

Ansible BestpracticeにあるAlternative Directory Layoutの例のように

「商用とステージングでPlaybookとグループは共通のものを使いたいけど、IPアドレスやURLなどの変数には別々の値を使いたい」という場合には

複数のインベントリファイル・ディレクトリに分けて使い分ける、という使い方をしているケースも多く、

その場合にはインベントリのhost_vars, group_varsを使い分ける必要があります。

インベントリのディレクトリ構成例(Best Practicesから抜粋)

inventories/

production/
hosts # inventory file for production servers
group_vars/
group1.yml # here we assign variables to particular groups
group2.yml
host_vars/
hostname1.yml # here we assign variables to particular systems
hostname2.yml

staging/
hosts # inventory file for staging environment
group_vars/
group1.yml # here we assign variables to particular groups
group2.yml
host_vars/
stagehost1.yml # here we assign variables to particular systems
stagehost2.yml




Packerでインベントリのgroup_varsを使う良い方法が調べていたのですが、

意外にもあまり情報が見つからなかったのでPackerの公式などを調べて動かした結果を書きます。


書き方


ファイル構成

packerコマンドを実行するディレクトリからの相対的なファイル構成は以下のようにしました。


ファイル構成

.

├── ansible
│   ├── group_vars
│   │   ├── all.yml
│   │   ├── db.yml
│   │   └── web.yml
│   ├── inventory
│   │   ├── production
│   │   │   └── group_vars
│   │   │   └── all.yml
│   │   └── staging
│   │   └── group_vars
│   │   └── all.yml
│   └── play.yml
└── packer.json

packer.jsonがpackerコマンドで引数となる設定ファイルで、今回は実行ディレクトリ直下においています。

そしてansible/inventory/productionおよびansible/inventory/stagingがインベントリに当たるディレクトリですが、

ポイントとしては、インベントリファイルは作成しないで、group_varsのみ(必要ならhost_varsも)を置いている点です。

そしてPackerの設定ファイルは以下のように書きました。


packer.json

{

"variables": {
"environment": "staging",
"instance_type": "t2.micro",
"source_ami": "ami-0a2de1c3b415889d2",
"role": ""
},
"builders": [
{
"type": "amazon-ebs",
"ami_name": "packer_ami_{{timestamp}}",
"instance_type": "{{user `instance_type`}}",
"source_ami": "{{user `source_ami`}}",
"ssh_username": "ec2-user",
"tags": [
{
"Name": "packer_ami"
}
]
}
],
"provisioners": [
{
"playbook_file": "ansible/play.yml",
"inventory_directory": "ansible/inventory/{{user `environment`}}",
"type": "ansible",
"user": "ec2-user",
"groups": ["{{user `role`}}"],
"extra_arguments": []
}
]
}

ポイントとしては

provisionersのディレクティブでinventory_directoryを指定して、

その上でgroupsにグループ名を指定する

ということです。

それでは実際に動作確認してみましょう。

Playbookは以下のようにしています。


ansible/play.yml

- hosts: all

become: true
tasks:
- name: インベントリのgroup_vars/all.ymlの確認
debug:
msg: "my environment is {{ env }}"

- name: プレイブックのgroup_vars/all.ymlの確認
yum:
name: "{{ common_package }}"

- name: プレイブックのgroup毎のgroup_varsの確認
yum:
name: "{{ item }}"
loop: "{{ dist_package }}"

- hosts: web
tasks:
- name: webグループの挙動確認
debug:
msg: "I am Web!!!"

- hosts: db
tasks:
- name: dbグループの挙動確認
debug:
msg: "I am db!!!"


各種ファイルに書いた変数はこちら

::::::::::::::

ansible/group_vars/all.yml
::::::::::::::
common_package: git
::::::::::::::
ansible/group_vars/db.yml
::::::::::::::
dist_package:
- mysql
- mariadb-server
::::::::::::::
ansible/group_vars/web.yml
::::::::::::::
dist_package:
- httpd
- php
::::::::::::::
ansible/inventory/production/group_vars/all.yml
::::::::::::::
env: production
::::::::::::::
ansible/inventory/staging/group_vars/all.yml
::::::::::::::
env: staging

Packerでは-var arg=varという形式でvariablesで指定した変数を上書きできます。(--varではなくハイフン1つなので注意)

実行結果は以下のようになりました。(Packerのメタな部分は適度にボカしてます)


実行結果

$ packer build -var environment=production -var role=web packer.json

amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name: packer_ami_1545451854
amazon-ebs: Found Image ID: ami-0a2de1c3b415889d2
==> amazon-ebs: Creating temporary keypair: packer_***************
==> amazon-ebs: Creating temporary security group for this instance: packer_***************
==> amazon-ebs: Authorizing access to port 22 from 0.0.0.0/0 in the temporary security group...
==> amazon-ebs: Launching a source AWS instance...
==> amazon-ebs: Adding tags to source instance
amazon-ebs: Adding tag: "Name": "Packer Builder"
amazon-ebs: Instance ID: i-***************
==> amazon-ebs: Waiting for instance (i-***************) to become ready...
==> amazon-ebs: Using ssh communicator to connect: ***************
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with Ansible...
==> amazon-ebs: Executing Ansible: ansible-playbook --extra-vars packer_build_name=amazon-ebs packer_builder_type=amazon-ebs -i ansible/inventory/production /home/hogehoge/code/packer-ansible/ansible/play.yml -e ansible_ssh_private_key_file=/tmp/ansible-key657870806
amazon-ebs:
amazon-ebs: PLAY [all] *********************************************************************
amazon-ebs:
amazon-ebs: TASK [Gathering Facts] *********************************************************
amazon-ebs: ok: [default]
amazon-ebs:
amazon-ebs: TASK [インベントリのgroup_vars/all.ymlの確認] ********************************************
amazon-ebs: ok: [default] => {
amazon-ebs: "msg": "my environment is production"
amazon-ebs: }
amazon-ebs:
amazon-ebs: TASK [プレイブックのgroup_vars/all.ymlの確認] ********************************************
amazon-ebs: changed: [default]
amazon-ebs:
amazon-ebs: TASK [プレイブックのgroup毎のgroup_varsの確認] *********************************************
amazon-ebs: changed: [default] => (item=httpd)
amazon-ebs: changed: [default] => (item=php)
amazon-ebs:
amazon-ebs: PLAY [web] *********************************************************************
amazon-ebs:
amazon-ebs: TASK [Gathering Facts] *********************************************************
amazon-ebs: ok: [default]
amazon-ebs:
amazon-ebs: TASK [webグループの挙動確認] ************************************************************
amazon-ebs: ok: [default] => {
amazon-ebs: "msg": "I am Web!!!"
amazon-ebs: }
amazon-ebs: [WARNING]: Could not match supplied host pattern, ignoring: db
amazon-ebs:
amazon-ebs: PLAY [db] **********************************************************************
amazon-ebs: skipping: no hosts matched
amazon-ebs:
amazon-ebs: PLAY RECAP *********************************************************************
amazon-ebs: default : ok=6 changed=2 unreachable=0 failed=0
amazon-ebs:
==> amazon-ebs: Stopping the source instance...
amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating unencrypted AMI packer_ami_1545451854 from instance i-***************
amazon-ebs: AMI: ami-***************
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Adding tags to AMI (ami-***************)...
==> amazon-ebs: Tagging snapshot: snap-***************
==> amazon-ebs: Creating AMI tags
amazon-ebs: Adding tag: "Name": "packer_ami"
==> amazon-ebs: Creating snapshot tags
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-***************


想定した通りになってますね!

ちなみにinventory_directoryの説明にもあるように、

inventory_directoryに指定したディレクトリに一時ファイルとしてインベントリファイルを作成します。

一時ファイルなので完了/失敗時に削除されるのですが、Ctrl+Cを連打したりすると削除処理までキャンセルしてしまう場合がありました。

その場合、どういう挙動なのかよくわかってませんが、次回実行時に残ったインベントリファイルを読み込んでデプロイにコケる、というケースがありました。

挙動がおかしい場合はインベントリディレクトリを調査して、インベントリファイルが残ってたら削除するとうまくいきました。

他にもいい方法があればコメントください。