Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

この記事は、エーピーコミュニケーションズ 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を連打したりすると削除処理までキャンセルしてしまう場合がありました。
その場合、どういう挙動なのかよくわかってませんが、次回実行時に残ったインベントリファイルを読み込んでデプロイにコケる、というケースがありました。
挙動がおかしい場合はインベントリディレクトリを調査して、インベントリファイルが残ってたら削除するとうまくいきました。

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

chataro0
ap-com
エーピーコミュニケーションズは「エンジニアから時間を奪うものをなくす」ため、ITインフラ自動化のプロフェッショナルとして、クラウドも含めたインフラ自動化技術で顧客の課題を解決すると同時に、SI業務の課題を解決するプロダクト・サービスを提供するNeoSIer(ネオエスアイヤー)です。
https://www.ap-com.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away