---
title: 通过 Kubernetes 学架构
date: '2018-09-03'
draft: false
summary: 用 Kubernetes 这套系统反过来训练架构视角。
slug: learning-architecture-by-kubernetes
syndication:
- platform: Weibo
  url: https://weibo.com/1648815335/4280120598286888
tags:
- kubernetes
- architecture
- cloud-native
topics:
- cloud-native
type: post
---

原本打算写一个 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 的特性，一遍思考它的架构。学习的第一境界是学会使用，第二境界是知道它的实现方式，第三境界是可以做到居高临下，把学习到的点用到自己的创造过程中去。

![](./k8s-geekbang.jpeg)

## 相关链接

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/)
