引言
前面我们已经介绍了 Kubernetes 的常用资源以及 Kubernetes 的实现原理,本文我们将介绍 Kubernetes 中的一些高级资源以及其使用场景,更多关于 Kubernetes 的介绍均收录于<Kubernetes系列文章>中。
高级特性
自动扩容
我们可以通过调高 ReplicationController、ReplicaSet、Deployment 等可伸缩资源的 replicas 字段,来手动实现 pod 中应用的横向扩容。我们也可以通过增加 pod 容器的资源请求和限制来纵向扩容 pod。虽然如果你能预先知道负载何时会飘升,或者如果负载的变化是较长时间内逐渐发生的,手动扩容也是可以接受的,但指望靠人工干预来处理突发而不可预测的流量增长,仍然不够理想。好在 Kubernetes 可以监控你的 pod, 并在检测到 CPU 使用率或其他度量增长时自动对它们扩容。如果 Kubernetes 运行在云端基础架构之上,它甚至能在现有节点无法承载更多 pod 之时自动新建更多节点。
横向 pod 自动伸缩是指由控制器管理的 pod 副本数量的自动伸缩。它由 Horizontal 控制器执行,我们通过创建一个 HorizontalPodAutoscaler(HPA)资源来启用和配置 Horizontal 控制器。该控制器周期性检查 pod 度量,计算满足 HPA 资源所配置的目标数值所需的副本数量,进而调整目标资源(如Deployment、ReplicaSet、ReplicationController、StatefulSet等)的 replicas 字段。
Autoscaler 本身并不负责采集 pod 度量数据,而是从另外的来源获取。正如前面提到的,pod 与节点度量数据是由运行在每个节点的 kubelet 之上,名为 cAdvisor 的 agent 采集的;这些数据将由集群级的组件 Heapster 聚合。HPA 控制器向 Heapster 发起 REST 调用来获取所有 pod 度量数据。这样的数据流意味着在集群中必须运行 Heapster 才能实现自动伸缩。
一旦 Autoscaler 获得了它所调整的资源(Deployment、ReplicaSet、ReplicationController 或 StatefulSet)所辖 pod 的全部度量,它便可以利用这些度量计算出所需的副本数量。它需要计算出一个合适的副本数量,以使所有副本上度量的平均值尽量接近配置的目标值。Autoscaler 控制器通过 Scale 子资源(ReplicaSet 等资源可以暴露该子资源)来修改被伸缩资源的 repplicas 字段。计算出最新的 pod 数量后,它会去更新 Scale 资源的 replicas 字段,剩下的工作就由 Replication 控制器,调度器,kublet 来完成。
计算时向上取整
下面的图中描述了 HPA 的完整工作流程:
那么,我们都能通过哪些指标来进行自动扩容呢?其中自然少不了 CPU,我们可以指定一个目标使用率,这个目标使用率表示的是容器请求的 CPU 资源的使用率,如下所示,如果容器请求了 100 毫核(100/1000 个核心),然后通过命令 kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max=5
并将目标 cpu 使用率定为 30%,最大 pod 数为 5,最小为 1。这时候一旦 CPU 平均使用率高于 30%,就会向上扩容但是最多扩大到 5,反之如果低于目标使用率很多倍,则会向下缩容。
resources:
requests:
cpu: 100m
通过 CPU 来进行度量是很容易的,但基于内存的自动伸缩比基于 CPU 的困难很多。主要原因在于,扩容之后原有的 pod 需要有办法释放内存。这只能由应用完成,系统无法代劳。系统所能做的只有杀死并重启应用,希望它能比之前少占用一些内存。
除了这些常规指标之外,还有关于 QPS 的扩容,这里就不再赘述了。下面来说一说,集群节点的自动扩容,HPA 在需要的时候会创建更多的 pod 实例。但万一所有的节点都满了,放不下更多 pod 了,怎么办?这时候集群扩容可能就是最好的选择了,一般来说云服务提供商会对接 Kubernetes,让 Kubernetes 可以通过 API 来增加新的节点然后加入集群,这样就能将 Pod 运行在这些新节点上了。
当节点利用率不足时,Cluster Autoscaler 也需要能够减少节点的数目。Cluster Autoscaler 通过监控所有节点上请求的 CPU 与内存来实现这一点。 如果某个节点上所有 pod 请求的CPU、内存都不到 50%, 该节点即被认定为不再需要。这并不是决定是否要归还某一节点的唯一因素。Cluster Autoscaler 也会检查是否有系统 pod (仅仅)运行在该节点上(这并不包括每个节点上都运行的服务,比如 DaemonSet 所部署的服务)。如果节点上有系统 pod 在运行,该节点就不会被归还。对非托管 pod, 以及有本地存储的 pod 也是如此, 否则就会造成这些 pod 提供的服务中断。换句话说,只有当 Cluster Autoscaler 知道节点上运行的 pod 能够重新调度到其他节点,该节点才会被归还。
当一个节点被选中下线,它首先会被标记为不可调度,随后运行其上的 pod 将被疏散至其他节点。因为所有这些 pod 都属于 ReplicaSet 或者其他控制器,它们的替代 pod 会被创建并调度到其他剩下的节点(这就是为何正被下线的节点要先标记为不可调度的原因)。
高级调度
前面我们在介绍调度器的时候,提到了筛选规则中有一个是: pod 是否能够容忍节点的污点,这又是什么呢?所谓污点就是集群管理员为集群节点打上的标记,比如kubectl taint node nodel.k8s node-type=production:NoSchedule
,如果 pod 的声明中没有显示的说明自己能够容忍 node-type=production
的话就不会被调度到该节点上。
这里大家可能会有疑问,污点声明中的 NoSchedule 是什么,它实际上污点关联的效果,加上 NoSchedule 总共有三种效果,它们的意义如下:
- NoSchedule 表示如果 pod 没有容忍这些污点,pod 则不能被调度到包含这些污点的节点上。
- PreferNoSchedule 是 NoSchedule 的一个宽松的版本,表示尽量阻止 pod 被调度到这个节点上,但是如果没有其他节点可以调度,pod 依然会被调度到这个节点上。
- NoExecute 不同于 NoSchedule 以及 PreferNoSchedule, 后两者只在调度期间起作用,而 NoExecute 也会影响正在节点上运行着的 pod。如果在一个节点上添加了 NoExecute 污点,那些在该节点上运行着的 pod, 如果没有容忍这个 NoExecute 污点,将会从这个节点去除。
继续回到刚才的例子中,如果我们希望自己的 pod 能被调度的生产环境中的节点上,可以通过如下配置:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: prod
spec:
replicas: 5
template:
spec:
...
tolerations: # 允许 pod 被调到生产环境的节点上
- key: node-type
operator: Equal
value: production
effect: NoSchedule
除了污点和容忍机制外,我们还提到过亲缘性,这又是什么呢?不知道大家还记不记得之前我们通过 nodeSelector
来将 pod 只部署到有 gpu 的节点上。通过亲缘性机制,我们同样可以做到这个。
apiVersion: v1
kind: Pod
metadata:
name: kubia-gpu
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingignoredDuringExecution: # 要求必须存在如下特性
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
那么既然 nodeSelector 可以做到的事,还需增加一个新的亲缘性概念呢?从上面的配置文件中不难看出,亲缘性机制相较于 nodeSelector 表达性更强,除此之外,还能用来描述调度的优先度,而不是像 nodeSelector 那样是必要条件。
apiVersion: v1
kind: Pod
metadata:
name: kubia-gpu
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingignoredDuringExecution: # 优先级
- weight: 80 # 节点优先调度到 zone1,这是最重要的偏好
preference:
matchExpressions:
- key: availability-zone
operator: In
values:
- zone1
- weight: 20 # 同时优先调度到独占节点,但是优先度没有上述的高
preference:
matchExpressions:
- key: share-type
operator: In
values:
- dedicated
除此之外,我们还可以用亲缘性机制做很多事,比如通过亲缘性,将所有 pod 调度到同一个机架上,这需要你先为节点设置机架标签,然后通过 topologyKey 和 labelSelector 就能达到这个效果。
也能通过非亲缘性(anti-affinity),来让 pod 工作在不同的节点上。
联合集群
我们探讨了 Kubernetes 是如何处理单个机器的故障,甚至整个服务器集群或基础设施的故障的。但是如果整个数据中心出了问题,该怎么办呢?为确保你不受数据中心级别故障的影响,应用程序应同时部署在多个数据中心或云可用区域中。当其中一个数据中心或可用区域变得不可用时,可将客户端请求路由到运行在其余健康数据中心或区域中的应用程序。
虽然 Kubernetes 并不要求你在同一个数据中心内运行控制面板和节点,但为了降低它们之间的网络延迟,减少连接中断的可能性,人们还是希望将它们部署到一起。与其将单个集群分散到多个位置,更好的选择是在每个位置都有一个单独的 Kubernetes 集群。
Kubernetes 允许你通过 ClusterFederation 将多个集群组合成联合集群。它允许用户在全球不同地点运行多个集群部署和管理应用程序,同时也支持跨不同的云提供商与本地集群(混合云)相结合。ClusterFederation 的目标不仅是为了确保高可用性,还要将多个异构集群合并为一个通过单一管理界面进行管理的超级集群。
联合集群就相当于是一个特殊的集群,只不过这个集群的节点是一个个完整的集群。它的控制面板中包含存储联合 API 对象的 etcd,Federation API 服务器和 Federation Controller Manager。
在联合集群中,包括 Namespace,ConfigMap,Secret,Service,Ingress,Deployment,ReplicaSet,Job,DaemonSet,HorizontalPodAutoscaler 资源。操作这些资源就像您操作 Kubernetes 集群一样,只不过这时候我们需要使用 Federation API。
对于一部分联合对象来说,当你在联合 API 服务器中创建对象的时候,Federation Controller Manager 中运行的控制器会在所有底层 Kubernetes 集群中创建普通的集群内资源,并管理这些资源直到联合对象被删除为止。
对于某些联合资源类型,在底层集群中创建的资源是联合资源的精确副本;对于其的联合资源而言,情况有些不同,这些联合资源根本不会在底层集群中创建任何对应的资源。副本与原始联合版本保持同步,但是同步只是单向的,只会从联合服务器到底层集群同步。如果修改底层集群中的资源,则这些更改将不会同步到联合 API 服务器。
ReplicaSet 和 Deployment是特例,它们不会盲目地被复制到底层集群,因为通常这不是用户想要的。毕竟,如果你创建一个期望副本数为 10 的 Deployment,那么可能你希望的并不是在每个底层集群中运行 10 个 pod 副本,而是一共需要 10 个副本。因此,当你在 Deployment 或 ReplicaSet 中指定所需的副本数时,联合控制器会在底层创建总数相同的副本。默认情况下,它们会均匀分布在集群中,当然也可以手动修改。
另一方面,联合 Ingress 资源不会导致在底层集群中创建任何 Ingress 对象。你可能还记得,Ingress 代表了外部客户访问服务的单一入口点。因此,联合 Ingress 资源创建了多底层集群范围全局入口点。
文章说明
更多有价值的文章均收录于贝贝猫的文章目录
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
创作声明: 本文基于下列所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。
参考内容
[1] kubernetes GitHub 仓库
[2] Kubernetes 官方主页
[3] Kubernetes 官方 Demo
[4] 《Kubernetes in Action》
[5] 理解Kubernetes网络之Flannel网络
[6] Kubernetes Handbook
[7] iptables概念介绍及相关操作
[8] iptables超全详解
[9] 理解Docker容器网络之Linux Network Namespace
[10] A Guide to the Kubernetes Networking Model
[11] Kubernetes with Flannel — Understanding the Networking
[12] 四层、七层负载均衡的区别