基于阿里云的 Terraform 入门实战

介绍

Terraform 是一种部署技术,任何想要通过基础设施即代码(Infrastructure as Code, IaC)的方式来管理基础设施的人,都可以使用这种技术。

在这里基础设施主要是指的是基于云的基础设施,不过从技术上来说,只要是能够通过应用程序接口进行控制的东西都是可以算基础设施。

基础设施即代码是通过定义配置代码来进行配置及管理基础设施的过程。通过使用 IaC 的方式自动完成本应该由手动完成的操作。如可以将重复的、一致的环境部署到云上。简化操作过程,优化操作成本。

Terraform 管理基础设施的配置代码,是 HashiCorp 自研的一种配置语言,称之为 HashiCorp Configuration Language,简称为 HCL。因此我们也需要学习这种配置语言,便于我们编写配置代码进行管理基础设施。

同时 HCL 与 JSON 是完全兼容的,这也意味着 HCL 能够直接转换为 JSON 格式,反之亦然。对 JSON 的兼容,也使得与 Terraform 之外的系统进行交互操作或者动态生成配置代码就变的非常简单了。

Terraform 还有一个非常不错的好处就是云无关。云无关指的是能够使用一组相同的工具和工作流,无缝运行在任何云平台上。也就是说,使用 Terraform 把基础设施部署到阿里云腾讯云或者 AWS 甚至是私有数据中心上是一样的简单。

云无关,在现在这种云厂商林立的情况下是非常重要的,因为这意味着你不必局限于一种云厂商,也不需要为了每次更换云厂商而去学习新的管理工具或者技术。

Terraform 是通过 provider(提供程序) 与不同的云集成。provider 是 Terraform 的插件机制。通过实现 provider 来与云厂商 API 进行交互。每个云厂商都会维护自己的 provider,使 provider 能够管理该云的资源。

provider 是使用 Go 语言编写的,它会以二进制文件的方式,发布到 Terraform 的注册表中,当我们需要用到的时候,Terraform 会将其保存到指定的目录中进行调用。同时它也是负责进行厂商的身份验证、发出 API 请求等操作的。当然你也可以自己实现一个 provider,后续的有机会的话,会介绍下如何实现。

Terraform 注册表是一个全球商店,用来分享 provider(提供程序) 的二进制文件。当 Terraform 初始化时,会从该注册表中自动查找和下载所需的 provider(提供程序)

Terraform 的表达能力也是非常强的,它的配置语言也是支持条件语句、for 表达式、指令、模板文件等等,通过这些可以轻松的使我们实现更为复杂的场景。

基于阿里云的 Terraform 入门实战

若未安装 Terraform,可进入以下官方地址,根据自身系统选择对应的安装方式,进行 Terraform 的安装。

https://developer.hashicorp.com/terraform/downloads?product_intent=terraform

Hello Terraform

接下来,以一个实战来演示一下 Terraform 的使用。

使用 Terraform 代替我们的手动操作,进行身份验证及 API 调用,在阿里云上创建一个 ECS 的实例。并且演示一下如何使用 Terraform 删除 ECS 实例。

基于阿里云的 Terraform 入门实战

Terraform 创建阿里云 ECS 实例的步骤:

  1. 编写 Terraform 的配置代码。
  2. 初始化 Terraform 项目目录,并安装阿里云的 provider
  3. 查看并创建 Terraform 的变更计划。(非必须)
  4. 执行创建 ECS 实例的计划。
  5. 清除 ECS 实例。

编写配置代码

下面我们来创建一个名叫 main.tf 的文件,通过这个文件来告诉 Terraform 我们想要干什么,然后让 Terraform 帮我们实现,我们想要的结果。

阿里云 provider 相关文档地址:https://registry.terraform.io/providers/aliyun/alicloud/latest/docs

.tf 为拓展名的文件,表示它是 Terraform 的配置代码文件。

Terraform 在运行的时候,会读取工作目录下的所有 .tf 为拓展名的文件,并将它们连接起来。

terraform {
  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
    }
  }
}

# 定义云厂商
provider "alicloud" {
  region     = "cn-shanghai"
  access_key = "LTAIxxxxxxxxxxxxxxxxx"
  secret_key = "hmbkxxxxxxxxxxxxxxxxxxxxxxxxx"
}

# 创建vpc
resource "alicloud_vpc" "vpc" {
  vpc_name   = "vpc_1"
  cidr_block = "10.0.0.0/16"
}

# 创建vswitch
# alicloud_vswitch是阿里云的资源字段,vsw_1字段是tf文件中的自定义唯一资源名称,vswitch_name字段是在阿里云上的自定义备注名
resource "alicloud_vswitch" "vsw_1" {
  vswitch_name = "vsw_aliyun1"
  vpc_id       = alicloud_vpc.vpc.id
  cidr_block   = "10.0.0.0/24"
  zone_id      = "cn-shanghai-b"
}

# 新建安全组
resource "alicloud_security_group" "nsg1" {
  name   = "lanyulei_aliyun_nsg1"
  vpc_id = alicloud_vpc.vpc.id
}

# 将 nsg_rule1 加入安全组 lanyulei_aliyun_nsg1 中
resource "alicloud_security_group_rule" "nsg_rule1" {
  type              = "ingress"
  ip_protocol       = "tcp"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "1/65535"
  priority          = 1
  security_group_id = alicloud_security_group.nsg1.id
  cidr_ip           = "0.0.0.0/0"
}

# 创建ECS实例
resource "alicloud_instance" "instance" {
  # cn-shanghai
  availability_zone          = "cn-shanghai-b"
  security_groups            = ["${alicloud_security_group.nsg1.id}"]
  instance_type              = "ecs.n1.small"
  system_disk_category       = "cloud_ssd"
  image_id                   = "centos_7_9_x64_20G_alibase_20220824.vhd"
  instance_name              = "lanyulei-ecs"
  vswitch_id                 = alicloud_vswitch.vsw_1.id
  internet_max_bandwidth_out = 1
  password                   = "4aI5wjyPGUlj"
}

ak、sk 需调整为自己的账号的。

初始化工作目录

在使用 Terraform 管理基础设施的之前,还需初始化工作目录。

执行 terraform init 命令,进行初始化及安装阿里云的 provider 。

➜  demo $ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of aliyun/alicloud...
- Installing aliyun/alicloud v1.196.0...
- Installed aliyun/alicloud v1.196.0 (signed by a HashiCorp partner, key ID 47422B4AA9FA381B)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

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 的变更计划

执行 terraform plan 命令,查看 Terraform 的变更计划。

  • 绿色的 +:新增。
  • 红色的 -:删除。
  • 黄色的 ~:变更。
➜  demo $ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # alicloud_instance.instance will be created
  + resource "alicloud_instance" "instance" {
      + availability_zone                  = "cn-shanghai-b"
      + credit_specification               = (known after apply)
      + deletion_protection                = false
      + deployment_set_group_no            = (known after apply)
      + dry_run                            = false
      + host_name                          = (known after apply)
      + http_endpoint                      = (known after apply)
      + http_put_response_hop_limit        = (known after apply)
      + http_tokens                        = (known after apply)
      + id                                 = (known after apply)
      + image_id                           = "centos_7_9_x64_20G_alibase_20220824.vhd"
      + instance_charge_type               = "PostPaid"
      + instance_name                      = "lanyulei-ecs"
      + instance_type                      = "ecs.n1.small"
      + internet_charge_type               = "PayByTraffic"
      + internet_max_bandwidth_in          = (known after apply)
      + internet_max_bandwidth_out         = 1
      + ipv6_address_count                 = (known after apply)
      + ipv6_addresses                     = (known after apply)
      + key_name                           = (known after apply)
      + maintenance_action                 = (known after apply)
      + password                           = (sensitive value)
      + private_ip                         = (known after apply)
      + public_ip                          = (known after apply)
      + role_name                          = (known after apply)
      + secondary_private_ip_address_count = (known after apply)
      + secondary_private_ips              = (known after apply)
      + security_groups                    = (known after apply)
      + spot_duration                      = (known after apply)
      + spot_strategy                      = "NoSpot"
      + status                             = (known after apply)
      + stopped_mode                       = (known after apply)
      + subnet_id                          = (known after apply)
      + system_disk_category               = "cloud_ssd"
      + system_disk_performance_level      = (known after apply)
      + system_disk_size                   = 40
      + volume_tags                        = (known after apply)
      + vswitch_id                         = (known after apply)
    }

  # alicloud_security_group.nsg1 will be created
  + resource "alicloud_security_group" "nsg1" {
      + id                  = (known after apply)
      + inner_access        = (known after apply)
      + inner_access_policy = (known after apply)
      + name                = "lanyulei_aliyun_nsg1"
      + security_group_type = "normal"
      + vpc_id              = (known after apply)
    }

  # alicloud_security_group_rule.nsg_rule1 will be created
  + resource "alicloud_security_group_rule" "nsg_rule1" {
      + cidr_ip           = "0.0.0.0/0"
      + id                = (known after apply)
      + ip_protocol       = "tcp"
      + nic_type          = "intranet"
      + policy            = "accept"
      + port_range        = "1/65535"
      + prefix_list_id    = (known after apply)
      + priority          = 1
      + security_group_id = (known after apply)
      + type              = "ingress"
    }

  # alicloud_vpc.vpc will be created
  + resource "alicloud_vpc" "vpc" {
      + cidr_block            = "10.0.0.0/16"
      + id                    = (known after apply)
      + ipv6_cidr_block       = (known after apply)
      + name                  = (known after apply)
      + resource_group_id     = (known after apply)
      + route_table_id        = (known after apply)
      + router_id             = (known after apply)
      + router_table_id       = (known after apply)
      + secondary_cidr_blocks = (known after apply)
      + status                = (known after apply)
      + vpc_name              = "vpc_1"
    }

  # alicloud_vswitch.vsw_1 will be created
  + resource "alicloud_vswitch" "vsw_1" {
      + availability_zone = (known after apply)
      + cidr_block        = "10.0.0.0/24"
      + id                = (known after apply)
      + name              = (known after apply)
      + status            = (known after apply)
      + vpc_id            = (known after apply)
      + vswitch_name      = "vsw_aliyun1"
      + zone_id           = "cn-shanghai-b"
    }

Plan: 5 to add, 0 to change, 0 to destroy.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

仅为查看执行计划,并未实际去进行基础设施的变更。

执行变更计划

执行 terraform apply -auto-approve,开始创建 ECS 实例。

➜  demo $ terraform apply -auto-approve

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # alicloud_instance.instance will be created
  + resource "alicloud_instance" "instance" {
      + availability_zone                  = "cn-shanghai-b"
      + credit_specification               = (known after apply)
      + deletion_protection                = false
      + deployment_set_group_no            = (known after apply)
      + dry_run                            = false
      + host_name                          = (known after apply)
      + http_endpoint                      = (known after apply)
      + http_put_response_hop_limit        = (known after apply)
      + http_tokens                        = (known after apply)
      + id                                 = (known after apply)
      + image_id                           = "centos_7_9_x64_20G_alibase_20220824.vhd"
      + instance_charge_type               = "PostPaid"
      + instance_name                      = "lanyulei-ecs"
      + instance_type                      = "ecs.n1.small"
      + internet_charge_type               = "PayByTraffic"
      + internet_max_bandwidth_in          = (known after apply)
      + internet_max_bandwidth_out         = 1
      + ipv6_address_count                 = (known after apply)
      + ipv6_addresses                     = (known after apply)
      + key_name                           = (known after apply)
      + maintenance_action                 = (known after apply)
      + password                           = (sensitive value)
      + private_ip                         = (known after apply)
      + public_ip                          = (known after apply)
      + role_name                          = (known after apply)
      + secondary_private_ip_address_count = (known after apply)
      + secondary_private_ips              = (known after apply)
      + security_groups                    = (known after apply)
      + spot_duration                      = (known after apply)
      + spot_strategy                      = "NoSpot"
      + status                             = (known after apply)
      + stopped_mode                       = (known after apply)
      + subnet_id                          = (known after apply)
      + system_disk_category               = "cloud_ssd"
      + system_disk_performance_level      = (known after apply)
      + system_disk_size                   = 40
      + volume_tags                        = (known after apply)
      + vswitch_id                         = (known after apply)
    }

  # alicloud_security_group.nsg1 will be created
  + resource "alicloud_security_group" "nsg1" {
      + id                  = (known after apply)
      + inner_access        = (known after apply)
      + inner_access_policy = (known after apply)
      + name                = "lanyulei_aliyun_nsg1"
      + security_group_type = "normal"
      + vpc_id              = (known after apply)
    }

  # alicloud_security_group_rule.nsg_rule1 will be created
  + resource "alicloud_security_group_rule" "nsg_rule1" {
      + cidr_ip           = "0.0.0.0/0"
      + id                = (known after apply)
      + ip_protocol       = "tcp"
      + nic_type          = "intranet"
      + policy            = "accept"
      + port_range        = "1/65535"
      + prefix_list_id    = (known after apply)
      + priority          = 1
      + security_group_id = (known after apply)
      + type              = "ingress"
    }

  # alicloud_vpc.vpc will be created
  + resource "alicloud_vpc" "vpc" {
      + cidr_block            = "10.0.0.0/16"
      + id                    = (known after apply)
      + ipv6_cidr_block       = (known after apply)
      + name                  = (known after apply)
      + resource_group_id     = (known after apply)
      + route_table_id        = (known after apply)
      + router_id             = (known after apply)
      + router_table_id       = (known after apply)
      + secondary_cidr_blocks = (known after apply)
      + status                = (known after apply)
      + vpc_name              = "vpc_1"
    }

  # alicloud_vswitch.vsw_1 will be created
  + resource "alicloud_vswitch" "vsw_1" {
      + availability_zone = (known after apply)
      + cidr_block        = "10.0.0.0/24"
      + id                = (known after apply)
      + name              = (known after apply)
      + status            = (known after apply)
      + vpc_id            = (known after apply)
      + vswitch_name      = "vsw_aliyun1"
      + zone_id           = "cn-shanghai-b"
    }

Plan: 5 to add, 0 to change, 0 to destroy.
alicloud_vpc.vpc: Creating...
alicloud_vpc.vpc: Creation complete after 6s [id=vpc-uf6lprsrz6c1cshob79kc]
alicloud_security_group.nsg1: Creating...
alicloud_vswitch.vsw_1: Creating...
alicloud_security_group.nsg1: Creation complete after 1s [id=sg-uf642pxnc6msqptaoctg]
alicloud_security_group_rule.nsg_rule1: Creating...
alicloud_security_group_rule.nsg_rule1: Creation complete after 0s [id=sg-uf642pxnc6msqptaoctg:ingress:tcp:1/65535:intranet:0.0.0.0/0:accept:1]
alicloud_vswitch.vsw_1: Creation complete after 6s [id=vsw-uf6un6zempw6yvrpb9xmz]
alicloud_instance.instance: Creating...
alicloud_instance.instance: Still creating... [10s elapsed]
alicloud_instance.instance: Creation complete after 12s [id=i-uf672vd7e0esv7i4lvjr]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

基础设施创建成功。

需注意,使用 Terraform 管理基础设施后,会在其对应的工作目录下创建一个 terraform.tfstate 的文件,这个文件是基础设施管理的状态文件,不可手动编辑或删除,否则 Terraform 将无法跟踪其管理的资源。

销毁资源实例

已经不再使用的基础设施,已经要尽快销毁,因为在云上运行基础设施是会持续产生费用的,若是不尽快销毁,那么损失的就是自己兜里的 RMB。

执行 terraform destroy -auto-approve 可销毁当前工作目录的对应配置代码文件的所有资源实例。

➜  demo $ terraform destroy -auto-approve
alicloud_vpc.vpc: Refreshing state... [id=vpc-uf6lprsrz6c1cshob79kc]
alicloud_security_group.nsg1: Refreshing state... [id=sg-uf642pxnc6msqptaoctg]
alicloud_vswitch.vsw_1: Refreshing state... [id=vsw-uf6un6zempw6yvrpb9xmz]
alicloud_security_group_rule.nsg_rule1: Refreshing state... [id=sg-uf642pxnc6msqptaoctg:ingress:tcp:1/65535:intranet:0.0.0.0/0:accept:1]
alicloud_instance.instance: Refreshing state... [id=i-uf672vd7e0esv7i4lvjr]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # alicloud_instance.instance will be destroyed
  - resource "alicloud_instance" "instance" {
      - availability_zone                  = "cn-shanghai-b" -> null
      - deletion_protection                = false -> null
      - dry_run                            = false -> null
      - host_name                          = "iZuf672vd7e0esv7i4lvjrZ" -> null
      - http_put_response_hop_limit        = 0 -> null
      - id                                 = "i-uf672vd7e0esv7i4lvjr" -> null
      - image_id                           = "centos_7_9_x64_20G_alibase_20220824.vhd" -> null
      - instance_charge_type               = "PostPaid" -> null
      - instance_name                      = "lanyulei-ecs" -> null
      - instance_type                      = "ecs.n1.small" -> null
      - internet_charge_type               = "PayByTraffic" -> null
      - internet_max_bandwidth_in          = -1 -> null
      - internet_max_bandwidth_out         = 1 -> null
      - ipv6_address_count                 = 0 -> null
      - ipv6_addresses                     = [] -> null
      - maintenance_action                 = "AutoRecover" -> null
      - maintenance_notify                 = false -> null
      - password                           = (sensitive value)
      - private_ip                         = "10.0.0.102" -> null
      - public_ip                          = "139.224.239.237" -> null
      - secondary_private_ip_address_count = 0 -> null
      - secondary_private_ips              = [] -> null
      - security_groups                    = [
          - "sg-uf642pxnc6msqptaoctg",
        ] -> null
      - spot_duration                      = 0 -> null
      - spot_price_limit                   = 0 -> null
      - spot_strategy                      = "NoSpot" -> null
      - status                             = "Running" -> null
      - stopped_mode                       = "Not-applicable" -> null
      - subnet_id                          = "vsw-uf6un6zempw6yvrpb9xmz" -> null
      - system_disk_category               = "cloud_ssd" -> null
      - system_disk_encrypted              = false -> null
      - system_disk_size                   = 40 -> null
      - tags                               = {} -> null
      - volume_tags                        = {} -> null
      - vswitch_id                         = "vsw-uf6un6zempw6yvrpb9xmz" -> null
    }

  # alicloud_security_group.nsg1 will be destroyed
  - resource "alicloud_security_group" "nsg1" {
      - id                  = "sg-uf642pxnc6msqptaoctg" -> null
      - inner_access        = true -> null
      - inner_access_policy = "Accept" -> null
      - name                = "lanyulei_aliyun_nsg1" -> null
      - security_group_type = "normal" -> null
      - tags                = {} -> null
      - vpc_id              = "vpc-uf6lprsrz6c1cshob79kc" -> null
    }

  # alicloud_security_group_rule.nsg_rule1 will be destroyed
  - resource "alicloud_security_group_rule" "nsg_rule1" {
      - cidr_ip           = "0.0.0.0/0" -> null
      - id                = "sg-uf642pxnc6msqptaoctg:ingress:tcp:1/65535:intranet:0.0.0.0/0:accept:1" -> null
      - ip_protocol       = "tcp" -> null
      - nic_type          = "intranet" -> null
      - policy            = "accept" -> null
      - port_range        = "1/65535" -> null
      - priority          = 1 -> null
      - security_group_id = "sg-uf642pxnc6msqptaoctg" -> null
      - type              = "ingress" -> null
    }

  # alicloud_vpc.vpc will be destroyed
  - resource "alicloud_vpc" "vpc" {
      - cidr_block            = "10.0.0.0/16" -> null
      - id                    = "vpc-uf6lprsrz6c1cshob79kc" -> null
      - name                  = "vpc_1" -> null
      - resource_group_id     = "rg-acfm2ogvfexgrly" -> null
      - route_table_id        = "vtb-uf6pyfkc87awmxaa4do32" -> null
      - router_id             = "vrt-uf69bn1f7xp89m3xfk1f1" -> null
      - router_table_id       = "vtb-uf6pyfkc87awmxaa4do32" -> null
      - secondary_cidr_blocks = [] -> null
      - status                = "Available" -> null
      - user_cidrs            = [] -> null
      - vpc_name              = "vpc_1" -> null
    }

  # alicloud_vswitch.vsw_1 will be destroyed
  - resource "alicloud_vswitch" "vsw_1" {
      - availability_zone = "cn-shanghai-b" -> null
      - cidr_block        = "10.0.0.0/24" -> null
      - id                = "vsw-uf6un6zempw6yvrpb9xmz" -> null
      - name              = "vsw_aliyun1" -> null
      - status            = "Available" -> null
      - tags              = {} -> null
      - vpc_id            = "vpc-uf6lprsrz6c1cshob79kc" -> null
      - vswitch_name      = "vsw_aliyun1" -> null
      - zone_id           = "cn-shanghai-b" -> null
    }

Plan: 0 to add, 0 to change, 5 to destroy.
alicloud_security_group_rule.nsg_rule1: Destroying... [id=sg-uf642pxnc6msqptaoctg:ingress:tcp:1/65535:intranet:0.0.0.0/0:accept:1]
alicloud_instance.instance: Destroying... [id=i-uf672vd7e0esv7i4lvjr]
alicloud_security_group_rule.nsg_rule1: Destruction complete after 0s
alicloud_instance.instance: Still destroying... [id=i-uf672vd7e0esv7i4lvjr, 10s elapsed]
alicloud_instance.instance: Destruction complete after 11s
alicloud_security_group.nsg1: Destroying... [id=sg-uf642pxnc6msqptaoctg]
alicloud_vswitch.vsw_1: Destroying... [id=vsw-uf6un6zempw6yvrpb9xmz]
alicloud_vswitch.vsw_1: Still destroying... [id=vsw-uf6un6zempw6yvrpb9xmz, 10s elapsed]
alicloud_security_group.nsg1: Still destroying... [id=sg-uf642pxnc6msqptaoctg, 10s elapsed]
alicloud_security_group.nsg1: Destruction complete after 16s
alicloud_vswitch.vsw_1: Still destroying... [id=vsw-uf6un6zempw6yvrpb9xmz, 20s elapsed]
alicloud_vswitch.vsw_1: Destruction complete after 23s
alicloud_vpc.vpc: Destroying... [id=vpc-uf6lprsrz6c1cshob79kc]
alicloud_vpc.vpc: Destruction complete after 5s

Destroy complete! Resources: 5 destroyed.

若发现最终销毁的基础设施数量不对的话,可多次执行 terraform destroy -auto-approve

本文结束。

本文为原创文章,未经授权禁止转载本站文章。
原文出处:兰玉磊的个人博客
原文链接:https://www.fdevops.com/2023/01/15/terraform-31220
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

(5)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
兰玉磊的头像兰玉磊
上一篇 2023年1月5日
下一篇 2023年1月18日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

评论列表(2条)

  • jami
    jami 2023年1月16日 19:26

    看似方便维护其他麻烦,特别在变更操作的时候。另外个人感觉阿里云的Terraform没有ucloud的好用。