<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; vm</title>
	<atom:link href="http://tonybai.com/tag/vm/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Wed, 29 Apr 2026 00:18:44 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Go正走在成为下一个企业级编程语言的轨道上</title>
		<link>https://tonybai.com/2019/05/03/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language/</link>
		<comments>https://tonybai.com/2019/05/03/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language/#comments</comments>
		<pubDate>Fri, 03 May 2019 06:01:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[error-handling]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go2]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[hotspot]]></category>
		<category><![CDATA[istio]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[JIT]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Ops]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[reader]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[terraform]]></category>
		<category><![CDATA[vendor]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[writer]]></category>
		<category><![CDATA[云计算]]></category>
		<category><![CDATA[企业级]]></category>
		<category><![CDATA[包管理]]></category>
		<category><![CDATA[多核]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[并行]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[文档]]></category>
		<category><![CDATA[架构师]]></category>
		<category><![CDATA[注释]]></category>
		<category><![CDATA[维护性]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[翻译]]></category>
		<category><![CDATA[虚拟机]]></category>
		<category><![CDATA[错误处理]]></category>
		<category><![CDATA[面向对象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2711</guid>
		<description><![CDATA[发展演化了十年的Go语言已经被证明了是云计算时代的首选编程语言，但Go的用武之地显然不局限于此。Kevin Goslar近期在Hacker Noon发表了一篇名为：《Go is on a Trajectory to Become the Next Enterprise Programming Language》的文章，阐述了Go可能成为下一个企业编程语言的理由，这里是那篇文章的中文译文，分享给大家。 摘要 Go是一种专门为大规模软件开发而设计的编程语言。它提供了强大的开发体验并避免了现有编程语言存在的许多问题。这些因素使其成为最有可能在未来替代Java主导企业软件平台的候选者之一。对于那些寻求在未来几十年内构建大规模云基础架构的安全和前瞻性技术的公司和开源计划而言，我建议它们将Go视为其主要的编程语言。Go的优势如下： 基于现实世界的经验 专注于大型工程 专注于可维护性 保持简单明了 使事情显式且明显 很容易学习 仅提供了一种做事方式 支持简单地内置并发 提供面向计算的语言原语 使用OO &#8211; 好的部分 拥有现代化的标准库 强制执行标准化格式 有一个非常快的编译器 使交叉编译变得容易 执行得非常快 需要较小的内存占用 部署规模小 部署完全独立 支持vendor依赖 提供兼容性保证 鼓励提供良好的文档 商业支持的开源 请继续阅读有关上述每个优势点的更多详细信息。然而，在进入Go之前，你应该注意： 不成熟的库 即将到来的改变 没有“硬实时”支持 简介 Go是Google开发的一种编程语言，在过去几年中取得了很大的成功。大部分现代云计算，网络和DevOps平台都是Go语言编写的，例如：Docker、Kubernetes、Terraform、ETCD或istio等。许多公司也将它用于通用软件开发。Go所具备的功能让这些项目吸引了大量用户，而Go的易用性也使得这些项目有了很多的贡献者。 Go的优势来自于简单和经过验证的想法的结合，同时避免了其他语言中出现的许多问题。这篇博客文章概述了Go背后的一些设计原则和工程智慧，并展示它们是如何结合在一起的 &#8211; 它们使Go成为下一代大型软件开发平台的优秀候选者。许多编程语言在个别领域都比较强大，但是在将所有领域都结合起来时，没有其他语言能够如此一致地“得分”，特别是在大型软件工程方面。 基于现实世界的经验 Go是由经验丰富的软件行业资深人士创建的，他们长期以来一直感受到现有语言的缺点带来的痛苦。几十年前，Rob Pike和Ken Thompson在Unix，C和Unicode的发明中发挥了重要作用。在实现了用于JavaScript和Java的V8和HotSpot虚拟机之后，Robert Griesemer在编译器和垃圾收集方面拥有着数十年的经验。在太多次的不得不等待他们的谷歌规模的C++/Java代码库的编译过程的推动下，他们开始着手创建一门新的编程语言，这门语言中凝聚了他们通过编写半个世纪代码过程中所学到的一切。 专注于大型工程 [...]]]></description>
			<content:encoded><![CDATA[<p>发展演化了<a href="https://tonybai.com/2017/09/24/go-ten-years-and-climbing/">十年的Go语言</a>已经被证明了是云计算时代的首选编程语言，但<a href="https://tonybai.com/tag/go">Go</a>的用武之地显然不局限于此。Kevin Goslar近期在<a href="https://hackernoon.com/">Hacker Noon</a>发表了一篇名为：<a href="https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e">《Go is on a Trajectory to Become the Next Enterprise Programming Language》</a>的文章，阐述了Go可能成为下一个企业编程语言的理由，这里是那篇文章的中文译文，分享给大家。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-enterprise-programming-lang-1.png" alt="img{512x368}" /></p>
<h2>摘要</h2>
<p>Go是一种专门为大规模软件开发而设计的编程语言。它提供了强大的开发体验并避免了现有编程语言存在的许多问题。这些因素使其成为最有可能在未来替代Java主导企业软件平台的候选者之一。对于那些寻求在未来几十年内构建大规模云基础架构的安全和前瞻性技术的公司和开源计划而言，我建议它们将Go视为其主要的编程语言。Go的优势如下：</p>
<ul>
<li>基于现实世界的经验</li>
<li>专注于大型工程</li>
<li>专注于可维护性</li>
<li>保持简单明了</li>
<li>使事情显式且明显</li>
<li>很容易学习</li>
<li>仅提供了一种做事方式</li>
<li>支持简单地内置并发</li>
<li>提供面向计算的语言原语</li>
<li>使用OO &#8211; 好的部分</li>
<li>拥有现代化的标准库</li>
<li>强制执行标准化格式</li>
<li>有一个非常快的编译器</li>
<li>使交叉编译变得容易</li>
<li>执行得非常快</li>
<li>需要较小的内存占用</li>
<li>部署规模小</li>
<li>部署完全独立</li>
<li>支持vendor依赖</li>
<li>提供兼容性保证</li>
<li>鼓励提供良好的文档</li>
<li>商业支持的开源</li>
</ul>
<p>请继续阅读有关上述每个优势点的更多详细信息。然而，在进入Go之前，你应该注意：</p>
<ul>
<li>不成熟的库</li>
<li>即将到来的改变</li>
<li>没有“硬实时”支持</li>
</ul>
<h2>简介</h2>
<p>Go是Google开发的一种编程语言，在过去几年中取得了很大的成功。大部分现代云计算，网络和DevOps平台都是Go语言编写的，例如：<a href="https://tonybai.com/tag/docker">Docker</a>、<a href="https://tonybai.com/tag/kubernetes">Kubernetes</a>、<a href="https://www.terraform.io/">Terraform</a>、<a href="https://coreos.com/etcd/">ETCD</a>或<a href="https://tonybai.com/2018/01/03/an-intro-of-microservices-governance-by-istio">istio</a>等。<a href="https://github.com/golang/go/wiki/GoUsers#united-states">许多公司</a>也将它用于通用软件开发。Go所具备的功能让这些项目吸引了大量用户，而Go的易用性也使得这些项目有了很多的贡献者。</p>
<p>Go的优势来自于简单和经过验证的想法的结合，同时避免了其他语言中出现的许多问题。这篇博客文章概述了Go背后的一些设计原则和工程智慧，并展示它们是如何结合在一起的 &#8211; 它们使Go成为下一代大型软件开发平台的优秀候选者。许多编程语言在个别领域都比较强大，但是在将所有领域都结合起来时，没有其他语言能够如此一致地“得分”，特别是在大型软件工程方面。</p>
<h2>基于现实世界的经验</h2>
<p>Go是由经验丰富的软件行业资深人士创建的，他们长期以来一直感受到现有语言的缺点带来的痛苦。几十年前，<a href="https://en.wikipedia.org/wiki/Rob_Pike">Rob Pike</a>和<a href="https://en.wikipedia.org/wiki/Ken_Thompson">Ken Thompson</a>在Unix，C和Unicode的发明中发挥了重要作用。在实现了用于JavaScript和Java的V8和HotSpot虚拟机之后，<a href="https://github.com/griesemer">Robert Griesemer</a>在编译器和垃圾收集方面拥有着数十年的经验。在太多次的不得不等待他们的谷歌规模的<a href="http://radar.oreilly.com/2012/09/golang.html">C++/Java代码库的编译过程</a>的推动下，他们开始着手创建一门新的编程语言，这门语言中凝聚了他们通过编写半个世纪代码过程中所学到的一切。</p>
<h2>专注于大型工程</h2>
<p>几乎任何编程语言都可以成功构建小型工程项目。当成千上万的开发人员在数十年的持续时间压力下在包含数千万行代码的大量代码库上进行协作时，真正痛苦的问题就会发生。这会导致以下问题：</p>
<ul>
<li>超长的编译时长会中断开发过程</li>
<li>代码库由几个人/团队/部门/公司拥有，混合了不同的编程风格</li>
<li>该公司雇佣了数千名工程师，架构师，测试人员，Ops专家，审计员，实习生等，他们需要了解代码库，但需要具有广泛的编码经验</li>
<li>依赖于许多外部库或运行时，其中一些不再以其最初的形式存在</li>
<li>每行代码在代码库的生命周期内平均被<a href="https://www.ybrikman.com/writing/2018/08/12/the-10-to-1-rule-of-writing-and-programming">重写了10次</a>，留下了疤痕，瑕疵和<a href="https://codeclimate.com/blog/are-you-experiencing-technical-drift">技术偏移</a></li>
<li>文档不完整</li>
</ul>
<p>Go专注于<a href="https://talks.golang.org/2012/splash.article">减轻这些大规模的工程难题</a>，有时是以使小型工程变得更加繁琐为代价，例如在这里和那里需要一些额外的代码。</p>
<h2>专注于可维护性</h2>
<p>Go强调尽可能多地将工作转交到自动代码维护工具中。Go工具链提供了最常用的功能，如格式化代码和自动package导入、查找符号的定义和用法、简单的重构以及代码味道的识别。由于标准化的代码格式化和单一的惯用方式，机器生成的代码更改看起来非常接近Go中人为生成的更改。并而使用类似的模式，使得人和机器的协作更加无缝。</p>
<h2>保持简单直接</h2>
<blockquote>
<p>初级程序员为简单问题创建简单的解决方案。高级程序员为复杂问题创建复杂的解决方案。伟大的程序员找到复杂问题的简单解决方案。-  <a href="http://www.chc-3.com/pub/beautifulsoftware_v10.htm">查尔斯康奈尔</a></p>
</blockquote>
<p>很多人都对Go不包含他们喜欢的其他语言概念感到惊讶。Go确实是一种非常小而简单的语言，只包含最少的正交和经过验证的概念。这鼓励开发人员以最少的认知开销编写最简单的代码，以便许多其他人可以理解并使用它。</p>
<h2>使事情显式而明显</h2>
<blockquote>
<p>良好的代码是显而易见的，避免聪明，模糊的语言功能，扭曲的控制流和间接性。</p>
</blockquote>
<p>许多语言都致力于使编写代码变得高效。然而，在其生命周期中，人们将花费大约（100倍）的时间阅读代码，而不是首先编写所需的代码。例如，审查，理解，调试，更改，重构或重用它。在查看代码时，通常只能看到并理解它的一小部分，通常没有对整个代码库的完整理解。为了解释这一点，Go将一切都显式化了。</p>
<p>一个例子是错误处理。让异常在各个点中断代码并使沿着调用链处理可能会更容易。Go需要<a href="https://tour.golang.org/methods/19">手动处理或返回每个错误</a>。这使得它可以准确地显示代码可以被中断的位置以及如何处理或包装错误。总的来说，这使得错误处理更容易编写，但更容易理解。</p>
<h2>简单易学</h2>
<p>Go非常小而且简单，可以在短短几天内研究整个语言及其基本概念。根据我们的经验，经过不超过一周的培训（与其他语言的以月为单位相比），初学者可以理解Go专家编写的代码，并为此做出贡献。为了方便大量人群，Go网站提供了所需的所有教程和深入的文章。这些教程在浏览器中运行，允许人们在将Go安装到本地计算机上之前学习和使用Go。</p>
<h2>一种做事方式</h2>
<p>Go语言通过个人自我表达赋予团队合作能力。</p>
<p>在Go（和Python）中，所有语言特征都是正交的并且彼此互补，通常做某事只有一种方法。如果您要求10位Python或Go程序员解决问题，您将获得10个相对类似的解决方案。不同的程序员在彼此的代码库中感觉更有家的感觉。在查看其他人的代码时，每分钟的<a href="https://www.osnews.com/story/19266/wtfsm">WTF</a>更少，而且人们的工作更好地融合在一起，从而形成一个人人都为之骄傲并且喜欢工作的一致性。这避免了大规模的工程问题，例如：</p>
<ul>
<li>开发人员将良好的工作代码视为“混乱”，并要求在他们可以使用之前重写它，因为他们不会像原作者那样思考。</li>
<li>不同的团队成员在该语言的不同子集中编写相同代码库的部分内容。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/go-enterprise-programming-lang-2.jpeg" alt="img{512x368}" /><br />
来源：https：//www.osnews.com/story/19266/wtfsm</p>
<h2>简单，内置并发</h2>
<blockquote>
<p>Go专为现代多核硬件而设计。</p>
</blockquote>
<p>目前使用的大多数编程语言（Java，JavaScript，Python，Ruby，C，C ++）都是在20世纪80年代到2000年代设计的，当时大多数CPU只有一个计算核心。这就是为什么它们本质上是单线程的，并将并行化视为事后增加的边缘情况，通过诸如线程和同步点之类的附加组件实现，这些附加组件既麻烦又难以正确使用。第三方库提供了更简单的并发形式，如Actor模型，但总有多个选项可用，导致语言生态系统碎片化。今天的硬件拥有越来越多的计算内核，软件必须并行化才能在其上高效运行。Go是在多核CPU时代编写的，并且在语言中内置了简单，高级的<a href="https://en.wikipedia.org/wiki/Communicating_sequential_processes">CSP风格</a>的并发特性。</p>
<h2>面向计算的语言原语</h2>
<p>在基础层面上，计算机系统接收数据，处理它（通常经过几个步骤），并输出结果数据。例如，Web服务器从客户端接收HTTP请求，并将其转换为一系列数据库或后端调用。一旦这些调用返回，它就会将接收到的数据转换为HTML或JSON并将其输出给调用者。Go的内置语言原语直接支持这种范例：</p>
<ul>
<li>结构体代表数据</li>
<li>reader和writer代表流式IO</li>
<li>函数处理数据</li>
<li>goroutines提供（几乎无限制的）并发</li>
<li>通道用于管理并发处理步骤之间的数据</li>
</ul>
<p>由于所有计算原语都是由语言以直接的形式提供的，因此Go源代码可以更直接地表达服务器执行的操作。</p>
<h2>OO &#8211; 好的部分</h2>
<p><img src="https://tonybai.com/wp-content/uploads/go-enterprise-programming-lang-3.gif" alt="img{512x368}" /><br />
在基类中改变某些东西的副作用</p>
<p>面向对象非常有用。这几十年OO的应用是富有成效的，并且让我们了解它的哪些部分比其他部分可以更好地扩展。基于这些认知，Go采用面向对象的新方法。它保留了封装和消息传递等优点。Go避免了继承，因为它现在被认为是<a href="https://www.javaworld.com/article/2073649/why-extends-is-evil.html">有害的</a>，Go为组合提供<a href="https://golang.org/doc/effective_go.html#embedding">头等的支持</a>。</p>
<h2>现代标准库</h2>
<p>许多当前使用的编程语言（Java，JavaScript，Python，Ruby）是在互联网成为当今无处不在的计算平台之前设计的。因此，这些语言的标准库仅为未针对现代互联网优化的网络提供相对通用的支持。Go是十年前创建的，当时互联网已经全面展开。Go的标准库允许在没有第三方库的情况下创建更复杂的网络服务。这可以防止使用第三方库的常见问题：</p>
<ul>
<li>碎片化：实现相同功能的总有多种选择</li>
<li>膨胀：库通常实现的不仅仅是它们的用途</li>
<li>依赖地狱：库通常依赖于特定版本的其他库</li>
<li>质量未知：第三方代码可能具有可疑的质量和安全性</li>
<li>未知支持：第三方库的开发可以随时停止</li>
<li>意外更改：第三方库通常不像标准库那样进行严格的版本管理</li>
</ul>
<p>Russ Cox的<a href="https://research.swtch.com/deps">更多背景</a>信息。</p>
<h2>标准化格式</h2>
<blockquote>
<p>Gofmt的风格是没有人喜欢的，但gofmt是每个人的最爱。 &#8211; Rob Pike</p>
</blockquote>
<p>Gofmt是一种以标准化方式格式化Go代码的程序。它不是最漂亮的格式化方式，而是最简单，最不讨厌的方式。标准化的源代码格式化具有惊人的积极影响：</p>
<ul>
<li>重点讨论重要主题：它消除了围绕标签与空格，缩进深度，每行长度，空行，花括号放置等的一系列<a href="https://en.wikipedia.org/wiki/Law_of_triviality">无意义的争论</a>。</li>
<li>开发人员在彼此的代码库中感到宾至如归，因为其他代码看起来很像他们编写的代码。每个人都喜欢自由地按照自己喜欢的方式格式化代码，但如果其他人冒昧地按照他们自己喜欢的方式格式化>代码，那么每个人都讨厌它。</li>
<li>自动代码更改不会弄乱手写代码的格式，例如通过引入意外的空白更改。</li>
</ul>
<p>许多其他语言社区现在正在开发gofmt等价物。当构建为第三方解决方案时，通常会有几种竞争格式标准。例如，JavaScript世界提供<a href="https://prettier.io/">Prettier</a>和<a href="https://standardjs.com/">StandardJS</a>。可以一起使用其中之一或两者。许多JS项目都没有采用它们，因为这是一个额外的决定。Go的格式化程序内置于该语言的标准工具链中，因此只有一个标准，每个人都在使用它。</p>
<h2>快速编译</h2>
<p><img src="https://tonybai.com/wp-content/uploads/go-enterprise-programming-lang-4.png" alt="img{512x368}" /><br />
来源：https://xkcd.com/303</p>
<p>大型代码库的长编译时间是引发Go语言起源的一个微小的原因。Google主要使用C++和Java，与Haskell，Scala或Rust等更复杂的语言相比，它可以相对快速地编译。尽管如此，当编译大型代码库时，即使是少量的慢速也会把人激怒，编译工作流中断导致编译延迟。Go是从头开始设计的，以使编译更有效，因此编译器速度非常快，几乎没有编译延迟。这为Go开发人员提供了类似于脚本语言的即时反馈，并具有静态类型检查的额外好处。</p>
<h2>交叉编译</h2>
<p>由于语言运行时非常简单，因此它已被移植到许多平台，如macOS，Linux，Windows，BSD，ARM等。Go可以开箱即用于编译所有这些平台的二进制文件。这使得我们可以轻松地从一台机器来进行部署。</p>
<h2>快速执行</h2>
<p>Go有着接近C的速度。与JITed(即时编译)语言（Java，JavaScript，Python等）不同，Go二进制文件不需要启动或预热时间，因为它们作为已编译和完全优化的本机代码提供。Go垃圾收集器仅以<a href="https://twitter.com/brianhatfield/status/804355831080751104">微秒</a>的指令引入可忽略的暂停。在其快速的单核性能上面，Go使得利用所有的CPU内核<a href="https://tour.golang.org/concurrency/1">更容易</a>。</p>
<h2>小内存占用</h2>
<p>像JVM，Python或Node这样的运行时不仅仅在运行时加载程序代码。它们还会加载大型且高度复杂的基础架构，以便在每次运行时编译和优化程序。这使得它们的启动时间变慢并导致它们使用大量（数百MB）的RAM。Go进程的开销较小，因为它们已经完全编译和优化，只需要运行。Go还以<a href="https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast">非常节省内存的方式存储数据</a>。这在内存有限且昂贵的云环境中以及在开发期间非常重要，在开发期间我们希望在单个机器上快速启动整个堆栈，同时为其他软件留下内存。</p>
<h2>小部署规模</h2>
<p>Go二进制文件的大小非常简洁。Go应用程序的Docker镜像通常比用Java或Node编写的等效文件<a href="https://derickbailey.com/2017/03/09/selecting-a-node-js-image-for-docker">小10倍</a>，因为它不需要包含编译器，JIT，并且需要更少的运行时基础结构。这在部署大型应用程序时很重要。想象一下，将一个简单的应用程序部署到100个生产服务器上 使用Node / JVM时，我们的docker仓库必须提供100个docker镜像@ 200 MB = 20 GB(总共)。这需要镜像仓库耗费一些时间来服务。想象一下，我们希望每天部署100次。使用Go服务时，Docker镜像仓库只需提供100个Docker镜像@ 20 MB = 2 GB。可以更快，更频繁地部署大型Go应用程序，从而允许重要更新更快地实现生产。</p>
<h2>自包含部署</h2>
<p>Go应用程序部署为包含所有依赖项的单个可执行文件。不需要安装特定版本的JVM，Node或Python运行时。不必将库下载到生产服务器上。不需要对运行Go二进制文件的机器进行任何更改。甚至不需要将Go二进制文件包装到Docker中来共享它们。您只需将Go二进制文件拖放到服务器上，无论该服务器上运行的是什么，它都会在那里运行。上述描述的唯一例外是使用net和os/user包时的动态链接glibc库时。</p>
<h2>vendor依赖关系</h2>
<p>Go故意避免使用第三方库的中央存储库。Go应用程序直接链接到相应的Git存储库，并将所有相关代码下载（vendor保存）到他们自己的代码库中。这有很多好处：</p>
<ul>
<li>我们可以在使用之前查看，分析和测试第三方代码。此代码与我们自己的代码一样，是我们应用程序的一部分，应符合相同的质量，安全性和可靠性标准。</li>
<li>无需永久访问存储依赖项的各个位置。可以一次性的从任何地方（包括私人Git仓库）获取您的第三方库，并永久拥有它们。</li>
<li>在checkout后编译代码库不需要进一步下载依赖项。</li>
<li>如果互联网上某处的代码存储库突然提供不同的代码，也不会造成surprises。</li>
<li>即使软件包存储库服务性能变慢或托管软件包不再存在，部署也不会中断。</li>
</ul>
<h2>兼容性保证</h2>
<p>Go团队<a href="https://golang.org/doc/go1compat">承诺</a>，现有的程序将继续适用于新版本语言。这使得即使是大型项目也可以轻松升级到更新编译器的版本，并从新版本带来的许多性能和安全性改进中受益。同时，由于Go二进制文件包含了他们需要的所有依赖项，因此可以在同一服务器计算机上并行运行使用不同版本的Go编译器编译的二进制文件，而无需进行复杂的设置多个版本的运行时或虚拟化。</p>
<h2>文档</h2>
<p>在大型工程中，文档对于使软件易于访问和维护非常重要。与其他功能类似，Go中的文档简单实用：</p>
<ul>
<li>它嵌入在源代码中，因此两者可以同时维护。</li>
<li>它不需要特殊的语法 &#8211; 文档只是普通的源代码注释。</li>
<li>可运行的单元测试通常是最好的文档形式，所以Go允许你将它们<a href="https://blog.golang.org/examples">嵌入到文档中</a>。</li>
<li>所有文档<a href="https://blog.golang.org/godoc-documenting-go-code">实用程序</a>都内置在工具链中，因此每个人都使用它们。</li>
<li>Go linter需要导出元素的文档，以防止“文档债务”的积累。</li>
</ul>
<h2>商业支持的开源</h2>
<p>当商业实体在公开场合发展时，一些最流行和最全面设计的软件就会发生。这种设置结合了商业软件开发的优势 &#8211; 一致性和优化，使系统健壮，可靠，高效 &#8211; 具有开放式开发的优势，如来自许多行业的广泛支持，来自多个大型实体和许多用户的支持，以及长期支持，即使商业支持停止。Go就是这样开发的。</p>
<h2>缺点</h2>
<p>当然，Go并不完美，每种技术选择总是有利有弊。在进入Go之前，这里有一小部分需要考虑的方面。</p>
<h2>未成熟</h2>
<p>虽然Go的标准库在<a href="https://blog.golang.org/h2push">支持HTTP/2服务器推送</a>等许多新概念方面处于行业领先地位，但与JVM生态系统中存在的相比，用于外部API的第三方Go库可能还不那么成熟。</p>
<h2>即将到来的变化</h2>
<p>Go团队知道几乎不可能改变现有的语言元素，因此只有在完全开发后才会添加新功能。在经历了10年稳定的故意阶段后，Go团队正在考虑对语言进行<a href="https://blog.golang.org/go2draft">一系列更大的改进</a>，作为Go 2.0之旅的一部分。</p>
<h2>没有硬实时</h2>
<p>虽然Go的垃圾收集器只引入了非常短的中断，但支持硬实时需要没有垃圾收集的技术，例如Rust。</p>
<h2>结论</h2>
<p>这篇博客文章给出了一些明智的背景知识，但往往没有那么明显的选择进入Go的设计，以及当他们的代码库和团队成数量级增长时，他们将如何从许多痛苦中拯救大型工程项目。总的来说，他们将Go定位为寻求Java之外的现代编程语言的大型开发项目的绝佳选择。</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/05/03/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>使用nomad在weave网络中部署工作负载</title>
		<link>https://tonybai.com/2019/04/20/deploy-workload-in-weave-network-using-nomad/</link>
		<comments>https://tonybai.com/2019/04/20/deploy-workload-in-weave-network-using-nomad/#comments</comments>
		<pubDate>Sat, 20 Apr 2019 04:15:56 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[CIDR]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[consul]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[driver]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[hashicorp]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[iproute]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[NAT]]></category>
		<category><![CDATA[nomad]]></category>
		<category><![CDATA[openssh-server]]></category>
		<category><![CDATA[overlay]]></category>
		<category><![CDATA[overlaynetwork]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[proxy]]></category>
		<category><![CDATA[route]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[subnet]]></category>
		<category><![CDATA[task]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[virtualbox]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[weave]]></category>
		<category><![CDATA[子网]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[微服务]]></category>
		<category><![CDATA[虚拟机]]></category>
		<category><![CDATA[路由]]></category>
		<category><![CDATA[镜像]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2707</guid>
		<description><![CDATA[当初Kubernetes网络的设计目标是使得开发者使用pod时在网络这一层面可以像使用传统物理主机或虚拟机一样。具体的基本要求如下： 所有pod间均应可以在无需NAT的情况下直接通信； 所有集群节点与所有集群的Pod之间均应可以在无需NAT的情况下直接通信； 容器自身的地址和其他pod看到的它的地址是同一个地址； 按照这样的要求，集群中的每个pod都在一个平坦的、共享网络命名空间中，并且每个Pod都拥有一个IP，通信时无需端口映射。 用户也需要额外考虑如何建立Pod之间的连接，也不需要考虑将容器端口映射到主机端口等问题。基于这些要求而实现的k8s pod网络模型，将具有向后兼容的特性，可以使得Pod从某些角度上可以被看成是一个传统的物理主机或vm来对待。 在《使用nomad实现集群管理和微服务部署调度》一文中，我们看到nomad部署调度的driver为docker的服务实例都是通过主机和容器间的端口映射来对外提供服务的。服务实例多的时候，大量服务端口出现在眼前，我们很难用端口判断这是什么服务。并且通过映射端口暴露服务有局限，对于那些需要映射到主机固定端口的服务来说，很可能存在与其他服务的端口冲突而导致部署失败。除此之外，这种端口映射的方式还缺少隔离的作用，所有实例暴露的端口在同一个全局网络空间。 nomad是否可以像k8s一样将服务实例部署到overlay网络中从而实现每个服务实例所在container可以被看成一个独立的vm；并且我们还可以通过划分overlay的网段来隔离，实现某种意义上的“多租户”呢？在本篇文章中，我们来试验一下上述想法是否可行。 一、搭建试验环境 我们这次在一个VirtualBox搭建的三节点环境中进行验证。如果小伙伴对这段很熟悉，或者有现成的环境可用，那么可以跳过这一小节。另外这节不是重点，我不会对这个过程用过多文字做解释。 1. 创建虚机，组建网络 我们在一台ubuntu 18.04 desktop版本主机上搭建环境，所使用的软件版本信息如下： VirtualBox: 5.2.18 Guest OS: Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-142-generic x86_64) 组件环境的虚拟机和网络拓扑示意图如下： 如上图所示：三个vm 通过连入host-only网络(vboxnet0)实现内网通；通过连入NAT网络（NatNetwork）实现外网通。（怪异：在windows上的virtualbox实际上通过natnetwork即可实现全通的，无需host-only network，但是在ubuntu下居然不行）。 每个vm中网络配置如下： # cat /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see [...]]]></description>
			<content:encoded><![CDATA[<p>当初<a href="https://tonybai.com/tag/kubernetes">Kubernetes</a>网络的设计目标是<strong>使得开发者使用pod时在网络这一层面可以像使用传统物理主机或虚拟机一样</strong>。具体的基本要求如下：</p>
<ul>
<li>所有pod间均应可以在无需NAT的情况下直接通信；</li>
<li>所有集群节点与所有集群的Pod之间均应可以在无需NAT的情况下直接通信；</li>
<li>容器自身的地址和其他pod看到的它的地址是同一个地址；</li>
</ul>
<p>按照这样的要求，集群中的每个pod都在一个平坦的、共享网络命名空间中，并且每个Pod都拥有一个IP，通信时无需端口映射。 用户也需要额外考虑如何建立Pod之间的连接，也不需要考虑将容器端口映射到主机端口等问题。基于这些要求而实现的k8s pod网络模型，将具有向后兼容的特性，可以使得Pod从某些角度上可以被看成是一个传统的物理主机或vm来对待。</p>
<p>在<a href="https://tonybai.com/2019/03/30/cluster-management-and-microservice-deployment-and-scheduled-by-nomad/">《使用nomad实现集群管理和微服务部署调度》</a>一文中，我们看到nomad部署调度的driver为docker的服务实例都是通过主机和容器间的端口映射来对外提供服务的。服务实例多的时候，大量服务端口出现在眼前，我们很难用端口判断这是什么服务。并且通过映射端口暴露服务有局限，对于那些需要映射到主机固定端口的服务来说，很可能存在与其他服务的端口冲突而导致部署失败。除此之外，这种端口映射的方式还缺少隔离的作用，所有实例暴露的端口在同一个全局网络空间。</p>
<p>nomad是否可以像k8s一样将服务实例部署到overlay网络中从而实现每个服务实例所在container可以被看成一个独立的vm；并且我们还可以通过划分overlay的网段来隔离，实现某种意义上的“多租户”呢？在本篇文章中，我们来试验一下上述想法是否可行。</p>
<h2>一、搭建试验环境</h2>
<p>我们这次在一个<a href="https://www.virtualbox.org/">VirtualBox</a>搭建的三节点环境中进行验证。<strong>如果小伙伴对这段很熟悉，或者有现成的环境可用，那么可以跳过这一小节</strong>。另外这节不是重点，我不会对这个过程用过多文字做解释。</p>
<h3>1. 创建虚机，组建网络</h3>
<p>我们在一台<a href="https://tonybai.com/tag/ubuntu">ubuntu</a> 18.04 desktop版本主机上搭建环境，所使用的软件版本信息如下：</p>
<ul>
<li>
<p>VirtualBox: 5.2.18</p>
</li>
<li>
<p>Guest OS: Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-142-generic x86_64)</p>
</li>
</ul>
<p>组件环境的虚拟机和网络拓扑示意图如下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/virtualbox-3-vm-environment-for-nomad.png" alt="img{512x368}" /></p>
<p>如上图所示：三个vm 通过连入host-only网络(vboxnet0)实现内网通；通过连入NAT网络（NatNetwork）实现外网通。（怪异：在windows上的virtualbox实际上通过natnetwork即可实现全通的，无需host-only network，但是在ubuntu下居然不行）。</p>
<p>每个vm中网络配置如下：</p>
<pre><code># cat /etc/network/interfaces

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp0s3
iface enp0s3 inet dhcp

auto enp0s8
iface enp0s8 inet dhcp

</code></pre>
<p>保存后，执行/etc/init.d/networking restart生效。</p>
<p>另外每个vm上安装了openssh-server(apt install openssh-server)并设置root可登陆。三个vm的主机名分为为u1、u2和u3（可通过hostnamectl &#8211;static set-hostname u1设置。并在/etc/hosts中添加主机名和内网IP的对应关系）。</p>
<p>每台主机上安装了docker引擎(通过apt install docker.io安装），docker版本信息如下：</p>
<pre><code># docker version
Client:
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        6247962
 Built:             Tue Feb 26 23:56:24 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       6247962
  Built:            Tue Feb 12 22:47:29 2019
  OS/Arch:          linux/amd64
  Experimental:     false

</code></pre>
<h2>二、使用weave创建跨节点的overlay network</h2>
<p>我们选择<a href="https://www.weave.works/">weave</a>作为overlay network的实现。</p>
<h3>1. 安装weave</h3>
<p>我们在每个vm节点上安装目前最新版本的weave，以一个节点为例：</p>
<pre><code># curl -L git.io/weave -o /usr/local/bin/weave
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:02 --:--:--     0
100   595    0   595    0     0     62      0 --:--:--  0:00:09 --:--:--   137
100 52227  100 52227    0     0   4106      0  0:00:12  0:00:12 --:--:-- 21187

# chmod a+x /usr/local/bin/weave

# weave version
weave script 2.5.1

... ...

</code></pre>
<p>通过weave setup预先将weave相关的容器Image下载到各个节点，为后面的weave launch所使用。</p>
<pre><code># weave setup

2.5.1: Pulling from weaveworks/weave
... ...
c458f7a37ca6: Pull complete
Digest: sha256:a170dd93fa7e678cc37919ffd65601d1015da6c3f10878534ac237381ea0db19
Status: Downloaded newer image for weaveworks/weave:2.5.1
2.5.1: Pulling from weaveworks/weaveexec
... ...
c11f30d06b58: Pull complete
Digest: sha256:ad53aaabf648548ec26cceac3ab49394778322e1623f0d184a2b74ad06338087
Status: Downloaded newer image for weaveworks/weaveexec:2.5.1
latest: Pulling from weaveworks/weavedb
9b0681f946a1: Pull complete
Digest: sha256:c280cf4e7208f4ca0d2514539e0f476dd12db70beacdc368793b7736de023d8d
Status: Downloaded newer image for weaveworks/weavedb:latest

</code></pre>
<h3>2. 启动跨多节点(peer) weave network</h3>
<p>weave的一个优点是建立跨节点overlay network时并不需要一个外部的存储(比如etcd），位于多个节点上的weave进程会自动同步相关信息。而且weave支持动态向weave overlay network中添加节点。</p>
<p>我们来初始化这个由三个vm节点构成的weave overlay network：</p>
<pre><code>root@u1:~# weave launch --no-dns 192.168.56.4 192.168.56.5
78f459a4a8acc07d46c1f86a15a519b91978c809876452b9d9c1294e760394a9

root@u2:~# weave launch --no-dns 192.168.56.3 192.168.56.5
1f379e50f3917e05bd133589f75594d7b2da20a680bb1e5e7172e37a18abe3ff

root@u3:~# weave launch --no-dns 192.168.56.3 192.168.56.4
aa600bfad8db8711e2cbc5f8e127022460ca3738226dd7aa33bb5b9b049f8cee

</code></pre>
<p>执行完上面命令后，在任意一个vm节点上执行下面命令，查看节点weave之间的连接状态：</p>
<pre><code>root@u1:~# weave status connections
&lt;- 192.168.56.4:54715    established fastdp 8e:d8:ad:a8:32:eb(u2) mtu=1376
&lt;- 192.168.56.5:51504    established fastdp f6:58:43:5c:68:d7(u3) mtu=1376

</code></pre>
<p>我们看到u1节点已经和u2、u3节点成功建立了连接，weave的工作模式是fastdp(fast data path)，mtu为默认的1376（<a href="https://tonybai.com/2019/04/18/benchmark-result-of-k8s-network-plugin-cni/">适当调节weave mtu可以提升weave overlay network的网络性能</a>）。<br />
我们也可以通过weave status命令查看一下weave网络的整体状态：</p>
<pre><code># weave status

        Version: 2.5.1 (up to date; next check at 2019/04/18 12:35:41)

        Service: router
       Protocol: weave 1..2
           Name: f6:58:43:5c:68:d7(u3)
     Encryption: disabled
  PeerDiscovery: enabled
        Targets: 3
    Connections: 3 (2 established, 1 failed)
          Peers: 3 (with 6 established connections)
 TrustedSubnets: none

        Service: ipam
         Status: ready
          Range: 10.32.0.0/12
  DefaultSubnet: 10.32.0.0/12

        Service: dns
         Domain: weave.local.
       Upstream: 10.0.3.3
            TTL: 1
        Entries: 0

        Service: proxy
        Address: unix:///var/run/weave/weave.sock

        Service: plugin (legacy)
     DriverName: weave

</code></pre>
<h3>3. 在weave overlay network中创建container并测试overlay网内container的互通性</h3>
<p>我们通过为docker指定net driver为weave的方式让docker在weave overlay network中创建container：</p>
<pre><code>root@u1:~# docker run -ti --net=weave busybox /bin/sh

root@u2:~# docker run -ti --net=weave busybox /bin/sh

root@u3:~# docker run -ti --net=weave busybox /bin/sh

</code></pre>
<p>我们在u1上启动的容器内去ping位于其他两个vm上启动的新容器：</p>
<pre><code>/ # ping -c 3 10.32.0.1
PING 10.32.0.1 (10.32.0.1): 56 data bytes
64 bytes from 10.32.0.1: seq=0 ttl=64 time=1.540 ms
64 bytes from 10.32.0.1: seq=1 ttl=64 time=1.548 ms
64 bytes from 10.32.0.1: seq=2 ttl=64 time=1.434 ms

--- 10.32.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.434/1.507/1.548 ms

/ # ping -c 3 10.46.0.0
PING 10.46.0.0 (10.46.0.0): 56 data bytes
64 bytes from 10.46.0.0: seq=0 ttl=64 time=5.118 ms
64 bytes from 10.46.0.0: seq=1 ttl=64 time=1.608 ms
64 bytes from 10.46.0.0: seq=2 ttl=64 time=1.837 ms

--- 10.46.0.0 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.608/2.854/5.118 ms

</code></pre>
<p>我们看到位于weave overlay network中的三个容器是连通的。</p>
<h3>4. 测试host到weave overlay网络中容器的连通性</h3>
<p>考虑到后续host上的<a href="https://tonybai.com/2018/09/10/setup-service-discovery-and-load-balance-based-on-consul/">consul</a>会对部署在weave overlay network中的container中的服务做health check，因此需要在host上能连通位于overlay network中的container。</p>
<p>我们来测试一下：</p>
<pre><code>root@u1:~# docker run -ti --net=weave busybox /bin/sh

/ # ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
29: ethwe0@if30: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1376 qdisc noqueue
    link/ether aa:8f:45:8f:5f:d6 brd ff:ff:ff:ff:ff:ff
    inet 10.40.0.0/12 brd 10.47.255.255 scope global ethwe0
       valid_lft forever preferred_lft forever
31: eth0@if32: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1500 qdisc noqueue
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever

root@u1:~# ping 10.40.0.0
PING 10.40.0.0 (10.40.0.0) 56(84) bytes of data.

^C
--- 10.40.0.0 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3024ms

</code></pre>
<p>从测试结果来看，在host无法ping通位于weave network上的container。这个问题实则也显而易见，因为当前host上的路由表中没有以weave网络range: 10.32.0.0/12为目的地址的路由，并且weave网络设备也并未启用ip地址：</p>
<pre><code>root@u1:~# ip route
default via 10.0.3.2 dev enp0s8
10.0.3.0/24 dev enp0s8  proto kernel  scope link  src 10.0.3.15
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
172.18.0.0/16 dev docker_gwbridge  proto kernel  scope link  src 172.18.0.1
192.168.56.0/24 dev enp0s3  proto kernel  scope link  src 192.168.56.3

</code></pre>
<p>关于这个问题，weave官方给出了<a href="https://www.weave.works/docs/net/latest/tasks/manage/host-network-integration/">答案</a>：我们可以通过weave expose命令自动为主机上的weave设备分配ip地址，添加到10.32.0.0/12的路由。</p>
<pre><code>root@u1:~# weave expose
10.40.0.1

root@u1:~# ip a

.... ...

7: weave: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1376 qdisc noqueue state UP group default qlen 1000
    link/ether b2:97:b5:7b:0f:a9 brd ff:ff:ff:ff:ff:ff
    inet 10.40.0.1/12 brd 10.47.255.255 scope global weave
       valid_lft forever preferred_lft forever
    inet6 fe80::b097:b5ff:fe7b:fa9/64 scope link
       valid_lft forever preferred_lft forever

.... ...

root@u1:~# ip route
default via 10.0.3.2 dev enp0s8
10.0.3.0/24 dev enp0s8  proto kernel  scope link  src 10.0.3.15
10.32.0.0/12 dev weave  proto kernel  scope link  src 10.40.0.1
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
172.18.0.0/16 dev docker_gwbridge  proto kernel  scope link  src 172.18.0.1
192.168.56.0/24 dev enp0s3  proto kernel  scope link  src 192.168.56.3

</code></pre>
<p>我们看到在u1节点上执行完expose之后，weave设备拥有了自己的ip地址，并且主机路由表中也增加了10.32.0.0/12网络的路由。我们再来测试一下u1上主机到container是否通了：</p>
<pre><code>root@u1:~# ping 10.40.0.0
PING 10.40.0.0 (10.40.0.0) 56(84) bytes of data.
64 bytes from 10.40.0.0: icmp_seq=1 ttl=64 time=4.42 ms

64 bytes from 10.40.0.0: icmp_seq=2 ttl=64 time=1.04 ms
64 bytes from 10.40.0.0: icmp_seq=3 ttl=64 time=1.21 ms
^C
--- 10.40.0.0 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.048/2.228/4.425/1.554 ms

</code></pre>
<p>网络已经打通。我们继续在u2、u3两个节点上执行weave expose，这样三台主机都可以通过网络reach到位于任何一台主机上的、weave network中的container。</p>
<p>而从container到host，原本就可以访问，以u1上的container为例：</p>
<pre><code>/ # ping 192.168.56.3
PING 192.168.56.3 (192.168.56.3): 56 data bytes
64 bytes from 192.168.56.3: seq=0 ttl=64 time=0.345 ms
^C
--- 192.168.56.3 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.345/0.345/0.345 ms

/ # ping 192.168.56.4
PING 192.168.56.4 (192.168.56.4): 56 data bytes
64 bytes from 192.168.56.4: seq=0 ttl=63 time=1.277 ms
^C
--- 192.168.56.4 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 1.277/1.277/1.277 ms

</code></pre>
<h2>三、安装consul和nomad集群</h2>
<p>在<a href="https://tonybai.com/2019/03/30/cluster-management-and-microservice-deployment-and-scheduled-by-nomad/">《使用nomad实现集群管理和微服务部署调度》</a>一文中，我们已经详细说过consul和nomad的安装配置过程，这里仅列出步骤，不再详细说明。已经有环境的朋友可以略过该步骤！</p>
<h3>1. 安装consul</h3>
<p>在每个节点上执行下面步骤安装：</p>
<pre><code># wget -c https://releases.hashicorp.com/consul/1.4.4/consul_1.4.4_linux_amd64.zip
# unzip consul_1.4.4_linux_amd64.zip
# mv consul /usr/local/bin

# mkdir -p ~/consul-install/consul-data

</code></pre>
<p>启动consul集群：</p>
<pre><code>u1:

# nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-1 -client=0.0.0.0 -bind=192.168.56.3 -datacenter=dc1 &gt; consul-1.log &amp; 2&gt;&amp;1

u2:

# nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-2 -client=0.0.0.0 -bind=192.168.56.4 -datacenter=dc1 -join 192.168.56.3 &gt; consul-2.log &amp; 2&gt;&amp;1

u3:

nohup consul agent -server -ui -dns-port=53  -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-3 -client=0.0.0.0 -bind=192.168.56.5 -datacenter=dc1 -join 192.168.56.3 &gt; consul-3.log &amp; 2&gt;&amp;1
</code></pre>
<p>查看启动状态：</p>
<pre><code>#  consul operator raft list-peers
Node      ID                                    Address            State     Voter  RaftProtocol
consul-1  db838e7c-2b02-949b-763b-a6646ee51981  192.168.56.3:8300  leader    true   3
consul-2  33c81139-5054-7e76-f320-7d28d7528cc8  192.168.56.4:8300  follower  true   3
consul-3  4eda7d24-3fe2-45f5-f4ad-b95fa39f13c1  192.168.56.5:8300  follower  true   3

</code></pre>
<p>如果输出类似上面的日志，则说明consul集群启动成功！</p>
<p>接下来为了利用consul内嵌的DNS server，我们修改一下各个node的DNS配置 /etc/resolvconf/resolv.conf.d/base：</p>
<pre><code>//  /etc/resolvconf/resolv.conf.d/base

nameserver 192.168.56.3
nameserver 192.168.56.4

options timeout:2 attempts:3 rotate single-request-reopen

# /etc/init.d/resolvconf restart

[ ok ] Restarting resolvconf (via systemctl): resolvconf.service.
</code></pre>
<h3>2. 安装nomad并启动nomad集群</h3>
<p>下面是在每个node上安装nomad的步骤：</p>
<pre><code># wget -c https://releases.hashicorp.com/nomad/0.8.7/nomad_0.8.7_linux_amd64.zip

# mkdir nomad-install

# unzip nomad_0.8.7_linux_amd64.zip

# mv nomad /usr/local/bin

# nomad version
Nomad v0.8.7 (21a2d93eecf018ad2209a5eab6aae6c359267933+CHANGES)

</code></pre>
<p>在每个node上创建agent.hcl文件，放到nomad-install下面：</p>
<pre><code>// agent.hcl

data_dir = "/root/nomad-install/nomad.d"

bind_addr = "192.168.56.3" //node 内网ip，这里以u1 host为例

server {
  enabled = true
  bootstrap_expect = 3
}

client {
  enabled = true
}

</code></pre>
<p>启动集群(基于consul)：</p>
<pre><code>u1:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-1.log &amp; 2&gt;&amp;1

u2:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-2.log &amp; 2&gt;&amp;1

u3:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-3.log &amp; 2&gt;&amp;1

</code></pre>
<p>查看nomad集群状态：</p>
<pre><code># nomad server members -address="http://192.168.56.3:4646"
Name       Address       Port  Status  Leader  Protocol  Build  Datacenter  Region
u1.global  192.168.56.3  4648  alive   false   2         0.8.7  dc1         global
u2.global  192.168.56.4  4648  alive   true    2         0.8.7  dc1         global
u3.global  192.168.56.5  4648  alive   false   2         0.8.7  dc1         global

# nomad operator raft list-peers -address="http://192.168.56.3:4646"
Node       ID                 Address            State     Voter  RaftProtocol
u3.global  192.168.56.5:4647  192.168.56.5:4647  follower  true   2
u2.global  192.168.56.4:4647  192.168.56.4:4647  leader    true   2
u1.global  192.168.56.3:4647  192.168.56.3:4647  follower  true   2

</code></pre>
<p>nomad集群启动成功！</p>
<h2>四. nomad实现在weave overlay network中的job部署</h2>
<h3>1. 创建位于weave overlay network中的nomad task service实例</h3>
<p>我们定义如下nomad job的配置文件：</p>
<pre><code>//httpbackend.nomad

job "httpbackend" {
  datacenters = ["dc1"]
  type = "service"

  group "httpbackend" {
    count = 3

    task "httpbackend" {
      driver = "docker"
      config {
        image = "bigwhite/httpbackendservice:v1.0.0"
        dns_servers =  ["192.168.56.3", "192.168.56.4", "192.168.56.5"]
        network_mode = "weave"
        logging {
          type = "json-file"
        }
      }

      resources {
        network {
          mbits = 10
        }
      }

      service {
        name = "httpbackend"
      }
    }
  }
}

</code></pre>
<p>与之前文章中job的配置文件不同的是，该job配置在task的config中增加了：</p>
<ul>
<li>
<p>dns_servers：由于docker 18.09在-net=weave下，container没有继承host的/etc/resolv.conf文件，我们为了能在container中通过服务的domain查询到其真实ip地址，我们在docker的执行参数中加入dns_servers，我们将u1,u2,u3都作为dns server提供了。</p>
</li>
<li>
<p>network_node：我们希望nomad调度负载、创建docker容器时将docker container创建在weave network中，因此我们在network_node中传入”weave”，这就相当于在执行docker时执行：docker run &#8230; &#8211;net=weave &#8230; &#8230;</p>
</li>
</ul>
<p>我们来创建一下该job：</p>
<pre><code># nomad job run -address=http://192.168.56.3:4646 httpbackend.nomad

==&gt; Monitoring evaluation "806eaecf"
    Evaluation triggered by job "httpbackend"
    Allocation "6e06be74" created: node "11212ed9", group "httpbackend"
    Allocation "e7ed8569" created: node "aa5a06fe", group "httpbackend"
    Allocation "fd6c6a05" created: node "fe7a7e9c", group "httpbackend"
    Evaluation status changed: "pending" -&gt; "complete"
==&gt; Evaluation "806eaecf" finished with status "complete"

# nomad job status -address=http://192.168.56.3:4646  httpbackend
ID            = httpbackend
Name          = httpbackend
Submit Date   = 2019-04-19T13:18:21+08:00
Type          = service
Priority      = 50
Datacenters   = dc1
Status        = running
Periodic      = false
Parameterized = false

Summary
Task Group   Queued  Starting  Running  Failed  Complete  Lost
httpbackend  0       0         3        0       0         0

Allocations
ID        Node ID   Task Group   Version  Desired  Status   Created  Modified
6e06be74  11212ed9  httpbackend  0        run      running  54s ago  7s ago
e7ed8569  aa5a06fe  httpbackend  0        run      running  54s ago  6s ago
fd6c6a05  fe7a7e9c  httpbackend  0        run      running  54s ago  12s ago

</code></pre>
<p>我们查看一下u1节点上的httpbackend负载的状态和ip：</p>
<pre><code>root@u1:~/nomad-install/jobs# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS               NAMES
2e2229cf8f64        c196c122feea             "/root/httpbackendse…"   49 seconds ago      Up 48 seconds                           httpbackend-e7ed8569-fdde-537b-91b3-84583d1ea238
912ac43350f7        weaveworks/weave:2.5.1   "/home/weave/weaver …"   22 hours ago        Up 22 hours                             weave

root@u1:~/nomad-install/jobs# docker exec 2e2229cf8f64 ip a
... ...
49: ethwe0@if50: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1376 qdisc noqueue
    link/ether a2:f1:ef:d7:89:ee brd ff:ff:ff:ff:ff:ff
    inet 10.40.0.0/12 brd 10.47.255.255 scope global ethwe0
       valid_lft forever preferred_lft forever
.... ...

</code></pre>
<p>我们看到新创建的container的ip为10.40.0.0，是weave network subnet range中的一个地址。</p>
<p>我们访问一下该服务：</p>
<pre><code># curl http://10.40.0.0:8081
this is httpbackendservice, version: v1.0.0

</code></pre>
<p>我们看到了预期返回的结果。通过consul的域名访问也同样ok：</p>
<pre><code># curl httpbackend.service.dc1.consul:8081
this is httpbackendservice, version: v1.0.0

</code></pre>
<p>我们从一个位于weave network中的container中去访问httpbackend服务，依然会得到正确的应答结果：</p>
<pre><code># docker run -ti --net=weave --dns=192.168.56.3 --dns=8.8.8.8 ubuntu /bin/bash

root@3fe76a39b66f:/# curl httpbackend.service.dc1.consul:8081
this is httpbackendservice, version: v1.0.0

</code></pre>
<h2>五、 应用隔离</h2>
<p>有些时候我们需要将部署的应用之间做隔离，让彼此无法互相访问。weave overlay network是支持这样做的，我们一起来看一下。</p>
<h3>1.重建weave网络</h3>
<p>我们首先需要重新创建weave网络，使之能支持划分不同subnet。</p>
<p>先在每个node上执行下面命令，将原有的weave网络清理干净：</p>
<pre><code># weave reset

</code></pre>
<p>执行后，发现weave网络设备、weave相关容器、路由表中有关weave的路由都不见了。</p>
<p>我们重新建立三节点的weave网络，在这个10.32.0.0/16的大网中，我们划分若干subnet，默认的subnet为10.32.0.0/24。</p>
<pre><code>u1:

# weave launch --no-dns --ipalloc-range 10.32.0.0/16 --ipalloc-default-subnet 10.32.0.0/24 192.168.56.4 192.168.56.5

# weave expose

u2:

# weave launch --no-dns --ipalloc-range 10.32.0.0/16 --ipalloc-default-subnet 10.32.0.0/24 192.168.56.3 192.168.56.5

# weave expose

u3:

# weave launch --no-dns --ipalloc-range 10.32.0.0/16 --ipalloc-default-subnet 10.32.0.0/24 192.168.56.3 192.168.56.4

# weave expose

</code></pre>
<p>接下来我们在不同的subnet下分别建立两个container：</p>
<p>首先在u1上，在default subnet下建立两个container a1和a2：</p>
<pre><code>#docker run -ti --net=weave --dns=192.168.56.3 --dns=8.8.8.8 --name a1 busybox /bin/sh

#docker run -ti --net=weave --dns=192.168.56.3 --dns=8.8.8.8 --name a2 busybox /bin/sh

</code></pre>
<p>再在u2上在subnet 10.32.1.0/24下建立两个container：b1和b2</p>
<pre><code>u2上：

# docker run -ti --net=weave --dns=192.168.56.3 --dns=8.8.8.8 -e WEAVE_CIDR=net:10.32.1.0/24 --name b1 busybox /bin/sh

# docker run -ti --net=weave --dns=192.168.56.3 --dns=8.8.8.8 -e WEAVE_CIDR=net:10.32.1.0/24 --name b2 busybox /bin/sh

</code></pre>
<p>我们经过测试发现：a1与a2、a1与b1都是可以ping通的，这与我们的预期a1与b1、b2不通不符。我们发现b1(10.32.0.2)、b2(10.32.0.3)两个容器的ip地址居然依然在default subnet内，似乎通过环境变量WEAVE_CIDR传递的subnet信息没有生效。<br />
在weave的一个<a href="https://github.com/weaveworks/weave/issues/2420">issue</a>中，有开发者提到：WEAVE_CIDR仅用于weave proxy模式，在weave作为plugin模式工作时，docker不会将该环境变量信息传递给weave。也就是说即便上面在u2上创建b1、b2时设置了环境变量WEAVE_CIDR，weave插件也无法得到该信息，于是依旧在默认subnet范围为b1、b2分配了ip。</p>
<h3>2. 让docker使用weave proxy模式</h3>
<p>weave proxy是位于docker client与docker engine(docker daemon)之间的代理服务：</p>
<pre><code>docker client --&gt; weave proxy ---&gt; docker engine/daemon

</code></pre>
<p>默认情况下，/var/run/docker.sock是docker client和docker engine之间的通信“媒介”，Docker daemon默认监听的Unix域套接字(Unix domain socket)：/var/run/docker.sock，docker client以及容器中的进程可以通过它与Docker daemon进行通信。</p>
<p>我们可通过docker -H xxx.sock或通过设置 DOCKER_HOST环境变量的方式让docker client与传入的unix socket通信。这样我们就可以将weave proxy的套接字unix:///var/run/weave/weave.sock（通过weave env查看到）传给docker client了。我们来测试一下：</p>
<pre><code>u1:

# docker -H unix:///var/run/weave/weave.sock run -ti --dns=192.168.56.3 --dns=8.8.8.8 --name a1 busybox /bin/sh

# docker -H unix:///var/run/weave/weave.sock run -ti --dns=192.168.56.3 --dns=8.8.8.8 --name a2 busybox /bin/sh

u2:

# docker -H unix:///var/run/weave/weave.sock  run -ti --dns=192.168.56.3 --dns=8.8.8.8 -e WEAVE_CIDR=net:10.32.1.0/24 --name b1 busybox /bin/sh

#docker -H unix:///var/run/weave/weave.sock run -ti --dns=192.168.56.3 --dns=8.8.8.8 -e WEAVE_CIDR=net:10.32.1.0/24 --name b2 busybox /bin/sh

</code></pre>
<p>四个container启动后，我们发现b1、b2的ip地址都在WEAVE_CIDR指定的空间内，a1、a2间互通；b1、b2间互通，但a1、a2与b1、b2间是不通的。这样就与预期相符了。</p>
<h3>3. nomad与weave proxy模式集成实现应用工作负载的隔离</h3>
<p>接下来，我们来看看如何将nomad和weave的proxy模式集成在一起，实现工作负载分配在不同subnet。</p>
<p>这里我们就无法仅仅通过在job配置文件中传入参数的方式来实现了，我们需要修改一下agent.hcl并重启nomad集群。以u1节点上的agent.hcl为例，我们需要改为下面这样：</p>
<pre><code>data_dir = "/root/nomad-install/nomad.d"

bind_addr = "192.168.56.5"

server {
  enabled = true
  bootstrap_expect = 3
}

client {
  enabled = true
  "options":{
     "docker.endpoint":"unix://var/run/weave/weave.sock"
  }
}

</code></pre>
<p>我们在client配置block中增加一个options，设置了docker.endpoint为weave proxy监听的weave.sock。重启集群：</p>
<pre><code>u1:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-1.log &amp; 2&gt;&amp;1

u2:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-2.log &amp; 2&gt;&amp;1

u3:

# nohup nomad agent -config=/root/nomad-install/agent.hcl  &gt; nomad-3.log &amp; 2&gt;&amp;1

</code></pre>
<p>接下来，我们重建一个httpbackend-another-subnet.nomad，内容如下：</p>
<pre><code>//httpbackend-another-subnet.nomad

job "httpbackend" {
  datacenters = ["dc1"]
  type = "service"

  group "httpbackend" {
    count = 3

    task "httpbackend" {
      driver = "docker"
      config {
        image = "bigwhite/httpbackendservice:v1.0.0"
        dns_servers =  ["192.168.56.3", "192.168.56.4", "192.168.56.5"]
        logging {
          type = "json-file"
        }
      }

      env {
        WEAVE_CIDR="net:10.32.1.0/24"
      }

      resources {
        network {
          mbits = 10
        }
      }

      service {
        name = "httpbackend"
      }
    }
  }
}

</code></pre>
<p>我们去掉了network_mode = “weave”，增加了一个env：WEAVE_CIDR=”net:10.32.1.0/24&#8243;。run这个job：</p>
<pre><code># nomad job run -address=http://192.168.56.3:4646 httpbackend-another-subnet.nomad
==&gt; Monitoring evaluation "e94bdd00"
    Evaluation triggered by job "httpbackend"
    Allocation "3f5032b5" created: node "11212ed9", group "httpbackend"
    Allocation "40d75ae8" created: node "aa5a06fe", group "httpbackend"
    Allocation "627fe1e7" created: node "fe7a7e9c", group "httpbackend"
    Evaluation status changed: "pending" -&gt; "complete"
==&gt; Evaluation "e94bdd00" finished with status "complete"

# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS               NAMES
700bbea7c89e        c196c122feea             "/w/w /root/httpback…"   17 seconds ago      Up 16 seconds                           httpbackend-40d75ae8-fe75-c560-b87b-c1272db4850c
8b7e29522b8b        weaveworks/weave:2.5.1   "/home/weave/weaver …"   10 hours ago        Up 10 hours                             weave
root@u1:~/nomad-install/jobs# docker exec 700bbea7c89e ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
142: eth0@if143: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
144: ethwe@if145: &lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&gt; mtu 1376 qdisc noqueue
    link/ether f2:55:9d:26:72:56 brd ff:ff:ff:ff:ff:ff
    inet 10.32.1.192/24 brd 10.32.1.255 scope global ethwe
       valid_lft forever preferred_lft forever

</code></pre>
<p>我们看到新创建的httpbackend container的ip已经分配到10.32.1.0/24 subnet下面了。这种方式使得我们可以任意安排我们的job放入哪个subnet。</p>
<h3>4. 遗留问题</h3>
<p>我们通过consul go api试图从consul中获取service: httpbackend的ip信息，我们得到了如下的输出：</p>
<pre><code>#  ./services
10.0.3.15 : 0
10.0.3.15 : 0
10.0.3.15 : 0
[]

</code></pre>
<p>如果在httpbackend的service配置中使用如下配置：</p>
<pre><code> service {
        name = "httpbackend"
        address_mode = "driver"
      }

</code></pre>
<p>那么，我们得到的是下面结果：</p>
<pre><code># ./services
172.17.0.3 : 0
172.17.0.2 : 0
172.17.0.2 : 0
[]

</code></pre>
<p>也就是说nomad在consul中记录的container的advertise ip不是我们想要的weave subnet网段的ip信息，这样就会导致我们通过consul的DNS服务或者通过consul api获取的服务ip信息有误，导致无法通过这两种方式访问到服务实例。在nomad的最新版v0.9.0中该问题依然存在。</p>
<p>综上，“隔离”的目的得到了部分满足，期待后续nomad的改进。</p>
<h2>六、参考资料</h2>
<ul>
<li>
<p>https://www.weave.works/docs/net/latest/install/installing-weave/</p>
</li>
<li>
<p>https://www.weave.works/docs/net/latest/install/using-weave/#peer-connections</p>
</li>
<li>
<p>https://www.weave.works/docs/net/latest/install/plugin/plugin/#launching</p>
</li>
<li>
<p>https://www.weave.works/docs/net/latest/tasks/manage/host-network-integration/</p>
</li>
<li>
<p>https://docs.docker.com/v17.09/engine/userguide/networking/configure-dns/</p>
</li>
<li>
<p>https://www.nomadproject.io/docs/drivers/docker.html#client-requirements</p>
</li>
<li>
<p>https://www.weave.works/docs/net/latest/tasks/manage/application-isolation/</p>
</li>
<li>
<p>https://www.weave.works/docs/net/latest/tasks/weave-docker-api/weave-docker-api/</p>
</li>
<li>
<p>https://www.nomadproject.io/docs/drivers/docker.html</p>
</li>
<li>
<p>https://www.nomadproject.io/docs/configuration/client.html</p>
</li>
<li>
<p>https://www.nomadproject.io/docs/job-specification/service.html#using-driver-address-mode</p>
</li>
<li>
<p>https://success.docker.com/article/networking</p>
</li>
</ul>
<p>本文涉及到的配置文件和源码，参见<a href="https://github.com/bigwhite/experiments/tree/master/nomad-demo/part3">这里</a>。</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/04/20/deploy-workload-in-weave-network-using-nomad/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>源创会2017沈阳站讲稿：基于Harbor的高可用企业级私有容器镜像仓库部署实践</title>
		<link>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/</link>
		<comments>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/#comments</comments>
		<pubDate>Mon, 23 Oct 2017 08:28:35 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[beego]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[CD]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[cgroups]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[daemon.json]]></category>
		<category><![CDATA[distribution]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[docker-registry]]></category>
		<category><![CDATA[dotCloud]]></category>
		<category><![CDATA[DX]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GroupReplication]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[KVM]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-container]]></category>
		<category><![CDATA[loadbalance]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[MGR]]></category>
		<category><![CDATA[mount]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[namespaces]]></category>
		<category><![CDATA[osc]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[portus]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[Scaling]]></category>
		<category><![CDATA[SUSE]]></category>
		<category><![CDATA[unionfs]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[vmware]]></category>
		<category><![CDATA[Xen]]></category>
		<category><![CDATA[伸缩]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[开发流水线]]></category>
		<category><![CDATA[开源中国]]></category>
		<category><![CDATA[持续交付]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[虚拟机]]></category>
		<category><![CDATA[镜像仓库]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2427</guid>
		<description><![CDATA[上周六开源中国的源创会在沈阳举办了一次技术活动，很荣幸以本地讲师的身份和大家交流了一个topic: “基于Harbor的高可用企业级私有容器镜像仓库部署实践”。之所以选择这个topic，是因为这是我们团队的项目实践心得。很多企业和组织在深入使用Docker之后，都会有类似的高可用私有容器仓库搭建的需求，于是我就把我们摸索的实践和填坑过程拿出来，用30分钟与大家分享一下。另外这算是一个入门级的分享，并未深入过多原理。以下就是本次分享的内容讲稿整理。如有不妥或不正确的地方，欢迎交流指正。 大家下午好，欢迎各位来到源创会沈阳站。在这里我也代表沈阳的IT人欢迎源创会来到沈阳，希望能有更多的像源创会这样的组织到沈阳举办技术活动。非常高兴能有这个机会在源创会这个平台上做分享， 今天和大家一起探讨的题目是：“基于Harbor的高可用企业级私有容器镜像仓库部署实践”。题目有些长，简单来说就是如何搭建一个好用的镜像仓库。 首先做个简单的自我介绍。我叫白明，东软(注：源创会这次活动的会场在东软沈阳园区)是我的主场，在这里工作很多年，目前就职东软云科技；Gopher一枚，近两年主要使用Go语言开发；技术译者，曾参与翻译过《七周七语言》一书；并且参与过智慧城市架构系列丛书的编著工作；GopherChina大会讲师，这里顺便说一下GopherChina大会，它是目前中国地区规模最大、水平最高的Go语言技术大会，一般每年4月份在北京或上海举行。希望有志于Go语言开发的开发者积极参与；Blogger，写博10多年，依旧笔耕不倦；目前主要从事Docker&#38;kubernetes的研究和实践。 当今，IT技术发展飞快。五年前， IT从业者口中谈论最多的技术是Virtual Machine，即虚拟化技术，人们经常争论的是到底是vmware的技术好,还是原生kvm技术稳定，又或是xen的技术完美。转眼间五年过去了，大家口中经常讨论的技术词汇发生了变化，越来越多的技术人在谈论Docker，谈论容器。 Docker是什么？ Docker这门技术非常热，但我们要透过现象看其本质： Docker技术并不是新技术，而是将已有技术进行了更好的整合和包装。 内核容器技术以一种完整形态最早出现在Sun公司的Solaris操作系统上，Solaris是当时最先进的服务器操作系统。2005年Solaris发布Solaris Container技术，从此开启了内核容器之门。 IT技术发展的趋势就是这样：商业有的，开源也要有。三年后，即2008年，以Google公司开发人员为主导的Linux Container，LXC功能在被merge到Linux内核。LXC是一种内核级虚拟化技术，主要基于namespaces和cgroup技术，实现共享一个os kernel前提下的进程资源隔离，为进程提供独立的虚拟执行环境，这样的一个虚拟的执行环境就是一个容器。本质上说，LXC容器与现在的Docker所提供容器是一样的。但是，当时LXC处于早期阶段，开发人员可能更为关注LXC的技术实现，而对开发体验方面有所忽略，导致LXC技术使用门槛较高，普通应用开发者学习、理解和使用它的心智负担较高，因此应用并不广泛。 这一情况一直持续到2013年，当时美国一家名不见经传的公司dotCloud发布了一款平台工具Docker，对外宣称可以实现：“build,ship and run any app and anywhere”。Docker实质上也是基于namespaces和cgroup技术的，Docker的创新之处在于其基于union fs技术定义了一套应用打包规范，真正将应用及其运行的所有依赖都封装到一个特定格式的文件中，这种文件就被称为image，即镜像文件。同时，Docker还提供了一套抽象层次更高的工具集，这套工具对dev十分友好，具有良好的开发体验(Developer eXperience)，开发者无需关心namespace, cgroups之类底层技术，即可很easy的启动一个承载着其应用的容器： Docker run ubuntu echo hello 因此， 从2013发布以来，Docker项目就像坐上了火箭，发展迅猛，目前已经是github上最火爆的开源项目之一。这里还要提一点就是：Docker项目是使用go语言开发的，Docker项目的成功，也或多或少得益于Go优异的开发效率和执行效率。 Docker技术的出现究竟给我们带来了哪些好处呢，个人觉得至少有以下三点： 交付标准化：Docker使得应用程序和依赖的运行环境真正绑定结合为一体，得之即用。这让开发人员、测试和运维实现了围绕同一交付物，保持开发交付上下文同步的能力，即“test what you write, ship what you test”； 执行高效化：应用的启动速度从原先虚拟机的分钟级缩短到容器的秒级甚至ms级，使得应用可以支持快速scaling伸缩； 资源集约化：与vm不同的是，Container共享一个内核，这使得一个container的资源消耗仅为进程级别或进程组级别。同时，容器的镜像也因为如此，其size可以实现的很小，最小可能不足1k，平均几十M。与vm动辄几百兆的庞大身段相比，具有较大优势。 有了image文件后，自然而言我们就有了对image进行存取和管理的需求，即我们需要一个镜像仓库，于是Docker推出了Docker registry这个项目。Docker Registry就是Docker image的仓库，用来存储、管理和分发image的；Docker registry由Docker公司实现，项目名为distribution，其实现了Docker Registr 2.0协议，与早前的Registry 1.x协议版本相比，Distribution采用Go语言替换了Python，在安全性和性能方面都有了大幅提升；Docker官方运行着一个世界最大的公共镜像仓库：hub.docker.com，最常用的image都在hub上，比如反向代理nginx、redis、ubuntu等。鉴于国内访问hub网速不佳，多使用国内容器服务厂商提供的加速器。Docker官方还将Registry本身打入到了一个image中，方便开发人员快速以容器形式启动一个Registry： docker run -d [...]]]></description>
			<content:encoded><![CDATA[<p>上周六<a href="http://www.oschina.net/">开源中国</a>的<a href="https://www.oschina.net/event/ych">源创会</a>在沈阳举办了一次技术活动，很荣幸以本地讲师的身份和大家交流了一个topic: “基于<a href="https://github.com/vmware/harbor">Harbor</a>的高可用企业级私有容器镜像仓库部署实践”。之所以选择这个topic，是因为这是我们团队的项目实践心得。很多企业和组织在深入使用Docker之后，都会有类似的高可用私有容器仓库搭建的需求，于是我就把我们摸索的实践和填坑过程拿出来，用30分钟与大家分享一下。另外这算是一个入门级的分享，并未深入过多原理。以下就是本次分享的内容讲稿整理。如有不妥或不正确的地方，欢迎交流指正。</p>
<p><img src="http://tonybai.com/wp-content/uploads/osc-shenyang-2017-1.jpg" alt="img{512x368}" /></p>
<p>大家下午好，欢迎各位来到源创会沈阳站。在这里我也代表沈阳的IT人欢迎源创会来到沈阳，希望能有更多的像源创会这样的组织到沈阳举办技术活动。非常高兴能有这个机会在源创会这个平台上做分享， 今天和大家一起探讨的题目是：“<a href="https://github.com/bigwhite/talks/tree/master/osc/2017">基于Harbor的高可用企业级私有容器镜像仓库部署实践</a>”。题目有些长，简单来说就是如何搭建一个好用的镜像仓库。</p>
<p><img src="http://tonybai.com/wp-content/uploads/osc-shenyang-2017-2.jpg" alt="img{512x368}" /></p>
<p>首先做个简单的自我介绍。我叫白明，东软(注：源创会这次活动的会场在东软沈阳园区)是我的主场，在这里工作很多年，目前就职东软云科技；<a href="https://golang.org/">Gopher</a>一枚，近两年主要使用Go语言开发；技术译者，曾参与翻译过《<a href="https://book.douban.com/subject/10555435/">七周七语言</a>》一书；并且参与过智慧城市架构系列丛书的编著工作；<a href="http://tonybai.com/2017/04/18/my-experience-of-gopherchina-2017-as-a-speaker/">GopherChina大会讲师</a>，这里顺便说一下<a href="http://gopherchina.org/">GopherChina大会</a>，它是目前中国地区规模最大、水平最高的Go语言技术大会，一般每年4月份在北京或上海举行。希望有志于<a href="http://tonybai.com/tag/go">Go语言</a>开发的开发者积极参与；Blogger，写博10多年，依旧笔耕不倦；目前主要从事<a href="http://tonybai.com/tag/docker">Docker</a>&amp;<a href="http://tonybai.com/tag/kubernetes">kubernetes</a>的研究和实践。</p>
<p>当今，IT技术发展飞快。五年前， IT从业者口中谈论最多的技术是<a href="https://en.wikipedia.org/wiki/Virtual_machine">Virtual Machine</a>，即虚拟化技术，人们经常争论的是到底是vmware的技术好,还是原生<a href="https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine">kvm</a>技术稳定，又或是<a href="https://en.wikipedia.org/wiki/Xen">xen</a>的技术完美。转眼间五年过去了，大家口中经常讨论的技术词汇发生了变化，越来越多的技术人在谈论<a href="https://en.wikipedia.org/wiki/Docker_%28software%29">Docker</a>，谈论容器。</p>
<p>Docker是什么？ Docker这门技术非常热，但我们要透过现象看其本质：</p>
<blockquote>
<p><strong>Docker技术并不是新技术，而是将已有技术进行了更好的整合和包装</strong>。</p>
</blockquote>
<p>内核容器技术以一种完整形态最早出现在<a href="https://en.wikipedia.org/wiki/Sun_Microsystems">Sun公司</a>的<a href="http://en.wikipedia.org/wiki/Solaris_(operating_system)">Solaris操作系统</a>上，Solaris是当时最先进的服务器操作系统。2005年Solaris发布<a href="https://en.wikipedia.org/wiki/Solaris_Containers">Solaris Container</a>技术，从此开启了内核容器之门。</p>
<p>IT技术发展的趋势就是这样：商业有的，开源也要有。三年后，即2008年，以Google公司开发人员为主导的<a href="https://en.wikipedia.org/wiki/LXC">Linux Container，LXC功能</a>在被merge到<a href="https://en.wikipedia.org/wiki/Linux_kernel">Linux内核</a>。LXC是一种内核级虚拟化技术，主要基于<a href="https://en.wikipedia.org/wiki/Linux_namespaces">namespaces</a>和<a href="https://en.wikipedia.org/wiki/Cgroups">cgroup</a>技术，实现共享一个os kernel前提下的进程资源隔离，为进程提供独立的虚拟执行环境，这样的一个虚拟的执行环境就是一个容器。本质上说，LXC容器与现在的Docker所提供容器是一样的。但是，当时LXC处于早期阶段，开发人员可能更为关注LXC的技术实现，而对开发体验方面有所忽略，导致LXC技术使用门槛较高，普通应用开发者学习、理解和使用它的心智负担较高，因此应用并不广泛。</p>
<p>这一情况一直持续到2013年，当时美国一家名不见经传的公司<a href="https://en.wikipedia.org/wiki/DotCloud">dotCloud</a>发布了一款平台工具Docker，对外宣称可以实现：“build,ship and run any app and anywhere”。Docker实质上也是基于namespaces和cgroup技术的，Docker的创新之处在于其基于<a href="https://en.wikipedia.org/wiki/Category:Union_file_systems">union fs技术</a>定义了一套应用打包规范，真正将应用及其运行的所有依赖都封装到一个特定格式的文件中，这种文件就被称为image，即镜像文件。同时，Docker还提供了一套抽象层次更高的工具集，这套工具对dev十分友好，具有良好的开发体验(Developer eXperience)，开发者无需关心namespace, cgroups之类底层技术，即可很easy的启动一个承载着其应用的容器：</p>
<pre><code>Docker run ubuntu echo hello
</code></pre>
<p>因此， 从2013发布以来，Docker项目就像坐上了火箭，发展迅猛，目前已经是github上最火爆的开源项目之一。这里还要提一点就是：Docker项目是使用go语言开发的，Docker项目的成功，也或多或少得益于Go优异的开发效率和执行效率。</p>
<p>Docker技术的出现究竟给我们带来了哪些好处呢，个人觉得至少有以下三点：</p>
<ul>
<li>交付标准化：Docker使得应用程序和依赖的运行环境真正绑定结合为一体，得之即用。这让开发人员、测试和运维实现了围绕同一交付物，保持开发交付上下文同步的能力，即“test what you write, ship what you test”；</li>
<li>执行高效化：应用的启动速度从原先虚拟机的分钟级缩短到容器的秒级甚至ms级，使得应用可以支持快速scaling伸缩；</li>
<li>资源集约化：与vm不同的是，Container共享一个内核，这使得一个container的资源消耗仅为进程级别或进程组级别。同时，容器的镜像也因为如此，其size可以实现的很小，最小可能不足1k，平均几十M。与vm动辄几百兆的庞大身段相比，具有较大优势。</li>
</ul>
<p>有了image文件后，自然而言我们就有了对image进行存取和管理的需求，即我们需要一个镜像仓库，于是Docker推出了Docker registry这个项目。Docker Registry就是Docker image的仓库，用来存储、管理和分发image的；Docker registry由Docker公司实现，项目名为<a href="https://github.com/docker/distribution">distribution</a>，其实现了Docker Registr 2.0协议，与早前的Registry 1.x协议版本相比，Distribution采用Go语言替换了Python，在安全性和性能方面都有了大幅提升；Docker官方运行着一个世界最大的公共镜像仓库：hub.docker.com，最常用的image都在hub上，比如反向代理nginx、redis、ubuntu等。鉴于国内访问hub网速不佳，多使用国内容器服务厂商提供的加速器。Docker官方还将Registry本身打入到了一个image中，方便开发人员快速以容器形式启动一个Registry：</p>
<pre><code>docker run -d -p 5000:5000 --restart=always --name registry registry:2
</code></pre>
<p>不过，这样启动的Registry更多仅仅是一个Demo级别或满足个体开发者自身需要的，离满足企业内部开发流程或生产需求还差了许多。</p>
<p>既然Docker官方运行着免费的镜像仓库，那我们还需要自己搭建吗？实际情况是，对Docker的使用越深入，对私有仓库的需求可能就越迫切。我们先来看一组Docker 2016官方的调查数据，看看Docker都应用在哪些场合。 从Docker 2016官方调查来看，Docker 更多用于dev、<a href="https://en.wikipedia.org/wiki/Continuous_integration">ci</a>和<a href="https://en.wikipedia.org/wiki/DevOps">DevOps</a>等环节，这三个场合下的应用占据了半壁江山。而相比于公共仓库，私有镜像仓库能更好的满足开发人员在这些场合对镜像仓库的需求。理由至少有四点：</p>
<ul>
<li>
<p>便于集成到内部CI/Cd<br />
以我司内部为例，由于公司内部办公需要使用正向代理访问外部网络，要想将Public Registry集成到你的内部CI中，技术上就会有很多坎儿，整个搭建过程可能是非常痛苦的；</p>
</li>
<li>
<p>对镜像可以更全面掌控<br />
一般来说，外部Public Registry提供的管理功能相对单一，往往无法满足企业内部的开发和交付需求；</p>
</li>
<li>
<p>内部网络，网络传输性能更好<br />
内部开发运维流水线很多环节是有一定的时间敏感性的，比如：一次CI如果因为network问题导致image pull总是timeout，会让dev非常闹心，甚至影响整体的开发和交付效率。</p>
</li>
<li>
<p>出于安全考虑<br />
总是有企业不想将自己开发的软件或数据放到公网上，因此在企业内部选择搭建一个private registry更会让这些企业得到满足；另外企业对仓库的身份验证可能还有LDAP支持的需求，这是外部registry无法满足的。</p>
</li>
</ul>
<p>一旦企业决定搭建自己的private仓库，那么就得做一个private仓库的技术选型。商业版不在我们讨论范围内，我们从开源软件中挑选。不过开源的可选的不多，Docker 官方的Registry更聚焦通用功能，没有针对企业客户需求定制，开源领域我们大致有两个主要候选者：<a href="https://github.com/SUSE/">SUSE</a>的<a href="https://github.com/SUSE/Portus">Portus</a>和Vmware的<a href="https://github.com/vmware/harbor">Harbor</a>。针对开源项目的技术选型，我个人的挑选原则最简单的就是看社区生态，落实到具体的指标上包括：</p>
<ul>
<li>项目关注度（即star数量）</li>
<li>社区对issue的反馈数量和积极性</li>
<li>项目维护者对issue fix的积极程度以及是否有远大的roadmap</li>
</ul>
<p>对比后，我发现在这三个指标上，目前Harbor都暂时领先portus一段距离，于是我们选择Harbor。</p>
<p>Harbor是VMware中国团队开源的企业级镜像仓库项目，聚焦镜像仓库的企业级需求，这里从其官网摘录一些特性，大家一起来看一下：</p>
<p>– 支持基于角色的访问控制RBAC;<br />
– 支持镜像复制策略(PUSH);<br />
– 支持无用镜像数据的自动回收和删除; – 支持LDAP/AD认证;<br />
– Web UI;<br />
– 提供审计日志功能;<br />
– 提供RESTful API,便于扩展;<br />
– 支持中文&amp;部署Easy。</p>
<p>不过，Harbor默认安装的是单实例仓库，并非是<a href="https://en.wikipedia.org/wiki/High_availability">高可用的</a>。对于接纳和使用Docker的企业来说，镜像仓库已经企业内部开发、交付和运维流水线的核心，一旦仓库停掉，流水线将被迫暂停，对开发交付的效率会产生重要影响；对于一些中大型企业组织，单实例的仓库性能也无法满足需求，为此高可用的Harbor势在必行。在设计Harbor HA方案之前，我们简单了解一下Harbor组成架构。</p>
<p>一个Harbor实例就是一组由<a href="https://github.com/docker/compose">docker-compose</a>工具启动的容器服务，主要包括四个主要组件：</p>
<ul>
<li>
<p>proxy<br />
实质就是一个反向代理<a href="http://tonybai.com/tag/nginx">nginx</a>，负责流量路由分担到ui和registry上；</p>
</li>
<li>
<p>registry<br />
这里的registry就是原生的docker官方的registry镜像仓库，Harbor在内部内置了一个仓库，所有仓库的核心功能均是由registry完成的；</p>
</li>
<li>
<p>core service<br />
包含了ui、token和webhook服务；</p>
</li>
<li>
<p>job service<br />
主要用于镜像复制供。</p>
</li>
</ul>
<p>同时，每个Harbor实例还启动了一个MySQL数据库容器，用于保存自身的配置和镜像管理相关的关系数据。</p>
<p>高可用系统一般考虑三方面：计算高可用、存储高可用和网络高可用。在这里我们不考虑网络高可用。基于Harbor的高可用仓库方案，这里列出两个。</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-ha-solutions.png" alt="img{512x368}" /></p>
<p>两个方案的共同点是计算高可用，都是通过lb实现的多主热运行，保证无单点；存储高可用则各有各的方案。一个使用了分布式共享存储，数据可靠性由共享存储provider提供；另外一个则需要harbor自身逻辑参与，通过镜像相互复制的方式保持数据的多副本。</p>
<p>两种方案各有优缺点，就看哪种更适合你的组织以及你手里的资源是否能满足方案的搭建要求。</p>
<p>方案1是Harbor开发团队推荐的标准方案，由于基于分布式共享存储，因此其scaling非常好；同样，由于多Harbor实例共享存储，因此可以保持数据是实时一致的。方案1的不足也是很明显的，第一：门槛高，需要具备共享存储provider；第二搭建难度要高于第二个基于镜像复制的方案。</p>
<p>方案2的优点就是首次搭建简单。不足也很多：scaling差，甚至是不能，一旦有三个或三个以上节点，可能就会出现“环形复制”；镜像复制需要时间，因此存在多节点上数据周期性不一致的情况；Harbor的镜像复制规则以Project为单位配置，因此一旦新增Project，需要在每个节点上手工维护复制规则，非常繁琐。因此，我们选择方案1。</p>
<p>我们来看一下方案1的细节： 这是一幅示意图。</p>
<ul>
<li>每个安放harbor实例的node都mount cephfs。ceph是目前最流行的分布式共享存储方案之一；</li>
<li>每个node上的harbor实例（包含组件：ui、registry等）都volume mount node上的cephfs mount路径；</li>
<li>通过Load Balance将request流量负载到各个harbor实例上；</li>
<li>使用外部MySQL cluster替代每个Harbor实例内部自维护的那个MySQL容器；对于MySQL cluster，可以使用<a href="http://galeracluster.com/products/">mysql galera cluster</a>或MySQL5.7以上版本自带的Group Replication (MGR) 集群。</li>
<li>通过外部Redis实现访问Harbor ui的session共享，这个功能是Harbor UI底层MVC框架-<a href="https://github.com/astaxie/beego">beego</a>提供的。</li>
</ul>
<p>接下来，我们就来看具体的部署步骤和细节。</p>
<p>环境和先决条件：</p>
<ul>
<li>三台VM(Ubuntu 16.04及以上版本)；</li>
<li><a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/">CephFS</a>、MySQL、Redis已就绪；</li>
<li>Harbor v1.1.0及以上版本；</li>
<li>一个域名：hub.tonybai.com:8070。我们通过该域名和服务端口访问Harbor，我们可以通过dns解析多ip轮询实现最简单的Load balance，虽然不完美。</li>
</ul>
<h3>第一步：挂载cephfs</h3>
<p>每个安装Harbor instance的节点都要mount cephfs的相关路径，步骤包括：</p>
<pre><code>#安装cephfs内核驱动
apt install ceph-fs-common

# 修改/etc/fstab，添加挂载指令，保证节点重启依旧可以自动挂载cephfs
xx.xx.xx.xx:6789:/apps/harbor /mnt/cephfs/harbor ceph name=harbor,secretfile=/etc/ceph/a dmin.secret,noatime,_netdev 0 2
</code></pre>
<p>这里涉及一个密钥文件admin.secret，这个secret文件可以在ceph集群机器上使用ceph auth tool生成。</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-prepare-process.png" alt="img{512x368}" /></p>
<p>前面提到过每个Harbor实例都是一组容器服务，这组容器启动所需的配置文件是在Harbor正式启动前由prepare脚本生成的，Prepare脚本生成过程的输入包括：harbor.cfg、docker-compose.yml和common/templates下的配置模板文件。这也是部署高可用Harbor的核心步骤，我们逐一来看。</p>
<h3>第二步：修改harbor.cfg</h3>
<p>我们使用域名访问Harbor，因此我们需要修改hostname配置项。注意如果要用域名访问，这里一定填写域名，否则如果这里使用的是Harbor node的IP，那么在后续会存在client端和server端仓库地址不一致的情况；</p>
<p>custom_crt=false 关闭 crt生成功能。注意：三个node关闭其中两个，留一个生成一套数字证书和私钥。</p>
<h3>第三步：修改docker-compose.yml</h3>
<p>docker-compose.yml是docker-compose工具标准配置文件，用于配置docker-compose即将启动的容器服务。针对该配置文件，我们主要做三点修改：</p>
<ul>
<li>修改volumes路径<br />
由/data/xxx 改为：/mnt/cephfs/harbor/data/xxx</li>
<li>由于使用外部Mysql，因此需要删除mysql service以及其他 service对mysql service的依赖 (depends_on)</li>
<li>修改对proxy外服务端口 ports:  8070:80</li>
</ul>
<h3>第四步：配置访问external mysql和redis</h3>
<p>external mysql的配置在common/templates/adminserver/env中，我们用external Mysql的访问方式覆盖下面四项配置：</p>
<pre><code>MYSQL_HOST=harbor_host
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

</code></pre>
<p>还有一个关键配置，那就是将RESET由false改为true。<a href="http://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/">只有改为true，adminserver启动时，才能读取更新后的配置</a>：</p>
<pre><code>RESET=true
</code></pre>
<p>Redis连接的配置在common/templates/ui/env中，我们需要新增一行：</p>
<pre><code>_REDIS_URL=redis_ip:6379,100,password,0
</code></pre>
<h3>第五步：prepare并启动harbor</h3>
<p>执行prepare脚本生成harbor各容器服务的配置；在每个Harbor node上通过下面命令启动harbor实例：</p>
<pre><code>docker-compose up -d

</code></pre>
<p>启动后，可以通过docker-compose ps命令查看harbor实例中各容器的启动状态。如果启动顺利，都是”Up”状态，那么我们可以在浏览器里输入：http://hub.tonybai.com:8070，不出意外的话，我们就可以看到Harbor ui的登录页面了。</p>
<p>至此，我们的高可用Harbor cluster搭建过程就告一段落了。</p>
<h3>Troubleshooting</h3>
<p>不过，对Harbor的认知还未结束，我们在后续使用Harbor的过程中遇到了一些问题，这里举两个例子。</p>
<h4>问题1： docker login hub.tonybai.com:8070 failed</h4>
<p>现象日志：</p>
<pre><code>Error response from daemon: Get https://hub.tonybai.com:8070/v1/users/: http: server gave HTTP response to HTTPS client
</code></pre>
<p>通过错误日志分析应该是docker daemon与镜像仓库所用协议不一致导致。docker engine默认采用https协议访问仓库，但之前我们搭建的Harbor采用的是http协议提供服务，两者不一致。</p>
<p>解决方法有两种，这里列出第一种：让docker引擎通过http方式访问harbor仓库：</p>
<pre><code>在/etc/docker/daemon.json中添加insecure-registry：

{
    "insecure-registries": ["hub.tonybai.com:8070"]
}

重启docker service生效
</code></pre>
<p>第二种方法就是让Harbor支持https，需要为harbor的proxy配置私钥和证书，位置：harbor.cfg中</p>
<pre><code>#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /data/cert/server.crt
ssl_cert_key = /data/cert/server.key
</code></pre>
<p>这里就不细说了。</p>
<h4>问题2：docker login hub.tonybai.com:8070 有时成功，有时failed</h4>
<p>现象日志:</p>
<pre><code>第一次登录成功：
# docker login -u user -p passwd http://hub.tonybai.com:8070 Login Succeeded

第二次登录失败：
# docker login -u user -p passwd http://hub.tonybai.com:8070
Error response from daemon: login attempt to http://hub.tonybai.com:8070/v2/ failed with status: 401 Unauthorized
</code></pre>
<p>这个问题的原因在于对docker registry v2协议登录过程理解不够透彻。docker registry v2是一个两阶段登录的过程：</p>
<ul>
<li>首先：docker client会到registry去尝试登录，registry发现request中没有携带token，则返回失败应答401，并告诉客户端到哪里去获取token；</li>
<li>客户端收到应答后，获取应答中携带的token service地址，然后到harbor的core services中的token service那里获取token（使用user, password进行校验）。一旦token service校验ok，则会使用private_key.pem生成一个token；</li>
<li>客户端拿到token后，再次到registry那里去登录，这次registry用root.crt去校验客户端携带的token，校验通过，则login成功。</li>
</ul>
<p>由于我们是一个harbor cluster，如果docker client访问的token service和registry是在一个harbor实例中的，那么login就会ok；否则docker client就会用harbor node1上token service生成的token到harbor node2上的registry去登录，由于harbor node2上root.crt与harbor node1上private_key.pem并非一对，因此<a href="http://tonybai.com/2017/06/15/fix-auth-fail-when-login-harbor-registry/">登录失败</a>。</p>
<p>解决方法：将所有节点上使用同一套root.crt和private_key.pem。即将一个harbor node（harbor.cfg中custom_crt=true的那个）上的 common/config/ui/private_key.pem和 common/config/registry/root.crt复制到其他harbor node;然后重建各harbor实例中的容器。</p>
<p>至此，我们的高可用Harbor仓库部署完了。针对上面的配置过程，我还做了几个录屏文件，由于时间关系，这里不能播放了，大家可以在下面这个连接下载并自行播放收看。</p>
<pre><code>Harbor install 录屏: https://pan.baidu.com/s/1o8JYKEe
</code></pre>
<p>谢谢大家！</p>
<h2>讲稿slide可以在<a href="https://github.com/bigwhite/talks/tree/master/osc/2017">这里</a>获取到。</h2>
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>理解Unikernels</title>
		<link>https://tonybai.com/2016/05/16/understanding-unikernels/</link>
		<comments>https://tonybai.com/2016/05/16/understanding-unikernels/#comments</comments>
		<pubDate>Mon, 16 May 2016 13:11:54 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ClickOS]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Erlang]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HaLVM]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[LING]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MirageOS]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[NetBSD]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[Ocaml]]></category>
		<category><![CDATA[OSv]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[RAMP]]></category>
		<category><![CDATA[rumpkernel]]></category>
		<category><![CDATA[rumprun]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[unikernel]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[虚拟机]]></category>
		<category><![CDATA[超融合]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1991</guid>
		<description><![CDATA[当Docker, Inc在今年年初宣布收购Unikernel Systems公司时，Unikernel对大多数技术人员来说还是很陌生的。直到今天，知名问答类网站知乎上也没有以Unikernel为名字的子话题。国内搜索引擎中关于Unikernel的内容很少，实践相关的内容就更少了。Docker收购Unikernel Systems，显然不是为了将这个其未来潜在的竞争对手干掉，而是嗅到了Unikernel身上的某些技术潜质。和关注Docker一样，本博客后续将持续关注Unikernel的最新发展和优秀实践，并将一些国外的优秀资料搬(翻)移(译)过来供国内Unikernel爱好者和研究人员参考。 本文翻译自BSD Magazine2016年第3期中Russell Pavlicek的文章《Understanding Unikernels》，译文全文如下。 当我们描述一台机器（物理的或虚拟的）上的操作系统内核时，我们通常所指的是运行在特定处理器模式（内核模式）下且所使用的地址空间有别于机器上其他软件运行地址空间的一段特定的软件代码。操作系统内核通常用于提供一些关键的底层函数，这些函数被操作系统中其他软件所使用。内核通常是一段通用的代码，（有需要时）一般会被做适当裁剪以适配支持机器上的应用软件栈。这个通用的内核通常会提供各种功能丰富的函数，但很多功能和函数并不是内核支持的特定应用程序所需要的。 事实上，如果看看今天大多数机器上运行的整体软件栈，我们会发现很难弄清楚到底哪些应用程序运行在那台机器上了。你可能会发现即便没有上千，也会有成百计的低级别实用程序（译注：主要是指系统引导起来后，常驻后台的一些系统服务程序），外加许多数据库程序，一两个Web服务程序，以及一些指定的应用程序。这台机器可能实际上只承担运行一个单独的应用程序，或者它也可能被用于同时运行许多应用。通过对系统启动脚本的细致分析来确定最终运行程序的集合是一个思路，但还远非精准。因为任何一个具有适当特权的用户都可以去启动系统中已有应用程序中的任何一个。 Unikernel的不同之处 基于Unikernel的机器的覆盖面（footprint）是完全不同的。在物理机器（或虚拟机映像）中，Unikernel扮演的角色与其他内核是相似的，但实现特征显著不同。 例如，对一个基于Unikernel的机器的代码进行分析就不会受到大多数其他软件栈的模糊性的影响。当你考虑分析一个Unikernel系统时，你会发现系统中只存在一个且只有一个应用程序。那种标准的多应用程序软件栈不见了，前面提到的过多的通用实用程序和支持函数也不见了。不过裁剪并未到此打住。不仅应用软件栈被裁剪到了最低限度，操作系统功能也同样被剪裁了。例如，多用户支持、多进程支持以及高级内存管理也都不见了。 认为这很激进？想想看：如果整个独立的操作系统层也不见了呢！内核不再有独立的地址空间，应用程序也不再有独立的地址空间了。为什么？因为内核的功能函数和应用程序现在都成为了同一个程序的一部分。事实上，整个软件栈是由一个单独的软件程序构成的，这个程序负责提供应用程序所需的所有代码以及操作系统的功能函数。如果这还不够的话，只需在Unikernel中提供应用所需的那些功能函数即可，所有其他应用程序所不需要的操作系统功能函数都会被整体移除掉。 一个反映新世纪现实的软件栈 Unikernel的出现，其背后的目的在于对这个行业的彻底的反思。几十年来，在这个行业里我们的工作一直伴随着这样一个理念：机器的最好架构是基于一个通用多用户操作系统启动，加载一系列有用的实用工具程序，添加我们可能需要使用的应用程序。最后，再使用一些包管理软件来管理这种混乱的情况。 35年前，这种做法是合乎情理的。那个时候，硬件很昂贵，虚拟化的选择非常有限甚至是不可用。安全仅局限于保证计算中心坐在你身旁的人没有在偷看你输密码。一台机器需要同时处理许多用户运行的许多应用程序以保证较高的成本效益。当我还在大学（1、2千年前。 译注：作者开玩笑，强调那时的古老^_^）时，在个人计算机出现之前，学校计算机中心有一个超级昂贵的机器（以今天的标准来看） &#8211; 一台DEC PDP-11/34a，配置了248K字节的内存和25M磁盘，为全校的计算机科学、工程以及数学专业的学生使用。这台机器必须服务于几百名学生每个学期想出的每个功能。 对比计算机历史上那个远古时代的恐龙和现代的智能手机，你会发现手机拥有的计算能力高出那台机器几个数量级。这样一来，我们为什么还要用在计算机石器时代所使用的那些原则去创建机器内核映像呢？重新思考与新的计算现实相匹配的软件栈难道不是很有意义吗？ 在现代世界，硬件十分便宜。虚拟化无处不在且运行效率很高。几乎所有计算设备都连接在一个巨大的、世界范围的且存在潜在恶意黑客的网络中。想想看：一台DNS服务器真的不需要上千兆的字节去完成它的工作；一台应用服务器也真的不需要为刚刚利用一个漏洞获得虚拟命令行访问权的黑客准备数千实用工具程序。 一个Web服务器并不需要验证500个不同的分时用户的命令行登录。那么为什么我们现在仍然在使用支持这些不需要的场景的过时的软件栈概念呢？ Unikernel的美丽新世界 那么一个现代软件栈应该是什么样子的呢？下面这个怎么样：单一应用映像，虚拟化的，高度安全的，超轻量的，具有超快启动速度。这些正是Unikernel所能提供的。我们逐一来说： 单一映像 叠加在一个通用内核上的数以百计的实用工具程序和大量应用程序被一个可执行体所替代。这个可执行体将所有需要的应用程序和操作系统代码放置在一个单一的映像中。它只包含它所需要的。 虚拟化的 就在几年前，你可以很幸运地在一台服务器上启动少量虚拟机。硬件的内存限制以及守旧的、吃内存的软件栈不允许你在一台服务器上同时启动太多虚机。今天我们有了配置了数千兆内存的高性能服务器，我们不再满足于每台机器仅能启动少量虚机了。如果每个虚机映像足够小，我们可以在一个服务器上同事运行数百个，甚至上千个虚机应用。 安全 在云计算时代，我们发现恶意黑客可以例行公事般入侵各地的服务器，即便是那些知名大公司和政府机构的服务器也不例外。这些违规行为常常是利用了某个网络服务的缺陷并进入了软件栈的更低层。从那开始，恶意入侵者可以利用系统中已有的实用程序或其他应用程序来实施他们的邪恶行为。在Unikernel栈中，没有其他软件可以协助这些恶意的黑客。黑客必须足够聪明才能入侵其中的应用程序，但接下来还是没有驻留的工具可以用来协助做坏事。虽然Unikernel栈不会使得软件彻底完全的变安全，但是它确能显著提升软件的安全级别。并且这是云计算时代长期未兑现的一种进步。 超轻量 一个正常的VM仅仅是为了能在网络中提供少量的服务就要占用千兆的磁盘和内存空间。若使用Unikernel，我们可以不再纠结于这些资源需求。例如，使用MirageOS(一个非常流行的Unikernel系统)，我们可以构建出一个具备DNS服务功能的VM映像，其占用的磁盘空间仅仅为449K &#8211; 是的，还不到半兆。使用ClickOS，一个来自NEC实验室的网络应用Unikernel系统制作的网络设备仅仅使用6兆内存却可以成功达到每秒5百万包的处理能力。这些绝不是基于Unikernel的设备的非典型例子。鉴于Unikernels的小巧精简，在单主机服务器上启动数百或数千这类微小虚拟机的想法似乎不再遥不可及。 快速启动 普通VM的引导启动消耗较长时间。在现代硬件上启动一个完整操作系统以及软件栈直到服务上线需要花费一分钟甚至更多的时间。但是对于基于Unikernel的VM来说，这种情况却不适用。绝大多数的Unikernel VM引导启动时间少于十分之一秒。例如，ClickOS网络VM文档中记录的引导启动时间在30毫秒以下。这个速度快到足以在服务请求到达网络时再启动一个用于处理该请求的VM了（这正是Jitsu项目所要做的事情，参见http://unikernel.org/files/2015-nsdi-jitsu.pdf）。 但是，容器不已经做到这一点了吗？ 在创建轻量级，快速启动的VM方面，容器已经走出了很远。但在幕后容器依然依赖着一个共享的、健壮的操作系统。从安全的角度来看，容器还有很多要锁定的地方。很明显我们需要加强我们在云中的安全，但不是去追求这些相同的、陈旧的、在云中就会快速变得漏洞百出的安全方法。除此之外，Unikernel的最终覆盖面仍然要比容器能提供的小得很多。因此容器走在了正确的方向上，而Unikernel则设法在这个未来云所需要的方向上走的更远。 Unikernels是如何工作的？ 正如之前提到的，传统机器自底向上构建：你选择一个通用的操作系统内核，添加大量实用工具程序，最后添加应用程序。Unikernel正好相反：它们是自顶向下构建的。聚焦在你要运行的应用程序上，恰到好处地添加使其刚好能运行的操作系统函数。大多数Unikernel系统依靠一个编译链接系统，这个系统编译应用程序源码并将应用程序所需的操作系统函数库链接进来，形成一个单独的编译映像。无需其他软件，这个映像就可以运行在VM中。 如何对结果进行调试？ 由于在最终的成品中没有操作系统或实用工具程序，绝大多数Unikernel系统使用了一种分阶段的方法来开发。通常，在开发阶段一次编译会生成一个适合在Linux或类Unix操作系统上进行测试的可执行程序。这个可执行程序可以运行和被调试，就像任何一个标准程序那样。一旦你对测试结果感到满意，你可以重新编译，打开开关，创建独立运行在VM中的最终映像。 在生产环境机器上缺少调试工具并没有最初想象的那样糟糕。绝大多数组织不允许开发人员在生产机器上调试，相反，他们收集日志和其他信息，在开发平台重现失败场景，修正问题并重新部署。这个事实让调试生产映像的限制也有所缓和。在Unikernel世界中，这个操作顺序也已具备。你只需要保证你的生产环境映像可以输出足够多的日志以方便重构失败场景。你的标准应用程序可能正在做这些事情了。 有哪些可用的Unikernel系统？ 现在有很多Unikernel可供选择，它们支持多种编程语言，并且Unikernel项目还在持续增加中。一些较受欢迎的Unikernel系统包括： MirageOS：最早的Unikernels系统之一，它使用Ocaml语言； HaLVM：另外一个早期Unikernels系统，由Haskell语言实现； LING：历史悠久的项目，使用Erlang实现； ClickOS：为网络应用优化的系统，支持C、C++和Python； OSv：稍有不同的Unikernel系统，它基于Java，并支持其他一些编程语言。支持绝大多数JAR文件部署和运行。 Rumprun：使用了来自NetBSD项目的模块代码，目标定位于任何符合POSIX标准的、不需要Fork的应用程序，特别适合将现有程序移植到Unikernel世界。 Unikernel是灵丹妙药吗？ [...]]]></description>
			<content:encoded><![CDATA[<p>当<a href="http://www.docker.com/">Docker, Inc</a>在今年年初宣布收购<a href="http://unikernel.com/">Unikernel Systems公司</a>时，<a href="http://unikernel.org/">Unikernel</a>对大多数技术人员来说还是很陌生的。直到今天，知名问答类网站<a href="https://www.zhihu.com/">知乎</a>上也没有以Unikernel为名字的子话题。国内搜索引擎中关于Unikernel的内容很少，实践相关的内容就更少了。Docker收购Unikernel Systems，显然不是为了将这个其未来潜在的竞争对手干掉，而是嗅到了Unikernel身上的某些技术潜质。和关注Docker一样，本博客后续将持续关注Unikernel的最新发展和优秀实践，并将一些国外的优秀资料搬(翻)移(译)过来供国内Unikernel爱好者和研究人员参考。</p>
<p>本文翻译自<a href="https://bsdmag.org/">BSD Magazine</a>2016年<a href="https://bsdmag.org/download/kernel_syscalls/">第3期</a>中Russell Pavlicek的文章<a href="https://bsdmag.org/understanding_unikernels/">《Understanding Unikernels》</a>，译文全文如下。</p>
<p>当我们描述一台机器（物理的或虚拟的）上的操作系统内核时，我们通常所指的是运行在特定处理器模式（内核模式）下且所使用的地址空间有别于机器上其他软件运行地址空间的一段特定的软件代码。操作系统内核通常用于提供一些关键的底层函数，这些函数被操作系统中其他软件所使用。内核通常是一段通用的代码，（有需要时）一般会被做适当裁剪以适配支持机器上的应用软件栈。这个通用的内核通常会提供各种功能丰富的函数，但很多功能和函数并不是内核支持的特定应用程序所需要的。</p>
<p>事实上，如果看看今天大多数机器上运行的整体软件栈，我们会发现很难弄清楚到底哪些应用程序运行在那台机器上了。你可能会发现即便没有上千，也会有成百计的低级别实用程序（译注：主要是指系统引导起来后，常驻后台的一些系统服务程序），外加许多数据库程序，一两个Web服务程序，以及一些指定的应用程序。这台机器可能实际上只承担运行一个单独的应用程序，或者它也可能被用于同时运行许多应用。通过对系统启动脚本的细致分析来确定最终运行程序的集合是一个思路，但还远非精准。因为任何一个具有适当特权的用户都可以去启动系统中已有应用程序中的任何一个。</p>
<h3>Unikernel的不同之处</h3>
<p>基于Unikernel的机器的覆盖面（footprint）是完全不同的。在物理机器（或虚拟机映像）中，Unikernel扮演的角色与其他内核是相似的，但实现特征显著不同。</p>
<p>例如，对一个基于Unikernel的机器的代码进行分析就不会受到大多数其他软件栈的模糊性的影响。当你考虑分析一个Unikernel系统时，你会发现系统中只存在一个且只有一个应用程序。那种标准的多应用程序软件栈不见了，前面提到的过多的通用实用程序和支持函数也不见了。不过裁剪并未到此打住。不仅应用软件栈被裁剪到了最低限度，操作系统功能也同样被剪裁了。例如，多用户支持、多进程支持以及高级内存管理也都不见了。</p>
<p>认为这很激进？想想看：如果整个独立的操作系统层也不见了呢！内核不再有独立的地址空间，应用程序也不再有独立的地址空间了。为什么？因为内核的功能函数和应用程序现在都成为了同一个程序的一部分。事实上，整个软件栈是由一个单独的软件程序构成的，这个程序负责提供应用程序所需的所有代码以及操作系统的功能函数。如果这还不够的话，只需在Unikernel中提供应用所需的那些功能函数即可，所有其他应用程序所不需要的操作系统功能函数都会被整体移除掉。</p>
<h3>一个反映新世纪现实的软件栈</h3>
<p>Unikernel的出现，其背后的目的在于对这个行业的彻底的反思。几十年来，在这个行业里我们的工作一直伴随着这样一个理念：机器的最好架构是基于一个通用多用户操作系统启动，加载一系列有用的实用工具程序，添加我们可能需要使用的应用程序。最后，再使用一些包管理软件来管理这种混乱的情况。</p>
<p>35年前，这种做法是合乎情理的。那个时候，硬件很昂贵，虚拟化的选择非常有限甚至是不可用。安全仅局限于保证计算中心坐在你身旁的人没有在偷看你输密码。一台机器需要同时处理许多用户运行的许多应用程序以保证较高的成本效益。当我还在大学（1、2千年前。 译注：作者开玩笑，强调那时的古老^_^）时，在个人计算机出现之前，学校计算机中心有一个超级昂贵的机器（以今天的标准来看） &#8211; 一台DEC PDP-11/34a，配置了248K字节的内存和25M磁盘，为全校的计算机科学、工程以及数学专业的学生使用。这台机器必须服务于几百名学生每个学期想出的每个功能。</p>
<p>对比计算机历史上那个远古时代的恐龙和现代的智能手机，你会发现手机拥有的计算能力高出那台机器几个数量级。这样一来，我们为什么还要用在计算机石器时代所使用的那些原则去创建机器内核映像呢？重新思考与新的计算现实相匹配的软件栈难道不是很有意义吗？</p>
<p>在现代世界，硬件十分便宜。虚拟化无处不在且运行效率很高。几乎所有计算设备都连接在一个巨大的、世界范围的且存在潜在恶意黑客的网络中。想想看：一台DNS服务器真的不需要上千兆的字节去完成它的工作；一台应用服务器也真的不需要为刚刚利用一个漏洞获得虚拟命令行访问权的黑客准备数千实用工具程序。 一个Web服务器并不需要验证500个不同的分时用户的命令行登录。那么为什么我们现在仍然在使用支持这些不需要的场景的过时的软件栈概念呢？</p>
<h3>Unikernel的美丽新世界</h3>
<p>那么一个现代软件栈应该是什么样子的呢？下面这个怎么样：单一应用映像，虚拟化的，高度安全的，超轻量的，具有超快启动速度。这些正是Unikernel所能提供的。我们逐一来说：</p>
<h4>单一映像</h4>
<p>叠加在一个通用内核上的数以百计的实用工具程序和大量应用程序被一个可执行体所替代。这个可执行体将所有需要的应用程序和操作系统代码放置在一个单一的映像中。它只包含它所需要的。</p>
<h4>虚拟化的</h4>
<p>就在几年前，你可以很幸运地在一台服务器上启动少量虚拟机。硬件的内存限制以及守旧的、吃内存的软件栈不允许你在一台服务器上同时启动太多虚机。今天我们有了配置了数千兆内存的高性能服务器，我们不再满足于每台机器仅能启动少量虚机了。如果每个虚机映像足够小，我们可以在一个服务器上同事运行数百个，甚至上千个虚机应用。</p>
<h4>安全</h4>
<p>在云计算时代，我们发现恶意黑客可以例行公事般入侵各地的服务器，即便是那些知名大公司和政府机构的服务器也不例外。这些违规行为常常是利用了某个网络服务的缺陷并进入了软件栈的更低层。从那开始，恶意入侵者可以利用系统中已有的实用程序或其他应用程序来实施他们的邪恶行为。在Unikernel栈中，没有其他软件可以协助这些恶意的黑客。黑客必须足够聪明才能入侵其中的应用程序，但接下来还是没有驻留的工具可以用来协助做坏事。虽然Unikernel栈不会使得软件彻底完全的变安全，但是它确能显著提升软件的安全级别。并且这是云计算时代长期未兑现的一种进步。</p>
<h4>超轻量</h4>
<p>一个正常的VM仅仅是为了能在网络中提供少量的服务就要占用千兆的磁盘和内存空间。若使用Unikernel，我们可以不再纠结于这些资源需求。例如，使用MirageOS(一个非常流行的Unikernel系统)，我们可以构建出一个具备DNS服务功能的VM映像，其占用的磁盘空间仅仅为449K &#8211; 是的，还不到半兆。使用ClickOS，一个来自NEC实验室的网络应用Unikernel系统制作的网络设备仅仅使用6兆内存却可以成功达到每秒5百万包的处理能力。这些绝不是基于Unikernel的设备的非典型例子。鉴于Unikernels的小巧精简，在单主机服务器上启动数百或数千这类微小虚拟机的想法似乎不再遥不可及。</p>
<h4>快速启动</h4>
<p>普通VM的引导启动消耗较长时间。在现代硬件上启动一个完整操作系统以及软件栈直到服务上线需要花费一分钟甚至更多的时间。但是对于基于Unikernel的VM来说，这种情况却不适用。绝大多数的Unikernel VM引导启动时间少于十分之一秒。例如，ClickOS网络VM文档中记录的引导启动时间在30毫秒以下。这个速度快到足以在服务请求到达网络时再启动一个用于处理该请求的VM了（这正是Jitsu项目所要做的事情，参见http://unikernel.org/files/2015-nsdi-jitsu.pdf）。</p>
<h3>但是，容器不已经做到这一点了吗？</h3>
<p>在创建轻量级，快速启动的VM方面，容器已经走出了很远。但在幕后容器依然依赖着一个共享的、健壮的操作系统。从安全的角度来看，容器还有很多要锁定的地方。很明显我们需要加强我们在云中的安全，但不是去追求这些相同的、陈旧的、在云中就会快速变得漏洞百出的安全方法。除此之外，Unikernel的最终覆盖面仍然要比容器能提供的小得很多。因此容器走在了正确的方向上，而Unikernel则设法在这个未来云所需要的方向上走的更远。</p>
<h3>Unikernels是如何工作的？</h3>
<p>正如之前提到的，传统机器自底向上构建：你选择一个通用的操作系统内核，添加大量实用工具程序，最后添加应用程序。Unikernel正好相反：它们是自顶向下构建的。聚焦在你要运行的应用程序上，恰到好处地添加使其刚好能运行的操作系统函数。大多数Unikernel系统依靠一个编译链接系统，这个系统编译应用程序源码并将应用程序所需的操作系统函数库链接进来，形成一个单独的编译映像。无需其他软件，这个映像就可以运行在VM中。</p>
<h3>如何对结果进行调试？</h3>
<p>由于在最终的成品中没有操作系统或实用工具程序，绝大多数Unikernel系统使用了一种分阶段的方法来开发。通常，在开发阶段一次编译会生成一个适合在Linux或类Unix操作系统上进行测试的可执行程序。这个可执行程序可以运行和被调试，就像任何一个标准程序那样。一旦你对测试结果感到满意，你可以重新编译，打开开关，创建独立运行在VM中的最终映像。</p>
<p>在生产环境机器上缺少调试工具并没有最初想象的那样糟糕。绝大多数组织不允许开发人员在生产机器上调试，相反，他们收集日志和其他信息，在开发平台重现失败场景，修正问题并重新部署。这个事实让调试生产映像的限制也有所缓和。在Unikernel世界中，这个操作顺序也已具备。你只需要保证你的生产环境映像可以输出足够多的日志以方便重构失败场景。你的标准应用程序可能正在做这些事情了。</p>
<h3>有哪些可用的Unikernel系统？</h3>
<p>现在有很多Unikernel可供选择，它们支持多种编程语言，并且Unikernel项目还在持续增加中。一些较受欢迎的Unikernel系统包括：</p>
<ul>
<li>MirageOS：最早的Unikernels系统之一，它使用Ocaml语言；</li>
<li>HaLVM：另外一个早期Unikernels系统，由Haskell语言实现；</li>
<li>LING：历史悠久的项目，使用Erlang实现；</li>
<li>ClickOS：为网络应用优化的系统，支持C、C++和Python；</li>
<li>OSv：稍有不同的Unikernel系统，它基于Java，并支持其他一些编程语言。支持绝大多数JAR文件部署和运行。</li>
<li>Rumprun：使用了来自NetBSD项目的模块代码，目标定位于任何符合POSIX标准的、不需要Fork的应用程序，特别适合将现有程序移植到Unikernel世界。</li>
</ul>
<h3>Unikernel是灵丹妙药吗？</h3>
<p>Unikernel远非万能的。由于他们是单一进程实体，运行在单一地址空间，没有高级内存管理，很多程序无法很容易地迁移到Unikernel世界。不过，运行于世界各地数据中心中的大量服务很适合该方案。将这些服务转换为轻量级Unikernel，我们可以重新分配服务器能力，任务较重的服务可以从额外的资源中受益。</p>
<p>转换成Unikernel的任务数量比你想象的要多。在2015年，Martin Lucina宣布成功创建了一个”RAMP”栈 &#8211; LAMP栈（Linux、Apache、MySQL和PHP/Python）的变种。RAMP栈使用了NGINX，MySQL和PHP，它们都构建在Rumprun之上。Rumprun是Rump内核的一个实例，而Rump内核则是基于NetBSD工程模块化操作系统功能函数集合的一个Unikernel系统。所以这种常见的解决方案堆栈可以成功地转化迁移到Unikernels世界中。</p>
<h3>更多信息</h3>
<p>要想学习更多有关Unikernels方面的内容，可以访问http://www.unikernel.org或观看2015年我在Southeast Linuxfest的<a href="https://www.youtube.com/watch?v=8UgiPODw3CY">演讲视频</a>。</p>
<p style='text-align:left'>&copy; 2016 &#8211; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/05/16/understanding-unikernels/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>部署devstack</title>
		<link>https://tonybai.com/2016/05/04/deploy-devstack/</link>
		<comments>https://tonybai.com/2016/05/04/deploy-devstack/#comments</comments>
		<pubDate>Wed, 04 May 2016 07:11:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[cloudcomputing]]></category>
		<category><![CDATA[CloudStack]]></category>
		<category><![CDATA[devstack]]></category>
		<category><![CDATA[horizon]]></category>
		<category><![CDATA[https_proxy]]></category>
		<category><![CDATA[http_proxy]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[neutron]]></category>
		<category><![CDATA[nova]]></category>
		<category><![CDATA[no_proxy]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[OpenStack]]></category>
		<category><![CDATA[openvswitch]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SDN]]></category>
		<category><![CDATA[SecureCRT]]></category>
		<category><![CDATA[stack.sh]]></category>
		<category><![CDATA[stackrc]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[unstack.sh]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[云计算]]></category>
		<category><![CDATA[代理]]></category>
		<category><![CDATA[公有云]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[私有云]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[虚拟机]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1984</guid>
		<description><![CDATA[新公司是一家数据与基础设施提供商(to B)。初来乍到，和这里的同事了解了一些云计算平台和大数据平台的技术栈。对于“新鲜”(only to me)的技术栈，自己总有一种折腾的冲动，于是就有了这一篇备忘性质的文章，记录一下自己部署devstack的步骤、遇到的问题和解决方法。 和诸多国内提供公有云的厂商一样，公司的云产品也是基于成熟的OpenStack云计算平台框架和组件搭建的，并做了一些定制。长久以来，我一直以为OpenStack等都是Java技术栈的，对Java技术栈出品的东西总有一种莫名的恐惧感，现在我才发现原来OpenStack是Python系（那个汗汗汗啊）。而OpenStack的另外一个竞争对手:CloudStack才是正经八百的Java系。 OpenStack是一堆云计算平台组件（诸如存储、网络、镜像管理等）的合称，十分庞大且十分复杂，入门门槛不低，即便是为开发目的而进行的OpenStack部署也会让你折腾许久，甚至始终无法搭建成功。为此OpenStack为入门者和开发者推出了一个OpenStack开发环境：devstack。通过devstack，你可以在一个主机节点上部署一个“五脏俱全”的OpenStack Cloud。 一、安装devstack 建议将devstack部署在物理机上，这样可以屏蔽掉许多在虚拟机上部署devstack的问题(具体不详，很多书籍都推荐这么做^_^)。这里讲devstack部署在一台ubuntu 14.04.1的刀片服务器上。 1、下载devstack源码 $ git clone https://github.com/openstack-dev/devstack.git ./devstack Cloning into './devstack'... remote: Counting objects: 33686, done. remote: Compressing objects: 100% (10/10), done. Receiving objects: 1% (337/33686), 92.01 KiB &#124; 35.00 KiB/s Receiving objects: 4% (1457/33686), 452.01 KiB &#124; 62.00 KiB/s ... ... 这里直接用trunk上的最新revision： devstack版本 revision： commit 96ffde28b6e2f55f95997464aec47ae2c6cf91d3 [...]]]></description>
			<content:encoded><![CDATA[<p>新公司是一家数据与基础设施提供商(to B)。初来乍到，和这里的同事了解了一些云计算平台和大数据平台的技术栈。对于“新鲜”(only to me)的技术栈，自己总有一种折腾的冲动，于是就有了这一篇备忘性质的文章，记录一下自己部署<a href="https://github.com/openstack-dev/devstack">devstack</a>的步骤、遇到的问题和解决方法。</p>
<p>和诸多国内提供公有云的厂商一样，公司的云产品也是基于成熟的<a href="https://www.openstack.org/">OpenStack</a>云计算平台框架和组件搭建的，并做了一些定制。长久以来，我一直以为OpenStack等都是Java技术栈的，对Java技术栈出品的东西总有一种莫名的恐惧感，现在我才发现原来OpenStack是Python系（那个汗汗汗啊）。而OpenStack的另外一个竞争对手:<a href="http://cloudstack.apache.org/">CloudStack</a>才是正经八百的Java系。</p>
<p>OpenStack是一堆云计算平台组件（诸如存储、网络、镜像管理等）的合称，十分庞大且十分复杂，入门门槛不低，即便是为开发目的而进行的OpenStack部署也会让你折腾许久，甚至始终无法搭建成功。为此OpenStack为入门者和开发者推出了一个OpenStack开发环境：<a href="https://github.com/openstack-dev/devstack">devstack</a>。通过devstack，你可以在一个主机节点上部署一个“五脏俱全”的OpenStack Cloud。</p>
<h3>一、安装devstack</h3>
<p>建议将devstack部署在物理机上，这样可以屏蔽掉许多在虚拟机上部署devstack的问题(具体不详，很多书籍都推荐这么做^_^)。这里讲devstack部署在一台ubuntu 14.04.1的刀片服务器上。</p>
<h4>1、下载devstack源码</h4>
<pre><code>$ git clone https://github.com/openstack-dev/devstack.git ./devstack
Cloning into './devstack'...
remote: Counting objects: 33686, done.
remote: Compressing objects: 100% (10/10), done.
Receiving objects:   1% (337/33686), 92.01 KiB | 35.00 KiB/s
Receiving objects:   4% (1457/33686), 452.01 KiB | 62.00 KiB/s
... ...
</code></pre>
<p>这里直接用trunk上的最新revision：</p>
<pre><code>devstack版本 revision：
commit 96ffde28b6e2f55f95997464aec47ae2c6cf91d3
Merge: c4a0d21 e3a04dd
Author: Jenkins &lt;jenkins@review.openstack.org&gt;
Date:   Tue Apr 26 10:21:16 2016 +0000
</code></pre>
<h4>2、创建stack账户</h4>
<pre><code>$cd devstack/tools
~/devstack/tools$ sudo ./create-stack-user.sh
[sudo] password for baiming:
Creating a group called stack
Creating a user called stack
Giving stack user passwordless sudo privileges

$ cat /etc/passwd|grep stack
stack:x:1006:1006::/opt/stack:/bin/bash
</code></pre>
<p>修改devstack目录的owner和group权限：</p>
<pre><code>$ sudo chown -R stack:stack ./devstack/
</code></pre>
<p>切换到stack用户下：</p>
<pre><code>baiming@baiming:~$ sudo -i -u stack
stack@baiming:~$ cd /home/baiming/devstack
stack@baiming:/home/baiming/devstack$ ls
clean.sh  exerciserc   extras.d   functions-common  HACKING.rst  LICENSE          openrc     run_tests.sh  setup.py  tests    unstack.sh
data      exercises    files      FUTURE.rst        inc          MAINTAINERS.rst  pkg        samples       stackrc   tools
doc       exercise.sh  functions  gate              lib          Makefile         README.md  setup.cfg     stack.sh  tox.ini
</code></pre>
<h3>二、启动devstack</h3>
<p>我们在devstack目录下创建配置文件：local.conf</p>
<pre><code># Credentials
ADMIN_PASSWORD=devstack
MYSQL_PASSWORD=devstack
RABBIT_PASSWORD=devstack
SERVICE_PASSWORD=devstack
SERVICE_TOKEN=token
#Enable/Disable Services
disable_service n-net
enable_service q-svc
enable_service q-agt
enable_service q-dhcp
enable_service q-l3
enable_service q-meta
enable_service neutron
enable_service tempest
HOST_IP=10.10.105.71

#NEUTRON CONFIG
#Q_USE_DEBUG_COMMAND=True
#CINDER CONFIG
VOLUME_BACKING_FILE_SIZE=102400M
#GENERAL CONFIG
API_RATE_LIMIT=False
# Output
LOGFILE=/home/baiming/devstack/logs/stack.sh.log
VERBOSE=True
LOG_COLOR=False
SCREEN_LOGDIR=/home/baiming/devstack/logs
</code></pre>
<p>执行devstack下的stack.sh来启动各OpenStack组件，stack.sh是devstack“一键安装”的总控脚本，stack.sh执行ok了，devstack也就部署OK了：</p>
<pre><code>$&gt;./stack.sh
</code></pre>
<p>但问题接踵而至！</p>
<h3>三、问题与解决方法</h3>
<p>stack.sh的执行过程是漫长的，且问题也是多多。</p>
<h4>1、git协议 or https协议</h4>
<p>stack.sh执行后会去openstack官方库下载一些东西，于是遇到了第一个错误：</p>
<pre><code>+functions-common:git_timed:599            timeout -s SIGINT 0 git clone git://git.openstack.org/openstack/requirements.git /opt/stack/requirements
Cloning into '/opt/stack/requirements'...
fatal: unable to connect to git.openstack.org:
git.openstack.org[0: 104.130.246.128]: errno=Connection timed out
git.openstack.org[1: 2001:4800:7819:103:be76:4eff:fe06:63c]: errno=Network is unreachable

+functions-common:git_timed:602            [[ 128 -ne 124 ]]
+functions-common:git_timed:603            die 603 'git call failed: [git clone' git://git.openstack.org/openstack/requirements.git '/opt/stack/requirements]'
+functions-common:die:186                  local exitcode=0
+functions-common:die:187                  set +o xtrace
[Call Trace]
./stack.sh:708:git_clone
/home/baiming/devstack/functions-common:536:git_timed
/home/baiming/devstack/functions-common:603:die
[ERROR] /home/baiming/devstack/functions-common:603 git call failed: [git clone git://git.openstack.org/openstack/requirements.git /opt/stack/requirements]
Error on exit
./stack.sh: line 488: generate-subunit: command not found
</code></pre>
<p>stack.sh尝试用git clone git://xxxx，但由于我的主机在代理后面，因此git协议不能被支持，需要改为支持的协议类型，比如https。</p>
<p>解决方法：修改stackrc，更换git_base协议，并且增加http和https代理变量：</p>
<pre><code># Base GIT Repo URL
# Another option is https://git.openstack.org
GIT_BASE=${GIT_BASE:-https://git.openstack.org}

export http_proxy='http://10.10.126.187:3129'
export https_proxy='http://10.10.126.187:3129'
</code></pre>
<p>stackrc之于stack.sh类似.bashrc之于bash，在stack.sh执行时会对stackrc进行source，使其中的export环境变量生效。http_proxy等环境变量添加到stackrc中的效果就是：在stack.sh执行过程中会有类似如下语句出现：</p>
<pre><code>sudo -H http_proxy=http://10.10.126.187:3129 https_proxy=http://10.10.126.187:3129 no_proxy= PIP_FIND_LINKS= /usr/local/bin/pip2.7 install -c /opt/stack/requirements/upper-constraints.txt -U virtualenv
</code></pre>
<h4>2、重启stack.sh</h4>
<p>解决完上述问题后，如果直接重新执行stack.sh，那么会收到“有另外一个stack.sh session在执行的”错误信息。</p>
<p>为此，每次重启stack.sh之前都要先执行：./unstack.sh，清理一下环境。</p>
<h4>3、apt包下载错误</h4>
<p>在stack.sh执行过程中，会更新ubuntu apt repository，并下载许多第三方包或工具：</p>
<pre><code>Preconfiguring packages ...
(Reading database ... 123098 files and directories currently installed.)
Preparing to unpack .../libitm1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libitm1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libgomp1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libgomp1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libasan0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libasan0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libatomic1_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libatomic1:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libtsan0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libtsan0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libquadmath0_4.8.4-2ubuntu1~14.04.1_amd64.deb ...
Unpacking libquadmath0:amd64 (4.8.4-2ubuntu1~14.04.1) over (4.8.4-2ubuntu1~14.04) ...
Preparing to unpack .../libstdc++-4.8-dev_4.8.4-2ubuntu1~14.04.1_amd64.deb ...

.... ..... ....

Setting up libitm1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libgomp1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libasan0:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libatomic1:amd64 (4.8.4-2ubuntu1~14.04.1) ...
Setting up libtsan0:amd64 (4.8.4-2ubuntu1~14.04.1) ...
.... .....

 * Setting sysfs variables...                  [ OK ]
Setting up vlan (1.9-3ubuntu10) ...
Processing triggers for libc-bin (2.19-0ubuntu6) ...
Processing triggers for ureadahead (0.100.0-16) ...
Processing triggers for initramfs-tools (0.103ubuntu4.2) ...
update-initramfs: Generating /boot/initrd.img-3.16.0-57-generic
.... ....
</code></pre>
<p>如果你的source.list中添加了一些不稳定的源，那么这个包更新过程很可能会失败，从而导致stack.sh执行失败。解决方法就是识别出哪些源导致的失败，将之注释掉！</p>
<h4>4、MySQL access denied</h4>
<p>继续执行stack.sh，我们遇到了如下MySQL访问错误：</p>
<pre><code>+lib/databases/mysql:configure_database_mysql:91  sudo mysql -u root -p devstack -h 127.0.0.1 -e 'GRANT ALL PRIVILEGES ON *.* TO '\''root'\''@'\''%'\'' identified by '\''devstack'\'';'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
+lib/databases/mysql:configure_database_mysql:1  exit_trap
+./stack.sh:exit_trap:474                  local r=1
++./stack.sh:exit_trap:475                  jobs -p
+./stack.sh:exit_trap:475                  jobs=
+./stack.sh:exit_trap:478                  [[ -n '' ]]
+./stack.sh:exit_trap:484                  kill_spinner
+./stack.sh:kill_spinner:370               '[' '!' -z '' ']'
+./stack.sh:exit_trap:486                  [[ 1 -ne 0 ]]
+./stack.sh:exit_trap:487                  echo 'Error on exit'
Error on exit
+./stack.sh:exit_trap:488                  generate-subunit 1461911655 5243 fail
+./stack.sh:exit_trap:489                  [[ -z /opt/stack/logs ]]
+./stack.sh:exit_trap:492                  /home/baiming/devstack/tools/worlddump.py -d /opt/stack/logs
World dumping... see /opt/stack/logs/worlddump-2016-04-29-080139.txt for details
</code></pre>
<p>这个问题浪费了我不少时间，遍历了许多网上资料，最终下面这个方法解决了问题：</p>
<p>查看/etc/mysql/debian.cnf文件：</p>
<pre><code># Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host     = localhost
user     = debian-sys-maint
password = WY9OFagMxMb4YmyV
socket   = /var/run/mysqld/mysqld.sock
[mysql_upgrade]
host     = localhost
user     = debian-sys-maint
password = WY9OFagMxMb4YmyV
socket   = /var/run/mysqld/mysqld.sock
basedir  = /usr
</code></pre>
<p>修改mysql root密码：</p>
<pre><code>$ [root@localhost ~]# mysql -u debian-sys-maint - p

输入密码： WY9OFagMxMb4YmyV  ，进入到mysql数据库

mysql&gt;use mysql  ；
mysql&gt;update user set password=password("你的新密码") where user="root";
mysql&gt;flush privileges;
mysql&gt;exit
</code></pre>
<p>然后尝试使用新密码登录，如果登录成功，说明密码修改ok。再执行stack.sh就不会出现MySQL相关错误了。</p>
<h4>5、openvswitch/db.sock权限问题</h4>
<p>接下来我们遇到的问题是openvswitch/db.sock权限问题，错误日志如下：</p>
<pre><code>+lib/keystone:create_keystone_accounts:368  local admin_project
++lib/keystone:create_keystone_accounts:369  openstack project show admin -f value -c id
Discovering versions from the identity service failed when creating the password plugin. Attempting to determine version from URL.
Could not determine a suitable URL for the plugin
+lib/keystone:create_keystone_accounts:369  admin_project=
+lib/keystone:create_keystone_accounts:1   exit_trap
+./stack.sh:exit_trap:474                  local r=1
++./stack.sh:exit_trap:475                  jobs -p
+./stack.sh:exit_trap:475                  jobs=
+./stack.sh:exit_trap:478                  [[ -n '' ]]
+./stack.sh:exit_trap:484                  kill_spinner
+./stack.sh:kill_spinner:370               '[' '!' -z '' ']'
+./stack.sh:exit_trap:486                  [[ 1 -ne 0 ]]
+./stack.sh:exit_trap:487                  echo 'Error on exit'
Error on exit
+./stack.sh:exit_trap:488                  generate-subunit 1461920296 413 fail
+./stack.sh:exit_trap:489                  [[ -z /opt/stack/logs ]]
+./stack.sh:exit_trap:492                  /home/baiming/devstack/tools/worlddump.py -d /opt/stack/logs
World dumping... see /opt/stack/logs/worlddump-2016-04-29-090510.txt for details
2016-04-29T09:05:10Z|00001|reconnect|WARN|unix:/var/run/openvswitch/db.sock: connection attempt failed (Permission denied)
ovs-vsctl: unix:/var/run/openvswitch/db.sock: database connection failed (Permission denied)
+./stack.sh:exit_trap:498                  exit 1
</code></pre>
<p>我们手工执行ovs-vsctl命令：</p>
<pre><code>$ sudo service openvswitch-switch status
openvswitch-switch start/running

 $ ovs-vsctl show
2016-04-29T09:44:19Z|00001|reconnect|WARN|unix:/var/run/openvswitch/db.sock: connection attempt failed (Permission denied)
ovs-vsctl: unix:/var/run/openvswitch/db.sock: database connection failed (Permission denied)
</code></pre>
<p>同样的错误。这个问题在网上似乎也没有很好的答案，这里做了一个权限更改处理：</p>
<pre><code>$&gt;chmod 777 /var/run/openvswitch/db.sock
</code></pre>
<p>问题解决了！</p>
<h4>6、http proxy问题</h4>
<p>我们接下来停在了这里：</p>
<pre><code>++lib/keystone:create_keystone_accounts:369  openstack project show admin -f value -c id
Discovering versions from the identity service failed when creating the password plugin. Attempting to determine version from URL.
Could not determine a suitable URL for the plugin
</code></pre>
<p>还是停在这里，但这回不是/var/run/openvswitch /db.sock权限问题了。似乎是stack.sh想访问某个url获得一些version信息，但没有获取到。我开始怀疑是代理设置的问题：这个环境是有代理设置的，一旦走代理访问自己，那么肯定什么信息都得不到。但代理还不能去掉，因此很多组件下载都需要使用到代理访问外网。为此我们需要在stackrc中加上no_proxy环境变量：</p>
<pre><code>export no_proxy='10.10.105.71'
</code></pre>
<p>再执行stack.sh，至少这个问题是pass了。</p>
<h3>四、devstack部署ok</h3>
<p>在经过很不耐烦的漫长等待后，devstack终于算是部署成功了！stack.sh打印出了下面信息后成功退出了：</p>
<pre><code>=========================
DevStack Component Timing
=========================
Total runtime         2574

run_process            57
apt-get-update        120
pip_install           859
restart_apache_server  11
wait_for_service       32
apt-get                14
=========================
This is your host IP address: 10.10.105.71
This is your host IPv6 address: ::1
Horizon is now available at http://10.10.105.71/dashboard
Keystone is serving at http://10.10.105.71:5000/
The default users are: admin and demo
The password: devstack
2016-05-03 08:01:03.667 | stack.sh completed in 2574 seconds.

</code></pre>
<p>我们看devstack究竟运行了哪些组件：</p>
<pre><code>$ ps -ef|grep python
stack     1464  1461  0 15:24 pts/5    00:00:14 /usr/bin/python /usr/bin/dstat -tcmndrylpg --output /opt/stack/logs/dstat-csv.log
stack     1465  1461  6 15:24 pts/5    00:03:01 /usr/bin/python /usr/bin/dstat -tcmndrylpg --top-cpu-adv --top-io-adv --swap
stack    11641 11490  0 15:48 pts/10   00:00:03 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11899 11641  0 15:48 pts/10   00:00:00 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11900 11641  0 15:48 pts/10   00:00:00 /usr/bin/python /usr/local/bin/glance-registry --config-file=/etc/glance/glance-registry.conf
stack    11978 11821  1 15:48 pts/11   00:00:24 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    12105 11978  1 15:48 pts/11   00:00:30 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    12106 11978  1 15:48 pts/11   00:00:30 /usr/bin/python /usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf
stack    13411 13262  2 15:51 pts/12   00:00:29 /usr/bin/python /usr/local/bin/nova-api
stack    13551 13411  0 15:52 pts/12   00:00:03 /usr/bin/python /usr/local/bin/nova-api
stack    13552 13411  0 15:52 pts/12   00:00:03 /usr/bin/python /usr/local/bin/nova-api
stack    13823 13411  0 15:52 pts/12   00:00:00 /usr/bin/python /usr/local/bin/nova-api
stack    13824 13411  0 15:52 pts/12   00:00:00 /usr/bin/python /usr/local/bin/nova-api
stack    14309 14159  1 15:52 pts/13   00:00:25 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15092 14941  3 15:52 pts/14   00:00:39 /usr/bin/python /usr/local/bin/nova-network --config-file /etc/nova/nova.conf
stack    15352 14309  2 15:52 pts/13   00:00:38 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15353 14309  2 15:52 pts/13   00:00:38 /usr/bin/python /usr/local/bin/nova-conductor --config-file /etc/nova/nova.conf
stack    15432 15274  1 15:52 pts/15   00:00:14 /usr/bin/python /usr/local/bin/nova-scheduler --config-file /etc/nova/nova.conf
stack    15920 15768  0 15:52 pts/16   00:00:05 /usr/bin/python /usr/local/bin/nova-novncproxy --config-file /etc/nova/nova.conf --web /opt/stack/noVNC
stack    16571 16415  1 15:52 pts/17   00:00:13 /usr/bin/python /usr/local/bin/nova-consoleauth --config-file /etc/nova/nova.conf
stack    17134 17131  3 15:53 pts/18   00:00:46 /usr/bin/python /usr/local/bin/nova-compute --config-file /etc/nova/nova.conf
stack    17890 17740  1 15:54 pts/19   00:00:23 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18027 17890  0 15:54 pts/19   00:00:00 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18028 17890  0 15:54 pts/19   00:00:01 /usr/bin/python /usr/local/bin/cinder-api --config-file /etc/cinder/cinder.conf
stack    18363 18212  2 15:54 pts/20   00:00:33 /usr/bin/python /usr/local/bin/cinder-scheduler --config-file /etc/cinder/cinder.conf
stack    18853 18699  1 15:54 pts/21   00:00:22 /usr/bin/python /usr/local/bin/cinder-volume --config-file /etc/cinder/cinder.conf
stack    19060 18853  2 15:54 pts/21   00:00:28 /usr/bin/python /usr/local/bin/cinder-volume --config-file /etc/cinder/cinder.conf
</code></pre>
<p>果然很复杂。devstack的安装体验比OpenStack似乎也好不到那里去。stack.sh执行的时间足够编译10次linux os内核了。好多依赖，好多download。</p>
<p>在devstack目录下，我们还可以执行一下devstack的测试，./exercise.sh会执行这些测试：</p>
<pre><code>*********************************************************************
SUCCESS: End DevStack Exercise: /home/baiming/devstack/exercises/volumes.sh
*********************************************************************
=====================================================================
SKIP neutron-adv-test
SKIP swift
PASS aggregates
PASS client-args
PASS client-env
PASS sec_groups
PASS volumes
FAILED boot_from_volume
FAILED floating_ips
=====================================================================
</code></pre>
<p>此刻访问 http://10.10.105.71/dashboard，我们可以看到devstack horizon的首页：</p>
<p><img src="http://tonybai.com/wp-content/uploads/openstack-horizon-login.png" alt="img{512x368}" /></p>
<p>不过由于是通过SecureCRT端口映射访问到的主页，不知为何，登录后始终无法显示dashboard的页面。但通过后台horizon的日志来看，登录(admin/devstack)是成功的。我们仅能探索cli操作devstack的方式了。</p>
<h3>五、CLI方式操作devstack</h3>
<p>devstack提供了CLI方式对虚拟机、存储和网络等组件进行操作，其功能还要超过GUI所能提供的。在使用cli工具前，我们需要设置一些cli所需的用户变量，放在shell文件中（比如.bashrc）：</p>
<pre><code>export OS_USERNAME=admin
export OS_PASSWORD=devstack
export OS_TENANT_NAME=admin
export OS_AUTH_URL=http://10.10.105.71:5000/v2.0
</code></pre>
<p>上述变量生效后，我们就可以通过cli来hack devstack了：</p>
<p>nova位置和nova版本：</p>
<pre><code>$ which nova
/usr/local/bin/nova

$ nova --version
4.0.0
</code></pre>
<p>当前image列表：</p>
<pre><code>$ nova image-list
WARNING: Command image-list is deprecated and will be removed after Nova 15.0.0 is released. Use python-glanceclient or openstackclient instead.
+--------------------------------------+---------------------------------+--------+--------+
| ID                                   | Name                            | Status | Server |
+--------------------------------------+---------------------------------+--------+--------+
| b3f25af2-b5e1-43fe-8648-842fe48ed380 | cirros-0.3.4-x86_64-uec         | ACTIVE |        |
| d6bcc064-e2aa-4550-89e7-fd2f6a454758 | cirros-0.3.4-x86_64-uec-kernel  | ACTIVE |        |
| 788dec66-8989-4e84-8722-d9f4c9ee5ab0 | cirros-0.3.4-x86_64-uec-ramdisk | ACTIVE |        |
+--------------------------------------+---------------------------------+--------+--------+
</code></pre>
<p>虚拟机规格列表：</p>
<pre><code>$ nova flavor-list
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| ID | Name      | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
| 1  | m1.tiny   | 512       | 1    | 0         |      | 1     | 1.0         | True      |
| 2  | m1.small  | 2048      | 20   | 0         |      | 1     | 1.0         | True      |
| 3  | m1.medium | 4096      | 40   | 0         |      | 2     | 1.0         | True      |
| 4  | m1.large  | 8192      | 80   | 0         |      | 4     | 1.0         | True      |
| 42 | m1.nano   | 64        | 0    | 0         |      | 1     | 1.0         | True      |
| 5  | m1.xlarge | 16384     | 160  | 0         |      | 8     | 1.0         | True      |
| 84 | m1.micro  | 128       | 0    | 0         |      | 1     | 1.0         | True      |
| c1 | cirros256 | 256       | 0    | 0         |      | 1     | 1.0         | True      |
| d1 | ds512M    | 512       | 5    | 0         |      | 1     | 1.0         | True      |
| d2 | ds1G      | 1024      | 10   | 0         |      | 1     | 1.0         | True      |
| d3 | ds2G      | 2048      | 10   | 0         |      | 2     | 1.0         | True      |
| d4 | ds4G      | 4096      | 20   | 0         |      | 4     | 1.0         | True      |
+----+-----------+-----------+------+-----------+------+-------+-------------+-----------+
</code></pre>
<p>启动一个虚拟机：</p>
<pre><code>$ nova boot --flavor 1 --image b3f25af2-b5e1-43fe-8648-842fe48ed380 devstack_instance_1
+--------------------------------------+----------------------------------------------------------------+
| Property                             | Value                                                          |
+--------------------------------------+----------------------------------------------------------------+
| OS-DCF:diskConfig                    | MANUAL                                                         |
| OS-EXT-AZ:availability_zone          |                                                                |
| OS-EXT-SRV-ATTR:host                 | -                                                              |
| OS-EXT-SRV-ATTR:hostname             | devstack-instance-1                                            |
| OS-EXT-SRV-ATTR:hypervisor_hostname  | -                                                              |
| OS-EXT-SRV-ATTR:instance_name        | instance-00000005                                              |
| OS-EXT-SRV-ATTR:kernel_id            | d6bcc064-e2aa-4550-89e7-fd2f6a454758                           |
| OS-EXT-SRV-ATTR:launch_index         | 0                                                              |
| OS-EXT-SRV-ATTR:ramdisk_id           | 788dec66-8989-4e84-8722-d9f4c9ee5ab0                           |
| OS-EXT-SRV-ATTR:reservation_id       | r-3rpqat0r                                                     |
| OS-EXT-SRV-ATTR:root_device_name     | -                                                              |
| OS-EXT-SRV-ATTR:user_data            | -                                                              |
| OS-EXT-STS:power_state               | 0                                                              |
| OS-EXT-STS:task_state                | scheduling                                                     |
| OS-EXT-STS:vm_state                  | building                                                       |
| OS-SRV-USG:launched_at               | -                                                              |
| OS-SRV-USG:terminated_at             | -                                                              |
| accessIPv4                           |                                                                |
| accessIPv6                           |                                                                |
| adminPass                            | dGBd6vj55vP2                                                   |
| config_drive                         |                                                                |
| created                              | 2016-05-03T09:00:33Z                                           |
| description                          | -                                                              |
| flavor                               | m1.tiny (1)                                                    |
| hostId                               |                                                                |
| host_status                          |                                                                |
| id                                   | bdb93a06-0c4f-434f-a1b5-ae2ca9293c58                           |
| image                                | cirros-0.3.4-x86_64-uec (b3f25af2-b5e1-43fe-8648-842fe48ed380) |
| key_name                             | -                                                              |
| locked                               | False                                                          |
| metadata                             | {}                                                             |
| name                                 | devstack_instance_1                                            |
| os-extended-volumes:volumes_attached | []                                                             |
| progress                             | 0                                                              |
| security_groups                      | default                                                        |
| status                               | BUILD                                                          |
| tenant_id                            | ce19134da8774d509bfa15daaca83665                               |
| updated                              | 2016-05-03T09:00:34Z                                           |
| user_id                              | 45436c9a744b4f41921edb3c368ce5f7                               |
+--------------------------------------+----------------------------------------------------------------+
</code></pre>
<p>通过nova list可以查看到当前主机上的虚拟机详情：</p>
<pre><code>$ nova list
+--------------------------------------+---------------------+--------+------------+-------------+------------------+
| ID                                   | Name                | Status | Task State | Power State | Networks         |
+--------------------------------------+---------------------+--------+------------+-------------+------------------+
| bdb93a06-0c4f-434f-a1b5-ae2ca9293c58 | devstack_instance_1 | ACTIVE | -          | Running     | private=10.0.0.5 |
+--------------------------------------+---------------------+--------+------------+-------------+------------------+
</code></pre>
<p>在host上ping该虚拟机实例，可以ping通：</p>
<pre><code>$ ping 10.0.0.5
PING 10.0.0.5 (10.0.0.5) 56(84) bytes of data.
64 bytes from 10.0.0.5: icmp_seq=1 ttl=64 time=0.935 ms
64 bytes from 10.0.0.5: icmp_seq=2 ttl=64 time=0.982 ms
^C
--- 10.0.0.5 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.935/0.958/0.982/0.038 ms
</code></pre>
<p>通过网桥工具查看网桥设备，看到多出一个br100的网桥，eth0、vnet0~vnet2均连接在该网桥上：</p>
<pre><code>$ brctl show
bridge name    bridge id        STP enabled    interfaces
br100        8000.0017a447a8a9    no        eth0
                            vnet0
                            vnet1
                            vnet2
</code></pre>
<p>挂起虚拟机：</p>
<pre><code>$ nova suspend bdb93a06-0c4f-434f-a1b5-ae2ca9293c58
$ nova list
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+
| ID                                   | Name                | Status    | Task State | Power State | Networks         |
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+
| bdb93a06-0c4f-434f-a1b5-ae2ca9293c58 | devstack_instance_1 | SUSPENDED | -          | Shutdown    | private=10.0.0.5 |
+--------------------------------------+---------------------+-----------+------------+-------------+------------------+
</code></pre>
<h3>六、小结</h3>
<p>devstack号称是为开发准备的，已经“一键化”，但从实际效果来看，体验依旧不佳。由此也可以估计出OpenStack的部署难度和坎坷度了:)。</p>
<p style='text-align:left'>&copy; 2016, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2016/05/04/deploy-devstack/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>VirtualBox虚拟机下Windows登录密码破解方法</title>
		<link>https://tonybai.com/2014/10/29/crack-windows-logon-password-under-virtualbox/</link>
		<comments>https://tonybai.com/2014/10/29/crack-windows-logon-password-under-virtualbox/#comments</comments>
		<pubDate>Wed, 29 Oct 2014 09:28:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[CDLinux]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[Crack]]></category>
		<category><![CDATA[livecd]]></category>
		<category><![CDATA[MacAir]]></category>
		<category><![CDATA[TinyCoreLinux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[virtualbox]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[Win7]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[破解]]></category>
		<category><![CDATA[虚拟机]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1589</guid>
		<description><![CDATA[近两年虚拟机的发展给开发人员带来了极大便利，安装一个新环境，只需从别人那里copy一份虚拟机文件即可，分分钟搞定。我之前一直在Ubuntu下工 作，Windows偶尔使用，于是在Ubuntu VirtualBox下安装了一个Windows 7。今年将工作环境迁移到Mac Air下了，但偶尔也有Windows的使用需求，于是直接从我原来的Ubuntu下将Win7的Vdi文件Copy到Air上，便直接可以使用Win7 了，省去了重新安装Win7以及庞大的Office组件的工作。 前两天，打开Mac Air下的VirtualBox，启动Win7虚拟机，在Win7登录界面输入密码后，系统提示我密码错误。反复输入多次，将我常用的密码都试了一遍依旧 无法进入。我只能在原Ubuntu下临时用用Win7。但毕竟在Air上没有Win7十分不便，一些Word, PPT文档需要在两天机器上传来传去。无奈下，我都由了重新在Air下安装一个Win7甚至是Win8的打算了。 今天又有一个PPT编写的task，这件事再次被提上日程。我换了下思维：能不能破解一下Win7登录密码呢？于是求助度娘（谷哥离去好久了）。还别说， 还真是有破解方法，多数是通过PE工具盘快速修改登录密码。但PE工具盘挺大（几百兆，公司下载不便），我的又是虚拟机环境，这种方法不是我的菜啊。于是 又看到另外一种思路：通过某个Linux livecd或安装盘引导，mount windows分区，将C:\Windows\System32\cmd.exe改名为osk.exe。osk.exe是虚拟键盘程序。在Win7登录页 面的左下角可以启动这个虚拟键盘程序。一旦我替换成功，启动虚拟键盘程序就变成了启动Win7命令行程序。有了命令行，我们就可以通过net user命令查看当前账户列表、重置某个用户的password了。思路很清晰，是我的菜。 在我的Ubuntu机器上倒是有几个Linux发行版的live cd iso文件，比如ubuntu 14.04.1 desktop, centos7 desktop，不过个头都太大了，传到我的Air上还是很费劲的。我想最好有一个tiny的linux发行版。度娘告诉我有很多选择。我首先选了Tiny Core Linux， TinyCore-5.4.iso才不到14M。于是打开Win7虚拟机的&#8220;设置&#8221;页面，将 TinyCore-5.4.iso作为虚拟iso&#8220;插入&#8221;IDE光驱。TinyCore的启动就是秒秒的事情。TinyCore的桌面风格模仿Mac OS，桌面下方放置了一个dock条。TinyCore自带mount tools，打开后，用鼠标点击sda2，sda2盘符由红变绿，说明mount成功。 打开TinyCore的Terminal程序，进入/mnt/sda2，本想安装方案修改cmd.exe的名字，但Tiny Core提示：这是个Read-Only Filesystem。显然这是个只读mount。于是各种尝试读写挂在（包括修改/etc/fstab、mount -a, mount -o remount等），都无法改变Read-Only Filesystem的事实，于是放弃。 换国人的发行版：CDLinux，这个发行版似乎已经不再更新，最新版本 CDlinux_mini-0.9.7.1.iso，发布时间是2012年3月18日。CDLinux的Size比TinyLinux稍大些，36M。 CDLinux启动略慢，并且只是Console Only（标准版带有桌面环境），没有图形桌面。进入命令行后，执行一下mount命令，发现/dev/sda2居然是rw方式挂载载/media /xxx下的，于是进入该目录，尝试touch test.txt，完全没有问题。 于是按照方案说明，将C:\Windows\System32下的osk.exe备份一下，将cmd.exe改名为osk.exe。 将光盘盘片删除，启动Win7，进入登陆页面时，点击左下角&#8220;轻松访问&#8221;按钮，选择&#8220;不使用键盘键入&#8221;，确定。命令行窗口弹出。 在命令行窗口执行net user 查看用户名列表。我的用户名是tonybai，再通过net user tonybai newpassword重置tonybai的密码。执行成功后，用新密码登陆，顺利进入Win7 Desktop。Crack成功！ &#169; 2014, [...]]]></description>
			<content:encoded><![CDATA[<p>近两年虚拟机的发展给开发人员带来了极大便利，安装一个新环境，只需从别人那里copy一份虚拟机文件即可，分分钟搞定。我之前一直在<a href="http://tonybai.com/tag/Ubuntu/">Ubuntu</a>下工 作，Windows偶尔使用，于是在Ubuntu<a href="http://virtualbox.org/"> VirtualBox</a>下安装了一个Windows 7。今年将工作环境迁移到Mac Air下了，但偶尔也有Windows的使用需求，于是直接从我原来的<a href="http://tonybai.com/2012/12/04/upgrade-ubuntu-to-1204-lts/">Ubuntu</a>下将Win7的Vdi文件Copy到Air上，便直接可以使用Win7 了，省去了重新安装Win7以及庞大的Office组件的工作。</p>
<p>前两天，打开Mac Air下的VirtualBox，启动Win7虚拟机，在Win7登录界面输入密码后，系统提示我密码错误。反复输入多次，将我常用的密码都试了一遍依旧 无法进入。我只能在原Ubuntu下临时用用Win7。但毕竟在Air上没有Win7十分不便，一些Word, PPT文档需要在两天机器上传来传去。无奈下，我都由了重新在Air下安装一个Win7甚至是Win8的打算了。</p>
<p>今天又有一个PPT编写的task，这件事再次被提上日程。我换了下思维：能不能破解一下Win7登录密码呢？于是求助度娘（谷哥离去好久了）。还别说， 还真是有破解方法，多数是通过PE工具盘快速修改登录密码。但PE工具盘挺大（几百兆，公司下载不便），我的又是虚拟机环境，这种方法不是我的菜啊。于是 又看到另外一种思路：通过某个Linux livecd或安装盘引导，mount windows分区，将C:\Windows\System32\cmd.exe改名为osk.exe。osk.exe是虚拟键盘程序。在Win7登录页 面的左下角可以启动这个虚拟键盘程序。一旦我替换成功，启动虚拟键盘程序就变成了启动Win7命令行程序。有了命令行，我们就可以通过net user命令查看当前账户列表、重置某个用户的password了。思路很清晰，是我的菜。</p>
<p>在我的Ubuntu机器上倒是有几个Linux发行版的live cd iso文件，比如ubuntu 14.04.1 desktop, centos7 desktop，不过个头都太大了，传到我的Air上还是很费劲的。我想最好有一个tiny的linux发行版。度娘告诉我有很多选择。我首先选了<a href="http://distro.ibiblio.org/tinycorelinux/">Tiny Core Linux</a>， TinyCore-5.4.iso才不到14M。于是打开Win7虚拟机的&ldquo;设置&rdquo;页面，将 TinyCore-5.4.iso作为虚拟iso&ldquo;插入&rdquo;IDE光驱。TinyCore的启动就是秒秒的事情。TinyCore的桌面风格模仿Mac OS，桌面下方放置了一个dock条。TinyCore自带mount tools，打开后，用鼠标点击sda2，sda2盘符由红变绿，说明mount成功。</p>
<p>打开TinyCore的Terminal程序，进入/mnt/sda2，本想安装方案修改cmd.exe的名字，但Tiny Core提示：这是个Read-Only Filesystem。显然这是个只读mount。于是各种尝试读写挂在（包括修改/etc/fstab、mount -a, mount -o remount等），都无法改变Read-Only Filesystem的事实，于是放弃。</p>
<p>换国人的发行版：<a href="http://cdlinux.info/wiki/">CDLinux</a>，这个发行版似乎已经不再更新，最新版本 CDlinux_mini-0.9.7.1.iso，发布时间是2012年3月18日。CDLinux的Size比TinyLinux稍大些，36M。 CDLinux启动略慢，并且只是Console Only（标准版带有桌面环境），没有图形桌面。进入命令行后，执行一下mount命令，发现/dev/sda2居然是rw方式挂载载/media /xxx下的，于是进入该目录，尝试touch test.txt，完全没有问题。</p>
<p>于是按照方案说明，将C:\Windows\System32下的osk.exe备份一下，将cmd.exe改名为osk.exe。</p>
<p>将光盘盘片删除，启动Win7，进入登陆页面时，点击左下角&ldquo;轻松访问&rdquo;按钮，选择&ldquo;不使用键盘键入&rdquo;，确定。命令行窗口弹出。</p>
<p>在命令行窗口执行net user 查看用户名列表。我的用户名是tonybai，再通过net user tonybai newpassword重置tonybai的密码。执行成功后，用新密码登陆，顺利进入Win7 Desktop。Crack成功！</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/29/crack-windows-logon-password-under-virtualbox/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>docker容器内服务程序的优雅退出</title>
		<link>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/</link>
		<comments>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/#comments</comments>
		<pubDate>Thu, 09 Oct 2014 13:58:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Dockerfile]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[nsenter]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[Signal]]></category>
		<category><![CDATA[supervisor]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[命令行]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[映像]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1555</guid>
		<description><![CDATA[近期在试验如何将我们的产品部署到docker容器中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。 一、优雅退出的原理 对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&#8220;优雅退出&#8221;操作。 与&#8220;优雅退出&#8221;对立的是&#8220;暴力退出&#8221;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&#8220;不一致&#8221;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。 二、测试用&#8220;服务程序&#8221; 为了测试docker容器对优雅退出的支持，我们编写如下&#8220;服务程序&#8221;用于放在docker容器中运行： //dockerapp1.go package main import &#34;fmt&#34; import &#34;time&#34; import &#34;os&#34; import &#34;os/signal&#34; import &#34;syscall&#34; type signalHandler func(s os.Signal, arg interface{}) type signalSet struct { &#160;&#160;&#160;&#160;&#160;&#160;&#160; m map[os.Signal]signalHandler } func signalSetNew() *signalSet { &#160;&#160;&#160;&#160;&#160;&#160;&#160; ss := new(signalSet) [...]]]></description>
			<content:encoded><![CDATA[<p><span style="line-height: 1.6em;">近期在试验如何将我们的产品部署到</span><a href="http://docker.com" style="line-height: 1.6em;">docker容器</a><span style="line-height: 1.6em;">中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。</span></p>
<p><b>一、优雅退出的原理</b></p>
<p>对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&ldquo;优雅退出&rdquo;操作。</p>
<p>与&ldquo;优雅退出&rdquo;对立的是&ldquo;暴力退出&rdquo;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&ldquo;不一致&rdquo;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。</p>
<p><b>二、</b><b>测试用&ldquo;服务程序&rdquo;</b></p>
<p>为了测试docker容器对优雅退出的支持，我们编写如下&ldquo;服务程序&rdquo;用于放在docker容器中运行：</p>
<p><font face="Courier New">//dockerapp1.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;<br />
	import &quot;os&quot;<br />
	import &quot;os/signal&quot;<br />
	import &quot;syscall&quot;</font></p>
<p><font face="Courier New">type signalHandler func(s os.Signal, arg interface{})</font></p>
<p><font face="Courier New">type signalSet struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m map[os.Signal]signalHandler<br />
	}</font></p>
<p><font face="Courier New">func signalSetNew() *signalSet {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := new(signalSet)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.m = make(map[os.Signal]signalHandler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ss<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) register(s os.Signal, handler signalHandler) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[s]; !found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[s] = handler<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) handle(sig os.Signal, arg interface{}) (err error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[sig]; found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[sig](sig, arg)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return fmt.Errorf(&quot;No handler available for signal %v&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(&quot;won&#39;t reach here&quot;)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;unknown signal received: %v, app exit unexpectedly\n&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>关于<a href="http://tonybai.com/tag/golang">Go语言</a>对系统Signal的处理，可以参考《<a href="http://tonybai.com/2012/09/21/signal-handling-in-go/">Go中的系统Signal处理</a>》一文。</p>
<p><b>三、制作测试用docker image</b></p>
<p>在《 <a href="http://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/">Ubuntu Server 14.04安装docker</a>》一文中，我们完成了在ubuntu 14.04上安装docker的步骤。要制作测试用docker image，我们首先需要pull一个base image。我们以CentOS6.5为例：</p>
<p>在Ubuntu 14.04上执行：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo&nbsp; docker pull centos:centos6</font></p>
<p>docker会自动从<a href="https://registry.hub.docker.com">官方仓库</a>下载一个制作好的docker image。下载成功后，我们可以run一下试试，像这样：</p>
<p><font face="Courier New">$&gt; sudo docker run -t -i centos:centos6 /bin/bash</font></p>
<p>我们查看一下CentOS6的小版本：<br />
	<font face="Courier New">$&gt; cat /etc/centos-release<br />
	CentOS release 6.5 (Final)</font></p>
<p>这是一个极其精简的CentOS，各种工具均未安装：<br />
	<font face="Courier New">bash-4.1# telnet<br />
	bash: telnet: command not found<br />
	bash-4.1# ssh<br />
	bash: ssh: command not found<br />
	bash-4.1# ftp<br />
	bash: ftp: command not found<br />
	bash-4.1# echo $PATH<br />
	/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</font></p>
<p>如果你要安装一些必要的工具，可以直接使用yum install，默认的base image已经将yum配置好了，可以直接使用。如果通过公司代理访问外部网络，别忘了先export http_proxy。另外docker直接使用宿主机的/etc/resolv.conf作为容器的DNS，我们也无需额外设置DNS。</p>
<p>接下来，我们就制作我们的第一个测试用image。安装官方推荐的Best Practice，我们使用Dockerfile来bulid一个测试用image。步骤如下：</p>
<p>- 建立~/ImagesFactory目录<br />
	- 将构建好的dockerapp1拷贝到~/ImagesFactory目录下<br />
	- 进入~/ImagesFactory目录，创建Dockerfile文件，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin<br />
	CMD /bin/dockerapp1</font></p>
<p>- 执行docker build，结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin<br />
	2014/10/09 16:05:25 lchown /var/lib/docker/aufs/mnt/fb0e864d3f07ca17ef8b6b69f034728e1f1158fd3f9c83fa48243054b2f26958/bin/dockerapp1: not a directory</font></p>
<p>居然build失败，提示什么not a directory。于是各种Search，终于发现问题所在，原来是&ldquo;<font face="Courier New">COPY ./dockerapp1 /bin</font>&rdquo;这条命令错了，少了个&ldquo;/&rdquo;，将&quot; /bin&quot;改为&ldquo;/bin/&rdquo;就OK了，Docker真是奇怪啊，这块明显应该做得更兼容些。新的Dockerfile如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin/<br />
	CMD /bin/dockerapp1</font></p>
<p>构建结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 20c3783c42ab<br />
	Removing intermediate container cab639ab4321<br />
	Step 3 : CMD /bin/dockerapp1<br />
	&nbsp;&#8212;&gt; Running in 31875d3c37f9<br />
	&nbsp;&#8212;&gt; 21a720a808a7<br />
	Removing intermediate container 31875d3c37f9<br />
	Successfully built 21a720a808a7</font></p>
<p><font face="Courier New">$ sudo docker images<br />
	REPOSITORY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TAG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VIRTUAL SIZE<br />
	test&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 21a720a808a7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 59 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 214.6 MB</font></p>
<p><b>四、第一个测试容器</b></p>
<p>我们基于image &quot;test:v1&quot;启动一个测试容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v1&quot;<br />
	daf3ae88fec23a31cde9f6b9a3f40057953c87b56cca982143616f738a84dcba</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	daf3ae88fec2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 17 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condescending_sammet&nbsp;&nbsp; </font></p>
<p>通过docker run命令，我们基于image&quot;test:v1&quot;启动了一个容器。通过docker ps命令可以看到容器成功启动，容器id：<font face="Courier New">daf3ae88fec2，别名为：</font><font face="Courier New">condescending_sammet。</font></p>
<p><font face="Courier New">根据Dockerfile我们知道，容器启动后将执行&quot;/bin/dockerapp1&quot;这个程序，dockerapp1退出，容器即退出。 run命令的&quot;-d&quot;选项表示容器将以daemon的形式运行，我们在前台无法看到容器的输出。那么我们怎么查看容器的输出呢？我们可以通过 docker logs + 容器id的方式查看容器内应用的标准输出或标准错误。我们也可以进入容器来查看。</font></p>
<p><font face="Courier New">进入容器有多种方法，比如用sudo docker attach </font><font face="Courier New"><font face="Courier New">daf3ae88fec2</font>。attach后，就好比将daemon方式运行的容器 拿到了前台，你可以Ctrl + C一下，可以看到如下dockerapp1的输出:</font></p>
<p><font face="Courier New">^Chandle signal: interrupt</font></p>
<p><font face="Courier New">另外一种方式是利用nsenter工具进入我们容器的namespace空间。ubuntu 14.04下可以通过如下方式安装该工具：</font></p>
<p><font face="Courier New">$ wget <a class="moz-txt-link-freetext" href="https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz">https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz</a>; tar xzvf util-linux-2.24.tar.gz<br />
	$ cd util-linux-2.24<br />
	$ ./configure &#8211;without-ncurses &amp;&amp; make nsenter<br />
	$ sudo cp nsenter /usr/local/bin</font></p>
<p>安装后，我们通过如下方式即可进入上面的容器：</p>
<p><font face="Courier New">$ echo $(sudo docker inspect &#8211;format &quot;{{ .State.Pid }}&quot; daf3ae88fec2)<br />
	5494<br />
	$ sudo nsenter &#8211;target 5494 &#8211;mount &#8211;uts &#8211;ipc &#8211;net &#8211;pid<br />
	-bash-4.1# ps -ef<br />
	UID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PID&nbsp; PPID&nbsp; C STIME TTY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TIME CMD<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:20 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 /bin/dockerapp1<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 -bash<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27&nbsp;&nbsp;&nbsp; 16&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 ps -ef<br />
	-bash-4.1# </font></p>
<p>进入容器后通过ps命令可以看到正在运行的dockerapp1程序。在容器内，我们可以通过kill来测试dockerapp1的运行情况：</p>
<p><font face="Courier New">-bash-4.1# kill -s SIGINT 1</font></p>
<p>通过前面的attach窗口，我们可以看到dockerapp1输出:</p>
<p><font face="Courier New">handle signal: interrupt</font></p>
<p>如果你发送SIGTERM信号，那么dockerapp1将终止运行，容器也就停止了。</p>
<p><font face="Courier New">-bash-4.1# kill 1</font></p>
<p>attach窗口显示：</p>
<p><font face="Courier New">signal termiate received, app exit normally</font></p>
<p>我们可以看到容器启动后默认执行的时Dockerfile中的CMD命令，如果Dockerfile中有多行CMD命令，Docker在启动容器 时只会执行最后一条CMD命令。如果在docker run中指定了命令，docker则会执行命令行中的命令而不会执行dockerapp1，比如：</p>
<p><font face="Courier New">$ sudo docker run -t -i &quot;test:v1&quot; /bin/bash<br />
	bash-4.1# </font></p>
<p>这里我们看到直接执行的时bash，dockerapp1并未执行。</p>
<p><b>五、docker stop的行为</b></p>
<p>我们先来看看docker stop的manual：</p>
<p><font face="Courier New">$ sudo docker stop &#8211;help<br />
	Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]<br />
	Stop a running container by sending SIGTERM and then SIGKILL after a grace period<br />
	&nbsp; -t, &#8211;time=10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.</font></p>
<p>可以看出当我们执行docker stop时，docker会首先向容器内的当前主程序发送一个SIGTERM信号，用于容器内程序的退出。如果容器在收到SIGTERM后没有马上退出， 那么stop命令会在等待一段时间（默认是10s）后，再向容器发送SIGKILL信号，将容器杀死，变为退出状态。</p>
<p>我们来验证一下docker stop的行为。启动刚才那个容器：</p>
<p><font face="Courier New">$ sudo docker start daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p><font face="Courier New">attach到容器daf3ae88fec2<br />
	$ sudo docker attach daf3ae88fec2</font></p>
<p>新打开一个窗口，执行docker stop命令：<br />
	<font face="Courier New">$ sudo docker stop daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p>可以看到attach窗口输出：<br />
	<font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>通过docker ps查看，发现容器已经退出。</p>
<p>也许通过上面的例子还不能直观的展示stop命令的<b>两阶段行为</b>，因为dockerapp1收到SIGTERM后直接就退出 了，stop命令无需等待容器慢慢退出，也无需发送SIGKILL。我们改造一下dockerapp1这个程序。</p>
<p>我们复制一下dockerapp1.go为dockerapp2.go，编辑dockerapp2.go，将handler中对SIGTERM的 处理注释掉，其他不变：</p>
<p><font face="Courier New">handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>我们使用dockerapp2来构建一个新image：test:v2，将Dockerfile中得dockerapp1换成 dockerapp2即可。</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v2&quot; ./<br />
	Sending build context to Docker daemon 9.369 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp2 /bin/<br />
	&nbsp;&#8212;&gt; 27cd613a9bd7<br />
	Removing intermediate container 07c760b6223b<br />
	Step 3 : CMD /bin/dockerapp2<br />
	&nbsp;&#8212;&gt; Running in 1aac086452a7<br />
	&nbsp;&#8212;&gt; 82eb876fefd2<br />
	Removing intermediate container 1aac086452a7<br />
	Successfully built 82eb876fefd2</font></p>
<p>利用image &quot;test:v2&quot;创建一个容器来测试stop。</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v2&quot;<br />
	29f3ec1af3c355458cbbd802a5e8a53da28e9f51a56ce822c7bba2a772edceac</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	29f3ec1af3c3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 7 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 6 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; romantic_feynman&nbsp;</font>&nbsp;&nbsp;</p>
<p>Attach到这个容器并观察，在另外一个窗口stop该container。我们在attach窗口只看到如下输出：</p>
<p><font face="Courier New">handle signal: terminated</font></p>
<p>stop命令的执行没有立即返回，而是等待容器退出。等待10s后，容器退出，stop命令执行结束。从这个例子我们可以明显看出stop的两阶 段行为。</p>
<p>如果我们以<font face="Courier New">sudo docker run -i -t &quot;test:v1&quot; /bin/bash</font>形式启动容器，那stop命令会将SIGTERM发送给bash这个程序，即使你通过nsenter进入容 器，启动了dockerapp1，dockerapp1也不会收到SIGTERM，dockerapp1会随着容器的退出而被强行终止，就像被 kill -9了一样。</p>
<p><b>六、多进程容器服务</b>程序</p>
<p>上面无论是dockerapp1还是dockerapp2，都是一个单进程服务程序。如果我们在容器内执行一个多进程程序，我们该如何优雅退出 呢？我们先来编写一个多进程的服务程序dockerapp3：</p>
<p>在dockerapp1.go的基础上对main和sysSignalHandleDemo进行修改形成dockerapp3.go，修改后这两 个函数的代码如下：</p>
<p><font face="Courier New">//dockerapp3.go<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;err fork process, err: %v\n&quot;, err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pid == 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am in child process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the child process wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am parent process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;fork ok, childpid = %v\n&quot;, pid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: handle signal: %v\n&quot;, syscall.Getpid(), s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: signal termiate received, app exit normally\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: unknown signal received: %v, app exit unexpectedly\n&quot;, syscall.Getpid(), sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>dockerapp3利用fork创建了一个子进程，这样dockerapp3实际上是两个进程在运行，各自有自己的signal监听 goroutine，goroutine的处理逻辑是相同的。注意：由于Windows和Mac OS X不具备fork语义，因此在这两个平台上运行dockerapp3不会得到预期结果。</p>
<p>利用dockerapp3，我们创建image &quot;test:v3&quot;:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v3&quot; ./<br />
	[sudo] password for tonybai:<br />
	Sending build context to Docker daemon 11.24 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp3 /bin/<br />
	&nbsp;&#8212;&gt; 6ccf97065853<br />
	Removing intermediate container 6d85fe241939<br />
	Step 3 : CMD /bin/dockerapp3<br />
	&nbsp;&#8212;&gt; Running in 75d76380992a<br />
	&nbsp;&#8212;&gt; c9e7bf361ed7<br />
	Removing intermediate container 75d76380992a<br />
	Successfully built c9e7bf361ed7</font></p>
<p>启动基于test:v3 image的容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v3&quot;<br />
	781cecb4b3628cb33e1b104ea57e506ad5cb4a44243256ebd1192af86834bae6<br />
	$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	781cecb4b362&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 5 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 4 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insane_bohr&nbsp;&nbsp;&nbsp;</font>&nbsp;&nbsp;&nbsp;</p>
<p>通过docker logs查看dockerapp3的输出：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13</font></p>
<p>可以看出主进程pid为1，子进程pid为13。我们通过stop停止该容器：</p>
<p><font face="Courier New">$ sudo docker stop 781cecb4b362<br />
	781cecb4b362</font></p>
<p>再次通过docker logs查看：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13<br />
	1: handle signal: terminated<br />
	1: signal termiate received, app exit normally</font></p>
<p>我们可以看到主进程收到了stop发来的SIGTERM并退出，主进程的退出导致容器退出，导致子进程13也无法生存，并且没有优雅退出。而在非 容器状态下，子进程是可以被init进程接管的。</p>
<p>因此对于docker容器内运行的多进程程序，stop命令只会将SIGTERM发送给容器主进程，要想让其他进程也能优雅退出，需要在主进程与 其他进程间建立一种通信机制。在主进程退出前，等待其他子进程退出。待所有其他进程退出后，主进程再退出，容器停止。这样才能保证服务程序的优雅 退出。</p>
<p><b>七、容器内启动多个服务程序</b></p>
<p>虽说docker <a href="https://docs.docker.com/articles/dockerfile_best-practices/">best practice</a>建议一个container内只放置一个服务程序，但对已有的一些遗留系统，在架构没有做出重构之前，很可能会有在一个 container中部署两个以上服务程序的情况和需求。而docker Dockerfile只允许执行一个CMD，这种情况下，我们就需要借助类似supervisor这样的进程监控管理程序来启动和管理container 内的多个程序了。</p>
<p>下面我们来自制作一个基于centos:centos6的安装了supervisord以及两个服务程序的image。我们将dockerapp1拷贝一份，并将拷贝命名为dockerapp1-brother。下面是我们的Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./dockerapp1 /bin/<br />
	COPY ./dockerapp1-brother /bin/<br />
	CMD ["/usr/bin/supervisord"]</font></p>
<p>supervisord的配置文件supervisord.conf内容如下：</p>
<p><font face="Courier New">; supervisor config file</font></p>
<p><font face="Courier New">[unix_http_server]<br />
	file=/var/run/supervisor.sock&nbsp;&nbsp; ; (the path to the socket file)<br />
	chmod=0700&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; sockef file mode (default 0700)</font></p>
<p><font face="Courier New">[supervisord]<br />
	logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)<br />
	pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)<br />
	childlogdir=/var/log/supervisor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; (&#39;AUTO&#39; child log dir, default $TEMP)</font></p>
<p><font face="Courier New">[rpcinterface:supervisor]<br />
	supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface</font></p>
<p><font face="Courier New">[supervisorctl]<br />
	serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL&nbsp; for a unix socket</font></p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p><font face="Courier New">[program:dockerapp1]<br />
	command=/bin/dockerapp1<br />
	stdout_logfile=/tmp/dockerapp1.log<br />
	stopsignal=TERM<br />
	stopwaitsecs=10</font></p>
<p><font face="Courier New">[program:dockerapp1-brother]<br />
	command=/bin/dockerapp1-brother<br />
	stdout_logfile=/tmp/dockerapp1-brother.log<br />
	stopsignal=QUIT<br />
	stopwaitsecs=10</font></p>
<p>开始build镜像：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; sudo docker build -t=&quot;test:supervisor-v1&quot; ./<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; Successfully built d006b9ad10eb</font></p>
<p>基于该镜像，启动一个容器：<br />
	<font face="Courier New">$&gt; sudo docker run -d &quot;test:supervisor-v1&quot;<br />
	05ded2b898c90059d4c9b5c6ccc8603b6848ae767360c42bd9b36ff87fb4b9df</font></p>
<p>执行ps命令查看镜像id：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES</font></p>
<p>怎么回事？Container没有启动起来？</p>
<p><font face="Courier New">$ sudo docker ps -a<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	05ded2b898c9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v1&nbsp;&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 22 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exited (0) 21 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hungry_engelbart</font></p>
<p>通过ps -a查看，container启动是成功了，但是成功退出了。于是尝试查看一下log：</p>
<p><font face="Courier New">sudo docker logs 05ded2b898c9<br />
	/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;</font></p>
<p>似乎是supervisord转为daemon程序，容器主进程退出了，容器随之终止了。</p>
<p>看来容器内的supervisord不能以daemon形式运行，应该以前台形式run。修改一下supervisord.conf中得配置：</p>
<p>将<br />
	<font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p>改为</p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=true</font></p>
<p>重新制作镜像:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:supervisor-v2&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : RUN yum install python-setuptools -y<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; e09c66a1ea8c<br />
	Step 3 : RUN easy_install supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9c8797e8c27e<br />
	Step 4 : RUN mkdir -p /var/log/supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9bfc67f8517d<br />
	Step 5 : COPY ./supervisord.conf /etc/supervisord.conf<br />
	&nbsp;&#8212;&gt; 8c514f998363<br />
	Removing intermediate container 4a185856e6ed<br />
	Step 6 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 0317bd4914d3<br />
	Removing intermediate container ac5738380854<br />
	Step 7 : COPY ./dockerapp1-brother /bin/<br />
	&nbsp;&#8212;&gt; d89711888bdf<br />
	Removing intermediate container eadc9444e716<br />
	Step 8 : CMD ["/usr/bin/supervisord"]<br />
	&nbsp;&#8212;&gt; Running in aaa042ac3914<br />
	&nbsp;&#8212;&gt; 9655256bbfed<br />
	Removing intermediate container aaa042ac3914<br />
	Successfully built 9655256bbfed</font></p>
<p>有了前面的铺垫，这次build image瞬间完成。启动容器，查看容器启动状态，查看容器内supervisord的运行日志如下：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:supervisor-v2&quot;<br />
	61916f1c82338b28ced101b6bde119e4afb7c7fa349b4332ed51a43a4586b1b9</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	61916f1c8233&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v2&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 16 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prickly_einstein</font></p>
<p><font face="Courier New">$ sudo docker logs 8eb3e9892e66</font></p>
<p><font face="Courier New">/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;<br />
	2014-10-09 14:36:02,334 CRIT Supervisor running as root (no user in config file)<br />
	2014-10-09 14:36:02,349 INFO RPC interface &#39;supervisor&#39; initialized<br />
	2014-10-09 14:36:02,349 CRIT Server &#39;unix_http_server&#39; running without any HTTP authentication checking<br />
	2014-10-09 14:36:02,349 INFO supervisord started with pid 1<br />
	2014-10-09 14:36:03,354 INFO spawned: &#39;dockerapp1&#39; with pid 14<br />
	2014-10-09 14:36:03,363 INFO spawned: &#39;dockerapp1-brother&#39; with pid 15<br />
	2014-10-09 14:36:04,368 INFO success: dockerapp1 entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)<br />
	2014-10-09 14:36:04,369 INFO success: dockerapp1-brother entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</font></p>
<p>可以看到supervisord已经将dockerapp1和dockerapp1-brother启动起来了。</p>
<p>现在我们尝试停止容器，我们预期是supervisord在退出前通知dockerapp1和dockerapp1-brother先退出，我们可以通过 查看容器内的/tmp/dockerapp1.log和/tmp/dockerapp1-brother.log来确认supervisord是否做了通 知。</p>
<p><font face="Courier New">$ sudo docker stop 61916f1c8233<br />
	61916f1c8233</font></p>
<p><font face="Courier New">$ sudo docker logs 61916f1c8233<br />
	&#8230; &#8230;<br />
	2014-10-09 14:37:52,253 WARN received SIGTERM indicating exit request<br />
	2014-10-09 14:37:52,254 INFO waiting for dockerapp1, dockerapp1-brother to die<br />
	2014-10-09 14:37:52,254 INFO stopped: dockerapp1-brother (exit status 0)<br />
	2014-10-09 14:37:52,256 INFO stopped: dockerapp1 (exit status 0)</font></p>
<p>通过容器的log，我们看出supervisord是等待两个程序退出后才退出的，不过我们还是要看看两个程序的输出日志以最终确认。重新启动容器，通过nsenter进入到容器中。</p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1-brother.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>两个程序的标准输出日志证实了我们的预期。</p>
<p>BTW，在物理机上测试supervisord以daemon形式运行，当kill掉supervisord时，supervisord是不会通知其监控 和管理的程序退出的。只有在以non-daemon形式运行时，supervisord才会在退出前先通知下面的程序退出。如果在一段时间内下面程序没有 退出，supervisord在退出前会kill -9强制杀死这些程序的进程。</p>
<p>最后要说的时，在验证一些想法时，没有必要build image，我们可以直接将本地文件copy到容器中，下面是一个例子，我们将dockerapp1和dockerapp1-brother拷贝到镜像中：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	4d8982bfccc7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; centos:centos6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 26 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 26 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sharp_thompson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker inspect -f &#39;{{.Id}}&#39; 4d8982bfccc7<br />
	4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4<br />
	$ sudo cp dockerapp1&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1<br />
	$ sudo cp dockerapp1-brother&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1-brother</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
