通过Terraform一键部署CCI在线应用
应用场景
Terraform是一个开源的IT基础设施编排管理工具,通过Terraform您可以轻松的创建、更新、删除华为云资源。部署CCI在线应用,用户除了需要部署业务容器,还要配置对应的网络资源和负载均衡资源。基于Terraform工具,用户可以自动化创建、更新和删除CCI实例、网络配置、负载均衡资源,并且Terraform可以自动处理资源之间的依赖关系,确保资源按正确的顺序创建和更新,一键部署应用。
示例说明
本文以Nginx镜像为例创建无状态工作负载,通过Poolbinding对象将容器实例自动更新至弹性负载均衡ELB服务后端服务器组的后端,实现Nginx应用发布和公网访问的目标。通过Terraform实现Nginx应用发布的过程,涉及创建的资源和依赖关系如下图所示。
参数名称 |
参数说明 |
---|---|
ns_name |
创建的命名空间名称,示例中默认值为“tf-ns“,用户可自定义。 |
network_name |
创建的network名称,示例中默认值为“tf-network“,用户可自定义。 |
project_id |
项目ID,详情请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0001.html |
domain_id |
账号ID,详情请参见https://support.huaweicloud.com/usermanual-ca/ca_01_0001.html |
deploy_name |
创建的deployment名称,示例中默认值为“tf-deploy“,用户可自定义。 |
replica |
创建的deployment副本数,示例中默认值为1,用户可自定义。 |
image_address |
创建的deployment容器镜像地址,示例中为容器镜像服务SWR镜像中Nginx镜像地址。 |
pool_binding_name |
创建的poolbinding名称,示例中默认值为“tf-binding“,用户可自定义。 |
container_port |
创建的deployment容器开放的端口,示例中采用Nginx镜像,默认80。该参数与容器镜像对应,如果配置错误,会导致访问异常。 |
vpc_name |
创建的vpc名称,示例中默认值为“tf-cci-vpc“,用户可自定义。 |
subnet_name |
创建的subnet名称,示例中默认值为“tf-cci-subnet“,用户可自定义。 |
sg_name |
创建的安全组名称,示例中默认值为“tf-sg“,用户可自定义。 |
bandwidth_name |
创建的带宽名称,示例中默认值为“tf-bandwidth“,用户可自定义。 |
elb_name |
创建的elb名称,示例中默认值为“tf-elb“,用户可自定义。 |
listener_name |
创建的listener名称,示例中默认值为“tf-listener“,用户可自定义。 |
pool_name |
创建的pool名称,示例中默认值为“tf-pool“,用户可自定义。 |
listener_protocol_port |
创建的listener监控端口,示例中默认值为“8080“ ,用户可自定义。 |
参数名称 |
参数说明 |
---|---|
swr_vpcep_service_id |
swr终端节点服务ID,获取方式请参见通过Terraform查询资源。 |
obs_vpcep_service_id |
obs终端节点服务ID,请向OBS服务提交工单获取。 |
l4_flavor_id |
弹性负载均衡配置的4层规格ID,获取方式请参见通过Terraform查询资源。 |
l7_flavor_id |
弹性负载均衡配置的7层规格ID,获取方式请参见通过Terraform查询资源。 |
前置条件
已安装配置Terraform。为了降低密钥泄露的风险,下文示例配置Terraform认证鉴权采用环境变量的方式,执行Terraform相关命令会自动获取凭据相关的环境变量,故操作步骤中不涉及静态凭据配置。
操作步骤
- 创建工作项目。
根据示例说明中相关资源和创建流程图,创建如下关系的工作目录和文件。
demo |- main.tf |- variables.tf |- outputs.tf |- modules |- vpc | |- main.tf | |- variables.tf | |- outputs.tf |- elb | |- main.tf | |- variables.tf | |- outputs.tf |- cci |- main.tf |- variables.tf |- outputs.tf(optional)
- demo:项目名称,用户可自定义。
- main.tf:provider和各模块声明。
- variables.tf:各模块资源所使用的参数声明。
- outputs.tf:各模块资源或表达式关系值的输出声明。
- modules/vpc:网络子模块,其中包含main.tf、variables.tf和outputs.tf。
- modules/elb:弹性负载均衡子模块,其中包含main.tf、variables.tf和outputs.tf。
- modules/cci:云容器实例子模块,其中包含main.tf、variables.tf和outputs.tf,其中outputs.tf可选,因为本示例不涉及,用户可自定义CCI资源输出信息。
创建完成后的工作目录和文件如下:
- 编写网络子模块(modules/vpc)配置文件。
- 编写main.tf文件,创建虚拟私有云(VPC)、子网(Subnet)、VPCEP(包含SWR VPCEP和OBS VPCEP)、安全组(SecurityGroups)、安全组规则(SecurityGroup Rule)和弹性公网IP(EIP)。
terraform { required_providers { huaweicloud = { source = "huaweicloud/huaweicloud" version = ">= 1.36.0" } } } # 1.创建VPC。 resource "huaweicloud_vpc" "vpc" { name = var.vpc_name # VPC名称,作为变量输入。 cidr = "172.16.0.0/16" # VPC下可用子网的范围。 } # 2.创建子网。 resource "huaweicloud_vpc_subnet" "subnet" { name = var.subnet_name # 子网名称,作为变量输入。 cidr = "172.16.0.0/24" # 子网的网段。 gateway_ip = "172.16.0.1" # 子网的网关,必须是ip格式。 vpc_id = huaweicloud_vpc.vpc.id # 子网所在的VPC ID,即创建VPC所返回的ID。 } # 3.创建SWR VPCEP和OBS VPCEP,根据SWR公共镜像仓库的实现,需要购买SWR VPCEP和OBS VPCEP,才能完成镜像拉取动作。 resource "huaweicloud_vpcep_endpoint" "swr_vpcep" { service_id = var.swr_vpcep_service_id # SWR终端节点服务的ID,作为变量输入。 vpc_id = huaweicloud_vpc.vpc.id # 终端节点所在的VPC ID,即创建VPC所返回的ID。 network_id = huaweicloud_vpc_subnet.subnet.id # 需要指定vpc_id对应VPC下已创建的网络(network)的ID。 } resource "huaweicloud_vpcep_endpoint" "obs_vpcep" { service_id = var.obs_vpcep_service_id # OBS终端节点服务的ID,作为变量输入。 vpc_id = huaweicloud_vpc.vpc.id # 终端节点所在的VPC ID,即创建VPC所返回的ID。 } # 4.创建安全组。 resource "huaweicloud_networking_secgroup" "secgroup" { name = var.sg_name # 安全组名称,作为变量输入。 } # 5.创建安全组入方向规则,出方向规则默认全放通,不单独配置。 resource "huaweicloud_networking_secgroup_rule" "ingress" { security_group_id = huaweicloud_networking_secgroup.secgroup.id # 安全组ID,即创建安全组所返回的ID。 direction = "ingress" # 安全组规则入控制方向。 ethertype = "IPv4" # IP地址协议类型。 ports = "1-65535" # 端口。 protocol = "tcp" # 协议类型。 } # 6.创建弹性公网IP。 resource "huaweicloud_vpc_eip" "eip" { publicip { type = "5_bgp" # 弹性公网IP的类型。 } bandwidth { share_type = "PER" # 带宽类型,PER为独占带宽。 charge_mode = "traffic" # 计费模式,traffic为按流量计费。 size = "5" # 带宽大小。 name = var.bandwidth_name # 带宽名称,作为变量输入。 } }
Terraform会根据创建资源的参数依赖关系,自动识别资源间的依赖关系,按顺序创建资源。例如创建子网时引用了VPC ID,最终创建资源时Terraform会先创建VPC,再创建子网。
- 编写variables.tf文件,配置网络子模块的输入变量。
variable "vpc_name" { default = "tf-vpc" } variable "subnet_name" {} variable "swr_vpcep_service_id" {} variable "obs_vpcep_service_id" {} variable "sg_name" {} variable "bandwidth_name" {}
variables.tf中的变量即上一步中创建的资源参数中配置的变量,支持通过父模块传递值,若父模块未传递值,也可以配置使用默认值,如vpc_name。
- 编写outputs.tf文件,配置网络子模块中向上一级模块回传的输出变量。
# 创建的子网IPv4 ID,创建CCI Network资源时引用。 output "subnet_ipv4_id" { value = huaweicloud_vpc_subnet.subnet.ipv4_subnet_id } # 创建的安全组ID,创建CCI network资源时引用。 output "sg_id" { value = huaweicloud_networking_secgroup.secgroup.id } # 创建的VPC ID,创建ELB资源时引用。 output "vpc_id" { value = huaweicloud_vpc.vpc.id } # 创建的弹性公网IP ID,创建ELB资源时引用。 output "eip_id" { value = huaweicloud_vpc_eip.eip.id } # 创建的弹性公网IP IPv4地址。 output "eip_address" { value = huaweicloud_vpc_eip.eip.address }
子模块中的output定义表示向上一级模块回传值,最终可作为根模块需要打印在终端上的信息,同级子模块也可以引用回传的值。
例:网络子模块上一级模块即为根模块,根模块outputs.tf文件引用输出变量后,执行应用,最终会在终端打印对应的信息。此外,网络子模块的输出变量,例如子网IPv4 ID,可以被云容器实例子模块引用,作为network资源对象的子网参数。
- 编写main.tf文件,创建虚拟私有云(VPC)、子网(Subnet)、VPCEP(包含SWR VPCEP和OBS VPCEP)、安全组(SecurityGroups)、安全组规则(SecurityGroup Rule)和弹性公网IP(EIP)。
- 编写弹性负载均衡子模块(modules/elb)配置文件。
- 编写main.tf文件,创建独享型负载均衡器(ELB)、监听器(Listener)、后端服务器组(Pool)。
terraform { required_providers { huaweicloud = { source = "huaweicloud/huaweicloud" version = ">= 1.36.0" } } } # 1.创建ELB。 resource "huaweicloud_elb_loadbalancer" "elb" { name = var.elb_name # ELB名称,作为变量输入。 vpc_id = var.vpc_id # ELB所在的VPC ID,作为变量输入,即网络子模块创建的VPC ID。 ipv4_subnet_id = var.ipv4_subnet_id # ELB所在子网的IPv4子网ID,作为变量输入,即网络子模块创建的子网IPv4 ID。 l4_flavor_id = var.l4_flavor_id # 网络型规格ID,作为变量输入,可查询对应局点的flavor获取。 l7_flavor_id = var.l7_flavor_id # 应用型规格ID,作为变量输入,可查询对应局点的flavor获取。 availability_zone = [ # ELB所在的可用区列表,为了支持可用区容灾,建议选取不少于2个可用区。 "ap-southeast-3a", "ap-southeast-3b", ] ipv4_eip_id = var.eip_id # ELB绑定的公网IP的ID,作为变量输入,即网络子模块创建的公网IP ID。 } # 2.创建HTTP协议的监听器。 resource "huaweicloud_elb_listener" "listener" { name = var.listener_name # 监听器名称,作为变量输入。 protocol = "HTTP" # 监听器的监听协议。 protocol_port = var.listener_protocol_port # 监听器的监听端口。 loadbalancer_id = huaweicloud_elb_loadbalancer.elb.id # 监听器所属的负载均衡器的ID,即创建ELB所返回的ID。 } # 3.创建后端服务器组。 resource "huaweicloud_elb_pool" "pool" { name = var.pool_name # 后端服务器组名称,作为变量输入。 protocol = "HTTP" # 后端服务器组的后端协议,创建的监听器为HTTP时,该参数必须为HTTP。 lb_method = "ROUND_ROBIN" # 后端服务器组的负载均衡算法,示例中使用加权轮询算法。 listener_id = huaweicloud_elb_listener.listener.id # 后端服务器组关联的监听器的ID,即创建监听器返回的ID。 vpc_id = var.vpc_id # 后端服务器组关联的虚拟私有云的ID,即网络子模块创建的VPC ID。 type = "instance" # 后端服务器组的类型,instance表示允许任意类型的后端。 }
Terraform会根据创建资源的参数依赖关系,自动识别资源间的依赖关系,按顺序创建资源。例如创建ELB时引用了网络子模块的公网IP ID,最终创建资源时Terraform会先创建网络子模块的弹性公网IP,再创建弹性负载均衡子模块的ELB。
- 编写variables.tf文件,配置网络子模块的输入变量。
variable "vpc_id" {} variable "ipv4_subnet_id" {} variable "elb_name" {} variable "l4_flavor_id" {} variable "l7_flavor_id" {} variable "eip_id" {} variable "listener_name" {} variable "pool_name" {} variable "listener_protocol_port" {}
- 编写outputs.tf文件,配置网络子模块中向上一级模块回传的输出变量。
# 创建的后端服务器组ID,创建CCI poolbinding资源时引用。 output "pool_id" { value = huaweicloud_elb_pool.pool.id }
子模块中的output定义表示向上一级模块回传值,最终可作为根模块需要打印在终端上的信息,同级子模块也可以引用回传的值。
例:弹性负载均衡子模块上一级模块即为根模块,根模块outputs.tf文件引用输出变量后,执行应用,最终会在终端打印对应的信息。此外,弹性负载均衡子模块的输出变量,例如后端服务器组ID,可以被云容器实例子模块引用,作为poolbinding资源对象的子网参数。
- 编写main.tf文件,创建独享型负载均衡器(ELB)、监听器(Listener)、后端服务器组(Pool)。
- 云容器实例子模块(modules/cci)配置文件编写。
- 编写main.tf文件,创建命名空间(namespace)、network、负载(deployment)和poolbinding。
terraform { required_providers { huaweicloud = { source = "huaweicloud/huaweicloud" version = ">= 1.36.0" } } } # 1.创建namespace。 resource "huaweicloud_cciv2_namespace" "ns" { name = var.ns_name # namespace名称,作为变量输入。 } # 2.创建network。 resource "huaweicloud_cciv2_network" "network" { name = var.network_name # network名称,作为变量输入 namespace = var.ns_name # network所在namespace名称,作为变量输入。 annotations = { "yangtse.io/project-id" = var.project_id # 项目ID,作为变量输入。 "yangtse.io/domain-id" = var.domain_id # 账号ID,作为变量输入。 } subnets { subnet_id = var.subnet_ipv4_id # 子网IPv4 ID,作为变量输入,即网络子模块创建的子网IPv4 ID。 } security_group_ids = var.sg_ids # 安全组ID,即网络子模块创建的安全组ID。 # 显示声明和namespace的依赖关系 depends_on = [huaweicloud_cciv2_namespace.ns] } # 3.创建deployment。 resource "huaweicloud_cciv2_deployment" "deploy" { namespace = var.ns_name # deployment所在namespace的名称,作为变量输入。 name = var.deploy_name # deployment的名称,作为变量输入。 replicas = var.replica # deployment的副本数,作为变量输入。 selector { match_labels = { app = var.deploy_name # 指定selector的labels,用户可自定义,作为变量输入。 } } template { metadata { labels = { app = var.deploy_name # 指定labels,用户可自定义,作为变量输入。 } } spec { containers { name = "container1" image = var.image_address # 容器镜像地址,用户可自定义,本示例使用Nginx镜像,作为变量输入。 resources { limits = { cpu = "1" memory = "2G" } requests = { cpu = "1" memory = "2G" } } } image_pull_secrets { name = "imagepull-secret" } } } # 显示声明和network的依赖关系 depends_on = [huaweicloud_cciv2_network.network] } # 4.创建poolbinding,后端关联创建的deployment。 resource "huaweicloud_cciv2_pool_binding" "binding" { name = var.pool_binding_name # poolbinding名称,作为变量输入。 namespace = var.ns_name # poolbinding所在namespace名称,作为变量输入。 pool_ref { id = var.pool_id # 关联的ELB后端服务器组ID,作为变量输入,即弹性负载均衡子模块创建的后端服务器组ID。 } target_ref { group = "cci/v2" # 关联的后端对象group。 kind = "Deployment" # 关联的后端对象类型。 name = var.deploy_name # 关联的后端对象deployment名称,作为变量输入。 namespace = var.ns_name # 关联的后端对象所在namespace名称,作为变量输入。 port = var.container_port # 关联的后端对象目标端口号,即开放的容器端口,作为变量输入。 } # 显示声明和Deployment的依赖关系 depends_on = [huaweicloud_cciv2_deployment.deploy] }
Terraform会根据创建资源的参数依赖关系,自动识别资源间的依赖关系,按顺序创建资源。如果两个资源存在依赖关系且无隐性依赖关系,可通过在资源中声明depends_on来为某个特性资源指定依赖,使其创建时间点位于被依赖资源之后。例如network的创建需要在namespace之后,但是创建network时参数中不涉及对创建的namespace资源的参数引用,因此通过声明depends_on显示指定network和namespace的依赖关系。
- 编写variables.tf文件,配置云容器实例子模块的输入变量。
variable "ns_name" {} variable "network_name" {} variable "project_id" {} variable "domain_id" {} variable "sg_ids" { type = list(string) } variable "subnet_ipv4_id" {} variable "deploy_name" {} variable "replica" { type = number } variable "image_address" {} variable "pool_binding_name" {} variable "container_port" {} variable "pool_id" {}
- 编写main.tf文件,创建命名空间(namespace)、network、负载(deployment)和poolbinding。
- 编写根模块配置文件。
- 编写main.tf文件,配置网络、弹性负载均衡和云容器实例子模块。
terraform { required_providers { huaweicloud = { source = "huaweicloud/huaweicloud" version = ">= 1.36.0" } } } # 云容器实例子模块。 module "cci" { # 声明云容器实例子模块所在的路径。 source = "./cci" # 云容器实例子模块涉及的变量输入,引用根模块变量或者其它子模块输出值。 ns_name = var.ns_name network_name = var.network_name sg_ids = [module.vpc.sg_id] subnet_ipv4_id = module.vpc.subnet_ipv4_id project_id = var.project_id domain_id = var.domain_id deploy_name = var.deploy_name replica = var.replica image_address = var.image_address pool_binding_name = var.pool_binding_name pool_id = module.elb.pool_id container_port = var.container_port # 显示声明云容器实例子模块和弹性负载均衡子模块的依赖关系。 depends_on = [module.elb] } # 网络子模块。 module "vpc" { # 申明网络子模块所在的路径。 source = "./vpc" # 网络子模块涉及的变量输入,引用根模块变量或者其它子模块输出值。 vpc_name = var.vpc_name subnet_name = var.subnet_name swr_vpcep_service_id = var.swr_vpcep_service_id obs_vpcep_service_id = var.obs_vpcep_service_id sg_name = var.sg_name bandwidth_name = var.bandwidth_name } # 弹性负载均衡子模块。 module "elb" { # 声明弹性负载均衡子模块的路径。 source = "./elb" # 弹性负载均衡子模块涉及的变量输入,引用根模块变量或者其它子模块输出值。 elb_name = var.elb_name l4_flavor_id = var.l4_flavor_id l7_flavor_id = var.l7_flavor_id vpc_id = module.vpc.vpc_id ipv4_subnet_id = module.vpc.subnet_ipv4_id listener_name = var.listener_name pool_name = var.pool_name eip_id = module.vpc.eip_id listener_protocol_port = var.listener_protocol_port }
- 编写variables.tf文件,配置根模块的输入变量。
# 云容器实例子模块涉及的输入变量。 # namespace名称。 variable "ns_name" { default = "tf-ns" } # network名称。 variable "network_name" { default = "tf-network" } # 用户项目ID。 variable "project_id" { default = "0582xxxxxxxxxxxxxxxxxxxxxxxxdb94" } # 用户账号ID。 variable "domain_id" { default = "0560xxxxxxxxxxxxxxxxxxxxxxxxa060" } # deployment名称。 variable "deploy_name" { default = "tf-deploy" } # deployment副本数。 variable "replica" { type = number default = "1" } # deployment容器镜像地址。 variable "image_address" { default = "library/nginx:stable-alpine-perl" } # poolbinding名称。 variable "pool_binding_name" { default = "tf-binding" } # deployment容器开放的端口,本文示例采用Nginx镜像,默认80端口。 variable "container_port" { default = "80" } # 网络子模块涉及的输入变量。 # vpc名称。 variable "vpc_name" { default = "tf-cci-vpc" } # subnet名称。 variable "subnet_name" { default = "tf-cci-subnet" } # swr终端节点服务ID。 variable "swr_vpcep_service_id" { default = "005axxxx-xxxx-xxxx-xxxx-xxxx019eb5c6" } # obs终端节点服务ID。 variable "obs_vpcep_service_id" { default = "a372xxxx-xxxx-xxxx-xxxx-xxxx8fb2e98f" } # 安全组名称。 variable "sg_name" { default = "tf-sg" } # 带宽名称。 variable "bandwidth_name" { default = "tf-bandwidth" } # 弹性负载均衡子模块涉及的输入变量。 # 弹性负载均衡器名称。 variable "elb_name" { default = "tf-elb" } # 弹性负载均衡配置的4层规格ID。 variable "l4_flavor_id" { default = "7699xxxx-xxxx-xxxx-xxxx-xxxxf76a3dc4" } # 弹性负载均衡配置的7层规格ID。 variable "l7_flavor_id" { default = "47b9xxxx-xxxx-xxxx-xxxx-xxxxd3e73e26" } # 监听器名称。 variable "listener_name" { default = "tf-listener" } # 后端服务器组名称。 variable "pool_name" { default = "tf-pool" } # 监听器的监听端口。 variable "listener_protocol_port" { default = "8080" }
根模块variables.tf中的变量即子模块涉及的外部输入变量,通过父模块传递给子模块,示例中均配置了默认值,执行Terraform创建命令时,若未设置外部输入,直接使用默认值。
- 编写outputs.tf文件,配置终端输出变量,本文示例只显示弹性公网IP IPv4地址,用户可定义输出其它变量。
# 弹性公网IP IPv4地址。 output "eip_address" { value = module.vpc.eip_address }
根模块中的output定义表示需要打印在终端上的信息。
- 编写main.tf文件,配置网络、弹性负载均衡和云容器实例子模块。
- 应用部署。
- 在项目”demo”中执行如下命令初始化。
terraform init
回显如下,首次执行时会下载HuaweiCloud Provider并安装。
- 在项目”demo”中执行如下命令查看要创建的资源。
terraform plan
回显如下,Terraform会显示要创建哪些资源。
- 在项目”demo”中执行如下命令创建资源。
terraform apply
根据提示输入“yes”,回显如下,可以看到资源已经创建,您也可以到华为云控制台上查看资源是否已经创建。
- 访问Nginx应用,根据资源创建后终端回显的弹性公网IP IPv4地址,构造curl命令。
curl http://${eip_address}:${port}
- eip_address:弹性公网IP IPv4地址,即创建资源后终端显示的地址。
- port:监听器的监听端口,若未在创建资源时指定,即5配置在variables.tf中的默认值。
回显如下,表明访问成功。
- 在项目”demo”中执行如下命令初始化。
- 资源销毁。
执行如下命令销毁资源,默认释放所有资源。
terraform destroy
根据提示输入“yes”,回显如下,可以看到资源全部删除,您也可以到华为云控制台上查看资源是否已经清理。
通过Terraform查询资源
Terraform支持资源查询,下文示例展示了用户如何通过Terraform命令查询SWR终端节点服务和弹性负载均衡服务支持的规格。

如果Terraform查询的资源信息过多,直接输出到终端会不便于阅读和检索。为提高可用性,建议将查询结果重定向保存到本地。
- 重新创建一个项目,例如命名为“demo2“,编写main.tf文件,内容如下所示。
terraform { required_providers { huaweicloud = { source = "huaweicloud/huaweicloud" version = ">= 1.20.0" } } } # 查询swr终端节点服务。 data "huaweicloud_vpcep_public_services" "swr_service" { service_name = "swr" } # 在终端输出swr终端节点服务查询结果。 output "swr_service" { value = data.huaweicloud_vpcep_public_services.swr_service } # 查询对应局点弹性负载均衡服务支持的规格。 data "huaweicloud_elb_flavors" "flavors" {} # 在终端输出规格查询结果。 output "all_flavors" { value = data.huaweicloud_elb_flavors.flavors }
- 在项目“demo2”中执行如下命令初始化。
terraform init
回显如下,首次执行时会下载HuaweiCloud Provider并安装。
- 在项目“demo2”中执行如下命令查询资源。
terraform apply
回显如下,可以看到查询结果示例如下。其中,用户可根据需要自行选择对应的4层和7层规格ID。