分享摘要:得到App是较早践行微服务架构设计的技术团队,众所周知,微服务带来最大的挑战是管理和运维。从2017年初开始调研,经历VM、Swarm、Kubernetes的两次变迁,到现在已经有两年的时间,目前容器平台运行了约70%的服务,支撑了约80%的流量。本次分享将围绕降低容器和Kubernetes使用门槛、项目标准化和容器设施的易用性展开,希望我们的经验对有计划使用Kubernetes和打算将服务容器化的企业有所帮助。

罗辑思维作为一家创业公司,产品得到App的后端服务目前主要运行在阿里云。
由于技术选型比较“激进”,并且践行微服务架构设计,目前的语言栈有:按照占比排名,Golang、Node.js、Python、Java、PHP、C++,之前使用云主机(ECS)带来的运行环境管理复杂、发布过程不统一等问题。所以,将应用容器化以及微服务治理,一直是较为迫切的需求

一、 得到App服务运行环境演进

容器落地要点
* 目标清晰
* 规范先行
* 落地有节奏
* 运维、开发协作
* 内部步道、推广
* 工具要易用

从2013年底docker开源,到现在已经发展了超过5年的时间,大家已经听说容器技术的优势和收益并逐渐接受准备拥抱之。但想要落地容器以及容器管理系统Kubernetes这种新一代基础设施,远没有想象中容易。
在新技术落地的过程中,阻碍往往不是来自于技术本身,

首先是目标的确立,类似“把大象放进冰箱”分为三步:打开冰箱,放进大象,关上冰箱。我们将容器技术落地的目标也简化为三步:代码镜像化,容器化部署,微服务治理。
其次离不开大量的内部推广、布道,以及不厌其烦的各种支持,这对运维团队的要求很高,需要有人能够对编程语言栈和容器技术以及运维工具有较深了解,才能打通整个流程。
在概念层面简单化,在内部我们统一将容器类比为“进程”,Kubernetes类比为管理进程的“操作系统”,工具平台简单易用,使开发人员只关注业务。
运维牵头,开发协助,制定出一系列规范和流程,阶段性总结和周知,收集反馈、改进工具,总而言之,微服务治理,是一个从上到下的系统工程。

目前得到App的主要运行环境是docker+Kubernetes,包括测试(联调等)、预发布、线上几个环境。
少量不适合容器化和历史项目运行在ECS(云主机)中。
大约70%项目和80%流量已经运行在Kubernetes管理的容器中,还有Swarm集群也在运行服务,即将结束的2019年Q1完成全面落地Kubernetes,到时候下线Swarm集群。

进展:
* 约70%项目
* 约80%流量
* 剩余部分在Swarm集群
* 下线Swarm集群

二、 服务容器化落地推进

回顾容器在得到App落地的过程,有几个关键的时间节点,希望能对大家评估有所帮助。
受2015下半年到2016年期间以docker为首的容器技术火爆,我们在2016下半年进行前期关注,2017年初开始立项,2017年5月份完成基于Swarm的容器管理方案设计,2017年8月份完成集群及周边建设、发布系统初版开发,开始小范围业务迁移,2017年11月份完成商城业务的迁移,经历了全链路压测和跨年活动的考验。2018年3月份开始调研Kubernetes,5月份完成设计方案,8月份完成集群搭建和新发布系统初版开发,开始小范围业务迁移,11月份完成部分业务迁移,经历全链路压测和跨年活动的考验,即将结束的2019年Q1将完成全部项目迁移工作。

里程碑:
* 2017年初开始立项
* 2017年5月份完成swarm集群方案
* 2017年8月 周边建设、开始小范围业务迁移
* 2017年11 商城业务迁移完成、经历高并发考验
* 2018年3月 调研Kubernetes
* 2018年5月 完成kubernetes设计方案
* 2018年8月 围绕kubernetes配套建设、开始小范围业务迁移
* 2018年11月 部分业务迁移,经历高并发
* 2019年3月 落地完成

这里简单罗列了一下前置的规范制定:
统一各语言运行时版本(建议最多3个),汇总各种模块列表
统一base镜像操作系统发行版、版本、汇总默认安装的常用工具
制定命名规范,使用能够描述项目用途的名字,域名≈项目名=发布系统内名字
镜像分3层:OS、runtime、code
编写 OS、runtime的base image
编写Dockerfile和entrypoint模板
框架中提供探活API,Liveness、Readness、Graceful Shutdown等
统一日志格式,建议access日志为单行json,相对nginx或httpd默认的空格分割字段方式,字段较容易扩展,反序列化友好,并且能够支持命令行工具分析。
服务分级,对项目根据重要程度和影响面进行分级,不同级别服务的发布流程、SLA要求各不相同。
有了这些前提条件的加持,后续容器落地效率会有很大提升。

前置条件:
* 统一运行版本
* 域名规范
* 端口规范
* 产出各个语言栈的base image
* Dockerfile模板
* entrypoint.sh模板
* 探活API
* Liveness
* Readness
* Graceful Shutdown
* 日志格式统一 json行
* 系统目录规范、项目目录规范、日志目录规范
* 域名规范
* 服务分级

三、容器平台的易用性设计

从2018年起,得到App的技术团队对于微服务治理和DevOps推进做了大量工作,产出了很多工具,右上角的Tools矩阵,大部分是2018年诞生的,目前看已经有了很大的成绩。

Kubernetes在底层基础设施跟上层微服务治理工具中间,起到了承接作用。

3.1 网络组件

网络组件使用Flannel+alivpc Backend,目前我们的服务全在阿里云VPC网络,该方案设计简洁,比较稳定,通过写入vRouter路由表方式,来达成容器网络与ECS网络对等,实现了容器IP与ECS IP互通。

3.2 服务治理

  • Artemis框架
  • 服务发现
  • APM(tracing)
  • API Gateway
    对于服务治理,我们是在2018年初启动,这方面我们的思路是:用规范和约定来将编程框架和基础设施打通、应用以编程框架的形式连接基础设施。自研的框架Artemis,集成了服务注册和配置中心以及tracing功能。
    服务发现和配置存储使用consul,每个容器中主服务进程启动前会启动一个agent,该agent会将IP地址及模块信息注册到服务发现中心,并跟consul保持心跳,agent启动时会拉取全量数据并缓存到本地,同时接受服务发现中心的push。当容器挂掉时,由consul的监控程序进行剔除,但容器主动退出时,Artemis框架会捕获SIGNTERM、SIGNKILL、SIGNQUIT信号,通过agent进行服务注销。服务间所有调用需要经过本地的agent,使用IP直连的方式,多实例的负载均衡也在本地的agent中实现。
    API Gateway基于服务发现服务发现中心的数据,进行后端实例的注册。
    APM作为链路追踪、问题分析的首要工具,给开发人员提供便捷的排障支持。

3.3 配置管理

  • 两部分ddns 配置文件
  • 配置文件 存放在项目中
  • 不同环境配置文件通过entrypoint.sh切换
  • entrypoint.sh 模板中接收RUN_ENV变量,激活(mv、cp)配置文件

对于配置,没有使用ConfigMap来存放应用配置,因为目前正在推进配置中心,现状是配置文件和配置中心共存的方式,应用启动时默认去配置中心查找,查找失败时会降级使用配置文件。
使用配置中心的方式,切换不同环境的配置文件较为灵活,不过对于配置文件,是通过entrypoint.sh来进行切换。

3.4 服务间调用

  • DDNS (自研服务发现,容器间直接ip调用)
  • Service (LoadBalance)阿里云SLB(作为降级方案)
  • Ingress(未接入DDNS)
    服务间调用,目前得到App内部正在落地服务发现,未来较多的是Pod间直接IP:Port调用;
    LoadBalancer类型的Service作为服务发现的降级方案使用;
    Ingress主要作为节省LoadBalancer资源,以及调试和测试环境使用。

3.5 Ingress

  • Canary Deployment
  • nginx ingress controller
  • 支撑少量业务、以及ddns降级
  • 节点独立

Ingress Controller Backend,对比了nginx、haproxy、traefik等方案,最后选择了nginx,原因是:稳定、性能好,支持协议多(TCP、UDP、HTTP、HTTP2),运维人员熟悉。
部署方式是:每个生产集群配置3台低配节点(4核8GB),通过nodeSelector或者taint功能,使nginx独享节点,跟应用节点资源隔离。
nginx ingress controller缺点是设计较为复杂,分为3层:golang nginx lua,较难定制和修改。
虽然traefik相较nginx ingress controller设计简单不少,并且是golang开发,易于二次开发,但通过压力测试结果来看,在资源管理上,跟nginx差距很大。
nginx可以使用split_clients模块进行灰度发布(Canary Deployment)。

3.6 监控方案

  • Prometheus
  • AlertManager
  • cAdvisor
  • node-exporter
  • kube-state-metrics
  • Geb(自研)
    我们的Prometheus是单独的服务器,以运行在集群外部的方式,通过APIServer获取资源信息,然后进行metrics pull。
    使用了Node-exporter,kube-state-metrics,以及自研的收集程序Geb,来导出不同维度的度数。
    使用Grafana做监控数据展示,展示维度有Pod、Deployment、Node、Cluster、大盘。
    当数据量大、查询较慢时,可使用Prometheus alert中的record语句,进行数据预处理,即将查询产生的结果存入新的metrics,使用新metric绘制图表和报警的rules检查,速度会有较大提升。
    触发阈值后发往AlertManager,在AlertManager中根据不同的级别对告警进行路由、沉默和收敛。
    通知通道有:邮件、企业微信、短信。

目前的监控项有:
* Ingress中的url探活
* CPU、内存、load
* TCP连接数
* 文件描述符
* nnf_conntrack

部分监控数据收集使用了Prometheus push gateway,此种方式可以将程序中的实时数据发给push gateway,然后由Prometheus进行收集、存储。
图中的Geb为开发的监控agent,目前主要收集容器中的网络连接数据。



监控数据展示使用Grafana,数据来自Prometheus,目前展示两个层次的数据:Component级别(即多个Pod的汇总),Pod级别。数据维度为CPU、内存、IO、TCP连接,开发人员从Chons平台中点击链接跳转过来,自助进行查询、分析。

监控大盘,主要展示资源水位,整体的状态,运维人员使用监控大盘,关注资源的分配情况,进行节点的增减。

简单到只需要将Dockerfile和entrypoint.sh两个模板文件添加到项目代码中即可,无需要任何修改。
Chons系统在创建Component时会默认注入一个RUN_ENV变量,entrypoint.sh脚本根据该变量值进行配置文件切换。

CI和CD系统是自研的,目前功能比较简单,通过接收Gitlab的push和merge request hook的通知,触发CI流程。
CI系统调用Chons封装的构建API,Chons处理后转发到Jenkins进行构建,产出image,push到registry存储,然后Chons回调CI系统API,CI系统调用Chons封装的部署API,Chons处理后转发到Kubernetes进行更新,Watch到更新完成的event后回调CI系统,CI系统继续出发后续任务节点。
在得到App内部,目前使用较多的是测试和预发布环境。

由于自建的日志系统需要较多服务器资源,后续要花费很多精力去优化,考虑到投入产出比,使用阿里云日志服务(SLS)比较有优势,目前我们容器内产生的日志都是使用ilogtail收集发往SLS,使用阿里云监控的日志关键字监控功能监控错误日志中的特定关键字。
filebeat目前只负责收集APM产生的trace日志,发往kafka,由日志处理程序来进行消费,处理后序列化到ElasticSearch。

3.7 日志方案

  • ilogtail
  • 阿里云日志服务
  • filebeat
  • kafka
  • ElasticSearch

3.7.1 日志收集

  • DaemonSet
  • 持久化stdout日志
  • Pod更新后删除原始日志
  • 日志收集Agent使用HostPath
volumeMounts:
  - mountPath: /data/logs
    name: logs
volumes:
- name: logs
  emptyDir: {}

volumeMounts:
  - mountPath: /host
    name: root
    readOnly: true
volumes:
 - hostPath:
    path: /
    type: ""
    name: root

Pod使用EmptyDir易失性存储方式,将日志写入磁盘,filebeat和ilogtail通过HostPath挂载形式收集,以DaemonSet方式部署,每个Worker节点上部署一个Agent。

3.8 发布系统演进

  • up (git)
  • Dozer(Swarm)
  • Chons(Kubernetes)
    得到App创立初期,运维同学编写shell脚本(rsync等工具)进行发布。
    后来引入开源项目walle(瓦力)发布系统,使得发布不再需要运维人员参与,并且引入了发布审核机制,大大提高了发布效率。
    进入容器化时代后,围绕Docker Swarm和阿里云资源开发了Dozer系统;
    新一代围绕Kubernetes、支持CI/CD的Chons平台。

    up发布系统,每个项目需要运维人员和开发人员协作,须经历购买服务器,初始化服务器环境,项目发版配置,调试部署过程脚本几个步骤,较为繁琐。并且发布过程调试较难。
    作为第一代发版系统,虽然有一些问题,但一直服役到现在,管理少量使用ECS的服务发布。

    Dozer是基于Docker Swarm集群API的容器发版系统,其底层是阿里云容器服务全家桶,上层根据发布流程和规范开发了相关功能。
    使用的组件有Gitlab(代码管理),Jenkins(调用其API进行docker build,并回调Dozer),Swarm API(发布、调整容器数量等核心功能),阿里云监控(容器监控)、阿里云日志服务(日志收集 存储 分析)、阿里云SLB(多容器实例的汇聚和负载均衡)、阿里云OpenAPI(添加集群Worker节点)。
    该方案的优点是:简单,快速,文档,有容器底层技术的支持,可以将精力投在内部的落地上。
    缺点也是明显的,即限制较多,强依赖阿里云,有时候业务的需求由于阿里云暂未开放相关功能,不得不进行取舍。


Dozer的服务详情页面,比较简陋,基本上是一些链接的入口页面,但在容器落地的第一个阶段支撑了几乎所有需求,目前随着Swarm集群即将下线,它也即将停用。

Chons项目,设计之初,目标就是一款私有容器云产品,它的竞争对手是业内容器云公司的产品。在开发迭代过程中广泛借鉴了各个产品的优点,避免它们的缺点。
它定位是:开发人员在工作中必须使用的系统,资源申请、开发、问题排查,Chons都是第一入口。
在易用性方面,屏蔽了Kubernetes中的各种概念,简化为两个概念Stack和Component。不给用户造成困扰,开发人员只关注Coding、监控数据和日志。
同时对Manifests有所展现,给希望了解Kubernetes的用户查看。


虽然Chons的设计支持一个Pod运行多个容器,但目前大部分服务的运行方式是,一个Pod中只有一个容器,容器内部有两个进程:服务发现agent,和业务进程。
容器内无持久化数据。

Component详情页,为最高频使用页面。
参考多个云商产品,合并Deployment|StatefulSet、Pod、Service、Ingress、日志、监控、灰度发布、构建历史、部署历史、事件等功能。


把Kubernetes-dashboard中的console前后端代码引入,原因是:
该web终端功能完善,支持颜色主题,支持bash快捷键,鼠标选取复制、粘贴,较为稳定。
我们加入了iterm2中的badge功能,提示用户当前所在容器。
不过该终端的后端服务单个实例负载能力有限,我们部署了多个后端服务,使用tengine的session_sticky功能进行负载均衡和调度。该console在公司内满意度较高。

四、取得的收益和遇到的问题

4.1 收益

  • 分钟级部署
  • 秒级伸缩、恢复
  • 迭代速度成倍增长
  • TCO节省
  • 简化资源管理
  • 为微服务治理打下基础
    过去,使用ECS扩容,需要人工登录阿里云管理界面 (磁盘快照、克隆节点、系统启动,预计5分钟扩容完毕)(新买实例、配置发版系统、发版,预计需要10分钟扩容完毕)。现在,自助点击修改实例数,30秒内扩容完毕。
    得益于Kubernetes的优秀设计,现在运维人员不需要关注节点用途,运行环境配置等功能,每个节点都只是资源池的一部分,只需关注集群资源水位,管理工作只剩下增减节点。
    通过容器的落地,简化了环境管理,统一了发布流程,屏蔽发布细节,基础设施只关注服务运行状态和端口,开放了运维能力,打通了开发和运维间屏障,是践行DevOps的基础,最终使生产效率得到提升。

4.2 踩坑


初期由于我们的服务器还运行在阿里云经典网络(IaaS的早期多租户网络),在Swarm集群中我们使用了overlay网络,每个容器创建或删除时,由于需要集群内部广播该容器IP等信息,随着容器数量的增加,会有同步失败情况,造成服务容器间不通问题。
得到App的服务,大部分是golang语言开发,由于我们的服务多为HTTP短连接形式,并且如果请求的是域名的话,golang会直接发起dns查询,当查询量过大时会遇到“lookup failed”相关报错,需要在容器内部运行nscd服务。
内核中的tcp参数,尤其是netfilter相关,对于容器网络稳定性影响较大,图中为目前我们在使用的内核参数。

4.3 未来

  • 混合云建设
  • 强化CI/CD 功能
  • 借鉴Serverless思想(Faas)
  • 开放更多运维能力
    未来我们将会进行混合云建设,Kubernetes和容器技术的特性,使得从公有云迁到线下,以及多公有云的支持,是必不可少的前提。
    后续也会在Chons中强化混合云和多云的容器调度、强化CI/CD的功能等。

Q&A

Q1:业务代码是否打到镜像里?
A:在,镜像分3层,os、runtime、code
Q5:业务环境有跨机房么 还是云环境 是共用一个集群 还是不同集群呢?
A:目前还是主要用阿里云,未来会建设混合云,混合云方案还未确定,个人倾向多集群,上层Federation Cluster 管理。
Q6:并发最高多少啊?服务器大概什么配置?集群节点有多少呢?
A:Worker节点32核64GB
Q7:代码使用发布是什么的呢 有使用helm么
A:通过Kubernetes API Server更新Deployment中template的spc里的container image。
Q8:单个应用发布一次耗时多长时间?主要耗时是在哪个阶段呢?
A:1分钟内,主要耗时在滚动发布过程中的Pod状态等待。
Q9:开发流程是什么样的,是开发创建镜像还是运维自己创建?谁负责发布到线上环境?
A:创建镜像有两种方式:CI和开发人员手工操作,线上环境开发提交上线单,QA审核。
Q11: Kubernetes 目前是都在阿里云上面吗? 有没有跨云去实现平台的搭建,如果使用混合云对你们最大的挑战?
A: 目前还是主要用阿里云,未来会建设混合云,混合云方案还未确定,个人倾向多集群,上层Federation Cluster 管理。
Q12: Chon和Dozer是自研的平台吗?网上没有找到相关的介绍。
A: 是自研的。
Q13:k8s存储是用的什么
A: 我们的微服务都是无状态的,没有存储。
Q15:overlay为何不能用在生产环境?你们现在不算生产环境吗?flannel不就是overlay吗?
A: Flannel有高性能的hostgw,在云上的话,还有各种公有云的backend,借助云的vpc网络实现。
Q16:为什么大量短链接需要优化dns?大量短链接会导致什么问题?
A: 对于PHP、python使用进程管理器的服务会有一定的缓存效果。golang对于携程的优化,默认编译会关闭glibc中的相关优化,使得对于dns服务有较多查询。
Q17:API gateway使用的什么技术呢?开源的还是自研的?
A: 自研的,golang开发,集成了服务发现。
Q20: 微服务治理部分考虑过使用istio吗
A: 有自研的服务发现和agent,未来可能会强化agent,借鉴istio。
Q21:日志这块使用emptydir的话会不会有日志丢弃的情况?
A: 没有,logtail和filebeat使用的是Watch和tail类似的模式,我们统计过,延迟最大在5秒。

分享嘉宾:孙青云,罗辑思维(得到App)容器平台负责人。