YAZONG 我的开源

Kubernetes(十一)落地实践(11.4/5)共享存储 --- PV、PVC和StorageClass(上/下)

  , , ,
0 评论0 浏览

硬骨头。

前面的服务都是无状态的。

有状态的应用就比较麻烦了,最常见的一种有状态就是本地存储。

不好的习惯:把文件保存到服务器目录。但很常见。

Docker每次重新部署,都会导致目录被清空。

因为每新建一个容器,磁盘空间都是全新的。

目录挂载呢?设置好节点的亲和性,节点每次调度到固定节点上,可行,但缺陷比较大,绑定了固定机器,不得不考虑数据备份问题,绑定到一台机器,那么只有一个副本,会有风险的。

这些问题K8S考虑到了,一个方案,共享存储。

PV

image.png

PV是PersistentVolume的简写,也是K8S的一种资源,描述的是持久化数据卷。

这里标注的是NFS的挂载目录。

PV一般来说,是运维事先定义好的。

定义哪些东西呢?

类型kind。

比较重要的字段capacity,存储容量是多少。

对于这块存储的访问模式是什么,accessModes,只有一个POD可以去使用这个PV,读写权限。

如果多个POD需要共享这个磁盘PV,可以设置ReadWriteMany。

nfs是K8S内置的存储类型,对nfs类型的存储,需要提供这样的信息,挂载到远程的哪个目录下,nfs的地址是什么。

上面就描述了一个nfs类型的共享存储的一个方法。

PVC

image.png

类型kind:描述的是一个POD希望使用的持久化存储的属性或一个需求。

比如使用一个磁盘,volume的大小、续写权限呢?

PVC一般是由开发人员管理的。这个里面要求具有读写权限的、并且只能被我独占的有实际空间大小的存储。这是描述了一个POD对共享存储的需求、一个期望。

PV和PVC的绑定

image.png

当PV和PVC两个资源都创建好了,

那么就可以kubectl create起来了

此时,POD还不能使用。

PV和PVC必须建立好绑定关系,才能达到可用状态。

如何才能绑定上呢?

首先得匹配,PV得满足PVC的需求,像存储大小、读写权限都要满足。

然后,PV和PVC的storageClassName必须一致,当满足这个条件之后,kubelet中有个persistVolumeController就会发现匹配的PV,自动的建立绑定关系,本质上,就是在PVC的资源描述对象里把PV的名字填进去,那么PV和PVC就绑定在一起了。

POD

image.png

当PV和PVC绑定之后,POD终于可以像普通的volume一样,去使用PVC了。

上述图跟之前的volume区别不大,唯一的区别是在红框中多个persistentVolumeClaim,指定了PVC的名字。

通过PV和PVC这两层的抽象,POD在使用共享存储的时候就比较简单,具体原理也不复杂。

POD里面声明了PVC的名字,PVC里描述了POD的需求,比如权限、空间,并且PVC绑定了一个PV,PV里描述了具体的后端、服务的地址如何访问、参数。

关于后端具体的存储服务和PV的配置一般由集群管理员事先配置好,其他由K8S自己去解决。

包括运维绑定的PV,开发定义的PVC,建立起绑定关系,把POD调度起来,把POD的volume设置好,都是由K8S来搞定的。

VOLUME

image.png

其中设置volume是比较复杂的一步。

首先POD的所有的volume都会在本地对应到具体的目录,

像普通的hostPath和emptyDir类型的volume对应的就是一个普通的目录。

但是对于共享存储,就不再是普通的目录了,每种类型的后端存储服务有不同的处理过程,像NFS就比较简单,kubelet会把这个目录远程挂载到PV指定的位置去,相当于执行一条mount -t nfs命令,把这个本地的目录挂载到远程的nfs server上,挂载好了之后,对于docker来说,这个目录并没有什么区别,还是一样使用docker run -v这个参数,把这个目录映射到容器里面,就实现了这个容器的共享存储。

StorageClass

#去管理glusterfs,本质上是去管理POD。所以需要去定义ClusterRole和ClusterRoleBinding。

#把ClusterRole和ServiceAccount绑定在一起。让ServiceAccount具备相应的操作权限。

#用法

https://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs”

https://github.com/kubernetes/examples/blob/master/staging/persistent-volume-provisioning/glusterfs/glusterfs-storageclass.yaml”

https://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs”

http://docs.kubernetes.org.cn/803.html”

https://blog.searce.com/glusterfs-dynamic-provisioning-using-heketi-as-external-storage-with-gke-bd9af17434e5”

https://docs.oracle.com/en/operating-systems/oracle-linux/gluster-storage/index.html”

image.png

到这好像挺完美的,但要有很多POD需要共享存储呢,每建一个POD就得让运维给配置一个PV,不用的话,还得通知运维去回收,代价太大。

所以K8S提供了一个自动管理PV的机制,叫做storageClass,本质就是PV的模板,就是为了K8S能够通过storageClass去自动的创建PV。

provisioner是K8S内置的存储插件的名字。

parameters是具体创建PV用到的参数。换了插件,可能参数有所区别。

有了StorageClass后的PVC

image.png

主要区别是指定了storageClassName。

这样当kubectl去创建PVC的时候,K8S就会根据storageClassName的名字去找到刚才定义的storageClassName,根据storageClassName去自动创建一个PV,大小是10Gi的PV。

然后建立起绑定关系。

共享存储的设计与原理

image.png

StorageClass并不是为创建PV存在的。

每个PV和PVC都有StorageClass,不管是动态的还是静态的。如果没有设置,那么就会有一个默认的、空的StorageClass。

再比如手动创建了一个PV,也可以指定它的StorageClass。

当PVC中指定了相同名字的StorageClass时,K8S会自动把它们绑定关系,并不会在意StorageClass是否真实存在,只要名字一样就可以。

image.png

全局梳理组件。

存储插件,最下面一行,K8S支持。

中间:可以手动创建PV。也可以定义好storageClass,由K8S自动创建的PV。对于一个PV或storageClassName,必定只能对应一种类型的后端存储。

对应手动创建的PV,会事先创建许多PV,等PVC来了,就可以使用了。

自动的情况就只需要事先准备好storageClass,一般来说,一个后端对应一个storageClass就够了,当然也可以有多个,当PVC创建起来的时候,K8S就会根据定义的storageClass去匹配,自动创建相应大小的PV。

最上面一层。PODS和PVC一般是由K8S用户负责。

如果POD需要用到共享存储,那么首先要创建PVC,在PVC里描述需要什么样类型的后端存储,想要多大的空间,K8S就会根据其需求去匹配现有的PV和storageClass。

如果匹配未成功,那么PVC就会处于pending状态,如果创建的POD使用了这个PVC,那么这个POD也会处于pending状态。

如果这个PVC匹配到了合适的PV,K8S会自动建立起PV和PVC的绑定关系。

如果这个PVC匹配到了storageClass,就会根据storageClass的配置,自动创建PV,再把PV和PVC建立绑定关系。
最后创建POD的时候,只需指定PVC的名字,就可以像volume一样去使用共享存储了。

其中一个POD可以使用多个PVC,一个PVC也可以同时给多个POD提供服务,一个PVC只能绑定一个PV,一个PV只能对应一种后端存储。

实践内容与要求

使用后端存储:ClusterFS

实践过程:包含了后端存储,服务提供者的部署,包含了PV、storageClass的定义、PVC的定义、POD怎样使用PVC这样的一整套流程。

首先看一下通过gluster-kubernetes方式去运行ClusterFS的。

https://github.com/gluster/gluster-kubernetes”

当然还需要的其他组件:

image.png

这种方式对基础环境的要求:

要求1:3个ClusterFS的节点,保证每份数据有三个备份。

要求2:每个节点上要有一个裸磁盘,没有经过分区/任何操作的磁盘(必须是裸设备,在虚拟机中添加一块磁盘。不要做任何其他操作。物理机的话就要插上一块硬盘。

),这也是ClusterFS的基本要求。ClusterFS会去完全接管这块磁盘,从而实现更高效的管理数据。

重新设置集群六个节点

#这里根据第四章的一键部署方案重新安装了六个节点的集群。

#节点和服务部署设计

image.png

image.png

修改内容

#操作系统初始化与一键安装的操作同第四章内容。

kubespray用到kubeadm,并且token是24h过期的。
[root@node-1 kubespray-2.15.0]# vim roles/kubernetes/kubeadm/tasks/main.yml
  command: "{{ bin_dir }}/kubeadm token create"
修改为:(生成永不过期的token)
  command: "{{ bin_dir }}/kubeadm token create --ttl 0"

#注意:当发现安装集群过程中,containerd安装完成,containerd服务正常,那么可以手工下载这些镜像,但要注意,可能有些镜像中途会莫名的卡住导致安装时间超长,那么可以在不同节点,手工导入和导出操作。

#手工把镜像导出和导入

#ctr -n k8s.io和crictl的镜像是通用的。

#打包的文件不要以":"冒号相隔,不然在SFTP和SCP等远程处理时,可能会识别错误文件或hostname。

#比如

[root@gluster-02 ~]# ctr -n k8s.io i export ingress-nginx_controller-v0.41.2.tar.gz  registry.cn-hangzhou.aliyuncs.com/kubernetes-ku
[root@gluster-01 ~]# ctr -n k8s.io i import ingress-nginx_controller-v0.41.2.tar.gz

安装结果

image.png

image.png

image.png

image.png

image.png

后续改造

#=================1、设置ingress为单节点

[root@node-1 ~]# cd /etc/kubernetes/addons/ingress_nginx/
[root@node-1 ingress_nginx]# kubectl delete -f clusterrolebinding-ingress-nginx.yml
clusterrolebinding.rbac.authorization.k8s.io "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl delete -f clusterrole-ingress-nginx.yml
clusterrole.rbac.authorization.k8s.io "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl delete -f cm-ingress-nginx.yml
configmap "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl delete -f cm-tcp-services.yml
configmap "tcp-services" deleted
[root@node-1 ingress_nginx]# kubectl delete -f cm-udp-services.yml
configmap "udp-services" deleted
[root@node-1 ingress_nginx]# kubectl delete -f ds-ingress-nginx-controller.yml
daemonset.apps "ingress-nginx-controller" deleted
[root@node-1 ingress_nginx]# kubectl delete -f rolebinding-ingress-nginx.yml
rolebinding.rbac.authorization.k8s.io "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl delete -f role-ingress-nginx.yml
role.rbac.authorization.k8s.io "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl delete -f sa-ingress-nginx.yml
serviceaccount "ingress-nginx" deleted
[root@node-1 ingress_nginx]# kubectl get all -n ingress-nginx
No resources found in ingress-nginx namespace.


[root@node-1 ingress_nginx]# vim ds-ingress-nginx-controller.yml

 25       nodeSelector:
 26         #kubernetes.io/os: linux
 27         app: ingress

[root@node-1 ingress_nginx]# kubectl label node gluster-03 app=ingress


[root@node-1 ingress_nginx]# kubectl apply -f 00-namespace.yml
namespace/ingress-nginx unchanged
[root@node-1 ingress_nginx]# kubectl apply -f clusterrolebinding-ingress-nginx.yml
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f clusterrole-ingress-nginx.yml
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f cm-ingress-nginx.yml
configmap/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f cm-tcp-services.yml 
configmap/tcp-services created
[root@node-1 ingress_nginx]# kubectl apply -f cm-udp-services.yml 
configmap/udp-services created
[root@node-1 ingress_nginx]# kubectl apply -f rolebinding-ingress-nginx.yml
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f role-ingress-nginx.yml 
role.rbac.authorization.k8s.io/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f sa-ingress-nginx.yml 
serviceaccount/ingress-nginx created
[root@node-1 ingress_nginx]# kubectl apply -f ds-ingress-nginx-controller.yml
daemonset.apps/ingress-nginx-controller created

[root@node-1 ingress_nginx]# kubectl get all -n ingress-nginx -o wide

image.png

#=================2、node-1、gluster-01和gluster-02安装docker

[root@node-1 ~]# cat /etc/docker/daemon.json 
{
#docker加速器
  "registry-mirrors": ["https://uuy3rgxh.mirror.aliyuncs.com"],
#设置docker为systemd模式
  "exec-opts":["native.cgroupdriver=systemd"],
#设置docker的http是代理
  "insecure-registries": ["hub.mooc.com"]
}
#=================3、gluster-01和gluster-02安装harbor

#卸载上述这俩节点ingress-nginx才能在gluster-01/02访问到harbor,并做复制。


#harbor代理
[root@node-1 ~]# cd nginx
[root@node-1 nginx]# pwd
/root/nginx
[root@node-1 nginx]# cat restart.sh 
[root@X ~]# vim /etc/hosts
10.0.0.21   hub.mooc.com
#=================4、测试ingress访问的demo

[root@node-1 ~]# kubectl create -f ingress-demo.yaml
#配置ingress的IP访问tomcat的host映射
[root@X ~]# vim /etc/hosts
10.0.0.26   tomcat.mooc.com
#=================5、最终的hosts

[root@node-1 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain
172.16.1.21 node-1
172.16.1.22 node-2
172.16.1.23 node-3

172.16.1.100 kxsw-1

172.16.1.24 gluster-01
172.16.1.25 gluster-02
172.16.1.26 gluster-03

# Ansible inventory hosts BEGIN
172.16.1.21 node-1.cluster.local node-1
172.16.1.22 node-2.cluster.local node-2
172.16.1.23 node-3.cluster.local node-3
172.16.1.24 gluster-01.cluster.local gluster-01
172.16.1.25 gluster-02.cluster.local gluster-02
172.16.1.26 gluster-03.cluster.local gluster-03
# Ansible inventory hosts END
::1 localhost6 localhost6.localdomain


172.16.1.24 harbor-01
172.16.1.25 harbor-02

10.0.0.21   hub.mooc.com
10.0.0.26   tomcat.mooc.com

开始这节内容

[root@node-1 ~]# cd deep-in-kubernetes
[root@node-1 deep-in-kubernetes]# mkdir 9-persistent-volume
[root@node-1 deep-in-kubernetes]# cd 9-persistent-volume  


#设置部署节点的label
#每个节点都打一个标签,这样在运行daemonset的时候,会在每个节点都运行一个POD。标签创建好后,就可以去创建服务了。
[root@node-1 9-persistent-volume]# kubectl label node gluster-01 storagenode=glusterfs
node/gluster-01 labeled
[root@node-1 9-persistent-volume]# kubectl label node gluster-02 storagenode=glusterfs
node/gluster-02 labeled
[root@node-1 9-persistent-volume]# kubectl label node gluster-03 storagenode=glusterfs
node/gluster-03 labeled
#检查label
[root@node-1 ingress_nginx]# kubectl get nodes --show-labels

image.png

#提前下载镜像
[root@X ~]# crictl pull docker.io/gluster/gluster-centos:latest
[root@X ~]# crictl pull docker.io/heketi/heketi/heketi:dev 

[root@gluster-01/02/03 ~]# fdisk -l
#执行fdisk -l,看下自己的设备名称。磁盘/dev/sdb是新挂载进去的磁盘。

image.png

[root@node-1/2/3 ~]# ps -ef|grep apiserver|grep allow-pri
--allow-privileged=true
[root@gluster-01/02/03 ~]# yum install -y glusterfs glusterfs-fuse

[root@node-1 ~]# kubectl get nodes -o wide

image.png

glusterfs-daemonset.yaml

#这是官方提供的标准的配置,实现的功能为我们提供glusterfs的服务端,相当于上述架构图的最底层存储服务的提供者。

#注意里面定义的路径。

[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-daemonset.yaml 
error: unable to recognize "glusterfs-daemonset-modify.yaml ": no matches for kind "DaemonSet" in version "extensions/v1beta1"

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#daemonset-v1-apps

apiVersion: extensions/v1beta1
改为
apiVersion: apps/v1

# 查看当前k8s支持的对象
[root@node-1 9-persistent-volume]# kubectl api-resources | grep deployment
deployments                       deploy       apps/v1                                true         Deployment
[root@node-1 9-persistent-volume]# kubectl api-versions | grep -i apps
apps/v1

[root@node-1 9-persistent-volume]# cp glusterfs-daemonset.yaml glusterfs-daemonset-modify.yaml 


[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-daemonset-modify.yaml
error: error validating "glusterfs-daemonset-modify.yaml": error validating data: ValidationError(DaemonSet.spec): 
missing required field "selector" in io.k8s.api.apps.v1.DaemonSetSpec; if you choose to ignore these errors, turn validation off with --validate=false
#修改为
spec:
  selector:
    matchLabels:
      glusterfs: pod
      glusterfs-node: pod
  template:
    metadata:
      name: glusterfs
      labels:
        glusterfs: pod
        glusterfs-node: pod
[root@node-1 9-persistent-volume]# cat glusterfs-daemonset-modify.yaml
---
#以DaemonSet的方式去运行
kind: DaemonSet
apiVersion: apps/v1
metadata:
#glusterfs的服务端
  name: glusterfs
  labels:
    glusterfs: daemonset
  annotations:
    description: GlusterFS DaemonSet
    tags: glusterfs
spec:
  selector:
    matchLabels:
      glusterfs: pod
      glusterfs-node: pod
  template:
    metadata:
      name: glusterfs
      labels:
        glusterfs: pod
        glusterfs-node: pod
    spec:
      nodeSelector:
#需要在三个gluster节点打一个标签,把这个标签打上去,才可以运行在正确的位置。
        storagenode: glusterfs
      hostNetwork: true
      containers:
	  #使用gluster-centos:latest的容器镜像
      - image: gluster/gluster-centos:latest
        imagePullPolicy: IfNotPresent
        name: glusterfs
	#下面定义了关于这个容器的一系列参数
        env:
        # alternative for /dev volumeMount to enable access to *all* devices
        - name: HOST_DEV_DIR
          value: "/mnt/host-dev"
        # set GLUSTER_BLOCKD_STATUS_PROBE_ENABLE to "1" so the
        # readiness/liveness probe validate gluster-blockd as well
        - name: GLUSTER_BLOCKD_STATUS_PROBE_ENABLE
          value: "1"
        - name: GB_GLFS_LRU_COUNT
          value: "15"
        - name: TCMU_LOGDIR
          value: "/var/log/glusterfs/gluster-block"
        resources:
          requests:
            memory: 100Mi
            cpu: 100m
		#定义volume
        volumeMounts:
        - name: glusterfs-heketi
          mountPath: "/var/lib/heketi"
        - name: glusterfs-run
          mountPath: "/run"
        - name: glusterfs-lvm
          mountPath: "/run/lvm"
        - name: glusterfs-etc
          mountPath: "/etc/glusterfs"
        - name: glusterfs-logs
          mountPath: "/var/log/glusterfs"
        - name: glusterfs-config
          mountPath: "/var/lib/glusterd"
        - name: glusterfs-host-dev
          mountPath: "/mnt/host-dev"
        - name: glusterfs-misc
          mountPath: "/var/lib/misc/glusterfsd"
        - name: glusterfs-block-sys-class
          mountPath: "/sys/class"
        - name: glusterfs-block-sys-module
          mountPath: "/sys/module"
        - name: glusterfs-cgroup
          mountPath: "/sys/fs/cgroup"
          readOnly: true
        - name: glusterfs-ssl
          mountPath: "/etc/ssl"
          readOnly: true
        - name: kernel-modules
          mountPath: "/usr/lib/modules"
          readOnly: true
        securityContext:
          capabilities: {}
          privileged: true
		#定义健康检查
        readinessProbe:
          timeoutSeconds: 3
          initialDelaySeconds: 40
          exec:
            command:
            - "/bin/bash"
            - "-c"
            - "if command -v /usr/local/bin/status-probe.sh; then /usr/local/bin/status-probe.sh readiness; else systemctl status glusterd.service; fi"
          periodSeconds: 25
          successThreshold: 1
          failureThreshold: 50
        #定义健康检查
		livenessProbe:
          timeoutSeconds: 3
          initialDelaySeconds: 40
          exec:
            command:
            - "/bin/bash"
            - "-c"
            - "if command -v /usr/local/bin/status-probe.sh; then /usr/local/bin/status-probe.sh liveness; else systemctl status glusterd.service; fi"
          periodSeconds: 25
          successThreshold: 1
          failureThreshold: 50
      volumes:
      - name: glusterfs-heketi
        hostPath:
          path: "/var/lib/heketi"
      - name: glusterfs-run
      - name: glusterfs-lvm
        hostPath:
          path: "/run/lvm"
      - name: glusterfs-etc
        hostPath:
          path: "/etc/glusterfs"
      - name: glusterfs-logs
        hostPath:
          path: "/var/log/glusterfs"
      - name: glusterfs-config
        hostPath:
          path: "/var/lib/glusterd"
      - name: glusterfs-host-dev
        hostPath:
          path: "/dev"
      - name: glusterfs-misc
        hostPath:
          path: "/var/lib/misc/glusterfsd"
      - name: glusterfs-block-sys-class
        hostPath:
          path: "/sys/class"
      - name: glusterfs-block-sys-module
        hostPath:
          path: "/sys/module"
      - name: glusterfs-cgroup
        hostPath:
          path: "/sys/fs/cgroup"
      - name: glusterfs-ssl
        hostPath:
          path: "/etc/ssl"
      - name: kernel-modules
        hostPath:
          path: "/usr/lib/modules"
[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-daemonset-modify.yaml
daemonset.apps/glusterfs created

[root@node-2 ~]# kubectl get all -o wide

image.png

heketi-security.yaml

[root@node-1 9-persistent-volume]# cat heketi-security-modify.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: heketi-clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: heketi-clusterrole
subjects:
- kind: ServiceAccount
  name: heketi-service-account
  namespace: default

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: heketi-service-account
  namespace: default

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
#heketi-clusterrole需要访问API-SERVER去做一些事情
#毕竟glusterfs是运行在POD中
#去管理glusterfs,本质上是去管理POD。
#所以需要去定义ClusterRole和ClusterRoleBinding。把ClusterRole和ServiceAccount绑定在一起。让ServiceAccount具备相应的操作权限。
  name: heketi-clusterrole
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - pods/status
  - pods/exec
  verbs:
  - get
  - list
  - watch
  - create
[root@node-1 9-persistent-volume]# kubectl apply -f heketi-security-modify.yaml 
clusterrolebinding.rbac.authorization.k8s.io/heketi-clusterrolebinding created
serviceaccount/heketi-service-account created
clusterrole.rbac.authorization.k8s.io/heketi-clusterrole created

[root@node-2 ~]# kubectl get clusterrolebinding
NAME                                                   ROLE                                                                               AGE
heketi-clusterrolebinding                              ClusterRole/heketi-clusterrole                                                     41s

[root@node-2 ~]# kubectl get serviceaccount
NAME                     SECRETS   AGE
default                  1         18h
heketi-service-account   1         77s

[root@node-2 ~]# kubectl get clusterrole
NAME                                                                   CREATED AT
heketi-clusterrole                                                     2022-12-08T10:24:54Z

heketi-deployment.yaml

#glusterfs服务部署完了,下面去初始化磁盘,让glusterfs把磁盘管理起来。

#为方便操作,引入heketi(“https://github.com/heketi/heketi”),用于管理和维护glusterfs。

#注意里面定义的路径。

# 查看当前k8s支持的对象
[root@node-1 9-persistent-volume]# kubectl api-resources | grep deployment
deployments                       deploy       apps/v1                                true         Deployment
[root@node-1 9-persistent-volume]# kubectl api-versions | grep -i apps
apps/v1
[root@node-1 9-persistent-volume]# cp heketi-deployment.yaml heketi-deployment-modify.yaml

[root@node-1 9-persistent-volume]# kubectl apply -f heketi-deployment-modify.yaml
service/heketi created
configmap/tcp-services configured
error: unable to recognize "heketi-deployment-modify.yaml": no matches for kind "Deployment" in version "extensions/v1beta1"
[root@node-1 9-persistent-volume]# kubectl delete -f heketi-deployment-modify.yaml   
service "heketi" deleted
configmap "tcp-services" deleted
error: unable to recognize "heketi-deployment-modify.yaml": no matches for kind "Deployment" in version "extensions/v1beta1"
#替换版本
[root@node-1 9-persistent-volume]# sed -i "s|extensions/v1beta1|apps/v1|g" /root/deep-in-kubernetes/9-persistent-volume/heketi-deployment-modify.yaml
[root@node-1 9-persistent-volume]# grep "extensions/v1beta1" heketi-deployment-modify.yaml
[root@node-1 9-persistent-volume]# kubectl apply -f heketi-deployment-modify.yaml
service/heketi created
configmap/tcp-services created
error: error validating "heketi-deployment-modify.yaml": error validating data: ValidationError(Deployment.spec): missing required field "selector" in io.k8s.api.apps.v1.DeploymentSpec; if you choose to ignore these errors, turn validation off with --validate=false
[root@node-1 9-persistent-volume]# kubectl delete -f heketi-deployment-modify.yaml   
service "heketi" deleted
configmap "tcp-services" deleted
Error from server (NotFound): error when deleting "heketi-deployment-modify.yaml": deployments.apps "heketi" not found
#修改为
spec:
  replicas: 1
  selector:
    matchLabels:
      name: heketi
      glusterfs: heketi-pod
  template:
    metadata:
      name: heketi
      labels: 
        name: heketi
        glusterfs: heketi-pod
  spec:
#设置部署节点
    nodeName: gluster-03
    #需要先创建heketi-service-account
    serviceAccountName: heketi-service-account
    containers:
[root@node-1 9-persistent-volume]# cat heketi-deployment-modify.yaml  
#这里的类型是Service
kind: Service
apiVersion: v1
metadata:
  name: heketi
  labels: 
    glusterfs: heketi-service
    deploy-heketi: support
  annotations: 
    description: Exposes Heketi Service
spec:
  selector: 
    name: heketi
  ports:
  #对应容器8080端口
  - name: heketi
    port: 80
    targetPort: 8080

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: tcp-services
  namespace: ingress-nginx
data:
#ConfigMap类型,TCP服务service暴露30001端口。
  "30001": default/heketi:80


---

kind: Deployment
apiVersion: apps/v1
metadata:
  name: heketi
  labels: 
    glusterfs: heketi-deployment
  annotations: 
    description: Defines how to deploy Heketi
spec:
  replicas: 1
  selector:
    matchLabels:
      name: heketi
      glusterfs: heketi-pod
  template:
    metadata:
      name: heketi
      labels: 
        name: heketi
        glusterfs: heketi-pod
    spec:
#设置部署节点
      nodeName: gluster-03
	#需要先创建heketi-service-account
      serviceAccountName: heketi-service-account
      containers:
      - image: heketi/heketi:dev
        imagePullPolicy: Always
        name: heketi
        env:
        - name: HEKETI_EXECUTOR
          value: "kubernetes"
        - name: HEKETI_DB_PATH
          value: "/var/lib/heketi/heketi.db"
        - name: HEKETI_FSTAB
          value: "/var/lib/heketi/fstab"
        - name: HEKETI_SNAPSHOT_LIMIT
          value: "14"
        - name: HEKETI_KUBE_GLUSTER_DAEMONSET
          value: "y"
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: db
          mountPath: /var/lib/heketi
        readinessProbe:
          timeoutSeconds: 3
          initialDelaySeconds: 3
          httpGet: 
            path: /hello
            port: 8080
        livenessProbe:
          timeoutSeconds: 3
          initialDelaySeconds: 30
          httpGet: 
            path: /hello
            port: 8080
      volumes:
      - name: db
	  #db以hostPath的形式挂载到"/heketi-data"中。
        hostPath:
          path: "/heketi-data"
[root@node-1 9-persistent-volume]# kubectl apply -f heketi-deployment-modify.yaml   
service/heketi created
configmap/tcp-services configured
deployment.apps/heketi created

[root@node-1 9-persistent-volume]# kubectl get service
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
#heketi的IP:PORT用于glusterfs-storage-class.yaml
#POD的IP重启会变,service的IP重启不会变。
heketi        ClusterIP   10.200.220.20   <none>        80/TCP    75m
kubernetes    ClusterIP   10.200.0.1      <none>        443/TCP   20h
tomcat-demo   ClusterIP   10.200.70.160   <none>        80/TCP    16h
[root@node-1 9-persistent-volume]# kubectl get configmap
No resources found in default namespace.
[root@node-1 9-persistent-volume]# kubectl get deployment
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
heketi        1/1     1            1           75m
tomcat-demo   1/1     1            1           16h

Heketi运行起来

image.png

[root@node-2 ~]# crictl ps
CONTAINER           IMAGE               CREATED                  STATE               NAME                      ATTEMPT             POD ID
f2976b94ea05f       3ffd29f1e74fe       52 minutes ago           Running             heketi                    0                   bf2d112f36177

[root@heketi-76f76d58d8-66xjj /]# export HEKETI_CLI_SERVER=http://localhost:8080
#一会要执行一个命令,需要这个环境变量去访问这个API-SERVER。
[root@heketi-76f76d58d8-66xjj /]# echo $HEKETI_CLI_SERVER
http://localhost:8080

[root@gluster-01/02/03 ~]# fdisk -l
#执行fdisk -l,看下自己的设备名称。磁盘/dev/sdb是新挂载进去的磁盘。

image.png

#下述的IP要修改成对应节点的IP(host映射要一致)
#配置了具体的gluster的信息,hostname和IP地址一定不要写错了。
#在不同的机器,裸设备的设备名称不同。
{
  "clusters": [
    {
      "nodes": [
        {
          "node": {
            "hostnames": {
              "manage": [
                "gluster-01"
              ],
              "storage": [
                "17.16.1.24"
              ]
            },
            "zone": 1
          },
          "devices": [
            {
              "name": "/dev/sdb",
              "destroydata": false
            }
          ]
        },
        {
          "node": {
            "hostnames": {
              "manage": [
                "gluster-02"
              ],
              "storage": [
                "17.16.1.25"
              ]
            },
            "zone": 1
          },
          "devices": [
            {
              "name": "/dev/sdb",
              "destroydata": false
            }
          ]
        },
        {
          "node": {
            "hostnames": {
              "manage": [
                "gluster-03"
              ],
              "storage": [
                "17.16.1.26"
              ]
            },
            "zone": 1
          },
          "devices": [
            {
              "name": "/dev/sdb",
              "destroydata": false
            }
          ]
        }
      ]
    }
  ]
}

#进入刚才heketi容器,修改好复制这个JSON到下述topology.json中、
#让heketi根据配置文件把gluster集群做一个初始化。
[root@heketi-76f76d58d8-66xjj /]# vi topology.json
#必须要有三个节点,虽然2个节点在这一步也可以初始化成功,但在后面使用会出现意想不到的问题。
#密码设置成和/etc/heketi/heketi.json中一致即可,这里未修改别的值也未测试。
#注意看下日志输出。初始化时间可能比较长。
[root@heketi-76f76d58d8-66xjj /]# heketi-cli --user admin --secret "My Secret"  topology load --json topology.json
Creating cluster ... ID: 9e26f288dc2b2d115ba3efeb89bea5fc
        Allowing file volumes on cluster.
        Allowing block volumes on cluster.
        Creating node gluster-01 ... ID: d2bdbc991ffc77122289e0fb6d493dd1
                Adding device /dev/sdb ... OK
        Creating node gluster-02 ... ID: de84243038e652ac253c6fbfb59f3c3c
                Adding device /dev/sdb ... OK
        Creating node gluster-03 ... ID: e7568ba4d639576cc47945bd6d768356
                Adding device /dev/sdb ... OK

#初始化完成可以去看一下当前集群的拓扑信息。
[root@heketi-76f76d58d8-66xjj /]# heketi-cli --user admin --secret "My Secret" topology info
#Cluster Id: 9e26f288dc2b2d115ba3efeb89bea5fc

image.png

#使用后会变成

image.png

image.png

[root@heketi-76f76d58d8-66xjj /]# cat /usr/bin/heketi-start.sh

image.png

#再看下heketi进程
[root@node-2 ~]# ps -ef|grep heketi
root      11076  11054  0 18:30 ?        00:00:01 /usr/bin/heketi --config=/etc/heketi/heketi.json
[root@heketi-76f76d58d8-66xjj /]# cat /etc/heketi/heketi.json

image.png

#==================这样,最下层的存储服务就可以正式提供服务了。

#验证一下gluster的监听端口是否启动了
[root@gluster-01/02/03 ~]# netstat -lntup|grep glusterd 
tcp        0      0 0.0.0.0:24007           0.0.0.0:*               LISTEN      9490/glusterd 


#也可以到容器中,看gluster集群的信息。三个节点都核实下。
#02节点和03节点的集群也是正常的。为啥上面显示映射名,下面显示IP。
[root@gluster-01 ~]# crictl ps
CONTAINER           IMAGE               CREATED                  STATE               NAME                ATTEMPT             POD ID
2243cefda2a38       b2919ab8d731c       About an hour ago        Running             glusterfs           0                   b768dc2d3f843

[root@gluster-01 ~]# crictl exec -it 2243cefda2a38 bash
[root@gluster-01 /]# gluster peer status
Number of Peers: 2

Hostname: 172.16.1.25
Uuid: 8a87203b-9116-49be-b6eb-40eb71a3907c
State: Peer in Cluster (Connected)

Hostname: 172.16.1.26
Uuid: 51736eff-8600-4642-9a40-2313f87c4c43
State: Peer in Cluster (Connected)

[root@gluster-01 /]# ll /var/log/glusterfs/
total 40
drwxr-xr-x. 2 root root 4096 Dec  8 10:22 bricks
-rw-------. 1 root root 2376 Dec  8 11:34 cli.log
-rw-------. 1 root root  300 Dec  8 11:30 cmd_history.log
drwxr-xr-x. 2 root root 4096 Dec  8 10:22 container
drwxr-xr-x. 2 root root 4096 Dec  8 10:22 geo-replication
drwxr-xr-x. 3 root root 4096 Dec  8 10:22 geo-replication-slaves
drwxr-xr-x. 2 root root 4096 Dec  8 10:22 gluster-block
-rw-------. 1 root root 9740 Dec  8 11:34 glusterd.log

#进入容器执行一下完整的命令,看看退出值是不是0
[root@gluster-01 /]# systemctl -q is-active glusterd.service

image.pngimage.png

image.png

glusterfs-secret.yaml

[root@node-1 ~]# kubectl describe pvc
  Warning  ProvisioningFailed  42s (x39 over 71m)  persistentvolume-controller  Failed to provision volume with StorageClass "glusterfs-storage-class": failed to get secret default/heketi-secret: secrets "heketi-secret" not found

“https://blog.searce.com/glusterfs-dynamic-provisioning-using-heketi-as-external-storage-with-gke-bd9af17434e5”

#这里创建heketi-name

[root@node-1 9-persistent-volume]# cp glusterfs-secret.yaml glusterfs-secret-modify.yaml

[root@node-1 9-persistent-volume]# echo -n "My Secret" | base64
TXkgU2VjcmV0
[root@node-1 9-persistent-volume]# cat glusterfs-secret-modify.yaml
apiVersion: v1
kind: Secret
metadata:
  name: heketi-secret
  namespace: default
type: "kubernetes.io/glusterfs"
data:
#上述base64后的值
  key: TXkgU2VjcmV0
[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-secret-modify.yaml
secret/heketi-secret created

[root@node-1 9-persistent-volume]# kubectl get secret
NAME                                 TYPE                                  DATA   AGE
default-token-24xzv                  kubernetes.io/service-account-token   3      20h
heketi-secret                        kubernetes.io/glusterfs               1      19s
heketi-service-account-token-lsmq4   kubernetes.io/service-account-token   3      76m

glusterfs-storage-class.yaml

#倒数第二层,准备PV和storageClass。

#用法

https://github.com/kubernetes/examples/blob/master/staging/persistent-volume-provisioning/glusterfs/glusterfs-storageclass.yaml”

https://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs”

http://docs.kubernetes.org.cn/803.html”


[root@node-1 9-persistent-volume]# cp glusterfs-storage-class.yaml glusterfs-storage-class-modify.yaml

[root@node-1 9-persistent-volume]# cat glusterfs-storage-class-modify.yaml
apiVersion: storage.k8s.io/v1
#类型StorageClass
kind: StorageClass
metadata:
  name: glusterfs-storage-class
#provisioner是glusterfs,K8S内置的一种存储类型。
provisioner: kubernetes.io/glusterfs
parameters:
#对应到glusterfs服务容器里面的8080端口
#(heketi暴露service的scvip和端口)
#分配 gluster 卷的需求的 Gluster REST 服务/Heketi 服务 url。
#POD的IP重启会变,service的IP重启不会变。
  resturl: "http://10.200.220.20:80"
#heketi-cli --user admin --secret "My Secret" topology info中的
#Cluster Id: 9e26f288dc2b2d115ba3efeb89bea5fc
  clusterid: "9e26f288dc2b2d115ba3efeb89bea5fc"
  restauthenabled: "true"
  restuser: "admin"
#K8S的默认default命名空间
  secretNamespace: "default"
#对应gluster-secret.yaml
  secretName: "heketi-secret"
  gidMin: "40000"
  gidMax: "50000"
  volumetype: "replicate:3"
[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-storage-class-modify.yaml
storageclass.storage.k8s.io/glusterfs-storage-class created
[root@node-1 9-persistent-volume]# kubectl get storageclass
NAME                      PROVISIONER               RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
glusterfs-storage-class   kubernetes.io/glusterfs   Delete          Immediate           false                  4s

glusterfs-pvc.yaml

#准备最上面一层PVC和POD

#创建POD之前要创建PVC

#在web-deploy.yaml中会使用

[root@node-1 9-persistent-volume]# cp glusterfs-pvc.yaml glusterfs-pvc-modify.yaml

[root@node-1 9-persistent-volume]# cat glusterfs-pvc-modify.yaml
#类型PersistentVolumeClaim
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: glusterfs-pvc
spec:
#指定storageClassName,对应刚才创建的glusterfs-storage-class.yaml。
  storageClassName: glusterfs-storage-class
  accessModes:
  #只允许自己挂载
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

[root@node-1 9-persistent-volume]# kubectl apply -f glusterfs-pvc-modify.yaml 
persistentvolumeclaim/glusterfs-pvc created

[root@node-1 9-persistent-volume]# kubectl get persistentvolumeclaim
[root@node-1 9-persistent-volume]# kubectl get pvc
[root@node-1 9-persistent-volume]# kubectl get pv

image.png

#PVC创建完,要创建POD。

#验证PVC是否可以有PV去绑定。

#先验证PVC当前状态为绑定状态。

#PV名字非常长,很明显是系统自动生成的PV,如何生成的?根据storageClass去自动生成的PV,跟pvc-078c这个名称的PVC做了一下绑定。

image.png

#如何体现出它的绑定呢,一个是状态Bound,还可以把PVC的详细信息打印出来。

可以看到PV的名字pvc-078c,就是在做PV和PVC绑定的时候,kubectl给其自动添加进去的。

image.png

image.png

准备测试镜像

#把服务做到镜像中

#第1步:搞定服务运行的相关文件。核实源文件的合法性。
[root@node-1 ~]# mkdir mooc-k8s-demo
[root@node-1 ~]# cd mooc-k8s-demo   
[root@node-1 mooc-k8s-demo]# mkdir springboot-web-demo
[root@node-1 mooc-k8s-demo]# cd springboot-web-demo  
[root@node-1 springboot-web-demo]# jar -tf springboot-web-demo-1.0-SNAPSHOT.jar
#浏览器访问"http://10.0.0.21:8080/hello?name=test"
Hello test! I'm springboot-web-demo controller!

#第2步:构建镜像-DockerFile
[root@node-1 springboot-web-demo]# cat Dockerfile 
FROM hub.mooc.com/kubernetes/openjdk:8-jre-alpine
COPY springboot-web-demo-1.0-SNAPSHOT.jar /springboot-web.jar
ENTRYPOINT ["java", "-jar", "/springboot-web.jar"]
[root@node-1 springboot-web-demo]# docker tag openjdk:8-jre-alpine hub.mooc.com/kubernetes/openjdk:8-jre-alpine
[root@node-1 springboot-web-demo]# docker push hub.mooc.com/kubernetes/openjdk:8-jre-alpine

#测试镜像
[root@node-1 springboot-web-demo]# docker build -t springboot-web:v1 .
[root@node-1 springboot-web-demo]# docker run -it springboot-web:v1
[root@node-1 ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS     NAMES
a61273739e93   springboot-web:v1   "java -jar /springbo¡­"   37 seconds ago   Up 36 seconds             eager_goodall

#第3步:把这个镜像打个tag放到harbor仓库中
[root@node-1 springboot-web-demo]# docker tag springboot-web:v1 hub.mooc.com/kubernetes/springboot-web:v1
[root@node-1 springboot-web-demo]# docker push hub.mooc.com/kubernetes/springboot-web:v1

web-deploy.yaml

#PVC准备完成之后,就可以去使用这个PVC了。

#看下POD的例子

[root@node-1 9-persistent-volume]# cp web-deploy.yaml web-deploy-modify.yaml 
#修改
  template:
    metadata:
      labels:
        app: web-deploy
    spec:
      nodeSelector:
        webdeploynode: webdeploy
      containers:
[root@node-1 9-persistent-volume]# kubectl label node gluster-01 webdeploynode=webdeploy
[root@node-1 9-persistent-volume]# kubectl label node gluster-02 webdeploynode=webdeploy

image.png

[root@node-1 9-persistent-volume]# cat web-deploy-modify.yaml 
#deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deploy
spec:
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  selector:
    matchLabels:
      app: web-deploy
#为了验证共享存储,副本数设置为2
  replicas: 2
  template:
    metadata:
      labels:
        app: web-deploy
spec:
#节点选择器
      nodeSelector:
        webdeploynode: webdeploy
      containers:
      - name: web-deploy
        image: hub.mooc.com/kubernetes/springboot-web:v1
        ports:
        - containerPort: 8080
        volumeMounts:
          - name: gluster-volume
		  #容器根目录
            mountPath: "/mooc-data"
			#可读可写
            readOnly: false
      volumes:
      - name: gluster-volume
        persistentVolumeClaim:
		#pvc的名字:glusterfs-pvc.yaml中定义的名字
          claimName: glusterfs-pvc


[root@node-1 9-persistent-volume]# kubectl apply -f web-deploy-modify.yaml 
deployment.apps/web-deploy created

image.png

如何测试呢(config监听变化)

#互相写入测试


[root@gluster-01 harbor]# crictl ps
CONTAINER           IMAGE               CREATED                  STATE               NAME                ATTEMPT             POD ID
052ce91e3a043       98458caa43631       44 minutes ago           Running             web-deploy          0                   45843eb661eac

[root@gluster-01 harbor]# crictl exec -it 052ce91e3a043 sh
/ # cd /mooc-data/
/mooc-data # ls
/mooc-data # echo "hello" > a
/mooc-data # cat a
hello
/mooc-data # cat b
ha

[root@gluster-02 harbor]#  crictl ps
CONTAINER           IMAGE               CREATED                  STATE               NAME                ATTEMPT             POD ID
8afdff2e0884d       98458caa43631       44 minutes ago           Running             web-deploy          0                   c3999f1d062ac



[root@gluster-02 harbor]# crictl exec -it 8afdff2e0884d sh
/ # cd /mooc-data/
/mooc-data # ls
/mooc-data # ls
a
/mooc-data # cat a
hello
/mooc-data # echo "ha" > b
/mooc-data # cat b
Ha

PV和PVC的生命周期

由此可见,共享存储可用了。

基于glusterfs+heketi的共享存储搭建和测试就基本完成了。

image.png

PVC和PV字段status=Bound

PV还有个字段,RECLAIM回收的策略,当前是delete。

生命周期就这么几种。

资源刚创建好是未使用状态。

当PV有了PVC之后是绑定状态。

如果有POD用了这个PVC,那么这个PVC就是使用中状态。


标题:Kubernetes(十一)落地实践(11.4/5)共享存储 --- PV、PVC和StorageClass(上/下)
作者:yazong
地址:https://blog.llyweb.com/articles/2022/12/09/1670529788892.html