上一节中我们学习了volume,但是volume还是不能解决pod被删除后内部数据的持久化问题。而这一节要学习的PerisstentVolume就是专门来解决这个问题的。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
文章目录
Persistent Volume
上一节的volume是pod内部声明的一种数据共享手段,pod删除volume内的数据也永久删除。但是往往有一种需求,就是针对一些有状态的服务(例如statefulset),希望其内部的数据会永久保存,便于下次新的服务起来的时候能读回上次的状态。这时候就需要存储的生命周期不随pod的生命周期结束,于是k8s引入了一个新的组件:Persistent Volume,简称PV,下同。
PV和volume一样,也是一个抽象的接口,后面可以有几十种媒介。官方文档列出了完整清单,例如各种云存储。这一节我们以NFS为例从集群外部引入额外存储来作为实际操作的例子。
Persistent Volume Claim
一个集群中往往声明和创建了很多PV对象,这些对象存储效率各异,存储媒介和大小也不同。而不同的pod所需要的PV类型也不一样,于是产生了一个多对多的匹配关系。为了将这种多对多匹配自动化,k8s引入了叫Persistent Volume Claim的新组件,简称PVC,下同。
PV和PVC的关系如下图所示
负责存储设备的Admin会在集群内布创建很多PV以供使用,开发人员Dev根据业务需求创建出一个需要某种PV的PVC,并且在pod的生成yaml中引用这个PVC。PVC会自动去寻找符合要求的并且占用资源最小的PV以供该pod使用。同时PVC的声明中还是指明PV的使用方式,后面实际操作的时候会看到。
需要注意
- PV和PVC的绑定关系是一一对应的
- pod如果被删除,其PVC做为单独组件并不会消失
StatefulSet
下面开始补一下前面讲控制器时候挖的一个坑,就是提供有状态服务的控制器:StatefulSet。
所谓有状态服务,指的是当pod挂了再创建一个新pod时(注意并不是容器崩溃导致的pod重启),pod还能拥有以下特点
- 固定的持久存储,即pod被重新调度后还能访问到跟之前完全相同的持久化数据,基于PVC来实现
- 固定的网络标识,即pod被重新调度后其在集群内部拥有跟之前一样的DNS标识(注意,并不是相同IP),基于Headless Service来实现
- 有序部署,扩展和删除,控制器下的pod都有自己的编号(0到n-1),前一个pod必须是Running或者Ready状态才会创建下一个pod,删除时反向进行,基于前面讲过的Init C实现
Headless Service
所谓的Headless Service就是在前面学习的ClusterIP的基础上,多加一行clusterIP: None
不指定Service的IP,效果就是对Service域名的DNS解析直接到后端的pod的IP。
用如下的yaml文件test-headless-service.yaml
创建一个Deployment和对应的Headless Service
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mynginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: mynginx
version: v2
spec:
containers:
- name: mynginx
image: mynginx:v2
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: mynginx-service
namespace: default
spec:
type: ClusterIP
clusterIP: None #<-- Careful
selector:
app: mynginx
version: v2
ports:
- name: http
port: 8080
targetPort: 80
pod起来以后对Service进行DNS解析
[root@k8s-master pv]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14d
mynginx-service ClusterIP None <none> 8080/TCP 5s
[root@k8s-master pv]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mynginx-deployment-b66f59f66-rj7xr 1/1 Running 0 4m27s 10.244.0.16 k8s-master <none> <none>
mynginx-deployment-b66f59f66-x4487 1/1 Running 0 4m27s 10.244.1.122 k8s-node1 <none> <none>
mynginx-deployment-b66f59f66-xswls 1/1 Running 0 4m27s 10.244.1.119 k8s-node1 <none> <none>
[root@k8s-master pv]# nslookup mynginx-service.default.svc.cluster.local 10.244.0.2
Server: 10.244.0.2
Address: 10.244.0.2#53
Name: mynginx-service.default.svc.cluster.local
Address: 10.244.0.16
Name: mynginx-service.default.svc.cluster.local
Address: 10.244.1.122
Name: mynginx-service.default.svc.cluster.local
Address: 10.244.1.119
因为k8s中是无法对pod进行DNS解析的,所以必须借助Headless Service实现pod的DNS解析。
实际操作
以下所有yaml文件托管在我的Github仓库
创建NFS
关于NFS的安装和配置和参考我的另一篇博客《Centos7搭建NFS文件共享服务器以及客户端连接》
我这里一共创建了如下3个NFS共享目录备用
- 172.29.56.177:/share/nfs1
- 172.29.56.177:/share/nfs2
- 172.29.56.177:/share/nfs3
因为配置了root_squash,客户端连接到服务端都变为了nfsnobody用户,为了实现可读可写,上面三个目录都更改所有者为nfsnobody。
之后去k8s集群中的所有node查看确保能看到这三个NFS目录如下
[root@k8s-master ~]# showmount -e 172.29.56.177
Export list for 172.29.56.177:
/share/nfs3 172.29.0.0/16
/share/nfs2 172.29.0.0/16
/share/nfs1 172.29.0.0/16
创建NFS的PV
创建pv的几个字段说明一下
字段 | 类型 | 说明 |
---|---|---|
spec.capacity | object | pv的能力,目前只有storage一项,后续会增加IOPS,throughput等 |
spec.volumeMode | string | 默认是Filesystem,表示锚定到pod一个目录,还可换成Block |
spec.accessModes | list | 声明pv支持的访问模式,ReadWriteOnce被单个node以可读写绑定/ReadOnlyMany被多个node以只读绑定/ReadWriteMany被多个node以读写绑定,在命令行简写为RWO/ROX/RWX,这里有各种媒介支持的模式列表 |
spec.storageClassName | string | 给pv指定一个分类的名字,只有请求这个分类的pvc才有可能绑定该pv |
spec.persistentVolumeReclaimPolicy | string | 从pvc解绑时的行为,手动创建的pv默认是Retain继续保留等待手动删除,还可设置Delete直接删除 |
mountOptions | list | pv在锚定到node时候的一些额外选项 |
创建如下yaml文件test-pv-nfs.yaml
文件
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv-nfs1
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: bronze
nfs:
path: /share/nfs1
server: 172.29.56.177
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv-nfs2
spec:
capacity:
storage: 8Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: silver
nfs:
path: /share/nfs2
server: 172.29.56.177
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv-nfs3
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: gold
nfs:
path: /share/nfs3
server: 172.29.56.177
硬盘厂商的GB是以1000为单位,计算机厂商的GBi或者Gi是以1024为单位
成功创造出三个pv
[root@k8s-master pv]# kubectl apply -f test-pv-nfs.yaml
persistentvolume/test-pv-nfs1 created
persistentvolume/test-pv-nfs2 created
persistentvolume/test-pv-nfs3 created
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Available bronze 6s
test-pv-nfs2 8Gi RWO Retain Available silver 6s
test-pv-nfs3 10Gi RWO Retain Available gold 6s
创建StatefulSet
通过如下yaml文件test-statefulset.yaml
创建statefulset和对应的headless service
apiVersion: v1
kind: Service
metadata:
name: mynginx-service
namespace: default
labels:
app: mynginx
spec:
type: ClusterIP
clusterIP: None #<-- Careful
selector:
app: mynginx
ports:
- name: http
port: 8080
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test-statefulset
spec:
selector:
matchLabels:
app: mynginx
serviceName: mynginx-service
replicas: 3
template:
metadata:
labels:
app: mynginx
spec:
containers:
- name: mynginx
image: mynginx:v2
ports:
- containerPort: 80
name: http
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
storageClassName: gold
resources:
requests:
storage: 5Gi
注意这里最后的PVC部分,其中storageClassName: gold
只有一个pv满足条件。下面创建看看
[root@k8s-master pv]# kubectl apply -f test-statefulset.yaml
service/mynginx-service unchanged
statefulset.apps/test-statefulset created
[root@k8s-master pv]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-statefulset-0 1/1 Running 0 11s 10.244.1.123 k8s-node1 <none> <none>
test-statefulset-1 0/1 Pending 0 7s <none> <none> <none> <none>
会发现只有一个pod被创建成功,第二个一直在pending,查看下详细信息
[root@k8s-master pv]# kubectl describe pod test-statefulset-1
Name: test-statefulset-1
Namespace: default
...
...
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 28s default-scheduler pod has unbound immediate PersistentVolumeClaims (repeated 2 times)
这个pod因为没有满足PVC的PV而一直无法被创建。对比发现PV的容积大小和读写类型都满足,只是class不满足
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Available bronze 7m24s
test-pv-nfs2 8Gi RWO Retain Available silver 7m24s
test-pv-nfs3 10Gi RWO Retain Bound default/www-test-statefulset-0 gold 7m24s
下面修改下,将剩余两个PV也改为gold
看看
[root@k8s-master pv]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-statefulset-0 1/1 Running 0 10m 10.244.1.123 k8s-node1 <none> <none>
test-statefulset-1 1/1 Running 0 10m 10.244.1.124 k8s-node1 <none> <none>
test-statefulset-2 1/1 Running 0 89s 10.244.1.125 k8s-node1 <none> <none>
会发现pod被逐渐创建出来,而pv和pvc也都被绑定上
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Bound default/www-test-statefulset-1 gold 11m
test-pv-nfs2 8Gi RWO Retain Bound default/www-test-statefulset-2 gold 11m
test-pv-nfs3 10Gi RWO Retain Bound default/www-test-statefulset-0 gold 11m
[root@k8s-master pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-test-statefulset-0 Bound test-pv-nfs3 10Gi RWO gold 11m
www-test-statefulset-1 Bound test-pv-nfs1 5Gi RWO gold 11m
www-test-statefulset-2 Bound test-pv-nfs2 8Gi RWO gold 2m46s
注意,这里是按照0-2的顺序依次有序启动的。
关于statefulset有下面几点说明:
- pod的名称为<statefulset名称>-<序列号>,序列号为0到n-1
- headless service为每个pod创建了一个独立的域名,格式为<pod_name>.<headless_service_name>.NAMESPACE.svc.cluster.local
- pvc的命名规则为<pvc_name>-<pod_name>
- 删除pod不会删除其pvc,手动删除pvc会自动删除pv
验证持久化存储
下面对www-test-statefulset-0
来验证持久化存储,其对应的pv是test-pv-nf3
,也就是nfs服务器上的/share/nfs3
目录。
写入内容到nfs服务器上,可以直接在容器内看到
echo hello pod1 > index.html
[root@k8s-master pv]# curl 10.244.1.123
hello pod1
这时删除www-test-statefulset-0
这个pod,发现其IP改变了,但是之前写入的数据还在
[root@k8s-master pv]# kubectl delete pod/test-statefulset-0
pod "test-statefulset-0" deleted
[root@k8s-master pv]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-statefulset-0 1/1 Running 0 4s 10.244.1.127 k8s-node1 <none> <none>
test-statefulset-1 1/1 Running 0 7h51m 10.244.1.124 k8s-node1 <none> <none>
test-statefulset-2 1/1 Running 0 7h42m 10.244.1.125 k8s-node1 <none> <none>
[root@k8s-master pv]# curl 10.244.1.127
hello pod1
再次强调,并不是pod的IP不会变,而是集群内的域名不会变
验证域名解析
Headless service为每个pod创建了唯一的域名,格式为<pod_name>.<headless_service_name>.NAMESPACE.svc.cluster.local,验证一下
[root@k8s-master pv]# nslookup test-statefulset-0.mynginx-service.default.svc.cluster.local 10.244.0.2
Server: 10.244.0.2
Address: 10.244.0.2#53
Name: test-statefulset-0.mynginx-service.default.svc.cluster.local
Address: 10.244.1.127
[root@k8s-master pv]# nslookup test-statefulset-1.mynginx-service.default.svc.cluster.local 10.244.0.2
Server: 10.244.0.2
Address: 10.244.0.2#53
Name: test-statefulset-1.mynginx-service.default.svc.cluster.local
Address: 10.244.1.124
[root@k8s-master pv]# nslookup test-statefulset-2.mynginx-service.default.svc.cluster.local 10.244.0.2
Server: 10.244.0.2
Address: 10.244.0.2#53
Name: test-statefulset-2.mynginx-service.default.svc.cluster.local
Address: 10.244.1.125
即使删除pod在被分配,即使ip变了,这个域名也不会变
[root@k8s-master pv]# kubectl delete pod/test-statefulset-1
pod "test-statefulset-1" deleted
[root@k8s-master pv]# nslookup test-statefulset-1.mynginx-service.default.svc.cluster.local 10.244.0.2
Server: 10.244.0.2
Address: 10.244.0.2#53
Name: test-statefulset-1.mynginx-service.default.svc.cluster.local
Address: 10.244.0.17
这样其余service或者pod想要访问statefulset中的pod,就可以访问其域名,而不用在意后端ip的改变。
彻底删除
不管是删除pod还是statefulset,持久化的数据都不会消失。那么如果有一天真的想彻底销毁这些数据该如何操作呢?
首先删除statefulset,直接利用yaml文件即可
[root@k8s-master pv]# kubectl delete -f test-statefulset.yaml
service "mynginx-service" deleted
statefulset.apps "test-statefulset" deleted
[root@k8s-master pv]# kubectl get po
No resources found.
[root@k8s-master pv]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14d
但是此时pv和pvc都还在
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Bound default/www-test-statefulset-1 gold 8h
test-pv-nfs2 8Gi RWO Retain Bound default/www-test-statefulset-2 gold 8h
test-pv-nfs3 10Gi RWO Retain Bound default/www-test-statefulset-0 gold 8h
[root@k8s-master pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-test-statefulset-0 Bound test-pv-nfs3 10Gi RWO gold 8h
www-test-statefulset-1 Bound test-pv-nfs1 5Gi RWO gold 8h
www-test-statefulset-2 Bound test-pv-nfs2 8Gi RWO gold 8h
然后删除pvc,之后pv变为Released
状态
[root@k8s-master pv]# kubectl delete pvc --all
persistentvolumeclaim "www-test-statefulset-0" deleted
persistentvolumeclaim "www-test-statefulset-1" deleted
persistentvolumeclaim "www-test-statefulset-2" deleted
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Released default/www-test-statefulset-1 gold 8h
test-pv-nfs2 8Gi RWO Retain Released default/www-test-statefulset-2 gold 8h
test-pv-nfs3 10Gi RWO Retain Released default/www-test-statefulset-0 gold 8h
此时这些pv还不能变为Available
,需要手动删除pv中的pvc信息,也就是claimRef
部分
[root@k8s-master pv]# kubectl edit pv/test-pv-nfs1
persistentvolume/test-pv-nfs1 edited
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Available gold 8h
test-pv-nfs2 8Gi RWO Retain Released default/www-test-statefulset-2 gold 8h
test-pv-nfs3 10Gi RWO Retain Released default/www-test-statefulset-0 gold 8h
依次删除3个pv中的pvc信息一直到都为Available
状态
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
test-pv-nfs1 5Gi RWO Retain Available gold 8h
test-pv-nfs2 8Gi RWO Retain Available gold 8h
test-pv-nfs3 10Gi RWO Retain Available gold 8h
之后再删除nfs服务器中的文件即可
总结
总结下本节的知识点
- pv用来声明可供使用的存储媒介,pvc用来声明所需要的存储媒介并自动绑定符合条件的pv
- statefulset利用headless service和pvc来达到有状态服务的目的,也就是固定存储和固定网络标识(注意不是ip),删除pod或者statefulset都不会改变。同时pod的创建和销毁都是有顺序的
- 要彻底将pv重复利用,需要手动在pv的配置里面删除claimRef部分的内容
学习了volume和pv,k8s集群中的存储就都学习完了。不过这里举例子只是用了nfs存储这一种后端存储媒介,还有很多种大家可以在实际使用中多多尝试。