第4章 容器摘要本章全面介绍了 Docker 容器的核心概念与操作实践。内容涵盖容器生命周期管理创建、启动、停止、重启、暂停、删除、资源限制内存、CPU、容器导入导出以及底层实现技术。重点讲解了docker run、docker exec、docker attach等关键命令的使用场景与区别并通过状态机图清晰展示了容器各状态间的转换关系。此外深入探讨了 cgroup 和 namespace 两种底层技术如何实现容器的资源限制与隔离。本章为容器化应用的部署与管理提供了完整的操作指南。续上篇博客 从 hello-world 到 Harbor 私有仓Docker 镜像核心原理与完整管理流程022 如何运行容器上一章我们学习了如何构建 Docker 镜像并通过镜像运行容器。本章将深入讨论容器学习容器的各种 操作容器各种状态之间如何转换以及实现容器的底层技术。运行容器docker rundocker create docker startdocker run是启动容器的方法。在讨论 Dockerfile 时我们已经学习到可用三种方式指定容器启动时 执行的命令CMD 指令。ENTRYPOINT 指令。在 docker run 命令行中指定。例如下面的例子[rootdocker ~]# docker create ubuntu #使用ubuntu镜像创建容器eb1aa0ca86b2d49250ebe64913af50e88482ad68b9c3e61ef8f8da9c24b00f7a#新创建的容器长ID[rootdocker ~]# docker ps -a #create的容器状态时CreatedCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eb1aa0ca86b2 ubuntu/bin/bash2seconds agoCreatedquizzical_goldwasser[rootdocker ~]# docker start eb1aa0ca86b2 #启动容器刚创建的容器IDeb1aa0ca86b2[rootdocker ~]# docker ps -a #查看容器状态启动了又退出了CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES eb1aa0ca86b2 ubuntu/bin/bash2seconds ago Exited(0)3seconds ago quizzical_goldwasser[rootdocker ~]# docker run ubuntu pwd #使用ubuntu镜像创建容器并执行pwd命令/容器启动时执行pwd返回的/ 是容器中的当前目录。 执行docker ps或docker container ls可以查看 Docker host 中当前运行的容器[rootdocker ~]# docker ps #查看所有正在运行中的容器CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES咦怎么没有容器用docker ps -a或docker container ls -a看看。-a会显示所有状态的容器可以看到之前的容器已经退出了状态为Exited 。这种“一闪而过”的容器通常不是我们想要的结果我们希望容器能够保持 runing 状态这样才能被我们使用。让容器长期运行如何让容器保存运行呢因为容器的生命周期依赖于启动时执行的命令只要该命令不结束容器也就不会退出。理解了这个原理我们就可以通过执行一个长期运行的命令来保持容器的运行状态。例如执行下面的命 令[rootdocker ~]# docker run ubuntu /bin/bash -c while true ; do sleep 1 ; echo hahaha; donewhile语句让 bash 不会退出。我们可以打开另一个终端查看容器的状态可见容器仍处于运行状态。不过这种方法有个缺点它占用了一个终端。我们可以加上参数-d以后台方式启动容器。[rootdocker ~]# docker run -d ubuntu /bin/bash -c while true ; do sleep 1 ; echo hahaha; done2a0bfa267fe146753b4fc8b23d55b08fbe3a5f9b5e093de6885133f6bbd20c56[rootdocker ~]#容器启动后回到了 docker host 的终端。这里看到 docker 返回了一串字符这是容器的 ID。通过docker ps查看容器现在我们有了两个正在运行的容器。这里注意一下容器的CONTAINER ID和NAMES这两个字段。CONTAINER ID是容器的 “短ID”前面启动容器时返回的是 “长ID”。短ID是长ID的前12个字符。NAMES字段显示容器的名字在启动容器时可以通过--name参数显示地为容器命名如果不指定docker 会自动为容器分配名字。对于容器的后续操作我们需要通过 “长ID”、“短ID” 或者 “名称” 来指定要操作的容器。比如下面停止一个容器[rootdocker ~]# docker stop 081be2bc2e1d081be2bc2e1d这里我们就是通过 “短ID” 指定了要停止的容器。通过while 启动的容器虽然能够保持运行但实际上没有干什么有意义的事情。容器常见的用途是运行后台服务例如前面我们已经看到的 http server这一次我们用–name 指定了容器的名字。 我们还看到容器运行的命令是httpd-foregroud,通过docker history可知这个命令是通过 CMD 指定的。我们经常需要进到容器里去做一些工作比如查看日志、调试、启动其他进程等。下一节学习如何进入容器内部。023 两种进入容器的方法我们经常需要进到容器里去做一些工作比如查看日志、调试、启动其他进程等。有两种方法进入容attach 和 exec。docker attach通过docker attach可以attach到容器启动命令的终端例如[rootdocker ~]# docker run -d ubuntu /bin/bash -c while true ; do sleep 1 ;echoI_am_in_container;done dc508b94447f83b46080267580607569a187fcc7f780433f646e9d66949731a6[rootdocker ~]#[rootdocker ~]# docker attachdc508b94447f83b46080267580607569a187fcc7f780433f646e9d66949731a6 I_am_in_container I_am_in_container I_am_in_container I_am_in_container这次我们通过 “长ID” attach 到了容器的启动命令终端之后看到的是echo每隔一秒打印的信息。注可通过 Ctrlp 然后 Ctrlq 组合键退出 attach 终端。docker exec通过docker exec进入相同的容器说明如下① -it 以交互模式打开 pseudo-TTY执行 bash其结果就是打开了一个 bash 终端。② 进入到容器中容器的 hostname 就是其 “短ID”。③ 可以像在普通 Linux 中一样执行命令。ps -elf显示了容器启动进程while以及当前的bash进程。④ 执行exit退出容器回到 docker host。docker exec -it bash|sh是执行 exec 最常用的方式。attach VS execattach 与 exec 主要区别如下:attach 直接进入容器 启动命令 的终端不会启动新的进程。exec 则是在容器中打开新的终端并且可以启动新的进程。如果想直接在终端中查看启动命令的输出用 attach其他情况使用 exec。当然如果只是为了查看启动命令的输出可以使用docker logs命令[rootdocker ~]# docker logs -f dc508b94447fI_am_in_container I_am_in_container I_am_in_container I_am_in_container I_am_in_container I_am_in_container I_am_in_container-f的作用与tail -f类似能够持续打印输出。下一节聊聊运行容器的最佳实践。024 运行容器的最佳实践按用途容器大致可分为两类服务类容器和工具类的容器。服务类容器以 daemon 的形式运行对外提供服务。比如 web server数据库等。通过-d以后台方式启动这类容器是非常合适的。如果要排查问题可以通过exec -it进入容器。工具类容器通常给能我们提供一个临时的工作环境通常以 run -it 方式运行比如[rootdocker ~]# docker run -it busybox/#/# wget www.baidu.comConnecting to www.baidu.com(223.109.82.6:80)saving toindex.htmlindex.html100%|********************|23810:00:00 ETAindex.htmlsaved /#/# exit[rootdocker ~]#运行 busyboxrun -it的作用是在容器启动后就直接进入。我们这里通过wget验证了在容器中访问internet 的能力。执行exit退出终端同时容器停止。工具类容器多使用基础镜像例如 busybox、debian、ubuntu 等。**容器运行小结 **容器运行相关的知识点当 CMD 或 Entrypoint 或 docker run 命令行指定的命令运行结束时容器停止。通过-d参数在后台启动容器。通过exec -it可进入容器并执行命令。指定容器的三种方法短ID。长ID。容器名称。 可通过--name为容器命名。重命名容器可执行docker rename容器按用途可分为两类服务类的容器。工具类的容器。下一节讨论容器的其他操作比如 stop, restart, pause, delete。025 容器常用操作前面讨论了如何运行容器本节学习容器的其他常用操作。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 httpd不加–restartalways参数dock stop不会重启attach进去ctrl_c终止进程会重启。--restartalways意味着无论容器因何种原因退出包括正常退出就立即重启。该参数的形式还 可以是--restarton-failure:3意思是如果启动进程退出代码非0则重启容器最多重启3次。pause/unpause 容器有时我们只是希望暂时让容器暂停工作一段时间比如要对容器的文件系统打个快照或者 dcoker host 需要使用 CPU这时可以执行docker pause。处于暂停状态的容器不会占用 CPU 资源直到通过docker unpause恢复运行。删除容器使用 docker 一段时间后host 上可能会有大量已经退出了的容器。这些容器依然会占用 host 的文件系统资源如果确认不会再重启此类容器可以通过docker rm删 除。[rootdocker ~]# docker rm 35af7150bd17 9769bb91580335af7150bd17 9769bb915803docker rm一次可以指定多个容器如果希望批量删除所有已经退出的容器可以执行如下命令docker rm -f $(docker ps -aq -f statusexited)# !!!慎用,删除所有状态容器[rootdocker ~]# docker rm -f $(docker ps -aq)顺便说一句docker rm是删除容器而docker rmi是删除镜像。一下学了这么多操作很有必要做个总结。下一节我们会用一张图来描述容器的状态机。026 一张图搞懂容器所有操作前面我们已经讨论了容器的各种操作对容器的生命周期有了大致的理解下面这张状态机很好地总结了容器各种状态之间是如何转换的。如果掌握了前面的知识要看懂这张图应该不难。不过有两点还是需要补充一下可以先创建容器稍后再启动。①docker create创建的容器处于 Created 状态。②docker start将以后台方式启动容器。docker run命令实际上是docker create和docker start的组合。只有当容器的启动进程 退出 时--restart才生效。退出包括正常退出或者非正常退出。这里举了两个例子启动进程正常退出或发生 OOM此时 docker 会根据--restart的策略判断是否需要重启容器。但如果容器是因为执行docker stop或docker kill退出则不会自动重启。#基于httpd镜像创建容器httpd11重启策略配置为always[rootdocker ~]# docker run -d --restartalways --name httpd11 httpd7671267fabf6aafe49fe74256af8d0af07cd65e4af4222933385f3203bc0228e#查看发现多了个httpd11容器[rootdocker ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7671267fabf6 httpdhttpd-foreground3seconds ago Up2seconds80/tcphttpd1127359e7cf87e httpdhttpd-foregroundAbout a minute ago Up About a minute80/tcp eager_northcutt#登陆到容器httpd11然后执行ctrl_c退出容器(会触发--restartalways参数)[rootdocker ~]# docker attach httpd11^C[Mon Nov1707:48:31.5827312025][mpm_event:notice][pid1:tid1]AH00491: caught SIGTERM, shutting down#发现httpd11重启了[rootdocker ~]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7671267fabf6 httpdhttpd-foreground32seconds ago Up2seconds80/tcphttpd1127359e7cf87e httpdhttpd-foreground2minute ago Up About a minute80/tcp eager_northcutt#管理员停止httpd11(不会触发重启)[rootdocker ~]# docker stop httpd11httpd11#发现httpd11没有chong[rootdocker ~]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7671267fabf6 httpdhttpd-foreground51seconds ago Exited(0)180/tcphttpd1127359e7cf87e httpdhttpd-foreground2minute ago Up About a minute80/tcp eager_northcutt好了容器操作就讨论到这里下一节我们将学习如何限制容器对资源的使用。027 限制容器对内存的使用cgroup简介docker 通过 cgroup 来控制容器使用的资源配额包括 CPU、内存、磁盘三大方面基本覆盖了常见的 资源配额和使用量控制。cgroup 是 Control Groups 的缩写是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物 理资源(如 cpu、memory、磁盘IO等等) 的机制被 LXC、docker 等很多项目用于实现进程资源控制。 cgroup 将任意进程进行分组化管理的 Linux 内核功能。cgroup 本身是提供将进程进行分组化管理的功 能和接口的基础结构I/O 或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。这些具体 的资源管理功能称为 cgroup 子系统有以下几大子系统实现blkio设置限制每个块设备的输入输出控制。例如:磁盘光盘以及 usb 等等。cpu使用调度程序为 cgroup 任务提供 cpu 的访问。cpuacct产生 cgroup 任务的 cpu 资源报告。cpuset如果是多核心的 cpu这个子系统会为 cgroup 任务分配单独的 cpu 和内存。devices允许或拒绝 cgroup 任务对设备的访问。freezer暂停和恢复 cgroup 任务。memory设置每个 cgroup 的内存限制以及产生内存资源报告。net_cls标记每个网络包以供 cgroup 方便使用。ns命名空间子系统。perf_event增加了对每 group 的监测跟踪的能力可以监测属于某个特定的 group 的所有线程 以及运行在特定CPU上的线程。stress是什么?是模拟压力测试的工具在机器上模拟cpu、内存等使用率来检测不同状态下的运行情况目前 docker 只是用了其中一部分子系统实现对资源配额和使用的控制。可以使用 stress 工具来测试 CPU 和内存。使用下面的 Dockerfile 来创建一个基于 Ubuntu 的 stress 工 具镜像。Dockerfile[rootdocker dockerfile]# vim Dockerfile# Version 1FROM ubuntu MAINTAINER gaoqd6946630qq.comRUNapt-get-yupdateapt-get-yinstallstress ENTRYPOINT[/usr/bin/stress]# 以服务或进程的形式运行使用Dockerfile构建镜像ubuntu-with-stress[rootdocker ~]# docker build -t ubuntu-with-stress .一个 docker host 上会运行若干容器每个容器都需要 CPU、内存和 IO 资源。对于 KVMVMware 等 虚拟化技术用户可以控制分配多少 CPU、内存资源给每个虚拟机。对于容器Docker 也提供了类似的 机制避免某个容器因占用太多资源而影响其他容器乃至整个 host 的性能。内存限额与操作系统类似容器可使用的内存包括两部分物理内存和 swap。 Docker 通过下面两组参数来控制容器内存的使用量。-m或--memory设置内存的使用限额例如 100M, 2G。--memory-swap设置 内存swap 的使用限额。当我们执行如下命令docker run -m 200M --memory-swap300M ubuntu其含义是允许该容器最多使用 200M 的内存和 100M 的 swap。正常情况下–memory-swap 的值包含容器可用内存和可用swap。所以 -m 300m --memory-swap1g 的含义为容器可用使用300M的物理内存并且可以使用700M1G-300的swap。如果–memory-swap 设置为0 或者不设置则容器可以使用的swap大小为-m值的两倍。如果 --memory-swap 的值和-m 值相同则容器不能使用swap如果 --memory-swap值为-1。它表示容器程序使用的内存受限而可以使用的swap空间不受限制 宿主机有多少swap空间该容器就可以使用多少下面我们将使用ubuntu-with-stress镜像来学习如何为容器分配内存。该镜像可用于对容器执行压力测 试。执行如下命令[rootdocker ~]# docker run -it -m 200M --memory-swap300M ubuntu-with-stress -- vm 1 --vm-bytes 280M -v--vm 1启动 1 个内存工作线程。--vm-bytes 280M每个线程分配 280M 内存。运行结果如下因为 280M 在可分配的范围300M内所以工作线程能够正常工作其过程是分配 280M 内存。释放 280M 内存。再分配 280M 内存。再释放 280M 内存。一直循环…如果让工作线程分配的内存超过 300M结果如下分配的内存超过限额stress 线程报错容器退出。如果在启动容器时只指定-m而不指定--memory-swap那么-emory-swap-m默认为-m的两倍比如docker run -it -m 200M ubuntu-with-stress容器最多使用 200M 物理内存和 200M swap。内存限额就讨论到这里下一节我们学习如何限制容器对 CPU 资源的使用。028 限制容器对CPU的使用上节学习了如何限制容器对内存的使用本节我们来看CPU。默认设置下所有容器可以平等地使用 host CPU 资源并且没有限制。Docker 可以通过-c或--cpu-shares设置容器使用 CPU 的权重。如果不指定默认值为 1024。–cpu-shares的值不能保证可以获得1个 vcpu 或者多少 GHz 的 CPU 资源仅仅只是一个弹性的加权 值。默认情况下每个 docker 容器的 cpu 份额都是1024。单独一个容器的份额是没有意义的只有在同时 运行多个容器时容器的 CPU 加权的效果才能体现出来。例如两个容器A、B的 CPU 份额分别为1000 和500在 CPU 进行时间片分配的时候容器 A 比容器 B 多一倍的机会获得 CPU 的时间片但分配的 结果取决于当时主机和其他容器的运行状态实际上也无法保证容器A一定能获得 CPU 时间片。比如容 器A的进程一直是空闲的那么容器B是可以获取比容器A更多的 CPU 时间片的。极端情况下比如说主 机上只运行了一个容器即使它的 CPU 份额只有 50它也可以独占整个主机的 CPU 资源。cgroups 只在容器分配的资源紧缺时也就是说在需要对容器使用的资源进行限制时才会生效。因 此无法单纯根据某个容器的 CPU 份额来确定有多少 CPU 资源分配给它资源分配结果取决于同时运 行的其他容器的 CPU 分配和容器中进程运行情况。换句话说通过 cpu share 可以设置容器使用 CPU 的优先级。比如在 host 中启动了两个容器docker run --name container_A -c 1024 ubuntudocker run --name container_B -c 512 ubuntucontainer_A 的 cpu share 1024是 container_B 的两倍。当两个容器都需要 CPU 资源时container_A 可以得到的 CPU 是 container_B 的两倍。需要特别注意的是这种按权重分配 CPU 只会发生在 CPU 资源紧张的情况下。如果 container_A 处于 空闲状态这时为了充分利用 CPU 资源container_B 也可以分配到全部·可用的 CPU。下面我们继续用 ubuntu-with-stress 做实验。启动 container_Acpu share 为 1024[rootdocker ~]# docker run --name container_A -it -c 1024 ubuntu-with stress --cpu 4 -vstress: info:[1]dispatching hogs:4cpu,0io,0vm,0hdd stress: dbug:[1]using backoffsleepof 12000us stress: dbug:[1]--hogcpu worker4[7]forked stress: dbug:[1]using backoffsleepof 9000us stress: dbug:[1]--hogcpu worker3[8]forked stress: dbug:[1]using backoffsleepof 6000us stress: dbug:[1]--hogcpu worker2[9]forked stress: dbug:[1]using backoffsleepof 3000us stress: dbug:[1]--hogcpu worker1[10]forked#再开一个窗口#使用如下命令创建容器则最终生成的 cgroup 的 CPU 份额配置可以下面的文件中找到。[rootdocker ~]# cat /sys/fs/cgroup/cpu/docker/容器长ID/cpu.shares1024--cpu用来设置工作线程的数量。因为当前 host 有 4颗 CPU所以要4个工作线程才能将 CPU 压 满。如果 host 有多颗 CPU则需要相应增加--cpu的数量。再开一个窗口启动 container_Bcpu share 为 512[rootdocker ~]# docker run --name container_B -it -c 512 ubuntu-with stress --cpu 4 -vstress: info:[1]dispatching hogs:4cpu,0io,0vm,0hdd stress: dbug:[1]using backoffsleepof 12000us stress: dbug:[1]--hogcpu worker4[7]forked stress: dbug:[1]using backoffsleepof 9000us stress: dbug:[1]--hogcpu worker3[8]forked stress: dbug:[1]using backoffsleepof 6000us stress: dbug:[1]--hogcpu worker2[9]forked stress: dbug:[1]using backoffsleepof 3000us stress: dbug:[1]--hogcpu worker1[10]forked[rootdocker ~]# cat /sys/fs/cgroup/cpu/docker/容器长ID/cpu.shares512在 host 中执行 top 查看容器对 CPU 的使用情况因为我们host是4核所以开启了4个进程为的就是充分让系统资源变得紧张只有这样竞争资源 我们设定的资源比例才可以显现出来如果只运行一个进程他们会自动分配到空闲的CPU这样 比例就无法看出来。目前可以看到总比例是2:1。container_A 消耗的 CPU 是 container_B 的两倍。再开一个窗口[rootdocker docker]# docker stats现在暂停 container_A[rootdocker ~]# docker pause container_Acontainer_Atop显示 container_B 在 container_A 空闲的情况下能够用满整颗 CPU029 export和import容器export-容器导出将容器导出为一个tar包[rootdocker ~]# docker export --helpUsage:dockerexport[OPTIONS]CONTAINER Export a containers filesystem as atararchive Aliases:dockercontainer export,dockerexportOptions: -o,--outputstring Write to a file, instead of STDOUT[rootdocker ~]#不管此时这个容器是否处于运行状态都可以导出为文件。示例# 创建容器httpd1用于测试[rootdocker ~]# docker run -d --name httpd1 httpde4f0a329c4df50ef0afb0bf21e22edc20e5a24c03ae64c6340aa2992d6e32525[rootdocker ~]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e4f0a329c4df httpdhttpd-foreground3minutes ago Up3minutes80/tcp httpd1[rootdocker ~]# docker export httpd1 -o myhttpd.tar[rootdocker ~]# lsmyhttpd.tarimport-容器tar包导入import将export导出的tar包导入成为镜像[rootdocker ~]# docker import --helpUsage:dockerimport[OPTIONS]file|URL|-[REPOSITORY[:TAG]]Import the contents from a tarball to create a filesystem image Aliases:dockerimage import,dockerimportOptions: -c,--changelist Apply Dockerfile instruction to the created image -m,--messagestring Set commit messageforimported image--platformstring Set platformifserver is multi-platform capable示例[rootdocker ~]# docker import myhttpd.tarsha256:9ae4699fa217a7240c73a50e93a8c1d359d22cf60869d350c8aab8b3c02aabcf[rootdocker ~]# docker import myhttpd.tar myweb:v1sha256:9fcb90561d2014130ef19f9dd8af5efd7a0a4a2154907bc818ac6b887f13755c[rootdocker ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE myweb v1 9fcb90561d203seconds ago 146MBnonenone9ae4699fa21732seconds ago 146MB httpd latest 90f191b9781e11days ago 148MB hello-world latest 74cc54e27dc46months ago10.1kBdocker save和docker export对比docker save将镜像保存为文件save会保存该镜像的所有元数据和历史记录。docker export将容器导出为文件文件会丢失所有元数据和历史记录仅保存容器当时的状 态再次导入会当作全新的镜像。思考docker export导出的文件是否可以使用docker load导入[rootdocker ~]# docker load -i myhttpd.taropen/var/lib/docker/tmp/docker-import-3425397234/boot/json: no suchfileor directory思考docker save导出的文件是否可以使用docker import导入提示我们可以将 docker save保存出来的文件解压后分析文件结构。[rootdocker ~]# docker save busybox -o busybox.tar[rootdocker ~]# lsbusybox.tar Dockerfile index.html PrjectJava centos.ssh.dockerfile httpd.dockerfile passwd.tar.gz tmpfile2[rootdocker ~]# docker import busybox.tar busybox:v1sha256:49492f42dd95daf41156e2a43edc14daee24b4329003397b273bec84742825f8[rootdocker ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE busybox v1 49492f42dd9515seconds ago4.68MB localhost:5000/httpd v1 00fe3afeb8c311days ago 117MB[rootdocker ~]# docker history busybox:v1IMAGE CREATED CREATED BY SIZE COMMENT 49492f42dd954minutes ago4.68MB Imported from -[rootdocker ~]# docker history busyboxIMAGE CREATED CREATED BY SIZE COMMENT c6348fa86ba07weeks ago BusyBox1.38.0(glibc), Debian134.45MB显示成功但是不建议镜像容易残缺030 实现容器的底层技术为了更好地理解容器的特性本节我们将讨论容器的底层实现技术。cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额namespace 实现资源隔离。cgroupcgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的 限额。相信你已经猜到了前面我们看到的--cpu-shares、-m、--device-write-bps实际上就是 在配置 cgroup。cgroup 到底长什么样子呢我们可以在 /sys/fs/cgroup 中找到它。还是用例子来说明启动一个容器设置--cpu-shares512[rootdocker docker]# docker run -it --cpu-shares 512 ubuntu-with-stress -c 1 -vstress: info:[1]dispatching hogs:1cpu,0io,0vm,0hdd stress: dbug:[1]using backoffsleepof 3000us stress: dbug:[1]--hogcpu worker1[7]forked查看容器的 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 配置。namespace在每个容器中我们都可以看到文件系统网卡等资源这些资源看上去是容器自己的。拿网卡来说 每个容器都会认为自己有一块独立的网卡即使 host 上只有一块物理网卡。这种方式非常好它使得容 器更像一个独立的计算机。Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源并可以让每个 容器都觉得只有自己在使用它。换句话说namespace 实现了容器间资源的隔离。Linux 使用了六种 namespace分别对应六种资源Mount、UTS、IPC、PID、Network 和 User下 面我们分别讨论。Mount namespaceMount namespace 让容器看上去拥有整个文件系统。容器有自己的 / 目录可以执行 mount 和 umount 命令。当然我们知道这些操作只在当前容器中生 效不会影响到 host 和其他容器。UTS namespace简单的说UTS namespace 让容器有自己的 hostname。 默认情况下容器的 hostname 是它的短 ID可以通过 -h 或 --hostname 参数设置。IPC namespaceIPC namespace 让容器拥有自己的共享内存和信号量semaphore来实现进程间通信而不会与host 和其他容器的 IPC 混在一起。PID namespace我们前面提到过容器在 host 中以进程的形式运行。例如当前 host 中运行了两个容器[rootdocker ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a80f1f8b692c ubuntu/bin/bash26seconds ago Up4seconds interesting_davinci 835dd4eebda2 httpdhttpd-foreground56seconds ago Up55seconds80/tcp adoring_pare通过ps axf可以查看容器进程所有容器的进程都挂在 dockerd 进程下同时也可以看到容器自己的子进程。 如果我们进入到某个容 器ps就只能看到自己的进程了[rootdocker ~]# docker exec -it a80f1f8b692c bashroota80f1f8b692c:/#roota80f1f8b692c:/# ps axfPID TTY STAT TIME COMMAND17pts/1 Ss0:00bash25pts/1 R0:00\_psaxf1pts/0 Ss0:00 /bin/bash而且进程的 PID 不同于 host 中对应进程的 PID容器中 PID1 的进程当然也不是 host 的 init 进程。也 就是说容器拥有自己独立的一套 PID这就是 PID namespace 提供的功能。Network namespaceNetwork namespace 让容器拥有自己独立的网卡、IP、路由等资源。我们会在后面网络章节详细讨论。User namespaceUser namespace 让容器能够管理自己的用户host 不能看到容器中创建的用户。[rootdocker ~]# docker exec -it a80f1f8b692c bashroota80f1f8b692c:/#roota80f1f8b692c:/# useradd lz #容器中创建用户lzroota80f1f8b692c:/#roota80f1f8b692c:/# exitexit[rootdocker ~]# su - lz#宿主机中并没有用户lzsu: user lz does not exist在容器中创建了用户 lz但 host 中并不会创建相应的用户。小结本章首先通过大量实验学习了容器的各种操作以及容器状态之间如何转换然后讨论了限制容器使用 CPU、内存和 Block IO 的方法最后学习了实现容器的底层技术cgroup 和 namespace。下面是容器的常用操作命令create 创建容器run 运行容器pause 暂停容器unpause 取消暂停继续运行容器stop 发送 SIGTERM 停止容器kill 发送 SIGKILL 快速停止容器start 启动容器restart 重启容器attach attach 到容器启动进程的终端exec 在容器中启动新进程通常使用 “-it” 参数logs 显示容器启动进程的控制台输出用 “-f” 持续打印rm 从磁盘中删除容器到这里我们已经学习完了容器章节。下一节开始讨论容器网络。