1. 客户端代码执行流程
1. GIT拉取客户端代码
https://wwwin-github.cisco.com/netascode/terraform-aac.git
2. tf配置文件结构
2.1 backend.tf 配置terraform 状态文件存储在哪 (local AWS S3...)
terraform {
backend "http" {}
}
2.2 main.tf terraform入口文件
module "aci" {
# 调用 netascode/nac-aci/aci:0.7.0 terraform repositry中的源码 并向源码中提交 (yaml_directories, manage_access_policies, manage_fabric_policies, write_default_values_file等变量)
source = "netascode/nac-aci/aci"
version = "0.7.0"
yaml_directories = ["data"]
manage_access_policies = false
manage_fabric_policies = false
manage_pod_policies = false
manage_node_policies = false
manage_interface_policies = false
manage_tenants = true
write_default_values_file = "defaults.yaml"
}
2.3 provider.tf 配置terraform供应商
terraform {
required_providers {
aci = {
source = "CiscoDevNet/aci"
version = ">= 2.1.0"
}
utils = {
source = "cloudposse/utils"
version = ">= 0.15.0"
}
}
}
provider "aci" {
# 调用CiscoDevNet/aci:2.10.0时像源码中提交的变量这里设置了 APIC URL, 用户名,密码 insecure, 以及尝试次数
url = var.apic_url
username = var.apic_user
password = var.apic_pwd
insecure = true
retries = 4
}
2.4 terraform.tfvars 以及 variables.tf 配置变量
对于terraform.tfvars文件,它是用来存储变量值的外部文件。在运行Terraform时,可以使用该文件来提供变量的值。它可以包含覆盖variables.tf中定义的默认值的变量值
# terraform.tfvars
apic_url = "http://f1apic1.aci.pub"
apic_user = "apic:tacacs\\\\aac-gitlab"
apic_pwd = "aac-gitlab"
# variables.tf
variable "apic_user" {
description = "APIC user"
type = string
}
variable "apic_pwd" {
description = "APIC password"
type = string
}
variable "apic_url" {
description = "APIC url"
type = string
}
2.5 data文件夹以及下面的yaml文件
# tenant_aac-linxu3-new.yaml
---
apic:
tenants:
- name: 'xiawang3_aci_jenkins_team'
vrfs:
- name: 'test'
bridge_domains:
- name: BD_VLAN100
vrf: PROD-linus-aac-terraform
- name: BD_VLAN101
vrf: PROD-linus-aac-terraform
- name: BD_VLAN102
vrf: PROD-linus-aac-terraform
application_profiles:
- name: PROD-linus-aac-terraform
endpoint_groups:
- name: EPG_VLAN100
bridge_domain: BD_VLAN100
physical_domains:
- PHYSICAL1
static_ports:
- node_id: 101
port: 1
vlan: 100
- name: EPG_VLAN101
bridge_domain: BD_VLAN101
physical_domains:
- PHYSICAL1
2.6 总结
netascode /terraform-aac 项目中
main.tf 文件会调用terraform仓库中的 netascode/nac-aci/aci:0.7.0源码, 源码中的variables.tf接受变量
provider.tf 文件会调用terraform仓库中的CiscoDevNet/aci:2.10.0源码并向其中传递, APIC用户名,密码,url等参数
文件执行顺序
- versions.tf
- provider.tf
- variables.tf
- main.tf
- merge.tf
- backend.tf
- outputs.tf
3. 查看上面提到netascode/nac-aci/aci:0.7.0的源码信息
terraform 仓库地址:
https://registry.terraform.io/modules/netascode/nac-aci/aci/latest
github 源代码地址:
https://github.com/netascode/terraform-aci-nac-aci
github 源码文件结构:
3.1 通过源码的tf文件,查看terraform程序执行过程
3.1.1 源码中包含的tf文件
以apic开头的tf文件为AAC可实现的User Case,先查看其他tf文件
包含tf 文件以及加载顺序如下
- versions.tf
- variables.tf
- merge.tf
- main.tf
- outputs.tf
3.1.1.1 version.tf
查看terraform版本是否大于等于 1.3.0
供应商版本是否满足要求
terraform {
required_version = ">= 1.3.0"
required_providers {
aci = {
source = "CiscoDevNet/aci"
version = ">= 2.6.1"
}
utils = {
source = "netascode/utils"
version = ">= 0.2.5"
}
local = {
source = "hashicorp/local"
version = ">= 2.3.0"
}
}
}
3.1.1.2 variables.tf
在 2.2中定义的变量会被传入并接收, 并覆盖默认值
variable "yaml_directories" {
description = "List of paths to YAML directories."
type = list(string)
default = []
}
variable "yaml_files" {
description = "List of paths to YAML files."
type = list(string)
default = []
}
variable "model" {
description = "As an alternative to YAML files, a native Terraform data structure can be provided as well."
type = map(any)
default = {}
}
variable "manage_access_policies" {
description = "Flag to indicate if access policies should be managed."
type = bool
default = false
}
variable "manage_fabric_policies" {
description = "Flag to indicate if fabric policies should be managed."
type = bool
default = false
}
variable "manage_pod_policies" {
description = "Flag to indicate if pod policies should be managed."
type = bool
default = false
}
variable "manage_node_policies" {
description = "Flag to indicate if node policies should be managed."
type = bool
default = false
}
variable "manage_interface_policies" {
description = "Flag to indicate if interface policies should be managed."
type = bool
default = false
}
variable "managed_interface_policies_nodes" {
description = "List of node IDs for which interface policies should be managed. By default interface policies for all nodes will be managed."
type = list(number)
default = []
}
variable "manage_tenants" {
description = "Flag to indicate if tenants should be managed."
type = bool
default = false
}
variable "managed_tenants" {
description = "List of tenant names to be managed. By default all tenants will be managed."
type = list(string)
default = []
}
variable "write_default_values_file" {
description = "Write all default values to a YAML file. Value is a path pointing to the file to be created."
type = string
default = ""
}
3.1.1.3 merge.tf 定义 locals{里面参数的keyword为可用的变量名称} 和 data类新的个实例(data.utils_yaml_merge.model/defaults/modules)
locals {
# yaml_strings_directories 使用了一个for循环来遍历var.yaml_directories列表中的每个目录,并使用fileset函数获取目录中的所有.yaml和.yml文件。 2.2中定义
yaml_strings_directories = flatten([
for dir in var.yaml_directories : [
for file in fileset(".", "${dir}/*.{yml,yaml}") : file(file)
]
])
# yaml_strings_files 为yaml所在的yaml file名称 可在2.2中定义
yaml_strings_files = [
for file in var.yaml_files : file(file)
]
# model_strings 使用了一个条件表达式判断var.model中是否有值,如果有值则将其转为一个列表,否则赋值一个空列表。
model_strings = length(keys(var.model)) != 0 ? [yamlencode(var.model)] : []
# user_defaults 使用了try函数来解析data.utils_yaml_merge.model.output中的defaults值,如果解析失败则返回一个空字典
user_defaults = { "defaults" : try(lookup(yamldecode(data.utils_yaml_merge.model.output), "defaults"), {}) }
defaults = lookup(yamldecode(data.utils_yaml_merge.defaults.output), "defaults")
user_modules = { "modules" : try(lookup(yamldecode(data.utils_yaml_merge.model.output), "modules"), {}) }
modules = lookup(yamldecode(data.utils_yaml_merge.modules.output), "modules")
model = yamldecode(data.utils_yaml_merge.model.output)
}
# 这里的 data.utils_yaml_merge.model 返回值就是data文件夹的yaml文件里定义的所有模块
data "utils_yaml_merge" "model" {
input = concat(local.yaml_strings_directories, local.yaml_strings_files, local.model_strings)
# 当规定了data文件夹有值,文件夹里有yaml文件, yaml文件不是空文件时才会返回model 要不会报错 "Either `yaml_directories`,`yaml_files` or a non-empty `model` value must be provided."
lifecycle {
precondition {
condition = length(var.yaml_directories) != 0 || length(var.yaml_files) != 0 || length(keys(var.model)) != 0
error_message = "Either `yaml_directories`,`yaml_files` or a non-empty `model` value must be provided."
}
}
}
# 从defaults文件夹中拿到defaults.yaml 里面的值设置 data.utils_yaml_merge.defaults
data "utils_yaml_merge" "defaults" {
input = [file("${path.module}/defaults/defaults.yaml"), yamlencode(local.user_defaults)]
}
# 从defaults文件夹中拿到modules.yaml 里面的值设置 data.utils_yaml_merge.modules
data "utils_yaml_merge" "modules" {
input = [file("${path.module}/defaults/modules.yaml"), yamlencode(local.user_modules)]
}
resource "local_sensitive_file" "defaults" {
count = var.write_default_values_file != "" ? 1 : 0
content = data.utils_yaml_merge.defaults.output
filename = var.write_default_values_file
}
3.1.1.4 main.tf
# tenant_aac-linxu3-new.yaml
---
apic:
tenants:
- name: 'xiawang3_aci_jenkins_team'
vrfs:
- name: 'test'
bridge_domains:
- name: BD_VLAN100
vrf: PROD-linus-aac-terraform
- name: BD_VLAN101
vrf: PROD-linus-aac-terraform
- name: BD_VLAN102
vrf: PROD-linus-aac-terraform
application_profiles:
- name: PROD-linus-aac-terraform
endpoint_groups:
- name: EPG_VLAN100
bridge_domain: BD_VLAN100
physical_domains:
- PHYSICAL1
static_ports:
- node_id: 101
port: 1
vlan: 100
- name: EPG_VLAN101
bridge_domain: BD_VLAN101
physical_domains:
- PHYSICAL1
以上代码是之前定义在data文件夹下的yaml文件, 里面只有 locals.apic.tenants
所以下面main.tf只需要看tenants部分即可
locals {
apic = try(local.model.apic, {})
access_policies = try(local.apic.access_policies, {})
fabric_policies = try(local.apic.fabric_policies, {})
pod_policies = try(local.apic.pod_policies, {})
node_policies = try(local.apic.node_policies, {})
interface_policies = try(local.apic.interface_policies, {})
nodes = [for node in try(local.apic.interface_policies.nodes, []) : {
id = node.id
name = try([for n in local.node_policies.nodes : n.name if n.id == node.id][0], "")
role = try([for n in local.node_policies.nodes : n.role if n.id == node.id][0], "")
interfaces = try(node.interfaces, [])
fexes = try(node.fexes, [])
} if length(var.managed_interface_policies_nodes) == 0 || contains(var.managed_interface_policies_nodes, node.id)]
# locals.tenants
# 这段代码使用了 Terraform 中的 `for` 表达式和条件语句来生成一个列表 `tenants`。
# 该表达式的含义是遍历 `local.apic.tenants` 列表中的每个 `tenant`,然后使用条件判断来过滤保留的 `tenant`。
# - 条件 `length(var.managed_tenants) == 0` 表示如果 `var.managed_tenants` 列表为空,则保留所有的 `tenant`。
# - 条件 `contains(var.managed_tenants, tenant.name)` 表示如果 `var.managed_tenants` 列表中包含当前 `tenant` 的 `name` 属性,则保留该 `tenant`。
# 最终生成的 `tenants` 列表中包含符合条件的 `tenant` 对象。
tenants = [for tenant in try(local.apic.tenants, []) : tenant if length(var.managed_tenants) == 0 || contains(var.managed_tenants, tenant.name)]
interface_types = flatten([
for node in try(local.interface_policies.nodes, []) : [
for interface in try(node.interfaces, []) : {
key = "${node.id}/${try(interface.module, local.defaults.apic.interface_policies.nodes.interfaces.module)}/${interface.port}"
pod_id = try([for n in try(local.node_policies.nodes, []) : try(n.pod, local.defaults.apic.node_policies.nodes.pod) if n.id == node.id][0], local.defaults.apic.node_policies.nodes.pod)
node_id = node.id
module = try(interface.module, local.defaults.apic.interface_policies.nodes.interfaces.module)
port = interface.port
type = interface.type
} if try(interface.type, null) != null
]
])
leaf_interface_policy_group_mapping = [
for pg in try(local.access_policies.leaf_interface_policy_groups, []) : {
name = pg.name
type = pg.type
node_ids = [
for node in try(local.interface_policies.nodes, []) :
node.id if length([for int in try(node.interfaces, []) : try(int.policy_group, null) if try(int.policy_group, null) == pg.name]) > 0
]
fex_ids = flatten([
for node in try(local.interface_policies.nodes, []) : [
for fex in try(node.fexes, []) :
fex.id if length([for int in try(fex.interfaces, []) : try(int.policy_group, null) if try(int.policy_group, null) == pg.name]) > 0
]
])
}
]
}
mian.tf中便定义好了yaml中规定tenants, 使用locals.tenants调用
3.1.1.5 aci_tenants.tf 下面的代码结构就很熟悉了, 定义多种模块,只能aci_tenant模块讲解
这段代码是使用Terraform的ACI模块创建ACI租户。ACI是Cisco的一种数据中心网络架构。
在模块的参数中,使用了netascode/tenant/aci
模块的版本0.1.1
。
for_each
语句循环遍历local.tenants
列表中的每个租户。使用try
函数判断tenant.managed
属性是否存在,如果不存在则使用local.defaults.apic.tenants.managed
的值来代替。判断local.modules.aci_tenant
和var.manage_tenants
是否为真来确定是否创建租户。如果满足条件,则以tenant.name
作为键值并以tenant
对应的值作为租户对象存储。
然后,将每个租户的属性作为参数传递给ACI租户模块。其中name
、alias
、description
和security_domains
等参数是根据每个租户的属性值来设置的。
在locals
块中,定义了一个vrfs
变量。使用嵌套的for
循环遍历local.tenants
列表中的每个租户以及租户中的每个VRF(虚拟路由和转发)。通过try
函数判断tenant.vrfs
属性是否存在,如果不存在则使用空列表代替。然后,将每个VRF的属性作为参数传递给ACI VRF模块。其中key
属性用于标识每个VRF,tenant
属性设置为租户的名称,name
属性根据VRF的名称和local.defaults.apic.tenants.vrfs.name_suffix
设置。alias
属性根据VRF的alias
属性设置。其他属性未提供。
根据提供的信息,这段代码的主要功能是根据给定的租户和VRF信息来创建ACI租户和VRF。
module "aci_tenant" {
source = "netascode/tenant/aci"
version = "0.1.1"
for_each = { for tenant in local.tenants : tenant.name => tenant if try(tenant.managed, local.defaults.apic.tenants.managed, true) && local.modules.aci_tenant && var.manage_tenants }
name = each.value.name
alias = try(each.value.alias, "")
description = try(each.value.description, "")
security_domains = try(each.value.security_domains, [])
}
locals {
vrfs = flatten([
for tenant in local.tenants : [
for vrf in try(tenant.vrfs, []) : {
key = format("%s/%s", tenant.name, vrf.name)
tenant = tenant.name
name = "${vrf.name}${local.defaults.apic.tenants.vrfs.name_suffix}"
alias = try(vrf.alias, "")
description = try(vrf.description, "")
enforcement_direction = try(vrf.enforcement_direction, local.defaults.apic.tenants.vrfs.enforcement_direction)
enforcement_preference = try(vrf.enforcement_preference, local.defaults.apic.tenants.vrfs.enforcement_preference)
data_plane_learning = try(vrf.data_plane_learning, local.defaults.apic.tenants.vrfs.data_plane_learning)
contract_consumers = try([for contract in vrf.contracts.consumers : "${contract}${local.defaults.apic.tenants.contracts.name_suffix}"], [])
contract_providers = try([for contract in vrf.contracts.providers : "${contract}${local.defaults.apic.tenants.contracts.name_suffix}"], [])
contract_imported_consumers = try([for contract in vrf.contracts.imported_consumers : "${contract}${local.defaults.apic.tenants.imported_contracts.name_suffix}"], [])
preferred_group = try(vrf.preferred_group, local.defaults.apic.tenants.vrfs.preferred_group)
transit_route_tag_policy = try(vrf.transit_route_tag_policy, null) != null ? "${vrf.transit_route_tag_policy}${local.defaults.apic.tenants.policies.route_tag_policies.name_suffix}" : ""
bgp_timer_policy = try("${vrf.bgp.timer_policy}${local.defaults.apic.tenants.policies.bgp_timer_policies.name_suffix}", "")
bgp_ipv4_address_family_context_policy = try("${vrf.bgp.ipv4_address_family_context_policy}${local.defaults.apic.tenants.policies.bgp_address_family_context_policies.name_suffix}", "")
bgp_ipv6_address_family_context_policy = try("${vrf.bgp.ipv6_address_family_context_policy}${local.defaults.apic.tenants.policies.bgp_address_family_context_policies.name_suffix}", "")
bgp_ipv4_import_route_target = try(vrf.bgp.ipv4_import_route_target, "")
bgp_ipv4_export_route_target = try(vrf.bgp.ipv4_export_route_target, "")
bgp_ipv6_import_route_target = try(vrf.bgp.ipv6_import_route_target, "")
bgp_ipv6_export_route_target = try(vrf.bgp.ipv6_export_route_target, "")
dns_labels = try(vrf.dns_labels, [])
pim_enabled = try(vrf.pim, null) != null ? true : false
pim_mtu = try(vrf.pim.mtu, local.defaults.apic.tenants.vrfs.pim.mtu)
pim_fast_convergence = try(vrf.pim.fast_convergence, local.defaults.apic.tenants.vrfs.pim.fast_convergence)
pim_strict_rfc = try(vrf.pim.strict_rfc, local.defaults.apic.tenants.vrfs.pim.strict_rfc)
pim_max_multicast_entries = try(vrf.pim.max_multicast_entries, local.defaults.apic.tenants.vrfs.pim.max_multicast_entries)
pim_reserved_multicast_entries = try(vrf.pim.reserved_multicast_entries, local.defaults.apic.tenants.vrfs.pim.reserved_multicast_entries)
pim_resource_policy_multicast_route_map = try(vrf.pim.resource_policy_multicast_route_map, null) != null ? "${vrf.pim.resource_policy_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_static_rps = [for rp in try(vrf.pim.static_rps, []) : {
ip = rp.ip
multicast_route_map = try(rp.multicast_route_map, null) != null ? "${rp.multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
}]
pim_fabric_rps = [for rp in try(vrf.pim.fabric_rps, []) : {
ip = rp.ip
multicast_route_map = try(rp.multicast_route_map, null) != null ? "${rp.multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
}]
pim_bsr_listen_updates = try(vrf.pim.bsr_listen_updates, local.defaults.apic.tenants.vrfs.pim.bsr_listen_updates)
pim_bsr_forward_updates = try(vrf.pim.bsr_forward_updates, local.defaults.apic.tenants.vrfs.pim.bsr_forward_updates)
pim_bsr_filter_multicast_route_map = try(vrf.pim.bsr_filter_multicast_route_map, null) != null ? "${vrf.pim.bsr_filter_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_auto_rp_listen_updates = try(vrf.pim.auto_rp_listen_updates, local.defaults.apic.tenants.vrfs.pim.auto_rp_listen_updates)
pim_auto_rp_forward_updates = try(vrf.pim.auto_rp_forward_updates, local.defaults.apic.tenants.vrfs.pim.auto_rp_forward_updates)
pim_auto_rp_filter_multicast_route_map = try(vrf.pim.auto_rp_filter_multicast_route_map, null) != null ? "${vrf.pim.auto_rp_filter_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_asm_shared_range_multicast_route_map = try(vrf.pim.asm_shared_range_multicast_route_map, null) != null ? "${vrf.pim.asm_shared_range_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_asm_sg_expiry = try(vrf.pim.asm_sg_expiry, local.defaults.apic.tenants.vrfs.pim.asm_sg_expiry)
pim_asm_sg_expiry_multicast_route_map = try(vrf.pim.asm_sg_expiry_multicast_route_map, null) != null ? "${vrf.pim.asm_sg_expiry_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_asm_traffic_registry_max_rate = try(vrf.pim.asm_traffic_registry_max_rate, local.defaults.apic.tenants.vrfs.pim.asm_traffic_registry_max_rate)
pim_asm_traffic_registry_source_ip = try(vrf.pim.asm_traffic_registry_source_ip, local.defaults.apic.tenants.vrfs.pim.asm_traffic_registry_source_ip)
pim_ssm_group_range_multicast_route_map = try(vrf.pim.ssm_group_range_multicast_route_map, null) != null ? "${vrf.pim.ssm_group_range_multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
pim_inter_vrf_policies = [for pol in try(vrf.pim.inter_vrf_policies, []) : {
tenant = pol.tenant
vrf = "${pol.vrf}${local.defaults.apic.tenants.vrfs.name_suffix}"
multicast_route_map = try(pol.multicast_route_map, null) != null ? "${pol.multicast_route_map}${local.defaults.apic.tenants.policies.multicast_route_maps.name_suffix}" : ""
}]
pim_igmp_ssm_translate_policies = [for pol in try(vrf.pim.igmp_context_ssm_translate_policies, []) : {
group_prefix = pol.group_prefix
source_address = pol.source_address
}]
leaked_internal_prefixes = [for prefix in try(vrf.leaked_internal_prefixes, []) : {
prefix = prefix.prefix
public = try(prefix.public, local.defaults.apic.tenants.vrfs.leaked_internal_prefixes.public)
destinations = [for dest in try(prefix.destinations, []) : {
description = try(dest.description, "")
tenant = dest.tenant
vrf = dest.vrf
public = try(dest.public, null)
}]
}]
leaked_external_prefixes = [for prefix in try(vrf.leaked_external_prefixes, []) : {
prefix = prefix.prefix
from_prefix_length = try(prefix.from_prefix_length, null)
to_prefix_length = try(prefix.to_prefix_length, null)
destinations = [for dest in try(prefix.destinations, []) : {
description = try(dest.description, "")
tenant = dest.tenant
vrf = dest.vrf
}]
}]
}
]
])
}
4.查看terraform仓库中netascode/tenant/aci:0.1.1源码
terraform仓库路径:
https://registry.terraform.io/modules/netascode/tenant/aci/latest
github url:
https://github.com/netascode/terraform-aci-tenant
目录结构:
4.1 直接查看main.tf
终于找到了resource资源,我们知道resource资源由provider提供,那么需要进入provider源码查看 资源类型为aci_rest_managed的配置
resource "aci_rest_managed" "fvTenant" {
dn = "uni/tn-${var.name}"
class_name = "fvTenant"
content = {
name = var.name
nameAlias = var.alias
descr = var.description
}
}
resource "aci_rest_managed" "aaaDomainRef" {
for_each = toset(var.security_domains)
dn = "${aci_rest_managed.fvTenant.dn}/domain-${each.value}"
class_name = "aaaDomainRef"
content = {
name = each.value
}
}
4.2 创建tenant时
5 查看provider源码
https://github.com/CiscoDevNet/terraform-provider-aci/blob/master/aci/data_source_aci_rest_managed.go
路径在这里,看的很吃力, 慢慢思索中