1、给Docker打标签
现在,用户已经通过提交容器保存了容器的状态,并且还得到了一个代表镜像ID的随机字符串。很显然,记住和管理这些包含大量数字的镜像ID是非常困难的。如果能够利用Docker的打标签功能给这些镜像赋予一些可读的名称(和标签)的话那就太给力了,它还能提醒用户为什么创建这些镜像。掌握这一技巧可以对镜像的用途一目了然,使机器上的镜像管理变得简单很多。
问题
想要方便地引用并且保存一次Docker提交。
解决方案
使用docker tag给这次提交命名。
讨论
打标签功能的基本用法是非常简单的:
(1)docker tag命令(2)想要命名的镜像ID(3)想给镜像起的名字上述操作就是给镜像命名,可以用这个名字来引用该镜像,例如:docker run imagename
这可比记住大量包含字母和数字的随机字符串轻松多了!如果想和别人分享镜像,除了设置标签还有其他事情要做。遗憾的是,与标签相关的术语可以说相当混乱。
像镜像(image)、名称(name)和仓库(repository)这些术语在使用时很容易混淆。表3-1给出了这些术语的一些定义。
表3-1 Docker标签的术语
在这个表里面最容易混淆的术语恐怕还是镜像和仓库。
一直以来我们使用的术语镜像其实可以简单地理解成是一组我们从一个容器划分出来的多个分层,但是从技术上来说,一个镜像即是一个递归地指向它的父镜像层的分层。
而仓库则是受托管的,这意味着它会被存放在某个地方(可以是在Docker守护进程,也可以是在一个注册中心)。
此外,仓库是一组打好标签的镜像,它们共同组成了容器的文件系统。这里用Git来进行类比可能有助于理解。当克隆一个Git仓库时,可以签出(check out)所要求的那个点的文件的状态。
这一点可以与镜像类比。该仓库保存了每一次提交时文件的整个历史,可以据此追溯到最初的那次提交。
因此,在最上面一层签出仓库时,其他的分层(或者提交)就都在克隆的那个仓库里了。在实践中,镜像和仓库这两个术语或多或少有些混用,因此不必太担心这一点。
但要注意的是,这些术语都是存在的,并且用起来也的确很相似。到目前为止所看到的内容都是如何给一个镜像ID命名的。令人不解的是,这个名字并不是该镜像的标签,虽说人们经常这样提到它。我们可以根据打标签(动词)和给镜像起名字的那个标签(名词)的行为来区分二者。这个标签(名词)允许用户给镜像的一个特定版本命名。用户可以通过添加标签来管理同一镜像不同版本的引用。
例如,可以把一个版本名称或者提交日期当作标签。带有多个标签的仓库的一个很好的例子便是Ubuntu镜像。如果拉取Ubuntu镜像,然后运行docker images,就会得到与代码清单清单3-9所示类似的输出结果。代码清单3-9 带有多个标签的镜像
上面REPOSITORY一列列出了一组托管的叫作“ubuntu”的分层。通常这指的便是镜像。这里的TAG一列列出了4个不同的名称(trusty、14.04、14.04.1和latest)。IMAGE ID一列列出了几个完全一致的镜像ID。这是因为这些打上不同标签的镜像都是同一个。
这表明用户可以拥有一个带有多个标签的相同镜像ID的仓库。尽管从理论上讲这些标签以后也可以指向不同的镜像ID。例如,如果“trusty”有一个安全更新的话,维护人员一次新的提交可能就会改变镜像ID,然后打上新的“trusty”“14.04.2”“latest”标签。
如果没有指定标签的话默认会给镜像打上一个“latest”标签。
“latest”标签的含义 “latest”标签在Docker里并没有什么特殊的含义——它只是在打标签和拉取镜像时的一个默认值。这并不意味着它是这个镜像设定的最后一个标签。镜像的“latest”标签也可能指向的是该镜像的一个老版本,因为这之后构建的版本可能会被打上一个特定的标签,如“v1.2.3”。
2、在Docker Hub上分享镜像
如果能和其他人分享这些名字(和镜像)的话,给镜像打标签的时候用一些带描述的名字可能会更有帮助。为了满足这一需求,Docker提供了轻松将镜像迁移到其他地方的能力,且Docker公司也创建了Docker Hub这一免费服务以鼓励这样的分享。
Docker Hub所需的账号 要利用本技巧的话,用户将需要一个Docker Hub账号,并且它已经在宿主机上通过运行docker login登录过。如果还没有配置这样的一个账号,可以到http://hub.docker.com上面创建一个。只需要照着注册说明的指示去做即可。
问题
想要公开分享一个Docker镜像。
解决方案
使用Docker Hub注册中心(registry)来分享镜像。
讨论
当讨论标签时,注册中心相关的各种术语可能容易让人混淆。表3-2应该有助于理解这些术语该如何使用。
表3-2 Docker注册中心的术语
正如之前所见,只要喜欢,用户可以给一个镜像打上多个标签。这对复制过来的镜像很有用,如此一来用户便拥有了它的控制权。例如,假设用户在Docker Hub上的用户名是adev。代码清单3-10中的3条命令展示了怎样从Docker Hub上将debian:wheezy镜像复制到自己的账号下。
代码清单3-10 将一个公用镜像复制和推送到adev的Docker Hub账号
docker pull debian:wheezy (1)
docker tag debian:wheezy adev/debian:mywheezy1 (2)
docker push adev/debian:mywheezy1 (3)
(1)从Docker Hub上拉取debian镜像
(2)给wheezy镜像打上自己的用户名(adev)并且指定一个标签(mywheezy1)
(3)推送新创建的标签至此,用户已经得到了一个下载好的Debian wheezy镜像的引用,可以维护、关联或者以它为基础构建其他镜像。如果有可以推送的私有仓库,除了必须在标签的前面指定注册中心的地址外,其他流程是完全一致的。假设有一个仓库放在http://mycorp.private.dockerregistry。代码清单3-11中列出的命令将会为镜像打上标签然后推送到该注册中心。
代码清单3-11 将一个公用镜像复制并推送到adev的私有注册中心
docker pull debian (1)
docker tag debian:wheezy
mycorp.private.dockerregistry/adev/
debian:mywheezy1 (2)
docker push mycorp.private.
dockerregistry/adev/debian:mywheezy1 (3)
(1)从Docker Hub上拉取Debian镜像
(2)用注册中心(mycorp.private.dockerregistry)、用户名(adev)和标签(mywheezy1)给wheezy镜像打上标签
(3)将新创建的标签推送到私有注册中心。需要注意的是,在打标签和推送时都必须指定私有注册中心服务器的地址,这样一来Docker才能确保把它推送到正确的位置上述命令将不会把镜像推送到公有的Docker Hub上,而是会把它推送到私有的仓库里,因此任何人只要可以访问该服务上的资源就能够拉取它。至此,用户已经具备了和其他人分享镜像的能力。这是一个同其他工程师分享工作成果、想法甚至于遇到的一些问题的绝佳办法。
3、在构建时指向特定的镜
在构建过程中绝大部分时间里所引用的将是一些通用的镜像名,如“node”或者“ubuntu”,而且这些用起来可能不会有问题。如果要引用一个镜像名的话,该镜像有可能会在标签保持不变的情况下发生变化。尽管听起来很荒谬,但是的确是这样的!仓库的名字只是一个引用,而它所指向的底层镜像可能会变成不同的。用冒号指定一个标签(如ubuntu:trusty)也没办法消除这一风险,因为像一些安全更新就可以用相同的标签自动地重新构建易受攻击的镜像。
绝大多数时候用户可能是希望这样的——镜像的维护人员可能找到了一个改进以及修补安全漏洞的方法,这通常是一件好事。不过有时候这也会是一个痛点。而这不只是一个理论上的风险:在一些场合下已经发生了这样的事情,它以一种难以调试的方式破坏了持续交付的构建。在使用Docker的初期,那些最受欢迎的镜像会定期地添加和删除软件包(这里面还包括一个令人难忘的回忆,passwd命令居然消失了!),造成之前还正常工作的构建突然崩掉。
问题
想要确保是从一个特定的未做更改的镜像构建。
解决方案
从一个特定的镜像ID构建。
讨论当想要绝对地确定构建时使用的是给定的文件时,可以在Dockerfile里指定一个特定的镜像ID。下面是一个例子(可能在读者的环境里无法正常工作):
FROM 8eaa4ff06b53 (1)
RUN echo "Built from image id:" > /etc/buildinfo (2)
RUN echo "8eaa4ff06b53" >> /etc/buildinfo RUN echo "an ubuntu 14.4.01 image" >> /etc/buildinfo CMD ["echo","/etc/buildinfo"] (3)
(1)从一个指定的镜像(或者分层)ID构建
(2)在这个镜像里运行一个命令,把构建时引用的镜像记录到新镜像的一个文件里
(3)构建的镜像默认会输出记录到/etc/buildinfo文件里的信息
要像这样从一个特定的镜像(或者分层)ID构建的话,镜像ID就必须存储到Docker守护进程本地。Docker注册中心将不会执行任何类型的查找操作来找出Docker Hub上可用镜像的各个分层的镜像ID,也不会在可能配置使用的任何其他注册中心上这样做。
值得一提的是,用户指向的那个镜像是不需要打过标签的——它可以是本地的任意一个镜像分层。用户可以从希望的任何分层开始构建。这在做某些调查或者一些实验性步骤而想要完成Dockerfile的构建分析时也许有用。如果想远程持久化镜像,那么最好是给该镜像打上标签,然后将它推送到远程注册中心里一个受控制的仓库下。
Docker镜像可能会停止工作 值得指出的是,绝大部分要面对的问题都发生在一个之前还工作的Docker镜像突然间就不工作了。通常这是因为一些东西在网络层面有所变动。这其中一个记忆犹新的例子便是,某个早上我们的构建因为apt-get update失败。我们假定这是本地deb缓存的问题,然后尝试调试但是没有成功,直到一位可爱的系统管理员指出我们正在构建的这个特定版本的Ubuntu已经不再被支持了。这意味着apt-get update的网络调用都在返回HTTP错误。
4、进程即环境
看待Docker的视角之一是把它看作将环境变成各个进程的工具。此外,虽说虚拟机同样也可以这样对待,但Docker使这件事情变得更加便捷、高效。为了说明这一点,我们将展示应该怎样加速启动、存储和重建容器状态,这可以做成一些以其他方式(几乎)很难办到的事情——在2048上夺冠!
5、在开发中“保存游戏”的方式
本技巧旨在提供一点儿轻松的调味剂,展示Docker可以怎样用来轻松地恢复状态。如果对2048不是很熟悉的话,不妨把它看作是一个容易上瘾的在板上推数字的游戏。如果想先熟悉一下它,在http://gabrielecirulli.github.io/2048有一个在线的最初版本。
问题为了能够在需要的时候恢复到一个已知的状态,想要定期保存容器的状态。解决方案当不确定是否可以活下来时使用docker commit来“保存游戏”。讨论我们在此之前已经创建了一个单体镜像,用户可以在一个拥有VNC服务器以及Firefox的Docker容器里玩2048。要使用这个镜像的话,用户需要安装一个VNC客户端。热门的实现方案有TigerVNC和VNC Viewer等。如果一个都没有的话,那么在宿主机上的包管理器里快速搜索关键字“vnc client”应该也能得到有用的结果。要启动容器,可以执行代码清单3-12中列出的命令。
代码清单3-12 启动2048容器
$ docker run -d -p 5901:5901 -p 6080:6080 --name win2048
imiell/win2048 (1)
$ vncviewer localhost:1 (2)
先从imiell/win2048镜像运行一个容器,这一步我们已经准备好了❶。我们在后台启动这个容器,然后指定它应该给宿主机开放两个端口(5901和6080)。在容器内部自动启动的VNC服务器将会使用这些端口,还给容器起了一个以后易于使用的名字——win2048。现在可以运行VNC Viewer了(根据安装的情况可执行文件可能会不同),然后指示它连接到本地计算机❷。因为相应的端口已经从容器里暴露出来,连接到本地主机实际上也就是连接到容器。如果宿主机上除了一个标准的桌面外没有X显示,那么localhost后面的:1便是合理的——要是有的话,用户可能就得选择一个不同的数字,然后查阅下VNC Viewer的文档,将VNC端口手动指定为5901。一旦连上了VNC服务器,它就会提示输入密码。这个镜像的VNC密码是vncpass。
我们会看到一个带有Firefox标签页的窗口和一个预先加载的2048的表格。点击它以获取焦点,然后玩到准备好保存游戏为止。要保存游戏的话,得在提交它之后给这个命名好的容器打上一个标签:
$ docker commit win2048 (1)
4ba15c8d337a0a4648884c691919b29891cbbe26cb709c0fde74db832a942083 (2)
$ docker tag 4ba15c8d337 my2048tag:$(date +%s)
在提交win2048容器❶后可以生成一个镜像ID❷,现在想给它赋予一个唯一的名字(因为可能创建了一堆这样的镜像)。为了做到这一点,我们将利用date +%s命令的输出作为镜像名称的一部分,该命令会输出一串从1970年的第一天开始算起的总秒数,提供一个唯一的(我们的目的)、不断增长的值。$(command)语法只是在该位置将内容替换为整个命令的输出。如果愿意的话,也可以手动执行date +%s,取而代之的是,粘贴输出作为镜像名称的一部分。
然后可以继续玩下去,直到输了为止。现在该表演魔术了!我们可以通过代码清单3-13所示的命令返回到存档点。
代码清单3-13 返回到之前的游戏存档
$ docker rm -f win2048 $ docker run -d -p 5901:5901 -p 6080:6080 --name win2048 my2048tag:$mytag
$mytag是在docker images命令里选出的一个标签。重复打标签、删除、运行这几个步骤,直到完成2048为止。