34

通过 Kubernetes 学架构

 5 years ago
source link: http://jolestar.com/learning-architecture-by-kubernetes/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原本打算写一个 Kubernetes 系列,标题就是这个,但后来技术方向改变,没有坚持下去。抽空整理了下,把原来写的序言和部分章节的摘要发出来。

『架构』这个词不同的人有不同的理解,有人觉得『架构』就好像一种工程师努力追寻的武学秘籍,也有人觉得『架构』就是画 PPT 或者在大会吹牛。确实不像学习编程,可以有许多教程可用,没有教程类的书教人如何做架构。脱离具体的业务场景和系统,谈架构总是让人感觉很虚,所以一直想找个系统作为依托,写写自己对架构的思考。

去年录制了一套 《Kubernetes 完全教程》,主要介绍了 Kubernetes 本身的特性以及使用,到最后发现只能让大家做到”知其然”,做不到”知其所以然”。正好经过几年的 Kubernetes 使用与研发经历,我个人也积累了许多问题,有的自己想明白了,但有的还在思考中。于是想到写一个高级版的 Kubernetes 架构系列,通过解析 Kubernetes 的架构,同时比较业界对同一个问题的不同解决方案,和大家一起探讨为什么 Kubernetes 选择了当前的方案,经过什么样的讨论,它背后的架构思想是什么。一方面可以深入理解和学习 Kubernetes,做到”知其所以然”,另外一方面可以学习它的架构思想,在以后自己研发的系统中应用。

基础研发领域,Kubernetes 的架构有非常多的借鉴之处。一方面它的许多设计是 Google 内部多年积累的最佳实践的沉淀,是 Borg,Omega 演进的结果。另外一方面,它是一个综合的系统工程,不是一个单纯的技术工程。系统工程的特点是任何一部分单独抽取出来,貌似都没有太高的技术含量,但它的复杂度在于如何让各个子系统优雅的结合在一起,对外表现出统一的设计思想,而不是堆砌和拼凑,如何设计对用户更友好,如何设计更利于扩展。这也意味着,它的许多设计和选择没有最优解,是一种优劣权衡的结果。而我们设计的大多数系统,都是这类型的系统,所以更值得借鉴。

Kubernetes 本身的架构在这里不细说,网上的资料非常多,如果不熟悉大家检索一下。这里抽几个值得思考和借鉴的点简单分析下。

设计思想之 声明式 VS 命令式

我们大多数系统的设计都是命令式的,或者叫过程式。系统的组件以及层级之间,通过一层一层调用和封装,组成一个大的系统。组件和层级之间都是基本都是强依赖关系,即便是要设计成弱依赖,也是代码显示完成的,比如调用超时后设置个默认值,也只是一种权宜之计。这种方式带来如下问题:

  1. 组件和层级间耦合严重,重构代价高昂,只能依赖工程师的代码抽象能力实现一定程度的解耦。
  2. 系统调用边界不可控,A 调用 B,但不知 B 又依赖什么组件和服务,可能很小一个错误,导致整个大的任务失败,并且很难重试和回滚。

以一个部署系统为例,这个系统需要下载安装包,推送到各服务器上(或者从各服务器上拉取,道理一样),从集群中摘掉该节点,关闭旧的进程,部署,启动新的进程,再加入到集群中对外服务。这个还是一个简化了的流程。这个流程中,任何一步都可能出现失败,超时等问题。如果是通过调用的方式完成,就需要有一个总的超时时间,然后各步骤也需要超时,重试机制,很难有一个优雅的方式处理。如果其中一步出错,重试几次后也失败,只能让整个任务失败,进行人工排查错误,如果贸然重试任务可能导致状态不一致。

而 Kubernetes 通过声明式方式实现的 Controller,则比较优雅的解决了这个问题。每个组件都只需要和 ETCD 这样的状态中心交互,完成自己的任务后把状态更新到 ETCD 中即可。如果失败则反复重试,直到失败条件解除。虽然任务的完成时间不可控,但可达到最终一致。

如果想进一步了解可以深入学习 Kubernetes 的 Pod,Service,ReplicaSet,Deployment,Autoscaling 不同组件之间的关系以及 Controller 的实现。

当然,申明式设计并不适合所有的系统,可以思考下哪些场景下比较适合这种设计。

思考:为什么 Kubernetes 一直没提供重启 Pod 的接口?大家普遍的概念里,一个部署系统,重启应用程序是一个必备功能。参看这个从 15 年开始讨论的 Issue https://github.com/kubernetes/kubernetes/issues/13488

如何设计出通用的 IAM 机制?

IAM(Identity and Access Management) 是所有企业级应用服务平台必须要考虑的功能之一。本想通过和各公有云的 IAM 机制的比较,来解析 Kubernetes 对安全认证机制的抽象和实现,学习和思考如何在自己的系统中设计出通用的 IAM 机制,但这里受限于篇幅,就简单分析下。

IAM 要解决的关键问题是:

  1. 身份认证:通过什么凭证来证明自己的身份,比如 用户名密码,Token,证书等。
  2. 访问权限控制:有哪些资源,不同的资源有什么操作(对应权限),如何定义权限集合,又如何和用户关联。

为了方便定义权限集合,一般需要引入 Role 的概念,一个 Role 对应一个权限集合,另外为了方便批量操作,会引入 Group 的概念把用户做一个集合。这个大体就是 Kubernetes RBAC(Role-based access control)的设计思路。

IAM 需要解决的另外一个问题是服务器端调用的身份认证问题。终端用户使用时,可以通过通用的身份凭证来校验用户,但如果两个系统之间需要互相调用呢?一种办法是创建一些系统账号,给比较高的权限,系统之前调用也是使用和终端用户一样的认证机制。带来的问题是这些账号的维护以及凭证的管理,密码等凭证泄露可能带来安全隐患。参考最近的某酒店拖库事件,最直接的原因是数据库地址以及账号密码被误提交到 GitHub,但根本原因是服务之间互相认证的凭证管理问题。

所以 Kubernetes 引入了一个 ServiceAccount 的机制。部署应用的时候,在配置文件中声明该应用拥有某 ServiceAccount 权限,这样 Kubernetes 会自动在容器中生成并挂载 ServiceAccount 的凭证,该应用通过标准 SDK 调用 Kubernetes 的接口的时候会自动使用该凭证,应用中无需配置身份凭证信息。

这个和 AWS 的 AWS Role(AWS IAM 中的 Role 和通常的理解不太一样),GCE ServiceAccount 机制类似,有兴趣的朋友可以深入调研一下。

思考:

  1. 为什么 Kubernetes 从最初的 ABAC(Attribute-based access control)演进到了 RBAC?
  2. 很多云服务将资源和用户账号关联,主要是为了方便权限控制以及计费。但企业场景下,由于员工离职等原因,可能需要在账号间迁移资源。如何设计 IAM,可以使这种迁移更便捷?

如何通过设计让资源提供方和使用方解耦?

我们的系统中总有许多依赖外部资源的地方,不同的环境,外部资源的供给方式不同,如何通过抽象,让资源供给放对使用方透明?Kubernetes 存储的 Provision 机制是一个典型案例。

Kubernetes 存储的 Provision 机制,将不同云,不同分布式存储系统,提供的存储能力抽象成统一的接口,使用方只需要在配置文件中申明需要多少存储,无需知道具体的资源提供方的信息,由 Kubernetes 系统将需求和供给进行撮合,如果当前供给不足就通过调度系统自动创建新的资源。

这种方式可以最大程度的弥合环境的异构,让应用可以在不同的环境中无缝迁移。

同时 Kubernetes 上的 service-catalog,也通过 Service Broker 的方式给其他服务提供了 Provision 机制。比如应用需要依赖另外一个服务,按照原来的方式,要么应用将依赖服务也一起打包进来,要么让用户手动操作,解决依赖问题。而 Service Broker 的方式是应用只需要声明,如果系统中尚未有类似服务,则会自动创建。

思考:自己开发的应用系统是否有和资源环境的耦合?当前是如何解决的?

复杂系统如何提供自定义机制?

任何一个软件系统本身都是对一种通用模式的沉淀,但任何通用的模式都会遇到特例,环境和场景的变化,新的技术的出现等都会给原有系统带来挑战。这时候设计一种扩展机制,以应对变化成为了必选项。

Kubernetes 提供了以下扩展能力:

  1. Admission Kubernetes 的 Admission 属于其安全系统的一部分,通过插件机制提供了一系列拦截器,以实现自定义的安全策略以及默认属性的注入。可以利用它来设计自己的安全策略以及运维规范。
  2. CloudProvider 给不同的云厂商的扩展
  3. Volume 存储的扩展,和前面说的 Provision 机制相关。
  4. CNI(Container Network Interface)网络扩展,实现不同的网络方案。
  5. CRD(CustomResourceDefinition) Kubernetes 的 CRD 是它的声明式设计的优势集中体现,有了这种扩展能力后,几乎可以把整个 DevOps 平台中涉及的工具统一纳入 Kubernetes 中,通过 Kubernetes 提供统一的控制平面,屏蔽后端工具的差异。

每一种扩展,都有不同的演进历程,当前的成熟度也不同。并且扩展的具体实现方式和编程语言也有关系。Kubernetes 是 Go 实现的,它的动态加载机制还不成熟,所以 Kubernetes 中的扩展大多数都是通过标准化的命令行调用或者 RPC 接口实现的。

思考:列举下自己知道的插件式系统,分析下它们的插件是如何抽象的,如何实现的,不同的编程语言有什么差异。

如何用代码生成机制减少重复代码,提高开发效率?

Kubernetes 中大量使用了代码生成机制,一方面是因为 Kubernetes 是用 go 语言开发的,go 当前不支持泛型,也无法支持像 java 那样的动态字节码生成技术,所以很多情况需要依赖静态代码生成机制。

另外一方面 Kubernetes 的 RESE API 设计的非常优雅,SDK 这种完全可以自动生成。Kubernetes 中的代码生成工具都已经单独抽取成独立的项目了,了解一下它的运作方式,对自己设计和架构系统也很有帮助。

思考:如何设计出便于代码生成的系统?

综合系统如何确定自己的边界?

Kubernetes 是一个综合系统,这样的系统在演化过程中会很容易膨胀成一个无所不能的系统。Kubernetes 又是如何控制自己的膨胀的『欲望』,探索和界定自己的边界的?这个问题是一个值得深入探讨和思考的问题。

后记

Kubernetes 中可以琢磨研究的点实际上很多,上面只是列举了几点,也只是抛出了问题,但并没有给出详细分析。这里推荐一个张磊的 Kubernetes 的课程,《深入剖析 Kubernetes》,看了他的课程目录,非常完备,可以兼顾初学者以及想进一步深入了解 Kubernetes 的朋友。

我已经听了几期,张磊兄从最初的 Docker 诞生以及演进开始分析,有技术,有故事,普通话也很好听,娓娓道来,兼顾趣味性与知识性,即便是老手也能有新的收获。

希望大家在听的过程中,一遍学习 Kubernetes 的特性,一遍思考它的架构。学习的第一境界是学会使用,第二境界是知道它的实现方式,第三境界是可以做到居高临下,把学习到的点用到自己的创造过程中去。

iYVfu2.jpg!web

QvQzi2.png!web

相关链接

  1. Kuberneters 中 Go 的泛型 https://medium.com/@arschles/go-experience-report-generics-in-kubernetes-25da87430301
  2. 深入 Kubernetes 的 CustomResource 代码生成机制 https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK