DockOne微信分享(一二一):喜马拉雅FM测试环境的Docker化实践案例

骨汤鸡蛋面 · · 1423 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。


【编者的话】随着容器技术的流行,作为线上应用Docker的铺垫,喜马拉雅FM从16年开始推进测试环境Docker化。本次分享将重点为大家介绍我们在Docker化的过程中如何进行技术选型、环境搭建,特别是实践中碰到的一些问题及其解决方案。

【3 天烧脑式 Docker 训练营 | 上海站】随着Docker技术被越来越多的人所认可,其应用的范围也越来越广泛。本次培训我们理论结合实践,从Docker应该场景、持续部署与交付、如何提升测试效率、存储、网络、监控、安全等角度进行。

简介

为什么要Docker化?

  1. 标准化

    • 配置标准化,以部署Tomcat为例,实际物理环境中,通常一台物理机部署多个Tomcat,这就存在Tomcat的端口及目录管理问题。 理想状态下:一个项目一个主机Tomcat,Tomcat永远位于/usr/local/tomcat(或其它你喜欢的位置)下,对外端口是8080,debug端口是8000。

    • 部署标准化,现在云平台越来越流行,同时,也不会立即丢弃物理环境,因此必然存在着同时向云环境和物理环境部署的问题。这就需要一系列工具,能够屏蔽物理环境和云环境的差异,Docker在其中扮演重要角色。

  2. API化,通过API接口操作项目的部署(CPU、内存分配、机器分配、实例数管理等),而不是原来物理机环境的的手工命令行操作。
  3. 自动化,调度系统可以根据API进行一些策略性的反应,比如自动扩容缩容。


上述工作,原有的技术手段不是不可以做,可是太麻烦,可用性和扩展性都不够好。

几个小目标


  1. 业务之间不互相干扰
    • 一个项目/war一虚拟机/容器
    • Ip-pert-task

  2. 容器之间、容器与物理机之间可以互通
  3. 容器编排:健康检查、容器调度等
  4. 使用方式:通过yaml/json来描述任务,通过API部署


屏幕快照_2017-05-24_15.37_.28_.png

总结一下,基于n台物理机搭建容器环境,整个工作的主线:一个项目一个主机 ==> 物理机资源不够 ==> 虚拟化 ==> 轻量级虚拟化 ==> Docker ==> 针对Docker容器带来的网络、存储等问题 ==> 集群编排 ==> 对CI/CD的影响。

网络

虚拟化网络的两种思路:

Overlay

  • 隧道,通常用到一个专门的解封包工具
  • 路由,每个物理机充作一个路由器,对外,将容器数据路由到目标机器上;对内,将收到的数据路由到目标容器中。


通常需要额外的维护物理机以及物理机上容器IP(段)的映射关系。

Underlay

不准确的说,容器的网卡暴露在物理网络中,直接收发,通常由外部设备(交换机)负责网络的连通性。

经过对比,我们采用了MacVLAN,主要是因为:
  1. 简单
  2. 效率高
  3. 我们就是想将容器“当成”虚拟机用,容器之间互通就行,不需要支持复杂的网络伸缩、隔离、安全等策略。


关于MacVLAN,这涉及到LAN ==> VLAN => MacVLAN的发展过程,请读者自行了解。网络部分参见《Docker MacVLAN实践》。

IP分配问题

对于物理机、KVM等虚拟机来说,其生命周期很长,IP一经分配便几乎不变,因此通常由人工通过命令或Web界面手动分配。而对于Docker容器来说,尤其是测试环境,容器的创建和销毁非常频繁,这就涉及到频繁的IP分配和释放。因此,IP分配必须是自动的,并且有一个IP资源池来管理IP。

在Docker网络中,CNM(Container Network Management)模块通过IPAM(IP address management)driver管理IP地址的分配。我们基于TalkingData/Shrike改写了自己的IPAM插件,fix了在多实例部署模式(一个Docker host部署一个IPAM,以防止单实例模式出现问题时,整个系统不可用的问题)下的重复存取问题。

编排

Docker解决了单机的虚拟化,但当一个新部署任务到达,由集群中的哪一个Docker执行呢?因此,Docker之外,需要一个编排工具,实现集群的资源管理和任务调度。
屏幕快照_2017-05-24_15.38_.27_.png

这些工具均采用Master/Slave架构,假设我们将物理机分为Master和Slave,这些工具在Slave上运行一个Agent(任务执行和数据上报),在Master上运行一个Manager(任务分发和数据汇总)。从功能上说,任务分发和容器资源汇总,这些工具基本都可以满足要求。就我的理解,其实这些工具的根本区别就是:发展历程的不同。
  1. 从一个Docker/容器化调度工具, 扩展成一个分布式集群管理工具
  2. 从一个分布式资源管理工具 ,增加支持Docker的Feature


其中的不同,请大家自己体会一下。

到目前为止,根据我们测试环境的实践,发现我司有以下特点:
  1. 对编排的需求很弱,基本都是单个微服务项目的部署。微服务项目的协同、服务发现等由公司的服务治理框架负责。
  2. 基础服务,比如MySQL、Hadoop等暂不上Docker环境。
  3. 需要查询编排工具的API接口,同时有一个良好的Web界面,对编排工具的数据汇总、资源管理能力有一定要求。


因此,最终我们决定使用Marathon + Mesos 方案。当然,后面在实践的过程中,因为网络和编排工具的选择,ip变化的问题给我们带来很大的困扰,甚至专门开发了几个小工具,参见下文。

image的组织

Docker的厉害之处,不在于发明了一系列新技术,而在于整合了一系列老技术,比如AUFS、LXC等,在Docker之前,我司运维也经常使用Cgroups来限制一些c项目进程使用的资源。阿里、腾讯等大厂在Cgroups、Namespace等基础上搞一套自己的容器工具,现在也广为人知。甚至在《尽在双11:阿里巴巴技术演进与超越》关于Docker部分中提到,对于阿里,使用Docker初衷是Docker镜像化,也就是其带来的应用环境标准化,而不是容器化。

Docker镜像的实践主要涉及到以下问题:
  1. 搭建私有image repository
  2. 对layer进行组织

  3. 镜像的分发较慢
    • 预分发,但这不解决根本,只适用部分场景
    • 对layer进行压缩,京东目前采用该方案

  4. 镜像化带来的容器重启问题。因为镜像是一体的,哪怕只有一点更改,镜像的发布都必须销毁之前的容器,然后按照新镜像创建新容器。耗时是一方面,对以下场景也很不友好:
    • 只是更新一个文件,项目、容器均不需要重启
    • 因为加载缓存等原因,项目、容器启动比较耗时


    对于具体的场景,可以有具体的办法规避。对于通用的解决方案,阿里通过改写Docker,对镜像支持HotFix标识,deploy这类镜像,不再创建新容器,而是更新容器。


我们要对镜像的layer进行组织,以最大化的复用layer。
屏幕快照_2017-05-24_15.39_.11_.png

因为我们还只是在测试环境使用,镜像较慢的矛盾还不是太突出,这方面并没有做什么工作。

写到这里,我们可以看到一个技术之外的技术问题。阿里对于docker image feature的改造。
  1. 可以减少容器的重启次数,进而减少IP的分配和释放。容器生命周期的延长,给用户的感觉是更像一个虚拟机。减少IP变化对其它组件的影响,一些组件不再必要。
  2. 影响到容器的编排策略。即deploy新的任务不再是选择一个机器运行容器,而是找到原来的容器应用变更。这大概是阿里采用Docker Swarm编排工具并改造Docker Swarm的部分原因。毕竟Docker Swarm起点就是一个Docker编排工具,跟Docker更亲近,也更容易改造。
  3. 我们在Docker化的过程中,对Docker的各种特性一则认为天经地义,二则逆来顺受。出现问题,要么想办法规避,要么在外围造个轮子去解决(还是规避)。这让我想到了最近在看的《大明王朝1566》,皇帝要大兴土木,严党要贪污,胡宗宪左支右绌,勉力维持。海瑞认为问题的根儿在皇帝,直接上了《治安疏》。两者都算不上什么错,胡宗宪在他的位置,重要的是保住总督的位置,这样才能打倭寇。作为一个名义上的严党分子,这样的话也不能他来说。我们在技术的选择上,也经常碰到这样的问题,各种妥协。但越早的认识到各种方案的缺陷,才会避免陷入为了方案而方案,做到预判,嗅到风向变化,随机应变。


CI

本质上Jenkins如何跟Marathon结合的问题,现成的方案很多,本文不再赘述。

关键是提供几套不同的模板,以方便不同业务的童鞋使用。

容器变化带来的问题

使用Docker后,容器在物理机之间自由漂移,物理机的角色弱化成了:单纯的提供计算资源。但带来的问题是,影响了许多系统的正常运行。

IP变化

许多系统的正常运行依赖IP,IP不稳定带来一系列的问题。而解决IP的变化问题主要有以下方案
  1. 新增组件屏蔽IP变化
  2. 提供DNS服务(有缓存和多实例问题)

  3. 不要改变IP
    • 既然重启后,IP会改变,就减少容器重启
    • 服务与IP绑定(这个方案非常不优雅)


对于Web服务,IP的变化导致要经常更改Nginx配置,为此,我们专门改写了一个Nginx插件来解决这个问题。参见一个大牛的工具weibocom/nginx-upsync-module,我为大牛的工具新增了zk支持,参见qiankunli/nginx-upsync-module-zk

对于RPC服务,我司有自己独立开发的服务治理系统,实现服务注册和发现。但该系统有审核机制,系统认为服务部署在新的机器上时(通过IP识别新机器)应先审核才能对外使用。我们和开发同学协调,在服务上线时,进行额外处理来屏蔽掉这个问题。遗憾的是,对于跨语言调用,因为rpc客户端不通用,仍有很多不便。

文件存储

有许多项目会将业务数据存储在文件中,这就意味着项目deploy进而容器重启之后,要能找到并访问这些文件。在Docker环境下主要有以下两种方案:
  1. Docker volumn + cluster fs
  2. Docker volume plugin


我们当下主要采用第一种,将cluster fs mount到每台Docker host的特定目录(例如/data),打通container /data ==> docker host /data == cluster fs /data,任意容器即可共享访问/data目录下的数据。

日志采集与查看

为了将日志持久化存储,我们将容器的日志目录映射到了物理机上。but,一个项目的日志分散在多个物理机中。

我司原有日志采集报警系统,负责日志采集、汇总、报警。因此容器化后,日志的采集和报警并不会有什么影响。但该系统只采集错误日志,导致开发人员要查看日志以调试程序时,比较麻烦。最初,我们提供了一个Web Console来访问容器,操作步骤为:login ==> find container ==> input console ==> op。但很多童鞋依然认为过于繁琐,并且Web Console的性能也不理想。而直接为每个容器配置ssh server,又会对safe shutdown等产生不良影响。因此
  1. 登陆测试环境,90%是为了查看日志
  2. 和开发约定项目的日志目录,并将其映射到物理机下
  3. 间接配置ssh。每个物理机启动一个固定ip的ssh container,并映射日志目录
  4. 使用go语言实现了一个essh工具,essh -i marathon_app_name即可访问对应的ssh container实例并查看日志。


当然,日志的问题,也可以通过elk解决。

部署有状态服务

其它问题

Mesos + Marathon + Docker的文章很多,其实这才是本文的重点。

  1. Base image的影响
    1. 时区、Tomcat PermGensize、编码等参数值的修正
    2. base image为了尽可能精简,使用了alpine。其一些文件的缺失,导致一些java代码无法执行。比如,当去掉/etc/hosts中ip和容器主机名的映射后,加上/etc/sysconfig/network的缺失,导致Java代码InetAddress.getLocalHost()执行失败。参见《Java InetAddress.getLocalHost() 在linux里实现

  2. Safe shutdown,部分服务退出时要保存状态数据

  3. 支持sshd(已解决,但对解决其他问题是个有益的参考),以方便大家查看日志文件(Web Console对查看大量日志时,还是不太好用)
    1. 使用supervisord(管理SSH和Tomcat),需要通过supervisord传导SIGTERM信号给Tomcat,以确保Tomcat可以safeshutdown。该方法比较low,经常发生supervisord让Tomcat退出,但自己不退出的情况。
    2. 每台机器启动跟一个专门的容器,映射一些必要的volume,以供大家查看日志等文件

  4. Marathon多机房主备问题
  5. 容器的漂移对日志采集、分析系统的影响
  6. 对容器提供DNS服务,以使其可以正确解析外部服务的hostname
  7. 如何更好的推广与应用的问题(这是个大问题,包括分享PPT的写作思路、Jenkins模板的创建等,不比解决技术难题耗费的精力少)


todo

  1. 日志采集,简化日志搜索

  2. 一个集中式的DC。当下,项目部署的各个阶段分散在不同的组件中。呈现出来的使用方式,不是面向用户的。
    • Jenkins负责代码的编译和Marathon job的触发
    • Marathon负责任务调度、销毁和回滚等
    • Portainer负责容器数据的界面化以及Web Console


这样带来的问题是:
  1. 对于运维人员人说,一些操作不能固化下来,比如回滚等,手工操作易出错。
  2. 对于用户来说,容易想当然的通过portainer进行增删改容器的操作,进而引起系统的不一致。
  3. 因为是现成系统,很难加入我们自己的逻辑,这使得配置上经常出现一些语义冲突的情况。


Q&A

Q:镜像精炼影响很大吗?Docker相同层不是只下载一次吗?

A:我们绝大部分是Java项目,通常一个war包五六十M,更大的也有,占用一个layer。频繁的发布和部署,仅仅是下载这一个layer,时间上还是有一点耗时的。
Q:网络对于Kubernetes出来容易,进去很难。不知道你们说的物理机和Docker网络是怎么互通的?希望能详细说下。

A:首先通过docker ipam driver我们为每个容器分配一个IP,Docker本身也支持MacVLAN,不准确的说,相当于每个容器有一个物理网卡,只是和物理机网段不同。我们通过交换机连通两个网段。
Q:问一下,配置的统一管理是怎么做的?哪些配置信息做了统一管理,数据库的链接信息也是放到配置信息里吗?
A:可能我文中用配置一词不太对,文中的配置更多指的是,Tomcat的安装目录,Tomcat的Web端口、debug端口号,日志目录命名等约定。这个是做镜像时便已经确定的。

Q:容器有了固定IP那么就不需要NAT了,那么在原Mesos下的服务发现就不适用了,能详细介绍下这块怎么搞么?

A:从根本上,不管容器IP变不变,因为以后做扩容缩容,我们认为服务的IP总是会变得。因此我们写了Nginx插件等额外组件来屏蔽IP的变化,而不是尝试让IP不变。
Q:怎么收集Tomcat启动时的log? 有没有想过用WebSocket?

A:我们公司有自己的日志采集系统,可以采集并分析业务日志。至于Tomcat日志,我们业务上暂时还不需要采集。我们有自己的手段来采集Tomcat运行状态。这里顺便说下,很多方案我们的选择,是基于公司目前已有的一些框架工具,大家要按照自己的情况因地制宜。
Q:将容器的应用端口映射到主机端口,能不能解决IP变化问题?

A:因为容器会在主机之间漂移,我们通过“物理机IP:物理机port”来访问容器时,容器对应的物理机IP在deploy后,还是会变得。
以上内容根据2017年5月23日晚微信群分享内容整理。分享人李乾坤,喜马拉雅FM Java开发工程师。14年开始接触Docker,15年东南大学硕士毕业,随即加入喜马拉雅FM,从事后台开发,16年参与测试环境Docker化,目前负责平台支持业务的维护与开发。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1423 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传