因为pod本身具有生命周期,容器内部的数据是不能永久存储的,有可能某一个pod出现问题后,重新启动的容器就不会在有这个数据的。 存储卷是定义在 Pod 上面被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身 是否支持持久机制。
无状态服务、普通有状态服务和有状态集群服务。
K8S使用RC(或更新的Replica Set)来保证一个服务的实例数量,如果说某个Pod实例由于某种原因Crash了,RC会立刻用这个Pod的模版新启一个Pod来替代它,由于是无状态的服务,新启的Pod与原来健康状态下的Pod一模一样。在Pod被重建后它的IP地址可能发生变化,为了对外提供一个稳定的访问接口,K8S引入了Service的概念重点内容。一个Service后面可以挂多个Pod,实现服务的高可用。
和无状态服务相比,它多了状态保存的需求。Kubernetes提供了以Volume和Persistent Volume为基础的存储系统,可以实现服务的状态保存。
与普通有状态服务相比,它多了集群管理的需求。K8S为此开发了一套以Pet Set为核心的全新特性,方便了有状态集群服务在K8S上的部署和管理。
K8S的存储系统从基础到高级又大致分为三个层次:普通Volume,Persistent Volume 和动态存储供应(dynamic provisioning)。
单节点Volume是最简单的普通Volume,它和Docker的存储卷类似,使用的是Pod所在K8S节点的本地目录。具体有两种,一种是 emptyDir,是一个匿名的空目录,由Kubernetes在创建Pod时创建,删除Pod时删除。另外一种是 hostPath,与emptyDir的区别是,它在Pod之外独立存在,由用户指定路径名。这类和节点绑定的存储卷在Pod迁移到其它节点后数据就会丢失,所以只能用于存储临时数据或用于在同一个Pod里的容器之间共享数据。
这种存储卷不跟某个具体的K8S节点绑定,而是独立于K8S节点存在的,整个存储集群和K8S集群是两个集群,相互独立。
普通volume目前支持的各种存储插件及情况如下:
pv(持久存储卷)一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过Persistent Volume Claim(PV索取),简称PVC来实现动态绑定。Pod定义里指定的是PVC,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用。
用户根据所需存储空间大小和访问模式创建(或在动态部署中已创建)一个PersistentVolumeClaim。
Kubernetes的Master节点循环监控新产生的PVC,找到与之匹配的PV(如果有的话),并把他们绑定在一起。
动态配置时,循环会一直将PV与这个PVC绑定,直到PV完全匹配PVC。避免PVC请求和得到的PV不一致。绑定一旦形成,PersistentVolumeClaim绑定就是独有的,不管是使用何种模式绑定的。
如果找不到匹配的Volume,用户请求会一直保持未绑定状态。在匹配的Volume可用之后,用户请求将会被绑定。比如,一个配置很多50Gi PV的集群不会匹配到一个要求100Gi的PVC。只有在100Gi PV被加到集群之后,这个PVC才可以被绑定。
ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载。
ReadOnlyMany:可以以只读的方式被多个Pod挂载。
ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是NFS。
在PVC绑定PV时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式,在CLI下,访问方式被简写为:
RWO – ReadWriteOnce
ROX – ReadOnlyMany
RWX – ReadWriteMany
PV的生命周期,首先是Provision,即创建PV,这里创建PV有两种方式,静态和动态。
静态PV
管理员手动创建一堆PV,组成一个PV池,供PVC来绑定。
动态PV
在现有PV不满足PVC的请求时,可以使用存储分类(StorageClass),描述具体过程为:PV先创建分类,PVC请求已创建的某个类(StorageClass)的资源,这样就达到动态配置的效果。即通过一个叫 Storage Class的对象由存储系统根据PVC的要求自动创建。
PV创建完后状态会变成Available,等待被PVC绑定。一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。Pod使用完后会释放PV,PV的状态变成Released。变成Released的PV会根据定义的回收策略做相应的回收工作。
有三种回收策略,Retain、Delete 和 Recycle。Retain就是保留现场,K8S什么也不做,等待用户手动去处理PV里的数据,处理完后,再手动删除PV。Delete 策略,K8S会自动删除该PV及里面的数据。Recycle方式,K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用。
在实际使用场景里,PV的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个PV池,开发人员创建Pod和PVC,PVC里定义了Pod所需存储的大小和访问模式,然后PVC会到PV池里自动匹配最合适的PV给Pod使用。
Kubernetes使用NFS共享存储有两种方式:
手动方式静态创建所需要的PV和PVC;
通过创建PVC动态地创建对应PV,无需手动创建PV。
安装环境:nfs数据共享目录------192.168.199.120
#安装NFS文件服务;
yum install nfs-utils -y
#配置共享目录&权限;
[root@master1 ~]# vim /etc/exports
/data/ *(rw,async,no_root_squash)
#启动NFS服务;
systemctl start nfs
[root@master1 ~]# ps -ef |grep ngf
root 30443 28926 0 13:38 pts/1 00:00:00 grep --color=auto ngf
[root@master1 ~]# ps -ef |grep nfs
root 30377 2 0 13:38 ? 00:00:00 [nfsd4_callbacks]
root 30383 2 0 13:38 ? 00:00:00 [nfsd]
root 30384 2 0 13:38 ? 00:00:00 [nfsd]
root 30385 2 0 13:38 ? 00:00:00 [nfsd]
root 30386 2 0 13:38 ? 00:00:00 [nfsd]
root 30387 2 0 13:38 ? 00:00:00 [nfsd]
root 30388 2 0 13:38 ? 00:00:00 [nfsd]
root 30389 2 0 13:38 ? 00:00:00 [nfsd]
root 30390 2 0 13:38 ? 00:00:00 [nfsd]
创建持久化PV存储卷,pv.yaml文件内容如下:
cat>pv.yaml<<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
namespace: default
spec:
capacity:
storage: 10G
accessModes:
- ReadWriteMany
nfs:
# FIXME: use the right IP
server: 192.168.199.120
path: /data/
EOF
PV配置参数如下:
| Capacity 指定 PV 的容量为 100M;
accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。
persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*;
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure、Disk、OpenStack Cinder Volume 等。
storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
指定 PV 在 NFS 服务器上对应的目录。 |
---|
创建持久化PVC存储卷索取,pvc.yaml文件内容如下
cat>pvc.yaml<<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
resources:
requests:
storage: 10G
EOF
[root@master1 nfs]# vim pv.yaml
[root@master1 nfs]# kubectl apply -f pv.yaml
persistentvolume/nfs-pv created
[root@master1 nfs]# kubectl apply -f pvc.yaml
persistentvolumeclaim/nfs-pvc created
[root@master1 nfs]#
[root@master1 nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv 10G RWX 29s
[root@master1 nfs]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv 10G RWX Retain Bound default/nfs-pvc 39s
[root@master1 nfs]#
[root@master1 nfs]# kubectl describe pv
Name: nfs-pv
Labels: <none>
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass:
Status: Bound
Claim: default/nfs-pvc
Reclaim Policy: Retain
Access Modes: RWX
VolumeMode: Filesystem
Capacity: 10G
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.199.120
Path: /data/
ReadOnly: false
Events: <none>
创建Nginx POD容器使用PVC存储卷索取,nginx.yaml文件内容如下:
cat>nginx.yaml<<EOF
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx-v1
labels:
name: nginx-v1
namespace: default
spec:
replicas: 1
selector:
name: nginx-v1
template:
metadata:
labels:
name: nginx-v1
spec:
containers:
- name: nginx-v1
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-data
ports:
- containerPort: 80
volumes:
- name: nginx-data
persistentVolumeClaim:
claimName: nfs-pvc
EOF
[root@master1 nfs]# kubectl describe pods nginx-v1-5ljkf
Mounts:
/usr/share/nginx/html from nginx-data (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-6dgp7 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
nginx-data:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: nfs-pvc
ReadOnly: false
default-token-6dgp7:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-6dgp7
Optional: false
可以在node节点检查是否挂载
root@node1(192.168.199.121)~>mount |grep nfs
192.168.199.120:/data on /var/lib/kubelet/pods/74326c90-7d82-4a9c-b75d-6d4663906ac3/volumes/kubernetes.io~nfs/nfs-pv 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.199.121,local_lock=none,addr=192.168.199.120)
root@node1(192.168.199.121)~>
在存储集群的/data/下创建webapp文件夹
[root@master1 data]# ls
webapps
[root@master1 data]#
在创建的容器内检查数据目录是否同步
[root@master1 data]# kubectl exec -it nginx-v1-5ljkf /bin/bash
root@nginx-v1-5ljkf:/# cd /usr/share/nginx/html/
root@nginx-v1-5ljkf:/usr/share/nginx/html# ls
webapps
通过创建PVC动态地创建对应PV,无需手动创建PV
动态创建PV,是指在现有PV不满足PVC的请求时,可以使用存储分类(StorageClass),描述具体过程为:PV先创建分类,PVC请求已创建的某个类(StorageClass)的资源,这样就达到动态配置的效果。即通过一个叫 Storage Class的对象由存储系统根据PVC的要求自动创建。
其中动态方式是通过StorageClass来完成的,这是一种新的存储供应方式。动态卷供给能力让管理员不必进行预先创建存储卷,而是随用户需求进行创建。
使用StorageClass有什么好处呢?除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。
在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度。如下为动态PV存储操作方式:
NFS默认不支持动态存储,使用了第三方的NFS插件安装NFS插件,GitHub地址:
https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client/deploy
git clone https://github.com/kubernetes-incubator/external-storage.git
cp external-storage/nfs-client/deploy/*.yaml ./
root@master1(192.168.199.120)~/nfs>ls
class.yaml deployment-arm.yaml deployment.yaml rbac.yaml test-claim.yaml test-pod.yaml
2.2.2 修改 deployment.yaml
这里修改的参数包括NFS服务器所在的IP地址(192.168.199.120),以及NFS服务器共享的路径(/data),两处都需要修改为你实际的NFS服务器和共享目录
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
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.199.120
- name: NFS_PATH
value: /data
volumes:
- name: nfs-client-root
nfs:
server: 192.168.199.120
path: /data
依次应用rbac.yaml class.yaml deployment.yaml 配置文件:
kubectl create -f rbac.yaml
kubectl create -f class.yaml
kubectl create -f deployment.yaml
在动态资源供应模式下,通过StorageClass和PVC完成资源动态绑定(系统自动生成PV),并供Pod使用的存储管理机制。
需要注意的是:provisioner属性要等于驱动所传入的环境变量PROVISIONER_NAME的值。否则,驱动不知道知道如何绑定storage class。
此处可以不修改,或者修改provisioner的名字,需要与上面的deployment的PROVISIONER_NAME名字一致。
root@master1(192.168.199.120)~/nfs>cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
imagePullSecrets:
- name: huoban-harbor
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx
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
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: nginx
selector:
app: nginx
clusterIP: None
[root@master1 nfs]# kubectl apply -f nginx.yaml
statefulset.apps/web created
service/nginx created
[root@master1 nfs]#
[root@master1 nfs]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 2m27s
[root@master1 nfs]#
#在/etc/kubernetes/manifests/kube-apiserver.yaml配置文件中加入如下代码:
- --feature-gates=RemoveSelfLink=false
[root@master1 nfs]# docker ps |grep apiserver |awk '{print $1}'|xargs docker restart
87ac86adb660
a4bb1d739ea7
[root@master1 nfs]# kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
pod/kube-apiserver created
[root@master1 nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE 4h5m
www-web-0 Bound pvc-59b45062-7419-46a8-a204-f6bcac25854a 1Gi RWO managed-nfs-storage 3h1m
[root@master1 nfs]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 12m
[root@master1 nfs]#
查看service的cluster-ip为空
[root@master1 nfs]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
centos7 NodePort 10.10.18.21 <none> 22:30001/TCP 6d2h
kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 7d8h
nginx ClusterIP None <none> 80/TCP 62s
登录NFS /data/目录下,可以看到default开头的动态PV创建的目录&相关的文件即可
[root@master1 data]# ls
default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05
检查是否数据共享
[root@master1 data]# cd default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05/
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# ls
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# touch index.html
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]# vim index.html
[root@master1 default-www-web-0-pvc-38a73061-3631-464f-a0ac-4585d28d7b05]#
[root@master1 ~]# kubectl exec -it web-0 bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@web-0:/#
exit
[root@master1 ~]# kubectl exec -it web-0 /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@web-0:/# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
boot docker-entrypoint.d etc lib media opt root sbin sys usr
root@web-0:/# cd /usr/share/nginx/html/
root@web-0:/usr/share/nginx/html# ls
index.html
root@web-0:/usr/share/nginx/html# cat index.html
hello
root@web-0:/usr/share/nginx/html#
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
nfs:
path: /data
server: 192.168.199.120
[root@master1 nfs]#
[root@master1 nfs]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-cbcc9bbc5-9ngbs 1/1 Running 0 29s
nginx-deployment-cbcc9bbc5-ggzzm 1/1 Running 0 24s
nginx-deployment-cbcc9bbc5-tjxlg 1/1 Running 0 27s
[root@master1 nfs]# kubectl
我们进入pod内部可以发现实际上pod内部是使用把192.168.199.120:/data mount 到 /usr/share/nginx/html
root@nginx-deployment-cbcc9bbc5-tjxlg:/# mount |grep 192
192.168.199.120:/data 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.199.123,local_lock=none,addr=192.168.199.120)
root@nginx-deployment-cbcc9bbc5-tjxlg:/#