Terraform整合到GitLab+Jenkins工具链
整体工作流程设计
GitLab(代码仓库) → Jenkins(CI/CD流水线) → Terraform(基础设施管理) → 云平台
- 开发者在GitLab提交Terraform代码
- GitLab触发Jenkins流水线
- Jenkins执行Terraform计划、验证和部署
- 最终在云平台创建/更新基础设施
阶段1:环境准备
1.1 安装必要工具
在Jenkins服务器上安装所需软件:
# 安装Terraform(以Linux为例)
sudo apt update && sudo apt 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 update && sudo apt install -y terraform# 安装Git(用于拉取代码)
sudo apt install -y git# 安装云平台CLI(以GCP为例)
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
sudo apt update && sudo apt install -y google-cloud-sdk
1.2 配置权限与凭证
1.2.1 GitLab凭证
- 登录GitLab,进入个人设置 → Access Tokens
- 创建令牌,权限勾选 read_repository(用于Jenkins拉取代码)
- 保存令牌(如 gitlab-tf-token)
1.2.2 云平台凭证
以GCP为例:
- 登录GCP控制台 → IAM与管理 → 服务账号
- 创建服务账号(如 terraform-sa),授予权限:Editor(或最小权限集)
- 为服务账号创建密钥(JSON格式),保存为 gcp-credentials.json
1.2.3 Jenkins凭证管理
- 登录Jenkins → Manage Jenkins → Manage Credentials
- 点击 (global) → Add Credentials,添加以下凭证:
- GitLab令牌:类型选择 Username with password,用户名任意,密码填GitLab令牌,ID设为 gitlab-credentials
- 云平台密钥:类型选择 Secret file,上传 gcp-credentials.json,ID设为 gcp-credentials
1.3 配置Terraform远程后端
使用GCS存储Terraform状态文件(已创建存储桶如 tf-state-bucket):
# main.tf 中配置后端
terraform {backend "gcs" {bucket = "tf-state-bucket" # 替换为你的存储桶名# prefix通过Jenkins动态传入,不在这里硬编码}
}
阶段2:GitLab配置
2.1 创建Terraform代码仓库
在GitLab中创建仓库(如 terraform-infra),并按以下结构提交代码:
terraform-infra/
├── main.tf # 主配置文件
├── variables.tf # 变量定义
├── outputs.tf # 输出定义
示例 main.tf 内容(创建GCP实例):
# 此代码与 Terraform 4.25.0 及向后兼容 4.25.0 的版本兼容。# 如需了解如何验证此 Terraform 代码,请参阅 https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/google-cloud-platform-build#format-and-validate-the-configuration
terraform {required_providers {google = {source = "hashicorp/google"version = "6.8.0"}}
}
provider "google" {project = var.projectcredentials = file("/root/.gcp-credentials.json")
}resource "google_compute_instance" "instance" {name = var.instance_namemachine_type = var.machine_typetags = var.network_tagszone = var.zoneboot_disk {auto_delete = truedevice_name = "var.instance_name"initialize_params {image = "projects/centos-cloud/global/images/centos-stream-9-v20250812"size = var.disk_sizetype = var.disk_type}mode = "READ_WRITE"}deletion_protection = truecan_ip_forward = falseenable_display = falselabels = {terraform = "001"year = "2025"}metadata = {enable-osconfig = "TRUE"}network_interface {access_config {network_tier = "PREMIUM"}queue_count = 0stack_type = "IPV4_ONLY"subnetwork = "projects/${var.project}/regions/${element(split("-", var.zone), 0)}-${element(split("-", var.zone), 1)}/subnetworks/default"}scheduling {automatic_restart = var.automatic_restarton_host_maintenance = "MIGRATE"preemptible = falseprovisioning_model = "STANDARD"}service_account {email = "your@gserviceaccount.com"scopes = ["https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/trace.append"]}shielded_instance_config {enable_integrity_monitoring = trueenable_secure_boot = falseenable_vtpm = true}}module "ops_agent_policy" {source = "github.com/terraform-google-modules/terraform-google-cloud-operations/modules/ops-agent-policy"project = var.projectzone = var.zoneassignment_id = "goog-ops-agent-${var.instance_name}-${var.zone}"agents_rule = {package_state = "installed"version = "latest"}instance_filter = {all = falseinclusion_labels = [{labels = {goog-ops-agent-policy = "v2-x86-template-1-4-0"}}]}
}
terraform {backend "gcs" {bucket = "tf-state-bucket"//prefix = "terraform/state/${var.project}/${var.zone}/vm/${var.instance_name}"}
}
示例 variables.tf 内容(创建GCP实例):
variable "project" {default = "your_project_id"
}
variable "instance_name" {default = "terrafrom-test-jenkins001"description = "Name of the instance"type = stringvalidation {# 验证名称是否符合正则规则condition = can(regex("^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$", var.instance_name))error_message = "不合法的 instance 名字, 必须匹配规则: ^[a-z]([-a-z0-9]{0,61}[a-z0-9])?."}
}variable "machine_type" {default = "e2-medium"
}variable "network_tags" {type = list(string)default = ["base", "office"]
}variable "disk_size" {description = "Number of disk size."type = numberdefault = 60validation {condition = var.disk_size >= 50error_message = "磁盘大小 ${var.disk_size} GB 不符合要求!磁盘大小必须大于或等于 50 GB。"}
}variable "automatic_restart" {description = "Number of disk size."type = booldefault = true
}variable "disk_type" {default = "pd-balanced"
}
variable "metadata" {description = "A map of metadata key-value pairs to assign to the instance"type = map(string)default = {} # 默认为空映射
}variable "zone" {default = "asia-east2-c"
}
示例 outputs.tf 内容(创建GCP实例):
output "instance_name" {value = google_compute_instance.instance.namedescription = "The name of the instance"
}output "机器类型" {value = google_compute_instance.instance.machine_typedescription = "The machine type of the instance"
}output "公网IP" {value = google_compute_instance.instance.network_interface.0.access_config.0.nat_ipdescription = "The external IP address of the instance"
}
output "内网IP" {value = google_compute_instance.instance.network_interface.0.network_ipdescription = "The internal IP address of the instance"
}
output "网络标记" {value = google_compute_instance.instance.tagsdescription = "The firewalld tags of the instance "
}output "磁盘信息" {value = "磁盘大小: ${google_compute_instance.instance.boot_disk.0.initialize_params.0.size}, 磁盘类型: ${google_compute_instance.instance.boot_disk.0.initialize_params.0.type}"description = "The boot disk info of the instance "
}
2.2 配置GitLab WebHook
让GitLab代码推送触发Jenkins流水线:
- 在GitLab仓库 → Settings → Integrations
- URL填写:http://:8080/gitlab-webhook/(需安装Jenkins的GitLab插件)
- 勾选触发事件:Push events 和 Merge request events
- 点击 Add webhook 并测试连接
阶段3:Jenkins配置
3.1 安装必要插件
- 进入Jenkins → Manage Jenkins → Plugins
- 安装以下插件:
- GitLab Plugin(GitLab集成)
- Pipeline(流水线支持)
- Credentials Binding(凭证绑定)
3.2 创建Jenkins流水线项目
- 新建项目 → 选择 Pipeline,名称设为 terraform-pipeline
- 在 Pipeline 配置中:
- 定义方式选择 Pipeline script from SCM
- SCM选择 Git,Repository URL填写GitLab仓库地址(如 https://gitlab.com/your-org/terraform-infra.git)
- Credentials选择 gitlab-credentials(之前创建的)
- 脚本路径填写 Jenkinsfile(仓库中流水线文件的路径)
3.3 编写Jenkinsfile
在GitLab仓库中添加 Jenkinsfile,定义完整流水线:
pipeline {agent {node {label 'tes-agent'}}environment {GOOGLE_APPLICATION_CREDENTIALS = "/root/.gcp-credentials.json"}parameters {string(name: 'instance_name', defaultValue: '----------', description: '请输入实例名字 示例:terraform-instance-211')choice(name: 'zone', choices: ['asia-east1-b', 'asia-east2-a', 'asia-southeast1-b'], description: '实例区域 台湾:asia-east1-b 新加坡:asia-southeast1-b 香港:asia-east2-a')string(name: 'machine_type', defaultValue: 'e2-custom-2-4096', description: '机器类型 示例:e2-custom-2-4096')string(name: 'disk_size', defaultValue: '50', description: '磁盘大小 示例:50')choice(name: 'disk_type', choices: ['pd-balanced', 'pd-ssd'], description: '磁盘类型 pd-balanced:平衡性永久磁盘 pd-ssd:SSD永久性磁盘')text(name: 'network_tags', defaultValue: '["base", "office"]', description: '防火墙网络标记,每行一个标签')booleanParam(name: 'AUTO_APPROVE', defaultValue: false, description: '是否自动批准部署')}
-
options {
timeout(time: 30, unit: ‘MINUTES’)disableConcurrentBuilds()}stages {stage('Pre-Build') {steps {// 构建开始前删除工作区deleteDir()echo 'Workspace deleted successfully'}}stage('拉取运维代码') {steps {checkout([$class: 'GitSCM',branches: [[name: 'terraform-test']], // 分支配置doGenerateSubmoduleConfigurations: false,extensions: [cleanAfterCheckout() // 配置 Clean after checkout],submoduleCfg: [],userRemoteConfigs: [[url: 'git@<gitlab-ip>.com:ops/devops.git',credentialsId: 'terraform-test-gitlab']]])}}stage('Terraform初始化') {steps {script {echo "初始化Terraform并配置远程后端..."sh '''cd ./terraform/gcp/vm/terraform init -backend-config="prefix=terraform/state/instance/${instance_name}"'''}}}stage('代码检查') {steps {script {echo "检查Terraform代码格式和语法..."sh '''# 格式检查(不符合则失败)cd ./terraform/gcp/vm/terraform fmtif ! terraform fmt -check; thenecho "代码格式不符合规范,请运行terraform fmt修复"exit 1fi# 语法验证terraform validate'''}}}stage('生成执行计划') {steps {script {echo "生成Terraform执行计划..."sh '''cd ./terraform/gcp/vm/terraform plan \-var "instance_name=${instance_name}" \-var "zone=${zone}" \-var "machine_type=${machine_type}" \-var "disk_size=${disk_size}" \-var "disk_type=${disk_type}" \-var "network_tags=${network_tags}" \-out=./tfplan_${zone}_${instance_name}'''}}}stage('Approval for Deployment') {steps {script {def approvalNote = timeout(time: 5, unit: 'MINUTES') {input(id: 'deployApproval',message: '是否批准部署到生产环境?',ok: '批准',parameters: [string(name: 'APPROVER_NOTE',defaultValue: '无特殊说明',description: '请输入审批备注(可选)')])}echo "审批通过!审批人备注:${approvalNote}"}}}stage('执行部署') {steps {script {echo "执行Terraform部署..."sh '''cd ./terraform/gcp/vm/terraform apply "tfplan_${zone}_${instance_name}"'''}}}}post {always {echo 'Pipeline 完成'}success {echo "成功在${zone}区域创建实例${instance_name}"// 可以添加通知逻辑}failure {echo "在${zone}区域创建实例${instance_name}失败"// 可以添加失败通知逻辑}}
}
阶段4:测试与验证
- 触发流水线:
- 方式1:向GitLab仓库推送代码,通过WebHook自动触发
- 方式2:在Jenkins手动点击 Build with Parameters,选择环境后执行
- 验证结果:
-
查看Jenkins控制台输出,确认各阶段执行成功
-
登录云平台控制台,检查资源是否按预期创建
-
查看GCS存储桶,确认状态文件已按环境路径(如 terraform/state/dev/)存储
-
关键最佳实践
- 安全控制:
- 敏感凭证通过Jenkins安全存储,不暴露在代码中
- 生产环境禁用 AUTO_APPROVE,必须人工确认
- 质量保障:加入代码格式化检查、语法验证和安全扫描
- 可追溯性:状态文件版本控制(GCS自动支持)+ 流水线日志归档