Docker之容器
简介
- 容器与容器之间相互隔离、互不干扰,容器可以进行被开始、启动、停止、删除等操作。开发者可以快速地把自己的应用打包到容器中进行发布
- 容器是镜像的一个运行实例,镜像是只读的,而容器带有额外的可写文件层(Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变)
- 镜像可以同时创建多个容器
基本命令操作
-
创建容器。
docker run
等价于docker create
和docker start
,即创建并启动容器,启动后可通过docker attach e81fb104d8e7
进入容器。-t选项让Docker分配一个伪终端并绑定到容器的标准输入上 -i则让容器的标准输入保持打开。可通过ctrl+d或者exit退出容器的终端 create只是创建容器,并未启动 $ docker create -ti redis:latest 5c1c19776667eac64e29a855976eac2975ea088b4ca57d76a288fe87aefe090b CONTAINER ID IMAGE NAMES COMMAND CREATED STATUS 5c1c19776667 redis:latest goofy_lalande "docker-entrypoint.s…" 4 minutes ago Created NAMES:容器的名称,可以通过--name显示指定自定义的名称,如果不指定会生成默认名称
-
运行容器:docker run,大多数情况下,需要让Docker容器在后台以守护进程的方式运行。可以通过
-d
来实现docker在后台运行的标准操作
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个IP地址给容器
- 执行用户指定的应用成功
- 执行完毕后容器被终止
-
终止容器,
docker stop [容器ID]
来终止一个运行中的容器。当docker容器中指定的应用终结时,容器也将终止。容器在Docker Host(宿主机)中实际上是一个进程,docker stop命令本质上是向该进程发送一个SIGTERM信号。如果想快速停止容器,可使用docker kill命令,其作用是向容器进程发送SIGKILL信号$ docker stop 5c1c19776667 5c1c19776667 $ docker kill 6dbe64a1f341 6dbe64a1f341 停止后重启使用docker start,它会保留容器的第一次启动时的所有参数。 $ docker start 6dbe64a1f341 可以重启容器,其作用就是依次执行docker stop和docker start $ docker restart 自动重启使用--restart=always。无论什么原因退出总是重启 如果容器是因为执行docker stop或docker kill退出,则不会自动重启 $ docker run -itd --restart=always redis --restart=on-failure:3 如果启动进程退出代码非0,则重启容器,最多重启3次。
-
暂停容器:有时只是希望让容器暂停工作一段时间,比如要对容器的文件系统打个快照,或者dcoker host需要使用CPU,这时可以执行
docker pause
。处于暂停状态的容器不会占用CPU资源,直到通过docker unpause
恢复运行可以看到 Up 11 seconds (Paused)是Paused状态 $ docker pause 6dbe64a1f341 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6dbe64a1f341 redis "docker-entrypoint.s…" 4 minutes ago Up 11 seconds (Paused) 6379/tcp practical_moser $ docker unpause 6dbe64a1f341
-
进入容器:
docker attach 容器ID
。当多个窗口同时attach到一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法操作可通过Ctrl+p,然后Ctrl+q组合键退出attach终端。 $ docker attach <容器id> $ docker exec -it <容器id> /bin/bash -d, --detach:在容器中后台执行命令; --detach-keys="":指定将容器切回后台的按键; -e, --env=[]:指定环境变量列表; -i, --interactive=true|false:打开标准输入接受用户输入命令,默认值为false; --privileged=true|false:是否给执行命令以高权限,默认值为false; -t, --tty=true|false:分配伪终端,默认值为false; -u, --user="":执行命令的用户名或ID。 进入到容器中,容器的hostname就是其【短容器ID】。可通过ctrl+d或者exit退出容器的终端 $ docker exec -it 4d06dad3f39d /bin/bash root@4d06dad3f39d:/data# $ docker attach 4d06dad3f39d
-
attach与exec主要区别如下:
- attach直接进入容器启动命令的终端,不会启动新的进程。
- exec则是在容器中打开新的终端,并且可以启动新的进程。
- 如果想直接在终端中查看启动命令的输出,用attach;其他情况使用exec。一般不使用attach
-
删除容器
$ docker rm `docker ps -a |awk '{print $1}' | grep [0-9a-z]` 批量删除所有已经退出的容器 $ docker rm -v $(docker ps -aq -f status=exited)
-
容器日志:Docker会将日志发送到容器的标准输出设备(STDOUT)和标准错误设备(STDERR), STDOUT和STDERR实际上就是容器的控制台终端。查看启动命令的输出,可以使用
docker logs -f
。-f
的作用与tail -f
类似,能够持续打印输出$ docker logs -f 4d06dad3f39d -details:打印详细信息; -f, -follow:持续保持输出; -since string:输出从某个时间开始的日志; -tail string:输出最近的若干日志; -t, -timestamps:显示时间戳信息; -until string:输出某个时间之前的日志
-
重命名容器名称
docker rename <源> <新名称>
$ docker rename modest_boyd redis-my
-
容器变动历史:
docker diff
命令显示了自镜像被实例化成一个容器以来哪些文件受到了影响$ docker diff 4d06dad3f39d C /root # C表示被修改 A /root/.bash_history # A表示新增,D表示删除
-
导出容器:导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态。导出之后可以将容器传输到其他机器上,然后再通过导入命令导入系统中,实现容器的迁移
1. 方式一:docker [container] export [-o|--output[=""]] CONTAINER $ docker export -o httpbin-b804919a247b.tar b804919a247b 2. 方式二:直接通过重定向来实现 $ docker export b804919a247b > httpbin-b804919a247b-2.tar $ ls ttpbin-b804919a247b-2.tar httpbin-b804919a247b.tar
-
导入容器:导出的文件又可以使用
docker [container] import
命令导入变成镜像。既可以使用docker load
命令来导入镜像存储文件到本地镜像库,也可以使用docker [container] import
命令来导入一个容器快照到本地镜像库。这两者的区别在于:容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积更大。此外,从容器快照文件导入时可以重新指定标签等元数据信息1. docker import [-c|--change[=[]]] [-m|--message[=MESSAGE]] file|URL|- [REPOSITORY[:TAG]] -c, --change=[]选项在导入的同时执行对容器进行修改的Dockerfile指令 $ docker import httpbin-b804919a247b.tar - httpbin:1.0.0
-
查看容器详情:
docker container inspect [OPTIONS] CONTAINER [CONTAINER...]
$ docker container inspect b804919a247b [ { "Id": "b804919a247b0b68990c309442d791ce410a6b981aa4f346ade658d12a6a43b1", "Created": "2021-09-18T05:41:11.954029248Z", "Path": "gunicorn", "Args": [ "-b", "0.0.0.0:80", "httpbin:app", "-k", "gevent" ] ...省略... ]
-
复制文件:在容器和主机之间复制文件
1. docker [container] cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH -a, -archive:打包模式,复制文件会带有原始的uid/gid信息; -L, -follow-link:跟随软连接。当原路径为软连接时,默认只复制链接信息,使用该选项会复制链接的目标内容。 2. 将本地的ca目录复制到容器的/tmp目录 $ docker cp ca b804919a247b:/tmp
-
查看端口映射
1. docker container port CONTAINER [PRIVATE_PORT[/PROTO]] $ docker port b804919a247b 或者docker container port b804919a247b 80/tcp -> 0.0.0.0:28888
-
更新容器配置:可以更新容器的一些运行时配置,主要是一些资源限制份额。
1. 命令格式为docker [container] update [OPTIONS] CONTAINER [CONTAINER...] -blkio-weight uint16:更新块IO限制,10~1000,默认值为0,代表着无限制; -cpu-period int:限制CPU调度器CFS(Completely Fair Scheduler)使用时间,单位为微秒,最小1000; -cpu-quota int:限制CPU调度器CFS配额,单位为微秒,最小1000; -cpu-rt-period int:限制CPU调度器的实时周期,单位为微秒; -cpu-rt-runtime int:限制CPU调度器的实时运行时,单位为微秒; -c, -cpu-shares int:限制CPU使用份额; -cpus decimal:限制CPU个数; -cpuset-cpus string:允许使用的CPU核,如0-3,0,1; -cpuset-mems string:允许使用的内存块,如0-3,0,1; -kernel-memory bytes:限制使用的内核内存; -m, -memory bytes:限制使用的内存; -memory-reservation bytes:内存软限制; -memory-swap bytes:内存加上缓存区的限制,-1表示为对缓冲区无限制; -restart string:容器退出后的重启策略 $ docker update --cpu-quota 10000 b804919a247b
-
容器状态转换图
容器日志
-
将容器日志发送到STDOUT和STDERR是Docker的默认日志行为
-
Docker提供了多种日志机制帮助用户从运行的容器中提取日志信息,这些机制被称作
logging driver
。Docker的默认logging driver是json-file。 -
json-file会将容器的日志保存在json文件中,Docker负责格式化其内容并输出到STDOUT和STDERR。我们可以在Host的容器目录中找到这个文件,容器路径为
/var/lib/docker/containers/<contariner ID>/<contariner ID>-json.log
$ docker ps 4d06dad3f39d redis ...省略... $ docker inspect -f '{{.HostConfig.LogConfig.Type}}' 4d06dad3f39d json-file $ tail -f /var/lib/docker/containers/4d06dad3f39d2933a1d9fa24f1aaec8677c2fa674514bb2e671a3145bee4d319/4d06dad3f39d2933a1d9fa24f1aaec8677c2fa674514bb2e671a3145bee4d319-json.log {"log":" `-.__.-' \r\n","stream":"stdout","time":"2021-09-16T08:10:43.911418859Z"} {"log":"\r\n","stream":"stdout","time":"2021-09-16T08:10:43.911420829Z"} {"log":"1:M 16 Sep 2021 08:10:43.911 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.\r\n","stream":"stdout","time":"2021-09-16T08:10:43.911422803Z"} {"log":"1:M 16 Sep 2021 08:10:43.911 # Server initialized\r\n","stream":"stdout","time":"2021-09-16T08:10:43.911435886Z"}
-
其他的日志驱动:https://docs.docker.com/config/containers/logging/configure/
-
设置docker容器日志大小(只对新建的容器有效)
1. 容器级别设置:在启动容器的时候增加一个参数设置该容器的日志大小,及日志驱动 --log-driver json-file #日志驱动 --log-opt max-size=[0-9+][k|m|g] #文件的大小 --log-opt max-file=[0-9+] #文件数量 2. docker-compose.yaml方式设置 ...省略... redis logging: driver: “json-file” options: max-size: “5g” 如果不想生成log logging: driver: "none" 3. 全局设置: $ vim /etc/docker/daemon.json 增加配置 "log-driver":"json-file", "log-opts": {"max-size":"500m", "max-file":"3"} max-size=500m,意味着一个容器日志大小上限是500M max-file=3 意味着一个容器有三个日志,分别是id+.json、id+1.json、id+2.json $ systemctl daemon-reload && systemctl restart docker
容器监控
-
命令
-
查看运行的容器
docker ps
-
查看容器中运行了哪些进程
docker top <containerId>
或者docker top <containerId> -au
(Linux操作系统ps命令的参数显示特定的信息) -
显示每个容器各种资源的使用情况
docker stats
容器启动时如果没有特别指定内存limit, stats命令这里会显示host的内存总量, 但这并不能表示每个容器都能使用到这么多的内存 $ docker stats CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 8c7f87e79c4f busy 0.00% 60KiB / 3.842GiB 0.00% 5.7kB / 0B 0B / 0B 1 4d06dad3f39d redis-my 0.14% 6.852MiB / 3.842GiB 0.17% 2.16kB / 0B 0B / 0B 5 -a, -all:输出所有容器统计信息,默认仅在运行中; -format string:格式化输出信息; -no-stream:不持续输出,默认会自动更新持续实时结果; -no-trunc:不截断输出信息。
-
运行容器的最佳实践
-
按用途容器分为
- 服务类容器:以daemon的方式运行,并对外提供服务,通过
-d
以后台方式启动这类容器是非常合适的 - 工具类容器:通常能给我们提供一个临时的工作环境,通常以
run -it
方式运行。比如运行python容器执行一个脚本,运行一个网络工具等
- 服务类容器:以daemon的方式运行,并对外提供服务,通过
-
容器运行注意问题
- 当CMD、Entrypoint和docker run命令行指定的命令运行结束时,容器停止
- 通过
-d
参数在后台启动容器。 - 通过
exec -it
可进入容器并执行命令。
-
指定容器的方法
- 短Hash ID
- 长Hash ID
- 容器名称(
--name
)
-
docker run
运行多个命令:需要把多个命令用&&连接,然后双引号包裹sh -c ' cmd1 && cmd2 '
$ docker run -it --rm \ -v ${basepath}/:/usr/src/scripts/ \ -w /usr/src/scripts \ python:3.7.10 sh -c "chmod +x /usr/src/scripts/&& sh /usr/src/scripts/python/run.sh"
容器大小
-
可以使用
docker ps -s
指令来查看容器的大小。$ docker ps -s CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE 4d06dad3f39d redis "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 6379/tcp modest_boyd 0B (virtual 105MB)
-
size
表示每个容器的可写层使用的大小。 -
virtual size
表示容器使用的用于只读镜像的数据的数据量加上容器可写层的大小。多个容器可以共享一些或者所有的只读镜像数据。 -
每个正在运行的容器所使用磁盘总空间大小是
virtual size
和size
的某种组合。如果多个容器从相同的基础镜像开始,所以这些容器在磁盘上的总大小为所有容器的大小size
(SUM of all container size
)加上一个镜像的大小(virtual size
-size
) -
镜像如果共享了相同的层只会在
/var/lib/docker
下存储一个,通过这个可以大大减少容器编译、推送的时长。
资源限制
-
容器可使用的内存包括两部分:物理内存和swap
-
Docker通过下面两组参数来控制容器内存的使用量。
-m或 --memory
:设置内存
的使用限额,例如100MB,2GB。--memory-swap
:设置内存+swap
的使用限额- 如果在启动容器时只指定
-m
而不指定--memory-swap
,那么 --memory-swap默认为 -m的两倍
-
示例
允许该容器最多使用200MB的内存和100MB的swap。
默认情况下,两组参数为 -1,即对容器内存和swap的使用没有限制
$ docker run -m 200M --memory-swap=300M ubuntu
容器最多使用200MB物理内存和400MB swap。
$ docker run -it -m 200M ubuntu
-
对容器进行压力测试:
progrium/stress镜像
可用于对容器执行压力测试--vm 1:启动1个内存工作线程。 --vm-bytes 180M:每个线程分配180MB内存 $ docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 180M 因为180MB在可分配的范围(200MB)内,所以工作线程能够正常工作 stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd stress: dbug: [1] using backoff sleep of 3000us stress: dbug: [1] --> hogvm worker 1 [6] forked stress: dbug: [6] allocating 188743680 bytes ... stress: dbug: [6] touching bytes in strides of 4096 bytes ... stress: dbug: [6] freed 188743680 bytes stress: dbug: [6] allocating 188743680 bytes ... ...省略... $ docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 320M stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd stress: dbug: [1] using backoff sleep of 3000us stress: dbug: [1] --> hogvm worker 1 [6] forked stress: dbug: [6] allocating 335544320 bytes ... stress: dbug: [6] touching bytes in strides of 4096 bytes ... stress: FAIL: [1] (416) <-- worker 6 got signal 9 stress: WARN: [1] (418) now reaping child worker processes stress: FAIL: [1] (422) kill error: No such process stress: FAIL: [1] (452) failed run completed in 0s 分配的内存超过限额,stress线程报错,容器退出
-
CPU限制:默认设置下,所有容器可以平等地使用host CPU资源并且没有限制。Docker可以通过
-c
或--cpu-shares
设置容器使用CPU的权重。如果不指定,默认值为1024。 -
通过
-c
设置的cpu share并不是CPU资源的绝对数量,而是一个相对的权重值。某个容器最终能分配到的CPU资源取决于它的cpu share占所有容器cpu share总和的比例。即通过cpu share可以设置容器使用CPU的优先级。containerA的cpu share 1024,是containerB的两倍。 当两个容器都需要CPU资源时,containerA可以得到的CPU是containerB的两倍。 $ docker run --name "container_A" -c 1024 ubuntu $ docker run --name "container_B" -c 512 ubuntu
-
这种按权重分配CPU只会发生在CPU资源紧张的情况下。如果containerA处于空闲状态,这时,为了充分利用CPU资源,containerB也可以分配到全部可用的CPU。
-
Block IO是另一种可以限制容器使用的资源。Block IO指的是磁盘的读写,docker可通过设置权重、限制bps和iops的方式控制容器读写磁盘的带宽。目前Block IO限额只对direct IO(不使用文件缓存)有效。默认情况下,所有容器能平等地读写磁盘,可以通过设置
--blkio-weight
参数来改变容器block IO的优先级。$ docker run -it --name container_A --blkio-weight 600 ubuntu $ docker run -it --name container_B --blkio-weight 300 ubuntu 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。
容器原理
-
cgroup(全称Control Group)实现资源限额,namespace实现资源隔离。
- Linux操作系统通过cgroup可以设置进程使用CPU、内存和IO资源的限额
- namespace管理着host中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。即namespace实现了容器间资源的隔离。Linux使用了6种namespace,分别对应6种资源:Mount、UTS、IPC、PID、Network和User
-
查看cgroup
$ ls -al /sys/fs/cgroup/ total 0 drwxr-xr-x 13 root root 340 Aug 16 10:40 . drwxr-xr-x 6 root root 0 Aug 16 10:40 .. dr-xr-xr-x 6 root root 0 Aug 16 10:40 blkio lrwxrwxrwx 1 root root 11 Aug 16 10:40 cpu -> cpu,cpuacct lrwxrwxrwx 1 root root 11 Aug 16 10:40 cpuacct -> cpu,cpuacct dr-xr-xr-x 6 root root 0 Aug 16 10:40 cpu,cpuacct dr-xr-xr-x 3 root root 0 Aug 16 10:40 cpuset dr-xr-xr-x 6 root root 0 Aug 16 10:40 devices dr-xr-xr-x 3 root root 0 Aug 16 10:40 freezer dr-xr-xr-x 3 root root 0 Aug 16 10:40 hugetlb dr-xr-xr-x 6 root root 0 Aug 16 10:40 memory lrwxrwxrwx 1 root root 16 Aug 16 10:40 net_cls -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Aug 16 10:40 net_cls,net_prio lrwxrwxrwx 1 root root 16 Aug 16 10:40 net_prio -> net_cls,net_prio dr-xr-xr-x 3 root root 0 Aug 16 10:40 perf_event dr-xr-xr-x 6 root root 0 Aug 16 10:40 pids dr-xr-xr-x 6 root root 0 Aug 16 10:40 systemd
-
在
/sys/fs/cgroup/cpu/docker
目录中,Linux会为每个容器创建一个cgroup目录,以容器长ID命名。-
目录中包含所有与cpu相关的cgroup配置,文件cpu.shares保存的就是
--cpu-shares
的配置 -
/sys/fs/cgroup/memory/docker
和/sys/fs/cgroup/blkio/docker
中保存的是内存以及Block IO的cgroup配置$ docker ps 6dbe64a1f341 redis ...省略... $ ls -al /sys/fs/cgroup/cpu/docker/6dbe64a1f3414809b2f4772575e02a6190161b7ac05a0109412281efbe536d48/ -rw-r--r-- 1 root root 0 Sep 16 13:09 cgroup.clone_children -rw-r--r-- 1 root root 0 Sep 15 23:56 cgroup.procs -r--r--r-- 1 root root 0 Sep 16 13:09 cpuacct.stat -rw-r--r-- 1 root root 0 Sep 16 13:09 cpuacct.usage -r--r--r-- 1 root root 0 Sep 16 13:09 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Sep 16 13:09 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Sep 16 13:09 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Sep 16 13:09 cpu.rt_period_us -rw-r--r-- 1 root root 0 Sep 16 13:09 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Sep 16 13:09 cpu.shares -r--r--r-- 1 root root 0 Sep 16 13:09 cpu.stat -rw-r--r-- 1 root root 0 Sep 16 13:09 notify_on_release -rw-r--r-- 1 root root 0 Sep 16 13:09 tasks $ cat /sys/fs/cgroup/cpu/docker/6dbe64a1f3414809b2f4772575e02a6190161b7ac05a0109412281efbe536d48/cpu.shares 1024
-