• 使用 oom-guard 在用户态处理 cgroup OOM
    • 背景
    • 原理
      • threshold notify
      • 达到阈值后的处理策略
      • 事件上报
    • 使用方法
      • 部署
      • 查看 oom-guard 日志
      • 查看 oom 相关事件
      • 卸载
    • 关于开源

    使用 oom-guard 在用户态处理 cgroup OOM

    背景

    由于 linux 内核对 cgroup OOM 的处理,存在很多 bug,经常有由于频繁 cgroup OOM 导致节点故障(卡死, 重启, 进程异常但无法杀死),于是 TKE 团队开发了 oom-guard,在用户态处理 cgroup OOM 规避了内核 bug。

    原理

    核心思想是在发生内核 cgroup OOM kill 之前,在用户空间杀掉超限的容器, 减少走到内核 cgroup 内存回收失败后的代码分支从而触发各种内核故障的机会。

    threshold notify

    参考文档: https://lwn.net/Articles/529927/

    oom-guard 会给 memory cgroup 设置 threshold notify, 接受内核的通知。

    以一个例子来说明阀值计算通知原理: 一个 pod 设置的 memory limit 是 1000M, oom-guard 会根据配置参数计算出 margin:

    1. margin = 1000M * margin_ratio = 20M // 缺省margin_ratio是0.02

    margin 最小不小于 mim_margin(缺省1M), 最大不大于 max_margin(缺省为30M)。如果超出范围,则取 mim_margin 或 max_margin。计算 threshold = limit - margin ,也就是 1000M - 20M = 980M,把 980M 作为阈值设置给内核。当这个 pod 的内存使用量达到 980M 时, oom-guard 会收到内核的通知。

    在触发阈值之前,oom-gurad 会先通过 memory.force_empty 触发相关 cgroup 的内存回收。 另外,如果触发阈值时,相关 cgroup 的 memory.stat 显示还有较多 cache, 则不会触发后续处理策略,这样当 cgroup 内存达到 limit 时,会内核会触发内存回收。 这个策略也会造成部分容器内存增长太快时,还是会触发内核 cgroup OOM

    达到阈值后的处理策略

    通过 --policy 参数来控制处理策略。目前有三个策略, 缺省策略是 process。

    • process: 采用跟内核cgroup OOM killer相同的策略,在该cgroup内部,选择一个 oom_score 得分最高的进程杀掉。 通过 oom-guard 发送 SIGKILL 来杀掉进程
    • container: 在该cgroup下选择一个 docker 容器,杀掉整个容器
    • noop: 只记录日志,并不采取任何措施

    事件上报

    通过 webhook reporter 上报 k8s event,便于分析统计,使用kubectl get event 可以看到:

    1. LAST SEEN FIRST SEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
    2. 14s 14s 1 172.21.16.23.158b732d352bcc31 Node Warning OomGuardKillContainer oom-guard, 172.21.16.23 {"hostname":"172.21.16.23","timestamp":"2019-03-13T07:12:14.561650646Z","oomcgroup":"/sys/fs/cgroup/memory/kubepods/burstable/pod3d6329e5-455f-11e9-a7e5-06925242d7ea/223d4795cc3b33e28e702f72e0497e1153c4a809de6b4363f27acc12a6781cdb","proccgroup":"/sys/fs/cgroup/memory/kubepods/burstable/pod3d6329e5-455f-11e9-a7e5-06925242d7ea/223d4795cc3b33e28e702f72e0497e1153c4a809de6b4363f27acc12a6781cdb","threshold":205520896,"usage":206483456,"killed":"16481(fakeOOM) ","stats":"cache 20480|rss 205938688|rss_huge 199229440|mapped_file 0|dirty 0|writeback 0|pgpgin 1842|pgpgout 104|pgfault 2059|pgmajfault 0|inactive_anon 8192|active_anon 203816960|inactive_file 0|active_file 0|unevictable 0|hierarchical_memory_limit 209715200|total_cache 20480|total_rss 205938688|total_rss_huge 199229440|total_mapped_file 0|total_dirty 0|total_writeback 0|total_pgpgin 1842|total_pgpgout 104|total_pgfault 2059|total_pgmajfault 0|total_inactive_anon 8192|total_active_anon 203816960|total_inactive_file 0|total_active_file 0|total_unevictable 0|","policy":"Container"}

    使用方法

    部署

    保存部署 yaml: oom-guard.yaml:

    1. apiVersion: v1
    2. kind: ServiceAccount
    3. metadata:
    4. name: oomguard
    5. namespace: kube-system
    6. ---
    7. apiVersion: rbac.authorization.k8s.io/v1
    8. kind: ClusterRoleBinding
    9. metadata:
    10. name: system:oomguard
    11. roleRef:
    12. apiGroup: rbac.authorization.k8s.io
    13. kind: ClusterRole
    14. name: cluster-admin
    15. subjects:
    16. - kind: ServiceAccount
    17. name: oomguard
    18. namespace: kube-system
    19. ---
    20. apiVersion: apps/v1
    21. kind: DaemonSet
    22. metadata:
    23. name: oom-guard
    24. namespace: kube-system
    25. labels:
    26. app: oom-guard
    27. spec:
    28. selector:
    29. matchLabels:
    30. app: oom-guard
    31. template:
    32. metadata:
    33. annotations:
    34. scheduler.alpha.kubernetes.io/critical-pod: ""
    35. labels:
    36. app: oom-guard
    37. spec:
    38. serviceAccountName: oomguard
    39. hostPID: true
    40. hostNetwork: true
    41. dnsPolicy: ClusterFirst
    42. containers:
    43. - name: k8s-event-writer
    44. image: ccr.ccs.tencentyun.com/paas/k8s-event-writer:v1.6
    45. resources:
    46. limits:
    47. cpu: 10m
    48. memory: 60Mi
    49. requests:
    50. cpu: 10m
    51. memory: 30Mi
    52. args:
    53. - --logtostderr
    54. - --unix-socket=true
    55. env:
    56. - name: NODE_NAME
    57. valueFrom:
    58. fieldRef:
    59. fieldPath: status.hostIP
    60. volumeMounts:
    61. - name: unix
    62. mountPath: /unix
    63. - name: oomguard
    64. image: ccr.ccs.tencentyun.com/paas/oomguard:nosoft-v2
    65. imagePullPolicy: Always
    66. securityContext:
    67. privileged: true
    68. resources:
    69. limits:
    70. cpu: 10m
    71. memory: 60Mi
    72. requests:
    73. cpu: 10m
    74. memory: 30Mi
    75. volumeMounts:
    76. - name: cgroupdir
    77. mountPath: /sys/fs/cgroup/memory
    78. - name: unix
    79. mountPath: /unix
    80. - name: kmsg
    81. mountPath: /dev/kmsg
    82. readOnly: true
    83. command: ["/oom-guard"]
    84. args:
    85. - --v=2
    86. - --logtostderr
    87. - --root=/sys/fs/cgroup/memory
    88. - --walkIntervalSeconds=277
    89. - --inotifyResetSeconds=701
    90. - --port=0
    91. - --margin-ratio=0.02
    92. - --min-margin=1
    93. - --max-margin=30
    94. - --guard-ms=50
    95. - --policy=container
    96. - --openSoftLimit=false
    97. - --webhook-url=http://localhost/message
    98. env:
    99. - name: NODE_NAME
    100. valueFrom:
    101. fieldRef:
    102. fieldPath: status.hostIP
    103. volumes:
    104. - name: cgroupdir
    105. hostPath:
    106. path: /sys/fs/cgroup/memory
    107. - name: unix
    108. emptyDir: {}
    109. - name: kmsg
    110. hostPath:
    111. path: /dev/kmsg

    一键部署:

    1. kubectl apply -f oom-guard.yaml

    检查是否部署成功:

    1. $ kubectl -n kube-system get ds oom-guard
    2. NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
    3. oom-guard 2 2 2 2 2 <none> 6m

    其中 AVAILABLE 数量跟节点数一致,说明所有节点都已经成功运行了 oom-guard

    查看 oom-guard 日志

    1. kubectl -n kube-system logs oom-guard-xxxxx oomguard

    查看 oom 相关事件

    1. kubectl get events |grep CgroupOOM
    2. kubectl get events |grep SystemOOM
    3. kubectl get events |grep OomGuardKillContainer
    4. kubectl get events |grep OomGuardKillProcess

    卸载

    1. kubectl delete -f oom-guard.yaml

    这个操作可能有点慢,如果一直不返回 (有节点 NotReady 时可能会卡住),ctrl+C 终止,然后执行下面的脚本:

    1. for pod in `kubectl get pod -n kube-system | grep oom-guard | awk '{print $1}'`
    2. do
    3. kubectl delete pod $pod -n kube-system --grace-period=0 --force
    4. done

    检查删除操作是否成功

    1. kubectl -n kube-system get ds oom-guard

    提示 ...not found 就说明删除成功了

    关于开源

    当前 oom-gaurd 暂未开源,正在做大量生产试验,后面大量反馈效果统计比较好的时候会考虑开源出来。