
简介这是一份面向 Azure DevOps 用户的 Azure Pipelines 官方文档操作手册适合需要搭建持续集成与持续交付流程的开发、运维及 DevOps 工程师阅读。内容系统覆盖核心概念与落地操作从代理、条件、表达式、环境、作业、阶段、任务、模板到触发器均有说明并针对 GitHub、Azure Repos、Bitbucket 等仓库集成、多环境部署、容器构建、测试执行及代理池管理等场景给出操作指南可帮助读者快速掌握管道编写与发布配置。资源为 1 个 PDF 文件整体大小 106.6MB页面内容为 Azure Pipelines 文档的中文版节选既适合新手从零了解 CI/CD 理念也对已使用者查阅具体任务参数与排错思路有参考价值。目前已有 412 人学习浏览。通过阅读这份手册读者可获得一份结构完整的 Azure Pipelines 速查资料便于在项目中落地自动化构建、测试与部署流程减少摸索时间。1. 从找不到代理开始为什么配置 Azure Pipelines 的第一步就让人想退坑某次帮同事调一个 Node.js 项目的持续集成YAML 文件写了三行自认为没什么可错的结果 pipeline 一跑就报错No agent found in pool。折腾了两个小时最后发现是自托管代理压根没装代理池名字还对不上。这个报错几乎每个刚接触 Azure Pipelines 的人都会遇到——它不会告诉你该装什么、该配什么只会给你一个毫无头绪的错误信息。Azure Pipelines 是 Azure DevOps 的核心 CI/CD 服务支持从 GitHub、Azure Repos、Bitbucket 拉代码在 Windows、Linux、macOS 上并行构建把 .NET、Java、Python、Node.js、Go 这类主流生态全部覆盖。但它的配置链路很长组织、项目、代理、服务连接、YAML 语法任何一环断了都跑不通。这篇笔记就是按我实际装过的顺序从版本选型、注册组织到写第一条 YAML、配自托管代理再到排错清单一步步给你走完一遍。2. 准备环境版本选型、免费额度与注册组织2.1 版本选型Azure DevOps Services、Server 2022 和 TFS 怎么选装 Azure Pipelines 之前先要分清你用的是哪一套环境因为注册方式、功能范围和踩坑点都不一样。Azure DevOps Services 是微软的云服务版本直接浏览器访问https://dev.azure.com/{你的组织名}就能用。它更新最快YAML 管道、环境、审批这些新特性都在这个版本上最先落地。私有项目每个月有 1800 分钟30 小时的免费管道运行时长公共项目可以直接免费跑管道这个额度对个人学习和小团队试用完全够。Azure DevOps Server 是自托管版本对应 2019、2022 两个大版本。它适合代码必须留在内网、不允许出域的场景。注意 Server 版本里某些 YAML 功能是缺失的比如环境的部署作业deployment job在 Server 2022 上支持但部分容器作业能力不如云版本。TFS2018 属于老古董了除非你被老项目绑死否则不建议新装因为很多文档里的新特性它都没有。判断方法很简单你的代码在 GitHub 或别人的云端仓库上选 Services代码只能留在公司内网选 Server。我一般建议个人开发者直接上 Services省掉自己维护服务器的成本。Server 版本要额外买许可证五个活跃用户以内用 Express 版是免费的但安装复杂度高不少。2.2 注册与创建组织免费启动流程无论用 Microsoft 账户还是 GitHub 账户都能注册 Azure DevOps。用 GitHub 账户注册有个前提如果 GitHub 邮箱关联的是支持 Azure AD 的组织就必须改用 Azure AD 账户登录否则会卡在授权那一步。打开 Azure DevOps 官网选择「免费启动」会进入组织创建页。需要填三项组织名称、主机位置、验证字符。组织名称就是将来访问 URL 的一部分比如填devops-learning最终地址就是https://dev.azure.com/devops-learning。主机位置一般选离你最近的数据中心国内用户通常选东南亚或东亚区域网络延迟更友好。提示组织名创建后可以改显示名称但 URL 的标识部分基本定了。起名别太随意后面所有 SSH 克隆地址、服务连接、pipeline 配置都会带上这个标识。创建完成后系统会提示创建一个项目。项目有公共和专用两种可见性CI/CD 学习阶段选专用就好。项目名最好和仓库名对应后面在 pipeline 里引用时会省事。接着用 Azure CLI 验证一下环境是否就绪az login az devops configure --defaults organizationhttps://dev.azure.com/devops-learning projectmy-first-project az devops user show --org https://dev.azure.com/devops-learning第一行是登录 Azure 账户第二行设置默认组织和项目让后续命令不用每次都带--org参数。第三行如果返回了用户信息说明命令行工具已经能访问你的 DevOps 组织了这一步可以作为环境是否配通的快速自检。2.3 代码仓库准备GitHub 还是 Azure ReposAzure Pipelines 支持的代码源不少GitHub、Azure Repos Git、Bitbucket Cloud、TFVC、Subversion。绝大多数新项目用的是 GitHub 或 Azure Repos。选 GitHub 的话pipeline 可以从 GitHub 仓库拉代码但需要在 Azure DevOps 里创建一个 GitHub 服务连接。选 Azure Repos 省掉服务连接这一步因为同在一个组织里天然打通。从实际操作体验来说我建议第一个 pipeline 用 Azure Repos 练手。原因有两点一是少配一层 GitHub 授权二是 Azure Repos 的 PR 分支策略和 pipeline 触发器的联动更直接。GitHub 集成可以等把 YAML 语法跑熟之后再加那时候就算遇到授权问题也知道是哪里断了。仓库里至少要先放一个代码文件。不需要多复杂一个 README 或者一个简单的main.py都行。因为后面创建 pipeline 时要能拉到一次代码空仓库会导致管道创建失败。3. 创建第一个管道从 YAML 到第一次绿勾3.1 两种管道形态YAML 与经典编辑器Azure Pipelines 提供两种定义方式YAML 管道和经典管道。YAML 管道的核心是一个azure-pipelines.yml文件放在代码仓库根目录。管道定义跟着代码走改了文件就是改了管道配置PR 里能看到管道变更的 diff这个特性在排查问题时非常有用。当前官方推荐的方式所有新功能基本都优先支持 YAML。经典管道是在网页界面上用拖拽表单的方式配置构建和发布配置存在 Azure DevOps 服务端不跟代码走。它也有存在价值发布管道Release Pipeline里的 Gates 功能部署前自动收集外部运行状况信号只在经典发布管道里可用YAML 里没有。另外任务组Task Group这个封装复用能力也是经典编辑器专属YAML 里对应的是模板Template。如果是新项目直接学 YAML。但要知道经典编辑器还活着老项目里大量存在遇到历史管道维护时至少得能读懂。3.2 最小可用的 azure-pipelines.yml逐字段拆解从空仓库开始第一个目标不是复杂部署而是让管道跑出一个绿勾。以下是我实际用过的最小 YAMLtrigger: - main pool: vmImage: ubuntu-latest steps: - script: echo Hello, Azure Pipelines! displayName: Print greeting - script: | python -c print(Python is ready) displayName: Check Python第一段trigger定义什么时候触发管道main表示推送到 main 分支就跑。第二段pool指定代理池这里用的是微软托管的 Ubuntu 镜像不需要自己维护机器。第三段steps是任务列表每个script就是一条命令displayName是这个步骤在运行日志里显示的名字。把默认分支改成main然后推到远端。推完之后去 Azure DevOps 页面的 Pipelines 菜单里应该能看到这次运行已经自动启动。点击进入运行详情看到两个步骤都变成绿色的对勾第一个管道就算跑通了。这里有个细节要注意YAML 的缩进语法比较严格steps下的每一项都要保持相同的缩进层级。我在实际使用中因为displayName缩进多了一格导致解析失败报错信息非常抽象根本看不出来是缩进问题。如果遇到 YAML 解析错误优先检查缩进和冒号后面有没有空格。3.3 从模板克隆与自定义页面上的「创建管道」向导其实不是从零写 YAML而是基于仓库类型和语言生成一个模板再让你在编辑器里改。选择 GitHub 或 Azure Repos 仓库后向导会扫描代码推荐对应模板。Node.js 项目会生成安装 npm 依赖、跑测试、发布工件的模板。Python 项目会生成pip install和pytest的模板。从模板起步的好处是官方模板已经帮你处理了缓存、依赖安装、条件执行这些细节。比如 Node.js 模板里会有- task: NodeTool0 inputs: versionSpec: 20.x displayName: Install Node.js这个NodeTool0任务专门负责在托管代理上安装指定版本的 Node.js。versionSpec就是版本号可以写成20.x这样的通配符。自己写的话得手动调apt-get或nvm用任务更可靠。模板能覆盖常见语言但一些特殊场景要自己加任务比如 Android 构建要装 JDK 和 Android SDKiOS 构建必须用 macOS 代理。模板只是开始真正生产级的管道基本都要手改。3.4 推送触发把第一个管道跑起来创建完 YAML 管道后管道会等代码推送触发。验证触发逻辑是否正常只需要改任意一个文件提交然后在 Pipelines 页面看运行列表里有没有新增一条记录。如果要同时验证 GitHub 和 Azure Repos 两种仓库源GitHub 那边需要在项目设置里创建 GitHub 服务连接。创建过程会引导跳转到 GitHub 授权页面授权时需要选择仓库访问范围选「仅限选定仓库」更安全避免把整个 GitHub 账号暴露给 Azure DevOps。注意服务连接的名称会在 YAML 里被引用。我习惯命名为github-read-only这种带用途的名字比默认的GitHub connection 1直观得多。我一般会故意把一个错误加进 YAML比如让脚本退出码非零确认管道确实会失败并展示红色状态然后再修回来。这一步能同时验证日志系统、失败通知和修复流程是花五分钟换来后面省半小时的做法。4. 把管道做厚阶段、作业、任务、模板与自托管代理4.1 阶段与作业从单步构建到多环境部署单个管道要支持多环境部署靠的是阶段stage和作业job两层编排。阶段是管道里最大的逻辑分组通常对应环境build、test、deploy-dev、deploy-prod。默认情况下阶段按顺序执行但可以用dependsOn控制依赖关系。作业是阶段内的执行单元一个阶段可以跑多个作业这些作业可以并行。stages: - stage: build displayName: Build Stage jobs: - job: compile pool: vmImage: ubuntu-latest steps: - script: echo Compiling... displayName: Compile code - stage: deploy_dev displayName: Deploy to Dev dependsOn: build condition: succeeded() jobs: - job: deploy pool: vmImage: ubuntu-latest steps: - script: echo Deploying to dev... displayName: Deploy devdependsOn: build表示这个阶段必须等 build 阶段跑完condition: succeeded()表示只有前面阶段全部成功后才会执行。这两个组合是最常见的部署闸门写法。condition是一个很关键的表达式字段。常见的取值有succeeded()、failed()、always()还有基于变量的判断如eq(variables[build.reason], PullRequest)后者可以区分推送触发的运行和 PR 触发的运行。多作业并行是托管代理最实用的能力。默认情况下 Azure DevOps 会给每个作业分配一个代理实例免费额度是按分钟计的并行作业越多消耗越快。自托管代理不会消耗 Microsoft 托管额度这也是很多团队搭自托管代理的原因。4.2 任务与脚本跑命令、装依赖、发工件任务是管道的最小执行单元。Azure Pipelines 内置了几百个任务常见的有NodeTool0装 Node、UsePythonVersion0选 Python 版本、DotNetCoreCLI2处理 .NET 命令、CopyFiles2复制文件和PublishBuildArtifacts1发布工件。通用任务无法覆盖的场景用script直接跑命令行、PowerShell 或 Shell。比如拉依赖和跑测试steps: - checkout: self - script: | python -m venv .venv source .venv/bin/activate pip install -r requirements.txt displayName: Install Python dependencies - script: | source .venv/bin/activate pytest tests/ --junitxmltest-results.xml displayName: Run tests with pytest - task: PublishTestResults2 inputs: testResultsFormat: JUnit testResultsFiles: test-results.xml displayName: Publish test results多行命令用|拼接每行一条命令。checkout: self显式拉取当前仓库代码默认行为也会拉但写出来更明确。PublishTestResults2把测试结果文件发布到管道运行记录里这样页面上能看到测试通过率、失败用例列表比只看命令行输出直观得多。发布工件是 CI 和 CD 的衔接点CI 阶段产出的文件通过PublishBuildArtifacts1保存CD 阶段再用下载任务取回来。工件有保留期设置默认 30 天生产环境上会设为 90 天或更长方便回溯历史版本。4.3 变量、变量组与运行时参数写死配置是管道维护的灾难源头。Azure Pipelines 的三层变量机制能避免这个问题。第一层是管道内变量直接在 YAML 里定义variables: buildConfiguration: Release imageName: myapp:${{ parameters.tag }}第二层是变量组Variable Group存在 Azure DevOps 库Library里可以跨管道共享。适合存不常变的配置比如服务器地址、告警 Webhook URL。同一个变量组可以被多个管道引用改一处就全部生效。第三层是密钥变量存在变量组里并标记为密钥运行时在日志里自动打码。数据库密码、API Key 这类敏感信息必须用这种方式不能明文写在 YAML 里。引用变量组的写法variables: - group: production-config - task: AzureCLI2 inputs: azureSubscription: prod-service-connection scriptType: bash scriptLocation: inlineScript inlineScript: | echo Deploying to $(serverUrl)$(serverUrl)会在运行时从production-config变量组里取值。注意 YAML 里变量引用的两种语法$(varName)是运行时替换模板里的${{ varName }}是模板展开时替换。前者在任务执行时解析后者在管道编译阶段就定下来了用错场景会导致取值不对。运行时参数Runtime Parameters是更灵活的输入方式parameters: - name: environment displayName: Deployment Target type: string default: dev values: - dev - staging - prod stages: - stage: deploy_${{ parameters.environment }} displayName: Deploy to ${{ parameters.environment }} jobs: - job: deploy_job steps: - script: echo Deploying to ${{ parameters.environment }}运行时参数可以在手动运行管道时从页面上选择也可以被 API 调用时传入。values限制了可选范围避免手滑部署到错误环境。我一般会给所有多环境部署管道都加这个参数省得每次改 YAML。4.4 模板与触发器告别复制粘贴两个以上的管道出现相同的任务序列时就应该抽成模板。模板文件通常放仓库的pipelines/templates目录下后缀还是.yml。基础模板示例# pipelines/templates/build-node.yml parameters: - name: nodeVersion default: 20.x steps: - task: NodeTool0 inputs: versionSpec: ${{ parameters.nodeVersion }} displayName: Install Node ${{ parameters.nodeVersion }} - script: | npm ci npm run test displayName: Install and test引用模板的管道trigger: - main pool: vmImage: ubuntu-latest extends: template: pipelines/templates/build-node.yml parameters: nodeVersion: 22.xextends是模板继承管道本身不再写steps全部由模板决定。另一种是steps里局部引用模板steps: - template: templates/build-node.yml parameters: nodeVersion: 20.x两种方式的选择标准管道结构和模板完全一致用extends只有部分步骤复用用- template。模板可以嵌套但要控制层级三层以上排查问题就很痛苦了。触发器除了分支触发还有计划触发和路径触发trigger: branches: include: - main - release/* paths: exclude: - docs/* schedules: - cron: 0 2 * * * displayName: Daily 2 AM build branches: include: - mainbranches.include是包含模式release/*会匹配所有 release 开头的分支。paths.exclude排除docs/*目录文档变更不触发构建。schedules是定时触发注意计划触发的时区默认是 UTC国内团队用的话要减 8 小时换算。PR 触发器在 GitHub 场景里很重要pr: - main这个配置让 PR 创建和更新时自动跑管道验证防止坏代码合并进主干。4.5 自托管代理从下载、配置到加入代理池微软托管代理省心但有两个硬伤自定义软件装不了免费分钟数有限。跑大型编译任务或者需要内网资源时自托管代理几乎是必须的。自托管代理的安装流程在不同操作系统上一致以 Linux 为例mkdir myagent cd myagent curl -o agent.tar.gz -L https://vstsagentpackage.azureedge.net/agent/3.x/vsts-agent-linux-x64-3.x.tar.gz tar zxvf agent.tar.gz ./config.sh --url https://dev.azure.com/devops-learning --agentpool default --token XXXXX --unattended --acceptteeeula ./run.sh第一步创建目录并下载代理包。第二步解压。第三步是配置代理--url指向组织地址--agentpool指定代理池名称--token用的是个人访问令牌PAT需要在 Azure DevOps 用户设置里生成至少要有 Agent Pools 的读和管理权限。--unattended表示非交互模式--acceptteeeula接受许可协议。最后./run.sh启动代理。代理启动后回到 Azure DevOps 页面在 Project Settings - Agent Pools - Default 里应该能看到这台代理显示为在线状态。此时 YAML 里把pool改成pool: name: default不再写vmImage管道就会跑到这台自托管代理上。提示自托管代理的run.sh在终端退出后就停了。生产环境要用systemd或者注册成 Windows 服务让代理常驻后台。我一般用 systemd service 管理同时加上Restartalways代理进程挂了能自动拉起来。公司网络有代理的需要在.env文件里加VSTS_HTTP_PROXY环境变量指向代理地址否则代理连不上 Azure DevOps。自托管代理的代价是你要为环境负责。托管代理上自带几百个预装软件自托管代理默认只有基础环境缺什么装什么。我一般会在配置脚本里写好apt-get install的依赖清单新代理从空白系统开始跑一遍脚本就能达到可用状态。5. 避坑与排查五条最常见的翻车记录5.1 YAML 解析失败报错却看不出来原因现象管道启动后立刻失败错误信息是YAML schematic validation failed或者Expecting a mapping不告诉具体哪一行有问题。原因绝大多数情况是缩进错误。YAML 对缩进极其敏感steps下的每一项必须保持层级一致script: |多行内容里的命令如果比父级多缩进或少缩进解析器会直接报错。另一种常见原因是冒号后面忘了加空格。解决先把 YAML 丢到本地编辑器里开 YAML 语法检查配合 Azure DevOps 页面上的编辑校验提醒一边改一边看。我的习惯是本地用 VS Code 写实时标错比到网页上试错快得多。另外 YAML 里不要用 Tab 键缩进统一两个空格这基本是行业共识了。5.2 私有项目每月 1800 分钟额度不够用现象月中管道就开始排队运行记录里显示No hosted parallelism has been purchased or granted。原因私有项目默认每月只送 1800 分钟30 小时多个并行作业或者长构建任务很快就会把额度吃完。公共项目可以免费无限时跑管道但如果代码是私有的这个方案不可行。解决短期做法是把不必要的 PR 触发器关掉减少自动运行次数。长期做法是搭自托管代理因为自托管代理不占托管额度。我遇到一个跑 Android 构建的团队每次构建要 25 分钟一个月 60 多次构建托管额度完全不够换了自托管代理后成本直降为零。5.3 自托管代理连不上一直显示离线现象Agent Pools 里代理状态是 offlinerun.sh启动时卡在Connecting to server。原因最常见的是公司网络出口有防火墙限制代理需要访问https://dev.azure.com以外的多个域名比如*.vlabs.servicebus.windows.net、*.visualstudio.com这些域名被拦了代理就永远处于连接中状态。另一个原因是代理版本太老和 Azure DevOps Server 版本不兼容。解决先确认网络策略可以在代理机上执行curl -I https://dev.azure.com验证能不能通。代理程序的日志在_diag目录下排查时先看这个文件。网络没问题的话升级代理到最新版再重新config一次。5.4 Azure 资源管理器服务连接失败现象AzureCLI2或AzureResourceManagerTemplateDeployment3任务报错提示Failed to get Azure Resource Manager service connection或者直接 401。原因服务连接Service Connection的认证凭证过期了或者创建服务连接时只剩选了资源组级别的权限。常见的是把服务连接创建成了「服务主体认证」模式但服务主体密钥过期没有及时轮转。解决检查项目设置里的 Service Connections选中对应的连接点「Edit」走一遍重新授权流程。如果是 Azure AD 服务主体模式去 Azure AD 重新生成客户端密钥再回 DevOps 更新连接信息。排查时先确认连接权限范围给资源组权限的就别去尝试列订阅级别的资源否则会看到权限不足的假报错。5.5 经典发布管道和 YAML 管道功能不对称现象照着文档写 YAML 部署发现在 YAML 里没有 Gates 功能。看老项目里有任务组的封装自己在 YAML 里找不到对应概念。原因Azure Pipelines 的两套体系功能并集才是完整能力YAML 管道的某些能力比如环境部署作业是经典发布管道没有的反之经典的 Gates 和任务组也是 YAML 没有的。解决新管道优先 YAML但碰到老项目或者特定需求比如必须用发布 Gates就别硬在 YAML 里折腾组件。正确做法是 YAML 管道负责构建和测试经典发布管道负责需要 Gates 的部署环节两者通过工件衔接。架构上多一层但每个环节用最合适的方式比强行统一省心。6. 装完怎么验证一条流水线从提交到上线的完整检查管道配置完最后的验证不只是看页面上的绿色对勾。我从实践中沉淀了一套检查清单照着走一遍能确认整条链路是真的通了而不是表面绿了。第一项检查提交触发。改动代码推到 main 分支去 Pipelines 页面的运行列表里看有没有新增记录。如果没触发先看 trigger 配置里的分支名对不对再看仓库的默认分支是不是 main。很多从 GitHub 导入的仓库默认分支是 masterYAML 里写的 main 自然不会触发。第二项检查代理状态。跑到自托管代理上的管道在运行详情里看 Agent 名称是不是你的那台机器。如果显示的是 Microsoft-hosted agent说明 YAML 里pool还是默认的azure-pipelines没切到自托管池。第三项检查测试报告。管道步骤里加了PublishTestResults2的去运行页面的 Tests 标签看统计总共多少用例、多少失败、失败的具体函数名。这里能看出测试数据有没有成功采集比终端日志里的输出更结构化。第四项检查工件。进入运行详情找 Artifacts 区域确认构建产物发布了多少 MB、文件列表对不对。再点下载解压后抽查一个文件内容是不是最新代码编译出来的。这个验证能提前发现工件没更新这种坑人的问题。第五项检查部署结果。如果是多环境管道走到最后确认deploy_prod阶段的日志里有明确的成功输出。我会在部署脚本最后加一条验证请求比如curl -I打到部署后的服务地址确认 HTTP 状态码是 200 才返回成功否则让管道报失败。最后的技巧是在管道末尾加一个自检任务把构建时间、提交 SHA、部署目标环境打出来然后跑一次完整的 PR 验证。我的习惯是改完azure-pipelines.yml先在本地把 YAML 缩进校验一遍再往一个 topic 分支推一次触发完整流程确认每个阶段都过了一遍才敢把变更移到 main 上。这套仪式感看似笨但已经帮我挡过多次回归事故了——最危险的不是管道失败而是管道绿了但产品是坏的。希望这些经验能帮到你少走几次弯路。本文还有配套的精品资源点击获取