EC2をAnsibleで管理する


#はじめに
AnsibleにはAWSのリソースを操作できるモジュールが豊富に用意されています。

今回は、定番のEC2をAnsibleで管理してみます。

#やること
- EC2インスタンス作成


##ポイント
ec2モジュールは、セキュリティグループについては名前で指定できるのですが、サブネットはIDで指定する必要があります。

しかし、サブネットIDをAnsibleのYAMLに書きたくないので、サブネット名からIDを取得する実装とします。


#前提
AWS関連のモジュール実行にはbotoが必要です。
credential情報は環境変数か`aws configure`でセットしてある必要があります。

下記リソースを前提に進めます。

- VPC
	- AnsibleVPC
- キーペア
	- keypair
- サブネット
	- public-a
	- public-c
- セキュリティグループ
	- common
	- web_server



#sample

以下のようなEC2インスタンスを作成します。

- testinstance1
	- AmazonLinux
	- アベイラビリティゾーンA
	- セキュリティグループ
		- common,web_server
- testinstance2
	- AmazonLinux
	- アベイラビリティゾーンC
	- セキュリティグループ
		- common


##ディレクトリ構成

```bash:ディレクトリ構成
site.yml
roles/
|--ec2/
|  |--tasks/
|  |  |--main.yml
hosts/aws    #inventory
host_vars/
|--localhost.yml
```

##inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

```bash:hosts/aws
[aws]
localhost
```

##vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。

```yaml:host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    vpc:
      name: AnsibleVPC    # ターゲットのVPC名
    ec2:
      testinstance1:
        ami_image: ami-56d4ad31 # Amazon Linux
        key_name: keypair # キーペア名
        security_group:
          - common
          - web_server
        instance_type: t2.micro
        device_name: /dev/xvda
        device_type: gp2
        volume_size: 8 # EBSのディスクサイズ（GB）
        subnet: public-a # サブネット名
        assign_public_ip: yes
        tags:
          Name: testinstance1
          Role: test
      testinstance2:
        ami_image: ami-56d4ad31 # Amazon Linux
        key_name: keypair # キーペア名
        security_group:
          - common
        instance_type: t2.micro
        device_name: /dev/xvda
        device_type: gp2
        volume_size: 8 # EBSのディスクサイズ（GB）
        subnet: public-c # サブネット名
        assign_public_ip: yes
        tags:
          Name: testinstance2
          Role: test
```

##Role

まずVPCを特定するためにidが必要ですが、[こちら](http://qiita.com/tomohiko_isobe/items/c245bedfa6966305eb10)と同様、VPC名でidを取得します。

今回はリストではなくディクショナリとしてEC2インスタンスを定義しましたので、`with_dict`でループさせます。

前述のように、サブネットはIDで指定する必要があるので、`ec2_vpc_subnet_facts`モジュールでIDを取得します。

定義されたEC2インスタンスの全てのサブネット名とIDのディクショナリを作成し、後続taskで参照します。

あとは`ec2`モジュールで作成しますが、`exact_count: 1`を指定することで重複作成を防止します（stop状態だと作成されてしまいますが）。

```yaml:roles/ec2/tasks/main.yml
---
- name: vpc_id取得
  ec2_vpc_net_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      "tag:Name": "{{ my_vars.aws.vpc.name }}"
  register: vpc_net_fact
  check_mode: no
  
- debug: var=vpc_net_fact

- name: subnet id取得
  ec2_vpc_subnet_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      "tag:Name": "{{ item.value.subnet }}"
  with_dict: "{{ my_vars.aws.ec2 }}"
  register: subnet_fact
  when: my_vars.aws.ec2 is defined

- name: subnet dict作成
  set_fact:
    subnet_dict: >-
      {%- set dict = {} -%}
      {%- for i in range(subnet_fact.results|length) -%}
      {%-   set _ = dict.update({subnet_fact.results[i].subnets[0].tags.Name: subnet_fact.results[i].subnets[0].id}) -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.ec2 is defined

- name: EC2インスタンスを作成
  ec2:
    image: "{{ item.value.ami_image }}"
    instance_type: "{{ item.value.instance_type }}"
    region: "{{ my_vars.aws.common.region }}"
    key_name: "{{ item.value.key_name }}"
    group: "{{ item.value.security_group }}"
    vpc_subnet_id: >-
      {%- set id = subnet_dict[item.value.subnet] -%}
      {{ id }}
    instance_tags: "{{ item.value.tags }}"
    assign_public_ip: "{{ item.value.assign_public_ip }}"
    private_ip: "{{ item.value.private_ip | default(omit) }}"
    wait: yes
    wait_timeout: 300
    volumes:
      - device_name: "{{ item.value.device_name }}"
        device_type: "{{ item.value.device_type }}"
        volume_size: "{{ item.value.volume_size }}"
        delete_on_termination: true
    count_tag:
      Name: "{{ item.value.tags.Name }}"
    exact_count: 1
    user_data: |
      #!/bin/bash
      # 初期設定スクリプトなど
  with_dict: "{{ my_vars.aws.ec2 }}"
  register: ec2
  when: my_vars.aws.ec2 is defined

- debug: var=ec2
```

##site.yml
```YAML:site.yml
---
- name: ec2
  hosts: localhost
  connection: local
  roles:
    - role: ec2
```

##実行
```bash:Command
$ ansible-playbook -i hosts/aws -l localhost site.yml
```

#まとめ
ネット上のサンプルでもサブネットIDを指定している例がほとんどですが、実際YAMLで管理する場合、IDではなく名前の方が分かりやすいと思います。
参考になれば幸いです。

#参考
- [VPCをAnsibleで作成する](http://qiita.com/tomohiko_isobe/items/c245bedfa6966305eb10)
- [IAMユーザをAnsibleで管理する](http://qiita.com/tomohiko_isobe/items/5ae6f15f52a2d20e2635)
- [AWSのセキュリティグループをAnsibleで管理する](http://qiita.com/tomohiko_isobe/items/7342b8ba874d997c5c6c)