k8s 资源控制系统
k8s 中大部分概念如:Node、Pod、Replication Controller、RS、Deployment、Service
等都可以被看作一种资源对象,激活所有的资源对象都可以通过 k8s 提供 kubectl
工具(或者 API 编程调用)执行 CRUD
等操作并将其保存在 etcd
中持久化存储。
从这个角度来看,k8s 其实是一个高度自动化的资源控制系统,它通过跟踪对比 etcd 库里保存的 “资源期望状态” 与当前环境中的 “实际资源状态” 的差异来实现 自动控制 和 自动纠错 的高级功能
。
在声明 k8s 资源对象的时候,需要注意一个关键属性:apiVersion
。以 Pod 声明为例,可以看到 Pod 这种资源对象归属 v1
这个核心 API。
apiVersion: v1
kind: Pod
metadata:name: nginxlabels: name: nginx
spec:containers:- name: nginximage: nginx:1.14.2ports:- containerPort: 80
k8s 平台 API Groups
k8s 平台采用了 “核心+外围扩展”
的设计思路,在保持平台核心稳定的同时,具备持续演进升级的优势
。k8s 大部分常见的核心资源对象都归属 v1 这个 core(核心)API,比如 Node、Pod、Service、Endpoints、Namespace、RC、PersistentVolume
等。在版本迭代过程中,k8s 先后扩展了 extensions/v1beta1、apps/v1beta1、apps/v1beta2
等 API 组,而在 v1.9 版本之后引入了 apps/v1
这个正式的扩展 API 组,正式 淘汰(deprecated)
了 extensions/v1beta1、apps/v1beta1、 apps/v1beta2
这三个 API 组。
我们可以采用 YAML
或 JSON
格式声明(定义或创建)一个 k8s 资源对象,每个资源对象都有自己的特定语法格式(可以理解为数据库中一个特定的表),但 随着 k8s 版本的持续升级,一些资源对象会不断引入新的属性
。为了在不影响当前功能的情况下引入对新特性的支持,我们通常会采用下面 两种典型方法
。
方法1:在设计数据库表的时候,在每个表中都增加一个很长的备注字段,之后扩展的数据以某种格式(如 XML、JSON、简单字符串拼接等)放入备注字段。因为数据库表的结构没有发生变化,所以此时程序的改动范围是最小的,风险也更小,但看起来不太美观优雅。
方法2:直接修改数据库表,增加一个或多个新的列,此时程序的改动范围较大,风险更大,但看起来比较美观。
显然,两种方法都不完美。更加优雅的做法是,先采用方法1实现这个新特性,经选几个版本的迭代,等新特性变得稳定成熟了以后,可以在后续版本中采用方法2升级到正式版
。为此, k8s 为每个资源对象都增加了类似数据库表里备注字段的通用属性 Annotations(注解),以实现方法1的升级
。
以 k8s v1.3 版本引入的 Pod 的 Init Container
新特性为例,参照对比如下:
在 k8s v1.8 版本中,资源对象中的很多 Alpha、Beta
版本的 Annotaions(注解)
被取消,升级成了常规定义方式,在学习 k8s 的过程中需要特别注意。
更多的 API Groups 请查看:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#-strong-api-groups-strong-
k8s 资源对象
在 k8s 中一切皆可被抽象定义为资源对象
,接下来我们了解下 k8s 中有哪些常用的资源对象,比如,Master、Node、Label、RC、RS、Deployment、DaemonSet、HPA、StatefulSet、Service、Job & CronJob、Volume、PV & PVC、Namespace、Annotation、ConfigMap、Secret
等。
Master
在 k8s 里 Master
指的是集群控制节点,负责整个 k8s 集群环境的管理和控制,基本上 k8s 的所有命令都发给它,并负责具体的执行过程,我们后面所有执行的命令基本上是在 Master
上运行的。
Master 通常会占据一个独立的服务器(生产环境为了保障高可用性,通常建议奇数台服务器组成 Master 集群,比如:3,5,7... 台服务器
),它对于整个 k8s 的集群环境非常重要,是 整个集群的 “大脑”
,如果 Master 宕机或者不可用,那么整个集群内的容器应用的管理都将全部失效。
在 Master 节点运行着以下关键进程:
kube-apiserver
,提供了基于 http 协议的 RESTful API 的关键进程,是 k8s 里面所有资源的 CRUD(增、删、改、查)等操作的唯一入口,也是集群控制的入口进程。kube-controller-manager
,是 k8s 里所有资源对象的自动化控制中心,可以将其理解为资源对象的 “大总管”。kube-scheduler
,负责资源调度(Pod 调度)的进程,相当于交通道路的指挥中心和公交公司的 “调度室”。
另外,在 Master 上面通常还需部署 etcd 数据库服务
,因为 k8s 里的所有资源对象的数据都被保存在 etcd 中持久化存储。
【注意】:etcd 数据库服务可以独立物理机环境部署,为了保障在生产环境中的高可用性,通常建议采用奇数节点组成 eetcd 集群环境,比如:3,5,7... 台服务器环境。
Node
除了 Master ,k8s 集群中其他机器还存在另一角色 Node
,在早期的版本中叫 Minion。与 Master 一样,Node 也可以是一台物理机或者虚拟机。Node 是 k8s 集群中的工作负载节点
,每个 Node 都会被 Master 分配一些工作负载(比如:Docker 容器),当某个 Node 宕机时,其上面的工作负载会被 Master 自动转移到其他的 Node 节点上继续运行,自动化的故障处理。
在每个 Node 上面都运行着以下关键进程:
kubelet
,等同于 Node 上面的Agent
,负责 Pod 对应的容器的创建、启动、停止等任务,同时与 Master 密切协作,实现 k8s 集群管理的基本功能。kube-proxy
,实现 k8sService
的通信与软件负载均衡(LAN-4)
机制的重要组件。Container Runtime(容器运行)
,负责本机的容器创建和管理工作,从 k8s v1.24 版本以后正式剔除 Docker ,采用Containerd 作为默认的 Container Runtime
,提供标准的 CRI 接口
,实现更多的容器运行时产品。
Node 可以在运行期间动态的在 k8s 集群中增加,前提是在这个节点上已经正确安装、配置和配置了上述关键的进程服务,在 默认情况下 kubelet 会向 Master 注册自己
,这也是 k8s 推荐的 Node 管理方式。一旦 Node 被纳入 k8s 集群管理范围,kubelet 进程就会定时向 Master 汇报自身的情况
,例如操作系统、容器版本、集群的CPU和 Mamory 内存情况,以及当前 Node 有哪些 Pod 在运行等,这样 Master 就可以获知每个 Node 的资源使用情况,并实现高效的资源调度策略
。而 某个 Node 在超过指定时间不上报信息时,会被 Master 判定为 “失联” ,此时 Node 的状态被标记为不可用(Not Ready),随后 Master 会触发 “ Worker 工作负载大转移” 的自动流程
。
向 API 服务器添加节点的方式主要有两种:
节点上的 kubelet 向控制面(Master 上面的
kube-controller-manager
)执行自注册。人工手动添加一个 Node 对象。
在你创建了 Node 对象或者该节点上的 kubelet 执行了自注册操作之后,控制面(Master)会检查新的 Node 对象是否合法。例如,如果你尝试使用下面的 JSON 对象来创建 Node 对象:
{"kind": "Node","apiVersion": "v1","metadata": {"name": "10.240.79.157","labels": {"name": "my-first-k8s-node"}}
}
k8s 会在内部创建一个 Node 对象作为节点的 label(标签)
。k8s 检查 kubelet 向 API 服务器注册节点时使用的 metadata.name
字段是否匹配。如果节点是健康的(即所有必要的服务都在运行中),则该节点可以用来运行 Pod。否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。
查看集群中 Node 的数量和状态:
kubectl get nodes
查看某一个 Node 的详细信息:
kubectl describe node k8s-node-01
说明:K8s 会一直保存着非法节点对应的对象,并持续检查该节点是否已经变得健康。你,或者某个控制器必须显式地删除该 Node 对象以停止健康检查操作。
关于更多 Node 信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/
Pod
Pod 是什么?
Pod 是 k8s 中最重要的基本概念,是可以在 k8s 中创建和管理的、最小的可部署的计算单元
。
Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器;这些容器共享存储、网络、以及怎样运行这些容器的声明。Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行
。Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init-Container
。你也可以在集群中支持 ephemeral containers(临时性容器)
的情况下,为调试的目的注入临时性容器。
Pod 的共享上下文包括一组 Linux 名字空间(namespace)、控制组(cgroup)和可能一些其他的隔离方面
, 即用来隔离(比如:Docker)容器的技术。在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
上图展示了 Pod 的组成示意图,在每一个 Pod 中都有一个特殊的 “根容器” Pause 容器
。Pause 容器对应的镜像属于 k8s 平台的一部分,在 Pod 中除了 Pause 容器,还包含一个或多个紧密相关的的用户业务容器。
为什么 k8s 会设计出一个全新的 Pod 的概念并且 Pod 有这样特殊的组成结构?
原因之一:
在一组容器作为一个单元的情况下,我们难以简单地对 “整体” 进行判断及有效地行动
。比如,一个容器死亡了,此时算是整体死亡,还是算 n/m 的死亡率呢?引入业务无关并且不易死亡的 Pause 容器作为 Pod 的根容器,以它的状态代表整个容器组的状态,就简单、巧妙地解决了这个难题。原因之二:
Pod 里的多个业务容器共享 Pause 容器的 IP,共享Pause 容器挂接的Volume
。这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。
k8s 为每个 Pod 都分配了唯一的 IP 地址,称之为 Pod IP
,一个 Pod 里的 多容器共享 Pod IP 地址
。K8s 要求底层网络支持集群内任意两个 Pod 之间的 TCP/IP
直接通信,这通常采用 虚拟二层网络技术
来实现,例如 Flannel、Open vSwitch
等,因此我们需要牢记一点:在 k8s 里,一个 Pod 里的容器与另外主机上的 Pod 容器能够直接通信(在 k8s 中网络环境要求 “扁平化” 管理,即 Pod 间可以相互网络通信,也可以跨主机网络通信)
。
Pod 分类
按运行方式,Pod 其实有两种类型:
**
自主式 Pod(也叫静态 Pod/ Static Pod)
**,这种 Pod 比较特殊,它并没被存放在 k8s 的 etcd 中存储持久化,而是被存放在某个具体的 Node 上的一个具体文件中,并且只在此 Node 上启动、运行。并且该 Pod 本身是不能自我修复的,当 Pod 被创建后(不论是由你直接创建还是被其他 Controller),都会被 k8s 调度到集群的 Node 上。直到 Pod 的进程终止、被删掉、因为缺少资源而被驱逐、或者 Node 故障之前这个 Pod 都会一直保持在那个 Node 上。Pod 不会自愈。如果 Pod 运行的 Node 故障,或者是调度器本身故障,这个 Pod 就会被删除。同样的,如果 Pod 所在 Node 缺少资源或者 Pod 处于维护状态,Pod 也会被驱逐。**
控制器管理的 Pod(也叫普通 Pod)
**,K8s 使用更高级的称为 Controller 的抽象层,来管理 Pod 实例。Controller 可以创建和管理多个 Pod,提供副本管理、滚动升级和集群级别的自愈能力。该 Pod 一旦被创建,就会被存入 etcd 中存储持久化,随后被 k8s Master 调度到某个具体的 Node 上并进行绑定(Binding),随后该 Pod 被对应的 Node 上的 kubelet 进程实例化成―组相关的 Docker 容器并启动。在默认情况下,当 Pod 里的某个容器停止时,k8s 会自动检测到这个问题并重新启动这个 Pod(重启 Pod 里的所有容器),如果 Pod 所在的 Node 宕机,就会将这个 Node 上的所有 Pod 重新调度到其他健康的 Node 上。虽然可以直接创建和使用 Pod,但是在 k8s 中通常是使用 Controller 来管理 Pod 的。
Pod 资源对象的 YAML 定义文件
k8s 里所有的资源对象都可以采用 YAML 或者 JSON 格式的文件来定义或描述
,下面我们来看一个 Pod 运行单个容器
的资源定义文件:
apiVersion: v1 # API 版本号
kind: Pod # 资源类型
metadata: # meta 信息name: myweblabels: # 标签 name: myweb
spec:containers:- name: myweb-aspnetcore # 容器名称image: aspnetcore-6.0:v1.0.0 # 镜像:版本imagePullPolicy: IfNotPresent # 如果镜像不存在则拉取ports:- containerPort: 5001 # 容器暴露端口env: # 环境变量- name: ASPNETCORE_ENVIRONMENT # asp.net core 运行时环境value: Development # 开发环境- name: ASPNETCORE_URLS # 设置 asp.net core 启动端口value: http://localhost:5001/ # 本地主机 5001 端口
上面 Pod 的 yaml 文件定义中,Pod 的 IP 加上容器端口(containerPort)就组成了一个新的概念 —— Endpoint
,它代表此 Pod 里的一个服务进程的对外通信地址。一个 Pod 也存在具有多个 Endpoint 的情况
,比如当我们把一个 RabbitMQ(image= rabbitmq:3.10.7-management)
定义为一个 Pod 时,可以对外暴露管理端口和服务端口这两个 Endpoint
。
接下来,我们再看一个 Pod 运行多容器
的资源定义文件:
apiVersion: v1
kind: Pod
metadata:name: kucc4
spec:containers:- name: nginximage: nginx:1.23.1- name: redisimage: redis:7.0.4- name: memcachedimage: memcached:1.6.16
上面 Pod 的 yaml 文件定义中,创建了一个名称为 kucc4 的 Pod,在该 Pod 里面分别为镜像 images (nginx + redis + memcache)单独运行一个 app container(应用程序容器)。
Pod Volume
大家熟悉的 Docker Volume 在 k8s 里面也有对应的概念 —— Pod Volume,后者有一些扩展,比如可以使用分布式文件系统 GlusterFS 实现后端存储功能;Pod Pod Volume 是被定义在 Pod 上,然后被各个容器挂载到自己的文件系统中的
。
k8s 中的 Event
Event 是一个事件的记录,记录了事件的最早生产时间、最后重现时间、重复次数、发起者、类型,以及导致此事件的原因等信息
。它通常会被关联到某个具体的资源对象上,是排查故障的重要参考信息。
例如当我们发现某个资源对象迟迟无法创建时,可以查看某个资源对象的描述信息,方便定位问题成因,执行命令如下:
kubectl describe <资源对象类型> <资源对象名称>kubectl describe pod xxxx # 查看 Pod 资源信息
kubectl describe node aaaa # 查看 Node 资源信息
Pod 中的资源配额
每个 Pod 都可以对其使用的服务器上的计算资源设置限额,常用的限额的计算资源有 CPU 和 Memory 两种:
CPU 资源单位为 CPU(Core)的数量,是一个绝对值,最小单位m
,1 个 CPU(Core 核)等于 1000m。一个 CPU 等于1 个物理 CPU 核 或者 一个虚拟核, 取决于节点是一台物理主机还是运行在某物理主机上的虚拟机。Memory 资源配额也是一个绝对值,单位是内存字节数
。可以使用普通的整数,或者带有以下 数量后缀 的定点数字来表示内存:E、P、T、G、M、k。你也可以使用对应的 2 的幂数:Ei、Pi、Ti、Gi、Mi、Ki。例如,以下表达式所代表的是大致相同的值:
128974848、129e6、129M、128974848000m、123Mi
说明:K8s 不允许设置精度小于 1m 的 CPU 资源。因此,当 CPU 单位小于 1 或 1000m 时,使用毫核的形式是有用的;例如 5m 而不是 0.005。
在 k8s 里,一个计算资源进行配额限定时需要设定以下两个参数:
Request
,该资源的最小申请量,系统必须满足要求。Limits
,该资源的最大允许使用量,不能被突破,当容器尝试使用超过这个量的资源时,可能会被 k8s “杀掉/ kill” 进程服务并重启。
以 Pod 为例,你可以配置为该 Pod 的资源请求为 0.5 CPU 和 128 MiB 内存,资源限制为 1 CPU 和 256MiB 内存。
apiVersion: v1
kind: Pod
metadata:name: frontendlabels: name: myweb
spec:containers:- name: appimage: images.my-company.example/app:v4resources:requests:memory: "128Mi"cpu: "500m" # 或者 0.5limits:memory: "256Mi"cpu: "1" # 或者 1000m
Pod 重启策略
Pod 的 spec 中包含一个 restartPolicy 字段
,其可能取值包括 Always、OnFailure 和 Never
。默认值是 Always。
restartPolicy
适用于 Pod 中的所有容器。restartPolicy 仅针对同一节点上 kubelet 的容器重启动作
。当 Pod 中的容器退出时,kubelet
会按指数回退 方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。一旦某容器执行了 10 分钟并且没有出现问题,kubelet 对该容器的重启回退计时器执行 重置操作
。
Pod 阶段(Phase)
Pod 的 status
字段是一个 PodStatus
对象,其中包含一个 phase
字段。
Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述
。该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。我们可以把它理解为 Pod 创建的一个总体阶段过程
。
Pod 阶段的数量和含义是严格定义的。除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase 值。
下面是 phase 可能的值:
取值 | 描述 |
---|---|
Pending(悬决) | Pod 已被 k8s 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 |
Running(运行中) | Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。 |
Succeeded(成功) | Pod 中的所有容器都已成功终止,并且不会再重启。 |
Failed(失败) | Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。 |
Unknown(未知) | 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。 |
如果某节点 “死掉” 或者与集群中其他节点 “失联”,k8s 会实施一种策略,将失去的节点上运行的所有 Pod 的 phase 设置为 Failed。
Pod 状态
Pod 有一个 PodStatus
对象,其中包含一个 PodConditions
数组。Pod 可能通过也可能未通过其中的一些状况测试。
PodScheduled:Pod 已经被调度到某节点;
ContainersReady:Pod 中所有容器都已就绪;
Initialized:所有的 Init 容器 都已成功完成;
Ready:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称 | 描述 |
---|---|
type | Pod 状况的名称 |
status | 表明该状况是否适用,可能的取值有 "True", "False" 或 "Unknown" |
lastProbeTime | 上次探测 Pod 状况时的时间戳 |
lastTransitionTime | Pod 上次从一种状态转换到另一种状态时的时间戳 |
reason | 机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因 |
message | 人类可读的消息,给出上次状态转换的详细信息 |
Container 状态
k8s 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段一样
。你可以使用 容器生命周期回调
来在容器生命周期中的特定时间点触发事件。
一旦调度器将 Pod 分派给某个节点,kubelet 就通过 容器运行时 开始为 Pod 创建容器。容器的状态有三种:Waiting(等待)、Running(运行中)和 Terminated(已终止)
。
要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称>
。其输出中包含 Pod 中每个容器的状态
。
Container 每种状态都有特定的含义:
字段名称 | 描述 |
---|---|
Waiting (等待) | 如果容器并不处在 Running 或 Terminated 状态之一,它就处在 Waiting 状态。处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如,从某个容器镜像 仓库拉取容器镜像,或者向容器应用 Secret 数据等等。当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。 |
Running(运行中) | Running 状态表明容器正在执行状态并且没有问题发生。如果配置了 postStart 回调,那么该回调已经执行且已完成。如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时,你也会看到 关于容器进入 Running 状态的信息。 |
Terminated(已终止) | 处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时,你会看到 容器进入此状态的原因、退出代码以及容器执行期间的起止时间。 |
说明,如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行。
Container 探针
probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
1、检查机制
使用探针来检查容器有四种不同的方法。每个探针都必须准确定义为这四种机制中的一种:
字段名称 | 描述 |
---|---|
exec | 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。 |
grpc | 使用 gRPC 执行一个远程过程调用。目标应该实现 gRPC健康检查。如果响应的状态是 "SERVING",则认为诊断成功。 |
gRPC | 探针是一个 alpha 特性,只有在你启用了 "GRPCContainerProbe" 特性门控时才能使用。 |
httpGet | 对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。 |
tcpSocket | 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。 |
2、探测结果
每次探测都将获得以下三种结果之一:
字段名称 | 描述 |
---|---|
Success(成功) | 容器通过了诊断。 |
Failure(失败) | 容器未通过诊断。 |
Unknown(未知) | 诊断失败,因此不会采取任何行动。 |
3、探测类型
针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
字段名称 | 描述 |
---|---|
livenessProbe(存活探针) | 指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success。 |
readinessProbe(就绪探针) | 指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。初始延迟之前的就绪态的状态值默认为 Failure。如果容器不提供就绪态探针,则默认状态为 Success。 |
startupProbe(启动探针) | 指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器,而容器依其 重启策略进行重启。如果容器没有提供启动探测,则默认状态为 Success。 |
如果想了解如何设置存活态、就绪态和启动探针的进一步细节,可以参阅 配置存活态、就绪态和启动探针。
何时该使用存活态探针?
特性状态:Kubernetes v1.0 [stable]
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针; kubelet 将根据 Pod 的
restartPolicy
自动执行修复操作。如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针, 并指定
restartPolicy
为 "Always" 或 "OnFailure"。
何时该使用就绪态探针?
特性状态:Kubernetes v1.0 [stable]
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着 Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,检查某个特定于 就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,你可以使用 启动探针。然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
Pod 优雅终止
由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时 “允许其体面地终止”
是很重要的。一般不应武断地使用 KILL
信号终止它们,导致这些进程没有机会完成清理操作。
设计的目标
是让你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除 操作终将完成。当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期, 而不是直接强制地杀死 Pod
。在存在强制关闭设施的前提下, kubelet 会尝试体面地终止 Pod。
通常情况下,容器运行时(container runtime)会发送一个 TERM 信号到每个容器中的主进程
。很多容器运行时都能够注意到容器镜像中 STOPSIGNAL
的值,并发送该信号而不是 TERM。一旦超出了体面终止限期,超出所计算时间点则认为 Pod 已死(dead),容器运行时会向所有剩余进程发送 KILL 信号,之后 Pod 就会被从 API 服务器 上移除。如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启, 集群会从头开始重试,赋予 Pod 完整的体面终止限期。
强制终止 Pod
注意:对于某些工作负载及其 Pod 而言,强制删除很可能会带来某种破坏。
默认情况下,所有的删除操作都会附有 30 秒钟的宽限期限
。kubectl delete 命令支持 --grace-period=<seconds> 选项
,允许你重载默认值, 设定自己希望的期限值。
将宽限期限强制设置为 0 意味着立即从 API 服务器删除 Pod
。如果 Pod 仍然运行于某节点上,强制删除操作会触发 kubelet
立即执行清理操作。
说明:你必须在设置 --grace-period=0 的同时额外设置 --force 参数才能发起强制删除请求。
执行强制删除操作时,API 服务器不再等待来自 kubelet 的、关于 Pod 已经在原来运行的节点上终止执行的确认消息。API 服务器直接删除 Pod 对象,这样新的与之同名的 Pod 即可以被创建。在 Node 节点侧,被设置为立即终止的 Pod 仍然会在被强行杀死之前获得一点点的宽限时间。
已终止 Pod 的垃圾收集
对于 已失败的 Pod
而言,对应的 API 对象仍然会保留在集群的 API 服务器上
,直到 用户或者控制器进程显式地 将其删除。
控制面组件会在 Pod 个数超出所配置的阈值 (根据 kube-controller-manager 的 terminated-pod-gc-threshold 设置)时 删除已终止的 Pod(阶段值为 Succeeded 或 Failed)
。这一行为会避免随着时间演进不断创建和终止 Pod 而引起的 资源泄露问题
。
Pod 容器资源管理:https://kubernetes.io/zh-cn/docs/concepts/configuration/manage-resources-containers/ Pod 的生命周期 :https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/ 关于 Pod 更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/
Label
Label & Label Selector
Lable(标签) 是 k8s 系统中另外一个核心概念。一个标签是一个 key=value 的键值对,其中 key 和 vaule 由用户自己指定。Label 可以被附加到各种资源对象上
,例如 Node、Pod、Service、RC、RS、Deployment、DaemonSet 和 Job 等,一个资源对象可以定义任意数量的 Label,同一个 Label 也可以被添加到任意数量的资源对象上
。Label 通常在资源对象定义时确定,也可以在对象创建后动态添加或删除。
我们可以通过给指定的资源对象捆绑一个或多个不同的 Label 来实现多维度的资源分组管理功能,以便灵活、方便地进行资源分配、调度、配置、部署等管理工作。例如,部署不同版本的应用到不同的环境中;监控和分析应用(日志记录、监控、告警)等。一些常用的 Label 示例如下:
版本标签
:"release": "stable"、"release": "canary"。环境标签
:"environment": "dev"、"environment": "qa"、"environment": "production"。架构标签
:"tier": "frontend"、"tier": "backend"、"tier": "middleware"。分区标签
:"partition": "customerA"、"partition": "customerB"、"partition": "customerC"。质量管控标签
:"track": "daily"、"track": "weekly" 。
给某个资源对象定义一个 Label,随后即可通过 Label Selector(标签选择器)
查询和筛选拥有某些 Label 的资源对象,k8s 通过这种方式实现了类似 SQL
的简单又通用的对象查询机制。
举例,给 Pod 资源对象添加一个 Label (name=redis-slave),然后 Label Selector 作用于 Poid,等效如下的 sql 语句:
select * from pod where pod`s name = redis-slave
当前 Label Selector(标签选择器)有两种类型:
**
基于等式的(Equality-based)
**,使用等式类表达式匹配标签,例如:name = redis-slave、env != production 。**
基于集合的(Set-based)
**,使用集合操作类表达式匹配标签,例如:name in (redis-master, name-slave)、name notin(frontend) 。
可以通过多个 Label Selector 表达式的组合实现复杂的条件选择,多个表达式之间用 “,” 进行分隔,几个条件之间是 “AND” 的关系,即同时满足多个条件
,比如下面的例子:
name = redis-slave,env != production
name notin(frontend),env != production
matchLabels & matchExpressions
selector: matchLabels: app: mywebmatchExpressions: - {key: tier, operator: In, values: [frontend]}- {key: environment, operator: NotIn, values: [dev]}
matchLabels
用于定义一组 Label,与直接写在 Selector 中的作用相同;matchExpressions
用于定义一组集合的筛选条件,**可用的条件运算符包括:In、NotIn、Exists 和 DoesNotExist
**。
如果同时设置了 matchLabels 和 matchExpressions ,则两组条件为 AND 关系
,即需要同时满足所有条件的才能完成 Selector
的筛选。
Label Selector
在 k8s 中的 重要使用场景 如下:
kube-controller
进程通过在资源对象 RC上定义的 Label Selector 来筛选要监控 Pod 的副本数量,使 Pod 副本数量始终符合预期设定的全自动控制流程。kube-proxy
进程通过 Service 的 Label Selector 来选择对应的 Pod,自动建立每个 Service 到对应 Pod 的请求转发路由表,从而实现 Service 的智能负载均衡机制。通过对某些 Node 定义特定的 Label,并且在 Pod 定义文件中使用
NodeSelector
这种标签调度策略,kube-scheduler 进程可以实现 Pod 定向调度的特性
。
总之,使用 Label 可以给对象创建多组标签,Label 和 Label Selector 共同构成了 k8s 系统中核心的应用模型
,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。
关于 Label 和 Label Selector 更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/labels/
RC & RS
ReplicationController
ReplicationController(简称 RC)
是 k8s 系统中的核心概念之一,简单来说,它的作用是 确保在任何时候都有特定数量的 Pod 副本处于运行状态
。换句话说,RC 确保一个 Pod 或一组同类的 Pod 总是可用的。
所以 RC 的定义包括如下几个部分:
Pod 期待的副本数量
。用于筛选目标 Pod 的 Label Selector
。当 Pod 的副本数量小于预期数量时,用于创建新 Pod 的 Pod 模板(template)
。
下面是一个完整的 RC 例子,这个示例 ReplicationController 配置运行 nginx Web 服务器的三个副本。
apiVersion: v1
kind: ReplicationController
metadata:name: nginx
spec:replicas: 3 # 目标 Pod 的副本数量selector:app: nginxtemplate:metadata:name: nginxlabels:app: nginxspec:containers:- name: nginximage: nginxports:- containerPort: 80
可以使用以下命令检查 RC
的状态:
kubectl describe replicationcontrollers/nginx
如果当前目标 Pod 副本数为 2,则将其扩展至 3。
kubectl scale --current-replicas=2 --replicas=3 deployment/myweb
设置多个 RC
中 Pod 副本数量。
kubectl scale --replicas=5 rc/foo rc/bar rc/baz
关于 RC 的更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicationcontroller/
RC 的替代方案 —— ReplicaSet
RC 由于与 k8s 代码中的模块 ReplicationController
同名,同时 “ReplicationController ” 无法准确的表达它的本意
,所以在 k8s v1.2 版本中,升级为另一个新的概念 —— **ReplicaSet(简称 RS)**,官方解释为 RS 是 “下一代的 RC”,RS 是 RC 的超集
,RC
只支持基于等式的 Label Selector( equality-based selector)
,RS
同时还支持新的基于集合的 Label Selector(Set-based selector)
,这使得 RS 的功能更强。
RS 的工作原理
RS
是通过一组字段来定义的,包括一个用来识别可获得的 Pod 的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板等等。每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值, 进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。
RS
通过 Pod 上的 metadata.ownerReferences
字段连接到附属 Pod,该字段给出当前对象的属主资源。ReplicaSet 所获得的 Pod 都在其 ownerReferences
字段中包含了属主 ReplicaSet 的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态,并据此计划其操作行为。
RS
使用其选择算符来辨识要获得的 Pod 集合。如果某个 Pod 没有 OwnerReference
或者其 OwnerReference
不是一个控制器, 且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。
何时使用 RS
RS
确保任何时间都有指定数量的 Pod 副本在运行。然而,Deployment
是一个更高级的概念,它管理 RS
,并向 Pod 提供声明式的更新以及许多其他有用的功能。因此,我们建议使用 Deployment
而不是直接使用 RS
, 除非你需要自定义更新业务流程或根本不需要更新。
这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是使用 Deployment,并在 spec 部分定义你的应用
。
RS 的 yaml 文件定义式例
apiVersion: apps/v1
kind: ReplicaSet
metadata:name: frontendlabels:app: guestbooktier: frontend
spec:# 按你的实际情况修改副本数replicas: 3selector:matchLabels:tier: frontendtemplate:metadata:labels:tier: frontendspec:containers:- name: php-redisimage: gcr.io/google_samples/gb-frontend:v3
RS 主要被 Deployment
用来作为一种编排 Pod 创建、删除及更新的机制。推荐使用 Deployment 而不是直接使用 ReplicaSet
,除非你需要自定义更新编排或根本不需要更新。
RS 与 Deployment 这两个重要的资源对象逐步替代了之前的 RC 的作用
,是 k8s v1.3 版本里 Pod 自动扩容(伸缩)这个告警功能实现的基础,也将继续在 k8s 未来版本中发挥重要作用。
RC(或 RS) 的一些特性与作用:
在大多数情况下,通过定义一个 RC 实现 Pod 的创建及副本数量的自动控制
。在 RC 里包括完整的 Pod 定义模板
。RC 通过 Label Selector 机制实现对 Pod 副本的自动控制
。通过改变 RC 里 Pod 的副本数量,可以实现 Pod 的扩容和缩容
。通过改变 RC 里 Pod 模板中的镜像版本,可以实现 Pod 的滚动升级(Rolling Update)
。
关于 RS 的更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicaset/
Deployment
Deployment (推荐)是 K8s 在 v1.2 版本中引人的新概念,是一种更高级别的 API 对象,**用于更新其底层 ReplicaSet 及其 Pod,更好地解决 Pod 的编排问题
** 。为此,Deployment 在内部使用了 Replica Set 来实现目的
,无论从 Deployment 的作用与目的、YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作 RC 的一次升级,两者的相似度超过90%。如果你想要这种 滚动更新
功能,那么推荐使用 Deployment,因为它们是 声明式的、服务端的,并且具有其它特性
。
我们看下官方的定义:
一个 Deployment 为 Pod 和 ReplicaSet 提供声明式的更新能力
。
你负责描述 Deployment
中的 目标状态
,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态
。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 回收其资源。
以下是 Deployments 的典型用例:
创建
Deployment
以将ReplicaSet
上线。ReplicaSet 在后台创建 Pods。检查 ReplicaSet 的上线状态,查看其是否成功。通过更新
Deployment
的PodTemplateSpec
,声明 Pod 的新状态 。新的ReplicaSet
会被创建,Deployment
以受控速率将 Pod 从旧ReplicaSet
迁移到新ReplicaSet
。每个新的ReplicaSet
都会更新Deployment
的修订版本。如果
Deployment
的当前状态不稳定,回滚到较早的Deployment
版本。每次回滚都会更新Deployment
的修订版本。扩大
Deployment
规模以承担更多负载。暂停
Deployment
以应用对PodTemplateSpec
所作的多项修改, 然后恢复其执行以启动新的上线版本。使用
Deployment
状态来判定上线过程是否出现停滞。清理较旧的不再需要的
ReplicaSet
。
下面是一个创建 Deployment 的示例。其中 创建了一个 ReplicaSet,负责启动三个 nginx Pod
:
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploymentlabels:app: nginx
spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.14.2ports:- containerPort: 80
按照以下步骤创建上述 Deployment :
通过运行以下命令创建 Deployment :
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
运行
kubectl get deployments
检查 Deployment 是否已创建。如果仍在创建 Deployment,则输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 0/3 0 0 1s
在检查集群中的 Deployment 时,所显示的字段有:
NAME 列出了集群中 Deployment 的名称。
READY 显示应用程序的可用的“副本”数。显示的模式是“就绪个数/期望个数”。
UP-TO-DATE 显示为了达到期望状态已经更新的副本数。
AVAILABLE 显示应用可供用户使用的副本数。
AGE 显示应用程序运行的时间。
请注意期望副本数是根据 .spec.replicas
字段设置 3。
要查看 Deployment 上线状态,运行
kubectl rollout status deployment/nginx-deployment
。输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out
几秒钟后再次运行
kubectl get deployments
。输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 18s
注意 Deployment 已创建全部三个副本,并且所有副本都是最新的(它们包含最新的 Pod 模板) 并且可用。
要查看 Deployment 创建的 ReplicaSet(RS),运行
kubectl get rs
。输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-75675f5897 3 3 3 18s
ReplicaSet 输出中包含以下字段:
NAME 列出名字空间中 ReplicaSet 的名称;
DESIRED 显示应用的期望副本个数,即在创建 Deployment 时所定义的值。此为期望状态;
CURRENT 显示当前运行状态中的副本个数;
READY 显示应用中有多少副本可以为用户提供服务;
AGE 显示应用已经运行的时间长度。
【注意】ReplicaSet 的名称始终被格式化为
[Deployment名称]-[随机字符串]
。其中的随机字符串是使用 pod-template-hash 作为种子随机生成的。
要查看每个 Pod 自动生成的标签,运行
kubectl get pods --show-labels
。返回以下输出:
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
所创建的 ReplicaSet
确保总是存在三个 nginx Pod。
说明:你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: nginx)。标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符, 它们可能会发生冲突执行难以预料的操作。
关于 Deployment 的更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/
Horizontal Pod Autoscaler(HPA)
上面我们讲解过 RC、RS、Deployment
可以扩容或缩容 Pod 的副本数量,也可以手工执行 kubectl scale
命令实现 Pod 的扩容或缩容。如果 k8s 的功能仅仅到此为止,很显然不符合谷歌对 k8s 的定位目标 —— **自动化、智能化**
。在谷歌看来,分布式系统要能够根据当前负载的变化自动触发水平扩容或缩容
,因此这一过程可能是频繁发生的,不可预测的,所以手动触发控制的方式是不实际的。
因此,在 k8s v1.0 版本实现后,就有人在默默研究 Pod 智能化的扩容特性了,并在 k8s v1.1 版本中首次发布了重量级新特性 —— HorizontalPodAutoscaler(简称 HPA,Pod 水平自动扩缩)
。在 k8s 中,HPA 自动更新工作负载资源 (例如 Deployment 或者 StatefulSet),目的是自动扩缩工作负载以满足需求
。
水平扩缩意味着对增加的负载的响应是部署更多的 Pods。这与 “垂直(Vertical)/ VPA” 扩缩不同,对于 k8s, VPA 垂直扩缩意味着将更多资源(例如:内存或 CPU)分配给已经为工作负载运行的 Pod
。
如果负载减少,并且 Pod 的数量高于配置的最小值, HPA
会指示工作负载资源( Deployment、StatefulSet
或其他类似资源)缩减。
水平 Pod 自动扩缩不适用于无法扩/缩容的对象(例如:DaemonSet
)。
HPA
与之前的 RC、RS、Deployment
一样,也属于一种 k8s 资源对象,HPA 被实现为 k8s API 资源(Resources)和控制器(Controller)。资源决定了 Controller 控制器的行为。在 k8s 控制平面(kube-controller-manager)内运行的水平 Pod 自动扩 / 缩控制器会定期调整其目标(例如:Deployment)的所需规模,以匹配观察到的指标
, 例如:平均 CPU 利用率、平均内存利用率或你指定的任何其他自定义指标
。
HPA 工作原理
HPA 通过追踪分析指定 RC(目前 k8s 新版本已经使用 RS 替换 RC) 控制器的所有目标 Pod 的负载变化情况,来确定是否需要有针对性地调整目标 Pod 的副本数量,这是 HPA 的实现原理。
HPA 的常见用途是将其配置为从聚合 API (metrics.k8s.io、custom.metrics.k8s.io 或 external.metrics.k8s.io
)获取指标。metrics.k8s.io
API 通常由名为 Metrics Server
的插件提供,需要单独启动。
HPA 控制器访问支持扩缩的相应工作负载资源(例如:Deployments 和 StatefulSet
)。这些资源每个都有一个名为 scale
的子资源,该接口允许你动态设置副本的数量并检查它们的每个当前状态。
大概的控制流程如下:HPA => Deployment(内置 Scale )=> RS(内置 Scale )=> Pod
k8s 将 HPA 实现为一个间歇运行的控制回路(它不是一个连续的过程)。间隔由 kube-controller-manager
的 --horizontal-pod-autoscaler-sync-period
参数设置(默认间隔为 15 秒)。
在每个时间段内,kube-controller-manager
都会根据每个 HPA 定义中指定的指标查询资源利用率。控制器管理器找到由 scaleTargetRef
定义的目标资源,然后根据目标资源的 .spec.selector
标签选择 Pod, 并从 资源指标 API(针对每个 Pod 的资源指标)
或 自定义指标(适用于所有其他指标)
获取指标 API。
当前 HPA 有以下两种方式作为 Pod 负载的度量指标:
CPUUtilizationPercentage
,它是一个算术平均值,即目标 Pod 所有副本自身的 CPU 利用率的平均值。应用程序自定义的度量指标
,比如服务在每秒内的相应请求数(TPS 或 QPS)。
HPA 算法细节
从最基本的角度来看,HPA 控制器根据 当前指标(currentMetricValue)
和 期望指标(desiredMetricValue)
来计算扩缩比例。
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
CPUUtilizationPercentage
一个 Pod 自身 CPU 的利用率是该 Pod 当前 CPU 的使用量除以它的 Pod Request 的值。
举个 “栗子”🌰,比如,定义一个 Pod 的 Pod Request 的值为 0.4,而当前 Pod 的 CPU 使用量为 0.2 ,则它的 CPU 使用率为 50%【=》(0.2 / 0.4 ) * 100%】,这样就可以推算出一个 RC/RS/Deployment
控制的所有 Pod 副本的 CPU 利用率的算术平均值了。
如果某一时刻
CPUUtilizationPercentage
的值超过 80%,则意味着当前 Pod 副本数量很可能不足以支撑下来更多的请求,需要进行动态扩容,而在请求高峰时段过去后,Pod 的 CPU 利用率又会下降,此时对应的 Pod 副本数量应该自动缩减到一个合理的水平。如果目标 Pod 没有定义
Pod Request
的值,则无法使用 CPUUtilizationPercentage 实现 Pod 的水平自动扩缩容。
HPA 资源监控指标 —— Metrics Server
k8s 中为了更好的支持 HAP 和其他需要用到基础性能数据的功能模块,在 k8s v1.7 版本开始,自身孵化了一个 基础性能数据采集监控框架 —— k8s Monitoring Architecture
,在这其中 k8s 定义了一套 标准化的 API 接口 Resource Metrics API
,更佳方便了客户端应用程序(HPA)从 Metrics Server 中获取目标资源对象的性能数据,例如:容器的 CPU 和 Memory 内存的使用数据
。
【度量资源用量】
1、CPU 报告为以 cpu 为单位测量的平均核心使用率
。在 k8s 中, 一个 cpu 相当于云提供商的 1 个 vCPU/Core,以及裸机 Intel 处理器上的 1 个超线程。
该值是通过对内核提供的累积 CPU 计数器(在 Linux 和 Windows 内核中)取一个速率得出的。用于计算 CPU 的时间窗口显示在 Metrics API 的窗口字段下。
要了解更多关于 Kubernetes 如何分配和测量 CPU 资源的信息,请参阅 CPU 的含义。
2、内存(memory)报告为在收集度量标准的那一刻的工作集大小,以字节为单位
。
在理想情况下,“工作集”是在内存压力下无法释放的正在使用的内存量。然而,工作集的计算因主机操作系统而异,并且通常大量使用启发式算法来产生估计。
Kubernetes 模型中,容器工作集是由容器运行时计算的与相关容器关联的匿名内存。工作集指标通常还包括一些缓存(文件支持)内存,因为主机操作系统不能总是回收页面。
要了解有关 Kubernetes 如何分配和测量内存资源的更多信息, 请参阅 内存的含义。
在 CPUUtilizationPercentage 计算过程中使用到的 Pod 的 CPU 使用量通常是 1min 内的平均值,通过查询 Metrics Server
监控指标来得到该值(在 k8s 早期版本中使用 Heapster 来获取监控值)。
metrics-server
从 kubelet
中获取资源指标,并通过 Metrics API
在 k8s API 服务器中公开它们,以供 HPA
和 VPA
使用。你还可以使用 kubectl top
命令查看这些指标。
metrics-server
使用 k8s API 来跟踪集群中的 Node 和 Pod。metrics-server 服务器通过 HTTP 查询每个 Node 以获取指标。metrics-server 还构建了 Pod 元数据(metadata)
的内部视图,并维护 Pod 健康状况的缓存。缓存的 Pod 健康信息可通过 metrics-server 提供的扩展 API 获得。
例如,对于 HPA 查询,metrics-server 需要确定哪些 Pod 满足 Deployment 中的 标签选择器(Label Selector)
。
metrics-server 调用 kubelet API 从每个节点收集指标
。根据它使用的度量服务器版本:
版本 v0.6.0+ 中,使用指标资源端点
/metrics/resource
旧版本中使用 Summary API 端点
/stats/summary
关于
metrics-server
的更多信息,请查看:https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/#metrics-server
HPA 演练示例
基于多项度量指标和自定义度量指标自动扩缩,利用 autoscaling/v2
API 版本,你可以在自动扩缩 myweb-aspnetcore 这个 Deployment 时使用其他度量指标。
下面的 yaml 文件定义示例中,演示了三种度量指标:
resource metric(资源度量指标)
,它表示容器上指定资源的百分比,目前支持的资源有CPU
和Memory
。custom metrics(自定义度量指标)
,即Pod 度量指标
和Object 度量指标
, 这些度量指标可能具有特定于集群的名称,并且需要更高级的集群监控设置。
如果 yaml 文件中指定了多个上述类型的度量指标,HPA 将会依次考量各个指标并计算每一个指标所提议的副本数量,然后最终选择一个最高值。
需要注意的是,targetCPUUtilizationPercentage
字段已经被名为 metrics
的数组所取代。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:name: myweb-aspnetcore
spec:scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: myweb-aspnetcoreminReplicas: 1maxReplicas: 10metrics:- type: Resourceresource:name: cputarget:type: UtilizationaverageUtilization: 50- type: Podspods:metric:name: packets-per-secondtarget:type: AverageValueaverageValue: 1k- type: Objectobject:metric:name: requests-per-seconddescribedObject:apiVersion: networking.k8s.io/v1kind: Ingressname: main-routetarget:type: Valuevalue: 10k
status:observedGeneration: 1lastScaleTime: <some-time>currentReplicas: 1desiredReplicas: 1currentMetrics:- type: Resourceresource:name: cpucurrent:averageUtilization: 0averageValue: 0- type: Objectobject:metric:name: requests-per-seconddescribedObject:apiVersion: networking.k8s.io/v1kind: Ingressname: main-routecurrent:value: 10k
上的 yaml 文件定义中,HPA 将会尝试确保每个 Pod 的 CPU 利用率在 50% 以内,每秒能够服务 1000 个数据包请求,并确保所有在 Ingress 后的 Pod 每秒能够服务的请求总数达到 10000 个。
除了上面的定义 yaml 文件并且调用 kubectl create
命令来创建一个 HPA 资源对象的方式,还可以通过下面的简单命令行直接创建等价的 HPA 对象:
kubectl autoscale deployment myweb-aspnetcore --help
关于 HPA 更多的示例,请查看:https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
VerticalPodAutoscaler (VPA)
VPA 简介
与 HPA
不同,Vertical Pod Autoscaler(VPA) / Pod 垂直自动扩缩,使用户无需为其 Pod 中的容器设置最新的资源限制和请求
。配置后,它将根据使用情况自动设置请求,从而允许对节点进行适当的调度,以便为每个 Pod 提供适当的资源量。它还将保持初始容器配置中指定的限制和请求之间的比率。
VPA 既可以缩小过度请求资源的 Pod,也可以根据资源随时间推移的使用情况对资源请求不足的升级 Pod
。
自动缩放配置了一个名为 VerticalPodAutoscaler 的 自定义资源定义对象。它允许指定哪些 Pod 应垂直自动缩放,以及是否/如何应用资源建议。
VPA 版本介绍
VPA 当前默认版本是 v0.11.0
,各版本兼容性如下:
VPA 版本 | Kubernetes version |
---|---|
0.11 | 1.22+ |
0.10 | 1.22+ |
0.9 | 1.16+ |
0.8 | 1.13+ |
0.4 至 0.7 | 1.11+ |
0.3.X 及更低 | 1.7+ |
VPA 资源对象的 yaml 文件示例
关于 VPA 的安装步骤,此处不再说明,请自行查阅相关资料文献,接下来我们看下 VPA 资源的 yaml 文件定义:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:name: nginx-vpa
spec:targetRef:apiVersion: "apps/v1"kind: Deploymentname: nginx
updatePolicy:updateMode: "Off"
resourcePolicy:containerPolicies:- containerName: '*'minAllowed:cpu: 20mmemory: 50MimaxAllowed:cpu: 1memory: 500MicontrolledResources: ["cpu", "memory"]
---
apiVersion: apps/v1
kind: Deployment
metadata:name: nginxlabels:app: nginx
spec:replicas: 2selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.7.8ports:- containerPort: 80resources:requests:cpu: 100mmemory: 250Mi
关于 VPA 的更多信息,请查看:https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler#readme https://discuss.kubernetes.io/t/vertical-pod-autoscaler-vpa-not-updating-request-limits/18603
DaemonSet
DaemonSet 的功效与应用
DaemonSet(简称 DS) 确保全部(或者某些)Node 上运行一个 Pod( Container 容器)的副本
。当有 Node 加入集群时, 也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet
的一些典型用法:
日志收集
,在每个节点上运行日志收集守护进程,比如fluentd,logstash
等。系统监控
,在每个节点上运行监控守护进程,比如Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond
等。系统程序
,在每个节点上运行集群守护进程,比如kube-proxy, kube-dns, glusterd, ceph
等。
一种简单的用法是
为每种类型的守护进程在所有的节点上都启动一个 DaemonSet
。一个稍微复杂的用法是
为同一种守护进程部署多个 DaemonSet
;
每个具有不同的标志,并且对不同硬件类型具有不同的 Memory(内存)、CPU 要求
。
DaemonSet 的 yaml 定义文件示例
例如,使用 Fluentd
收集日志的 YAML 文件定义:
apiVersion: apps/v1
kind: DaemonSet
metadata:name: fluentd-elasticsearchnamespace: kube-systemlabels:k8s-app: fluentd-logging
spec:selector:matchLabels:name: fluentd-elasticsearchtemplate:metadata:labels:name: fluentd-elasticsearchspec:tolerations:# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们- key: node-role.kubernetes.io/control-planeoperator: Existseffect: NoSchedule- key: node-role.kubernetes.io/masteroperator: Existseffect: NoSchedulecontainers:- name: fluentd-elasticsearchimage: quay.io/fluentd_elasticsearch/fluentd:v2.5.2resources:limits:cpu: 300mmemory: 500Mirequests:cpu: 100mmemory: 200MivolumeMounts:- name: varlogmountPath: /var/log- name: varlibdockercontainersmountPath: /var/lib/docker/containersreadOnly: trueterminationGracePeriodSeconds: 30volumes:- name: varloghostPath:path: /var/log- name: varlibdockercontainershostPath:path: /var/lib/docker/containers
DaemonSet 与 Deployments 的区别
DaemonSet 与 Deployments 非常类似
, 它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。
建议为无状态的服务使用 Deployments
,比如前端服务。对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。当需要 Pod 副本总是运行在全部或特定主机上
,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时,应该使用 DaemonSet
。
例如,网络插件
通常包含一个以 DaemonSet
运行的组件。这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。
关于
DaemonSet
的更多信息,请查看:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/daemonset/#running-pods-on-only-some-nodes
除了 DaemonSet
外,以下这些特性也可以做到类似 DaemonSet 的功效,接下来我们会逐个介绍。
将 Pod 分配给指定 Node (DS 的等效替换方案)
DaemonSet
会忽略 Node 的 unschedulable
状态,有两种方式来指定 Pod 只运行在指定的 Node节点上:
nodeSelector
:只调度到匹配指定 label 的 Node 上。nodeAffinity
:功能更丰富的 Node 选择器,比如支持集合操作。podAffinity
:调度到满足条件的 Pod 所在的 Node 上。
nodeSelector
nodeSelector
提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。
【nodeSelector 示例】
首先给 Node
打上标签 label
kubectl label nodes node-01 disktype=ssd
然后在 daemonset
中指定 nodeSelector
为 disktype=ssd
:
spec:nodeSelector:disktype: ssd
nodeAffinity(亲和性)& podAntiAffinity(反亲和性)
与 nodeSelector
相比,亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:
亲和性(nodeAffinity)与反亲和性(podAntiAffinity)语言的表达能力更强。nodeSelector 只能选择拥有所有指定标签的 Node。亲和性、反亲和性为你提供对选择逻辑的更强控制能力。
你可以标明某规则是 “软需求” 或者 “偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。
你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用 Node 本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。
亲和性功能由两种类型的亲和性组成:
节点亲和性(nodeAffinity)
功能类似于nodeSelector
字段,但它的表达能力更强,并且允许你指定软规则。Pod 间亲和性(podAffinity)
&反亲和性(podAntiAffinity)
允许你根据其他 Pod 的标签来约束 Pod。
说明:
如果你同时指定了
nodeSelector
和nodeAffinity
,两者 必须都要满足,才能将 Pod 调度到候选节点上。如果你指定了多个与
nodeAffinity
类型关联的nodeSelectorTerms
, 只要其中一个nodeSelectorTerms
满足的话,Pod 就可以被调度到节点上。如果你指定了多个与同一
nodeSelectorTerms
关联的matchExpressions
, 则只有当所有matchExpressions
都满足时 Pod 才可以被调度到节点上。
亲和性与反亲和性是 Pod 中的属性,更多信息请查看:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity
weight(节点亲和性权重)
你可以为 preferredDuringSchedulingIgnoredDuringExecution
亲和性类型的每个实例设置 weight
字段,其取值范围是 1 到 100
。当调度器找到能够满足 Pod 的其他调度请求的节点时,调度器会遍历节点满足的所有的偏好性规则, 并将对应表达式的 weight 值加和
。
最终的加和值会添加到该节点的其他优先级函数的评分之上。在调度器为 Pod 作出调度决定时,总分最高的节点的优先级也最高。
nodeAffinity(节点亲和性)
nodeAffinity 目前支持两种:
requiredDuringSchedulingIgnoredDuringExecution
(硬性)必须满足条件。调度器只有在规则被必须满足的时候才能执行调度。此功能类似于nodeSelector
, 但其语法表达能力更强。preferredDuringSchedulingIgnoredDuringExecution
(软性,可选)尝试满足条件。调度器会尝试寻找满足对应规则的节点。如果找不到匹配的 Node,调度器仍然会调度该 Pod。
【nodeAffinity 示例】
比如下面的例子代表调度到包含 label
标签 kubernetes.io/e2e-az-name
并且 values
值为 e2e-az1
或 e2e-az2
的 Node上,并且优选还带有标签 another-node-label-key=another-node-label-value
的 Node。
apiVersion: v1
kind: Pod
metadata:name: with-node-affinity
spec:affinity:nodeAffinity:# 必须满足条件requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/e2e-az-nameoperator: Invalues:- e2e-az1- e2e-az2# 优先满足条件preferredDuringSchedulingIgnoredDuringExecution:# 节点亲和性权重- weight: 1preference:matchExpressions:- key: another-node-label-keyoperator: Invalues:- another-node-label-valuecontainers:- name: with-node-affinityimage: gcr.io/google_containers/pause:2.0
podAffinity (Pod 间亲和性)
podAffinity 基于 Pod 的标签来选择 Node,仅调度到满足条件 Pod 所在的 Node 上
,支持 podAffinity(Pod 亲和性)
和 podAntiAffinity(Pod 反亲和性)
。这个功能比较绕,举个 “栗子”🌰:
【podAffinity 示例】
如果一个 “ Node 所在 Zone 中包含至少一个带有
security=S1
标签且运行中的 Pod ”,那么可以调度到该 Node 。不调度到 “包含至少一个带有
security=S2
标签且运行中 Pod” 的 Node 上。
apiVersion: v1
kind: Pod
metadata:name: with-pod-affinity
spec:affinity:# pod 亲和性podAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: securityoperator: Invalues:- S1topologyKey: failure-domain.beta.kubernetes.io/zone# pod 反亲和性podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: securityoperator: Invalues:- S2topologyKey: kubernetes.io/hostnamecontainers:- name: with-pod-affinityimage: gcr.io/google_containers/pause:2.0
Taint(污点)和 Toleration(容忍度)
nodeAffinity(节点亲和性)
是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点 (这可能出于一种偏好,也可能是硬性要求)。污点(Taint)
则相反——它使节点能够排斥一类特定的 Pod。
容忍度(Toleration)
是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。
Static Pod(静态Pod)
除了 DaemonSet
,还可以使用 静态 Pod
来在每台机器上运行指定的 Pod,这需要 kubelet
在启动的时候指定 manifest
目录:
kubelet --pod-manifest-path=/etc/kubernetes/manifests
然后将所需要的 Pod 定义文件放到指定的 manifest
目录中。
【注意】静态 Pod 不能通过
API Server
来删除,但可以通过删除manifest
文件来自动删除对应的 Pod。
未完待续
先分享到这里,想了解更多 k8s 中常用的资源对象,敬请期待 下一篇文章 继续讲解。