下述为 UCloud 资深工程师邱模炯在 InfoQ 架构师峰会上的演讲——《UCloud 云平台的内核实践》中非常受关注的内核热补丁技术的一部分。给大家揭开了 UCloud 云平台内核技术的神秘面纱。
如何零代价修复海量服务器的 Linux 内核缺陷?
对于一个拥有成千上万台服务器的公司,Linux 内核缺陷导致的死机屡见不鲜。让工程师们纠结的是,到底要不要通过给服务器升级内核来修复缺陷?升级意味者服务器重启、业务中断以及繁重的准备工作;不升级则担心服务器死机,同样造成业务中断和繁重的善后工作。
而在今天的云计算时代,一台宿主机往往运行多个云主机,每一次重启不管是主动升级还是被动死机,都意味着中断其上运行的所有云主机。因此,宿主机内核缺陷的修复更加棘手。
而作为一个支撑着上万家企业用户 IT 基础架构的云服务商,UCloud 云平台上的海量宿主机又是如何修复内核缺陷的呢?
邱模炯透露,如果按照传统的重启方式来修复,那么无论是对于 UCloud 或是用户,都意味着繁重的运维和业务中断。但是,UCloud 通过“内核热补丁技术”——即给运行中的内核打上二进制补丁,UCloud 已经做到了零代价免重启修复海量服务器的内核缺陷!目前为止,UCloud 对所发现的上游内核 10+ 个缺陷全以热补丁方式修复,累计数万台次,无一例失败且无任何副作用;理论上避免了相应次数的宿主机重启及所隐含的云主机业务中断。这项技术在 UCloud 已经成熟。
UCloud 内核热补丁技术揭秘
UCloud 的热补丁技术基于多年前的开源 ksplice 加以定制优化而来,通过加载一个特殊准备的热补丁模块来修复内核。其过程如下图所示:
热补丁模块由 ksplice 程序编译生成,包含有缺陷的二进制指令和修复后的二进制指令(这些二进制按函数级别组织);模块加载后,自动定位到内核的缺陷处并以修复指令动态替换缺陷指令。
ksplice 热补丁模块的创建原理见下图:
首先获取一份运行中内核对应的源码并编译出二进制,称为 pre 对象;打上源码补丁再次编译,称为 post 对象。而运行中的内核二进制称为 run 对象。post 和 pre 逐条指令比较并找出存在差异的函数,之后把这些差异合并为内核模块形式的热补丁。
创建好的热补丁模块在加载到内核时还会做些检验工作:对比 pre 和 run 对象。只有通过检验才能成功加载进内核。pre-run 比较的目的是为了辨别编译过程差异是否过大以致于不能打入 post 对象的热补丁;更重要的是,从 pre-run 差异中提取的关键信息才能把 post 对象的热补丁顺利打入运行中内核。
热补丁模块加载到内核后,便自动进行内核修复。也就是使用热补丁中的二进制指令替换有缺陷的二进制指令。这里 ksplice 利用了 Linux 内核的 stop_machine 机制:停止所有 CPU 的执行,只留下主 CPU 进行二进制指令替换。值得注意的是,stop_machine 后如果发现任何一个线程栈的内容与热补丁存在冲突,就需要退出指令替换以避免系统崩溃。所以并非所有热补丁都能打入内核,有些频繁使用的内核函数(如 schedule, hrtimer 相关)就无法热补丁,重试次数再多也无济于事。
ksplice 同时支持对内核和模块进行热补丁,也支持热补丁后叠加热补丁,灵活方便。不过也存在一些缺陷:stop_machine 期间整个系统处于中断状态,虽然单次中断小于 1ms,但有些时候多次重试的累计中断也不小;另外,有些频繁使用的函数无法打入热补丁。
kpatch 和 kgraft kpatch 和 kgraft 均是近期新出现的内核热补丁技术,前者属于 Redhat 公司,后者 SuSE。两者原理和 ksplice 大致相同,都想合并进 Linux 内核,内核社区正在争议对比。
kpatch 原理和前面讲的 ksplice 很接近。最大的区别在于二进制指令替换,stop_machine 停止所有 CPU 执行后 ksplice 直接修改,而 kpatch 则借助 ftrace 机制来触发替换。目前的实现上,kpatch 有较大局限性,不支持对模块打热补丁,不支持函数静态变量等。
kgraft 原理也基本一样。主要的差异有两点:
1)热补丁生成方法不同; 2)热补丁打入内核过程里 kgraft 用到了 RCU 渐进方法。得益于 RCU 方法,kgraft 无需像 ksplice 和 kpatch 一样调用 stop_machine 并检查线程栈的冲突。不过它的缺点也缘于 RCU,涉及到数据结构改变时,kgraft 更难通过编写辅助代码打入热补丁,这限制了 kgraft 的应用范围。
有关 kpatch 和 kgraft 的详细情况请分别参考 Redhat 和 SuSE 网站的技术资料。
除了免重启修复,热补丁还用于内核开发过程的性能分析和故障定位。比如,加上性能统计代码生成热补丁,就可以在线分析感兴趣的性能问题;加入额外调试代码捕捉运行中内核的异常。这些非常有用,更是海量服务器里捕捉不可重现内核异常的不二法宝。由于热补丁不需要重启服务器,既可打入也可撤销,所以不会有副作用。
UCloud 对开源 Ksplice 的优化主要在以下三个方面:
支持高版本内核 热补丁技术与内核紧密耦合。不同版本的内核在指令结构体,符合表结构体和一些特性上(比如早期内核没有 ftrace)有所不同,直接影响热补丁成败。UCloud 研究了各版本内核的区别,使得同一份 ksplice 支持各个版本的 Linux 内核。值得一提的是,解决了 ftrace 与 ksplice 不兼容的问题。
允许热修复频繁调用的函数 不管什么样的热补丁技术,两种类型的内核函数难以热补丁:频繁使用的内核函数如 schedule, hrtimer;经常处于线程栈内核部分顶部的函数,如 sys_poll, sys_read。UCloud 更改了 ksplice 相关内核代码和用户态工具,成功解除了这些限制,比如 UCloud 现网服务器已打入了三个 hrtimer 热补丁。
减少业务中断时间 ksplice 是在 stop_machine 后替换二进制指令的。虽然单次 stop_machine 对业务造成的中断在一毫秒左右,但有些频繁使用的内核函数需要大量重试才能碰到合适的热补丁时机,于是会造成最长达上百毫秒的中断。UCloud 在此做过一点优化,使得业务中断时间控制在十毫秒级别。
海量服务器环境下热补丁技术可用来零代价且无副作用地修复内核缺陷,而且内核开发也因热补丁能走得更远更好。以前因为缺乏辅助分析手段和惧怕内核 BUG,即使适合在内核实现的特性也被告诫移到用户态实现,然而有了热补丁,相关观念也可以适当调整,内核开发也可以更加大胆和跳脱。