两个月前,领导甩过来一份文件:信创改造时间表,要求Q3结束前把所有 CentOS 服务器迁移到国产操作系统。我数了一下,生产环境加测试环境,一共 20 台。
CentOS 7 早就停服了,安全补丁断供,这个道理我都懂。但"懂"和"干"之间,隔着一个运维人的血泪。下面把这两个月的迁移经历原原本本写出来,包括我踩的 6 个坑,希望能让后面要迁的同行少走弯路。
一、选型:openEuler、麒麟、统信UOS,到底选谁
市面上主流的国产服务器操作系统就三个方向:openEuler(华为系)、银河麒麟(中国电子系)和统信UOS(统信软件)。我花了一周做对比,结论是没有最好的,只有最合适的。
# 三大国产服务器OS对比(运维视角)
openEuler 22.03 LTS
- 内核: 5.10(可选6.6)
- 包管理: dnf/yum(RPM系,CentOS运维最熟悉)
- 社区活跃度最高,文档全,踩坑有人陪
- 适合: 云原生、容器、Web服务器
银河麒麟 V10 SP3
- 内核: 4.19(稳定优先)
- 包管理: dnf/yum(RPM系)
- 等保资质齐全,政企项目必备
- 适合: 政务、金融、等保要求高的场景
统信UOS Server V20
- 内核: 5.10
- 包管理: apt/dpkg(Debian系)
- 桌面端体验最好,服务器端偏少
- 适合: 需要桌面端的办公场景
我们公司的业务是 Web 应用 + Java 后端 + MySQL 数据库,没有等保强制要求,所以选了 openEuler 22.03 LTS SP3。理由很简单:RPM 系包管理,运维团队不用重新学;社区活跃,有问题搜得到;LTS 版本支持到 2027 年,够用了。
选型建议:如果你的环境以容器和云原生为主,openEuler 是首选;如果有等保合规要求,直接上麒麟 V10;如果团队更熟悉 Debian/Ubuntu 系,统信UOS 也能用。千万别哪个流行选哪个,要跟着你的业务场景走。
二、迁移前评估:这步比迁移本身重要十倍
很多人拿到迁移任务第一反应就是找台机器开始装系统,这是最危险的。迁移出问题,八成是评估没做好。
我在正式迁移前做了三件事:
第一件:盘点所有软件依赖。SSH 到每台机器,把已安装的 RPM 包列表导出来,重点标注非标准仓库安装的包、源码编译安装的软件、以及依赖特定 glibc 版本的应用。
# 导出已安装包列表
rpm -qa --queryformat '%{NAME} %{VERSION} %{ARCH}\n' | sort > installed_pkgs.txt
# 找出非标准仓库安装的包
rpm -qa --qf '%{NAME} %{VENDOR}\n' | grep -v "CentOS" | grep -v "Red Hat"
# 检查源码编译安装的软件(通常在/usr/local下)
ls /usr/local/ | grep -v bin | grep -v lib | grep -v share
# 检查glibc版本(很多老应用卡在这里)
ldd --version | head -1
第二件:梳理定时任务和系统服务。crontab 里的任务、systemd 自定义服务、rc.local 里的启动脚本,这些迁移后最容易遗漏。我建了个表格,每台机器列出所有非默认启用的服务和定时任务。
第三件:跑兼容性评估工具。openEuler 官方提供了 x2openEuler 工具,可以自动扫描 CentOS 系统的软件兼容性,生成评估报告。
# 安装并运行 x2openEuler 评估工具
yum install -y x2openEuler
x2openEuler assess --target centos7 --output ./assess_report
这步帮我提前发现了 3 个问题:一个用了 CentOS 7 特有老版本 glibc 的 Java 应用、一个依赖 Python 2.7 的运维脚本、一个编译时硬编码了路径的 C 程序。如果直接迁,这三个坑够我 debug 一星期。
三、第一台迁移:从测试环境开始试水
评估做完,正式开干。第一台我选了测试环境的一台 Web 服务器,跑的是 Nginx + 一个 Python Flask 应用,业务影响最小,适合当小白鼠。
迁移方式有两种:原地升级和全新部署。我选了全新部署——新装一台 openEuler,把应用搬过去,旧机器保留一周作为回退后路。原地升级虽然省事,但残留的旧包和配置容易埋雷,生产环境不推荐。
# 1. 新装 openEuler 22.03 LTS SP3(最小化安装)
# 安装后先更新系统
dnf update -y
# 2. 安装基础运维工具
dnf install -y vim net-tools bind-utils tar wget curl tree htop
dnf install -y policycoreutils-python-utils # SELinux 管理工具
# 3. 从旧机器同步应用配置
# Nginx 配置
scp root@old-server:/etc/nginx/nginx.conf /etc/nginx/
scp -r root@old-server:/etc/nginx/conf.d/ /etc/nginx/
# 4. 安装应用运行环境
dnf install -y nginx
dnf install -y python3 python3-pip
pip3 install flask gunicorn
# 5. 同步应用代码和数据
rsync -avz root@old-server:/opt/app/ /opt/app/
# 6. 启动服务并验证
systemctl enable --now nginx
systemctl enable --now gunicorn
看起来很顺利对吧?实际上从第 3 步开始就出事了。下面是让我加班到深夜的 6 个坑。
坑一:包名一样,版本不一样
旧机器上 Nginx 是 CentOS 7 用 EPEL 源装的 1.20 版本,openEuler 官方源里的 Nginx 是 1.24。版本号差了不说,配置文件的默认参数也不一样。
最隐蔽的一个差异:openEuler 的 Nginx 默认关掉了 gzip_types 里的 text/css 和 application/javascript。旧的 nginx.conf 直接拷过来没问题,但如果你像我们一样用 conf.d 里的独立配置文件管理 gzip,新版本默认行为变了,前端资源加载速度直接慢了一倍。
教训:迁移后别只验证"服务能不能起来",要逐项验证配置参数是否生效。我用 nginx -T 把完整生效配置打印出来,和旧机器 diff 了一遍,才发现这个差异。
坑二:Python 版本地狱
CentOS 7 默认 Python 是 2.7,openEuler 默认 Python 是 3.9(没有 Python 2)。我们有一套老的运维监控脚本是用 Python 2 写的,直接搬过来全报错。
报错集中在几个地方:print 语句变 print 函数、urllib2 模块拆分成了 urllib.request 和 urllib.error、ConfigParser 变成了 configparser。这些都是 Python 2 到 3 的经典迁移问题,但当你一次面对几十个脚本的时候,改起来还是头大。
# 方案一:装 Python 2(不推荐,但能救急)
# openEuler 官方源没有 python2,需要编译安装
dnf install -y gcc openssl-devel bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz
tar xzf Python-2.7.18.tgz && cd Python-2.7.18
./configure --enable-optimizations && make altinstall
# 方案二:用 2to3 工具批量转换(推荐)
python3 -m lib2to3 -w -n /opt/scripts/monitor.py
# -w 直接修改文件,-n 不创建备份(建议先备份再跑)
我选了方案二,用 2to3 批量转换后人工检查。大部分脚本能自动转,但有几个用了 commands.getoutput()(Python 3 里没有这个模块)的,得手动改成 subprocess.getoutput()。一共 23 个脚本,改了半天。
教训:迁移前先跑一遍 grep -rn "python2\|#!/usr/bin/python" /opt/ /usr/local/",把所有 Python 2 脚本找出来,提前改。别等迁移完了才发现脚本跑不了,业务等着你修,压力完全不一样。
坑三:内核参数不兼容,性能掉了一截
openEuler 22.03 默认内核是 5.10,CentOS 7 是 3.10。内核大版本跳了两个,有些 sysctl 参数要么改名了,要么默认值变了。
我把旧的 sysctl 配置直接拷过来,sysctl -p 的时候报了三个错。
# CentOS 7 能用但 openEuler 报错的参数
net.bridge.bridge-nf-call-ip6tables = 1 # 模块名变了
net.bridge.bridge-nf-call-iptables = 1 # 同上
net.ipv4.neigh.default.gc_thresh3 = 8192 # 默认值已调大,不需要手动设
# openEuler 正确写法
net.bridge.bridge-nf-call-ip6tables = 1 # 需先 modprobe br_netfilter
net.bridge.bridge-nf-call-iptables = 1
# gc_thresh3 默认已经是 8192,删掉即可
更隐蔽的问题是性能。我们的 Java 应用迁移后吞吐量掉了大约 15%,排查了两天才发现是 内核的透明大页(THP)策略变了。CentOS 7 默认是 always,openEuler 默认是 madvise。对于 Java 这种内存密集型应用,改成 always 之后性能立刻恢复。
# 检查当前 THP 策略
cat /sys/kernel/mm/transparent_hugepage/enabled
# 输出: [madvise] always never <- 中括号表示当前值
# 改成 always(Java 应用推荐)
echo always > /sys/kernel/mm/transparent_hugepage/enabled
# 永久生效
echo "echo always > /sys/kernel/mm/transparent_hugepage/enabled" >> /etc/rc.local
chmod +x /etc/rc.local
坑四:SELinux 策略比 CentOS 严格一截
openEuler 的 SELinux 默认是 enforcing 模式,而且策略比 CentOS 7 更严格。我们的 Nginx 要反向代理到一个非标准端口(8080),CentOS 上跑得好好的,openEuler 上直接 502。
查了半天日志,发现是 SELinux 阻止了 Nginx 连接 8080 端口。
# 查看 SELinux 拦截日志
grep denied /var/log/audit/audit.log | tail -20
# 查看 Nginx 允许连接的端口
semanage port -l | grep http_port_t
# 输出里没有 8080
# 添加 8080 到允许列表
semanage port -a -t http_port_t -p tcp 8080
# 或者临时关闭 SELinux 排查(不推荐生产长期关闭)
setenforce 0
教训:很多人遇到 SELinux 问题第一反应是 setenforce 0,这不叫解决问题,这叫掩耳盗铃。正确做法是逐条排查 audit 日志,用 audit2allow 生成自定义策略。多花半小时,但安全底线保住了。
坑五:systemd 服务脚本行为变了
我们有个 Java 应用用 systemd 管理启动,service 文件里写了 LimitNOFILE=65536。CentOS 上一直正常,openEuler 上应用启动后文件句柄上限还是 1024。
查了半天才知道,openEuler 的 systemd 版本更新了,LimitNOFILE 的生效逻辑变了。新版本要求同时设置 LimitNOFILE=65536 和 LimitNOFILESoft=65536,否则 soft limit 不生效。
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/app/bin/startup.sh
LimitNOFILE=65536
LimitNOFILESoft=65536 # openEuler 必须加这行
LimitNPROC=65536
LimitNPROCSoft=65536 # 同理
# 改完后重载
systemctl daemon-reload
systemctl restart myapp
# 验证进程的实际 limit
cat /proc/$(pidof java)/limits | grep "Max open files"
类似的问题还有 TimeoutStartSec,CentOS 默认 90 秒,openEuler 默认 120 秒。如果你的服务文件里没显式设置,行为就不一样。Java 应用启动慢的,这个差异可能导致你以为启动成功了其实超时了。
坑六:编译环境变了,老C程序编不过
我们有个用 C 写的流量采集程序,源码编译安装。CentOS 7 上 gcc 4.8,openEuler 上 gcc 10。版本跳了这么多,编译直接报错:一堆 implicit function declaration 的 warning 变成了 error。
原因是 gcc 10 开始默认开启 -Werror=implicit-function-declaration,老代码里没 include 头文件就调用函数的习惯,在新编译器下直接过不了。
# 方案一:Makefile 里加编译选项(快速救急)
CFLAGS += -Wno-error=implicit-function-declaration
CFLAGS += -Wno-error=int-conversion
CFLAGS += -fcommon # gcc 10 默认 -fno-common,老代码可能需要改回
# 方案二:补全缺失的 #include(治本)
# 逐个修复编译错误,加上正确的头文件引用
我先用方案一让程序编过跑起来,然后安排开发逐步修方案二。这种技术债不能一直欠着,但迁移期间优先保证业务可用。
教训:所有源码编译安装的程序,迁移前一定要在新系统上试编译一次。编译问题比运行时问题好排查,至少报错信息明确。等上了生产才发现编不过,那就只能干瞪眼了。
四、迁移后验证:一张清单保平安
每台机器迁完,我都按这张清单逐项验证,确认全部通过才切流量。
1、基础验证
2、服务验证
3、应用验证
五、速查清单:CentOS 迁移国产系统避坑指南
1、选型跟业务走,不跟热度走。容器云原生选 openEuler,等保合规选麒麟,桌面办公选统信UOS。
2、评估工具先跑一遍。x2openEuler 或类似的兼容性扫描工具能帮你提前发现 80% 的问题。
3、全新部署优于原地升级。留旧机器做回退后路,新机器验证通过后再切流量。
4、软件版本差异要逐项 diff。同名的包不同版本,默认参数可能变了,别只看"装上了没"。
5、Python 2 脚本提前迁移。用 2to3 批量转 + 人工检查,别等迁移当天才发现跑不了。
6、内核参数别无脑拷贝。大版本跳跃后参数名和默认值可能变了,sysctl -p 逐条验证。
7、SELinux 别直接关。用 audit2allow 生成策略,保住安全底线。
8、systemd 服务文件检查 limit 配置。新版本可能需要同时设置 hard 和 soft limit。
9、源码编译的程序提前试编。gcc 版本跨度大,编译报错比运行报错好排查。
10、验证清单逐项过。基础、服务、应用三层验证,全过了才切流量。
20 台服务器,前后花了 6 周,踩了 6 个坑。说实话,迁移本身不难,难的是那些藏在细节里的差异。包名一样版本不一样、参数一样默认值不一样、配置一样行为不一样——这些才是真正费时间的地方。
但换个角度想,CentOS 停服是不可逆的趋势,早迁早安心。趁着还有时间从容地迁,总比等出了安全漏洞被逼着临时迁要好。
作者丨Ваня
来源丨公众号:码农庄园的运维师(ID:gh_385167a730aa)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
下一篇:没有了