✍ 道路千万条,安全第一条。操作不规范,运维两行泪。

最近在学习《AIOps》相关的知识课程,为了让学习有一定的收获,所以将其进行了总结分享,如果你恰好也需要,很荣幸能帮到你。

概述

AIOps 的时候为什么要讲 基础设施即代码(Infrastructure as Code,简称IaC) 呢?

在企业中,不论是先进的技术也好,还是优秀的思想也罢,终究都是服务于业务,这些业务都是部署在各种基础设施之上,包括 AIOps 本身的应用,所以可以理解 IaCAIOps 实现自动化的基础平台。

AIOps 的加持下,IaC 可以变的更聪明,比如:

  • 自动识别资源的浪费或瓶颈
  • 根据负载预测推荐最优的实例类型
  • 在部署失败的时候提供根因分析和修复建议

那什么是 基础设施即代码(以下简称IaC) 呢?

顾名思义,基础设施即代码(Infrastructure as Code,简称 IaC) 就是一种通过 代码 来定义、管理和部署 IT 基础设施的技术方法。换句话说就是 把以前“手动点击配置服务器”的过程,变成像写程序一样用“代码”来描述和部署基础设施。

举个例子,过去你可能这样做:

  • 登录云平台控制台
  • 手动创建 VPC、子网、安全组
  • 创建 EC2 实例并安装软件
  • 配置负载均衡器和数据库

现在你可以这样写一段代码(比如用 Terraform):

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

然后运行:

terraform apply

系统就会自动帮你创建一个 AWS 实例。

通过 IaC 可以将重复简单的工作简单化,它主要有以下优势:

特性描述
🔁 可重复每次部署都是一样的结果,避免“在我机器上能跑”的问题
📦 可版本化使用 Git 管理基础设施的变更历史
🚀 可自动化能与 CI/CD 流程集成,实现一键部署
🔄 易于扩展快速复制环境(开发、测试、生产)
🛠️ 可维护性强修改配置只需改代码,无需手动操作

常见的一些 IaC 工具有:

类型工具说明
声明式(Declarative)Terraform、Kubernetes(YAML)、AWS CloudFormation描述目标状态,工具负责实现
命令式(Imperative)Ansible、SaltStack、Chef、Puppet编写具体指令一步步执行
容器编排Docker Compose、Helm Charts定义容器化应用的部署结构
云厂商专用AWS CDK、Azure Bicep结合云平台特性的 IaC 工具

我们知道了 IaC 的优势以及常用的工具集,那我们具体能用它做什么呢?

我们可以:

  1. 通过声明式方式定义和创建基础设施,使基础设施的管理类似于编写代码,实现创建和更新的自动化。
  2. 统一管理所有资源,无论这些资源最初是否由IC工具创建或管理,都可以导入进行统一的管理,提升整体的资源管控效率。
  3. 提供更安全的基础设施更改流程,通过预先列出影响范围并经由工程师确认后再执行变更,确保更改的安全性和准确性。
  4. 与CICD工具整合,形成基础设施管理的自动化工作流,例如在工作流中自动开通开发环境所需的云基础设施,实现环境快速搭建和标准化。
  5. 提供可复用的模块,编写的IC代码可以作为模块供其他团队复用,促进团队间协作和代码的标准化使用,实现基础设施管理流程的标准化和高效性。

在众多 IaC 工具中, Terraform 一直是优选工具,所以下面会主要介绍 Terraform

什么是Terraform

TerraformIaC 的一个开源工具,它由 HashiCorp 开发,用于安全高效地 预配、管理和销毁基础设施资源 。它就像是一把“万能钥匙”,你可以用它来创建服务器、数据库、网络、容器、负载均衡器等资源,而无需手动点击云平台界面或写脚本去一个个配置。

image-yYVY.png

Terraform 采用 HCL(HashiCorp Configuration Language) 语言来定义这些文档,HCL 比较简单易学,它是一种DSL(即领域特定语言),用于简化基础设施参数配置的复杂性,它具有以下优势:

  • 保留YAML和JSON的可读性优势,同时引入动态编程特性,使其在编写时类似编程语言,但专注于配置
  • 使用HCL描述和编写JSON对象时,代码变得更清晰且简洁

比如要在AWS上创建一台EC2,只需要使用 HCL 编写以下文档:

# main.tf

# 指定 AWS 提供商
provider "aws" {
  region = "us-west-2"
}

# 创建 EC2 实例
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI ID
  instance_type = "t2.micro"
}

运行 terraform initterraform apply 后,你的 AWS 控制台中就会出现这台新创建的 EC2 实例。

Terraform架构

Terraform 的执行过程中,主要包含以下几个组件配置:

  • Terraform Config:使用 HCL 编写的声明式配置,用于描述用户希望的状态
  • Terraform Core:它是Terraform的核心编排引擎,负责处理基础设施的变更,它会通过用户编写的HCL代码转换成期望的基础设施最终状态。
  • Terraform Provider:Provider是Terraform和云厂商通信的插件,负责将 HCL 配置映射为云平台的具体 API 请求。不同云厂商有不同的Provider,这些Provider一般由云厂商自己维护。
  • Terraform State:State文件用于记录当前基础设施的真实状态,由Terraform进行管理,用于保持配置和实际状态是一致的,当有变更操作的时候,Terraform就会对比期望状态和实际状态,然后生成对应的变更计划。

image-UQTQ.png

特别说明

  1. 注意 Provider 版本兼容性
    • 引入不同 Provider 时,需关注其版本,避免因上游更新导致代码异常或参数废弃。
  2. 推荐固定版本以确保稳定性
    • 建议使用固定版本号 ,防止因自动更新带来的不兼容问题。
  3. 使用“波浪号加大于号(~>)”进行版本约束
    • 这是一种悲观约束符 ,用于限制 Provider 的版本范围。
    • 它允许小版本更新(如 bug 修复、功能增强) ,但禁止大版本升级(可能带来破坏性变更)

比如,采用以下配置表示允许使用 5.0.05.999.999 之间的版本,但不会升级到 6.0

provider "aws" {
  version = "~> 5.0"
}

Terraform的核心命令

Terraform 的命令可以使用 terraform -help 来查看,这里介绍一个常用的命令。

基础命令

常用的基础命令有:

  • terraform init:初始化工作目录,下载provider插件等依赖
  • terraform plan:查看将要执行的操作,不会实际执行
  • terraform apply:执行配置,该命令执行后会实际执行
  • terraform destroy:销毁所有由terraform创建的资源

状态管理命令

常用的状态管理命令有:

  • terraform state list:列出当前状态中的所有资源
  • terraform state show <resource>:查看某资源的具体信息
  • terraform state rm <resource>:从状态中移除某个资源(不删除真实资源)
  • terraform state pull:从远端拉取状态到本地
  • terraform state push:更新本地的状态到远端
  • terraform refresh:从基础设施实际状态更新 state 状态
  • terraform import <resource> <id>:将已有资源导入到terraform中

查询与调试命令

常用的查询与调试命令有:

  • terraform show:显示当前状态文件的内容
  • terraform output:显示outputs中定义的输出值
  • terraform graph:生成资源配置的依赖图
  • terraform validate:检查配置语法是否正确

其他命令

还有一些比较实用的命令:

  • terraform fmt:格式化.tf文件,统一风格
  • terraform taint <resource>:标记某个资源为“污染”,下次apply的时候重建
  • terraform workspace:管理多个环境(dev / staging / prod)

Terraform State

这里着重把 Terraform State 拿出来讲,是因为它是 Terraform 生命周期中必不可少的元素,它保存了真实基础设施的所有元数据。默认情况下,这些信息保存在一个名为 "terraform.tfstate" 的文件中。

Terraform 使用状态来创建执行计划并更改您的基础设施。当 Terraform 通过配置文件创建或者更改了远端对象时,它会将该远端对象的标识记录在与之对应的资源实例中,并保存在状态文件中,之后,Terraform 可能会根据未来的配置更改更新或删除该对象。

image-UKCu.png

每个在资源块中创建的基础设施资源都是通过其resource_name在 Terraform 状态中进行标识的,其对资源的管理流程大致如下:

  • 当第一次通过 terraform apply 应用 Terraform 配置时,会创建基础设施资源,同时自动生成一个状态文件,该文件引用资源块中声明的名称
  • 如果一个资源已经在 Terraform 状态文件中有标识,那么 Terraform 会将配置文件与状态文件和当前的资源远端的实际状态进行比较,并根据比较结果,会生成一个执行计划
  • 当执行该计划时,它会更新资源的状态以匹配配置文件中的定义,如果由于远端 API 限制无法实现就地更新参数,那么该执行计划将会先销毁资源,然后再重新创建新的资源;如果是一个资源销毁的计划,将发起资源的销毁操作
  • 计划执行成功后,Terraform 状态文件会更新以反映当前的基础设施状态
  • 如果某资源已从当前 Terraform 配置中移除但在状态文件中仍然存在,Terraform 则会比较配置文件并销毁不再存在的资源

Terraform 默认将本地状态文件保存在当前工作目录中,扩展名为 .tfstate,因此它们不需要额外的维护。本地状态文件适用于只有一个开发人员工作的项目,当多个开发人员同时运行 Terraform 并且每台机器都有对当前基础设施的理解和配置时,默认的本地状态文件的配置方式就会变得棘手。

在团队协作开发场景中使用本地状态时主要存在以下几个问题:

  1. 本地状态没有共享访问权限:当使用 Terraform 更新你的基础设施,团队中的每个成员都需要访问相同的状态文件,这意味着这些文件必须存储在一个共享的位置,比如 ECS 实例特定的位置,而这无形增加了管理成本。
  2. 不能锁定本地状态文件:如果两个团队成员同时运行 Terraform,他们可能会遇到竞争条件,因为多个 Terraform 进程可能同时在更新状态文件。在这种情况下,可能带来导致冲突、数据丢失和状态文件损坏等风险。
  3. 本地状态文件不保密:当信息以明文形式存储在状态文件中时,敏感数据将存在被暴露的风险,例如数据库凭证,SSH 登录密码等。

因此,在团队协作开发的场景中,推荐使用远程替代本地存储,这种存储模式下会:

  • 远端状态文件会自动更新:每次使用 plan 或者 apply 命令的时候会自动从远端加载状态文件,每次执行apply之后也会把状态文件自动同步到远端存储中。
  • 远端状态文件支持状态锁定:当执行 terraform 的时候,可以对远端状态文件进行加锁,这样在多个开发人员同时运行 terraform apply 的时候不会因同时更新而损坏。
  • 远端状态文件存储比本地存储更安全:比如像 OSS 这类远端存储不仅支持精细化控制访问权限,还支持传输和远端加密功能。

比如采用 阿里云OSS 作为后端存储,其配置如下:

terraform {
  backend "oss" {
    bucket = "bucket-for-terraform-state"
    prefix   = "path/mystate"
    key   = "version-1.tfstate"
    region = "cn-beijing"
    tablestore_endpoint = "https://terraform-remote.cn-hangzhou.ots.aliyuncs.com"
    tablestore_table = "statelock"
  }
}

其中:

  • bucket:为阿里云OSS Bucket的名称
  • prefix:在Bucket中存放状态文件的路径前缀
  • key:状态文件的名称
  • region:Bucket所在区域
  • tablestore_endpoint:tablestore的访问地址,用于状态锁
  • tablestore_table:用于状态锁的tablestore表名

说明:当你运行 terraform applydestroy 时,Terraform 会尝试对状态文件加锁。如果已有其他人在操作,就会提示“lock failed”,防止并发修改导致错误。

Terraform项目布局

任何项目的开发都需要比较好的项目布局,Terraform 也不例外,在简单的项目中推荐使用 Terraform Layout 的项目布局方式,将文件拆分成四个文件:

  • main.tf:表示主要的业务逻辑
  • outputs.tf:定义输出的内容
  • variables.tf:定义变量参数
  • version.tf:定义依赖和版本

其中, main.tf 是业务的主逻辑,比如这里要创建一个EC2,定义如下:

resource "aws_instance" "my-example" {
   ami = "ami-xxxxxxx"
   instance_type = var.instance_type
}

这里的 var.instance_type 是定义的变量,所以我们还要创建一个 variables.tf,内容如下:

variable "instance_type" {
  description = "EC2 实例类型"
  default     = "t2.micro"
}

然后,我们喜欢得到创建后的EC2实例的IP,所以我们还要定义 outputs.tf,如下:

output "public_ip" {
  value = aws_instance.my-example.public_ip
}

另外,还有一个 version.tf,这个文件主要是用来固定版本的,避免上游版本变化导致脚本出现不可预知的BUG。其定义大概如下:

# versions.tf

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }

    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }

    # 示例:阿里云 Provider
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.2.0"
    }
  }
}

如果项目比较复杂,可以将 main.tf 再进行拆分,比如我们要使用 terraform 部署 vpc、rds,则可以将其按模块进行拆分,如下:

main.tf
variables.tf
outputs.tf
modules/
  └── vpc/
      ├── main.tf
      ├── variables.tf
      └── outputs.tf
  └── rds/
      ├── main.tf
      ├── variables.tf
      └── outputs.tf

然后在 main.tf 中去引用即可,如下:

module "vpc" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
  tags       = { Name = "my-vpc" }
}

如果你的项目涉及多个环境(dev/staging/prod),且要提高代码的复用,我们可以将目录结构定义如下:

project-root/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── staging/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── modules/              # 可选:用于复用的模块
│   ├── vpc/
│   └── rds/
├── versions.tf           # 固定版本号(推荐)
└── backend.tf            # 远程后端配置(可选)

在具体使用的时候我们应该先创建 workspace ,然后再执行。比如要在 dev 环境执行命令,则用:

$ terraform workspace new dev    # 创建 dev 的workspace
$ cd environments/dev            # 切换到dev的主入口
$ terrafotm apply --auto-approve # 在dev workspace 中创建资源
# 如果不清楚目前有哪些 workspace,可以使用 terraform workspace list 查看
$ terraform workspace list
# 如果想切换到某个 workspace,可以使用 terraform workspace select <workspace> 进行切换
$ terraform workspace select dev
# 如果要删除某个 workspace 中的资源,可以使用 terraform destroy --auto-approve 命令
$ terraform destroy --auto-approve

Terraform实战

下面我们将以 模块化 + 多环境支持 的方式设计这个 Terraform 项目,并以 阿里云(Alibaba Cloud) 平台为例进行说明。

Tips:代码未经调试,原因是没钱

整体的项目需求是:

  • 创建VPC
  • 创建ECS
  • 在ECS中部署K8S,版本是1.32.1
  • 在K8S中部署Nginx,版本是latest

整体的项目目录结构规划如下:

terraform-k8s-on-ecs/
├── environments/
│   └── dev/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── ecs/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── k8s-install/
│   │   ├── main.tf
│   │   ├── install-k8s.sh
│   │   └── variables.tf
│   └── k8s-deploy-nginx/
│       ├── main.tf
│       └── nginx-deployment.yaml
├── versions.tf
└── providers.tf

1、固定版本号

# versions.tf

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.2.0"
    }

    null = {
      source  = "hashicorp/null"
      version = "~> 3.0"
    }

    template = {
      source  = "hashicorp/template"
      version = "~> 2.0"
    }
  }
}

2、创建VPC模块

(1)定义变量
# modules/vpc/variables.tf

variable "vpc_name" {
  description = "VPC 名称"
  type        = string
}

variable "cidr_block" {
  description = "VPC CIDR"
  type        = string
  default     = "10.0.0.0/16"
}

variable "subnet_cidr" {
  description = "子网 CIDR"
  type        = string
  default     = "10.0.1.0/24"
}

variable "zone_id" {
  description = "可用区 ID"
  type        = string
}
(2)定义主入口
# modules/vpc/main.tf

resource "alicloud_vpc" "main" {
  vpc_name   = var.vpc_name
  cidr_block = var.cidr_block
}

resource "alicloud_vswitch" "main" {
  vswitch_name = "${var.vpc_name}-vsw"
  cidr_block   = var.subnet_cidr
  vpc_id       = alicloud_vpc.main.id
  zone_id      = var.zone_id
}

3、创建ECS模块

(1)定义变量
# modules/ecs/variables.tf

variable "instance_name" {
  description = "ECS 实例名称"
  type        = string
}

variable "image_id" {
  description = "镜像 ID(如 centos_7_9_x64_20G_alibase_20220310.vhd)"
  type        = string
}

variable "instance_type" {
  description = "实例类型(如 ecs.n4.small)"
  type        = string
}

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "vswitch_id" {
  description = "VSwitch ID"
  type        = string
}

variable "zone_id" {
  description = "可用区 ID"
  type        = string
}

variable "root_password" {
  description = "ECS root 密码"
  type        = string
}
(2)定义主入口
# modules/ecs/main.tf

resource "alicloud_security_group" "k8s-node" {
  name   = "k8s-node-sg"
  vpc_id = var.vpc_id
}

resource "alicloud_instance" "k8s-master" {
  instance_name        = var.instance_name
  image_id             = var.image_id
  instance_type        = var.instance_type
  availability_zone    = var.zone_id
  system_disk_category = "cloud_efficiency"
  system_disk_size     = 40
  vswitch_id           = var.vswitch_id
  security_groups      = [alicloud_security_group.k8s-node.id]
  internet_max_bandwidth_out = 100
  password             = var.root_password
}

4、创建Kubernetes集群(采用kubeadm)

(1)创建安装K8S脚本
# modules/k8s-install/install-k8s.sh

#!/bin/bash

# 安装 Docker
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 
yum install -y docker-ce docker-ce-cli containerd.io
systemctl start docker
systemctl enable docker

# 安装 kubelet kubeadm kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg  https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg 
exclude=kube*
EOF

yum install -y kubelet-1.32.1 kubeadm-1.32.1 kubectl-1.32.1
systemctl enable kubelet
systemctl start kubelet

# 初始化集群
kubeadm init --pod-network-cidr=10.244.0.0/16

# 配置 kubectl
mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 安装 CNI(Flannel)
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 
(1)定义变量
# modules/k8s-install/variables.tf

variable "ecs_public_ip" {
  description = "ECS 公网 IP"
  type        = string
}

variable "root_password" {
  description = "ECS root 密码"
  type        = string
}
(2)定义主入口
# modules/k8s-install/main.tf

resource "null_resource" "install_k8s" {
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = var.ecs_public_ip
  }

  provisioner "file" {
    source      = "${path.module}/install-k8s.sh"
    destination = "/root/install-k8s.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /root/install-k8s.sh",
      "/root/install-k8s.sh"
    ]
  }
}

5、在K8S中部署Nginx

(1)、创建NG的Deployment
# modules/k8s-deploy-nginx/nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: NodePort
(2)、定义变量
# modules/k8s-deploy-nginx/variables.tf

variable "ecs_public_ip" {
  description = "ECS 公网 IP"
  type        = string
}

variable "root_password" {
  description = "ECS root 密码"
  type        = string
}
(3)、定义主入口
# modules/k8s-deploy-nginx/main.tf

resource "null_resource" "deploy_nginx" {
  depends_on = [module.k8s-install]

  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = var.ecs_public_ip
  }

  provisioner "file" {
    source      = "${path.module}/nginx-deployment.yaml"
    destination = "/root/nginx-deployment.yaml"
  }

  provisioner "remote-exec" {
    inline = [
      "kubectl apply -f /root/nginx-deployment.yaml"
    ]
  }
}

6、配置开发环境的主业务逻辑


# environments/dev/main.tf

# 创建 VPC
module "vpc" {
  source = "../../modules/vpc"

  vpc_name    = "dev-vpc"
  cidr_block  = "10.0.0.0/16"
  subnet_cidr = "10.0.1.0/24"
  zone_id     = "cn-beijing-a"
}

# 创建 ECS,依赖 VPC
module "ecs" {
  source = "../../modules/ecs"

  instance_name = "k8s-master"
  image_id      = "centos_7_9_x64_20G_alibase_20220310.vhd"
  instance_type = "ecs.n4.small"
  vpc_id        = module.vpc.vpc_id
  vswitch_id    = module.vpc.vswitch_id
  zone_id       = "cn-beijing-a"
  root_password = "your-root-password-123"
}

# 安装 Kubernetes,依赖 ECS
module "k8s-install" {
  source = "../../modules/k8s-install"

  ecs_public_ip = module.ecs.ecs_public_ip
  root_password = "your-root-password-123"

  depends_on = [module.ecs]  # 等 ECS 创建完成后再安装 K8s
}

# 部署 Nginx,依赖 Kubernetes 安装完成
module "deploy-nginx" {
  source = "../../modules/k8s-deploy-nginx"

  ecs_public_ip = module.ecs.ecs_public_ip
  root_password = "your-root-password-123"

  depends_on = [module.k8s-install]  # 等 K8s 安装完成后再部署应用
}

配置输出:

# environments/dev/outputs.tf

output "vpc_id" {
  value = module.vpc.vpc_id
}

output "ecs_public_ip" {
  value = module.ecs.ecs_public_ip
}

output "k8s_config" {
  value = "/root/.kube/config"
}

7、部署

当所有脚本开发完成后,为了保证后续的可扩展,整体的命令执行流程是:

# 切换到 dev 目录下
$ cd environments/dev
# 创建 dev workspace
$ terraform workspace new dev
# 初始化下载依赖
$ terraform init
# 预执行
$ terraform plan
# 执行部署
$ terraform apply

总结

综上所述,文章系统阐述了基础设施即代码(IaC)的核心概念、与AIOps的关联及显著优势,并聚焦IaC工具Terraform,深入解析其定义、架构、核心命令、状态管理、项目布局等关键内容,最后通过在阿里云平台部署VPC、ECS、K8S及Nginx的实战案例,完整呈现了Terraform在模块化和多环境支持下的应用流程。这不仅展现了IaC通过代码化管理基础设施的高效性与规范性,也为实际运维中利用Terraform实现自动化部署提供了清晰的思路与参考。

引用

[1] https://developer.hashicorp.com/terraform/docs
[2] https://help.aliyun.com/zh/terraform/what-is-terraform?spm=a2c4g.help-menu-95817.d_0_1.dee2c092b7ORVO