らくがきちょう

なんとなく ~所属組織/団体とは無関係であり、個人の見解です~

Terraform で ACI 上に DHCP Relay 設定を含む Tenant を作成する

Terraform を使って Cisco ACI に「DHCP Relay」を設定するサンプルをメモしておきます。 DHCP サーバ自体は L3out の外部にあるものとします。 また、L3out は OSPF 接続としています。

登録中の Issue

現時点では DHCP Relay Policy などの Resouce が無い為、REST API を使って設定しています。 しかし、GitHub に以下の Issue が登録されている為、いずれは REST API を使わずに設定出来るようになるかもしれません。

Terraform の設定ファイル

Terraform の設定ファイルは以下の通りです。 設定量が多いので機能ごとにファイルを分割しています。

  1. main.tf
  2. tenant.tf
  3. vrf.tf
  4. contract.tf
  5. L3out.tf
  6. bd.tf
  7. epg.tf

1. main.tf

terraform {
  required_providers {
    aci = {
      source  = "CiscoDevNet/aci"
      version = "0.5.4"
    }
  }
}

provider "aci" {
  username = "admin"
  password = "password"
  url      = "https://10.0.0.1"
  insecure = true
}

2. tenant.tf

# Tenant
resource "aci_tenant" "tenant" {
  name = "Tenant1"
}

3. vrf.tf

# VRF
resource "aci_vrf" "vrf" {
  tenant_dn = aci_tenant.tenant.id
  name      = "Vrf1"
}

4. contract.tf

# Contract1
resource "aci_filter" "filter1" {
  tenant_dn = aci_tenant.tenant.id
  name      = "Filter1"
}

resource "aci_filter_entry" "entry1-10" {
  name      = "0010"
  filter_dn = aci_filter.filter1.id
  ether_t   = "unspecified"
}

resource "aci_contract" "contract1" {
  tenant_dn = aci_tenant.tenant.id
  name      = "Contract1"
}

resource "aci_contract_subject" "subject1" {
  contract_dn = aci_contract.contract1.id
  name        = "Subject1"
  relation_vz_rs_subj_filt_att = [
    aci_filter.filter1.id,
  ]
}

5. L3out.tf

l3out.tf
# Domain
data "aci_l3_domain_profile" "l3dom" {
  name = "ExtRoutedDom"
}

# L3Out
resource "aci_l3_outside" "l3out1" {
  tenant_dn                    = aci_tenant.tenant.id
  name                         = "L3out1"
  relation_l3ext_rs_ectx       = aci_vrf.vrf.id
  relation_l3ext_rs_l3_dom_att = data.aci_l3_domain_profile.l3dom.id
}

resource "aci_rest" "l3out1_ospf" {
  path       = "/api/mo/uni/tn-Tenant1/out-L3out1.json"
  class_name = "ospfExtP"
  content = {
    "areaType" = "regular"
    "areaCost" = "1"
    "areaId"   = "backbone"
    "areaCtrl" = "redistribute,summary"
  }
  depends_on = [aci_l3_outside.l3out1]
}

resource "aci_logical_node_profile" "l3out1_lnprof1" {
  l3_outside_dn = aci_l3_outside.l3out1.id
  name          = "NodeProf"
}

resource "aci_logical_node_to_fabric_node" "l3out1_lnode1" {
  logical_node_profile_dn = aci_logical_node_profile.l3out1_lnprof1.id
  tdn                     = "topology/pod-1/node-201"
  rtr_id                  = "10.0.99.201"
  rtr_id_loop_back        = "no"
}

resource "aci_logical_interface_profile" "l3out1_lifprof1" {
  logical_node_profile_dn           = aci_logical_node_profile.l3out1_lnprof1.id
  name                              = "IntProf"
  relation_l3ext_rs_path_l3_out_att = toset(["topology/pod-1/paths-201/pathep-[eth1/1]"])
}

resource "aci_rest" "l3out1_att" {
  path       = "/api/mo/uni/tn-Tenant1/out-L3out1/lnodep-NodeProf/lifp-IntProf.json"
  class_name = "l3extRsPathL3OutAtt"
  content = {
    "addr"      = "10.0.100.254/24"
    "autostate" = "enabled"
    "encap"     = "vlan-100"
    "ifInstT"   = "ext-svi"
    "mtu"       = "1500"
    "tDn"       = "topology/pod-1/paths-201/pathep-[eth1/1]"
  }
  depends_on = [aci_logical_interface_profile.l3out1_lifprof1]
}

resource "aci_rest" "l3out1_att_ospf" {
  path       = "/api/mo/uni/tn-Tenant1/out-L3out1/lnodep-NodeProf/lifp-IntProf.json"
  class_name = "ospfIfP"
  content = {
    "authKeyId" = "1"
    "authType"  = "none"
  }
  depends_on = [aci_rest.l3out1_att]
}

# L3Out1 External EPG
resource "aci_external_network_instance_profile" "l3out1_epg1" {
  l3_outside_dn       = aci_l3_outside.l3out1.id
  name                = "ExtEpg1"
  relation_fv_rs_cons = [aci_contract.contract1.id]
  relation_fv_rs_prov = [aci_contract.contract1.id]
}

resource "aci_l3_ext_subnet" "l3out1_subnet1" {
  external_network_instance_profile_dn = aci_external_network_instance_profile.l3out1_epg1.id
  ip                                   = "0.0.0.0/0"
  scope                                = ["import-security"]
}

6. bd.tf

# DHCP Relay
resource "aci_rest" "dhcp_relay" {
  path       = "/api/mo/uni/tn-Tenant1/relayp-DhcpRelay.json"
  class_name = "dhcpRelayP"
  content = {
    "name"  = "DhcpRelay"
    "owner" = "tenant"
  }
  depends_on = [aci_l3_outside.l3out1]
}

resource "aci_rest" "dhcp_provider" {
  path       = "/api/mo/uni/tn-Tenant1/relayp-DhcpRelay/rsprov-[uni/tn-Tenant1/out-L3out1/instP-ExtEpg1].json"
  class_name = "dhcpRsProv"
  content = {
    "addr" = "10.199.199.199"
    "tDn"  = "uni/tn-Tenant1/out-L3out1/instP-ExtEpg1"
  }
  depends_on = [aci_rest.dhcp_relay]
}

# BD1
resource "aci_bridge_domain" "bd1" {
  tenant_dn                = aci_tenant.tenant.id
  name                     = "Bd1"
  relation_fv_rs_ctx       = aci_vrf.vrf.id
  relation_fv_rs_bd_to_out = [aci_l3_outside.l3out1.id]
  depends_on               = [aci_rest.dhcp_relay]
}

resource "aci_subnet" "bd1_subnet" {
  parent_dn = aci_bridge_domain.bd1.id
  ip        = "10.0.101.254/24"
  scope     = ["public"]
}

resource "aci_rest" "bd1_dhcp_relay" {
  path       = "/api/mo/uni/tn-Tenant1/BD-Bd1/dhcplbl-DhcpRelay.json"
  class_name = "dhcpLbl"
  content = {
    "name"  = "DhcpRelay"
    "owner" = "tenant"
  }
  depends_on = [aci_bridge_domain.bd1]
}

7. epg.tf

# Application Profile
resource "aci_application_profile" "ap1" {
  tenant_dn = aci_tenant.tenant.id
  name      = "Ap1"
}

# Domain
data "aci_physical_domain" "physdom" {
  name = "PhysDom"
}

# EPG1

resource "aci_application_epg" "epg1" {
  application_profile_dn = aci_application_profile.ap1.id
  name                   = "Epg1"
  relation_fv_rs_bd      = aci_bridge_domain.bd1.id
}

resource "aci_epg_to_domain" "epg1_physdom" {
  application_epg_dn = aci_application_epg.epg1.id
  tdn                = data.aci_physical_domain.physdom.id
}

resource "aci_epg_to_static_path" "egp1_port1" {
  application_epg_dn = aci_application_epg.epg1.id
  tdn                = "topology/pod-1/paths-202/pathep-[eth1/1]"
  encap              = "vlan-101"
}

resource "aci_epg_to_contract" "epg1_contract1a" {
  application_epg_dn = aci_application_epg.epg1.id
  contract_dn        = aci_contract.contract1.id
  contract_type      = "consumer"
}

resource "aci_epg_to_contract" "epg1_contract1b" {
  application_epg_dn = aci_application_epg.epg1.id
  contract_dn        = aci_contract.contract1.id
  contract_type      = "provider"
}