20240613_容器内如何处理SIGTERM
处理SIGTERM
信号
一、exec-app.sh
与app.sh
,(两种形式都不建议使用):
- 使用如下脚本当中指定需要使用
#!/bin/bash
+exec
的形式启动进程
1[root@3114-db-mysql ubuntu]# cat exec-app.sh
2#!/bin/bash
3exec /gosignal
4
5# 进入到容器内查看进程结构
6
7root@4d40c42c130e:/# ps -auxf
8USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
9root 14 1.0 0.0 4628 3528 pts/0 Ss 15:55 0:00 bash
10root 22 0.0 0.0 7064 1588 pts/0 R+ 15:55 0:00 \_ ps -auxf
11root 1 0.3 0.0 2892 1000 ? Ss 15:54 0:00 /bin/sh -c /exec-app.sh
12root 7 0.0 0.0 1225800 2892 ? Sl 15:54 0:00 /gosignal
13
14# 此时gosignal的进程是脱离于/bin/sh 存在而运行的,PID为7
15
16# 当使用 kill -15 7 时,由于gosignal退出,pid 为1 的进程没有了执行程序,因此容器会退出。
- 修改启动脚本exe-app.sh使用
#!/bin/sh
+exec
执行
1root@826eced13ef9:/# cat exec-app.sh
2#!/bin/sh
3exec /gosignal
4
5root@826eced13ef9:/# ps -auxf
6USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
7root 14 0.5 0.0 4628 3756 pts/0 Ss 17:22 0:00 bash
8root 22 0.0 0.0 7064 1668 pts/0 R+ 17:22 0:00 \_ ps -auxf
9root 1 0.1 0.0 2892 956 ? Ss 17:22 0:00 /bin/sh -c /exec-app.sh
10root 7 0.0 0.0 1225288 2888 ? Sl 17:22 0:00 /gosignal
11# 可以发现此时不会再出bash,1号进程直接执行exec-app.sh脚本,gosignal 程序父进程为1号进程。
12
13[root@3114-db-mysql ubuntu]# docker logs -f 826eced13ef9
14Starting application,Pid is :7, PPid is: 1
15
16# 但是执行# docker stop 826eced13ef9 或者 kill -15 1 的操作时候。 gosignal无法感知到SIGTERM信号。
- 启动脚本当中没有exec的参数,但是脚本申明
#!/bin/bash
1[root@3114-db-mysql ubuntu]# cat app.sh
2#!/bin/bash
3/gosignal
4
5# 进入到容器内查看进程结构
6root@c23faa311be9:/# ps -auxf
7USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
8root 14 0.5 0.0 4628 3576 pts/0 Ss 16:06 0:00 bash
9root 22 0.0 0.0 7064 1556 pts/0 R+ 16:06 0:00 \_ ps -auxf
10root 1 0.1 0.0 2892 960 ? Ss 16:06 0:00 /bin/sh -c /app.sh
11root 7 0.0 0.0 4364 3328 ? S 16:06 0:00 /bin/bash /app.sh
12root 8 0.0 0.0 1225800 2888 ? Sl 16:06 0:00 \_ /gosignal
13[root@3114-db-mysql ubuntu]# docker logs -f c23faa311be9
14Starting application,Pid is :8, PPid is: 7
15#此时可以看到,exec-app.sh 脚本会新启一个bash 进行执行gosignal,且其自己进程为8,父进程为7
16
17#使用kill -15 8 则直接进程被Terminated ,没有触发gosignal的型号捕捉
18[root@3114-db-mysql ubuntu]# docker logs -f c23faa311be9
19Starting application,Pid is :8, PPid is: 7
20Terminated
- 启动脚本当中没有exec的参数,但是脚本申明
#!/bin/sh
1[root@3114-db-mysql ubuntu]# cat app.sh
2#!/bin/sh
3/gosignal
4
5# 进程结构与使用bash的方式一样
6[root@3114-db-mysql gosignal]# docker exec -it 3f1 bash
7root@3f188daf5b79:/# ps -auxf
8USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
9root 15 1.0 0.0 4628 3760 pts/0 Ss 17:34 0:00 bash
10root 23 0.0 0.0 7064 1612 pts/0 R+ 17:34 0:00 \_ ps -auxf
11root 1 0.0 0.0 2892 996 ? Ss 17:34 0:00 /bin/sh -c /app.sh
12root 7 0.0 0.0 2892 952 ? S 17:34 0:00 /bin/sh /app.sh
13root 8 0.0 0.0 1225544 2892 ? Sl 17:34 0:00 \_ /gosignal
14
15# 由于gosignal 不是1号进程,因此也无法感知SIGTERM信号
结论:如论使用何种方式进行都无法让gosignal
收到 15的停止信号。
二、entrypoint-signal.sh形式
- 未启用
Dockerfile
的SHELL ["/bin/bash","-c"]
参数时,bash
不会为1号进程
1[root@3114-db-mysql gosignal]#cat /entrypoint-signal.sh
2#!/bin/bash
3/gosignal & PID1="$!"
4echo "gosignal started with pid $PID1"
5handle_sigterm(){
6 echo "[INFO] Received SIGTERM"
7 kill -SINTERM $PID1
8 wait $PID1
9}
10trap handle_sigterm SIGTERM
11wait
12
13# 进程形式:
14root@17b995db94df:/# ps -auxf
15USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
16root 16 0.6 0.0 4628 3708 pts/0 Ss 16:58 0:00 bash
17root 24 0.0 0.0 7064 1544 pts/0 R+ 16:58 0:00 \_ ps -auxf
18root 1 0.0 0.0 2892 964 ? Ss 16:57 0:00 /bin/sh -c /entrypoint-signal.sh
19root 7 0.0 0.0 4364 3244 ? S 16:57 0:00 /bin/bash /entrypoint-signal.sh
20root 8 0.0 0.0 1225800 2888 ? Sl 16:57 0:00 \_ /gosignal
21# 使用docker stop 该进程,无法得到响应,最终被强制删掉
2.启用Dockerfile
的SHELL ["/bin/bash","-c"]
参数。
1[root@3114-db-mysql ubuntu]# cat Dockerfile
2FROM ubuntu:22.04
3LABEL "author"="kid"
4# 注意这里使用了SHELL为BASH,如果不加,则1号进程会变为/bin/sh -c的形式
5SHELL ["/bin/bash","-c"]
6ENV TINI_VERSION v0.19.0
7ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
8RUN chmod +x /tini
9COPY gosignal/gosignal /gosignal
10COPY entrypoint-signal.sh /entrypoint-signal.sh
11RUN chmod +x /entrypoint-signal.sh
12COPY exec-app.sh /exec-app.sh
13RUN chmod +x /exec-app.sh
14ENTRYPOINT /entrypoint-signal.sh
15
16#此时的1号进程发生了变化,
17root@c82450e19085:/# ps -auxf
18USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
19root 15 0.5 0.0 4628 3688 pts/0 Ss 17:06 0:00 bash
20root 23 0.0 0.0 7064 1608 pts/0 R+ 17:06 0:00 \_ ps -auxf
21root 1 0.0 0.0 4364 3120 ? Ss 17:06 0:00 /bin/bash /entrypoint-signal.sh
22root 7 0.0 0.0 1225800 2892 ? Sl 17:06 0:00 /gosignal
23
24
25#对1号进程进行执行kill -15 1 或者docker stop c82 的的操作是进程均或感知到SIGTERM的信号。
26[root@3114-db-mysql ubuntu]# docker logs -f c82
27gosignal started with pid 7
28Starting application,Pid is :7, PPid is: 1
29[INFO] Received SIGTERM
30已捕捉到信号:SIGTERM, 执行关闭,sig is : terminated
31Exiting!
三、使用tini工具
Tini
作为ENTRYPOINT
,gosignal
应用程序作为CMD
的入参传入
1[root@3114-db-mysql ubuntu]# cat Dockerfile
2FROM ubuntu:22.04
3LABEL "author"="kid"
4#SHELL ["/bin/bash","-c"]
5ENV TINI_VERSION v0.19.0
6ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
7RUN chmod +x /tini
8COPY gosignal/gosignal /gosignal
9COPY entrypoint-signal.sh /entrypoint-signal.sh
10RUN chmod +x /entrypoint-signal.sh
11COPY exec-app.sh /exec-app.sh
12RUN chmod +x /exec-app.sh
13ENTRYPOINT ["/tini", "--"]
14CMD ["/gosignal"]
15
16# 容器内进程结构:
17root@91ed8c1f402d:/# ps -auxf
18USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
19root 14 0.6 0.0 4628 3572 pts/0 Ss 17:42 0:00 bash
20root 22 0.0 0.0 7064 1584 pts/0 R+ 17:42 0:00 \_ ps -auxf
21root 1 0.0 0.0 2780 996 ? Ss 17:41 0:00 /tini -- /gosignal
22root 7 0.0 0.0 1225800 2888 ? Sl 17:41 0:00 /gosignal
23
24# 执行docker stop 或 kill -15 1 此时信号量能够传入到可执行程序当中。
25[root@3114-db-mysql ubuntu]# docker stop tini-without-shell
26tini-without-shell
27
28[root@3114-db-mysql ubuntu]# docker logs -f tini-without-shell
29Starting application,Pid is :7, PPid is: 1
30已捕捉到信号:SIGTERM, 执行关闭,sig is : terminated
31Exiting!
-
tini
作为1号进程,通常不会直接以CMD
的形式运行二进制程序,企业在使用过程当中会在CMD
后写一个运行脚本,在脚本内执行业务、与启动代码逻辑,此时脚本使用的是bash的话,bash如果不作trap的处理又会出现无法传导SIGTERM
给应用程序的情况。模拟以上场景,在
ENTRYPOINT ["/tini", "--"]
后调用exec-app.sh
与app.sh
两种脚本
1# 场景1
2[root@3114-db-mysql ubuntu]# cat exec-app.sh
3#!/bin/bash
4exec /gosignal
5[root@3114-db-mysql ubuntu]# cat Dockerfile
6FROM ubuntu:22.04
7LABEL "author"="kid"
8#SHELL ["/bin/bash","-c"]
9ENV TINI_VERSION v0.19.0
10ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
11RUN chmod +x /tini
12COPY gosignal/gosignal /gosignal
13COPY entrypoint-signal.sh /entrypoint-signal.sh
14RUN chmod +x /entrypoint-signal.sh
15COPY exec-app.sh /exec-app.sh
16RUN chmod +x /exec-app.sh
17COPY app.sh /app.sh
18RUN chmod +x /app.sh
19ENTRYPOINT ["/tini", "--"]
20CMD ["/exec-app.sh"]
21
22# 进程结构:
23root@375509e8492e:/# ps -auxf
24USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
25root 15 1.0 0.0 4628 3740 pts/0 Ss 17:57 0:00 bash
26root 23 0.0 0.0 7064 1644 pts/0 R+ 17:57 0:00 \_ ps -auxf
27root 1 0.0 0.0 2780 1056 ? Ss 17:57 0:00 /tini -- /exec-app.sh
28root 7 0.0 0.0 1225800 2888 ? Sl 17:57 0:00 /gosignal
29
30# 执行docker stop 动作
31[root@3114-db-mysql ubuntu]# docker stop tini-exec-app
32tini-exec-app
33[root@3114-db-mysql ubuntu]# docker logs -f tini-exec-app
34Starting application,Pid is :7, PPid is: 1
35已捕捉到信号:SIGTERM, 执行关闭,sig is : terminated
36Exiting!
37#结论:在该进程结构下,应用程序能够正常接收到SIGTERM信号.
38
39# 场景2
40[root@3114-db-mysql ubuntu]# cat app.sh
41#!/bin/sh
42/gosignal
43[root@3114-db-mysql ubuntu]# cat Dockerfile
44FROM ubuntu:22.04
45LABEL "author"="kid"
46ENV TINI_VERSION v0.19.0
47ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
48RUN chmod +x /tini
49COPY gosignal/gosignal /gosignal
50COPY entrypoint-signal.sh /entrypoint-signal.sh
51RUN chmod +x /entrypoint-signal.sh
52COPY exec-app.sh /exec-app.sh
53RUN chmod +x /exec-app.sh
54COPY app.sh /app.sh
55RUN chmod +x /app.sh
56ENTRYPOINT ["/tini", "--"]
57CMD ["/app.sh"]
58
59# 进程结构:
60root@dff1564118e0:/# ps -auxf
61USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
62root 15 1.0 0.0 4628 3780 pts/0 Ss 18:04 0:00 bash
63root 23 0.0 0.0 7064 1560 pts/0 R+ 18:04 0:00 \_ ps -auxf
64root 1 0.0 0.0 2780 976 ? Ss 18:02 0:00 /tini -- /app.sh
65root 7 0.0 0.0 2892 1000 ? S 18:02 0:00 /bin/sh /app.sh
66root 8 0.0 0.0 1225544 2888 ? Sl 18:02 0:00 \_ /gosignal
67# 执行docker stop 动作
68[root@3114-db-mysql ubuntu]# docker stop tini-app
69tini-app
70[root@3114-db-mysql ubuntu]# docker logs -f tini-app
71Starting application,Pid is :8, PPid is: 7
72# 结论:此时由于gosignal的父进程是PID为7的sh,sh无法给gosingal信号,虽然TINI进行了信号传递,但是结果是gosingal被暴力的杀死,没有处理SIGTERM的过程。
tini
作为1号进程,CMD
脚本(这里使用entrypoint-signal.sh
)具备trap SIGTERM
的逻辑,能够传递信号量给自己的子进程当中
1[root@3114-db-mysql ubuntu]# cat entrypoint-signal.sh
2#!/bin/bash
3/gosignal & PID1="$!"
4echo "gosignal started with pid $PID1"
5handle_sigterm(){
6 echo "[INFO] Received SIGTERM"
7 kill -SIGTERM $PID1
8 wait $PID1
9
10}
11trap handle_sigterm SIGTERM
12wait
13
14[root@3114-db-mysql ubuntu]# cat Dockerfile
15FROM ubuntu:22.04
16LABEL "author"="kid"
17ENV TINI_VERSION v0.19.0
18ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
19RUN chmod +x /tini
20COPY gosignal/gosignal /gosignal
21COPY entrypoint-signal.sh /entrypoint-signal.sh
22RUN chmod +x /entrypoint-signal.sh
23COPY exec-app.sh /exec-app.sh
24RUN chmod +x /exec-app.sh
25COPY app.sh /app.sh
26RUN chmod +x /app.sh
27ENTRYPOINT ["/tini", "--"]
28CMD ["/entrypoint-signal.sh"]
29
30# 进程结构
31root@d24562e8169a:/# ps -auxf
32USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
33root 15 0.6 0.0 4628 3800 pts/0 Ss 18:14 0:00 bash
34root 24 0.0 0.0 7064 1596 pts/0 R+ 18:14 0:00 \_ ps -auxf
35root 1 0.0 0.0 2780 980 ? Ss 18:12 0:00 /tini -- /entrypoint-signal.sh
36root 7 0.0 0.0 4364 3276 ? S 18:12 0:00 /bin/bash /entrypoint-signal.sh
37root 8 0.0 0.0 1225544 2888 ? Sl 18:12 0:00 \_ /gosignal
38# 此时gosignal进程作为bash的子进程形式存在。
39
40# 执行docker stop 动作
41[root@3114-db-mysql ubuntu]# docker stop tini-entrypoint-signal
42tini-entrypoint-signal
43[root@3114-db-mysql ubuntu]# docker logs -f tini-entrypoint-signal
44gosignal started with pid 8
45Starting application,Pid is :8, PPid is: 7
46[INFO] Received SIGTERM
47已捕捉到信号:SIGTERM, 执行关闭,sig is : terminated
48Exiting!
49
50# 结论:可以看到,脚本处理了SIGTERM的信号
四、总结
-
使用脚本如
ENTRYPOINT /entrypoint-signal.sh
这种能够处理信号的脚本作为应用启动的1号进程过程当中一定需要记得在Dockerfile
添加与该脚本一致的SHELL ["/bin/bash","-c"]
参数,否则很有可能由于默认存在的sh -c
原因导致entrypoint-signal.sh
么有办法成为1号进程,从而对信号处理失败。 -
在使用
ENTRYPOINT ["/tini", "--"]
的过程当中,如果使用CMD
+脚本启动业务进程
,切记此时的程序启动脚本需要能够进行trap handle_sigterm SIGTERM
的信号捕捉动作,或者在脚本当中使用exec
的方式跳脱bash/sh父进程管控的形式执行,否则使用tini
也并无实际意义,依然是粗暴结束业务进程。更建议是以CMD
+业务二进制
程序的方式,这样一来容器内PID
为1
的tini
作为所有进程的父进程,在收到到SIGTERM
时,会向所有PPID
为1的进程广播放送信号。
附:gosignal
:
1package main
2
3import (
4 "fmt"
5 "os"
6 "os/signal"
7 "syscall"
8 "time"
9)
10func main() {
11 pid := os.Getpid()
12 ppid := os.Getppid()
13 sigchan := make(chan os.Signal, 1)
14 done := make(chan bool, 1)
15 // 注册一个通道,关注的系统信号量包含TERM、KILL、HUP
16 signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGHUP)
17 go func() {
18 sig := <-sigchan
19 fmt.Printf("已捕捉到信号:SIGTERM, 执行关闭,sig is : %v\n", sig)
20 time.Sleep(time.Second * 5)
21 done <- true
22 }()
23 fmt.Printf("Starting application,Pid is :%v, PPid is: %v\n", pid, ppid)
24 <-done
25 fmt.Println("Exiting!")
26}
- 原文作者:Kid
- 原文链接:https://shuanglu.life/post/20240613_%E5%AE%B9%E5%99%A8%E5%86%85%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86SIGTERM/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。