在 Windows 上可以用 Docker 吗?

Docker,或者准确一点说,容器技术,在近几年里几乎成为了应用分发和集群部署的默认技术了。背景部分,如果感兴趣,请参考闲谈集群管理模式一文。Docker 生态的成熟还有赖于其周边工具和实践模式的兴起。比如,曾经雨后春笋般出现的编排技术,以及基于容器技术的 DevOps 实践大规模地开展。

那么这么好的技术,在 Windows 上能用吗?在各种场合,都有人与我讨论这个的话题。每次听到这样的疑问,我也是很无奈的。毕竟,只要稍微搜索一下,就不难回答:是可以的。不过,深入想一下,人们有这样的疑问也是有道理的:毕竟 Docker 是起源于 Linux 上的技术。

Docker 是基于 Linux 内置的 Namespace 和 CGroup 等系统内隔离机制而抽象出来的一种轻虚拟化技术。与虚拟机相比,它以一种轻量级的方式实现了运行空间的隔离。如果物理机是一幢住宅楼,虚拟机就是大楼中的一个个套间,而容器技术就是套间里的一个个隔断。不难理解,Docker 作为一种隔断,它并不能基于一种内核(Linux)提供另一种内核(Windows)的虚拟化运行环境。所以,基于 Linux 的 Docker 是不支持运行 Windows 应用的。

早在 Docker 之前,Linux 就已经提供了今天的 Docker 所使用的那些基础技术。当年 Docker 仿佛一夜之间突然火爆全球的背后,技术上的积累并不是瞬间完成的。这一切在 Windows 上显得有些滞后。在 Docker 已经众所周知的时候,Windows 系统却根本没有类似的机制,更别提 Windows 独有的工具链和实践方法了。所以,我们看到,早期 Windows 与 Docker 的交集只是为其提供应用开发环境。

boot2docker 与 Docker for Windows

可以在 Windows 开发面向 Docker 部署的应用程序——Windows 的桌面体验比 Linux 好太多,所以很早就出现了在异构操作系统上以虚拟机的形式运行 Docker 的项目出现,也就是 boot2docker。它既支持 Windows,也支持 macOS。

后来,Docker 公司开始推出自己的 Docker for Windows 工具包,它旨在为开发人员在 Windows 上开发面向 Docker 的应用程序提供完整的工具链,其中包括运行环境、客户端,Docker Swarm 编排工具和其他工具。Docker for Windows 中负责运行环境配置的工具是 Docker Machine。与 boot2docker 类似,Docker Machine 也会在 Windows 上创建一个 Linux 虚拟机,用于运行 Docker 引擎。也就是说,这个环境也只支持 Linux 的应用程序格式的,并不支持 Windows 应用程序的运行。

(在 Windows 上运行的 Docker for Windows(图片来自 Docker 文档))

Windows 容器技术

正当 Linux 世界的容器技术借着 Docker 的东风刮遍世界的时候,Windows 系统也发现了容器粒度的重要性。

微软与 Docker 在 2014 年宣布了合作,以期将容器技术带到 Windows Server 操作系统,并为传统的 Windows 应用程序的容器化改造提供更直接的支持。不久之后,微软在 Ignite 2015 上宣布将推出为容器优化的 Windows Nano Server;第一次 Windows 容器真正与与开发者见面是在 Windows 10 的年度更新(2016.8)上,它正式提供 Windows 容器的开发环境。在 2017 年 10 月发布的 Windows Server 1709 版本包含了 Windows 容器,意味着这项技术可以用于生产环境了。Windows 容器是真正能够运行 Windows 应用程序的容器技术,包括依赖 IIS、注册表等大量 Windows 特性的应用程序都可以在 Windows 容器中运行。

虽然 Windows 对容器的支持有些姗姗来迟,但社区对 Windows 容器的关注和运用却是异常活跃。这主要得益于容器技术本身生态的成熟,一来人们对这项技术已经有了充分的认知,同时周边工具和实践都已经日趋完善。另一方面,在与 Docker 公司一同打造这项技术的过程中,也注意了与已有技术的兼容性。人们发现,在电脑上启用 Windows Container 功能之后,接下来的操作步骤仍然是基于 Docker 客户端完成的,命令行参数与 Linux 上的 Docker 也没有区别。

几乎与 Windows 容器技术本身日趋成熟过程的同时,周边工具对 Windows 容器的支持也在同步完善。Docker for Windows 在新的版本中添加了一个贴心的菜单,可以一键切换 Linux 容器和 Windows 容器;Kubernetes 从 1.5 版本开始增加对 Windows 容器的支持;云环境方面,包括 Azure 和 AWS 在内的众多云环境都第一时间提供了 Windows 容器的支持……

Windows 容器架构

Windows 是如何既提供自有容器技术,又提供与 Docker 兼容的操作接口的呢? 下面的上图是 Linux 容器的架构,下图则是 Windows 容器的。可以发现两者结构很类似。与 Linux 类似,Windows 也新新抽象出来了 CGroup 和 Namespace 的概念,并提供出一个新的抽象层次 Compute Service,即宿主机运算服务(Host Compute Service,hcs)。相较于底层可能经常重构的实现细节,hcs 旨在为外部(比如 Docker 引擎)提供较稳定的操作接口。hcs 的操作接口目前有 Go 语言版本,以及 C# 语言版本,前者目前在 Docker 客户端中用来操作 Windows 容器。

(图片来自 Black Belt 在 DockerCon 的演讲:Docker 与 Windows 容器揭秘

容器镜像方面,微软自己提供了 Server Core 和 Nano Server 两种服务器版本。Server Core 可以理解为 Windows Server 去掉了 GUI 的部分,因此功能更完整(比如包括文件服务器、DNS 服务器等功能),同时镜像大小也更大(2GB~5GB);而 Nono Server 则是专为容器优化的迷你型系统,只包含有核心的 Windows 服务器功能,镜像大小为(130MB~400MB)。基于基础镜像来构建自己镜像的方法与 Linux 镜像是一样的,所以 DockerFile 文件的格式和语法并没有不同。

授权方面,只要用户已经取得宿主机的授权,微软并不会单独向用户收取容器镜像的授权费

小结

容器技术本身以及围绕它的一系列工具和实践让应用程序的打包和发布变得标准化,很大程度上可以消除应用程序对特定环境的依赖,进而为高效的集群化部署和运维提供有力保障。作为容器技术的代表,Docker 可以以两种形式运行在 Windows 上:以 Hyper-V 虚拟机的形式运行 Linux 格式的容器,或者运行原生的 Windows 容器。其中前者运行 Linux 格式的应用程序,后者能运行 Windows 应用程序。如果稍微用一点技巧,还可以让这两者同时运行在 Windows 电脑上

Windows 10 和 Windows Server 都提供了对 Windows 容器的支持,各种容器化工具对 Windows 容器的支持也在日趋完善当中。基于 Windows 开发新的应用时一方面可以优先考虑跨平台容器化部署的能力,另一方面也可以与存量应用程序一样考虑借助 Windows 容器技术实现容器化、云原生的特性。


更多精彩洞见,请关注微信公众号:思特沃克

Share

你以为是微服务或Docker?其实是组织架构!

It’s Microservices …It’s docker …It’s organization structure

微服务和容器毫无争议的成为了这个时代的主旋律,大家争先恐后地让自己的团队和企业去尝试这样的旋律,但往往发现曲高和寡,难以在整个组织内形成共鸣。在本文中,我们尝试揭开微服务和容器技术背后映射出的组织结构的变迁,以及组织结构对落地微服务和容器化架构所带来的反向制约,最后用INVEST原则来看看支撑这样松耦合架构的组织结构应有的特质。希望能够帮助迷茫中的企业和组织重新思考自己的微服务之路。

Microservices(微服务)和docker(容器)成了近一年来软件行业的新宠,每次参加相关活动总会感到康威老先生站在背后邪邪地笑着:“我早告诉你们了”。

尽管Martin Fowler在“定义”微服务时十分小心的警告了大家所要付出的代价,但好像微服务的优点太过于吸引人,以至于大部分软件开发组织和企业都把微服务这种架构方式作为了未来的必选,大家都觉得我就是那个高个子(I’m that tall!)。

1-be-tall

(Martin Fowler对伴随微服务架构的高工程实践能力的比喻。)

随着容器技术到达生产应用的临界点,这种化学效应好像一触即发,我们仿佛看到了未来一个不一样的软件微服务大集市在逐步展开!

在这个集市里会有淘宝这样的平台,为中小服务卖家搭起一个在线商城,买家可以根据自己的需要搜索及购买琳琅满目、各式各样的服务,在线或是二进制、代码质量及自动化覆盖率等指标成为同类服务评级的重要标准。

杀手级的服务如区块链或者量子加密可能成为皇冠销量服务。最后掀起一大波程序猿开微服务店的热潮~ 很期待那是怎样的卖家秀和买家秀啊!

这种几乎接近于科幻的描述可能只适合作为微信上的谈资,但微服务和容器技术的流行却并非偶然。康威老先生用自己的定律揭示了一个更深刻的道理:

这不是一次技术架构或者基础设施的革命,而是为了保持组织灵活性的必由之路。

换句话说,软件开发组织或企业开始意识到一切的管理和技术实践都必须为保持尽可能高的组织灵活性服务。一定会有人问为啥要保持“尽可能高”的灵活性呢?铁打的营盘流水的兵、稳定的规章制度不也缔造出了历史上那么多成功的组织和企业吗?

论尽可能高的组织灵活性

所以我在前面加了定语“软件开发”,当然现在我们有一个更广泛的提法:科技企业。通常我们认为产品或服务的技术含量比较高,具有核心竞争力,能不断推出适销对路的新产品,不断开拓市场的企业为科技企业。

在我们所处的软件时代,大量的科技企业都跟软件沾上了边。但历史上我们可以回想大生产时代炼钢也曾是高科技,信息时代发邮件也是高科技。前两波的“科技企业”给我们的印象可不是灵活的:几大钢铁巨擘让人联想到的应该是当年国家呼唤生产力全民建设的宏伟场景;信息时代佼佼者如Microsoft和IBM让人联想到的应该是动辄千人的大型工业软件开发队伍,一个部署都得来一个专家队伍。那么为啥现在咱们的科技企业必须灵活,而且必须尽可能高呢?

2-steel

这里我们再次使用康威老先生的定律来做推论,康威定律说

“一个产品或系统的设计(架构)受到其生产组织自身交流沟通结构的制约”,

换句话说如果你有一个前端展现团队、一个后端服务团队和一个数据库团队,那么我们可以肯定,搞出来的系统会分前后台和数据库的设计。这本身是一个悲观的定律,所以前面的团队发现新需求来了必须沟通三次,前后台团队关心新需求对自身架构的影响,数据库设计关心对现有数据结构的冲击,最后总是会在各方的争执中得到一个别扭的解决方案。

很多团队早已经习惯了这样的痛苦,数字化时代的变化频率将这样的痛苦逐渐推向了顶峰。举例感受一下:达到100亿产值,首钢用了71年,联想用了13年,这个时代的小米用了仅仅3年!而今年的小米已经不是站在浪潮之巅的科技新贵了。

所以康威老先生说:

如果要保持产品的持续竞争力,就要保持组织的灵活性。

曾经有人跟我争辩说:“我们做的是数字化时代的后台系统,不直接面对市场,需求很稳定,搞那么灵活成本反而高。”于是我指着他们有着千万行代码的系统说:“你们至少有30%代码是冗余的,这就是组织缺乏灵活性的另外一个恶果。”

3-code

这里我们先收缩范围到软件开发,非常有意思的是在咱们这个行业里,针对同一份需求,没有两个开发人员实现出来的代码是一样的(也许Hello World例外)。甚至,当我发现两个程序员使用的变量命名一样的时候我会怀疑他们抄袭了。这说明软件开发从需求提出到写代码实际都是在做设计,不同的人设计出来的东西就会不同,像大家的签名一样。

设计甚至延续到了后面的软件测试和部署,同样的应用在不同的网络拓扑结构下表现可能是完全不一样的。那些追求稳定的组织希望尽早结束设计这个高度不确定性的活动,从而能够通过标准化来提高效率。

即使在敏捷开发主流化的今天,很多团队仍然是架构师“画图”,码农堆砌代码。所以这样的组织很快发现自己深陷二进制的泥潭,进退维谷。我经常跟这样的团队讲:你们缺乏“代码的响应力”。而响应力对组织的要求就是灵活,能够从前到后驾驭设计活动带来的不确定性。

小结一下:数字化时代的市场变化是迅猛的,康威老先生已经告诉我们,处在这样时代背景下的科技企业保持组织灵活性是十分重要的。而软件(广义讲新科技领域)本身由于强设计而带来的不确定性又加重了对组织灵活性的诉求。

于是在这个时代我们看到了如Google、海尔这样已然成功的企业开始大刀阔斧地改革自己的组织结构,这种对灵活性的极致追求成就了这些组织持续保持市场领先水平的核心竞争力。毫无疑问,微服务架构的优点也正反向映射出了组织结构的灵活性,而容器技术的运用降低了这种松散集市结构的运营成本,就如同淘宝平台的出现给千千万卖家和买家搭建了一个基础的交易平台。

弹性伸缩的容器化计算资源加上松耦合的微服务架构必然会吸引追求组织灵活性的企业去打败康威定律,保持组织活力。

 

组织结构的INVEST原则

前面咱们辩证了数字化时代科技企业保持组织灵活性的必要,那么灵活的组织结构应该满足什么原则呢?下面我们就借用敏捷开发中赫赫有名的需求管理原则INVEST来剖析一下怎样的组织结构才能够真正落地微服务架构和容器技术带来的灵活性优势,或者从另外一个角度看支撑微服务架构的运用。

5-organization-structure

(两种组织结构对比示意)

独立的:Independent

按照微服务架构的团队应该对外提供一种或多种服务,服务和服务之间应该是松耦合的,所以背后的团队也应该是相对独立的。遵循康威定律,如果一个大型组织没有能够划分出服务导向的相对独立团队,那么最后对外提供的产品或系统的内部结构也不可能是简单的服务组装,而会是我们常说的“意大利面”,内部结构纠缠不清以至于最后响应市场新需求越来越慢。 便于沟通的:Negotiable

“小“服务团队的结构必然造成整个组织的集市化、社区化。如果没有建立良好的团队间的沟通机制,很难想象这样的组织里会有任何的产出。Amazon被认为是一个微服务架构运用的成功典范,其2pizza团队的原则成为了业内的标杆。

但这样服务导向小团队集合的底层是长期磨合形成的良好团队间沟通机制,甚至当我们问到Amazon各个团队如何发现其它服务或要求其它团队协助完成需求时,团队都说不出具体的流程机制,一切都变得很自然,全然像我们走进自己熟悉的超市一样,能够很自然的找到日常所需。

有价值的:Valuable

毫无疑问,每个团队必然是面向价值交付的。敏捷开发方法的提出其实很早就指出了传统方式下按照功能部门划分的瀑布交付模式的原罪,即每个功能部门都不对最终的价值交付负责(Output over Outcome,输出大于结果)。这样的组织结构必然造成对市场变化响应的滞后。

值得一提的是面向价值交付的团队往往也是跨职能的,按照微服务的架构,团队需要负责服务从需求到部署运营的全生命周期(Outcome over Output,结果大于输出)。这也是为什么在基础的工程交付平台及实践上团队必须是一个“高个子”。

可估计的:Estimable

这样的服务团队交付周期应该是很短且可以准确估计的,上线应该是家常便饭,而不是过去短则数月、长则一年的大爆炸模式。持续交付在这样的组织里应该是标准实践,让软件系统时刻处于可发布状态是团队的共同责任。从Amazon和Netfliex这样的现代科技企业身上我们已经看到了这样组织结构下逐步形成的工程能力优势,并最终转换成了业务服务上的巨大成功。

短小:Small

前面提到了Amazon的2pizza团队,人数10人以内,经典的敏捷管理框架Scrum也建议5~9人的团队,可见小团队成为组织灵活性的一个必要条件。中国俗语有“船小好调头”朴实地揭示了小的灵活性,但为什么不再小一点呢?比如两个人结对一个团队。

显然大家很容易发现软件开发本身的复杂性决定了要端到端交付价值两个人的团队是搞不定的。从整个组织的健壮性来讲,过小的团队也会增加企业形成单点依赖的风险。虽然没有正式确认,但我们交流中发现Amazon这样的微服务组织里其实也是存在服务冗余的,这样的重复保证了组织在切割成小团队后风险得到适当的规避。

可测试的:Testable

在面对市场情况高度不确定性时,我们应该直面试错这件事情。传统职能型的大组织结构往往是不能容错的,错误的代价就是整个企业走偏了方向,或者一个部门在企业里失去了话语权。

在灵活性高的组织里我们却应该是能够很容易进行这样的“测试”,企业更能够利用这样的结构进行动态的投资组合管理,像Google著名的7:2:1投资比例,最后的一成就是利用组织的灵活性进行创新的测试。测试的结果往往是失败的,但正是这样的不断测试创造了Google历史上很多著名的“黑天鹅”。

打破康威定律

最近很多以精益(LEAN)为关键字的理论框架在咱们这个领域冒了出来,也包括我前期撰文提到的精益企业(Lean Enterprise),于是有朋友揶揄说又开始炒概念了。我却很严肃地澄清正是不希望炒概念,所以才回到了上个世纪就论证和发展起来的理念:精益。

6-lean

来源于丰田制造的精益总结出了很多的原则和实践,但有意无意中丰田完成了自身组织持续灵活性打造这项超越同期其它企业的伟业。其结果就是在响应需求多样化时展现出的更强适应能力。

如果用我们前面的INVEST原则来看待精益组织,你会很快找到对应的原则和实践,即使在传统的工业流水线上,丰田也在形成一个个的小团队(cell,单元生产),也在通过员工的多技能培养来完成小团队内部的“跨职能”。其持续改进(Kaizen)的核心思想有力保证了团队面向价值的工作方式和良好的跨团队沟通文化。

某种意义上讲精益在康威定律定义之前就打破了康威定律!

微服务和容器技术无疑是这个时代工程架构方面支撑组织灵活性的重要一步,然而我们不能忘记一个组织是五脏俱全的,如精益企业提到的,组织的财务审计、人力资源、采购合规等功能如何有效的“微服务”化和如何能够合力构建一个弹性的“容器”支撑平台仍然需要诸君努力!

Share

利用Docker开启持续交付之路

持续交付即Continuous Delivery,简称CD,随着DevOps的流行正越来越被传统企业所重视。持续交付讲求以短周期、小细粒度,自动化的方式频繁的交付软件,在这个过程中要求开发、测试、用户体验等角色紧密合作,快速收集反馈,从而不断改善软件质量并减少浪费。然而,在我所接触的传统企业中,对于持续交付实践的实施都还非常初级,坦白说,大部分还停留的手工生成发布包,手工替换文件进行部署的阶段,这样做无疑缺乏管理且容易出错。如果究其原因,我想主要是因为构建一个可实际运行且适合企业自身环境的持续发布流程并不简单。然而,Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做自动化部署,这些特性非常有助于降低构建持续交付流程的复杂度。本文将通过一个实际案例分享我们在一个真实项目中就如何使用Docker构建持续发布流程的经验总结,这些实践也许不是最先进的,但确是非常实际和符合当时环境的。

项目背景

我们的客户来自物流行业,由于近几年业务的飞速发展,其老的门户网站对于日常访问和订单查询还勉强可以支撑,但每当遇到像双十一这样访问量成倍增长的情况就很难招架了。因此,客户希望我们帮助他们开发一个全新的门户网站。

新网站采用了动静分离的策略,使用Java语言,基于REST架构,并结合CMS系统。简单来说,可以把它看成是时下非常典型的一个基于Java的Web应用,它具体包含如下几个部分:

  • 基于Jersey的动态服务(处理客户端的动态请求)
  • 二次开发的OpenCMS系统,用于静态导出站点
  • 基于js的前端应用并可以打包成为一个OpenCMS支持的站点
  • 后台任务处理服务(用于处理实时性要求不高的任务,如:邮件发送等)

以下是系统的逻辑软件架构图:

01

面临的挑战以及为什么选择Docker

在设计持续交付流程的过程中,客户有一个非常合理的需求:是否可以在测试环境中尽量模拟真实软件架构(例如:模拟静态服务器的水平扩展),以便尽早发现潜在问题?基于这个需求,可以尝试将多台机器划分不同的职责并将相应服务按照职责进行部署。然而,我们遇到的第一个挑战是:硬件资源严重不足尽管客户非常积极的配合,但无奈于企业内部层层的审批制度。经过两个星期的努力,我们很艰难的申请到了两台四核CPU加8G内存的物理机(如果申请虚拟机可能还要等一段时间),同时还获得了一个Oracle数据库实例。因此,最终我们的任务就变为把所有服务外加持续集成服务器(Jenkins)全部部署在这两台机器上,并且,还要模拟出这些服务真的像是分别运行在不同职责的机器上并进行交互。如果采用传统的部署方式,要在两台机器上完成这么多服务的部署是非常困难的,需要小心的调整和修改各个服务以及中间件的配置,而且还面临着一旦出错就有可能耗费大量时间排错甚至需要重装系统的风险。第二个挑战是:企业内部对UAT(与产品环境配置一致,只是数据不同)和产品环境管控严格,我们无法访问,也就无法自动化。这就意味着,整个持续发布流程不仅要支持自动化部署,同时也要允许下载独立发布包进行手工部署。

最终,我们选择了Docker解决上述两个挑战,主要原因如下:

  • Docker是容器,容器和容器之间相互隔离互不影响,利用这个特性就可以非常容易在一台机器上模拟出多台机器的效果
  • Docker对操作系统的侵入性很低,因其使用LXC虚拟化技术(Linux内核从2.6.24开始支持),所以在大部分Linux发行版下不需要安装额外的软件就可运行。那么,安装一台机器也就变为安装Linux操作系统并安装Docker,接着它就可以服役了
  • Docker容器可重复运,且Docker本身提供了多种途径分享容器,例如:通过export/import或者save/load命令以文件的形式分享,也可以通过将容器提交至私有Registry进行分享,另外,别忘了还有Docker Hub

下图是我们利用Docker设计的持续发布流程:

02

图中,我们专门设计了一个环节用于生成唯一发布包,它打包所有War/Jar、数据库迁移脚本以及配置信息。因此,无论是手工部署还是利用Docker容器自动化部署,我们都使用相同的发布包,这样做也满足持续交付的单一制品原则(Single Source Of Truth,Single Artifact)。

Docker与持续集成

持续集成(以下简称CI)可以说是当前软件开发的标准配置,重复使用率极高。而将CI与Docker结合后,会为CI的灵活性带来显著的提升。由于我们项目中使用Jenkins,下面会以Jenkins与Dcoker结合为例进行说明。

1.创建Jenkins容器

相比于直接把Jenkins安装到主机上,我们选择把它做为Docker容器单独使用,这样就省去了每次安装Jenkins本身及其依赖的过程,真正做到了拿来就可以使用。

Jenkins容器使创建一个全新的CI变的非常简单,只需一行命令就可完成:

docker run -d -p 9090:8080 ——name jenkins jenkins:1.576

该命令启动Jenkins容器并将容器内部8080端口重定向到主机9090端口,此时访问:主机IP:9090,就可以得到一个正在运行的Jenkins服务了。

为了降低升级和维护的成本,可将构建Jenkins容器的所有操作写入Dockerfile并用版本工具管理,如若需要升级Jenkins,只要重新build一次Dockerfile:

FROM ubuntu
ADD sources.list /etc/apt/sources.list
RUN apt-get update && apt-get install -y -q wget
RUN wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add -
ADD jenkins.list /etc/apt/sources.list.d/
RUN apt-get update
RUN apt-get install -y -q jenkins
ENV JENKINS_HOME /var/lib/jenkins/
EXPOSE 8080
CMD ["java", "-jar", "/usr/share/jenkins/jenkins.war"]

每次build时标注一个新的tag:

docker build -t jenkins:1.578 —rm .

另外,建议使用Docker volume功能将外部目录挂载到JENKINS_HOME目录(Jenkins会将安装的插件等文件存放在这个目录),这样保证了升级Jenkins容器后已安装的插件都还存在。例如:将主机/usr/local/jenkins/home目录挂载到容器内部/var/lib/jenkins:

docker run -d -p 9090:8080 -v /usr/local/jenkins/home:/var/lib/jenkins ——name jenkins jenkins:1.578

2. 使用Docker容器作为Jenkins容器的Slave

在使用Jenkins容器时,我们有一个原则:不要在容器内部存放任何和项目相关的数据。因为运行中的容器不一定是稳定的,而Docker本身也可能有Bug,如果把项目数据存放在容器中,一旦出了问题,就有丢掉所有数据的风险。因此,我们建议Jenkins容器仅负责提供Jenkins服务而不负责构建,而是把构建工作代理给其他Docker容器做。

例如,为了构建Java项目,需要创建一个包含JDK及其构建工具的容器。依然使用Dockerfile构建该容器,以下是示例代码(可根据项目实际需要安装其他工具,比如:Gradle等):

FROM ubuntu
RUN apt-get update && apt-get install -y -q openssh-server openjdk-7-jdk
RUN mkdir -p /var/run/sshd
RUN echo 'root:change' |chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

在这里安装openssh-server的原因是Jenkins需要使用ssh的方式访问和操作Slave,因此,ssh应作为每一个Slave必须安装的服务。运行该容器:

docker run -d -P —name java java:1.7

其中,-P是让Docker为容器内部的22端口自动分配重定向到主机的端口,这时如果执行命令:

docker ps
804b1d9e4202       java:1.7           /usr/sbin/sshd -D     6 minutes ago       Up 6 minutes       0.0.0.0:49153->22/tcp   java

端口22被重定向到了49153端口。这样,Jenkins就可以通过ssh直接操作该容器了(在Jenkins的Manage Nodes中配置该Slave)。

有了包含构建Java项目的Slave容器后,我们依然要遵循容器中不能存放项目相关数据的原则。此时,又需要借助volume:

docker run -d -v /usr/local/jenkins/workspace:/usr/local/jenkins -P —name java java:1.7

这样,我们在Jenkins Slave中配置的Job、Workspace以及下载的源码都会被放置到主机目录/usr/local/jenkins/workspace下,最终达成了不在容器中放置任何项目数据的目标。

通过上面的实践,我们成功的将一个Docker容器配置成了Jenkins的Slave。相比直接将Jenkins安装到主机上的方式,Jenkins容器的解决方案带来了明显的好处:

  • 重用更加简单,只需一行命令就可获得CI的服务;
  • 升级和维护也变的容易,只需要重新构建Jenkins容器即可;
  • 灵活配置Slave的能力,并可根据企业内部需要预先定制具有不同能力的Slave,比如:可以创建出具有构建Ruby On Rails能力的Slave,可以创建出具有构建NodeJS能力的Slave。当Jenkisn需要具备某种能力的Slave时,只需要docker run将该容器启动,并配置为Slave,Jenkins就立刻拥有了构建该应用的能力。

如果一个组织内部项目繁多且技术栈复杂,那么采用Jenkins结合Docker的方案会简化很多配置工作,同时也带来了相率的提升。

Docker与自动化部署

说到自动化部署,通常不仅仅代表以自动化的方式把某个应用放置在它应该在的位置,这只是基本功能,除此之外它还有更为重要的意义:

  • 以快速且低成本的部署方式验证应用是否在目标环境中可运行(通常有TEST/UAT/PROD等环境);
  • 以不同的自动化部署策略满足业务需求(例如:蓝绿部署);
  • 降低了运维的成本并促使开发和运维人员以端到端的方式思考软件开发(DevOps)。

在我们的案例中,由于上述挑战二的存在,导致无法将UAT乃至产品环境的部署全部自动化。回想客户希望验证软件架构的需求,我们的策略是:尽量使测试环境靠近产品环境。

  1. 标准化Docker镜像

很多企业内部都存在一套叫做标准化的规范,在这套规范中定义了开发中所使用的语言、工具的版本信息等等,这样做可以统一开发环境并降低运维团队负担。在我们的项目上,依据客户提供的标准化规范,我们创建了一系列容器并把它们按照不同的职能进行了分组,如下图:

03

图中,我们把Docker镜像分为三层:基础镜像层、服务镜像层以及应用镜像层,下层镜像的构建依赖上层镜像,越靠上层的镜像越稳定越不容易变。

基础镜像层

  • 负责配置最基本的、所有镜像都需要的软件及服务,例如上文提到的openssh-server

服务镜像层

  • 负责构建符合企业标准化规范的镜像,这一层很像SaaS

应用镜像层

  • 和应用程序直接相关,CI的产出物

分层后, 由于上层镜像已经提供了应用所需要的全部软件和服务,因此可以显著加快应用层镜像构建的速度。曾经有人担心如果在CI中构建镜像会不会太慢?经过这样的分层就可以解决这个问题。

在Dockerfile中使用FROM命令可以帮助构建分层镜像。例如:依据标准化规范,客户的产品环境运行RHEL6.3,因此在测试环境中,我们选择了centos6.3来作为所有镜像的基础操作系统。这里给出从构建base镜像到Java镜像的方法。首先是定义base镜像的Dockerfile:

FROM centos
# 可以在这里定义使用企业内部自己的源
RUN yum install -y -q unzip openssh-server
RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN echo 'root:changeme' | chpasswd
RUN sed -i "s/#UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config \
&& sed -i "s/UsePAM.*/UsePAM no/g" /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

接着,构建服务层基础镜像Java,依据客户的标准化规范,Java的版本为:jdk-6u38-linux-x64:

FROM base
ADD jdk-6u38-linux-x64-rpm.bin /var/local/
RUN chmod +x /var/local/jdk-6u38-linux-x64-rpm.bin
RUN yes | /var/local/jdk-6u38-linux-x64-rpm.bin &>/dev/null
ENV JAVA_HOME /usr/java/jdk1.6.0_38
RUN rm -rf var/local/*.bin
CMD ["/usr/sbin/sshd", "-D"]

如果再需要构建JBoss镜像,就只需要将JBoss安装到Java镜像即可:

FROM java
ADD jboss-4.3-201307.zip /app/
RUN unzip /app/jboss-4.3-201307.zip -d /app/ &>/dev/null && rm -rf /app/jboss-4.3-201307.zip
ENV JBOSS_HOME /app/jboss/jboss-as
EXPOSE 8080
CMD ["/app/jboss/jboss-as/bin/run.sh", "-b", "0.0.0.0"]

这样,所有使用JBoss的应用程序都保证了使用与标准化规范定义一致的Java版本以及JBoss版本,从而使测试环境靠近了产品环境。

  1. 更好的组织自动化发布脚本

为了更好的组织自动化发布脚本,版本化控制是必须的。我们在项目中单独创建了一个目录:deploy,在这个目录下存放所有与发布相关的文件,包括:用于自动化发布的脚本(shell),用于构建镜像的Dockerfile,与环境相关的配置文件等等,其目录结构是:

├── README.md
├── artifacts   # war/jar,数据库迁移脚本等
├── bin         # shell脚本,用于自动化构建镜像和部署
├── images       # 所有镜像的Dockerfile
├── regions     # 环境相关的配置信息,我们只包含本地环境及测试环境
└── roles       # 角色化部署脚本,会本bin中脚本调用

这样,当需要向某一台机器上安装java和jboss镜像时,只需要这样一条命令:

bin/install.sh images -p 10.1.2.15 java jboss

而在部署的过程中,我们采用了角色化部署的方式,在roles目录下,它是这样的:

├── nginx
│   └── deploy.sh
├── opencms
│   └── deploy.sh
├── service-backend
│   └── deploy.sh
├── service-web
│   └── deploy.sh
└── utils.sh

这里我们定义了四种角色:nginx,opencms,service-backend以及service-web。每个角色下都有自己的发布脚本。例如:当需要发布service-web时,可以执行命令:

bin/deploy.sh -e test -p 10.1.2.15 service-web

该脚本会加载由-e指定的test环境的配置信息,并将service-web部署至IP地址为10.1.2.15的机器上,而最终,bin/deploy.sh会调用每个角色下的deploy.sh脚本。

角色化后,使部署变的更为清晰明了,而每个角色单独的deploy脚本更有利于划分责任避免和其他角色的干扰。

  1. 构建本地虚拟化环境

通常在聊到自动化部署脚本时,大家都乐于说这些脚本如何简化工作增加效率,但是,其编写过程通常都是痛苦和耗时,需要把脚本放在相应的环境中反复执行来验证是否工作正常。这就是我为什么建议最好首先构建一个本地虚拟化环境,有了它,就可以在自己的机器上反复测试而不受网络和环境的影响。

Vagrant(http://www.vagrantup.com/)是很好的本地虚拟化工具,和Docker结合可以很容易的在本地搭建起与测试环境几乎相同的环境。以我们的项目为例,可以使用Vagrant模拟两台机器,以下是Vagrantfile示例:

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "server1", primary: true do |server1|
server1.vm.box = "raring-docker"
server1.vm.network :private_network, ip: "10.1.2.15"
end
config.vm.define "server2" do |server2|
server2.vm.box = "raring-docker"
server2.vm.network :private_network, ip: "10.1.2.16"
end
end

由于部署脚本通常采用SSH当方式连接,所以,完全可以把这两台虚拟机看做是网络中两台机器,调用部署脚本验证是否正确。限于篇幅,这里就不多说了。

4 构建企业内部的Docker Registry

上文提到了诸多分层镜像,如何管理这些镜像?如何更好的分享?答案就是使用Docker Registry。Docker Registry是一个镜像仓库,它允许你向Registry中提交(push)镜像同时又可以从中下载(pull)。

构建本地的Registry非常简单,执行下面的命令:

docker run -p 5000:5000 registry

更多关于如何使用Registry可以参考地址:https://github.com/docker/docker-registry

当搭建好Registry后,就可以向它push你的镜像了,例如:需要将base镜像提交至Registry:

docker push your_registry_ip:5000/base:centos

而提交Java和JBoss也相似:

docker push your_registry_ip:5000/java:1.6
docker push your_registry_ip:5000/jboss:4.3

使用下面的方式下载镜像:

docker pull your_registry_ip:5000/jboss:4.3

总结

本文总结我们在实际案例中使用Docker一些实践,它给我们的印象就是非常灵活,几乎是一个多面手,给整个流程带来了极大的灵活性和扩展性,并且也展现了极好的性能,符合它天生就为部署而生的特质。

Share