
团队最近在选型的讨论群里又炸开了锅同样是要落地 CI/CD 流水线有人坚持 Jenkins 插件多能解决一切有人觉得 GitLab CI 跟代码仓库一体更省心还有人被 GitHub Actions 的 Marketplace 圈粉。说来说去大家真正纠结的点无非是两个——跑得快不快、稳不稳以及权限和安全上会不会出纰漏。我这两年陆陆续续帮几个团队折腾过 GitLab CI、Jenkins、Gitea Actions也踩过不少“看上去很美、用起来想哭”的坑。这篇评测不打算罗列官方文档里那些漂亮的特性清单而是想站在“提效和安全双平衡”的角度把几个主流流水线产品放到真实场景里比一遍顺便聊聊那些文档里不会写的配置细节和排错经验。无论你是刚要选型的技术负责人还是天天跟流水线搏斗的 DevOps 工程师这篇文章都能帮你少走一段弯路。1. 为什么评测的重点是“提效与安全的双平衡”1.1 先想清楚你要的到底是工具还是流程在对比具体产品之前我觉得有必要先纠正一个很容易犯的错很多人选 CI/CD 工具第一反应是“哪个工具厉害”但真实需求往往是“怎么让交付动作更快、更稳、更不容易出事”。这里要区分两个概念。CI 解决的是“合并代码之后能不能自动验证、自动构建出可交付的制品”CD 解决的是“这些制品能不能自动部署到目标环境、部署错了能不能快速回滚”。两个动作合起来就是交付流水线而工具只是这条流水线的执行引擎。我在给一个做 Java 后台系统的团队做调研时负责选型的同学一开始拼命对比 Jenkins 和 GitLab 的功能数量我反问了他三个问题你们的交付物是几个镜像、一个安装包还是一整套带数据库迁移的分布式系统团队是 5 个人还是 50 个人有没有专职的运维生产环境有没有安全审计、权限隔离、制品溯源的要求这三个问题问完选型范围基本就缩小了。这也是评测的一个思路与其列一个“六边形战士”式的能力矩阵不如先明确自己的交付场景和约束条件。工具能帮你提速但提效的最大瓶颈往往不是工具本身而是流程设计是否理顺了。1.2 评测模型四个维度加两个坐标为了对比有依据我给自己建了一个简单的评测模型。四个维度分别是执行效率、安全能力、运维复杂度、社区生态。执行效率看构建并发、缓存机制、任务启动速度安全能力看权限模型、密钥管理、制品签名与审计日志运维复杂度看自托管难度、升级维护成本社区生态看插件市场、文档质量、问题排查的资料多不多。在此基础上再叠加两个坐标提效坐标和安全坐标。每个工具我都会给出一个通俗的判断方便不同规模、不同合规要求的团队对号入座。评测维度具体关注点对选型的影响执行效率并发度、缓存命中率、Runner 调度方式决定交付快不快直接体现“提效”安全能力权限细粒度、密钥引用、供应链扫描、审计决定你敢不敢放开自动化体现“安全”运维复杂度是否好升级、是否需要额外数据库、插件治理成本决定长期维护的负担社区生态文档、插件量、坑的曝光率决定遇到问题时能不能快速找到答案后面几节的横向对比都会落到这套模型上。2. 主流流水线产品横向对比2.1 GitLab CI/CD仓库和流水线一体化安全审计家用顺手GitLab CI/CD 是这四年我在中小团队里推荐得最多的一个。原因很简单代码仓库本身在 GitLab那么 Merge Request 触发的流水线、代码评审和制品仓库天然在一个平台里。权限模型沿用了 GitLab 的群组/项目体系环境变量可以按环境去隔离受保护分支可以约束谁能执行部署阶段审计日志也相对完整。GitLab 的流水线配置用.gitlab-ci.yml文件入库好处是“流水线即代码”团队成员能够在同一个 Merge Request 里同时评审代码和流水线改动。执行单元叫 RunnerRunner 可以注册到特定的项目或者共享到整个实例底层支持 Shell Executor、Docker Executor、Kubernetes Executor。很多人搜索“gitlab docker engine ci/cd”其实要解决的就是 Docker Executor 怎么搭、怎么在构建容器里再跑 Docker 命令这类问题。它的短板也明显单机模式下的 Job 排队调度相比云原生方案不是最轻盈的高级功能比如环境看板、动态安全扫描、合规管道撑着团队用得顺但社区版功能有限。如果你所在团队本来就重度使用 GitLab那选它做 CI/CD 的增量成本很低。靠它做权限审计、给“代码不被乱动”上了一道锁是它优于 Jenkins 的核心地方。2.2 Jenkins插件宇宙的“瑞士军刀”安全治理是长期课题Jenkins 是老牌选手我对它的感情很复杂。它最大的优势是插件几乎覆盖一切从编译、打包、镜像扫描到通知、审批凡是你能想到的步骤基本都能找到插件。遇到极其定制化的流水线逻辑时Jenkins Pipeline 配合 Groovy 脚本能写得很灵活。但“灵活”的另一面是“混乱”。我见过不少 Jenkins 实例任务靠手工点“立即构建”维护配置散落在不同 Job 里密钥在系统管理员的“凭据”里存了一堆谁有权限查看也说不清楚。Jenkins 本身用的是 Java 技术栈启动开销和插件安全更新是需要持续投入的。每年因为插件漏洞出的安全通告不少团队如果没有专人治理插件版本很容易变成一台“年久失修的复杂机器”。我并不是劝退 Jenkins。如果团队已经有一堆 Jenkins 共享库、插件生态沉淀了几年换掉它的迁移成本反而高于维护成本。这种情况下合理的做法不是推翻重来而是收紧权限、定期更新、把流水线脚本代码化逐步驯化这头大象。新团队如果求快且又想要强权限约束我会更推荐先看 GitLab CI 或者 Gitea Actions。2.3 GitHub Actions 与 Gitea Actions云原生语境的轻量选手GitHub Actions 的优势是它绑定了全球最大的开源生态。配置格式很简洁社区市场里有大量现成的 Action 可以直接复用字符串处理、云平台发布、人工审批都有成熟封装。新建仓库之后几乎不需要额外搭建什么服务非常适合开源项目或愿意接受托管服务的团队。但一样要考虑边界代码托管在第三方平台时敏感数据控制、私有网络资源访问、合规审计这些就不是想当然能解决的问题了。所谓“提效安全双平衡”在这里要格外留意“代码在哪里”和“制品去哪里”。Gitea Actions 则是另一个思路把轻量自托管和 GitHub Actions 的语法习惯融合在一起。Gitea 本身就是轻量级的代码托管很适合内网或中小团队Actions 用.gitea/workflows/*.yaml的方式声明生态兼容性好维护成本也低。它的执行器基于 Act Runner调度模型和 GitHub Actions 相近。不过 Gitea 的生态毕竟比 GitLab 和 Jenkins 年轻复杂场景比如多环境审批、企业级审计报表、大规模并行调度文档和踩坑案例会少一些。对于 5 到 20 人、需求相对标准的团队它是我很看好的选择。2.4 Drone、Tekton 等专业选手场景化补充而非大众默认除了上面三位主角还有一个容易被忽略的类别面向容器或 Kubernetes 场景的专业选手。Drone 的配置非常清爽每个 Pipeline 就是容器化步骤的串联对镜像构建、K8s 发布这类场景支持得很顺手。Tekton 则是 Kubernetes 原生的 CI/CD 框架它把构建、测试、发布都定义成 CRD适合平台型团队想把流水线能力内聚到自己 PaaS 里的场景。但这类工具的通用性相对弱学习曲线也更陡。日常团队如果没有强烈的容器平台化诉求直接上手 Drone 或 Tekton 可能会觉得“杀鸡用牛刀”且身边能讨论的人少。把它们的名字记着等团队规模上来或平台化需求清晰时再考虑是比较务实的策略。3. 影响最终体验的四组关键配置选型定了不代表高枕无忧。同一套工具配置的人不同跑出来的体验可能天壤之别。这节我不聊大而全的产品功能只讲四个我在实测中觉得直接影响“提效”和“安全”的配置点。3.1 Runner 或 Agent 的调度方式快不快主要看执行器很多人抱怨“GitLab CI 跑得慢”但慢很多时候不是工具的问题而是 Runner 没配好。GitLab Runner 的 Executor 类型决定了每个 Job 是在裸机 shell 里跑、在容器里跑还是在 Kubernetes Pod 里跑。我在小团队里最常用的组合是一个 4 核 8G 的机器注册一个 Docker Executor 的 Runner。配置大概是这样的[[runners]] name docker-runner url https://gitlab.example.com token 替换成注册时生成的token executor docker [runners.docker] image alpine:latest privileged false volumes [/cache, /var/run/docker.sock:/var/run/docker.sock]需要注意两点。第一privileged false是更安全的选择除非你的流水线确实需要 Docker 动态嵌套否则不要随便给 Runner 开特权模式。第二个就是/var/run/docker.sock的挂载问题你要是构建脚本里需要给宿主机 Docker 发指令这会方便很多但也意味着流水线里跑的代码实质上获得了宿主机的 Docker 控制权有风险。我的习惯是优先用 Docker-in-Docker常见缩写 dind的方式隔离构建环境而不是简单挂载 Socket。Runner 的 Concurrency 参数也值得单独看。并发数调太高机器资源会被打满Job 互相抢 CPU 反而集体变慢调得太低团队提交多的时候排队又长。我在一台 8 核机器上通常把concurrent 4起步配合每个 Runner 的limit 2做限制。这个数字没有标准答案要观察 CPU 和内存余量动态调。3.2 缓存与制品仓库提效的隐形抓手流水线提速最直接的两个手段一是依赖缓存二是制品仓库的读写速度。以 Java 项目为例每次流水线启动如果是全新容器mvn要重新下载一堆依赖不仅慢还制造网络压力。GitLab CI 里用cache关键字把本地 Maven 仓库、npm 的node_modules缓存下来是常见做法。需要留心的是缓存 Key 的设计。我见过有人图省事直接写死一个 Key结果两个分支并发构建时缓存互相覆盖构建出来一会儿对一会儿错。比较好的做法是用分支名做 Key至少保证同一分支的构建结果稳定。参考写法cache: key: $CI_COMMIT_REF_SLUG paths: - .m2/ - node_modules/制品仓库同样重要。构建出来的 Jar 包、镜像如果都推到同一个仓库需要用标签或路径区分版本避免覆盖。容器镜像尤其要养成“不可变标签”的习惯每次构建都用 commit SHA 打标签而不是统一覆盖latest这样回滚时只需要重新拉指定标签的镜像即可。3.3 密钥管理用引用代替明文别把秘密留在日志里无论选哪个工具密钥管理都是安全里最容易被“图省事”的人拖垮的一环。我在一个现场排查经历里遇到过团队把 MySQL 密码直接写在构建脚本的echo语句里日志系统一开放等于给所有能看日志的人发了数据库密码。各家 CI 工具都有配套的密钥管理GitLab 的 CI/CD Variables、Jenkins 的 Credentials、GitHub Actions 的 Secrets、Gitea Actions 的 Secrets原理都差不多——在配置界面录入密文流水线里通过变量的方式引用平台会尽力给变量打码脱敏。核心原则就是代码库里不允许出现任何密钥日志里不允许打印任何环境变量内容。还有一层容易被忽视环境变量作用域。GitLab CI 的变量可以做环境维度隔离同一个变量名在不同环境有不同的值。我通常会建development、staging、production三套环境作用域变量生产环境的数据库地址和凭据只有生产部署 Job 能引用避免一个变量泄漏导致所有环境暴露。3.4 依赖供应链与镜像签名安全不止于权限现在的 CI/CD 安全已经从“账号权限别乱开”扩展到了“交付物是否可信”。比较成熟的组合是用开源扫描器在流水线里做依赖漏洞检查同时用签名工具给镜像做签名。依赖扫描我比较常用的是 Trivy一条命令就能对镜像或文件系统做漏洞扫描输出结果为不达标时让 Job 失败。镜像签名则可以借助 cosign构建并推送后顺手对镜像打签名部署时在目标环境验签。这套流程并不复杂但对供应链安全能提升一大截。对于合规要求比较高的场景还需要检查 SBOM软件物料清单。用 syft 之类的工具生成一份 SBOM随镜像或制品一起保存后期做漏洞溯源的时候方便很多。这属于一次配置、长期受益的事值得提前放进去。4. 典型场景实战从若依框架部署到 Dify 知识库刷新4.1 若依前后端项目的完整流水线示例这里用一个很典型的“若依”框架项目来演示。若依是很多 Java 团队做管理系统时的快速开发框架常见的交付物包括后端 Spring Boot 工程和前端 Vue 工程部署时后端是一个可执行 Jar 包或镜像前端是一堆静态资源。我在给这类团队配置 GitLab CI 时会拆成几个阶段stages: - test - build - image - deploy cache: key: $CI_COMMIT_REF_SLUG paths: - .m2/ - ruoyi-ui/node_modules/ backend-test: stage: test image: maven:3.8-openjdk-8 script: - cd ruoyi-framework mvn test only: - merge_requests backend-build: stage: build image: maven:3.8-openjdk-8 script: - mvn clean package -DskipTests artifacts: paths: - ruoyi-admin/target/*.jar frontend-build: stage: build image: node:18 script: - cd ruoyi-ui - npm install - npm run build:prod artifacts: paths: - ruoyi-ui/dist/ build-image: stage: image image: docker:24 services: - docker:24-dind script: - docker build -t ${IMAGE_REGISTRY}/ruoyi-demo:${CI_COMMIT_SHORT_SHA} . - docker push ${IMAGE_REGISTRY}/ruoyi-demo:${CI_COMMIT_SHORT_SHA} only: - tags deploy-production: stage: deploy image: docker:24 script: - ssh deployprod-host docker pull ${IMAGE_REGISTRY}/ruoyi-demo:${CI_COMMIT_SHORT_SHA} docker stop ruoyi-demo || true docker rm ruoyi-demo || true docker run -d --name ruoyi-demo -p 8080:8080 ${IMAGE_REGISTRY}/ruoyi-demo:${CI_COMMIT_SHORT_SHA} environment: name: production when: manual only: - tags有几个细节值得解释。backend-build阶段把 Jar 包作为 artifact 留档方便在 CI 页面直接下载镜像构建阶段只对 Tag 生效避免每次分支提交都构建一堆只用于临时的镜像部署阶段设置成手动触发等于在代码无损的情况下给运维留了一个人工确认的闸门。实际生产里我更建议用docker compose或docker stack deploy来管理容器而不是在 SSH 命令里先 stop 再 run因为后者在容器中途挂掉时容易留下脏状态。如果你已经用 Kubernetes那就把 deploy 阶段改成更新 Deployment 的镜像 Tag从而让 Pod 滚动更新。4.2 部署策略别把“自动发布”做成“自动事故”在流水线里做部署最常见的问题不是工具层面而是发布策略太粗暴。我见过一个团队CD 阶段就是 SSH 上去把进程杀了再启动上线窗口内服务必然有一段空白。更合理的做法是在 CD 阶段引入蓝绿或滚动发布思路。对单机部署的若依这类系统最低成本的平滑发布是“先拉起新容器再去掉旧容器”。比如先启动一个用新 Tag 的容器监听不同的端口然后通过 Nginx 或网关把流量切过来最后清理旧容器。这套逻辑完全可以在 CI 脚本里写成函数封装每次发布调用同一个函数避免“发布脚本里临时新写一堆不可维护的 shell”。对于有多台机器或集群的场景滚动发布更合适。GitLab 的环境看板能显示当前部署版本部署时通过人工审批闸门控制风险。总之CD 的价值不只是省一次手动操作的时间更重要的是让每次部署都走同一条经过验证的路。4.3 非软件交付场景Dify 知识库的自动更新流水线近一年大模型应用落地多了Dify 这类提供知识库能力的平台也被更多团队引入。知识库的更新维护往往被忽略自动化——文档改了但知识库里的内容迟迟没同步导致大模型回答用的还是旧资料。其实 Dify 知识库的更新完全可以纳入 CI/CD 的范畴。思路很简单文档仓库任意改动时触发一条流水线去调用 Dify 的知识库创建或更新接口。以 GitLab CI 为例可以在文档目录发生变化时只跑一个同步 Jobstages: - sync sync-dify-knowledge: stage: sync image: python:3.11 script: - pip install requests - python scripts/sync_dify_kb.py only: changes: - docs/**/* variables: DIFY_BASE_URL: ${DIFY_BASE_URL} DIFY_DATASET_ID: ${DIFY_DATASET_ID} DIFY_API_KEY: ${DIFY_API_KEY}这个脚本里做三件事读取文档目录、调用 Dify 的文档上传接口、触发索引重建。API 具体端点随 Dify 版本会有变化配置前以官方 API 文档为准。但整体的价值是明确的它把“人工上传文档再等待向量化”变成了“文档更新就自动同步”这种场景其实非常适合用流水线承载也是 CI/CD 从传统应用交付走向更广泛自动化的一种体现。4.4 一个小提醒别把“流水线”搜成另一个世界在收集资料的时候我注意到一个很有意思的现象很多初学者搜“流水线设计”会搜到 logisim 这类数字电路课程里的 CPU 流水线设计研究的是取指、译码、执行这些硬件阶段而我们要做的 CI/CD 流水线是围绕代码提交、集成、测试、部署的自动化过程。两者都叫流水线但完全是两个领域。如果你正在给同事或者学生准备 CI/CD 入门材料记得把概念边界讲清楚。软件交付流水线关心的是“代码从提交到上线的自动化链路”硬件流水线关心的是“指令执行的并行效率”搜资料时别混在一起要不然一头扎进 logisim 的接口设计文档里半天绕不出来。5. 踩过的坑五个高频问题实录5.1 Docker 构建时连不上 daemon这是 GitLab CI 配合 Docker Executor 时代最常见的问题。在 Runner 的 Docker 容器里执行docker build经常会报Cannot connect to the Docker daemon。原因是构建容器里根本没有 Docker daemon需要额外找一个 daemon 提供服务。GitLab 官方的解法是在 Job 里声明 Docker-in-Docker 服务。上面的示例里已经写了services: - docker:24-dind这样构建容器就能通过特殊网络访问到 dind 容器里的 daemon 了。还有一个变通方案是直接把宿主机的 Docker Socket 挂载进构建容器但前面提到过这种方式的权限边界很粗。坏处是流水线里的任意代码都能直接控制宿主机 Docker风险不可控我一般只在信任度较高的内部 Runner 上这么干。5.2 缓存不命中或者缓存被污染缓存 Key 设计不合理缓存更新频率和构建并发不匹配都容易出问题。最典型的是多个分支共用同一个缓存路径合并代码时缓存里的依赖版本一会儿新一会儿旧测试结果没法复现。解决办法是把缓存 Key 分成两层稳定依赖一个 Key可变依赖另一个 Key。Maven 仓库这类基本不变的依赖可以用分支加锁文件名组合npm 的node_modules如果追求更快建议直接用锁文件内容或哈希做 Key依赖一变自然重新缓存避免旧包残留干扰构建。另外缓存目录的大小也要关注。我在一个项目里看到node_modules缓存已经几百 MB每次构建都要先把缓存拉下来再基于它构建反而比冷构建还慢。这时就需要定期清理缓存或只缓存 npm 的全局缓存目录而不是项目里的node_modules。5.3 多个分支同时发布同一环境当多个 MR 同时合并如果合并后自动触发生产环境部署就可能出现两个 Job 同时操作同一台服务器、同一个容器名的情况。后果往往是后一个 Job 把前一个正在发布的版本覆盖掉甚至两个 Job 同时在 stop 和 start 同一个容器直接发布失败。解决思路有三层第一部署阶段限制来源只允许特定分支或 Tag 触发从源头减少并发面第二利用工具的环境级串行能力让同一个环境同一时间只有一个部署任务在跑第三如果实在绕不开就在部署脚本里加文件锁或者调度锁。我自己更推崇前两层因为工具层面的约束比脚本锁更可靠也更可审计。5.4 权限模型混乱普通开发拥有生产环境的钥匙有一次帮一个客户排查事故发现流水线里的生产部署 Job 只要登录服务器就能执行而 Jenkins 的“任何人可以构建”设置让每个开发都能点“立即部署”。一个误触就能把生产环境重启一遍更不说恶意场景了。在这方面不同工具的机制差别很明显。GitLab 支持受保护环境和审批配置生产部署阶段可以限定只有 Maintainer 或指定角色能手动点击执行Jenkins 也能通过基于角色的插件区分权限但需要有人认真去配置。我的经验是流水线权限设置应该和代码分支保护保持同一策略——生产部署的触发人至少要和生产分支的合并权限对齐不能有“代码合并要两个人审批部署却能一个人随意点”的漏洞。5.5 制品过期与跨版本回滚流水线跑得飞快顺手把制品推上去之后回滚却成了问题。常见原因是镜像或 Jar 包被覆盖了旧版本无处可寻。我在一个项目里就遇到过“生产出问题想回滚发现 GitLab 里的 artifact 已经按默认保留策略被清理了镜像仓库里的latest又已经被覆盖成新版本”。把制品做“不可变”处理后这个问题就好办很多。镜像 Tag 务必用 Git 短 commit SHA 或带时间戳的版本号旧版本镜像自然不会因为新构建而消失。GitLab 的 artifact 也可以调整保留时间重要环境的制品建议设置 30 天或更长的保留期。回滚时只需把环境指向旧版本标签重新部署而不是把整条流水线再跑一遍。6. 决定选谁一张决策表与个人体会6.1 对照你的场景做决策很多选型文章写到这一步会直接给结论但我觉得“工具好不好”要放在具体场景里才会成立。下面这张表是我实践中的经验总结可以直接对照你的团队情况去用。需求特征推荐工具核心理由什么时候需要谨慎团队已重度使用 GitLab在乎权限审计GitLab CI/CD平台一体化、权限细、流水线即代码高级安全功能社区版受限时需考虑付费Java 团队沉淀了大量 Jenkins 插件Jenkins插件覆盖广、共享库积累必须补插件治理和安全更新机制开源项目或可接受托管服务GitHub Actions生态丰富、开始成本低数据敏感度和合规要求高时避免内网小团队自托管、想要轻量Gitea Actions资源占用小、语法兼容 Actions复杂企业级审计需求暂未满足时平台型团队、已有 Kubernetes 体系Tekton / Drone云原生、调度能力强学习成本和维护成本双高需要注意表格里给出的“推荐”是以比较标准的场景做依据。真实情况复杂得多比如大团队可能同时需要 Jenkins 承担既有任务、GitLab CI 承担新项目。不同工具并存并不是坏事只要团队的维护精力足够。6.2 我个人的选型体会如果一定要我给一个倾向我会说新团队新项目没有历史包袱优先选 GitLab CI/CD因为仓库、评审、流水线、审计在同一条链路上安全和提效的平衡最自然。已经有 Jenkins 资产且团队能拿出精力做治理的继续用 Jenkins 完全没问题但要把“流水线脚本入库、权限最小化、插件定期更新”这三件事当成纪律来执行。如果是 5 人左右的轻量团队Gitea Actions 是我最近很喜欢的选项它让我免去了给一个小团队部署一整套 Heavy 平台的负担同时又能获得类似 GitHub Actions 的体验。工具只是手段真正让交付又快又稳的是清晰的流水线设计、合理的权限边界和愿意持续改进流程的人。这个认知是我折腾了这么多套 CI/CD 环境后最想分享的一句话。