前言:
持久化存储是容器无法绕开的一个问题,其主要原因是我们的容器或者pod有些是有状态服务,有些是无状态服务的,而容器或者pod的生命周期是短暂的,例如,一个MySQL容器或者pod,容器或者pod启动后,对该服务做了一些修改,比如修改密码,添加插件,删除无用数据等等操作后,如果容器或者pod在这些操作后被销毁了或者重启了,那么,前面做的这些改动就将消失了(容器或者pod规定的,只要重新启动将会恢复初始状态)。因此,数据的持久化对于有状态服务来说就是非常必要的了。
这里简单提一句,有状态服务和无状态服务到底是指的什么?
无状态服务
-认为所有pod都是一样的,不具备与其他实例有不同的关系。
- 没有顺序的要求。
- 不用考虑再哪个Node运行。
- 随意扩容缩容。
- Deployment控制器设计原则:管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容。这种应用称为“无状态”,例如Web服务
有状态服务
- 集群节点之间的关系。
- 数据不完全一致。
- 实例之间不对等的关系。
- 依靠外部存储的应用。
- 通过dns维持身份
- 在实际的场景中,并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群
如何理解呢?http协议大家应该都清楚是无状态的协议服务,而电子商务内的购物车等等是有事务,有状态的,带session了嘛,比如,电子商务里的已付款商品和未付款商品,电子商务的后台对每种状态情况做了标识了,这样就算有状态了。举一个常见的例子,在商城里购买一件商品。需要经过放入购物车、确认订单、付款等多个步骤。由于HTTP协议本身是无状态的,所以为了实现有状态服务,就需要通过一些额外的方案。比如最常见的session,将用户挑选的商品(购物车),保存到session中,当付款的时候,再从购物车里取出商品信息 。
实验目标:
前面说了那么多,做了N多铺垫,也就是想使用一下在k8s中应用最简单的nfs网络存储来持久化存储一个MySQL5.7.23的单实例,并且该MySQL单实例能通过navicat连接上,并进行一些初步的管理工作。
实验环境:
(1)一个可正常运行的k8s环境,使用的服务器是centos7版本,三台虚拟机服务器,IP地址为:192.168.217.16/17/18 ,其中16服务器是主master服务器。17,18为node节点。
[root@master ~]# k get nodes NAME STATUS ROLES AGE VERSION c7n.cnn Ready master 32d v1.19.4 slave1 Ready <none> 32d v1.19.4 slave2 Ready <none> 32d v1.19.4
(2)nfs服务
nfs服务搭建在master节点,也就是16服务器上。配置文件内容如下:
[root@master ~]# cat /etc/exports /data/nfs-sc 10.244.0.0/16(rw,no_root_squash,no_subtree_check) 192.168.217.16(rw,no_root_squash,no_subtree_check) 192.168.217.0/24(rw,no_root_squash,no_subtree_check)
(3)
nfs-client-provisioner镜像使用的是这个:
[root@master ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry.cn-shanghai.aliyuncs.com/c7n/nfs-client-provisioner v3.1.0-k8s1.11 e47e31bbe424 19 months ago 49.8MB
下载链接:https://pan.baidu.com/s/1ytRwIlMnF_FDzT_V9MQj0A?pwd=k8ss
提取码:k8ss
三个服务器都导入该镜像,因为不知道pod会调度到哪个节点。
实验步骤:
一,
rbac角色权限建立,文件内容如下:
[root@master ~]# cat serviceacount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: kube-system --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["get", "list", "watch","create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: kube-system roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: kube-system rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: kube-system subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: kube-system roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
这里需要注意一点,都是使用的namespace kube-system ,下面的文件也需要指定到kube-system命名空间
执行该文件输出应该如下():
[root@master ~]# k apply -f serviceacount.yaml serviceaccount/nfs-client-provisioner created clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
执行命令:kubectl apply -f serviceacount.yaml
(二)
部署pod
[root@master ~]# cat deploy-nfs.yaml apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner --- kind: Deployment apiVersion: apps/v1 metadata: name: nfs-client-provisioner namespace: kube-system spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: registry.cn-shanghai.aliyuncs.com/c7n/nfs-client-provisioner:v3.1.0-k8s1.11 imagePullPolicy: IfNotPresent volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: fuseim.pri/ifs - name: NFS_SERVER value: 192.168.217.16 - name: NFS_PATH value: /data/nfs-sc volumes: - name: nfs-client-root nfs: server: 192.168.217.16 path: /data/nfs-sc
nfs-client-provisioner这个镜像的部署,其中的env 需要根据自己的实际情况填写,也就是这些:
- name: PROVISIONER_NAME value: fuseim.pri/ifs #可以自己定义一个任意的名字 - name: NFS_SERVER #这里不修改 value: 192.168.217.16 #nfs服务所在的服务器IP - name: NFS_PATH #这里不修改 value: /data/nfs-sc #nfs服务配置文件里写的路径,也就是/etc/exports文件内定义的路径 volumes: - name: nfs-client-root #这不修改 nfs: server: 192.168.217.16#nfs服务所在的服务器IP path: /data/nfs-sc #nfs服务配置文件里写的路径,也就是/etc/exports文件内定义的路径
除了需要根据实际情况修改的地方,还有一个地方需要注意, serviceAccountName: nfs-client-provisioner,这个账号由于
metadata:
name: nfs-client-provisioner
namespace: kube-system
这一段是指定的命名空间kube-system,因此,上面的账号生成文件也必须是使用namespace 为kube-system 。总的来说,也就是两者使用的命名空间要保持一致。
执行命令:kubectl apply -f deploy-nfs.yaml
这两步完成后,应该就能在命名空间kube-system 里看到一个正常运行的pod:
[root@master ~]# k get po -n kube-system|grep nfs nfs-client-provisioner-6fc484bd4f-7vrb8 1/1 Running 0 135m
(三)
建立StorageClasses
vim storageclass-nfs.yaml
此sc建立的时候就设定为了default class
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage annotations: storageclass.beta.kubernetes.io/is-default-class: "true" provisioner: fuseim.pri/ifs reclaimPolicy: Delete allowVolumeExpansion: True #允许pvc创建后扩容
这个StorageClass是没有和命名空间namespace有依赖的,提供者provisioner: fuseim.pri/ifs这个是建立pvc时候必须指定的 哦,这个是非常重要的一个键值对,它的值由文件deploy-nfs.yaml内定义。
执行命令:kubectl apply -f storageclass-nfs.yaml
(四)
查询sc是否正常
[root@master nfs-sc]# k get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage (default) fuseim.pri/ifs Delete Immediate true 9d
这四步完成后,基本nfs持久化数据卷就已经认为可用了,可以算作一个阶段了。剩下的就是建立pvc,然后查看pvc是否自动生成pv。
为什么要说这四步完成就是一个阶段了呢?因为,后面的pvc pv的建立是实际的应用阶段了,现有的nfs持久卷挂载服务已经可用了。后面的步骤都是测试的啦!!!!
(五)
建立pvc并测试挂载pv是否正常,这个pvc名称是test-claim:
[root@master nfs-sc]# cat test-claim.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi --- kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: busybox:1.24 command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && exit 0 || exit 1" volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim
检测功能是否正常:
[root@master nfs-sc]# k get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE database mysql2-7b77d5f496-8r5tr 1/1 Running 1 7h36m default nfs-client-provisioner-6fc484bd4f-pjxm7 1/1 Running 14 8d default nginx-7b54d48599-x2zc5 1/1 Running 10 5d13h default read-pod 0/1 Error 0 9h default test-pod 0/1 Completed 0 30s
有一个名字叫test-pod 的pod 是completed状态,在/data/nfs-sc/目录下:
[root@master nfs-sc]# pwd /data/nfs-sc [root@master nfs-sc]# ls default-test-claim-pvc-a17177ba-d1df-452e-84ec-12499f453b88
可以看到一个SUCCESS文件,表示功能正常
[root@master nfs-sc]# cd default-test-claim-pvc-a17177ba-d1df-452e-84ec-12499f453b88/ [root@master default-test-claim-pvc-a17177ba-d1df-452e-84ec-12499f453b88]# ls SUCCESS
可以看到pvc名称是test-claim,所在命名空间是default,这说明其它的命名空间可以自由使用kube-system下的pod nfs-client-provisioner :
1. [root@master ~]# k get pvc -A 2. NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE 3. default test-claim Bound pvc-9e72b552-f3dd-432e-af7d-4d0a848447bf 1Mi RWX managed-nfs-storage 77m
可以看到pv的名称是pvc-9e72b552-f3dd-432e-af7d-4d0a848447bf,capacity,也就是申请的存储最大容量是1M,这个数值可以和前面的pvc定义文件相符:
[root@master ~]# k get pv -A NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-9e72b552-f3dd-432e-af7d-4d0a848447bf 1Mi RWX Delete Bound default/test-claim managed-nfs-storage 81m
(六)
部署一个单实例MySQL并挂载
建立一个pvc ,名称为test-claim1:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim1 namespace: database spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1Gi storageClassName: managed-nfs-storage
[root@master mysql]# cat deploy_mysql.yaml apiVersion: apps/v1 kind: Deployment metadata: name: mysql2 namespace: database spec: selector: matchLabels: app: mysql2 template: metadata: labels: app: mysql2 spec: containers: - name: mysql2 image: mysql:5.7.23 env: - name: MYSQL_ROOT_PASSWORD value: "123456" ports: - containerPort: 3306 volumeMounts: - name: nfs-pvc-test mountPath: /var/lib/mysql subPath: mysql volumes: - name: nfs-pvc-test persistentVolumeClaim: claimName: test-claim1
(七)
mysql2这个pod 服务发布:
[root@master mysql]# cat svc_mysql.yaml apiVersion: v1 kind: Service metadata: name: mysql2 namespace: database spec: type: NodePort ports: - port: 3306 targetPort: 3306 nodePort: 32222 selector: app: mysql2 selector: app: mysql2
执行此文件即可: kubectl apply -f svc_mysql.yaml
(八)
Navicat连接数据库,IP地址填写任意一个集群内IP,比如,192.168.217.16,端口号是32222
(九)
单元测试
查看pod:
[root@master ~]# k get po -A NAMESPACE NAME READY STATUS RESTARTS AGE database mysql2-7b77d5f496-8r5tr 1/1 Running 2 15h
查看pod的详细信息:
[root@master ~]# k describe pod mysql2-7b77d5f496-2p6bl -n database Name: mysql2-7b77d5f496-2p6bl Namespace: database Priority: 0 Node: slave1/192.168.217.17 Start Time: Sat, 16 Jul 2022 12:48:05 +0800 Labels: app=mysql2 pod-template-hash=7b77d5f496 Annotations: <none> Status: Running IP: 10.244.1.6 IPs: IP: 10.244.1.6 Controlled By: ReplicaSet/mysql2-7b77d5f496 Containers: mysql2: Container ID: docker://95c44eb4ed328ea8993190450c753bd8bfaae573929f81f25c1adf3d8f3d7ab9 Image: mysql:5.7.23 Image ID: docker://sha256:1b30b36ae96ace2d29cd9c7a724cbb9d1ce59424a79cad4d117175d273d1689b Port: 3306/TCP Host Port: 0/TCP State: Running Started: Sat, 16 Jul 2022 12:48:06 +0800 Ready: True Restart Count: 0 Environment: MYSQL_ROOT_PASSWORD: 123456 Mounts: /var/lib/mysql from nfs-pvc-test (rw,path="mysql") /var/run/secrets/kubernetes.io/serviceaccount from default-token-v2llp (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: nfs-pvc-test: Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace) ClaimName: test-claim1 ReadOnly: false default-token-v2llp: Type: Secret (a volume populated by a Secret) SecretName: default-token-v2llp Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: <none>
这里可以看到,使用的pvc名称是test-claim1,并没有手动建立pv,总体功能是完全正常的。
总结:
一二三四步骤是第一个阶段,五以及后面的步骤都是pvc的创建以及pv的自动创建测试。PVC创建的时候使用的namespace和后面部署pod的namespace要一致,第六步有详细的演示,两个文件内定义的namespace必须要一致哦,和最开始的rbac角色创建和nfs-client-provisioner的pod的部署的时候的namespace没有关系了。PROVISIONER_NAME 这个需要两个文件相互呼应,storageclass-nfs.yaml 这个文件会引用到这个值。pvc创建的时候只需要storageClassName这个名称了,因为StorageClass里已经定义过了。这里提到的两个值可以通过命令快速查出:
[root@master ~]# k get sc -A NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storager fuseim.pri/ifs Delete Immediate true 4h6m