离线部署
本文范围
这篇讲通用离线部署方法:现场摸底、依赖清单、交付包目录、校验文件、RPM/DEB、Maven、npm、Docker 镜像、内网仓库、配置模板、数据库初始化、启动顺序、证书时间端口、断点续跑、回滚、排障和验收。
不展开某个具体中间件的完整安装细节,也不替代 Docker、Harbor、Nexus、K8S、Jenkins 的专题文章。这里重点解决一个问题:没有公网时,如何把依赖、安装、验证和回滚做成闭环。
前置条件
离线部署至少要先确认三类环境:
| 环境 | 用途 | 要求 |
|---|---|---|
| 联网准备机 | 下载系统包、Maven/npm 依赖、Docker 镜像和官方二进制 | 能访问官方源、企业源或可信代理 |
| 内网仓库机 | 承载 Maven/npm 私服、镜像仓库、通用制品仓库 | 目标机器能访问,磁盘和备份已规划 |
| 目标部署机 | 真正安装业务系统 | 系统版本、CPU 架构、磁盘、端口、时间、证书和安全策略已确认 |
还要提前明确交付边界:谁准备包、谁校验包、谁导入仓库、谁执行安装、谁验收业务、失败后谁决策回滚。离线环境最怕“东西在包里但没人知道怎么用”。
先别急着安装
离线部署最容易犯的错误,就是一上来就问“安装命令是什么”。真正到了无公网、企业内网、国产化服务器上,命令往往不是第一问题,第一问题是:你带过去的东西全不全、对不对、能不能验证、失败后能不能退回来。
比如你要部署一个 Java 后端、一个前端静态页面、一个 MySQL、一个 Redis,再加几个 Docker 镜像。在线环境里缺什么就现拉一下,离线环境里不行。少一个 RPM 包、少一个 Maven 插件、少一个 npm 二进制依赖、镜像 tag 不对、证书时间不对,现场都会卡住。
所以离线部署先按这条线走:
摸清现场 -> 列依赖清单 -> 准备交付包 -> 生成校验 -> 导入依赖 -> 安装配置 -> 启动验证 -> 回滚演练 -> 交付验收这个链路最好在正文里直接画出来。离线部署最怕“准备机上看起来没问题,目标现场缺一环”,所以每个节点都要有可执行的验证命令,而不是只写交付说明。
现场执行时,至少先跑这组验证命令:
sha256sum -c checksums/SHA256SUMS
command -v java docker mysql redis-cli curl sha256sum
docker load -i images/order-service_1.8.3_linux-amd64.tar
docker images --digests | grep order-service
scripts/precheck.sh
scripts/verify.sh常见坑也都在图上能看出来:目标机器架构没采集,镜像平台拉错;校验文件不是同一版生成,现场还继续安装;SQL 初始化不幂等,失败重跑把数据写乱;回滚包没有进交付目录,失败后只能临时找旧包。生产上要把“重新出包”和“切回旧包”当成两个正式分支写进手册,不能让现场靠口头经验处理。
这里要注意,离线部署不是某个组件的安装教程。Docker、MySQL、Redis、Nginx、K8S、Harbor、Nexus 都有自己的细节。这篇只讲通用交付方法:怎么把依赖闭环、校验闭环、安装闭环、验证闭环和回滚闭环做出来。
一般需要准备三类机器:
| 机器 | 用来做什么 | 要求 |
|---|---|---|
| 联网准备机 | 下载系统包、Maven/npm 依赖、Docker 镜像,生成校验文件 | 能访问官方源、企业源或可信代理 |
| 内网仓库机 | 放 Maven/npm 私服、镜像仓库、通用制品 | 目标服务器能访问它 |
| 目标部署机 | 真正安装业务系统 | 按生产权限、生产网络、安全策略来配置 |
如果现场连内网仓库都没有,也可以只靠一个完整离线包部署。但只要不是一次性项目,建议尽快升级成“内网制品仓库 + 私有镜像仓库 + 标准交付包”。后面运维会轻松很多。
第一步:先把现场摸清楚
不要只问一句“服务器是 Linux 吗”。离线部署前,先把目标机器的系统、架构、磁盘、网络、时间、防火墙、SELinux、运行时版本都查一遍。
登录目标机后先执行:
cat /etc/os-release
uname -a
arch
lscpu
free -h
df -hT
lsblk
ip addr
ip route
timedatectl
ss -lntup
systemctl is-active firewalld 2>/dev/null || true
getenforce 2>/dev/null || true这些命令解决几个问题:
cat /etc/os-release看系统发行版,判断是 RPM 系还是 DEB 系。arch和lscpu看 CPU 架构,确认是x86_64还是aarch64。df -hT和lsblk看磁盘,确认应用、日志、数据库、镜像仓库放哪里。timedatectl看时区和时间,证书、token、集群心跳都怕时间不准。ss -lntup看端口占用,避免安装完才发现端口被占。firewalld和getenforce看安全策略,避免服务起来了但外部访问不了。
如果是国产化系统,再补几条:
rpm -qa | sort > os-rpm-list.txt 2>/dev/null || true
dpkg -l > os-deb-list.txt 2>/dev/null || true
ldd --version
java -version
node -v 2>/dev/null || true
npm -v 2>/dev/null || true
docker version 2>/dev/null || true
lscpu | grep -E 'avx|avx2|sse4' || true这里要注意,国产化适配经常不是业务代码问题,而是运行时和系统包问题。比如某些数据组件或镜像要求 AVX2,现场 CPU 不支持;某些二进制依赖 glibc 版本,目标机版本偏旧;某些镜像只有 linux/amd64,而现场是 aarch64。这些都要在交付前发现。
采集完之后,建议整理成一个环境文件:
target:
os: "Kylin V10 SP3"
arch: "x86_64"
package_manager: "dnf"
systemd: true
selinux: "enforcing"
firewall: "firewalld"
timezone: "Asia/Shanghai"
network:
internet: false
internal_repo: true
proxy: false
disks:
app: "/opt/order"
data: "/data/order"
logs: "/data/logs/order"
ports:
- 80
- 443
- 8080
- 3306
- 6379后面准备包、写脚本、开端口、做验收,都以这个文件为准。
第二步:把依赖清单列完整
离线部署不怕包多,怕的是不知道少了什么。交付前一定要有一份依赖清单,最好既能给人看,也能给脚本读。
一个常见业务系统至少有这些依赖:
| 类型 | 例子 | 要记录什么 |
|---|---|---|
| 系统包 | docker-ce、nginx、openssl、chrony | 包名、版本、架构、来源、依赖 |
| 运行时 | JDK、Node.js、Python、glibc | 版本、安装目录、环境变量 |
| 构建依赖 | Maven、Gradle、npm、pnpm 包 | lockfile、本地仓库、私服地址 |
| 应用制品 | jar、war、前端 dist | 构建号、Git commit、校验值 |
| 容器镜像 | 应用镜像、MySQL、Redis、Nginx | image、tag、digest、platform |
| 初始化数据 | SQL、字典、租户、账号 | 执行顺序、幂等条件、回滚边界 |
| 安全材料 | TLS 证书、CA、license、密钥 | 过期时间、用途、权限 |
推荐做一个 manifest.yaml:
delivery:
name: "order-platform"
version: "1.8.3"
build: "20260709.1"
git_commit: "a1b2c3d"
created_at: "2026-07-09T10:00:00+08:00"
runtime:
os_family:
- "rhel-like"
- "debian-like"
arch:
- "linux/amd64"
java: "21"
node: "24 LTS"
artifacts:
- type: "jar"
file: "apps/order-service-1.8.3.jar"
sha256: "..."
- type: "docker-image"
image: "registry.internal/order/order-service:1.8.3"
file: "images/order-service_1.8.3_linux-amd64.tar"
platform: "linux/amd64"
sha256: "..."
ports:
- name: "web"
port: 8080
protocol: "tcp"
- name: "mysql"
port: 3306
protocol: "tcp"
rollback:
previous_version: "1.8.2"
config_backup: "rollback/config-1.8.2.tar.gz"
image_backup: "rollback/images-1.8.2.txt"这里要注意两点。
第一,latest 不能当生产版本。nginx:latest、redis:latest、order-service:latest 这种写法,在离线交付里很危险。你过一周重新导出,可能已经不是同一个镜像。
第二,构建环境也要记录。很多人只记录应用版本,不记录 JDK、Node、Maven、npm 版本,结果现场重新构建时产物不一致。
第三步:交付包要有固定目录
离线包不要随手压一个 package.zip。现场最怕打开包以后不知道哪个文件先执行、哪个是配置模板、哪个是旧版本备份。
推荐这样放:
offline-delivery-order-platform-1.8.3/
manifest.yaml
README.md
checksums/
SHA256SUMS
packages/
rpm/
docker/
nginx/
tools/
deb/
nginx/
tools/
maven/
repository/
settings-offline.xml
npm/
cache/
npmrc-offline
images/
order-service_1.8.3_linux-amd64.tar
mysql_8.4_linux-amd64.tar
redis_7.4_linux-amd64.tar
apps/
order-service-1.8.3.jar
web-dist-1.8.3.tar.gz
configs/
templates/
application.yml.tpl
nginx.conf.tpl
env/
prod.env
sql/
00_schema.sql
01_seed.sql
99_verify.sql
certs/
ca.crt
server.crt
server.key.example
scripts/
lib.sh
precheck.sh
import.sh
install.sh
render-config.sh
init-db.sh
start.sh
verify.sh
rollback.sh
rollback/
README.md
docs/
ports.md
acceptance.md
troubleshooting.md目录放好以后,在联网准备机上生成校验文件:
cd offline-delivery-order-platform-1.8.3
find . -type f \
! -path './checksums/*' \
! -path './rollback/*' \
-print0 | sort -z | xargs -0 sha256sum > checksums/SHA256SUMS这条命令会把交付包里的文件逐个算出 SHA256。到了目标机,先验证:
cd offline-delivery-order-platform-1.8.3
sha256sum -c checksums/SHA256SUMS正常情况下,每一行后面都应该是 OK。如果有失败,先不要安装,先看是哪一个文件:
sha256sum -c checksums/SHA256SUMS | grep -v ': OK'一般就三种原因:
- 文件传输坏了。
- 文件被人改过。
- 校验文件不是这一版生成的。
离线环境里,校验失败还继续安装,后面出了问题很难判断是部署问题还是包本身有问题。
第四步:RPM 和 DEB 包怎么准备
RPM 系统
Kylin、openEuler、RHEL、Rocky、Alma、CentOS Stream 这类系统,一般用 RPM 包。联网准备机上先把主包和依赖下载完整。
比如准备 Docker 相关包:
sudo dnf install -y dnf-plugins-core
mkdir -p packages/rpm/docker
dnf download --resolve --destdir packages/rpm/docker docker-ce docker-ce-cli containerd.io这里 --resolve 的作用是把依赖一起下载下来,不然你只带一个主包,到现场安装时会报缺依赖。
下载完先看一下:
ls -lh packages/rpm/docker到了目标机,先做测试安装:
cd packages/rpm/docker
sudo rpm -ivh ./*.rpm --test如果这里报缺依赖,就说明包没带全,要回到联网准备机补齐,不要直接 --nodeps。
正式安装:
if command -v dnf >/dev/null 2>&1; then
sudo dnf localinstall -y ./*.rpm
elif command -v yum >/dev/null 2>&1; then
sudo yum localinstall -y ./*.rpm
else
echo "no dnf/yum found"
exit 1
fi验证:
docker version
systemctl status docker --no-pager如果 docker version 有 Client 但没有 Server,通常是 Docker 服务没启动,继续看:
systemctl status docker --no-pager
journalctl -u docker -n 100 --no-pager这里要注意,--force 和 --nodeps 不要写进正式脚本。它们能让安装“看起来过去了”,但运行时会把问题还给你。
DEB 系统
Ubuntu、Debian、UOS 这类系统一般用 DEB 包。小规模可以准备 .deb,复杂一点建议直接做内网 APT 仓库。
如果只是下载一个包:
mkdir -p packages/deb/nginx
cd packages/deb/nginx
apt-get download nginx如果要把依赖也闭环,推荐用 apt-offline:
# 在离线目标机生成需求文件
sudo apt-offline set offline.sig --install-packages nginx
# 在联网机器下载依赖包
apt-offline get offline.sig --bundle offline-nginx.zip
# 回到离线目标机安装
sudo apt-offline install offline-nginx.zip验证:
nginx -v
systemctl status nginx --no-pager如果用 dpkg -i ./*.deb 报 dependency problems,不要在现场反复执行。说明包不全,回联网准备机重新补依赖。
第五步:Maven 依赖要先预热
Java 项目最好不要在生产服务器上构建。更稳的做法是 CI 已经打出 jar/war,现场只负责校验、安装和启动。
如果内网 CI 必须重新构建,就要把 Maven 依赖提前准备好。
联网准备机执行:
mvn -B -U \
-Dmaven.repo.local=./maven/repository \
dependency:go-offline这里 -Dmaven.repo.local=./maven/repository 是把依赖下载到交付包里的本地仓库,后面可以整体打包带到内网。
下载完后,立刻做一次离线构建验证:
mvn -B -o \
-Dmaven.repo.local=./maven/repository \
clean package-o 就是 offline。能在联网机器上用 offline 模式构建成功,才说明这个 Maven 仓库基本可用。
如果企业有内网 Maven 私服,准备一个 settings-offline.xml:
<settings>
<localRepository>/opt/offline-delivery/maven/repository</localRepository>
<mirrors>
<mirror>
<id>internal-maven</id>
<mirrorOf>*</mirrorOf>
<url>http://repo.internal/repository/maven-public/</url>
</mirror>
</mirrors>
</settings>构建时指定它:
mvn -B -s maven/settings-offline.xml clean package如果失败,先这样排查:
mvn -o -X -Dmaven.repo.local=./maven/repository clean package
find ./maven/repository -name "*.lastUpdated"
mvn -Dmaven.repo.local=./maven/repository dependency:tree常见坑:
- 只下载了业务依赖,没下载 Maven plugin。
- 本地仓库里有
.lastUpdated,说明之前下载失败过。 settings.xml没有把所有仓库都指向内网私服。- 构建机 JDK 和现场 JDK 不一致。
生产建议:Maven 私服不要让每个项目各配各的外网仓库。统一走 Nexus、Artifactory 或同类仓库管理器,把 hosted、proxy、group 规划好。
第六步:npm 离线构建要看 lockfile
前端项目离线构建,先看有没有 lockfile。没有 package-lock.json、pnpm-lock.yaml 或 yarn.lock,就不要谈可重复构建。
npm 项目在 CI 里一般这样构建:
npm ci
npm run buildnpm ci 的特点是严格按 package-lock.json 安装。如果 lock 和 package.json 不一致,它会直接失败,这反而是好事,说明依赖没有被偷偷改掉。
联网准备机预热缓存:
rm -rf node_modules
npm ci --cache ./npm/cache --prefer-offline然后做离线验证:
rm -rf node_modules
npm ci --cache ./npm/cache --offline
npm run build如果这一步能通过,再把 npm/cache 带进交付包。
内网环境一般会配 .npmrc:
registry=http://repo.internal/repository/npm-group/
cache=/opt/offline-delivery/npm/cache
prefer-offline=true
cafile=/etc/pki/ca-trust/source/anchors/internal-ca.crt这里要注意,不建议长期用 strict-ssl=false。临时自签证书环境可以用来验证,但正式环境应该把内网 CA 放到系统信任链,或者用 cafile 指定。
如果 npm ci --offline 失败,按这个顺序看:
npm config list
npm cache verify --cache ./npm/cache
npm ci --offline --cache ./npm/cache --loglevel verbose常见坑:
- cache 没预热完整。
- lockfile 没提交。
- 某些依赖的
postinstall脚本还要访问外网下载二进制。 - 内网证书没有被 npm 信任。
生产建议:前端最好在 CI 里产出 dist.tar.gz,现场只负责解压到 Nginx 静态目录。生产服务器上临时 npm install,可控性很差。
第七步:Docker 镜像不能只看 tag
容器镜像离线交付,至少要记录四件事:镜像名、tag、digest、platform。
联网准备机拉镜像:
docker pull --platform linux/amd64 nginx:1.27
docker image inspect nginx:1.27 --format '{{json .RepoDigests}}'这里 --platform linux/amd64 是为了防止多架构镜像拉错。现场如果是 ARM,就改成 linux/arm64,不要混用。
导出镜像:
mkdir -p images
docker save -o images/nginx_1.27_linux-amd64.tar nginx:1.27
sha256sum images/nginx_1.27_linux-amd64.tar到了目标机,先校验,再导入:
sha256sum -c checksums/SHA256SUMS
docker load -i images/nginx_1.27_linux-amd64.tar
docker images nginx正常情况下,docker images nginx 应该能看到 nginx 和 1.27 这一行。
如果 docker load 之后只看到 image ID,或者 tag 不符合预期,不要随手拿最新镜像 ID 打 tag。更稳的做法是按 manifest.yaml 里的目标 tag 处理:
TARGET_IMAGE="registry.internal/base/nginx:1.27"
docker load -i images/nginx_1.27_linux-amd64.tar | tee docker-load.log
docker tag nginx:1.27 "$TARGET_IMAGE"
docker image inspect "$TARGET_IMAGE" >/dev/null这里还有一个和原理相关的点要记住:Docker 镜像是分层的,容器启动后会在镜像层上面加一层可写层。Overlay2 和 CoW 的意义不是让你背概念,而是提醒你两件事:
- 多个镜像可以复用相同层,所以磁盘占用和你看到的单个镜像大小不完全一样。
- 容器可写层不适合放业务数据,容器删了或者重建了,这一层就可能丢。
所以数据库文件、上传文件、日志,都不要写在容器可写层里。要挂载到宿主机目录或 volume。
docker run -d \
--name nginx-demo \
-p 8080:80 \
-v /data/nginx/html:/usr/share/nginx/html:ro \
nginx:1.27验证:
docker ps
curl -I http://127.0.0.1:8080
docker logs --tail=50 nginx-demo访问后如果看到 HTTP 200 或 304,说明入口通了。如果连接失败,先看容器是否运行,再看端口映射和防火墙。
第八步:内网仓库比到处拷包稳
小环境用 docker save/load 可以解决问题。项目一多、服务器一多、发布频率一高,就应该上内网仓库。
临时验证可以跑一个最小 Registry:
docker run -d \
--name registry \
--restart=always \
-p 5000:5000 \
-v /data/registry:/var/lib/registry \
registry:3推送一个镜像:
docker tag order-service:1.8.3 registry.internal:5000/order/order-service:1.8.3
docker push registry.internal:5000/order/order-service:1.8.3
curl http://registry.internal:5000/v2/_catalog访问 _catalog 后应该能看到仓库列表。如果看不到,先查 Registry 容器日志:
docker logs --tail=100 registry生产环境一般不要只靠这个最小 Registry。更推荐 Harbor 或企业制品平台,因为它们能解决权限、项目隔离、机器人账号、保留策略、漏洞扫描、复制同步和审计问题。
内网制品仓库一般这样分工:
| 仓库 | 解决什么问题 |
|---|---|
| Maven group 仓库 | Java 依赖统一入口 |
| npm group 仓库 | 前端依赖统一入口 |
| Docker/Harbor | 镜像统一存储和分发 |
| Generic 仓库 | jar、tar.gz、SQL、离线包 |
| 文件服务器 | 临时交付包和历史归档 |
这里要注意,内网仓库也要备份。否则离线环境里仓库一坏,所有发布都会停。
第九步:配置不要手改,统一模板渲染
现场手改配置最容易留下隐患。今天改了一个 IP,明天改了一个密码,最后谁也不知道线上到底是什么。
配置建议用模板:
server:
port: ${APP_PORT}
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DB}?useSSL=false&serverTimezone=Asia/Shanghai
username: ${MYSQL_USER}
password: ${MYSQL_PASSWORD}
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}环境变量放在 prod.env:
APP_PORT=8080
MYSQL_HOST=10.10.10.20
MYSQL_PORT=3306
MYSQL_DB=order
MYSQL_USER=order_app
MYSQL_PASSWORD=change-me
REDIS_HOST=10.10.10.21
REDIS_PORT=6379渲染配置:
set -a
. configs/env/prod.env
set +a
envsubst < configs/templates/application.yml.tpl > /etc/order/application.yml渲染完要检查有没有变量漏掉:
grep -R '\${' /etc/order && echo "template variable not rendered" && exit 1如果没有输出,说明变量基本渲染完了。接着检查权限:
ls -l /etc/order
chmod 600 /etc/order/application.yml生产建议:密码、证书私钥、license 不要放进通用交付包。可以由现场安全渠道提供,脚本只负责检查文件是否存在、权限是否正确。
第十步:数据库初始化要能重复执行
数据库初始化脚本不能写成“执行一次就不能再执行”。现场安装失败后重跑脚本是常事,SQL 必须尽量幂等。
目录可以这样拆:
sql/
00_create_database.sql
01_schema.sql
02_seed_data.sql
03_account.sql
99_verify.sql执行:
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u root -p < sql/00_create_database.sql
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u root -p "$MYSQL_DB" < sql/01_schema.sql
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u root -p "$MYSQL_DB" < sql/02_seed_data.sql
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u root -p "$MYSQL_DB" < sql/99_verify.sql00_create_database.sql 可以这样写:
CREATE DATABASE IF NOT EXISTS order_platform DEFAULT CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS sys_version (
version varchar(64) PRIMARY KEY,
applied_at timestamp DEFAULT current_timestamp
);验证脚本里可以放几条轻量查询:
SELECT DATABASE();
SELECT COUNT(*) FROM sys_version;这里要注意,数据库回滚要提前说清楚。新增表、新增可空字段,通常可以保留;删字段、改类型、数据迁移,就必须提前备份并写好回退方案。
第十一步:启动顺序不要乱
离线部署时不要一口气把所有服务都拉起来。按依赖顺序来:
系统依赖 -> 时间同步 -> 证书 -> 数据库 -> Redis/MQ/注册中心 -> 应用服务 -> Nginx/网关 -> 验证每一步都要有验证命令。
时间:
timedatectl
chronyc tracking 2>/dev/null || true数据库:
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p -e 'select 1'Redis:
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping应用:
systemctl status order-service --no-pager
curl -fsS http://127.0.0.1:8080/actuator/health端口:
ss -lntup | grep -E ':3306|:6379|:8080'如果端口监听了,但接口不通,不要马上怀疑防火墙。先看应用日志和依赖连接,很多时候是应用启动了,但数据库、Redis、MQ 还没连上。
journalctl -u order-service -n 200 --no-pager
tail -n 200 /data/logs/order/app.log第十二步:证书、时间、端口要单独验
离线环境经常没有公网 NTP,也经常使用内网 CA。证书和时间最好单独检查,不要混在应用启动报错里才看。
证书检查:
openssl x509 -in certs/server.crt -noout -subject -issuer -dates
openssl verify -CAfile certs/ca.crt certs/server.crt第一条看证书主体、签发者、有效期。第二条看证书链能不能验证通过。
端口检查:
ss -lntup
nc -vz 10.10.10.20 3306
curl -vk https://app.internal/health如果 nc 连接失败,一般从三处查:
- 服务有没有监听。
- 防火墙有没有放行。
- 路由或安全组有没有拦截。
防火墙检查:
systemctl status firewalld 2>/dev/null || true
firewall-cmd --list-ports 2>/dev/null || true
iptables -S 2>/dev/null | head生产建议:脚本可以检查防火墙,也可以输出需要开放的端口,但不要静默关闭防火墙。安全策略应该走变更。
第十三步:脚本要能断点续跑
离线部署脚本不是把命令堆在一起。一个好脚本至少要做到:失败能停、日志能看、重复执行不破坏现场、某一步完成后能跳过。
公共库可以这样写:
#!/usr/bin/env bash
set -euo pipefail
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
fail() {
log "ERROR: $*"
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "command not found: $1"
}
mark_done() {
mkdir -p .state
touch ".state/$1.done"
}
is_done() {
test -f ".state/$1.done"
}预检查脚本:
#!/usr/bin/env bash
set -euo pipefail
. scripts/lib.sh
log "check os"
cat /etc/os-release
log "check arch"
ARCH="$(arch)"
test "$ARCH" = "x86_64" || fail "unsupported arch: $ARCH"
log "check disk"
df -h /opt /data
log "check port"
for p in 80 443 8080 3306 6379; do
if ss -lnt | awk '{print $4}' | grep -Eq ":$p$"; then
fail "port already in use: $p"
fi
done
log "check checksum"
sha256sum -c checksums/SHA256SUMS导入镜像脚本:
#!/usr/bin/env bash
set -euo pipefail
. scripts/lib.sh
if is_done images_imported; then
log "images already imported, skip"
exit 0
fi
for image_tar in images/*.tar; do
log "load image: $image_tar"
docker load -i "$image_tar"
done
docker images
mark_done images_imported第一次失败后,先看 .state:
ls -lah .state如果某一步确认要重跑,删除对应状态文件:
rm -f .state/images_imported.done
scripts/import.sh这里要注意,状态文件不是为了偷懒,而是为了让现场安装可控。没有状态管理的脚本,失败后很容易越修越乱。
第十四步:回滚先设计,再上线
离线部署回滚不是“把旧包拷回去”这么简单。应用、配置、镜像、数据库都要有边界。
应用可以用版本目录和软链:
VERSION=1.8.3
mkdir -p /opt/order/releases/$VERSION
cp apps/order-service-$VERSION.jar /opt/order/releases/$VERSION/
ln -sfn /opt/order/releases/$VERSION /opt/order/current
systemctl restart order-service回滚应用:
PREVIOUS=1.8.2
ln -sfn /opt/order/releases/$PREVIOUS /opt/order/current
systemctl restart order-service配置回滚前先备份:
tar -czf rollback/config-$(date +%F-%H%M%S).tar.gz /etc/order镜像回滚:
docker tag registry.internal/order/order-service:1.8.2 registry.internal/order/order-service:current
docker compose up -d数据库回滚要分情况:
| 变更 | 回滚策略 |
|---|---|
| 只新增表 | 多数可以保留,应用回滚即可 |
| 新增可空字段 | 旧应用要验证能否忽略 |
| 删除字段 | 必须有备份和逆向脚本 |
| 数据迁移 | 必须有快照、校验和人工确认点 |
回滚后也要验证:
scripts/verify.sh
curl -fsS http://127.0.0.1:8080/actuator/health不要只看 systemctl restart 成功。服务启动成功不等于业务可用。
常见故障怎么查
安装包缺依赖
现象:
Error: Problem: conflicting requests
nothing provides xxx needed by yyy定位:
rpm -ivh package.rpm --test
rpm -qpR package.rpm处理:回联网准备机补依赖,重新生成交付包和校验文件。
Maven 离线构建失败
现象:
Cannot access central in offline mode
Plugin ... was not found定位:
mvn -o -X -Dmaven.repo.local=./maven/repository clean package
find ./maven/repository -name "*.lastUpdated"处理:联网机重新执行 dependency:go-offline,并跑一次完整构建。
npm 离线构建失败
现象:
request to ... failed: cache mode is 'only-if-cached'定位:
npm config list
npm cache verify --cache ./npm/cache
npm ci --offline --cache ./npm/cache --loglevel verbose处理:重新预热 cache;如果是 postinstall 下载二进制,要把二进制也纳入交付。
Docker 镜像导入后 tag 不对
定位:
docker load -i images/app.tar | tee docker-load.log
docker images
docker image inspect IMAGE_ID处理:按 manifest.yaml 里的目标镜像名重新 tag,不要随便取最新镜像 ID。
端口监听了但服务不可用
定位:
ss -lntup | grep 8080
curl -v http://127.0.0.1:8080/actuator/health
journalctl -u order-service -n 200 --no-pager
tail -n 200 /data/logs/order/app.log常见原因:
- 应用启动了,但数据库没连上。
- 健康检查路径不对。
- Nginx 转发到错误端口。
- 证书或时间问题导致 TLS 握手失败。
落地工程深水区
离线部署的深水区只有一个核心:现场没有临时补救空间,所以每个依赖和每个动作都要提前闭环。
依赖完整性:系统包、Maven 插件、npm 二进制依赖、Docker 镜像、JDK、数据库客户端、证书工具都要列清单。只跑一次成功不算,换一台干净机器从零安装也成功才算。
架构和 CPU:x86_64、aarch64、国产化系统、glibc 版本、OpenSSL 版本都会影响二进制可用性。准备包时把 arch、ldd --version、系统发行版写进交付记录。
校验和可信来源:所有 tar、rpm、deb、jar、tgz、镜像包都要有 sha256。现场导入前先校验:
sha256sum -c checksums/SHA256SUMS没有校验值的包,不应该进入生产离线环境。
配置和密钥:配置用模板渲染,敏感值由现场安全流程注入。不要把真实密码、token、证书私钥写进交付包样例、脚本注释或日志。
回滚和数据兼容:离线部署失败时,不能指望重新下载修复包。每次上线必须保留上一版应用包、配置、镜像、数据库变更脚本和恢复步骤。数据库变更必须先判断是否可逆;不可逆就只能做前滚修复和备份恢复预案。
验收和长期维护:交付不是“服务启动”。还要验端口、健康检查、日志、监控、备份、证书过期、时间同步、磁盘增长和二次升级路径。没有二次升级方案,第一次离线部署成功也只是一次性手工活。
交付验收清单
交付完成后,不要只说“已经装好了”。按清单验收。
| 项目 | 验收方式 |
|---|---|
| 交付包完整 | sha256sum -c checksums/SHA256SUMS 全部 OK |
| 版本可追踪 | manifest.yaml 有版本、构建号、commit、生成时间 |
| 系统包完整 | 离线机能执行 dnf localinstall 或 apt-offline install |
| Maven/npm 闭环 | 离线模式构建成功,或制品已在 CI 构建完成 |
| 镜像闭环 | docker load 成功,tag、digest、platform 记录完整 |
| 配置可渲染 | 模板变量无残留,敏感文件权限正确 |
| 数据库可初始化 | SQL 可重复执行或有明确幂等判断 |
| 中间件可验证 | 数据库、Redis、MQ、注册中心均有命令级验证 |
| 端口可访问 | ss、nc、curl 验证通过 |
| 证书有效 | openssl verify 和过期时间检查通过 |
| 时间同步 | 所有节点时区和时间误差符合要求 |
| 日志可定位 | systemd、容器、应用日志路径明确 |
| 回滚可执行 | 旧版本应用、配置、镜像、数据边界清楚 |
上线后再执行一遍:
systemctl status order-service --no-pager
curl -fsS http://127.0.0.1:8080/actuator/health
ss -lntup | grep 8080
tail -n 100 /data/logs/order/app.log
df -h
free -h如果这些命令都能跑通,离线部署才算有了基本交付质量。
常用命令速查
系统信息:
cat /etc/os-release
arch
lscpu
df -hT
timedatectl
ss -lntup校验:
sha256sum file.tar
sha256sum -c checksums/SHA256SUMSRPM:
dnf download --resolve --destdir rpms package-name
dnf localinstall -y ./*.rpm
rpm -ivh package.rpm --test
rpm -qpR package.rpmDEB:
apt-offline set offline.sig --install-packages nginx
apt-offline get offline.sig --bundle offline.zip
apt-offline install offline.zip
dpkg -i ./*.debMaven:
mvn -B -Dmaven.repo.local=./maven/repository dependency:go-offline
mvn -B -o -Dmaven.repo.local=./maven/repository clean package
mvn dependency:treenpm:
npm ci --cache ./npm/cache --prefer-offline
npm ci --cache ./npm/cache --offline
npm cache verify --cache ./npm/cacheDocker:
docker pull --platform linux/amd64 nginx:1.27
docker save -o images/nginx_1.27_linux-amd64.tar nginx:1.27
docker load -i images/nginx_1.27_linux-amd64.tar
docker images --digests
docker inspect nginx:1.27证书和端口:
openssl x509 -in server.crt -noout -subject -issuer -dates
openssl verify -CAfile ca.crt server.crt
nc -vz host 443
curl -vk https://host/health官方文档入口
- Docker CLI:
docker image save、docker image load:https://docs.docker.com/reference/cli/docker/image/save/ - Docker Distribution / Registry:https://distribution.github.io/distribution/
- Kubernetes kubeadm 镜像命令:https://kubernetes.io/docs/reference/setup-tools/kubeadm/
- Maven Dependency Plugin
go-offline:https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html - Maven settings 与 mirror:https://maven.apache.org/settings.html
- npm
ci与 config:https://docs.npmjs.com/cli/v11/commands/npm-ci/ - DNF reposync:https://dnf-plugins-core.readthedocs.io/en/latest/reposync.html
- Debian
apt-offline:https://manpages.debian.org/bookworm/apt-offline/apt-offline.8.en.html - Harbor 安装与离线安装包:https://goharbor.io/docs/main/install-config/
- Sonatype Nexus Repository 类型:https://help.sonatype.com/en/repository-types.html
sha256summan page:https://man7.org/linux/man-pages/man1/sha256sum.1.html
正式落地时要重新查看目标软件当天的官方文档、企业安全基线、国产化系统兼容矩阵和内网仓库策略。离线环境最怕凭记忆安装,越是没有公网,越要把依赖、校验、验证和回滚写清楚。
