DOCKER FAQ系列(二) (基础篇)

写在前头

docker已经逐渐成为测试人员的基本技能了,在我们使用docker的时候,掌握一些原理,分析一些问题原因对我们能力提升有巨大的帮助。


docker里目录挂载是什么意思,为什么要挂载

Docker容器启动的时候,如果要挂载宿主机的一个目录,可以用-v参数指定。

譬如我要启动一个centos容器,宿主机的/data目录挂载到容器的/home目录,


可通过以下方式指定:


挂载目录的意思,将宿主机的某个目录挂到容器里的某个位置(为什么用  这个词语,是因为 linux的文件系统是以 / 为根,各级目录为子叶向下递进的一个树形结构;


如果我们有块磁盘,一般是先格式化,然后将该磁盘  到某个位置。)

有些人理解的挂载,是说把容器里的目录挂出来,某方面讲这种理解是有错误的,也许你懂了,可是也许你就理解篇了,因为你颠倒了主次。


首先想,现有宿主机还是容器,当然是宿主机。

其次,宿主机的文件目录先有,还是容器里文件目录先有? 当然宿主机。(有人可能疑问,-v /data:/home 如果 /data 在宿主机不存在也会自动创建一个,依然会在容器创建之前)


我们用命令验证下:


看到 source 和destination应该明白了吧?

  • 特别注意:

挂载的时候两个目录都最好使用绝对路径,如果使用了相对路径发现不是自己想要的结果,可以通过·docker inspect yourcontainer id 检查 Mounts内容


那么为什么要挂? 理由很多

  • 容器被创建时候,宿主机给他分配了一块存储空间,这块存储空间由该容器自己拥有,当我们销毁该容器的时候,该存储空间也会被回收

  • 当我们需要查方便的检查日志的时候(容器的命令可能会限制,一些方便的命令容器可能不存在)

  • 当我们需要持久化某些容器内有状态数据的时候

  • 当我们需要方便的修改某些容器内配置参数的时候


为什么绑定了宿主的文件到容器,宿主修改了文件,容器内看到的还是旧的内容啊(蜗牛语录)


在绑定宿主内容的形式中,有一种特殊的形式,就是绑定宿主文件,既:


在 myapp.ini 文件不发生改变的情况下,这样的绑定是和绑定宿主目录性质一样,同样是将宿主文件绑定到容器内部,容器内可以看到这个文件。但是,一旦文件发生改变,情况则有不同。


简单的文件修改,比如 echo "name = jessie" >> myapp.ini,这类修改依旧还是原来的文件,宿主(或容器)对文件进行的改动,另一方是可以看到的。


而复杂的文件操作,比如使用 vim,或者其它编辑器编辑文件,则很有可能会导致一方的修改,另一方看不到。


其原因是这类编辑器在保存文件的时候,经常会采用一种避免写入过程中发生故障而导致文件丢失的策略,既先把内容写到一个新的文件中去,写好了后,再删除旧的文件,然后把新文件改名为旧的文件名,从而完成保存的操作。从这个操作流程可以看出,虽然修改后的文件的名字和过去一样,但对于文件系统而言是一个新的文件了。换句话说,虽然是同名文件,但是旧的文件的 inode 和修改后的文件的 inode 不同。


如上面的例子可以看到,经过 vim 编辑文件后,inode 从 268541 变为了 268716,这就是刚才说的,名字还是那个名字,文件已不是原来的文件了。


而 Docker 的 绑定宿主文件,实际上在文件系统眼里,针对的是 inode,而不是文件名。因此容器内所看到的,依旧是之前旧的 inode 对应的那个文件,也就是旧的内容。


这就出现了之前的那个问题,在宿主内修改绑定文件的内容,结果发现容器内看不到改变,其原因就在于宿主的那个文件已不是原来的文件了?


这类问题解决办法很简单,如果文件可能改变,那么就不要绑定宿主文件,而是绑定一个宿主目录,这样只要目录不跑,里面文件爱咋改就咋改?。


Docker 日志都在哪里?怎么收集(蜗牛语录)

Docker 引擎日志 一般是交给了 Upstart(Ubuntu 14.04) 或者 systemd (CentOS 7, Ubuntu 16.04)。前者一般位于 /var/log/upstart/docker.log 下,后者一般通过 jounarlctl -u docker 来读取。不同系统的位置都不一样,SO上有人总结了一份列表,我修正了一下,可以参考:


为什么我启动容器的时候,容器没多久就退出了?

比如下面一条命令:


很多人运行上面这个命令后,发现两件事

  • 运行该命令后,命令行进入了容器,此时另起shell 运行 docker ps 会发现容器是up的

  • 此时如果输入exit 会退出容器,然后在用docker ps ,容器不见了,docker ps -a 发现该容器exited了


为什么?

  • 容器的运行需要一个主进程,当主进程退出后,容器的生命周期完结 上述我们用 /bin/bash作为入口命令,启动了一个bash进程,由于该进程会进入交互式

  • 当处于交互式状态时候,其实这个进程是不会主动退出的,所以容器会一直运行, 但是当你退出了这个交互进程(比如手动执行exit)容器也就down了

怎么保证我启动一个容器是常驻的,不会退出的

  • 首先你得有一个不会主动退出的前台进程,一般是服务主进程

  • 配合 docker run -d 选项,d 是daemon的意思

如:


当按照上述的方式启动ubuntu容器

  • 不会进入容器

  • 容器不会退出,因为/bin/bash 交给了daemon托管


容器的 ENTRYPOINT 和 CMD 有啥区别,怎么用

CMD

这两个东西可能是大家最容易忽略的了,很多人会说这俩效果一样,可出现问题的时候,你会发现没明白这两者区别的时候是会有多难受

如果大家会写Dockerfile, 那么这两者你就一定要搞清楚


首先看CMD,官网解释地址: https://docs.docker.com/engine/reference/builder/#cmd


一定要看官网(其它的总结,除非你有辨别能力,否则不要信)

这点cmd理解我不准备扯,我准备带大家翻译:


首先说了, CMD的形式结构大概有三种

  • CMD [“executable”,”param1”,”param2”] (exec 形式, 这是首选的)

  • CMD [“param1”,”param2”] (这样写是为作为 ENTRYPOINT的参数的)

  • CMD command param1 param2 (shell 形式)


然后下一句说了, 在Dockerfile里只有一个CMD生效且是最后一个

再下一句说明了CMD 主要目的: 是提供正在执行的容器提供默认值,这些默认值包括可执行. 或者也可以忽略执行,在某些场景你同样还必须配合指定一个ENTRYPOINT的声明


这句翻译就有点拗口了

那么联系下我们的docker build过程,我们根据Dockerfile可以打成image , 再者,打好的image可以被新一个image from , 那么旧者的image的 默认CMD 和新者的CMD 谁会执行? 你是否忽然明白上面说的 包括可执行,和忽略执行?

那么为什么某些场景我们必须配合指定一个 ENTRYPOINT的声明呢?一会我们介绍ENTRYPOINT再说


下面三个Note ,提醒我们要注意:

  • 假如CMD 被用来提供 ENTRYPOINT的默认参数时候, CMD 和 ENTRYPOINT 必须同时在Dockerfile里指定,且必须是 JsonArray的形式

  • 当你提供JsonArray的时候 注意不要使用单引号,这是json规范

  • 不像Sehll形式, exec形式不会唤起一个 command shell. 这意味着正常的shell处理过程不会发生。 举个例子: CMD [“echo”,”$HOME”] 不会做变量的代替(我们启动shell 会自动加载一些环境变量,这是个代替的过程.$HOME代替/root这样)。假如你想执行一个 shell形式的命令或者直接运行一个shell 使用 CMD [ “sh”, “-c”, “echo $HOME” ].即可。 当时用 exec形式的并且直接执行一个shell, 那么这里的环境变量的代替是shell做的不是 docker本身。


官网文档接着结合实例来说了CMD的具体用法:(注意我们这里说的 CMD 是指Dockerfile里写的CMD)


当我们使用 exec形式 和 shell形式时候,当我们运行镜像启动一个容器时候,如果不额外指定,是会默认执行CMD里配置的。

当你在Dockerfile指定这样的:


那么这里 CMD后面的指令将会默认作为 /bin/sh -c的参数执行,即在shell中执行


如果你希望你的CMD 不使用shell执行, 那么你必须如上所说,将你的命令表达成 json array形式,并且还要指定执行体的全路径。这是CMD首选的使用形式。


任何执行体的参数应该独立用字符串在json array中表示如下:


如果你想你的镜像每次启动成容器时执行一个相同的执行命令,你应该考虑使用 ENTRYPOINT 和CMD 结合使用

  • 注意: 如果用户在使用docker run启动容器的时候 指定了额外的命令参数, 它会覆盖你 Dockerfile写的CMD

  • 注意: 不要对CMD和RUN的区别感到困惑, RUN 实际执行了一个命令并且提交成结果;CMD在build image期间并没有做任何执行操作,但是它为了image 启动容器时候提供了默认执行命令


ENTRYPOINT

ENTRYPOINT 有2种形式:

  • ENTRYPOINT [“executable”, “param1”, “param2”] (exec form, preferred)

  • ENTRYPOINT command param1 param2 (shell form)

ENTRYPOINT 允许你把你的容器启动时候去执行某件事情,让你的容器变成可执行的。


exec形式

举个例子下面这条命令会默认启动nginx,并监听在80(注意命令并没有指定额外的运行参数,只是运行了一个nginx image)


docker run <image> 后面若接了命令行参数, 他会默认追加到 exec 形式的ENTRYPOINT 之后,并且会覆盖 CMD指定的参数。


这允许给 entrypoint传递参数。

比如 docker run <image> -d 将会把-d 传给 entrypoint

你可以在命令行中使用 docker run --entrypoint 去覆盖image本身的 ENTRYPOINT


shell形式

在Dockerfile 中指定shell 形式的 ENTRYPOINT


exec形式ENTRYPOINT 举例

您可以使用exec形式ENTRYPOINT来设置相对不变的默认命令和参数,

然后使用其中任何一种形式CMD来设置更可能更改的其他默认值。


以上命令使用 docker build . --tag top

docker run -it --rm --name test top -H

官网doc这里使用这里的命令是点迷惑, 这里的top是它的镜像名,而不是入口命令。注意自己辨别下。


此时你可以另起shell 使用docker exec 查看上述容器进程:

执行: docker exec -it test ps aux


你会发现 首先: Dockerfile 里的CMD 没有生效 -H 被传给了 ENTRYPOINT作为参数,并且Dockerfile里的exec形式的 ENTRYPOINT的启动的容器,PID 为1

Dockerfile里的 ENTRYPOINT 要想在 docker run期间 重写覆盖,你可以使用docker run的 –entrypoint 来覆盖,另外 exec form注意也要用json array,不能有单引号。


此外和shell命令不同,exec 也不会调用shell,那么你执行一个命令时候,像shell那种环境变量的替代是不会发生的,如果你希望用shell 执行,你可以:


shell形式的ENTRYPOINT示例

当你在Dockerfile里指定了 shell形式的ENTRYPOINT , 首先entrypoint会调用 /bin/sh -c . 这种形式会有环境变了的替代过程,并且会忽略 Dockerfile里的CMD 指令,以及 docker run的命令行参数


我们看如下的例子:

Dockerfile里:


当你执行这个镜像时候,你会看到PID 1进程


这时候你可以干净的停止容器


假如你的ENTRYPOINT 没有写 exec ,如下Dockerfile


此时, top -b 将会作为 /bin/sh -c 的参数执行, top -b 将不会是 PID为1的主进程


首先看到 CMD里的 -H 会被忽略,其次 主进程 PID=1是 /bin/sh -c

这时候我们停止容器(docker stop test)的时候,会是一个不干净的停止操作,即我们使用 docker stop test 其实是关闭了top -b的父进程 /bin/sh -c


即 top -b 进程是接受不到 docker stop 发出的 SIGTERM 信号的

所以 top命令还是会存在的,所以 stop不了容器, docker stop 在超时时会强制对容器发出 SIGKILL 信号干掉容器。


说到这,总结下 ENTRYPOINT 和 CMD

  • 首先 一个Dockerfile 至少指定 CMD 和 ENTRYPOINT 中任何一个

  • 当你希望定义一个可执行的容器时候,你应该考虑使用ENTRYPOINT

  • CMD 应该被定义为 ENTRYPOINT的默认参数。 或者是执行那种临时性的命令

  • CMD 当你启动容器提供了一些可选参数,CMD会被覆盖


下表说明了 CMD/ENTRYPOINT的结合(Dockerfile里):


从上表可以得出几个重要结论:

  • 当Dockerfile里 ENTRYPOINT 里使用了shell形式的时候, CMD 无论在哪指定都会被忽略(包括从命令行docker run输入 cmd)


  • 当image没有指定默认的 ENTRYPOINT的时候, CMD将发挥作用,而一旦指定了ENTRYPOINT,CMD会作为 ENTRYPOINT的参数或者完全被 ENTRYPOINT 覆盖


  • 另外在kubernetes中,也有另外两个和这里的CMD/ENTRYPOINT对应,它们叫 command和args 详情见 https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/ 后续在K8S的相关的介绍中会给大家整理。


猜你喜欢

转载自blog.csdn.net/itest_2016/article/details/79131574
今日推荐