运行容器、容器常用操作、容器资源限制、实现容器的底层技术
第 4 章 Docker 容器
4.1 运行容器
4.1.1 如何运行容器
docker run
是启动容器的方法。例如:
> docker run ubuntu pwd
/
容器启动时执行pwd
,返回的/
是容器中的当前目录。
执行docker ps
或docker container ls
可以查看 Docker Host 中当前运行的容器,添加-a
参数会显示所有状态的容器。
若想让容器保持运行状态而不占用终端窗口,可以加上-d
参数,以后台方式启动容器:
docker run --name "my_http_server" -d httpd
4.1.2 两种进入容器的方法
我们经常需要进到容器里去做一些工作,比如查看日志、调试、启动其他进程等。有两种方法进入容器:attach 和 exec。
docker attach
通过docker attach
可以连接到容器启动命令的终端,例如:
这里通过 “长ID” attach 到了容器的启动命令终端,之后看到的是echo
每隔一秒打印的信息。
可通过
Ctrl+P
然后Ctrl+Q
组合键退出 attach 终端
docker exec
通过docker exec
进入相同的容器:
说明如下:
-it
以交互模式打开 pseudo-TTY,执行 bash,其结果就是打开了一个 bash 终端- 进入到容器中,容器的 hostname 就是其 “短ID”
- 可以像在普通 Linux 中一样执行命令。
ps -elf
显示了容器启动进程while
以及当前的bash
进程 - 执行
exit
退出容器,回到 docker host
docker exec -it <container> bash|sh
是执行 exec 最常用的方式
attach VS exec
主要区别如下:
- attach 直接进入容器启动命令的终端,不会启动新的进程
- exec 则是在容器中打开新的终端,并且可以启动新的进程
- 如果想直接在终端中查看启动命令的输出,用 attach;其他情况则使用 exec。
当然,如果只是为了查看启动命令的输出,可以使用docker logs
命令:
-f
作用与tail -f
类似,能够持续打印输出。
4.1.3 运行容器的最佳实践
容器按用途分类
按用途容器大致可分为两类:服务类容器和工具类容器。
- 服务类容器以 daemon 的形式运行,对外提供服务。比如 web server,数据库等。通过
-d
以后台方式启动这类容器是非常合适的。如果要排查问题,可以通过exec -it
进入容器。 - 工具类容器通常给能我们提供一个临时的工作环境,通常以
run -it
方式运行。执行exit
退出终端,同时容器停止。
工具类容器多使用基础镜像,例如 busybox、debian、ubuntu 等。
容器运行小结
容器运行的相关知识点:
- 当 CMD 或 Entrypoint 或
docker run
命令行指定的命令运行结束时,容器停止。 - 通过
-d
参数在后台启动容器。 - 通过
exec -it
可进入容器并执行命令。
指定容器的三种方法:
- 短ID(长ID 前 12 位)
- 长ID
- 容器名称。可通过
--name
为容器命名
容器按用途可分为两类:
- 服务类的容器
- 工具类的容器
4.2 容器常用操作
4.2.1 stop/start/restart
通过docker stop
可以停止运行的容器。
容器在 docker host 中实际是一个进程。docker stop
命令本质上是向该进程发送一个 SIGTERM 信号。如果想快速停止容器,可使用docker kill
命令,其作用是向容器进程发送 SIGKILL 信号。
对于处于停止状态的容器,可以通过docker start
重新启动。
docker start
会保留容器的第一次启动时的所有参数。
docker restart
可以重启容器,其作用就是依次执行docker stop
和docker start
。
容器可能会因某种错误而停止运行。对于服务类容器,我们通常希望在这种情况下容器能够自动重启。启动容器时设置--restart
就可以达到这个效果:
docker run -d --restart=always httpd
--restart=always
意味着无论容器因何种原因退出(包括正常退出),就立即重启。该参数的形式还可以是--restart=on-failure:3
,意思是如果启动进程退出代码非 0,则重启容器,最多重启3次。
4.2.2 pause/unpause
有时我们只是希望暂时让容器暂停工作一段时间,比如要对容器的文件系统打个快照,或者 docker host 需要使用 CPU,这时可以执行docker pause
。
处于暂停状态的容器不会占用 CPU 资源,直到通过
docker unpause
恢复运行。
4.2.3 rm/rmi
使用 docker 一段时间后,host 上可能会有大量已经退出了的容器。这些容器依然会占用 host 的文件系统资源,如果确认不会再重启此类容器,可以通过docker rm
删除。
docker rm
一次可指定多个容器。如果希望批量删除所有已经退出的容器,可以执行如下命令:
docker rm -v $(docker ps -aq -f status=exited)
docker rm
是删除容器,而docker rmi
则是删除镜像。
4.2.4 一张图搞懂容器所有操作
有两点需要补充:
1) 可以先创建容器,稍后再启动:
> docker create httpd
> docker start 989e12e4d8ea
docker create
创建的容器处于 Created 状态docker start
将以后台方式启动容器。docker run
命令实际上是docker create
和docker start
命令的组合
2) 只有当容器的启动进程退出时,--restart
才生效。
退出包括正常退出或者非正常退出。这里举了两个例子:启动进程正常退出或发生 OOM,此时 docker 会根据--restart
的策略判断是否需要重启容器。但如果容器是因为执行docker stop
或docker kill
退出,则不会自动重启。
4.3 容器资源限制
4.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 内存
注意:如果在启动容器时只指定-m
而不指定--memory-swap
,那么--memory-swap
默认为-m
的两倍。
4.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。
4.3.3 限制容器的 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
的速率为 30MB/s:
docker run -it --device-write-bps /dev/sda:30MB ubuntu
4.4 实现容器的底层技术
在容器的底层实现技术中,cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额, namespace 实现资源隔离。
4.4.1 cgroup
cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。之前提到的--cpu-shares
、-m
、--device-write-bps
实际上就是在配置 cgroup,可以在 host 的/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 配置。
4.4.2 namespace
Linux 实现容器间资源隔离的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User。
Mount
Mount namespace 让容器看上去拥有整个文件系统。
容器有自己的/
目录,可以执行 mount 和 umount 命令。当然这些操作只在当前容器中生效,不会影响到 host 和其他容器。
UTS
简单的说,UTS namespace 让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短ID,可以通过-h
或--hostname
参数设置:
IPC
IPC namespace 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。
PID
容器在 host 中以进程的形式运行。例如当前 host 中运行了两个容器:
通过ps axf
可以查看容器进程:
所有容器的进程都挂在 dockerd 进程下,同时也可以看到容器自己的子进程。 如果我们进入到某个容器,ps
就只能看到自己的进程了:
而且进程的 PID 不同于 host 中对应进程的 PID,容器中 PID=1 的进程当然也不是 host 的 init 进程。也就是说:容器拥有自己独立的一套 PID,这就是 PID namespace 提供的功能。
Network
Network namespace 让容器拥有自己独立的网卡、IP、路由等资源。
User
User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。
4.5 容器小结
下面是容器的常用操作命令:
- create:创建容器
- run:运行容器
- pause:暂停容器
- unpause:取消暂停继续运行容器
- stop:发送 SIGTERM 停止容器
- kill:发送 SIGKILL 快速停止容器
- start:启动容器
- restart:重启重启
- attach:attach 到容器启动进程的终端
- exec:在容器中启动新进程,通常使用
-it
参数 - logs:显示容器启动进程的控制台输出,使用
-f
参数持续打印 - rm:从磁盘中删除容器