赞
踩
今天我将围绕 Docker 核心概念镜像展开,首先重点讲解一下镜像的基本操作,然后介绍一下镜像的实现原理。首先说明,咱们本课时的镜像均指 Docker 镜像。
你是否还记得镜像是什么?我们先回顾一下。
镜像是一个只读的 Docker 容器模板,包含启动容器所需要的所有文件系统结构和内容。简单来讲,镜像是一个特殊的文件系统,它提供了容器运行时所需的程序、软件库、资源、配置等静态数据。即镜像不包含任何动态数据,镜像内容在构建后不会被改变。
然后我们来看下如何操作镜像。
图 1 镜像操作
从图中可知,镜像的操作可分为:
拉取镜像,使用docker pull
命令拉取远程仓库的镜像到本地 ;
重命名镜像,使用docker tag
命令“重命名”镜像 ;
查看镜像,使用docker image ls
或docker images
命令查看本地已经存在的镜像 ;
删除镜像,使用docker rmi
命令删除无用镜像 ;
构建镜像,构建镜像有两种方式。第一种方式是使用docker build
命令基于 Dockerfile 构建镜像,也是我比较推荐的镜像构建方式;第二种方式是使用docker commit
命令基于已经运行的容器提交为镜像。
下面,我们逐一详细介绍。
Docker 镜像的拉取使用docker pull
命令, 命令格式一般为 docker pull [Registry]/[Repository]/[Image]:[Tag]。
Registry 为注册服务器,Docker 默认会从 docker.io 拉取镜像,如果你有自己的镜像仓库,可以把 Registry 替换为自己的注册服务器。
Repository 为镜像仓库,通常把一组相关联的镜像归为一个镜像仓库,library
为 Docker 默认的镜像仓库。
Image 为镜像名称。
Tag 为镜像的标签,如果你不指定拉取镜像的标签,默认为latest
。
例如,我们需要获取一个 busybox 镜像,可以执行以下命令:
busybox 是一个集成了数百个 Linux 命令(例如 curl、grep、mount、telnet 等)的精简工具箱,只有几兆大小,被誉为 Linux 系统的瑞士军刀。我经常会使用 busybox 做调试来查找生产环境中遇到的问题。
$ docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
61c5ed1cbdf8: Pull complete
Digest: sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
- 1
- 2
- 3
- 4
- 5
- 6
- 7
实际上执行docker pull busybox
命令,都是先从本地搜索,如果本地搜索不到busybox
镜像则从 Docker Hub 下载镜像。
拉取完镜像,如果你想查看镜像,应该怎么操作呢?
Docker 镜像查看使用docker images
或者docker image ls
命令。
下面我们使用docker images
命令列出本地所有的镜像。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 4bb46517cac3 9 days ago 133MB
nginx 1.15 53f3fd8007f7 15 months ago 109MB
busybox latest 018c9d7b792b 3 weeks ago 1.22MB
- 1
- 2
- 3
- 4
- 5
如果我们想要查询指定的镜像,可以使用docker image ls
命令来查询。
$ docker image ls busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 018c9d7b792b 3 weeks ago 1.22MB
- 1
- 2
- 3
当然你也可以使用docker images
命令列出所有镜像,然后使用grep
命令进行过滤。使用方法如下:
$ docker images |grep busybox
busybox latest 018c9d7b792b 3 weeks ago 1.22MB
- 1
- 2
如果你想要自定义镜像名称或者推送镜像到其他镜像仓库,你可以使用docker tag
命令将镜像重命名。docker tag
的命令格式为 docker tag [SOURCE_IMAGE][:TAG] [TARGET_IMAGE][:TAG]。
下面我们通过实例演示一下:
$ docker tag busybox:latest mybusybox:latest
- 1
执行完docker tag
命令后,可以使用查询镜像命令查看一下镜像列表:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 018c9d7b792b 3 weeks ago 1.22MB
mybusybox latest 018c9d7b792b 3 weeks ago 1.22MB
- 1
- 2
- 3
- 4
可以看到,镜像列表中多了一个mybusybox
的镜像。但细心的同学可能已经发现,busybox
和mybusybox
这两个镜像的 IMAGE ID 是完全一样的。为什么呢?实际上它们指向了同一个镜像文件,只是别名不同而已。
如果我不需要mybusybox
镜像了,想删除它,应该怎么操作呢?
你可以使用docker rmi
或者docker image rm
命令删除镜像。
举例:你可以使用以下命令删除mybusybox
镜像。
$ docker rmi mybusybox
Untagged: mybusybox:latest
此时,再次使用docker images
命令查看一下我们机器上的镜像列表。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 018c9d7b792b 3 weeks ago 1.22MB
- 1
- 2
- 3
通过上面的输出,我们可以看到,mybusybox
镜像已经被删除。
如果你想构建属于自己的镜像,应该怎么做呢?
构建镜像主要有两种方式:
使用docker commit
命令从运行中的容器提交为镜像;
使用docker build
命令从 Dockerfile 构建镜像。
首先介绍下如何从运行中的容器提交为镜像。我依旧使用 busybox 镜像举例,使用以下命令创建一个名为 busybox 的容器并进入 busybox 容器。
$ docker run --rm --name=busybox -it busybox sh
/ #
执行完上面的命令后,当前窗口会启动一个 busybox 容器并且进入容器中。在容器中,执行以下命令创建一个文件并写入内容:
/ # touch hello.txt && echo "I love Docker. " > hello.txt
/ #
此时在容器的根目录下,已经创建了一个 hello.txt 文件,并写入了 "I love Docker. "。下面,我们新打开另一个命令行窗口,运行以下命令提交镜像:
$ docker commit busybox busybox:hello
sha256:cbc6406aaef080d1dd3087d4ea1e6c6c9915ee0ee0f5dd9e0a90b03e2215e81c
- 1
- 2
然后使用上面讲到的docker image ls
命令查看镜像:
$ docker image ls busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox hello cbc6406aaef0 2 minutes ago 1.22MB
busybox latest 018c9d7b792b 4 weeks ago 1.22MB
- 1
- 2
- 3
- 4
此时我们可以看到主机上新生成了 busybox:hello 这个镜像。
第二种方式是最重要也是最常用的镜像构建方式:Dockerfile。Dockerfile 是一个包含了用户所有构建命令的文本。通过docker build
命令可以从 Dockerfile 生成镜像。
使用 Dockerfile 构建镜像具有以下特性:
Dockerfile 的每一行命令都会生成一个独立的镜像层,并且拥有唯一的 ID;
Dockerfile 的命令是完全透明的,通过查看 Dockerfile 的内容,就可以知道镜像是如何一步步构建的;
Dockerfile 是纯文本的,方便跟随代码一起存放在代码仓库并做版本管理。
看到使用 Dockerfile 的方式构建镜像有这么多好的特性,你是不是已经迫不及待想知道如何使用了。别着急,我们先学习下 Dockerfile 常用的指令。
Dockerfile 指令 | 指令简介 |
---|---|
FROM | Dockerfile 除了注释第一行必须是 FROM ,FROM 后面跟镜像名称,代表我们要基于哪个基础镜像构建我们的容器。 |
RUN | RUN 后面跟一个具体的命令,类似于 Linux 命令行执行命令。 |
ADD | 拷贝本机文件或者远程文件到镜像内 |
COPY | 拷贝本机文件到镜像内 |
USER | 指定容器启动的用户 |
ENTRYPOINT | 容器的启动命令 |
CMD | CMD 为 ENTRYPOINT 指令提供默认参数,也可以单独使用 CMD 指定容器启动参数 |
ENV | 指定容器运行时的环境变量,格式为 key=value |
ARG | 定义外部变量,构建镜像时可以使用 build-arg = 的格式传递参数用于构建 |
EXPOSE | 指定容器监听的端口,格式为 [port]/tcp 或者 [port]/udp |
WORKDIR | 为 Dockerfile 中跟在其后的所有 RUN、CMD、ENTRYPOINT、COPY 和 ADD 命令设置工作目录。 |
看了这么多指令,感觉有点懵?别担心,我通过一个实例让你来熟悉它们。这是一个 Dockerfile:
FROM centos:7
COPY nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx
EXPOSE 80
ENV HOST=mynginx
CMD ["nginx","-g","daemon off;"]
好,我来逐行分析一下上述的 Dockerfile。
第一行表示我要基于 centos:7 这个镜像来构建自定义镜像。这里需要注意,每个 Dockerfile 的第一行除了注释都必须以 FROM 开头。
第二行表示拷贝本地文件 nginx.repo 文件到容器内的 /etc/yum.repos.d 目录下。这里拷贝 nginx.repo 文件是为了添加 nginx 的安装源。
第三行表示在容器内运行yum install -y nginx
命令,安装 nginx 服务到容器内,执行完第三行命令,容器内的 nginx 已经安装完成。
第四行声明容器内业务(nginx)使用 80 端口对外提供服务。
第五行定义容器启动时的环境变量 HOST=mynginx,容器启动后可以获取到环境变量 HOST 的值为 mynginx。
第六行定义容器的启动命令,命令格式为 json 数组。这里设置了容器的启动命令为 nginx ,并且添加了 nginx 的启动参数 -g 'daemon off;' ,使得 nginx 以前台的方式启动。
上面这个 Dockerfile 的例子基本涵盖了常用的镜像构建指令,代码我已经放在 GitHub上,如果你感兴趣可以到 GitHub 下载源码并尝试构建这个镜像。
学习了镜像的各种操作,下面我们深入了解一下镜像的实现原理。
其实 Docker 镜像是由一系列镜像层(layer)组成的,每一层代表了镜像构建过程中的一次提交。下面以一个镜像构建的 Dockerfile 来说明镜像是如何分层的。
FROM busybox
COPY test /tmp/test
RUN mkdir /tmp/testdir
上面的 Dockerfile 由三步组成:
第一行基于 busybox 创建一个镜像层;
第二行拷贝本机 test 文件到镜像内;
第三行在 /tmp 文件夹下创建一个目录 testdir。
为了验证镜像的存储结构,我们使用docker build
命令在上面 Dockerfile 所在目录构建一个镜像:
$ docker build -t mybusybox .
这里我的 Docker 使用的是 overlay2 文件驱动,进入到/var/lib/docker/overlay2
目录下使用tree .
命令查看产生的镜像文件:
$ tree .
# 以下为 tree . 命令输出内容
|-- 3e89b959f921227acab94f5ab4524252ae0a829ff8a3687178e3aca56d605679
| |-- diff # 这一层为基础层,对应上述 Dockerfile 第一行,包含 busybox 镜像所有文件内容,例如 /etc,/bin,/var 等目录
... 此次省略部分原始镜像文件内容
| `-- link
|-- 6591d4e47eb2488e6297a0a07a2439f550cdb22845b6d2ddb1be2466ae7a9391
| |-- diff # 这一层对应上述 Dockerfile 第二行,拷贝 test 文件到 /tmp 文件夹下,因此 diff 文件夹下有了 /tmp/test 文件
| | `-- tmp
| | `-- test
| |-- link
| |-- lower
| `-- work
|-- backingFsBlockDev
|-- bec6a018080f7b808565728dee8447b9e86b3093b16ad5e6a1ac3976528a8bb1
| |-- diff # 这一层对应上述 Dockerfile 第三行,在 /tmp 文件夹下创建 testdir 文件夹,因此 diff 文件夹下有了 /tmp/testdir 文件夹
| | `-- tmp
| | `-- testdir
| |-- link
| |-- lower
| `-- work
...
通过上面的目录结构可以看到,Dockerfile 的每一行命令,都生成了一个镜像层,每一层的 diff 夹下只存放了增量数据,如图 2 所示。
图 2 镜像文件系统
分层的结构使得 Docker 镜像非常轻量,每一层根据镜像的内容都有一个唯一的 ID 值,当不同的镜像之间有相同的镜像层时,便可以实现不同的镜像之间共享镜像层的效果。
总结一下, Docker 镜像是静态的分层管理的文件组合,镜像底层的实现依赖于联合文件系统(UnionFS)。充分掌握镜像的原理,可以帮助我们在生产实践中构建出最优的镜像,同时也可以帮助我们更好地理解容器和镜像的关系。
到此,相信你已经对 Docker 镜像这一核心概念有了较深的了解,并熟悉了 Docker 镜像的常用操作(拉取、查看、“重命名”、删除和构建自定义镜像)及底层实现原理。
本课时内容精华,我帮你总结如下:
镜像操作命令:
拉取镜像,使用 docker pull 命令拉取远程仓库的镜像到本地 ;
重命名镜像,使用 docker tag 命令“重命名”镜像 ;
查看镜像,使用 docker image ls 或 docker images 命令查看本地已经存在的镜像;
删除镜像,使用 docker rmi 命令删除无用镜像 ;
构建镜像,构建镜像有两种方式。第一种方式是使用 docker build 命令基于 Dockerfile 构建镜像,也是我比较推荐的镜像构建方式;第二种方式是使用 docker commit 命令基于已经运行的容器提交为镜像。
镜像的实现原理:
镜像是由一系列的镜像层(layer )组成,每一层代表了镜像构建过程中的一次提交,当我们需要修改镜像内的某个文件时,只需要在当前镜像层的基础上新建一个镜像层,并且只存放修改过的文件内容。分层结构使得镜像间共享镜像层变得非常简单和方便。
最后试想下,如果有一天我们机器存储空间不足,那你知道使用什么命令可以清理本地无用的镜像和容器文件吗?思考后,可以把你的想法写在留言区。
前几天在咱们的社群里看到有同学在讨论,说面试的时候被问到容器和镜像的区别,有同学回答说没什么区别,也许是在开玩笑,不过这两者的区别很大。今天,我们就来看看容器的相关知识,比如什么是容器?容器的生命周期,以及容器常用的操作命令。学完之后你可以对比下与镜像的区别。
容器是基于镜像创建的可运行实例,并且单独存在,一个镜像可以创建出多个容器。运行容器化环境时,实际上是在容器内部创建该文件系统的读写副本。 这将添加一个容器层,该层允许修改镜像的整个副本。如图 1 所示。
图1 容器组成
了解完容器是什么,接下来我们聊一聊容器的生命周期。
容器的生命周期是容器可能处于的状态,容器的生命周期分为 5 种。
created:初建状态
running:运行状态
stopped:停止状态
paused: 暂停状态
deleted:删除状态
各生命周期之前的转换关系如图所示:
图2 容器的生命周期
通过docker create
命令生成的容器状态为初建状态,初建状态通过docker start
命令可以转化为运行状态,运行状态的容器可以通过docker stop
命令转化为停止状态,处于停止状态的容器可以通过docker start
转化为运行状态,运行状态的容器也可以通过docker pause
命令转化为暂停状态,处于暂停状态的容器可以通过docker unpause
转化为运行状态 。处于初建状态、运行状态、停止状态、暂停状态的容器都可以直接删除。
下面我通过实际操作和命令来讲解容器各生命周期间的转换关系。
容器的操作可以分为五个步骤:创建并启动容器、终止容器、进入容器、删除容器、导入和导出容器。下面我们逐一来看。
容器十分轻量,用户可以随时创建和删除它。我们可以使用docker create
命令来创建容器,例如:
$ docker create -it --name=busybox busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
61c5ed1cbdf8: Pull complete
Digest: sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Status: Downloaded newer image for busybox:latest
2c2e919c2d6dad1f1712c65b3b8425ea656050bd5a0b4722f8b01526d5959ec6
$ docker ps -a| grep busybox
2c2e919c2d6d busybox "sh" 34 seconds ago Created busybox
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果使用docker create
命令创建的容器处于停止状态,我们可以使用docker start
命令来启动它,如下所示。
$ docker start busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6f3d364fad3 busybox "sh" 16 seconds ago Up 8 seconds busybox
- 1
- 2
- 3
- 4
这时候我们可以看到容器已经处于启动状态了。
容器启动有两种方式:
使用docker start
命令基于已经创建好的容器直接启动 。
使用docker run
命令直接基于镜像新建一个容器并启动,相当于先执行docker create
命令从镜像创建容器,然后再执行docker start
命令启动容器。
使用docker run
的命令如下:
$ docker run -it --name=busybox busybox
- 1
当使用docker run
创建并启动容器时,Docker 后台执行的流程为:
Docker 会检查本地是否存在 busybox 镜像,如果镜像不存在则从 Docker Hub 拉取 busybox 镜像;
使用 busybox 镜像创建并启动一个容器;
分配文件系统,并且在镜像只读层外创建一个读写层;
从 Docker IP 池中分配一个 IP 给容器;
执行用户的启动命令运行镜像。
上述命令中, -t 参数的作用是分配一个伪终端,-i 参数则可以终端的 STDIN 打开,同时使用 -it 参数可以让我们进入交互模式。 在交互模式下,用户可以通过所创建的终端来输入命令,例如:
$ ps aux
PID USER TIME COMMAND
1 root 0:00 sh
6 root 0:00 ps aux
- 1
- 2
- 3
- 4
我们可以看到容器的 1 号进程为 sh 命令,在容器内部并不能看到主机上的进程信息,因为容器内部和主机是完全隔离的。同时由于 sh 是 1 号进程,意味着如果通过 exit 退出 sh,那么容器也会退出。所以对于容器来说,杀死容器中的主进程,则容器也会被杀死。
容器启动后,如果我们想停止运行中的容器,可以使用docker stop
命令。命令格式为 docker stop [-t|--time[=10]]。该命令首先会向运行中的容器发送 SIGTERM 信号,如果容器内 1 号进程接受并能够处理 SIGTERM,则等待 1 号进程处理完毕后退出,如果等待一段时间后,容器仍然没有退出,则会发送 SIGKILL 强制终止容器。
$ docker stop busybox
busybox
- 1
- 2
如果你想查看停止状态的容器信息,你可以使用 docker ps -a 命令。
$ docker ps -a
CONTAINERID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox "sh" 26 minutes ago Exited (137) About a minute ago busybox
- 1
- 2
- 3
处于终止状态的容器也可以通过docker start
命令来重新启动。
$ docker start busybox
busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox "sh" 30 minutes ago Up 25 seconds busybox
- 1
- 2
- 3
- 4
- 5
此外,docker restart
命令会将一个运行中的容器终止,并且重新启动它。
$ docker restart busybox
busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox "sh" 32 minutes ago Up 3 seconds busybox
- 1
- 2
- 3
- 4
- 5
处于运行状态的容器可以通过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
/ #
- 1
- 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
- 1
- 2
- 3
- 4
- 5
- 6
我们进入容器后,可以看到容器内有两个sh
进程,这是因为以exec
的方式进入容器,会单独启动一个 sh 进程,每个窗口都是独立且互不干扰的,也是使用最多的一种方式。
我们已经掌握了用 Docker 命令创建、启动和终止容器。那如何删除处于终止状态或者运行中的容器呢?删除容器命令的使用方式如下:docker rm [OPTIONS] CONTAINER [CONTAINER...]
。
如果要删除一个停止状态的容器,可以使用docker rm
命令删除。
docker rm busybox
- 1
如果要删除正在运行中的容器,必须添加 -f (或 --force) 参数, Docker 会发送 SIGKILL 信号强制终止正在运行的容器。
docker rm -f busybox
- 1
导出容器
我们可以使用docker export CONTAINER
命令导出一个容器到文件,不管此时该容器是否处于运行中的状态。导出容器前我们先进入容器,创建一个文件,过程如下。
首先进入容器创建文件
docker exec -it busybox sh
cd /tmp && touch test
- 1
- 2
然后执行导出命令
docker export busybox > busybox.tar
- 1
执行以上命令后会在当前文件夹下生成 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
- 1
此时,busybox.tar 被导入成为新的镜像,镜像名称为 busybox:test 。下面,我们使用docker run
命令启动并进入容器,查看上一步创建的临时文件
docker run -it busybox:test sh
/ # ls /tmp/
test
- 1
- 2
- 3
可以看到我们之前在 /tmp 目录下创建的 test 文件也被迁移过来了。这样我们就通过docker export
和docker import
命令配合实现了容器的迁移。
到此,我相信你已经了解了容器的基本概念和组成,并已经熟练掌握了容器各个生命周期操作和管理。那容器与镜像的区别,你应该也很清楚了。镜像包含了容器运行所需要的文件系统结构和内容,是静态的只读文件,而容器则是在镜像的只读层上创建了可写层,并且容器中的进程属于运行状态,容器是真正的应用载体。
那你知道为什么容器的文件系统要设计成写时复制(如图 1 所示),而不是每一个容器都单独拷贝一份镜像文件吗?思考后,可以把你的想法写在留言区。
在第三课时“镜像使用:Docker 环境下如何配置你的镜像?”里,我介绍了镜像的基本操作和镜像的原理,那么有了镜像,我们应该如何更好地存储和分发镜像呢?答案就是今天的主角——Docker 的镜像仓库。其实我们不仅可以使用公共镜像仓库存储和分发镜像,也可以自己搭建私有的镜像仓库,那在搭建之前,我们先回顾下仓库的基础知识。
仓库(Repository)是存储和分发 Docker 镜像的地方。镜像仓库类似于代码仓库,Docker Hub 的命名来自 GitHub,Github 是我们常用的代码存储和分发的地方。同样 Docker Hub 是用来提供 Docker 镜像存储和分发的地方。
有的同学可能经常分不清注册服务器(Registry)和仓库(Repository)的概念。在这里我可以解释下这两个概念的区别:注册服务器是存放仓库的实际服务器,而仓库则可以被理解为一个具体的项目或者目录;注册服务器可以包含很多个仓库,每个仓库又可以包含多个镜像。例如我的镜像地址为 docker.io/centos,docker.io 是注册服务器,centos 是仓库名。 它们之间的关系如图 1 所示。
按照类型,我们将镜像仓库分为公共镜像仓库和私有镜像仓库。
公共镜像仓库一般是 Docker 官方或者其他第三方组织(阿里云,腾讯云,网易云等)提供的,允许所有人注册和使用的镜像仓库。
Docker Hub 是全球最大的镜像市场,目前已经有超过 10w 个容器镜像,这些容器镜像主要来自软件供应商、开源组织和社区。大部分的操作系统镜像和软件镜像都可以直接在 Docker Hub 下载并使用。
图 2 Docker Hub 镜像
下面我以 Docker Hub 为例,教你如何使用公共镜像仓库分发和存储镜像。
我们首先访问Docker Hub官网,点击注册按钮进入注册账号界面。
图 3 注册 Docker Hub 账号
注册完成后,我们可以点击创建仓库,新建一个仓库用于推送镜像。
图 4 创建仓库
这里我的账号为 lagoudocker,创建了一个名称为 busybox 的仓库,创建好仓库后我们就可以推送本地镜像到这个仓库里了。下面我通过一个实例来演示一下如何推送镜像到自己的仓库中。
首先我们使用以下命令拉取 busybox 镜像:
$ docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
Digest: sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Status: Image is up to date for busybox:latest
docker.io/library/busybox:latest
在推送镜像仓库前,我们需要使用docker login
命令先登录一下镜像服务器,因为只有已经登录的用户才可以推送镜像到仓库。
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: lagoudocker
Password:
Login Succeeded
使用docker login
命令登录镜像服务器,这时 Docker 会要求我们输入用户名和密码,输入我们刚才注册的账号和密码,看到Login Succeeded
表示登录成功。登录成功后就可以推送镜像到自己创建的仓库了。
docker login
命令默认会请求 Docker Hub,如果你想登录第三方镜像仓库或者自建的镜像仓库,在docker login
后面加上注册服务器即可。例如我们想登录访问阿里云镜像服务器,则使用docker login registry.cn-beijing.aliyuncs.com
,输入阿里云镜像服务的用户名密码即可。
在本地镜像推送到自定义仓库前,我们需要先把镜像“重命名”一下,才能正确推送到自己创建的镜像仓库中,使用docker tag
命令将镜像“重命名”:
$ docker tag busybox lagoudocker/busybox
镜像“重命名”后使用docker push
命令就可以推送镜像到自己创建的仓库中了。
$ docker push lagoudocker/busybox
The push refers to repository [docker.io/lagoudocker/busybox]
514c3a3e64d4: Mounted from library/busybox
latest: digest: sha256:400ee2ed939df769d4681023810d2e4fb9479b8401d97003c710d0e20f7c49c6 size: 527
此时,busybox
这个镜像就被推送到自定义的镜像仓库了。这里我们也可以新建其他的镜像仓库,然后把自己构建的镜像推送到仓库中。
有时候,出于安全或保密的需求,你可能想要搭建一个自己的镜像仓库,下面我带你一步一步构建一个私有的镜像仓库。
Docker 官方提供了开源的镜像仓库 Distribution,并且镜像存放在 Docker Hub 的 Registry 仓库下供我们下载。
我们可以使用以下命令启动一个本地镜像仓库:
$ docker run -d -p 5000:5000 --name registry registry:2.7
Unable to find image 'registry:2.7' locally
2.7: Pulling from library/registry
cbdbe7a5bc2a: Pull complete
47112e65547d: Pull complete
46bcb632e506: Pull complete
c1cc712bcecd: Pull complete
3db6272dcbfa: Pull complete
Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d
Status: Downloaded newer image for registry:2.7
d7e449a8a93e71c9a7d99c67470bd7e7a723eee5ae97b3f7a2a8a1cf25982cc3
使用docker ps
命令查看一下刚才启动的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d7e449a8a93e registry:2.7 "/entrypoint.sh /etc…" 50 seconds ago Up 49 seconds 0.0.0.0:5000->5000/tcp registry
此时我们就拥有了一个私有镜像仓库,访问地址为localhost
,端口号为 5000。
我们依旧使用 busybox 镜像举例。首先我们使用docker tag
命令把 busybox 镜像"重命名"为localhost:5000/busybox
$ docker tag busybox localhost:5000/busybox
此时 Docker 为busybox
镜像创建了一个别名localhost:5000/busybox
,localhost:5000
为主机名和端口,Docker 将会把镜像推送到这个地址。
使用docker push
推送镜像到本地仓库:
$ docker push localhost:5000/busybox
The push refers to repository [localhost:5000/busybox]
514c3a3e64d4: Layer already exists
latest: digest: sha256:400ee2ed939df769d4681023810d2e4fb9479b8401d97003c710d0e20f7c49c6 size: 527
这里可以看到,我们已经可以把busybox
推送到了本地镜像仓库。
此时,我们验证一下从本地镜像仓库拉取镜像。首先,我们删除本地的busybox
和localhost:5000/busybox
镜像。
$ docker rmi busybox localhost:5000/busybox
Untagged: busybox:latest
Untagged: busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Untagged: localhost:5000/busybox:latest
Untagged: localhost:5000/busybox@sha256:400ee2ed939df769d4681023810d2e4fb9479b8401d97003c710d0e20f7c49c6
查看一下本地busybox
镜像:
$ docker image ls busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
可以看到此时本地已经没有busybox
这个镜像了。下面,我们从本地镜像仓库拉取busybox
镜像:
$ docker pull localhost:5000/busybox
Using default tag: latest
latest: Pulling from busybox
Digest: sha256:400ee2ed939df769d4681023810d2e4fb9479b8401d97003c710d0e20f7c49c6
Status: Downloaded newer image for localhost:5000/busybox:latest
localhost:5000/busybox:latest
然后再使用docker image ls busybox
命令,这时可以看到我们已经成功从私有镜像仓库拉取busybox
镜像到本地了
我们知道,容器是无状态的。上面私有仓库的启动方式可能会导致镜像丢失,因为我们并没有把仓库的数据信息持久化到主机磁盘上,这在生产环境中是无法接受的。下面我们使用以下命令将镜像持久化到主机目录:
$ docker run -v /var/lib/registry/data:/var/lib/registry -d -p 5000:5000 --name registry registry:2.7
我们在上面启动registry
的命令中加入了-v /var/lib/registry/data:/var/lib/registry
,-v
的含义是把 Docker 容器的某个目录或文件挂载到主机上,保证容器被重建后数据不丢失。-v
参数冒号前面为主机目录,冒号后面为容器内目录。
事实上,registry 的持久化存储除了支持本地文件系统还支持很多种类型,例如 S3、Google Cloud Platform、Microsoft Azure Blob Storage Service 等多种存储类型。
到这里我们的镜像仓库虽然可以本地访问和拉取,但是如果你在另外一台机器上是无法通过 Docker 访问到这个镜像仓库的,因为 Docker 要求非localhost
访问的镜像仓库必须使用 HTTPS,这时候就需要构建外部可访问的镜像仓库。
要构建一个支持 HTTPS 访问的安全镜像仓库,需要满足以下两个条件:
拥有一个合法的域名,并且可以正确解析到镜像服务器;
从证书颁发机构(CA)获取一个证书。
在准备好域名和证书后,就可以部署我们的镜像服务器了。这里我以regisry.lagoudocker.io
这个域名为例。首先准备存放证书的目录/var/lib/registry/certs
,然后把申请到的证书私钥和公钥分别放到该目录下。 假设我们申请到的证书文件分别为regisry.lagoudocker.io.crt
和regisry.lagoudocker.io.key
。
如果上一步启动的仓库容器还在运行,我们需要先停止并删除它。
$ docker stop registry && docker rm registry
然后使用以下命令启动新的镜像仓库:
$ docker run -d \
--name registry \
-v "/var/lib/registry/data:/var/lib/registry \
-v "/var/lib/registry/certs:/certs \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/regisry.lagoudocker.io.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/regisry.lagoudocker.io.key \
-p 443:443 \
registry:2.7
这里,我们使用 -v 参数把镜像数据持久化在/var/lib/registry/data
目录中,同时把主机上的证书文件挂载到了容器的 /certs 目录下,同时通过 -e 参数设置 HTTPS 相关的环境变量参数,最后让仓库在主机上监听 443 端口。
仓库启动后,我们就可以远程推送镜像了。
$ docker tag busybox regisry.lagoudocker.io/busybox
$ docker push regisry.lagoudocker.io/busybox
Docker 官方开源的镜像仓库Distribution
仅满足了镜像存储和管理的功能,用户权限管理相对较弱,并且没有管理界面。
如果你想要构建一个企业的镜像仓库,Harbor 是一个非常不错的解决方案。Harbor 是一个基于Distribution
项目开发的一款企业级镜像管理软件,拥有 RBAC (基于角色的访问控制)、管理用户界面以及审计等非常完善的功能。目前已经从 CNCF 毕业,这代表它已经有了非常高的软件成熟度。
图 5 Harbor 官网
Harbor 的使命是成为 Kubernetes 信任的云原生镜像仓库。 Harbor 需要结合 Kubernetes 才能发挥其最大价值,因此,在这里我就不展开介绍 Harbor 了。如果你对 Harbor 构建企业级镜像仓库感兴趣,可以到它的官网了解更多。
到此,相信你不仅可以使用公共镜像仓库存储和拉取镜像,还可以自己动手搭建一个私有的镜像仓库。那当你使用 Docker Hub 拉取镜像很慢的时候,你知道如何加快镜像的拉取速度吗?思考后,可以把你的想法写在留言区。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。