服务器 pod 在合并期间被 OOMKilled

症状

一个一直运行正常的服务器 pod 被终止并反复重启,实例没有任何变化。该 pod 的最后状态为 OOMKilled,退出代码为 137。终止前的服务器日志显示内存限制异常,而当时执行的是后台合并,而不是用户查询。

在容器内存限制为 1Gi 时观察到此问题。持续重启最终会表现为 CrashLoopBackOff

原因

容器内存限制是整个服务器进程的硬上限,而不仅仅是查询的上限。后台合并会重写分片,并且需要与正在合并的分片大小成比例的工作内存,因此,按照空闲使用量或轻量查询负载设置的限制,会在首次执行大型合并时超出。终止发生在合并期间,因此看起来像是无故发生:没有客户端执行任何异常操作。

这是一个容量规划问题,而不是缺陷。该行为很容易复现——operator 自身的示例包括一个特意设置得过小的 pod 模板,内存限制为 32Mi;其文档说明的结果是,服务器被 out-of-memory 处理程序终止,并且 pod 依次进入 OOMKilledCrashLoopBackOff 状态。

确认

kubectl -n <namespace> describe pod <pod> | sed -n '/Last State/,/Restart Count/p'
kubectl -n <namespace> get pod <pod> \
  -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'

OOMKilled137 可确认内核终止了容器。

读取容器上一个实例的日志:

kubectl -n <namespace> logs <pod> -c clickhouse --previous | tail -100

检查配置的限制:

kubectl -n <namespace> get pod <pod> \
  -o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.resources.limits.memory}{"\n"}{end}'

如果服务器运行时间足够长,可以查询服务器自身跟踪的内容:

SELECT metric, formatReadableSize(value)
FROM system.asynchronous_metrics
WHERE metric LIKE '%Memory%'
ORDER BY metric;

SELECT database, table, elapsed, formatReadableSize(memory_usage) AS mem, progress
FROM system.merges
ORDER BY memory_usage DESC;

修复

提高 pod 模板中的容器内存限制并重新应用:

spec:
  defaults:
    templates:
      podTemplate: ch-pod
  templates:
    podTemplates:
      - name: ch-pod
        spec:
          containers:
            - name: clickhouse
              resources:
                requests:
                  cpu: "2"
                  memory: 8Gi
                limits:
                  cpu: "4"
                  memory: 16Gi

指导建议:

  • 不要在生产环境中以 1Gi 的限制运行实例。 应根据预期的最大合并进行容量规划,而不是根据空闲使用量。
  • 将内存请求设置为与限制相等。 这样可以使 pod 保持在 guaranteed 类别中,避免节点过度分配资源而将其终止。
  • 在常驻工作集之上预留足够余量。 合并内存是缓存以及并发运行的查询所需内存之外的额外内存。
  • 在增加 ingest 量之前提高限制,不要等到之后再提高。分片大小会随 ingest 增长,合并内存也会随分片大小增长。

此更改会一次滚动一个主机,因此请先确认第一个主机恢复,再假定整个实例已修复。

验证

kubectl -n <namespace> get pods -l clickhouse.altinity.com/chi=<instance> \
  -o custom-columns=NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount

在包含实际合并活动的时间段内观察重启次数。在负载下重启次数保持稳定,才能确认问题已解决;空闲实例上的重启次数保持稳定并不能证明任何问题,因为触发终止的是合并。

持续跟踪内存使用量与限制的关系,而不是只检查一次 — 请参阅 监控


ClickHouse 是 ClickHouse, Inc. 的注册商标。https://clickhouse.com

Alauda 是独立供应商。本产品与 ClickHouse, Inc. 没有关联,也未得到其认可或赞助。所有商标均归其各自所有者所有,此处仅用于标识目的。