部署运维总清单
本文范围
这篇是部署运维入口组的总控清单,不替代 Docker、Nginx、数据库、Redis、MQ、监控告警、证书和备份恢复专题。它负责把一次发布从申请、上线前检查、执行发布、发布后验证、回滚、观察、巡检和复盘串成一条可执行链路。
本文会覆盖:
- 发布前需要确认的服务器、网络、制品、配置、数据库、中间件、证书和监控。
- 发布中对 Systemd/Jar、Docker Compose、K8S、Nginx、数据库变更的关键门禁。
- 发布后健康检查、日志、业务冒烟、监控指标和告警窗口。
- 回滚触发条件、Jar/Docker/K8S/Nginx/数据库回滚边界。
- 巡检、长期治理、风险项速查、模板和命令速查。
- 中国大陆公网、企业内网、完全离线环境的依赖闭环和工具包要求。
本文不讲完整平台选型,也不把每个中间件的部署细节展开。清单的目标是现场不漏项,具体命令背后的专项知识仍然回到对应文章。
前置条件
使用这份清单前,至少要明确发布系统、服务名、目标环境、版本号、构建产物、配置差异、数据库变更、回滚负责人和验证人。发布现场需要能访问服务器、制品仓库、镜像仓库、监控系统和日志系统;离线环境还要准备工具包、校验文件和恢复脚本。
先确认基础工具:
command -v systemctl journalctl ss curl docker nginx mysql redis-cli openssl
git --version 2>/dev/null || true
kubectl version --client 2>/dev/null || true如果某个命令不存在,不要在生产窗口临时去公网安装。先走企业软件源、离线工具包或平台内置能力。本文示例中的域名、数据库地址、Redis 地址、Kafka 地址、证书路径和命名空间都是占位,落地时替换为受控变量。
本文怎么用
这篇可以当成你上线时放在手边的总控稿。它不是 Docker、Nginx、MySQL、Redis、MQ、监控告警或证书的完整教程,而是把这些东西在一次发布里怎么串起来讲清楚。
很多生产事故不是因为某个命令不会敲,而是因为发布现场漏了一步:包是不是这一个、配置是不是生产的、库有没有备份、Nginx 有没有 reload、告警有没有人看、出问题能不能退。我们先把这些散点收拢起来。
一次发布最少要回答五个问题:
- 上线前要检查什么。
- 发布中要盯什么。
- 发布后怎么证明真的正常。
- 出问题时怎么回滚。
- 事后怎么巡检、复盘和补流程。
真正的发布不是“把包扔到服务器上重启一下”。一个可靠发布至少要留下这些证据:发布版本、构建产物、配置差异、数据库备份、回滚入口、日志位置、监控窗口、验证结果、负责人和复盘记录。后面所有命令,都是为了拿到这些证据。
本文命令默认面向 Linux 服务器。不同发行版、容器环境、K8S 集群、云厂商托管服务会有差异,落地时先替换主机名、端口、服务名、镜像名、命名空间和凭证变量。
command -v systemctl
command -v journalctl
command -v ss
command -v curl
command -v docker
command -v nginx
command -v mysql
command -v redis-cli这里要注意,command -v 不是为了好看,它是在确认发布现场能不能用这些基础工具。如果某个命令没有输出,就说明当前机器没有这个工具,或者它不在 PATH 里。生产环境不要临时到公网下载,先找内网软件源、离线工具包或运维基线脚本补齐。
部署运维总清单的第一原则:所有高风险动作都要能回答“执行前状态是什么、执行了什么、执行后怎么验证、失败后怎么退回”。
总流程
一次发布建议按下面七个阶段执行。不要一上来就重启服务,先把发布当成闯关,每过一关都要留下能复查的记录。
总流程可以先理解成一组门禁:每个阶段都要留下证据,下一步才能继续。
先用下面这张全链路地图把门禁背后的对象补齐,后面的表格再把它拆成阶段目标和必须保留的证据。
部署运维全链路地图
上面的总流程解决“阶段顺序”,但真实生产发布还要把代码、制品、配置、数据、依赖、入口、验证和回滚串起来。下面这张图可以当成总清单的主索引:任何一个节点没有证据,都不应该直接跳到执行发布。
这张图最重要的约束是:发布不是“把新包放上去”这一个动作。制品没有指纹,回滚时就不知道退到哪一份;配置没有 diff,故障时就分不清是代码问题还是配置问题;数据库没有备份和兼容策略,应用包退回去也可能读不懂新数据。
发布前至少保留这些验证证据:
git rev-parse --short HEAD
git status --short
sha256sum target/*.jar
docker image inspect registry.example.com/demo/app:20260709 --format '{{.Id}} {{.RepoDigests}}'
diff -u config/application-prod.yml config/application-prod.yml.new
nginx -t
curl -fsS http://127.0.0.1:8080/actuator/health常见坑是只盯着发布工具的绿色状态。流水线成功不代表入口层、数据库变更、消息消费、日志采集、监控告警都正常。生产约束是:不要使用无法追溯的 latest;数据库变更必须提前定义回滚或补偿边界;发布窗口内不要临时改配置、临时换包、临时跑 SQL;需要静默告警时必须设置恢复时间和负责人,不能把告警长期关掉。
| 阶段 | 目标 | 必须留下的证据 |
|---|---|---|
| 1. 发布申请 | 明确发布内容、影响范围、时间窗口、负责人 | 发布单、需求/缺陷链接、版本号、回滚负责人 |
| 2. 上线前检查 | 确认服务器、网络、制品、配置、数据和依赖都具备发布条件 | 检查清单、命令输出、备份文件、回滚方案 |
| 3. 发布冻结 | 停止临时改配置、临时改 SQL、临时换包 | 冻结时间、最终 commit、最终镜像 tag、审批记录 |
| 4. 执行发布 | 按固定步骤部署应用、配置、Nginx、数据库变更 | 执行命令、流水线记录、服务状态、日志 |
| 5. 发布后验证 | 用健康检查、业务冒烟、日志、监控证明发布有效 | 验证清单、接口结果、监控窗口、日志截取 |
| 6. 回滚或观察 | 达到回滚条件就回滚,否则进入观察期 | 回滚命令、回滚验证、观察截止时间 |
| 7. 复盘归档 | 沉淀风险、补监控、补脚本、补清单 | 复盘记录、问题单、后续动作负责人 |
推荐把发布单做成固定模板。字段看起来多,但现场真正出问题时,你会感谢这些字段,因为它能立刻告诉你找谁、退到哪一版、看哪个日志、有没有数据风险。
| 字段 | 填写内容 |
|---|---|
| 发布系统 | 系统名、服务名、模块名 |
| 发布环境 | 测试、预发、生产、灾备 |
| 发布窗口 | 开始时间、预计结束时间、观察截止时间 |
| 发布版本 | Git commit、tag、构建号、镜像 tag、镜像 digest |
| 变更范围 | 代码、配置、数据库、Nginx、证书、定时任务、消息订阅 |
| 影响范围 | 用户、接口、任务、报表、第三方依赖、下游系统 |
| 前置条件 | 备份完成、审批通过、依赖服务可用、监控负责人在线 |
| 回滚方式 | 代码回滚、镜像回滚、配置回滚、数据库回滚或补偿 |
| 验证人 | 技术验证、业务验证、监控观察 |
| 应急联系人 | 研发、运维、DBA、测试、业务、值班负责人 |
中国大陆、内网和离线环境
先说一个很现实的问题:不要默认生产机器能访问 Docker Hub、GitHub、国外软件源、npm、Maven Central、证书签发服务和 SaaS 监控平台。
开发机上 docker pull 一下就好了,生产内网里可能连 DNS 都解析不了。这里要提前确认依赖从哪里来、镜像从哪里来、证书从哪里签、监控往哪里发。
| 环境 | 常见风险 | 推荐方案 |
|---|---|---|
| 中国大陆公网 | 镜像拉取慢、依赖下载失败、证书签发失败、GitHub 不稳定 | 使用认证账号、云厂商仓库、企业制品库;发布前预拉取并校验 |
| 企业内网 | 无法访问外网源、证书链不完整、DNS/NTP 受限、代理复杂 | 建 Nexus、Harbor、内部 Git、内部 npm/Maven 仓库;统一代理与 CA 证书 |
| 完全离线 | 依赖包缺失、镜像架构不匹配、补丁无法临时下载、工具不可用 | 准备离线包、镜像 tar、校验和、恢复脚本、离线文档和应急工具包 |
构建依赖先确认来源。下面这些命令不是为了重新讲 Maven、Gradle、npm,而是先证明“这台机器按当前配置能不能稳定构建”。
# Maven
mvn -v
mvn help:effective-settings
mvn -s settings.xml -DskipTests package
mvn -o -s settings.xml -DskipTests package
# Gradle
./gradlew --version
./gradlew clean build --offline
# npm
npm config get registry
npm ci --prefer-offline
npm run build看结果时重点看三件事:
- Maven 的
effective-settings里是不是走企业私服或明确可用的镜像仓库。 - Gradle / npm 的离线模式能不能跑通,不能跑通就说明依赖闭环还不完整。
- 构建失败时先看依赖下载失败、证书失败、代理失败,别急着改业务代码。
镜像和离线包要有校验。离线发布最怕“文件传过去了,但不是同一个文件”,所以 tar 包和校验和要成对出现。
docker pull registry.example.com/team/app:20260709-001
docker image inspect registry.example.com/team/app:20260709-001
docker save -o app-20260709-001.tar registry.example.com/team/app:20260709-001
sha256sum app-20260709-001.tar > app-20260709-001.tar.sha256
sha256sum -c app-20260709-001.tar.sha256
docker load -i app-20260709-001.tar
docker images | grep 'team/app'sha256sum -c 输出 OK 才能继续。docker load 完以后再用 docker images 看 tag 是否正确。如果 tag 对不上,后面 compose 或 K8S 很可能拉到旧镜像,现场会非常迷惑。
离线环境里不要把发布脚本写成临时联网模式。能提前打包的工具、镜像、脚本和文档,都放进发布包里。
# 不推荐
curl -fsSL https://example.com/install.sh | bash
docker pull app:latest
npm install -g some-cli
# 推荐
sha256sum -c ops-tools.tar.gz.sha256
tar -zxf ops-tools.tar.gz -C /opt/ops-tools
/opt/ops-tools/bin/check-env.sh
docker load -i images/app-20260709-001.tar这里的重点不是“不能用 curl”,而是生产脚本不能把成功建立在临时外网上。公网抖一下,发布就挂,这种脚本不适合生产。
上线前检查清单
上线前检查不是“看一眼服务器”。你可以把它理解成开车前绕车一圈:轮胎、油量、灯光、刹车都要看。发布也是一样,服务器、代码、制品、配置、数据库、中间件、入口、证书、日志、监控、备份和回滚都要过一遍。
| 检查项 | 必须确认 | 常用命令 |
|---|---|---|
| 服务器基线 | CPU、内存、磁盘、inode、负载、时间、内核、系统版本 | uptime、free -h、df -hT、df -i、timedatectl |
| 端口网络 | 监听端口、连通性、DNS、路由、安全组、防火墙 | ss -lntup、curl、nc -zv、dig、ip route |
| 代码版本 | 分支、commit、tag、变更范围、合并记录 | git status、git log -1、git describe |
| 构建产物 | jar/war/dist/image 是否可复现,校验和是否记录 | sha256sum、ls -lh、docker image inspect |
| 镜像版本 | 不使用裸 latest,记录 tag、digest、构建号 | docker images、docker inspect |
| 配置变更 | 环境变量、配置文件、Nacos/配置中心、密钥、开关 | diff -u、grep、配置中心导出记录 |
| 数据库 | 备份、DDL/DML、索引、回滚 SQL、执行窗口 | mysqldump、mysql、EXPLAIN |
| Redis | 连通、内存、持久化、主从/集群、慢命令、大 key | redis-cli ping、INFO、--bigkeys |
| MQ | 节点状态、队列积压、消费者、死信、权限 | rabbitmqctl、kafka-consumer-groups.sh |
| Nginx | 配置语法、 upstream、证书、日志、reload 方案 | nginx -t、nginx -T |
| 证书 | 域名、证书链、到期时间、续期任务 | openssl x509、certbot certificates |
| 日志 | 应用日志、Nginx 日志、容器日志、日志轮转 | tail、journalctl、logrotate |
| 监控告警 | 大盘、告警规则、通知链路、值班人 | Prometheus、Grafana、Alertmanager |
| 回滚 | 旧包、旧镜像、旧配置、旧 Nginx、数据回退边界 | ln -sfn、docker compose、kubectl rollout undo |
服务器基线
发布前先确认机器还撑得住。磁盘满、inode 满、时间不同步、端口被占用,都会让发布变成现场救火。
hostnamectl
uname -a
date
timedatectl
uptime
nproc
free -h
df -hT
df -i
lsblk -f
ip addr
ip route
ss -lntup这些命令执行完先别急着往下走,现场一般按这个顺序看:
date和timedatectl看时间和时区,时间错了会影响证书、定时任务、日志排查和集群选主。df -hT看磁盘,df -i看 inode。磁盘有空间但 inode 满了,也会写不进日志、建不了临时文件。ss -lntup看端口,确认目标端口是不是已经被旧进程、临时进程或错误服务占了。uptime看负载,如果发布前机器已经很高,发布后重启、预热、缓存击穿都会放大风险。
如果这里已经异常,不建议继续发布。先把机器状态处理干净,再进入应用发布。
判断标准:
| 项 | 正常表现 | 风险表现 |
|---|---|---|
| 时间 | 时区正确,节点间时间差在可接受范围内 | 定时任务错乱、日志时间不准、证书校验异常 |
| 磁盘 | 数据盘、日志盘、Docker 数据目录有余量 | 构建失败、容器起不来、日志写不进去 |
| inode | inode 使用率没有接近 100% | 小文件过多,磁盘有空间但无法创建文件 |
| 端口 | 目标端口未被错误进程占用 | 服务启动失败、Nginx upstream 连不上 |
| 负载 | load 与 CPU 核数匹配 | 发布后轻微流量就雪上加霜 |
端口和网络
发布前至少做内外两层探测:服务器本机探测、调用方视角探测。本机通不代表用户能访问,入口域名通也不代表应用本机是好的,所以两边都要测。
# 本机监听
ss -lntup | grep -E ':80|:443|:8080|:3306|:6379|:9092'
# 本机访问应用
curl -fsS http://127.0.0.1:8080/health
# 访问入口域名
curl -I https://www.example.com
curl -fsS https://www.example.com/health
# TCP 连通性
nc -zv db.example.com 3306
nc -zv redis.example.com 6379
nc -zv mq.example.com 5672
# DNS 和路由
dig www.example.com
nslookup www.example.com
traceroute www.example.com看结果时可以这样判断:
ss能看到监听,说明进程至少把端口占起来了。curl 127.0.0.1通过,说明本机应用健康接口能响应。curl https://www.example.com/health通过,说明从入口层到应用链路基本通。nc -zv失败时,优先查安全组、防火墙、DNS、路由和对端监听,不要只盯应用日志。
如果没有 nc、dig、traceroute,不要临时从公网安装,先确认内网工具包里有没有同类工具。完全离线环境至少要准备 curl、ss、ip 这些基础排障命令。
代码和制品版本
发布前必须能把“线上跑的是什么”说清楚。不要只说“发最新代码”,要能说出 commit、tag、构建号、镜像 tag 和镜像 digest。
git status --short
git branch --show-current
git rev-parse --short HEAD
git log -1 --oneline
git describe --tags --always --dirty这里要注意 git status --short。如果它有输出,说明工作区不是干净的,要先确认这些改动是不是本次发布内容。git describe --dirty 里出现 dirty,也要停下来问清楚,不要把本地临时改动混进生产包。
构建产物要记录校验和。校验和的作用很简单:证明你传到服务器上的包,和流水线构出来的是同一个包。
# Java
mvn -DskipTests clean package
sha256sum target/*.jar
# Gradle
./gradlew clean build -x test
sha256sum build/libs/*.jar
# Node
npm ci
npm run build
tar -czf dist-20260709-001.tar.gz dist/
sha256sum dist-20260709-001.tar.gzsha256sum 的输出要写进发布单或流水线记录里。后面如果有人问“线上包是不是被换过”,直接在服务器上再算一次校验和就能对。
镜像版本要记录 tag 和 digest。tag 是人看的版本号,digest 是镜像内容的指纹,两者都记才稳。
docker build -t registry.example.com/team/app:20260709-001 .
docker push registry.example.com/team/app:20260709-001
docker image inspect registry.example.com/team/app:20260709-001 \
--format '{{json .RepoTags}} {{json .RepoDigests}}'生产发布不要只依赖 latest。latest 可以作为开发环境便利标签,生产环境要使用明确版本号,并在发布单里记录 commit、构建号、tag、digest。这里踩坑很多:同一个 latest 在两台机器上可能不是同一个镜像,回滚时也不知道退到哪里。
配置变更
配置变更要比代码变更更谨慎,因为配置经常绕过测试和代码 review。很多线上事故不是代码错了,而是连错库、开错开关、日志级别开成 debug、调用了测试地址。
# 对比新旧配置
diff -u application-prod.yml.current application-prod.yml.new
# 搜索明显不该出现在生产的配置
grep -RInE 'localhost|127\.0\.0\.1|debug: true|mock|test|TODO' config/
# 检查环境变量
env | sort | grep -E 'APP_|JAVA_|SPRING_|DB_|REDIS_|MQ_'diff -u 要看的是“改了哪些关键项”,不是只看命令有没有执行成功。grep 如果扫到 localhost、mock、test、debug: true,不要直接删,先确认它是不是生产必须配置;确认不是,再改配置并重新留档。
配置检查的重点:
| 配置项 | 检查内容 |
|---|---|
| 数据库 | host、port、库名、用户名、连接池、超时 |
| Redis | host、port、密码、db、集群节点、超时 |
| MQ | broker、topic/queue、consumer group、并发、重试 |
| 日志 | 日志级别、路径、切割、保留天数 |
| 外部接口 | base URL、超时、重试、熔断、鉴权 |
| 开关 | 新功能开关、灰度开关、降级开关 |
| 密钥 | 不出现在 Git、日志、命令历史和发布单明文里 |
数据库变更
数据库变更必须单独列出来。代码回滚通常容易,数据回滚通常很难。你把 jar 退回去很快,但数据被改错了,可能要按行、按时间点、按业务补偿慢慢救。
上线前至少准备四件事:
- 变更 SQL。
- 备份命令和备份文件。
- 验证 SQL。
- 回滚 SQL 或补偿方案。
MySQL 逻辑备份示例:
export TS=$(date +%Y%m%d_%H%M%S)
mysqldump \
--single-transaction \
--routines \
--triggers \
--set-gtid-purged=OFF \
-h db.example.com \
-u backup_user \
-p \
app_db > app_db_${TS}.sql
gzip app_db_${TS}.sql
sha256sum app_db_${TS}.sql.gz > app_db_${TS}.sql.gz.sha256这里几个参数要知道是干什么的:
--single-transaction尽量用一致性快照备份,适合 InnoDB 场景。--routines和--triggers把存储过程、触发器一起带上,避免恢复后对象缺失。- 备份后立刻
gzip和sha256sum,后面要能证明备份文件完整。
备份文件生成后一般还要看一下大小,0 字节或者明显过小都不能继续发布。
执行前先在测试环境跑一遍。测试环境执行不是形式主义,它能提前发现语法错误、字段不存在、数据范围不对。
mysql -h test-db.example.com -u app_user -p app_db < change.sql
mysql -h test-db.example.com -u app_user -p app_db < verify.sql生产执行要保留输出。tee 的作用是边看边保存,现场有报错,事后也能复查。
mysql --show-warnings -h db.example.com -u app_user -p app_db < change.sql | tee change_${TS}.log
mysql -h db.example.com -u app_user -p app_db < verify.sql | tee verify_${TS}.log如果 change.log 里有 warning,不要忽略。先看 warning 是字段截断、隐式转换、索引不可用,还是无影响提示。影响行数也要和预估范围对上,差太多就停。
高风险 SQL 的基本约束:
| SQL 类型 | 发布约束 |
|---|---|
ALTER TABLE | 确认表大小、锁表风险、执行时长、回滚边界 |
UPDATE / DELETE | 先 SELECT 确认范围,大批量分批执行,必要时先备份目标行 |
| 新增索引 | 评估磁盘、锁、复制延迟、业务低峰窗口 |
| 删除字段/表 | 原则上先下线代码引用,再观察,再删除 |
| 数据修复 | 必须有修复前快照、修复后校验、误修复补偿方案 |
Nginx 和证书
Nginx 是入口层,发布前必须先测配置,再 reload。这里要注意顺序:先 nginx -t,再 reload,不要把生产入口当试验田。
nginx -t
nginx -T | grep -E 'server_name|listen|proxy_pass|ssl_certificate'nginx -t 看到 syntax is ok 和 test is successful 才能继续。nginx -T 会打印最终合并后的配置,适合确认 server_name、listen、proxy_pass 和证书路径是不是你想要的。
证书检查:
openssl x509 -in /etc/nginx/certs/fullchain.pem -noout -subject -issuer -dates
# 30 天内过期则返回非 0
openssl x509 -checkend $((30 * 24 * 3600)) \
-noout \
-in /etc/nginx/certs/fullchain.pem如果使用自动续期,也要检查续期任务。
systemctl list-timers | grep -i certbot
certbot certificates
certbot renew --dry-run访问后应该看到证书主体、签发者和有效期都符合预期。openssl x509 -checkend 返回 0 说明 30 天内不会过期,返回非 0 就要提前处理续期,不要等浏览器报错。
证书相关的发布风险:
| 风险 | 现象 | 检查方式 |
|---|---|---|
| 证书过期 | 浏览器报不安全,接口 TLS 握手失败 | openssl x509 -dates |
| 证书链不完整 | 部分客户端访问失败 | openssl s_client -connect host:443 -servername host |
| 域名不匹配 | 证书名称错误 | 检查 SAN |
| reload 未生效 | 旧证书仍在使用 | 访问入口域名查看证书 |
| 内网 CA 未信任 | 内部服务调用失败 | 检查系统 CA 和 Java truststore |
Redis 和 MQ
Redis 检查不只看 PONG,还要看内存、持久化、主从/集群状态。PONG 只能说明端口通、服务活着,不能说明它健康。
redis-cli -h redis.example.com -p 6379 ping
redis-cli -h redis.example.com -p 6379 info server
redis-cli -h redis.example.com -p 6379 info memory
redis-cli -h redis.example.com -p 6379 info persistence
redis-cli -h redis.example.com -p 6379 info replication
redis-cli -h redis.example.com -p 6379 info cluster这里重点看:
used_memory和maxmemory的距离,接近上限就要小心淘汰和延迟。rdb_last_bgsave_status、aof_last_bgrewrite_status是否为ok。- 主从场景看
master_link_status、复制延迟;集群场景看cluster_state。
如果是集群,额外看节点状态。
redis-cli -h redis.example.com -p 6379 cluster nodes
redis-cli -h redis.example.com -p 6379 cluster infoRabbitMQ 检查示例:
rabbitmqctl status
rabbitmq-diagnostics status
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers
rabbitmqctl list_connections name user state channelsKafka 检查示例:
kafka-topics.sh --bootstrap-server kafka.example.com:9092 --list
kafka-consumer-groups.sh --bootstrap-server kafka.example.com:9092 --describe --all-groupsMQ 这里最关键的是“有没有积压、有没有消费者、积压是不是还在涨”。如果队列消息很多但消费者为 0,发布应用只会把问题带到新版本里;如果 Kafka lag 一直上升,先恢复消费能力,再谈发布。
发布前要特别关注:
| 组件 | 风险 | 发布判断 |
|---|---|---|
| Redis | 内存接近上限、持久化失败、主从延迟、集群 fail | 先处理状态,再发布 |
| RabbitMQ | 队列积压、消费者为 0、连接异常、磁盘水位告警 | 暂停发布或先扩容/恢复消费者 |
| Kafka | consumer lag 高、分区无 leader、ISR 异常 | 暂停发布或降低发布风险 |
发布中检查
发布中要少做临时判断,多按固定步骤执行。每一步都要能快速停下来。
现场最忌讳边发布边想方案。发布前写好的命令,发布中照着执行;命令输出不符合预期,就停在当前步骤,不要带着异常继续往后跑。
Systemd / Jar 服务
一种常见发布方式是保留 releases 目录,用 current 指向当前版本。这样做的好处很直接:新版本是一个目录,旧版本也是一个目录,回滚就是把软链接指回去。
export APP=demo-app
export VERSION=20260709-001
export BASE=/opt/${APP}
install -d ${BASE}/releases/${VERSION}
cp ${APP}.jar ${BASE}/releases/${VERSION}/
sha256sum ${BASE}/releases/${VERSION}/${APP}.jar
ln -sfn ${BASE}/releases/${VERSION} ${BASE}/current
systemctl restart ${APP}
systemctl status ${APP} --no-pager
journalctl -u ${APP} -n 200 --no-pager执行完重点看三处:
sha256sum是否和发布单里的产物校验和一致。systemctl status是否是active (running),如果是failed,先看journalctl。journalctl里有没有端口占用、配置加载失败、数据库连不上、类找不到、权限不足这些启动错误。
发布中观察:
curl -fsS http://127.0.0.1:8080/health
ss -lntp | grep ':8080'
journalctl -u demo-app --since '-5 min' -p warning --no-pager访问后应该看到健康接口返回 2xx,端口能被 ss 查到,最近 5 分钟没有新增 warning/error。如果健康接口失败,先不要急着改包,先按“进程、端口、配置、依赖、日志”这个顺序排查。
Docker Compose 服务
发布前先渲染配置。docker compose config 很重要,它会把变量替换、compose 文件合并后的最终配置打印出来,很多环境变量写错的问题会在这里提前暴露。
docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200 appdocker compose ps 里要看容器是不是 running,有没有反复重启。logs 里如果看到 permission denied、端口绑定失败、连接数据库失败、配置文件找不到,一般不是镜像本身问题,先查挂载目录、环境变量和网络。
验证镜像版本:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
docker inspect app --format '{{.Config.Image}} {{.State.Status}} {{.State.StartedAt}}'这里要确认容器实际运行的镜像就是发布单里的 tag。如果 compose 文件改了,但容器没有重建,docker inspect 会把真相告诉你。
K8S 服务
如果服务跑在 K8S,发布中至少要看 rollout 状态、Pod 事件和镜像拉取。K8S 不是执行了 set image 就算发布成功,真正成功要看 Pod ready、探针通过、事件正常。
kubectl -n prod set image deployment/demo-app app=registry.example.com/team/app:20260709-001
kubectl -n prod rollout status deployment/demo-app --timeout=5m
kubectl -n prod get pods -l app=demo-app -o wide
kubectl -n prod describe pod -l app=demo-app
kubectl -n prod logs -l app=demo-app --tail=200如果出现 ImagePullBackOff,先查镜像仓库、tag、pull secret、内网网络。出现 CrashLoopBackOff,先看启动日志。readiness probe 失败时,Pod 可能已经启动但不能接流量,这时不要继续扩大发布面。
Nginx 配置发布
Nginx 修改必须先测试再 reload。这里和应用重启不一样,Nginx 是入口层,错一次影响面很大。
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d_%H%M%S)
nginx -t
nginx -s reloadreload 后验证入口。
curl -I https://www.example.com
curl -fsS https://www.example.com/health
tail -n 100 /var/log/nginx/error.log
awk '$9 ~ /^5/ {print}' /var/log/nginx/access.log | tailcurl -I 要看状态码、Server、Location、缓存头是否符合预期。error.log 如果出现 connect() failed,一般是 upstream 不通;如果出现 permission denied,先查文件权限、SELinux 或目录权限;如果 access log 里 5xx 突然变多,先回退入口配置。
Nginx 官方 reload 机制会先检查新配置并尝试应用。如果打开日志文件或监听端口失败,会继续使用旧配置。即便如此,生产发布仍然要保留备份文件和回退命令。
数据库变更执行
数据库变更不要和代码发布混成一坨。建议单独记录执行时间、执行人、SQL 文件、影响行数和验证结果。
export TS=$(date +%Y%m%d_%H%M%S)
mysql -h db.example.com -u app_user -p app_db < precheck.sql | tee db_precheck_${TS}.log
mysql --show-warnings -h db.example.com -u app_user -p app_db < change.sql | tee db_change_${TS}.log
mysql -h db.example.com -u app_user -p app_db < verify.sql | tee db_verify_${TS}.log执行中观察数据库连接和慢查询。
SHOW PROCESSLIST;
SHOW VARIABLES LIKE 'slow_query_log';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Questions';SHOW PROCESSLIST 里如果看到大量锁等待、长事务、复制延迟,就先停。大批量更新建议分批执行,避免长事务、锁等待、主从延迟和回滚时间过长。
发布后验证
发布后验证要覆盖四层:进程活着、入口可访问、核心业务正常、指标没有恶化。
这里要注意,“服务启动成功”不是发布成功。发布成功的标准是用户入口能访问、核心链路能跑、日志没有新增异常、监控指标没有明显变差。
基础验证
systemctl status demo-app --no-pager
curl -fsS http://127.0.0.1:8080/health
curl -I https://www.example.com
curl -fsS https://www.example.com/health访问后应该看到健康接口返回成功,并且响应时间在正常范围内。curl -I 只看入口状态,不能替代业务接口验证;健康接口正常,也不能说明下单、登录、支付、消息消费都正常。
如果服务提供版本接口,必须校验版本。
curl -fsS https://www.example.com/version没有版本接口时,至少保留构建号、容器镜像、commit 文件或启动日志里的版本输出。
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
journalctl -u demo-app --since '-10 min' --no-pager | grep -E 'version|commit|build'如果版本接口返回的版本不是目标版本,先查负载均衡是否还有旧节点、容器是否重建、Nginx upstream 是否指到了旧服务、K8S rollout 是否完成。
日志验证
应用日志先看最近 10 分钟,不要从几天前的历史错误里找答案:
journalctl -u demo-app --since '-10 min' -p warning --no-pager
tail -n 300 /opt/demo-app/current/logs/app.log
grep -E 'ERROR|Exception|Timeout|OutOfMemory|Connection refused' /opt/demo-app/current/logs/app.log | tail -n 50Nginx 日志:
tail -n 200 /var/log/nginx/error.log
awk '$9 ~ /^5/ {print}' /var/log/nginx/access.log | tail -n 50
awk '{count[$9]++} END {for (code in count) print code,count[code]}' /var/log/nginx/access.log容器日志:
docker compose logs --tail=200 app
docker logs --tail=200 app这里要注意,日志不是只看有没有 ERROR。Timeout、Connection refused、Too many connections、OutOfMemory、permission denied 都是发布后必须处理的信号。
业务冒烟验证
业务验证不要只测首页。首页打开只能说明入口和静态资源还活着,不能说明业务链路正常。至少覆盖:
| 类型 | 示例 |
|---|---|
| 读接口 | 首页、详情页、列表页、核心查询 |
| 写接口 | 创建、修改、删除或提交测试单据 |
| 登录鉴权 | 登录、刷新 token、权限菜单 |
| 异步链路 | 发送消息、消费消息、定时任务 |
| 文件链路 | 上传、下载、预览 |
| 外部依赖 | 支付、短信、邮件、第三方 API |
可用 curl 记录最小验证。
curl -fsS -H 'Authorization: Bearer <token>' https://www.example.com/api/orders?page=1
curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"name":"smoke-test"}' \
https://www.example.com/api/smoke生产冒烟要使用可识别、可清理、不会污染真实业务的数据。一般会加固定前缀、固定测试账号、固定备注,方便发布后清理和审计。
监控验证
发布后至少观察一个完整业务周期。低流量系统可以观察 30 到 60 分钟,高流量系统可以先观察 5 到 15 分钟再继续扩大范围。
| 指标 | 观察重点 |
|---|---|
| 机器 | CPU、内存、load、磁盘、网络、文件句柄 |
| 应用 | QPS、错误率、响应时间、线程池、连接池 |
| 数据库 | 连接数、慢 SQL、锁等待、主从延迟、磁盘 |
| Redis | 内存、命中率、连接数、慢命令、持久化 |
| MQ | 积压、消费速率、消费者数量、死信 |
| Nginx | 4xx、5xx、upstream 响应时间、连接数 |
| 证书 | 到期时间、证书链、续期任务 |
| 告警 | 是否有新增告警、是否误静默、通知是否到达 |
Prometheus 查询示例:
up{job="demo-app"}
rate(http_server_requests_seconds_count[5m])
rate(http_server_requests_seconds_count{status=~"5.."}[5m])
histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m]))看监控时不要只看当前值,还要和发布前基线对比。错误率从 0.1% 涨到 1%,单看 1% 可能不吓人,但对核心接口就是明显异常。
发布期间如果临时设置了告警静默,发布完成后必须取消或确认自动过期。很多事故不是没有告警,而是发布时静默了,发布后没人恢复。
回滚准备
回滚不是“出了问题再说”。上线前就要写清楚回滚条件和回滚命令。
这里要把话说得直接一点:真正事故现场没有时间重新设计回滚方案。你能不能回滚,取决于发布前有没有旧包、旧镜像、旧配置、旧 SQL 方案和验证命令。
回滚触发条件
建议提前定义硬条件。硬条件的意思是到了就执行,不要在现场反复争论。
| 条件 | 动作 |
|---|---|
| 健康检查连续失败 | 立即回滚 |
| 5xx 明显高于发布前基线 | 立即评估,达到阈值回滚 |
| 核心业务不可用 | 立即回滚 |
| 数据写错或重复消费 | 先止血,再按数据预案处理 |
| 数据库变更不可逆 | 停止继续发布,进入应急补偿 |
| MQ 积压持续扩大 | 暂停发布,恢复消费者或扩容 |
| 证书、Nginx、DNS 入口异常 | 回退入口配置 |
Jar / Systemd 回滚
如果使用 releases 目录,回滚就是把 current 指回旧版本并重启。这个方案简单,但前提是旧版本目录和旧配置还在。
export APP=demo-app
export BASE=/opt/${APP}
export PREV_VERSION=20260708-003
ln -sfn ${BASE}/releases/${PREV_VERSION} ${BASE}/current
systemctl restart ${APP}
systemctl status ${APP} --no-pager
curl -fsS http://127.0.0.1:8080/health回滚后不要只看服务起来了,还要确认版本已经变回旧版本、核心接口恢复、日志不再刷错误。否则只是“重启成功”,不是“回滚成功”。
Docker Compose 回滚
回滚前确认旧镜像仍在仓库或本机。生产环境不要等事故发生后才发现旧镜像被清理了。
docker images | grep 'team/app'
docker pull registry.example.com/team/app:20260708-003
# 修改 compose 文件中的镜像 tag 后
docker compose config
docker compose up -d app
docker compose ps
docker compose logs --tail=200 app如果旧镜像拉不下来,先查仓库保留策略、镜像 tag、认证凭证和内网仓库同步。这里也能看出为什么发布前要记录 digest。
K8S 回滚
如果发布对象保留了 revision,可以直接回滚。先看历史,再 undo,不要凭感觉写 revision。
kubectl -n prod rollout history deployment/demo-app
kubectl -n prod rollout undo deployment/demo-app --to-revision=3
kubectl -n prod rollout status deployment/demo-app --timeout=5m
kubectl -n prod get pods -l app=demo-app -o wide如果 .spec.revisionHistoryLimit 设置为 0,将没有历史版本可回滚,这种配置不适合需要快速回滚的生产服务。线上服务一般要保留足够的历史版本。
Nginx 回滚
cp /etc/nginx/nginx.conf.bak.20260709_210000 /etc/nginx/nginx.conf
nginx -t
nginx -s reload
curl -I https://www.example.com
tail -n 100 /var/log/nginx/error.logNginx 回滚后要看入口状态码和 error.log。特别是证书、upstream、root 路径改动,表面 reload 成功,实际请求仍然可能 404、502 或证书不匹配。
数据库回滚
数据库回滚要先判断类型。不是所有数据问题都适合直接恢复整库,整库恢复会覆盖恢复点之后的正常业务写入。
| 变更类型 | 回滚方式 |
|---|---|
| 新增表/字段 | 通常可保留,代码回滚即可 |
| 新增索引 | 可评估删除,但不一定需要立刻回滚 |
| 修改字段类型 | 高风险,需要提前准备兼容方案 |
| 删除字段/表 | 高风险,必须先备份,最好分阶段发布 |
| 更新/删除数据 | 必须有备份表、恢复 SQL 或业务补偿 |
| 数据迁移 | 需要双写/读切换/数据一致性验证和回切方案 |
数据变更前可以先保存目标行。这个动作很朴素,但真出问题时非常有用。
CREATE TABLE backup_user_status_20260709 AS
SELECT *
FROM user_status
WHERE status = 'old_status';
SELECT COUNT(*) FROM backup_user_status_20260709;误改后按备份恢复。恢复前要先 SELECT 对比影响行数,确认 join 条件不会扩大范围。
UPDATE user_status u
JOIN backup_user_status_20260709 b ON u.id = b.id
SET u.status = b.status,
u.update_time = b.update_time;不是所有数据库问题都适合直接导入整库备份。整库恢复会影响恢复点之后的所有数据,必须先评估 RPO、业务写入和下游影响。
巡检和长期治理
发布完成后,不代表这次部署结束。至少要进入观察和巡检。
很多问题不是发布后立刻出现,而是在缓存过期、定时任务启动、消息堆积、用户高峰到来之后暴露。所以发布完成后还要继续看一段时间。
发布后 30 分钟检查
| 检查项 | 命令或入口 | 结论 |
|---|---|---|
| 服务状态 | systemctl status / docker ps / kubectl get pods | 正常 / 异常 |
| 健康接口 | curl -fsS /health | 正常 / 异常 |
| 错误日志 | journalctl / 应用日志 / Nginx 日志 | 无新增异常 / 有异常 |
| Nginx 5xx | access log 或监控大盘 | 正常 / 异常 |
| 数据库 | 连接数、慢 SQL、锁等待 | 正常 / 异常 |
| Redis | INFO memory、INFO persistence | 正常 / 异常 |
| MQ | 队列积压、消费者数量 | 正常 / 异常 |
| 监控告警 | Grafana / Alertmanager | 正常 / 异常 |
| 业务验证 | 核心链路冒烟 | 通过 / 不通过 |
日常巡检
日常巡检建议固定在每天业务开始前或低峰期完成。巡检不是为了写“已巡检”,而是为了提前发现磁盘、日志、证书、备份、中间件状态这些慢性问题。
date
uptime
free -h
df -hT
df -i
ss -lntup
systemctl --failed
journalctl -p err -S today --no-pager这里一般先看机器状态,再看失败服务,再看当天错误日志。systemctl --failed 有输出就要处理,不能放着不管。
备份巡检:
ls -lh /backup
find /backup -type f -mtime -1 -ls
sha256sum -c latest-backup.sha256备份巡检不能只看目录里“有文件”。还要看文件是不是今天生成的、大小是否合理、校验是否通过,最好定期做恢复演练。
证书巡检:
openssl x509 -checkend $((30 * 24 * 3600)) \
-noout \
-in /etc/nginx/certs/fullchain.pem巡检记录必须写“异常处理结果”,不能只写“已巡检”。例如“磁盘 82%,已清理 30 天前日志,新增 85% 告警”,这才是有用记录。
落地工程深水区
部署运维总清单真正难的地方,不是清单长,而是每一项都要能落到证据、责任人和回滚动作上。否则清单会变成上线前随手打勾的仪式。
第一是发布窗口和冻结边界。发布前要冻结包、配置、SQL、镜像 tag 和发布脚本,不要在窗口里临时改。临时改动必须重新走构建、校验、审批和备份,否则现场所有验证都失去可信基础。
第二是依赖和容量。上线前不只看应用机器,还要看数据库连接数、Redis 内存、MQ 积压、磁盘、日志、Nginx upstream、证书和第三方接口。核心命令要能留下输出,发布后对比是否恶化。没有容量基线时,发布失败很容易被误判成“代码问题”。
第三是回滚边界。代码、镜像、Nginx 配置可以较快回滚;数据库 DDL、批量数据修复、消息消费位点、缓存结构变化可能无法直接回滚。发布单必须写清楚哪些可自动回滚,哪些只能补偿,哪些触发停机或人工接管。
第四是监控告警。发布期间必须有人看日志、指标和告警,不要发布完才去问有没有报错。至少观察错误率、延迟、CPU、内存、磁盘、连接数、队列积压、核心接口成功率。没有监控的服务,不应直接进入无人值守发布。
第五是权限和审计。谁批准、谁执行、谁验证、谁决定回滚,要在发布单里明确。生产命令和流水线记录要可追溯,敏感变量要脱敏。事故发生时,审计不是追责工具,而是还原事实的唯一办法。
第六是长期维护。清单要随着事故复盘更新。每一次漏项都要沉淀成脚本检查、发布模板字段、监控告警或文档约束。只写“下次注意”没有用,能被系统自动检查的尽量自动检查,不能自动检查的明确责任人和证据。
风险项速查
| 风险项 | 典型现象 | 排查命令 | 预防动作 |
|---|---|---|---|
| 磁盘满 | 服务无法写日志,Docker 起不来 | df -hT、du -sh | 日志轮转、容量告警、数据目录独立 |
| inode 满 | 有空间但不能创建文件 | df -i | 清理小文件、调整日志策略 |
| 时间不同步 | 证书异常、日志错乱、集群状态异常 | timedatectl、date | NTP/chrony、节点时间巡检 |
| 端口占用 | 服务启动失败 | ss -lntup | 发布前端口扫描 |
| 镜像拉取失败 | ImagePullBackOff、compose 拉取失败 | docker pull、kubectl describe pod | 私有仓库、离线镜像、认证凭证 |
| 配置未生效 | 服务启动但连错库/错环境 | env、diff、启动日志 | 配置 review、版本化配置 |
| 数据库无备份 | SQL 误执行无法恢复 | 查备份文件和恢复演练记录 | 变更前备份,定期恢复演练 |
| Redis 内存高 | 延迟升高、淘汰、写入失败 | redis-cli info memory | 设置告警、容量评估、大 key 巡检 |
| MQ 积压 | 消费延迟、业务状态滞后 | list_queues、consumer group lag | 发布前后看积压趋势 |
| Nginx 配置错误 | 502、504、reload 失败 | nginx -t、error.log | 配置备份,先测再 reload |
| 证书过期 | HTTPS 访问失败 | openssl x509 -dates | 提前 30 天告警,自动续期演练 |
| 日志缺失 | 故障无法定位 | tail、journalctl | 统一日志路径、切割和采集 |
| 告警失效 | 故障没人知道 | Alertmanager、通知群 | 告警演练和值班表 |
| 回滚不可执行 | 故障时只能硬修 | 回滚演练 | 每次发布前写回滚命令 |
清单模板
上线前清单
## 上线前检查
- [ ] 发布单已创建,发布窗口已确认
- [ ] 发布负责人、验证人、回滚负责人已确认
- [ ] Git commit / tag / 构建号已记录
- [ ] 构建产物校验和已记录
- [ ] 镜像 tag / digest 已记录
- [ ] 配置变更已 review,配置差异已保存
- [ ] 数据库变更 SQL 已在测试环境执行
- [ ] 数据库备份已完成,备份可读,校验和已记录
- [ ] Redis 状态正常
- [ ] MQ 状态正常,无异常积压
- [ ] Nginx 配置 `nginx -t` 通过
- [ ] 证书有效期大于 30 天
- [ ] 日志路径和监控大盘已确认
- [ ] 告警通知链路正常,值班人在线
- [ ] 回滚命令已写好,旧版本产物可用
- [ ] 中国大陆 / 内网 / 离线依赖已准备发布中清单
## 发布中检查
- [ ] 已进入发布窗口
- [ ] 已停止临时配置变更
- [ ] 已确认当前线上版本
- [ ] 已执行数据库变更或确认本次无数据库变更
- [ ] 已部署应用新版本
- [ ] 服务进程 / 容器 / Pod 状态正常
- [ ] 本机健康检查通过
- [ ] Nginx reload 成功
- [ ] 入口域名访问正常
- [ ] 应用日志无新增 ERROR
- [ ] Nginx 5xx 未异常升高
- [ ] Redis / MQ / 数据库指标无异常
- [ ] 关键业务冒烟通过发布后验证清单
## 发布后验证
- [ ] 版本接口或日志显示为目标版本
- [ ] 核心读接口验证通过
- [ ] 核心写接口验证通过
- [ ] 登录和权限验证通过
- [ ] 定时任务或异步消息验证通过
- [ ] 文件上传下载验证通过
- [ ] 外部依赖调用正常
- [ ] 监控大盘无异常趋势
- [ ] 告警无新增严重问题
- [ ] 观察期结束,发布完成回滚记录模板
## 回滚记录
- 触发时间:
- 触发原因:
- 影响范围:
- 决策人:
- 回滚版本:
- 回滚命令:
- 数据库是否回滚:
- 配置是否回滚:
- Nginx 是否回滚:
- 回滚开始时间:
- 回滚完成时间:
- 回滚验证结果:
- 后续处理:复盘记录模板
## 发布复盘
- 发布版本:
- 发布时间:
- 参与人员:
- 变更范围:
- 是否按计划完成:
- 是否发生告警:
- 是否发生回滚:
- 问题现象:
- 根因分析:
- 临时处置:
- 永久修复:
- 需要补充的监控:
- 需要补充的脚本:
- 需要更新的清单:
- 负责人和截止时间:命令速查
服务器:
uptime
free -h
df -hT
df -i
lsblk -f
ss -lntup
systemctl --failed
journalctl -p err -S today --no-pager代码和制品:
git rev-parse --short HEAD
git describe --tags --always --dirty
git log -1 --oneline
sha256sum target/*.jar
docker image inspect registry.example.com/team/app:tagDocker:
docker ps
docker logs --tail=200 app
docker inspect app
docker stats --no-stream
docker compose config
docker compose up -d
docker compose logs --tail=200 app
docker save -o app.tar registry.example.com/team/app:tag
docker load -i app.tarNginx:
nginx -t
nginx -T
nginx -s reload
tail -n 100 /var/log/nginx/error.log
awk '$9 ~ /^5/ {print}' /var/log/nginx/access.log | tail数据库:
mysqldump --single-transaction --routines --triggers -h db.example.com -u backup_user -p app_db > app_db.sql
mysql -h db.example.com -u app_user -p app_db < change.sql
mysql -h db.example.com -u app_user -p app_db < verify.sqlRedis:
redis-cli -h redis.example.com -p 6379 ping
redis-cli -h redis.example.com -p 6379 info memory
redis-cli -h redis.example.com -p 6379 info persistence
redis-cli -h redis.example.com -p 6379 info replication
redis-cli -h redis.example.com -p 6379 cluster nodesMQ:
rabbitmqctl status
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers
kafka-consumer-groups.sh --bootstrap-server kafka.example.com:9092 --describe --all-groups证书:
openssl x509 -in /etc/nginx/certs/fullchain.pem -noout -subject -issuer -dates
openssl x509 -checkend $((30 * 24 * 3600)) -noout -in /etc/nginx/certs/fullchain.pem
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/nullK8S:
kubectl -n prod get pods -o wide
kubectl -n prod rollout status deployment/demo-app --timeout=5m
kubectl -n prod rollout history deployment/demo-app
kubectl -n prod rollout undo deployment/demo-app --to-revision=3
kubectl -n prod describe pod -l app=demo-app
kubectl -n prod logs -l app=demo-app --tail=200官方文档入口
- Docker Engine 安装与离线 RPM:https://docs.docker.com/engine/install/
- Docker
save/load:https://docs.docker.com/reference/cli/docker/image/save/ ,https://docs.docker.com/reference/cli/docker/image/load/ - Docker Hub pull 限制:https://docs.docker.com/docker-hub/usage/pulls/
- Nginx 控制与 reload:https://nginx.org/en/docs/control.html
- systemd
systemctl/journalctl:https://www.freedesktop.org/software/systemd/man/latest/systemctl.html ,https://www.freedesktop.org/software/systemd/man/latest/journalctl.html - MySQL 备份恢复:https://dev.mysql.com/doc/refman/8.4/en/backup-and-recovery.html
- PostgreSQL 备份恢复:https://www.postgresql.org/docs/current/backup.html
- Redis
INFO与持久化:https://redis.io/docs/latest/commands/info/ ,https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/ - RabbitMQ 监控与 CLI:https://www.rabbitmq.com/docs/monitoring ,https://www.rabbitmq.com/docs/cli
- Prometheus 告警:https://prometheus.io/docs/alerting/latest/overview/
- Let’s Encrypt 证书与续期建议:https://letsencrypt.org/docs/faq/ ,https://letsencrypt.org/docs/integration-guide/
- OpenSSL
x509:https://docs.openssl.org/3.2/man1/openssl-x509/ - Git revert / tag:https://git-scm.com/docs/git-revert ,https://git-scm.com/docs/git-tag
- Kubernetes rollout:https://kubernetes.io/docs/reference/kubectl/generated/kubectl_rollout/
- Maven mirror:https://maven.apache.org/guides/mini/guide-mirror-settings.html
- npm
.npmrc:https://docs.npmjs.com/cli/v11/configuring-npm/npmrc/ - Harbor 安装配置:https://goharbor.io/docs/2.13.0/install-config/
- Nexus Docker Registry:https://help.sonatype.com/en/docker-registry.html
