#はじめに
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)
- private
- パラメータグループ
- mysql57-sample
- RDSインスタンス
- sample-db
- セキュリティグループ
- db_server
- サブネットグループ
- private
- パラメータグループ
- mysql57-sample
- セキュリティグループ
- sample-db
##ディレクトリ構成
site.yml
roles/
|--rds/
| |--tasks/
| | |--main.yml
hosts/aws #inventory
host_vars/
|--localhost.yml
##inventory
AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。
[aws]
localhost
##vars
こんな感じに変数を定義します。今回はhost_varsで定義しました。
RDSインスタンスのパスワードをそのまま記載していますが、実際はansible-vaultで暗号化したファイルなどに記載してください。
---
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を参照するためにディクショナリを生成します。
---
- 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
---
- name: rds
hosts: localhost
connection: local
roles:
- role: rds
##実行
$ ansible-playbook -i hosts/aws -l localhost site.yml
#まとめ
RDSは、関連リソースを合わせて作成する必要がありますが、Ansibleで自動化できると楽です。
また、今回のplaybookはインスタンスの変更には対応していません。
参考になれば幸いです。
#参考