云原生应用

本文有 7596 字,大约需要 18 分钟可以读完, 创建于 2018-04-26

云原生应用试图从思想层面对我们编写和发布软件的方式来一次深刻的革命;这一设想其实是基于目前的大部分云计算平台上运行的应用都是从传统的非云平台环境上移植过来的。

由于传统的应用期望其运行在可靠而又昂贵的硬件或者基础设施之上,软件本身的设计便没有对云计算的基础设施做很好的针对性设计。 云原生应用的思路是,既然云计算平台无处不在了,能否从设计之初就仅仅考虑软件默认就是运行在不可靠的云基础设施上?

按照传统的应用软件开发思路,软件被运行在可靠性很高的硬件设施之上,存储设备可能本身有提供基于RAID的硬件级别的高可用特性, 网络接口上可能有基于bond的双以太网连接互为主从备份的机制保证大部分情况下并不为真正出错;应用的架构采用相对简单的三层架构, 数据库管理软件本身提供了复杂而又可靠的事务属性保证,从而应用程序设计上并不需要太多可能的异常情况;出现问题的时候,简单重启程序即可。

云计算的提出和被快速采纳促进了更复杂而又去中心化的微服务架构的流行,它结合了运算能力、存储、网络虚拟化的特性, 将软件底层所运行的物理资源做了隔离,应用程序不得不假设底层硬件可能是不可靠的;事实上云计算的数据中心处于成本考虑往往搭建在相对廉价的X86机器上。

云原生应用和一些基础问题

云原生应用开发的设想是,既然云计算已经无处不在了,我们为什么不忘记传统企业应用软件的这些昂贵而又奢侈的硬件基础设施, 从软件设计上就做好防范考虑好可能的异常情况,天然假定这些可靠的基础设施都不存在,虚拟的处理器可能被重启、磁盘可能会丢数据、网络也可能随时失效。 这样写出来的软件自然也很容易在传统的可靠平台上运行,因为大部分应用的复杂度都被精心设计了。

云原生应用天然地和分布式软件架构绑定在一起,它必须解决Sun的科学家提出的分布式计算几个陷阱

  • 计算机网络是不可靠的,应用软件必须随之准备好可能的网络失效、超时等
  • 网络的延时不等于零,在多个分布式应用之间转移数据必然导致整个业务流的处理时间变长,甚至使得整个业务流失效
  • 网络带宽永远是受限的,浪费网络带宽可能带来严重的系统性能问题
  • 网络的安全威胁始终不容忽视,跨越多个网络节点的通信会带来复杂的恶意用户监听、偷听、掉包、窃取等威胁
  • 网络拓扑结构的变化可能带来整个系统行为的变化,尤其是基于IP网络的数据包可能在发送和接收方向经过完全不同的路由
  • 跨越网络节点的传输、封装包和解析包的开销必须经常被仔细考虑,某些时候这些开销都不应该被忽略
  • 网络结构可能不是同构的,这一条反过来会影响前面一些因素,包括带宽、时延、安全等

CNCF基金会

随着微服务软件架构的深入发展和演进,人们发现上述的很多底层的问题有一定的通用性,适合用一些共有的基础设施来解决,而不是选择各自实现自己的一套。 譬如服务如果需要提供多个实例的软件层面的高可靠,就往往需要服务发现、路由和负载均衡;如果需要避免局部服务失败引起的系统拥塞,则可能需要连接层面的断路器, 以便在服务不可用的时候即使返回错误,避免浪费资源等待;分布式的服务需要公共的仪表盘查看整个系统的处理动态和健康状况,也往往需要良好的诊断和日志工具, 以方便在问题的情况下及时追踪、诊断并解决问题。

CNCF是Linux基金会旗下的一个致力于集合开源社区的力量来提高云原生应用可用性和可持续性的开源组织,它聚合了一大批基于开源模式开发的微服务应用程序栈, 利用已经成熟的容器技术,将这些微服务各自打包为独立的容器以便分发和使用,并支持动态地配置和编排这些服务以更有效地使用虚拟化的云资源。 通过这一努力,应用软件开发程序员可以通过聚焦于自己的业务领域问题更快地开发和交付软件产品。

它的项目大多专注于上述的一些云原生应用开发的基础设施,大概可以分为两大类

  • 云计算平台本身相关的一些开源项目,包括OpenStack,Kubernetes,容器,AWS等;这里也包含了一些商业组织提供的开源项目,如阿里、华为的云平台等
  • 微服务开发的一些基础软件服务,包括gRPC、Linkerd、Fluentd、Promethus等

Kubernetes

Kubernetes又称K8s,近来已经确切地赢得了容器云平台的战争,成为事实上的开源容器调度、编排平台的事实标准;它的主要功能包括

  • 自动化方式完成基于容器的应用程序的打包、动态扩展和管理
  • 极高的扩展性,这方面给基于google内部已经使用了十几年的庞大的基础设施集群,可支持管理和自动伸缩扩展数十亿数量级的容器应用
  • 极高的部署灵活性,不仅适用于内部测试环境也可以自动适应管理生产环境的需要
  • 强大的周围生态系统,可以和已有的各种公有云、私有云乃至混合云平台无缝融合

CNCF项目孵化毕业模型

CNCF内部将其项目按照成熟度模型分为以下几类

  • 进入沙箱的项目必须满足几个关键的标准,包括为云原生计算的核心内容 - 容器化、编排、微服务或者一些几者的结合增加价值并遵循CNCF的章程; 代码符合ASIL的版权要求或者对应的版权协议被CNCF治理委员会接纳;同意将相关商标移交给基金会并同意协助完成可能的新商标注册;每12个月委员会需要投票 决定是否仍然可以停留在沙箱阶段或者可以变成孵化项目
  • 孵化阶段的项目除了满足上述标准,还应该用显式的文档说明该项目被成功应用在三个以上的成熟产品中,这些应用场景需要被委员会认可满足特定的质量和场景; 拥有健康的代码提交者;具备持续的代码提交和合并贡献行为;这些数据本身可能是非常动态的,因此委员会有最终的裁决权来决定是否某个项目可以进入孵化阶段
  • 如果想达到毕业成熟的状态,除了上述标准,项目还必须满足至少拥有来自两个组织的代码提交者;获取或者维持满足Linux基金会核心基础设施的最佳实践徽章, 目前这一徽章的获得者包括OpenSSL/Curl/GNUMake/GNUPG等明星项目;采纳CNCF的行为准则; 显式地定义项目的治理和提交贡献流程,并提供GOVERANCE.md和ONWERS.md文件,指明目前的项目治理和提交贡献名单;至少要在主项目的库上提供公开的项目采纳者名单; 最终需要收到投票委员会确认的毕业许可。

目前K8S于2018年3月份成功从孵化阶段毕业成为首个CNCF毕业的项目。

K8s的基本架构

K8s从资源管理角度,将最小的容器调度单元抽象为一个pod;一个pod可能包含一个或者多个容器应用;这些应用总是按照预先制定的部署、扩展规则被同时部署和调度, 运行在一个共享的包括存储、网络、计算等共享资源的上下文环境中。从概念上来说,一个pod可以认为是一个逻辑的机器,在这台逻辑机器上,多个容器应用程序从部署的角度 紧密地耦合在一起。从传统的非云计算环境或者基于虚拟机的环境来看,一个pod等同于一个物理机或者虚拟机。

从最广泛的实现支持角度看,可以认为一个pod包含了多个docker实例,它们会共享同一个内核的命名空间,进程cgroups,以及其它一些Linux内核所支持的资源隔离策略。 他们也会共享相同的ip地址空间和端口,可以通过localhost互相访问彼此,并可以使用传统的UNIX IPC设施。而多个pod之间的应用程序在没有经过特殊配置的情况下则不能访问彼此, 他们往往需要通过pod暴露的地址和端口尽快互操作。

K8s通过APIServer来获取用户的容器配置信息并通过这些信息来管理用户的容器;通常情况下应用程序需要提供一个YAML配置文件来声明具体的细节信息, 用户可以通过将对应的状态修改通过API的方式发送给APIServer来完成容器状态的变更和维护,包括诸如需要运行的程序工作负载,对应应用程序的容器镜像信息, 需要分配的网络和磁盘资源信息等等。背后的APIServer其实仅仅是一个网关,K8s内部的实现上使用了etcd来提供高可用和负载均衡, 大部分情况下用户只需要看到这个逻辑的API网关即可。

另外一个配合管理容器的组件是Controller,它负责完成对应的集群的控制,使期望的容器最终达到预先设定的运行或者终止状态。默认情况下, K8s的controller进程是kube-controller-managercloud-controller-manager,它也允许高级用户自定义自己的controller。 Controller的基本逻辑可以简化为如下的循环

  1. 当前集群的状态是多少?假定为X
  2. 期望的目标集群的状态是什么,假定是Y
  3. 如果两者相等,则什么也不做。否则 X!=Y那么执行内部的调动认为比如启动新的容器或者终止容器或者扩展容器实例的个数等;然后继续回到第一步。

K8s也提供了一个叫kubectl的命令行工具来简化API调用的操作,用户也可以使用自己定义的客户端来直接操作这些REST API,或者使用K8s官方支持的客户端来管理容器应用。

Prometheus

Prometheus是一个服务于微服务生态系统的开源基础组件,它的核心功能是提供对微服务实例的运行时监控和参数触发告警。 它的核心数据模型构建于一个基于时间序列的多维数据模型之上;每一项数据除了所关联的时间戳之外,另外包含了参数名(关注的监控参数)和一系列Key-Value对的关联参数。 用户可以根据自己的业务模型需要,定义自己的标签,这样当多个微服务产生大量的参数信息的时候,用户可以根据预先定义好的这些标签过滤自己感兴趣的参数随时间变化的情况; 方便对微服务系统的运行状况进行实时监控、诊断和分析。

四种参数信息

Prometheus提供了四种类型的参数信息供客户端使用

  • 计数(Counter)信息,往往用来表述单调增加的基于正整数的统计信息,譬如对应的请求被处理了多少次、完成的任务个数、发生错误的数量等。
  • 纯计量(Gauge)信息,可能增加也可能减小的数值信息,譬如微服务中正在运行的gorountine数量,当前CPU使用率等。
  • 直方图(Histogram)信息,往往用于基于固定时间段的统计抽样信息,譬如5分钟内处理的响应个数随时间变化的直方图统计等;每一个时间段的信息往往是累加的。
  • 概要(Summary)信息,和上面的直方图有些类似,所不同的是它还提供了所有观测值的总和,以及给定时间窗口内可配置的百分比统计信息,譬如给定参数的90%统计值,99%范围内的统计值等。 该类型信息在统计某些性能参数的时候特别有用。

实例信息和查询

当Prometheus抓取同一个微服务的多个运行实例的时候(逻辑上是一个微服务,通过一个负载均衡器分摊运行期负载),它会自动为抓取到的数据分配2个标签:

  • Job名字,用户配置的抓取给定服务的一个名字,譬如设置为微服务的名字
  • 实例信息,截取URL中的<host>:<port>信息,方便识别是哪个实例上报的参数 同时它还可以提供额外的参数信息,如给定的抓取Job是否正常运行,抓取的持续时间,参数被重新打标签的时候还剩余的未抓取的采样时间,目标服务提供的参数的总个数等。

Prometheus提供了强大的参数查询和可视化功能,用户可以在它自身的仪表盘上输入查询表达式,生成实时更新的参数采样信息监控图标,方便地知道服务的运行情况, 比如CPU使用情况,API请求的负载情况等信息。用户可以选择将自己定义的查询以rules的方式添加到Prometheus的配置文件中,让它自动生成自定义的参数信息。

自动告警管理

依据配置的规则自动向特定的目标放发送报警是Prometheus提供的又一个重要的功能。该功能由两个部分协作完成

  • 报警规则(Alerting rules) 允许用户指定基于查询表达式的告警触发条件,当给定条件满足的时候,Prometheus会自动向AlertManager发送告警
  • 告警管理器(Alert Manager) 接收来自Prometheus服务器的告警通知,完成诸如去重复、分组、路由到集成的第三方工具的功能; 用户可以配置邮件客户端,在线服务群或者其它在线运维渠道。同时它也可以提供抑制告警的功能。 告警管理器不是默认Prometheus的部分,而是作为一个单独的服务存在。

告警管理器提供的管理功能包括如下几种

  • 分组功能将接收到的多个本质上相似的通知信息转换成一个告警,减少噪音;这在大的系统中多个微服务实例同时出错的情况下格外有用; 比如当发生网络分隔故障的时候,被隔离的微服务集群如果有数百个实例在同时运行,依据配置它们将发送大量的数据库无法访问的告警信息, 进而出现上百个不同的微服务发送的数据库无法访问的告警通知;用户在这种情况下可能仅仅想看到是哪一个服务受到影响而不是该服务的多少个实例受到影响。 分组功能则在不丢失原有通知的情况下,提供一个单独的告警通知用户数据库访问出了故障。

  • 告警抑制功能可以在某些告警已经出发的情况下,自动过滤掉另外一些次生的告警。
  • 静默告警功能提供一种直接的告警静默功能;基于预先配置的匹配规则(正则表达式等),当接收到的告警满足给定的规则时,该告警则完全不可见。
  • 高可用方面,它支持用户通过配置将多个AlertManager配置为一个网格;关键之处是官方不建议用户将AlertManager配置在一个负载均衡器的背后, 而是将其列表直接暴露给Prometheus服务。

部署和集成扩展

Prometheus本身是用Go语言写成并静态编译和打包,因而其部署非常简单;生成的包也比较小巧紧凑,docker镜像只有20MB左右。 它的这些参数信息用自己定制的格式同时存放在内存和磁盘中;本身的扩展可以使用其他已有的数据存储扩展方式。 微服务作为客户端可以使用它提供的REST API向Prometheus报告数据;官方提供了超过十种编程语言的客户端,方便微服务侧根据自己的需求选择。

gRPC

gRPC正在慢慢成为微服务领域除了REST API之外的一个新的服务间接口语言。和REST不同的是,它基于传统的RPC模型而非资源抽象模型,更适合于符合显示命令语义的场景; 其依托于Protobuf和HTTP/2协议的高效的协议编码和双向通信机制对很多追求高性能的微服务应用而言有巨大的吸引力。 传统的基于REST API通信的应用要实现双向的通信往往不得不借助间接的双向REST调用或者引入消息队列并不得不处理复杂的应用程序逻辑, 而gRPC不但省去了不必要的HTTP协议头开销,而且支持服务端在一条底层连接上同时向服务使用方(客户端)推送请求,可以极大地提高传输效率降低通信延迟。

很多流行的开源项目都加入了支持gRPC接口的行列,甚至一些微服务基础设施内部就是用gRPC进行通信的; 具体的细节可以参看前面这篇文章

其它一些热门的项目

还有一些处于孵化期的项目,相当一部分都和云计算的基础设施平台有关,譬如用于服务网格的边车代理服务乃至提供无服务器架构平台的FaaS、BaaS设施等。 这里仅侧重看一下容器生态相关的一些项目,虽然基于Docker技术的容器应用已经在大部分环境中被顺利部署,但是为了避免所谓的vendor lockin、降低迁移成本, 还是有一些开源项目致力于标准化这些容器生态关键的接口和规范,防止被某个特定的平台提供商所锁定。

Containerd

Containerd是一个开源社区提供的工业标准的容器运行时服务,它的设计目标是简单、鲁棒又可移植。 作为一个容器运行时管理工具,它可以在容器的宿主机环境中完成容器生命周期的完整管理,包括

  • 容器镜像的转移和存储管理
  • 容器的运行和状态监控
  • 底层网络资源和存储资源的叠加和控制

作为一个后台运行管理服务,它的目标就不是被普通服务开发者直接使用的,而是被设计为潜入到一个更大的系统中去。 Containerd的架构设计上选择了暴露一个基于gRPC的API接口,该接口工作与本地的UNIX Socket上而不是TCP/IP网络因为它工作的对象是一个单机环境; 该API仅仅提供比较底层的功能,更高层的服务可以基于此进行扩展和进一步封装。它也提供了一个用于调试的CLI接口,并使用基于OCI规范的runC来运行容器。

Containerd本身来自于成熟的Docker Engine项目的一部分,基于Docker本身成熟的生态环境,它可以提供更开放并同时具有时间检验的成熟的功能特性。 较新的Docker 17.12版本也已经将原有的私有实现提供为基于Containerd的后台实现。 目前Docker仅仅使用Containerd完成容器运行和进程管理的功能;其它大部分诸如网络、分发、存储管理的功能依然是Docker Engine自己的私有实现。

长期来说Containerd打算重构更多的Docker Engine代码并将合适的处理逻辑剥离出来, 贡献给开源社区,并同时重写Docker Engine自己的基于开源Containerd的实现,其未来目标如下 dockerd_future_arch

rkt

rkt是CoreOS提供的一个功能上和Docker有些类似而架构完全不同的、原生支持pod概念的容器管理工具。 rkt除了支持docker镜像之外,还支持AppC规范定义的App Container Image,它的设计目标侧重于以下一些要素

  • pod-native将Pod作为基本的执行单元,自动将其他资源链接在一个执行环境中
  • 安全,默认提供对SELinux的支持,并可以将应用程序容器运行在硬件隔离的虚拟环境
  • 可组合,提供对已有的初始化系统的原生支持,支持包括systemd、upstart等初始化服务的集成,支持和K8s的集成
  • 支持开放标准AppC和CNI规范,可以运行docker和ACI镜像

rkt的进程模型以及和Docker的比较见下图 rkt_docker_process_model

服务网格项目

目前Linkerd和Envoy都处于孵化状态,二者本身也是服务网格领域的佼佼者;更多细节可以参考之前的 这篇服务网格的文章

OpenTracing

OpenTracing是一个开源的服务于微服务应用架构的服务跟踪应用。它本身期望解决的是, 多个微服务实例在运行时服务运行状态跟踪、诊断及故障分析困难的问题。这在传统的单体应用情况下根本不是问题,而微服务的高度分布式、自适应的扩展性给跟踪带来了额外的困难。

同领域的一些跟踪系统还有Zipkin、Dapper等项目可以完成类似的任务,OpenTracing的独特之处是它提供提供商中立的API,有效地减小Vendor-Lock的风险。 它已经提供了对9种主流编程语言的支持,并支持诸如gRPC、Flask、django、MOTAN等知名开源框架。

引用资料

  1. CNCF landscape
  2. CNCF graduation criteria
  3. Prometheus
  4. Containerd

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...