kubernetes快速入门9-存储卷volumes

volumes的出现主要解决以下两个问题:

  1. 容器中数据的持久化,因容器中的文件是临时存放的,一旦容器崩溃后kubelet将重建容器,容器将以一个干净的状态被重建
  2. 一个Pod中多个容器间数据共享

存储大体上可以分为本地类型存储网络连接类型存储,而网络连接型的存储又可以分为传统意义上的NAS和SAN存储分布式类型网络连接存储第三方云端存储

在k8s中pod是调度的最小单位,volumes也是挂载在pod上的。Kubernetes 可以支持许多类型的卷,Pod 也能同时使用任意数量的卷。使用卷时, Pod 声明中需要提供卷的类型 (.spec.volumes 字段)和卷挂载的位置 (.spec.containers.volumeMounts 字段).

Volume的类型

emptyDir

pods.spec.volumes
	emptyDir	<Object>  空目录
		medium	<string>  卷的介质,默认值为"",表示使用节点的磁盘介质,也可以设置为Memory,表示使用内存
		sizeLimit	<string> 大小上限制,使用磁盘为存储介质时,大小限制取绝于磁盘大小
		
最佳实践:实际使用中,使用“emptyDir {}”,表示使用磁盘介质,大小也受限于磁盘

emptyDir类型的卷的生生命周期和pod相同,pod一旦被删除,那卷里的数据也被删除。

以一个pod运行两个container,共享emptDir存储卷举一个事例。

k8s@node01:~/my_manifests/volumes$ cat emptydir-pods.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: default
  labels:
    app: myapp
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
    volumeMounts:
    - name: html
      mountPath: /data/
    command: ["/bin/sh", "-c", "while true; do echo $(date) >> /data/index.html; sleep 2; done"]
  volumes:
  - name: html
    emptyDir: {}
# 资源配置清单说明:
# pod中两个容器运行后都会先挂载指定类型为emptyDir的存储卷,容器myapp挂载到/usr/share/nginx/html/,覆盖原有的nginx的root目录,此时该目录为空目录;容器busybox启动后也把emptyDir这个存储卷挂载到/data/目录,并每隔2秒种向该目录的index.html文件写数据,而myqpp容器提供的http服务就有了主页文件。

k8s@node01:~/my_manifests/volumes$ kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
myapp   2/2     Running   0          19s   10.244.2.93   node03   <none>           <none>

# 访问测试
k8s@node01:~/my_manifests/volumes$ curl 10.244.2.93
Tue Jul 28 08:15:12 UTC 2020
Tue Jul 28 08:15:14 UTC 2020
Tue Jul 28 08:15:16 UTC 2020
Tue Jul 28 08:15:18 UTC 2020
Tue Jul 28 08:15:20 UTC 2020
Tue Jul 28 08:15:22 UTC 2020
Tue Jul 28 08:15:24 UTC 2020
Tue Jul 28 08:15:26 UTC 2020
Tue Jul 28 08:15:28 UTC 2020

hostPath

pods.spec.volumes
	hostPath	<Object>  在工作节点上挂载一个已存在的文件或目录
		path	<string> -required-  工作节点上的文件路径
		type	<string>   hostPath的类型,有许多,参考:https://kubernetes.io/zh/docs/concepts/storage/volumes/#hostpath

hostPath类型的存储卷不会因pod被删除而删除,它能把数据持久化在工作节点上,但pod被删除重建后被调度到其他工作上运行,那之前持久化的数据在该工作节点上是没有的,所以在k8s中此种类型的存储卷很少用。

k8s@node01:~/my_manifests/volumes$ cat hostpath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-hostpath
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    hostPath:
      path: /data/pub/html/
      type: DirectoryOrCreate  # 目录不存在自动创建
      
k8s@node01:~/my_manifests/volumes$ kubectl get pods -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
myapp-hostpath   1/1     Running   0          8s    10.244.2.95   node03   <none>           <none>

# pod运行在了node03上,所以在node03上创建一个主页文件
root@node03:~# echo "hello." >> /data/pub/html/index.html

# 访问测试
root@node03:~# curl 10.244.2.95
hello.

nfs

在pod启动时将挂载nfs存储卷,pod被删除时只是卸载已挂载的存储卷,而存储卷中的内容被存储所保留。

pods.spec.volumes
	nfs <Object>
		path	<string> -required-    nfs的挂载路径
		server	<string> -required-  nfs地址

先准备nfs环境,就在node01主节点上安装nfs服务

# 安装nfs服务
k8s@node01:~$ sudo apt install nfs-kernel-server
k8s@node01:~$ ss -tanl | grep 2049
LISTEN   0         64                   0.0.0.0:2049             0.0.0.0:*
LISTEN   0         64                      [::]:2049                [::]:*

# 准备nfs共享目录及文件
k8s@node01:~$ cat /data/nfs/volume/index.html
this is a test page.

k8s@node01:~$ cat /etc/exports
/data/nfs/volume  192.168.101.0/24(rw,sync,no_subtree_check,root_squash)

k8s@node01:~$ sudo exportfs -ra

在各工作节点上安装nfs-common客户端工具,让mount支持nfs类型的挂载

root@node03:~# apt-get install nfs-common
# 测试
root@node03:~# mount -t nfs node01:/data/nfs/volume /mnt
root@node03:~# cat /mnt/index.html
this is a test page.

编写清单文件并运用

k8s@node01:~/my_manifests/volumes$ cat nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-nfs
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-nfs
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    nfs:  # nfs类型只需要提供挂载目录及服务器地址
      path: /data/nfs/volume/
      server: node01.k8s.com

k8s@node01:~/my_manifests/volumes$ kubectl apply -f nfs-pod.yaml
k8s@node01:~/my_manifests/volumes$ kubectl get pods -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
myapp-nfs   1/1     Running   0          6s    10.244.2.96   node03   <none>           <none>
k8s@node01:~/my_manifests/volumes$ curl 10.244.2.96
this is a test page.

PV及PVC

Persistent Volumes简写为PV,译为持久卷;Persistent Volumes Clain简写为PVC,译为持久卷认领。

在k8s集群中,pod中的数据想持久化,一般是需要外部的存储集群来提供该能力,而存储集群可以多种,可以是NFS,Ceph RBD,GlsterFS, azureDisk等,要想接入这些存储,需要熟悉使用这些存储的接口API,各种参数,而对于k8s用户来说这十分困难,所以引入了PV和PVC这两个概念。

PV和PVC都是k8s中的资源,PV没有名称空间的概念,它是针对k8s整个集群而言,它运行在各种存储集群之上的一种抽象,对于不同的存储,会具有不同的特性,比如存储的速度,大小等,针对这些特性,管理员需要创建不同特性的PV,PV不直接对用户开放,用户想要使用存储只需要向PVC申请即可,用户在申请存储使用时只需要表明使用存储的一些基本特性,比如要多大的存储设备,要速度快的等,PVC就可以在创建好的PV中寻找一个适合要求的与之建立绑定关系并分配相应的存储空间给用户使用,用户就不需要关心到底使用的何种存储,细节被PVC抹平。PV与PVC是一一对应的关系,PV一旦被一个PVC绑定就不能被其他的PVC绑定。

使用kubectl explain pv查看帮助信息

KIND:     PersistentVolume
VERSION:  v1    属于核心群组中的v1

spec <Object>
	accessModes	<[]string>   访问模型,参考:https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes
	nfs	<Object>  nfs类型的pv
	capacity	<map[string]string>  定义pv的容量
		storage <string>  大小,单位以1000进制时使用(E, P, T, G, M, K, m),以1024进制时使用(Ei, Pi, Ti, Gi, Mi, Ki)

以前边的NFS存储事例来实现使用PVC来请求存储卷功能。

# 修改nfs的导出目录,导出3个目录,相应的目录需要创建
k8s@node01:~$ cat /etc/exports
/data/nfs/volume/v1  192.168.101.0/24(rw,sync,no_subtree_check,root_squash)
/data/nfs/volume/v2  192.168.101.0/24(rw,sync,no_subtree_check,root_squash)
/data/nfs/volume/v3  192.168.101.0/24(rw,sync,no_subtree_check,root_squash)

# 给予写权限
k8s@node01:~$ sudo chmod o+w -R /data/nfs/volume/

k8s@node01:~$ sudo exportfs -rav
exporting 192.168.101.0/24:/data/nfs/volume/v3
exporting 192.168.101.0/24:/data/nfs/volume/v2
exporting 192.168.101.0/24:/data/nfs/volume/v1

# 创建PV资源
k8s@node01:~/my_manifests/volumes$ cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv001
  labels:
    name: pv001
    speed: slow
spec:
  nfs:
    path: /data/nfs/volume/v1
    server: node01.k8s.com
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv002
  labels:
    name: pv002
    speed: medium
spec:
  nfs:
    path: /data/nfs/volume/v2
    server: node01.k8s.com
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv003
  labels:
    name: pv003
    speed: medium
spec:
  nfs:
    path: /data/nfs/volume/v3
    server: node01.k8s.com
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 3Gi

k8s@node01:~/my_manifests/volumes$ kubectl apply -f pv-nfs.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
k8s@node01:~/my_manifests/volumes$ kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   1Gi        RWO,RWX        Retain           Available                                   4s
pv002   2Gi        RWO,RWX        Retain           Available                                   4s
pv003   3Gi        RWO            Retain           Available                                   4s

创建一个pod,使用pvc来使用pv提供的存储卷,使用kubectl explain pvc查看帮助信息

KIND:     PersistentVolumeClaim
VERSION:  v1

spec <Object>
	accessModes	<[]string>   是pv中定义的访问模型的子集
	resources	<Object>  资源要求
		requests	<map[string]string>  请求资源的大小要求
			storge <string>  大小

编写配置清单,应用并测试

k8s@node01:~/my_manifests/volumes$ cat pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-html
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  selector:
    matchLabels:
      speed: medium
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-pvc
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: nginx-root
      mountPath: /usr/share/nginx/html/
  volumes:
    - name: nginx-root
      persistentVolumeClaim:
        claimName: pvc-html
# 配置清单中的pvc资源中使用了标签选择器,pv002和pv003满足条件,又使用了resources来定义空间大小为2Gi,最后挑选出的pv只有pv002

k8s@node01:~/my_manifests/volumes$ kubectl apply -f pvc-pod.yaml
persistentvolumeclaim/pvc-html created
pod/pod-vol-pvc configured
k8s@node01:~/my_manifests/volumes$ kubectl get pvc
NAME       STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-html   Bound    pv002    2Gi        RWO,RWX                       5s

# 连接到pod的容器中创建一个主页文件
k8s@node01:~/my_manifests/volumes$ kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
pod-vol-pvc   1/1     Running   0          10m   10.244.2.98   node03   <none>           <none>
k8s@node01:~/my_manifests/volumes$ kubectl exec -it pod-vol-pvc -- /bin/sh
/ # echo ok > /usr/share/nginx/html/index.html

# 访问测试
k8s@node01:~/my_manifests/volumes$ curl 10.244.2.98
ok
# 查看nfs服务器上的相应共享目录是否有文件
k8s@node01:~$ cat /data/nfs/volume/v2/index.html
ok

当pvc删除后,之前分配给该pvc的pv就处于Released状态,该pv就不能分配给pvc了,此时需要手动处理该pv,

k8s@node01:~/my_manifests/volumes$ kubectl delete -f pvc-pod.yaml
persistentvolumeclaim "pvc-html" deleted
pod "pod-vol-pvc" deleted
k8s@node01:~/my_manifests/volumes$ kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
pv001   1Gi        RWO,RWX        Retain           Available                                              152m
pv002   2Gi        RWO,RWX        Retain           Released    default/pvc-html                           152m
pv003   3Gi        RWO            Retain           Available                                              152m
k8s@node01:~/my_manifests/volumes$ kubectl edit pv pv002
# 删除spec.claimRef字段的所有键值
spec:
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: pvc-html
    namespace: default
    resourceVersion: "649874"
    uid: 945c88fe-9513-4b15-83bc-0edebc95ab53
# 保存退出后相应的pv状态即可恢复
k8s@node01:~/my_manifests/volumes$ kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   1Gi        RWO,RWX        Retain           Available                                   155m
pv002   2Gi        RWO,RWX        Retain           Available                                   155m
pv003   3Gi        RWO            Retain           Available                                   155m

而在实际生产环境中,pvc申请的存储卷时并不会刚好有符合条件的pv存在,此pvc将不能够有相应的pv与之匹配。k8s在此种情况下引入了StorageClass抽象资源,把后端存储进行分类,pvc在申请存储卷时可以动态创建pv。

Secret和ConfigMap特殊存储卷

配置容器化应用的方式:

  1. 直接把配置文件打包到镜像

  2. 在定义pod资源清单时传递args,以参数形式传递给程序

  3. 使用环境变量

    3.1 Cloud Native的应用程序可直接使用环境变量加载配置
    3.2 通过entrypoint脚本预处理环境变量为配置文件的配置信息
    

 4. 存储卷

ConfigMap和Secret对象也是k8s中一种特殊的存储卷对象。

ConfigMap

ConfigMap是k8s中的一等公民,它存放一个个“kev/value”形式的数据。

configmap可简写为cm,使用kubectl explain cm查看资源清单配置时的帮助信息。

命令创建ConfigMap对象
## 命令行直接传递key和value值
k8s@node01:~$ kubectl create configmap nginx-port --from-literal=nginx_port=80 --from-literal=server_name=test.k8s.com
configmap/nginx-port created
k8s@node01:~$ kubectl get cm
NAME         DATA   AGE
nginx-port   2      4s

# 查看详细信息的两种方式
k8s@node01:~$ kubectl get cm nginx-port -o yaml
k8s@node01:~$ kubectl describe cm nginx-port

## 使用本地文件当作数据创建configmap资源
k8s@node01:~/cm$ cat www.conf
server {
    server_name test.k8s.com;
    listen 80;
    root /data/web/html/;
}
k8s@node01:~/cm$ kubectl create configmap nginx-www-conf --from-file=./www.conf
configmap/nginx-www-conf created
k8s@node01:~/cm$ kubectl get cm
NAME             DATA   AGE
nginx-port       2      5m8s
nginx-www-conf   1      5s

# 使用文件创建时,如果不给key的名称,那文件名当作key,文件内容当作value
Pod中使用env引用ConfigMap对象

ConfigMap对象创建好后,可以在pod中进行引用,kubectl explain pods.spec.containers.env查看相应的帮助信

KIND:     Pod
VERSION:  v1

FIELDS:
	spec <Object>
		containers <[]Object>
			env <[]Object>
				name	<string> -required-
				valueFrom	<Object>
					configMapKeyRef	<Object>
						name	<string>  表示引用哪个configmap资源
						key	<string> -required-  表示引用configmap资源中的key名称
						optional	<boolean>  该key是否是可选,true表示key必须定义
k8s@node01:~/cm$ kubectl get cm
NAME             DATA   AGE
nginx-port       2      62m
nginx-www-conf   1      57m
k8s@node01:~/cm$ kubectl describe cm nginx-port
Name:         nginx-port
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
nginx_port:
----
80
server_name:
----
test.k8s.com
Events:  <none>

k8s@node01:~/my_manifests/volumes$ cat configmap-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-configmap
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    env:
    - name: NGINX_SERVER_PORT
      valueFrom:
        configMapKeyRef:
          name: nginx-port
          key: nginx_port
    - name: NGINX_SERVER_NAME
      valueFrom:
        configMapKeyRef:
          name: nginx-port
          key: server_name
k8s@node01:~/my_manifests/volumes$ kubectl apply -f configmap-pod.yaml
pod/pod-vol-configmap created
k8s@node01:~/my_manifests/volumes$ kubectl get pods
NAME                READY   STATUS    RESTARTS   AGE
pod-vol-configmap   1/1     Running   0          4s

k8s@node01:~/my_manifests/volumes$ kubectl exec -it pod-vol-configmap -- printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=pod-vol-configmap
TERM=xterm
NGINX_SERVER_PORT=80   # 从configmap中获取的变量
NGINX_SERVER_NAME=test.k8s.com  # 从configmap中获取的变量
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
...

**注意:**以env方式引用ConfigMap对象中的变量只在pod启动时获取,如果pod运行后再edit ConfigMap资源,那pod的变量是不能进行更新的。

pod中以存储卷形式挂载ConfigMap对象
k8s@node01:~/my_manifests/volumes$ cat configmap-pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-configmap-2
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: nginxconf
      mountPath: /etc/nginx/conf.d/
      readOnly: true
  volumes:
  - name: nginxconf
    configMap:
      name: nginx-www-conf

k8s@node01:~/my_manifests/volumes$ kubectl apply -f configmap-pod-2.yaml
pod/pod-vol-configmap-2 created
k8s@node01:~/my_manifests/volumes$ kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
pod-vol-configmap     1/1     Running   0          17m
pod-vol-configmap-2   1/1     Running   0          6s

# configMap对象以存储卷的形式挂载在容器中
k8s@node01:~/my_manifests/volumes$ kubectl exec -it pod-vol-configmap-2 -- cat /etc/nginx/conf.d/www.conf
server {
    server_name test.k8s.com;
    listen 80;
    root /data/web/html/;
}

# 修改configmap资源中的数据
k8s@node01:~/my_manifests/volumes$ kubectl edit cm nginx-www-conf
# 把
listen 80;
# 修改为
listen 8080;
# 保存退出

# 需要等一会,容器中挂载的卷内容也会被改变
k8s@node01:~/my_manifests/volumes$ kubectl exec -it pod-vol-configmap-2 -- cat /etc/nginx/conf.d/www.conf
server {
    server_name test.k8s.com;
    listen 8080;
    root /data/web/html/;
}

Secret

和ConfigMap类似,但只是用于保存敏感数据。

命令创建Secret资源
$ kubectl create secret --help

Available Commands:
  docker-registry Create a secret for use with a Docker registry 当使用私有仓库拉取镜像时保存认证信息
  generic         Create a secret from a local file, directory or literal value 其他类型的敏感数据都可以使用此类型
  tls             Create a TLS secret  服务需要使用证书认证时用于保存证书及key的信息
# 创建一个通用类型的secret对象
k8s@node01:~/my_manifests/volumes$ kubectl create secret generic mysql-password --from-literal=password=passwd@123!
secret/mysql-password created

k8s@node01:~/my_manifests/volumes$ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-ndclg   kubernetes.io/service-account-token   3      7d
ingress-https         kubernetes.io/tls                     2      31h
mysql-password        Opaque                                1      11s

k8s@node01:~/my_manifests/volumes$ kubectl get secret mysql-password -o yaml
apiVersion: v1
data:
  password: cGFzc3dkQDEyMyE=   # 通过base64编码
kind: Secret
metadata:
  creationTimestamp: "2020-07-29T09:31:59Z"
...

# 通过base64可解码,也只是在一定程序上对敏感数据进行保护
k8s@node01:~/my_manifests/volumes$ echo cGFzc3dkQDEyMyE= | base64 -d
passwd@123!
pod中使用secret资源

secret资源同样可以使用env的方式注入到pod中

k8s@node01:~/my_manifests/volumes$ cat secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-secret
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    env:
    - name: MYSQL_ROOT_PASSWD
      valueFrom:
        secretKeyRef:
          name: mysql-password
          key: password
k8s@node01:~/my_manifests/volumes$ kubectl apply -f secret-pod.yaml
pod/pod-vol-secret created
k8s@node01:~/my_manifests/volumes$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
pod-vol-secret   1/1     Running   0          3s
k8s@node01:~/my_manifests/volumes$ kubectl exec pod-vol-secret -- printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=pod-vol-secret
MYSQL_ROOT_PASSWD=passwd@123!  # 解码后的密码

同样也可以向ConfigMap一样使用卷的方式挂载在Pod中,这里就不举例了。