微信分享

微信分享

DockOne微信分享(二一一):基于Actor模型的CQRS/ES解决方案分享

JetLee 发表了文章 • 0 个评论 • 325 次浏览 • 2019-06-05 16:17 • 来自相关话题

【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将 ...查看全部
【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将从不同视角为大家介绍Actor模型、CQRS/ES架构以及Service Fabric在高并发场景中的考量和应用。

最近一段时间我一直是这个话题的学习者、追随者,这个话题目前生产环境落地的资料少一些,分享的内容中有一些我个人的思考和理解,如果分享的内容有误、有疑问欢迎大家提出,希望通过分享这种沟通方式大家相互促进,共同进步。
# 引言
本话题由三部分组成:

  • Actor模型&Orleans:在编程的层面,从细粒度-由下向上的角度介绍Actor模型;
  • CQRS/ES:在框架的层面,从粗粒度-由上向下的角度介绍Actor模型,说明Orleans技术在架构方面的价值;
  • Service Fabric:从架构部署的角度将上述方案落地上线。
群里的小伙伴技术栈可能多是Java和Go体系,分享的话题主要是C#技术栈,没有语言纷争,彼此相互学习。比如:Scala中,Actor模型框架有akka,CQRS/ES模式与编程语言无关,Service Fabric与Kubernetes是同类平台,可以相互替代,我自己也在学习Kubernetes。# Actor模型&Orleans(细粒度)##共享内存模型多核处理器出现后,大家常用的并发编程模型是共享内存模型。
1.png
这种编程模型的使用带来了许多痛点,比如:
  • 编程:多线程、锁、并发集合、异步、设计模式(队列、约定顺序、权重)、编译
  • 无力:单系统的无力性:①地理分布型、②容错型
  • 性能:锁,性能会降低
  • 测试:
- 从坑里爬出来不难,难的是我们不知道自己是不是在坑里(开发调试的时候没有热点可能是正常的) - 遇到bug难以重现。有些问题特别是系统规模大了,可能运行几个月才能重现问题
  • 维护:
- 我们要保证所有对象的同步都是正确的、顺序的获取多个锁。 - 12个月后换了另外10个程序员仍然按照这个规则维护代码。简单总结:
  • 并发问题确实存在
  • 共享内存模型正确使用掌握的知识量多
  • 加锁效率就低
  • 存在许多不确定性
##Actor模型Actor模型是一个概念模型,用于处理并发计算。Actor由3部分组成:状态(State)+行为(Behavior)+邮箱(Mailbox),State是指actor对象的变量信息,存在于actor之中,actor之间不共享内存数据,actor只会在接收到消息后,调用自己的方法改变自己的state,从而避免并发条件下的死锁等问题;Behavior是指actor的计算行为逻辑;邮箱建立actor之间的联系,一个actor发送消息后,接收消息的actor将消息放入邮箱中等待处理,邮箱内部通过队列实现,消息传递通过异步方式进行。
2.png
Actor是分布式存在的内存状态及单线程计算单元,一个Id对应的Actor只会在集群种存在一个(有状态的 Actor在集群中一个Id只会存在一个实例,无状态的可配置为根据流量存在多个),使用者只需要通过Id就能随时访问不需要关注该Actor在集群的什么位置。单线程计算单元保证了消息的顺序到达,不存在Actor内部状态竞用问题。举个例子:多个玩家合作在打Boss,每个玩家都是一个单独的线程,但是Boss的血量需要在多个玩家之间同步。同时这个Boss在多个服务器中都存在,因此每个服务器都有多个玩家会同时打这个服务器里面的Boss。如果多线程并发请求,默认情况下它只会并发处理。这种情况下可能造成数据冲突。但是Actor是单线程模型,意味着即使多线程来通过Actor ID调用同一个Actor,任何函数调用都是只允许一个线程进行操作。并且同时只能有一个线程在使用一个Actor实例。##Actor模型:OrleansActor模型这么好,怎么实现?可以通过特定的Actor工具或直接使用编程语言实现Actor模型,Erlang语言含有Actor元素,Scala可以通过Akka框架实现Actor编程。C#语言中有两类比较流行,Akka.NET框架和Orleans框架。这次分享内容使用了Orleans框架。特点:Erlang和Akka的Actor平台仍然使开发人员负担许多分布式系统的复杂性:关键的挑战是开发管理Actor生命周期的代码,处理分布式竞争、处理故障和恢复Actor以及分布式资源管理等等都很复杂。Orleans简化了许多复杂性。优点:
  • 降低开发、测试、维护的难度
  • 特殊场景下锁依旧会用到,但频率大大降低,业务代码里甚至不会用到锁
  • 关注并发时,只需要关注多个actor之间的消息流
  • 方便测试
  • 容错
  • 分布式内存
缺点:
  • 也会出现死锁(调用顺序原因)
  • 多个actor不共享状态,通过消息传递,每次调用都是一次网络请求,不太适合实施细粒度的并行
  • 编程思维需要转变
3.png
第一小节总结:上面内容由下往上,从代码层面细粒度层面表达了采用Actor模型的好处或原因。# CQRS/ES(架构层面)##从1000万用户并发修改用户资料的假设场景开始
4.png
[list=1]
  • 每次修改操作耗时200ms,每秒5个操作
  • MySQL连接数在5K,分10个库
  • 5 5k 10=25万TPS
  • 1000万/25万=40s
  • 5.png
    在秒杀场景中,由于对乐观锁/悲观锁的使用,推测系统响应时间更复杂。##使用Actor解决高并发的性能问题
    6.png
    1000万用户,一个用户一个Actor,1000万个内存对象。
    7.png
    200万件SKU,一件SKU一个Actor,200万个内存对象。
    • 平均一个SKU承担1000万/200万=5个请求
    • 1000万对数据库的读写压力变成了200万
    • 1000万的读写是同步的,200万的数据库压力是异步的
    • 异步落盘时可以采用批量操作
    总结:由于1000万+用户的请求根据购物意愿分散到200万个商品SKU上:每个内存领域对象都强制串行执行用户请求,避免了竞争争抢;内存领域对象上扣库存操作处理时间极快,基本没可能出现请求阻塞情况;从架构层面彻底解决高并发争抢的性能问题。理论模型,TPS>100万+……##EventSourcing:内存对象高可用保障Actor是分布式存在的内存状态及单线程计算单元,采用EventSourcing只记录状态变化引发的事件,事件落盘时只有Add操作,上述设计中很依赖Actor中State,事件溯源提高性能的同时,可以用来保证内存数据的高可用。
    8.png
    9.png
    ##CQRS上面1000万并发场景的内容来自网友分享的PPT,与我们实际项目思路一致,就拿来与大家分享这个过程,下图是我们交易所项目中的架构图:
    10.png
    开源版本架构图:
    11.png
    开源项目GitHub:https://github.com/RayTale/Ray第二小节总结:由上往下,架构层面粗粒度层面表达了采用Actor模型的好处或原因。# Service Fabric 系统开发完成后Actor要组成集群,系统在集群中部署,实现高性能、高可用、可伸缩的要求。部署阶段可以选择Service Fabric或者Kubernetes,目的是降低分布式系统部署、管理的难度,同时满足弹性伸缩。交易所项目可以采用Service Fabric部署,也可以采用K8S,当时K8S还没这么流行,我们采用了Service Fabric,Service Fabric 是一款微软开源的分布式系统平台,可方便用户轻松打包、部署和管理可缩放的可靠微服务和容器。开发人员和管理员不需解决复杂的基础结构问题,只需专注于实现苛刻的任务关键型工作负荷,即那些可缩放、可靠且易于管理的工作负荷。支持Windows与Linux部署,Windows上的部署文档齐全,但在Linux上官方资料没有。现在推荐Kubernetes。第三小节总结:[list=1]
  • 借助Service Fabric或K8S实现低成本运维、构建集群的目的。
  • 建立分布式系统的两种最佳实践:

  • - 进程级别:容器+运维工具(Kubernetes/Service Fabric)
    - 线程级别:Actor+运维工具(Kubernetes/Service Fabric)

    上面是我对今天话题的分享。

    CQRS/ES部分内容参考:《领域模型 + 内存计算 + 微服务的协奏曲:乾坤(演讲稿)》 2017年互联网应用架构实战峰会。
    #Q&A
    Q:单点故障后,正在处理的Cache数据如何处理的,例如,http、tcp请求……毕竟涉及到钱?
    A:actor有激活和失活的生命周期,激活的时候使用快照和Events来恢复最新内存状态,失活的时候保存快照。actor框架保证系统中同一个key只会存在同一个actor,当单点故障后,actor会在其它节点重建并恢复最新状态。

    Q:event ID生成的速度如何保证有效的scale?有没有遇到需要后期插入一些event,修正前期系统运行的bug?有没有遇到需要把前期已经定好的event再拆细的情况?有遇到系统错误,需要replay event的情况?
    A:当时项目中event ID采用了MongoDB的ObjectId生成算法,没有遇到问题;有遇到后期插入event修正之前bug的情况;有遇到将已定好的event修改的情况,采用的方式是加版本号;没有,遇到过系统重新迁移删除快照重新replay event的情况。

    Q:数据落地得策略是什么?还是说就是直接落地?
    A:event数据直接落地;用于支持查询的数据,是Handler消费event后异步落库。

    Q:actor跨物理机器集群事务怎么处理?
    A:结合事件溯源,采用最终一致性。

    Q:Grain Persistence使用Relational Storage容量和速度会不会是瓶颈?
    A:Grain Persistence存的是Grain的快照和event,event是只增的,速度没有出现瓶颈,而且开源版本测试中PostgreSQL性能优于MongoDB,在存储中针对这两个方面做了优化:比如分表、归档处理、快照处理、批量处理。

    Q7:开发语言是erlang吗?Golang有这样的开发模型库支持吗?
    A:开发语言是C#。Golang我了解的不多,proto.actor可以了解一下:https://github.com/AsynkronIT/protoactor-go。

    Q:每个Pod的actor都不一样,如何用Kubernetes部署actor,失败的节点如何监控,并借助Kubernetes自动恢复?
    A:actor是无状态的,失败恢复依靠重新激活时事件溯源机制。Kubernetes部署actor官方有支持,可以参考官方示例。在实际项目中使用Kubernetes部署Orleans,我没有实践过,后来有同事验证过可以,具体如何监控不清楚。

    Q:Orleans中,持久化事件时,是否有支持并发冲突的检测,是如何实现的?
    A:Orleans不支持;工作中,在事件持久化时做了这方面的工作,方式是根据版本号。

    Q:Orleans中,如何判断消息是否重复处理的?因为分布式环境下,同一个消息可能会被重复发送到actor mailbox中的,而actor本身无法检测消息是否重复过来。
    A:是的,在具体项目中,通过框架封装实现了幂等性控制,具体细节是通过插入事件的唯一索引。

    Q:同一个actor是否会存在于集群中的多台机器?如果可能,怎样的场景下可能会出现这种情况?
    A:一个Id对应的Actor只会在集群种存在一个。

    以上内容根据2019年6月4日晚微信群分享内容整理。分享人郑承良,上海某科技公司架构师,对高并发场景下的分布式金融系统拥有丰富的实战经验,曾为澳大利亚、迪拜多家交易所提供技术支持。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二一零):平安证券Kubernetes容器集群的DevOps实践

    齐达内 发表了文章 • 0 个评论 • 381 次浏览 • 2019-05-29 11:54 • 来自相关话题

    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同 ...查看全部
    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同行有所借鉴和帮助。
    #生产环境的高可用Master部署方案
    Kubernetes的高可用Master部署,现在网络上成熟的方案不少。大多数是基于Haproxy和Keepalived实现VIP的自动漂移部署。至于Haproxy和Keepalived,可独立出来,也可寄生于Kubernetes Master节点。

    我司在IT设备的管理上有固定的流程,VIP这种IP地址不在标准交付范围之内。于是,我们设计了基于DNS解析的高可用方案。这种方案,是基于Load Balancer变形而来。图示如下:
    1.png

    这种构架方案,平衡了公司的组织结构和技术实现。如果真发生Master挂掉,系统应用不受影响,DNS的解析切换可在十分钟内指向新的Master IP,评估在可接受范围之内。

    公司内部安装Master节点时,使用的基本工具是Kubeadm,但是作了脚本化改造及替换成了自己的证书生成机制。经过这样的改进之后,使用kubeadm进行集群安装时,就更有条理性,步骤更清晰,更易于在公司进行推广。

    底层的etcd集群使用独立的Docker方式部署,但共享kubeadm相关目录下的证书文件,方便了api-server和etcd的认证通信。脚本的相关配置如下:
    2.png

    当以DNS域名的形式进行部署后,各个证书配置认证文件,就不会再以IP形式连接,而是以DNS域名形式连接api-server了。如下图所示:
    3.png

    #分层的Docker镜像管理
    接下来,我们分享一下对Docker镜像的管理。Docker的企业仓库,选用的是业界流行的Harbor仓库。根据公司研发语言及框架的广泛性,采用了三层镜像管理,分为公共镜像,业务基础镜像,业务镜像(tag为部署发布单),层层叠加而成,即形成标准,又照顾了一定的灵活性。

    * 公共镜像:一般以alpine基础镜像,加上时区调整,简单工具。
    * 业务基础镜像:在公共镜像之上,加入JDK、Tomcat、Node.js、Python等中间件环境。
    * 业务镜像:在业务基础镜像之上,再加入业务软件包。

    4.png

    #Dashboard、Prometheus、Grafana的安全实践
    尽管在Kubernetes本身技术栈之外,我司存在体系化的日志收集,指标监控及报警平台,为了运维工具的丰富,我们还是在Kubernetes内集成了常用的Dashboard、Prometheus、Grafana组件,实现一些即时性运维操作。

    那么,这些组件部署,我们都纳入一个统一的Nginx一级url下,二级url才是各个组件的管理地址。这样的设计,主要是为了给Dashborad及Prometheus增加一层安全性(Grafana自带登陆验证)。

    这时,可能有人有疑问,Dashboard、kubectl都是可以通过cert证书及RBAC机制来实现安全性的,那为什么要自己来引入Nginx作安全控制呢?

    在我们的实践过程中,cert证书及RBAC方式,结合SSH登陆帐号,会形成一系列复杂操作,且推广难度高,我们早期实现了这种模式,但目前公司并不具备条件,所以废弃了。公司的Kubernetes集群,有专门团队负责运维,我们就针对团队设计了这个安全方案。

    Prometheus的二级目录挂载参数如下:
    5.png

    Grafana的二级目录挂载参数如下:
    6.png

    Dashboard在Nginx里的配置如下:
    7.png

    #一个能生成所有软件包的Jenkins Job
    在CI流水线实践,我们选用的GitLab作为源代码管理组件,Jenkins作为编译组件。但为了能实现更高效标准的部署交付,公司内部实现一个项目名为prism(棱镜)的自动编译分发部署平台。在容器化时代,衍生出一个Prism4k项目,专门针对Kubernetes环境作CI/CD流程。Prism4k版的构架图如下所示:
    8.png

    在这种体系下,Jenkins就作为我们的一个纯编译工具和中转平台,高效的完成从源代码到镜像的生成。

    由于每个IT应用相关的变量,脚本都已组织好,放到Prism4k上。故而,Jenkins只需要一个Job,就可以完成各样各样的镜像生成功能。其主要Pipeline脚本如下(由于信息敏感,只列举主要流程,有删节):
    9.png

    在Jenkins中,我们使用了一个Yet Another Docker Plugin,来进行Jenkins编译集群进行Docker生成时的可扩展性。作到了编译节点的容器即生即死,有编译任务时,指定节点才生成相关容器进行打包等操作。
    #计算资源在线配置及应用持续部署
    在Prism4k平台中,针对Jenkins的Job变量是通过网页配置的。在发布单的编译镜像过程中,会将各个变量通过API发送到Jenkins,启动Jenkins任务,完成指定task任务。
    10.png

    Pod的实例数,CPU和内存的配置,同样通过Web方式配置。
    11.png

    在配置好组件所有要素之后,日常的流程就可以基于不同部门用户的权限把握,实现流水线化的软件持续交付。

    * 研发:新建发布单,编译软件包,形成镜像,上传Harbor库。
    * 测试:环境流转,避免部署操作污染正在进行中的测试。
    * 运维:运维人员进行发布操作。

    在FAT这样的测试环境中,为加快测试进度,可灵活的为研发人员赋予运维权限。但在更正式的测试环境和线上生产环境,作为金融行业的IT建设标准,则必须由运维团队成员操作。

    下面配合截图,了解一下更具体的三大步骤。

    1. 发布单
    12.png

    在Prism4k与Jenkins的API交互,我们使用了Jenkins的Python库。

    1. 环境流转
    13.png


    1. 部署
    14.png


    在部署操作过程中,会将这次发布的信息全面展示给运维同事,让运维同事可以进行再次审查,减少发布过程中的异常情况。
    #总结
    由于Kubernetes版本的快速更新和发布,我们对于其稳定性的功能更为青睐,而对于实验性的功能,或是需要复杂运维技能的功能,则保持理智的观望态度。所以,我们对Kubernetes功能只达到了中度使用。当然,就算是中度使用,Kubernetes的运维和使用技巧,还是有很多方面在此没有涉及到,希望以后有机会,能和各位有更多的沟通和交流。愿容器技术越来越普及,运维的工作越来越有效率和质量。
    #Q&A
    Q:镜像有进行安全扫描吗:
    A:外部基本镜像进入公司内部,我们基于Harbor内置的安全功能进行扫描。

    Q:Harbor有没有做相关监控,比如发布了多少镜像,以及镜像同步时长之类的?
    A:我们没有在Harbor上作扩展,只是在我们自己的Prism4k上,会统计各个项目的一些镜像发布数据。

    Q:有没有用Helm来管理镜像包?后端存储是用的什么,原因是?
    A:没有使用Helm。目前集群有存储需求时,使用的是NFS。正在考虑建基于Ceph的存储,因为现在接入项目越来越多,不同的需求会导致不同的存储。

    Q:想了解下目前贵公司监控的纬度和监控的指标和告警这块。
    A:监控方面,我公司也是大致大致划分为基础资源,中间件,业务指标三大块监控。方法论上也是努力在向业界提倡的RED原则靠拢。

    Q:想了解下,Yaml文件怎么管理的,可以自定义生成吗?
    A:我们的Yaml文件,都统一纳到Prism4k平台管理,有一些资源是可以自定义的,且针对不同的项目,有不同的Yaml模板,然后,透过django的模块功能统一作解析。熟悉Yaml书写的研发同事可以自己定义自己项目的Yaml模板。

    Q:Pipeline会使用Jenkinfile来灵活code化Pipeline,把Pipeline的灵活性和创新性还给开发团队,这比一个模板化的统一Pipeline有哪些优势?
    A:Pipeline的运行模式,采用单一Job和每个项目自定义Job,各有不同的应用场景。因为我们的Jenkins是隐于幕后的组件,研发主要基于Prism4k操作,可以相对减少研发的学习成本。相对来说,Jenkins的维护人力也会减少。对于研发各种权限比较高的公司,那统一的Job可能并不合适。

    Q:想了解下贵公司使用什么网络方案?Pod的网络访问权限控制怎么实现的?
    A:公司现在用的是Flannel网络CNI方案。同时,在不同的集群,也有作Calico网络方案的对比测试。Pod的网络权限,这块暂时没有,只是尝试Istio的可行性研究。

    Q: 一个Job生成所有的Docker镜像,如果构建遇到问题,怎么去追踪这些记录?
    A:在项目前期接入时,生成镜像的流程都作了宣传和推广。标准化的流程,会减少产生问题的机率。如果在构建中遇到问题,Prism4k的界面中,会直接有链接到本次建的次序号。点击链接,可直接定位到Console输出。

    Q:遇到节点Node上出现100+ Pod,Node会卡住,贵公司Pod资源怎么做限制的?
    A:我们的业务Pod资源,都作了limit和request限制。如果出现有卡住的情况,现行的方案是基于项目作拆分。Prism4k本身对多环境和多集群都是支持的。

    Q:多环境下,集中化的配置管理方案,你们选用的是哪个,或是自研的?
    A:我们现在正在研发的Prism4k,前提就是要支持多环境多集群的部署,本身的功能里,yaml文件的配置管理,都是其内置功能。

    Q:etcd的--initial-cluster-state选项设置为new,重启etcd后会不会创建新的etcd集群?还是加入原有的etcd集群?
    A:我们测试过轮流将服务器(3 Master)完全重启,ectd集群的功能均未受影响。但全部关机重启,还未测试过。所以不好意思,这个问题,我暂时没有考虑过。

    Q:网络方案用的什么?在选型的时候有没有对比?
    A:目前主要应用的还是Flannel方案,今年春节以来,还测试过Flannel、Caclico、kube-router方案。因为我们的集群有可能越机房,而涉及到BGP协议时,节点无法加入,所以一直选用了Flannel。

    Q:部署的动态过程是在Jenkins的Web界面上看还是在自研的Prism4k上能看到,如果是Prism4k的话,整个可视化过程的展示这些等等也是自己开发的吗?Prism4k是用什么语言开发的,Python吗?
    A:部署的动态过程,是在Prism4k上显示。可视化方案,也只是简单的使用ajax及websocket。Prism4k后端是基于Django 2.0以上开发,其中使用了RESTful framework、channels等库,前端使用了一些js插件。

    以上内容根据2019年5月28日晚微信群分享内容整理。分享人陈刚,平安证券运维研发工程师,负责经纪业务IT应用的持续交付平台的设计和开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零九):荔枝运维平台容器化实践

    Andy_Lee 发表了文章 • 0 个评论 • 861 次浏览 • 2019-04-30 12:01 • 来自相关话题

    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布 ...查看全部
    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布系统重构过程中,技术选型的考虑以及实践过程中遇到的一些问题和解决方案。
    #背景

    荔枝后端微服务化进程较早,目前已有上千个服务模块,绝大多数是Java。得益于良好的规范,旧的运维平台实现了一套简单的自动化部署流程:

    1. 对接Jenkins进行编译打包和版本标记
    2. 把指定版本的jar包,配置文件,启动脚本一起发布到指定的机器上裸机运行
    3. 通过对Java进程的管理来完成重启,关闭应用等运维操作

    但是随着开发人员,项目数量,请求量的增加,旧的运维平台逐渐暴露出以下一些问题:

    1. Java实例部署所需资源没有清晰的统计和系统层面的隔离,仅仅依赖于启动脚本中的JVM参数来进行内存的约束,新增实例或新上项目时往往需要运维人员靠“感觉”指定部署的机器,没有有效地分配机器资源,项目之间资源争用会导致性能问题。
    2. 虽然大多数应用依赖的环境只有JDK 7和JDK 8,但一些JDK的小版本差异,以及一些自研发Java agent的使用,使得简单地指定JAVA_HOME目录的方式很难有效地管理运行环境。
    3. 开发人员的服务器权限需要回收。一个服务器上可能运行多个不同部门的项目,相关开发人员误操作可能会导致其他项目被影响。

    上述问题虽然也可以通过一些技术和规范约束来解决,但天生就是为了解决环境依赖和资源管理的容器技术是当下最合适的方案。
    #技术选型

    核心组件方面,Docker和Kubernetes是当下最成熟的开源容器技术。我们对强隔离没有太多的需求,所以没有使用KVM等虚拟机方案,直接在裸机上部署Kubernetes。

    分布式存储方面,容器化的项目大多是无状态的云原生应用,没有分布式存储的需求。极少数项目需要分布式存储的场合,我们会把已有的MFS集群挂载到宿主机,由宿主机挂载到容器里提供简单的分布式存储。

    容器本地数据卷方面,使用Docker默认的OverlayFS 2。我们服务器操作系统主要是CentOS,DeviceMapper在生产环境必须使用direct-lvm模式,该模式需要独立数据设备,对已有的SA自动化管理有一些影响。而OverlayFS 2在Linux内核4.17以上已经比较稳定,也不需要太多复杂的配置,开箱即用。

    日志收集方面,我们已有一套基于ELK的收集方案,对应用日志也有一定约束(必须将日志打印到指定目录下)。传统的基于控制台输出的Docker日志方案需要修改应用的日志输出配置,并且海量的控制台日志输出也会影响dockerd的性能,所以我们通过挂载日志数据盘的方式即可解决问题。

    监控方面,原有的监控设施是Zabbix,但在Kubernetes监控设施上Zabbix的方案显然没有亲儿子Prometheus成熟和开箱即用。所以在Kubernetes的监控方面,我们以Prometheus+Granfana为核心,使用kube-state-metrics采集Kubernetes运行数据。相比于Heapster,kube-state-metrics是Kubernetes生态的一部分,从Kubernetes的资源角度去采集数据,维度更多,信息更全面。

    最后是比较重要的Kubernetes网络方面,我们使用了比较新的网络方案kube-router。kube-router是基于三层Routing和BGP的路由方案,其优点如下:

    * 比Flannel等在数据包上再封装一层通信协议(常见是VXLAN)的网络实现性能上更优秀。
    * 比同样是基于BGP和三层路由的Calico来说更轻量简单,易于部署。
    * Macvlan技术会使宿主机网络和Pod网络隔离,不太符合我们的需求。
    * 在开启Service Proxy模式后可以取代默认组件kube-proxy,Service Proxy的实现是IPVS,在性能上和负载均衡策略上灵活度更高(在Kubernetes 1.8后kube-proxy也有IPVS的实现支持,但到现在还是实验性质)

    当然kube-router也存在一些不足:

    * 项目比较新,现在最新的还是v0.2.5,使用过程=踩坑。
    * 节点间网络必须二层可达,不像Calico提供了IPIP的解决方案。
    * 依赖于iptables,网络要求高的场景Netfilter本身会成为瓶颈。
    * 对于Pod IP的分配,Pod之间网络的ACL实现较为简单,无法应付安全要求高的场景。

    基于三层路由的CNI解决方案:
    1.jpg

    #业务落地实践

    搭好Kubernetes只是一个开始,我们这次重构有个很重要的目标是尽可能让业务开发方无感知无修改地把项目迁移到Kubernetes上,并且要保证实例部署和容器部署同时并行过度。

    理想的项目应该有Dockerfile声明自己的运行环境,有Jenkinsfile解决编译打包,有对应的Deployment和Service来告诉Kubernetes如何部署,但现实很骨干,我们有上千个项目,对应上千个Jenkins编译打包项目,逐一地修改显然不太现实。自动化运维的前提是标准化,好在项目规范比较严谨,符合了标准化这个充分条件。

    重新设计后的部署流程如下图所示:
    2.jpg

    构建方面,项目统一使用同一个Dockerfile模板,通过变更基础镜像来解决一些不同环境项目(比如需要使用JDK 7)的问题。基于Kubernetes Job和dind技术,我们开发了一个构建worker来实现从Jenkins拉取编译后的应用包并打包成镜像的流程,这样Jenkins打出来的应用可以同时用在实例部署和容器部署上。在运维后台上即可完成版本的构建:
    3.jpg

    部署方面,项目的部署配置分成两方面。资源配置一般不经常修改,所以仅仅只是在运维平台上修改记录。经常变更的版本变更和实例数变更则与部署操作绑定。将Kubernetes复杂的对象封装成扩展成原有项目对象的资源配置参数,执行部署时,根据项目资源配置,版本和实例数生成对应的Deployment和Service,调用Kubernetes API部署到指定的Kubernetes集群上。如果项目有在运维平台上使用静态配置文件,则使用ConfigMap存储并挂载到应用Pod里。
    4.jpg

    5.jpg

    在运维平台上提供Pod列表展示,预发环境debug应用,灰度发布,状态监控和webshell,方便开发观察应用运行情况,调试和日志查看,同时也避免开发SSH到生产环境服务器上,回收了服务器权限。
    6.jpg

    在应用从实例部署迁移到容器部署的过程中主要遇到以下几个问题:

    * Kubernetes集群内的Pod和集群外业务的通信问题。为了风险可控,实例部署和容器部署之间将会存在很长一段时间的并行阶段,应用方主要使用Dubbo做微服务治理,Kubernetes集群内的Pod和集群外业务的通信就成为问题了。kube-router是基于三层Routing实现,所以通过上层路由器指定Pod IP段的静态路由,或对接BGP动态交换路由表来解决问题。
    * JVM堆内存配置问题导致OOMKill的问题。因为JVM的内存不止有Xmx配置的堆内存,还有Metaspace或PermSize,以及某些如Netty等框架还有堆外内存,把Xmx的配置等同于容器内存配置几乎是一定会出现OOMKiil,所以必须放宽容器内存限制。以我们的经验来说,容器内存比Xmx多20%左右一般可以解决问题,但也有部分例外,需要额外配置。
    * Pod启动失败难以排查的问题。有一些Pod一启动就失败,输出的日志难以分析问题,我们构建和部署的描述文件都是运维平台动态生成的,很难使用传统docker run目标镜像的方式进行调试,所以我们在运维平台上提供了debug容器的功能,新建一个和原有deployment一样的debug部署,仅去掉健康检查相关的参数和修改command参数使pod运行起来,业务开发方即可通过webshell控制台进入Pod调试应用。

    #未来


    1. 开发经常需要使用的一些调试工具比如Vim,Arthas之类的,现在我们是打包到基础镜像中提供,但这样不仅增加了镜像的体积,而且需要重新打包新的镜像。目前看到比较好的解决方案是起一个调试用的容器并加到指定Pod的namespace中,但还需二次开发集成到webshell中。
    2. 跨机房Kubernetes集群调度。当现有资源无法满足峰值需求时,借助公有云来扩展系统是比较好的选择,我们希望借助Kubernetes多集群调度功能做到快速扩容到公有云上。
    3. 峰值流量的自动扩容和缩容,Kubernetes提供的HPA策略较为简单,我们希望能从更多的维度来计算扩容和缩容的数量,做到精准的控制。

    #Q&A

    Q:容器的Pod网络和外部网络全部打通吗,如何实现的?
    A:因为kube-router是基于三层路由,所以只要在顶层交换上指定Pod IP的静态路由即可,比如宿主机是192.168.0.1,该宿主机上的pod ip range是10.0.0.1/24,那只要在交换机或需要访问Pod的外部主机上添加路由10.0.0.1/24 via 192.168.0.1 ...即可。

    Q:你们如何去保证io的隔离?
    A:目前网络和硬盘的io没有做隔离,暂时还没有这方面的刚需。kube-router对网络IO这方面控制比较弱。硬盘IO方面Docker支持IOPS控制,但Kubernetes我还不太清楚是否支持。

    Q:Job和dind如何配合去实现打包镜像的呢?
    A:首先是dind技术,通过挂载宿主机的docker client和docker sock,可以实现在容器内调用宿主机的Docker来做一些事情,这里我们主要就用于build。Kubernetes的Job则是用于执行这个构建worker的方式,利用Kubernetes的Job来调度构建任务,充分利用测试集群的空闲资源。

    Q:从宿主机部署直接跨步到Kubernetes部署,不仅需要强力的power来推动,在落地实施的过程中,应该也会经历应用架构上的调整,能阐述你们在这方面遇到的困难和解决方式吗?
    A:Pod网络是最大的痛点,解决方式如文中所说。架构方面我们很早就是微服务去中心化的部署,API网关,服务注册发现等也是在容器化之前就已经完成改造的,所以应用架构反倒是没遇到多大的难题。

    Q:你们Kubernetes里面 统一配置是用的ConfigMap还是集成了第三方工具,例如Disconf。你们在Kubernetes中,APM用的是什么呢?Pinpoint还是Sky还是Jeager?还是其他?
    A:过去的项目配置文件是放运维平台上的,所以只要ConfigMap挂进去就可以了。后来新的项目开始采用携程的Apollo,Kubernetes上就只要通过ENV把Apollo的一些相关敏感信息传给Pod即可。APM方面因为我们是Java栈所以使用的skywalking,也是开篇提到的Java agent技术。

    Q:镜像仓库为什么选用Harbor,选型上有什么考虑?
    A:Harbor主要有UI方便管理,相对来说也容易部署和使用,尤其是权限管理这方面。

    Q:Macvlan和IPvlan性能非常好,几乎没有损耗,但默认都是容器和宿主机网络隔离的,但是也有解决方案,你们这边是没有考虑还是使用了一些解决方案发现有问题又放弃的?如果是后者,有什么问题让你们选择放弃?
    A:Macvlan之类的方式需要交换机层面上做一些配置打通VLAN,并且性能上并不会比基于三层的解决方案要高非常多,权衡之下我们还是选择比较易用的基于三层的方案,甚至为了易用而选择了更为激进的kube-router。

    Q:容器的多行日志收集如何解决?或者是,很多业务日志需要上下文关系,但是ELK只能查询到单条,这种情况怎么处理呢?
    A:容器多行日志的问题只存在于标准输出里,我们应用的日志是输出到指定目录下的,Filebeat有一些通用的多行日志解决方案。因为日志是存放在ES里的,所以可以通过调ES接口拿到某个Pod一个时间段里的日志,在UI上把它展示出来即可。

    Q:请问用的存储是什么?如何集成的?存储架构是怎样的?有考虑过Ceph吗?
    A:只有极少部分项目需要接分布式存储,并且对存储的管理,IOPS限制等没有硬性要求,所以我们把已有的MFS集群挂载到宿主机,再挂到容器里来实现。

    Q:Jenkins的Slave是用Pod Template创建的吗?Slave是Job共享还是需要时自动创建?
    A:Jenkins还是传统的master-slave单机部署模式,因为版本比较旧连Kubernetes Slave都不支持,所以我们只是调用了Jenkins的API来完成这个部署的过程。

    Q:Kubernetes在做存储挂载的时候,怎么保证容器漂移依然可以读取到共享存储?
    A:MFS挂载的话,文件是写入到MFS集群中的,那么挂载同样的MFS即可读到同一个文件。

    Q:关于命名空间,这边有哪些应用场景呢?
    A:按部门和场景区分ns,按ns分配节点和资源。未来打算基于ns做网络上的隔离和控制。

    Q:请问镜像大小是否有做优化?生产中有用alpine之类的base镜像吗?
    A:暂时没有,我们镜像的大小大约在100-300M之间。而且比起镜像大小的优化,运行环境的稳定和调试的便利更为重要。镜像有分层的策略,即使是很大的镜像,只要每次版本部署时更新的是最底层的镜像就不会导致每次都要拉取完整镜像。

    以上内容根据2019年4月29日晚微信群分享内容整理。分享人陈偲轶,荔枝研发中心DevOps工程师,负责运维平台的设计和开发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零八):华尔街见闻Istio生产实践

    尼古拉斯 发表了文章 • 0 个评论 • 750 次浏览 • 2019-04-24 18:41 • 来自相关话题

    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, ...查看全部
    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, 同时带来了智能路由、服务熔断、错误注入等重要的特性。整个搭建过程中也遇到了很多坑和经验,希望和大家分享。

    见闻开发团队以Golang为主,同时存在Python,Java服务,这就需要SRE提供更容易接入的微服务基础组件,常见的方案就是为每种语言提供适配的微服务基础组件,但痛点是基础组件更新维护的成本较高。

    为了解决痛点,我们将目光放到服务网格,它能利用基础设施下沉来解决多语言基础库依赖的问题,不同的语言不需要再引入各种不同的服务发现、监控等依赖库,只需简单的配置并运行在给定的环境下,就能享有服务发现、流量监控、链路追踪等功能,同时网络作为最重要的通信组件,可以基于它实现很多复杂的功能,譬如智能路由、服务熔断降级等。

    我们调研了一些服务网格方案,包括Istio、Linkerd。

    对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构可行性讨论,我们选择Istio作为实践方案。

    服务网格架构图:
    1.png

    这张图介绍了华尔街见闻典型的服务网格架构,左半图介绍了用户请求是如何处理,右半图介绍运维系统是如何监控服务。
    #架构可行性
    通过架构图,我们拆分出以下关键组件,经过评估,Istio高度模块化、可拓展,各个组件的可用性、拓展性都有相应的策略达到保障,我们认为Istio是具有可实施性的。
    ##Istio Ingress高性能,可拓展

    性能:Istio Ingress用来处理用户入流量,使用Envoy实现,转发性能高。


    可用性:保证实例数量并使用服务探活接口保证服务可用性。


    拓展性:挂载在负载均衡后,通过增加实例实现可拓展。


    ##Istio Proxy随应用部署,轻微性能损耗,可随应用数量拓展

    Istio Proxy以Sidecar形式随应用一起部署,增加2次流量转发,存在性能损耗。

    
性能:4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)

    
单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。 通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。


    CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量解决该问题,经过测试环境测试,延时可以接受。 


    可用性:基于Envoy,我们认为Envoy的可用性高于应用。依赖Pilot Discovery进行服务路由,可用性受Pilot Discovery影响。


    拓展性:Sidecar形式,随应用数拓展。


    ##Istio Policy服务可拓展,但同步调用存在风险

    Istio Policy需要在服务调用前访问,是同步请求,会增加服务调用延时,通过拓展服务数量增加处理能力。属于可选服务,华尔街见闻生产环境未使用该组件。 


    性能:未测试。


    可用性:若开启Policy,必须保证Policy高可用,否则正常服务将不可用。


    拓展性:增加实例数量进行拓展。

    ##Istio Telemetry监控收集服务
    
性能:从监控上观察Report 5000qps,使用25核,响应时间p99在72ms。异步调用不影响应用的响应时间。


    可用性:Telemetry不影响服务可用性。


    拓展性:增加实例数量进行拓展。

    ##Pilot Discovery

    性能:服务发现组件(1.0.5版本)经过监控观察,300个Service,1000个Pod,服务变更次数1天100次,平均CPU消耗在0.04核左右,内存占用在1G以内。


    可用性:在服务更新时需要保证可用,否则新创建的Pod无法获取最新路由规则,对于已运行Pod由于Proxy存在路由缓存不受Pilot Discovery关闭的影响。


    拓展性:增加实例数量可以增加处理量。
    #服务监控
    Istio通过mixer来搜集上报的遥测数据,并自带Prometheus、Grafana等监控组件,可方便的对服务状态进行监控。见闻目前监控在用Istio自带的,利用Prometheus拉取集群指标,经由Grafana看板展示,可直观展示出各种全局指标及应用服务指标,包括全局QPS、全局调用成功率、各服务延时分布情况、QPS及状态码分布等, 基本满足监控所需。

    存储暂未做持久化操作,采用Prometheus的默认的内存存储,数据的存储策略可通过设定Prometheus的启动参数`--storage.tsdb.retention`来设定,见闻生产时间设定为了6h。

    Prometheus服务由于数据存储原因会消耗大量内存,所以在部署时建议将Prometheus部署在专有的大内存node节点上,这样如果内存使用过大导致该node节点报错也不会对别的服务造成影响。Istio mixer担负着全网的遥测数据搜集任务,容易成为性能瓶颈,建议和Prometheus一样将其部署在专有节点上, 避免出现问题时对应用服务造成影响。

    以下是Mesh汇总面板的Demo:
    2.jpg

    Service Mesh汇总监控页面
    #链路追踪
    Envoy原生支持分布式链路追踪, 如Jaeger、Zipkin等,见闻生产选择了Istio自带的Jaeger作为自身链路追踪系统。 默认Jaeger服务为all_in_one形式,包含jaeger-collect、jaeger-query、jaeger-agent等组件,数据存储在内存中。

    见闻测试集群采用了此种部署形式。见闻生产环境基于性能与数据持久性考虑,基于Cassandra存储搭建了单独的jaeger-collect、jaeger-query服务。Jaeger兼容Zipkin,可以通过设定Istio ConfigMap中的`zipkinAddress`参数值为jaeger-collector对应地址,来设置Proxy的trace上报地址。同时通过设置istio-pilot环境变量`PILOT_TRACE_SAMPLING`来设置tracing的采样率,见闻生产采用了1%的采样率。

    Tracing不像Istio监控系统一样对业务代码完全无侵入性。Envoy在收发请求时若发现该请求没有相关trace headers,就会为该请求自动创建。tracing的实现需要对业务代码稍作变动,这个变动主要用来传递trace相关的header,以便将一次调用产生的各个span串联起来。

    见闻底层采用 grpc 微服务框架,在Gateway层面将trace header 加入到入口grpc调用的context中,从而将trace headers 逐级传递下去。
    3.jpg

    #Istio探活
    Istio通过向Pod注入Sidecar接管流量的形式实现服务治理,那么就会有Sidecar与业务容器状态不同步的可能,从而造成各种的调用问题,如下两方面:

    * Sidecar就绪时间晚于业务容器:业务容器此时若发起调用,由于Sidecar还未就绪, 就会出现类似no healthy upstream之类错误。若此时该Pod接收请求,就又会出现类似upstream connect error错误。
    * Sidecar就绪时间早于业务容器:例如某业务容器初始化时间过长导致Kubernetes误以为该容器已就绪,Sidecar开始进行处理请求,此时就会出现类似upstream connect error错误。

    4.jpg

    5.jpg

    探活涉及Sidecar、业务容器两部分,只有两部分同时就绪,此Pod才可以正式对外提供服务,分为下面两方面:

    * Sidecar探活:v1.0.3及以上版本针对Pilot-agent新增了一个探活接口healthz/ready,涉及statusPort、applicationPorts、adminPort等三个端口。其基本逻辑为在statusPort上启动健康监测服务,监听applicationPorts设定的端口是否至少有一个成功监听,之后通过调用本地adminPort端口获取xDS同步状态。若满足至少一个applicationPort成功监听,且CDS、LDS都进行过同步,则该Sidecar才被标记为Ready。
    * 业务容器探活:见闻通过基本库,在所有Golang grpc后端服务中注册了一个用于健康检查的handler, 该handler可由开发人员根据自身业务自定义,最后根据handler返回值来判断业务容器状态(如下图)。

    6.jpg

    #Istio应用更新
    为了实现灰度部署,见闻基于Kubernetes Dashboard进行了二次开发,增加了对Istio相关资源的支持,利用Gateway、VirtualService、DestinationRule等crd实现了应用程序的灰度部署,实际细节如下:

    1. 更新流量控制将流量指向已有版本
,利用VirtualService将流量全部指向v1版本(见下动图)。
    2. 部署新版本的Deployment
,查找旧的Deployment配置,通过筛选app标签符合应用名的Deployment,运维人员基于该Deployment创建v2版本的Deployment,并向destinationRule中增加v2版本。
    3. 更新流量控制将流量指向新版,本
利用VirtualService将ServiceA的服务流量全部指向v2版本。
    4. 下线老版本的Deployment并删除对应DestinationRule。

    利用Istio Dashboard来实现上述流程:
    07.gif

    为了方便开发人员服务部署,开发了精简版后台,并对可修改部分进行了限定。最终,SRE提供两个后台,对Istio Dashboard进行精细控制:
    08.gif

    #实践中的宝贵经验
    在Istio实践过程中,有哪些需要注意的问题。
    ##API server的强依赖,单点故障
    
Istio对Kubernetes的API有很强的依赖,诸如流量控制(Kubernetes资源)、集群监控(Prometheus通过Kubernetes服务发现查找Pod)、服务权限控制(Mixer Policy)。所以需要保障API server的高可用,我们曾遇到Policy组件疯狂请求Kubernetes API server使API server无法服务,从而导致服务发现等服务无法更新配置。


    
* 为避免这种请求,建议使用者了解与API server直接通信组件的原理,并尽量减少直接通信的组件数量,增加必要的Rate limit。
    

* 尽量将与API server通信的服务置于可以随时关闭的环境,这是考虑如果部署在同一Kubernetes集群,如果API server挂掉,无法关闭这些有问题的服务,导致死锁(又想恢复API server,又要依靠API server关闭服务)。

    ##服务配置的自动化
    
服务配置是Istio部署后的重头戏,避免使用手动方式更改配置,使用代码更新配置,将常用的几个配置更新操作做到运维后台,相信手动一定会犯错。
    ##关于Pilot Discovery

    Pilot Discovery 1.0.0版本有很大的性能问题,1.0.4有很大的性能提升,但引入了一个新bug,所以请使用1.0.5及以上的版本,我们观察到CPU消耗至少是1.0.0版本的1/10,大大降低了Proxy同步配置的延时。
    ##关于Mixer Policy 1.0.0

    这个组件曾导致API server负载过高(很高的list pods请求),所以我们暂时束之高阁,慎用。
    ##性能调优

    在使用Proxy、Telemetry时,默认它们会打印访问日志,我们选择在生产上关闭该日志。
时刻观察Istio社区的最新版本,查看新版本各个组件的性能优化以及bug修复情况,将Istio当做高度模块化的系统,单独升级某些组件。上面就提到我们在Istio1.0的基础上使用了1.0.5版本的Policy、Telemetry、Pilot Discovery等组件。
    ##服务平滑更新和关闭
    
Istio依靠Proxy来帮助APP进行路由,考虑几种情况会出现意外的状态:

    
* APP启动先于Proxy,并开始调用其它服务,这时Proxy尚未初始化完毕,APP调用失败。
    

* Service B关闭时,调用者Service A的Proxy尚未同步更新Service B关闭的状态,向Service B发送请求,调用失败。
第一种情况要求APP有重试机制,能适当重试请求,避免启动时的Proxy初始化与APP初始化的时差,Istio提供了retry次数配置,可以考虑使用。 
第二种情况,一种是服务更新时,我们使用新建新服务,再切流量;一种是服务异常退出,这种情况是在客户端重试机制。希望使用Istio的开发人员有更好的解决方案。

    #Q&A
    Q:学Service Mesh什么用?
    A:Service Mesh是最近比较火的一个概念,和微服务、Kubernetes有密切关系。出于以后业务发展需要,可以学习下, 增加知识储备。见闻上Istio的主要目的在文章已说明,主要是基础服务的下沉,解决了语言兼容性问题, 还有一个就是灰度发布。

    Q:链路追踪的采集方式是怎样的,比如Nodejs,C++等多语言如何支持的?
    A:Envoy本身支持链路追踪,也就是将Envoy会在请求head中增加链路追踪相关的数据头,比如x-b3-traceid,x-b3-spanid等等。业务要做的就是将这些head沿着调用链路传递下去即可。所以多语言的话需要在自己的业务侧实现该逻辑。所以Istio的链路追踪对业务代码还是有很小的侵入性的(这个分享中有说明)。

    Q:Istio与Spring Cloud两者的优缺点与今后的发展趋势会是怎么样?
    A:见闻技术栈是Golang,所以没太认真对比过两者。从社区活跃度上将,Istio > Spring Cloud,稳定性方面,Spring Cloud是更有优势,更适合Java沉淀较深的企业。但个人感觉对于更多企业来讲,跨越了语言兼容性的Istio未来发展很值得期待。

    Q:Docker镜像部署可以做到代码保护吗,比如像Nodejs这种非二进制执行程序的项目?
    A:代码保护可以通过将镜像上传至指定云服务商上的镜像仓库中,见闻是将自己的业务镜像提交保存在了腾讯云。如果镜像泄露,那么非二进制执行程序的代码还是有泄露风险的。

    Q:选型时为什么没考虑Linkerd?
    A:选型之初也调研了Linkerd, 对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构相较于见闻有较大可行性,所以选择了Istio作为实践方案。

    Q:Istio在做运维部署时没有UI工具,你们如何实现运维人员更加便捷地使用?
    A:见闻基于Kubernetes官方的Dashboard, 对内容进行了扩充,增加了对Gateway,VirtualService等Istio crd资源的支持, 同时增加了灰度部署等和见闻运维业务相关的功能,所以一定程度上解决了运维部署的问题。

    Q:流量从Sidecar代理势必会对请求响应时间有影响,这个有没有更详细案例的说明性能上的损耗情况?
    A:Sidecar的转发其实带来了性能一定的性能损耗。4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。 CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量缓解该问题,经过测试环境测试,延时可以接受。

    Q:Sidecar在生产中资源占用为多少?是否会对集群资源占用很多?
    A:以单个Pod为例,见闻业务单个Pod中Sidecar所占资源约占整个Pod所耗资源的1/10。

    Q:Jeager你们是进行了代码埋点吗?更为底层代码级别的追踪,有用其他方案吗?
    A:Envoy本身对tracing有良好的支持,所以业务端所做的改动只需将其所产生的追踪数据延续下去即可。上Istio之前,见闻在相关微服务中通过在基础库中增加链路追踪逻辑(Zipkin)实现了链路追踪,不过只做了Golang版,多语言兼容开发运维难度较大。

    Q:相信咱们的mixer在生产环境中,也出现过瓶颈,咱们从哪几个大方向优化的?如何优化的?方面讲解一下吗?
    A:mixer见闻在生产过程中所遇的坑在于Policy组件, 会疯狂的list pod,导致API server负载骤增,之后见闻基于自身业务关闭了Policy。

    Q:Istio组件实现了高可用么?
    A:Istio本身也是基于Kubernetes,所以可用性还是有较好保证的。

    以上内容根据2019年4月23日晚微信群分享内容整理。分享人张安伟,华尔街见闻SRE团队运维工程师,负责公司日常运维开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零七):瓜子云平台的实践经验

    齐达内 发表了文章 • 0 个评论 • 952 次浏览 • 2019-04-17 15:38 • 来自相关话题

    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结 ...查看全部
    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结出了一些云平台建设的实践和经验。

    这篇文章和大家分享下瓜子云平台的一些实践经验。瓜子是在 2017 年年中启动云平台项目的,当时有如下背景:

    * 技术栈多样化,PHP、Java、Go、Python 都有使用,但只有 PHP 建立了相对统一的部署流程
    * 业务迭代速度快,人员扩张速度快,再加上微服务化改造,项目数量激增,基于虚拟机的运维压力很大
    * 测试环境没有统一管理,业务开发人员自行零散维护

    基于此,我们的 0.x 版本,也是原型版本选择了如下的切入点:

    * 在 CI/CD 层面,先定义出标准流程,但是并不涉及细节的规范化,便于用户学习,快速将现有流程套进去
    * 同时支持 image 和 tar 包两种产出,为云上部署和虚拟机部署做好构建路径的兼容,在将来迁移时这部分可以做到几乎无缝
    * 先支持测试环境的部署,在验证平台稳定性的同时,不断收集用户需求进行快速迭代

    #集群核心组件的技术选型
    在服务编排和资源管理方面,Docker 和 Kubernetes 已经比较成熟了,基于容器的微服务化也是大势,再加上我们对强隔离没有诉求,所以跳过了 OpenStack,直接选择了 Kubernetes 方案。

    既然用了 Kubernetes 肯定就要解决跨节点的容器网络通信问题,因为我们是自建机房,没有公有云在网络层面的限制,所以没有考虑应用范围更广但是性能和可调试性较差的 VXLAN 方案。最开始选择的是 Macvlan + 自研 IPAM 的方式,之后转向了方案完成度更高的基于 BGP 协议的 Project Calico。

    Calico 的优点如下:

    * 基于 BGP 协议,转发平面依靠主机路由表,不涉及任何封包解包操作,性能非常接近原生网卡,并且方便抓包调试
    * 组件结构简单,对 Kubernetes 支持很好
    * 可以和 IDC 路由器通过 BGP 协议打通,直接对外广播容器 IP,让集群内外可以通过 IP 直连
    * 有 ACL 能力,类似 AWS 的 Security Group

    当然肯定有缺点:

    * 节点间网络最好是二层可达,否则只能使用 IP-in-IP 这种隧道技术,那就失去大半意义了
    * 因为是基于三层转发的,无法做到二层隔离,安全诉求高的场景不适用
    * 严重依赖 iptables,海量并发情况下 netfilter 本身可能成为瓶颈

    编排和网络解决后,集群就可以拉起来,接下来需要考虑的是流量如何进入,也就是负载均衡器的选型。在不想过多自己开发的前提下,使用 Kubernetes Ingress 是一个比较好的选择,但是不同 Ingress 之间功能和成熟度差异都很大,我们也是尝试了好几种,从 Nginx 到 Linkerd,最终选择了 Istio。我们在 0.2 版本开始用 Istio,初期它的不稳定带来了很多困扰,不过在 1.x 版本发布后相对稳定很多了。选择 Istio 的另一个原因是为将来向 Service Mesh 方向演进积累经验。

    以上一个最小功能可用集群就基本完备了,接下来在 CI/CD 这块,我们封装了一个命令行工具 med,它通过读取项目中的 med.yml,自动生成 dockerfile 和 deployment.yml,之后调用 Docker 进行构建,并提交给 Kubernetes 集群进行部署。

    一个 med.yml 例子如下:
    01.png

    从上面这个例子能看到,我们将构建环节拆分成了 prepare 和 build 两个部分,这个设计其实来源于 dockerfile 的一个最佳实践:由于拉取依赖的环节往往较慢,所以我们习惯将依赖安装和编译过程放到不同的命令中,形成不同的 layer,便于下次复用,提高编译速度。prepare 阶段就是把这部分耗时但是变更不频繁的操作提出来,通过 version 字段控制,只要 version 值不变,prepare 部分就不会再重复执行,而是直接利用之前已经生成好的 prepare 镜像进行接下来的 build 环节。build 环节构建结束后将 image 提交到镜像仓库,等待用户调用 deploy 命令进行部署。

    在实际开发过程中用户提出同一个代码仓库构建多个产出的需求,比如一个 Java 项目同时构建出用于发布的 Web Service 和提交到 Maven 的 jar 包。所以 build 和 deploy 阶段都支持多个产出,通过 name 进行区分,参数方面则支持有限的模板变量替换。

    最后,我们将 med.yml 和 GitLab CI 结合,一个 CI/CD 流程就出来了,用户提交代码后自动触发构建,上传镜像并部署到他的测试环境
    02.png

    #云平台产品化的一些思考
    ##云平台 1.x
    在测试环境跑了一段时间后,大家纷纷尝到了甜头,希望不只是在测试环境使用,生产环境也可以部署。这时简易的客户端工具就不满足需求了,新增的主要诉求围绕着权限管理和灰度发布,同时也希望有一个更加可视化的平台能看到实例运行的状态,上线的进度和监控日志等。需求细化如下:

    * 能直观看到当前项目的运行状态,部署状态
    * 项目有权限控制
    * 发布系统支持预发布和灰度部署
    * 日志收集和监控报警能力
    * 配置管理能力
    * 定时任务支持

    于是经过几轮迭代后,可视化的控制台上线了:
    03.png

    这里可以详细展开下部署环节,为了满足应用上线时的小流量测试需求,最开始是通过用户换个名字手工部署一个新版本,然后灵活调整不同版本部署之间的流量百分比来实现。但是在运行一段时间后,发现太高的灵活度调整非常容易出错,比如忘记了一个小流量部署,导致线上不正常,还很难 debug,并且整个操作过程也很繁琐。于是我们收敛了功能,实现了流程发布功能:
    04.png

    无论是流量调整还是流程发布,Kubernetes 默认的滚动更新都是无法满足需求的。我们是通过先创建一组新版本实例,然后再变更 Istio 的流量切换规则来实现的,示意图如下:
    05.png

    同时为了既有项目平滑上云,我们还提供了容器和虚拟机部署的联合部署,支持一个项目在一个发布流程中同时部署到云平台和虚拟机上。再配合外部 Nginx 权重的跳转,就可以实现业务逐步将流量切换到云上,最终完全去掉虚拟机

    这里有一个小插曲,由于基于 TCP 的 Dubbo 服务流量不经过 Istio 网关,而是通过注册到 ZooKeeper 里面的 IP 来进行流量调整的,上面的流程发布和联合部署对 Dubbo 服务没有意义。我们在这个版本进行了一个网络调整,将集群内部的 BGP 路由和外部打通,允许容器和虚拟机直接通信。由于 Calico 更换网段是要重新创建所有容器的,所以我们选择拉起一个新集群,将所有应用全部迁移过去,切换流量入口,再下掉旧集群。这里就体现了容器的便捷性了,数百个应用的迁移只花了十几分钟就再另一个集群完全拉起,业务几乎没有感知。

    配置管理方面,最开始是通过 env 管理,后来很多应用不太方便改成读取 env 的方式,又增加了基于 ConfigMap 和配置中心的配置文件注入能力。
    06.png

    日志收集方面,最开始使用 ELK,只收集了容器的 stdout 和 stderr 输出,后来对于放在指定位置的文件日志也纳入了收集目标中。由于 Logstash 实在太耗资源,我们直接使用 ES 的 ingest 能力进行日志格式化,去掉了中间的 Kafka 和 Logstash,从 Filebeat 直接输出到 ES。当然 ingest pipeline 本身调试起来比较复杂,如果有较多的日志二次处理需求,不建议使用。日志展示方面,Kibana 原生的日志搜索能力已经比较强大,不过很多人还是喜欢类似 tail -f 的那种查看方法,我们使用了 Kibana 的第三方插件 Logtrail 进行了模拟,提供了一键从控制台跳转到对应容器日志查看界面的能力。

    监控收集和展示,也是标准的 Prometheus + Grafana,在收集 Kubernetes 暴露出来的性能指标的同时,也允许用户配置自定义监控的 metric url,应用上线后会自动抓取。报警方面,因为 Prometheus 自带的 Alert Manager 规则配置门槛比较高,所以我们开发了一个用于规则配置的项目 NieR,将常用规则由运维同学写好后模板化,然后再提供给用户订阅,当然用户也可以自行建立自己的模板。监控报警系统展开说能再写一篇文章了,这里就只放一下架构图:
    07.png

    ##云平台 2.x
    在 1.x 版本迭代的时候,我们发现,早期为了给用户最大灵活性的 med.yml 在用户量持续增长后带来的培训、运维成本越来越高。每一个第一次接触平台的同事都要花费半天的时间阅读文档,然后在后续的使用中还有很多文档没有描述清楚的地方要搞明白,变相提高了项目上云的门槛。另外这种侵入用户代码仓库的方式,每次调整代价都非常大,服务端控制力度也太弱。

    针对上述问题,我们在 2.x 版本彻底去掉了 med.yml,实现了全部 UI 化。这里并不是说把之前的配置文件丢到一个管理页面上就算搞定了。拿构建来说,用户希望的是每种语言有一个标准的构建流程,只需要稍微修改下构建命令就可以直接使用,于是我们定义了语言模板:
    08.png

    然后替用户填好了大部分可以规范化的选项,当然也允许用户自行编辑:
    09.png

    10.png

    在部署层面,除了和构建产出联动外,最大的变动是参数合理化布局,让新用户基本不用看文档就能明白各个参数的用途。
    11.png

    2.x 版本才刚刚起步,后续还有非常多在排期安排的事情,比如在功能方面:

    * 支持多集群部署之后如何做到跨集群调度
    * 如何方便的能让用户快速拉起一套测试环境,乃至于构建自己的内部应用市场
    * 监控系统能不能进一步抽象,直接通过 UI 的方式配置监控模板,能不能自动建议用户合理的监控阈值
    * 给出各个业务的资源利用率和账单

    在基础设施层面:

    * 能不能做到不超售但是还能达成合理的资源利用率
    * 离线计算能不能在低峰期复用在线集群资源,但是不能影响业务
    * ServiceMesh 的进一步研究推广
    * 有状态服务的支持等等

    等等

    以上就是瓜子云平台的整体迭代路径。在整个开发过程中,我们感触最深的一点是,需要始终以产品化而不是做工具的思想去设计和实现。技术是为了需求服务的,而不是反过来,把一个用户最痛的痛点打透比做一百个酷炫的功能有用的多。但打透说起来容易,做起来有很多脏活累活。

    首先在需求分析阶段,基础设施的变更影响非常广泛,在征求大部分人意见的同时,如何引导大家往业界内更先进的路线上演进是要经过深思熟虑的。另外不同阶段的需求也不尽相同,不要一窝蜂的追随技术潮流,适合当前阶段的才是最好的技术选型。

    其次切入点最好是选择共识基础好,影响范围大的需求点,阻力小,成果明显。待做出成果后再一步步扩展到分歧比较严重的深水区。

    最后落地的时候要做好技术运营,做好上线前的宣传培训,帮助用户从旧系统尽量无痛的进行迁移。上线后的持续跟踪,通过数据化的手段,比如前端埋点,核心指标报表等手段观察用户的使用情况,不断调整策略。

    上面这些在团队小的时候可能都不是问题,一个沟通群里直接就能聊清楚。但当团队变大后,核心功能上一个不当的设计往往带来的就是上千工时的白白消耗甚至造成线上事故,一个云平台产品能不能落地,技术架构和实现是一方面,上面这些产品和运营策略是否运用得当也是非常重要的。
    #Q&A
    Q:请问瓜子私有云是一朵独立的云还是多云部署?如果是多云部署,云间网络是采用的什么技术?如何打通多云之间的网络的?谢谢
    A:我们在设计之初就考虑多集群 / 多 IDC 部署的,这也是选择 Calico 的一个原因。通过 BGP 协议将容器 IP 广播出去后,云间互联和普通虚拟机互联没有区别,当然这需要网络设备支持。

    Q:云平台在 PaaS 层,采用的编排工具是什么,如何打通容器之间的部署,在容器的架构上是怎么实现负载均衡的?
    A:采用的是 Kubernetes,打通使用的是 Calico,负载均衡使用的是 Istio Ingress。

    Q:新版本实例发布的时候怎么切Istio才能保障灰度的流量不丢失呢?
    A:在流程发布里面,我们相当于先新建一组新的实例,在它们的 Readiness 检查通过后,切换 Istio 规则指向新实例做到的。

    Q:稳定性方面有没有出现过比较大的问题,怎么解决的?
    A:有两个时期稳定性故障较多,一个是 Istio 版本比较低的时候,这个只能说一路趟坑趟过来,我们甚至自己改过 Istio 代码,现在的版本已经没出过问题了;一个是集群用的太狠,当集群接近满载时,Kubernetes 会出现很多连锁问题,这个主要是靠监控来做及时扩容。

    Q:自建机房的话为什么不接着使用 Macvlan + IPAM 方案呢?是为了之后上公有云做准备吗?
    A:当时面临一个本机 Macvlan 容器互相不通的问题,再加上有熟悉的团队已经在生产跑了很久 Calico 了,所以就直接换到了 Calico。

    Q:如果服务发现是基于 Dubbo + ZooKeeper,那 Kubernetes 自身的 Service 还有在使用吗?
    A:Kubernetes 自己的 Service 我们现在内部管理工具在使用,在 2.x 版本也开始开放给业务使用了(文中截图能看到内部域名)。

    Q:请问几秒的时延对一些高效的服务来讲也是不可接受的。咱们有想过通过何种方式去避免灰度的流量损失问题么?
    A:还真没遇到过这个需求。我理解如果有一个服务如此关键,那么在整个流量变更环节(从机房入口开始)到灰度策略上都得仔细考虑。如果接受不了 Istio 这个延时,一个思路是完全抛弃 Istio Ingress,直接使用一个切换迅速的负载均衡器。因为容器 IP 是直通的,完全可以从集群外直接连进来,只要解决服务发现的问题就行。

    Q:应用服务追踪怎么处理的?对接Istio?
    A:语言栈比较多的情况下这是个痛点,目前我们也是在尝试,即使是 Sidecar 也不能完全对业务没侵入。公司内部 Java 技术栈更喜欢 Skywalking 这种完全无侵入的方式。

    Q:使用 Istio 时,怎么解决性能损坏问题的?
    A:目前还没有启用 Mixer 这些对性能影响比较大的组件,所以目前性能损耗还是比较小的。如果对性能有严格的要求,我们会建议他使用 service name 做直连。

    Q:Prometheus 的告警是靠编辑大量的 rule.yml,请问生产中是怎么管理的?规则编辑比较麻烦,怎么解决?Prometheus 是单点,有没有扩容方案?
    A:就是靠 Nier 这个组件,将规则抽象成模板,甚至在云平台上面对于简单的规则直接变成了选项。然后模板渲染成规则。我们的 Prometheus 用的官方的联邦集群模式,存储放在了 Ceph 上面。


    Q:为什么 Kubernetes 默认的滚动更新不能满足要求?哪里有问题?
    A:没办法精细控制灰度粒度,比如部署了 4 个实例,我要求切 10% 的流量灰度,这就做不到了。另外,滚动更新回滚也比较慢。

    Q:注册到 ZooKeeper 的 IP 是 Pod IP?ZooKeeper 从外部直接访问Pod IP 吗?
    A:对的,Pod 和 VM 能直通,也就是说一个 Dubbo 服务能同时部署在 VM 和容器里面。

    Q:目前支撑的生产应用服务规模和云平台的规模能介绍下?包括一些指标,比如多少应用进行灰度更新耗时?
    A:应用规模的话目前超过 1000 了,每个月发布次数超过 10000。灰度更新是用户自行控制整个发布进度的,所以耗时不太有参考意义。

    以上内容根据2019年4月16日晚微信群分享内容整理。分享人高永超(flex),瓜子二手车技术总监,在容器云、DevOps 领域有丰富的架构经验。目前在主导瓜子云平台的研发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零六):容器环境下的持续集成最佳实践

    大卫 发表了文章 • 0 个评论 • 1000 次浏览 • 2019-04-03 11:49 • 来自相关话题

    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semanti ...查看全部
    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    云原生(Cloud Native)是伴随的容器技术发展出现的的一个词,最早出自 Pivotal 公司(即开发了 Spring 的公司)的一本技术小册子 Migrating to Cloud-Native Application Architectures, 其中定义了云原生应用应当具备的一些特质,如无状态、可持续交付、微服务化等。随后云原生概念被广为引用,并围绕这一概念由数家大厂牵头,成立了 CNCF 基金会来推进云原生相关技术的发展,主要投资并孵化云原生生态内的若干项目,包括了如 Kubernetes / etcd / CoreDNS 等耳熟能详的重要项目。而这张大大的云原生版图仍然在不断的扩展和完善。

    从个人理解来说,传统的应用由于年代久远,更多会考虑单机部署、进程间通信等典型的“单机问题”,虽然也能工作在容器下,但由于历史包袱等原因,架构上已经很难做出大的调整。而“云原生”的应用大都是从一开始就为容器而准备,很少考虑在单机环境下使用,有些甚至无法脱离容器环境工作;考虑的场景少,从而更轻量,迭代更快。比如 etcd 之于 ZooKeeper, Traefik 之于 Nginx 等,相信只要在容器环境下实现一次同样的功能,就能强烈的体会到云原生应用所特有的便捷之处。

    在 CNCF 的版图下,持续集成与持续交付(Continuous Integration & Delivery)板块一直缺少一个钦定的主角,虽然也不乏 Travis CI、GitLab、Jenkins 这样的知名项目,但最能给人云原生应用感觉的,应该还是 Drone 这个项目,本文将围绕 Drone 结合 GitFlow 及 Kubernetes 介绍一些容器环境下持续集成、持续发布(CI/CD)方面的实践经验。
    #主流 CI/CD 应用对比
    之前我也介绍过基于 Travis CI 的一些持续集成实践。后来经过一些比较和调研,最终选择了 Drone 作为主力 CI 工具。截止本文,团队已经使用 Drone 有 2 年多的时间,从 v0.6 一路用到现在即将发布的 v1.0,虽然也踩了不少坑,但总的来说 Drone 还是可以满足大部分需求,并以不错的势头在完善和发展的。

    下面这张表总结了主流的几个 CI/CD 应用的特点:
    B1.png

    Travis CI 和 CircleCI 是目前占有率最高的两个公有云 CI,易用性上相差无几,只是收费方式有差异。由于不支持私有部署,如果并行的任务量一大,按进程收费其实并不划算;而且由于服务器位置的原因,如果推送镜像到国内,速度很不理想。

    GitLab CI 虽然好用,但和 GitLab 是深度绑定的,我们的代码托管在 GitHub,整体迁移代码库的成本太大,放弃。

    Jenkins 作为老牌劲旅,也是目前市场占有率最高的 CI,几乎可以覆盖所有 CI 的使用场景,由于使用 Java 编写,配置文件使用 Groovy 语法,非常适合 Java 为主语言的团队。Jenkins 显然是可以满足我们需要的,只是团队并非 Java 为主,又已经习惯了使用 YAML 书写 CI 配置,抱着尝鲜的心态,将 Jenkins 作为了保底的选择。

    综上,最终选择 Drone 的结论也就不难得出了,Drone 即开源,又可以私有化部署,同时作为云原生应用,官方提供了针对 Docker、Docker Swarm、Kubernetes 等多种容器场景下的部署方案,针对不同容器场景还有特别优化,比如在 Docker Swarm 下 Drone 是以 Agent 方式运行 CI 任务的,而在 Kubernetes 下则通过创建 Kubernetes Job 来实现,显然充分利用了容器的优势所在,这也是 Drone 优于其他 CI 应用之处。个人还觉得 Drone 的语法是所有 CI 中最容易理解和掌握的,由于 Drone 每一个步骤都是运行一个 Docker 容器,本地模拟或调试也非常容易。

    一句话概况 Drone,可以将其看做是可以支持私有化部署的开源版 CircleCI,并且目前仍然没有看到有其他主打这个定位的 CI 工具,因此个人认为 Drone 是 CI/CD 方面云原生应用头把交椅的有力竞争者。
    #容器环境下一次规范的发布应该包含哪些内容
    技术选型完成后,我想首先演示一下最终的成果,希望能直观的体现出 CI 对自动化效率起到的提升,不过这就涉及到一个问题:在容器环境下,一次发布应该包含哪些内容,其中有哪些部分是可以被 CI 自动化完成的。这个问题虽然每家公司各不相同,不过按经验来说,容器环境下一次版本发布通常包含这样一个 Checklist:

    * 代码的下载构建及编译
    * 运行单元测试,生成单元测试报告及覆盖率报告等
    * 在测试环境对当前版本进行测试
    * 为待发布的代码打上版本号
    * 编写 ChangeLog 说明当前版本所涉及的修改
    * 构建 Docker 镜像
    * 将 Docker 镜像推送到镜像仓库
    * 在预发布环境测试当前版本
    * 正式发布到生产环境

    看上去很繁琐对吗,如果每次发布都需要人工去处理上述的所有内容,不仅容易出错,而且也无法应对 DevOps 时代一天至少数次的发布频率,那么下面就来使用 CI 来解决所有问题吧。
    #CI 流程演示
    为了对 CI 流程有最直观的认识,我创建了一个精简版的 GitHub 项目 AlloVince/drone-ci-demo 来演示完整的流程,同时项目对应的 CI 地址是 cloud.drone.io/AlloVince/drone-ci-demo ,项目自动构建的 Docker 镜像会推送到 docker registry 的 allovince/drone-ci-demo。为了方便说明,假设这个项目的核心文件只有 index.html 一个静态页面。
    ##单人开发模式
    目前这个项目背后的 CI 都已经配置部署好,假设我是这个项目的唯一开发人员,如何开发一个新功能并发布新版本呢?

    * Clone 项目到本地, 修改项目代码, 如将 Hello World 改为 Hello World V2。
    * git add .,然后书写符合约定的 Commit 并提交代码, git commit -m "feature: hello world v2”。
    * 推送代码到代码库git push,等待数分钟后,开发人员会看到单元测试结果,GitHub 仓库会产生一次新版本的 Release,Release 内容为当前版本的 ChangeLog, 同时线上已经完成了新功能的发布。

    虽然在开发者看来,一次发布简单到只需 3 个指令,但背后经过了如下的若干次交互,这是一次发布实际产生交互的时序图,具体每个环节如何工作将在后文中详细说明。
    01.png

    ##多人开发模式
    一个项目一般不止一个开发人员,比如我是新加入这个项目的成员,在这个 Demo 中应该如何上线新功能呢?同样非常简单:

    1. Clone 项目到本地,创建一个分支来完成新功能的开发, git checkout -b feature/hello-world-v3。在这个分支修改一些代码,比如将Hello World V2修改为Hello World V3
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world v3”
    3. 将代码推送到代码库的对应分支, git push origin feature/hello-world
    4. 如果功能已经开发完毕,可以向 Master 分支发起一个 Pull Request,并让项目的负责人 Code Review
    5. Review 通过后,项目负责人将分支合并入主干,GitHub 仓库会产生一次新版本的 Release,同时线上已经完成了新功能的发布。

    这个流程相比单人开发来多了 2 个环节,很适用于小团队合作,不仅强制加入了 Code Review 把控代码质量,同时也避免新人的不规范行为对发布带来影响。实际项目中,可以在 GitHub 的设置界面对 Master 分支设置写入保护,这样就从根本上杜绝了误操作的可能。当然如果团队中都是熟手,就无需如此谨慎,每个人都可以负责 PR 的合并,从而进一步提升效率。
    02.png

    ##GitFlow 开发模式
    在更大的项目中,参与的角色更多,一般会有开发、测试、运维几种角色的划分;还会划分出开发环境、测试环境、预发布环境、生产环境等用于代码的验证和测试;同时还会有多个功能会在同一时间并行开发。可想而知 CI 的流程也会进一步复杂。

    能比较好应对这种复杂性的,首选 GitFlow 工作流, 即通过并行两个长期分支的方式规范代码的提交。而如果使用了 GitHub,由于有非常好用的 Pull Request 功能,可以将 GitFlow 进行一定程度的简化,最终有这样的工作流:
    03.png


    * 以 dev 为主开发分支,Master 为发布分支
    * 开发人员始终从 dev 创建自己的分支,如 feature-a
    * feature-a 开发完毕后创建 PR 到 dev 分支,并进行 code review
    * review 后 feature-a 的新功能被合并入 dev,如有多个并行功能亦然
    * 待当前开发周期内所有功能都合并入 dev 后,从 dev 创建 PR 到 master
    * dev 合并入 Master,并创建一个新的 Release

    上述是从 Git 分支角度看代码仓库发生的变化,实际在开发人员视角里,工作流程是怎样的呢。假设我是项目的一名开发人员,今天开始一期新功能的开发:

    1. Clone 项目到本地,git checkout dev。从 dev 创建一个分支来完成新功能的开发, git checkout -b feature/feature-a。在这个分支修改一些代码,比如将Hello World V3修改为Hello World Feature A
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world feature A"
    3. 将代码推送到代码库的对应分支, git push origin feature/feature-a:feature/feature-a
    4. 由于分支是以 feature/ 命名的,因此 CI 会运行单元测试,并自动构建一个当前分支的镜像,发布到测试环境,并自动配置一个当前分支的域名如 test-featue-a.avnpc.com
    5. 联系产品及测试同学在测试环境验证并完善新功能
    6. 功能通过验收后发起 PR 到 dev 分支,由 Leader 进行 code review
    7. Code Review 通过后,Leader 合并当前 PR,此时 CI 会运行单元测试,构建镜像,并发布到测试环境
    8. 此时 dev 分支有可能已经积累了若干个功能,可以访问测试环境对应 dev 分支的域名,如 test.avnpc.com,进行集成测试。
    9. 集成测试完成后,由运维同学从 Dev 发起一个 PR 到 Master 分支,此时会 CI 会运行单元测试,构建镜像,并发布到预发布环境
    10. 测试人员在预发布环境下再次验证功能,团队做上线前的其他准备工作
    11. 运维同学合并 PR,CI 将为本次发布的代码及镜像自动打上版本号并书写 ChangeLog,同时发布到生产环境。

    由此就完成了上文中 Checklist 所需的所有工作。虽然描述起来看似冗长,但不难发现实际作为开发人员,并没有任何复杂的操作,流程化的部分全部由 CI 完成,开发人员只需要关注自己的核心任务:按照工作流规范,写好代码,写好 Commit,提交代码即可。

    接下来将介绍这个以 CI 为核心的工作流,是如何一步步搭建的。
    #Step by Step 构建 CI 工作流
    ##Step.0:基于 Kubernetes 部署 Drone v1.0.0
    以 GitHub 为例,截止本文完成时间(2019 年 3 月 28 日), Drone 刚刚发布了第一个正式版本 v1.0.0。官方文档已经提供了分别基于 Docker、Kubernetes 的 Drone 部署说明,不过比较简略,因此这里给出一个相对完整的配置文件。

    首先需要在 GitHub 创建一个 Auth App,用于 repo 的访问授权。应用创建好之后,会得到 Client ID 和 Client Secret 。同时 Authorization callback URL 应填写 Drone 服务对应域名下的 /login,如 https://ci.avnpc.com/login。

    Drone 支持 SQLite、MySQL、Postgres、S3 等多种后端存储,主要用于记录 build logs 等文本信息,这些信息并不是特别重要,且我们的 CI 有可能做迁移,因此个人更推荐使用 SQLite。

    而在 Kubernetes 环境下,SQLite 更适合用挂载 NAS 的方式供节点使用,因此首先将存储的部分独立为文件 drone-pvc.yml,可以根据实际情况配置 nfs.path 和 nfs.server:
    kubectl apply -f drone-pvc.yaml

    Drone 的配置主要涉及两个镜像:

    * drone/kubernetes-secrets 加密数据服务,用于读取 Kubernetes 的 secrets
    * drone/drone:1.0.0-rc.6 就是 Drone 的 server 端,由于在 Kubernetes 下 Drone 利用了 Job 机制,因此不需要部署 Agent。

    这部分配置较长,可以直接参考示例 drone.yaml

    主要涉及到的配置项包括:

    * Drone/kubernetes-secrets 镜像中

    * SECRET_KEY:数据加密传输所用的 key,可以使用 openssl rand -hex 16 生成一个

    * Drone/Drone镜像中

    * DRONE_KUBERNETES_ENABLED:开启 Kubernetes 模式
    * DRONE_KUBERNETES_NAMESPACE:Drone 所使用的 Namespace, 这里使用 default
    * DRONE_GITHUB_SERVER:GitHub 服务器地址,一般为 https://github.com
    * DRONE_GITHUB_CLIENT_ID:上文创建 GitHub Auth App 得到的 Client ID
    * DRONE_GITHUB_CLIENT_SECRET:上文创建 GitHub Auth App 得到的 Client Secret
    * DRONE_SERVER_HOST:Drone 服务所使用的域名
    * DRONE_SERVER_PROTO:http 或 https
    * DRONE_DATABASE_DRIVER:Drone 使用的数据库类型,这里为 SQLite3
    * DRONE_DATABASE_DATASOURCE:这里为 SQLite 数据库的存放路径
    * DRONE_SECRET_SECRET:对应上文的 SECRET_KEY
    * DRONE_SECRET_ENDPOINT:加密数据服务的地址,这里通过 Kubernetes Service 暴露,无需修改

    最后部署即可:
    kubectl apply -f drone.yaml

    部署后首次登录 Drone 就会跳转到 GitHub Auth App 进行授权,授权完毕后可以看到所有能读写的 Repo,选择需要开启 CI 的 Repo,点击 ACTIVATE 即可。 如果开启成功,在 GitHub Repo 的 Settings > Webhooks 下可以看到 Drone 的回调地址。
    ##Step.1:Hello World for Drone
    在正式开始搭建工作流之前,首先可以测试一下 Drone 是否可用。Drone 默认的配置文件是 .drone.yml, 在需要 CI 的 repo 根目录下创建.drone.yml, 内容如下,提交并git push到代码仓库即可触发 Drone 执行 CI。
    kind: pipeline  
    name: deploy

    steps:
    [list]
    [*]name: hello-world[/*]
    [/list] image: docker
    commands:
    - echo "hello world"

    Drone v1 的语法主要参考的 Kubernetes 的语法,非常直观,无需阅读文档也可以知道,我们首先定义了一个管道(Pipeline),管道由若干步骤(step)组成,Drone 的每个步骤是都基于容器实现的,因此 Step 的语法就回到了我们熟悉的 Docker,一个 Step 会拉取 image 定义的镜像,然后运行该镜像,并顺序执行 commands 定义的指令。

    在上例中,Drone 首先 clone git repo 代码到本地,然后根据 .drone.yml 所定义的,拉取 Docker 的官方镜像,然后运行该进行并挂载 git repo 的代码到 /drone/src 目录。

    在 Drone 的界面中,也可以清楚的看到这一过程。
    04.png

    本阶段对应:

    * 代码部分:https://github.com/AlloVince/drone-ci-demo/tree/hello-world
    * Drone 构建记录:https://cloud.drone.io/AlloVince/drone-ci-demo/1
    * Docker 镜像:无

    ##Step.2:单人工作流,自动化单元测试与 Docker 镜像构建
    有了 Hello World 的基础,接下来我们尝试将这个工作流进行扩充。

    为了方便说明,这里假设项目语言为 JavaScript,项目内新增了 test/index.js 文件用于模拟单元测试,一般在 CI 中,只要程序的返回值为 0,即代表运行成功。这个文件中我们仅仅输出一行 Log Unit test passed用于模拟单元测试通过。

    我们希望将代码打包成 Docker 镜像,根目录下增加了 Dockerfile 文件,这里直接使用 Nginx 的官方镜像,构建过程只有 1 行 COPY index.html /usr/share/nginx/html/, 这样镜像运行后可以通过 http 请求看到 index.html 的内容。

    至此我们可以将工作流改进为:

    * 当 Master 分支接收到 push 后,运行单元测试
    * 当 GitHub 发布一次 Release, 构建 Docker 镜像,并推送到镜像仓库

    对应的 Drone 配置文件如下:
    kind: pipeline  
    name: deploy

    steps:
    - name: unit-test
    image: node:10
    commands:
    - node test/index.js
    when:
    branch: master
    event: push
    - name: build-image
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    auto_tag: true
    when:
    event: tag

    虽然比 Hello World 复杂了一些,但是可读性仍然很好,配置文件中出现了几个新概念:

    Step 运行条件, 即 when 部分,上例中展示了当代码分支为 Master,且收到一个 push;以及当代码被标记 tag 这两种情况。Drone 还支持 repo、运行结果等很多其他条件,可以参考 Drone Conditions 文档。

    Plugin 插件,上例中用于构建和推送镜像的是 plugins/docker 这个 Plugin, 一个 Plugin 本质上仍然是一个 Docker 镜像,只是按照 Drone 的规范接受特定的输入,并完成特定的操作。所以完全可以将 Plugin 看做一个无法更改 command 的 Docker 镜像。

    Docker 这个 Plugin 由 Drone 官方提供,用于 Docker 镜像的构建和推送,具体的用法可以查看 Docker 插件的文档。例子中演示的是将镜像推送到私有仓库,如果不做特殊配置,镜像将被推送到 Docker 的官方仓库。

    此外 Docker 插件还有一个很方便的功能,如果设置 auto_tag: true,将根据代码的版本号自动规划 Docker 镜像的标签,如代码版本为1.0.0,将为 Docker 镜像打三个标签 1, 1.0, 1.0.0。如果代码版本号不能被解析,则镜像标签为 latest。

    目前 Drone 的插件已经有很多,可以覆盖主流的云服务商和常见的工作流,并且自己制作插件的成本也不高。

    Secret 加密数据,镜像仓库的用户名和密码都属于敏感信息,因此可以使用 from_secret 获取加密数据。一条加密数据就是一个 key / value 对,如上例中的 DOCKER_PASSWORD 就是我们自己定义的加密数据 key。即便加密数据在 log 中被打印,UI 也只能看到 ***。加密数据的 value 需要提前保存好,保存的方式有 3 种:

    * 通过 Drone UI 界面中, repo -> Settings -> Secrets 添加,所添加的加密数据将保存在 Drone 的数据库中,仅能在当前 repo 中使用。
    * 通过 Drone cli 加密后保存在 .drone.yml 文件中, 使用范围仅限 yaml 文件内
    * 通过 Kubernetes 保存为 Kubernetes Secret,称为 External Secrets,所有的 repo 都可以共享。如果是团队使用的话,这种保存方式显然是最方便的,但也要注意安全问题,因此 External Secrets 还支持 repo 级别的权限管理, 可以只让有当前 repo 写入权限的人才能使用对应 secret。

    这个阶段对应:

    * 代码仓库:https://github.com/AlloVince/drone-ci-demo/tree/single-person
    * push 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/4
    * Release 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/5
    * Release 后 CI 构建的 Docker 镜像:allovince/drone-ci-demo:latest

    ##Step.3:GitFlow 多分支团队工作流
    上面的工作流已经基本可以应付单人的开发了,而在团队开发时,这个工作流还需要一些扩展。不需要引入 Drone 的新功能,只需要在上文基础上根据分支做一点调整即可。

    首先保证单元测试位于 steps 的第一位,并且限定团队工作的分支,在 push 和 pull_request 时,都能触发单元测试。

     - name: unit-test  
    image: node:10
    commands:
    - node test/index.js
    when:
    branch:
    include:
    - feature/*
    - master
    - dev
    event:
    include:
    - push
    - pull_request

    然后根据 GitFlow 的流程对于不同的分支构建 Docker 镜像并打上特定标签,以 feature 分支为例,下面的配置约定了当分支名满足 feature/*,并收到 push 时,会构建 Docker 镜像并打标签,标签名称为当前分支名去掉 feature/。如分支 feature/readme, 对应 Docker 镜像为 allovince/drone-ci-demo:readme,考虑到 feature 分支一般都出于开发阶段,因此新的镜像会覆盖旧的。配置如下:
    - name: build-branch-image  
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    tag:
    - ${DRONE_BRANCH[size=16]feature/} [/size]
    when:
    branch: feature/*
    event: push

    镜像的 Tag 处不再使用自动方式,其中 DRONE_BRANCH 是 Drone 的内置环境变量(Environment),对应当前的分支名。feature/ 是执行了一个字符串的替换操作(Substitution)。更多的环境变量和字符串操作都可以在文档中找到。

    以此类推,可以查看这个阶段的完整 .drone.yml ,此时我们的工作流示例如下:

    * 团队成员从 dev 分支 checkout 自己的分支 feature/readme
    * 向feature/readme提交代码并 push, CI 运行单元测试,构建镜像allovince/drone-ci-demo:readme
    * 功能开发完成后,团队成员向 dev 分支 发起 pull request , CI 运行单元测试
    * 团队其他成员 merge pull request, CI 运行单元测试,构建镜像allovince/drone-ci-demo:test
    * 运维人员从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * 运维人员 merge pull request, 并 Release 新版本 pre-0.0.2, CI 构建镜像allovince/drone-ci-demo:pre-0.0.2

    可能细心的同学会发现 dev -> Master 的 pull request 时,构建镜像失败了,这是由于 Drone 出于安全考虑限制了在 pull request 时默认无法读取加密数据,因此无法得到 Docker Registry 密码。如果是私有部署的话,可以在 Repo Settings 中勾选 Allow Pull Requests,此处就可以构建成功。
    ##Step.4:语义化发布
    上面基本完成了一个支持团队协作的半自动 CI 工作流,如果不是特别苛刻的话,完全可以用上面的工作流开始干活了。

    不过基于这个工作流工作一段时间,会发现仍然存在痛点,那就是每次发布都要想一个版本号,写 ChangeLog,并且人工去 Release。

    标记版本号涉及到上线后的回滚,追溯等一系列问题,应该是一项严肃的工作,其实如何标记早已有比较好的方案,即语义化版本。在这个方案中,版本号一共有 3 位,形如 1.0.0,分别代表:

    1. 主版本号:当你做了不兼容的 API 修改,
    2. 次版本号:当你做了向下兼容的功能性新增,
    3. 修订号:当你做了向下兼容的问题修正。

    虽然有了这个指导意见,但并没有很方便的解决实际问题,每次发布要搞清楚代码的修改到底是不是向下兼容的,有哪些新的功能等,仍然要花费很多时间。

    而语义化发布(Semantic Release)就能很好的解决这些问题。

    语义化发布的原理很简单,就是让每一次 Commit 所附带的 Message 格式遵守一定规范,保证每次提交格式一致且都是可以被解析的,那么进行 Release 时,只要统计一下距离上次 Release 所有的提交,就分析出本次提交做了何种程度的改动,并可以自动生成版本号、自动生成 ChangeLog 等。

    语义化发布中,Commit 所遵守的规范称为约定式提交(Conventional Commits)。比如 Node.js、 Angular、Electron 等知名项目都在使用这套规范。

    语义化发布首先将 Commit 进行分类,常用的分类(Type)有:

    * feat:新功能
    * fix:BUG 修复
    * docs:文档变更
    * style:文字格式修改
    * refactor:代码重构
    * perf:性能改进
    * test:测试代码
    * chore:工具自动生成

    每个 Commit 可以对应一个作用域(Scope),在一个项目中作用域一般可以指不同的模块。

    当 Commit 内容较多时,可以追加正文和脚注,如果正文起始为BREAKING CHANGE,代表这是一个破坏性变更。

    以下都是符合规范的 Commit:
    feat:增加重置密码功能

    fix(邮件模块):修复邮件发送延迟BUG

    feat(API):API重构

    BREAKING CHANGE:API v3上线,API v1停止支持

    有了这些规范的 Commit,版本号如何变化就很容易确定了,目前语义化发布默认的规则如下:
    Commit	版本号变更
    BREAKING CHANGE 主版本号
    feat 次版本号
    fix / perf 修订号

    因此在 CI 部署 semantic-release 之后,作为开发人员只需要按照规范书写 Commit 即可,其他的都由 CI 完成。

    具体如何将语义化发布加入 CI 流程中呢, semantic-release 是 js 实现的,如果是 js 的项目,可以直接在 package.json 中增加配置项,而对于任意语言的项目,推荐像 Demo 中一样,在根目录下增加 配置文件release.config.js。这个配置目的是为了禁用默认开启的 npm 发布机制,可以直接套用。

    semantic-release 要执行 GitHub Release,因此我们需要在 CI 中配置自己的 Personal access tokens 让 CI 有 GitHub Repo 的读写权限, 可以通过 GitHub 点击自己头像 -> Settings -> Developer settings -> Personal access tokens -> Generate new token 生成一个 Token。 然后在 Drone 的 repo 设置界面新增一个 Secret, key 为 GITHUB_TOKEN, value 填入刚生成的 Token。

    最后在 .drone.yml 中增加这样一段就可以了。
    - name: semantic-release  
    image: gtramontina/semantic-release:15.13.3
    environment:
    GITHUB_TOKEN:
    from_secret: GITHUB_TOKEN
    entrypoint:
    - semantic-release
    when:
    branch: master
    event: push

    来再次模拟一下流程,feature 分支部分与上文相同:

    * 从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * merge pull request,CI 会执行单元测试并运行 semantic-release , 运行成功的话能看到 GitHub 新增 Release v1.0.0
    * GitHub Release 再次触发CI 构建生产环境用 Docker 镜像allovince/drone-ci-demo:1.0.0

    最终我们能得到这样一个赏心悦目的 Release。
    05.png

    ##Step.5:Kubernetes 自动发布
    Docker 镜像推送到仓库后,我们还剩最后一步就可以完成全自动发布的闭环,即通知 Kubernetes 将镜像发布到生产环境。这一步实现比较灵活,因为很多云服务商在容器服务都会提供 Trigger 机制,一般是提供一个 URL,只要请求这个 URL 就可以触发容器服务的发布。Demo 中我们使用更为通用的方法,就是将 kubectl 打包为容器,以客户端调用 Kubernetes 集群 Master 节点 API(kube-apiserver)的形式完成发布。

    假设我们在生产环境下 drone-ci-demo 项目的 Kubernetes 发布文件如下:
    ---  
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: ci-demo-deployment
    namespace: default
    spec:
    replicas: 1
    template:
    spec:
    containers:
    - image: allovince/drone-ci-demo
    name: ci-demo
    restartPolicy: Always

    对应 .drone.yml 中增加 step 如下。这里使用的插件是 honestbee/drone-kubernetes, 插件中 kubectl 连接 API 使用的是证书 + Token 的方式鉴权,因此需要先获得证书及 Token, 已经授权的 Token 保存于 Kubernetes Secret,可以通过 kubectl get secret [ your default secret name ] -o yaml | egrep 'ca.crt:|token:' 获得并配置到 Drone 中,注意插件要求 Token 是明文的,需要 base64 解码一下:echo [ your token ] | base64 -d && echo ''。
    - name: k8s-deploy  
    image: quay.io/honestbee/drone-kubernetes
    settings:
    kubernetes_server:
    from_secret: KUBERNETES_SERVER
    kubernetes_cert:
    from_secret: KUBERNETES_CERT
    kubernetes_token:
    from_secret: KUBERNETES_TOKEN
    namespace: default
    deployment: ci-demo-deployment
    repo: allovince/drone-ci-demo
    container: ci-demo
    tag:
    - ${DRONE_TAG}
    when:
    event: tag

    在示例中,可以看到在语义化发布之后 CI 会将新版本的 Docker 镜像自动发布到 Kubernetes,这里为了演示仅打印了指令并未实际运行。相当于运行了如下的指令:
    kubectl -n default set image deployment/ci-demo-deployment ci-demo=allovince/drone-ci-demo:v1.0.2

    由于自动发布的环节势必要接触到生产服务器,需要格外注意安全问题,首推的方式当然是将 CI 和 Kubernetes 集群放于同一内网中,同时可以使用 Kubernetes 的 RBAC 权限控制,为自动发布单独创建一个用户,并删除不必要的权限。
    #后话
    总结一下,本文展示了从 Hello World 到单人单分支手动发布再到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到 通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    最终 Step 的完成品可以适配之前的所有 Step,如果不太在意实现细节的话,可以在此基础上稍作修改,直接使用。

    然而写好每一个 Commit 这个看似简单的要求,其实对于大多数团队来说并不容易做到,在实施过程中,经常会遇到团队成员不理解为什么要重视 Commit 规范,每个 Commit 都要深思熟虑是否过于吹毛求疵等等疑问。

    以 Commit 作为 CI 的核心,个人认为主要会带来以下几方面的影响:

    1. 一个好的 Commit,代表着开发人员对当前改动之于整个系统的影响,有非常清楚的认识,代码的修改到底算 feat 还是 fix ,什么时候用 BREAKING CHANGE 等都是要仔细斟酌的,每个 Commit 都会在 ChangeLog 里“留底”,从而约束团队不随意提交未经思考的代码,提高代码质量。
    2. 一个好的 Commit 也代表开发人员有能力对所实现功能进行精细的划分,一个分支做的事情不宜过多,一个提交也应该专注于只解决一个问题,每次提交(至少是每次 push)都应该保持系统可构建、可运行、可测试,如果能坚持做到这些,对于合并代码时的冲突解决,以及集成测试都有很大帮助。
    3. 由于每次发布能清楚的看到所有关联的 Commit 以及 Commit 的重要程度,那么线上事故的回滚也会非常轻松,回滚到哪个版本,回滚后哪些功能会受到影响,只要看 CI 自动生成的 Release 记录就一目了然。如果没有这些,回滚误伤到预期外的功能从而引发连锁反应的惨痛教训,可能很多运维都有过类似经历吧。

    因此 CI 自动化其实是锦上添花而非雪中送炭,如果团队原本就无视规范,Commit 全是空白或者没有任何意义的单词,分支管理混乱,发布困难,奢望引入一套自动化 CI 来能解决所有这些问题,无疑是不现实的。而只有原本就重视代码质量,有一定规范意识,再通过自动化 CI 来监督约束,团队在 CI 的帮助下代码质量提高,从而有机会进一步改进 CI 的效率,才能形成良心循环。

    愿天下不再有难发布的版本。
    #Q&A
    Q:Kubernetes 上主流的 CI/CD 方案是啥?
    A:其实这无关 Kubernetes,从市场占有率来看,前三名分别是

    1. Jenkins
    2. JetBrains TeamCity
    3. CircleCI

    来源:https://www.datanyze.com/market-share/ci

    Q:GitLab 自带的 CI 与 Jenkins 和 GitLab 结合的 CI,该如何选择?想知道更深层次的理解。
    A:还是要结合自己团队的实际情况做选择。从成熟度来说,肯定是 Jenkins 用户最多,成熟度最高,缺点是侧重 Java,配置相对繁琐。GitLab 自带的 CI 相对简单,可以用 yaml,和 GitLab 结合的最好,但功能肯定没有 Jenkins 全面。如果是小团队新项目,GitLab CI 又已经可以满足需求的话,并不需要上 Jenkins,如果是较大的团队,又是偏 Java 的,个人更偏向 Jenkins。

    Q:Jenkins 如果不想运行在 Kubernetes 里面,该怎么和 Kubernetes 集成?
    A:从 CI 的流程来说,CI 应用是不是跑在 Kubernetes 的并不重要,CI 只要能访问代码库,有权限在生产环境发布,是不是跑在容器里从效果来说其实没有区别,只是用 Kubernetes 部署 Jenkins 的话,运维的一致性比较好,运维团队不用额外花时间维护一套物理机的部署方案。

    Q:Kubernetes 的回滚方案是回滚代码,重做镜像,还是先切流量,后做修改?
    A:代码一定是打包到镜像里的,镜像的版本就是代码的版本,所以一定是切镜像。至于回滚操作本身,Kubernetes 已经内置了很多滚动发布(Rolling update)的策略,无论是发新版本还是回滚版本,都可以做到用户无感知。

    Q:镜像大到几 G 的话如何更新部署,有什么好的实践呢,以及如何回滚?
    A:几个要点:

    1. 镜像仓库部署在内网,镜像推拉走内网,几个 G 的镜像传输起来也只是秒级别的
    2. 镜像构建时将不经常变动的部分划分到 1 层,因为 Docker 镜像是分层的,这样每次拉镜像可以利用到 Docker 的缓存传输的只有镜像发生变化的部分
    3. 选择 alpine 这样尽量小的镜像

    回滚如果发生在相邻的版本,一般物理机上是有缓存的,速度都很快。

    Q:Drone 开放 API 服务吗?这样方便其他系统集成。
    A:可以调整一下思路,直接把需要的功能做成镜像在 Drone 里调用就好了。

    Q:如果有 Drone 的 Server怎么做高可用?
    A:Drone serve r用 Kubernetes 部署的话本身只起到了一个任务调度的作用,很难会遇到性能瓶颈。真的有性能问题可以尝试水平扩展 Drone server,共享同一数据库。

    作者博客:https://avnpc.com/pages/drone-gitflow-kubernetes-for-cloud-native-ci

    以上内容根据2019年4月2日晚微信群分享内容整理。分享人徐谦,高级架构师,10 年以上研发经验,2 次创业经历。现从事互联网金融相关研发,关注 Node.js、容器技术、机器学习等。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零五):基于OVN的Kubernetes网络架构解析

    灵雀云 发表了文章 • 0 个评论 • 872 次浏览 • 2019-03-28 17:42 • 来自相关话题

    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络 ...查看全部
    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络的完整控制平面,并已经成为了最新的OpenStack网络标准。我们将OVN的网络架构和Kubernetes的容器平台进行结合,将业界成熟的网络架构引入Kubernetes大幅增强现有容器网络的能力。
    #Kubernetes网络的局限性
    Kubernetes提出了很多网络概念,很多开源项目都有自己的实现。然而由于各个网络功能都是在不同的项目中实现,功能和性能也各有千秋,缺乏统一的解决方案,在使用过程中经常会陷入到底该用哪个的抉择中。同时CNI、DNS、Service的实现又在不同的项目,一旦网络出现问题,排查也会在多个组件间游走,是一个十分痛苦的过程。

    尽管Kubernetes提出了很多网络的概念,但是在真实应用中很多人会有这样的感觉:网络这块还是很薄弱,很多功能缺乏,方案也不够灵活。尤其是和搞传统基础设施网络的人沟通会发现,在他们眼里,Kubernetes的网络还很初级。我们熟悉的Kubernetes网络是CNI、Service、DNS、Ingress、Network Policy这样的模式。而做IaaS的视角完全不同,他们每次提起是VPC、Subnet、VNIC、 Floating IP,在此之上有DHCP,路由控制,安全组,QoS,负载均衡,域名解析这样的基础网络功能。

    从IaaS的视角来看,Kubernetes的网络功能确实比较单薄。经常碰到来自传统网络部门的挑战,诸如子网划分VLAN隔离,集群内外网络打通,容器NAT设置,带宽动态调节等等。现有的开源网络方案很难完美支持,最简单的一个例子,比如提及容器的固定IP功能通常就要上升到意识形态斗争的层面去讨论。这本质上还是Kubernetes的网络功能不足,模型也不够灵活导致的。从更高层面来说,Kubernetes中抽象类一层网络虚拟化的内容,然而网络虚拟化或者SDN并不是Kubernetes带来的新东西,相关技术已经发展很久。尤其是在IaaS领域里已经有着比较成熟且完善的一整套网络方案。

    传统网络部门的人都会问,为什么不用OVS来做网络方案,很多需求用只要容器网络接入OVS网络,剩下事情网络部门自己就知道怎么去做了,都不用我们实现太多额外的功能。也有很多人向我们推荐了OVN,用这个能很方便地实现这些功能。也正由此我们开始去关注OVS/OVN这种之前主要应用于OpenStack生态系统的网络工具。下面我就来介绍一下OVS和OVN。
    #OVS和OVN网络方案的能力
    网络的概念比较晦涩一些,但是好在大家都对Docker和Kubernetes比较熟悉,可以做个类比。如果说Docker是对单机计算资源的虚拟化,那么OVS就是对单机网络进行虚拟化的一个工具。它最基本的功能是实现了虚拟交换机,可以把虚拟网卡和虚拟交换机的端口连接,这样一个交换机下的多个网卡网络就打通了,类似Linux Bridge的功能。在此之上,OVS很重要的一点就是支持OpenFlow,这是一种可编程的流量控制语言,可以方便我们以编程的方式对流量进行控制,例如转发,拒绝,更改包信息,NAT,QoS 等等。此外OVS还支持多中网络流量监控的协议,方便我们可视化监控并跟踪整个虚拟网络的流量情况。

    但是,OVS只是一个单机软件,它并没有集群的信息,自己无法了解整个集群的虚拟网络状况,也就无法只通过自己来构建集群规模的虚拟网络。这就好比是单机的Docker,而OVN就相当于是OVS的Kubernetes,它提供了一个集中式的OVS控制器。这样可以从集群角度对整个网络设施进行编排。同时OVN也是新版OpenStack中Neutron的后端实现,基本可以认为未来的OpenStack网络都是通过OVN来进行控制的。
    01.jpeg

    上图是一个OVN的架构,从下往上看:

    ovs-vswitchd和ovsdb-server可以理解为单机的Docker负责单机虚拟网络的真实操作。

    ovn-controller类似于kubelet,负责和中心控制节点通信获取整个集群的网络信息,并更新本机的流量规则。

    Southbound DB类似于etcd(不太准确),存储集群视角下的逻辑规则。

    Northbound DB类似apiserver,提供了一组高层次的网络抽象,这样在真正创建网络资源时无需关心负责的逻辑规则,只需要通过Northoboud DB的接口创建对应实体即可。

    CMS可以理解为OpenStacke或者Kubernetes这样的云平台,而 CMS Plugin是云平台和OVN对接的部分。

    下面我们具体介绍一下OVN提供的网络抽象,这样大家就会有比较清晰的认知了。

    Logical_Switch最基础的分布式虚拟交换机,这样可以将多台机器上的容器组织在一个二层网络下,看上去就好像所有容器接在一台交换机上。之后可以在上面增加诸如ACL、LB、QoS、DNS、VLAN等等二层功能。

    Logical_Router虚拟路由器,提供了交换机之间的路由,虚拟网络和外部网络连接,之后可以在路由器层面增加DHCP、NAT、Gateway等路由相关的功能。

    Loadbalancer,L2和L3的Loadbalancer,可以类比公有云上的内部LB和外部LB的功能。

    ACL基于L2到L4的所有控制信息进行管控的一组DSL,可以十分灵活,例如:outport == “port1” && ip4 && tcp && tcp.src >= 10000 && tcp.dst <= 1000。

    QoS,可以基于和ACL同样的DSL进行带宽的控制。

    NAT,同时提供DNAT和SNAT的控制方便内外网络通信。

    DNS,内置的分布式DNS,可以在本机直接返回内部DNS的请求。

    Gateway控制内部和外部之间的集中式通信。

    了解了这些,大家应该就能发现,Kubernetes目前的网络从功能层面其实只是OVN支持的一个子集,基本上所有Kubernetes的网络需求都能在OVN中找到映射关系,我们简单来看下他们之间的映射。
    #OVN和Kubernetes的结合
    Switch/Router -> Kubernetes基本的东西向互通容器网络。这块OVN的能力其实是大大超出,毕竟OVN的这套模型是针对多租户进行设计的,而Kubernetes现在只需要一个简单的二层网络。

    Loadbalancer → ClusterIP以及Loadbalancer类型的Service,可以取代kube-proxy的功能,OVN本身也可以实现云上ELB的功能。

    ACL -> Networkpolicy本质上更灵活因为可以从L2控制到L4并且DSL也支持更多的语法规则。

    DNS -> 可以取代Kube-DNS/CoreDNS,同时由于OVN实现的是分布式DNS,整体的健壮性会比现在的Kubernetes方案要好。

    可以看到Kubernetes的CNI、kube-proxy、Kube-DNS、NetworkPolicy、Service等等概念OVN都有对应的方案,而且在功能或者稳定性上还有增强。更不要说还有QoS、NAT、Gateway等等现在Kubernetes中没有的概念。可以看到如果能把IaaS领域的网络能力往Kubernetes平移,我们还有很多的提升空间。
    #Kubernetes网络未来增强的方向
    最后来说说我认为的未来Kubernetes网络可能的增强方向。
    ##Kubernetes网络功能和IaaS网络功能打平
    现在的Kubernetes网络模型很类似之前公有云上的经典网络,所有用户大二层打通,通过安全策略控制访问。我们现在也都知道公有云多租户不能这么做VPC肯定是标配。因此未来Kubernetes网络可能也会向着多租户方向前进,在VPC的基础上有更多的路由控制、NAT控制、带宽控制、浮动IP等等现在IaaS上很常见的功能。
    ##性能
    现有的开源方案其实主要还是依赖原有的Linux网络栈,没有针对性的优化。理论上容器的密度比传统虚拟化高,网络压力会更大。OVS现在有DPDK等Kernel bypass的DataPath,绕过Linux内核栈来实现低延迟和大吞吐网络。未来随着容器的密度越来越大,我认为会出现这种针对容器架构专门优化的网络方案,而不是依旧依赖Linux网络栈。
    ##监控和问题排查
    现有的网络问题排查十分困难,如果所有的数据平面都由一个项目完成,比如OVN,那么学习成本和排障都会容易一些。此外OVS社区已经有了很多成熟的监控,追踪,排障方案,随着容器的使用场景变多,我认为外围的工具也需要能够很好的支撑这种模式的网络运维问题。
    #Q&A
    Q:OVS方案与基于三层交换机方案对比,各有什么优缺点?

    A:OVS最大的优点在于可编程,灵活性比较好。虚拟网络不用手动插网线,而且有OpenFlow加持,可以实现一些普通交换机无法实现的流量控制。物理交换机的主要有点就是性能好,而且比较稳定,不容易出问题。



    Q:OVN不支持ECMP,貌似现在还是active-standby机制,你们怎么解决Gateway瓶颈问题?

    A:有几种方式:1. Gateway用DPDK这样的高速DataPath;2. 多Gateway,用策略路不同的IP段走不同Gateway,可以分担流量;3. 不使用OVN概念的Gateway,自己做一些手脚从每台宿主机直接出去,也是分担流量降低单点的风险。



    Q:OVN-Kubernetes好像只支持每个部署节点一个虚拟网络网段。如何避免IP池浪费和不均衡?

    A:这个其实是这个项目实现的网络模型的一个局限性。在我们的实现里是以namespace为粒度划分子网,可以对每个namespace进行控制,情况会好很多。



    Q:OVN如果有不同的Chassis,但是相同IP就会造成网络异常(比如物理机,VM装上OVN,注册到远端后,被重建,后面又注册到远端,但是Chassis已经改变),会导致大量节点Geneve网络异常。你们怎么解决这个问题?

    A:暂时没碰到这个问题,但是我们在实现的一个原则就是尽可能保证所有的操作都是幂等的。向这种可能需要在重连前做一个检查,判断是否有过期的数据需要清理,再连接,或者复用旧的连接信息去连接。



    Q:如何debug OVN逻辑拓扑是否配置有问题?

    A:目前debug确实很多情况只能靠眼看,也可以使用ovn-trace这个工具可以打印数据包在逻辑流里的链路来排查。



    Q:OVS跟Calico等有啥区别?

    A:Calico主要依赖Linux的路由功能做网络打通,OVS是在软件交换机层面实现网络打通,并提供了更丰富的网络功能。



    Q:OVS的封包支持有STT和Geneve,你们选用哪种,为什么?

    A:其实还支持VXLAN,我们选的Geneve。原因比较简单,Geneve是第一个OVN支持的封包协议,而且看了一些评测,据说在搞内核开启UDP Checksum的情况下性能会比VXLAN要好一些。



    Q:OVS如何实现固定容器IP?

    A:这个其实OVS有对应的设置可以给每个端口设定IP和MACE,这样网卡启动时配置相同的信息就可以了,难点其实是如何控制OVN来分配 IP,感觉这个话题可以再开一场分享来讨论了。



    Q:可以简单介绍下你们准备开源的网络功能吗?

    A:每个namespace和一个logical_switch绑定,支持子网分配,支持固定 IP,支持 QoS,支持NetworkPolicy,内置的LB,内置的DNS,大致就是把OVN的概念映射到Kubernetes。



    Q:想了解一下,如果采用OVN,是不是意味着使用OpenStack平台和Kubernetes网络可以直接互通?完成业务在虚拟机和Pod之间的全新负载方式?

    A:是这样的,如果涉及的合理可以做到容器和VM使用同一个底层网络基础设施,VM和容器之间可以IP直达,所有的ACL、LB都是打通的。



    Q:直接把OpenShift OVS抽出来做Kubernetes的网络插件和灵雀云做的这个区别在哪?

    A:功能上有很多是相同的,因为底层都是OVS。如果关注Roadmap会发现OpenShift之后也会采用OVS的模式。从架构的角度来看,现在openshift-multitenant的实现很类似Neutron之前那一套,各种Agent,之后会统一到OVN。另一方面OpenShift的网络插件绑定的太死了,所以我们决定还是自己抽出来,顺便能实现我们的一些特殊功能,比如固定IP,子网共享,以及一些网关控制层面的功能。



    Q:请问Geneve和VXLAN的区别有哪些?

    A:Geneve可以理解为下一代VXLAN,VXLAN相对VLAN来说头部更长可以支持更多的VLAN,但是由于是固定长度的封装头,不能任意加控制信息。Geneve采用变长的封装头,可以加很多自定义的控制信息,方便之后做更复杂的网络管控。



    Q:Docker CNM也支持固定IP,和你说的固定IP是一回事吗?另外,基于OVS建立的网络是CNI还是CNM的呢?

    A:基于CNI,因为我们依赖Kubernetes的模型。不过话说回来我很喜欢Docker CNM那套模型,比CNI要实用很多。固定IP其实只是个功能,各种模型下都可以实现,效果就是可以给Pod指定IP启动,Workload下的多个Pod实用的是一组固定的地址。



    Q:目前你们对企业的解决方案里会默认采用这种网络模式么?

    A:这个方案是我们这几年需求和碰到坑的一个积累吧,现在还不会直接给企业用,我们还需要一些功能的开发和测试,但是之后Overlay的场景这个肯定是主推了,主要是取代原来的Flannel VXLAN网络。



    Q:你了解Contiv网络方案吗,和贵公司的实现有哪些区别?

    A:Contiv是思科做的,也是OVS实现的,不过它的实现比较早,自己手动实现了整个控制平面,可以认为自己写了个跟OVN类似的东西来控制 OVS。不过看它最近已经很少更新了。用OVN能用很少的代码就实现基本相同的功能。Contiv有个比较独特的功能就是支持BGP的网络间通信,这个是OVN暂时不支持的。



    以上内容根据2019年3月26日晚微信群分享内容整理。分享人刘梦馨,灵雀云高级工程师。2014年加入灵雀云容器团队,长期参与容器调度平台和容器网络架构的产品研发和技术架构演进,参与自研容器网络和容器应用网关。目前主要专注于容器网络功能的拓展和架构优化。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零四):小团队微服务落地实践

    齐达内 发表了文章 • 0 个评论 • 1100 次浏览 • 2019-03-21 10:14 • 来自相关话题

    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将Dev ...查看全部
    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将DevOps落实到产品中的愿景,一步步建设出适合我们的微服务平台。
    # 要不要微服务
    我们的产品是Linkflow,企业运营人员使用的客户数据平台(CDP)。产品的一个重要部分类似企业版的“捷径",让运营人员可以像搭乐高积木一样创建企业的自动化流程,无需编程即可让数据流动起来。从这一点上,我们的业务特点就是聚少成多,把一个个服务连接起来就成了数据的海洋。理念上跟微服务一致,一个个独立的小服务最终实现大功能。当然我们一开始也没有使用微服务,当业务还未成型就开始考虑架构,那么就是“过度设计"。另一方面需要考虑的因素就是“人",有没有经历过微服务项目的人,团队是否有DevOps文化等等,综合考量是否需要微服务化。

    微服务的好处是什么?

    • 相比于单体应用,每个服务的复杂度会下降,特别是数据层面(数据表关系)更清晰,不会一个应用上百张表,新员工上手快。
    • 对于稳定的核心业务可以单独成为一个服务,降低该服务的发布频率,也减少测试人员压力。
    • 可以将不同密集型的服务搭配着放到物理机上,或者单独对某个服务进行扩容,实现硬件资源的充分利用。
    • 部署灵活,在私有化项目中,如果客户有不需要的业务,那么对应的微服务就不需要部署,节省硬件成本,就像上文提到的乐高积木理念。
    微服务有什么挑战?
    • 一旦设计不合理,交叉调用,相互依赖频繁,就会出现牵一发动全身的局面。想象单个应用内Service层依赖复杂的场面就明白了。
    • 项目多了,轮子需求也会变多,需要有人专注公共代码的开发。
    • 开发过程的质量需要通过持续集成(CI)严格把控,提高自动化测试的比例,因为往往一个接口改动会涉及多个项目,光靠人工测试很难覆盖所有情况。
    • 发布过程会变得复杂,因为微服务要发挥全部能力需要容器化的加持,容器编排就是最大的挑战。
    • 线上运维,当系统出现问题需要快速定位到某个机器节点或具体服务,监控和链路日志分析都必不可少。
    下面详细说说我们是怎么应对这些挑战的。# 开发过程的挑战## 持续集成通过CI将开发过程规范化,串联自动化测试和人工Review。我们使用Gerrit作为代码&分支管理工具,在流程管理上遵循GitLab的工作流模型。
    • 开发人员提交代码至Gerrit的magic分支
    • 代码Review人员Review代码并给出评分
    • 对应Repo的Jenkins job监听分支上的变动,触发Build job。经过IT和Sonar的静态代码检查给出评分
    • Review和Verify皆通过之后,相应Repo的负责人将代码merge到真实分支上
    • 若有一项不通过,代码修改后重复过程
    • Gerrit将代码实时同步备份至的两个远程仓库中
    1.png
    ## 集成测试一般来说代码自动执行的都是单元测试(Unit Test),即不依赖任何资源(数据库,消息队列)和其他服务,只测试本系统的代码逻辑。但这种测试需要mock的部分非常多,一是写起来复杂,二是代码重构起来跟着改的测试用例也非常多,显得不够敏捷。而且一旦要求开发团队要达到某个覆盖率,就会出现很多造假的情况。所以我们选择主要针对API进行测试,即针对controller层的测试。另外对于一些公共组件如分布式锁,json序列化模块也会有对应的测试代码覆盖。测试代码在运行时会采用一个随机端口拉起项目,并通过http client对本地API发起请求,测试只会对外部服务做mock,数据库的读写,消息队列的消费等都是真实操作,相当于把Jmeter的事情在Java层面完成一部分。Spring Boot项目可以很容易的启动这样一个测试环境,代码如下:
    @RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    测试过程的http client推荐使用`io.rest-assured:rest-assured`支持JsonPath,十分好用。测试时需要注意的一个点是测试数据的构造和清理。构造又分为schema的创建和测试数据的创建。
    • schema由flyway处理,在启用测试环境前先删除所有表,再进行表的创建。
    • 测试数据可以通过`@Sql`读取一个SQL文件进行创建,在一个用例结束后再清除这些数据。
    顺带说一下,基于flyway的schema upgrade功能我们封成了独立的项目,每个微服务都有自己的upgrade项目,好处一是支持command-line模式,可以细粒度的控制升级版本,二是也可以支持分库分表以后的schema操作。upgrade项目也会被制作成docker image提交到docker hub。测试在每次提交代码后都会执行,Jenkins监听Gerrit的提交,通过`docker run -rm {upgrade项目的image}`先执行一次schema upgrade,然后`gradle test`执行测试。最终会生成测试报告和覆盖率报告,覆盖率报告采用JaCoCo的Gradle插件生成。如图:
    2.png
    3.png
    这里多提一点,除了集成测试,服务之间的接口要保证兼容,实际上还需要一种consumer-driven testing tool,就是说接口消费端先写接口测试用例,然后发布到一个公共区域,接口提供方发布接口时也会执行这个公共区域的用例,一旦测试失败,表示接口出现了不兼容的情况。比较推荐大家使用Pact或是Spring Cloud Contact。我们目前的契约基于“人的信任”,毕竟服务端开发者还不多,所以没有必要使用这样一套工具。集成测试的同时还会进行静态代码检查,我们用的是sonar,当所有检查通过后Jenkins会+1分,再由reviewer进行代码review。## 自动化测试单独拿自动化测试出来说,就是因为它是质量保证的非常重要的一环,上文能在CI中执行的测试都是针对单个微服务的,那么当所有服务(包括前端页面)都在一起工作的时候是否会出现问题,就需要一个更接近线上的环境来进行测试了。在自动化测试环节,我们结合Docker提高一定的工作效率并提高测试运行时环境的一致性以及可移植性。在准备好基础的Pyhton镜像以及Webdriver(selenium)之后,我们的自动化测试工作主要由以下主要步骤组成:
    • 测试人员在本地调试测试代码并提交至Gerrit
    • Jenkins进行测试运行时环境的镜像制作,主要将引用的各种组件和库打包进一个Python的基础镜像
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的自动化测试环境更新,然后拉取自动化测试代码启动一次性的自动化测试运行时环境的Docker容器,将代码和测试报告的路径镜像至容器内
    • 自动化测试过程将在容器内进行
    • 测试完成之后,不必手动清理产生的各种多余内容,直接在Jenkins上查看发布出来的测试结果与趋势
    4.png
    5.png
    关于部分性能测试的执行,我们同样也将其集成到Jenkins中,在可以直观的通过一些结果数值来观察版本性能变化情况的回归测试和基础场景,将会很大程度的提高效率,便捷的观察趋势。
    • 测试人员在本地调试测试代码并提交至Gerrit
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的性能测试环境更新以及可能的Mock Server更新
    • 拉取最新的性能测试代码,通过Jenkins的性能测试插件来调用测试脚本
    • 测试完成之后,直接在Jenkins上查看通过插件发布出来的测试结果与趋势

    6.png

    7.png

    # 发布过程的挑战
    上面提到微服务一定需要结合容器化才能发挥全部优势,容器化就意味着线上有一套容器编排平台。我们目前采用是Redhat的OpenShift。所以发布过程较原来只是启动jar包相比要复杂的多,需要结合容器编排平台的特点找到合适的方法。
    ## 镜像准备
    公司开发基于GitLab的工作流程,Git分支为master,pre-production和prodution三个分支,同时生产版本发布都打上对应的tag。每个项目代码里面都包含dockerfile与jenkinsfile,通过Jenkins的多分支Pipeline来打包Docker镜像并推送到Harbor私库上。
    8.jpg

    Docker镜像的命令方式为 `项目名/分支名:git_commit_id`,如 `funnel/production:4ee0b052fd8bd3c4f253b5c2777657424fccfbc9`,tag版本的Docker镜像命名为 `项目名/release:tag名`,如 `funnel/release:18.10.R1`。
    9.png

    10.png

    在Jenkins中执行build docker image job时会在每次pull代码之后调用Harbor的API来判断此版本的docker image是否已经存在,如果存在就不执行后续编译打包的stage。在Jenkins的发布任务中会调用打包Job,避免了重复打包镜像,这样就大大的加快了发布速度。
    ## 数据库Schema升级
    数据库的升级用的是flyway,打包成Docker镜像后,在OpenShift中创建Job去执行数据库升级。Job可以用最简单的命令行的方式去创建:
    oc run upgrade-foo --image=upgrade/production --replicas=1 --restart=OnFailure --command -- java -jar -Dprofile=production /app/upgrade-foo.jar

    脚本升级任务也集成在Jenkins中。
    ## 容器发布
    OpenShift有个特别概念叫DeploymentConfig,原生Kubernetes Deployment与之相似,但OpenShift的DeploymentConfig功能更多些。

    DeploymentConfig关联了一个叫做ImageStreamTag的东西,而这个ImagesStreamTag和实际的镜像地址做关联,当ImageStreamTag关联的镜像地址发生了变更,就会触发相应的DeploymentConfig重新部署。我们发布是使用了Jenkins+OpenShift插件,只需要将项目对应的ImageStreamTag指向到新生成的镜像上,就触发了部署。
    11.png

    如果是服务升级,已经有容器在运行怎么实现平滑替换而不影响业务呢?

    配置Pod的健康检查,Health Check只配置了ReadinessProbe,没有用LivenessProbe。因为LivenessProbe在健康检查失败之后,会将故障的Pod直接干掉,故障现场没有保留,不利于问题的排查定位。而ReadinessProbe只会将故障的Pod从Service中踢除,不接受流量。使用了ReadinessProbe后,可以实现滚动升级不中断业务,只有当Pod健康检查成功之后,关联的Service才会转发流量请求给新升级的Pod,并销毁旧的Pod。
    readinessProbe:
    failureThreshold: 4
    httpGet:
    path: /actuator/metrics
    port: 8090
    scheme: HTTP
    initialDelaySeconds: 60
    periodSeconds: 15
    successThreshold: 2
    timeoutSeconds: 2

    # 线上运维的挑战
    ## 服务间调用
    Spring Cloud使用Eruka接受服务注册请求,并在内存中维护服务列表。当一个服务作为客户端发起跨服务调用时,会先获取服务提供者列表,再通过某种负载均衡算法取得具体的服务提供者地址(IP + Port),即所谓的客户端服务发现。在本地开发环境中我们使用这种方式。

    由于OpenShift天然就提供服务端服务发现,即Service模块,客户端无需关注服务发现具体细节,只需知道服务的域名就可以发起调用。由于我们有Node.js应用,在实现Eureka的注册和去注册的过程中都遇到过一些问题,不能达到生产级别。所以决定直接使用Service方式替换掉Eureka,也为以后采用Service Mesh做好铺垫。具体的做法是,配置环境变量`EUREKA_CLIENT_ENABLED=false`,`RIBBON_EUREKA_ENABLED=false`,并将服务列表如 `FOO_RIBBON_LISTOFSERVERS: 'http://foo:8080'` 写进ConfigMap中,以`envFrom: configMapRef`方式获取环境变量列表。

    如果一个服务需要暴露到外部怎么办,比如暴露前端的html文件或者服务端的Gateway。

    OpenShift内置的HAProxy Router,相当于Kubernetes的Ingress,直接在OpenShift的Web界面里面就可以很方便的配置。我们将前端的资源也作为一个Pod并有对应的Service,当请求进入HAProxy符合规则就会转发到UI所在的Service。Router支持A/B test等功能,唯一的遗憾是还不支持URL Rewrite。
    12.png

    13.png

    对于需要URL Rewrite的场景怎么办?那么就直接将Nginx也作为一个服务,再做一层转发。流程变成 Router → Nginx Pod → 具体提供服务的Pod。
    ## 链路跟踪
    开源的全链路跟踪很多,比如Spring Cloud Sleuth + Zipkin,国内有美团的CAT等等。其目的就是当一个请求经过多个服务时,可以通过一个固定值获取整条请求链路的行为日志,基于此可以再进行耗时分析等,衍生出一些性能诊断的功能。不过对于我们而言,首要目的就是trouble shooting,出了问题需要快速定位异常出现在什么服务,整个请求的链路是怎样的。

    为了让解决方案轻量,我们在日志中打印RequestId以及TraceId来标记链路。RequestId在Gateway生成表示唯一一次请求,TraceId相当于二级路径,一开始与RequestId一样,但进入线程池或者消息队列后,TraceId会增加标记来标识唯一条路径。举个例子,当一次请求会向MQ发送一个消息,那么这个消息可能会被多个消费者消费,此时每个消费线程都会自己生成一个TraceId来标记消费链路。加入TraceId的目的就是为了避免只用RequestId过滤出太多日志。

    实现上,通过ThreadLocal存放APIRequestContext串联单服务内的所有调用,当跨服务调用时,将APIRequestContext信息转化为Http Header,被调用方获取到Http Header后再次构建APIRequestContext放入ThreadLocal,重复循环保证RequestId和TraceId不丢失即可。如果进入MQ,那么APIRequestContext信息转化为Message Header即可(基于RabbitMQ实现)。

    当日志汇总到日志系统后,如果出现问题,只需要捕获发生异常的RequestId或是TraceId即可进行问题定位。
    14.png

    经过一年来的使用,基本可以满足绝大多数trouble shooting的场景,一般半小时内即可定位到具体业务。
    ## 容器监控
    容器化前监控用的是Telegraf探针,容器化后用的是Prometheus,直接安装了OpenShift自带的cluster-monitoring-operator。自带的监控项目已经比较全面,包括Node,Pod资源的监控,在新增Node后也会自动添加进来。

    Java项目也添加了Prometheus的监控端点,只是可惜cluster-monitoring-operator提供的配置是只读的,后期将研究怎么将Java的JVM监控这些整合进来。
    15.png

    16.png

    # 更多的
    开源软件是对中小团队的一种福音,无论是Spring Cloud还是Kubernetes都大大降低了团队在基础设施建设上的时间成本。当然其中有更多的话题,比如服务升降级,限流熔断,分布式任务调度,灰度发布,功能开关等等都需要更多时间来探讨。对于小团队,要根据自身情况选择微服务的技术方案,不可一味追新,适合自己的才是最好的。
    #Q&A
    Q:服务治理问题,服务多了,调用方请求服务方,超时或者网络抖动等需要可能需要重试,客户端等不及了怎么办?比如A->B->C,等待超时时间都是6s,因为C服务不稳定,B做了重试,那么增加了A访问B的时长,导致连锁反应?
    A:服务发现有两种,一种是客户端发现,一种是服务端发现。如果是客户端发现的话,客户端需要设置超时时间,如果超时客户端需要自己重试,此时如果是轮询应该可以调用到正常的服务提供方。Spring Coud的Ribbon就是对这一流程做了封装。至于连锁反应的问题,需要有降级熔断,配置Hystrix相关参数并实现fallback方法。看源码能够发现hystrixTimeout要大于ribbonTimeout,即Hystrix熔断了以后就不会重试了,防止雪崩。

    Q:JVM如何export,是多container吗,监控数据,搜刮到Prometheus?
    A:JVM的用的是Prometheus埋点,Java里面的路径是/actuator/prometheus,在yaml里面定义prometheus.io/path: /actuator/prometheu prometheus.io/port: '8090' prometheus.io/scrape: 'true',再在Prometheus里面进行相应的配置,就可以去搜刮到这些暴露的指标。

    Q:Kubernetes和OpenShift哪个更适合微服务的使用?
    A:OpenShift是Kubernetes的下游产品,是Kubernetes企业级的封装,都是一样的。OpenShift封装有功能更强大的监控管理工具,并且拥有Kubernetes不太好做的权限管理系统。

    Q:可以介绍一下你们在优化镜像体积上面做了哪些工作吗?
    A:RUN命令写在一行上,产生的临时文件再删掉。只安装必须要的包。JDK和Node.Js都有slim镜像,一般都是以那个为基础镜像去做。

    Q:数据库是否真的适合最容器化?
    A:我们生产数据库用的是RDS,开发测试环境用的是Docker Compose起的。从理论上,数据库最好做容器化,模块的独立性高,需要考虑好的是数据库容器的数据永久化存储。

    Q:为什么选择了OpenShift?
    A:因为OpenShift有个很方便的UI,大多数都可以在UI里面操作,包括yaml文件的修改,重新部署回退等操作。对于开发测试来讲,学习的成本比较低,不需要花时间熟悉CLI操作。

    Q:Python基础镜像怎么制作最好,如果加入GCC,C++等编译需要的工具,镜像会特别大?
    A:Python基础镜像直接从Python官方Docker镜像上做就行了。GCC,C++那个做出来的镜像大也没办法。如果没这个需求的话,可以用Python slim镜像去做。

    Q:在Gateway中Ribbon如何根据客户端的IP负载到对应的IP注册的服务?
    A:如果使用了Eureka的话,服务提供方启动时会自注册到Eureka。服务调用方发起请求前会从Eureka上读取提供方的列表,再进行负载均衡定位到具体的IP和Port。如果跟我们一样直接使用Kubernetes的Service,其实就是由Kubernetes控制了,服务调用方访问Kubernetes暴露的Service,然后由Kubernetes负载均衡到这个Service背后的具体的Pod。

    Q:如何实现远程发布、打包?
    A:Jenkins打包镜像发布到Harbor上,Jenkins再调用OpenShift去从Harbor上拉取镜像,重新tag一下就可以实现发布。

    Q:譬如客户端IP是10,希望Gateway负载到10注册的order服务,而不是其他IP注册的order服务,希望开发使用集中的Eureka和Gateway?
    A:是说不需要负载均衡?最简单的可以看下Ribbon的实现,负载均衡算法可以自己定义,如果只是要固定IP的话,那么遍历服务列表再判断就可以了。两次判断,if serviceId=order,if ip = 10。

    Q:Docker管理工具一般用什么?
    A:Kubernetes,简称k8s是目前较热门的Docker管理工具。离线安装Kubernetes比较繁琐,有两款比较好的自动化部署工具,Ubuntu系统的Juju和Red Hat系统的OpenShift,OpenShift又称为企业版的Kubernetes,有收费的企业版和免费版。

    Q:Prometheus是每个集群部署一套吗?存储是怎么处理?存本地还是?
    A:每个集群部署一套,存储暂时存在本地,没有用持久化存储。因为现在环境都是在云上面,本身云厂商就有各种的监控数据,所以Prometheus的监控数据也只是做个辅助作用。

    以上内容根据2019年3月19日晚微信群分享内容整理。分享人徐鹏,Linkflow产品运维负责人,负责公司运维平台建设和管理,同时兼顾SaaS版本和私有化版本的交付流程。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零三):骞云科技DevOps实践

    JetLee 发表了文章 • 0 个评论 • 845 次浏览 • 2019-02-28 22:23 • 来自相关话题

    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + J ...查看全部
    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + Jenkins + CMP(Ansible)共同构建了更好的 CI/CD 流程,对自动化持续交付流水线进行了优化;另一方面,CMP(Self-Service Portal)帮助建立了自服务自运维门户,公司所有人员都可以通过统一的门户自助申请各类资源,并自助完成日常运维。
    #为什么我们要加强 DevOps?

    在公司创立早期,为了尽快实现产品从0到1的转化,我们将更多的资源投入到了产品的新功能开发上,在产品开发自动化方面的投入并不高。
    随着公司业务的迅速发展,一方面,团队规模不断扩大,服务器资源也越来越多;另一方面,产品的功能逐渐丰富,开发代码工程数和分支数增加,而开发测试和资源管理仍以半自动化为主。

    面临的问题:
    ##人力资源浪费

    手工打包、手工创建虚机、手工部署、手工升级、较低程度的自动化测试,这些重复且低效的开发测试模式导致开发测试人员不能将宝贵的时间用于更加有创造力的工作上,不利于个人和公司的快速发展。
    ##IaaS资源管理混乱

    我们的开发测试环境主要构建在内部的vSphere和OpenStack云台上,当然也会在Aliyun、AWS、Azure等公有云上创建资源。在日常工作过程中,我们经常会听到这样的声音:“我的环境怎么这么卡”、“阿里云上又没钱啦”、“OpenStack上的机器我要删了啊”、“谁删了我的机器,我的数据还在上面呢”。由于没有权限控制导致资源随意创建,资源不及时释放导致大量资源闲置和浪费,另外还存在资源误删除情况。
    ##内部系统运维成本居高不下

    我们没有专职的内部系统运维人员,平时开发过程中,遇到CPU/Memory调整、磁盘物理卷/逻辑卷扩容、操作系统故障、应用故障等一系列问题都会占用研发人员大量的时间和精力。
    ##产品交付难度大

    为满足稳定性、高可用性、可扩展性等交付需求,我们的产品在软件架构设计上具有较高的复杂度,这样一来安装部署实施的难度也就比较大。售前团队到客户现场做POC,想要快速部署一套公司产品比较困难;售后团队在项目交付的过程中也经常遇到各种各样的安装配置问题。

    基于上述问题,我们希望通过对DevOps工作流程进行改造和增强,以提高产品开发效率和交付效率,以及提升个人生产力和资源利用率。
    #DevOps整体规划

    我们将DevOps工作流程改造分为了两个方面,一个是对CI/CD工作流的优化,一个是搭建自服务自运维门户。
    ##CI/CD目标


    * 所有代码工程能够自动化打包
    * 所有代码提交后能够自动构建编译检查以及单元测试任务
    * 每小时完成一次软件集成、部署以及核心功能的集成测试(API&UI)
    * 每天完成一次完整功能的集成测试(API&UI)
    * 每周完成一次7x24小时Longrun系统测试
    * 自动更新经过测试的nightly build到开发测试环境
    * 自动发布经过测试的weekly build到Demo环境

    ##自服务自运维目标

    公司所有人员,都可以通过一个统一门户Portal,自助申请各种类型的IaaS资源,如: x86物理服务器、vSphere虚拟机、OpenStack虚拟机、Kubernetes上的容器服务,以及Aliyun、AWS、Azure等公有云上的云资源;自助申请日常开发所需的软件应用,如:Nginx、Tomcat、MySQL、RabbitMQ,以及SmartCMP等。

    * 开发Dev,测试QA,售前交付需要使用不同的资源,做到资源隔离;
    * 资源的使用需要有权限控制;
    * 需要能够一键部署单节点和HA多节点应用系统;
    * 提供环境自动初始化,一键升级能力;
    * 提供系统和应用级别的监控告警;
    * 资源需要能够定期回收。

    #构建更好的CI/CD流程

    ##概述

    我们的DevOps工具链由 GitLab、Gerrit、Jenkins、CMP 构成。

    * GitLab:代码托管
    * Gerrit:代码审查
    * Jenkins:单元测试、自动化打包、集成测试
    * CMP:vSphere、OpenStack、Kubernetes等云资源统一管理,应用系统自动化部署,版本更新

    具体的工作流如下图所示:
    01.png

    开发人员提交代码后,触发Jenkins完成代码编译检查和单元测试,Jenkins返回代码检查结果给Gerrit,人工Review后merge代码,触发Jenkins完成该项目的打包。Jenkins定时完成各个工程的集成打包,然后通过调用CMP的API触发自动化部署,部署完成后进行场景化的集成测试,测试完成后卸除资源。
    02.png

    ##持续集成(Jenkins)

    起初我们使用GitLab Runner实现CI,在每个代码工程中添加“.gitlab-ci.yaml”文件,不同项目各自创建和维护自己的.gitlab-ci.yaml脚本,这样的实现可以解决各自工程的编译测试和打包问题,在代码工程数量较少时,我们也使用了较长一段时间。

    现在我们的代码工程数量已超过20个,每个代码工程都设置了访问权限,如果需要专人维护CI脚本的话,那他需要能够访问所有代码工程,显然这样是不合理的,而且把集成打包脚本放在哪个工程里都不合适。

    考虑到Jenkins有强大的CI能力:通过安装插件就能快速与Gerrit、GitLab集成,能够参数化执行各种类型的脚本。所以,我们使用Jenkins代替gitlab-runner完成CI,通过Jenkins可以统一管理各个工程的编译、测试、打包,而且比较方便构建流水线完成较多工程集成打包及测试。
    03.png

    ##持续部署(Ansible)

    我们的产品由20多个服务组成,可部署在一个或多个虚拟机上,使用Shell脚本或Python脚本已经很难完成这么多服务程序的自动化安装部署配置。

    恰巧团队有使用Ansible做复杂系统部署的经验,Ansible的学习成本也较低,所以我们选择使用Ansible Playbook 实现这20多个服务程序的统一编排部署和配置,并且可以同时支持单节点和HA多节点自动化部署。

    下图是Ansible自动化部署拓扑图:
    04.png

    我们设计Ansible Playbook时,将每个服务都独立成一个角色,这样保证了各个服务部署的独立性,这种分布式部署架构为将来容器化部署和微服务化奠定了基础。

    Ansible自动化标准化部署不仅大大缩短了部署时间,也极大地降低了部署出错的概率。原先,按照HA架构部署一套产品需要1天时间来完成各个服务的部署和配置,通过使用Ansible playbook,我们只需要45分钟,而且中间过程完全可以放手去做别的事情。
    ##集成测试(Robot Framework)

    目前,我们在Jenkins中使用Robot Framework框架做集成测试。Robot Framework(以下简称RF)是一个基于Python的、可扩展的、关键字驱动的测试自动化框架。

    选用RF的原因:

    * 一致性:目前公司的UI自动化测试使用的就是RF框架,RF框架也完全有能力做集成测试,因此使用RF框架做集成测试,可以降低学习成本,提高可维护性。
    * 复用性:在安装了Robot-Framework-JMeter-Library后,RF可以运行JMeter脚本,并且将JMeter运行结果转为Html格式。公司目前性能测试用的就是JMeter,对于相同场景,只要小幅修改JMeter脚本即可将其复用到集成测试上面。

    05.png

    选用RF也存在一些问题:

    * 如果不复用JMeter脚本,编写的API测试用例的成本非常高。
    RF对于变量类型的规定堪称僵硬(当然,这么规定带来的好处是方便类型检测),RF中对于字典类型的创建非常麻烦(嵌套的字典实例如下),对于我们公司API请求中携带大量参数的情况,只能创建关键字来解决,不管是采取RF自带创建字典的方法,还是创建关键字的方法,都比较浪费时间(因为难以复用)。
    06.png

    * RF可以轻松扩展关键字,也因此可能带来乱扩展关键字的问题,导致测试用例可读性和可维护性差的问题。

    在RF中,关键字其实就是Python/Java的类方法,因此扩展起来非常容易,但是关键字一旦多起来,一个同事写的测试用例,其他人(甚至他自己过了一段时间)维护就非常麻烦(需要回去看关键字是如何规定的)。因此需要严格规定关键字的创建规范是一件值得深入讨论的事情。
    #建立自服务自运维门户

    我们使用云管理平台(以下简称CMP,Cloud Management Platform)管理公司内部资源,使得公司所有人员,都可以通过CMP提供的自服务门户(Self-Service Portal),完成计算/存储/网络等IaaS资源和软件应用自助申请,并且能够自助进行日常运维操作。
    ##CMP平台准备工作

    通过LDAP方式将公司AD账户导入CMP平台中,为开发、测试、售前售后团队创建不同的业务组和资源池,每个资源池给到不同的资源配额,做到资源合理分配和资源相互隔离。为每个业务组设定一个管理员,审批业务组成员的资源申请。
    07.png

    我们使用Shell、Python脚本或Ansible配置管理工具将内部常用的一些软件及应用系统的安装过程进行封装,并发布到CMP平台中,提供标准化蓝图方便大家申请。
    08.png

    ##自服务自运维门户

    在门户中,大家可以看到已经发布好的服务卡片,通过点击服务卡片即可完成IaaS资源或应用系统的自助申请,在平时的开发测试过程中,我们不再需关心底层复杂的系统或网络配置。
    09.png

    在门户中,大家也可以清晰地看到自己所管理的资源的性能情况,还可以简单便捷地完成一些日常的基础运维操作:重启、调整配置、添加逻辑卷、扩展逻辑卷等。
    10.png

    此外,使用管理账号登录CMP管理平台,可以清晰地看到公司内部资源的总体使用情况。
    11.png

    12.png

    13.png

    #总结与建议

    在骞云科技的DevOps实践中,一方面,我们将GitLab、Gerrit、Jenkins、Ansible、JMeter、Robot Framework等成熟的开源工具开源技术和企业内部的云管理平台相结合,实现了较高程度的开发测试流程自动化,推动了产品的持续集成和持续交付,减少了大量的重复劳动,提高了开发测试效率。

    另一方面,通过使用云管理平台,将复杂异构的IaaS资源服务化,降低了使用难度;结合业务部门需求合理划分资源,减少了资源浪费,加强了资源的有效隔离,避免了误操作;自服务自运维的模式,也极大地提升了公司整体研发效率。

    我们的DevOps实践方案适用的场景非常多样,比如:弹性伸缩、迁移、负载均衡。在传统IT、金融、互联网、游戏等行业也具有普适性。
    #未来发展方向

    在介绍Ansible自动化部署时有提到,我们的业务系统由20多个服务组成,符合服务化的架构设计,目前已经可以满足私有化的部署需求。随着新功能的不断引入,部分业务子系统复杂度和团队开发耦合度会逐渐升高,协作效率和部署效率会变得低下。另外,当前的软件架构和部署架构不能满足将来的SaaS化部署。所以,我们仍需要将服务进行更细粒度的拆分,逐步向微服务架构转变并使用容器化部署,进一步降低开发和部署成本。
    #Q&A

    Q:CMP和各个云平台打通都使用了平台的jar,并且需要各种资源生成,这个工作量也不小吧?并且如果api更新代码量也大吧?
    A:我们的核心业务就是做云管理平台,我们产品已经完成了对各个云平台的对接,主要调用各个云平台的API。公有云的API更新频率并不是很高,每当API有更新时,我们也及时去适配。

    Q:Jenkins初次提交也能触发构建吗?每次自动化构建版本号是如何更新的呢?
    A:我们的项目代码具备构建条件后,才在Jenkins上创建了项目构建Job,所以并没有在初次提交时触发构建。每次构建的版本号由两部分组成,一部分是产品的Release大版本号,另一部分直接使用的Jenkins build number这个环境变量。

    Q:有了Gerrit,为什么还要GitLab,Gerrit也可以托管代码啊?
    A:这个是有历史背景的,我们是先选择使用GitLab做代码托管,后期才加入Gerrit做code review。Gerrit在代码review方面比GitLab的merge request要方便许多,更适合企业内部使用。关于这个,我的想法是,要么将GitLab迁移到Gerrit,要么不用Gerrit,可以使用GitLab的merge request来进行review,那GitLab其实是可以不要的。

    以上内容根据2019年2月26日晚微信群分享内容整理。分享人夏飞,骞云科技SmartCMP云管理平台后端开发负责人,负责云管理平台的建设和推广,目前负责公司内部DevOps工作流程的改造。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零二):房多多Service Mesh实践

    大卫 发表了文章 • 0 个评论 • 837 次浏览 • 2019-02-22 10:13 • 来自相关话题

    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。 #概述和 ...查看全部
    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。
    #概述和需求背景

    房多多国内领先的经纪人直卖平台,目前房多多APP和多多卖房APP的后端服务主要运行在自建的IDC机房。2018年之前,我们的服务都是运行在VM上,也同时基本上完成了基于Dubbo的微服务改造,目前的语言技术栈主要有:Java、Node.js、Golang。相对于其他公司而言,技术栈不是特别分散,这给我们服务容器化带来了极大的便利。

    我们在2018年Q3的时候已经完成大部分服务容器化,此时我们的微服务数量已经超过400个了,在沟通和维护上的成本很高。因为我们后端服务是主要是基于Dubbo的,跟前端直接的交互还是通过http的方式,在没有上Service Mesh之前,http请求都是经过Nginx转发的。维护Nginx的配置文件是一个工作量大且重复性高的事情,运维团队和业务团队迫切的需要更高效的方案来解决配置管理和沟通成本的问题,构建房多多Service Mesh体系就显得尤为重要。Envoy是一个成熟稳定的项目,也能够满足近期的需求,在现阶段我们并没有人力去动Envoy,所以我们直接使用了Envoy做Service Mesh的数据平面,关于控制平面这块,在调研了一些方案之后,我们采用了自研的方案。
    #整体平台架构

    我们的容器平台整体是基于物理机的,除了调度节点是用的虚拟机以外,所有的工作节点都是使用物理机。之前我们的虚拟化方案是用的Xenserver,Xenserver对高版本的内核支持并不好,会时不时的出现自动虚拟机关机的bug,所以我们在工作节点上只使用物理机以保障业务的稳定和高效。

    我们的业务主要工作在自建IDC机房,公有云只有少量的灾备服务。因为是自建机房,相比较公有云而言,自建机房使用Macvlan是一个比较好的方案。我们预先划分了几个20位地址的子网,每个子网内配置一些物理机,通过集中式的IP管理服务去管理物理机和容器的IP。相比较bridge网络,Macvlan网络有着非常接近物理网络的性能,尤其是在大流量场景下性能出色,下面一张图显示了性能对比:
    1.png

    2.png

    3.png

    我们把Envoy作为两个网络之间的连接桥,这么做的好处在于可以控制流量都经过负载均衡器,便于集中管理以及对流量做分析。看到这里,肯定有疑问是为什么不使用Sidecar的方式部署Envoy。关于Sidecar我的考虑是,在现有的业务场景下,集中部署的维护成本更低且性能满足需求,也相对来说比较简单。我们在2018年Q4已经完成主要业务http2接入,目前来看,我们的网站速度应该是同行业中最快的,大家可以体验一下,https://m.fangdd.com 。
    4.png

    #如何解决虚拟机业务和容器业务的并存问题

    我们原有的架构大量使用了虚拟机,迁移虚拟机上面的服务是一个漫长的过程,当前阶段还需要解决业务的并存问题,我们自己开发了Envoy对应的配置集中管理服务,同时支持虚拟机服务和容器服务。

    控制平面服务主要基于data-plane-api开发,功能上主要是给数据平面提供服务的集群配置、路由配置等信息,并且需要实现微服务架构中降级和限流的配置功能。容器内部的集群数据主要依赖DNSRR实现,而虚拟机上的服务,我们在CMDB上存有AppID和机器的对应关系,很容器生成集群配置数据。


    由于历史原因,还有相当多的业务无法从VM迁移到容器,需要有一个同时兼容容器和VM的数据平面服务,目前XDS服务的支持的功能如下:

    * 集群数据来源同时包括容器内部DNS和外部CMDB中的VM数据
    * 支持多个 vhost 配置
    * 支持路由配置
    * 支持速率控制和网关错误重试

    5.png

    #应用开发流程变化

    初步建设起Service Mesh体系之后,理论上业务开发只需要开发一个单体服务,在服务间互相调用的过程中,只需要知道服务名即可互相调用,这就很像把所有服务都看做一个微服务一样,所以我们的业务开发流程发生了以下变化:
    6.png

    同时也降低了框架开发成本和业务改动的成本,每次推动业务升级框架都需要比较长的一段时间,业务无法及时用上新框架的功能,多种框架版本也加重运维负担,Service Mesh帮我们解决了很多痛点。同时再加上我们的网关层建设,我们上线一个新服务几乎是零配置成本的。

    * 代理层实现服务发现,对于开发而言只需要开发一个单机的应用,降低框架开发成本
    * 降级和限流都在代理层实现,规则灵活,方便修改策略
    * 框架功能的升级无需依赖业务

    #SOA和Service Mesh的对比与取舍

    在我们的Service Mesh实践中,增加了链路的请求长度,并且服务的链路越长,链路请求的放大效应会越明显,这就带来了一些性能上面的担忧。毫无疑问,Mesh层本身的业务逻辑开销是不大,但是在网络传输和内存复制上的性能消耗在一定程度上会影响链路的性能,Envoy也在探索相关的方案来优化网络传输性能,如Bpfilter和VPP,减少用户态和内核态的内存拷贝。在可定制性层面,SOA能做的事情也相对较多,可以实现很多hack的需求。

    在我们现有的业务场景下,Service Mesh主要还是解决前后端的微服务对接问题,当做前后端服务的连接桥梁。但不可否认的是,Service Mesh带来研发效率的提升,在现有的场景下的价值远大于性能上的损失。在大多数的场景下,维护业务框架需要比较大的人力成本,很多团队都没有全职的人去维护业务框架。此外,推动业务框架的更新和升级也相对来说成本较高,这也是我们考虑的一个重要方面。
    #总结

    得益于云原生架构,Service Mesh可以使用云原生的基础设施,基础设施能力的改进可以直接赋能业务,而不像传统的框架一样,基础设施的升级和改进无法提高传统框架的服务能力。房多多的Service Mesh还处于初级阶段,后面还将继续探索。
    #Q&A

    Q:容器和微服务的区别以及它们的关联性、应用场景、客户群体、带来的价值?
    A:微服务的应用场景主要是解决降低单个服务体积,满足业务的快速开发需求。容器可以说是微服务的载体,容器方面还是运维关注的比较多,带来的标准化流程和环境的提升对整个研发团队都有很大的作用。

    Q:软件实现 Service Mesh,Istio?
    A:我们目前只使用了Envoy,Istio也做过一些选型的调研,不是很符合我们现在的业务场景和业务需求,我们在之后的实践中会考虑使用一部分Istio的功能。

    Q:实施过程当中有使用到Istio吗?有定制一些Mixer适配器吗?
    A:目前还没有用,之后会考虑是用Istio中的pilot,我们目前在流量的控制的精细程度上面还欠缺,粒度还很粗。

    Q:请问,实现微服务过程中有没有考虑分布式跟踪和分布式?
    A:Service Mesh层可以做到简单的分布式追踪,比如可以做到基于请求的追踪,Envoy可以把trace数据接入Zipkin这样的trace系统,但是更细粒度的trace目前还做不到。

    Q:不论是使用都会产生大量的配置(yaml),尤其是Envoy/Istio,系统中会有大量零散的配置文件,维护困难,还有版本管理,有什么很好的维护实践经验吗?谢谢。
    A:是的,据我所知有些团队会使用ConfigMap来管理配置,我们做了一个配置的集中管理服务,从CMDB和DNS定时的抓取数据,数据会存在数据库里面,也会存一定量的副本用于配置回退,这个地方还是要结合你们现在其他配套系统的建设来看看怎么做比较好的。

    Q:有没有遇到过Envoy被oom kill的情况,你们是如何处理的?
    A:这个我有碰到过一次,之前我们在对Envoy做测试的时候,发现Envoy总会尽可能的占满CGroup的内存大小,这个之前在使用TLS的情况下碰到的比较多。但是目前我们内部服务间使用TLS的情况并不多,所以后续这个问题就没有继续跟进了。

    Q:性化方案有哪些?
    A:之前文章中有提到过,对于http服务可以全量接入http2,http2的长连接和多路复用对于一般的业务来说升是很明显的,我们在全量接入http2之后,网站的响应时间几乎下降了50%。另外还可以在底层的依赖上面做一些优化,比如底层的libc库,以及尽可能的减少基础镜像的大小,我们基本上所有业务都使用了alpine,这样可以保证发布性能和速度在一个很好的水平上。

    Q:还是有一个服务治理/配置管理的问题请教,比如CPU,内存,这种资源request,在dev,test,staging,prod环境均不同,那么我们在编写Kubernetes配置的时候不同环境会不同,比如测试环境的replics数跟CPU需求就很低,生产就很高,另外这个配置在不同环境是多个还是一个呢?谢谢。
    A:我们现在会在CMDB里面维护这些配置数据,一般来说在新建项目的时候,我们就会要求业务线评估一下这个业务需要的资源,比如你说到的test和staging环境这种,我们默认会给一个很低的需求,比如1c1g这样的,而且replication也会默认设置为1,除非业务有特殊的需求,然后我们去根据配置的数据去生成yaml格式为配置。
    配置在数据存储的形式上是多个,但是在对业务展示上,尽量让业务感觉到是一个数据,这样也能使每个环境都规范起来,跟生产环境尽可能保持一致,这个对测试的好处还是很大的。

    Q:你们目前的项目中,大量的微服务,以及调度层,瓶颈和容灾是如何处理的?
    A:由于我们的业务类型还是B端业务为主,流量的峰值是可以预估的,很少会出现突发的大流量的情况,我们一般都预留了1倍的容量用于临时的扩容。容灾和调度的话我们主要还是尽可能的隔离工作节点和调度节点,以及大量使用物理机集群,从我们的使用体验上来看,物理机的稳定性还是很不错的。

    Q:如何用Jenkins自动完成Kubernetes部署?
    A:自动部署这块我们有完善的发布系统,如果单纯只要实现Jenkins自动完成Kubernetes的话,Jenkins可以直接调用Kubernetes的API,这个网上有挺多资料的,你可以找找看。

    Q:Service Mesh比传统的微服务治理好在哪里?
    A:降低框架开发成本、代理规则灵活,方便修改策略、框架功能的升级无需依赖业务,最大的好处我觉得就是可以降低框架开发成本。

    Q:我理解房多多目前的Mesh方案没有给每个微服务配一个Envoy作为Sidecar,而是使用一组Enovy并自研了xDS的配置发布管理系统,对吗?我想请问backend微服务之间的请求现在是怎么走的呢?谢谢。
    A:是的,刚刚文章中说了,我们后端SOA服务还是基于Dubbo的,这块目前我们还没有做改动,之后的话我们的初步构想会通过Sidecar的方式把这些Dubbo服务都接入到Mesh里面来。我们目前的Envoy也是会充当网关的角色。

    以上内容根据2019年2月21日晚微信群分享内容整理。分享人杜雅林,房多多平台工具负责人,负责容器平台、发布系统、Service Mesh相关功能开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二一一):基于Actor模型的CQRS/ES解决方案分享

    JetLee 发表了文章 • 0 个评论 • 325 次浏览 • 2019-06-05 16:17 • 来自相关话题

    【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将 ...查看全部
    【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将从不同视角为大家介绍Actor模型、CQRS/ES架构以及Service Fabric在高并发场景中的考量和应用。

    最近一段时间我一直是这个话题的学习者、追随者,这个话题目前生产环境落地的资料少一些,分享的内容中有一些我个人的思考和理解,如果分享的内容有误、有疑问欢迎大家提出,希望通过分享这种沟通方式大家相互促进,共同进步。
    # 引言
    本话题由三部分组成:

    • Actor模型&Orleans:在编程的层面,从细粒度-由下向上的角度介绍Actor模型;
    • CQRS/ES:在框架的层面,从粗粒度-由上向下的角度介绍Actor模型,说明Orleans技术在架构方面的价值;
    • Service Fabric:从架构部署的角度将上述方案落地上线。
    群里的小伙伴技术栈可能多是Java和Go体系,分享的话题主要是C#技术栈,没有语言纷争,彼此相互学习。比如:Scala中,Actor模型框架有akka,CQRS/ES模式与编程语言无关,Service Fabric与Kubernetes是同类平台,可以相互替代,我自己也在学习Kubernetes。# Actor模型&Orleans(细粒度)##共享内存模型多核处理器出现后,大家常用的并发编程模型是共享内存模型。
    1.png
    这种编程模型的使用带来了许多痛点,比如:
    • 编程:多线程、锁、并发集合、异步、设计模式(队列、约定顺序、权重)、编译
    • 无力:单系统的无力性:①地理分布型、②容错型
    • 性能:锁,性能会降低
    • 测试:
    - 从坑里爬出来不难,难的是我们不知道自己是不是在坑里(开发调试的时候没有热点可能是正常的) - 遇到bug难以重现。有些问题特别是系统规模大了,可能运行几个月才能重现问题
    • 维护:
    - 我们要保证所有对象的同步都是正确的、顺序的获取多个锁。 - 12个月后换了另外10个程序员仍然按照这个规则维护代码。简单总结:
    • 并发问题确实存在
    • 共享内存模型正确使用掌握的知识量多
    • 加锁效率就低
    • 存在许多不确定性
    ##Actor模型Actor模型是一个概念模型,用于处理并发计算。Actor由3部分组成:状态(State)+行为(Behavior)+邮箱(Mailbox),State是指actor对象的变量信息,存在于actor之中,actor之间不共享内存数据,actor只会在接收到消息后,调用自己的方法改变自己的state,从而避免并发条件下的死锁等问题;Behavior是指actor的计算行为逻辑;邮箱建立actor之间的联系,一个actor发送消息后,接收消息的actor将消息放入邮箱中等待处理,邮箱内部通过队列实现,消息传递通过异步方式进行。
    2.png
    Actor是分布式存在的内存状态及单线程计算单元,一个Id对应的Actor只会在集群种存在一个(有状态的 Actor在集群中一个Id只会存在一个实例,无状态的可配置为根据流量存在多个),使用者只需要通过Id就能随时访问不需要关注该Actor在集群的什么位置。单线程计算单元保证了消息的顺序到达,不存在Actor内部状态竞用问题。举个例子:多个玩家合作在打Boss,每个玩家都是一个单独的线程,但是Boss的血量需要在多个玩家之间同步。同时这个Boss在多个服务器中都存在,因此每个服务器都有多个玩家会同时打这个服务器里面的Boss。如果多线程并发请求,默认情况下它只会并发处理。这种情况下可能造成数据冲突。但是Actor是单线程模型,意味着即使多线程来通过Actor ID调用同一个Actor,任何函数调用都是只允许一个线程进行操作。并且同时只能有一个线程在使用一个Actor实例。##Actor模型:OrleansActor模型这么好,怎么实现?可以通过特定的Actor工具或直接使用编程语言实现Actor模型,Erlang语言含有Actor元素,Scala可以通过Akka框架实现Actor编程。C#语言中有两类比较流行,Akka.NET框架和Orleans框架。这次分享内容使用了Orleans框架。特点:Erlang和Akka的Actor平台仍然使开发人员负担许多分布式系统的复杂性:关键的挑战是开发管理Actor生命周期的代码,处理分布式竞争、处理故障和恢复Actor以及分布式资源管理等等都很复杂。Orleans简化了许多复杂性。优点:
    • 降低开发、测试、维护的难度
    • 特殊场景下锁依旧会用到,但频率大大降低,业务代码里甚至不会用到锁
    • 关注并发时,只需要关注多个actor之间的消息流
    • 方便测试
    • 容错
    • 分布式内存
    缺点:
    • 也会出现死锁(调用顺序原因)
    • 多个actor不共享状态,通过消息传递,每次调用都是一次网络请求,不太适合实施细粒度的并行
    • 编程思维需要转变
    3.png
    第一小节总结:上面内容由下往上,从代码层面细粒度层面表达了采用Actor模型的好处或原因。# CQRS/ES(架构层面)##从1000万用户并发修改用户资料的假设场景开始
    4.png
    [list=1]
  • 每次修改操作耗时200ms,每秒5个操作
  • MySQL连接数在5K,分10个库
  • 5 5k 10=25万TPS
  • 1000万/25万=40s
  • 5.png
    在秒杀场景中,由于对乐观锁/悲观锁的使用,推测系统响应时间更复杂。##使用Actor解决高并发的性能问题
    6.png
    1000万用户,一个用户一个Actor,1000万个内存对象。
    7.png
    200万件SKU,一件SKU一个Actor,200万个内存对象。
    • 平均一个SKU承担1000万/200万=5个请求
    • 1000万对数据库的读写压力变成了200万
    • 1000万的读写是同步的,200万的数据库压力是异步的
    • 异步落盘时可以采用批量操作
    总结:由于1000万+用户的请求根据购物意愿分散到200万个商品SKU上:每个内存领域对象都强制串行执行用户请求,避免了竞争争抢;内存领域对象上扣库存操作处理时间极快,基本没可能出现请求阻塞情况;从架构层面彻底解决高并发争抢的性能问题。理论模型,TPS>100万+……##EventSourcing:内存对象高可用保障Actor是分布式存在的内存状态及单线程计算单元,采用EventSourcing只记录状态变化引发的事件,事件落盘时只有Add操作,上述设计中很依赖Actor中State,事件溯源提高性能的同时,可以用来保证内存数据的高可用。
    8.png
    9.png
    ##CQRS上面1000万并发场景的内容来自网友分享的PPT,与我们实际项目思路一致,就拿来与大家分享这个过程,下图是我们交易所项目中的架构图:
    10.png
    开源版本架构图:
    11.png
    开源项目GitHub:https://github.com/RayTale/Ray第二小节总结:由上往下,架构层面粗粒度层面表达了采用Actor模型的好处或原因。# Service Fabric 系统开发完成后Actor要组成集群,系统在集群中部署,实现高性能、高可用、可伸缩的要求。部署阶段可以选择Service Fabric或者Kubernetes,目的是降低分布式系统部署、管理的难度,同时满足弹性伸缩。交易所项目可以采用Service Fabric部署,也可以采用K8S,当时K8S还没这么流行,我们采用了Service Fabric,Service Fabric 是一款微软开源的分布式系统平台,可方便用户轻松打包、部署和管理可缩放的可靠微服务和容器。开发人员和管理员不需解决复杂的基础结构问题,只需专注于实现苛刻的任务关键型工作负荷,即那些可缩放、可靠且易于管理的工作负荷。支持Windows与Linux部署,Windows上的部署文档齐全,但在Linux上官方资料没有。现在推荐Kubernetes。第三小节总结:[list=1]
  • 借助Service Fabric或K8S实现低成本运维、构建集群的目的。
  • 建立分布式系统的两种最佳实践:

  • - 进程级别:容器+运维工具(Kubernetes/Service Fabric)
    - 线程级别:Actor+运维工具(Kubernetes/Service Fabric)

    上面是我对今天话题的分享。

    CQRS/ES部分内容参考:《领域模型 + 内存计算 + 微服务的协奏曲:乾坤(演讲稿)》 2017年互联网应用架构实战峰会。
    #Q&A
    Q:单点故障后,正在处理的Cache数据如何处理的,例如,http、tcp请求……毕竟涉及到钱?
    A:actor有激活和失活的生命周期,激活的时候使用快照和Events来恢复最新内存状态,失活的时候保存快照。actor框架保证系统中同一个key只会存在同一个actor,当单点故障后,actor会在其它节点重建并恢复最新状态。

    Q:event ID生成的速度如何保证有效的scale?有没有遇到需要后期插入一些event,修正前期系统运行的bug?有没有遇到需要把前期已经定好的event再拆细的情况?有遇到系统错误,需要replay event的情况?
    A:当时项目中event ID采用了MongoDB的ObjectId生成算法,没有遇到问题;有遇到后期插入event修正之前bug的情况;有遇到将已定好的event修改的情况,采用的方式是加版本号;没有,遇到过系统重新迁移删除快照重新replay event的情况。

    Q:数据落地得策略是什么?还是说就是直接落地?
    A:event数据直接落地;用于支持查询的数据,是Handler消费event后异步落库。

    Q:actor跨物理机器集群事务怎么处理?
    A:结合事件溯源,采用最终一致性。

    Q:Grain Persistence使用Relational Storage容量和速度会不会是瓶颈?
    A:Grain Persistence存的是Grain的快照和event,event是只增的,速度没有出现瓶颈,而且开源版本测试中PostgreSQL性能优于MongoDB,在存储中针对这两个方面做了优化:比如分表、归档处理、快照处理、批量处理。

    Q7:开发语言是erlang吗?Golang有这样的开发模型库支持吗?
    A:开发语言是C#。Golang我了解的不多,proto.actor可以了解一下:https://github.com/AsynkronIT/protoactor-go。

    Q:每个Pod的actor都不一样,如何用Kubernetes部署actor,失败的节点如何监控,并借助Kubernetes自动恢复?
    A:actor是无状态的,失败恢复依靠重新激活时事件溯源机制。Kubernetes部署actor官方有支持,可以参考官方示例。在实际项目中使用Kubernetes部署Orleans,我没有实践过,后来有同事验证过可以,具体如何监控不清楚。

    Q:Orleans中,持久化事件时,是否有支持并发冲突的检测,是如何实现的?
    A:Orleans不支持;工作中,在事件持久化时做了这方面的工作,方式是根据版本号。

    Q:Orleans中,如何判断消息是否重复处理的?因为分布式环境下,同一个消息可能会被重复发送到actor mailbox中的,而actor本身无法检测消息是否重复过来。
    A:是的,在具体项目中,通过框架封装实现了幂等性控制,具体细节是通过插入事件的唯一索引。

    Q:同一个actor是否会存在于集群中的多台机器?如果可能,怎样的场景下可能会出现这种情况?
    A:一个Id对应的Actor只会在集群种存在一个。

    以上内容根据2019年6月4日晚微信群分享内容整理。分享人郑承良,上海某科技公司架构师,对高并发场景下的分布式金融系统拥有丰富的实战经验,曾为澳大利亚、迪拜多家交易所提供技术支持。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二一零):平安证券Kubernetes容器集群的DevOps实践

    齐达内 发表了文章 • 0 个评论 • 381 次浏览 • 2019-05-29 11:54 • 来自相关话题

    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同 ...查看全部
    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同行有所借鉴和帮助。
    #生产环境的高可用Master部署方案
    Kubernetes的高可用Master部署,现在网络上成熟的方案不少。大多数是基于Haproxy和Keepalived实现VIP的自动漂移部署。至于Haproxy和Keepalived,可独立出来,也可寄生于Kubernetes Master节点。

    我司在IT设备的管理上有固定的流程,VIP这种IP地址不在标准交付范围之内。于是,我们设计了基于DNS解析的高可用方案。这种方案,是基于Load Balancer变形而来。图示如下:
    1.png

    这种构架方案,平衡了公司的组织结构和技术实现。如果真发生Master挂掉,系统应用不受影响,DNS的解析切换可在十分钟内指向新的Master IP,评估在可接受范围之内。

    公司内部安装Master节点时,使用的基本工具是Kubeadm,但是作了脚本化改造及替换成了自己的证书生成机制。经过这样的改进之后,使用kubeadm进行集群安装时,就更有条理性,步骤更清晰,更易于在公司进行推广。

    底层的etcd集群使用独立的Docker方式部署,但共享kubeadm相关目录下的证书文件,方便了api-server和etcd的认证通信。脚本的相关配置如下:
    2.png

    当以DNS域名的形式进行部署后,各个证书配置认证文件,就不会再以IP形式连接,而是以DNS域名形式连接api-server了。如下图所示:
    3.png

    #分层的Docker镜像管理
    接下来,我们分享一下对Docker镜像的管理。Docker的企业仓库,选用的是业界流行的Harbor仓库。根据公司研发语言及框架的广泛性,采用了三层镜像管理,分为公共镜像,业务基础镜像,业务镜像(tag为部署发布单),层层叠加而成,即形成标准,又照顾了一定的灵活性。

    * 公共镜像:一般以alpine基础镜像,加上时区调整,简单工具。
    * 业务基础镜像:在公共镜像之上,加入JDK、Tomcat、Node.js、Python等中间件环境。
    * 业务镜像:在业务基础镜像之上,再加入业务软件包。

    4.png

    #Dashboard、Prometheus、Grafana的安全实践
    尽管在Kubernetes本身技术栈之外,我司存在体系化的日志收集,指标监控及报警平台,为了运维工具的丰富,我们还是在Kubernetes内集成了常用的Dashboard、Prometheus、Grafana组件,实现一些即时性运维操作。

    那么,这些组件部署,我们都纳入一个统一的Nginx一级url下,二级url才是各个组件的管理地址。这样的设计,主要是为了给Dashborad及Prometheus增加一层安全性(Grafana自带登陆验证)。

    这时,可能有人有疑问,Dashboard、kubectl都是可以通过cert证书及RBAC机制来实现安全性的,那为什么要自己来引入Nginx作安全控制呢?

    在我们的实践过程中,cert证书及RBAC方式,结合SSH登陆帐号,会形成一系列复杂操作,且推广难度高,我们早期实现了这种模式,但目前公司并不具备条件,所以废弃了。公司的Kubernetes集群,有专门团队负责运维,我们就针对团队设计了这个安全方案。

    Prometheus的二级目录挂载参数如下:
    5.png

    Grafana的二级目录挂载参数如下:
    6.png

    Dashboard在Nginx里的配置如下:
    7.png

    #一个能生成所有软件包的Jenkins Job
    在CI流水线实践,我们选用的GitLab作为源代码管理组件,Jenkins作为编译组件。但为了能实现更高效标准的部署交付,公司内部实现一个项目名为prism(棱镜)的自动编译分发部署平台。在容器化时代,衍生出一个Prism4k项目,专门针对Kubernetes环境作CI/CD流程。Prism4k版的构架图如下所示:
    8.png

    在这种体系下,Jenkins就作为我们的一个纯编译工具和中转平台,高效的完成从源代码到镜像的生成。

    由于每个IT应用相关的变量,脚本都已组织好,放到Prism4k上。故而,Jenkins只需要一个Job,就可以完成各样各样的镜像生成功能。其主要Pipeline脚本如下(由于信息敏感,只列举主要流程,有删节):
    9.png

    在Jenkins中,我们使用了一个Yet Another Docker Plugin,来进行Jenkins编译集群进行Docker生成时的可扩展性。作到了编译节点的容器即生即死,有编译任务时,指定节点才生成相关容器进行打包等操作。
    #计算资源在线配置及应用持续部署
    在Prism4k平台中,针对Jenkins的Job变量是通过网页配置的。在发布单的编译镜像过程中,会将各个变量通过API发送到Jenkins,启动Jenkins任务,完成指定task任务。
    10.png

    Pod的实例数,CPU和内存的配置,同样通过Web方式配置。
    11.png

    在配置好组件所有要素之后,日常的流程就可以基于不同部门用户的权限把握,实现流水线化的软件持续交付。

    * 研发:新建发布单,编译软件包,形成镜像,上传Harbor库。
    * 测试:环境流转,避免部署操作污染正在进行中的测试。
    * 运维:运维人员进行发布操作。

    在FAT这样的测试环境中,为加快测试进度,可灵活的为研发人员赋予运维权限。但在更正式的测试环境和线上生产环境,作为金融行业的IT建设标准,则必须由运维团队成员操作。

    下面配合截图,了解一下更具体的三大步骤。

    1. 发布单
    12.png

    在Prism4k与Jenkins的API交互,我们使用了Jenkins的Python库。

    1. 环境流转
    13.png


    1. 部署
    14.png


    在部署操作过程中,会将这次发布的信息全面展示给运维同事,让运维同事可以进行再次审查,减少发布过程中的异常情况。
    #总结
    由于Kubernetes版本的快速更新和发布,我们对于其稳定性的功能更为青睐,而对于实验性的功能,或是需要复杂运维技能的功能,则保持理智的观望态度。所以,我们对Kubernetes功能只达到了中度使用。当然,就算是中度使用,Kubernetes的运维和使用技巧,还是有很多方面在此没有涉及到,希望以后有机会,能和各位有更多的沟通和交流。愿容器技术越来越普及,运维的工作越来越有效率和质量。
    #Q&A
    Q:镜像有进行安全扫描吗:
    A:外部基本镜像进入公司内部,我们基于Harbor内置的安全功能进行扫描。

    Q:Harbor有没有做相关监控,比如发布了多少镜像,以及镜像同步时长之类的?
    A:我们没有在Harbor上作扩展,只是在我们自己的Prism4k上,会统计各个项目的一些镜像发布数据。

    Q:有没有用Helm来管理镜像包?后端存储是用的什么,原因是?
    A:没有使用Helm。目前集群有存储需求时,使用的是NFS。正在考虑建基于Ceph的存储,因为现在接入项目越来越多,不同的需求会导致不同的存储。

    Q:想了解下目前贵公司监控的纬度和监控的指标和告警这块。
    A:监控方面,我公司也是大致大致划分为基础资源,中间件,业务指标三大块监控。方法论上也是努力在向业界提倡的RED原则靠拢。

    Q:想了解下,Yaml文件怎么管理的,可以自定义生成吗?
    A:我们的Yaml文件,都统一纳到Prism4k平台管理,有一些资源是可以自定义的,且针对不同的项目,有不同的Yaml模板,然后,透过django的模块功能统一作解析。熟悉Yaml书写的研发同事可以自己定义自己项目的Yaml模板。

    Q:Pipeline会使用Jenkinfile来灵活code化Pipeline,把Pipeline的灵活性和创新性还给开发团队,这比一个模板化的统一Pipeline有哪些优势?
    A:Pipeline的运行模式,采用单一Job和每个项目自定义Job,各有不同的应用场景。因为我们的Jenkins是隐于幕后的组件,研发主要基于Prism4k操作,可以相对减少研发的学习成本。相对来说,Jenkins的维护人力也会减少。对于研发各种权限比较高的公司,那统一的Job可能并不合适。

    Q:想了解下贵公司使用什么网络方案?Pod的网络访问权限控制怎么实现的?
    A:公司现在用的是Flannel网络CNI方案。同时,在不同的集群,也有作Calico网络方案的对比测试。Pod的网络权限,这块暂时没有,只是尝试Istio的可行性研究。

    Q: 一个Job生成所有的Docker镜像,如果构建遇到问题,怎么去追踪这些记录?
    A:在项目前期接入时,生成镜像的流程都作了宣传和推广。标准化的流程,会减少产生问题的机率。如果在构建中遇到问题,Prism4k的界面中,会直接有链接到本次建的次序号。点击链接,可直接定位到Console输出。

    Q:遇到节点Node上出现100+ Pod,Node会卡住,贵公司Pod资源怎么做限制的?
    A:我们的业务Pod资源,都作了limit和request限制。如果出现有卡住的情况,现行的方案是基于项目作拆分。Prism4k本身对多环境和多集群都是支持的。

    Q:多环境下,集中化的配置管理方案,你们选用的是哪个,或是自研的?
    A:我们现在正在研发的Prism4k,前提就是要支持多环境多集群的部署,本身的功能里,yaml文件的配置管理,都是其内置功能。

    Q:etcd的--initial-cluster-state选项设置为new,重启etcd后会不会创建新的etcd集群?还是加入原有的etcd集群?
    A:我们测试过轮流将服务器(3 Master)完全重启,ectd集群的功能均未受影响。但全部关机重启,还未测试过。所以不好意思,这个问题,我暂时没有考虑过。

    Q:网络方案用的什么?在选型的时候有没有对比?
    A:目前主要应用的还是Flannel方案,今年春节以来,还测试过Flannel、Caclico、kube-router方案。因为我们的集群有可能越机房,而涉及到BGP协议时,节点无法加入,所以一直选用了Flannel。

    Q:部署的动态过程是在Jenkins的Web界面上看还是在自研的Prism4k上能看到,如果是Prism4k的话,整个可视化过程的展示这些等等也是自己开发的吗?Prism4k是用什么语言开发的,Python吗?
    A:部署的动态过程,是在Prism4k上显示。可视化方案,也只是简单的使用ajax及websocket。Prism4k后端是基于Django 2.0以上开发,其中使用了RESTful framework、channels等库,前端使用了一些js插件。

    以上内容根据2019年5月28日晚微信群分享内容整理。分享人陈刚,平安证券运维研发工程师,负责经纪业务IT应用的持续交付平台的设计和开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零九):荔枝运维平台容器化实践

    Andy_Lee 发表了文章 • 0 个评论 • 861 次浏览 • 2019-04-30 12:01 • 来自相关话题

    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布 ...查看全部
    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布系统重构过程中,技术选型的考虑以及实践过程中遇到的一些问题和解决方案。
    #背景

    荔枝后端微服务化进程较早,目前已有上千个服务模块,绝大多数是Java。得益于良好的规范,旧的运维平台实现了一套简单的自动化部署流程:

    1. 对接Jenkins进行编译打包和版本标记
    2. 把指定版本的jar包,配置文件,启动脚本一起发布到指定的机器上裸机运行
    3. 通过对Java进程的管理来完成重启,关闭应用等运维操作

    但是随着开发人员,项目数量,请求量的增加,旧的运维平台逐渐暴露出以下一些问题:

    1. Java实例部署所需资源没有清晰的统计和系统层面的隔离,仅仅依赖于启动脚本中的JVM参数来进行内存的约束,新增实例或新上项目时往往需要运维人员靠“感觉”指定部署的机器,没有有效地分配机器资源,项目之间资源争用会导致性能问题。
    2. 虽然大多数应用依赖的环境只有JDK 7和JDK 8,但一些JDK的小版本差异,以及一些自研发Java agent的使用,使得简单地指定JAVA_HOME目录的方式很难有效地管理运行环境。
    3. 开发人员的服务器权限需要回收。一个服务器上可能运行多个不同部门的项目,相关开发人员误操作可能会导致其他项目被影响。

    上述问题虽然也可以通过一些技术和规范约束来解决,但天生就是为了解决环境依赖和资源管理的容器技术是当下最合适的方案。
    #技术选型

    核心组件方面,Docker和Kubernetes是当下最成熟的开源容器技术。我们对强隔离没有太多的需求,所以没有使用KVM等虚拟机方案,直接在裸机上部署Kubernetes。

    分布式存储方面,容器化的项目大多是无状态的云原生应用,没有分布式存储的需求。极少数项目需要分布式存储的场合,我们会把已有的MFS集群挂载到宿主机,由宿主机挂载到容器里提供简单的分布式存储。

    容器本地数据卷方面,使用Docker默认的OverlayFS 2。我们服务器操作系统主要是CentOS,DeviceMapper在生产环境必须使用direct-lvm模式,该模式需要独立数据设备,对已有的SA自动化管理有一些影响。而OverlayFS 2在Linux内核4.17以上已经比较稳定,也不需要太多复杂的配置,开箱即用。

    日志收集方面,我们已有一套基于ELK的收集方案,对应用日志也有一定约束(必须将日志打印到指定目录下)。传统的基于控制台输出的Docker日志方案需要修改应用的日志输出配置,并且海量的控制台日志输出也会影响dockerd的性能,所以我们通过挂载日志数据盘的方式即可解决问题。

    监控方面,原有的监控设施是Zabbix,但在Kubernetes监控设施上Zabbix的方案显然没有亲儿子Prometheus成熟和开箱即用。所以在Kubernetes的监控方面,我们以Prometheus+Granfana为核心,使用kube-state-metrics采集Kubernetes运行数据。相比于Heapster,kube-state-metrics是Kubernetes生态的一部分,从Kubernetes的资源角度去采集数据,维度更多,信息更全面。

    最后是比较重要的Kubernetes网络方面,我们使用了比较新的网络方案kube-router。kube-router是基于三层Routing和BGP的路由方案,其优点如下:

    * 比Flannel等在数据包上再封装一层通信协议(常见是VXLAN)的网络实现性能上更优秀。
    * 比同样是基于BGP和三层路由的Calico来说更轻量简单,易于部署。
    * Macvlan技术会使宿主机网络和Pod网络隔离,不太符合我们的需求。
    * 在开启Service Proxy模式后可以取代默认组件kube-proxy,Service Proxy的实现是IPVS,在性能上和负载均衡策略上灵活度更高(在Kubernetes 1.8后kube-proxy也有IPVS的实现支持,但到现在还是实验性质)

    当然kube-router也存在一些不足:

    * 项目比较新,现在最新的还是v0.2.5,使用过程=踩坑。
    * 节点间网络必须二层可达,不像Calico提供了IPIP的解决方案。
    * 依赖于iptables,网络要求高的场景Netfilter本身会成为瓶颈。
    * 对于Pod IP的分配,Pod之间网络的ACL实现较为简单,无法应付安全要求高的场景。

    基于三层路由的CNI解决方案:
    1.jpg

    #业务落地实践

    搭好Kubernetes只是一个开始,我们这次重构有个很重要的目标是尽可能让业务开发方无感知无修改地把项目迁移到Kubernetes上,并且要保证实例部署和容器部署同时并行过度。

    理想的项目应该有Dockerfile声明自己的运行环境,有Jenkinsfile解决编译打包,有对应的Deployment和Service来告诉Kubernetes如何部署,但现实很骨干,我们有上千个项目,对应上千个Jenkins编译打包项目,逐一地修改显然不太现实。自动化运维的前提是标准化,好在项目规范比较严谨,符合了标准化这个充分条件。

    重新设计后的部署流程如下图所示:
    2.jpg

    构建方面,项目统一使用同一个Dockerfile模板,通过变更基础镜像来解决一些不同环境项目(比如需要使用JDK 7)的问题。基于Kubernetes Job和dind技术,我们开发了一个构建worker来实现从Jenkins拉取编译后的应用包并打包成镜像的流程,这样Jenkins打出来的应用可以同时用在实例部署和容器部署上。在运维后台上即可完成版本的构建:
    3.jpg

    部署方面,项目的部署配置分成两方面。资源配置一般不经常修改,所以仅仅只是在运维平台上修改记录。经常变更的版本变更和实例数变更则与部署操作绑定。将Kubernetes复杂的对象封装成扩展成原有项目对象的资源配置参数,执行部署时,根据项目资源配置,版本和实例数生成对应的Deployment和Service,调用Kubernetes API部署到指定的Kubernetes集群上。如果项目有在运维平台上使用静态配置文件,则使用ConfigMap存储并挂载到应用Pod里。
    4.jpg

    5.jpg

    在运维平台上提供Pod列表展示,预发环境debug应用,灰度发布,状态监控和webshell,方便开发观察应用运行情况,调试和日志查看,同时也避免开发SSH到生产环境服务器上,回收了服务器权限。
    6.jpg

    在应用从实例部署迁移到容器部署的过程中主要遇到以下几个问题:

    * Kubernetes集群内的Pod和集群外业务的通信问题。为了风险可控,实例部署和容器部署之间将会存在很长一段时间的并行阶段,应用方主要使用Dubbo做微服务治理,Kubernetes集群内的Pod和集群外业务的通信就成为问题了。kube-router是基于三层Routing实现,所以通过上层路由器指定Pod IP段的静态路由,或对接BGP动态交换路由表来解决问题。
    * JVM堆内存配置问题导致OOMKill的问题。因为JVM的内存不止有Xmx配置的堆内存,还有Metaspace或PermSize,以及某些如Netty等框架还有堆外内存,把Xmx的配置等同于容器内存配置几乎是一定会出现OOMKiil,所以必须放宽容器内存限制。以我们的经验来说,容器内存比Xmx多20%左右一般可以解决问题,但也有部分例外,需要额外配置。
    * Pod启动失败难以排查的问题。有一些Pod一启动就失败,输出的日志难以分析问题,我们构建和部署的描述文件都是运维平台动态生成的,很难使用传统docker run目标镜像的方式进行调试,所以我们在运维平台上提供了debug容器的功能,新建一个和原有deployment一样的debug部署,仅去掉健康检查相关的参数和修改command参数使pod运行起来,业务开发方即可通过webshell控制台进入Pod调试应用。

    #未来


    1. 开发经常需要使用的一些调试工具比如Vim,Arthas之类的,现在我们是打包到基础镜像中提供,但这样不仅增加了镜像的体积,而且需要重新打包新的镜像。目前看到比较好的解决方案是起一个调试用的容器并加到指定Pod的namespace中,但还需二次开发集成到webshell中。
    2. 跨机房Kubernetes集群调度。当现有资源无法满足峰值需求时,借助公有云来扩展系统是比较好的选择,我们希望借助Kubernetes多集群调度功能做到快速扩容到公有云上。
    3. 峰值流量的自动扩容和缩容,Kubernetes提供的HPA策略较为简单,我们希望能从更多的维度来计算扩容和缩容的数量,做到精准的控制。

    #Q&A

    Q:容器的Pod网络和外部网络全部打通吗,如何实现的?
    A:因为kube-router是基于三层路由,所以只要在顶层交换上指定Pod IP的静态路由即可,比如宿主机是192.168.0.1,该宿主机上的pod ip range是10.0.0.1/24,那只要在交换机或需要访问Pod的外部主机上添加路由10.0.0.1/24 via 192.168.0.1 ...即可。

    Q:你们如何去保证io的隔离?
    A:目前网络和硬盘的io没有做隔离,暂时还没有这方面的刚需。kube-router对网络IO这方面控制比较弱。硬盘IO方面Docker支持IOPS控制,但Kubernetes我还不太清楚是否支持。

    Q:Job和dind如何配合去实现打包镜像的呢?
    A:首先是dind技术,通过挂载宿主机的docker client和docker sock,可以实现在容器内调用宿主机的Docker来做一些事情,这里我们主要就用于build。Kubernetes的Job则是用于执行这个构建worker的方式,利用Kubernetes的Job来调度构建任务,充分利用测试集群的空闲资源。

    Q:从宿主机部署直接跨步到Kubernetes部署,不仅需要强力的power来推动,在落地实施的过程中,应该也会经历应用架构上的调整,能阐述你们在这方面遇到的困难和解决方式吗?
    A:Pod网络是最大的痛点,解决方式如文中所说。架构方面我们很早就是微服务去中心化的部署,API网关,服务注册发现等也是在容器化之前就已经完成改造的,所以应用架构反倒是没遇到多大的难题。

    Q:你们Kubernetes里面 统一配置是用的ConfigMap还是集成了第三方工具,例如Disconf。你们在Kubernetes中,APM用的是什么呢?Pinpoint还是Sky还是Jeager?还是其他?
    A:过去的项目配置文件是放运维平台上的,所以只要ConfigMap挂进去就可以了。后来新的项目开始采用携程的Apollo,Kubernetes上就只要通过ENV把Apollo的一些相关敏感信息传给Pod即可。APM方面因为我们是Java栈所以使用的skywalking,也是开篇提到的Java agent技术。

    Q:镜像仓库为什么选用Harbor,选型上有什么考虑?
    A:Harbor主要有UI方便管理,相对来说也容易部署和使用,尤其是权限管理这方面。

    Q:Macvlan和IPvlan性能非常好,几乎没有损耗,但默认都是容器和宿主机网络隔离的,但是也有解决方案,你们这边是没有考虑还是使用了一些解决方案发现有问题又放弃的?如果是后者,有什么问题让你们选择放弃?
    A:Macvlan之类的方式需要交换机层面上做一些配置打通VLAN,并且性能上并不会比基于三层的解决方案要高非常多,权衡之下我们还是选择比较易用的基于三层的方案,甚至为了易用而选择了更为激进的kube-router。

    Q:容器的多行日志收集如何解决?或者是,很多业务日志需要上下文关系,但是ELK只能查询到单条,这种情况怎么处理呢?
    A:容器多行日志的问题只存在于标准输出里,我们应用的日志是输出到指定目录下的,Filebeat有一些通用的多行日志解决方案。因为日志是存放在ES里的,所以可以通过调ES接口拿到某个Pod一个时间段里的日志,在UI上把它展示出来即可。

    Q:请问用的存储是什么?如何集成的?存储架构是怎样的?有考虑过Ceph吗?
    A:只有极少部分项目需要接分布式存储,并且对存储的管理,IOPS限制等没有硬性要求,所以我们把已有的MFS集群挂载到宿主机,再挂到容器里来实现。

    Q:Jenkins的Slave是用Pod Template创建的吗?Slave是Job共享还是需要时自动创建?
    A:Jenkins还是传统的master-slave单机部署模式,因为版本比较旧连Kubernetes Slave都不支持,所以我们只是调用了Jenkins的API来完成这个部署的过程。

    Q:Kubernetes在做存储挂载的时候,怎么保证容器漂移依然可以读取到共享存储?
    A:MFS挂载的话,文件是写入到MFS集群中的,那么挂载同样的MFS即可读到同一个文件。

    Q:关于命名空间,这边有哪些应用场景呢?
    A:按部门和场景区分ns,按ns分配节点和资源。未来打算基于ns做网络上的隔离和控制。

    Q:请问镜像大小是否有做优化?生产中有用alpine之类的base镜像吗?
    A:暂时没有,我们镜像的大小大约在100-300M之间。而且比起镜像大小的优化,运行环境的稳定和调试的便利更为重要。镜像有分层的策略,即使是很大的镜像,只要每次版本部署时更新的是最底层的镜像就不会导致每次都要拉取完整镜像。

    以上内容根据2019年4月29日晚微信群分享内容整理。分享人陈偲轶,荔枝研发中心DevOps工程师,负责运维平台的设计和开发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零八):华尔街见闻Istio生产实践

    尼古拉斯 发表了文章 • 0 个评论 • 750 次浏览 • 2019-04-24 18:41 • 来自相关话题

    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, ...查看全部
    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, 同时带来了智能路由、服务熔断、错误注入等重要的特性。整个搭建过程中也遇到了很多坑和经验,希望和大家分享。

    见闻开发团队以Golang为主,同时存在Python,Java服务,这就需要SRE提供更容易接入的微服务基础组件,常见的方案就是为每种语言提供适配的微服务基础组件,但痛点是基础组件更新维护的成本较高。

    为了解决痛点,我们将目光放到服务网格,它能利用基础设施下沉来解决多语言基础库依赖的问题,不同的语言不需要再引入各种不同的服务发现、监控等依赖库,只需简单的配置并运行在给定的环境下,就能享有服务发现、流量监控、链路追踪等功能,同时网络作为最重要的通信组件,可以基于它实现很多复杂的功能,譬如智能路由、服务熔断降级等。

    我们调研了一些服务网格方案,包括Istio、Linkerd。

    对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构可行性讨论,我们选择Istio作为实践方案。

    服务网格架构图:
    1.png

    这张图介绍了华尔街见闻典型的服务网格架构,左半图介绍了用户请求是如何处理,右半图介绍运维系统是如何监控服务。
    #架构可行性
    通过架构图,我们拆分出以下关键组件,经过评估,Istio高度模块化、可拓展,各个组件的可用性、拓展性都有相应的策略达到保障,我们认为Istio是具有可实施性的。
    ##Istio Ingress高性能,可拓展

    性能:Istio Ingress用来处理用户入流量,使用Envoy实现,转发性能高。


    可用性:保证实例数量并使用服务探活接口保证服务可用性。


    拓展性:挂载在负载均衡后,通过增加实例实现可拓展。


    ##Istio Proxy随应用部署,轻微性能损耗,可随应用数量拓展

    Istio Proxy以Sidecar形式随应用一起部署,增加2次流量转发,存在性能损耗。

    
性能:4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)

    
单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。 通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。


    CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量解决该问题,经过测试环境测试,延时可以接受。 


    可用性:基于Envoy,我们认为Envoy的可用性高于应用。依赖Pilot Discovery进行服务路由,可用性受Pilot Discovery影响。


    拓展性:Sidecar形式,随应用数拓展。


    ##Istio Policy服务可拓展,但同步调用存在风险

    Istio Policy需要在服务调用前访问,是同步请求,会增加服务调用延时,通过拓展服务数量增加处理能力。属于可选服务,华尔街见闻生产环境未使用该组件。 


    性能:未测试。


    可用性:若开启Policy,必须保证Policy高可用,否则正常服务将不可用。


    拓展性:增加实例数量进行拓展。

    ##Istio Telemetry监控收集服务
    
性能:从监控上观察Report 5000qps,使用25核,响应时间p99在72ms。异步调用不影响应用的响应时间。


    可用性:Telemetry不影响服务可用性。


    拓展性:增加实例数量进行拓展。

    ##Pilot Discovery

    性能:服务发现组件(1.0.5版本)经过监控观察,300个Service,1000个Pod,服务变更次数1天100次,平均CPU消耗在0.04核左右,内存占用在1G以内。


    可用性:在服务更新时需要保证可用,否则新创建的Pod无法获取最新路由规则,对于已运行Pod由于Proxy存在路由缓存不受Pilot Discovery关闭的影响。


    拓展性:增加实例数量可以增加处理量。
    #服务监控
    Istio通过mixer来搜集上报的遥测数据,并自带Prometheus、Grafana等监控组件,可方便的对服务状态进行监控。见闻目前监控在用Istio自带的,利用Prometheus拉取集群指标,经由Grafana看板展示,可直观展示出各种全局指标及应用服务指标,包括全局QPS、全局调用成功率、各服务延时分布情况、QPS及状态码分布等, 基本满足监控所需。

    存储暂未做持久化操作,采用Prometheus的默认的内存存储,数据的存储策略可通过设定Prometheus的启动参数`--storage.tsdb.retention`来设定,见闻生产时间设定为了6h。

    Prometheus服务由于数据存储原因会消耗大量内存,所以在部署时建议将Prometheus部署在专有的大内存node节点上,这样如果内存使用过大导致该node节点报错也不会对别的服务造成影响。Istio mixer担负着全网的遥测数据搜集任务,容易成为性能瓶颈,建议和Prometheus一样将其部署在专有节点上, 避免出现问题时对应用服务造成影响。

    以下是Mesh汇总面板的Demo:
    2.jpg

    Service Mesh汇总监控页面
    #链路追踪
    Envoy原生支持分布式链路追踪, 如Jaeger、Zipkin等,见闻生产选择了Istio自带的Jaeger作为自身链路追踪系统。 默认Jaeger服务为all_in_one形式,包含jaeger-collect、jaeger-query、jaeger-agent等组件,数据存储在内存中。

    见闻测试集群采用了此种部署形式。见闻生产环境基于性能与数据持久性考虑,基于Cassandra存储搭建了单独的jaeger-collect、jaeger-query服务。Jaeger兼容Zipkin,可以通过设定Istio ConfigMap中的`zipkinAddress`参数值为jaeger-collector对应地址,来设置Proxy的trace上报地址。同时通过设置istio-pilot环境变量`PILOT_TRACE_SAMPLING`来设置tracing的采样率,见闻生产采用了1%的采样率。

    Tracing不像Istio监控系统一样对业务代码完全无侵入性。Envoy在收发请求时若发现该请求没有相关trace headers,就会为该请求自动创建。tracing的实现需要对业务代码稍作变动,这个变动主要用来传递trace相关的header,以便将一次调用产生的各个span串联起来。

    见闻底层采用 grpc 微服务框架,在Gateway层面将trace header 加入到入口grpc调用的context中,从而将trace headers 逐级传递下去。
    3.jpg

    #Istio探活
    Istio通过向Pod注入Sidecar接管流量的形式实现服务治理,那么就会有Sidecar与业务容器状态不同步的可能,从而造成各种的调用问题,如下两方面:

    * Sidecar就绪时间晚于业务容器:业务容器此时若发起调用,由于Sidecar还未就绪, 就会出现类似no healthy upstream之类错误。若此时该Pod接收请求,就又会出现类似upstream connect error错误。
    * Sidecar就绪时间早于业务容器:例如某业务容器初始化时间过长导致Kubernetes误以为该容器已就绪,Sidecar开始进行处理请求,此时就会出现类似upstream connect error错误。

    4.jpg

    5.jpg

    探活涉及Sidecar、业务容器两部分,只有两部分同时就绪,此Pod才可以正式对外提供服务,分为下面两方面:

    * Sidecar探活:v1.0.3及以上版本针对Pilot-agent新增了一个探活接口healthz/ready,涉及statusPort、applicationPorts、adminPort等三个端口。其基本逻辑为在statusPort上启动健康监测服务,监听applicationPorts设定的端口是否至少有一个成功监听,之后通过调用本地adminPort端口获取xDS同步状态。若满足至少一个applicationPort成功监听,且CDS、LDS都进行过同步,则该Sidecar才被标记为Ready。
    * 业务容器探活:见闻通过基本库,在所有Golang grpc后端服务中注册了一个用于健康检查的handler, 该handler可由开发人员根据自身业务自定义,最后根据handler返回值来判断业务容器状态(如下图)。

    6.jpg

    #Istio应用更新
    为了实现灰度部署,见闻基于Kubernetes Dashboard进行了二次开发,增加了对Istio相关资源的支持,利用Gateway、VirtualService、DestinationRule等crd实现了应用程序的灰度部署,实际细节如下:

    1. 更新流量控制将流量指向已有版本
,利用VirtualService将流量全部指向v1版本(见下动图)。
    2. 部署新版本的Deployment
,查找旧的Deployment配置,通过筛选app标签符合应用名的Deployment,运维人员基于该Deployment创建v2版本的Deployment,并向destinationRule中增加v2版本。
    3. 更新流量控制将流量指向新版,本
利用VirtualService将ServiceA的服务流量全部指向v2版本。
    4. 下线老版本的Deployment并删除对应DestinationRule。

    利用Istio Dashboard来实现上述流程:
    07.gif

    为了方便开发人员服务部署,开发了精简版后台,并对可修改部分进行了限定。最终,SRE提供两个后台,对Istio Dashboard进行精细控制:
    08.gif

    #实践中的宝贵经验
    在Istio实践过程中,有哪些需要注意的问题。
    ##API server的强依赖,单点故障
    
Istio对Kubernetes的API有很强的依赖,诸如流量控制(Kubernetes资源)、集群监控(Prometheus通过Kubernetes服务发现查找Pod)、服务权限控制(Mixer Policy)。所以需要保障API server的高可用,我们曾遇到Policy组件疯狂请求Kubernetes API server使API server无法服务,从而导致服务发现等服务无法更新配置。


    
* 为避免这种请求,建议使用者了解与API server直接通信组件的原理,并尽量减少直接通信的组件数量,增加必要的Rate limit。
    

* 尽量将与API server通信的服务置于可以随时关闭的环境,这是考虑如果部署在同一Kubernetes集群,如果API server挂掉,无法关闭这些有问题的服务,导致死锁(又想恢复API server,又要依靠API server关闭服务)。

    ##服务配置的自动化
    
服务配置是Istio部署后的重头戏,避免使用手动方式更改配置,使用代码更新配置,将常用的几个配置更新操作做到运维后台,相信手动一定会犯错。
    ##关于Pilot Discovery

    Pilot Discovery 1.0.0版本有很大的性能问题,1.0.4有很大的性能提升,但引入了一个新bug,所以请使用1.0.5及以上的版本,我们观察到CPU消耗至少是1.0.0版本的1/10,大大降低了Proxy同步配置的延时。
    ##关于Mixer Policy 1.0.0

    这个组件曾导致API server负载过高(很高的list pods请求),所以我们暂时束之高阁,慎用。
    ##性能调优

    在使用Proxy、Telemetry时,默认它们会打印访问日志,我们选择在生产上关闭该日志。
时刻观察Istio社区的最新版本,查看新版本各个组件的性能优化以及bug修复情况,将Istio当做高度模块化的系统,单独升级某些组件。上面就提到我们在Istio1.0的基础上使用了1.0.5版本的Policy、Telemetry、Pilot Discovery等组件。
    ##服务平滑更新和关闭
    
Istio依靠Proxy来帮助APP进行路由,考虑几种情况会出现意外的状态:

    
* APP启动先于Proxy,并开始调用其它服务,这时Proxy尚未初始化完毕,APP调用失败。
    

* Service B关闭时,调用者Service A的Proxy尚未同步更新Service B关闭的状态,向Service B发送请求,调用失败。
第一种情况要求APP有重试机制,能适当重试请求,避免启动时的Proxy初始化与APP初始化的时差,Istio提供了retry次数配置,可以考虑使用。 
第二种情况,一种是服务更新时,我们使用新建新服务,再切流量;一种是服务异常退出,这种情况是在客户端重试机制。希望使用Istio的开发人员有更好的解决方案。

    #Q&A
    Q:学Service Mesh什么用?
    A:Service Mesh是最近比较火的一个概念,和微服务、Kubernetes有密切关系。出于以后业务发展需要,可以学习下, 增加知识储备。见闻上Istio的主要目的在文章已说明,主要是基础服务的下沉,解决了语言兼容性问题, 还有一个就是灰度发布。

    Q:链路追踪的采集方式是怎样的,比如Nodejs,C++等多语言如何支持的?
    A:Envoy本身支持链路追踪,也就是将Envoy会在请求head中增加链路追踪相关的数据头,比如x-b3-traceid,x-b3-spanid等等。业务要做的就是将这些head沿着调用链路传递下去即可。所以多语言的话需要在自己的业务侧实现该逻辑。所以Istio的链路追踪对业务代码还是有很小的侵入性的(这个分享中有说明)。

    Q:Istio与Spring Cloud两者的优缺点与今后的发展趋势会是怎么样?
    A:见闻技术栈是Golang,所以没太认真对比过两者。从社区活跃度上将,Istio > Spring Cloud,稳定性方面,Spring Cloud是更有优势,更适合Java沉淀较深的企业。但个人感觉对于更多企业来讲,跨越了语言兼容性的Istio未来发展很值得期待。

    Q:Docker镜像部署可以做到代码保护吗,比如像Nodejs这种非二进制执行程序的项目?
    A:代码保护可以通过将镜像上传至指定云服务商上的镜像仓库中,见闻是将自己的业务镜像提交保存在了腾讯云。如果镜像泄露,那么非二进制执行程序的代码还是有泄露风险的。

    Q:选型时为什么没考虑Linkerd?
    A:选型之初也调研了Linkerd, 对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构相较于见闻有较大可行性,所以选择了Istio作为实践方案。

    Q:Istio在做运维部署时没有UI工具,你们如何实现运维人员更加便捷地使用?
    A:见闻基于Kubernetes官方的Dashboard, 对内容进行了扩充,增加了对Gateway,VirtualService等Istio crd资源的支持, 同时增加了灰度部署等和见闻运维业务相关的功能,所以一定程度上解决了运维部署的问题。

    Q:流量从Sidecar代理势必会对请求响应时间有影响,这个有没有更详细案例的说明性能上的损耗情况?
    A:Sidecar的转发其实带来了性能一定的性能损耗。4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。 CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量缓解该问题,经过测试环境测试,延时可以接受。

    Q:Sidecar在生产中资源占用为多少?是否会对集群资源占用很多?
    A:以单个Pod为例,见闻业务单个Pod中Sidecar所占资源约占整个Pod所耗资源的1/10。

    Q:Jeager你们是进行了代码埋点吗?更为底层代码级别的追踪,有用其他方案吗?
    A:Envoy本身对tracing有良好的支持,所以业务端所做的改动只需将其所产生的追踪数据延续下去即可。上Istio之前,见闻在相关微服务中通过在基础库中增加链路追踪逻辑(Zipkin)实现了链路追踪,不过只做了Golang版,多语言兼容开发运维难度较大。

    Q:相信咱们的mixer在生产环境中,也出现过瓶颈,咱们从哪几个大方向优化的?如何优化的?方面讲解一下吗?
    A:mixer见闻在生产过程中所遇的坑在于Policy组件, 会疯狂的list pod,导致API server负载骤增,之后见闻基于自身业务关闭了Policy。

    Q:Istio组件实现了高可用么?
    A:Istio本身也是基于Kubernetes,所以可用性还是有较好保证的。

    以上内容根据2019年4月23日晚微信群分享内容整理。分享人张安伟,华尔街见闻SRE团队运维工程师,负责公司日常运维开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零七):瓜子云平台的实践经验

    齐达内 发表了文章 • 0 个评论 • 952 次浏览 • 2019-04-17 15:38 • 来自相关话题

    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结 ...查看全部
    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结出了一些云平台建设的实践和经验。

    这篇文章和大家分享下瓜子云平台的一些实践经验。瓜子是在 2017 年年中启动云平台项目的,当时有如下背景:

    * 技术栈多样化,PHP、Java、Go、Python 都有使用,但只有 PHP 建立了相对统一的部署流程
    * 业务迭代速度快,人员扩张速度快,再加上微服务化改造,项目数量激增,基于虚拟机的运维压力很大
    * 测试环境没有统一管理,业务开发人员自行零散维护

    基于此,我们的 0.x 版本,也是原型版本选择了如下的切入点:

    * 在 CI/CD 层面,先定义出标准流程,但是并不涉及细节的规范化,便于用户学习,快速将现有流程套进去
    * 同时支持 image 和 tar 包两种产出,为云上部署和虚拟机部署做好构建路径的兼容,在将来迁移时这部分可以做到几乎无缝
    * 先支持测试环境的部署,在验证平台稳定性的同时,不断收集用户需求进行快速迭代

    #集群核心组件的技术选型
    在服务编排和资源管理方面,Docker 和 Kubernetes 已经比较成熟了,基于容器的微服务化也是大势,再加上我们对强隔离没有诉求,所以跳过了 OpenStack,直接选择了 Kubernetes 方案。

    既然用了 Kubernetes 肯定就要解决跨节点的容器网络通信问题,因为我们是自建机房,没有公有云在网络层面的限制,所以没有考虑应用范围更广但是性能和可调试性较差的 VXLAN 方案。最开始选择的是 Macvlan + 自研 IPAM 的方式,之后转向了方案完成度更高的基于 BGP 协议的 Project Calico。

    Calico 的优点如下:

    * 基于 BGP 协议,转发平面依靠主机路由表,不涉及任何封包解包操作,性能非常接近原生网卡,并且方便抓包调试
    * 组件结构简单,对 Kubernetes 支持很好
    * 可以和 IDC 路由器通过 BGP 协议打通,直接对外广播容器 IP,让集群内外可以通过 IP 直连
    * 有 ACL 能力,类似 AWS 的 Security Group

    当然肯定有缺点:

    * 节点间网络最好是二层可达,否则只能使用 IP-in-IP 这种隧道技术,那就失去大半意义了
    * 因为是基于三层转发的,无法做到二层隔离,安全诉求高的场景不适用
    * 严重依赖 iptables,海量并发情况下 netfilter 本身可能成为瓶颈

    编排和网络解决后,集群就可以拉起来,接下来需要考虑的是流量如何进入,也就是负载均衡器的选型。在不想过多自己开发的前提下,使用 Kubernetes Ingress 是一个比较好的选择,但是不同 Ingress 之间功能和成熟度差异都很大,我们也是尝试了好几种,从 Nginx 到 Linkerd,最终选择了 Istio。我们在 0.2 版本开始用 Istio,初期它的不稳定带来了很多困扰,不过在 1.x 版本发布后相对稳定很多了。选择 Istio 的另一个原因是为将来向 Service Mesh 方向演进积累经验。

    以上一个最小功能可用集群就基本完备了,接下来在 CI/CD 这块,我们封装了一个命令行工具 med,它通过读取项目中的 med.yml,自动生成 dockerfile 和 deployment.yml,之后调用 Docker 进行构建,并提交给 Kubernetes 集群进行部署。

    一个 med.yml 例子如下:
    01.png

    从上面这个例子能看到,我们将构建环节拆分成了 prepare 和 build 两个部分,这个设计其实来源于 dockerfile 的一个最佳实践:由于拉取依赖的环节往往较慢,所以我们习惯将依赖安装和编译过程放到不同的命令中,形成不同的 layer,便于下次复用,提高编译速度。prepare 阶段就是把这部分耗时但是变更不频繁的操作提出来,通过 version 字段控制,只要 version 值不变,prepare 部分就不会再重复执行,而是直接利用之前已经生成好的 prepare 镜像进行接下来的 build 环节。build 环节构建结束后将 image 提交到镜像仓库,等待用户调用 deploy 命令进行部署。

    在实际开发过程中用户提出同一个代码仓库构建多个产出的需求,比如一个 Java 项目同时构建出用于发布的 Web Service 和提交到 Maven 的 jar 包。所以 build 和 deploy 阶段都支持多个产出,通过 name 进行区分,参数方面则支持有限的模板变量替换。

    最后,我们将 med.yml 和 GitLab CI 结合,一个 CI/CD 流程就出来了,用户提交代码后自动触发构建,上传镜像并部署到他的测试环境
    02.png

    #云平台产品化的一些思考
    ##云平台 1.x
    在测试环境跑了一段时间后,大家纷纷尝到了甜头,希望不只是在测试环境使用,生产环境也可以部署。这时简易的客户端工具就不满足需求了,新增的主要诉求围绕着权限管理和灰度发布,同时也希望有一个更加可视化的平台能看到实例运行的状态,上线的进度和监控日志等。需求细化如下:

    * 能直观看到当前项目的运行状态,部署状态
    * 项目有权限控制
    * 发布系统支持预发布和灰度部署
    * 日志收集和监控报警能力
    * 配置管理能力
    * 定时任务支持

    于是经过几轮迭代后,可视化的控制台上线了:
    03.png

    这里可以详细展开下部署环节,为了满足应用上线时的小流量测试需求,最开始是通过用户换个名字手工部署一个新版本,然后灵活调整不同版本部署之间的流量百分比来实现。但是在运行一段时间后,发现太高的灵活度调整非常容易出错,比如忘记了一个小流量部署,导致线上不正常,还很难 debug,并且整个操作过程也很繁琐。于是我们收敛了功能,实现了流程发布功能:
    04.png

    无论是流量调整还是流程发布,Kubernetes 默认的滚动更新都是无法满足需求的。我们是通过先创建一组新版本实例,然后再变更 Istio 的流量切换规则来实现的,示意图如下:
    05.png

    同时为了既有项目平滑上云,我们还提供了容器和虚拟机部署的联合部署,支持一个项目在一个发布流程中同时部署到云平台和虚拟机上。再配合外部 Nginx 权重的跳转,就可以实现业务逐步将流量切换到云上,最终完全去掉虚拟机

    这里有一个小插曲,由于基于 TCP 的 Dubbo 服务流量不经过 Istio 网关,而是通过注册到 ZooKeeper 里面的 IP 来进行流量调整的,上面的流程发布和联合部署对 Dubbo 服务没有意义。我们在这个版本进行了一个网络调整,将集群内部的 BGP 路由和外部打通,允许容器和虚拟机直接通信。由于 Calico 更换网段是要重新创建所有容器的,所以我们选择拉起一个新集群,将所有应用全部迁移过去,切换流量入口,再下掉旧集群。这里就体现了容器的便捷性了,数百个应用的迁移只花了十几分钟就再另一个集群完全拉起,业务几乎没有感知。

    配置管理方面,最开始是通过 env 管理,后来很多应用不太方便改成读取 env 的方式,又增加了基于 ConfigMap 和配置中心的配置文件注入能力。
    06.png

    日志收集方面,最开始使用 ELK,只收集了容器的 stdout 和 stderr 输出,后来对于放在指定位置的文件日志也纳入了收集目标中。由于 Logstash 实在太耗资源,我们直接使用 ES 的 ingest 能力进行日志格式化,去掉了中间的 Kafka 和 Logstash,从 Filebeat 直接输出到 ES。当然 ingest pipeline 本身调试起来比较复杂,如果有较多的日志二次处理需求,不建议使用。日志展示方面,Kibana 原生的日志搜索能力已经比较强大,不过很多人还是喜欢类似 tail -f 的那种查看方法,我们使用了 Kibana 的第三方插件 Logtrail 进行了模拟,提供了一键从控制台跳转到对应容器日志查看界面的能力。

    监控收集和展示,也是标准的 Prometheus + Grafana,在收集 Kubernetes 暴露出来的性能指标的同时,也允许用户配置自定义监控的 metric url,应用上线后会自动抓取。报警方面,因为 Prometheus 自带的 Alert Manager 规则配置门槛比较高,所以我们开发了一个用于规则配置的项目 NieR,将常用规则由运维同学写好后模板化,然后再提供给用户订阅,当然用户也可以自行建立自己的模板。监控报警系统展开说能再写一篇文章了,这里就只放一下架构图:
    07.png

    ##云平台 2.x
    在 1.x 版本迭代的时候,我们发现,早期为了给用户最大灵活性的 med.yml 在用户量持续增长后带来的培训、运维成本越来越高。每一个第一次接触平台的同事都要花费半天的时间阅读文档,然后在后续的使用中还有很多文档没有描述清楚的地方要搞明白,变相提高了项目上云的门槛。另外这种侵入用户代码仓库的方式,每次调整代价都非常大,服务端控制力度也太弱。

    针对上述问题,我们在 2.x 版本彻底去掉了 med.yml,实现了全部 UI 化。这里并不是说把之前的配置文件丢到一个管理页面上就算搞定了。拿构建来说,用户希望的是每种语言有一个标准的构建流程,只需要稍微修改下构建命令就可以直接使用,于是我们定义了语言模板:
    08.png

    然后替用户填好了大部分可以规范化的选项,当然也允许用户自行编辑:
    09.png

    10.png

    在部署层面,除了和构建产出联动外,最大的变动是参数合理化布局,让新用户基本不用看文档就能明白各个参数的用途。
    11.png

    2.x 版本才刚刚起步,后续还有非常多在排期安排的事情,比如在功能方面:

    * 支持多集群部署之后如何做到跨集群调度
    * 如何方便的能让用户快速拉起一套测试环境,乃至于构建自己的内部应用市场
    * 监控系统能不能进一步抽象,直接通过 UI 的方式配置监控模板,能不能自动建议用户合理的监控阈值
    * 给出各个业务的资源利用率和账单

    在基础设施层面:

    * 能不能做到不超售但是还能达成合理的资源利用率
    * 离线计算能不能在低峰期复用在线集群资源,但是不能影响业务
    * ServiceMesh 的进一步研究推广
    * 有状态服务的支持等等

    等等

    以上就是瓜子云平台的整体迭代路径。在整个开发过程中,我们感触最深的一点是,需要始终以产品化而不是做工具的思想去设计和实现。技术是为了需求服务的,而不是反过来,把一个用户最痛的痛点打透比做一百个酷炫的功能有用的多。但打透说起来容易,做起来有很多脏活累活。

    首先在需求分析阶段,基础设施的变更影响非常广泛,在征求大部分人意见的同时,如何引导大家往业界内更先进的路线上演进是要经过深思熟虑的。另外不同阶段的需求也不尽相同,不要一窝蜂的追随技术潮流,适合当前阶段的才是最好的技术选型。

    其次切入点最好是选择共识基础好,影响范围大的需求点,阻力小,成果明显。待做出成果后再一步步扩展到分歧比较严重的深水区。

    最后落地的时候要做好技术运营,做好上线前的宣传培训,帮助用户从旧系统尽量无痛的进行迁移。上线后的持续跟踪,通过数据化的手段,比如前端埋点,核心指标报表等手段观察用户的使用情况,不断调整策略。

    上面这些在团队小的时候可能都不是问题,一个沟通群里直接就能聊清楚。但当团队变大后,核心功能上一个不当的设计往往带来的就是上千工时的白白消耗甚至造成线上事故,一个云平台产品能不能落地,技术架构和实现是一方面,上面这些产品和运营策略是否运用得当也是非常重要的。
    #Q&A
    Q:请问瓜子私有云是一朵独立的云还是多云部署?如果是多云部署,云间网络是采用的什么技术?如何打通多云之间的网络的?谢谢
    A:我们在设计之初就考虑多集群 / 多 IDC 部署的,这也是选择 Calico 的一个原因。通过 BGP 协议将容器 IP 广播出去后,云间互联和普通虚拟机互联没有区别,当然这需要网络设备支持。

    Q:云平台在 PaaS 层,采用的编排工具是什么,如何打通容器之间的部署,在容器的架构上是怎么实现负载均衡的?
    A:采用的是 Kubernetes,打通使用的是 Calico,负载均衡使用的是 Istio Ingress。

    Q:新版本实例发布的时候怎么切Istio才能保障灰度的流量不丢失呢?
    A:在流程发布里面,我们相当于先新建一组新的实例,在它们的 Readiness 检查通过后,切换 Istio 规则指向新实例做到的。

    Q:稳定性方面有没有出现过比较大的问题,怎么解决的?
    A:有两个时期稳定性故障较多,一个是 Istio 版本比较低的时候,这个只能说一路趟坑趟过来,我们甚至自己改过 Istio 代码,现在的版本已经没出过问题了;一个是集群用的太狠,当集群接近满载时,Kubernetes 会出现很多连锁问题,这个主要是靠监控来做及时扩容。

    Q:自建机房的话为什么不接着使用 Macvlan + IPAM 方案呢?是为了之后上公有云做准备吗?
    A:当时面临一个本机 Macvlan 容器互相不通的问题,再加上有熟悉的团队已经在生产跑了很久 Calico 了,所以就直接换到了 Calico。

    Q:如果服务发现是基于 Dubbo + ZooKeeper,那 Kubernetes 自身的 Service 还有在使用吗?
    A:Kubernetes 自己的 Service 我们现在内部管理工具在使用,在 2.x 版本也开始开放给业务使用了(文中截图能看到内部域名)。

    Q:请问几秒的时延对一些高效的服务来讲也是不可接受的。咱们有想过通过何种方式去避免灰度的流量损失问题么?
    A:还真没遇到过这个需求。我理解如果有一个服务如此关键,那么在整个流量变更环节(从机房入口开始)到灰度策略上都得仔细考虑。如果接受不了 Istio 这个延时,一个思路是完全抛弃 Istio Ingress,直接使用一个切换迅速的负载均衡器。因为容器 IP 是直通的,完全可以从集群外直接连进来,只要解决服务发现的问题就行。

    Q:应用服务追踪怎么处理的?对接Istio?
    A:语言栈比较多的情况下这是个痛点,目前我们也是在尝试,即使是 Sidecar 也不能完全对业务没侵入。公司内部 Java 技术栈更喜欢 Skywalking 这种完全无侵入的方式。

    Q:使用 Istio 时,怎么解决性能损坏问题的?
    A:目前还没有启用 Mixer 这些对性能影响比较大的组件,所以目前性能损耗还是比较小的。如果对性能有严格的要求,我们会建议他使用 service name 做直连。

    Q:Prometheus 的告警是靠编辑大量的 rule.yml,请问生产中是怎么管理的?规则编辑比较麻烦,怎么解决?Prometheus 是单点,有没有扩容方案?
    A:就是靠 Nier 这个组件,将规则抽象成模板,甚至在云平台上面对于简单的规则直接变成了选项。然后模板渲染成规则。我们的 Prometheus 用的官方的联邦集群模式,存储放在了 Ceph 上面。


    Q:为什么 Kubernetes 默认的滚动更新不能满足要求?哪里有问题?
    A:没办法精细控制灰度粒度,比如部署了 4 个实例,我要求切 10% 的流量灰度,这就做不到了。另外,滚动更新回滚也比较慢。

    Q:注册到 ZooKeeper 的 IP 是 Pod IP?ZooKeeper 从外部直接访问Pod IP 吗?
    A:对的,Pod 和 VM 能直通,也就是说一个 Dubbo 服务能同时部署在 VM 和容器里面。

    Q:目前支撑的生产应用服务规模和云平台的规模能介绍下?包括一些指标,比如多少应用进行灰度更新耗时?
    A:应用规模的话目前超过 1000 了,每个月发布次数超过 10000。灰度更新是用户自行控制整个发布进度的,所以耗时不太有参考意义。

    以上内容根据2019年4月16日晚微信群分享内容整理。分享人高永超(flex),瓜子二手车技术总监,在容器云、DevOps 领域有丰富的架构经验。目前在主导瓜子云平台的研发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零六):容器环境下的持续集成最佳实践

    大卫 发表了文章 • 0 个评论 • 1000 次浏览 • 2019-04-03 11:49 • 来自相关话题

    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semanti ...查看全部
    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    云原生(Cloud Native)是伴随的容器技术发展出现的的一个词,最早出自 Pivotal 公司(即开发了 Spring 的公司)的一本技术小册子 Migrating to Cloud-Native Application Architectures, 其中定义了云原生应用应当具备的一些特质,如无状态、可持续交付、微服务化等。随后云原生概念被广为引用,并围绕这一概念由数家大厂牵头,成立了 CNCF 基金会来推进云原生相关技术的发展,主要投资并孵化云原生生态内的若干项目,包括了如 Kubernetes / etcd / CoreDNS 等耳熟能详的重要项目。而这张大大的云原生版图仍然在不断的扩展和完善。

    从个人理解来说,传统的应用由于年代久远,更多会考虑单机部署、进程间通信等典型的“单机问题”,虽然也能工作在容器下,但由于历史包袱等原因,架构上已经很难做出大的调整。而“云原生”的应用大都是从一开始就为容器而准备,很少考虑在单机环境下使用,有些甚至无法脱离容器环境工作;考虑的场景少,从而更轻量,迭代更快。比如 etcd 之于 ZooKeeper, Traefik 之于 Nginx 等,相信只要在容器环境下实现一次同样的功能,就能强烈的体会到云原生应用所特有的便捷之处。

    在 CNCF 的版图下,持续集成与持续交付(Continuous Integration & Delivery)板块一直缺少一个钦定的主角,虽然也不乏 Travis CI、GitLab、Jenkins 这样的知名项目,但最能给人云原生应用感觉的,应该还是 Drone 这个项目,本文将围绕 Drone 结合 GitFlow 及 Kubernetes 介绍一些容器环境下持续集成、持续发布(CI/CD)方面的实践经验。
    #主流 CI/CD 应用对比
    之前我也介绍过基于 Travis CI 的一些持续集成实践。后来经过一些比较和调研,最终选择了 Drone 作为主力 CI 工具。截止本文,团队已经使用 Drone 有 2 年多的时间,从 v0.6 一路用到现在即将发布的 v1.0,虽然也踩了不少坑,但总的来说 Drone 还是可以满足大部分需求,并以不错的势头在完善和发展的。

    下面这张表总结了主流的几个 CI/CD 应用的特点:
    B1.png

    Travis CI 和 CircleCI 是目前占有率最高的两个公有云 CI,易用性上相差无几,只是收费方式有差异。由于不支持私有部署,如果并行的任务量一大,按进程收费其实并不划算;而且由于服务器位置的原因,如果推送镜像到国内,速度很不理想。

    GitLab CI 虽然好用,但和 GitLab 是深度绑定的,我们的代码托管在 GitHub,整体迁移代码库的成本太大,放弃。

    Jenkins 作为老牌劲旅,也是目前市场占有率最高的 CI,几乎可以覆盖所有 CI 的使用场景,由于使用 Java 编写,配置文件使用 Groovy 语法,非常适合 Java 为主语言的团队。Jenkins 显然是可以满足我们需要的,只是团队并非 Java 为主,又已经习惯了使用 YAML 书写 CI 配置,抱着尝鲜的心态,将 Jenkins 作为了保底的选择。

    综上,最终选择 Drone 的结论也就不难得出了,Drone 即开源,又可以私有化部署,同时作为云原生应用,官方提供了针对 Docker、Docker Swarm、Kubernetes 等多种容器场景下的部署方案,针对不同容器场景还有特别优化,比如在 Docker Swarm 下 Drone 是以 Agent 方式运行 CI 任务的,而在 Kubernetes 下则通过创建 Kubernetes Job 来实现,显然充分利用了容器的优势所在,这也是 Drone 优于其他 CI 应用之处。个人还觉得 Drone 的语法是所有 CI 中最容易理解和掌握的,由于 Drone 每一个步骤都是运行一个 Docker 容器,本地模拟或调试也非常容易。

    一句话概况 Drone,可以将其看做是可以支持私有化部署的开源版 CircleCI,并且目前仍然没有看到有其他主打这个定位的 CI 工具,因此个人认为 Drone 是 CI/CD 方面云原生应用头把交椅的有力竞争者。
    #容器环境下一次规范的发布应该包含哪些内容
    技术选型完成后,我想首先演示一下最终的成果,希望能直观的体现出 CI 对自动化效率起到的提升,不过这就涉及到一个问题:在容器环境下,一次发布应该包含哪些内容,其中有哪些部分是可以被 CI 自动化完成的。这个问题虽然每家公司各不相同,不过按经验来说,容器环境下一次版本发布通常包含这样一个 Checklist:

    * 代码的下载构建及编译
    * 运行单元测试,生成单元测试报告及覆盖率报告等
    * 在测试环境对当前版本进行测试
    * 为待发布的代码打上版本号
    * 编写 ChangeLog 说明当前版本所涉及的修改
    * 构建 Docker 镜像
    * 将 Docker 镜像推送到镜像仓库
    * 在预发布环境测试当前版本
    * 正式发布到生产环境

    看上去很繁琐对吗,如果每次发布都需要人工去处理上述的所有内容,不仅容易出错,而且也无法应对 DevOps 时代一天至少数次的发布频率,那么下面就来使用 CI 来解决所有问题吧。
    #CI 流程演示
    为了对 CI 流程有最直观的认识,我创建了一个精简版的 GitHub 项目 AlloVince/drone-ci-demo 来演示完整的流程,同时项目对应的 CI 地址是 cloud.drone.io/AlloVince/drone-ci-demo ,项目自动构建的 Docker 镜像会推送到 docker registry 的 allovince/drone-ci-demo。为了方便说明,假设这个项目的核心文件只有 index.html 一个静态页面。
    ##单人开发模式
    目前这个项目背后的 CI 都已经配置部署好,假设我是这个项目的唯一开发人员,如何开发一个新功能并发布新版本呢?

    * Clone 项目到本地, 修改项目代码, 如将 Hello World 改为 Hello World V2。
    * git add .,然后书写符合约定的 Commit 并提交代码, git commit -m "feature: hello world v2”。
    * 推送代码到代码库git push,等待数分钟后,开发人员会看到单元测试结果,GitHub 仓库会产生一次新版本的 Release,Release 内容为当前版本的 ChangeLog, 同时线上已经完成了新功能的发布。

    虽然在开发者看来,一次发布简单到只需 3 个指令,但背后经过了如下的若干次交互,这是一次发布实际产生交互的时序图,具体每个环节如何工作将在后文中详细说明。
    01.png

    ##多人开发模式
    一个项目一般不止一个开发人员,比如我是新加入这个项目的成员,在这个 Demo 中应该如何上线新功能呢?同样非常简单:

    1. Clone 项目到本地,创建一个分支来完成新功能的开发, git checkout -b feature/hello-world-v3。在这个分支修改一些代码,比如将Hello World V2修改为Hello World V3
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world v3”
    3. 将代码推送到代码库的对应分支, git push origin feature/hello-world
    4. 如果功能已经开发完毕,可以向 Master 分支发起一个 Pull Request,并让项目的负责人 Code Review
    5. Review 通过后,项目负责人将分支合并入主干,GitHub 仓库会产生一次新版本的 Release,同时线上已经完成了新功能的发布。

    这个流程相比单人开发来多了 2 个环节,很适用于小团队合作,不仅强制加入了 Code Review 把控代码质量,同时也避免新人的不规范行为对发布带来影响。实际项目中,可以在 GitHub 的设置界面对 Master 分支设置写入保护,这样就从根本上杜绝了误操作的可能。当然如果团队中都是熟手,就无需如此谨慎,每个人都可以负责 PR 的合并,从而进一步提升效率。
    02.png

    ##GitFlow 开发模式
    在更大的项目中,参与的角色更多,一般会有开发、测试、运维几种角色的划分;还会划分出开发环境、测试环境、预发布环境、生产环境等用于代码的验证和测试;同时还会有多个功能会在同一时间并行开发。可想而知 CI 的流程也会进一步复杂。

    能比较好应对这种复杂性的,首选 GitFlow 工作流, 即通过并行两个长期分支的方式规范代码的提交。而如果使用了 GitHub,由于有非常好用的 Pull Request 功能,可以将 GitFlow 进行一定程度的简化,最终有这样的工作流:
    03.png


    * 以 dev 为主开发分支,Master 为发布分支
    * 开发人员始终从 dev 创建自己的分支,如 feature-a
    * feature-a 开发完毕后创建 PR 到 dev 分支,并进行 code review
    * review 后 feature-a 的新功能被合并入 dev,如有多个并行功能亦然
    * 待当前开发周期内所有功能都合并入 dev 后,从 dev 创建 PR 到 master
    * dev 合并入 Master,并创建一个新的 Release

    上述是从 Git 分支角度看代码仓库发生的变化,实际在开发人员视角里,工作流程是怎样的呢。假设我是项目的一名开发人员,今天开始一期新功能的开发:

    1. Clone 项目到本地,git checkout dev。从 dev 创建一个分支来完成新功能的开发, git checkout -b feature/feature-a。在这个分支修改一些代码,比如将Hello World V3修改为Hello World Feature A
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world feature A"
    3. 将代码推送到代码库的对应分支, git push origin feature/feature-a:feature/feature-a
    4. 由于分支是以 feature/ 命名的,因此 CI 会运行单元测试,并自动构建一个当前分支的镜像,发布到测试环境,并自动配置一个当前分支的域名如 test-featue-a.avnpc.com
    5. 联系产品及测试同学在测试环境验证并完善新功能
    6. 功能通过验收后发起 PR 到 dev 分支,由 Leader 进行 code review
    7. Code Review 通过后,Leader 合并当前 PR,此时 CI 会运行单元测试,构建镜像,并发布到测试环境
    8. 此时 dev 分支有可能已经积累了若干个功能,可以访问测试环境对应 dev 分支的域名,如 test.avnpc.com,进行集成测试。
    9. 集成测试完成后,由运维同学从 Dev 发起一个 PR 到 Master 分支,此时会 CI 会运行单元测试,构建镜像,并发布到预发布环境
    10. 测试人员在预发布环境下再次验证功能,团队做上线前的其他准备工作
    11. 运维同学合并 PR,CI 将为本次发布的代码及镜像自动打上版本号并书写 ChangeLog,同时发布到生产环境。

    由此就完成了上文中 Checklist 所需的所有工作。虽然描述起来看似冗长,但不难发现实际作为开发人员,并没有任何复杂的操作,流程化的部分全部由 CI 完成,开发人员只需要关注自己的核心任务:按照工作流规范,写好代码,写好 Commit,提交代码即可。

    接下来将介绍这个以 CI 为核心的工作流,是如何一步步搭建的。
    #Step by Step 构建 CI 工作流
    ##Step.0:基于 Kubernetes 部署 Drone v1.0.0
    以 GitHub 为例,截止本文完成时间(2019 年 3 月 28 日), Drone 刚刚发布了第一个正式版本 v1.0.0。官方文档已经提供了分别基于 Docker、Kubernetes 的 Drone 部署说明,不过比较简略,因此这里给出一个相对完整的配置文件。

    首先需要在 GitHub 创建一个 Auth App,用于 repo 的访问授权。应用创建好之后,会得到 Client ID 和 Client Secret 。同时 Authorization callback URL 应填写 Drone 服务对应域名下的 /login,如 https://ci.avnpc.com/login。

    Drone 支持 SQLite、MySQL、Postgres、S3 等多种后端存储,主要用于记录 build logs 等文本信息,这些信息并不是特别重要,且我们的 CI 有可能做迁移,因此个人更推荐使用 SQLite。

    而在 Kubernetes 环境下,SQLite 更适合用挂载 NAS 的方式供节点使用,因此首先将存储的部分独立为文件 drone-pvc.yml,可以根据实际情况配置 nfs.path 和 nfs.server:
    kubectl apply -f drone-pvc.yaml

    Drone 的配置主要涉及两个镜像:

    * drone/kubernetes-secrets 加密数据服务,用于读取 Kubernetes 的 secrets
    * drone/drone:1.0.0-rc.6 就是 Drone 的 server 端,由于在 Kubernetes 下 Drone 利用了 Job 机制,因此不需要部署 Agent。

    这部分配置较长,可以直接参考示例 drone.yaml

    主要涉及到的配置项包括:

    * Drone/kubernetes-secrets 镜像中

    * SECRET_KEY:数据加密传输所用的 key,可以使用 openssl rand -hex 16 生成一个

    * Drone/Drone镜像中

    * DRONE_KUBERNETES_ENABLED:开启 Kubernetes 模式
    * DRONE_KUBERNETES_NAMESPACE:Drone 所使用的 Namespace, 这里使用 default
    * DRONE_GITHUB_SERVER:GitHub 服务器地址,一般为 https://github.com
    * DRONE_GITHUB_CLIENT_ID:上文创建 GitHub Auth App 得到的 Client ID
    * DRONE_GITHUB_CLIENT_SECRET:上文创建 GitHub Auth App 得到的 Client Secret
    * DRONE_SERVER_HOST:Drone 服务所使用的域名
    * DRONE_SERVER_PROTO:http 或 https
    * DRONE_DATABASE_DRIVER:Drone 使用的数据库类型,这里为 SQLite3
    * DRONE_DATABASE_DATASOURCE:这里为 SQLite 数据库的存放路径
    * DRONE_SECRET_SECRET:对应上文的 SECRET_KEY
    * DRONE_SECRET_ENDPOINT:加密数据服务的地址,这里通过 Kubernetes Service 暴露,无需修改

    最后部署即可:
    kubectl apply -f drone.yaml

    部署后首次登录 Drone 就会跳转到 GitHub Auth App 进行授权,授权完毕后可以看到所有能读写的 Repo,选择需要开启 CI 的 Repo,点击 ACTIVATE 即可。 如果开启成功,在 GitHub Repo 的 Settings > Webhooks 下可以看到 Drone 的回调地址。
    ##Step.1:Hello World for Drone
    在正式开始搭建工作流之前,首先可以测试一下 Drone 是否可用。Drone 默认的配置文件是 .drone.yml, 在需要 CI 的 repo 根目录下创建.drone.yml, 内容如下,提交并git push到代码仓库即可触发 Drone 执行 CI。
    kind: pipeline  
    name: deploy

    steps:
    [list]
    [*]name: hello-world[/*]
    [/list] image: docker
    commands:
    - echo "hello world"

    Drone v1 的语法主要参考的 Kubernetes 的语法,非常直观,无需阅读文档也可以知道,我们首先定义了一个管道(Pipeline),管道由若干步骤(step)组成,Drone 的每个步骤是都基于容器实现的,因此 Step 的语法就回到了我们熟悉的 Docker,一个 Step 会拉取 image 定义的镜像,然后运行该镜像,并顺序执行 commands 定义的指令。

    在上例中,Drone 首先 clone git repo 代码到本地,然后根据 .drone.yml 所定义的,拉取 Docker 的官方镜像,然后运行该进行并挂载 git repo 的代码到 /drone/src 目录。

    在 Drone 的界面中,也可以清楚的看到这一过程。
    04.png

    本阶段对应:

    * 代码部分:https://github.com/AlloVince/drone-ci-demo/tree/hello-world
    * Drone 构建记录:https://cloud.drone.io/AlloVince/drone-ci-demo/1
    * Docker 镜像:无

    ##Step.2:单人工作流,自动化单元测试与 Docker 镜像构建
    有了 Hello World 的基础,接下来我们尝试将这个工作流进行扩充。

    为了方便说明,这里假设项目语言为 JavaScript,项目内新增了 test/index.js 文件用于模拟单元测试,一般在 CI 中,只要程序的返回值为 0,即代表运行成功。这个文件中我们仅仅输出一行 Log Unit test passed用于模拟单元测试通过。

    我们希望将代码打包成 Docker 镜像,根目录下增加了 Dockerfile 文件,这里直接使用 Nginx 的官方镜像,构建过程只有 1 行 COPY index.html /usr/share/nginx/html/, 这样镜像运行后可以通过 http 请求看到 index.html 的内容。

    至此我们可以将工作流改进为:

    * 当 Master 分支接收到 push 后,运行单元测试
    * 当 GitHub 发布一次 Release, 构建 Docker 镜像,并推送到镜像仓库

    对应的 Drone 配置文件如下:
    kind: pipeline  
    name: deploy

    steps:
    - name: unit-test
    image: node:10
    commands:
    - node test/index.js
    when:
    branch: master
    event: push
    - name: build-image
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    auto_tag: true
    when:
    event: tag

    虽然比 Hello World 复杂了一些,但是可读性仍然很好,配置文件中出现了几个新概念:

    Step 运行条件, 即 when 部分,上例中展示了当代码分支为 Master,且收到一个 push;以及当代码被标记 tag 这两种情况。Drone 还支持 repo、运行结果等很多其他条件,可以参考 Drone Conditions 文档。

    Plugin 插件,上例中用于构建和推送镜像的是 plugins/docker 这个 Plugin, 一个 Plugin 本质上仍然是一个 Docker 镜像,只是按照 Drone 的规范接受特定的输入,并完成特定的操作。所以完全可以将 Plugin 看做一个无法更改 command 的 Docker 镜像。

    Docker 这个 Plugin 由 Drone 官方提供,用于 Docker 镜像的构建和推送,具体的用法可以查看 Docker 插件的文档。例子中演示的是将镜像推送到私有仓库,如果不做特殊配置,镜像将被推送到 Docker 的官方仓库。

    此外 Docker 插件还有一个很方便的功能,如果设置 auto_tag: true,将根据代码的版本号自动规划 Docker 镜像的标签,如代码版本为1.0.0,将为 Docker 镜像打三个标签 1, 1.0, 1.0.0。如果代码版本号不能被解析,则镜像标签为 latest。

    目前 Drone 的插件已经有很多,可以覆盖主流的云服务商和常见的工作流,并且自己制作插件的成本也不高。

    Secret 加密数据,镜像仓库的用户名和密码都属于敏感信息,因此可以使用 from_secret 获取加密数据。一条加密数据就是一个 key / value 对,如上例中的 DOCKER_PASSWORD 就是我们自己定义的加密数据 key。即便加密数据在 log 中被打印,UI 也只能看到 ***。加密数据的 value 需要提前保存好,保存的方式有 3 种:

    * 通过 Drone UI 界面中, repo -> Settings -> Secrets 添加,所添加的加密数据将保存在 Drone 的数据库中,仅能在当前 repo 中使用。
    * 通过 Drone cli 加密后保存在 .drone.yml 文件中, 使用范围仅限 yaml 文件内
    * 通过 Kubernetes 保存为 Kubernetes Secret,称为 External Secrets,所有的 repo 都可以共享。如果是团队使用的话,这种保存方式显然是最方便的,但也要注意安全问题,因此 External Secrets 还支持 repo 级别的权限管理, 可以只让有当前 repo 写入权限的人才能使用对应 secret。

    这个阶段对应:

    * 代码仓库:https://github.com/AlloVince/drone-ci-demo/tree/single-person
    * push 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/4
    * Release 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/5
    * Release 后 CI 构建的 Docker 镜像:allovince/drone-ci-demo:latest

    ##Step.3:GitFlow 多分支团队工作流
    上面的工作流已经基本可以应付单人的开发了,而在团队开发时,这个工作流还需要一些扩展。不需要引入 Drone 的新功能,只需要在上文基础上根据分支做一点调整即可。

    首先保证单元测试位于 steps 的第一位,并且限定团队工作的分支,在 push 和 pull_request 时,都能触发单元测试。

     - name: unit-test  
    image: node:10
    commands:
    - node test/index.js
    when:
    branch:
    include:
    - feature/*
    - master
    - dev
    event:
    include:
    - push
    - pull_request

    然后根据 GitFlow 的流程对于不同的分支构建 Docker 镜像并打上特定标签,以 feature 分支为例,下面的配置约定了当分支名满足 feature/*,并收到 push 时,会构建 Docker 镜像并打标签,标签名称为当前分支名去掉 feature/。如分支 feature/readme, 对应 Docker 镜像为 allovince/drone-ci-demo:readme,考虑到 feature 分支一般都出于开发阶段,因此新的镜像会覆盖旧的。配置如下:
    - name: build-branch-image  
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    tag:
    - ${DRONE_BRANCH[size=16]feature/} [/size]
    when:
    branch: feature/*
    event: push

    镜像的 Tag 处不再使用自动方式,其中 DRONE_BRANCH 是 Drone 的内置环境变量(Environment),对应当前的分支名。feature/ 是执行了一个字符串的替换操作(Substitution)。更多的环境变量和字符串操作都可以在文档中找到。

    以此类推,可以查看这个阶段的完整 .drone.yml ,此时我们的工作流示例如下:

    * 团队成员从 dev 分支 checkout 自己的分支 feature/readme
    * 向feature/readme提交代码并 push, CI 运行单元测试,构建镜像allovince/drone-ci-demo:readme
    * 功能开发完成后,团队成员向 dev 分支 发起 pull request , CI 运行单元测试
    * 团队其他成员 merge pull request, CI 运行单元测试,构建镜像allovince/drone-ci-demo:test
    * 运维人员从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * 运维人员 merge pull request, 并 Release 新版本 pre-0.0.2, CI 构建镜像allovince/drone-ci-demo:pre-0.0.2

    可能细心的同学会发现 dev -> Master 的 pull request 时,构建镜像失败了,这是由于 Drone 出于安全考虑限制了在 pull request 时默认无法读取加密数据,因此无法得到 Docker Registry 密码。如果是私有部署的话,可以在 Repo Settings 中勾选 Allow Pull Requests,此处就可以构建成功。
    ##Step.4:语义化发布
    上面基本完成了一个支持团队协作的半自动 CI 工作流,如果不是特别苛刻的话,完全可以用上面的工作流开始干活了。

    不过基于这个工作流工作一段时间,会发现仍然存在痛点,那就是每次发布都要想一个版本号,写 ChangeLog,并且人工去 Release。

    标记版本号涉及到上线后的回滚,追溯等一系列问题,应该是一项严肃的工作,其实如何标记早已有比较好的方案,即语义化版本。在这个方案中,版本号一共有 3 位,形如 1.0.0,分别代表:

    1. 主版本号:当你做了不兼容的 API 修改,
    2. 次版本号:当你做了向下兼容的功能性新增,
    3. 修订号:当你做了向下兼容的问题修正。

    虽然有了这个指导意见,但并没有很方便的解决实际问题,每次发布要搞清楚代码的修改到底是不是向下兼容的,有哪些新的功能等,仍然要花费很多时间。

    而语义化发布(Semantic Release)就能很好的解决这些问题。

    语义化发布的原理很简单,就是让每一次 Commit 所附带的 Message 格式遵守一定规范,保证每次提交格式一致且都是可以被解析的,那么进行 Release 时,只要统计一下距离上次 Release 所有的提交,就分析出本次提交做了何种程度的改动,并可以自动生成版本号、自动生成 ChangeLog 等。

    语义化发布中,Commit 所遵守的规范称为约定式提交(Conventional Commits)。比如 Node.js、 Angular、Electron 等知名项目都在使用这套规范。

    语义化发布首先将 Commit 进行分类,常用的分类(Type)有:

    * feat:新功能
    * fix:BUG 修复
    * docs:文档变更
    * style:文字格式修改
    * refactor:代码重构
    * perf:性能改进
    * test:测试代码
    * chore:工具自动生成

    每个 Commit 可以对应一个作用域(Scope),在一个项目中作用域一般可以指不同的模块。

    当 Commit 内容较多时,可以追加正文和脚注,如果正文起始为BREAKING CHANGE,代表这是一个破坏性变更。

    以下都是符合规范的 Commit:
    feat:增加重置密码功能

    fix(邮件模块):修复邮件发送延迟BUG

    feat(API):API重构

    BREAKING CHANGE:API v3上线,API v1停止支持

    有了这些规范的 Commit,版本号如何变化就很容易确定了,目前语义化发布默认的规则如下:
    Commit	版本号变更
    BREAKING CHANGE 主版本号
    feat 次版本号
    fix / perf 修订号

    因此在 CI 部署 semantic-release 之后,作为开发人员只需要按照规范书写 Commit 即可,其他的都由 CI 完成。

    具体如何将语义化发布加入 CI 流程中呢, semantic-release 是 js 实现的,如果是 js 的项目,可以直接在 package.json 中增加配置项,而对于任意语言的项目,推荐像 Demo 中一样,在根目录下增加 配置文件release.config.js。这个配置目的是为了禁用默认开启的 npm 发布机制,可以直接套用。

    semantic-release 要执行 GitHub Release,因此我们需要在 CI 中配置自己的 Personal access tokens 让 CI 有 GitHub Repo 的读写权限, 可以通过 GitHub 点击自己头像 -> Settings -> Developer settings -> Personal access tokens -> Generate new token 生成一个 Token。 然后在 Drone 的 repo 设置界面新增一个 Secret, key 为 GITHUB_TOKEN, value 填入刚生成的 Token。

    最后在 .drone.yml 中增加这样一段就可以了。
    - name: semantic-release  
    image: gtramontina/semantic-release:15.13.3
    environment:
    GITHUB_TOKEN:
    from_secret: GITHUB_TOKEN
    entrypoint:
    - semantic-release
    when:
    branch: master
    event: push

    来再次模拟一下流程,feature 分支部分与上文相同:

    * 从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * merge pull request,CI 会执行单元测试并运行 semantic-release , 运行成功的话能看到 GitHub 新增 Release v1.0.0
    * GitHub Release 再次触发CI 构建生产环境用 Docker 镜像allovince/drone-ci-demo:1.0.0

    最终我们能得到这样一个赏心悦目的 Release。
    05.png

    ##Step.5:Kubernetes 自动发布
    Docker 镜像推送到仓库后,我们还剩最后一步就可以完成全自动发布的闭环,即通知 Kubernetes 将镜像发布到生产环境。这一步实现比较灵活,因为很多云服务商在容器服务都会提供 Trigger 机制,一般是提供一个 URL,只要请求这个 URL 就可以触发容器服务的发布。Demo 中我们使用更为通用的方法,就是将 kubectl 打包为容器,以客户端调用 Kubernetes 集群 Master 节点 API(kube-apiserver)的形式完成发布。

    假设我们在生产环境下 drone-ci-demo 项目的 Kubernetes 发布文件如下:
    ---  
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: ci-demo-deployment
    namespace: default
    spec:
    replicas: 1
    template:
    spec:
    containers:
    - image: allovince/drone-ci-demo
    name: ci-demo
    restartPolicy: Always

    对应 .drone.yml 中增加 step 如下。这里使用的插件是 honestbee/drone-kubernetes, 插件中 kubectl 连接 API 使用的是证书 + Token 的方式鉴权,因此需要先获得证书及 Token, 已经授权的 Token 保存于 Kubernetes Secret,可以通过 kubectl get secret [ your default secret name ] -o yaml | egrep 'ca.crt:|token:' 获得并配置到 Drone 中,注意插件要求 Token 是明文的,需要 base64 解码一下:echo [ your token ] | base64 -d && echo ''。
    - name: k8s-deploy  
    image: quay.io/honestbee/drone-kubernetes
    settings:
    kubernetes_server:
    from_secret: KUBERNETES_SERVER
    kubernetes_cert:
    from_secret: KUBERNETES_CERT
    kubernetes_token:
    from_secret: KUBERNETES_TOKEN
    namespace: default
    deployment: ci-demo-deployment
    repo: allovince/drone-ci-demo
    container: ci-demo
    tag:
    - ${DRONE_TAG}
    when:
    event: tag

    在示例中,可以看到在语义化发布之后 CI 会将新版本的 Docker 镜像自动发布到 Kubernetes,这里为了演示仅打印了指令并未实际运行。相当于运行了如下的指令:
    kubectl -n default set image deployment/ci-demo-deployment ci-demo=allovince/drone-ci-demo:v1.0.2

    由于自动发布的环节势必要接触到生产服务器,需要格外注意安全问题,首推的方式当然是将 CI 和 Kubernetes 集群放于同一内网中,同时可以使用 Kubernetes 的 RBAC 权限控制,为自动发布单独创建一个用户,并删除不必要的权限。
    #后话
    总结一下,本文展示了从 Hello World 到单人单分支手动发布再到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到 通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    最终 Step 的完成品可以适配之前的所有 Step,如果不太在意实现细节的话,可以在此基础上稍作修改,直接使用。

    然而写好每一个 Commit 这个看似简单的要求,其实对于大多数团队来说并不容易做到,在实施过程中,经常会遇到团队成员不理解为什么要重视 Commit 规范,每个 Commit 都要深思熟虑是否过于吹毛求疵等等疑问。

    以 Commit 作为 CI 的核心,个人认为主要会带来以下几方面的影响:

    1. 一个好的 Commit,代表着开发人员对当前改动之于整个系统的影响,有非常清楚的认识,代码的修改到底算 feat 还是 fix ,什么时候用 BREAKING CHANGE 等都是要仔细斟酌的,每个 Commit 都会在 ChangeLog 里“留底”,从而约束团队不随意提交未经思考的代码,提高代码质量。
    2. 一个好的 Commit 也代表开发人员有能力对所实现功能进行精细的划分,一个分支做的事情不宜过多,一个提交也应该专注于只解决一个问题,每次提交(至少是每次 push)都应该保持系统可构建、可运行、可测试,如果能坚持做到这些,对于合并代码时的冲突解决,以及集成测试都有很大帮助。
    3. 由于每次发布能清楚的看到所有关联的 Commit 以及 Commit 的重要程度,那么线上事故的回滚也会非常轻松,回滚到哪个版本,回滚后哪些功能会受到影响,只要看 CI 自动生成的 Release 记录就一目了然。如果没有这些,回滚误伤到预期外的功能从而引发连锁反应的惨痛教训,可能很多运维都有过类似经历吧。

    因此 CI 自动化其实是锦上添花而非雪中送炭,如果团队原本就无视规范,Commit 全是空白或者没有任何意义的单词,分支管理混乱,发布困难,奢望引入一套自动化 CI 来能解决所有这些问题,无疑是不现实的。而只有原本就重视代码质量,有一定规范意识,再通过自动化 CI 来监督约束,团队在 CI 的帮助下代码质量提高,从而有机会进一步改进 CI 的效率,才能形成良心循环。

    愿天下不再有难发布的版本。
    #Q&A
    Q:Kubernetes 上主流的 CI/CD 方案是啥?
    A:其实这无关 Kubernetes,从市场占有率来看,前三名分别是

    1. Jenkins
    2. JetBrains TeamCity
    3. CircleCI

    来源:https://www.datanyze.com/market-share/ci

    Q:GitLab 自带的 CI 与 Jenkins 和 GitLab 结合的 CI,该如何选择?想知道更深层次的理解。
    A:还是要结合自己团队的实际情况做选择。从成熟度来说,肯定是 Jenkins 用户最多,成熟度最高,缺点是侧重 Java,配置相对繁琐。GitLab 自带的 CI 相对简单,可以用 yaml,和 GitLab 结合的最好,但功能肯定没有 Jenkins 全面。如果是小团队新项目,GitLab CI 又已经可以满足需求的话,并不需要上 Jenkins,如果是较大的团队,又是偏 Java 的,个人更偏向 Jenkins。

    Q:Jenkins 如果不想运行在 Kubernetes 里面,该怎么和 Kubernetes 集成?
    A:从 CI 的流程来说,CI 应用是不是跑在 Kubernetes 的并不重要,CI 只要能访问代码库,有权限在生产环境发布,是不是跑在容器里从效果来说其实没有区别,只是用 Kubernetes 部署 Jenkins 的话,运维的一致性比较好,运维团队不用额外花时间维护一套物理机的部署方案。

    Q:Kubernetes 的回滚方案是回滚代码,重做镜像,还是先切流量,后做修改?
    A:代码一定是打包到镜像里的,镜像的版本就是代码的版本,所以一定是切镜像。至于回滚操作本身,Kubernetes 已经内置了很多滚动发布(Rolling update)的策略,无论是发新版本还是回滚版本,都可以做到用户无感知。

    Q:镜像大到几 G 的话如何更新部署,有什么好的实践呢,以及如何回滚?
    A:几个要点:

    1. 镜像仓库部署在内网,镜像推拉走内网,几个 G 的镜像传输起来也只是秒级别的
    2. 镜像构建时将不经常变动的部分划分到 1 层,因为 Docker 镜像是分层的,这样每次拉镜像可以利用到 Docker 的缓存传输的只有镜像发生变化的部分
    3. 选择 alpine 这样尽量小的镜像

    回滚如果发生在相邻的版本,一般物理机上是有缓存的,速度都很快。

    Q:Drone 开放 API 服务吗?这样方便其他系统集成。
    A:可以调整一下思路,直接把需要的功能做成镜像在 Drone 里调用就好了。

    Q:如果有 Drone 的 Server怎么做高可用?
    A:Drone serve r用 Kubernetes 部署的话本身只起到了一个任务调度的作用,很难会遇到性能瓶颈。真的有性能问题可以尝试水平扩展 Drone server,共享同一数据库。

    作者博客:https://avnpc.com/pages/drone-gitflow-kubernetes-for-cloud-native-ci

    以上内容根据2019年4月2日晚微信群分享内容整理。分享人徐谦,高级架构师,10 年以上研发经验,2 次创业经历。现从事互联网金融相关研发,关注 Node.js、容器技术、机器学习等。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零五):基于OVN的Kubernetes网络架构解析

    灵雀云 发表了文章 • 0 个评论 • 872 次浏览 • 2019-03-28 17:42 • 来自相关话题

    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络 ...查看全部
    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络的完整控制平面,并已经成为了最新的OpenStack网络标准。我们将OVN的网络架构和Kubernetes的容器平台进行结合,将业界成熟的网络架构引入Kubernetes大幅增强现有容器网络的能力。
    #Kubernetes网络的局限性
    Kubernetes提出了很多网络概念,很多开源项目都有自己的实现。然而由于各个网络功能都是在不同的项目中实现,功能和性能也各有千秋,缺乏统一的解决方案,在使用过程中经常会陷入到底该用哪个的抉择中。同时CNI、DNS、Service的实现又在不同的项目,一旦网络出现问题,排查也会在多个组件间游走,是一个十分痛苦的过程。

    尽管Kubernetes提出了很多网络的概念,但是在真实应用中很多人会有这样的感觉:网络这块还是很薄弱,很多功能缺乏,方案也不够灵活。尤其是和搞传统基础设施网络的人沟通会发现,在他们眼里,Kubernetes的网络还很初级。我们熟悉的Kubernetes网络是CNI、Service、DNS、Ingress、Network Policy这样的模式。而做IaaS的视角完全不同,他们每次提起是VPC、Subnet、VNIC、 Floating IP,在此之上有DHCP,路由控制,安全组,QoS,负载均衡,域名解析这样的基础网络功能。

    从IaaS的视角来看,Kubernetes的网络功能确实比较单薄。经常碰到来自传统网络部门的挑战,诸如子网划分VLAN隔离,集群内外网络打通,容器NAT设置,带宽动态调节等等。现有的开源网络方案很难完美支持,最简单的一个例子,比如提及容器的固定IP功能通常就要上升到意识形态斗争的层面去讨论。这本质上还是Kubernetes的网络功能不足,模型也不够灵活导致的。从更高层面来说,Kubernetes中抽象类一层网络虚拟化的内容,然而网络虚拟化或者SDN并不是Kubernetes带来的新东西,相关技术已经发展很久。尤其是在IaaS领域里已经有着比较成熟且完善的一整套网络方案。

    传统网络部门的人都会问,为什么不用OVS来做网络方案,很多需求用只要容器网络接入OVS网络,剩下事情网络部门自己就知道怎么去做了,都不用我们实现太多额外的功能。也有很多人向我们推荐了OVN,用这个能很方便地实现这些功能。也正由此我们开始去关注OVS/OVN这种之前主要应用于OpenStack生态系统的网络工具。下面我就来介绍一下OVS和OVN。
    #OVS和OVN网络方案的能力
    网络的概念比较晦涩一些,但是好在大家都对Docker和Kubernetes比较熟悉,可以做个类比。如果说Docker是对单机计算资源的虚拟化,那么OVS就是对单机网络进行虚拟化的一个工具。它最基本的功能是实现了虚拟交换机,可以把虚拟网卡和虚拟交换机的端口连接,这样一个交换机下的多个网卡网络就打通了,类似Linux Bridge的功能。在此之上,OVS很重要的一点就是支持OpenFlow,这是一种可编程的流量控制语言,可以方便我们以编程的方式对流量进行控制,例如转发,拒绝,更改包信息,NAT,QoS 等等。此外OVS还支持多中网络流量监控的协议,方便我们可视化监控并跟踪整个虚拟网络的流量情况。

    但是,OVS只是一个单机软件,它并没有集群的信息,自己无法了解整个集群的虚拟网络状况,也就无法只通过自己来构建集群规模的虚拟网络。这就好比是单机的Docker,而OVN就相当于是OVS的Kubernetes,它提供了一个集中式的OVS控制器。这样可以从集群角度对整个网络设施进行编排。同时OVN也是新版OpenStack中Neutron的后端实现,基本可以认为未来的OpenStack网络都是通过OVN来进行控制的。
    01.jpeg

    上图是一个OVN的架构,从下往上看:

    ovs-vswitchd和ovsdb-server可以理解为单机的Docker负责单机虚拟网络的真实操作。

    ovn-controller类似于kubelet,负责和中心控制节点通信获取整个集群的网络信息,并更新本机的流量规则。

    Southbound DB类似于etcd(不太准确),存储集群视角下的逻辑规则。

    Northbound DB类似apiserver,提供了一组高层次的网络抽象,这样在真正创建网络资源时无需关心负责的逻辑规则,只需要通过Northoboud DB的接口创建对应实体即可。

    CMS可以理解为OpenStacke或者Kubernetes这样的云平台,而 CMS Plugin是云平台和OVN对接的部分。

    下面我们具体介绍一下OVN提供的网络抽象,这样大家就会有比较清晰的认知了。

    Logical_Switch最基础的分布式虚拟交换机,这样可以将多台机器上的容器组织在一个二层网络下,看上去就好像所有容器接在一台交换机上。之后可以在上面增加诸如ACL、LB、QoS、DNS、VLAN等等二层功能。

    Logical_Router虚拟路由器,提供了交换机之间的路由,虚拟网络和外部网络连接,之后可以在路由器层面增加DHCP、NAT、Gateway等路由相关的功能。

    Loadbalancer,L2和L3的Loadbalancer,可以类比公有云上的内部LB和外部LB的功能。

    ACL基于L2到L4的所有控制信息进行管控的一组DSL,可以十分灵活,例如:outport == “port1” && ip4 && tcp && tcp.src >= 10000 && tcp.dst <= 1000。

    QoS,可以基于和ACL同样的DSL进行带宽的控制。

    NAT,同时提供DNAT和SNAT的控制方便内外网络通信。

    DNS,内置的分布式DNS,可以在本机直接返回内部DNS的请求。

    Gateway控制内部和外部之间的集中式通信。

    了解了这些,大家应该就能发现,Kubernetes目前的网络从功能层面其实只是OVN支持的一个子集,基本上所有Kubernetes的网络需求都能在OVN中找到映射关系,我们简单来看下他们之间的映射。
    #OVN和Kubernetes的结合
    Switch/Router -> Kubernetes基本的东西向互通容器网络。这块OVN的能力其实是大大超出,毕竟OVN的这套模型是针对多租户进行设计的,而Kubernetes现在只需要一个简单的二层网络。

    Loadbalancer → ClusterIP以及Loadbalancer类型的Service,可以取代kube-proxy的功能,OVN本身也可以实现云上ELB的功能。

    ACL -> Networkpolicy本质上更灵活因为可以从L2控制到L4并且DSL也支持更多的语法规则。

    DNS -> 可以取代Kube-DNS/CoreDNS,同时由于OVN实现的是分布式DNS,整体的健壮性会比现在的Kubernetes方案要好。

    可以看到Kubernetes的CNI、kube-proxy、Kube-DNS、NetworkPolicy、Service等等概念OVN都有对应的方案,而且在功能或者稳定性上还有增强。更不要说还有QoS、NAT、Gateway等等现在Kubernetes中没有的概念。可以看到如果能把IaaS领域的网络能力往Kubernetes平移,我们还有很多的提升空间。
    #Kubernetes网络未来增强的方向
    最后来说说我认为的未来Kubernetes网络可能的增强方向。
    ##Kubernetes网络功能和IaaS网络功能打平
    现在的Kubernetes网络模型很类似之前公有云上的经典网络,所有用户大二层打通,通过安全策略控制访问。我们现在也都知道公有云多租户不能这么做VPC肯定是标配。因此未来Kubernetes网络可能也会向着多租户方向前进,在VPC的基础上有更多的路由控制、NAT控制、带宽控制、浮动IP等等现在IaaS上很常见的功能。
    ##性能
    现有的开源方案其实主要还是依赖原有的Linux网络栈,没有针对性的优化。理论上容器的密度比传统虚拟化高,网络压力会更大。OVS现在有DPDK等Kernel bypass的DataPath,绕过Linux内核栈来实现低延迟和大吞吐网络。未来随着容器的密度越来越大,我认为会出现这种针对容器架构专门优化的网络方案,而不是依旧依赖Linux网络栈。
    ##监控和问题排查
    现有的网络问题排查十分困难,如果所有的数据平面都由一个项目完成,比如OVN,那么学习成本和排障都会容易一些。此外OVS社区已经有了很多成熟的监控,追踪,排障方案,随着容器的使用场景变多,我认为外围的工具也需要能够很好的支撑这种模式的网络运维问题。
    #Q&A
    Q:OVS方案与基于三层交换机方案对比,各有什么优缺点?

    A:OVS最大的优点在于可编程,灵活性比较好。虚拟网络不用手动插网线,而且有OpenFlow加持,可以实现一些普通交换机无法实现的流量控制。物理交换机的主要有点就是性能好,而且比较稳定,不容易出问题。



    Q:OVN不支持ECMP,貌似现在还是active-standby机制,你们怎么解决Gateway瓶颈问题?

    A:有几种方式:1. Gateway用DPDK这样的高速DataPath;2. 多Gateway,用策略路不同的IP段走不同Gateway,可以分担流量;3. 不使用OVN概念的Gateway,自己做一些手脚从每台宿主机直接出去,也是分担流量降低单点的风险。



    Q:OVN-Kubernetes好像只支持每个部署节点一个虚拟网络网段。如何避免IP池浪费和不均衡?

    A:这个其实是这个项目实现的网络模型的一个局限性。在我们的实现里是以namespace为粒度划分子网,可以对每个namespace进行控制,情况会好很多。



    Q:OVN如果有不同的Chassis,但是相同IP就会造成网络异常(比如物理机,VM装上OVN,注册到远端后,被重建,后面又注册到远端,但是Chassis已经改变),会导致大量节点Geneve网络异常。你们怎么解决这个问题?

    A:暂时没碰到这个问题,但是我们在实现的一个原则就是尽可能保证所有的操作都是幂等的。向这种可能需要在重连前做一个检查,判断是否有过期的数据需要清理,再连接,或者复用旧的连接信息去连接。



    Q:如何debug OVN逻辑拓扑是否配置有问题?

    A:目前debug确实很多情况只能靠眼看,也可以使用ovn-trace这个工具可以打印数据包在逻辑流里的链路来排查。



    Q:OVS跟Calico等有啥区别?

    A:Calico主要依赖Linux的路由功能做网络打通,OVS是在软件交换机层面实现网络打通,并提供了更丰富的网络功能。



    Q:OVS的封包支持有STT和Geneve,你们选用哪种,为什么?

    A:其实还支持VXLAN,我们选的Geneve。原因比较简单,Geneve是第一个OVN支持的封包协议,而且看了一些评测,据说在搞内核开启UDP Checksum的情况下性能会比VXLAN要好一些。



    Q:OVS如何实现固定容器IP?

    A:这个其实OVS有对应的设置可以给每个端口设定IP和MACE,这样网卡启动时配置相同的信息就可以了,难点其实是如何控制OVN来分配 IP,感觉这个话题可以再开一场分享来讨论了。



    Q:可以简单介绍下你们准备开源的网络功能吗?

    A:每个namespace和一个logical_switch绑定,支持子网分配,支持固定 IP,支持 QoS,支持NetworkPolicy,内置的LB,内置的DNS,大致就是把OVN的概念映射到Kubernetes。



    Q:想了解一下,如果采用OVN,是不是意味着使用OpenStack平台和Kubernetes网络可以直接互通?完成业务在虚拟机和Pod之间的全新负载方式?

    A:是这样的,如果涉及的合理可以做到容器和VM使用同一个底层网络基础设施,VM和容器之间可以IP直达,所有的ACL、LB都是打通的。



    Q:直接把OpenShift OVS抽出来做Kubernetes的网络插件和灵雀云做的这个区别在哪?

    A:功能上有很多是相同的,因为底层都是OVS。如果关注Roadmap会发现OpenShift之后也会采用OVS的模式。从架构的角度来看,现在openshift-multitenant的实现很类似Neutron之前那一套,各种Agent,之后会统一到OVN。另一方面OpenShift的网络插件绑定的太死了,所以我们决定还是自己抽出来,顺便能实现我们的一些特殊功能,比如固定IP,子网共享,以及一些网关控制层面的功能。



    Q:请问Geneve和VXLAN的区别有哪些?

    A:Geneve可以理解为下一代VXLAN,VXLAN相对VLAN来说头部更长可以支持更多的VLAN,但是由于是固定长度的封装头,不能任意加控制信息。Geneve采用变长的封装头,可以加很多自定义的控制信息,方便之后做更复杂的网络管控。



    Q:Docker CNM也支持固定IP,和你说的固定IP是一回事吗?另外,基于OVS建立的网络是CNI还是CNM的呢?

    A:基于CNI,因为我们依赖Kubernetes的模型。不过话说回来我很喜欢Docker CNM那套模型,比CNI要实用很多。固定IP其实只是个功能,各种模型下都可以实现,效果就是可以给Pod指定IP启动,Workload下的多个Pod实用的是一组固定的地址。



    Q:目前你们对企业的解决方案里会默认采用这种网络模式么?

    A:这个方案是我们这几年需求和碰到坑的一个积累吧,现在还不会直接给企业用,我们还需要一些功能的开发和测试,但是之后Overlay的场景这个肯定是主推了,主要是取代原来的Flannel VXLAN网络。



    Q:你了解Contiv网络方案吗,和贵公司的实现有哪些区别?

    A:Contiv是思科做的,也是OVS实现的,不过它的实现比较早,自己手动实现了整个控制平面,可以认为自己写了个跟OVN类似的东西来控制 OVS。不过看它最近已经很少更新了。用OVN能用很少的代码就实现基本相同的功能。Contiv有个比较独特的功能就是支持BGP的网络间通信,这个是OVN暂时不支持的。



    以上内容根据2019年3月26日晚微信群分享内容整理。分享人刘梦馨,灵雀云高级工程师。2014年加入灵雀云容器团队,长期参与容器调度平台和容器网络架构的产品研发和技术架构演进,参与自研容器网络和容器应用网关。目前主要专注于容器网络功能的拓展和架构优化。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零四):小团队微服务落地实践

    齐达内 发表了文章 • 0 个评论 • 1100 次浏览 • 2019-03-21 10:14 • 来自相关话题

    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将Dev ...查看全部
    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将DevOps落实到产品中的愿景,一步步建设出适合我们的微服务平台。
    # 要不要微服务
    我们的产品是Linkflow,企业运营人员使用的客户数据平台(CDP)。产品的一个重要部分类似企业版的“捷径",让运营人员可以像搭乐高积木一样创建企业的自动化流程,无需编程即可让数据流动起来。从这一点上,我们的业务特点就是聚少成多,把一个个服务连接起来就成了数据的海洋。理念上跟微服务一致,一个个独立的小服务最终实现大功能。当然我们一开始也没有使用微服务,当业务还未成型就开始考虑架构,那么就是“过度设计"。另一方面需要考虑的因素就是“人",有没有经历过微服务项目的人,团队是否有DevOps文化等等,综合考量是否需要微服务化。

    微服务的好处是什么?

    • 相比于单体应用,每个服务的复杂度会下降,特别是数据层面(数据表关系)更清晰,不会一个应用上百张表,新员工上手快。
    • 对于稳定的核心业务可以单独成为一个服务,降低该服务的发布频率,也减少测试人员压力。
    • 可以将不同密集型的服务搭配着放到物理机上,或者单独对某个服务进行扩容,实现硬件资源的充分利用。
    • 部署灵活,在私有化项目中,如果客户有不需要的业务,那么对应的微服务就不需要部署,节省硬件成本,就像上文提到的乐高积木理念。
    微服务有什么挑战?
    • 一旦设计不合理,交叉调用,相互依赖频繁,就会出现牵一发动全身的局面。想象单个应用内Service层依赖复杂的场面就明白了。
    • 项目多了,轮子需求也会变多,需要有人专注公共代码的开发。
    • 开发过程的质量需要通过持续集成(CI)严格把控,提高自动化测试的比例,因为往往一个接口改动会涉及多个项目,光靠人工测试很难覆盖所有情况。
    • 发布过程会变得复杂,因为微服务要发挥全部能力需要容器化的加持,容器编排就是最大的挑战。
    • 线上运维,当系统出现问题需要快速定位到某个机器节点或具体服务,监控和链路日志分析都必不可少。
    下面详细说说我们是怎么应对这些挑战的。# 开发过程的挑战## 持续集成通过CI将开发过程规范化,串联自动化测试和人工Review。我们使用Gerrit作为代码&分支管理工具,在流程管理上遵循GitLab的工作流模型。
    • 开发人员提交代码至Gerrit的magic分支
    • 代码Review人员Review代码并给出评分
    • 对应Repo的Jenkins job监听分支上的变动,触发Build job。经过IT和Sonar的静态代码检查给出评分
    • Review和Verify皆通过之后,相应Repo的负责人将代码merge到真实分支上
    • 若有一项不通过,代码修改后重复过程
    • Gerrit将代码实时同步备份至的两个远程仓库中
    1.png
    ## 集成测试一般来说代码自动执行的都是单元测试(Unit Test),即不依赖任何资源(数据库,消息队列)和其他服务,只测试本系统的代码逻辑。但这种测试需要mock的部分非常多,一是写起来复杂,二是代码重构起来跟着改的测试用例也非常多,显得不够敏捷。而且一旦要求开发团队要达到某个覆盖率,就会出现很多造假的情况。所以我们选择主要针对API进行测试,即针对controller层的测试。另外对于一些公共组件如分布式锁,json序列化模块也会有对应的测试代码覆盖。测试代码在运行时会采用一个随机端口拉起项目,并通过http client对本地API发起请求,测试只会对外部服务做mock,数据库的读写,消息队列的消费等都是真实操作,相当于把Jmeter的事情在Java层面完成一部分。Spring Boot项目可以很容易的启动这样一个测试环境,代码如下:
    @RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    测试过程的http client推荐使用`io.rest-assured:rest-assured`支持JsonPath,十分好用。测试时需要注意的一个点是测试数据的构造和清理。构造又分为schema的创建和测试数据的创建。
    • schema由flyway处理,在启用测试环境前先删除所有表,再进行表的创建。
    • 测试数据可以通过`@Sql`读取一个SQL文件进行创建,在一个用例结束后再清除这些数据。
    顺带说一下,基于flyway的schema upgrade功能我们封成了独立的项目,每个微服务都有自己的upgrade项目,好处一是支持command-line模式,可以细粒度的控制升级版本,二是也可以支持分库分表以后的schema操作。upgrade项目也会被制作成docker image提交到docker hub。测试在每次提交代码后都会执行,Jenkins监听Gerrit的提交,通过`docker run -rm {upgrade项目的image}`先执行一次schema upgrade,然后`gradle test`执行测试。最终会生成测试报告和覆盖率报告,覆盖率报告采用JaCoCo的Gradle插件生成。如图:
    2.png
    3.png
    这里多提一点,除了集成测试,服务之间的接口要保证兼容,实际上还需要一种consumer-driven testing tool,就是说接口消费端先写接口测试用例,然后发布到一个公共区域,接口提供方发布接口时也会执行这个公共区域的用例,一旦测试失败,表示接口出现了不兼容的情况。比较推荐大家使用Pact或是Spring Cloud Contact。我们目前的契约基于“人的信任”,毕竟服务端开发者还不多,所以没有必要使用这样一套工具。集成测试的同时还会进行静态代码检查,我们用的是sonar,当所有检查通过后Jenkins会+1分,再由reviewer进行代码review。## 自动化测试单独拿自动化测试出来说,就是因为它是质量保证的非常重要的一环,上文能在CI中执行的测试都是针对单个微服务的,那么当所有服务(包括前端页面)都在一起工作的时候是否会出现问题,就需要一个更接近线上的环境来进行测试了。在自动化测试环节,我们结合Docker提高一定的工作效率并提高测试运行时环境的一致性以及可移植性。在准备好基础的Pyhton镜像以及Webdriver(selenium)之后,我们的自动化测试工作主要由以下主要步骤组成:
    • 测试人员在本地调试测试代码并提交至Gerrit
    • Jenkins进行测试运行时环境的镜像制作,主要将引用的各种组件和库打包进一个Python的基础镜像
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的自动化测试环境更新,然后拉取自动化测试代码启动一次性的自动化测试运行时环境的Docker容器,将代码和测试报告的路径镜像至容器内
    • 自动化测试过程将在容器内进行
    • 测试完成之后,不必手动清理产生的各种多余内容,直接在Jenkins上查看发布出来的测试结果与趋势
    4.png
    5.png
    关于部分性能测试的执行,我们同样也将其集成到Jenkins中,在可以直观的通过一些结果数值来观察版本性能变化情况的回归测试和基础场景,将会很大程度的提高效率,便捷的观察趋势。
    • 测试人员在本地调试测试代码并提交至Gerrit
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的性能测试环境更新以及可能的Mock Server更新
    • 拉取最新的性能测试代码,通过Jenkins的性能测试插件来调用测试脚本
    • 测试完成之后,直接在Jenkins上查看通过插件发布出来的测试结果与趋势

    6.png

    7.png

    # 发布过程的挑战
    上面提到微服务一定需要结合容器化才能发挥全部优势,容器化就意味着线上有一套容器编排平台。我们目前采用是Redhat的OpenShift。所以发布过程较原来只是启动jar包相比要复杂的多,需要结合容器编排平台的特点找到合适的方法。
    ## 镜像准备
    公司开发基于GitLab的工作流程,Git分支为master,pre-production和prodution三个分支,同时生产版本发布都打上对应的tag。每个项目代码里面都包含dockerfile与jenkinsfile,通过Jenkins的多分支Pipeline来打包Docker镜像并推送到Harbor私库上。
    8.jpg

    Docker镜像的命令方式为 `项目名/分支名:git_commit_id`,如 `funnel/production:4ee0b052fd8bd3c4f253b5c2777657424fccfbc9`,tag版本的Docker镜像命名为 `项目名/release:tag名`,如 `funnel/release:18.10.R1`。
    9.png

    10.png

    在Jenkins中执行build docker image job时会在每次pull代码之后调用Harbor的API来判断此版本的docker image是否已经存在,如果存在就不执行后续编译打包的stage。在Jenkins的发布任务中会调用打包Job,避免了重复打包镜像,这样就大大的加快了发布速度。
    ## 数据库Schema升级
    数据库的升级用的是flyway,打包成Docker镜像后,在OpenShift中创建Job去执行数据库升级。Job可以用最简单的命令行的方式去创建:
    oc run upgrade-foo --image=upgrade/production --replicas=1 --restart=OnFailure --command -- java -jar -Dprofile=production /app/upgrade-foo.jar

    脚本升级任务也集成在Jenkins中。
    ## 容器发布
    OpenShift有个特别概念叫DeploymentConfig,原生Kubernetes Deployment与之相似,但OpenShift的DeploymentConfig功能更多些。

    DeploymentConfig关联了一个叫做ImageStreamTag的东西,而这个ImagesStreamTag和实际的镜像地址做关联,当ImageStreamTag关联的镜像地址发生了变更,就会触发相应的DeploymentConfig重新部署。我们发布是使用了Jenkins+OpenShift插件,只需要将项目对应的ImageStreamTag指向到新生成的镜像上,就触发了部署。
    11.png

    如果是服务升级,已经有容器在运行怎么实现平滑替换而不影响业务呢?

    配置Pod的健康检查,Health Check只配置了ReadinessProbe,没有用LivenessProbe。因为LivenessProbe在健康检查失败之后,会将故障的Pod直接干掉,故障现场没有保留,不利于问题的排查定位。而ReadinessProbe只会将故障的Pod从Service中踢除,不接受流量。使用了ReadinessProbe后,可以实现滚动升级不中断业务,只有当Pod健康检查成功之后,关联的Service才会转发流量请求给新升级的Pod,并销毁旧的Pod。
    readinessProbe:
    failureThreshold: 4
    httpGet:
    path: /actuator/metrics
    port: 8090
    scheme: HTTP
    initialDelaySeconds: 60
    periodSeconds: 15
    successThreshold: 2
    timeoutSeconds: 2

    # 线上运维的挑战
    ## 服务间调用
    Spring Cloud使用Eruka接受服务注册请求,并在内存中维护服务列表。当一个服务作为客户端发起跨服务调用时,会先获取服务提供者列表,再通过某种负载均衡算法取得具体的服务提供者地址(IP + Port),即所谓的客户端服务发现。在本地开发环境中我们使用这种方式。

    由于OpenShift天然就提供服务端服务发现,即Service模块,客户端无需关注服务发现具体细节,只需知道服务的域名就可以发起调用。由于我们有Node.js应用,在实现Eureka的注册和去注册的过程中都遇到过一些问题,不能达到生产级别。所以决定直接使用Service方式替换掉Eureka,也为以后采用Service Mesh做好铺垫。具体的做法是,配置环境变量`EUREKA_CLIENT_ENABLED=false`,`RIBBON_EUREKA_ENABLED=false`,并将服务列表如 `FOO_RIBBON_LISTOFSERVERS: 'http://foo:8080'` 写进ConfigMap中,以`envFrom: configMapRef`方式获取环境变量列表。

    如果一个服务需要暴露到外部怎么办,比如暴露前端的html文件或者服务端的Gateway。

    OpenShift内置的HAProxy Router,相当于Kubernetes的Ingress,直接在OpenShift的Web界面里面就可以很方便的配置。我们将前端的资源也作为一个Pod并有对应的Service,当请求进入HAProxy符合规则就会转发到UI所在的Service。Router支持A/B test等功能,唯一的遗憾是还不支持URL Rewrite。
    12.png

    13.png

    对于需要URL Rewrite的场景怎么办?那么就直接将Nginx也作为一个服务,再做一层转发。流程变成 Router → Nginx Pod → 具体提供服务的Pod。
    ## 链路跟踪
    开源的全链路跟踪很多,比如Spring Cloud Sleuth + Zipkin,国内有美团的CAT等等。其目的就是当一个请求经过多个服务时,可以通过一个固定值获取整条请求链路的行为日志,基于此可以再进行耗时分析等,衍生出一些性能诊断的功能。不过对于我们而言,首要目的就是trouble shooting,出了问题需要快速定位异常出现在什么服务,整个请求的链路是怎样的。

    为了让解决方案轻量,我们在日志中打印RequestId以及TraceId来标记链路。RequestId在Gateway生成表示唯一一次请求,TraceId相当于二级路径,一开始与RequestId一样,但进入线程池或者消息队列后,TraceId会增加标记来标识唯一条路径。举个例子,当一次请求会向MQ发送一个消息,那么这个消息可能会被多个消费者消费,此时每个消费线程都会自己生成一个TraceId来标记消费链路。加入TraceId的目的就是为了避免只用RequestId过滤出太多日志。

    实现上,通过ThreadLocal存放APIRequestContext串联单服务内的所有调用,当跨服务调用时,将APIRequestContext信息转化为Http Header,被调用方获取到Http Header后再次构建APIRequestContext放入ThreadLocal,重复循环保证RequestId和TraceId不丢失即可。如果进入MQ,那么APIRequestContext信息转化为Message Header即可(基于RabbitMQ实现)。

    当日志汇总到日志系统后,如果出现问题,只需要捕获发生异常的RequestId或是TraceId即可进行问题定位。
    14.png

    经过一年来的使用,基本可以满足绝大多数trouble shooting的场景,一般半小时内即可定位到具体业务。
    ## 容器监控
    容器化前监控用的是Telegraf探针,容器化后用的是Prometheus,直接安装了OpenShift自带的cluster-monitoring-operator。自带的监控项目已经比较全面,包括Node,Pod资源的监控,在新增Node后也会自动添加进来。

    Java项目也添加了Prometheus的监控端点,只是可惜cluster-monitoring-operator提供的配置是只读的,后期将研究怎么将Java的JVM监控这些整合进来。
    15.png

    16.png

    # 更多的
    开源软件是对中小团队的一种福音,无论是Spring Cloud还是Kubernetes都大大降低了团队在基础设施建设上的时间成本。当然其中有更多的话题,比如服务升降级,限流熔断,分布式任务调度,灰度发布,功能开关等等都需要更多时间来探讨。对于小团队,要根据自身情况选择微服务的技术方案,不可一味追新,适合自己的才是最好的。
    #Q&A
    Q:服务治理问题,服务多了,调用方请求服务方,超时或者网络抖动等需要可能需要重试,客户端等不及了怎么办?比如A->B->C,等待超时时间都是6s,因为C服务不稳定,B做了重试,那么增加了A访问B的时长,导致连锁反应?
    A:服务发现有两种,一种是客户端发现,一种是服务端发现。如果是客户端发现的话,客户端需要设置超时时间,如果超时客户端需要自己重试,此时如果是轮询应该可以调用到正常的服务提供方。Spring Coud的Ribbon就是对这一流程做了封装。至于连锁反应的问题,需要有降级熔断,配置Hystrix相关参数并实现fallback方法。看源码能够发现hystrixTimeout要大于ribbonTimeout,即Hystrix熔断了以后就不会重试了,防止雪崩。

    Q:JVM如何export,是多container吗,监控数据,搜刮到Prometheus?
    A:JVM的用的是Prometheus埋点,Java里面的路径是/actuator/prometheus,在yaml里面定义prometheus.io/path: /actuator/prometheu prometheus.io/port: '8090' prometheus.io/scrape: 'true',再在Prometheus里面进行相应的配置,就可以去搜刮到这些暴露的指标。

    Q:Kubernetes和OpenShift哪个更适合微服务的使用?
    A:OpenShift是Kubernetes的下游产品,是Kubernetes企业级的封装,都是一样的。OpenShift封装有功能更强大的监控管理工具,并且拥有Kubernetes不太好做的权限管理系统。

    Q:可以介绍一下你们在优化镜像体积上面做了哪些工作吗?
    A:RUN命令写在一行上,产生的临时文件再删掉。只安装必须要的包。JDK和Node.Js都有slim镜像,一般都是以那个为基础镜像去做。

    Q:数据库是否真的适合最容器化?
    A:我们生产数据库用的是RDS,开发测试环境用的是Docker Compose起的。从理论上,数据库最好做容器化,模块的独立性高,需要考虑好的是数据库容器的数据永久化存储。

    Q:为什么选择了OpenShift?
    A:因为OpenShift有个很方便的UI,大多数都可以在UI里面操作,包括yaml文件的修改,重新部署回退等操作。对于开发测试来讲,学习的成本比较低,不需要花时间熟悉CLI操作。

    Q:Python基础镜像怎么制作最好,如果加入GCC,C++等编译需要的工具,镜像会特别大?
    A:Python基础镜像直接从Python官方Docker镜像上做就行了。GCC,C++那个做出来的镜像大也没办法。如果没这个需求的话,可以用Python slim镜像去做。

    Q:在Gateway中Ribbon如何根据客户端的IP负载到对应的IP注册的服务?
    A:如果使用了Eureka的话,服务提供方启动时会自注册到Eureka。服务调用方发起请求前会从Eureka上读取提供方的列表,再进行负载均衡定位到具体的IP和Port。如果跟我们一样直接使用Kubernetes的Service,其实就是由Kubernetes控制了,服务调用方访问Kubernetes暴露的Service,然后由Kubernetes负载均衡到这个Service背后的具体的Pod。

    Q:如何实现远程发布、打包?
    A:Jenkins打包镜像发布到Harbor上,Jenkins再调用OpenShift去从Harbor上拉取镜像,重新tag一下就可以实现发布。

    Q:譬如客户端IP是10,希望Gateway负载到10注册的order服务,而不是其他IP注册的order服务,希望开发使用集中的Eureka和Gateway?
    A:是说不需要负载均衡?最简单的可以看下Ribbon的实现,负载均衡算法可以自己定义,如果只是要固定IP的话,那么遍历服务列表再判断就可以了。两次判断,if serviceId=order,if ip = 10。

    Q:Docker管理工具一般用什么?
    A:Kubernetes,简称k8s是目前较热门的Docker管理工具。离线安装Kubernetes比较繁琐,有两款比较好的自动化部署工具,Ubuntu系统的Juju和Red Hat系统的OpenShift,OpenShift又称为企业版的Kubernetes,有收费的企业版和免费版。

    Q:Prometheus是每个集群部署一套吗?存储是怎么处理?存本地还是?
    A:每个集群部署一套,存储暂时存在本地,没有用持久化存储。因为现在环境都是在云上面,本身云厂商就有各种的监控数据,所以Prometheus的监控数据也只是做个辅助作用。

    以上内容根据2019年3月19日晚微信群分享内容整理。分享人徐鹏,Linkflow产品运维负责人,负责公司运维平台建设和管理,同时兼顾SaaS版本和私有化版本的交付流程。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零三):骞云科技DevOps实践

    JetLee 发表了文章 • 0 个评论 • 845 次浏览 • 2019-02-28 22:23 • 来自相关话题

    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + J ...查看全部
    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + Jenkins + CMP(Ansible)共同构建了更好的 CI/CD 流程,对自动化持续交付流水线进行了优化;另一方面,CMP(Self-Service Portal)帮助建立了自服务自运维门户,公司所有人员都可以通过统一的门户自助申请各类资源,并自助完成日常运维。
    #为什么我们要加强 DevOps?

    在公司创立早期,为了尽快实现产品从0到1的转化,我们将更多的资源投入到了产品的新功能开发上,在产品开发自动化方面的投入并不高。
    随着公司业务的迅速发展,一方面,团队规模不断扩大,服务器资源也越来越多;另一方面,产品的功能逐渐丰富,开发代码工程数和分支数增加,而开发测试和资源管理仍以半自动化为主。

    面临的问题:
    ##人力资源浪费

    手工打包、手工创建虚机、手工部署、手工升级、较低程度的自动化测试,这些重复且低效的开发测试模式导致开发测试人员不能将宝贵的时间用于更加有创造力的工作上,不利于个人和公司的快速发展。
    ##IaaS资源管理混乱

    我们的开发测试环境主要构建在内部的vSphere和OpenStack云台上,当然也会在Aliyun、AWS、Azure等公有云上创建资源。在日常工作过程中,我们经常会听到这样的声音:“我的环境怎么这么卡”、“阿里云上又没钱啦”、“OpenStack上的机器我要删了啊”、“谁删了我的机器,我的数据还在上面呢”。由于没有权限控制导致资源随意创建,资源不及时释放导致大量资源闲置和浪费,另外还存在资源误删除情况。
    ##内部系统运维成本居高不下

    我们没有专职的内部系统运维人员,平时开发过程中,遇到CPU/Memory调整、磁盘物理卷/逻辑卷扩容、操作系统故障、应用故障等一系列问题都会占用研发人员大量的时间和精力。
    ##产品交付难度大

    为满足稳定性、高可用性、可扩展性等交付需求,我们的产品在软件架构设计上具有较高的复杂度,这样一来安装部署实施的难度也就比较大。售前团队到客户现场做POC,想要快速部署一套公司产品比较困难;售后团队在项目交付的过程中也经常遇到各种各样的安装配置问题。

    基于上述问题,我们希望通过对DevOps工作流程进行改造和增强,以提高产品开发效率和交付效率,以及提升个人生产力和资源利用率。
    #DevOps整体规划

    我们将DevOps工作流程改造分为了两个方面,一个是对CI/CD工作流的优化,一个是搭建自服务自运维门户。
    ##CI/CD目标


    * 所有代码工程能够自动化打包
    * 所有代码提交后能够自动构建编译检查以及单元测试任务
    * 每小时完成一次软件集成、部署以及核心功能的集成测试(API&UI)
    * 每天完成一次完整功能的集成测试(API&UI)
    * 每周完成一次7x24小时Longrun系统测试
    * 自动更新经过测试的nightly build到开发测试环境
    * 自动发布经过测试的weekly build到Demo环境

    ##自服务自运维目标

    公司所有人员,都可以通过一个统一门户Portal,自助申请各种类型的IaaS资源,如: x86物理服务器、vSphere虚拟机、OpenStack虚拟机、Kubernetes上的容器服务,以及Aliyun、AWS、Azure等公有云上的云资源;自助申请日常开发所需的软件应用,如:Nginx、Tomcat、MySQL、RabbitMQ,以及SmartCMP等。

    * 开发Dev,测试QA,售前交付需要使用不同的资源,做到资源隔离;
    * 资源的使用需要有权限控制;
    * 需要能够一键部署单节点和HA多节点应用系统;
    * 提供环境自动初始化,一键升级能力;
    * 提供系统和应用级别的监控告警;
    * 资源需要能够定期回收。

    #构建更好的CI/CD流程

    ##概述

    我们的DevOps工具链由 GitLab、Gerrit、Jenkins、CMP 构成。

    * GitLab:代码托管
    * Gerrit:代码审查
    * Jenkins:单元测试、自动化打包、集成测试
    * CMP:vSphere、OpenStack、Kubernetes等云资源统一管理,应用系统自动化部署,版本更新

    具体的工作流如下图所示:
    01.png

    开发人员提交代码后,触发Jenkins完成代码编译检查和单元测试,Jenkins返回代码检查结果给Gerrit,人工Review后merge代码,触发Jenkins完成该项目的打包。Jenkins定时完成各个工程的集成打包,然后通过调用CMP的API触发自动化部署,部署完成后进行场景化的集成测试,测试完成后卸除资源。
    02.png

    ##持续集成(Jenkins)

    起初我们使用GitLab Runner实现CI,在每个代码工程中添加“.gitlab-ci.yaml”文件,不同项目各自创建和维护自己的.gitlab-ci.yaml脚本,这样的实现可以解决各自工程的编译测试和打包问题,在代码工程数量较少时,我们也使用了较长一段时间。

    现在我们的代码工程数量已超过20个,每个代码工程都设置了访问权限,如果需要专人维护CI脚本的话,那他需要能够访问所有代码工程,显然这样是不合理的,而且把集成打包脚本放在哪个工程里都不合适。

    考虑到Jenkins有强大的CI能力:通过安装插件就能快速与Gerrit、GitLab集成,能够参数化执行各种类型的脚本。所以,我们使用Jenkins代替gitlab-runner完成CI,通过Jenkins可以统一管理各个工程的编译、测试、打包,而且比较方便构建流水线完成较多工程集成打包及测试。
    03.png

    ##持续部署(Ansible)

    我们的产品由20多个服务组成,可部署在一个或多个虚拟机上,使用Shell脚本或Python脚本已经很难完成这么多服务程序的自动化安装部署配置。

    恰巧团队有使用Ansible做复杂系统部署的经验,Ansible的学习成本也较低,所以我们选择使用Ansible Playbook 实现这20多个服务程序的统一编排部署和配置,并且可以同时支持单节点和HA多节点自动化部署。

    下图是Ansible自动化部署拓扑图:
    04.png

    我们设计Ansible Playbook时,将每个服务都独立成一个角色,这样保证了各个服务部署的独立性,这种分布式部署架构为将来容器化部署和微服务化奠定了基础。

    Ansible自动化标准化部署不仅大大缩短了部署时间,也极大地降低了部署出错的概率。原先,按照HA架构部署一套产品需要1天时间来完成各个服务的部署和配置,通过使用Ansible playbook,我们只需要45分钟,而且中间过程完全可以放手去做别的事情。
    ##集成测试(Robot Framework)

    目前,我们在Jenkins中使用Robot Framework框架做集成测试。Robot Framework(以下简称RF)是一个基于Python的、可扩展的、关键字驱动的测试自动化框架。

    选用RF的原因:

    * 一致性:目前公司的UI自动化测试使用的就是RF框架,RF框架也完全有能力做集成测试,因此使用RF框架做集成测试,可以降低学习成本,提高可维护性。
    * 复用性:在安装了Robot-Framework-JMeter-Library后,RF可以运行JMeter脚本,并且将JMeter运行结果转为Html格式。公司目前性能测试用的就是JMeter,对于相同场景,只要小幅修改JMeter脚本即可将其复用到集成测试上面。

    05.png

    选用RF也存在一些问题:

    * 如果不复用JMeter脚本,编写的API测试用例的成本非常高。
    RF对于变量类型的规定堪称僵硬(当然,这么规定带来的好处是方便类型检测),RF中对于字典类型的创建非常麻烦(嵌套的字典实例如下),对于我们公司API请求中携带大量参数的情况,只能创建关键字来解决,不管是采取RF自带创建字典的方法,还是创建关键字的方法,都比较浪费时间(因为难以复用)。
    06.png

    * RF可以轻松扩展关键字,也因此可能带来乱扩展关键字的问题,导致测试用例可读性和可维护性差的问题。

    在RF中,关键字其实就是Python/Java的类方法,因此扩展起来非常容易,但是关键字一旦多起来,一个同事写的测试用例,其他人(甚至他自己过了一段时间)维护就非常麻烦(需要回去看关键字是如何规定的)。因此需要严格规定关键字的创建规范是一件值得深入讨论的事情。
    #建立自服务自运维门户

    我们使用云管理平台(以下简称CMP,Cloud Management Platform)管理公司内部资源,使得公司所有人员,都可以通过CMP提供的自服务门户(Self-Service Portal),完成计算/存储/网络等IaaS资源和软件应用自助申请,并且能够自助进行日常运维操作。
    ##CMP平台准备工作

    通过LDAP方式将公司AD账户导入CMP平台中,为开发、测试、售前售后团队创建不同的业务组和资源池,每个资源池给到不同的资源配额,做到资源合理分配和资源相互隔离。为每个业务组设定一个管理员,审批业务组成员的资源申请。
    07.png

    我们使用Shell、Python脚本或Ansible配置管理工具将内部常用的一些软件及应用系统的安装过程进行封装,并发布到CMP平台中,提供标准化蓝图方便大家申请。
    08.png

    ##自服务自运维门户

    在门户中,大家可以看到已经发布好的服务卡片,通过点击服务卡片即可完成IaaS资源或应用系统的自助申请,在平时的开发测试过程中,我们不再需关心底层复杂的系统或网络配置。
    09.png

    在门户中,大家也可以清晰地看到自己所管理的资源的性能情况,还可以简单便捷地完成一些日常的基础运维操作:重启、调整配置、添加逻辑卷、扩展逻辑卷等。
    10.png

    此外,使用管理账号登录CMP管理平台,可以清晰地看到公司内部资源的总体使用情况。
    11.png

    12.png

    13.png

    #总结与建议

    在骞云科技的DevOps实践中,一方面,我们将GitLab、Gerrit、Jenkins、Ansible、JMeter、Robot Framework等成熟的开源工具开源技术和企业内部的云管理平台相结合,实现了较高程度的开发测试流程自动化,推动了产品的持续集成和持续交付,减少了大量的重复劳动,提高了开发测试效率。

    另一方面,通过使用云管理平台,将复杂异构的IaaS资源服务化,降低了使用难度;结合业务部门需求合理划分资源,减少了资源浪费,加强了资源的有效隔离,避免了误操作;自服务自运维的模式,也极大地提升了公司整体研发效率。

    我们的DevOps实践方案适用的场景非常多样,比如:弹性伸缩、迁移、负载均衡。在传统IT、金融、互联网、游戏等行业也具有普适性。
    #未来发展方向

    在介绍Ansible自动化部署时有提到,我们的业务系统由20多个服务组成,符合服务化的架构设计,目前已经可以满足私有化的部署需求。随着新功能的不断引入,部分业务子系统复杂度和团队开发耦合度会逐渐升高,协作效率和部署效率会变得低下。另外,当前的软件架构和部署架构不能满足将来的SaaS化部署。所以,我们仍需要将服务进行更细粒度的拆分,逐步向微服务架构转变并使用容器化部署,进一步降低开发和部署成本。
    #Q&A

    Q:CMP和各个云平台打通都使用了平台的jar,并且需要各种资源生成,这个工作量也不小吧?并且如果api更新代码量也大吧?
    A:我们的核心业务就是做云管理平台,我们产品已经完成了对各个云平台的对接,主要调用各个云平台的API。公有云的API更新频率并不是很高,每当API有更新时,我们也及时去适配。

    Q:Jenkins初次提交也能触发构建吗?每次自动化构建版本号是如何更新的呢?
    A:我们的项目代码具备构建条件后,才在Jenkins上创建了项目构建Job,所以并没有在初次提交时触发构建。每次构建的版本号由两部分组成,一部分是产品的Release大版本号,另一部分直接使用的Jenkins build number这个环境变量。

    Q:有了Gerrit,为什么还要GitLab,Gerrit也可以托管代码啊?
    A:这个是有历史背景的,我们是先选择使用GitLab做代码托管,后期才加入Gerrit做code review。Gerrit在代码review方面比GitLab的merge request要方便许多,更适合企业内部使用。关于这个,我的想法是,要么将GitLab迁移到Gerrit,要么不用Gerrit,可以使用GitLab的merge request来进行review,那GitLab其实是可以不要的。

    以上内容根据2019年2月26日晚微信群分享内容整理。分享人夏飞,骞云科技SmartCMP云管理平台后端开发负责人,负责云管理平台的建设和推广,目前负责公司内部DevOps工作流程的改造。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零二):房多多Service Mesh实践

    大卫 发表了文章 • 0 个评论 • 837 次浏览 • 2019-02-22 10:13 • 来自相关话题

    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。 #概述和 ...查看全部
    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。
    #概述和需求背景

    房多多国内领先的经纪人直卖平台,目前房多多APP和多多卖房APP的后端服务主要运行在自建的IDC机房。2018年之前,我们的服务都是运行在VM上,也同时基本上完成了基于Dubbo的微服务改造,目前的语言技术栈主要有:Java、Node.js、Golang。相对于其他公司而言,技术栈不是特别分散,这给我们服务容器化带来了极大的便利。

    我们在2018年Q3的时候已经完成大部分服务容器化,此时我们的微服务数量已经超过400个了,在沟通和维护上的成本很高。因为我们后端服务是主要是基于Dubbo的,跟前端直接的交互还是通过http的方式,在没有上Service Mesh之前,http请求都是经过Nginx转发的。维护Nginx的配置文件是一个工作量大且重复性高的事情,运维团队和业务团队迫切的需要更高效的方案来解决配置管理和沟通成本的问题,构建房多多Service Mesh体系就显得尤为重要。Envoy是一个成熟稳定的项目,也能够满足近期的需求,在现阶段我们并没有人力去动Envoy,所以我们直接使用了Envoy做Service Mesh的数据平面,关于控制平面这块,在调研了一些方案之后,我们采用了自研的方案。
    #整体平台架构

    我们的容器平台整体是基于物理机的,除了调度节点是用的虚拟机以外,所有的工作节点都是使用物理机。之前我们的虚拟化方案是用的Xenserver,Xenserver对高版本的内核支持并不好,会时不时的出现自动虚拟机关机的bug,所以我们在工作节点上只使用物理机以保障业务的稳定和高效。

    我们的业务主要工作在自建IDC机房,公有云只有少量的灾备服务。因为是自建机房,相比较公有云而言,自建机房使用Macvlan是一个比较好的方案。我们预先划分了几个20位地址的子网,每个子网内配置一些物理机,通过集中式的IP管理服务去管理物理机和容器的IP。相比较bridge网络,Macvlan网络有着非常接近物理网络的性能,尤其是在大流量场景下性能出色,下面一张图显示了性能对比:
    1.png

    2.png

    3.png

    我们把Envoy作为两个网络之间的连接桥,这么做的好处在于可以控制流量都经过负载均衡器,便于集中管理以及对流量做分析。看到这里,肯定有疑问是为什么不使用Sidecar的方式部署Envoy。关于Sidecar我的考虑是,在现有的业务场景下,集中部署的维护成本更低且性能满足需求,也相对来说比较简单。我们在2018年Q4已经完成主要业务http2接入,目前来看,我们的网站速度应该是同行业中最快的,大家可以体验一下,https://m.fangdd.com 。
    4.png

    #如何解决虚拟机业务和容器业务的并存问题

    我们原有的架构大量使用了虚拟机,迁移虚拟机上面的服务是一个漫长的过程,当前阶段还需要解决业务的并存问题,我们自己开发了Envoy对应的配置集中管理服务,同时支持虚拟机服务和容器服务。

    控制平面服务主要基于data-plane-api开发,功能上主要是给数据平面提供服务的集群配置、路由配置等信息,并且需要实现微服务架构中降级和限流的配置功能。容器内部的集群数据主要依赖DNSRR实现,而虚拟机上的服务,我们在CMDB上存有AppID和机器的对应关系,很容器生成集群配置数据。


    由于历史原因,还有相当多的业务无法从VM迁移到容器,需要有一个同时兼容容器和VM的数据平面服务,目前XDS服务的支持的功能如下:

    * 集群数据来源同时包括容器内部DNS和外部CMDB中的VM数据
    * 支持多个 vhost 配置
    * 支持路由配置
    * 支持速率控制和网关错误重试

    5.png

    #应用开发流程变化

    初步建设起Service Mesh体系之后,理论上业务开发只需要开发一个单体服务,在服务间互相调用的过程中,只需要知道服务名即可互相调用,这就很像把所有服务都看做一个微服务一样,所以我们的业务开发流程发生了以下变化:
    6.png

    同时也降低了框架开发成本和业务改动的成本,每次推动业务升级框架都需要比较长的一段时间,业务无法及时用上新框架的功能,多种框架版本也加重运维负担,Service Mesh帮我们解决了很多痛点。同时再加上我们的网关层建设,我们上线一个新服务几乎是零配置成本的。

    * 代理层实现服务发现,对于开发而言只需要开发一个单机的应用,降低框架开发成本
    * 降级和限流都在代理层实现,规则灵活,方便修改策略
    * 框架功能的升级无需依赖业务

    #SOA和Service Mesh的对比与取舍

    在我们的Service Mesh实践中,增加了链路的请求长度,并且服务的链路越长,链路请求的放大效应会越明显,这就带来了一些性能上面的担忧。毫无疑问,Mesh层本身的业务逻辑开销是不大,但是在网络传输和内存复制上的性能消耗在一定程度上会影响链路的性能,Envoy也在探索相关的方案来优化网络传输性能,如Bpfilter和VPP,减少用户态和内核态的内存拷贝。在可定制性层面,SOA能做的事情也相对较多,可以实现很多hack的需求。

    在我们现有的业务场景下,Service Mesh主要还是解决前后端的微服务对接问题,当做前后端服务的连接桥梁。但不可否认的是,Service Mesh带来研发效率的提升,在现有的场景下的价值远大于性能上的损失。在大多数的场景下,维护业务框架需要比较大的人力成本,很多团队都没有全职的人去维护业务框架。此外,推动业务框架的更新和升级也相对来说成本较高,这也是我们考虑的一个重要方面。
    #总结

    得益于云原生架构,Service Mesh可以使用云原生的基础设施,基础设施能力的改进可以直接赋能业务,而不像传统的框架一样,基础设施的升级和改进无法提高传统框架的服务能力。房多多的Service Mesh还处于初级阶段,后面还将继续探索。
    #Q&A

    Q:容器和微服务的区别以及它们的关联性、应用场景、客户群体、带来的价值?
    A:微服务的应用场景主要是解决降低单个服务体积,满足业务的快速开发需求。容器可以说是微服务的载体,容器方面还是运维关注的比较多,带来的标准化流程和环境的提升对整个研发团队都有很大的作用。

    Q:软件实现 Service Mesh,Istio?
    A:我们目前只使用了Envoy,Istio也做过一些选型的调研,不是很符合我们现在的业务场景和业务需求,我们在之后的实践中会考虑使用一部分Istio的功能。

    Q:实施过程当中有使用到Istio吗?有定制一些Mixer适配器吗?
    A:目前还没有用,之后会考虑是用Istio中的pilot,我们目前在流量的控制的精细程度上面还欠缺,粒度还很粗。

    Q:请问,实现微服务过程中有没有考虑分布式跟踪和分布式?
    A:Service Mesh层可以做到简单的分布式追踪,比如可以做到基于请求的追踪,Envoy可以把trace数据接入Zipkin这样的trace系统,但是更细粒度的trace目前还做不到。

    Q:不论是使用都会产生大量的配置(yaml),尤其是Envoy/Istio,系统中会有大量零散的配置文件,维护困难,还有版本管理,有什么很好的维护实践经验吗?谢谢。
    A:是的,据我所知有些团队会使用ConfigMap来管理配置,我们做了一个配置的集中管理服务,从CMDB和DNS定时的抓取数据,数据会存在数据库里面,也会存一定量的副本用于配置回退,这个地方还是要结合你们现在其他配套系统的建设来看看怎么做比较好的。

    Q:有没有遇到过Envoy被oom kill的情况,你们是如何处理的?
    A:这个我有碰到过一次,之前我们在对Envoy做测试的时候,发现Envoy总会尽可能的占满CGroup的内存大小,这个之前在使用TLS的情况下碰到的比较多。但是目前我们内部服务间使用TLS的情况并不多,所以后续这个问题就没有继续跟进了。

    Q:性化方案有哪些?
    A:之前文章中有提到过,对于http服务可以全量接入http2,http2的长连接和多路复用对于一般的业务来说升是很明显的,我们在全量接入http2之后,网站的响应时间几乎下降了50%。另外还可以在底层的依赖上面做一些优化,比如底层的libc库,以及尽可能的减少基础镜像的大小,我们基本上所有业务都使用了alpine,这样可以保证发布性能和速度在一个很好的水平上。

    Q:还是有一个服务治理/配置管理的问题请教,比如CPU,内存,这种资源request,在dev,test,staging,prod环境均不同,那么我们在编写Kubernetes配置的时候不同环境会不同,比如测试环境的replics数跟CPU需求就很低,生产就很高,另外这个配置在不同环境是多个还是一个呢?谢谢。
    A:我们现在会在CMDB里面维护这些配置数据,一般来说在新建项目的时候,我们就会要求业务线评估一下这个业务需要的资源,比如你说到的test和staging环境这种,我们默认会给一个很低的需求,比如1c1g这样的,而且replication也会默认设置为1,除非业务有特殊的需求,然后我们去根据配置的数据去生成yaml格式为配置。
    配置在数据存储的形式上是多个,但是在对业务展示上,尽量让业务感觉到是一个数据,这样也能使每个环境都规范起来,跟生产环境尽可能保持一致,这个对测试的好处还是很大的。

    Q:你们目前的项目中,大量的微服务,以及调度层,瓶颈和容灾是如何处理的?
    A:由于我们的业务类型还是B端业务为主,流量的峰值是可以预估的,很少会出现突发的大流量的情况,我们一般都预留了1倍的容量用于临时的扩容。容灾和调度的话我们主要还是尽可能的隔离工作节点和调度节点,以及大量使用物理机集群,从我们的使用体验上来看,物理机的稳定性还是很不错的。

    Q:如何用Jenkins自动完成Kubernetes部署?
    A:自动部署这块我们有完善的发布系统,如果单纯只要实现Jenkins自动完成Kubernetes的话,Jenkins可以直接调用Kubernetes的API,这个网上有挺多资料的,你可以找找看。

    Q:Service Mesh比传统的微服务治理好在哪里?
    A:降低框架开发成本、代理规则灵活,方便修改策略、框架功能的升级无需依赖业务,最大的好处我觉得就是可以降低框架开发成本。

    Q:我理解房多多目前的Mesh方案没有给每个微服务配一个Envoy作为Sidecar,而是使用一组Enovy并自研了xDS的配置发布管理系统,对吗?我想请问backend微服务之间的请求现在是怎么走的呢?谢谢。
    A:是的,刚刚文章中说了,我们后端SOA服务还是基于Dubbo的,这块目前我们还没有做改动,之后的话我们的初步构想会通过Sidecar的方式把这些Dubbo服务都接入到Mesh里面来。我们目前的Envoy也是会充当网关的角色。

    以上内容根据2019年2月21日晚微信群分享内容整理。分享人杜雅林,房多多平台工具负责人,负责容器平台、发布系统、Service Mesh相关功能开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二一一):基于Actor模型的CQRS/ES解决方案分享

    JetLee 发表了文章 • 0 个评论 • 325 次浏览 • 2019-06-05 16:17 • 来自相关话题

    【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将 ...查看全部
    【编者的话】2017年加密货币比较流行,我曾有幸在加密货币交易所参与开发工作,为了应对交易系统高性能、高并发、高可用的要求,我们使用基于Actor模型的Orleans技术,采用CQRS/ES架构结合Service Fabric开展了富有挑战性的工作。本次分享将从不同视角为大家介绍Actor模型、CQRS/ES架构以及Service Fabric在高并发场景中的考量和应用。

    最近一段时间我一直是这个话题的学习者、追随者,这个话题目前生产环境落地的资料少一些,分享的内容中有一些我个人的思考和理解,如果分享的内容有误、有疑问欢迎大家提出,希望通过分享这种沟通方式大家相互促进,共同进步。
    # 引言
    本话题由三部分组成:

    • Actor模型&Orleans:在编程的层面,从细粒度-由下向上的角度介绍Actor模型;
    • CQRS/ES:在框架的层面,从粗粒度-由上向下的角度介绍Actor模型,说明Orleans技术在架构方面的价值;
    • Service Fabric:从架构部署的角度将上述方案落地上线。
    群里的小伙伴技术栈可能多是Java和Go体系,分享的话题主要是C#技术栈,没有语言纷争,彼此相互学习。比如:Scala中,Actor模型框架有akka,CQRS/ES模式与编程语言无关,Service Fabric与Kubernetes是同类平台,可以相互替代,我自己也在学习Kubernetes。# Actor模型&Orleans(细粒度)##共享内存模型多核处理器出现后,大家常用的并发编程模型是共享内存模型。
    1.png
    这种编程模型的使用带来了许多痛点,比如:
    • 编程:多线程、锁、并发集合、异步、设计模式(队列、约定顺序、权重)、编译
    • 无力:单系统的无力性:①地理分布型、②容错型
    • 性能:锁,性能会降低
    • 测试:
    - 从坑里爬出来不难,难的是我们不知道自己是不是在坑里(开发调试的时候没有热点可能是正常的) - 遇到bug难以重现。有些问题特别是系统规模大了,可能运行几个月才能重现问题
    • 维护:
    - 我们要保证所有对象的同步都是正确的、顺序的获取多个锁。 - 12个月后换了另外10个程序员仍然按照这个规则维护代码。简单总结:
    • 并发问题确实存在
    • 共享内存模型正确使用掌握的知识量多
    • 加锁效率就低
    • 存在许多不确定性
    ##Actor模型Actor模型是一个概念模型,用于处理并发计算。Actor由3部分组成:状态(State)+行为(Behavior)+邮箱(Mailbox),State是指actor对象的变量信息,存在于actor之中,actor之间不共享内存数据,actor只会在接收到消息后,调用自己的方法改变自己的state,从而避免并发条件下的死锁等问题;Behavior是指actor的计算行为逻辑;邮箱建立actor之间的联系,一个actor发送消息后,接收消息的actor将消息放入邮箱中等待处理,邮箱内部通过队列实现,消息传递通过异步方式进行。
    2.png
    Actor是分布式存在的内存状态及单线程计算单元,一个Id对应的Actor只会在集群种存在一个(有状态的 Actor在集群中一个Id只会存在一个实例,无状态的可配置为根据流量存在多个),使用者只需要通过Id就能随时访问不需要关注该Actor在集群的什么位置。单线程计算单元保证了消息的顺序到达,不存在Actor内部状态竞用问题。举个例子:多个玩家合作在打Boss,每个玩家都是一个单独的线程,但是Boss的血量需要在多个玩家之间同步。同时这个Boss在多个服务器中都存在,因此每个服务器都有多个玩家会同时打这个服务器里面的Boss。如果多线程并发请求,默认情况下它只会并发处理。这种情况下可能造成数据冲突。但是Actor是单线程模型,意味着即使多线程来通过Actor ID调用同一个Actor,任何函数调用都是只允许一个线程进行操作。并且同时只能有一个线程在使用一个Actor实例。##Actor模型:OrleansActor模型这么好,怎么实现?可以通过特定的Actor工具或直接使用编程语言实现Actor模型,Erlang语言含有Actor元素,Scala可以通过Akka框架实现Actor编程。C#语言中有两类比较流行,Akka.NET框架和Orleans框架。这次分享内容使用了Orleans框架。特点:Erlang和Akka的Actor平台仍然使开发人员负担许多分布式系统的复杂性:关键的挑战是开发管理Actor生命周期的代码,处理分布式竞争、处理故障和恢复Actor以及分布式资源管理等等都很复杂。Orleans简化了许多复杂性。优点:
    • 降低开发、测试、维护的难度
    • 特殊场景下锁依旧会用到,但频率大大降低,业务代码里甚至不会用到锁
    • 关注并发时,只需要关注多个actor之间的消息流
    • 方便测试
    • 容错
    • 分布式内存
    缺点:
    • 也会出现死锁(调用顺序原因)
    • 多个actor不共享状态,通过消息传递,每次调用都是一次网络请求,不太适合实施细粒度的并行
    • 编程思维需要转变
    3.png
    第一小节总结:上面内容由下往上,从代码层面细粒度层面表达了采用Actor模型的好处或原因。# CQRS/ES(架构层面)##从1000万用户并发修改用户资料的假设场景开始
    4.png
    [list=1]
  • 每次修改操作耗时200ms,每秒5个操作
  • MySQL连接数在5K,分10个库
  • 5 5k 10=25万TPS
  • 1000万/25万=40s
  • 5.png
    在秒杀场景中,由于对乐观锁/悲观锁的使用,推测系统响应时间更复杂。##使用Actor解决高并发的性能问题
    6.png
    1000万用户,一个用户一个Actor,1000万个内存对象。
    7.png
    200万件SKU,一件SKU一个Actor,200万个内存对象。
    • 平均一个SKU承担1000万/200万=5个请求
    • 1000万对数据库的读写压力变成了200万
    • 1000万的读写是同步的,200万的数据库压力是异步的
    • 异步落盘时可以采用批量操作
    总结:由于1000万+用户的请求根据购物意愿分散到200万个商品SKU上:每个内存领域对象都强制串行执行用户请求,避免了竞争争抢;内存领域对象上扣库存操作处理时间极快,基本没可能出现请求阻塞情况;从架构层面彻底解决高并发争抢的性能问题。理论模型,TPS>100万+……##EventSourcing:内存对象高可用保障Actor是分布式存在的内存状态及单线程计算单元,采用EventSourcing只记录状态变化引发的事件,事件落盘时只有Add操作,上述设计中很依赖Actor中State,事件溯源提高性能的同时,可以用来保证内存数据的高可用。
    8.png
    9.png
    ##CQRS上面1000万并发场景的内容来自网友分享的PPT,与我们实际项目思路一致,就拿来与大家分享这个过程,下图是我们交易所项目中的架构图:
    10.png
    开源版本架构图:
    11.png
    开源项目GitHub:https://github.com/RayTale/Ray第二小节总结:由上往下,架构层面粗粒度层面表达了采用Actor模型的好处或原因。# Service Fabric 系统开发完成后Actor要组成集群,系统在集群中部署,实现高性能、高可用、可伸缩的要求。部署阶段可以选择Service Fabric或者Kubernetes,目的是降低分布式系统部署、管理的难度,同时满足弹性伸缩。交易所项目可以采用Service Fabric部署,也可以采用K8S,当时K8S还没这么流行,我们采用了Service Fabric,Service Fabric 是一款微软开源的分布式系统平台,可方便用户轻松打包、部署和管理可缩放的可靠微服务和容器。开发人员和管理员不需解决复杂的基础结构问题,只需专注于实现苛刻的任务关键型工作负荷,即那些可缩放、可靠且易于管理的工作负荷。支持Windows与Linux部署,Windows上的部署文档齐全,但在Linux上官方资料没有。现在推荐Kubernetes。第三小节总结:[list=1]
  • 借助Service Fabric或K8S实现低成本运维、构建集群的目的。
  • 建立分布式系统的两种最佳实践:

  • - 进程级别:容器+运维工具(Kubernetes/Service Fabric)
    - 线程级别:Actor+运维工具(Kubernetes/Service Fabric)

    上面是我对今天话题的分享。

    CQRS/ES部分内容参考:《领域模型 + 内存计算 + 微服务的协奏曲:乾坤(演讲稿)》 2017年互联网应用架构实战峰会。
    #Q&A
    Q:单点故障后,正在处理的Cache数据如何处理的,例如,http、tcp请求……毕竟涉及到钱?
    A:actor有激活和失活的生命周期,激活的时候使用快照和Events来恢复最新内存状态,失活的时候保存快照。actor框架保证系统中同一个key只会存在同一个actor,当单点故障后,actor会在其它节点重建并恢复最新状态。

    Q:event ID生成的速度如何保证有效的scale?有没有遇到需要后期插入一些event,修正前期系统运行的bug?有没有遇到需要把前期已经定好的event再拆细的情况?有遇到系统错误,需要replay event的情况?
    A:当时项目中event ID采用了MongoDB的ObjectId生成算法,没有遇到问题;有遇到后期插入event修正之前bug的情况;有遇到将已定好的event修改的情况,采用的方式是加版本号;没有,遇到过系统重新迁移删除快照重新replay event的情况。

    Q:数据落地得策略是什么?还是说就是直接落地?
    A:event数据直接落地;用于支持查询的数据,是Handler消费event后异步落库。

    Q:actor跨物理机器集群事务怎么处理?
    A:结合事件溯源,采用最终一致性。

    Q:Grain Persistence使用Relational Storage容量和速度会不会是瓶颈?
    A:Grain Persistence存的是Grain的快照和event,event是只增的,速度没有出现瓶颈,而且开源版本测试中PostgreSQL性能优于MongoDB,在存储中针对这两个方面做了优化:比如分表、归档处理、快照处理、批量处理。

    Q7:开发语言是erlang吗?Golang有这样的开发模型库支持吗?
    A:开发语言是C#。Golang我了解的不多,proto.actor可以了解一下:https://github.com/AsynkronIT/protoactor-go。

    Q:每个Pod的actor都不一样,如何用Kubernetes部署actor,失败的节点如何监控,并借助Kubernetes自动恢复?
    A:actor是无状态的,失败恢复依靠重新激活时事件溯源机制。Kubernetes部署actor官方有支持,可以参考官方示例。在实际项目中使用Kubernetes部署Orleans,我没有实践过,后来有同事验证过可以,具体如何监控不清楚。

    Q:Orleans中,持久化事件时,是否有支持并发冲突的检测,是如何实现的?
    A:Orleans不支持;工作中,在事件持久化时做了这方面的工作,方式是根据版本号。

    Q:Orleans中,如何判断消息是否重复处理的?因为分布式环境下,同一个消息可能会被重复发送到actor mailbox中的,而actor本身无法检测消息是否重复过来。
    A:是的,在具体项目中,通过框架封装实现了幂等性控制,具体细节是通过插入事件的唯一索引。

    Q:同一个actor是否会存在于集群中的多台机器?如果可能,怎样的场景下可能会出现这种情况?
    A:一个Id对应的Actor只会在集群种存在一个。

    以上内容根据2019年6月4日晚微信群分享内容整理。分享人郑承良,上海某科技公司架构师,对高并发场景下的分布式金融系统拥有丰富的实战经验,曾为澳大利亚、迪拜多家交易所提供技术支持。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二一零):平安证券Kubernetes容器集群的DevOps实践

    齐达内 发表了文章 • 0 个评论 • 381 次浏览 • 2019-05-29 11:54 • 来自相关话题

    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同 ...查看全部
    【编者的话】最近两三年,Docker容器技术及Kubernetes编排调度系统,在DevOps领域,大有星火燎原,一统天下之势。平安证券IT团队一直紧跟最新技术,践行科技赋能。本次分享,聚焦于公司在DevOps转型过程中的几个典型的技术细节的解决方案,希望对同行有所借鉴和帮助。
    #生产环境的高可用Master部署方案
    Kubernetes的高可用Master部署,现在网络上成熟的方案不少。大多数是基于Haproxy和Keepalived实现VIP的自动漂移部署。至于Haproxy和Keepalived,可独立出来,也可寄生于Kubernetes Master节点。

    我司在IT设备的管理上有固定的流程,VIP这种IP地址不在标准交付范围之内。于是,我们设计了基于DNS解析的高可用方案。这种方案,是基于Load Balancer变形而来。图示如下:
    1.png

    这种构架方案,平衡了公司的组织结构和技术实现。如果真发生Master挂掉,系统应用不受影响,DNS的解析切换可在十分钟内指向新的Master IP,评估在可接受范围之内。

    公司内部安装Master节点时,使用的基本工具是Kubeadm,但是作了脚本化改造及替换成了自己的证书生成机制。经过这样的改进之后,使用kubeadm进行集群安装时,就更有条理性,步骤更清晰,更易于在公司进行推广。

    底层的etcd集群使用独立的Docker方式部署,但共享kubeadm相关目录下的证书文件,方便了api-server和etcd的认证通信。脚本的相关配置如下:
    2.png

    当以DNS域名的形式进行部署后,各个证书配置认证文件,就不会再以IP形式连接,而是以DNS域名形式连接api-server了。如下图所示:
    3.png

    #分层的Docker镜像管理
    接下来,我们分享一下对Docker镜像的管理。Docker的企业仓库,选用的是业界流行的Harbor仓库。根据公司研发语言及框架的广泛性,采用了三层镜像管理,分为公共镜像,业务基础镜像,业务镜像(tag为部署发布单),层层叠加而成,即形成标准,又照顾了一定的灵活性。

    * 公共镜像:一般以alpine基础镜像,加上时区调整,简单工具。
    * 业务基础镜像:在公共镜像之上,加入JDK、Tomcat、Node.js、Python等中间件环境。
    * 业务镜像:在业务基础镜像之上,再加入业务软件包。

    4.png

    #Dashboard、Prometheus、Grafana的安全实践
    尽管在Kubernetes本身技术栈之外,我司存在体系化的日志收集,指标监控及报警平台,为了运维工具的丰富,我们还是在Kubernetes内集成了常用的Dashboard、Prometheus、Grafana组件,实现一些即时性运维操作。

    那么,这些组件部署,我们都纳入一个统一的Nginx一级url下,二级url才是各个组件的管理地址。这样的设计,主要是为了给Dashborad及Prometheus增加一层安全性(Grafana自带登陆验证)。

    这时,可能有人有疑问,Dashboard、kubectl都是可以通过cert证书及RBAC机制来实现安全性的,那为什么要自己来引入Nginx作安全控制呢?

    在我们的实践过程中,cert证书及RBAC方式,结合SSH登陆帐号,会形成一系列复杂操作,且推广难度高,我们早期实现了这种模式,但目前公司并不具备条件,所以废弃了。公司的Kubernetes集群,有专门团队负责运维,我们就针对团队设计了这个安全方案。

    Prometheus的二级目录挂载参数如下:
    5.png

    Grafana的二级目录挂载参数如下:
    6.png

    Dashboard在Nginx里的配置如下:
    7.png

    #一个能生成所有软件包的Jenkins Job
    在CI流水线实践,我们选用的GitLab作为源代码管理组件,Jenkins作为编译组件。但为了能实现更高效标准的部署交付,公司内部实现一个项目名为prism(棱镜)的自动编译分发部署平台。在容器化时代,衍生出一个Prism4k项目,专门针对Kubernetes环境作CI/CD流程。Prism4k版的构架图如下所示:
    8.png

    在这种体系下,Jenkins就作为我们的一个纯编译工具和中转平台,高效的完成从源代码到镜像的生成。

    由于每个IT应用相关的变量,脚本都已组织好,放到Prism4k上。故而,Jenkins只需要一个Job,就可以完成各样各样的镜像生成功能。其主要Pipeline脚本如下(由于信息敏感,只列举主要流程,有删节):
    9.png

    在Jenkins中,我们使用了一个Yet Another Docker Plugin,来进行Jenkins编译集群进行Docker生成时的可扩展性。作到了编译节点的容器即生即死,有编译任务时,指定节点才生成相关容器进行打包等操作。
    #计算资源在线配置及应用持续部署
    在Prism4k平台中,针对Jenkins的Job变量是通过网页配置的。在发布单的编译镜像过程中,会将各个变量通过API发送到Jenkins,启动Jenkins任务,完成指定task任务。
    10.png

    Pod的实例数,CPU和内存的配置,同样通过Web方式配置。
    11.png

    在配置好组件所有要素之后,日常的流程就可以基于不同部门用户的权限把握,实现流水线化的软件持续交付。

    * 研发:新建发布单,编译软件包,形成镜像,上传Harbor库。
    * 测试:环境流转,避免部署操作污染正在进行中的测试。
    * 运维:运维人员进行发布操作。

    在FAT这样的测试环境中,为加快测试进度,可灵活的为研发人员赋予运维权限。但在更正式的测试环境和线上生产环境,作为金融行业的IT建设标准,则必须由运维团队成员操作。

    下面配合截图,了解一下更具体的三大步骤。

    1. 发布单
    12.png

    在Prism4k与Jenkins的API交互,我们使用了Jenkins的Python库。

    1. 环境流转
    13.png


    1. 部署
    14.png


    在部署操作过程中,会将这次发布的信息全面展示给运维同事,让运维同事可以进行再次审查,减少发布过程中的异常情况。
    #总结
    由于Kubernetes版本的快速更新和发布,我们对于其稳定性的功能更为青睐,而对于实验性的功能,或是需要复杂运维技能的功能,则保持理智的观望态度。所以,我们对Kubernetes功能只达到了中度使用。当然,就算是中度使用,Kubernetes的运维和使用技巧,还是有很多方面在此没有涉及到,希望以后有机会,能和各位有更多的沟通和交流。愿容器技术越来越普及,运维的工作越来越有效率和质量。
    #Q&A
    Q:镜像有进行安全扫描吗:
    A:外部基本镜像进入公司内部,我们基于Harbor内置的安全功能进行扫描。

    Q:Harbor有没有做相关监控,比如发布了多少镜像,以及镜像同步时长之类的?
    A:我们没有在Harbor上作扩展,只是在我们自己的Prism4k上,会统计各个项目的一些镜像发布数据。

    Q:有没有用Helm来管理镜像包?后端存储是用的什么,原因是?
    A:没有使用Helm。目前集群有存储需求时,使用的是NFS。正在考虑建基于Ceph的存储,因为现在接入项目越来越多,不同的需求会导致不同的存储。

    Q:想了解下目前贵公司监控的纬度和监控的指标和告警这块。
    A:监控方面,我公司也是大致大致划分为基础资源,中间件,业务指标三大块监控。方法论上也是努力在向业界提倡的RED原则靠拢。

    Q:想了解下,Yaml文件怎么管理的,可以自定义生成吗?
    A:我们的Yaml文件,都统一纳到Prism4k平台管理,有一些资源是可以自定义的,且针对不同的项目,有不同的Yaml模板,然后,透过django的模块功能统一作解析。熟悉Yaml书写的研发同事可以自己定义自己项目的Yaml模板。

    Q:Pipeline会使用Jenkinfile来灵活code化Pipeline,把Pipeline的灵活性和创新性还给开发团队,这比一个模板化的统一Pipeline有哪些优势?
    A:Pipeline的运行模式,采用单一Job和每个项目自定义Job,各有不同的应用场景。因为我们的Jenkins是隐于幕后的组件,研发主要基于Prism4k操作,可以相对减少研发的学习成本。相对来说,Jenkins的维护人力也会减少。对于研发各种权限比较高的公司,那统一的Job可能并不合适。

    Q:想了解下贵公司使用什么网络方案?Pod的网络访问权限控制怎么实现的?
    A:公司现在用的是Flannel网络CNI方案。同时,在不同的集群,也有作Calico网络方案的对比测试。Pod的网络权限,这块暂时没有,只是尝试Istio的可行性研究。

    Q: 一个Job生成所有的Docker镜像,如果构建遇到问题,怎么去追踪这些记录?
    A:在项目前期接入时,生成镜像的流程都作了宣传和推广。标准化的流程,会减少产生问题的机率。如果在构建中遇到问题,Prism4k的界面中,会直接有链接到本次建的次序号。点击链接,可直接定位到Console输出。

    Q:遇到节点Node上出现100+ Pod,Node会卡住,贵公司Pod资源怎么做限制的?
    A:我们的业务Pod资源,都作了limit和request限制。如果出现有卡住的情况,现行的方案是基于项目作拆分。Prism4k本身对多环境和多集群都是支持的。

    Q:多环境下,集中化的配置管理方案,你们选用的是哪个,或是自研的?
    A:我们现在正在研发的Prism4k,前提就是要支持多环境多集群的部署,本身的功能里,yaml文件的配置管理,都是其内置功能。

    Q:etcd的--initial-cluster-state选项设置为new,重启etcd后会不会创建新的etcd集群?还是加入原有的etcd集群?
    A:我们测试过轮流将服务器(3 Master)完全重启,ectd集群的功能均未受影响。但全部关机重启,还未测试过。所以不好意思,这个问题,我暂时没有考虑过。

    Q:网络方案用的什么?在选型的时候有没有对比?
    A:目前主要应用的还是Flannel方案,今年春节以来,还测试过Flannel、Caclico、kube-router方案。因为我们的集群有可能越机房,而涉及到BGP协议时,节点无法加入,所以一直选用了Flannel。

    Q:部署的动态过程是在Jenkins的Web界面上看还是在自研的Prism4k上能看到,如果是Prism4k的话,整个可视化过程的展示这些等等也是自己开发的吗?Prism4k是用什么语言开发的,Python吗?
    A:部署的动态过程,是在Prism4k上显示。可视化方案,也只是简单的使用ajax及websocket。Prism4k后端是基于Django 2.0以上开发,其中使用了RESTful framework、channels等库,前端使用了一些js插件。

    以上内容根据2019年5月28日晚微信群分享内容整理。分享人陈刚,平安证券运维研发工程师,负责经纪业务IT应用的持续交付平台的设计和开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零九):荔枝运维平台容器化实践

    Andy_Lee 发表了文章 • 0 个评论 • 861 次浏览 • 2019-04-30 12:01 • 来自相关话题

    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布 ...查看全部
    【编者的话】荔枝微服务化进程较早,目前已有上千个服务模块,先前的运维平台渐渐无法满足微服务架构下运维管理的需求,于是决定从2018年开始重构运维平台,结合容器化技术,让开发人员尽可能无感知迁移的同时享受容器化带来的诸多好处。本次分享将主要为大家介绍我们项目发布系统重构过程中,技术选型的考虑以及实践过程中遇到的一些问题和解决方案。
    #背景

    荔枝后端微服务化进程较早,目前已有上千个服务模块,绝大多数是Java。得益于良好的规范,旧的运维平台实现了一套简单的自动化部署流程:

    1. 对接Jenkins进行编译打包和版本标记
    2. 把指定版本的jar包,配置文件,启动脚本一起发布到指定的机器上裸机运行
    3. 通过对Java进程的管理来完成重启,关闭应用等运维操作

    但是随着开发人员,项目数量,请求量的增加,旧的运维平台逐渐暴露出以下一些问题:

    1. Java实例部署所需资源没有清晰的统计和系统层面的隔离,仅仅依赖于启动脚本中的JVM参数来进行内存的约束,新增实例或新上项目时往往需要运维人员靠“感觉”指定部署的机器,没有有效地分配机器资源,项目之间资源争用会导致性能问题。
    2. 虽然大多数应用依赖的环境只有JDK 7和JDK 8,但一些JDK的小版本差异,以及一些自研发Java agent的使用,使得简单地指定JAVA_HOME目录的方式很难有效地管理运行环境。
    3. 开发人员的服务器权限需要回收。一个服务器上可能运行多个不同部门的项目,相关开发人员误操作可能会导致其他项目被影响。

    上述问题虽然也可以通过一些技术和规范约束来解决,但天生就是为了解决环境依赖和资源管理的容器技术是当下最合适的方案。
    #技术选型

    核心组件方面,Docker和Kubernetes是当下最成熟的开源容器技术。我们对强隔离没有太多的需求,所以没有使用KVM等虚拟机方案,直接在裸机上部署Kubernetes。

    分布式存储方面,容器化的项目大多是无状态的云原生应用,没有分布式存储的需求。极少数项目需要分布式存储的场合,我们会把已有的MFS集群挂载到宿主机,由宿主机挂载到容器里提供简单的分布式存储。

    容器本地数据卷方面,使用Docker默认的OverlayFS 2。我们服务器操作系统主要是CentOS,DeviceMapper在生产环境必须使用direct-lvm模式,该模式需要独立数据设备,对已有的SA自动化管理有一些影响。而OverlayFS 2在Linux内核4.17以上已经比较稳定,也不需要太多复杂的配置,开箱即用。

    日志收集方面,我们已有一套基于ELK的收集方案,对应用日志也有一定约束(必须将日志打印到指定目录下)。传统的基于控制台输出的Docker日志方案需要修改应用的日志输出配置,并且海量的控制台日志输出也会影响dockerd的性能,所以我们通过挂载日志数据盘的方式即可解决问题。

    监控方面,原有的监控设施是Zabbix,但在Kubernetes监控设施上Zabbix的方案显然没有亲儿子Prometheus成熟和开箱即用。所以在Kubernetes的监控方面,我们以Prometheus+Granfana为核心,使用kube-state-metrics采集Kubernetes运行数据。相比于Heapster,kube-state-metrics是Kubernetes生态的一部分,从Kubernetes的资源角度去采集数据,维度更多,信息更全面。

    最后是比较重要的Kubernetes网络方面,我们使用了比较新的网络方案kube-router。kube-router是基于三层Routing和BGP的路由方案,其优点如下:

    * 比Flannel等在数据包上再封装一层通信协议(常见是VXLAN)的网络实现性能上更优秀。
    * 比同样是基于BGP和三层路由的Calico来说更轻量简单,易于部署。
    * Macvlan技术会使宿主机网络和Pod网络隔离,不太符合我们的需求。
    * 在开启Service Proxy模式后可以取代默认组件kube-proxy,Service Proxy的实现是IPVS,在性能上和负载均衡策略上灵活度更高(在Kubernetes 1.8后kube-proxy也有IPVS的实现支持,但到现在还是实验性质)

    当然kube-router也存在一些不足:

    * 项目比较新,现在最新的还是v0.2.5,使用过程=踩坑。
    * 节点间网络必须二层可达,不像Calico提供了IPIP的解决方案。
    * 依赖于iptables,网络要求高的场景Netfilter本身会成为瓶颈。
    * 对于Pod IP的分配,Pod之间网络的ACL实现较为简单,无法应付安全要求高的场景。

    基于三层路由的CNI解决方案:
    1.jpg

    #业务落地实践

    搭好Kubernetes只是一个开始,我们这次重构有个很重要的目标是尽可能让业务开发方无感知无修改地把项目迁移到Kubernetes上,并且要保证实例部署和容器部署同时并行过度。

    理想的项目应该有Dockerfile声明自己的运行环境,有Jenkinsfile解决编译打包,有对应的Deployment和Service来告诉Kubernetes如何部署,但现实很骨干,我们有上千个项目,对应上千个Jenkins编译打包项目,逐一地修改显然不太现实。自动化运维的前提是标准化,好在项目规范比较严谨,符合了标准化这个充分条件。

    重新设计后的部署流程如下图所示:
    2.jpg

    构建方面,项目统一使用同一个Dockerfile模板,通过变更基础镜像来解决一些不同环境项目(比如需要使用JDK 7)的问题。基于Kubernetes Job和dind技术,我们开发了一个构建worker来实现从Jenkins拉取编译后的应用包并打包成镜像的流程,这样Jenkins打出来的应用可以同时用在实例部署和容器部署上。在运维后台上即可完成版本的构建:
    3.jpg

    部署方面,项目的部署配置分成两方面。资源配置一般不经常修改,所以仅仅只是在运维平台上修改记录。经常变更的版本变更和实例数变更则与部署操作绑定。将Kubernetes复杂的对象封装成扩展成原有项目对象的资源配置参数,执行部署时,根据项目资源配置,版本和实例数生成对应的Deployment和Service,调用Kubernetes API部署到指定的Kubernetes集群上。如果项目有在运维平台上使用静态配置文件,则使用ConfigMap存储并挂载到应用Pod里。
    4.jpg

    5.jpg

    在运维平台上提供Pod列表展示,预发环境debug应用,灰度发布,状态监控和webshell,方便开发观察应用运行情况,调试和日志查看,同时也避免开发SSH到生产环境服务器上,回收了服务器权限。
    6.jpg

    在应用从实例部署迁移到容器部署的过程中主要遇到以下几个问题:

    * Kubernetes集群内的Pod和集群外业务的通信问题。为了风险可控,实例部署和容器部署之间将会存在很长一段时间的并行阶段,应用方主要使用Dubbo做微服务治理,Kubernetes集群内的Pod和集群外业务的通信就成为问题了。kube-router是基于三层Routing实现,所以通过上层路由器指定Pod IP段的静态路由,或对接BGP动态交换路由表来解决问题。
    * JVM堆内存配置问题导致OOMKill的问题。因为JVM的内存不止有Xmx配置的堆内存,还有Metaspace或PermSize,以及某些如Netty等框架还有堆外内存,把Xmx的配置等同于容器内存配置几乎是一定会出现OOMKiil,所以必须放宽容器内存限制。以我们的经验来说,容器内存比Xmx多20%左右一般可以解决问题,但也有部分例外,需要额外配置。
    * Pod启动失败难以排查的问题。有一些Pod一启动就失败,输出的日志难以分析问题,我们构建和部署的描述文件都是运维平台动态生成的,很难使用传统docker run目标镜像的方式进行调试,所以我们在运维平台上提供了debug容器的功能,新建一个和原有deployment一样的debug部署,仅去掉健康检查相关的参数和修改command参数使pod运行起来,业务开发方即可通过webshell控制台进入Pod调试应用。

    #未来


    1. 开发经常需要使用的一些调试工具比如Vim,Arthas之类的,现在我们是打包到基础镜像中提供,但这样不仅增加了镜像的体积,而且需要重新打包新的镜像。目前看到比较好的解决方案是起一个调试用的容器并加到指定Pod的namespace中,但还需二次开发集成到webshell中。
    2. 跨机房Kubernetes集群调度。当现有资源无法满足峰值需求时,借助公有云来扩展系统是比较好的选择,我们希望借助Kubernetes多集群调度功能做到快速扩容到公有云上。
    3. 峰值流量的自动扩容和缩容,Kubernetes提供的HPA策略较为简单,我们希望能从更多的维度来计算扩容和缩容的数量,做到精准的控制。

    #Q&A

    Q:容器的Pod网络和外部网络全部打通吗,如何实现的?
    A:因为kube-router是基于三层路由,所以只要在顶层交换上指定Pod IP的静态路由即可,比如宿主机是192.168.0.1,该宿主机上的pod ip range是10.0.0.1/24,那只要在交换机或需要访问Pod的外部主机上添加路由10.0.0.1/24 via 192.168.0.1 ...即可。

    Q:你们如何去保证io的隔离?
    A:目前网络和硬盘的io没有做隔离,暂时还没有这方面的刚需。kube-router对网络IO这方面控制比较弱。硬盘IO方面Docker支持IOPS控制,但Kubernetes我还不太清楚是否支持。

    Q:Job和dind如何配合去实现打包镜像的呢?
    A:首先是dind技术,通过挂载宿主机的docker client和docker sock,可以实现在容器内调用宿主机的Docker来做一些事情,这里我们主要就用于build。Kubernetes的Job则是用于执行这个构建worker的方式,利用Kubernetes的Job来调度构建任务,充分利用测试集群的空闲资源。

    Q:从宿主机部署直接跨步到Kubernetes部署,不仅需要强力的power来推动,在落地实施的过程中,应该也会经历应用架构上的调整,能阐述你们在这方面遇到的困难和解决方式吗?
    A:Pod网络是最大的痛点,解决方式如文中所说。架构方面我们很早就是微服务去中心化的部署,API网关,服务注册发现等也是在容器化之前就已经完成改造的,所以应用架构反倒是没遇到多大的难题。

    Q:你们Kubernetes里面 统一配置是用的ConfigMap还是集成了第三方工具,例如Disconf。你们在Kubernetes中,APM用的是什么呢?Pinpoint还是Sky还是Jeager?还是其他?
    A:过去的项目配置文件是放运维平台上的,所以只要ConfigMap挂进去就可以了。后来新的项目开始采用携程的Apollo,Kubernetes上就只要通过ENV把Apollo的一些相关敏感信息传给Pod即可。APM方面因为我们是Java栈所以使用的skywalking,也是开篇提到的Java agent技术。

    Q:镜像仓库为什么选用Harbor,选型上有什么考虑?
    A:Harbor主要有UI方便管理,相对来说也容易部署和使用,尤其是权限管理这方面。

    Q:Macvlan和IPvlan性能非常好,几乎没有损耗,但默认都是容器和宿主机网络隔离的,但是也有解决方案,你们这边是没有考虑还是使用了一些解决方案发现有问题又放弃的?如果是后者,有什么问题让你们选择放弃?
    A:Macvlan之类的方式需要交换机层面上做一些配置打通VLAN,并且性能上并不会比基于三层的解决方案要高非常多,权衡之下我们还是选择比较易用的基于三层的方案,甚至为了易用而选择了更为激进的kube-router。

    Q:容器的多行日志收集如何解决?或者是,很多业务日志需要上下文关系,但是ELK只能查询到单条,这种情况怎么处理呢?
    A:容器多行日志的问题只存在于标准输出里,我们应用的日志是输出到指定目录下的,Filebeat有一些通用的多行日志解决方案。因为日志是存放在ES里的,所以可以通过调ES接口拿到某个Pod一个时间段里的日志,在UI上把它展示出来即可。

    Q:请问用的存储是什么?如何集成的?存储架构是怎样的?有考虑过Ceph吗?
    A:只有极少部分项目需要接分布式存储,并且对存储的管理,IOPS限制等没有硬性要求,所以我们把已有的MFS集群挂载到宿主机,再挂到容器里来实现。

    Q:Jenkins的Slave是用Pod Template创建的吗?Slave是Job共享还是需要时自动创建?
    A:Jenkins还是传统的master-slave单机部署模式,因为版本比较旧连Kubernetes Slave都不支持,所以我们只是调用了Jenkins的API来完成这个部署的过程。

    Q:Kubernetes在做存储挂载的时候,怎么保证容器漂移依然可以读取到共享存储?
    A:MFS挂载的话,文件是写入到MFS集群中的,那么挂载同样的MFS即可读到同一个文件。

    Q:关于命名空间,这边有哪些应用场景呢?
    A:按部门和场景区分ns,按ns分配节点和资源。未来打算基于ns做网络上的隔离和控制。

    Q:请问镜像大小是否有做优化?生产中有用alpine之类的base镜像吗?
    A:暂时没有,我们镜像的大小大约在100-300M之间。而且比起镜像大小的优化,运行环境的稳定和调试的便利更为重要。镜像有分层的策略,即使是很大的镜像,只要每次版本部署时更新的是最底层的镜像就不会导致每次都要拉取完整镜像。

    以上内容根据2019年4月29日晚微信群分享内容整理。分享人陈偲轶,荔枝研发中心DevOps工程师,负责运维平台的设计和开发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零八):华尔街见闻Istio生产实践

    尼古拉斯 发表了文章 • 0 个评论 • 750 次浏览 • 2019-04-24 18:41 • 来自相关话题

    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, ...查看全部
    【编者的话】随着见闻业务不断增加,所涉及语⾔也越来越多。由于微服务化的引入,就需要为不同语言开发各自的服务发现、监控、链路追踪等组件,更新运维成本较高。同时应用的灰度部署也是见闻在着⼒解决的问题。 Istio通过下沉基础设置,很好的解决了组件跨语言兼容问题, 同时带来了智能路由、服务熔断、错误注入等重要的特性。整个搭建过程中也遇到了很多坑和经验,希望和大家分享。

    见闻开发团队以Golang为主,同时存在Python,Java服务,这就需要SRE提供更容易接入的微服务基础组件,常见的方案就是为每种语言提供适配的微服务基础组件,但痛点是基础组件更新维护的成本较高。

    为了解决痛点,我们将目光放到服务网格,它能利用基础设施下沉来解决多语言基础库依赖的问题,不同的语言不需要再引入各种不同的服务发现、监控等依赖库,只需简单的配置并运行在给定的环境下,就能享有服务发现、流量监控、链路追踪等功能,同时网络作为最重要的通信组件,可以基于它实现很多复杂的功能,譬如智能路由、服务熔断降级等。

    我们调研了一些服务网格方案,包括Istio、Linkerd。

    对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构可行性讨论,我们选择Istio作为实践方案。

    服务网格架构图:
    1.png

    这张图介绍了华尔街见闻典型的服务网格架构,左半图介绍了用户请求是如何处理,右半图介绍运维系统是如何监控服务。
    #架构可行性
    通过架构图,我们拆分出以下关键组件,经过评估,Istio高度模块化、可拓展,各个组件的可用性、拓展性都有相应的策略达到保障,我们认为Istio是具有可实施性的。
    ##Istio Ingress高性能,可拓展

    性能:Istio Ingress用来处理用户入流量,使用Envoy实现,转发性能高。


    可用性:保证实例数量并使用服务探活接口保证服务可用性。


    拓展性:挂载在负载均衡后,通过增加实例实现可拓展。


    ##Istio Proxy随应用部署,轻微性能损耗,可随应用数量拓展

    Istio Proxy以Sidecar形式随应用一起部署,增加2次流量转发,存在性能损耗。

    
性能:4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)

    
单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。 通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。


    CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量解决该问题,经过测试环境测试,延时可以接受。 


    可用性:基于Envoy,我们认为Envoy的可用性高于应用。依赖Pilot Discovery进行服务路由,可用性受Pilot Discovery影响。


    拓展性:Sidecar形式,随应用数拓展。


    ##Istio Policy服务可拓展,但同步调用存在风险

    Istio Policy需要在服务调用前访问,是同步请求,会增加服务调用延时,通过拓展服务数量增加处理能力。属于可选服务,华尔街见闻生产环境未使用该组件。 


    性能:未测试。


    可用性:若开启Policy,必须保证Policy高可用,否则正常服务将不可用。


    拓展性:增加实例数量进行拓展。

    ##Istio Telemetry监控收集服务
    
性能:从监控上观察Report 5000qps,使用25核,响应时间p99在72ms。异步调用不影响应用的响应时间。


    可用性:Telemetry不影响服务可用性。


    拓展性:增加实例数量进行拓展。

    ##Pilot Discovery

    性能:服务发现组件(1.0.5版本)经过监控观察,300个Service,1000个Pod,服务变更次数1天100次,平均CPU消耗在0.04核左右,内存占用在1G以内。


    可用性:在服务更新时需要保证可用,否则新创建的Pod无法获取最新路由规则,对于已运行Pod由于Proxy存在路由缓存不受Pilot Discovery关闭的影响。


    拓展性:增加实例数量可以增加处理量。
    #服务监控
    Istio通过mixer来搜集上报的遥测数据,并自带Prometheus、Grafana等监控组件,可方便的对服务状态进行监控。见闻目前监控在用Istio自带的,利用Prometheus拉取集群指标,经由Grafana看板展示,可直观展示出各种全局指标及应用服务指标,包括全局QPS、全局调用成功率、各服务延时分布情况、QPS及状态码分布等, 基本满足监控所需。

    存储暂未做持久化操作,采用Prometheus的默认的内存存储,数据的存储策略可通过设定Prometheus的启动参数`--storage.tsdb.retention`来设定,见闻生产时间设定为了6h。

    Prometheus服务由于数据存储原因会消耗大量内存,所以在部署时建议将Prometheus部署在专有的大内存node节点上,这样如果内存使用过大导致该node节点报错也不会对别的服务造成影响。Istio mixer担负着全网的遥测数据搜集任务,容易成为性能瓶颈,建议和Prometheus一样将其部署在专有节点上, 避免出现问题时对应用服务造成影响。

    以下是Mesh汇总面板的Demo:
    2.jpg

    Service Mesh汇总监控页面
    #链路追踪
    Envoy原生支持分布式链路追踪, 如Jaeger、Zipkin等,见闻生产选择了Istio自带的Jaeger作为自身链路追踪系统。 默认Jaeger服务为all_in_one形式,包含jaeger-collect、jaeger-query、jaeger-agent等组件,数据存储在内存中。

    见闻测试集群采用了此种部署形式。见闻生产环境基于性能与数据持久性考虑,基于Cassandra存储搭建了单独的jaeger-collect、jaeger-query服务。Jaeger兼容Zipkin,可以通过设定Istio ConfigMap中的`zipkinAddress`参数值为jaeger-collector对应地址,来设置Proxy的trace上报地址。同时通过设置istio-pilot环境变量`PILOT_TRACE_SAMPLING`来设置tracing的采样率,见闻生产采用了1%的采样率。

    Tracing不像Istio监控系统一样对业务代码完全无侵入性。Envoy在收发请求时若发现该请求没有相关trace headers,就会为该请求自动创建。tracing的实现需要对业务代码稍作变动,这个变动主要用来传递trace相关的header,以便将一次调用产生的各个span串联起来。

    见闻底层采用 grpc 微服务框架,在Gateway层面将trace header 加入到入口grpc调用的context中,从而将trace headers 逐级传递下去。
    3.jpg

    #Istio探活
    Istio通过向Pod注入Sidecar接管流量的形式实现服务治理,那么就会有Sidecar与业务容器状态不同步的可能,从而造成各种的调用问题,如下两方面:

    * Sidecar就绪时间晚于业务容器:业务容器此时若发起调用,由于Sidecar还未就绪, 就会出现类似no healthy upstream之类错误。若此时该Pod接收请求,就又会出现类似upstream connect error错误。
    * Sidecar就绪时间早于业务容器:例如某业务容器初始化时间过长导致Kubernetes误以为该容器已就绪,Sidecar开始进行处理请求,此时就会出现类似upstream connect error错误。

    4.jpg

    5.jpg

    探活涉及Sidecar、业务容器两部分,只有两部分同时就绪,此Pod才可以正式对外提供服务,分为下面两方面:

    * Sidecar探活:v1.0.3及以上版本针对Pilot-agent新增了一个探活接口healthz/ready,涉及statusPort、applicationPorts、adminPort等三个端口。其基本逻辑为在statusPort上启动健康监测服务,监听applicationPorts设定的端口是否至少有一个成功监听,之后通过调用本地adminPort端口获取xDS同步状态。若满足至少一个applicationPort成功监听,且CDS、LDS都进行过同步,则该Sidecar才被标记为Ready。
    * 业务容器探活:见闻通过基本库,在所有Golang grpc后端服务中注册了一个用于健康检查的handler, 该handler可由开发人员根据自身业务自定义,最后根据handler返回值来判断业务容器状态(如下图)。

    6.jpg

    #Istio应用更新
    为了实现灰度部署,见闻基于Kubernetes Dashboard进行了二次开发,增加了对Istio相关资源的支持,利用Gateway、VirtualService、DestinationRule等crd实现了应用程序的灰度部署,实际细节如下:

    1. 更新流量控制将流量指向已有版本
,利用VirtualService将流量全部指向v1版本(见下动图)。
    2. 部署新版本的Deployment
,查找旧的Deployment配置,通过筛选app标签符合应用名的Deployment,运维人员基于该Deployment创建v2版本的Deployment,并向destinationRule中增加v2版本。
    3. 更新流量控制将流量指向新版,本
利用VirtualService将ServiceA的服务流量全部指向v2版本。
    4. 下线老版本的Deployment并删除对应DestinationRule。

    利用Istio Dashboard来实现上述流程:
    07.gif

    为了方便开发人员服务部署,开发了精简版后台,并对可修改部分进行了限定。最终,SRE提供两个后台,对Istio Dashboard进行精细控制:
    08.gif

    #实践中的宝贵经验
    在Istio实践过程中,有哪些需要注意的问题。
    ##API server的强依赖,单点故障
    
Istio对Kubernetes的API有很强的依赖,诸如流量控制(Kubernetes资源)、集群监控(Prometheus通过Kubernetes服务发现查找Pod)、服务权限控制(Mixer Policy)。所以需要保障API server的高可用,我们曾遇到Policy组件疯狂请求Kubernetes API server使API server无法服务,从而导致服务发现等服务无法更新配置。


    
* 为避免这种请求,建议使用者了解与API server直接通信组件的原理,并尽量减少直接通信的组件数量,增加必要的Rate limit。
    

* 尽量将与API server通信的服务置于可以随时关闭的环境,这是考虑如果部署在同一Kubernetes集群,如果API server挂掉,无法关闭这些有问题的服务,导致死锁(又想恢复API server,又要依靠API server关闭服务)。

    ##服务配置的自动化
    
服务配置是Istio部署后的重头戏,避免使用手动方式更改配置,使用代码更新配置,将常用的几个配置更新操作做到运维后台,相信手动一定会犯错。
    ##关于Pilot Discovery

    Pilot Discovery 1.0.0版本有很大的性能问题,1.0.4有很大的性能提升,但引入了一个新bug,所以请使用1.0.5及以上的版本,我们观察到CPU消耗至少是1.0.0版本的1/10,大大降低了Proxy同步配置的延时。
    ##关于Mixer Policy 1.0.0

    这个组件曾导致API server负载过高(很高的list pods请求),所以我们暂时束之高阁,慎用。
    ##性能调优

    在使用Proxy、Telemetry时,默认它们会打印访问日志,我们选择在生产上关闭该日志。
时刻观察Istio社区的最新版本,查看新版本各个组件的性能优化以及bug修复情况,将Istio当做高度模块化的系统,单独升级某些组件。上面就提到我们在Istio1.0的基础上使用了1.0.5版本的Policy、Telemetry、Pilot Discovery等组件。
    ##服务平滑更新和关闭
    
Istio依靠Proxy来帮助APP进行路由,考虑几种情况会出现意外的状态:

    
* APP启动先于Proxy,并开始调用其它服务,这时Proxy尚未初始化完毕,APP调用失败。
    

* Service B关闭时,调用者Service A的Proxy尚未同步更新Service B关闭的状态,向Service B发送请求,调用失败。
第一种情况要求APP有重试机制,能适当重试请求,避免启动时的Proxy初始化与APP初始化的时差,Istio提供了retry次数配置,可以考虑使用。 
第二种情况,一种是服务更新时,我们使用新建新服务,再切流量;一种是服务异常退出,这种情况是在客户端重试机制。希望使用Istio的开发人员有更好的解决方案。

    #Q&A
    Q:学Service Mesh什么用?
    A:Service Mesh是最近比较火的一个概念,和微服务、Kubernetes有密切关系。出于以后业务发展需要,可以学习下, 增加知识储备。见闻上Istio的主要目的在文章已说明,主要是基础服务的下沉,解决了语言兼容性问题, 还有一个就是灰度发布。

    Q:链路追踪的采集方式是怎样的,比如Nodejs,C++等多语言如何支持的?
    A:Envoy本身支持链路追踪,也就是将Envoy会在请求head中增加链路追踪相关的数据头,比如x-b3-traceid,x-b3-spanid等等。业务要做的就是将这些head沿着调用链路传递下去即可。所以多语言的话需要在自己的业务侧实现该逻辑。所以Istio的链路追踪对业务代码还是有很小的侵入性的(这个分享中有说明)。

    Q:Istio与Spring Cloud两者的优缺点与今后的发展趋势会是怎么样?
    A:见闻技术栈是Golang,所以没太认真对比过两者。从社区活跃度上将,Istio > Spring Cloud,稳定性方面,Spring Cloud是更有优势,更适合Java沉淀较深的企业。但个人感觉对于更多企业来讲,跨越了语言兼容性的Istio未来发展很值得期待。

    Q:Docker镜像部署可以做到代码保护吗,比如像Nodejs这种非二进制执行程序的项目?
    A:代码保护可以通过将镜像上传至指定云服务商上的镜像仓库中,见闻是将自己的业务镜像提交保存在了腾讯云。如果镜像泄露,那么非二进制执行程序的代码还是有泄露风险的。

    Q:选型时为什么没考虑Linkerd?
    A:选型之初也调研了Linkerd, 对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构相较于见闻有较大可行性,所以选择了Istio作为实践方案。

    Q:Istio在做运维部署时没有UI工具,你们如何实现运维人员更加便捷地使用?
    A:见闻基于Kubernetes官方的Dashboard, 对内容进行了扩充,增加了对Gateway,VirtualService等Istio crd资源的支持, 同时增加了灰度部署等和见闻运维业务相关的功能,所以一定程度上解决了运维部署的问题。

    Q:流量从Sidecar代理势必会对请求响应时间有影响,这个有没有更详细案例的说明性能上的损耗情况?
    A:Sidecar的转发其实带来了性能一定的性能损耗。4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。通过代理访问的QPS6.8k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。 CPU消耗以及转发消耗降低了QPS,增加了延时,通过增加机器核数并增加服务部署数量缓解该问题,经过测试环境测试,延时可以接受。

    Q:Sidecar在生产中资源占用为多少?是否会对集群资源占用很多?
    A:以单个Pod为例,见闻业务单个Pod中Sidecar所占资源约占整个Pod所耗资源的1/10。

    Q:Jeager你们是进行了代码埋点吗?更为底层代码级别的追踪,有用其他方案吗?
    A:Envoy本身对tracing有良好的支持,所以业务端所做的改动只需将其所产生的追踪数据延续下去即可。上Istio之前,见闻在相关微服务中通过在基础库中增加链路追踪逻辑(Zipkin)实现了链路追踪,不过只做了Golang版,多语言兼容开发运维难度较大。

    Q:相信咱们的mixer在生产环境中,也出现过瓶颈,咱们从哪几个大方向优化的?如何优化的?方面讲解一下吗?
    A:mixer见闻在生产过程中所遇的坑在于Policy组件, 会疯狂的list pod,导致API server负载骤增,之后见闻基于自身业务关闭了Policy。

    Q:Istio组件实现了高可用么?
    A:Istio本身也是基于Kubernetes,所以可用性还是有较好保证的。

    以上内容根据2019年4月23日晚微信群分享内容整理。分享人张安伟,华尔街见闻SRE团队运维工程师,负责公司日常运维开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零七):瓜子云平台的实践经验

    齐达内 发表了文章 • 0 个评论 • 952 次浏览 • 2019-04-17 15:38 • 来自相关话题

    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结 ...查看全部
    【编者的话】私有云平台的建设和公司在不同阶段的需求是息息相关的,瓜子云平台从 2017 年启动项目,到目前承载了公司上千个应用服务,每月服务发布次数达上万次。在公司业务爆发性增长的背景下,云平台团队从 0 到 1 的完成了平台搭建,初步实现了平台产品化,也总结出了一些云平台建设的实践和经验。

    这篇文章和大家分享下瓜子云平台的一些实践经验。瓜子是在 2017 年年中启动云平台项目的,当时有如下背景:

    * 技术栈多样化,PHP、Java、Go、Python 都有使用,但只有 PHP 建立了相对统一的部署流程
    * 业务迭代速度快,人员扩张速度快,再加上微服务化改造,项目数量激增,基于虚拟机的运维压力很大
    * 测试环境没有统一管理,业务开发人员自行零散维护

    基于此,我们的 0.x 版本,也是原型版本选择了如下的切入点:

    * 在 CI/CD 层面,先定义出标准流程,但是并不涉及细节的规范化,便于用户学习,快速将现有流程套进去
    * 同时支持 image 和 tar 包两种产出,为云上部署和虚拟机部署做好构建路径的兼容,在将来迁移时这部分可以做到几乎无缝
    * 先支持测试环境的部署,在验证平台稳定性的同时,不断收集用户需求进行快速迭代

    #集群核心组件的技术选型
    在服务编排和资源管理方面,Docker 和 Kubernetes 已经比较成熟了,基于容器的微服务化也是大势,再加上我们对强隔离没有诉求,所以跳过了 OpenStack,直接选择了 Kubernetes 方案。

    既然用了 Kubernetes 肯定就要解决跨节点的容器网络通信问题,因为我们是自建机房,没有公有云在网络层面的限制,所以没有考虑应用范围更广但是性能和可调试性较差的 VXLAN 方案。最开始选择的是 Macvlan + 自研 IPAM 的方式,之后转向了方案完成度更高的基于 BGP 协议的 Project Calico。

    Calico 的优点如下:

    * 基于 BGP 协议,转发平面依靠主机路由表,不涉及任何封包解包操作,性能非常接近原生网卡,并且方便抓包调试
    * 组件结构简单,对 Kubernetes 支持很好
    * 可以和 IDC 路由器通过 BGP 协议打通,直接对外广播容器 IP,让集群内外可以通过 IP 直连
    * 有 ACL 能力,类似 AWS 的 Security Group

    当然肯定有缺点:

    * 节点间网络最好是二层可达,否则只能使用 IP-in-IP 这种隧道技术,那就失去大半意义了
    * 因为是基于三层转发的,无法做到二层隔离,安全诉求高的场景不适用
    * 严重依赖 iptables,海量并发情况下 netfilter 本身可能成为瓶颈

    编排和网络解决后,集群就可以拉起来,接下来需要考虑的是流量如何进入,也就是负载均衡器的选型。在不想过多自己开发的前提下,使用 Kubernetes Ingress 是一个比较好的选择,但是不同 Ingress 之间功能和成熟度差异都很大,我们也是尝试了好几种,从 Nginx 到 Linkerd,最终选择了 Istio。我们在 0.2 版本开始用 Istio,初期它的不稳定带来了很多困扰,不过在 1.x 版本发布后相对稳定很多了。选择 Istio 的另一个原因是为将来向 Service Mesh 方向演进积累经验。

    以上一个最小功能可用集群就基本完备了,接下来在 CI/CD 这块,我们封装了一个命令行工具 med,它通过读取项目中的 med.yml,自动生成 dockerfile 和 deployment.yml,之后调用 Docker 进行构建,并提交给 Kubernetes 集群进行部署。

    一个 med.yml 例子如下:
    01.png

    从上面这个例子能看到,我们将构建环节拆分成了 prepare 和 build 两个部分,这个设计其实来源于 dockerfile 的一个最佳实践:由于拉取依赖的环节往往较慢,所以我们习惯将依赖安装和编译过程放到不同的命令中,形成不同的 layer,便于下次复用,提高编译速度。prepare 阶段就是把这部分耗时但是变更不频繁的操作提出来,通过 version 字段控制,只要 version 值不变,prepare 部分就不会再重复执行,而是直接利用之前已经生成好的 prepare 镜像进行接下来的 build 环节。build 环节构建结束后将 image 提交到镜像仓库,等待用户调用 deploy 命令进行部署。

    在实际开发过程中用户提出同一个代码仓库构建多个产出的需求,比如一个 Java 项目同时构建出用于发布的 Web Service 和提交到 Maven 的 jar 包。所以 build 和 deploy 阶段都支持多个产出,通过 name 进行区分,参数方面则支持有限的模板变量替换。

    最后,我们将 med.yml 和 GitLab CI 结合,一个 CI/CD 流程就出来了,用户提交代码后自动触发构建,上传镜像并部署到他的测试环境
    02.png

    #云平台产品化的一些思考
    ##云平台 1.x
    在测试环境跑了一段时间后,大家纷纷尝到了甜头,希望不只是在测试环境使用,生产环境也可以部署。这时简易的客户端工具就不满足需求了,新增的主要诉求围绕着权限管理和灰度发布,同时也希望有一个更加可视化的平台能看到实例运行的状态,上线的进度和监控日志等。需求细化如下:

    * 能直观看到当前项目的运行状态,部署状态
    * 项目有权限控制
    * 发布系统支持预发布和灰度部署
    * 日志收集和监控报警能力
    * 配置管理能力
    * 定时任务支持

    于是经过几轮迭代后,可视化的控制台上线了:
    03.png

    这里可以详细展开下部署环节,为了满足应用上线时的小流量测试需求,最开始是通过用户换个名字手工部署一个新版本,然后灵活调整不同版本部署之间的流量百分比来实现。但是在运行一段时间后,发现太高的灵活度调整非常容易出错,比如忘记了一个小流量部署,导致线上不正常,还很难 debug,并且整个操作过程也很繁琐。于是我们收敛了功能,实现了流程发布功能:
    04.png

    无论是流量调整还是流程发布,Kubernetes 默认的滚动更新都是无法满足需求的。我们是通过先创建一组新版本实例,然后再变更 Istio 的流量切换规则来实现的,示意图如下:
    05.png

    同时为了既有项目平滑上云,我们还提供了容器和虚拟机部署的联合部署,支持一个项目在一个发布流程中同时部署到云平台和虚拟机上。再配合外部 Nginx 权重的跳转,就可以实现业务逐步将流量切换到云上,最终完全去掉虚拟机

    这里有一个小插曲,由于基于 TCP 的 Dubbo 服务流量不经过 Istio 网关,而是通过注册到 ZooKeeper 里面的 IP 来进行流量调整的,上面的流程发布和联合部署对 Dubbo 服务没有意义。我们在这个版本进行了一个网络调整,将集群内部的 BGP 路由和外部打通,允许容器和虚拟机直接通信。由于 Calico 更换网段是要重新创建所有容器的,所以我们选择拉起一个新集群,将所有应用全部迁移过去,切换流量入口,再下掉旧集群。这里就体现了容器的便捷性了,数百个应用的迁移只花了十几分钟就再另一个集群完全拉起,业务几乎没有感知。

    配置管理方面,最开始是通过 env 管理,后来很多应用不太方便改成读取 env 的方式,又增加了基于 ConfigMap 和配置中心的配置文件注入能力。
    06.png

    日志收集方面,最开始使用 ELK,只收集了容器的 stdout 和 stderr 输出,后来对于放在指定位置的文件日志也纳入了收集目标中。由于 Logstash 实在太耗资源,我们直接使用 ES 的 ingest 能力进行日志格式化,去掉了中间的 Kafka 和 Logstash,从 Filebeat 直接输出到 ES。当然 ingest pipeline 本身调试起来比较复杂,如果有较多的日志二次处理需求,不建议使用。日志展示方面,Kibana 原生的日志搜索能力已经比较强大,不过很多人还是喜欢类似 tail -f 的那种查看方法,我们使用了 Kibana 的第三方插件 Logtrail 进行了模拟,提供了一键从控制台跳转到对应容器日志查看界面的能力。

    监控收集和展示,也是标准的 Prometheus + Grafana,在收集 Kubernetes 暴露出来的性能指标的同时,也允许用户配置自定义监控的 metric url,应用上线后会自动抓取。报警方面,因为 Prometheus 自带的 Alert Manager 规则配置门槛比较高,所以我们开发了一个用于规则配置的项目 NieR,将常用规则由运维同学写好后模板化,然后再提供给用户订阅,当然用户也可以自行建立自己的模板。监控报警系统展开说能再写一篇文章了,这里就只放一下架构图:
    07.png

    ##云平台 2.x
    在 1.x 版本迭代的时候,我们发现,早期为了给用户最大灵活性的 med.yml 在用户量持续增长后带来的培训、运维成本越来越高。每一个第一次接触平台的同事都要花费半天的时间阅读文档,然后在后续的使用中还有很多文档没有描述清楚的地方要搞明白,变相提高了项目上云的门槛。另外这种侵入用户代码仓库的方式,每次调整代价都非常大,服务端控制力度也太弱。

    针对上述问题,我们在 2.x 版本彻底去掉了 med.yml,实现了全部 UI 化。这里并不是说把之前的配置文件丢到一个管理页面上就算搞定了。拿构建来说,用户希望的是每种语言有一个标准的构建流程,只需要稍微修改下构建命令就可以直接使用,于是我们定义了语言模板:
    08.png

    然后替用户填好了大部分可以规范化的选项,当然也允许用户自行编辑:
    09.png

    10.png

    在部署层面,除了和构建产出联动外,最大的变动是参数合理化布局,让新用户基本不用看文档就能明白各个参数的用途。
    11.png

    2.x 版本才刚刚起步,后续还有非常多在排期安排的事情,比如在功能方面:

    * 支持多集群部署之后如何做到跨集群调度
    * 如何方便的能让用户快速拉起一套测试环境,乃至于构建自己的内部应用市场
    * 监控系统能不能进一步抽象,直接通过 UI 的方式配置监控模板,能不能自动建议用户合理的监控阈值
    * 给出各个业务的资源利用率和账单

    在基础设施层面:

    * 能不能做到不超售但是还能达成合理的资源利用率
    * 离线计算能不能在低峰期复用在线集群资源,但是不能影响业务
    * ServiceMesh 的进一步研究推广
    * 有状态服务的支持等等

    等等

    以上就是瓜子云平台的整体迭代路径。在整个开发过程中,我们感触最深的一点是,需要始终以产品化而不是做工具的思想去设计和实现。技术是为了需求服务的,而不是反过来,把一个用户最痛的痛点打透比做一百个酷炫的功能有用的多。但打透说起来容易,做起来有很多脏活累活。

    首先在需求分析阶段,基础设施的变更影响非常广泛,在征求大部分人意见的同时,如何引导大家往业界内更先进的路线上演进是要经过深思熟虑的。另外不同阶段的需求也不尽相同,不要一窝蜂的追随技术潮流,适合当前阶段的才是最好的技术选型。

    其次切入点最好是选择共识基础好,影响范围大的需求点,阻力小,成果明显。待做出成果后再一步步扩展到分歧比较严重的深水区。

    最后落地的时候要做好技术运营,做好上线前的宣传培训,帮助用户从旧系统尽量无痛的进行迁移。上线后的持续跟踪,通过数据化的手段,比如前端埋点,核心指标报表等手段观察用户的使用情况,不断调整策略。

    上面这些在团队小的时候可能都不是问题,一个沟通群里直接就能聊清楚。但当团队变大后,核心功能上一个不当的设计往往带来的就是上千工时的白白消耗甚至造成线上事故,一个云平台产品能不能落地,技术架构和实现是一方面,上面这些产品和运营策略是否运用得当也是非常重要的。
    #Q&A
    Q:请问瓜子私有云是一朵独立的云还是多云部署?如果是多云部署,云间网络是采用的什么技术?如何打通多云之间的网络的?谢谢
    A:我们在设计之初就考虑多集群 / 多 IDC 部署的,这也是选择 Calico 的一个原因。通过 BGP 协议将容器 IP 广播出去后,云间互联和普通虚拟机互联没有区别,当然这需要网络设备支持。

    Q:云平台在 PaaS 层,采用的编排工具是什么,如何打通容器之间的部署,在容器的架构上是怎么实现负载均衡的?
    A:采用的是 Kubernetes,打通使用的是 Calico,负载均衡使用的是 Istio Ingress。

    Q:新版本实例发布的时候怎么切Istio才能保障灰度的流量不丢失呢?
    A:在流程发布里面,我们相当于先新建一组新的实例,在它们的 Readiness 检查通过后,切换 Istio 规则指向新实例做到的。

    Q:稳定性方面有没有出现过比较大的问题,怎么解决的?
    A:有两个时期稳定性故障较多,一个是 Istio 版本比较低的时候,这个只能说一路趟坑趟过来,我们甚至自己改过 Istio 代码,现在的版本已经没出过问题了;一个是集群用的太狠,当集群接近满载时,Kubernetes 会出现很多连锁问题,这个主要是靠监控来做及时扩容。

    Q:自建机房的话为什么不接着使用 Macvlan + IPAM 方案呢?是为了之后上公有云做准备吗?
    A:当时面临一个本机 Macvlan 容器互相不通的问题,再加上有熟悉的团队已经在生产跑了很久 Calico 了,所以就直接换到了 Calico。

    Q:如果服务发现是基于 Dubbo + ZooKeeper,那 Kubernetes 自身的 Service 还有在使用吗?
    A:Kubernetes 自己的 Service 我们现在内部管理工具在使用,在 2.x 版本也开始开放给业务使用了(文中截图能看到内部域名)。

    Q:请问几秒的时延对一些高效的服务来讲也是不可接受的。咱们有想过通过何种方式去避免灰度的流量损失问题么?
    A:还真没遇到过这个需求。我理解如果有一个服务如此关键,那么在整个流量变更环节(从机房入口开始)到灰度策略上都得仔细考虑。如果接受不了 Istio 这个延时,一个思路是完全抛弃 Istio Ingress,直接使用一个切换迅速的负载均衡器。因为容器 IP 是直通的,完全可以从集群外直接连进来,只要解决服务发现的问题就行。

    Q:应用服务追踪怎么处理的?对接Istio?
    A:语言栈比较多的情况下这是个痛点,目前我们也是在尝试,即使是 Sidecar 也不能完全对业务没侵入。公司内部 Java 技术栈更喜欢 Skywalking 这种完全无侵入的方式。

    Q:使用 Istio 时,怎么解决性能损坏问题的?
    A:目前还没有启用 Mixer 这些对性能影响比较大的组件,所以目前性能损耗还是比较小的。如果对性能有严格的要求,我们会建议他使用 service name 做直连。

    Q:Prometheus 的告警是靠编辑大量的 rule.yml,请问生产中是怎么管理的?规则编辑比较麻烦,怎么解决?Prometheus 是单点,有没有扩容方案?
    A:就是靠 Nier 这个组件,将规则抽象成模板,甚至在云平台上面对于简单的规则直接变成了选项。然后模板渲染成规则。我们的 Prometheus 用的官方的联邦集群模式,存储放在了 Ceph 上面。


    Q:为什么 Kubernetes 默认的滚动更新不能满足要求?哪里有问题?
    A:没办法精细控制灰度粒度,比如部署了 4 个实例,我要求切 10% 的流量灰度,这就做不到了。另外,滚动更新回滚也比较慢。

    Q:注册到 ZooKeeper 的 IP 是 Pod IP?ZooKeeper 从外部直接访问Pod IP 吗?
    A:对的,Pod 和 VM 能直通,也就是说一个 Dubbo 服务能同时部署在 VM 和容器里面。

    Q:目前支撑的生产应用服务规模和云平台的规模能介绍下?包括一些指标,比如多少应用进行灰度更新耗时?
    A:应用规模的话目前超过 1000 了,每个月发布次数超过 10000。灰度更新是用户自行控制整个发布进度的,所以耗时不太有参考意义。

    以上内容根据2019年4月16日晚微信群分享内容整理。分享人高永超(flex),瓜子二手车技术总监,在容器云、DevOps 领域有丰富的架构经验。目前在主导瓜子云平台的研发工作。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零六):容器环境下的持续集成最佳实践

    大卫 发表了文章 • 0 个评论 • 1000 次浏览 • 2019-04-03 11:49 • 来自相关话题

    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semanti ...查看全部
    【编者的话】本次分享结合 Drone + GitFlow + Kubernetes 介绍一些容器环境下持续集成、持续发布方面的实践经验。分享将展示从 Hello World 到单人单分支手动发布到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    云原生(Cloud Native)是伴随的容器技术发展出现的的一个词,最早出自 Pivotal 公司(即开发了 Spring 的公司)的一本技术小册子 Migrating to Cloud-Native Application Architectures, 其中定义了云原生应用应当具备的一些特质,如无状态、可持续交付、微服务化等。随后云原生概念被广为引用,并围绕这一概念由数家大厂牵头,成立了 CNCF 基金会来推进云原生相关技术的发展,主要投资并孵化云原生生态内的若干项目,包括了如 Kubernetes / etcd / CoreDNS 等耳熟能详的重要项目。而这张大大的云原生版图仍然在不断的扩展和完善。

    从个人理解来说,传统的应用由于年代久远,更多会考虑单机部署、进程间通信等典型的“单机问题”,虽然也能工作在容器下,但由于历史包袱等原因,架构上已经很难做出大的调整。而“云原生”的应用大都是从一开始就为容器而准备,很少考虑在单机环境下使用,有些甚至无法脱离容器环境工作;考虑的场景少,从而更轻量,迭代更快。比如 etcd 之于 ZooKeeper, Traefik 之于 Nginx 等,相信只要在容器环境下实现一次同样的功能,就能强烈的体会到云原生应用所特有的便捷之处。

    在 CNCF 的版图下,持续集成与持续交付(Continuous Integration & Delivery)板块一直缺少一个钦定的主角,虽然也不乏 Travis CI、GitLab、Jenkins 这样的知名项目,但最能给人云原生应用感觉的,应该还是 Drone 这个项目,本文将围绕 Drone 结合 GitFlow 及 Kubernetes 介绍一些容器环境下持续集成、持续发布(CI/CD)方面的实践经验。
    #主流 CI/CD 应用对比
    之前我也介绍过基于 Travis CI 的一些持续集成实践。后来经过一些比较和调研,最终选择了 Drone 作为主力 CI 工具。截止本文,团队已经使用 Drone 有 2 年多的时间,从 v0.6 一路用到现在即将发布的 v1.0,虽然也踩了不少坑,但总的来说 Drone 还是可以满足大部分需求,并以不错的势头在完善和发展的。

    下面这张表总结了主流的几个 CI/CD 应用的特点:
    B1.png

    Travis CI 和 CircleCI 是目前占有率最高的两个公有云 CI,易用性上相差无几,只是收费方式有差异。由于不支持私有部署,如果并行的任务量一大,按进程收费其实并不划算;而且由于服务器位置的原因,如果推送镜像到国内,速度很不理想。

    GitLab CI 虽然好用,但和 GitLab 是深度绑定的,我们的代码托管在 GitHub,整体迁移代码库的成本太大,放弃。

    Jenkins 作为老牌劲旅,也是目前市场占有率最高的 CI,几乎可以覆盖所有 CI 的使用场景,由于使用 Java 编写,配置文件使用 Groovy 语法,非常适合 Java 为主语言的团队。Jenkins 显然是可以满足我们需要的,只是团队并非 Java 为主,又已经习惯了使用 YAML 书写 CI 配置,抱着尝鲜的心态,将 Jenkins 作为了保底的选择。

    综上,最终选择 Drone 的结论也就不难得出了,Drone 即开源,又可以私有化部署,同时作为云原生应用,官方提供了针对 Docker、Docker Swarm、Kubernetes 等多种容器场景下的部署方案,针对不同容器场景还有特别优化,比如在 Docker Swarm 下 Drone 是以 Agent 方式运行 CI 任务的,而在 Kubernetes 下则通过创建 Kubernetes Job 来实现,显然充分利用了容器的优势所在,这也是 Drone 优于其他 CI 应用之处。个人还觉得 Drone 的语法是所有 CI 中最容易理解和掌握的,由于 Drone 每一个步骤都是运行一个 Docker 容器,本地模拟或调试也非常容易。

    一句话概况 Drone,可以将其看做是可以支持私有化部署的开源版 CircleCI,并且目前仍然没有看到有其他主打这个定位的 CI 工具,因此个人认为 Drone 是 CI/CD 方面云原生应用头把交椅的有力竞争者。
    #容器环境下一次规范的发布应该包含哪些内容
    技术选型完成后,我想首先演示一下最终的成果,希望能直观的体现出 CI 对自动化效率起到的提升,不过这就涉及到一个问题:在容器环境下,一次发布应该包含哪些内容,其中有哪些部分是可以被 CI 自动化完成的。这个问题虽然每家公司各不相同,不过按经验来说,容器环境下一次版本发布通常包含这样一个 Checklist:

    * 代码的下载构建及编译
    * 运行单元测试,生成单元测试报告及覆盖率报告等
    * 在测试环境对当前版本进行测试
    * 为待发布的代码打上版本号
    * 编写 ChangeLog 说明当前版本所涉及的修改
    * 构建 Docker 镜像
    * 将 Docker 镜像推送到镜像仓库
    * 在预发布环境测试当前版本
    * 正式发布到生产环境

    看上去很繁琐对吗,如果每次发布都需要人工去处理上述的所有内容,不仅容易出错,而且也无法应对 DevOps 时代一天至少数次的发布频率,那么下面就来使用 CI 来解决所有问题吧。
    #CI 流程演示
    为了对 CI 流程有最直观的认识,我创建了一个精简版的 GitHub 项目 AlloVince/drone-ci-demo 来演示完整的流程,同时项目对应的 CI 地址是 cloud.drone.io/AlloVince/drone-ci-demo ,项目自动构建的 Docker 镜像会推送到 docker registry 的 allovince/drone-ci-demo。为了方便说明,假设这个项目的核心文件只有 index.html 一个静态页面。
    ##单人开发模式
    目前这个项目背后的 CI 都已经配置部署好,假设我是这个项目的唯一开发人员,如何开发一个新功能并发布新版本呢?

    * Clone 项目到本地, 修改项目代码, 如将 Hello World 改为 Hello World V2。
    * git add .,然后书写符合约定的 Commit 并提交代码, git commit -m "feature: hello world v2”。
    * 推送代码到代码库git push,等待数分钟后,开发人员会看到单元测试结果,GitHub 仓库会产生一次新版本的 Release,Release 内容为当前版本的 ChangeLog, 同时线上已经完成了新功能的发布。

    虽然在开发者看来,一次发布简单到只需 3 个指令,但背后经过了如下的若干次交互,这是一次发布实际产生交互的时序图,具体每个环节如何工作将在后文中详细说明。
    01.png

    ##多人开发模式
    一个项目一般不止一个开发人员,比如我是新加入这个项目的成员,在这个 Demo 中应该如何上线新功能呢?同样非常简单:

    1. Clone 项目到本地,创建一个分支来完成新功能的开发, git checkout -b feature/hello-world-v3。在这个分支修改一些代码,比如将Hello World V2修改为Hello World V3
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world v3”
    3. 将代码推送到代码库的对应分支, git push origin feature/hello-world
    4. 如果功能已经开发完毕,可以向 Master 分支发起一个 Pull Request,并让项目的负责人 Code Review
    5. Review 通过后,项目负责人将分支合并入主干,GitHub 仓库会产生一次新版本的 Release,同时线上已经完成了新功能的发布。

    这个流程相比单人开发来多了 2 个环节,很适用于小团队合作,不仅强制加入了 Code Review 把控代码质量,同时也避免新人的不规范行为对发布带来影响。实际项目中,可以在 GitHub 的设置界面对 Master 分支设置写入保护,这样就从根本上杜绝了误操作的可能。当然如果团队中都是熟手,就无需如此谨慎,每个人都可以负责 PR 的合并,从而进一步提升效率。
    02.png

    ##GitFlow 开发模式
    在更大的项目中,参与的角色更多,一般会有开发、测试、运维几种角色的划分;还会划分出开发环境、测试环境、预发布环境、生产环境等用于代码的验证和测试;同时还会有多个功能会在同一时间并行开发。可想而知 CI 的流程也会进一步复杂。

    能比较好应对这种复杂性的,首选 GitFlow 工作流, 即通过并行两个长期分支的方式规范代码的提交。而如果使用了 GitHub,由于有非常好用的 Pull Request 功能,可以将 GitFlow 进行一定程度的简化,最终有这样的工作流:
    03.png


    * 以 dev 为主开发分支,Master 为发布分支
    * 开发人员始终从 dev 创建自己的分支,如 feature-a
    * feature-a 开发完毕后创建 PR 到 dev 分支,并进行 code review
    * review 后 feature-a 的新功能被合并入 dev,如有多个并行功能亦然
    * 待当前开发周期内所有功能都合并入 dev 后,从 dev 创建 PR 到 master
    * dev 合并入 Master,并创建一个新的 Release

    上述是从 Git 分支角度看代码仓库发生的变化,实际在开发人员视角里,工作流程是怎样的呢。假设我是项目的一名开发人员,今天开始一期新功能的开发:

    1. Clone 项目到本地,git checkout dev。从 dev 创建一个分支来完成新功能的开发, git checkout -b feature/feature-a。在这个分支修改一些代码,比如将Hello World V3修改为Hello World Feature A
    2. git add .,书写符合规范的 Commit 并提交代码, git commit -m "feature: hello world feature A"
    3. 将代码推送到代码库的对应分支, git push origin feature/feature-a:feature/feature-a
    4. 由于分支是以 feature/ 命名的,因此 CI 会运行单元测试,并自动构建一个当前分支的镜像,发布到测试环境,并自动配置一个当前分支的域名如 test-featue-a.avnpc.com
    5. 联系产品及测试同学在测试环境验证并完善新功能
    6. 功能通过验收后发起 PR 到 dev 分支,由 Leader 进行 code review
    7. Code Review 通过后,Leader 合并当前 PR,此时 CI 会运行单元测试,构建镜像,并发布到测试环境
    8. 此时 dev 分支有可能已经积累了若干个功能,可以访问测试环境对应 dev 分支的域名,如 test.avnpc.com,进行集成测试。
    9. 集成测试完成后,由运维同学从 Dev 发起一个 PR 到 Master 分支,此时会 CI 会运行单元测试,构建镜像,并发布到预发布环境
    10. 测试人员在预发布环境下再次验证功能,团队做上线前的其他准备工作
    11. 运维同学合并 PR,CI 将为本次发布的代码及镜像自动打上版本号并书写 ChangeLog,同时发布到生产环境。

    由此就完成了上文中 Checklist 所需的所有工作。虽然描述起来看似冗长,但不难发现实际作为开发人员,并没有任何复杂的操作,流程化的部分全部由 CI 完成,开发人员只需要关注自己的核心任务:按照工作流规范,写好代码,写好 Commit,提交代码即可。

    接下来将介绍这个以 CI 为核心的工作流,是如何一步步搭建的。
    #Step by Step 构建 CI 工作流
    ##Step.0:基于 Kubernetes 部署 Drone v1.0.0
    以 GitHub 为例,截止本文完成时间(2019 年 3 月 28 日), Drone 刚刚发布了第一个正式版本 v1.0.0。官方文档已经提供了分别基于 Docker、Kubernetes 的 Drone 部署说明,不过比较简略,因此这里给出一个相对完整的配置文件。

    首先需要在 GitHub 创建一个 Auth App,用于 repo 的访问授权。应用创建好之后,会得到 Client ID 和 Client Secret 。同时 Authorization callback URL 应填写 Drone 服务对应域名下的 /login,如 https://ci.avnpc.com/login。

    Drone 支持 SQLite、MySQL、Postgres、S3 等多种后端存储,主要用于记录 build logs 等文本信息,这些信息并不是特别重要,且我们的 CI 有可能做迁移,因此个人更推荐使用 SQLite。

    而在 Kubernetes 环境下,SQLite 更适合用挂载 NAS 的方式供节点使用,因此首先将存储的部分独立为文件 drone-pvc.yml,可以根据实际情况配置 nfs.path 和 nfs.server:
    kubectl apply -f drone-pvc.yaml

    Drone 的配置主要涉及两个镜像:

    * drone/kubernetes-secrets 加密数据服务,用于读取 Kubernetes 的 secrets
    * drone/drone:1.0.0-rc.6 就是 Drone 的 server 端,由于在 Kubernetes 下 Drone 利用了 Job 机制,因此不需要部署 Agent。

    这部分配置较长,可以直接参考示例 drone.yaml

    主要涉及到的配置项包括:

    * Drone/kubernetes-secrets 镜像中

    * SECRET_KEY:数据加密传输所用的 key,可以使用 openssl rand -hex 16 生成一个

    * Drone/Drone镜像中

    * DRONE_KUBERNETES_ENABLED:开启 Kubernetes 模式
    * DRONE_KUBERNETES_NAMESPACE:Drone 所使用的 Namespace, 这里使用 default
    * DRONE_GITHUB_SERVER:GitHub 服务器地址,一般为 https://github.com
    * DRONE_GITHUB_CLIENT_ID:上文创建 GitHub Auth App 得到的 Client ID
    * DRONE_GITHUB_CLIENT_SECRET:上文创建 GitHub Auth App 得到的 Client Secret
    * DRONE_SERVER_HOST:Drone 服务所使用的域名
    * DRONE_SERVER_PROTO:http 或 https
    * DRONE_DATABASE_DRIVER:Drone 使用的数据库类型,这里为 SQLite3
    * DRONE_DATABASE_DATASOURCE:这里为 SQLite 数据库的存放路径
    * DRONE_SECRET_SECRET:对应上文的 SECRET_KEY
    * DRONE_SECRET_ENDPOINT:加密数据服务的地址,这里通过 Kubernetes Service 暴露,无需修改

    最后部署即可:
    kubectl apply -f drone.yaml

    部署后首次登录 Drone 就会跳转到 GitHub Auth App 进行授权,授权完毕后可以看到所有能读写的 Repo,选择需要开启 CI 的 Repo,点击 ACTIVATE 即可。 如果开启成功,在 GitHub Repo 的 Settings > Webhooks 下可以看到 Drone 的回调地址。
    ##Step.1:Hello World for Drone
    在正式开始搭建工作流之前,首先可以测试一下 Drone 是否可用。Drone 默认的配置文件是 .drone.yml, 在需要 CI 的 repo 根目录下创建.drone.yml, 内容如下,提交并git push到代码仓库即可触发 Drone 执行 CI。
    kind: pipeline  
    name: deploy

    steps:
    [list]
    [*]name: hello-world[/*]
    [/list] image: docker
    commands:
    - echo "hello world"

    Drone v1 的语法主要参考的 Kubernetes 的语法,非常直观,无需阅读文档也可以知道,我们首先定义了一个管道(Pipeline),管道由若干步骤(step)组成,Drone 的每个步骤是都基于容器实现的,因此 Step 的语法就回到了我们熟悉的 Docker,一个 Step 会拉取 image 定义的镜像,然后运行该镜像,并顺序执行 commands 定义的指令。

    在上例中,Drone 首先 clone git repo 代码到本地,然后根据 .drone.yml 所定义的,拉取 Docker 的官方镜像,然后运行该进行并挂载 git repo 的代码到 /drone/src 目录。

    在 Drone 的界面中,也可以清楚的看到这一过程。
    04.png

    本阶段对应:

    * 代码部分:https://github.com/AlloVince/drone-ci-demo/tree/hello-world
    * Drone 构建记录:https://cloud.drone.io/AlloVince/drone-ci-demo/1
    * Docker 镜像:无

    ##Step.2:单人工作流,自动化单元测试与 Docker 镜像构建
    有了 Hello World 的基础,接下来我们尝试将这个工作流进行扩充。

    为了方便说明,这里假设项目语言为 JavaScript,项目内新增了 test/index.js 文件用于模拟单元测试,一般在 CI 中,只要程序的返回值为 0,即代表运行成功。这个文件中我们仅仅输出一行 Log Unit test passed用于模拟单元测试通过。

    我们希望将代码打包成 Docker 镜像,根目录下增加了 Dockerfile 文件,这里直接使用 Nginx 的官方镜像,构建过程只有 1 行 COPY index.html /usr/share/nginx/html/, 这样镜像运行后可以通过 http 请求看到 index.html 的内容。

    至此我们可以将工作流改进为:

    * 当 Master 分支接收到 push 后,运行单元测试
    * 当 GitHub 发布一次 Release, 构建 Docker 镜像,并推送到镜像仓库

    对应的 Drone 配置文件如下:
    kind: pipeline  
    name: deploy

    steps:
    - name: unit-test
    image: node:10
    commands:
    - node test/index.js
    when:
    branch: master
    event: push
    - name: build-image
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    auto_tag: true
    when:
    event: tag

    虽然比 Hello World 复杂了一些,但是可读性仍然很好,配置文件中出现了几个新概念:

    Step 运行条件, 即 when 部分,上例中展示了当代码分支为 Master,且收到一个 push;以及当代码被标记 tag 这两种情况。Drone 还支持 repo、运行结果等很多其他条件,可以参考 Drone Conditions 文档。

    Plugin 插件,上例中用于构建和推送镜像的是 plugins/docker 这个 Plugin, 一个 Plugin 本质上仍然是一个 Docker 镜像,只是按照 Drone 的规范接受特定的输入,并完成特定的操作。所以完全可以将 Plugin 看做一个无法更改 command 的 Docker 镜像。

    Docker 这个 Plugin 由 Drone 官方提供,用于 Docker 镜像的构建和推送,具体的用法可以查看 Docker 插件的文档。例子中演示的是将镜像推送到私有仓库,如果不做特殊配置,镜像将被推送到 Docker 的官方仓库。

    此外 Docker 插件还有一个很方便的功能,如果设置 auto_tag: true,将根据代码的版本号自动规划 Docker 镜像的标签,如代码版本为1.0.0,将为 Docker 镜像打三个标签 1, 1.0, 1.0.0。如果代码版本号不能被解析,则镜像标签为 latest。

    目前 Drone 的插件已经有很多,可以覆盖主流的云服务商和常见的工作流,并且自己制作插件的成本也不高。

    Secret 加密数据,镜像仓库的用户名和密码都属于敏感信息,因此可以使用 from_secret 获取加密数据。一条加密数据就是一个 key / value 对,如上例中的 DOCKER_PASSWORD 就是我们自己定义的加密数据 key。即便加密数据在 log 中被打印,UI 也只能看到 ***。加密数据的 value 需要提前保存好,保存的方式有 3 种:

    * 通过 Drone UI 界面中, repo -> Settings -> Secrets 添加,所添加的加密数据将保存在 Drone 的数据库中,仅能在当前 repo 中使用。
    * 通过 Drone cli 加密后保存在 .drone.yml 文件中, 使用范围仅限 yaml 文件内
    * 通过 Kubernetes 保存为 Kubernetes Secret,称为 External Secrets,所有的 repo 都可以共享。如果是团队使用的话,这种保存方式显然是最方便的,但也要注意安全问题,因此 External Secrets 还支持 repo 级别的权限管理, 可以只让有当前 repo 写入权限的人才能使用对应 secret。

    这个阶段对应:

    * 代码仓库:https://github.com/AlloVince/drone-ci-demo/tree/single-person
    * push 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/4
    * Release 时触发的 Drone CI:https://cloud.drone.io/AlloVince/drone-ci-demo/5
    * Release 后 CI 构建的 Docker 镜像:allovince/drone-ci-demo:latest

    ##Step.3:GitFlow 多分支团队工作流
    上面的工作流已经基本可以应付单人的开发了,而在团队开发时,这个工作流还需要一些扩展。不需要引入 Drone 的新功能,只需要在上文基础上根据分支做一点调整即可。

    首先保证单元测试位于 steps 的第一位,并且限定团队工作的分支,在 push 和 pull_request 时,都能触发单元测试。

     - name: unit-test  
    image: node:10
    commands:
    - node test/index.js
    when:
    branch:
    include:
    - feature/*
    - master
    - dev
    event:
    include:
    - push
    - pull_request

    然后根据 GitFlow 的流程对于不同的分支构建 Docker 镜像并打上特定标签,以 feature 分支为例,下面的配置约定了当分支名满足 feature/*,并收到 push 时,会构建 Docker 镜像并打标签,标签名称为当前分支名去掉 feature/。如分支 feature/readme, 对应 Docker 镜像为 allovince/drone-ci-demo:readme,考虑到 feature 分支一般都出于开发阶段,因此新的镜像会覆盖旧的。配置如下:
    - name: build-branch-image  
    image: plugins/docker
    settings:
    repo: allovince/drone-ci-demo
    username: allovince
    password:
    from_secret: DOCKER_PASSWORD
    tag:
    - ${DRONE_BRANCH[size=16]feature/} [/size]
    when:
    branch: feature/*
    event: push

    镜像的 Tag 处不再使用自动方式,其中 DRONE_BRANCH 是 Drone 的内置环境变量(Environment),对应当前的分支名。feature/ 是执行了一个字符串的替换操作(Substitution)。更多的环境变量和字符串操作都可以在文档中找到。

    以此类推,可以查看这个阶段的完整 .drone.yml ,此时我们的工作流示例如下:

    * 团队成员从 dev 分支 checkout 自己的分支 feature/readme
    * 向feature/readme提交代码并 push, CI 运行单元测试,构建镜像allovince/drone-ci-demo:readme
    * 功能开发完成后,团队成员向 dev 分支 发起 pull request , CI 运行单元测试
    * 团队其他成员 merge pull request, CI 运行单元测试,构建镜像allovince/drone-ci-demo:test
    * 运维人员从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * 运维人员 merge pull request, 并 Release 新版本 pre-0.0.2, CI 构建镜像allovince/drone-ci-demo:pre-0.0.2

    可能细心的同学会发现 dev -> Master 的 pull request 时,构建镜像失败了,这是由于 Drone 出于安全考虑限制了在 pull request 时默认无法读取加密数据,因此无法得到 Docker Registry 密码。如果是私有部署的话,可以在 Repo Settings 中勾选 Allow Pull Requests,此处就可以构建成功。
    ##Step.4:语义化发布
    上面基本完成了一个支持团队协作的半自动 CI 工作流,如果不是特别苛刻的话,完全可以用上面的工作流开始干活了。

    不过基于这个工作流工作一段时间,会发现仍然存在痛点,那就是每次发布都要想一个版本号,写 ChangeLog,并且人工去 Release。

    标记版本号涉及到上线后的回滚,追溯等一系列问题,应该是一项严肃的工作,其实如何标记早已有比较好的方案,即语义化版本。在这个方案中,版本号一共有 3 位,形如 1.0.0,分别代表:

    1. 主版本号:当你做了不兼容的 API 修改,
    2. 次版本号:当你做了向下兼容的功能性新增,
    3. 修订号:当你做了向下兼容的问题修正。

    虽然有了这个指导意见,但并没有很方便的解决实际问题,每次发布要搞清楚代码的修改到底是不是向下兼容的,有哪些新的功能等,仍然要花费很多时间。

    而语义化发布(Semantic Release)就能很好的解决这些问题。

    语义化发布的原理很简单,就是让每一次 Commit 所附带的 Message 格式遵守一定规范,保证每次提交格式一致且都是可以被解析的,那么进行 Release 时,只要统计一下距离上次 Release 所有的提交,就分析出本次提交做了何种程度的改动,并可以自动生成版本号、自动生成 ChangeLog 等。

    语义化发布中,Commit 所遵守的规范称为约定式提交(Conventional Commits)。比如 Node.js、 Angular、Electron 等知名项目都在使用这套规范。

    语义化发布首先将 Commit 进行分类,常用的分类(Type)有:

    * feat:新功能
    * fix:BUG 修复
    * docs:文档变更
    * style:文字格式修改
    * refactor:代码重构
    * perf:性能改进
    * test:测试代码
    * chore:工具自动生成

    每个 Commit 可以对应一个作用域(Scope),在一个项目中作用域一般可以指不同的模块。

    当 Commit 内容较多时,可以追加正文和脚注,如果正文起始为BREAKING CHANGE,代表这是一个破坏性变更。

    以下都是符合规范的 Commit:
    feat:增加重置密码功能

    fix(邮件模块):修复邮件发送延迟BUG

    feat(API):API重构

    BREAKING CHANGE:API v3上线,API v1停止支持

    有了这些规范的 Commit,版本号如何变化就很容易确定了,目前语义化发布默认的规则如下:
    Commit	版本号变更
    BREAKING CHANGE 主版本号
    feat 次版本号
    fix / perf 修订号

    因此在 CI 部署 semantic-release 之后,作为开发人员只需要按照规范书写 Commit 即可,其他的都由 CI 完成。

    具体如何将语义化发布加入 CI 流程中呢, semantic-release 是 js 实现的,如果是 js 的项目,可以直接在 package.json 中增加配置项,而对于任意语言的项目,推荐像 Demo 中一样,在根目录下增加 配置文件release.config.js。这个配置目的是为了禁用默认开启的 npm 发布机制,可以直接套用。

    semantic-release 要执行 GitHub Release,因此我们需要在 CI 中配置自己的 Personal access tokens 让 CI 有 GitHub Repo 的读写权限, 可以通过 GitHub 点击自己头像 -> Settings -> Developer settings -> Personal access tokens -> Generate new token 生成一个 Token。 然后在 Drone 的 repo 设置界面新增一个 Secret, key 为 GITHUB_TOKEN, value 填入刚生成的 Token。

    最后在 .drone.yml 中增加这样一段就可以了。
    - name: semantic-release  
    image: gtramontina/semantic-release:15.13.3
    environment:
    GITHUB_TOKEN:
    from_secret: GITHUB_TOKEN
    entrypoint:
    - semantic-release
    when:
    branch: master
    event: push

    来再次模拟一下流程,feature 分支部分与上文相同:

    * 从 dev 向 master 发起 pull request,CI 运行单元测试,并构建镜像allovince/drone-ci-demo:latest
    * merge pull request,CI 会执行单元测试并运行 semantic-release , 运行成功的话能看到 GitHub 新增 Release v1.0.0
    * GitHub Release 再次触发CI 构建生产环境用 Docker 镜像allovince/drone-ci-demo:1.0.0

    最终我们能得到这样一个赏心悦目的 Release。
    05.png

    ##Step.5:Kubernetes 自动发布
    Docker 镜像推送到仓库后,我们还剩最后一步就可以完成全自动发布的闭环,即通知 Kubernetes 将镜像发布到生产环境。这一步实现比较灵活,因为很多云服务商在容器服务都会提供 Trigger 机制,一般是提供一个 URL,只要请求这个 URL 就可以触发容器服务的发布。Demo 中我们使用更为通用的方法,就是将 kubectl 打包为容器,以客户端调用 Kubernetes 集群 Master 节点 API(kube-apiserver)的形式完成发布。

    假设我们在生产环境下 drone-ci-demo 项目的 Kubernetes 发布文件如下:
    ---  
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: ci-demo-deployment
    namespace: default
    spec:
    replicas: 1
    template:
    spec:
    containers:
    - image: allovince/drone-ci-demo
    name: ci-demo
    restartPolicy: Always

    对应 .drone.yml 中增加 step 如下。这里使用的插件是 honestbee/drone-kubernetes, 插件中 kubectl 连接 API 使用的是证书 + Token 的方式鉴权,因此需要先获得证书及 Token, 已经授权的 Token 保存于 Kubernetes Secret,可以通过 kubectl get secret [ your default secret name ] -o yaml | egrep 'ca.crt:|token:' 获得并配置到 Drone 中,注意插件要求 Token 是明文的,需要 base64 解码一下:echo [ your token ] | base64 -d && echo ''。
    - name: k8s-deploy  
    image: quay.io/honestbee/drone-kubernetes
    settings:
    kubernetes_server:
    from_secret: KUBERNETES_SERVER
    kubernetes_cert:
    from_secret: KUBERNETES_CERT
    kubernetes_token:
    from_secret: KUBERNETES_TOKEN
    namespace: default
    deployment: ci-demo-deployment
    repo: allovince/drone-ci-demo
    container: ci-demo
    tag:
    - ${DRONE_TAG}
    when:
    event: tag

    在示例中,可以看到在语义化发布之后 CI 会将新版本的 Docker 镜像自动发布到 Kubernetes,这里为了演示仅打印了指令并未实际运行。相当于运行了如下的指令:
    kubectl -n default set image deployment/ci-demo-deployment ci-demo=allovince/drone-ci-demo:v1.0.2

    由于自动发布的环节势必要接触到生产服务器,需要格外注意安全问题,首推的方式当然是将 CI 和 Kubernetes 集群放于同一内网中,同时可以使用 Kubernetes 的 RBAC 权限控制,为自动发布单独创建一个用户,并删除不必要的权限。
    #后话
    总结一下,本文展示了从 Hello World 到单人单分支手动发布再到团队多分支 GitFlow 工作流再到团队多分支 semantic-release 语义化发布最后到 通知 Kubernetes 全自动发布,如何从零开始一步一步搭建 CI 将团队开发、测试、发布的流程全部自动化的过程,最终能让开发人员只需要认真提交代码就可以完成日常的所有 DevOps 工作。

    最终 Step 的完成品可以适配之前的所有 Step,如果不太在意实现细节的话,可以在此基础上稍作修改,直接使用。

    然而写好每一个 Commit 这个看似简单的要求,其实对于大多数团队来说并不容易做到,在实施过程中,经常会遇到团队成员不理解为什么要重视 Commit 规范,每个 Commit 都要深思熟虑是否过于吹毛求疵等等疑问。

    以 Commit 作为 CI 的核心,个人认为主要会带来以下几方面的影响:

    1. 一个好的 Commit,代表着开发人员对当前改动之于整个系统的影响,有非常清楚的认识,代码的修改到底算 feat 还是 fix ,什么时候用 BREAKING CHANGE 等都是要仔细斟酌的,每个 Commit 都会在 ChangeLog 里“留底”,从而约束团队不随意提交未经思考的代码,提高代码质量。
    2. 一个好的 Commit 也代表开发人员有能力对所实现功能进行精细的划分,一个分支做的事情不宜过多,一个提交也应该专注于只解决一个问题,每次提交(至少是每次 push)都应该保持系统可构建、可运行、可测试,如果能坚持做到这些,对于合并代码时的冲突解决,以及集成测试都有很大帮助。
    3. 由于每次发布能清楚的看到所有关联的 Commit 以及 Commit 的重要程度,那么线上事故的回滚也会非常轻松,回滚到哪个版本,回滚后哪些功能会受到影响,只要看 CI 自动生成的 Release 记录就一目了然。如果没有这些,回滚误伤到预期外的功能从而引发连锁反应的惨痛教训,可能很多运维都有过类似经历吧。

    因此 CI 自动化其实是锦上添花而非雪中送炭,如果团队原本就无视规范,Commit 全是空白或者没有任何意义的单词,分支管理混乱,发布困难,奢望引入一套自动化 CI 来能解决所有这些问题,无疑是不现实的。而只有原本就重视代码质量,有一定规范意识,再通过自动化 CI 来监督约束,团队在 CI 的帮助下代码质量提高,从而有机会进一步改进 CI 的效率,才能形成良心循环。

    愿天下不再有难发布的版本。
    #Q&A
    Q:Kubernetes 上主流的 CI/CD 方案是啥?
    A:其实这无关 Kubernetes,从市场占有率来看,前三名分别是

    1. Jenkins
    2. JetBrains TeamCity
    3. CircleCI

    来源:https://www.datanyze.com/market-share/ci

    Q:GitLab 自带的 CI 与 Jenkins 和 GitLab 结合的 CI,该如何选择?想知道更深层次的理解。
    A:还是要结合自己团队的实际情况做选择。从成熟度来说,肯定是 Jenkins 用户最多,成熟度最高,缺点是侧重 Java,配置相对繁琐。GitLab 自带的 CI 相对简单,可以用 yaml,和 GitLab 结合的最好,但功能肯定没有 Jenkins 全面。如果是小团队新项目,GitLab CI 又已经可以满足需求的话,并不需要上 Jenkins,如果是较大的团队,又是偏 Java 的,个人更偏向 Jenkins。

    Q:Jenkins 如果不想运行在 Kubernetes 里面,该怎么和 Kubernetes 集成?
    A:从 CI 的流程来说,CI 应用是不是跑在 Kubernetes 的并不重要,CI 只要能访问代码库,有权限在生产环境发布,是不是跑在容器里从效果来说其实没有区别,只是用 Kubernetes 部署 Jenkins 的话,运维的一致性比较好,运维团队不用额外花时间维护一套物理机的部署方案。

    Q:Kubernetes 的回滚方案是回滚代码,重做镜像,还是先切流量,后做修改?
    A:代码一定是打包到镜像里的,镜像的版本就是代码的版本,所以一定是切镜像。至于回滚操作本身,Kubernetes 已经内置了很多滚动发布(Rolling update)的策略,无论是发新版本还是回滚版本,都可以做到用户无感知。

    Q:镜像大到几 G 的话如何更新部署,有什么好的实践呢,以及如何回滚?
    A:几个要点:

    1. 镜像仓库部署在内网,镜像推拉走内网,几个 G 的镜像传输起来也只是秒级别的
    2. 镜像构建时将不经常变动的部分划分到 1 层,因为 Docker 镜像是分层的,这样每次拉镜像可以利用到 Docker 的缓存传输的只有镜像发生变化的部分
    3. 选择 alpine 这样尽量小的镜像

    回滚如果发生在相邻的版本,一般物理机上是有缓存的,速度都很快。

    Q:Drone 开放 API 服务吗?这样方便其他系统集成。
    A:可以调整一下思路,直接把需要的功能做成镜像在 Drone 里调用就好了。

    Q:如果有 Drone 的 Server怎么做高可用?
    A:Drone serve r用 Kubernetes 部署的话本身只起到了一个任务调度的作用,很难会遇到性能瓶颈。真的有性能问题可以尝试水平扩展 Drone server,共享同一数据库。

    作者博客:https://avnpc.com/pages/drone-gitflow-kubernetes-for-cloud-native-ci

    以上内容根据2019年4月2日晚微信群分享内容整理。分享人徐谦,高级架构师,10 年以上研发经验,2 次创业经历。现从事互联网金融相关研发,关注 Node.js、容器技术、机器学习等。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零五):基于OVN的Kubernetes网络架构解析

    灵雀云 发表了文章 • 0 个评论 • 872 次浏览 • 2019-03-28 17:42 • 来自相关话题

    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络 ...查看全部
    【编者的话】Kubernetes经过了几年的发展,存在着很多的网络方案。然而网络虚拟化在Kubernetes出现前就一直在发展,其中基于OpenVswitch的方案在OpenStack中已经有了很成熟的方案。其中OVN作为OVS的控制器提供了构建分布式虚拟网络的完整控制平面,并已经成为了最新的OpenStack网络标准。我们将OVN的网络架构和Kubernetes的容器平台进行结合,将业界成熟的网络架构引入Kubernetes大幅增强现有容器网络的能力。
    #Kubernetes网络的局限性
    Kubernetes提出了很多网络概念,很多开源项目都有自己的实现。然而由于各个网络功能都是在不同的项目中实现,功能和性能也各有千秋,缺乏统一的解决方案,在使用过程中经常会陷入到底该用哪个的抉择中。同时CNI、DNS、Service的实现又在不同的项目,一旦网络出现问题,排查也会在多个组件间游走,是一个十分痛苦的过程。

    尽管Kubernetes提出了很多网络的概念,但是在真实应用中很多人会有这样的感觉:网络这块还是很薄弱,很多功能缺乏,方案也不够灵活。尤其是和搞传统基础设施网络的人沟通会发现,在他们眼里,Kubernetes的网络还很初级。我们熟悉的Kubernetes网络是CNI、Service、DNS、Ingress、Network Policy这样的模式。而做IaaS的视角完全不同,他们每次提起是VPC、Subnet、VNIC、 Floating IP,在此之上有DHCP,路由控制,安全组,QoS,负载均衡,域名解析这样的基础网络功能。

    从IaaS的视角来看,Kubernetes的网络功能确实比较单薄。经常碰到来自传统网络部门的挑战,诸如子网划分VLAN隔离,集群内外网络打通,容器NAT设置,带宽动态调节等等。现有的开源网络方案很难完美支持,最简单的一个例子,比如提及容器的固定IP功能通常就要上升到意识形态斗争的层面去讨论。这本质上还是Kubernetes的网络功能不足,模型也不够灵活导致的。从更高层面来说,Kubernetes中抽象类一层网络虚拟化的内容,然而网络虚拟化或者SDN并不是Kubernetes带来的新东西,相关技术已经发展很久。尤其是在IaaS领域里已经有着比较成熟且完善的一整套网络方案。

    传统网络部门的人都会问,为什么不用OVS来做网络方案,很多需求用只要容器网络接入OVS网络,剩下事情网络部门自己就知道怎么去做了,都不用我们实现太多额外的功能。也有很多人向我们推荐了OVN,用这个能很方便地实现这些功能。也正由此我们开始去关注OVS/OVN这种之前主要应用于OpenStack生态系统的网络工具。下面我就来介绍一下OVS和OVN。
    #OVS和OVN网络方案的能力
    网络的概念比较晦涩一些,但是好在大家都对Docker和Kubernetes比较熟悉,可以做个类比。如果说Docker是对单机计算资源的虚拟化,那么OVS就是对单机网络进行虚拟化的一个工具。它最基本的功能是实现了虚拟交换机,可以把虚拟网卡和虚拟交换机的端口连接,这样一个交换机下的多个网卡网络就打通了,类似Linux Bridge的功能。在此之上,OVS很重要的一点就是支持OpenFlow,这是一种可编程的流量控制语言,可以方便我们以编程的方式对流量进行控制,例如转发,拒绝,更改包信息,NAT,QoS 等等。此外OVS还支持多中网络流量监控的协议,方便我们可视化监控并跟踪整个虚拟网络的流量情况。

    但是,OVS只是一个单机软件,它并没有集群的信息,自己无法了解整个集群的虚拟网络状况,也就无法只通过自己来构建集群规模的虚拟网络。这就好比是单机的Docker,而OVN就相当于是OVS的Kubernetes,它提供了一个集中式的OVS控制器。这样可以从集群角度对整个网络设施进行编排。同时OVN也是新版OpenStack中Neutron的后端实现,基本可以认为未来的OpenStack网络都是通过OVN来进行控制的。
    01.jpeg

    上图是一个OVN的架构,从下往上看:

    ovs-vswitchd和ovsdb-server可以理解为单机的Docker负责单机虚拟网络的真实操作。

    ovn-controller类似于kubelet,负责和中心控制节点通信获取整个集群的网络信息,并更新本机的流量规则。

    Southbound DB类似于etcd(不太准确),存储集群视角下的逻辑规则。

    Northbound DB类似apiserver,提供了一组高层次的网络抽象,这样在真正创建网络资源时无需关心负责的逻辑规则,只需要通过Northoboud DB的接口创建对应实体即可。

    CMS可以理解为OpenStacke或者Kubernetes这样的云平台,而 CMS Plugin是云平台和OVN对接的部分。

    下面我们具体介绍一下OVN提供的网络抽象,这样大家就会有比较清晰的认知了。

    Logical_Switch最基础的分布式虚拟交换机,这样可以将多台机器上的容器组织在一个二层网络下,看上去就好像所有容器接在一台交换机上。之后可以在上面增加诸如ACL、LB、QoS、DNS、VLAN等等二层功能。

    Logical_Router虚拟路由器,提供了交换机之间的路由,虚拟网络和外部网络连接,之后可以在路由器层面增加DHCP、NAT、Gateway等路由相关的功能。

    Loadbalancer,L2和L3的Loadbalancer,可以类比公有云上的内部LB和外部LB的功能。

    ACL基于L2到L4的所有控制信息进行管控的一组DSL,可以十分灵活,例如:outport == “port1” && ip4 && tcp && tcp.src >= 10000 && tcp.dst <= 1000。

    QoS,可以基于和ACL同样的DSL进行带宽的控制。

    NAT,同时提供DNAT和SNAT的控制方便内外网络通信。

    DNS,内置的分布式DNS,可以在本机直接返回内部DNS的请求。

    Gateway控制内部和外部之间的集中式通信。

    了解了这些,大家应该就能发现,Kubernetes目前的网络从功能层面其实只是OVN支持的一个子集,基本上所有Kubernetes的网络需求都能在OVN中找到映射关系,我们简单来看下他们之间的映射。
    #OVN和Kubernetes的结合
    Switch/Router -> Kubernetes基本的东西向互通容器网络。这块OVN的能力其实是大大超出,毕竟OVN的这套模型是针对多租户进行设计的,而Kubernetes现在只需要一个简单的二层网络。

    Loadbalancer → ClusterIP以及Loadbalancer类型的Service,可以取代kube-proxy的功能,OVN本身也可以实现云上ELB的功能。

    ACL -> Networkpolicy本质上更灵活因为可以从L2控制到L4并且DSL也支持更多的语法规则。

    DNS -> 可以取代Kube-DNS/CoreDNS,同时由于OVN实现的是分布式DNS,整体的健壮性会比现在的Kubernetes方案要好。

    可以看到Kubernetes的CNI、kube-proxy、Kube-DNS、NetworkPolicy、Service等等概念OVN都有对应的方案,而且在功能或者稳定性上还有增强。更不要说还有QoS、NAT、Gateway等等现在Kubernetes中没有的概念。可以看到如果能把IaaS领域的网络能力往Kubernetes平移,我们还有很多的提升空间。
    #Kubernetes网络未来增强的方向
    最后来说说我认为的未来Kubernetes网络可能的增强方向。
    ##Kubernetes网络功能和IaaS网络功能打平
    现在的Kubernetes网络模型很类似之前公有云上的经典网络,所有用户大二层打通,通过安全策略控制访问。我们现在也都知道公有云多租户不能这么做VPC肯定是标配。因此未来Kubernetes网络可能也会向着多租户方向前进,在VPC的基础上有更多的路由控制、NAT控制、带宽控制、浮动IP等等现在IaaS上很常见的功能。
    ##性能
    现有的开源方案其实主要还是依赖原有的Linux网络栈,没有针对性的优化。理论上容器的密度比传统虚拟化高,网络压力会更大。OVS现在有DPDK等Kernel bypass的DataPath,绕过Linux内核栈来实现低延迟和大吞吐网络。未来随着容器的密度越来越大,我认为会出现这种针对容器架构专门优化的网络方案,而不是依旧依赖Linux网络栈。
    ##监控和问题排查
    现有的网络问题排查十分困难,如果所有的数据平面都由一个项目完成,比如OVN,那么学习成本和排障都会容易一些。此外OVS社区已经有了很多成熟的监控,追踪,排障方案,随着容器的使用场景变多,我认为外围的工具也需要能够很好的支撑这种模式的网络运维问题。
    #Q&A
    Q:OVS方案与基于三层交换机方案对比,各有什么优缺点?

    A:OVS最大的优点在于可编程,灵活性比较好。虚拟网络不用手动插网线,而且有OpenFlow加持,可以实现一些普通交换机无法实现的流量控制。物理交换机的主要有点就是性能好,而且比较稳定,不容易出问题。



    Q:OVN不支持ECMP,貌似现在还是active-standby机制,你们怎么解决Gateway瓶颈问题?

    A:有几种方式:1. Gateway用DPDK这样的高速DataPath;2. 多Gateway,用策略路不同的IP段走不同Gateway,可以分担流量;3. 不使用OVN概念的Gateway,自己做一些手脚从每台宿主机直接出去,也是分担流量降低单点的风险。



    Q:OVN-Kubernetes好像只支持每个部署节点一个虚拟网络网段。如何避免IP池浪费和不均衡?

    A:这个其实是这个项目实现的网络模型的一个局限性。在我们的实现里是以namespace为粒度划分子网,可以对每个namespace进行控制,情况会好很多。



    Q:OVN如果有不同的Chassis,但是相同IP就会造成网络异常(比如物理机,VM装上OVN,注册到远端后,被重建,后面又注册到远端,但是Chassis已经改变),会导致大量节点Geneve网络异常。你们怎么解决这个问题?

    A:暂时没碰到这个问题,但是我们在实现的一个原则就是尽可能保证所有的操作都是幂等的。向这种可能需要在重连前做一个检查,判断是否有过期的数据需要清理,再连接,或者复用旧的连接信息去连接。



    Q:如何debug OVN逻辑拓扑是否配置有问题?

    A:目前debug确实很多情况只能靠眼看,也可以使用ovn-trace这个工具可以打印数据包在逻辑流里的链路来排查。



    Q:OVS跟Calico等有啥区别?

    A:Calico主要依赖Linux的路由功能做网络打通,OVS是在软件交换机层面实现网络打通,并提供了更丰富的网络功能。



    Q:OVS的封包支持有STT和Geneve,你们选用哪种,为什么?

    A:其实还支持VXLAN,我们选的Geneve。原因比较简单,Geneve是第一个OVN支持的封包协议,而且看了一些评测,据说在搞内核开启UDP Checksum的情况下性能会比VXLAN要好一些。



    Q:OVS如何实现固定容器IP?

    A:这个其实OVS有对应的设置可以给每个端口设定IP和MACE,这样网卡启动时配置相同的信息就可以了,难点其实是如何控制OVN来分配 IP,感觉这个话题可以再开一场分享来讨论了。



    Q:可以简单介绍下你们准备开源的网络功能吗?

    A:每个namespace和一个logical_switch绑定,支持子网分配,支持固定 IP,支持 QoS,支持NetworkPolicy,内置的LB,内置的DNS,大致就是把OVN的概念映射到Kubernetes。



    Q:想了解一下,如果采用OVN,是不是意味着使用OpenStack平台和Kubernetes网络可以直接互通?完成业务在虚拟机和Pod之间的全新负载方式?

    A:是这样的,如果涉及的合理可以做到容器和VM使用同一个底层网络基础设施,VM和容器之间可以IP直达,所有的ACL、LB都是打通的。



    Q:直接把OpenShift OVS抽出来做Kubernetes的网络插件和灵雀云做的这个区别在哪?

    A:功能上有很多是相同的,因为底层都是OVS。如果关注Roadmap会发现OpenShift之后也会采用OVS的模式。从架构的角度来看,现在openshift-multitenant的实现很类似Neutron之前那一套,各种Agent,之后会统一到OVN。另一方面OpenShift的网络插件绑定的太死了,所以我们决定还是自己抽出来,顺便能实现我们的一些特殊功能,比如固定IP,子网共享,以及一些网关控制层面的功能。



    Q:请问Geneve和VXLAN的区别有哪些?

    A:Geneve可以理解为下一代VXLAN,VXLAN相对VLAN来说头部更长可以支持更多的VLAN,但是由于是固定长度的封装头,不能任意加控制信息。Geneve采用变长的封装头,可以加很多自定义的控制信息,方便之后做更复杂的网络管控。



    Q:Docker CNM也支持固定IP,和你说的固定IP是一回事吗?另外,基于OVS建立的网络是CNI还是CNM的呢?

    A:基于CNI,因为我们依赖Kubernetes的模型。不过话说回来我很喜欢Docker CNM那套模型,比CNI要实用很多。固定IP其实只是个功能,各种模型下都可以实现,效果就是可以给Pod指定IP启动,Workload下的多个Pod实用的是一组固定的地址。



    Q:目前你们对企业的解决方案里会默认采用这种网络模式么?

    A:这个方案是我们这几年需求和碰到坑的一个积累吧,现在还不会直接给企业用,我们还需要一些功能的开发和测试,但是之后Overlay的场景这个肯定是主推了,主要是取代原来的Flannel VXLAN网络。



    Q:你了解Contiv网络方案吗,和贵公司的实现有哪些区别?

    A:Contiv是思科做的,也是OVS实现的,不过它的实现比较早,自己手动实现了整个控制平面,可以认为自己写了个跟OVN类似的东西来控制 OVS。不过看它最近已经很少更新了。用OVN能用很少的代码就实现基本相同的功能。Contiv有个比较独特的功能就是支持BGP的网络间通信,这个是OVN暂时不支持的。



    以上内容根据2019年3月26日晚微信群分享内容整理。分享人刘梦馨,灵雀云高级工程师。2014年加入灵雀云容器团队,长期参与容器调度平台和容器网络架构的产品研发和技术架构演进,参与自研容器网络和容器应用网关。目前主要专注于容器网络功能的拓展和架构优化。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零四):小团队微服务落地实践

    齐达内 发表了文章 • 0 个评论 • 1100 次浏览 • 2019-03-21 10:14 • 来自相关话题

    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将Dev ...查看全部
    【编者的话】微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过1年多的探索和实践,本着将DevOps落实到产品中的愿景,一步步建设出适合我们的微服务平台。
    # 要不要微服务
    我们的产品是Linkflow,企业运营人员使用的客户数据平台(CDP)。产品的一个重要部分类似企业版的“捷径",让运营人员可以像搭乐高积木一样创建企业的自动化流程,无需编程即可让数据流动起来。从这一点上,我们的业务特点就是聚少成多,把一个个服务连接起来就成了数据的海洋。理念上跟微服务一致,一个个独立的小服务最终实现大功能。当然我们一开始也没有使用微服务,当业务还未成型就开始考虑架构,那么就是“过度设计"。另一方面需要考虑的因素就是“人",有没有经历过微服务项目的人,团队是否有DevOps文化等等,综合考量是否需要微服务化。

    微服务的好处是什么?

    • 相比于单体应用,每个服务的复杂度会下降,特别是数据层面(数据表关系)更清晰,不会一个应用上百张表,新员工上手快。
    • 对于稳定的核心业务可以单独成为一个服务,降低该服务的发布频率,也减少测试人员压力。
    • 可以将不同密集型的服务搭配着放到物理机上,或者单独对某个服务进行扩容,实现硬件资源的充分利用。
    • 部署灵活,在私有化项目中,如果客户有不需要的业务,那么对应的微服务就不需要部署,节省硬件成本,就像上文提到的乐高积木理念。
    微服务有什么挑战?
    • 一旦设计不合理,交叉调用,相互依赖频繁,就会出现牵一发动全身的局面。想象单个应用内Service层依赖复杂的场面就明白了。
    • 项目多了,轮子需求也会变多,需要有人专注公共代码的开发。
    • 开发过程的质量需要通过持续集成(CI)严格把控,提高自动化测试的比例,因为往往一个接口改动会涉及多个项目,光靠人工测试很难覆盖所有情况。
    • 发布过程会变得复杂,因为微服务要发挥全部能力需要容器化的加持,容器编排就是最大的挑战。
    • 线上运维,当系统出现问题需要快速定位到某个机器节点或具体服务,监控和链路日志分析都必不可少。
    下面详细说说我们是怎么应对这些挑战的。# 开发过程的挑战## 持续集成通过CI将开发过程规范化,串联自动化测试和人工Review。我们使用Gerrit作为代码&分支管理工具,在流程管理上遵循GitLab的工作流模型。
    • 开发人员提交代码至Gerrit的magic分支
    • 代码Review人员Review代码并给出评分
    • 对应Repo的Jenkins job监听分支上的变动,触发Build job。经过IT和Sonar的静态代码检查给出评分
    • Review和Verify皆通过之后,相应Repo的负责人将代码merge到真实分支上
    • 若有一项不通过,代码修改后重复过程
    • Gerrit将代码实时同步备份至的两个远程仓库中
    1.png
    ## 集成测试一般来说代码自动执行的都是单元测试(Unit Test),即不依赖任何资源(数据库,消息队列)和其他服务,只测试本系统的代码逻辑。但这种测试需要mock的部分非常多,一是写起来复杂,二是代码重构起来跟着改的测试用例也非常多,显得不够敏捷。而且一旦要求开发团队要达到某个覆盖率,就会出现很多造假的情况。所以我们选择主要针对API进行测试,即针对controller层的测试。另外对于一些公共组件如分布式锁,json序列化模块也会有对应的测试代码覆盖。测试代码在运行时会采用一个随机端口拉起项目,并通过http client对本地API发起请求,测试只会对外部服务做mock,数据库的读写,消息队列的消费等都是真实操作,相当于把Jmeter的事情在Java层面完成一部分。Spring Boot项目可以很容易的启动这样一个测试环境,代码如下:
    @RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    测试过程的http client推荐使用`io.rest-assured:rest-assured`支持JsonPath,十分好用。测试时需要注意的一个点是测试数据的构造和清理。构造又分为schema的创建和测试数据的创建。
    • schema由flyway处理,在启用测试环境前先删除所有表,再进行表的创建。
    • 测试数据可以通过`@Sql`读取一个SQL文件进行创建,在一个用例结束后再清除这些数据。
    顺带说一下,基于flyway的schema upgrade功能我们封成了独立的项目,每个微服务都有自己的upgrade项目,好处一是支持command-line模式,可以细粒度的控制升级版本,二是也可以支持分库分表以后的schema操作。upgrade项目也会被制作成docker image提交到docker hub。测试在每次提交代码后都会执行,Jenkins监听Gerrit的提交,通过`docker run -rm {upgrade项目的image}`先执行一次schema upgrade,然后`gradle test`执行测试。最终会生成测试报告和覆盖率报告,覆盖率报告采用JaCoCo的Gradle插件生成。如图:
    2.png
    3.png
    这里多提一点,除了集成测试,服务之间的接口要保证兼容,实际上还需要一种consumer-driven testing tool,就是说接口消费端先写接口测试用例,然后发布到一个公共区域,接口提供方发布接口时也会执行这个公共区域的用例,一旦测试失败,表示接口出现了不兼容的情况。比较推荐大家使用Pact或是Spring Cloud Contact。我们目前的契约基于“人的信任”,毕竟服务端开发者还不多,所以没有必要使用这样一套工具。集成测试的同时还会进行静态代码检查,我们用的是sonar,当所有检查通过后Jenkins会+1分,再由reviewer进行代码review。## 自动化测试单独拿自动化测试出来说,就是因为它是质量保证的非常重要的一环,上文能在CI中执行的测试都是针对单个微服务的,那么当所有服务(包括前端页面)都在一起工作的时候是否会出现问题,就需要一个更接近线上的环境来进行测试了。在自动化测试环节,我们结合Docker提高一定的工作效率并提高测试运行时环境的一致性以及可移植性。在准备好基础的Pyhton镜像以及Webdriver(selenium)之后,我们的自动化测试工作主要由以下主要步骤组成:
    • 测试人员在本地调试测试代码并提交至Gerrit
    • Jenkins进行测试运行时环境的镜像制作,主要将引用的各种组件和库打包进一个Python的基础镜像
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的自动化测试环境更新,然后拉取自动化测试代码启动一次性的自动化测试运行时环境的Docker容器,将代码和测试报告的路径镜像至容器内
    • 自动化测试过程将在容器内进行
    • 测试完成之后,不必手动清理产生的各种多余内容,直接在Jenkins上查看发布出来的测试结果与趋势
    4.png
    5.png
    关于部分性能测试的执行,我们同样也将其集成到Jenkins中,在可以直观的通过一些结果数值来观察版本性能变化情况的回归测试和基础场景,将会很大程度的提高效率,便捷的观察趋势。
    • 测试人员在本地调试测试代码并提交至Gerrit
    • 通过Jenkins定时或手动触发,调用环境部署的Job将专用的性能测试环境更新以及可能的Mock Server更新
    • 拉取最新的性能测试代码,通过Jenkins的性能测试插件来调用测试脚本
    • 测试完成之后,直接在Jenkins上查看通过插件发布出来的测试结果与趋势

    6.png

    7.png

    # 发布过程的挑战
    上面提到微服务一定需要结合容器化才能发挥全部优势,容器化就意味着线上有一套容器编排平台。我们目前采用是Redhat的OpenShift。所以发布过程较原来只是启动jar包相比要复杂的多,需要结合容器编排平台的特点找到合适的方法。
    ## 镜像准备
    公司开发基于GitLab的工作流程,Git分支为master,pre-production和prodution三个分支,同时生产版本发布都打上对应的tag。每个项目代码里面都包含dockerfile与jenkinsfile,通过Jenkins的多分支Pipeline来打包Docker镜像并推送到Harbor私库上。
    8.jpg

    Docker镜像的命令方式为 `项目名/分支名:git_commit_id`,如 `funnel/production:4ee0b052fd8bd3c4f253b5c2777657424fccfbc9`,tag版本的Docker镜像命名为 `项目名/release:tag名`,如 `funnel/release:18.10.R1`。
    9.png

    10.png

    在Jenkins中执行build docker image job时会在每次pull代码之后调用Harbor的API来判断此版本的docker image是否已经存在,如果存在就不执行后续编译打包的stage。在Jenkins的发布任务中会调用打包Job,避免了重复打包镜像,这样就大大的加快了发布速度。
    ## 数据库Schema升级
    数据库的升级用的是flyway,打包成Docker镜像后,在OpenShift中创建Job去执行数据库升级。Job可以用最简单的命令行的方式去创建:
    oc run upgrade-foo --image=upgrade/production --replicas=1 --restart=OnFailure --command -- java -jar -Dprofile=production /app/upgrade-foo.jar

    脚本升级任务也集成在Jenkins中。
    ## 容器发布
    OpenShift有个特别概念叫DeploymentConfig,原生Kubernetes Deployment与之相似,但OpenShift的DeploymentConfig功能更多些。

    DeploymentConfig关联了一个叫做ImageStreamTag的东西,而这个ImagesStreamTag和实际的镜像地址做关联,当ImageStreamTag关联的镜像地址发生了变更,就会触发相应的DeploymentConfig重新部署。我们发布是使用了Jenkins+OpenShift插件,只需要将项目对应的ImageStreamTag指向到新生成的镜像上,就触发了部署。
    11.png

    如果是服务升级,已经有容器在运行怎么实现平滑替换而不影响业务呢?

    配置Pod的健康检查,Health Check只配置了ReadinessProbe,没有用LivenessProbe。因为LivenessProbe在健康检查失败之后,会将故障的Pod直接干掉,故障现场没有保留,不利于问题的排查定位。而ReadinessProbe只会将故障的Pod从Service中踢除,不接受流量。使用了ReadinessProbe后,可以实现滚动升级不中断业务,只有当Pod健康检查成功之后,关联的Service才会转发流量请求给新升级的Pod,并销毁旧的Pod。
    readinessProbe:
    failureThreshold: 4
    httpGet:
    path: /actuator/metrics
    port: 8090
    scheme: HTTP
    initialDelaySeconds: 60
    periodSeconds: 15
    successThreshold: 2
    timeoutSeconds: 2

    # 线上运维的挑战
    ## 服务间调用
    Spring Cloud使用Eruka接受服务注册请求,并在内存中维护服务列表。当一个服务作为客户端发起跨服务调用时,会先获取服务提供者列表,再通过某种负载均衡算法取得具体的服务提供者地址(IP + Port),即所谓的客户端服务发现。在本地开发环境中我们使用这种方式。

    由于OpenShift天然就提供服务端服务发现,即Service模块,客户端无需关注服务发现具体细节,只需知道服务的域名就可以发起调用。由于我们有Node.js应用,在实现Eureka的注册和去注册的过程中都遇到过一些问题,不能达到生产级别。所以决定直接使用Service方式替换掉Eureka,也为以后采用Service Mesh做好铺垫。具体的做法是,配置环境变量`EUREKA_CLIENT_ENABLED=false`,`RIBBON_EUREKA_ENABLED=false`,并将服务列表如 `FOO_RIBBON_LISTOFSERVERS: 'http://foo:8080'` 写进ConfigMap中,以`envFrom: configMapRef`方式获取环境变量列表。

    如果一个服务需要暴露到外部怎么办,比如暴露前端的html文件或者服务端的Gateway。

    OpenShift内置的HAProxy Router,相当于Kubernetes的Ingress,直接在OpenShift的Web界面里面就可以很方便的配置。我们将前端的资源也作为一个Pod并有对应的Service,当请求进入HAProxy符合规则就会转发到UI所在的Service。Router支持A/B test等功能,唯一的遗憾是还不支持URL Rewrite。
    12.png

    13.png

    对于需要URL Rewrite的场景怎么办?那么就直接将Nginx也作为一个服务,再做一层转发。流程变成 Router → Nginx Pod → 具体提供服务的Pod。
    ## 链路跟踪
    开源的全链路跟踪很多,比如Spring Cloud Sleuth + Zipkin,国内有美团的CAT等等。其目的就是当一个请求经过多个服务时,可以通过一个固定值获取整条请求链路的行为日志,基于此可以再进行耗时分析等,衍生出一些性能诊断的功能。不过对于我们而言,首要目的就是trouble shooting,出了问题需要快速定位异常出现在什么服务,整个请求的链路是怎样的。

    为了让解决方案轻量,我们在日志中打印RequestId以及TraceId来标记链路。RequestId在Gateway生成表示唯一一次请求,TraceId相当于二级路径,一开始与RequestId一样,但进入线程池或者消息队列后,TraceId会增加标记来标识唯一条路径。举个例子,当一次请求会向MQ发送一个消息,那么这个消息可能会被多个消费者消费,此时每个消费线程都会自己生成一个TraceId来标记消费链路。加入TraceId的目的就是为了避免只用RequestId过滤出太多日志。

    实现上,通过ThreadLocal存放APIRequestContext串联单服务内的所有调用,当跨服务调用时,将APIRequestContext信息转化为Http Header,被调用方获取到Http Header后再次构建APIRequestContext放入ThreadLocal,重复循环保证RequestId和TraceId不丢失即可。如果进入MQ,那么APIRequestContext信息转化为Message Header即可(基于RabbitMQ实现)。

    当日志汇总到日志系统后,如果出现问题,只需要捕获发生异常的RequestId或是TraceId即可进行问题定位。
    14.png

    经过一年来的使用,基本可以满足绝大多数trouble shooting的场景,一般半小时内即可定位到具体业务。
    ## 容器监控
    容器化前监控用的是Telegraf探针,容器化后用的是Prometheus,直接安装了OpenShift自带的cluster-monitoring-operator。自带的监控项目已经比较全面,包括Node,Pod资源的监控,在新增Node后也会自动添加进来。

    Java项目也添加了Prometheus的监控端点,只是可惜cluster-monitoring-operator提供的配置是只读的,后期将研究怎么将Java的JVM监控这些整合进来。
    15.png

    16.png

    # 更多的
    开源软件是对中小团队的一种福音,无论是Spring Cloud还是Kubernetes都大大降低了团队在基础设施建设上的时间成本。当然其中有更多的话题,比如服务升降级,限流熔断,分布式任务调度,灰度发布,功能开关等等都需要更多时间来探讨。对于小团队,要根据自身情况选择微服务的技术方案,不可一味追新,适合自己的才是最好的。
    #Q&A
    Q:服务治理问题,服务多了,调用方请求服务方,超时或者网络抖动等需要可能需要重试,客户端等不及了怎么办?比如A->B->C,等待超时时间都是6s,因为C服务不稳定,B做了重试,那么增加了A访问B的时长,导致连锁反应?
    A:服务发现有两种,一种是客户端发现,一种是服务端发现。如果是客户端发现的话,客户端需要设置超时时间,如果超时客户端需要自己重试,此时如果是轮询应该可以调用到正常的服务提供方。Spring Coud的Ribbon就是对这一流程做了封装。至于连锁反应的问题,需要有降级熔断,配置Hystrix相关参数并实现fallback方法。看源码能够发现hystrixTimeout要大于ribbonTimeout,即Hystrix熔断了以后就不会重试了,防止雪崩。

    Q:JVM如何export,是多container吗,监控数据,搜刮到Prometheus?
    A:JVM的用的是Prometheus埋点,Java里面的路径是/actuator/prometheus,在yaml里面定义prometheus.io/path: /actuator/prometheu prometheus.io/port: '8090' prometheus.io/scrape: 'true',再在Prometheus里面进行相应的配置,就可以去搜刮到这些暴露的指标。

    Q:Kubernetes和OpenShift哪个更适合微服务的使用?
    A:OpenShift是Kubernetes的下游产品,是Kubernetes企业级的封装,都是一样的。OpenShift封装有功能更强大的监控管理工具,并且拥有Kubernetes不太好做的权限管理系统。

    Q:可以介绍一下你们在优化镜像体积上面做了哪些工作吗?
    A:RUN命令写在一行上,产生的临时文件再删掉。只安装必须要的包。JDK和Node.Js都有slim镜像,一般都是以那个为基础镜像去做。

    Q:数据库是否真的适合最容器化?
    A:我们生产数据库用的是RDS,开发测试环境用的是Docker Compose起的。从理论上,数据库最好做容器化,模块的独立性高,需要考虑好的是数据库容器的数据永久化存储。

    Q:为什么选择了OpenShift?
    A:因为OpenShift有个很方便的UI,大多数都可以在UI里面操作,包括yaml文件的修改,重新部署回退等操作。对于开发测试来讲,学习的成本比较低,不需要花时间熟悉CLI操作。

    Q:Python基础镜像怎么制作最好,如果加入GCC,C++等编译需要的工具,镜像会特别大?
    A:Python基础镜像直接从Python官方Docker镜像上做就行了。GCC,C++那个做出来的镜像大也没办法。如果没这个需求的话,可以用Python slim镜像去做。

    Q:在Gateway中Ribbon如何根据客户端的IP负载到对应的IP注册的服务?
    A:如果使用了Eureka的话,服务提供方启动时会自注册到Eureka。服务调用方发起请求前会从Eureka上读取提供方的列表,再进行负载均衡定位到具体的IP和Port。如果跟我们一样直接使用Kubernetes的Service,其实就是由Kubernetes控制了,服务调用方访问Kubernetes暴露的Service,然后由Kubernetes负载均衡到这个Service背后的具体的Pod。

    Q:如何实现远程发布、打包?
    A:Jenkins打包镜像发布到Harbor上,Jenkins再调用OpenShift去从Harbor上拉取镜像,重新tag一下就可以实现发布。

    Q:譬如客户端IP是10,希望Gateway负载到10注册的order服务,而不是其他IP注册的order服务,希望开发使用集中的Eureka和Gateway?
    A:是说不需要负载均衡?最简单的可以看下Ribbon的实现,负载均衡算法可以自己定义,如果只是要固定IP的话,那么遍历服务列表再判断就可以了。两次判断,if serviceId=order,if ip = 10。

    Q:Docker管理工具一般用什么?
    A:Kubernetes,简称k8s是目前较热门的Docker管理工具。离线安装Kubernetes比较繁琐,有两款比较好的自动化部署工具,Ubuntu系统的Juju和Red Hat系统的OpenShift,OpenShift又称为企业版的Kubernetes,有收费的企业版和免费版。

    Q:Prometheus是每个集群部署一套吗?存储是怎么处理?存本地还是?
    A:每个集群部署一套,存储暂时存在本地,没有用持久化存储。因为现在环境都是在云上面,本身云厂商就有各种的监控数据,所以Prometheus的监控数据也只是做个辅助作用。

    以上内容根据2019年3月19日晚微信群分享内容整理。分享人徐鹏,Linkflow产品运维负责人,负责公司运维平台建设和管理,同时兼顾SaaS版本和私有化版本的交付流程。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零三):骞云科技DevOps实践

    JetLee 发表了文章 • 0 个评论 • 845 次浏览 • 2019-02-28 22:23 • 来自相关话题

    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + J ...查看全部
    【编者的话】随着公司业务的快速发展,需要加快开发流程的规范化和自动化,以提高产品的开发效率和交付效率。之前的开发测试和资源管理主要是半自动化的,个人生产力和资源利用率仍有很大提升空间。在DevOps的具体实践中,一方面, Gerrit + GitLab + Jenkins + CMP(Ansible)共同构建了更好的 CI/CD 流程,对自动化持续交付流水线进行了优化;另一方面,CMP(Self-Service Portal)帮助建立了自服务自运维门户,公司所有人员都可以通过统一的门户自助申请各类资源,并自助完成日常运维。
    #为什么我们要加强 DevOps?

    在公司创立早期,为了尽快实现产品从0到1的转化,我们将更多的资源投入到了产品的新功能开发上,在产品开发自动化方面的投入并不高。
    随着公司业务的迅速发展,一方面,团队规模不断扩大,服务器资源也越来越多;另一方面,产品的功能逐渐丰富,开发代码工程数和分支数增加,而开发测试和资源管理仍以半自动化为主。

    面临的问题:
    ##人力资源浪费

    手工打包、手工创建虚机、手工部署、手工升级、较低程度的自动化测试,这些重复且低效的开发测试模式导致开发测试人员不能将宝贵的时间用于更加有创造力的工作上,不利于个人和公司的快速发展。
    ##IaaS资源管理混乱

    我们的开发测试环境主要构建在内部的vSphere和OpenStack云台上,当然也会在Aliyun、AWS、Azure等公有云上创建资源。在日常工作过程中,我们经常会听到这样的声音:“我的环境怎么这么卡”、“阿里云上又没钱啦”、“OpenStack上的机器我要删了啊”、“谁删了我的机器,我的数据还在上面呢”。由于没有权限控制导致资源随意创建,资源不及时释放导致大量资源闲置和浪费,另外还存在资源误删除情况。
    ##内部系统运维成本居高不下

    我们没有专职的内部系统运维人员,平时开发过程中,遇到CPU/Memory调整、磁盘物理卷/逻辑卷扩容、操作系统故障、应用故障等一系列问题都会占用研发人员大量的时间和精力。
    ##产品交付难度大

    为满足稳定性、高可用性、可扩展性等交付需求,我们的产品在软件架构设计上具有较高的复杂度,这样一来安装部署实施的难度也就比较大。售前团队到客户现场做POC,想要快速部署一套公司产品比较困难;售后团队在项目交付的过程中也经常遇到各种各样的安装配置问题。

    基于上述问题,我们希望通过对DevOps工作流程进行改造和增强,以提高产品开发效率和交付效率,以及提升个人生产力和资源利用率。
    #DevOps整体规划

    我们将DevOps工作流程改造分为了两个方面,一个是对CI/CD工作流的优化,一个是搭建自服务自运维门户。
    ##CI/CD目标


    * 所有代码工程能够自动化打包
    * 所有代码提交后能够自动构建编译检查以及单元测试任务
    * 每小时完成一次软件集成、部署以及核心功能的集成测试(API&UI)
    * 每天完成一次完整功能的集成测试(API&UI)
    * 每周完成一次7x24小时Longrun系统测试
    * 自动更新经过测试的nightly build到开发测试环境
    * 自动发布经过测试的weekly build到Demo环境

    ##自服务自运维目标

    公司所有人员,都可以通过一个统一门户Portal,自助申请各种类型的IaaS资源,如: x86物理服务器、vSphere虚拟机、OpenStack虚拟机、Kubernetes上的容器服务,以及Aliyun、AWS、Azure等公有云上的云资源;自助申请日常开发所需的软件应用,如:Nginx、Tomcat、MySQL、RabbitMQ,以及SmartCMP等。

    * 开发Dev,测试QA,售前交付需要使用不同的资源,做到资源隔离;
    * 资源的使用需要有权限控制;
    * 需要能够一键部署单节点和HA多节点应用系统;
    * 提供环境自动初始化,一键升级能力;
    * 提供系统和应用级别的监控告警;
    * 资源需要能够定期回收。

    #构建更好的CI/CD流程

    ##概述

    我们的DevOps工具链由 GitLab、Gerrit、Jenkins、CMP 构成。

    * GitLab:代码托管
    * Gerrit:代码审查
    * Jenkins:单元测试、自动化打包、集成测试
    * CMP:vSphere、OpenStack、Kubernetes等云资源统一管理,应用系统自动化部署,版本更新

    具体的工作流如下图所示:
    01.png

    开发人员提交代码后,触发Jenkins完成代码编译检查和单元测试,Jenkins返回代码检查结果给Gerrit,人工Review后merge代码,触发Jenkins完成该项目的打包。Jenkins定时完成各个工程的集成打包,然后通过调用CMP的API触发自动化部署,部署完成后进行场景化的集成测试,测试完成后卸除资源。
    02.png

    ##持续集成(Jenkins)

    起初我们使用GitLab Runner实现CI,在每个代码工程中添加“.gitlab-ci.yaml”文件,不同项目各自创建和维护自己的.gitlab-ci.yaml脚本,这样的实现可以解决各自工程的编译测试和打包问题,在代码工程数量较少时,我们也使用了较长一段时间。

    现在我们的代码工程数量已超过20个,每个代码工程都设置了访问权限,如果需要专人维护CI脚本的话,那他需要能够访问所有代码工程,显然这样是不合理的,而且把集成打包脚本放在哪个工程里都不合适。

    考虑到Jenkins有强大的CI能力:通过安装插件就能快速与Gerrit、GitLab集成,能够参数化执行各种类型的脚本。所以,我们使用Jenkins代替gitlab-runner完成CI,通过Jenkins可以统一管理各个工程的编译、测试、打包,而且比较方便构建流水线完成较多工程集成打包及测试。
    03.png

    ##持续部署(Ansible)

    我们的产品由20多个服务组成,可部署在一个或多个虚拟机上,使用Shell脚本或Python脚本已经很难完成这么多服务程序的自动化安装部署配置。

    恰巧团队有使用Ansible做复杂系统部署的经验,Ansible的学习成本也较低,所以我们选择使用Ansible Playbook 实现这20多个服务程序的统一编排部署和配置,并且可以同时支持单节点和HA多节点自动化部署。

    下图是Ansible自动化部署拓扑图:
    04.png

    我们设计Ansible Playbook时,将每个服务都独立成一个角色,这样保证了各个服务部署的独立性,这种分布式部署架构为将来容器化部署和微服务化奠定了基础。

    Ansible自动化标准化部署不仅大大缩短了部署时间,也极大地降低了部署出错的概率。原先,按照HA架构部署一套产品需要1天时间来完成各个服务的部署和配置,通过使用Ansible playbook,我们只需要45分钟,而且中间过程完全可以放手去做别的事情。
    ##集成测试(Robot Framework)

    目前,我们在Jenkins中使用Robot Framework框架做集成测试。Robot Framework(以下简称RF)是一个基于Python的、可扩展的、关键字驱动的测试自动化框架。

    选用RF的原因:

    * 一致性:目前公司的UI自动化测试使用的就是RF框架,RF框架也完全有能力做集成测试,因此使用RF框架做集成测试,可以降低学习成本,提高可维护性。
    * 复用性:在安装了Robot-Framework-JMeter-Library后,RF可以运行JMeter脚本,并且将JMeter运行结果转为Html格式。公司目前性能测试用的就是JMeter,对于相同场景,只要小幅修改JMeter脚本即可将其复用到集成测试上面。

    05.png

    选用RF也存在一些问题:

    * 如果不复用JMeter脚本,编写的API测试用例的成本非常高。
    RF对于变量类型的规定堪称僵硬(当然,这么规定带来的好处是方便类型检测),RF中对于字典类型的创建非常麻烦(嵌套的字典实例如下),对于我们公司API请求中携带大量参数的情况,只能创建关键字来解决,不管是采取RF自带创建字典的方法,还是创建关键字的方法,都比较浪费时间(因为难以复用)。
    06.png

    * RF可以轻松扩展关键字,也因此可能带来乱扩展关键字的问题,导致测试用例可读性和可维护性差的问题。

    在RF中,关键字其实就是Python/Java的类方法,因此扩展起来非常容易,但是关键字一旦多起来,一个同事写的测试用例,其他人(甚至他自己过了一段时间)维护就非常麻烦(需要回去看关键字是如何规定的)。因此需要严格规定关键字的创建规范是一件值得深入讨论的事情。
    #建立自服务自运维门户

    我们使用云管理平台(以下简称CMP,Cloud Management Platform)管理公司内部资源,使得公司所有人员,都可以通过CMP提供的自服务门户(Self-Service Portal),完成计算/存储/网络等IaaS资源和软件应用自助申请,并且能够自助进行日常运维操作。
    ##CMP平台准备工作

    通过LDAP方式将公司AD账户导入CMP平台中,为开发、测试、售前售后团队创建不同的业务组和资源池,每个资源池给到不同的资源配额,做到资源合理分配和资源相互隔离。为每个业务组设定一个管理员,审批业务组成员的资源申请。
    07.png

    我们使用Shell、Python脚本或Ansible配置管理工具将内部常用的一些软件及应用系统的安装过程进行封装,并发布到CMP平台中,提供标准化蓝图方便大家申请。
    08.png

    ##自服务自运维门户

    在门户中,大家可以看到已经发布好的服务卡片,通过点击服务卡片即可完成IaaS资源或应用系统的自助申请,在平时的开发测试过程中,我们不再需关心底层复杂的系统或网络配置。
    09.png

    在门户中,大家也可以清晰地看到自己所管理的资源的性能情况,还可以简单便捷地完成一些日常的基础运维操作:重启、调整配置、添加逻辑卷、扩展逻辑卷等。
    10.png

    此外,使用管理账号登录CMP管理平台,可以清晰地看到公司内部资源的总体使用情况。
    11.png

    12.png

    13.png

    #总结与建议

    在骞云科技的DevOps实践中,一方面,我们将GitLab、Gerrit、Jenkins、Ansible、JMeter、Robot Framework等成熟的开源工具开源技术和企业内部的云管理平台相结合,实现了较高程度的开发测试流程自动化,推动了产品的持续集成和持续交付,减少了大量的重复劳动,提高了开发测试效率。

    另一方面,通过使用云管理平台,将复杂异构的IaaS资源服务化,降低了使用难度;结合业务部门需求合理划分资源,减少了资源浪费,加强了资源的有效隔离,避免了误操作;自服务自运维的模式,也极大地提升了公司整体研发效率。

    我们的DevOps实践方案适用的场景非常多样,比如:弹性伸缩、迁移、负载均衡。在传统IT、金融、互联网、游戏等行业也具有普适性。
    #未来发展方向

    在介绍Ansible自动化部署时有提到,我们的业务系统由20多个服务组成,符合服务化的架构设计,目前已经可以满足私有化的部署需求。随着新功能的不断引入,部分业务子系统复杂度和团队开发耦合度会逐渐升高,协作效率和部署效率会变得低下。另外,当前的软件架构和部署架构不能满足将来的SaaS化部署。所以,我们仍需要将服务进行更细粒度的拆分,逐步向微服务架构转变并使用容器化部署,进一步降低开发和部署成本。
    #Q&A

    Q:CMP和各个云平台打通都使用了平台的jar,并且需要各种资源生成,这个工作量也不小吧?并且如果api更新代码量也大吧?
    A:我们的核心业务就是做云管理平台,我们产品已经完成了对各个云平台的对接,主要调用各个云平台的API。公有云的API更新频率并不是很高,每当API有更新时,我们也及时去适配。

    Q:Jenkins初次提交也能触发构建吗?每次自动化构建版本号是如何更新的呢?
    A:我们的项目代码具备构建条件后,才在Jenkins上创建了项目构建Job,所以并没有在初次提交时触发构建。每次构建的版本号由两部分组成,一部分是产品的Release大版本号,另一部分直接使用的Jenkins build number这个环境变量。

    Q:有了Gerrit,为什么还要GitLab,Gerrit也可以托管代码啊?
    A:这个是有历史背景的,我们是先选择使用GitLab做代码托管,后期才加入Gerrit做code review。Gerrit在代码review方面比GitLab的merge request要方便许多,更适合企业内部使用。关于这个,我的想法是,要么将GitLab迁移到Gerrit,要么不用Gerrit,可以使用GitLab的merge request来进行review,那GitLab其实是可以不要的。

    以上内容根据2019年2月26日晚微信群分享内容整理。分享人夏飞,骞云科技SmartCMP云管理平台后端开发负责人,负责云管理平台的建设和推广,目前负责公司内部DevOps工作流程的改造。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(二零二):房多多Service Mesh实践

    大卫 发表了文章 • 0 个评论 • 837 次浏览 • 2019-02-22 10:13 • 来自相关话题

    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。 #概述和 ...查看全部
    【编者的话】随着微服务数量越来越多,给业务开发团队的增加了沟通和维护的成本,为了解决这一的痛点,我们关注到了Service Mesh技术,本次分享主要介绍房多多在Service Mesh中的一些经历和过程,以及在工程效率方面的一些感悟。
    #概述和需求背景

    房多多国内领先的经纪人直卖平台,目前房多多APP和多多卖房APP的后端服务主要运行在自建的IDC机房。2018年之前,我们的服务都是运行在VM上,也同时基本上完成了基于Dubbo的微服务改造,目前的语言技术栈主要有:Java、Node.js、Golang。相对于其他公司而言,技术栈不是特别分散,这给我们服务容器化带来了极大的便利。

    我们在2018年Q3的时候已经完成大部分服务容器化,此时我们的微服务数量已经超过400个了,在沟通和维护上的成本很高。因为我们后端服务是主要是基于Dubbo的,跟前端直接的交互还是通过http的方式,在没有上Service Mesh之前,http请求都是经过Nginx转发的。维护Nginx的配置文件是一个工作量大且重复性高的事情,运维团队和业务团队迫切的需要更高效的方案来解决配置管理和沟通成本的问题,构建房多多Service Mesh体系就显得尤为重要。Envoy是一个成熟稳定的项目,也能够满足近期的需求,在现阶段我们并没有人力去动Envoy,所以我们直接使用了Envoy做Service Mesh的数据平面,关于控制平面这块,在调研了一些方案之后,我们采用了自研的方案。
    #整体平台架构

    我们的容器平台整体是基于物理机的,除了调度节点是用的虚拟机以外,所有的工作节点都是使用物理机。之前我们的虚拟化方案是用的Xenserver,Xenserver对高版本的内核支持并不好,会时不时的出现自动虚拟机关机的bug,所以我们在工作节点上只使用物理机以保障业务的稳定和高效。

    我们的业务主要工作在自建IDC机房,公有云只有少量的灾备服务。因为是自建机房,相比较公有云而言,自建机房使用Macvlan是一个比较好的方案。我们预先划分了几个20位地址的子网,每个子网内配置一些物理机,通过集中式的IP管理服务去管理物理机和容器的IP。相比较bridge网络,Macvlan网络有着非常接近物理网络的性能,尤其是在大流量场景下性能出色,下面一张图显示了性能对比:
    1.png

    2.png

    3.png

    我们把Envoy作为两个网络之间的连接桥,这么做的好处在于可以控制流量都经过负载均衡器,便于集中管理以及对流量做分析。看到这里,肯定有疑问是为什么不使用Sidecar的方式部署Envoy。关于Sidecar我的考虑是,在现有的业务场景下,集中部署的维护成本更低且性能满足需求,也相对来说比较简单。我们在2018年Q4已经完成主要业务http2接入,目前来看,我们的网站速度应该是同行业中最快的,大家可以体验一下,https://m.fangdd.com 。
    4.png

    #如何解决虚拟机业务和容器业务的并存问题

    我们原有的架构大量使用了虚拟机,迁移虚拟机上面的服务是一个漫长的过程,当前阶段还需要解决业务的并存问题,我们自己开发了Envoy对应的配置集中管理服务,同时支持虚拟机服务和容器服务。

    控制平面服务主要基于data-plane-api开发,功能上主要是给数据平面提供服务的集群配置、路由配置等信息,并且需要实现微服务架构中降级和限流的配置功能。容器内部的集群数据主要依赖DNSRR实现,而虚拟机上的服务,我们在CMDB上存有AppID和机器的对应关系,很容器生成集群配置数据。


    由于历史原因,还有相当多的业务无法从VM迁移到容器,需要有一个同时兼容容器和VM的数据平面服务,目前XDS服务的支持的功能如下:

    * 集群数据来源同时包括容器内部DNS和外部CMDB中的VM数据
    * 支持多个 vhost 配置
    * 支持路由配置
    * 支持速率控制和网关错误重试

    5.png

    #应用开发流程变化

    初步建设起Service Mesh体系之后,理论上业务开发只需要开发一个单体服务,在服务间互相调用的过程中,只需要知道服务名即可互相调用,这就很像把所有服务都看做一个微服务一样,所以我们的业务开发流程发生了以下变化:
    6.png

    同时也降低了框架开发成本和业务改动的成本,每次推动业务升级框架都需要比较长的一段时间,业务无法及时用上新框架的功能,多种框架版本也加重运维负担,Service Mesh帮我们解决了很多痛点。同时再加上我们的网关层建设,我们上线一个新服务几乎是零配置成本的。

    * 代理层实现服务发现,对于开发而言只需要开发一个单机的应用,降低框架开发成本
    * 降级和限流都在代理层实现,规则灵活,方便修改策略
    * 框架功能的升级无需依赖业务

    #SOA和Service Mesh的对比与取舍

    在我们的Service Mesh实践中,增加了链路的请求长度,并且服务的链路越长,链路请求的放大效应会越明显,这就带来了一些性能上面的担忧。毫无疑问,Mesh层本身的业务逻辑开销是不大,但是在网络传输和内存复制上的性能消耗在一定程度上会影响链路的性能,Envoy也在探索相关的方案来优化网络传输性能,如Bpfilter和VPP,减少用户态和内核态的内存拷贝。在可定制性层面,SOA能做的事情也相对较多,可以实现很多hack的需求。

    在我们现有的业务场景下,Service Mesh主要还是解决前后端的微服务对接问题,当做前后端服务的连接桥梁。但不可否认的是,Service Mesh带来研发效率的提升,在现有的场景下的价值远大于性能上的损失。在大多数的场景下,维护业务框架需要比较大的人力成本,很多团队都没有全职的人去维护业务框架。此外,推动业务框架的更新和升级也相对来说成本较高,这也是我们考虑的一个重要方面。
    #总结

    得益于云原生架构,Service Mesh可以使用云原生的基础设施,基础设施能力的改进可以直接赋能业务,而不像传统的框架一样,基础设施的升级和改进无法提高传统框架的服务能力。房多多的Service Mesh还处于初级阶段,后面还将继续探索。
    #Q&A

    Q:容器和微服务的区别以及它们的关联性、应用场景、客户群体、带来的价值?
    A:微服务的应用场景主要是解决降低单个服务体积,满足业务的快速开发需求。容器可以说是微服务的载体,容器方面还是运维关注的比较多,带来的标准化流程和环境的提升对整个研发团队都有很大的作用。

    Q:软件实现 Service Mesh,Istio?
    A:我们目前只使用了Envoy,Istio也做过一些选型的调研,不是很符合我们现在的业务场景和业务需求,我们在之后的实践中会考虑使用一部分Istio的功能。

    Q:实施过程当中有使用到Istio吗?有定制一些Mixer适配器吗?
    A:目前还没有用,之后会考虑是用Istio中的pilot,我们目前在流量的控制的精细程度上面还欠缺,粒度还很粗。

    Q:请问,实现微服务过程中有没有考虑分布式跟踪和分布式?
    A:Service Mesh层可以做到简单的分布式追踪,比如可以做到基于请求的追踪,Envoy可以把trace数据接入Zipkin这样的trace系统,但是更细粒度的trace目前还做不到。

    Q:不论是使用都会产生大量的配置(yaml),尤其是Envoy/Istio,系统中会有大量零散的配置文件,维护困难,还有版本管理,有什么很好的维护实践经验吗?谢谢。
    A:是的,据我所知有些团队会使用ConfigMap来管理配置,我们做了一个配置的集中管理服务,从CMDB和DNS定时的抓取数据,数据会存在数据库里面,也会存一定量的副本用于配置回退,这个地方还是要结合你们现在其他配套系统的建设来看看怎么做比较好的。

    Q:有没有遇到过Envoy被oom kill的情况,你们是如何处理的?
    A:这个我有碰到过一次,之前我们在对Envoy做测试的时候,发现Envoy总会尽可能的占满CGroup的内存大小,这个之前在使用TLS的情况下碰到的比较多。但是目前我们内部服务间使用TLS的情况并不多,所以后续这个问题就没有继续跟进了。

    Q:性化方案有哪些?
    A:之前文章中有提到过,对于http服务可以全量接入http2,http2的长连接和多路复用对于一般的业务来说升是很明显的,我们在全量接入http2之后,网站的响应时间几乎下降了50%。另外还可以在底层的依赖上面做一些优化,比如底层的libc库,以及尽可能的减少基础镜像的大小,我们基本上所有业务都使用了alpine,这样可以保证发布性能和速度在一个很好的水平上。

    Q:还是有一个服务治理/配置管理的问题请教,比如CPU,内存,这种资源request,在dev,test,staging,prod环境均不同,那么我们在编写Kubernetes配置的时候不同环境会不同,比如测试环境的replics数跟CPU需求就很低,生产就很高,另外这个配置在不同环境是多个还是一个呢?谢谢。
    A:我们现在会在CMDB里面维护这些配置数据,一般来说在新建项目的时候,我们就会要求业务线评估一下这个业务需要的资源,比如你说到的test和staging环境这种,我们默认会给一个很低的需求,比如1c1g这样的,而且replication也会默认设置为1,除非业务有特殊的需求,然后我们去根据配置的数据去生成yaml格式为配置。
    配置在数据存储的形式上是多个,但是在对业务展示上,尽量让业务感觉到是一个数据,这样也能使每个环境都规范起来,跟生产环境尽可能保持一致,这个对测试的好处还是很大的。

    Q:你们目前的项目中,大量的微服务,以及调度层,瓶颈和容灾是如何处理的?
    A:由于我们的业务类型还是B端业务为主,流量的峰值是可以预估的,很少会出现突发的大流量的情况,我们一般都预留了1倍的容量用于临时的扩容。容灾和调度的话我们主要还是尽可能的隔离工作节点和调度节点,以及大量使用物理机集群,从我们的使用体验上来看,物理机的稳定性还是很不错的。

    Q:如何用Jenkins自动完成Kubernetes部署?
    A:自动部署这块我们有完善的发布系统,如果单纯只要实现Jenkins自动完成Kubernetes的话,Jenkins可以直接调用Kubernetes的API,这个网上有挺多资料的,你可以找找看。

    Q:Service Mesh比传统的微服务治理好在哪里?
    A:降低框架开发成本、代理规则灵活,方便修改策略、框架功能的升级无需依赖业务,最大的好处我觉得就是可以降低框架开发成本。

    Q:我理解房多多目前的Mesh方案没有给每个微服务配一个Envoy作为Sidecar,而是使用一组Enovy并自研了xDS的配置发布管理系统,对吗?我想请问backend微服务之间的请求现在是怎么走的呢?谢谢。
    A:是的,刚刚文章中说了,我们后端SOA服务还是基于Dubbo的,这块目前我们还没有做改动,之后的话我们的初步构想会通过Sidecar的方式把这些Dubbo服务都接入到Mesh里面来。我们目前的Envoy也是会充当网关的角色。

    以上内容根据2019年2月21日晚微信群分享内容整理。分享人杜雅林,房多多平台工具负责人,负责容器平台、发布系统、Service Mesh相关功能开发。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。
    每周二晚,不见不散。