docker run是启动容器的方法,可以用三种指定的方式指定容器启动时执行的命令。
(1)CMD指令
(2)ENTRYPOINT指令
(3)在docker run中命令行中指定
但是docker run并不能长期保持running状态,我们经常需要进入到容器中去做一些工作,比如查看日志、调试、启动其他进程等。有两种进入容器的方式:attach和exec。

docker attach

首先启动一个容器,保持后台长期运行

docker run -d ubuntu /bin/bash -c "while true; do sleep 1; echo i_am_a_container;done

首先检查容器的ID

docker ps -a

docker学习-docker容器运行

CONTAINER ID就是容器的编号,其实就是容器ID前12个字符,又叫短ID
IMAGE就是base IMAGE
NAMES是容器的名称,在启动容器的时候可以通过 --name参数显式的为容器命名。

通过docker attach可以attach到容器启动命令的终端。

docker attach 1e5cc7e3b22b 

docker学习-docker容器运行
通过短ID attach到了容器的启动命令终端,之后看到的是echo每秒输出一次打印信息
可以通过ctl+p,然后ctl+q退出attach终端

docker exec

通过docker exec进入相同的容器

docker exec -it 1e5cc7e3b22b bash

docker学习-docker容器运行
说明
1、-it 以交互的形式打开终端,执行bash
2、可以像普通linux一样执行命令,显示了容器启动的进程
3、退出容器

docker exec命令格式如下:

docker exec -it <container> bash |sh

attach vs exec

两者的主要区别如下:
1、attch直接进入容器启动命令的终端,不会启动新的进程
2、exec则是在容器中打开新的终端,而且可以启动新进程
3、如果想直接在终端中查看启动命令的输出,用attach,其他情况用exec

运行容器的最佳实践

按用途容器大致可以分为两类:服务类容器和工具的容器
前者以daemon的形式运行,对外提供服务,比如Web Server、数据库等。通过-d 以后台的方式启动这类容器是非常合适的,如果要排查问题,可以通过exec -it进入容器
后者容器通常能给我们提供一个临时的工作环境,通常以run -it方式进行

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

总结如下:

(1)当CMD,Entypoint和docker run命令行指定的命令运行结束时,容器停止
(2)通过-d参数在后台启动容器
(3)通过exec -it可以进入容器并执行命令

容器生命周期

stop/start/restart容器

docker stop:停止运行中的容器,容器在docker host中其实就是一个进程,该命令本质上是像该进程发送一个SIGTERM信号,也可通过docker kill命令快速停止容器

docker start: 对于停止的容器。可以通过该命令进行启动,会保留容器的第一次启动时的参数

docker restart :重启容器

pause/unpause容器

docker pause:让容器暂停,如需要对容器的文件系统打快照
unpause:处于pause状态的容器不会占用CPU资源,直到通过dokcer unpause恢复运行

删除容器

docker rm:使用docker一段时间后,host上可能会有大量已经退出的容器,这些容器依然会占用host的文件系统资源,可以使用docker rm进行删除
如果希望一次删除多个容器,则可以使用如下命令

docker rm -v $(docker ps -aq -f status=exited)

容器的状态机制

整个容器的生命周期状态机制,如下所示:
docker学习-docker容器运行

资源限制

一个docker host上会运行若干容器,每个容器都需要cpu,内存和IO资源。Docker提供了类似的机制避免某个容器因占用太多资源而影响到其他容器或者整个HOST的资源

内存限额

与操作系统类似,容器可使用的内存包括两个部分:物理内存和swap。Docker通过下面的两组参数来控制容器内存的使用量
(1)-m或 --memory:设置内存的使用限制
(2)--memory-swap:设置内存+swap的使用限制
例如:
docker run -m 200M --memory-swap=300M ubuntu
含义时允许该容器最多使用200M的内存和300MB的 swap。默认情况下,上面两组参数为-l,即对容器内存和swap的使用没有限制

测试使用progrium/stress镜像,该镜像可以用于对容器执行压力测试

docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M

说明:
1、--vm1:启动1个内存工作线程
2、--vm-bytes 280M,每个线程分配280MB内存
docker学习-docker容器运行
过程:
1分配280M内存
2释放280M内存
3再分配280M内存
4再释放
5 一致持续循环

cpu限制

默认设置下,所有的容器可以平等的使用hostCPU资源,而且没有限制
docker可以通过-c或者--cpu-shares设置容器使用cpu的权重,如果不指定,默认值1024
与内存限额不同,通过-c设置的cpu share并不是cpu资源的绝对数量,而是一个相对的权重值。某个容器最终能分配的到的CPU资源取决于它的cpu share总和和比例,换句话说通过cpu share可以设置容器使用CPU的优先级

docker run --name "container_A" -c 1024 ubuntu && docker run --name "container_B" -c 512 ubuntu

container_A的cpu share 1024,是containerB的两倍,当两个容器都需要CPU资源时,前者可以得到的cpu是后者的两倍

需要特别注意的是,这种按权重分配CPU的只会发生在CPU资源紧张的情况下。如果containerA处于空闲状态,这时为了充分利用CPU资源,containrB也可以分配到全部可用的cpu

Block IO带宽限制

Block IO是另一种可以限制容器资源,它是指磁盘的读写,docker可以通过设置权重,限制bsp和iops的方式控制容器读写磁盘的带宽。

IO权重
默认情况下,所有的容器能平等的读写磁盘,可以通过设置 --blkio-weight 参数来改变容器的block IO的优先级
--blkio-weight与--cpu-share类似,设置的是相对权重,默认为500

docker run -it --name container_A --blkio-weight 600 ubuntu && docker run --it --name container_B --blkio-weight 300 ubuntu

通过命令行设定,containerA的读写磁盘带宽是containerB的两倍

限制bps和iops

bsp是byte per second ,每秒读写的数据量
iops是ip per second,每秒IO的次数

可以通过以下参数控制容器的bsp和 iops
--device-read-bps,限制读某个设备的 bps。
--device-write-bps,限制写某个设备的 bps。
--device-read-iops,限制读某个设备的 iops。
--device-write-iops,限制写某个设备的 iops。

cgroup和namespace

cgropu和namespace是实现容器底层的最重要的两种技术。cgroup实现资源限制,namespace实现资源的隔离

cgroup

cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。前面我们看到的--cpu-shares、-m、--device-write-bps 实际上就是在配置 cgroup。
可以在/sys/fs/cgroup中找到

docker run -it --cpu-shares 512 progrium/stress -c 1

记录容器ID。在 /sys/fs/cgrouXXXpu/docker 目录中,Linux 会为每个容器创建一个 cgroup 目录,以容器长ID 命名:
docker学习-docker容器运行
目录中包含所有与 cpu 相关的 cgroup 配置,文件 cpu.shares 保存的就是 --cpu-shares 的配置,值为 512。
同样的,/sys/fs/cgroup/memory/docker 和 /sys/fs/cgroup/blkio/docker 中保存的是内存以及 Block IO 的 cgroup 配置

namespace

在每个容器中,我们都可以看到文件系统,网卡等资源,这些资源看上去是容器自己的。拿网卡来说,每个容器都会认为自己有一块独立的网卡,即使 host 上只有一块物理网卡。这种方式非常好,它使得容器更像一个独立的计算机。

Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、I*、PID、Network 和 User。

Mount namespace
Mount namespace 让容器看上去拥有整个文件系统。

容器有自己的 / 目录,可以执行 mount 和 umount 命令。当然我们知道这些操作只在当前容器中生效,不会影响到 host 和其他容器。
UTS namespace
简单的说,UTS namespace 让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短ID,可以通过 -h 或 --hostname 参数设置。

I P C namespace
IPC namespace 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。

PID namespace
能够使容器能够拥有自己的PID
Network namespace
Network namespace 让容器拥有自己独立的网卡、IP、路由等资源。我们会在后面网络章节详细讨论。

User namespace
User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。

常用命令

以下是容器的常用操作命令
create 创建容器
run 运行容器
pause 暂停容器
unpause 取消暂停继续运行容器
stop 发送 SIGTERM 停止容器
kill 发送 SIGKILL 快速停止容器
start 启动容器
restart 重启容器
attach attach 到容器启动进程的终端
exec 在容器中启动新进程,通常使用 "-it" 参数
logs 显示容器启动进程的控制台输出,用 "-f" 持续打印
rm 从磁盘中删除容器