简介:《Azure-devops-pipelines操作手册》是一份面向开发、运维及DevOps实践者的CI/CD学习资料,适合需要理解Azure Pipelines从入门到落地部署的初中级读者。压缩包内含1个PDF文件,约106.6MB,内容围绕Azure DevOps中的管道构建、测试与发布流程展开。资料系统梳理了YAML架构、预定义变量、代理与代理池、作业、阶段、任务、模板和触发器等关键概念,并涉及GitHub、Azure Repos、Bitbucket等代码仓库集成,以及Azure SQL、AKS、Key Vault等部署场景。读者还可了解持续集成、持续交付、持续测试、包发布、Slack与Teams集成及常见故障排查思路。目前已有412人学习下载,适合作为搭建自动化流水线和查漏补缺的参考手册。
1. 从一条提交后一直显示“等待代理”的管道说起
很多人第一次碰 Azure Pipelines,临门一脚失败的情况不是 YAML 报错,而是提交之后界面一直显示Waiting for an agent,十几分钟后变成超时。代码没毛病,配置也没毛病,问题出在环境侧:组织建在哪个区域、代理池有没有授权给项目、pool写的名字对不对,任何一环没对上,流水线就停在那儿空转。
这类问题在 CI/CD 的持续集成、持续交付落地里极常见,Azure Pipelines 又是把 YAML 定义、代理、服务连接、环境审批全部拆开配置的一套东西,任意一块没装好,整条链路就跑不通。下面按我实际装过一遍的顺序来写:组织怎么注册、仓库怎么接、代理怎么装、YAML 骨架怎么搭、坏了怎么救、最后怎么验证真的跑通了。
2. 注册组织与接入仓库:第一个管道跑通前的三件事
装 Azure Pipelines 这件事有一个容易忽略的前提:它不是一个独立软件,没有setup.exe可以双击。整套东西挂在 Azure DevOps 组织下面,组织建错了,后面所有配置都得推倒重来。所以第一步不是写 YAML,而是把组织、项目、仓库这三层关系确认清楚,再让管道去对接仓库。
我一般把这一步拆成三件独立的事:注册组织(不可逆参数在这里定)、接入仓库(授权范围在这里定)、提交第一份 YAML(验证整条链路是否通)。顺序颠倒也能做,但如果先接仓库再改组织名,GitHub 侧的授权记录会留下一个指向旧组织的残余,清理起来比重新注册还麻烦。
2.1 组织名、主机位置与项目可见性:三个基本不可回退的选项
注册入口很直接,打开 Azure Pipelines 首页选“免费启动”,用 Microsoft 账户或 GitHub 账户登录。真正需要停下来想的是后面这几项,它们在建完之后基本改不动,或者改起来成本极高。
| 参数 | 可选值 | 创建后能否修改 | 影响面 |
|---|---|---|---|
| 组织名 | 全局唯一的字符串 | 不能,只能新建组织 | 决定访问地址https://dev.azure.com/{organization} |
| 主机位置 | 就近区域列表 | 不能迁移 | 决定数据存放位置与部分服务可用性 |
| 项目可见性 | Public / Private | 可以改 | 直接决定免费额度口径 |
| 版本控制 | Git / TFVC | 项目内可加,但主仓库类型定了 | 影响管道 checkout 行为 |
组织名是全局命名空间,别人占了你就用不了,常见做法是带上团队缩写或用途后缀,别用test、demo这种,后期想改只能重建组织再迁一遍项目。主机位置选完就固定,这一项影响的不只是延迟,还有后面服务连接能连到哪些资源,注册时随手选一个默认值的,后面往往会想补一句“当时要是看一眼就好了”。
项目可见性这项最值得算笔账。公共项目走开源额度,管道基本不占用私有项目的分钟数;私有项目每月的免费管道作业时长是 1800 分钟,折合 30 小时。一个稍大的 .NET 或 Node 项目,单次构建跑单元测试加打包,十几分钟是常态,一个月几百次提交就会把这 1800 分钟吃满。所以在创建项目这一步就要想清楚:这是开源演示仓库,还是内部业务仓库。选错了不是不能用,是要开始算账。
账号体系上还有一个坑。用 GitHub 账户注册看着方便,但如果这个 GitHub 邮箱同时关联了企业目录下的组织,登录会被拦下来,要求改用目录账户。不是报错,是直接不给登录入口,第一次遇到很容易以为是网络问题。带企业邮箱的账号,直接用 Microsoft 账户注册会省事很多。
提示:组织名和主机位置在创建向导里没有二次确认,点“继续”就生效。填之前把名字写在记事本里念一遍,比事后重建组织省时间。
2.2 仓库接入:GitHub 授权范围和 Azure Repos 的差别
Azure Pipelines 要求源码必须放在版本控制系统里,它支持 Git 和 TFVC 两大类,具体到接入方式又分 Azure Repos Git、GitHub、Bitbucket Cloud、Subversion、通用 Git 几种。不同仓库的接入成本差别很大,主要在鉴权这一步。
Azure Repos 是同组织的仓库,管道天然有权限,选仓库、选分支、提交 YAML,三步就到运行页。GitHub 走的是 OAuth 加 GitHub App 安装,第一次会跳出去授权,授权完成后回到 Azure DevOps 选仓库。这里有一个容易被点错的选项:GitHub App 的仓库访问范围,默认有“所有仓库”和“仅选定仓库”两档。图省事点“所有”,后面每次有新仓库进来都会自动出现在管道列表里,权限面比预期大一圈。我一般选“仅选定仓库”,需要哪个手动勾哪个,多花十几秒,少一堆后续审批。
其余的 Bitbucket Cloud、Subversion、通用 Git 都走外部连接模式,需要额外的服务连接或凭据。通用 Git 最灵活,也最容易被误用成“什么都能连”,实际上它拿不到 PR 触发这类深度集成能力,只适合只读拉代码的场景。
接入完成后,仓库在项目里表现为一个可选的checkout源。默认情况下管道第一步就是checkout: self,把触发它的那个仓库拉到工作目录。多仓库场景(比如配置文件放在另一个仓库)需要显式声明resources.repositories,这个放到第 4 章讲。
2.3 第一份 azure-pipelines.yml:三行骨架和第一次运行观察点
仓库接好之后,进 Pipelines 页面新建管道,选仓库,再选“Starter pipeline”,系统会给一份默认 YAML。别急着改业务逻辑,先用最小骨架把链路走通一次,确认“提交 → 触发 → 分配代理 → 执行 → 出日志”整条链路没有断点。
# 最小可运行骨架:只做三件事,声明触发器、指定代理镜像、跑一条命令 trigger: - main # 只监听 main 分支的推送 pool: vmImage: 'ubuntu-22.04' # 微软托管代理的镜像标识 steps: - script: | # 内联 shell 脚本,在代理上执行 echo "pipeline start" uname -a # 打印代理系统信息,用于确认镜像版本 displayName: '打印环境信息'这份骨架里三个块各管一件事。trigger决定什么事件会把管道叫起来,写成列表形式就是监听列表里的分支,后面还能加paths过滤,这个在第 4 章展开。pool.vmImage是微软托管代理的镜像标识,值写错不会有语法报错,只会在排队阶段报“找不到满足条件的代理”,这也是新手最常见的翻车点之一,ubuntu-22.04和ubuntu-latest都合法,但拼成ubuntu22.04就查无此镜。steps里的script是通用 shell 步骤,Linux 和 macOS 上走 bash,Windows 上会退化成 cmd,跨平台脚本建议显式写bash:或pwsh:。
第一次运行不要盯着“成功”两个字,盯四个信号:管道是不是被提交自动触发(而不是手点 Run)、代理是不是在十几秒内分配下来、日志里uname -a输出的是不是你指定的镜像、结尾有没有出现Build succeeded。这四个都对了,说明组织、仓库、代理、权限这条主链路是通的,后面加任务才不会有“不知道哪一环坏了”的黑匣子感。
3. 代理安装与代理池:Microsoft 托管代理和自托管代理怎么选
管道跑起来之后,第二个必须做的决策是跑在哪台机器上。Azure Pipelines 给两种答案:微软托管代理,用完即弃,维护成本为零;自托管代理,装在自己的机器上,能力更强但要自己养。这两个不是替代关系,实际项目里经常是公共构建走托管、内网部署走自托管,混着用。选错的代价不小,托管代理每次都是干净环境,依赖得现装;自托管代理省了装依赖的时间,但机器一挂整条流水线停摆,半夜被叫起来重启代理是常态。
3.1 Microsoft 托管代理:vmImage 怎么选、额度怎么算
微软托管代理按操作系统分三大类,pool.vmImage的值直接对应镜像标识。选型逻辑其实不复杂,主要是被“最新的就是最好的”带偏,用了-latest之后某天上游镜像升级,构建脚本里的路径或编译器版本跟着变,出现“昨天还好好的今天挂了”的情况。
| vmImage 标识 | 系统 | 常见用途 | 注意点 |
|---|---|---|---|
ubuntu-latest/ubuntu-22.04 | Linux | .NET、Java、Node、Python、Go、Android | 默认路径/home/vsts/work,Docker 可用 |
windows-latest/windows-2022 | Windows | .NET Framework、MSBuild、PowerShell | 路径含空格,脚本引号要写全 |
macOS-latest/macOS-14 | macOS | Xcode、iOS 构建 | 启动最慢,排队时间最长,Xcode 版本需显式指定 |
-latest适合验证性流水线,正式构建我一般锁到具体版本,比如ubuntu-22.04,改版本是一次显式提交,而不是某天早上的意外。macOS 代理还有一个额外成本:启动比其他两类慢不少,且免费额度下并发只有1个作业,多个 iOS 任务会排队排得很难受。
额度这块要看清单个数字:私有项目每月 1800 分钟的管道作业时长,公共项目免费。这 1800 分钟是按“作业实际占用时间”算的,一个阶段里三个作业并行,就按三份时间吃额度。优化方向很直接:把耗时的单元测试和打包拆在不同作业里并行跑,比串在一个作业里省不了额度但能省墙钟时间;真正的省法是把不需要每次都跑的任务加上条件,或者用缓存任务把依赖下载缓存下来。
托管代理每次运行完即销毁,工作目录、临时文件、安装的全局包全部不保留。这一点和自托管代理的差异最大,很多“本地能跑管道不能跑”的问题根子都在这:本地装了全局工具,托管代理上没有。常见做法是把工具安装写进 YAML 的步骤里,或者用Cache@2任务缓存依赖目录。
3.2 自托管 Linux 代理:下载、解压、config.sh 三段装法
自托管代理的安装流程是固定的三段:拿到正确的包、解压到指定目录、用config.sh注册到代理池。中间出问题的环节几乎全在第三段,PAT 的权限范围、代理池名字、服务账户权限,任何一个不对都会卡在注册那一步。
先准备一个 PAT(个人访问令牌),作用域选“Agent Pools (Read & manage)”就够了,别顺手把全组织所有作用域都勾上。然后按下面的顺序执行,路径按自己的规划改:
# 1. 新建目录,注意不要用 root 之外的用户去装 mkdir -p /opt/azagent && cd /opt/azagent # 2. 从代理池页面复制对应平台的下载链接,替换 {agentVersion} 和平台标识 curl -fsSL -o agent.tar.gz \ "https://vstsagentpackage.azureedge.net/agent/{agentVersion}/vsts-agent-linux-x64-{agentVersion}.tar.gz" # 3. 解压,目录里不保留压缩包 tar zxvf agent.tar.gz && rm -f agent.tar.gz # 4. 注册到组织下的指定代理池,参数逐个都要对上 ./config.sh --unattended \ --url https://dev.azure.com/{yourOrganization} \ --auth pat \ --token "{yourPAT}" \ --pool "SelfHosted-Linux" \ --agent "$(hostname)-01" \ --work "_work" \ --replace # 5. 注册成系统服务,开机自启 sudo ./svc.sh install sudo ./svc.sh start sudo ./svc.sh status--url必须是https://dev.azure.com/{组织名}这个形式,写旧版的{org}.visualstudio.com在部分场景会被重定向,但代理注册可能报鉴权失败。--auth pat表示用令牌认证,--token后面跟的就是那个 PAT 字符串,带不带引号都行,带引号能防特殊字符被 shell 吞掉,我习惯加上。--pool的名字必须和组织里的代理池名称完全一致,包括大小写,这是最常见的注册失败原因,报错信息里只会说找不到池,不会提示你打错了。--replace允许在已有同名代理记录时覆盖,重装机器时非常有用,不然得先去页面上手动删旧记录。
--agent的名字建议带上主机名或用途后缀,同时装多台时页面上才分得清哪台是哪个。--work指定工作目录,默认就是安装目录下的_work,单独指定一个独立磁盘位置的好处是清理临时文件方便,也不容易把系统分区撑爆。
服务化那一步在 Linux 上依赖 systemd,svc.sh install默认用当前执行用户身份注册服务。所以装之前要想清楚:这个用户能不能读写工作目录、能不能执行 Docker 命令、要不要 sudo 权限。用 root 装代理能省事,但不建议,管道里跑的脚本继承的是代理用户权限,权限过大等于把机器的控制权交给了任何能改 YAML 的人。
Windows 和 macOS 的安装命令骨架一样,只是压缩包和运行脚本后缀不同,macOS 的注册命令需要额外加--acceptTeeEula接受授权协议,漏了会卡在交互提示上。反过来,Linux 上加这个参数虽然不报错,但属于多余的参数,容易在排错时混淆视线。
3.3 代理池、项目授权与并发限额:三处最容易漏的权限
代理注册成功、svc.sh status显示 running,并不代表管道就能用它。代理池是组织级资源,项目要用得先授权,这一层漏掉时的表现就是第 1 章开头那个“一直等代理”。
具体有三处:
一是代理池对项目的可见性。组织设置里进 Agent Pools,选中池,进 Security,确认对应项目或项目集合组有“Reader”以上权限,否则项目里根本看不到这个池。
二是项目设置里的 Agent Pools 页面要把池加进来,或者用pool: name: 'SelfHosted-Linux'显式指定池名,而不是依赖默认池。多池环境里默认池经常是个空池,管道就在空池上无限排队。
三是并发限额。组织级设置里有一个 Parallel jobs 的额度,自托管代理虽然不消耗托管分钟数,但并发作业数是有上限的。授权了一个自托管代理却只有1个并行作业额度时,两条流水线同时触发,第二条会一直等到第一条跑完。这个和代理数量是两件事,装了三台代理不代表能同时跑三个作业。
注意:代理服务的运行用户对工作目录
_work要有完整读写权限,目录是在第一次跑作业时创建并填充的,权限不对的表现是作业启动后几秒内失败,日志里能看到明显的权限拒绝记录。
4. YAML 管道骨架:触发器、变量、阶段、工件怎么摆
链路和代理都通之后,才轮到写真正的管道定义。Azure Pipelines 的 YAML 结构是四层嵌套:stages→jobs→steps→task,少写一层并不报错,系统会按默认值补,但补出来的结构经常不是你想要的。比如只写steps不写jobs,整个管道就是一个隐式作业,想加并行或者部署作业就得整体重排。所以一上手就按完整结构写,比后面返工便宜。
经典发布管道是老路子,在 Web 界面点任务卡片,适合完全不想碰 YAML 的团队;YAML 管道走代码版本控制,跟着分支和 PR 走评审。同一份管道定义,如果团队里有分支策略强制 PR 评审,YAML 的优势几乎是压倒性的:改管道和改代码走同一套流程。经典管道的部分能力(比如部署组、门禁)在 YAML 里没有完全对等物,反过来容器作业、环境、模板这几项又只在 YAML 里有。新项目直接上 YAML,这一点没什么好犹豫的。
4.1 触发器配置:分支过滤与路径过滤的组合写法
触发器写不好最典型的症状是“提交了不跑”或者“跑得太勤”。前者多半是分支名写错或者trigger: none忘删,后者多半是没加路径过滤,改个 README 也跑一遍完整构建。
# 主分支和 release 分支的推送触发,排除文档目录的改动 trigger: branches: include: - main - release/* exclude: - release/legacy paths: include: - src/* - build/* exclude: - docs/* - '*.md' # PR 触发单独配置,可以把文档改动排除在外 pr: branches: include: - main paths: exclude: - docs/*branches.include支持通配,release/*匹配所有 release 前缀分支,exclude优先级高于include,所以先全匹配再排除是常见写法。paths是路径过滤,两个条件同时存在时是“与”的关系:分支对得上、路径也对得上才触发。'*.md'加了引号是因为*在 YAML 里作为值的首字符有歧义,虽然多数情况能解析,但显式加引号能免掉一类莫名其妙的解析错误。
pr和trigger是两个独立开关,只写trigger不会让 PR 自动跑。反过来,如果这个仓库的分支策略里已经配了 Build Validation,YAML 里再写pr可能出现同一次 PR 跑两遍的情况,这个要根据团队实际策略二选一。想彻底关掉自动触发、只允许手动运行,写trigger: none和pr: none,别只写一个。
4.2 变量、变量组与运行时参数:三种“可变值”的分工
值会变的东西在 Azure Pipelines 里有三种承载方式,用混了会出各种怪问题:编译期就确定用variables,跨管道共享的密钥用变量组,运行前由人填的用parameters。
# 运行时参数:在手动运行页面弹出输入框,有默认值和可选范围 parameters: - name: buildConfiguration displayName: '构建配置' type: string default: 'Release' values: - Debug - Release - name: runIntegrationTests displayName: '是否跑集成测试' type: boolean default: false variables: - group: 'shared-secrets' # 引用变量组,需要在界面里授权给本条管道 - name: artifactName value: 'drop' - name: isMain value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] # 运行时表达式 stages: - stage: Build jobs: - job: Compile steps: - script: echo "配置=$(buildConfiguration) 产物=$(artifactName)" displayName: '打印参数' - script: echo "集成测试开关=$(runIntegrationTests)" condition: eq('${{ parameters.runIntegrationTests }}', 'true')parameters是在排队前求值的,${{ }}语法在编译期展开,所以它能控制“要不要生成某个步骤”,但不能在运行时根据前一步的结果变化。variables里的$[ ]是运行期表达式,能在作业执行时求值,适合放分支判断这类依赖上下文的逻辑。这个区别不搞清楚,写出来的条件永远不生效。
变量组是一种跨管道复用的键值集合,密钥类的值放这里比写在 YAML 里安全,但需要在管道页面的 Variables 里显式授权。授权这一步漏了,运行时报的是变量不存在,而不是权限错误,排查方向容易跑偏。
密钥变量的使用还有一个硬性要求:不能直接写进script里展开,必须通过env映射成环境变量再读取。直接展开会在日志里被打码,但脚本本身可能拿不到值,表现是空字符串。
4.3 阶段、依赖与条件:把多环境部署串成一条线
多环境部署的组织单位是stage,阶段之间用dependsOn连,用condition控制跑不跑。三个环境(开发、预发、生产)写成三个阶段,前一个成功才走下一个,是标准结构。
stages: - stage: Build jobs: - job: BuildJob steps: - script: echo "编译中" - publish: $(Build.ArtifactStagingDirectory) # 简写形式,等价于 PublishPipelineArtifact 任务 artifact: drop - stage: DeployStaging dependsOn: Build condition: succeeded() jobs: - deployment: DeployToStaging environment: 'staging' # 环境对象,审批在这里挂 strategy: runOnce: deploy: steps: - download: current artifact: drop - script: echo "部署到预发" - stage: DeployProd dependsOn: DeployStaging condition: and(succeeded(), eq(variables.isMain, true)) # 只有主分支才允许上生产 jobs: - deployment: DeployToProd environment: 'production' strategy: runOnce: deploy: steps: - script: echo "部署到生产"dependsOn只表达先后,不表达“成功才继续”。默认行为是前序阶段失败后跳过后续阶段,但一旦你写了自定义condition,这个默认行为就被覆盖了,必须显式写succeeded(),否则前一个阶段失败、后一个阶段照样跑,这是很多人踩过的坑。and(succeeded(), ...)这种组合是最稳妥的写法。
deployment作业和普通job的差别在于它绑定了一个environment对象。环境是部署目标资源的集合,审批、检查、部署历史都挂在这上面。用普通job也能script一条部署命令,但拿不到审批和部署记录,属于能跑但不可控。
strategy.runOnce是最简单的部署策略,先下工件再执行步骤,还有rolling和canary两种,前者适合逐步替换实例,后者适合按比例放量。选哪种取决于部署目标本身的能力,目标不支持滚动,写rolling也没用。
4.4 工件发布与下载:路径写错是最常见的翻车点
构建产物从构建阶段传到部署阶段,靠的是工件。发布用publish简写或PublishPipelineArtifact@1任务,下载用download简写或DownloadPipelineArtifact@2。这里最常见的错误是源路径写成了相对路径,发布出去的目录结构跟你以为的不一样。
| 参数 | 含义 | 常见错法 |
|---|---|---|
targetPath | 要发布的目录 | 写成bin/Release,实际发布出去的是整个目录含父级 |
artifact/artifactName | 工件名称 | 发布和下载两边名字不一致,下载步骤静默通过但目录为空 |
publishLocation | 存储位置 | 老版本默认落到容器里,新版默认走 Pipeline Artifact,混用会找不到 |
download的current | 当前运行的工件 | 写成specific却没给runId |
$(Build.ArtifactStagingDirectory)是系统预定义变量,指向代理上的一个暂存目录,构建步骤应该把产物复制到这里再发布,而不是直接发布$(Build.SourcesDirectory)下的编译输出目录。直接发布源码目录的后果是工件里混进一堆中间文件,下载端还得自己筛。
下载端用download: current表示取当前这次运行的工件,写download: specific要配runId或pipeline参数,跨管道取工件才用后者。两个模式的参数不通用,混写会报参数校验失败。
发布和下载的路径结构默认是“工件名/内部目录”,下载到$(Pipeline.Workspace)下面。部署脚本里如果直接引用$(System.DefaultWorkingDirectory)/drop/xxx,在deployment作业里往往找不到,因为部署作业的工作目录和普通作业不同。稳妥写法是先download再用$(Pipeline.Workspace)/{artifactName}拼路径,或者下载时用targetPath显式指定落点。
5. 常见问题排查:五条真踩过的坑和对应解法
环境装完之后,真正的麻烦才开始。下面五条是我在实际配置里反复遇到的,每一条都按“看到什么现象、真实原因是什么、怎么修”写。这些问题的共同点是报错信息指向的位置往往不是根因,照着报错改半天没效果。
5.1 现象:代码推上去了,管道完全不触发
现象很明确:提交记录在仓库里能看到,Pipelines 页面上没有任何新运行记录,连排队都没有。
原因通常有三个方向。一是 YAML 里的trigger只写了分支名但分支名对不上,比如仓库主分支叫master而 YAML 里写的是main;二是 YAML 文件不在默认分支上,第一次创建管道时系统读的是默认分支那份配置,其他分支上的 YAML 改了不会影响触发规则;三是这个仓库属于 fork 或者外部来源,PR 触发的安全策略默认不自动跑。
解决顺序:先确认默认分支和 YAML 里的分支名词是否一致;再进管道页面的 Triggers 菜单,看“Override the YAML continuous integration trigger”有没有被勾上,这个界面开关的优先级高于 YAML 文件,很容易被前任配置遗留下来;最后检查分支策略里是否配了 Build Validation,如果配了,PR 触发就由策略接管,YAML 里的pr块应该删掉避免重复。
还有一个隐蔽情况:路径过滤写成了exclude: - '*'而没有对应的include,规则上进不来任何提交,看上去就是“完全不触发”。路径过滤我一般先不写,等构建稳定了再加。
5.2 现象:作业一直显示等待代理,直到超时
现象是运行记录有了,状态停在Waiting for an agent,十几分钟后失败,日志里没有更多信息。
原因分两类。托管代理场景下,多半是pool.vmImage的值拼错,或者指定的镜像在所在区域不可用;自托管场景下,八成是代理池没授权给项目、池名写错、或者代理服务本身挂了。
解决:托管代理先把vmImage抄成ubuntu-latest试一次,能跑就说明是版本标识问题,再逐个换回具体版本号。自托管代理先在服务器上执行./svc.sh status看服务状态,再去组织设置里确认池下面那台代理是 Online 而不是 Offline。Offline 状态下页面上会显示最后一次心跳时间,超过几分钟没更新基本就是服务停了。池名对不上的情况,日志里只有一句“找不到满足条件的代理”,不会告诉你它找了哪个池,所以 YAML 里显式写pool: name: 'xxx'比依赖默认池更容易定位问题。
5.3 现象:服务连接在部署阶段报 401 或 403
现象是构建阶段全部通过,进到部署阶段那几个步骤直接失败,日志里是一段鉴权错误,看不出是哪一层拒绝的。
原因是服务连接的凭据过期或者权限不足。Azure Resource Manager 类型的服务连接有服务主体,服务主体的密钥有有效期,到期后连接状态还是“可用”,直到实际调用才失败。另一种情况是服务主体在目标资源上没有角色分配,连接本身能建,读操作能过,写操作被拒。
解决:进项目设置的服务连接页面,点开对应连接看“Verify”能不能通过,失败就重新授权或更新密钥;能通过但部署仍报 403,就去目标资源那边确认这个服务主体被授予了足够角色。这一条我是吃过亏的,之前一直以为是 YAML 里资源名写错了,折腾半天才发现是密钥到期。
5.4 现象:YAML 编辑页校验通过,运行时提示意外值
现象是保存时没报错,一运行就在解析阶段挂掉,报的是某个字段取值不在允许范围内。
原因是编辑器的校验只做语法层面,不理解上下文。典型例子是condition里用了单引号包布尔值,eq(variables.isMain, true)写成eq(variables.isMain, 'true'),前者比较布尔,后者比较字符串,结果永远为假;还有把dependsOn写成列表但漏了缩进,YAML 解析成了字符串。
解决:遇到这类报错先看它指向的行号,把那一行单独摘出来在编辑器里对齐缩进看一遍。布尔和字符串的区分要在心里过一遍:true不带引号是布尔,'true'是字符串,比较两边类型不一致时表达式静默返回假,不报错,这种最难查。
5.5 现象:工件下载成功但目录是空的
现象是部署阶段的download步骤显示成功,后面脚本引用产物路径时报文件不存在。
原因是发布端的targetPath指向了一个不存在的路径,任务会把“没有文件可发布”当成正常完成,生成一个空工件。另一种是发布和下载用的名字大小写不同,管道工件名在部分场景下大小写敏感。
解决:在发布步骤下面加一条script: ls -R $(Build.ArtifactStagingDirectory)打印实际内容,确认产物真的落到了暂存目录;下载端加一条同样的ls打印$(Pipeline.Workspace),两边对一遍就清楚了。这个调试脚本我一般会保留到管道稳定为止,比翻日志快得多。
提示:调试阶段可以在运行页面勾选“Enable system diagnostics”,日志会多出大量代理侧信息,包括变量展开结果和环境变量清单,排完记得关掉,日志量会翻好几倍。
6. 装完怎么验证:从一次空提交到产物可下载的完整走查
配置写完之后,别急着往里塞业务构建。我习惯做一遍端到端走查,用最小的改动把整条链路的每个环节都点亮一次,确认没有环节是“看起来通了实际没通”。走查的核心原则是:每一步都要有一个可观察的输出,不能靠“没报错”判断成功。
具体做法是提交一个只改一行注释的 commit,然后按下面的顺序核对四个信号。
| 检查点 | 观察位置 | 合格标准 |
|---|---|---|
| 触发器命中 | 运行列表页 | 提交后 30 秒内出现新运行记录,触发者显示为提交人 |
| 代理分配 | 运行详情页顶部 | 显示具体代理名称或镜像标识,不显示等待状态 |
| 步骤执行 | 日志展开 | 每个script步骤都有实际输出,不是空行 |
| 工件产物 | 运行详情的 Artifacts | 工件可展开,内部文件数和大小与预期一致 |
两个信号都对上之后,再用命令行补一次确认。装好 Azure CLI 的 devops 扩展之后,可以直接拉运行列表,比翻页面快:
# 列出最近 5 次运行,确认触发原因和结果 az pipelines runs list \ --organization https://dev.azure.com/{yourOrganization} \ --project "{yourProject}" \ --top 5 \ --output table # 手动触发一次,验证手动运行通道和参数输入是否正常 az pipelines run \ --organization https://dev.azure.com/{yourOrganization} \ --project "{yourProject}" \ --name "{pipelineName}" \ --branch main--top控制条数,排查时给 5 到 10 就够看趋势。--output table让结果按列对齐,重点是result和trigger两列:result是succeeded表示作业层面没问题,trigger显示individualCI表示是提交触发的,显示manual表示是手动跑的,两者能区分开,就能确认触发器配置真的生效而不是靠手点。
还有一项容易被跳过:把trigger临时改成trigger: none,提交一次,确认管道确实不再自动跑,再改回来。这一步是在验证“触发器是有意配的,不是默认行为碰巧对上了”。管道出问题的时候,能分清哪些是显式配置、哪些是默认兜底,排查范围会小一半。
从那以后我每次搭完一套新管道,都会先跑一遍这条空提交走查,再往里加业务步骤,宁可开头多花二十分钟,也不想在发布日当天去猜是哪一层断了。希望帮到你。
本文还有配套的精品资源,点击获取