Docker实践

978-7-115-47458-2
作者: 【美】伊恩 • 米尔(Ian Miell) 艾丹 • 霍布森 • 塞耶斯(Aidan Hobson Sayers)
译者: 吴佳兴 梁晓勇 黄博文 杨锐
编辑: 杨海玲

图书目录:

详情

本书由浅入深地讲解了Docker,涵盖从开发环境到DevOps流水线再一路到生产环境的整个落地过程。书中介绍Docker的核心概念和架构,以及将Docker和开发环境有机、高效地结合起来的方法,包括用作轻量级的虚拟机以及与构建和宿主机编排、配置管理、精简镜像等。本书还通过“问题/解决方案/讨论”的形式,将等一系列难题拆解成101个相关的实用技巧,为读者提供解决方案以及一些实践经验。

图书摘要

版权信息

书名:Docker实践

ISBN:978-7-115-47458-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] 伊恩 • 米尔(Ian Miell)

       [美] 艾丹 • 霍布森 • 塞耶斯(Aidan Hobson Sayers)

  译    吴佳兴 梁晓勇 黄博文 杨 锐

  责任编辑 杨海玲

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Original English language edition, entitled Docker in Practiceby Ian Miell and Aidan Hobson Sayers published by Manning Publications Co., 209 Bruce Park Avenue, Greenwich, CT 06830. Copyright © 2016 by Manning Publications Co.

Simplified Chinese-language edition copyright © 2018 by Posts & Telecom Press. All rights reserved.

本书中文简体字版由Manning Publications Co.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


本书由浅入深地讲解了Docker的相关内容,涵盖从开发环境到DevOps流水线,再一路到生产环境的整个落地过程以及相关的实用技巧。书中介绍Docker的核心概念和架构,以及将Docker和开发环境有机、高效地结合起来的方法,包括用作轻量级的虚拟机以及构建和宿主机编排、配置管理、精简镜像等。不仅如此,本书还通过“问题/解决方案/讨论”的形式,将“Docker如何融入DevOps流水线”“如何在生产环境落地”等一系列难题拆解成101个相关的实用技巧,为读者提供解决方案以及一些细节和技巧方面的实践经验。阅读本书,读者将学到的不只是Docker,还包括持续集成、持续交付、构建和镜像管理、容器编排等相关领域的一线生产经验。本书编写时一些案例参考的Docker版本是Docker 1.9。

本书要求读者具备一定的容器管理和运维的基础知识,适合想要将Docker投入实践的相关技术人员阅读,尤其适合具有中高级DevOps和运维背景的读者阅读。


吴佳兴,毕业于华东理工大学计算机系,目前是bilibili基础平台研发团队的一员,主要研究方向有CI\CD、监控和运维自动化、基于容器的PaaS平台建设、微服务架构等。2014年年底有幸加入DockOne社区,作为译者,利用闲暇时间为社区贡献一些微薄的力量。个人博客devopstarter.info。欢迎邮件联系(wjx_colstu@hotmail.com)。

黄博文,ThoughtWorks资深软件工程师/咨询师,担任过开发、测试、运维、技术经理等角色,在国内外多家企业做过技术教练以及技术咨询,拥有丰富的敏捷团队工作经验。目前专注于DevOps技术及云端架构,在搭建持续集成及部署平台、自动化构建基础设施、虚拟化环境、云端运维等方面有着丰富的经验。拥有AWS解决方案架构师以及开发者证书。个人博客为www.huangbowen.net,个人邮箱为huangbowen521@gmail.com。译作有《Effective JavaScript》《Html5和CSS3响应式设计指南》《C#多线程编程实战》《面向对象的思考过程》《基础设施即代码》等。

杨锐,前ThoughtWorks咨询师,DevOps领域持续关注者,任职期间曾任某海外大型项目DevOps工程师,对其持续交付、基础设施即代码、流水线即代码等方面进行了持续推动,对云计算、容器化、持续交付等有一定经验。现供职美团点评。

梁晓勇,毕业于厦门大学,现任某互联网金融公司架构师,DockOne社区编外人员。长期奋战在技术研发第一线,在网络管理、技术开发、架构设计等方面略有心得。热爱互联网技术,积极投身开源社区,对Docker等容器技术具有浓厚兴趣。欢迎邮件联系(sunlxy@yahoo.com)。


可能是鄙人的拙见,不过Docker真的相当了不起。

在不久以前应用程序还是一些庞大的单体软件,独自运行在一堆钢铁和硅块上。这样的情况持续了很多年,它们拒绝改变,不思进取。对于那些想要快速前行的组织而言,这的确是一个难题,因此虚拟机的崛起也就不足为奇了。应用程序不必再和这些硬件捆绑在一起,这使得一切可以更快更替,也更加灵活。

但是,虚拟机本身也很复杂。它们在其他的机器上模拟出整台计算机,而这台虚拟计算机仍然是异常复杂并且需要管理的。再者,因为虚拟机更小而且更容易被创建,所以围绕着它们也就产生了更多需要管理的东西。

我们到底该如何管理所有的复杂性呢?通过配置管理可以办到,当然,这又是另外一个极其复杂的系统。

Docker则采取了一种截然不同的做法。如果用户将软件放到一个容器里,它就会将应用程序本身的复杂度与基础设施相隔离开来,这使得基础设施本身可以变得更加简单,而应用程序也更加易于装配。基于这样的组织效率,与虚拟机相比,技术速度和执行效率都有了巨大的飞跃。容器的启动是毫秒级的,而不是分钟级的。内存是共享的,而不是预先分配的。这使得用户的应用程序能够以更低的成本运行,同样也意味着他可以按照想要的方式来架设应用,而不用再受缓慢的、不太灵活的基础设施的制约。

在我第一次见到Solomon Hykes(即“Docker之父”)谈论Docker并将其与装载集装箱做类比的时候,我便意识到他正在做的是一件了不起的大事。全球航运业建立标准之前的混乱状态可以很好地用来类比容器出现以前管理软件的复杂状态。Solomon的观点非常有说服力,我甚至为此开了一家公司,专门围绕Docker构建工具,这家公司最终被Docker公司收购了,并且最终变成了我们现在所熟知的Docker Compose。

初次见到Ian是在伦敦我们组织的某个Docker聚会(meetup)上。当时,我们坚称:“Docker还没有为生产环境做好准备,请不要在生产环境里使用它!”然而,Ian是何许人也,他无视这个看似明智的建议,一意孤行,非要让它运行在生产环境里。那时,他和Aidan一起为OpenBet公司工作,当我看到他们用我们当时的代码处理过的货币数额时,真的让我感到有点儿懵。

Ian和Aidan发现,他们在使用Docker的过程中收获的价值超越了其测试阶段使用它时伴随而来的不便之处。他们在很早的时候便用上了这一技术,因此,对怎样更好地运用它也有自己独到的见解。他们在OpenBet内部构建的工具指出了Docker本身缺失的一些东西,而我们之间私下的一些闲聊也实际影响了我们所采用的设计和产品方向。

Docker自Ian和Aidan第一次使用以来,已然有了一些长足的进步,到如今已有成千上万的组织在用它来解决遇到的真实问题,如更快地装配软件、管理惊人的复杂性、提高基础设施的效率、解决“这个在我机器上运行得好好的”问题等。这促使我们在构建、部署和管理软件方式上的巨大转变,并且围绕着它正在形成一套全新的工具及理念。容器化的美好前景的确是让人兴奋的,但是它同我们之前所习惯的方式也迥然不同。

对读者而言,恐怕很难从这本书中看到怎样由此及彼,但是这本书中涵盖了大量如何应用Docker去解决自己当下遇到的种种问题的实用建议。遵循这些建议,用户的组织将能够快速前行。同时或许更重要的是,构建和部署应用的过程将会变得更有意思。

Ben Firshman

Docker公司产品管理总监、Docker Compose联合创始人


2013年9月,浏览黑客志(Hacker News)的时候,我无意中在《连线》上看到一篇介绍一项叫作“Docker”的新技术的文章。在读到这篇文章时,我便意识到Docker所拥有的革命性的潜力,并为此兴奋不已。

我为之工作了十余年的这家公司一直饱受软件交付速度不够快的困扰。准备环境是一件高成本、费时、手工且十分不优雅的事情。持续集成几乎没有,而且配置开发环境也是一件很考验耐心的事情。由于我的职位含有“DevOps经理”的字样,所以我有特别的动力来解决这些问题!

我通过公司的邮件列表招募了一批积极进取的同事(他们中有一位如今是我的合著者),接着我们的创新团队一起努力,将一个尚处于测试阶段的工具变为商业优势,为公司省去了高昂的虚拟机成本,并且开启了构建和部署软件的新思路。我们甚至构建并开源了一款自动化工具(ShutIt),以满足我们的组织的交付需求。

Docker 为我们提供了一个打包和维护的工具,它解决了很多如果仅靠我们自己根本很难逾越的难题。这是开源技术最棒的地方,它为我们提供了利用业余时间接受挑战的机会,帮助克服技术债务,并且每天都能有收获。我们可以从中学到的不只是Docker,还包括持续集成、持续交付、打包、自动化以及人们该如何应对日新月异的技术革新。

对我们来说,Docker是一个用途异常广泛的工具。只要使用Linux系统来运行软件,Docker便有用武之地。这也使编写这一主题的图书充满了挑战,毕竟我们的视角是落在广袤的软件本身。为了迎合软件生产方式这样一个本质上的变化,Docker生态系统也在飞速地产出新的解决方案,这也使写书的任务变得更加艰巨。随着时间的推移,我们开始逐渐了解这些问题和解决方案的本质,而在本书里,我们将竭尽所能地传达这些经验。这可以帮助读者找出满足自己的特定技术和业务约束场景的解决方案。

在聚会上面发表演讲时,我们也为Docker在愿意接纳它的组织内部迅速高效地投入使用的方法而感到震撼。本书如实讲述了我们是怎样使用Docker的,涵盖了从桌面到DevOps流水线,再一路到生产环境的整个过程。因此,这本书可能会显得不那么正统,但是作为工程师来说,我们相信纯粹性有时候必须让步于实用性,尤其是涉及节约成本方面的话题!本书的所有内容均来源于一线生产的实际经验,我们衷心希望读者可以从我们来之不易的经验中获益。

Ian Miell

如果没有我们最亲近的人的支持、牺牲和耐心,这本书是绝对无法完成的。特别要提到的是Stephen Hazleton,本书的不少内容正是他为了使Docker能够造福我们的客户,同我们一起不懈努力的成果。

几位Docker的贡献者和Docker的员工还非常热情地在不同阶段帮忙审阅了本书的内容,并且提供了许多有价值的反馈,下面几位阅读了本书的初稿:Benoit Benedetti、Burkhard Nestmann、Chad Davis、David Moravec、Ernesto Cárdenas Cangahuala、Fernando Rodrigues、JoséSan Leandro、Kirk Brattkus、Pethuru Raj、Scott Bates、Steven Lembark、Stuart Woodward、Ticean Bennett、Valmiky Arquissandas和 Wil Moore III。

最后,本书很大程度上还归功于Manning出版社的编辑团队,他们用自己的方式推动我们改进,使这本书虽不至于完美,但已然做到尽可能好。我们希望他们会为在我们身上付出的努力而感到自豪。

Ian Miell感谢Sarah、Isaac和Rachel,感谢你们忍受我深夜编程,包容一位紧盯着笔记本屏幕的父亲,并且没完没了地念叨“Docker这Docker那,Docker……”,还要感谢我的父母从小就鼓励我去质疑现状,还给我买Spectrum(腕表)。

Aidan Hobson Sayers感谢Mona的支持和鼓励,感谢我的父母睿智和鼓励的话,还有我的合著者决定命运的那封“有谁试过Docker这种东西?”的电子邮件。


Docker可以说是目前增长速度最快的软件项目。它于2013年3月开源,到2016年它已经获得了近30 000个GitHub star以及超过7500次fork。它还接受了大量像Red Hat、IBM、微软、谷歌、思科和Vmware这些厂商的pull request。

Docker在这个关键时刻的出现正是为了迎合许多软件组织的一个迫切需求:以一种开放和灵活的方式来构建软件,然后在不同环境下能够可靠和一致地部署它。用户不需要学习新的编程语言,购买昂贵的硬件,也无须再为了构建、部署和运行可移植的应用程序而在安装或者配置过程上花费过多工夫。

本书将会通过我们在不同场景下用到的一些技术,带读者领略真实世界里的Docker实践案例。我们已经竭力尝试阐明这些技术,尽可能做到无须在阅读前事先具备其他相关技术的知识背景。我们假定读者了解一些基本的开发技术和概念,如开发一些结构化代码的能力,以及对软件开发和部署流程的一些了解。此外,我们认为读者还应了解一些核心的源代码管理理念并且对像TCP/IP、HTTP和端口这样的网络基础知识有一个基本的了解。其他不怎么主流的技术会在我们介绍到的时候予以说明。

我们将从第一部分介绍Docker的基础知识开始,而到了第二部分,我们将把重点放在介绍如何将Docker用到单台机器的开发环境。在第三部分里,我们将介绍Docker在DevOps流水线中的用法,介绍持续集成、持续交付和测试等内容。本书的最后一部分则覆盖了Docker生产实践的内容,重点关注与编排相关的一些备选方案。

Docker是一个用途广泛、灵活和动态的工具,以至于没有一点儿魄力的话很难追上它快速发展的脚步。我们会尽力通过真实世界的应用和例子让读者更好地理解其中的一些关键概念,目的是希望读者能够有实力、有信心在Docker的生态系统里审慎评估未来采用的工具和技术。我们一直在努力让本书变得更像是一次愉快的旅行,即介绍我们在很多方面见证的Docker是怎样使我们的生活变得更加轻松甚至于更加有趣的。我们正沉浸在Docker以一个别致的方式为我们呈现的覆盖整个软件生命周期的许多有意思的软件技术里,而我们希望本书的读者同样也能分享这样的体验。

本书包括12章,分为4个部分。

第一部分奠定了本书其余部分的基础,介绍Docker的概念并且教读者运行一些基本的Docker命令。第2章的一些内容旨在让读者熟悉Docker的客户-服务器架构以及如何调试它,这对在非常规的Docker配置中定位问题是非常有用的。

第二部分关注熟悉Docker以及在自己的机器上如何充分利用Docker。我们将用到一个读者可能比较熟悉的相关概念——虚拟机,作为第3章的基础,提供Docker使用的一些介绍。然后第 4 章会详细介绍几个我们发现自己每天都在使用的Docker技巧。这一部分的最后一章则探索了更为深入的镜像构建方面的主题。

第三部分从关注Docker在DevOps上下文中的使用开始,从用它完成软件构建和测试的自动化到将它迁移至不同的环境。这一部分还会花一章的篇幅来总结Docker的虚拟网络,介绍Docker Compose,并且覆盖一些更为高级的网络主题,如网络模拟以及Docker网络插件等。

第四部分会介绍几个针对在生产环境中如何有效地利用Docker的主题。这一部分从第9章开始,在这一章里我们调研了一些最主流的容器编排工具,然后指出了它们往往倾向用在哪些场景。第10章讨论的是安全性的重要话题,阐明了如何锁定在容器里运行的进程,以及如何限制访问对外暴露的Docker守护进程。最后两章则会细讲一些在生产环境中运行Docker的重要实用信息。第11章会展示如何将经典的系统管理知识应用到容器上下文中,从登录到资源限制,而第12章着眼于一些读者可能遇到的问题并且给出对应的调试和解决步骤。

附录里则是一些以不同方式安装、使用和配置Docker的具体细节,包括在虚拟机里以及在Windows上。

本书中用到的所有由作者创建的工具、应用以及Docker镜像的源代码都可以在Manning出版社网站下载,地址是www.manning.com/books/docker-in-practice,读者也可以在GitHub上的docker-in-practise组织https://github.com/docker-in-practice/找到这些源代码[1]。Docker Hub上dockerinpractise用户下的镜像(https://hub.docker.com/u/dockerinpractice/)均是从其中一个GitHub仓库自动构建生成的。在这里,我们已经意识到读者可能会有兴趣对技术背后的一些源代码做进一步的研究,因此在技术讨论里也嵌入了相关仓库的链接。

为了方便读者跟进,本书中列出的大量代码均以终端会话的形式,与相应的命令输出一起展示。这些会话里有几件事情要注意一下。

很长的终端命令可能会使用shell的续行字符(\)将一条命令分割成多行。虽然读者直接把它贴到自己的shell下面运行也能工作,但是读者也可以略去这个续行字符,在一行里键入整条命令。

当输出的部分对于读者来说没有提供额外有用的信息时,它可能会被省略并在相应的位置用省略号([...])替代。

购买本书的读者还可免费得到由Manning出版社运营的一个私有网站论坛的访问权限,在这里读者可以对本书作出评论,询问技术问题,并获得来自主要作者以及其他读者的帮助。要访问和订阅该论坛的话,请用Web浏览器打开www.manning.com/books/docker-in-practice。该页面将会提供一些信息,包括读者注册后该如何登录到论坛,能获得怎样的帮助,以及论坛里的一些行为准则。

Manning对读者的承诺是会为其提供一个读者之间以及读者和作者之间进行有价值讨论的场所。但并不承诺作者的参与程度,作者对论坛的贡献目前仍然还是停留在志愿性质(并且是无报酬的)。我们建议读者试着问一些有挑战的问题,以激发作者回答问题的兴趣!作者在线论坛和之前讨论的档案只要书还在发行就一直可以在Manning出版社网站上访问。


[1] 本书源代码读者也可以登录异步社区(https://www.epubit.com)本书页面免费注册下载。——编者注

本书的封面图片的标题是“一个来自克罗地亚赛尔切的男人”(Man from selc, croatia)。这张图片取自19世纪中期Nikola Arsenovic的一本克罗地亚传统服饰图集的复刻版,由克罗地亚斯普利特人种学博物馆在2003年出版。该图由人种学博物馆的一位热心的图书管理员提供。该博物馆位于中世纪时罗马帝国的核心城镇,从约公元304年起,帝国国王戴克里先退位后居住的皇宫的遗迹就在这里。这本书中涵盖了来自克罗地亚各个地区的华丽的彩色图片,并介绍了他们的服饰和日常生活。

在这过去的200年里,服饰和生活方式都发生了巨大的变化,各地当时的特色也已随时间消逝。如今,来自不同大陆的人都已经变得难以区分,更不用说相隔仅数千米的村镇居民了。或许,文化多样性也是我们为获得丰富多彩的个人生活而付出的代价——现在生活无疑是更多姿多彩的、快节奏的高科技生活。

Manning出版社用两个世纪前各地独具特色的生活方式来赞美计算机行业的诞生和发展,并用古老的书籍和图册中的图片让我们领略那个时代的风土人情。


本书的第一部分由第1章和第2章构成,将带领读者开始使用Docker,并讲解其基础知识。第1章阐述Docker的起源及其核心概念,如镜像、容器和分层。在第1章的最后,读者将动手使用Dockerfile创建自己的第一个镜像。第2章介绍一些有用的技巧,让读者深入理解Docker的架构。我们通过依次讲解每个主要组件,阐述Docker守护进程与其客户端、Docker注册中心和Docker Hub之间的关系。在第一部分结束时,读者将对Docker的核心概念有所了解,并能够演示一些有用的技巧,为理解本书的后续内容打下坚实的基础。


本章主要内容

Docker是一个允许用户“在任何地方构建、分发及运行任何应用”的平台。它在极短的时间内发展壮大,目前已经被视为解决软件中最昂贵的方面之一——部署的一个标准方法。

在Docker出现之前,开发流水线通常由用于管理软件活动的不同技术组合而成,如虚拟机、配置管理工具、不同的包管理系统以及各类依赖库复杂的网站。所有这些工具需要由专业的工程师管理和维护,并且多数工具都具有自己独特的配置方式。

Docker改变了这一切,允许不同的工程师参与到这个过程中,有效地使用同一门语言,这让协作变得轻而易举。所有东西通过一个共同的流水线转变成可以在任何目标平台上使用的单一的产出——无须继续维护一堆让人眼花缭乱的工具配置,如图1-1所示。

与此同时,只要现存的软件技术栈依然有效,用户就无须抛弃它——用户可以将其原样打包到一个Docker容器内供其他人使用。由此获得的额外好处是,用户能看到这些容器是如何构建的,因此如果需要深入其细节,完全没问题。

本书针对的是具有一定Docker知识的中级开发人员。如果读者对本书的基础部分较熟悉,可随意跳到后续章节。本书的目标是揭示Docker所带来的现实世界的挑战,并展示其解决之道。不过,首先我们将提供一个Docker自身的快速回顾。如果读者想了解更全面的Docker基础,请查阅Jeff Nickoloff编写的《Docker in Action》一书(Manning Publications,2016)。

第2章将更深入地介绍Docker的架构,并通过一些技巧来演示其威力。在本章中,读者将了解到Docker是什么、为什么它很重要,并开始使用它。

图1-1 Docker如何消除了工具维护的负担

在动手实践之前,我们将对Docker稍做讨论,以便读者了解它的背景、“Docker”名字的来历以及为什么使用它!

要理解Docker是什么,从一个比喻开始会比技术性解释来得简单,而且这个Docker的比喻非常有说服力。Docker原本是指在船只停靠港口之后将商品移进或移出的工人。箱子和物品的大小和形状各异,而有经验的码头工人能以合算的方式手工将商品装入船只,因而他们倍受青睐(见图1-2)。雇人搬东西并不便宜,但除此之外别无选择。

对在软件行业工作的人来说,这听起来应该很熟悉。大量时间和精力被花在将奇形怪状的软件放置到装满了其他奇形怪状软件、大小各异的船只上,以便将其卖给其他地方的用户或商业机构。

图1-3展示了使用Docker概念时如何能节省时间和金钱。

图1-2 标准化集装箱前后的航运对比

在Docker出现之前,部署软件到不同环境所需的工作量巨大。即使不是采用手工运行脚本的方式在不同机器上进行软件配备(还是有很多人这么做),用户也不得不全力应付那些配置管理工具,它们掌管着渴求资源且快速变化的环境的状态。即便将这些工作封装到虚拟机中,还是需要花费大量时间来部署这些虚拟机、等待它们启动并管理它们所产生的额外的资源开销。

使用Docker,配置工作从资源管理中分离了出来,而部署工作则是微不足道的:运行docker run,环境的镜像会被拉取下来并准备运行,所消耗的资源更少并且是内含的,因此不会干扰其他环境。

读者无须担心容器是将被分发到Red Hat机器、Ubuntu机器还是CentOS虚拟机镜像中,只要上面有Docker,就没有问题。

图1-3 使用Docker前后软件交付的对比

几个重要的实际问题出现了:为什么要使用Docker,Docker用在什么地方?针对“为什么”的简要答案是:只需要一点点付出,Docker就能快速为企业节省大量金钱。部分方法(肯定不是所有的)将在随后的几节中讨论。我们已经在实际工作环境中切身体会到所有这些益处。

1.替代虚拟机(VM)

Docker可以在很多情况下替代虚拟机。如果用户只关心应用程序,而不是操作系统,可以用Docker替代虚拟机,并将操作系统交给其他人去考虑。Docker不仅启动速度比虚拟机快,迁移时也更为轻量,同时得益于它的分层文件系统,与其他人分享变更时更简单、更快捷。而且,它牢牢地扎根在命令行中,非常适合脚本化。

2.软件原型

如果想快速体验软件,同时避免干扰目前的设置或配备一个虚拟机的麻烦,Docker可以在几毫秒内提供一个沙箱环境。在亲身体验之前,很难感受到这种解放的效果。

3.打包软件

因为对Linux用户而言,Docker镜像实际上没有依赖,所以非常适合用于打包软件。用户可以构建镜像,并确保它可以运行在任何现代Linux机器上——就像Java一样,但不需要JVM。

4.让微服务架构成为可能

Docker 有助于将一个复杂系统分解成一系列可组合的部分,这让用户可以用更离散的方式来思考其服务。用户可以在不影响全局的前提下重组软件使其各部分更易于管理和可插拔。

5.网络建模

由于可以在一台机器上启动数百个(甚至数千个)隔离的容器,因此对网络进行建模轻而易举。这对于现实世界场景的测试非常有用,而且所费无几。

6.离线时启用全栈生产力

因为可以将系统的所有部分捆绑在Docker容器中,所以用户可以将其编排运行在笔记本电脑中移动办公,即便在离线时也没问题。

7.降低调试支出

不同团队之间关于软件交付的复杂谈判在业内司空见惯。我们亲身经历过不计其数的这类讨论:失效的库、有问题的依赖、更新被错误实施或是执行顺序有误,甚至可能根本没执行以及无法重现的错误等。估计读者也遇到过。Docker让用户可以清晰地说明(即便是脚本的形式)在一个属性已知的系统上调试问题的步骤,错误和环境重现变得更简单,而且通常与所提供的宿主机环境是分离的。

8.文档化软件依赖及接触点

通过使用结构化方式构建镜像,为迁移到不同环境做好准备,Docker 强制用户从一个基本出发点开始明确地记录软件依赖。即使用户不打算在所有地方都使用Docker,这种对文档记录的需要也有助于在其他地方安装软件。

9.启用持续交付

持续交付(continuous delivery,CD)是一个基于流水线的软件交付范型,该流水线通过一个自动化(或半自动化)流程在每次变动时重新构建系统然后交付到生产环境中。

因为用户可以更准确地控制构建环境的状态,Docker 构建比传统软件构建方法更具有可重现性和可复制性。使持续交付的实现变得更容易。通过实现一个以Docker为中心的可重现的构建过程,标准的持续交付技术,如蓝/绿部署(blue/green deployment,在生产环境中维护“生产”和“最新”部署)和凤凰部署(phoenix deployment,每次发布时都重新构建整个系统)变得很简单。

现在,读者对Docker如何能够提供帮助有了一定了解。在进入一个真实示例之前,让我们来了解一下几个核心概念。

在本节中,我们将介绍一些关键的Docker概念,如图1-4所示。

图1-4 Docker的核心概念

在开始运行Docker命令之前,将镜像、容器及层的概念牢记于心是极其有用的。简而言之,容器运行着由镜像定义的系统。这些镜像由一个或多个层(或差异集)加上一些Docker的元数据组成。

让我们来看一些核心的Docker命令。我们将把镜像转变成容器,修改它们,并添加层到我们即将提交的新镜像中。如果这一切听上去有点儿混乱,不用太担心。在本章结束时,一切都将更加清晰。

1.关键的Docker命令

Docker的中心功能是构建、分发及在任何具有Docker的地方运行软件。

对终端用户而言,Docker是一个用于运行的命令行程序。就像git(或任何源代码控制工具)一样,这个程序具有用于执行不同操作的子命令。

表1-1中列出了将在宿主机上使用的主要的Docker子命令。

表1-1 Docker子命令

命  令

目  的

docker build

构建一个Docker镜像

docker run

以容器形式运行一个Docker镜像

docker commit

将一个Docker容器作为一个镜像提交

docker tag

给一个Docker镜像打标签

2.镜像与容器

如果读者不熟悉Docker,这可能是第一次听说本文所说的“容器”和“镜像”这两个词。它们很可能是Docker中最重要的概念,因此有必要花点儿时间明确其差异。

在图1-5中,读者将看到这些概念的展示,里面有从一个基础镜像启动的3个容器。

图1-5 Docker镜像与容器

看待镜像和容器的一种方式是将它们类比成程序与进程。一个进程可以视为一个被执行的应用程序,同样,一个Docker容器可以视为一个运行中的Docker镜像。

如果读者熟悉面向对象原理,看待镜像和容器的另一种方法是将镜像看作类而将容器看作对象。对象是类的具体实例,同样,容器是镜像的实例。用户可以从单个镜像创建多个容器,就像对象一样,它们之间全都是相互隔离的。不论用户在对象内修改了什么,都不会影响类的定义——它们从根本上就是不同的东西。

现在,我们要动手使用Docker来构建一个简单的“to-do”应用程序(todoapp)镜像了。在这个过程中,读者会看到一些关键的Docker功能,如Dockerfile、镜像复用、端口暴露及构建自动化。这是接下来10分钟读者将学到的东西:

to-do应用是协助用户跟踪待完成事项的一个应用程序。我们所构建的应用将存储并显示可被标记为已完成的信息的简短字符串,它以一个简单的网页界面呈现。

图1-6展示了如此操作将得到的结果。

图1-6 构建一个Docker应用程序

应用程序的细节不是重点。我们将演示从我们所提供的一个简短的Dockerfile开始,读者可以放心地在自己的宿主机上使用与我们相同的方法构建、运行、停止和启动一个应用程序,而无须考虑应用程序的安装或依赖。这正是 Docker 赋予我们的关键部分——可靠地重现并简便地管理和分享开发环境。这意味着用户无须再遵循并迷失在那些复杂的或含糊的安装说明中。

to-do应用程序 这个to-do应用程序将贯穿本书,多次使用,它非常适合用于实践和演示,因此值得读者熟悉一下。

创建Docker镜像有4种标准的方式。表1-2逐一列出了这些方法。

表1-2 创建Docker镜像的方式

方  法

描  述

详见技巧

Docker命令/“手工”

使用docker run启动一个容器,并在命令行输入命令来创建镜像。使用docker commit来创建一个新镜像

详见技巧14

Dockerfile

从一个已知基础镜像开始构建,并指定一组有限的简单命令来构建

稍后讨论

Dockerfile及配置管理(configuration management,CM)工具

与Dockerfile相同,不过将构建的控制权交给了更为复杂的CM工具

详见技巧47

从头创建镜像并导入一组文件

从一个空白镜像开始,导入一个含有所需文件的TAR文件

详见技巧10

如果用户所做的是概念验证以确认安装过程是否正常,那么第一种“手工”方式是没问题的。在这个过程中,用户应对所采取的步骤做记录,以便在需要时回到同一点上。

到某个时间点,用户会想要定义创建镜像的步骤。这就是第二种方式(也就是我们这里所用的方式)。

对于更复杂的构建,用户需要使用第三种方式,特别是在Dockerfile功能还不足以满足镜像要求的时候。

最后一种方式从一个空镜像开始,通过叠加一组运行镜像所需要的文件进行构建。如果用户想导入一组在其他地方创建好的自包含的文件,这将非常有用,不过这在主流应用中非常罕见。

现在,我们来看一下Dockerfile方法,其他方法将在本书后面再说明。

Dockerfile是一个包含一系列命令的文本文件。本示例中我们将使用的Dockerfile如下:

FROM node (1)
MAINTAINER ian.miell@gmail.com (2)
RUN git clone -q https://github.com/docker-in-practice/todo.git (3)
WORKDIR todo (4)
RUN npm install > /dev/null (5)
EXPOSE 8000 (6)
CMD ["npm","start"] (7)

(1)❶定义基础镜像

(2)❷声明维护人员

(3)❸克隆todoapp代码

(4)❹移动到新的克隆目录

(5)❺运行node包管理器的安装命令(npm)

(6)❻指定从所构建的镜像启动的容器需要监听这个端口

(7)❼指定在启动时需要运行的命令

Dockerfile的开始部分是使用FROM命令定义基础镜像❶。本示例使用了一个Node.js镜像以便访问Node.js程序。官方的Node.js镜像名为node

接下来,使用MAINTAINER命令声明维护人员❷。在这里,我们使用的是其中一个人的电子邮件地址,读者也可以替换成自己的,因为现在它是你的Dockerfile了。这一行不是创建可工作的Docker镜像所必需的,不过将其包含进来是一个很好的做法。到这个时候,构建已经继承了node容器的状态,读者可以在它上面做操作了。

接下来,使用RUN命令克隆todoapp代码❸。这里使用指定的命令获取应用程序的代码:在容器内运行git。在这个示例中,Git是安装在基础node镜像里的,不过读者不能对这类事情做假定。

现在移动到使用WORKDIR命令新克隆的目录中❹。这不仅会改变构建环境中的目录,最后一条WORKDIR命令还决定了从所构建镜像启动容器时用户所处的默认目录。

接下来,运行node包管理器的安装命令(npm)❺。这将为应用程序设置依赖。我们对输出的信息不感兴趣,所以将其重定向到/dev/null上。

由于应用程序使用了8000端口,使用EXPOSE命令告诉Docker从所构建镜像启动的容器应该监听这个端口❻。

最后,使用CMD命令告诉Docker在容器启动时将运行哪条命令❼。

这个简单的示例演示了Docker及Dockerfile的几个核心功能。Dockerfile是一组严格按顺序运行的有限的命令集的简单序列。它们影响了最终镜像的文件和元数据。这里的RUN命令通过签出并安装应用程序影响了文件系统,而EXPOSECMDWORKDIR命令影响了镜像的元数据。

读者已经定义了自己的Dockerfile的构建步骤。现在可以键入图1-7所示的命令从而构建Docker镜像了。

图1-7 Docker build命令

读者将看到类似下面这样的输出:

Sending build context to Docker daemon 178.7 kB (1)
Sending build context to Docker daemon
Step 0 : FROM node (2)
 ---> fc81e574af43 (3)            
Step 1 : MAINTAINER ian.miell@gmail.com
 ---> Running in 21af1aad6950
 ---> 8f32669fe435
Removing intermediate container 21af1aad6950 (4)
Step 2 : RUN git clone https://github.com/ianmiell/todo.git
 ---> Running in 0a030ee746ea
Cloning into 'todo'...
 ---> 783c68b2e3fc
Removing intermediate container 0a030ee746ea
Step 3 : WORKDIR todo
 ---> Running in 2e59f5df7152
 ---> 8686b344b124
Removing intermediate container 2e59f5df7152
Step 4 : RUN npm install
 ---> Running in bdf07a308fca
npm info it worked if it ends with ok (5)
[...]
npm info ok
 ---> 6cf8f3633306
Removing intermediate container bdf07a308fca
Step 5 : RUN chmod -R 777 /todo
 ---> Running in c03f27789768
 ---> 2c0ededd3a5e
Removing intermediate container c03f27789768
Step 6 : EXPOSE 8000
 ---> Running in 46685ea97b8f
 ---> f1c29feca036
Removing intermediate container 46685ea97b8f
Step 7 : CMD npm start
 ---> Running in 7b4c1a9ed6af
 ---> 66c76cea05bb
Removing intermediate container 7b4c1a9ed6af
Successfully built 66c76cea05bb (6)

(1)Docker会上传docker build指定目录下的文件和目录

(2)每个构建步骤从 0 开始按顺序编号,并与命令一起输出

(3)每个命令会导致一个新镜像被创建出来,其镜像ID在此输出

(4)为节省空间,在继续前每个中间容器会被移除

(5)构建的调试信息在此输出(限于篇幅,本代码清单做了删减)

(6)此次构建的最终镜像ID,可用于打标签

现在,拥有了一个具有镜像ID(前面示例中的“66c76cea05bb”,不过读者的ID会不一样)的Docker镜像。总是引用这个ID会很麻烦,可以为其打标签以方便引用。

输入图1-8所示的命令,将66c76cea05bb替换成读者生成的镜像ID。

图1-8 Docker tag命令

现在就能从一个Dockerfile构建自己的Docker镜像副本,并重现别人定义的环境了!

读者已经构建出Docker镜像并为其打上了标签。现在可以以容器的形式来运行它了:

  docker run -p 8000:8000 --name example1 todoapp (1) 
  npm install
  npm info it worked if it ends with ok
  npm info using npm@2.14.4
  npm info using node@v4.1.1
  npm info prestart todomvc-swarm@0.0.1

  > todomvc-swarm@0.0.1 prestart /todo (2)
  > make all
  npm install
  npm info it worked if it ends with ok
  npm info using npm@2.14.4
  npm info using node@v4.1.1
  npm WARN package.json todomvc-swarm@0.0.1 No repository field.
  npm WARN package.json todomvc-swarm@0.0.1 license should be a 
  ➥ valid SPDX license expression
  npm info preinstall todomvc-swarm@0.0.1
  npm info package.json statics@0.1.0 license should be a valid 
  ➥ SPDX license expression
  npm info package.json react-tools@0.11.2 No license field.
  npm info package.json react@0.11.2 No license field.
  npm info package.json node-jsx@0.11.0 license should be a valid 
  ➥ SPDX license expression
  npm info package.json ws@0.4.32 No license field.
  npm info build /todo
  npm info linkStuff todomvc-swarm@0.0.1
  npm info install todomvc-swarm@0.0.1
  npm info postinstall todomvc-swarm@0.0.1
  npm info prepublish todomvc-swarm@0.0.1
  npm info ok
  if [ ! -e dist/ ]; then mkdir dist; fi
  cp node_modules/react/dist/react.min.js dist/react.min.js

  LocalTodoApp.js:9:   // TODO: default english version
  LocalTodoApp.js:84:        fwdList = 
  ➥ this.host.get('/TodoList#'+listId); // TODO fn+id sig
  TodoApp.js:117:   // TODO scroll into view
  TodoApp.js:176:   if (i>=list.length()) { i=list.length()-1;} 
  ➥ // TODO .length
  local.html:30:  <!-- TODO 2-split, 3-split -->
  model/TodoList.js:29: 
  ➥ // TODO one op - repeated spec? long spec?
  view/Footer.jsx:61:    // TODO: show the entry's metadata
  view/Footer.jsx:80:       todoList.addObject(new TodoItem()); 
  ➥ // TODO create default
  view/Header.jsx:25: 
  ➥ // TODO list some meaningful header (apart from the id)

npm info start todomvc-swarm@0.0.1

> todomvc-swarm@0.0.1 start /todo
> node TodoAppServer.js

Swarm server started port 8000
^C (3)                 
$ docker ps –a (4)           
CONTAINER ID IMAGE     COMMAND     CREATED   
➥ STATUS          PORTS NAMES
b9db5ada0461 todoapp:latest "npm start" 2 minutes ago 
➥ Exited (130) 2 minutes ago example1
$ docker start example1 (5)    
example1
$ docker ps -a
CONTAINER ID  IMAGE      COMMAND  CREATED
➥ STATUS      PORTS         NAMES
b9db5ada0461 todoapp:latest "npm start" 8 minutes ago
➥ Up 10 seconds 0.0.0.0:8000->8000/tcp example1 (6)
$ docker diff example1 (7)            
C /todo (8)              
A /todo/.swarm
A /todo/.swarm/TodoItem
A /todo/.swarm/TodoItem/1tlOc02+A~4Uzcz (9)    
A /todo/.swarm/_log  
A /todo/dist
A /todo/dist/LocalTodoApp.app.js
A /todo/dist/TodoApp.app.js
A /todo/dist/react.min.js

(1)❶docker run子命令启动容器,-p将容器的 8000 端口映射到宿主机的8000端口上,--name给容器赋予一个唯一的名字,最后一个参数是镜像

(2)容器的启动进程的输出被发送到终端中

(3)❷在此按Ctrl+C终止进程和容器

(4)❸运行这个命令查看已经启动和移除的容器,以及其ID和状态(就像进程一样)

(5)❹重新启动容器,这次是在后台运行

(6)❺再次运行ps命令查看发生变化的状态

(7)❻docker diff子命令显示了自镜像被实例化成一个容器以来哪些文件受到了影响

(8)❼修改了/todo目录

(9)❽增加了/todo/.swarm目录

docker run子命令启动容器❶。-p标志将容器的8000端口映射到宿主机的8000端口上,读者现在应该可以使用浏览器访问http://localhost:8000来查看这个应用程序了。--name标志赋予了容器一个唯一的名称,以便后面引用。最后的参数是镜像名称。

一旦容器启动,我们按下CTRL-C终止进程和容器❷。读者可以运行ps命令查看被启动且未被移除的容器❸。注意,每个容器都具有自己的容器 ID 和状态,与进程类似。它的状态是Exited(已退出),不过读者可以重新启动它❹。这么做之后,注意状态已经改变为Up(运行中),且容器到宿主机的端口映射现在也显示出来了❺。

docker diff子命令显示了自镜像被实例化成一个容器以来哪些文件受到了影响❻。在这个示例中,修改了todo目录❼,并增加了其他列出的文件❽。没有文件被删除,这是另一种可能性。

如读者所见,Docker“包含”环境的事实意味着用户可以将其视为一个实体,在其上执行的动作是可预见的。这赋予了Docker宽广的能力——用户可以影响从开发到生产再到维护的整个软件生命周期。这种改变正是本书所要描述的,在实践中展示Docker所能完成的东西。

接下来读者将了解Docker的另一个核心概念——分层。

Docker分层协助用户管理在大规模使用容器时会遇到的一个大问题。想象一下,如果启动了数百甚至数千个to-do应用,并且每个应用都需要将文件的一份副本存储在某个地方。

可想而知,磁盘空间会迅速消耗光!默认情况下,Docker在内部使用写时复制(copy-on-write)机制来减少所需的硬盘空间量(见图 1-9)。每当一个运行中的容器需要写入一个文件时,它会通过将该项目复制到磁盘的一个新区域来记录这一修改。在执行Docker提交时,这块磁盘新区域将被冻结并记录为具有自身标识符的一个层。

图1-9 启动时复制与写时复制对比

这部分解释了Docker容器为何能如此迅速地启动——它们不需要复制任何东西,因为所有的数据已经存储为镜像。

写时复制 写时复制是计算技术中使用的一种标准的优化策略。在从模板创建一个新的(任意类型)对象时,只在数据发生变化时才能将其复制进来,而不是复制整个所需的数据集。依据用例的不同,这能省下相当可观的资源。

图1-10展示了构建的to-do应用,它具有我们所感兴趣的3层。

因为层是静态的,所以用户只需要在想引用的镜像之上进行构建,变更的内容都在上一层中。在这个to-do应用中,我们从公开可用的node镜像构建,并将变更叠加在最上层。

图1-10 Docker中to-do应用的文件系统分层

所有这3层都可以被多个运行中的容器共享,就像一个共享库可以在内存中被多个运行中的进程共享一样。对于运维人员来说,这是一项至关重要的功能,可以在宿主机上运行大量基于不同镜像的容器,而不至于耗尽磁盘空间。

想象一下,你将所运行的to-do应用作为在线服务提供给付费用户,可以将服务扩散给大量用户。如果你正在开发中,可以一下在本地机器上启动多个不同的环境。如果你正在进行测试,可以比之前同时运行更多测试,速度也更快。有了分层,所有这些东西都成为可能。

通过使用Docker构建和运行一个应用程序,读者开始见识到Docker能给工作流带来的威力。重现并分享特定的环境,并能在不同的地方落地,让用户在开发过程中兼具灵活性和可控性。

依据读者以往的Docker经验不同,本章可能存在一个陡峭的学习曲线。我们在短时间内讨论了大量的基础内容。

读者现在应该:

我们已经使用这项知识:

接下来,我们将介绍几个有助于读者理解Docker如何工作的技巧,然后讨论有关Docker用法的一些更广泛的技术争论。前两个介绍性的章节构成了本书其余部分的基础,后者涵盖了从开发到生产的全过程,展示如何使用Docker来提升工作流。


本章主要内容

掌握Docker的架构是更全面地理解Docker的关键。在本章中,读者将在自己的主机和网络上对Docker的主要组件进行大致了解,并学习一些有助于增进这种理解的技巧。

在这个过程中,将学习一些有助于更有效地使用Docker(及Linux)的小窍门。后续的更高级的很多技巧都是基于这里所见的部分,因此请特别留意以下内容。

图2-1展示了Docker的架构,这将是本章的核心内容。我们将从高层次视角入手,然后聚焦到每个部分,使用设计好的技巧来巩固理解。

宿主机上的Docker(在编写本书时)分成两个部分:一个具有REST风格API的守护进程,以及一个与守护进程通信的客户端。图2-1展示的是运行着Docker客户端和守护进程的宿主机。

REST风格 一个REST风格API是指使用标准HTTP请求类型,如GETPOSTDELETE等,来执行通常符合HTTP设计者预想的功能的API。

调用Docker客户端可以从守护进程获取信息或给它发送指令。守护进程是一个服务器,它使用HTTP协议接收来自客户端的请求并返回响应。相应地,它会向其他服务发起请求来发送和接收镜像,使用的同样是HTTP协议。该服务器将接收来自命令行客户端或被授权连接的任何人的请求。守护进程还负责在幕后处理用户的镜像和容器,而客户端充当的是用户与REST风格API之间的媒介。

图2-1 Docker架构概览

私有Docker注册中心是存储Docker镜像的一项服务,可以从任何有相应权限的Docker守护进程向其发送请求。这个注册中心处于内部网络中,不能公开访问,因此被视为是私有的。

宿主机一般坐落在一个私有网络上。在收到请求时,Docker守护进程将连接互联网来获取镜像。

Docker Hub是由Docker公司运营的一个公共的注册中心。互联网上也存在其他公共的注册中心,且Docker守护进程可与之进行交互。

在第1章中我们说可以将Docker容器分发到任何能运行Docker的地方——这并不完全正确。实际上,只有当守护进程可以被安装到机器上时,容器才能在这台机器上运行。最明显的事实是,Docker客户端可以运行在Windows上,但守护进程(还)不行。

理解这张图的关键在于,当用户在自己的机器上运行Docker时,与其进行交互的可能是自己机器上的另一个进程,或者甚至是运行在内部网络或互联网上的服务。

现在,对Docker的结构有了大致的印象,我们来介绍几个与图中不同部分有关的技巧。

Docker守护进程(见图2-2)是用户与Docker交互的枢纽,因而它是理解所有相关部分的最佳切入点。它控制着用户机器上的Docker访问权限,管理着容器与镜像的状态,同时代理着与外界的交互。

图2-2 Docker守护进程

守护进程与服务器 守护进程是运行在后台的一个进程,不在用户的直接控制之下。服务器是负责接受客户端请求,并执行用于满足该请求所需的操作的一个进程。守护进程通常也是服务器,接收来自客户端的请求,为其执行操作。docker命令是一个客户端,而Docker守护进程则作为服务器对Docker容器和镜像进行操作。

我们来看几个技巧,这些技巧用于展示Docker作为守护进程高效运行,同时使用docker命令与其进行的交互被限制为执行操作的简单请求,就像与Web服务器进行交互一样。第一个技巧允许其他人连接你的Docker守护进程,并执行与你在宿主机上所能执行的相同操作,第二个技巧说明的是Docker容器是由守护进程管理的,而不是你的shell会话。

虽然默认情况下Docker的守护进程只能在宿主机上访问,但是有些情况下还是需要允许其他人访问它。读者可能遇到了一个问题,需要其他人来远程调试,或者可能想让DevOps工作流中的某一部分在宿主机上启动一个进程。

不安全! 尽管这个技巧很强大也很有用,但它被认为是不安全的。开放的Docker守护进程可能被别有用心的人所利用,并获得提升的权限。

问题

想要将Docker服务器开放给其他人访问。

解决方案

使用开放的TCP地址启动Docker守护进程。

讨论

图2-3给出了这个技巧的工作概览。

图2-3 Docker可访问性:正常情况与公开情况

在开放Docker守护进程之前,必须先停止正在运行的实例。操作的方式因操作系统而异。如果不清楚怎么做,可以首先试试这个命令:

$ sudo service docker stop

如果得到一个类似下面这样的消息,说明这是一个基于systemctl的启动系统:

The service command supports only basic LSB actions (start, stop, restart, 
try-restart, reload, force-reload, status). For other actions, please try 
to use systemctl.

可以试试这个命令:

$ systemctl stop docker

如果这个方法有效,以下命令将看不到任何输出:

ps -ef | grep -E 'docker (-d|daemon)\b' | grep -v grep

一旦Docker守护进程停止,就可以使用以下命令手工重启并向外界用户开放它:

docker daemon -H tcp://0.0.0.0:2375

这个命令以守护进程方式启动Docker(docker daemon),使用-H标志定义宿主机服务器,使用TCP协议,绑定到所有IP地址上(使用0.0.0.0),并以标准的Docker服务器端口(2375)开放。如果Docker提示daemon不是一个有效的子命令,请尝试使用旧版的-d参数。

可以从外部使用如下命令进行连接:

$ docker -H tcp://<宿主机IP>:2375

需要注意的是,在本地机器内部也需要这么做,因为Docker已经不再在其默认位置进行监听了。

如果想在宿主机上让这项变更永久生效,需要对启动系统进行配置。其操作方式参见附录B。

使用IP限制 如果开放了守护进程,请务必只向特定IP范围开放,同时不要绑定到0.0.0.0上,这样非常不安全!

在熟悉了Docker之后,(与我们一样)读者会开始思考Docker的其他使用场景,首先想到的使用场景之一是以运行服务的方式来运行Docker容器。

以服务方式运行Docker容器,通过软件隔离实现可预测行为,是Docker的主要使用场景之一。本技巧将让读者使用适合自己的操作的方式来管理服务。

问题

想要以服务方式在后台运行一个Docker容器。

解决方案

docker run命令中使用-d标志,并使用相关的容器管理标志定义此服务特性。

讨论

Docker容器与多数进程一样,默认在前台运行。在后台运行Docker容器最常见的方式是使用标准的&控制操作。虽然这行得通,但如果用户注销终端会话就可能出现问题,用户被迫使用nohup标志,而这将在本地目录中创建一个不得不管理的输出文件……是的,使用Docker守护进程的功能完成这一点将简洁得多。

要做到这一点,可使用-d标志。

$ docker run -d -i -p 1234:1234 --name daemon ubuntu nc -l 1234

docker run一起使用的-d标志将以守护进程方式运行容器。-i标志则赋予容器与Telnet会话交互的能力。使用-p将容器的1234端口公布到宿主机上。通过--name标志赋予容器一个名称,以便后期用来对它进行引用。最后,使用netcat(nc)在1234端口上运行一个简单的监听应答(echo)服务器。

如果现在使用Telnet连接它并发送消息,就可以使用docker logs命令看到容器已经接收到该消息,如代码清单2-1所示。

代码清单2-1 使用Telnet连接容器netcat服务器

$ telnet localhost 1234 (1)
Trying ::1...
Connected to localhost.
Escape character is '^]'.
hello daemon (2)
^] (3)    

telnet> q (4)
Connection closed.
$ docker logs daemon (5)
hello daemon
$ docker rm daemon (6) 
daemon
$

(1)使用telnet命令连接到容器的netcat服务器

(2)输入发送给netcat服务器的一行文本

(3)按Ctrl+]然后按回车键退出Telnet会话

(4)输入q然后按回车键退出Telnet程序

(5)运行docker logs命令查看容器的输出

(6)使用rm命令清除容器

由此可见,以守护进程方式运行一个容器是相当简单的,但在实际操作中,还有一些问题有待解答:

幸运的是,Docker为每个问题都提供了相应标志!

非必需的标志 尽管重启标志经常会与守护进程标志(-d)一起使用,但从技术角度来说,与-d一起运行这些标志并不是必需的。

重启标志

docker run --restart标志允许用户应用一组容器终止时需要遵循的规则(就是所谓的“重启策略”,见表2-1)。

表2-1 重启策略

策  略

描  述

no

容器退出时不重启

always

容器退出时总是自动重启

on-failure[:max-retry]

只在失败时重启

no策略很简单:当容器退出时,它不会被重启。这是默认值。

always策略也很简单,不过还是值得简要讨论一下:

$ docker run -d --restart=always ubuntu echo done

这个命令以守护进程方式(-d)运行容器,并总是在容器终止时自动重启(--restart=always)。它发送了一个简单的快速完成的echo命令,然后退出容器。

如果运行了上述命令,然后运行docker ps命令,就会看到类似下面这样的输出:

$ docker ps
CONTAINER ID    IMAGE     COMMAND     CREATED    
➥ STATUS     PORTS            NAMES
69828b118ec3    ubuntu:14.04  "echo done"   4 seconds ago 
➥ Restarting (0) Less than a second ago    sick_brattain

docker ps命令列出了所有运行中的容器及其信息,包括以下内容:

注意,STATUS一栏还告诉我们,容器在不到一秒前退出并正在重启。这是因为echo done命令会立即退出,而Docker必须不断地重启这个容器。

需要特别说明的是,Docker复用了容器ID。这个ID在重启时不会改变,并且对于这个Docker调用来说,ps表里永远只会有一条。

最后,on-failure策略只在容器从它的主进程返回一个非0(一般表示失败)退出码时重启:

$ docker run -d --restart=on-failure:10 ubuntu /bin/false

这条命令以守护进程(-d)形式运行容器,并设置了重启的尝试次数限制(--restart=on- failure:10),如果超出限制则退出。它运行了一个快速完成并肯定失败的简单的命令(/bin/ false)。

如果运行上述命令并等待一分钟,然后运行docker ps -a,就会看到类似下面这样的输出:

$ docker ps -a
CONTAINER ID    IMAGE      COMMAND     CREATED 
➥ STATUS          PORTS       NAMES
b0f40c410fe3    ubuntu:14.04  "/bin/false"   2 minutes ago 
➥ Exited (1) 25 seconds ago         loving_rosalind

Docker把所有与容器和镜像有关的数据都存储在一个目录下。由于它可能会存储大量不同的镜像,这个目录可能会迅速变大!

如果宿主机具有不同分区(这在企业Linux工作站上很常见),用户可能会更快遭遇空间限制。在这种情况下,用户会想移动Docker所操作的目录。

问题

想要移动Docker存储数据的位置。

解决方案

停止Docker守护进程,并使用-g标志指定新的位置来启动。

讨论

首先必须将Docker守护进程停止(有关这个问题的讨论参见附录B)。

假设想在/home/dockeruser/mydocker运行Docker。运行下列命令将在这个目录中创建一组新的目录和文件:

docker daemon -g /home/dockeruser/mydocker

这些目录是Docker内部使用的,对其进行操作风险自担(因为我们已经尝过滋味了!)。

请注意,这看起来像是把容器和镜像从之前的Docker守护进程清除了。不过不用担心。如果杀掉刚才运行的Docker进程,并重启Docker服务,Docker客户端就会指回它原来的位置,容器和镜像也将回归。

如果想让这个移动永久有效,需要对宿主机系统的启动进程进行相应配置。

Docker客户端(见图2-4)是Docker架构中最简单的部件。在主机上输入docker rundocker pull这类命令时运行的便是它。它的任务是通过HTTP请求与Docker守护进程进行通信。

图2-4 Docker客户端

在本节中,读者将看到如何监听Docker客户端与服务器之间的信息,还将看到一些与端口映射有关的基本技巧,这是向本书后续的编排章节迈进的一小步,也是使用浏览器作为Docker客户端的一种方式。

有时docker命令可能会不按预期工作。多数时候是因为没有理解命令行参数的某些部分,不过偶尔也存在更严重的安装问题,如Docker的二进制文件过时了。为了诊断问题,查看与之通信的Docker守护进程来往的数据流是十分有用的。

Docker不是不稳定的 不用惊慌!本技巧的存在不表示Docker需要经常调试,或者有任何的不稳定!这条技巧在此是为了理解Docker架构的一个工具,同时也是为了介绍socat这个强大的工具。如果读者像我们一样,在众多不同的地方使用Docker,所使用的Docker版本将会有差异。与任何软件一样,不同的版本将具有不同的功能和标志,这可能会让读者无所适从。

问题

想要调试一个Docker命令的问题。

解决方案

使用流量监听器(traffic snooper)来检查API调用,并自行解决。

讨论

在本技巧中,用户将在自己的请求与服务器套接字之间插入一个代理Unix域套接字,并查看通过它的内容(如图2-5所示)。注意,要完成这一步需要root或sudo权限。

图2-5 宿主机上的Docker客户/服务器架构

要创建这个代理,会用到socat

$ sudo socat -v UNIX-LISTEN:/tmp/dockerapi.sock \
  UNIX-CONNECT:/var/run/docker.sock &

socat socat是一个强大的命令,能让用户在两个几乎任意类型的数据通道之间中继数据。如果熟悉netcat,可以将其看作是加强版的netcat。

在这条命令中,-v用于提高输出的可读性,带有数据流的指示。UNIX-LISTEN部分是让socat在一个Unix套接字上进行监听,而UNIX-CONNECT是让socat连接到Docker的Unix套接字。“&”符号指定在后台运行该命令。

发往守护进程的请求所经过的新路由如图2-6所示。所有双向流量都会被socat看到,并与Docker客户端所提供的任何输出一起记录到终端日志中。

图2-6 插入socat作为代理的Docker客户端与服务器

现在一个简单的docker命令的输出看起来将类似下面这样:

$ docker -H unix:///tmp/dockerapi.sock ps –a (1)
> 2015/01/12 04:34:38.790706 length=105 from=0 to=104 (2)
GET /v1.16/containers/json?all=1 HTTP/1.1\r
Host: /tmp/dockerapi.sock\r
User-Agent: Docker-Client/1.4.1\r
\r
< 2015/01/12 04:34:38.792516 length=544 from=0 to=543 (3) 
HTTP/1.1 200 OK\r
Content-Type: application/json\r
Date: Mon, 12 Jan 2015 09:34:38 GMT\r
Content-Length: 435\r
\r
[{"Command":"/bin/bash","Created":1420731043,"Id":
➥ "4eec1b50dc6db7901d3b3c5a8d607f2576829fd6902c7f658735c3bc0a09a39c",
➥ "Image":"debian:jessie","Names":["/lonely_mclean"],"Ports":[],
➥ "Status":"Exited (0) 3 days ago"} (4)
,{"Command":"/bin/bash","Created":1420729129,"Id":
➥ "029851aeccc887ecf9152de97f524d30659b3fa4b0dcc3c3fe09467cd0164da5",
➥ "Image":"debian:jessie","Names":["/suspicious_torvalds"],"Ports":[],
➥ "Status":"Exited (130) 3 days ago"}
]CONTAINER ID    IMAGE        COMMAND        CREATED   
➥ STATUS         PORTS         NAMES (5)            
4eec1b50dc6d    debian:jessie    "/bin/bash"      3 days ago 
➥ Exited (0) 3 days ago             lonely_mclean
029851aeccc8    debian:jessie    "/bin/bash"      3 days ago 
➥ Exited (130) 3 days ago            suspicious_torvalds

(1)用于查看请求与响应所发送的命令

(2)HTTP请求从此处开始,左侧带有右尖括号

(3)HTTP响应从此处开始,左侧带有左尖括号

(4)来自Docker服务器的响应的JSON内容

(5)用户正常看到的输出,由Docker客户端从前面的JSON解释而来

小心 如果在前面的示例中以root身份运行socat,需要使用sudo来运行docker -H命令。这是因为dockerapi.sock文件的所有者是root。

使用socat不仅对Docker来说是一种强大的调试方式,对工作过程中可能碰到的任何其他网络服务也是如此。

Docker容器从一开始就被设计用于运行服务。在大多数情况下,都是这样或那样的 HTTP服务。其中很大一部分是可以使用浏览器访问的Web服务。

这会造成一个问题。如果有多个Docker容器运行在其内部环境中的80端口上,它们将无法全部通过宿主机的80端口进行访问。接下来的技巧将展示如何通过暴露和映射容器的端口来管理这一常见场景。

问题

想要将多个运行在同一个端口的Docker容器服务暴露到宿主机上。

解决方案

使用Docker的-p标志将容器的端口映射到宿主机上。

讨论

在这个示例中,我们将使用tutum-wordpress镜像。假设想在宿主机上运行两个实例来服务不同的博客。

由于此前有很多人想这么做,已经有人准备了任何人都可以获取并启动的镜像。要从外部地址获取镜像,可以使用docker pull命令。在默认情况下,镜像将从Docker Hub下载:

$ docker pull tutum/wordpress

要运行第一个博客,可使用如下命令:

$ docker run -d -p 10001:80 --name blog1 tutum/wordpress

这里的docker run命令以守护进程方式(-d)及发布标志(-p)运行容器。它指定将宿主机端口(10001)映射到容器端口(80)上,并赋予该容器一个名称用于识别它(--name blog1 tutum/wordpress)。

可以对第二个博客做相同操作:

$ docker run -d -p 10002:80 --name blog2 tutum/wordpress

如果现在运行这个命令:

$ docker ps -a | grep blog

将看到列出的两个博客容器及其端口映射,看起来像下面这样:

9afb95ad3617 tutum/wordpress:latest "/run.sh" 9 seconds ago Up 9 seconds 
➥ 3306/tcp, 0.0.0.0:10001->80/tcp blog1 
31ddc8a7a2fd tutum/wordpress:latest "/run.sh" 17 seconds ago Up 16 seconds
➥ 3306/tcp, 0.0.0.0:10002->80/tcp blog2

现在可以通过浏览http://localhost:10001和http://localhost:10002来访问自己的容器。

要在完成后删除这些容器(假设不想保留它们),可运行下面这个命令:

$ docker rm -f blog1 blog2

如果需要的话,现在就可以通过管理端口分配在宿主机上运行多个相同的镜像和服务了。

牢记-p标志的参数顺序 在使用-p标志时,很容易忘记哪个端口属于宿主机,哪个端口属于容器。我们可以将它看作是在从左向右读一个句子。用户连接到宿主机(-p),并从宿主机的端口传递到容器的端口(宿主机端口:容器端口)。如果熟悉SSH的端口转发命令的话,会发现它们的格式是一样的。

技巧5展示的是如何通过暴露端口将容器开放给宿主机网络。用户不会总想将服务暴露给宿主机或外界,但是会希望容器彼此相连。

本技巧展示的是如何使用Docker的链接标志来实现这一点,并确保外人无法访问内部服务。

问题

出于内部目的,想要让容器间实现通信。

解决方案

使用Docker的链接功能可以让容器彼此通信。

讨论

继续安装WordPress的任务,我们将把mysql数据库层从wordpress容器中分离出来,并将它们链接在一起,且不需要进行端口配置。图2-7展示了最终状态的概览。

为什么这一点很有用 既然已经可以将端口暴露给宿主机来使用,为什么还要用链接?链接可以让用户封装并定义容器间的关系,而无须将服务暴露给宿主机网络(即可能暴露给外界)。用户可能会因为安全因素而这么做。

图2-7 使用链接容器设置WordPress

要像这样运行容器,可按照以下顺序执行,并在第一条和第二条命令之间暂停大约一分钟:

$ docker run --name wp-mysql \
  -e MYSQL_ROOT_PASSWORD=yoursecretpassword -d mysql (1)
$ docker run --name wordpress \
  --link wp-mysql:mysql -p 10003:80 -d wordpress (2)

(1)❶

(2)❷

首先将mysql容器命名为wp-mysql,用于在后面引用它❶。还需要提供一个环境变量以便mysql容器可以初始化数据库(-e MYSQL_ROOT_PASSWORD=yoursecretpassword)。两个容器都以守护进程方式运行(-d),同时使用了Docker Hub上官方mysql镜像的引用。

在第二个命令❷ 中,将wordpress容器命名为wordpress,以备后面要引用它。同时将wp-mysql容器链接到wordpress容器中(--link wp-mysql:mysql)。在wordpress容器内对mysql服务器的引用将被发送到名为wp-mysql的容器中。有如技巧5所述,使用了一个本地端口映射(-p 10003:80),并添加了Docker Hub上官方wordpress镜像(wordpress)的引用。请注意,链接不会等待被链接容器启动,因此才有在命令之间暂停的指示。完成这一步更精确的方法是,在运行wordpress容器之前,在docker logs wp-mysql的输出中查找mysqid: ready for connections

如果现在浏览http://localhost:10003,将会看到wordpress介绍画面,并可设置这个wordpress实例。

这个示例的关键在于第二条命令里的--link标志。这个标志会设置容器的host文件以便wordpress容器能够引用mysql服务器,这将被路由到具有“wp-mysql”名称的容器。这有很大的好处,即无须对wordpress容器做任何改动,就可以将不同的mysql容器交换进来,使不同服务的配置管理变得更简单。

启动顺序至关重要 容器必须以正确的顺序启动,以便能对已经存在的容器名称做映射。截至编写本书时,Docker不具备动态解析链接的功能。

为了使用这种方式链接容器,在构建镜像时必须指定暴露容器的端口。这可以通过在镜像构建的Dockerfile中使用EXPOSE命令来达成。

现在,已经见识了Docker编排的一个简单示例,并朝着微服务构架前进了一步。在这个例子中,可以在不影响wordpress容器的同时对mysql容器进行操作,反之亦然。这种对运行中服务的细粒度控制是微服务构架的关键的运维优势之一。

销售新技术可能很艰难,因此简单而有效的演示是非常有价值的。让演示可操作则效果更佳,这也是为什么我们发现,为了以易于达成的方式给新手带来Docker的初体验,创建一个能在浏览器中与容器进行交互的网页是一个非常棒的技巧。这种让人眼前一亮的体验没有坏处!

问题

想要演示Docker的强大威力,用户无须自己安装Docker或运行自己不理解的命令。

解决方案

使用一个开放端口启动Docker守护进程,并启用CORS[1]。然后使用所选择的Web服务器为Docker终端仓库提供服务。

讨论

REST API最常见的用法是在一台服务器上暴露它,并在一个网页上使用JavaScript来调用。由于Docker正巧是通过REST API来执行所有交互的,因此可以使用相同方式来控制Docker。尽管一开始看起来有点儿令人惊讶,但这种控制一直延伸到能通过浏览器里的终端与容器进行交互。

我们在技巧1中已经讨论过如何在2375端口上启动守护进程,因而不再赘述。此外,CORS太庞大,这里无法深入讲述(可以参考Monsur Hossain所著的CORS in Action[Manning Publications, 2014])——简言之,它是小心地绕过限制JavaScript只能访问当前域这一常规限制的一种机制。在这个例子中,它将允许守护进程监听一个与提供Docker终端页面不同的端口上。要启用它,需要使用--api-enable-cors选项和用于监听端口的选项一起来启动Docker守护进程。

现在,先决条件已经梳理好,我们将它运行起来。首先,需要获取代码:

git clone https://github.com/aidanhs/Docker-Terminal.git
cd Docker-Terminal

然后需要提供文件服务:

python2 -m SimpleHTTPServer 8000

上述命令使用Python内置的一个模块为目录中的静态文件服务。用户可以使用任何自己喜欢的等效服务。

现在可以在浏览器中访问http://localhost:8000并启动一个容器。

图2-8展示了Docker终端是如何连接起来的。页面托管在本地计算机中,并连接到本地计算机上的Docker守护进程,以执行所有操作。

图2-8 Docker终端是如何工作的

如果想把链接发送给其他人,以下几点值得注意。

这里为什么不使用Docker 如果读者的Docker经验更丰富,会奇怪为什么我们没在这个技巧中使用Docker。原因是,我们还在介绍Docker,不想给刚接触Docker的读者增加复杂度。“Docker化”这个技巧将作为一个练习留给读者。

一旦创建了镜像,读者可能就想与其他用户分享它。这是Docker注册中心概念的所在。

图2-9中的3个注册中心差别在于它们的可达性。一个处于私有网络上,一个开放在公共网络中,而另一个是公共的但只有注册用户才能使用Docker访问。它们全部使用相同的API完成相同的功能,这就是Docker守护进程如何知道怎样与它们进行相互通信。

图2-9 一个Docker注册中心

Docker注册中心允许多个用户使用REST风格API将镜像推送到一个中央存储中,也可以从中拉取镜像。

与Docker自身一样,注册中心代码也是开源的。很多公司(如我们公司)建立了私有注册中心在内部存储和共享专有的镜像。这是在进一步说明Docker公司的注册中心之前,我们这里将要讨论的东西。

读者已经看到Docker公司具有一项服务,人们可以在其上公开地共享他们的镜像(如果想私下进行,可以付费实现)。不过存在一些不想通过Hub来共享镜像的原因——有些商业组织想尽可能把东西保留在内部,或者镜像可能很大,通过互联网传输太慢,或者也许想在试验时保持镜像私有化,同时又不想付费。不管出于什么原因,幸运的是有一个简单的解决方案。

问题

想要一个在本地托管镜像的方法。

解决方案

在本地网络上建立一个注册中心服务器。

讨论

要让注册中心运行起来,可在一台具有大量磁盘空间的机器上发起以下命令:

$ docker run -d -p 5000:5000 -v $HOME/registry:/var/lib/registry registry:2

这条命令让注册中心运行于Docker宿主机的5000端口上(-p 5000:5000),并使用主目录下的registry作为容器的/var/lib/registry目录,后者是容器里的registry默认存储文件的位置。它同时指定了容器内的registry将文件存储在/registry目录下(STORAGE_PATH=/registry)。

在所有想访问这个注册中心的机器上,将下列内容添加到守护进程选项中(HOSTNAME是新的注册中心服务器的主机名或IP地址):--insecure-registry HOSTNAME

现在可以执行docker push HOSTNAME:5000/image:tag

正如所见,一个本地注册中心最基础层次的配置很简单,所有数据都存储在$HOME/registry目录中。如果要扩容或让它变得更健壮,GitHub上的仓库(https://github.com/docker/distribution/ blob/v2.2.1/docs/storagedrivers.md )罗列了一些可选项,例如,在Amazon S3里存储数据。

读者可能会对--insecure-registry选项感到好奇。为了帮助用户保持安全,Docker只允许使用签名HTTPS证书从注册中心上拉取。因为对本地网络相当信任,我们覆盖了这个选项。不过,毫无疑问的是,在互联网上这么做必须慎之又慎!

注册中心路线图 与Docker生态系统里的其他事物一样,注册中心也在发生变化。尽管注册中心镜像将保持可用及稳定,但它最终将被一个名为distribution(见https://github.com/docker/distribution)的新工具取代。

Docker Hub(见图2-10)是由Docker公司维护的一个注册中心。它拥有成千上万个镜像可供下载和运行。任何Docker用户都可以在上面创建免费账号及公共Docker镜像。除了用户提供的镜像,上面还维护着一些作为参考的官方镜像。

图2-10 Docker Hub

镜像受用户认证的保护,同时具有一个与GitHub类似的支持率打星系统。

这些官方镜像的表现形式可能是Linux发行版,如Ubuntu或Cent OS,或是预装软件包,如Node.js,或是完整的软件栈,如WordPress。

Docker注册中心造就的是与GitHub相似的社交编码文化。如果读者有兴趣尝试一个新的软件应用程序,或正在找寻服务于某个特定用途的新的应用程序,那么Docker镜像将是一个简单的实验手段,它不会对宿主机造成干扰,不需要配备一个虚拟机,也不必担心安装步骤。

问题

想要查找一个Docker镜像形式的应用程序或工具,并进行尝试。

解决方案

使用docker search命令来查找要拉取的镜像,然后运行它。

讨论

假设读者对Node.js有兴趣。在下面的代码中,我们使用docker search命令搜索出匹配“node”的镜像:

$ docker search node
NAME               DESCRIPTION  
➥ STARS     OFFICIAL  AUTOMATED
node               Node.js is a JavaScript-based platform for... 
➥ 432      [OK] (1)                                   
dockerfile/nodejs       Trusted automated Node.js (http://nodejs.o... 
➥ 57      [OK] (2)     
dockerfile/nodejs-bower-grunt Trusted automated Node.js (http://nodejs.o... 
➥ 17      [OK] (3)
nodesource/node        
➥ 9       [OK] (4) 
selenium/node-firefox          
➥ 5       [OK]
selenium/node-chrome                   
➥ 5       [OK]
selenium/node-base        
➥ 3       [OK]
strongloop/node        StrongLoop, Node.js, and tools.      
➥ 3       [OK]
selenium/node-chrome-debug    
➥ 3       [OK]
dockerfile/nodejs-runtime   Trusted automated Node.js runtime Build ..   
➥ 3       [OK]
jprjr/stackbrew-node      A stackbrew/ubuntu-based image for Docker,...  
➥ 2       [OK]
selenium/node-firefox-debug           
➥ 2       [OK]
maccam912/tahoe-node      Follow "The Easy Way" in the description t...  
➥ 1       [OK]
homme/node-mapserv       The latest checkouts of Mapserver and its ...  
➥ 1       [OK]
maxexcloo/nodejs        Docker framework container with Node.js an...  
➥ 1       [OK]
brownman/node-0.10       
➥ 0       [OK]
kivra/node           Image with build dependencies for frontend... 
➥ 0       [OK]
thenativeweb/node    
➥ 0       [OK]
thomaswelton/node    
➥ 0       [OK]
siomiz/node-opencv      _/node + node-opencv  
➥ 0       [OK]
bradegler/node      
➥ 0       [OK]
tcnksm/centos-node      Dockerfile for CentOS packaging node      
➥ 0       [OK]
azukiapp/node     
➥ 0       [OK]
onesysadmin/node-imagetools   
➥ 0       [OK]
fishead/node   
➥ 0       [OK]

(1)docker search的输出是按评星数量排序的

(2)描述是上传者对镜像用途的解释

(3)官方镜像是指受Docker Hub信任的镜像

(4)自动化镜像是指使用Docker Hub自动化构建功能构建的镜像

一旦选择了一个镜像,就可以通过对其名称执行docker pull命令来下载它:

$ docker pull node (1)
node:latest: The image you are pulling has been verified
81c86d8c1e0c: Downloading
81c86d8c1e0c: Pull complete
3a20d8faf171: Pull complete
c7a7a01d634e: Pull complete
2a13c2a76de1: Pull complete
4cc808131c54: Pull complete
bf2afba3f5e4: Pull complete
0cba665db8d0: Pull complete
322af6f234b2: Pull complete
9787c55efe92: Pull complete
511136ea3c5a: Already exists
bce696e097dc: Already exists
58052b122b60: Already exists
Status: Downloaded newer image for node:latest (2)

(1)从Docker Hub拉取名为node的镜像

(2)如果Docker拉取了一个新的镜像(与之相对的是说明没有比已有镜像更新的版本),会显示这条信息。读者看到的输出可能会有所不同

接着,可以使用-t-i标志以交互方式运行它。-t标志指明创建一个tty设备(一个终端),而-i标志指明该Docker会话是交互式的:

$ docker run -t -i node /bin/bash
root@c267ae999646:/# node
> process.version
'v0.12.0'
>

-ti标志惯用法 可以在上述docker run调用中用-ti取代-t -i来减少输入。从这里开始,本书将使用这种用法。

常常会有来自镜像维护人员的有关如何运行镜像的建议。在http://hub.docker.com网站上搜索镜像将引导到该镜像的页面。其描述标签页可提供更多信息。

这个镜像可信吗 如果用户下载并运行了一个镜像,运行的将是自己无法充分验证的代码。虽然使用受信任的镜像具有相对的安全性,但是通过互联网下载和运行软件时,没有什么是能保证100%安全的。

有了这方面的知识和经验,现在可以对Docker Hub提供的大量资源进行挖掘了。毫不夸张地说,要试用这成千上万的镜像,有很多东西要学。请慢慢享受!

在本章中,我们学习了Docker是如何结合在一起的,并且使用这一认知对不同组件进行了操作。

下面是涉及的主要领域:

前两章已经涵盖了基础知识(但仍希望读者学到了一些新东西,即使对Docker已经比较熟悉)。现在我们继续学习第二部分,看一看Docker在软件开发世界中扮演的角色。

[1]  Cross-Origin Resource Sharing,跨域资源共享。——译者注


相关图书

云原生测试实战
云原生测试实战
Kubernetes快速入门(第2版)
Kubernetes快速入门(第2版)
Kubernetes零基础实战
Kubernetes零基础实战
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:编程基础篇
深入浅出Windows API程序设计:编程基础篇
云原生技术中台:从分布式到云平台设计
云原生技术中台:从分布式到云平台设计

相关文章

相关课程