第4章 容器

摘要: 本章全面介绍了 Docker 容器的核心概念与操作实践。内容涵盖容器生命周期管理(创建、启动、停止、重启、暂停、删除)、资源限制(内存、CPU)、容器导入导出以及底层实现技术。重点讲解了 docker rundocker execdocker attach 等关键命令的使用场景与区别,并通过状态机图清晰展示了容器各状态间的转换关系。此外,深入探讨了 cgroup 和 namespace 两种底层技术如何实现容器的资源限制与隔离。本章为容器化应用的部署与管理提供了完整的操作指南。

续上篇博客 从 hello-world 到 Harbor 私有仓:Docker 镜像核心原理与完整管理流程

022 如何运行容器?

上一章我们学习了如何构建 Docker 镜像,并通过镜像运行容器。本章将深入讨论容器:学习容器的各种 操作,容器各种状态之间如何转换,以及实现容器的底层技术。

运行容器

docker run=docker create + docker start

在这里插入图片描述

docker run 是启动容器的方法。在讨论 Dockerfile 时我们已经学习到,可用三种方式指定容器启动时 执行的命令:

  1. CMD 指令。
  2. ENTRYPOINT 指令。
  3. 在 docker run 命令行中指定。

例如下面的例子:

[root@docker ~]# docker create ubuntu     #使用ubuntu镜像创建容器
eb1aa0ca86b2d49250ebe64913af50e88482ad68b9c3e61ef8f8da9c24b00f7a  #新创建的容器长ID

[root@docker ~]# docker ps -a    #create的容器状态时Created
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS    PORTS     NAMES  
eb1aa0ca86b2   ubuntu    "/bin/bash"   2 seconds ago   `Created`           quizzical_goldwasser

[root@docker ~]# docker start eb1aa0ca86b2   #启动容器,刚创建的容器ID
eb1aa0ca86b2 
[root@docker ~]# docker ps -a     #查看容器状态,启动了又退出了
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS                    PORTS     NAMES  
eb1aa0ca86b2   ubuntu    "/bin/bash"   2 seconds ago   Exited (0) 3 seconds ago      quizzical_goldwasser


[root@docker ~]# docker run ubuntu pwd   #使用ubuntu镜像创建容器并执行pwd命令
/ 

容器启动时执行pwd ,返回的/ 是容器中的当前目录。 执行docker psdocker container ls可以查看 Docker host 中当前运行的容器:

[root@docker ~]# docker ps    #查看所有正在运行中的容器
CONTAINER ID   IMAGE     COMMAND    CREATED      STATUS    PORTS    NAMES 

咦,怎么没有容器?用 docker ps -adocker container ls -a 看看。

在这里插入图片描述

-a 会显示所有状态的容器,可以看到,之前的容器已经退出了,状态为Exited 。

这种“一闪而过”的容器通常不是我们想要的结果,我们希望容器能够保持 runing 状态,这样才能被我们使用。

让容器长期运行
如何让容器保存运行呢?
因为容器的生命周期依赖于启动时执行的命令,只要该命令不结束,容器也就不会退出。

理解了这个原理,我们就可以通过执行一个长期运行的命令来保持容器的运行状态。例如执行下面的命 令:

[root@docker ~]# docker run ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"

while 语句让 bash 不会退出。我们可以打开另一个终端查看容器的状态:

在这里插入图片描述

可见容器仍处于运行状态。不过这种方法有个缺点:它占用了一个终端。

我们可以加上参数 -d 以后台方式启动容器。

[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"
2a0bfa267fe146753b4fc8b23d55b08fbe3a5f9b5e093de6885133f6bbd20c56
[root@docker ~]#

容器启动后回到了 docker host 的终端。这里看到 docker 返回了一串字符,这是容器的 ID。通过 docker ps 查看容器:

在这里插入图片描述

现在我们有了两个正在运行的容器。这里注意一下容器的CONTAINER IDNAMES 这两个字段。
CONTAINER ID 是容器的 “短ID”,前面启动容器时返回的是 “长ID”。短ID是长ID的前12个字符。
NAMES 字段显示容器的名字,在启动容器时可以通过--name 参数显示地为容器命名,如果不指定,docker 会自动为容器分配名字。

对于容器的后续操作,我们需要通过 “长ID”、“短ID” 或者 “名称” 来指定要操作的容器。比如下面停止一个容器:

[root@docker ~]# docker stop 081be2bc2e1d
081be2bc2e1d

这里我们就是通过 “短ID” 指定了要停止的容器。

通过while 启动的容器虽然能够保持运行,但实际上没有干什么有意义的事情。容器常见的用途是运行后台服务,例如前面我们已经看到的 http server:

在这里插入图片描述

这一次我们用–name 指定了容器的名字。 我们还看到容器运行的命令是httpd-foregroud,通过docker history 可知这个命令是通过 CMD 指定的。

在这里插入图片描述

我们经常需要进到容器里去做一些工作,比如查看日志、调试、启动其他进程等。下一节学习如何进入容器内部。

023 两种进入容器的方法

我们经常需要进到容器里去做一些工作,比如查看日志、调试、启动其他进程等。有两种方法进入容:attach 和 exec。

docker attach

通过 docker attach 可以 attach 到容器启动命令的终端,例如:

[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; 
echo I_am_in_container ; done"
dc508b94447f83b46080267580607569a187fcc7f780433f646e9d66949731a6
[root@docker ~]#
[root@docker ~]# docker attach 
dc508b94447f83b46080267580607569a187fcc7f780433f646e9d66949731a6
I_am_in_container
I_am_in_container
I_am_in_container
I_am_in_container 

这次我们通过 “长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 bash|sh 是执行 exec 最常用的方式。

attach VS exec

attach 与 exec 主要区别如下:

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

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

[root@docker ~]# docker logs -f dc508b94447f
I_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 运行容器的最佳实践

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

  1. 服务类容器以 daemon 的形式运行,对外提供服务。比如 web server,数据库等。通过-d 以后台方式启动这类容器是非常合适的。如果要排查问题,可以通过 exec -it 进入容器。
  2. 工具类容器通常给能我们提供一个临时的工作环境,通常以 run -it` 方式运行,比如:
[root@docker ~]# docker run -it busybox
/ #
/ # wget www.baidu.com
Connecting to www.baidu.com (223.109.82.6:80)
saving to 'index.html'
index.html           100% |********************|  2381  0:00:00 ETA
'index.html' saved
/ #
/ # exit
[root@docker ~]#

运行 busybox, run -it 的作用是在容器启动后就直接进入。我们这里通过wget 验证了在容器中访问internet 的能力。执行 exit 退出终端,同时容器停止。

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

**容器运行小结 **

容器运行相关的知识点:

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

指定容器的三种方法:

  1. 短ID。
  2. 长ID。
  3. 容器名称。 可通过--name 为容器命名。重命名容器可执行docker rename

容器按用途可分为两类:

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

下一节讨论容器的其他操作,比如 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 stopdocker start

容器可能会因某种错误而停止运行。对于服务类容器,我们通常希望在这种情况下容器能够自动重启。 启动容器时设置 --restart 就可以达到这个效果。

如果docker run -d httpd不加–restart=always参数,dock stop不会重启,attach进去ctrl_c(终止进程)会重启。

在这里插入图片描述

在这里插入图片描述

--restart=always 意味着无论容器因何种原因退出(包括正常退出),就立即重启。该参数的形式还 可以是 --restart=on-failure:3 ,意思是如果启动进程退出代码非0,则重启容器,最多重启3次。

pause/unpause 容器

有时我们只是希望暂时让容器暂停工作一段时间,比如要对容器的文件系统打个快照,或者 dcoker host 需要使用 CPU,这时可以执行 docker pause

在这里插入图片描述

处于暂停状态的容器不会占用 CPU 资源,直到通过 docker unpause 恢复运行。

在这里插入图片描述

删除容器

使用 docker 一段时间后,host 上可能会有大量已经退出了的容器。

在这里插入图片描述

这些容器依然会占用 host 的文件系统资源,如果确认不会再重启此类容器,可以通过 docker rm 删 除。

[root@docker ~]# docker rm 35af7150bd17 9769bb915803
35af7150bd17
9769bb915803

docker rm 一次可以指定多个容器,如果希望批量删除所有已经退出的容器,可以执行如下命令: docker rm -f $(docker ps -aq -f status=exited)

在这里插入图片描述

# !!!慎用,删除所有状态容器
[root@docker ~]# docker rm -f $(docker ps -aq)

顺便说一句: docker rm 是删除容器,而 docker rmi是删除镜像。

一下学了这么多操作,很有必要做个总结。下一节我们会用一张图来描述容器的状态机。

026 一张图搞懂容器所有操作

前面我们已经讨论了容器的各种操作,对容器的生命周期有了大致的理解,下面这张状态机很好地总结了容器各种状态之间是如何转换的。

在这里插入图片描述

在这里插入图片描述

如果掌握了前面的知识,要看懂这张图应该不难。不过有两点还是需要补充一下:

  1. 可以先创建容器,稍后再启动。

在这里插入图片描述

docker create 创建的容器处于 Created 状态。

docker start 将以后台方式启动容器。 docker run 命令实际上是 docker createdocker start 的组合。

  1. 只有当容器的启动进程 退出 时,--restart 才生效。

在这里插入图片描述

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

#基于httpd镜像创建容器httpd11,重启策略配置为always
[root@docker ~]# docker run -d --restart=always --name httpd11 httpd   
7671267fabf6aafe49fe74256af8d0af07cd65e4af4222933385f3203bc0228e

#查看发现多了个httpd11容器
[root@docker ~]# docker ps      
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS         
      PORTS    NAMES
7671267fabf6   httpd     "httpd-foreground"   3 seconds ago       Up 2 seconds
      80/tcp   `httpd11`
27359e7cf87e   httpd     "httpd-foreground"   About a minute ago  Up About a minute
      80/tcp   eager_northcutt

#登陆到容器httpd11,然后执行ctrl_c退出容器(会触发--restart=always参数)
[root@docker ~]# docker attach httpd11
^C[Mon Nov 17 07:48:31.582731 2025] [mpm_event:notice] [pid 1:tid 1] AH00491: 
caught SIGTERM, shutting down

#发现httpd11重启了
[root@docker ~]# docker ps -a    
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS         
      PORTS    NAMES
7671267fabf6   httpd     "httpd-foreground"   32 seconds ago      Up 2 seconds
      80/tcp   `httpd11`
27359e7cf87e   httpd     "httpd-foreground"   2 minute ago        Up About a minute
      80/tcp   eager_northcutt
      
#管理员停止httpd11(不会触发重启)
[root@docker ~]# docker stop httpd11
httpd11

#发现httpd11没有chong
[root@docker ~]# docker ps -a    
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS         
      PORTS    NAMES
7671267fabf6   httpd     "httpd-foreground"   51 seconds ago      Exited (0) 1 
      80/tcp   `httpd11`
27359e7cf87e   httpd     "httpd-foreground"   2 minute ago        Up About a minute
      80/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

[root@docker dockerfile]# vim Dockerfile
# Version 1
FROM ubuntu
MAINTAINER gaoqd "6946630@qq.com"
RUN apt-get -y update && apt-get -y install stress
ENTRYPOINT ["/usr/bin/stress"]      # 以服务或进程的形式运行

使用Dockerfile构建镜像ubuntu-with-stress

[root@docker ~]# docker build -t ubuntu-with-stress .

一个 docker host 上会运行若干容器,每个容器都需要 CPU、内存和 IO 资源。对于 KVM,VMware 等 虚拟化技术,用户可以控制分配多少 CPU、内存资源给每个虚拟机。对于容器,Docker 也提供了类似的 机制避免某个容器因占用太多资源而影响其他容器乃至整个 host 的性能。

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

  1. -m --memory :设置内存的使用限额,例如 100M, 2G。
  2. --memory-swap :设置 内存+swap 的使用限额。

当我们执行如下命令:

docker run -m 200M --memory-swap=300M ubuntu

其含义是允许该容器最多使用 200M 的内存和 100M 的 swap。

正常情况下,–memory-swap 的值包含容器可用内存和可用swap。所以 -m 300m --memory-swap=1g 的含义为:容器可用使用300M的物理内存,并且可以使用700M(1G-300)的swap。

  • 如果–memory-swap 设置为0 或者不设置,则容器可以使用的swap大小为-m值的两倍。
  • 如果 --memory-swap 的值和-m 值相同,则容器不能使用swap
  • 如果 --memory-swap值为-1。它表示容器程序使用的内存受限,而可以使用的swap空间不受限制 (宿主机有多少swap空间该容器就可以使用多少)

下面我们将使用ubuntu-with-stress镜像来学习如何为容器分配内存。该镜像可用于对容器执行压力测 试。执行如下命令:

[root@docker ~]# docker run -it -m 200M --memory-swap=300M ubuntu-with-stress -- vm 1 --vm-bytes 280M -v

--vm 1:启动 1 个内存工作线程。

--vm-bytes 280M:每个线程分配 280M 内存。

运行结果如下:

在这里插入图片描述

因为 280M 在可分配的范围(300M)内,所以工作线程能够正常工作,其过程是:

  1. 分配 280M 内存。
  2. 释放 280M 内存。
  3. 再分配 280M 内存。
  4. 再释放 280M 内存。
  5. 一直循环…

如果让工作线程分配的内存超过 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 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。

下面我们继续用 ubuntu-with-stress 做实验。

  1. 启动 container_A,cpu share 为 1024:

    [root@docker ~]# docker run --name "container_A" -it -c 1024 ubuntu-with stress --cpu 4 -v
    stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
    stress: dbug: [1] using backoff sleep of 12000us
    stress: dbug: [1] --> hogcpu worker 4 [7] forked
    stress: dbug: [1] using backoff sleep of 9000us
    stress: dbug: [1] --> hogcpu worker 3 [8] forked
    stress: dbug: [1] using backoff sleep of 6000us
    stress: dbug: [1] --> hogcpu worker 2 [9] forked
    stress: dbug: [1] using backoff sleep of 3000us
    stress: dbug: [1] --> hogcpu worker 1 [10] forked
    
    #再开一个窗口
    #使用如下命令,创建容器,则最终生成的 cgroup 的 CPU 份额配置可以下面的文件中找到。
    [root@docker ~]# cat /sys/fs/cgroup/cpu/docker/<容器长ID>/cpu.shares
    1024
    

    --cpu 用来设置工作线程的数量。因为当前 host 有 4颗 CPU,所以要4个工作线程才能将 CPU 压 满。如果 host 有多颗 CPU,则需要相应增加--cpu的数量。

  2. 再开一个窗口,启动 container_B,cpu share 为 512:

    [root@docker ~]# docker run --name "container_B" -it -c 512 ubuntu-with stress --cpu 4 -v
    stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
    stress: dbug: [1] using backoff sleep of 12000us
    stress: dbug: [1] --> hogcpu worker 4 [7] forked
    stress: dbug: [1] using backoff sleep of 9000us
    stress: dbug: [1] --> hogcpu worker 3 [8] forked
    stress: dbug: [1] using backoff sleep of 6000us
    stress: dbug: [1] --> hogcpu worker 2 [9] forked
    stress: dbug: [1] using backoff sleep of 3000us
    stress: dbug: [1] --> hogcpu worker 1 [10] forked
    
    [root@docker ~]# cat /sys/fs/cgroup/cpu/docker/<容器长ID>/cpu.shares
    512
    
  3. 在 host 中执行 top ,查看容器对 CPU 的使用情况:

    在这里插入图片描述

    因为我们host是4核所以开启了4个进程,为的就是充分让系统资源变得紧张,只有这样竞争资源, 我们设定的资源比例才可以显现出来,如果只运行一个进程,他们会自动分配到空闲的CPU,这样 比例就无法看出来。目前可以看到总比例是2:1。

    container_A 消耗的 CPU 是 container_B 的两倍。

    再开一个窗口

    [root@docker docker]# docker stats
    

    在这里插入图片描述

  4. 现在暂停 container_A:

    [root@docker ~]# docker pause container_A
    container_A
    

    在这里插入图片描述

  5. top 显示 container_B 在 container_A 空闲的情况下能够用满整颗 CPU:

    在这里插入图片描述

029 export和import容器

export-容器导出

将容器导出为一个tar包

[root@docker ~]# docker export --help

Usage:  docker export [OPTIONS] CONTAINER

Export a container's filesystem as a tar archive

Aliases:
  docker container export, docker export

Options:
  -o, --output string   Write to a file, instead of STDOUT
[root@docker ~]#

不管此时这个容器是否处于运行状态,都可以导出为文件。

示例:

# 创建容器httpd1用于测试
[root@docker ~]# docker run -d --name httpd1 httpd
e4f0a329c4df50ef0afb0bf21e22edc20e5a24c03ae64c6340aa2992d6e32525

[root@docker ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED            STATUS        PORTS         NAMES         
e4f0a329c4df   httpd     "httpd-foreground"   3 minutes ago      Up 3 minutes
80/tcp        httpd1

[root@docker ~]# docker export httpd1 -o myhttpd.tar
[root@docker ~]# ls
myhttpd.tar

import-容器tar包导入

import将export导出的tar包导入成为镜像

[root@docker ~]# docker import --help

Usage:  docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

Import the contents from a tarball to create a filesystem image

Aliases:
  docker image import, docker import

Options:
  -c, --change list       Apply Dockerfile instruction to the created image
  -m, --message string    Set commit message for imported image
      --platform string   Set platform if server is multi-platform capable

示例:

[root@docker ~]# docker import myhttpd.tar
sha256:9ae4699fa217a7240c73a50e93a8c1d359d22cf60869d350c8aab8b3c02aabcf

[root@docker ~]# docker import myhttpd.tar myweb:v1
sha256:9fcb90561d2014130ef19f9dd8af5efd7a0a4a2154907bc818ac6b887f13755c

[root@docker ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
myweb         v1        9fcb90561d20   3 seconds ago    146MB
<none>        <none>    9ae4699fa217   32 seconds ago   146MB
httpd         latest    90f191b9781e   11 days ago      148MB
hello-world   latest    74cc54e27dc4   6 months ago     10.1kB

docker save docker export对比:

  • docker save:将镜像保存为文件,save会保存该镜像的所有元数据和历史记录。
  • docker export:将容器导出为文件,文件会丢失所有元数据和历史记录,仅保存容器当时的状 态,再次导入会当作全新的镜像。

思考: docker export导出的文件是否可以使用 docker load导入?

[root@docker ~]# docker load -i myhttpd.tar
open /var/lib/docker/tmp/docker-import-3425397234/boot/json: no such file or 
directory

思考:docker save导出的文件是否可以使用docker import导入?

提示:我们可以将 docker save保存出来的文件解压后分析文件结构。

[root@docker ~]# docker save busybox -o busybox.tar
[root@docker ~]# ls
busybox.tar            Dockerfile        index.html     PrjectJava
centos.ssh.dockerfile  httpd.dockerfile  passwd.tar.gz  tmpfile2
[root@docker ~]# docker import busybox.tar busybox:v1
sha256:49492f42dd95daf41156e2a43edc14daee24b4329003397b273bec84742825f8
[root@docker ~]# docker images
REPOSITORY             TAG       IMAGE ID       CREATED          SIZE
busybox                v1        49492f42dd95   15 seconds ago   4.68MB
localhost:5000/httpd   v1        00fe3afeb8c3   11 days ago      117MB
[root@docker ~]# docker history busybox:v1
IMAGE          CREATED         CREATED BY   SIZE      COMMENT
49492f42dd95   4 minutes ago                4.68MB    Imported from -
[root@docker ~]# docker history busybox
IMAGE          CREATED       CREATED BY                          SIZE      COMMENT
c6348fa86ba0   7 weeks ago   BusyBox 1.38.0 (glibc), Debian 13   4.45MB

显示成功,但是不建议,镜像容易残缺

030 实现容器的底层技术

为了更好地理解容器的特性,本节我们将讨论容器的底层实现技术。

cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额,namespace 实现资源隔离

cgroup

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

cgroup 到底长什么样子呢?我们可以在 /sys/fs/cgroup 中找到它。还是用例子来说明,启动一个容器,设置--cpu-shares=512

[root@docker docker]# docker run -it --cpu-shares 512 ubuntu-with-stress -c 1 -v
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [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 namespace

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

容器有自己的 / 目录,可以执行 mount 和 umount 命令。当然我们知道这些操作只在当前容器中生 效,不会影响到 host 和其他容器。

UTS namespace

简单的说,UTS namespace 让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短 ID,可以通过 -h 或 --hostname 参数设置。

在这里插入图片描述

IPC namespace

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

PID namespace
我们前面提到过,容器在 host 中以进程的形式运行。例如当前 host 中运行了两个容器:

[root@docker ~]# docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS             
PORTS     NAMES
a80f1f8b692c   ubuntu    "/bin/bash"          26 seconds ago   Up 4 seconds     
       interesting_davinci
835dd4eebda2   httpd     "httpd-foreground"   56 seconds ago   Up 55 seconds
80/tcp    adoring_pare 

通过 ps axf 可以查看容器进程:

在这里插入图片描述

所有容器的进程都挂在 dockerd 进程下,同时也可以看到容器自己的子进程。 如果我们进入到某个容 器,ps 就只能看到自己的进程了:

[root@docker ~]# docker exec -it a80f1f8b692c bash
root@a80f1f8b692c:/#
root@a80f1f8b692c:/# ps axf
PID TTY      STAT   TIME COMMAND
 17 pts/1    Ss     0:00 bash
 25 pts/1    R+     0:00  \_ ps axf
  1 pts/0    Ss+    0:00 /bin/bash

而且进程的 PID 不同于 host 中对应进程的 PID,容器中 PID=1 的进程当然也不是 host 的 init 进程。也 就是说:容器拥有自己独立的一套 PID,这就是 PID namespace 提供的功能。

Network namespace

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

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

[root@docker ~]# docker exec -it a80f1f8b692c bash
root@a80f1f8b692c:/#
root@a80f1f8b692c:/# useradd lz       #容器中创建用户lz
root@a80f1f8b692c:/#
root@a80f1f8b692c:/# exit
exit
[root@docker ~]# su - lz              
#宿主机中并没有用户lz
su: 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 从磁盘中删除容器

到这里,我们已经学习完了容器章节。下一节开始讨论容器网络。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐