网站开发公司电话,注册网站用于跳转虚拟货币网站违法,企业如何宣传推广,上海设计公司排名榜作者主页#xff1a; 正函数的个人主页 文章收录专栏#xff1a; Docker 欢迎大家点赞 #x1f44d; 收藏 ⭐ 加关注哦#xff01; 使用 Docker 镜像
在之前的介绍中#xff0c;我们知道镜像是 Docker 的三大组件之一。
Docker 运行容器前需要本地存在对应的镜像#x… 作者主页 正函数的个人主页 文章收录专栏 Docker 欢迎大家点赞 收藏 ⭐ 加关注哦 使用 Docker 镜像
在之前的介绍中我们知道镜像是 Docker 的三大组件之一。
Docker 运行容器前需要本地存在对应的镜像如果本地不存在该镜像Docker 会从镜像仓库下载该镜像。
本章将介绍更多关于镜像的内容包括 从仓库获取镜像 管理本地主机上的镜像 介绍镜像实现的基本原理。 一、获取镜像
之前提到过Docker Hub 上有大量的高质量的镜像可以用这里我们就说一下怎么获取这些镜像。
从 Docker 镜像仓库获取镜像的命令是 docker pull。其命令格式为
$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]具体的选项可以通过 docker pull --help 命令看到这里我们说一下镜像名称的格式。
Docker 镜像仓库地址地址的格式一般是 域名/IP[:端口号]。默认地址是 Docker Hub(docker.io)。仓库名如之前所说这里的仓库名是两段式名称即 用户名/软件名。对于 Docker Hub如果不给出用户名则默认为 library也就是官方镜像。
比如
$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
92dc2a97ff99: Pull complete
be13a9d27eb8: Pull complete
c8299583700a: Pull complete
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04上面的命令中没有给出 Docker 镜像仓库地址因此将会从 Docker Hub docker.io获取镜像。而镜像名称是 ubuntu:18.04因此将会获取官方镜像 library/ubuntu 仓库中标签为 18.04 的镜像。docker pull 命令的输出结果最后一行给出了镜像的完整名称即 docker.io/library/ubuntu:18.04。
从下载过程中可以看到我们之前提及的分层存储的概念镜像是由多层存储所构成。下载也是一层层的去下载并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后给出该镜像完整的 sha256 的摘要以确保下载一致性。
在使用上面命令的时候你可能会发现你所看到的层 ID 以及 sha256 的摘要和这里的不一样。这是因为官方镜像是一直在维护的有任何新的 bug或者版本更新都会进行修复再以原来的标签发布这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。
如果从 Docker Hub 下载镜像非常缓慢可以参照 镜像加速器 一节配置加速器。
运行
有了镜像后我们就能够以这个镜像为基础启动并运行一个容器。以上面的 ubuntu:18.04 为例如果我们打算启动里面的 bash 并且进行交互式操作的话可以执行下面的命令。
$ docker run -it --rm ubuntu:18.04 bashroote7009c6ce357:/# cat /etc/os-release
NAMEUbuntu
VERSION18.04.1 LTS (Bionic Beaver)
IDubuntu
ID_LIKEdebian
PRETTY_NAMEUbuntu 18.04.1 LTS
VERSION_ID18.04
HOME_URLhttps://www.ubuntu.com/
SUPPORT_URLhttps://help.ubuntu.com/
BUG_REPORT_URLhttps://bugs.launchpad.net/ubuntu/
PRIVACY_POLICY_URLhttps://www.ubuntu.com/legal/terms-and-policies/privacy-policy
VERSION_CODENAMEbionic
UBUNTU_CODENAMEbionicdocker run 就是运行容器的命令具体格式我们会在 容器 一节进行详细讲解我们这里简要的说明一下上面用到的参数。
-it这是两个参数一个是 -i交互式操作一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果因此我们需要交互式终端。--rm这个参数是说容器退出后随之将其删除。默认情况下为了排障需求退出的容器并不会立即删除除非手动 docker rm。我们这里只是随便执行个命令看看结果不需要排障和保留结果因此使用 --rm 可以避免浪费空间。ubuntu:18.04这是指用 ubuntu:18.04 镜像为基础来启动容器。bash放在镜像名后的是 命令这里我们希望有个交互式 Shell因此用的是 bash。
进入容器后我们可以在 Shell 下操作执行任何所需的命令。这里我们执行了 cat /etc/os-release这是 Linux 常用的查看当前系统版本的命令从返回的结果可以看到容器内是 Ubuntu 18.04.1 LTS 系统。
最后我们通过 exit 退出了这个容器。 二、列出镜像
要想列出已经下载下来的镜像可以使用 docker image ls 命令。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB
mongo 3.2 fe9198c04d62 5 days ago 342 MB
none none 00285df0df87 5 days ago 342 MB
ubuntu 18.04 329ed837d508 3 days ago 63.3MB
ubuntu bionic 329ed837d508 3 days ago 63.3MB列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间。
其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID 则是镜像的唯一标识一个镜像可以对应多个 标签。因此在上面的例子中我们可以看到 ubuntu:18.04 和 ubuntu:bionic 拥有相同的 ID因为它们对应的是同一个镜像。
镜像体积
如果仔细观察会注意到这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。比如ubuntu:18.04 镜像大小在这里是 63.3MB但是在 Docker Hub 显示的却是 25.47 MB。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 docker image ls 显示的是镜像下载到本地后展开的大小准确说是展开后的各层所占空间的总和因为镜像到本地后查看空间的时候更关心的是本地磁盘空间占用的大小。
另外一个需要注意的问题是docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构并且可以继承、复用因此不同镜像可能会因为使用相同的基础镜像从而拥有共同的层。由于 Docker 使用 Union FS相同的层只需要保存一份即可因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
你可以通过 docker system df 命令来便捷的查看镜像、容器、数据卷所占用的空间。
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 0 1.992GB 1.992GB (100%)
Containers 1 0 62.82MB 62.82MB (100%)
Local Volumes 9 0 652.2MB 652.2MB (100%)
Build Cache 0B 0B虚悬镜像
上面的镜像列表中还可以看到一个特殊的镜像这个镜像既没有仓库名也没有标签均为 none。
none none 00285df0df87 5 days ago 342 MB这个镜像原本是有镜像名和标签的原来为 mongo:3.2随着官方镜像维护发布了新版本后重新 docker pull mongo:3.2 时mongo:3.2 这个镜像名被转移到了新下载的镜像身上而旧的镜像上的这个名称则被取消从而成为了 none。除了 docker pull 可能导致这种情况docker build 也同样可以导致这种现象。由于新旧镜像同名旧镜像名称被取消从而出现仓库名、标签均为 none 的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image) 可以用下面的命令专门显示这类镜像
$ docker image ls -f danglingtrue
REPOSITORY TAG IMAGE ID CREATED SIZE
none none 00285df0df87 5 days ago 342 MB一般来说虚悬镜像已经失去了存在的价值是可以随意删除的可以用下面的命令删除。
$ docker image prune中间层镜像
为了加速镜像构建、重复利用资源Docker 会利用 中间层镜像。所以在使用一段时间后可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示顶层镜像如果希望显示包括中间层镜像在内的所有镜像的话需要加 -a 参数。
$ docker image ls -a这样会看到很多无标签的镜像与之前的虚悬镜像不同这些无标签的镜像很多都是中间层镜像是其它镜像所依赖的镜像。这些无标签镜像不应该删除否则会导致上层镜像因为依赖丢失而出错。实际上这些镜像也没必要删除因为之前说过相同的层只会存一遍而这些镜像是别的镜像的依赖因此并不会因为它们被列出来而多存了一份无论如何你也会需要它们。只要删除那些依赖它们的镜像后这些依赖的中间层镜像也会被连带删除。
列出部分镜像
不加任何参数的情况下docker image ls 会列出所有顶层镜像但是有时候我们只希望列出部分镜像。docker image ls 有好几个参数可以帮助做到这个事情。
根据仓库名列出镜像
$ docker image ls ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 329ed837d508 3 days ago 63.3MB
ubuntu bionic 329ed837d508 3 days ago 63.3MB列出特定的某个镜像也就是说指定仓库名和标签
$ docker image ls ubuntu:18.04
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 329ed837d508 3 days ago 63.3MB除此以外docker image ls 还支持强大的过滤器参数 --filter或者简写 -f。之前我们已经看到了使用过滤器来列出虚悬镜像的用法它还有更多的用法。比如我们希望看到在 mongo:3.2 之后建立的镜像可以用下面的命令
$ docker image ls -f sincemongo:3.2
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB想查看某个位置之前的镜像也可以只需要把 since 换成 before 即可。
此外如果镜像构建时定义了 LABEL还可以通过 LABEL 来过滤。
$ docker image ls -f labelcom.example.version0.1
...以特定格式显示
默认情况下docker image ls 会输出一个完整的表格但是我们并非所有时候都会需要这些内容。比如刚才删除虚悬镜像的时候我们需要利用 docker image ls 把所有的虚悬镜像的 ID 列出来然后才可以交给 docker image rm 命令作为参数来删除指定的这些镜像这个时候就用到了 -q 参数。
$ docker image ls -q
5f515359c7f8
05a60462f8ba
fe9198c04d62
00285df0df87
329ed837d508
329ed837d508--filter 配合 -q 产生出指定范围的 ID 列表然后送给另一个 docker 命令作为参数从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见不仅仅是镜像将来我们会在各个命令中看到这类搭配以完成很强大的功能。因此每次在文档看到过滤器后可以多注意一下它们的用法。
另外一些时候我们可能只是对表格的结构不满意希望自己组织列或者不希望有标题这样方便其它程序解析结果等这就用到了 Go 的模板语法。
比如下面的命令会直接列出镜像结果并且只包含镜像ID和仓库名
$ docker image ls --format {{.ID}}: {{.Repository}}
5f515359c7f8: redis
05a60462f8ba: nginx
fe9198c04d62: mongo
00285df0df87: none
329ed837d508: ubuntu
329ed837d508: ubuntu或者打算以表格等距显示并且有标题行和默认一样不过自己定义列
$ docker image ls --format table {{.ID}}\t{{.Repository}}\t{{.Tag}}
IMAGE ID REPOSITORY TAG
5f515359c7f8 redis latest
05a60462f8ba nginx latest
fe9198c04d62 mongo 3.2
00285df0df87 none none
329ed837d508 ubuntu 18.04
329ed837d508 ubuntu bionic三、删除本地镜像
如果要删除本地的镜像可以使用 docker image rm 命令其格式为
$ docker image rm [选项] 镜像1 [镜像2 ...]用 ID、镜像名、摘要删除镜像
其中镜像 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。
比如我们有这么一些镜像
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 0584b3d2cf6d 3 weeks ago 196.5 MB
redis alpine 501ad78535f0 3 weeks ago 21.03 MB
docker latest cf693ec9b5c7 3 weeks ago 105.1 MB
nginx latest e43d811ce2f4 5 weeks ago 181.5 MB我们可以用镜像的完整 ID也称为 长 ID来删除镜像。使用脚本的时候可能会用长 ID但是人工输入就太累了所以更多的时候是用 短 ID 来删除镜像。docker image ls 默认列出的就已经是短 ID 了一般取前3个字符以上只要足够区分于别的镜像就可以了。
比如这里如果我们要删除 redis:alpine 镜像可以执行
$ docker image rm 501
Untagged: redis:alpine
Untagged: redissha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7我们也可以用镜像名也就是 仓库名:标签来删除镜像。
$ docker image rm centos
Untagged: centos:latest
Untagged: centossha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38当然更精确的是使用 镜像摘要 删除镜像。
$ docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB$ docker image rm nodesha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: nodesha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228Untagged 和 Deleted
如果观察上面这几个命令的运行输出信息的话你会注意到删除行为分为两类一类是 Untagged另一类是 Deleted。我们之前介绍过镜像的唯一标识是其 ID 和摘要而一个镜像可以有多个标签。
因此当我们使用上面命令删除镜像的时候实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签因此当我们删除了所指定的标签后可能还有别的标签指向了这个镜像如果是这种情况那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为有可能仅仅是取消了某个标签而已。
当该镜像所有的标签都被取消了该镜像很可能会失去了存在的意义因此会触发删除行为。镜像是多层存储结构因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况依旧不会触发删除该层的行为。直到没有任何层依赖当前层时才会真实的删除当前层。这就是为什么有时候会奇怪为什么明明没有别的标签指向这个镜像但是它还是存在的原因也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的原因。
除了镜像依赖以外还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在即使容器没有运行那么同样不可以删除这个镜像。之前讲过容器是以镜像为基础再加一层容器存储层组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的那么删除必然会导致故障。如果这些容器是不需要的应该先将它们删除然后再来删除镜像。
用 docker image ls 命令来配合
像其它可以承接多个实体的命令一样可以使用 docker image ls -q 来配合使用 docker image rm这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。
比如我们需要删除所有仓库名为 redis 的镜像
$ docker image rm $(docker image ls -q redis)或者删除所有在 mongo:3.2 之前的镜像
$ docker image rm $(docker image ls -q -f beforemongo:3.2)充分利用你的想象力和 Linux 命令行的强大你可以完成很多非常赞的功能。 三、利用 commit 理解镜像构成 注意如果您是初学者您可以暂时跳过后面的内容直接学习 容器 一节。 注意 docker commit 命令除了学习之外还有一些特殊的应用场合比如被入侵后保存现场等。但是不要使用 docker commit 定制镜像定制镜像应该使用 Dockerfile 来完成。如果你想要定制镜像请查看下一小节。
镜像是容器的基础每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求而当这些镜像无法直接满足需求时我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。
回顾一下之前我们学到的知识镜像是多层存储每一层是在前一层的基础上进行的修改而容器同样也是多层存储是在以镜像为基础层在其基础上加一层作为容器运行时的存储层。
现在让我们以定制一个 Web 服务器为例子来讲解镜像是如何构建的。
$ docker run --name webserver -d -p 80:80 nginx这条命令会用 nginx 镜像启动一个容器命名为 webserver并且映射了 80 端口这样我们可以用浏览器去访问这个 nginx 服务器。
如果是在本机运行的 Docker那么可以直接访问http://localhost 如果是在虚拟机、云服务器上安装的 Docker则需要将 localhost 换为虚拟机地址或者实际云服务器地址。
直接用浏览器访问的话我们会看到默认的 Nginx 欢迎页面。 现在假设我们非常不喜欢这个欢迎页面我们希望改成欢迎 Docker 的文字我们可以使用 docker exec 命令进入容器修改其内容。
$ docker exec -it webserver bash
root3729b97e8226:/# echo h1Hello, Docker!/h1 /usr/share/nginx/html/index.html
root3729b97e8226:/# exit
exit我们以交互式终端方式进入 webserver 容器并执行了 bash 命令也就是获得一个可操作的 Shell。
然后我们用 h1Hello, Docker!/h1 覆盖了 /usr/share/nginx/html/index.html 的内容。
现在我们再刷新浏览器的话会发现内容被改变了。 我们修改了容器的文件也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。
$ docker diff webserver
C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp现在我们定制好了变化我们希望能将其保存下来形成镜像。
要知道当我们运行一个容器的时候如果不使用卷的话我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令可以将容器的存储层保存下来成为镜像。换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像。以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化。
docker commit 的语法格式为
docker commit [选项] 容器ID或容器名 [仓库名[:标签]]我们可以用下面的命令将容器保存为镜像
$ docker commit \--author Tao Wang twang2218gmail.com \--message 修改了默认网页 \webserver \nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214其中 --author 是指定修改的作者而 --message 则是记录本次修改的内容。这点和 git 版本控制相似不过这里这些信息可以省略留空。
我们可以在 docker image ls 中看到这个新定制的镜像
$ docker image ls nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 07e334659748 9 seconds ago 181.5 MB
nginx 1.11 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB我们还可以用 docker history 具体查看镜像内的历史记录如果比较 nginx:latest 的历史记录我们会发现新增了我们刚刚提交的这一层。
$ docker history nginx:v2
IMAGE CREATED CREATED BY SIZE COMMENT
07e334659748 54 seconds ago nginx -g daemon off; 95 B 修改了默认网页
e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD [nginx -g daemon 0 B
missing 4 weeks ago /bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp 0 B
missing 4 weeks ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx/ 22 B
missing 4 weeks ago /bin/sh -c apt-key adv --keyserver hkp://pgp. 58.46 MB
missing 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION1.11.5-1 0 B
missing 4 weeks ago /bin/sh -c #(nop) MAINTAINER NGINX Docker Ma 0 B
missing 4 weeks ago /bin/sh -c #(nop) CMD [/bin/bash] 0 B
missing 4 weeks ago /bin/sh -c #(nop) ADD file:23aa4f893e3288698c 123 MB新的镜像定制好后我们可以来运行这个镜像。
docker run --name web2 -d -p 81:80 nginx:v2这里我们命名为新的服务为 web2并且映射到 81 端口。访问 http://localhost:81 看到结果其内容应该和之前修改后的 webserver 一样。
至此我们第一次完成了定制镜像使用的是 docker commit 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉。
慎用 docker commit
使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念但是实际环境中并不会这样使用。
首先如果仔细观察之前的 docker diff webserver 的结果你会发现除了真正想要修改的 /usr/share/nginx/html/index.html 文件外由于命令的执行还有很多文件被改动或添加了。这还仅仅是最简单的操作如果是安装软件包、编译构建那会有大量的无关内容被添加进来将会导致镜像极为臃肿。
此外使用 docker commit 意味着所有对镜像的操作都是黑箱操作生成的镜像也被称为 黑箱镜像换句话说就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像别人根本无从得知。而且即使是这个制作镜像的人过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
而且回顾之前提及的镜像所使用的分层存储的概念除当前层外之前的每一层都是不会发生改变的换句话说任何修改的结果仅仅是在当前层进行标记、添加、修改而不会改动上一层。如果使用 docker commit 制作镜像以及后期修改的话每一次修改都会让镜像更加臃肿一次所删除的上一层的东西并不会丢失会一直如影随形的跟着这个镜像即使根本无法访问到。这会让镜像更加臃肿。 四、使用 Dockerfile 定制镜像
从刚才的 docker commit 的学习中我们可以了解到镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本用这个脚本来构建、定制镜像那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件其内包含了一条条的 指令(Instruction)每一条指令构建一层因此每一条指令的内容就是描述该层应当如何构建。
还以之前定制 nginx 镜像为例这次我们使用 Dockerfile 来定制。
在一个空白目录中建立一个文本文件并命名为 Dockerfile
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile其内容为
FROM nginx
RUN echo h1Hello, Docker!/h1 /usr/share/nginx/html/index.html这个 Dockerfile 很简单一共就两行。涉及到了两条指令FROM 和 RUN。
FROM 指定基础镜像
所谓定制镜像那一定是以一个镜像为基础在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器再进行修改一样基础镜像是必须指定的。而 FROM 就是指定 基础镜像因此一个 Dockerfile 中 FROM 是必备的指令并且必须是第一条指令。
在 Docker Hub 上有非常多的高质量的官方镜像有可以直接拿来使用的服务类的镜像如 nginx、redis、mongo、mysql、httpd、php、tomcat 等也有一些方便开发、构建、运行各种语言应用的镜像如 node、openjdk、python、ruby、golang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
如果没有找到对应服务的镜像官方镜像中还提供了一些更为基础的操作系统镜像如 ubuntu、debian、centos、fedora、alpine 等这些操作系统的软件库为我们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外Docker 还存在一个特殊的镜像名为 scratch。这个镜像是虚拟的概念并不实际存在它表示一个空白的镜像。
FROM scratch
...如果你以 scratch 为基础镜像的话意味着你不以任何镜像为基础接下来所写的指令将作为镜像第一层开始存在。
不以任何系统为基础直接将可执行文件复制进镜像的做法并不罕见对于 Linux 下静态编译的程序来说并不需要有操作系统提供运行时支持所需的一切库都已经在可执行文件里了因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
RUN 执行命令
RUN 指令是用来执行命令行命令的。由于命令行的强大能力RUN 指令在定制镜像时是最常用的指令之一。其格式有两种
shell 格式RUN 命令就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
RUN echo h1Hello, Docker!/h1 /usr/share/nginx/html/index.htmlexec 格式RUN [可执行文件, 参数1, 参数2]这更像是函数调用中的格式。
既然 RUN 就像 Shell 脚本一样可以执行命令那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢比如这样
FROM debian:stretchRUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz http://download.redis.io/releases/redis-5.0.3.tar.gz
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install之前说过Dockerfile 中每一个指令都会建立一层RUN 也不例外。每一个 RUN 的行为就和刚才我们手工建立镜像的过程一样新建立一层在其上执行这些命令执行结束后commit 这一层的修改构成新的镜像。
而上面的这种写法创建了 7 层镜像。这是完全没有意义的而且很多运行时不需要的东西都被装进了镜像里比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像不仅仅增加了构建部署的时间也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。
Union FS 是有最大层数限制的比如 AUFS曾经是最大不得超过 42 层现在是不得超过 127 层。
上面的 Dockerfile 正确的写法应该是这样
FROM debian:stretchRUN set -x; buildDepsgcc libc6-dev make wget \ apt-get update \ apt-get install -y $buildDeps \ wget -O redis.tar.gz http://download.redis.io/releases/redis-5.0.3.tar.gz \ mkdir -p /usr/src/redis \ tar -xzf redis.tar.gz -C /usr/src/redis --strip-components1 \ make -C /usr/src/redis \ make -C /usr/src/redis install \ rm -rf /var/lib/apt/lists/* \ rm redis.tar.gz \ rm -r /usr/src/redis \ apt-get purge -y --auto-remove $buildDeps首先之前所有的命令只有一个目的就是编译、安装 redis 可执行文件。因此没有必要建立很多层这只是一层的事情。因此这里没有使用很多个 RUN 一一对应不同的命令而是仅仅使用一个 RUN 指令并使用 将各个所需命令串联起来。将之前的 7 层简化为了 1 层。在撰写 Dockerfile 的时候要经常提醒自己这并不是在写 Shell 脚本而是在定义每一层该如何构建。
并且这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式以及行首 # 进行注释的格式。良好的格式比如换行、缩进、注释等会让维护、排障更为容易这是一个比较好的习惯。
此外还可以看到这一组命令的最后添加了清理工作的命令删除了为了编译构建所需要的软件清理了所有下载、展开的文件并且还清理了 apt 缓存文件。这是很重要的一步我们之前说过镜像是多层存储每一层的东西并不会在下一层被删除会一直跟随着镜像。因此镜像构建时一定要确保每一层只添加真正需要添加的东西任何无关的东西都应该清理掉。
很多人初学 Docker 制作出了很臃肿的镜像的原因之一就是忘记了每一层构建的最后一定要清理掉无关文件。
构建镜像
好了让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容那么让我们来构建这个镜像吧。
在 Dockerfile 文件所在目录执行
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx--- e43d811ce2f4
Step 2 : RUN echo h1Hello, Docker!/h1 /usr/share/nginx/html/index.html--- Running in 9cdc27646c7b--- 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c从命令的输出结果中我们可以清晰的看到镜像的构建过程。在 Step 2 中如同我们之前所说的那样RUN 指令启动了一个容器 9cdc27646c7b执行了所要求的命令并最后提交了这一层 44aa4490ce2c随后删除了所用到的这个容器 9cdc27646c7b。
这里我们使用了 docker build 命令进行镜像构建。其格式为
docker build [选项] 上下文路径/URL/-在这里我们指定了最终镜像的名称 -t nginx:v3构建成功后我们可以像之前运行 nginx:v2 那样来运行这个镜像其结果会和 nginx:v2 一样。
镜像构建上下文Context
如果注意会看到 docker build 命令最后有一个 .。. 表示当前目录而 Dockerfile 就在当前目录因此不少初学者以为这个路径是在指定 Dockerfile 所在路径这么理解其实是不准确的。如果对应上面的命令格式你可能会发现这是在指定 上下文路径。那么什么是上下文呢
首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker 引擎也就是服务端守护进程和客户端工具。Docker 的引擎提供了一组 REST API被称为 Docker Remote API而如 docker 命令这样的客户端工具则是通过这组 API 与 Docker 引擎交互从而完成各种功能。因此虽然表面上我们好像是在本机执行各种 docker 功能但实际上一切都是使用的远程调用形式在服务端Docker 引擎完成。也因为这种 C/S 设计让我们操作远程服务器的 Docker 引擎变得轻而易举。
当我们进行镜像构建的时候并非所有定制都会通过 RUN 指令完成经常会需要将一些本地文件复制进镜像比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像其实并非在本地构建而是在服务端也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中如何才能让服务端获得本地文件呢
这就引入了上下文的概念。当构建的时候用户会指定构建镜像上下文的路径docker build 命令得知这个路径后会将路径下的所有内容打包然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写
COPY ./package.json /app/这并不是要复制执行 docker build 命令所在的目录下的 package.json也不是复制 Dockerfile 所在目录下的 package.json而是复制 上下文context 目录下的 package.json。
因此COPY 这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 无法工作的原因因为这些路径已经超出了上下文的范围Docker 引擎无法获得这些位置的文件。如果真的需要那些文件应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令 docker build -t nginx:v3 . 中的这个 .实际上是在指定上下文的目录docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
如果观察 docker build 输出我们其实已经看到了这个发送上下文的过程
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...理解构建上下文对于镜像构建是很重要的避免犯一些不应该的错误。比如有些初学者在发现 COPY /opt/xxxx /app 不工作后于是干脆将 Dockerfile 放到了硬盘根目录去构建结果发现 docker build 执行后在发送一个几十 GB 的东西极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘这显然是使用错误。
一般来说应该会将 Dockerfile 置于一个空目录下或者项目根目录下。如果该目录下没有所需文件那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎那么可以用 .gitignore 一样的语法写一个 .dockerignore该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢这是因为在默认情况下如果不额外指定 Dockerfile 的话会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。
这只是默认行为实际上 Dockerfile 的文件名并不要求必须为 Dockerfile而且并不要求必须位于上下文目录中比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。
当然一般大家习惯性的会使用默认的文件名 Dockerfile以及会将其置于镜像构建上下文目录中。
其它 docker build 的用法
直接用 Git repo 进行构建
或许你已经注意到了docker build 还支持从 URL 构建比如可以直接从 Git repo 中构建
# $env:DOCKER_BUILDKIT0
# export DOCKER_BUILDKIT0$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-worldStep 1/3 : FROM scratch---
Step 2/3 : COPY hello /--- ac779757d46e
Step 3/3 : CMD [/hello]--- Running in d2a513a760ed
Removing intermediate container d2a513a760ed--- 038ad4142d2b
Successfully built 038ad4142d2b这行命令指定了构建所需的 Git repo并且指定分支为 master构建目录为 /amd64/hello-world/然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。
用给定的 tar 压缩包构建
$ docker build http://server/context.tar.gz如果所给出的 URL 不是个 Git repo而是个 tar 压缩包那么 Docker 引擎会下载这个包并自动解压缩以其作为上下文开始构建。
从标准输入中读取 Dockerfile 进行构建
docker build - Dockerfile或
cat Dockerfile | docker build -如果标准输入传入的是文本文件则将其视为 Dockerfile并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容它没有上下文因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。
从标准输入中读取上下文压缩包进行构建
$ docker build - context.tar.gz如果发现标准输入的文件格式是 gzip、bzip2 以及 xz 的话将会使其为上下文压缩包直接将其展开将里面视为上下文并开始构建。 六、Dockerfile 指令详解详见下章
我们已经介绍了 FROMRUN还提及了 COPY, ADD其实 Dockerfile 功能很强大它提供了十多个指令。下面我们继续讲解其他的指令。 作者主页 正函数的个人主页 文章收录专栏 Docker 七、多阶段构建
之前的做法
在 Docker 17.05 版本之前我们构建 Docker 镜像时通常会采用两种方式
全部放入一个 Dockerfile
一种方式是将所有的构建过程编包含在一个 Dockerfile 中包括项目及其依赖库的编译、测试、打包等流程这里可能会带来的一些问题 镜像层次多镜像体积较大部署时间变长 源代码存在泄露的风险
例如编写 app.go 文件该程序输出 Hello World!
package mainimport fmtfunc main(){fmt.Printf(Hello World!);
}编写 Dockerfile.one 文件
FROM golang:alpineRUN apk --no-cache add git ca-certificatesWORKDIR /go/src/github.com/go/helloworld/COPY app.go .RUN go get -d -v github.com/go-sql-driver/mysql \ CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o app . \ cp /go/src/github.com/go/helloworld/app /rootWORKDIR /root/CMD [./app]构建镜像
$ docker build -t go/helloworld:1 -f Dockerfile.one .分散到多个 Dockerfile
另一种方式就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包好后再将其拷贝到运行环境中这种方式需要我们编写两个 Dockerfile 和一些编译脚本才能将其两个阶段自动整合起来这种方式虽然可以很好地规避第一种方式存在的风险但明显部署过程较复杂。
例如编写 Dockerfile.build 文件
FROM golang:alpineRUN apk --no-cache add gitWORKDIR /go/src/github.com/go/helloworldCOPY app.go .RUN go get -d -v github.com/go-sql-driver/mysql \ CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o app .编写 Dockerfile.copy 文件
FROM alpine:latestRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY app .CMD [./app]新建 build.sh
#!/bin/sh
echo Building go/helloworld:builddocker build -t go/helloworld:build . -f Dockerfile.builddocker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extractecho Building go/helloworld:2docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app现在运行脚本即可构建镜像
$ chmod x build.sh$ ./build.sh对比两种方式生成的镜像大小
$ docker image lsREPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB使用多阶段构建
为解决以上问题Docker v17.05 开始支持多阶段构建 (multistage builds)。使用多阶段构建我们就可以很容易解决前面提到的问题并且只需要编写一个 Dockerfile
例如编写 Dockerfile 文件
FROM golang:alpine as builderRUN apk --no-cache add gitWORKDIR /go/src/github.com/go/helloworld/RUN go get -d -v github.com/go-sql-driver/mysqlCOPY app.go .RUN CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o app .FROM alpine:latest as prodRUN apk --no-cache add ca-certificatesWORKDIR /root/COPY --from0 /go/src/github.com/go/helloworld/app .CMD [./app]构建镜像
$ docker build -t go/helloworld:3 .对比三个镜像大小
$ docker image lsREPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 d6911ed9c846 7 seconds ago 6.47MB
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB很明显使用多阶段构建的镜像体积小同时也完美解决了上边提到的问题。
只构建某一阶段的镜像
我们可以使用 as 来为某一阶段命名例如
FROM golang:alpine as builder例如当我们只想构建 builder 阶段的镜像时增加 --targetbuilder 参数即可
$ docker build --target builder -t username/imagename:tag .构建时从其他镜像复制文件
上面例子中我们使用 COPY --from0 /go/src/github.com/go/helloworld/app . 从上一阶段的镜像中复制文件我们也可以复制任意镜像中的文件。
$ COPY --fromnginx:latest /etc/nginx/nginx.conf /nginx.conf实战多阶段构建 Laravel 镜像详见下章 八、构建多种系统架构支持的 Docker 镜像 – docker manifest 命令详解
我们知道使用镜像创建一个容器该镜像必须与 Docker 宿主机系统架构一致例如 Linux x86_64 架构的系统中只能使用 Linux x86_64 的镜像创建容器。 Windows、macOS 除外其使用了 binfmt_misc 提供了多种架构支持在 Windows、macOS 系统上 (x86_64) 可以运行 arm 等其他架构的镜像。 例如我们在 Linux x86_64 中构建一个 username/test 镜像。
FROM alpineCMD echo 1构建镜像后推送到 Docker Hub之后我们尝试在树莓派 Linux arm64v8 中使用这个镜像。
$ docker run -it --rm username/test可以发现这个镜像根本获取不到。
要解决这个问题通常采用的做法是通过镜像名区分不同系统架构的镜像例如在 Linux x86_64 和 Linux arm64v8 分别构建 username/test 和 username/arm64v8-test 镜像。运行时使用对应架构的镜像即可。
这样做显得很繁琐那么有没有一种方法让 Docker 引擎根据系统架构自动拉取对应的镜像呢
我们发现在 Linux x86_64 和 Linux arm64v8 架构的计算机中分别使用 golang:alpine 镜像运行容器 $ docker run golang:alpine go version 时容器能够正常的运行。
这是什么原因呢
原因就是 golang:alpine 官方镜像有一个 manifest 列表 (manifest list)。
当用户获取一个镜像时Docker 引擎会首先查找该镜像是否有 manifest 列表如果有的话 Docker 引擎会按照 Docker 运行环境系统及架构查找出对应镜像例如 golang:alpine。如果没有的话会直接获取镜像例如上例中我们构建的 username/test。
我们可以使用 $ docker manifest inspect golang:alpine 查看这个 manifest 列表的结构。
$ docker manifest inspect golang:alpine{schemaVersion: 2,mediaType: application/vnd.docker.distribution.manifest.list.v2json,manifests: [{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:5e28ac423243b187f464d635bcfe1e909f4a31c6c8bce51d0db0a1062bec9e16,platform: {architecture: amd64,os: linux}},{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:2945c46e26c9787da884b4065d1de64cf93a3b81ead1b949843dda1fcd458bae,platform: {architecture: arm,os: linux,variant: v7}},{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:87fff60114fd3402d0c1a7ddf1eea1ded658f171749b57dc782fd33ee2d47b2d,platform: {architecture: arm64,os: linux,variant: v8}},{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:607b43f1d91144f82a9433764e85eb3ccf83f73569552a49bc9788c31b4338de,platform: {architecture: 386,os: linux}},{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:25ead0e21ed5e246ce31e274b98c09aaf548606788ef28eaf375dc8525064314,platform: {architecture: ppc64le,os: linux}},{mediaType: application/vnd.docker.distribution.manifest.v2json,size: 1365,digest: sha256:69f5907fa93ea591175b2c688673775378ed861eeb687776669a48692bb9754d,platform: {architecture: s390x,os: linux}}]
}可以看出 manifest 列表中包含了不同系统架构所对应的镜像 digest 值这样 Docker 就可以在不同的架构中使用相同的 manifest (例如 golang:alpine) 获取对应的镜像。
下面介绍如何使用 $ docker manifest 命令创建并推送 manifest 列表到 Docker Hub。
构建镜像
首先在 Linux x86_64 构建 username/x8664-test 镜像。并在 Linux arm64v8 中构建 username/arm64v8-test 镜像构建好之后推送到 Docker Hub。
创建 manifest 列表
# $ docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]
$ docker manifest create username/test \username/x8664-test \username/arm64v8-test当要修改一个 manifest 列表时可以加入 -a 或 --amend 参数。
设置 manifest 列表
# $ docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST
$ docker manifest annotate username/test \username/x8664-test \--os linux --arch x86_64$ docker manifest annotate username/test \username/arm64v8-test \--os linux --arch arm64 --variant v8这样就配置好了 manifest 列表。
查看 manifest 列表
$ docker manifest inspect username/test推送 manifest 列表
最后我们可以将其推送到 Docker Hub。
$ docker manifest push username/test测试
我们在 Linux x86_64 Linux arm64v8 中分别执行 $ docker run -it --rm username/test 命令发现可以正确的执行。
官方博客
详细了解 manifest 可以阅读官方博客。
https://www.docker.com/blog/multi-arch-all-the-things/ 九、其它制作镜像的方式
除了标准的使用 Dockerfile 生成镜像的方法外由于各种特殊需求和历史原因还提供了一些其它方法用以生成镜像。
从 rootfs 压缩包导入
格式docker import [选项] 文件|URL|- [仓库名[:标签]]
压缩包可以是本地文件、远程 Web 文件甚至是从标准输入中得到。压缩包将会在镜像 / 目录展开并直接作为镜像第一层提交。
比如我们想要创建一个 OpenVZ 的 Ubuntu 16.04 模板的镜像
$ docker import \http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \openvz/ubuntu:16.04Downloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
sha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213这条命令自动下载了 ubuntu-16.04-x86_64.tar.gz 文件并且作为根文件系统展开导入并保存为镜像 openvz/ubuntu:16.04。
导入成功后我们可以用 docker image ls 看到这个导入的镜像
$ docker image ls openvz/ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
openvz/ubuntu 16.04 412b8fc3e3f7 55 seconds ago 505MB如果我们查看其历史的话会看到描述中有导入的文件链接
$ docker history openvz/ubuntu:16.04
IMAGE CREATED CREATED BY SIZE COMMENT
f477a6e18e98 About a minute ago 214.9 MB Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gzDocker 镜像的导入和导出 docker save 和 docker load
Docker 还提供了 docker save 和 docker load 命令用以将镜像保存为一个文件然后传输到另一个位置上再加载进来。这是在没有 Docker Registry 时的做法现在已经不推荐镜像迁移应该直接使用 Docker Registry无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。
保存镜像
使用 docker save 命令可以将镜像保存为归档文件。
比如我们希望保存这个 alpine 镜像。
$ docker image ls alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest baa5d63471ea 5 weeks ago 4.803 MB保存镜像的命令为
$ docker save alpine -o filename
$ file filename
filename: POSIX tar archive这里的 filename 可以为任意名称甚至任意后缀名但文件的本质都是归档文件
注意如果同名则会覆盖没有警告
若使用 gzip 压缩
$ docker save alpine | gzip alpine-latest.tar.gz然后我们将 alpine-latest.tar.gz 文件复制到了到了另一个机器上可以用下面这个命令加载镜像
$ docker load -i alpine-latest.tar.gz
Loaded image: alpine:latest如果我们结合这两个命令以及 ssh 甚至 pv 的话利用 Linux 强大的管道我们可以写一个命令完成从一个机器将镜像迁移到另一个机器并且带进度条的功能
docker save 镜像名 | bzip2 | pv | ssh 用户名主机名 cat | docker load十、镜像的实现原理
Docker 镜像是怎么实现增量的修改和维护的
每个镜像都由很多层次构成Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。
通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
Docker 在 OverlayFS 上构建的容器也是利用了类似的原理。 作者主页 正函数的个人主页 文章收录专栏 Docker 欢迎大家点赞 收藏 ⭐ 加关注哦 如果你认为这篇文章对你有帮助请给正函数点个赞吧如果发现什么问题欢迎评论区留言