はじめに
この記事は、エーピーコミュニケーションズ 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コマンドを実行するディレクトリからの相対的なファイル構成は以下のようにしました。
.
├── 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の設定ファイルは以下のように書きました。
{
"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は以下のようにしています。
- 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
を連打したりすると削除処理までキャンセルしてしまう場合がありました。
その場合、どういう挙動なのかよくわかってませんが、次回実行時に残ったインベントリファイルを読み込んでデプロイにコケる、というケースがありました。
挙動がおかしい場合はインベントリディレクトリを調査して、インベントリファイルが残ってたら削除するとうまくいきました。
他にもいい方法があればコメントください。