Help us understand the problem. What is going on with this article?

Ansibleを使ったAWS Direct Connectの自動化

はじめに

こんにちは。小川と申します。
主にネットワークインフラを担当しています。
私が担当している箇所では、AWSでDirect Connect(以下DXと言います)を利用しています。
小さい単位でAWSアカウントを分けたいので、接続の構成は以下のようになります。
構成図.png
2018年12月現在、DXは、Cloud Formationで自動化できる対象に入っていない点と、DXGWがクロスアカウントに対応していない点から、AWSアカウントが増えるたびに以下の作業が必要になります。

  • AWS DX用アカウントで追加対象のアカウント向けVIFを追加
  • AWS 追加対象のアカウントでVIFを承認
  • オンプレ側スイッチにVLANを追加
  • オンプレ側スイッチのインタフェースにVLAN Tagを追加
  • オンプレ側スイッチにSVIを追加
  • オンプレ側スイッチにBGP Peerを追加

※2019年になると、トランジットゲートウェイがDXに対応するようですので、そちらに期待です。
https://aws.amazon.com/jp/blogs/news/new-aws-transit-gateway/

これらの作業をAWSのコンソールやスイッチを手作業で行うのはとても手間なので、Ansibleで自動化できるようにしてみました。
動作している環境は以下になります。

項目 内容
OS CentOS7.5
Ansibleバージョン 2.6.1
スイッチ Arista EOS 4.18

準備

AnsibleのインストールとAnsibleからAWS CLIとネットワーク機器を操作する為の準備を行います。

Ansibleのインストール

Ansibleのインストールを以下を参考に行います。
https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html

AWS CLIのインストール

AWS CLIのインストールを以下を参考に行います。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/installing.html

AWS CLIの設定

ansible-playbookを実行するユーザーで、aws configureを実行し、アクセスキーIDとシークレットアクセスキーを登録します。具体的な手順は以下になります。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html

コネクションIDの確認

aws directconnectコマンドを利用してコネクションIDを確認します。

DXの設定
$ aws directconnect describe-connections
{
    "connections": [
        {
            "awsDevice": "DeviceName",
            "ownerAccount": "account ID",
            "connectionId": "【この値がコネクションID】dxcon-xxxxxxxx",
            "connectionState": "available",
            "bandwidth": "1Gbps",
            "location": "Location",
            "connectionName": "dx001",
            "loaIssueTime": 1532566140.0,
            "region": "ap-northeast-1",
            "awsDeviceV2": "DeviceName"
        },
        {
            "awsDevice": "DeviceName",
            "ownerAccount": "account ID",
            "connectionId": "【この値がコネクションID】dxcon-yyyyyyyy",
            "connectionState": "available",
            "bandwidth": "1Gbps",
            "location": "Location",
            "connectionName": "dx002",
            "loaIssueTime": 1532566166.0,
            "region": "ap-northeast-1",
            "awsDeviceV2": "DeviceName"
        }
    ]
}

スイッチ側SSHキーの登録

Ansibleからスイッチ(Arista EOS)への接続は大人の事情からHTTP APIではなく、SSHを利用しています。
Ansibleを実行するユーザーからSSHでパスワードなしの公開鍵認証をできるようにスイッチ側で以下の設定を行います。

show running-configから抜粋
aaa authorization exec default local
!
no aaa root
!
username [Ansibleを実行するユーザー名] privilege 15 secret [パスワード]
username [Ansibleを実行するユーザー名] sshkey [ssh-rsaで始まる公開鍵をそのまま記載]
!
management ssh
   vrf MGMT
      no shutdown
!

インベントリの登録

ansible-playbookの-iオプションで指定するインベントリファイルを作成します。
インベントリファイルの詳細は以下をご確認ください。
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html

インベントリファイルの中身
# switch01,02をaristaというグループにまとめる
[arista]
# 操作対象のホスト名を記載
switch01
switch02

[arista:vars]
# Ansibleからの接続にnetwork_cli(SSH)を利用
ansible_connection=network_cli
# EOS(AristaのOS)を指定
ansible_network_os=eos
# SSHを実行するユーザーを指定
ansible_user=[ユーザー名]
# SSHの接続で利用する秘密鍵を指定
ansible_ssh_private_key_file=/home/[ユーザー名]/.ssh/id_rsa

# aws-cliをlocalhostで実行する為、インベントリに記載
[local]
localhost

正しく登録されているかをAnsibleのPingモジュールを使って確認します。

$ ansible -i hosts -m ping arista
switch01 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
switch02 | SUCCESS => {
    "changed": false,
    "ping": "pong"

Playbook、Roles、変数の準備

AnsibleはPlaybookを使ってタスクを実行しますが、今回のようにAWS側のVIF追加、オンプレ側のVLAN追加、BGP Peerの追加など項目が多く、場合によっては実行しないものなどがある為、Rolesを使って細かくタスクを分割します。Rolesの詳細を以下をご確認ください。
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html

Playbookの作成

Playbookを任意のディレクトリに作成します。Playbookの中身はrolesを記載するだけにして、作業に応じて必要なものを追加したりコメントアウトするだけの運用にしています。

Playbookの中身
# AWS向けの設定
## インベントリのlocalグループに対して実行
- hosts: local
  connection: local
  gather_facts: no
## rolesの記載
  roles:
##### AWS DX VIFの追加
    - add_dxvif
# スイッチ向けの設定
## インベントリのaristaグループに対して実行
- hosts: arista
  gather_facts: no
## rolesの記載。必要に応じて追加、削除する
  roles:
    - add_vlans
    - add_vlantag
    - add_svi
    - add_bgppeer

Rolesの作成

Rolesは、Playbookの配下にrolesディレクトリを作って、その中にPlaybookのrolesに記載したものと同じ名前のディレクトリを作成します。その下のディレクトリ構造はAnsibleのマニュアルにある「Role Directory Structure」を参考にしてください。
今回は、tasksとtemplatesだけを利用します。
ディレクトリ構造は以下になります。

ディレクトリ構造
$ tree
.
#### arista.ymlがPlaybook
|-- arista.yml
|-- host_vars
|   |-- localhost
|   |-- switch01
|   `-- switch02
#### hostsがインベントリ
|-- hosts
`-- roles
    |-- add_bgppeer
    |   |-- tasks
    |   |   `-- main.yml
    |   |-- templates
    |   |   `-- add_bgppeer.j2
    |-- add_dxvif
    |   |-- tasks
    |   |   `-- main.yml
    |-- add_svi
    |   |-- tasks
    |   |   `-- main.yml
    |   |-- templates
    |    |   `-- add_svi.j2
    |-- add_vlans
    |   |-- tasks
    |   |   `-- main.yml
    |   |-- templates
    |   |   `-- vlans.j2
    `-- add_vlantag
        |-- tasks
        |   `-- main.yml
        |-- templates
            `-- add_vlantag.j2

AWS VIF追加用のrolesの作成

タスクを定義したmain.ymlという名前のYAMLファイルをtasksの配下に設置します。ローカルでaws direct-connectコマンドを追加するVIF毎に実行します。繰り返し実行させるために、shellモジュールにJinja2テンプレートを埋め込んでいます。

main.yml
- name: Add VIF
  shell: "{% for i in dx %}aws directconnect allocate-private-virtual-interface --connection-id {{ i.connectionid }} --owner-account {{ i.account }} --new-private-virtual-interface-allocation virtualInterfaceName={{ i.name }},vlan={{ i.vlan }},asn=65534,authKey=password,amazonAddress={{ i.awsip }},customerAddress={{ i.localip }} --profile {{ i.profile }}\n{% endfor %}"

コマンドで設定する引数は以下になります。VIF毎に変更が必要な項目については後述の変数で値を設定します。

項目 内容
connection-id 前述の手順で確認したDXのID 変数で投入
owner-account VIFを払い出すAWSアカウント 変数で投入
virtualInterfacename VIFの名前 変数で投入
vlan VIFのVLAN ID 変数で投入
asn スイッチ側のAS番号 65534
authKey BGPのパスワード password
amazonAddress AWS側のIPアドレス 変数で投入
customerAddress スイッチ側のIPアドレス 変数で投入

※AWS側のAS番号は64512(デフォルト)、スイッチ側のAS番号は65534、BGPのパスワードはpasswordで固定しています。

スイッチ用のrolesの作成

スイッチに対しては、tasksにJinja2テンプレートを埋め込むのではなく、外部のファイルに記載します。その為、main.ymlへはJinja2テンプレートを呼び出すタスクだけを記載します。
全てのrolesのtasksディレクトリに以下のmain.ymlファイルを設置し、templatesディレクトリにJinja2テンプレートを使って投入するConfigを記載します。

VLAN追加用のrolesの作成

../templates/vlans.j2を呼び出してスイッチへConfigを投入します。
save_when: alwaysを記載しておくと毎回Configを保存します。

roles/add_vlans/tasks/main.yml
- name: Add VLANs
  eos_config:
    src: vlans.j2
    save_when: always

VLAN IDとVLANの名前を変数で定義します。

roles/add_vlans/templates/vlans.j2
{% for i in vlans %}
vlan {{ i.vlanid }}
   name {{ i.name }}
{% endfor %}

VLAN Tag追加用のrolesの作成

../templates/add_vlantag.j2を呼び出してスイッチへConfigを投入します。

roles/add_vlantag/tasks/main.yml
- name: Add vlantag
  eos_config:
    src: add_vlantag.j2
    save_when: always

Tagを追加するインタフェースとVLAN Tagを変数で定義します。

roles/add_vlantag/templates/add_vlantag.j2
{% for i in interfaces %}
interface {{ i.intname }}
   switchport trunk allowed vlan add {{ i.vlan }}
{% endfor %}

SVI追加用のrolesの作成

../templates/add_svi.j2を呼び出してスイッチへConfigを投入します。

roles/add_svi/tasks/main.yml
- name: Add SVI
  eos_config:
    src: add_svi.j2
    save_when: always

SVIのインタフェース名、Desctiption、IPアドレスを変数で定義します。

roles/add_svi/templates/add_svi.j2
{% for i in svi %}
interface {{ i.intname }}
  description {{ i.description }}
  ip address {{ i.address }}
{% endfor %}

BGP Peer追加用のrolesの作成

BGPの設定は共通化できる項目が多いので、peer-groupを利用して共通項目を設定します。
Arista EOSのBGPの詳細設定については以下をご確認ください。
https://www.arista.com/en/um-eos/eos-section-31-2-configuring-bgp

peer-groupの設定
## 自ASは65534
router bgp 65534
   ### ピアグループ awsを使う
   neighbor aws peer-group
   ### AWS側のASは64512で固定
   neighbor aws remote-as 64512
   ### BFDを使って間接リンクの障害を検知する
   neighbor aws fall-over bfd
   ### パスワードの設定
   neighbor aws password [パスワード]
   ### AWSにはデフォルトルートを広告する
   neighbor aws default-originate

../templates/add_bgppeer.j2を呼び出してスイッチへConfigを投入します。

roles/add_bgppeer/tasks/main.yml
- name: Add BGP Peer
  eos_config:
    src: add_bgppeer.j2
    save_when: always

peerのIPを変数で定義します。基本的な設定を読み込むためにpeer-groupを指定しています。

roles/add_bgppeer/templates/add_bgppeer.j2
{% for i in bgppeers %}
router bgp 65534
   neighbor {{ i.peer }} peer-group aws
{% endfor %}

変数の作成

AWSの設定はlocalhostへ、スイッチの設定はswitch01,02に異なる設定を投入する必要があります。
それらの変数をプレイブックと同じディレクトリにhost_varsディレクトリを作成し、その中にファイル名をホスト名にしたファイルへ変数をYAML形式で記載します。ディレクトリ構成は以下のようになります。

ディレクトリ構成
#### arista.ymlがプレイブック
|-- arista.yml
#### この下にホスト名をファイル名にした変数ファイルを設置
|-- host_vars
|   |-- localhost
|   |-- switch01
|   `-- switch02
`-- hosts

AWS向け変数ファイル

VIFの名前、VIFを払い出すアカウント、VLAN、IPアドレス、コネクションIDを記載します。

AWS向け変数
dx:
  - name: vif-a
    account: 123456789012
    vlan: 1111
    awsip: 1.1.1.1/30
    localip: 1.1.1.2/30
    connectionid: dxcon-xxxxxxxx
## リストにして複数のVIFを作成する事も可能
  - name: vif-b
    account: 123456789123
    vlan: 2221
    awsip: 2.2.2.1/30
    localip: 2.2.2.2/30
    connectionid: dxcon-yyyyyyyy

スイッチ向け変数ファイル

VLAN、インタフェース、SVI、BGP Peerの設定内容を記載します。
2台ある場合は2台分記載します。

スイッチ向け変数
### VLANの設定
vlans:
  - vlanid: 1111
    name: for-vif-a
  ## リストにして複数のVLANを一気に登録することも可能
  - vlanid: 2221
    name: for-vif-b

### インタフェースの設定
interfaces:
  - intname: Ethernet5
    description: for-dxcon-xxxxxxxx
    mode: trunk
    vlan: 1111
  ## リストにして複数のインタフェースを一気に登録することも可能
  - intname: Ethernet6
    description: for-dxcon-yyyyyyyy
    mode: trunk
    vlan: 2221

### SVIの設定
svi:
  - intname: Vlan1111
    description: vif-a
    address: 1.1.1.2/30
  ## リストにして複数のSVIを一気に登録することも可能
  - intname: Vlan2221
    description: vif-b
    address: 2.2.2.2/30

### BGP Peerの設定
bgppeers:
  - peer: 1.1.1.1
  ## リストにして複数のPeerを一気に登録することも可能
  - peer: 2.2.2.1

Playbookの実行

ansible-playbookコマンドを利用してPlaybookを実行します。

実行結果
## -iでインベントリファイルを指定
$ ansible-playbook -i hosts arista.yml

PLAY [local] *****************************************************************************************

TASK [add_dxvif : Add VIF] ************************************************
changed: [localhost]

PLAY [arista] **************************************************************************************

TASK [add_vlans : Add VLANs] *****************************************************************
changed: [switch01]
changed: [switch02]

TASK [add_vlantag : Add vlantag] *****************************************************************
changed: [switch01]
changed: [switch02]

TASK [add_svi : Add SVI] *****************************************************************
changed: [switch01]
changed: [switch02]

TASK [add_bgppeer : Add BGP Peer] *****************************************************************
changed: [switch01]
changed: [switch02]

PLAY RECAP *******************************************************************************************
localhost                  : ok=0    changed=1    unreachable=0    failed=0
switch01                : ok=0    changed=4    unreachable=0    failed=0
switch02                : ok=0    changed=4    unreachable=0    failed=0

運用方法

host_varsのファイルを都度更新してからansible-playbookコマンドを実行します。これまでは手順書、Configファイルをレビューしてから作業を行っていましたが、変数が正しい事が確認できればコマンドを1回実行するだけで変更することができるようになりました。

おわりに

今回はAWSのDXに関わる個所のみの変更でしたが、rolesで作業を細分化する事で、日々のネットワーク機器の運用に応用できるようになるかと思います。
又、今回Aristaの機器で自動化を行いましたが、CiscoやJuniperなどもAnsibleのモジュールが充実しているのでネットワークの自動化にどんどん取り組んでいこうと考えています!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away