DockOne微信分享(一九六):聚美优品云平台实践


【编者的话】当下Kubernetes事实上已成为容器届编排的标准,但对于围绕容器构建的周边生态却是各有千秋。聚美优品云平台项目从2017初开始调研到现在落地推广也有快两年的时间,虽然享受到了Kubernetes对容器标准化操作的红利,但实际上在推进过程中,改造原有的架构去适配容器环境,仍会有不小的挑战。本次分享将围绕聚美云平台内部实现的细节和在电商平台中应用的场景,希望给大家带来一个不同的设计和思路。

项目背景

云平台第一版

聚美优品的云平台项目是从2017年上半年开始调研的,当时在面对基础设施管理这块我们主要面临两个选择,一个是业界比较成熟的OpenStack私有云架构,另一个是刚出来不久的Kubernetes容器方案。由于之前我们的运维团队有OpenStack相关的技术沉淀,而对于Docker方面的技术栈几乎为0,所以早期云平台选型时的方案是基于OpenStack Mitaka来做的。虽然云平台第一版做出来上线后确实为我们节省了一部分计算资源的交付时间,但是对于线上业务系统的交付运维仍然需要花大量时间去配置和调试。

云平台第二版

得益于Docker镜像环境的一致性和Kubernetes调度的灵活性,所以很快我们在2018年初开始对云平台进行重构,主要为其引入了Kubernetes来解决我们电商大促期间的弹性资源调度问题和日常物理资源的管理问题。聚美云平台第二版又名Galaxy,直译过来代表星系,寓意群星闪耀,翻译成中文也可理解为一群出色的人。在Galaxy中,目前我们已经完成了对Kubernetes,OpenStack和公有云的资源对接,并有统一的管理页面提供给运维和研发团队使用。对于Galaxy的定位,它更像是一个公共的基础设施服务,让运维从传统繁琐的基础操作中解脱出来,将重心倾向于check业务层面的东西。

Galaxy主要支持如下功能:
  • 统一的管理界面,同时支持多机房,多种资源的接入和管理;
  • 支持对Kubernetes内部资源的管理(命名空间,Deployment,DaemonSet、Service等);
  • 支持应用的健康检查和容器生命周期配置管理;
  • 支持应用的服务注册与发现;
  • 集成内部CI/CD平台,提高了运维效率;
  • 提供带权限管理的WebConsole;
  • 支持API,提供外部服务调用。


目前聚美Galaxy云平台运行在2个机房,下面资源层各分别部署了3套OpenStack和Kubernetes集群,同时还支持腾讯云、阿里云和UCloud等三方公有云资源的接入,运行时的最大规模是2K+宿主机和2W+的容器实例。
1.png

容器结构

信号量管理

为了保证Docker中的进程足够轻量和简单,我们摒弃了使用传统的Sysvinit或Supervisor的方式来管理容器内部的1号进程。取而代之的是采用dumb-init这个工具来作为容器的1号PID。dumb-init是一个天生为轻量级容器而设计的进程管理器,它能够很好向下传递信号量,并且还能够接管因子进程退出而无法找到其父进程的僵尸进程。对于容器运行时的挂起,我们采用是pause这个工具,通过Linveness探针来监控容器内进程的存活性。在处理容器内部子进程优雅退出的流程时,是通过trap这个命令来捕获terminal信号量的。一个基础的容器启动脚本如下:
2.png

容器内部调用

由于容器内进程做的足够轻量,所以当一个Pod内的多个Container服务之间有内部调用时,我们优先使用网络套接字的方式处理。如果有的业务只能使用套接字文件的话,在Kubernetes的编排中,我们添加了一个emptyDir来临时存放套接字文件,然后通过挂载Volume的形式让每个容器能够正常发现套接字文件的路径的形式来实现服务间通讯,当然套接字文件的名称和路径需要和研发同事协商一致。例如下面这个样例主要就实现了,容器A和B中的服务通过读取/var/run/service目录下的套接字文件来实现过程调用。
3.png

容器启动与停止

因为Kubernetes对于Pod内容器的启动和停止是无序化的管理方式,这对于我们在管理Container起停之间的依赖也来了一些挑战。以目前我们PHP-FPM类的容器来举个例子,每个容器都会在启动阶段去check自己依赖的容器是否已经正常运行,如果检查失败则退出重新运行,直到依赖的从容器启动完成后自己才会进行接下来的启动逻辑。同理,容器在停止的时候,利用preStop这个特性在停止容器前sleep不同的时间来控制容器停止的顺序。
4.png

5.png

代码容器

聚美优品的持续构建交付平台由于是自研的,和业界利用Jenkins生态构建的平台有很大差别,所以这里我仅举一个比较有代表性的例子来跟大家分享我们是如何在容器平台下解决代码发布的问题。首先,我们在线上的deployment编排里面大量运用了Kubernetes中的initContainer容器,它能够在正常容器起来之前预处理一些东西,例如配置下载、环境的设置等等。我们通过initContainer提前将业务打包好的代码下载解压到code volume中,等到业务容器启动时,code volume会随着容器的启动挂载到指定的代码存放路径。code volume我们使用的emptyDir来存放代码,它会随着容器的生命周期消失而销毁,所以这里我们会与研发协商好代码目录中不会有任何需要持久化到本地的数据。
6.png

容器网络

CNI

云平台的容器网络插件是采用IPVLAN来实现内部通信和外部通信。IPVLAN是Linux内核4.2+版本新支持的一个网络虚拟化方案,其工作原理类似于Macvlan。IPVLAN主要提供三种工作模式L2、L3和L3S。我们主要采用的是L2模式,简单来说,在L2模式下,操作系统内核会在master网卡上虚拟出多块网卡分给Docker容器使用,而每一块虚拟网卡都共享master网卡上的Mac地址,但是每块虚拟网卡是可以有独立的IP地址的。一个简单的基于IPVLAN构建的虚拟网络拓扑结构如下图:
7.png

IPAM

对于IPAM我们是通过host-local的方式来管理的,也就是说我们在宿主机上会预配置好一个固定给容器使用的IP地址段,而对于容器的网络规划主要是通过自研的容器网络管理平台来实现统一分配和管理。使用的逻辑如下,首先我们会在IDC机房提前规划好一批容器使用的网络,并在接入层交换机上配置好路由和访问规则,其次将容器的网络和宿主机的元数据录入到管理平台,平台会自动根据录入的元数据为宿主机分配一个全局唯一的IP地址段,同时存入到Consul的KV当中。当新增加kubelet节点宿主机在初始化CNI的时候,就会从Consul中读取到自己所对应的值去渲染CNI的配置文件。
8.png

服务和配置管理

统一配置中心

对于容器中的应用配置管理这块,我们采用的是聚美自研的配置管理系统(Dove),它能为各业务系统(不限于业务系统)提供统一的配置管理平台和方案。主要解决目前我们各项目配置维护困难、容易失误、同步效率低、管理成本太高等问题。

Dove主要的技术特性如下:
  • 极度松散耦合,不需改动原业务代码即可集成或弃用配置管理系统。
  • 高效,无需额外开销,配置即本地开发语言对象。
  • 多环境统一管理
  • 配置分组权限控制
  • 变更日志:可追溯历史内容及修改者。
  • 离线运行:配置获取后,即使配置服务下线也不会影响应用当前版本的运行。
  • 离线恢复:在无配置服务器在线的情况下,应用或其服务器重启可以自动回复上次的配置,继续正常运行。
  • 调试模式:使用调试的客户端(DEV环境),开发者可以在本地编写自己的配置内容来忽略服务端进行调试。


WechatIMG4.png

SOA服务治理

随着核心服务往容器化方向迁移,传统的通过填写IP地址管理服务上下线的运维方式也得跟着改变。首先我们面临的问题是,容器IP地址是随着容器的生命周期而存在,如果容器重启后IP发生变化,要求运维将新的IP配置到应用当中已经变得不再现实。所以在18年初,我们花了大量时间投入到SOA服务框架(Lark)的研发和落地当中,其中重点解决的问题之一,就是服务发现的问题。
10.png

lark-agent以Sidecar模式和业务容器共同组成一个Pod实例,当发起一个服务调用时,客户端会将RPC协议的请求数据发送至lark-agent,Lark根据协议中的服务名称将请求转发到后端状态正常的服务实例中去。经过一年时间的沉淀,我们已经将线上所有核心业务接入到lark平台当中,而带来的收益也非常明显,其中主要就包含:
  • 为客户端和服务端增加Lark代理,将复杂的长连接协议隐藏起来,降低了不同语言间协议的适配成本;
  • Lark主动向云端上报心跳,根据自定义策略报告服务器负载情况,把运维成本降到最低;
  • 实现与云端定时通讯,获取当前集群运行参数,根据定制化的策略,智能选择最优节点,拒绝“雪崩”情况的发生;
  • 自动剔除无法与云端进行心跳保持的节点,让上下线服务器,业务升级更加平滑;
  • 实现故障节点自动惩罚机制,连接类错误容错重试,将大大减少服务器“抖动”类问题。


非SOA架构的服务

对于非SOA架构的服务,因为kube-proxy使用iptables转发的效率问题,我们并没有直接采用Kubernetes的Service的方案,而是选择了聚美已有的一套API服务注册平台。这套平台主要是基于Consul集群、Consul-Template和Tengine/Haproxy来实现服务的管理。Consul集群本身支持多数据中心模式,我们将consul-agent容器和业务容器共同组成一个Pod实例。当容器服务器被拉起来时,consul-agent会根据ConfigMap定义好的健康检查接口对内部服务发起请求,只有服务状态正常的容器才会被渲染至Upstream或Backend配置当中去。
11.png

服务注册与销毁

运行在容器中的服务注册和销毁主要依赖Kubernetes的Pod生命周期管理的postStart和preStop钩子来实现的。在容器上线前,利用postStart调用我们预先定义好的health脚本去检查业务状态,当退出码为0时,我们认为容器服务正常,便会将服务注册到服务管理平台。同理,在容器销毁前,利用preStop去调用health脚本触发服务下线。在Kubernetes的编排中如下示例:
12.png

容器日志与监控

日志采集

容器内日志采集的问题,业界一直存在两个大方向,一种是日志落盘后通过log-agent方式将日志发往服务端,另一种是日志不落盘,通过异步方式发送日志到服务端。这两个方案各有各的好处,这里我们不做讨论。聚美云平台使用的是第一种方案,即业务代码通过聚美自研的日志组件MNLogger将正常日志和exception日志全部落地到服务器本地硬盘上,再由客户端发往至Logstash做日志过滤后转发至Kafka集群。日志采集客户端采用的是log-courier,实例是跟随着业务容器在一个Pod里面运行。
13.png

容器Mount目录

对于在容器中,如何将日志持久化到硬盘上,我们采用的是hostPath的方式,将挂载一个宿主机的日志目录给容器使用,而在容器里面主要通过创建软连接的方式将业务日志写入Volume当中。
14.png

15.png

监控服务

聚美在云平台之前就已经有了自己完善的监控体系,其中就包含链路追踪、日志监控、性能监控和统一告警几大要素。而对于云平台的接入,我们除了要收集业务的异常日志外,还需要对容器性能和状态的指标有一个全面的收集。
16.png

这里我简单列举两个点跟大家分享。

  1. 容器的性能数据的采集,云平台主要利用Prometheus联邦配合服务发现来实现的。对于不同业务逻辑的我们划分了三种服务类型的Promethues实例,最终由上层Prometheus实例负责汇总所有Metrics。这里需要着重强调的是,大家在自己在开发和设计Metrics的Labels时,应保证遵循一个统一的命名规范,最好能够结合被采集实例的元数据命名。这样有助于后期大家在出监控图时候利用Label关联多项Metrics。
    17.png

  2. 对更新正在运行时日志容器的配置文件,我们自己研发了日志采集管理平台,用户只需通过在平台上去更新日志采集路径、日志类型、kafaka topic_id等参数。参数提交成功后,平台会自动将其渲染成业务所需要的配置文件,并通过HTTP服务生成一个全局唯一的URL提供给客户端下载。当客户端检查到配置文件更新就会下载最新的配置文件,并触发进程的reload机制。
    18.png


内部应用场景

大促弹性扩容

聚美优品是一家以主打化妆品限时特卖的网上电商平台。众所周知,国内的电商平台每年都会有那么几天会推出"剁手"季的活动,例如刚刚过去的双11/双12购物节。那么该如何去满足公司在大促活动期间对资源的弹性需求呢,我这里简单发几条我们遇到的需求场景,希望起到抛砖引玉的作用引发大家思考:
  1. 扩容的资源需灵活调度容器,并支持跨机房的调度;
  2. 容器大规模上线或镜像更新,带来的镜像pull洪峰问题;
  3. 大促前需要业务承载能力进行压测评估,扩容资源会经历多次创建和销毁;
  4. 电商活动一般会在整点迎来处理请求的峰值,甚至在某个时间点前端请求出现翻数倍的场景;
  5. 对扩容资源的稳定和成本之间平衡出一个最优的结果。


其实聚美云平台内部,机房的网络环境与三方云提供商已经做了VPC网络专线的互通,对于大促期间扩容的计算资源,我们通过定义内部的元数据服务来确定在云主机启动之后自己该注册到哪个Region的Kubernetes Master。如果资源元数据发生变动,可以很快的通过下发脚本的方式重新对资源进行注册。

关于第2点,镜像洪峰问题,我们会提前将docker镜像植入到云主机镜像当中,这样当云主机在pull镜像时只需拉取镜像更新的差异层,可以节省大量流量,缩短容器上线时间。

对于第3、4、5点,我们主要利用Kubernetes的DaemonSet类型来满足容器的创建,调度和销毁需求。目前我们对于大促期间的容器使用策略是一主机一项目的机制,也就是说扩容的云主机上,我们只会调度一个项目的容器在上面运行,同时Pod的网络采用hostNetwork。这样主要有两个好处,一是调度足够简单,通过对Node添加项目标签的方式快速实现一个项目容器的上线。二是单容器实例可以有效避免因高并发来临时多容器间资源争抢问题引起滚雪球式的报错。另外还有一个不得不考虑的问题是云上容器网络选型问题。对于聚美私有云来说,公有云上的资源运行不是常态,所以我们会直接让容器使用主机网络与外部通信以降低网络的开销。
19.png

大数据Yarn弹性资源

聚美优品大数据团队的服务经常需要在凌晨跑各种业务统计数据和其它离线数据,受限于硬件资源的扩展,导致可用资源非常紧张,经常会出现数据出不来或者很晚才出来的情况,严重影响了业务同学的进度和工作。与大数据业务场景相反,业务的资源在凌晨的使用率是很低的,所以在今年年中,我们尝试和大数据团队合作,将大数据现有Yarn服务容器化后接入到的聚美私有云平台来弥补资源紧张的状况。

这里着重强调的是在Yarn容器下线过程中如果有任务正在执行,就可能导致这些任务失败。具体来说,当AM失败并且在配置的重试次数内,整个Application可以重新开始执行;如果是分配给Application(MR类型)的非AM用的Container挂掉了,AM会为其重新申请Container在其他机器上运行。所以我们修改Yarn源码让除了MR任务的Spark、Flink等任务不要分配到Docker容器中的Node上。还禁止了AppMaster分配到Docker节点中,避免因为下线Docker导致的任务异常。
20.png

结束语

总体来说,聚美云平台经过2年时间沉淀,能够满足我们大部分的日常使用场景。但是未来仍然有很多东西需要去持续优化和改进,例如对容器网络的QoS支持和动态的容器调度等等。

Q&A

Q:为啥采用Consul作为容器的服务发现与配置管理,而不采用默认的etcd?
A:这个问题主要是聚美对于Web类服务已经存在了基于Consul的发现机制,所以云平台在推广时就利用了现有的架构。

Q:请问IPVLAN网络会不会导致容器内无法访问本地物理网卡?或者说宿主机无法访问本地的容器?
A:这个问题很好,最初我们采用IPVLAN时也遇到了kubelet在通过Liveness探针时与容器网络不通。后来我们通过将主机网络和容器网络分开,解决了这个问题。

Q:服务间的Socket通讯原理是通过挂载Volume方式进行访问,这个/var/run/service内容能透露一下吗?是聚美优品的RPC框架通信协议文件吗?
A:/var/run/service其实就是一个临时目录,里面就保存了Pod的各个Container中运行进程的Sockets文件。至于服务间(不同Pod间)通讯是聚美这边自己实现的一个私有的RPC框架通信协议。

Q:问一下,你那边的不同业务分配资源的时候是每个节点都打上labels吗?
A:是的,我们主要还是通过label的的形式来做容器的调度。另外每个deployment都会通过resourcelimit来做资源限制。目前对于高资源利用的容器还是通过平台kill容器的方式,重新拉起新的容器。

Q:Prometheus是什么运行方式,拉取数据会有什么问题注意吗,单节点够用吗?
A:目前我们Prometheus是部署在Kubernetes当中,根据不通过的服务发现模式做了逻辑切分。上层通过Prometheus Federation来统一汇聚数据。

Q:那不同的业务之间访问有状态应用是通过定义ExternalName吗?比如访问MySQL或Redis等?
A:目前我们大部分跑在云平台上的容器都是无状态的服务,对于有状态的服务,还是建议通过传统的方式运行。

Q:请问你那边Pod是直接跑在虚拟机上吗,还是做了一层虚拟化分配使用虚拟机呢?
A:Pod的运行主要分两块类型。对于DaemonSet方式运行的容器,我们大都通过虚拟机或者云主机来支撑,分享里面也提到我们通过DaemonSet方式来解决我们大促期间快速扩容的需求

Q:Pod跨节点的访问是怎么做到的,如果两个节点不在同一个swtich下,需要跨路由器的情况下?
A:IPVLAN的网络是提前规划好的,并保存在网络配置管理平台上。对于IPVLAN不同网段的之间的通信,还是在三层交换机上做路由来实现的。

以上内容根据2018年12月18日晚微信群分享内容整理。分享人马青,聚美集团架构部运维负责人,目前负责聚美电商和街电运维团队的管理和集团内部云平台的建设与交付。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

0 个评论

要回复文章请先登录注册