1 认识PV/PVC/StorageClass
管理存储是管理计算的一个明显问题。该PersistentVolume子系统为用户和管理员提供了一个API,用于抽象如何根据消费方式提供存储的详细信息。为此,我们引入了两个新的API资源:PersistentVolume和PersistentVolumeClaim。
PersistentVolume(PV)是集群中由管理员配置的一段网络存储。它是集群中的资源,就像节点是集群资源一样。PV是容量插件,如Volumes,但其生命周期独立于使用PV的任何单个pod。此API对象捕获存储实现的详细信息,包括NFS,iSCSI或特定于云提供程序的存储系统。
PersistentVolumeClaim(PVC)是由用户进行存储的请求。它类似于pod。Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是PersistentVolumes对于不同的问题,用户通常需要具有不同属性(例如性能)。集群管理员需要能够提供各种PersistentVolumes不同的方式,而不仅仅是大小和访问模式,而不会让用户了解这些卷的实现方式。对于这些需求,有StorageClass资源。
StorageClass为管理员提供了一种描述他们提供的存储的“类”的方法。不同的类可能映射到服务质量级别,或备份策略,或者由集群管理员确定的任意策略。Kubernetes本身对于什么类别代表是不言而喻的。这个概念有时在其他存储系统中称为“配置文件”。
PVC和PV是一一对应的。
1.1 生命周期
PV是群集中的资源。PVC是对这些资源的请求,并且还充当对资源的检查。PV和PVC之间的相互作用遵循以下生命周期:
Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling
(1)供应准备Provisioning
通过集群外的存储系统或者云平台来提供存储持久化支持。
(1-1)静态提供Static:集群管理员创建多个PV。它们携带可供集群用户使用的真实存储的详细信息。它们存在于Kubernetes API中,可用于消费。
(1-2)动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。要求该类的声明有效地为自己禁用动态配置。
(2)绑定Binding
用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
(3)使用Using
用户可在pod中像volume一样使用pvc。
(4)释放Releasing
用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
(5)回收Recycling
pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
(5-1)保留策略:允许人工处理保留的数据。
(5-2)删除策略:将删除pv和外部关联的存储资源,需要插件支持。
(5-3)回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。
注:目前只有NFS和HostPath类型卷支持回收策略,AWS EBS,GCE PD,Azure Disk和Cinder支持删除(Delete)策略。
1.2 PV类型
1.3 PV卷阶段状态
(1)Available
资源尚未被claim使用
(2)Bound
卷已经被绑定到claim了
(3)Released
claim被删除,卷处于释放状态,但未被集群回收。
(4)Failed
卷自动回收失败
1.4 pv三种访问方式
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes。
在Kubenetes体系内,针对每一个持久化存储卷,都有三种访问方式: ReadWriteOnce(RWO), ReadOnlyMany(ROX), ReadWriteMany(RWX)。
在当前的定义中,这三种方式都是针对节点级别的,也就是说,对于一个Persistent Volume, 如果是RWO, 那么只能被挂载在某一个Kubernetes的工作节点(以下简称节点)上,当再次尝试在其他节点挂载的时候,系统会报Multi-Attach的错误(当然,在只有一台可调度节点的情况,即使RWO也是能够被多个Pod同时使用)。
如果是RWX, 那么可以同时在多个节点上挂载并被不同的Pod使用。
三种访问方式。
2 演示:创建PV静态提供
(1)在nfs服务器上先建立存储卷对应的目录
cd /data/volumes
mkdir v{1,2,3,4,5}
echo "<h1>static stor 01</h1>" > v1/index.html
echo "<h1>static stor 02</h1>" > v2/index.html
echo "<h1>static stor 03</h1>" > v3/index.html
echo "<h1>static stor 04</h1>" > v4/index.html
echo "<h1>static stor 05</h1>" > v5/index.html
(2)修改nfs的配置
#vim /etc/exports
/data/volumes/v1 *(rw,no_root_squash)
/data/volumes/v2 *(rw,no_root_squash)
/data/volumes/v3 *(rw,no_root_squash)
/data/volumes/v4 *(rw,no_root_squash)
/data/volumes/v5 *(rw,no_root_squash)
其中,rw:读/写权限,只读权限的参数为ro;
其中,no_root_squash:NFS 服务器共享目录用户的属性,如果用户是 root,那么对于这个共享目录来说就具有 root 的权限。
(3)查看nfs的配置
#exportfs -arv
(4)使配置生效
#showmount -e
2.1 创建持久卷pv
创建5个pv,存储大小各不相同,是否可读也不相同。pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: myuse1
accessModes: ["ReadWriteMany","ReadWriteOnce"]
storageClassName: slow
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: myuse2
accessModes: ["ReadWriteOnce"]
storageClassName: slow
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: myuse3
accessModes: ["ReadWriteMany","ReadWriteOnce"]
storageClassName: slow
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: myuse4
accessModes: ["ReadWriteMany","ReadWriteOnce"]
storageClassName: slow
capacity:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: myuse5
accessModes: ["ReadWriteMany","ReadWriteOnce"]
storageClassName: slow
capacity:
storage: 15Gi
#kubectl apply -f pv.yaml
#kubectl get pv
#kubectl delete pv pv001删除指定pv
(1)回收策略:保留(Retain)
(2)Available资源尚未被claim使用
PV可以有一个类,通过将storageClassName属性设置为StorageClass的名称来指定。 特定类的PV只能绑定到请求该类的PVC。 没有storageClassName的PV没有类,只能绑定到不需要特定类的PVC。
在过去,使用了注释volume.beta.kubernetes.io/storage-class而不是storageClassName属性。 该注释仍然可以工作,但将来Kubernetes版本将不再适用。
2.2 创建持久卷声明PVC
2.2.1 指定selector和storageclass
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
storageClassName: slow
selector:
matchLabels:
name: pv003
#kubectl get pvc
#kubectl get pv
2.2.2 仅指定storageclass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 6Gi
storageClassName: slow
#kubectl get pv
创建一个pvc,需要6G存储;所以不会匹配pv001、pv002、pv003
#kubectl get pvc
2.2.3 删除处于terminiting状态的pv
#kubectl patch pvc mypvc -p '{"metadata":{"finalizers":null}}'
2.3 在pod中使用PVC
2.3 1 测试tomcat镜像
#docker pull tomcat
#docker run -id -p 8080:8080 --name=c_tomcat -v /data/volumes/:/usr/local/tomcat/webapps tomcat:latest
#docker exec -it c_tomcat /bin/bash
http://10.23.241.97:8080/v1/index.html
http://10.23.241.97:8080/v2/index.html
#docker stop c_tomcat
2.3.2 文件deployment.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-pvc
namespace: default
labels:
app: myapp
spec:
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
containers:
- name: myapp
image: tomcat:latest
volumeMounts:
- name: html
mountPath: /usr/local/tomcat/webapps
部署
#kubectl apply -f deployment.yaml
#kubectl exec -it vol-pvc -n default -- /bin/bash
在目录/data/volumes/v4中创建子目录
#mkdir aa
#cp index.html ./aa
进入pod中查看tomcat的运行情况
2.3.3 文件service.yaml
kind: Service
apiVersion: v1
metadata:
name: mysvc
namespace: default
spec:
type: NodePort
ports:
- port: 8123
nodePort: 32001
targetPort: 8080
selector:
app: myapp
Service中主要涉及三种Port:
(1)port 这里的port表示service暴露在clusterIP上的端口,clusterIP:Port 是提供给集群内部访问kubernetes服务的入口。
(2)targetPort
也是containerPort,targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。
(3)nodePort
nodeIP:nodePort 是提供给从集群外部访问kubernetes服务的入口。
总的来说,port和nodePort都是service的端口,前者暴露给从集群内访问服务,后者暴露给从集群外访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端具体pod的targetPort,从而进入到pod上的容器内。
创建Service的type类型不同,可分成不同模式:
(1)ClusterIP: 默认方式。根据是否生成ClusterIP又可分为普通Service和Headless Service两类:
(1-1)普通Service:通过为Kubernetes的Service分配一个集群内部可访问的固定虚拟IP(Cluster IP),实现集群内的访问。为最常见的方式。
(1-2)Headless Service:该服务不会分配Cluster IP,也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为podIP列表。主要供StatefulSet使用。
(2)NodePort:除了使用Cluster IP之外,还通过将service的port映射到集群内每个节点的相同一个端口,实现通过nodeIP:nodePort从集群外访问服务。
查看网页
2.4 删除PVC
一般删除步骤为:先删pod再删pvc最后删pv
(1)删除pod
#kubectl delete pod volumeop-basic-csgvg-275778418 -n kubeflow
#kubectl delete pod volumeop-basic-csgvg-3408782246 -n kubeflow
(2)删除pvc
#kubectl delete pvc volumeop-basic-csgvg-my-pvc -n kubeflow
(3)删除pv
在删除pvc的时候pv会被自动删掉。
3 基于存储类实现PV动态提供
在k8s中部署有状态应用时,通常需要做数据持久化存储。
基于存储类,实现PV自动供给;
(创建存储类,在资源清单中指明地址和共享挂载卷目录即可实现持久化存储)
下载项目:
for file in class.yaml deployment.yaml rbac.yaml test-claim.yaml ;
do wget https://raw.githubusercontent.com/kubernetes-incubator/external-storage/master/nfs-client/deploy/$file ;
done
3.1 class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs
parameters:
archiveOnDelete: "false"
provisioner: fuseim.pri/ifs # or choose another name, must match deployment’s env PROVISIONER_NAME’
3.2 rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
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
# replace with namespace where provisioner is deployed
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
# replace with namespace where provisioner is deployed
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
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
3.3 deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
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: quay.io/external_storage/nfs-client-provisioner:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.0.165
- name: NFS_PATH
value: /some/path
volumes:
- name: nfs-client-root
nfs:
server: 192.168.0.165
path: /some/path
创建存储类
#kubectl create -f class.yaml
#kubectl create -f rbac.yaml
#kubectl create -f deployment.yaml
3.4 修改一个storageclass为默认
default表示这个storageclass是默认的。
# kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
4 测试
部署一个pvc或者声明存储的应用,测试是否自动创建出PV而且自动绑定PVC,
4.1 test-claim.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 create -f test-claim.yaml
4.2 nginx-demo.yaml
StatefulSet方式部署的nginx应用
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "managed-nfs-storage"
resources:
requests:
storage: 1Gi