在 K8s 中模拟时钟偏移而不影响节点上的其他容器
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Chaos Mesh 是一个易于使用的开源云原生混沌工程平台,专为 Kubernetes(K8s)设计。其新增的 TimeChaos 功能可模拟时钟偏移现象。通常当我们在容器中修改时钟时,需要将影响范围最小化,避免影响节点上的其他容器。然而在实际操作中,实现这一目标可能比想象中更困难。Chaos Mesh 是如何解决这个问题的?
本文将介绍我们如何探索多种时钟偏移模拟方案,以及 Chaos Mesh 的 TimeChaos 如何实现容器内时间的自由偏移。
在节点上模拟时钟偏移而不影响其他容器
时钟偏移指的是网络中节点间时钟的时间差异。它可能导致分布式系统可靠性问题,是复杂分布式系统设计者和开发者关注的重点。例如在分布式 SQL 数据库中,必须保持节点间本地时钟同步,才能实现一致的全局快照并确保事务的 ACID 特性。
目前已有公认的时钟同步解决方案,但未经充分测试,你永远无法确保实现方案的健壮性。
那么如何测试分布式系统的全局快照一致性?答案显而易见:通过模拟时钟偏移,测试分布式系统在异常时钟条件下能否保持一致的全局快照。虽然部分测试工具支持在容器中模拟时钟偏移,但它们会对物理节点产生影响。
TimeChaos 能够在容器内模拟时钟偏移,测试其对应用程序的影响,同时确保不会波及整个节点。这样我们就能精准定位时钟偏移的潜在影响并采取对应措施。
我们探索过的时钟偏移模拟方案
审视现有方案后,我们发现它们均不适用于运行在 Kubernetes 上的 Chaos Mesh。两种常见的时钟偏移模拟方式——直接修改节点时钟和使用 Jepsen 框架——都会改变节点上所有进程的时间。在 Kubernetes 容器环境中,若注入影响整个节点的时钟偏移错误,同节点上的其他容器将受到干扰,这种粗放式方案显然不可接受。
那么该如何解决这个问题?我们首先想到的是在内核层通过 Berkeley Packet Filter(BPF)寻找解决方案。
LD_PRELOAD
LD_PRELOAD 是 Linux 环境变量,用于指定程序执行前优先加载的动态链接库。
该变量具备两大优势:
-
无需了解源码即可调用自定义函数
-
能向其他程序注入代码实现特定功能
对于调用 glibc 时间函数的语言(如 Rust 和 C),使用 LD_PRELOAD 足以模拟时钟偏移。但 Golang 的情况更为复杂——这类语言直接解析虚拟动态共享对象 (vDSO) 这种加速系统调用的机制。由于需要获取时间函数地址,我们无法直接通过 LD_PRELOAD 拦截 glibc 接口。因此 LD_PRELOAD 并非可行方案。
使用 BPF 修改 clock_gettime 系统调用的返回值
我们还尝试用 BPF 过滤任务进程标识符 (PID),从而在指定进程中模拟时钟偏移并修改 clock_gettime 系统调用的返回值。
这看似可行,但我们发现一个问题:在大多数情况下,vDSO 加速了 clock_gettime 的调用,但 clock_gettime 本身不会发起系统调用。这个方案同样行不通。哎呀。
庆幸的是,我们确认当系统内核版本 ≥4.18 且使用 HPET 时钟时,clock_gettime() 会通过常规系统调用(而非 vDSO)获取时间。基于此我们实现了时钟偏移方案,该方案在 Rust 和 C 中运行良好。但对 Golang 而言,程序虽能正确获取时间,若在时钟偏移注入期间执行 sleep 操作,线程极易被阻塞——即使取消注入,系统仍无法恢复。因此我们最终放弃了此方案。
TimeChaos:我们的终极方案
前文可知程序通常通过调用 clock_gettime 获取系统时间。由于 clock_gettime 利用 vDSO 加速调用,我们无法用 LD_PRELOAD 劫持 clock_gettime 系统调用。
找到症结后,解决方案浮出水面:从 vDSO 切入。若能重定向 vDSO 中存储 clock_gettime 返回值的地址到自定义地址,问题便迎刃而解。
知易行难。实现此目标需解决以下问题:
-
获取 vDSO 使用的用户态地址
-
获取 vDSO 的内核态地址(用于通过内核态地址修改 vDSO 中的
clock_gettime函数) -
掌握 vDSO 数据修改方法
首先探查 vDSO 内部结构。通过 /proc/pid/maps 可查看 vDSO 内存地址。
$ cat /proc/pid/maps
...
7ffe53143000-7ffe53145000 r-xp 00000000 00:00 0 [vdso]
末行为 vDSO 信息,其内存空间权限标记为 r-xp(可读、可执行但不可写)。这意味着用户态无法修改此内存,但可通过 ptrace 突破该限制。
接着使用 gdb dump memory 导出 vDSO,并通过 objdump 解析其内容:
(gdb) dump memory vdso.so 0x00007ffe53143000 0x00007ffe53145000
$ objdump -T vdso.so
vdso.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
ffffffffff700600 w DF .text 0000000000000545 LINUX_2.6 clock_gettime
可见整个 vDSO 类似 .so 文件,可采用可执行与可链接格式 (ELF) 进行解析。基于此,TimeChaos 的基础实现流程逐渐清晰:

上图展示了 TimeChaos(Chaos Mesh 的时钟偏移实现方案)的工作流程。
-
使用 ptrace 附加到指定 PID 进程以暂停当前进程
-
使用 ptrace 在调用进程的虚拟地址空间中创建新映射,并通过
process_vm_writev将自定义的fake_clock_gettime函数写入内存空间。 -
利用
process_vm_writev将指定参数写入fake_clock_gettime,这些参数即要注入的时间偏移量(例如向后偏移两小时或向前偏移两天)。 -
使用 ptrace 修改 vDSO 中的
clock_gettime函数指针,将其重定向至fake_clock_gettime函数。 -
通过 ptrace 解除对 PID 进程的附着。
若需了解实现细节,请参见 Chaos Mesh GitHub 仓库。
在分布式 SQL 数据库中模拟时钟偏移
实践是检验真理的最佳标准。我们将对 TiDB(一个开源的 NewSQL 分布式 SQL 数据库,支持 HTAP 混合负载)进行 TimeChaos 测试,验证混沌工程的实际效果。
TiDB 通过中心化的时间戳授时服务 (TSO) 获取全局一致性版本号,确保事务版本号单调递增。TSO 服务由 Placement Driver (PD) 组件管理。因此我们随机选择一个 PD 节点,定期注入 TimeChaos(每次产生 10 毫秒的时钟回拨),观察 TiDB 能否应对这一挑战。
测试使用 bank 作为负载工具,该工具模拟银行系统的转账交易行为,常用于验证数据库事务的正确性。
测试配置如下:
apiVersion: chaos-mesh.org/v1alpha1
kind: TimeChaos
metadata:
name: time-skew-example
namespace: tidb-demo
spec:
mode: one
selector:
labelSelectors:
"app.kubernetes.io/component": "pd"
timeOffset:
sec: -600
clockIds:
- CLOCK_REALTIME
duration: "10s"
scheduler:
cron: "@every 1m"
测试过程中,Chaos Mesh 每隔 1 毫秒向选定 PD Pod 注入 TimeChaos,持续 10 秒。在此期间 PD 获取的时间将与实际时间产生 600 秒偏移。详见 Chaos Mesh 文档。
执行 kubectl apply 命令创建 TimeChaos 实验:
kubectl apply -f pd-time.yaml
通过以下命令查看 PD 日志:
kubectl logs -n tidb-demo tidb-app-pd-0 | grep "system time jump backward"
日志输出如下:
[2020/03/24 09:06:23.164 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585041383060109693]
[2020/03/24 09:16:32.260 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585041992160476622]
[2020/03/24 09:20:32.059 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585042231960027622]
[2020/03/24 09:23:32.059 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585042411960079655]
[2020/03/24 09:25:32.059 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585042531963640321]
[2020/03/24 09:28:32.060 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585042711960148191]
[2020/03/24 09:33:32.063 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043011960517655]
[2020/03/24 09:34:32.060 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043071959942937]
[2020/03/24 09:35:32.059 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043131978582964]
[2020/03/24 09:36:32.059 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043191960687755]
[2020/03/24 09:38:32.060 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043311959970737]
[2020/03/24 09:41:32.060 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043491959970502]
[2020/03/24 09:45:32.061 +00:00] [ERROR] [systime_mon.go:32] ["system time jump backward"] [last=1585043731961304629]
...
从日志可见 PD 会周期性检测到系统时间回拨,这表明:
-
TimeChaos 成功模拟了时钟偏移现象
-
PD 能够正确处理时钟偏移场景
这一结果令人振奋。那么 TimeChaos 是否会影响 PD 以外的服务?可通过 Chaos Dashboard 验证:

监控面板清晰显示:TimeChaos 以 1 毫秒间隔持续注入 10 秒。更重要的是,TiDB 服务未受任何影响——bank 程序正常运行且性能指标保持稳定。
立即体验 Chaos Mesh
作为云原生混沌工程平台,Chaos Mesh 提供覆盖 Kubernetes 复杂系统的全方位故障注入能力,涵盖 Pod、网络、文件系统乃至内核层的故障模拟。
想体验混沌工程的实战操作?欢迎使用 Chaos Mesh。这份10分钟教程将带您快速入门混沌工程,并运行您的第一个 Chaos Mesh 混沌实验。