香港服务器运行CentOS 7的业务系统如何迁移到Rocky Linux,避免因CentOS停更导致的安全风险?

我在香港机房做迁移时,面对的不是抽象的“系统升级”,而是一组真实跑在 CentOS 7 上的生产业务节点:双路 E5 的 Dell R630、带 NVMe 的白牌超融合集群,还有承载核心数据库的 HP DL360。所有节点都在承压跑流量,凌晨时段 CPU 负载依旧维持在 30% 以上。CentOS 7 已经 EOL,3.10 内核与老旧的 glibc 成为潜在的安全隐患,而应用层又依赖 Docker、MariaDB、Redis、Nginx 等关键组件。迁移目标很明确:在最小化停机的前提下,把这些节点平滑切到 Rocky Linux,确保内核、glibc、OpenSSL 等核心库升级到可长期维护的版本。
场景与目标
- 背景:CentOS 7 已停更(EOL),3.10 内核与旧版 glibc 带来的安全风险逐周上升。业务包括:Nginx + PHP-FPM、Node.js、Go 服务、Redis、MariaDB、RabbitMQ、以及若干容器化工作负载(Docker CE,非 K8s)。
- 机房:香港 MEGA-i + Equinix HK2 混合;对大陆与东南亚有高比例访问,晚高峰明显。
目标:
- 将 CentOS 7 业务迁至 Rocky Linux 8(或 9);
- 停机时间 ≤ 5 分钟/节点(大盘流量通过灰度/蓝绿摊平);
- 迁移后通过 OpenSCAP/Lynis 与 CIS baseline 的最小要求;
- 部署链路与回滚路径可重复、可审计。
迁移路线选择
我实际落地了两条路线,并在不同业务上混用:
就地升级(in-place):
使用 ELevate(leapp) 从 EL7 → EL8,目标直接 Rocky 8。适合状态可控、依赖清爽的节点(无第三方内核、无存储内核模块、数据库从库/只读副本等)。
重装替换(blue/green):
新装 Rocky 8/9,将流量切过去,再逐步下线旧节点。适合核心数据库主库、复杂内核模块(如旧版 Mellanox OFED)、历史遗留太多的机器。
经验:能重装就重装。就地升级只在你非常清楚这台机器上发生过什么时使用。数据库主库我一律重装替换,压力测完再切主。
我手上的硬件与网络
| 机型 | CPU | 内存 | 系统盘 | 数据盘 | RAID/HBA | NIC | 备注 |
|---|---|---|---|---|---|---|---|
| Dell R630 | 2×E5-2680 v4 | 128 GB | SATA SSD 480G×2(RAID1) | NVMe U.2 1.92T×4 | LSI 9361-8i(CacheCade) | 2×10Gb SFP+(ixgbe) | 业务中台 |
| HP DL360 Gen10 | 1×Gold 6130 | 128 GB | SATA SSD 480G×2(RAID1) | SAS 10K 1.8T×6(RAID10) | SmartArray P408i | 2×10Gb(bnx2x) | 消息队列/日志 |
| 自研白牌(超融合集群) | EPYC 7302P | 256 GB | M.2 NVMe 1T×1 | NVMe U.2 3.84T×6 | 直通 HBA 9300-8i | 2×25Gb(mlx5) | 存储/对象网关 |
坑点提示:
leapp 会检查内核模块与驱动兼容性。ixgbe/mlx5 驱动版本在 EL8/EL9 下变化明显;有 DKMS/ELRepo 的,先移除再升级。
SmartArray 的控制器在内核切换后偶有写缓存策略被重置,升级后我都会复核 WriteBack/WriteThrough。
迁移前的“家底摸清”
我先跑了一个 Ansible playbook,把需要的事实都收上来(版本、内核模块、挂载、repo、服务端口、数据库版本、容器镜像等),结果输出到一张表,业务方一眼能懂。
| 检查项 | 命令/脚本 | 期望 | 处理动作 |
|---|---|---|---|
| OS 版本 | cat /etc/centos-release |
7.9 | 不满足则先 yum update 到 7.9 |
| 3rd-party 内核 | `rpm -qa | grep elrepo` | 无 |
| Docker 版本 | docker version |
≥ 20.10 | 低于要求先离线备份镜像与卷 |
| MariaDB | mysql -V |
≥ 10.3(EL8 兼容) | 5.5/10.1 需 逻辑导出 + 重建 |
| 网卡驱动 | ethtool -i |
EL8/9 支持 | DKMS/自编译先卸载 |
| 防火墙 | iptables-services | 故障前置 | 迁移到 firewalld/nft,或保留兼容层 |
| SELinux | Permissive/Enforcing | 最终 Enforcing | 迁移后按策略修正规则 |
| 挂载 | lsblk -f |
LVM/UEFI 足额 | /boot ≥ 1G(给 leapp/内核) |
我曾因 /boot 只有 200MB,leapp 直接拒绝升级。现场用 lsblk 看了下分区,最后是缩 root 扩 boot(XFS/LVM),有风险,建议提前在变更日之前做。
备份与回滚
虚拟化节点:拍快照 + 磁盘层 snapshot(Ceph RBD / VMware snapshot)。
物理机:
配置 LVM 快照(适合关键配置的小范围回滚);
业务数据一律做 逻辑备份:
MySQL/MariaDB:mysqldump --single-transaction --master-data=2;
Redis:触发 BGSAVE 并复制 dump.rdb 与 appendonly.aof;
Nginx/应用:/etc、/opt、/var/www 打包。
冷备镜像:partclone/clonezilla 到 NAS(香港本地 10Gb 链路)。
回滚策略:
就地升级失败 → 直接用 IPMI 虚拟介质引导 救援 ISO,还原镜像。
蓝绿失败 → 立即切回旧节点(保留 1:1 拓扑 24–48h)。
路线 A:就地升级(ELevate → Rocky 8)
我在“计算节点(无数据库主库)+ 中间件从节点”上采用此法,99% 准备充分时成功率很高。
0)冻结与清理
# 切换到 CentOS 7.9 的 vault 源,避免升级过程依赖漂移
sudo sed -ri 's/mirrorlist/#mirrorlist/g; s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Base.repo
sudo yum clean all && sudo yum makecache
# 升到 7.9 最新
sudo yum -y update
# 移除第三方内核/驱动(非常关键)
sudo yum -y remove 'kernel-ml*' 'kmod-*' 'elrepo-release*'
# 关停非必要服务,固定应用发布
sudo systemctl stop crond || true
sudo systemctl stop docker # 容器后面重装
1)安装 ELevate(leapp)工具并预检查
# ELevate/Leapp 组件(面向 Rocky 目标)
sudo yum -y install leapp-upgrade leapp-data-rocky
# 预检查
sudo leapp preupgrade
# 看 /var/log/leapp/leapp-report.txt 与 answerfile
常见阻断(Inhibitors)我遇到的
- 旧版 python2-numpy、initscripts、ntp:直接卸载或替换为 chrony。
- boot 分区空间不足:扩大到 ≥ 1G。
- 第三方 repo(remi/epel)未禁用:把 /etc/yum.repos.d/*.repo 里非官方的先 enabled=0。
- SELinux 策略包冲突:先设为 permissive,升级后再恢复。
2)执行升级
# 正式升级到 Rocky 8
sudo leapp upgrade
# 重启后进入 leapp initramfs,自动完成包级迁移
sudo reboot
升级完成后登录检查:
cat /etc/os-release
uname -r
rpm -qa | wc -l
3)重装 Docker / 运行时与服务恢复
EL8/EL9 的 cgroup 与 iptables 默认栈变化较大(EL9 为 cgroup v2,EL8 仍 v1)。我在 EL8 保持 v1 以兼容历史镜像:
# Docker CE 安装(EL8)
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io
sudo systemctl enable --now docker
# 需要 cgroup v1 的,/etc/default/grub 添加:
# GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0"
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# 恢复镜像与卷(若前面有打包)
docker load -i /backup/docker-images.tar
网络与防火墙:
# firewalld 统一管理(保持历史 iptables 规则也可)
sudo dnf -y install firewalld
sudo systemctl enable --now firewalld
# 常用端口放行
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
SELinux:
# 迁移期 Permissive,验证通过后 Enforcing
sudo setenforce 0
# /etc/selinux/config -> SELINUX=permissive
数据库/中间件版本位移(示意):
| 组件 | CentOS 7 | Rocky 8 建议 |
|---|---|---|
| MariaDB | 5.5/10.1 | 10.5/10.11(AppStream) |
| Redis | 3.x/4.x | 6.x/7.x |
| Nginx | 1.16/1.20(第三方) | 1.20+(AppStream或官方) |
| OpenSSL | 1.0.2k | 1.1.1/3.0(EL9 才 3.0) |
MariaDB 升级要点:5.5/10.1 → 10.5 最稳妥做逻辑导出导入,字符集、排序规则统一到 utf8mb4_0900_ai_ci(若为 MySQL 8),避免线上写入导致 redo 兼容性事故。
路线 B:重装替换(蓝绿/灰度)
我在数据库主库、流量焦点节点用此法。重装 Rocky 9 对未来寿命更长,但 Docker/K8s 与 cgroup v2 的兼容要提前评估。
1)准备新节点(或同节点 PXE 重装)
Kickstart 片段(UEFI,XFS on LVM):
#version=RHEL8
text
lang en_US.UTF-8
keyboard us
timezone Asia/Hong_Kong --ntpservers=ntp.aliyun.com,ntp.tencent.com
network --bootproto=dhcp --device=eno1 --hostname=app-rk8-01.local
rootpw --iscrypted $6$randomhash
zerombr
clearpart --all --initlabel
part /boot/efi --fstype=efi --size=600
part /boot --fstype=xfs --size=1024
part pv.01 --size=1 --grow
volgroup rklvg pv.01
logvol / --vgname=rklvg --name=root --size=40960 --fstype=xfs
logvol /var --vgname=rklvg --name=var --size=20480 --fstype=xfs
logvol swap --vgname=rklvg --name=swap --size=8192
%packages
@^minimal-environment
vim-enhanced
chrony
tuned
%end
%post
systemctl enable chronyd
tuned-adm profile throughput-performance
%end
2)数据迁移与灰度
静态内容:rsync -a --delete,Nginx 配置注意 stream/http 模块差异。
数据库:
- 建立并行从库/只读副本;
- 压测新库(TPC-C/TPC-H 之类的业务型压测);
- 秒级切换:主从角色转换或 Proxy 层(如 HAProxy/ProxySQL)指针切换。
流量灰度:
- LVS/NGINX/SLB 按 5%→20%→50%→100%,每步观察 10–30 分钟;
- 监控指标对比(P95/P99、错误率、GC 时间、系统负载、磁盘延迟)。
我用的“变更剧本”(节选)
一键前置检查(Shell)
#!/usr/bin/env bash
set -euo pipefail
log(){ echo "[$(date +%F' '%T)] $*"; }
log "OS: $(cat /etc/redhat-release || true)"
log "Kernel: $(uname -r)"
log "3rd kernel: $(rpm -qa | egrep 'kernel-ml|elrepo' || echo none)"
log "Docker: $(command -v docker >/dev/null && docker --version || echo none)"
log "DB: $(mysql -V 2>/dev/null || echo none)"
log "/boot: $(df -h /boot | tail -1)"
log "Repos:"
egrep -R "^\[|^enabled=" /etc/yum.repos.d/*.repo | sed 's/^/ /'
Ansible:批量滚动升级(就地)
- hosts: web_kv
serial: 1
any_errors_fatal: true
tasks:
- name: Freeze repos to vault
become: yes
lineinfile:
path: /etc/yum.repos.d/CentOS-Base.repo
regexp: '{{ item.regexp }}'
line: '{{ item.line }}'
loop:
- {regexp: '^mirrorlist', line: '#mirrorlist'}
- {regexp: '^#baseurl=http://mirror.centos.org', line: 'baseurl=http://vault.centos.org'}
- name: Remove elrepo kernels
become: yes
yum:
name:
- "kernel-ml*"
- "kmod-*"
- "elrepo-release*"
state: absent
- name: Preupgrade
become: yes
command: leapp preupgrade
- name: Upgrade
become: yes
command: leapp upgrade
- name: Reboot to finish
become: yes
reboot:
reboot_timeout: 3600
迁移后的系统加固与优化
内核/系统调优(通用 Web/DB)
# /etc/sysctl.d/99-tune.conf
net.core.somaxconn = 1024
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65000
vm.swappiness = 10
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
fs.file-max = 2097152
sysctl --system
ulimit -n 1048576
NVMe/XFS:
# 按周 TRIM(若直连 NVMe)
sudo systemctl enable fstrim.timer --now
# 挂载选项 noatime,nodiratime
tuned:
sudo tuned-adm profile throughput-performance
chrony(替代 ntp):
sudo dnf -y install chrony
sudo systemctl enable --now chronyd
chronyc sources -v
安全基线
SELinux:逐项放行策略后切回 Enforcing;
firewalld:只开放白名单端口,源地址段收紧;
OpenSCAP:
dnf -y install scap-security-guide openscap-scanner
oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_standard \
--results-arf /root/scan-arf.xml --report /root/scan-report.html \
/usr/share/xml/scap/ssg/content/ssg-rocky8-ds.xml
Lynis:
dnf -y install lynis
lynis audit system --quiet
我遇到的典型坑 & 现场处理
| 坑点 | 表现 | 原因 | 处理 |
|---|---|---|---|
| leapp 卡在 preupgrade 报 initscripts | 无法继续 | EL7 旧包与 EL8 冲突 | 卸载/替换,清理后重跑 |
/boot 空间不足 |
升级中止 | 旧分区太小 | 线下扩容到 ≥1G 或重装 |
| Docker 容器跑不起来 | cgroup 不兼容 | EL8/EL9 cgroup 行为变化 | 强制 v1(EL8),重装 docker-ce |
| Mellanox 驱动丢失 | 网卡不亮/报错 | DKMS 自编译模块不兼容 | 先移除 DKMS,使用内核自带 mlx5 |
| MariaDB 升级后字符集异常 | 某些表中文乱 | 升级跨越大版本 | 统一字符集并逻辑导出/导入 |
| iptables 规则失效 | 端口未放行 | 转向 nftables | firewalld 统一,或安装兼容层 |
| RAID 写策略改变 | IO 抖动 | 控制器策略恢复默认 | 手工改回 WriteBack + BBU |
验证清单(上线前/后)
# 版本与内核
cat /etc/os-release
uname -r
# 网络
ping -c3 内部服务
curl -I https://外部健康检查域名
ss -lntp | egrep '80|443|3306|6379'
# 存储与 IO
lsblk -f
fio --name=randread --rw=randread --bs=4k --iodepth=32 --numjobs=4 --size=1G --runtime=60 --group_reporting
# 日志
journalctl -p 3 -xb # 错误级日志
dmesg | tail -n 50
# 应用健康
systemctl status nginx
systemctl status mariadb
docker ps
我最终的时间线与人手安排(模板)
| 阶段 | 动作 | 负责人 | 时间窗 |
|---|---|---|---|
| 资产盘点/预检查 | Ansible 扫描、堵住 inhibitors | 我/系统工程师 | D-7 ~ D-5 |
| 备份/回滚演练 | 逻辑备份 + 镜像还原演练 | DBA/存储 | D-4 ~ D-3 |
| 试点 | 10% 节点就地升级/重装 | SRE 值班 | D-2 |
| 全量灰度 | 5%→20%→50%→100% | SRE/应用 | D 日 00:00–04:00 |
| 验证与收尾 | 基线扫描/性能对比/文档归档 | 全员 | D+1 |
FAQ:Rocky 8 还是 Rocky 9?
Rocky 8:兼容历史包最多,容器与 cgroup v1 迁移成本低;生命周期到 2029 左右。
Rocky 9:更长寿(到 2032+),OpenSSL 3.0、glibc 2.34;但容器与某些 SDK 要提前适配(cgroup v2、OpenSSL 3.0 不向后 ABI 兼容)。
我的做法:核心系统上先到 8,稳定后再 8→9(leapp 二次升级),避免一次跨越过大。
结语:为什么这事要“现在就做”
CentOS 7 停更后,系统不是“慢慢不安全”,而是逐渐暴露在零日与供应链风险之下:OpenSSL/内核/C 标准库 一旦出 CVE,EL7 不再修,你只能自己 backport。把不可控因素变成可控,就是我们做迁移的意义。
如果你跟我一样,需要在香港机房夜里做这件事:
- 提前把“能重装就重装”的节点划出来;
- 就地升级的,确保 预检查 0 个 inhibitor 再动手;
- 备份与回滚,不是写在变更单里的口号,而是在变更前真的做过一次;
- 最后,把脚本、Checklist、压测与事故处理复盘写进团队知识库——下一次你会更快、更稳。