一文吃透前端 CI/CD:Azure Pipelines / Jenkins / GitHub Actions 实战对照(附完整配置)

一文吃透前端 CI/CD:Azure Pipelines / Jenkins / GitHub Actions 实战对照(附完整配置)
你刚接手一个项目在仓库根目录看到一个azure-pipelines.yml里面写满了trigger、pool、task。这东西是干嘛的能删吗和Jenkins 是一回事吗本文从一份真实在跑的前端部署流水线出发把 CI/CD 这件事从原理讲到实践它怎么运作、那份 YAML 每一段在干嘛、同样一件事用 Jenkins 和 GitHub Actions 又怎么写以及三者到底怎么选。适合会写业务代码、但对 CI/CD 还是“能看懂不会写”的前端 / 全栈同学。一、先把问题摆上桌没有 CI/CD 的时候有多痛设想你负责一个前端项目每次发版要手动走这么一套本地git pull拉最新代码npm install装依赖npm run build打包出dist/打开 FTP / 控制台把dist/里的文件拖上服务器祈祷没传错。这套流程的问题不是“麻烦”这么简单人会错忘了git pull、传错环境、只传了一半、忘了备份旧版本。不可复现“在我机器上是好的”—— 你本地 Node 是 18同事是 20打出来的包不一样。出事难回滚上线发现白屏想退回上一版发现没人备份。不透明谁、什么时间、用哪个 commit 部署的全靠口头问。CI/CD 要解决的就是这些。一句话把“从代码到上线”这条路变成一条自动、可重复、可追溯的流水线。二、CI/CD 到底是什么一条流水线的通用心智模型2.1 CI 和 CD 拆开看CIContinuous Integration持续集成代码一提交就自动拉下来、装依赖、跑 lint / 测试 / 构建。目的是尽早发现“合进来的代码能不能成功构建”。CDContinuous Delivery / Deployment持续交付 / 部署构建产物自动部署到测试 / 生产环境。对前端来说最常见的形态就是本文这种push 代码 → 自动构建 → 自动部署到 CDN / 对象存储。2.2 记住这四个词换什么工具都一样不管是 Azure Pipelines、Jenkins 还是 GitHub Actions一条流水线都是这四个部件拼出来的部件是什么前端类比trigger触发什么事件启动流水线push 到某分支 / 定时 / 手动事件监听器addEventListenerrunner / agent机器在哪台机器上跑这些命令跑npm run build的那台电脑steps / stages步骤依次执行的命令列表package.json里串起来的 scriptsartifact产物构建产出、用于部署的东西如dist/npm run build出的dist/这是本文最值钱的一句话学会任何一个 CI/CD 工具理解了trigger → runner → steps → artifact这套通用模型换另一个主要就是查语法。核心是“你懂不懂 CI/CD 这件事”而不是“你会不会 Jenkins 某个插件”。这条流水线画成图是这样的push 到 dev 分支CI 平台开一台临时构建机装 Node 依赖npm run build:dev产出 dist/部署到 S3backup → temp → liveCDN / 用户访问三、解剖一份真实的前端部署流水线Azure Pipelines先上完整代码trigger:-dev# 只有推到 dev 分支才触发pool:vmImage:ubuntu-latest# 微软托管的临时构建机variables:environment:devbucketName:my-app-fe-$(environment)# 例如 my-app-fe-devbuildCommand:build:devsteps:# checkout 是隐式的Azure 自动把代码拉到机器上-task:NodeTool0inputs:versionSpec:20.xdisplayName:Install Node.js 20.x-script:npm installdisplayName:Install dependencies-script:npm run $(buildCommand)displayName:Build-task:AWSShellScript1inputs:awsCredentials:aws-connection-dev# Azure DevOps 里配的 Service ConnectionregionName:ap-southeast-1scriptType:inlineinlineScript:|set -e BUCKET$(bucketName) TIMESTAMP$(date %Y%m%d%H%M%S%3N) LIVEs3://$BUCKET/live TEMPs3://$BUCKET/temp BACKUPs3://$BUCKET/backup/$TIMESTAMP/aws s3 sync $LIVE $BACKUP||echo no live to backup# ① 备份当前线上aws s3 rm $TEMP--recursive||echo no temp to clean# ② 清空临时目录aws s3 sync ./dist $TEMP--cache-control max-age31536000--exclude index.html# ③ 静态资源aws s3 cp ./dist/index.html $TEMP/index.html--cache-control no-cache,no-store,must-revalidate# ④ HTMLaws s3 sync $TEMP/ $LIVE/--delete# ⑤ 原子切换到线上displayName:Deploy to S33.1 逐段拆头部都在声明“什么情况下、在哪里跑”trigger: - dev只有推送到dev分支才启动。这就是上面说的trigger。pool: vmImage: ubuntu-latest告诉微软“给我开一台临时 Ubuntu 机器跑干完就销毁”。这就是runner。注意你不拥有任何机器。variables变量。$(environment)是 Azure 的变量插值语法拼出my-app-fe-dev。steps按顺序跑的步骤。task是官方 / 市场提供的现成能力如NodeTool0装 Node、AWSShellScript1跑 AWS 脚本script则是直接跑 shell。⚠️一个容易被忽略、但迁移时最坑的点Azure 会隐式自动拉代码。你在steps里根本没看到 checkout 一步但代码就是在机器上。后面会看到 Jenkins / GitHub Actions 都必须手动写这一步。3.2 部署脚本里的门道backup → temp → live 三段式很多人以为“部署”就是aws s3 sync ./dist s3://bucket一行事。这份脚本多了几道工序每一道都有理由先备份live/到backup/时间戳/—— 出事能回滚。先传到temp/而不是直接覆盖live/—— 上传过程中用户访问的还是旧版不会看到“传一半”的破站。最后temp/ → live/ --delete一次性同步—— 接近原子切换并且--delete会清掉 live 里新版已不存在的旧文件。这是一个很值得学的部署思路用一个中转目录 备份把“部署”从“边传边生效”变成“准备好再一键切换”。3.3 缓存策略为什么 index.html 不缓存、其余缓存一年这是整份配置里最能体现“懂行”的两行# 静态资源JS/CSS/图片缓存一年aws s3sync./dist$TEMP--cache-controlmax-age31536000--excludeindex.html# index.html绝不缓存aws s3cp./dist/index.html$TEMP/index.html --cache-controlno-cache, no-store, must-revalidate原理是现代前端打包的文件指纹hash机制Vite / Webpack 打包出的 JS/CSS 文件名都带内容 hash如index-a1b2c3.js。内容一变文件名就变——所以旧文件永远不会被覆盖让浏览器狠狠地缓存一年都安全。index.html是唯一的“入口”名字固定里面引用的是那些带 hash 的文件名。它必须不缓存才能保证用户一刷新就拿到指向新 JS/CSS 的最新 HTML。 一句话带 hash 的资源长缓存不带 hash 的入口 HTML 不缓存——这是 SPA 部署的标准姿势。配错了会出现“发了新版但用户还是旧页面”或“新 HTML 配旧 JS 白屏”。四、同一条管道三种方言概念通用但语法各不相同。先看一张概念映射表横扫一行 同一件事的三种写法概念Azure PipelinesJenkinsGitHub Actions配置文件 / 语法azure-pipelines.yml·YAMLJenkinsfile·Groovy.github/workflows/*.yml·YAML触发trigger: [dev]多分支流水线靠分支名筛选on: push: branches: [dev]执行机器pool: vmImage微软托管agent any你自己的机器runs-onGitHub 托管拉代码隐式·自动checkout scmuses: actions/checkoutv4装 Nodetask: NodeTool0tools { nodejs node-20 }uses: actions/setup-nodev4凭证awsCredentialsService ConnectionwithCredentials([...])secrets.AWS_ACCESS_KEY_ID部署 shellaws s3 sync ...aws s3 sync ...aws s3 sync ...最关键的认知那段真正干活的aws s3 sync部署 shell三家一字不差看上表最后一行。工具差异只在“怎么声明触发 / 机器 / 步骤 / 凭证”这层外壳里面的活是纯 shell跟用哪个 CI 无关。4.1 Jenkinsfile 版pipeline{agent any// 用你自己挂的 Jenkins 机器environment{BUCKET_NAMEmy-app-fe-devBUILD_COMMANDbuild:devAWS_REGIONap-southeast-1}tools{nodejsnode-20}// 需先装 NodeJS 插件并配置名为 node-20 的工具stages{stage(Checkout){steps{checkout scm}}// - Azure 不用写这里必须写stage(Install){steps{shnpm install}}stage(Build){steps{shnpm run${BUILD_COMMAND}}}stage(Deploy to S3){steps{withCredentials([[$class:AmazonWebServicesCredentialsBinding,credentialsId:aws-connection-dev]]){sh set -e BUCKET$BUCKET_NAME TIMESTAMP$(date %Y%m%d%H%M%S%3N) aws s3 sync s3://$BUCKET/live s3://$BUCKET/backup/$TIMESTAMP/ || true aws s3 rm s3://$BUCKET/temp --recursive || true aws s3 sync ./dist s3://$BUCKET/temp --cache-control max-age31536000 --exclude index.html aws s3 cp ./dist/index.html s3://$BUCKET/temp/index.html --cache-control no-cache, no-store, must-revalidate aws s3 sync s3://$BUCKET/temp/ s3://$BUCKET/live/ --delete }}}}}4.2 GitHub Actions 版name:Deploy (dev)on:push:branches:[dev]env:BUCKET_NAME:my-app-fe-devBUILD_COMMAND:build:devjobs:build-and-deploy:runs-on:ubuntu-lateststeps:-uses:actions/checkoutv4# 必须显式写否则没代码-uses:actions/setup-nodev4with:node-version:20.x-run:npm install-run:npm run ${{env.BUILD_COMMAND}}-uses:aws-actions/configure-aws-credentialsv4with:aws-access-key-id:${{secrets.AWS_ACCESS_KEY_ID}}aws-secret-access-key:${{secrets.AWS_SECRET_ACCESS_KEY}}aws-region:ap-southeast-1-name:Deploy to S3run:|set -e BUCKET${{ env.BUCKET_NAME }} TIMESTAMP$(date %Y%m%d%H%M%S%3N) aws s3 sync s3://$BUCKET/live s3://$BUCKET/backup/$TIMESTAMP/ || true aws s3 rm s3://$BUCKET/temp --recursive || true aws s3 sync ./dist s3://$BUCKET/temp --cache-control max-age31536000 --exclude index.html aws s3 cp ./dist/index.html s3://$BUCKET/temp/index.html --cache-control no-cache, no-store, must-revalidate aws s3 sync s3://$BUCKET/temp/ s3://$BUCKET/live/ --delete五、三大工具的真实差异选型参考Azure PipelinesJenkinsGitHub Actions本质微软托管的云服务你自己搭的一台 CI 服务器GitHub 托管的云服务机器谁维护微软你装、升级、修磁盘GitHub语法YAMLGroovy现代也支持 JenkinsfileYAML与仓库关系Azure DevOps 自带、一体独立系统需接 Git和 GitHub 一体生态Task开箱即用插件极丰富但要自己维护Marketplace Actions生态最活跃适合谁代码在 Azure DevOps要高度定制 / 内网私有部署代码在 GitHub开源项目首选 一句话选型代码托管在哪就用配套的那个。GitHub → GitHub ActionsAzure DevOps → Azure Pipelines需要完全自控 / 内网环境 / 超多定制 → Jenkins。六、从 Azure 迁到别家踩坑清单如果有一天你要把这条流水线从 Azure 搬到 Jenkins / GitHub Actions以下几点最容易翻车checkout 必须显式写。Azure 自动拉代码另两家不写就是空目录npm install直接报找不到package.json。凭证机制不同。Azure 用 Service Connection后台配好名字引用GitHub 用 SecretsJenkins 用 Credentials 插件。别把密钥硬编码进 YAML。语法不能复制粘贴。Azure / GitHub 是 YAMLJenkins 经典写法是 Groovy迁移 重写外壳但里面那段 shell 可以直接搬。变量插值语法各不一样Azure$(var)、GitHub${{ env.var }}、Jenkins${var}。隐藏的环境差异Jenkins 的agent是你自己的机器可能没装awsCLI / Node托管的 runner 预装了常见工具自托管要自己保证环境齐全。七、最佳实践清单可直接抄走部署前先备份当前线上backup/时间戳/留回滚后路用 temp 中转 一次性同步到 live避免“边传边生效”的中间态静态资源带 hash长缓存max-age31536000入口index.html绝不缓存no-cache, no-store, must-revalidate部署脚本开头加set -e任何一步失败立即中断不要带病上线密钥走平台凭证机制Secrets / Service Connection / Credentials绝不硬编码跨工具迁移时记得手动补上 checkout 一步把环境相关变量bucket、region、build 命令抽成变量别散在脚本里八、一句话总结CI/CD 工具是“一个抽象概念 多个具体实现”就像前端框架之下有 React / Vue / Svelte。记住 trigger → runner → steps → artifact 这条通用链你看懂的就不只是一份 YAML而是 CI/CD 这件事本身。真正干活的那段 shell换哪个工具都一样。