这是K8s入门与实践这个短系列的第三篇文章,也是理论部分的最后一篇,今天我们来聊一聊关于K8s对象的那些事。
是的,K8s 也面向对象
K8s 这个体系也是面向对象的,听起来有没有那么一点点那么颤抖,天天上班、回家都得面向"对象”了,到头来学学云原生基建,结果还得面向对象。不过你反过来想一想,既然都有多年的面向对象经验了,学学面向对象的K8s不还得手拿把攥啊。
在K8s中,对象是系统中持久化的实体,K8s 使用对象来表示集群中各种资源,以及跟踪他们在集群中的状态。
K8s 中有很多种对象,大到集群的节点(Node)、小到配置项 (ConfigMap) 都是对象,这些我们一股脑全都学会也不现实,也没必要,只要我们掌握要领就什么对象都能面向了。
那么有哪些我们作为开发人员会接触到的K8s对象呢,放一张图:
K8s常用对象间的关系
上面这张图就是K8s各个对象之间的关系,上一节我们也提到过,常用的K8s对象有这些:
- Pod
- Deployment
- StatuefulSet
- Service
- DaemonSet
- Ingress
你现在看这个图可能有点懵,不过等看完下面的解释和例子后,再回过来看,对这张图里这些对象他们之间的关系就能有比较好的理解了。
K8s的对象这么多,如果你刚开始学上来就硬记,倒也不是不行哈,毕竟我们都是经过九年义务教育和大学四年考试的临时抱佛脚王者选手,硬记肯定没问题。不过你要硬记就没我什么事儿了,直接叉掉文章走吧哈哈,这里我总结了一个方法,让大家秒懂K8s对象。咱们接着往下看
K8s对象的三要素
我们之前提过Pod是K8s 里的最小调度单元,控制器则是管理 Pod 用的,他们在 K8s 系统中都是通过相应的对象来表示的。要创建 / 更改对象时, 我们只需要把他们的声明文件(YAML) 提交给 K8s 的 API Server,集群就会自动帮我们创建、更新对象。
比如下面这个 Pod 的YAML定义文件:
Pod对象的定义
在这个文件中,我们最能直观地看到的就是它是一个运行Nginx容器的Pod。提交给集群的声明YAML 文件中,会写清楚,对象的类型、元信息、以及预期在集群里预期的状态。这就构成了K8s 对象定义的三要素。
其实应该算四要素,K8s对象的第四要素是 Status,不过这是给K8s集群用的,K8s通过Status来获取资源对象的实时状态,我们在定义K8s对象时使用的是前三个要素,它们分别是:
- Kind : 对象种类
- metadata:对象的元信息, 名字、标签、uid、所在的命名空间等等
- spec:技术规范或者叫技术说明,描述对象的技术细节、各个资源要设置的细节不一样,以及提交者者期望对象达到的理想状态。PS:所有预期状态的定义都是声明式的(Declarative)的而不是命令式(Imperative),在分布式系统中的好处是稳定,不怕丢操作或执行多次。比如设定期望 3 个运行 Nginx 的Pod,执行多次也还是一个结果,而给副本数加1的操作就不是声明式的,执行多次结果就错了
这三个元素在上面 Pod 对象的定义里我们都能看到,这下对对象的理解是不是清晰了一点。
我们再来看一下控制器 Deployment 的定义,Deployment 是创建、滚动更新 Pod 的控制器,文章后面会细讲,在它的对象定义里我们同样能看到刚才提到的三要素。
Deployment对象的定义
下面我们来学习一下K8s中经常会用到的控制器对象
常用的控制器对象
控制器对象的通用模式
K8s 中能经常被我们用到的控制器对象有下面这些:
- Deployment
- StatuefulSet
- Service
- DaemonSet
- Ingress
K8s 本身提供的控制器远不止这些,他们都遵循 K8s 项目中的一个通用编排模式,即:控制循环(control loop),有的资料里解释K8s是做容器编排的(当然K8s远不止这个单一功能),这个编排就是由这些控制器来完成的。
比如,现在有一种待编排的对象 X,它有一个对应的控制器。那么,可以用一段 Go 语言风格的伪代码,为你描述这个控制循环:
// 这段内容参考自极客时间《深入剖析Kubernetes》
for {
实际状态 := 获取集群中对象X的实际状态(Actual State)
期望状态 := 获取集群中对象X的期望状态(Desired State)
if 实际状态 == 期望状态{
什么都不做
} else {
执行编排动作,将实际状态调整为期望状态
}
}
Deployment
Deployment 控制器用来管理无状态应用的,创建、水平扩容/缩容、滚动更新、健康检查等。为啥叫无状态应用呢,就是它的创建和滚动更新是不保证顺序的,这个特征就特别适合管控运行着 Web 服务的 Pod, 因为一个 Web 服务的重启、更新并不依赖副本的顺序。不像 MySQL 这样的服务,肯定是要先启动主节点再启动从节点才行。
那种情况我们应该用 StatefulSet 控制器。
Deployment 是一个复合型的控制器,它包装了一个叫做 ReplicaSet -- 副本集的控制器。ReplicaSet 管理正在运行的Pod数量,Deployment 在其之上实现 Pod 滚动更新,对Pod的运行状况进行健康检查以及回滚更新的能力。他们三者之间的关系可以用下面这张图表示。
Deployment、ReplicaSet和Pod之间的关系
回滚更新是 Deployment 在内部记录了 ReplicaSet 每个版本的对象,要回滚就直接把生效的版本切回原来的ReplicaSet 对象
ReplicaSet 和 Pod 的定义其实是包含在 Deployment 对象的定义中的,我们再把上一个 Deployment 定义的例子拿过来看一下。
Deployment对象的定义
定义文件里的replicas: 3
代表的就是我期望一个拥有三个副本 Pod 的副本集,而template
这个 YAML 定义也叫做Pod模板,意思就是副本集的Pod,要按照这个样板创建出来。
所以这里有一点需要注意,虽然我们知道了 Pod 是K8s 里最小的调度单元,ReplicaSet 控制器可以控制 Pod 的可用数量始终保持在想要数量。但是在 K8s 里我们却不应直接定义和操作他们俩。对这两种对象的所有操作都应该通过 Deployment 来执行。这么做最主要的好处是能控制 Pod 的滚动更新。
Deployment 里边有很多配置允许我们制定 Pod 的滚动更新,比如允许在更新期间多创建出来多少个副本,以及最多容忍多少个副本不可用等等。这个滚动更新我们就先不细究了,只要先记住一点,Deployment 对Pod 的滚动更新是先创建新Pod, 逐步的用新创建的 Pod 替换掉老版本的 Pod。
这里给大家来个动图演示一下 Deployment 对 Pod 的滚动更新。
Pod的滚动更新过程
关于 Deployment 对Pod进行的滚动更新、健康检查等功能的详细配置可以参考我之前写的文章:
- 玩转K8s Pod的滚动更新
- 浅析K8s Pod 的重启和健康检查策略
StatefulSet
StatefulSet,是在Deployment的基础上扩展出来的控制器。使用Deployment时多数时候你不会在意Pod的调度方式。但当你需要调度有拓扑状态的应用时,就需要关心Pod的部署顺序、对应的持久化存储、 Pod 在集群内拥有固定的网络标识(即使重启或者重新调度后也不会变)这些文图,这个时候,就需要 StatefulSet 控制器实现调度目标。
Web 服务一般不用这个控制器部署, 关于 StatefulSet 的内容,请阅读我之前专门写的StatefulSet文章。
- 深入理解StatefulSet,用Kubernetes编排有状态应用
Service
Service 是另一个我们必用到的控制器对象,因为在K8s 里 Pod 的 IP 是不固定的,所以 K8s 通过 Service 对象向应用程序的客户端提供一个静态/稳定的网络地址,另外因为应用程序往往是由多个Pod 副本构成, Service还可以为它负责的 Pod 提供负载均衡的功能。
每个 Service 都具有一个ClusterIP和一个可以解析为该IP的DNS名,并且由这个 ClusterIP 向 Pod 提供负载均衡。
Service 控制器也是靠着 Pod 的标签在集群里筛选到自己要代理的 Pod,被选中的 Pod 叫做 Service 的端点(EndPoint)
看一下 Service 对象定义的模板:
Svc对象的定义模版
K8s 中提供了好几种类似的Service,他们分别适用于不同场景,下面介绍两个最常用的Service,更多类型的Service 和他们的实现原理可以参考我之前的文章:
- 学练结合,快速掌握Kubernetes Service
ClusterIP Service
这是默认的Service类型,会将Service对象通过一个内部IP暴露给集群内部,这种类型的Service只能够在集群内部使用<ClusterIP>:<port>
的形式访问。
集群内使用的ClusterIp Service
NodePort Service
会在每个 Node 的一个指定的固定端口上暴露Service,与此同时还会自动创建一个ClusterIP类型的Service,NodePort类型的Service会将集群外部的请求路由给ClusterIP类型的Service。
使用<NodeIP>:<NodePort>
访问NodePort类型的Service,NodePort的端口范围为30000-32767。
向集群外暴露的NodePort类型的Service
NodePort 类型的Service 是向集群外暴露服务的最原始方式,也是最好让人理解的。优点是简单,好理解,通过IP+端口的方式就能访问,不过它的缺点也很明显。
- 每向外暴露一个服务都要占用所有Node的一个端口,如果多了难以管理。
- NodePort的端口区间固定,只能使用30000–32767间的端口。
- 如果Node的IP发生改变,负载均衡代理需要跟着改后端端点IP才行。
正因为有这些缺点,K8s才提供了 Ingress,诶嘛好累,终于到本文要介绍的最后一个控制器对象啦。
Ingress
Ingress 在 K8s 集群里的角色是给 Service 充当反向代理。它可以位于多个 Service 的前端,给这些 Service 充当“智能路由”或者集群的入口点。
Ingress工作原理
使用 Ingress 对象前需要先安装 Ingress-Controller, 像阿里云、亚马逊 AWS 他们的 K8s 企业服务都会提供自己的Controller ,对于自己搭建的集群,通常使用nginx-ingress作为控制器,它使用 NGINX 服务器作为反向代理,访问 Ingress 的流量按规则路由给集群内部的Service。
下面看一下 Ingress 的配置
Ingress对象的定义
正常的生产环境,因为Ingress是公网的流量入口,所以压力比较大肯定需要多机部署。一般会在集群里单独出几台Node,只用来跑Ingress-Controller,可以使用deamonSet的让节点创建时就安装上Ingress-Controller,在这些Node上层再做一层负载均衡,把域名的DNS解析到负载均衡IP上。
关于Ingress更多概念和实操可以访问之前的文章:
- 在K8S上的Web服务该怎么做域名解析呢?
DaemonSet
这个控制器不常用,主要保证每个 Node上 都有且只有一个 DaemonSet 指定的 Pod 在运行。当然可以定义多个不同的 DaemonSet 来运行各种基础软件的 Pod。
比如新建节点的网络配置、或者是每个节点上收集日志的组件往往是靠 DaemonSet 来保证的, 他会在集群创建时优先于其他组件执行, 为的是做好集群的基础设施建设。
关于DaemonSet的详细介绍,我也写过文章,请参考:
- DaemonSet--K8s系统的"硬装"师傅
总结
这篇文章从K8s面向对象的思路开始,提供了我们掌握它的众多对象的三要素,紧接着我们通过一个Nginx 服务的简单例子把我们日常能用到的K8s对象捋了一遍。
不过文章里我尽量通过让大家更容易理解的方式快速过了一下他们的概念,如果以后真上手开始构建自己的服务了,需要把我在每部分里给出的参考文章好好看看,这样才能构建一个能在生产上稳定运行的服务。
终于花了三篇文章,把K8s入门的理论知识给大家讲清楚了,后面还有两节,咱们面向实践,把这里学到的理论知识再应用一遍。我会分别写一个Go服务和SpringBoot服务,从零开始怎么从写代码到最后把他们部署到K8s上运行的详细过程,后面多多期待吧。
本文的示例已经上传我自己维护的K8s学习仓库,给公众号「网管叨bi叨」发私信k8s,即可获得地址,页面上搜入门就能找到这个入门系列的所有示例。如果找不到的话也别着急,发信息给我,等摸鱼的时候回你。