K8s对于有状态的容器应用或者对数据需要持久化的应用,不仅需要将容器内的目录挂载到宿主机的目录或者emptyDir临时存储卷,而且需要更加可靠的存储来保存应用产生的重要数据,以便容器应用在重建之后,仍然可以使用之前的数据。K8s引入PersistentVolume和PersistentVolumeClaim两个资源对象来实现对存储的管理子系统。
PV作为存储资源,主要包括存储能力、访问模式、存储类型、回收策略、后端存储类型等关键信息的设置。
下面的例子生命的PV具有如下属性:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity: 5Gi
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
nfs:
path: /tmp
server: 172.17.0.2
K8s支持的PV类型如下:
1)存储能力(Capacity)
2)访问模式(Access Modes)
对PV进行访问模式的设置,用于描述用户应用对存储资源的访问的权限。访问模式如下:
ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂载。
ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。
ReadWriteMany(RWX):读写权限,允许被多个Node挂载。
某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问模式,多种访问模式不能同时生效。
不同的存储提供者支持的访问模式:
Volume Plugin | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
---|---|---|---|
ClusterFS | ✅ | ✅ | ✅ |
HostPath | ✅ | ||
NFS | ✅ | ✅ | ✅ |
3)存储类别(Class)
4)回收策略(Reclaim Policy)
目前支持如下三种回收策略:
目前NFS和HostPath两种类型的存储支持Recyle策略。
AWS EBS、GCE PD、Azure Disk和Cinder volumes支持Delete策略。
某个PV在生命周期中,可能处于以下4个阶段之一。
在将PV挂载到一个Node时,根据后端存储的特点,可能需要设置额外的挂载参数,可以通过在PV的定义中,设置一个名为"volume.beta.kubernetes.io/mount-options"的annotation来实现。
下面的例子对一个类型为gcePersistentDisk的PV设置了挂载参数"discard":
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: gce-disk-1
annotations:
volume.beta.kubernetes.io/mount-options: "discard"
spec:
capacity:
storage: "10Gi"
accessModes:
- "ReadWriteOnce"
gcePersistentDisk:
fsType: "ext4"
pdName: "gce-disk-1"
PVC作为用户对存储资源的需求申请,主要包括存储空间请求、访问模式、PV选择条件和存储类别等信息的设置。
下面的例子声明的PVC具有如下属性:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In,values: [dev]}
PVC的关键配置参数说明如下:
注意:
K8s支持两种资源供应模式:
资源供应的结果就是创建好的PV。
在静态资源供应模式下,通过PV和PVC完成绑定,并供Pod使用的存储管理机制。
在动态资源供应模式下,通过StorageClass和PVC完成资源动态绑定(系统自动生成PV),并供Pod使用的存储管理机制。
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节。
使用基于StorageClass的动态资源供应模式将逐步成为云平台的标准存储配置模式。
StorageClass的定义主要包括名称、后端存储的提供者(Provisioner)和后端存储的相关参数配置。StorageClass一旦被创建出来,将无法修改。如需更改,则只能删除原StorageClass的定义重建。
下面的例子定义了一个名为"standard"的StorageClass,提供者为aws-ebs,其参数设置了一个type=gp2。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
1)提供者(Provisioner)
描述存储资源的提供者,也可以看作后端存储驱动。目前K8s支持的Provisioner都以kubernetes.io/为开头。
2)参数(Parameters)
后端存储资源提供者的参数设置,不同的Provisioner包括不同的参数设置。
以GlusterFS存储卷为例子:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://127.0.0.1:8081"
clusterid: "630839984929283f99393"
restauthenabled: "true"
restuser: "admin"
secretNamespace: "default"
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"
要在系统中设置一个默认的StorageClass,首先需要启用名为DefaultStorageClass的admission controller,即在kube-apiserver的命令行参数–admission-control中增加:
--admission-control=...,DefaultStorageClass
然后在StorageClass的定义中设置一个annotation:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gold
annotations:
storageclass.beta.kubernetes.io/is-default-class="true"
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
通过kubectl create命令创建成功后,查看StorageClass列表,可以看到名为gold的StorageClass被标记为"default":
kubectl get sc
NAME TYPE
gold (default) kubernetes.io/gce-pd
本节以GlusterFS为例,从定义StorageClass、创建GlusterFS和Heketi服务、用户申请PVC到创建Pod使用存储资源,对StorageClass和动态资源分配进行详细说明,进一步剖析K8s的存储机制。
为了能够使用GlusterFS,首先在计划用于GlusterFS的各Node上安装GlusterFS客户端:
yum install glusterfs glusterfs-fuse
GlusterFS管理服务容器需要以特权模式运行,在kube-apiserver的启动参数中增加:
--allow-privileged=true
给要部署GlusterFS管理服务的节点打上"storagenode=glusterfs"的标签,是为了将GlusterFS容器定向部署到安装了GlusterFS的Node:
kubectl label node k8s-node-1 storagenode=glusterfs
kubectl label node k8s-node-2 storagenode=glusterfs
kubectl label node k8s-node-3 storagenode=glusterfs
GlusterFS管理服务容器以Daemonset的方式进行部署,确保每台Node上都运行一个GlusterFS管理服务。
glusterfs-daemonset.yaml内容如下:
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: glusterfs
labels:
glusterfs: daemonset
annotations:
description: GlusterFS DaemonSet
tags: glusterfs
spec:
template:
metadata:
name: glusterfs
labels:
glusterfs-node: pod
spec:
nodeSelector:
storagenode: glusterfs
hostNetwork: true
containers:
- image: gluster/gluster-centos:latest
name: glusterfs
volumeMounts:
- name: glusterfs-heketi
mountPath: "/var/lib/heketi"
- name: glusterfs-run
mountPath: "/run/lvm"
- name: glusterfs-etc
mountPath: "/etc/glusterfs"
- name: glusterfs-logs
mountPath: "/var/lib/glusterfs"
- name: glusterfs-config
mountPath: "/var/lib/glusterd"
- name: glusterfs-dev
mountPath: "/dev"
- name: glusterfs-misc
mountPath: "/var/lib/misc/glusterfsd"
- name: glusterfs-cgroup
mountPath: "/sys/fs/cgroup"
readOnly: true
- name: glusterfs-ssl
mountPath: "/etc/ssl"
readOnly: true
securityContext:
capabilities: {}
privileged: true
readinessProbe:
timeoutSeconds: 3
initialDelaySeconds: 60
exec:
command:
- "/bin/bash"
- "-c"
- systemctl status glusterd.service
livenessProbe:
timeoutSeconds: 3
initialDelaySeconds: 60
exec:
command:
- "/bin/bash"
- "-c"
- systemctl status glusterd.service
volumes:
- name: glusterfs-heketi
hostPath:
path: "/var/lib/heketi"
- name: glusterfs-run
- name: glusterfs-lvm
hostPath:
path: "/run/lvm"
- name: glusterfs-etc
hostPath:
path: "/etc/glusterfs"
- name: glusterfs-logs
hostPath:
path: "/var/log/glusterfs"
- name: glusterfs-config
hostPath:
path: "/var/lib/glusterd"
- name: glusterfs-dev
hostPath:
path: "/dev"
- name: glusterfs-misc
hostPath:
path: "/var/lib/misc/glusterfsd"
- name: glusterfs-cgroup
hostPath:
path: "/sys/fs/cgroup"
- name: glusterfs-ssl
hostPath:
path: "/etc/ssl"
kubectl create -f glusterfs-daemonset.yaml
daemonset "glusterfs" created
kubectl get po
Heketi是一个提供RESTful API管理GlusterFS卷的框架,并能够在OpenStack、K8s等云平台上实现动态存储资源供应,支持GlusterFS多集群管理,便于管理GlusterFS进行操作。
在部署Heketi服务之前,需要为它创建ServiceAccount对象:
heketi-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: heketi-service-account
kubectl create -f heketi-service-account.yaml
部署Heketi服务:
heketi-deployment-svc.yaml
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: deploy-heketi
labels:
glusterfs: heketi-deployment
deploy-heketi: heketi-deployment
annotations:
description: Defines how to deploy Heketi
spec:
replicas: 1
template:
metadata:
name: deploy-heketi
labels:
name: deploy-heketi
glusterfs: heketi-pod
spec:
serviceAccountName: heketi-service-account
containers:
- image: heketi/heketi:dev
name: deploy-heketi
env:
- name: HEKEATI_EXECUTOR
value: kubernetes
- name: HEKETI_FSTAB
value: "/var/lib/heketi/fstab"
- name: HEKETI_SNAPSHOT_LIMIT
value: '14'
- name: HEKETI_KUBE_GLUSTER_DAEMONSET
value: "y"
ports:
- containerPort: 8080
volumeMounts:
- name: db
mountPath: "/var/lib/heketi"
readinessProbe:
timeoutSeconds: 3
initialDelaySeconds: 3
httpGet:
path: "/hello"
port: 8080
livenessProbe:
timeoutSeconds: 3
initialDelaySeconds: 30
httpGet:
path: "/hello"
port: 8080
volumes:
- name: db
hostPath:
path: "/heketi-data"
---
kind: Service
apiVersion: v1
metadata:
name: deploy-heketi
labels:
glusterfs: heketi-service
deploy-heketi: support
annotations:
description: Exposes Heketi Service
spec:
selector:
name: deploy-heketi
ports:
- name: deploy-heketi
port: 8080
targetPort: 8080
注意:Heketi的db数据需要持久化保存,建议使用hostPath或其他共享存储进行保存。
kubectl create -f heketi-deployment-svc.yaml
在Heketi能够管理GlusterFS集群之前,首先要为其设置GlusterFS集群的信息。可以用一个topology.json配置文件来完成各个GlusterFS节点和设备的定义。Heketi要求一个GlusterFS集群至少有3个节点。
进入Heketi容器,使用命令行工具heketi-cli完成GlusterFS集群的创建:
kubectl-cli topology load --json=topology.json
经过这个操作,Heketi完成了GlusterFS集群的创建,同时在GlusterFS集群的各个节点的/dev/sdb盘上成功创建了PV和VG。
查看Heketi的topology信息,可以看到Node和Device的详细信息,包括磁盘空间的大小和剩余空间。Volumes和Bricks还未创建。
heketi-cli topology info
准备工作已经就绪,集群管理员可以在K8s集群定义一个StorageClass了。
storageclass-gluster-heketi.yaml配置文件内容如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gluster-heketi
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://172.17.2.2:8080"
restauthenabled: "false"
Provisioner参数必须设置为:kubernetes.io/glusterfs
resturl的地址需要设置为API Server所在主机可以访问到的Heketi服务的某个地址,可以使用服务ClusterIP+端口号、容器IP地址+端口号或将服务映射到物理机,使用物理机IP+NodePort。
创建这个StorageClass资源对象:
kubectl create -f storageclass-gluster-heketi.yaml
用户可以申请一个PVC了。例如一个用户申请一个1Gi空间的共享存储资源。StorageClass使用gluster-heketi,未定义任何Selector,说明使用动态资源供应的模式。
pvc-gluster-heketi.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-gluster-heketi
spec:
storageClassName: gluster-heketi
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
kubectl create -f pvc-gluster-heketi.yaml
PVC定义一旦生成,系统便将触发Heketi进行相应的操作,主要为在GlusterFS集群上创建brick,再创建并启动一个volume。
查看PVC的状态,可见其已经为Bound(已绑定):
kubectl get pvc
查看PV,可见系统自动创建PV
kubectl get pv
查看PV的详细信息,可以看到容量、引用的StorageClass等信息都已正确设置,状态也为Bound(已绑定),回收策略则为默认的Delete。同时Gluster的Endpoint和Path也由Heketi自动完成了设置。
至此一个可供Pod使用的PVC就创建成功了,接下来Pod就能通过volume宕设置将这个PVC挂载到容器内部进行使用。
在Pod中使用PVC定义的存储资源非常容易,只需设置一个volume,类型为persistentVolumeClaim,即可轻松引用一个PVC。
下例中使用一个busybox容器验证对PVC的使用,注意Pod需要与PVC属于同一个namespace:
pod-use-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-use-pvc
spec:
containers:
- name: pod-use-pvc
image: busybox
command:
- sleep
- "3600"
volumeMounts:
- name: gluster-volume
mountPath: "/pv-data"
readOnly: false
volumes:
- name: gluster-volume
persistentVolumeClaim:
claimName: pvc-gluster-heketi
kubectl create -f pod-use-pvc.yaml
进入容器pod-use-pvc,在/pv-data目录下创建一些文件:
kubectl exec -ti pod-use-pvc -- /bin/sh
cd /pv-data
touch a
echo "hello" > b
可以验证文件a和b在GlusterFS集群中正确生成。
在使用动态存储供应模式的情况下,相对于静态模式的优势至少包括以下两点: