AnsibleでRoute53を管理する


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

今回は、CLIだと何かと心が折れるRoute53をAnsibleで管理してみます。

#やること
- パブリックゾーン作成／削除
- プライベートゾーン作成／削除
- Aレコード他各種レコード作成／削除
- ALIASレコード作成／削除


##ポイント
ELBについては、DNS名を自動取得してALIASレコードを登録するようにします。
レコード／ゾーンの削除については`enable: no`を設定することで実現します。
未定義だからといって既存レコードを削除しないようにします。


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

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

- VPC
	- AnsibleVPC
	
※プライベートゾーン作成時に必要

#注意
ALIASレコード作成時のhosted_zone_idについては、リソースごとに固定となっています。
http://docs.aws.amazon.com/ja_jp/general/latest/gr/rande.html

#sample

以下のようなDNSゾーンおよびレコードを作成します。

- public
	- testdomain.com
		- www.testdomain.com (Aレコード)
		- text.testdomain.com (TXTレコード)
		- cname.testdomain.com (CNAMEレコード)
		- elb.testdomain.com (ALIASレコード:ELB)
		- s3.testdomain.com (ALIASレコード:s3)
- private
	- testdomain.local
		- test.testdomain.local(Aレコード:複数IPアドレス)

##ディレクトリ構成

```bash:ディレクトリ構成
site.yml
roles/
|--route53/
|  |--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名
    route53:
      - name: testdomain.com
        enable: yes       # デフォルトはyes。noを指定すると削除になる
        description: public zone
        records:
          - record: testdomain.com #レコード名がドメイン名と同じになる場合はドメイン名を指定する
            record_values:
              - 10 mail.testdomain.com
            ttl: 300
            type: MX
          - record: www
            type: A       # 指定なしだとAレコード
            ttl: 7200     # 指定なしだと300
            record_values:
              - 10.1.1.1
          - record: text
            type: TXT
            record_values:
              - '"var"'   # このようにクォートする
          - record: cname
            type: CNAME
            record_values:
              - www.example.com
          - record: elb
            alias: yes    # ALIASレコードの場合
            target: elb   # ELBを指定するとrecord_valuesのELB名からDNS名を取得
            record_values:
              - testelb
            hosted_zone_id: Z14GRHDCWA56QT #固定値（ELB）
          - record: s3
            alias: yes
            record_values:
              - s3-website-ap-northeast-1.amazonaws.com
            hosted_zone_id: Z2M4EHUR26P7ZW #固定値（s3）
          - record: cloudfront
            alias: yes
            record_values:
              - XXXX.cloudfront.net
            hosted_zone_id: Z2FDTNDATAQYW2 #固定値（CloudFront）
      - name: testdomain.local
        description: private zone
        private: yes      # プライベートゾーン
        records:
          - record: test
            type: A
            record_values:
              - 192.168.1.2 # 複数指定する場合
              - 192.168.1.3
              - 192.168.1.4
            enable: yes   # デフォルトはyes。noを指定すると削除になる
          - record: test2
            type: A
            record_values:
              - 192.168.1.5
            enable: no    # 削除する場合
```

##Role

Route53はリージョン、VPCに依存しませんが、プライベートゾーン作成にはVPC情報が必要になります。
そのためのVPCを特定するためにはidが必要ですが、[こちら](http://qiita.com/tomohiko_isobe/items/c245bedfa6966305eb10)と同様、VPC名でidを取得します。

`ec2_elb_facts`モジュールでELBのエイリアスレコード作成で必要となるELBのDNS名を取得し、ELB名からDNS名を参照するためにディクショナリを生成します。

```yaml:roles/route53/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

- name: Zone作成
  route53_zone:
    zone: "{{ item.name }}"
    state: >-
      {%- if item.enable is defined and item.enable == False -%}
        absent
      {%- else -%}
        present
      {%- endif -%}
    vpc_id: >-
      {%- if item.private is defined and item.private == True -%}
        {{ vpc_net_fact.vpcs[0].id }}
      {%- else -%}
        {{ omit }}
      {%- endif -%}
    vpc_region: >-
      {%- if item.private is defined and item.private == True -%}
        {{ my_vars.aws.common.region }}
      {%- else -%}
        {{ omit }}
      {%- endif -%}
    comment: "{{ item.description | default(omit) }}"
  register: route53_zone
  with_items: "{{ my_vars.aws.route53 }}"
  when: item.name is defined

- debug: var=route53_zone

- name: elb情報収集
  ec2_elb_facts:
    region: "{{ my_vars.aws.common.region }}"
    names: "{{ item.1.record_values }}"
  register: elb_facts
  with_subelements:
    - "{{ my_vars.aws.route53 }}"
    - records
  when: item.1.target is defined and item.1.target == "elb"
  check_mode: no

- name: elb dict作成
  set_fact:
    elb_dict: >-
      {%- set dict = {} -%}
      {%- for i in range(elb_facts.results|length) -%}
      {%-   if elb_facts.results[i].elbs is defined -%}
      {%-     set _ = dict.update({elb_facts.results[i].elbs[0].name: elb_facts.results[i].elbs[0].dns_name}) -%}
      {%-   endif -%}
      {%- endfor -%}
      {{ dict }}
  when: elb_facts is defined
  check_mode: no

- name: record作成
  route53:
    zone: "{{ item.0.name }}"
    private_zone: "{{ item.0.private | default('False') }}"
    command: >-
      {%- if item.1.enable is defined and item.1.enable == False -%}
        delete
      {%- else -%}
        create
      {%- endif -%}
    overwrite: yes
    record: >-
      {%- if item.1.record == item.0.name -%}
        {{ item.1.record }}
      {%- else -%}
        {{ item.1.record }}.{{ item.0.name }}
      {%- endif -%}
    type: "{{ item.1.type | default('A')}}"
    alias: "{{ item.1.alias | default('False')}}"
    alias_hosted_zone_id: "{{ item.1.hosted_zone_id | default(omit)}}"
    ttl: "{{ item.1.ttl | default('300')}}"
    value: >-
      {%- if item.1.target is defined and item.1.target == "elb" -%}
        {{ elb_dict[item.1.record_values.0] }}
      {%- else -%}
        {{ item.1.record_values | join(',') }}
      {%- endif -%}
  register: route53_record
  with_subelements:
    - "{{ my_vars.aws.route53 }}"
    - records
  when: item.0.enable is undefined or item.0.enable == True

- debug: var=route53_record  
```

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

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


#まとめ
Route53のコード化については、[RoadWorker](https://github.com/codenize-tools/roadworker)というツールが非常に強力で、以前活用していたのですが、Ansibleでも同様のことができます。
リソース定義もYAMLの方が個人的にはわかりやすいです。

#参考
##AnsibleでAWSリソースを管理するシリーズ
- [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)
- [EC2をAnsibleで管理する](http://qiita.com/tomohiko_isobe/items/163b23d5a0e566aaa159)
- [ec2_group_factsモジュールをAnsible2.2で使えるようにする](http://qiita.com/tomohiko_isobe/items/8684b4dc8f99793e5cf8)
- [RDSをAnsibleで管理する](http://qiita.com/tomohiko_isobe/items/7ce42217e177f0c6b7ec)