(vms21)192.168.26.21——master1
(vms22)192.168.26.22——worker1
(vms23)192.168.26.23——worker2
(vms24)192.168.26.24——NFS
如下这样一个pod的yaml:pod1.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: OnFailure
status: {}
我们在将这个pod创建出来
kubectl apply -f pod1.yaml
查看该pod在哪个节点上运行
kubectl get pods -o wide
可以看到它在节点vms23上运行
我们进入pod并创建aaa.txt
kubectl exec -it pod1 -- bash
touch aaa.txt
我们来到节点23上,通过文件名查找,能找到该文件
find / -name aaa.txt
此时我们将pod1删除
kubectl delete pod pod1
删除后,再回到vms23查找aaa.txt,就会发现该文件也被删除了
这其实和容器一样,若没有使用数据卷,随着容器的删除,数据就会丢失
因此我们也可以在pod中定义数据卷
定义方法:
在pod的yaml文件中定义
1.在spec下定义pod里的卷
2.在spec.containers下,容器引用该卷
在spec下定义pod里的卷,卷的类型有emptyDir、hostPath、NFS等
#没参数时
volumes:
- name: [卷名]
[卷类型]: {}
#有参数时
volumes:
- name: [卷名]
[卷类型]:
[参数1]: 值1
[参数2]: 值2
#定义多个卷
volumes:
- name: 卷1
...
- name: 卷2
...
- name: 卷3
...
在容器内引用卷
可以设置readOnly是否只读,若不设置则默认为false,设置为true则容器会以只读的形式挂载,将无法在这个卷内写入数据/文件
volumeMounts:
- name: [关联pod里定义的卷名]
mountPath: [容器内的卷挂载点]
#readOnly: true
emptyDir类型的卷就类似于容器指定数据卷时只指定一个目录(只指定容器内数据卷目录,宿主机中则随机生成一个目录与之映射)的情况
定义emptyDir类型卷:
emptyDir无需参数,因此后面直接是“{}”,即没有指定宿主机数据卷路径,将会在宿主机中随机生成一个目录
...
spec:
volumes:
- name: v1
emptyDir: {}
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
volumeMounts:
- name: v1
mountPath: /data
#readOnly: true
...
创建这个pod,然后我们可以试着进入pod内的容器,并在/data中创建一个文件,然后我们删除这个pod,会发现数据会被一起删除,那么这个emptyDir的意义何在呢?
emptyDir的应用场景:
如下一个节点,里有两个容器,一个主容器,一个辅容器(sidecar)
主容器里主要负责的服务是转码,但是没有提供好的日志分析,日志分析就由sidecar来负责,这些日志不需要永久保留,日志随着pod删除时一起删除就行了,这时emptyDir就很符合这个需要
不同于emptyDir,hostPath可以永久保留数据,并且可以指定宿主机的卷目录,创建pod时,会在相应节点上创建该目录
定义hostPath类型卷:
可以添加一个参数path来指定宿主机卷目录
...
spec:
volumes:
- name: v1
hostPath:
path: [宿主机卷目录]
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
volumeMounts:
- name: v1
mountPath: /data
#readOnly: true
...
使用hostPath也会有一个问题,因为我们目的是想使得数据持久化,使用hostPath,虽然在删除pod后,数据依然能够保留在宿主机(节点)中,但是再次创建这个pod的时候,在不指定的情况下,我们不能保证该pod还会在上次的节点中创建,若不是在上次的节点上,那么之前的数据依然等于是丢失的(各节点间的数据没有同步)
要同步多个节点之间的数据也有一些方法,如:
a.手动同步 rsync
b.搭建一个共享存储,每个节点都挂载上来,即NFS,下面介绍NFS
NFS共享存储会将共享目录自动与各个节点内的一个存储目录挂载起来,当创建pod的时候,就会类似hostPath一样,将pod内的容器与节点的存储目录挂载起来,要使用NFS,所有的worker节点上都必须要安装客户端工具
环境准备:准备一台CentOS7虚拟机(vms24.rhce.cc),ip地址为:192.168.26.24,作为NFS服务端
搭建NFS环境:
所有worker节点及NFS服务端都安装客户端工具nfs-utils
yum install nfs-utils -y
vms24上开启nfs服务端进程,并设为开机自启动
systemctl enable nfs-server --now
vms24上创建一个目录/aa,作为共享存储目录
mkdir /aa
编辑/etc/exports
vim /etc/exports
#插入以下内容
#“*”表示允许所有客户端访问,“()”里的内容表示有哪些权限
#no_root_squash 若不写这项,则虽然在节点上时是以root身份访问挂载点,但在nfs服务端,不认为你是root,会压缩权限,加上no_root_squash则在nfs服务端上同样以root身份访问
/aa *(rw,no_root_squash)
保存后,重新加载这个文件
exportfs -arv
在worker节点上测试
showmount -e 192.168.26.24
测试所有worker节点是否能挂载共享目录
mount 192.168.26.24:/aa /mnt
在节点上查看挂载
mount | grep 192
然后卸载
umount /mnt
现在创建一个pod,使用nfs的存储卷类型
数据卷类型为nfs,server定义nfs服务端的地址,path定义nfs服务端的共享目录位置
yaml文件如下:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
volumes:
- name: v1
nfs:
server: 192.168.26.24
path: /aa
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
volumeMounts:
- name: v1
mountPath: /data
dnsPolicy: ClusterFirst
restartPolicy: OnFailure
status: {}
将这个pod创建出来
kubectl apply -f xxx.yaml
进入pod内容器bash,输入“df -hT”
kubectl exec -it pod1 -- bash
df -hT
可以看到,似乎感觉上是把共享目录/aa与容器内/data挂载起来了
其实共享目录是先与宿主机(worker节点)先挂载起来,然后宿主机与容器挂载起来的
查看该pod在哪个节点上创建
kubectl get pods -owide
我们来到vms23,然后查看挂载信息
mount | grep 192
可以看到挂载到宿主机的目录位置
这样一来,各个worker节点之间都可以同步共享目录里的数据了
NFS缺点:
k8s中有不同的命名空间,不同的用户访问到不同的命名空间,用户1创建一个pod要使用一个挂载点,用户2创建一个pod也想要创建一个挂载点,这时,NFS服务端管理员创建了一个共享目录1给用户1使用,创建了一个共享目录2给用户2使用,那么用户越来越多,共享目录也越来越多,那么让管理员来处理这么多的创建、管理共享目录是不靠谱的
因此,更好的方案是动态卷供应,动态卷供应是基于持久性存储来实现的
在一个k8s环境里,有两个命名空间,命名空间1和命名空间2,用户1登录命名空间1,用户2登录命名空间2
配置了一个NFS共享存储
管理员创建一个pv(persistentVolume)持久性卷,这个pv是全局的,不属于任何命名空间,所有命名空间都能访问这个pv
pv与NFS服务端的共享目录关联
各个用户并不直接访问NFS共享存储,而是在自己的命名空间创建一个pvc(persistentvolumeClaim)持久性卷声明,pvc是基于命名空间的,pvc与pv关联
pvc与pv是一对一关联,一个pv不能同时关联多个pvc,若此时命名空间1的pvc与pv关联了,则其他命名空间的pvc此时就不能与这个pv关联了(Pending),只有在命名空间1的pvc与pv解除关联了,其他命名空间的pvc才可以关联这个pv
master上创建pv:
通过yaml文件创建,可以参考k8s官方模板
yaml文件如下
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv
#labels:
#xx: xx
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
#persistentVolumeReclaimPolicy: Recycle
#storageClassName: slow
nfs:
path: /aa
server: 192.168.26.24
metadata.labels——定义标签
capacity.storage——定义这个pv提供多大的容量
volumeMode——定义存储系统类型
accessModes——访问类型:
ReadWriteOnce:同时只有一个节点以读写的方式去挂载
ReadOnlyMany:同时多个节点以只读的方式去挂载
ReadWriteMany:同时多个节点以读写的方式去挂载
ReadWriteOncePod:同时只允许一个pod以读写的方式去挂载
persistentVolumeReclaimPolicy——镜像的回收策略:
Recycle:会删除数据,生成一个pod回收数据,删除pvc之后,pv可以复用,pv状态由Released变为Available
Retain:不回收数据,但是删除pvc之后,pv依然不可用,pv状态长期保持为Released
Delete:当使用如阿里云作为云磁盘时,删除云磁盘里的数据
persistentVolumeReclaimPolicy在1.24之后废弃了,默认使用Retain(保留)
nfs——这里定义后的端存储类型,如hostPath等,这里为nfs,所以写nfs,path定义共享目录,server定义服务端地址
在master上创建该pv
kubectl apply -f xxx.yaml
查看该pv
kubectl get pv
查看pv与共享目录的关系信息
kubectl describe pv mypv
master上创建pvc:
通过yaml文件创建,参考模板如下
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 3Gi
#selector:
#matchLabels:
#xx: "xx"
selector.matchLabels——选择器,通过标签匹配,若配置后,该pvc只会关联到含有标签xx=xx的pv上
创建该pvc
kubectl apply -f xxx.yaml
查看该pvc
kubectl get pvc
可以看到该pvc已经关联上了名为mypv的这个pv
那么没有指定时,它是怎么关联上的呢?
有这么几个规则:
(1)pv与pvc定义的accessModes要一致,若不一致则无法匹配
(2)pv里的capacity.storage容量大小要 >= pvc里request.storage的大小,否则无法匹配
(3)若有标签,会有标签控制
如上,mypvc已经和mypv关联上了,此时若把mypvc删除了,此时这个pv的状态会是Released,再次创建mypvc与mypv关联,就会失败(Pending),这是因为回收策略Retain的关系(当删除pvc后,pv里该pvc的数据是不会被删除的,pv与该pvc的关联关系并没有被解除),只有pv状态为Available时,才能让pvc和其匹配
kubectl delete pvc mypvc
kubectl get pv
kubectl apply -f mypvc.yaml
kubecty get pvc
此时,只有先删除pv,再次创建pv和pvc,pv状态为Available,才能关联上
kubectl delete pvc mypvc
kubectl delete pv mypv
kubectl apply -f mypv.yaml ; kubectl apply -f mypvc.yaml
pv与pvc关联后,pod中使用pvc
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
volumes:
- name: v1
persistentVolumeClaim:
claimName: mypvc
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
volumeMounts:
- name: v1
mountPath: /data
dnsPolicy: ClusterFirst
restartPolicy: OnFailure
status: {}
pod的yaml文件中spec.volumes下,使用persistentVolumeClaim作为卷类型,并用claimName定义使用的pvc名
持续性存储缺点:需要先创建pv、创建pvc,非常麻烦,因此有更好的方案,即动态卷供应
使用动态卷供应,需要在我们的k8s环境里去创建一个个的分配器(制备器Provisioner)
不同的分配器关联到不同的后端存储中去
创建一个sc(storageClass),这个sc是全局的,不同的命名空间都能访问到
sc中要指定使用的是哪个分配器,即一个sc要是与一个分配器关联在一起的
创建一个pvc,一个pvc要与一个sc关联在一起,当创建pvc的时候,sc会自动帮我们创建一个pv,这个pv与这个pvc关联
综上,sc关联的分配器使用的后端存储是什么,那么pv就与这个后端存储关联在一起
因此管理员只要创建好sc后就不用管pv了,用户创建pvc的时候,sc会自动创建出来一个pv和这个pvc绑定
若我们后端存储使用NFS,但是k8s官方并没有内置NFS的分配器,因此我们需要使用第三方的分配器
nfs-subdir-external-provisioner的下载和安装
下载kubernetes-sigs/nfs-subdir-external-provisioner
地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
点击tags,下载最新版本,这里下载了nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.16.tar.gz
在master的root下创建nfs目录,并进入改目录,将下载的nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.16.tar.gz文件复制进来
mkdir nfs
cd nfs
解压该文件
tar zxf /root/nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.16.tar.gz
ls查看文件,并进入nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.16
cd nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.16/
ls查看文件,进入deploy
cd deploy
ls查看文件,可以看到以下这些文件
class.yaml deployment.yaml objects rbac.yaml test-claim.yaml test-pod.yaml
其中:
rbae.yaml——这个文件中可以去创建一些权限
deployment.yaml——我们可以通过这个文件查看需要什么镜像,并且通过这个文件来创建分配器
grep image deployment.yaml
#输出:
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
可以看到需要用到的镜像是:k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner
国内无法访问k8s.gcr.io,所以我这里科学上网下载了
master节点和所有worker节点上都下载这个镜像
nerdctl pull k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
deployment.yaml是用来给我们创建分配器的,我们查看该文件,并需要修改几个地方
vim deployment.yaml
deployment.yaml如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
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: k8s.gcr.io/sig-storage/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.26.24
- name: NFS_PATH
value: /aa
volumes:
- name: nfs-client-root
nfs:
server: 192.168.26.24
path: /aa
spce.template.spce.containers.env下定义的这几个变量:
PROVISIONER_NAME——分配器名字,下面的value即分配器名字
NFS_SERVER——给分配器指定的后端存储的服务端地址
NFS_PATH——后端存储的目录地址
修改的地方:
a.将NFS_SERVER改为192.168.26.24、将NFS_PATH改为/aa
b.将spce.template.spce.volumes.nfs下的server改为192.168.26.24、将path改为/aa
部署:
确保rabc.yaml、deployment.yaml的命名空间都在default下
grep namespace rabc.yaml
grep namespace deployment.yaml
创建权限
kubectl apply -f rbac.yaml
#输出:
#serviceaccount/nfs-client-provisioner created
#clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
#clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
#role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
#rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
创建分配器
kubectl apply -f deployment.yaml
查看default命名空间下的pod,检查分配器是否创建成功
kubectl get pods -n default
#输出:
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-74b779d448-j8m67 1/1 Running 0 15s
创建sc:
回到deploy目录下,找到class.yaml,通过这个文件来创建sc
首先可以编辑class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysc
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
metadata下的name即sc名称,可以自定义修改
provisioner为分配器名称,必须与deployment.yaml中env配置的PROVISIONER_NAME匹配
然后创建
kubectl apply -f class.yaml
通过kubectl get sc #storageclass简写为sc
可以看到sc已经创建出来了
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
mysc k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 5s
sc和分配器都部署好了,现在就可以尝试来创建pvc了,创建了pvc,sc就会自动帮我们创建一个pv来进行关联
首先,我们需要在pvc的yaml文件中spec下增加storageClassName来指定关联的sc
编辑yaml如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 3Gi
storageClassName: mysc
创建pvc
kubectl apply -f mypvc.yaml
创建了pvc后,sc就自动帮我们创建了pv,并绑定
kubectl get pvc
#输出:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pvc-3e807a34-003f-46c9-8e8c-30b1ec6dfe4b 3Gi RWO mysc 7s
kubectl get pv
#输出:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3e807a34-003f-46c9-8e8c-30b1ec6dfe4b 3Gi RWO Delete Bound default/mypvc mysc 15s
查看pv挂载的后端存储,可以看到Source中的server、path信息
kubectl describe pv pvc-3e807a34-003f-46c9-8e8c-30b1ec6dfe4b
#输出:
Name: pvc-3e807a34-003f-46c9-8e8c-30b1ec6dfe4b
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: k8s-sigs.io/nfs-subdir-external-provisioner
Finalizers: [kubernetes.io/pv-protection]
StorageClass: mysc
Status: Bound
Claim: default/mypvc
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 3Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.26.24
Path: /aa/default-mypvc-pvc-3e807a34-003f-46c9-8e8c-30b1ec6dfe4b
ReadOnly: false
Events: <none>
sc环境中,用户删除了pvc,pv也会随之删除
定义sc的class.yaml中也有很多的配置项,可以通过kubectl explain sc
查看更多的配置项
其中,allowVolumeExpansion这个配置项,设置为true,则可以允许在线修改pvc的requests.storage的大小,也就可以扩展pv的容量大小
编辑class.yaml
kubectl edit sc
添加配置项allowVolumeExpansion为true
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"mysc"},"parameters":{"archiveOnDelete":"false"},"provisioner":"k8s-sigs.io/nfs-subdir-external-provisioner"}
creationTimestamp: "2022-07-05T03:55:01Z"
name: mysc
resourceVersion: "850018"
uid: 7c77e5a3-098c-49d0-8d97-c639a1f13a8d
parameters:
archiveOnDelete: "false"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate