D03-Docker容器
D03-Docker容器
3.1 docker run
让容器长期运行
运行容器的最佳实践
各类有效参数及功能
name hostname link network 等
3.2 容器state machine 及常用操作命令
stop start restart
pause unpause
rm rename
cp diff等
3.3 资源限制
3.4 容器的底层技术
3.1 运行容器
docker run 是启动容器的方法。可用三种方式指定容器启动时执行的命令:
-
CMD 指令。(dockerfile)
-
ENTRYPOINT 指令。(dockerfile)
-
在
docker run命令行中指定。(覆盖CMD)
“一闪而过”的容器:docker run ubuntu pwd 或其他执行即结束的命令,容器随命令结束即退出
这种“一闪而过”的容器通常不是我们想要的结果,我们希望容器能够保持 runing 状态,这样才能被我们使用。
3.1.1 让容器长期运行
如何让容器保存运行呢?
因为容器的生命周期依赖于启动时执行的命令,只要该命令不结束,容器也就不会退出。
理解了这个原理,我们就可以通过执行一个长期运行的命令来保持容器的运行状态。
例如几个docker run命令:
docker run ubuntu /bin/bash -c "while true ; do sleep1; done" docker run ubuntu tail -f /dev/null
while 语句让 bash 不会退出;不过这种方法有个缺点:它占用了一个终端。
或加上参数 -d 以后台方式启动容器
docker run -itd --name=u1 --hostname=u1 ubuntu /bin/bash docker run -itd --name=u1 --hostname=u1 baseapp:v201803 /usr/sbin/sshd -D
docker ps 可以查看正在运行的容器;这里注意一下容器的 CONTAINER ID和 NAMES 这两个字段。
CONTAINER ID 是容器的 “短ID”,启动容器时返回的是 “长ID”。短ID是长ID的前12个字符。
NAMES 字段显示容器的名字,在启动容器时可以通过 --name 参数显示地为容器命名,如果不指定,docker 会自动为容器分配名字。
对于容器的后续操作,我们需要通过 “长ID”、“短ID” 或者 “名称” 来指定要操作的容器。
比如停止一个容器:
docker stop container_id
docker stop name
通过 while 启动的容器虽然能够保持运行,但实际上没有干什么有意义的事情。
容器常见的用途是运行后台服务,例如前面我们已经看到的 http server,可以通过CMD/ENTRYPOINT/docker run 指定启动一些服务,或调用自定义的脚本等。
3.1.2 进入容器的方法
我们经常需要进到容器里去做一些工作,比如查看日志、调试、启动其他进程等。有三种方法进入容器:attach 、 exec、安装并启动ssh服务。
docker attach
通过 docker attach 可以 attach 到容器启动命令的终端;可通过 Ctrl+p 然后 Ctrl+q 组合键退出 attach 终端。
注:需保证容器使用了-it参数运行;若未使用上述组合键退出,会导致容器停止
docker exec
通过 docker exec 进入相同的容器:
docker exec -it <container> bash|sh 是执行 exec 进入容器的最常用的方式。
说明如下:
① -it 以交互模式打开 pseudo-TTY,执行 bash,其结果就是打开了一个 bash 终端。
② 进入到容器中,容器的 hostname 就是其 “短ID”。
③ 可以像在普通 Linux 中一样执行命令。ps -elf 显示了容器启动进程while 以及当前的 bash 进程。
④ 执行 exit 退出容器,回到 docker host。
另docker exec可用于容器执行脚本,如查看容器中sshd服务状态并启动:
docker exec container service sshd status&&service sshd start
attach VS exec
attach 与 exec 主要区别如下:
-
attach 直接进入容器 启动命令 的终端,不会启动新的进程。
-
exec 则是在容器中打开新的终端,并且可以启动新的进程。
-
如果想直接在终端中查看启动命令的输出,用 attach;其他情况使用 exec。
- 推荐使用exec
当然,如果只是为了查看启动命令的输出,可以使用 docker logs 命令:
docker logs -f container
ssh:
若想通过ssh远程连接容器,需保证容器ip及ssh端口可被外部访问,且安装并启动了ssh服务。
如下:
docker run -itd --restart=always --net=macvlan_br0 --ip=*.*.*.* \ --name=NAME1 --hostname=HOSTNAME1 \ baseapp:v201803 \ /usr/sbin/sshd -D
若镜像中安装了桌面,也可通过安装配置vncserver,另外部可使用vncviewer远程连接容器桌面。(除非特殊需求,不建议安装)
3.1.3 容器分类
按用途容器大致可分为两类:服务类容器和工具类的容器。
1. 服务类容器以 daemon 的形式运行,对外提供服务。比如 web server,数据库等。
通过 -d 以后台方式启动这类容器是非常合适的。如果要排查问题,可以通过 exec -it 进入容器。
测试环境中各系统应用多为服务类容器,需长期运行,对外提供服务或接口。
2. 工具类容器通常给能我们提供一个临时的工作环境,通常以 run -it 方式运行,比如
3.1.4 容器运行小结
更多docker run参数在后面讲解,此部分内容小结如下。
容器运行相关的知识点:
-
当 CMD 或 Entrypoint 或 docker run 命令行指定的命令运行结束时,容器停止。
-
通过
-d参数在后台启动容器。 -
通过
exec -it可进入容器并执行命令。
指定容器的三种方法:
-
短ID。
-
长ID。
-
容器名称。 可通过
--name为容器命名。重命名容器可执行docker rename。
容器按用途可分为两类:
-
服务类的容器。
-
工具类的容器。
3.1.5 docker run常用参数
待整理:
各类有效参数及功能 name hostname link network
-v 挂载文件,重启容器就可以更新
--link 父容器:别名,容器可以通过别名访问父容器
-p:端口映射
-name:名字
-v:文件挂载,配置文件、日志文件、数据文件。
-e:环境变量
3.2 状态机和容器常用操作命令
3.2.1 容器生命周期
下面这张状态机很好地总结了容器各种状态之间是如何转换的。

docker create 创建的容器处于 Created 状态。docker start 将以后台方式启动容器。 docker run 命令实际上是 docker create 和 docker start 的组合。
只有当容器的启动进程 退出 时,--restart 才生效。
退出包括正常退出或者非正常退出。
这里举了两个例子:启动进程正常退出或发生 OOM,此时 docker 会根据 --restart 的策略判断是否需要重启容器。
但如果容器是因为执行 docker stop 或docker kill 退出,则不会自动重启。
3.2.2 常用操作命令汇总:
创建/启动一个容器:
docker create <image-id> docker create 命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器。注意,这个容器并没有运行。 docker start <container-id> Docker start命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。 docker run <image-id> docker run 命令先是利用镜像创建了一个容器,然后运行这个容器。 docker run就是docker create和docker start两个命令的组合。 --link:一个容器连接到另一个容器 docker run -it --name u01-ctp -d –link u01-sie:sie ctp:201803 u01-ctp容器连接到u01-sie容器,并将u01-sie容器重命名为sie。这样,u01-ctp容器就可以使用sie的相关的环境变量了。容器u01-ctp内/etc/hosts文件增加一条记录,映射主机名sie到u01-sie的IP地址。在容器u01-ctp内,就可以直接解析sie了。
容器的基本操作
docker ps docker ps 命令会列出所有运行中的容器。 docker ps –a docker ps –a命令会列出所有的容器,不管是运行的,还是停止的。 docker stop <container-id> docker stop命令会向运行中的容器发送一个SIGTERM的信号,然后停止所有的进程。 docker kill <container-id> docker kill 命令向所有运行在容器中的进程发送了一个不友好的SIGKILL信号。 docker pause <container-id> docker stop和docker kill命令会发送UNIX的信号给运行中的进程,docker pause命令则不一样,它利用了cgroups的特性将运行中的进程空间暂停。 docker rm <container-id> docker rm命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。 删除运行中的容器docker rm –f <container-id> docker commit <container-id> docker commit命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。 docker exec <running-container-id> docker exec 命令会在运行中的容器执行一个新进程/命令。 docker inspect <container-id> or <image-id> docker inspect命令会提取出容器或者镜像最顶层的元数据。 docker cp /dest1/filename container:/dest2/ 拷贝文件至容器 docker cp container:/dest1/filename /dest2/ 拷贝容器文件至主机 docker diff <容器名or ID>查看容器内文件的改变 docker logs –f 查看容器输出/日志 docker top <容器名or ID>显示容器内进程信息 docker stats <容器名or ID>显示容器内统计信息 docker port <容器名or ID> 显示容器与主机端口映射信息 docker rename<容器名> <新名字>改名
镜像的基本操作
docker images docker images命令会列出了所有顶层(top-level)镜像。只有创建容器时使用的镜像或者是直接pull下来的镜像能被称为顶层(top-level)镜像,并且每一个顶层镜像下面都隐藏了多个镜像层。 docker images –a docker images –a命令列出了所有的镜像,也可以说是列出了所有的可读层。如果你想要查看某一个image-id下的所有层,可以使用docker history来查看。 docker rmi <image-id> docker rmi 命令会移除构成镜像的一个只读层。你只能够使用docker rmi来移除最顶层(top level layer)(也可以说是镜像),你也可以使用-f参数来强制删除中间的只读层。 docker commit <container-id> docker commit命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。 docker build docker build命令非常有趣,它会反复的执行多个命令。使用dockerfile build一个镜像 docker inspect <container-id> or <image-id> docker inspect命令会提取出容器或者镜像最顶层的元数据。 docker save <image-id> docker save命令会创建一个镜像的压缩文件,这个文件能够在另外一个主机的Docker上使用。和export命令不同,这个命令为每一个层都保存了它们的元数据。这个命令只能对镜像生效。 docker export <container-id> docker export命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容(expoxt后 的容器再import到Docker中,通过docker images –tree命令只能看到一个镜像;而save后的镜像则不同,它能够看到这个镜像的历史镜像)。 docker history <image-id> docker history命令递归地输出指定镜像的历史镜像。 查看历史镜像详细信息:docker history <image-id> --no-trunc
其他命令
docker rm$(docker ps -a -q) 删除所有容器
3.3 资源限制
一个 docker host 上会运行若干容器,每个容器都需要 CPU、内存和 IO 资源。
对于 KVM,VMware 等虚拟化技术,用户可以控制分配多少 CPU、内存资源给每个虚拟机。
对于容器,Docker 也提供了类似的机制避免某个容器因占用太多资源而影响其他容器乃至整个 host 的性能。
3.3.1 内存限额
与操作系统类似,容器可使用的内存包括两部分:物理内存和 swap。
Docker 通过下面两组参数来控制容器内存的使用量。
-
-m或--memory:设置内存的使用限额,例如 100M, 2G。 -
--memory-swap:设置 内存+swap 的使用限额。
当我们执行如下命令:
docker run -m 200M --memory-swap=300M ubuntu
其含义是允许该容器最多使用 200M 的内存和 100M 的 swap。
默认情况下,上面两组参数为 -1,即对容器内存和 swap 的使用没有限制。
下面我们将使用 progrium/stress 镜像来学习如何为容器分配内存。
该镜像可用于对容器执行压力测试。执行如下命令:
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
--vm 1:启动 1 个内存工作线程。
--vm-bytes 280M:每个线程分配 280M 内存。
运行结果如下:

因为 280M 在可分配的范围(300M)内,所以工作线程能够正常工作,其过程是:
-
分配 280M 内存。
-
释放 280M 内存。
-
再分配 280M 内存。
-
再释放 280M 内存。
-
一直循环......
如果让工作线程分配的内存超过 300M,结果如下:

分配的内存超过限额,stress 线程报错,容器退出。
如果在启动容器时只指定 -m 而不指定 --memory-swap,那么 --memory-swap 默认为 -m 的两倍,比如:
docker run -it -m 200M ubuntu
容器最多使用 200M 物理内存和 200M swap。
上节学习了如何限制容器对内存的使用,本节我们来看CPU。
3.3.2 CPU资源限制
默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制。
Docker 可以通过 -c 或 --cpu-shares 设置容器使用 CPU 的权重。如果不指定,默认值为 1024。
与内存限额不同,通过 -c 设置的 cpu share 并不是 CPU 资源的绝对数量,而是一个相对的权重值。
某个容器最终能分配到的 CPU 资源取决于它的 cpu share 占所有容器 cpu share 总和的比例。
换句话说:通过 cpu share 可以设置容器使用 CPU 的优先级。
比如在 host 中启动了两个容器:
docker run --name "container_A" -c 1024 ubuntu
docker run --name "container_B" -c 512 ubuntu
container_A 的 cpu share 1024,是 container_B 的两倍。
当两个容器都需要 CPU 资源时,container_A 可以得到的 CPU 是 container_B 的两倍。
需要特别注意的是,这种按权重分配 CPU 只会发生在 CPU 资源紧张的情况下。如果 container_A 处于空闲状态,这时,为了充分利用 CPU 资源,container_B 也可以分配到全部可用的 CPU。
下面我们继续用 progrium/stress 做实验。
-
启动 container_A,cpu share 为 1024:
![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170613-1497354466520000561.png]()
--cpu用来设置工作线程的数量。因为当前 host 只有 1 颗 CPU,所以一个工作线程就能将 CPU 压满。如果 host 有多颗 CPU,则需要相应增加--cpu的数量。 -
启动 container_B,cpu share 为 512:
![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170613-1497354466533020022.png]()
-
在 host 中执行
top,查看容器对 CPU 的使用情况:![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170613-1497354466972043225.png]()
container_A 消耗的 CPU 是 container_B 的两倍。 -
现在暂停 container_A:
![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170613-1497354466692032953.png]()
-
top显示 container_B 在 container_A 空闲的情况下能够用满整颗 CPU:![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170613-1497354466872040429.png]()
3.3.2 Block IO资源限制
Block IO 是另一种可以限制容器使用的资源。
Block IO 指的是磁盘的读写,docker 可通过设置权重、限制 bps 和 iops 的方式控制容器读写磁盘的带宽,下面分别讨论。
注:目前 Block IO 限额只对 direct IO(不使用文件缓存)有效。
block IO 权重
默认情况下,所有容器能平等地读写磁盘,可以通过设置 --blkio-weight 参数来改变容器 block IO 的优先级。
--blkio-weight 与 --cpu-shares 类似,设置的是相对权重值,默认为 500。在下面的例子中,container_A 读写磁盘的带宽是 container_B 的两倍。
docker run -it --name container_A --blkio-weight 600 ubuntu
docker run -it --name container_B --blkio-weight 300 ubuntu
限制 bps 和 iops
bps 是 byte per second,每秒读写的数据量。
iops 是 io per second,每秒 IO 的次数。
可通过以下参数控制容器的 bps 和 iops:--device-read-bps,限制读某个设备的 bps。--device-write-bps,限制写某个设备的 bps。--device-read-iops,限制读某个设备的 iops。--device-write-iops,限制写某个设备的 iops。
下面这个例子限制容器写 /dev/sda 的速率为 30 MB/s
docker run -it --device-write-bps /dev/sda:30MB ubuntu
我们来看看实验结果:

通过 dd 测试在容器中写磁盘的速度。因为容器的文件系统是在 host /dev/sda 上的,在容器中写文件相当于对 host /dev/sda 进行写操作。另外,oflag=direct 指定用 direct IO 方式写文件,这样 --device-write-bps 才能生效。
结果表明,bps 25.6 MB/s 没有超过 30 MB/s 的限速。
作为对比测试,如果不限速,结果如下:

其他参数的使用方法类似。
3.4 容器的底层实现技术
cgroup 和 namespace 是最重要的两种技术。
cgroup 实现资源限额, namespace 实现资源隔离。
3.4.1 cgroup
cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。相信你已经猜到了:前面我们看到的--cpu-shares、-m、--device-write-bps 实际上就是在配置 cgroup。
cgroup 到底长什么样子呢?我们可以在 /sys/fs/cgroup 中找到它。还是用例子来说明,启动一个容器,设置 --cpu-shares=512:

查看容器的 ID:

在 /sys/fs/cgroup/cpu/docker 目录中,Linux 会为每个容器创建一个 cgroup 目录,以容器长ID 命名:

目录中包含所有与 cpu 相关的 cgroup 配置,文件 cpu.shares 保存的就是 --cpu-shares 的配置,值为 512。
同样的,/sys/fs/cgroup/memory/docker 和 /sys/fs/cgroup/blkio/docker 中保存的是内存以及 Block IO 的 cgroup 配置。
3.4.2 namespace
在每个容器中,我们都可以看到文件系统,网卡等资源,这些资源看上去是容器自己的。拿网卡来说,每个容器都会认为自己有一块独立的网卡,即使 host 上只有一块物理网卡。这种方式非常好,它使得容器更像一个独立的计算机。
Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User,下面我们分别讨论。
Mount namespace
Mount namespace 让容器看上去拥有整个文件系统。
容器有自己的 / 目录,可以执行 mount 和 umount 命令。当然我们知道这些操作只在当前容器中生效,不会影响到 host 和其他容器。
UTS namespace
简单的说,UTS namespace 让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短ID,可以通过 -h 或 --hostname 参数设置。

IPC namespace
IPC namespace 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。
PID namespace
我们前面提到过,容器在 host 中以进程的形式运行。例如当前 host 中运行了两个容器:

通过 ps axf 可以查看容器进程:

所有容器的进程都挂在 dockerd 进程下,同时也可以看到容器自己的子进程。 如果我们进入到某个容器,ps 就只能看到自己的进程了:

而且进程的 PID 不同于 host 中对应进程的 PID,容器中 PID=1 的进程当然也不是 host 的 init 进程。也就是说:容器拥有自己独立的一套 PID,这就是 PID namespace 提供的功能。
Network namespace
Network namespace 让容器拥有自己独立的网卡、IP、路由等资源。我们会在后面网络章节详细讨论。
User namespace
User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。

在容器中创建了用户 cloudman,但 host 中并不会创建相应的用户。
3.5 小结
本章首先学习了容器的各种操作以及容器状态之间如何转换,然后讨论了限制容器使用 CPU、内存和 Block IO 的方法,最后学习了实现容器的底层技术:cgroup 和 namespace。
下面是容器的常用操作命令:
create 创建容器
run 运行容器
pause 暂停容器
unpause 取消暂停继续运行容器
stop 发送 SIGTERM 停止容器
kill 发送 SIGKILL 快速停止容器
start 启动容器
restart 重启容器
attach attach 到容器启动进程的终端
exec 在容器中启动新进程,通常使用 "-it" 参数
logs 显示容器启动进程的控制台输出,用 "-f" 持续打印
rm 从磁盘中删除容器






浙公网安备 33010602011771号