尼奥:为什么那些程序会被删除?
先知:或许发生故障了,或许有更好的程序替代它,这种事天天发生,一旦这种事情发生了,程序就会躲在母体里或是选择回到万物之源。
“重启”是一个具有哲学意义的话题,比如《黑客帝国》中特工史密斯可以随时在一个身体上重启自己;《明日边缘》中阿汤哥饰演的男主角在一次次的重启中不断进步,最终战胜了大 Boss;程序员的段子中,“你重启下试试”非常经典。虽然重启不能解决所有的问题,但还是能解决大部分问题的,因为重启能清除应用的当前状态,包括任何不在正常状态的代码。对于 Docker,我们可以将 restart 作为一种自我修复的机制。
如果把 Docker 容器看做生命,那必然是“向死而生”的一生:它们可能会随时被停止或删除。默认情况下,所有运行过程中产生的数据也将被清除。但就像《黑客帝国》中的特工史密斯一样,容器也可以立马以初始的状态被重启。无疑从这个角度来讲,暂时的任务是容器最常用的场景。但实际上,容器也可以完美运行 web 服务,还可以通过 volume 实现数据库和持久化数据存储。MongoDB,MySQL 和 Postgres 都是 Docker Hub 上最流行的 Docker 镜像。
一个容器的“传记”(Containers Life Story)
都有哪些因素影响了容器的平均寿命呢?我们可以通过容器的整个存在过程进行深入研究。在 Docker Engine 记录了容器整个生命周期的事件,还有其它的重要信息,都保存在了 /var/log/docker.log
中。我们也可以通过 docker events
命令行大体看下某个容器。这个命令会向 Docker Engine 查询某个时间段内容器内发生的重要事件。比如我们先启动一个容器:
然后,运行 docker events
看看哪些事件会被记录下来:
哇!能在命令行看到容器启动过程幕后发生了什么!
-
首先,由于在本地未能找到镜像,Docker client 通过 Remote API 从 Docker Hub pull 下镜像;
-
接下来,创建容器,添加 stdout/stderr 到终端;
-
然后,新容器被加入到默认的 bridge 网络,被 engine 启动;
-
最终,容器完成自己的使命后,就会被删除。在此之前,Docker engine 会将其移出默认的 bridge 网络中。
容器已死,容器万岁!(The container is dead, long live the container!)
虽然这个容器已经“死了”,我们还是能用 docker ps -a
命令捕捉到它生前的“音容笑貌”。
看起来这个容器去得很平静,因为 Exit(0) 并不是每个容器都能达到的境界。
而且虽然容器停止了,我们还是能找到它的“遗产”,因为所有容器的数据存储只有在 docker rm
执行后才会被正式销毁。 docker export
命令可以将容器的文件系统存储到一个 tar 包中。然后可以用 docker import
将其导入到同主机上的另一个容器中,或一个新的容器中。
请注意 docker export
导出的数据中不包含容器的历史。实际上,当 tar 包被导入到一个镜像中,结果镜像会被压缩为一层。
如果你在运行短期的前台操作容器,这些数据堆积会额外占用很多空间。docker run -rm
可以在容器停止后自动清理容器状态数据和镜像层。
重生?(Is there life after death?)
研究发现动物的基因并不会在其身体死亡立即消失,而是会存活长达四天,甚至有一些基因会在死后变得更加活跃。对于容器的“基因”当然可以保持更长的时间,而且还可以随时“复活”。
昨日重现(Containers entire life flashes in front of your eyes the second before they die)
docker event
不但让我们看到 Docker Engine 内部的工作,还能帮我们自动应对一些容器的事件。比如,类似 “Registrator” 的工具会在服务上线和下线时,使用这种机制向 Consul 自动发送注册和注销请求,可以告诉负载均衡器有新的实例进来,或有实例不能提供服务了。
生命本该如此(This is your life)
来自 GliderLabs 的 Matt Good 曾用一张图描述过 Docker 容器整个生命周期的事件,和引发这些事件的命令。
熟悉 Linux 信号的人都会发现这很像 Linux 中进程的生命周期,因为容器本质上就是 Linux 进程。只是 Docker Engine 利用了 Linux 内核的特性,将容器隔离起来,但容器内的进程可以和文件系统或网络交互,就好像自己是系统内唯一的进程。信号提供了一种处理异步事件的方式,可以用在 Docker 容器的进程上。
不能承受的容器之轻(The unbearable lightness of being a container)
就像 Linux 一样,可以用 kill 命令停止一个不正常或空闲的容器,并且无需注销或重启底层的服务器。Kill 命令会向容器中的主进程发送一个 SIGKILL 的 Linux 信号,同样 stop 命令会发送 SIGSTOP 的Linux 信号。这些在 Brian DeHamer 的文章中有详细的描述,本文将重点分析上图中其它的命令和事件:pause,OOM 和 destroy。
暂停(Making a pause)
为什么要 pause 一个容器?好吧,你可能需要暂停一个拖慢进度的容器,或者想对这个容器做一下备份。
有人预测 docker pause
未来可以用在容器的热迁移上。理论上,容器的热迁移是没有意义的,因为它们是无状态的,一次性的,还可以随时重启,但是……
避免“拥堵”(Defending yourself from being choked)
默认情况下,所有的容器都是平等的:它们享有同样的 CPU 周期和 IO,还能自由地使用内存。但某些情况下,我们需要用一些限制参数打破这种平等的待遇。比如,为了防止某些容器一直占用内存,造成 OOM(Out Of Memory)事件,“堵塞”服务器。但是,需要先去设置内存的限制。
首先我们来模拟下 OOM 的情景,来看下如果设置了内存限制,Docker Engine 是如何发挥作用的:
这个例子基于《Docker in Practice》一书中 301 的一个实例,在第一种情况下,被杀死的是进程(非主进程),而第二种情况下才是容器,为什么?
健康检查(Health checks that could save your container)
要注意 Linux 内核在异常情况(比如资源不足)下只会 kill 一个进程,可能这样做已经太迟了。为什么不在应用出现问题前,提前检查呢?
在 Docker 1.12 中,不但可以在运行时进行限制,还能在启动容器时添加用户自定义的健康检查探针。比如,我们可以周期性地验证一个 web 服务是否在正常工作,而不仅仅是避免紊乱情况或内存溢出。
可以将健康检查作为 docker run
的选项,或写在 Dockerfile 中,docker ps
命令除了容器的常规状态,也会显示其“健康状况”。如下所示,第一次 docker ps
执行时,监控探针还是 starting 的状态,当第一个探针通过后变成 healthy 的状态。接下来我们用 docker inspect
查看容器的健康状况。我们通过删除一个配置文件制造不健康的假象,在一系列的探针失败后,最终容器被加上了 unhealthy 的标志。
在当前的 Docker Engine 版本(1.12r3)中,容器不会在 unhealthy 的状态下重启,所以要检测容器的状态,手动地重启容器。
重启(Reboot your life or your container)
在程序员的段子中,“你重启下试试”是很经典的一个,虽然重启不能解决所有的问题,但还是能解决大部分问题的,因为重启能清除应用的当前状态,包括任何不在正常状态的代码。
对于 Docker,我们可以用 restart 作为一种自我修复的机制(就像《明日边缘》中汤姆克鲁斯饰演的男主角)。但是,需要注意的是,默认情况下容器不会在主进程存在的情况下重新启动。我们可以强制 Docker Engine 在任何情况下都重新发布这个容器,或者只在主进程错误退出时重启这个容器:
重置(We should just reset)
小故障可能会引起整个应用的崩溃,on-failure 重启的政策可以帮助我们在容器返回非 0 退出时就重新发布。
注意上面 Docker Engine 是如何提高再启动延时的,直到其达到最大的 on-failure 重启数。
不被打扰的容器(Untroubled containers)
在 Docker 1.12 中,又可以运行 daemonless 的容器啦!这意味着你可以在不影响或重启容器的情况下,停止、升级、重启Docker Engine,期间服务不会受到任何影响。这个特性之前被取消过,因为会给 Docker 初学者造成困惑。
为了能使用这个功能,要在启动 Docker Engine 时添加 live-restore 的 选项,保证 Docker 在关闭和重启的过程中,不会 kill 运行中的容器。如下,使用 docker-machine 传递 live-restore 的选项:
颂词(A eulogy)
容器确实随时都面临着“死亡”,但是有了重启,健康检查探针和热修复机制,没有必要担心。套用马克·吐温的一句话:“对死亡的恐惧源于对生活的恐惧。完全活过的容器,随时都准备着死。”