Ansible で EC2 の環境をプロビジョニングするときの 本番やステージングなどを切り替える方法

EC2 インスタンスに対して Ansible するときは Ansible に付属の `ec2.py` などの Dynamic Inventory でインスタンスの一覧を取得しますが、本番やステージングを切り替えるためのインベントリの構成についてのメモ。

## 環境変数 で切り替える

`ec2.py` に環境変数 `AWS_PROFILE` でアドホックにプロファイルを指定できるので、Dynamic Inventory がインスタンスを取得してくる AWS アカウントを切り替えられます。

```sh
AWS_PROFILE=prod ansible-playbook -i ec2.py site.yml
```

この場合、インベントリはすべての環境で共通で良いので `ansible.cfg` で指定しておけばコマンドラインでの指定は不要です。

```ini
# ansible.cfg
[defaults]
inventory = ec2.py
```

環境固有の変数（`ansible_ssh_common_args` とか）は、インスタンスに `Env=prod` のようなタグを付与しておけば `ec2.py` がタグ名と値で `tag_Env_prod` のようなグループを作るので `group_vars/tag_Env_prod.yml` のようなファイルで指定できます。

```yaml
# group_vars/tag_Env_prod.yml
ansible_ssh_common_args: -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'
```

ファイル名が `group_vars/tag_Env_prod.yml` だとちょっと気持ち悪いので、下記のようなディレクトリ構成にして、

```sh
ansible.cfg
site.yml
roles/
    ...
group_vars/
    # 環境ごとのグループ変数
    prod.yml
    stage.yml
    dev.yml
    # その他のグループ変数
    ap.yml
    web.yml
inventory/
    ec2.py
    ec2.ini
    group.conf
```

`group.conf` で `ec2.py` によるグループを元にそれっぽいグループを作ります。前半で `tag_Env_prod` などの空のグループを複数定義している部分は `:children` で指定したグループが存在しないと警告になるので必要です。

```ini
# inventory/group.conf

[tag_Env_prod]
[tag_Env_stage]
[tag_Env_dev]

[prod:children]
tag_Env_prod

[stage:children]
tag_Env_stage

[dev:children]
tag_Env_dev

[ap:children]
tag_Role_ap

[web:children]
tag_Role_web
```

インベントリにはディレクトリを指定します。

```ini
# ansible.cfg
[defaults]
inventory = inventory/
```

これで `group_vars/prod.yml` などのファイルを読ませることができます。`vars.conf` などのインベントリを作成してそこに環境名のグループ変数を書いても良いです。

```ini
# inventory/vars.conf

[prod:vars]
ansible_ssh_common_args = -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'

[stage:vars]
ansible_ssh_common_args = -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'

[dev:vars]
ansible_ssh_common_args = -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'
```

なお、この方法は `AWS_PROFILE` 環境変数だけで対象環境を切り替えているので、1つの AWS アカウントにリージョンや VPC を分けて複数の環境を構築する場合は難しいです。

リージョンが違えば `AWS_REGION` 環境変数で指定できますけど、特定の VPC のインスタンス、とか、特定のタグが付いたインスタンス、とかをやるためには環境変数 `EC2_INI_PATH` を指定するなりして `ec2.ini` を分けてやる必要があります。

そんなことするぐらいなら後述のインベントリディレクトリで切り替えるほうが良いと思うので、基本的にこの方法はオススメできません。

## インベントリディレクトリで切り替える

次のようなディレクトリ構成にします。

```sh
ansible.cfg
site.yml
roles/
    ...
group_vars/
    ap.yml
    web.yml
envs/
    prod/
        ec2.py
        ec2.ini
        group.conf
        vars.conf
        group_vars/
            all.yml
    stage/
        ec2.py
        ec2.ini
        group.conf
        vars.conf
        group_vars/
            all.yml
    dev/
        ec2.py
        ec2.ini
        group.conf
        vars.conf
        group_vars/
            all.yml
```

`ec2.ini` でプロファイルやタグのフィルタを指定します。

```ini
# envs/${env}/ec2.ini

instance_filters = tag:Env=prod
boto_profile = prod
```

環境固有の変数は `vars.conf` に下記のようなインベントリの形式で書いても良いし、

```ini
# envs/${env}/vars.conf

[all]
ansible_ssh_common_args = -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'
```

`group_vars/all.yml` にグループ変数として書いても良いです。

```yaml
# envs/${env}/group_vars/all.yml

ansible_ssh_common_args: -o ProxyCommand='ssh -W %h:%p ore@192.0.2.123'
```

`group.conf` は `ec2.py` による `tag_Name_Value` のようなグループからそれっぽい名前のグループを作るために使います。

```ini
# envs/${env}/group.conf
[ap:children]
tag_Role_ap

[web:children]
tag_Role_web
```

Ansible は -i でインベントリディレクトリを指定して実行します。

```sh
ansible-playbook -i envs/prod/ site.yml
```

環境変数 `ANSIBLE_INVENTORY` でも良いです。

```sh
ANSIBLE_INVENTORY=envs/prod/ ansible-playbook site.yml
```

この方法は環境変数 `AWS_PROFILE` で切り替える方法と比べて `ec2.ini` が環境ごとに書けるので自由度が高いです。`ec2.py` とかが複数のディレクトリに同じ内容で散らばるのがちょっと気持ち悪いですけど。