文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何优雅的关闭容器,看本文就够了

2024-12-02 14:37

关注

[[438741]]

要说如何优雅的关闭容器,那就不得不提到信号(Signal)的理念,以及 Dockerfile 中 ENTRYPOINT 和 CMD 指令了。在具体说优雅关闭之前,先了解一下信号这个 Linux 中的基础概念。

1 信号

信号是事件发生时对进程的通知机制,有时也称之为软件中断。

信号有不同的类型,Linux 对标准信号的编号为 1~31,可以通过 kill -l 获取信号名称: 

  1. # kill -l  
  2.  1) SIGHUP       2) SIGINT       3) SIGQUIT     
  3.  4) SIGILL       5) SIGTRAP      6) SIGABRT    
  4.  7) SIGBUS       8) SIGFPE       9) SIGKILL 
  5.  10) SIGUSR1    11) SIGSEGV     12) SIGUSR2 
  6.  13) SIGPIPE    14) SIGALRM     15) SIGTERM 
  7. ... ... 

实际列出的信号超过了 31 个,有些是其它名称的同义词,有些则是定义但未使用的。以下介绍几个常用的信号:

值得注意的是, Control-D 不会发起信号,它表示 EOF(End-Of-File),关闭标准输入(stdin)管道(比如可以通过 Control-D 退出当前 shell)。如果程序不读取当前输入的话,是不受 Control-D 影响的。

程序可以针对信号捕捉,然后执行相应函数:

 

以上知识大部分都来自 《Linux/UNIX 系统编程手册》,想要了解更多的,可以查看该书上册的 20、21、22 章节。

2 ENTRYPOINT 、 CMD

可能有人会问,说了半天,那信号和优雅的关闭容器有半毛钱的关系啊?话说,这和钱确实没关系,但是和如何优雅关闭容器却关系密切。

接着说 Dockerfile 中的 ENTRYPOINT 和 CMD 指令,它们的主要功能是指定容器启动时执行的程序。

CMD 有三种格式:

ENTRYPOINT 有两种格式:

其中,不管你 Dockerfile 用其中哪个指令,两个指令都推荐使用 exec 格式,而不是 shell 格式。原因就是因为使用 shell 格式之后,程序会以 /bin/sh -c 的子命令启动,并且 shell 格式下不会传递任何信号给程序。这也就导致,在 docker stop 容器的时候,以这种格式运行的程序捕捉不到发送的信号,也就谈不上优雅的关闭了。 

  1. ➜  ~ docker stop --help  
  2. Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER...]  
  3. Stop one or more running containers  
  4. Options:  
  5.       --help       Print usage  
  6.   -t, --time int   Seconds to wait for stop before killing it (default 10) 

docker stop 停掉容器的时候,默认会发送一个 SIGTERM 的信号,默认 10s 后容器没有停止的话,就 SIGKILL 强制停止容器。通过 -t 选项可以设置等待时间。 

  1. ➜  ~ docker kill --help  
  2. Usage:  docker kill [OPTIONS] CONTAINER [CONTAINER...]  
  3. Kill one or more running containers  
  4. Options:  
  5.       --help            Print usage  
  6.   -s, --signal string   Signal to send to the container (default "KILL") 

通过 docker kill 的 -s 选项还可以指定给容器发送的信号。

所以,说了那么多,只要 Dockerfile 中通过 exec 格式执行容器启动命令就相安无事了?那当然是,没有那么简单的了,接下来我们通过实例来看看具体的效果是怎么样的。

3 实例

通过 Go 写一个简单的信号处理器: 

  1. ➜  ~ cat signals.go  
  2. package main  
  3. import ( 
  4.     "fmt"  
  5.     "os"  
  6.     "os/signal"  
  7.     "syscall"  
  8. func main() {  
  9.     sigs :make(chan os.Signal, 1)  
  10.     done :make(chan bool, 1)  
  11.     signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)  
  12.     go func() {  
  13.         sig :<-sigs  
  14.         fmt.Println()  
  15.         fmt.Println(sig)  
  16.         done <- true  
  17.     }()  
  18.     fmt.Println("awaiting signal")  
  19.     <-done  
  20.     fmt.Println("exiting")  

3.1 实例 1 

  1. ➜  ~ GOOS=linux GOARCH=amd64 go build signals.go  
  2. ➜  ~ ls  
  3. Dockerfile signals    signals.go  
  4. ➜  ~ cat Dockerfile  
  5. FROM busybox  
  6. COPY signals /signals  
  7. CMD ["/signals"]    # exec 格式执行  
  8. ➜  ~ docker build -t signals . 

通过 tmux 开启两个面板,一个运行容器,一个执行 docker stop : 

  1. ➜  ~ docker run -it --rm --name signals signals  
  2. awaiting signal  
  3. terminated  
  4. exiting  
  1. ➜  ~ time docker stop signals  
  2. signals  
  3. docker stop signals  0.01s user 0.02s system 4% cpu 0.732 total 
  4. ➜  ~ 

可以发现,容器停止之前,程序接收到信号并输出相应信息,并且停止总耗时为 0.732 s,达到了优雅的效果。

修改 Dockerfile 中 CMD 执行格式,执行相同操作: 

  1. ➜  ~ cat Dockerfile  
  2. FROM busybox  
  3. COPY signals /signals  
  4. CMD /signals        # shell 格式执行  
  5. ➜  ~ docker build -t signals .  
  1. ➜  ~ docker run -it --rm --name signals signals  
  2. awaiting signal  
  3. ➜  ~  
  1. ➜  ~ time docker stop signals  
  2. signals  
  3. docker stop signals  0.01s user 0.01s system 0% cpu 10.719 total 

通过 shell 格式之后,可以发现容器停止之前,程序并未接收到任何信号,并且停止时间为 10.719s,说明该容器是被强制停止的。

结论很明显,为了优雅的退出容器,我们应该采用 exec 这种格式。

3.2 实例 2

通过实例 1 我们都会在 Dockerfile 中都会通过 exec 这种格式来执行程序了,那如果执行的程序本身也是一个 shell 脚本呢? 

  1. ➜  ~ ls  
  2. Dockerfile signals    signals.go start.sh  
  3. ➜  ~ cat Dockerfile  
  4. FROM busybox  
  5. COPY signals /signals  
  6. COPY start.sh /start.sh     # 引入 shell 脚本启动  
  7. CMD ["/start.sh"]  
  8. ➜  ~ cat start.sh  
  9. #!/bin/sh  
  10. /signals  
  11. ➜  ~ 

测试依然引用实例 1 中的方法: 

  1. ➜  ~ docker run -it --rm --name signals signals  
  2. awaiting signal  
  3. ➜  ~  
  1. ➜  ~ time docker stop signals  
  2. signals  
  3. docker stop signals  0.01s user 0.02s system 0% cpu 10.765 total  
  4. ➜  ~ 

可以发现,即使 Dockerfile 中的 CMD 指令使用的是 exec 格式,容器中的程序依然没有接收到信号,最后被强制关闭。因为 shell 脚本中执行的原因,导致信号依然没有被传递,我们需要针对 shell 脚本做一些改造: 

  1. ➜  ~ cat start.sh  
  2. #!/bin/sh 
  3. exec /signals   # 加入 exec 执行  
  4. ➜  ~ docker build -t signals .  
  1. ➜  ~ docker run -it --rm --name signals signals  
  2. awaiting signal  
  3. terminated  
  4. exiting  
  1. ➜  ~ time docker stop signals  
  2. signals  
  3. docker stop signals  0.02s user 0.02s system 4% cpu 0.744 total  
  4. ➜  ~ 

可以看到,加入 exec 命令之后,程序又可以接收到信号正常退出了。当然,如果你 Dockerfile 中的 CMD 是以 shell 格式运行的,即使启动脚本中加入 exec 也是无效的。再者,如果你的程序本身不能针对信号做一些处理,也就谈不上优雅关闭了。 

 

来源:Linux公社内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯