1. 镜像和容器

1.1. 镜像依赖技术

镜像相当于是一个 root 文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含一些为运行时准备的配置参数:例如匿名卷、环境变量、用户等。镜像不包含任何动态的数据,镜像内容在其构建之后就不会被改变。一个镜像可以被重复使用多次,创建无数个相同的容器,所以我们在前面说镜像是带有创建 Docker 容器命令说明的只读模板。镜像还可以是一种标准化的软件交付方式,镜像内包含基础运行环境和程序代码。

镜像的分层依赖于一系列的底层技术,例如联合文件系统(Union FileSystem)、写时复制(copy no write)。

联合文件系统(Union FileSystem)

联合文件系统可以将多个目录(也称为分支,因为联合文件系统支持对文件系统的修改作为一次提交来层层叠加)的内容联合挂载到一个目录下,而不修改目录的物理位置。联合文件系统还支持只读和可读写目录共存,这是实现镜像拥有只读层和可读写层的原理。联合文件系统是 Docker 镜像的基石,镜像的分层可以让不同容器之间共享资源。例如两个使用 Ubuntu:20.04 镜像创建的容器,一个容器安装了 Nginx,另一个容器安装了 Tomcat,那么这两个容器其实是共享 Ubuntu:20.04 镜像层,只有安装 Nginx 和 Tomcat 的那一层才是它们自己的改动层,联合文件系统在实现资源共享的同时提高了存储效率。

Docker 目前支持的联合文件系统有:overlay2、overlay、aufs、btrfs、vfs、devicemapper。

aufs 是 Docker 以前版本默认的存储驱动,如果 Linux 内核是 4.0 或者更高版本,并且使用 Docker Engine - Community,请使用新的 overlay2,它的性能比 aufs 存储驱动更好。

写时复制(copy no write)

我们已经知道 Docker 镜像是由多个只读层叠加构建的,那么在启动容器时,我们对容器的操作是如何被保存的呢?其实,Docker 启动时会加载镜像的只读层并在镜像层的顶部添加一个读写层,如果此时在容器中修改了一个已存在的文件,该文件将会从读写层下面的只读层复制到读写层并进行修改,该文件的只读版本仍然存在,但是容器已经无法看到存在于下层的这个文件了。只有被修改的文件才会被复制到读写层,写时复制最大限度减少了 I/O 和后续层的大小。

1.2. 容器依赖技术

在宿主机上看,一个容器其实就是宿主机的一个进程,但是这个进程是和宿主机存在隔离的。容器内的进程只能看到自己 namespace 的“世界”,与宿主机上的其他进程互相无感知。主要是使用了 Linux 内核底层的 namespace 和 cgroup 技术。

Namespace 资源隔离

namespace 是 Linux 提供的一种由内核实现的隔离技术。是在 Unix 的 chroot 系统调用(通过修改根目录把用户监禁在一个特定目录下)的基础上,实现了六种 namespace 隔离机制。

Namespace 系统调用参数 隔离内容
UTS CLONE_NEWUTS 主机名和域名
IPC CLONE_NEWIPC 共享内存、信号量、消息队列
PID CLONE_NEWPID 进程号
Network CLONE_NEWNET 网络设备、网络协议栈、端口
Mount CLONE_NEWNS 文件系统挂载点
User CLONE_NEWUSER 用户和用户组

Cgroup 资源限制

Cgroup(Control Group)是 Linux 内核的一个功能,用来限制、控制与分离一个进程组群的资源(例如 CPU、内存、磁盘 IO 等)。Cgroup 可以为系统中运行的进程分配资源,拒绝进程访问某些资源,还提供 Cgroup 配置的监控。

Cgroup 的主要作用有:

  • Resource limitation: 限制资源使用,例如 CPU、内存、磁盘。
  • Prioritization: 优先级控制,为进程组分配特定的 CPU(多核) 或者磁盘 IO 吞吐。
  • Accounting: 统计资源使用量,CPU 使用时间、内存使用量等,按量计费非常有用。
  • Control: 进程组控制,挂起或者恢复执行进程。

Cgroup 子系统:

Cgroup 子系统 作用
blkio 为块设备设定 IO 限制,例如磁盘
cpu 使用调度程序提供对 CPU 的 Cgroup 任务访问
cpuacct 自动生成 cgroup 中任务所使用的 CPU 报告
cpuset 给 Cgroup 中的任务分配独立 CPU(多核)和内存节点
memory 为 Cgroup 任务提供对 Memory 的限制
freezer 暂停和恢复 Cgroup 任务
devices 允许或拒绝 Cgroup 任务对设备的访问
net_cls 标记网络数据包,可允许 Linux 流量控制程序识别从具体 Cgroup 中生成的数据包
net_prio 设计网络流量的优先级
hugetlb 主要针对于 HugeTLB 系统进行限制,这是一个大页文件系统

1.3. 镜像和容器的操作

1.3.1. 配置镜像加速

每一个开通了阿里云容器镜像服务的用户,都会有一个镜像加速地址。

[root@docker ~]# sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://265wemgl.mirror.aliyuncs.com"]
}
EOF
[root@docker ~]# sudo systemctl daemon-reload
[root@docker ~]# sudo systemctl restart docker

1.3.2. 下载镜像

Docker Hub 是 Docker 官方提供的公共镜像仓库,使用 docker pull 命令默认就是从这个仓库拉取镜像。拉取镜像的命令格式如下:

[root@docker ~]# docker pull [OPTIONS] NAME[:TAG|@DIGEST]

镜像名称后面可以跟上标签或者镜像摘要,不写的话 Docker 默认拉取 latest(最新版本)。如果要拉取某一个镜像,但是不知道仓库中是否有这个镜像,可以使用搜索查看,--filter 是根据条件对搜索结果进行过滤。

[root@docker ~]# docker search --filter STARS=100  nginx
NAME                          DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                         Official build of Nginx.                        15928     [OK]
jwilder/nginx-proxy           Automated Nginx reverse proxy for docker con…   2101                 [OK]
richarvey/nginx-php-fpm       Container running Nginx + PHP-FPM capable of…   820                  [OK]
jc21/nginx-proxy-manager      Docker container for managing Nginx proxy ho…   288
linuxserver/nginx             An Nginx container, brought to you by LinuxS…   160
tiangolo/nginx-rtmp           Docker image with Nginx using the nginx-rtmp…   147                  [OK]
jlesage/nginx-proxy-manager   Docker container for Nginx Proxy Manager        145                  [OK]
alfg/nginx-rtmp               NGINX, nginx-rtmp-module and FFmpeg from sou…   111                  [OK]
[root@docker ~]#

这里下载的是 ubuntu:20.04 的镜像,library/ubuntu 指的用户名/仓库名,library 则是官方默认的名字。

[root@docker ~]# docker pull ubuntu:20.04
20.04: Pulling from library/ubuntu
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04
[root@docker ~]#

1.3.3. 查看镜像

我们可以列出下载的镜像信息,信息包含仓库名、标签、镜像 ID、创建时间以及所占的空间。

[root@docker ~]# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    f652ca386ed1   5 days ago     141MB
ubuntu       20.04     ba6acccedd29   7 weeks ago    72.8MB
ubuntu       latest    ba6acccedd29   7 weeks ago    72.8MB
ubuntu       18.04     5a214d77f5d7   2 months ago   63.1MB
[root@docker ~]#

1.3.4. 启动容器

下载好镜像之后,我们就能以镜像为基础去启动一个容器了。这里以 ubuntu:20.04 的镜像启动一个容器,并在容器中执行 date 命令后退出并删除容器,屏幕打印的信息与我们平时执行 date 命令一样,但是这个信息是执行容器内的 date 命令打印出来的。

[root@docker ~]# docker run --rm ubuntu:20.04 /bin/date
Wed Dec  8 09:34:46 UTC 2021
[root@docker ~]#

上面是以非交互式启动的容器,容器还可以交互式启动。其中,-t 是分配一个伪终端并绑定到容器的标准输入上,-i 是以交互式启动,就是让容器的标准输入保持打开。在交互模式下,用户创建容器之后自动进入到容器环境了,我们可以看到 ubuntu 容器也拥有自己的根目录结构。

[root@docker ~]# docker run -it ubuntu:20.04 /bin/bash
root@cc0a1a750941:/# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@docker ~]#

但是更多时候,我们的容器都是后台运行,就是不把容器内的执行结果输出到宿主机上,通过 -d 参数实现。后台运行启动的容器,会返回一个唯一的容器 ID。

[root@docker ~]# docker run -d ubuntu:20.04 /bin/sh -c "while true; do echo 'hello docker' >> /var/log/docker.log; sleep 1; done"
20b03a8a8d7463efa2ad2cd045db654be27a83f4f499a9fb8df83436d2cfdc53
[root@docker ~]#

可以通过 docker container ls 命令来查看运行容器的信息,加上 -a 参数是显示所有容器。

[root@docker ~]# docker container ls
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS     NAMES
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   26 minutes ago   Up 26 minutes             flamboyant_booth
[root@docker ~]#

1.3.5. 进入容器

我们使用 -d 参数创建的容器,默认会丢到后台去运行,但是很多时候我们需要进入到一个后台运行的容器进行某些操作,这时候就需要使用到 exec 命令了。

[root@docker ~]# docker exec -it flamboyant_booth bash
root@20b03a8a8d74:/# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@20b03a8a8d74:/#

如果仅使用 -i 参数,由于没有 -t 参数分配的伪终端,进入容器后没有 Linux 命令提示符,但是仍然可以执行命令并返回结果。所以一般 -it 参数一起使用。

[root@docker ~]# docker exec -i flamboyant_booth bash
date
Mon Dec 13 01:24:06 UTC 2021
pwd
/

我们使用 exec 命令进入容器,如果这个标准输入 exit 时,并不会导致容器停止。容器还在正常运行,只是这个标准输入断开了。可以按下 Ctrl + d 或者在容器命令行执行 exit 退出容器。

[root@docker ~]# docker exec -it flamboyant_booth bash
root@20b03a8a8d74:/# date
Mon Dec 13 01:31:49 UTC 2021
root@20b03a8a8d74:/# exit

1.3.6. 停止容器

我们可以手动执行 docker container stop 命令停止容器。当然,容器内的应用程序运行结束时,容器也会自动退出。退出状态的容器需要使用 docker container ls -a 命令才能看到。我们还可以使用 docker container start 命令来启动容器,docker container restart 命令是将一个容器停止再重新启动。

[root@docker ~]# docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED      STATUS      PORTS     NAMES
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   2 days ago   Up 2 days             flamboyant_booth
[root@docker ~]# docker container stop flamboyant_booth
flamboyant_booth
[root@docker ~]# docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED      STATUS                       PORTS     NAMES
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   2 days ago   Exited (137) 2 seconds ago             flamboyant_booth
[root@docker ~]#

1.3.7. 删除容器

容器是有生命周期的。因为运行结束而进入退出状态的容器,或者因为发布版本而被替换的容器,这些容器都不处于运行的状态,我们可以使用 docker container rm 命令来删除这些容器。如果要删除一个运行状态的容器,可以使用 -f 参数,docker 会发送 SIGKILL 信号停止容器进程并删除容器。

[root@docker ~]# docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED        STATUS                      PORTS     NAMES
6ca17aec7f97   ubuntu:20.04   "bash date"              28 hours ago   Exited (126) 28 hours ago             condescending_bouman
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   3 days ago     Up 29 hours                           flamboyant_booth
[root@docker ~]# docker container rm 6ca17aec7f97
6ca17aec7f97
[root@docker ~]# docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED      STATUS        PORTS     NAMES
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   4 days ago   Up 30 hours             flamboyant_booth
[root@docker ~]#

如果需要删除所有退出状态的容器,可以使用 docker container prune 命令一次性清理。

[root@docker ~]# docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS                      PORTS     NAMES
c38b5ecba22c   ubuntu:20.04   "/bin/bash -c pwd"       5 seconds ago    Exited (0) 3 seconds ago              gallant_torvalds
c69212a7de81   ubuntu:20.04   "/bin/bash -c date"      19 seconds ago   Exited (0) 17 seconds ago             quizzical_raman
20b03a8a8d74   ubuntu:20.04   "/bin/sh -c 'while t…"   4 days ago       Up 30 hours                           flamboyant_booth
[root@docker ~]# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
c38b5ecba22c24c15789a53b89a687ec7ecf918e2b4e42d3548d5c101cac54e7
c69212a7de811575b2dc7994059ba3bf18479312d18273c1e5f63e0685860696

Total reclaimed space: 0B
[root@docker ~]#

1.3.8. 删除镜像

删除本地的镜像使用 docker image rm 命令,或者 docker rmi 命令。其中,可以使用镜像 ID、镜像名、摘要来指定要删除的镜像。下面我们删除 redis 镜像:

[root@docker ~]# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
redis        latest    aea9b698d7d1   11 days ago    113MB
nginx        latest    f652ca386ed1   11 days ago    141MB
centos       latest    5d0da3dc9764   2 months ago   231MB
[root@docker ~]# docker image rm redis:latest
Untagged: redis:latest
Untagged: redis@sha256:2f502d27c3e9b54295f1c591b3970340d02f8a5824402c8179dcd20d4076b796
Deleted: sha256:aea9b698d7d1d2fb22fe74868e27e767334b2cc629a8c6f9db8cc1747ba299fd
Deleted: sha256:beb6c508926e807f60b6a3816068ee3e2cece7654abaff731e4a26bcfebe04d8
Deleted: sha256:a5b5ed3d7c997ffd7c58cd52569d8095a7a3729412746569cdbda0dfdd228d1f
Deleted: sha256:ee76d3703ec1ab8abc11858117233a3ac8c7c5e37682f21a0c298ad0dc09a9fe
Deleted: sha256:60abc26bc7704070b2977b748ac0fd4ca94b818ed4ba1ef59ca8803e95920161
Deleted: sha256:6a2f1dcfa7455f60a810bb7c4786d62029348f64c4fcff81c48f8625cf0d995a
[root@docker ~]# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    f652ca386ed1   11 days ago    141MB
centos       latest    5d0da3dc9764   2 months ago   231MB
[root@docker ~]#

1.3.9. 备份迁移

save 和 load

docker save 命令可以保存一个或多个镜像,例如将本地的 centos 镜像和 ubuntu 镜像保存到 image.tar 文件中。

[root@docker ~]# docker save -o images.tar centos:latest ubuntu:latest
[root@docker ~]# ll -sh images.tar
300M -rw------- 1 root root 300M Dec 15 11:40 images.tar
[root@docker ~]#

docker load 命令用于将由 docker save 命令打包生成的 tar 文件载入到本地镜像。

[root@docker ~]# docker load -i images.tar
74ddd0ec08fa: Loading layer [==================================================>]  238.6MB/238.6MB
Loaded image: centos:latest
Loaded image: ubuntu:latest
[root@docker ~]#

export 和 import

docker export 命令可以保存 container 文件系统,例如将运行中的容器保存为 ubuntu.tar 文件。

[root@docker ~]# docker export 20b03a8a8d74 -o ubuntu.tar

docker import 命令用于将由 docker export 命令打包生成的 tar 文件载入到本地镜像。

[root@docker ~]# docker import ubuntu.tar ubuntu:1.0
sha256:bcead6020c0ca44be9d380d29e6cf1e5ed304e855f93858e897374ca7ec84734
[root@docker ~]#

docker save 命令保存的是镜像,docker export 命令保存的是容器。docker load 命令用来载入镜像包文件,docker import 命令用来载入容器包文件,但是两者都是将包文件载入到本地镜像。在载入镜像时,docker load 命令不能对镜像重命名,而 docker import 命令可以对镜像指定名称。

1.3.10. 容器日志

后台运行的容器,应用程序的日志并不会打印出来。如果没有配置数据持久化,日志文件是存放在容器内的。但是我们不需要进入容器才能查看日志,我们可以使用 docker logs 命令实现。

[root@docker ~]# docker logs -f nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2021/12/16 07:00:00 [notice] 1#1: using the "epoll" event method
2021/12/16 07:00:00 [notice] 1#1: nginx/1.20.2
2021/12/16 07:00:00 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2021/12/16 07:00:00 [notice] 1#1: OS: Linux 5.4.0-91-generic
2021/12/16 07:00:00 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2021/12/16 07:00:00 [notice] 1#1: start worker processes
2021/12/16 07:00:00 [notice] 1#1: start worker process 31
2021/12/16 07:00:00 [notice] 1#1: start worker process 32

1.3.11. 资源控制

我们在启动容器的时候,可以控制容器对资源的使用。例如限制容器最多使用 500M 物理内存,物理内存 + 交换分区使用的总大小限制在 800M,并且禁用 OOM Killer。

docker container run -d --name "nginx" --memory="500M" --memory-swap="800M" --oom-kill-disable nginx:1.20

[info] 说明

--memory-swap 是物理内存加交换分区的总使用量限制,并且 --memory-swap 必须比 --memory 设置的要大,--memory 允许的最小值为 6M。

如果 --memory 设置为500M,--memory-swap 不设置或者设置为 0,则容器可使用和 --memory 一样多的交换分区 ,那么容器的内存和交换分区一共可以使用 1G。

想要禁止容器使用交换分区,需要把 --memory 和 --memory-swap 设置为相同的值,因为 --memory-swap 是物理内存加交换分区的总使用量限制。

对于 CPU 资源的控制,例如主机有 2 个 CPU,限制容器最多使用 1.5 个 CPU。还可以使用 --cpus=".5" 参数让容器总是使用 50% 的 CPU。

docker container run -d --name "nginx" --cpus="1.5" nginx:1.20

我们还可以限制容器使用特定的 CPU 或特定的核,例如主机有多个 CPU,则容器可以使用 CPU 编号(从 0 开始)加逗号分隔符来描述容器具体使用那个 CPU。

[root@docker ~]# docker container run -d --name "nginx" --cpuset-cpus="0,1" nginx:1.20
a242e04bff0c10ed6388001119e0d08d4f85a8c075b6f0e0e1c81fa87b195944
[root@docker ~]# docker inspect nginx | grep CpusetCpus
            "CpusetCpus": "0,1",
[root@docker ~]#
Copyright © 荒原饮露 2019 all right reserved,powered by Gitbook该文件修订时间: 2022-06-07 17:23:25