一、PV/PVC/Storage Class简介

管理存储是管理计算的一个明显问题。该PersistentVolume子系统为用户和管理员提供了一个API,用于抽象如何根据消费方式提供存储的详细信息。为此,我们引入了两个新的API资源:PersistentVolume和PersistentVolumeClaim

  • PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。 PV是容量插件,如Volumes,但其生命周期独立于使用PV的任何单个pod。 此API对象捕获存储实现的详细信息,包括NFS,iSCSI或特定于云提供程序的存储系统。
  • PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。

  虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是PersistentVolumes对于不同的问题,用户通常需要具有不同属性(例如性能)。群集管理员需要能够提供各种PersistentVolumes不同的方式,而不仅仅是大小和访问模式,而不会让用户了解这些卷的实现方式。对于这些需求,有Storage Class 资源。  Storage Class为管理员提供了一种描述他们提供的存储的“类”的方法。 不同的类可能映射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。 Kubernetes本身对于什么类别代表是不言而喻的。 这个概念有时在其他存储系统中称为“配置文件”。   PVC和PV是一一对应的。

二、生命周期

PV是群集中的资源。PVC是对这些资源的请求,并且还充当对资源的检查。PV和PVC之间的相互作用遵循以下生命周期:Provisioning = > Binding =>Using=>Releasing=>Recycling 供应准备Provisioning---通过集群外的存储系统或者云平台来提供存储持久化支持。 - 静态提供Static:集群管理员创建多个PV。 它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费 - 动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于Storage Classes:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。 绑定Binding---用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。 使用Using---用户可在pod中像volume一样使用pvc。 释放Releasing---用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。 回收Recycling---pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。 - 保留策略:允许人工处理保留的数据。 - 删除策略:将删除pv和外部关联的存储资源,需要插件支持。 - 回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。 注:目前只有NFS和HostPath类型卷支持回收策略,AWS EBS,GCE PD,Azure Disk和Cinder支持删除(Delete)策略。

三、pv的主要类型

GCEPersistentDisk AWSElasticBlockStore AzureFile AzureDisk FC (Fibre Channel) Flexvolume Flocker NFS iSCSI RBD (Ceph Block Device) CephFS Cinder (OpenStack block storage) Glusterfs VsphereVolume Quobyte Volumes HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster) Portworx Volumes ScaleIO Volumes StorageOS

四、pv卷阶段状态

Available – 资源尚未被claim使用 Bound – 卷已经被绑定到claim了 Released – claim被删除,卷处于释放状态,但未被集群回收。 Failed – 卷自动回收失败

案例1

存储对象以nfs为例,所以需要先配置好nfs,部署及配置过程此处就省略了首先,nfs创建共享挂载目录

mkdir -p /data/volumes/v{1,2,3,4,5}

echo "

NFS stor 01

" > v1/index.htmlecho "


NFS stor 02

" > v2/index.htmlecho "


NFS stor 03

" > v3/index.htmlecho "


NFS stor 04

" > v4/index.htmlecho "


NFS stor 05

" > v5/index.html

exportfs -arv

showmount -e

Export list for zeng-dptool:
/data/k8sdata *
/data/volumes02 *
/data/volumes/v5 *
/data/volumes/v4 *
/data/volumes/v3 *
/data/volumes/v2 *
/data/volumes/v1 *
/data/k8s/prometheus

编写资源文件,创建5个pv,存储大小各不相同,是否可读也不相同

cat pv-damo.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00001
labels:
name: pv00001
spec:
nfs:
path: /data/volumes/v1
server: 192.168.1.77
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00002
labels:
name: pv00002
spec:
nfs:
path: /data/volumes/v2
server: 192.168.1.77
accessModes: ["ReadWriteOnce"]
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00003
labels:
name: pv00003
spec:
nfs:
path: /data/volumes/v3
server: 192.168.1.77
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 20Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00004
labels:
name: pv00004
spec:
nfs:
path: /data/volumes/v4
server: 192.168.1.77
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv00005
labels:
name: pv00005
spec:
nfs:
path: /data/volumes/v5
server: 192.168.1.77
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 15G

kubectl apply -f pv-damo.yaml

kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM                          STORAGECLASS          REASON   AGE
grafana-pv01 2Gi RWO Recycle Bound default/www-web-0 managed-nfs-storage 6d8h
pv00001 2Gi RWO,RWX Retain Available 3d23h
pv00002 5Gi RWO Retain Available 3d23h
pv00003 20Gi RWO,RWX Retain Available 3d23h
pv00004 10Gi RWO,RWX Retain Bound default/mypvc 3d23h
pv00005 15Gi RWO,RWX Retain Available 3d23

编写资源文件,创建一个pvc,需要6G存储;所以不会匹配pv00001、pv00002

cat vol-pvc-demo.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 6Gi
---
apiVersion: v1
kind: Pod
metadata:
name: vol-pvc
namespace: default
spec:
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html

查询验证:pvc已经绑定到pv00004上

kubectl get pvc

NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
grafana-pvc01 Bound pv004 10Gi RWO,RWX 6d6h
mypvc Bound pv00004 10Gi RWO,RWX 3d23

获取服务pod的IP地址

kubectl get pods -owide |grep vol-pvc

vol-pvc                                   1/1     Running     0          4d3h    192.168.124.73   zeng-k8s-nd03   <none>           <none

查看是否是确实使用了nfs共享出来的挂载目录,正常情况如下:

curl 192.168.124.73

<h1>NFS stor 04</h1
**********************上面讲的案例属于静态手动创建及配置pv/pvc的情况
**********************下面讲的案例属于动态自动创建及配置pv/pvc的情况

五、Storage Class

1、什么是Storage Class

Kubernetes提供了一套可以自动创建PV的机制,即:Dynamic Provisioning.而这个机制的核心在于:Storage Class这个API对象.Storage Class对象会定义下面两部分内容:

  • PV的属性.比如,存储类型,Volume的大小等.
  • 创建这种PV需要用到的存储插件

有了这两个信息之后,Kubernetes就能够根据用户提交的PVC,找到一个对应的Storage Class,之后Kubernetes就会调用该Storage Class声明的存储插件,进而创建出需要的PV.但是其实使用起来是一件很简单的事情,你只需要根据自己的需求,编写YAML文件即可,然后使用kubectl create命令执行即可 复制代码 2、为什么需要Storage Class 在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败.而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。 而且,不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:Storage Class,通过 Storage  Class 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 Storage Class 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。 3、Storage Class运行原理及部署流程 要使用 Storage Class,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner,这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。 Storage Class是通过存储分配器(provisioner)来动态分配PV的,但是Kubernetes官方内置的存储分配器并不支持NFS,所以需要额外安装NFS存储分配器。

  • 动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中
  • 而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上。

PV/PVC/Storage Class#yyds干货盘点#_pv

搭建Storage Class+NFS,大致有以下几个步骤:

  • 创建一个可用的NFS Serve
  • 创建Service Account.这是用来管控NFS provisioner在k8s集群中运行的权限
  • 创建Storage Class.负责建立PVC并调用NFS provisioner进行预定的工作,并让PV与PVC建立管理
  • 创建NFS provisioner.有两个功能,一个是在NFS共享目录下创建挂载点(volume),另一个则是建了PV并将PV与NFS的挂载点建立关联
案例1:
  • 环境:k8s v1.20.0
  • 部署

首先,创建一个文件夹,用了统一存放相关的资源文件mkdir -p /data/nfs-storage 创建rbac

cat rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default #根据实际环境设定namespace,下面类同
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.i

创建存储分配器(provisioner)

cat nfs-client-provisioner.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default #与RBAC文件中的namespace保持一致
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs #provisioner名称,请确保该名称与 nfs-StorageClass.yaml文件中的provisioner名称保持一致
- name: NFS_SERVER
value: 192.168.1.77 #nfs server地址
- name: NFS_PATH
value: /data/k8sdata #nfs server共享的挂载卷
volumes:
- name: nfs-client-root
nfs:
server: 192.168.1.77 #nfs server地址
path: /data/k8sdata #nfs server共享的挂载

创建存储类(storageclass)

cat storage-class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
archiveOnDelete: "false

创建pvc和storageclass绑定

cat pvc-for-storage-class.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-for-storageclass
spec:
accessModes:
- ReadWriteMany
storageClassName: managed-nfs-storage #和storage-class.yaml中的provisioner保持一致
resources:
requests:
storage: 500M

cd /data/nfs-storage && kubectl apply -f ./

kubectl get pods |grep prov

nfs-client-provisioner-7887874b64-5fnzt   1/1     Running   2          3h34

kubectl get sc

NAME                  PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage fuseim.pri/ifs Delete Immediate false 3h33m
[root@zeng-k8s-ms01 nfs-storage03]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
.....
pvc-0410ff17-5d36-42e9-8f5d-1c28e759a3c7 200Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 52m
pvc-f39e0e49-465e-46dc-af30-6e27eec1acc1 10Mi RWX Delete Bound default/test-claim managed-nfs-storage 3h33m
pvc-fdaa748b-f66d-4ca2-8540-9ddbaac0a936 500Mi RWX Delete Bound default/pvc-for-storageclass managed-nfs-storage 3h33m
....

kubectl get pvc

.....
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-for-storageclass Bound pvc-fdaa748b-f66d-4ca2-8540-9ddbaac0a936 500Mi RWX managed-nfs-storage 3h37m
www-web-0 Bound grafana-pv01 2Gi RWO managed-nfs-storage 53m
www-web-1 Bound pvc-0410ff17-5d36-42e9-8f5d-1c28e759a3c7 200Mi RWO managed-nfs-storage 53m
......

使用验证

方式一:直接调用storageclass

cat nginx-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: hub.c.163.com/library/nginx:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "managed-nfs-storage" #和storage-class.yaml中的provisioner保持一致
resources:
requests:
storage: 200M

创建服务:

kubectl apply -f nginx-statefulset.yaml

服务运行正常,说明调用storegeclass进行数据持久化ok

kubectl get pods

......
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-7887874b64-5fnzt 1/1 Running 2 3h44m
web-0 1/1 Running 0 57m
web-1 1/1 Running 0 57m
....
方式二:调用pvc,通过pvc间接调用storageclass

cat test-pod.yaml

kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1" #创建一个SUCCESS文件后退出
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: pvc-for-storageclass #与pvc-for-storage-class.yaml中的metadata.name名称保持一

验证结果:去对应文件里面去查看是否有“SUCCESS”这个文件,文件上级目录名称是按照${namespace}-${pvcName}-${pvName}规则创建的。

ls /data/k8sdata/default-pvc-for-storageclass-pvc-fdaa748b-f66d-4ca2-8540-9ddbaac0a936/

SUCCES

里面有一个 SUCCESS 的文件,证明我们上面的验证是成功

六、关于StorageClass回收策略对数据的影响

1、第一种配置

#archiveOnDelete: "false"  #这个配置暂时没发现有什么作用,后期继续探究
reclaimPolicy: Delete #默认没有配置,默认值为Delet

测试结果:

  • pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • 删除PVC后,PV被删除且NFS Server对应数据被删除

2、第二种配置

reclaimPolicy: Retain

测试结果:

  • pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • 删除PVC后,PV不会被删除,且状态由Bound变为Released,NFS Server对应数据被保留
  • 重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

3、第三种配置

#archiveOnDelete: "true"
reclaimPolicy: Retain

结果:

  • pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  • 删除PVC后,PV不会被删除,且状态由Bound变为Released,NFS Server对应数据被保留
  • 重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

七、默认storageclass设置及使用

1、storageclass默认设置

方式一:命令行(patch)

kubectl get sc

NAME                            PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage fuseim.pri/ifs Delete Immediate false 10

kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' #设置managed-nfs-storage为默认后端存储

再次查看sc,发现多了个default标识

kubectl get sc

NAME                            PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage (default) fuseim.pri/ifs Delete Immediate false 10

取消也很简单,将“true”改为“false”即可

kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' #取消默认存储后端

方式二:直接修改sc的资源文件,添加注解cat storage-class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
annotations:
"storageclass.kubernetes.io/is-default-class": "true" #添加此注释
provisioner: fuseim.pri/ifs
parameters:
#archiveOnDelete: "true"
reclaimPolicy: Retain #只有NFS 和hostPth支持两种回收策
2、默认storageclass使用

如果集群有一个默认的StorageClass能够满足我们的需求,那么剩下所有需要做的就是创建PersistentVolumeClaim(PVC),剩下的都有默认的动态配置搞定,包括无需去指定storageClassName例子1:

cat test-default-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: testpvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10M

kubectl apply -f test-default-pvc.yaml

kubectl get pvc |grep testpvc

testpvc                Bound    pvc-1462f59e-f3e4-43ec-8659-b6d8830ca51e   10Mi       RWO            managed-nfs-storage   151

正常情况可以看到,pv通过默认的storageclass被动态配置了

kubectl get pv |grep testpvc

pvc-1462f59e-f3e4-43ec-8659-b6d8830ca51e   10Mi       RWO            Delete           Bound         default/testpvc                managed-nfs-storage            153

八、其他

  • 如果没有默认的StorageClass对象,那么PersistentVolumeClaim对象(在不指定StorageClass情况下)将不会自动触发动态配置。
  • 如果一个卷是动态配置的卷,且默认的回收策略为“删除”,这意味着,在默认的情况下,当PVC被删除时,基础的PV和对应的存储也会被删除。如果需要保留存储在卷上的数据,则必须在PV被设置之后将回收策略从“Delete”更改为“Retain”。
  • 静态时,PVC与PV绑定时会根据storageClassName(存储类名称)和accessModes(访问模式)判断哪些PV符合绑定需求。然后再根据存储量大小判断,首先存PV储量必须大于或等于PVC声明量;其次就是PV存储量越接近PVC声明量,那么优先级就越高(PV量越小优先级越高)。
  • “使用中的存储对象保护” :该功能的目的是确保在Pod活动时使用的PersistentVolumeClaims (PVC)和绑定到PVC的PersistentVolume (PV)不会从系统中删除,因为这可能会导致数据丢失。

如果用户删除了Pod正在使用的PVC,则不会立即删除该PVC;PVC的清除被推迟,直到任何Pod不再主动使用PVC。另外,如果管理员删除绑定到PVC的PV,则不会立即删除该PV;PV的去除被推迟,直到PV不再与PVC结合。表现形式就是命令一直卡住不动,如下图:

PV/PVC/Storage Class#yyds干货盘点#_k8s_02

直到pvc(或pv)对应的pod不存在了或不活动了,命令才会结束。===========================================================

问题汇总:

1、错误一
  • 问题描述:通过storageclass自动创建pv不成功,pvc显示pending状态

kubectl get pvc

.........
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-for-storageclass Pending managed-nfs-storage 13s
test-claim Pending managed-nfs-storage 87

查看处于pending状态的pvc详情

kubectl describe pvc pvc-for-storageclass

.........
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ExternalProvisioning 4s (x4 over 39s) persistentvolume-controller waiting for a volume to be created, either by external provisioner "fuseim.pri/ifs" or manually created by system administrato
  • 问题原因:Kubernetes v1.20 (opens new window)开始,默认删除了 metadata.selfLink 字段,然而,部分应用仍然依赖于这个字段,例如 nfs-client-provisioner
  • 解决方案:

通过配置 apiserver 启动参数中的 –feature-gates 中的 RemoveSelfLink=false,可以重新启用 metadata.selfLink 字段。修改/etc/kubernetes/manifests/kube-apiserver.yaml 文件,添加添加- --feature-gates=RemoveSelfLink=false

grep -B 5 'feature-gates' /etc/kubernetes/manifests/kube-apiserver.yaml

- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
- --feature-gates=RemoveSelfLink=false #添加内

下面命令执行一次如果报错,就执行两次

kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml

kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml