整体工作流程设计
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.project
credentials = file("/root/.gcp-credentials.json")
}
resource "google_compute_instance" "instance" {
name = var.instance_name
machine_type = var.machine_type
tags = var.network_tags
zone = var.zone
boot_disk {
auto_delete = true
device_name = "var.instance_name"
initialize_params {
image = "projects/centos-cloud/global/images/centos-stream-9-v20250812"
size = var.disk_size
type = var.disk_type
}
mode = "READ_WRITE"
}
deletion_protection = true
can_ip_forward = false
enable_display = false
labels = {
terraform = "001"
year = "2025"
}
metadata = {
enable-osconfig = "TRUE"
}
network_interface {
access_config {
network_tier = "PREMIUM"
}
queue_count = 0
stack_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_restart
on_host_maintenance = "MIGRATE"
preemptible = false
provisioning_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 = true
enable_secure_boot = false
enable_vtpm = true
}
}
module "ops_agent_policy" {
source = "github.com/terraform-google-modules/terraform-google-cloud-operations/modules/ops-agent-policy"
project = var.project
zone = var.zone
assignment_id = "goog-ops-agent-${var.instance_name}-${var.zone}"
agents_rule = {
package_state = "installed"
version = "latest"
}
instance_filter = {
all = false
inclusion_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 = string
validation {
# 验证名称是否符合正则规则
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 = number
default = 60
validation {
condition = var.disk_size >= 50
error_message = "磁盘大小 ${var.disk_size} GB 不符合要求!磁盘大小必须大于或等于 50 GB。"
}
}
variable "automatic_restart" {
description = "Number of disk size."
type = bool
default = 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.name
description = "The name of the instance"
}
output "机器类型" {
value = google_compute_instance.instance.machine_type
description = "The machine type of the instance"
}
output "公网IP" {
value = google_compute_instance.instance.network_interface.0.access_config.0.nat_ip
description = "The external IP address of the instance"
}
output "内网IP" {
value = google_compute_instance.instance.network_interface.0.network_ip
description = "The internal IP address of the instance"
}
output "网络标记" {
value = google_compute_instance.instance.tags
description = "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 fmt if ! terraform fmt -check; then echo "代码格式不符合规范,请运行terraform fmt修复" exit 1 fi # 语法验证 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自动支持)+ 流水线日志归档