k8s之存储、SC、STS、DS篇(一些常见的存储方案)

家电修理 2023-07-16 19:17www.caominkang.com电器维修

为什么要做持久化存储?

在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到多个节点。
  • 使用
    • 需要找一个存储服务器,把它划分成多个存储空间
    • 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,也会从相关的外部设施中删除存储资产

以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/下提供的方法创建。

StorageClass的reclaimPolicy字段 回收策略
  • alloVolumeExpansion允许卷扩展
  • PersistentVolume 可以配置成可扩展 将此功能设置为true时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的alloVolumeExpansion字段设置为 true 时,以下类型的卷支持卷扩展。

安装nfs provisioner,用于配合存储类动态生成pv

  • 创建运行nfs-provisioner需要的sa( serviceaount )账号
    • 什么是sa?
      serviceaount是为了方便Pod里面的进程调用Kuberes API或其他外部服务而设计的。
      指定了serviceaount之后,我们把pod创建出来了,我们在使用这个pod时,这个pod就有了我们指定的账户的权限了。
[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都有自己专用的存储卷。

Statefulset使用案例

部署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   None     80/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   node2        
eb-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.124     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
Statefulset管理pod扩容、缩容、更新
  • 扩容
    • 方法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 直接编辑控制器实现扩容
[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 edited
  

[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概述

DaemonSet控制器能够确保k8s集群所有的节点都运行一个相同的pod副本,当向k8s集群中增加node节点时,这个node节点也会自动创建一个pod副本,当node节点从集群移除,这些pod也会自动删除;删除Daemonset也会删除它们创建的pod

DaemonSet工作原理如何管理Pod?

daemonset的控制器会监听kuberntes的daemonset对象、pod对象、node对象,这些被监听的对象之变动,就会触发syncLoop循环让kuberes集群朝着daemonset对象描述的状态进行演进。

Daemonset典型的应用场景

在集群的每个节点上运行存储,比如glusterd 或 ceph。
在每个节点上运行日志收集组件,比如flunentd 、 logstash、filebeat等。
在每个节点上运行监控组件,比如Prometheus、 Node Exporter 、collectd等。

DaemonSet 与 Deployment 的区别

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	 #定义容器的
   status	  #状态信息,不能改
 

查看DaemonSet的spec字段如何定义?

[root@master1 ~]# kubectl explain ds.spec
KIND:  DaemonSet
VERSION:  apps/v1
RESOURCE: spec 
DESCRIPTION:
  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: spec 
DESCRIPTION:
  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: template 
FIELDS:
   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   3            65s
[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的名字是由控制器的名字-随机数组成的

daemonset的滚动更新

查看滚动更新的字段定义

[root@master1 ~]# kubectl explain ds.spec.updateStrategy.rollingUpdate
KIND:  DaemonSet
VERSION:  apps/v1
RESOURCE: rollingUpdate 
DESCRIPTION:
  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

Copyright © 2016-2025 www.caominkang.com 曹敏电脑维修网 版权所有 Power by