Kubernetes 存储卷 Volume

2018/09/18 Kubernetes

存储卷概述

在 Kubernetes 中,存储卷主要用来解决容器崩溃重启时数据文件丢失;第二,在通过一个 Pod 中一起运行的容器,通常需要共享容器之间的一些数据。

在 Kubernetes 中支持多种类型的卷,而 Pod 可以同时使用各种类型和任意数量的存储卷。在 Pod 中通过指定下面的字段来使用存储卷:

  • spec.volumes:通过此字段提供指定的存储卷
  • spec.containers.volumeMounts:通过此字段将存储卷挂载到容器中

存储卷类型和示例

当前 Kubernetes 支持如下所列这些存储卷类型,并以 emptyDir、hostPath、nfs 和 persistentVolumeClaim 类型的存储卷为例,介绍如何定义存储卷,以及如何在 Pod 中被使用。

  • awsElasticBlockStore
  • azureDisk
  • azureFile
  • cephfs
  • configMap
  • csi
  • downwardAPI
  • emptyDir
  • fc (fibre channel)
  • flocker
  • gcePersistentDisk
  • gitRepo
  • glusterfs
  • hostPath
  • iscsi
  • local
  • nfs
  • persistentVolumeClaim
  • projected
  • portworxVolume
  • quobyte
  • rbd
  • scaleIO
  • secret
  • storageos
  • vsphereVolume

emptyDir

将 Pod 分配给节点时,首先会创建一个 emptyDir 卷,只要 Pod 在该节点上运行,就会存在该空卷。 顾名思义,它最初是空的。 Pod 中的容器都可以在emptyDir 卷中读取和写入相同的文件,尽管该卷可以安装在每个 Container 中相同或不同的路径上。 当出于任何原因从节点中删除 Pod 时,将永久删除 emptyDir 中的数据。

More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

接下来演示如何定义一个存储类型为 emptyDir 的卷:

$ cat emptyDir-example.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-example
  namespace: default
  labels:
    type: emptydir
spec:
  containers:
  - name: nginx
    image: nginx:1.8.1
    imagePullPolicy: IfNotPresent
    ports:
    - name: https
      containerPort: 80
    volumeMounts:
    - name: html # 挂载名为 html 的存储卷
      mountPath: /usr/share/nginx/html # 挂载到 Nginx 默认根目录
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts: 
    - name: html # 挂载名为 html 的存储卷
      mountPath: # /data 挂载到容器内的 /data 目录
    command:
    - "/bin/sh"
    - "-c"
    - "while true; do echo $(date) >> /data/index.html; sleep 2; done"
  volumes: 定义存储卷
  - name: html 存储卷的名字
    emptyDir: {} 存储卷的类型为emptyDir

上面我们定义了一个 Pod 内部运行了两个容器,Nginx 和 busybox,同时挂载了名为 html 的存储卷,busybox 容器每隔两秒钟生成一个时间戳追加到 index.html 中,共享给 Nginx 容器作为首页。

接下来我们建立 Pod

$ kubectl apply -f emptyDir-example.yaml 
pod/emptydir-example created

查看 Pod 相关资料

$ kubectl get pods -o wide
NAME               READY     STATUS    RESTARTS   AGE       IP            NODE
emptydir-example   2/2       Running   0          12s       10.244.2.12   db03v-test-ops.bd-yg.com

# 查看详细信息
$ kubectl describe pod emptydir-example
Name:               emptydir-example
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               db03v-test-ops.bd-yg.com/10.100.4.183
Start Time:         Wed, 19 Sep 2018 10:04:45 +0800
Labels:             type=emptydir
Annotations:        kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"type":"emptydir"},"name":"emptydir-example","namespace":"default"},"spec":{"con...
Status:             Running
IP:                 10.244.2.12
Containers:
  nginx:
    Container ID:   docker://ee3c5f5ed4ea18c384b39565868d9818e2e3f0fd8e2035762f554dfa4e5e23fa
    Image:          nginx:1.8.1
    Image ID:       docker-pullable://nginx@sha256:9b3e9f189890ef9d6713c3384da3809731bdb0bff84e7b68da330ebadf533085
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Wed, 19 Sep 2018 10:04:47 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from html (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-ldqb7 (ro)
  busybox:
    Container ID:  docker://7a4faf63634479d149bf64338b00aa00681e07688e8f970d6b31505defedf468
    Image:         busybox:latest
    Image ID:      docker-pullable://busybox@sha256:cb63aa0641a885f54de20f61d152187419e8f6b159ed11a251a09d115fdff9bd
    Port:          <none>
    Host Port:     <none>
    Command:
      /bin/sh
      -c
      while true; do echo $(date) >> /data/index.html; sleep 2; done
    State:          Running
      Started:      Wed, 19 Sep 2018 10:04:47 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from html (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-ldqb7 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  html:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:  
  default-token-ldqb7:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-ldqb7
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                               Message
  ----    ------     ----  ----                               -------
  Normal  Scheduled  6m    default-scheduler                  Successfully assigned default/emptydir-example to db03v-test-ops.bd-yg.com
  Normal  Pulled     6m    kubelet, db03v-test-ops.bd-yg.com  Container image "nginx:1.8.1" already present on machine
  Normal  Created    6m    kubelet, db03v-test-ops.bd-yg.com  Created container
  Normal  Started    6m    kubelet, db03v-test-ops.bd-yg.com  Started container
  Normal  Pulled     6m    kubelet, db03v-test-ops.bd-yg.com  Container image "busybox:latest" already present on machine
  Normal  Created    6m    kubelet, db03v-test-ops.bd-yg.com  Created container
  Normal  Started    6m    kubelet, db03v-test-ops.bd-yg.com  Started container

现在我们来请求 Pod 的地址,看看是否是我们预期的状态:

$ curl 10.244.2.12
Wed Sep 19 02:04:47 UTC 2018
Wed Sep 19 02:04:49 UTC 2018
Wed Sep 19 02:04:51 UTC 2018
Wed Sep 19 02:04:53 UTC 2018
Wed Sep 19 02:04:55 UTC 2018
Wed Sep 19 02:04:57 UTC 2018
Wed Sep 19 02:04:59 UTC 2018
Wed Sep 19 02:05:01 UTC 2018
Wed Sep 19 02:05:03 UTC 2018
Wed Sep 19 02:05:05 UTC 2018
Wed Sep 19 02:05:07 UTC 2018

通过观察可以看到正是按照我们预期的状态每隔 2 秒钟就打印一次时间。

hostPath

hostPath 类型的存储卷用于将宿主机的文件系统的文件或目录挂接到 Pod 中,除了需要指定 path 字段之外,在使用 hostPath 类型的存储卷时,也可以设置 type,type 支持的枚举值由下表:

行为
  空字符串(默认)是用于向后兼容,这意味着在挂接主机路径存储卷之前不执行任何检查。
DirectoryOrCreate 如果path指定目录不存在,则会在宿主机上创建一个新的目录,并设置目录权限为0755,此目录与kubelet拥有一样的组和拥有者。
Directory path指定的目标必需存在
FileOrCreate 如果path指定的文件不存在,则会在宿主机上创建一个空的文件,设置权限为0644,此文件与kubelet拥有一样的组和拥有者。
File path指定的文件必需存在
Socket path指定的UNIX socket必需存在
CharDevice path指定的字符设备必需存在
BlockDevice 在path给定路径上必须存在块设备。

下面演示使用 hostPath 作为存储卷的 YAML 文件,此 YAML 文件定义了一个名称为 hostpath-example 的 Pod 资源。它通过 hostPath 类型的存储卷,将 Pod 宿主机上的 /data/volumes 目录挂载到容器中的 /usr/share/nginx/html 目录为 nginx 提供网页文件。

$ cat hostpath-example.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-example
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.8.1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: webpage
      mountPath: /usr/share/nginx/html
  volumes:
  - name: webpage
    hostPath:
      path: /data/volumes
      type: DirectoryOrCreate

接下来我们建立这个 Pod

$ kubectl apply -f hostpath-example.yaml 
pod/hostpath-example created

# 查看 Pod 信息
$ kubectl get pods hostpath-example -o wide
NAME               READY     STATUS    RESTARTS   AGE       IP            NODE
hostpath-example   1/1       Running   0          28m       10.244.1.10   db02v-test-ops.bd-yg.com

我这里 Pod 被调度到 db02v-test-ops.bd-yg.com 节点在这台宿主机上 /data/volumes目录内创建一个 index.html 第一台内容为 <h1>node01 hostPath Volume</h1> 然后我们请求 Pod 的地址

$ curl 10.244.1.10
<h1>node01 hostPath Volume</h1>

NFS

NFS 卷允许将现有 NFS(网络文件系统)共享挂载到 Pod 中。 与删除 Pod 时删除的 emptyDir 不同,nfs 卷的内容将被保留,并且仅卸载卷。 这意味着可以使用数据预先填充 NFS 卷,并且可以在 Pod之间 “切换” 该数据。 NFS 可以由多个 Pod 同时挂载。

下面演示使用 NFS 作为存储卷的 YAML 文件,此 YAML 文件定义了一个名称为 nfs-example 的 Pod 资源。它通过 nfs 类型的存储卷,将 nfs Server 服务器上共享的 /data/volumes 目录,挂载到容器中的 /usr/share/nginx/html 目录为 nginx 提供网页文件。

首先我们要找一台服务器作为 NFS Server 服务器,并且在所有 Node 节点上安装 nfs-utils 软件包

$ yum -y install nfs-utils

然后我们在 NFS Server 服务器上定义共享文件配置:

# 创建要共享的目录
$ mkdir -pv /data/volumes

# 定义共享属性
$ vim /etc/exports
/data/volumes/ 10.100.4.0/24(rw,no_root_squash)

# 启动 NFS
$ systemctl start nfs
$ showmount -e
Export list for mycat-test-ops.bd-yg.com:
/data/volumes 10.100.4.0/24

定义 YAML 文件

$ vim nfs-example.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nfs-example
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.8.1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
  - name: html
    nfs:
      path: /data/volumes
      server: 10.100.4.180

查看 Pod 信息

$ kubectl get pods -o wide
NAME          READY     STATUS    RESTARTS   AGE       IP            NODE
nfs-example   1/1       Running   0          8s        10.244.2.13   db03v-test-ops.bd-yg.com

# 查看详细信息
$ kubectl describe pod nfs-example
Name:               nfs-example
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               db03v-test-ops.bd-yg.com/10.100.4.183
Start Time:         Wed, 19 Sep 2018 13:18:52 +0800
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nfs-example","namespace":"default"},"spec":{"containers":[{"image":"nginx:1.8.1","...
Status:             Running
IP:                 10.244.2.13
Containers:
  nginx:
    Container ID:   docker://1e92c2c1cc293d83071db3b4f2955826b915faf0673d1799d286f2f68ec502bc
    Image:          nginx:1.8.1
    Image ID:       docker-pullable://nginx@sha256:9b3e9f189890ef9d6713c3384da3809731bdb0bff84e7b68da330ebadf533085
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Wed, 19 Sep 2018 13:18:54 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from html (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-ldqb7 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  html:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    10.100.4.180
    Path:      /data/volumes
    ReadOnly:  false
  default-token-ldqb7:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-ldqb7
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                               Message
  ----    ------     ----  ----                               -------
  Normal  Scheduled  1m    default-scheduler                  Successfully assigned default/nfs-example to db03v-test-ops.bd-yg.com
  Normal  Pulled     1m    kubelet, db03v-test-ops.bd-yg.com  Container image "nginx:1.8.1" already present on machine
  Normal  Created    1m    kubelet, db03v-test-ops.bd-yg.com  Created container
  Normal  Started    1m    kubelet, db03v-test-ops.bd-yg.com  Started container

接下来我们在 NFS Server 上创建一个 html

$ echo "NFS Volumes" >> /data/volumes/index.html

请求 Pod 地址

-w665

PV 和 PVC

以下信息来源于简书:https://www.jianshu.com/p/fda9de00ba5f

PersistentVolume(PV) 和 PersistentVolumeClaim(PVC)是 K8S 提供的两种 API 资源,用于抽象存储细节。管理员关注如何通过 PV 提供存储功能而无需关注用户如何使用,同样的用户只需要挂载 PVC 到容器中而不需要关注存储卷采用何种技术实现。

PVC 和 PV 的关系与 Pod 和 Node 关系类似,前者消耗后者的资源。PVC 可以向 PV 申请指定大小的存储资源并设置访问模式,这就可以通过 Provision -> Claim 的方式来对存储资源进行控制。

生命周期

PV 和 PVC 遵循以下生命周期:

pv和pvc遵循以下生命周期:

  • 供应准备。通过集群外的存储系统或者云平台来提供存储持久化支持。
    • 静态提供:管理员手动创建多个PV,供PVC使用。
    • 动态提供:动态创建PVC特定的PV,并绑定。
  • 绑定。用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。

  • 使用。用户可在pod中像volume一样使用pvc。

  • 释放。用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。

  • 回收(Reclaiming)。pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
    • 保留策略:允许人工处理保留的数据。
    • 删除策略:将删除pv和外部关联的存储资源,需要插件支持。
    • 回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。

PV类型

pv支持以下类型:

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • Glusterfs
  • AzureFile
  • AzureDisk
  • CephFS
  • cinder
  • FC
  • FlexVolume
  • Flocker
  • PhotonPersistentDisk
  • Quobyte
  • VsphereVolume
  • HostPath (single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)

PV卷阶段状态:

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

创建 PV

下面演示使用 NFS 作为 PV 持久卷的 YAML 文件,此 YAML 文件定义了三个 PV 名称为 nfs001、nfs002、nfs003 其容量大小为 5Gi、10Gi、15Gi

首先在 NFS Server 上创建用于共享的三个目录

$ mkdir /data/volumes/v{1,2,3}

# 定义共享三个目录
$ vim  /etc/exports
/data/volumes/v1 10.100.4.0/24(rw,no_root_squash)
/data/volumes/v2 10.100.4.0/24(rw,no_root_squash)
/data/volumes/v3 10.100.4.0/24(rw,no_root_squash)

# 重载 NFS 共享配置
$ exportfs -ar
$ showmount -e
Export list for mycat-test-ops.bd-yg.com:
/data/volumes/v3 10.100.4.0/24
/data/volumes/v2 10.100.4.0/24
/data/volumes/v1 10.100.4.0/24

接下来我们定义 PV

$ vim  pv-example.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs001
  labels:
    name: pv001
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  nfs:
    path: /data/volumes/v1
    server: 10.100.4.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs002
  labels:
    name: pv002
spec:
  capacity:
    storage: 10Gi
  accessModes: ["ReadWriteOnce"]
  nfs:
    path: /data/volumes/v2
    server: 10.100.4.180
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs003
  labels:
    name: pv003
spec:
  capacity:
    storage: 15Gi
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  nfs:
    path: /data/volumes/v3
    server: 10.100.4.180

NFS 属性的定义方式与直接在 Pod 中定义 volumes 的方式相同,path 指定存储服务器被共享的目录,server 指定 NFS 服务器的IP地址。

capacity(容量)

一般来说,PV 会指定存储容量。这里需要使用 PV 的 capcity 属性。

accessModes(访问模式)

只要资源提供者支持,持久卷能够被用任何方式加载到主机上。每种存储都会有不同的能力,每个 PV 的访问模式也会被设置成为该卷所支持的特定模式。例如 NFS 能够支持多个读写客户端,但是某个 NFS PV 可能会在服务器上以只读方式使用。每个 PV 都有自己的一系列的访问模式,这些访问模式取决于 PV 的能力。

访问模式的可选范围如下:

  • ReadWriteOnce:该卷能够以读写模式被加载到一个节点上。
  • ReadOnlyMany:该卷能够以只读模式加载到多个节点上。
  • ReadWriteMany:该卷能够以读写模式被多个节点同时加载。

在 CLI 下,访问模式缩写为:

  • RWO:ReadWriteOnce
  • ROX:ReadOnlyMany
  • RWX:ReadWriteMany

定义好 YAML 文件之后我们来建立 PV

$ kubectl apply -f pv-example.yaml 
persistentvolume/nfs001 created
persistentvolume/nfs002 created
persistentvolume/nfs003 created

# 查看 PV 信息
$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
nfs001    5Gi        RWO,RWX        Retain           Available                                      5s
nfs002    10Gi       RWO            Retain           Available                                      5s
nfs003    15Gi       RWO,RWX        Retain           Available                                      5s

PV 的几种状态

一个卷会处于如下阶段之一:

  • Available:可用资源,尚未被绑定到 PVC 上
  • Bound:该卷已经被绑定
  • Released:PVC 已经被删除,但该资源尚未被集群回收
  • Failed:该卷的自动回收过程失败。

创建 PVC

$ cat pvc-example.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 6Gi

上面的 spec.resources 用来定义该 PVC 最小需要 6Gi 的存储资源,没有明确指明使用哪个 PV 它会自动在已有可用的 PV 资源中找到一个满足条件的并绑定。

建立上面的 PVC

$ kubectl apply -f pvc-example.yaml 
persistentvolumeclaim/pvc-example created

# 查看 PVC 信息
$ kubectl get pvc
NAME          STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-example   Bound     nfs003    15Gi       RWO,RWX                       3s

之前我们定义了三个 PV 容量分别为 5Gi, 10Gi, 15Gi 而上面定义的 PVC 所请求的容量是 6Gi 那么为什么会绑定到 15Gi 空间的 PV 而不是 10Gi 的呢,实际是因为我们在定义 PVC 中 accessModes 定义的访问模式为 ReadWriteMany 而即满足 6Gi 又满足访问模式的 PV 只有 nfs003 所以将此 PV 分配给了这个 PVC 请求。

此时我们再来查看 PV 的状态

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                 STORAGECLASS   REASON    AGE
nfs001    5Gi        RWO,RWX        Retain           Available                                                  41s
nfs002    10Gi       RWO            Retain           Available                                                  41s
nfs003    15Gi       RWO,RWX        Retain           Bound       default/pvc-example                            41s

nfs003 这个 PV 的状态已经转换为 Bound 了。

Search

    Table of Contents