Watch & Learn

Debugwar Blog

To be or not to be, this is a problem ...

[笔记] 学习K8S过程中遇到的坑

2021-07-07 01:56:19

coredns问题


使用kubeadm部署集群,部署的过程中,kubeadm会在kube-system命名空间下创建k8s需要的基础服务。但是笔者的1.21.2版本集群,这个镜像无法创建成功。具体表现为:pod/coredns的STATUS字段为ImagePullBackOff或ErrImagePull。

这个问题会导致,整个k8s集群中的节点,在使用“域名”访问时,会无法解析“域名”,从而访问失败。如果你的业务有用到headless service或者节点中的域名,要注意这个服务有没有问题。

笔者出问题的镜像已经不在了,用个别人的图1


问题原因


一句话原因:源中不存在coredns镜像。
详情:由于众所周知的原因,相信大家在部署k8s集群的时候都会选择国内的源,我相信很多小伙伴都会用下面这个源

  1. registry.aliyuncs.com  

然而这个源中是不存在coredns这个镜像的

  1. [root@env ~]# kubectl -n kube-system describe pod/coredns-6f6b8cc4f6-9c67s  
  2. Name:                 coredns-6f6b8cc4f6-9c67s  
  3. Namespace:            kube-system  
  4. …………  
  5. Containers:  
  6.   coredns:  
  7.     Container ID:  docker://e655081d120efe6a69e564e20edfa88c0e0ad2a29cb11f35870fe2cef1057fb0  
  8.     Image:       registry.aliyuncs.com/google_containers/coredns:v1.8.0  
  9.     Image ID:      docker-pullable://coredns/coredns@sha256:cc8fb77bc2a0541949d1d9320a641b82fd392b0d3d8145469ca4709ae769980e  
  10. …………  
  11.   
  12. [root@env ~]# docker pullregistry.aliyuncs.com/google_containers/coredns:v1.8.0  
  13. Error response from daemon: manifest for registry.aliyuncs.com/google_containers/coredns:v1.8.0 not found: manifest unknown: manifest unknown  

解决方案


一句话解决方案:从官方源下载coredns镜像,然后本地tag成需要的镜像。

详情


首先从官方源获取coredns的image:

  1. docker pull coredns/coredns:1.8.0    

然后转换成本地的镜像:

  1. docker tag coredns/coredns:1.8.0 registry.aliyuncs.com/google_containers/coredns/coredns:v1.8.0  

以上操作理论上需要在所有k8s节点上执行,因为coredns的容器可能会被分配到任意节点。

NFS持久化卷unbound报错


NFS绝对是k8s中入门级的、成本最小的方案之一。然而笔者在部署使用NFS卷容器时,经常遇见容器一直Pending的状态,经过describe查询,发现是如下问题:


问题原因


一句话原因:没有响应PVC请求的“人”,导致PVC无法真正落地成磁盘上的存储区域。

详情

在k8s体系中,所有对持久化卷的请求都是以PVC的方式存在。然而请求发出去了,谁来处理呢?有两种办法,一种是人工处理PVC请求,另外一种是使用容器自动处置PVC请求。

解决方案


网上大量搜到的教程,都是使用如下容器处理NFS的PVC请求:

  1. quay.io/external_storage/nfs-client-provisioner:latest

找到一篇不错的文章来描述整个部署过程2,这里贴一些关键的点:

由于nfs-client-provisioner容器需要访问k8s集群的一些状态和属性,因此需要先给一下授权,文件rbac.yaml如下:

  1. apiVersion: v1  
  2. kind: ServiceAccount  
  3. metadata:  
  4.   name: nfs-client-provisioner  
  5.   # replace with namespace where provisioner is deployed  
  6.   namespace: kafka-test  
  7. ---  
  8. kind: ClusterRole  
  9. apiVersion: rbac.authorization.k8s.io/v1  
  10. metadata:  
  11.   name: nfs-client-provisioner-runner  
  12. rules:  
  13.   - apiGroups: [""]  
  14.     resources: ["persistentvolumes"]  
  15.     verbs: ["get""list""watch""create""delete"]  
  16.   - apiGroups: [""]  
  17.     resources: ["persistentvolumeclaims"]  
  18.     verbs: ["get""list""watch""update"]  
  19.   - apiGroups: ["storage.k8s.io"]  
  20.     resources: ["storageclasses"]  
  21.     verbs: ["get""list""watch"]  
  22.   - apiGroups: [""]  
  23.     resources: ["events"]  
  24.     verbs: ["create""update""patch"]  
  25. ---  
  26. kind: ClusterRoleBinding  
  27. apiVersion: rbac.authorization.k8s.io/v1  
  28. metadata:  
  29.   name: run-nfs-client-provisioner  
  30. subjects:  
  31.   - kind: ServiceAccount  
  32.     name: nfs-client-provisioner  
  33.     # replace with namespace where provisioner is deployed  
  34.     namespace: kafka-test  
  35. roleRef:  
  36.   kind: ClusterRole  
  37.   name: nfs-client-provisioner-runner  
  38.   apiGroup: rbac.authorization.k8s.io  
  39. ---  
  40. kind: Role  
  41. apiVersion: rbac.authorization.k8s.io/v1  
  42. metadata:  
  43.   name: leader-locking-nfs-client-provisioner  
  44.   # replace with namespace where provisioner is deployed  
  45.   namespace: kafka-test  
  46. rules:  
  47.   - apiGroups: [""]  
  48.     resources: ["endpoints"]  
  49.     verbs: ["get""list""watch""create""update""patch"]  
  50. ---  
  51. kind: RoleBinding  
  52. apiVersion: rbac.authorization.k8s.io/v1  
  53. metadata:  
  54.   name: leader-locking-nfs-client-provisioner  
  55.   # replace with namespace where provisioner is deployed  
  56.   namespace: kafka-test  
  57. subjects:  
  58.   - kind: ServiceAccount  
  59.     name: nfs-client-provisioner  
  60.     # replace with namespace where provisioner is deployed  
  61.     namespace: kafka-test  
  62. roleRef:  
  63.   kind: Role  
  64.   name: leader-locking-nfs-client-provisioner  
  65.   apiGroup: rbac.authorization.k8s.io  

然后使用如下命令创建授权:

  1. kubectl apply -f rbac.yaml  

然后创建nfs-client-provisioner POD, 首先是deployment.yaml文件:

  1. apiVersion: apps/v1  
  2. kind: Deployment  
  3. metadata:  
  4.   name: nfs-client-provisioner  
  5.   labels:  
  6.     app: nfs-client-provisioner  
  7.   # replace with namespace where provisioner is deployed  
  8.   namespace: kafka-test  
  9. spec:  
  10.   replicas: 1  
  11.   strategy:  
  12.     type: Recreate  
  13.   selector:  
  14.     matchLabels:  
  15.       app: nfs-client-provisioner  
  16.   template:  
  17.     metadata:  
  18.       labels:  
  19.         app: nfs-client-provisioner  
  20.     spec:  
  21.       serviceAccountName: nfs-client-provisioner  
  22.       containers:  
  23.         - name: nfs-client-provisioner  
  24.           image: quay.io/external_storage/nfs-client-provisioner:latest  
  25.           volumeMounts:  
  26.             - name: nfs-client-root  
  27.               mountPath: /persistentvolumes  
  28.           env:  
  29.             - name: PROVISIONER_NAME  
  30.               value: fuseim.pri/ifs  
  31.             - name: NFS_SERVER  
  32.               value: 192.168.50.42  
  33.             - name: NFS_PATH  
  34.               value: /volume1/nfs-kafka  
  35.       volumes:  
  36.         - name: nfs-client-root  
  37.           nfs:  
  38.             server: 192.168.50.42  
  39.             path: /volume1/nfs-kafka  

然后用如下命令创建:

  1. kubectl apply -f deployment.yaml  

最后,创建对应的NFS StorageClass, storage-class.yaml文件:

  1. apiVersion: storage.k8s.io/v1  
  2. kind: StorageClass  
  3. metadata:  
  4.   name: managed-nfs-storage  # 注意这里的值,要和业务容器volumeClaimTemplates的storageClassName一致
  5. provisioner: fuseim.pri/ifs # 注意这里的值要和nfs-client-provisioner容器的部署文件(deployment.yaml)中的 env PROVISIONER_NAME'一致  
  6. parameters:  
  7.   archiveOnDelete: "false"  

以上就是NFS卷几个比较关键的点了,然而你以为这就完了吗…………?

由NFS容器引出的k8s bug


在使用nfs-client-provisioner容器的时候,发现容器一直报这个错:

  1. unexpected error getting claim reference: selfLink was empty, can't make reference   

这个报错会导致NFS卷无法被正确创建,进而导致业务无法正常部署。

最终发现是由于k8s 1.20.x与1.21.x版本的bug导致的3

  1. It looks like newer Kubernetes (1.20 / 1.21) have deprecated selflinks and this mandates a code change in NFS provisioners. See this issue for details: https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/issues/25.  
  2.   
  3. I was able to work around this for now using the - --feature-gates=RemoveSelfLink=false work around mentioned there, but this isn't a long term solution.  

解决方案


一句话解决方案:在/etc/kubernetes/manifests/kube-apiserver.yaml中新增- --feature-gates=RemoveSelfLink=false参数

详情

涉及到的改动如下4(只需要在apiserver节点,即master节点执行此操作):

  1. [root@env]# cat /etc/kubernetes/manifests/kube-apiserver.yaml   
  2. apiVersion: v1  
  3. kind: Pod  
  4. metadata:  
  5.   labels:  
  6.     component: kube-apiserver  
  7.     tier: control-plane  
  8.   name: kube-apiserver  
  9.   namespace: kube-system  
  10. spec:  
  11.   containers:  
  12.   - command:  
  13.     - kube-apiserver  
  14.     …………  
  15.     - --feature-gates=RemoveSelfLink=false  
  16.     image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.21.2  
  17.     …………  

volumeClaimTemplates与persistentVolumeClaim的区别


实际上volumeClaimTemplates与persistentVolumeClaim都会产生PVC,但是两者是有区别的。先一句话概括一下:volumeClaimTemplates产生的PVC名字会自动加上app name,而persistentVolumeClaim不会,persistentVolumeClaim你写什么就是什么。

来看个具体的例子。

volumeClaimTemplates


volumeClaimTemplates的使用方法:

  1. kind: StatefulSet  
  2. apiVersion: apps/v1  
  3. metadata:  
  4.   labels:  
  5.     app: rabbitmq-cluster  
  6.   name: rabbitmq-cluster  
  7.   namespace: ns-public-rabbitmq  
  8. spec:  
  9.   replicas: 3  
  10.   selector:  
  11.     matchLabels:  
  12.       app: rabbitmq-cluster  
  13.   serviceName: rabbitmq-cluster  
  14.   template:  
  15.     metadata:  
  16.       labels:  
  17.         app: rabbitmq-cluster  
  18.     spec:  
  19.       containers:  
  20.       - args:  
  21.         ………………  
  22.         volumeMounts:  
  23.         - mountPath: /var/lib/rabbitmq  
  24.           name: rabbitmq-storage  
  25.           readOnly: false  
  26.   volumeClaimTemplates:  
  27.   - metadata:  
  28.       name: rabbitmq-storage  
  29.     spec:  
  30.       accessModes:  
  31.       - ReadWriteMany  
  32.       storageClassName: "rabbitmq-nfs-storage"  
  33.       resources:  
  34.         requests:  
  35.           storage: 2Gi  

看一下volumeClaimTemplates创建的pvc:

  1. [root@platform01v ~]# kubectl -n ns-public-rabbitmq get pvc  
  2. NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS           AGE  
  3. rabbitmq-storage-rabbitmq-cluster-0   Bound    pvc-b75e6346-bee5-49fb-8881-308d975ae135   2Gi        RWX            rabbitmq-nfs-storage   40h  
  4. rabbitmq-storage-rabbitmq-cluster-1   Bound    pvc-4c8fa6a6-2818-41f4-891a-f2f389341f51   2Gi        RWX            rabbitmq-nfs-storage   40h  
  5. rabbitmq-storage-rabbitmq-cluster-2   Bound    pvc-941893ea-d600-40ac-a97f-ce9099ba9bbb   2Gi        RWX            rabbitmq-nfs-storage   40h  

可以看到, NAME字段的格式为

[.spec.volumeClaimTemplates.metadata.name]-[PodName]

persistentVolumeClaim


再来看看persistentVolumeClaim创建的, 首先是如何使用:

  1. kind: StatefulSet  
  2. apiVersion: apps/v1  
  3. metadata:  
  4.   labels:  
  5.     app: workplatform-cluster  
  6.   name: workplatform-cluster  
  7.   namespace: ns-analyzer-workplatform  
  8. spec:  
  9.   replicas: 3  
  10.   selector:  
  11.     matchLabels:  
  12.       app: workplatform-cluster  
  13.   serviceName: workplatform-cluster  
  14.   template:  
  15.     metadata:  
  16.       labels:  
  17.         app: workplatform-cluster  
  18.     spec:  
  19.       containers:  
  20.       - args:  
  21.         ………………  
  22.         volumeMounts:  
  23.         - mountPath: /exports/apps  
  24.           name: workplatform-nfs-storage  
  25.           readOnly: false  
  26.       volumes:  
  27.         - name: workplatform-nfs-storage  
  28.           persistentVolumeClaim:  
  29.             claimName: workplatform-nfs-pvc  

看一下persistentVolumeClaim创建的PVC:

  1. [root@env work-platform]# kubectl -n ns-analyzer-workplatform get pvc  
  2. NAME                   STATUS   VOLUME                CAPACITY   ACCESS MODES   STORAGECLASS               AGE  
  3. workplatform-nfs-pvc   Bound    workplatform-nfs-pv   5Gi        RWX            nfs-workplatform-storage   14h  

可以看到NAME没有做任何修改,为指定的

[.spec.volumes.persistentVolumeClaim.claimName]

这个差异会导致,容器挂在的目录是不一样的,使用volumeClaimTemplates的集群会通过nfs-client-provisioner容器为每个Pod创建一个文件夹并挂载,而使用persistentVolumeClaim会为所有的Pod挂载同一个文件夹:


参考


  1. k8s的 coredns 的ImagePullBackOff 和ErrImagePull 问题解决
  2. K8S的StorageClass实战(NFS)
  3. unexpected error getting claim reference: selfLink was empty, can't make reference
  4. k8s 1.20.x版本NFS动态存储配置
Catalog
  • coredns问题
  • 问题原因
  • 解决方案
  • NFS持久化卷unbound报错
  • 问题原因
  • 解决方案
  • 由NFS容器引出的k8s bug
  • 解决方案
  • volumeClaimTemplates与persistentVolumeClaim的区别
  • volumeClaimTemplates
  • persistentVolumeClaim
  • 参考
  • CopyRight(c) 2020 - 2025 Debugwar.com

    Designed by Hacksign