找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5590

积分

0

好友

777

主题
发表于 昨天 16:26 | 查看: 7| 回复: 0

问题背景

很多公司过去几年里一步步从单云走向多云,原因各有不同:

  • 业务出海,需要在多个地域部署。
  • 监管要求数据在境内保存,但海外用户多,需要海外资源。
  • 单一云厂商出过故障,迫使做灾备。
  • 不同业务用不同云服务(AWS 的 Lambda、阿里云的 ACK、Azure 的 AAD),整合管理。
  • 公司收购,IT 资产在不同云上。

但多云管理是个大坑:

  • 阿里云 ECS、AWS EC2、Azure VM 各有各的 API,各有各的 SDK。
  • IAM 体系不同,权限模型不同。
  • 资源状态没有统一视图,盘点困难。
  • 不同云的同名概念含义不同(VPC 在 AWS 和 Azure 里范围不一样)。
  • 手工创建的资源没人记得,改了哪个、删了哪个都对不上账。
  • 出了安全事件需要排查所有云上的资源,散落在多个控制台。

Terraform 是目前最主流的多云基础设施即代码(IaC)工具。阿里云、AWS、Azure、GCP、腾讯云、华为云都提供官方或社区的 Terraform Provider。这篇文章讲清楚怎么用 Terraform 统一管理多云资源,避开常见陷阱,建立可维护的 IaC 项目。

适用读者

  • 负责多云架构的运维工程师、SRE、DevOps。
  • 想从手工控制台 / 自研脚本迁移到 IaC 的同学。
  • 维护 Terraform 项目、解决漂移 / 状态冲突 / 漂移检测的工程师。
  • 准备把 Terraform 集成到 CI/CD 流水线的同学。

适用场景

  • 中大规模基础设施(10~10000 个云资源)。
  • 多云或混合云(AWS + 阿里云 + 私有 K8s)。
  • 应用、数据库、网络、监控的代码化管理。
  • 与 Jenkins、GitLab CI、GitHub Actions、Argo CD 配合的 GitOps 流水线。

核心知识点

Terraform 是什么

Terraform 是 HashiCorp 公司开发的基础设施即代码工具,用声明式 HCL(HashiCorp Configuration Language)描述"我要什么资源",然后由 Terraform 协调云厂商 API 把现实拉到这个状态。

关键概念:

  • Provider:对接云厂商 / 服务商的插件,例如 awsalicloudazurermgoogletencentcloud
  • Resource:要管理的资源,例如 aws_instancealicloud_vpc
  • Data Source:查询已有资源,例如 aws_amialicloud_zones
  • State:Terraform 维护的"实际状态"文件,对比期望状态和实际状态。
  • Plan:根据期望和状态算出要做的操作(create / read / update / delete)。
  • Apply:执行 plan,把实际状态推到期望状态。
  • Module:可复用的 Terraform 代码单元。

Terraform 生命周期

1. 写代码(HCL)       →  *.tf 文件
2. terraform init      → 下载 provider / module
3. terraform plan      → 算出 diff
4. terraform apply     → 推送到云
5. terraform destroy   → 销毁(慎用)
6. terraform state     → 操作 state 文件
7. terraform import    → 把已有资源纳入管理

安装

# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Ubuntu / Debian
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | \
  gpg --dearmor | \
  sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install terraform

# 验证
terraform version

多云场景的核心挑战

  1. Provider 差异:每个云有自己的资源模型,跨云抽象困难。
  2. 身份认证:每个云的 AK/SK、IAM Role 都不一样。
  3. State 同步:多云资源在同一个 state 里,要考虑一致性。
  4. 网络打通:跨云网络(VPC Peering、VPN、SD-WAN)需要规划。
  5. 统一监控:跨云指标、日志、告警需要统一平台。
  6. 合规:不同云合规要求不同,数据驻留、加密、审计。
  7. 成本管理:多云账单合并分析。

Terraform 解决的是"用统一的语言描述资源",但具体落地仍然要懂每个云。

实战一:第一个多云 Terraform 项目

项目结构

terraform-multi-cloud/
├── main.tf                  # 入口
├── versions.tf              # provider / terraform 版本
├── variables.tf             # 输入变量
├── outputs.tf               # 输出
├── terraform.tfvars         # 变量值(不进版本控制)
├── backend.tf               # state 后端
├── providers/
│   ├── aws.tf
│   ├── alicloud.tf
│   └── azurerm.tf
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── README.md
│   ├── ecs/
│   └── rds/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── production/
└── .gitignore

versions.tf

# versions.tf
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.200"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    tencentcloud = {
      source  = "tencentcloudstack/terraform-provider-tencentcloud"
      version = "~> 1.80"
    }
  }
}

provider 配置

# providers/aws.tf
provider "aws" {
  region = "ap-southeast-1"

  default_tags {
    Environment = "production"
    ManagedBy   = "terraform"
    Project     = "myapp"
  }
}

# providers/alicloud.tf
provider "alicloud" {
  region = "cn-hangzhou"

  profile = "default"
}

# providers/azurerm.tf
provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
}

# providers/google.tf
provider "google" {
  project = var.gcp_project_id
  region  = "asia-southeast1"
}

风险提示:每个 provider 都需要认证信息,不要把 AK/SK 写进代码。建议用环境变量、CI secret、Vault。

后端配置

# backend.tf
terraform {
  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "multi-cloud/terraform.tfstate"
    region         = "ap-southeast-1"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:ap-southeast-1:111122223333:key/abcd-1234"
    dynamodb_table = "terraform-lock"
  }
}

不同云的后端:

# 阿里云 OSS 后端
terraform {
  backend "oss" {
    bucket   = "myorg-terraform-state"
    key      = "multi-cloud/terraform.tfstate"
    region   = "cn-hangzhou"
    prefix   = "terraform/state"
    encrypt  = true
  }
}

# 腾讯云 COS 后端
terraform {
  backend "cos" {
    secret_id  = var.tencent_secret_id
    secret_key = var.tencent_secret_key
    region     = "ap-shanghai"
    bucket     = "myorg-terraform-state-1234567890"
    prefix     = "terraform/state"
    key        = "multi-cloud/terraform.tfstate"
  }
}

变量

# variables.tf
variable "environment" {
  description = "环境名"
  type        = string
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "环境必须是 dev、staging 或 production。"
  }
}

variable "aws_region" {
  description = "AWS 区域"
  type        = string
  default     = "ap-southeast-1"
}

variable "alicloud_region" {
  description = "阿里云区域"
  type        = string
  default     = "cn-hangzhou"
}

variable "instance_type" {
  description = "ECS/EC2 实例规格"
  type        = string
  default     = "ecs.t5.large"
}

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

  validation {
    condition     = can(cidrnetmask(var.vpc_cidr))
    error_message = "VPC CIDR 必须是合法的 CIDR。"
  }
}

变量值文件(每个环境一个):

# environments/production/terraform.tfvars
environment     = "production"
aws_region      = "ap-southeast-1"
alicloud_region = "cn-hangzhou"
instance_type   = "ecs.g6.xlarge"
vpc_cidr        = "10.0.0.0/16"

风险提示:tfvars 文件可能含敏感信息(数据库密码、IP 白名单),建议不进 Git,用 CI secret 注入。

创建 VPC(多云)

# modules/vpc/main.tf
variable "vpc_cidr" {
  type        = string
  description = "VPC CIDR"
}

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

variable "environment" {
  type        = string
  description = "环境"
}

# AWS VPC
resource "aws_vpc" "this" {
  cidr_block = var.vpc_cidr

  tags = {
    Name        = "${var.vpc_name}-aws"
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  count             = 3
  vpc_id            = aws_vpc.this.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "${var.vpc_name}-public-${count.index + 1}"
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

# 阿里云 VPC
resource "alicloud_vpc" "this" {
  vpc_name   = "${var.vpc_name}-aliyun"
  cidr_block = var.vpc_cidr
}

resource "alicloud_vswitch" "public" {
  count        = 3
  vpc_id       = alicloud_vpc.this.id
  cidr_block   = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  zone_id      = data.alicloud_zones.available.zones[count.index].id
  vswitch_name = "${var.vpc_name}-public-${count.index + 1}"
}

data "alicloud_zones" "available" {
  available_resource_creation = "VSwitch"
}

output "aws_vpc_id" {
  value = aws_vpc.this.id
}

output "alicloud_vpc_id" {
  value = alicloud_vpc.this.id
}

创建 ECS / EC2

# modules/ecs/main.tf
variable "instance_type" {
  type = string
}

variable "vpc_id" {
  type = string
}

variable "subnet_ids" {
  type = list(string)
}

variable "image_id" {
  type = string
}

variable "count" {
  type    = number
  default = 2
}

# AWS EC2
resource "aws_instance" "this" {
  count         = var.count
  ami           = var.image_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_ids[count.index % length(var.subnet_ids)]

  vpc_security_group_ids = [aws_security_group.this.id]

  tags = {
    Name = "web-${count.index + 1}"
  }
}

resource "aws_security_group" "this" {
  name        = "web-sg"
  description = "Allow HTTP and SSH"
  vpc_id      = var.vpc_id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

编排入口

# environments/production/main.tf
module "vpc" {
  source = "../../modules/vpc"

  vpc_cidr    = var.vpc_cidr
  vpc_name    = "prod"
  environment = var.environment
}

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

  instance_type = var.instance_type
  vpc_id        = module.vpc.aws_vpc_id
  subnet_ids    = module.vpc.aws_subnet_ids
  image_id      = data.aws_ami.ubuntu.id
  count         = 5
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

output "ec2_public_ips" {
  value = module.ecs.public_ips
}

跑起来

cd environments/production
terraform init
terraform plan -out=tfplan
# 检查 plan 输出,确认要创建/改/删的资源符合预期
terraform apply tfplan

风险提示:第一次跑前必须用 terraform plan 看清要做的操作。apply 前再加一次 --dry-run 或者让同事帮忙 review plan 输出。

实战二:State 管理

State 是什么

State 是 Terraform 记录"上次 apply 后实际状态"的文件。每次 apply 之后,Terraform 会更新 state 与云上资源保持一致。

State 存在哪里、谁能访问、是分布式还是本地,关系到协作能力和安全。

远程后端选择

后端 适合 特点
Local 个人开发 简单,团队不能用
S3 AWS 生态 标配,DynamoDB 加锁
OSS 阿里云 国产云首选
COS 腾讯云 国产云
GCS GCP 谷歌云
Azure Storage Azure 微软云
Terraform Cloud 跨云 HashiCorp 官方,付费
Consul 自建 老方案,HashiCorp 维护
etcd v3 自建 复杂,不推荐
HTTP 简单 自建服务器存

S3 + DynamoDB 后端

# 创建 S3 bucket 和 DynamoDB 表
resource "aws_s3_bucket" "terraform_state" {
  bucket = "myorg-terraform-state"

  lifecycle {
    prevent_destroy = true  # 防止误删
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.terraform_state.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

resource "aws_kms_key" "terraform_state" {
  description             = "KMS key for Terraform state"
  deletion_window_in_days = 30
}

State 命令

# 列出 state 中的资源
terraform state list

# 查看某个资源
terraform state show aws_instance.web[0]

# 移动资源(在 state 中重命名)
terraform state mv aws_instance.old aws_instance.new

# 移除资源(不销毁云上资源)
terraform state rm aws_instance.web

# 导入已有资源
terraform import aws_instance.web i-1234567890abcdef0

State 锁

并发 apply 同一个 state 会冲突。S3 后端用 DynamoDB 表做锁,OSS 后端用 OSS 自带锁。开启后,同一时间只能有一个 apply 在跑。

如果 apply 中途失败(比如网络断),锁可能没释放,会阻塞后续操作:

# 查看锁
terraform force-unlock <LOCK_ID>

风险提示:force-unlock 是高风险操作,确认没有其他人正在 apply 才能执行。

实战三:模块化设计

模块基础

模块是 Terraform 的复用单元。source 可以是本地路径、Git 地址、Terraform Registry。

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

  vpc_cidr = "10.0.0.0/16"
  vpc_name = "prod"
}
module "consul" {
  source  = "hashicorp/consul/aws"
  version = "~> 0.3"

  servers = 3
}
module "github_repo" {
  source = "git@github.com:myorg/terraform-modules.git//vpc?ref=v1.0.0"
}

模块版本约束

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"  # >= 5.0, < 6.0
}

version 是强烈建议。ref 锁定 Git tag 能保证可复现。

模块输入输出

# modules/vpc/variables.tf
variable "vpc_cidr" {
  type        = string
  description = "VPC CIDR"
}

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

# modules/vpc/outputs.tf
output "vpc_id" {
  value       = aws_vpc.this.id
  description = "VPC ID"
}

output "subnet_ids" {
  value       = aws_subnet.public
  • .id   description = "Public subnet IDs" }
  • 模块组合

    module "vpc" {
      source = "./modules/vpc"
      ...
    }
    
    module "web" {
      source = "./modules/web"
      vpc_id = module.vpc.vpc_id
      subnet_ids = module.vpc.subnet_ids
      ...
    }

    多云资源在一个 module 里

    # modules/web/main.tf
    resource "aws_lb" "aws_web" {
      name               = "web-alb"
      internal           = false
      load_balancer_type = "application"
      security_groups    = [var.aws_security_group_id]
      subnets            = var.aws_subnet_ids
    }
    
    resource "alicloud_slb" "aliyun_web" {
      name          = "web-slb"
      address_type  = "internet"
      internet      = true
      vswitch_id    = var.alicloud_vswitch_id
      bandwidth     = 5
    }

    但要谨慎:多云资源耦合在一起会让 state 变大、apply 变慢。建议多云资源分模块,按云或按业务域拆分。

    实战四:多环境管理

    方案 A:目录分层

    environments/
    ├── dev/
    │   ├── main.tf
    │   └── terraform.tfvars
    ├── staging/
    │   ├── main.tf
    │   └── terraform.tfvars
    └── production/
        ├── main.tf
        └── terraform.tfvars

    每个环境独立 state、独立 plan、独立 apply。

    cd environments/production
    terraform init
    terraform apply

    优点:环境间完全隔离,state 独立。缺点:环境多了要切目录、init 多次。

    方案 B:Workspace

    terraform workspace new dev
    terraform workspace new staging
    terraform workspace new production
    terraform workspace select production
    terraform apply

    Workspace 把 state 按目录存到同一个后端,前缀是 env:/<workspace_name>

    优点:不用切目录。缺点:环境间差异不直观、容易误操作、不适合大差异(不同 region、不同云)。

    方案 C:Terragrunt

    Terragrunt 是 Terraform 的 wrapper,提供更复杂的多环境管理(DRY 配置、依赖管理、远程 state 自动化)。

    # terragrunt.hcl
    include "root" {
      path = find_in_parent_folders()
    }
    
    terraform {
      source = "../../modules//vpc"
    }
    
    inputs = {
      vpc_cidr = "10.0.0.0/16"
      vpc_name = "prod"
    }
    terragrunt init
    terragrunt plan
    terragrunt apply

    优势:

    • 多模块共享配置(remote state、provider)。
    • 自动管理依赖(module 间依赖)。
    • terragrunt run-all plan 一键跑多个模块。

    学习曲线略陡,但大项目必备。

    实战五:流水线集成

    典型流程

    Git Push → CI 触发 → terraform init → terraform plan → 输出 plan 给 review → merge → terraform apply

    GitHub Actions

    # .github/workflows/terraform.yml
    name: Terraform
    on:
      pull_request:
        branches: [main]
      push:
        branches: [main]
    
    jobs:
      terraform:
        name: Terraform
        runs-on: ubuntu-latest
    
        steps:
        - uses: actions/checkout@v4
    
        - name: Setup Terraform
          uses: hashicorp/setup-terraform@v3
          with:
            terraform_version: 1.6.6
    
        - name: Terraform Init
          run: terraform init
          working-directory: environments/production
    
        - name: Terraform Plan
          id: plan
          run: terraform plan -no-color -out=tfplan
          working-directory: environments/production
          env:
            AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
            AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
        - name: Show Plan
          if: github.event_name == 'pull_request'
          run: terraform show -no-color tfplan
          working-directory: environments/production
    
        - name: Terraform Apply
          if: github.ref == 'refs/heads/main' && github.event_name == 'push'
          run: terraform apply -auto-approve tfplan
          working-directory: environments/production
          env:
            AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
            AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

    风险提示:CI 上 apply 是高风险操作,建议加手动审批(environment: production + when: manual)。

    GitLab CI

    # .gitlab-ci.yml
    stages:
    - validate
    - plan
    - apply
    
    terraform:validate:
      stage: validate
      image: hashicorp/terraform:1.6.6
      script:
      - cd environments/production
      - terraform init -backend=false
      - terraform validate
      - terraform fmt -check
    
    terraform:plan:
      stage: plan
      image: hashicorp/terraform:1.6.6
      script:
      - cd environments/production
      - terraform init
      - terraform plan -out=tfplan
      - terraform show -no-color tfplan > plan.txt
      artifacts:
        paths:
        - environments/production/tfplan
        expire_in: 1 day
      environment:
        name: production
      rules:
      - if: $CI_MERGE_REQUEST_ID
    
    terraform:apply:
      stage: apply
      image: hashicorp/terraform:1.6.6
      script:
      - cd environments/production
      - terraform init
      - terraform apply -auto-approve tfplan
      environment:
        name: production
      rules:
      - if: $CI_COMMIT_BRANCH == "main"
        when: manual

    Atlantis

    Atlantis 是 Terraform 专用 CI 工具,通过 PR 评论触发 plan/apply:

    # atlantis.yaml
    version: 3
    projects:
    - name: production
      dir: environments/production
      workspace: production
      terraform_version: 1.6.6
      apply_requirements:
      - approved
      - mergeable
      workflow: terraform-workflow
    workflows:
      terraform-workflow:
        plan:
          steps:
          - init
          - plan
        apply:
          steps:
          - apply

    在 PR 上评论:

    atlantis plan
    atlantis apply

    Atlantis 会自动评论 plan 输出,需要 maintainer 加 atlantis approve 才能 apply。

    Spacelift / Env0

    商业化 Terraform 平台,提供 SaaS / Private 部署。功能齐全:policy as code、drift detection、cost estimation、并行执行。

    实战六:漂移检测

    什么是漂移

    "漂移(Drift)"是 Terraform 期望状态和云上实际状态不一致。可能因为:

    • 有人在控制台手工改了资源。
    • 自动化脚本(非 Terraform)改资源。
    • 资源被云厂商主动调整(比如磁盘扩容)。
    • 资源被外部删除。

    漂移会导致 Terraform 状态不准确,下次 plan 会显示 reconcile 操作。

    漂移检测方法

    方法 1:定时 plan
    # 加到 cron,每天跑一次 plan
    0 2 * * * cd /path/to/project && terraform plan -detailed-exitcode -out=tfplan || (echo "drift detected"; terraform show -no-color tfplan | mail -s "drift" ops@example.com)

    -detailed-exitcode

    • 0:无 diff
    • 1:执行出错
    • 2:有 diff,需要 apply
    方法 2:Driftctl

    Driftctl 是专门做漂移检测的开源工具,比 terraform plan 更专业,能并行检测大量资源:

    # 安装
    curl -L "$(curl -s https://api.github.com/repos/snyk/driftctl/releases/latest | grep -o 'https://.*driftctl_linux_amd64' | head -1)" -o driftctl
    chmod +x driftctl
    sudo mv driftctl /usr/local/bin/
    
    # 扫描
    driftctl scan --to aws+tf
    
    # 输出 JSON
    driftctl scan --to aws+tf --output json://drift.json
    
    # 上传到 S3 / 发 Slack 通知

    Driftctl 还能识别"已删除"和"未管理"的资源。

    方法 3:Spacelift / Env0

    商业化平台自带漂移检测。

    方法 4:refresh-only plan

    Terraform 1.5+ 提供 refresh-only plan:

    terraform plan -refresh-only

    只刷新 state,不做 create/update/delete。用来识别"什么资源实际状态变了"。

    处理漂移

    发现漂移后,处理方式:

    • 真漂移(应该恢复到 Terraform 期望):terraform apply
    • 云上调整(云厂商主动扩容):更新 Terraform 代码反映新状态。
    • 真删除(云上资源应该被删):从 state 中移除。
    • 非预期修改:人工 review 决定。

    实战七:常见陷阱

    陷阱 1:把密码写进代码

    resource "aws_db_instance" "this" {
      password = "MyPassword123"  # 错!会被 Git 记录
    }

    正确做法:

    resource "aws_db_instance" "this" {
      password = var.db_password  # 从变量读
    }
    
    # variables.tf
    variable "db_password" {
      type      = string
      sensitive = true
    }
    # 用环境变量
    export TF_VAR_db_password="xxx"
    terraform apply

    或者用 data "aws_ssm_parameter" 读 AWS Parameter Store / Secrets Manager / 阿里云 KMS。

    陷阱 2:循环依赖

    resource "aws_security_group" "web" {
      ingress {
        cidr_blocks = [aws_instance.bastion.private_ip]  # 反向依赖
      }
    }
    
    resource "aws_instance" "bastion" {
      vpc_security_group_ids = [aws_security_group.web.id]  # 正向依赖
    }

    正确做法:解耦,安全组规则用 CIDR 块而不是 IP。

    陷阱 3:State 锁死

    现象:Error: Error acquiring the state lock

    原因:上一次 apply 没正常结束,锁没释放。

    处理:

    # 确认没有其他人正在 apply
    terraform force-unlock <LOCK_ID>

    风险提示:force-unlock 是高危操作,必须先确认。

    陷阱 4:误删资源

    terraform destroy

    会删除所有管理的资源。生产环境必须用 -target 限定范围或 -var 切换环境。

    风险提示:永远不要在生产跑 terraform destroy。如果要下线环境,先在测试环境试一遍,限制 IAM 权限加 MFA。

    陷阱 5:漂移导致 apply 失败

    # 现象
    Error: Provider produced inconsistent final plan

    资源在 apply 中被外部改了。解决:

    # 重新 plan
    terraform plan
    # 看 diff,refresh state,再 apply
    terraform apply

    陷阱 6:Provider 版本不兼容

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

    升级 provider 后资源字段可能变。先在测试环境升级,看 plan 输出。

    陷阱 7:模块地狱

    modulemodulemodule,每个 module 有大量 input,最后没人能改。

    经验法则:

    • 最多 3 层嵌套。
    • module inputs ≤ 10 个。
    • module outputs 越少越好。
    • 业务模块和基础设施模块分开。

    陷阱 8:多云网络误配置

    # 错:把两个云的资源放在同一 module 里,没考虑网络隔离
    resource "aws_instance" "this" {
      vpc_security_group_ids = [aws_security_group.merged.id]
    }
    
    resource "alicloud_instance" "this" {
      security_groups = [alicloud_security_group.merged.id]
    }

    跨云网络是独立的,强行合并配置会增加耦合。

    陷阱 9:IAM 权限过宽

    # 错:给 Terraform 用 root 账号
    provider "aws" {
      access_key = "AKIA..."  # root user
      secret_key = "..."
    }

    正确做法:给 Terraform 创建专用 IAM 用户 / 角色,权限最小化。

    陷阱 10:日志泄露敏感信息

    # 错:CI 输出日志
    terraform apply
    # 看到 plan 输出里包含明文密码

    正确做法:

    variable "db_password" {
      type      = string
      sensitive = true  # 标记为敏感
    }

    sensitive = true 后,Terraform 默认不在 plan/apply 输出中显示这个变量值。

    实战八:迁移现有资源到 Terraform

    方法 1:terraform import

    # 把已有 AWS EC2 导入
    terraform import aws_instance.web i-1234567890abcdef0
    
    # 阿里云 ECS
    terraform import alicloud_instance.web i-bp1234567890abcdef0

    导入后,Terraform 接管该资源。下次 plan 时会显示资源的当前状态(不是 Terraform 代码写的状态)。

    风险提示:导入只能导入已有资源,要让 Terraform 真的不重新创建,需要把资源的当前属性写到 Terraform 代码里。

    方法 2:Terraformer

    Terraformer 是 Google 出的工具,能自动从云上扫描资源并生成 Terraform 代码:

    # 安装
    brew install terraformer
    
    # 扫描 AWS
    terraformer import aws --resources=vpc,subnet,ec2 --regions=ap-southeast-1
    
    # 扫描阿里云
    terraformer import alicloud --resources=vpc,vswitch,ecs --regions=cn-hangzhou

    生成的代码放在 generated/ 目录。

    方法 3:手动写 + moved 块

    Terraform 1.1+ 提供 moved 块,资源重命名时不需要 destroy/import:

    # 老资源地址
    moved {
      from = aws_instance.old_name
      to   = aws_instance.new_name
    }

    实战九:跨云灾备

    案例:阿里云主 + AWS 备

    # 主集群在阿里云
    module "primary_aliyun" {
      source = "./modules/web"
      cloud  = "alicloud"
      ...
    }
    
    # 备集群在 AWS
    module "dr_aws" {
      source = "./modules/web"
      cloud  = "aws"
      ...
    }
    
    # 跨云数据同步
    resource "alicloud_oss_bucket" "dr_source" {
      bucket = "myorg-dr-source"
    }
    
    resource "aws_s3_bucket" "dr_target" {
      bucket = "myorg-dr-target"
    }
    
    resource "aws_s3_bucket_replication_configuration" "dr" {
      bucket = aws_s3_bucket.dr_target.id
      role   = aws_iam_role.replication.arn
    
      rule {
        id     = "dr-rule"
        status = "Enabled"
    
        destination {
          bucket        = aws_s3_bucket.dr_target.arn
          storage_class = "STANDARD_IA"
        }
      }
    }

    跨云灾备的关键:

    • 数据同步(OSS → S3、PolarDB → RDS、Redis AOF)。
    • 健康检查 + DNS 切换(Route 53 / 阿里云 DNS)。
    • 演练(季度 / 半年一次)。

    实战十:Policy as Code(Sentinel / OPA)

    商业版 Terraform Cloud / Enterprise 提供 Sentinel 策略引擎。开源方案是 OPA + Conftest。

    # policy/s3_public.rego
    package terraform.s3
    
    deny[msg] {
      resource := input.resource.aws_s3_bucket[name]
      resource.acl == "public-read"
      msg := sprintf("S3 bucket '%s' 不能是 public-read", [name])
    }
    
    deny[msg] {
      resource := input.resource.aws_s3_bucket[name]
      resource.acl == "public-read-write"
      msg := sprintf("S3 bucket '%s' 不能是 public-read-write", [name])
    }
    # 用 conftest 验证
    conftest test plan.json --policy policy/

    CI 流水线里加这一步骤,违规就阻断。

    实战十一:成本管理

    成本估算

    Terraform Cloud / Infracost 提供 cost estimation:

    # 安装 Infracost
    curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/packages/cli/docker/install.sh | sh
    
    # 生成 cost report
    infracost breakdown --path=environments/production --format=json --out-file=infracost.json
    infracost output --path=infracost.json --format=table

    CI 集成 Infracost,每次 PR 显示 cost 变化:

    # GitHub Actions
    - name: Infracost
      uses: infracost/actions/setup@v2
      with:
        api-key: ${{ secrets.INFRACOST_API_KEY }}
    
    - name: Generate cost estimate
      run: |
        infracost breakdown --path=environments/production --format=json --out-file=/tmp/infracost.json
        infracost comment gitlab --path=/tmp/infracost.json --repo=$GITHUB_REPOSITORY --pull-request=$PR_NUMBER --behavior=update --token=$GITHUB_TOKEN

    标签规范

    locals {
      common_tags = {
        Environment = var.environment
        ManagedBy   = "terraform"
        Project     = "myapp"
        CostCenter  = "engineering"
        Owner       = "ops-team"
      }
    }
    
    # AWS
    provider "aws" {
      default_tags {
        tags = local.common_tags
      }
    }
    
    # 阿里云
    resource "alicloud_vpc" "this" {
      vpc_name = "prod"
      tags = local.common_tags
    }

    标签让账单分析、权限管理、自动化都更容易。

    实战十二:实战案例:完整的多云项目

    背景

    某 SaaS 公司:AWS 主力(数据库 + 核心服务)、阿里云辅助(中国用户)、Cloudflare 全球 CDN。

    项目结构

    infra/
    ├── modules/
    │   ├── vpc/                    # 通用 VPC
    │   ├── ecs/                    # 通用 VM
    │   ├── rds/                    # 通用数据库
    │   ├── eks/                    # AWS K8s
    │   ├── ack/                    # 阿里云 K8s
    │   ├── s3/                     # AWS S3 存储
    │   ├── oss/                    # 阿里云 OSS
    │   └── cloudfront/             # CDN
    ├── environments/
    │   ├── aws-production/
    │   ├── aws-staging/
    │   ├── alicloud-production/
    │   ├── alicloud-staging/
    │   └── shared-services/        # 跨环境共享
    ├── policies/                   # OPA 策略
    ├── scripts/                    # 辅助脚本
    └── .github/workflows/

    跨云共享:CDN

    # modules/cloudfront/main.tf
    resource "aws_cloudfront_distribution" "this" {
      enabled = true
      comment = "Global CDN"
    
      origin {
        domain_name = var.alicloud_oss_bucket_host  # 阿里云 OSS 作为源
        origin_id   = "alibaba-oss"
    
        custom_origin_config {
          http_port              = 80
          https_port             = 443
          origin_protocol_policy = "https-only"
          origin_ssl_protocols   = ["TLSv1.2"]
        }
      }
    
      default_cache_behavior {
        target_origin_id       = "alibaba-oss"
        viewer_protocol_policy = "redirect-to-https"
        allowed_methods        = ["GET", "HEAD"]
        cached_methods         = ["GET", "HEAD"]
    
        forwarded_values {
          query_string = false
          cookies {
            forward = "none"
          }
        }
      }
    
      restrictions {
        geo_restriction {
          restriction_type = "none"
        }
      }
    
      viewer_certificate {
        acm_certificate_arn = var.certificate_arn
        ssl_support_method  = "sni-only"
      }
    }

    实战十三:常见监控和告警

    CloudWatch 告警

    resource "aws_cloudwatch_metric_alarm" "high_cpu" {
      alarm_name          = "high-cpu-${var.environment}"
      comparison_operator = "GreaterThanThreshold"
      evaluation_periods  = "2"
      metric_name         = "CPUUtilization"
      namespace           = "AWS/EC2"
      period              = "120"
      statistic           = "Average"
      threshold           = "80"
      alarm_description   = "EC2 CPU > 80% for 4 minutes"
      alarm_actions       = [aws_sns_topic.alerts.arn]
    
      dimensions = {
        AutoScalingGroupName = aws_autoscaling_group.web.name
      }
    }

    阿里云云监控

    resource "alicloud_cloud_monitor_service_alarm" "cpu_alarm" {
      service = "ecs"
      resources = join(",", alicloud_instance.web
  • .id)   metric_name = "CPUUtilization"   threshold = 80   statistics = "Average"   comparison_operator = ">"   evaluation_count = 3   contact_groups = [alicloud_cloud_monitor_contact_group.default.id]   period = 300 }
  • 实战十四:故障案例复盘

    案例 1:state 锁死导致 apply 卡 6 小时

    现象:CI pipeline terraform apply 卡在 acquiring state lock 6 小时。

    根因:上一次 CI job OOM 退出,state 锁未释放。

    修复:

    # 1. 确认没人正在 apply
    # 2. 查看 DynamoDB 表中 LockID
    aws dynamodb scan --table-name terraform-lock
    # 3. 强制解锁
    terraform force-unlock <LOCK_ID>

    复盘:CI 加超时机制,apply 超时后清理 lock。Terraform 1.6+ 锁有自动过期。

    案例 2:误删生产 RDS

    现象:测试环境跑 terraform destroy 时忘记切环境变量,误删了生产 RDS 实例。

    根因:terraform destroy 不带 -target 会销毁所有 state 里的资源,工程师切错 profile。

    修复:

    • 强制使用 prevent_destroy 标记关键资源:
    resource "aws_db_instance" "production" {
      ...
      lifecycle {
        prevent_destroy = true
      }
    }
    • IAM 权限隔离:测试 IAM 不能动生产资源。
    • terraform destroy 加二次确认。
    • 数据库开启自动备份 + Point-in-Time Recovery。

    案例 3:Provider 升级导致资源重建

    现象:升级 hashicorp/aws 5.0 → 5.50,plan 显示要重建 200 个 EC2 实例。

    根因:新版本对某些字段默认值做了调整,触发资源属性变更。

    修复:

    • 升级前在测试环境 plan,diff 确认。
    • 强制锁定资源不重建:
    resource "aws_instance" "this" {
      lifecycle {
        prevent_destroy = true  # 防止误删
        # 还能用 ignore_changes 忽略某些字段
        ignore_changes = [ami, user_data]
      }
    }
    • Provider 升级拆小步,不要一次跳多个 minor。

    案例 4:跨 region 资源错配

    现象:plan 在 ap-southeast-1,但实际创建在 us-east-1

    根因:CI 环境变量没传,Terraform 用了默认 region。

    修复:CI 强制注入 AWS_REGIONALICLOUD_REGION 等变量,并在 plan 输出中校验。

    风险提示清单

    • 生产 terraform destroy:必须加审批,必须用 -target 限制范围。
    • force-unlock:确认无人在 apply。
    • provider 升级:测试环境先 plan,prod 环境不变更字段。
    • 资源 attribute 变化:用 lifecycle { ignore_changes = [...] } 屏蔽。
    • 敏感信息:用 Vault / KMS / Parameter Store / 环境变量,不要写代码。
    • IAM 权限:Terraform 用专用账号,最小权限。
    • 状态文件加密:S3 / OSS 开启 SSE-KMS。
    • 跨云网络:VPC Peering / VPN / SD-WAN 配置要单独管理。
    • CI 流水线泄露 plan:plan 输出可能含敏感信息,加 sensitive = true 标记。
    • destroy protection:关键资源加 lifecycle { prevent_destroy = true }
    • State 备份:S3 开启 versioning,定期备份到异地。
    • 多云账户:用 AWS Organizations / 阿里云资源目录 / GCP Folders 统一管理。

    最佳实践 Checklist

    • [ ] 项目分层:modules/environments/providers/ 分离。
    • [ ] 状态后端用远程(不用 local),加锁、加密。
    • [ ] 变量用 variable 块定义,敏感变量标记 sensitive
    • [ ] 资源命名规范:{project}-{env}-{role}-{instance}
    • [ ] 统一标签:EnvironmentManagedByProjectCostCenterOwner
    • [ ] 模块化:业务模块和基础设施模块分开。
    • [ ] 版本约束:required_versionrequired_providersmodule.version
    • [ ] terraform fmt 统一格式。
    • [ ] terraform validate 静态检查。
    • [ ] tflint 静态检查(社区 linter)。
    • [ ] checkov / tfsec 安全扫描。
    • [ ] 关键资源加 prevent_destroy
    • [ ] 漂移检测:定时 plan / driftctl。
    • [ ] CI 集成:plan → review → apply 流程。
    • [ ] 敏感信息用 Vault / KMS,不进代码。
    • [ ] Policy as Code:OPA / Conftest 阻断违规。
    • [ ] 成本监控:Infracost / 账单分析。
    • [ ] 文档:每个 module 有 README,说明 inputs / outputs / 用途。

    常见问题 FAQ

    Q1:Terraform 1.5+ 跟以前有什么不一样?

    A:1.1+ 引入 moved 块做资源重命名,1.2+ 引入 removed 块,1.5+ 引入 import block、refresh-only plan、check block,1.6+ 加 state lock 自动过期。生产建议 1.6+。

    Q2:Terraform 1.x 之后还有 Terraform 0.x 吗?

    A:没有了。Terraform 0.13 之后改为 1.0。当前最新是 1.7+(以实际版本为准)。

    Q3:OpenTofu 是什么?

    A:HashiCorp 把 Terraform 改 BSL 协议后,社区 fork 出 OpenTofu(基于 Terraform 1.5)。OpenTofu 是 MPL 协议开源,兼容 Terraform 1.5 API。

    Q4:Terraform 和 Pulumi 选哪个?

    A:Terraform 是声明式 HCL,生态最大。Pulumi 用通用语言(Python、Go、TypeScript)写 IaC,对程序员更友好,但生态略小。多数团队用 Terraform。

    Q5:Terraform 怎么加密敏感变量?

    A:三种方式:(1) sensitive = true 标记,CI 输出不显示;(2) 后端用 SSE-KMS;(3) 敏感值从 Vault / KMS / Parameter Store 读,不写在 tfvars 里。

    Q6:Terraform 的 state 文件能共享吗?

    A:可以,但要避免多人同时改。用远程后端 + 锁,每个环境一个 state,按目录或 workspace 隔离。

    Q7:怎么从云控制台手工创建的资源迁移到 Terraform?

    A:用 terraform import(单资源)或 terraformer(批量)。导入后把资源属性写进 Terraform 代码。

    Q8:Terraform 怎么管理已经存在但不在 state 里的资源?

    A:terraform import 把资源加入 state,然后写 Terraform 代码描述它。

    Q9:Terraform 跑在 K8s 里?

    A:可以,常见的做法是 terraform-operator / Spacelift Agent / Atlantis。生产里用 Kubernetes 跑 Atlantis Pod 比较常见。

    Q10:怎么减少 apply 时间?

    A:(1) 用 -target 限定范围;(2) 用 -parallelism 提高并发;(3) 把大 state 拆成小 state;(4) 用 -refresh=false 跳过 refresh(不推荐,可能漏掉漂移)。

    Q11:Terraform Cloud / Enterprise 值得用吗?

    A:团队规模小(< 5 人)、项目少(< 10 个),用 GitHub Actions + S3 后端足够。规模大了上 Terraform Cloud / Spacelift / Env0,节省状态管理、policy、审计成本。

    Q12:如何处理跨云资源依赖?

    A:避免强耦合。把多云资源分到不同 module / state,跨云信息通过 output + 远程 state 数据源传递(terraform_remote_state)。

    Q13:Terraform drift 怎么处理?

    A:定期 terraform plan -detailed-exitcode 或用 driftctl。漂移原因通常是人手工改了云上资源。处理:(1) 重新 apply 让 state 同步;(2) 把真实状态写进 Terraform 代码;(3) 严禁手工改云上资源(这是 IaC 的核心约定)。

    Q14:怎么测试 Terraform 代码?

    A:商业版有 Sentinel / OPA。开源自建:单元测试用 terraform plan 验证资源数 / 属性,集成测试用 terratest(Go)。

    总结

    Terraform 解决的是"用统一语言描述多云资源"的问题。但实际落地需要:

    • 理解每个云的资源模型和差异。
    • 严格管理 state(远程、加密、加锁)。
    • 模块化设计(避免一个 state 太大)。
    • 流水线集成(plan → review → apply)。
    • 漂移检测(保证期望和实际一致)。
    • Policy as Code(安全合规)。
    • 成本监控(多云账单分析)。

    核心心法:

    • 所有资源用代码描述,禁手工改云上。
    • 状态用远程后端,加锁加密。
    • 流水线审批后才 apply。
    • 模块化拆分,避免大 state。
    • 漂移检测,定时 plan。
    • 敏感信息走 Vault / KMS。
    • 关键资源 prevent_destroy

    把这套流程走完,多云管理能上一个台阶。

    如果你还想与更多同行交流多云运维的实战经验,不妨到云栈社区看看,那里常有关于 IaC 与 CI/CD 的深度讨论。

    附录:常用命令速查

    # 初始化
    terraform init
    terraform init -upgrade  # 升级 provider
    
    # 格式化
    terraform fmt
    terraform fmt -check  # CI 检查用
    
    # 验证
    terraform validate
    
    # Plan / Apply
    terraform plan
    terraform plan -out=tfplan
    terraform plan -detailed-exitcode  # CI 用
    terraform plan -refresh-only  # 1.5+ 刷新 state 不 apply
    terraform apply
    terraform apply tfplan
    terraform apply -auto-approve
    terraform apply -target=module.vpc  # 限定范围
    terraform destroy
    terraform destroy -target=...
    
    # State
    terraform state list
    terraform state show <addr>
    terraform state mv <from> <to>
    terraform state rm <addr>
    terraform state pull  # 下载 state
    terraform state push  # 上传 state(高危)
    
    # Import
    terraform import <addr> <id>
    terraform import aws_instance.web i-1234567890abcdef0
    
    # Workspace
    terraform workspace new dev
    terraform workspace list
    terraform workspace select production
    terraform workspace delete dev
    
    # 调试
    TF_LOG=DEBUG terraform apply  # 详细日志
    TF_LOG_PATH=/tmp/tf.log terraform apply

    附录:HCL 语法速查

    # 变量
    variable "name" {
      type        = string
      default     = "default"
      description = "描述"
      sensitive   = false
      validation {
        condition     = length(var.name) > 0
        error_message = "name 不能为空。"
      }
    }
    
    # 输出
    output "name" {
      value       = aws_instance.web.id
      description = "Web 实例 ID"
      sensitive   = false
    }
    
    # 资源
    resource "aws_instance" "web" {
      ami           = "ami-12345"
      instance_type = "t3.micro"
    
      tags = {
        Name = "web"
      }
    
      lifecycle {
        create_before_destroy = true
        prevent_destroy       = true
        ignore_changes        = [ami]
      }
    
      depends_on = [aws_vpc.main]
    }
    
    # 数据源
    data "aws_ami" "ubuntu" {
      most_recent = true
      owners      = ["099720109477"]
    
      filter {
        name   = "name"
        values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
      }
    }
    
    # 本地值
    locals {
      common_tags = {
        Environment = "production"
        ManagedBy   = "terraform"
      }
    }
    
    # Module
    module "vpc" {
      source  = "./modules/vpc"
      version = "1.0.0"
    
      vpc_cidr = "10.0.0.0/16"
    }
    
    # Provider
    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
      }
    }
    
    provider "aws" {
      region = "ap-southeast-1"
    }
    
    # 远程 state 数据源
    data "terraform_remote_state" "vpc" {
      backend = "s3"
      config = {
        bucket = "myorg-terraform-state"
        key    = "vpc/terraform.tfstate"
        region = "ap-southeast-1"
      }
    }

    附录:资源属性与字段示例

    AWS EC2

    resource "aws_instance" "web" {
      ami                    = "ami-12345"
      instance_type          = "t3.medium"
      subnet_id              = "subnet-12345"
      vpc_security_group_ids = ["sg-12345"]
      key_name               = "my-key"
      user_data              = filebase64("userdata.sh")
      monitoring             = true
      ebs_optimized          = true
    
      root_block_device {
        volume_size = 20
        volume_type = "gp3"
        encrypted   = true
      }
    
      metadata_options {
        http_endpoint = "enabled"
        http_tokens   = "required"
      }
    
      tags = {
        Name = "web"
      }
    }

    阿里云 ECS

    resource "alicloud_instance" "web" {
      image_id                   = "ubuntu_22_04_x64"
      instance_type              = "ecs.g6.large"
      security_groups            = [alicloud_security_group.web.id]
      vswitch_id                 = alicloud_vswitch.public.id
      internet_max_bandwidth_out = 5
      internet_charge_type       = "PayByTraffic"
      instance_name              = "web"
      key_name                   = "my-key"
      password                   = var.instance_password
      system_disk_category       = "cloud_essd"
      system_disk_size           = 40
    
      tags = {
        Name = "web"
      }
    }

    腾讯云 CVM

    resource "tencentcloud_instance" "web" {
      instance_name     = "web"
      availability_zone = "ap-shanghai-2"
      image_id          = "img-12345"
      instance_type     = "S5.MEDIUM4"
      vpc_id            = tencentcloud_vpc.main.id
      subnet_id         = tencentcloud_subnet.public.id
    
      allocate_public_ip = true
      internet_max_bandwidth_out = 5
    
      security_groups = [tencentcloud_security_group.web.id]
    
      tags = {
        Name = "web"
      }
    }

    附录:常见资源 type 速查

    资源 type
    AWS aws_vpc, aws_subnet, aws_instance, aws_db_instance, aws_s3_bucket, aws_lb, aws_eks_cluster
    阿里云 alicloud_vpc, alicloud_vswitch, alicloud_instance, alicloud_db_instance, alicloud_oss_bucket, alicloud_slb, alicloud_cs_kubernetes
    腾讯云 tencentcloud_vpc, tencentcloud_subnet, tencentcloud_instance, tencentcloud_cdb_instance, tencentcloud_cos_bucket, tencentcloud_clb
    Azure azurerm_virtual_network, azurerm_subnet, azurerm_virtual_machine, azurerm_postgresql_server, azurerm_storage_account
    GCP google_compute_network, google_compute_subnetwork, google_compute_instance, google_sql_database_instance, google_storage_bucket

    不同 provider 版本字段可能略有差异,以实际版本为准。

    附录:参考资源




    上一篇:崩溃不算漏洞?Telegram 四端持久化 DoS 疑云与成因分析
    下一篇:SaltStack 批量管控落地:架构解析、SLS 实战与踩坑复盘
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-9-10 16:01 , Processed in 0.997971 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表