硬骨头。
前面的服务都是无状态的。
有状态的应用就比较麻烦了,最常见的一种有状态就是本地存储。
不好的习惯:把文件保存到服务器目录。但很常见。
Docker每次重新部署,都会导致目录被清空。
因为每新建一个容器,磁盘空间都是全新的。
目录挂载呢?设置好节点的亲和性,节点每次调度到固定节点上,可行,但缺陷比较大,绑定了固定机器,不得不考虑数据备份问题,绑定到一台机器,那么只有一个副本,会有风险的。
这些问题K8S考虑到了,一个方案,共享存储。
PV
PV是PersistentVolume的简写,也是K8S的一种资源,描述的是持久化数据卷。
这里标注的是NFS的挂载目录。
PV一般来说,是运维事先定义好的。
定义哪些东西呢?
类型kind。
比较重要的字段capacity,存储容量是多少。
对于这块存储的访问模式是什么,accessModes,只有一个POD可以去使用这个PV,读写权限。
如果多个POD需要共享这个磁盘PV,可以设置ReadWriteMany。
nfs是K8S内置的存储类型,对nfs类型的存储,需要提供这样的信息,挂载到远程的哪个目录下,nfs的地址是什么。
上面就描述了一个nfs类型的共享存储的一个方法。
PVC
类型kind:描述的是一个POD希望使用的持久化存储的属性或一个需求。
比如使用一个磁盘,volume的大小、续写权限呢?
PVC一般是由开发人员管理的。这个里面要求具有读写权限的、并且只能被我独占的有实际空间大小的存储。这是描述了一个POD对共享存储的需求、一个期望。
PV和PVC的绑定
当PV和PVC两个资源都创建好了,
那么就可以kubectl create起来了
此时,POD还不能使用。
PV和PVC必须建立好绑定关系,才能达到可用状态。
如何才能绑定上呢?
首先得匹配,PV得满足PVC的需求,像存储大小、读写权限都要满足。
然后,PV和PVC的storageClassName必须一致,当满足这个条件之后,kubelet中有个persistVolumeController就会发现匹配的PV,自动的建立绑定关系,本质上,就是在PVC的资源描述对象里把PV的名字填进去,那么PV和PVC就绑定在一起了。
POD
当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
其中设置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://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs”
“http://docs.kubernetes.org.cn/803.html”
“https://docs.oracle.com/en/operating-systems/oracle-linux/gluster-storage/index.html”
到这好像挺完美的,但要有很多POD需要共享存储呢,每建一个POD就得让运维给配置一个PV,不用的话,还得通知运维去回收,代价太大。
所以K8S提供了一个自动管理PV的机制,叫做storageClass,本质就是PV的模板,就是为了K8S能够通过storageClass去自动的创建PV。
provisioner是K8S内置的存储插件的名字。
parameters是具体创建PV用到的参数。换了插件,可能参数有所区别。
有了StorageClass后的PVC
主要区别是指定了storageClassName。
这样当kubectl去创建PVC的时候,K8S就会根据storageClassName的名字去找到刚才定义的storageClassName,根据storageClassName去自动创建一个PV,大小是10Gi的PV。
然后建立起绑定关系。
共享存储的设计与原理
StorageClass并不是为创建PV存在的。
每个PV和PVC都有StorageClass,不管是动态的还是静态的。如果没有设置,那么就会有一个默认的、空的StorageClass。
再比如手动创建了一个PV,也可以指定它的StorageClass。
当PVC中指定了相同名字的StorageClass时,K8S会自动把它们绑定关系,并不会在意StorageClass是否真实存在,只要名字一样就可以。
全局梳理组件。
存储插件,最下面一行,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”
当然还需要的其他组件:
这种方式对基础环境的要求:
要求1:3个ClusterFS的节点,保证每份数据有三个备份。
要求2:每个节点上要有一个裸磁盘,没有经过分区/任何操作的磁盘(必须是裸设备,在虚拟机中添加一块磁盘。不要做任何其他操作。物理机的话就要插上一块硬盘。
),这也是ClusterFS的基本要求。ClusterFS会去完全接管这块磁盘,从而实现更高效的管理数据。
重新设置集群六个节点
#这里根据第四章的一键部署方案重新安装了六个节点的集群。
#节点和服务部署设计
修改内容
#操作系统初始化与一键安装的操作同第四章内容。
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
安装结果
后续改造
#=================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
#=================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
#提前下载镜像
[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是新挂载进去的磁盘。
[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
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
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运行起来
[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是新挂载进去的磁盘。
#下述的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
#使用后会变成
[root@heketi-76f76d58d8-66xjj /]# cat /usr/bin/heketi-start.sh
#再看下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
#==================这样,最下层的存储服务就可以正式提供服务了。
#验证一下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
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://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
#PVC创建完,要创建POD。
#验证PVC是否可以有PV去绑定。
#先验证PVC当前状态为绑定状态。
#PV名字非常长,很明显是系统自动生成的PV,如何生成的?根据storageClass去自动生成的PV,跟pvc-078c这个名称的PVC做了一下绑定。
#如何体现出它的绑定呢,一个是状态Bound,还可以把PVC的详细信息打印出来。
可以看到PV的名字pvc-078c,就是在做PV和PVC绑定的时候,kubectl给其自动添加进去的。
准备测试镜像
#把服务做到镜像中
#第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
[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
如何测试呢(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的共享存储搭建和测试就基本完成了。
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