做者:新浪微博架构师陈飞linux
如今愈来愈多的企业开始全面拥抱云计算,开始关注云原生技术。从管理物理数据中心到使用云主机,咱们不用再关心基础运维。从云主机到 Kubernetes 容器,咱们不用再关心机器的管理。云上抽象层级越高,就越少人须要关心底层问题,企业就可以节省大量的人力成本与资源投入。云原生技术就是更高一层的抽象, CNCF 对云原生技术的定义是:程序员
有利利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展应用。经过容器器、 服务网格、微服务、不可变基础设施和声明式API等技术,构建容错性好、易易于管理和便于观察的松耦合系统。redis
例如 FaaS 架构,开发者能够彻底不用考虑服务器,构建并运行应用程序和服务。还有面向开源架构的的云原生技术,与提供 MySQL, Redis 云服务相似,提供基于 Spring Cloud、Dubbo、HSF 等开源微服务架构的应用管理服务,开发者无需考虑部署、监控、运维的问题。算法
微博也一直在致力于推进基础设施云原生化,咱们围绕 Kubernetes 构建面向容器的云原生基础设施,造成了物理数据中心加多个公有云的混合云 Kubernetes 平台,提供秒级伸缩能力。构建开箱即用的 CI/CD 体系,依托云原生伸缩能力,保证大量的 Job 稳定运行,让开发人员摆脱代码发布泥沼。接下介绍这几方面的实践经验。数据库
物理数据中心 Kubernetes 化json
面向单机器的基础设施架构已经没法发挥云的最大优点。把容器按照服务颗粒度进行管理,每一个服务对应一组虚拟机,虽然基础运维经过 IaaS 层抽象获得了极大简化,可是业务的运维成本依然很高,业务 SRE 须要维护复杂的设备配置脚本,管理不同服务设备配置的差别性,须要7*24小时对故障设备进行干预。并且资源利用率没法最大化,服务池是按设备划分,一个新设备添加到服务池后只能被这个服务使用,它的冗余的计算能力并不能为其余服务使用。另外不同业务容器运行在不一样的机器上,容器网络架构更关注性能而非隔离性,一般会采用 Host 模式,这也提升了服务混合部署的运维成本。后端
基础设施只有造成集群,才能最大程度发挥容器的良好隔离、资源分配与编排管理的优点。目前 Kubernetes 已经成为容器编排系统的事实标准,提供面向应用的容器集群部署和管理系统,消除物理(虚拟)机,网络和存储基础设施的负担。同时 CNCF 推出一致性认证,推进各公有云厂商提供标准的 Kubernetes 服务,这就确保经过 Kubernetes 部署的应用在不一样云厂商之间具备可迁移性,避免被厂商锁定。缓存
以前提到微博的容器会独占物理机的网络协议栈,虽然可以作到网络效率的最大化,可是会致使多容器部署时出现端口冲突,没法知足 Kubernetes 动态编排的需求。为了解决端口冲突问题,咱们首先测试了了 vxlan 网络架构,由于其数据平面须要进行封装、解封操做,网络性能损耗超过5%,并不知足微博后端服务对网络性能的要求。最后咱们评估可行的网络方案有两种 MacVlan 和 Calico BGP。安全
其中 MacVlan 成熟稳定,经过机房上联交换机改成 Vlan Trunk 模式,在物理机上建立 MacVlan 网卡子接口,经过 CNI 插件将虚拟网卡插入 Pause 容器中,实现容器网络与物理网络打通。容器的网络通讯直接经过 MacVlan 物理子接口,发出的报文在网卡上打 VlanTag ,数据平面基本没有性能损耗。控制平面因须要对全部上联交换机进行 Vlan Trunk 改造,工做量量较大,因此这个方案仅针对高配物理机所在网络进行了改造。服务器
Calico BGP 是能够同时实现数据平面0损耗与控制平面自动化的容器网络解决方案。与 MacVlan 实现的扁平二层网络不一样,Calico 在每一个节点上部署 BGP Client 与 Reflector 实现了一个扁平的三层网络,每一个节点发布的路由状态由 Felix 维护。不过因为Felix采用iptables实现路路由ACLs功能,对性能存在必定影响。由于物理数据中心不面向外部用户开放,因此 ACLs 功能对微博是能够去除的,咱们对 Calico 进行了优化,去除 iptables 依赖。
微博也主动回馈 Kubernetes 社区,也包括为 Kubernetes 代码库作贡献,例如修复多租户下网络隔离 TC 资源泄露问题。
以前的运维是面向物理机的,因此物理机上存在不少运维工具,如日志推送、域名解析、时钟同步、定时任务等。业务经过 Kubernetes 编排后,以上的功能都须要进行容器化改造。例如在容器中使用 systemd 会涉及到提权问题,在实践过程当中发现用 systemd 若是权限控制不当会形成容器器被 kill 的状况。因此咱们单独开发了兼容 linux crontab 语法的定时任务工具 gorun,把这个工具集成在了运维容器里面。
由于业务容器会产生大量日志,出于I/O性能考虑,同时为了方便快速定位,日志会存储于本地 PVC 中,支持配额管理,避免一个容器把磁盘写满。运维基础设施容器经过监听文件,对老旧日志进行压缩清理,性能 Profile 日志会在本地进行统计计算后经过 UDP 协议推送到 Graphite 或 Prometheus 。对于关键日志,会经过 Flume 推送到 Kafka 集群,并且支持失败重传,保证日志的一致性。
经过对运维容器化后,全部业务 Pod 都具有相同的运维能力,造成标准化的监控报警、运维决策、流量切换、服务降级,异常封杀、日志查询的服务保障体系,服务可运维性大幅度提高。
容器编排
Kubernetes 的 Deployment 支持 Pod 自我修复,滚动升级和回滚,扩容和缩容,这些特性都是云原生基础设施必备的。可是 Kubernetes 设计原则中对集群的管理尤为是服务升级过程当中保持“无损”升级,对 Deployment 进行行滚动升级,会建立新 Pod 替换老 Pod ,以保证 Deployment 中 Pod 的副本数量。原有里面的IP地址和滚动升级以前的IP地址是不会相同的。而若是集群够大,一次滚动发布就会致使负载均衡变动(集群副本数/滚动发布步长)次。对于微博服务来讲,频繁变动会致使这个负载均衡管辖下的后端实例的接口不稳定。
微博实现了常备 Pod 的 In-place Rolling Updates 功能,根据业务冗余度及业务实际须要来调整上线的步长,上线过程当中保持容器的IP不变,减小在上线过程当中业务的抖动。由于业务的启动须要必定时间,不能按照容器启停来作步长控制,咱们利用 Kubernetes 容器生命周期管理的 liveness/readiness probe 实现容器提供服务的状态,避免了上线过程当中容器大面积重启的问题。同时优化了 Kubernetes 的 postStar 的原生实现,由于原生里面只调用一次,不管成功与否都会杀掉容器,改为不成功会按照指定的次数或时间进行重试。IP的静态分配使用 Calico CNI 实现:
Kubernetes 的编排策略相对灵活,分为三个阶段,初筛阶段用于筛选出符合基本要求的物理机节点,优选阶段用于获得在初筛的节点里面根据策略略来完成选择最优节点。在优选完毕以后,还有一个绑定过程,用于把 Pod 和物理机进行绑定,锁定机器上的资源。这三步完成以后,位于节点上的 kubelet 才开始建立 Pod。在实际状况中,把物理机上的容器迁移到 Kubernetes,须要保持容器的部署结构尽可能一致,例如一个服务池中每台物理机上分配部署了 wb_service_a 和 wb_service_b 两个容器,能够经过 podAffinity 来完成服务混部的编排:
一些比较复杂的,运维复杂的集群,经过 Kubernetes Operator 进行容器编排。Operator 是由 CoreOS 开发的,用来扩展 Kubernetes API ,特定的应用程序控制器,它用来建立、配置和管理复杂的有状态应用,如数据库、缓存和监控系统。Operator 基于 Kubernetes 的资源和控制器概念之上构建,但同时又包含了了应用程序特定的领域知识。Operator 能够将运维人员对软件操做的知识给代码化,同时利用 Kubernetes 强大的抽象来管理大规模的软件应用。例如 CacheService 的运维是比较复杂的,须要资源编排,数据同步,HA结构编排,备份与恢复,故障恢复等等。经过实现 CacheService Operator 可让开发经过声明式的 Yaml 文件便可建立、配置、管理复杂的 Cache 集群。CacheService Operator 支持:
1. 建立/销毁:经过Yaml声明CacheService规格,便可经过Kubernetes一键部署,删除
2. 伸缩:能够修改Yaml中声明的副本数量,Operator实现扩容,配置主从结构,挂载域名等操做
3. 备份:Operator根据Yaml中声明的备份机制,实现自动的备份功能,例例如按期备份,错峰备份等
4. 升级:实现不停机版本升级,并支持回滚
5. 故障恢复:单机故障时,自动HA切换,同时恢复副本数量,并自动恢复主从结构
复杂的应用在 Kubernetes 上部署,服务数量众多,服务间的依赖关系也比较复杂,每一个服务都有本身的资源文件,而且能够独立的部署与伸缩,这给采用 Kubernetes 作应用编排带来了诸多挑战:
1. 管理、编辑与更新大量的 Yaml 配置文件,
2. 部署一个含有大量配置文件的复杂 Kubernetes 应用,例如上面提到的 CacheService Operator
3. 参数化配置模板支持多个环境
Helm 能够解决这些问题。Helm 把 Kubernetes 资源(如Pods, Deployments, Services等) 打包到一个 Chart 中,实现可配置的发布是经过模板加配置文件,动态生成资源清单文件。
弹性伸缩
在云时代,弹性已经成为新常态。并且微博的社交媒体属性,不可提早预期的突发峰值是屡见不鲜,因此基础设施不但须要具有弹性,并且须要具有在短期内提供足够资源的能力。Kubernetes 基于容器技术在启动时间方面比虚拟机更具优点,省去了虚拟机建立、环境初始化、配置管理等诸多环节,直接拉起业务 Pod, 扩容时间能够从分钟级缩短到秒级。
并且峰值流量突发时,运维、开发同窗多是在吃饭、睡觉、休假,这个时候靠人为干预确定是来不及的,因此系统须要自动作出扩容决策。对于复杂的分布式系统,实现自动决策须要解决两个问题,一个是容量量决策,一个是依赖关系。Kubernetes 的 HPA(Horizontal Pod Autoscaling) 能够根据 Metric 自动伸缩一个 Deployment 中的 Pod 数量。HPA 由一个控制循环实现,循环周期由 horizontal-pod-autoscaler-sync-period 标志指定(默认是 30 秒)。在每一个周期内,查询 HPA 中定义的资源利利用率。而且在扩容前会有一个冷静期,通常是5分钟(可经过horizontal-pod-autoscaler-downscale-stabilization参数设置),而后经过下面的公式进行扩缩容:
可是这种容量决策存在两个问题。由于突发峰值流量上涨迅速,上述扩容机制第一次扩容往扩不不到位,触发连续屡次扩容,致使服务在流量上涨期间一直处于过载状态,影响服务SLA。另外一个问题是冷静期问题, 若是冷静期过长,会致使峰值流量没法获得及时扩容,冷静期太短会错把抖动当作峰值,形成不必要的成本浪费。第三个问题是复杂的业务系统依赖关系复杂,每一个服务根据各自指标进行伸缩,因为上面还未伸缩流量被挡在了了上游,下游这时感知不到准确流量趋势,从总体应用角度看很容易出现上游泄洪下游被淹的问题。
微博总体的弹性伸缩架构是基于混合云的架构,内网私有云,公有云虚机,云 Kubernetes ,一体化 Kubernetes 弹性集群,实现快速自动化资源调度,解决了跨 IDC 调度、定制的调度算法与策略、容量评估、服务间扩容依赖关系等,构建了全链路路,压测,指标,报警,干预多维度的能力:
1. 全链路是构建一个应用总体的容量决策体系,各服务不再独自断定容量,而是根据全链路容量指标做出一致性扩容决策
2. 压测能够帮助了解目前部署的冗余状况,合理的设定扩容公式,避免屡次重复性扩容
3. 指标体系是要从成千上万个 Metric 中抽象出能够做为决策的依据,打通负载均衡,Web 服务,数据库资源等多维度指标
4. 报警及时多路径触达,避免单点
5. 干预不但要支持快速伸缩,还应支持快速优雅降级,为服务扩容争取时间
CI/CD
云计算技术的普及,研发流程也随之变化,愈来愈多的组织和团队开始接受 DevOps 理念。持续集成(CI) 和持续交付(CD)是 DevOps 的基石。可是 CI/CD 在实际落地过程当中存在诸多困难,致使实际效果不不理想。以 CI 为例,开发同窗应该对“顺利的话,会有大约100个失败的测试”这种情形并不陌生。因为开发环境与测试环境并不一致等诸多因素,CI 常常出现不相干的偶发失败,久而久之开发同窗会默认选择忽略 CI 环节的报错警告,最终致使 CI/CD 沦为一句口号。
利用云原生的声明性基础架构,能够将应用系统和应用程序存放在 Git 的版本控制库中,每一个开发人员均可以提交拉取请求代码,轻松在 Kubernetes 上部署应用程序和运维任务,开发人员能够更高效地将注意力集中在建立新功能而不是运维相关任务上。基于 Git 的持续交付流水线,有诸多优点和特色:
1. 版本控制的声明性容器器编排,Kubermetes 做为一个云原生的工具,能够把它的“声明性”看做是“代码”,声明意味着配置由一组事实而不是一组指令组成,例如,“有十个redis服务器”,而不是“启动十个redis服务器,告诉我它是否有效”
2. Git 做为事实的惟一真实来源,任何可以被描述的内容都必须存储在 Git 库中,包括系统相关的:策略, 代码,配置,甚至监控事件
3. 与其余工具相结合,例如监控系统能够方便地监控集群,以及检查比较实际环境的状态与代码库上的状态是否一致
目前大多数 CI/CD 工具都使用基于推送的模型。基于推送的流水线意味着代码从 CI 系统开始,经过一系列构建测试等最终生成镜像,最后手动使用 “kubectl” 将容器部署到 Kubernetes 集群。程序员是最不喜欢开发流程被打断,多个系统间的切换会极大影响程序员的开发效率。因此咱们经过 CI和 IDE 结合,把 CI 流程融入到开发自测环节中,让程序员能够进行面向 CI 的测试驱动开发,提升对交付代码质量的信心。
CI/CD 流水线是围绕程序员常用的 GitLab 构建,程序员能够对 Merge Request 的 CI 结果一目了然,避免了在多个系统间来回切换。每次代码提交都要执行基于分支的完整 CI 流程,借助云原生的弹性能力和共享存储,解决了大量并发的 Job 的计算资源瓶颈,同时缓解了 Job 间共享数据的带宽压力以及网络传输延时。
持续部署要比持续集成更加复杂。部署流程中依赖人工的环节很是多,例如灰度是由运维部署到生产环境部分机器,验证须要依靠开发和运维同窗经验检查新版本各项指标是否正常,滚动发布与回滚也须要运维同窗全程干预。金丝雀部署能够有效规避风险,在生产环境的基础设施中小范围的部署新的应用代码,若是没有错误,新版本才逐渐推广到整个服务,而不用一次性从老版本切换到新版本。不过如何验证没有错误是比较有挑战的,微服务依赖复杂、部署范围广、指标维度多,是最易出错,最耗时的环节。咱们针对这个问题,开发了智能时序数据异常识别服务,覆盖操做系统,JVM,资源 SLA,业务 SLA 的上千维度指标。它不但能够自动准确识别异常、性能衰减等人工经验可以发现的问题,也可以识别如资源不合理访问等人工很难察觉的问题。如今的 CD 流程包含部署、集成测试、金丝雀验证、滚动发布、回滚自动化环节。
Weibo Mesh
Service Mesh 并非什么新的技术,它所关注的高性能、高可用、服务发现和治理等有服务化的一天就已经存在,社区也不乏这方面的最佳实践。不过以前主要是两种方式,一种是微服务 RPC 框架形式,例如 Motan, gRPC, Thrift, Dubbo 等。传统微服务框架有诸多弊端:
1. 升级困难,框架、SDK 的与业务代码强绑定
2. 多语言问题,各类语言的服务治理能力天差地别,服务质量体系难以统一
还有一种是集中式 Proxy 形式,例如 Nginx, Twemproxy, SQL Proxy 等。虽然Proxy的形式必定程度上解决了胖客户端的问题,没有了升级问题,多语言能够统一接入。可是在性能方面的损耗,对于耗时较长的请求来讲还能够接受,但这在服务间调用这种毫秒级请求时,性能是不能容忍的,并且服务的拆分势必致使整个体系内耗时随着微服务规模的扩大而剧增,并且 Proxy 自己很容易成为整个系统中的瓶颈点。因此常常能够看到后端服务是同时使用 Proxy 和 RPC 的状况。
而 Cloud Native 会催生出如此火爆的 Service Mesh ,最主要的因素是 Kubernetes 使基础设施的标准化,你们发现以前这些很重的RPC框架能够抽离出来,本来须要增长维护的复杂性被 Kubernetes 解决掉了,跨语言、服务治理等收益凸显出来。并且 Mesh 的 SideCard 形式,相比 Proxy 在请求耗时方面优点也至关明显。
微博将 Motan RPC 胖客户端实现的治理功能下沉到 Agent 上,服务注册和发现依赖微博自研 Vintage 命名和配置服务,对服务的订阅和发现来创建服务间依赖的逻辑网络。业务与的通讯协议保持一致,Agent 支持 HTTP 和 RPC 的调用,业务只需把原有的调用指向 Agent 便可,不须要改造业务代码。在跨语言通讯协议设计方面,Google 的 Protocol Buffers(pb)序列化可以提供优秀的跨语言序列化能力,可是在一是老旧 http 迁移到 pb 协议的改形成本太高,二是部分语言(例如 PHP)在作复杂 pb 对象序列化时性能比较差,甚至比 json 序列化还要慢3倍左右。微博实现了全新语言无关的通讯协议 Motan2 和跨语言友好的数据序列化协议 Simple 来应对跨语言。
除了代理 Service 的能力外,Mesh 体系提供了缓存、队列等服务化代理,业务方能够与依赖缓存、队列资源治理解耦的能力。能够大幅提升那些治理能力比较薄弱的业务和语言的架构水平。随着云原生技术的日趋完善,会有愈来愈多的基础设施从原有的 SDK 中抽象出来。将来数据库访问会以 Database Mesh 形式提供访问,封装数据分片、读写分离、从库负载均衡、熔断、链路路采集能力,例如 Google Cloud SQL 提供本地 Proxy ,业务方无需将 I P地址列入白名单或配置 SSL,便可安全地访问 Cloud SQL 。