はじめに
本ブログでは、これまでいくつかのAnsible と ACI 対応についての記事を書いてきましたが、 Terraform も ACI に対応していることを最近知りました。
少しだけ試してみましたのでまとめます。
環境
■ Terraform の ACI 対応
Terraform の公式ドキュメントに詳細が記載されています。カテゴリとしては、Network ではなく、Cloudという扱いのようです。
https://www.terraform.io/docs/providers/aci/index.htmlwww.terraform.io
Tenant、Bridge Domain、Subnet、Contract などのオブジェクトを扱う Data Resources や Resources がそれぞれ 40以上用意されています。
aci_any Data Source や、aci_any、aci_rest Resource もあるので、専用のものがなくても、融通が効きそうです。
■ おためし
今回は、以下のような Tenant、Bridge Domain、VRF、Subnet を作ります。
tf ファイルの作成
main.tf
と、bd.tf
の 2つのファイルを作成します。(雰囲気で分けています)
main.tf
の作成
provider
の定義をします。
provider "aci" { username = "admin" password = "xxxdummyxxx" url = "https://sandboxapicdc.cisco.com/" insecure = true }
以前の記事でも書いたとおり、APIC の認証方式には、パスワードベース認証方式と、署名ベース認証方式の2種類あります。今回は手軽さ重視でパスワードベース認証方式を利用するように、privider
を定義しています。
HTTPS 接続時の証明書の検証を無効化する場合は、insecure
を true
に指定します。デフォルトも true
です。
その他、パラメータの説明はCisco ACI Provider の公式ドキュメントを参照してください。
bd.tf
の作成
Tenant、Bridge Domain、VRF、Subnet の Resource を定義をします。
resource "aci_tenant" "test_tenant1" { name = "test_tenant1" description = "test tenant" } resource "aci_vrf" "test_vrf1" { tenant_dn = aci_tenant.test_tenant1.id name = "test_vrf1" description = "test vrf" } resource "aci_bridge_domain" "test_bd1" { tenant_dn = aci_tenant.test_tenant1.id name = "test_bd1" description = "test bridge domain" relation_fv_rs_ctx = aci_vrf.test_vrf1.name } resource "aci_subnet" "test_subnet1" { bridge_domain_dn = aci_bridge_domain.test_bd1.id description = "test_subnet1" ip = "10.0.3.28/27" }
補足
少し調べるのにつまずいたのが、Bridge Domain から VRF への関連付けの方法です。以下の部分です。
relation_fv_rs_ctx = aci_vrf.test_vrf1.name
aci_bridge_domain を参照しても vrf という文字が見当たらないので、対応するパラメーターは無いのかもと思っていました。
ですが、ACI 用語としての VRF は Context とも呼ばれていて、クラス名?が fvCtx
であることを思いだして、relation_fv_rs_ctx
で設定を試したした。
なお、よく見かけるサンプルでは、id などの参照を "${
と }"
で囲って
tenant_dn = "${aci_tenant.test_tenant1.id}"
のようにしていますが、
Warning: Interpolation-only expressions are deprecated
という警告が表示されるので修正しました。
- 参考
初回実行
terraform init
コマンドの実行
terraform init
コマンドで初期化します。
実行ログ(クリックして広げる)
$ terraform init Initializing the backend... Initializing provider plugins... - Checking for available provider plugins... - Downloading plugin for provider "aci" (terraform-providers/aci) 0.1.4... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aci: version = "~> 0.1" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. $
terraform plan
コマンドの実行
terraform plan
コマンドで確認します。
実行ログ(クリックして広げる)
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aci_bridge_domain.test_bd1 will be created + resource "aci_bridge_domain" "test_bd1" { + annotation = (known after apply) + arp_flood = (known after apply) + bridge_domain_type = (known after apply) + description = "test bridge domain" + ep_clear = (known after apply) + ep_move_detect_mode = (known after apply) + host_based_routing = (known after apply) + id = (known after apply) + intersite_bum_traffic_allow = (known after apply) + intersite_l2_stretch = (known after apply) + ip_learning = (known after apply) + ipv6_mcast_allow = (known after apply) + limit_ip_learn_to_subnets = (known after apply) + ll_addr = (known after apply) + mac = (known after apply) + mcast_allow = (known after apply) + multi_dst_pkt_act = (known after apply) + name = "test_bd1" + name_alias = (known after apply) + optimize_wan_bandwidth = (known after apply) + relation_fv_rs_ctx = "test_vrf1" + tenant_dn = (known after apply) + unicast_route = (known after apply) + unk_mac_ucast_act = (known after apply) + unk_mcast_act = (known after apply) + v6unk_mcast_act = (known after apply) + vmac = (known after apply) } # aci_subnet.test_subnet1 will be created + resource "aci_subnet" "test_subnet1" { + annotation = (known after apply) + bridge_domain_dn = (known after apply) + ctrl = (known after apply) + description = "test_subnet1" + id = (known after apply) + ip = "10.0.3.28/27" + name_alias = (known after apply) + preferred = (known after apply) + scope = (known after apply) + virtual = (known after apply) } # aci_tenant.test_tenant1 will be created + resource "aci_tenant" "test_tenant1" { + annotation = (known after apply) + description = "test tenant" + id = (known after apply) + name = "test_tenant1" + name_alias = (known after apply) } # aci_vrf.test_vrf1 will be created + resource "aci_vrf" "test_vrf1" { + annotation = (known after apply) + bd_enforced_enable = (known after apply) + description = "test vrf" + id = (known after apply) + ip_data_plane_learning = (known after apply) + knw_mcast_act = (known after apply) + name = "test_vrf1" + name_alias = (known after apply) + pc_enf_dir = (known after apply) + pc_enf_pref = (known after apply) + tenant_dn = (known after apply) } Plan: 4 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. $
もろもろオブジェクトが作成されることが確認できます。plan
なのでまだ実際に作成はされれいません。
terraform apply
コマンドの実行
いよいよ terraform apply
コマンドで、変更を適用します。
$ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aci_bridge_domain.test_bd1 will be created + resource "aci_bridge_domain" "test_bd1" { + annotation = (known after apply) + arp_flood = (known after apply) + bridge_domain_type = (known after apply) + description = "test bridge domain" + ep_clear = (known after apply) + ep_move_detect_mode = (known after apply) + host_based_routing = (known after apply) + id = (known after apply) + intersite_bum_traffic_allow = (known after apply) + intersite_l2_stretch = (known after apply) + ip_learning = (known after apply) + ipv6_mcast_allow = (known after apply) + limit_ip_learn_to_subnets = (known after apply) + ll_addr = (known after apply) + mac = (known after apply) + mcast_allow = (known after apply) + multi_dst_pkt_act = (known after apply) + name = "test_bd1" + name_alias = (known after apply) + optimize_wan_bandwidth = (known after apply) + relation_fv_rs_ctx = "test_vrf1" + tenant_dn = (known after apply) + unicast_route = (known after apply) + unk_mac_ucast_act = (known after apply) + unk_mcast_act = (known after apply) + v6unk_mcast_act = (known after apply) + vmac = (known after apply) } # aci_subnet.test_subnet1 will be created + resource "aci_subnet" "test_subnet1" { + annotation = (known after apply) + bridge_domain_dn = (known after apply) + ctrl = (known after apply) + description = "test_subnet1" + id = (known after apply) + ip = "10.0.3.28/27" + name_alias = (known after apply) + preferred = (known after apply) + scope = (known after apply) + virtual = (known after apply) } # aci_tenant.test_tenant1 will be created + resource "aci_tenant" "test_tenant1" { + annotation = (known after apply) + description = "test tenant" + id = (known after apply) + name = "test_tenant1" + name_alias = (known after apply) } # aci_vrf.test_vrf1 will be created + resource "aci_vrf" "test_vrf1" { + annotation = (known after apply) + bd_enforced_enable = (known after apply) + description = "test vrf" + id = (known after apply) + ip_data_plane_learning = (known after apply) + knw_mcast_act = (known after apply) + name = "test_vrf1" + name_alias = (known after apply) + pc_enf_dir = (known after apply) + pc_enf_pref = (known after apply) + tenant_dn = (known after apply) } Plan: 4 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aci_tenant.test_tenant1: Creating... aci_tenant.test_tenant1: Creation complete after 3s [id=uni/tn-test_tenant1] aci_vrf.test_vrf1: Creating... aci_vrf.test_vrf1: Still creating... [10s elapsed] aci_vrf.test_vrf1: Creation complete after 18s [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_bridge_domain.test_bd1: Creating... aci_bridge_domain.test_bd1: Creation complete after 5s [id=uni/tn-test_tenant1/BD-test_bd1] aci_subnet.test_subnet1: Creating... aci_subnet.test_subnet1: Creation complete after 2s [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. $
無事に Tenant、Bridge Domain、VRF、Subnet が作成されました。
APIC 画面でも確認します。
■ 宣言的であることの確認
宣言的と手続き的
ここまでで、一通りのオブジェクトが作成されました。
Terraform の特徴を掴むために、tf
ファイルを少し修正して動作を確認します。
具体的に確認したい点は、Terraform は宣言的であるという点です。tf
ファイルでは、あるべき状態を宣言的に定義します。そのため、定義を削除して apply
すると、差分を検出して削除処理が実行されます。
一方、Ansible の Playook は全体としては手続き型です。そのため、タスク定義を削除した場合、そのタスクは実行されないというだけで、実態は残ったままになります。
確認1: Subnet 定義の削除
db.tf
の修正
まず、db.tf
から、Subnet の定義を削除します。
resource "aci_tenant" "test_tenant1" { name = "test_tenant1" description = "test tenant" } resource "aci_vrf" "test_vrf1" { tenant_dn = aci_tenant.test_tenant1.id name = "test_vrf1" description = "test vrf" } resource "aci_bridge_domain" "test_bd1" { tenant_dn = aci_tenant.test_tenant1.id name = "test_bd1" description = "test bridge domain" relation_fv_rs_ctx = aci_vrf.test_vrf1.name } # Subnet の定義は削除
実行
続いて、terraform plan
、terraform apply
します。
terraform plan 実行ログ(クリックして広げる)
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. aci_subnet.test_subnet1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]] aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1] aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1] ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aci_subnet.test_subnet1 will be destroyed - resource "aci_subnet" "test_subnet1" { - bridge_domain_dn = "uni/tn-test_tenant1/BD-test_bd1" -> null - ctrl = "nd" -> null - description = "test_subnet1" -> null - id = "uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]" -> null - ip = "10.0.3.28/27" -> null - preferred = "no" -> null - scope = "private" -> null - virtual = "no" -> null } Plan: 0 to add, 0 to change, 1 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.
terraform apply 実行ログ(クリックして広げる)
$ terraform apply aci_subnet.test_subnet1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]] aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1] aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1] An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aci_subnet.test_subnet1 will be destroyed - resource "aci_subnet" "test_subnet1" { - bridge_domain_dn = "uni/tn-test_tenant1/BD-test_bd1" -> null - ctrl = "nd" -> null - description = "test_subnet1" -> null - id = "uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]" -> null - ip = "10.0.3.28/27" -> null - preferred = "no" -> null - scope = "private" -> null - virtual = "no" -> null } Plan: 0 to add, 0 to change, 1 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aci_subnet.test_subnet1: Destroying... [id=uni/tn-test_tenant1/BD-test_bd1/subnet-[10.0.3.28/27]] aci_subnet.test_subnet1: Destruction complete after 1s Apply complete! Resources: 0 added, 0 changed, 1 destroyed. $
確認
APIC の画面で確認します。
Subnet の定義が削除され、宣言的な動作を確認できました。
Ansible の Playbook の場合は、Subnet 定義のタスクを削除しても、Subnet 定義は残ったままになるはずです。
確認2: Tenant 定義ごとの削除
db.tf
の修正
今度は、bd.tf
を空にして、Tenant の定義ごと削除します。
実行
続いて、terraform plan
、terraform apply
コマンドを実行します。
terraform plan 実行ログ(クリックして広げる)
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1] aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1] ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aci_bridge_domain.test_bd1 will be destroyed - resource "aci_bridge_domain" "test_bd1" { - arp_flood = "no" -> null - bridge_domain_type = "regular" -> null - description = "test bridge domain" -> null - ep_clear = "no" -> null - host_based_routing = "no" -> null - id = "uni/tn-test_tenant1/BD-test_bd1" -> null - intersite_bum_traffic_allow = "no" -> null - intersite_l2_stretch = "no" -> null - ip_learning = "yes" -> null - limit_ip_learn_to_subnets = "yes" -> null - ll_addr = "::" -> null - mac = "00:22:BD:F8:19:FF" -> null - mcast_allow = "no" -> null - multi_dst_pkt_act = "bd-flood" -> null - name = "test_bd1" -> null - optimize_wan_bandwidth = "no" -> null - relation_fv_rs_ctx = "test_vrf1" -> null - tenant_dn = "uni/tn-test_tenant1" -> null - unicast_route = "yes" -> null - unk_mac_ucast_act = "proxy" -> null - unk_mcast_act = "flood" -> null - v6unk_mcast_act = "flood" -> null - vmac = "not-applicable" -> null } # aci_tenant.test_tenant1 will be destroyed - resource "aci_tenant" "test_tenant1" { - description = "test tenant" -> null - id = "uni/tn-test_tenant1" -> null - name = "test_tenant1" -> null } # aci_vrf.test_vrf1 will be destroyed - resource "aci_vrf" "test_vrf1" { - bd_enforced_enable = "no" -> null - description = "test vrf" -> null - id = "uni/tn-test_tenant1/ctx-test_vrf1" -> null - ip_data_plane_learning = "enabled" -> null - knw_mcast_act = "permit" -> null - name = "test_vrf1" -> null - pc_enf_dir = "ingress" -> null - pc_enf_pref = "enforced" -> null - tenant_dn = "uni/tn-test_tenant1" -> null } Plan: 0 to add, 0 to change, 3 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.
terraform apply 実行ログ(クリックして広げる)
$ terraform apply aci_tenant.test_tenant1: Refreshing state... [id=uni/tn-test_tenant1] aci_vrf.test_vrf1: Refreshing state... [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_bridge_domain.test_bd1: Refreshing state... [id=uni/tn-test_tenant1/BD-test_bd1] An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # aci_bridge_domain.test_bd1 will be destroyed - resource "aci_bridge_domain" "test_bd1" { - arp_flood = "no" -> null - bridge_domain_type = "regular" -> null - description = "test bridge domain" -> null - ep_clear = "no" -> null - host_based_routing = "no" -> null - id = "uni/tn-test_tenant1/BD-test_bd1" -> null - intersite_bum_traffic_allow = "no" -> null - intersite_l2_stretch = "no" -> null - ip_learning = "yes" -> null - limit_ip_learn_to_subnets = "yes" -> null - ll_addr = "::" -> null - mac = "00:22:BD:F8:19:FF" -> null - mcast_allow = "no" -> null - multi_dst_pkt_act = "bd-flood" -> null - name = "test_bd1" -> null - optimize_wan_bandwidth = "no" -> null - relation_fv_rs_ctx = "test_vrf1" -> null - tenant_dn = "uni/tn-test_tenant1" -> null - unicast_route = "yes" -> null - unk_mac_ucast_act = "proxy" -> null - unk_mcast_act = "flood" -> null - v6unk_mcast_act = "flood" -> null - vmac = "not-applicable" -> null } # aci_tenant.test_tenant1 will be destroyed - resource "aci_tenant" "test_tenant1" { - description = "test tenant" -> null - id = "uni/tn-test_tenant1" -> null - name = "test_tenant1" -> null } # aci_vrf.test_vrf1 will be destroyed - resource "aci_vrf" "test_vrf1" { - bd_enforced_enable = "no" -> null - description = "test vrf" -> null - id = "uni/tn-test_tenant1/ctx-test_vrf1" -> null - ip_data_plane_learning = "enabled" -> null - knw_mcast_act = "permit" -> null - name = "test_vrf1" -> null - pc_enf_dir = "ingress" -> null - pc_enf_pref = "enforced" -> null - tenant_dn = "uni/tn-test_tenant1" -> null } Plan: 0 to add, 0 to change, 3 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aci_bridge_domain.test_bd1: Destroying... [id=uni/tn-test_tenant1/BD-test_bd1] aci_bridge_domain.test_bd1: Destruction complete after 2s aci_vrf.test_vrf1: Destroying... [id=uni/tn-test_tenant1/ctx-test_vrf1] aci_vrf.test_vrf1: Destruction complete after 0s aci_tenant.test_tenant1: Destroying... [id=uni/tn-test_tenant1] aci_tenant.test_tenant1: Destruction complete after 0s Apply complete! Resources: 0 added, 0 changed, 3 destroyed.
確認
APIC の画面で確認します。
test_tenant1 ごと削除され、宣言的な動作を確認できました。
さすがに、他の Tenant は今回の tf
ファイルの管理外のため残ったままです。
おわりに
Terraform の Cisco ACI Provider を使って、簡単な設定追加、削除をためしてみました。
Ansible と比較すると、やはり宣言的である点が特徴に感じました。
Terraform は Windows からでも実行できるため、手元の業務 windows 端末から実行できる点も特徴ではないかと思います。
参考
- Cisco ACI Provider
tf
ファイルサンプル- Cisco ブログ - Cisco Releases Terraform Support for ACI
- Ansible から Terraform を呼ぶ
terraform
モジュール
[2021/01/23 追記] - Introduction to Terraform with Cisco ACI, Part 1 - Cisco Blogs
余談
この記事を書くときに、はてなブログの Markdown のシンタックスハイライトが tf
に対応していることを知りました。