#はじめに
AnsibleにはAWSのリソースを操作できるモジュールが豊富に用意されています。
今回は、複雑になりがちなセキュリティグループをAnsibleで管理してみます。
#やること
- セキュリティグループ作成
- ルール作成(インバウンドのみ)
##ポイント
セキュリティグループは、通信元、宛先をIPアドレスではなくセキュリティグループを指定することもできるため、Ansibleで作成する際に矛盾が生じることもあります。
例えば、
A:Bからの通信を許可
B:Aからの通信を許可
のようなケースの場合、Aの作成時にはBが存在しないとエラーになりそうですが、実際はルールが空の状態でBも作成されます。
Ansibleで構成する場合、YAMLであるべき構成を定義していくことになります。
with_dict
でループさせて作成する場合、ループの順序が考慮されないため、Aの作成時にBが存在しない等でタスクがfailしてしまうことがありますが、上記の通り、空のBが作成されていますので、もう一度タスクを実行すると成功します。
このあたりの挙動に気をつけて実装していきます。
#前提
AWS関連のモジュール実行にはbotoが必要です。
credential情報は環境変数かaws configure
でセットしてある必要があります。
#sample
以下のようなセキュリティグループを作成します。
- common
- xxx.xxx.xxx.xxxからSSH許可
- commonからのICMPを全て許可
- web_server
- AnyからHTTP許可
- db_server
- web_serverからMySQL許可
##ディレクトリ構成
site.yml
roles/
|--security_group/
| |--tasks/
| | |--main.yml
hosts/aws #inventory
host_vars/
|--localhost.yml
##inventory
AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。
[aws]
localhost
##vars
こんな感じに変数を定義します。今回はhost_varsで定義しました。
---
my_vars:
aws:
common:
region: ap-northeast-1
vpc:
name: AnsibleVPC # ターゲットのVPC名
security_group:
common:
group_name: common
description: common rules
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: xxx.xxx.xxx.xxx/32
- proto: icmp
from_port: -1
to_port: -1
group_name: common
web_server:
group_name: web_server
description: web_server rules
rules:
- proto: tcp
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
db_server:
group_name: db_server
description: db_server rules
rules:
- proto: tcp
from_port: 3306
to_port: 3306
group_name: web_server
##Role
まずVPCを特定するためにidが必要ですが、こちらと同様、VPC名でidを取得します。
今回はリストではなくディクショナリとしてセキュリティグループを定義しましたので、with_dict
でループさせます。
ルールの定義はリストで複数指定できますので、そのようにvarsに定義しました。
このままカンマで繋げてtaskで使用します。
ただし、1つしか定義されていない場合、不要なカンマがついてしまうので、要素数で処理を分岐させています。
アウトバウンドは、多くの場合デフォルト(全許可)で使用すると思うので、vars未定義の場合デフォルト値が採用されるようにしています。
セキュリティグループの構成上、一度目のtaskがfailした場合、リトライするようにしました。
---
- 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: セキュリティグループ作成
ec2_group:
name: "{{ item.value.group_name }}"
description: "{{ item.value.description }}"
region: "{{ my_vars.aws.common.region }}"
vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
rules: >-
{%- if item.value.rules|length == 1 -%}
{{ item.value.rules }}
{%- else -%}
{{ item.value.rules | join(',') }}
{%- endif -%}
rules_egress: >-
{%- if item.value.rules_egress is undefined -%}
{%- set egress_item = {"proto":"all", "from_port":"0", "to_port":"65535", "cidr_ip":"0.0.0.0/0"} -%}
{%- set egress = [egress_item] -%}
{{ egress }}
{%- elif item.value.rules_egress|length == 1 -%}
{{ item.value.rules_egress }}
{%- else -%}
{{ item.value.rules_egress | join(',') }}
{%- endif -%}
with_dict: "{{ my_vars.aws.security_group }}"
when: my_vars.aws.security_group is defined
ignore_errors: yes
register: sg
- debug: var=sg
- name: セキュリティグループ作成リトライ
ec2_group:
name: "{{ item.value.group_name }}"
description: "{{ item.value.description }}"
region: "{{ my_vars.aws.common.region }}"
vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
rules: >-
{%- if item.value.rules|length == 1 -%}
{{ item.value.rules }}
{%- else -%}
{{ item.value.rules | join(',') }}
{%- endif -%}
rules_egress: >-
{%- if item.value.rules_egress is undefined -%}
{%- set egress_item = {"proto":"all", "from_port":"0", "to_port":"65535", "cidr_ip":"0.0.0.0/0"} -%}
{%- set egress = [egress_item] -%}
{{ egress }}
{%- elif item.value.rules_egress|length == 1 -%}
{{ item.value.rules_egress }}
{%- else -%}
{{ item.value.rules_egress | join(',') }}
{%- endif -%}
with_dict: "{{ my_vars.aws.security_group }}"
when: my_vars.aws.security_group is defined and sg | failed
register: sg_retry
- debug: var=sg_retry
##site.yml
---
- name: security_group
hosts: localhost
connection: local
roles:
- role: security_group
##実行
$ ansible-playbook -i hosts/aws -l localhost site.yml
#まとめ
同じタスクを2つ定義するのがカッコ悪いですが、なんとかセキュリティグループを管理できるようになりました。
セキュリティグループはマネコンからだとどういう構成になっているか把握しづらいですが、AnsibleのYAMLで管理すると見通しがよくなるのではないでしょうか。
#参考