跳到主要内容

CI/CD 自动化发布指南

本指南将帮助您构建自动化的 AgentRun 应用发布流程。在现代软件工程实践中,手动部署既耗时又容易出错,而自动化流水线可以确保每次部署的一致性和可靠性。我们将深入探讨如何将 AgentRun 与主流 CI/CD 平台集成,包括 GitHub Actions、GitLab CI、Jenkins、阿里云云效等,并分享经过生产环境验证的最佳实践。

理解 CI/CD 在 AgentRun 中的价值

在介绍具体的集成方法之前,让我们先理解为什么 CI/CD 对 AgentRun 应用如此重要。传统的手动部署流程存在诸多问题:开发者需要记住复杂的部署命令,不同环境的配置容易混淆,版本管理依赖人工记录,出现问题时难以快速定位。更重要的是,手动部署缺乏必要的验证环节,可能将存在缺陷的代码直接发布到生产环境。

持续集成和持续部署(CI/CD)通过自动化流水线解决了这些问题。每次代码提交都会触发自动化的构建和测试流程,确保代码质量符合标准。部署过程标准化且可追溯,每个环境使用明确定义的配置,避免了"在我机器上可以运行"的经典问题。版本管理与代码仓库深度集成,可以清晰地追踪每次变更的内容和原因。

对于 AgentRun 应用而言,CI/CD 还带来了额外的价值。Agent 的行为高度依赖提示词、模型配置和工具集成,这些要素的变化可能产生难以预测的影响。通过 CI/CD 流水线,您可以在多个环境中验证变更,使用自动化测试评估 Agent 的响应质量,通过灰度发布逐步推广新版本。这种系统化的发布流程大大降低了生产环境的风险。

设计发布流程架构

在开始编写 CI/CD 配置之前,需要先设计清晰的发布流程架构。一个典型的 AgentRun 应用发布流程包含六个关键阶段,每个阶段都有明确的职责和产出。

第一阶段是代码质量检查。当开发者提交代码到仓库时,流水线首先运行静态代码分析工具,检查代码风格、潜在错误和安全隐患。对于 Python 项目,常用的工具包括 flake8、pylint、black 等。这个阶段的目标是在问题进入测试环节之前就发现它们,节省宝贵的反馈时间。

第二阶段是自动化测试。这包括单元测试验证各个函数的正确性,集成测试验证 Agent 与模型、沙箱等外部服务的交互,端到端测试模拟真实用户场景。测试覆盖率应该达到合理的水平,特别是核心业务逻辑必须有完善的测试保护。

第三阶段是构建部署包。使用 Serverless Devs 的 s build 命令在 Docker 容器中安装依赖,生成与云端运行环境兼容的部署包。构建产物应该缓存起来,在后续的部署阶段重复使用,避免重复构建浪费时间。

第四阶段是部署到测试环境。将构建好的应用部署到开发环境或预发布环境,运行冒烟测试验证基本功能。这个阶段通常自动触发,不需要人工干预。测试环境使用较低的资源配置,以节省成本。

第五阶段是部署到生产环境。这个阶段通常需要人工审批,确保变更经过充分评估。部署到生产环境时使用更高的资源配置,启用 VPC 网络隔离和日志服务,确保安全性和可观测性。

第六阶段是部署后验证。通过健康检查接口验证服务可用性,运行关键业务场景的自动化测试,监控错误率和响应时间等指标。

分支策略和环境映射

分支策略定义了代码如何在不同分支间流动,以及每个分支对应的部署目标。我们推荐使用 Git Flow 策略,它为不同类型的工作提供了清晰的分支结构。

develop 分支是主要的开发分支,所有新功能的开发都在这里集成。当代码推送到 develop 分支时,CI/CD 流水线自动将其部署到开发环境。开发环境使用较低的资源配置,允许频繁的部署和实验,是验证新想法的理想场所。

mainmaster 分支是生产分支,只包含经过充分测试的稳定代码。推送到这个分支的代码会触发生产环境的部署流程,但通常需要人工审批才能执行。生产环境使用高规格配置,启用完整的安全和监控措施。

feature/* 分支用于开发独立的功能。您可以为每个功能分支创建临时的预览环境,让产品经理或测试人员在隔离的环境中验证功能。预览环境在功能合并后自动销毁,避免资源浪费。

hotfix/* 分支用于紧急修复生产环境的问题。这类分支从 main 创建,修复完成后直接合并回 maindevelop,并触发快速部署流程。

不同环境应该使用独立的配置文件。开发环境使用 s.dev.yaml,配置较低的 CPU 和内存,启用详细的日志级别,方便调试。生产环境使用 s.prod.yaml,配置更高的资源规格,启用 VPC 网络隔离,配置日志服务和监控告警。这种配置隔离确保了环境的独立性,避免了配置错误导致的生产事故。

集成 GitHub Actions

GitHub Actions 是 GitHub 原生的 CI/CD 平台,与代码仓库深度集成,配置简单且功能强大。如果您的项目托管在 GitHub 上,它是最自然的选择。

构建基础工作流

GitHub Actions 的工作流定义在 .github/workflows 目录下的 YAML 文件中。让我们从一个完整的部署工作流开始,逐步理解每个部分的作用。

创建 .github/workflows/deploy.yml 文件:

name: Deploy to AgentRun

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main

env:
NODE_VERSION: '18'
PYTHON_VERSION: '3.12'

工作流的开头定义了触发条件。on.push 指定当代码推送到 maindevelop 分支时触发流水线,on.pull_request 则在创建 Pull Request 时触发,用于在合并前验证代码质量。环境变量部分定义了 Node.js 和 Python 的版本,这些版本应该与您的本地开发环境和生产运行环境保持一致。

接下来定义代码检查和测试任务:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: |
cd code
pip install -r requirements.txt

- name: Run linter
run: |
cd code
pip install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics

这个任务运行在 Ubuntu 虚拟机上,首先检出代码,然后配置 Python 环境,安装项目依赖,最后运行代码检查工具。flake8 的参数 --select=E9,F63,F7,F82 只检查严重的错误,如语法错误、未定义的变量等,避免因为代码风格问题阻塞部署。您可以根据团队规范调整检查规则。

开发环境的部署任务需要考虑依赖关系和触发条件:

  deploy-dev:
needs: test
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment:
name: development

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install Serverless Devs
run: npm install -g @serverless-devs/s

- name: Configure Aliyun credentials
run: |
s config add \
--AccountID ${{ secrets.ALIYUN_ACCOUNT_ID }} \
--AccessKeyID ${{ secrets.ALIYUN_ACCESS_KEY_ID }} \
--AccessKeySecret ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} \
--alias default

- name: Build
run: s build -t s.dev.yaml

- name: Deploy to development
run: s deploy -t s.dev.yaml --assume-yes

- name: Get endpoint URL
id: get-url
run: |
ENDPOINT_URL=$(s info -t s.dev.yaml 2>/dev/null | grep -oP 'https://[^\s]+' | head -1)
echo "endpoint_url=$ENDPOINT_URL" >> $GITHUB_OUTPUT
echo "Endpoint URL: $ENDPOINT_URL"

- name: Health check
if: steps.get-url.outputs.endpoint_url != ''
run: |
sleep 10
ENDPOINT_URL="${{ steps.get-url.outputs.endpoint_url }}"
echo "Testing endpoint: ${ENDPOINT_URL}/openai/v1/chat/completions"

curl -f "${ENDPOINT_URL}/openai/v1/chat/completions" \
-X POST \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "hello"}], "stream": false}' \
|| echo "Health check failed, but continuing..."

needs: test 表示这个任务依赖于 test 任务,只有在代码检查通过后才会执行。if: github.ref == 'refs/heads/develop' 限制了触发条件,只有 develop 分支的推送才会部署到开发环境。environment: development 启用了 GitHub 的环境保护功能,可以在 GitHub 仓库设置中为这个环境配置额外的保护规则。

配置凭证时使用 ${{ secrets.* }} 语法引用 GitHub Secrets 中存储的敏感信息。这些 Secrets 在工作流日志中会被自动遮蔽,确保不会泄露。--assume-yes 参数让 Serverless Devs 跳过所有确认提示,适合在自动化流水线中使用。

生产环境的部署流程与开发环境类似,但触发条件和配置不同:

  deploy-prod:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install Serverless Devs
run: npm install -g @serverless-devs/s

- name: Configure Aliyun credentials
run: |
s config add \
--AccountID ${{ secrets.ALIYUN_ACCOUNT_ID_PROD }} \
--AccessKeyID ${{ secrets.ALIYUN_ACCESS_KEY_ID_PROD }} \
--AccessKeySecret ${{ secrets.ALIYUN_ACCESS_KEY_SECRET_PROD }} \
--alias production

- name: Build
run: s build -t s.prod.yaml

- name: Deploy to production
run: s deploy -t s.prod.yaml --assume-yes

- name: Get endpoint URL
id: get-url
run: |
ENDPOINT_URL=$(s info -t s.prod.yaml 2>/dev/null | grep -oP 'https://[^\s]+' | head -1)
echo "endpoint_url=$ENDPOINT_URL" >> $GITHUB_OUTPUT
echo "Production Endpoint URL: $ENDPOINT_URL"

生产环境使用独立的 Secrets(ALIYUN_ACCOUNT_ID_PROD 等),这样即使开发环境的凭证泄露,也不会影响生产环境的安全。在 GitHub 仓库设置中,可以为 production 环境配置审批流程,要求特定人员审批后才能部署,这为生产发布增加了一道安全防线。

配置 Secrets 和环境保护

在 GitHub 仓库中配置 Secrets 是集成的关键步骤。进入仓库的 Settings → Secrets and variables → Actions,点击 "New repository secret" 添加凭证。

对于开发环境,需要添加三个 Secrets:

  • ALIYUN_ACCOUNT_ID:您的阿里云账号 ID,这是一个数字,可以在阿里云控制台右上角的账号信息中找到
  • ALIYUN_ACCESS_KEY_ID:访问密钥 ID,用于身份验证
  • ALIYUN_ACCESS_KEY_SECRET:访问密钥 Secret,对应的密钥内容

生产环境建议使用独立的凭证,以实现环境隔离:

  • ALIYUN_ACCOUNT_ID_PROD
  • ALIYUN_ACCESS_KEY_ID_PROD
  • ALIYUN_ACCESS_KEY_SECRET_PROD

如果您有多个阿里云账号,可以为不同环境使用不同账号,这样可以实现账户级别的隔离,提供最高级别的安全保障。

除了配置 Secrets,还应该为生产环境设置保护规则。在 Settings → Environments 中,选择或创建 production 环境,可以配置以下保护措施:

首先是必需审批者。指定一个或多个团队成员,部署到生产环境前必须得到他们的批准。这确保了关键变更经过人工审查,避免了自动化流水线将有问题的代码直接推到生产环境。

其次是等待计时器。设置一个延迟时间,例如 5 分钟,在这段时间内部署不会开始。这给团队成员一个窗口期来取消可能有问题的部署。

第三是限制分支。只允许特定分支(如 main)部署到生产环境,防止意外的部署。

这些保护措施共同构建了安全的发布流程,在自动化效率和人工把控之间取得平衡。

实现灰度发布工作流

灰度发布是生产环境推荐的发布策略,它允许您先将新版本暴露给一小部分用户,观察效果后再全量发布。创建 .github/workflows/canary.yml 文件:

name: Canary Deployment

on:
workflow_dispatch:
inputs:
target_version:
description: 'Target version number for canary'
required: true
type: string
traffic_weight:
description: 'Traffic weight (0.0-1.0)'
required: true
default: '0.1'
type: string

jobs:
canary-deploy:
runs-on: ubuntu-latest
environment:
name: production

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Install Serverless Devs
run: npm install -g @serverless-devs/s

- name: Configure credentials
run: |
s config add \
--AccountID ${{ secrets.ALIYUN_ACCOUNT_ID_PROD }} \
--AccessKeyID ${{ secrets.ALIYUN_ACCESS_KEY_ID_PROD }} \
--AccessKeySecret ${{ secrets.ALIYUN_ACCESS_KEY_SECRET_PROD }} \
--alias production

- name: Create/Update canary endpoint
run: |
s endpoint publish \
--endpoint-name canary \
--target-version ${{ inputs.target_version }} \
--weight ${{ inputs.traffic_weight }} \
--description "Canary deployment - ${{ inputs.traffic_weight }} traffic to version ${{ inputs.target_version }}" \
-t s.prod.yaml

- name: Get canary endpoint
run: |
echo "Canary endpoint created/updated successfully"
s endpoint get --endpoint-name canary -t s.prod.yaml

这个工作流使用 workflow_dispatch 触发器,意味着它需要手动触发。在 GitHub Actions 页面,您可以选择这个工作流,输入目标版本号和流量权重,然后点击"Run workflow"执行。这种设计给了您完全的控制权,可以根据实际情况灵活调整灰度策略。

target_version 参数指定要灰度发布的版本号,这个版本应该是之前通过正常部署流程发布的版本。traffic_weight 参数是一个 0 到 1 之间的小数,表示路由到新版本的流量比例。例如 0.1 表示 10% 的流量,0.5 表示 50% 的流量。

实际使用时,典型的灰度流程是这样的:首先将新代码合并到 main 分支并部署,这会创建新版本但不会立即接收流量。然后手动触发 canary 工作流,设置 10% 的流量到新版本。观察 10-30 分钟,检查错误率、响应时间等指标。如果一切正常,再次触发工作流,逐步将流量提高到 50%、100%。

类似地,您还可以创建版本发布工作流 .github/workflows/release.yml:

name: Publish Version

on:
workflow_dispatch:
inputs:
description:
description: 'Version description'
required: false
type: string

jobs:
publish-version:
runs-on: ubuntu-latest
environment:
name: production

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Install Serverless Devs
run: npm install -g @serverless-devs/s

- name: Configure credentials
run: |
s config add \
--AccountID ${{ secrets.ALIYUN_ACCOUNT_ID_PROD }} \
--AccessKeyID ${{ secrets.ALIYUN_ACCESS_KEY_ID_PROD }} \
--AccessKeySecret ${{ secrets.ALIYUN_ACCESS_KEY_SECRET_PROD }} \
--alias production

- name: Publish version
run: |
if [ -n "${{ inputs.description }}" ]; then
s version publish --description "${{ inputs.description }}" -t s.prod.yaml
else
s version publish -t s.prod.yaml
fi

- name: List versions
run: s version list -t s.prod.yaml

这个工作流将当前部署发布为正式版本,并添加描述信息。发布版本后,可以在灰度发布工作流中引用版本号,实现清晰的版本管理。

集成 GitLab CI

GitLab CI 是 GitLab 内置的 CI/CD 平台,与 GitLab 仓库无缝集成。如果您的团队使用 GitLab 托管代码,它提供了强大且灵活的流水线功能。

设计流水线结构

GitLab CI 的配置文件是项目根目录的 .gitlab-ci.yml。GitLab CI 使用阶段(stages)和任务(jobs)的概念组织流水线,每个阶段可以包含多个并行执行的任务,不同阶段按顺序执行。

创建 .gitlab-ci.yml 文件:

stages:
- test
- build
- deploy

variables:
NODE_VERSION: "18"
PYTHON_VERSION: "3.12"

test:
stage: test
image: python:${PYTHON_VERSION}
script:
- cd code
- pip install -r requirements.txt
- pip install flake8
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
only:
- develop
- main

build:dev:
stage: build
image: node:${NODE_VERSION}
only:
- develop
before_script:
- npm install -g @serverless-devs/s
- |
s config add \
--AccountID $ALIYUN_ACCOUNT_ID \
--AccessKeyID $ALIYUN_ACCESS_KEY_ID \
--AccessKeySecret $ALIYUN_ACCESS_KEY_SECRET \
--alias default
script:
- s build -t s.dev.yaml
artifacts:
paths:
- .s/
expire_in: 1 hour

build:prod:
stage: build
image: node:${NODE_VERSION}
only:
- main
before_script:
- npm install -g @serverless-devs/s
- |
s config add \
--AccountID $ALIYUN_ACCOUNT_ID_PROD \
--AccessKeyID $ALIYUN_ACCESS_KEY_ID_PROD \
--AccessKeySecret $ALIYUN_ACCESS_KEY_SECRET_PROD \
--alias production
script:
- s build -t s.prod.yaml
artifacts:
paths:
- .s/
expire_in: 1 hour

deploy:dev:
stage: deploy
image: node:${NODE_VERSION}
only:
- develop
environment:
name: development
on_stop: stop:dev
dependencies:
- build:dev
before_script:
- npm install -g @serverless-devs/s
- |
s config add \
--AccountID $ALIYUN_ACCOUNT_ID \
--AccessKeyID $ALIYUN_ACCESS_KEY_ID \
--AccessKeySecret $ALIYUN_ACCESS_KEY_SECRET \
--alias default
script:
- s deploy -t s.dev.yaml --assume-yes
- echo "Deployment completed"
- s info -t s.dev.yaml
after_script:
- |
ENDPOINT_URL=$(s info -t s.dev.yaml 2>/dev/null | grep -oP 'https://[^\s]+' | head -1)
if [ -n "$ENDPOINT_URL" ]; then
echo "Testing endpoint: ${ENDPOINT_URL}/openai/v1/chat/completions"
sleep 10
curl -f "${ENDPOINT_URL}/openai/v1/chat/completions" \
-X POST \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "hello"}], "stream": false}' \
|| echo "Health check warning"
fi

deploy:prod:
stage: deploy
image: node:${NODE_VERSION}
only:
- main
when: manual
environment:
name: production
dependencies:
- build:prod
before_script:
- npm install -g @serverless-devs/s
- |
s config add \
--AccountID $ALIYUN_ACCOUNT_ID_PROD \
--AccessKeyID $ALIYUN_ACCESS_KEY_ID_PROD \
--AccessKeySecret $ALIYUN_ACCESS_KEY_SECRET_PROD \
--alias production
script:
- s deploy -t s.prod.yaml --assume-yes
- echo "Production deployment completed"
- s info -t s.prod.yaml

stop:dev:
stage: deploy
image: node:${NODE_VERSION}
only:
- develop
when: manual
environment:
name: development
action: stop
before_script:
- npm install -g @serverless-devs/s
- |
s config add \
--AccountID $ALIYUN_ACCOUNT_ID \
--AccessKeyID $ALIYUN_ACCESS_KEY_ID \
--AccessKeySecret $ALIYUN_ACCESS_KEY_SECRET \
--alias default
script:
- s remove -t s.dev.yaml -y

这个配置定义了三个阶段:测试阶段运行代码检查,构建阶段生成部署包,部署阶段将应用发布到目标环境。image 字段指定任务运行的 Docker 镜像。before_script 在主脚本之前执行,用于准备运行环境。artifacts 部分将构建产物保存起来,后续的部署任务可以下载这些产物而无需重新构建。

when: manual 让生产环境的部署任务需要手动触发。流水线运行到这里会暂停,等待授权人员点击"播放"按钮才会继续。这为生产发布增加了人工审核环节,确保关键变更经过充分评估。

配置 GitLab CI/CD 变量

GitLab CI/CD 的变量配置在项目的 Settings → CI/CD → Variables 中。点击"Add variable"添加每个凭证。

对于开发环境,添加三个变量:

  • Key: ALIYUN_ACCOUNT_ID, Value: 您的账号 ID, Protected: 不勾选, Masked: 不勾选
  • Key: ALIYUN_ACCESS_KEY_ID, Value: 您的 AK ID, Protected: 不勾选, Masked: 不勾选
  • Key: ALIYUN_ACCESS_KEY_SECRET, Value: 您的 AK Secret, Protected: 不勾选, Masked: 勾选

对于生产环境,使用独立的变量名:

  • Key: ALIYUN_ACCOUNT_ID_PROD, Value: 生产账号 ID, Protected: 勾选, Masked: 不勾选
  • Key: ALIYUN_ACCESS_KEY_ID_PROD, Value: 生产 AK ID, Protected: 勾选, Masked: 不勾选
  • Key: ALIYUN_ACCESS_KEY_SECRET_PROD, Value: 生产 AK Secret, Protected: 勾选, Masked: 勾选

"Protected"选项让变量只在受保护的分支(如 main)上可用,增加了安全性。"Masked"选项让变量的值在日志中自动遮蔽,防止意外泄露敏感信息。

集成 Jenkins

Jenkins 是最成熟的 CI/CD 平台之一,拥有丰富的插件生态和高度的可定制性。如果您的组织已经在使用 Jenkins,可以通过 Jenkinsfile 将 AgentRun 应用集成到现有的流水线体系中。

创建声明式流水线

Jenkins 支持两种流水线语法:声明式和脚本式。声明式流水线使用结构化的语法,更易于理解和维护,是推荐的选择。在项目根目录创建 Jenkinsfile:

pipeline {
agent any

environment {
NODE_VERSION = '18'
PYTHON_VERSION = '3.12'
}

stages {
stage('Checkout') {
steps {
checkout scm
}
}

stage('Test') {
agent {
docker {
image "python:${PYTHON_VERSION}"
reuseNode true
}
}
steps {
dir('code') {
sh '''
pip install -r requirements.txt
pip install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
'''
}
}
}

stage('Build') {
agent {
docker {
image "node:${NODE_VERSION}"
reuseNode true
}
}
steps {
script {
def configFile = env.BRANCH_NAME == 'main' ? 's.prod.yaml' : 's.dev.yaml'
def credentialsId = env.BRANCH_NAME == 'main' ? 'aliyun-prod-credentials' : 'aliyun-dev-credentials'

withCredentials([
string(credentialsId: "${credentialsId}-account-id", variable: 'ACCOUNT_ID'),
string(credentialsId: "${credentialsId}-access-key-id", variable: 'ACCESS_KEY_ID'),
string(credentialsId: "${credentialsId}-access-key-secret", variable: 'ACCESS_KEY_SECRET')
]) {
sh '''
npm install -g @serverless-devs/s
s config add \
--AccountID ${ACCOUNT_ID} \
--AccessKeyID ${ACCESS_KEY_ID} \
--AccessKeySecret ${ACCESS_KEY_SECRET} \
--alias default
'''
sh "s build -t ${configFile}"
}
}
}
}

stage('Deploy to Development') {
when {
branch 'develop'
}
agent {
docker {
image "node:${NODE_VERSION}"
reuseNode true
}
}
steps {
withCredentials([
string(credentialsId: 'aliyun-dev-credentials-account-id', variable: 'ACCOUNT_ID'),
string(credentialsId: 'aliyun-dev-credentials-access-key-id', variable: 'ACCESS_KEY_ID'),
string(credentialsId: 'aliyun-dev-credentials-access-key-secret', variable: 'ACCESS_KEY_SECRET')
]) {
sh '''
npm install -g @serverless-devs/s
s config add \
--AccountID ${ACCOUNT_ID} \
--AccessKeyID ${ACCESS_KEY_ID} \
--AccessKeySecret ${ACCESS_KEY_SECRET} \
--alias default
s deploy -t s.dev.yaml --assume-yes
echo "Development deployment completed"
s info -t s.dev.yaml
'''
}
}
}

stage('Deploy to Production') {
when {
branch 'main'
}
agent {
docker {
image "node:${NODE_VERSION}"
reuseNode true
}
}
steps {
input message: 'Deploy to production?', ok: 'Deploy'

withCredentials([
string(credentialsId: 'aliyun-prod-credentials-account-id', variable: 'ACCOUNT_ID'),
string(credentialsId: 'aliyun-prod-credentials-access-key-id', variable: 'ACCESS_KEY_ID'),
string(credentialsId: 'aliyun-prod-credentials-access-key-secret', variable: 'ACCESS_KEY_SECRET')
]) {
sh '''
npm install -g @serverless-devs/s
s config add \
--AccountID ${ACCOUNT_ID} \
--AccessKeyID ${ACCESS_KEY_ID} \
--AccessKeySecret ${ACCESS_KEY_SECRET} \
--alias production
s deploy -t s.prod.yaml --assume-yes
echo "Production deployment completed"
s info -t s.prod.yaml
'''
}
}
}
}

post {
success {
echo "Pipeline completed successfully!"
}
failure {
echo "Pipeline failed!"
}
}
}

agent { docker } 指定阶段在 Docker 容器中运行。reuseNode true 让容器在同一个节点上运行,可以访问之前检出的代码。withCredentials 从 Jenkins 凭证管理器中获取敏感信息,并将其注入到环境变量中。凭证只在这个块内可用,执行完毕后自动清理,确保了安全性。

input 步骤会暂停流水线,在 Jenkins UI 中显示确认对话框。只有授权用户点击"Deploy"按钮后,流水线才会继续执行。

配置 Jenkins 凭证

在 Jenkins 中配置凭证的步骤如下。首先进入 Jenkins 主页,点击"Manage Jenkins" → "Manage Credentials"。选择适当的域(通常是 Global),点击"Add Credentials"。

对于每个凭证,选择"Secret text"类型,然后填写:

开发环境账号 ID:

  • Kind: Secret text
  • Scope: Global
  • Secret: 您的账号 ID
  • ID: aliyun-dev-credentials-account-id
  • Description: Aliyun Dev Account ID

开发环境访问密钥 ID:

  • Kind: Secret text
  • Scope: Global
  • Secret: 您的 AccessKey ID
  • ID: aliyun-dev-credentials-access-key-id
  • Description: Aliyun Dev AccessKey ID

开发环境访问密钥 Secret:

  • Kind: Secret text
  • Scope: Global
  • Secret: 您的 AccessKey Secret
  • ID: aliyun-dev-credentials-access-key-secret
  • Description: Aliyun Dev AccessKey Secret

生产环境使用相同的方式配置,但使用不同的 ID:

  • aliyun-prod-credentials-account-id
  • aliyun-prod-credentials-access-key-id
  • aliyun-prod-credentials-access-key-secret

ID 必须与 Jenkinsfile 中引用的一致。配置完成后,这些凭证可以在流水线中安全地使用,Jenkins 会确保它们不会出现在日志中。

集成其他 CI/CD 平台

除了前面详细介绍的三个平台,AgentRun 还可以与许多其他 CI/CD 系统集成。这里简要介绍几个常用平台的配置要点。

阿里云云效 DevOps

云效是阿里云提供的 DevOps 平台,与阿里云服务深度集成。如果您的基础设施完全在阿里云上,云效是非常自然的选择。创建 .aliyun-ci.yml 文件来定义流水线。云效的配置结构与 GitLab CI 类似,使用阶段和任务组织流水线。

一个关键的优势是云效可以直接使用阿里云 RAM 角色进行身份验证,无需在配置中传递 AccessKey,这提供了更高的安全性。此外,云效还提供了审批流程的原生支持,可以在生产环境部署阶段配置 approval 字段,指定审批人员和审批策略。

Bitbucket Pipelines

Bitbucket Pipelines 是 Atlassian Bitbucket 的 CI/CD 功能,如果您使用 Bitbucket 托管代码,可以通过 bitbucket-pipelines.yml 文件配置流水线。

Bitbucket Pipelines 的一个特点是使用"定义"(definitions)来复用配置。您可以在 definitions.steps 中定义可重用的步骤,然后在不同的流水线中引用它们,避免重复配置。另一个特点是部署环境的原生支持,通过 deployment 字段指定部署目标,Bitbucket 会在部署页面显示历史记录。

CircleCI

CircleCI 是一个流行的云端 CI/CD 平台,以速度和并行能力著称。通过 .circleci/config.yml 文件配置流水线。

CircleCI 的工作流(workflows)概念允许您定义复杂的任务依赖关系和并行策略。例如,可以让测试任务和构建任务并行执行以节省时间,然后让部署任务等待它们都完成。CircleCI 还提供了"手动批准"(approval)任务类型,可以在工作流中插入人工审批环节。

所有这些平台的集成遵循相同的核心模式:检出代码、运行测试、构建部署包、部署到目标环境、验证部署结果。选择哪个平台主要取决于您的团队已经在使用什么工具,以及特定平台的独特优势是否符合您的需求。

生产环境最佳实践

自动化流水线搭建起来后,如何让它真正为生产环境服务?这需要遵循一些经过验证的最佳实践,涵盖安全性、可靠性、可观测性等多个维度。

实施最小权限原则

CI/CD 系统需要访问云端资源来执行部署,但不应该给予过多的权限。遵循最小权限原则,为 CI/CD 创建专用的 RAM 用户,只授予必要的权限。

AgentRun 推荐使用以下系统权限策略组合:

核心权限(必需):

  • AliyunAgentRunFullAccess - AgentRun 服务的完整管理权限
  • AliyunFCFullAccess - 函数计算服务权限(AgentRun 底层依赖)
  • AliyunDevsFullAccess - Serverless Devs 工具所需权限

辅助权限(按需配置):

  • AliyunRAMReadOnlyAccess - 查看角色和权限信息
  • AliyunOSSReadOnlyAccess - 查看 OSS 文件列表(使用 OSS 代码包部署时需要)
  • AliyunLogReadOnlyAccess - 查看日志信息
  • AliyunVPCReadOnlyAccess - 获取 VPC 配置信息(使用 VPC 网络时需要)
  • AliyunContainerRegistryReadOnlyAccess - 访问容器镜像(使用容器部署时需要)
  • AliyunARMSReadOnlyAccess - 访问应用监控服务

在 RAM 控制台为 CI/CD 专用用户授予这些策略,具体操作请参考授权RAM用户使用AgentRun

这个策略组合只包含部署 AgentRun 应用所需的权限集。它不包括删除其他资源、修改安全组、变更网络配置等危险操作的权限。如果 CI/CD 凭证泄露,攻击者的破坏能力被限制在很小的范围内。

定期审查和收紧权限。随着应用的演进,某些权限可能不再需要,应该及时移除。使用阿里云的 ActionTrail 服务监控 API 调用,识别未使用的权限。

此外,应该定期轮换 AccessKey。建议每季度创建新的 AccessKey,更新 CI/CD 平台的配置,然后删除旧的 AccessKey。这个过程可以通过脚本自动化,进一步降低泄露风险。

实现严格的环境隔离

不同环境应该在多个层面实现隔离,避免互相影响。最理想的方案是为每个环境使用独立的阿里云账号,这样可以实现账户级别的完全隔离。即使某个环境被攻破,也无法影响其他环境。

如果使用单个账号,则应该为每个环境创建独立的 RAM 用户,使用不同的 AccessKey。开发环境的凭证即使泄露,也无法访问生产环境的资源。

资源命名也应该明确区分环境。例如,开发环境的 Agent Runtime 命名为 myagent-dev,生产环境命名为 myagent-prod。这避免了配置错误导致的意外部署到错误环境。

网络层面,生产环境应该部署在 VPC 中,配置安全组规则严格控制访问。开发环境可以使用更宽松的配置,方便调试和测试。日志和监控也应该分别配置,避免开发环境的大量日志干扰生产环境的分析。

选择合适的部署策略

不同的部署策略适用于不同的场景,理解它们的特点可以帮助您选择最合适的方案。

蓝绿部署维护两个完全相同的生产环境,一个是当前服务用户的蓝色环境,另一个是即将发布的绿色环境。部署时,首先在绿色环境部署新版本,进行充分测试后,将流量一次性切换到绿色环境。如果出现问题,可以立即切回蓝色环境。

蓝绿部署的优势是切换快速且彻底,非常适合有严格 SLA 要求的关键业务。缺点是需要双倍的资源,成本较高。在 AgentRun 中实现蓝绿部署的方式如下:

# 1. 部署新版本
s deploy -t s.prod.yaml

# 2. 发布为版本 2
s version publish --description "v2.0" -t s.prod.yaml

# 3. 创建蓝色端点指向新版本
s endpoint publish --endpoint-name blue --target-version 2 -t s.prod.yaml

# 4. 测试蓝色端点
# 运行自动化测试和手动验证

# 5. 切换生产流量到蓝色环境
s endpoint publish --endpoint-name production --target-version 2 -t s.prod.yaml

# 6. 保留绿色环境用于快速切换回去
s endpoint publish --endpoint-name green --target-version 1 -t s.prod.yaml

金丝雀发布(也称灰度发布)是另一种常用策略,它将新版本逐步推广给越来越多的用户。首先只将很小比例(如 5%)的流量路由到新版本,观察一段时间后,如果没有问题,逐步增加比例到 10%、25%、50%,最终达到 100%。

金丝雀发布的优势是风险可控,即使新版本有问题,影响范围也很小。它特别适合用户体验敏感的场景,可以在早期发现问题并及时调整。缺点是发布周期较长,需要更多的监控和分析工作。

# 1. 部署新版本并发布
s deploy -t s.prod.yaml
s version publish --description "v2.0" -t s.prod.yaml

# 2. 5% 流量到新版本
s endpoint publish --endpoint-name canary --target-version 2 --weight 0.05 -t s.prod.yaml

# 3. 观察 10-30 分钟,检查错误率、延迟等指标

# 4. 逐步增加流量
s endpoint publish --endpoint-name canary --target-version 2 --weight 0.10 -t s.prod.yaml
# 继续观察...

s endpoint publish --endpoint-name canary --target-version 2 --weight 0.50 -t s.prod.yaml
# 继续观察...

# 5. 全量发布
s endpoint publish --endpoint-name production --target-version 2 -t s.prod.yaml

选择部署策略时,需要考虑业务特点、用户规模、SLA 要求等因素。对于初创产品或内部工具,简单的直接替换可能就足够了。对于有大量用户的生产服务,金丝雀发布是更稳妥的选择。对于有严格可用性要求的关键系统,蓝绿部署提供了最快的切换能力。

构建完善的监控和告警体系

可观测性是生产系统的生命线,没有监控的系统就像盲人开车。为 AgentRun 应用建立完善的监控和告警体系,需要关注多个层面的指标。

业务层面的指标反映了用户体验:

  • 请求成功率应该保持在 99% 以上,低于这个阈值说明系统存在问题
  • 响应时间的 P50、P90、P99 分位值可以揭示性能特征,P99 响应时间尤其重要
  • 请求量和 QPS 的趋势可以帮助您了解业务增长和流量模式

系统层面的指标反映了资源使用情况:

  • 实例数量和并发度显示了系统的负载状况
  • 冷启动次数和延迟影响用户体验
  • 内存和 CPU 使用率如果长期过高,说明资源配置不足

成本层面的指标帮助您控制开支:

  • 模型调用次数和 Token 消耗直接影响成本
  • 沙箱使用时长和次数也会产生费用
  • 通过分析这些指标,可以找到优化成本的机会

在 CI/CD 流水线中集成监控验证是一个好习惯。部署完成后,自动查询关键指标,如果发现异常就发出警告:

# 部署后等待一段时间
sleep 300

# 查询最近 5 分钟的错误率
ERROR_COUNT=$(s logs --type fail --start-time "5 minutes ago" -t s.prod.yaml 2>/dev/null | wc -l)

# 如果错误数量过多,发出告警
if [ $ERROR_COUNT -gt 10 ]; then
echo "⚠️ Warning: High error rate detected - $ERROR_COUNT errors in last 5 minutes"
echo "Please check logs and consider switching back to previous version"
exit 1
fi

echo "✓ Deployment validated successfully"

告警配置同样重要。为关键指标设置阈值,当指标超过阈值时立即通知相关人员:

  • P0 级告警:系统完全不可用,通过短信或电话立即通知,任何时间都要响应
  • P1 级告警:系统部分功能异常,应该在 30 分钟内响应
  • P2 级告警:潜在问题,应该在工作时间处理

通过邮件、钉钉、Slack 等渠道发送告警,确保信息能够及时送达。告警消息应该包含足够的上下文信息,例如环境名称、影响范围、异常指标的具体值、日志查询链接等。

集成通知机制

在 CI/CD 流水线中集成通知,让团队成员及时了解部署状态。

Slack 通知(GitHub Actions):

- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "AgentRun Deployment ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Environment:* ${{ github.environment }}\n*Status:* ${{ job.status }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

钉钉通知:

# 在部署后发送钉钉通知
curl -X POST $DINGTALK_WEBHOOK \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "AgentRun 部署通知",
"text": "## 部署成功\n\n- **环境**: 生产环境\n- **版本**: v2.0\n- **提交**: '"$COMMIT_SHA"'\n- **时间**: '"$(date)"'"
}
}'

部署后验证

在 CI/CD 流水线中添加自动化验证,确保部署的应用正常工作:

# 健康检查
ENDPOINT_URL=$(s info -t s.prod.yaml 2>/dev/null | grep -oP 'https://[^\s]+' | head -1)

# 基础测试
curl -f "${ENDPOINT_URL}/openai/v1/chat/completions" \
-X POST \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "test"}], "stream": false}'

# 性能测试(使用 ab 或 wrk)
ab -n 100 -c 10 -p test_payload.json -T application/json "${ENDPOINT_URL}/openai/v1/chat/completions"

优化流水线性能

随着项目的发展,CI/CD 流水线可能会变得越来越慢,影响开发效率。优化流水线性能可以从多个角度入手。

并行化。识别可以并行执行的任务,让它们同时运行。例如,代码检查和单元测试通常可以并行,构建不同环境的部署包也可以并行。

缓存。重复安装依赖是最耗时的环节之一,使用缓存可以大大加快速度:

- name: Cache Python dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: pip install -r requirements.txt

增量构建。如果代码没有变化,就不需要重新构建:

- name: Check if code changed
id: code-changes
run: |
if git diff --name-only HEAD~1 | grep -q "^code/"; then
echo "code_changed=true" >> $GITHUB_OUTPUT
else
echo "code_changed=false" >> $GITHUB_OUTPUT
fi

- name: Build
if: steps.code-changes.outputs.code_changed == 'true'
run: s build -t s.prod.yaml

使用更快的工具。例如,用 uv 替代 pip 安装 Python 依赖可以节省大量时间。用轻量级的基础镜像(如 Alpine)替代完整的 Ubuntu 镜像可以减少下载和启动时间。

常见问题和故障排查

在实施 CI/CD 的过程中,您可能会遇到各种问题。以下是一些常见问题的诊断和解决方法。

问题:构建失败,提示"Docker is not running"

这个问题通常出现在需要使用 Docker 执行构建的 CI/CD 平台上。原因是 CI/CD 环境中的 Docker 服务未正确配置或启动。

GitHub Actions 解决方案:

services:
docker:
image: docker:dind
options: --privileged

GitLab CI 解决方案:

services:
- docker:dind

variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""

Jenkins 解决方案:

确保构建节点上的 Docker 守护进程正在运行,或者使用 Docker agent。

问题:部署超时或非常缓慢

部署慢通常有几个原因。首先是构建包太大,上传和解压需要很长时间。检查 requirements.txt,移除不必要的依赖,使用更轻量的替代品。启用构建缓存,避免每次都重新安装所有依赖。

其次可能是网络问题,从 CI/CD 环境访问阿里云的速度较慢。考虑使用 OSS 代码包部署,先将构建产物上传到 OSS,然后在部署时引用:

agent:
code:
ossBucketName: my-code-bucket
ossObjectName: agent-code-${{ github.sha }}.zip
language: python3.12
command: [python3, app.py]

第三个原因可能是并发限制。如果同时有多个部署任务,可能会相互竞争资源。在流水线中添加并发控制:

concurrency:
group: production-deployment
cancel-in-progress: false

问题:凭证配置错误,无法访问阿里云资源

凭证错误的表现是部署失败,错误信息提示 AccessKey 无效或权限不足。

首先验证 Secrets 中的凭证是否正确。在 CI/CD 平台的 Secrets 配置中,仔细检查每个值是否准确,特别注意不要有多余的空格或换行符。

然后测试凭证的有效性。在流水线中添加验证步骤:

# 验证凭证配置
s config get

# 测试 API 调用权限
s info -t s.yaml || echo "Failed to retrieve agent info, please check credentials and permissions"

如果凭证本身是正确的但仍然失败,检查 RAM 用户的权限。在阿里云控制台进入 RAM → 用户 → 权限管理,确认用户被授予了必要的策略。

问题:端点 URL 无法访问或返回错误

部署成功但端点无法访问,可能有多个原因。

首先检查 Agent Runtime 的状态:

s info -t s.yaml

查看输出中的 status 字段,应该是 READY。如果是 CREATINGUPDATING,说明部署尚未完成,需要等待。如果是 FAILED,说明部署失败,需要查看日志找出原因:

s logs --type fail -t s.yaml

其次检查端点配置:

s endpoint list -t s.yaml
s endpoint get --endpoint-name production -t s.yaml

确认端点指向了正确的版本,URL 格式正确。

第三,检查网络配置。如果配置了 VPC,确保安全组规则允许访问。如果配置了 internetAccess: false,端点可能只能从内网访问。

最后,查看 Agent 应用的日志,可能是代码本身的问题:

s logs --tail -t s.yaml

健康检查失败通常会在日志中留下线索,例如模型调用失败、沙箱启动超时、环境变量缺失等。

问题:灰度发布后新版本错误率较高

灰度发布的目的就是在小范围内发现问题,这种情况下应该立即停止灰度,将流量切回稳定版本:

# 将 canary 端点的流量设为 0
s endpoint publish --endpoint-name canary --target-version $NEW_VERSION --weight 0 -t s.prod.yaml

# 或者将 production 端点切换回旧版本
s endpoint publish --endpoint-name production --target-version $OLD_VERSION -t s.prod.yaml

然后分析问题原因。查看新版本的详细日志:

s logs --type fail --start-time "30 minutes ago" -t s.prod.yaml

对比新旧版本的差异,定位引入问题的变更。修复问题后,重新部署并进行更谨慎的灰度,例如从 1% 的流量开始,每次增加的幅度更小,观察的时间更长。

总结

通过实施 CI/CD 自动化,您可以获得诸多收益。首先是效率的显著提升,自动化流水线让部署从几十分钟缩短到几分钟,开发者可以更频繁地发布新功能,快速响应用户需求。其次是质量的保障,自动化测试在每次提交时运行,确保代码质量不会退化,减少了人为错误导致的生产事故。第三是可追溯性,每次部署都有完整的记录,包括变更内容、审批人员、部署时间等,便于审计和问题排查。

最重要的是,CI/CD 让团队能够更自信地进行变更。有了自动化测试的保护、灰度发布的验证、环境隔离的保障,开发者不再害怕部署到生产环境。这种信心鼓励创新和实验,推动产品持续改进。

选择适合您团队的 CI/CD 平台,遵循本指南介绍的最佳实践,构建可靠的自动化流水线。从简单的自动部署开始,逐步增加测试、监控、灰度发布等高级特性,不断优化和完善流程。CI/CD 不是一次性的项目,而是持续改进的过程,随着团队经验的积累和工具的演进,您的发布流程会变得越来越高效和可靠。