Kubernetes Volume

环境准备

root@master30~ 16:45:06# kubectl create ns storage
namespace/storage created
root@master30~ 16:45:17# kubectl config set-context --current --namespace storage
Context "kubernetes-admin@kubernetes" modified.

一、Volume类型

Kubernetes支持Volume类型有:

root@master30~ 16:48:29# kubectl explain pod.spec.volumes | grep '^  [a-zA-Z]'
  awsElasticBlockStore  <AWSElasticBlockStoreVolumeSource>
  azureDisk     <AzureDiskVolumeSource>
  azureFile     <AzureFileVolumeSource>
  cephfs        <CephFSVolumeSource>
  cinder        <CinderVolumeSource>
  configMap     <ConfigMapVolumeSource>
  csi   <CSIVolumeSource>
  downwardAPI   <DownwardAPIVolumeSource>
  emptyDir      <EmptyDirVolumeSource>
  ephemeral     <EphemeralVolumeSource>
  fc    <FCVolumeSource>
  flexVolume    <FlexVolumeSource>
  flocker       <FlockerVolumeSource>
  gcePersistentDisk     <GCEPersistentDiskVolumeSource>
  gitRepo       <GitRepoVolumeSource>
  glusterfs     <GlusterfsVolumeSource>
  hostPath      <HostPathVolumeSource>
  iscsi <ISCSIVolumeSource>
  name  <string> -required-
  nfs   <NFSVolumeSource>
  persistentVolumeClaim <PersistentVolumeClaimVolumeSource>
  photonPersistentDisk  <PhotonPersistentDiskVolumeSource>
  portworxVolume        <PortworxVolumeSource>
  projected     <ProjectedVolumeSource>
  quobyte       <QuobyteVolumeSource>
  rbd   <RBDVolumeSource>
  scaleIO       <ScaleIOVolumeSource>
  secret        <SecretVolumeSource>
  storageos     <StorageOSVolumeSource>
  vsphereVolume <VsphereVirtualDiskVolumeSource>

最常用的类型为:

  • emptyDir:临时存储,Pod 内多容器共享缓存、日志、临时文件;Pod 删除数据直接清空。
  • hostPath:直接挂载宿主机目录 / 文件到容器。缺点:绑定节点,Pod 漂移数据丢失。
  • configMap / secret:配置类卷,放配置文件、环境变量、密码证书。
    • configMap:普通明文配置
    • secret:密码、密钥、证书加密存储
  • persistentVolumeClaim(PVC):标准持久化存储,搭配 PV/StorageClass,真正持久化数据,Pod 重建、漂移数据不丢。
  • nfs:传统共享网络存储,简单易用,多节点同时读写。

二、emptyDir

默认情况下,当Pod分配到Node上时,将会创建emptyDir,只要Node上的Pod一直运行,Volume就会一直存。当Pod(不管任何原因)从Node上被删除时,emptyDir也同时会删除,存储的数据也将永久删除。

示例:

root@master30~ 16:48:35# vim pod-with-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  volumes:
  - name: datavolume
    emptyDir: {}
  containers:
  - name: busybox1
    image: docker.io/library/busybox
    imagePullPolicy: IfNotPresent
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
    volumeMounts:
    - mountPath: /data
      name: datavolume
  - name: busybox2
    image: docker.io/library/busybox
    imagePullPolicy: IfNotPresent
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
    volumeMounts:
    - mountPath: /data
      name: datavolume  

配置说明:

  • volumeMounts.name绑定关联 spec.volumes里定义的卷
  • volumeMounts.mountPath为容器内挂载点
  • spec.volumes:定义卷,是默认卷类型。
  • spec.containers.volumeMounts:引用卷
#创建Pod
root@master30~ 17:06:48# kubectl apply -f pod-with-emptyDir.yaml
pod/busybox created

#获取容器ID
root@master30~ 17:07:05# kubectl describe pod busybox | grep 'Container ID'
    Container ID:  containerd://0f0b94abaf6146a2c753c57e8336ad86d5c8bd595a3aaa23a9ae4c11fee1673f
    Container ID:  containerd://47a4afa3c123fc1d780e1a03f197316ce5a366b97cd2c573533894c4d1987416

#查看容器所在节点
root@master30~ 17:07:29# kubectl describe pod busybox | grep Node
Node:             worker32.hgq.cloud/10.1.8.32
Node-Selectors:              <none>

#在worker32上查看容器挂载情况
root@worker32~ 17:10:04# crictl inspect 47 | grep datavolume
        "hostPath": "/var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume",
          "host_path": "/var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume"
          "source": "/var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume",

#创建并查看数据
root@master30~ 17:08:51# kubectl exec busybox -c busybox1 -- touch /data/test1
root@master30~ 17:12:20# kubectl exec busybox -c busybox2 -- ls /data
test1

#在worker32节点上查看数据
root@worker32~ 17:10:19# ls /var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume
test1

#删除Pod,验证emptyDir
root@master30~ 17:12:34# kubectl delete pod busybox
pod "busybox" deleted
root@worker32~ 18:42:34# ls /var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume
ls: cannot access '/var/lib/kubelet/pods/d5896fbe-d5f5-4d45-a251-678c10b911a7/volumes/kubernetes.io~empty-dir/datavolume': No such file or directory

三、hostPath

hostPath允许Pod将Node的文件系统中某个目录挂载到Pod内部。pod删除后,hostPath卷数据保留。

示例:

root@master30~ 18:45:44# vim pod-with-hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  labels:
    app: busybox
spec:
  volumes:
  - name: datavolume
    hostPath:
      path: /busyboxdir
  containers:
  - name: busybox1
    image: docker.io/library/busybox
    imagePullPolicy: IfNotPresent
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
    volumeMounts:
    - mountPath: /data
      name: datavolume
  - name: busybox2
    image: docker.io/library/busybox
    imagePullPolicy: IfNotPresent
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
    volumeMounts:
    - mountPath: /data
      name: datavolume
      # 设置readOnly,控制读写,默认值是false,也就是读写访问。
      readOnly: true
#创建Pod
root@master30~ 18:47:11# kubectl apply -f pod-with-hostPath.yaml
pod/busybox created

#查看容器ID
root@master30~ 18:47:25# kubectl describe pod busybox | grep 'Container ID'
    Container ID:  containerd://f8f2d8a1c55ac9504758ca70dc5776b3a63b2238f6a8bce34d9bca8811c9eaf3
    Container ID:  containerd://373e1bfdfbdad56f1c621d2d9567b8d3c48f0a3f3ea71a54fc80a60832696830

#获取容器所在节点
root@master30~ 18:47:55# kubectl describe pod busybox | grep Node
Node:             worker31.hgq.cloud/10.1.8.31
Node-Selectors:              <none>

#查看挂载详情
root@worker31~ 18:49:18# crictl inspect 37 | grep /data
        "containerPath": "/data",
          "container_path": "/data",
          "destination": "/data",
root@worker31~ 18:49:44# crictl inspect 37 | grep busyboxdir
        "hostPath": "/busyboxdir",
          "host_path": "/busyboxdir",
          "source": "/busyboxdir",

#创建数据
root@master30~ 18:48:31# kubectl exec busybox -c busybox1 -- touch /data/test1
root@master30~ 18:54:23# kubectl exec busybox -c busybox2 -- ls /data
test1
root@master30~ 18:54:38# kubectl exec busybox -c busybox2 -- touch /data/test2
touch: /data/test2: Read-only file system
command terminated with exit code 1
#因为busybox2被设置为只读

#在worker31上查看数据
root@worker31~ 18:49:53# ls /busyboxdir/
test1

#pod删除后,hostPath卷数据保留
root@master30~ 18:54:58# kubectl delete pod busybox
pod "busybox" deleted
root@worker31~ 18:56:19# ls /busyboxdir/
test1

四、NFS存储

NFS卷,将数据存储在NFS共享中。

准备NFS共享

#安装NFS server
root@worker30 18:58:04# apt install -y nfs-kernel-server

#创建NFS目录,修改文件夹权限
root@master30~ 19:06:45# mkdir -m 777 /nfsshares
root@master30~ 19:06:53# echo hello world > /nfsshares/index.html

#配置共享,允许所有客户端访问
root@master30~ 19:07:07# cat << EOF > /etc/exports
> /nfsshares/ *(rw)
> EOF

#重启服务
root@master30~ 19:07:40# systemctl restart nfs-server.service

#客户端安装
root@worker31~ 19:05:56# apt install -y nfs-kernel-server
root@worker32~ 18:44:04# apt install -y nfs-kernel-server

准备Pod

root@master30~ 19:08:00# vim pod-with-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  volumes:
  - name: nfs
    nfs:
      server: 10.1.8.30
      path: "/nfsshares"
  containers:
  - image: docker.io/library/nginx
    name: nginx
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: nfs
      mountPath: "/usr/share/nginx/html"
#创建Pod
root@master30~ 19:09:22# kubectl apply -f pod-with-nfs.yaml
pod/nginx created

#查看容器ID与所在节点
root@master30~ 19:12:46# kubectl describe pod nginx | grep 'Container ID'
    Container ID:   containerd://ba054b61f4d4a044d9c4b5abe38201c26c7410ef4cf8c28b24ad0dd220815fe0
root@master30~ 19:15:29# kubectl describe pod nginx | grep Node
Node:             worker32.hgq.cloud/10.1.8.32
Node-Selectors:              <none>

#查看挂载详情
root@worker32~ 19:18:01# crictl inspect ba | grep -e nfs -e html
        "containerPath": "/usr/share/nginx/html",
        "hostPath": "/var/lib/kubelet/pods/7fdcab39-2c3c-4e54-8a32-43f805e77cd5/volumes/kubernetes.io~nfs/nfs",
          "container_path": "/usr/share/nginx/html",
          "host_path": "/var/lib/kubelet/pods/7fdcab39-2c3c-4e54-8a32-43f805e77cd5/volumes/kubernetes.io~nfs/nfs"
          "destination": "/usr/share/nginx/html",
          "source": "/var/lib/kubelet/pods/7fdcab39-2c3c-4e54-8a32-43f805e77cd5/volumes/kubernetes.io~nfs/nfs",
          
#访问容器
root@master30~ 19:15:44# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP              NODE                 NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          6m32s   10.224.96.217   worker32.hgq.cloud   <none>           <none>
root@master30~ 19:18:53# curl 10.224.96.217
hello world

#创建数据
root@master30~ 19:19:51# kubectl exec nginx -- touch /usr/share/nginx/html/test.html
root@master30~ 19:20:28# ls /nfsshares/
index.html  test.html

#Pod删除后,nfs数据卷保留
pod "nginx" deleted
root@master30~ 19:21:26# ls /nfsshares/
index.html  test.html

五、持久性存储

Kubernetes 使用 persistent volume(PV)架构为集群提供永久存储。

PV 和 PVC 架构

开发人员不知道特定云环境的细节的情况下,只需要使用persistentVolumeClaim(PVC)请求PV资源,实现持久化存储。

  • Persistent Volume,由PersistentVolume API对象定义,代表集群中现有存储。PV的生命周期与使用其的pod无关。Persistent Volume 是集群级别资源。

  • Persistent Volume Claim,由PersistentVolumeClaim API对象定义,代表开发人员请求PV。Persistent Volume Claim 是 namespace 级别资源。

创建 PV 和 PVC

创建 PV

集群管理员可以创建任意数量PV,取决于后端存储,这里采用NFS后端存储。

示例:

root@master30~ 19:23:29# vim pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /nfsshares
    server: 10.1.8.30
root@master30~ 19:23:54# kubectl apply -f pv.yaml
persistentvolume/web created
root@master30~ 19:33:00# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
web    5Gi        RWO            Retain           Available                          <unset>                          7s
创建 PVC

用户创建PVC,pod使用PVC申请特定容量、特定modes和特定存储类别的存储。master监控PVCs,查找匹配的PV或者等待后端存储创建相应PV,然后绑定PV和PVC。

示例:

root@master30~ 19:33:07# vim pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: webclaim
spec:
  accessModes:
    - ReadWriteOnce 
  resources:
    requests:
      storage: 5Gi
root@master30~ 19:34:13# kubectl apply -f pvc.yaml
persistentvolumeclaim/webclaim created
root@master30~ 19:34:59# kubectl get pvc
NAME       STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
webclaim   Bound    web      5Gi        RWO                           <unset>                 4s
创建Pod使用PV
root@master30~ 19:41:41# vim pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    name: web
spec:
  containers:
    - image: docker.io/library/nginx
      name: web
      ports:
        - containerPort: 80
          name: web-port
      volumeMounts:
        - name: web-persistent-storage
          mountPath: /usr/share/nginx/html
  volumes:
    - name: web-persistent-storage
      persistentVolumeClaim:
        claimName: webclaim
root@master30~ 19:44:36# kubectl apply -f pod-with-pvc.yaml
pod/web created

#测试
root@master30~ 19:47:47# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE                 NOMINATED NODE   READINESS GATES
web    1/1     Running   0          49s   10.224.128.13   worker31.hgq.cloud   <none>           <none>
root@master30~ 19:47:55# curl 10.224.128.13
hello world

PV accessModes

PersistentVolume 卷访问模式有:

  • ReadWriteOnce,卷可以被一个节点以读写方式挂载,也允许同一节点上的多个 Pod 读写访问卷。

  • ReadOnlyMany,卷可以被多个节点以只读方式挂载。

  • ReadWriteMany,卷可以被多个节点以读写方式挂载。

  • ReadWriteOncePod,卷可以被单个 Pod 以读写方式挂载

在命令行接口(CLI)中,访问模式也使用以下缩写形式:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany
  • RWOP - ReadWriteOncePod

不同存储后端支持不同的模式:

卷插件 ReadWriteOnce ReadOnlyMany ReadWriteMany ReadWriteOncePod
AzureFile -
CephFS -
CSI 取决于驱动 取决于驱动 取决于驱动 取决于驱动
FC - -
FlexVolume 取决于驱动 -
GCEPersistentDisk - -
Glusterfs -
HostPath - - -
iSCSI - -
NFS -
RBD - -
VsphereVolume - -(Pod 运行于同一节点上时可行) -
PortworxVolume - -

重要: 每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持多种访问模式。 例如,一个 GCEPersistentDisk 卷可以被某节点以 ReadWriteOnce 模式挂载,或者被多个节点以 ReadOnlyMany 模式挂载,但不可以同时以两种模式挂载。

常见的NAS存储(Network Attached Storage,网络附加存储)都支持三种存储模式:ReadWriteOnce、ReadOnlyMany、ReadWriteMany。

PV volumeModes

特性状态: Kubernetes v1.18 [stable]

针对 PV 持久卷,Kubernetes 支持两种卷模式(volumeModes):Filesystem(文件系统)Block(块)volumeMode 是一个可选的 API 参数。 如果该参数被省略,默认的卷模式是 Filesystem

  • Filesystem 卷,会被 Pod 挂载(Mount) 到某个目录。 如果卷的存储来自某块设备而该设备目前为空,Kuberneretes 会在第一次挂载卷之前在设备上创建文件系统。

  • Block 卷,会被作为原始块设备来使用。 这类卷以块设备的方式交给 Pod 使用,其上没有任何文件系统。 这种模式对于为 Pod 提供一种使用最快可能方式来访问卷而言很有帮助, Pod 和卷之间不存在文件系统层。另外,Pod 中运行的应用必须知道如何处理原始块设备。

PV 回收策略

当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除, 从而允许该资源被回收再利用。PersistentVolume 对象的回收策略告诉集群, 当其被从申领中释放时如何处理该数据卷。

PersistentVolume 回收策略支持:Retain(保留)、Recycle(回收)、Delete(删除)。

Retain(保留)

回收策略 Retain 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然保留上一次关联的pvc信息,清理掉上一次关联的pvc信息才可分配给其他pvc。Retain(保留)策略是默认策略

示例:

root@master30~ 19:40:17# vim pv-pvc-Retain.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
spec:
  capacity:
    storage: 5Gi
  #persistentVolumeReclaimPolicy: Retain
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /nfsshares
    server: 10.1.8.30
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: webclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
root@master30~ 19:48:51# kubectl apply -f pv-pvc-Retain.yaml
root@master30~ 19:51:07# kubectl get pv,pvc
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/web   5Gi        RWO            Retain           Bound    storage/webclaim                  <unset>                          18m

NAME                             STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/webclaim   Bound    web      5Gi        RWO                           <unset>                 16m

#删除PVC验证
root@master30~ 19:51:10# kubectl delete pvc webclaim
persistentvolumeclaim "webclaim" deleted
root@master30~ 19:52:25# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
web    5Gi        RWO            Retain           Released   storage/webclaim                  <unset>                          19m

#重新创建PVC也无法绑定,webclaim的状态为pending
root@master30~ 19:52:33# kubectl apply -f pvc.yaml
persistentvolumeclaim/webclaim created
root@master30~ 19:53:19# kubectl get pv,pvc
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/web   5Gi        RWO            Retain           Released   storage/webclaim                  <unset>                          20m

NAME                             STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/webclaim   Pending                                                     <unset>                 17s

#清除claimRef部分
root@master30~ 20:01:44# kubectl edit pv web
.......
 # 删除claimRef部分
  # claimRef:
  #   apiVersion: v1
  #   kind: PersistentVolumeClaim
  #   name: webclaim
  #   namespace: test
  #   resourceVersion: "112157"
  #   uid: 0839cdc1-81bb-11e9-9ca8-52540000fa0a
  # 删除claimRef部分
......

#此时二者已经绑定成功了
root@master30~ 20:03:13# kubectl get pv,pvc
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/web   5Gi        RWO            Retain           Bound    storage/webclaim                  <unset>                          36m

NAME                             STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/webclaim   Bound    web      5Gi        RWO                           <unset>                 8m6s
Recycle(回收)

**警告:**回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。

Recycle(回收)策略的效果与 Retaine(保留)一致。

Delete(删除)

对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS 或 GCE PD 卷)中移除所关联的存储资产。

示例:

root@master30~ 20:12:28# vim pv-pvc-delete.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web
spec:
  capacity:
    storage: 5Gi
  persistentVolumeReclaimPolicy: Delete
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /nfsshares
    server: 10.1.8.30
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: webclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
root@master30~ 20:14:46# kubectl apply -f pv-pvc-delete.yaml
persistentvolume/web created
persistentvolumeclaim/webclaim created
root@master30~ 20:15:01# kubectl get pv,pvc
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/web   5Gi        RWO            Delete           Bound    storage/webclaim                  <unset>                          15s

NAME                             STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/webclaim   Bound    web      5Gi        RWO                           <unset>                 15s

#删除pvc验证
root@master30~ 20:15:16# kubectl delete pvc webclaim

#查看pv,pv自动变成delete
persistentvolumeclaim "webclaim" deleted
root@master30~ 20:15:47# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
web    5Gi        RWO            Delete           Failed   storage/webclaim                  <unset>                          59s

root@master30~ 20:16:00# kubectl describe pv web
Name:            web
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:
Status:          Failed
Claim:           storage/webclaim
Reclaim Policy:  Delete
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        5Gi
Node Affinity:   <none>
Message:         error getting deleter volume plugin for volume "web": no deletable volume plugin matched
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    10.1.8.30
    Path:      /nfsshares
    ReadOnly:  false
Events:
  Type     Reason              Age   From                         Message
  ----     ------              ----  ----                         -------
  Warning  VolumeFailedDelete  90s   persistentvolume-controller  error getting deleter volume plugin for volume "web": no deletable volume plugin matched
####由于NFS原生不支持自动删除后端存储,K8s找不到对应的删除插件,于是PV卡死在 Failed 状态,此时可以手动删除pv,手动删除pv,并不会删除后端存储数据。

PV卷的类型

通过指定PV卷的类型说明PV使用何种类型的后端存储。

PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件:

  • csi - 容器存储接口 (CSI)
  • fc - Fibre Channel (FC) 存储
  • hostPath - HostPath 卷 (仅供单节点测试使用;不适用于多节点集群;请尝试使用 local 卷作为替代)
  • iscsi - iSCSI (SCSI over IP) 存储
  • local - 节点上挂载的本地存储设备
  • nfs - 网络文件系统 (NFS) 存储

以下的持久卷已被弃用。这意味着当前仍是支持的,但是 Kubernetes 将来的发行版会将其移除。

  • azureFile - Azure File (于 v1.21 弃用
  • flexVolume - FlexVolume (于 v1.23 弃用
  • gcePersistentDisk - GCE Persistent Disk (于 v1.17 弃用
  • portworxVolume - Portworx 卷 (于 v1.25 弃用
  • vsphereVolume - vSphere VMDK 卷 (于 v1.19 弃用
  • cephfs - CephFS 卷 (于 v1.28 弃用
  • rbd - Rados Block Device (RBD) 卷 (于 v1.28 弃用

旧版本的 Kubernetes 仍支持这些“树内(In-Tree)”持久卷类型:

  • awsElasticBlockStore - AWS Elastic Block Store (EBS) (v1.27 开始不可用
  • azureDisk - Azure Disk (v1.27 开始不可用
  • cinder - Cinder (OpenStack block storage) (v1.27 开始不可用
  • photonPersistentDisk - Photon 控制器持久化盘。(从 v1.15 版本开始将不可用
  • scaleIO - ScaleIO 卷(v1.21 之后不可用
  • flocker - Flocker 存储 (v1.25 之后不可用
  • quobyte - Quobyte 卷 (v1.25 之后不可用
  • storageos - StorageOS 卷 (v1.25 之后不可用

原始块卷支持

特性状态: Kubernetes v1.18 [stable]

以下卷插件支持原始块卷,包括其动态制备(如果支持的话)的卷:

  • CSI
  • FC(光纤通道)
  • GCEPersistentDisk(已弃用)
  • iSCSI
  • Local 卷
  • OpenStack Cinder
  • RBD(已弃用)
  • RBD(Ceph 块设备,已弃用)
  • VsphereVolume

PV示例

apiVersion: v1
kind: PersistentVolume
metadata:
  name: block-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  persistentVolumeReclaimPolicy: Retain
  fc:
    targetWWNs: ["50060e801049cfd1"]
    lun: 0
    readOnly: false

PVC示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  resources:
    requests:
      storage: 10Gi

Pod示例

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-block-volume
spec:
  containers:
    - name: fc-container
      image: fedora:26
      command: ["/bin/sh", "-c"]
      args: [ "tail -f /dev/null" ]
      volumeDevices:
        - name: data
          devicePath: /dev/xvda
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: block-pvc

**说明:**向 Pod 中添加原始块设备时,你要在容器内设置设备路径而不是挂载路径。

注意:

两条互斥路线

路线 1:裸块设备 Block

PV:volumeMode: Block

PVC:volumeMode: Block

Pod:volumeDevices: [{name: xx, devicePath: /dev/xxx}]

路线 2:文件系统 Filesystem(默认)

PV:省略 volumeMode / volumeMode: Filesystem

PVC:省略 volumeMode / volumeMode: Filesystem

Pod:volumeMounts: [{name: xx, mountPath: /xxx}]

六、PV 与 PVC 绑定匹配完整规则

核心匹配优先级总序(最高→最低)

  1. claimRef 强制锁定校验(最高优先级,独立分支,跳过常规匹配)
  2. StorageClass 存储类严格匹配
  3. PV 访问模式兼容匹配
  4. 存储容量满足 PVC 最低申请要求
  5. 同规则下最优 PV 筛选

前置说明

两种绑定分支互斥:

  • 分支 A:PV 配置了spec.claimRef → 走强制绑定逻辑,不执行下方常规匹配流程;
  • 分支 B:PV 无claimRef → 依次执行 StorageClass、访问模式、容量等常规匹配规则。

一、分支 A:claimRef 强制一对一绑定(最高优先级)

只要 PV 定义claimRef,该 PV 脱离通用匹配池,仅服务指定 PVC,所有常规匹配规则失效。

1. 匹配细则
  1. PV 内claimRef包含两个必填标识:name(PVC 名称)、namespace(PVC 所在命名空间);
  2. 只有同名、同命名空间的 PVC 才能绑定该 PV;
  3. 其他 PVC 哪怕 StorageClass、访问模式、容量全部吻合,也无法占用此 PV;
  4. 强制绑定仍需满足基础硬条件(否则一直 Pending):
    • PV 容量 ≥ PVC 申请容量
    • PV 访问模式兼容 PVC 申请模式
    • PV 状态为Available空闲
  5. 一旦绑定成功,PV 状态变为Bound,永久锁定该 PVC,不可被其他 PVC 抢占。
2. claimRef 示例 PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-fixed
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: local-fast
  # 强制绑定default命名空间下名为web-pvc的PVC
  claimRef:
    name: web-pvc
    namespace: default
  local:
    path: /mnt/local-disk
3. 特殊场景约束
  • PVC 无法反向指定绑定 PV,仅能在 PV 侧配置claimRef锁定 PVC;
  • 若指定的 PVC 不存在,该 PV 会持续保持Available等待,不会参与其他 PVC 匹配;
  • 若对应 PVC 删除,PV 根据自身persistentVolumeReclaimPolicy决定回收逻辑(Retain/Delete/Recycle)。

二、分支 B:无 claimRef,常规自动匹配完整规则

规则一:StorageClass 存储类严格匹配(常规匹配第一道门槛)

StorageClass 是 PV、PVC 的分类标识,用于区分不同存储类型、不同存储介质、不同命名空间的存储资源,是第一道匹配门槛,规则为强一致匹配,不兼容则直接无法绑定。

  1. 有存储类场景:若 PV 配置了 storageClassName,PVC 必须配置完全一致字符串(大小写敏感)才可匹配;
  2. 无存储类场景:PV 未配置 storageClassName(空),PVC 也不能配置该字段,双方均为空才能匹配;
  3. 禁止交叉匹配:带 SC 的 PV 只能匹配同 SC 的 PVC,无 SC PV 只能匹配无 SC PVC。
规则二:PV 访问模式兼容匹配(次优先级)

访问模式权限由低到高:ROX < RWO < RWX,高权限 PV 兼容低权限 PVC 需求,低权限无法兼容高权限申请。

  1. PVC 申请 ROX:ROX/RWO/RWX 所有 PV 均可匹配;
  2. PVC 申请 RWO:仅 RWO、RWX PV 可匹配,ROX 不满足;
  3. PVC 申请 RWX:仅 RWX PV 可匹配,ROX/RWO 权限不足。
规则三:存储容量满足 PVC 最低申请要求
  1. 仅保留通过前两层校验、容量≥PVC 申请容量的空闲 PV;
  2. 同分组(同 SC、同访问模式)PV 按容量升序排序;
  3. 最优分配策略:选择满足容量的最小 PV绑定,避免大容量存储浪费。

三、全局通用补充约束规则(同时适用于 claimRef 强制绑定、常规自动匹配)

  1. PV 状态约束:仅Available空闲 PV 可参与绑定;Bound、Released、Failed 状态 PV 直接排除;
  2. 一对一绑定约束:同一 PV 同一时间只能绑定一个 PVC,绑定后状态变为 Bound,不再参与任何匹配;
  3. 核心参数不可变更:绑定后 PV/PVC 的 StorageClass、accessModes、申请容量无法修改,变更需重建资源;
  4. volumeMode 约束:PV 与 PVC 的volumeMode(Filesystem/Block)必须完全一致,否则匹配失败;
  5. 回收策略不影响绑定逻辑:persistentVolumeReclaimPolicy仅控制 PVC 删除后 PV 处理方式,不参与匹配校验。

四、完整绑定流程总览

流程 1:先判断 PV 是否存在 claimRef
  1. PV 存在claimRef

    ① 校验集群内是否存在同 namespace、同名 PVC;

    ② 校验 PV 容量、访问模式、volumeMode 是否满足该 PVC 需求;

    ③ 全部满足则直接绑定,跳过所有常规 SC / 容量筛选逻辑;

    ④ 不满足则 PV 持续等待,不参与其他 PVC 匹配。

流程 2:PV 无 claimRef,执行自动匹配流水线
  1. 过滤出 StorageClass 完全一致、状态 Available 的 PV 集合;
  2. 集合内过滤出访问模式权限≥PVC 申请模式、volumeMode 一致的 PV;
  3. 保留其中存储容量≥PVC 申请容量的 PV;
  4. 剩余 PV 按容量从小到大排序,选取最小容量 PV 完成绑定。

五、典型场景区分举例

  1. 静态 PV 配置 claimRef:运维提前固定存储给指定业务 PVC,隔离资源,防止其他应用抢占;
  2. 无 claimRef 静态 PV 池:多业务共享同类型存储,k8s 自动调度分配;
  3. StorageClass 动态供给:动态创建 PV,自动匹配逻辑与无 claimRef 静态 PV 完全一致,不会生成带 claimRef 的 PV。

Kubernetes Configure

环境准备:

root@master30~ 13:21:35# kubectl create ns configure
root@master30~ 13:21:48# kubectl config set-context --current --namespace configure

ConfigMap

ConfigMap 介绍

ConfigMap 是一个存储其他对象所需要使用的配置的 API 对象。 ConfigMap 使用 databinaryData 字段,保存键值对数据。

  • data 字段,用来保存 UTF-8 字符串
  • binaryData 字段,用来保存二进制数据(base64 编码的字串)

ConfigMap 的名字必须是一个合法的 DNS 子域名databinaryData 字段下面的每个键的名称都必须由字母、数字或者 -_. 组成。在 data 下保存的键名不可以与在 binaryData 下出现的键名有重叠。

ConfigMap 使用建议

  • 使用 ConfigMap 存储配置数据,不存数据 / 文件,上限 = 1 MiB。

  • **ConfigMap 在设计上不是用来保存大量数据的。**如果你需要保存大量数据,考虑使用挂载存储卷或者使用独立的数据库或者文件服务。

ConfigMap 创建

使用帮助

root@master30~/configure 13:27:51# kubectl create configmap --help | tail -4
Usage:
  kubectl create configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none] [options]

Use "kubectl options" for a list of global command-line options (applies to all commands).
键值对类型

--from-literal=键名=值

root@master30~/configure 13:28:14# kubectl create configmap mysql --from-literal=password=redhat
root@master30~/configure 13:29:51# kubectl get cm mysql -o yaml | grep ^data -A1
data:
  password: redhat
文件类型

--from-file 表示从本地文件导入内容生成 ConfigMap,文件名为 key,文件里全部文本为 value

root@master30~/configure 13:33:44# kubectl create configmap web1 --from-file=index.html
configmap/web1 created
root@master30~/configure 13:33:56# kubectl get cm web1 -o yaml | grep ^data -A2
data:
  index.html: |
    hello world
目录类型
root@master30~/configure 13:37:09# kubectl create configmap web2 --from-file=web2/
configmap/web2 created
root@master30~/configure 13:37:43# kubectl get cm web2 -o yaml | grep ^data -A4
data:
  error.html: |
    error
  index.html: |
    hello world

ConfigMap 引用

环境变量方式引用

注意:环境变量属于特定容器级别

root@master30~/configure 13:38:04# vim pod-cm-env.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  containers:
  - image: hub.laoma.cloud/library/mysql:latest
    imagePullPolicy: IfNotPresent
    name: mysql
    ports:
    - containerPort: 3306
      name: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      # 原先通过value设置环境变量值
      # valuefrom代表不从固定 value 写死值,而是从外部资源读取变量值
      valueFrom:
        #configMapRef表示指定数据源是ConfigMap
        configMapKeyRef:
          #name: mysql,引用的ConfigMap资源名称
          name: mysql
          #key: password,取该ConfigMapdata.password对应的value作为环境变量的值。
          key: password
          

验证:

root@master30~/configure 14:45:43# kubectl exec mysql -- bash -c 'echo $MYSQL_ROOT_PASSWORD'
redhat
root@master30~/configure 14:45:49# kubectl exec -it mysql -- bash -c 'mysql -uroot -predhat'
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 9.6.0 MySQL Community Server - GPL

Copyright (c) 2000, 2026, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

或者
root@master30~/configure 14:47:02# apt install -y mysql-client
root@master30~/configure 14:49:46# kubectl get pods -o wide | awk '{print$6}'
IP
10.224.96.233
root@master30~/configure 14:48:26# mysql -uroot -predhat -h 10.224.96.233
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 9.6.0 MySQL Community Server - GPL

Copyright (c) 2000, 2026, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
以volume方式引用

**注意:**volumes属于pod级别,通过volumeMounts挂载。

引用整体
root@master30~/configure 14:52:59# vim pod-cm-volume-all.yaml
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web:
  name: web
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    name: web
    volumeMounts:
    - name: webcontent
      # mountPath值是一个挂载点,将configuremap中所有键值对挂载过来
      mountPath: "/usr/share/nginx/html"
  volumes:
  - name: webcontent
    configMap:
      name: web2

验证:

root@master30~/configure 14:54:27# kubectl apply -f pod-cm-volume-all.yaml
root@master30~/configure 14:58:05# kubectl exec web -- ls /usr/share/nginx/html
error.html
index.html
root@master30~/configure 15:00:44# kubectl get pods -o wide | awk '{print$6}'
IP
10.224.128.32
root@master30~/configure 15:00:49# curl 10.224.128.32
hello world
root@master30~/configure 15:01:20# curl 10.224.128.32/error.html
error
引用特定key
root@master30~/configure 15:04:20# vim pod-cm-volume-single.yaml
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web:
  name: web
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    name: web
    volumeMounts:
    - name: webcontent
      # mountPath值是一个挂载点,将configuremap中所有键值对挂载过来
      mountPath: "/usr/share/nginx/html"
  volumes:
  - name: webcontent
    configMap:
      name: web2

验证:

root@master30~/configure 15:10:43# kubectl apply -f pod-cm-volume-single.yaml
pod/web created
root@master30~/configure 15:10:45# kubectl get pods -o wide | awk '{print$6}'
IP
10.224.128.33
root@master30~/configure 15:11:04# curl 10.224.128.33
hello world
root@master30~/configure 15:11:12# kubectl exec web -- ls /usr/share/nginx/html
index.html

🌟当卷中使用的 ConfigMap 被更新时,所投射的键最终也会被更新。 kubelet 组件会在每次周期性同步时检查所挂载的 ConfigMap 是否为最新。

⭐️ConfigMap 是 Kubernetes 集群用于存放非敏感明文内容的统一配置仓库,既支持存储端口、地址、功能开关这类单行键值参数,也支持完整配置文件、静态网页、脚本等多行文本;集群内 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob 等各类工作负载,以及初始化容器 initContainer、集群系统组件(CoreDNS、kube-proxy 等)均可使用,能够通过环境变量注入或数据卷挂载两种方式为容器交付配置,彻底实现配置与业务镜像解耦,支持多服务共享同一份配置、在线修改配置无需重新打包推送镜像,便于全集群统一管控、批量运维。

k8s中的ConfigMap

以kube-proxy为例

root@master30~/web 16:28:33# kubectl get cm -n kube-system
NAME                                                   DATA   AGE
calico-config                                          4      3d23h
coredns                                                1      3d23h
extension-apiserver-authentication                     6      3d23h
kube-apiserver-legacy-service-account-token-tracking   1      3d23h
kube-proxy                                             2      3d23h
kube-root-ca.crt                                       1      3d23h
kubeadm-config                                         1      3d23h
kubelet-config                                         1      3d23h
#列属性DATA表示当前这个ConfigMap内部存储了多少组 key-value 数据。

#查看kube-proxy的yaml文件
root@master30~/web 16:32:35# kubectl get configmaps kube-proxy -n kube-system  -o yaml
apiVersion: v1
data:
  config.conf: |-
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    bindAddress: 0.0.0.0
    bindAddressHardFail: false
    clientConnection:
      acceptContentTypes: ""
      burst: 0
      contentType: ""
      kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
      qps: 0
    clusterCIDR: 10.224.0.0/16
    configSyncPeriod: 0s
    conntrack:
      maxPerCore: null
      min: null
      tcpBeLiberal: false
      tcpCloseWaitTimeout: null
      tcpEstablishedTimeout: null
      udpStreamTimeout: 0s
      udpTimeout: 0s
    detectLocal:
      bridgeInterface: ""
      interfaceNamePrefix: ""
    detectLocalMode: ""
    enableProfiling: false
    healthzBindAddress: ""
    hostnameOverride: ""
    iptables:
      localhostNodePorts: null
      masqueradeAll: false
      masqueradeBit: null
      minSyncPeriod: 0s
      syncPeriod: 0s
    ipvs:
      excludeCIDRs: null
      minSyncPeriod: 0s
      scheduler: ""
      strictARP: false
      syncPeriod: 0s
      tcpFinTimeout: 0s
      tcpTimeout: 0s
      udpTimeout: 0s
    kind: KubeProxyConfiguration
    logging:
      flushFrequency: 0
      options:
        json:
          infoBufferSize: "0"
        text:
          infoBufferSize: "0"
      verbosity: 0
    metricsBindAddress: ""
    ######这里的mode值为空#########
    mode: ""
    nftables:
      masqueradeAll: false
      masqueradeBit: null
      minSyncPeriod: 0s
      syncPeriod: 0s
    nodePortAddresses: null
    oomScoreAdj: null
    portRange: ""
    showHiddenMetricsForVersion: ""
    winkernel:
      enableDSR: false
      forwardHealthCheckVip: false
      networkName: ""
      rootHnsEndpointName: ""
      sourceVip: ""
  kubeconfig.conf: |-
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        server: https://10.1.8.30:6443
      name: default
    contexts:
    - context:
        cluster: default
        namespace: default
        user: default
      name: default
    current-context: default
    users:
    - name: default
      user:
        tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
kind: ConfigMap
metadata:
  annotations:
    kubeadm.kubernetes.io/component-config.hash: sha256:7575011b7a20fcf5b8c31230aa579b3bb2eb35c4bfd10454001184112165b9fa
  creationTimestamp: "2026-06-23T09:05:07Z"
  labels:
    app: kube-proxy
  name: kube-proxy
  namespace: kube-system
  resourceVersion: "263"
  uid: 8c603ca7-f3de-43e2-b27d-84e48156a94a

# kube-proxy有两种工作模式ipvs和iptables
# 这里 mode 值为空,查看日志发现默认使用iptables。
root@master30~/web 16:35:21# kubectl logs -n kube-system kube-proxy-xzxkq
I0627 02:02:44.058959       1 server_linux.go:69] "Using iptables proxy"
......

#更改模式为ipvs
root@master30~/web 16:35:32# kubectl edit cm -n kube-system kube-proxy
......
    mode: "ipvs"
......

# 删除pod,等待控制器重新创建
root@master30~/web 16:39:10# kubectl delete pods -n kube-system kube-proxy-pfbpn kube-proxy-smc9w kube-proxy-xzxkq
pod "kube-proxy-pfbpn" deleted
pod "kube-proxy-smc9w" deleted
pod "kube-proxy-xzxkq" deleted

root@master30~/web 16:39:42# kubectl get pods -n kube-system | grep kube-proxy                     kube-proxy-6vjf7                             1/1     Running   0                6s
kube-proxy-jcsgn                             1/1     Running   0                6s
kube-proxy-mkcrg                             1/1     Running   0                7s

# 再次查看日志,mode变为ipvs
root@master30~/web 16:39:47# kubectl logs -n kube-system kube-proxy-6vjf7
I0627 08:39:42.021586       1 server.go:1062] "Successfully retrieved node IP(s)" IPs=["10.1.8.32"]
I0627 08:39:42.026965       1 conntrack.go:59] "Setting nf_conntrack_max" nfConntrackMax=131072
I0627 08:39:42.064501       1 server.go:659] "kube-proxy running in dual-stack mode" primary ipFamily="IPv4"
I0627 08:39:42.529531       1 server_linux.go:233] "Using ipvs Proxier"
......

综合案例:haproxy+web

需求

  1. 创建一个名称为 haproxy 的pod,使用镜像 haproxy,pod haproxy 的配置文件保存在 configmap 中,通过 volume 方式挂载到 /usr/local/etc/haproxy/haproxy.cfg。将流量转发到pod webapp-1webapp-2

    haproxy 的配置文件模板内容如下:

    global
        daemon
        maxconn 256
    
    defaults
        mode http
        timeout connect 5000ms
        timeout client 50000ms
        timeout server 50000ms
    
    frontend http-in
        bind *:8080
        default_backend servers
    
    backend servers
        server  app1 10.224.84.80:80 check
        server  app2 10.224.149.25:80 check
    
  2. 创建第一个pod,名称为webapp-1,使用镜像nginx。为该pod创建一个cm,名称为webapp-1,该cm保存两个值:

    • index.html=“hello webapp-1”
    • error.html=“sorry, error.”

    这个两个值通过volume方式挂载到**/usr/share/nginx/html**目录。

  3. 创建第二个pod,名称为webapp-2,使用镜像nginx。为该pod创建一个cm,名称为webapp-2,该cm保存两个值:

    • index.html=“hello webapp-2”
    • error.html=“sorry, error.”

    这个两个值通过volume方式挂载到**/usr/share/nginx/html**目录。

示例如下:

创建第一个cm和pod

root@master30~/web 15:26:29# kubectl create cm webapp-1 --from-literal=index.html="hello world" --from-literal=error.html="error"
root@master30~/web 15:28:24# kubectl get cm webapp-1 -o yaml | grep ^data -A2
data:
  error.html: error
  index.html: hello world
root@master30~/web 15:28:30# vim pod-webapp-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp-1
spec:
  containers:
  - name: nginx
    image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: config
      mountPath: "/usr/share/nginx/html"
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: webapp-1
root@master30~/web 15:28:30# kubectl apply -f  pod-webapp-1.yaml

创建第二个cm和pod

root@master30~/web 15:29:40# kubectl create cm webapp-2 --from-literal=index.html="hello webapp-2" --from-literal=error.html="sorry, error."
root@master30~/web 15:30:56# vim pod-webapp-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp-2
spec:
  containers:
  - name: nginx
    image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: config
      mountPath: "/usr/share/nginx/html"
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: webapp-2
root@master30~/web 15:31:17# kubectl apply -f pod-webapp-2.yaml

创建haproxy配置文件。

#获取pod ip地址
root@master30~/web 15:32:55# kubectl get pods -o wide | awk '{print$1,$6}'
NAME IP
webapp-1 10.224.128.34
webapp-2 10.224.96.234

# haproxy内容如下:
root@master30~/web 15:32:57# vim haproxy.cfg
global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http-in
    bind *:8080
    default_backend servers

backend servers
    server  app1 10.224.84.80:80 check
    server  app2 10.224.149.25:80 check

工作流程:客户端 → HAProxy 8080 端口 → 七层 HTTP 转发 → 轮询分发到 app1 /app2 两台 80 端口 Web 服务

# 创建haproxy.cfg configmap
root@master30~/web 15:32:57# kubectl create cm haproxy.cfg --from-file=haproxy.cfg=haproxy.cfg

# 创建pod haproxy
root@master30~/web 15:34:24# vim pod-haproxy.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: haproxy
spec:
  containers:
  - name: haproxy
    image: hub.laoma.cloud/library/haproxy:3.4
    imagePullPolicy: IfNotPresent
    securityContext:
      allowPrivilegeEscalation: true
    volumeMounts:
    - name: config
      mountPath: "/usr/local/etc/haproxy"
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: haproxy.cfg
root@master30~/web 16:20:14# kubectl apply -f pod-haproxy.yaml

验证:

root@master30~/web 16:20:34# kubectl get pods haproxy -o wide | awk '{print$6}'
IP
10.224.128.38
root@master30~/web 16:21:06# curl 10.224.128.38:8080
hello webapp-1
root@master30~/web 16:21:07# curl 10.224.128.38:8080
hello webapp-2

Secret类型

Secret 介绍

Secret 是一种包含少量敏感信息的对象,例如密码、令牌或密钥。 这样的信息可能会被放在 Pod 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。

创建 Secret 可以独立于使用它们的 Pod, 减少在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施, 例如避免将敏感数据写入非易失性存储。

Secret 类似于 ConfigMap 但专门用于保存机密数据。

Secret 与ConfigMap的区别在于:Secret对数据编码,ConfigMap不对数据编码。

Secret 类型

  • generic:定义键值对,对变量值加密,类型为Opaque。

  • docker-registry:用于访问registry仓库的凭据,类型kubernetes.io/dockerconfigjson。

  • tls:保存TLS公钥和私钥,类型为Opaque。

Secret 创建

generic
Usage:
  kubectl create secret generic NAME [--type=string] [--from-file=[key=]source]
[--from-literal=key1=value1] [--dry-run=server|client|none] [options]
基于键值对

一般用于传递变量值。

root@master30~/web 18:43:40# kubectl create secret generic mysecret --from-literal=user=tom --from-literal=password1=redhat --from-literal=password2=redhat
secret/mysecret created
root@master30~/web 18:49:41# kubectl get secrets mysecret -o yaml | grep ^data -A3
data:
  password1: cmVkaGF0
  password2: cmVkaGF0
  user: dG9t
或者
root@master30~/web 18:46:21# kubectl get secrets mysecret -o jsonpath={.data} | json_reformat
{
    "password1": "cmVkaGF0",
    "password2": "cmVkaGF0",
    "user": "dG9t"
}
# json_reformat 工具由软件包 yajl-tools 提供

# secret 通过 base64 编码
root@master30~/web 18:50:23# echo -n tom | base64
dG9t
基于普通文件

一般用于传递配置文件。

root@master30~/web 18:51:19# echo -n tom > user
root@master30~/web 18:52:22# echo -n redhat > password1
root@master30~/web 18:52:33# echo -n redhat > password2
root@master30~/web 18:52:36# kubectl create secret generic mysecret2 --from-file=user --from-file=password1 --from-file=password2
root@master30~/web 18:54:10# kubectl get secrets
NAME        TYPE     DATA   AGE
mysecret    Opaque   3      8m55s
mysecret2   Opaque   3      5s
root@master30~/web 18:54:15# kubectl get secrets mysecret2 -o yaml
apiVersion: v1
data:
  password1: cmVkaGF0
  password2: cmVkaGF0
  user: dG9t
kind: Secret
metadata:
  creationTimestamp: "2026-06-27T10:54:10Z"
  name: mysecret2
  namespace: configure
  resourceVersion: "229660"
  uid: f372e931-7a3a-4d3b-91bd-693c19b2faac
type: Opaque
基于键值对内容的文件
root@master30~/web 18:56:43# echo 'user=tom
password1=redhat
password2=redhat' > env.txt
root@master30~/web 18:57:03# kubectl create secret generic mysecret4 --from-env-file=./env.txt
secret/mysecret4 created
root@master30~/web 18:57:09# kubectl get secrets mysecret4 -o yaml
apiVersion: v1
data:
  password1: cmVkaGF0
  password2: cmVkaGF0
  user: dG9t
kind: Secret
metadata:
  creationTimestamp: "2026-06-27T10:57:09Z"
  name: mysecret4
  namespace: configure
  resourceVersion: "229929"
  uid: 49d79a19-8dc4-4cde-886d-7d50c2fd7f75
type: Opaque
基于目录

文件名用作key名,文件内容用作value。

root@master30~/web 18:57:58# mkdir config
root@master30~/web 18:58:34# mv user password1 password2 config
root@master30~/web 19:00:10# kubectl create secret generic mysecret5 --from-file=config/
root@master30~/web 19:00:42# kubectl get secrets mysecret5 -o yaml
apiVersion: v1
data:
  password1: cmVkaGF0
  password2: cmVkaGF0
  user: dG9t
kind: Secret
metadata:
  creationTimestamp: "2026-06-27T11:00:37Z"
  name: mysecret5
  namespace: configure
  resourceVersion: "230237"
  uid: 6d1949a8-ba38-42d9-a8ca-c8fbae5d950c
type: Opaque
基于 yaml 文件
apiVersion: v1
kind: Secret
metadata:
  name: mysecret6
type: Opaque
data:
  user: dG9t
  password1: cmVkaGF0
  password2: cmVkaGF0

yaml格式中变量值使用转换后的值。

docker-registry

回顾docker login命令:

docker login DOCKER_REGISTRY_SERVER -u=DOCKER_USER -p=DOCKER_PASSWORD

docker 登录的信息默认保存在~/.dockercfg,后续docker pull和push使用该凭据文件访问registry。

Usage:
  kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-literal=key1=value1] [--dry-run=server|client|none] [options]

示例:

[root@master30 ~]# kubectl create secret docker-registry registry --docker-username=laoma --docker-password=redhat --docker-email=laoma@laoma.cloud --docker-server=registry.laoma.cloud
secret/docker-registry-secret created
[root@master30 ~]# kubectl get secrets docker-registry-secret -o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5yZWRoYXQuZnVuIjp7InVzZXJuYW1lIjoibGFvbWEiLCJwYXNzd29yZCI6InJlZGhhdCIsImVtYWlsIjoibGFvbWFAcmVkaGF0LmZ1biIsImF1dGgiOiJiR0Z2YldFNmNtVmthR0YwIn19fQ==
kind: Secret
metadata:
......
  name: docker-registry-secret
  namespace: laoma
  resourceVersion: "29629"
  selfLink: /api/v1/namespaces/laoma/secrets/docker-registry-secret
  uid: 017f736a-11f5-42d8-a9ed-47dad254f2c2
type: kubernetes.io/dockerconfigjson
TLS

创建 TLS secret 保存公钥/私钥对。 公钥证书必须是 .PEM 编码。

Usage: 
  kubectl create secret tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none] [options]

创建私钥和证书

#--1--生成私钥 
[root@master30 ~]# mkdir certs && cd certs
[root@master30 certs]# openssl genrsa -out www.key 2048  

#--2--生成请求文件csr
[root@master30 certs]# openssl req -new -key www.key -out www.csr -subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=www.laoma.cloud/emailAddress=webadmin@laoma.cloud" 
# CN的值必须是网站域名

#--3--使用自己的私钥对请求文件签名,以生成证书 
[root@master30 certs]# openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt

创建 tls 类型 secret

[root@master30 certs]# kubectl create secret tls www-tls --cert=./www.crt --key=./www.key

Secret 引用

Secrets可以以卷方式挂载给pods,也可以通过环境变量方式传递给pods。

以环境变量方式引用

示例:

root@master30~/web 19:04:57# vim pod-secret-env.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  containers:
  - image: hub.laoma.cloud/library/mysql:latest
    imagePullPolicy: IfNotPresent
    name: mysql
    ports:
    - containerPort: 3306
      name: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      # 原先通过value设置环境变量值
      valueFrom:
        secretKeyRef:
          name: mysecret
          key: password1
root@master30~/web 19:31:06# kubectl exec mysql -- bash -c 'echo $MYSQL_ROOT_PASSWORD'
redhat
以 volume 方式引用
root@master30~/web 19:33:11# echo hello world > index.html
root@master30~/web 19:33:28# echo error > error.html
root@master30~/web 19:33:38# kubectl create secret generic web --from-file=index.html --from-file=error.html
引用整体

示例:

root@master30~/web 19:34:36# vim pod-secret-volume-all.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web
  name: web
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    name: web
    volumeMounts:
    - name: webcontent
      # mountPath值是一个挂载点,将secrete中所有键值对挂载过来
      mountPath: "/usr/share/nginx/html"
  volumes:
  - name: webcontent
    secret:
      secretName: web

验证:

root@master30~/web 19:38:35# kubectl apply -f pod-secret-volume-all.yaml
pod/web created
root@master30~/web 19:38:48# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE                 NOMINATED NODE   READINESS GATES
web    1/1     Running   0          8s    10.224.128.39   worker31.hgq.cloud   <none>           <none>
root@master30~/web 19:38:56# curl 10.224.128.39
hello world
root@master30~/web 19:39:05# curl 10.224.128.39/error.html
error
引用特定 key

示例:

root@master30~/web 19:39:28# vim pod-secret-volume-single.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web
  name: web
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx:latest
    imagePullPolicy: IfNotPresent
    name: web
    volumeMounts:
    - name: webcontent
      mountPath: "/usr/share/nginx/html"
  volumes:
  - name: webcontent
    secret:
      secretName: web
      items:
      - key: index.html
        # 通过path设置挂载的文件名,这里我们path值与key一致。
        path: index.html

验证:

root@master30~/web 19:40:20# kubectl apply -f pod-secret-volume-single.yaml
pod/web created
root@master30~/web 19:40:47# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE                 NOMINATED NODE   READINESS GATES
web    1/1     Running   0          58s   10.224.128.40   worker31.hgq.cloud   <none>           <none>
root@master30~/web 19:41:45# curl 10.224.128.40
hello world
root@master30~/web 19:41:51# kubectl exec web -- ls /usr/share/nginx/html
index.html

以卷方式引用,支持动态更新secret内容:

root@master30~/web 19:42:47# echo 'hello nginx' |base64
aGVsbG8gbmdpbngK
root@master30~/web 19:42:54# kubectl edit secrets web
......
apiVersion: v1
data:
  index: aGVsbG8gbmdpbngK
......
# 等30秒左右查看
root@master30~/web 19:44:29# curl 10.224.128.40
hello nginx

注意:

  1. Volume 挂载 Secret(和 ConfigMap 逻辑一样

把 Secret 里每一个 key 变成容器内真实文件:

  • secret 的 key = 文件名
  • secret 的 value = 文件内容
  • 程序读取本地文件拿密码,不会暴露在环境变量列表里。
  • Secret 更新后,容器内文件会自动刷新。
  1. env 环境变量引用 Secret

只是把 Secret 的值提取出来,放进容器的环境变量里:

  • 没有生成任何文件;
  • 执行 envps 命令就能明文看到密码,泄露风险高;
  • Secret 修改后,必须重启 Pod 才能加载新值。
文件权限

映射过去文件默认权限是644(8进制),对应10进制是420.。

设置映射的所有文件权限,defaultMode。

  volumes:
  - name: foo
    secret:
      secretName: mysecret1
      defaultMode: 256

10进制是256转换成8进制是400。因为json格式不支持8进制,所以使用10进制。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐