Dockerfile+JenkinsCI
一、从 cron + commit 出发:那段用 crontab 拯救世界的日子
- 在尝试 CI/CD 自动化构建之前,我最初是用一个非常“朴素”的方式维护 Kibana 镜像的:
0 1 */3 * * /path/to/kibana_commit_rotate.sh >> /path/to/kibana_cron.log 2>&1
这条 crontab 任务,每三天在凌晨 1 点执行一次自动备份流程。它配合了一份自写的 kibana_commit_rotate.sh 脚本,完成了容器快照、镜像打 tag、老容器替换重启的完整逻辑:
#!/bin/bash
CONTAINER="kibana"
IMAGE="myname/kibana"
PORT=5601
DATE=$(date +%Y%m%d)
TAG="v1.0.4-${DATE}"
# 1. 保存当前容器为新镜像
docker commit $CONTAINER ${IMAGE}:${TAG}
# 2. 停止并删除旧容器
docker stop $CONTAINER
docker rm $CONTAINER
# 3. 启动新容器(确保 restart=always)
docker run -d --restart=always --name $CONTAINER -p ${PORT}:${PORT} ${IMAGE}:${TAG}
这种方式极简、直观、可跑,但同时也有明显的局限:
-
🌀 每次都是完整快照,难以复现;
-
🔍 镜像命名不统一,版本语义不清;
-
💥 一旦脚本改动漏掉某步就可能全线中断;
-
⛔ 与 Jenkins、CI 工具链毫无连接点,无法集成。
不过这套体系在早期仍旧维持了长达数月的“可用状态”。它是我镜像管理1.0阶段的起点,是一次用 bash 脚本和 crontab 拼凑出的“类版本管理”体系。
这是 v1.x 镜像线的起点。
二、第一次尝试 CI 化:Jenkins pipeline 的设想
- 想法是将 dashboard 导出 + 镜像构建两个步骤集成进 Jenkins 流水线。
- Pipeline 中使用 docker build,希望打 tag 后 push 到公司仓库。
docker build -t myrepo/kibana:v2.0.0-$(date)-$(git rev-parse --short HEAD) .
决定放弃 crontab 的某一天,我试图通过 Jenkins 让镜像构建流程自动化。
Pipeline 的设计很清晰:
第一步:执行一个 export_dashboards.sh 脚本,把 dashboard 配置导出;
第二步:用 Dockerfile 构建 Kibana 镜像;
最终:把打好的 tag 输出到日志,供后续部署使用。
一切听起来都很合理,但现实并没有按照剧本发展。
三、构建失败 29 次:Jenkins CI 的踩坑与挣扎
💥 一条条错误,一次次重来
从第一次提交开始,Jenkins 日志就不断抛出:
docker: command not found
我试图把 docker 命令路径写死为 /usr/bin/docker,也试过修改 PATH、加 sudo,但都无济于事 —— Jenkins 的运行环境和我的本地完全不同。没有 docker,这一切都跑不下去。
与此同时,export_dashboards.sh 脚本中的相对路径也踩了雷:我在脚本里用了类似 ./dashboards/ 的路径,结果在 Jenkins 的工作目录中根本找不到目标文件。最后只能无奈注释掉这一段。
更令人崩溃的是,脚本中还有如下逻辑:
IMAGE_TAG="xxx:$(git rev-parse --short HEAD)"
但 Jenkins 的构建目录并不是 Git 仓库,调用 git rev-parse 会直接报:
fatal: not a git repository
最终,我只能写死 tag,删掉 git 相关命令。
我一度误以为镜像构建成功了 —— 因为我写了 echo "✅ Image built",还加了 docker images | grep kibana-custom。但其实这只是脚本层的输出,镜像根本没有生成,docker 根本没执行起来。
🧱 这29次失败,换来了对“环境差异”的深刻认知
这一轮 CI 的失败,让我意识到:
-
Jenkins 的 shell 环境 ≠ 我本地的 shell;
-
Jenkins 的 workspace ≠ 我的代码目录结构;
-
Jenkins 没有 docker,就是没法构建;
-
构建失败日志 ≠ 问题提示明确,大多数时候只是一堆提示“没有”;
-
脚本中那些 “echo ✅” 是没有意义的自我安慰。
这就是我经历的第1~29次构建:一次次碰壁,一次次尝试修复,但从未真正跑通。
四、构建失败的反复探索:路径、权限与环境变量的陷阱
第1次构建失败之后,直到第29次,每一次失败背后,都是一次微调、试探与不甘心的验证。
这些错误有的来自环境变量不一致、有的源于路径解析不匹配,更多的,是在 Jenkins 和本地之间来回试错时,被“黑盒行为”逼疯的过程。
🧨 报错碎片与修补尝试
以下是我逐步遇到并尝试解决的关键问题整理(按问题维度):
| 问题类型 | 报错信息 / 现象 | 原因分析 | 修补尝试 | 是否成功 |
|---|---|---|---|---|
| Jenkins 构建目录与 export.sh 中路径不一致 | fatal: not a git repository |
脚本内依赖 .git 路径存在,但 Jenkins workspace 下未初始化完整 repo |
注释掉 git 相关逻辑(如 git rev-parse) |
✅ 规避掉错误,但只是绕开 |
| docker 无法执行 | docker: command not found |
Jenkins agent 运行环境下没有正确加载 PATH 或未配置 docker |
尝试了 echo $PATH 验证 Jenkins 执行上下文,后改手动指定 /usr/bin/docker |
❌ 报错依旧(可能与非 root shell 有关) |
| 构建镜像后无法确认是否成功 | echo 输出被吞、docker images 无打印 |
Jenkins 中间步骤没有 stdout,导致无法看到执行结果 | 加入 `docker images | grep kibana-custom` |
| Jenkins 强制 clone git repo,与 export.sh 逻辑冲突 | not a git repo 报错重复出现 |
Jenkins 中 checkout 步骤与 export 脚本绑定不一致 |
曾手动写入 .git 文件夹模拟 repo,但效果有限 |
❌ 没解决根本问题 |
🧠 疲惫又坚决:我不是在修 Jenkins,而是在理解它
这些失败过程,其实逐步帮我看清了 Jenkins 的“性格”:
-
它运行的 shell 和本地 shell 不一样;
-
它的 workspace 每次构建都是隔离的;
-
它吞掉 stdout 的行为让调试成本倍增;
-
它 clone 下来的 repo 和我自己用脚本 export 的路径、结构并不匹配。
每一次构建失败我都手动触发,观察输出、复查路径、加 debug log、反复删掉 .git、试着加 echo —— 不是我不想一次做对,而是它让我根本无法确定“正确”到底长啥样。
五、第30次构建成功:Makefile接管构建权
不再在 Jenkins 流水线里“拼凑命令”,而是将构建逻辑交还给 Makefile 管理。
Jenkins 只保留调度、变量传递和执行角色,构建的核心命令归入版本控制。
🧱 Makefile:构建逻辑的唯一入口
以下是我重构后的 Makefile 片段:
IMAGE_TAG=test-kibana:$(shell date +%Y%m%d)-v2.0.0
build:
docker build -t $(IMAGE_TAG) .
docker images | grep test-kibana
这里做了几点关键调整:
| 项目 | 说明 |
|---|---|
IMAGE_TAG 变量 |
用 shell 命令生成带时间戳的 tag,取代 Jenkins 中易出错的 git rev-parse |
构建命令集中在 build 目标 |
不再分散在 Jenkins 脚本里,方便本地调试 |
| 加入镜像查询逻辑 | 明确输出构建产物,用于验证(比 echo 更可靠) |
⚙️ Jenkinsfile:负责“调用”,不再“执行逻辑”
Jenkins 的构建脚本也做了同步调整:
docker: "aaa.bbb.ccc.com/jenkins-ttttest:1.13.2"
stages:
build:
name: "Build Container"
steps:
build:
name: "build"
language:
python: 3.8
command:
- |
#!/bin/bash
set -euxo pipefail
source /init/modules/docker_aaaaauuuuuuttt.bash
IMAGE_TAG=test001
make -C $JENKINS_BUILD_DIR prod
不再配置一堆 shell 命令、路径处理、echo 验证 —— 全部封装在 Makefile 中。Jenkins 只需要相信它,并调用它。
🧱 一些容易被忽视的细节
你看到 build 成功了,不代表这个 image 可用:它只存在于 pipeline 构建容器内,除非你执行 docker push,否则 build 出来的镜像最终会随着容器销毁而消失。
不加 tag 的话不会 push,只会执行 build,这对验证构建过程是否成功很有帮助,避免误传生产。
.pipeline.yml 中的 secretKeyID、email、slackChannel 是公司平台内部通知和凭证配置,不加也不影响构建,但如果涉及发布流程可能会校验。
六、从 cron 到 CI:我的构建路径进化史
这次 Jenkins 构建踩坑过程,其实是我技术栈演进中的一个缩影。过去我习惯写一些 cron 任务,在服务器上定时运行脚本,完成日志清理、数据导出、甚至 Docker 构建等操作。这种模式虽然简单易控,但有几个明显限制:
| 限制 | 表现 |
|---|---|
| 可复现性差 | 本地脚本路径、环境变量、依赖版本不一致,导致难以共享/协作 |
| 状态不可见 | cron 成功或失败只靠邮件或日志,缺乏可视化反馈 |
| 手动操作频繁 | 经常需要 ssh 进去调试、执行或清理,极易出错 |
🚀 Jenkins CI 带来的变化
本次重构后,我第一次真正将这些脚本交给 CI 平台托管执行,收获了以下好处:
七、写在最后:构建不是命令,而是工程
我一开始只是想让脚本“能跑起来”,但随着构建失败次数的增长,我意识到:
构建并不只是几个命令的堆砌,而是一套“工程思维”的落地方式。
当我把它们系统化、结构化,并抽象为 Makefile 和 CI 流水线之后,一切不再混乱。
这就是我从 cron 走向 CI 的真实蜕变。
从「crontab + export 脚本 + docker build」分裂模式,走向了可维护、可移植、清晰可控的构建统一入口。
浙公网安备 33010602011771号