清理网卡前先清理相关资源:unregister_netdevice 的坑

LXC 销毁后,hyper 内核报 unregister_netdevice 网卡错误,困扰了很久。根因是网卡 down 后 IPv6 协议栈仍持有 netdevice 引用,在 hyper 中加一个 disable_ipv6=1 的操作就好了。 现象 LXC 销毁后,hyper 内核报网卡错误 unregister_netdevice,这个问题困扰了很久没有解决。 按道理,应该是清理网卡之前,清理相关资源。但我们的程序是直接 down 网卡。 后来有人在网卡 down 后加了一个清理 ipv6 neigh 的操作,然后就好了。卧槽。 排查 先看接口没 down 的时候,比如 eth0 上有 neigh: root@host:~# ip n s 10.0.0.252 dev eth1 lladdr 36:95:2f:f5:af:85 STALE 10.0.0.1 dev eth0 lladdr 02:54:d7:af:4b:cb STALE 10.0.0.253 dev eth1 lladdr 52:54:d7:c6:e8:47 STALE fe80::5054:d7ff:fec6:e847 dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE fd00:2024:822:0:167:ffff:ffff:fffd dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE fe80::e01e:53ff:fe85:5ca0 dev eth0 lladdr 02:54:d7:af:4b:cb router STALE 把接口 eth0 down 掉: ...

June 13, 2026 · 2 min · 275 words · 文叨叨

为什么新系统跑不起来?默认 qemu 指令集不够用

新系统跑不起来,报 “CPU does not support x86-64-v2”,根因是现代 OS 默认按较新指令集编译,而默认的 qemu64 模型只提供最基础指令。给虚拟机配上合适的 CPU 指令集就能解决。 现象 一台 primusos2403sp1x64 的虚拟机,没有配置 CPU 指令集的情况下,运行僵死。配置指令集后,就能正常运行了。 排查 用 virsh reset vm-example && virsh console vm-example 可以看到 glibc 打印的错误: [ 1.536627][ T1] Run /init as init process Fatal glibc error: CPU does not support x86-64-v2 [ 1.538855][ T1] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00007f00 [ 1.540681][ T1] CPU: 6 PID: 1 Comm: init Not tainted 6.6.0-103.0.0.106.oe2403sp1.x86_64 #1 [ 1.542633][ T1] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org 04/01/2014 [ 1.545203][ T1] Call Trace: [ 1.545959][ T1] <TASK> [ 1.546568][ T1] dump_stack_lvl+0x32/0x50 [ 1.547593][ T1] panic+0x304/0x340 [ 1.548411][ T1] do_exit+0x3f4/0x410 [ 1.549284][ T1] do_group_exit+0x2d/0x80 [ 1.550272][ T1] __x64_sys_exit_group+0x14/0x20 [ 1.551151][ T1] do_syscall_64+0x55/0x100 [ 1.551963][ T1] entry_SYSCALL_64_after_hwframe+0x78/0xe2 [ 1.553370][ T1] RIP: 0033:0x7f40667d3db5 [ 1.557729][ T1] RSP: 002b:00007ffcb3db2f68 EFLAGS: 00000202 ORIG_RAX: 00000000000000e7 [ 1.559088][ T1] RAX: ffffffffffffffda RBX: 00007f40667cf490 RCX: 00007f40667d3db5 [ 1.560108][ T1] RDX: 0000000000000000 RSI: 00007ffcb3db2630 RDI: 000000000000007f [ 1.561078][ T1] RBP: 00007ffcb3db3140 R08: 0000000000000010 R09: 0000000000000000 [ 1.562048][ T1] R10: 0000000000000001 R11: 0000000000000202 R12: 00007f40667e2ab0 [ 1.563025][ T1] R13: 0000000000000000 R14: 0000000000000000 R15: 00007f40667b4000 [ 1.563990][ T1] </TASK> [ 1.564790][ T1] Kernel Offset: 0x38c00000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff) [ 1.566210][ T1] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00007f00 ]--- 根因 这是已知问题:现代操作系统都开启了指令集优化,纯 qemu 的模拟指令无法满足运行的基本要求,必须配上指令集才能运行。 ...

June 7, 2026 · 2 min · 280 words · 文叨叨

热迁移后虚拟机卡死:CPU 100% 的时间中断死循环

热迁移后虚拟机卡死、CPU 100%,往往是时间处理出了问题。用 crash 看 dump 能确认死循环卡在内核路径,再用 perf 在虚拟机内和宿主机上对比,就能定位到时间中断处理异常。 现象 把一台虚拟机从 Ubuntu 20 宿主机热迁移到 Ubuntu 22 宿主机后,虚拟机卡死了。 从宿主机看,virsh migrate 是成功的,虚拟机处于 running 状态,qemu 进程也正常。但被迁移的虚拟机 CPU 占用 100%,完全无响应。 排查 用 crash 分析 virsh dump 出来的内存转储,发现内核一直在刷下面的报错,看起来是死循环卡在了调度进程 swapper 上: [7686175795.936645] Call Trace: [7686175795.936647] <IRQ> [7686175795.936651] run_timer_softirq+0x2a/0x50 [7686175795.936655] __do_softirq+0xd1/0x2c1 [7686175795.936658] irq_exit+0xae/0xb0 [7686175795.936660] smp_apic_timer_interrupt+0x7b/0x140 [7686175795.936662] apic_timer_interrupt+0xf/0x20 [7686175795.936662] </IRQ> [7686175795.936664] RIP: 0010:native_safe_halt+0xe/0x10 [7686175823.935201] watchdog: BUG: soft lockup - CPU#0 stuck for 23s! [swapper/0:0] 0 号进程就是 swapper 进程,也就是调度进程。 这种 CPU 100% 表现出来的卡死,必然是因为某种原因让 CPU 进入了死循环——CPU 被占住放不出来,无法执行其他进程,所以表现出来就是"卡死"。 ...

June 6, 2026 · 1 min · 114 words · 文叨叨

长运行与临时调整要分离

存量 VM 不切换,我们可以提供一个接口来调整虚拟机的 cpu_mode 来实现。这样长运行的逻辑简单,而调整和长运行逻辑分离,降低复杂性。 核心:长运行与临时调整分离 存量 VM 不切换。我们可以提供一个接口来调整虚拟机的 cpu_mode 来实现。 这样长运行的逻辑简单,而调整和长运行逻辑分离,降低复杂性。 一定要区分长运行,有些需求确实是真需求,但不属于长运行逻辑。 比如存储虚拟机的切换。 我们太多控制开关,就是为了做这些东西,本来是一个脚本能批量处理的事,结果把逻辑搞的异常复杂。 一个例子 对,有个接口层的。 我们一个查询接口很慢,最后排查发现这个查询接口里一行一行的在更新数据。 追溯发现,是增加了某个字段,这个字段在后面的流程里比较重要,且不能简单的设置某个值。 所以在查询的时候,通过运算实时赋值。简直了。 大哥,你弄个脚本全部跑一遍就行了啊,升级的时候执行这个脚本啊。 小结 长运行与临时调整要分离; 存量 VM 不切换,提供一个接口来调整,长运行的逻辑简单; 一定要区分长运行,有些需求是真需求,但不属于长运行逻辑; 批量、整体操作,用脚本跑一遍就行,不要把逻辑搞的异常复杂; 不要在查询接口里一行一行的更新数据。

May 24, 2026 · 1 min · 29 words · 文叨叨

架构决定稳定性:核心对象 + 流水线

架构决定下限,代码决定上限。虽然代码影响很大,但是整体的稳定性,依然是架构占大部分决定因素。linux 网络栈足够复杂,但又有足够的扩展性,且非常的高性能,为什么?因为它流水线 + sk_buff。 核心:架构决定稳定性 架构决定下限,代码决定上限。 虽然代码影响很大,但是整体的稳定性,依然是架构占大部分决定因素。 linux 网络栈(sk_buff),足够复杂,但又有足够的扩展性,且非常的高性能。 它确很稳定,为什么? 因为他流水线 + sk_buff: 它的核心对象,是高内聚的、面向对象的; 它子模块是松耦合的,通过核心对象来协作。 我们的 iaas 流程呢? 没有核心对象,全是临时去数据库查,临时到数据库更新,临时到 hyper 获取环境信息。 修改也是修改很细微的变量,而不是一个从一而终的对象。 这种构成了各种千丝万缕的关系,且环节之间直接调用,相互耦合。 缺乏一个解耦式的流水线的设计。 即便不是 k8s 那种声明式的架构,即便是流程式的、面向过程的架构,也不至于这么乱呀。 最简单的解法 最简单的,主机是一个核心对象,从创建开始就确定了的,后续就不要再改了。 volume 是核心对象,和主机绑在一起。再加个网络核心对象,和主机绑定。 全流程从头到尾,都操作这三个核心对象,且通过责任链的方式,A 环节操作完了,B 环节。 这样每个环节是解耦的,他们不关心上下环节,只关心核心对象。 这种简单、稳定多了呀。 web 框架 gin,也是这样呀,大家都操作一个 context 对象,这样各个处理模块互不耦合,且扩展性好。 小结 架构决定下限,代码决定上限; 整体的稳定性,依然是架构占大部分决定因素; linux 网络栈稳定,因为它流水线 + sk_buff,核心对象高内聚,子模块松耦合; 我们的 iaas 流程没有核心对象,全是临时去数据库查、临时更新,构成千丝万缕的关系; 主机、volume、网络是核心对象,全流程操作这三个核心对象,通过责任链的方式,每个环节解耦。

May 23, 2026 · 1 min · 52 words · 文叨叨

简单是唯一标准吗?

很多时候讨论方案,“这个改起来简单”,然后就没有人说话了。简单没有错,问题是简单就是指这一次改动的简单呢?那运维的简单性、扩展的简单性、稳定的简单性、排查的简单性呢? 现象 很多时候讨论方案,“这个改起来简单”,然后就没有人说话了。 因为这东西真有点无从反驳。 改起来简单,风险就低,你怎么反驳,又不是你改,又不是你背锅。 包括网络上有一些图,基本表达是"能跑就别动"。 但是这玩意真的对吗? 有些时候不是跑的问题,是业务发展、竞品拼杀、商业博弈,必须有做一些特性和功能,这个时候是必须要动。 简单不是唯一标准 动不是问题,问题是怎么动,衡量标准是什么。 简单没有错,问题是简单就是指这一次改动的简单呢? 那运维的简单性呢?扩展的简单性呢?稳定的简单性呢?排查的简单性呢?这些都不谈? 比如要对接第三方备份厂商,居然有其他厂商入侵到我们计算节点,并且到我们计算节点删除 img 的操作方案。 可能这次是简单了,那下次呢?下个备份厂商呢? 包括备份对接 s3 接口,多好的需求,符合生态,非常标准化。 结果硬是做成了非标能力,绑定存储,绑定各种特定流程,非得模仿老功能,依赖中转节点。 难道就不能从计算节点直接上传 s3? 数不胜数的例子,这类问题,基本都是当时评审的时候"这个方案改动小"导致的。 这类问题真无解 这类问题其实真无解,就看 leader 的品味。 因为其他队员发言是没有权威的,文无第一。 尤其是"改动小",比如你的方案实现了从计算节点直接上传备份到 s3,并且是标准 s3 接口,以后不做改动对接任何备份厂商。 很好吧,所有人都知道这是对的,但是不会有人主动去赞同,因为改动大,得背锅。 这类问题短期没有人在意,但是时间长了,赚钱的部门、商业线第一个跳出来: “你们写的代码怎么这么多限制,为什么别人支持,我们不支持,很奇怪。为什么这个支持 s3,那个不支持,很奇怪。” 不过到这依然没有结论,依然看 leader 的品味,看老板的品味。 小结 简单是唯一标准吗?不是; 简单没有错,问题是简单就是指这一次改动的简单呢; 还要考虑运维的简单性、扩展的简单性、稳定的简单性、排查的简单性; 这类问题真无解,就看 leader 的品味; 所有人都知道是对的,但不会有人主动去赞同,因为改动大,得背锅。

May 17, 2026 · 1 min · 47 words · 文叨叨

边界:计算、存储、网络要分离

拿快照来说,为啥我们支持快照的多链这么复杂?按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的。如果计算存储分离,快照局限于存储模块,计算只需要一个卷。 现象:快照的多链为什么这么复杂 拿快照来说,为啥我们支持快照的多链这么复杂? 按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的。 但是放到我们代码,复杂无比,即便是单链也异常复杂,多链简直就做不下去了。 如果分开,不过多复杂的快照链,只需要在快照模块处理。 但是我们现在的代码,各种功能和存储都在一起: 开关机要管理快照链(不是调接口,而是一行一行的命令呀); 克隆要管理快照链; 迁移对快照链的操作更多了; 回滚快照、打快照,那更复杂了(快照的管理,一行一行的命令,元数据的修改,都在各种流程中交错)。简直了! 为什么这么难改 你可能会说,改呀,重构呀。 几十万行的屎山,牵一发动全身。不是一个人就能改的了的。 而且你改的过程中,别人还在持续扩展新功能,别人要怎么配合你的修改,都是很难的事。 这种推动,一定是自上而下的。不是说一人之力不能改,而是没人敢让你上。 得需要资源做 QA 大规模测试,还需要新扩展能力的配合,还需要领导有魄力,胆大心细。 执行人能做到心细,但是执行人胆大没用,别人怕出了问题你承担不了呀,得别人背锅。 需要决策人有魄力,自顶向下的推动。 但是如果老板或决策者离一线(售后、研发)远了,很可能不看重这些。 决策者大概率觉得,能跑就行,技术是工具,能满足客户需求即可。 但实际是,很多客户因为稳定性而流失,稳定性是因为屎山代码、架构问题、代码问题。 但是这些东西不能给财务数据带来改善,销售才能改善财务数据,所以继续怼销售提的需求,继续挖坑,继续流失客户。 根因:快照和计算绑定 就是因为我们的快照的处理、链的管理,都在计算里面。 因为我们主要用的 qcow2,qcow2 的快照性能高呀。 但是它有个问题,qcow2 的快照是和计算绑定的。 它为啥性能高,因为它需要计算层联动切换读写头。 如果要计算存储分离,就需要存储提供一种快照模式,不切换读写头,就能做快照。 lvm 是一种方式,bitmap,但是它做快照后写性能很低。 不考虑性能的因素下,计算、存储分离,极大降低快照开发的复杂性。 快照局限于存储模块,甚至是存储模块的快照子模块。计算只需要一个卷,不管其背后的链。 快照模块如何实现多链 A->B->C-> active 回滚到 A 后,A->active。 这个时候,要实现 B、C 的保留,且不被影响,也不影响别人,保留且独立。 如果是 qcow2 快照,那么天然是独立的,保留卷、保留链的数据关系即可; 如果是 lvm 这种,每个快照本身是独立的,保留即可,保留链关系元数据即可。 归根到底,链关系好处理,复杂性还是在存储端,性能和空间的取舍。 数据表的设计也简单,就是一个以原卷为根的一个树结构。 小结 边界:计算、存储、网络要分离; 按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的; 计算存储分离,极大降低快照开发的复杂性; 快照局限于存储模块,计算只需要一个卷,不管其背后的链; 几十万行的屎山,牵一发动全身,这种推动一定是自上而下的。

May 16, 2026 · 1 min · 61 words · 文叨叨

可变与不可变:找到变化,封装变化

设计模式一个基本点两个原则:一个基本点是找到变化、封装变化;两个原则是基于接口而不是基于实现、组合优于继承。核心是可变与不可变的隔离、解耦。 核心:找到变化,封装变化 找到变化,封装变化。 易变与不易变的隔离,可变与不可变的隔离,稳定与不稳定的隔离。 设计模式一个基本点两个原则: 一个基本点:find what varies and then encapsulate it。找到变化、封装变化; 两个原则:基于接口,而不是基于实现;组合优于继承。 找到变化、封装变化:核心是可变与不可变的隔离、解耦。开闭原则也有这个思想。 所有的扩展性都是因为变化,分解可变与不可变作为一个基本点,没毛病。 基于接口,其实目的也是为了组合 基于接口,其实本质也是为了解耦可变与稳定,接口应该是稳定的,实现是易变的。 组合优于继承,因为继承是紧耦合的。 组合更利于可变,组合的前提是抽出来不可变的接口,即封装可变与不可变。 接口和组合,其实都是可变与不可变的实践。 通过不可变的组合实现可变。 一个例子:iaas 的调度方案 存储组,会选一个默认存储池,它会影响调度结果,所以这个也应该做到调度里。 这是一个很容易进入误区的陷阱。 实际上上层很多东西都会影响到底层呀,因为底层接收的是参数,上层参数变了,底层结果当然也会变。这不能作为划分边界的依据呀。 比如调解手机声音,不能说,腾讯音乐能调整,所以我要把腾讯音乐做到手机的扬声器模块。 我们调度,肯定是一个原子调度模块,我们提供参数,上层传参,控制调度结果。 我们提供各种单一的原子的能力,上层通过组合这些原子能力实现多样化可扩展的调度。 比如选择存储池,底层原子接口只接受存储池参数,不应该帮去改变或者帮他去选择。这是单一的原子的。如果加了选择的逻辑,就不单一了呀。 同时,底层应该是不易变的。 自动选择就是一个易变的逻辑,不同客户需求不同。但是根据存储池调度,这个是稳定的。 所以,我们只需要接受存储池参数即可。至于这个参数怎么玩出花,是上层的事。 所以存储组交给上层服务是 ok 的。 分层,哪些是产品层,哪些是应用层,哪些是基础层,边界。 基础层必然是稳定的、原子的、提供基础能力,这就是边界。 小结 找到变化,封装变化; 设计模式一个基本点两个原则:找到变化封装变化、基于接口而非实现、组合优于继承; 核心是可变与不可变的隔离、解耦; 接口应该是稳定的,实现是易变的; 通过不可变的组合实现可变; 基础层必然是稳定的、原子的、提供基础能力,这就是边界。

May 10, 2026 · 1 min · 45 words · 文叨叨

简单 + 组合,才是最强的

《UNIX编程艺术》里讲的:cmd + 管道 + 文本的设计理念,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力。单元简单,才容易组合。 核心:简单 + 组合才是最强的 《UNIX编程艺术》里面讲的: cmd + 管道 + 文本的设计理念,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力。 单元简单,才容易组合; 单元复杂,组合困难; 世界复杂,业务多元,提供简单的单元,让业务按照自己的需求自己组合,这才是终极形态。 真实世界也是这样,原子、质子,组合成万物。 小结 简单 + 组合才是最强的; cmd + 管道 + 文本,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力; 单元简单,才容易组合;单元复杂,组合困难; 提供简单的单元,让业务按照自己的需求自己组合,这才是终极形态。

May 9, 2026 · 1 min · 29 words · 文叨叨

高可用要么不做,要么做完善:fence 是必备

稳定性相关的,要么不做,要么就要完善,要不然做了高可用,反而造成雪崩。高可用必备 fence,fence 就是 ipmi 强制关机,这是最稳妥的方式。 核心:高可用要么不做,要么做完善 稳定性相关的,要么不做,要么就要完善。 要不然做了高可用,反而造成雪崩。 比如一个 hyper 负载很高,本来可能 hyper 上个别业务有影响,结果触发灾难迁移,然后又迁移失败,反而导致多业务受影响,且恢复 2 天。哪个客户能接受! 比如我们的灾难,触发了,迁移到一半又发现空间不足,迁移失败。就是不完整嘛,能不能迁移,要提前规划好,不能迁移就不要动 hyper。告警通知给用户,用户自己处理。 半死不活的状态,客户更希望给他们通知,他们自己处理,而不是我们下线 hyper。 灾难当然复杂,尤其是半死不活的状态,就是薛定谔的猫,这种确实不好处理。 所以更需要多方参照开源系统的设计,看他们背后的考量和策略,结合我们自己的业务来系统性的设计,而不是打补丁式的缝缝补补。 比如增加超时时间,比如增加个 ping 检查,比如增加个本地自检。 高可用必备 fence fence 就是 ipmi 强制关机,这就是最稳妥的方式。 其他方式都会有各种问题,且会带来其他复杂性。 比如自检,节点都有异常了,怎么保证自检能正常运行? 外部观察是通过网络观察的,所有网络如果有问题就会判断为有问题,这种情况自检是正常的,两者如何协作? 自检和外部检测是独立的,如果要协作就涉及到网络和协同,这两个本身就很复杂,再加上两个复杂的检测。 如果不协同,保持独立,那么两者的时间差会导致哪些状况?又是复杂场景。 如果采用集中式观察,采用 fence,这是业界主流的做法。 状态,状态,状态 这里面的要素是什么? node 和资源,node 和资源的状态,非常非常重要。 要通过状态在分布式环境下协同控制。 状态,状态,状态。一定要围绕最终状态,一定要维护好最终状态,一定要确保最终状态的一致性、正确性。 然后其他机制都围绕最终状态设计。 这样通过最终状态,来解耦各种前因后果,解耦异常事件和恢复处理。 为什么这两个解耦非常重要? 因为异常事件千千万,不同的异常的恢复处理也不同,形成千丝万缕,一个网状复杂性。 但是通过状态来解耦后,各种异常回归到状态,状态再对应到恢复处理。这是一个港股,两头各种线,线性复杂性。 这其实又对应到 k8s 的声明式哲学。 在当前系统已有架构下,核心状态就是两个维度: node,状态:是否正常; vm,状态:属于哪个 node,是否应该运行。 node 可能无法期望,因为硬件可能客观因素故障。vm 是有期望状态的,比如期望是运行,实际是关机的。 小结 高可用要么不做,要么做完善,要不然做了高可用反而造成雪崩; 能不能迁移要提前规划好,不能迁移就不要动 hyper; 高可用必备 fence,fence 就是 ipmi 强制关机,这是最稳妥的方式; 状态,状态,状态,一定要围绕最终状态,维护好最终状态的一致性、正确性; 通过最终状态来解耦异常事件和恢复处理,把网状复杂性变成线性复杂性。

May 3, 2026 · 1 min · 70 words · 文叨叨