【编者的话】Vivek Juneja是一名工作首尔的云服务工程师。他从2008年就开始接触云服务,是最早的AWS和Eucalyptus的使用者。本文中总结了在生产环境中使用容器的几个方面,特别是对虚拟机与容器的混合部署的观点很值得推荐给大家。
如果只是把容器限制在开发测试环境中,那么您并没有享受到面向容器研发和发布工作的全部红利。对在生产环境中使用容器的抵触情绪来源于对安全与隔离性的担忧,同时也包括对管理容器的运维经验的缺乏。
在不同程度上使用容器的组织中,迁移这些容器到生产环境的决定是需要非常大的决心的。而为全新的系统和应用采用容器技术就会轻松许多,这些系统是完美的容器原生系统。
容器原生是什么概念?一个容器原生的应用被设计和构建在容器的生命周期内,并将容器视为第一等公民。对于那些为了适应容器而被改造的应用,迁移到生产环境的决定通常是很难下的。这里是指那些遗留应用,这类应用如果要选择容器化的开发与发布模式,更是需要大范围的改造。
理解在生产环境运行容器对现有工作流程的影响是非常有必要的。下面列举了一些可能受容器影响的工作流程:
运维团队负责虚拟的整个生命周期,应用代码通常使用自动或人工工作流程拷贝到虚拟机中。虚拟机很少因为新的发布而被销毁,相反虚拟机只是用新版本的代码来进行发布。这些虚拟机偶尔的发生变化,变化是通过使用新版本的经典镜像来重建虚拟机。
另一方面,一些组织实践在发布更新时作废老虚拟机,再重新生成新的虚拟机。 Netflix的Aminator ( https://github.com/Netflix/aminato r) 和围绕它周围的实践已经成为如何用虚拟机管理生产环境并实践不变性的一股潮流。
容器的支持者们持续的宣传使用裸机来运行容器化应用。这可以避免从主机与操作系统连接层产生的性能和上下文切换的代价。这个建议在我们运行不与其他非信任系统共享资源的单体应用时会显得更加有必要。
一般组织在早期做容器选型时会使用混合策略来处理这种情况:组合运行在虚拟机和裸机中的容器化应用。这种组合可以通过分析管理和性能指标后进行调优。当运维容器的成熟度达到一定阶段后,这些组织会采用在裸机上运行他们的应用。
在共享的生产设施中运行多个应用时,在裸机中运行容器的决定需要依靠先前的经验。一个较为安全的选项是将不同的租户用虚拟机包装起来,让容器在这些虚拟机的内部作为你的服务的运行时环境。每个部署容器的虚拟机属于相同的租户,这样就在不同的租户之间提供了隔离性。
图一:在成熟度低的时候,租户之间不会共享生产环境。可以使用虚拟机或者物理机进行隔离。在成熟度高的时候,每个租户的应用组件被扩散到共享的设施中。作为容器来说,高成熟度代表着每个租户的应用组件被扩散到共享的虚拟机或者裸机的设施中。
## 在生产环境中发布应用
容器的临时天性使得升级应用时应该使用全新的容器发布程序变更,而不是去更新已经存在的实例。当一个特定的修改被标记为需要发布到生产环境时,一组使用新版本的镜像被创建出来,新的容器被新的标签标记。新标签最后是使用持续集成工具创建的。生成出来的镜像和标签被存储到容器镜像注册中心内,那里可被生产环境访问。
相比于容器注册中心在所有环境中被共享-包括开发,测试和生产环境-在某些情况下,为生产环境使用单独的容器镜像注册中心是合理的。单独的生产环境容器镜像注册中心不会与其他环境共享。在这种情况下,需要一个较好的工作流程将开发和测试环境中的候选的容器镜像迁移到生产环境中。一个中间系统将会从开发/测试注册中心拉取候选的镜像,然后重新标记而后推送到生产环境的注册中心内。使用这个方法,将会在其他环境和生产环境之间的容器镜像产生清晰的隔离。
容器的临时特性极大的限制了原有的运维经验,那就是使用静态端口绑定和IP地址来发布主机中的应用。这种静态手段对于配置网络防火墙和交换机是有帮助的。在生产环境中发布更新的最佳实践是去选用滚动更新。这就要求在生产环境中部署额外的设施去修改已经存在的负载均衡器和代理的配置,当一组新的容器实例启动现有版本。
为了在发布容器的时候保持一定的可恢复性,非常明智的做法是使用编排工具来管理容器运行环境。如果不使用任何 编排工具 ,那么运行时配置策略,比如"--restart",就是非常必要的。推荐的重启策略是在"on-failure"和"unless-stopped"之间切换,或者根据你的环境从中选择一个。
在你为容器准备生产设施时一个重要的考虑关键点是了解容器镜像的“密封”特性。容器镜像需要一套标准化的设施用以保障在开发,测试和生产环境的配置是相同的。这套标准化设施可以严格保障在生产环境中得到符合预期的行为。以下是需要考虑的要点:
虽然背离以上要素的一部分是很有诱惑性的,但是任何改变都会带来意想不到的结果,所以需要认真衡量这些改变。解决这个问题最简单的方法是从开发,测试到生产的所有环境都保持相同。一开始这点是很难做到的,但是考虑你选择容器的终极目标是什么后,你会说服自己的。所有的环境使用相同的Linux内核;它们使用相同的主机配置,文件系统拓扑,网络配置和用户角色。标准化所有的元素帮助你去保证容器镜像在被构建和测试后一直到生产环境均保持相同。
容器运行时的某些方面的行为是会有些不同的,比如访问其他依赖应有的URL或者日志级别。这些运行时配置可以通过一些手段传递给容器。其中一种是通过环境变量将运行时配置注入到容器实例中。这些环境变量指向服务注册中心,那些注册中心包含了正确的依赖服务。诸如 ZooKeeper 和 Consul 这样的工具对于实现这种操作是有帮助的。
如果主机已经使用了配置管理工具,比如 Chef , Puppet 和 Ansible ,那么最好是在所有环境中尽可能使用单一的配置。对于可扩展的设施来说,考虑到可用性和性能的要求,在所有环境中不同的配置应该只是实例的数量。
主机需要偶尔的升级到最新的内核分支或容器版本等。这些升级不能被封装在容器镜像中。这些对主机配置的修改应该遵循滚动升级的原则,那就是进行阶段性升级,而不是做一次全量升级。
最后,像主机一样,支援设施的配置也应该保持相似,比如日志聚合系统,监控,度量,服务路由和发现系统。这其实是要求与容器镜像有关的系统,不仅仅是主机,而且包括在环境中所有参与的系统,都需要有一定层次的一致性。
对于容器化应用来说标准化服务发现机制是一个重要的考量点。运行容器化应用的方式与运行虚拟机内应用的方式很少是一致的:那是因为在一台主机内会运行超过一个的容器。在这个极其罕见的情况下,如果你使用桥接网络模式,你可以为你的每个容器使用静态端口。这意味着你对主机中的容器预先选择了需要暴露的端口,并使用这些端口去配置负载均衡器和代理。
重配负载均衡和代理可能是不需要的,如果对在主机中增加容器仅仅是有限的需求。但是在大多数情况下,容器的增减是基于对稳定部署的负载或者扩展实践的思考,并且那是服务发现系统发挥作用的地方。
在生产环境中容器对于支援服务的需要与非容器环境是一样的。这包括了如何从容器实例中捕获日志并将它们传输到集中化日志管理系统中。内建的日志后端已经在Docker守护进程中得到了支持,并且现在有一些个性化的解决方案也可以使用。
目前有许多管理Docker容器产生日志的手段。Docker的默认设置是将日志非压缩的写入到磁盘中,并且可以选择删除策略去限制日志占用的磁盘空间。在生产环境中,有许多日志驱动与Docker引擎协同工作并提供给产生日志的应用许多灵活方案去透明化管理日志。如果你已经使用了一种集中化日志管理解决方案,那么你可以选择一种Dokcer日志驱动来向它填充日志数据。日志驱动支持许多种协议,比如Syslog,它可以被用在许多内嵌系统中或者云化的SaaS方案中。
容器的临时状态在容器的日志和监控领域是需要被强调的一个关键特性。在生产环境做发布时,新容器会替换掉老的。这对于传统概念中假设有状态和长时间运行的计算单元是一种破坏。滚动容器特性对于传统日志和监控理念带来了新的问题。与让计算实例运行长达数天甚至数月不同,容器可能在以小时为窗口的时间内就发生变化。
如果你实践持续化集成,这个窗口可能更小。一个以主机维度构建的日志和监控解决方案不能适应容器的灵活性。由于容器短暂的生命周期,几乎不能有一个预先说明的方式去监控它们。并且最后是将它们作为一个群组去监控,而不是监控每个容器。
将容器归为一组的一种方法是使用合理的元数据,比如标签。一个标签指的是“镜像标签”,它可以在生产环境中被运行。不要使用令人困惑的“最新”标签。
举例,假如你发布了一个叫做“product-api”的应用,并使用镜像标签“25”,环境变量设置位“production”。这意味着这是被构建系统第25个标记的镜像,并且容器运行在生产环境中。你可能在给定的时间上在容器设施中运行了很多这种实例。标签在新的发布时会被改变,但是环境变量配置将会保持为“production”
监控系统将会监控环境变量被设置为“production”的容器镜像,就好像监控一个长时间运行的生产服务一样,这样就避免了发布新版本带来的持续更新的问题。如果你使用编排工具,你应该使用更有表达力的标签来编组你得容器实例。
容器生产环境中较为合理的监控和日志策略是使用非侵入解决方案,这样就与运行时容器进行了隔离。容器监控工具需要容器感知,甚至容器平台感知。这种感知能力将会帮助监控工具在报告错误时避免错乱,例如当容器在一台主机中停止的时候并被容器平台迁移到另外一台主机。容器的足迹导致一些而外的数据需要被监控和被日志系统搜集,因此产生了额外的数据需要管理。
容器监控是一个飞速发展的领域,我们会在今年晚些时候发布 第五部电子书 中详细介绍这部分内容。目前有一些SaaS服务和本地化工具来帮助你应对该领域中的问题。
目前被广发接受的管理容器数据的方案是在生产环境中使用无状态的容器,这些容器不会在其内部储存任何数据,也就是完全事务性的容器。无状态容器在外部储存处理过的数据,超过容器范围的数据,更可取的方法是使用合适的存储服务作为可靠并可用的持久化后端。安全性在存储服务中是被更加重视的,比如数据库,队列和缓存服务。对于这些有状态容器,可接受的模式是使用数据容器。有状态服务的运行时引擎在运行态时与数据容器链接。在实践中,这意味着数据库引擎运行在一个容器中,但是“数据容器”被挂在为卷轴来存储状态。
如果你使用编排平台来运行一组主机环境,那么使用分布式存储方案是非常有必要的,比如Cluster和Ceph,他们提供了共享挂载点。如果容器实例基于可用性原则在集群中来回移动,这些方案是非常有用的。
安全是经常让你担心的,尤其当不同租户的多个容器实例运行在共享的机器中时。这种担心来源于对容器技术能否提供良好的隔离性的信心的缺失,这种期待来源于虚拟机技术的实现。但是,把容器作为虚拟机的替代者并不能改善这种担心。容器的实现,例如Docker,对应用提供安全包装。容器运行态抽象了在不同命名空间配置细粒度权限的复杂性,比如用户,网络和进程。
如果考虑到在共享设施中一个容器的多租户发布过程,使用虚拟机来隔离不同的租户,并且使用容器来隔离一个租户的不同应用组件。当社区中容器发布持续增长时,那么就需要去掉虚拟机的隔离,让所有用户共享相同的设施。
运行一个不变容器实例需要考虑的另外一个要点是避免将需要保证安全的密钥和证书暴露出去。现在有很多方法来解决方这个问题,从将密码通过环境变量设置,到挂载加密数据容器为卷轴来保护它们。但是,在这些技术中还是又 很大隐患 ,并且从容器运行提供者那里没有可用的标准方案。
成功将容器应用于生产环境的一个重要的条件是建立良好的社区。工具和实践持续发布,直到在大多数组织中运行容器变成一个常规的选项。在此之前,这种适应容器的战争在组织内部不会完结。使所有的利益相关者,特别是运维和安全团队,深刻的认识到他们对于容器的要求是需要很多工作才能实现的。在生产环境中使用容器和使用虚拟机是非常不同的,并且它将优先需要的是开发经验和运维简化,除了资源利用率的好处外。
原文链接: HOW TO RUN CONTAINERS IN PRODUCTION ENVIRONMENTS (翻译:高洪涛)
===========================================
译者介绍
高洪涛,当当网架构师,开源数据库分库分表中间件Sharding-JDBC作者。目前从事Docker相关研究工作。