告别CentOS:RHEL 9.4与Ubuntu 24.04选型对比及迁移实战

那年我们公司启动了一个新项目,需要搭建一套支撑内部核心业务系统的私有云环境。当时摆在面前的第一个问题就是:操作系统选什么?CentOS 7 已经在不少老机器上跑着,但 CentOS 8 停服、CentOS Stream 转向滚动更新后,我们这些一线开发就不得不重新评估企业级 Linux 的选型。我当时的核心诉求很明确:系统必须稳定支持到 2029 年以后,安全补丁要跟得上,且能无缝对接我们现有的自动化运维体系。

经过两周的测试与对比,我把目标锁定在了 RHEL 9.4(2024年5月发布)和 Ubuntu 24.04 LTS(2024年4月发布)。这两个版本都是 2024 年最新的企业级稳定版,支持周期都足够长。

我为什么这么选?RHEL 9.4 的优势在于它的商业支持和企业级生态。我们的核心数据库集群跑在 Oracle 和 SAP HANA 上,这些软件官方对 RHEL 的兼容性认证是最全的。而且 RHEL 9.4 默认搭载了 Linux Kernel 6.9 的特性,对 Intel 第四代至强处理器和 DDR5 内存的管理更精细。但代价是订阅费用,对于拥有 50 台物理机的我们来说,这是一笔不小的开支。

Ubuntu 24.04 LTS 则是另一种逻辑。它的软件包更新,内核默认就是 6.8 版本,对 AI 推理所需的 NVIDIA 驱动支持非常顺滑。我们在做 AI 算力节点(跑 Spark 和 PyTorch)时,Ubuntu 的社区文档和第三方库(如 Python 3.12 的各类依赖)安装起来比 RHEL 省心太多。更重要的是,它是免费的,且 Pro 订阅比 RHEL 便宜不少。

最后我们的落地方案是:核心交易数据库用 RHEL 9.4,AI 和 Web 前端节点用 Ubuntu 24.04 LTS

在迁移过程中,我遇到的一个实际问题是:老系统用的是 Python 3.6,而新系统默认是 Python 3.9 或 3.12,导致不少自动化脚本挂了。下面是我写的一个迁移兼容性检查脚本,专门用来在 RHEL 9.4 环境下批量检查并安装缺失的依赖,确保业务不中断:

#!/bin/bash # 文件名: migrate_check.sh # 场景:RHEL 9.4 环境下检查旧应用依赖 # 执行环境:RHEL 9.4, Python 3.12 log_file="/var/log/migrate_check.log" python_apps=("/opt/order_service" "/opt/user_api") echo "开始检查 RHEL 9.4 迁移兼容性 - $(date)" | tee -a $log_file # 检查 Python 版本及依赖 for app in "${python_apps[@]}"; do if [ -d "$app" ]; then echo "检查应用目录: $app" | tee -a $log_file # 检查是否使用了旧的 yaml 库 if grep -r "import yaml" $app > /dev/null; then echo "检测到 PyYAML 依赖,正在安装 Python 3.12 版本..." | tee -a $log_file # RHEL 9.4 使用 dnf 安装,注意包名变化 sudo dnf install -y python3.12-PyYAML >> $log_file 2>&1 fi # 检查 SELinux 上下文,RHEL 9.4 默认 enforcing if [ "$(getenforce)" == "Enforcing" ]; then echo "SELinux 开启,修正 $app 目录上下文" | tee -a $log_file sudo semanage fcontext -a -t httpd_sys_content_t "$app(/.*)?" sudo restorecon -Rv $app >> $log_file 2>&1 fi fi done echo "检查完成,请查看日志: $log_file"

这段脚本在 RHEL 9.4 上跑的时候,我发现有几个点特别要注意。RHEL 9.4 默认开启了 SELinuxenforcing 模式,如果直接把旧系统的代码目录拷贝过来,往往会因为安全上下文不对导致 Nginx 或 Gunicorn 无法读取文件。如果不做上面的 semanagerestorecon 操作,你会看到 403 或者 500 错误,而且很难排查。另外,RHEL 9.4 的 dnf 包名规则和 Ubuntu 的 apt 完全不同,Python 包通常带有版本号后缀,这也是我为什么在脚本里明确写 python3.12-PyYAML 的原因。

---

百万日活电商系统CPU飙升200%:从top到eBPF的排查实录

今年 3 月,我们的电商系统正准备迎接一次大促。系统架构是标准的微服务:Nginx 做网关,后面接 20 个 Spring Boot 实例,数据库用的是 MySQL 8.0,缓存是 Redis 7.2。系统在平时 CPU 利用率稳定在 15% 左右,QPS 大概在 2000。

大促开始后的第 10 分钟,告警系统突然提示订单核心服务的 CPU 使用率飙升到了 200%(8 核 16 线程的虚拟机,满载大概是 1600%)。当时我第一反应是流量突增,但看了 Nginx 的访问日志,QPS 虽然涨了,也就到了 5000,远没到压测的上限(我们压测能抗住 12000 QPS)。

我先用 top 看了下,发现是一个叫 java 的进程占用了 180% 的 CPU。接着用 top -H -p 查看线程,发现有两个线程 ID(比如 0x4a2b0x4a2c)占用极高。

# 第一步:定位高 CPU 进程 top # 发现 PID 13452 的 java 进程占用 180% # 第二步:查看该进程下的线程 top -H -p 13452 # 观察到线程 18987 和 18988 占用 CPU 最高 # 第三步:把线程 ID 转成 16 进制(因为 Java 线程栈里用的是 nid=0x...) printf "%x\n" 18987 # 输出 4a2b

拿到 0x4a2b 这个 ID 后,我执行了 jstack 13452 | grep -A 20 'nid=0x4a2b'。结果让我很困惑,这个线程显示是在做 HashMap.put 操作。我检查了代码,那里并没有明显的死循环。

这时候 top 已经给不了我更多信息了。我需要看到内核层面的系统调用情况。我在这台 Ubuntu 24.04 LTS 的机器上启动了 bcc 工具集(基于 eBPF 技术),这是 2024 年排查性能问题的利器。

我用了 execsnoopprofile 两个工具。特别是 profile,它能通过 eBPF 在内核态采样,生成类似火焰图的数据,而且开销极低(不到 1%),不像 perf 那样对高负载服务器有入侵性。

# 安装 bcc 工具(Ubuntu 24.04 默认内核 6.8 支持) sudo apt install bpfcc-tools # 使用 profile 工具抓取 CPU 栈 # 每隔 1 秒采样一次,持续 10 秒,只针对 PID 13452 sudo /usr/sbin/profile -p 13452 10 1 > /tmp/flamegraph.txt # 也可以使用 trace 来追踪具体的函数调用 # 比如我们怀疑是锁竞争,追踪 futex 调用 sudo /usr/sbin/trace 'p::SyS_futex { printf("futex call by %s (pid: %d)\n", comm, pid); }' -p 13452

分析 profile 输出的结果时,我发现大量的 CPU 时间消耗在 pthread_mutex_locktryLock 上。结合 trace 的输出,我终于找到了根因:那个 HashMap 是在一个多线程共享的上下文里,虽然代码里没写死循环,但是因为并发扩容导致链表成环,加上一个定时任务在疯狂重试获取锁,造成了逻辑上的死循环。

如果不使用 eBPF 技术,我可能只能靠猜或者反复重启服务。eBPF 让我看到了内核层面的真实行为。修复方案很简单,把那个 HashMap 换成了 ConcurrentHashMap,发布后 CPU 立刻降回了 20%。这次经历让我意识到,在 Linux Kernel 6.9 及更高版本中,eBPF 已经成为了系统可观测性的事实标准,像 Cilium 这样的网络方案也是基于这个原理,以后排查问题真的离不开它。

---

从Shell到Ansible:基于IaC的自动化运维体系搭建与压测对比

几年前,我们管理 10 台服务器的时候,我习惯写一堆 Shell 脚本。比如要部署一个新的 Nginx 配置,我就写个 deploy_nginx.sh,里面用 scp 拷贝文件,然后 ssh 进去执行 nginx -s reload。当时觉得挺快,几分钟搞定。

直到上个月,我们要扩容到 80 台服务器,并且需要同时部署三个环境(开发、测试、生产)。我试着跑了一下那个 Shell 脚本,结果悲剧了。因为其中两台机器的 SELinux 配置不一样,导致 nginx -s reload 权限不足,脚本直接报错退出,但其他机器已经执行成功了。我陷入了“配置漂移”的泥潭:有的机器装了 vim,有的没装;有的 firewalld 开着,有的关着。

这时候我意识到,必须转向 基础设施即代码 (IaC)。我选择了 Ansible,而不是 Terraform,因为我们的服务器是物理机托管,Terraform 更适合云资源的编排,而 Ansible 对存量服务器的配置管理更轻量。

我搭建了一套基于 Ansible 的自动化体系。核心逻辑是:所有操作必须是幂等的。也就是说,不管你运行这个脚本多少次,结果都是一样的。比如,如果我用 Shell 写 yum install nginx,跑两次没问题,但如果我写 echo "foo" >> /etc/hosts,跑两次就会加两行。

下面是一个我实际编写的 Ansible Playbook,用于在 RHEL 9.4 和 Ubuntu 24.04 混合环境下配置 Web 服务器,并做 TCP 参数优化以应对高并发:

--- - name: 配置高并发 Web 服务器 hosts: all become: yes vars: # 根据 OS 家族区分包管理器 is_ubuntu: "{{ ansible_facts['os_family'] == 'Debian' }}" is_rhel: "{{ ansible_facts['os_family'] == 'RedHat' }}" tasks: - name: 安装 Nginx (Ubuntu) apt: name: nginx state: latest update_cache: yes when: is_ubuntu - name: 安装 Nginx (RHEL) dnf: name: nginx state: latest when: is_rhel - name: 优化 TCP 连接参数 (应对高并发) sysctl: name: "{{ item.key }}" value: "{{ item.value }}" state: present reload: yes loop: - { key: 'net.core.somaxconn', value: '4096' } - { key: 'net.ipv4.tcp_tw_reuse', value: '1' } - { key: 'net.ipv4.ip_local_port_range', value: '1024 65535' } notify: - Restart Nginx - name: 部署自定义 Nginx 配置 template: src: ./nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' notify: - Restart Nginx handlers: - name: Restart Nginx systemd: name: nginx state: restarted enabled: yes

这个 Playbook 我跑了 80 台机器,只用了 12 秒。为什么这么快?因为 Ansible 是基于 SSH 并行执行的(默认并发 5 个,我调成了 20 个)。

我做过一个对比压测。场景是:模拟 5000 个并发用户访问一个静态页面。

这种一致性是 Shell 脚本给不了的。而且,Ansible 的 handlers 机制很聪明,只有配置文件真的发生了变化,才会触发 Restart Nginx。如果不这么做,每次都重启服务,线上业务就会一直断连。这就是我为什么放弃写 Shell 而选择 Ansible 的原因:它把运维操作变成了一个严谨的工程,而不是碰运气的手工活。

4. 云原生时代的监控革命:Prometheus + eBPF深度可观测性实战

去年我们团队接手了一个基于 Kubernetes 的电商交易系统,部署在 Ubuntu 24.04 LTS 节点上。当时遇到一个棘手的问题:大促期间订单服务的 P99 延迟突然从 120ms 飙升到 800ms,但传统的 tophtop 显示 CPU 和内存使用率都很正常。这让我意识到,在微服务架构下,传统的监控手段已经不够用了。

为什么需要 eBPF?传统监控工具要么依赖内核探针(开销大),要么只能看到进程级指标。而 eBPF 技术允许我们在内核中运行沙箱程序,以极低的开销(通常小于 1% 的 CPU 占用)获取深度可观测性数据。我们当时在 Linux Kernel 6.9 上部署了 Cilium 1.15,它利用 eBPF 实现了网络流量的细粒度观测。

具体落地时,我采用了 Prometheus 作为指标存储,配合 eBPF 导出器。这里有一个关键判断:为什么不直接用 Prometheus 的 node_exporter?因为 node_exporter 看不到容器间的网络调用关系。我们当时的场景是,订单服务调用支付服务时出现超时,但不知道是网络问题还是服务问题。

实际部署时,我写了这样一个配置:

# prometheus-ebpf-config.yaml scrape_configs: - job_name: 'cilium-ebpf' static_configs: - targets: ['cilium-agent:9962'] # Cilium 1.15 的 eBPF 指标端点 scrape_interval: 5s metrics_path: /metrics - job_name: 'order-service' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_label_app] regex: order-service action: keep

这个配置让我能同时看到基础设施指标和业务指标。有一次排查发现,支付服务的 TCP 重传率在正常负载下达到了 0.5%(正常应低于 0.1%),最终定位到是 Kubernetes 节点的 kube-proxy 规则冲突。如果不上 eBPF,这种网络层问题可能要花好几天才能发现。

eBPF 的另一个优势是动态追踪。我们曾遇到一个内存泄漏问题,传统工具只能看到进程内存增长,但用 eBPF 的 bpftrace 工具,我直接追踪到了是某个 Go 协程在频繁分配 16KB 的临时对象:

# 追踪 order-service 进程的内存分配 sudo bpftrace -e 'uretprobe:/usr/local/bin/order-service:malloc { if (retval > 0 && arg0 == 16384) { printf("PID %d allocated 16KB at %p\n", pid, retval); } }'

这个命令在 Ubuntu 24.04 LTS 上直接运行,不需要重启服务。最终发现是序列化库的一个缓存 bug,修复后内存占用从 2.3GB 降到了 1.1GB。

2024 年 eBPF 生态已经非常成熟,Cilium 现在支持基于 eBPF 的流量加密和负载均衡。我们在 RHEL 9.4 的测试环境中验证过,eBPF 的 XDP(eXpress Data Path)技术能让 DDoS 防护性能提升 3 倍,从原来的 50Gbps 处理能力提升到 150Gbps。

5. 零信任安全加固:SELinux与SSH密钥管理的企业级落地实践

去年我们帮一家金融客户迁移到 Rocky Linux 9.3(现在是 9.4),他们有个硬性要求:必须通过等保三级认证。当时我面临的选择是,要么用传统的 iptables 做网络隔离,要么上 SELinux 实现真正的零信任。我选了后者,因为金融系统的数据库被入侵过一次,损失了 300 多万,他们再也不敢只靠边界防护了。

为什么 SELinux 比防火墙更重要?防火墙只能控制网络层的访问,而 SELinux 能限制进程的行为。比如我们的 MySQL 进程,即使用 root 用户运行,在 SELinux 的 mysqld_t 域下,也只能访问 /var/lib/mysql 目录,不能去读 /etc/passwd。去年有个真实的攻击案例:攻击者通过 Web 漏洞上传了 Webshell,但因为 SELinux 的 httpd_t 域限制,Webshell 无法执行 system() 调用,攻击链在这里就断了。

实际配置时,我没有直接用 enforcing 模式,而是先用了 permissive 模式收集日志。这里有一个细节:RHEL 9.4 的 SELinux 策略比老版本更严格,我们当时遇到 PHP-FPM 无法写入会话文件的问题,排查发现是 httpd_var_run_t 类型不对。

解决过程是这样的:

# 1. 查看拒绝日志 sudo ausearch -m AVC -ts recent # 2. 分析具体拒绝原因 sudo audit2allow -a /var/log/audit/audit.log # 3. 生成自定义策略模块 sudo audit2allow -a -M my-phpfpm sudo semodule -i my-phpfpm.pp

这个过程中,我发现 Ubuntu 24.04 LTS 的 AppArmor 和 SELinux 有冲突,最终建议客户统一用 RHEL 9.4 系。SSH 密钥管理方面,我们当时有个运维人员用密码登录跳板机,结果被撞库攻击。后来我强制推行了 ED25519 密钥,因为 RSA 3072 在量子计算面前已经不够安全了。

具体实施时,我写了这个自动化脚本:

#!/bin/bash # 强制 SSH 密钥认证并禁用密码登录 # 适用于 RHEL 9.4 / Rocky Linux 9.4 # 1. 生成 ED25519 密钥对(如果不存在) if [ ! -f /root/.ssh/id_ed25519 ]; then ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" -C "root@$(hostname)" fi # 2. 修改 SSH 配置 sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config echo "PermitRootLogin prohibit-password" >> /etc/ssh/sshd_config # 3. 重启 SSH 服务 systemctl restart sshd # 4. 设置 SELinux 上下文(关键!) restorecon -Rv /root/.ssh/

这个脚本在 50 台服务器上批量执行,把密码爆破攻击从每天 2000 多次降到了 0。但有个教训:第一次部署时,我没注意 SELinux 上下文,导致密钥文件被 sshd 进程拒绝读取。后来用 ls -Z /root/.ssh/ 检查,发现应该是 ssh_home_t 类型,而不是默认的 user_home_t

零信任的另一个层面是网络微隔离。我们用 firewalld 替代了 iptables,因为它支持动态区域管理。在 Kubernetes 环境中,我们结合 Cilium 的网络策略,实现了 Pod 间的零信任通信。有一次测试发现,即使攻击者拿到了某个 Pod 的权限,也无法访问数据库 Pod,因为 Cilium 的 eBPF 网络策略在内核层就拦截了非法流量。

6. AIOps落地尝试:利用LLM辅助分析ELK日志与故障自愈设计

今年初我们开始尝试 AIOps,起因是运维团队每天要处理 2000 多条告警,其中 80% 都是重复的。当时我们的 ELK 集群(Elasticsearch 8.12 + Logstash 8.12 + Kibana 8.12)每天产生 500GB 日志,人工分析根本看不过来。我决定引入 LLM 来辅助分析,不是为了替代人,而是为了过滤噪音。

为什么需要 LLM 分析日志?传统的关键字匹配太死板。比如 "connection timeout" 可能是网络问题,也可能是数据库慢查询。我们当时有个订单服务,每天有 300 多个 timeout 日志,人工排查要花 2 小时,但用 LLM 分析后发现,其中 280 个都是同一个第三方支付接口的临时抖动,不需要处理。

具体实现时,我没有直接调 OpenAI 的 API(数据合规要求),而是用开源的 Llama 3 8B 模型本地部署。架构是这样的:Logstash 把日志同时输出到 Elasticsearch 和一个 Kafka 主题,然后我写了个 Python 消费者,把日志聚合后送给 LLM 分析。

这里有个关键代码片段,展示如何用 LLM 做根因分析:

import json from langchain_community.llms import LlamaCpp from elasticsearch import Elasticsearch # 初始化本地 LLM llm = LlamaCpp( model_path="./llama-3-8b-q4.gguf", temperature=0.1, max_tokens=512, top_p=0.95, n_ctx=2048 ) # 从 ELK 获取异常日志 def get_error_logs(service_name, time_range=15): es = Elasticsearch(["http://localhost:9200"]) query = { "query": { "bool": { "must": [ {"match": {"service": service_name}}, {"range": {"@timestamp": {"gte": f"now-{time_range}m"}}} ] } }, "sort": [{"@timestamp": "desc"}], "size": 50 } res = es.search(index="logs-*", body=query) return [hit["_source"] for hit in res["hits"]["hits"]] # LLM 分析函数 def analyze_with_llm(logs): prompt = f"""分析以下微服务错误日志,给出可能的根因和排查建议: 日志内容: {json.dumps(logs, indent=2, ensure_ascii=False)} 要求: 1. 识别错误模式(如超时、异常、资源不足) 2. 给出最可能的根因(不超过3个) 3. 建议具体的排查命令或操作 输出格式:JSON""" response = llm(prompt) try: return json.loads(response) except: return {"error": "LLM 解析失败", "raw": response} # 实际调用 if __name__ == "__main__": logs = get_error_logs("order-service", 10) if logs: analysis = analyze_with_llm(logs) print("LLM 分析结果:", analysis) # 如果根因是已知的,触发自愈动作 if "数据库连接池耗尽" in analysis.get("root_cause", ""): trigger_restart("order-service")

这个方案上线后,我们把故障平均定位时间从 45 分钟降到了 8 分钟。有一次线上 Redis 连接泄漏,LLM 从 200 多条日志中识别出 "Too many open files" 和 "redis connection timeout" 的关联,直接建议检查文件描述符限制,我们按提示把 fs.file-max 从 65536 调到 131072,问题立刻解决。

但 LLM 不是万能的。有一次它把内存告警误判为 "正常业务波动",因为训练数据里没有我们这种突发流量模式。后来我加入了基于 Prometheus 指标的验证环节:如果 LLM 判断是 "资源不足",就自动检查 node_memory_MemAvailable_bytes 指标,如果确实低于 10%,才触发扩容。

故障自愈方面,我们设计了一个分级响应机制。一级自愈(自动执行)包括重启服务、清理临时文件;二级自愈(人工审批)包括扩容、配置变更。用 Ansible 实现时,我特别注意了幂等性,比如这个重启 Playbook:

- name: 重启 order-service(幂等性检查) hosts: order_servers tasks: - name: 检查服务是否真的需要重启 shell: systemctl status order-service | grep -c "active (running)" register: service_status changed_when: false - name: 仅在服务异常时重启 systemd: name: order-service state: restarted when: service_status.stdout == "0" notify: 验证服务恢复 handlers: - name: 验证服务恢复 uri: url: "http://{{ inventory_hostname }}:8080/health" return_content: yes register: health_check until: health_check.status == 200 retries: 5 delay: 10

这个设计在 Ubuntu 24.04 LTS 和 RHEL 9.4 上都验证过。根据 2024 年的趋势,AIOps 正在向 "智能体"(Agent)方向发展,我们下一步计划让 LLM 不仅能分析,还能直接调用 Ansible API 执行修复,当然这需要在严格的沙箱环境中进行。

站长实战手记

一次让我失眠的数据库迁移

去年我给一个做在线教育的客户做迁移,他们之前的论坛系统跑在一台老旧的 CentOS 7 上,磁盘 IO 已经爆表了。业务场景很明确:要把 MySQL 和 Nginx 迁移到新的 Ubuntu 22.04 环境,而且必须在周六凌晨完成,因为周日早上有个重要的直播课。

当时我太自信了,直接用了 rsync 做全量数据同步,然后打算切流量。结果在凌晨 2 点切过去后,数据库一启动,CPU 直接拉满,网站响应慢得像蜗牛。我盯着屏幕上的 top 输出看了十分钟,发现是 InnoDB 缓冲池的配置没跟着新机器的内存调整,导致疯狂的磁盘读写。

更尴尬的是,我之前为了省事,没在测试环境跑完整的压力测试,直接上了 Ansible 脚本。虽然脚本跑得很顺,但参数没针对业务优化。最后我只能紧急回滚,在那台老服务器上又多撑了两天,重新调整了 innodb_buffer_pool_sizevm.swappiness 参数,第二次迁移才平稳落地。

关于技术选型的碎碎念

对于 eBPFPrometheus,我有自己的看法。

* eBPF 确实很酷,能看透内核级别的东西。但如果你只是个日活几千的小站,装一套 Cilium 或者 Pixie 纯属给自己找麻烦。传统的 tophtop 足够你用了,别为了炫技增加维护成本。

* Ansible 很好用,但在节点少于 5 个的时候,我宁愿写个 Shell 脚本。Ansible 的 YAML 语法有时候调试起来比直接跑命令还费劲,特别是处理复杂的逻辑判断时。

给读者的真心话

学运维千万别只看文档,一定要去搞台云服务器,哪怕是最小规格的,把上面的环境搞崩几次。只有当你在凌晨三点面对黑屏白字的报错,手忙脚乱地查日志、改配置时,那些知识才会真正长在你脑子里。别怕出错,只要不是生产环境,随便折腾。