你的Kubernetes Java应用优雅停机了吗?

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 你的Kubernetes Java应用优雅停机了吗?假如我们从 kafka 拉取数据然后生成任务处理数据,在服务退出时,如何保证内存中的数据能被正常处理完不丢失呢?假如服务是部署在 Kubernetes 中又该如何处理?Java 应用优雅停机我们首先考虑下,一般在什么场景下数据会丢失呢?升级服务时pod重启时服务器断电时

你的Kubernetes Java应用优雅停机了吗?

javayouhuiquan.jpg

假如我们从 kafka 拉取数据然后生成任务处理数据,在服务退出时,如何保证内存中的数据能被正常处理完不丢失呢?假如服务是部署在 Kubernetes 中又该如何处理?

Java 应用优雅停机

我们首先考虑下,一般在什么场景下数据会丢失呢?

  • 升级服务时
  • pod重启时
  • 服务器断电时

因为服务器断电属于极端情况,我们暂且不考虑。那就只有 Java 退出时我们要保证数据的完整性了。在 Java 中,有一个方法可以实现应用退出时候的优雅停机:shutdown hookSpring boot把这个东西封装了一下,可以通过 @PreDestroy 注解实现。当 JVM 收到退出的信号时,会调用 shutdown hook 中的方法,完成清理操作。示例代码如下:

Runtime.getRuntime().addShutdownHook(new Thread() {
  @Override
  public void run() {
    System.out.println("Start to run shutdown hook.");
  }
})

Shutdown hook 可以保证在我们代码主动调用 System.exit()OOM, 在终端执行 Ctrl+C,以及应用主动关闭等情况下时被调用。在实际的场景中,我们可以在上述的线程中执行清理操作。比如,停止 kafka 的数据消费,以及任务的及时处理等。

当我们使用 java -jar *.jar 运行 Java程序后,通过执行 kill $pid,可以发现程序确实可以优雅退出。但是当我把服务部署到 Kubernetes 时,发现这个逻辑并没有被执行,到底哪里出了问题?

在 Kubernetes 中优雅停机

当我们发送 delete 命令给 pod 时,Kubernetes 会使用优雅停机(默认30s时间),在优雅停机过程中,此 podAPI server 中会被更新为dead状态。当我们用kubectl 命令查看此pod时,它被展示为Terminating 的状态。当 Kubelet 看到 pod被标记为了 Terminating 状态时,它就会开始执行 podshutdown 程序。如果我们 pod 的容器定义了 preStop hook,那么这个 hook 会在容器中执行;与此同时,Kubelet 会向容器内发送一个TERM信号。Service也会将此 pod 从 endpoint 列表移除。当优雅停机时间过后,在 pod 里仍然存活的进程则会被SIGKILL命令杀掉。Kubelet会在 API server 里通过设置 grace period=0(立即删除)来完成 Pod 的删除操作。删除后此 Pod 会在API中消失,并且在客户端也不可见了。

以上,可以看出,我们的容器是会收到 TERM 信号的,按照常理,如果我们的 Java 进程收到了 TERM 信号是可以正常执行我们写的 shutdown hook 优雅退出的,但是这里却没有执行,很有可能是我们的 Java 进程根本就没有收到信号。

查看我们的 Dockerfile,发现我们定义的启动命令是执行一个 run.sh 的脚本,在 run.sh 脚本中,进一步执行了启动 Java 进程的命令。

# run.sh
...
sh start.sh start
...
while [1]
do 
  sleep 30
done

可以看到,我们在 run.sh 中进一步执行了 start.sh,Java 进程的启动逻辑在start.sh脚本中。我们可以执行 ps -ef 查看下当前容器中的进程

UID   PID   PPID    C   STIME     TTY   TIME    CMD
root    1   0   0 11:01   ? 00:00:00  bash ~/run.sh 
root    4084    1   8 11:01   ? 00:15:00  java -Dname=test
root    14913   1   0 13:49   ? 00:00:00  sleep 30
root    14914   0   0 13:50   pts/0 00:00:00  bash
root    14955   14914   0 13:50   pts/0 00:00:00  ps -ef

可以看到,我们运行的 run.sh 的 PID 是 1,Java 进程的 PID 是 4084,Java 进程是 run.sh 进程的一个子进程。问题就出在这里,在 pod 被删除时,TERM 信号只会发送给 1号进程,而 run.sh 接收到此信号后并不会将其转发给 Java 进程,因此 Java 便无法触发 shutdown hook,无法实现优雅退出。最终,Java 是被 SIGKILL 信号杀掉的(强制退出)。所以,我们只需要让 Java 进程作为 1号进程就行了。改写下脚本,我们把启动 Java 进程的命令放到 run.sh

# run.sh
...
exec java $JAVA_OPTS -jar ./*.jar --server.port=8080
...
while [1]
do 
  sleep 30
done

exec 的作用是被执行的命令行替换掉当前的 shell 进程。测试发现 OK,此时我们实现了优雅停机。但是,这足够优雅吗?

更优雅地停机

在上一步,我们实现了优雅停机,但是其实这并不是最优方案。我在看 start.sh 脚本中,发现此脚本定义了 start, restart, stop, status 4个方法,而且这个脚本中定义了很多额外的变量,如果我们要把之前的功能都实现的话,就需要把逻辑都搬到 run.sh 中。这无疑会增大工作量,这是不优雅的原因之一。

其次,一般是不推荐把 Java 进程作为1号进程的。因为在 Linux中,1号进程有特殊作用:1号进程会作为孤儿进程的父进程,它需要对自己的子进程进行清理回收,避免系统产生僵尸进程。bash可以很好地处理这种清理工作,我们一般自己写的 Java 程序是不会考虑这种东西的。

那么,就需要我们在 shell 中接收到 TERM 信号后把信号传递给 Java 进程了。这需要怎么做呢?我们需要使用trap命令。trap 命令的作用是捕捉信号和其他事件并执行命令。

# run.sh
...
sh start.sh start
grace_exit() {
  echo 'grace exit started'
  sh start.sh stop &
  wait $!
  echo 'grace exit finished'
}
trap 'grace_exit' TERM INT
...
while [1]
do 
  sleep 30
done

在脚本中,我们使用 trap 捕捉 TERMKubelet 发送的信号) 和 INT(快速关闭,当用户输入 Control-C时由终端程序发送) 信号,捕捉到了以后,我们执行了 grace_exit 方法,在此方法中,调用了 start.sh 脚本的 stop 方法,其实这个 stop 方法就是找到了 Java 进程,然后给其发送了 kill 命令,我们直接在 grace_exit 中执行相同逻辑也是可以的,这里是为了复用逻辑。我们还使用了 & 保证 stop 方法在后台运行,这样方便我们获取其进程号($!会返回shell最后运行的后台进程的 PID),等待其执行结束。 这样,当我们 delete``pod 时,Kubelet 发送 TERM 信号后,我们就能传达给 Java 进程,进而让 Java 进程进行优雅停机了。


标题你的Kubernetes Java应用优雅停机了吗?

作者末日没有进行曲

转载地址https://www.cnblogs.com/dengkaiting/p/15808785.html

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
5天前
|
弹性计算 Kubernetes 安全
Kubernetes 的架构问题之在Serverless Container中保障应用的安全防护如何解决
Kubernetes 的架构问题之在Serverless Container中保障应用的安全防护如何解决
44 8
|
12天前
|
机器学习/深度学习 人工智能 算法
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
本文全面介绍了人工智能(AI)的基础知识、操作教程、算法实现及其在实际项目中的应用。首先,从AI的概念出发,解释了AI如何使机器具备学习、思考、决策和交流的能力,并列举了日常生活中的常见应用场景,如手机助手、推荐系统、自动驾驶等。接着,详细介绍了AI在提高效率、增强用户体验、促进技术创新和解决复杂问题等方面的显著作用,同时展望了AI的未来发展趋势,包括自我学习能力的提升、人机协作的增强、伦理法规的完善以及行业垂直化应用的拓展等...
96 3
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
|
1天前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
6 1
|
1天前
|
安全 Java 测试技术
深入探讨Java安全编程的最佳实践,帮助开发者保障应用的安全性
在网络安全日益重要的今天,确保Java应用的安全性成为了开发者必须面对的课题。本文介绍Java安全编程的最佳实践,包括利用FindBugs等工具进行代码审查、严格验证用户输入以防攻击、运用输出编码避免XSS等漏洞、实施访问控制确保授权访问、采用加密技术保护敏感数据等。此外,还强调了使用最新Java版本、遵循最小权限原则及定期安全测试的重要性。通过这些实践,开发者能有效提升Java应用的安全防护水平。
5 1
|
4天前
|
存储 设计模式 Java
Java中的if-else语句:深入解析与应用实践
Java中的if-else语句:深入解析与应用实践
|
12天前
|
Kubernetes Cloud Native API
自动扩缩容:Kubernetes Autoscaler的神奇魔法,让你的应用在云海中遨游!
【8月更文挑战第8天】在云原生环境中,容器化与微服务架构普及的同时,应用管理复杂度也随之提升。自动扩缩容作为解决资源动态调整的关键技术,可根据负载变化自动增减资源,从而优化成本和性能。本文以Kubernetes为例,介绍其Autoscaler组件如HPA如何基于CPU使用率等指标自动调整Pod数量,并探讨如何利用自定义指标实现更灵活的自动扩缩容策略,以满足现代应用的弹性需求。
36 9
|
5天前
|
Kubernetes Cloud Native API
Kubernetes云原生问题之Kubernetes帮助业务应用较少关注底层基础设施差异如何解决
Kubernetes云原生问题之Kubernetes帮助业务应用较少关注底层基础设施差异如何解决
21 1
|
9天前
|
存储 分布式计算 Java
Java在云计算中的应用如何?
Java在云计算中的应用如何?【8月更文挑战第11天】
17 4
|
9天前
|
消息中间件 负载均衡 Java
"Kafka核心机制揭秘:深入探索Producer的高效数据发布策略与Java实战应用"
【8月更文挑战第10天】Apache Kafka作为顶级分布式流处理平台,其Producer组件是数据高效发布的引擎。Producer遵循高吞吐、低延迟等设计原则,采用分批发送、异步处理及数据压缩等技术提升性能。它支持按消息键值分区,确保数据有序并实现负载均衡;提供多种确认机制保证可靠性;具备失败重试功能确保消息最终送达。Java示例展示了基本配置与消息发送流程,体现了Producer的强大与灵活性。
28 3
|
11天前
|
Kubernetes 网络协议 Python
运维开发.Kubernetes探针与应用
运维开发.Kubernetes探针与应用
33 2

推荐镜像

更多