Alibaba Cloud ROS Plugin for Jenkins 与 Git 仓库集成,可构建代码变更驱动的基础设施自动部署流水线,实现 ROS 模板版本化管理与多环境隔离部署。
业务场景说明
ROS 模板和参数文件托管在 Git 仓库中做版本化管理。模板或参数变更后,基础设施需要同步完成创建或更新。手动登录控制台部署效率低下,且容易因操作遗漏或环境混淆引发故障。开发与生产环境的参数配置需要严格隔离,阿里云 AccessKey 等凭证不能以明文出现在代码仓库或构建日志中。
Jenkins Pipeline 监听 Git 仓库变更,自动读取对应环境的 ROS 模板和参数文件,调用 Alibaba Cloud ROS Plugin 完成资源栈创建或更新。凭证通过 Jenkins Credential Store 统一管理,全程不暴露敏感信息。代码合并后,基础设施在数分钟内自动完成变更,无需人工干预。
方案架构

Jenkins 通过 pollSCM('H/5 * * * *') 定时轮询 Git 仓库(也可配置 GitHub Webhook 实现实时触发),发现新提交后自动启动构建流水线。Git SSH Key 存储在 Jenkins Credential Store 中,与其他凭证隔离管理。
流水线先尝试调用 CreateRosStack 创建资源栈;若资源栈已存在,则自动切换为 UpdateRosStack 执行增量更新。不同环境通过独立的参数文件(如 parameters/dev.json)实现配置隔离,开发环境参数仅包含对应权限范围内的值。
所有 ROS 操作通过 credentialsId 参数引用 Jenkins 中预配置的阿里云凭证,AccessKey 不会出现在流水线脚本或构建日志中。
实施步骤
步骤一:组织 Git 仓库与 ROS 模板
开始之前,需要完成 ROS Jenkins 插件的安装和配置。具体操作,请参见ROS Jenkins插件工具介绍。
仓库采用模板与参数分离的目录结构:
my-project/
├── templates/ # ROS 模板目录
│ └── vpc-template.json # 基础设施定义
├── parameters/ # 环境参数隔离
│ ├── dev.json # 开发环境参数
│ └── prod.json # 生产环境参数(权限严格管控)
└── Jenkinsfile templates/*.json 文件必须包含 ROSTemplateFormatVersion 字段,禁止在模板中硬编码环境变量,所有环境差异通过参数注入。parameters/*.json 文件仅包含 ParameterValue,禁止在其中存储密码等敏感信息。
ROS 模板文件
templates/vpc-template.json 定义了包含 VPC、VSwitch 和安全组的基础网络环境。模板通过 Parameters 接收外部参数,Fn::Sub 将环境标识拼接到资源名称中,确保不同环境的资源互不冲突:
{
"ROSTemplateFormatVersion": "2015-09-01",
"Description": {
"en": "ROS Template: Creating a VPC.",
"zh-cn": "ROS模板:创建VPC。"
},
"Parameters": {
"Environment": {
"Type": "String",
"Default": "development",
"AllowedValues": [
"development",
"staging",
"production"
],
"Description": {
"en": "Environment Type",
"zh-cn": "环境类型"
}
},
"VpcName": {
"Type": "String",
"Default": "my-vpc",
"Description": {
"en": "Vpc Name",
"zh-cn": "VPC名称"
}
},
"CidrBlock": {
"Type": "String",
"Default": "192.168.0.0/16",
"Description": {
"en": "Cidr Block",
"zh-cn": "VPC CIDR块"
}
}
},
"Resources": {
"VPC": {
"Type": "ALIYUN::ECS::VPC",
"Properties": {
"CidrBlock": {
"Ref": "CidrBlock"
},
"VpcName": {
"Fn::Sub": "${VpcName}-${Environment}"
},
"Description": "VPC for ${Environment} environment"
}
},
"VSwitch": {
"Type": "ALIYUN::ECS::VSwitch",
"Properties": {
"VpcId": {
"Ref": "VPC"
},
"CidrBlock": "192.168.1.0/24",
"ZoneId": {
"Fn::Select": [
0,
{
"Fn::GetAZs": {
"Ref": "ALIYUN::Region"
}
}
]
},
"VSwitchName": {
"Fn::Sub": "vswitch-${Environment}"
}
}
},
"SecurityGroup": {
"Type": "ALIYUN::ECS::SecurityGroup",
"Properties": {
"VpcId": {
"Ref": "VPC"
},
"SecurityGroupName": {
"Fn::Sub": "sg-${Environment}"
},
"Description": "Security group for ${Environment} environment"
}
}
},
"Outputs": {
"VpcId": {
"Value": {
"Ref": "VPC"
},
"Description": {
"en": "VPC ID.",
"zh-cn": "创建的VPC ID。"
}
}
}
}
环境参数文件
parameters/dev.json 为开发环境定义了 VPC 名称、CIDR 段和环境标识:
[
{
"ParameterKey": "VpcName",
"ParameterValue": "dev-name"
},
{
"ParameterKey": "CidrBlock",
"ParameterValue": "192.168.0.0/16"
},
{
"ParameterKey": "Environment",
"ParameterValue": "development"
}
]
步骤二:配置 Jenkins 凭证与流水线
凭证需要在创建流水线之前配置完成。流水线脚本通过凭证 ID 引用凭证,不在代码中暴露敏感信息。
配置安全凭证
在 中配置两项凭证。Git 凭证的类型选择 SSH Username with private key,ID 设置为 git-credentials,用于 Jenkins 拉取 Git 仓库代码。阿里云凭证的类型选择 Alibaba Cloud Credentials,ID 设置为 aliyun-ak-creds,供 ROS 插件调用阿里云 API。
创建 Pipeline 任务
在 Jenkins 控制台选择 New Item,输入任务名称,选择 Pipeline 类型,单击 OK。

在 Pipeline 配置区域配置 Git 源代码管理,
credentialsId引用上一步创建的 Git 凭证:
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
url: 'https://github.com/your-org/my-project.git', // [修改] 替换为实际仓库地址
credentialsId: 'git-creds' // [必须] 使用 Jenkins 凭据 ID
]]
])配置流水线脚本
在 Pipeline 区域输入流水线脚本,单击 Save 保存:
pipeline {
agent any
parameters {
string(name: 'BRANCH', defaultValue: 'master', description: 'The branch to build')
string(name: 'CREDENTIALS_ID', defaultValue: 'test123', description: 'ros ak')
string(name: 'REGION_ID', defaultValue: 'cn-shenzhen', description: 'stack region')
string(name: 'STACK_NAME', defaultValue: 'my-jenkins-stack', description: 'stack name')
booleanParam(name: 'PERFORM_DEPLOY', defaultValue: false, description: 'Check if you want to deploy')
}
triggers {
pollSCM('H/5 * * * *') // 每 5 分钟检查 Git 仓库变更
}
stages {
stage('① Checkout: 拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
url: 'https://github.com/your-repo.git', // <-- [修改] 替换为实际仓库 URL
credentialsId: 'git-credentials' // <-- [确认] 使用已配置的 Git 凭据 ID
]]
])
sh 'echo "代码已从分支 $BRANCH 拉取完毕"'
}
}
stage('Build') {
steps {
echo 'Building the application...'
}
}
stage('Deploy with ROS') {
steps {
script {
def templatePath = "${WORKSPACE}/templates/vpc-template.json"
def templateContent = readFile(templatePath)
def paramsFile = "${WORKSPACE}/parameters/${env.ENVIRONMENT ?: 'dev'}.json"
def tplParams = readFile(paramsFile)
echo "Using template: ${templatePath}"
try {
def stackId = CreateRosStack(
stackName: "${params.STACK_NAME}",
regionId: "${params.REGION_ID}",
credentialsId: "${params.CREDENTIALS_ID}",
parameters: tplParams,
templateBody: templateContent
)
echo "Created stack with ID: ${stackId}"
env.STACK_ID = stackId
} catch (Exception e) {
echo "Stack already exists, will attempt to update it."
if (e.getMessage().contains("already exists")) {
def stacks = ListRosStacks(
stackName: ["${params.STACK_NAME}"],
regionId: "${params.REGION_ID}",
credentialsId: "${params.CREDENTIALS_ID}"
)
echo "stacks: ${stacks}"
if (stacks && stacks.TotalCount > 0) {
def stackId = stacks.Stacks[0].StackId
try {
echo "Stack ${stackId} already exists, will attempt to update it."
UpdateRosStack(
stackId: stackId,
regionId: "${params.REGION_ID}",
credentialsId: "${params.CREDENTIALS_ID}",
parameters: tplParams,
templateBody: templateContent
)
env.STACK_ID = stackId
} catch (Exception ex) {
echo ex.getMessage()
}
}
} else {
echo "An unexpected error occurred: ${e.getMessage()}"
currentBuild.result = 'FAILURE'
throw e
}
}
}
}
}
stage('Test') {
steps {
echo 'Running tests...'
}
}
stage('Deploy') {
steps {
echo 'Deploying the application...'
}
}
}
post {
always {
echo 'Pipeline finished.'
}
success {
echo 'Pipeline succeeded!'
}
failure {
echo 'Pipeline failed!'
}
}
}
核心逻辑在 Deploy with ROS 阶段。pollSCM 每 5 分钟轮询 Git 仓库,检测到新提交后拉取代码,读取 templates/ 下的 ROS 模板和 parameters/ 下对应环境的参数文件。部署阶段先尝试调用 CreateRosStack 创建资源栈;如果捕获到 "already exists" 异常,则通过 ListRosStacks 查询现有资源栈 ID,再调用 UpdateRosStack 执行更新。这种先创建、失败则更新的模式确保了首次部署和后续变更都能自动处理。无论更新成功或失败,后续的测试和部署阶段仍会继续执行。
步骤三:验证首次部署
在 Jenkins 任务页单击 Build with Parameters,手动触发构建。
查看构建日志,确认输出类似如下内容:
[Deploy ROS] Created stack with ID: 4ee9****-****-****-****-************
[Post-build] ✅ dev 环境部署成功!资源栈: 4ee9****-****-****-****-************在 ROS 控制台确认资源栈状态为 CREATE_COMPLETE。
步骤四:验证 Git 变更触发自动部署
修改参数文件并推送到 Git 仓库,验证 Jenkins 能否自动检测变更并完成资源栈更新。
提交参数变更
修改
parameters/dev.json,将VpcName参数值改为dev-new-name:
[
{
"ParameterKey": "VpcName",
"ParameterValue": "dev-new-name"
},
{
"ParameterKey": "CidrBlock",
"ParameterValue": "192.168.0.0/16"
},
{
"ParameterKey": "Environment",
"ParameterValue": "development"
}
]提交变更并推送到远程仓库:
git add parameters/dev.json
git commit -m "调整 VpcName 为 dev-new-name"
git push origin master确认自动部署结果
Jenkins 每 5 分钟轮询 Git 仓库,检测到新提交后自动触发构建。流水线拉取最新代码,读取更新后的参数文件,调用 UpdateRosStack 执行资源栈更新。整个过程通常在 2 分钟内完成,资源栈状态从 UPDATE_IN_PROGRESS 变为 UPDATE_COMPLETE。
在 ROS 控制台查看资源栈事件,确认 VPC 的 VpcName 已更新为 dev-new-name-development(模板中 Fn::Sub 自动拼接了环境后缀)。同时在 Jenkins 构建日志中搜索 Updated stack 确认更新执行成功。