随着容器技术的流行,作为线上应用 Docker 的铺垫,喜马拉雅 FM 从 2016 年开始推进测试环境的 Docker 化。本文重点介绍笔者在 Docker 化的过程中如何进行技术选型、环境搭建,和实践中碰到的一些问题及其解决方案。
为什么要Docker化?
1.标准化
- 配置标准化,以部署 Tomcat 为例,实际物理环境中,通常一台物理机部署多个 Tomcat,这就存在 Tomcat 的端口及目录管理问题。理想状态下:一个项目一个主机 Tomcat,Tomcat 永远位于/ usr / local / tomcat(或其他你喜欢的位置)下,对外端口是 8080,debug 端口是 8000。
- 部署标准化,现在云平台越来越流行,同时,也不会立即丢弃物理环境,因此必然存在着同时向云环境和物理环境部署的问题。这就需要一系列工具,能够屏蔽物理环境和云环境的差异,Docker 在其中扮演重要角色。
2.API 化
通过API接口操作项目的部署(CPU、内存分配、机器分配、实例数管理等),而不是原来物理机环境的的手工命令行操作。
3.自动化
调度系统可以根据API进行一些策略性的反应,比如自动扩容缩容。
上述工作,原有的技术手段不是不可以做,可是太麻烦,可用性和扩展性都不够好。
Docker 化的四个小目标
1.业务之间不互相干扰
- 一个项目/ war 一虚拟机/容器
- Ip-pert-task
2.容器之间、容器与物理机之间可以互通
3.容器编排:健康检查、容器调度等
4.使用方式:通过 yaml / json 来描述任务,通过 API 部署
总结一下,基于 n 台物理机搭建容器环境,整个工作的主线:
一个项目一个主机 ==>物理机资源不够 ==>虚拟化 ==>轻量级虚拟化 ==> Docker ==>针对 Docker 容器带来的网络、存储等问题 ==>集群编排 ==>对 CI / CD 的影响。
虚拟化网络的两种思路
Overlay
- 隧道,通常用到一个专门的解封包工具。
- 路由,每个物理机充当一个路由器,对外,将容器数据路由到目标机器上;对内,将收到的数据路由到目标容器中。
通常需要额外的维护物理机以及物理机上容器 IP(段)的映射关系。
Underlay
不准确的说,容器的网卡暴露在物理网络中,直接收发,通常由外部设备(交换机)负责网络的连通性。
经过对比,我们采用了 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 执行呢?因此,Docker 之外,需要一个编排工具,实现集群的资源管理和任务调度。
这些工具均采用 Master / Slave 架构,假设我们将物理机分为 Master 和 Slave,这些工具在 Slave 上运行一个 Agent(任务执行和数据上报),在 Master 上运行一个 Manager(任务分发和数据汇总)。从功能上说,任务分发和容器资源汇总,这些工具可以满足要求。它们的根本区别就是:发展历程的不同。
- 从一个 Docker / 容器化调度工具, 扩展成一个分布式集群管理工具
- 从一个分布式资源管理工具,增加支持 Docker 的 Feature
到目前为止,根据喜马拉雅FM测试环境的实践,发现有以下特点:
- 对编排的需求很弱,基本都是单个微服务项目的部署。微服务项目的协同、服务发现等由公司的服务治理框架负责。
- 基础服务,比如 MySQL、Hadoop 等暂不上 Docker 环境。
- 需要查询编排工具的 API 接口,同时有一个良好的 Web 界面,对编排工具的数据汇总、资源管理能力有一定要求。
因此我们决定使用 Marathon + Mesos 方案。在后面实践的过程中,因为网络和编排工具的选择,IP 变化的问题带来很大的困扰,我们甚至专门开发了几个小工具,参见下文。
image 的组织
Docker 的厉害之处,不在于发明了一系列新技术,而在于整合了一系列老技术,广为人知的阿里、腾讯等大厂在 Cgroups、Namespace 等基础上搞了一套自己的容器工具。对于阿里,使用 Docker 初衷是 Docker 镜像化,也就是其带来的应用环境标准化,而不是容器化。
Docker镜像的实践主要涉及到以下三个问题:
1.搭建私有 imagerepository
2.对 layer 进行组织
3.镜像的分发较慢
- 预分发,但这不解决根本,只适用部分场景
- 对 layer 进行压缩,京东目前采用该方案
4.镜像化带来容器重启
因为镜像是一体的,哪怕只有一点更改,镜像的发布都必须销毁之前的容器,然后按照新镜像创建新容器。耗时是一方面,对以下场景也很不友好:
- 只是更新一个文件,项目、容器均需要重启
- 因为加载缓存等原因,项目、容器启动比较耗时
对于具体的场景,可以有具体的办法规避。对于通用的解决方案,阿里通过改写 Docker,对镜像支持 HotFix 标识,deploy 这类镜像,不再创建新容器,而是更新容器。
我们要对镜像的 layer 进行组织,以***化的复用 layer。
因为只是在测试环境使用,镜像较慢的矛盾还不是太突出。这里有一个技术之外的问题。阿里对于 docker image feature 的改造,实现了如下优化:
- 可以减少容器的重启次数,进而减少 IP 的分配和释放。
- 影响到容器的编排策略,即 deploy 新的任务不再是选择一个机器运行容器,而是找到原来的容器应用变更。
- 对 Docker 的各种特性的认识,一则认为天经地义,二则逆来顺受。出现问题,要么想办法规避,要么在外围造个轮子去解决(还是规避)。
CI
Jenkins 如何跟 Marathon 结合,现成的方案很多。关键是提供几套不同的模板,方便不同业务的使用。
容器变化带来四大问题
使用 Docker 后,容器在物理机之间自由漂移,物理机的角色弱化成了:单纯的提供计算资源。但带来的问题是,影响了许多系统的正常运行。
1.IP 变化
许多系统的正常运行依赖 IP,但 IP 不稳定带来一系列的问题。而解决IP的变化问题主要有以下方案:
- 新增组件屏蔽 IP 变化
- 提供 DNS 服务(有缓存和多实例问题)
- 不要改变 IP
既然重启后,IP 会改变,就减少容器重启
服务与 IP 绑定(这个方案非常不优雅)
对于 Web 服务,IP的变化导致要经常更改 Nginx 配置,为此,我们专门改写了一个 Nginx 插件来解决这个问题。
对于 RPC 服务,技术团队有独立开发的服务治理系统,实现服务注册和发现。但该系统有审核机制,对于跨语言调用,因为rpc客户端不通用,仍有很多不便。
2.文件存储
有许多项目会将业务数据存储在文件中,这就意味着项目 deploy 进而容器重启之后,要能找到并访问这些文件。在 Docker 环境下主要有以下两种方案:
- Dockervolumn + cluster fs
- Dockervolume plugin
我们当下主要采用***种,将 cluster fs mount 到每台 Docker host 的特定目录(例如 / data),打通 container / data ==> docker host / data == cluster fs / data,任意容器即可共享访问 /data 目录下的数据。
3.日志采集与查看
为了日志持久化存储,技术将容器的日志目录映射到了物理机上。but,一个项目的日志分散在多个物理机中。
原有的日志采集报警系统,负责日志采集、汇总、报警。因此容器化后,并不会有什么影响。但该系统只采集错误日志,导致开发人员要查看日志以调试程序时,比较麻烦。
最初,提供了一个 Web Console 来访问容器,操作步骤为:login ==> findcontainer ==> input console ==> op。
但依然过于繁琐,并且 Web Console 的性能也不理想。而直接为每个容器配置 ssh server,又会对 safe shutdown 等产生不良影响。因此:
- 登陆测试环境,90%是为了查看日志。
- 和开发约定项目的日志目录,并将其映射到物理机下。
- 间接配置 SSH。每个物理机启动一个固定 IP 的 SSHContainer,并映射日志目录。
- 使用 Go 语言实现了一个 essh 工具,essh-imarathon_app_name 即可访问对应的 SSH Container 实例并查看日志。
当然,日志的问题,也可以通过 elk 解决。
其他问题
1.Base image 的影响
- 时区、Tomcat PermGensize、编码等参数值的修正。
- base image 为了尽可能精简,使用了 alpine。它的一些文件缺失,导致一些Java代码无法执行。比如,当去掉 / etc / hosts 中 ip 和容器主机名的映射后,加上 /etc/sysconfig/network 的缺失,导致 Java 代码 InetAddress.getLocalHost()执行失败。
2.Safeshutdown,部分服务退出时要保存状态数据
3.支持 sshd,以方便大家查看日志文件
使用 supervisord(管理 SSH 和 Tomcat),需要通过 supervisord 传导 。SIGTERM 信号给 Tomcat,以确保 Tomcat 可以 safeshutdown。该方法经常发生 supervisord 让 Tomcat 退出,但自己不退出的情况。
每台机器启动跟一个专门的容器,映射一些必要的 volume,以供大家查看日志等文件。
4.Marathon 多机房主备问题
5.容器的漂移对日志采集、分析系统的影响
6.对容器提供 DNS 服务,以使其可以正确解析外部服务的 hostname
7.如何更好的推广与应用的问题
todo
1.日志采集,简化日志搜索
2.一个集中式的 DC
当下,项目部署的各个阶段分散在不同的组件中。呈现出来的使用方式,不是面向用户的。
- Jenkins 负责代码的编译和 Marathonjob 的触发
- Marathon 负责任务调度、销毁和回滚等
- Portainer 负责容器数据的界面化以及 WebConsole
这样带来的问题是:
对于运维人员来说,一些操作不能固化下来,比如回滚等,手工操作易出错。
对于用户来说,容易想当然的通过 portainer 进行增删改容器的操作,进而引起系统的不一致。
因为是现成系统,很难加入技术自己的逻辑,这使得配置上经常出现一些语义冲突的情况。
李乾坤
喜马拉雅 FM Java 开发工程师
李乾坤,喜马拉雅 FM Java 开发工程师。2014 年开始接触 Docker,2015 年东南大学硕士毕业,随即加入喜马拉雅 FM,从事后台开发,2016 年参与测试环境 Docker 化,目前负责平台支持业务的维护与开发。