1. 项目起底:为什么是“北科为”与“欧拉Linux”
先把这个标题拆开说。在实际的国产化替代和自主可控项目里,我们经常遇到一类需求:某单位内部要搭建一套基于国产操作系统的应用环境,但对系统的安全性和可控性要求极高,不能使用在线公共源,甚至不能连接外网。这种场景下,“北科为”这个标签通常指的是一类定制化、面向特定业务场景的国产化Linux系统构建方案,而“欧拉Linux”则是大家熟知的openEuler系操作系统的通称。
我在实际项目中接触过好几次类似的配置:客户要求底层系统必须使用国产化内核,同时要保证离线环境下能够完成安装、依赖补齐和后续运维。说实话,很多刚接触这块的人第一反应是“不就是装个系统吗”,但真正做起来才发现,这里面的坑从镜像选型、分区规划到repo源配置、依赖裁剪,每一步都能让你折腾到深夜。
这篇文章不聊虚的,直接讲清楚一件事:在纯内网、无外网、强管控环境下,怎么把一套“欧拉系”的定制Linux系统从零搭起来,并且让它能跑稳、能维护、能升级。适合谁看?适合正在做国产化替换的运维工程师、刚接手某内网项目系统交付的同学,以及那些被“离线安装依赖”折磨过的人。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 镜像选型与前置环境准备
2.1 选版本:不要盲目追求新内核
欧拉系目前常见的有几个分支,比如标准服务器版、高级服务器版,以及社区版。很多人在选版本时习惯性选最新的,但在内网生产环境,我的建议是:
- 优先选长期支持版本,而不是最新版本。原因很简单,长期支持版本的内核和用户态工具链经过更多轮次的修复,而且第三方软件(比如数据库、中间件)的适配验证通常也是基于这些版本做的。
- 关注内核版本与硬件兼容性。如果你用的是新出的服务器芯片,可能需要较新的内核才能正确识别;但如果是存量设备,老版本更稳妥。
- 尽量保持大版本一致。比如选定了某个大版本,后续所有节点的系统都用同一版本,避免不同节点之间glibc版本不一致导致编译出来的程序分发困难。
我踩过一次亏:某项目最开始用了最新版本,结果发现客户现场有一套老旧的RAID卡,新内核里对应的驱动模块有问题,数据盘死活识别不了。后来换回长期支持版本,一切正常。所以,选版本之前,务必先摸清现场硬件清单。
2.2 准备安装源:离线环境的第一道坎
无论你用哪种方式安装(光盘、U盘、PXE),都要提前准备好软件源。这一步最常见的错误是:以为安装完系统就完事了,结果后面装数据库、编译软件时发现缺一堆依赖,又没法联网下载,直接卡死。
正确做法是准备一套完整的离线repo包。具体来说:
-
在有外网的环境下,找一台同架构的机器,用工具同步整个欧拉系的基础源和EPOL源。需要同步的目录通常包括
BaseOS、EPOL、extras(如果有的话)。同步工具可以用reposync或dnf reposync,注意保留repodata元数据目录,不然内网机器上用dnf安装软件时会报找不到元数据。 -
把同步下来的数据打包,传到内网机器上。如果是多台机器,建议放到一台内部HTTP服务器或NFS服务器上,统一对外提供服务,避免每台机器都存一份大文件。
-
在内网每台机器上,配置一个本地repo文件,指向内网源地址。这里有个细节:repo文件的
gpgcheck建议设成0,因为离线环境下你大概率没有导入GPG公钥,如果设成1而公钥缺失,安装包时会被拒绝。
bash复制# /etc/yum.repos.d/local.repo 示例
[BaseOS]
name=BaseOS
baseurl=http://192.168.1.10/repo/BaseOS
enabled=1
gpgcheck=0
[EPOL]
name=EPOL
baseurl=http://192.168.1.10/repo/EPOL
enabled=1
gpgcheck=0
提示:如果你不想搭HTTP服务,也可以直接用
file://协议指向本机挂载的ISO或同步目录,但多台机器管理起来比较麻烦,能上HTTP还是上HTTP。
2.3 分区规划:给系统盘和业务盘留够余地
再强调一次,生产环境的分区规划,不能按默认来。默认安装通常会把大部分空间分给根分区,但实际运行中你可能会遇到:
- 日志文件写满根分区(尤其是一些不轮转的中间件日志)
- 临时目录
/tmp被大文件占满 - 容器镜像路径
/var/lib/containers空间不够
我的习惯做法:
| 挂载点 | 建议大小 | 说明 |
|---|---|---|
/boot |
1GB | 内核和引导文件,不需要太大 |
/ |
50GB~100GB | 系统根分区,别贪心 |
/var |
单独分区,50GB以上 | 日志、容器数据、缓存都在这里 |
/data 或 /opt |
剩余空间 | 业务数据与自定义应用 |
如果你的机器内存大(比如256GB以上),还建议把 swap 单独分出来,或者至少留一个swap文件作为兜底。有些国产化数据库要求必须有swap,不信你装的时候就会看到它的预检查直接报错。
3. 定制系统构建:从最小化安装到内核参数调优
3.1 最小化安装的原则
定制系统的第一步是做最小化安装。什么叫最小化?不是说你只装个最小的Server包就行,而是要结合业务需求选包。比如你是用来跑数据库的,那就连图形界面、桌面相关的包一概不装;跑容器平台的话,可以加上容器运行时需要的工具,但别装KVM虚拟化那一套(除非你真的需要)。
实用的做法是安装时选择“带开发工具”的服务器环境,这样后面编译软件时,gcc、make、glibc-headers 这些东西都在,省去后面折腾依赖的麻烦。如果你担心包太多太杂,可以安装完后用 dnf history 查看安装了哪些包,再用 dnf autoremove 清掉不需要的。
3.2 内核参数调整:不要照抄网上的“性能优化脚本”
网上流传的那些内核参数“大全”,直接搬过来用往往出问题。不同场景下,适合的参数组合完全不同。这里只挑几个最关键的说:
vm.swappiness
这个参数控制系统使用swap的倾向,默认是30。对数据库、中间件这类应用,建议调低到10左右,减少不必要的内存换页开销。但如果你的业务是内存吃紧型(比如跑Java服务),反而不要调太低,免得内存不足时进程被OOM kill。
bash复制echo "vm.swappiness = 10" >> /etc/sysctl.d/99-custom.conf
sysctl -p /etc/sysctl.d/99-custom.conf
net.ipv4.ip_local_port_range
如果你的机器会发起大量对外连接(比如作为负载均衡后端、调用外部API),默认的端口范围可能不够用,容易被 Cannot assign requested address 折磨。建议放宽:
bash复制net.ipv4.ip_local_port_range = 1024 65535
fs.file-max 与 ulimit
这两个是经典配置,但很多人只改了系统级参数而忘记改进程级。实际上,你还需要检查你启动服务的用户(比如 mysql、es 用户)的 /etc/security/limits.conf:
code复制mysql soft nofile 65535
mysql hard nofile 65535
只改内核参数不改limits文件,等于白改,进程一样会被文件描述符限制卡住。
vm.max_map_count 和 vm.overcommit_memory
如果你要跑Elasticsearch这类服务,vm.max_map_count 默认值65530通常会触发“max virtual memory areas vm.max_map_count [65530] is too low”错误,需要调高到262144以上。而 vm.overcommit_memory 要结合业务来判断,设成1表示允许内存超卖,适合跑大量较小内存进程的场景;设成0(默认)则更保守,适合内存需求明确的服务。
bash复制vm.max_map_count = 262144
注意:内核参数不是越多越好,每加一个参数都要确保自己清楚它的用途,否则系统运行几天后出现诡异的性能问题,排查起来相当痛苦。
3.3 内核模块与设备驱动的裁剪
定制化系统通常需要对内核模块做裁剪,减小系统体积、降低攻击面。但这步操作的风险等级很高,做得不好可能直接导致重启后找不到硬盘或者网卡不见了。
我的建议:非特殊情况,不要删除内核模块文件,只需在 /etc/modprobe.d/ 下创建黑名单配置,屏蔽不用的模块即可。比如你用不到无线网卡,可以这样屏蔽:
bash复制# /etc/modprobe.d/blacklist-wireless.conf
blacklist cfg80211
blacklist mac80211
如果你确实想压缩内核模块体积,等系统跑稳定了再动这个,而且一定要留一个可用内核,并且做一次重启验证后再继续。
4. 核心功能实现:软件仓库、用户权限与系统服务
4.1 搭建内网软件仓库:一劳永逸解决依赖问题
前面提到了离线repo的准备,这里展开说一下实际搭建过程。我通常会在内网挑一台性能一般但有较大磁盘的机器做仓库服务器:
- 安装Nginx或者直接用Python的
http.server提供目录访问。Nginx的好处是支持并发高、方便加访问限制,但如果只是十几台机器使用,Python那个也够用了。
bash复制# 简单起见,用Python在仓库目录起服务
cd /data/repo
nohup python3 -m http.server 8080 --bind 0.0.0.0 > /var/log/repo-server.log 2>&1 &
- 使用
reposync同步源。这个东西会根据repo文件配置去下载所有RPM包到本地。建议加--download-metadata参数,这样本地的repodata也会同步下来,后续dnf才能正常工作。
bash复制reposync --repoid=BaseOS --download-metadata -p /data/repo/BaseOS
reposync --repoid=EPOL --download-metadata -p /data/repo/EPOL
如果同步过程中断,可以重新执行,reposync 会自动跳过已经下载过的包。
- 定期更新。虽然内网环境没有外网源,但如果你们有内部的安全补丁分发渠道,可以把补丁包合并到仓库中,再统一分发,这样所有机器只需执行
dnf upgrade就能同步更新。
4.2 用户权限管理:sudo规则与SSH加固
系统装完,第一件事不是测试业务,而是做用户权限和访问控制。这个环节如果疏忽,后面出了安全事故责任就大了。
- 禁用root远程登录。所有管理操作通过普通用户 + sudo 来完成。命令很简单:
bash复制echo "PermitRootLogin no" >> /etc/ssh/sshd_config
systemctl restart sshd
- 创建带sudo权限的部署用户。注意,不要把
/etc/sudoers直接改成给某个用户NOPASSWD: ALL,除非你能确认这台机器不会有其他人登录。更稳妥的做法是建一个用户组wheel,然后在/etc/sudoers里限制为:
code复制%wheel ALL=(ALL) ALL
这样每次sudo都要求输入密码,虽然多一步操作,但日志审计里能记录到是谁执行的命令。
- 配置SSH免密登录时,一定要限制来源IP和执行的命令。比如你打算从跳板机免密到应用服务器执行部署命令,那就在目标机器的
~/.ssh/authorized_keys里加上from="192.168.1.100"前缀,再限制要执行的命令。这些细节虽然繁琐,但关键时候能救命。
4.3 系统服务管理:用systemd把业务全面托管
内网环境下,我们很忌讳用 nohup 启动后台进程,一方面是没有统一的管理手段,另一方面进程崩溃后不会自动拉起。
我自己在定制系统时,习惯把所有业务进程都写成systemd服务单元文件。举一个实际例子:
ini复制# /etc/systemd/system/myapp.service
[Unit]
Description=My Custom Application
After=network.target
[Service]
User=appuser
Group=appgroup
ExecStart=/opt/myapp/bin/start.sh
ExecStop=/opt/myapp/bin/stop.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
这里有个细节,很多人会漏掉:一旦设置了 Restart=always,你执行 systemctl stop myapp 时,如果进程因为超时没退出,systemd可能会反复拉起它,导致出现“明明stop了又自己起来了”的错觉。
解决办法是设置 TimeoutStopSec,比如:
ini复制TimeoutStopSec=30
如果一个应用在30秒内还不能优雅退出,那就直接kill,避免僵死。另外,ExecStop 里最好写上明确的停服逻辑,而不只是发一个信号让进程自己处理,否则有些进程不管你发什么信号都无视,最终只能靠超时强杀。
4.4 容器镜像制作:把应用固化下来
现在做系统交付,越来越离不开容器。基于欧拉系定制的系统,通常也会要求支持容器化部署。
关于容器镜像,我想强调一点:不要直接拿 docker run 或者 podman run 出来的容器当生产环境用,你至少要做这几件事:
- 使用多阶段构建,把构建工具链和运行环境分开,减小镜像体积。
- 确定基础镜像的版本,不要用
latest标签。基于欧拉系,我可以选择某欧拉容器基础镜像作为底层,加上自己需要的运行库。 - 镜像里尽量不使用root用户运行进程。
这里给一个基础镜像制作的Dockerfile思路(当然,如果你用的是podman,命令基本一样):
dockerfile复制FROM openeuler-22.03-base:latest AS build
RUN dnf install -y gcc make libuuid-devel && dnf clean all
WORKDIR /src
COPY . .
RUN make
FROM openeuler-22.03-base:latest
# 把编译好的二进制拷入运行环境
COPY --from=build /src/myapp /usr/local/bin/myapp
# 设置时区,安装必要运行库
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& dnf install -y libuuid && dnf clean all
# 创建非root用户
RUN useradd -r -s /sbin/nologin appuser
USER appuser
ENTRYPOINT ["myapp"]
容器里的欧拉系镜像默认没有设置时区,容器跑起来之后日志时间会差8个小时,排查问题时非常痛苦,所以时区设置这步别省。
5. 实战落地方案:从裸机到业务的完整部署记录
5.1 自动安装:用kickstart实现无人值守
如果只有一两台机器,手动安装还能接受。但客户现场往往有几十台甚至上百台服务器,一台台光驱安装能把你心态搞崩。所以定制系统必须配kickstart(KS)文件,实现无人值守安装。
这里给一个简化的KS文件示例,注意看分区和软件包选择部分:
kickstart复制# /root/ks.cfg 简版示例
install
text
url --url="http://192.168.1.10/repo/BaseOS"
lang en_US.UTF-8
keyboard us
timezone Asia/Shanghai
rootpw --iscrypted $6$...省略...
auth --enableshadow --passalgo=sha512
firstboot --disable
selinux --enforcing
services --disabled="firewalld"
# 分区方案
clearpart --all --initlabel
part /boot --fstype="xfs" --size=1024
part pv.01 --size=1 --grow
volgroup vg_sys pv.01
logvol / --fstype="xfs" --name=lv_root --vgname=vg_sys --size=51200
logvol /var --fstype="xfs" --name=lv_var --vgname=vg_sys --size=51200
logvol /data --fstype="xfs" --name=lv_data --vgname=vg_sys --size=1 --grow
# 软件包
%packages
@base
@development
vim
tar
%end
# 安装后脚本
%post
echo "nameserver 114.114.114.114" > /etc/resolv.conf
echo "192.168.1.10 repo.local" >> /etc/hosts
curl -o /etc/yum.repos.d/local.repo http://192.168.1.10/ks/local.repo
%end
几个关键点:
- ks文件里的密码字符串一定得是加密后的,不要直接写明文密码。用
grub-crypt或python3 -c 'import crypt; print(crypt.crypt("yourpassword", crypt.mksalt(crypt.METHOD_SHA512)))'生成。 url --url指向的是内核包所在的目录。如果你用的是ISO文件,需要先把ISO挂载或解压到一个能通过HTTP访问的位置。- 安装源地址如果配置错了,安装过程会在读取包元数据时报错,这种情况通常不是网络不通,而是路径不对,优先检查HTTP目录列表里能不能看到
repodata文件夹。
5.2 等保加固操作脚本的核心思路
在政企客户这边,系统交付后一般都要做等保合规检查。这块内容虽然枯燥,但绕不开。通常的加固项包括:
- 口令策略:默认密码有效期、复杂度要求
- 登录失败锁定策略(通过PAM配置)
- 禁用不必要的系统服务
- 开启审计日志(auditd)
- 内核参数中关闭IP转发(如果这台机器不是路由器)
- SSH配置里调整空闲超时时间
有些配置项直接写配置文件就行,比如:
bash复制# /etc/ssh/sshd_config 增加
ClientAliveInterval 300
ClientAliveCountMax 2
# /etc/login.defs 增加
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_WARN_AGE 7
要注意的是,加固动作不能影响应用运行。比如你做了口令过期策略后,如果部署服务用了某个系统账号且密码已经过期,很可能导致应用启动时JDBC连接失败。所以加固前先列出所有应用依赖的系统账号,把这些账号设置成 chage -M -1(永不过期),只对人工登录账号启用密码过期策略。
5.3 备份与回滚:有个快照,心里不慌
在定制系统过程中,最容易翻车的就是改内核参数和升级软件包。所以我前期的习惯是,在做任何可能有风险的变更之前,先做一个系统备份或快照。
物理机一般靠备份工具做分区镜像;虚拟机的话,直接打快照最省事。
软件包升级前,用 dnf install 或 dnf upgrade 时想办法记录当前系统所有RPM包的版本快照:
bash复制rpm -qa --qf "%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n" > /data/backup/rpm-list-$(date +%F).txt
如果升级后发现问题,就可以根据这个清单,用 dnf downgrade 把相关包降回去。
6. 常见问题与排查技巧实录
6.1 离线安装依赖时的无限循环
这是我遇到最多的一个问题:安装软件A时提示缺依赖B,安装B时又提示缺A的某个子依赖。这种依赖循环在离线环境下非常揪心。
我的处理思路:
- 先用
dnf deplist或rpm -qpR查看软件包的完整依赖列表 - 找一台能上网的机器,用
dnf download --resolve把RPM包和依赖全部下载下来,再传到内网 - 遇到缺少某个库文件的情况(比如
libxxx.so.1),用yum provides */libxxx.so.1反查它是属于哪个包
还有一个既笨但很有效的办法:把整个EPOL源同步下来,即使某个包不在BaseOS里,在EPOL里基本都能找到。
6.2 systemd服务启动失败但日志为空
这个问题也常见。你执行 systemctl start app,它提示失败,但 journalctl -u app 看不到任何输出。很多时候是因为服务启动过程中直接fork了子进程,而主进程提前退出,systemd认为启动失败。
排查思路:
- 用
systemctl status app -l看完整状态输出。 - 如果状态里显示
status=0/SUCCESS,但进程就是没起来,检查一下你的ExecStart脚本里是否用了daemon或nohup这类后台启动方式。在systemd里,前台运行是铁律,任何导致启动命令提前返回的行为都会让systemd误判。 - 解决办法是把启动脚本改成直接前台运行,或者使用
Type=forking并配合PIDFile=参数,但后者如果PID文件路径不对,照样起不来。
6.3 网络配置重启后丢失
欧拉系默认网络管理工具可能是NetworkManager,也可能是systemd-networkd,取决于你安装时选择的包组。如果你发现配置的网络在重启后丢失,通常是因为:
- 你改了
/etc/sysconfig/network-scripts/ifcfg-xxx但没执行nmcli connection reload或systemctl restart NetworkManager - 网卡设备名在重启后可预测性命名规则下发生变化,配置对应不上
我的习惯做法:如果是固定IP的环境,干脆把NetworkManager停用,改用network服务,并且通过 uuidgen 生成固定UUID写入ifcfg文件。这样做虽然笨,但最稳定。
bash复制systemctl disable NetworkManager
systemctl enable network
systemctl start network
6.4 内核参数修改后无法启动
这个情况属于重大事故级别了,但也不是没法恢复。如果你在 /etc/sysctl.d/ 里写了一个搞崩溃的内存参数(比如把 vm.max_map_count 写成了不合理的上限),重启后大概率进不了系统或启动后立即Oops。
恢复方法:在GRUB引导界面按 e 进入编辑模式,在 linux 那行末尾追加:
text复制sysctl.conf=/dev/null
这个参数会让系统在启动阶段忽略所有sysctl配置文件,以默认内核参数启动。进入系统后,把配置改回来,再重启验证。
注意:这个操作需要你在服务器现场或者有IPMI带外管理,否则纯靠远程SSH是没法操作的。所以修改内核参数之前,一定确认自己能否物理访问机器。
7. 把过程固化成文档与经验模板
做这类定制系统项目,最怕的是人走事亡。系统交付完,过几个月客户找你问“当时这个分区是怎么分的”“那个内核参数为什么要改”,你要是没留文档,只能再花时间重新排查。
所以我坚持做两件事:
第一,把每一步操作都记录到一个部署手册里,包括命令、输出、遇到什么问题、怎么解决的。不用写得多华丽,但一定要能让人照着走一遍就能复现整个过程。
第二,把KS文件、repo配置、sysctl配置、systemd单元文件这些内容放到一个配置仓库里统一保存。每次项目交付,复制一份全新模板,再根据现场调整。这样下次遇到类似需求,直接基于模板改,而不是从零开始。
我在实际项目里试过,维护这么一套模板,初期虽然要花时间整理,但后面每次交付至少能省出两到三天的排查和调试时间。尤其是内核参数和设备兼容性这部分,有了历史记录,就能很快判断当前硬件会不会有同样的坑。
8. 最后再分享几个现场经验
回到标题说的“北科为 欧拉Linux”,如果你也是在做这种“从零到一”的国产化系统构建,有几句实在话想告诉你们:
第一,别迷信“最新版”。在国产化这个领域,稳定压倒一切。新内核、新工具链带来新特性,但也带来新bug,尤其在老硬件和特殊外设上,问题特别多。能用旧版满足需求,就别给自己找事。
第二,离线不是“断网”这么简单。你需要提前把系统安装源、软件依赖源、安全补丁源、容器镜像全都准备好,还要考虑怎么分发到几十台机器上。唯一庆幸的是,这些工作一次做好,后面所有机器都能复用。
第三,任何一步改动都要有回滚方案。不管是内核参数、系统服务还是软件包,改之前想清楚出了问题怎么恢复。别等到系统起不来才临时翻文档。
这个项目做下来,最大的体会就是:定制系统没有太多高深的技术,拼的是细心和周全。把每个环节的细节都想到,把每个命令的含义都吃透,系统的稳定性和可维护性自然就有了。
以后如果你们也要做类似的系统交付,希望这篇文章能帮你们少踩几个坑。特别是离线仓库和无人值守安装这两块,提前准备好,后面能省下大把的时间和头发。
