title: k8s数据持久化之自动创建pv #标题tags: 数据持久化 #标签
date: 2020-08-09
categories: k8s # 分类
要k8s使用nfs实现数据持久化,流程上有些问题,手动配置的流程大概是: 搭建nfs底层存储===》创建PV====》创建PVC===》创建pod。最终pod中的container实现数据的持久化。
上述流程中,看似没什么问题,但细想一下,PVC在向PV申请存储空间的时候,是根据指定的pv名称、访问模式、容量大小来决定具体向哪个PV来申请空间的,如果PV的容量为20G,定义的访问模式是WRO(只允许以读写的方式挂载到单个节点),而PVC申请的存储空间为10G,那么一旦这个PVC是向上面的PV申请的空间,也就是说,那个PV有10个G的空间被浪费了,因为其只允许被单个节点挂载。就算不考虑这个问题,我们每次手动去创建PV也就比较麻烦的事情,这时,我们就需要一个自动化的工具来替我们创建PV。
这个东西就是阿里提供的一个开源镜像“nfs-client-provisioner”,这个东西是通过k8s内置的NFS驱动挂载远端的NFS服务器到本地目录(这里的本地目录指的是 nfs-client-provisioner 容器内部的目录,而不是k8s宿主机的本地目录),然后容器自身作为storage(存储)。
当然,PVC是无法直接去向nfs-client-provisioner申请使用的存储空间的,这时,就需要通过SC(storageClass)这个资源对象去申请了,SC的根本作用就是根据PVC定义的值去自动创建PV。
总结流程为:搭建nfs底层存储===》运行容器nfs-client-provisioner ====》创建SC ====》创建PVC(在此时,SC会自动创建PV) ===》创建pod
这篇博文将来写下具体实现过程,并用nginx及mysql来做数据持久化,废话不多说,开搞。
搭建nfs
yum -y install nfs-utils
systemctl enable rpcbind
mkdir -p /data
cat >> /etc/exports << EOF
/data *(rw,sync,no_root_squash)
EOF
systemctl start nfs-server
systemctl enable nfs-server
showmount -e
Export list for master:
/data *
创建RBAC授权
$ cat > rbac-rolebind.yaml << EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner
namespace: default
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: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get","create","list", "watch","update"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
EOF
$ kubectl apply -f rbac-rolebind.yaml
运行 nfs-client-provisioner 容器
$ cat > nfs-deployment.yaml << EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs-client-provisioner
name: db-nfs-client
namespace: default
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
containers:
- env:
- name: PROVISIONER_NAME
value: nfs-data
- name: NFS_SERVER
value: 192.168.20.2 # 指定nfs服务IP
- name: NFS_PATH
value: /data # 执行nfs服务共享目录
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
imagePullPolicy: IfNotPresent
name: nfs-client-provisioner
volumeMounts:
- mountPath: /persistentvolumes
name: nfs-client-root
restartPolicy: Always
serviceAccount: nfs-provisioner
volumes:
- name: nfs-client-root
nfs: # 指定nfs共享目录及IP
path: /data
server: 192.168.20.2
EOF
$ kubectl apply -f nfs-deployment.yaml
创建SC(StroageClass)
$ cat > stroageclass.yaml << EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: statefu-nfs
namespace: default
provisioner: nfs-data #这里要和nfs-client-provisioner的env环境变量中的value值对应。
reclaimPolicy: Retain
EOF
$ kubectl apply -f stroageclass.yaml
ReclaimPolicy:PV的回收策略
- Recycle:重复利用。
- Retain:保留。
- Delete:删除
- PS:注意这里的回收策略是指,在PV被删除后,在这个PV下所存储的源文件是否删除。
Recycle
此回收策略将在PV回收时,执行一个基本的清除操作(rm -rf /thevolume/*),并使其再次被新的PVC绑定。
Retain
此回收策略最为保险,需要集群管理员手动回收该资源,当绑定的PVC被删除后,PV仍然存在,并被认为是“已释放”。但是此时该PV仍然不能被其他PVC绑定,因为前一个绑定的PVC对应的容器组数据还在其中。
若要回收,可以通过以下步骤来回收;
- 删除该PV,PV删除后,其数据仍然存在于对应的外部存储介质中(nfs、cefpfs、GFS等)。
- 手工删除对应存储介质上的数据。
- 手工删除对应的存储介质,也可以创建一个新的PV并再次使用该存储介质。
Delete
此策略将从k8s集群中移除PV以及其关联的外部存储介质(云环境中的 AWA EBS、GCE PD、Azure Disk 或 Cinder volume)。
注: 动态提供的 PV 将从其对应的 StorageClass 继承回收策略的属性。
至此,准备工作完成。
使用pvc存储运行nginx
创建PVC
$ cat > nginx-pvc.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-claim
namespace: default
spec:
storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应
accessModes:
- ReadWriteMany #访问模式为RWM
resources:
requests:
storage: 100Mi
EOF
# PVC只能同时绑定到一个PV,但一个PV可以通过设置访问模式,被多个PVC绑定使用,关于PV的访问模式如下:
# - ReadWriteOnce:只能以读写的方式挂载到单个节点(单个节点意味着只能被单个PVC声明使用)。
# - ReadOnlyMany:能以只读的方式挂载到多个节点。
# - ReadWriteMany:能以读写的方式挂载到多个节点。
$ kubectl apply -f nginx-pvc.yaml
$ kubectl get pv,pvc # 查看pv及pvc,确定pv,pvc状态都为 bound,表示关联成功。
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX Delete Bound default/nginx-claim statefu-nfs 3m48s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/nginx-claim Bound pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX statefu-nfs 3m48s
至此,已经实现了根据PVC的申请存储空间去自动创建PV(本地的nfs共享目录下已经生成了一个目录,名字挺长的,是pv+pvc名字定义的目录名),至于这个PVC申请的空间是给哪个pod使用,这已经无所谓了。
创建nginx容器
$ cat > nginx.yaml <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web-nginx
namespace: default
spec:
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: web
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: web
spec:
containers:
- image: 'nginx:latest'
imagePullPolicy: IfNotPresent
name: myweb
volumeMounts:
- mountPath: /usr/share/nginx/html/
name: myweb-persistent-storage
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: myweb-persistent-storage
persistentVolumeClaim:
claimName: nginx-claim
---
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web-nginx
namespace: default
spec:
externalTrafficPolicy: Cluster
ports:
- name: n5tdhh
nodePort: 32565
port: 80
protocol: TCP
targetPort: 80
selector:
app: web
type: NodePort
EOF
$ kubectl apply -f nginx.yaml
# 自定义网页内容(你的pvc生成的目录名字和我的应该不一样,请自行改动)
$ echo 'test nginx pv pvc' >> /data/default-nginx-claim-pvc-08f79600-01f8-4996-b7ac-c7d4d7707067/index.html
浏览器访问:
至此,以后只要将你业务上的网页代码存放到本地的nfs目录下即可供容器使用。
使用pvc存储运行mysql
创建pvc
cat > mysql-pvc.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-claim
namespace: default
spec:
storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应
accessModes:
- ReadWriteMany #访问模式为RWM
resources:
requests:
storage: 200Mi
EOF
$ kubectl apply -f mysql-pvc.yaml
$ kubectl get pv,pvc # 查看pv及pvc
创建mysql容器
cat > mysql.yaml << EOF
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db-mysql
spec:
selector:
matchLabels:
app: mysql
replicas: 1
serviceName: db-mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- env:
- name: MYSQL_ROOT_PASSWORD
value: 123.com
image: 'mysql:5.7'
imagePullPolicy: IfNotPresent
name: mysql
volumeMounts:
- mountPath: /var/lib/mysql
name: vo1
volumes:
- name: vo1
persistentVolumeClaim:
claimName: mysql-claim
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
type: NodePort
ports:
- port: 3306
targetPort: 3306
nodePort: 31111
selector:
app: mysql
EOF
$ kubectl apply -f mysql.yaml
验证数据持久化效果
$ kubectl get pods -o wide # 查看容器详细信息
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-mysql-0 1/1 Running 0 7m27s 10.100.140.73 node02 <none> <none>
db-nfs-client-745d766695-94zb6 1/1 Running 0 39m 10.100.140.70 node02 <none> <none>
web-nginx-85c4748b9f-6sl5p 1/1 Running 0 23m 10.100.140.71 node02 <none> <none>
web-nginx-85c4748b9f-xbj5j 1/1 Running 0 23m 10.100.196.136 node01 <none> <none>
web-nginx-85c4748b9f-znwvh 1/1 Running 0 23m 10.100.196.135 node01 <none> <none>
$ kubectl exec -it db-mysql-0 -- /bin/bash # 进入容器
# 以下是创库,创表,写测试数据等操作。
$ mysql> create database db_test;
Query OK, 1 row affected (0.03 sec)
$ mysql> use db_test;
Database changed
$ mysql> create table t1 (
-> id int(4),
-> name varchar(15)
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> insert into t1 value(1,'zhangsan');
Query OK, 1 row affected (0.03 sec)
mysql> insert into t1 value(1,'lisi');
Query OK, 1 row affected (0.00 sec)
# 退出容器后,删除mysql这个pod(目的是删除后,StatefulSet控制器自动拉起一个新的pod):
$ kubectl delete pod/db-mysql-0 # 删除此pod
pod "db-mysql-0" deleted
$ kubectl get pods -o wide # 再次查看,会发现pod的IP已经发生了变化,说明这是一个新的pod,之前的pod已经没了
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
db-mysql-0 1/1 Running 0 19s 10.100.140.74 node02 <none> <none>
db-nfs-client-745d766695-94zb6 1/1 Running 0 41m 10.100.140.70 node02 <none> <none>
web-nginx-85c4748b9f-6sl5p 1/1 Running 0 25m 10.100.140.71 node02 <none> <none>
web-nginx-85c4748b9f-xbj5j 1/1 Running 0 25m 10.100.196.136 node01 <none> <none>
web-nginx-85c4748b9f-znwvh 1/1 Running 0 25m 10.100.196.135 node01 <none> <none>
# 进入pod,查看数据是否还在
$ kubectl exec -it pod/db-mysql-0 -- /bin/bash
$ mysql -uroot -p123.com
mysql> show databases; # 库还在
+--------------------+
| Database |
+--------------------+
| information_schema |
| db_test |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.02 sec)
mysql> select * from db_test.t1; # 数据也还在
+------+----------+
| id | name |
+------+----------+
| 1 | zhangsan |
| 1 | lisi |
+------+----------+
2 rows in set (0.01 sec)
经此验证,可以保障mysql运行在k8s中,数据做到持久化,不管容器怎样,只要本地的持久化数据还在,或者说有备份,那么随时拉起一个pod,使用这个持久化的数据,都可以继续使用。