新世界
最近翻出来了之前买的k8s in action,决定静下心来好好看看学习了解一下,打开书没看多久就有一种打开新世界的大门豁然开朗的,为什么这么说呢,因为之前对于小公司和大公司的开发以及运维都比较了解,开发以及运维的痛点也都比较清楚,我一直觉得开发、运维这两个事情很大层面上来说是一个事情,但现在被拆成了两个事情,初期看还不错,但随着业务发展集群规模增长会发现两者的沟壑越来越大,沟通成本也越来越高,举个例子我开发的服务交给别人来运维,你觉得谁更了解这个服务?谁更能把他管理好?但因为对于基于虚拟机的场景来说,除了本身的服务以外,还包含了vps的管理,环境部署、权限控制、安全配置、网络配置等额外操作,确实没有办法省掉运维这一环。但现在有了k8s之后,一切就逐渐变得不一样了!
码头工人
在介绍k8s之前先简单介绍概括一下docker(码头工人,哈哈),docker是在系统之上的虚拟技术,最早期的时候只有物理机,但由于物理机的种种限制以及资源难以充分利用的问题,后期发展出来了虚拟机,也就是云厂商买的那些云服务器,它很大程度解决了物理机的问题,例如相对便于管理,但依然存在开头说的哪些问题,于是就又迭代出来了docker,其原理是在进程维度,利用系统的cgroup+namespace机制实现在同一个os之上,达到进程层面的隔离,并且针对进程有单独的文件和网络系统虚拟化,能做到更精细化的管控以及资源利用(因为vps的虚拟化一定要比进程维度虚拟化浪费资源),docker在英文里面是搬运工人的意思,一个项目被打包之后的runtime就是容器(container也有集装箱的意思),从此项目运行不需要再做环境初始化等前置动作了,而是直接江container下载下来之后直接run就可以,非常方便。
如果仅仅是有docker的存在,那么相对于原来的vps其实只解决了环境部署和运行以及资源利用的问题,其他很多问题例如扩缩容、调度、监控等操作还是没有变化,优秀的工程师总是善于总结经验解决问题,你看我们操作系统是不是负责进程调度啊?那么既然我们都已经把服务做了进程虚拟化了,是不是也可以搞一套针对容器的调度系统呢?这样就能像把进程托管给操作系统一样把我们的容器托管出去,不用再去关注像扩缩容,调度等问题了?技术领域经过几年的混战,最终k8s横空出世。
集群调度
在k8s之下,开发可以非常低成本的把跟服务相关的运维操作包办了,比如我要多少机器,我需要什么配置,网络连接是怎么样的,七层代理怎么配置,服务监控怎么判断,机器的扩缩容就像编程时对内存的申请和释放一样方便,这样就完全闭环起来了!几乎不涉及到vps的东西,更底层的服务器虚拟机集群都可以屏蔽掉。
一些基本概念
- Pod,k8s不会直接操作docker,集群调度的基本单位是pod(豆荚,这个很形象,一个豌豆荚包含了N个豆子),pod和容器的关系也是1对N的关系
- ReplicaSet,用来管理和确保pod的运行数量
- Deployments,为Pod何ReplicaSet提供声明式的管理能力,负责进行部署、滚动更新,生产环境中主要使用的类型(也有其他开源的强化组件例如CloneSet)
- StatefulSet,与Deployments类似,区别就是名称描述所说,用来管理有状态的服务类型
- DaemonSet, daemon顾名思义确保全部(或者某些)节点上运行一个 Pod 的副本,例如监控、日志收集服务
- Job,创建一个或者多个 Pod,直到指定数量的 Pod 成功终止
- CronJob,创建基于时隔重复调度的 Job(不得不说真的是方便,crontab也退休了
- ConfigMap,一种 API 对象,用来将非机密性的数据保存到键值对中,在启动服务的时候传递进去,通过这种方式将配置文件依赖与镜像解耦
- Secret,类似于 ConfigMap 但专门用于保存机密数据
- 声明式,对目标状态进行声明之后由管理端来操作确认达到目标状态,屏蔽了其中的操作细节
- 过程式,传统的很多运维操作其实就是过程式,需要一步一步操作(不管是手动点击、还是基于api),过程繁琐且容易出错
- Label,在k8s的生态当中对于服务的分类和管理十分灵活,这很大成都取决于Label这个完美而又灵活性爆棚的设计,通过打标签(key:value)的方式可以在后续进行各种灵活的选择匹配,不仅是k8s自己,包括Prometheus对于监控数据、alertmanager对于告警都是一脉形成的延续了这种灵活、拓展性极强的Label设计
以上是服务启动和管理所需要的一些基本概念,但服务启动之后距离投入实际使用还有一些步骤与额外的概念
- Liveness,服务暴露监测接口,k8s通过存活探针来判断服务状态
- Readyness,服务暴露的ready接口,k8s通过就绪探针来判断服务的启动状态
- Endpoints,因为服务是虚拟化启动因此其端口并不会对外暴露,需要通过创建endpoints资源来做端口映射暴露端口对外提供服务
- Service,是软件服务的命名抽象,包含代理要侦听的本地端口(例如 3306)和一个选择算符, 选择算符用来确定哪些 Pod 将响应通过代理发送的请求
- Ingress,集成了nginx七层代理,可以想配置nginx的location一样配置流量转发到启动的服务
- Namespace,感觉可以类比于编程当中的namespace可以对资源进行隔离
文件系统概念
介绍完服务以及相关概念,接下来还有一些文件系统的概念,因为服务启动会涉及到文件读取,运行中也会涉及到文件写入也很重要,因此单独作为一块进行记录
- 卷(Volume),可以理解成容器的磁盘,默认情况下是临时存放的,当容器崩溃销毁后会丢失数据,因此k8s也支持了多种类型
- 持久卷(Persistent Volume),是集群中的一块存储,由管理员事先制备,独立于pod的生命周期(可以理解成重装系统格式化好的文件系统
- 持久卷申领(PersistentVolumeClaim,PVC),也是一种资源类型,在创建服务时进行申请
- 上面说的ConfigMap和Secret也是卷的一种
- emptyDir, Pod 启动时为空,存储空间来自本地的 kubelet 根目录(通常是根磁盘)或内存
调度相关概念
在服务运行期间,k8s会根据相关配置对服务节点进行相关的调度、扩缩容等操作,以下也是一些比较重要的概念
- requests、limit,用来申请和说明限制的对cpu和内存的最小值和最大值,这样就能进行更科学的调度
- 服务质量(QoS),结合request以及limit字段有如下几个服务质量等级
- Guaranteed(最高):Pod 里的每个容器都必须有内存/CPU 限制和请求,而且值必须相等(如果只设置了limits没有设置requests那么默认requests是和limit相等的)
- Burstable:Pod 里至少有一个容器有内存或者 CPU 请求且不满足 Guarantee 等级的要求,即内存/CPU 的值设置的不同
- BestEffort(最低):容器必须没有任何内存或者 CPU 的限制或请求,这样就没有资源保证,在发生资源抢占的时候是最早被oom的一批
- 扩缩容,通过配置支持对pod进行扩容以及缩容的操作,这样就达到了资源合理使用的目的,像谷歌这种全球范围的公司服务全球不同时区的用户,在不同时区的服务负载不一样,就在不停地根据服务用量进行扩缩容,甚至有一个专门的小组做资源使用的预测,需要注意的是k8s的扩缩容最后那个cpu和内存是不一样的,cpu属于计算资源直接累加就可以,但是内存数据有状态的资源,没有办法直接从1G升程2G,k8s能做的就是杀死原来1G的pod然后创建出来一个2G的重新启动,所以这种场景需要应用本身进行相应的处理(1.8之后支持)
- 调度器,调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。 调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行
- 节点隔离/限制,通过label的设置,可以通过设置NodeRestriction来进行节点隔离
- 亲和性/反亲和性,通过设置nodeSelector标签Kubernetes 只会将 Pod 调度到拥有所指定的每个标签的节点上,场景:例如存在调用关系的前端服务和后端服务部署在同一个节点上会比较合适,或者高IO的服务部署在ssd的机器上
- 节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种
- requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强
- preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod
- 节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种
- 污点(Taint),与亲和性相反——它使节点能够排斥一类特定的 Pod,(污点是作用于节点Node的
- NoSchedule表示如果pod没有容忍这些污点这不会被调度到包含这些污点的节点上
- PreferNoSchedule是NoSchedule的宽松版本,表示尽量组织pod被调度到这个节点上,但是如果没有其他节点可以调度,pod依然会被调度到这个节点
- NoExecute不仅在带哦度期间起作用还会影响正在运行的pod,如果一个pod所在节点被添加了NoExecute并且自身没有容忍该污点,将会被去除。
1 | 给节点 node1 增加一个污点,它的键名是 key1,键值是 value1,效果是 NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1 这个节点 |
容忍度(Toleration),是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数
打散,调度到不同地方,可避免因软硬件故障、光纤故障、断电或自然灾害等因素导致服务不可用,以实现服务的高可用部署
Pod 反亲和 (Pod Anti-Affinity),通过指定podAntiAffinity来防止调度到某个节点
拓扑分布约束(Topology Spread Constraints) 来控制 Pod 在集群内故障域之间的分布, 例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。 这样做有助于实现高可用并提升资源利用率(属于比较新的feature,功能比反亲和性更强,但目前也有以一些已知问题,例如缩容操作会导致节点不平衡,污点也会被统计)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# 配置一个拓扑分布约束
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # 可选;自从 v1.25 开始成为 Beta
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
matchLabelKeys: <list> # 可选;自从 v1.25 开始成为 Alpha
nodeAffinityPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
nodeTaintsPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
### 其他 Pod 字段置于此处
调度逻辑
- 过滤所有节点,找出能分配给pod的可用节点列表
- 对可用节点按优先级排序,找出最优节点。如果多个节点都有最高优先级份数,那么循环分配,确保平均分配给pod
- 节点是否能滿足 pod对硬件资源的请求。
- 节点是否耗尽资源 (是否报告过内存/ 硬盘压力参数), pod 是否要求被调度到指定节点 (通过名字),是否是当前节点? 。节点是否有和pod规格定义里的节点选择器一致的标签(如果定义了的话)?
- 如果 pod 要求绑定指定的主机端口,那么这个节点上的该端口是否已经被占用?
- 如果pod 要求有特定类型的卷,该节点是否能为此pod 加载此卷,或者说该 节点上是否己经有 pod 在使用该卷了?
- pod 是否能够容忍节点的污点。
- pod 是否定义了节点、pod 的亲缘性以及非亲缘性规则?如果是,那么调度节 点给该pod 是否会违反规则?
- 所有这些测试都必须通过,节点才有资格调度给 pod。在对每个节点做过这些检查后,调度器得到节点集的一个子集。任何这些节点都可以运行 pod,因为它1 那有足够的可用资源,也确认过满足 pod 定义的所有要求。
总结
这只是看完k8s in action之后需要记忆和记录的一些概念,真正操作起来他们的组合和实际效果又会复杂很多,不得不说k8s真不愧是云操作系统,它使得我们的服务可以想进程调度一样方便,很大程度上抹平了开发、运维之间的沟壑,也降低了资源浪费,相应的监控、运维领域很多旧的都不在适用,每一个人都需要尽快尽早在这一波技术浪潮中学会驾驶k8s这艘快船
转载请注明来源链接 http://just4fun.im/2021/03/01/kubernetes-in-action读书记录/ 尊重知识,谢谢:)