ARTICLE DETAIL

资讯详情

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

Docker部署Jenkins与CI/CD流水线配置全指南

Docker部署Jenkins与CI/CD流水线配置全指南 以前部署Jenkins我最烦的不是Jenkins本身而是它依赖的JDK版本、Tomcat容器、系统环境变量、权限配置。换一台机器就要从头折腾一遍遇到依赖冲突更是头疼。后来我把整套Jenkins迁到Docker里配合流水线做自动部署一键启停、升级回滚都清爽多了。这篇文章就围绕docker安装jenkins并配置这条主线把我从镜像选型、目录规划、容器启动到插件换源、宿主机Docker调用、真实流水线落地的完整过程写出来。适合刚接触容器化部署、或者已经装好Jenkins但还没跑通自动部署的读者参考也欢迎大家对照自己的环境做调整。我会尽量把每一步的命令、参数含义和踩坑点写透不绕弯子。尤其是容器内调用宿主机Docker、插件下载慢、权限报错这几块很多教程一笔带过实际生产里恰恰最容易卡住。1. 为什么推荐用Docker部署Jenkins镜像选型与前置条件1.1 裸机安装与容器化部署的取舍先说实话如果你只是在自己电脑上临时起一个Jenkins玩一下那直接下载war包用java -jar跑起来也完全没问题。但一旦涉及到团队协作、多环境部署、频繁升级裸机那套方案的维护成本就上来了。用Docker装Jenkins核心收益其实就三点环境隔离Jenkins依赖的JDK、运行库都在镜像里不会污染宿主机反过来宿主机上的环境变化也不会影响Jenkins。升级回滚极快想升级就换一个镜像标签重新起容器数据留在卷里不满意随时回滚到旧镜像。可迁移、可复现本地调试好的镜像拿到测试服务器、生产服务器上都能跑出一致的结果。代价当然也有多了一层容器网络和卷的维护刚开始不熟悉的话参数写错一个就能卡半天。但总体来说收益远大于成本。1.2 官方镜像别选错Docker Hub上带jenkins关键词的镜像很多名字五花八门jenkinsci/jenkins、jenkins/jenkins、jenkinsci/blueocean、bitnami/jenkins……这里我建议直接用官方维护的jenkins/jenkins原因很简单jenkinsci/jenkins是早期社区镜像Jenkins官方后来把仓库迁到了jenkins/jenkins下面旧的虽然还能用但更新频率和官方支持力度已经不一样了。jenkinsci/blueocean集成了Blue Ocean界面适合做可视化流水线演示实际生产里很多人还是用标准镜像自己装插件。标签选择上**推荐lts】。Jenkins有长期支持版LTS和每周更新版weeklyLTS版本迭代慢、稳定性优先级高生产环境别追求最新功能稳定压倒一切。另外注意一个细节jenkins/jenkins:lts-alpine这类Alpine变体会更省空间但有些插件依赖glibc或者特定系统库在Alpine的musl环境下偶尔会出兼容问题。第一次部署我的建议就是直接用默认的lts镜像等熟悉了再考虑精简版。1.3 准备Docker环境与镜像加速开始安装前先确认Docker本身没问题docker version docker ps如果docker ps都能正常返回说明Docker守护进程在运行。Linux服务器上常用的是dockerdWindows/macOS上一般是Docker Desktop这两个不冲突选一个适合自己环境的就行。Windows用户如果遇到Docker Desktop failed to start because virtualisation support wasnt detected这类报错大概率是BIOS里虚拟化没开或者未安装WSL2。这个问题在Windows下很常见一般在BIOS开启Intel VT-x/AMD-V然后启用Windows Hypervisor Platform和WSL2即可解决。Linux服务器上这类问题少但如果用的是虚拟机也要确认嵌套虚拟化开了。然后是镜像加速这个直接影响docker pullJenkins镜像的速度。国内直连Docker Hub经常慢到怀疑人生配置镜像加速的标准姿势是修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerhub.icu, https://docker.registry.cyou ] }改完后重启Dockersudo systemctl restart docker注意加速地址不是越多越好选一两个稳定可用的就行。配置后可以用docker info | grep -A 10 Registry Mirrors验证是否生效。Docker就绪后就可以开始规划Jenkins的数据目录和容器启动参数了。2. 一次跑通的安装命令目录规划与容器启动2.1 先想清楚数据放哪里Jenkins的所有配置、插件、构建记录、任务定义都存放在JENKINS_HOME目录里。容器内这个路径固定是/var/jenkins_home但这个目录默认位于容器可写层容器一删数据就没了。所以必须把宿主机的目录挂载进去实现数据持久化。我习惯的做法是在宿主机创建独立目录比如sudo mkdir -p /data/jenkins_home sudo chown -R 1000:1000 /data/jenkins_home这里1000:1000是一个很关键的细节Jenkins官方镜像内部使用uid 1000的用户运行进程。如果宿主机目录属主不是1000容器内Jenkins进程可能没有写权限启动后会报各种Permission denied或者日志目录无法写入的错误。你可能会问为什么不直接chmod 777省事是真省事但安全性很差而且后续如果做备份、迁移777目录也会带来一堆麻烦事。老老实实chown到1000最稳。2.2 先跑一个最小化的容器很多人一上来就照着网上的全家桶命令抄把docker.sock、宿主机docker二进制、时区文件全挂进去结果报错了一个接一个根本分不清是哪里出的问题。我的建议是先跑通再做增强。第一步最小安装docker run -d \ --name jenkins \ -p 8080:8080 \ -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -e TZAsia/Shanghai \ --restartalways \ jenkins/jenkins:lts逐项解释一下-d后台运行容器。--name jenkins给容器命名后续操作直接用名字即可。-p 8080:8080Jenkins Web界面默认端口。左边是宿主机端口右边是容器端口。如果你宿主机8080被占了可以改成-p 18080:8080。-p 50000:50000Jenkins与Jenkins Agent构建节点通信的JNLP端口。如果暂时不用Agent这个端口可以不映射但建议保留后面加构建节点时不用重建容器。-v /data/jenkins_home:/var/jenkins_home数据目录挂载这就是Jenkins的家。-e TZAsia/Shanghai设置容器时区为中国时区。不设的话容器默认UTCJenkins构建日志时间会比本地时间慢8小时排查问题非常别扭。--restartalwaysDocker守护进程重启或机器重启后容器自动拉起省去手动启动的麻烦。启动后看下容器状态和日志docker ps docker logs -f jenkins看到类似Jenkins is fully up and running的日志说明容器起来了。2.3 端口、内存与JVM参数细节8080端口是主界面入口浏览器访问http://宿主机IP:8080即可。有两点经验说一下如果是在云服务器上部署记得在安全组/防火墙里放行8080端口否则浏览器永远打不开。如果通过Nginx反向代理建议把/jenkins路径或者独立子域名代理到容器的8080端口这里不展开。JVM内存也是容易被忽略的点。Jenkins默认堆内存偏保守任务多了以后经常卡顿甚至OOM。可以通过JAVA_OPTS环境变量控制docker run -d \ --name jenkins \ -p 8080:8080 \ -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -e TZAsia/Shanghai \ -e JAVA_OPTS-Xms1024m -Xmx2048m \ --restartalways \ jenkins/jenkins:lts-Xms是初始堆内存-Xmx是最大堆内存。机器内存大于4G的话给Jenkins分2G比较合理。50000端口如果只用Jenkins主控节点做事这个端口用不上但一旦想添加Linux/Windows构建Agent节点或者用SSH方式连接外部节点这个端口还是留着。容器启动后再想加端口映射只有重建容器一条路所以建议一开始就映射上。3. 初始化配置解锁、换源、最小插件集3.1 解锁实例与初始密码容器跑起来后浏览器访问http://宿主机IP:8080第一屏会要求输入解锁密码。这个密码Jenkins生成后放到了容器内执行下面命令查看docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword把输出的一串随机字符串粘贴进去就能解锁。有个小细节如果你用的是自定义挂载目录密码文件也在这个目录下直接在宿主机上cat /data/jenkins_home/secrets/initialAdminPassword一样能拿到。这个密码只在首次解锁时使用后面创建完管理员账号就没用了。3.2 插件中心换源解锁后会提示安装插件。这里有个非常关键的操作先把Jenkins插件下载源切换成国内镜像。默认插件源updates.jenkins.io在国内访问极慢经常插件列表加载半天、安装超时。不换源你连安装推荐的插件这步都能卡十分钟。两种改法我推荐第二种方式一网页端修改进入Manage Jenkins→Plugins→Advanced settings在Update Site处把URL改为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json这种方式操作简单但某些版本里只改网页不触发配置落盘Jenkins重启后URL会被还原。方式二直接改配置文件先进入容器查看当前配置docker exec -it jenkins bash cat /var/jenkins_home/hudson.model.UpdateCenter.xml里面会有一个url节点指向默认的https://updates.jenkins.io/update-center.json。把它替换为清华镜像sed -i s#https://updates.jenkins.io/update-center.json#https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json# /var/jenkins_home/hudson.model.UpdateCenter.xml然后重启容器让配置生效docker restart jenkins提示改完配置文件后网页端手动触发一次检查更新插件列表下载速度会明显改善。顺带提一句除了清华源像华为云、阿里云的Jenkins镜像源也能用选稳定能访问的就行。3.3 插件别贪多我常用的最小集合首次安装插件时界面会问你要不要安装推荐的插件。说实话推荐列表里有一些建设用不上的比如某些云平台插件、邮件通知全家桶装多了不仅占空间还拖慢启动速度插件版本冲突的概率也更大。我的做法是选选择要安装的插件然后按场景打勾。如果是做常规的代码拉取、构建、自动部署下面这四个大类够用了Git相关Git、Git Client—— 没有它没法从Git仓库拉代码。Pipeline相关Pipeline、Pipeline: Stage View—— 这是定义流水线的基础。Docker相关Docker Pipeline、Docker Commons、Docker API—— 配合后续容器内调用Docker的场景。辅助类Credentials Binding凭据管理、Timestamper构建日志显示时间戳、Blue Ocean可视化流水线界面可选。插件不是一次装完就一劳永逸的后续根据项目需要随时在Manage Plugins里补装。装完插件后重启一次Jenkins。3.4 全局工具链配置JDK、Maven、GitJenkins要干活得有JDK、Maven这些构建工具。进入Manage Jenkins→Tools这里支持两种方式自动安装勾选Install automaticallyJenkins会从对应发行商的网站下载JDK、Maven。但在国内网络环境下自动安装经常卡在下载环节。手动指定路径如果宿主机或容器内已经有JDK、Maven直接填写JAVA_HOME路径即可。容器内自带Git这一点不用额外配置。JDK我推荐用自动安装的方式但下载慢时可以手动下载JDK包放进容器或挂载目录里然后在Tools里指定路径。判断容器内Java在哪里可以执行docker exec jenkins which java一般会输出/opt/java/openjdk之类的路径。后面Pipeline构建时Jenkins会根据你在Tools里配置的JDK/Maven来执行对应的sh命令。4. 让容器内Jenkins使用宿主机Docker两种思路4.1 构建任务为什么需要Docker命令单纯的代码拉取编译打包其实不一定要Docker。但一旦要做镜像构建和自动部署流程就绕不开docker build、docker tag、docker push。而Jenkins是跑在容器里的容器内默认没有Docker CLI也没有Docker守护进程。这时候有两个主流方案方案隔离性实现复杂度适用场景Docker-in-DockerDinD较高较高需挂载docker.sock或让dind容器管理需要隔离构建环境的场景挂载宿主机Docker socket较低较低直接复用宿主机Docker大多数中小团队的标准做法4.2 DinD方案什么时候才值得用DinD的典型做法是再起一个docker:dind容器Jenkins容器通过网络访问它的Docker API。好处是Jenkins构建用的Docker环境相对独立不会和宿主机上的容器混在一起坏处是需要额外维护一个Docker服务容器监控、资源隔离都更麻烦。对于普通团队我的建议是别上来就搞DinD因为你不仅要处理Docker API地址的问题还要解决TLS认证、网络互通配置量成倍增加。除非你有严格的隔离需求否则跳过。4.3 挂载Docker.sock方案推荐做法这个方案的核心思想把宿主机的Docker控制权交给Jenkins容器。具体操作分两步。第一步重新创建容器。这里明确说需要挂两个东西docker run -d \ --name jenkins \ -p 8080:8080 \ -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker \ -e TZAsia/Shanghai \ -e JAVA_OPTS-Xms1024m -Xmx2048m \ --restartalways \ jenkins/jenkins:lts如果你之前已经用最小命令起过容器这一步需要先docker rm -f jenkins然后重新运行上面的完整命令。数据目录没动所以原来的配置和任务都还在。第二步解决权限问题。挂载了docker.sock后容器内执行docker ps经常会报Got permission denied while trying to connect to the Docker daemon socket原因很简单容器内的Jenkins用户uid 1000不在宿主机的docker用户组里没有权限访问/var/run/docker.sock这个socket文件。解决办法有两种选一个办法A宿主机层面授权。查看socket所属组ls -l /var/run/docker.sock假设输出是root:docker那么把宿主机上uid 1000对应的用户加入docker组或者直接调整socket权限。但更通用的是办法B。办法B在容器内同步docker组ID。先在宿主机查看docker组的GIDgrep docker /etc/group假设输出是docker:x:994那么在容器内创建同名同GID的组并让Jenkins用户加入docker exec -it jenkins bash sudo groupadd -g 994 docker sudo usermod -aG docker jenkins这里的994要替换成你自己宿主机上的实际GID。操作完成后重启容器docker restart jenkins提示如果宿主机Docker安装方式比较特殊比如rootless模式socket路径可能不是/var/run/docker.sock需要先用docker context ls确认当前API socket在哪。4.4 验证容器内Docker可用重启后在容器内执行docker exec jenkins docker ps docker exec jenkins docker version如果能看到宿主机上的容器列表说明Jenkins容器已经成功操控宿主机Docker。还有个容易踩的坑容器内的docker客户端版本和宿主机docker服务端版本不一致。如果宿主机Docker版本高于容器内CLI版本执行命令时可能报client version is too new或者干脆通信失败。我建议挂载宿主机/usr/bin/docker文件让容器内使用与宿主机完全一致的客户端这样版本问题基本能避免。上面完整命令里第三行-v /usr/bin/docker:/usr/bin/docker就是干这个的。5. 从拉代码到自动部署一条真实流水线5.1 先配置代码仓库凭据流水线要拉代码得先让Jenkins能访问你的Git仓库。进入Manage Jenkins→Credentials→System→Global credentials添加凭据。这里我强烈推荐用SSH Key而不是用户名密码。原因很简单SSH Key和用户身份绑定不需要在Jenkins里明文保存密码。多个项目共用同一个账号时维护Key比维护密码可靠。和GitLab/GitHub/Gitea的集成都友好。具体操作先在宿主机或本地生成一对Keyssh-keygen -t rsa -b 4096 -C jenkinsexample.com然后把公钥添加到Git平台的SSH Keys里私钥内容粘贴到Jenkins凭据中类型选择SSH Username with private key填好Username和私钥内容给凭据起一个ID比如my-git-key后面Pipeline里直接用这个ID引用。5.2 一个可以直接抄的Pipeline脚本下面这条流水线覆盖了拉代码→Maven构建→Docker镜像构建→推送到镜像仓库→远程部署的全流程。直接新建一个Pipline任务把脚本粘贴进去即可pipeline { agent any environment { DOCKER_REGISTRY registry.example.com IMAGE_NAME myapp IMAGE_TAG ${BUILD_NUMBER} GIT_REPO gitgitlab.com:group/myapp.git } tools { maven maven-3 jdk jdk-17 } stages { stage(拉取代码) { steps { git branch: main, credentialsId: my-git-key, url: ${GIT_REPO} } } stage(Maven构建) { steps { sh mvn clean package -DskipTests } } stage(构建Docker镜像) { steps { sh docker build -t ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} . } } stage(推送镜像) { steps { withDockerRegistry( registry: https://${DOCKER_REGISTRY}, credentialsId: harbor-account ) { sh docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} } } } stage(远程部署) { steps { sh ssh deploy-userprod-server docker pull ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} docker stop myapp || true docker rm myapp || true docker run -d --name myapp -p 8081:8080 ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} } } } post { success { echo 构建和部署成功镜像标签: ${IMAGE_TAG} } failure { echo 流水线执行失败请查看构建日志 } } }5.3 脚本关键点解析tools块这里引用了你在第3.4节配置的Maven和JDK名称。名称必须和Tools里设置的保持一致否则Pipeline会报未找到工具。**stage(Maven构建)**中的sh mvn clean package -DskipTests实际执行时Jenkins会使用tools块里配置的Maven。-DskipTests是跳过单元测试我一般先跳过跑通全流程再根据项目情况放开测试。withDockerRegistry这是docker-pipeline插件提供的步骤作用是在执行docker push前自动向镜像仓库完成登录认证。credentialsId引用的是镜像仓库的用户名密码凭据。不用这个插件的话也可以手动docker login -u xxx -p xxx但会把密码暴露在Pipeline脚本里不推荐。远程部署那段ssh deploy-userprod-server这里演示的是最朴素的部署方式——通过SSH登录远程服务器拉取镜像、停旧容器、起新容器。生产环境更优雅的做法是用Ansible、Kubernetes编排但原理都一样先确保镜像到了目标机器再让容器跑起来。5.4 任务触发方式轮询还是WebhookPipeline写好后在任务配置里可以设置触发方式。两种常用方式Poll SCM按固定时间间隔检查Git仓库有没有新提交。配置方法是在任务配置的Build Triggers里勾选Poll SCM填入类似H/2 * * * *的Cron表达式意思是每两分钟扫描一次。好处是配置简单缺点是最高频率到分钟级仓库提交后最多延迟2分钟才触发。WebhookGit平台在push后主动请求Jenkins接口。需要Jenkins安装Git Hook Plugin然后在Git平台配置Webhook地址http://jenkins地址:8080/git/notifyCommit?url仓库地址。这个方案实时性最好但要确保Jenkins能被Git平台网络访问到。如果只是个人项目Poll SCM够用了团队协作建议Webhook体验会好很多。6. 高频坑位实录镜像下载慢、权限报错、时区乱了6.1 Jenkins镜像下载慢docker pull jenkins/jenkins:lts卡在几分钟不动这是最常见的开局问题。解决思路跟上文镜像加速一样配置/etc/docker/daemon.json的registry-mirrors再重启Docker。注意加速只对Docker Hub官方镜像生效。如果你后续还要从其他私有仓库拉镜像加速不影响私有仓库。6.2 数据卷权限与容器删除后的数据安全前面反复强调chown -R 1000:1000再补充一个场景。有些人图省事直接用-v jenkins_home:/var/jenkins_home这种命名卷好处是不用手动建目录、权限问题少但坏处是卷挂载在/var/lib/docker/volumes/下面备份和迁移时不容易直观看到数据。如果你用命名卷想知道数据实际位置执行docker volume inspect jenkins_home输出里的Mountpoint路径就是真实数据目录。另外删容器时千万别顺手把卷删了。docker rm -f jenkins没问题但docker rm -fv jenkins会把卷一起删掉Jenkins里的任务、插件配置全没了。升级或迁移前先备份卷目录tar -czvf jenkins_home_backup.tar.gz /data/jenkins_home6.3 时区不正确导致构建日志时间混乱容器默认UTC时间而国内是UTC8。如果启动命令忘了加-e TZAsia/ShanghaiJenkins构建日志里的时间戳会比真实时间晚8小时。排查问题时你看到下午3点的构建记录心里得先换算成晚上11点非常折磨人。解决方式有两个最简单的是在启动命令里加-e TZAsia/Shanghai这是环境变量级别的时区设置。更彻底的做法是同时挂载宿主机的时区文件-v /etc/localtime:/etc/localtime:ro。两种方式配合使用日志和系统时间就能完全对齐。6.4 容器内构建时Docker权限报错这个问题上面提到过这里完整还原一下排查链路。场景Jenkins任务执行docker build时报错permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock排查步骤先在宿主机看Docker socket能否访问docker ps正常。进容器看docker exec jenkins docker ps同样报权限错误。查看socket权限ls -l /var/run/docker.sock确认属主是root:docker。确认容器内Jenkins用户所属组docker exec jenkins id发现jenkins用户不在docker组里。执行第4.3节的办法B同步宿主机docker组GID把jenkins用户加进去。重启容器问题解决。这个排查思路可以复用到其他权限类报错上先确认socket存在再看属主和用户组最后通过组ID同步解决问题。6.5 内置更新中心URL被覆盖有个隐蔽问题某些Jenkins版本在网页端修改Update Site后重启容器URL又变回了updates.jenkins.io。这是因为网页端修改的配置和hudson.model.UpdateCenter.xml文件没有完全同步。我的处理习惯是改完配置文件后用docker restart jenkins把容器重启一次然后在网页端Manage Plugins里再点一次Check now。这样插件列表下载源就是镜像站了后续安装插件速度才有保障。6.6 Jenkins升级的正确姿势容器部署的升级很简单核心思路先备份数据再换镜像标签重启新容器。# 备份数据 tar -czvf jenkins_backup_$(date %Y%m%d).tar.gz /data/jenkins_home # 停旧容器 docker rm -f jenkins # 拉新镜像或重新带上新标签 docker pull jenkins/jenkins:lts-jdk17 # 重新启动容器数据卷指向同一个目录 docker run -d --name jenkins \ -p 8080:8080 -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker \ -e TZAsia/Shanghai \ --restartalways \ jenkins/jenkins:lts-jdk17注意标签变了但数据卷不变所以升级后你的Job定义、插件配置都还在。唯一需要注意的是插件版本可能和旧版不兼容升级后第一件事是去Manage Plugins里把插件都更新到兼容版本。写在最后的一些体会整套流程跑下来我个人最大的感受是Docker装Jenkins本身不难难点全在细节参数和权限链路上。比如uid 1000的目录归属、docker.sock的GID同步、Update Center的换源方式这些单拎出来都是小问题但串在一起足够卡住一个下午。如果看了这篇文章还是觉得命令行太多、不好记那就把docker run那串参数存成一个shell脚本或直接写成Docker Compose文件下一次部署十分钟搞定。等Jenkins跑起来、第一条流水线自动部署成功后你会觉得前面这些折腾都值了。
返回列表