纯 IPv6 内网 Jenkins 一键部署实战:Gogs + 4 个服务(2 Java 后端 + 2 Vue 前端)
环境:Ubuntu 服务器若干台 + 自建 Gogs + 原生安装的 Jenkins(systemd 管理),内网只有 IPv6 出网 。
目标:四个服务(AIX 前后端、NIX 前后端)各一个构建按钮,点一下 = 拉代码 → 打包 → 传服务器 → 重启/替换 → 健康检查。
本文是实战复盘:每一步怎么配、每一个坑为什么踩、怎么验。所有 IP/端口/路径均已替换为占位符,对照表在文末。
1. 背景:一台 Jenkins 管四台服务器
我们有很多 Java 项目,代码都托管在自己的 Gogs 上,服务分散在三台服务器上:
| 机器 | 角色 |
|---|---|
| 131 | Jenkins(复用现有机器,不单独占用一台) |
| 105 | Nginx,两个 Vue 前端的静态文件都在这台 |
| 107 | AIX Java 后端 |
| 108 | NIX Java 后端 |
以前的更新流程是纯手工:本地打包 → FinalShell 拖文件上服务器 → systemctl restart,一次更新四遍,还容易漏。目标是让 Jenkins 代劳,而且我们不要 webhook 自动触发------发不发版、什么时候发,由人点按钮决定(这不是微服务,四个服务彼此独立,更新节奏不同)。
先回答一个当时的疑虑:一台 Jenkins 管理多台不同服务器,完全没问题。Jenkins 只负责"构建 + 用 SSH 把产物推过去",目标服务器上不需要装任何 Jenkins 的东西。最终形态就是 Jenkins 首页四个 Job、四个按钮:
AIX(Java后端) NIX(Java后端) AIX-UI(Vue前端) NIX-UI(Vue前端)
2. 安装 Jenkins
这一步我们机器上已经完成,这里只留记录。官方下载页:**https://www.jenkins.io/ ,里面有 Linux(Debian/Ubuntu 系)的 apt 安装方式,照官方文档走即可。
我们的安装形态(后面的配置都基于它):
- apt 原生安装 ,
systemctl start/stop/restart jenkins管理; - 默认端口 8080,
JENKINS_HOME在/var/lib/jenkins; - 初始化时那个"管理员初始密码"在
/var/lib/jenkins/secrets/initialAdminPassword。
💡 给纯内网/受限网络环境的建议:初始化向导里先别选"安装推荐插件",直接跳过。因为推荐插件要从更新源下载,而你的更新源大概率需要先按第 3 章改掉------先把源改对,再装插件,少走一遍弯路。
3. 插件更新源:从"镜像全灭"到官方源
装完 Jenkins 第一个撞上的就是它:插件市场一片空白 。网上教程的标准答案是"把更新源改成清华镜像",但我们改了没用------报 FileNotFoundException。
3.1 先看清本质:国内镜像的更新源已经全下线了
我把国内主要镜像站挨个实测了一遍(清华、中科大、北外、上交、华为云、腾讯云、阿里云),结论:
- 所有国内镜像站的 Jenkins 更新源(
/jenkins/updates/元数据目录)都已 404 下线。网上教程里那批清华源地址全是历史信息; - 部分镜像(中科大、腾讯)还保留着
/jenkins/plugins/插件文件归档,但没有元数据,浏览器里依然刷不出插件列表; - 那句
FileNotFoundException本质是 HTTP 404------能收到 404 恰恰说明网络、DNS、HTTPS 全是通的(这个判断很重要,后面排错全靠它)。
我也试过手动下 hpi 包离线安装,死路:插件之间相互依赖、版本冲突,依赖地狱,果断放弃。
3.2 转机:官方源本身就有 IPv6
updates.jenkins.io 走 Azure,有 IPv6 记录 ------对我们这种"只有 IPv6 出网"的内网,官方源反而能直连。更妙的是插件文件的下载链接会被官方 CDN 按访客位置 302 调度(我们的服务器被调度到 get.jenkins.io,同样有 IPv6;国内有的场景被调度到清华的插件归档),反正都能下。
动手前先在 Jenkins 那台服务器上跑两条 curl 验证(预期:第一条 200,第二条 302):
bash
# 1. 测元数据(3MB 多的 json,几秒到十几秒正常)
curl -sIL -o /dev/null -w "HTTP %{http_code}, %{time_total}s\n" \
"https://updates.jenkins.io/current/update-center.json"
# 2. 测插件文件调度(预期 302,看它跳到哪)
curl -sI -o /dev/null -w "HTTP %{http_code} -> %{redirect_url}\n" \
"https://updates.jenkins.io/download/plugins/AnchorChain/1.0/AnchorChain.hpi"
两条都符合预期,后面就一定能成;如果第一条超时,先解决 IPv6 出网,别往下走。
3.3 把更新源改成官方
Manage Jenkins → Plugins → 左侧 Advanced settings ,拉到页面最底部的 Update Site 表格:
-
编辑 default 那一行,改成:
https://updates.jenkins.io/current/update-center.json(注意是
/current/路径,老地址会 301 过去,Jenkins 能自动跟随,但直接写新地址最稳。) -
保存,点旁边的 Check now(立即检查);
-
回 Available plugins 刷新,列表就出来了。
3.4 两个"假故障",提前知道免得慌
- Jenkins 首页可能显示红色"离线/无网络连接"------那是 Jenkins 拿
google.com测的网络,在国内永远显示离线,但不影响下载插件,无视即可; - 第一次加载 Available plugins 要拉 3MB 的元数据,慢一点正常,失败就多点两次 Check now。
4. 中文界面与插件安装
4.1 界面汉化
在 Available plugins 里搜索 Chinese,安装 Localization: Chinese (Simplified) (中文简体语言包),装完勾选页面右下角的"完成后重启"。重启后界面即变中文(个别插件页面可能仍是英文,属翻译覆盖度问题,不影响使用)。
4.2 必装插件清单
按"拉代码 → 打包 → 部署"的工作流,插件一共 6 个,别贪多:
| 搜索关键词 | 插件 | 对应流程的哪一步 |
|---|---|---|
Git |
Git plugin | 从 Gogs 拉代码(自动带上 Git Client、Credentials 等依赖) |
Pipeline |
Pipeline | 流水线本身,Jenkinsfile 就靠它 |
SSH Agent |
SSH Agent | 部署:sshagent { ssh/scp 到目标机执行命令 } |
NodeJS |
NodeJS | 前端 Vue 打包(提供 nodejs 流水线步骤) |
Maven Integration |
Maven Integration | Tools 页出现 Maven 配置入口 |
Pipeline Maven Integration |
Pipeline Maven Integration | 流水线里能用 withMaven 步骤 |
照旧全部勾"完成后重启"。依赖会自动一起装上------这正是手动下 hpi 包最头疼的问题,改对更新源之后彻底不存在了。
4.3 服务器上补两样系统工具
bash
sudo apt update && sudo apt install -y git
git 必须装:不装的话 Tools 页的 Git installations 会挂红字(Jenkins 找不到 git 可执行文件)。Maven 不用 apt 装,原因见下一章。
5. 构建工具配置(Tools 页)
入口:Manage Jenkins → Tools(全局工具配置)。这一章有一条贯穿始终的铁律,先放这里:
⚠️ 铁律:Tools 页里给工具起的名字,必须和流水线脚本里引用的名字一字不差(区分大小写)。
我们在这上面摔了两次:脚本里写
withMaven(maven: '3.8.6')而 Tools 里叫M3,报Invalid tool ID;脚本里写node16而 Tools 里没建或名字不对,报No installation node16 found。名字对不上时 Jenkins 会退回系统 PATH 找,找不到就是mvn: not found(exit 127)。
5.1 Maven:别用"自动安装",手动放一份到 /opt
先说结论:Tools 页里勾 "Install automatically" 的官方路线在我们环境里是死的 ------Jenkins 自动下载 Maven 的源 archive.apache.org/dist/maven/binaries/ 对新版 Maven 已经 404(实测 3.9.9、3.9.11 都 404),这是 Jenkins 的已知问题,不是配置错。
手动装(腾讯云镜像,IPv6 实测可用):
bash
cd /opt
sudo curl -LO https://mirrors.cloud.tencent.com/apache/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.tar.gz
sudo tar xzf apache-maven-3.9.16-bin.tar.gz
sudo rm apache-maven-3.9.16-bin.tar.gz
ls /opt/apache-maven-3.9.16/bin/mvn # 看到这个文件就是好了
然后 Tools 页 → Maven 安装 → 新增 Maven:
- Name :
M3(流水线里withMaven(maven: 'M3')引用的就是它) - 取消勾选 Install automatically
- 安装路径(MAVEN_HOME) :
/opt/apache-maven-3.9.16
5.2 给 Jenkins 用户配 Maven 镜像(一次配好,全局生效)
Maven 下依赖的默认源是中央仓库。阿里云 Maven 镜像没有 IPv6(排除),腾讯云镜像有(已验证),给 Jenkins 用户配上:
bash
sudo -u jenkins mkdir -p /var/lib/jenkins/.m2
sudo -u jenkins tee /var/lib/jenkins/.m2/settings.xml >/dev/null <<'EOF'
<settings>
<mirrors>
<mirror>
<id>tencent</id>
<mirrorOf>central</mirrorOf>
<url>https://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>
</mirror>
</mirrors>
</settings>
EOF
配完之后构建日志里下载依赖会显示 from/to tencent (...) 而不是 central------这个细节是后面排错的重要线索。
5.3 Node:同样手动装 16 到 /opt
老 Vue 项目(Vue2 + 老 webpack)在 Node 17+ 上会报 error:0308010C digital envelope routines::unsupported 直接编不过,所以选 16 的最后一个版本 16.20.2:
bash
cd /opt
sudo curl -LO https://mirrors.cloud.tencent.com/nodejs-release/v16.20.2/node-v16.20.2-linux-x64.tar.xz
sudo tar xJf node-v16.20.2-linux-x64.tar.xz
sudo rm node-v16.20.2-linux-x64.tar.xz
/opt/node-v16.20.2-linux-x64/bin/node -v # 输出 v16.20.2 就成了
Tools 页 → NodeJS 安装 → 新增 NodeJS:
- Name :
node16(全小写,一字不差) - 取消勾选 Install automatically
- 安装路径 :
/opt/node-v16.20.2-linux-x64
5.4 JDK 8:给老项目编译用
我们的 Java 项目是 Spring Boot 2.1 时代的老项目(JDK 8),而 Jenkins 服务器默认 JDK 17/21,编译时报:
module jdk.compiler does not "opens com.sun.tools.javac.processing" to unnamed module
这是 Lombok 老版本跑在新 JDK 上 的经典报错。修法不是给新 JDK 加一串 --add-opens 闯关(后面还有老版 Kotlin 插件等着炸),而是忠实还原本地构建环境------装 JDK 8:
bash
sudo apt install -y openjdk-8-jdk
ls /usr/lib/jvm/ # 记下目录名,一般是 java-8-openjdk-amd64
它只 在流水线的 environment 里以 JAVA_HOME 的形式使用(见第 7 章),不影响 Jenkins 自己跑在新 JDK 上。
5.5 Tools 页其他区块
- Maven 配置 / 默认 settings 提供:保持 "Use default maven settings" 不动;
- Pipeline Maven Configuration 那一大块(DAO/JDBC/Database) :全不动,
no storage mode就是正确状态; - JDK 安装:不配(JDK 8 走流水线环境变量);
- Git installations :apt 装完 git 后红字自动消失,Name 保持
Default、Path 保持git。
6. 认证配置:SSH 免密与 Jenkins 凭据
Jenkins 部署的本质是"拿私钥登录目标机器执行命令",所以要准备两份凭据:Gogs 账号 (拉代码)和 SSH 私钥(部署)。
6.1 SSH 免密:五步(全在 Jenkins 那台 131 上做)
先说原理,避免跑错机器(我们就跑错了:在 105 上生成了一遍密钥,白做):私钥留在"主动去连别人"的机器(131 的 Jenkins),公钥放到"被登录"的机器(105/107/108)。
bash
# 1. 先看有没有生成过密钥(避免覆盖)
ls ~/.ssh/
# 2. 没有的话生成(无密码密钥,Jenkins 非交互登录必需)
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
# 3. 把公钥发给目标机(每台输一次密码,之后就免密)
# 命令行里 IPv6 地址要用引号包起来
ssh-copy-id root@'<105的内网IPv6>'
ssh-copy-id root@'<107的内网IPv6>'
ssh-copy-id root@'<108的内网IPv6>'
# 4. 验证免密生效(不问密码、直接输出主机名 = 成功)
ssh root@'<107的内网IPv6>' hostname
# 5. 取私钥内容(下一步要粘进 Jenkins 凭据)
cat ~/.ssh/id_ed25519
id_ed25519 是私钥(自己留着),id_ed25519.pub 是公钥(发出去)。
6.2 在 Jenkins 里建两份凭据
入口:Manage Jenkins → Credentials → 全局域 → Add Credentials
| 第一份:Gogs 账号 | 第二份:SSH 私钥 | |
|---|---|---|
| 类型(Kind) | Username with password | SSH Username with private key |
| 内容 | Gogs 用户名 + 密码 | Username 填 root;Private Key 选 Enter directly ,把 cat ~/.ssh/id_ed25519 的整段 内容(-----BEGIN ... END-----)粘进去 |
| ID | gogs-cred |
deploy-ssh |
ID 必须和流水线脚本里写的 credentialsId 一致(自己起名也行,两边对上就行)。
6.3 部署用户写 root 还是普通用户?
TARGET_USER 取决于你 ssh 免密是给哪个用户做的 ,两边必须一致。怎么判断自己在用谁:看终端提示符------[root@hlht1 aixs]# 是 root(root@ 开头、# 结尾),普通用户是 用户名@主机$。我们所有服务本来就是 root 跑的(User=root),就沿用了 root。
如果以后要收紧安全改用普通用户,要多两步:免密对这个用户重做一遍;
systemctl restart改成sudo systemctl restart并给该用户配 sudo 免密码(visudo)。建议先把一键部署跑通,安全收紧后置。
7. Java 后端流水线
7.1 建 Job
新建任务 → 名称(如 AIX)→ 类型选"流水线" 。General 和 Triggers 两页什么都不用勾 ------全不勾就是标准的"手动点按钮构建"模式,正是我们要的。"定义"选 Pipeline script,脚本粘进下面的文本框。
7.2 AIX 后端完整脚本(终稿)
groovy
pipeline {
agent any
environment {
GIT_URL = 'http://[<Gogs公网IPv6>]:<Gogs端口>/<项目组>/aix-web-api.git'
BRANCH = 'union-version-1.3'
GIT_CRED = 'gogs-cred'
SSH_CRED = 'deploy-ssh'
TARGET_USER = 'root'
TARGET_HOST = '<107的内网IPv6>'
DEPLOY_DIR = '</home/web/web-ui/aixs>'
SVC_NAME = 'aixs'
JAR_PATH = 'base-web/target/*.jar'
MAVEN_OPTS = '-Djava.net.preferIPv6Addresses=true'
JAVA_HOME = '/usr/lib/jvm/java-8-openjdk-amd64'
}
stages {
stage('拉代码') {
steps {
git branch: env.BRANCH, credentialsId: env.GIT_CRED, url: env.GIT_URL
}
}
stage('打包') {
steps {
withMaven(maven: 'M3') {
sh 'mvn -B -DskipTests package'
}
}
}
stage('部署') {
steps {
sshagent(credentials: [env.SSH_CRED]) {
sh '''
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "mkdir -p $DEPLOY_DIR"
scp -o StrictHostKeyChecking=no $JAR_PATH "$TARGET_USER@[$TARGET_HOST]:$DEPLOY_DIR/"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "systemctl restart $SVC_NAME"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "sleep 5 && systemctl is-active $SVC_NAME"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "cd $DEPLOY_DIR && ls -t *.jar | tail -n +4 | xargs -r rm -f"
'''
}
}
}
}
}
逐块说:
- environment :脚本里唯一需要按实际情况改的地方。
GIT_URL里 IPv6 要带方括号(URL 语法);DEPLOY_DIR是 jar 在目标机上的目录;SVC_NAME是systemctl服务名(aixs→aixs.service); - JAR_PATH :多模块工程打包后 jar 分散在各模块的
target/里,必须指明启动模块 。确认办法------在目标机上ls -lh <部署目录>看现有 jar 文件名(暴露模块名),systemctl cat aixs看ExecStart实际指向哪个文件; - 部署五连 :建目录 → 传 jar → 重启 → 等 5 秒查
is-active→ 只保留最新 3 个 jar,更旧的删掉(119MB 一个,攒多了磁盘吃不消;放在重启成功之后执行,不会误删正在用的); - 我们的服务用带时间戳+commit 号的 jar 文件名、由启动脚本自动挑最新的拉起,所以 Jenkins 只要把新 jar 丢进目录再 restart,和原手动流程完全一致,服务器上任何现有东西都不用改。
7.3 第一次构建前 30 秒核对单
- Tools 里
M3建好了?(没有的话withMaven第一步就报M3 not found) gogs-cred、deploy-ssh两份凭据建好了?TARGET_HOST和 ssh-copy-id 时用的地址一致?(ssh root@'<地址>' hostname再验一遍)- 心理预期:第一次构建 5-15 分钟------要下载 Maven 本体 + 整个工程依赖(多模块 + Kotlin 更慢),之后有缓存越跑越快(我们第二次构建只要 1 分钟)。
点 Build Now ,构建过程点左侧进度条 → Console Output 看全部日志。
7.4 我们踩过的四个坑(对照排查)
| 报错 | 原因 | 修法 |
|---|---|---|
Invalid tool ID xxx + mvn: not found |
脚本里的工具名和 Tools 页对不上;且自动安装源已 404 | 脚本改 withMaven(maven: 'M3') + 手动装 Maven(5.1) |
下载依赖 网络不可达(from/to central) |
Maven 是独立 Java 进程,不继承 Jenkins 的 IPv6 优先参数;settings.xml 也没生效 | environment 加 MAVEN_OPTS(9.2 节)+ 重建 settings.xml(5.2) |
module jdk.compiler does not "opens ..." |
Lombok 老版本跑在新 JDK 上 | 装 JDK 8 + environment 加 JAVA_HOME(5.4) |
ssh: Could not resolve hostname [<v6>] |
ssh 命令行不认方括号(方括号是 scp/URL 的语法) |
TARGET 拆成 TARGET_USER+TARGET_HOST:ssh 用裸 v6,scp 保留方括号(9.3 节) |
顺带两个不是错 的日志噪音:
No test report files were found(跳过了测试当然没报告);老项目依赖的 WARNING(达梦驱动 systemPath 之类),不用管。
7.5 NIX 后端:照抄改 5 个值
新建流水线任务(创建页底部 Copy from 填 AIX 更省事),只改 environment 块:
| 变量 | NIX 的值 |
|---|---|
GIT_URL |
http://[<Gogs公网IPv6>]:<Gogs端口>/<项目组>/nix-web-api.git |
BRANCH |
union-version-1.3(以 Gogs 里真实存在的分支为准) |
TARGET_HOST |
<108的内网IPv6> |
DEPLOY_DIR |
</home/web/web-ui/nixs> |
SVC_NAME |
nixs |
动手前 30 秒预检(都是 AIX 踩过点的孪生兄弟):
bash
# ① 免密通不通(131 上跑,输出主机名即可;不通补 ssh-copy-id)
ssh root@'<108的内网IPv6>' hostname
# ② 服务名到底是啥(108 上跑,显示 nixs.service 就用 nixs)
systemctl list-units --type=service | grep -i nix
# ③ 现有 jar 长啥样(108 上跑,确认 JAR_PATH 的模块名)
ls -lh /home/web/web-ui/nixs/
NIX 工程更老,第一次构建要下载它自己的一套依赖,比 AIX 的 1 分钟长几分钟,正常。
8. Vue 前端流水线
前端和后端的差异只在"打包方式"和"部署动作":没有 systemd 服务,构建产物是静态文件,替换到 105 的 Nginx 目录即生效。
8.1 你们的部署机制长什么样(先看清楚再写脚本)
105 上每个前端目录(如 </home/web/web-ui/aixs>)里有一份 replace_dist.sh,读一遍它,三个细节直接决定流水线怎么写:
- 它自动挑目录里最新的
dist-aix-ui-*.tar.gz,不用传参数 ;但压缩包里必须带着dist这层目录本身 (脚本解压后检查dist目录存在,失败自动回滚)。所以打包命令是tar czf 包名 dist,不是把 dist 里的内容散着打; - 脚本带交互确认(
INTERACTIVE=true,运行时问"是否替换 (y/N)")。Jenkins 的 ssh 是非交互的,没人按键它会直接"操作已取消"------而且退出码是 0,构建显示绿色但什么都没替换 (假成功,最坑的一种)。解法:echo y | ./replace_dist.sh,喂一个 y 进去,不用改服务器上的脚本; - NIX 的那份脚本大概率是从 AIX 复制的,检查第 5 行
PATTERN是不是dist-nix-ui-*.tar.gz------如果还写着dist-aix-ui,它永远找不到 NIX 的包(又一种假成功):
bash
head -6 /home/web/web-ui/nixs/replace_dist.sh
# 不对就一句修好:
sed -i 's/dist-aix-ui/dist-nix-ui/' /home/web/web-ui/nixs/replace_dist.sh
8.2 AIX 前端完整脚本(终稿)
新建流水线任务 AIX-UI,类型流水线:
groovy
pipeline {
agent any
environment {
GIT_URL = 'http://[<Gogs公网IPv6>]:<Gogs端口>/<项目组>/aix-ui.git'
BRANCH = 'feature-1.2'
GIT_CRED = 'gogs-cred'
SSH_CRED = 'deploy-ssh'
TARGET_USER = 'root'
TARGET_HOST = '<105的内网IPv6>'
DEPLOY_DIR = '</home/web/web-ui/aixs>'
}
stages {
stage('拉代码') {
steps {
git branch: env.BRANCH, credentialsId: env.GIT_CRED, url: env.GIT_URL
}
}
stage('打包') {
steps {
nodejs(nodeJSInstallationName: 'node16') {
sh 'SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass/ npm install --registry=https://registry.npmmirror.com && npm run build'
}
}
}
stage('部署') {
steps {
sshagent(credentials: [env.SSH_CRED]) {
sh '''
TS=$(date +%Y%m%d_%H%M%S)
TARBALL="dist-aix-ui-$TS.tar.gz"
tar czf "$TARBALL" dist
scp -o StrictHostKeyChecking=no "$TARBALL" "$TARGET_USER@[$TARGET_HOST]:$DEPLOY_DIR/"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "cd $DEPLOY_DIR && echo y | ./replace_dist.sh"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "ls -ld $DEPLOY_DIR/dist"
ssh -o StrictHostKeyChecking=no $TARGET_USER@$TARGET_HOST "cd $DEPLOY_DIR && ls -t dist-*.tar.gz | tail -n +4 | xargs -r rm -f"
rm -f "$TARBALL"
'''
}
}
}
}
}
说明:
--registry=https://registry.npmmirror.com:npmmirror 镜像有 IPv6(实测),npm 官方源慢且不稳;SASS_BINARY_SITE:给老项目常见的 node-sass 准备的,从国内镜像拉预编译二进制,避免去 GitHub 下载失败;项目没用到它也无害;ls -ld $DEPLOY_DIR/dist:看时间戳确认真的替换了;- 最后一条清理旧压缩包只留 3 个(脚本自己生成的备份目录不动);
- 第一次
npm install要下全量依赖(几分钟),之后有缓存越跑越快。
8.3 NIX 前端:改 4 个值 + 查一次 PATTERN
| 变量 | NIX 的值 |
|---|---|
GIT_URL |
http://[<Gogs公网IPv6>]:<Gogs端口>/<项目组>/nix-ui.git |
BRANCH |
feature-1.2 |
DEPLOY_DIR |
</home/web/web-ui/nixs> |
| 压缩包前缀 | TARBALL="dist-nix-ui-$TS.tar.gz" |
外加 8.1 的第 3 点:head -6 确认 NIX 那份 replace_dist.sh 的 PATTERN 指向 dist-nix-ui-*。
部署完浏览器 Ctrl+F5 强刷验证(静态文件有缓存)。
9. IPv6 环境专项
我们的内网只有 IPv6 出网,访问 IPv4-only 的地址是"黑洞"------不立刻报错,而是一直卡到超时。整个部署过程一半的坑源于此,集中讲透。
9.1 镜像站 IPv6 实测汇总(2026-09)
| 用途 | 站点 | 结论 |
|---|---|---|
| Jenkins 更新源 | 清华/中科大/北外/上交/华为云/腾讯云/阿里云 | ❌ 更新元数据(/jenkins/updates/)全部下线,教程全过时 |
| Jenkins 更新源 | 官方 updates.jenkins.io |
✅ 有 IPv6(Azure),插件文件 302 自动调度,全链路可通 |
| Maven 依赖 | 阿里云镜像 | ❌ 无 IPv6 |
| Maven 依赖 | 腾讯云镜像 | ✅ 有 IPv6(settings.xml 用它) |
| Maven 依赖 | 中央仓库 repo.maven.apache.org | ✅ 走 Cloudflare,有 IPv6(不开镜像也能用,就是慢) |
| Maven 本体自动安装 | archive.apache.org |
❌ 新版路径 404(Jenkins 已知问题,与网络无关) |
| Node 本体 | nodejs.org / 腾讯 nodejs-release |
✅ 都有 IPv6 |
| npm 包 | registry.npmmirror.com |
✅ 有 IPv6;腾讯 npm 路径 404 |
排错时的一个小陷阱:
dig/nslookup输出里混进的 DNS 服务器自身地址(比如2400:3200::1是阿里 DNS)别误认成"镜像站有 IPv6"。要直接测目标域名。
9.2 Java 默认"IPv4 优先",两处都要修
现象:curl 3 秒能通官方源,Jenkins 界面却报 "Unable to connect"。
原理 :updates.jenkins.io 同时有 IPv4 和 IPv6 地址。curl 是双栈并发(Happy Eyeballs),IPv4 不通立刻换 IPv6;Java 默认优先拿 IPv4 去连------IPv4 黑洞不拒绝也不响应,于是卡到超时。
修法是让 Java 优先走 IPv6,而且要修两个进程:
① Jenkins 主进程(systemd 配置,一次配好):
bash
sudo mkdir -p /etc/systemd/system/jenkins.service.d
sudo tee /etc/systemd/system/jenkins.service.d/override.conf >/dev/null <<'EOF'
[Service]
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.net.preferIPv6Addresses=true"
EOF
sudo systemctl daemon-reload
sudo systemctl restart jenkins
# 验证(应输出 preferIPv6Addresses=true)
ps -ef | grep -i jenkins | grep -v grep | grep -o 'preferIPv6Addresses=true'
💡 这里有个 systemd 的坑:
sudo systemctl edit jenkins打开的编辑器里,满屏#注释全是参考内容,底部藏着"此线以下保存时会被丢弃"------配置必须写在文件最顶部 。写进注释堆里,:wq之后内容跟着注释一起被扔,看起来就像"老被覆盖"。用上面tee的写法根本没有 vim,写坏不了。
② Maven 进程(每次构建的独立 Java 进程,不继承主进程的参数):在流水线 environment 里加:
groovy
MAVEN_OPTS = '-Djava.net.preferIPv6Addresses=true'
少了这行,症状是打包阶段下载依赖报 网络不可达------主进程好好的,子进程原样掉进 IPv4 黑洞。
应急备用招:把 IPv6 地址直接写进
/etc/hosts,让 Java 只剩 IPv6 可选。缺点是官方换 IP 就失效,正规修法还是上面的 JVM 参数。
9.3 ssh 和 scp 的方括号语法差异
IPv6 地址里的冒号会和"host:port"的分隔符冲突,所以各工具的规矩不同:
| 场景 | 写法 |
|---|---|
| URL(git 地址、curl) | http://[<IPv6>]:<端口>/... 必须带方括号 |
scp |
user@[<IPv6>]:/path 必须带方括号 |
ssh |
ssh user@<IPv6> 不能带 方括号(带了报 Could not resolve hostname [...]) |
| shell 命令行裸地址 | 用单引号包起来:ssh root@'<IPv6>' hostname |
这也是为什么脚本里把 TARGET 拆成 TARGET_USER 和 TARGET_HOST 两个变量------ssh 和 scp 的拼法不一样,拆开各拼各的。另外脚本里 "$TARGET_USER@[$TARGET_HOST]:$DEPLOY_DIR/" 的双引号不能省,方括号不加引号可能被 shell 当通配符解析掉。
9.4 判 Yourself:两条 curl 自查命令模板
任何"下载不通"先用它定性(元数据通不通、文件调度通不通、跳到哪):
bash
curl -sIL -o /dev/null -w "HTTP %{http_code}, %{time_total}s\n" "<元数据或页面URL>"
curl -sI -o /dev/null -w "HTTP %{http_code} -> %{redirect_url}\n" "<文件下载URL>"
- 200/302 且秒回 → 网络没问题,问题在应用配置(八成是工具名、凭据 ID、路径填错);
- 超时 → 目标大概率 IPv4-only,换 9.1 表里有 IPv6 的镜像;
- 404 → 路径死了(镜像下线/版本路径变更),换源或换版本号。
10. 附:占位符对照表
| 占位符 | 去哪找真实值 |
|---|---|
<Gogs公网IPv6> / <Gogs端口> |
Gogs 站点首页地址(跨局域网用公网 v6 地址) |
<项目组> |
Gogs 仓库地址里的组织/组名(如 AIX、NIX) |
<105的内网IPv6> / <107的内网IPv6> / <108的内网IPv6> |
FinalShell 对应连接的属性面板;或在目标机上 ip addr |
<Gogs用户名/密码> |
你登录 Gogs 网页的账号,填进凭据 gogs-cred |
<SSH私钥> |
Jenkins 机(131)上 cat ~/.ssh/id_ed25519 整段输出,填进凭据 deploy-ssh |
</home/web/web-ui/aixs> / </home/web/web-ui/nixs> |
目标机上现有部署目录(ls 一下就有) |
<107的内网IPv6> 用于 TARGET_HOST |
注意脚本里是裸地址(不带方括号) |
保留未占位的值 (无害且是理解脚本的关键):分支名 union-version-1.3(后端)/ feature-1.2(前端)、服务名 aixs / nixs、工具名 M3 / node16、凭据 ID gogs-cred / deploy-ssh。
完。四个按钮集齐之后:发版 = 点对应按钮 → 看绿 →(前端 Ctrl+F5)→ 收工。