深入 Docker:容器和镜像

来源:互联网 发布:十大淘宝服装模特 编辑:程序博客网 时间:2024/05/21 21:33

原文:https://segmentfault.com/a/1190000002766882

Docker 是一个非常有趣的项目。它自己宣称可以减轻部署服务器的难度,当然我相信里面有炒作的成分。但是实际使用后,我觉得 Docker 的表现还是可圈可点的。本篇文章将会从头开始进行操作镜像,同时试图通过实例和文档来解答实际操作 Docker 中遇到的问题。

本文仅仅是试图使用深入讲解 Docker 镜像和容器的基础知识,而不是像 了解 Docker:一种更好的虚拟化方式一样试图总结出 Docker 的所有操作方法。

如果你打算按照本文操作的话,那么你首先有台安装好 Docker 的 Linux 主机。使用 Docker Machine 安装 Docker 很简单,但是同时我也推荐使用 Digital Ocean 中已经安装好 Docker 的云主机直接操作。本篇文章所使用的 Docker 版本为 1.6.0,同时文中所有的命令都需要 root 权限。

技巧

同样,译者在正文开始提供一个操作 Docker 的小知识。下文中也会多次使用到这一点。

Docker 会为所有已经运行(包括已经停止)的容器随机分配一个唯一的名字和一个唯一的 ID,docker 命令可以识别 ID,也可以识别这个名字。

如图所示,第一行的容器的 ID 是 43de70a54ec1,名字是 admiring_ardinghelli
想删除第一行对应的容器,我们只需要 docker rm 43de70a54ec1,或者简写成 docker rm 43de,或者 docker rm admiring_ardinghelli

容器去哪儿了?

刚开始使用 Docker 的用户会发现,运行完一个容器,再次运行这个容器,原来的容器内的内容已经消失了,例如:

现在我们使用 -i(交互式)和 -t(临时终端)参数运行一个容器,然后输入一些交互命令:

(HOST) # docker run -it ubuntu /bin/bash(CONTAINER) root@1f608dc4e5b4:/# echo hello docker > /message.txt(CONTAINER) root@1f608dc4e5b4:/# cat /message.txthello docker(CONTAINER) root@1f608dc4e5b4:/# exit

在上面那个容器内,我们创建了 /message.txt 文件,现在我们尝试重新读取这个文件:

(HOST) # docker run -it ubuntu cat /message.txtcat: /message.txt: No such file or directory

刚刚我们明明新建了这个文件,现在怎么没了?
同时,运行 docker ps 列出容器,刚刚那个 1f608dc4e5b4 容器到哪里去了?

好吧,那么我们用 docker ps -a 命令列出所有容器,然后仔细观察一下:

(HOST) # docker ps -aCONTAINER ID        IMAGE                 COMMAND              CREATED             STATUS                      PORTS               NAMESb3d8c3ef31a0        ubuntu:latest         "cat /message.txt"   33 minutes ago      Exited (1) 33 minutes ago                       admiring_lalande1f608dc4e5b4        ubuntu:latest         "/bin/bash"          34 minutes ago      Exited (0) 15 minutes ago                       insane_wright

现在大家应该可以发现问题了:居然有两个不同的容器,一个执行了 /bin/bash,一个执行了 cat /message.txt

在本专栏以往的文章中,多次提到:Docker 使用一个叫做 UnionFS 的层级文件系统进行镜像操作。容器对镜像文件的所有操作均是在虚拟出的“改动层”上进行的。当然对容器而言,UnionFS 和普通的文件系统并无差别,也无法看到任何“改动层”。

每次运行 docker run 命令的时候,Docker 都会指定新建容器,并且为容器自己的改动层。所以我们两次都运行了 ubuntu 镜像,那么我们也将会有两个新的、不同的容器,每个容器也都会有自己独立的的“改动层”。因此,在第一个容器内创建的 /message.txt 文件在第二个容器内是无法访问的。当然,要是能互相访问,那我们就得为 Docker 的安全性担心了。

容器到底去哪儿了?

刚刚的问题是:“容器去哪儿了?”
现在的问题是:“容器到底去哪儿了?我需要里面的 message.txt 文件,怎么才能取出来?”

已经停止的容器中的数据并不会消失,而是被存储在相应的“改动层”中。我们可以通过 docker ps -a 进行查找容器。在这个例子中,我们要找的容器便是执行了 /bin/bash 命令的容器,ID是 1f608dc4e5b4,名称是insane_wright

对比起 ID,容器的名称更加易读。因此在本篇文章后面的例子中,我都将用名称来进行操作和识别容器。当然,你也可以在 docker run 的时候用 --name 参数指定容器的名称。

那么现在我们现在运行上面例子中已经停止的 insane_wright 容器:

(HOST) # docker start -ai insane_wright(CONTAINER) root@1f608dc4e5b4:/# cat /message.txthello docker(CONTAINER) root@1f608dc4e5b4:/# exit

使用 -a 参数将容器的输出导出到终端,同时使用 -i 参数进行交互式的操作。这条命令可以让我们继续运行容器 insane_wright,现在你应该能看得到我们刚刚创建的 /message.txt 文件了。

容器能不能提取出来?

现在我们已经运行了自己的容器,容器内也有独立的数据,我们也知道如何获取容器内的数据,那么问题来了:我们能不能把这个状态下的容器给保存起来?

本专栏前面的文章提到过得益于 UnionFS,对于 Docker 来说,其实容器和镜像的差别并不大。容器可以认为是已经运行过的或正在运行的镜像,只不过是镜像上面添加了几个改动层。当然,大部分镜像也是如此。例如某些 mysql 镜像,便仅仅是在官方 ubuntu 镜像的基础上增加了一个 mysql 改动层。

对于上面例子中的容器,我们可以用下面这条命令将其打包成镜像:

(HOST) $ docker commit -a "Jordan Bach" -m "saved my message" insane_wright jbgo/message:v0.0.11c9195e4c24c894a274f857a60b46fb828ee70ff0e78d18017dfb79c5bf68409

本条命令将容器 insange_wright 的改动层的属性变为“只读”,并且指定存储为 jbgo/message 镜像。镜像基于原 ubuntu 镜像,增加了一个改动层,标签为 v0.0.1。标签可以为任意,当然一般用来记录版本信息。

当然,如果你打算自己尝试的话,记得将我的名字 jbgo 替换为你自己在 Docker Hub 上的用户名。即便你没有 Docker Hub 账户,也要养成给镜像做签名的习惯。

可以用如下命令检查当前的镜像:

(HOST) # docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZEjbgo/message        v0.0.1              1c9195e4c24c        18 seconds ago      188.3 MBubuntu              latest              b7cf8f0d9e82        4 days ago          188.3 MB

现在我们有两个镜像了:一个是原有的 ubuntu 镜像,镜像是我们在刚开始进行 docker run 的时候自动从Docker Hub 上面下载下来的;另一个便是我们刚刚保存的,有一个 /message.txt 文件的镜像。

不信?
那我运行给你看。

(HOST) # docker run -it jbgo/message:v0.0.1 cat /message.txthello docker

这里我们并没有运行刚刚的 insane_wright 容器,而是运行了我们刚刚保存的镜像。
我们确实把刚刚的容器保存为一个新的镜像了。

还不信?
那我检查给你看。

(HOST) # docker ps -aCONTAINER ID        IMAGE                 COMMAND              CREATED             STATUS                      PORTS               NAMES01bd4c098203        jbgo/message:v0.0.1   "cat /message.txt"   10 seconds ago      Exited (0) 9 seconds ago                        hopeful_lovelaceb3d8c3ef31a0        ubuntu:latest         "cat /message.txt"   33 minutes ago      Exited (1) 33 minutes ago                       admiring_lalande1f608dc4e5b4        ubuntu:latest         "/bin/bash"          34 minutes ago      Exited (0) 15 minutes ago                       insane_wright

容器运行记录里面多了一个叫做 hopeful_lovelace 的容器,是运行 jdgo/message:v0.0.1 镜像而创建的这个容器。

如何备份镜像?

上面我们创建了带有一个 /message.txt 文件的镜像,也用它运行了容器,那么如果 Docker 宿主机崩溃了或者文件消失了怎么办?如何保证我们创建的镜像在系统崩溃后仍然不丢失?

我们需要 Docker Registry,可以存储和下载镜像的地方。Docker Hub 官方提供了 Docker Hub Registry 让我们来存储镜像,当然我们也可以自己搭建 Docker Registry。本篇文章将会使用 Docker Hub Registry,如果你想按照本文中继续操作的话,记得将我的名字 jbgo 换成你自己在 Docker Hub 上的用户名。

使用如下操作将镜像上传到 Docker Registry 上:

(HOST) # docker push jbgo/messageThe push refers to a repository [jbgo/message] (len: 1)1c9195e4c24c: Image push failedPlease login prior to push:Username: jbgoPassword:Email: jordanbach@gmail.comWARNING: login credentials saved in /root/.dockercfg.Login SucceededThe push refers to a repository [jbgo/message] (len: 1)1c9195e4c24c: Image already existsb7cf8f0d9e82: Image successfully pushed2c014f14d3d9: Image successfully pusheda62a42e77c9c: Image successfully pushed706766fe1019: Image successfully pushedDigest: sha256:b2a98b19e06a4df91150f8a2cd47a6e440cbc13b39f1fc235d46f97f631ad117

因为我是第一次在本机执行 docker push 操作,所以 Docker Registry 需要验证我的身份。

本次上传的镜像被保存在这里。
你可以使用 docker pull jbgo/message 来下载我的镜像。

当然,如果你按照上面的来操作的话,可能会出现一点小问题:

(HOST) # docker pull jbgo/messagePulling repository jbgo/messageFATA[0007] Tag latest not found in repository jbgo/message

下面我来解释一下到底出了什么问题:
当你使用 docker push jbgo/message 命令的时候,默认上传标签为 latest 的镜像。如果没有便会报错。你可以采用如下命令解决: docker tag jbgo/message:v0.0.1 jbgo/message:latest
这次再尝试推送:

(HOST) # docker push jbgo/message:latestThe push refers to a repository [jbgo/message] (len: 1)1c9195e4c24c: Image already existsb7cf8f0d9e82: Image already exists2c014f14d3d9: Image already existsa62a42e77c9c: Image already exists706766fe1019: Image already existsDigest: sha256:cc2fbbb2029c6402cea639b2454da08ef05672da81176ae97f57d4f51be19fc3

这次上传会快得多,因为服务器上已经有了这个镜像,我们上传的仅仅是相同镜像的不同标签而已。

你不信?
那我验证给你看:

(HOST) # docker pull jbgo/messagelatest: Pulling from jbgo/message1c9195e4c24c: Already exists706766fe1019: Already existsa62a42e77c9c: Already exists2c014f14d3d9: Already existsb7cf8f0d9e82: Already existsDigest: sha256:cc2fbbb2029c6402cea639b2454da08ef05672da81176ae97f57d4f51be19fc3Status: Downloaded newer image for jbgo/message:latest

当然,别台服务器一样可以使用这个命令下载我的镜像。

当然,也可以使用如下命令上传特定标签的镜像:docker push jbgo/message:v0.0.1
建议使用版本标签标记镜像,并推送特定版本标签的镜像和 latest 标签的镜像

使用完毕的容器如何处理?

上面只是介绍了容器如何理解容器和镜像,如果有很多历史容器被保存在硬盘上,想要释放掉这些空间,我们应该怎么做?

使用如下命令删除多个容器:

(HOST) # docker rm hopeful_lovelace insane_wright admiring_lalandehopeful_lovelaceinsane_wrightadmiring_lalande

现在再检查一下还有没有这些容器:

(HOST) # docker ps -aCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

我们运行的所有容器和所有的历史容器都会被保存在硬盘上,除非你手动 docker rm 它们。手动删除的容器不能恢复。

为什么上传了不止一个镜像?

有心人注意到,刚刚我们第一次上传 jbgo/message 镜像的时候,Docker 上传了不止一个镜像。

(HOST) # docker push jbgo/message:v0.0.1...1c9195e4c24c: Image already existsb7cf8f0d9e82: Image successfully pushed2c014f14d3d9: Image successfully pusheda62a42e77c9c: Image successfully pushed706766fe1019: Image successfully pushed

到底发生了什么?

首先使用 docker history 检查一下镜像改动历史:

(HOST) # docker history jbgo/message:v0.0.1IMAGE               CREATED             CREATED BY                                      SIZE1c9195e4c24c        33 minutes ago      /bin/bash                                       108 Bb7cf8f0d9e82        4 days ago          /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B2c014f14d3d9        4 days ago          /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kBa62a42e77c9c        4 days ago          /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   194.5 kB706766fe1019        4 days ago          /bin/sh -c #(nop) ADD file:777fad733fc954c0c1   188.1 MB

会发现,其实我们创建了不止一个镜像,也就是有不止一个改动层。
使用 docker inspect 命令仔细观察第一个镜像(第一层)进行过的改动:

(HOST) # docker inspect 1c9195e4c24c[{    "Architecture": "amd64",    "Author": "Jordan Bach",    "Comment": "saved my message",    "Config": {

这个肯定就是我们的 /message.txt 改动层了,那么其他的那么多是什么?

jbgo/message 是基于 ubuntu 镜像的,我们来检查一下 ubuntu 镜像的改动历史:

(HOST) # docker history ubuntuIMAGE               CREATED             CREATED BY                                      SIZEb7cf8f0d9e82        4 days ago          /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B2c014f14d3d9        4 days ago          /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kBa62a42e77c9c        4 days ago          /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   194.5 kB706766fe1019        4 days ago          /bin/sh -c #(nop) ADD file:777fad733fc954c0c1   188.1 MB

那么这样一切就很明确了:那些是 ubuntu 镜像的改动历史。为了确保本地和上传到 Docker Hub 的镜像一致,我们上传到 Docker Hub 中的镜像便是包含着五层改动层的新镜像。

 如何删除镜像?

前面提到过,我们删除容器,那么我们本机上仍然保存着镜像,如何删掉它们?

(HOST) # docker rmi jbgo/messageError response from daemon: Conflict, cannot delete 1c9195e4c24c because the container 2ea39e64a130 is using it, use -f to forceFATA[0000] Error: failed to remove one or more images

提示出错了,根据错误信息可以看出,还有容器在用这一个镜像。

但是我们没有正在运行着的容器啊,到底哪儿出错了?

检查所有容器,发现容器 2ea39e64a130

(HOST) # docker ps -aCONTAINER ID        IMAGE                 COMMAND              CREATED             STATUS                     PORTS               NAMES2ea39e64a130        jbgo/message:latest   "cat /message.txt"   2 minutes ago       Exited (0) 2 minutes ago                       lonely_jones

当然,这是 Docker 的机制:如果你仍然有容器的历史记录,那么为了确保你能再次启动这个容器,这些镜像是不能删除的。

首先删除掉这个容器:docker rm lonely_jones

然后删除镜像:

(HOST) # docker rmi jbgo/messageUntagged: jbgo/message:latestDeleted: 1c9195e4c24c894a274f857a60b46fb828ee70ff0e78d18017dfb79c5bf68409Deleted: b7cf8f0d9e82c9d96bd7afd22c600bfdb86b8d66c50d29164e5ad2fb02f7187bDeleted: 2c014f14d3d95811df672ddae2af376f9557f6b8f5623e3e3f8c5ca3f6ff42e6Deleted: a62a42e77c9c3626118dc411092d23cf658220261acdafe21a7589a8eeba627eDeleted: 706766fe101906a1a6628173c2677173a5f8c6c469075083f3cf3a8f5e5eb367

这次我们可以很轻松地删除掉 jbgo/message 镜像了。
确认一下:

(HOST) # docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE确实没了。

后记

本篇文章介绍了容器和镜像的操作实例,当然 Docker 还有更多新奇的功能和只是等待大家去发现。
本专栏仍在继续组织翻译更多的系列文章,希望大家多多关注。


原文:http://www.csdn.net/article/2014-12-15/2823143-Docker-image

玩转Docker镜像


前言

Docker是Docker.Inc公司开源的一个基于轻量级虚拟化技术的容器引擎项目,整个项目基于Go语言开发,并遵从Apache 2.0协议。通过分层镜像标准化和内核虚拟化技术,Docker使得应用开发者和运维工程师可以以统一的方式跨平台发布应用,并且以几乎没有额外开销的情况下提供资源隔离的应用运行环境。由于众多新颖的特性以及项目本身的开放性,Docker在不到两年的时间里迅速获得诸多IT厂商的参与,其中更是包括Google、Microsoft、VMware等业界行业领导者。同时,Docker在开发者社区也是一石激起千层浪,许多如我之码农纷纷开始关注、学习和使用Docker,许多企业,尤其是互联网企业,也在不断加大对Docker的投入,大有掀起一场容器革命之势。

Docker镜像命名解析

镜像是Docker最核心的技术之一,也是应用发布的标准格式。无论你是用docker pull image,或者是在Dockerfile里面写FROM image,从Docker官方Registry下载镜像应该是Docker操作里面最频繁的动作之一了。那么在我们执行docker pull image时背后到底发生了什么呢?在回答这个问题前,我们需要先了解下docker镜像是如何命名的,这也是Docker里面比较容易令人混淆的一块概念:Registry,Repository, Tag and Image。

下面是在本地机器运行docker images的输出结果:


我们可以发现我们常说的“ubuntu”镜像其实不是一个镜像名称,而是代表了一个名为ubuntu的Repository,同时在这个Repository下面有一系列打了tag的Image,Image的标记是一个GUID,为了方便也可以通过Repository:tag来引用。

那么Registry又是什么呢?Registry存储镜像数据,并且提供拉取和上传镜像的功能。Registry中镜像是通过Repository来组织的,而每个Repository又包含了若干个Image。

  • Registry包含一个或多个Repository
  • Repository包含一个或多个Image
  • Image用GUID表示,有一个或多个Tag与之关联
那么在哪里指定Registry呢?让我们再拉取一个更完整命名的镜像吧:


上面我试图去拉取一个ubuntu镜像,并且指定了Registry为我本机搭建的私有Registry。下面是Docker CLI中pull命令的代码片段 (docker/api/client/command.go中的CmdPull函数)


在运行时,上面的taglessRemote变量会被传入localhost:5000/ubuntu。上面代码试图从taglessRemote变量中解析出Registry的地址,在我们的例子中,它是localhost:5000。

那我们回过头再来看看下面这个耳熟能详的pull命令背后的故事吧:


我们跟着上面的示例代码,进一步进入解析函数ResolveRepositoryName的定义代码片段(docker/registry/registry.go)

我们发现,Docker CLI会判断传入的taglessRemote参数的第一部分中是否包含’.’或者':’,如果存在则认为第一部分是Registry地址,否则会使用Docker官方默认的Registry(即index.docker.io其实这里是一个Index Server,和Registry的区别留在后面再去深究吧),即上面代码中高亮的部分。背后的故事还没有结束,如果你向DockerHub上传过镜像,应该记得你上传的镜像名称格式为user-name/repository:tag,这样用户Bob和用户Alice可以有相同名称的Repository,通过用户名前缀作为命名空间隔离,比如Bob/ubuntu和Alice/ubuntu。官方镜像是通过用户名library来区分的,具体代码片段如下(docker/api/client/command.go中的CmdPull函数)

我们回过头再去看Docker命令行中解析Tag的逻辑吧(docker/api/client/command.go中的CmdPull函数):


代码会试着在用户输入的Image名称中找’ : ‘后面的tag,如果不存在,会使用默认的‘DEFAULTTAG’,即‘latest’。

也就是说在我们的例子里面,命令会被解析为下面这样(注意,下面的命令不能直接运行,因为Docker CLI不允许明确指定官方Registry地址)


配置Registry Mirror

Docker之所以这么吸引人,除了它的新颖的技术外,围绕官方Registry(Docker Hub)的生态圈也是相当吸引人眼球的地方。在Docker Hub上你可以很轻松下载到大量已经容器化好的应用镜像,即拉即用。这些镜像中,有些是Docker官方维护的,更多的是众多开发者自发上传分享的。而且你还可以在Docker Hub中绑定你的代码托管系统(目前支持Github和Bitbucket)配置自动生成镜像功能,这样Docker Hub会在你代码更新时自动生成对应的Docker镜像,是不是很方便?

不幸的是Docker Hub并没有在国内放服务器或者用国内的CDN,下载个镜像20分钟最起码,我等码农可耗不起这么长时间,老板正站在身后催着我们搬运代码呢。为了克服跨洋网络延迟,一般有两个解决方案:一是使用私有Registry,另外是使用Registry Mirror,我们下面一一展开聊聊.

方案一就是搭建或者使用现有的私有Registry,通过定期和Docker Hub同步热门的镜像,私有Registry上保存了一些镜像的副本,然后大家可以通过docker pull private-registry.com/user-name/ubuntu:latest,从这个私有Registry上拉取镜像。因为这个方案需要定期同步Docker Hub镜像,因此它比较适合于使用的镜像相对稳定,或者都是私有镜像的场景。而且用户需要显式的映射官方镜像名称到私有镜像名称,私有Registry更多被大家应用在企业内部场景。私有Registry部署也很方便,可以直接在Docker Hub上下载Registry镜像,即拉即用,具体部署可以参考官方文档。

方案二是使用Registry Mirror,它的原理类似于缓存,如果镜像在Mirror中命中则直接返回给客户端,否则从存放镜像的Registry上拉取并自动缓存在Mirror中。最酷的是,是否使用Mirror对Docker使用者来讲是透明的,也就是说在配置Mirror以后,大家可以仍然输入docker pull ubuntu来拉取Docker Hub镜像,除了速度变快了,和以前没有任何区别。

了以更便捷的方式对接Docker Hub生态圈,使用Registry Mirror自然成为我的首选。接下来我就和大家一起看看Docker使用Mirror来拉取镜像的过程。下面的例子,我使用的是由DaoCloud提供的Registry Mirror服务,在申请开通Mirror服务后你会得到一个Mirror地址,然后我们要做的就是把这个地址配置在Docker Server启动脚本中,重启Docker服务后Mirror配置就生效了(如何获得Mirror服务可以参考本篇文章的附录)

Ubuntu下配置Docker Registry Mirror的命令如下:

sudo echo “DOCKER_OPTS=\”\$DOCKER_OPTS –registry-mirror=http://your-id.m.daocloud.io -d\”” >> /etc/default/docker
sudo service docker restart

如果你是用的Boot2Docker,配置命令为:

# 进入Boot2Docker Start Shell,并执行
sudo su
echo “EXTRA_ARGS=\”–registry-mirror=http://your-id.m.daocloud.io\”” >> /var/lib/boot2docker/profile
exit
# 重启Boot2Docker

配置好Registry Mirror后,就可以拉取Docker镜像了,经我测试,使用DaoCloud的Mirror后,拉取常见镜像的速度可以达到1.5M左右,具体速度在你的网络环境可能会略有不同。

我们来看看配置了Registry Mirror后,Docker拉取镜像的过程吧。首先是CLI拉取镜像命令代码片段(docker/api/client/command.go中的CmdPull函数)


首先,Docker CLI会试图获得授权,在我们的例子中会向https://index.docker.io/v1请求认证,认证完成后,认证服务器会返回一个对应的Token。注意,这里用户认证与配置的Registry Mirror完全无关,这样我们就不用担心使用Mirror的安全问题了。接着Docker CLI会调用Docker Server(即Docker daemon程序)的创建镜像命令,Docker Server随之会执行具体的拉取镜像动作,代码片段如下(docker/graph/pull.gopullRepository函数)


从代码中可以发现,如果配置了Registry Mirror,Docker Server会首先从Mirror中拉取镜像,如果Mirror拉取失败会退而求其次从镜像中指定的Registry拉取。大家又可以松口气了,就算配置的Registry Mirror失效,也不会影响用户拉取镜像,只不过速度就。。。

镜像拉下来后,就可以运行容器了


0 0
原创粉丝点击