はじめに
こんにちは。小川と申します。
主にネットワークインフラを担当しています。
私が担当している箇所では、AWSでDirect Connect(以下DXと言います)を利用しています。
小さい単位でAWSアカウントを分けたいので、接続の構成は以下のようになります。
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を確認します。
$ 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でパスワードなしの公開鍵認証をできるようにスイッチ側で以下の設定を行います。
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を記載するだけにして、作業に応じて必要なものを追加したりコメントアウトするだけの運用にしています。
# 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テンプレートを埋め込んでいます。
- 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を保存します。
- name: Add VLANs
eos_config:
src: vlans.j2
save_when: always
VLAN IDとVLANの名前を変数で定義します。
{% for i in vlans %}
vlan {{ i.vlanid }}
name {{ i.name }}
{% endfor %}
VLAN Tag追加用のrolesの作成
../templates/add_vlantag.j2を呼び出してスイッチへConfigを投入します。
- name: Add vlantag
eos_config:
src: add_vlantag.j2
save_when: always
Tagを追加するインタフェースとVLAN Tagを変数で定義します。
{% for i in interfaces %}
interface {{ i.intname }}
switchport trunk allowed vlan add {{ i.vlan }}
{% endfor %}
SVI追加用のrolesの作成
../templates/add_svi.j2を呼び出してスイッチへConfigを投入します。
- name: Add SVI
eos_config:
src: add_svi.j2
save_when: always
SVIのインタフェース名、Desctiption、IPアドレスを変数で定義します。
{% 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
## 自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を投入します。
- name: Add BGP Peer
eos_config:
src: add_bgppeer.j2
save_when: always
peerのIPを変数で定義します。基本的な設定を読み込むためにpeer-groupを指定しています。
{% 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を記載します。
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のモジュールが充実しているのでネットワークの自動化にどんどん取り組んでいこうと考えています!