目录
临时存储
Kubernetes 支持很多类型的卷。 Pod可以同时使用任意数目的卷类型。
临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。
但是Kubernetes 不会销毁 持久卷
。
hostPath
hostPath
允许挂载 Node 上的文件系统到 Pod 里面去。如果 Pod 需要使用 Node 上的文件,可以使用 hostPath。
即使 Pod 被删除后重启,也可以重新加载到保存在 Node 主机上的该目录,该目录下的文件不会丢失。
注意:hostPath 之所以被归为临时存储,是因为实际开发中,我们一般都是通过 Deployment 部署 Pod 的,一旦 Pod 被 Kubernetes 杀死或重启拉起等,并不一定会部署到原来的 Node 节点中。
除了必需的 path
属性之外,用户可以选择性地为 hostPath
卷指定 type
。
取值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate | 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 |
Directory | 在给定路径上必须存在的目录。 |
FileOrCreate | 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 |
File | 在给定路径上必须存在的文件。 |
Socket | 在给定路径上必须存在的 UNIX 套接字。 |
CharDevice | 在给定路径上必须存在的字符设备。 |
BlockDevice | 在给定路径上必须存在的块设备。 |
hostPath 的典型应用就是时间同步:通常而言,Node 节点的时间是同步的(云厂商提供的云服务器的时间都是同步的),但是,Pod 中的容器的时间就不一定了,有 UTC 、CST 等;同一个 Pod ,如果部署到中国,就必须设置为 CST 了。
创建文件hostPath-pod.yaml,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx-host-path
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath: # hostPath
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
上面示例就是把node上的目录的时区文件挂载到容器中。
EmptyDir
emptyDir
主要用于一个 Pod 中不同的 Container 共享数据使用的,由于只是在 Pod 内部使用,因此与其他 volume 比较大的区别是,当 Pod 如果被删除了,那么 emptyDir 也会被删除。
存储介质可以是任意类型,如 SSD、磁盘或网络存储。可以将 emptyDir.medium 设置为 Memory 让 k8s 使用 tmpfs(内存支持文件系统),速度比较快,但是重启 tmpfs 节点时,数据会被清除,且设置的大小会计入到 Container 的内存限制中。
emptyDir
的一些用途:
-
- 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
-
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
emptyDir
卷存储在该节点的磁盘或内存中,如果设置 emptyDir.medium = Memory
,那么就告诉 Kubernetes 将数据保存在内存中,并且在 Pod 被重启或删除前,所写入的所有文件都会计入容器的内存消耗,受到容器内存限制约束。
示例:
创建两个pod,挂载同一个emptyDir。
第一个:
apiVersion: v1
kind: Pod
metadata:
name: test-pd1
spec:
containers:
- image: nginx
name: nginx-emptydir
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
第二个:
apiVersion: v1
kind: Pod
metadata:
name: test-pd2
spec:
containers:
- image: nginx
name: nginx-emptydir
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
这样如果在一个容器中修改了文件夹,另一个容器也会跟着修改。具体效果不再演示。
NFS 挂载
NFS是持久化数据卷的方式之一。
NFS 的简介:网络文件系统,英文Network File System(NFS),是由 SUN公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。
NFS 卷能将 NFS挂载到 Pod 中。 不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
注意:实际开发中,不建议使用 NFS 作为 Kubernetes 集群持久化的驱动。
安装NFS
- 本次以 Master (192.168.65.100)节点作为 NFS 服务端:
yum install -y nfs-utils
- 在 Master(192.168.65.100)节点创建 /etc/exports 文件:
# * 表示暴露权限给所有主机;* 也可以使用 192.168.0.0/16 代替,表示暴露给所有主机
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
- 在 Master(192.168.65.100)节点创建 /nfs/data/ (共享目录)目录,并设置权限:
mkdir -pv /nfs/data/
chmod 777 -R /nfs/data/
- 在 Master(192.168.65.100)节点启动 NFS :
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
- 在 Master(192.168.65.100)节点加载配置:
exportfs -r
- 在 Master(192.168.65.100)节点检查配置是否生效:
exportfs
# /nfs/data/
- 在 Node(192.168.65.101、192.168.65.102)节点安装 nfs-utils :
# 服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许,否则远端客户无法连接。
yum install -y nfs-utils
- 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令检查 nfs 服务器端是否有设置共享目录:
# showmount -e $(nfs服务器的IP)
showmount -e 192.168.65.100
- 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令挂载 nfs 服务器上的共享目录到本机路径
/root/nd
:
mkdir /nd
# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount
mount -t nfs 192.168.65.100:/nfs/data /nd
- 在 Node (192.168.65.101)节点写入一个测试文件:
echo "hello nfs server" > /nd/test.txt
- 在 Master(192.168.65.100)节点验证文件是否写入成功:
cat /nfs/data/test.txt
配置文件
使用时,配置如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/ # / 一定是文件夹
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
nfs: # 使用 nfs 存储驱动
path: /nfs/data # nfs 共享的目录
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
restartPolicy: Always
PV和PVC
概述
前面我们已经学习了使用 NFS 提供存储,此时就要求用户会搭建 NFS 系统,并且会在 yaml 配置 NFS,这就带来的一些问题:
-
① 开发人员对 Pod 很熟悉,非常清楚 Pod 中的容器那些位置适合挂载出去。但是,由于 Kubernetes 支持的存储系统非常之多,开发人员并不清楚底层的存储系统,而且要求开发人员全部熟悉,不太可能(术业有专攻,运维人员比较熟悉存储系统)。
-
② 在 yaml 中配置存储系统,就意味着将存储系统的配置信息暴露,非常不安全(容易造成泄露)。
为了能够屏蔽底层存储实现的细节,方便用户使用,Kubernetes 引入了 PV 和 PVC 两种资源对象。
持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。
持久卷声明(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
PV 的缺点:
① 需要运维事先准备好 PV 池。
② 资源浪费:没有办法预估合适的 PV,假设运维向 k8s 申请了 20m 、50m、10G 的 PV,而开发人员申请 2G 的 PVC ,那么就会匹配到 10G 的PV ,这样会造成 8G 的空间浪费。
也有人称 PV 为静态供应。
PV 的回收策略
- 保留(Retain)
回收策略 Retain 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在这前一声明人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:
- 删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在。
- 根据情况,手动清除所关联的存储资产上的数据。
- 手动删除所关联的存储资产。
如果你希望重用该存储资产,可以基于存储资产的定义创建新的 PersistentVolume 卷对象。
- 删除(Delete)
对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 动态制备的卷会继承其 StorageClass 中设置的回收策略, 该策略默认为 Delete。管理员需要根据用户的期望来配置 StorageClass; 否则 PV 卷被创建之后必须要被编辑或者修补。
- 回收(Recycle)
警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。
如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的擦除 (rm -rf /thevolume/*)操作,之后允许该卷用于新的 PVC 申领。
PV 的访问模式
访问模式(accessModes):用来描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
-
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。
-
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。
-
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载。
PV 的状态
一个 PV 的生命周期,可能会处于 4 种不同的阶段:
-
- Available(可用):表示可用状态,还未被任何 PVC 绑定。
-
- Bound(已绑定):表示 PV 已经被 PVC 绑定。
-
- Released(已释放):表示 PVC 被删除,但是资源还没有被集群重新释放。
-
- Failed(失败):表示该 PV 的自动回收失败。
使用PV和PVC
- 创建 PV (一般是运维人员操作)
先创建文件夹:
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
创建PV的资源文件 k8s-pv.yaml,内容如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-10m
spec:
storageClassName: nfs-storage # 创建pv的存储类名,用于分组,要与pvc的相同
capacity: # 容量配置
storage: 10m # 容量
accessModes: #访问模式
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain #回收策略
nfs: # 使用 nfs 存储驱动
path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-20m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 20m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-500m
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 500m
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1g
spec:
storageClassName: nfs-storage # 用于分组
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
nfs: # 使用 nfs 存储驱动
path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
执行文件:
kubectl apply -f k8s-pv.yaml
- 创建PVC (一般是开发人员)
一般PVC是随着Pod一起创建,编辑资源文件k8s-pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc-500m # pvc的名字
namespace: default
labels:
app: nginx-pvc-500m # pvc的label
spec:
storageClassName: nfs-storage # 与pv的一致
accessModes: # 访问模式,与pv的一致
- ReadWriteOnce
resources: # 资源定义
requests:
storage: 500m # 最大存储500m,资源可以小于pv,但是不能大于pv,否则匹配不上
---
apiVersion: v1
kind: Pod # 创建pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts: # 定义挂载数据卷
- name: html # 要挂载的数据卷的名字
mountPath: /usr/share/nginx/html/ # 挂载大屏容器的目录
volumes: # 定义数据卷
- name: html # 数据卷的名字
persistentVolumeClaim: # 这个使用pvc作为存储卷
claimName: nginx-pvc-500m # 要关联的pvc的名字,与定义pvc的一 致
readOnly: false
restartPolicy: Always
执行文件:
kubectl apply -f k8s-pvc.yaml
成功部署之后,这样就完成了
生命周期
PVC 和 PV 是一一对应的,PV 和 PVC 之间的相互作用遵循如下的生命周期:
-
① 资源供应:管理员手动创建底层存储和 PV。
-
② 资源绑定:
-
- 用户创建 PVC ,Kubernetes 负责根据 PVC 声明去寻找 PV ,并绑定在用户定义好 PVC 之后,系统将根据 PVC 对存储资源的请求在以存在的 PV 中选择一个满足条件的。
-
-
- 一旦找到,就将该 PV 和用户定义的 PVC 进行绑定,用户的应用就可以使用这个 PVC 了。
-
-
-
- 如果找不到,PVC 就会无限期的处于 Pending 状态,直到系统管理员创建一个符合其要求的 PV 。
-
-
- PV 一旦绑定到某个 PVC 上,就会被这个 PVC 独占,不能再和其他的 PVC 进行绑定了。
-
③ 资源使用:用户可以在 Pod 中像 Volume 一样使用 PVC ,Pod 使用 Volume 的定义,将 PVC 挂载到容器内的某个路径进行使用。
-
④ 资源释放:
-
- 用户删除 PVC 来释放 PV 。
-
- 当存储资源使用完毕后,用户可以删除 PVC,和该 PVC 绑定的 PV 将会标记为
已释放
,但是还不能立刻和其他的 PVC 进行绑定。通过之前 PVC 写入的数据可能还留在存储设备上,只有在清除之后该 PV 才能再次使用。
- 当存储资源使用完毕后,用户可以删除 PVC,和该 PVC 绑定的 PV 将会标记为
-
⑤ 资源回收:
-
- Kubernetes 根据 PV 设置的回收策略进行资源的回收。
-
- 对于 PV,管理员可以设定回收策略,用于设置与之绑定的 PVC 释放资源之后如何处理遗留数据的问题。只有 PV 的存储空间完成回收,才能供新的 PVC 绑定和使用。
动态供应
StorageClass
上面通过手动的方式创建了一个 NFS Volume,这在管理很多 Volume 的时候不太方便。Kubernetes 还提供了 StorageClass 来动态创建 PV,不仅节省了管理员的时间,还可以封装不同类型的存储供 PVC 选用。
StorageClass 包括四个部分
- provisioner:指定 Volume 插件的类型,包括内置插件(如
kubernetes.io/glusterfs
)和外部插件(如 external-storage 提供的ceph.com/cephfs
)。 - mountOptions:指定挂载选项,当 PV 不支持指定的选项时会直接失败。比如 NFS 支持
hard
和nfsvers=4.1
等选项。 - parameters:指定 provisioner 的选项,比如
kubernetes.io/aws-ebs
支持type
、zone
、iopsPerGB
等参数。 - reclaimPolicy:指定回收策略,同 PV 的回收策略。
在使用 PVC 时,可以通过 DefaultStorageClass
准入控制设置默认 StorageClass, 即给未设置 storageClassName 的 PVC 自动添加默认的 StorageClass。而默认的 StorageClass 带有 annotation storageclass.kubernetes.io/is-default-class=true
。
动态供应流程
-
① 集群管理员预先创建存储类(StorageClass)。
-
② 用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim)。
-
③ 存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume)。
-
④ 系统读取存储类的信息。
-
⑤ 系统基于存储类的信息,在后台自动创建 PVC 需要的 PV 。
-
⑥ 用户创建一个使用 PVC 的 Pod 。
-
⑦ Pod 中的应用通过 PVC 进行数据的持久化。
-
⑧ PVC 使用 PV 进行数据的最终持久化处理。
NFS 动态供应案例
注意:不一定需要设置 NFS 动态供应,可以直接使用云厂商提供的 StorageClass 。
部署 NFS 动态供应:
创建文件k8s-nfs-provisioner.yaml:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定一个供应商的名字
# or choose another name, 必须匹配 deployment 的 env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false" # 删除 PV 的时候,PV 中的内容是否备份
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default
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: ccr.ccs.tencentyun.com/gcr-containers/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.65.100 # NFS 服务器的地址
- name: NFS_PATH
value: /nfs/data # NFS 服务器的共享目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.65.100
path: /nfs/data
# 下面都是权限相关的配置了
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- 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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
上面涉及到Role和RoleBinding等权限相关的资源,这里可以不用管,后面还会介绍,只需要知道他是负责创建和管理权限的即可。因为我们要创建StorageClass必须要制定一个provisioner(制备器,可存储供应商),要让provisioner帮我们管理pv,那就要给他一定的权限。
部署上面文件即可:
kubectl apply -f k8s-nfs-provisioner.yaml
这样就让provisioner通过StorageClass帮我们管理PV了。
下面使用时,指定StorageClass的名字:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
namespace: default
labels:
app: nginx-pvc
spec:
storageClassName: nfs-client # 注意此处
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
- name: html
persistentVolumeClaim:
claimName: nginx-pvc
readOnly: false
restartPolicy: Always
目前,只需要运维人员部署好各种 storageclass,开发人员在使用的时候,创建 PVC 即可;但是,存储系统太多太多,运维人员也未必会一一掌握,可以 Rook 来统一管理。