- Kubernetes 入门
- Kubernetes 是什么
首先,它是一个全新的基于容器技术的分布式架构领先方案。这个方案虽然还很新,但它 是谷歌十几年以来大规模应用容器技术的经验积累和升华的一个重要成果。确切地说,Kubernetes 是谷歌严格保密十几年的秘密武器–Borg的一个开源版本。Borg是谷歌的一个久负盛名的内部使用的大规模集群管理系统,它基于容器技术,目的是实现资源管理的自动化,以及跨多个数据中心的资源利用率的最大化。十几年来,谷歌一直通过Borg系统管理着数量庞大的应用程序集群。由于谷歌员工都签署了保密协议,即便离职也不能泄露 Borg 的内部设计,所以外界一直无法了解关于它的更多信息。直到2015年4月,传闻许久的 Borg 论文伴随kubernetes的高调宣传被谷歌首次公开,大家才得以了解它的更多内幕。正是由于站在Borg 这个前辈的肩膀上,吸取了Borg 过去十年间的经验与教训,所以Kubernetes 经开源就一鸣惊人,并迅速称霸了容器技术领域。
其次,如果我们的系统设计遵循了kubernetes的设计思想,那么传统系统架构中那些和业 务没有多大关系的底层代码或功能模块,都可以立刻从我们的视线中消失,我们不必再费心于 负载均衡器的选型和部署实施问题,不必再考虑引入或自己开发一个复杂的服务治理框架,不必再头疼于服务监控和故障处理模块的开发。总之,使用kubernetes提供的解决方案,我们不 仅节省了不少于 30% 的开发成本,同时可以将精力更加集中于业务本身,而且由于kubernetes提供了强大的自动化机制,所以系统后期的运维难度和运维成本大幅度降低。
然后,Kubemetes是一个开放的开发平台。与J2EE 不同,它不局限于任何一种语言,没有限定任何编程接口,所以不论是用 Java、Go、C++还是用 Python 编写的服务,都可以毫无困难 地映射为Kubernetes的 Service,并通过标准的 TCP 通信协议进行交互。此外,由于Kubernetes平台对现有的编程语言、编程框架、中间件没有任何侵入性,因此现有的系统也很容易改造升 级井迁移到Kubernetes平台上。
最后,Kubernetes是一个完备的分布式系统支撑平台。Kubernetes具有完备的集群管理能力, 包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、 内建智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩 展的资源自动调度机制,以及多粒度的资源配额管理能力。同时,Kubernetes提供了完善的管 理工具,这些工具涵盖了包括开发、部署测试、运维监控在内的各个环节。因此,Kubernetes是一个全新的基于容器技术的分布式架构解决方案,并且是一个一站式的完备的分布式系统开发和支撑平台。
在正式开始本章的 Hello World 之旅之前,我们首先要学习Kubernetes的一些基本知识,这 样我们才能理解Kubernetes提供的解决方案。
在Kubernetes中,Service(服务)是分布式集群架构的核心,一个Service对象拥有如下关 键特征。
- 拥有一个唯一指定的名字(比如 mysql-server)。
- 拥有一个虚拟 IP C Cluster IP、Service IP 或 VIP)和端口号。
- 能够提供某种远程服务能力。
- 被映射到了提供这种服务能力的一组容器应用上。
Service 的服务进程目前都基于 Socket 通信方式对外提供服务,比如 Redis、Memcac挝、 MySQL、Web Server,或者是实现了某个具体业务的一个特定的 TCP Server 进程。虽然一个Service通常由多个相关的服务进程来提供服务,每个服务进程都有一个独立的 Endpoint CIP+Port)访问点,但Kubernetes能够让我们通过Service(虚拟 Cluster IP +Service Port)连接 到指定的Service上。有了Kubernetes内建的透明负载均衡和故障恢复机制,不管后端有多少服 务进程,也不管某个服务进程是否会由于发生故障而重新部署到其他机器,都不会影响到我们 对服务的正常调用。更重要的是这个Service本身一旦创建就不再变化,这意味着,在Kubernetes集群中,我们再也不用为了服务的 IP 地址变来变去的问题而头疼了。
容器提供了强大的隔离功能,所以有必要把为Service提供服务的这组进程放入容器中进行 隔离。为此,Kubernetes设计了Pod对象,将每个服务进程包装到相应的Pod中,使其成为Pod中运行的二个容器(Container)。为了建立Service 和Pod间的关联关系,Kubernetes 首先给每 个Pod贴上一个标签(Label),给运行 MySQL的Pod贴上 name=mysql 标签,给运行 PHP 的Pod贴上 name=php 标签,然后给相应的Service 定义标签选择器(Label Selector),比如MySQLService的标签选择器的选择条件为 name=mysql,意为该Service要作用于所有包含 name=mysql Label的Pod上。这样一来,就巧妙地解决了Service 与Pod的关联问题。
说到 Pod,我们这里先简单介绍其概念。首先,Pod运行在一个我们称之为节点(Node )的环境中,这个节点既可以是物理机,也可以是私有云或者公有云中的一个虚拟机,通常在一 个节点上运行几百个Pod:其次,每个Pod里运行着一个特殊的被称之为 Pause 的容器,其他 容器则为业务容器,这些业务容器共享Pause 容器的网络技和 Volume 挂载卷,因此它们之间的 通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放 入同一个Pod中;最后,需要注意的是,并不是每个Pod和它里面运行的容器都能 “映射” 到 一个Service上,只有那些提供服务(无论是对内还是对外)的一组Pod才会被 “映射” 成一个 服务。
在集群管理方面,Kubernetes将集群中的机器划分为一个 Master 节点和一群工作节点(Node)。 其中,在 Master 节点上运行着集群管理相关的一组进程 kube-apiserver、kube-controller-manager 和 kube-scheduler,这些进程实现了整个集群的资源管理、Pod调度、弹性伸缩、安全控制、系 统监控和纠错等管理功能,并且都是全自动完成的。Node 作为集群中的工作节点,运行真正的 应用程序,在 Node 上Kubernetes管理的最小运行单元是Pod。Node 上运行着Kubernetes的 kubelet、kube-proxy 服务进程,这些服务进程负责Pod的创建、启动、监控、重启、销毁,以 及实现软件模式的负载均衡器。
最后,我们再来看看传统的 IT 系统中服务扩容和服务升级这两个难题,以及Kubernetes所提供的全新解决思路。服务的扩容涉及资源分配(选择哪个节点进行扩容)、实例部署和启动 等环节,在一个复杂的业务系统中,这两个问题基本上靠人工一步步操作才得以完成,费时费 力又难以保证实施质量。
在Kubernetes集群中,你只需为需要扩容的Service关联的Pod创建一个 Replication Controller(简称 RC),则该Service的扩容以至于后来的Service升级等头疼问题都迎刃而解。 在一个 RC 定义文件中包括以下 3 个关键信息。
- 目标Pod的定义。
- 目标Pod需要运行的副本数量(Replicas)。
- 要监控的目标Pod的标签(Label)
在创建好 RC(系统将自动创建好 Pod)后,Kubernetes会通过 RC 中定义的 Label 筛选出 对应的Pod实例并实时监控其状态和数量,如果实例数量少于定义的副本数量(Replicas),则 会根据 RC 中定义的Pod模板来创建一个新的 Pod,然后将此Pod调度到合适的 Node 上启动运 行,直到 Pod实例的数量达到预定目标。这个过程完全是自动化的 ,无须人工干预。有了 RC,服务的扩容就变成了一个纯粹的简单数字游戏了,只要修改 RC 中的副本数量即可。后续的Service 升级也将通过修改 RC 来自动完成。
以将在第 2 章介绍的 PHP+Redis 留言板应用为例,只要为 PHP 留言板程序(frontend)创 建一个有 3 个副本的 RC+Service,为 Redis 读写分离集群创建两个RC:写节点(redis-master)创建一个单副本的 RC+Service,读节点(redis-slaver)创建一个有两个副本的 RC+Service,就 可以分分钟完成整个集群的搭建过程了。
- 为什么要用 Kubernetes
使用 Kubernetes 的理由很多,最根本的一个理由就是 :IT 从来都是一个由新技术驱动的 行业。
Docker 这个新兴的容器化技术当前己经被很多公司所采用 ,其从单机走向集群己成为必然 , 而云计算的蓬勃发展正在加速这一进程 。Kubernetes 作为当前唯一被业界广泛认可和看好的 Docker 分布式系统解决方案 ,可以预见,在未来几年内 ,会有大量的新系统选择它 ,不管这些 系统是运行在企业本地服务器上还是被托管到公有云上 。
使用了 Kubernetes 又会收获哪些好处呢 ?
首先,最直接的感受就是我们可以 “轻装上阵” 地开发复杂系统了 。以前动不动就需要十 几个人而且团队里需要不少技术达人一起分工协作才能设计实现和运维的分布式系统 ,在采用 Kubernetes 解决方案之后 ,只需一个精悍的小团队就能轻松应对 。在这个团队里 ,一名架构师 专注于系统中 “服务组件” 的提炼,几名开发工程师专注于业务代码的开发 ,一名系统兼运维 工程师负责 Kubernetes 的部署和运维,从此再也不用“ 996 ” 了,这并不是因为我们少做了什么 , 而是因为 Kubernetes 己经帮我们做了很多。
其次,使用 Kubernetes 就是在全面拥抱微服务架构 。微服务架构的核心是将一个巨大的单 体应用分解为很多小的互相连接的微服务 ,一个微服务背后可能有多个实例副本在支撑 ,副本 的数量可能会随着系统的负荷变化而进行调整 ,内嵌的负载均衡器在这里发挥了重要作用 。微 服务架构使得每个服务都可以由专门的开发团队来开发 ,开发者可以自由选择开发技术 ,这对 于大规模团队来说很有价值 ,另外每个微服务独立开发 、升级、扩展,因此系统具备很高的稳 定性和快速迭代进化能力 。谷歌、亚马逊、eBay 、NetFlix 等众多大型互联网公司都采用了微服 务架构,此次谷歌更是将微服务架构的基础设施直接打包到 Kubernetes 解决方案中 ,让我们有 机会直接应用微服务架构解决复杂业务系统的架构问题 。
然后,我们的系统可以随时随地整体 “搬迁” 到公有云上 。Kubernetes 最初的目标就是运行在谷歌自家的公有云 GCE 中,未来会支持更多的公有云及基于 OpenStack 的私有云。同时, 在 Kubemetes 的架构方案中,底层网络的细节完全被屏蔽 ,基于服务的Cluster IP 甚至都无须我 们改变运行期的配置文件 ,就能将系统从物理机环境中无缝迁移到公有云中 ,或者在服务高峰 期将部分服务对应的 Pod 副本放入公有云中以提升系统的吞吐量 ,不仅节省了公司的硬件投入, 还大大改善了客户体验 。我们所熟知的铁道部的 12306 购票系统,在春节高峰期就租用了阿里 云进行分流 。
最后,Kubemetes 系统架构具备了超强的横向扩容能力 。对于互联网公司来说 ,用户规模就 等价于资产 ,谁拥有更多的用户 ,谁就能在竞争中胜出 ,因此超强的横向扩容能力是互联网业 务系统的关键指标之一 。不用修改代码 ,一个 Kubemetes 集群即可从只包含几个 Node 的小集 群平滑扩展到拥有上百个 Node 的大规模集群 ,我们利用 Kubemetes 提供的工具 ,甚至可以在 线完成集群扩容 。只要我们的微服务设计得好 ,结合硬件或者公有云资源的线性增加 ,系统就 能够承受大量用户并发访问所带来的巨大压力 。
- 从一个简单的例子开始
考虑到本书第 1 版中的 PHP+Redis 留言板的 Hello World 例子对于绝大多数刚接触 Kubemetes 的人来说比较复杂,难以顺利上子和实践 ,所以我们在此将这个例子替换成一个简 单得多的 Java Web 应用,可以让新手快速上手和实践 。
此 Java Web 应用的结构比较简单 ,是一个运行在Tomcat 里的 Web App ,如图1.1 所示,JSP 页面通过 JDBC 直接访问 MySQL 数据库并展示数据 。为了演示和简化的目的 ,只要程序正确 连接到了数据库上 ,它就会自动完成对应的 Table 的创建与初始化数据的准备工作 。所以,当 我们通过浏览器访问此应用的时候 ,就会显示一个表格的页面 ,数据则来自数据库 。
此应用需要启动两个容器 :Web App 容器和 MySQL 容器,并且 Web App 容器需要访问 MySQL 容器。在 Docker 时代,假设我们在一个宿主机上启动了这两个容器 ,则我们需要把 MySQL 容器的 IP 地址通过环境变量的方式注入 Web App 容器里:同时,需要将Web App 容器 的 8080 端口映射到宿主机的 8080 端口,以便能在外部访问 。在本章的这个例子里 ,我们看看 在 Kubemetes 时代是如何完成这个目标的 。
- 环境准备
首先 ,我们开始准备 Kubemetes 的安装和相关镜像下载 ,本书建议采用 VirtualBox 或者 VMware Workstation 在本机虚拟一个 64 位的 CentOS 7 虚拟机作为学习环境 ,虚拟机采用 NAT 的网络模式以便能够连接外网 ,然后按照以下步骤快速安装 Kubemetes。
- 关闭 CentOS 自带的防火墙服务
systemctl stop firewalld
systemctl disable firewalld
- 安装 etcd 和 Kubemetes 软件 (会自动安装 Docker 软件)
yum install -y etcd kubernetes
- 修改配置
安装好软件后 ,修改两个配置文件 (其他配置文件使用系统默认的配置参数即可) 。
Docker配置文件为/etc/sysconfig/docker ,其中 OPTIONS的内容设置为:
OPTIONS=’–selinux-enabled=false –insecure-registry gcr.io’
Kubemetes apiserver 配置文件为/etc/kubernetes/apiserver ,把–admission_control 参数中的 ServiceAccount删除。
- 按顺序启动所有的服务
systemctl start etcd
systemctl start docker
systemctl start kube-apiserver
systemctl start kube-controller-manager
systemctl start kube-scheduler
systemctl start kubelet
systemctl start kube-proxy
至此,一个单机版的 Kubemetes 集群环境就安装启动完成了 。 接下来 ,我们可以在这个单机版的 Kubemetes 集群中上子练习了 。
注:本书示例中的 Docker 镜像下载地址为 https://hub.docker.com/u/kubeguide/
- 启动 MySQL 服务
首先为 MySQL 服务创建一个 RC 定义文件:mysql-rc.yaml ,下面给出了该文件的完整内容:
apiVersion : v1
kind : ReplicationController
metadata :
name : mysql
spec :
replicas : 1
selector :
app: mysql
template :
metadata :
labels :
app : mysql
spec:
containers :
– name : mysql
image : mysql
ports:
– containerPort : 3306
env:
– name : MYSQL_ROOT_PASSWORD
value : “123456”
yaml 定义文件中 的 kind 属 性 ,用 来表明此资 源对象的类型 ,比如这 里 的值为 “ReplicationController ”,表示这是一个 RC; spec 一节中是 RC 的相关属性定义,比如spec.selector 是 RC 的Pod 标签 ( Label) 选择器,即监控和管理拥有这些标签的 Pod 实例,确保当前集群上 始终有且仅有 replicas 个 Pod 实例在运行 ,这里我们设置 replicas=l 表示只能运行一个 MySQL Pod 实例。当集群中运行的Pod 数量小于 replicas 时,RC 会根据spec.template 一节中定义的 Pod 模板来生成一个新的 Pod 实例,spec.template.metadata. labels 指定了该 Pod 的标签,需要特别注 意的是:这里的labels 必须匹配之前的spec.selector ,否则此RC 每次创建了一个无法匹配 Label 的 Pod ,就会不停地尝试创建新的 Pod ,最终陷入 “只为他人做嫁衣” 的悲惨世界中 ,永无翻身之时
创建好 redis-master-controller. yaml 文件以后 ,为了将它发布到 Kubernetes 集群中 ,我们在Master 节点执行命令 :
创建好mysql-rc.yaml文件以后 ,为了将它发布到 Kubernetes 集群中 ,我们在Master 节点执行命令 :
[root@localhost kubernetes]# kubectl create -f mysql-rc.yaml
replicationcontroller “mysql” created
接下来 ,我们用 kubectl 命令查看刚刚创建的 RC:
[root@localhost kubernetes]# kubectl get rc
NAME DESIRED CURRENT READY AGE
mysql 1 1 0 1m
查看 Pod 的创建情况时 ,可以运行下面的命令
[root@localhost kubernetes]# kubectl get pods
NAME READY STATUS RESTARTS AGE
mysql-nl3mb 0/1 ContainerCreating 0 1m
我们看到一个名为 mysql-xxxxx 的 Pod 实例,这是 Kubemetes 根据 mysql 这个 RC 的定义 自动创建的 Pod 。由于 Pod 的调度和创建需要花费一定的时间 ,比如需要一定的时间来确定调 度到哪个节点上 ,以及下载 Pod 里容器的镜像需要一段时间 ,所以 开始我们看到 Pod 的状态 将显示为 Pending 。当 Pod 成功创建完成以后,状态最终会被更新为 Running 。
我们通过 docker ps 指令查看正在运行的容器 ,发现提供 MySQL 服务的 Pod 容器己经创建 并正常运行了,此外,你会发现 MySQL Pod 对应的容器还多创建了一个来自谷歌的 pause 容器, 这就是 Pod 的 “根容器”,详见后续说明 。
最后,我们创建一个与之关联的Kubemetes Service–MySQL的定义文件 ( 文件名为mysql-svc.yaml ) ,完整的内容和解释如下所示。
apiVersion : vl
kind: Service
metadata :
name : mysql
spec:
ports :
– port : 3306
selector :
app : mysql
其中,metadata.name 是 Service 的服务名 ( ServiceName ) ; port 属性则定义了 Service 的虚 端口;spec.selector 确定了哪些 Pod 副本 ( 实例) 对应到本服务 。类似地,我们通过 kubectl create 命令创建 Service 对象。
运行 kubectl 命令 ,创建 service:
kubectl create -f mysql-svc.yaml
运行 kubectl 命令,可以查看到刚刚创建的 service:
kubectl get svc
注意到 MySQL 服务被分配了一个值为 169.169.253.143 的Cluster IP 地址,这是一个虚地址 , 随后,Kubernetes 集群中其他新创建的 Pod 就可以通过 Service 的 Cluster IP+端口号 6379 来连 接和访问它了 。
在通常情况下 ,Cluster IP 是在 Service 创建后由 Kubernetes 系统自动分配的 ,其他 Pod 无 法预先知道某个 Service 的 Cluster IP 地址,因此需要一个服务发现机制来找到这个服务。为此, 最初的时候 ,Kubernetes 巧妙地使用了 Linux 环境变量 ( Environment Variable ) 来解决这个问题 , 后面会详细说明其机制 。现在我们只需知道,根据 Service 的唯一名字,容器可以从环境变量中 获取到 Service 对应的 Cluster IP 地址和端口 ,从而发起 TCP/IP 连接请求了 。
- k8s设计文档
- kubernetes 架构
Kubernetes最初源于谷歌内部的Borg,提供了面向应用的容器集群部署和管理系统。Kubernetes 的目标旨在消除编排物理/虚拟计算,网络和存储基础设施的负担,并使应用程序运营商和开发人员完全将重点放在以容器为中心的原语上进行自助运营。Kubernetes 也提供稳定、兼容的基础(平台),用于构建定制化的workflows 和更高级的自动化任务。
Kubernetes 具备完善的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建负载均衡器、故障发现和自我修复能力、服务滚动升级和在线扩容、可扩展的资源自动调度机制、多粒度的资源配额管理能力。
Kubernetes 还提供完善的管理工具,涵盖开发、部署测试、运维监控等各个环节。
- Borg简介
Borg是谷歌内部的大规模集群管理系统,负责对谷歌内部很多核心服务的调度和管理。Borg的目的是让用户能够不必操心资源管理的问题,让他们专注于自己的核心业务,并且做到跨多个数据中心的资源利用率最大化。
Borg主要由BorgMaster、Borglet、borgcfg和Scheduler组成,如下图所示
- BorgMaster是整个集群的大脑,负责维护整个集群的状态,并将数据持久化到Paxos存储中。
- Scheduer负责任务的调度,根据应用的特点将其调度到具体的机器上去。
- Borglet负责真正运行任务(在容器中)。
- borgcfg是Borg的命令行工具,用于跟Borg系统交互,一般通过一个配置文件来提交任务。
- kubernetes架构图及组件
kubernetes借鉴了Borg的设计理念,比如Pod、Service、Labels和单Pod单IP等。Kubernetes的整体架构跟Borg非常像,如下图所示
Kubernetes主要由以下几个核心组件组成:
- etcd保存了整个集群的状态;
- apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
- controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
- scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
- kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
- Container runtime负责镜像管理以及Pod和容器的真正运行(CRI);
- kube-proxy负责为Service提供cluster内部的服务发现和负载均衡;
除了核心组件,还有一些推荐的Add-ons:
- kube-dns负责为整个集群提供DNS服务
- Ingress Controller为服务提供外网入口
- Heapster提供资源监控
- Dashboard提供GUI
- Federation提供跨可用区的集群
- Fluentd-elasticsearch提供集群日志采集、存储与查询
- kubernetes分层架构
Kubernetes设计理念和功能其实就是一个类似Linux的分层架构,如下图所示
- 核心层:Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境
- 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS解析等)
- 管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等)
- 接口层:kubectl命令行工具、客户端SDK以及集群联邦
- 生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴
- Kubernetes外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS应用、ChatOps等
- Kubernetes内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等
- Kubernetes设计理念
分析和理解Kubernetes的设计理念可以使我们更深入地了解Kubernetes系统,更好地利用它管理分布式部署的云原生应用,另一方面也可以让我们借鉴其在分布式系统设计方面的经验。
- API设计原则
- 对于云计算系统,系统API实际上处于系统设计的统领地位,正如本文前面所说,K8s集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,支持对该功能的管理操作,理解掌握的API,就好比抓住了K8s系统的牛鼻子。K8s系统API的设计有以下几条原则:
- 所有API应该是声明式的。正如前文所说,声明式的操作,相对于命令式操作,对于重复操作的效果是稳定的,这对于容易出现数据丢失或重复的分布式环境来说是很重要的。另外,声明式操作更容易被用户使用,可以使系统向用户隐藏实现的细节,隐藏实现的细节的同时,也就保留了系统未来持续优化的可能性。此外,声明式的API,同时隐含了所有的API对象都是名词性质的,例如Service、Volume这些API都是名词,这些名词描述了用户所期望得到的一个目标分布式对象。
- API对象是彼此互补而且可组合的。这里面实际是鼓励API对象尽量实现面向对象设计时的要求,即“高内聚,松耦合”,对业务相关的概念有一个合适的分解,提高分解出来的对象的可重用性。事实上,K8s这种分布式系统管理平台,也是一种业务系统,只不过它的业务就是调度和管理容器服务。
- 高层API以操作意图为基础设计。如何能够设计好API,跟如何能用面向对象的方法设计好应用系统有相通的地方,高层设计一定是从业务出发,而不是过早的从技术实现出发。因此,针对K8s的高层API设计,一定是以K8s的业务为基础出发,也就是以系统调度管理容器的操作意图为基础设计。
- 低层API根据高层API的控制需要设计。设计实现低层API的目的,是为了被高层API使用,考虑减少冗余、提高重用性的目的,低层API的设计也要以需求为基础,要尽量抵抗受技术实现影响的诱惑。
- 尽量避免简单封装,不要有在外部API无法显式知道的内部隐藏的机制。简单的封装,实际没有提供新的功能,反而增加了对所封装API的依赖性。内部隐藏的机制也是非常不利于系统维护的设计方式,例如PetSet和ReplicaSet,本来就是两种Pod集合,那么K8s就用不同API对象来定义它们,而不会说只用同一个ReplicaSet,内部通过特殊的算法再来区分这个ReplicaSet是有状态的还是无状态。
- API操作复杂度与对象数量成正比。这一条主要是从系统性能角度考虑,要保证整个系统随着系统规模的扩大,性能不会迅速变慢到无法使用,那么最低的限定就是API的操作复杂度不能超过O(N),N是对象的数量,否则系统就不具备水平伸缩性了。
- API对象状态不能依赖于网络连接状态。由于众所周知,在分布式环境下,网络连接断开是经常发生的事情,因此要保证API对象状态能应对网络的不稳定,API对象的状态就不能依赖于网络连接状态。
- 尽量避免让操作机制依赖于全局状态,因为在分布式系统中要保证全局状态的同步是非常困难的。
- 控制机制设计原则
- 控制逻辑应该只依赖于当前状态。这是为了保证分布式系统的稳定可靠,对于经常出现局部错误的分布式系统,如果控制逻辑只依赖当前状态,那么就非常容易将一个暂时出现故障的系统恢复到正常状态,因为你只要将该系统重置到某个稳定状态,就可以自信的知道系统的所有控制逻辑会开始按照正常方式运行。
- 假设任何错误的可能,并做容错处理。在一个分布式系统中出现局部和临时错误是大概率事件。错误可能来自于物理系统故障,外部系统故障也可能来自于系统自身的代码错误,依靠自己实现的代码不会出错来保证系统稳定其实也是难以实现的,因此要设计对任何可能错误的容错处理。
- 尽量避免复杂状态机,控制逻辑不要依赖无法监控的内部状态。因为分布式系统各个子系统都是不能严格通过程序内部保持同步的,所以如果两个子系统的控制逻辑如果互相有影响,那么子系统就一定要能互相访问到影响控制逻辑的状态,否则,就等同于系统里存在不确定的控制逻辑。
- 假设任何操作都可能被任何操作对象拒绝,甚至被错误解析。由于分布式系统的复杂性以及各子系统的相对独立性,不同子系统经常来自不同的开发团队,所以不能奢望任何操作被另一个子系统以正确的方式处理,要保证出现错误的时候,操作级别的错误不会影响到系统稳定性。
- 每个模块都可以在出错后自动恢复。由于分布式系统中无法保证系统各个模块是始终连接的,因此每个模块要有自我修复的能力,保证不会因为连接不到其他模块而自我崩溃。
- 每个模块都可以在必要时优雅地降级服务。所谓优雅地降级服务,是对系统鲁棒性的要求,即要求在设计实现模块时划分清楚基本功能和高级功能,保证基本功能不会依赖高级功能,这样同时就保证了不会因为高级功能出现故障而导致整个模块崩溃。根据这种理念实现的系统,也更容易快速地增加新的高级功能,以为不必担心引入高级功能影响原有的基本功能。
- Kubernetes的核心技术概念和API对象
API对象是K8s集群中的管理操作单元。K8s集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,支持对该功能的管理操作。例如副本集Replica Set对应的API对象是RS。
每个API对象都有3大类属性:元数据metadata、规范spec和状态status。元数据是用来标识API对象的,每个对象都至少有3个元数据:namespace,name和uid;除此以外还有各种各样的标签labels用来标识和匹配不同的对象,例如用户可以用标签env来标识区分不同的服务部署环境,分别用env=dev、env=testing、env=production来标识开发、测试、生产的不同服务。规范描述了用户期望K8s集群中的分布式系统达到的理想状态(Desired State),例如用户可以通过复制控制器Replication Controller设置期望的Pod副本数为3;status描述了系统实际当前达到的状态(Status),例如系统当前实际的Pod副本数为2;那么复制控制器当前的程序逻辑就是自动启动新的Pod,争取达到副本数为3。
K8s中所有的配置都是通过API对象的spec去设置的,也就是用户通过配置系统的理想状态来改变系统,这是k8s重要设计理念之一,即所有的操作都是声明式(Declarative)的而不是命令式(Imperative)的。声明式操作在分布式系统中的好处是稳定,不怕丢操作或运行多次,例如设置副本数为3的操作运行多次也还是一个结果,而给副本数加1的操作就不是声明式的,运行多次结果就错了。