はじめに

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

ディレクトリ構成

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

inventory

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

hosts/aws
[aws]
localhost

vars

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

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が必要ですが、こちらと同様、VPC名でidを取得します。

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

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

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

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

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

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

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

まとめ

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

参考