Docker run参考(7) – Restart策略(–restart)

运行容器时使用–restart参数可以指定一个restart策略,来指示在退出时容器应该如何重启或不应该重启。
当容器启用restart策略时,将会在docker ps显示Up或者Restarting状态。也可以使用docker events命令来生效中的restart策略。
docker支持如下restart策略:

  • no – 容器退出时不要自动重启。这个是默认值。
  • on-failure[:max-retries] – 只在容器以非0状态码退出时重启。可选的,可以退出docker daemon尝试重启容器的次数。
  • always – 不管退出状态码是什么始终重启容器。当指定always时,docker daemon将无限次数地重启容器。容器也会在daemon启动时尝试重启,不管容器当时的状态如何。
  • unless-stopped – 不管退出状态码是什么始终重启容器,不过当daemon启动时,如果容器之前已经为停止状态,不要尝试启动它。
  • 在每次重启容器之前,不断地增加重启延迟[上一次重启的双倍延迟,从100毫秒开始]来防止影响服务器。这意味着daemon将等待100ms,然后200 ms, 400, 800, 1600等等,直到超过on-failure限制,或执行docker stop或docker rm -f。
    如果容器重启成功[容器启动后并运行至少10秒],然后delay重置为默认的100ms。
    你可以使用on-failure策略指定docker尝试重启容器的最大次数。默认下docker将无限次数重启容器。可以通过docker inspect来查看已经尝试重启容器了多少次。例如,获取容器“my-container”的重启次数:

    1. $ docker inspect -f "{{ .RestartCount }}" my-container
    2. # 2

    或者获取上一次容器重启时间:

    1. $ docker inspect -f "{{ .State.StartedAt }}" my-container
    2. # 2015-03-04T23:47:07.691840179Z

    示例

    1. $ docker run –restart=always redis

    这运行了一个restart策略为always的redis容器,以使得容器退出时,docker将重启它。

    1. $ docker run –restart=on-failure:10 redis

    这个运行了一个restart策略为on-failure,最大重启次数为10的redis容器。如果redis以非0状态退出连续退出超过10次,那么docker将中断尝试重启这个容器。只有on-failure策略支持设置最大重启次数限制。

    Docker run参考(6) – 网络设置

    1. –dns=[]           : Set custom dns servers for the container
    2. –network="bridge" : Connect a container to a network
    3.                       ‘bridge’: create a network stack on the default Docker bridge
    4.                       ‘none’: no networking
    5.                       ‘container:<name|id>’: reuse another container’s network stack
    6.                       ‘host’: use the Docker host network stack
    7.                       ‘<network-name>|<network-id>’: connect to a user-defined network
    8. –network-alias=[] : Add network-scoped alias for the container
    9. –add-host=""      : Add a line to /etc/hosts (host:IP)
    10. –mac-address=""   : Sets the container’s Ethernet device’s MAC address
    11. –ip=""            : Sets the container’s Ethernet device’s IPv4 address
    12. –ip6=""           : Sets the container’s Ethernet device’s IPv6 address
    13. –link-local-ip=[] : Sets one or more container’s Ethernet device’s link local IPv4/IPv6 addresses

    默认下,所有容器都启用了网络并且能够访问外部网络。使用docker run –network none能够完全地禁用网络,这将禁止所有的入站和出站连接。在这个情况下,你只能能够文件或STDIN和STDOUT完成I/O通信。
    发布端口和链接到其它容器只在默认的bridge工作。链接功能是一个旧的功能。你应该始终首选Docker网络驱动的链接功能。
    容器默认使用与主机相同的DNS服务器,不过可以使用–dns覆盖它。
    默认情况下,使用分配给容器的IP地址生成MAC地址。你可以通过–mac-address参数[格式:12:34:56:78:9a:bc]设置一个指定的容器MAC地址。要注意如果你手动指定MAC地址,docker不会检查地址是否唯一。

    支持的网络

  • none – 不使用网络
  • bridge[默认] – 通过veth接口连接容器到bridge
  • host – 在容器内使用主机的网络堆栈
  • container: – 使用其它容器的网络堆栈,通过name或id指定
  • NETWORK – 连接容器到一个用户创建的网络 [使用docker network create命令]
  • Network: none

    设置网络为none容器将无法与外部通信。容器仍然会有一个loopback接口,不过没有外部流量的路由。

    Network: bridge

    设置网络为bridge,容器将使用docker默认的网络设置。主机创建有一个bridge,名为docker0,并为容器创建一对veth接口。其中一个vetch在主机上附着到bridge,而另一个将设置到容器的命名空间内,除了loopback接口。容器在bridge网络分配到一个IP地址,且流量通过这个bridge路由到容器。
    容器默认能够通过它们的IP地址通信。如果要通过名称通信,并且使用链接功能。

    Network: host

    设置网络为host,容器将共享主机的网络堆栈,主机的所有接口将对容器可用。容器的主机名将与主机系统上的主机名匹配。注意–mac-address在host模式时无效。即使在host网络模式下容器默认有它自己的UTS命名空间。因此–hostname在host网络中是允许的,且只更改容器内的hostname。与–hostname类似的–add-host,–dns,–dns-search和–dns-opt选项可用于host网络模式中。这些选项将更新容器内的/etc/hosts或/etc/resolv.conff。不会更改主机中的/etc/hosts和/etc/resolv.conf。
    与默认的bridge模式对比,host模式有更好的网络性能,因为它使用了主机的本地网络堆栈,而bridge必须通过docker deamon进行一级虚拟化。当网络性能要求非常高时,推荐使用这个模式运行容器,例如,生产环境的负载均衡或高性能web server。

    注意:–network=”host”给了容器对本地系统服务全部的访问权限,如D-bus,因此此模式认为是不安全的。

    Network: container

    设置网络为container时,容器将共享另一个容器的网络堆栈。需要以–network container:的格式提供另一个容器的名称。–add-host –hostname –dns –dns-search –dns-opt和–mac-address 在container网络模式中是无效的,–publish –publish-all –expose在container网络模式同样无效。
    如下示例,启动了一个redis容器,绑定在localhost,然后运行另一个容器执行redis-cli命令通过localhost接口连接redis服务器。

    1. $ docker run -d –name redis example/redis –bind 127.0.0.1
    2. $ # use the redis container’s network stack to access localhost
    3. $ docker run –rm -it –network container:redis example/redis-cli -h 127.0.0.1

    User-defined network

    使用docker网络驱动或外部网络驱动插件可以创建一个自己的网络。之后可以连接多个容器到这个网络。一旦连接到用户自定义网络,容器将可以只使用另一个容器的IP地址或名称来通信。
    对于支持多主机网络通信的overlay网络或自定义插件,连接到相同的多主机网络但是从不同主机启动的容器能以这种方式互相通信。
    如下示例使用docker内置的bridge网络驱动创建了一个网络,并在这个网络运行一个容器。

    1. $ docker network create -d bridge my-net
    2. $ docker run –network=my-net -itd –name=container3 busybox

    管理/etc/hosts

    容器将会在/etc/hosts添加容器自身主机名条目,localhost和其它一些常见的条目。–add-host参数可以用来在/etc/hosts添加额外的条目。

    1. $ docker run -it –add-host db-static:86.75.30.9 ubuntu cat /etc/hosts
    2. 172.17.0.22     09d03f76bf2c
    3. fe00::0         ip6-localnet
    4. ff00::0         ip6-mcastprefix
    5. ff02::1         ip6-allnodes
    6. ff02::2         ip6-allrouters
    7. 127.0.0.1       localhost
    8. ::1             localhost ip6-localhost ip6-loopback
    9. 86.75.30.9      db-static

    如果一个容器连接到默认的bridge网络且链接到其它容器,那么这个容器的/etc/hosts文件将添加被链接容器的名称条目。
    如果容器连接到用户定义的网络,容器的/etc/hosts文件将添加在这个网络的所有其它容器的名称条目。

    注意:因此docker会实时更新容器的/etc/hosts文件,可能会出现当容器内的进程读取到空的或不完整的/etc/hosts文件。大多数情况下,重新读取应该能解决这个问题。

    Docker run参考(5) – UTS(–uts)和IPC (–ipc)设置

    UTS设置(–uts)

    1. –uts=""  : Set the UTS namespace mode for the container,
    2.        ‘host’: use the host’s UTS namespace inside the container

    UTS命名空间用于设置主机名和对该命名空间中正在运行的进程可见的域。默认下,所有的容器,包括那么以–network=host运行的容器,有它们自己的UTS命名空间。设置UTS为host将使容器使用与主机相同的U
    TS命名空间。注意–hostname在host UTS模式是无效的。
    当你想在主机更改hostname之后,同时也更改同样的hostname到容器, 这就需要与主机共享UTS命名空间。一个更高级的用例是从容器更改主机的hostname。

    IPC设置(–ipc)

    1. –ipc=""  : Set the IPC mode for the container,
    2.              ‘container:<name|id>’: reuses another container’s IPC namespace
    3.              ‘host’: use the host’s IPC namespace inside the container

    默认下,所有的容器都启用了IPC命名空间。
    IPC(POSIX / SysV IPC)命名空间提供命名的共享内存段,信号量和消息队列的分离。
    共享内存段用来加速内部进程以内存速度通信,而不是通过管道或网络。共享内存通常由数据库和定制(通常是C / OpenMPI,C ++ /使用boost库)的高性能应用程序用于科学计算和金融服务行业。如果这些类型的应用程序分成多个容器,可能需要共享容器的IPC机制。

    Docker run参考(4) – PID设置(–pid)

    1. –pid=""  : Set the PID (Process) Namespace mode for the container,
    2.              ‘container:<name|id>’: joins another container’s PID namespace
    3.              ‘host’: use the host’s PID namespace inside the container

    默认下,所有的容器都启用了PID命名空间。
    PID命名空间提供了进程的分离。PID命名空间删除系统进程视图,允许进程ID可重用,包括pid 1。
    在一些情况下需要容器共享主机进程命名空间,基本上允许容器内的进程可以查看主机的所有进程。例如,构建了一个带调试工具如strace或gdb的容器,想在容器使用这些工具来调试主机的进程。

    示例:容器内运行htop

    创建这个Dockerfile:

    1. FROM alpine:latest
    2. RUN apk add –update htop && rm -rf /var/cache/apk/*
    3. CMD ["htop"]

    构建Dockerfile并tag镜像为myhtop:

    1. $ docker build -t myhtop .

    使用如下命令在容器内运行htop命令:

    1. $ docker run -it –rm –pid=host myhtop

    这样htop就能看到宿主机上的所有进程。

    示例:加入其它容器pid命令空间

    启动一个容器运行redis服务器:

    1. $ docker run –name my-redis -d redis

    通过运行一个带strace的容器来debug这个redis容器:

    1. $ docker run -it –pid=container:my-redis my_strace_docker_image bash
    2. $ strace -p 1

    Docker run参考(3) – 容器标识

    Name (–name)

    此参数有三种方式识别一个容器:

    标识类型 示例值
    UUID long identifier “f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”
    UUID short identifier “f78375b1c487”
    Name “evil_ptolemy”

    UUID标识符来自docker daemon。如果不使用–name参数指定一个容器名称,那么daemon将生成一个随机的名称。定义一个有意义容器名称方便识别容器。此名称可以用来把容器关联在一个指定的网络内。

    PID

    最后,为了方便自动化,可以让docker把容器ID写到一个你指定的文件。这个与一些程序把它们的进程id写到一个文件类似。

    1. –cidfile="": Write the container ID to the file

    Image[:tag]

    这个虽然不是严格识别一个容器的方法,不过可以使用image[:tag]来指定一个特定版本的镜像来运行一个容器。例如docker run ubuntu:14.04。

    Image[@digest]

    使用v2镜像或之后镜像的格式有一个称为digest的内容可寻址标识符。只要用来生成镜像的内容输入没有更改,这个digest值是可预测和可引用的。
    下面的示例使用sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0 digest来从alpine镜像运行一个容器:

    1. $ docker run alpine@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0 date

    Docker run参考(2) – Detached vs foreground模式

    当要启动一个docker容器时,首先必须决定是以detached模式在后台运行容器还是以默认的foreground模式运行:

    1. -d=false: Detached mode: Run container in the background, print new container id

    Detached (-d)

    要在detached模式启动一个容器,必须使用-d=true或仅-d选项。docker是这样设计的,当运行在容器的根进程退出时,以detached模式启动的容器也退出。以detached模式运行的容器当它停止时无法自动删除,因此–rm选项和-d选项不能一起使用。
    不要传递一个service x start命令到deatched的容器。例如,下面的命令是尝试启动nginx服务。

    1. $ docker run -d -p 80:80 my_image service nginx start

    这个命令会成功启动nginx服务。不过在detached的容器会失败。根进程service nginx start启动后立即返回,导致detached容器按照设计停止。因此,nginx服务虽然启动了,不过无法使用。所以要按如下命令启动nginx服务:

    1. $ docker run -d -p 80:80 my_image nginx -g ‘daemon off;’

    要对一个detached容器输入/输出,使用网络连接或共享数据卷。
    要重新附着到一个detached容器,使用docker attach命令。

    Foreground

    在forgroud模式[当-d不指定时],docker run能够在容器启动进程并附着控制台到进程的标准输入,输出和标准错误。它甚至可以伪装为一个TTY(这是大多数命令行可执行程序所需要的)并传递信号。有配置的选项有:

    1. -a=[]           : Attach to `STDIN`, `STDOUT` and/or `STDERR`
    2. -t              : Allocate a pseudo-tty
    3. –sig-proxy=true: Proxy all received signals to the process (non-TTY mode only)
    4. -i              : Keep STDIN open even if not attached

    如果你没有指定-a,那么docker将附着所有标准流。可以从三个标准流(STDIN, STDOUT, STDERR)指定你想连接的标准流。如:

    1. $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash

    对于交互的进程[如shell],为了给容器进程分配一个tty,必须-i -t一起使用。-i -t经常写为-it。当客户端标准输出重定向或管道传递时,不能指定-t:

    1. $ echo test | docker run -i busybox cat

    Linux对运行在容器PID 1的进程特别对待:它会忽略任何信号的默认行为。所以,进程不会收到SIGINT或SIGTERM时停止,除非它要这么做。

    Docker run参考(1) – 一般格式

    基本的docker run命令格式为:

    1. $ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG…]

    docker run命令必须指定创建容器的镜像。镜像开发者可以定义镜像默认的关于:

  • 后台或前台运行
  • 容器标识
  • 网络设置
  • CPU和内存运行时限制
  • 使用docker run [OPTIONS],镜像用户能够添加或覆盖镜像开发者设置的默认参数。并且,镜像用户几乎能够覆盖docker运行时的默认参数。之所以docker run命令有这么多选项设置是因为它能够覆盖镜像和docker运行默认参数。

    Dockerfile参考(18) – SHELL设置执行命令的shell

    格式:

    1. SHELL ["executable", "parameters"]

    SHELL指令可以覆盖命令的shell模式所使用的默认shell。Linux的默认shell是[“/bin/sh”, “-c”],Windows的是[“cmd”, “/S”, “/C”]。SHELL指令必须以JSON格式编写。
    SHELL指令在有两个常用的且不太相同的本地shell:cmd和powershell,以及可选的sh的windows上特别有用。
    SHELL指令可以出现多次。每个SHELL指令覆盖之前的SHELL指令设置的shell,并影响随便的指令。例如:

    1. FROM windowsservercore
    2.  
    3. # Executed as cmd /S /C echo default
    4. RUN echo default
    5.  
    6. # Executed as cmd /S /C powershell -command Write-Host default
    7. RUN powershell -command Write-Host default
    8.  
    9. # Executed as powershell -command Write-Host hello
    10. SHELL ["powershell", "-command"]
    11. RUN Write-Host hello
    12.  
    13. # Executed as cmd /S /C echo hello
    14. SHELL ["cmd", "/S"", "/C"]
    15. RUN echo hello

    当RUN, CMD和ENTRYPOINT使用shell形式时,将使用SHELL指令设置的shell执行。
    以下的示例是windows常见的模式,可以使用SHELL指令精简:

    1. RUN powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

    由docker解析会是:

    1. cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

    这个命令之所以低效有两个原因。首先,没有必须调用cmd.exe。第二,每个使用shell模式的RUN指令需要一个额外的powershell。
    为了优化这个命令更有效率,有两个方法,其中之一使用RUN指令的JSON形式,如:

    1. RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 "c:\foo.txt""]

    虽然没有调用了cmd.exe,不过需要对双引号进行转义,看起来比较冗长。另一种机制是使用SHELL指令和shell形式,使得windows用户可以使用更自然的语法,特别是与escape指令一起用时:

    1. # escape=`
    2.  
    3. FROM windowsservercore
    4. SHELL ["powershell","-command"]
    5. RUN New-Item -ItemType Directory C:Example
    6. ADD Execute-MyCmdlet.ps1 c:example
    7. RUN c:exampleExecute-MyCmdlet -sample ‘hello world’

    构建结果为:

    1. PS E:dockerbuildshell> docker build -t shell .
    2. Sending build context to Docker daemon 3.584 kB
    3. Step 1 : FROM windowsservercore
    4.  —> 5bc36a335344
    5. Step 2 : SHELL powershell -command
    6.  —> Running in 87d7a64c9751
    7.  —> 4327358436c1
    8. Removing intermediate container 87d7a64c9751
    9. Step 3 : RUN New-Item -ItemType Directory C:Example
    10.  —> Running in 3e6ba16b8df9
    11.  
    12.  
    13.     Directory: C:
    14.  
    15.  
    16. Mode                LastWriteTime         Length Name
    17. —-                ————-         —— —-
    18. d—–         6/2/2016   2:59 PM                Example
    19.  
    20.  
    21.  —> 1f1dfdcec085
    22. Removing intermediate container 3e6ba16b8df9
    23. Step 4 : ADD Execute-MyCmdlet.ps1 c:example
    24.  —> 6770b4c17f29
    25. Removing intermediate container b139e34291dc
    26. Step 5 : RUN c:exampleExecute-MyCmdlet -sample ‘hello world’
    27.  —> Running in abdcf50dfd1f
    28. Hello from Execute-MyCmdlet.ps1 – passed hello world
    29.  —> ba0e25255fda
    30. Removing intermediate container abdcf50dfd1f
    31. Successfully built ba0e25255fda
    32. PS E:dockerbuildshell>

    Docker 1.12开始添加了此SHELL功能。

    Dockerfile参考(17) – HEALTHCHECK检查容器是否正常工作

    HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command [通过在容器内运行一个命令来检查容器健康情况]
  • HEALTHCHECK NONE [禁用从base镜像继承的任何healthcheck]
  • HEALTHCHECK指令告诉Docker如何测试一个容器来检查它是否工作正常。这个可以用来检测如web server陷入了死循环且已经无法处理新的连接了,即使server进程仍然在运行。
    当一个容器设置了healthcheck之后,除了正常的状态,它多了一个health状态。这个状态初始为starting。当健康检查通过后,它变成了healthy(不管之前是什么状态)。当连续出现几次失败后,就变成unhealthy。
    在CMD之前的选项有:

  • –interval=DURATION [默认30s]
  • –timeout=DURATION [默认30s]
  • –retries=N [默认3]
  • 当容器启动之后,首先等待interval秒然后进行健康检查,然后等这次检查完成后再等interval秒之后继续再次检查,如此循环。
    如果有一个检查所花的时间超过了timeout秒,那么就认为这次检查失败了。
    如果连续retries次失败,就认为此容器状态为unhealthy。
    Dockerfile中只能使用一个HEALTHCHECK指令,如果设置了多次,就取最后一个。
    这个命令的CMD之后的命令可以是一个shell命令[如HEALTHCHECK CMD /bin/check-running]或者exec数组[像Dockerfile命令ENTRYPOINT使用的数组]。
    命令退出状态表示容器的健康状态。可能的值为:

  • 0: success – 容器是健康的。
  • 1: unhealthy – 容器工作不正常。
  • 2: reserved – 不要使用这个退出代码
  • 例如,隔5分钟检查一次容器确保web server能够在3秒内正常输出主页:

    1. HEALTHCHECK –interval=5m –timeout=3s
    2.   CMD curl -f http://localhost/ || exit 1

    为了方便调查失败原因,检查命令的输出(UTF-8编码)会写到health状态中,并且可以通过docker inspect查看。输出应该尽量短,不大于4096字节。
    HEALTHCHECK功能是在Docker 1.12添加的。

    Dockerfile参考(16) – ONBUILD向镜像添加触发指令

    格式:

    1. ONBUILD [INSTRUCTION]

    ONBUILD指令向镜像添加稍后要执行的触发指令,该触发指令在该镜像作为另一个镜像构建的base镜像时执行。触发指令在另一个镜像构建的Dockerfile的FROM指令后马上执行,就像FROM指令后插入触发指令一样。
    任何的构建指令都可以注册为触发指令。
    如果你正在构建的镜像会作为构建其它镜像的base镜像时,ONBUILD会有用,例如一个应用程序的构建环境或者可能需要用户自定义配置的daemon。
    例如,如果你的镜像是一个可重复使用的Python应用程序镜像,你将需要把应用程序源码添加到指定的目录,和有可能需要在添加代码之后执行脚本启动应用程序。你不能仅仅的在base镜像中调用ADD和RUN,因为这时还没有应用程序源代码,并且每个应用程序配置都可能不一样。你可以简单地为应用程序开发者提供一个样本Dockerfile来复制粘贴到它们的应用程序,不过这个比较低效,容易出错和难以更新,因为它与应用程序特定代码混合了。
    解决方案是使用ONBUIL来注册一个在下一个镜像构建时执行的高级指令。
    下面是它的工作原理:
    1.当镜像构建程序遇到ONBUILD指令时,构建程序把这个触发器添加到正在构建的镜像元数据中。这个ONBUILD指令不会影响到当前镜像的构建过程。
    2.在完成构建镜像后,在关键词OnBuild下,所有的触发器明显地存储到了镜像。之后也可以使用docker inspect来查看它们。
    3.之后其它镜像构建可以会用到上面的镜像作为base镜像,这个可以直接使用FROM引入base镜像。在构建程序执行Dockerfile中的FROM指令时,其中部分处理过程查找ONBUILD触发器,并按原来注册的顺序来执行触发指令。如果任何的一个触发器失败了,FROM指令将中断,反过来导致构建失败。如果所有的触发器指令执行成功,FROM指令就执行完成了,构建继教执行FROM后的指令。
    4.构建新镜像完成时会清除触发器,也就是使用带触发器镜像作为base镜像构建新镜像不会继承它的触发器。
    例如base镜像的Dockerfile如下,镜像名称为baseimg:

    1. […]
    2. ONBUILD ADD . /app/src
    3. ONBUILD RUN /usr/local/bin/python-build –dir /app/src
    4. […]

    然后下面是构建新镜像的Dockerfile,镜像名称newimg:

    1. FROM baseimg
    2. […]

    当构建newimg镜像时,在构建程序执行FROM baseimg指令时,会依次执行baseimg注册的触发指令ADD . /app/src和RUN /usr/local/bin/python-build –dir /app/src,相当于在FROM baseimg指令后面自动添加了这两个指令。