在Java应用程序领域,OutOfMemoryError(OOM)问题是一个挑战,当应用程序耗尽其分配的内存时会发生。传统上,在处理标准Java设置中的OOM时,该过程涉及触发堆转储 - 即应用程序在特定时间点的内存快照。这个诊断工具通过提供应用程序内存使用情况的状态来帮助开发人员准确定位与内存相关的问题。
在Kubernetes中,当Java应用程序遇到OOM时,平台会启动重新启动以保持运行的顺利进行。与传统设置不同,在那里OOM可能导致完全关闭,Kubernetes会自动恢复,确保持续可用性。这种自动重新启动功能增加了一层复杂性,当尝试捕获堆转储时,需要一种在Pod被删除之前捕获堆转储的方法。
方法一:
在OOM发生之前触发堆转储。
可以通过多种方式实现,其中之一是使用Prometheus指标。
您可以选择一个阈值,例如JVM内存的90%,当超过该阈值时,通过Web钩子或使用Robusta等工具触发堆转储。
缺点:
- 需要开发外部Web钩子或在集群中安装Robusta
- 需要在应用程序中拥有Jmap或允许临时容器。这两个选项对于生产环境来说都不常见
方法二:
当Pod遇到OOM时创建堆转储。
但等等,我们不是刚刚说过当Pod达到JVM最大内存时它会重新启动吗?
这就是一些JVM参数派上用场的地方。通过设置以下JAVA_OPTS,我们可以在发送终止信号时创建堆转储。
JAVA_OPTS = "XX:+HeapDumpOnOutOfMemoryError"“XX:+HeapDumpOnOutOfMemoryError”
现在我们有一个在Pod内部创建的堆转储,但这对我们来说并没有太多帮助,因为当Pod重新启动时它会被删除。
为了将我们的堆转储持久化以供以后分析,我们将看几个选项。
Sidecar:
对于每个包含Java应用程序的Pod,我们可以有一个旁车,它在这些容器之间共享卷(Volume)。旁车的工作是获取堆转储并将其上传到我们希望持久化堆转储的位置。
在我们的JAVA_OPTS中,我们将添加类似以下内容的内容,将堆转储放入共享卷中。
-XX:HeapDumpPath=/etc/shared-path/${pod_name}.hprof"${pod_name}.hprof"
该架构看起来像这样:
缺点:
- 代码维护 - 现在每个Pod都需要有一个旁车和两个容器之间的挂载(可以使用Helm库实现这一点)。
- CPU和内存使用 - 即使是空闲的旁车也会有一些占用,当运行多个Pod时,这可能会迅速累积。
这两个缺点导致了另一种方法:
Network volume(e.g. NFS):
可以创建一个单一的NFS卷并挂载到所有集群节点上。这将允许我们在主机路径上挂载相关的Pod,并将堆转储定向到此路径。
-XX:HeapDumpPath=/etc/efs-mount/${pod_name}.hprof"XX:HeapDumpPath=/etc/efs-mount/${pod_name}.hprof"
现在剩下的是一个单独的Pod,它不断运行并监视NFS卷,并将其复制到我们的目标位置(例如一个S3存储桶)。
现在架构看起来是这样的:
监控 Pod 可以是一个小型 Python 应用程序,如下所示:
import boto3
import os
import logging
import time
DUMP_FOLDER = '/etc/efs-mount'
BUCKET_PATH = 'heap-dumps'
BUCKET_NAME = 'my-app-heap-dumps'
def main():
files_list = os.listdir(DUMP_FOLDER)
s3 = boto3.resource('s3')
if len(files_list) == 0:
logging.info("No Heap dumps to upload")
else:
for file in files_list:
full_file_path = '{}/{}'.format(DUMP_FOLDER, file)
logging.info("Uploading {} to {}".format(file, BUCKET_NAME))
s3.meta.client.upload_file(full_file_path, '{}-{}'.format(BUCKET_NAME),
'{}/{}'.format(BUCKET_PATH, file))
os.remove(full_file_path)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
while True:
main()
time.sleep(300)
这个解决方案提供了更便宜的成本,因为它只有一个空闲资源(监控Pod),而挂载的存储基本上是免费的(例如EFS),因为您不会存储很多数据,也不会超过几分钟。
注意事项:
对于与云存储(例如S3存储桶)进行身份验证,我建议使用类似kube2iam之类的工具。
像示例中那样,将堆转储命名为Pod名称。这个解决方案的棘手之处在于它需要放在Pod的入口点而不是清单参数中。
这并不解决由于达到Pod的限制而导致的OOM问题。