k8s数据持久化常见的方案解读

k8s数据持久化常见的方案解读

软件版本
docker最新版
kubernetes1.23.1
calico3.25
节点IP系统功能CPU内存硬盘
node110.80.10.1centos7.9k8s-master4核心8GB20GB
node210.80.10.2centos7.9k8s-node4核心8GB20GB

node1

查看支持存储类型:

1
# kubectl explain pod.spec.volumes

常用类型:

  • emptydir。

  • hostpath。

  • nfs。

  • persistentvolumeclaim。

  • glusterfs。

  • cephfs。

  • configmap。

  • secret。

使用存储卷的步骤:

  • 定义pod的volume,这个volume指明它要关联到哪个存储上的。

  • 在容器中要使用volumemounts挂载对应的存储

k8s持久化存储emptydir:

emptydir类型的volume是在pod分配到Node上时被创建,kubernetes会在node上自动分配一个目录,因此无需指定宿主机node上对应的目录文件。 这个目录的初始内容为空,当pod从node上移除时,emptydir中的数据会被永久删除。emptydir volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mkdir /root/volume && cd /root/volume
# vim emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-empty
spec:
containers:
- name: container-empty
image: nginx:1.19.10
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- emptyDir:
{}
name: cache-volume
1
2
3
4
# kubectl apply -f emptydir.yaml
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-empty 1/1 Running 0 23s 10.244.36.102 k8s-node1 <none> <none>

查看pod的uid:

1
2
# kubectl get pods pod-empty -o yaml | grep uid
uid: 411b132f-3354-4e64-a45e-108ba3f237c7

node2

查看临时目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# tree /var/lib/kubelet/pods/411b132f-3354-4e64-a45e-108ba3f237c7/
/var/lib/kubelet/pods/411b132f-3354-4e64-a45e-108ba3f237c7/
├── containers
│   └── container-empty
│   └── 0d44b53f
├── etc-hosts
├── plugins
│   └── kubernetes.io~empty-dir
│   ├── cache-volume
│   │   └── ready
│   └── wrapped_kube-api-access-fjbm5
│   └── ready
└── volumes
├── kubernetes.io~empty-dir
│   └── cache-volume
└── kubernetes.io~projected
└── kube-api-access-fjbm5
├── ca.crt -> ..data/ca.crt
├── namespace -> ..data/namespace
└── token -> ..data/token

11 directories, 7 files

创建数据:

1
# echo aa > /var/lib/kubelet/pods/411b132f-3354-4e64-a45e-108ba3f237c7/volumes/kubernetes.io~empty-dir/cache-volume/aa.txt

node1

查看pod里面的数据,删除后目录不存在:

1
2
# kubectl exec -it pod-empty -- /bin/cat /cache/aa.txt
aa
1
# kubectl delete -f emptydir.yaml

k8s持久化存储hostpath:

hostpath volume是指pod挂载宿主机上的目录或文件。 hostpath volume使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# vim hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- image: nginx:1.19.10
imagePullPolicy: IfNotPresent
name: test-nginx
volumeMounts:
- mountPath: /test-nginx
name: test-volume
- image: tomcat:8.5.34-jre8
imagePullPolicy: IfNotPresent
name: test-tomcat
volumeMounts:
- mountPath: /test-tomcat
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data1
type: DirectoryOrCreate
1
2
3
4
# kubectl apply -f hostpath.yaml
# kubectl get pods
NAME READY STATUS RESTARTS AGE
test-hostpath 2/2 Running 0 78s

node2

自动创建了目录:

1
2
# ls -d /data1/
/data1/

创建目录:

1
2
# cd /data1/
# mkdir lucky123

进入tomcat容器查看:

1
2
3
4
5
6
7
8
# kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash
# cd /test-tomcat/
# ls
lucky123
# mkdir abc
# ls
abc lucky123
# exit

物理机创建了目录:

1
2
# ls
abc lucky123

进入nginx容器查看:

1
2
3
4
# kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
# ls /test-nginx/
abc lucky123
# exit

pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失。

可以用分布式存储:nfs、cephfs、glusterfs

k8s持久化存储nfs:

node1、node2

下载安装nfs:

1
2
3
# yum install -y nfs-utils
# systemctl enable nfs --now
# systemctl status nfs

node1

创建共享目录:

1
2
3
# mkdir -p /data/volumes
# vim /etc/exports
/data/volumes *(rw,no_root_squash)
1
# exportfs -arv

node2

挂载nfs,测试后卸载:

1
2
3
4
5
# mkdir /test
# mount 10.80.10.1:/data/volumes /test/
# df -h | grep test
10.80.10.1:/data/volumes 17G 5.4G 12G 32% /test
# umount /test

node1

创建nfs的pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# vim nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-volume
spec:
containers:
- name: test-nfs
image: nginx:1.19.10
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- name: nfs-volumes
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volumes
nfs:
path: /data/volumes
server: 10.80.10.1
1
2
3
# kubectl apply -f nfs.yaml
# kubectl get pods -o wide | grep nfs
test-nfs-volume 1/1 Running 0 16s 10.244.36.104 k8s-node1 <none> <none>

访问测试:

1
2
3
# echo "nfs test" > /data/volumes/index.html
# curl 10.244.36.104
nfs test

k8s持久化存储pvc:

persistentvolume(pv)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod是k8s集群资源一样。 pv是容量插件,如volumes,其生命周期独立于使用pv的任何单个pod。

persistentvolumeclaim(pvc)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储卷。 它类似于一个pod。pod消耗节点资源,pvc消耗pv资源。pod可以请求特定级别的资源(cpu和内存)。 pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。

k8s pvc和pv工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pv是群集中的资源。 pvc是对这些资源的请求。 
pv和pvc之间的相互作用遵循以下生命周期:

(1)pv的供应方式
可以通过两种方式配置pv:静态或动态。

静态的:集群管理员创建了许多pv。它们包含可供群集用户使用的实际存储的详细信息。它们存在于kubernetes api中,可供使用。
动态的:当管理员创建的静态pv都不匹配用户的persistentvolumeclaim时,群集可能会尝试为pvc专门动态配置卷。此配置基于storageclasses,pvc必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。

(2)绑定
用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。

(3)使用
a)需要找一个存储服务器,把它划分成多个存储空间。
b)k8s管理员可以把这些存储空间定义成多个pv。
c)在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv。
d)pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷。
e)pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了。
f)我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处于pending状态。

(4)回收策略
当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除:
retain
recycle(不推荐使用,1.15可能被废弃了)
delete
retain:当删除pvc的时候,pv仍然存在,处于released状态,但是它不能被其他pvc绑定使用,里面的数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略。
delete:删除pvc时即会从kubernetes中移除pv,也会从相关的外部设施中删除存储资产。

创建共享目录:

1
2
3
4
5
# mkdir -p /data/volume_test/v{1,2}
# vim /etc/exports
# 尾行,添加配置
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
1
# exportfs -arv

创建pv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# vim pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: v1
spec:
capacity:
storage: 1Gi # pv的存储空间容量
accessModes: ["ReadWriteOnce"]
nfs:
path: /data/volume_test/v1 # 把nfs的存储空间创建成pv
server: 10.80.10.1 # nfs服务器的地址
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v2
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteMany"]
nfs:
path: /data/volume_test/v2
server: 10.80.10.1
1
2
3
4
5
# kubectl apply -f pv.yaml
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 12s
v2 2Gi RWX Retain Available 12s

创建pvc,和符合条件的pv绑定,bound表示已经绑定:

1
2
3
4
5
6
7
8
9
10
# vim pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
1
2
3
4
5
6
7
8
# kubectl apply -f pvc.yaml
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 80s
v2 2Gi RWX Retain Bound default/my-pvc 80s
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v2 2Gi RWX 53s

创建pod,挂载pvc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# vim pod_pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc
spec:
containers:
- name: nginx
image: nginx:1.19.10
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nginx-html
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-html
persistentVolumeClaim:
claimName: my-pvc
1
2
3
# kubectl apply -f pod_pvc.yaml
# kubectl get pods | grep pod-pvc
pod-pvc 1/1 Running 0 14s

挂载目录创建文件:

1
2
3
# mkdir /data/volume_test/v2/aa
# kubectl exec -it pod-pvc -- /bin/ls /usr/share/nginx/html
aa

使用pvc和pv的注意事项:

  • 我们每次创建pvc的时候,需要事先有划分好的pv,这样可能不方便,那么可以在创建pvc的时候直接动态创建一个pv这个存储类,pv事先是不存在的。

  • pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。

删除v2的pv:

1
2
3
# kubectl delete -f pod_pvc.yaml
# kubectl delete pvc my-pvc
# kubectl delete pv v2

再次创建pv和pvc和pod,数据还在:

1
2
3
4
5
6
7
8
9
10
11
12
# kubectl apply -f pv.yaml
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 24m
v2 2Gi RWX Retain Available 14s
# kubectl apply -f pvc.yaml
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v2 2Gi RWX 8s
# kubectl apply -f pod_pvc.yaml
# kubectl exec -it pod-pvc -- /bin/ls /usr/share/nginx/html
aa

k8s持久化存储storageclass:

上面介绍的pv和pvc模式都是需要先创建好pv,然后定义好pvc和pv进行一对一的bond,但是如果pvc请求成千上万,那么就需要创建成千上万的pv,对于运维人员来说维护成本很高,kubernetes提供一种自动创建pv的机制,叫storageclass,它的作用就是创建pv的模板。k8s集群管理员通过创建storageclass可以动态生成一个存储卷pv供k8s pvc使用。

每个storageclass都包含字段provisioner,parameters和reclaimpolicy。 具体来说,storageclass会定义以下两部分:

  • pv的属性 ,比如存储的大小、类型等。

  • 创建这种pv需要使用到的存储插件,比如ceph、nfs等。

查看字段:

1
# kubectl explain storageclass

provisioner:供应商,storageclass需要有一个供应者,用来确定我们使用什么样的存储来创建pv。

创建运行nfs-provisioner需要的sa账号:

1
2
3
4
5
# vim serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
1
# kubectl apply -f serviceaccount.yaml

sa的全称是serviceaccount,serviceaccount是为了方便pod里面的进程调用kubernetes api或其他外部服务而设计的。指定了serviceaccount之后,我们把pod创建出来了,我们在使用这个pod时,这个pod就有了我们指定的账户的权限了。

对sa授权:

1
# kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner

安装nfs-provisioner程序:

1
2
3
4
# mkdir -p /data/nfs_pro
# vim /etc/exports
# 尾行,添加配置
/data/nfs_pro *(rw,no_root_squash)
1
# exportfs -arv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# vim nfs-deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-provisioner
spec:
selector:
matchLabels:
app: nfs-provisioner
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: example.com/nfs
- name: NFS_SERVER
value: 10.80.10.1
- name: NFS_PATH
value: /data/nfs_pro
volumes:
- name: nfs-client-root
nfs:
server: 10.80.10.1
path: /data/nfs_pro
1
2
3
# kubectl apply -f nfs-deployment.yaml
# kubectl get pods | grep nfs-provisioner
nfs-provisioner-86877fb775-v5jtg 1/1 Running 0 56s

创建storageclass,动态供给pv:

1
2
3
4
5
6
# vim nfs-storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: nfs
provisioner: example.com/nfs
  • provisioner处写的example.com/nfs应该跟安装nfs provisioner时候的env下的PROVISIONER_NAME的value值保持一致。
1
2
3
4
# kubectl apply -f nfs-storageclass.yaml
# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs example.com/nfs Delete Immediate false 14s

创建pvc,通过storageclass动态生成pv:

1
2
3
4
5
6
7
8
9
10
11
# vim claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim1
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 1Gi
storageClassName: nfs
1
2
3
# kubectl apply -f claim.yaml
# kubectl get pvc | grep claim1
test-claim1 Bound pvc-0c511e5d-cfe6-4020-92b7-867d873cd395 1Gi RWX nfs 6m52s

步骤总结:

  • 创建一个nfs provisioner。

  • 创建storageclass,storageclass指定刚才创建的供应商。

  • 创建pvc,这个pvc指定storageclass。

创建pod,挂载storageclass动态生成的pvc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# vim read-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: read-pod
spec:
containers:
- name: read-pod
image: nginx:1.19.10
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-pvc
mountPath: /usr/share/nginx/html
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim1
1
2
3
# kubectl apply -f read-pod.yaml
# kubectl get pods | grep read
read-pod 1/1 Running 0 13s