LoginSignup
1
2

More than 5 years have passed since last update.

RDSをAnsibleで管理する

Last updated at Posted at 2017-04-18

はじめに

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

今回は、RDSをAnsibleで管理してみます。
RDSインスタンスを作成するためには、

  • サブネットグループ
  • パラメータグループ

が必要になるので、一気通貫で作成できるようにします。

やること

  • サブネットグループ作成
  • パラメータグループ作成
  • RDSインスタンス作成

ポイント

サブネットグループを作成するためにサブネットIDが、RDSインスタンスを作成するためにセキュリティグループIDがそれぞれ必要となりますが、IDをAnsibleのYAMLに書きたくないので、それぞれ名前からIDを取得する実装とします。

また、Ansibleのrdsモジュールにはなぜかストレージタイプを指定するためのオプションがなく、作成されたインスタンスのストレージはマグネティックになってしまいますので、インスタンス作成の部分はshellモジュールでaws cliを利用して実装しています。

冪等性担保のために、インスタンス作成前に同名のインスタンスの存在確認をして、その結果に基づいてインスタンス作成処理を実行します。

注意

RDSのストレージタイプには、マグネティック、汎用SSD、プロビジョンドIOPS SSDがありますが、今回のPlaybookはプロビジョンドIOPS SSDには対応していません(プロビジョンドIOPSパラメータを定義していないため)。

前提

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

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

  • VPC
    • AnsibleVPC
  • サブネット
    • private-a
    • private-c
  • セキュリティグループ
    • db_server

sample

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

  • サブネットグループ
    • private
      • private-a(AZ-a)
      • private-c(AZ-c)
  • パラメータグループ
    • mysql57-sample
  • RDSインスタンス
    • sample-db
      • セキュリティグループ
        • db_server
      • サブネットグループ
        • private
      • パラメータグループ
        • mysql57-sample

ディレクトリ構成

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

inventory

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

hosts/aws
[aws]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。
RDSインスタンスのパスワードをそのまま記載していますが、実際はansible-vaultで暗号化したファイルなどに記載してください。

host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    vpc:
      name: AnsibleVPC    # ターゲットのVPC名
    rds:
      subnet_group:
        - name: private
          description: 'private subnet group'
          subnets:
            - private-a #サブネット名
            - private-c #サブネット名
      param_group:
        - name: mysql57-sample
          description: 'MySql5.7 sample'
          engine: mysql5.7
          params:
            character_set_database: utf8
            character_set_server: utf8
      instance:
        - name: sample-db
          db_engine: MySQL
          engine_version: 5.7.16
          multi_zone: no
          zone: a
          size: 5
          instance_type: db.t2.micro
          storage_type: gp2
          parameter_group: mysql57-sample
          subnet_group: private
          security_group:
            - db-server #セキュリティグループ名
          username: ADMIN_USER
          password: ADMIN_PASSWORD
          tags:
            - key: Role
              value: sample-db
            - key: Env
              value: dev

Role

まずVPCを特定するためにidが必要ですが、こちらと同様、VPC名でidを取得します。

後続タスクで必要となる、サブネットIDとセキュリティグループIDを取得し、それぞれ名前からIDを参照するためにディクショナリを生成します。

roles/rds/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: subnet id取得
  ec2_vpc_subnet_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      "tag:Name": "{{ item.1 }}"
  with_subelements:
    - "{{ my_vars.aws.rds.subnet_group }}"
    - subnets
  register: subnet_fact
  when: my_vars.aws.rds.subnet_group is defined

- name: subnet dict作成
  set_fact:
    subnets_dict: >-
      {%- set dict = {} -%}
      {%- set inc = 0 -%}
      {%- set subnet_group_cnt = my_vars.aws.rds.subnet_group|length -%}
      {%- for i in range(subnet_group_cnt) -%}
      {%-   set list = [] -%}
      {%-   for j in range(my_vars.aws.rds.subnet_group[i].subnets|length) -%}
      {%-     set _ = list.append(subnet_fact.results[inc+j].subnets[0].id) -%}
      {%-   endfor -%}
      {%-   set _ = dict.update({my_vars.aws.rds.subnet_group[i].name: list}) -%}
      {%-   set inc = inc + subnet_group_cnt -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.rds.subnet_group is defined

- name: securitygroup id取得
  ec2_group_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      group_name: "{{ item.1 }}"
  with_subelements:
    - "{{ my_vars.aws.rds.instance }}"
    - security_group
  register: sg_fact
  when: my_vars.aws.rds.instance is defined

- name: securitygroup dict作成
  set_fact:
    sg_dict: >-
      {%- set dict = {} -%}
      {%- for i in range(sg_fact.results|length) -%}
      {%-   set _ = dict.update({sg_fact.results[i].security_groups[0].group_name: sg_fact.results[i].security_groups[0].group_id}) -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.rds.instance is defined

- name: RDS subnet-group作成
  rds_subnet_group:
    state: present
    name: "{{ item.name }}"
    description: "{{ item.description }}"
    region: "{{ my_vars.aws.common.region }}"
    subnets: >-
      {%- set subnetname = item.name -%}
      {%- set list = subnets_dict[item.name] -%}
      {{ list }}
  with_items: "{{ my_vars.aws.rds.subnet_group }}"
  register: rds_subnet_group

- debug: var=rds_subnet_group

- name: RDS パラメータグループ作成
  rds_param_group:
    state: present
    name: "{{ item.name }}"
    description: "{{ item.description }}"
    region: "{{ my_vars.aws.common.region }}"
    engine: "{{ item.engine }}"
    params: "{{ item.params }}"
  with_items: "{{ my_vars.aws.rds.param_group }}"
  register: rds_param_group
  when: my_vars.aws.rds.param_group is defined

- debug: var=rds_param_group

- name: RDS インスタンス存在確認
  shell: >-
    export AWS_DEFAULT_REGION='{{ my_vars.aws.common.region }}' && \
    aws rds describe-db-instances \
      --db-instance-identifier {{ item.name }} \
      --query 'DBInstances[].DBInstanceIdentifier' \
      --output text
  with_items: "{{ my_vars.aws.rds.instance }}"
  check_mode: no
  failed_when: no
  changed_when: no
  register: rds_instance_chk
  when: my_vars.aws.rds.instance is defined

- name: RDS インスタンス list作成
  set_fact:
    rds_list: >-
      {%- set rdslist = [] -%}
      {%- for i in range(rds_instance_chk.results|length) -%}
      {%-   if rds_instance_chk.results[i].stdout != "" -%}
      {%-     set _ = rdslist.append(rds_instance_chk.results[i].stdout) -%}
      {%-   endif -%}
      {%- endfor -%}
      {{ rdslist }}
  check_mode: no

- name: RDS インスタンス作成
  shell: >-
    export AWS_DEFAULT_REGION='{{ my_vars.aws.common.region }}' && \
    aws rds create-db-instance \
      --db-instance-identifier {{ item.name }} \
      --db-instance-class {{ item.instance_type }} \
      --allocated-storage {{ item.size }} \
      --engine {{ item.db_engine }} \
      --master-username {{ item.username }} \
      --master-user-password {{ item.password }} \
      --vpc-security-group-ids \
      {% set sg_list = [] %}
      {%- for i in range(item.security_group|length) -%}
      {%-   set _ = sg_list.append(sg_dict[item.security_group[i]]) -%}
      {%- endfor -%}
      {{ sg_list | join(' ') }} \
      --availability-zone {{ my_vars.aws.common.region }}{{ item.zone }} \
      --db-subnet-group-name {{ item.subnet_group }} \
      --db-parameter-group-name {{ item.parameter_group }} \
      {% if item.multi_zone == True %}
      --multi-az \
      {% else %}
      --no-multi-az \
      {% endif %}
      --engine-version {{ item.engine_version }} \
      --storage-type {{ item.storage_type }} \
      --copy-tags-to-snapshot \
      --tags \
      {% set tag_list = [] %}
      {%- for i in range(item.tags|length) -%}
      {%-   set _ = tag_list.append("Key="+item.tags[i]['key']+",Value="+item.tags[i]['value']) -%}
      {%- endfor -%}
      {{ tag_list | join(' ') }}
  with_items: "{{ my_vars.aws.rds.instance }}"
  when: >-
    not ansible_check_mode
    and '{{ item.name }}' not in rds_list

site.yml

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

実行

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

まとめ

RDSは、関連リソースを合わせて作成する必要がありますが、Ansibleで自動化できると楽です。
また、今回のplaybookはインスタンスの変更には対応していません。

参考になれば幸いです。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2