Jenkins部署经验

纯 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 表格:

  1. 编辑 default 那一行,改成:

    复制代码
    https://updates.jenkins.io/current/update-center.json

    (注意是 /current/ 路径,老地址会 301 过去,Jenkins 能自动跟随,但直接写新地址最稳。)

  2. 保存,点旁边的 Check now(立即检查)

  3. 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

  • NameM3(流水线里 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

  • Namenode16(全小写,一字不差)
  • 取消勾选 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_NAMEsystemctl 服务名(aixsaixs.service);
  • JAR_PATH :多模块工程打包后 jar 分散在各模块的 target/ 里,必须指明启动模块 。确认办法------在目标机上 ls -lh <部署目录> 看现有 jar 文件名(暴露模块名),systemctl cat aixsExecStart 实际指向哪个文件;
  • 部署五连 :建目录 → 传 jar → 重启 → 等 5 秒查 is-active只保留最新 3 个 jar,更旧的删掉(119MB 一个,攒多了磁盘吃不消;放在重启成功之后执行,不会误删正在用的);
  • 我们的服务用带时间戳+commit 号的 jar 文件名、由启动脚本自动挑最新的拉起,所以 Jenkins 只要把新 jar 丢进目录再 restart,和原手动流程完全一致,服务器上任何现有东西都不用改

7.3 第一次构建前 30 秒核对单

  1. Tools 里 M3 建好了?(没有的话 withMaven 第一步就报 M3 not found
  2. gogs-creddeploy-ssh 两份凭据建好了?
  3. TARGET_HOST 和 ssh-copy-id 时用的地址一致?(ssh root@'<地址>' hostname 再验一遍)
  4. 心理预期:第一次构建 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 fromAIX 更省事),只改 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,读一遍它,三个细节直接决定流水线怎么写:

  1. 它自动挑目录里最新的 dist-aix-ui-*.tar.gz,不用传参数 ;但压缩包里必须带着 dist 这层目录本身 (脚本解压后检查 dist 目录存在,失败自动回滚)。所以打包命令是 tar czf 包名 dist不是把 dist 里的内容散着打;
  2. 脚本带交互确认(INTERACTIVE=true,运行时问"是否替换 (y/N)")。Jenkins 的 ssh 是非交互的,没人按键它会直接"操作已取消"------而且退出码是 0,构建显示绿色但什么都没替换 (假成功,最坑的一种)。解法:echo y | ./replace_dist.sh,喂一个 y 进去,不用改服务器上的脚本;
  3. 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.shPATTERN 指向 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_USERTARGET_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 仓库地址里的组织/组名(如 AIXNIX
<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)→ 收工。

相关推荐
刚入门的大一新生2 小时前
Linux-进程控制
linux·运维·服务器·c++
上火的金鱼妹2 小时前
K8S基础组件作用和关系整理
linux·运维·服务器·kubernetes
Vcaker2 小时前
Linux学习25-harbor私有仓库部署
linux·运维·学习
醉颜凉2 小时前
网络安全必学:粘性MAC地址(Sticky MAC)原理与应用全解析
运维·服务器·网络·安全·web安全
不懂的浪漫3 小时前
ToDesk 连接 Linux 后分辨率过低的解决方法
linux·运维·数据库
姚不倒4 小时前
etcd 学习系列(七):排障指南 —— 常见问题与解决方案
运维·etcd
广州灵眸科技有限公司4 小时前
从手动三步到上电即连:4G模组systemd开机自连方案
linux·运维·服务器·开发语言·网络·人工智能·php
Lear4 小时前
Docker 私有仓库搭建与使用
运维·自动化运维
姚不倒5 小时前
etcd 学习系列(一):从业务需求出发,理解 etcd 是什么
运维·数据库·etcd