容器的进一步认识
# 容器的进一步认识
# 容器(Container)是什么?
容器是基于镜像创建的可运行实例,并且单独存在,一个镜像可以创建出多个容器。运行容器化环境时,实际上是在容器内部创建该文件系统的读写副本。 这将添加一个容器层,该层允许修改镜像的整个副本。如图 1 所示。
如果说你对基于镜像创建的可运行实例
这一概念,还是感觉有点模糊,那么你可以记住,Docker容器的启动依赖于镜像,它(容器)本身包括,动态文件(能记录运行时产生的数据,存放临时数据)和运行时的程序,还要加上于底层操作系统提供的资源隔离功能。换句话说,为了实现可运行的实例——这一目标,我们必须提供这三者才可以。
# 容器的生命周期
容器的生命周期是容器可能处于的状态,容器的生命周期分为 5 种。
- created:初建状态
- running:运行状态
- stopped:停止状态
- paused: 暂停状态
- deleted:删除状态
各生命周期之前的转换关系如图所示:
通过docker create
命令生成的容器状态为初建状态,初建状态通过docker start
命令可以转化为运行状态,运行状态的容器可以通过docker stop
命令转化为停止状态,处于停止状态的容器可以通过docker start
转化为运行状态,运行状态的容器也可以通过docker pause
命令转化为暂停状态,处于暂停状态的容器可以通过docker unpause
转化为运行状态 。处于初建状态、运行状态、停止状态、暂停状态的容器都可以直接删除。
# 容器的操作
容器的操作可以分为五个步骤:创建并启动容器、终止容器、进入容器、删除容器、导入和导出容器。下面我们逐一来看。
# 1 创建
我们可以使用docker create
命令来创建容器,例如:
docker create -it --name=busybox busybox
上述命令的作用是在 Docker 环境中创建一个新的容器,但并不启动它。这条命令的具体细节如下:
docker create
: 这是 Docker 命令行工具中的一个子命令,用于创建一个新的容器实例,但创建后保持在停止状态,不自动启动。-it
: 这两个选项一起使用,其中-i
表示以交互模式运行容器,-t
则表示为容器分配一个伪终端(pseudo-TTY)。这使得你可以在新创建的容器内与之进行交互,比如执行命令并查看输出。--name=busybox
: 这个选项指定了新创建的容器的名称为busybox
。给容器命名可以帮助你在后续操作中更容易地引用它,比如启动、停止或删除容器。busybox
: 这里指定了容器的基础镜像为busybox
。BusyBox 是一个集成了大量常用 Unix 工具的小型软件套件,非常适合用作轻量级的基础镜像。它包含了许多常用的 Linux 命令行工具,因此常被用作测试或构建小型应用的环境。
综上所述,执行这条命令后,Docker 会基于 BusyBox 镜像创建一个新的容器,并赋予其名称 busybox
,但这个容器在创建后会保持停止状态,等待用户手动使用 docker start
命令来启动。由于指定了 -it
选项,一旦启动并进入该容器,你就能得到一个交互式的命令行界面。
那命令执行完毕了,我们该如何查看创建的容器呢?
docker ps -a
这个命令会列出所有容器,无论它们是运行中还是已经停止。
如果你只想看正在运行的容器,就使用
docker ps
;如果你想查看包括停止在内的所有容器,则应该使用docker ps -a
。
在容器少的情况下,我们使用该命令,通过肉眼能很快的找到对应的容器,但如果容器很多,肉眼找就有些不够用来,这时我们可以考虑加上筛选条件
docker ps -a|grep busybox
这样结果中,就只会显示带有busybox字样的容器。
# 2 启动
容器启动有两种方式:
- 使用
docker start
命令基于已经创建好的容器直接启动 。 - 使用
docker run
命令直接基于镜像新建一个容器并启动,相当于先执行docker create
命令从镜像创建容器,然后再执行docker start
命令启动容器。
既然我们刚刚已经创建好了容器,那我们试试docker start
来启动:
docker start busybox
就像让某人做一件事一样,要启动某个容器也需要先知道它的ID或者名称(通过docker ps -a)。然后便是执行 docker start
命令,后跟容器的ID或名称。
docker run
这种方式启动容器则更为简单,因为它包含了容器的创建过程,我们直接找到对应镜像,并执行即可,对应语法格式如下:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
常用选项:
-d
:以后台模式运行容器(daemonized)。-p
:端口映射,格式为HOST_PORT:CONTAINER_PORT
。--name
:为容器指定一个名称。-v
:绑定宿主机目录到容器目录,格式为HOST_DIR:CONTAINER_DIR
。-e
:设置环境变量,格式为KEY=VALUE
。
一个例子送上:
docker run -it --name=my_busybox busybox
使用该命令之后,Docker 后台执行的流程为:
- Docker 会检查本地是否存在 busybox 镜像,如果镜像不存在则从 Docker Hub 拉取 busybox 镜像;
- 使用 busybox 镜像创建并启动一个容器;
- 分配文件系统,并且在镜像只读层外创建一个读写层;
- 从 Docker IP 池中分配一个 IP 给容器;
- 执行用户的启动命令运行镜像。
上述命令中, -t 参数的作用是分配一个伪终端,-i 参数则可以终端的 STDIN 打开,同时使用 -it 参数可以让我们进入交互模式。 在交互模式下,用户可以通过所创建的终端来输入命令,例如:
/ # ps au
PID USER TIME COMMAND
1 root 0:00 sh
5 root 0:00 ps au
PID USER TIME COMMAND
1 root 0:00 sh
6 root 0:00 ps aux
/ #
2
3
4
5
6
7
8
我们可以看到容器的 1 号进程为 sh 命令,在容器内部并不能看到主机上的进程信息,因为容器内部和主机是完全隔离的。同时由于 sh 是 1 号进程,意味着如果通过 exit 退出 sh,那么容器也会退出。所以对于容器来说,杀死容器中的主进程,则容器也会被杀死。
Docker 的交互模式(Interactive Mode)是指容器在启动时能够与用户进行交互的一种运行模式。在这种模式下,Docker 会为容器分配一个伪终端(TTY)并保持标准输入(stdin)打开,允许用户通过命令行与容器内的进程进行实时的输入和输出操作,就像在本地系统上操作一样。
# 3 终止容器
容器启动后,如果我们想终止(或停止)运行中的容器,可以使用docker stop
命令。命令格式为 docker stop [-t|–time[=10]]。该命令首先会向运行中的容器发送 SIGTERM 信号,如果容器内 1 号进程接受并能够处理 SIGTERM,则等待 1 号进程处理完毕后退出,如果等待一段时间后,容器仍然没有退出,则会发送 SIGKILL 强制终止容器。
docker stop busybox
busybox
2
之后,再使用docker ps,便会不会再看到busybox。当然,容器busybox虽然已经停止了,但还可以通过docker start busybox继续启动。
此外,docker restart
命令会将一个运行中的容器终止,并且重新启动它。
docker restart busybox
busybox
2
# 4 暂停
现在我们已经能启动或者停止容器了,那docker为什么又提供了一个"暂停操作"呢?我们不使用的时候,直接停止不就好了吗?
首先你要知道,暂停容器是一种在特定场景下非常有用的操作,主要目的是临时冻结容器内所有进程的执行
而不彻底停止容器
。之所以说它有或者说需要它主要基于以下几个原因:
- 资源调配: 当系统资源紧张,特别是CPU资源有限时,暂停一些非关键或暂时不需要服务的容器可以释放CPU资源给更重要的进程使用,同时保持容器状态以便快速恢复服务,避免了容器重启带来的初始化开销。
- 维护操作: 在进行系统维护、数据迁移或其他不影响容器内部状态的操作时,暂停容器可以保证维护期间容器内部状态不变,操作完成后直接恢复服务,简化了维护流程。
- 问题诊断: 如果发现某个容器有异常行为但又不希望立即终止它以保留现场,可以先暂停容器,然后调查问题原因。这样既能阻止问题进一步扩大,又能保留容器当前状态供分析。
- 节省资源: 在某些开发或测试环境中,可能有一部分容器在非工作时间不需要运行,但又希望保持其状态以便快速恢复工作,此时可以选择暂停这些容器以减少资源消耗。
- 按需服务: 在某些按需提供服务的场景下,如果用户请求减少,可以暂停部分容器来节省资源,待需求增加时再快速恢复服务。
总之,暂停容器提供了一种灵活的资源管理手段,能够在不丢失容器状态的前提下快速调整资源分配和进行必要的维护操作。
注意:暂停容器后,对内存的消耗并不会有较大的减少
暂停容器后,容器内的进程确实会被暂停,不再执行任何指令,因此CPU使用率会降到几乎为零。然而,关于内存,情况稍微复杂一些。虽然进程暂停了,但是它们占用的内存(包括栈、堆、共享库等)并不会立即被释放回操作系统。这是因为暂停状态是为了能够快速恢复容器运行,保持所有进程状态的完整性是非常重要的,这包括它们当前的内存使用情况。
因此,暂停容器后,它仍然会占用与运行时大致相同的内存量。这意味着,如果你的主要目标是通过暂停容器来释放内存资源,这可能不是一个有效的方法。暂停容器更适用于需要临时冻结服务、进行无状态维护操作或是快速释放CPU资源的场景。如果需要回收内存资源,完全停止容器(使用
docker stop
命令)会更加合适。
说了这么多,来看看如何暂停容器吧,暂停后,通过docker ps同样可以看到,该容器已经暂停
docker pause [容器ID或名称]
# 比如
docker pause busybox
2
3
恢复容器:当需要恢复容器的执行时,使用docker unpause
命令,同样需要指定容器的ID或名称:
docker unpause [容器ID或名称]
#
docker unpause busybox
2
3
注意:只有在容器处于运行状态时(即不是已经停止或已经被删除的状态)才能进行。
# 5 删除容器
能创建跟使用容器还不够,我们还需要学会删除容器。因为容器即使停止运行,也会占用磁盘空间,删除不再使用的容器可以帮助释放这些资源,尤其是对于资源有限的环境尤为重要。同时,如果有多个容器使用相同的端口或其他资源,或者需要重新配置容器,删除旧容器后再重新创建可以避免潜在的资源冲突问题。总之,删除容器主要是为了清理资源、管理环境以及确保安全。
删除容器命令的使用方式如下:docker rm [OPTIONS] CONTAINER [CONTAINER...]
。
如果要删除一个停止状态的容器,可以使用docker rm
命令删除。
docker rm busybox
如果要删除正在运行中的容器,必须添加 -f (或 –force) 参数, Docker 会发送 SIGKILL 信号强制终止正在运行的容器。
docker rm -f busybox
# 进入容器
接下来再介绍一个docker的强大特性,那就是进入容器,它为管理员和开发者提供了极大的灵活性和便利性。以下是进入容器能帮助我们做的一些关键事情:
- 调试: 当容器内运行的应用程序出现问题时,可以直接进入容器内部查看日志、运行诊断命令或使用调试工具,这对于定位和解决问题至关重要。
- 配置更新: 如果需要修改容器内部的配置文件或脚本,可以直接在容器内进行编辑,尤其是在容器启动初期进行环境调整或配置验证。
- 数据查看与操作: 在开发或维护阶段,可能需要直接查看或修改容器内的数据库记录、文件内容等,进入容器提供了直接操作数据的途径。
- 服务管理: 可以在容器内启动、停止或重启服务进程,特别是在那些没有外露管理接口或需要直接控制服务的场景下。
- 安装或升级软件: 在某些情况下,可能需要在容器内安装额外的软件包或更新现有软件,直接进入容器执行相应的包管理命令可以简化这一过程。
- 性能监控: 通过进入容器,可以运行top、ps、vmstat等命令来监控容器的资源使用情况,帮助优化性能。
- 测试环境模拟: 对于开发人员来说,进入容器可以模拟生产环境,便于进行功能测试或集成测试,确保代码在相似环境下表现正常。
- 教育与学习: 对于学习Docker的新手而言,通过进入容器探索其内部结构和运行机制是一个很好的学习方法。
处于运行状态的容器可以通过docker attach
、docker exec
、nsenter
等多种方式进入容器。
- 使用
docker attach
命令进入容器
使用 docker attach ,进入我们上一步创建好的容器,如下所示。
$ docker attach busybox
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps aux
/ #
2
3
4
5
6
注意:当我们同时使用docker attach
命令同时在多个终端运行时,所有的终端窗口将同步显示相同内容,当某个命令行窗口的命令阻塞时,其他命令行窗口同样也无法操作。 由于docker attach
命令不够灵活,因此我们一般不会使用docker attach
进入容器。我们推荐使用docker exec
这种更加灵活的进入容器的方式
- 使用 docker exec 命令进入容器
Docker 从 1.3 版本开始,提供了一个更加方便地进入容器的命令docker exec
,我们可以通过docker exec -it CONTAINER
的方式进入到一个已经运行中的容器,如下所示。
$ docker exec -it busybox sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 sh
12 root 0:00 ps aux
2
3
4
5
6
我们进入容器后,可以看到容器内有两个sh
进程,这是因为以exec
的方式进入容器,会单独启动一个 sh 进程,每个窗口都是独立且互不干扰的,也是使用最多的一种方式。
关于它们各自的优缺点这里就不展开了,大家感兴趣自行搜索学习。下面简单介绍下其中的docker exec。
# 进入容器-docker exec简介
下面是关于docker exec
的详细用法介绍:
- 基本语法
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
- OPTIONS:可选参数,用来定制执行命令的行为,例如:
-i
,--interactive
:保持标准输入(stdin)打开,使得可以与命令进行交互。-t
,--tty
:为命令分配一个伪终端(TTY),这对于需要终端交互的命令(如shell)非常有用。-d
,--detach
:在后台运行命令,不等待命令结束。--user USER
:以指定的用户身份运行命令。-e
,--env KEY=VALUE
:设置环境变量。
- CONTAINER:要执行命令的容器的名称或ID。
- COMMAND [ARG...]:要在容器内执行的具体命令及其参数。
都已经介绍完毕了,举几个例子吧
- 简单命令执行:在名为
busybox
的容器内执行ls
命令查看目录内容。
docker exec busybox ls
- 指定用户执行:以非root用户身份执行命令。
docker exec --user www-data my_container touch /var/www/html/newfile.txt
- 在容器内部启动shell会话
docker exec -it busybox sh
- -it: 这是两个选项,合在一起使用。
- -i 或 --interactive 表示保持标准输入(stdin)打开,使得容器命令能够接收来自终端的输入。这对于交互式的命令行会话是必需的。
- -t 或 --tty 为命令分配一个伪终端(TTY)。它模拟了一个终端环境,允许命令具有颜色高亮、使用键盘上的特殊键(如方向键)以及其他与终端相关的功能。
- sh: 这是要在容器内部执行的命令。
sh
是 Bourne Shell 的一个变体,是许多Unix和类Unix系统中的标准命令行解释器。在这里,它启动了一个Shell会话,允许用户在容器内输入命令并与之交互。
# 导出和导入容器
容器的出现解决了程序在不同系统上环境不一致的问题,但是数据迁移,在目前我们了解到的里面,docker并没有给出解决方案。没有数据的程序对我们而言价值并不大。
数据库对于数据迁移,提供了导入和导出功能。docker会有吗?那必然是有的,那就是接下来我们要介绍的容器的导入和导出。
在Docker中,导入容器
(import)与导出容器
(export)是两个用于容器快照状态保存和恢复的操作。这两个操作主要用于快速备份或迁移容器的当前状态,而不包含容器的构建历史或元数据。
# 导出容器(docker export)
命令格式:
docker export CONTAINER > FILE.tar
作用: 将指定容器的文件系统导出为一个tar文件。此操作捕获容器运行时的文件系统状态,但不包括容器的配置、环境变量、标签等元数据。导出的文件可以看作是容器的一个静态快照。
使用场景: 当你需要迁移或备份一个容器的即时状态到另一个系统上,但不关心容器的构建历史或配置细节时,可以使用此命令。
# 导入容器(docker import)
命令格式:
docker import FILE.tar [REPOSITORY[:TAG]]
作用: 将一个tar文件(通常是通过docker export
导出的文件或任何符合Docker镜像格式的tar文件)导入Docker,创建一个新的镜像。导入后,你就可以使用这个新的镜像来创建容器。注意,导入的镜像不包含导出容器时的元数据,如环境变量、命令等,只是一个纯净的文件系统快照。
使用场景: 当你有一个容器的快照文件,需要在本地或其他Docker宿主机上基于这个状态创建一个新的镜像,并从中启动容器时,使用此命令。此操作适用于分享或迁移容器状态,但不适合需要保留完整构建历史的场景。
注意:使用
export/import
会丢失容器的构建历史、标签和其他元数据,仅适用于状态迁移而不是镜像的版本控制或分发。
一个例子送上:
- 导出容器
首先进入容器创建文件
docker exec -it busybox sh
cd /tmp && touch test
2
然后执行导出命令
docker export busybox > busybox.tar
执行以上命令后会在当前文件夹下生成 busybox.tar 文件,我们可以将该文件拷贝到其他机器上,通过导入命令实现容器的迁移。
- 导入容器
通过docker export
命令导出的文件,可以使用docker import
命令导入,执行完docker import
后会变为本地镜像,最后再使用docker run
命令启动该镜像,这样我们就实现了容器的迁移。
导入容器的命令格式为 docker import [OPTIONS] file|URL [REPOSITORY[:TAG]]。接下来我们一步步将上一步导出的镜像文件导入到其他机器的 Docker 中并启动它。
首先,使用docker import
命令导入上一步导出的容器
docker import busybox.tar busybox:test
此时,busybox.tar 被导入成为新的镜像,镜像名称为 busybox:test 。下面,我们使用docker run
命令启动并进入容器,查看上一步创建的临时文件
docker run -it busybox:test sh
/ # ls /tmp/
test
2
3
可以看到我们之前在 /tmp 目录下创建的 test 文件也被迁移过来了。这样我们就通过docker export
和docker import
命令配合实现了容器的迁移。