mirror of
https://github.com/Baim0/baim0-learn.git
synced 2026-10-21 03:27:43 +08:00
vault backup: 2024-05-25 17:26:36
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
---
|
||||
title: 01 descheduler
|
||||
date: 2023-04-20 21:23:07
|
||||
tags:
|
||||
- kubernetes杂谈
|
||||
categories:
|
||||
- kubernetes杂谈
|
||||
---
|
||||
|
||||
原创:阳明,摘录其社区运营公众号,kubernetes技术圈
|
||||
|
||||
从 kube-scheduler 的角度来看,它是通过一系列算法计算出最佳节点运行 Pod,当出现新的 Pod 进行调度时,调度程序会根据其当时对 Kubernetes 集群的资源描述做出最佳调度决定,但是 Kubernetes 集群是非常动态的,由于整个集群范围内的变化,比如一个节点为了维护,我们先执行了驱逐操作,这个节点上的所有 Pod 会被驱逐到其他节点去,但是当我们维护完成后,之前的 Pod 并不会自动回到该节点上来,因为 Pod 一旦被绑定了节点是不会触发重新调度的,由于这些变化,Kubernetes 集群在一段时间内就可能会出现不均衡的状态,所以需要均衡器来重新平衡集群。
|
||||

|
||||
当然我们可以去手动做一些集群的平衡,比如手动去删掉某些 Pod,触发重新调度就可以了,但是显然这是一个繁琐的过程,也不是解决问题的方式。为了解决实际运行中集群资源无法充分利用或浪费的问题,可以使用 descheduler 组件对集群的 Pod 进行调度优化,descheduler 可以根据一些规则和配置策略来帮助我们重新平衡集群状态,其核心原理是根据其策略配置找到可以被移除的 Pod 并驱逐它们,其本身并不会进行调度被驱逐的 Pod,而是依靠默认的调度器来实现,目前支持的策略有:
|
||||
|
||||
- RemoveDuplicates
|
||||
- LowNodeUtilization
|
||||
- HighNodeUtilization
|
||||
- RemovePodsViolatingInterPodAntiAffinity
|
||||
- RemovePodsViolatingNodeAffinity
|
||||
- RemovePodsViolatingNodeTaints
|
||||
- RemovePodsViolatingTopologySpreadConstraint
|
||||
- RemovePodsHavingTooManyRestarts
|
||||
- PodLifeTime
|
||||
- RemoveFailedPods
|
||||
|
||||
这些策略都是可以启用或者禁用的,作为策略的一部分,也可以配置与策略相关的一些参数,默认情况下,所有策略都是启用的。另外,还有一些通用配置,如下:
|
||||
|
||||
- nodeSelector:限制要处理的节点
|
||||
- evictLocalStoragePods: 驱逐使用 LocalStorage 的 Pods
|
||||
- ignorePvcPods: 是否忽略配置 PVC 的 Pods,默认是 False
|
||||
- maxNoOfPodsToEvictPerNode:节点允许的最大驱逐 Pods 数
|
||||
|
||||
我们可以通过如下所示的 DeschedulerPolicy 来配置:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
kind: "DeschedulerPolicy"
|
||||
nodeSelector: "node=node1" # 如果没有设置,所有内容都将被处理,无需进行此设置。
|
||||
maxNoOfPodsToEvictPerNode: 5000 # 如果没有设置,就不需要进行限制。
|
||||
maxNoOfPodsToEvictPerNamespace: 5000
|
||||
profiles:
|
||||
- name: ProfileName
|
||||
pluginConfig:
|
||||
- name: "DefaultEvictor"
|
||||
args:
|
||||
evictSystemCriticalPods: true
|
||||
evictFailedBarePods: true
|
||||
evictLocalStoragePods: true
|
||||
nodeFit: true
|
||||
plugins:
|
||||
evict:
|
||||
enabled:
|
||||
- "DefaultEvictor"
|
||||
deschedule:
|
||||
enabled:
|
||||
- ...
|
||||
balance:
|
||||
enabled:
|
||||
- ...
|
||||
[...]
|
||||
```
|
||||
## 安装
|
||||
descheduler 可以以 CronJob 或者 Deployment 的形式运行在 k8s 集群内,同样我们可以使用 Helm Chart 来安装 descheduler:
|
||||
```bash
|
||||
➜ helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
|
||||
```
|
||||
通过 Helm Chart 我们可以配置 descheduler 以 CronJob 或者 Deployment 方式运行,默认情况下 descheduler 会以一个 critical pod 运行,以避免被自己或者 kubelet 驱逐了,需要确保集群中有 system-cluster-critical 这个 Priorityclass:
|
||||
```bash
|
||||
➜ kubectl get priorityclass system-cluster-critical
|
||||
NAME VALUE GLOBAL-DEFAULT AGE
|
||||
system-cluster-critical 2000000000 false 87d
|
||||
```
|
||||
使用 Helm Chart 安装默认情况下会以 CronJob 的形式运行,执行周期为 schedule: "*/2 * * * *",这样每隔两分钟会执行一次 descheduler 任务,默认的配置策略如下所示:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: descheduler
|
||||
data:
|
||||
policy.yaml: |
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
LowNodeUtilization:
|
||||
enabled: true
|
||||
params:
|
||||
nodeResourceUtilizationThresholds:
|
||||
targetThresholds:
|
||||
cpu: 50
|
||||
memory: 50
|
||||
pods: 50
|
||||
thresholds:
|
||||
cpu: 20
|
||||
memory: 20
|
||||
pods: 20
|
||||
RemoveDuplicates:
|
||||
enabled: true
|
||||
RemovePodsHavingTooManyRestarts:
|
||||
enabled: true
|
||||
params:
|
||||
podsHavingTooManyRestarts:
|
||||
includingInitContainers: true
|
||||
podRestartThreshold: 100
|
||||
RemovePodsViolatingInterPodAntiAffinity:
|
||||
enabled: true
|
||||
RemovePodsViolatingNodeAffinity:
|
||||
enabled: true
|
||||
params:
|
||||
nodeAffinityType:
|
||||
- requiredDuringSchedulingIgnoredDuringExecution
|
||||
RemovePodsViolatingNodeTaints:
|
||||
enabled: true
|
||||
RemovePodsViolatingTopologySpreadConstraint:
|
||||
enabled: true
|
||||
params:
|
||||
includeSoftConstraints: false
|
||||
```
|
||||
通过配置 DeschedulerPolicy 的 strategies,可以指定 descheduler 的执行策略,这些策略都是可以启用或禁用的,下面我们会详细介绍,这里我们使用默认策略即可,使用如下命令直接安装即可:
|
||||
```bash
|
||||
➜ helm upgrade --install descheduler descheduler/descheduler --set image.repository=cnych/descheduler -n kube-system
|
||||
```
|
||||
部署完成后会创建一个 CronJob 资源对象来平衡集群状态:
|
||||
```bash
|
||||
➜ kubectl get cronjob -n kube-system
|
||||
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
|
||||
descheduler */2 * * * * False 1 8s 117s
|
||||
➜ kubectl get job -n kube-system
|
||||
NAME COMPLETIONS DURATION AGE
|
||||
descheduler-28032982 1/1 15s 17s
|
||||
➜ kubectl get pods -n kube-system -l job-name=descheduler-28032982
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
descheduler-28032982-vxn24 0/1 Completed 0 31s
|
||||
```
|
||||
正常情况下就会创建一个对应的 Job 来执行 descheduler 任务,我们可以通过查看日志可以了解做了哪些平衡操作:
|
||||
```bash
|
||||
➜ kubectl logs -f descheduler-28032982-vxn24 -nkube-system
|
||||
I0420 08:22:10.019936 1 named_certificates.go:53] "Loaded SNI cert" index=0 certName="self-signed loopback" certDetail="\"apiserver-loopback-client@1681978930\" [serving] validServingFor=[apiserver-loopback-client] issuer=\"apiserver-loopback-client-ca@1681978929\" (2023-04-20 07:22:09 +0000 UTC to 2024-04-19 07:22:09 +0000 UTC (now=2023-04-20 08:22:10.019885292 +0000 UTC))"
|
||||
I0420 08:22:10.020138 1 secure_serving.go:210] Serving securely on [::]:10258
|
||||
I0420 08:22:10.020301 1 tlsconfig.go:240] "Starting DynamicServingCertificateController"
|
||||
I0420 08:22:10.021237 1 policyconfig.go:211] converting Deschedule plugin: %sRemovePodsViolatingInterPodAntiAffinity
|
||||
I0420 08:22:10.021255 1 policyconfig.go:211] converting Deschedule plugin: %sRemovePodsViolatingNodeAffinity
|
||||
I0420 08:22:10.021262 1 policyconfig.go:211] converting Deschedule plugin: %sRemovePodsViolatingNodeTaints
|
||||
I0420 08:22:10.021269 1 policyconfig.go:202] converting Balance plugin: %sRemovePodsViolatingTopologySpreadConstraint
|
||||
I0420 08:22:10.021280 1 policyconfig.go:202] converting Balance plugin: %sLowNodeUtilization
|
||||
I0420 08:22:10.021296 1 policyconfig.go:202] converting Balance plugin: %sRemoveDuplicates
|
||||
I0420 08:22:10.021312 1 policyconfig.go:211] converting Deschedule plugin: %sRemovePodsHavingTooManyRestarts
|
||||
# ......
|
||||
I0420 08:22:11.630980 1 removeduplicates.go:162] "Duplicate found" pod="kruise-system/kruise-controller-manager-7d78fc5c97-pxsqx"
|
||||
I0420 08:22:11.630997 1 removeduplicates.go:103] "Processing node" node="node2"
|
||||
I0420 08:22:11.631052 1 removeduplicates.go:103] "Processing node" node="node3"
|
||||
I0420 08:22:11.631113 1 removeduplicates.go:103] "Processing node" node="master1"
|
||||
I0420 08:22:11.631184 1 removeduplicates.go:194] "Adjusting feasible nodes" owner={namespace:kruise-system kind:ReplicaSet name:kruise-controller-manager-7d78fc5c97 imagesHash:openkruise/kruise-manager:v1.3.0} from=4 to=3
|
||||
I0420 08:22:11.631200 1 removeduplicates.go:203] "Average occurrence per node" node="node1" ownerKey={namespace:kruise-system kind:ReplicaSet name:kruise-controller-manager-7d78fc5c97 imagesHash:openkruise/kruise-manager:v1.3.0} avg=1
|
||||
I0420 08:22:11.647438 1 evictions.go:162] "Evicted pod" pod="kruise-system/kruise-controller-manager-7d78fc5c97-pxsqx" reason="" strategy="RemoveDuplicates" node="node1"
|
||||
I0420 08:22:11.647494 1 descheduler.go:408] "Number of evicted pods" totalEvicted=1
|
||||
I0420 08:22:11.647583 1 reflector.go:227] Stopping reflector *v1.Namespace (0s) from k8s.io/client-go/informers/factory.go:150
|
||||
I0420 08:22:11.647702 1 reflector.go:227] Stopping reflector *v1.PriorityClass (0s) from k8s.io/client-go/informers/factory.go:150
|
||||
I0420 08:22:11.647761 1 tlsconfig.go:255] "Shutting down DynamicServingCertificateController"
|
||||
I0420 08:22:11.647764 1 reflector.go:227] Stopping reflector *v1.Node (0s) from k8s.io/client-go/informers/factory.go:150
|
||||
I0420 08:22:11.647811 1 secure_serving.go:255] Stopped listening on [::]:10258
|
||||
......
|
||||
```
|
||||
从日志中我们就可以清晰的知道因为什么策略驱逐了哪些 Pods。
|
||||
## PDB
|
||||
由于使用 descheduler 会将 Pod 驱逐进行重调度,但是如果一个服务的所有副本都被驱逐的话,则可能导致该服务不可用。如果服务本身存在单点故障,驱逐的时候肯定就会造成服务不可用了,这种情况我们强烈建议使用反亲和性和多副本来避免单点故障,但是如果服务本身就被打散在多个节点上,这些 Pod 都被驱逐的话,这个时候也会造成服务不可用了,这种情况下我们可以通过配置 PDB(PodDisruptionBudget) 对象来避免所有副本同时被删除,比如我们可以设置在驱逐的时候某应用最多只有一个副本不可用,则创建如下所示的资源清单即可:
|
||||
```bash
|
||||
# pdb-demo.yaml
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: pdb-demo
|
||||
spec:
|
||||
maxUnavailable: 1 # 设置最多不可用的副本数量,或者使用 minAvailable,可以使用整数或百分比
|
||||
selector:
|
||||
matchLabels: # 匹配Pod标签
|
||||
app: demo
|
||||
```
|
||||
关于 PDB 的更多详细信息可以查看官方文档:https://kubernetes.io/docs/tasks/run-application/configure-pdb/。
|
||||
所以如果我们使用 descheduler 来重新平衡集群状态,那么我们强烈建议给应用创建一个对应的 PodDisruptionBudget 对象进行保护。
|
||||
## 策略
|
||||
### PodLifeTime:驱逐超过指定时间限制的 pod
|
||||
该策略用于驱逐比 maxPodLifeTimeSeconds 更旧的 Pods,可以通过 podStatusPhases 来配置哪类状态的 Pods 会被驱逐,建议为每个应用程序创建一个 PDB,以确保应用程序的可用性,比如我们可以配置如下所示的策略来驱逐运行超过 7 天的 Pod:
|
||||
```bash
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"PodLifeTime":
|
||||
enabled: true
|
||||
params:
|
||||
maxPodLifeTimeSeconds: 604800 # Pods 运行最多7天
|
||||
```
|
||||
### RemoveDuplicates
|
||||
该策略确保只有一个和 Pod 关联的 RS、Deployment 或者 Job 资源对象运行在同一节点上。如果还有更多的 Pod 则将这些重复的 Pod 进行驱逐,以便更好地在集群中分散 Pod。如果某些节点由于某些原因崩溃了,这些节点上的 Pod 漂移到了其他节点,导致多个与 RS 关联的 Pod 在同一个节点上运行,就有可能发生这种情况,一旦出现故障的节点再次准备就绪,就可以启用该策略来驱逐这些重复的 Pod。
|
||||

|
||||
RemoveDuplicates
|
||||
配置策略的时候,可以指定参数 excludeOwnerKinds 用于排除类型,这些类型下的 Pod 不会被驱逐:
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemoveDuplicates":
|
||||
enabled: true
|
||||
params:
|
||||
removeDuplicates:
|
||||
excludeOwnerKinds:
|
||||
- "ReplicaSet"
|
||||
```
|
||||
### LowNodeUtilization
|
||||
该策略主要用于查找未充分利用的节点,并从其他节点驱逐 Pod,以便 kube-scheduler 重新将它们调度到未充分利用的节点上。该策略的参数可以通过字段 nodeResourceUtilizationThresholds 进行配置。
|
||||
节点的利用率不足可以通过配置 thresholds 阈值参数来确定,可以通过 CPU、内存和 Pods 数量的百分比进行配置。如果节点的使用率均低于所有阈值,则认为该节点未充分利用。
|
||||

|
||||
LowNodeUtilization
|
||||
此外,还有一个可配置的阈值 targetThresholds,用于计算可能驱逐 Pods 的潜在节点,该参数也可以配置 CPU、内存以及 Pods 数量的百分比进行配置。thresholds 和 targetThresholds 可以根据你的集群需求进行动态调整,如下所示示例:
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"LowNodeUtilization":
|
||||
enabled: true
|
||||
params:
|
||||
nodeResourceUtilizationThresholds:
|
||||
thresholds:
|
||||
"cpu": 20
|
||||
"memory": 20
|
||||
"pods": 20
|
||||
targetThresholds:
|
||||
"cpu": 50
|
||||
"memory": 50
|
||||
"pods": 50
|
||||
```
|
||||
需要注意的是:
|
||||
|
||||
- 仅支持以下三种资源类型:cpu、memory、pods
|
||||
- thresholds 和 targetThresholds 必须配置相同的类型
|
||||
- 参数值的访问是 0-100(百分制)
|
||||
- 相同的资源类型,thresholds 的配置不能高于 targetThresholds 的配置
|
||||
|
||||
如果未指定任何资源类型,则默认是 100%,以避免节点从未充分利用变为过度利用。和 LowNodeUtilization 策略关联的另一个参数是 numberOfNodes,只有当未充分利用的节点数大于该配置值的时候,才可以配置该参数来激活该策略,该参数对于大型集群非常有用,其中有一些节点可能会频繁使用或短期使用不足,默认情况下,numberOfNodes 为 0。
|
||||
### RemovePodsViolatingInterPodAntiAffinity
|
||||
该策略可以确保从节点中删除违反 Pod 反亲和性的 Pod,比如某个节点上有 podA 这个 Pod,并且 podB 和 podC(在同一个节点上运行)具有禁止它们在同一个节点上运行的反亲和性规则,则 podA 将被从该节点上驱逐,以便 podB 和 podC 运行正常运行。当 podB 和 podC 已经运行在节点上后,反亲和性规则被创建就会发送这样的问题。
|
||||

|
||||
RemovePodsViolatingInterPodAntiAffinity
|
||||
要禁用该策略,直接配置成 false 即可:
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemovePodsViolatingInterPodAntiAffinity":
|
||||
enabled: false
|
||||
```
|
||||
### RemovePodsViolatingNodeTaints
|
||||
该策略可以确保从节点中删除违反 NoSchedule 污点的 Pod,比如有一个名为 podA 的 Pod,通过配置容忍 key=value:NoSchedule 允许被调度到有该污点配置的节点上,如果节点的污点随后被更新或者删除了,则污点将不再被 Pods 的容忍满足,然后将被驱逐:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemovePodsViolatingNodeTaints":
|
||||
enabled: true
|
||||
```
|
||||
### RemovePodsViolatingNodeAffinity
|
||||
该策略确保从节点中删除违反节点亲和性的 Pod。比如名为 podA 的 Pod 被调度到了节点 nodeA,podA 在调度的时候满足了节点亲和性规则 requiredDuringSchedulingIgnoredDuringExecution,但是随着时间的推移,节点 nodeA 不再满足该规则了,那么如果另一个满足节点亲和性规则的节点 nodeB 可用,则 podA 将被从节点 nodeA 驱逐,如下所示的策略配置示例:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemovePodsViolatingNodeAffinity":
|
||||
enabled: true
|
||||
params:
|
||||
nodeAffinityType:
|
||||
- "requiredDuringSchedulingIgnoredDuringExecution"
|
||||
```
|
||||
### RemovePodsViolatingTopologySpreadConstraint
|
||||
该策略确保从节点驱逐违反拓扑分布约束的 Pods,具体来说,它试图驱逐将拓扑域平衡到每个约束的 maxSkew 内所需的最小 Pod 数,不过该策略需要 k8s 版本高于 1.18 才能使用。
|
||||
默认情况下,此策略仅处理硬约束,如果将参数 includeSoftConstraints 设置为 True,也将支持软约束。
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemovePodsViolatingTopologySpreadConstraint":
|
||||
enabled: true
|
||||
params:
|
||||
includeSoftConstraints: false
|
||||
```
|
||||
### RemovePodsHavingTooManyRestarts
|
||||
该策略确保从节点中删除重启次数过多的 Pods,它的参数包括 podRestartThreshold(这是应将 Pod 逐出的重新启动次数),以及包括InitContainers,它确定在计算中是否应考虑初始化容器的重新启动,策略配置如下所示:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"RemovePodsHavingTooManyRestarts":
|
||||
enabled: true
|
||||
params:
|
||||
podsHavingTooManyRestarts:
|
||||
podRestartThreshold: 100
|
||||
includingInitContainers: true
|
||||
```
|
||||
## Filter Pods
|
||||
在驱逐 Pods 的时候,有时并不需要所有 Pods 都被驱逐,descheduler 提供了两种主要的方式进行过滤:命名空间过滤和优先级过滤。
|
||||
### 命名空间过滤
|
||||
该策略可以配置是包含还是排除某些名称空间。可以使用该策略的有:
|
||||
|
||||
- PodLifeTime
|
||||
- RemovePodsHavingTooManyRestarts
|
||||
- RemovePodsViolatingNodeTaints
|
||||
- RemovePodsViolatingNodeAffinity
|
||||
- RemovePodsViolatingInterPodAntiAffinity
|
||||
- RemoveDuplicates
|
||||
- RemovePodsViolatingTopologySpreadConstraint
|
||||
|
||||
比如只驱逐某些命令空间下的 Pods,则可以使用 include 参数进行配置,如下所示:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"PodLifeTime":
|
||||
enabled: true
|
||||
params:
|
||||
podLifeTime:
|
||||
maxPodLifeTimeSeconds: 86400
|
||||
namespaces:
|
||||
include:
|
||||
- "namespace1"
|
||||
- "namespace2"
|
||||
```
|
||||
又或者要排除掉某些命令空间下的 Pods,则可以使用 exclude 参数配置,如下所示:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"PodLifeTime":
|
||||
enabled: true
|
||||
params:
|
||||
podLifeTime:
|
||||
maxPodLifeTimeSeconds: 86400
|
||||
namespaces:
|
||||
exclude:
|
||||
- "namespace1"
|
||||
- "namespace2"
|
||||
```
|
||||
### 优先级过滤
|
||||
所有策略都可以配置优先级阈值,只有在该阈值以下的 Pod 才会被驱逐,我们可以通过设置 thresholdPriorityClassName(将阈值设置为指定优先级类别的值)或 thresholdPriority(直接设置阈值)参数来指定该阈值。默认情况下,该阈值设置为 system-cluster-critical 这个 PriorityClass 类的值。
|
||||
比如使用 thresholdPriority:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"PodLifeTime":
|
||||
enabled: true
|
||||
params:
|
||||
podLifeTime:
|
||||
maxPodLifeTimeSeconds: 86400
|
||||
thresholdPriority: 10000
|
||||
```
|
||||
或者使用 thresholdPriorityClassName 进行过滤:
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha1"
|
||||
kind: "DeschedulerPolicy"
|
||||
strategies:
|
||||
"PodLifeTime":
|
||||
enabled: true
|
||||
params:
|
||||
podLifeTime:
|
||||
maxPodLifeTimeSeconds: 86400
|
||||
thresholdPriorityClassName: "priorityclass1"
|
||||
```
|
||||
不过需要注意不能同时配置 thresholdPriority 和 thresholdPriorityClassName,如果指定的优先级类不存在,则 descheduler 不会创建它,并且会引发错误。
|
||||
## 注意事项
|
||||
当使用 descheduler 驱除 Pods 的时候,需要注意以下几点:
|
||||
|
||||
- 关键性 Pod 不会被驱逐,比如 priorityClassName 设置为 system-cluster-critical 或 system-node-critical 的 Pod
|
||||
- 不属于 RS、Deployment 或 Job 管理的 Pods 不会被驱逐
|
||||
- DaemonSet 创建的 Pods 不会被驱逐
|
||||
- 使用 LocalStorage 的 Pod 不会被驱逐,除非设置 evictLocalStoragePods: true
|
||||
- 具有 PVC 的 Pods 不会被驱逐,除非设置 ignorePvcPods: true
|
||||
- 在 LowNodeUtilization 和 RemovePodsViolatingInterPodAntiAffinity 策略下,Pods 按优先级从低到高进行驱逐,如果优先级相同,Besteffort 类型的 Pod 要先于 Burstable 和 Guaranteed 类型被驱逐
|
||||
- annotations 中带有 descheduler.alpha.kubernetes.io/evict 字段的 Pod 都可以被驱逐,该注释用于覆盖阻止驱逐的检查,用户可以选择驱逐哪个 Pods
|
||||
- 如果 Pods 驱逐失败,可以设置 --v=4 从 descheduler 日志中查找原因,如果驱逐违反 PDB 约束,则不会驱逐这类 Pods
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: 02 如何让你的集群更加整洁一点
|
||||
date: 2023-04-20 22:23:07
|
||||
tags:
|
||||
- kubernetes杂谈
|
||||
categories:
|
||||
- kubernetes杂谈
|
||||
---
|
||||
|
||||
## 问题背景
|
||||
|
||||
首先,你有没有遇到过这样的情况
|
||||
|
||||
```bash
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-cm
|
||||
namespace: default
|
||||
labels:
|
||||
test-label: test
|
||||
managedFields:
|
||||
- manager: kubectl
|
||||
operation: Apply
|
||||
apiVersion: v1
|
||||
time: "2010-10-10T0:00:00Z"
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
f:metadata:
|
||||
f:labels:
|
||||
f:test-label: {}
|
||||
f:data:
|
||||
f:key: {}
|
||||
data:
|
||||
key: some value
|
||||
```
|
||||
|
||||
这特么的一堆括号是什么东西??????
|
||||
|
||||
有什么用????????
|
||||
|
||||
其实是记录的迭代的信息。要说有啥用,其实真的没有啥用
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 结论
|
||||
|
||||
以kubectl get no场景为例,当集群节点非常多的时候,集群pod非常多的时候,建议优化关闭不需要的字段。
|
||||
|
||||
```bash
|
||||
ServerSideApply=false
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
kubectl get 资源的执行过程,是以500个对象为一个分页,从apiserver到etcd进行一致性读操作,将全部分页读到本地 并显示;过程中,会get每个资源对象的yaml。通过kubectl get <资源> -v 9 可以看到详细执行过程。所以减少单个资源 对象的yaml size,可以缩短kubectl get 的执行时间。
|
||||
|
||||
|
||||
|
||||
## 优缺点
|
||||
|
||||
|
||||
|
||||
关闭ServerSideApply featureGate (https://kubernetes.io/zh-cn/docs/reference/using-api/server-side-apply/ ),关闭managedFields更新。需要修改apiserver参数,期间apiserver会滚动更新,对生产业务无影响。
|
||||
|
||||
|
||||
|
||||
优点:
|
||||
|
||||
关闭之后,该字段不会更新,从而控制该字段导致的持续增长。针对新开集群操作后,可以避免后续get资源的时 候产生较大的数据量,造成kubectl缓慢。随开随用。
|
||||
|
||||
|
||||
|
||||
缺点:
|
||||
|
||||
在后续部署的时候,yaml中无法携带managedFields字段,否则会apply失败。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
关闭了之后你会发现:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-cm
|
||||
namespace: default
|
||||
labels:
|
||||
test-label: test
|
||||
data:
|
||||
key: some value
|
||||
```
|
||||
|
||||
哇擦,好整洁,但是只对新增的资源生效哦,不会对已有的资源有改变。
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: 03 如何删除终止状态的名称空间
|
||||
date: 2023-04-22 21:25:07
|
||||
tags:
|
||||
- kubernetes杂谈
|
||||
categories:
|
||||
- kubernetes杂谈
|
||||
---
|
||||
|
||||
|
||||
|
||||
尝试删除Kubernetes命名空间后,长时间停留在终止状态。本文介绍如何解决命名空间处于终止状态的问题。
|
||||
|
||||
## 问题现象
|
||||
|
||||
尝试删除Kubernetes命名空间后,长时间停留在终止状态。
|
||||
|
||||
```csharp
|
||||
kubectl delete ns <namespacename>
|
||||
Error from server (Conflict): Operation cannot be fulfilled on namespaces "<namespacename>": The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.
|
||||
|
||||
kubectl describe ns <namespacename>
|
||||
Name: <namespacename>
|
||||
Labels: <none>
|
||||
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"<namespacename>","namespace":""}}
|
||||
|
||||
Status: Terminating
|
||||
```
|
||||
|
||||
## 可能原因
|
||||
|
||||
通常是因为从集群中删除的这些命名空间下存在资源。
|
||||
|
||||
## 解决方案
|
||||
|
||||
删除命名空间的finalizers。
|
||||
|
||||
该选项将会快速清除处于终止状态的命名空间,但可能会导致属于该命名空间的资源留在集群中,因为无法自动删除它们。在finalizers数组为空并且状态为终止之后,Kubernetes将删除命名空间。
|
||||
|
||||
1. 打开Shell终端,为您的Kubernetes集群创建一个反向代理。
|
||||
|
||||
```bash
|
||||
kubectl proxy
|
||||
```
|
||||
|
||||
输出示例如下:
|
||||
|
||||
```bash
|
||||
Starting to serve on 127.0.0.1:8001
|
||||
```
|
||||
|
||||
2. 打开一个新的Shell终端,通过定义环境变量来连接到Kubernetes集群,使用curl测试连接性和授权。
|
||||
|
||||
```bash
|
||||
export TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
|
||||
curl http://localhost:8001/api/v1/namespaces --header "Authorization: Bearer $TOKEN" --insecure
|
||||
```
|
||||
|
||||
3. 获取命名空间定义的内容,以命名空间istio-system为例。
|
||||
|
||||
```sql
|
||||
kubectl get namespace istio-system -o json > istio-system.json
|
||||
```
|
||||
|
||||
4. 将finalizers数组置为空,并重新保存文件。
|
||||
|
||||
```json
|
||||
"spec": {
|
||||
"finalizers": [
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
5. 执行以下命令去除finalizers,以命名空间istio-system为例。
|
||||
|
||||
```bash
|
||||
curl -X PUT --data-binary @istio-system.json http://localhost:8001/api/v1/namespaces/istio-system/finalize -H "Content-Type: application/json" --header "Authorization: Bearer $TOKEN" --insecure
|
||||
```
|
||||
@@ -0,0 +1,295 @@
|
||||
---
|
||||
title: 04 coredns相关配置
|
||||
date: 2023-04-30 13:52:07
|
||||
tags:
|
||||
- kubernetes杂谈
|
||||
categories:
|
||||
- kubernetes杂谈
|
||||
---
|
||||
|
||||
## 场景说明
|
||||
|
||||
使用`dnsPolicy: ClusterFirst`策略。示例配置如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: alpine
|
||||
namespace: default
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine
|
||||
command:
|
||||
- sleep
|
||||
- "10000"
|
||||
imagePullPolicy: Always
|
||||
name: alpine
|
||||
dnsPolicy: ClusterFirst
|
||||
```
|
||||
|
||||
关于**dnsPolicy**配置和场景说明,请参见[DNS原理和配置说明](https://help.aliyun.com/document_detail/188179.htm#task-1985778)。
|
||||
|
||||
## CoreDNS的默认配置
|
||||
|
||||
在命名空间kube-system下,ACK集群有一个CoreDNS配置项(有关如何查看配置项的具体步骤,请参见[管理配置项](https://help.aliyun.com/document_detail/86769.htm#section-hly-ioi-ozy))。CoreDNS会基于该配置项启用和配置插件。不同CoreDNS版本的配置项有略微差异,修改配置前请仔细阅读[CoreDNS官方文档](https://coredns.io/plugins/)。以下是一个1.6.2版本CoreDNS默认采用的配置文件:
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
log
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
kubernetes {{.ClusterDomain}} in-addr.arpa ip6.arpa {
|
||||
pods verified
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf {
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
|
||||
**说明** 配置文件中`ClusterDomain`代指集群创建过程中填写的集群本地域名,默认值为`cluster.local`。
|
||||
|
||||
| 参数 | 描述 |
|
||||
| :------------------------- | :----------------------------------------------------------- |
|
||||
| **errors** | 错误信息到标准输出。 |
|
||||
| **health** | CoreDNS自身健康状态报告,默认监听端口8080,一般用来做健康检查。您可以通过`http://localhost:8080/health`获取健康状态。 |
|
||||
| **ready** | CoreDNS插件状态报告,默认监听端口8181,一般用来做可读性检查。可以通过`http://localhost:8181/ready`获取可读状态。当所有插件都运行后,ready状态为200。 |
|
||||
| **kubernetes** | CoreDNS Kubernetes插件,提供集群内服务解析能力。 |
|
||||
| **prometheus** | CoreDNS自身metrics数据接口。可以通过`http://localhost:9153/metrics`获取prometheus格式的监控数据。 |
|
||||
| **forward**(或**proxy**) | 将域名查询请求转到预定义的DNS服务器。默认配置中,当域名不在Kubernetes域时,将请求转发到预定义的解析器(/etc/resolv.conf)中。默认使用宿主机的/etc/resolv.conf配置。 |
|
||||
| **cache** | DNS缓存。 |
|
||||
| **loop** | 环路检测,如果检测到环路,则停止CoreDNS。 |
|
||||
| **reload** | 允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,请等待两分钟以使更改生效。 |
|
||||
| **loadbalance** | 循环DNS负载均衡器,可以在答案中随机A、AAAA、MX记录的顺序。 |
|
||||
|
||||
## CoreDNS的扩展配置
|
||||
|
||||
针对以下不同场景,您可以扩展CoreDNS的配置:
|
||||
|
||||
- 场景一:开启日志服务
|
||||
|
||||
如果需将CoreDNS每次域名解析的日志打印出来,您可以开启Log插件,在Corefile里加上log。示例配置如下:
|
||||
|
||||
```bash
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
log
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf {
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
|
||||
- 场景二:特定域名使用自定义DNS服务器
|
||||
|
||||
如果example.com类型后缀的域名需要经过自建DNS服务器(IP为10.10.0.10)进行解析的话,您可为域名配置一个单独的服务块。示例配置如下:
|
||||
|
||||
```bash
|
||||
example.com:53 {
|
||||
errors
|
||||
cache 30
|
||||
forward . 10.10.0.10
|
||||
prefer_udp
|
||||
}
|
||||
```
|
||||
|
||||
完整配置如下:
|
||||
|
||||
```bash
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf {
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
example.com:53 {
|
||||
errors
|
||||
cache 30
|
||||
forward . 10.10.0.10
|
||||
prefer_udp
|
||||
}
|
||||
```
|
||||
|
||||
- 场景三:外部域名完全使用自建DNS服务器
|
||||
|
||||
如果您需要使用的自建DNS服务的域名没有统一的域名后缀,您可以选择所有集群外部域名都使用自建DNS服务器(此时需要您将自建的DNS服务不能解析的域名转发到阿里云DNS,禁止直接更改集群ECS上的/etc/resolv.conf文件)。例如,您自建的DNS服务器IP为10.10.0.10和10.10.0.20,可以更改**forward**参数进行配置。示例配置如下:
|
||||
|
||||
```bash
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . 10.10.0.10 10.10.0.20{
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
|
||||
- 场景四:自定义Hosts
|
||||
|
||||
如果您需要为特定域名指定hosts,如为www.example.com指定IP为127.0.0.1,可以使用Hosts插件来配置。示例配置如下:
|
||||
|
||||
```go
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
|
||||
hosts {
|
||||
127.0.0.1 www.example.com
|
||||
fallthrough
|
||||
}
|
||||
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf {
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
|
||||
**重要** 请配置**fallthrough**,否则会造成非定制Hosts域名解析失败。
|
||||
|
||||
- 场景五:集群外部访问集群内服务
|
||||
|
||||
如果您希望运行在集群ECS上的进程能够访问到集群内的服务,虽然可以通过将ECS的/etc/resolv.conf文件内`nameserver`配置为集群kube-dns的ClusterIP地址来达到目的,但不推荐您直接更改ECS的/etc/resolv.conf文件的方式来达到任何目的。
|
||||
|
||||
内网场景下,您可以将集群内的服务通过内网SLB进行暴露,然后在[云解析PrivateZone控制台](https://dns.console.aliyun.com/#/dns/domainList)通过添加A记录到该SLB的内网IP进行解析。具体操作,请参见[添加解析记录](https://help.aliyun.com/document_detail/64635.html)。
|
||||
|
||||
- 场景六:统一域名访问服务或是在集群内对域名的做CNAME解析
|
||||
|
||||
您可以实现在公网、内网和集群内部通过统一域名foo.example.com访问您的服务,原理如下:
|
||||
|
||||
- 集群内的服务`foo.default.svc.cluster.local`通过公网SLB进行了暴露,且有域名`foo.example.com`解析到该公网SLB的IP。
|
||||
|
||||
- 集群内服务`foo.default.svc.cluster.local`通过内网SLB进行了暴露,且通过[云解析PrivateZone](https://dns.console.aliyun.com/#/dns/domainList)在VPC内网中将`foo.example.com`解析到该内网SLB的IP。具体步骤,请参见上述[场景四:自定义Hosts](https://help.aliyun.com/document_detail/380963.html?spm=a2c4g.750001.0.i1#section-6jf-fgj-j2f)。
|
||||
|
||||
- 在集群内部,您可以通过Rewrite插件将
|
||||
|
||||
```go
|
||||
foo.example.com
|
||||
```
|
||||
|
||||
|
||||
|
||||
CNAME到
|
||||
|
||||
```go
|
||||
foo.default.svc.cluster.local
|
||||
```
|
||||
|
||||
。示例配置如下:
|
||||
|
||||
```go
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
ready
|
||||
|
||||
rewrite stop {
|
||||
name regex foo.example.com foo.default.svc.cluster.local
|
||||
answer name foo.default.svc.cluster.local foo.example.com
|
||||
}
|
||||
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf {
|
||||
prefer_udp
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
|
||||
- 场景七:禁止CoreDNS对IPv6类型的AAAA记录查询返回
|
||||
|
||||
当业务容器不需要AAAA记录类型时,可以在CoreDNS中将AAAA记录类型拦截,返回空(NODATA),以减少不必要的网络通信。示例配置如下:
|
||||
|
||||
```yaml
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health {
|
||||
lameduck 15s
|
||||
}
|
||||
#新增以下一行Template插件,其它数据请保持不变。
|
||||
template IN AAAA .
|
||||
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,214 @@
|
||||
> 原文链接:[](https://zhuanlan.zhihu.com/p/630430941)
|
||||
|
||||
某个客户在Kubernetes中部署了大规模的业务,因此选用IPVS作为`kube-proxy`的负载均衡转发模式。以追求更高的转发性能和更新规则的速度。
|
||||
|
||||
但是,IPVS有一个存在了很久的连接重用问题,当客户发布服务,因为存在大量TCP短连接,客户端出现了`No route to host`报错,业务服务出现故障。
|
||||
|
||||
这是一个存在已久的issue:[kubernetes#81775](https://link.zhihu.com/?target=https%3A//github.com/kubernetes/kubernetes/issues/81775)。我们今天就来简单分析一下并介绍解决方案。
|
||||
|
||||
### 复现
|
||||
|
||||
故障必须经过复现才有排查的手段,我这里介绍一个简单的复现方法。
|
||||
|
||||
首先,准备一个测试用的Kubernetes集群,kube-proxy使用IPVS模式,并且为了复现故障,把内核参数`net.ipv4.vs.conn_reuse_mode`设为`0`(在一些低版本的`kube-proxy`或低版本内核中,你会发现它默认为`0`):
|
||||
|
||||
```bash
|
||||
echo 0 > /proc/sys/net/ipv4/vs/conn_reuse_mode
|
||||
```
|
||||
|
||||
部署一个简单的HTTP服务,包含10个副本,并定义一个`LoadBalancer`类型的Service以模拟负载均衡的场景:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/ucloud-load-balancer-vserver-protocol: "TCP"
|
||||
service.beta.kubernetes.io/ucloud-load-balancer-type: "outer"
|
||||
name: httpgraceful
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: httpgraceful
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: httpgraceful
|
||||
labels:
|
||||
app: httpgraceful
|
||||
spec:
|
||||
replicas: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
app: httpgraceful
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: httpgraceful
|
||||
spec:
|
||||
containers:
|
||||
- name: http
|
||||
image: uhub.service.ucloud.cn/wxyz/httpgraceful:1.0.0
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- cat
|
||||
- /tmp/healthz
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 3
|
||||
```
|
||||
|
||||
等待Pod运行起来后,查看Service地址(这里假设为`106.75.10.10`):
|
||||
|
||||
```bash
|
||||
$ kubectl get svc
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
httpgraceful LoadBalancer 172.17.43.192 106.75.10.10 80:31984/TCP 48s
|
||||
```
|
||||
|
||||
确认该Service可以访问:
|
||||
|
||||
```bash
|
||||
$ curl http://106.75.10.10
|
||||
hello world
|
||||
version:1.0.0
|
||||
```
|
||||
|
||||
现在,使用`ab`命令,创建大量对该Service的短连接:
|
||||
|
||||
```bash
|
||||
ab -c 10 -n 2000000 http://106.75.10.10/
|
||||
```
|
||||
|
||||
同时,将HTTP服务的副本数改为5,以模拟服务发布的场景:
|
||||
|
||||
```bash
|
||||
kubectl scale deploy httpgraceful --replicas=5
|
||||
```
|
||||
|
||||
很快,你就可以看到`ab`命令输出错误:
|
||||
|
||||
```text
|
||||
Benchmarking 106.75.10.10 (be patient)
|
||||
apr_socket_recv: No route to host (113)
|
||||
Total of 5251 requests completed
|
||||
```
|
||||
|
||||
`No route to host`出现了!复现出了客户的错误。
|
||||
|
||||
这时候,登录机器,查看IPVS规则,你会发现该Service对应的RS出现了很多权重为0的项:
|
||||
|
||||
```bash
|
||||
$ ipvsadm -Ln
|
||||
IP Virtual Server version 1.2.1 (size=4096)
|
||||
Prot LocalAddress:Port Scheduler Flags
|
||||
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
|
||||
TCP 106.75.35.59:80 rr
|
||||
-> 10.9.19.40:8080 Masq 1 5 2
|
||||
-> 10.9.26.56:8080 Masq 0 1 0
|
||||
-> 10.9.82.126:8080 Masq 0 1 1
|
||||
-> 10.9.99.18:8080 Masq 0 1 0
|
||||
-> 10.9.127.64:8080 Masq 0 2 0
|
||||
-> 10.9.136.110:8080 Masq 1 6 1
|
||||
-> 10.9.157.207:8080 Masq 1 5 0
|
||||
-> 10.9.161.247:8080 Masq 1 5 0
|
||||
-> 10.9.187.95:8080 Masq 1 5 0
|
||||
```
|
||||
|
||||
没错,这就是导致这次问题的根因,我们会在下面进行分析。
|
||||
|
||||
### 原因分析
|
||||
|
||||
当业务进行缩容或者滚动迭代过程中,一定有旧的Pod被销毁。`No route to host`的直接原因是,IPVS把访问流量转发到了一个被销毁的Pod上面。
|
||||
|
||||
具体地说,当一个Pod被销毁后,`kube-controller-manager`中的`Endpoint Controller`会随之把对应的Pod Ip从`Endpoints`对象中摘除。kube-proxy随即通过informer获取到旧Pod Ip被摘除的事件,相应地更新ipvs转发规则:
|
||||
|
||||
- 先将旧的IP对应的RS权重置为0,防止新的连接调度到这个IP上。
|
||||
- 等存量连接归零后(ActiveConn+InactiveConn=0),再彻底摘除掉这个旧IP。
|
||||
|
||||
从上面`ipvsadm -Ln`的结果也可以看到,几个权重为0的RS都还有连接,所以没有被摘除。
|
||||
|
||||
kube-proxy不直接摘除RS是为了实现Pod的优雅退出,以让Pod在处理完所有请求之后再退出。
|
||||
|
||||
但我们碰到的问题实际上是,权重被置为0后,依然有新连接被调度到旧IP上。这样存量连接计数永远不会归零,旧RS永远无法被摘除。而由于旧的IP实际已经不存在,就出现了`No route to host`报错。只有把流量撤走一段时间后,连接计数终于为0,旧RS被摘除。
|
||||
|
||||
这一切的罪魁祸首是一个内核参数:`net.ipv4.vs.conn_reuse_mode`。
|
||||
|
||||
众所周知,在大量TCP短连接的场景下,会有很多连接处于`TIME_WAIT`状态,为了减少资源占用,内核会重用这些连接的端口。
|
||||
|
||||
而如果`net.ipv4.vs.conn_reuse_mode`为0,并且客户端的源端口被复用了,那么IPVS会将流量直接转发到之前RS,而绕过了正常的负载均衡调度。这样即使RS的权重为0,也可能会有流量被转发上去。
|
||||
|
||||
在大量TCP短连接时,端口复用的频率会非常高,这样即使RS的权重被设为了0,还是会有部分流量打上来,而其背后的Pod已经被删除了(优雅退出有超时时间,超过之后Kubernetes会强制删除Pod),就导致了流量被转发到了一个不存在的IP上,出现`No route to host`错误。
|
||||
|
||||
那么其实只要将`net.ipv4.vs.conn_reuse_mode`设为1,强制让复用的连接也经过负载均衡转发不就可以了?
|
||||
|
||||
可惜,在`5.9`之前的内核版本中,`net.ipv4.vs.conn_reuse_mode`设为1并使用了`conntrack`时,如果出现了连接复用,IPVS会 DROP 掉第一个 SYN 包,导致 SYN 的重传,有 1s 延迟。而 Kube-proxy 在 IPVS 模式下,使用了 iptables 进行`MASQUERADE`,也正好开启了`net.ipv4.vs.conntrack`。
|
||||
|
||||
所以:
|
||||
|
||||
- 如果将`net.ipv4.vs.conn_reuse_mode`设为0,当出现大量短TCP连接时,会出现部分流量被转发到销毁Pod的问题。
|
||||
- 如果将`net.ipv4.vs.conn_reuse_mode`设为1,当出现大量短TCP连接时,会有部分连接多出了1s的延迟的问题。
|
||||
|
||||
### 解决
|
||||
|
||||
也就是说,在以前的内核版本中,理想的情况是:
|
||||
|
||||
- `net.ipv4.vs.conn_reuse_mode`设为1,强制复用连接走负载均衡。
|
||||
- `net.ipv4.vs.conntrack`设为0,防止IPVS对复用连接进行DROP SYNC操作。
|
||||
|
||||
但是,很可惜,这实现不了。**因此,如果你的内核版本低于`5.9`,并且存在大量TCP短连接场景,不建议使用IPVS,建议替换为iptables模式。**
|
||||
|
||||
`5.9`的一个[patch](https://link.zhihu.com/?target=http%3A//patchwork.ozlabs.org/project/netfilter-devel/patch/20200701151719.4751-1-ja%40ssi.bg)修复了在启用`conntrack`时的1s延迟问题,因此,如果内核版本大于等于`5.9`,将`net.ipv4.vs.conn_reuse_mode`设为1是安全的,保持这个内核参数为1即可解决问题。
|
||||
|
||||
事实上,新的kube-proxy会自动根据你的内核版本来判断是否要修改这个内核参数:
|
||||
|
||||
```go
|
||||
const connReuseFixedKernelVersion = "5.9"
|
||||
|
||||
if kernelVersion.LessThan(version.MustParseGeneric(connReuseMinSupportedKernelVersion)) {
|
||||
klog.ErrorS(nil, "Can't set sysctl, kernel version doesn't satisfy minimum version requirements", "sysctl", sysctlConnReuse, "minimumKernelVersion", connReuseMinSupportedKernelVersion)
|
||||
} else if kernelVersion.AtLeast(version.MustParseGeneric(connReuseFixedKernelVersion)) {
|
||||
// https://github.com/kubernetes/kubernetes/issues/93297
|
||||
klog.V(2).InfoS("Left as-is", "sysctl", sysctlConnReuse)
|
||||
} else {
|
||||
// Set the connection reuse mode
|
||||
if err := utilproxy.EnsureSysctl(sysctl, sysctlConnReuse, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
而因为我们提供给客户的内核版本经过了魔改,虽然其已经包含了这个patch,但是版本小于`5.9`,所以需要在kube-proxy启动之后强制将`net.ipv4.vs.conn_reuse_mode`设为1。
|
||||
|
||||
如果使用systemd部署kube-proxy,可以用`ExecStartPost`来完成内核参数的修改:
|
||||
|
||||
```text
|
||||
[Service]
|
||||
...
|
||||
ExecStartPost=/sbin/sysctl -w net.ipv4.vs.conn_reuse_mode=1
|
||||
```
|
||||
|
||||
如果用Pod部署kube-proxy,可以使用PostStart完成:
|
||||
|
||||
```yaml
|
||||
lifecycle:
|
||||
postStart:
|
||||
exec:
|
||||
command:
|
||||
- /sbin/sysctl
|
||||
- -w
|
||||
- net.ipv4.vs.conn_reuse_mode=1
|
||||
```
|
||||
|
||||
|
||||
|
||||
https://mp.weixin.qq.com/s/H5zrelsEVtAmy96ObhuokA
|
||||
Reference in New Issue
Block a user