Kubernetes 的卷 volume
容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。首先,容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失。其次,当在一个 Pod 中同时运行多个容器的时候,常常需要在这些容器之间共享文件。kubernetes 抽象出 volume 对象来解决这两个问题。
Docker 也有 volume 的概念,在 Docker 中,volume 是磁盘上或者另外一个容器内的一个目录。直到最近,Docker 才支持对基于本地磁盘的 volumn 的生存期进行管理。虽然 Docker 现在也提供 volumn 驱动程序,但是目前功能还非常有限(比如截止 Docker1.7 每个容器只允许有一个 volume 驱动程序,并且无法将参数传递给卷)。
另一方面,kubernetes 卷具有明确的声明周期--与包裹它的 Pod 不同。因此,卷比 Pod 中运行的任何容器的存活期都长,在容器重新启动时数据也会得到保留。当然,当一个 Pod 不再存在时,卷也将不再存在。也许更重要的是,kubernetes 可以支持许多类型的卷,Pod 也能同时使用任意数量的卷。
卷的核心是包含一些数据的目录,Pod 中的容器可以访问该目录。特定的卷类型可以决定这个目录是如何形成的,并能决定它支持何种介质,以及目录中存放什么内容。
使用卷时,Pod 声明中需要提供卷的类型(通过 .spec.volumes 字段)和卷挂载的位置(通过 .spec.containers.volumeMounts 字段)
Kubernetes 提供了众多的 volume 类型,包括:emptyDir, hostPth, nfs, glusterfs, cephfs, ceph
emptyDir
当 Pod 指定到某个节点上时,首先创建的是一个 emptyDir 卷,并且只要 Pod 在该节点上运行,卷就一直存在。当 Pod 被从节点删除时,emptyDir 卷中的数据也会永久删除。容器崩溃并不会导致 Pod 被从节点上删除,因此容器崩溃时,emptyDir 卷中的数据是安全的。
emptyDir 的一些用途:
- 缓存空间,例如基于磁盘的归并排序
- 为耗时较长的计算任务提供检查点,以便任务能方便的从崩溃前状态恢复执行
- 在 web 服务器容器服务数据时,保存内容管理器类型容器获取的文件
hostPath
hostPath 卷能将主机节点文件系统上的文件或目录挂载到 Pod 中。虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的持久化能力。
vim hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
spec:
containers:
- name: test-container
image: nginx
volumeMounts:
# 容器内的挂载目录
- mountPath: /test-nginx
name: myhostpath
volumes:
- name: myhostpath
hostPath:
# 虚拟机上的挂载目录
path: /tmp/nginx
type: DirectoryOrCreate
mkdir /tmp/nginx -p
kubectl create -f hostpath.yaml
可以看到
pod/hostpath-pod created
kubectl get po hostpath-pod
可以看到
NAME READY STATUS RESTARTS AGE
hostpath-pod 1/1 Running 0 59s
[root@node2 ~]# kubectl exec -it hostpath-pod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys test-nginx tmp usr var
# cd test-nginx
# touch a.txt
# echo 1 > a.txt
# exit
[root@node2 ~]# cd /tmp/nginx/
[root@node2 nginx]# ls
a.txt
[root@node2 nginx]# cat a.txt
1
[root@node2 nginx]# echo 2 > a.txt
[root@node2 nginx]# kubectl exec -it hostpath-pod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys test-nginx tmp usr var
# cd test-nginx
# pwd
/test-nginx
# ls
a.txt
# cat a.txt
2
# exit
[root@node2 nginx]#
挂载 NFS 卷
应用场景:很多应用需要在集群内部有一个统一的地方存储文件,比如图片,日志等。而使用 hostpath 的方式不够灵活,因为需要指定 host 地址。
-
在 master 节点安装 nfs 服务
yum install -y nfs-utils rpcbind
-
在 master 节点配置共享目录
mkdir /nfsdata
vim /etc/exports
/nfsdata *(rw,sync,no_root_squash)
-
在 master 节点配置自动启动
systemctl enable --now rpcbind systemctl enable --now nfs
-
在 master 节点激活配置
exportfs -r
-
查看挂载效果
showmount -e master
可以看到
Export list for master: /nfsdata *
-
在 node2 节点安装 nfs 服务
yum install -y nfs-utils rpcbind
-
在 node2 节点创建 Pod ,引用 NFS 存储
vim nfs.yaml
apiVersion: v1 kind: Pod metadata: name: nfs-pd spec: containers: - name: test-container image: nginx volumeMounts: # node 节点挂载的目录 - mountPath: /usr/share/nginx/html name: test-volume volumes: - name: test-volume nfs: server: master # master 节点挂载的目录 path: /nfsdata
kubectl apply -f nfs.yaml
可以看到
pod/nfs-pd created
-
进入 pod
[root@node2 ~]# kubectl exec -it nfs-pd sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. # mount |grep nfs master:/nfsdata 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.190.133,local_lock=none,addr=192.168.190.131) # cd /usr/share/nginx/html # pwd /usr/share/nginx/html # ls # touch a.txt # echo hello kubernetes volume > a.txt # cat a.txt hello kubernetes volume # exit [root@node2 ~]#
-
进入 master 节点
[root@master ~]# cd /nfsdata/ [root@master nfsdata]# ls a.txt [root@master nfsdata]# cat a.txt hello kubernetes volume [root@master nfsdata]#
持久化存储
存储的管理是一个与计算实例的管理完全不同的问题。 PersistentVolume 子系统为用户和管理员提供了一组 API ,将存储如何供应的细节从其如何被使用中抽象出来。为了实现这点,我们引入了两个新的 API 资源 PersistenVolume 和 PersistenVolumeClaim。
持久卷(PersistenVolume, PV)是集群中的一块存储,可以有管理员事先供应,或者使用存储类(Storage Class)来动态供应。持久卷是集群资源,就像节点也是集群资源一样。PV 和普通的 volume 一样,也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。此 API 对象中记述了存储的实现细节,无论其背后是 NFS, iSCSI 还是特定于云平台的存储系统。
创建 PV
vim pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-demo
spec:
# 容量
capacity:
storage: 5Gi
accessModes:
# 每次允许一个 worknode 读写
- ReadWriteOnce
# 回收策略:可回收
persistentVolumeReclaimPolicy: Recycle
nfs:
# 挂载路径
path: /nfsdata
server: master
kubectl apply -f pv.yaml
可以看到
persistentvolume/pv-demo created
kubectl get pv pv-demo
可以看到
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-demo 1Gi RWO Recycle Available 53s
持久卷申领
持久卷申领(PersistenVolumeClaim)
每个 PVC 对象都有 spec 和 status 部分,分别对应 Claim 的规定和状态
vim pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
# 申领的容量
storage: 2Gi
kubectl apply -f pvc.yaml
可以看到
persistentvolumeclaim/pvc-demo created
kubectl get pvc pvc-demo
可以看到
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-demo Bound pv-demo 5Gi RWO 10s
kubectl get pv pv-demo
可以看到 ,PV 的状态由 Available 变成了 Bound
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-demo 5Gi RWO Recycle Bound default/pvc-demo 61s
存储类
什么是 StorageClass
Kubernetes 提供了一套可以自动创建 PV 的机制:Dynamic Provisioning 。这个机制的核心是 StorageClass 这个API 对象。
StorageClass 对象会定义下面两部分内容:
- PV 的属性,比如存储类型,volume 的大小等
- 创建这种 PV 需要用到的存储插件
为什么需要 StorageClass
在一个大规模的 kubernetes 集群里,可能有成千上万个 PVC,这就意味着必须创建出多个 PV ,而且随着项目的需要,会有新的 PVC 不断被提交,那么就需要不断添加新的 PV ,否则新的 Pod 就会因为 PVC 绑定不到 PV 而创建失败。不同的应用程序对于存储性能的要求不尽相同,比如读写速度、并发性能等。通过 StorageClass ,可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,这样就可以根据应用的特性去申请合适的存储资源了。
NFS Provisioner
NFS Provisioner 是一个自动配置卷程序,它使用现有的 NFS 服务器来支持通过持久卷声明动态配置 kubernetes 持久卷。
持久卷配置为:{namespace}-{pvcName}-{pvName}
在下面的实例中,通过该容器定义 ENV 变量:PROVISONER_NAME 为 qgg-nfs-storage,该容器会动态生产一个名为 qgg-nfs-storage 的持久卷,从而实现持久卷的动态创建。
实践 PV, PVC 挂载 NFS
在 node2 节点:
创建 Persistent Volume Provisioner
vim nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
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: registry.cn-beijing.aliyuncs.com/qingfeng666/nfs-client-provisioner:v3.1.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: qgg-nfs-storage
- name: NFS_SERVER
value: master
- name: NFS_PATH
value: /nfsdata
volumes:
- name: nfs-client-root
nfs:
server: master
path: /nfsdata
kubectl apply -f nfs-provisioner.yaml
可以看到
deployment.apps/nfs-client-provisioner created
创建用户和角色
vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
kubectl apply -f rbac.yaml
可以看到
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
创建 StorageClass
vim storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: qgg-nfs-storage
parameters:
archiveOnDelete: "false"
kubectl apply -f storageclass.yaml
可以看到
storageclass.storage.k8s.io/managed-nfs-storage created
kubectl get storageclass.storage.k8s.io/managed-nfs-storage
可以看到
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage qgg-nfs-storage Delete Immediate false 101s
创建 PVC
vim 8-6-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
kubectl apply -f 8-6-pvc.yaml
可以看到
persistentvolumeclaim/test-claim created
kubectl get pvc test-claim
可以看到
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5 1Mi RWX managed-nfs-storage 72s
kubectl get po
可以看到
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6976b6b79c-hdqm9 1/1 Running 0 13m
kubectl get storageclass
可以看到
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage qgg-nfs-storage Delete Immediate false 9m56s
my-storage-class kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 29h
创建测试 Pod
vim 8-6-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: nginx
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
kubectl apply -f 8-6-pod.yaml
可以看到
pod/test-pod created
kubectl get po
可以看到 test-pod 的 状态是 Completed,说明任务已经执行完成。
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6976b6b79c-hdqm9 1/1 Running 0 20m
test-pod 0/1 Completed 0 80s
在 master 节点:
cd /nfsdata
ls
可以看到
[root@master nfsdata]# ls
default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5
[root@master nfsdata]# cd default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5/
[root@master default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5]# ls
SUCCESS