随着容器技术的普及,作为Docker在线应用的基础,喜马拉雅FM在2016年开始推动测试环境Docker化。本文重点介绍笔者如何选择技术,搭建环境,Docker化过程中在实践中遇到的一些问题及解决方法。为什么Docker化?1.标准化配置标准化,以部署Tomcat为例,在实际物理环境中,通常一台物理机部署多台Tomcat,这就存在Tomcat的端口和目录管理的问题。理想情况:一个项目,一台主机Tomcat,Tomcat一直位于/usr/local/tomcat下(或者你喜欢的其他位置),对外端口为8080,调试端口为8000。部署标准化,现在云平台越来越越来越流行,同时物理环境也不会马上被丢弃,所以肯定存在同时部署到云环境和物理环境的问题。这就需要一系列能够屏蔽物理环境和云环境差异的工具,而Docker在其中扮演着重要的角色。2、通过API接口进行基于API的操作项目部署(CPU、内存分配、机器分配、实例数管理等),而不是原来物理机环境的手动命令行操作。3、自动调度系统可以根据API进行一些策略性的响应,比如自动扩缩容。上面的工作用原来的技术手段也不是做不到,但是太麻烦了,可用性和扩展性也不够好。Docker化的四个小目标1.业务之间互不干扰Aproject/war虚拟机/容器Ip-pert-task2.容器、容器与物理机互通3.容器编排:healthcheck,Containerscheduling,etc.etc.4.用法:使用yaml/json描述任务,通过API部署总结,搭建基于n台物理机的容器环境,整个工作的主线:一个项目,一台主机==>物理机资源不足==>虚拟化==>轻量级虚拟化==>Docker==>针对Docker容器带来的网络和存储问题==>集群编排==>对CI/CD的影响。虚拟化网络的两种思考方式Overlay隧道通常使用特殊的解封装工具。路由,每台物理机作为一个路由器,对外,将容器数据路由到目标机器;在内部,将接收到的数据路由到目标容器。通常需要在物理机上维护物理机和容器IP(网段)的映射关系。Underlay说的不准确,容器的网卡暴露在物理网络中,可以直接收发。通常,外部设备(交换机)负责网络的连接。经过比较,我们采用了MacVLAN,主要是因为:简单高效。容器可以作为虚拟机使用,容器之间相互通信就可以了。无需支持复杂的网络扩展、隔离、安全等策略。IP分配问题对于物理机、KVM等虚拟机来说,它的生命周期是很长的,而且IP一旦分配好,就几乎不会发生变化。因此,通常通过命令或Web界面手动分配。对于Docker容器,尤其是在测试环境中,容器的创建和销毁非常频繁,涉及IP的频繁分配和释放。所以IP分配必须是自动的,有一个IP资源池来管理IP。在Docker网络中,CNM(ContainerNetworkManagement)模块通过IPAM(IPaddressmanagement)驱动来管理IP地址的分配。我们基于TalkingData/Shrike重写了自己的IPAM插件,修复了多实例部署模式下重复访问的问题(Docker宿主机部署一个IPAM,防止单机出问题时整个系统不可用)-实例模式)。Docker,Docker之外的编排工具,解决了单机的虚拟化,但是当一个新的部署任务到来时,集群中的哪个Docker会去执行呢?因此,除了Docker之外,还需要一个编排工具来实现集群资源管理和任务调度。这些工具都采用Master/Slave架构。假设我们将物理机分为Master和Slave。这些工具在Slave上运行一个Agent(任务执行和数据上报),在Master上运行一个Manager(任务分发和数据聚合)。在功能、任务分发、容器资源聚合等方面,这些工具都能满足要求。它们的根本区别在于:开发过程不同。从Docker/容器化调度工具扩展到分布式集群管理工具从分布式资源管理工具,添加支持Docker的特性至此,根据喜马拉雅FM测试环境的实践,发现如下特性:需求是很弱,而且基本都是单个微服务项目的部署。公司的服务治理框架负责微服务项目的协作和服务发现。Docker环境中没有MySQL、Hadoop等基础服务。需要查询编排工具的API接口,同时要有良好的web接口,这对编排工具的数据聚合和资源管理能力有一定的要求。所以我们决定采用Marathon+Mesos的方案。在后面的实践中,因为网络和编排工具的选择,IP变更的问题造成了很大的困扰。我们甚至开发了几个小工具,见下文。镜像组织Docker的强大之处不在于一系列新技术的发明,而在于对一系列老技术的整合。阿里、腾讯等知名大公司都在Cgroups和Namespace的基础上构建了一套自己的容器工具。.对于阿里来说,使用Docker的初衷是Docker镜像,即它带来的应用环境的标准化,而不是容器化。Docker镜像的实践主要涉及以下三个问题:1.构建私有镜像仓库2.解决方案4.镜像带来容器重启。因为镜像是集成的,即使只有很小的变化,镜像的发布也必须销毁之前的容器,然后根据新的镜像创建新的容器。耗时是一方面,对以下场景不友好:只是更新一个文件,需要重启项目和容器。由于加载缓存等原因,项目和容器的启动比较耗时。对于特定的场景,有特定的方法可以避免。一个通用的解决方案,阿里重写了Docker来支持镜像的HotFix标志,部署这样的镜像并不会创建新的容器,而是更新容器。我们需要组织镜像层以最大化层重用。因为只是在测试环境下使用,镜像慢的矛盾并不算太突出。这里有一个非技术问题。阿里针对docker镜像特性的改造做了如下优化:可以减少容器的重启次数,从而减少IP的分配和释放。它影响了容器的编排策略,即deploy的新任务不再是选择一台机器来运行容器,而是找到原来的容器并应用变更。对于Docker的各种特性的理解,一个认为是天经地义的,一个是逆来顺受的。当问题出现时,要么想办法避免,要么在外围造轮子解决(或避免)。如何把CIJenkins和Marathon结合起来,有很多现成的解决方案。关键是提供几套不同的模板,方便不同的业务使用。容器变革带来的四大问题使用Docker后,容器可以在物理机之间自由漂移,物理机的作用被削弱:单纯提供计算资源。但问题是它影响了很多系统的正常运行。1、IP变化很多系统的正常运行都依赖于IP,但是IP不稳定带来了一系列的问题。解决IP变化问题主要有以下解决方案:增加新组件屏蔽IP变化,提供DNS服务(缓存和多实例问题)不改变IP由于重启后IP会变化,减少容器重启服务和IP绑定(这个方案很不优雅)对于web服务来说,IP变化导致Nginx配置频繁变化。为此,我们专门重写了一个Nginx插件来解决这个问题。针对RPC服务,技术团队有自主研发的服务治理体系,实现服务注册和发现。但是,该系统具有审核机制。对于跨语言调用,由于rpc客户端不通用,还是有很多不便之处。2.文件存储有很多项目都是将业务数据存储在文件中,这意味着在项目部署和容器重启后,这些文件必须被找到并访问。在Docker环境中,主要有以下两种方案:Dockervolumn+clusterfsDockervolumeplugin我们目前主要使用第一种,将clusterfs挂载到各个Docker主机的特定目录(如/data),并打开container/data==>dockerhost/data==clusterfs/data,任何容器都可以共享和访问/data目录下的数据。3.日志收集查看对于持久化日志存储,该技术将容器的日志目录映射到物理机上。但是,一个项目的日志是分散在多台物理机上的。原有的日志采集与告警系统,负责日志的采集、汇总、告警。所以容器化之后,不会有影响。但是系统只收集错误日志,这给开发者查看日志调试程序带来了较大的麻烦。最初提供了一个WebConsole来访问容器,操作步骤为:login==>findcontainer==>inputconsole==>op。但是还是太繁琐了,而且WebConsole的性能也不理想。但是直接为每个容器配置sshserver会对安全关机等产生不良影响。因此:登录测试环境90%是为了查看日志。以及开发协议项目的日志目录,并映射到物理机上。间接配置SSH。每台物理机启动一个固定IP的SSHContainer,并映射日志目录。Go语言实现了一个essh工具,essh-imarathon_app_name可以访问对应的SSHContainer实例并查看日志。当然日志问题也可以用elk来解决。其他问题1.影响Baseimage时区、TomcatPermGensize、编码等参数值的修正。基本图像使用alpine以尽可能紧凑。它的一些文件丢失了,导致一些Java代码无法执行。例如,当/etc/hosts中ip和容器主机名的映射被移除,并且/etc/sysconfig/network丢失时,Java代码InetAddress.getLocalHost()的执行失败。2.Safeshutdown,一些服务退出时要保存状态数据。3.支持sshd,方便大家查看日志文件。使用supervisord(SSH和Tomcat的管理)需要通过supervisord进行。SIGTERM信号给Tomcat以确保Tomcat安全关闭。这种方法经常发生supervisord要求tomcat退出,自己不退出。每台机器都有一个专用的容器,并映射一些必要的卷供大家查看日志和其他文件。4.Marathon多机房主备问题5.容器漂移对日志采集分析系统的影响6.为容器提供DNS服务,使其能够正确解析对外服务的主机名7.如何更好的推广应用待办事项1。日志收集,简化日志查找2.中心化的DC目前,项目部署的每个阶段都分散在不同的组件中。提供的用法不是面向用户的。Jenkins负责Marathonjob的代码编译和触发。Marathon负责任务的调度、销毁和回滚。Portainer负责容器数据与WebConsole的接口。滚等,人工操作容易出错。对于用户来说,很容易想当然地通过portainer对容器进行增删改查,从而造成系统不一致。由于是现成的系统,很难加入技术本身的逻辑,这使得配置中经常会出现一些语义冲突。李乾坤HimalayaFMJavaDevelopmentEngineer李乾坤,喜马拉雅FMJava开发工程师。2014年开始接触Docker,2015年东南大学硕士毕业,后加入喜马拉雅FM,从事后台开发,2016年参与测试环境Docker化,目前负责维护开发平台支持服务。
