【Docker】Docker run 命令的使用方法

admin / Docker / / 7400 字 · 约 42 分钟 / 616 次阅读 /
📑 文章章节大纲 (点击平滑直达)

【编者的话】在 Docker 中,run 应该是用户使用最多的命令了,很多读者反馈不是很明白 run 命令的用法,而且相关的书籍、中文资料中对 run 命令的描述也不是非常完整,所以 DockerOne 组织翻译了 Docker 官方的文档,以飨读者。注意,本文基于最新的 Docker 1.4 文档翻译。

Docker 会在隔离的容器中运行进程。当运行 docker run 命令时,Docker 会启动一个进程,并为这个进程分配其独占的文件系统、网络资源和以此进程为根进程的进程组。在容器启动时,镜像 可能已经定义了要运行的二进制文件、暴露的网络端口等,但是用户可以通过 docker run 命令重新定义(译者注:docker run 可以控制一个容器运行时的行为,它可以覆盖 docker build 在构建镜像时的一些默认配置),这也是为什么 run 命令相比于其它命令有如此多的参数的原因。

命令格式

最基本的 docker run 命令的格式如下:

$ sudo docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]

如果需要查看[OPTIONS]的详细使用说明,请参考 Docker 关于 OPTIONS 的 章节。这里仅简要介绍 Run 所使用到的参数。OPTIONS 总起来说可以分为两类:

  1. 设置运行方式:
    • 决定容器的运行方式,前台执行还是后台执行;
    • 设置 containerID;
    • 设置网络参数;
    • 设置容器的 CPU 和内存参数;
  2. 设置镜像的默认资源,也就是说用户可以使用该命令来覆盖在镜像构建时的一些默认配置。

docker run [OPTIONS] 可以让用户完全控制容器的生命周期,并允许用户覆盖执行 docker build 时所设定的参数,甚至也可以修改本身由 Docker 所控制的内核级参数。

Operator exclusive options

当执行 docker run 时可以设置以下参数:

  • Detached vs Foreground
    • Detached (-d)
  • Container Identification
    • Name (--name)
  • IPC Setting
  • Network Settings
  • Clean Up (--rm)
  • Runtime Constraints on CPU and Memory
  • Runtime Privilege, Linux Capabilities, and LXC Configuration

接下来我们依次进行介绍。

Detached vs foreground

当我们启动一个容器时,首先需要确定这个容器是运行在前台还是运行在后台。

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

Detached (-d)

如果在 docker run 后面追加 -d=true 或者 -d ,那么容器将会运行在后台模式。此时所有 I/O 数据只能通过网络资源或者共享卷组来进行交互。因为容器不再监听你执行 docker run 的这个终端命令行窗口。但你可以通过执行 docker attach 来重新附着到该容器的回话中。需要注意的是,容器运行在后台模式下,是不能使用 --rm 选项的。

Foregroud 在前台模式下(不指定 -d 参数即可),Docker 会在容器中启动进程,同时将当前的命令行窗口附着到容器的标准输入、标准输出和标准错误中。也就是说容器中所有的输出都可以在当前窗口中看到。甚至它都可以虚拟出一个 TTY 窗口,来执行信号中断。这一切都是可以配置的:

-a=[]             : Attach to `STDIN`, `STDOUT` and/or `STDERR`
-t=false           : Allocate a pseudo-tty
--sig-proxy=true : Proxify all received signal to the process (non-TTY mode only)
-i=false            : Keep STDIN open even if not attached

如果在执行 run 命令时没有指定 -a 参数,那么 Docker 默认会挂载所有标准数据流,包括输入输出和错误,你可以单独指定挂载哪个标准流。

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

如果要进行交互式操作(例如 Shell 脚本),那我们必须使用 -i -t 参数同容器进行数据交互。但是当通过管道同容器进行交互时,就不需要使用 -t 参数,例如下面的命令:

echo test | docker run -i busybox cat

容器识别

Name(--name)

可以通过三种方式为容器命名:

  1. 使用 UUID 长命名("f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778")
  2. 使用 UUID 短命令("f78375b1c487")
  3. 使用 Name("evil_ptolemy")

这个 UUID 标示是由 Docker deamon 生成的。如果你在执行 docker run 时没有指定 --name ,那么 deamon 会自动生成一个随机字符串 UUID。但是对于一个容器来说有个 name 会非常方便,当你需要连接其它容器时或者类似需要区分其它容器时,使用容器名称可以简化操作。无论容器运行在前台或者后台,这个名字都是有效的。

PID equivalent

如果在使用 Docker 时有自动化的需求,你可以将 containerID 输出到指定的文件中(PIDfile),类似于某些应用程序将自身 ID 输出到文件中,方便后续脚本操作。

--cidfile="": Write the container ID to the file

Image[:tag]

当一个镜像的名称不足以分辨这个镜像所代表的含义时,你可以通过 tag 将版本信息添加到 run 命令中,以执行特定版本的镜像。例如: docker run ubuntu:14.04

IPC Settings

默认情况下,所有容器都开启了 IPC 命名空间。

--ipc=""  : Set the IPC mode for the container,
'container:<name|id>': reuses another container's IPC namespace
'host': use the host's IPC namespace inside the container

IPC(POSIX/SysV IPC)命名空间提供了相互隔离的命名共享内存、信号灯变量和消息队列。

共享内存可以提高进程数据的交互速度。共享内存一般用在数据库和高性能应用(C/OpenMPI、C++/using boost libraries)上或者金融服务上。如果需要容器中部署上述类型的应用,那么就应该在多个容器直接使用共享内存了。

Network settings

默认情况下,所有的容器都开启了网络接口,同时可以接受任何外部的数据请求。

--dns=[]         : Set custom dns servers for the container
--net="bridge"   : Set the Network mode for the container
                          'bridge': creates a new network stack for the container on the docker bridge
                          'none': no networking for this container
                          'container:<name|id>': reuses another container network stack
                          'host': use the host network stack inside the container
--add-host=""    : Add a line to /etc/hosts (host:IP)
--mac-address="" : Sets the container's Ethernet device's MAC address

你可以通过 docker run --net none 来关闭网络接口,此时将关闭所有网络数据的输入输出,你只能通过 STDIN、STDOUT 或者 files 来完成 I/O 操作。默认情况下,容器使用主机的 DNS 设置,你也可以通过 --dns 来覆盖容器内的 DNS 设置。同时 Docker 为容器默认生成一个 MAC 地址,你可以通过 --mac-address 12:34:56:78:9a:bc 来设置你自己的 MAC 地址。

Docker 支持的网络模式有:

  • none。关闭容器内的网络连接
  • bridge。通过 veth 接口来连接容器,默认配置
  • host。允许容器使用 host 的网络堆栈信息。 注意:这种方式将允许容器访问 host 中类似 D-BUS 之类的系统服务,所以认为是不安全的。
  • container。使用另外一个容器的网络堆栈信息。 None 模式

将网络模式设置为 none 时,这个容器将不允许访问任何外部 router。这个容器内部只会有一个 loopback 接口,而且不存在任何可以访问外部网络的 router。

Bridge 模式

Docker 默认会将容器设置为 bridge 模式。此时在主机上面将会存在一个 docker0 的网络接口,同时会针对容器创建一对 veth 接口。其中一个 veth 接口是在主机充当网卡桥接作用,另外一个 veth 接口存在于容器的命名空间中,并且指向容器的 loopback。Docker 会自动给这个容器分配一个 IP,并且将容器内的数据通过桥接转发到外部。

Host 模式

当网络模式设置为 host 时,这个容器将完全共享 host 的网络堆栈。host 所有的网络接口将完全对容器开放。容器的主机名也会存在于主机的 hostname 中。这时,容器所有对外暴露的端口和对其它容器的连接,将完全失效。

Container 模式

当网络模式设置为 Container 时,这个容器将完全复用另外一个容器的网络堆栈。同时使用时这个容器的名称必须要符合下面的格式:--net container:.

比如当前有一个绑定了本地地址 localhost 的 Redis 容器。如果另外一个容器需要复用这个网络堆栈,则需要如下操作:

$ sudo docker run -d --name redis example/redis --bind 127.0.0.1
$ # use the redis container's network stack to access localhost
$ sudo docker run --rm -ti --net container:redis example/redis-cli -h 127.0.0.1

管理/etc/hosts /etc/hosts 文件中会包含容器的 hostname 信息,我们也可以使用 --add-host 这个参数来动态添加/etc/hosts 中的数据。

$ /docker run -ti --add-host db-static:86.75.30.9 ubuntu cat /etc/hosts
172.17.0.22     09d03f76bf2c
fe00::0         ip6-localnet
ff00::0         ip6-mcastprefix
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters
127.0.0.1       localhost
::1             localhost ip6-localhost ip6-loopback
86.75.30.9      db-static

Clean up (--rm)

默认情况下,每个容器在退出时,它的文件系统也会保存下来,这样一方面调试会方便些,因为你可以通过查看日志等方式来确定最终状态。另外一方面,你也可以保存容器所产生的数据。但是当你仅仅需要短暂的运行一个容器,并且这些数据不需要保存,你可能就希望 Docker 能在容器结束时自动清理其所产生的数据。

这个时候你就需要--rm 这个参数了。 注意:--rm 和 -d 不能共用!

--rm=false: Automatically remove the container when it exits (incompatible with -d)

Security configuration

--security-opt="label:user:USER"   : Set the label user for the container
--security-opt="label:role:ROLE"   : Set the label role for the container
--security-opt="label:type:TYPE"   : Set the label type for the container
--security-opt="label:level:LEVEL" : Set the label level for the container
--security-opt="label:disable"     : Turn off label confinement for the container
--secutity-opt="apparmor:PROFILE"  : Set the apparmor profile to be applied  to the container

你可以通过 --security-opt 修改容器默认的 schema 标签。比如说,对于一个 MLS 系统来说(译者注:MLS 应该是指 Multiple Listing System),你可以指定 MCS/MLS 级别。使用下面的命令可以在不同的容器间分享内容:

#docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash

如果是 MLS 系统,则使用下面的命令:

# docker run --security-opt label:level:TopSecret -i -t rhel7 bash

使用下面的命令可以在容器内禁用安全策略:

# docker run --security-opt label:disable -i -t fedora bash

如果你需要在容器内执行更为严格的安全策略,那么你可以为这个容器指定一个策略替代,比如你可以使用下面的命令来指定容器只监听 Apache 端口:

# docker run --security-opt label:type:svirt_apache_t -i -t centos bash

注意:此时,你的主机环境中必须存在一个名为 svirt_apache_t 的安全策略。

Runtime constraints on CPU and memory

下面的参数可以用来调整容器内的性能。

-m="": Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
-c=0 : CPU shares (relative weight)

通过 docker run -m 可以调整容器所使用的内存资源。如果主机支持 swap 内存,那么可以使用 -m 可以设定比主机物理内存还大的值。

同样,通过 -c 可以调整容器的 CPU 优先级。默认情况下,所有的容器拥有相同的 CPU 优先级和 CPU 调度周期,但你可以通过 Docker 来通知内核给予某个或某几个容器更多的 CPU 计算周期。

比如,我们使用 -c 或者 --cpu-shares =0 启动了 C0、C1、C2 三个容器,使用-c/--cpu-shares=512 启动了 C3 容器。这时,C0、C1、C2 可以 100%的使用 CPU 资源(1024),但 C3 只能使用 50%的 CPU 资源(512)。如果这个主机的操作系统是时序调度类型的,每个 CPU 时间片是 100 微秒,那么 C0、C1、C2 将完全使用掉这 100 微秒,而 C3 只能使用 50 微秒。

Runtime privilege, Linux capabilities, and LXC configuration

--cap-add: Add Linux capabilities
--cap-drop: Drop Linux capabilities
--privileged=false: Give extended privileges to this container
--device=[]: Allows you to run devices inside the container without the --privileged flag.
--lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"

默认情况下,Docker 的容器是没有特权的,例如不能在容器中再启动一个容器。这是因为默认情况下容器是不能访问任何其它设备的。但是通过"privileged",容器就拥有了访问任何其它设备的权限。

当操作者执行 docker run --privileged 时,Docker 将拥有访问主机所有设备的权限,同时 Docker 也会在 apparmor 或者 selinux 做一些设置,使容器可以容易的访问那些运行在容器外部的设备。你可以访问 Docker 博客 来获取更多关于--privileged 的用法。

同时,你也可以限制容器只能访问一些指定的设备。下面的命令将允许容器只访问一些特定设备:

$ sudo docker run --device=/dev/snd:/dev/snd ...

默认情况下,容器拥有对设备的读、写、创建设备文件的权限。使用 :rwm 来配合 --device ,你可以控制这些权限。

 $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk  /dev/xvdc

Command (m for help): q
$ sudo docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk  /dev/xvdc
You will not be able to write the partition table.

Command (m for help): q

$ sudo docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk  /dev/xvdc
    crash....

$ sudo docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk  /dev/xvdc
fdisk: unable to open /dev/xvdc: Operation not permitted

使用 --cap-add--cap-drop ,配合 --privileged ,你可以更细致的控制人哦怒气。默认使用这两个参数的情况下,容器拥有一系列的内核修改权限,这两个参数都支持 all 值,如果你想让某个容器拥有除了 MKNOD 之外的所有内核权限,那么可以执行下面的命令:

$ sudo docker run --cap-add=ALL --cap-drop=MKNOD ...

如果需要修改网络接口数据,那么就建议使用 --cap-add=NET_ADMIN ,而不是使用 --privileged

$ docker run -t -i --rm  ubuntu:14.04 ip link add dummy0 type dummy
RTNETLINK answers: Operation not permitted
$ docker run -t -i --rm --cap-add=NET_ADMIN ubuntu:14.04 ip link add dummy0 type dummy

如果要挂载一个 FUSE 文件系统,那么就需要 --cap-add--device 了。

$ docker run --rm -it --cap-add SYS_ADMIN sshfs sshfs sven@10.10.10.20:/home/sven /mnt
fuse: failed to open /dev/fuse: Operation not permitted
$ docker run --rm -it --device /dev/fuse sshfs sshfs sven@10.10.10.20:/home/sven /mnt
fusermount: mount failed: Operation not permitted
$ docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs
# sshfs sven@10.10.10.20:/home/sven /mnt
The authenticity of host '10.10.10.20 (10.10.10.20)' can't be established.
ECDSA key fingerprint is 25:34:85:75:25:b0:17:46:05:19:04:93:b5:dd:5f:c6.
Are you sure you want to continue connecting (yes/no)? yes
sven@10.10.10.20's password:
root@30aa0cfaf1b5:/# ls -la /mnt/src/docker
total 1516
drwxrwxr-x 1 1000 1000   4096 Dec  4 06:08 .
drwxrwxr-x 1 1000 1000   4096 Dec  4 11:46 ..
-rw-rw-r-- 1 1000 1000     16 Oct  8 00:09 .dockerignore
-rwxrwxr-x 1 1000 1000    464 Oct  8 00:09 .drone.yml
drwxrwxr-x 1 1000 1000   4096 Dec  4 06:11 .git
-rw-rw-r-- 1 1000 1000    461 Dec  4 06:08 .gitignore

如果 Docker 守护进程在启动时选择了 lxc lxc-driver( docker -d --exec-driver=lxc ),那么就可以使用 --lxc-conf 来设定 LXC 参数。但需要注意的是,未来主机上的 Docker deamon 有可能不会使用 LXC,所以这些参数有可能会包含一些没有实现的配置功能。这意味着,用户在操作这些参数时必须要十分熟悉 LXC。

特别注意:当你使用 --lxc-conf 修改容器参数后,Docker deamon 将不再管理这些参数,那么用户必须自行进行管理。比如说,你使用 --lxc-conf 修改了容器的 IP 地址,那么在/etc/hosts 里面是不会自动体现的,需要你自行维护。

Overriding Dockerfile image defaults

当开发者使用 Dockerfile 进行 build 或者使用 commit 提交容器时,开发人员可以设定一些镜像默认参数。

这些参数中,有四个是无法被覆盖的:FROM、MAINTAINER、RUN 和 ADD,其余参数都可以通过 docker run 进行覆盖。我们将介绍如何对这些参数进行覆盖。

  • CMD (Default Command or Options)
  • ENTRYPOINT (Default Command to Execute at Runtime)
  • EXPOSE (Incoming Ports)
  • ENV (Environment Variables)
  • VOLUME (Shared Filesystems)
  • USER
  • WORKDIR

CMD (default command or options)

$ sudo docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]

这个命令中的 COMMAND 部分是可选的。因为这个 IMAGE 在 build 时,开发人员可能已经设定了默认执行的命令。作为操作人员,你可以使用上面命令中新的 command 来覆盖旧的 command。

如果镜像中设定了 ENTRYPOINT,那么命令中的 CMD 也可以作为参数追加到 ENTRYPOINT 中。

ENTRYPOINT (default command to execute at runtime)

--entrypoint="": Overwrite the default entrypoint set by the image

这个 ENTRYPOINT 和 COMMAND 类似,它指定了当容器执行时,需要启动哪些进程。相对 COMMAND 而言,ENTRYPOINT 是很难进行覆盖的,这个 ENTRYPOINT 可以让容器设定默认启动行为,所以当容器启动时,你可以执行任何一个二进制可执行程序。你也可以通过 COMMAND 为 ENTRYPOINT 传递参数。但当你需要在容器中执行其它进程时,你就可以指定其它 ENTRYPOINT 了。

下面就是一个例子,容器可以在启动时自动执行 Shell,然后启动其它进程。

$ sudo docker run -i -t --entrypoint /bin/bash example/redis
#or two examples of how to pass more parameters to that ENTRYPOINT:
$ sudo docker run -i -t --entrypoint /bin/bash example/redis -c ls -l
$ sudo docker run -i -t --entrypoint /usr/bin/redis-cli example/redis --help

EXPOSE (incoming ports)

Dockefile 在网络方面除了提供一个 EXPOSE 之外,没有提供其它选项。下面这些参数可以覆盖 Dockefile 的 expose 默认值:

--expose=[]: Expose a port or a range of ports from the container
        without publishing it to your host
-P=false   : Publish all exposed ports to the host interfaces
-p=[]      : Publish a container᾿s port to the host (format:
         ip:hostPort:containerPort | ip::containerPort |
         hostPort:containerPort | containerPort)
         (use 'docker port' to see the actual mapping)
--link=""  : Add link to another container (name:alias)
  • -expose 可以让容器接受外部传入的数据。容器内监听的端口不需要和外部主机的端口相同。比如说在容器内部,一个 HTTP 服务监听在 80 端口,对应外部主机的端口就可能是 49880.

如果使用 -p 或者 -P ,那么容器会开放部分端口到主机,只要对方可以连接到主机,就可以连接到容器内部。当使用 -P 时,Docker 会在主机中随机从 49153 和 65535 之间查找一个未被占用的端口绑定到容器。你可以使用 docker port 来查找这个随机绑定端口。

当你使用 --link 方式时,作为客户端的容器可以通过私有网络形式访问到这个容器。同时 Docker 会在客户端的容器中设定一些环境变量来记录绑定的 IP 和 PORT。

ENV (environment variables)

HOME    Set based on the value of USER
HOSTNAME    The hostname associated with the container
PATH    Includes popular directories, such as :
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TERM    xterm if the container is allocated a psuedo-TTY

当容器启动时,会自动在容器中初始化这些变量。

操作人员可以通过 -e 来设定任意的环境变量,甚至覆盖已经存在的环境变量,或者是在 Dockerfile 中通过 ENV 设定的环境变量。

$ sudo docker run -e "deep=purple" --rm ubuntu /bin/bash -c export
declare -x HOME="/"
declare -x HOSTNAME="85bc26a0e200"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -x PWD="/"
declare -x SHLVL="1"
declare -x container="lxc"
declare -x deep="purple"

操作人员可以通过 -h 来设定 hostname。也可以使用"--link name:alias"来设定环境变量,当使用 --link 后,Docker 将根据后面提供的 IP 和 PORT 信息来连接服务端容器。下面就是使用 redis 的例子:

# Start the service container, named redis-name
$ sudo docker run -d --name redis-name dockerfiles/redis
4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3

# The redis-name container exposed port 6379
$ sudo docker ps
CONTAINER ID        IMAGE                      COMMAND                CREATED             STATUS              PORTS               NAMES
4241164edf6f        $ dockerfiles/redis:latest   /redis-stable/src/re   5 seconds ago       Up 4 seconds        6379/tcp            redis-name

# Note that there are no public ports exposed since we didn᾿t use -p or -P
$ sudo docker port 4241164edf6f 6379

你使用 --link 后,就可以获取到关于 Redis 容器的相关信息。

$ sudo docker run --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c export
declare -x HOME="/"
declare -x HOSTNAME="acda7f7b1cdc"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -x PWD="/"
declare -x REDIS_ALIAS_NAME="/distracted_wright/redis"
declare -x REDIS_ALIAS_PORT="tcp://172.17.0.32:6379"
declare -x REDIS_ALIAS_PORT_6379_TCP="tcp://172.17.0.32:6379"
declare -x REDIS_ALIAS_PORT_6379_TCP_ADDR="172.17.0.32"
declare -x REDIS_ALIAS_PORT_6379_TCP_PORT="6379"
declare -x REDIS_ALIAS_PORT_6379_TCP_PROTO="tcp"
declare -x SHLVL="1"
declare -x container="lxc"
#And we can use that information to connect from another container as a client:
$ sudo docker run -i -t --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT'
172.17.0.32:6379>

Docker 也会将这个 alias 的 IP 地址写入到/etc/hosts 文件中。然后你就可以通过别名来访问 link 后的容器。

$ sudo docker run -d --name servicename busybox sleep 30
$ sudo docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias

如果你重启了源容器(servicename),相关联的容器也会同步更新/etc/hosts。

VOLUME (shared filesystems)

-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro].
   If "container-dir" is missing, then docker creates a new volume.
--volumes-from="": Mount all volumes from the given container(s)

关于 volume 参数,可以在 Managing data in containers 查看详细说明,需要注意的是开发人员可以在 Dockerfile 中设定多个 volume,但是只能由运维人员设置容器直接的 volume 访问。

USER

容器中默认的用户是 root,但是开发人员创建新的用户之后,这些新用户也是可以使用的。开发人员可以通过 Dockerfile 的 USER 设定默认的用户,并通过"-u "来覆盖这些参数。

WORKDIR

容器中默认的工作目录是根目录(/)。开发人员可以通过 Dockerfile 的 WORKDIR 来设定默认工作目录,操作人员可以通过"-w"来覆盖默认的工作目录。

**原文链接:Docker run reference **

admin

admin

云原生架构 · 全栈开发

感谢阅读本文!记录系统架构思考与实战复盘。专注于 Python、Django、PostgreSQL 与云原生容器化工程实践。