目录
使用Docker 镜像和仓库
上一篇文章中,我们学习了包括 docker run 在内的许多对容器进行操作的基本指令,那么在本节中,我们主要探讨 Docker 镜像的一些概念,比如什么是镜像,如何对镜像进行管理,如何修改镜像,如何创建、存储、共享自己创建的镜像等,那么就开始我们的学习
什么是 Docker 镜像
Docker 镜像是由文件系统叠加而成,最底端是一个引导文件系统,也就是bootfs
,这很像典型的 Linux/Unix 的引导文件系统。Docker 用户永远不会和引导文件系统有什么交互。实际上,一个容器启动后,它就会被移入内容,而引导文件系统则会被卸载,从而留出更多的空间。(感觉有点像古代的餐馆招待?负责引导顾客进入餐馆,自己的工作就算是完成了)
传统的Linux 引导过程中,root文件系统最先以只读的方式加载,当引导结束后,会切换为读写模式。但是在Docker 中,root文件系统永远只是只读状态,并且使用联合加载的技术一次同时加载多个文件系统。联合加载会将各层系统文件叠加在一起,最终的文件系统包含底层的文件和目录。
联合加载:联合加载指的是一次同时加载多个文件系统,但是外面看起来只有一个文件系统。
Docker 将这样的文件系统成为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像称为父镜像,一次类推,知道镜像栈的最底部,最底部的镜像称为基础镜像。最后,当一个镜像启动容器时,Docker会在镜像的最顶层加载一个文件系统。我们想在 Docker 中运行的程序就是在这个读写层中执行的。
用一幅图来表示一下:
列出 Docker 镜像
我们先从如何列出系统中的 Docker 镜像来开始,可以使用 docker images 命令来实现,如下
可以看到,我们已经获取了一个镜像列表。那么,这些镜像是从哪来的呢?我们执行 docker run 命令时,同时进行了镜像下载
镜像从仓库下载下来。镜像保存在仓库中,而仓库存在于 Registry 中。默认的 Registry 是由 Docker 公司运行的公共 Registry 服务,即 Docker Hub。需要进行ID的注册
Docker Registry 的代码是开源的,你也可以拥有自己的Registry。
在 Docker Hub (或者是你自己运营的 Docker Registry)中,镜像是保存在仓库中的,可以将镜像仓库想象成类似于Git 仓库的东西。它包括镜像、层、以及包括镜像的元数据。
仓库可以包含很多镜像,你可以使用docker pull
来拉取仓库中的镜像,如下
Git 拉取代码的指令是 git pull ,这样就很相似了。
再来使用 docker images 看一下现在有哪些镜像
因为我的 Docker Hub 仓库中只有一个 ubuntu 的镜像,所以图中标红的这个镜像是我们刚从 Docker Hub 上下载下来的。
tag 标签
为了区分同一个仓库中的不同镜像,Docker 为我们提供了 tag 这个标签,每个镜像在列出来的时候都带有一个标签,如12.10、 12.04等,这种标签机制使得一个仓库中允许存储多个镜像。
我们可以在仓库后面加一个冒号:标签名
的方式来指定该仓库中的某一个镜像,例如 docker run -t -i --name new_container ubuntu:12.04 /bin/bash
Docker 会自动帮我们切换到 Ubuntu 的环境下,当然,这种方式创建了一个交互式任务。
在构建容器时指定仓库的标签也是一个好习惯,这样便可以准确的指定容器来源于哪里。
Docker Hub
Docker Hub 有两种仓库,一种是用户仓库,一种是顶层仓库。用户仓库是由开发人员自己创建的,顶层仓库是由Docker Hub 内部人员管理。
用户仓库的命名由两部分构成,如 cxuan/ubuntu
- 用户名 例如 : cxuan
- 仓库名 例如 : ubuntu
相对的,顶层仓库的命名就比较严谨,如 ubuntu 仓库。顶层仓库由 Docker 公司和选定的优质基础镜像厂商来管理,用户可以基于这些镜像构建自己的镜像。
用户镜像都是由爱好者社区自己提供的,没有经过 Docker 公司的认证,所以需要自己承担相应的风险。
拉取镜像
还记得docker run 的启动过程吗?再来一下这张图回顾一下
其实也可以通过 docker pull 命令先预先拉取镜像到本地,使用 docker pull 命令可以节省从一个新镜像启动一个容器所需要的时间。下面就来领取一下fedora
基础镜像( fedora 是 Fedora 优质厂商提供的基础镜像 )
可以使用 docker images 查看新镜像是否拉取到本地,不过我们这次只希望看到 fedora 的镜像,那么你可以使用这个命令: docker images fedora
可以看到我们已经把 fedora 镜像拉取到了本地
查找镜像
我们可以通过 docker search 命令来查找所有 Docker Hub 上公共可用的镜像,如下
下面还有很多镜像,我们主要看一下每条镜像都返回了哪些内容
- 仓库名称
- 镜像描述
- 用户评价 --- 反应一个镜像的受欢迎程度
- 是否官方 --- 是否由Docker 公司及其指定厂商开发的镜像
- 是否自动构建 --- 表示这个镜像是由 Docker Hub 自动构建的
从上面查询的结果中选择一个镜像进行拉取,docker pull jamtur01/puppetmaster
这条命令将会下载 jamtur01/puppetmaster镜像到本地。
接下来就可以使用这个镜像来构建一个容器,下面就用 docker run 命令构建一个容器。
...
查看版本号
构建镜像
上面我们看到如何拉取并且构建好带有定制内容的 Docker 镜像,那么我们如何修改自己的镜像,并且管理和更新这些镜像呢?
- 使用 docker commit 命令
- 使用 docker build 命令和 Dockerfile 文件
现在我们不推荐使用 docker commit 命令,相反应该使用更灵活更强大的 Dockerfile 来构建镜像。不过,为了对 Docker 又一个更深的了解,我们还是会先介绍一下 docker commit 构建镜像。之后,我们重点介绍Docker 所推荐的构建方法:编写 Dockerfile 之后使用 docker build
命令。
创建Docker Hub 账号
构建镜像中很重要的一环就是如何共享和发布镜像。可以将镜像推送到 Docker Hub中或者自己的私有 Registry 中。为了完成这项工作,需要在 Docker Hub上创建一个账号
如果你还没有Docker 通行证,在 hub.docker.com 注册一个,记下你的用户名,登录本地计算机上的Docker公共注册表。使用docker login
,输入用户名和密码进行登录
你的个人信息会保存在 $HOME/.dockercfg 文件中
使用 Docker 的commit 命令创建镜像
创建 Docker镜像的第一种方式是使用 docker commit 命令。可以将此想象为我们是在版本控制系统里面提交变更,毕竟这和 git commit 命令真是太像了。
我们先从创建一个容器开始,这个容器基于我们前面见过的 ubuntu 镜像。如下
接下来,我们在 ubuntu 中安装 apache 服务器,使用apt-get -yqq update
和 apt-get -y install apache2
命令。
我们启动了一个容器,并安装了 Apache 服务器,我们会将这个服务器作为 Web 服务器运行,所以我们想把它当前状态保存起来。这样下次启动就不用重新安装了。为了完成这项工作,需要先使用 exit 从 ubuntu 中退出,之后再运行 docker commit 命令。如下
我们看到,在上图所示的 docker commit 命令中,指定了要提交修改过的容器ID(可以通过 docker ps -l -q 命令得到刚刚创建的容器 ID),以及一个镜像仓库和镜像名,这里是 jamtur01/puppetmaster
可以使用 docker images jamtur01/puppetmaster
命令查看刚刚创建的镜像。
可以在提交时指定更多的数据,就和 git 的命令是一样的,使用 docker commit -m
命令
这条命令中我们使用 -m(message) 指定提交信息,同时指定了 --authro 选项,列出镜像作者信息。接着列出了想要提交的容器ID, 最后指定了 jamtur01/puppetmaster ,并为其打上了 webserver 的tag 标签。
可以使用 docker inspect
命令来查看新创建的镜像的详细信息。
使用 Dockerfile 构建镜像
我们并不推荐使用 docker commit 方法来构建镜像。相反,我们推荐使用 Dockerfile
和 docker build
的命令来构建镜像。Dockerfile 使用基于 DSL 语法的指令来构建一个 Docker 镜像,之后使用 docker build 命令基于 Dockerfile 中的指令构建一个新的镜像。
我们的第一个 Dockerfile
下面我们创建一个目录并初始化 Dockerfile,我们创建一个包含简单web服务器的Docker镜像
如上图所示,我们在 /usr/local/docker 目录下创建了一个 static_web
的目录,再创建了一个 Dockerfile 文件。那么这个 static_web 目录就是我们的构建环境。Docker 称此环境为上下文(context)
或者 构建上下文(build context)
,Docker 会在构建镜像时将构建上下文和该上下文中的文件和目录上传到 Docker 守护进程。这样 Docker 守护进程就可以直接访问你镜像中的 代码、文件和数据。
下面是一个通过 Dockerfile 来构建 web 服务器的 Docker 镜像
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/usr/share/nginx/html/index.html
EXPOSE 80
该 Dockerfile 由一系列指令和参数组成。每条指令,如FROM,都必须为大写字母,而且后面要跟随一个参数: FROM ubuntu:14.04。Dockerfile 中的指令会按照顺序由上向下
执行,所以编写 Dockerfile 时,请注意它的顺序。
如果不能使用
:wq
来进行保存的话,请首先使用sudo su
切换到管理员模式,然后就可以保存啦。
每条指令都会创建一个新的镜像层并对镜像进行提交。Docker 大体按照如下流程执行 Dockerfile 指令
- Docker 从基础镜像运行一个容器
- 执行一条指令,对容器作出修改
- 执行类似docker commit 操作,提交一个新的镜像层
- Docker 再基于刚提交的镜像运行一个新容器
- 执行 Dockerfile 中的下一条指令,直到所有指令都执行完毕
从上面可以看出,如果你的Dockerfile 由于某些原因(例如指令失败了)没有正常结束,那么你将得到了一个可以使用的镜像。这对调试很有帮助: 可以基于镜像运行一个具备交互功能的容器,使用最后创建的镜像对你最后失败的指令做出调试
Dockerfile 也支持中文注释,以 # 开头的行都会被认为是注释。Dockerfile 中的第一行就是注释的例子
每个 Dockerfile 的第一行指令都应该是 FROM。FROM指令指定一个已经存在的镜像,后续指令都将基于该镜像进行,这个镜像被称为基础镜像(base image)。
在上面的示例中,我们使用了
ubuntu:14.04
作为新镜像基础镜像。基于这个 Dockerfile 构建的新镜像以 Ubuntu 14.04 操作系统为基础。再运行一个容器时,必须要指明基于哪个基础镜像进行构建。接着指定了
MAINTAINER
指令,这条指令会告诉 Docker 该镜像的作者是谁,以及作者的电子邮件地址,这有助于标示镜像的所有者以及联系方式。在这些指令之后,我们指定了三条
RUN
指令。RUN指令会在当前的镜像中运行指定的命令。在这个例子中,我们通过 RUN 指令更新了已经安装的 APT 仓库,安装了 nginx 包,之后创建了 /usr/share/nginx/html/index.html 文件,该文件由一些简单的示例文本。像前面说的那样,每条RUN指令都会创建一个新的镜像层,如果该命令执行成功,就会将此镜像提交,继续执行下一条指令。默认情况下,RUN指令会在shell里使用命令包装器 /bin/sh -c 来执行。如果是在一个不支持 shell 的平台上运行或者不希望在 shell 中运行,也可以使用 exec 格式的 RUN 指令
如下 : RUN["apt-get", "install", "-y", "nginx"]
在这种方式中,我们使用数组的方式来指定要运行的命令和要传递的参数。
接着设置了
EXPOSE
命令,这条执行告诉 Docker 容器内的应用程序将会使用容器的指定接口。这并不意味着可以自动访问任意容器运行中的服务端口,可以指定多个 EXPOSE 指令向外公开多个端口。
基于 Dockerfile 构建新镜像
执行 docker build
命令时,Dockerfile 中的所有指令都会被执行并且提交,并且在命令成功结束后返回一个新镜像,下面就来看看如何构建一个新镜像。
一定不要忘记最后面有个空格 和.
,也可以在构建镜像的过程中为镜像设置一个标签: 使用方法为“镜像名 : 标签”,如下所示
指令失败时呢?
之前大致介绍了一下指令失败时的执行过程,下面来看一个例子: 假设我们在上面的 Dockerfile 中把 nginx 拼成了 ngnx ,再来构建一遍
我们可以看到,程序出错了,这个时候我们希望去调试一下这次失败。我们使用 docker run 命令来基于这次构建到目前为止已经成功的最后一步创建一个容器,这里它的ID 是 dee85a65a396
,我们可以使用如下命令
docker run -t -i dee85a65a396 /bin/bash
,来恢复到出错之前的镜像,然后重新运行出错的指令apt-get install -y ngnx
,可以看到哪里出错了
但是感觉这个步骤是多余了一些,如果 Dockerfile 中出现了错误,那么 Docker 就会给你提示,用不着重新运行命令来找出问题原因。
Dockerfile 和构建缓存
由于每一步的结果都会作为下一步的基础镜像,所以Docker 构建镜像的过程非常聪明,它会将之前的镜像层作为缓存。
正如上面 Dockerfile 来举例,比如,在我们调试过程中,不需要在第一步和第三步之间做任何修改,因此 Docker 会将之前构建时创建的镜像当作缓存并作为新的开始点。再次构建时,Docker 会直接从第四步开始。当之前的构建步骤没有变化时,这会节省大量的时间。如果第一步到第三步之间有什么变化,则回到第一步开始。
然而,有的时候不希望有缓存的功能,这个时候你需要使用 apt-get update
,那么 Docker 将不会刷新 APT 包的缓存,要想略过缓存,可以使用 docker build
的 --no-cache 标志。
基于构建缓存的 Dockerfile 模版
构建缓存的一个好处就是,我们可以实现简单的 Dockerfile 模版,一般会在 Dockerfile 文件顶部使用相同的指令集模版,比如对 ubuntu ,使用下面的模版
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2019-08-15
RUN apt-get -qq update
我们来分析一下这个新的 Dockerfile :
- 首先,通过 FROM 指令为新镜像设置了一个基础镜像 ubuntu:14.04。
- 接着,使用 MAINTAINER 指令添加了自己的详细信息
- 然后,通过 ENV 指令设置了一个名为 REFRESHED_AT 的环境变量,用来表示最后一次的更新时间
- 最后,使用 RUN 指令运行 apt-get -qq update 命令,该指令会刷新 APT 包的缓存,用来确保每个安装的软件包都在最新版本。
查看新镜像
现在来看一下新构建的镜像,使用 docker images 命令来完成
如果想要了解镜像是如何构建出来的,可以使用 docker history 命令,如下
从结果可以看出镜像构建的每一层都是哪些指令构成的
从新镜像启动容器
我们可以基于新构建的镜像启动新容器,来检查我们的构建工作是否正常
在这里,我们使用 docker run 命令,启动一个 static_web 的容器, -d
表示的是以分离(detached) 的方式在后台运行。这种方式适合 nginx守护进程 这种需要长时间运行的进程。我们也指定了需要在 容器中运行的命令: nginx -g "daemon off;"
,将以前台方式运行 nginx 作为我们的服务器。
我们这里也使用了一个新的 -p 标志,用来控制 Docker 再运行时应该给外部开放哪些端口
- Docker 可以在宿主机上随机选择 49153 --- 65535 之间的一个比较大的端口映射到 80 端口上
- 可以在 Docker 宿主机指定一个具体的端口来映射到 80 端口上
使用 docker ps
查看一下端口分配情况
Docker 把 32769 端口映射到了 80 端口上
也可以通过 docker port
查看端口的映射情况
Dockerfile 指令
Dockerfile 指令比较多,这里我们会对 Dockerfile 单独列一个章节进行说明
将镜像推送至 Docker Hub
镜像构建完毕之后,我们也可以将它上传到 Docker Hub 上面去,这样其他人就能使用这个镜像了。
Docker Hub 的私有仓库是需要收费的
我们可以使用 docker push 命令将镜像推送至 Docker Hub。命令如下
为什么推送不上去?
网上搜索了一下,大概是镜像标签的问题,重新为镜像设置一个标签
然后把这个标签推送上去,相当于就是把镜像推送上去
我们可以在 Docker Hub 上看到我们推送的镜像了
删除镜像
如果不再需要一个镜像了,也可以将它删除,使用 docker rmi
命令来删除一个镜像
该操作只能删除本地镜像,如果你已经推送至 Docker Hub 上,那么你还需要在 Docker Hub 上将其删除
登录 Docker Hub ,直接点下面的链接删除
docker rmi 删除多个容器的方式直接在后面枚举容器即可,中间用空格隔开
总结
本篇文章主要讲述了 Docker 中的镜像和仓库的一些概念和基本用法,那么你是否能回顾起来下面这些内容呢?
- 什么是镜像
- 如何列出Docker中的镜像,tag标签是干什么用的
- 如何拉取远程仓库中的镜像
- 如何查找镜像
- 对于镜像构建,你能想到哪些内容
- 如何推送镜像至 Docker Hub
- 如何删除镜像