聊一聊互联网公司Docker容器化是如何实践的?
简介
docker容器技术在17年可谓是炽手可热,docker不仅仅改变了传统软件服务的交付流程,更是为云计算和微服务大规模集群管理部署,提供了强有力的技术支撑。当今各大公司企业也是把容器化技术作为不可或缺的技术战略,于17年初我们也开始步入docker技术的生态圈,尝试进行对当前服务做容器化的改造,而持续交付作为项目开发流程中较为核心的一步,也是落地实践最早一步。
我们的问题
17年初,基于微服务的架构,仅我们团队内的服务单元已经过百,特别是经历网站的促销活动,机器扩容,资源分配等问题真的是苦不堪言,除此之外更是有一些困绕已久的问题等待解决:
1. 发布系统不统一
因为涉及到不同语言的系统开发,各个系统的构建流程都有不同,涉及到node,C#,Java还有python,发布到不同服务器上,需要通过对应的发布系统,各个系统之间记录关联很难核对,不便于追踪。
2. 多环境的管理
服务器资源新增,修改部署的配置比较多,包括打通与发布系统的环境相关联,基础服务的安装配,包括node,jdk和tomcat服务器等。
3. 环境的一致性
由于各种历史问题,导致项目发布的测试环境、集成环境和产线环境配置不一致,给系统问题的排查定位带来一定的难度,项目部署目录,服务启动用户,甚至包括jdk版本的问题,这些问题同时加重了运维工作的复杂度。
4. 资源的分配
主要是软资源的计算分配,因为服务单元非常多,每个服务单元的内存,端口分配问题会随着服务数量的增加,越来越难以计算和管理,CMDB统计不够实时,造成最终的结果就是资源利用率比较低。
微服务架构
选择docker一个重要的原因,就不得不提到微服务了,微服务相比传统的服务采用单体的架构方式,部署方式和开发模式上有很大优势。单体架构的方式,服务系统过于庞大臃肿,项目发布部署风险较高,迭代开发周期较长,而微服务按照业务维度进行功能模块拆分,使其各自成为一个可独立提供服务,可独立部署运行的单元模块,通过指定的网络协议模块之间进行通信,并且在开发模式上,也是相互独立,大幅提高了项目开发上线的周期,做到快速迭代。但是随着则业务复杂性的提升,微服务的集群数量也会急速上升,带来的问题就是面对如此多服务如何高效的管理,部署,监控等一些问题,而docker处理这些却有着得天独厚的优势。
我们微服务架构核心采用的框架是基于Spring Boot和Dubbo,并且是支持双协议的通信,http/https和Dubbo RPC的调用方式,Dubbo的主要是内部模块调用依赖,而http的接口主要是提供给异构系统的接入。整体的项目架构图如下:
容器化之路
如今再次说到docker的时候,我们往往已经不仅仅谈论的只是docker本身,因为docker engine 只是改变了应用运行时的状态管理,更多的我们还关注到服务的调度,编排,网络管理,存储,监控,配置管理等等一系列的服务,docker 俨然已经是一个庞大的技术生态圈,这个生态圈里面,大家比较关心的其中一个核心服务的就是容器编排了,因为容器编排彻底的改变了传统软件服务交付流程,而容器编排则又以k8s 和mesos 为主的居多,更确切的说,k8s已经不仅仅是作为一个编排工具了,此处暂时不做讨论,两者的选型介绍可后续关注,此次也不做过多讨论。
我们选择的容器编排是以mesos为主的,简单介绍一个以mesos为核心的各个组件以及服务依赖。
zk 注册中心,这个注册并不是服务的注册,而是用来管理mesos集群状态数据的中心服务,通过zk来构建一套高可用的mesos集群。
mesos-slave 部署在每个node(主机)节点的服务,用来采集服务器数据信息,执行指令。
mesos-master 管理服务器资源信息,分派mesos-slave资源调度指令。
marathon 资源计算服务,根据应用部署需要的资源信息,提供资源分配,并且提供了一套UI,通过UI可方便的进行管理操作。
各个组件服务结构如下图:
镜像
我们的服务,主要的包括Java和node两种类型的服务,所以基础镜像也根据不同的版本号,提供了多种类型。镜像基础使用的是alpine的版本,镜像文件非常小,jdk使用的openjdk,加上启动服务的脚本以及其他配置,配置内容,镜像最终大小100M左右,加上项目打包好以后的镜像,在150M上下,当然为了一些项目的特殊需求,也提供了oracle jdk的镜像版本,但是至今未使用过。
构建
我们的技术框架最初是基于Spring Boot,通过Spring Boot提供的打包插件,可以方便的把项目构建成一个运行时需要的jar包,启动jar包和指定的运行时的一些参数信息,是通过gitlab统一管理的一些shell文件,通过jenkins构建过程,动态的把项目的jar包和启动脚本,封装在一起,然后发布到应用服务器,解压启动。但是如果采用docker部署,这种方式就不可行了,因为docker部署面向的是镜像。
docker镜像的构建方式有多种,最合理的一种莫过于Dockerfile了,但此时我们产线运行的项目已经非常多了,一个个往里面添加Dockerfile已经不现实,所以就提供了一个Dockerfile模板,里面标注了基础镜像信息,在项目构建的流程中,通过获取一些项目参数,动态生成一个Dockerfile文件,最后通过docker build生成镜像文件,然后提交到镜像库,这个过程对于开发人员完全无感知,就这样悄无声息的完成了项目格式的转化,并且对于不同类型的项目,提供了不同类型的Dockerfile模板,甚至可以通过项目做定制化。
上面说Dockerfile是动态生成并且对开发人员无感知的,而这个构建过程,也是通过执行容器实现的,通过容器挂载的方式,把构建的结果显示在宿主机上,这整个的过程都做成了docker镜像,并且使用Dockerfile管理这些镜像,通过不同的Dockerfile轻而易举的自定义各个类型项目的构建过程,比如node项目的构建和Java的构建不同,就定义两个Dockerfile就可以了,统一在gitlab上管理,维护成本变得非常低。
发布
项目构建完成,容器的的编排部署,主要是通过marathon操作。marathon本身提供了一些对外开放的api接口,这个开放接口也是我们做自动化基石。项目构建完毕,流程并没有结束。marathon管理的服务编排,主要是配置文件,定义应用,内存,端口,以及健康监测都是在配置文件里面管理的,所以基于此,我按照格式定义了一个项目,专门来管理这些项目的配置,并且把这些配置提交到gitlab上进行管理,项目结构如下:
每一个环境对应的目录下,都是一个marathon的配置文件模板,之所以是模板,因为每次构建生成不同的镜像tag是系统自动生成的,会动态替换参数,并且调用marathon的api接口进行服务更新,服务的更新策略也都配置在marathon配置文件中的,整个的构建和发布流程,分为各自独立的两模块,通过一套shell完成,也可以独立使用,但还是为了考虑操作的简单性,接入了jenkins,并且通过jenkins增强了权限控制,并且对操作记录可追踪。构建流程结构如下图:
运行
一开始我们采用docker,只是拿了部分服务测试,并非一蹴而就,全部直接推广使用。项目测试运行刚上线,开始遇到第一个问题。写到这里,简单普及一下docker的网络模式,docker的网络模式默认的四种方式host,bridge,none和自定义的网络模式,host的方式就是容器和主机共享网络空间,容器和主机共用网络端口,这种方式的弊端不言而喻,就是部署应用的时候要关注服务器的端口分配。bridge的方式是采用的独立的网络空间,通过虚拟网桥的方式利用主机的网卡进行通信,容器的网络空间是独立的,容器内部服务端口和主机映射端口是随机的。none的形式就是关闭网络模式,这种适合于计算的服务类型。而自定义的网络模式,主要是来解决跨主机网络通信的问题,每个容器网络空间也是各自独立的,而我们的服务主要采用的就是种方式,问题也恰恰出在这里。
由于采用的是部分服务做docker化,老的服务可能需要访问新的服务,但是这个时候,老服务在利用Dubbo调用新服务的通信的时候,发现对方注册是IP地址无法访问,原因就是docker容器内的Dubbo提供的服务,注册上去的其实是个虚拟IP地址,和原有老的服务根本就不同一个网络内。所以网络单向是不通的,网络调用结构如下图:
既然是网络的问题,那么针对该问题的解决方案也列了几个:
方案一:所有服务进行docker容器化部署,这样的话所有服务就处于同一个虚拟网络段内,但是这个迁移的成本和风险非常大。
方案二:基于物理层面网络做修改配置,但是这种维护的成本较大,可扩展性不好。
方案三:更改容器内服务运行的网络,采用host的方式配置,这种方式其实并没有充分利用容器化带来的便利之处,但是确实最简单解决这个问题的方法,所以最终考虑,选择这个方案解决,而后期的优化方案,去Dubbo化。
其实针对容器网络的模块,这里也只是冰山一角,未来面临更复杂的产线环境,将会遇到更多网络的配置问题。
总结
容器化之路是,这里仅仅只是起点,本次只是简单的介绍了最开始容器化项目构建交付的流程,实际上在实践中遇到的问题不止如此,希望对大家有所帮助,思路上也希望能有共鸣,后续对相关细节问题还会再次介绍。
已有 0 人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐