哈尔滨网站建设有哪些,3d建模工资一般多少,上饶商城网站建设,什么是互联网公司什么是优雅关闭#xff1f; 优雅关闭#xff1a; 在关闭前#xff0c;执行正常的关闭过程#xff0c;释放连接和资源#xff0c;如我们操作系统执行shutdown 目前业务系统组件众多#xff0c;互相之间调用关系也比较复杂#xff0c;一个组件的下线、关闭会涉及到多个组件…什么是优雅关闭 优雅关闭 在关闭前执行正常的关闭过程释放连接和资源如我们操作系统执行shutdown 目前业务系统组件众多互相之间调用关系也比较复杂一个组件的下线、关闭会涉及到多个组件。 对于任何一个线上应用如何保证服务更新部署过程中从应用停止到重启恢复服务这个过程中不影响正常的业务请求这是应用开发运维团队必须要解决的问题。 传统的解决方式是通过将应用更新流程划分为手工摘流量、停应用、更新重启三个步骤由人工操作实现客户端不对更新感知。这种方式简单而有效但是限制较多不仅需要使用借助网关的支持来摘流量还需要在停应用前人工判断来保证在途请求已经处理完毕。 同时在应用层也有一些保障应用优雅停机的机制目前Tomcat、Spring Boot、Dubbo等框架都有提供相关的内置实现如SpringBoot 2.3内置graceful shutdown可以很方便的直接实现优雅停机时的资源处理同时一个普通的Java应用也可以基于Runtime.getRuntime().addShutdownHook()来自定义实现它们的实现原理都基本一致通过等待操作系统发送的SIGTERM信号然后针对监听到该信号做一些处理动作。 优雅停机是指在停止应用时执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等优雅停机可以避免非正常关闭程序可能造成数据异常或丢失应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。 现状 现阶段业务容器化后业务启动是通过shell脚本启动业务对应的在容器内PID为1的进程为shell进程但shell 程序不转发signals也不响应退出信号。所以在容器应用中如果应用容器中启动 shell占据了 pid1 的位置那么就无法接收k8s发送的SIGTERM信号只能等超时后被强行杀死了。 案例
package mainimport (fmtosos/signalsyscalltime
)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 --frombuilder /go/app .
CMD ./app# 构建镜像docker build -t app:v1.0-shell .# 进入容器查看进程docker exec -it app-shell ps auxUSER 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-shelldocker logs app-shell
进程启动... EXEC模式FROM golang as builder
WORKDIR /go/
COPY app.go .
RUN go build app.go
FROM ubuntu
WORKDIR /root/
COPY --frombuilder /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-execdocker logs app-exec
进程启动...
退出 terminated
正在退出...
执行清理...
退出完成... 注意以上测试在ubuntu做为应用的base镜像测试成功在alpine做为应用的base镜像时shell模式和exec模式都一样都是应用进程为PID 1的进程。 直接启动应用和通过脚本启动区别 在实际生产环境中因为应用启动命令后会接很多启动参数所以通常我们会使用一个启动脚本来启动应用方便我们启动应用。 对应的在容器内PID为1的进程为shell进程但shell 程序不转发signals也不响应退出信号。所以在容器应用中如果应用容器中启动 shell占据了 pid1 的位置那么就无法接收k8s发送的SIGTERM信号只能等超时后被强行杀死了。 # 启动脚本cat start.sh EOF
#!/bin/sh
sh -c /root/app# dockerfileFROM golang as builder
WORKDIR /go/
COPY app.go .
RUN go build app.go
FROM alpine
WORKDIR /root/
COPY --frombuilder /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 auxdocker 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优雅关闭 # 启动脚本cat start.sh EOF
#!/bin/sh
./app
EOF# 关闭脚本
cat stop.sh EOF
#!/bin/sh
ps -ef|grep app|grep -v grep|awk {print $1}|xargs kill -15
EOF# dockerfileFROM golang as builder
WORKDIR /go/
COPY app.go .
RUN go build app.go
FROM alpine
WORKDIR /root/
COPY --frombuilder /go/app .
ADD start.sh /root/
CMD [/bin/sh,/root/start.sh]# 构建镜像docker build -t app:v1.0-prestop .# k8s部署apiVersion: apps/v1
kind: Deployment
metadata:name: app-prestoplabels:app: prestop
spec:replicas: 1selector:matchLabels:app: prestoptemplate:metadata:labels:app: prestopspec:containers:- name: prestopimage: harbor.codemiracle.com/library/app:v1.0-prestoplifecycle:preStop:exec:command:- sh- /root/stop.sh# 查看POD日志然后删除pod副本kubectl get pod kubectl logs app-prestop-847f5c4db8-mrbqr -f
进程启动...# 另外窗口删除PODkubectl logs app-prestop-847f5c4db8-mrbqr -f
进程启动...退出 terminated
正在退出...
执行清理...
退出完成...可以看见执行了Prestop脚本进行优雅关闭。同样的可以将yaml文件中的Prestop脚本取消进行对比测试可以发现就会进行强制删除。方案二shell脚本修改为exec执行
# start.sh#!/bin/sh
exec ./appshell中添加一个 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 --frombuilder /go/app .
ADD start.sh tini /root/
RUN chmoad ax 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会将关闭信号向子进程传递但不会等待子进程完全退出后自己在退出。而是传递完后直接就退出了 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 --frombuilder /go/app .
ADD start.sh /root/
ADD smell-baron /bin/smell-baron
RUN chmod ax /bin/smell-baron chmod ax 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-barondocker stop app-smell-baron进程启动...
退出 terminated
正在退出...
执行清理...
退出完成... 1. 对于容器化应用启动命令建议使用EXEC模式。 2. 对于应用本身代码层面已经实现了优雅关闭的业务但有shell启动脚本容器化后部署到k8s上建议使方案一和方案二。 3. 对于应用本身代码层面没有实现优雅关闭的业务建议使用方案三 GitHub - insidewhy/smell-baron: A tiny init system written in C for docker containers.
GitHub - Yelp/dumb-init: A minimal init system for Linux containers
https://github.com/krallin/tini
搜集自网络作为学习笔记