AnsibleによるVPC Peering

2016.10.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

今回は、VPC Peering関連のAnsibleのAWSモジュールを紹介します。

AWSではVPCがネットワークのベースとなり、VPCに幾つかのサブネットを作成し、リソースを配置することになります(一部、VPCと関連しないリソースもある)。 VPCは、システム単位に分割したり、検証環境と本番環境で分割したりと、複数作成するでしょう。 この時、原則としては各VPCは独立したネットワークであり、相互に通信を行うことができません。 このため、セキュアにネットワークを構成できるわけです。

一方、大規模システムで、複数のシステムが各VPCに構築されている場合、複数のAWSアカウントにシステムが分散されている場合など、VPC間の通信を許可したいケースもあります。 このような要求を満たすのが、VPC Peeringです。 VPC Peeringでは、複数のVPCの通信を許可します。 ただし、各VPCのCIDR Blockが重複していないことが前提です。

なお、今回利用するec2_vpc_peerモジュールは、Ansible2.2以降で利用可能なモジュールです。

VPC Peeringを行う場合のPlaybook

通常、Ansibleでは、環境(開発/検証/本番)毎にホストとグループ変数を利用して共通のPlaybookを利用すべきです。 これは重複を減らす事でメンテナンス性を高くしたいからです。 しかし、VPCピアリングが絡む場合、ひとつのPlaybookで可能な限りまとめましょう。

これは、VPCピアリングを行うと、VPC同士が相互参照することになるためです。 さらに、Routeテーブルなど、先にすべてのVPCとVPCピアリングの設定を済ませないと設定できないことがあります。 また、Ansibleでは依存関係の解決などは行われないため、どのリソースを先に作成するかも考慮します。

同一AWSアカウントでのVPC Peering

Playbookです。

---
- hosts: localhost
  connection: local
  gather_facts: False
  vars:
    profile: default
    region: us-east-2
    vpc_a:
      name: VPC A
      cidr_blodk: 10.0.0.0/16
    vpc_b:
      name: VPC B
      cidr_blodk: 10.1.0.0/16
  tasks:
    - name: "{{ vpc_a.name }}"
      ec2_vpc_net:
        name: "{{ vpc_a.name }}"
        cidr_block: "{{ vpc_a.cidr_blodk }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _vpc_a
    - debug: var=_vpc_a verbosity=1
    - name: "{{ vpc_b.name }}"
      ec2_vpc_net:
        name: "{{ vpc_b.name }}"
        cidr_block: "{{ vpc_b.cidr_blodk }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _vpc_b
    - debug: var=_vpc_b verbosity=1
    # VPC Peering
    - name: "VPC Peering"
      ec2_vpc_peer:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        peer_vpc_id: "{{ _vpc_b.vpc.id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _vpc_peer
    - debug: var=_vpc_peer verbosity=1
    - name: "Accept VPC Peering request"
      ec2_vpc_peer:
        peering_id: "{{ _vpc_peer.peering_id }}"
        state: accept
        profile: "{{ profile }}"
        region: "{{ region }}"
      when: _vpc_peer.peering_id is defined
    - name: Public Route VPC A
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_b.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined
    - name: Public Route VPC B
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_b.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_a.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined

ポイントとなるのは、ピアリングするVPCをそれぞれ作成し、registerで登録した変数からVPC IDを取得している点です。 また、Routeはピアリングが完了した後に作成しなければなりません。 なお、VPCのCIDRブロックが重複しているとピアリングは失敗します。

ec2_vpc_peer

ec2_vpc_peerモジュールは、VPC ピアリングを作成します。

    - name: "VPC Peering"
      ec2_vpc_peer:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        peer_vpc_id: "{{ _vpc_b.vpc.id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      register: _vpc_peer

ピアリング元のVPC IDと、ピアリング先のVPC IDを指定してください。 各VPC IDはec2_vpc_netモジュールの実行結果から取得できます。

続けてピアリングを承認します。

    - name: "Accept VPC Peering request"
      ec2_vpc_peer:
        peering_id: "{{ _vpc_peer.peering_id }}"
        state: accept
        profile: "{{ profile }}"
        region: "{{ region }}"
      when: _vpc_peer.peering_id is defined

承認時は、peering_idと、stateacceptを指定してください。 when句でpeering_idの存在チェックを行っているのは、ec2_vpc_peerがcheckモードに対応していないためです。 checkモードの時に変数参照に失敗し、エラーになることを回避しています。 このあたりは今後のアップデートで改善するでしょう。

注意しなければならないのはec2_vpc_peerモジュールでは、作成と承認を別のタスクとして定義しなければならない点です。 これは違和感を感じるかも知れませんが、AWSアカウントを横断したピアリングの例を見ると納得できると思います。

ルーティング

VPC ピアリングをしただけでは通信することができません。 ec2_vpc_peerモジュールを使い、ルーティングを設定しましょう。

    - name: Public Route VPC A
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_b.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined
    - name: Public Route VPC B
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_b.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_a.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined

ポイントとなるのは、routesvpc_peering_connection_idの指定時に、registerで登録されたPEERING IDを利用している点です。 また、destには通信先となるVPCのCIDRブロックを指定することになるため、グループ変数をそのまま利用しています。

このように変数を参照することで、よりプログラマチックにPlaybookを書くことができます。

AWSアカウントを横断したVPC Peering

Playbookです。

---
- hosts: localhost
  connection: local
  gather_facts: False
  vars:
    profile_a: account_a
    profile_b: account_b
    aws_account_id_b: "999999999999"
    region: us-east-2
    vpc_a:
      name: VPC A
      cidr_blodk: 10.0.0.0/16
    vpc_b:
      name: VPC B
      cidr_blodk: 10.1.0.0/16
  tasks:
    - name: "{{ vpc_a.name }}"
      ec2_vpc_net:
        name: "{{ vpc_a.name }}"
        cidr_block: "{{ vpc_a.cidr_blodk }}"
        profile: "{{ profile_a }}"
        region: "{{ region }}"
      register: _vpc_a
    - debug: var=_vpc_a verbosity=1
    - name: "{{ vpc_b.name }}"
      ec2_vpc_net:
        name: "{{ vpc_b.name }}"
        cidr_block: "{{ vpc_b.cidr_blodk }}"
        profile: "{{ profile_b }}"
        region: "{{ region }}"
      register: _vpc_b
    - debug: var=_vpc_b verbosity=1
    # VPC Peering
    - name: "VPC Peering"
      ec2_vpc_peer:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        peer_owner_id: "{{ aws_account_id_b }}"
        peer_vpc_id: "{{ _vpc_b.vpc.id }}"
        profile: "{{ profile_a }}"
        region: "{{ region }}"
      register: _vpc_peer
    - debug: var=_vpc_peer verbosity=1
    - name: "Accept VPC Peering request"
      ec2_vpc_peer:
        peering_id: "{{ _vpc_peer.peering_id }}"
        state: accept
        profile: "{{ profile_b }}"
        region: "{{ region }}"
      when: _vpc_peer.peering_id is defined
    - name: Public Route VPC A
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_a.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_b.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile_a }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined
    - name: Public Route VPC B
      ec2_vpc_route_table:
        vpc_id: "{{ _vpc_b.vpc.id }}"
        tags:
          Name: "PublicRoute"
        routes:
          - dest: "{{ vpc_a.cidr_blodk }}"
            vpc_peering_connection_id: "{{ _vpc_peer.peering_id }}"
        profile: "{{ profile_b }}"
        region: "{{ region }}"
      when:
        - _vpc_peer.peering_id is defined

VPC AとVPC Bがそれぞれ別のAWSアカウントに作るため、各AWSアカウントのプロファイルが設定されます。

ピアリング作成時にpeer_owner_idを指定しなければなりません。 また、ピアリング承認はピアリング先のAWSアカウントで行うため、プロファイルの指定が変わってくる点を注意しましょう。

まとめ

ec2_vpc_peerモジュールを利用して、VPCピアリングを作成しました。 WSリソースを構成管理する場合、今回の複数アカウント間のVPCピアリングのような、複数のAWSアカウントに横断するケースがあります。 Ansibleの場合、各モジュールのprofileを指定し、順を追った手順を記述すれば、良いことになります。

このように、AWS CLIで構築スクリプトを書くことに近いでしょう。 構築スクリプトよりも宣言的でメンテしやすいのがAnsibleでAWSリソースの構成管理を行うメリットです。