容器轻松上阵,优雅下线才是胜负之道

news2024/11/13 22:08:28

概述

优雅关闭: 在关闭前,执行正常的关闭过程,释放连接和资源,如我们操作系统执行shutdown。

目前业务系统组件众多,互相之间调用关系也比较复杂,一个组件的下线、关闭会涉及到多个组件。

对于任何一个线上应用,如何保证服务更新部署过程中从应用停止到重启恢复服务这个过程中不影响正常的业务请求,这是应用开发运维团队必须要解决的问题。

传统的解决方式是通过将应用更新流程划分为手工摘流量、停应用、更新重启三个步骤,由人工操作实现客户端不对更新感知。这种方式简单而有效,但是限制较多:不仅需要使用借助网关的支持来摘流量,还需要在停应用前人工判断来保证在途请求已经处理完毕。

同时,在应用层也有一些保障应用优雅停机的机制,目前Tomcat、Spring Boot、Dubbo等框架都有提供相关的内置实现,如SpringBoot 2.3内置graceful shutdown可以很方便的直接实现优雅停机时的资源处理,同时一个普通的Java应用也可以基于Runtime.getRuntime().addShutdownHook()来自定义实现,它们的实现原理都基本一致,通过等待操作系统发送的SIGTERM信号,然后针对监听到该信号做一些处理动作。

优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码

现状分析

现阶段,业务容器化后业务启动是通过shell脚本启动业务,对应的在容器内PID为1的进程为shell进程但shell 程序不转发signals,也不响应退出信号。所以在容器应用中如果应用容器中启动 shell,占据了 pid=1 的位置,那么就无法接收k8s发送的SIGTERM信号,只能等超时后被强行杀死了

关注--云原生百宝箱

行万里路,此处相逢,共话云原生之道。 偶逗趣事,明月清风,与君同坐。

案例分析

go开发的一个Demo

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main()  {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
    go func() {
        for s := range c {
            switch s {
            case syscall.SIGINT, syscall.SIGTERM:
                fmt.Println("退出", s)
                ExitFunc()
            default:
                fmt.Println("other", s)
            }
        }
    }()

    fmt.Println("进程启动...")
    time.Sleep(time.Duration(200000)*time.Second)
}

func ExitFunc()  {
    fmt.Println("正在退出...")
    fmt.Println("执行清理...")
    fmt.Println("退出完成...")
    os.Exit(0)
}

1、Signal.Notify会监听括号内指定的信号,若没有指定,则监听所有信号。 2、通过switch对监听到信号进行判断,如果是SININT和SIGTERM则条用Exitfunc函数执行退出。

SHELL模式和CMD模式带来的差异性

在Dockerfile中CMD和ENTRYPOINT用来启动应用,有shell模式和exec模式

使用shell模式,PID为1的进程为shell;使用exec模式PID为1的进程为业务本身。

SHELL模式
FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM ubuntu
WORKDIR /root/
COPY --from=builder /go/app .
CMD ./app

构建镜像

docker build -t app:v1.0-shell ./

运行查看

docker exec -it app-shell ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.7  0.0   2608   548 pts/0    Ss+  03:22   0:00 /bin/sh -c ./
root           6  0.0  0.0 704368  1684 pts/0    Sl+  03:22   0:00 ./app
root          24  0.0  0.0   5896  2868 pts/1    Rs+  03:23   0:00 ps aux

可以看见PID为1的进程是sh进程

此时执行docker stop,业务进程是接收不到SIGTERM信号的,要等待一个超时时间后被KILL

日志没有输出SIGTERM关闭指令

docker stop app-shell
app-shell

docker logs app-shell
进程启动...
EXEC模式
FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM ubuntu
WORKDIR /root/
COPY --from=builder /go/app .
CMD ["./app"]

构建镜像

docker build -t app:v1.0-exec ./

运行查看

docker exec -it app-exec ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  2.0  0.0 703472  1772 pts/0    Ssl+ 03:33   0:00 ./app
root          14  0.0  0.0   5896  2908 pts/1    Rs+  03:34   0:00 ps aux

可以看见PID为1的进程是应用进程

此时执行docker stop,业务进程是可以接收SIGTERM信号的,会优雅退出

docker stop app-exec
app-exec

docker logs app-exec
进程启动...
退出 terminated
正在退出...
执行清理...
退出完成...

注意: 1、以上测试在ubuntu做为应用的base镜像测试成功,在alpine做为应用的base镜像时shell模式和exec模式都一样,都是应用进程为PID 1的进程。

直接启动应用和通过脚本启动区别

在实际生产环境中,因为应用启动命令后会接很多启动参数,所以通常我们会使用一个启动脚本来启动应用,方便我们启动应用。

对应的在容器内PID为1的进程为shell进程但shell 程序不转发signals,也不响应退出信号。所以在容器应用中如果应用容器中启动 shell,占据了 pid=1 的位置,那么就无法接收k8s发送的SIGTERM信号,只能等超时后被强行杀死了。

启动脚本

cat > start.sh<< EOF 
#!/bin/sh
sh -c /root/app

dockerfile

FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM alpine
WORKDIR /root/
COPY --from=builder /go/app .
ADD start.sh /root/
CMD ["/bin/sh","/root/start.sh"]

构建应用

docker build -t app:v1.0-script ./

查看

docker exec -it app-script ps aux
PID   USER     TIME  COMMAND
1     root     0:00  /bin/sh /root/start.sh
6     root     0:00  /root/app
19    root     0:00  ps aux

docker stop关闭应用

docker stop app-script

是登待超时后被强行KILL

docker logs app-script
进程启动...

容器应用优雅关闭方案介绍

方案介绍

正常的优雅停机可以简单的认为包括两个部分:

  • • 应用:应用自身需要实现优雅停机的处理逻辑,确保处理中的请求可以继续完成,资源得到有效的关闭释放,等等。针对应用层,不管是Java应用还是其他语言编写的应用,其实现原理基本一致,都提供了类似的监听处理接口,根据规范要求实现即可。

  • • 平台:平台层要能够将应用从负载均衡中去掉,确保应用不会再接受到新的请求连接,并且能够通知到应用要进行优雅停机处理。在传统的部署模式下,这部分工作可能需要人工处理,但是在K8s容器平台中,K8s的Pod删除默认就会向容器中的主进程发送优雅停机命令,并提供了默认30s的等待时长,若优雅停机处理超出30s以后就会强制终止。同时,有些应用在容器中部署时,并不是通过容器主进程的形式进行部署,那么K8s也提供了PreStop的回调函数来在Pod停止前进行指定处理,可以是一段命令,也可以是一个HTTP的请求,从而具备了较强的灵活性。 通过以上分析,理论上应用容器化部署以后仍然可以很好的支持优雅停机,甚至相比于传统方式实现了更多的自动化操作,本文档后面会针对该方案进行详细的方案验证。

  • • 容器应用中第三方Init:在构建应用中使用第三方init如tini或dumb-init

方案验证

方案一:通过k8s Prestop参数调用

通过k8s的prestop参数调用容器内进程关闭脚本,实现优雅关闭。

在前面脚本启动的dockerfile基础上,定义一个优雅关闭的脚本,通过k8s-prestop在关闭POD前调用优雅关闭脚本,实现pod优雅关闭。

启动脚本 start.sh

cat > start.sh<< EOF 
#!/bin/sh
./app

stop.sh 优雅关闭脚本

#!/bin/sh
ps -ef|grep app|grep -v grep|awk '{print $1}'|xargs kill -15

dockerfile

FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM alpine
WORKDIR /root/
COPY --from=builder /go/app .
ADD start.sh /root/
CMD ["/bin/sh","/root/start.sh"]

构建镜像

docker build -t app:v1.0-prestop ./

通过yaml部署到k8s中

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-prestop
  labels:
    app: prestop
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prestop
  template:
    metadata:
      labels:
        app: prestop
    spec:
      containers:
      - name: prestop
        image: 172.16.1.31/library/app:v1.0-prestop
        lifecycle:
          preStop:
            exec:
              command:
              - sh
              - /root/stop.sh

查看POD日志,然后删除pod副本

kubectl get pod 
NAME                            READY   STATUS    RESTARTS   AGE
app-prestop-847f5c4db8-mrbqr    1/1     Running   0          73s

查看日志

kubectl logs app-prestop-847f5c4db8-mrbqr -f
进程启动...

另外窗口删除POD

kubectl logs app-prestop-847f5c4db8-mrbqr -f
进程启动...


退出 terminated
正在退出...
执行清理...
退出完成...

可以看见执行了Prestop脚本进行优雅关闭。 同样的可以将yaml文件中的Prestop脚本取消进行对比测试可以发现就会进行强制删除。

方案二:shell脚本修改为exec执行

修改start.sh脚本

#!/bin/sh
exec ./app

shell中添加一个 exec 即可让应用进程替代当前shell进程,可将SIGTERM信号传递到业务层,让业务实现优雅关闭。

可使用上面例子,进行修改测试。

方案三:通过第三init工具启动

通过第三方init进程传递SIGTERM到进程中。

使用dump-init或tini做为容器的主进程,在收到退出信号的时候,会将退出信号转发给进程组所有进程。,主要适用应用本身无关闭信号处理的场景。docker–init本身也是集成的tini。

FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM alpine
WORKDIR /root/
COPY --from=builder /go/app .
ADD start.sh tini /root/
RUN chmoad a+x start.sh && apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/root/tini", "--", /root/start.sh"]

首先运行了 /root/tini 作为初始化进程,然后启动了 /root/start.sh 脚本。这种设置有助于确保容器中的进程以正确的方式启动和终止,并处理信号,以便在容器停止时进行清理和关闭工作。

tini 是一个用于初始化进程并正确处理信号的工具,通常用于确保容器中的进程以正确的方式启动和终止。

构建镜像

docker build -t app:v1.0-tini ./

测试运行

docker run -itd --name app-tini app:v1.0-tini

查看日志

docker logs app-tini

进程启动...

发现容器快速停止了,但没有输出应用关闭和清理的日志

后面查阅相关资料发现

使用tini或dump-init做为应用启动的主进程。 tini和dumb-init会将关闭信号向子进程传递,但不会等待子进程完全退出后自己在退出。而是传递完后直接就退出了。

相关issue: https://github.com/krallin/tini/issues/180

后面又查到另外一个第三方的组件smell-baron能实现等待子进程优雅关闭后在关闭本身功能。 但这个项目本身热度不是特别高,并且有很久没有维护了。

FROM golang as builder
WORKDIR /go/
COPY app.go    .
RUN go build app.go
FROM ubuntu
WORKDIR /root/
COPY --from=builder /go/app .
ADD start.sh /root/
ADD smell-baron /bin/smell-baron
RUN chmod a+x /bin/smell-baron  && chmod a+x start.sh
ENTRYPOINT ["/bin/smell-baron"]
CMD ["/root/start.sh"]

构建镜像

docker build -t app:v1.0-smell-baron ./

测试

docker run -itd --name app-smell-baron app:v1.0-smell-baron

docker stop  app-smell-baron

进程启动...
退出 terminated
正在退出...
执行清理...
退出完成...

总结

  1. 1. 对于容器化应用启动命令建议使用EXEC模式。

  2. 2. 对于应用本身代码层面已经实现了优雅关闭的业务,但有shell启动脚本,容器化后部署到k8s上建议使方案一和方案二。

  3. 3. 对于应用本身代码层面没有实现优雅关闭的业务,建议使用方案三。

参考地址

  1. 1. https://github.com/insidewhy/smell-baron

  2. 2. https://github.com/Yelp/dumb-init

  3. 3. https://github.com/krallin/tini

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1080280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(2022|NIPS,CogLM,分层,LoPAR,icetk)CogView2:通过分层 Transformer 更快更好地文本到图像生成

CogView2: Faster and Better Text-to-Image Generation via Hierarchical Transformers 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 相关工作 3. 方法 3.…

基于SpringBoot+Vue的教师人事档案管理系统

1 简介 基于SpringBootVue的教师人事档案管理系统&#xff0c;教师人事档案管理系统利用信息的合理管理&#xff0c;动态的、高效的、安全的实现了教师的各种需求&#xff0c;改变了传统的网上查看方式&#xff0c;使教师可以足不出户的在线查看最适合自己个人档案、奖惩信息、…

百度一面:谈谈 @Transactional 的原理和坑

百度一面&#xff1a;谈谈 Transactional 的原理和坑 这篇文章&#xff0c;会先讲述 Transactional 的 4 种不生效的 Case&#xff0c;然后再通过源码解读&#xff0c;分析 Transactional 的执行原理&#xff0c;以及部分 Case 不生效的真正原因。 项目准备 下面是 DB 数据和…

js的BoM事件(二)

js的BoM事件&#xff08;二&#xff09;&#xff0c;上一篇的补充 一.alert,confirm二.prompt三.open四.close 一.alert,confirm 实例&#xff1a; alert(hi); confirm(message);二.prompt <body><button ></button><script>var btndocument.querySe…

dbeaver 插入别名设置禁用

1&#xff0c;前提 最近换了一个数据库连接工具&#xff0c;初次使用&#xff0c;非常别扭。 2&#xff0c;问题 首先遇到的第一个问题是 每次输入from table时&#xff0c;后面就会自动添加一个表别名 tt&#xff0c;然后语句就变成这样 from table tt &#xff0c;所以每次…

全面深入了解自动化测试

一、自动化测试 在软件测试中&#xff0c;自动化测试指的是使用独立于待测软件的其他软件来自动执行测试、比较实际结果与预期并生成测试报告这一过程。在测试流程已经确定后&#xff0c;测试自动化可以自动执行的一些重复但必要测试工作。也可以完成手动测试几乎不可能完成的…

飞凌嵌入式受邀参加「NXP创新技术论坛」

2023年10月10日&#xff0c;「NXP创新技术论坛」在深圳湾万丽酒店举行&#xff0c;飞凌嵌入式作为NXP金牌合作伙伴受邀参加此次论坛&#xff0c;与众多智能工业行业的伙伴深入交流市场趋势与行业洞察&#xff0c;共同促进未来市场的发展。 本次论坛&#xff0c;飞凌嵌入式展示了…

GaussDB向量数据库为盘古大模型再添助力

在今年7月7日的华为开发者大会2023(Cloud)期间,华为云盘古大模型3.0正式发布。目前盘古大模型已在政务、金融、制造、医药研发、气象等诸多行业发挥巨大价值。此次华为云发布的GaussDB向量数据库,具备一站式部署、全栈自主创新优势,不仅如此,它的ANN算法在行业排名第一,…

龙讯旷腾:如何建立基于第一性原理的正向研发模式,原子级计算伴随的时间和空间尺度增长将带来的变革

2023年10月8-11日&#xff0c;由中国材料研究学会主办的第四届中国新材料产业发展大会在浙江温州隆重举行。来自全国各地的6000余名新材料专家、企业家、投资家、当地高等院校和企事业单位的代表以及51位两院院士出席了本次大会。龙讯旷腾总经理吕海峰特邀做“先进材料数字化研…

点击、拖曳,15分钟搞定BI零售数据分析

早几年做数据分析还很依赖IT&#xff0c;过程复杂、耗时长、灵活性差&#xff0c;但这几年随着BI智能数据分析技术的成长&#xff0c;零售数据分析发生了翻天覆地的变化&#xff0c;其中最直观的一点就是&#xff1a;点击、拖曳&#xff0c;15分钟内就能搞定BI零售数据分析。 …

uCOSIII实时操作系统 五 任务API(任务创建和删除)

任务创建和删除 引入&#xff1a;一个任务的三要素是任务主体函数&#xff0c;任务栈&#xff0c;任务控制块&#xff0c;那么怎样吧这个三要素联系在一起呐&#xff1f; 任务创建&#xff1a; 在UCOSIII中我们通过函数OSTaskCreate()来创建任务。 作用&#xff1a;任务控制块…

有root权限的共享服务器,返现福利

以下是目前各类型服务器配置与价格目录&#xff1a; 可咨询文末微信号领取返现福利&#xff0c;注册链接&#xff1a; 西柚云超算https://www.xiyoucloud.net/aff/YADJJHWA 微信号&#xff1a;生信小博士

需求解析思路

需求&#xff1a;如果一个学生N天没学习了 根据question_user_submit_record&#xff08;N配置&#xff09;&#xff0c;公众号推送通知到java8c.com学习 由于写代码需要严谨快速&#xff1a; 当前写代码方式&#xff1a;先写controller&#xff0c;再写sql语句&#xff0c;…

Postman历史版本下载

1. 下载对应版本的postman 历史版本下载 请把下面链接的"版本号"替换为指定的版本号&#xff0c;例如&#xff1a;8.8.0 Windows64位 ​https://dl.pstmn.io/download/version/版本号/win64​ Windows32位 https://dl.pstmn.io/download/version/版本号…

SpringBoot+Dubbo+Nacos 开发Demo

1、是什么 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架&#xff0c;使得应用可通过高性能的RPC实现服务的输出和输入功能&#xff0c;可以和Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架&#xff0c;它提供了三大核心能力&#xff1a;面向接口的…

MinIO的安装与使用

文章目录 1.MINIO是什么&#xff1f;2.MINIO安装3.启动脚本4.打开MINIO页面5.MC命令6.MINIO备份脚本 1.MINIO是什么&#xff1f; MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。 MinIO与…

智能导诊系统、智能在线问诊系统源码

一、需求背景 目前各大城市的著名医院吸引越来越多的患者&#xff0c;咨询台的服务需求也随之增加。医院虽然设置了少数咨询台&#xff0c;但由于患者人数众多&#xff0c;咨询台往往无法满足患者咨询要求&#xff0c;护士工作量巨大&#xff0c;医院巨大的规模也让咨询台数量…

佳音通讯400电话中心:在线自选,惠及企业

在当今竞争激烈的商业环境中&#xff0c;企业需要提供卓越的客户服务来脱颖而出。而一个高效的400电话中心则成为了越来越多企业的选择。佳音通讯400电话中心官方网站是企业选择400电话服务的首选平台&#xff0c;提供了在线自选功能&#xff0c;让企业能够根据自身需求灵活选择…

实现一个自己的脚手架教程

前言 脚手架并不实现&#xff0c;难的是最佳实践的整理和沉淀。本文不会涉及到最佳实践方面的内容&#xff0c;只是教会你如何实现一个最基础的脚手架&#xff0c;以此作为展示最佳实践的载体。 如何搭建一个脚手架的工程 如何开发和调试一个脚手架 脚手架中如何接收和处理命…

C++教程(2)

C 环境设置 本地环境设置 如果您想要设置 C 语言环境&#xff0c;您需要确保电脑上有以下两款可用的软件&#xff0c;文本编辑器和 C 编译器。 文本编辑器 这将用于输入您的程序。文本编辑器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。 文…