k8s之存储、SC、STS、DS篇(一些常见的存储方案)
为什么要做持久化存储?
在k8s中部署的应用都是以pod容器的形式运行的,假如我们部署MySQL、Redis等数据库,需要对这些数据库产生的数据做备份。因为Pod是有生命周期的,如果pod不挂载数据卷,那pod被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod数据持久化存储。
k8s支持那些持久化存储[root@master1 ~]# kubectl explain pods.spec.volumes
常用的如下
emptyDir、hostPath、nfs、persistenVolumeClaim、glusterfs、cephfs、configMap、secret
如果我们使用持久化存储,需要以下步骤
- 定义pod的volume,这个volume要指明它关联到哪个存储上的
- 在容器中要使用volumemounts挂载对应的存储
emptyDir持久化存储
emptyDir是最基础的Volume类型。正如其名字所示,一个 emptyDir Volume是Host上的一个空目录。
emptyDir Volume对于容器来说是持久的,对于Pod则不是。当 Pod从节点删除时,Volume的内容也会被删除。但如果只是容器被销 毁而Pod还在,则Volume不受影响。
也就是说emptyDir Volume的生命周期与Pod一致
Pod中的所有容器都可以共享Volume,它们可以指定各自的 mount路径。
这里我们模拟了一个producer-consumer场景。Pod有两个容器 producer和consumer,它们共享一个Volume。producer负责往Volume 中写数据,consumer则是从Volume读取数据。
① 文件最底部volumes定义了一个emptyDir类型的Volume shared- volume。
② producer容器将shared-volume mount到/producer_dir目录。
③ producer通过echo将数据写到文件hello里。
④ consumer容器将shared-volume mount到/consumer_dir目录。
⑤ consumer通过cat从文件hello读数据
[root@master1 emptydir]# cat emptydir.yaml apiVersion: v1 kind: Pod metadata: name: producer-sumer spec: containers: - image: busybox name: producer volumeMounts: - mountPath: /producer_dir name: shared-volume args: - /bin/sh - -c - echo "hello orld" > /producer_dir/hello ; sleep 3600 - image: busybox name: sumer volumeMounts: - mountPath: /sumer_dir name: shared-volume args: - /bin/sh - -c - cat /sumer_dir/hello; sleep 3600 volumes: - name: shared-volume emptyDir: {}
[root@master1 emptydir]# kubectl apply -f emptydir.yaml pod/producer-sumer created [root@master1 emptydir]# kubectl logs producer-sumer sumer hello orld
kubectl logs显示容器consumer成功读到了producer写入的数据, 验证了两个容器共享emptyDir Volume。
因为emptyDir是Docker Host文件系统里的目录,其效果相当于执 行了docker run -v/producer_dir和docker run -v /consumer_dir。通过 docker inspect查看容器的详细配置信息,我们发现两个容器都mount 了同一个目录,
emptyDir是Host上创建的临时目录,其优点是能够方便地为Pod 中的容器提供共享存储,不需要额外的配置。它不具备持久性,如果 Pod不存在了,emptyDir也就没有了。根据这个特性,emptyDir特别 适合Pod中的容器需要临时共享存储空间的场景,比如前面的生产者 消费者用例
注意 存储的卷一般在宿主机的/var/lib/kubelet/pods目录下
hostpath持久化存储
hostPath Volume的作用是将Docker Host文件系统中已经存在的目 录mount给Pod的容器。大部分应用都不会使用hostPath Volume,因为 这实际上增加了Pod与节点的耦合,限制了Pod的使用。不过那些需 要访问Kuberes或Docker内部数据(配置文件和二进制库)的应用 则需要使用hostPath。
hostPath Volume是指Pod挂载宿主机上的目录或文件。 hostPath Volume使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径)节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。
查看hostpath的用法
[root@master1 ~]# kubectl explain pods.spec.volumes.hostPath
创建一个Pod,挂载HostPath存储卷
[root@master1 volume]# cat hostpath.yaml apiVersion: v1 kind: Pod metadata: name: test-hostpath spec: containers: - name: test-nginx image: nginx volumeMounts: - name: test-volume mountPath: /test-nginx - name: test-tomcat image: tomcat volumeMounts: - name: test-volume mountPath: /test-tomcat volumes: - name: test-volume hostPath: path: /data1 type: DirectoryOrCreate
注意
DirectoryOrCreate表示本地有/data1目录,就用本地的,本地没有就会在pod调度到的节点自动创建一个
常见的type类型有
- 创建pod资源
[root@master1 volume]# kubectl apply -f hostpath.yaml pod/test-hostpath created
- 查看pod资源
[root@master1 volume]# kubectl get pods -o ide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test-hostpath 2/2 Running 0 3m30s 10.1.104.12 node2
- 登录到node2上,看是否存在/data1目录
[root@node2 ~]# ll /data1/ total 0
- 测试卷是否能正常使用
[root@node2 data1]# mkdir test [root@node2 data1]# ls test [root@master1 ~]# kubectl exec -it test-hostpath -c test-nginx -- /bin/bash root@test-hostpath:/# ls /test-nginx/ test [root@master1 ~]# kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash root@test-hostpath:/usr/local/tomcat# ls /test-tomcat/ test
通过上面测试可以看到,同一个pod里的test-nginx和test-tomcat这两个容器是共享存储卷的。
hostpath存储卷缺点
单节点
pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失
NFS持久化存储
hostPath存储,存在单点故障,pod挂载hostPath时,只有调度到同一个节点,数据才不会丢失。那可以使用nfs作为持久化存储。
搭建的nfs服务器
[root@master1 ~]# yum install nfs-utils -y # 创建共享目录 [root@master1 ~]# mkdir /data/volumes -p # 配置nfs共享服务器上的/data/volumes目录 [root@master1 ~]# vim /etc/exports /data/volumes 192.168.0.0/24(r,no_root_squash) #no_root_squash: 用户具有根目录的完全管理访问权限 #使NFS配置生效 [root@master1 ~]# exportfs -arv exporting 192.168.0.0/24:/data/nfs_pro [root@master1 ~]# systemctl restart nfs [root@master1 ~]# systemctl enable nfs
在pod中使用nfs存储作为持久化存储
[root@master1 volume]# cat nfs.yaml apiVersion: v1 kind: Pod metadata: name: test-nfs-volume spec: containers: - name: test-nfs image: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: nginx-port protocol: TCP volumeMounts: - name: nfs-volumes mountPath: /usr/share/nginx/html volumes: - name: nfs-volumes nfs: path: /data/volumes server: 192.168.0.180
- 创建nfs为存储的pod资源
[root@master1 volume]# kubectl apply -f nfs.yaml pod/test-nfs-volume created [root@master1 volume]# kubectl get pods -o ide | grep nfs test-nfs-volume 1/1 Running 0 102s 10.1.166.166 node1
- 测试数据的写入。是否nginx能够访问
[root@master1 volumes]# pd /data/volumes [root@master1 volumes]# cat index.html hello, Nice to meet you! [root@master1 volume]# curl 10.1.166.166 hello, Nice to meet you!
通过上面可以看到,在共享目录创建的index.html已经被pod挂载了,nfs支持多个客户端挂载,可以创建多个pod,挂载同一个nfs服务器共享出来的目录;nfs如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs
PVC持久化存储
参考官网:
https://kuberes.io/docs/concepts/storage/persistent-volumes/#aess-modes
PV是什么?
PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod是k8s集群资源一样。 PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。
PVC是什么?
PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储卷。 它类似于一个pod。 Pod消耗节点资源,PVC消耗PV资源。 Pod可以请求特定级别的资源(CPU和内存)。 pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。
特别说明
PV是群集中的资源。 PVC是对这些资源的请求。
PV和PVC之间的相互作用遵循以下生命周期
- pv的供应方式
-
静态的方式配置PV
集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于Kuberes API中,可供使用。
-
动态的方式配置PV
当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,群集可能会尝试为PVC专门动态配置卷。此配置基于StorageClasses,PVC必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。
-
- 绑定
用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态- 常用访问模式(aessModes)
- ReadWriteOnce表示PV能以read-rite模式mount到单个节点
- ReadOnlyMany表示PV能以read-only模式mount到多个节点
- ReadWriteMany表示PV能以read-rite模式mount到多个节点。
- 常用访问模式(aessModes)
- 使用
- 需要找一个存储服务器,把它划分成多个存储空间
- k8s管理员可以把这些存储空间定义成多个pv
- 在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv
- pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷
- pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了
- 我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处于pending状态
- 回收策略
当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除- Retain表示需要管理员手工回收
当删除pvc的时候,pv仍然存在,处于released状态,它不能被其他pvc绑定使用,里面的数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略 - Recycle表示清除PV中的数据(不推荐使用,1.15可能被废弃了)
- Delete表示删除Storage Provider上的对应存储资源
删除pvc时即会从Kuberes中移除PV,也会从相关的外部设施中删除存储资产
- Retain表示需要管理员手工回收
以nfs为存储服务器,使用pvc作为持久化存储卷
- 配置共享目录的资源,用于创建pv
# 创建用于存储的的共享目录 [root@master1 volume]# mkdir /data/volume_test/v{1,2,3,4,5,6,7,8,9,10} -p # 修改配置文件,实目录成为共享 [root@master1 volume]# cat /etc/exports /data/volumes 192.168.0.0/24(r,no_root_squash) /data/volume_test/v1 192.168.0.0/24(r,no_root_squash) /data/volume_test/v2 192.168.0.0/24(r,no_root_squash) /data/volume_test/v3 192.168.0.0/24(r,no_root_squash) /data/volume_test/v4 192.168.0.0/24(r,no_root_squash) /data/volume_test/v5 192.168.0.0/24(r,no_root_squash) /data/volume_test/v6 192.168.0.0/24(r,no_root_squash) /data/volume_test/v7 192.168.0.0/24(r,no_root_squash) /data/volume_test/v8 192.168.0.0/24(r,no_root_squash) /data/volume_test/v9 192.168.0.0/24(r,no_root_squash) /data/volume_test/v10 192.168.0.0/24(r,no_root_squash) # 加载配置文件 [root@master1 volume]# exportfs -arv exporting 192.168.0.0/24:/data/nfs_pro exporting 192.168.0.0/24:/data/volume_test/v10 exporting 192.168.0.0/24:/data/volume_test/v9 exporting 192.168.0.0/24:/data/volume_test/v8 exporting 192.168.0.0/24:/data/volume_test/v7 exporting 192.168.0.0/24:/data/volume_test/v6 exporting 192.168.0.0/24:/data/volume_test/v5 exporting 192.168.0.0/24:/data/volume_test/v4 exporting 192.168.0.0/24:/data/volume_test/v3 exporting 192.168.0.0/24:/data/volume_test/v2 exporting 192.168.0.0/24:/data/volume_test/v1 exporting 192.168.0.0/24:/data/volumes # 重启服务 [root@master1 volume]# systemctl restart nfs
- 创建PV
参考https://kuberes.io/zh/docs/concepts/storage/persistent-volumes/#reclaiming
[root@master1 volume]# cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: v1 spec: capacity: storage: 1Gi aessModes: ["ReadWriteOnce"] nfs: path: /data/volume_test/v1 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v2 spec: capacity: storage: 2Gi aessModes: ["ReadWriteMany"] nfs: path: /data/volume_test/v2 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v3 spec: capacity: storage: 3Gi aessModes: ["ReadOnlyMany"] nfs: path: /data/volume_test/v3 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v4 spec: capacity: storage: 4Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v4 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v5 spec: capacity: storage: 5Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v5 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v6 spec: capacity: storage: 6Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v6 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v7 spec: capacity: storage: 7Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v7 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v8 spec: capacity: storage: 8Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v8 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v9 spec: capacity: storage: 9Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v9 server: 192.168.0.180 --- apiVersion: v1 kind: PersistentVolume metadata: name: v10 spec: capacity: storage: 10Gi aessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v10 server: 192.168.0.180
[root@master1 volume]# kubectl apply -f pv.yaml persistentvolume/v1 created persistentvolume/v2 created persistentvolume/v3 created persistentvolume/v4 created persistentvolume/v5 created persistentvolume/v6 created persistentvolume/v7 created persistentvolume/v8 created persistentvolume/v9 created persistentvolume/v10 created # #STATUS是Available,表示pv是可用的 [root@master1 volume]# kubectl get pv v1 1Gi RWO Retain Available 54s v10 10Gi RWO,RWX Retain Available 54s v2 2Gi RWX Retain Available 54s v3 3Gi ROX Retain Available 54s v4 4Gi RWO,RWX Retain Available 54s v5 5Gi RWO,RWX Retain Available 54s v6 6Gi RWO,RWX Retain Available 54s v7 7Gi RWO,RWX Retain Available 54s v8 8Gi RWO,RWX Retain Available 54s v9 9Gi RWO,RWX Retain Available 54s
- 创建PVC,绑定到PV
[root@master1 volume]# cat pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: aessModes: ["ReadWriteMany"] resources: requests: storage: 2Gi [root@master1 volume]# kubectl apply -f pvc.yaml persistentvolumeclaim/my-pvc created
- 查看pv的绑定状态
[root@master1 volume]# kubectl get pv | grep v2 v2 2Gi RWX Retain Bound default/my-pvc 10m [root@master1 volume]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi RWX 87s
- 创建Pod资源,使用Pvc持久化存储
[root@master1 volume]# cat pod-pvc.yaml apiVersion: v1 kind: Pod metadata: name: pod-pvc spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent volumeMounts: - name: nginx-html mountPath: /usr/share/nginx/html volumes: - name: nginx-html persistentVolumeClaim: claimName: my-pvc
- 更新Pod资源
[root@master1 volume]# kubectl apply -f pod-pvc.yaml pod/pod-pvc created [root@master1 volume]# kubectl get pods -o ide | grep pod-pvc pod-pvc 1/1 Running 0 111s 10.1.166.167 node1
- 数据持久化存储测试
[root@master1 v2]# pd /data/volume_test/v2 [root@master1 v2]# cat index.html hello, test pv [root@master1 v2]# curl 10.1.166.167 hello, test pv
pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。
[root@master1 volume]# kubectl delete -f pod-pvc.yaml pod "pod-pvc" deleted [root@master1 volume]# kubectl delete -f pvc.yaml persistentvolumeclaim "my-pvc" deleted You have ne mail in /var/spool/mail/root [root@master1 volume]# kubectl apply -f pvc.yaml persistentvolumeclaim/my-pvc created [root@master1 volume]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v4 4Gi RWO,RWX 4s
[root@master1 volume]# cat pv.yaml | grep persistentVolumeReclaimPolicy persistentVolumeReclaimPolicy: Delete [root@master1 volume]# kubectl apply -f pv.yaml persistentvolume/v1 created persistentvolume/v2 created persistentvolume/v3 created persistentvolume/v4 created persistentvolume/v5 created persistentvolume/v6 created persistentvolume/v7 created persistentvolume/v8 created persistentvolume/v9 created persistentvolume/v10 created [root@master1 volume]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE v1 1Gi RWO Retain Available 6s v10 10Gi RWO,RWX Retain Available 6s v2 2Gi RWX Delete Available 6s v3 3Gi ROX Retain Available 6s v4 4Gi RWO,RWX Retain Available 6s v5 5Gi RWO,RWX Retain Available 6s v6 6Gi RWO,RWX Retain Available 6s v7 7Gi RWO,RWX Retain Available 6s v8 8Gi RWO,RWX Retain Available 6s v9 9Gi RWO,RWX Retain Available 6s [root@master1 volume]# kubectl apply -f pvc.yaml persistentvolumeclaim/my-pvc created [root@master1 volume]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi RWX 4s [root@master1 volume]# kubectl delete pvc my-pvc persistentvolumeclaim "my-pvc" deleted You have ne mail in /var/spool/mail/root [root@master1 volume]# kubectl delete pv v2 persistentvolume "v2" deleted [root@master1 v2]# ls index.html [root@master1 volume]# kubectl apply -f pv.yaml [root@master1 volume]# kubectl apply -f pvc.yaml [root@master1 volume]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi RWX 4s
从上面可以看到,即使pv和pvc删除了,也不会删除nfs共享目录下的数据,删除的过的v2pvc,再从重新生成新的pvc,还是会绑定到原来的v2上。持有原来的数据!
PV动态供给
动态供给是通过StorageClass实现的,StorageClass定义了如何创 建PV。
V和PVC模式都是需要先创建好PV,然后定义好PVC和pv进行一对一的Bond,如果PVC请求成千上万,那么就需要创建成千上万的PV,对于运维人员来说维护成本很高,Kuberes提供一种自动创建PV的机制,叫StorageClass,它的作用就是创建PV的模板。k8s集群管理员通过创建storageclass可以动态生成一个存储卷pv供k8s pvc使用。
StorageClass会定义以下两部分
1、PV的属性 ,比如存储的大小、类型等;
2、创建这种PV需要使用到的存储插件,比如Ceph、NFS等
有了这两部分信息,Kuberes就能够根据用户提交的PVC,找到对应的StorageClass,然后Kuberes就会调用 StorageClass声明的存储插件,创建出需要的PV。
StorageClass的provisioner字段storageclass需要有一个供应者,用来确定我们使用什么样的存储来创建pv,常见的provisioner如下:
provisioner既可以由内部供应商提供,也可以由外部供应商提供,如果是外部供应商可以参考https://github./kuberes-incubator/external-storage/下提供的方法创建。
- alloVolumeExpansion允许卷扩展
- PersistentVolume 可以配置成可扩展 将此功能设置为true时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的alloVolumeExpansion字段设置为 true 时,以下类型的卷支持卷扩展。
安装nfs provisioner,用于配合存储类动态生成pv
- 创建运行nfs-provisioner需要的sa( serviceaount )账号
- 什么是sa?
serviceaount是为了方便Pod里面的进程调用Kuberes API或其他外部服务而设计的。
指定了serviceaount之后,我们把pod创建出来了,我们在使用这个pod时,这个pod就有了我们指定的账户的权限了。
- 什么是sa?
[root@master1 storageclass]# cat serviceaount.yaml apiVersion: v1 kind: ServiceAount metadata: name: nfs-provisioner [root@master1 storageclass]# kubectl apply -f serviceaount.yaml serviceaount/nfs-provisioner created
- 对 nfs-provisioner这个sa用户授权
[root@master1 storageclass]# kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaount=default:nfs-provisioner
- 安装nfs-provisioner程序
# 创建提供sc自动创建pv的存储资源 [root@master1 storageclass]# mkdir /data/nfs_pro -p [root@master1 storageclass]# cat /etc/exports /data/nfs_pro 192.168.0.0/24(r,no_root_squash) [root@master1 storageclass]# exportfs -arv exporting 192.168.0.0/24:/data/volumes [root@master1 storageclass]# systemctl restart nfs
[root@master1 storageclass]# cat nfs-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner spec: selector: matchLabels: app: nfs-provisioner replicas: 1 strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAount: nfs-provisioner containers: - name: nfs-provisioner image: registry.-beijing.aliyuncs./mydlq/nfs-subdir-external-provisioner:v4.0.0 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: example./nfs - name: NFS_SERVER value: 192.168.0.180 - name: NFS_PATH value: /data/nfs_pro volumes: - name: nfs-client-root nfs: server: 192.168.0.180 path: /data/nfs_pro
- 查看提供商的pod资源
[root@master1 storageclass]# kubectl apply -f nfs-deployment.yaml deployment.apps/nfs-provisioner created [root@master1 storageclass]# kubectl get pods | grep nfs nfs-provisioner-75c9fb8f-96b22 1/1 Running 0 40s
- 创建storageclass,动态供给pv
[root@master1 storageclass]# cat nfs-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs provisioner: example./nfs # provisioner处写的example./nfs应该跟安装nfs provisioner时候的env下的PROVISIONER_NAME的value值保持一致, [root@master1 storageclass]# kubectl apply -f nfs-storageclass.yaml storageclass.storage.k8s.io/nfs created
- 创建pvc,通过storageclass动态生成pv
[root@master1 storageclass]# cat claim.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-claim spec: aessModes: ["ReadWriteMany"] resources: requests: storage: 1Gi storageClassName: nfs # 这里使用的是上一步创建的storageclass [root@master1 storageclass]# kubectl apply -f claim.yaml persistentvolumeclaim/test-claim created
- 查看pvc的绑定情况
[root@master1 storageclass]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test-claim Bound pvc-49c4f612-8bca-462e-9648-654ac73ff5f1 1Gi RWX nfs 29s
通过上面可以看到test-claim1的pvc已经成功创建了,绑定的pv是pvc-49c4f612-8bca-462e-9648-654ac73ff5f1 ,这个pv是由storageclass调用nfs provisioner自动生成的。
步骤
1、供应商创建一个nfs provisioner
2、创建storageclass,storageclass指定刚才创建的供应商
3、创建pvc,这个pvc指定storageclass
- 创建pod,挂载storageclass动态生成的pvctest-claim1
[root@master1 storageclass]# cat read-pod.yaml apiVersion: v1 kind: Pod metadata: name: read-pod spec: containers: - name: read-pod image: nginx volumeMounts: - name: nfs-pvc mountPath: /usr/share/nginx/html restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim [root@master1 storageclass]# kubectl apply -f read-pod.yaml pod/read-pod created
- 写入数据,访问测试
[root@master1 storageclass]# kubectl get pods -o ide | grep read read-pod 1/1 Running 0 3m3s 10.1.104.19 node2[root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# pd /data/nfs_pro/default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1 [root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# echo "this is test" > index.html [root@master1 default-test-claim-pvc-49c4f612-8bca-462e-9648-654ac73ff5f1]# curl 10.1.104.19 this is test
Statefulset控制器
StatefulSet是为了管理有状态服务的问题而设计的
什么是有状态服务?
StatefulSet是有状态的集合,管理有状态的服务,它所管理的Pod的名称不能随意变化。数据持久化的目录也是不一样,每一个Pod都有自己独有的数据持久化存储目录。比如MySQL主从、redis集群等。
什么是无状态服务
RC、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,所有pod都是共用一个数据卷的,部署的tomcat就是无状态的服务,tomcat被删除,在启动一个新的tomcat,加入到集群即可,跟tomcat的名字无关。
StatefulSet由以下几个部分组成
- Headless Service用来定义pod网路标识,生成可解析的DNS记录
- volumeClaimTemplates存储卷申请模板,创建pvc,指定pvc名称大小,自动创建pvc,且pvc由存储类供应。
- StatefulSet管理pod的
什么是Headless service?
Headless service不分配clusterIP,headless service可以通过解析service的DNS,返回所有Pod的dns和ip地址 (statefulSet部署的Pod才有DNS),普通的service,只能通过解析service的DNS返回service的ClusterIP。
- headless service会为service分配一个域名
service name.name spacename.svc.cluster.local
为什么要用headless service(没有service ip的service)?
在使用Deployment时,创建的Pod名称是没有顺序的,是随机字符串,在用statefulset管理pod时要求pod名称必须是有序的 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。因为pod IP是变化的,所以要用Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称。
-
StatefulSet会为关联的Pod保持一个不变的Pod Name
statefulset中Pod的名字格式为(StatefulSet name)-(pod序号) -
StatefulSet会为关联的Pod分配一个dnsName
Pod name.service name.namespace name.svc.cluster.local
为什么要用volumeClaimTemplate?
对于有状态应用都会用到持久化存储,比如mysql主从,由于主从数据库的数据是不能存放在一个目录下的,每个mysql节点都需要有自己独立的存储空间。而在deployment中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,它们数据是同步的,而statefulset定义中的每一个pod都不能使用同一个存储卷,这就需要使用volumeClainTemplate,当在使用statefulset创建pod时,volumeClainTemplate会自动生成一个PVC,从而请求绑定一个PV,每一个pod都有自己专用的存储卷。
部署eb站点
- 创建存储类
[root@master1 statefulset]# cat class-eb.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-eb provisioner: example./nfs [root@master1 statefulset]# kubectl apply -f class-eb.yaml storageclass.storage.k8s.io/nfs-eb created
- 部署statefulsetful eb服务
[root@master1 statefulset]# cat statefulset.yaml apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: eb clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: eb spec: selector: matchLabels: app: nginx serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: eb volumeMounts: - name: mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: spec: aessModes: ["ReadWriteOnce"] storageClassName: "nfs-eb" resources: requests: storage: 1Gi
- 查看pod资源是否创建成功
[root@master1 statefulset]# kubectl apply -f statefulset.yaml service/nginx unchanged statefulset.apps/eb created [root@master1 statefulset]# kubectl get statefulset -o ide NAME READY AGE CONTAINERS IMAGES eb 2/2 2m17s nginx nginx # 可以看见这个nginx service是没有ip的 [root@master1 statefulset]# kubectl get service -o ide | grep nginx nginx ClusterIP None80/TCP 3m25s app=nginx #可以看到创建的pod是有序的 [root@master1 statefulset]# kubectl get pods -l app=nginx NAME READY STATUS RESTARTS AGE eb-0 1/1 Running 0 5m12s eb-1 1/1 Running 0 5m10s
- 查看pvc的绑定情况
[root@master1 statefulset]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -eb-0 Bound pvc-1a16870f-1ce8-466e-95e7-83a9806e3632 1Gi RWO nfs-eb 3d5h -eb-1 Bound pvc-a86b7d-ae3f-4d35-8972-01a652eb4ae6 1Gi RWO nfs-eb 3d5h
- 测试是否能通过域名解析出pod的IP
[root@master1 statefulset]# kubectl get pods -l app=nginx -o ide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES eb-0 1/1 Running 0 30m 10.1.104.20 node2eb-1 1/1 Running 0 30m 10.1.104.15 node2 [root@master1 ~]# kubectl exec -it eb-0 -- /bin/bash root@eb-0:/# apt-get update root@eb-0:/# apt-get install -y dnsutils root@eb-0:/# nslookup eb-0.nginx.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: eb-0.nginx.default.svc.cluster.local Address: 10.1.104.20 root@eb-0:/# nslookup eb-1.nginx.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: eb-1.nginx.default.svc.cluster.local Address: 10.1.104.15 root@eb-0:/# nslookup nginx.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: nginx.default.svc.cluster.local Address: 10.1.104.20 Name: nginx.default.svc.cluster.local Address: 10.1.104.15
通过上面可以看到可以成功解析出pod里
service 和headless service区别
[root@master1 statefulset]# cat deploy-service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: type: ClusterIP ports: - port: 80 #service的端口,暴露给k8s集群内部服务访问 protocol: TCP targetPort: 80 #pod容器中定义的端口 selector: run: my-nginx #选择拥有run=my-nginx标签的pod --- apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: busybox imagePullPolicy: IfNotPresent ports: - containerPort: 80 mand: - sleep - "3600"
[root@master1 statefulset]# kubectl apply -f deploy-service.yaml service/my-nginx created deployment.apps/my-nginx created [root@master1 statefulset]# kubectl get svc -l run=my-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx ClusterIP 10.104.61.124Statefulset管理pod扩容、缩容、更新80/TCP 12s [root@master1 statefulset]# kubectl get pods -l run=my-nginx NAME READY STATUS RESTARTS AGE my-nginx-68f486d49b-sxzjt 1/1 Running 0 17s my-nginx-68f486d49b-trvbt 1/1 Running 0 17s # 通过上面可以看到deployment创建的pod是随机生成的 [root@master1 statefulset]# kubectl exec -it eb-1 -- /bin/bash root@eb-1:/# nslookup my-nginx.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: my-nginx.default.svc.cluster.local Address: 10.104.61.124 #解析的是service的ip地址 root@eb-1:/# exit
- 扩容
- 方法1 修改配置文件statefulset.yaml里的replicas的值即可,原来replicas: 2,现在变成replicaset: 3
[root@master1 statefulset]# vim statefulset.yaml [root@master1 statefulset]# kubectl apply -f statefulset.yaml service/nginx unchanged statefulset.apps/eb configured [root@master1 statefulset]# kubectl get pods -l app=nginx NAME READY STATUS RESTARTS AGE eb-0 1/1 Running 0 95m eb-1 1/1 Running 0 95m eb-2 1/1 Running 0 15s
-
- 方法2 直接编辑控制器实现扩容
- 方法2 直接编辑控制器实现扩容
[root@master1 statefulset]# kubectl edit sts eb statefulset.apps/eb edited [root@master1 statefulset]# kubectl get pods -l app=nginx NAME READY STATUS RESTARTS AGE eb-0 1/1 Running 0 97m eb-1 1/1 Running 0 97m eb-2 1/1 Running 0 2m26s eb-3 1/1 Running 0 42s
-
缩容更扩容差不多,把数字改小就好了
-
Statefulset实现pod的更新
[root@master1 statefulset]# kubectl edit sts eb statefulset.apps/eb editedDaemonSet概述[root@master1 statefulset]# kubectl get sts -o ide NAME READY AGE CONTAINERS IMAGES eb 4/4 103m nginx ikuberes/myapp:v2 [root@master1 statefulset]# kubectl get pods -o ide -l app=nginx NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES eb-0 1/1 Running 0 69s 10.1.104.18 node2 eb-1 1/1 Running 0 79s 10.1.104.21 node2 eb-2 1/1 Running 0 112s 10.1.166.173 node1 eb-3 1/1 Running 0 2m7s 10.1.104.14 node2
DaemonSet控制器能够确保k8s集群所有的节点都运行一个相同的pod副本,当向k8s集群中增加node节点时,这个node节点也会自动创建一个pod副本,当node节点从集群移除,这些pod也会自动删除;删除Daemonset也会删除它们创建的pod
DaemonSet工作原理如何管理Pod?daemonset的控制器会监听kuberntes的daemonset对象、pod对象、node对象,这些被监听的对象之变动,就会触发syncLoop循环让kuberes集群朝着daemonset对象描述的状态进行演进。
Daemonset典型的应用场景DaemonSet 与 Deployment 的区别在集群的每个节点上运行存储,比如glusterd 或 ceph。
在每个节点上运行日志收集组件,比如flunentd 、 logstash、filebeat等。
在每个节点上运行监控组件,比如Prometheus、 Node Exporter 、collectd等。
Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本。
DaemonSet 的不同之处在于每个 Node 上最多只能运行一个副本。
查看定义Daemonset资源需要的字段有哪些?
[root@master1 ~]# kubectl explain daemonset KIND: DaemonSet VERSION: apps/v1 DESCRIPTION: DaemonSet represents the configuration of a daemon set. FIELDS: apiVersion#当前资源使用的api版本,跟VERSION: apps/v1保持一致 kind #资源类型,跟KIND: DaemonSet保持一致 metadata
查看DaemonSet的spec字段如何定义?
[root@master1 ~]# kubectl explain ds.spec KIND: DaemonSet VERSION: apps/v1 RESOURCE: specDESCRIPTION: The desired behavior of this daemon set. More info: https://git.k8s.io/munity/contributors/devel/sig-architecture/api-conventions.md#spec-and-status DaemonSetSpec is the specification of a daemon set. FIELDS: minReadySeconds #当新的pod启动几秒种后,再kill掉旧的pod。 revisionHistoryLimit #历史版本 selector -required- #用于匹配pod的标签选择器 template -required- #定义Pod的模板,基于这个模板定义的所有pod是一样的 updateStrategy #daemonset的升级策略 #查看DaemonSet的spec.template字段如何定义? #对于template而言,其内部定义的就是pod,pod模板是一个独立的对象
查看DaemonSet的spec字段如何定义?
[root@master1 ~]# kubectl explain daemonset.spec KIND: DaemonSet VERSION: apps/v1 RESOURCE: specDESCRIPTION: The desired behavior of this daemon set. More info: https://git.k8s.io/munity/contributors/devel/sig-architecture/api-conventions.md#spec-and-status DaemonSetSpec is the specification of a daemon set. FIELDS: minReadySeconds #当新的pod启动几秒种后,再kill掉旧的pod。 revisionHistoryLimit #历史版本 selector -required- #用于匹配pod的标签选择器 template -required- #定义Pod的模板,基于这个模板定义的所有pod是一样的 updateStrategy #daemonset的升级策略
查看DaemonSet的spec.template字段如何定义?
[root@master1 ~]# kubectl explain ds.spec.template KIND: DaemonSet VERSION: apps/v1 RESOURCE: templateFIELDS: metadata # 定义pod的元数据 spec # 定义容器
日志收集案例实战
[root@master1 daemonset]# cat daemonset.yaml apiVersion: apps/v1 # Daemonset使用api的版本 kind: DaemonSet # 资源类型 metadata: name: fluentd-elasticsearch # 资源的名字 namespace: kube-system # 资源所在的名称空间 labels: k8s-app: fluentd-logging # 资源具有的标签 spec: selector: # 标签选择器 matchLabels: name: fluentd-elasticsearch template: metadata: labels: name: fluentd-elasticsearch # 基于模版,定义pod的标签 spec: tolerations: # 定义容忍度 - key: node-role.kuberes.io/master effect: NoSchedule containers: #定义容器 - name: fluentd-elasticsearch image: fluentd imagePullPolicy: IfNotPresent resources: # 容器的资源配额 requests: cpu: 100m memory: 200Mi limits: memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log #把本地的/var/log/目录挂载到容器内 - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true # 挂载目录制度权限 terminationGracePeriodSeconds: 30 #优雅关闭服务 volumes: - name: varlog # 基于本地目录,创建一个卷 hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers
查看daemonset控制器的资源
[root@master1 daemonset]# kubectl apply -f daemonset.yaml daemonset.apps/fluentd-elasticsearch unchanged [root@master1 daemonset]# kubectl get ds -n kube-system | grep fluentd-elasticsearch fluentd-elasticsearch 3 3 3 3 365s [root@master1 daemonset]# kubectl get pods -n kube-system -o ide | grep fluentd fluentd-elasticsearch-9nttl 1/1 Running 0 2m59s 10.1.137.100 master1 fluentd-elasticsearch-fb4cb 1/1 Running 0 2m59s 10.1.166.158 node1 fluentd-elasticsearch-lkn7v 1/1 Running 0 2m59s 10.1.104.2 node2
通过上面可以看到在k8s的三个节点均创建了fluentd这个pod
pod的名字是由控制器的名字-随机数组成的
查看滚动更新的字段定义
[root@master1 ~]# kubectl explain ds.spec.updateStrategy.rollingUpdate KIND: DaemonSet VERSION: apps/v1 RESOURCE: rollingUpdateDESCRIPTION: Rolling update config params. Present only if type = "RollingUpdate". Spec to control the desired behavior of daemon set rolling update. FIELDS: maxUnavailable
上面表示rollingUpdate更新策略只支持maxUnavailabe,先删除在更新;因为我们不支持一个节点运行两个pod,需要先删除一个,在更新一个。
- 命令行更新镜像
[root@master1 daemonset]# kubectl set image daemonsets fluentd-elasticsearch fluentd-elasticsearch=ikuberes/filebeat:5.6.6-alpine -n kube-system daemonset.apps/fluentd-elasticsearch image updated
空调维修
- 海信电视维修站 海信电视维修站点
- 格兰仕空调售后电话 格兰仕空调维修售后服务电
- 家电售后服务 家电售后服务流程
- 华扬太阳能维修 华扬太阳能维修收费标准表
- 三菱电机空调维修 三菱电机空调维修费用高吗
- 美的燃气灶维修 美的燃气灶维修收费标准明细
- 科龙空调售后服务 科龙空调售后服务网点
- 华帝热水器维修 华帝热水器维修常见故障
- 康泉热水器维修 康泉热水器维修故障
- 华凌冰箱维修电话 华凌冰箱维修点电话
- 海尔维修站 海尔维修站点地址在哪里
- 北京海信空调维修 北京海信空调售后服务
- 科龙空调维修 科龙空调维修故障
- 皇明太阳能售后 皇明太阳能售后维修点
- 海信冰箱售后服务 海信冰箱售后服务热线电话
- 海尔热水器服务热线