Kubernetes Pod警告:1个节点存在卷节点亲和性冲突。

134

我尝试搭建Kubernetes集群。我已经设置好并运行了持久卷(Persistent Volume)、持久卷声明(Persistent Volume Claim)和存储类(Storage Class),但是当我想从部署创建Pod时,Pod被创建了但停留在“Pending”状态。在描述(describe)后,我只得到了这个警告:“1个节点存在卷节点亲和性冲突”。有人可以告诉我在我的卷配置中缺少什么吗?

apiVersion: v1
kind: PersistentVolume
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: mariadb-pv0
  name: mariadb-pv0
spec:
  volumeMode: Filesystem
  storageClassName: local-storage
  local:
    path: "/home/gtcontainer/applications/data/db/mariadb"
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 2Gi
  claimRef:
    namespace: default
    name: mariadb-claim0
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/cvl-gtv-42.corp.globaltelemetrics.eu
            operator: In
            values:
            - master

status: {}

1
这是验证问题的步骤。 - Vishrant
18个回答

174

“卷节点亲和性冲突”错误发生在持久化卷声明被调度到不同区域,而不是一个区域,并且实际的 Pod 无法被调度,因为它无法从另一个区域连接到卷。 您可以查看所有持久化卷的详细信息来检查此问题。要检查,请首先获取您的 PVC:

$ kubectl get pvc -n <namespace>

然后获取持久卷的详细信息(而不是卷声明)

$  kubectl get pv

找到与您的PVC对应的PV,并描述它们。

$  kubectl describe pv <pv1> <pv2>

您可以检查每个PV的Source.VolumeID,很可能它们属于不同的可用区,因此您的Pod会出现亲和性错误。 为了解决这个问题,请为单个可用区创建一个StorageClass并在您的PVC中使用该StorageClass。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: region1storageclass
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  encrypted: "true" # if encryption required
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: failure-domain.beta.kubernetes.io/zone
    values:
    - eu-west-2b # this is the availability zone, will depend on your cloud provider
    # multi-az can be added, but that defeats the purpose in our scenario

1
有没有一种方法可以将不同区域的音量连接到安排在另一个区域节点上的 Pod? - Vedant Aggrawal
1
在默认的StorageClass上设置volumeBindingMode: WaitForFirstConsumer是否足够? - Sid
6
我通过以下方式解决了这个问题:
  • 删除 PVC 和 PV
  • 在存储类中设置 "volumeBindingMode: WaitForFirstConsumer",而不是 Immediate
  • 重新部署 Pod
- rh0x
将存储类设置为与节点位于同一区域,但似乎计算优化节点在分配存储时存在问题。 - Dzmitry Lahoda
1
我在AKS上遇到了同样的问题。我的默认SC已经设置了volumeBindingMode: WaitForFirstConsumer。这是在Azure中进行Kubernetes升级过程后发生的,我怀疑存在一些超额承诺导致CPU /内存消耗过高,从而导致Pod被调度到另一个节点。 - Nik
显示剩余6条评论

49

0. 如果在其他答案中没有找到解决方案...

在我们的情况下,该错误发生在使用 Pulumi 最新提供的 AWS EKS 集群上 (请见完整代码)。这个错误让我感到非常困扰,因为我没有更改任何内容,只是按照 Buildpacks Tekton 文档中描述的方式创建了一个 PersistentVolumeClaim

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: buildpacks-source-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

除了默认的EKS配置之外,我没有更改其他任何内容,也没有添加/更改任何PersistentVolumeStorageClass(实际上我甚至不知道如何做)。由于默认的EKS设置似乎依赖于2个节点,所以我遇到了以下错误:

0/2 nodes are available: 2 node(s) had volume node affinity conflict.

阅读Sownak Roy的回答,我初步了解了要做什么,但不知道如何操作。因此,对于那些感兴趣的人,这里是我解决错误的所有步骤:

1. 检查 EKS 节点上的 failure-domain.beta.kubernetes.io 标签

正如在本文“Statefull applications”一节中所述,两个节点在其他 AWS 可用区提供,并且创建了由我们上面描述的PersistendVolumeClaim应用程序生成的持久卷(PV)。

为了检查这一点,您需要使用kubectl get nodes命令查看/描述您的节点:

$ kubectl get nodes
NAME                                             STATUS   ROLES    AGE     VERSION
ip-172-31-10-186.eu-central-1.compute.internal   Ready    <none>   2d16h   v1.21.5-eks-bc4871b
ip-172-31-20-83.eu-central-1.compute.internal    Ready    <none>   2d16h   v1.21.5-eks-bc4871b

接着使用kubectl describe node <node-name>命令查看Label部分:

$ kubectl describe node ip-172-77-88-99.eu-central-1.compute.internal
Name:               ip-172-77-88-99.eu-central-1.compute.internal
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=t2.medium
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eu-central-1
                    failure-domain.beta.kubernetes.io/zone=eu-central-1b
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=ip-172-77-88-99.eu-central-1.compute.internal
                    kubernetes.io/os=linux
                    node.kubernetes.io/instance-type=t2.medium
                    topology.kubernetes.io/region=eu-central-1
                    topology.kubernetes.io/zone=eu-central-1b
Annotations:        node.alpha.kubernetes.io/ttl: 0
...

在我的情况下,节点ip-172-77-88-99.eu-central-1.compute.internalfailure-domain.beta.kubernetes.io/region定义为eu-central-1,并且将az与failure-domain.beta.kubernetes.io/zone定义为eu-central-1b

而另一个节点则将failure-domain.beta.kubernetes.io/zone定义为az eu-central-1a

$ kubectl describe nodes ip-172-31-10-186.eu-central-1.compute.internal
Name:               ip-172-31-10-186.eu-central-1.compute.internal
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=t2.medium
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eu-central-1
                    failure-domain.beta.kubernetes.io/zone=eu-central-1a
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=ip-172-31-10-186.eu-central-1.compute.internal
                    kubernetes.io/os=linux
                    node.kubernetes.io/instance-type=t2.medium
                    topology.kubernetes.io/region=eu-central-1
                    topology.kubernetes.io/zone=eu-central-1a
Annotations:        node.alpha.kubernetes.io/ttl: 0
...

2. 检查PersistentVolumetopology.kubernetes.io字段

现在,我们应该检查手动申请PersistentVolumeClaim后自动分配的PersistentVolume。使用kubectl get pv命令进行检查:

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                           STORAGECLASS   REASON   AGE
pvc-93650993-6154-4bd0-bd1c-6260e7df49d3   1Gi        RWO            Delete           Bound    default/buildpacks-source-pvc   gp2                     21d

紧接着kubectl describe pv <pv-name>

$ kubectl describe pv pvc-93650993-6154-4bd0-bd1c-6260e7df49d3
Name:              pvc-93650993-6154-4bd0-bd1c-6260e7df49d3
Labels:            topology.kubernetes.io/region=eu-central-1
                   topology.kubernetes.io/zone=eu-central-1c
Annotations:       kubernetes.io/createdby: aws-ebs-dynamic-provisioner
...

使用标签topology.kubernetes.io/zone在azeu-central-1c 配置了 PersistentVolume这导致我们的Pod抱怨找不到它们的卷——因为它们位于完全不同的az!

3. 添加allowedTopologiesStorageClass

Kubernetes文档中所述,解决这个问题的一种方法是向StorageClass添加allowedTopologies配置。如果您已经像我一样配置了EKS集群,则需要使用以下命令检索已定义的StorageClass:

kubectl get storageclasses gp2 -o yaml

将其保存到名为storage-class.yml的文件中,并添加一个allowedTopologies部分,该部分与您的节点的failure-domain.beta.kubernetes.io标签匹配,如下所示:

allowedTopologies:
- matchLabelExpressions:
  - key: failure-domain.beta.kubernetes.io/zone
    values:
    - eu-central-1a
    - eu-central-1b
allowedTopologies 配置定义了 PersistentVolumefailure-domain.beta.kubernetes.io/zone 必须是 eu-central-1a 或者 eu-central-1b,而不是 eu-central-1c!完整的 storage-class.yml 如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp2
parameters:
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: failure-domain.beta.kubernetes.io/zone
    values:
    - eu-central-1a
    - eu-central-1b

使用以下命令将增强的StorageClass配置应用于您的EKS集群:

kubectl apply -f storage-class.yml

4. 删除PersistentVolumeClaim,在其中添加storageClassName: gp2并重新应用

为了让事情重新开始工作,我们需要先删除PersistentVolumeClaim

为了将PersistentVolumeClaim映射到我们之前定义的StorageClass,我们需要在pvc.yml中的PersistendVolumeClaim定义中添加storageClassName: gp2

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: buildpacks-source-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
  storageClassName: gp2

最后使用kubectl apply -f pvc.yml重新应用PersistentVolumeClaim,这样应该可以解决错误。


谢谢您提供这些步骤,但我不确定这是否会在eu-central-1a和1b中创建pv并将其从1c中删除。 - kent
你的步骤似乎没有说明如何让节点显示在特定的区域。我三次阅读过,是我漏掉了吗?我猜想节点和PVC都必须固定在特定的区域才能正常工作,对吗? - undefined

17

出现此错误可能有以下原因:

  1. 节点未正确标记。我在AWS上遇到过这个问题,当我的工作节点没有适当的标签(但主节点有)时,就像这样:

    failure-domain.beta.kubernetes.io/region=us-east-2

    failure-domain.beta.kubernetes.io/zone=us-east-2c

    添加了这些标签后,“1 node(s) had volume node affinity conflict”错误消失了,因此PV、PVC与pod一起部署成功了。 这些标签的值是云提供商特定的。基本上,云提供商应该根据定义在cube-controller、API-server、kubelet中的--cloud-provider选项来设置这些标签。如果没有设置适当的标签,则请检查CloudProvider集成是否正确。我使用了kubeadm,所以设置起来有点麻烦,但是使用其他工具,如kops,就可以立即使用了。

  2. 根据您的PV定义和nodeAffinity字段的使用,您正在尝试使用本地卷(阅读此处的本地卷说明链接,官方文档),请确保您设置了“NodeAffinity字段”,就像这样(在我的AWS案例中可行):

    nodeAffinity:

         required:
          nodeSelectorTerms:
           - matchExpressions:
             - key: kubernetes.io/hostname
               operator: In
               values:
               - my-node  # it must be the name of your node(kubectl get nodes)
    

    这样,在创建资源并对其运行描述后,它将像这样显示:

         Required Terms:  
                    Term 0:  kubernetes.io/hostname in [your node name]
  1. 为使本地存储正常工作,必须使用 volumeBindingMode 设置为 WaitForFirstConsumer 创建名为 local-storage 的 StorageClass 定义。请参考此处的示例storage class local description, official doc来了解其原因。

感谢您的建议。最近在我的Windows机器上进行升级/重新安装后,我的Kubernetes节点的默认名称已经悄悄地从“docker-for-desktop”更改为“docker-desktop”。 - morechilli
我在使用nodeAffinity时只使用了主机名,而没有使用kubectl get nodes中描述的完全限定主机名 - 这是我的错误。非常感谢! - snukone

8

经过一番令人头痛的调查,有几个需要检查的事项:

Azure:

  • 您的集群是否选择了多个区域?(区域1、2、3)
  • 您的默认存储类别是否具有正确的存储提供程序?(ZRS区域冗余存储)

如果没有:

  • 将存储类别更改为正确的提供程序
  • 备份PV数据
  • 停止使用PVC的部署(将副本数设置为0)
  • 删除PVC并确认关联的PV已被删除。
  • 重新应用PVC配置文件(不要引用旧存储类别名称)
  • 启动使用PVC的部署(将副本数设置为1)
  • 手动导入备份数据

AKS的存储类别示例:

allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: zone-redundant-storage
parameters:
  skuname: StandardSSD_ZRS
provisioner: disk.csi.azure.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

GKE:

  • 您的集群是否选择了多个区域?(A 区,B 区,C 区)
  • 您的默认存储类是否具有复制类型参数?(replication-type:regional-pd)

如果没有:

  • 更改存储类以使用正确的参数。
  • 备份 PV 数据。
  • 停止使用 PVC 的部署(将副本数设置为 0)。
  • 删除 PVC 并确认关联的 PV 已被删除。
  • 重新应用 PVC 配置 YAML(不参考旧的存储类名称)。
  • 启动使用 PVC 的部署(将副本数设置为 1)。
  • 手动导入备份数据。

GKE 的示例存储类:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: standard-regional-pd-storage
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-standard
  replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer

此后,PV将在选定的区域之间具有冗余性,使得Pod可以从不同区域的其他节点访问PV。


感谢您建议创建持久卷的备份,这里唯一的评论是建议这样做! 许多解决方案都假定持久卷上没有数据,并建议在没有任何备份的情况下进行删除。 - Aron Gates
我将使用 pd-balanced 类型。 - JackTheKnife
如何手动导入数据? - Fadi
如何手动导入数据? - undefined

8
"1 node(s) had volume node affinity conflict"错误是由调度器创建的,因为它无法将您的Pod调度到符合持久化存储Volume(PV)中persistenvolume.spec.nodeAffinity字段的节点。
换句话说,您在PV中指定了使用此PV的Pod必须被调度到带有标签kubernetes.io/cvl-gtv-42.corp.globaltelemetrics.eu = master的节点,但由于某些原因这是不可能的。
造成Pod无法调度到该节点的原因可能有很多:
  • Pod具有与目标节点冲突的节点亲和性、Pod亲和性等
  • 目标节点已被标记
  • 目标节点已达到“每个节点的最大Pod数”限制
  • 不存在带有给定标签的节点
查找原因的起点是节点和Pod的定义。

在阅读您的帖子之前,我已经意识到了这一点,但是“目标节点已达到其'每个节点的最大Pod数'限制”对我来说很关键。+1 - Rambatino

7

救命的注释 - Alexander Nekrasov

6
在我的情况下,这发生在升级到k8s v1.25之后的GKE上。对我来说,上述解决方法都没有奏效,所以我研究了如何克隆卷,因为我不想丢失数据。
这篇文章引导我启用了“Compute Engine persistent disk CSI Driver”(计算引擎持久磁盘CSI驱动程序),一旦启用,问题就得到了解决。请参考:此文此链接

2
遇到了相同的问题。运行 gcloud container clusters update CLUSTER-NAME --update-addons=GcePersistentDiskCsiDriver=ENABLED 后,问题得到解决,Pod 正确启动。 - needRhelp
2
哦,我的天啊。那真的救了我一命。我升级了我们的集群和数据库,但使用PVC的Redis服务器没有启动。解决方案实际上是启用驱动程序。我不知道为什么这个方法没有在任何地方记录。 - David Dehghan
1
似乎自从我多年前创建了我的集群以来,它从未启用过此选项。也许新的集群创建默认已经包含了这个选项。 - David Dehghan
更多的背景信息:在v1.25中,GCP的内部CSI驱动程序被删除了,而且我认为发布说明没有很好地强调这一点。这个主题在“紧急升级说明”中缺失,而在变更日志中,措辞是“csi迁移已晋升为GA”。使用v1.22+创建的集群默认启用了外部驱动程序。请参见此处以获取更多信息:https://binx.io/2023/01/20/do-this-before-you-upgrade-gke-to-k8s-1-25-or-you-might-feel-sorry/ - Michał Łazowik

6

Sownak Roy提供了很好的答案。我也遇到了类似的情况,即PV在与本应使用它的节点不同的区域中创建。我采用的解决方案基于Sownak的答案,但在我的情况下,只需指定存储类而不需要“allowedTopologies”列表,如下所示:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cloud-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
volumeBindingMode: WaitForFirstConsumer

2
这在过去对我起作用。一个月后,我再次启动了我的集群,但遇到了同样的问题。Storageclass已经应用。 :( - Nikhil Utane

5
  1. 确保Kubernetes节点具有所需的标签。您可以使用以下命令验证节点标签:
kubectl get nodes --show-labels

其中一个kubernetes节点应该显示出持久卷的名称/标签,而您的Pod应该调度在同一节点上

  1. 确保在PersistentVolumeClaim中请求的大小与PersistentVolume的大小匹配。如果大小不匹配,则要么更正PersistentVolumeClaim中的resources.requests.storage,要么删除旧的PersistentVolume并创建一个具有正确大小的新卷。

验证步骤:

  1. 描述您的持久卷:
kubectl describe pv postgres-br-proxy-pv-0

输出:

...
Node Affinity:
  Required Terms:
    Term 0:        postgres-br-proxy in [postgres-br-proxy-pv-0]
...
  1. 显示节点标签:
kubectl get nodes --show-labels

输出:

NAME    STATUS   ROLES    AGE   VERSION   LABELS
node3   Ready    <none>   19d   v1.17.6   postgres-br-proxy=postgres-br-proxy-pv-0

如果节点上没有持久卷标签,你的 pod 正在使用的,那么该 pod 将无法被调度。

5

在我的情况下,根本原因是持久卷位于us-west-2c,而新的工作节点被重新启动以位于us-west-2a和us-west-2b。解决方案是要么拥有更多的工作节点,使它们位于更多区域,要么为应用程序删除/扩大节点亲和性,以便更多的工作节点符合绑定到持久卷的条件。


使用AWS EKS时,我为节点组(NG)设置的desired_size(或min_size)是2,这比我的EKS子网数量(3)小。因此,1个可用区域没有任何节点覆盖。当我将NG大小增加到3时(因此跨越所有可用区域),我的问题得到了解决。我认为这提出了一个最佳实践:在使用PersistentVolume时,请确保节点组大小大于或等于可用区域数量。 - Vincent Yin
1
谢谢,这是最不危险的解决方案。 - Meir Gabay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接