在K8S中,数据卷是通过Pod实现持久化的,如果Pod删除,数据卷也会一起删除。k8s的数据卷是docker数据卷的扩展,K8S适配各种存储系统,包括本地存储EmptyDir、HostPath, 网络存储NFS、GlusterFS、PV/PVC等,以及云存储gce pcd、azure disk、OpenStack cinder、aws ebs、vSphere volume等。
一、本地存储
1) emptyDir
emptyDir 按需创建、随着pod的删除,它也会被删除,可以充当临时空间或cache;同一个pod内的多个containers 之间可以共享emptyDir类型的卷。
例1、

[root@docker79 volume]# vim pod-vol-demo.yaml
[root@docker79 volume]# cat pod-vol-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
  labels:
    app: myapp
    tier: frontend
  annotations:
    inspiry.com/author: "cluster admin"
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /data/web/html/
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /data/
    command:
    - "/bin/sh"
    - "-c"
    - "sleep 7200"
  volumes:
  - name: html
    emptyDir: {}
[root@docker79 volume]# kubectl apply -f pod-vol-demo.yaml
pod/pod-demo created
[root@docker79 volume]# kubectl get pods
NAME             READY     STATUS    RESTARTS   AGE
pod-demo         2/2       Running   0          11s
[root@docker79 volume]# kubectl exec -it pod-demo -c busybox  -- /bin/sh
/ # ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # ls /data
/ # echo $(date) >> /data/index.html
/ # 
[root@docker79 volume]# kubectl exec -it pod-demo -c myapp  -- /bin/sh
/ # ls
bin    dev    home   media  proc   run    srv    tmp    var
data   etc    lib    mnt    root   sbin   sys    usr
/ # ls /data/web/html/index.html
/data/web/html/index.html
/ # cat /data/web/html/index.html
Wed Sep 5 02:21:51 UTC 2018
/ #
[root@docker79 volume]# kubectl delete -f pod-vol-demo.yaml
pod "pod-demo" deleted

例2、

[root@docker79 volume]# vim pod-vol-demo.yaml
[root@docker79 volume]# cat pod-vol-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
  labels:
    app: myapp
    tier: frontend
  annotations:
    inspiry.com/author: "cluster admin"
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /data/
    command:
    - "/bin/sh"
    - "-c"
    - "while true; do echo $$(date) >> /data/index.html ; sleep 2; done"
  volumes:
  - name: html
    emptyDir: {}
[root@docker79 volume]# kubectl apply -f pod-vol-demo.yaml
pod/pod-demo created

[root@docker79 volume]# kubectl get pods -o wide
NAME        READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
pod-demo    2/2       Running   0          23s       10.244.2.49   docker78   
[root@docker79 volume]# curl http://10.244.2.49
Wed Sep 5 02:43:32 UTC 2018
Wed Sep 5 02:43:34 UTC 2018
......

2) hostPath
hostPath 在宿主机上创建,与容器建立关联关系。
例、

[root@docker79 volume]# cat pod-vol-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-hostpath
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    hostPath:
      path: /data/pod/volume1/
      type: DirectoryOrCreate
[root@docker79 volume]#

[root@docker79 ~]# ssh docker78
Last login: Tue Sep  4 14:56:29 2018 from docker79
[root@docker78 ~]# mkdir -p /data/pod/volume1
[root@docker78 ~]# echo 78 > /data/pod/volume1/index.html
[root@docker78 ~]# 登出
Connection to docker78 closed.
[root@docker79 ~]# ssh docker77
Last login: Tue Aug 28 15:04:15 2018 from docker79
[root@docker77 ~]# mkdir -p /data/pod/volume1
[root@docker77 ~]# echo 77 > /data/pod/volume1/index.html
[root@docker77 ~]# 登出
Connection to docker77 closed.

[root@docker79 ~]# cd manifests/volume/
[root@docker79 volume]# kubectl apply -f pod-vol-hostpath.yaml
pod/pod-vol-hostpath created
[root@docker79 volume]# kubectl get pods -o wide
NAME        READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
pod-demo    2/2       Running   0          26m       10.244.2.49   docker78   
pod-vol-hostpath    1/1       Running   0          11s       10.244.2.50   docker78   
[root@docker79 volume]#
[root@docker79 volume]# curl http://10.244.2.50
78
[root@docker79 volume]#

二、网络存储
1) nfs
Pod (Container) ------> NFS storage

[root@docker ~]# echo nfs > /data/volumes/index.html
[root@docker ~]#
[root@docker ~]# ip add show ens192
2: ens192:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:c7:42:5b brd ff:ff:ff:ff:ff:ff
    inet 192.168.20.223/24 brd 192.168.20.255 scope global noprefixroute ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::bdac:2fd7:290b:aba/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
[root@docker ~]# cat /etc/exports
/data/volumes 192.168.20.0/24(rw,no_root_squash)
[root@docker ~]#
[root@docker79 volume]# vim pod-vol-nfs.yaml
[root@docker79 volume]# cat pod-vol-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-nfs
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    nfs:
      path: /data/volumes
      server: 192.168.20.223
[root@docker79 volume]# kubectl apply -f pod-vol-nfs.yaml
pod/pod-vol-nfs created
[root@docker79 volume]# kubectl get pods -o wide
NAME                             READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
pod-demo                         2/2       Running   0          2h        10.244.2.49   docker78   
pod-vol-hostpath                 1/1       Running   0          1h        10.244.2.50   docker78   
pod-vol-nfs                      1/1       Running   0          6s        10.244.2.51   docker78   
[root@docker79 volume]# curl http://10.244.2.51
nfs
[root@docker79 volume]#

2) persistentVolumeClaim
persistentVolumeClaim是 将分散式的存储层资源组成PV,然后在PV上创建PVC,最后将PVC挂载到Pod中的Container上进行存储数据的一种方式。底层的存储资源可以是NFS、iscsi、ceph、glusterfs等。

PV和PVC的生命周期
供应准备:通过集群外的存储系统或者公有云存储方案来提供存储持久化支持。
静态提供:管理员手动创建多个PV,供PVC使用。
动态提供:动态创建PVC特定的PV,并绑定。
绑定:用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
使用:用户可在pod中像使用volume一样使用pvc。
释放:用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
回收(Reclaiming):pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)
保留策略:允许人工处理保留的数据。
删除策略:将删除pv和外部关联的存储资源,需要插件支持。
回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。

PV卷阶段状态
Available – 资源尚未被PVC使用
Bound – 卷已经被绑定到PVC了
Released – PVC被删除,PV卷处于释放状态,但未被集群回收。
Failed – PV卷自动回收失败

PV卷的访问模式
ReadWriteOnce – 单node的读写 
ReadOnlyMany – 多node的只读 
ReadWriteMany – 多node的读写

操作步骤
本文中使用nfs演示,如下:
Pod (Container) ----> PVC ----> PV ----> NFS storage

首先准备底层存储资源

[root@docker ~]# cat /etc/exports
/data/volumes/v1 192.168.20.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.20.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.20.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.20.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.20.0/24(rw,no_root_squash)
[root@docker ~]# exportfs -rv
exporting 192.168.20.0/24:/data/volumes/v5
exporting 192.168.20.0/24:/data/volumes/v4
exporting 192.168.20.0/24:/data/volumes/v3
exporting 192.168.20.0/24:/data/volumes/v2
exporting 192.168.20.0/24:/data/volumes/v1
[root@docker ~]#

然后利用存储资源 创建 PersistentVolume (PV)

[root@docker79 volume]# vim pv-vol-demo.yaml
[root@docker79 volume]# cat pv-vol-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01
  labels:
    name: pv01
spec:
  nfs:
    path: /data/volumes/v1
    server: 192.168.20.223
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv02
  labels:
    name: pv02
spec:
  nfs:
    path: /data/volumes/v2
    server: 192.168.20.223
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv03
  labels:
    name: pv03
spec:
  nfs:
    path: /data/volumes/v3
    server: 192.168.20.223
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 15Gi
[root@k8s-master-dev volumes]# kubectl apply -f pv-vol-demo.yaml
persistentvolume/pv01 created
persistentvolume/pv02 created
persistentvolume/pv03 created
[root@k8s-master-dev volumes]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
pv01      5Gi        RWO,RWX        Retain           Available                                      13s
pv02      10Gi       RWO,RWX        Retain           Available                                      13s
pv03      15Gi       RWO,RWX        Retain           Available                                      13s
[root@k8s-master-dev volumes]#

最后创建 PersistentVolumeClaim (PVC) ,并将其挂载到pod 中的container上。

[root@k8s-master-dev volumes]# vim pod-pvc-vol-demo.yaml
[root@k8s-master-dev volumes]# cat pod-pvc-vol-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 6Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-pvc-vol
  namespace: default
spec:
  containers:
  - name: myapp
    image: nginx:1.15-alpine
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    persistentVolumeClaim:
      claimName: mypvc
[root@k8s-master-dev volumes]# kubectl apply -f pod-pvc-vol-demo.yaml
persistentvolumeclaim/mypvc created
pod/pod-pvc-vol created
[root@k8s-master-dev volumes]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON    AGE
pv01      5Gi        RWO,RWX        Retain           Available                                            5m
pv02      10Gi       RWO,RWX        Retain           Bound       default/mypvc                            5m
pv03      15Gi       RWO,RWX        Retain           Available                                            5m
[root@k8s-master-dev volumes]# kubectl get pvc
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc     Bound     pv02      10Gi       RWO,RWX                       13s
[root@k8s-master-dev volumes]#

由于创建的PVC容量要求6G,所以创建PVC时它自动选择pv02(10G) 。上例中必须事先手工创建pv,然后才可以创建PVC并mount,这种方式属于静态提供。如果希望了解动态提供(storageclass) ,可参考:http://docs.kubernetes.org.cn/803.html