AnsibleによるEC2インスタンスの構築

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

過去にも同様のエントリーが存在するのですが、AWSといえばEC2ということで、最新のAWSモジュールを利用してEC2インスタンスを作成してみます。

グループ変数でインスタンス定義を明確にする

可読性の高いグループ変数は、このシリーズのテーマです。 今回は、次のようなグループ変数でEC2インスタンスを定義したいと思います。

ec2:
  - name: FrontWebA
    instance_type: t2.small
    image: ami-1a15c77b
    instance_profile_name: web
    key_name: dev-key
    subnet_name: FrontA
    group:
      - Internal
      - Mainte
    root_volume_size: 8
    assign_public_ip: false
  - name: FrontWebC
    instance_type: t2.small
    image: ami-1a15c77b
    instance_profile_name: web
    key_name: dev-key
    subnet_name: FrontC
    group:
      - Internal
      - Mainte
    root_volume_size: 8
    assign_public_ip: false

EC2を作成するPlaybook

Playbookはこんな感じです。

- hosts: localhost
  connection: local
  gather_facts: False
  become: False
  tasks:
    - name: Facts subnet
      ec2_vpc_subnet_facts:
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _subnet_facts
    - debug: var=_subnet_facts verbosity=1
    - name: "EC2"
      ec2:
        image: "{{ item.image }}"
        instance_profile_name: "{{ item.instance_profile_name }}"
        instance_type: "{{ item.instance_type }}"
        key_name: "{{ item.key_name }}"
        vpc_subnet_id: "{{ _subnet_facts.subnets | selectattr('tags.Name', 'defined') | selectattr('tags.Name', 'equalto', item.subnet_name) |  map(attribute='id') | first }}"
        group: "{{ item.group }}"
        volumes:
          - device_name: /dev/xvda
            volume_type: gp2
            volume_size: "{{ item.root_volume_size }}"
            delete_on_termination: true
        instance_tags:
          Name: "{{ item.name }}"
        count_tag:
          Name: "{{ item.name }}"
        exact_count: 1
        assign_public_ip: "{{ item.assign_public_ip }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      with_items: "{{ ec2 }}"

ec2_vpc_subnet_factsモジュールでサブネットIDを収集する

はじめに、EC2インスタンスを配置するサブネットの情報を取得するため、ec2_vpc_subnet_factsモジュールを実行します。 Ansibleでは多くの_factsとサフィックスがついたモジュールが提供されており、様々な情報を取得するために利用できます。 AWS CLIであれば、describe系のコマンドと考えて良いでしょう。

    - name: Facts subnet
      ec2_vpc_subnet_facts:
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _subnet_facts

ここでは、filterなどのオプションは指定せず、指定したリージョンの全サブネットを取得し、registerで変数に格納しておきます。 実行結果は次のような形式です。

TASK [debug] *******************************************************************
ok: [localhost] => {
    "_subnet_facts": {
        "changed": false, 
        "subnets": [
            {
                "availability_zone": "ap-northeast-1c", 
                "available_ip_address_count": 250, 
                "cidr_block": "172.31.101.0/24", 
                "default_for_az": "false", 
                "id": "subnet-xxxxxxxx1", 
                "map_public_ip_on_launch": "false", 
                "state": "available", 
                "tags": {
                    "Name": "FrontC"
                }, 
                "vpc_id": "vpc-xxxxxxxx"
            }, 
            {
                "availability_zone": "ap-northeast-1a", 
                "available_ip_address_count": 250, 
                "cidr_block": "172.31.100.0/24", 
                "default_for_az": "false", 
                "id": "subnet-xxxxxxxx2", 
                "map_public_ip_on_launch": "false", 
                "state": "available", 
                "tags": {
                    "Name": "FrontA"
                }, 
                "vpc_id": "vpc-xxxxxxxx"
            }
        ]
    }
}

ec2モジュール

ec2モジュールはEC2インスタンスの構成管理だけでなく、stop/startなどもサポートするモジュールです。 設定次第で色々とできてしまう点は注意してください。

    - name: "EC2"
      ec2:
        image: "{{ item.image }}"
        instance_profile_name: "{{ item.instance_profile_name }}"
        instance_type: "{{ item.instance_type }}"
        key_name: "{{ item.key_name }}"
        vpc_subnet_id: "{{ _subnet_facts.subnets | selectattr('tags.Name', 'defined') | selectattr('tags.Name', 'equalto', item.subnet_name) |  map(attribute='id') | first }}"
        group: "{{ item.group }}"
        volumes:
          - device_name: /dev/xvda
            volume_type: gp2
            volume_size: "{{ item.root_volume_size }}"
            delete_on_termination: true
        instance_tags:
          Name: "{{ item.name }}"
        count_tag:
          Name: "{{ item.name }}"
        exact_count: 1
        assign_public_ip: "{{ item.assign_public_ip }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      with_items: "{{ ec2 }}"

ec2モジュールでは、instance_profile_nameにはプロファイル名を、instance_typeにはインスタンスタイプを、と想像通りのパラメータを持ちます。 ポイントとなるパラメータに絞って解説しますので、それ以外のパラメータはドキュメントを参照してください。

image

imageにはイメージIDを指定します。

  image: "{{ item.image }}"

今回はイメージIDをグループ変数に直接定義しましたが、最新のAmazon Linuxなど動的にイメージIDを取得することもできます。 取得する場合は、AWS CLIを実行してもよいですが、ec2_ami_findモジュールも利用できます。

vpc_subnet_id

vpc_subnet_idはサブネットIDを指定します。 IDであるため、グループ変数でIDを指定するか、動的に取得するかの二択ですが、今回はec2_vpc_subnet_factsモジュールを使って動的に取得しています。 Jinja2のフィルタ機能を利用し、タグ名から要素をフィルタし、IDのみを抽出しました。

_subnet_facts.subnets | selectattr('tags.Name', 'defined')
   | selectattr('tags.Name', 'equalto', item.subnet_name)
   |  map(attribute='id') | first

group

groupにはセキュリティグループ名のリストを指定します。

  group: "{{ item.group }}"

グループ変数に定義された名前でセキュリティグループを適用するので、IDを指定する必要がありません。 なお、group_idを利用してID指定も可能です。

count_tag, exact_count

count_tagexact_countはセットで利用します。 このパラメータは、AnsibleからEC2インスタンスを識別するために利用します。

  count_tag:
    Name: "{{ item.name }}"
  exact_count: 1

count_tagがEC2を識別するためのタグを定義します。 ここでは、Nameタグとその値を指定しました。 count_tagで識別したインスタンスが存在する場合、EC2インスタンスを新しく作成しません。

exact_countは識別したインスタンスが幾つ存在すべきかを定義します。 ここでは、1を指定しているため、識別したインスタンスが1つあれば、それ以上のEC2インスタンスを作成しません。

通常、Nameタグで指定したEC2インスタンスの名前でインスタンスが識別できれば良いので、この設定を覚えておくと良いでしょう。

まとめ

ec2モジュールでEC2インスタンスを作成する時には、count_tagexact_countを利用し、EC2インスタンスの識別方法を定義することがポイントです。 また、可読性の高いグループ変数の定義があれば、EC2インスタンスの仕様書となります。 宣言的で解りやすい仕様書があれば、構築作業もスムーズに進むことでしょう。