本文介绍如何将已有资源导入 Terraform,以及如何通过复制文件的方式创建新资源,完成跨地域复制。
已有资源导入到 Terraform
大部分刚接触 Terraform 的用户,可能在云上已经存在资源并期望将他们导入到 Terraform 中管理,Terraform 支持单个资源的导入,但是碰到多资源多实例的导入,则需要借助开源工具实现,以下介绍这两种场景的导入方法。
导入单个资源
Terraform 支持 Import 命令导入单个资源,格式
terraform import [资源类型].[名称] [入参]
。名称可以自定义,入参则是查询资源必要的字符串(一般为 ID ,部分资源是名字或者多字段组合)。以云服务器实例为例,通过查询 CVM Resource 文档 ,可知导入命令为:$ terraform import tencentcloud_instance.ins ins-jvu2hiw2 -allow-missing-config
其中
-allow-missing-config
表示允许本地不需要预先声明 block ,否则需要在文件中预先写一段 resource [资源类型].[名称] {}
这样的空块导入完成后,字段不会写入 TF 文件中,需要执行 terraform show
查看导入的资源代码:# tencentcloud_instance.ins:resource "tencentcloud_instance" "ins" {allocate_public_ip = trueavailability_zone = "ap-guangzhou-3"create_time = "2022-01-01T01:11:11Z"id = "ins-xxxxxxxx"image_id = "img-xxxxxxxx"instance_charge_type = "POSTPAID_BY_HOUR"instance_name = "xxxxxxxx"instance_status = "RUNNING"instance_type = "S3.MEDIUM2"internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR"internet_max_bandwidth_out = 1key_name = "skey-xxxxxxxx"private_ip = "10.0.1.1"project_id = 0public_ip = "1.1.1.1"running_flag = truesecurity_groups = ["sg-xxxxxxxx",]subnet_id = "subnet-xxxxxxxx"system_disk_id = "disk-xxxxxxxx"system_disk_size = 50system_disk_type = "CLOUD_PREMIUM"tags = {}vpc_id = "vpc-xxxxxxxx"}
resource "tencentcloud_instance" "ins" {allocate_public_ip = trueavailability_zone = "ap-guangzhou-3"# create_time = "2022-01-01T01:11:11Z"# id = "ins-xxxxxxxx"image_id = "img-xxxxxxxx"instance_charge_type = "POSTPAID_BY_HOUR"instance_name = "xxxxxxxx"# instance_status = "RUNNING"instance_type = "S3.MEDIUM2"internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR"internet_max_bandwidth_out = 1key_name = "skey-xxxxxxxx"private_ip = "10.0.1.1"project_id = 0# public_ip = "1.1.1.1"running_flag = truesecurity_groups = ["sg-xxxxxxxx",]subnet_id = "subnet-xxxxxxxx"system_disk_id = "disk-xxxxxxxx"system_disk_size = 50system_disk_type = "CLOUD_PREMIUM"tags = {}vpc_id = "vpc-xxxxxxxx"}
使用 Terraformer 批量导入
通过上文可以看到使用 Terraform 的导入相当繁琐,仅适合导入少量资源。您可能需要借助 Terraformer 进行批量导入。Terraformer 是一个属于 GoogleCloudPlatform 的命令行工具,可以把账号下大部分云资源标记并导入为 TF 文件。
1. 安装
$ brew install terraformer
2. 执行导入命令,假如我想导入腾讯云广州区下所有的 CVM 和 VPC 资源,那么命令格式如下:
terraformer import tencentcloud --resources="vpc,cvm" --regions=ap-guangzhou
命令执行完成后,Terraformer 默认将导入的资源文件写入
./generated
目录,示例如下:.└── tencentcloud├── cvm│ └── ap-guangzhou│ ├── instance.tf│ ├── key_pair.tf│ ├── outputs.tf│ ├── provider.tf│ ├── terraform.tfstate│ └── variables.tf└── vpc└── ap-guangzhou├── outputs.tf├── provider.tf├── terraform.tfstate└── vpc.tf
3. 换源:TencentCloudProvider 由我们腾讯云维护而非 Terraform 官方,需要在生成的
provider.tf
中添加 source
字段,值为 tencentcloudstack/tencentcloud
。provider "tencentcloud" {version = "~> 1.77.11"}terraform {required_providers {tencentcloud = {source = "tencentcloudstack/tencentcloud" # 添加 source 以指定命名空间version = "~> 1.77.11"}}}
跨地域复制
实现原理
一个简单的 Terraform 工作目录结构如下:
.├── .terraform│ └── providers # 引用到的 Provider├── .terraform.lock.hcl # Provider 锁版本├── main.tf # TF 文件├── vars.tf # TF 文件├── outputs.tf # TF 文件└── terraform.tfstate # 状态文件
而本地的工作目录跟腾讯云资源映射结构如下图所示:
当用户执行
terraform apply
命令且部署完成后。会生成 terraform.tfstate
文件。它是一份 JSON 格式的文件,默认存储在本地,或者配置在远端存储桶中(需要配置 Backend)用来描述 TF 声明的资源和真实云资源的映射关系。如果本地目录或 Backend 中不存在 terraform.tfstate
,或者该文件没有写入云资源数据,Terraform 就会认为资源没有被部署,执行 apply
会进行资源创建操作。示例:TKE Serverless 集群跨地域复制
只要没有
tfstate
映射的资源声明都视为创建。基于这一思路,我们通过复制文件并修改地域的方法,再执行 apply
即可完成资源跨地域复制。假设我们已经通过 Terraform 在广州部署了一套基于 Serverless 集群服务的应用,目录如下:
eks-app-guangzhou├── crds.tf├── infra.tf├── main.tf├── terraform.log└── terraform.tfstate
其中
main.tf
指定 Terraform 和 Provider 的元信息。代码如下:terraform {required_providers {tencentcloud = {source = "tencentcloudstack/tencentcloud"}}}provider "tencentcloud" {region = "ap-guangzhou"}
infra.tf
指定 TKE Serverless 集群和所需的资源:VPC、子网、安全组、TKE Serverless 集群、负载均衡。代码如下:# 服务对外放通测试 IP 地址variable "accept_ip" {description = "Use EnvVar: $TF_VAR_accept_ip instead"}resource "tencentcloud_vpc" "vpc" {name = "eks-vpc"cidr_block = "10.2.0.0/16"}resource "tencentcloud_subnet" "sub" {vpc_id = tencentcloud_vpc.vpc.idname = "eks-subnet"cidr_block = "10.2.0.0/20"availability_zone = "ap-guangzhou-3"}resource "tencentcloud_security_group" "sg" {name = "eks-sg"}resource "tencentcloud_security_group_lite_rule" "sgr" {security_group_id = tencentcloud_security_group.sg.idingress = ["ACCEPT#10.2.0.0/16#ALL#ALL","ACCEPT#${var.accept_ip}#ALL#ALL"]}resource "tencentcloud_eks_cluster" "foo" {cluster_name = "tf-test-eks"k8s_version = "1.20.6"vpc_id = tencentcloud_vpc.vpc.idsubnet_ids = [tencentcloud_subnet.sub.id,]cluster_desc = "test eks cluster created by terraform"service_subnet_id = tencentcloud_subnet.sub.idenable_vpc_core_dns = trueneed_delete_cbs = truepublic_lb {enabled = truesecurity_policies = [var.accept_ip]}internal_lb {enabled = truesubnet_id = tencentcloud_subnet.sub.id}}resource "tencentcloud_clb_instance" "ingress-lb" {address_ip_version = "ipv4"clb_name = "example-lb"internet_bandwidth_max_out = 1internet_charge_type = "BANDWIDTH_POSTPAID_BY_HOUR"load_balancer_pass_to_target = truenetwork_type = "OPEN"security_groups = [tencentcloud_security_group.sg.id]vpc_id = tencentcloud_vpc.vpc.id}
crds.tf
指定基于 TKE Serverless 集群的 CRD。代码如下:locals {kubeconfig = yamldecode(tencentcloud_eks_cluster.foo.kube_config)}provider "kubernetes" {host = local.kubeconfig.clusters[0].cluster.servercluster_ca_certificate = base64decode(local.kubeconfig.clusters[0].cluster["certificate-authority-data"])client_key = base64decode(local.kubeconfig.users[0].user["client-key-data"])client_certificate = base64decode(local.kubeconfig.users[0].user["client-certificate-data"])}resource "kubernetes_namespace" "test" {metadata {name = "nginx"}}resource "kubernetes_deployment" "test" {metadata {name = "nginx"namespace = kubernetes_namespace.test.metadata.0.name}spec {replicas = 2selector {match_labels = {app = "MyTestApp"}}template {metadata {labels = {app = "MyTestApp"}}spec {container {image = "nginx"name = "nginx-container"port {container_port = 80}}}}}}resource "kubernetes_service" "test" {metadata {name = "nginx"namespace = kubernetes_namespace.test.metadata.0.name}spec {selector = {app = kubernetes_deployment.test.spec.0.template.0.metadata.0.labels.app}type = "NodePort"port {node_port = 30201port = 80target_port = 80}}}resource "kubernetes_ingress_v1" "test" {metadata {name = "test-ingress"namespace = "nginx"annotations = {"ingress.cloud.tencent.com/direct-access" = "false""kubernetes.io/ingress.class" = "qcloud""kubernetes.io/ingress.existLbId" = tencentcloud_clb_instance.ingress-lb.id"kubernetes.io/ingress.extensiveParameters" = "{\\"AddressIPVersion\\": \\"IPV4\\"}""kubernetes.io/ingress.http-rules" = "[{\\"path\\":\\"/\\",\\"backend\\":{\\"serviceName\\":\\"nginx\\",\\"servicePort\\":\\"80\\"}}]""kubernetes.io/ingress.https-rules" = "null""kubernetes.io/ingress.qcloud-loadbalance-id" = tencentcloud_clb_instance.ingress-lb.id"kubernetes.io/ingress.rule-mix" = "false"}# selfLink = "/apis/networking.k8s.io/v1/namespaces/nginx/ingresses/test-ingress"}spec {rule {http {path {backend {service {name = kubernetes_service.test.metadata.0.nameport {number = 80}}}path = "/"}}}}}
如果想要将这些资源复制一份到其他地域(以新加坡为例),那么可以执行以下步骤:
1. 复制该目录下的所有 .tf 文件到新的目录下,如
eks-app-singapore
,断开原目录的 tfstate
引用:$ mkdir ../eks-app-singapore$ cp *.tf ../eks-app-singapore$ cd ../eks-app-singapore
2. 修改 TencentCloud Provider 的地域。代码如下:
provider "tencentcloud" {# - replace# region = "ap-guangzhou"# + toregion = "ap-singapore"}
3. 在新目录
eks-app-singapore
下执行 terraform init
和 terraform plan
。由于没有 tfstate
文件, plan 提示即将创建新的资源:Plan: 11 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.
4. 确认无误后,执行
terraform apply
即可配置新目录下的云资源在新地域管理。局限性
不同产品的业务形态和逻辑差异较大,导致跨地域复制也是一个比较繁琐的操作。主要限制如下:
实例规格和库存限制
如云服务器、云硬盘、云数据库等实例的资源,各个可用区的实例规格和库存差异较大,很可能出现当前实例规格在其他区域售罄或者不可用的情况,建议使用动态的实例类型,即查询各个资源的 datasource 查询可用的实例规格而非硬编码在文件中,例如:
在上海四区购买2核2G的 CVM 实例。代码如下:
resource "tencentcloud_instance" "cvm" {name = "my-instance"availability_zone = "ap-shanghai-4"image_id = "local.cvm_img_id"instance_type = "S5.MEDIUM2"}
切换到广州地域,替换成 datasource 动态获取。代码如下:
provider "tencentcloud" {region = "ap-guangzhou"}# 查询广州地域 CVM 有哪些可用区data "tencentcloud_availability_zones_by_product" "cvm" {product = "cvm"}# 查询以 Tencent 开头的 CVM 镜像data "tencentcloud_images" "img" {image_name_regex = "Tencent"}# 查询指定可用区下 2 核 2G 有哪些实例类型data "tencentcloud_instance_types" "types" {availability_zone = data.tencentcloud_availability_zones_by_product.cvm.zones.0.namecpu_core_count = 2memory_size = 2}locals {# 挑选第可用区列表的第一个结果cvm_zone = data.tencentcloud_availability_zones_by_product.cvm.zones.0.name# 挑选镜像列表的第一个结果cvm_img_id = data.tencentcloud_images.img.images.0.image_id# 挑选实例类型的第一个结果cvm_type = data.tencentcloud_instance_types.types.instance_types.0.instance_type}resource "tencentcloud_instance" "cvm" {name = "my-instance"availability_zone = local.cvm_zoneimage_id = local.cvm_img_idinstance_type = local.cvm_type}
资源数量限制
不需要复制的资源
有些资源本身没有地域属性,例如 CAM 用户/角色和策略、SSL 证书、SSH 密钥等。这些资源在进行整体复制操作时需要过滤掉,避免重复创建。