news 2026/8/6 21:15:06

附录:一次真实搭建的全过程记录与故障复盘——那些教程不会告诉你的 11 个坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
附录:一次真实搭建的全过程记录与故障复盘——那些教程不会告诉你的 11 个坑

附录:一次真实搭建的全过程记录与故障复盘——那些教程不会告诉你的 11 个坑

前面 9 篇讲的是"应该怎么做"。这一篇讲的是"实际发生了什么"。

我用 4 台华为云 ECS,在不到 2 小时的窗口期里从零搭一套 GitLab + Jenkins + Ansible 的持续交付���台。
过程中失败了 11 次,其中 3 次是方案级返工(整个安装路线推倒重来),1 次是把我彻底挡在门外的网络故障。

我把每一次失败的原始报错、排查路径、根因判断和最终解法都原样记下来。
如果你只想看"一键成功"的教程,前 9 篇够了;如果你要在真实环境里干这件事,这一篇的价值可能比前 9 篇加起来还大。


一、约束条件:为什么这是一次"高压实战"

先说清楚前提,否则很多决策看起来会很奇怪。

约束具体情况带来的影响
时间窗口机器可用时长1 小时 50 分钟没有"慢慢查文档"的余地,失败要在 2 分钟内决定"修还是换方案"
带宽公网 5 Mbit/sGitLab 1.3 GB 的包理论下载 ≥ 35 分钟,占掉三分之一窗口
网络位置本地 Windows → 公网 → 华为云北京四每一次交互都受运营商链路影响
操作方式纯 SSH,无控制台、无 VNC一旦 SSH 不通,等于完全失联
机器规格FlexusX 8 vCPU / 16 GB × 4,Ubuntu 24.04内存够,但 GitLab 全家桶默认配置仍然吃紧

关键决策一:所有操作脚本化,不手敲。
时间窗口太短,手敲命令的最大问题不是慢,而是失败后无法精确重放。所以第一件事不是装 GitLab,是写一个远程执行器。

关键决策二:所有脚本幂等。
因为一定会失败,一定会重跑。不幂等的脚本重跑一次就会制造新的脏状态。


二、真实时间线

22:15 拿到 4 台机器,开始探活 22:21 写完 rx.py(paramiko 并行执行器)+ 00-base.sh,4 节点并行初始化 —— 成功 22:22 GitLab apt 源方案 —— 失败 #1 #2 22:25 n3/n4 目标运行环境(JRE/Nginx/systemd) —— 成功 22:28 GitLab 改 deb 直装方案,开始下载 1.3G —— 进行中 22:29 n2 生成 ed25519 部署密钥并分发到 n1/n3/n4 —— 成功 22:34 Jenkins apt 源方案 —— 失败 #4 22:36 n4 蓝绿双实例(blue 8081 / green 8082) —— 成功 22:37 Jenkins deb 装完但起不来 —— 失败 #5 22:38 Java 21 适配 —— 成功,Jenkins 8080 返回 200 22:40 Jenkins CLI 装插件 —— 失败 #6 #7 #8 22:43 GitLab deb 包校验发现截断 —— 失败 #3 22:45 断点续传 + SHA256 校验重下;update-center 手动注入 22:52 **本地到 4 台机器全部失联** —— 失败 #9 #10 #11 23:05 链路诊断确认:安全组来源白名单静默丢包 23:1x 机器停机/释放,转入交付整理

一句话总结:基础设施层(网络、目录、systemd、密钥、蓝绿骨架)100% 完成;软件安装层因为镜像源和网络问题反复返工;最后倒在了网络出口 IP 变更上。


三、失败 #1 / #2:GitLab 的 apt 源方案

现象

配置好镜像源后:

apt-getupdate# ...# W: GPG error: https://<mirror>/gitlab/gitlab-ce/ubuntu noble InRelease:# The following signatures were invalid: NODATA 1 NODATA 2# E: The repository '... noble InRelease' is not signed.apt-getinstall-ygitlab-ce# E: Unable to locate package gitlab-ce

排查

两个报错其实是同一个因果链:

  1. NODATA说明InRelease文件下载到了,但内容不是有效的 PGP 签名数据——大概率是镜像站返回了一个 HTML 错误页或空文件;
  2. 签名校验失败 → apt 拒绝使用该仓库 → 包索引根本没建立 →Unable to locate package是必然结果。

curl -I直接看那个 InRelease 的 URL,返回的Content-Type不是application/pgp-signature,实锤。

根因

镜像站对gitlab-ce这个第三方仓库的同步不完整(或 noble 发行版目录未建立)。这类第三方 repo 的镜像同步质量普遍不如官方 Ubuntu 源。

解法与思考

放弃 apt,改 deb 直装。

这个决策的依据是:GitLab Omnibus 包是自包含的——它把 PostgreSQL、Redis、NGINX、Puma、Sidekiq 全打进了一个 deb 里,几乎不依赖系统 apt 源。既然 apt 唯一的价值(依赖解析)在这里不成立,那就没必要为一个坏掉的源纠缠。

# 南京大学镜像,直接取 debURL=https://mirror.nju.edu.cn/gitlab-ce/ubuntu/pool/noble/main/g/gitlab-ce/gitlab-ce_18.10.3-ce.0_amd64.debcurl-fSL-o/tmp/gitlab-ce.deb"$URL"dpkg-i/tmp/gitlab-ce.deb

经验:遇到第三方 apt 源签名问题,先判断"我到底需不需要 apt"。自包含的大包(GitLab、Jenkins、Zabbix)直接下 deb/rpm 往往比修源更快。


四、失败 #3:1.3 GB 的包,下载"成功"却是坏的

这是本次最有代表性的一个坑。

现象

dpkg-i/tmp/gitlab-ce.deb# dpkg-deb: error: <decompress> subprocess returned error exit status 2# lzma error: unexpected end of input# dpkg: error processing archive /tmp/gitlab-ce.deb (--install)

curl没有报错,退出码 0,文件也确实存在。

排查

stat-c%s /tmp/gitlab-ce.deb# 1078448480# 对比服务端声明的大小curl-sI"$URL"|grep-icontent-length# Content-Length: 1402522986

差了整整 324 MB。下载在中途被截断,但 curl 认为连接是"正常结束"的。

根因

curl -fsSL -o file url这种最常见的写法有三个隐患:

  1. 没有-C -:连接中断后无法续传,只能从头再来;
  2. 没有校验:curl 的退出码只表示"HTTP 层面没出错",服务端提前关闭连接(尤其在 5 Mbit/s 长连接、经过多层 CDN 时)不一定被判定为失败;
  3. -s静默:把进度和异常都吞了,人看不到异常。

在 35 分钟量级的大文件下载上,这三点叠加就是灾难。

解法:下载三板斧

写进12-gitlab-resume.sh的通用片段,任何大文件下载都建议照抄:

URL="https://mirror.nju.edu.cn/gitlab-ce/ubuntu/pool/noble/main/g/gitlab-ce/gitlab-ce_18.10.3-ce.0_amd64.deb"DEB=/tmp/gitlab-ce.debEXPECT_SIZE=1402522986EXPECT_SHA=e46618ac47e9459133c029f8a19f21f18765f147540afeb5356d386f6ac336eb# 板斧 1:断点续传 + 自动重试curl-C--fL--retry8--retry-delay5--retry-all-errors\--speed-limit10240--speed-time60\-o"$DEB""$URL"# 板斧 2:字节数比对(最快的第一道关)SIZE=$(stat-c%s"$DEB")echo"SIZE=$SIZEEXPECT=$EXPECT_SIZE"["$SIZE"="$EXPECT_SIZE"]||{echo"SIZE_MISMATCH";exit11;}# 板斧 3:SHA256 摘要校验(防止内容错乱而非仅截断)SHA=$(sha256sum"$DEB"|awk'{print $1}')echo"SHA=$SHA"["$SHA"="$EXPECT_SHA"]||{echo"CHECKSUM_MISMATCH";exit12;}echo"CHECKSUM=ok, start dpkg"dpkg-i"$DEB"

几个细节值得说:

  • --speed-limit 10240 --speed-time 60:连续 60 秒速度低于 10 KB/s 就判定失败并触发重试,避免"挂着不动但也不断开"的僵死连接白白耗掉窗口期;
  • --retry-all-errors:默认--retry只对部分错误重试,加上这个才覆盖连接层错误;
  • 字节数比对放在 SHA256 之前:算 1.3 GB 的 SHA256 要十几秒,先用stat一秒排除掉最常见的截断。

经验:任何超过 100 MB 的下载,都必须"续传 + 校验"。curl 退出码 0不等于文件是完整的。


五、失败 #4 / #5:Jenkins 的两连击

#4:apt 源同样不可用

apt-getupdate# Err:x https://<mirror>/jenkins/debian-stable binary/ Packages# 404 Not Foundapt-getinstall-yjenkins# E: Unable to locate package jenkins

根因和 GitLab 类似:镜像站的 Debian 风格仓库目录(binary/Packages)没同步。解法一致——直接下 deb:

curl-C--fL--retry5-o/tmp/jenkins.deb\"https://mirrors.tuna.tsinghua.edu.cn/jenkins/debian-stable/jenkins_2.568.1_all.deb"dpkg-i/tmp/jenkins.deb||apt-get-finstall-y

#5:装完起不来——Java 版本

systemctl status jenkins# Active: failed (Result: exit-code)journalctl-ujenkins --no-pager-n30# Running with Java 17 from /usr/lib/jvm/java-17-openjdk-amd64,# which is older than the minimum required version (Java 21).# Supported Java versions are: [21, 25]

Ubuntu 24.04 的default-jre是 Java 17,而 Jenkins 2.5xx 新版本线已经把最低要求提到 Java 21。

解法有两步,缺一不可

# 步骤 1:装 Java 21 并切换系统默认apt-getinstall-yopenjdk-21-jdk-headless update-alternatives--setjava/usr/lib/jvm/java-21-openjdk-amd64/bin/java# 步骤 2:显式给 jenkins 服务指定 JAVA_HOME(关键!)mkdir-p/etc/systemd/system/jenkins.service.dcat>/etc/systemd/system/jenkins.service.d/override.conf<<'EOF' [Service] Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64" Environment="PATH=/usr/lib/jvm/java-21-openjdk-amd64/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" EOFsystemctl daemon-reload systemctl restart jenkins

为什么只做步骤 1 不够?因为 systemd 服务不走交互式 shell 的初始化流程,它的环境变量来自 unit 定义和/etc/default/jenkinsupdate-alternatives改的是/usr/bin/java软链,Jenkins 的 systemd unit 里如果显式引用了$JAVA_HOME或某个绝对路径,改软链就无效。改 service 的 Environment 才是确定性的做法。

这里还顺带解决了另一个问题:应用本身用 Spring Boot 3.3.5 + Java 17 编译,而 Jenkins 本体要 Java 21。两套 JDK 必须共存——系统默认给 Jenkins 用 21,Maven 编译时通过JAVA_HOME或 toolchain 指到 17。这不是问题,是常态。

验证:

curl-s-o/dev/null-w'%{http_code}\n'http://127.0.0.1:8080/login# 200

六、失败 #6 / #7 / #8:Jenkins CLI 与插件的三连坑

#6:Jenkins URL is not configured

java-jarjenkins-cli.jar-shttp://127.0.0.1:8080/-authadmin:*** list-jobs# ERROR: anonymous is missing the Overall/Read permission ... 403# 或# Jenkins URL is not configured

根因:Jenkins 全局配置里的Jenkins URL 为空,CLI 的根 URL 校验直接拒绝。

解法——用启动时执行的 Groovy 脚本固化(Configuration as Code 的轻量版):

// /var/lib/jenkins/init.groovy.d/02-location.groovyimportjenkins.model.JenkinsLocationConfigurationdefloc=JenkinsLocationConfiguration.get()loc.setUrl("http://124.70.29.36:8080/")loc.setAdminAddress("admin@cd-lab.local")loc.save()println"--> Jenkins URL configured:${loc.getUrl()}"

#7:Unexpected request origin (check your reverse proxy settings)

配好 URL 后再调 CLI,换了个 403:

ERROR: Unexpected request origin (check your reverse proxy settings). ... 403

这个坑非常隐蔽。根因是:CLI 连的地址(http://127.0.0.1:8080/)与 Jenkins 全局配置的 URL(http://124.70.29.36:8080/)不一致,触发了 Jenkins 的 origin 校验(防 CSRF / DNS rebinding)。

解法一行字:CLI 必须用与 Jenkins URL 完全一致的地址调用,包括协议、主机、端口和结尾的斜杠。

# 错误java-jarjenkins-cli.jar-shttp://127.0.0.1:8080/...# 正确java-jarjenkins-cli.jar-shttp://124.70.29.36:8080/...

经验:Jenkins 里凡是遇到莫名其妙的 403,先检查"我访问的 URL"和"Jenkins 自己认为的 URL"是不是同一个。

#8:No update center data is retrieved

java-jarjenkins-cli.jar-s$JURL-auth$AUTHinstall-plugingitworkflow-aggregator# ERROR: git is neither a valid file, URL, nor a plugin artifact name in the update center# No update center data is retrieved, so plugin installation is impossible.

根因:Jenkins 无法访问updates.jenkins.io,本地一份 update-center 元数据都没有,install-plugin git这种按名字装的方式无从解析。

前两次尝试(改hudson.model.UpdateCenter.xml、直接 sed 替换default.json)都因为 Jenkins 已缓存/未加载而没生效。最终可用的方案是 postBack 注入

UC=https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.jsonMIRROR=https://mirrors.tuna.tsinghua.edu.cn/jenkins/plugins# 1. 下载 JSONP 格式的 update-center.jsoncurl-fsSL-o/tmp/uc.jsonp"$UC"# 2. 剥掉 JSONP 外壳 updateCenter.post( {...} ); 并把下载地址换成镜像python3 -<<'PY' import re, io raw = open('/tmp/uc.jsonp', encoding='utf-8').read().strip() raw = re.sub(r'^updateCenter\.post\(\s*', '', raw) raw = re.sub(r'\)\s*;?\s*$', '', raw) raw = raw.replace('https://updates.jenkins.io/download/plugins', 'https://mirrors.tuna.tsinghua.edu.cn/jenkins/plugins') open('/tmp/uc.json', 'w', encoding='utf-8').write(raw) print('UC_JSON_BYTES=', len(raw)) PY# 3. 把元数据 POST 回 Jenkins 的 update centercurl-s-XPOST-u"$JUSER:$JPASS"\-H'Content-Type: application/json'\--data-binary @/tmp/uc.json\"${JURL}updateCenter/byId/default/postBack"# 4. 现在可以按名字装插件了java-jarjenkins-cli.jar-s$JURL-auth$AUTHinstall-plugin\gitworkflow-aggregator pipeline-stage-view credentials-binding\ssh-credentials ssh-agent timestamper ws-cleanup junit\build-timeout gitlab-plugin matrix-auth-deploy

三种改法的对比:

方案做法优点缺点
hudson.model.UpdateCenter.xml<url>换成镜像的 update-center.json配置持久化需重启,且首次仍要在线拉取才生效
sed 替换updates/default.json直接改磁盘上的缓存文件不需要外网Jenkins 内存里可能已加载旧数据,时机难把握
postBack 注入用 API 把 JSON 灌进运行中的 Jenkins立即生效、可脚本化、不需重启JSONP 外壳要自己剥

经验:内网/弱网环境下部署 Jenkins,"插件怎么装"往往比"Jenkins 怎么装"更耗时。最稳的兜底是离线 hpi 包:在能上网的机器上把 hpi 及其依赖全下好,打包传进去丢到$JENKINS_HOME/plugins/再重启。


七、失败 #9:nohup &起的后台任务,SSH 一断就没了

现象

用 paramiko 的exec_command启动长任务:

client.exec_command("nohup bash /tmp/12-gitlab-resume.sh > /tmp/x.log 2>&1 &")

命令立刻返回,但过一会儿去查:

ps-ef|grep12-gitlab-resume# 没有进程cat/tmp/x.log# 只有开头几行

退出码取到-1(通道异常关闭)。

根因

SSH 的 exec 通道和交互式 shell 不同:通道关闭时,该会话的进程组会收到SIGHUPnohup理论上能屏蔽SIGHUP,但通过 exec 通道启动时,进程并没有真正脱离会话(没有 setsid、stdin/stdout 仍绑在通道上),通道一关就被连带清理。paramiko 这类库执行完命令立刻关通道,问题被放大。

解法:交给 systemd 托管

systemd-run--unit=gl-resume--collectbash/tmp/12-gitlab-resume.sh

好处是全方位的:

维度nohup &systemd-run --unit
是否脱离会话不可靠彻底脱离,由 PID 1 接管
状态可查pssystemctl status gl-resume
日志自己重定向journalctl -u gl-resume -f,自动轮转
退出码拿不到systemctl show gl-resume -p ExecMainStatus
资源限制可加-p MemoryMax=-p CPUQuota=
清理残留--collect退出后自动回收 unit

配套的查询命令:

systemctl status gl-resume --no-pager systemctl show gl-resume-pActiveState-pSubState-pExecMainStatus journalctl-ugl-resume --no-pager-n50

经验:云上远程运维,凡是超过 60 秒的任务,一律systemd-run托管。这个习惯在后面救了我一次——SSH 完全断掉之后,服务器上的 GitLab 安装任务仍在继续跑。


八、失败 #10 / #11:出口 IP 一变,全军覆没

这是压垮窗口期的最后一根稻草,也是最值得写的一条。

现象

搭建进行到一半,突然所有rx.py调用超时。逐项探测:

$curl-s-o/dev/null-w"%{http_code}\n"--max-time8http://124.70.29.36:8080/login 000 $curl-s-o/dev/null-w"%{http_code}\n"--max-time8https://www.baidu.com200$curl-s-o/dev/null-w"%{http_code}\n"--max-time10https://gitcode.com200

公网正常,但 4 台机器全挂。四台机器同时故障的概率极低,所以问题几乎一定在链路或访问控制,而不在服务器本身。

排查

第一步,看是不是端口级问题——用 bash 内建的/dev/tcp快速探 22 端口(比 telnet/nc 更通用,不用装东西):

forhin124.70.84.208124.70.29.36119.3.248.27124.70.110.32;dotimeout4bash-c"echo > /dev/tcp/$h/22"2>/dev/null\&&echo"$h:22 OK"||echo"$h:22 FAIL"done# 124.70.84.208:22 FAIL# 124.70.29.36:22 FAIL# 119.3.248.27:22 FAIL# 124.70.110.32:22 FAIL

第二步,ICMP 也不通:

124.70.29.36 的 Ping 统计信息: 数据包: 已发送 = 3,已接收 = 0,丢失 = 3 (100% 丢失)

第三步,看路径断在哪:

通过最多 12 个跃点跟踪到 124.70.29.36 的路由 1 <1 毫秒 <1 毫秒 <1 毫秒 192.168.3.1 2 3 ms 11 ms 7 ms 113.90.145.65 3 3 ms 6 ms 8 ms 202.105.155.189 4 * * * 请求超时。 5 * * * 请求超时。 6 * * * 请求超时。 7 37 ms 37 ms 37 ms 106.38.244.114 8 * * * 请求超时。 ... 12 * * * 请求超时。

第 7 跳(37ms,已经到了北方骨干)之后全部静默,没有任何 ICMP unreachable 回包

第四步,查本机出口 IP:

$curl-shttps://myip.ipip.net 当前 IP:113.90.145.122 来自于:中国 广东 深圳 电信

和最初建立连接时的出口 IP不一致——宽带重新拨号/NAT 池切换导致公网 IP 变了。

根因判断

把四条证据串起来:

  1. 四台机器同时失联 → 不是单机故障;
  2. 公网其它站点正常 → 不是本地断网;
  3. 路径能进骨干网但在云厂商边界前静默消失、无 ICMP 拒绝回包→ 典型的防火墙 DROP(丢弃)而非 REJECT(拒绝)
  4. 本机出口 IP 发生了变更。

结论:安全组入方向规则按来源 IP 白名单放行,我的新出口 IP 不在白名单里,所有包被静默丢弃。

这里有个很实用的判断法则:

  • 端口不通但立刻返回Connection refused→ 服务没起,网络是通的;
  • 端口不通且卡到超时、traceroute 静默中断 → 大概率是防火墙/安全组 DROP;
  • 只有个别端口不通 → 安全组端口规则;所有端口 + ICMP 全不通 → 来源 IP 被整体挡掉

应对

已经发生时能做的

  1. 立刻确认服务器侧任务不受影响——因为长任务用了systemd-run托管,SSH 断了它照跑,这就是第七节那个习惯的回报;
  2. 启动带退避的自动重连探测,避免人工反复试:
foriin$(seq1120);doforhin124.70.29.36124.70.84.208;doiftimeout4bash-c"echo > /dev/tcp/$h/22"2>/dev/null;thenecho"SSH_RECOVERED host=$hattempt=$i";exit0fidoneprintf'.';sleep8doneecho"probe-end (not recovered)"
  1. 把不依赖服务器的工作切到前台继续做(本次是:整理脚本、写文档、推代码仓库),不让窗口期空转。

下次如何避免

措施说明
安全组按网段而非单 IP 放行家宽/办公网出口通常在一个 /24 里,放113.90.145.0/24比放单 IP 稳得多
22 端口走堡垒机/VPN把白名单固定成堡垒机的固定 IP,本地 IP 怎么变都不影响
准备带外通道云厂商的 VNC / 远程登录(走控制台,不经安全组)是最后的救命稻草
关键任务服务器侧自治用 systemd/cron 托管,让任务不依赖控制端在线
提前把制品和源码放到公网可达的仓库服务器可以自己git clone,不依赖"本地 → 服务器"的文件传输通道

最后一条尤其重要。本次我把演示应用和全部脚本推到了 GitCode,50-gitlab-seed.sh里 GitLab 的代码种子是这样取的:

gitclone--depth1https://gitcode.com/cpyaxjq/continuous-delivery-in-action.git srcSRC=/tmp/seedsrc/src/cd-demo-app

这样只要服务器能上网,即使我本地传不上去文件,服务器也能自己把代码拉全。把"控制端"从关键路径上移除,是远程运维一个很值钱的设计原则。


九、11 个坑的速查表

#现象根因解法
1apt-get updateNODATA镜像站 InRelease 非有效签名数据放弃 apt,deb 直装
2Unable to locate package gitlab-ce仓库被拒 → 索引未建立同上
3dpkglzma error: unexpected end of input1.3G 包下载被截断,curl 未报错续传 + 字节比对 + SHA256
4Jenkins 源Packages 404镜像 Debian 目录未同步直接下jenkins_2.568.1_all.deb
5Java 17 is older than minimum required (Java 21)Ubuntu 24.04 默认 JDK 17装 JDK21 +update-alternatives+service override
6CLI 403Jenkins URL is not configured全局 URL 为空init.groovy.dJenkinsLocationConfiguration.setUrl()
7CLI 403Unexpected request originCLI 地址与全局 URL 不一致CLI 必须用完全一致的 URL
8No update center data is retrieved拉不到 update-center.json剥 JSONP + 换镜像 URL +postBack注入
9nohup &起的任务 SSH 断即死exec 通道关闭触发 SIGHUP,进程未脱离会话systemd-run --unit=x --collect
104 台机器同时全端口不通出口 IP 变更,安全组白名单静默 DROP按网段放行 / 堡垒机 / 带外通道
11内联 shell 命令多层引号转义失败Python → SSH → bash 三层转义改为 SFTP 上传.shbash执行

关于 #11 补充一句:一开始我用client.exec_command(f"bash -c '{cmd}'")这种内联方式下发命令,只要命令里带引号、$、反引号就必然出问题,排查成本极高。改成"先把脚本 SFTP 上去,再执行文件"之后,转义问题一次性归零,而且脚本还留在服务器上可以复查、可以重跑。这是本次最省事的一个改动。

defrun_script(host,local_path,timeout=600):"""上传脚本再执行,彻底规避多层引号转义"""remote=f"/tmp/{os.path.basename(local_path)}"sftp=client.open_sftp()sftp.put(local_path,remote)sftp.close()returnrun(host,f"bash{remote}",timeout=timeout)

十、完成度盘点:诚实地说清楚做到了哪一步

技术文章最忌讳的是把"设计好了"说成"跑通了"。这里逐项对账:

组件 / 能力状态说明
4 节点基础环境(主机名、hosts、时区、内核参数、工具)✅ 已验证00-base.sh四机并行执行成功
节点间内网互通✅ 已验证同 VPC,内网 IP 互 ping/ssh 通
ed25519 部署密钥生成与分发✅ 已验证n2 → n1/n3/n4 免密登录验证通过
n3 测试环境(JRE17 / Nginx / appuser / 目录规范 / systemd)✅ 已验证30-target.sh执行成功
n4 生产环境 + 蓝绿双实例骨架(8081/8082 / upstream / active_color)✅ 已验证31-prod-bluegreen.sh执行成功
Jenkins 安装并启动✅ 已验证curl :8080/login返回 200
Jenkins 插件安装⚠️ 中断postBack 方案已跑通元数据注入,插件下载阶段遇上断网
GitLab 安装⚠️ 中断deb 续传任务由systemd-run托管中,断网后无法验证结果
演示应用工程(Spring Boot + 单测 + build-info)✅ 已完成代码完整,见仓库cd-demo-app/
Ansible 剧本(deploy / bluegreen / rollback)✅ 已完成剧本完整,含 block/rescue 自动回滚
Jenkinsfile 十阶段流水线✅ 已完成见仓库cd-demo-app/Jenkinsfile
GitLab 项目/分支/Webhook 自动化种子脚本✅ 已编写50-gitlab-seed.sh,未获得执行窗口
Jenkins Job/凭据自动化脚本✅ 已编写51-jenkins-setup.sh,未获得执行窗口
端到端流水线跑通❌ 未完成断网 + 机器回收,窗口期用尽

没跑通就是没跑通。但这次失败暴露出来的 11 个坑,恰恰是任何一篇"顺利成功"的教程给不了的东西——因为顺利的时候你根本遇不到它们。

如果你拿这套脚本去自己的环境里跑,上面每一个坑我都已经在脚本里做了防御:续传校验、幂等重跑、systemd 托管、URL 一致性、update-center 注入。你大概率会比我顺利。


十一、把这次经历抽象成方法论

最后留三条我认为最有普适价值的结论。

第一,把失败当成默认路径来设计流程。
不是"如果失败了怎么办",而是"失败之后如何低成本重来"。这次能在 2 小时里返工三次方案还保住大部分成果,靠的就是:所有操作脚本化、所有脚本幂等、所有长任务托管、所有下载校验。这四条本身就是持续交付的核心思想——可重复、可验证、可回滚——只不过这次它们用在了搭平台上,而不是发版上。

第二,控制端是最脆弱的一环,要主动削弱对它的依赖。
本地网络、本地 IP、本地 SSH 会话,任何一个出问题都会让你从"在干活"变成"完全失联"。把任务下沉到服务器侧自治(systemd)、把物料放到双方都能访问的第三方(代码仓库),能显著提高整体韧性。

第三,遇到"莫名其妙"的问题,先分清是网络层、权限层还是应用层。
本次 11 个坑里,有 4 个第一反应会以为是应用配置错了(GPG NODATA、dpkg lzma、CLI 403、插件装不上),实际根因分别在镜像同步、传输完整性、URL 一致性、外网可达性。养成"先看报错发生在哪一层"的习惯,比记住任何具体解法都重要。


附:本文涉及的全部脚本

所有脚本、Ansible 剧本、Jenkinsfile 与演示应用源码已开源:

https://gitcode.com/cpyaxjq/continuous-delivery-in-action

ops/rx.py paramiko 并行远程执行器(含 run_script 防转义方案) ops/00-base.sh 四节点幂等初始化 ops/11-gitlab-deb.sh GitLab deb 直装 + gitlab.rb 轻量化调优 ops/12-gitlab-resume.sh 大文件下载三板斧(续传/字节比对/SHA256) ops/21-jenkins-deb.sh Jenkins deb 直装 + init.groovy.d 安全初始化 ops/22-jenkins-java21.sh Java 21 适配(含 service override) ops/26-jenkins-uc.sh update-center postBack 注入 ops/30-target.sh 目标环境标准化 ops/31-prod-bluegreen.sh 蓝绿双实例骨架 ops/40-sshkey.sh 部署密钥生成分发 ops/50-gitlab-seed.sh GitLab 项目/分支保护/Webhook/DeployKey/代码种子 ops/51-jenkins-setup.sh Jenkins 凭据/Job/首次构建 ops/52-e2e-evidence.sh 端到端实操证据采集(含蓝绿零中断验证)

欢迎在自己的环境里复现,也欢迎把你踩到的新坑提 Issue 补充进来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 21:13:37

Tk-Instruct-small-def-pos与GPT对比:指令跟随模型的全方位测评

Tk-Instruct-small-def-pos与GPT对比&#xff1a;指令跟随模型的全方位测评 【免费下载链接】tk-instruct-small-def-pos 项目地址: https://ai.gitcode.com/hf_mirrors/LLM-Research/tk-instruct-small-def-pos Tk-Instruct-small-def-pos是一款基于T5架构的轻量级指令…

作者头像 李华
网站建设 2026/8/6 21:09:42

NLP第二阶段学习 标注用的原始数据参考

NLP第二阶段学习 标注用的原始数据参考 文件名&#xff1a;classification_data.txt京东物流机器人配送服务 创新创业教育加强 自动驾驶测试里程突破 芯片制造工艺达到7纳米 人民币汇率保持稳定 网红主播带货销售额惊人 滑雪运动员在冬奥会上获得银牌 人工智能在医疗诊断领域取…

作者头像 李华
网站建设 2026/8/6 21:07:21

10分钟上手gatsby-starter-bee:新手必备的博客搭建教程

10分钟上手gatsby-starter-bee&#xff1a;新手必备的博客搭建教程 【免费下载链接】gatsby-starter-bee &#x1f41d;Full Package | Simple | Fresh UI | Blog Template :: Lets start to blogging with gatsby-starter-bee! 项目地址: https://gitcode.com/gh_mirrors/ga…

作者头像 李华
网站建设 2026/8/6 21:06:11

从0到1掌握SecPipe:新手必备的MCP服务器安装与基础操作教程

从0到1掌握SecPipe&#xff1a;新手必备的MCP服务器安装与基础操作教程 【免费下载链接】secpipe MCP server for AI-driven security pipelines 项目地址: https://gitcode.com/gh_mirrors/fu/secpipe SecPipe是一款基于AI驱动的安全管道MCP服务器&#xff0c;专为安全…

作者头像 李华
网站建设 2026/8/6 21:04:03

大模型技术之-企业级大模型的部署

1、企业级大模型部署概述 1.1 为什么要部署&#xff1f; 企业部署大模型&#xff0c;不是为了解决“能不能用”&#xff0c;而是必须把敏感数据和服务的控制权牢牢掌握在自己手里。要想数据安全&#xff0c;就需要实现私有化的部署。这里包括大语言模型、嵌入模型、重排序模型…

作者头像 李华