1. 存储机制概述
容器内部存储的生命周期是短暂的,会随着容器环境的销毁而被销毁,具有不稳定性。同时,如果多个容器希望共享同一份存储资源,则仅仅依赖容器本身是很难实现的。因此 Kubernetes 通过将容器应用所需的存储资源抽象为 Volume(存储卷)来解决这些问题。
Volume 在 Kubernetes 中也是一种资源,Kubernetes 提供了多种类型的 Volume 供容器应用使用。Pod 通过挂载(Mount)的方式来使用一个或多个 Volume。
Volume 可以被分为两大类:
- 临时卷:具有与Pod相同的生命周期。
- 持久卷:通常比Pod的生命周期更长。在Pod被销毁时,持久卷通常不会被立刻删除,而是交给用户来处理其中的数据。
2. 临时卷详解
临时卷与 Pod 具有相同的生命周期,包括为 Pod 创建的临时卷(emptyDir、Generic Ephemeral、CSI Ephemeral),以及通过 Kubernetes 的资源对象 configMap、secret、Downward API、Service Account Token、Projected Volume 为 Pod 提供数据的临时卷等。
2.1. emptyDir
这种类型的 Volume 将在 Pod 被调度到 Node 时由 kubelet 进行创建,在初始状态下其目录是空的,所以被命名为“空目录”(Empty Directory)。它与 Pod 具有相同的生命周期,当 Pod 被销毁时,emptyDir 对应的目录也会被删除。同一个 Pod 中的多个容器都可以挂载这种类型的 Volume。
常见应用场景:
- 基于磁盘进行合并排序操作时需要的暂存空间。
- 长时间计算任务的中间检查点文件。
- 为某个Web服务提供的临时网站内容文件。
在默认情况下,kubele 会在 Node 的 /var/lib/kubelet/pods 目录下为 Pod 创建 emptyDir目录,这个目录的存储介质可能是本地磁盘、SSD磁盘或者网络存储设备,具体取决于环境的配置。
另外,emptyDir 可以通过 medium 字段设置存储介质为“Memory”,表示使用基于内存的文件系统。在主机重启之后,tmpfs 中的内容就会被清空。此外,写入 tmpfs 中的数据将被计入容器的内存使用量,受到容器级别内存资源上限的限制。
示例:
...
spec:
containers:
...
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
YAML设置 emptyDir 为内存示例:
...
spec:
containers:
...
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: "Memory"
# 在开启 SizeMemoryBackedVolumes 后可以设置使用内存上限
volumes:
- name: cache-volume
emptyDir: "Memory"
sizeLimit: 500Mi
YAML2.2. Generic Ephemeral
2.3. CSI Ephemeral
2.4. 其他类型临时卷
3. 持久卷详解
前面介绍了临时存储,临时存储数据不能持久化。因此,Kubernetes 引人了 Persistent Volume(简称PV,持久卷)和 Persistent Volume Claim(简称PVC,持久卷申请申明)两个资源对象来实现存储持久化管理。
3.1. PV 与 PVC
我们可以将 PV 看作可用的存储资源,PVC 则是对存储资源的需求。
PV 将存储定义为一种容器应用可以使用的资源。PV 由管理员创建和配置,它与存储资源提供者的具体实现直接相关。不同提供者提供的 PV 类型包括 NFS、iSCSI、RBD 或者由 GCE 或 AWS 公有云提供的共享存储等。
如果某个 Pod 想要使用 PV,则需要通过 PVC 来完成申请,随后由 Kubernetes 完成从PVC 到 PV 的自动绑定流程,PVC 可以申请存储空间的大小(Size)和访问模式(例如ReadWriteOnce、 ReadOnlyMany 或 ReadWriteMany)。
目前 PV 和 PVC 之间是一对一绑定的关系,也就是说一个 PV 只能被一个 PVC 绑定。
3.2. StorageClass
使用PVC申请的存储空间可能仍然不满足应用对存储设备的各种需求。例如:
- 在大规模集群场景下,管理员手动管理 PV 非常不方便。
- 不同存储设备IO性能不同,种类不同,需要进行归类,方便 pod 使用。
为了解决上面两个问题,Kubernetes 从 v1.4 版本开始引入了一个新的资源对象StorageClass,用于标记存储资源的特性和性能,根据 PVC 的需求动态供给合适的 PV 资源。通过 StorageClass 创建的 PV 又称之为动态 PV。
3.3. PV 详解
PV 没有名称空间限制。
yaml 文件示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: '/data/test1'
type: DirectoryOrCreate
storageClassName: manual # 我们是静态pv,此处声明的sc,代表设定该pv归属的类为 manual,代表静态/手动
YAMLPV 资源对象需要设置的关键配置参数如下:
- 存储容量(capacity):存储容量用于描述存储的容量,目前仅支持对存储空间的设置(storage=xx),未来可能加人对 IOPS、吞吐率等的设置。
- 存储卷模式(volumeMode):其中可以设置的选项包括 Filesystem (文件系统,默认值)和 Block(块设备)。
- 访问模式(accessModes):AccessModes 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,包括下面几种方式(都是针对节点级别的):
- ReadWriteOnce(RWO):读写权限,并且只能被单个单个节点上的一个或多个 pod 挂载使用。
- ReadOnlyMany(ROX):只读权限,可以同时在多个节点上挂载并被不同的Pod。
- ReadWriteMany(RWX):读写权限,那么可以同时在多个节点上挂载并被不同的Pod使用。
- ReadWriteOncePod(RWOP):可以被单个 Pod 以读写方式挂载的模式,该特性在 Kubernetes v1.22 版本中被引人。仅支持 CSI 存储卷,用于在集群中只有一个 Pod 时以读写方式使用这种模式的 PVC。
- 存储类别(Class):PV 可以设定其存储的类别,通过 storageClassName 参数指定一个 StorageClass 资源对象的名称。具有特定类别的 PV 只能与请求了该类别的 PVC 绑定。未设定类别的 PV 则只能与不请求任何类别的 PVC 绑定。
- 回收策略(ReclaimPolicy):回收策略通过 PV 定义中的 persistentVolumeReclaimPolicy 字段进行设置,可选项如下:
- Retain:保留数据,当 PVC 被删除时,PV 不会自动删除。它的状态变为“Released”,但保留资源不能被其他 PVC 继续使用,直到管理员手动处理。这允许管理员手动回收资源或者检查数据。重要的数据还是推荐用该策略。
- Delete:在这种回收策略下,PVC 被删除时,PV 以及底层的存储资源也会自动被删除。
- Recycle(已弃用):PVC 被删除时,PV 上的数据将被简单地删除,而不会销毁 PV 本身。该策略会对 PV 进行基本清理(如删除文件内容),然后将其重新标记为Available ,使其可以被新的 PVC 再次绑定。由于安全和效能考虑,这个选项在较新的 Kubernetes 版本中已经被标记为弃用。
- 节点亲和性(nodeAffinity):PV 可以通过设置节点亲和性来实现只能通过某些Node 访问 Volume,这可以在 PV 定义的 nodeAffinity 字段中进行设置。使用这些Volume 的 Pod 将被调度到满足亲和性要求的 Node 上。示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prometheus-local
labels:
app: prometheus
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
storageClassName: local-storage
local:
path: /data/k8s/prometheus
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-master-03-u-203
persistentVolumeReclaimPolicy: Retain
YAML- PV 生命周期的各个阶段
- Available:可用状态,还未与某个 PVC 绑定。
- Bound:已与某个 PVC 绑定。
- Released:与之绑定的 PVC 已被删除,但未完成资源回收,不能被其他PVC使用。
- Failed:自动资源回收失败。
3.4. PVC 详解
受限于名称空间,PVC 与 使用该 PVC 的 Pod 需要再同一个名称空间。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: manual
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
YAMLPVC 关键参数说明:
- 资源请求(resources):描述对存储资源的请求,通过 resources.requests.storage 字段设置需要的存储空间大小。
- 访问模式(accessModes):PVC 也可以设置访问模式,用于描述用户应用对存储资源的访问权限。其访问模式的设置与 PV 的设置相同。
- 存储卷模式(volumeMode):PVC 也可以设置存储卷模式,用于描述希望使用的 PV 存储卷模式,包括文件系统和块设备。PVC 设置的存储卷模式应该与 PV 存储卷模式相同,以实现绑定;如果不同,则可能出现不同的绑定结果。PV 和 PVC 在各种组合模式下是否可以绑定的结果如下表所示:(未设定默认采用 Filesystem)
PV 的存储卷模式 | PVC 的存储卷模式 | 是否可以绑定 |
Filesystem | Filesystem | true |
Filesystem | Block | false |
Block | Filesystem | false |
Block | Block | true |
- PV 选择条件(selector):通过设置 LabelSelector ,可使 PVC 对于系统中已存在的各种 PV 进行筛选。系统将根据标签选出合适的 PV 与该 PVC 进行绑定。对于选择条件可以通过 matchLabels 和 matchExpressions 进行设置,如果两个字段都已设置,则 Selector 的逻辑将是两组条件同时满足才能完成匹配。
- 存储类别(Class):在定义 PV C时可以设定需要的后端存储的类别(通过 storageClassName 字段进行指定),只有设置了该 Class 的 PV 才能被系统筛选出来,并与该 PVC 进行绑定。
3.4. StorageClass 详解
StorageClass 作为对存储资源的抽象定义,可对用户设置的 PVC 申请蔽后端存储的细节,这一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手动管理 PV的工作,由系统自动完成 PV 的创建和绑定,实现动态的资源供应。
StorageClass 配置 yaml 文件示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- debug
volumeBindingMode: Immediate
YAMLStorageClass 资源关键配置:
- 存储资源提供者(Provisioner):Provisioner 描述存储资源的提供者,用于提供具体的 PV 资源,可以将其看作后端存储驱动。
- 回收策略(Reclaim Policy):通过动态资源供应模式创建的 PV 将继承在StorageClass 上设置的回收策略,配置字段名称为“reclaimPolicy”,可以设置的选项包括 Delete 和 Retain。如果 StorageClass 没有指定 reclaimPolicy 字段,则默认值为 Delete。
- 是否允许存储卷扩容(Allow Volume Expansion):当 StorageClass 的 AllowVolumeExpansion 字段被设置为 true 时,表示 PV 被配置为可以扩容,系统将允许用户通过编辑增加 PVC 的存储空间自动完成 PV 的扩容。
- 存储绑定模式(Volume Binding Mode):StorageClass 资源对象的 volumeBindingMode 字段用于控制何时将 PVC 与动态创建的 PV 绑定。目前支持的绑定模式包括:
- Immediate:表示当一个 PersistentVolumeClaim (PVC)被创建出来时,就动态创建 PV 并进行 PVC 与 PV 的绑定操作。注意:提前绑定可能导致使用 PVC 的 Pod 调度到资源不匹配的 Node 导致 Pod 启动失败。
- WaitForFirstConsumer:表示 PVC 与 PV 的绑定操作延迟到第一个使用 PVC 的 Pod 创建出来时再进行。系统将根据 Pod 的调度需求,在 Pod 所在的Node上创建 PV。
4. 持久化存储方案示例
4.1. hostPath
hostPath 类型的存储卷,使用的是目标节点上的本地目录。
特点:持久化数据在固定节点上,Pod 不能随意调度。
部署示例:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web-test
name: web-test
spec:
replicas: 1
selector:
matchLabels:
app: web-test
template:
metadata:
labels:
app: web-test
spec:
nodeName: k8s-node-01-c-211 # 需要固定调度到某个节点才行,否则重启 pod 调度到别的节点了就找不到 hostPath
containers:
- image: nginx:1.26.2
name: nginx
# 挂载到容器内
volumeMounts:
- name: test-volume
mountPath: /usr/share/nginx/html
volumes:
- name: test-volume
hostPath:
path: /test-data # 宿主机目录
# type: Directory # 表示该目录必须存在
type: DirectoryOrCreate # 表示目录不存在则会自动创建
YAML测试:
# 在挂载目录创建测试文件
echo wwwwww > index.html
# 访问 pod ip
curl 10.244.2.62
# 显示结果
wwwwww
ShellScript4.2. Local PV
在 hostPath 的基础上,k8s 依靠 pv 与 pv c实现了一个新特性叫 Local PV。
注意:Local pv 对应的存储介质应该是一块额外挂载到宿主机上的磁盘或者块设备,避免与宿主机文件共用一个磁盘。否则会导致磁盘空间管理不可控。
创建 PV:
# local-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /local-pv-data # k8s-node-01-c-211节点上的目录,该目录强烈建议挂载一块单独的磁盘,该目录需要手动创建
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node-01-c-211
YAML创建目录:
mkdir /local-pv-data
ShellScript接下了不能直接创建 PVC 与 PV 绑定,如果上述 PV 创建了多个且亲和不同的节点,则直接创建的 PVC 会随机选择一个 PV 绑定。再创建 Pod 后使用该 PVC 的 Pod 会调度到绑定的 PV 亲和的节点上,如何该节点资源不足,则 Pod 会启动失败。需要通过 storageClass 对象实现延迟绑定。
创建 storageClass:
# local-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner # 指定为 no-provisioner 代表我们是手动创建的 PV
volumeBindingMode: WaitForFirstConsumer
YAML创建 PVC:
# local-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-storage
YAML创建 Pod:
# pod-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web-test
name: web-test
spec:
replicas: 1
selector:
matchLabels:
app: web-test
template:
metadata:
labels:
app: web-test
spec:
containers:
- image: nginx:1.26.2
resources:
requests:
cpu: 1
memory: 1.5Gi
name: nginx
# 挂载到容器内
volumeMounts:
- name: test-volume
mountPath: /usr/share/nginx/html
# PVC声明
volumes:
- name: test-volume
persistentVolumeClaim:
claimName: local-pvc
YAML上面的方案只能手动创建 PV ,下面推荐一个自动创建 PV 的插件:
由 Rancher 开发的 local-path-provisioner 这个存储方案允许 Kubernetes 用户在每个节点上使用本地存储,对外提供一个 storageclass 默认名为 local-path。
4.3. NFS 共享存储
4.3.1. 手动创建 PV
在存储节点安装 nfs 服务端:
# 内网环境可以直接关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
# 安装
yum install -y nfs-utils rpcbind
# 创建数据共享目录
mkdir -p /data/nfs
chmod 755 /data/nfs
# 配置共享目录
cat > /etc/exports <<EOF
/data/nfs *(rw,sync,no_root_squash)
EOF
# *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
# rw:读写的权限
# sync:表示文件同时写入硬盘和内存
# no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份
# 启动nfs服务
systemctl start rpcbind.service
systemctl enable rpcbind
systemctl status rpcbind
systemctl start nfs
systemctl enable nfs
systemctl status nfs
ShellScriptk8s 集群节点上安装客户端:
sudo yum install -y nfs-utils
sudo apt install -y nfs-common
ShellScript创建基于 nfs 的 PV:
# nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/nfs # 指定 nfs 的挂载目录
server: 192.168.2.50 # 指定 nfs 服务地址
YAML创建 PVC:
# nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
YAML测试 Pod:
# nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-volumes
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: web
image: nginx:1.26.2
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
subPath: test-volumes
mountPath: '/usr/share/nginx/html'
YAML4.3.2. 动态创建 PV
插件官网:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
安装:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
# 镜像地址可以更换为自己的
helm upgrade --install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=192.168.2.50 --set nfs.path=/data/nfs --set storageClass.defaultClass=true --set image.repository=crpi-sozjkv641zbs4m9x.cn-shenzhen.personal.cr.aliyuncs.com/pingk-k8s-test/nfs-subdir-external-provisioner --set image.tag=v4.0.2 -n kube-system
# 详细的安装可以通过 values.yaml 文件配置安装
ShellScript常用 StorageClass 配置命令:
# 关闭默认
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
# 设置为默认
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# StorageClass 回收策略不支持修改,只能重新创建
ShellScript