パラメタシートを入力するだけで Cisco ACIを設定・管理!? 〜Terraform (IaC) によるACI 自動化をシンプルに〜


🎅この記事は Cisco Systems 合同会社 社員有志 による 2022年Advent Calendar 2 の15日目分として投稿しています🎄
🎁2022年版その1  https://qiita.com/advent-calendar/2022/cisco 
🍗2022年版その2  https://qiita.com/advent-calendar/2022/cisco2

# はじめに
本稿では、**Cisco ACI** において **Terraform (Infrastructure as Code)** を利用する上で、よりシンプルに使う方法を試行錯誤した内容を紹介させていただきます。

Terraform を使うことで、**「設定自動化」**・**「構成管理ができる」** というメリットがある一方で、利用しているうちに
- 設定項目が増えてくると**パラメタの管理・コードへの入力が煩雑**に(variables/変数として定義するにも限界が。。。）
- コードが長くなり、**構成を把握するのが困難**

という課題が新しく出てきたと感じました。

そこで、**「パラメタシートの内容をインポートして、そっくりそのまま設定できる」** ことができれば
- パラメタシートの内容をterraform のコードとして逐一変換する必要がない
- 構成はパラメタシートを見ればすぐに把握できる（そして表形式になっていて分かりやすい！）
-
と、設定自動化および構成管理の両面にメリットがあると考え、実装を作成してみました。

**設定の定義および構成管理が1つのパラメタシート上で完結** するため、日頃の運用をさらに効率化できるのではないかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/45fa1655-1dc8-630c-ac01-56c879ffff78.png)


今回利用したコードの説明の前に、一般的な Infrastructure as Code の説明や動作概要について記載しています。

## Infrastructure as Code とは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/f6fcded3-3155-d8ff-fb09-64c86d523c18.png)
**Infrastructure as Code (IaC)** とはインフラの自動化手法の1つで、従来のGUI・CLI ベースの手作業による設定に代わり、設定内容を**コード**として記述し設定を行います。
コードでインフラ構成のテンプレートのようなものを定義し、そのコードを実行するだけで設定を行うことができるため、一般的には
- 複雑な設定であっても**迅速・正確**に設定できる
- コードを機械的に複製でき、**拡張性**に優れている
- 設定内容がテキストとして記録されており、**構成管理**に活用できる

といったメリットがあると言われています。

## Cisco ACI で Infrastructure as Code を利用するメリット

**Cisco ACI** は データセンター向けのSDNソリューションで、コントローラ(APIC)から柔軟に仮想ネットワークを設定できることが特徴ですが、上記メリットをACIに当てはめると下記のようなものが挙げられるかと思います。
- **ACI特有のネットワーク設定・ポリシー設定**をテンプレート化して、**誰でも簡単に設定できる**ようになる
- **大規模な設定**が発生する場合（既存ネットワークの移行や新規サービス・ユーザー用のネットワークを展開する際など）でも**テンプレートを再利用して迅速に設定**できる
- ACIの設定内容の**構成管理**が可能

## IaC ツールの中でも Terraform を採用した理由

本稿で **Terraform** を利用する理由としては、Terraform は **「宣言型」** のコードとしてシンプルに記述できるからです。

Infrastructure as Code では **「命令型」** ・ **「宣言型」** と、コードの記述方法に2つのパターンがあります。
   -   **命令型**：実行する設定変更内容・手順を具体的に記述する必要あり
　→コードを書くときに、**既存の設定を踏まえた上で具体的な変更内容を記述** しなければならない
   -   **宣言型**：最終的に必要な構成だけ記述し、必要となる設定変更内容・手順はツール側が調整
　→ コードの記述が **シンプル。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/f6eac807-0783-533d-ef2e-d9737d01eb8e.png)

上の図で命令型・宣言型のコードのイメージの違いを記載しています。
インフラ設定を追加・削除する際に、
**命令型** の場合は、ユーザー側で具体的な設定変更の内容(追加・削除)を明示的に定義する必要がありますが、
**宣言型** の場合は、必要な構成を記載するのみで、実際に行う設定手順まで記載する必要はありません。
また、コードの内容が環境の構成と一致しているため、**コードを見ることでインフラの構成を把握する** ことができます。

## Terraform による ACI 設定の動作概要

※Terraform の詳細な説明は今回割愛しますが、[今年の Cisco Advent Calendar 1日目の記事]( https://qiita.com/kenbutsu/items/a9e6e01e47417a50411c)で分かりやすい解説があるので、こちらをご参考にしていただければと思います!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/4495bd36-1f09-01d2-e92d-3260183b34f4.png)

**ポイント**
1\. Terraform のコードでACIの設定したい構成を記述
→ HCLという Terraform 独自の形式でコードを記述します。「宣言型」で、最終的に必要な構成を記載します。
2\. State file で Terraform が既存のACI設定を把握しており、コードで記述されている設定と既存設定を比較して、必要な設定変更を自動計算
3\. Terraformのコードを実行するにあたり、ProviderというプラグインがHCLで記載されたコードをACIのAPIに変換します。

# 今回紹介する実装の内容
ACI における L3 ネットワークの展開を例に作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/e5b0cbe4-9b26-2e41-ed8d-5b96f2da8041.png)

**ポイント**
1\. ACIのネットワーク設定を Terraform のコードで**テンプレート化**
→ ACI で L3 のネットワークを展開するためには幾つかのオブジェクトの作成が必要となるため、ここでは設定の枠組みをテンプレートとして設定します。
今回、`Tenant`, `VRF`, `Bridge Domain`, `Subnet`, `Application Profile`, `EPG` というオブジェクトを作成します。
2\. 実際に設定する内容、つまりテンプレート設定内容のパラメタは　**パラメタシート(csv形式)** で定義
→ 必要な L3 ネットワークの分だけパラメタシートを埋めて利用します。
3\. 1・2 のインプットをもとに、ACIに設定を適用

**メリット**
- ACIで繰り返し設定する内容を自動化でき、**正確・迅速に設定**ができる
- パラメータシートに記載されている内容が直接インフラに設定されているので、**パラメタシートと実際のインフラ設定が同期しており、シンプルに構成が管理** できる
- 設定されている構成とそのパラメタを **見やすいシート形** 式で一覧確認できる

## 今回利用した Terraform のコード

```main.tf
# Required Provider ：このTerraform のコードはACI設定用に変換するように定義
terraform {
    required_providers {
      aci = {
        source = "CiscoDevNet/aci"
        version = "2.2.1"
      }
    }
  }
 
# Provider の定義：このTerraform のコードで設定する対象(APIC)のアクセス情報を定義
# アクセス情報は外部ファイルにて定義
provider "aci" {
  # APIC のユーザーネームを変数で定義
    username = var.apic_username
  # APIC のユーザーネームを変数で定義
    password = var.apic_password
  # APIC のURLを変数で定義
    url      = var.apic_url
　# 証明書利用設定
    insecure = true
  }

# csvファイルで定義したパラメタシートのインポート
locals {
    nw = csvdecode(file("networks.csv")) 
}

###　以下、ACI における L3 ネットワークの設定を resource ブロックで定義 ###
 # Define an ACI Tenant Resource.
resource "aci_tenant" "terraform_tenant" {
    name = "ACI-Tenant"
}

# Denine an ACI VRF Resource.
resource "aci_vrf" "VRF1" {
  tenant_dn = aci_tenant.terraform_tenant.id
  name      = "VRF1"
}

# Define an ACI Bridge Domain Resource.
resource "aci_bridge_domain" "BD" {
  for_each = { for nw in local.nw : nw.NW_id => nw }
  tenant_dn          = aci_tenant.terraform_tenant.id
  relation_fv_rs_ctx = aci_vrf.VRF1.id
  name               = each.value.bd
}

# Define an ACI Bridge Domain subnet Resource.
resource "aci_subnet" "bd_subnet" {
  for_each = { for nw in local.nw : nw.NW_id => nw }
  parent_dn = aci_bridge_domain.BD[each.key].id
  ip               = each.value.subnet
}

# Define an ACI Application Profile Resource.
resource "aci_application_profile" "terraform_ap" {
    tenant_dn  = aci_tenant.terraform_tenant.id
    name       = "AP1"
    description = "App Profile Created Using Terraform"
}

# Define an ACI Application EPG Resource.
resource "aci_application_epg" "terraform_epg" {
    for_each = { for nw in local.nw : nw.NW_id => nw }
    application_profile_dn  = aci_application_profile.terraform_ap.id
    name                    = each.value.epg
    relation_fv_rs_bd       = aci_bridge_domain.BD[each.key].id
    description             = "EPG Created Using Terraform"
}
```

## インポートするパラメタシート (csv ファイル) のイメージ

パラメタシートの一行につき一つの L3 ネットワークが作成されます。
**パラメタシート上で構成の追加・削除を行うと、同期する形で設定変更が行われます。**
| VRF | NW_id| BD | subnet | epg |
|:-:|:-:|:-:|:-:|:-:|
|VRF1| 1 | BD1 | 192.168.1.254/24  | EPG1  |
|VRF2| 2 | BD2 | 192.168.2.254/24  | EPG2  |
|VRF3| 3 | BD3 | 192.168.3.254/24  | EPG3  |
|VRF4| 4 | BD4 | 192.168.4.254/24  | EPG4  |
|VRF5| 5 | BD5 | 192.168.5.254/24  | EPG5  |
|...| ... | ... | ...  | ...  |
|VRF10| 10 | BD10 | 192.168.10.254/24  | EPG10  |
				


## パラメタシート(csv ファイル) をインポートして利用するための設定ポイント
Terraform の 関数を利用して、**csvファイルのパラメタをインポートし、Terraform で作成したテンプレート上の変数をマッピング** します。
コード内でインポートとマッピングの設定を行います。

何段階かの設定で構成されているので順番に記載します。

``` terraform:main.tf より抜粋
locals {
    nw = csvdecode(file("networks.csv")) 
}
```
ここでは、
 `file` により、ディレクトリからファイルのインポートを行い
 `csvdecode` により、csv ファイルを list に変換し
 `locals` により、 `nw` という名のローカル変数を定義します。

続いて、インポートした csv ファイルのパラメタと Terraform のテンプレートのマッピングですが

``` terraform:main.tf より抜粋
resource "aci_bridge_domain" "BD" {
  for_each = { for nw in local.nw : nw.NW_id => nw }
  tenant_dn          = aci_tenant.terraform_tenant.id
  relation_fv_rs_ctx = aci_vrf.VRF1.id
  name               = each.value.bd
}
```

`for_each = { for nw in local.nw : nw.NW_id => nw }　` の行で、

`for` により、まず先ほどcsvファイルをインポートしたリスト形式のローカル変数 nw を key-value形式のマップに変換します。
csv ファイルのパラメタのうち、`NW_id` が key となり、その他パラメタが value として扱われます。
`for_each` により、オブジェクトをループして複製し、この時マップ形式に変換された nw のパラメタを利用してループ処理を行います。
例えば`each.value.bd` の箇所では、マップの valueのうち`bd`のパラメタをマッピングします。

このコードの箇所では、パラメタシートに記載された内容の、BD(Bridge Domain) のオブジェクトが作成されます。

コードの他の箇所も見ると、BD 以外にも、subnet, EPG のオブジェクトがパラメタシートからマッピングされて作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691389/4cf966bb-2034-c3b6-b225-9644bdb485d3.png)

まとめると、
`file` で csv ファイルのパスを指定して読み込み、
`csvdecode` でcsvファイルを「リスト」に変換し
`for` で「リスト」を「マップ」に変換し
`for each` でマップをもとにループでオブジェクトを作成し、`each.value.XX` で指定されたパラメタをマッピングする、
という流れになります。


# Demo Video

実際にこちらのコードを使ってACI の設定を行う様子をキャプチャした動画です。パラメタシートの変更に同期する形で ACI 環境の設定内容も変更されるイメージが伝われば幸いです。
<iframe width="560" height="315" src="https://www.youtube.com/embed/a8lm47UhZP0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

※追記：動画が非公開になってしまっていましたが、権限を修正いたしました。大変失礼いたしました。
ご指摘ありがとうございました。

# おわりに

Terraform のコードに作り込みを加えることで、パラメタシートと同期させながらインフラの設定変更を行うことができるようになりました。
日々の運用はパラメタシートのメンテナンスで完結し、よりシンプルに自動化を利用できるのではないかと思います。

一方で、パラメタをマッピングする設定は少し複雑ではあるので、他により簡単な方法がないかを引き続き試したいと考えています。
また、Terraformではない他のツール(Postmanなど)では、csvファイルのパラメタとテンプレート上の変数をGUI上で簡単にマッピングできる機能があり、Terraform でも同様の実装ができるとかなり使い勝手が上がるのではないかとこっそり期待しています。

# 免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。