Dockerfile-Notebook
关于Docker有太多太多值得去说,以我目前的水平,还无法在一个完全系统化的角度去解读。但Docker还是要用的,故姑且先把Dockerfile的格式小小地记录一下。
Dockerfile
长话短说,Dockerfile即用于告诉Docker如何构建容器的配置文件,其中记录了容器的各种配置信息,包括容器的名称、容器的根目录、容器的环境变量、容器的镜像、容器的端口映射等。
1 |
|
ENTRYPOINT 与 CMD
对于没构建过镜像,仅仅只是下载封装好的镜像,docker run执行的初心者而言,容器的行为与镜像似乎是绑定的,所谓用什么镜像、做什么事。这确乎是事实,因为镜像的搭建目的也就在于为特定应用的执行提供环境,但是正确地认识镜像与容器还是十分有必要的。
我们知道,容器的实现其实是基于container-shim进程为其所服务的主进程提供一个虚拟的环境,你需要CPU执行,container-shim向OS申请时隙,但是确实执行容器内的进程;需要内存,container-shim向OS申请,等等。容器的生命周期也就围绕着其内部的主进程展开,一旦主进程结束,容器也就不再活跃。那么,这个进程是如何决定、设定的呢?
最简单的形式
1 | docker run image "command" |
即在生成容器的指令中在镜像后追加。
当然,这样的指令较为灵活,但是导致使用起来不太便利,因为许多应用的启动指令涉及到不少参数,且往往是固定不变的,反复输入不得不说是一种折磨。
CMD
在编写dockerfile时,我们认识到,可以使用CMD指令设置镜像的初始指令。
1 | CMD ["bash","-c","command"] |
这样配置后,在使用docker run启动容器时,若不设置command,就将默认使用dockerfile中CMD后的指令;当然,若是需要执行与默认不同的指令,还是需要全量输入。
当然,这还远远不足。容器的使用场景,往往是一个固定的应用,但又需要根据具体使用环境微调参数,无论是全量的输入,还是固定的默认,都难以满足要求。
那么,有没有在利用默认机制的同时,又保留一定灵活性的方案呢?
ENTRYPOINT
解决方案就是ENTRYPOINT
1 |
|
ENTRYPOINT同CMD一样,其目的在于为镜像构建容器时指定默认初始指令,但是,ENTRYPOINT所执行的指令,不仅仅是我们为其设定的entrypoint,而是entrypoint+command,即我们在docker run中指定的command,将被追加在entrypoint后,这样,我们就可以将指令固定部分放在entrypoint中,而在构建容器时在决定参数了,非常方便。
值得一提,ENTRYPOINT与CMD可以同时使用,这种情况下,CMD的command将被作为默认参数使用;entrypoint也可以借由在docker run时使用–entrypoint重新指定。
题外话
容器追踪其主进程生命周期的方式很好的表现了其设计思想,但是,却也造成了许多不便。
许多时候,在在容器中运行主要指令前,我们需要对环境进行一定的调整,如果我们将调整的指令写入command,那么在容器执行调整后,就将结束,这显然不是我们想要的;我们也可以使用bash作为指令,再进入容器手动调整与启动,这当然可行,但对于自动化来说却是不可接受的;我们也可以将所有指令写入脚本,直接将脚本作为entrypoint,但这样又将失去灵活性。那么成熟的容器是怎么做的呢?
1 | ENTRYPOINT ["entrypoint.sh"] |
观察redis可以发现,redis将entrypoint设置为entrypoint.sh脚本,在脚本前段执行环境初始化设置指令,最后,以exec “$@”结束。exec 指令将替换当前进程bash的代码,而去执行其后的程序,$@指代全部参数组成的列表,也就是说,容器的主进程bash在执行完初始化后,将以我们给入的参数为可执行程序,重置进程内容,此处我们往往给出程序的启动程序,如redis-server。
当然,我们呢又不得不面对全量拼写与固定默认的抉择了,这也算是一种妥协吧。