运行容器、容器常用操作、容器资源限制、实现容器的底层技术

《每天5分钟玩转Docker容器技术》
《每天5分钟玩转Docker容器技术》

第 4 章 Docker 容器

4.1 运行容器

4.1.1 如何运行容器

docker run是启动容器的方法。例如:

> docker run ubuntu pwd
/

容器启动时执行pwd,返回的/是容器中的当前目录

执行docker psdocker 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进入相同的容器:

说明如下:

  1. -it以交互模式打开 pseudo-TTY,执行 bash,其结果就是打开了一个 bash 终端
  2. 进入到容器中,容器的 hostname 就是其 “短ID”
  3. 可以像在普通 Linux 中一样执行命令。ps -elf显示了容器启动进程while以及当前的bash进程
  4. 执行exit退出容器,回到 docker host

docker exec -it <container> bash|sh是执行 exec 最常用的方式

attach VS exec

主要区别如下:

  1. attach 直接进入容器启动命令的终端,不会启动新的进程
  2. exec 则是在容器中打开新的终端,并且可以启动新的进程
  3. 如果想直接在终端中查看启动命令的输出,用 attach;其他情况则使用 exec。

当然,如果只是为了查看启动命令的输出,可以使用docker logs命令:

-f作用与tail -f类似,能够持续打印输出

4.1.3 运行容器的最佳实践

容器按用途分类

按用途容器大致可分为两类:服务类容器工具类容器

  1. 服务类容器以 daemon 的形式运行,对外提供服务。比如 web server,数据库等。通过-d后台方式启动这类容器是非常合适的。如果要排查问题,可以通过exec -it进入容器。
  2. 工具类容器通常给能我们提供一个临时的工作环境,通常以run -it方式运行。执行exit退出终端,同时容器停止。

工具类容器多使用基础镜像,例如 busybox、debian、ubuntu 等。

容器运行小结

容器运行的相关知识点:

  1. CMDEntrypointdocker run命令行指定的命令运行结束时,容器停止
  2. 通过-d参数在后台启动容器
  3. 通过exec -it进入容器并执行命令

指定容器的三种方法:

  1. 短ID(长ID 前 12 位)
  2. 长ID
  3. 容器名称。可通过--name为容器命名

容器按用途可分为两类:

  1. 服务类的容器
  2. 工具类的容器

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 stopdocker 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 createdocker start命令的组合

2) 只有当容器的启动进程退出时--restart才生效。

退出包括正常退出或者非正常退出。这里举了两个例子:启动进程正常退出或发生 OOM,此时 docker 会根据--restart的策略判断是否需要重启容器。但如果容器是因为执行docker stopdocker kill退出,则不会自动重启。

4.3 容器资源限制

4.3.1 限制容器对内存的使用

与操作系统类似,容器可使用的内存包括两部分:物理内存和 swap。Docker 通过下面两组参数来控制容器内存的使用量

  1. -m--memory设置内存的使用限额,例如100M2G
  2. --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 实现容器的底层技术

容器的底层实现技术中,cgroupnamespace 是最重要的两种技术。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、NetworkUser

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:从磁盘中删除容器