目录
pod启动创建过程
这里有三个 List-Watch,分别是 Controller Manager(运行在 Master),Scheduler(运行在 Master),kubelet(运行在 Node)。 他们在进程已启动就会监听(Watch)APIServer 发出来的事件。
用户通过 kubectl 或其他 API 客户端提交创建 Pod 的请求给 APIServer
APIServer 将 Pod 对象的元数据尝试存入 etcd 中,待写入操作执行完成,APIServer返回确认信息给客户端。
etcd 接受创建 Pod 的信息后,在集群中保存该信息,并触发 Create 事件给APIserver。
Controller Manager 监听到 Create 事件后,调用 Replication Controller 来确保 Node 上的副本数量符合定义。一旦副本数量少于 RC 中定义的数量,RC 会自动创建副本。总之它是保证副本数量的 Controller(PS:扩容缩容的担当)。
Replication Controller 创建 Pod 的副本。
APIServer 更新 etcd 中的 Pod 信息,并向 Controller Manager 发送更新事件。
Scheduler 监听到更新事件后,根据调度算法将 Pod 绑定到合适的 Node 上。
Scheduler 更新 Pod 的信息,包括它所部署的 Node,然后将信息反馈给 APIServer。
APIServer 更新 etcd 中的 Pod 信息,并将更新成功的事件发送给 Scheduler。
kubelet 在 Node 上监听 APIServer 发送的 Pod 更新事件,根据更新信息调用 Docker 启动容器。
kubelet 将 Pod 的状态信息反馈给 APIServer,并更新至 etcd。
APIServer 将 Pod 的状态信息持久化存储在 etcd 中,APIServer将确认信息发送至相关的 kubelet,事件将通过它被接受,完成整个启动创建过程。
kubelet持续监听的原因
kubelet持续监听的原因在于保持对集群中 Pod 状态的实时感知和响应。即使创建过程完成,kubelet仍然需要:
实时调整资源: 如果通过 kubectl 进行扩容或缩容操作,kubelet会感知这些变化,根据最新的 Pod 副本数量,调整 Node 上的资源。
镜像更新: 如果 Pod 的镜像文件升级,kubelet会自动获取最新的镜像文件并加载,确保 Pod 使用的是最新的容器镜像。
通过持续监听,kubelet能够及时响应各种变化,确保集群中的 Pod 始终处于最新、正确的状态。这种实时性是 Kubernetes 系统的关键特性之一。
调度概念
Kubernetes集群调度是指Kubernetes系统中的调度器(scheduler)负责将应用程序的工作负载(如Pod)分配到可用的集群节点上。调度器通过考虑诸如节点资源利用率、硬件约束、亲和性和反亲和性规则等因素,选择最佳的节点来放置Pod,以实现高效的资源利用和负载均衡。调度器的工作包括评估节点的可用资源、满足Pod的资源需求、遵循用户定义的调度策略等。
Kubernetes 是通过 List-Watch 机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。
用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面建立 Pod 和 Container。
APIServer 经过 API 调用,权限控制,调用资源和存储资源的过程,实际上还没有真正开始部署应用。这里 需要 Controller Manager、Scheduler 和 kubelet 的协助才能完成整个部署过程。
在 Kubernetes 中,所有部署的信息都会写到 etcd 中保存。实际上 etcd 在存储部署信息的时候,会发送 Create 事件给 APIServer,而 APIServer 会通过监听(Watch)etcd 发过来的事件。其他组件也会监听(Watch)APIServer 发出来的事件。
调度约束
Kubernetes中的调度约束,这些约束是指在将Pod调度到节点上时需要考虑的各种条件和限制。这些约束包括但不限于:
资源约束: 每个节点有一定的资源限制,如CPU和内存。调度器会考虑Pod的资源请求和节点的可用资源,确保Pod能够得到满足并不会超出节点的资源容量。
亲和性和反亲和性: 可以指定Pod之间或Pod与节点之间的亲和性和反亲和性规则,以确保它们被调度到合适的节点上。例如,可以将Pod调度到与特定标签匹配的节点上,或者避免将它们调度到某些节点上。
节点亲和性: 通过节点亲和性规则,可以指定Pod应该调度到与某些节点属性匹配的节点上。例如,将Pod调度到具有特定硬件特性或数据存储的节点上。
Pod 亲和性: 通过Pod亲和性规则,可以确保一组Pod被调度到同一节点上,或者避免它们被调度到同一节点上。
污点和容忍: 污点用于标记节点上的不可接受条件,而容忍则允许一些Pod调度到带有污点的节点上。这有助于将特定类型的工作负载调度到适合的节点上。
这些约束通过Kubernetes的调度器实现,调度器会根据这些约束选择最佳的节点来放置Pod,以满足用户需求并确保集群的高效利用。
调度过程
Kubernetes(k8s)调度涉及调度器、控制器管理器和kubelet。当使用kubectl
创建作业时,kube-controller检测到它,创建一个Pod实例,并将其发送到API服务器。kube-scheduler在检测到未调度的Pod时,选择一个节点,将Pod绑定到它,并向API服务器发送绑定指令。相应节点上的kubelet在检测到绑定指令后,指示节点的容器API运行Pod。这个过程确保了Kubernetes集群中资源的有效分配和工作负载的分发。
优点
Scheduler是Kubernetes的调度器,其主要任务是将定义的Pod分配到集群的节点上。
公平:确保每个节点都能获得公平的资源分配,避免资源不均衡。
资源高效利用:最大化集群中所有资源的利用率,确保资源被充分利用而不浪费。
效率:调度器需要具备良好的性能,能够快速而有效地对大批量的Pod完成调度工作,以满足用户的需求。
灵活:允许用户根据自己的需求控制调度的逻辑,例如通过标签、调度策略等方式定制调度规则,以适应不同的应用场景和需求。
原理
调度器作为一个独立的程序运行,并监听 API Server,负责将未指定节点的 Pod 分配到合适的节点上。整个调度过程分为预算策略(predicate)和优选策略(priorities)两个阶段,分别用于先过滤不满足条件的节点和对通过的节点进行排序。最后,从中选择优先级最高的节点进行调度。如果在任何一个阶段出现错误,调度器会立即返回错误信息。
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。 经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。
优先级选项
Kubernetes Pod 调度过程中使用的一系列优先级选项及其作用:
LeastRequestedPriority(最低资源请求优先级):根据节点的 CPU 和内存使用率来确定权重,使用率越低的节点权重越高。这意味着优先选择资源利用率较低的节点来部署 Pod。
BalancedResourceAllocation(平衡资源分配优先级):根据节点上 CPU 和内存使用率的平衡程度来确定权重。如果节点上的 CPU 和内存使用率越接近,权重越高。通常与最低资源请求优先级一起使用,以便在选择节点时考虑资源的平衡分配。
ImageLocalityPriority(镜像本地性优先级):倾向于选择已经存在所需镜像的节点部署 Pod。权重根据镜像的总大小来确定,镜像总大小越大,权重越高。
示例
举例来说,假设有三个 Kubernetes 节点:node01、node02 和 node03。现在有一个 Pod 需要调度到其中一个节点上。
LeastRequestedPriority(最低资源请求优先级):如果 node01 的 CPU 和内存使用率较低,node02 的使用率中等,而 node03 的使用率较高,那么优先选择调度到 node01,因为它的资源使用率最低。
BalancedResourceAllocation(平衡资源分配优先级):假设 node01 的 CPU 使用率为20%,内存使用率为60%,而 node02 的 CPU 和内存使用率均为50%,则根据平衡资源分配优先级,可能更倾向于选择 node02,因为它的资源使用情况更平衡。
ImageLocalityPriority(镜像本地性优先级):如果 node01 已经存在 Pod 所需的镜像,而 node02 和 node03 没有,那么优先选择调度到 node01,因为它具有镜像的本地性。
指定调度节点
pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配
vim myapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
nodeName: node01
containers:
- name: myapp
image: soscscs/myapp:v1
ports:
- containerPort: 80
kubectl apply -f myapp.yaml
kubectl get pods -o wide
apiVersion: apps/v1
: 指定了使用的 Kubernetes API 版本。kind: Deployment
: 定义了这个配置文件描述的 Kubernetes 资源类型,即部署。metadata
: 包含有关资源的元数据,比如名称。name: myapp
: 指定部署的名称为 "myapp"。spec
: 定义了部署的规格,包括副本数量、选择器和模板。replicas: 3
: 指定了要创建的 Pod 的副本数量为 3。selector
: 定义了用于选择要管理的 Pod 的标签。matchLabels
: 指定了匹配标签的条件。app: myapp
: 指定了标签app
的值为myapp
。
template
: 定义了要创建的 Pod 的模板。metadata
: 包含了 Pod 模板的元数据,包括标签。labels
: 指定了 Pod 模板的标签。app: myapp
: 指定了标签app
的值为myapp
。
spec
: 指定了 Pod 的规格。nodeName: node01
: 使用nodeName
字段将 Pod 直接调度到名为node01
的节点上。containers
: 指定了要在 Pod 中运行的容器列表。name: myapp
: 定义了容器的名称为myapp
。image: soscscs/myapp:v1
: 指定了要在容器中运行的镜像。ports
: 指定了容器要监听的端口。containerPort: 80
: 指定了容器监听的端口号为 80。
通过应用此配置文件,成功地创建了一个名为 myapp
的 Deployment,其中包含了三个 Pod,每个 Pod 都直接调度到了名为 node01
的节点上,并且每个 Pod 内运行一个名为 myapp
的容器,该容器使用 soscscs/myapp:v1
镜像并监听端口 80。
kubectl describe pod myapp-
查看详细事件,发现未经过 scheduler 调度分配
这些事件显示了 Pod 的创建过程,但没有显示与调度相关的事件,这是因为在 Pod 的配置中明确指定了 nodeName: node01
,这意味着 Pod 将直接调度到名为 node01
的节点上,而不经过调度器的调度分配过程。
因此,在描述中看不到与调度相关的事件,而只能看到与 Pod 创建、拉取镜像、容器创建和启动等过程相关的事件。
pod.spec.nodeSelector:通过 kubernetes 的 label-selector 机制选择节点,由调度器调度策略匹配 label,然后调度 Pod 到目标节点,该匹配规则属于强制约束
标签基本操作
获取标签帮助
kubectl label --help
添加标签(Add Labels):
添加标签是为资源附加额外的元数据,以便更容易地对其进行标识和分类。例如,为名为nginx
的Pod添加标签app=web
:
kubectl label <资源类型> <资源名称> <标签键>=<标签值>
kubectl label pods nginx app=web
这将给名为
nginx
的Pod添加了一个名为app
,值为web
的标签。添加标签是为资源附加额外的元数据,以便更容易地对其进行标识和分类。例如,为名为
nginx
的Pod添加标签app=web
:
更新标签(Update Labels)
更新标签是对已存在的标签进行更改。例如,将名为nginx
的Pod的app
标签的值从web
更改为frontend
:
kubectl label <资源类型> <资源名称> <标签键>=<新标签值> --overwrite
kubectl label pods nginx app=frontend --overwrite
- 使用
--overwrite
标志来确保标签的值被覆盖。
删除标签(Remove Labels)
删除标签是从资源中删除指定的标签。例如,从名为nginx
的Pod中删除app
标签:
kubectl label <资源类型> <资源名称> <标签键>-
kubectl label pods nginx app-
删除一个 label,只需在命令行最后指定 label 的 key 名并与一个减号相连即可
这将从Pod中删除名为
app
的标签。
查询标签(Query Labels)
查询标签是查找具有特定标签的资源。例如,查找具有app=web
标签的所有Pod:
kubectl get <资源类型> -l <标签选择器>
kubectl get pods -l app=web
这将返回所有具有app=web
标签的Pod列表。
使用标签选择器(Label Selectors)
spec:
selector:
<标签键>: <标签值>
在定义资源时使用标签选择器,以便在对资源进行操作时能够指定匹配的标签。例如,定义一个Service并选择具有特定标签的Pod:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
在这个例子中,Service选择了具有app=web
标签的Pod作为其后端。
标签匹配的运算符
In:表示 label 的值必须在指定的列表中。
例如:
key: "app", operator: In, values: ["web", "db"]
表示 "app" 的值必须是 "web" 或 "db"。NotIn:表示 label 的值不能在指定的列表中。
例如:
key: "env", operator: NotIn, values: ["dev", "test"]
表示 "env" 的值不能是 "dev" 或 "test"。Gt:表示 label 的值必须大于指定的值。
例如:
key: "replica", operator: Gt, values: ["3"]
表示 "replica" 的值必须大于 3。Lt:表示 label 的值必须小于指定的值。
例如:
key: "version", operator: Lt, values: ["2.0"]
表示 "version" 的值必须小于 2.0。Exists:表示某个 label 必须存在。
例如:
key: "app", operator: Exists
表示 "app" 必须存在。DoesNotExist:表示某个 label 不能存在。
例如:
key: "deprecated", operator: DoesNotExist
表示 "deprecated" 不能存在。这些运算关系在定义亲和性规则时用于匹配标签的条件
亲和性
在 Kubernetes 中,"亲和性"(Affinity)是一种机制,用于控制 Pod 如何被调度到节点上。亲和性规则允许你指定 Pod 与特定节点或其他 Pod 之间的偏好关系。通过使用亲和性,可以确保相关的 Pod 在同一节点上运行,或者避免将它们调度到同一节点上,以提高性能、可用性或安全性。
亲和性在 Kubernetes 中有两种类型:节点亲和性和 Pod 亲和性。节点亲和性定义了 Pod 与节点之间的关系,而 Pod 亲和性定义了 Pod 与其他 Pod 之间的关系。
节点亲和性:
通过 nodeAffinity 字段指定,可以使用 nodeSelector 或 nodeSelectorTerms 定义节点上的标签和条件,以确保 Pod 被调度到满足这些条件的节点上。
pod.spec.nodeAffinity
中的preferredDuringSchedulingIgnoredDuringExecution
是软策略,表示首选但不是强制要求。系统会尽量遵循这些偏好,但在无法满足时也可以进行调度。pod.spec.nodeAffinity
中的requiredDuringSchedulingIgnoredDuringExecution
是硬策略,表示 Pod 必须满足这些条件才能被调度。如果无法满足条件,Pod 将不会被调度。
Pod 亲和性:
通过 affinity 字段中的 podAffinity 和 podAntiAffinity 字段定义。podAffinity 用于描述 Pod 与其他 Pod 的关系,而 podAntiAffinity 用于定义 Pod 与其他 Pod 的排斥关系。可以使用 labelSelector 和 topologyKey 来定义匹配规则。
pod.spec.affinity.podAffinity
和pod.spec.affinity.podAntiAffinity
中的preferredDuringSchedulingIgnoredDuringExecution
是软策略,表示首选但不是强制要求。系统会尽量遵循这些偏好,但在无法满足时也可以进行调度。pod.spec.affinity.podAffinity
和pod.spec.affinity.podAntiAffinity
中的requiredDuringSchedulingIgnoredDuringExecution
是硬策略,表示 Pod 必须满足这些条件才能被调度。如果无法满足条件,Pod 将不会被调度。
硬策略和软策略
在 Kubernetes 中,硬策略和软策略通常用于定义 Pod 的亲和性或节点的亲和性。这些策略决定了 Kubernetes 调度器在安排 Pod 时的行为。
硬策略(requiredDuringSchedulingIgnoredDuringExecution):
当使用硬策略时,调度器必须严格遵守亲和性规则。这意味着 Pod 必须满足定义的亲和性条件才能被调度到节点上。如果无法满足条件,Pod 将会一直处于 Pending 状态,直到满足条件为止。即使在运行时,如果条件不再满足,Pod 也不会被迁移到其他节点。
软策略(preferredDuringSchedulingIgnoredDuringExecution):
当使用软策略时,调度器会尽量遵守定义的亲和性规则,但不是必须的。如果有多个节点符合软策略的条件,调度器会倾向于选择满足条件的节点,但如果无法满足条件,调度器仍然可以将 Pod 调度到其他节点上。如果在运行时条件不再满足,Pod 也不会被迁移到其他节点。
这两种策略提供了灵活性和可靠性之间的权衡。硬策略确保 Pod 被调度到满足条件的节点上,但可能会导致更多的 Pod 无法调度。软策略提供了更灵活的调度,但可能会导致一些 Pod 被调度到不太理想的节点上。在设计亲和性规则时,需要根据具体情况选择适当的策略来平衡资源利用率和可用性要求
硬策略示例
# 创建目录
mkdir /opt/affinity
# 切换目录
cd /opt/affinity
# 编辑 Pod 配置文件
vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
# 定义节点亲和性
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname # 指定node的标签
operator: NotIn # 设置Pod安装到kubernetes.io/hostname的标签值不在values列表中的node上
values:
- node02
# 应用 Pod 配置
kubectl apply -f pod1.yaml
# 获取 Pod 列表
kubectl get pods -o wide
# 删除所有 Pod,重新应用配置,并查看 Pod 列表
kubectl delete pod --all && kubectl apply -f pod1.yaml && kubectl get pods -o wide
# 如果硬策略不满足条件,Pod 状态一直会处于 Pending 状态。
这个示例中,创建了一个名为 affinity
的 Pod,并且定义了节点亲和性规则,使用了硬策略 requiredDuringSchedulingIgnoredDuringExecution
。逐步解析这个示例:
metadata
部分指定了 Pod 的元数据,包括名称和标签。spec
部分定义了 Pod 的规格,其中包含了容器的相关信息。在
affinity
字段下,定义了节点亲和性规则。这里使用了nodeAffinity
,表示节点亲和性。在
requiredDuringSchedulingIgnoredDuringExecution
中,指定了节点选择器条件。matchExpressions
用于指定匹配条件,key
指定了要匹配的标签键,这里是kubernetes.io/hostname
,即节点的主机名。operator
设置为NotIn
,表示标签值不在指定的列表中。而values
则列出了不允许的节点主机名,这里只有一个节点node02
。最后,应用了这个 Pod 的配置文件,并通过
kubectl get pods -o wide
命令来查看 Pod 的状态和所在的节点。由于节点亲和性规则指定了 Pod 不能运行在node02
上,所以 Pod 被成功调度到了node01
上,状态为Running
。最后的删除并重新创建操作是为了再次验证规则,确保当条件不满足时,Pod 状态会一直保持在
Pending
。
这个示例清晰地展示了硬策略的行为:当指定的条件无法满足时,Pod 将无法被调度,并且状态会一直保持在 Pending
。
软策略示例
# 编辑 Pod2 配置文件
vim pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
# 定义节点亲和性,优先使用软策略
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 # 如果有多个软策略选项的话,权重越大,优先级越高
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node03
# 应用 Pod2 配置
kubectl apply -f pod2.yaml
# 获取 Pod 列表
kubectl get pods -o wide
这个YAML文件描述了一个Pod对象,其中包含一个名为affinity
的Pod。
apiVersion
: 指定了Kubernetes API的版本,这里使用的是v1
版本。kind
: 定义了Kubernetes对象的类型,这里是一个Pod
对象。metadata
: 包含了有关该Pod的元数据,包括名称(name
)和标签(labels
)。name
: 指定了Pod的名称为affinity
。labels
: 为Pod添加了一个标签,标签的键为app
,值为node-affinity-pod
。spec
: 包含了Pod的规格,即容器和其他配置信息。containers
: 定义了Pod中的容器列表。name
: 指定了容器的名称为with-node-affinity
。image
: 指定了容器所使用的镜像为soscscs/myapp:v1
。
affinity
: 定义了Pod的亲和性规则。nodeAffinity
: 定义了节点亲和性规则。preferredDuringSchedulingIgnoredDuringExecution
: 指定了软节点亲和性规则,即在调度时优先考虑,但在执行时忽略。weight
: 指定了该规则的权重为1,权重越大,优先级越高。preference
: 指定了偏好项,即优先调度到具有特定主机名的节点。matchExpressions
: 定义了匹配表达式。key
: 指定了匹配的键为kubernetes.io/hostname
,即主机名。operator
: 指定了匹配操作符为In
,表示匹配主机名列表中的任何一个值。values
: 指定了匹配的主机名列表,这里只有一个值node03
。
总结:该Pod对象具有一个软节点亲和性规则,表示优先调度到具有主机名为node03
的节点。权重为1,这意味着在具有多个软策略选项的情况下,优先级较高。然而,根据输出结果,该Pod最终被调度到了节点node02
上,这是由于node02
满足了调度要求并且优先级高于node03
。
软硬策略结合示例
如果把硬策略和软策略合在一起使用,则要先满足硬策略之后才会满足软策略
# 定义 Pod 配置
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
# 定义节点亲和性
affinity:
nodeAffinity:
# 先满足硬策略,排除有 kubernetes.io/hostname=node02 标签的节点
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- node02
# 再满足软策略,优先选择有 aaa=a 标签的节点
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: aaa
operator: In
values:
- a
这个示例中,定义了一个Pod对象,其中包含了硬节点亲和性规则和软节点亲和性规则。让我们分析一下:
requiredDuringSchedulingIgnoredDuringExecution
:这是一个硬节点亲和性规则,表示在调度时必须满足的条件。在这个规则中,指定了排除具有主机名为node02
的节点,即必须在不包括node02
的节点上调度该Pod。preferredDuringSchedulingIgnoredDuringExecution
:这是一个软节点亲和性规则,表示在调度时优先考虑的条件,但在执行时会被忽略。在这个规则中,指定了优先选择具有标签aaa=a
的节点,即希望将该Pod调度到具有aaa=a
标签的节点上。
根据这两个规则的组合,首先,调度器会先满足硬节点亲和性规则,即排除node02
节点。然后,在满足了硬性要求的节点中,调度器会优先考虑软节点亲和性规则,即选择具有aaa=a
标签的节点。这样,最终的调度结果将是在不包括node02
节点的节点中,优先选择具有aaa=a
标签的节点。
pod亲和和反亲和
在Kubernetes(k8s)中,Pod亲和性和反亲和性用于指定Pod与节点的关系。亲和性定义了Pod如何倾向于在同一节点上调度,而反亲和性则定义了Pod如何避免与特定节点调度在一起。
通过使用nodeSelector字段,你可以在PodSpec中指定节点标签,使Pod倾向于调度到具有特定标签的节点上,这就是亲和性的一种实现方式。相反,反亲和性可以通过tolerations字段实现,该字段定义了Pod可以容忍的节点上的污点,从而避免在这些节点上被调度。
这样的机制有助于优化Pod的调度,使其更好地适应节点资源和约束条件。
调度策略
当使用 Kubernetes 进行 Pod 调度时,可以使用三种不同的调度策略来控制 Pod 的部署位置,分别是 nodeAffinity
(节点亲和性)、podAffinity
(Pod 亲和性)和 podAntiAffinity
(Pod 反亲和性)。
节点亲和性(nodeAffinity):
匹配标签:对节点的标签进行匹配。
操作符:支持的操作符包括
In
、NotIn
、Exists
、DoesNotExist
、Gt
(大于)和Lt
(小于)等。拓扑域支持:不支持拓扑域,只考虑节点的标签。
调度目标:指定 Pod 应该调度到具有特定标签的节点上。
Pod 亲和性(podAffinity):
匹配标签:对其他 Pod 的标签进行匹配。
操作符:与节点亲和性相同,支持的操作符包括
In
、NotIn
、Exists
、DoesNotExist
等。拓扑域支持:支持拓扑域,可以指定 Pod 应该与其他 Pod 在同一拓扑域(比如同一节点)还是不同拓扑域。
调度目标:指定 Pod 应该与其他具有特定标签的 Pod 部署在同一拓扑域。
Pod 反亲和性(podAntiAffinity):
匹配标签:同样对其他 Pod 的标签进行匹配。
操作符:与节点亲和性和 Pod 亲和性相同。
拓扑域支持:同样支持拓扑域,可以指定 Pod 应该与其他 Pod 在同一拓扑域还是不同拓扑域。
调度目标:指定 Pod 应该与其他具有特定标签的 Pod 部署在不同的拓扑域,即不在同一节点或同一拓扑域。
这些调度策略可以帮助用户更灵活地控制 Pod 的部署,根据业务需求和系统架构,优化资源利用和提高容错能力。
Pod 亲和性调度策略的详细说明
调度条件:
仅当节点和至少一个已运行且具有标签键为“app”且值为“myapp01”的 Pod 处于同一拓扑域时,才可以将该 Pod 调度到节点上。
具体来说,如果节点 N 具有带有键为 lab 和某个值 V 的标签,则 Pod 有资格在节点 N 上运行,以便集群中至少有一个具有键为 lab和值为 V 的节点正在运行具有键“app”和值“myapp01”的 Pod。
topologyKey:
topologyKey
是节点标签的键。如果两个节点使用此键标记并且具有相同的标签值,则调度器会将这两个节点视为处于同一拓扑域中。调度器试图在每个拓扑域中放置数量均衡的 Pod。
拓扑域的定义:
如果 lab对应的值不一样就是不同的拓扑域。比如 Pod1 在 lab=a 的 Node 上,Pod2 在 lab=b 的 Node 上,Pod3 在 lab=a 的 Node 上,则 Pod2 和 Pod1、Pod3 不在同一个拓扑域,而 Pod1 和 Pod3 在同一个拓扑域。
示例
kubectl label node node01 lab=a
kubectl label node node02 lab=b
这两个命令的目的是给节点 node01
和 node02
分别打上标签 lab=a
和 lab=b
创建一个标签为 app=myapp01 的 Pod
vim pod3.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp01 # 设置 Pod 的名称为 myapp01
labels:
app: myapp01 # 给 Pod 添加标签 app: myapp01
spec:
containers:
- name: with-node-affinity # 定义容器名称为 with-node-affinity
image: soscscs/myapp:v1 # 使用镜像 soscscs/myapp:v1
kubectl apply -f pod3.yaml
kubectl get pods --show-labels -o wide
这段代码创建了一个名为 myapp01
的 Pod,并为其打上了标签 app=myapp01
。下面是对代码的说明:
vim pod3.yaml
:创建了一个名为pod3.yaml
的文件,并编辑该文件以定义 Pod 对象的配置。metadata
部分指定了 Pod 的名称和标签。spec
部分定义了 Pod 的规格,其中包含一个容器,使用镜像soscscs/myapp:v1
。kubectl apply -f pod3.yaml
:使用 Pod 配置文件pod3.yaml
创建 Pod 对象。kubectl get pods --show-labels -o wide
:显示所有 Pod 的信息,包括标签,以宽格式输出。可以看到myapp01
Pod 已经成功创建,并且具有标签app=myapp01
,并且运行在node01
节点上。
这样就成功创建了一个名为 myapp01
的 Pod,并为其打上了指定的标签。
使用 Pod 亲和性调度,创建多个 Pod 资源
vim pod4.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp02 # 设置 Pod 的名称为 myapp02
labels:
app: myapp02 # 给 Pod 添加标签 app: myapp02
spec:
containers:
- name: myapp02 # 定义容器名称为 myapp02
image: soscscs/myapp:v1 # 使用镜像 soscscs/myapp:v1
affinity: # 定义亲和性
podAffinity: # Pod 亲和性规则
requiredDuringSchedulingIgnoredDuringExecution: # 在调度期间要求的 Pod 亲和性规则,在执行期间被忽略
- labelSelector: # 标签选择器
matchExpressions: # 匹配表达式列表
- key: app # 标签键为 app
operator: In # 使用 In 操作符
values: # 值列表
- myapp01 # 匹配值为 myapp01
topologyKey: lab # 拓扑域键为 lab
kubectl apply -f pod4.yaml
kubectl get pods --show-labels -o wide
这段代码创建了一个名为 myapp02
的 Pod,并使用了 Pod 亲和性调度策略,以确保它与具有特定标签的其他 Pod 在同一拓扑域上。下面是对代码的说明:
vim pod4.yaml
:创建了一个名为pod4.yaml
的文件,并编辑该文件以定义 Pod 对象的配置。metadata
部分指定了 Pod 的名称和标签。spec
部分定义了 Pod 的规格,其中包含一个容器,使用镜像soscscs/myapp:v1
。affinity
部分定义了 Pod 的亲和性策略,这里使用了podAffinity
策略。requiredDuringSchedulingIgnoredDuringExecution
指定了 Pod 调度时必须满足的条件。labelSelector
指定了匹配其他 Pod 标签的条件,这里要求匹配的标签为app=myapp01
。topologyKey
指定了用于确定拓扑域的键,这里设置为lab
。
这段代码将确保创建的
myapp02
Pod 与具有标签app=myapp01
的其他 Pod 在同一拓扑域上调度。
使用 Pod 反亲和性调度
vim pod5.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp10 # Pod 的名称
labels:
app: myapp10 # Pod 的标签
spec:
containers:
- name: myapp10 # 容器的名称
image: soscscs/myapp:v1 # 容器所使用的镜像
affinity:
podAntiAffinity: # 反亲和性调度规则
preferredDuringSchedulingIgnoredDuringExecution: # 在调度时优先考虑的规则,执行时忽略
- weight: 100 # 权重
podAffinityTerm: # 匹配的 Pod 亲和性条件
labelSelector: # 标签选择器
matchExpressions: # 匹配的表达式
- key: app # 匹配的键
operator: In # 匹配操作符
values: # 匹配的值
- myapp01 # 与该 Pod 亲和的标签值
topologyKey: kubernetes.io/hostname # 拓扑域键
kubectl apply -f pod5.yaml
kubectl get pods --show-labels -o wide
- 这个示例展示了如何在 Pod 中使用反亲和性调度。在这个例子中,Pod
myapp10
的调度规则指定了反亲和性,即希望它不要与具有特定标签的其他 Pod 调度到同一个节点上。
vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp20 # Pod 的名称
labels:
app: myapp20 # Pod 的标签
spec:
containers:
- name: myapp20 # 容器的名称
image: soscscs/myapp:v1 # 容器所使用的镜像
affinity:
podAntiAffinity: # 反亲和性调度规则
requiredDuringSchedulingIgnoredDuringExecution: # 在调度时必须满足的规则,执行时忽略
- labelSelector: # 标签选择器
matchExpressions: # 匹配的表达式
- key: app # 匹配的键
operator: In # 匹配操作符
values: # 匹配的值
- myapp01 # 与该 Pod 亲和的标签值
topologyKey: lab # 拓扑域键
- 这样配置的 Pod 在调度时将会确保不与具有标签
app: myapp01
的其他 Pod 调度到同一个拓扑域上
由于指定 Pod 所在的 node01 节点上具有带有键 lab和标签值 a 的标签,node02 也有这个lab=a的标签,所以 node01 和 node02 是在一个拓扑域中,反亲和要求新 Pod 与指定 Pod 不在同一拓扑域,所以新 Pod 没有可用的 node 节点,即为 Pending 状态。
kubectl get pod --show-labels -owide
kubectl label nodes node02 lab=b --overwrite
kubectl get pod --show-labels -o wide
根据输出,可以看出新的 Pod
myapp20
因为反亲和性调度规则而处于 Pending 状态。由于 Podmyapp01
具有标签app=myapp01
,并且拓扑域键lab
的值为a
,而节点node01
和node02
都有拓扑域键lab
的标签,因此它们被认为处于相同的拓扑域。由于新的 Pod
myapp20
需要避免与具有标签app=myapp01
的 Pod 调度到同一拓扑域,但在目前的节点中,没有节点符合该条件,因此该 Pod 保持在 Pending 状态。更新了node02
的标签,但似乎仍然不符合新 Pod 的调度要求。最终,创建了一个新的 Pod
myapp21
,它成功在node02
上运行,因为现在node02
的标签为lab=b
,与之前的标签不同,因此不再满足与 Podmyapp20
的反亲和性调度要求。
污点(Taint) 和 容忍(Tolerations)
节点亲和性,是Pod的一种属性(偏好或硬性要求),它使Pod被吸引到一类特定的节点。Taint 则相反,它使节点能够排斥一类特定的 Pod。
Taint 和 Toleration 相互配合,可以用来避免 Pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 Pod,是不会被该节点接受的。如果将 toleration 应用于 Pod 上,则表示这些 Pod 可以(但不一定)被调度到具有匹配 taint 的节点上。
使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。
概念
污点(Taints):
污点是应用于节点的标记,用于阻止将新的Pod调度到带有该污点的节点上。
污点由键值对(key=value)组成,其中键表示污点的名称,而值表示污点的效果,例如NoSchedule、PreferNoSchedule和NoExecute。
通过在节点上设置污点,可以限制哪些Pod可以被调度到该节点上。
容忍度(Tolerations):
容忍度是Pod的属性,用于指定Pod可以被调度到带有特定污点的节点上。
每个容忍度包含键值对(key=value),其中键表示要容忍的污点的名称,而值表示要容忍的污点的效果。
Pod只有在其定义了与节点上污点匹配的容忍度时,才能被调度到带有该污点的节点上。
调度行为:
如果Pod的容忍度与节点上的污点匹配,那么该Pod可以被调度到带有该污点的节点上。
如果Pod的容忍度与节点上的污点不匹配,且污点的效果是NoSchedule或PreferNoSchedule,那么该Pod将不会被调度到该节点上。
如果Pod的容忍度与节点上的污点不匹配,但污点的效果是NoExecute,那么该节点上的已有Pod可能会被驱逐(Evicted)。
示例用法:
- 可以使用污点和容忍度来实现特定的调度需求,例如将某些特殊的Pod调度到专门的节点上,或者确保某些Pod不会被调度到某些节点上。
格式和支持的效果(选项)
#污点的组成格式如下:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。
个污点由键值对(key=value)和一个效果(effect)组成。效果描述了污点的作用,而键值对表示该污点的标签。支持的效果包括:
NoSchedule(不调度): Kubernetes将不会将Pod调度到具有该污点的节点上。
PreferNoSchedule(尽量避免调度): Kubernetes将尽量避免将Pod调度到具有该污点的节点上,但不是绝对禁止。
NoExecute(不执行): Kubernetes将不会将Pod调度到具有该污点的节点上,并且会将节点上已经存在的Pod驱逐出去,确保节点不再运行这些Pod。
这种机制为Kubernetes提供了更精细的调度控制,使得系统可以根据特定的需求和场景进行灵活的调度决策。
污点的基本操作
用一个具体的示例来说明如何在Kubernetes中执行这些操作。
假设我们有一个名为node-1
的节点,我们要给它添加一个污点,阻止Pod在这个节点上调度,除非它们具有特定的标签。我们将添加一个名为special=true:NoSchedule
的污点。
添加污点:
kubectl taint nodes <node-name> key=value:taint-effect
kubectl taint nodes node-1 special=true:NoSchedule
这将在node-1
节点上添加一个名为special
、值为true
的污点,并且该污点的效果为NoSchedule
,意味着除非Pod具有与此污点匹配的容忍(Toleration),否则不会在该节点上调度。
查看污点:
kubectl describe node <node-name>
kubectl describe node node-1
运行此命令将显示有关node-1
节点的详细信息,包括其上的污点。
删除污点:
kubectl taint nodes <node-name> key:NoSchedule-
kubectl taint nodes node-1 special:NoSchedule-
这会从node-1
节点上删除名为special
的污点,并且污点的效果为NoSchedule
。
示例
# 显示集群中的节点信息,包括名称、状态、角色、年龄和版本
kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready master 11d v1.20.11
node01 Ready <none> 11d v1.20.11
node02 Ready <none> 11d v1.20.11
# master 节点设置了一个 NoSchedule 类型的污点,导致不允许在此节点上调度 Pod
kubectl describe node master
......
Taints: node-role.kubernetes.io/master:NoSchedule
# 给 node01 节点设置一个名为 key1,值为 value1 的 NoSchedule 类型的污点
kubectl taint node node01 key1=value1:NoSchedule
# 在节点说明中查找 Taints 字段,用于查看节点上设置的污点信息
kubectl describe node node-name
# 从 node01 节点中移除名为 key1 的污点
kubectl taint node node01 key1:NoSchedule-
# 显示 Pod 的信息,包括名称、就绪状态、状态、重启次数、年龄、IP 地址、所在节点等
kubectl get pods -o wide
# 给 node02 节点设置一个名为 check,值为 mycheck 的 NoExecute 类型的污点,导致 Pod 在此节点上被驱逐
kubectl taint node node02 check=mycheck:NoExecute
# 查看 Pod 状态,会发现 node02 上的 Pod 已经被全部驱逐
# 注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod
kubectl get pods -o wide
容忍
通过在 Pod 上设置容忍(Tolerations),可以允许 Pod 在存在污点的节点上被调度。这使得在一些特定情况下,例如需要部署某些特殊任务或者维护节点时,依然能够将 Pod 调度到被标记为污点的节点上。容忍(Tolerations)允许 Pod 忽略节点上的污点并允许被调度,从而灵活地管理集群资源。
举例说明
# 在节点 node01 上设置了一个名为 mycheck 的污点,并且指定了 NoExecute 效果
kubectl taint node node01 check=mycheck:NoExecute
# 编辑名为 pod3.yaml 的 Pod 配置文件
vim pod3.yaml
# 应用 Pod3 的配置文件
kubectl apply -f pod3.yaml
# 当在两个节点上都设置了污点后,Pod 将无法创建成功
kubectl get pods -o wide
# 此时输出显示 Pod 处于 Pending 状态,无法调度到任何节点运行
# 编辑 pod3.yaml 文件,为 Pod 定义容忍策略,使其能够在具有污点的节点上运行
vim pod3.yaml
# pod3.yaml 文件内容
apiVersion: v1
kind: Pod
metadata:
name: myapp01
labels:
app: myapp01
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
tolerations:
- key: "check"
operator: "Equal"
value: "mycheck"
effect: "NoExecute"
tolerationSeconds: 3600
# 为 Pod 设置容忍策略,确保其能够在具有指定污点的节点上运行
# 其中的 key、value、effect 需要与节点上设置的污点保持一致
# operator 的值为 Exists 将会忽略 value 值,即存在即可
# tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在节点上继续保留运行的时间
# 再次应用更新后的 Pod3 配置文件
kubectl apply -f pod3.yaml
# 验证 Pod 创建成功,并在指定的节点上正常运行
kubectl get pods -o wide
# 输出显示 Pod 已成功创建并运行在节点 node01 上
值得注意的操作
- 未指定 key 值时,表示容忍所有的污点 key。
tolerations:
- operator: "Exists"
- 未指定 effect 值时,表示容忍所有的污点作用。
tolerations:
- key: "key"
operator: "Exists"
- 多个 Master 存在时,防止资源浪费,可设置如下:
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule
- 若某个 Node 更新升级系统组件,为防止业务中断,可先在该 Node 设置 NoExecute 污点,驱逐 Pod。
kubectl taint node node01 check=mycheck:NoExecute
- 若其他 Node 资源不足,可暂时给 Master 设置 PreferNoSchedule 污点,使 Pod 在 Master 上创建。
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule
- 所有 Node 更新操作完成后,移除污点。
kubectl taint node node01 check=mycheck:NoExecute-
维护节点和应用程序操作
管理节点的调度状态:
Cordon: 使用
kubectl cordon <NODE_NAME>
命令将指定节点标记为不可调度状态。这将阻止新的 Pod 在此节点上调度。Drain: 使用
kubectl drain <NODE_NAME>
命令使节点进入排水状态。这将释放节点上的所有 Pod,并且不再接受新的 Pod。执行排水操作通常在需要维护节点或者从集群中移除节点时使用。- 注:执行 drain 命令,会自动做了两件事情: 1.设定此 node 为不可调度状态(cordon) 2.evict(驱逐)了 Pod
Uncordon: 使用
kubectl uncordon <NODE_NAME>
命令将节点标记为可调度状态,恢复节点的正常调度功能。这允许新的 Pod 再次在该节点上调度。
设置和移除污点:
设置污点: 使用
kubectl taint node <NODE_NAME> <KEY>=<VALUE>:<EFFECT>
命令在节点上设置污点。污点可以阻止 Pod 在具有特定标签和效果的节点上调度。移除污点: 使用
kubectl taint node <NODE_NAME> <KEY>=<VALUE>:<EFFECT>-
命令在节点上移除指定的污点。这将允许 Pod 再次在该节点上调度。
常见的污点效果(Effect):
NoSchedule: 阻止新的 Pod 调度到节点上,但不会驱逐现有的 Pod。
PreferNoSchedule: 倾向于不在该节点上调度新的 Pod,但不会阻止 Pod 调度。
NoExecute: 阻止新的 Pod 调度到节点上,并且将现有 Pod 驱逐出该节点。
常见参数:
--ignore-daemonsets:
- 当执行
kubectl drain <NODE_NAME>
命令时,使用该选项可以忽略 DaemonSet 管理下的 Pod。DaemonSet 是 Kubernetes 中一种控制器类型,它确保在集群中的每个节点上运行一个副本的 Pod。通常情况下,节点的排水操作不会影响 DaemonSet 管理的 Pod,因为它们被认为是关键的系统组件,需要保持运行。使用--ignore-daemonsets
选项可以确保在排水节点时不会影响 DaemonSet 的 Pod。
- 当执行
--delete-local-data:
- 当执行
kubectl drain <NODE_NAME>
命令时,如果节点上有挂载本地卷的 Pod,使用该选项可以强制删除这些 Pod。本地卷是直接挂载在节点上的存储卷,而不是通过网络挂载的。在某些情况下,需要强制删除这些 Pod,例如当节点需要被彻底清空时。
- 当执行
--force:
- 当执行
kubectl drain <NODE_NAME>
命令时,使用该选项可以强制释放不是由控制器管理的 Pod。控制器管理的 Pod 包括 Deployment、StatefulSet、DaemonSet 等。通常情况下,控制器会确保 Pod 的数量和状态符合预期,但是如果需要强制释放一些不是由控制器管理的 Pod,可以使用--force
选项。
- 当执行
通过合理管理节点的调度状态和设置/移除污点,可以有效地管理集群中的资源,确保应用程序的高可用性和稳定性。
pod启动阶段的状态解读与排错技巧
Pod启动阶段(相位 phase)
Pod 创建完之后,一直到持久运行起来,中间有很多步骤,也就有很多出错的可能,因此会有很多不同的状态。
调度到某台 node 上: Kubernetes 根据一定的优先级算法选择一个 node 节点来运行 Pod。
拉取镜像: Pod 需要拉取其包含的镜像,以便在节点上创建容器。
挂载存储配置等: Pod 可能需要挂载存储卷或配置文件等资源。
运行起来: 当所有必需的资源准备就绪后,Pod 开始在节点上运行。如果定义了健康检查,系统会根据检查结果设置 Pod 的状态。
pod可能的状态
Pending(挂起): 表示 Pod 资源对象已创建,但尚未完成调度或正在拉取镜像。
Running(运行中): Pod 已经被调度到节点上,并且至少有一个容器正在运行,或处于启动或重启状态。也就是说Running状态下的Pod不一定能被正常访问
Succeeded(成功): 表示 Pod 中的所有容器已成功终止,通常用于短期任务执行完毕后的状态反馈。有些pod不是长久运行的,比如job、cronjob,一段时间后Pod中的所有容器都被成功终止,并且不会再重启。需要反馈任务执行的结果。
Failed(失败): 所有容器都已终止,并且至少有一个容器由于失败而终止,可能是由于命令错误等原因。也就是说,容器以非0状态退出或者被系统终止,比如 command 写的有问题。
Unknown(未知): 表示无法读取 Pod 的状态,通常是由于控制器(kube-controller-manager)无法与 Pod 通信导致的。
故障排除步骤
需要排查 Pod 启动或运行中的问题时,以下是一些详细的故障排除步骤:
- 查看 Pod 事件: 使用
kubectl describe
命令检查 Pod 事件,以了解 Pod 启动过程中可能出现的问题。这将提供关于 Pod 创建、调度和运行过程中的详细信息。
kubectl describe pod <POD_NAME>
期望输出: Pod 的事件日志,包括调度、镜像拉取、容器启动等信息。
- 查看 Pod 日志: 如果 Pod 处于 Failed 状态,使用
kubectl logs
命令查看 Pod 的日志以获取失败的原因。可以通过指定容器名称来查看特定容器的日志。
kubectl logs <POD_NAME> [-c <CONTAINER_NAME>]
期望输出: 容器的标准输出和标准错误日志,显示应用程序的运行日志或错误信息。
- 进入 Pod 内部: 如果 Pod 处于 Running 状态但服务没有提供,可以使用
kubectl exec
命令进入 Pod 内部。这样可以检查 Pod 内部的环境和运行状态,以便进一步诊断问题。
kubectl exec -it <POD_NAME> -- /bin/bash
期望操作: 在 Pod 内部运行命令进行故障排查,如检查网络配置、运行状态或日志文件。
- 查看集群信息: 使用
kubectl get nodes
命令检查集群中节点的状态,以确保节点正常运行。这可以帮助排除可能导致 Pod 无法调度或运行的节点问题。
kubectl get nodes
期望输出: 集群中所有节点的列表,包括它们的状态(Ready、NotReady 或其他)。
- 检查集群状态: 使用
kubectl cluster-info
命令检查整个集群的状态,包括控制平面和核心服务。这有助于确定是否存在集群范围的问题。
kubectl cluster-info
期望输出: 集群控制平面组件的状态和访问URL,如 Kubernetes master 和 kubeDNS。
- 查看 kubelet 日志: 最后,查看 kubelet 的日志以发现与 Pod 启动或运行相关的任何问题。可以使用
journalctl
命令来查看 kubelet 的日志。
journalctl -u kubelet
期望输出: kubelet 服务的日志,提供与 Pod 生命周期和节点状态相关的详细信息。
通过执行以上步骤,可以对 Pod 启动或运行中的问题进行详细的排查和诊断,从而有效地解决问题并确保应用程序正常运行。