做了这么多年持续集成,我最大的感受是:搞不定环境配置的人,通常不是栽在 Jenkins 本身,而是栽在仓库链路上。代码仓库、依赖仓库、镜像仓库这三层没理顺,流水线就像断了粮草的后勤线,跑起来全是毛病。这篇实战教程就顺着“三大仓库 + 自动构建 + 公网远程部署”这条主线,把从 Gitee 上代码、Maven 拉依赖、Docker 出镜像,到 Jenkins 自动构建,最后部署到带公网访问能力的服务器这一整条链路拆开讲透。
这文章我是按“拿来就能用”的标准写的。你跟着走一遍,会得到一个完整的自动化部署闭环:开发提交代码 → Gitee Webhook 通知 Jenkins → Jenkins 拉代码编译打包 → 构建 Docker 镜像推到仓库 → 远程服务器拉镜像重启容器 → 用户通过域名正常访问。适合刚接手 CI/CD 的运维、准备把个人项目做成标准流程的后端开发,以及小团队里需要独立搞定研发流程建设的人。新手不用怕,基础概念我会顺带讲;有经验的老手也可以重点关注多镜像源配置和公网部署的几个坑。
1. 整体设计与思路拆解
1.1 三大仓库各自承担什么角色
仓库这个词在不同语境下意思完全不一样,先分清楚再动手,否则后面配置会乱套。
代码仓库存的是源码,Gitee、GitHub、自建的 Gogs 都算。它解决的是“代码放哪、怎么协作、怎么留痕”的问题。在这套方案里,代码仓库是整条流水线的起点,所有构建动作都因它而起。
依赖仓库解决的是“构建时依赖从哪拉”的问题。Java 项目用 Maven 仓库拉 jar 包,Node 项目用 npm 仓库拉依赖。国内直连 Maven 中央仓库速度异常感人,所以我们要配置阿里云镜像。这一步不做,构建任务能卡在下载依赖上十几分钟不动。
镜像仓库解决的是“打包好的软件怎么分发和部署”的问题。Docker 镜像构建出来后需要一个地方存放,生产服务器再从那里拉取运行。私有镜像仓库(Harbor、阿里云 ACR、Registry 2)解决了直接分发 Docker 镜像包的低效问题,也让版本回滚变得简单。
三层仓库的逻辑关系就像流水线:源码进仓库,依赖进构建,镜像进分发,最终跑到线上环境。任一层断了,整个链路就卡住。
1.2 自动构建 + 远程部署的完整链路
自动构建的核心是事件驱动:代码一旦变更,构建立刻被触发。触发方式有很多种,最常见的是 Webhook——Gitee 把“有代码提交了”这个事件推送给 Jenkins,Jenkins 收到后自动执行预设好的流水线。
这条流水线的完整画像是这样的:
- Git 拉取最新代码到工作区
- Maven 根据 pom.xml 从镜像仓库拉依赖并编译打包
- 执行单元测试,质量不合格直接中断
- 构建 Docker 镜像,给镜像打上版本号标签
- 推送镜像到私有仓库
- 通过 SSH 连接到目标服务器,远程执行拉取镜像、停旧容器、起新容器的命令
- 浏览器访问域名验证服务是否正常
每一步都可能出问题,这就意味着每个阶段的状态都要看得见。Jenkins 流水线的 Stage 视图正好能把每步的耗时、日志、成功与否都展示出来,排查问题的时候不需要靠猜。
1.3 方案选型:这套组合的优势在哪
总有人问“GitHub Actions 不是更简单吗?”确实简单,但很多团队的代码放在内网 Git 或 Gitee 上,不可能把源码推到 GitHub 去跑 Actions;另外 Jenkins 是纯私有化部署,管线脚本都在自己手里,可以自由地和内网资源(内网 Docker Registry、内网 Maven 私服)打通,不依赖任何外部 SaaS 服务。
Jenkins 的 Pipeline 即代码(Jenkinsfile)设计也是它的核心优势。构建流程不再是界面上一堆点来点去的按钮配置,而是跟着项目走的纯文本脚本,代码更新了流水线也跟着变,配合版本控制还能追溯每一次构建逻辑的改动。这一点在后期维护中带来的便利,比初期多写一点 Groovy 语法值得多得多。
2. 环境准备与 Jenkins 基础配置
2.1 安装 Jenkins 与汉化设置
我建议用 Docker 方式部署 Jenkins,理由很简单:环境隔离干净,备份迁移方便,几个命令就能起一个实例。目前最新稳定版本已经更新到 2.541.3 系列,对 JDK17 及 Pipeline 的支持非常成熟,老项目的兼容性也更稳。
docker run -d \ --name jenkins \ --restart=always \ -p 8080:8080 \ -p 50000:50000 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v jenkins_home:/var/jenkins_home \ jenkins/jenkins:lts这串命令里有几个细节值得说道。挂载/var/run/docker.sock是为了让 Jenkins 容器直接调用宿主机的 Docker 守护进程,这样后边在构建时能直接执行 docker login、docker build、docker push。如果你把这个挂载去掉,Jenkins 内部没有 Docker CLi,后面所有镜像相关操作都会报“docker: command not found”或者权限错误。这一步属于后方避坑的关键,建议一开始就做。
提示:如果 Jenkins 容器要作为独立部署环境使用,可以不挂 docker.sock,改用 Docker-in-Docker(DinD)方案,即在 Jenkins 容器内再跑一个 Docker 守护进程。但那个方案资源开销更大,网络配置也更麻烦,单机场景下共享宿主机 Docker 是更务实的选择。
启动后浏览器访问http://服务器IP:8080,第一次进入会让你输初始密码。密码在容器日志里能看到:
docker logs jenkins | grep -A 2 "Administrator password"输入密码、创建管理员账号、选安装建议插件,走完向导就进主界面了。界面是英文的话,在“系统管理 → Plugin Manager → Available plugins”里搜Localization: Chinese (Simplified),安装后重启,页面就换成中文。这个汉化插件只翻译 Jenkins 核心界面,第三方插件的部分菜单还是英文,属于正常情况,不影响使用。
2.2 插件加速与标配插件清单
Jenkins 装完默认连的是官方插件中心,在国内访问非常不稳定,插件下载经常超时。解决办法是换到国内镜像更新中心,这类镜像服务属于正规公开的资源共享平台,配置方式也很简单。
路径:“系统管理 → 插件管理 → 高级 → Update Site”,把 URL 替换为清华或华为云的 Jenkins 更新中心地址,保存后重启生效。配置完成后,去插件管理里安装下面这几个必要插件,这些是后面流水线要用的:
| 插件名 | 作用 |
|---|---|
| Git Plugin | 拉取 Git 仓库代码 |
| Pipeline | 创建流水线任务、支持 Jenkinsfile |
| Gitee Plugin | 接收 Gitee Webhook、自动触发构建 |
| Docker Pipeline | 在流水线中执行 docker build 等命令 |
| Publish Over SSH | 通过 SSH 在远程服务器上执行部署命令 |
| Blue Ocean | 更直观的流水线展示界面 |
插件版本注意和 Jenkins 主版本兼容,一般安装“推荐”或“latest”版本即可,不要盲目追新。某个插件安装失败导致整个更新中断是常见问题,不影响的插件可以单独装,不要一次全选。
2.3 必须掌握的 Jenkins 环境变量
之前有人问我“Jenkins 怎么拿到当前的构建号?”其实就是环境变量。Jenkins 在执行构建时自动注入了很多预定义的环境变量,在流水线里直接就能读。最常用的几个:
| 变量名 | 含义 | 使用场景 |
|---|---|---|
| BUILD_NUMBER | 当前构建的编号 | 拼镜像版本号,比如v1.2.3.${BUILD_NUMBER} |
| JOB_NAME | 任务名称 | 区分不同项目的构建产物 |
| WORKSPACE | 当前构建的工作目录路径 | 指向你代码 checkout 后的路径 |
| GIT_COMMIT | 当前构建对应的 Git 提交哈希 | 排查代码版本问题、定位缺陷 |
| GIT_BRANCH | 当前构建的分支名 | 区分测试分支与主干分支 |
| BUILD_URL | 本次构建的完整地址 | 通知里直接附链接 |
在流水线里用起来也很简单:
stage('Print Env') { steps { echo "当前构建号: ${env.BUILD_NUMBER}" echo "当前分支: ${env.GIT_BRANCH}" echo "工作目录: ${env.WORKSPACE}" } }这些变量不需要自己去定义,Jenkins 运行时自动填充。在 Jenkinsfile 里写版本号标签时,BUILD_NUMBER用得最频繁,用它拼出来的镜像版本不会重复覆盖,回滚也方便。
3. 三大仓库的配置全解析
3.1 代码仓库:Gitee 从零建仓到上传
代码仓库的选择各人偏好不同,原理一致。我以国内最常用的 Gitee 为例,因为它在国内访问速度比 GitHub 稳得多,Webhook 触发 Jenkins 也流畅。
先在 Gitee 上“新建仓库”,填仓库名、选私有或开源、初始化时勾选README.md和.gitignore(Java 项目要选 Java 模板,Node 项目选 Node 模板),其余保持默认。建好之后本地电脑需要和远程仓库建立信任关系,最普遍的方式是 SSH 密钥。
生成密钥:
ssh-keygen -t ed25519 -C "你的邮箱" -f ~/.ssh/id_ed25519把~/.ssh/id_ed25519.pub的内容复制到 Gitee 的“设置 → SSH 公钥”里。然后本地初始化并推代码:
git init git add . git commit -m "init project" git remote add origin git@gitee.com:你的用户名/仓库名.git git push -u origin master这里有个新手高发问题:仓库里已经勾选了 README 初始化,本地再 init 一次就会出现“远端已经有内容,本地也有内容”的冲突。解决办法是git pull origin master --allow-unrelated-histories,把两边内容先合并再推送。这条命令本质是允许两个不相干的历史合并,除非你知道自己在干什么,否则不要反复强推覆盖远端。
如果你已经有了本地 Git 历史,想整体迁移到 Gitee,思路也一样:把原仓库的 remote 换成新的 Gitee 地址,直接推上去。Gogs 这类自建 Git 服务也支持迁移外部仓库,后台操作界面提供“迁移仓库”功能,填原仓库地址和账号信息即可。
还有两个高频操作顺带说清楚。删除 Git 仓库:本地项目里删掉.git目录就是解除版本控制,远端仓库在 Gitee 的后台“设置 → 删除仓库”操作。另外 Gitee 仓库本身不支持混合嵌套子项目,子项目要单独建仓库,通过 Git 的submodule机制关联,构建时需要额外执行子模块更新,普通项目不建议上来就用 submodule,维护成本容易让人头大。
3.2 Maven 仓库:阿里云镜像与多镜像源配置
凡是 Java 项目,Maven 依赖源直接决定构建速度。中央仓库直连经常卡到超时,换成阿里云镜像后下载速度基本是“秒开”。配置在 Maven 的settings.xml里,路径一般在${MAVEN_HOME}/conf/settings.xml(如果你是用 IDEA 自带 Maven,则在 IDEA 安装目录的 plugins 下找)。
单镜像的经典配置:
<mirror> <id>aliyunmaven</id> <name>aliyun maven mirror</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>*</mirrorOf> </mirror>mirrorOf的值是*,表示所有中央仓库请求都走镜像。这里有个容易踩的坑:如果项目的 pom.xml 指定了repositories里的第三方仓库(比如公司私有 Nexus 或某个特殊构件仓库),*也会把这些请求拦下来转到阿里云,最终导致“找不到依赖”。解决办法是改用更精确的匹配:
<mirror> <id>aliyunmaven</id> <name>aliyun maven mirror</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror>mirrorOf只匹配central,其他第三方仓库还是走原地址。如果你有多个镜像源,比如一个为阿里云、一个为华为云,想要顺序逐个尝试,Maven 的 mirror 设计是“只取第一个匹配到的镜像”,不是轮询。多源真正的做法是配置多个profile,激活逻辑上保持互斥:
<profiles> <profile> <id>aliyun</id> <repositories> <repository> <id>aliyun-public</id> <url>https://maven.aliyun.com/repository/public</url> </repository> </repositories> </profile> <profile> <id>huaweicloud</id> <repositories> <repository> <id>huawei</id> <url>https://repo.huaweicloud.com/repository/maven/</url> </repository> </repositories> </profile> </profiles>然后通过-P aliyun或-P huaweicloud选择激活哪组,这就是 2026 年很多项目在用的多源仓库接口配置思路。
注意:Maven 的仓库网页版入口常被提到,阿里云的公共仓库管理后台提供了构件搜索、版本查询功能,你可以在网页端确认某个依赖是否存在、版本号是否正确,排查依赖下载失败时非常有用。但最终构建生效的还是
settings.xml中的镜像配置。
3.3 Docker 仓库:国内镜像源与私有仓库推送
Docker 镜像源的问题分两段:构建时拉基础镜像(比如openjdk:17-jdk-slim)和部署时拉构建产物镜像。公共镜像拉取如果慢,在 Docker 客户端配置 registry mirror 即可,一般是写/etc/docker/daemon.json:
{ "registry-mirrors": [ "https://docker.m.daocloud.io" ] }改完重启 Docker:systemctl restart docker,再拉镜像速度就会有质的提升。但有一个逻辑必须拎清楚:registry-mirrors只影响拉公共镜像(比如从 Docker Hub 拉),不影响自行构建后推送到私有仓库的镜像。
私有镜像仓库,我推荐两条路。已有云资源就直接用云厂商的容器镜像服务(如阿里云 ACR),自动附带公网访问地址、镜像扫描、版本管理、多集群授权,小团队最省心。想完全私有化就自建 Harbor 或轻量 Registry 2:
docker run -d -p 5000:5000 \ --restart=always \ --name registry \ -v /data/registry:/var/lib/registry \ registry:2有了仓库之后,需要在 Jenkins 里先做一次docker login。凭据一律加到 Jenkins 的“凭据管理”里,不要在脚本里明文写密码,然后通过 Docker Pipeline 插件调用:
stage('Login & Push') { steps { withCredentials([usernamePassword(credentialsId: 'docker-hub-key', passwordVariable: 'DOCKER_PASS', usernameVariable: 'DOCKER_USER')]) { sh 'docker login --username=$DOCKER_USER --password=$DOCKER_PASS registry.example.com:5000' sh 'docker tag myapp:${BUILD_NUMBER} registry.example.com:5000/myapp:${BUILD_NUMBER}' sh 'docker push registry.example.com:5000/myapp:${BUILD_NUMBER}' } } }私有仓库端口如果是非 443,远程服务器拉取时需要先在目标机的 daemon.json 里加上"insecure-registries": ["registry.example.com:5000"],否则 Docker 会因为证书问题拒绝拉取。这个配置容易遗漏,部署时直接报x509: certificate signed by unknown authority,原因就是 HTTPS 证书校验失败。
4. 自动构建流水线落地
4.1 第一个 Pipeline 任务怎么建
回到 Jenkins 主界面,点击“新建任务”,输入任务名,选择“流水线(Pipeline)”。任务名建议直接用项目名,比如myapp-deploy,简洁明确。建好后先不急着写脚本,先把“参数化构建过程”配置上,勾选“字符串参数”,添加一个BRANCH参数,默认值设为master,意思是构建时可以选择从哪个分支拉代码。
“构建触发器”和“流水线定义”这两块在界面配置完成后,核心逻辑都写在 Jenkinsfile 里。注意“Hello World”式的简单界面配置只适合测试,真正的项目建议全部用 Jenkinsfile 管理,这样流水线本身就是项目的一部分,换机器、换 Jenkins 实例都不怕。
4.2 用 Jenkinsfile 描述整个构建过程
Jenkinsfile 是 Groovy 语法,但不用把它想得很复杂,核心就是几个 stage 按顺序执行。下面是一个标准的多阶段流水线示例,覆盖了拉取代码、Maven 构建、Docker 打包、推送仓库、远程部署这五步:
pipeline { agent any environment { // 镜像仓库地址 REGISTRY = 'registry.example.com:5000' // 项目名,推送到仓库后作为镜像名 IMAGE_NAME = 'myapp' // 目标服务器 DEPLOY_HOST = '跳板机或目标机IP' } stages { stage('拉取代码') { steps { checkout scm } } stage('Maven 构建') { steps { sh 'mvn clean package -DskipTests -P aliyun' } } stage('构建 Docker 镜像') { steps { script { def tag = "${env.BUILD_NUMBER}" sh "docker build -t ${REGISTRY}/${IMAGE_NAME}:${tag} ." sh "docker tag ${REGISTRY}/${IMAGE_NAME}:${tag} ${REGISTRY}/${IMAGE_NAME}:latest" } } } stage('推送镜像') { steps { script { def tag = "${env.BUILD_NUMBER}" withCredentials([usernamePassword(credentialsId: 'registry-credentials', passwordVariable: 'REG_PASS', usernameVariable: 'REG_USER')]) { sh "docker login --username=${REG_USER} --password=${REG_PASS} ${REGISTRY}" sh "docker push ${REGISTRY}/${IMAGE_NAME}:${tag}" sh "docker push ${REGISTRY}/${IMAGE_NAME}:latest" } } } } stage('远程部署') { steps { sshPublisher( publishers: [ sshPublisherDesc( configName: 'prod-server', transfers: [ sshTransfer( execCommand: "docker pull ${REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER} && docker stop myapp || true && docker rm myapp || true && docker run -d --name myapp -p 8081:8080 ${REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}" ) ] ) ] ) } } } post { success { echo '构建成功' } failure { echo '构建失败' } } }这段脚本有几个要点需要展开。checkout scm是 Jenkins 自带的代码检出步骤,前提是任务配置里已经填了 Git 仓库地址和凭据;environment里定义的是全局变量,stage 内部可以直接引用;-P aliyun是激活上一章那个 Maven profile 的写法,具体名称按你的配置文件来。
实际生产环境里,我建议把部署时那串长命令拆开写,并入一个deploy.sh,放在项目的deploy/目录里提交到代码仓库,Jenkinsfile 里只做远程执行。原因很简单:部署脚本应该跟着项目版本走,而不是绑在一个 Jenkins 任务里。改部署逻辑只需要改代码仓库,不需要动 Jenkins。
4.3 Webhook 触发:提交代码自动构建
手动点“立即构建”只是验证流程,真正自动化靠的是 Webhook。目标:开发把代码 push 到 Gitee 仓库,Jenkins 自动触发上面那条流水线。
在 Gitee 仓库后台找到“管理 → WebHooks”,URL 填http://Jenkins服务器IP:8080/gitee-project/myapp-deploy(取决于你装的 Gitee 插件),勾选“Push”事件,保存。
注意:Webhook 能调通的前提是 Jenkins 端能访问到 Gitee 的请求,反过来 Gitee 也要能访问到 Jenkins。本地联调时经常遇到内网环境访问不到的问题,实际生产中 Jenkins 和 Gitee 都部署在有公网访问能力的服务器上,这个问题基本不存在。
Gitee 插件推荐开启自动管理 hook 模式,不过要确保 Jenkins 任务里“构建触发器”那栏勾上了 “Gitee webhook 触发构建”。配置完成后,随便提交一次代码,就能在 Jenkins 构建历史里看到一条由 push 事件触发的记录。此时再看 Blue Ocean 里的流水线视图,每个阶段哪一步耗时多、卡在哪一步,一目了然。
5. 公网远程部署实战
5.1 部署目标机器与密钥准备
远程部署的目标机器,可以是一台内网服务器,也可以是一台带公网 IP 的云主机。公网远程部署不是说 Jenkins 机器必须有公网 IP,而是最终运行的业务需要对用户提供公网访问入口。通常做法是:Jenkins 部署在内网,通过 SSH 连到带公网 IP 的应用服务器上执行部署命令。
这台应用服务器需要做的准备有四项:装 Docker、开放业务端口、配置 SSH 登录免密、确认能访问镜像仓库。免密登录用 SSH 公钥实现:
# 在 Jenkins 机器上 ssh-keygen -t ed25519 -C "jenkins-deploy" -f /var/jenkins_home/.ssh/id_deploy # 将公钥追加写入目标服务器 ssh-copy-id -i /var/jenkins_home/.ssh/id_deploy.pub deploy@你的目标服务器IP如果想把密钥直接交给 Jenkins 使用,可以在“系统管理 → 凭据管理 → 添加凭据”里选择 “SSH Username with private key”,把私钥贴进去,指定用户名。这一步比简单地配置全局免密更安全,一旦 Jenkins 机器被攻破,私钥不会裸露在全局目录。
5.2 远程执行部署命令的实现
刚才 Jenkinsfile 里用的sshPublisher是 Publish over SSH 插件的写法。第一步在“系统管理 → 系统配置”里找到 “Publish over SSH”,添加一个 SSH Server:
| 配置项 | 建议值 |
|---|---|
| Name | prod-server |
| Hostname | 目标服务器的公网 IP 或内网 IP |
| Username | docker 用户或 deploy 用户 |
| Remote Directory | /opt/deploy |
| 凭据 | 选择刚才添加的 SSH 私钥凭据 |
保存之后,流水线里就能通过configName: 'prod-server'引用它。远程执行命令时注意,sshPublisher默认的执行目录是你在 Remote Directory 指定的目录,脚本里的相对路径都以此为准。
远程命令本身要做得“幂等”,即无论执行多少次,结果都一致。示例里的docker stop myapp || true就是为了处理容器不存在时报错的问题。部署命令建议写成脚本,首行加set -e,这样哪一步失败脚本会立即返回非零状态,Jenkins 界面会直接显示该阶段失败,不会出现“命令执行了但实际没起来”的假象。
5.3 域名、安全组与 Nginx 反向代理
服务器上容器跑起来了,端口也映射出来了(比如 8081),但用户访问不能总靠 IP 加端口。正规做法是:域名解析到服务器公网 IP,Nginx 监听 443/80,把请求反向代理到本机容器的映射端口。
安全组这一步千万别忽略。云厂商控制台的“安全组规则”里必须放行 80、443(以及 SSH 22),否则 Nginx 配置得再对,外部流量也进不来。我见过不少人在服务器上纠结半天端口不通,最后发现是安全组没放开,白白浪费一晚上。
Nginx 反向代理配置示例:
server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置完执行nginx -t验证语法,然后systemctl reload nginx。HTTPS 证书推荐用云厂商的免费数字证书或 Let’s Encrypt 自动签发,申请下来后在 Nginx 里加一个 443 的 server 块,把证书路径指过去即可。证书配置完成之后,像 HTTP 到 HTTPS 的重定向也可以顺手加上,避免用户通过明文协议访问。
5.4 回滚与验证:线上部署的最后一步
自动部署不是部署完就结束了,验证和回滚才决定整个流程是不是真的可靠。部署完成后要立刻验证服务健康状态,最简单的办法是请求健康检查接口:
curl -s http://127.0.0.1:8081/actuator/health | jq .status返回UP说明服务正常。也可以再通过域名做一次端到端验证。如果服务起不来,回滚方案要能立刻执行。正因为我们在推送时同时打了:latest和:构建号两个标签,回滚时只需要把上上次构建的镜像 tag 拿过来重新 run 即可:
docker run -d --name myapp_rollback -p 8081:8080 registry.example.com:5000/myapp:上上次构建号回滚完成之后,再对照旧版本的验证流程做一次健康检查,确认线上稳定再决定是否继续排查新版本的问题。
6. 常见问题与排查技巧实录
6.1 构建期典型问题速查表
跑流水线最烦的是看到红色失败,这里把高频问题和对应的处理手段整理成一张表,排查时按图索骥:
| 错误现象 | 可能原因 | 处理办法 |
|---|---|---|
Host key verification failed | Jenkins 首次连接目标服务器,没有确认主机指纹 | 在目标机 known_hosts 中预先添加公钥,或使用 ssh-keyscan 写入 |
Permission denied (publickey) | 私钥没配对或权限过大 | 检查 Jenkins 凭据里的私钥、目标机 authorized_keys 里的公钥、私钥文件权限设为 600 |
docker: command not found | Jenkins 容器内没有安装 Docker CLI | 挂载宿主机/usr/bin/docker或重新选择带 docker 的镜像 |
no space left on device | 镜像和构建缓存把磁盘占满了 | 定期执行docker system prune -f或者对构建产物做自动清理 |
Could not resolve host | 内网环境的 DNS 或者代理配置问题 | 检查 Jenkins 及目标机的 DNS 配置,确认能解析镜像仓库地址 |
6.2 仓库相关的高频问题
依赖拉不下来,先分清是哪一层仓库出问题。Maven 中央仓库和镜像仓库的问题,检查settings.xml里的 mirror 配置和网络连通性;Docker 公共镜像拉不动,检查 daemon.json 的 registry-mirrors 和 DNS;私有镜像仓库拉取报证书错误,检查目标机 daemon.json 里有没有加insecure-registries。
镜像仓库的“最新镜像”问题也经常让人绕弯。推送到私有仓库后,在服务器上docker pull拉不到latest,原因可能是拉取动作发生在重新构建之前,没有先执行docker pull刷新本地缓存。我们的 Jenkinsfile 里每个远程部署命令第一步就是先 pull,其实就为了同步这个状态。另外,私有仓库镜像多了以后记得启用定期清理策略,否则仓库占用的磁盘空间会一路涨到爆仓。
6.3 容器权限与远程执行问题
Jenkins 容器内使用 Docker 命令挂载 docker.sock 的方案,权限一般没问题,但要注意宿主机的 Docker 如果升级了 API 版本,Jenkins 容器内的 docker CLI 可能因为版本太旧而报 “client and server don't have same version”。解决办法是尽量在 Jenkins 容器内使用与宿主机一致或更新的 Docker CLI,或者干脆 Jenkins 镜像升级到带新版 Docker 的 tag。
远程执行命令久了还会遇到另一个隐性坑:Publish over SSH 的超时时间设置太短,部署脚本里拉大镜像需要几分钟,如果超时设置默认的 30 秒,流水线会提示对方关闭。路径在“系统配置 → Publish over SSH → 高级 → Timeout (ms)”里改大,比如 120000 毫秒。
6.4 上线前的自检清单
积累了一些实战经验之后,每次往公网环境推版本前我都会跑一遍下面的检核,相当于给自己一个敬畏线上环境的仪式:
- 镜像版本是否唯一?是否同时更新了
latest标签? - 目标服务器是否已登录镜像仓库?凭据有没有过期?
- 远程部署命令是否幂等?容器不存在时会怎样?
- 健康检查接口是否存在?返回是否符合预期?
- 回滚版本号是否记录下来?资源和镜像是否可用?
- Nginx 配置是否需要改动?是否已经
nginx -t? - HTTPS 证书是否过期?有没有设置到期提醒?
- 安全组端口是否放行?数据库和其他远端依赖是否可达?
这些检查项每一条我都踩过对应的坑。特别是 HTTPS 证书过期,我遇到过一整个凌晨线上访问打不开,排查半天才发现是证书到期了。现在我会在证书到期前一个月在云控制台设置到期提醒,从根上避免这个问题。
另外强烈建议把部署通知接入到团队用的通讯软件上,构建成功、失败、回滚这类关键事件,让相关同事第一时间知道。通知内容建议包含构建号、分支、提交人和构建地址,这样有问题时大家不用靠问,打开消息记录就能定位是哪次构建引入的异常。
在我反复折腾这套流程的过程中,一个很深的体会是:自动化构建和公网部署的难点从来不在某个单一环节,而在整条链路的联动。每次有人问“怎么快速搭一套”,我都会建议照着这篇文章的链路先跑通一个 Hello World 项目,再把业务代码逐步加进来。链路通了,后面加节点只是往流水线里插一段的事;链路没通就堆功能,最后排查问题的时间能翻倍。希望这套从三大仓库到公网部署的完整过程,能帮你在自动化部署这条路上少走几段弯路。