与Docker中一样,同样是通过Volume来管理存储资源,Kubernetes的Volume比Docker的类型多,支持的存储也多,如:nfs、fastdfs、iscsi等
Volume
使用k8s Volume将数据持久化存储映射到容器
Pod的生命周期是短暂的,会被频繁的销毁和创建。容器销毁时,保存在容器内的数据也会被全部清除。
为了持久化保存数据,可以使用Kubernetes Volume
Volume生命周期独立于容器,Pod中的容器可能被销毁和重建,但Volume会被保留
与Docker Volume类似,Kubernetes Volume也是一个目录,当Volume被mount到Pod,Pod中的所有容器都可以访问这个Volume。Kubernetes Volume也支持多种backend类型,包括emptyDir、hostPath、GCE Persistent Disk、AWS Elastic Block Store、NFS、Ceph 等,完整列表参考https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes
Volume 提供了对各种 backend 的抽象,容器在使用 Volume 读写数据的时候不需要关心数据到底是存放在本地节点的文件系统中呢还是云硬盘上。对它来说,所有类型的 Volume 都只是一个目录。
emptyDir存储
emptyDir 是最基础的 Volume 类型。正如其名字所示,一个 emptyDir Volume 是 Host 上的一个空目录
emptyDir Volume 对于容器来说是持久的,对于 Pod 则不是。当 Pod 从节点删除时,Volume 的内容也会被删除。但如果只是容器被销毁而 Pod 还在,则 Volume 不受影响。
也就是说:emptyDir Volume 的生命周期与 Pod 一致。
Pod 中的所有容器都可以共享 Volume,它们可以指定各自的 mount 路径。
配置emptyDir
[root@node1 ~]# vim emptyDir.yml
apiVersion: v1
kind: Pod
metadata:
name: emptydir
spec:
containers:
- name: emptydir
image: busybox
volumeMounts: # 将最下方volumes名为test的目录挂载到容器内的/emptydir目录中
- mountPath: /emptydir
name: test
args: # 在挂载后的目录中创建文件test
- /bin/sh
- -c
- echo "hello world!" > /emptydir/test; sleep 3000000
- name: test # 第二个Pod
image: busybox
volumeMounts: # 将最下方volumes名为test的目录挂载到容器内的/testdir目录中
- mountPath: /testdir
name: test
args: # 因为第一个容器已经将test的volumes中写入了一个test文件,这里直接查看,能看到则挂载成功
- /bin/sh
- -c
- cat /testdir/test; sleep 30000000
volumes:
- name: test
emptyDir: {}
运行Pod
[root@node1 ~]# kubectl apply -f emptyDir.yml
pod/emptydir created
查看Pod
[root@node1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
emptydir 2/2 Running 0 82s
查看第二个容器是否可以执行cat成功
[root@node1 ~]# kubectl logs emptydir test
hello world!
挂载成功~,使用第一个容器emptydir写入hello world!,第二个容器挂载后,可以直接看到第一个容器写入的内容。
目录挂载流程
创建emptydir容器,将emptyDir类型的空目录挂载到容器内的/emptydir目录。
在/emptydir目录中创建一个test文件,内容为hello world!
创建第二个容器test,将emptyDir类型的目录(目录内已有test文件),挂载到容器内的/testdir目录
在test容器中使用cat /testdir/test查看该文件内容,与emptydir容器创建的test一致
前面说到emptyDir类型是主机上的一个空目录,目录可以查看详细信息看到,Pod被创建到node2中,所以在node2中查看
test容器
[root@node2 ~]# docker container inspect k8s_test_emptydir_default_a0caa7aa-3a2f-46ac-ab74-d7ff09898ae4_0
...
"Mounts": [
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/a0caa7aa-3a2f-46ac-ab74-d7ff09898ae4/volumes/kubernetes.io~empty-dir/test",
"Destination": "/testdir",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
...
emptydir容器
[root@node2 ~]# docker container inspect k8s_emptydir_emptydir_default_a0caa7aa-3a2f-46ac-ab74-d7ff09898ae4_0
...
"Type": "bind",
"Source": "/var/lib/kubelet/pods/a0caa7aa-3a2f-46ac-ab74-d7ff09898ae4/volumes/kubernetes.io~empty-dir/test",
"Destination": "/emptydir",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
...
两个容器内的Source目录都是/var/lib/kubelet/pods/a0caa7aa-3a2f-46ac-ab74-d7ff09898ae4/volumes/kubernetes.io~empty-dir/test
,这个就是emptyDir在主机上的真正路径。且只在容器所在主机有此目录
emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。但它不具备持久性,如果 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景
删除方便以下实验
[root@node1 ~]# kubectl delete -f emptyDir.yml
pod "emptydir" deleted
hostPath Volume
hostPath Volume 的作用是将 Docker Host 文件系统中已经存在的目录 mount 给 Pod 的容器。大部分应用都不会使用 hostPath Volume,因为这实际上增加了 Pod 与节点的耦合,限制了 Pod 的使用。不过那些需要访问 Kubernetes 或 Docker 内部数据(配置文件和二进制库)的应用则需要使用 hostPath。
kube-apiserver 和 kube-controller-manager 就是这样的应用
查看kube-apiserver的挂载方式
[root@node1 ~]# kubectl edit --namespace kube-system pod kube-apiserver-node1
# 只截取了关于volume部分的内容
...
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/pki
name: etc-pki
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
...
volumes:
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/pki
type: DirectoryOrCreate
name: etc-pki
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
如果 Pod 被销毁了,hostPath 对应的目录也还会被保留,从这点看,hostPath 的持久性比 emptyDir 强。不过一旦 Host 崩溃,hostPath 也就没法访问了。
例:
[root@node1 ~]# vim httpd.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd-deployment
spec:
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:latest
ports:
- containerPort: 80
volumeMounts:
- name: htdocs
mountPath: /usr/local/apache2/htdocs
readOnly: True
volumes:
- name: htdocs
hostPath:
path: /root/htdocs
因为hostPath方式的目录需要存在,所以需要创建,注意创建的时候,因为master不会运行Pod容器,所以要在node2和node3都有这个目录,否则是映射不到的
[root@node2 ~]# mkdir /root/htdocs
[root@node2 ~]# echo "Chai" >> htdocs/index.html
[root@node2 ~]# scp -r htdocs/* root@node3:/root
运行Pod
[root@node1 ~]# kubectl apply -f httpd.yml
deployment.apps/httpd-deployment created
查看Pod的ip尝试访问
[root@node1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
httpd-deployment-767654dffc-ltjjq 1/1 Running 0 74s 10.244.1.9 node2
httpd-deployment-767654dffc-qhp8x 1/1 Running 0 74s 10.244.1.8 node2
httpd-deployment-767654dffc-vhs2x 1/1 Running 0 74s 10.244.1.7 node2
访问这些Pod的ip
[root@node1 ~]# curl 10.244.1.7
Chai
[root@node1 ~]# curl 10.244.1.8
Chai
[root@node1 ~]# curl 10.244.1.9
Chai
外部存储
如果 Kubernetes 部署在诸如 AWS、GCE、Azure 等公有云上,可以直接使用云硬盘作为 Volume
腾讯云:https://cloud.tencent.com/document/product/457/44237
PV and PVC
使用前面的emptyDir和hostPath都是需要在k8s集群主机必须有目录存在的,一旦集群节点损坏,数据也随之消失,在可管理性上还有不足
如果要使用外部存储的话,事先要知道Volume来自哪个云厂商,创建好的Volume的id。
Pod通常由应用的开发人员维护,而Volume则通常是由存储系统的管理员维护。开发人员要知道云硬盘的具体信息,需要去询问管理员。
这就造成了管理上的问题 ,规模较小或者开发环境还能接受,但当集群规模较大,或者生产环境中,考虑效率和安全,就必须要解决这样的问题
对于这样的问题,Kubernetes有两种解决方案:PersistentVolume和PersistentVolumeClaim,也叫PV和PVC
PersistentVolume(PV)
是外部存储系统中的一块存储空间,由管理员创建和维护。与Volume一样,PV具有持久性,生命周期独立于Pod
PersistentVolumeClaim(PVC)
是对PV的申请(Claim)。PVC通常由普通用户创建和维护,需要为Pod分配存储资源时,用户可以创建一个PVC,指明存储资源的容量代销和访问模式(如:只读)等信息,k8s会查找并提供满足条件的PV。
有了PVC,用户只需要告诉k8s,需要什么样的资源,而不必知道是从哪里分配的资源和如何访问,有外部存储的管理员来处理。
创建NFS PV
搭建nfs
在node1搭建nfs
[root@node1 ~]# yum -y install nfs-utils rpcbind
创建nfs共享目录
[root@node1 ~]# mkdir /nfsdata
将目录共享
[root@node1 ~]# vim /etc/exports
/nfsdata *(rw,no_root_squash,sync)
[root@node1 ~]# exportfs -r
[root@node1 ~]# systemctl start rpcbind nfs-server
[root@node1 ~]# systemctl enable rpcbind nfs-server
在node2查看是否可以看到共享目录
[root@node2 ~]# showmount -e 192.168.1.11
Export list for 192.168.1.11:
/nfsdata *
创建PV
[root@node1 ~]# vim nfs-pv1.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv1
spec:
capacity: # 指定PV容量
storage: 1Gi
accessModes: # 指定访问模式
# - ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
# - ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
# - ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # 当容器被删除时,PV里面的数据也被删除
# 支持的策略有:
# Retain – 需要管理员手工回收。删除容器后数据被保留下来
# Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。
# Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
storageClassName: nfs
#storageClassName指定PV的class为nfs,相当于为PV设置了一个分类,PVC可以指定class申请相应class的PV
nfs: # 指定PV在NFS服务器上对应的目录
path: /nfsdata/pv1 # nfs服务器共享目录,pv1需要创建
server: 192.168.1.11 # nfs服务器ip
创建pv1目录
[root@node1 ~]# mkdir /nfsdata/pv1
运行创建PV
[root@node1 ~]# kubectl apply -f nfs-pv1.yml
persistentvolume/mypv1 created
查看创建好的PV
[root@node1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 1Gi RWO Recycle Available nfs 6s
STATUS
为 Available
,表示 mypv1
就绪,可以被 PVC 申请。
创建NFS PVC
用来申请创建的PV存储资源
[root@node1 ~]# vim nfs-pvc1.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc1
spec:
accessModes: # 同样是设置访问模式
- ReadWriteOnce
resources: # 指定向PV申请的容量
requests:
storage: 1Gi
storageClassName: nfs # Class名要和PV中的Class名一致,就像Service指定Pod的标签一样
运行PVC
[root@node1 ~]# kubectl apply -f nfs-pvc1.yml
persistentvolumeclaim/mypvc1 created
查看PVC
[root@node1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc1 Bound mypv1 1Gi RWO nfs 4s
PV被申请后的变化,CLAIM(申请)字段是mypvc1,说明已经被PVC申请了PV的资源
[root@node1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 1Gi RWO Recycle Bound default/mypvc1 nfs 6h31m
Pod使用PVC
[root@node1 ~]# vim pod1.yml
apiVersion: v1
kind: Pod
metadata:
name: mypod1
spec:
containers:
- name: mypod1
image: busybox
args:
- /bin/sh
- -c
- sleep 30000000
volumeMounts:
- mountPath: '/mydata'
name: mydata
volumes:
- name: mydata
persistentVolumeClaim: # 使用PVC类型的持久化方式
claimName: mypvc1 # 指定使用的PVC的name为mypvc1,容量为1G
运行Pod
[root@node1 ~]# kubectl apply -f pod1.yml
pod/mypod1 created
查看Pod
[root@node1 ~]# kubectl get pod mypod1 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mypod1 1/1 Running 0 106s 10.244.2.84 node3 <none> <none>
在mypod1容器中的挂载目录/mydata中创建一个文件
[root@node1 ~]# kubectl exec mypod1 -- touch /mydata/cyj
[root@node1 ~]# ls /nfsdata/pv1/
cyj
在nfs目录中创建目录pjf
[root@node1 ~]# mkdir /nfsdata/pv1/pjf
[root@node1 ~]# kubectl exec mypod1 -- ls /mydata
cyj
pjf
回收PV
当PV不再需要时,可通过删除PVC回收(谨慎)
当有Pod正在使用PVC时,是不能进行回收的,必须先将Pod删除才能开始PVC的回收
[root@node1 ~]# kubectl delete -f pod1.yml
pod "mypod1" deleted
删除PVC
[root@node1 ~]# kubectl delete pvc mypvc1
persistentvolumeclaim "mypvc1" deleted
[root@node1 ~]# kubectl get pvc
No resources found in default namespace.
但是此时PV还在,只不过没有PVC在申请使用资源了
[root@node1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 1Gi RWO Recycle Available nfs 7h10m
nfs目录中的内容也被删除了
[root@node1 ~]# ls /nfsdata/pv1/
内容被清除是因为设置的回收PV策略为Recycle,如果想要保留数据,则将策略设置为Retain
修改策略
[root@node1 ~]# vim nfs-pv1.yml
persistentVolumeReclaimPolicy: Retain
更新PV
[root@node1 ~]# kubectl apply -f nfs-pv1.yml
persistentvolume/mypv1 configured
验证新的回收策略
# 运行PVC申请PV
[root@node1 ~]# kubectl apply -f nfs-pvc1.yml
persistentvolumeclaim/mypvc1 created
# 运行Pod使用PVC
[root@node1 ~]# kubectl apply -f pod1.yml
pod/mypod1 created
在容器内的挂载目录下创建cgj文件
[root@node1 ~]# kubectl exec mypod1 -- touch /mydata/cgj
[root@node1 ~]# ls /nfsdata/pv1/
cgj
删除PVC后,再次查看文件是否存在于nfs共享目录
[root@node1 ~]# kubectl delete -f pod1.yml
pod "mypod1" deleted
[root@node1 ~]# kubectl delete pvc mypvc1
persistentvolumeclaim "mypvc1" deleted
[root@node1 ~]# ls /nfsdata/pv1/
cgj