如何设计可复用的 Terraform 模块支持 MySQL 与 PostgreSQL?
解读
在国内云原生面试中,这道题考察的是“一次编写、多处复用”的基础设施即代码能力。面试官希望听到:
- 如何用变量+条件表达式把两套引擎差异收敛到同一套代码;
- 如何兼顾国内网络合规(VPC、Private Service Connect、跨境数据不出境);
- 如何与**现有 CICD(如云效、蓝鲸、GitLab-Runner on ACK)**无缝集成;
- 如何做到最小权限(IAM 最小化、国内多项目隔离);
- 如何灰度发布与成本可控(抢占式只读实例、按量计费转包年包月)。
一句话:不是“写一段能跑起来的 HCL”,而是“写一段国内金融客户也能直接落地的可复用、可审计、可灰度的模块”。
知识点
- Terraform 模块化语义:root module / child module、count 与 for_each 的引擎级开关
- Google 国内合规底座:VPC 内网 Private IP、Cloud SQL Auth Proxy sidecar、CMEK 国密算法
- 引擎差异矩阵:MySQL 8.0 与 PostgreSQL 15 的 flags、用户、database 级 collation、max_connections 计算方式
- IAM 条件属性:google_sql_database_instance 的 google_project_iam_member 与 google_project_iam_custom_role,配合国内多项目的“项目-环境-引擎”三级命名规范
- 状态文件治理:Terraform Cloud 在国内网络下使用 GCS 后端 + OSS 镜像缓存,开启对象版本+服务端加密
- 费用治理:通过 preemptible 只读节点 + 定时 Cloud Scheduler + Cloud Function 转包年包月,节省 35% 成本
- 灰度策略:使用 module 的 var.engine_version 做“小版本锁定”,配合国内蓝绿发布——DNS 内网解析切换
答案
- 目录结构
modules/cloudsql/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
└── engine/
├── mysql.tf
└── postgres.tf
- 变量设计(片段)
variable "engine" {
type = string
description = "mysql or postgres"
validation {
condition = contains(["mysql", "postgres"], var.engine)
error_message = "国内合规只支持 mysql 与 postgres."
}
}
variable "tier" {
type = string
# 国内金融区仅开放 db-n1-standard-2 及以上
validation {
condition = can(regex("db-n\\d+-standard-\\d+", var.tier))
error_message = "国内节点不支持共享型机器."
}
}
- 引擎路由
# main.tf
module "mysql" {
source = "./engine/mysql"
for_each = var.engine == "mysql" ? { "mysql" = true } : {}
# 透传统一变量
project_id = var.project_id
region = var.region
tier = var.tier
}
module "postgres" {
source = "./engine/postgres"
for_each = var.engine == "postgres" ? { "postgres" = true } : {}
project_id = var.project_id
region = var.region
tier = var.tier
}
- 引擎差异收敛
- mysql.tf 使用
google_sql_database_instance的settings.mysql_config块; - postgres.tf 使用
settings.postgres_config块; - 共同变量如
disk_size、backup_configuration提升到根模块,通过local.common_settings合并,保证 80% 代码复用。
- 国内合规加固
- 开启
private_ip并绑定已获批的 VPC 内网地址段(RFC1918 10.0.0.0/8 子集); - 使用
google_kms_crypto_key指定国密 SM4 算法的 CMEK; - 通过
google_sql_user仅创建Least-Privilege 用户,禁止 root 与 cloudsqlsuperuser 外网登录; - 在
backup_configuration打开transaction_log_ret_days = 7满足等保 2.0 日志留存要求。
- 输出标准化
output "connection_name" {
value = var.engine == "mysql" ? module.mysql["mysql"].connection_name : module.postgres["postgres"].connection_name
}
output "private_ip" {
value = var.engine == "mysql" ? module.mysql["mysql"].private_ip : module.postgres["postgres"].private_ip
}
- 使用示例
module "rds" {
source = "gcs::https://www.googleapis.com/storage/v1/tf-modules//cloudsql?ref=v1.2.0"
engine = "postgres"
project_id = "prod-cn"
region = "shanghai-a"
tier = "db-n1-standard-4"
}
- CICD 集成
- 在云效 Flow 中配置 terraform-plan 与 terraform-apply 两个阶段;
- 使用国内镜像源
https://mirrors.aliyun.com/terraform/加速 provider 下载; - plan 结果通过钉钉群机器人@DBA 审批,二次确认后才 apply。
拓展思考
- 双引擎共用读写分离:只读实例通过
google_sql_database_instance.read_replica创建,MySQL 用 binlog、PostgreSQL 用 logical replication,如何统一输出只读 endpoint? - 跨地域容灾:北京与上海双 Region,使用Cloud SQL 跨区域只读副本+国内 DNS 内网解析权重实现 RPO<5min,如何在不改代码的情况下让模块自动识别主 Region?
- Serverless 场景:国内小程序高峰弹性,Cloud SQL Enterprise Plus + 自动扩容与AlloyDB Omni on GKE 的取舍?
- Terraform 0.13+ 的 count/for_each 在引擎级开关的局限:当客户要求MySQL 与 PostgreSQL 并存于同一项目时,如何重构模块以支持“多引擎多实例”而非“单引擎单实例”?
- 合规审计:国内央行检查要求每日自动导出慢日志到 OSS并加密归档 5 年,如何把
google_logging_project_sink+google_storage_bucket+ 国内 OSS 跨域复制 集成到同一模块而不破坏复用性?
把以上五点想透,基本可以在国内头部互联网或券商的 Google Cloud SQL 岗位面试中拿到**“Strong Hire”**。