一、从"能用"到"好用":国产系统在存储场景里的那些事
这两年,国产操作系统不再是"实验室里的玩具",很多企业的核心业务开始往上面迁移。尤其是存储这块,以前大家总觉得只有Linux老牌发行版或者商业Unix才靠谱,但openEuler这几年的发展,确实让不少运维老手刮目相看。我接触过不少做存储的团队,他们最担心的其实不是性能,而是"适配过程中会不会踩坑没人管"。今天咱们就抛开那些晦涩的术语,用大白话聊聊我们在企业级存储场景下,基于openEuler做适配的真实经历。
先说结论:openEuler在存储场景下完全能打,但前提是你得摸清它的脾气。它的内核裁剪、文件系统支持、IO调度器行为,都和RHEL/CentOS有那么点微妙的差异。如果你只是把原来跑在CentOS上的脚本原封不动搬过来,大概率会碰一鼻子灰。但只要你理解了它的设计逻辑,很多问题都能迎刃而解。
二、为什么存储场景特别考验操作系统
2.1 存储的"七寸"在哪
企业级存储,说白了就是数据落盘的速度和可靠性。这里头有几个绕不开的点:首先是IO路径的长短,每多一层封装,延迟就高一点;其次是文件系统的元数据性能,碎文件多了能不能扛住;再一个是缓存策略,写缓存刷盘时机不对,断电就可能丢数据。这些都得靠操作系统底层来支撑。
举个最常见的场景:一台存储服务器,接了24块NVMe固态盘,上面跑了数十个虚拟机,每个虚拟机都在疯狂读写。这时候操作系统的IO调度器如果还在用老式的"电梯算法",那SSD的随机读能力就被废掉一大半。传统Linux默认的cfq调度器已经被淘汰了,openEuler默认用的是mq-deadline和none的组合,对于高性能NVMe来说,这个选择就很明智。
2.2 openEuler的"家底"
openEuler是华为主导、社区共建的开源系统,它的内核不是自己闭门造车,而是紧跟着上游主线走,同时加入了很多针对企业场景的优化补丁。在存储方面,它最拿手的几样东西包括:完善的NVMe支持、优化过的ext4和XFS用户态工具、以及一套基于iocost的IO带宽控制机制。尤其是iocost,它能按权重精确分配IO资源,多个应用抢硬盘的时候不会再互相拖累。
再说兼容性。openEuler提供了LTS版本(比如20.03 LTS、22.03 LTS),每个LTS都维护多年,安全补丁跟得上。这对于存储这种动辄运行五年不重启的业务来说,安全感很足。
三、我们的适配实践:从零搭建一套SAN存储控制器
3.1 场景描述
我们做的项目是一套中小型企业的SAN存储,具体功能是iSCSI Target + LVM快照 + 定期去重。硬件是这样的:双路Xeon处理器、64GB内存、两块RAID卡、12块SAS盘作为数据盘、两块NVMe作为缓存盘。软件stack我们决定全部围绕openEuler展开,只用一套技术栈,避免引入太多变数。
3.2 技术栈说明
本文所有示例均使用 openEuler 22.03 LTS + Shell脚本 + systemd + targetcli 这一套技术栈,不涉及其他编程语言。这样做的原因是让读者聚焦于系统配置和运维思路,减少语言层面的干扰。
3.3 第一步:安装openEuler并优化内核参数
安装过程就不啰嗦了,图形界面和命令行都行。关键在装完后要立刻调整内核参数。存储服务器对网络和内存的微调非常敏感。我们写了一个初始化脚本,大家可参考:
#!/bin/bash
# tunefs-storage.sh —— 存储节点内核参数优化脚本
# 适用环境:openEuler 22.03 LTS x86_64 / aarch64
# 使用方式:bash tunefs-storage.sh
set -e # 任何一步出错就停止,防止带着错误配置启动服务
# 备份原始配置,这是好习惯,改坏了能还原
SYSCTL_CONF="/etc/sysctl.d/99-storage-tuning.conf"
if [ -f "$SYSCTL_CONF" ]; then
cp "$SYSCTL_CONF" "${SYSCTL_CONF}.bak.$(date +%s)"
fi
cat > "$SYSCTL_CONF" <<'EOF'
# ---- 内存回收策略 ----
vm.swappiness = 10
# 减少交换分区使用,存储进程的page cache尽量留在内存
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5
# 脏页到达5%就开始后台刷盘,避免突然的大量写IO卡顿
# ---- 网络性能 ----
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432
# iSCSI走TCP,大缓冲区能提升吞吐量,但别超过物理内存的10%
# ---- IO请求队列 ----
# 普通机械盘让调度器合并IO,NVMe盘则推荐用none
# 注意:下面的设置是针对SAS盘的,NVMe盘请单独通过udev设置
echo "please set scheduler per-device carefully"
EOF
# 立即生效
sysctl --system > /dev/null
echo "存储内核参数已更新,请查看 /etc/sysctl.d/99-storage-tuning.conf 确认内容。"
这段脚本里的注释已经把每个参数的原因写清楚了。特别提醒一下,vm.dirty_ratio别设太高,否则断电时丢失的数据量会变大。我们是20%,你有更严格的数据安全要求,可以调到10,但是性能会下降,得自己权衡。
3.4 第二步:配置设备并绑定正确的IO调度器
openEuler默认对多队列块设备使用none调度器,这对PCIe NVMe是对的。但我们那块数据盘是SAS盘,经过RAID卡后,/sys/block/sdb/queue/scheduler里显示的是[mq-deadline] none,也就是说默认就是deadline。如果你发现某块盘被设成了bfq,最好改成none或者mq-deadline,因为bfq是为桌面交互设计的,在存储阵列上反而会带来额外的CPU开销。
怎么永久绑定?使用udev规则最稳。下面这个例子把sata接口下的机械盘统一设为mq-deadline:
# udev规则文件:/etc/udev/rules.d/60-io-scheduler.rules
# 匹配所有SATA盘,设置调度器为mq-deadline
SUBSYSTEM=="block", KERNEL=="sd*", ATTRS{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
# 匹配所有NVMe盘,设置调度器为none(其实就是内部的多队列直通)
SUBSYSTEM=="block", KERNEL=="nvme*", ATTR{queue/scheduler}="none"
写完规则后执行udevadm control --reload && udevadm trigger,再用cat /sys/block/sda/queue/scheduler验证就会发现已经变成你设置的值了。
3.5 第三步:用targetcli创建iSCSI Target
iSCSI Target是存储控制器的核心。openEuler自带的targetcli非常方便,它把一堆复杂的lun、portal、acl封装成了树形命令。假设你的数据盘是/dev/sdb(已经由LVM管理,我们把它放在VG vg_data中的LV lv_disk1),下面的脚本会创建一个名为iqn.2024-08.storage.test:array1的target:
#!/bin/bash
# setup-iscsi.sh —— 一键创建iSCSI Target和LUN
# 用法:首先确保有LVM卷,本示例中使用 /dev/vg_data/lv_disk1
ISCSI_NAME="iqn.2024-08.storage.test:array1"
DEV_PATH="/dev/vg_data/lv_disk1" # 数据LUN对应的块设备
PORTAL_IP="192.168.10.10" # iSCSI监听地址
INITIATOR_ALLOW="iqn.2024-08.com.client:host1" # 允许连接的客户端IQN
targetcli <<EOF
backstores/block create name=disk1 dev=$DEV_PATH
# 创建一个block类型backstore,名字叫disk1,后端是那个LV
iscsi/ create $ISCSI_NAME
# 创建target
iscsi/$ISCSI_NAME/tpg1/luns create /backstores/block/disk1
# 给target的TPG1添加LUN 0,映射到disk1
iscsi/$ISCSI_NAME/tpg1/portals create $PORTAL_IP 3260
# 在指定IP上开启3260端口
iscsi/$ISCSI_NAME/tpg1/acls create $INITIATOR_ALLOW
# 仅允许这个initiator连接,相当于白名单
iscsi/$ISCSI_NAME/tpg1 set attribute authentication=0
# 测试环境先关闭CHAP认证,生产环境请务必开启
saveconfig
EOF
echo "iSCSI target 配置完成,客户端可用下列命令连接:"
echo "iscsiadm -m discovery -t st -p $PORTAL_IP"
echo "iscsiadm -m node -T $ISCSI_NAME -p $PORTAL_IP -l"
这个脚本最大的好处是幂等性。你重复执行它会报"已经存在"的错误,所以我在实际项目中通常会用systemctl stop target && targetcli clearconfig --force先清理旧配置,再重新执行。另外注意,saveconfig是必须的,否则重启之后target配置就没了。openEuler的target.service默认会在开机时从/etc/target/saveconfig.json恢复配置。
3.6 第四步:让服务随系统启动
手动把target服务拉起来还不够,存储服务器重启之后必须自动恢复。我们把上面的脚本做成一个systemd单元,这样能控制依赖顺序和启动失败后的行为。
# /etc/systemd/system/iscsi-target-setup.service
# 这个服务在targetcli之前运行,主要做设备准备和参数调整
[Unit]
Description=Prepare storage devices and setup iSCSI target config
# 开机顺序上,必须等本地文件系统挂载和LVM激活完成
After=local-fs.target lvm2-activation.target
Before=target.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setup-iscsi.sh
# 如果执行失败,进入emergency模式,而不是静默忽略
RemainAfterExit=no
[Install]
WantedBy=multi-user.target
别忘了运行:
systemctl daemon-reload
systemctl enable iscsi-target-setup.service
systemctl start iscsi-target-setup.service
这样开机后,target服务会自动加载我们刚才生成的配置文件。整个链路是:文件系统就绪 → LVM激活 → 我们的配置脚本执行 → target启动 → iSCSI端口监听。
四、深入剖析几个容易翻车的环节
4.1 LVM快照和thin pool的那点事
存储场景经常要用快照做备份。openEuler的LVM版本很新,支持thin provision。但我们一开始没注意,直接创建了厚快照,结果数据量一大,快照区立马爆满。后来改成thin pool就优雅多了。下面演示怎么创建带thin pool的存储池:
#!/bin/bash
# setup-thinpool.sh —— 创建LVM thin pool及相关逻辑卷
# 注意:以下命令假设物理卷 /dev/sdb1 已经加入卷组 vg_thin
VG_NAME="vg_thin"
POOL_NAME="thinpool"
DATA_LV_SIZE="8T" # thin pool虚拟大小
POOL_LV_SIZE="5T" # 实际预分配的元数据+数据空间
THIN_VOL_SIZE="2T"
# 第一步:创建thin池,这里有个关键参数 chunk_size 默认值64KiB就够了
lvcreate -L ${POOL_LV_SIZE} -T ${VG_NAME}/${POOL_NAME} -c 128k
# 第二步:在thin池里创建一块虚拟盘 map
lvcreate -V ${THIN_VOL_SIZE} -T ${VG_NAME}/${POOL_NAME} -n vol01
# 展示逻辑卷及其池信息
lvs -a -o lv_name,lv_size,data_percent,metadata_percent ${VG_NAME}
# 建议:将自动扩展阈值设为80%,
# 这样元数据区达到80%时会自动扩展,省得出故障
lvextend --use-policies ${VG_NAME}/${POOL_NAME}
这里重点讲一下-c 128k的chunk_size。这个值越小,随机IO效率越高,但元数据占用也大;越大,块对齐简单,顺序IO吞吐好。我们在混合随机读写场景下用128K,效果不错。你要是全跑大文件视频流,弄256K也行。记住:改chunk_size只能在创建时设定,中途想改只能重建池子,所以开始就定好。
4.2 文件系统选型:ext4还是XFS?
这是老生常谈。openEuler对两者都支持得很好。在我们这个场景里,用LVM管理块设备,然后创建xfs,因为xfs对大型文件和高并发写入稍微强一点。但也有个坑:xfs不支持缩小,只能扩大。你要是规划不当,磁盘空间富裕了却没法缩回去。开发测试环境下用ext4更好,它支持收缩,万一分错了还能调。
给一条最实在的建议:数据盘用XFS,系统盘用ext4。因为系统盘要频繁更新软件包,ext4的崩溃一致性表现稳定,而数据盘要扛大IO,XFS更皮实。
# 在当前环境的另一台机器上创建文件系统的示例
# mkfs.xfs 指定block size为4096, 打开inode64特性,支持大容量
mkfs.xfs -f -b size=4096 -m reflink=1 -n ftype=1 /dev/vg_thin/thinvol
# 挂载时加上 noatime 减少元数据写入,对存储很有帮助
mount -o noatime,nodiratime,logbufs=8 -t xfs /dev/vg_thin/thinvol /data1
注释里提到的reflink=1很重要,它能实现"写时复制"的文件克隆,配合后面要讲的快照去重,能省大量空间。
4.3 IO延迟突然飙高?可能是CPU调频惹的祸
有一阵我们发现存储节点的IO延迟像过山车一样,有时候1毫秒,有时候跳成50毫秒。查遍存储链路没发现问题,最后用turbostat一看,CPU频率在波动。原来是openEuler默认电源管理策略是cpufreq的schedutil,他会在低负载时降频节省电力。但在存储场景里,零星的IO请求一来,CPU频率还没来得及提升,请求就堵在驱动层。
解决办法很简单,把CPU调频模式调整成performance:
# 查看当前调频驱动
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 设置为performance(注意:如果系统没有启用intel_pstate,则此路径可能不同)
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > "$gov"
done
# 永久生效的话,用systemd tmpfiles或自定义服务
# 这里我用systemd-tmpfiles的方案:
cat > /etc/tmpfiles.d/95-cpufreq.conf <<'EOF'
w /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - - - - performance
EOF
systemd-tmpfiles --create /etc/tmpfiles.d/95-cpufreq.conf
如果你的物理机是ARM架构,比如飞腾、鲲鹏,同样有类似的配置,只是路径名可能是scaling_available_governors。别担心,方法相通。
五、详细分析:应用场景与优缺点
5.1 最适合的应用场景
根据我们的实践,openEuler在企业级存储里最适合下面几类地方:
第一类是生产存储控制器,比如iSCSI/NFS/SMB网关。它的内核稳定,包管理工具dnf操作方便,运行时长超过半年没有出过内核panic。
第二类是备份归档节点。这类节点不需要极致的低延迟,更看重吞吐量和稳定性。openEuler对以太网bonding的支持很成熟,我们用它搭配Samba和LTFS磁带库,效率很高。
第三类是分布式存储的OSD节点。比如Ceph集群,openEuler的优势在于官方仓库里直接提供ceph相关的包,而且会跟随社区修bug。不需要像某些发行版那样自己去编译KRBD模块。
5.2 优点清单
- 快速迭代:openEuler的维护节奏紧跟开源社区,新的文件系统特性(比如erofs、fscrypt)很快就能用上。
- 安全合规:它通过了等保认证,自带一些安全强化的工具,比如
oscap配置检查,这在政府/金融客户那里加分。 - 生态兼容:很多商业存储软件(比如NexentaStor的替代品)都出了openEuler版本,厂商支持不再是问题。
- 内存管理出色:在KVM虚拟化存储场景里,openEuler的透明大页回收机制比CentOS更积极,减少了内存碎片导致的卡顿。
5.3 缺点与难题
- 资料相对较少:中文社区的问题解答虽然多,但某些冷门模块的文档还不全。好在遇到问题可以通过提issue给社区,响应速度还不错。
- 默认SELinux策略偶发奇怪:在配置targetcli时,如果没有正确设置
httpd_t或targetd_t的上下文,客户端连接会被拒绝。这个问题浪费了我们半天时间。 - 依赖部分厂商的驱动:如果是比较老的HBA卡,厂商只提供RHEL的驱动包,想在openEuler上直接用
rpm -ivh可能报错。解决办法是找社区编译版或者用DKMS框架自己编。
5.4 注意事项(血泪经验)
- 不要轻易关闭SELinux。很多人一遇到权限问题就
setenforce 0,实际是懒。你就老老实实学一下semanage fcontext -a -t targetd_var_lib_t之类的命令,把标签修好,既安全又不折腾。 - 升级内核前一定要备份modules。openEuler的内核模块和DKMS并不完全兼容,有时候升级完内核,第三方模块没跟上,机器重启后网络都起不来。建议生产环境里固定内核版本,不要天天追新。
- CLI工具变化较大:比如
ip命令取代ifconfig是常识,但fdisk和parted在openEuler里的默认单位不同,写脚本时要注意。最好用lsblk -l -o NAME,SIZE,TYPE先看一眼,再下笔。 - 监控指标别漏掉dm-era和dm-array:对于存储健康检查,
iostat只看到设备层,不能反馈dm内部状态。我们额外采集/sys/kernel/debug/dm/下的计数器,才能在坏块增多时提前预警。
六、一个更综合的示例:写一个存储健康检查脚本
为了把上面的知识点串起来,我给大家展示一个实用的健康巡检脚本。它会把CPU调频模式、内核对IO调度器的设置、目标服务状态、LVM元数据使用率汇总报告。这脚本在我们公司天天都有定时任务跑着。
#!/bin/bash
# storage-health-check.sh —— 存储节点健康巡检
# 用法:bash storage-health-check.sh [--full],--full 表示显示更详细信息
# 依赖:bash >= 4.0, targetcli, lvm2, sysstat (iostat)
FULL_MODE=false
[ "$1" == "--full" ] && FULL_MODE=true
echo "=================== 存储节点健康报告 ==================="
date "+检查时间: %Y-%m-%d %H:%M:%S %Z"
# 1. 检查CPU调频模式
echo ""
echo "【1】CPU频率调节状态"
for cpu in /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor; do
echo " 当前策略: $(cat $cpu)"
done
# 2. 检查文件系统剩余空间
echo ""
echo "【2】挂载点容量"
df -hT | grep -E "xfs|ext4" | awk '{print " "$7": "$4" 可用 ("$5"已用)"}'
# 3. 检查LVM thin池水位,防止元数据写满
echo ""
echo "【3】LVM Thin池状态"
lvs -o lv_name,lv_size,data_percent,metadata_percent --separator " | " | grep -E "thinpool|vol" || echo " 没有查找到thin池"
# 4. 检查iSCSI target运行状态
echo ""
echo "【4】iSCSI Target服务"
if systemctl is-active target.service >/dev/null; then
echo " target.service: active"
targetcli status 2>/dev/null || echo " (targetcli status不可用,但服务活跃)"
else
echo " target.service: INACTIVE —— 请立即检查!"
systemctl status target.service --no-pager | tail -n 15
fi
# 5. 检查RAID卡电池和温度(这里用smartctl做示意,实际需安装smartmontools)
if command -v smartctl >/dev/null && [ "$FULL_MODE" = true ]; then
echo ""
echo "【5】磁盘SMART摘要"
for disk in /dev/sd?; do
health=$(smartctl -H -d sat "$disk" | grep "SMART overall-health" | awk '{print $6}')
temp=$(smartctl -A -d sat "$disk" | awk '/Temperature_Celsius/{print $10}')
echo " $disk : 健康状态=$health, 温度=${temp}C"
done
fi
echo ""
echo "=================== 巡检结束(无报错即正常) ==================="
这段脚本虽然不长,但很实用。尤其那个--full参数,日常跑的时候不看温度,周末跑全量检查,考虑得很周到。有一点要注意:脚本内用了grep -E "xfs|ext4",如果文件系统是大写的,或者用btrfs,得自己改改。
七、文章总结
回头看看这次基于openEuler的适配之路,感触最深的有三点:
第一,别把"适配"当成移植。很多人觉得只要把CentOS的命令照搬就是适配,结果遇到systemd版本差异、SELinux策略、默认内核模块缺失,就会懵。适配的核心是理解openEuler的底层设计,比如它把块设备多队列、iocost控制都融入到了标准内核里,你得主动去适应这些新特性的使用方式。
第二,存储无小事,万事留退路。所有配置改动都要先备份,所有脚本都要做幂等设计,所有服务都要有systemd守护。就像前面的示例中,每次修改sysctl之前,我都先备份原文件。这不是怂,这是专业。
第三,openEuler社区的价值比想象中大。虽然它不如Debian/Ubuntu那样"通用",但当你真正遇到企业级存储的疑难杂症时,在openEuler社区提问,往往能得到比搜索引擎更针对性的答案。因为社区的很多成员就是在做类似的存储交付项目,他们会告诉你哪个版本的内核改进了XFS日志锁冲突,哪条命令在特定固件下不生效。
最后说一句掏心窝的话:国产系统不是不行,而是需要我们多给它一点耐心和靠谱的实践。当你把一个存储集群在openEuler上稳稳跑了一年,每天看到Client的IO稳稳当当,你会觉得当初的选择值。希望这篇文章能给你一些借鉴,下次轮到你自己做技术选型或者迁移评估时,心里会更踏实。
评论
围绕“基于openEuler的国产操作系统在企业级存储场景下的适配实践”参与讨论