AnsibleによるVPCの構成管理
渡辺です。
AnsibleによるAWSリソースの構成管理シリーズ、 今回はVPC(ネットワーク)周りを構築するAnsible Playbookを紹介します。 Ansible2.0で追加されたモジュールを利用しているため、現行のAnsible2.1系でも利用可能です。
グループ変数のサンプル
今回紹介するPlaybookで定義するグループ変数のサンプルです。
env: Prd profile: default region: ap-northeast-1 vpc: name: VPC cidr_blodk: 10.0.0.0/16 subnet: - name: PublicA az: ap-northeast-1a cidr: 10.0.1.0/24 route: Public - name: PublicC az: ap-northeast-1c cidr: 10.0.2.0/24 route: Public - name: PrivateA az: ap-northeast-1a cidr: 10.0.11.0/24 route: Private - name: PrivateC az: ap-northeast-1c cidr: 10.0.12.0/24 route: Private
VPCとサブネットを構成するにあたって必要最低限の情報がスッキリとまとまっているかと思います。 この定義、すなわちグループ変数を読み込んで、VPCを作るのが目標です。
VPC Playbook
VPC構築用のPlaybookです。
--- - hosts: localhost connection: local gather_facts: False tasks: - name: "{{ env }} VPC" ec2_vpc_net: name: "{{ env }} {{ vpc.name }}" cidr_block: "{{ vpc.cidr_blodk }}" profile: "{{ profile }}" region: "{{ region }}" register: _vpc - debug: var=_vpc verbosity=1 - name: Internet gateway ec2_vpc_igw: vpc_id: "{{ _vpc.vpc.id }}" profile: "{{ profile }}" region: "{{ region }}" register: _igw - name: "{{ env }}Public subnet" ec2_vpc_subnet: vpc_id: "{{ _vpc.vpc.id }}" az: "{{ item.az }}" cidr: "{{ item.cidr }}" resource_tags: Name: "{{ item.name }}" profile: "{{ profile }}" region: "{{ region }}" with_items: "{{ subnet }}" register: _subnet - debug: var=_subnet verbosity=1 - name: Public Route ec2_vpc_route_table: vpc_id: "{{ _vpc.vpc.id }}" tags: Name: "{{ env }}PublicRoute" subnets: "{{ _subnet.results | selectattr('item.route', 'equalto', 'Public') | map(attribute='subnet.id') | list }}" routes: - dest: 0.0.0.0/0 gateway_id: "{{ _igw.gateway_id }}" profile: "{{ profile }}" region: "{{ region }}" register: _public_route - debug: var=_public_route verbosity=1 - name: Private Route ec2_vpc_route_table: vpc_id: "{{ _vpc.vpc.id }}" tags: Name: "{{ env }}PrivateRoute" subnets: "{{ _subnet.results | selectattr('item.route', 'equalto', 'Private') | map(attribute='subnet.id') | list }}" profile: "{{ profile }}" region: "{{ region }}" register: _private_route - debug: var=_private_route verbosity=1
ec2_vpc_net
ec2_vpc_netモジュールは、VPCを構築するモジュールです。 似た名前のモジュールでec2_vpcモジュールがありますが、こちらはレガシーなモジュールになっているので注意してください。
- name: "{{ env }} VPC" ec2_vpc_net: name: "{{ env }} {{ vpc.name }}" cidr_block: "{{ vpc.cidr_blodk }}" profile: "{{ profile }}" region: "{{ region }}" register: _vpc
ec2_vpc_net
では、VPC名とCIDRブロックを指定し、VPCを定義します。
VPCの情報は変数_vpcに格納し、他のモジュールでVPC IDなどを指定するために利用します。
Ansibleは冪等性を確保しますので、初回実行時にAWS環境にVPCが作成されます。 2回目の実行を行った場合、VPCが既に存在するので作成はされません。 この時、VPC名で識別しているため、VPC名を変更すると別のVPCとして認識しますので注意しましょう。
ec2_vpc_igw
ec2_vpc_igwモジュールは、Internet Gatewayを構築するモジュールです。
- name: Internet gateway ec2_vpc_igw: vpc_id: "{{ _vpc.vpc.id }}" profile: "{{ profile }}" region: "{{ region }}" register: _igw
ec2_vpc_igw
モジュールでは、VPC IDをパラメータとして指定します。
VPC IDは、ec2_vpc_net実行時の結果に含まれるので、register
で保存した変数_vpc
から参照します。
なお、実行結果にどのような構造で情報が格納されているかを確認するには、debugモジュールを利用します。
- debug: var=_vpc verbosity=1
実行結果のサンプルは次のようになります。
TASK [debug] ******************************************************************* ok: [localhost] => { "_vpc": { "changed": false, "vpc": { "cidr_block": "10.0.0.0/16", "classic_link_enabled": null, "dhcp_options_id": "dopt-xxxxxxxxx", "id": "vpc-xxxxxxxxx", "instance_tenancy": "default", "is_default": false, "state": "available", "tags": { "Name": "Prd VPC" } } } }
ec2_vpc_subnet
ec2_vpc_subnetモジュールはサブネットを構築するモジュールです。
- name: "{{ env }}Public subnet" ec2_vpc_subnet: vpc_id: "{{ _vpc.vpc.id }}" az: "{{ item.az }}" cidr: "{{ item.cidr }}" resource_tags: Name: "{{ item.name }}" profile: "{{ profile }}" region: "{{ region }}" with_items: "{{ subnet }}" register: _subnet
ec2_vpc_subnet
モジュールでは、VPC IDの他にAZやCIDRなどをパラメータとして指定します。
このモジュール利用時のポイントは、with_items
構文を用いて、グループ変数で定義されたsubnet
の要素をループ実行している点です。
サブネットの定義を1行づつ行うことも可能ですが、Playbookの再利用性を高くするため、リスト型の変数とループをを活用しました。
これならば、サブネットが多いシステムであってもPlaybookの変更は不要で、グループ変数の変更のみで済むことになります。
with_items
構文でループを行う時は、item
という変数に各ループでの要素が代入されます。
item
の各属性は.で参照可能なので直感的でしょう。
ec2_vpc_route_table
ec2_vpc_route_tableモジュールは、Routeテーブルを構築するモジュールです。
PublicRoute
とPrivateRoute
の2つを作成し、サブネットをバインドします。
- name: Public Route ec2_vpc_route_table: vpc_id: "{{ _vpc.vpc.id }}" tags: Name: "{{ env }}PublicRoute" subnets: "{{ _subnet.results | selectattr('item.route', 'equalto', 'Public') | map(attribute='subnet.id') | list }}" routes: - dest: 0.0.0.0/0 gateway_id: "{{ _igw.gateway_id }}" profile: "{{ profile }}" region: "{{ region }}" register: _public_route - name: Private Route ec2_vpc_route_table: vpc_id: "{{ _vpc.vpc.id }}" tags: Name: "{{ env }}PrivateRoute" subnets: "{{ _subnet.results | selectattr('item.route', 'equalto', 'Private') | map(attribute='subnet.id') | list }}" profile: "{{ profile }}" region: "{{ region }}" register: _private_route
vpc_id
やgateway_id
の参照はもう問題無く理解できると思います。
ここでポイントとなるのは、subnets
です。
subnets
には対象となるサブネットのsubnet idをリストとして指定しなければなりません。
しかし、サブネットはループで作成しており、変数_subnet
には複数のサブネットの作成結果が格納されます。
デバッグ出力するとこんなイメージです(長いので一部省略)。
TASK [debug] ******************************************************************* ok: [localhost] => { "_subnet": { "changed": false, "msg": "All items completed", "results": [ { "item": { "az": "us-east-2a", "cidr": "10.0.1.0/24", "name": "FrontA", "route": "Public" }, "subnet": { "id": "subnet-a9c136c0" } }, { "item": { "az": "us-east-2c", "cidr": "10.0.2.0/24", "name": "FrontC", "route": "Public" }, "subnet": { "id": "subnet-815970cb" } }, { "item": { "az": "us-east-2a", "cidr": "10.0.11.0/24", "name": "ServiceA", "route": "Public" }, "subnet": { "id": "subnet-acc136c5" } }, { "item": { "az": "us-east-2c", "cidr": "10.0.12.0/24", "name": "ServiceC", "route": "Public" }, "subnet": { "id": "subnet-9a5970d0" } }, { "item": { "az": "us-east-2a", "cidr": "10.0.21.0/24", "name": "DatabaseA", "route": "Private" }, "subnet": { "id": "subnet-a6c136cf" } }, { "item": { "az": "us-east-2c", "cidr": "10.0.22.0/24", "name": "DatabaseC", "route": "Private" }, "subnet": { "id": "subnet-935970d9" } } ] } }
すなわち、results
属性にリストとして各ループの実行結果が格納されています。
また、item
属性では各ループで利用したitem
属性を参照できます。
作成されたサブネットのid
はsubnet.id
で参照できるようです。
プログラマであれば、MapとFilterが使えれば、サブネットIDリストを作れると考えるでしょう。 AnsibleではJinja2の構文が利用できるため、次のようにしてサブネットIDリストに変換します。
_subnet.results | selectattr('item.route', 'equalto', 'Public') | map(attribute='subnet.id') | list _subnet.results | selectattr('item.route', 'equalto', 'Private') | map(attribute='subnet.id') | list
_subnet.results
(リスト)をselectattr
でitem.route
の値でフィルタリングし、subnet.id
にマッピングした後、リスト化する、といった流れです。
結果はそれぞれ次のようになります。
["subnet-a9c136c0", "subnet-815970cb", "subnet-acc136c5", "subnet-9a5970d0"] ["subnet-a6c136cf", "subnet-935970d9"]
まとめ
今回はAnsibleのVPC関連モジュールを利用してネットワークを構成するPlaybookを紹介しました。
あわせて、ループやJinja2の構文を活用したプログラマティックなAnsibleのテクニックを紹介しました。 Ansibleでは、このようなループや強力なフィルタができることが大きなメリットです。 しかし、複雑すぎるPlaybookは可読性を低くする可能性があるので注意しなければなりません。 グループ変数の可読性を高くするか、Playbookの可読性を高くするか、バランス良く選択してください。