Docker 镜像管理

1,481 阅读7分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

上一篇:Docker 容器的使用

镜像的本质

前面的文章介绍过,镜像是一个只读的文件系统,我们可以基于一个镜像来实例化容器。这部分对镜像做进一步的介绍。

镜像本质上由多个只读文件系统叠加而成。最底层是一个引导文件系统,与 Linux 操作系统的引导文件系统类似;第二层是 root 文件系统,可以是一种或多种操作系统(Debian 或者 Ubuntu 文件系统),这一层是基础镜像。其它的镜像都是直接或者间接基于基础镜像构建的,每次构建都在其基于的镜像的文件系统栈上再叠加一层只读文件系统。Docker 通过联合加载的技术,一次加载多层文件系统,但是对外只提供一个包含所有底层文件和目录的文件系统。

这样的文件系统,就是镜像。除了基础镜像外,每一个镜像都是在其父镜像的顶部再叠加一层文件系统而成,镜像中的多层文件系统都是只读的,因此镜像本身也是只读的,其在外面看来,就是一个只读的文件系统。当基于一个镜像启动一个新的容器时,Docker 会在镜像的最顶层再加载一个读写文件系统,我们在 Docker 中运行的程序和执行的所有操作就是在这个读写层执行的。

这里还要提到一个技术,叫做写时复制(Copy on Write) 。在容器第一次被启动时,它的读写层是空的,只有当文件系统发生变化的时候,读写层才会发生变化。比如我们修改了一个文件,这个文件会先从只读层复制到读写层,再在读写层进行修改。修改之前的只读版本依然存在,只是当我们访问这个文件的时候,它被读写层的修改后的文件隐藏了起来。

也就是说,镜像层以及其上的读写层,再加上一些配置数据,构成了容器。

镜像存储在哪儿

我们可以在命令行执行 docker images 命令来查看本地存储的镜像列表,执行结果如下:

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    ba6acccedd29   2 weeks ago   72.8MB

在上一篇文章中,我们运行了基于 ubuntu 镜像的容器,由于本地没有 ubuntu 镜像,因此 Docker 自动从 Docker Hub 拉取了镜像。实际上,如果你使用的 Linux 操作系统,Docker 将镜像和容器文件都保存在 /var/lib/docker 目录下。

Docker Hub 是 Docker 官方维护的 Registry,Docker Registry的代码是开源的,因此,你也可以搭建自己的 Registry,很多企业都有供内部使用的私有 Registry。

Registry 中包含了镜像仓库,仓库中包含了镜像、层、关于镜像的元数据。每个仓库中都包含了很多镜像,比如 ubuntu 仓库包含了许多版本的 ubuntu,他们以 tag 区分。当我们需要拉取指定 tag 的镜像时,可以使用一下的命令。

# 格式:docker pull image:tag
docker pull ubuntu:12.04

Docker Hub 中的仓库名称,除了类似于 ubuntu 这种形式,还有类似于 pseudocode/someapp 这种形式。这两种名称格式分别用于 Docker Hub 中的顶层仓库和用户仓库中的镜像。顶层仓库是由 Docker 官方来管理的,用户仓库是 Docker 用户创建的。

pseudocode/someapp 中,pseudocode 是用户名,someapp 是仓库名。

我们可以使用仓库名在 Docker Hub 网站 搜索镜像,也可以在命令行通过 docker search 命令来搜索,比如:

docker search nginx

会得到类似如下的结果:

NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                             Official build of Nginx.                        15742     [OK]
...

如何构建/修改镜像

除了使用已有的镜像,我们也可以自己构建镜像,并分享给别人使用。接下来,我们会构建镜像并分享到 Docker Hub,因此,需要去 Docker Hub 网站先注册一个账号

然后在命令行通过 docker login 命令登陆,如果成功,命令行会提示「Login Succeeded」。

构建镜像的方式有两种,分别是使用 docker commit 命令构建,以及通过 Dockerfile 文件构建,下面分别介绍。

使用 commit 命令构建镜像

前面我们说过,一个镜像通常是基于另一个镜像增加一个只读文件系统层构建的,因此,我们可以修改一个现有的镜像,生成新的镜像。

接下来,我们以 ubuntu 镜像为基础,在其中安装 nginx 然后构建新的包含 Nginx 的镜像。

首先,创建一个基于 ubuntu 镜像的容器,并运行 bash。

docker run -i -t ubuntu /bin/bash

然后在容器中执行命令安装 Nginx。

apt-get update && apt-get install -y nginx

安装完成之后退出容器,在宿主机的命令行中执行一下命令:

docker commit 70e797fe9800 pseudocode/nginx

以上命令中,docker commit 命令后面紧跟的是刚才运行的容器 ID,最后是目标镜像仓库的镜像名称(注意,这里用户名的部分要用你自己注册的 Docker Hub 用户名)。执行成功后,便可以使用 docker images 命令看到刚刚创建的镜像。

相比于通过这种方式构建镜像,使用 Dockerfile 是更好额选择,它更灵活更强大。

使用 Dockerfile 文件构建镜像

现在宿主机创建一个目录,再在其中创建 Dockerfile 文件,比如:

mkdir nginx_image
➜  cd nginx_image
➜  touch Dockerfile

这个 nginx_image 目录会被 Docker 作为构建上下文,在构建镜像的时候,Docker 会将其中的所有内容发送到 Docker 的守护进程,以便 Docker 守护进程可以直接访问用户想在镜像中存储的任何数据。

接下来,需要写 Dockerfile 中的指令。Dockerfile 是由一系列指令和参数构成的,以下是一个示例:

# 一些注释
FROM ubuntu:14.04
RUN apt-get update && apt-get install -y nginx 
EXPOSE 80

这里的指令都是大写字母,后面跟随参数。

  • FROM 是父镜像的名称和TAG。
  • RUN 指令会在当前镜像中运行指定的命令,我们在此安装了 Nginx 服务器。
  • EXPOSE 制定了容器应用程序会使用的端口号。

之后,我们在构建上下文目录中执行一下命令:

docker build -t="pseudocode/nginx_image" .

这里的 -t 参数指定了镜像的名称(注意,这里同样要用你自己注册的 Docker Hub 用户名),最后的 . 表示在当前目录查找 Dockerfile 文件。

当执行完成之后,就可以使用 docker image 中看到名称为 pseudocode/nginx_image 的镜像了。接下来,用刚才创建的镜像运行一个容器:

docker run -d -p 80 pseudocode/nginx_image nginx -g "daemon off;"

这里的 -p 80 指令会让 Docker 将容器内部的端口开放给外部,我们没有手动指定要映射到的端口号,因此 Docker 会在宿主机随机选择一个空闲的端口号。最后的 nginx -g "daemon off;" 将以前台的方式启动 Nginx 服务器。容器运行起来之后,使用 docker ps 命令查看结果如下:

CONTAINER ID   IMAGE                    COMMAND                  CREATED         STATUS         PORTS                   NAMES
0204000c73f9   pseudocode/nginx_image   "nginx -g 'daemon of…"   3 seconds ago   Up 2 seconds   0.0.0.0:61190->80/tcp   wonderful_aryabhata

这里可以看到,容器内的 80 端口被映射到了宿主机的 61190 端口,我们可以试着放问一下。

成功。

在 Dockerfile 文件中可以使用的指令非常多,我们可以使用这些指令执行各种复杂操作,之后会单独写一篇文章介绍 Dockerfile 中的指令。

在 Docker Hub 共享镜像

如果想讲自己创建的镜像分享出去,可以使用 docker push 命令将其推送到 Docker Hub。

docker push pseudocode/nginx

执行成功之后,就可以在 Docker Hub 网站看到自己的镜像。

删除镜像

如果一个镜像不再使用了,就没必要再让它占用本地的存储空间,可以使用 docker rmi 命令删除。

docker rmi pseudocode/nginx

如果想要删除 Docker Hub 上分享的镜像仓库,登录网站后找到仓库设置中的 Delete Repository 按钮点击删除即可。