原创不易,未经允许,请勿转载。

在Linux系统运行一个程序时,如果需要关闭它,一般有两种方式。如果该程序是以非守护进程的方式运行的话,我们可以通过CTRL+C发送SIGINT信号,默认进程会结束。如果是守护进程方式运行的话,可以通过kill命令发送停止信号,例如kill -2 ${pid}-2信号表示中断当前进程,跟CTRL+C等效。

总之要关闭一个进程,我们需要向它发送一个信号,进程在收到对应信号后会做出对应的相应动作,例如终止进程。

使用过docker的同学应该知道,当我们执行docker stop xxx命令时,会停止对应的容器。那么docker在停止容器时,是怎么关闭容器中运行的进程呢?

根据不同业务场景的需要,有些程序我们需要再关闭前进行清理缓存,保存数据等操作。要进行这些操作的前提就是我们程序可以处理系统信号,然后做出相应操作。但有些信号的默认行为是无法被更该的,例如kill -9 ${pid}。使用-9信号会强制杀死进程,不会给业务程序任何反应的时间。

那么在docker中,当停止容器时,docker内部会发送什么信号给进程,又有多少时间供进程处理善后操作呢?

我们可以写一个代码验证一下,以go代码为例

初始化go mod

go mod init docker-stop-demo

新建一个main.go文件,代码内容如下。

package main

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

func main() {
	// 创建一个接受信号的通道
	sigs := make(chan os.Signal, 1)
	// 这里不指定具体的信号类型的话,表示监听所有信号
	signal.Notify(sigs)

	// 程序会阻塞在此处,直到接收到信号
	sig := <-sigs
	fmt.Println("接收到信号:", sig)
	i := 0
	// 每个一秒打印一次,最后通过查看日志判断持续了多久
	for {
		fmt.Println(i)
		i++
		time.Sleep(time.Second)
	}
}

编写Dockerfile用于构建镜像

FROM golang:1.19-alpine AS builder

LABEL stage=gobuilder
WORKDIR /app
ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

COPY . .

RUN go build -ldflags="-s -w" -o docker-stop-demo ./main.go
CMD ["./docker-stop-demo"]

构建镜像

docker build -t docker-stop-demo:0.0.1 .

构建成功之后,运行docker 容器

docker run -id --name docker-stop-demo docker-stop-demo:0.0.1

接着执行docker stop命令,关闭该容器。执行完命令后,我们可以观察到卡了一会后,命令才执行结束。

docker stop docker-stop-demo

我们查看下容器打印了什么日志出来。可以看到接收到一个信号为15的terminated信号。后面紧跟着十个数字,说明docker容器给了十秒的缓存时间,让我们的程序完成最后的事项。

docker logs docker-stop-demo
接收到信号: terminated
0
1
2
3
4
5
6
7
8
9

经过上面的实验我们可以得出,当执行docker stop命令后,docker内部会向我们程序发送信号15,即中断信号,并给了十秒的缓冲时间。

博客主页:http://xiaojujiang.cn/