当一个开发者拿到Docker文档之后,一定会按捺不住内心的激动,把所有的Getting Started跑一遍,当然中间可能会碰到问题,仔细阅读过本书第一部分的读者应该能更加游刃有余地做完这些事情。甚至尝试过第4章的高级玩法后,当第一个demo开始工作时,终于可以当仁不让地宣布自己已经是个高级玩家。

那么问题来了:接下来该做什么?

以一个开发者的视角继续往下看,往后事情的发展无外乎两种可能:第一,开发者默默地记住这些技能,然后把Docker当作自己的独门武器,以至于最后老板都开始怀疑这家伙的开发效率怎么会突然变得这么高。第二,热心的开发者开始向全组推广Docker,甚至鼓动运维也加入Docker行列。一般情况下,我们会称赞第二种开发者为“愿意当将军的士兵”。

于是,我们的“将军开发者”决定从最简单的需求开始演示自己的计划,只用了几分钟,他就搭建好了一个容器集群,如图5-4所示。这是一个来自《第一本Docker书》的例子,与第2章中我们手把手搭建过的“第一个Docker集群”有点类似。

在这个组合实例中,他成功地将一个Node.js应用运行起来,并且使用Redis集群来存储这个应用的session信息,最后还使用ELK组合(ElasticSearch+Logstash+Kibana)完成了应用和Redis的日志转发、存储和检索功能。当然最酷的一定是这些内容全是跑在Docker容器里的,他只用了几条命令外加几分钟的时间就全部搞定了。团队里的其他人只要把Dockerfile拿走,几分钟就可以搭建一套一模一样的环境出来!

enter image description here

“真不错!”大家纷纷称赞这种基于容器来构建服务栈的方式是多么地优雅。

“可是要上线的话,负载均衡总要有的吧?”一位不大讨人喜欢的开发经理提出了第一个需求。

的确,在经典互联网应用场景里,无论后端系统多么地复杂强大,最前面放置的一般都该是负载均衡设备而非Web服务器,并且负载均衡这一环节还有很多必须额外设定的配置(比如session sticky、静态动态内容分离、URL重定向等)。在此基础上,应用还往往被复制成多份,在负载均衡管理下统一提供对外服务,这项技术对于分流、灰度发布、高可用以及弹性伸缩都是必需的。好在有了Docker的帮助,这一切都不算难,“将军开发者”挠挠头的功夫就build了一个HAProxy镜像启动起来,然后又启动了一个完全相同的Node.js应用的容器(得益于镜像,这类操作非常便捷),最后将两个应用容器的IP和端口配置到了HAProxy的backend servers里面,完成了负载均衡实现的所有工作。如图5-5所示。

“好像可以工作了呢。”

“等等,负载均衡里怎么能把后端的实例配置成固定参数呢?”不招人喜欢的开发经理又提出了第二个需求,而且还有点棘手。

enter image description here

首先要面对的问题是,怎么才能保证后端应用容器失败重启或者升级扩展之后,HAProxy能及时更新自己的配置文件呢?要知道Docker容器可没有静态IP这个说法(至少在学习过本书高级网络实践之前是这样的)。不过如果求助于GitHub情况就不一样了,“将军开发者”很快找到一个专门负责配置文件远程修改的组件confd。

第二个要面对的问题是,哪个组件负责探测应用容器退出或者创建的事件发生然后通知confd修改HAProxy配置文件呢?这可让“将军开发者”着实花了一番心思。

“这好像是一个服务发现的场景呢。”

没错!所有应用容器都应该把本身IP和端口信息注册到etcd当中去(etcd是服务发现存储仓库,将在第6章详细介绍),然后依靠confd定时查询etcd中数据变化来更新HAProxy的配置文件。不需要写很多代码,只需要配置一下confd,然后build一个新的HAProxy和一个etcd容器就足够了。话音未落,“将军开发者”的新系统又上线了。如图5-6所示。

这样应该可以了吧。确切地说,这个服务栈不仅拥有了负载均衡和多实例的功能,还能够以一种“发现”的方式向负载均衡节点注册或者解注册应用实例,而且整个过程都是平滑升级的,不会出现服务中断。

“应用健康检查怎么办?”一直在旁默不作声的运维终于坐不住了。

“自发现”机制确实保证了容器自身高可用能力,但是容器中运行着的应用进程实际上并不是完全保险的。最典型的场景是Java Web Server:当应用异常的时候,Web Server是完全有可能不退出的,用户只能拿到4XX或者5XX的返回值。所以,在一个真实的应用平台需求下,“垂直监控”是非常有必要的,至少需要能检测到应用访问的返回值是2XX。

这还不算完。“将军开发者”虽然构建了一个多实例的应用集群,但生产环境下,这些实例应该将会分布在不同服务器上。这又会带来新的问题,如下所示。

enter image description here

 如何保证同一个应用的不同容器实例分布在不同或者指定的宿主节点上?

 当一个宿主节点意外退出的时候,如何保证该节点上的容器实例能够在其他宿主节点上恢复?

 如何比较当前容器实例的运行情况同期望的运行状态的差异,用以决定是否要进行上述高可用动作?

 如何构建一个覆盖“测试—开发—上线”完整流程的运行机制来充分发挥Docker镜像的一致性?

 Docker容器本身的网络应如何配置,尤其是在跨主机环境下怎么处理,是否需要静态IP?

 当开发者创建的镜像非常多时,复杂的镜像关系会大大拖延容器创建和启动速度,这时该如何处理复杂关系对容器性能的影响?

 大量删除操作可能带来不可预知的“孤儿”容器,不光占用大量资源,还可能带来各种莫名异常,造成大量“孤儿”容器的局面该如何应对?

 挂载volume的数据该如何进行备份,是否需要实现高可用或跨主机迁移?磁盘写满该如何处理?

 所有CPU、内存、磁盘资源限制如何才算合理?不合理的资源限制加上欠考虑的调度策略会不会产生新的资源浪费?

……

“将军开发者”突然发现,原来说服别人接受自己计划所面临的困难要远比搭建demo大得多,尤其是需要涉及现有的生产环境时。事实上,Docker是运维友好的,相比传统运维方式,通过流程和规范来保证环境一致性的做法,Docker镜像已经给运维工作带来了很大便利,更不用说它几乎可以忽略的启动时间和简单高效的配置方式了。同样,Docker更是开发者友好的,光是它伸手即来的安装和启动方式以及灵活通用的Dockerfile就足以让传统PaaS提供商汗颜。此外,它不存在任何供应商锁定和引入特殊依赖的问题了。可是,就是这样一种对各利益方都友好的技术,在真正用于生产环境时却需要解决一个棘手的问题:如何使用Docker特性来提供、升级和简化现有生产环境已经具备的运维能力?

引发这个问题的原因其实很简单,Docker给工业界带来的不只是一项技术——容器技术已经足够普及了——它带来的更多是一种思维转变。遗憾的是,Docker的思考方式与目前任何一项业务运行的方式都不是原生兼容的。

这解释了为什么我们在自建的环境中使用Docker能如鱼得水,一旦想要将它推广至生产环境中,就会感到十分棘手。而且我们发现,这些困难往往不是来自容器技术本身,而是与容器相关的网络、存储、集群、高可用等已经在传统场景中得到解决的“泥潭”。为了解决这些问题,我们的“将军开发者”就不得不经历一次又一次“从小工到专家”的历练,要么学会将Docker与传统场景中现有的解决方案集成,要么基于Docker技术重新解决一遍这些问题。于是他开始努力研究HAProxy和etcd,开始写Docker scheduler、health checker、stager、builder、deployer,终成一代“Docker大神”。而“容器云”就是在无数这样的Docker大神的努力中产生的。

现在就让我们来聊聊“容器云”吧。我们在第1章中其实已经提到过,所谓容器云,就是以容器为资源分割和调度的基本单位,封装整个软件运行时环境,为开发者和系统管理员提供用于构建、发布和运行分布式应用的平台。

容器云最直观的形态是一个颇具规模的容器集群,但它与开发者或者运维人员自己维护的“裸”容器集群不同。容器云中会被按功能或者依赖敏感性划分成组,不同容器组之间完全隔离,组内容器允许一定程度共享。容器之间的关系不再简单依靠docker link这类原生命令来进行组织,而往往是借助全局网络管理组件来进行统一治理。容器云用户也不需要直接面对Docker API,而是借助某种控制器来完成用户操作到Docker容器之间的调用转译,从而保证底层容器操作对最终用户的友好性。大多数容器云还会提供完善的容器状态健康检查和高可用支持,并尽可能做到旁路控制而非直接侵入Docker体系,从而免除“将军开发者”们不得不重复造轮子的尴尬。“容器云”会提供一个高效、完善、可配置的调度器,调度器可以说是容器云系统需要解决的第一要务,这也正是“将军开发者”最头痛的事情——他面对的容器越多,运维和管理困难程度往往会呈指数级上升。在接下来的章节中,让我们一起来逐层揭开“容器云”的面纱。

它们或来自于小而美的创业团队,或来自于数一数二的业界巨头;有的专注于服务发布,有的专注于数据存储;有的只解决编排与运维,有的却几乎可以媲美一个传统IaaS。但是,无论是那些灵活轻巧的编排工具还是庞大复杂的容器服务,它们都试图为热爱Docker并尝试真正应用Docker的“将军开发者”们解决一个核心问题:如何迈过从“容器运行”到“生产使用”之间的这条鸿沟。

评论

本文目前还没有评论……

我要评论

需要登录后才能发言
登录未成功,请修改提交。