k8s 存储卷与数据持久化

1.存储卷

1.1 存储卷概述

因为pod本身具有生命周期,容器内部的数据是不能永久存储的,有可能某一个pod出现问题后,重新启动的容器就不会在有这个数据的。 存储卷是定义在 Pod 上面被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身 是否支持持久机制。

1.2 K8S服务运行状态

无状态服务、普通有状态服务和有状态集群服务。

1.2.1 无状态服务

K8S使用RC(或更新的Replica Set)来保证一个服务的实例数量,如果说某个Pod实例由于某种原因Crash了,RC会立刻用这个Pod的模版新启一个Pod来替代它,由于是无状态的服务,新启的Pod与原来健康状态下的Pod一模一样。在Pod被重建后它的IP地址可能发生变化,为了对外提供一个稳定的访问接口,K8S引入了Service的概念重点内容。一个Service后面可以挂多个Pod,实现服务的高可用。

1.2.2 普通有状态服务

和无状态服务相比,它多了状态保存的需求。Kubernetes提供了以Volume和Persistent Volume为基础的存储系统,可以实现服务的状态保存。

1.2.3 有状态集群服务

与普通有状态服务相比,它多了集群管理的需求。K8S为此开发了一套以Pet Set为核心的全新特性,方便了有状态集群服务在K8S上的部署和管理。

1.3 K8S存储系统

K8S的存储系统从基础到高级又大致分为三个层次:普通Volume,Persistent Volume 和动态存储供应(dynamic provisioning)。

1.3.1 普通Volum&单节点Volume

单节点Volume是最简单的普通Volume,它和Docker的存储卷类似,使用的是Pod所在K8S节点的本地目录。具体有两种,一种是 emptyDir,是一个匿名的空目录,由Kubernetes在创建Pod时创建,删除Pod时删除。另外一种是 hostPath,与emptyDir的区别是,它在Pod之外独立存在,由用户指定路径名。这类和节点绑定的存储卷在Pod迁移到其它节点后数据就会丢失,所以只能用于存储临时数据或用于在同一个Pod里的容器之间共享数据。

1.3.2 跨节点存储卷

这种存储卷不跟某个具体的K8S节点绑定,而是独立于K8S节点存在的,整个存储集群和K8S集群是两个集群,相互独立。
普通volume目前支持的各种存储插件及情况如下:image.png

1.3.3 Persistent Volume

pv(持久存储卷)一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过Persistent Volume Claim(PV索取),简称PVC来实现动态绑定。Pod定义里指定的是PVC,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用。

1.3.4 K8S存储绑定的概念

用户根据所需存储空间大小和访问模式创建(或在动态部署中已创建)一个PersistentVolumeClaim。
Kubernetes的Master节点循环监控新产生的PVC,找到与之匹配的PV(如果有的话),并把他们绑定在一起。
动态配置时,循环会一直将PV与这个PVC绑定,直到PV完全匹配PVC。避免PVC请求和得到的PV不一致。绑定一旦形成,PersistentVolumeClaim绑定就是独有的,不管是使用何种模式绑定的。
如果找不到匹配的Volume,用户请求会一直保持未绑定状态。在匹配的Volume可用之后,用户请求将会被绑定。比如,一个配置很多50Gi PV的集群不会匹配到一个要求100Gi的PVC。只有在100Gi PV被加到集群之后,这个PVC才可以被绑定。

1.3.5 PV的访问模式

ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载。
ReadOnlyMany:可以以只读的方式被多个Pod挂载。
ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是NFS。
在PVC绑定PV时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式,在CLI下,访问方式被简写为:
RWO – ReadWriteOnce
ROX – ReadOnlyMany
RWX – ReadWriteMany
image.png
PV的生命周期,首先是Provision,即创建PV,这里创建PV有两种方式,静态和动态。
静态PV
管理员手动创建一堆PV,组成一个PV池,供PVC来绑定。
动态PV
在现有PV不满足PVC的请求时,可以使用存储分类(StorageClass),描述具体过程为:PV先创建分类,PVC请求已创建的某个类(StorageClass)的资源,这样就达到动态配置的效果。即通过一个叫 Storage Class的对象由存储系统根据PVC的要求自动创建。
PV创建完后状态会变成Available,等待被PVC绑定。一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。Pod使用完后会释放PV,PV的状态变成Released。变成Released的PV会根据定义的回收策略做相应的回收工作。
有三种回收策略,Retain、Delete 和 Recycle。Retain就是保留现场,K8S什么也不做,等待用户手动去处理PV里的数据,处理完后,再手动删除PV。Delete 策略,K8S会自动删除该PV及里面的数据。Recycle方式,K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用。
在实际使用场景里,PV的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个PV池,开发人员创建Pod和PVC,PVC里定义了Pod所需存储的大小和访问模式,然后PVC会到PV池里自动匹配最合适的PV给Pod使用。

2.部署

2.1 K8S+NFS静态存储模式

Kubernetes使用NFS共享存储有两种方式:
手动方式静态创建所需要的PV和PVC;
通过创建PVC动态地创建对应PV,无需手动创建PV。

2.1.1 静态PV存储操作方式

安装环境:nfs数据共享目录------192.168.199.120

#安装NFS文件服务;	
yum install nfs-utils -y

#配置共享目录&权限;
[root@master1 ~]# vim /etc/exports
/data/ *(rw,async,no_root_squash)

#启动NFS服务;
systemctl start nfs 
[root@master1 ~]# ps -ef |grep ngf
root     30443 28926  0 13:38 pts/1    00:00:00 grep --color=auto ngf
[root@master1 ~]# ps -ef |grep nfs
root     30377     2  0 13:38 ?        00:00:00 [nfsd4_callbacks]
root     30383     2  0 13:38 ?        00:00:00 [nfsd]
root     30384     2  0 13:38 ?        00:00:00 [nfsd]
root     30385     2  0 13:38 ?        00:00:00 [nfsd]
root     30386     2  0 13:38 ?        00:00:00 [nfsd]
root     30387     2  0 13:38 ?        00:00:00 [nfsd]
root     30388     2  0 13:38 ?        00:00:00 [nfsd]
root     30389     2  0 13:38 ?        00:00:00 [nfsd]
root     30390     2  0 13:38 ?        00:00:00 [nfsd]

2.1.2 PV存储卷创建

创建持久化PV存储卷,pv.yaml文件内容如下:

cat>pv.yaml<<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
  namespace: default
spec:
  capacity:
    storage: 10G
  accessModes:
    - ReadWriteMany
  nfs:
    # FIXME: use the right IP
    server: 192.168.199.120
		path: /data/
EOF

PV配置参数如下:

| Capacity 指定 PV 的容量为 100M;
accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。
persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*;
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure、Disk、OpenStack Cinder Volume 等。
storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。

指定 PV 在 NFS 服务器上对应的目录。

2.1.3 PVC存储卷创建

创建持久化PVC存储卷索取,pvc.yaml文件内容如下

cat>pvc.yaml<<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 10G
EOF

[root@master1 nfs]# vim pv.yaml 
[root@master1 nfs]# kubectl apply -f pv.yaml 
persistentvolume/nfs-pv created
[root@master1 nfs]# kubectl apply -f pvc.yaml 
persistentvolumeclaim/nfs-pvc created
[root@master1 nfs]# 

[root@master1 nfs]# kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-pvc   Bound    nfs-pv   10G        RWX                           29s
[root@master1 nfs]# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
nfs-pv   10G        RWX            Retain           Bound    default/nfs-pvc                           39s
[root@master1 nfs]# 

image.png

[root@master1 nfs]# kubectl describe pv
Name:            nfs-pv
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Bound
Claim:           default/nfs-pvc
Reclaim Policy:  Retain
Access Modes:    RWX
VolumeMode:      Filesystem
Capacity:        10G
Node Affinity:   <none>
Message:         
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    192.168.199.120
    Path:      /data/
    ReadOnly:  false
Events:        <none>

2.1.4 Nginx整合PV存储卷

创建Nginx POD容器使用PVC存储卷索取,nginx.yaml文件内容如下:

cat>nginx.yaml<<EOF
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx-v1
  labels:
    name: nginx-v1
  namespace: default
spec:
  replicas: 1
  selector:
    name: nginx-v1
  template:
    metadata:
      labels:
       name: nginx-v1
    spec:
      containers:
      - name: nginx-v1
        image: nginx
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: nginx-data
        ports:
        - containerPort: 80
      volumes:
      - name: nginx-data
        persistentVolumeClaim:
          claimName: nfs-pvc
EOF

image.png

[root@master1 nfs]# kubectl describe pods nginx-v1-5ljkf  
    Mounts:
      /usr/share/nginx/html from nginx-data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6dgp7 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  nginx-data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  nfs-pvc
    ReadOnly:   false
  default-token-6dgp7:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6dgp7
    Optional:    false

可以在node节点检查是否挂载

root@node1(192.168.199.121)~>mount |grep nfs
192.168.199.120:/data on /var/lib/kubelet/pods/74326c90-7d82-4a9c-b75d-6d4663906ac3/volumes/kubernetes.io~nfs/nfs-pv type nfs4 (rw,relatime,vers=4.1,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.199.121,local_lock=none,addr=192.168.199.120)
root@node1(192.168.199.121)~>

在存储集群的/data/下创建webapp文件夹

[root@master1 data]# ls
webapps
[root@master1 data]# 

在创建的容器内检查数据目录是否同步

[root@master1 data]# kubectl exec -it nginx-v1-5ljkf  /bin/bash
root@nginx-v1-5ljkf:/# cd /usr/share/nginx/html/ 
root@nginx-v1-5ljkf:/usr/share/nginx/html# ls
webapps

2.2 K8S+NFS动态存储模式

通过创建PVC动态地创建对应PV,无需手动创建PV
动态创建PV,是指在现有PV不满足PVC的请求时,可以使用存储分类(StorageClass),描述具体过程为:PV先创建分类,PVC请求已创建的某个类(StorageClass)的资源,这样就达到动态配置的效果。即通过一个叫 Storage Class的对象由存储系统根据PVC的要求自动创建。
其中动态方式是通过StorageClass来完成的,这是一种新的存储供应方式。动态卷供给能力让管理员不必进行预先创建存储卷,而是随用户需求进行创建。
image.png
使用StorageClass有什么好处呢?除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。
在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度。如下为动态PV存储操作方式:

NFS默认不支持动态存储,使用了第三方的NFS插件安装NFS插件,GitHub地址:
https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client/deploy

2.2.1 下载NFS&动态PV配置文件;

git clone https://github.com/kubernetes-incubator/external-storage.git
cp external-storage/nfs-client/deploy/*.yaml   ./
root@master1(192.168.199.120)~/nfs>ls
class.yaml  deployment-arm.yaml  deployment.yaml   rbac.yaml  test-claim.yaml  test-pod.yaml

2.2.2 修改 deployment.yaml
这里修改的参数包括NFS服务器所在的IP地址(192.168.199.120),以及NFS服务器共享的路径(/data),两处都需要修改为你实际的NFS服务器和共享目录


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: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 192.168.199.120
            - name: NFS_PATH
              value: /data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.199.120
            path: /data

2.2.2 应用配置文件

依次应用rbac.yaml class.yaml deployment.yaml 配置文件:

kubectl create -f rbac.yaml
kubectl create -f class.yaml
kubectl create -f deployment.yaml

2.2.3 class.yaml的含义

在动态资源供应模式下,通过StorageClass和PVC完成资源动态绑定(系统自动生成PV),并供Pod使用的存储管理机制。
需要注意的是:provisioner属性要等于驱动所传入的环境变量PROVISIONER_NAME的值。否则,驱动不知道知道如何绑定storage class。
此处可以不修改,或者修改provisioner的名字,需要与上面的deployment的PROVISIONER_NAME名字一致。

root@master1(192.168.199.120)~/nfs>cat class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false"

2.2.4 创建pod应用

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      imagePullSecrets:
      - name: huoban-harbor
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: nginx
  selector:
    app: nginx
  clusterIP: None
[root@master1 nfs]# kubectl apply -f nginx.yaml 
statefulset.apps/web created
service/nginx created
[root@master1 nfs]# 

2.2.5 报错解决

[root@master1 nfs]# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
web-0                                    0/1     Pending   0          2m27s
[root@master1 nfs]# 

#在/etc/kubernetes/manifests/kube-apiserver.yaml配置文件中加入如下代码:
- --feature-gates=RemoveSelfLink=false

[root@master1 nfs]# docker ps |grep apiserver |awk '{print $1}'|xargs docker restart
87ac86adb660
a4bb1d739ea7
[root@master1 nfs]# kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
pod/kube-apiserver created

[root@master1 nfs]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE                         4h5m
www-web-0   Bound    pvc-59b45062-7419-46a8-a204-f6bcac25854a   1Gi        RWO            managed-nfs-storage   3h1m
[root@master1 nfs]# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
web-0                                    1/1     Running   0          12m
[root@master1 nfs]# 

查看service的cluster-ip为空
[root@master1 nfs]# kubectl get service
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
centos7        NodePort    10.10.18.21     <none>        22:30001/TCP     6d2h
kubernetes     ClusterIP   10.10.0.1       <none>        443/TCP          7d8h
nginx          ClusterIP   None            <none>        80/TCP           62s

image.png

登录NFS /data/目录下,可以看到default开头的动态PV创建的目录&相关的文件即可

[root@master1 data]# ls
default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05  

检查是否数据共享

[root@master1 data]# cd default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05/
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# ls
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# touch index.html
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# vim index.html 
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# 

[root@master1 ~]# kubectl exec -it web-0 bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@web-0:/# 
exit
[root@master1 ~]# kubectl exec -it web-0 /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@web-0:/# ls
bin   dev		   docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc			 lib   media  opt  root  sbin  sys  usr
root@web-0:/# cd /usr/share/nginx/html/
root@web-0:/usr/share/nginx/html# ls
index.html
root@web-0:/usr/share/nginx/html# cat index.html 
hello 
root@web-0:/usr/share/nginx/html# 

2.3 在deployment/statefulset中直接使用

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
      volumes:
      - name: data
        nfs:
          path: /data
          server: 192.168.199.120

[root@master1 nfs]# 
[root@master1 nfs]# kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-cbcc9bbc5-9ngbs   1/1     Running   0          29s
nginx-deployment-cbcc9bbc5-ggzzm   1/1     Running   0          24s
nginx-deployment-cbcc9bbc5-tjxlg   1/1     Running   0          27s
[root@master1 nfs]# kubectl 

我们进入pod内部可以发现实际上pod内部是使用把192.168.199.120:/data mount 到 /usr/share/nginx/html

root@nginx-deployment-cbcc9bbc5-tjxlg:/# mount |grep 192
192.168.199.120:/data on /usr/share/nginx/html type nfs4 (rw,relatime,vers=4.1,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.199.123,local_lock=none,addr=192.168.199.120)
root@nginx-deployment-cbcc9bbc5-tjxlg:/# 

猜你喜欢

转载自blog.csdn.net/xiaolong1155/article/details/130152399