运维知识
悠悠
2026年5月3日

Jenkins 对接 Docker 做 CI/CD,我是怎么一步步跑通的,踩坑也一起说清楚

很多人一提到 CI/CD,就容易说得很玄,什么自动化交付、持续集成、持续部署,词都没毛病,但真到机器上落地,往往不是那么回事。页面点半天、插件装一堆、脚本复制来复制去,最后构建一跑就红,控制台一片报错,最常见的还是 Jenkins 找不到 Docker,或者镜像打出来了却推不上仓库。

我自己在项目里折腾 Jenkins 和 Docker 集成,这种场景不算少。小一点的项目,目标很简单,代码提交后自动拉代码、自动构建、自动打镜像、自动推仓库、自动部署。听起来很顺,但中间每一步都可能掉坑。今天就把这个过程掰开了讲,不讲虚的,就按实际怎么做、怎么排错、怎么让它稳定跑起来来写。

这篇内容不绕弯,直接从环境开始说。


一、Jenkins 和 Docker 到底是怎么配合的

这个事如果一句话说透,其实就是:

Jenkins 负责“调度流程”,Docker 负责“打包和运行环境”。

Jenkins 像个流程总控,代码一变更,它就去拉代码、执行构建命令、做测试、生成镜像、推送镜像、再触发部署。Docker 呢,负责把应用和运行环境一起封进镜像里,部署时不用担心“你机器能跑,我机器不能跑”。

很多团队刚开始时,是 Jenkins 直接在服务器上执行 jar 包替换、重启进程。这种方式不是不能用,但时间一长就乱。依赖版本、运行参数、服务启动方式,全靠人工记忆,机器一多就更难控。后面引入 Docker 以后,镜像就成了交付标准件,部署会顺很多。

说白了,Jenkins 是干活的,Docker 是装货的,镜像仓库就是中转站。


二、先把基础环境弄对,不然后面都是白搭

我这里以 Linux 服务器为例,比较常见,很多公司的 Jenkins 也是跑在 Linux 上。

1)安装 Docker

先确认 Docker 正常安装并能启动。

docker --version
systemctl status docker

如果没装,CentOS 系可以这样:

yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker
systemctl start docker

Ubuntu 就换成 apt,思路差不多。

装完别急着走,先跑个 hello-world 试一下:

docker run hello-world

能正常输出说明 Docker 基本没问题。


2)安装 Jenkins

Jenkins 安装方式很多,war 包、yum、docker 跑都可以。我平时更偏向用宿主机安装,图个直观,排查问题也方便。

比如在 CentOS 上:

yum install -y java-11-openjdk
yum install -y wget
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
yum install -y jenkins
systemctl enable jenkins
systemctl start jenkins

确认端口监听:

ss -lntp | grep 8080

首次登录要输初始化密码:

cat /var/lib/jenkins/secrets/initialAdminPassword

Jenkins 能打开只是第一步,很多人到这就以为差不多了,其实真正的坑从这里才开始。


三、Jenkins 想调用 Docker,权限必须打通

这个问题太常见了,几乎可以说是“新手必踩”。

Jenkins 服务一般是 jenkins 用户在跑,而 Docker 命令默认只有 root 或 docker 组用户能执行。所以你在终端里手工敲 docker ps 没问题,不代表 Jenkins 任务里也能用。

先看 Jenkins 是哪个用户:

ps -ef | grep jenkins

通常就是 jenkins

把它加入 docker 组:

usermod -aG docker jenkins

然后重启服务:

systemctl restart docker
systemctl restart jenkins

这一步很多人做了还是不生效,我也碰到过。原因也不复杂,服务进程没重新拿到组权限,或者压根 Jenkins 不是用这个用户启动的。最直接的办法,是在 Jenkins 里新建一个任务执行:

whoami
id
docker ps

看它到底是不是 jenkins,有没有 docker 组。

如果还报类似这种错:

Got permission denied while trying to connect to the Docker daemon socket

就继续查 Docker socket 权限:

ls -l /var/run/docker.sock

一般会看到属组是 docker,权限大概这样:

srw-rw---- 1 root docker ...

那就回到前面那步,把 Jenkins 用户和 docker 组关系确认清楚。

有时候我图省事,也会在测试环境里把 Jenkins 直接用 root 跑,但生产不太建议,后面安全审计会盯你,别问我怎么知道的。


四、Jenkins 里该装哪些插件,不用一股脑全装

插件这个东西,很容易装上瘾。看着像有用,全点了,最后 Jenkins 越来越臃肿,升级还容易出兼容问题。

做 Jenkins 和 Docker 集成,常用的几个就够了:

  • Pipeline
  • Git
  • Docker Pipeline
  • Credentials Binding
  • SSH Agent
  • Publish Over SSH(如果你要远程部署)
  • Blue Ocean(可装可不装,界面好看点)

进入:

Manage Jenkins -> Plugins

搜插件安装就行。

其实很多场景,不装 Docker 插件也能做。只要 Jenkins 能执行 shell,直接在 Pipeline 里写 docker builddocker push 也可以。我自己的习惯反而更偏这个,简单粗暴,好排查。插件太多,有时候反而多一层抽象,出了问题看着更烦。


五、准备一个最常见的 CI/CD 流程

咱们不要一上来就整特别复杂的那种,先用一个最典型的 Java 项目来举例,流程差不多是这样:

  1. 开发提交代码到 Git
  2. Jenkins 拉取代码
  3. Maven 编译打包
  4. Docker 构建镜像
  5. 推送到镜像仓库
  6. 登录目标服务器拉取新镜像
  7. 停旧容器,起新容器

你看,逻辑并不花哨。真正难的是把每一个环节串起来,还要考虑凭据、权限、失败回滚、日志可追踪。


六、代码仓库里要准备好 Dockerfile 和 Jenkinsfile

这个习惯特别重要。不要把构建逻辑全写在 Jenkins 页面里,后面迁移、维护、协作都会比较难。最好把流程脚本跟代码一起版本化管理。

1)Dockerfile 示例

假设是 Spring Boot 项目,打包后得到 app.jar

FROM openjdk:8-jre-alpine
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

这个文件看着简单,但已经够用了。

有些项目镜像特别大,动不动 700MB、1GB,多半是基础镜像没选好,或者把一些没必要的文件也复制进去了。生产环境里,镜像大小真的会影响推送速度和部署效率,尤其网络一般的时候,构建完看它传半天,人会有点烦。


2)Jenkinsfile 示例

下面给一个比较实用的 Jenkinsfile,包含拉代码、打包、构建镜像、推镜像、远程部署。

pipeline {
    agent any

    environment {
        APP_NAME = "my-app"
        IMAGE_REPO = "registry.example.com/project/my-app"
        IMAGE_TAG = "${BUILD_NUMBER}"
        CONTAINER_NAME = "my-app"
        DEPLOY_HOST = "192.168.1.100"
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://git.example.com/project/my-app.git'
            }
        }

        stage('Build Jar') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        stage('Build Docker Image') {
            steps {
                sh 'docker build -t ${IMAGE_REPO}:${IMAGE_TAG} .'
                sh 'docker tag ${IMAGE_REPO}:${IMAGE_TAG} ${IMAGE_REPO}:latest'
            }
        }

        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
                    sh 'echo "$DOCKER_PASS" | docker login registry.example.com -u "$DOCKER_USER" --password-stdin'
                    sh 'docker push ${IMAGE_REPO}:${IMAGE_TAG}'
                    sh 'docker push ${IMAGE_REPO}:latest'
                }
            }
        }

        stage('Deploy') {
            steps {
                withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh', keyFileVariable: 'SSH_KEY')]) {
                    sh '''
                    ssh -i $SSH_KEY -o StrictHostKeyChecking=no root@${DEPLOY_HOST} "
                    docker pull ${IMAGE_REPO}:${IMAGE_TAG} &&
                    docker stop ${CONTAINER_NAME} || true &&
                    docker rm ${CONTAINER_NAME} || true &&
                    docker run -d --name ${CONTAINER_NAME} -p 8080:8080 ${IMAGE_REPO}:${IMAGE_TAG}
                    "
                    '''
                }
            }
        }
    }

    post {
        success {
            echo '部署成功'
        }
        failure {
            echo '部署失败'
        }
    }
}

这个脚本不算特别复杂,但已经能跑通基本流程。

有个细节要说一下,latest 标签虽然常用,但生产里我更建议一定保留一个明确版本号,比如这里的 ${BUILD_NUMBER}。不然你后面回滚的时候会很难受。镜像仓库里一堆 latest,你根本分不清到底是哪一版。到最后大家嘴里都是“就上一版那个 latest”,这就有点玄学了。


七、镜像仓库这块别忽略,很多故障都出在这里

Jenkins 构建完镜像,通常不会直接拿本地镜像部署,而是先推送到镜像仓库。常见的有:

  • Docker Hub
  • Harbor
  • 阿里云容器镜像服务
  • 腾讯云、华为云的镜像仓库
  • 公司自建私有仓库

我个人更推荐用 Harbor,自建环境里比较常见,权限控制、项目分组、镜像管理都还不错。

在 Jenkins 里,要提前把镜像仓库账号密码配置好:

Manage Jenkins -> Credentials

新增用户名密码类型,比如:

  • ID:docker-registry
  • Username:仓库用户名
  • Password:仓库密码

这样在 Pipeline 里就可以引用,不用明文写死。

有一次我接手别人的 Jenkins,脚本里直接写了:

docker login -u admin -p 123456 registry.xxx.com

我看到的时候人都沉默了。脚本在仓库里,仓库很多人都能看,这种事不能说绝对危险,只能说迟早出事。


八、部署机器怎么接镜像,怎么起容器,也要考虑清楚

很多文章写到这里就结束了,好像 Jenkins 能 docker builddocker push 就叫完成集成了。实际上部署才是最后那一截,也是最容易出线上问题的地方。

我一般会在部署机上做好以下准备:

1)提前能拉镜像

先在部署机手工测一下:

docker login registry.example.com
docker pull registry.example.com/project/my-app:latest

如果这里都拉不下来,那 Jenkins 再怎么自动化也没意义。

有些公司内网环境,Jenkins 能访问仓库,业务服务器不能访问,或者 DNS 没配好,这种情况特别真实。你看流水线前面一片绿,到了部署阶段突然超时,然后大家就开始怀疑 Jenkins,其实问题根本不在 Jenkins。


2)旧容器替换逻辑别写得太莽

最基础的替换命令就是:

docker stop my-app || true
docker rm my-app || true
docker run -d --name my-app -p 8080:8080 registry.example.com/project/my-app:100

能用,但比较硬。

如果你项目稍微正式一点,最好加上挂载日志目录、环境变量、重启策略这些:

docker run -d \
  --name my-app \
  -p 8080:8080 \
  --restart=always \
  -e SPRING_PROFILES_ACTIVE=prod \
  -v /data/logs/my-app:/app/logs \
  registry.example.com/project/my-app:100

再往前一步,其实可以用 docker-compose 或者现在的 docker compose 管理。尤其一个服务依赖 redis、mysql、nginx 之类的时候,单独写一堆 docker run 不太优雅,改起来也麻烦。

比如一个简单的 docker-compose.yml

version: '3'
services:
  web:
    image: registry.example.com/project/my-app:latest
    container_name: my-app
    ports:
      - "8080:8080"
    restart: always
    environment:
      - SPRING_PROFILES_ACTIVE=prod

部署时:

docker compose pull
docker compose up -d

这个方式我挺常用,维护起来比直接一长串命令舒服。


九、Jenkins 里创建 Pipeline 项目,别把配置搞散了

Jenkins 页面里新建任务时,选 Pipeline

然后有两种方式:

  • 直接在 Jenkins 页面写 Pipeline Script
  • 从 SCM 拉 Jenkinsfile

我基本都选第二种。也就是:

Pipeline script from SCM

配置 Git 地址、分支、Jenkinsfile 路径。

这样做的好处很明显,流程变更有记录,谁改了什么一目了然。要是你把脚本全堆在 Jenkins 页面里,改几次以后连自己都记不清上次改了哪儿。更别说多环境、多项目共存的时候,那画面就有点不忍直视。


十、一次真实的报错排查过程,基本都绕不开这几类

光讲流程容易显得太顺了,现实里哪有这么顺。下面说几个我实际遇到过的坑,挺典型的。

1)Jenkins 里执行 docker 命令报权限错误

报错内容:

permission denied while trying to connect to the Docker daemon socket

处理思路:

  • 检查 Jenkins 运行用户
  • 检查是否加入 docker 组
  • 检查 /var/run/docker.sock 权限
  • 重启 Jenkins 和 Docker

这类错误最常见,也最容易让人误判。很多人以为是 Docker 没装好,其实本质是权限问题。


2)构建镜像时报找不到 jar 包

报错像这样:

COPY target/app.jar app.jar failed: no such file or directory

这个问题一般不是 Docker 有毛病,是你前面的 Maven 打包产物路径不对。

有些项目打出来的文件名不是 app.jar,而是:

target/demo-1.0.0.jar

那 Dockerfile 里就得改,或者在打包后做一个重命名:

cp target/*.jar target/app.jar

这类问题特别像生活里的那种“快递明明到了楼下,但地址写成隔壁单元”。东西是有的,就是没对上。


3)推送镜像失败,提示 no basic auth credentials

这基本就是登录仓库没成功,或者凭据没传进去。

先在 Jenkins 构建日志里看 docker login 是否成功,再确认凭据 ID 有没有写对,仓库地址有没有写错。有一次我就遇到少写了端口号,折腾了半小时,最后看日志才反应过来,嗯,确实挺像自己给自己挖坑。


4)部署成功了,但服务访问不了

这个也不少见。Jenkins 页面显示全绿,容器也启动了,就是浏览器打不开。

这时候别盯着 Jenkins 了,直接去部署机排查:

docker ps
docker logs -f my-app
ss -lntp | grep 8080
curl 127.0.0.1:8080

看看是:

  • 容器没起来
  • 程序启动失败
  • 端口映射不对
  • 防火墙没放行
  • 应用监听地址有问题
  • 依赖服务没连上

尤其 Java 项目,容器起了不代表服务就 ready 了。有时候启动类报错,容器退出又起来,配了 restart=always 看起来像“它活着”,其实在反复去世。


十一、流水线稳定以后,建议再补几件事

到这里,其实 Jenkins 和 Docker 已经能配合起来了。但如果真想在项目里长期跑,还是建议再补一些细节。

1)加构建清理

Jenkins 节点如果长期不清理,磁盘迟早爆。尤其 Docker 镜像和中间层,很占空间。

可以在流水线末尾加:

docker image prune -f
docker system prune -f

当然别太激进,生产节点要谨慎。有些共享环境乱删镜像,会影响别的任务。

Jenkins 工作空间也建议定期清理,不然 workspace 目录越堆越大,最后报警一来,又是一阵找。


2)加通知

构建失败没人知道,那自动化就自动了个寂寞。常见的通知方式:

  • 企业微信
  • 钉钉
  • 飞书
  • 邮件

至少做到构建失败、部署失败时能及时通知相关人。别等业务说“怎么接口挂了”,你再打开 Jenkins 看,才发现昨天晚上流水线就红了。


3)给不同环境分开

测试环境、预发环境、生产环境,不建议全写在一个硬编码脚本里。

可以用参数化构建,或者不同分支对应不同环境。比如:

  • develop -> 测试环境
  • release -> 预发环境
  • main -> 生产环境

再搭配不同的镜像 tag 和部署目标机器,整体会清楚很多。


4)考虑回滚

这个真的很重要。部署不怕失败,怕的是失败后没法迅速回去。

如果你镜像 tag 规范,比如保留 build number、git commit id,那回滚就很简单:

docker run -d registry.example.com/project/my-app:98

如果你只用 latest,那回滚全靠运气和记忆力。说实话,这两样东西在故障现场都不太稳定。


十二、如果你想再进一层,可以这么做

Jenkins 加 Docker 只是第一步,后面如果项目规模上来,一般还会继续演进:

  • Jenkins + Docker + Harbor
  • Jenkins + Docker Compose
  • Jenkins + Kubernetes
  • GitLab CI / GitHub Actions 替代部分 Jenkins 能力

但这不是说 Jenkins 过时了。很多公司里 Jenkins 依然很稳,插件多,生态成熟,尤其历史项目多的时候,改造成本也没那么低。你说它老派也行,但它确实能干活。

而且我一直有个感觉,运维工具这东西,不是新就一定好。能不能稳稳跑住、出了问题能不能查、团队会不会用,这些更重要。花里胡哨整一大套,结果每次发布都要祈祷,那就有点本末倒置了。


十三、最后把完整思路再捋一下

如果你现在要做 Jenkins 和 Docker 集成,实际落地时就盯住这几个关键点:

  • Jenkins 能正常跑
  • Docker 能正常跑
  • Jenkins 用户能执行 Docker 命令
  • Git 凭据、镜像仓库凭据、部署 SSH 凭据都提前配好
  • 代码仓库里准备好 Dockerfile 和 Jenkinsfile
  • Pipeline 完成拉代码、构建、打镜像、推镜像、部署
  • 部署机能拉镜像,能起容器,端口和依赖没问题
  • 最后再补通知、清理、回滚这些增强项

你会发现,这套东西并不神秘。难点不是“概念理解”,难点是把环境、权限、脚本、仓库、部署这些零碎东西拼起来,而且每个点都别掉链子。运维很多时候就是这样,听起来像搭积木,做起来像拆炸弹,尤其是晚上发版的时候,那种感觉更明显。


总结

Jenkins 和 Docker 集成,说到底就是让构建和部署流程标准化、自动化。Jenkins 负责编排流程,Docker 负责封装运行环境,两者配合起来,能把“手工发布”这件事尽量收敛成固定动作。只要前期把权限、插件、镜像仓库、部署脚本这些细节打磨好,后面整体会省心不少。

这篇我尽量按实际落地的思路写,没有故意讲太虚的东西。真正在项目里用的时候,你大概率还是会遇到一些奇奇怪怪的小问题,不过别慌,很多问题最后看下来,无非就是权限、路径、网络、凭据这几类,经验多了,定位会越来越快。

如果你也在折腾 Jenkins、Docker、CI/CD 这类内容,或者最近刚好在做发布流程改造,欢迎把这篇先收藏着,哪天报错了回来翻一眼,可能还真能省点时间。

觉得这篇有点用的话,也欢迎转发给正在折腾自动化发布的朋友,一起少踩几个坑。

关注我,后面我会继续把生产里常见的 Jenkins、Docker、K8s、Nginx、Linux 排障和落地实践慢慢写出来,尽量都写成能直接拿去用的东西。

公众号:运维躬行录
个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码