【Kubernetes 014】持久存储PersistentVolume原理以及配合StatefulSet实现有状态服务实际操作详解

上一节中我们学习了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的关系如下图所示
1-pv.png

负责存储设备的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.capacityobjectpv的能力,目前只有storage一项,后续会增加IOPS,throughput等
spec.volumeModestring默认是Filesystem,表示锚定到pod一个目录,还可换成Block
spec.accessModeslist声明pv支持的访问模式,ReadWriteOnce被单个node以可读写绑定/ReadOnlyMany被多个node以只读绑定/ReadWriteMany被多个node以读写绑定,在命令行简写为RWO/ROX/RWX,这里有各种媒介支持的模式列表
spec.storageClassNamestring给pv指定一个分类的名字,只有请求这个分类的pvc才有可能绑定该pv
spec.persistentVolumeReclaimPolicystring从pvc解绑时的行为,手动创建的pv默认是Retain继续保留等待手动删除,还可设置Delete直接删除
mountOptionslistpv在锚定到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存储这一种后端存储媒介,还有很多种大家可以在实际使用中多多尝试。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值