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」分裂模式,走向了可维护、可移植、清晰可控的构建统一入口。

posted @ 2025-06-28 17:33  vivi~  阅读(44)  评论(2)    收藏  举报