操作系统:ubuntu 16.04
kubernetes版本:1.10.0
ceph:10.2.10
kubernetes常见的三种应用
1、无状态应用
容器内的应用状态无需保持,
kubernetes通过replicaset保证pod的数量,一旦pod挂掉或崩溃,会基于image重建pod,此时pod内数据丢失。
2、有状态应用
和无状态应用相比,有状态应用多了一个应用状态保存的需求.
3、有状态集群应用
和有状态应用相比,多了集群管理的需求,那么需要解决的问题有两个,一个是应用状态的保存,一个是集群的管理。
有状态应用和有状态集群应用需要持久化存储里面的数据,比如数据库我们需要存储里面的数据库文件,直接用docker,我们可以使用docker的bind-mont、docker-managed-volume 、volume-container的方式,在kubernetes中解决方案是kubernetes volume和kubernetes persistent volume 。volume独立于pod,pod被销毁了数据没有了,但volume还是存在的,可以给其他pod使用。本质上,volume就是一个目录根目录的映射,它后端可以对应各种各样的存储,但pod在使用时,并不需要关心这些,对于pod来说,它看见的只是一个目录,这点根docker volume基本一样,当一个volume mount到pod中时,这个pod中所有容器都可以访问这个volume,kuberne volume支持多种类型的后端存如awsElasticBlockStore、azureDisk、ceph-rbd
完整参考列表
https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes
kubernetes中volume分为两种类型静态供给的和动态供给的
静态供给volume:emptyDir、hostpath,Persistent volume
动态供给volume:storage-class
静态供给volume
缺点:
pod直接访问volume可移值性和可扩展性、安全性较差
emptyDir
emptydir:临时空目录,与pod紧密联接在创建pod是创建,在删除这个pod时,也会自动删除,pod迁移到其他节点数据会丢失。
用途:用于存储一些临时数据,如cookie等。
1 | 例: |
创建一个pod里面有两个container,生产者、消费者共享一个emptyDir,生产者向/producer_dir/hello写入hello world
通过docker inspect查看映射到宿主机哪个目录
1 | docker inspect 5681556dfd95|grep Source |
hostpath:bind-mount与宿主机目录1:1映射,这类存储卷,当数据迁移到其他节点后,就会造成数据丢失。
用途:根DaemonSet配合使用如EFK,中fluentd 根容器日志目录映射,来达到收集日志效果。
例
1 | apiVersion: apps/v1beta1 |
将pod内的/var/lib/mysql与宿主机的/tmp/mysql映射。
storage-private
不使用host存储空间,使用公有云厂商对象存储或分布式
persisten volume
volume虽然能提供很好的数据持久化,但在可管理性上,还是不足的,要使用volume,用户必须,知道当前的volume信息和提前创建好对应的volume,kubernetes推荐使用pv、pvc来解决存储持久化的问题。因此kubernetes给出的解决方案是pv(persistent volume)、pvc(persistent volume Claim)
persisten volume(pv)是k8s里面的一个资源对象,它是直接和底层存储关联的,pv具有持久性,生命周期独立于pod。
persisten volume claim(pvc)是对pv的具体实现,pod使用存储是直接使用pvc,然后pvc会根据pod需要的存储空间大小和访问模式在去寻找合适的pv然后绑定。
kubernetes支持的pv类型
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes
例子
这里以nfs做为pv后端存储为例讲解pv和pvc的使用
搭建nfs
1 | apt install nfs-kernel-server nfs-common rpcbind |
配置共享目录
修改/etc/exports文件
1 | /nfsdata *(rw,sync,no_root_squash) |
1 | *:表示所有用户都可以访问 |
启动rpcbind
1 | systemctl status rpcbind |
启动nfs
1 | systemctl start nfs-kernel-server |
验证
1 | showmount -e nfs_server_ip |
挂载
1 | mount -t nfs 172.31.164.57:/nfsdata /mnt/ |
配置pv
1 | apiVersion: v1 |
pv的回收模式
- persistentVolumeReclaimPolicy 为当pvc删除后pv的回收策略。
- Retain – pvc删除后pv和数据仍然保留但此时不可以在创建pvc了,需要管理员手工回收。
- Recycle – pvc删除后回自动起一个pod将pv内的数据全部清空,可以创建新的pvc。
- Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
pv的访问模式
在pvc绑定pv时通常根据两个条件绑定,一个是存储的大小,另外一个是存储的模式
- ReadWriteOnce(RWO):可读写模式支持单节点挂载。
- ReadOnlyMany(ROX): 只读模式支持多节点挂载。
- ReadWriteMany(RWX):可读写模式支持多节点挂载,目前只有少数存储支持这种方式,像ceph-rbd目前只能当个节点挂载。
创建pvc
1 | kind: PersistentVolumeClaim |
pvc只需要指定大小,访问模式和className就会根pv关联。
创建mysql使用pvc
1 | apiVersion: apps/v1beta1 |
进入container创建些数据
将pod所在宿主机关闭了,过几分钟kubernetes会在另外宿主机上启动,并且还是使用这个pvc,因为nfs支持多个host同时挂载.
动态供给volume(storage_class)
storage-class:
直接使用pv方式都是静态供给,需要管理员提前将pv创建好,然后再与pvc绑定,在kubernetes中动态卷是通过storage-class去实现的。配好storage-class与backend对接,当没有满足pvc条件的pv时,storage-class会动态的去创建一个pv
动态卷的优势
1、不需要提前创建好pv,提高效率和资源利用率
2、封装不同的存储类型给pvc使用,在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度。
在PVC里除了常规的大小、访问模式的要求外,还通过annotation指定了Storage Class的名字为fast,这样这个PVC就会绑定一个SSD,而不会绑定一个普通的磁盘。
例这里我们以ceph-rbd为例配置一个storage-class
因为
操作系统加载rbd module
modprobe rbd
记得加入开机启动脚本,不然重启module又没加载
创建一个ceph-rbd的storage-class
获取admin的secret并用base64加密
1 | ceph auth get-key client.admin |base64 |
创建个secret对象
1 | apiVersion: v1 |
创建storage-class
1 | kind: StorageClass |
monitors: ceph-monitor地址+端口
adminId: secret的用户名
adminSecretName: 上面创建secret的名字
pool:rbd 在ceph哪个pool
userSecretName:上面创建secret的名字
1 | kind: PersistentVolumeClaim |
storageClassName指向我们刚刚创建storage-class
可以看见已经Bound了
查看ceph的pool,发现块设备已经创建好了
继续使用上面的yml创建mysql,会直接用这个pvc
到rke-node3上去
可以看见kernel rbd module将pool里面的块映射出来了,挂载到pod里面了
如果非hyperkube部署的kubernetes集群,如kubeadm部署的,在创建pvc时会报错如下
1 | persistentvolume-controller Warning ProvisioningFailed Failed to provision volume with StorageClass "rbd": failed to create rbd image: executable file not found in $PATH, command output: |
因为我们的k8s集群是使用kubeadm创建的,k8s的几个服务也是跑在集群静态pod中,而kube-controller-manager组件会调用rbd的api,但是因为它的pod中没有安装rbd,所以会报错。解决办法如下
使用kubernetes上的存储扩展卷来解决
https://github.com/kubernetes-incubator/external-storage
1 | git clone https://github.com/kubernetes-incubator/external-storage |
执行
1 | kubectl apply -f ./ |
然后在重新创建storage-class
1 | cat ceph-storageclass.yaml |
1 | kind: StorageClass |
注意provisioner由provisionen: kubernetes.io/rbd改成provisioner: ceph.com/rbd
然后在创建pvc就没有问题了。
使用rbd做为pv的backend或storagclass的backend整体原理如下:
创建pvc—-对应在ceph pool内创建一个rbd对象。
pod挂载这个pvc—-将对应的rbd块通过内核rbd模块map到宿主机上。然后在mount到宿主机 /var/lib/kubelet/pods/xxxx目录,在将这个目录根pod对bind mount。
删除pod—-在对应的host上umount目录然后将rbd块与宿主机unmap。
在kubernetes1.11之前的版本有个问题就是删除pod后,不会自动将rbd块从host上unmap。
PR如下 1.11版本已经修复这个问题
https://github.com/kubernetes/kubernetes/pull/63579
就是在使用pod如果挂载后端为rbd的pvc后,在底层实际上是会从ceph的pool里面将一个rbd块设备map的host上然后在mount到pod内,
需要注意的是:
- 因为在k8s里面是直接用的是内核的rbd块,openstack用的是librbd接口,所以这里需要在每个host上内核要加载rbd模块,不然挂载不上去的,modprobe rbd。
- ceph-rbd 在AccessMode为ReadWriteOnce情况下只支持单节点挂载,不能被多个pod同时使用这个pvc。也就是说一个host挂了,如果上面pod使用了ceph-rbd,哪里在另外一个host上重建这个pod时会报这个pvc已经被使用了,但在AccessMode为ReadWriteOnce+ReadOnlyMany的情况下支持一个pvc被多个pod挂载。
local-pv
Local-pv是让用户使用标准的k8s pv和pvc的接口使用node节点的本地存储,kubernetes1.7为alpha版本,1.14正式GA,这和我们上面说的host-path和empty-Volume有个共同点都是使用node节点的本地存储,但区别在于hostpath是单节点的本地存储,也就是说只有当pod调度到哪台节点便使用那台节点对应的存储,无法提供node亲和性和POD调度支持,但通过localpv可以在pv的定义中配置节点亲和性,这样对应的使用这个pvc的POD也会根据亲和性配置调度到对应的节点上,比如在local-pv的定义配置了主机亲和性为node-2,那么使用这个pvc的pod也会调度到node-2上。
使用场景:
1、分布式数据库和分布式文件系统(redis、ElasticSearch、memcache、ceph),这类应用在应用本身能实现高可用,只是依靠localpv实现高性能存储。
2、需要临时缓存存储的应用,当应用删除后对应的数据也能随之清理,因为使用localpv可以灵活设置pv的回收策略。
测试版本:kubernetesv1.14.3
部署storageclass
1 | apiVersion: storage.k8s.io/v1 |
volumeBindingMode: WaitForFirstConsumer :配置延迟绑定,也就是pv control并不会立刻将pvc与pv关联,而是等待pod调度后才去bond,还有种默认的Immediate模式则是当pvc创建时立刻绑定。
创建pv
1 | apiVersion: v1 |
注意点 : volumeMode: Filesystem为文件系统模式,也就是对应的目录,这里也可以直接设置为BlockVolume模式直接对应主机上的块设备,当需要在kube-apiserver、kube-controlmanager、kubelet开启–feature-gates=BlockVolume=true参数,但这个参数在1.13版本已经默认是true了,参考:https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
local path设置的就是对应宿主机的供挂载的目录,nodeselectorTerms设置的节点的亲和性。
创建pvc
1 | apiVersion: v1 |
创建POD使用
1 | kind: Pod |
局限性
1、目前local-pv不支持对空间申请管理,需要手动对空间进行配置和管理.
2、默认local pv的StorageClass的provisioner是kubernetes.io/no-provisioner , 这是因为local pv不支持Dynamic Provisioning, 所以它没有办法在创建出pvc的时候, 自动创建对应pv.
static-provisioner配置
可以自己实现一个static-provisioner来创建和管理PV,可以直接使用社区已经写的static-provisioner:https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume#option-1-using-the-local-volume-static-provisioner
当然也可以自己实现,这里使用Rancher实现的static-provisioner
https://github.com/rancher/local-path-provisioner
部署
1 | kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml |
默认pv的回收模式为delete模式,需要修改为recycle模式直接修改yaml即可。
执行后会部署
1 | namespace/local-path-storage created |
查看POD
1 | kubectl get pod -n local-path-storage |
查看storage-class
1 | kubectl get storageclass |
创建PVC
1 | kubectl create -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/examples/pvc.yaml |
在没创建pod使用此PVC前,默认模式为 WaitForFirstConsumer,所以pvc的状态会是pending状态。
创建POD
1 | kubectl create -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/examples/pod.yaml |
默认目录为/opt/local-path-provisioner目录下,可以在POD所在节点上查看
当我们需要进行细粒化配置时,如在node1节点上使用/data目录,在node2节点使用/data2目录,其他节点使用/data3目录时可以修改 local-path-storage命名空间内的local-path-config这个configmap
1 | kind: ConfigMap |
更新成功后,查看
1 | kubectl logs pod/local-path-provisioner-848fdcff-lr2pj -n local-path-storage |
可以看见其加载配置的信息。
参考链接
https://www.kubernetes.org.cn/3462.html
https://blog.csdn.net/liukuan73/article/details/60089305