ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jenkins结合GitLab的持续集成实战:从Webhook到自动部署

Jenkins结合GitLab的持续集成实战:从Webhook到自动部署 说起持续集成很多团队都卡在同一个地方代码交到GitLab之后剩下的事情全靠人肉——手动拉代码、手动构建、手动跑测试、手动部署。短时间还能忍等提交频率一高矛盾就全暴露了。Jenkins结合GitLab做自动化持续集成就是把这套人肉流程替换成一条自动链路GitLab收到push事件Webhook通知JenkinsJenkins自动拉代码、执行构建和测试脚本然后把结果反馈回GitLab需要的话再接着做部署。这篇文章把我从零搭建这套链路的过程、踩过的坑以及那些网上一问一大片的报错完整整理出来给正准备动手的运维和开发同学做个参考。1. 想清楚再动手这套组合真正解决了什么问题1.1 持续集成不是装两个软件那么简单很多人一提持续集成第一反应就是装上Jenkins配上GitLab完事。但实际上如果没想明白它解决的问题装完也是一堆摆设。我见过最典型的场景团队几十号人代码全堆在GitLab的master分支上每次发版前专门抽一个人花半天时间手动构建、手动部署构建到一半发现有人提交了坏代码又得回滚重来。这不是个例而是绝大多数没做持续集成的团队的真实状态。持续集成的核心思想可以用做饭来类比一道菜如果等全部做完才尝味道发现盐放多了补救成本很高。但如果每放一种调料就尝一口问题当场就能发现。CI要做的就是把尝味道这个动作提前到每一次代码提交——每次push都自动拉代码、自动构建、自动跑测试有问题当场暴露在提交者面前而不是等发版前一天全组一起抓狂。这套链路在技术上并不复杂难点在于三个层面环境怎么搭得稳、Jenkins和GitLab之间怎么安全可靠地通信、流水线脚本怎么写才不至于变成一团乱麻。下面我按实际动手顺序一个个拆开讲。1.2 Jenkins和GitLab CI怎么选每次聊到这个话题总会有人问GitLab不是自带了CI/CD吗为什么还要单独上Jenkins这个问题问得很实在我的回答是按你的团队现状来千万别盲目跟风。如果团队从零开始项目也不多GitLab CI确实够用。它天然集成在GitLab里.gitlab-ci.yml放在仓库里MR页面直接能看到流水线状态维护成本很低非常适合中小型团队起步。但如果你的团队已经有多个仓库、多套技术栈、多个环境还涉及到复杂的构建调度、插件生态、定时任务、人工审批甚至要对接Kubernetes和AnsibleJenkins的优势就出来了。它是独立于代码仓库之外的调度中心所有任务围绕Job组织谁触发的、什么时候跑的、跑在哪个Agent上都归统一管理。我见过不少团队把GitLab CI硬撑到几十个服务最后配置文件互相引用、变量满天飞维护成本反而比Jenkins高。Jenkins虽然界面老、配置项多但它的灵活性和生态积累是实打实的这也是为什么很多公司即使上了GitLabJenkins依然活得好好的。1.3 这套方案的能力边界与适用团队咱们这篇讨论的是Jenkins结合GitLab也就是把GitLab当作代码托管和触发源Jenkins当作流水线执行引擎。这套组合适合什么团队我的判断标准是这三条代码已经托管在GitLab上、团队有统一的构建和测试规范、需要把构建结果和代码提交强关联起来。满足任何两条这套方案都能落得很有价值。同时也要说清楚边界它不负责代码质量本身只负责把构建—测试—反馈这条链路变成自动化。如果代码本身一团糟CI只会更频繁地把问题暴露出来这不是坏事但团队要有个心理准备。另外如果只是个人项目或者团队只有三五个人、项目就一两个我还是建议先用GitLab CI别为了技术热度引入第二套系统。2. 环境搭建GitLab和Jenkins怎么部署最省心2.1 用Docker部署GitLab社区版GitLab部署方式很多RPM包、云厂商镜像、Docker容器都有。我个人最推荐Docker方式理由很简单升级回滚方便数据目录清晰迁移的时候整个目录打包就走。社区版功能完全够用不要一上来就上企业版。下面是我实际使用的部署命令sudo docker run -d \ --name gitlab \ --restart always \ -p 80:80 -p 443:443 -p 22:22 \ -v /data/gitlab/etc:/etc/gitlab \ -v /data/gitlab/log:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest三个端口分别对应HTTP、HTTPS和SSH。要特别注意22端口如果宿主机本身已经开了SSH这里直接把22映射出去会冲突。我的习惯是把宿主机SSH换成其他端口把22留给GitLab这样团队用gitgitlab.example.com:group/project.git地址克隆代码不用在URL里带端口号。如果确实不想动宿主机SSH端口也可以把GitLab的SSH映射到其他端口比如-p 2222:22但这样每个仓库的SSH URL都要带上ssh://githost:2222/很麻烦不推荐。部署完启动很慢我第一次等了两三分钟还以为卡死了。可以用下面的命令看启动日志sudo docker logs -f gitlab看到gitlab Reconfigured!字样基本就绪。另外GitLab很吃内存4GB起步加到8GB会比较舒服。内存不够的表现是页面半天打不开或者服务起来后频繁重启后面踩坑实录里细说。2.2 Jenkins部署war包还是DockerJenkins的部署方式我试过两种直接跑war包和Docker容器跑。结论是新环境别纠结直接上Docker。war包方式的好处是调试直观但如果服务器上同时存在多个Java版本很容易遇到启动环境对不上的问题。而且Jenkins的配置全部存在~/.jenkins目录里换机迁移时要手动整理一堆文件容易漏。Docker方式就清爽很多目录映射清楚升级Jenkins只需要换镜像版本号。我的部署命令sudo docker run -d \ --name jenkins \ --restart always \ -p 8080:8080 -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ jenkins/jenkins:lts8080是Web端口50000是Master和Slave通信用的端口如果后面要加构建节点这个端口必须留出来。有个细节很多人会忽略/var/jenkins_home目录权限。容器首次启动时容器内的jenkins用户UID是1000如果宿主机挂载的目录属主不是这个UIDJenkins会没有写权限。我遇到过一挂载就报错的解决方案很简单先chown 1000:1000 /data/jenkins_home再启动容器。2.3 首次初始化、汉化与插件加速Jenkins起来后浏览器访问http://服务器IP:8080会要求输入初始管理员密码。这个密码不是注册生成的而是存在容器里用这条命令拿出来sudo docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword登录后第一步是选插件。新手容易被Install suggested plugins带跑装上几十个用不上的插件拖慢启动速度。我的做法是选Select plugins to install只装Git、Pipeline、SSH Agent、JUnit、HTML Publisher这几个核心插件后面需要什么再随时补。汉化方面装Locale插件和Localization: Chinese插件然后在Manage Jenkins→Locale里勾选强制使用中文。注意汉化插件只翻译Jenkins自身的界面第三方插件里的很多词汇仍然是英文这是正常现象同时对新Jenkins版本如果系统语言检测到非英文环境其实默认就会显示中文。插件下载慢是个老大难问题尤其是刚从国外源拉插件的时候。解决办法是在Manage Jenkins→Plugin Manager→Advanced→Update Site里把插件更新地址换成国内镜像站的Jenkins更新中心。这个操作对首次安装和后续更新都有效实测插件拉取速度快很多。3. 打通两侧握手Token、凭据和Webhook3.1 GitLab侧创建个人访问令牌Jenkins要和GitLab通信首先得拿到一张通行证。最常用的是个人访问令牌Personal Access Token简称PAT。创建路径GitLab右上角头像→Preferences→Access Tokens。名称随意Expiration date建议设长一点比如一年避免之后频繁失效。权限范围上如果只是拉代码勾read_repository就够如果还要触发流水线、修改仓库设置就需要勾api。我通常勾api和read_repository两个通用性最好。Token生成后只会显示一次一定要当时复制保存丢了就得重新生成。这玩意本质上就是一把钥匙谁拿到谁就能调用对应权限范围内的GitLab API千万别提交到代码仓库里。顺带回答一个搜来的人特别多的问题GitLab的Developer角色能不能直接提交到master默认情况下master分支是受保护的Developer不能直接push只能走Merge Request。如果想要Developer能提交需要在项目设置里调整Protected Branches把master的保护级别放开。但从工程实践角度我不建议放开MR加CI的流程本身就是一道质量闸门放开保护等于把闸门拆了。3.2 Jenkins侧配置GitLab凭据的三种方式Jenkins里配置GitLab凭据常见的路径有三种用户名密码、SSH Key、Token。我直接给个表格对比方便你按场景选方式配置位置优点缺点用户名密码Jenkins凭据类型Username with password最直观操作门槛低成员离职或改密码后全链路失效SSH Key私钥作为凭据公钥配到GitLab稳定、与用户解耦、适合服务器间通信初始配置比密码多一步Token选GitLab API Token类型适合调用API触发任务有有效期需要定期续期我自己的习惯是拉代码统一走SSH Key触发和API调用走Token。原因很简单SSH Key不受账号密码变更影响一台构建服务器配一次能稳定用很久Token则按用途单独建比如jenkins-webhook一个、jenkins-deploy一个作用域隔离某个token泄漏了也不会把所有权限暴露出去。SSH Key的配置步骤先在构建服务器上生成一对密钥ssh-keygen -t rsa -b 4096把公钥内容填到GitLab的用户设置→SSH Keys里私钥内容在Jenkins的Credentials→Add Credentials里选择SSH Username with private key粘贴进去。就这么简单后面所有拉代码操作都用这一套。3.3 配置Webhook实现push自动触发光有凭据还不够要真正实现push后自动构建得靠Webhook。GitLab在代码提交、MR更新、Tag推送这些事件发生时会往你配置的URL发一个HTTP请求Jenkins收到请求后跑任务。配置路径GitLab项目→Settings→Webhooks。URL填Jenkins的任务触发地址格式是http://Jenkins服务器地址/project/任务名注意不是首页地址必须带/project/路径。在Jenkins任务配置里找到构建触发器勾选Build when a change is pushed to GitLab下面会生成一个Secret Token把它复制到GitLab Webhook的Secret Token字段两侧保持一致。事件选择上我勾的是Push events和Tag push eventsMR事件一般不开否则Merge Request一频繁更新就触发一堆没有意义的构建。这里有一个特别坑的地方新版GitLab默认会拦截发往本地网络的Webhook请求表现为Request has been blocked by the network security policy。解决办法是在GitLab管理后台的Settings→Network→Outbound requests里勾选Allow requests to the local network from webhooks and integrations。如果用的是GitLab.com官方托管服务这个选项是锁死的自建GitLab才能开。3.4 高频报错GitLab版本与工具链不兼容在很多技术论坛和搜索引擎里idea login failed. gitlab versions older than 14.0 are not supported算得上高频问题了。这个报错字面意思是你在IntelliJ IDEA的GitLab插件里登录GitLab实例时插件检测到GitLab的API版本低于14.0出于API兼容策略拒绝继续登录。不只是IDEAGitLab CLI工具、某些Jenkins插件也存在类似逻辑。它们的处理思路一致官方只保证对某个最低版本以上的API做完整支持低于这个版本就直接报错避免中途出现不可预期行为。解决办法核心就两条。第一把GitLab升级到支持范围内的版本尤其是老版本GitLab比如13.x升到14.0以上这能一劳永逸解决这类兼容报错。第二改用不依赖特定API版本的方式比如用git命令行直接提交推送、用SSH方式拉代码这些底层操作完全绕开API版本限制。反正我的原则是能用官方最新稳定版就用最新稳定版等报错出现再去排查版本兼容问题成本更高。4. 第一个流水线拉代码、构建、跑自动化测试4.1 项目类型自由风格还是PipelineJenkins里建任务最经典的选择是Freestyle project和Pipeline。Freestyle是图形界面有人在界面里点选配置就行但好处也是坏处——所有配置都存在Jenkins服务器里脱离版本管理想审阅、想备份、想换环境重搭都不方便。Pipeline则是用Jenkinsfile写代码存放在代码仓库里随代码一起走版本管理。同一个仓库的流水线开发者可以直接在MR里看到改了什么。我的结论很直接新项目一律用Pipeline别再用Freestyle了。Pipeline语法有两种风格Scripted和Declarative。我只推荐Declarative结构清晰、可读性好Post阶段也能统一处理收尾动作对团队成员非常友好。4.2 从拉代码到自动测试一个典型的Jenkinsfile下面是一个Python项目的Pipeline脚本流程覆盖了拉代码、装依赖、跑pytest、归档报告这个骨架你换成Java、Node、Go其实都一样只改构建命令而已pipeline { agent any stages { stage(拉取代码) { steps { checkout scm } } stage(安装依赖) { steps { sh python3 -m venv venv sh ./venv/bin/pip install -r requirements.txt } } stage(自动化测试) { steps { sh ./venv/bin/pytest tests/ -v --junitxmlreport.xml } } } post { always { junit report.xml archiveArtifacts artifacts: dist/**, allowEmptyArchive: true } onFailure { echo 这次构建失败了快去抢修 } } }第一行的checkout scm是最容易被忽略但最应该理解的一步。它读取的是Jenkins任务里配置的仓库地址和凭据。如果你在任务里已经选择了Git并填好Repository URL和Credentialscheckout scm就会自动完成拉取不需要手动写git clone也不需要硬编码分支名。junit report.xml这行很关键它把pytest生成的JUnit格式报告喂给Jenkins之后构建历史页面就会出现测试趋势图一眼看出测试是变好还是恶化。pytest命令里的--junitxmlreport.xml就是生成这个报告文件。4.3 pytest接入细节报告、失败阈值与反馈闭环有人会用pytest -v跑完就算完这样Jenkins无法区分测试全部通过和测试压根没跑。要让测试结果真正参与质量反馈必须生成机器可读的报告用--junitxml参数。在小项目里junit report.xml的路径写对就能用了一旦测试用例变多建议在第一个阶段额外加一步目录清理避免旧的report.xml混进来。关于失败阈值的设置Jenkins默认只要JUnit报告里有失败的testcase构建就会标红。这一点不用额外配置但要注意一种边界情况——如果因为环境问题导致测试进程直接崩溃根本没有生成report.xmlJenkins的junit步骤会直接报找不到报告文件构建自然失败。这是好事说明反馈闭环生效了。还有个小技巧如果你后面想接入Allure报告就在Pipeline里增加一步把report.xml转换成Allure的HTML结果再用Allure插件发布。不过Allure对服务器资源有一定消耗小团队直接用JUnit自带的趋势图和HTML Publisher就够。4.4 分支策略与触发配置第一个Pipeline跑通之后很多人就直接用了但分支策略建议趁早定不然后面会乱。我的做法是开发分支比如dev的push触发自动构建只跑编译和测试不改任何环境master分支的push同步触发构建并且只有master分支的流水线才进入部署阶段。Tag推送则对应版本发布可以走完整的构建→测试→制品归档流程。如果不想用WebhookJenkins也支持定时轮询或者远程触发。远程触发就是在任务URL后面加build?tokenxxx通过带token的HTTP请求手动触发这个在外部系统对接时很实用。不管用哪种方式都记得在Jenkinsfile里通过环境变量标注当前分支比如在阶段显示当前构建分支${env.GIT_BRANCH}否则同一份Jenkinsfile跑在多个分支上日志里根本分不清是哪条分支。5. 进阶玩法容器内Docker、环境变量、自动化部署5.1 Jenkins容器内使用Docker命令DinD方案很多团队把Jenkins用Docker跑起来后发现一个尴尬问题容器里的流水线要执行docker build、docker push但容器里没有Docker命令。即便装了docker客户端也没有可用的Docker daemon。解决办法最常见是挂载宿主机的Docker socket让容器内的docker客户端直接和宿主机的daemon通信。在Jenkins容器启动命令里加一行参数-v /var/run/docker.sock:/var/run/docker.sock再进容器确认docker命令存在如果镜像里没带客户端可以用docker exec jenkins docker version验证没有就需要装。这种方式的配置成本最低但必须清楚一个代价容器内的Jenkins任务从此拥有了宿主机Docker的控制权基本等同于宿主机root权限。控制在受信团队手里没问题但如果Jenkins面上挂着一些不受控的外部任务风险就大了这也是为什么有些团队改走Docker in Docker方案虽然隔离性更好但也要接受额外的daemon资源开销。5.2 用好Jenkins环境变量排查流水线问题时环境变量是最容易被忽视的诊断工具。Jenkins在构建时会注入一批内置环境变量直接在脚本里读取就行。我用得最多的是这几个变量名含义典型使用场景JOB_NAME任务名称日志输出、制品目录命名BUILD_NUMBER当前构建序号区分每次构建的产物BUILD_URL本次构建网页地址通知消息里附链接WORKSPACE工作目录路径跨stage传递文件位置信息GIT_COMMIT当前构建对应的commit号回滚定位、记录版本GIT_BRANCH分支名区分环境、条件判断GIT_URL仓库地址脚本里需要仓库信息时排查问题时最粗暴有效的一招是在Pipeline里加一步sh env把全部环境变量打出来看。你可能会在里面发现很多文档里没写透的隐藏变量比如和Git相关的这在调试为什么这台机器拉的是旧代码这类问题时非常有价值。5.3 结合Ansible做自动化部署构建和测试跑完下一步就是部署。部署目标如果是云服务器或者内网物理机我最常配合的方案是Ansible。典型的链路是Jenkins负责构建出制品Ansible负责把制品分发到目标机器并执行服务重启。核心思路是构建产物不直接scp到生产环境而是先推到一台部署基线上的服务器或制品仓库Ansible Playbook从那里拉取。这样做的好处是可追踪每次部署用的是哪个构建号清清楚楚。在客户端集成方面Jenkins服务器要装Ansible并把目标服务器的SSH密钥放到Jenkins凭据里。Playbook本身建议用Git管理每次部署前在Pipeline里ansible-playbook -i inventory/hosts deploy.yml拉最新版本执行。生产环境部署我强烈建议加上人工审批步骤用Jenkins的Input Step构建完成后暂停等负责人点击确认才继续。这一道关卡虽然让流程没那么全自动但能拦住90%的误操作风险。顺手说一句有些做网络设备运维的同事会问我能不能用这套自动化流程跑网络设备的配置下发完全可以。只要目标设备支持SSH登录和命令行操作Ansible的网络模块就能接管思路和服务器部署一致只是Playbook换成对应的网络模块写法。5.4 向Kubernetes集群扩展的思路项目规模到了一定程度固定的Jenkins节点资源不够用就需要考虑动态扩展了。Kubernetes插件是这条路的核心每个构建任务按需在K8s集群里创建独立的Pod作为临时执行环境任务结束自动销毁资源不浪费环境也不互相污染。配置上要在Jenkins的Cloud配置里填K8s集群地址和命名空间再定义几个不同label的Pod模板比如一个带PythonNode的一个带JavaMaven的。Pipeline里agent any改成agent { label python }Jenkins就会自动到K8s集群里调度这个label对应的Pod来执行任务。这套方案对团队最大的价值是并发能力提升几十个任务同时跑不再互相抢资源每个任务的环境都是干净的。代价是Jenkins集群本身要维护K8s集群的架构复杂度也不低这是从能用走向规模化时考虑的事。如果团队只有几个项目一台机器跑Jenkins完全够用没必要上来就上K8s平台复杂本身也是成本。6. 踩坑实录这些问题在网上问得最多6.1 GitLab启动不了、端口被占搜索gitlab启动不了的人非常多的多数是部署环节出了岔子。最典型的是端口冲突宿主机22端口已经被SSH占用GitLab的-p 22:22参数直接失败容器反复重启。排查时先看容器状态和日志sudo docker ps -a | grep gitlab sudo docker logs --tail 100 gitlab如果日志里有Address already in use就用ss -lntp | grep 22查出哪个进程占了端口按上面说的二选一处理改宿主机SSH端口或者改GitLab映射端口。另一个常见问题是内存不足。GitLab全家桶PostgreSQL、Redis、Sidekiq、Gitaly都跑在一个容器里内存1GB肯定不够表现是GitLab页面一直转圈或者容器起来几十秒就被杀。把宿主机内存扩到4GB以上或者在docker run里加--memory6g限制资源分配基本能解决。6.2 Docker拉取镜像慢自建GitLab和Jenkins都要拉官方镜像国内网络的拉取速度有时候很感人。配置Docker镜像加速器是最常规的做法修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker镜像加速地址 ] }改完重启Dockersudo systemctl restart docker再拉镜像速度会有质变。这里有个容易踩的坑如果Docker版本是Windows Desktop或Mac版镜像加速配置在图形界面里设置不读daemon.json别改了半天没生效。另外镜像拉取成功不代表构建时所有依赖都下载快Jenkins里的插件更新、GitLab里的自带镜像同样都是网络问题但解决路径不同。插件这边走Update Site镜像GitLab那边走官方加速配置或检查网络环境别混为一谈。6.3 插件装不上、汉化不生效Jenkins插件安装失败先别急着重试多半是更新源不可达。在Manage Jenkins→Plugin Manager→Advanced页面的Update Site里把默认地址换成可达的镜像地址然后再回Available选项卡搜索插件就不会一直转圈了。还有一种场景是企业内网环境根本连不上外网这种只能离线安装。先去能联网的机器从镜像站或官网下载.hpi文件再在Manage Jenkins→Manage Plugins→Advanced页面最下方通过Upload Plugin上传安装。注意插件有依赖关系比如装某个插件前要求先装Pipeline离线场景要手动逐个满足依赖建议一次传多个文件让Jenkins自己处理。汉化不生效的问题多半出在Locale插件的配置上。装好Locale插件后要把默认语言改成中文并在User Default Language里设置否则只翻译英文界面或者干脆没反应。另外Jenkins网页缓存也可能导致界面新旧混杂加个?参数强刷一下经常就正常了。6.4 Webhook不触发、401与CSRFWebhook配置完但就是不动是最让人抓狂的问题。按这个顺序排查十有八九能定位。第一看GitLab的Webhook历史记录。在Webhook配置页点之前的推送请求会显示响应状态码。如果显示URL blocked就是本地网络拦截问题按前面说的去后台勾选允许本机网络请求。第二看Jenkins的系统日志sudo docker logs --tail 50 jenkins确认请求有没有进来。第三检查Secret Token是否一致这是401最常见的原因。第四如果GitLab服务端启用了CSRF保护而Jenkins端的触发地址没有带正确的认证信息也会收到非预期响应。还有一个容易被忽略的Jenkins和GitLab不在同一台机器时网络访问要通。最笨但最有效的验证方式是直接在GitLab所在的服务器上curl http://Jenkins地址/project/xxx看返回是不是你配置的认证结果。网络不通时排查防火墙和云安全组比在配置页里反复改参数有效得多。6.5 凭据失效、权限不足与master提交限制持续集成跑得好好的突然某天构建开始报权限错误第一反应先查两处GitLab个人访问令牌是不是过期了或者Jenkins里存的SSH私钥是不是对应账号被移出了项目组。Token有效期过了就重新生成然后去Jenkins的Credentials里更新这操作很机械但确实是最常发生的问题。关于gitlab developer可以提交代码到master吗这个问题我已经在前面说了默认是不行的这里再补充一句如果你的CI任务用的是Developer权限的账号那它同样不能直接往受保护的master分支推代码。发布阶段的流水线需要推tag或者更新版本号建议单独为CI建一个Maintainer权限的机器人账号不要用任何人的个人账号。这样即使CI机器被攻破影响面也只局限在代码仓库操作不至于把管理员权限带出去。最后分享一点我的个人体会整套Jenkins结合GitLab的链路跑顺之后最大的体会是工具链条本身并不神秘GitLab管代码、Jenkins管调度、Webhook做触发、Pipeline写流程每个环节都是公开文档里翻得到的东西。真正的分水岭还是你愿不愿意把每次提交都自动构建自动测试这件事变成团队习惯。如果让我重新搭一遍我不会先去倒腾界面和Webhook而是先把Jenkinsfile写好、放进GitLab走版本管理让整个流水线配置可以被评审、被回溯。等Jenkinsfile稳定了再建Webhook就是顺手的事。持续集成这条路没有终点流水线只会越写越厚、越接越复杂守住一切配置皆代码这条底线后面扩展就不会乱。
返回列表