香港服务器如何实现从 CentOS 7 迁移至 AlmaLinux/Rocky Linux,确保关键应用的平滑过渡与持续稳定?

凌晨 2:10,香港机房机柜 24U 的最下层,白色理线板里穿着一束束蓝色 Cat6A,左手边那台 EPYC 7302P + 双盘 NVMe 的机器风扇刚把夜色吹得更静。我把一杯还冒着热气的冻柠茶挪开,盯着屏幕上的大字:“从 CentOS 7 升级到 AlmaLinux/Rocky:确认执行?”。
这不是第一次做大版本迁移,但这次要求特别“苛刻”:关键业务 24×7,允许最长 3 分钟中断;数据库必须无数据丢失;网络出口使用双线 BGP(PCCW/DIA + CN2 GIA)并保留会话粘性。更要命的是——这台机器上既有传统进程型服务(Nginx + PHP-FPM + Redis),也有容器化工作负载(部分 Go 服务历史上用 Docker CE 起的),而 CentOS 7 → EL8/EL9 不只是“换发行版”,它是用户态、内核、包生态、网络堆栈、SELinux 与防火墙体系的一次整体跃迁。
下面是我这次在香港机房实打实走过的一套可复用方法论与可执行步骤;我尽量把坑点、参数、对照表、演练与回滚都写清楚,让无论新手还是老手都能顺着路径把这件事“稳稳当当地办成”。
一、项目背景与目标约束
业务画像(抽象化):
- Web 流量:峰值 ~18k RPS(静态 + 动态),Nginx 前置,后端 PHP-FPM(部分逐步 Go 化)。
- 数据层:MySQL 5.7 主从(主在此机房),Redis 5(主要做会话与热点 KV)。
- 服务形态:传统 Systemd 服务 + 少量 Docker CE 容器(历史原因)。
- SLO:可用性 99.95%,切换窗口 ≤ 3 分钟;RPO=0、RTO≤3 分钟。
现网硬件(核心节点):
| 角色 | 型号/配置 | 备注 |
|---|---|---|
| 应用与数据库主节点(物理) | AMD EPYC 7302P(16C/32T)/ 128GB ECC | 单路,1U |
| 本地存储 | 2× NVMe Samsung PM9A3 1.92TB(RAID1,mdadm) | 4KiB 逻辑扇区;队列深度:64 |
| 网卡 | 2× Intel X710 10GbE(bonding LACP) | 上联双交换机,BGP 到边 |
| 管理 | IPMI(独立管理口) | 可远程介入 |
| 系统盘分区 | /(xfs, ftype=1),/var、/data 独立挂载 | 为容器/数据库预留 |
迁移目标:
从 CentOS 7 平滑过渡到 AlmaLinux 8/9 或 Rocky Linux 8/9(本文以 7→8 为第一阶段,8→9 作为第二阶段可选)。
关键应用零数据丢失,业务中断 ≤ 3 分钟;具备可回滚与可复演能力。
清理历史债务:iptables→nft(firewalld),Python2→Python3 运行时,network-scripts→NetworkManager,Docker→(过渡保留/迁移到 Podman)。
二、路线选择:就地“提级” vs. 双机“蓝绿”
| 方案 | 停机 | 成本 | 复杂度 | 适用 |
|---|---|---|---|---|
| A. 就地 ELevate/Leapp 升级 | 1–3 分钟 | 低 | 中高(需逐项修障) | 单机资源紧、设备难扩容 |
| B. 双机蓝绿(新装 EL8/9) | ≈ 0–30 秒(VIP 切换) | 中 | 中 | 流量高、允许临时并行资源 |
我在这次实际落地中采用混合策略:
数据层(MySQL/Redis)优先走 双机蓝绿(复制/哨兵/切换),保证数据安全与热切换。
应用层(Nginx/PHP-FPM/Go 服务)在新节点编译/打包并热切,旧节点最后做就地升级(沉淀可复用“升降机”流程)。
如果你只有一台机器,A 方案也能做成,但要把备份、快照与回滚剧本写到“肌肉记忆级别”。
三、风险清单与“能打的”前置检查
下面这张表,是我在机房里最常用的一页 A4(打印贴在机柜门里):
| 检查项 | 目标 | 命令/方法 | 备注 |
|---|---|---|---|
| XFS ftype | =1 | `xfs_info / | grep ftype` |
| 启动模式 | UEFI/Legacy 匹配 | ls /sys/firmware/efi |
ELevate/Leapp 对 GRUB2 有变更 |
| 第三方仓库 | el8 对照 | 列表/映射表(见后) | Remi、EPEL、Percona 等 |
| 防火墙栈 | firewalld/nft | systemctl status firewalld |
从 iptables 迁移(见示例) |
| Python 依赖 | py3 化 | `rpm -qa | grep -i python` |
| Network | NM 接管 | nmcli / 迁移配置 |
network-scripts 将废弃 |
| SELinux | Permissive→Enforcing | /etc/selinux/config |
升级阶段先 Permissive |
| 驱动/DKMS | 可重建 | dkms status |
ZFS/特殊驱动要注意 |
| MySQL/Redis | 复制/哨兵就绪 | show slave status\G/Sentinel |
先“活过来”再谈切换 |
四、仓库映射与软件栈差异
EL7→EL8 常见仓库映射:
| 组件 | EL7 仓库 | EL8 替代/对应 | 备注 |
|---|---|---|---|
| EPEL | epel-release |
epel-release(el8) |
版本对应 |
| PHP(多版本) | Remi | Remi for EL8 | yum-config-manager --enable remi-phpXY |
| Percona/MySQL | Percona | Percona el8 / 官方 MySQL 8.0 | 5.7→8.0 有语法/认证变更 |
| Nginx | 官方 nginx.org | 官方 el8 | 编译参数变化 |
| Docker CE | docker-ce(el7) | docker-ce(el8)/ 或 Podman | Docker → Podman(可阶段并存) |
五、演练与回滚:我如何把风险“装进盒子里”
1)快照/镜像(物理机也能稳)
本地 LVM 快照(适合根分区在 LVM 上):
lvcreate -L 30G -s -n rootsnap /dev/vg/root
# 需要确保 /boot 独立且可回滚策略明确(如备份 tar)
全盘镜像(停机窗口短、速度快):
# 从救援系统/LiveCD 启动,使用 partclone (速度快于 dd)
partclone.ext4 -c -s /dev/nvme0n1p3 -o /mnt/backup/nvme0n1p3.img
异地/异盘 rsync(数据分区):
rsync -aHAX --numeric-ids --info=progress2 /data/ /mnt/backup/data/
2)一键回滚脚本(示意)
#!/usr/bin/env bash
set -euo pipefail
echo "[!] Rolling back root LV snapshot..."
umount / || true
lvconvert --merge /dev/vg/rootsnap
echo "[OK] Snapshot merged. Rebooting..."
reboot
回滚不是“可选项”,而是“默认项”。先写回滚,再做升级。
六、资产盘点与漂移治理(Ansible 示例)
目标:把“机器现在长什么样”拉成结构化数据表,特别是服务清单/依赖库/自定义配置。
Ansible Playbook(核心片段):
- hosts: hk-core
gather_facts: yes
tasks:
- name: Collect rpm list
command: rpm -qa
register: rpms
- name: Collect services
shell: "systemctl list-unit-files --type=service --state=enabled | awk '{print $1}'"
register: services
- name: Collect iptables rules
command: iptables-save
register: ipt
- name: Save audit file
copy:
dest: "/root/pre-migration/audit_{{ inventory_hostname }}.txt"
content: |
HOST={{ inventory_hostname }}
KERNEL={{ ansible_kernel }}
OS={{ ansible_distribution }} {{ ansible_distribution_major_version }}
RPM_COUNT={{ rpms.stdout_lines | length }}
SERVICES_ENABLED={{ services.stdout_lines | join(',') }}
IPT={{ ipt.stdout }}
这份审计文件在后面“对比/回归”阶段非常有用。
七、数据库与会话的“无感”迁移
1)MySQL 5.7 → EL8 上的 MySQL 8.0(蓝绿)
步骤要点:
在新节点(EL8)部署 MySQL 8.0,参数与存储布局与旧机一致/更优(innodb_flush_log_at_trx_commit=1、sync_binlog=1 保证持久性)。
建立基线备份 + 复制:
# 旧主(CentOS 7)
mysqldump --single-transaction --master-data=2 --routines --triggers --events \
-u root -p --databases appdb | pv | gzip > /backup/appdb_base.sql.gz
# 新从(EL8,MySQL 8.0)
zcat /backup/appdb_base.sql.gz | mysql -u root -p
CHANGE MASTER TO MASTER_HOST='10.10.10.7', MASTER_USER='repl',
MASTER_PASSWORD='xxxx', MASTER_LOG_FILE='mysql-bin.000123', MASTER_LOG_POS=456789;
START SLAVE;
SHOW SLAVE STATUS\G
双写冲突规避:切换窗口前只写旧主,新节点仅追日志。
切换时:停写(或全局只读)、确认从库追平、VIP/Proxy 切换,新主上解除只读。
2)Redis 会话不中断(Sentinel)
在新节点启动 Redis 6(兼容 5),作为从节点同步。
引入 Sentinel 集群(3 节点,含一个轻量旁路虚机即可),先监控旧主。
切换时刻(TTL 减至 60s),令 Sentinel 促发故障转移到新主;客户端使用 Sentinel 发现机制。
实战经验:切换前把应用的长连接/连接池寿命降到 30–60s,能够明显减少“半开连接”导致的重试抖动。
八、防火墙与网络:从 iptables 到 firewalld/nft
CentOS 7 上不少规则还是 iptables 保存的;EL8 默认 firewalld(nft 后端)。我先导出并“翻译”,再按服务维度重建区域策略。
快速翻译(参考):
iptables-save | awk '/^-A/ { $1=""; print substr($0,2) }' | \
while read -r rule; do iptables-translate $rule; done > /root/ipt.rules.nft
用 firewalld 重建最小策略:
# 基础
systemctl enable --now firewalld
firewall-cmd --permanent --set-default-zone=public
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
# MySQL/Redis 对内开放(示例:只对 10.10.0.0/16)
firewall-cmd --permanent --new-zone=internal
firewall-cmd --permanent --zone=internal --add-source=10.10.0.0/16
firewall-cmd --permanent --zone=internal --add-port=3306/tcp
firewall-cmd --permanent --zone=internal --add-port=6379/tcp
firewall-cmd --reload
Bond 与 VLAN(NetworkManager):
把 ifcfg-* 迁到 NM:
nmcli con add type bond ifname bond0 mode 802.3ad
nmcli con add type ethernet ifname enp129s0f0 master bond0
nmcli con add type ethernet ifname enp129s0f1 master bond0
nmcli con add type vlan dev bond0 id 100 con-name bond0.100
nmcli con mod bond0.100 ipv4.addresses 192.0.2.10/24 ipv4.gateway 192.0.2.1 ipv4.dns "8.8.8.8 1.1.1.1" ipv4.method manual
nmcli con up bond0; nmcli con up bond0.100
九、应用层:Nginx、PHP-FPM、Go 服务的“热切 + 校验”
1)Nginx(EL8 编译选项变化)
OpenSSL/HTTP3/动态模块差异明显。建议在新节点先编译/打包,确保与旧配置的 include/map/geo/lua 模块一致。
灰度切换:上游配置两端并存,权重 90/10→50/50→100/0,观察 5xx、P95 延迟。
2)PHP-FPM(多版本来自 Remi 仓库)
dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf module reset php -y
dnf module enable php:remi-8.2 -y
dnf install -y php php-fpm php-mysqlnd php-opcache
systemctl enable --now php-fpm
3)容器:Docker → Podman(过渡期可并存)
若历史上 Docker 绑定 overlay2,请确认 XFS ftype=1。不满足时严禁直接起容器。
Podman 兼容 docker run 大多数语义;CI/CD 用 buildah 构建镜像。
dnf install -y podman buildah
podman run --name app -p 8080:8080 -v /data/app:/app:Z --restart=always ghcr.io/acme/app:1.2.3
十、就地升级(ELevate/Leapp):CentOS 7 → Alma/Rocky 8
我把数据库/Redis 已经切到新节点,此时旧机只承载 Web 与少量后台,窗口可控。
1)预备与仓库
yum update -y
curl -O https://repo.almalinux.org/elevate/elevate-release-latest-el7.noarch.rpm
yum install -y elevate-release-latest-el7.noarch.rpm
yum install -y leapp leapp-repository
2)预检查 & 修障
leapp preupgrade
less /var/log/leapp/leapp-report.txt
典型阻断项与处理(我现场真的遇到过的):
| 报告项 | 现象 | 处理 |
|---|---|---|
remove_pam_pkcs11_module_check |
提示 EL8 不再使用该模块 | 在 answerfile 里确认移除 |
inhibitor: Unsupported 3rd-party repo |
el7 的 Remi/EPEL 直挂 | 禁用 el7 源,准备 el8 对应 |
inhibitor: docker overlayfs on xfs ftype=0 |
容器层叠不兼容 | 停容器、迁盘或改存储驱动/重建分区 |
Network-scripts deprecated |
ifcfg 脚本 | 预生成 NM 连接配置(见上) |
Answerfile(示例) /var/log/leapp/answerfile:
[remove_pam_pkcs11_module_check]
confirm = True
[dnf_conflicting_packages]
confirm = True
[remove_kmods]
confirm = True
应用答复:
leapp answer --section remove_pam_pkcs11_module_check.confirm=True
leapp answer --section dnf_conflicting_packages.confirm=True
leapp answer --section remove_kmods.confirm=True
3)执行升级与重启
leapp upgrade
reboot
重启后进入新的 EL8 用户态;登录第一件事:
cat /etc/redhat-release
uname -r
dnf clean all && dnf makecache
4)收尾:仓库、服务、SELinux、监控
# EPEL/Remi 等
dnf install -y epel-release
dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm
# firewalld、fail2ban 等
dnf install -y firewalld fail2ban
systemctl enable --now firewalld
# SELinux 回到 Enforcing(验证后再切)
setenforce 1
sed -ri 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
# Node Exporter/Agent 重装/校准
systemctl status node_exporter || dnf install -y node_exporter
十一、(可选)第二阶段:EL8 → EL9
等业务平稳一两周后再做(或新节点直接装 EL9)。原则同上:先蓝绿,后就地;先把数据库与会话再“装进盒子里”。
十二、指标对比:迁移前后“肉眼可见”的变化
同一业务 10 分钟窗口(压测/真实流量混合):
| 指标 | CentOS 7 | Alma/Rocky 8 | 变化 |
|---|---|---|---|
| Nginx P95 延迟(ms) | 38.7 | 28.2 | ↓ 27% |
| PHP-FPM 平均耗时(ms) | 21.4 | 17.9 | ↓ 16% |
| NVMe 读延迟(avg, µs) | 92 | 76 | ↓ 17% |
| CPU 上下文切换(/s) | 23k | 18k | ↓ 21% |
| Redis GET QPS | 210k | 242k | ↑ 15% |
注:以上为现场一次窗口观测,你的数据以实测为准。EL8 的新内核/编译链 + firewalld/nft 带来的系统调用路径变化,确实能把尾延迟压下去。
十三、完整剧本(Runbook)汇总
T-7 天:
资产盘点 + 性能基线;数据库/Redis 蓝绿环境搭建完毕;压一轮小流量验证。
写好回滚脚本与救援介质,打印到纸质清单。
T-1 天:
freeze 业务变更;DB/Redis 开只读演练;Nginx 灰度到新节点 30%。
T 时刻(窗口 3 分钟):
业务侧暂停写操作(或降级为只读页);
确认从库/新主追平;进行主从切换,更新 VIP/Proxy;
恢复写入与全量流量。
T+1 小时:
观察指标、日志;压测队列回放;确认回滚条件无触发后,开始旧机就地 ELevate 到 EL8。
回滚条件(任一触发即回退):
关键指标(5xx、P95)超阈值 2 倍以上持续 3 分钟;
MySQL 延迟复制积压 > 5s 且不可快速恢复;
Redis 命中率或延迟异常波动且 Sentinel 不稳定。
十四、常见“坑洞”与我的现场解法
xfs ftype=0(历史装机手误)
→ 别硬上,把容器目录迁到一个新建的 xfs(ftype=1)或 ext4 分区:
mkfs.xfs -n ftype=1 /dev/nvme1n1p3
mount /dev/nvme1n1p3 /var/lib/containers
rsync -aHAX /old/containers/ /var/lib/containers/
network-scripts 被废弃,bond/VLAN 丢失配置
→ 提前用 nmcli 生成连接,并保留离线 keyfile 到 /etc/NetworkManager/system-connections/。
iptables 复杂自定义表
→ 分解为服务维度的 firewalld 区域;必要时把关键 DNAT/SNAT 迁到边界设备(路由器/防火墙)。
Python2 脚本跑不动
→ 给业务脚本建 pyenv/venv,逐步改造(至少把 shebang 指到 python3)。
MySQL 5.7 → 8.0 认证与保留字
→ 切换前统一 default_authentication_plugin=mysql_native_password;对 SQL 中的 groups 等保留字加反引号。
SELinux 恐惧症
→ 先 Permissive 跑起来,收集 AVC,再按需加策略后拉回 Enforcing。不要永久关。
十五、关键配置样例(可抄可改)
Nginx upstream(灰度)
upstream app_backend {
least_conn;
server 10.10.10.7:9000 weight=9; # 旧
server 10.10.10.8:9000 weight=1; # 新
keepalive 128;
}
MySQL 只读切换脚本(片段)
mysql -uroot -p -e "SET GLOBAL read_only=ON; FLUSH TABLES WITH READ LOCK;"
# …确认从库追平后在新主:
mysql -uroot -p -e "SET GLOBAL read_only=OFF;"
Prometheus 告警(切换保护)
- alert: High5xxDuringCutover
expr: sum(rate(nginx_http_requests_total{status=~"5.."}[1m]))
/ sum(rate(nginx_http_requests_total[1m])) > 0.05
for: 2m
labels: { severity: page }
annotations:
summary: "5xx 激增(切换窗口)"
十六、选择 AlmaLinux 还是 Rocky Linux?
两者都稳定、兼容 RHEL,生态与社区都成熟。我的实践偏好:
- AlmaLinux:ELevate 项目起步早,7→8、8→9 升级体验成熟;对“就地升级”友好。
- Rocky Linux:稳,文档清晰;如果团队里已有 Rocky 的模板/镜像,迁移成本更低。
选哪一个都不耽误你把“可靠迁移”这件事做好——关键在你的剧本与演练。
十七、结尾:灯还是那盏灯,系统已不是那个系统
凌晨 3:03,Nginx 的 5xx 曲线终于稳稳压回了零界线,Redis 的延迟像刚洗过的玻璃一样透亮。工单里“计划外中断:0 分 42 秒”,在可接受范围内。
我把答疑群里最后一条“ok”的表情点了赞,合上机柜门,背上的汗跟冷气打了个照面,忽然就不黏了。
这活儿,其实不神秘:把风险装进盒子,把回滚写在手上,把监控调到“会吼”的音量,然后按部就班地走。系统不是那个系统了,可灯,还是那盏灯。等下次有人问“CentOS 7 怎么稳到 Alma/Rocky?”,我会把这篇实操文丢给他——然后告诉他:先做演练,再动生产。
附录:一页速查(可打印)
命令清单(最小集)
# 盘点
xfs_info / | grep ftype
ls /sys/firmware/efi
rpm -qa | wc -l
iptables-save > /root/iptables.bak
# 备份
mysqldump --single-transaction ...
rsync -aHAX /data/ /mnt/backup/data/
# ELevate
yum install -y leapp leapp-repository elevate-release-*.rpm
leapp preupgrade
leapp answer --section remove_pam_pkcs11_module_check.confirm=True
leapp upgrade && reboot
# EL8 收尾
dnf install -y epel-release firewalld
systemctl enable --now firewalld
setenforce 1
仓库映射(记住三件事)
- Remi/EPEL 换 el8 版本;
- Docker 慎重(优先 Podman 或重建容器盘);
- MySQL 8.0 认证/语法变更提前“除雷”。
回滚剧本要素
- 启动介质 + LVM/镜像路径 + 恢复口令;
- 触发条件明确、一步到位。
——写到这里,冻柠茶的冰已经化成了一杯“常温的清醒”。希望你读完,能带着这份“剧本”去机房走一遍。祝迁移顺利,稳定优先。