第九章 数据管理
Pod是短暂的,Pod在销毁时,保存在容器内部的文件系统各种的数据会被清除。
为了持久化保存容器中的的数据,可以使用K8s Volume。
9.1 Volume
9.1.1 emptyDir
对于容器来说是持久的,对于Pod不是。当Pod从节点删除时,Volume的内容也会被删除。但是如果只是容器被销毁而Pod存在,则volume不受影响。也就是说:emptyDir Volume的生命周期与Pod一致。
Pod中的所有容器都可以共享Volume,它们可以指定各自的mount路径。
如下Pod有两个容器: producer 和 consumer,它们共享一个Volume. Producer 写, consumer 读。
vim emptyDir.yml
apiVersion: v1
kind: Pod
metadata:
name: producer-consumer
spec:
containers:
- image: busybox
name: producer
volumeMounts: # 将shared-volume mount 到 producer_dir目录
- mountPath: /producer_dir
name: shared-volume
args: # 将数据写入到文件hello中
- /bin/sh
- -c
- echo "hello world" > /producer_dir/hello ; sleep 30000
- image: busybox
name: consumer
volumeMounts: # 将shared-volume mount 到 /consumer_dir
- mountPath: /consumer_dir
name: shared-volume
args:
- /bin/sh
- -c
- cat /consumer_dir/hello ; sleep 30000 # 通过cat从文件hello读数据
volumes: # 定义了一个emptyDir类型的Volume,名字是shared-volume.
- name: shared-volume
emptyDir: {}
执行
$kubectl apply -f emptyDir.yml
检查
$kubectl logs producer-consumer consumer
hello world
#
$kubectl get pods producer-consumer -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
producer-consumer 2/2 Running 0 26m 10.244.1.218 k8s-node-122132072 <none> <none>
#在node节点上
docker ps |grep producer -i
#
docker inspect e2f72565c8bf
"Mounts": [
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/c5879c3e-8803-4d0d-a0ce-e41304853f3c/volumes/kubernetes.io~empty-dir/shared-volume",
"Destination": "/producer_dir",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
emptyDir是host上创建的临时目录,其优点是能够方便地为Pod中的容器提供共享存储,不需要额外的配置。它不具备持久性,如果Pod没有了,emptyDir也就没有了。所以emptyDir的使用场景是: 适合Pod中的容器需要临时共享存储空间的场景。
9.1.2 hostPath
hostPath volume的作用是将Docker Host文件系统中已经存在的目录mount给Pod的容器。大部分应用不会使用HostPath,因为它增加了Pod与节点的耦合。
应用场景: 需要访问K8s或docker内部数据(配置文件和二进制库)的应用需要使用hostPath.
9.1.2 外部Storage Provider
如果K8s部署在公有云上(比如AWS, Azure等),可以直接使用云硬盘作为Volume.
Ceph: 相对于emptyDir和hostPath,这些volume类型的最大特点就是不依赖K8s。Volume的底层基础设施由独立的存储系统管理,与K8S集群分离。
9.2 PersistentVolume & PersistentVolumeClaim
Volume在可管理上有不足: 要使用Volume,Pod必须事先知道如下信息:
(1)当前Volume来自AWS,CEPH
(2)AWS/CEPT Volume 已经提前创建, 并且知道确切的Volume-id
Pod是开发人员维护,Volume是存储系统管理员维护。二者之间耦合,不利于大规模系统的开发,沟通效率低下。
K8s给出的解决方案是:PersistentVolume(PV) 和 PersistentVolumeClaim(PVC)
PV: 外部存储系统中的一块存储空间,由管理员维护和创建。PV具有持久性,声明周期独立于Pod。
PVC: 是对PV的申请。PVC通常由普通用户创建和维护。需要为Pod分配资源时,用户可以创建一个PVC,指定存储资源容量的大小和访问模式,K8s会查找并提供满足条件的PV。
有了PVC,用户只需要告诉K8s需要什么资源,而不必关心真正的空间从哪里来、如何访问。
K8s支持多种类型的PV,比如AWS EBS、Ceph、NFS等。
9.2.1 NFS PV
1. 建立NFS服务
#所有节点安装nfs
yum install -y nfs-common nfs-utils
#在master节点创建共享目录
mkdir /data1/nfsdata
#授权共享目录
chmod 666 /data1/nfsdata
#编辑exports文件
cat /etc/exports
/data1/nfsdata *(rw,no_root_squash,no_all_squash,sync)
配置生效
exportfs -r
#启动rpc和nfs(注意顺序)
systemctl start rpcbind
systemctl start nfs
#systemctl status rpcbind
systemctl status nfs
exportfs命令:
exportfs [-aruv]
-a :全部mount或者unmount /etc/exports中的内容
-r :重新mount /etc/exports中分享出来的目录
-u :umount 目录
-v 在export的时候,将详细的信息输出到屏幕上
客户端测试NFS
client:
yum install nfs-utils rpcbind
mkdir /data1/nfs_disk
service rpcbind start
service nfs start
$showmount -e 10.122.xx.71 #nfs-server ip
Export list for 10.122.xx.71:
/data1/nfsdata 10.122.xx.0/24
mount -t nfs -o noatime,nodiratime 10.122.xx.71:/data1/nfsdata /data1/nfs_disk
$df -h |grep nfs
10.122.xx.71:/data1/nfsdata 33T 3.1G 33T 1% /data1/nfs_disk
2. 创建PV
创建一个 PV mypv1,配置文件 nfs-pv1.yml
vim nfs-pv1.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv1
namespace: default
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
storageClassName: nfs
persistentVolumeReclaimPolicy: Recycle
nfs:
path: "/data1/nfsdata"
server: 10.122.132.71
创建 mypv1
$kubectl apply -f nfs-pv.yaml
persistentvolume/mypv1 created
$kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 1Gi RWX Recycle Available nfs 30s
3. 创建PVC
PVC 就很简单了,只需要指定 PV 的容量,访问模式和 class。
vim nfs-pvc1.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc1
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
#创建pvc
kubectl apply -f nfs-pvc1.yml
persistentvolumeclaim/mypvc1 created
#查看
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc1 Bound mypv1 1Gi RWX nfs 25s
4. 创建pod
上面已经创建好了pv和pvc,pod中直接使用这个pvc即可
vim nfs-pod1.yml
apiVersion: v1
kind: Pod
metadata:
name: mypod1
spec:
containers:
- name: mypod1
image: busybox
args:
- /bin/sh
- -c
- sleep 10000
volumeMounts:
- name: mydata
mountPath: "/mydata"
volumes:
- name: mydata
persistentVolumeClaim:
claimName: mypvc1
##创建pod
kubectl apply -f nfs-pod1.yml
pod/mypod1 created
#查看
kubectl get pod -o wide |egrep "name|mypod" -i
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mypod1 1/1 Running 0 42s 10.244.3.7 k8s-node-122132073 <none> <none>
与使用普通 Volume 的格式类似,在 volumes 中通过 persistentVolumeClaim 指定使用 mypvc1 申请的 Volume。
5. 验证
kubectl exec -it mypod1 /bin/sh
/ # ls mydata
/ # echo 'hello nfs'>mydata/hello.txt
/ # ls mydata/
hello.txt
可见,在 Pod 中创建的文件 /mydata/hello 确实已经保存到了 NFS 服务器目录 /data1/nfsdata中。
9.2.2 PV的回收
如果不再需要使用 PV,可用删除 PVC 回收 PV。
$kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 1Gi RWX Recycle Bound default/mypvc1 nfs 138m
$kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc1 Terminating mypv1 1Gi RWX nfs 43m
未删除pvc之前 pv的状态是Bound.
$kubectl delete pvc mypvc1
persistentvolumeclaim "mypvc1" deleted
$kubectl get pvc
$kubectl delete pv mypv1
$kubectl get pv
因为 PV 的回收 ,但这可能不是我们想要的结果。如果我们希望保留数据,可以将策略设置为Retain。
9.2.3 PV的动态供给
前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态供给(Static Provision)。
与之对应的是动态供给(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。
动态供给是通过 StorageClass 实现的,StorageClass 定义了如何创建 PV,下面是两个例子。
1.StorageClass standard: AWS EBS(略)
#AWS EBS略
Kubernetes 支持其他多种动态供给 PV 的 Provisioner,完整列表请参考 https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
2.glusterfs provisioner
https://kubernetes.io/docs/concepts/storage/storage-classes/#glusterfs
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://127.0.0.1:8081"
clusterid: "630372ccdc720a92c681fb928f27b53f"
restauthenabled: "true"
restuser: "admin"
secretNamespace: "default"
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"
9.3 一个数据库例子
下面演示如何为 MySQL 数据库提供持久化存储,步骤为:
创建 PV 和 PVC。
部署 MySQL。
向 MySQL 添加数据。
模拟节点宕机故障,Kubernetes 将 MySQL 自动迁移到其他节点。
验证数据一致性。
首先创建 PV 和 PVC,配置如下:
mysql-pv.yml
vim mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
namespace: default
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: nfs
persistentVolumeReclaimPolicy: Retain
nfs:
path: "/data1/nfsdata/mysql-pv"
server: 10.122.132.71
# 应用
kubectl apply -f mysql-pv.yaml
persistentvolume/mysql-pv created
mysql-pvc.yml
vim mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
#kubectl apply -f mysql-pvc.yaml
persistentvolumeclaim/mysql-pvc created
检查
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mysql-pv 1Gi RWO Retain Bound default/mysql-pvc nfs 3m30s
[root@k8s-master-122132071 k8s]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pvc Bound mysql-pv 1Gi RWO nfs 51s
接下来部署 MySQL,配置文件如下:
vim mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
selector:
app: mysql
ports:
- protocol: "TCP"
port: 3306
targetPort: 3306
type: LoadBalancer
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
# Use secret in real usage
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: tz-config
mountPath: /etc/localtime
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
- name: tz-config
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
PVC mysql-pvc Bound 的 PV mysql-pv 将被 mount 到 MySQL 的数据目录 var/lib/mysql。
应用mysql-service.yaml
#
$kubectl apply -f mysql-service.yaml
service/mysql created
deployment.apps/mysql created
#
$kubectl get pv
$kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pvc Bound mysql-pv 1Gi RWO nfs 16m
#
$kubectl get svc -o wide |egrep "NAME|mysql" -i
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
mysql ClusterIP 10.10.76.104 <none> 3306/TCP 25m app=mysql
#
$kubectl get pods -o wide |egrep "NAME|mysql" -i
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-fd9db4d59-hjjrb 1/1 Running 0 2m11s 10.244.3.10 k8s-node-122132073 <none> <none>
#查看详情
$kubectl describe pod mysql-fd9db4d59-hjjrb
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m59s default-scheduler Successfully assigned default/mysql-fd9db4d59-hjjrb to k8s-node-122132073
Normal Pulling 2m52s kubelet, k8s-node-122132073 Pulling image "mysql:5.6"
Normal Pulled 2m43s kubelet, k8s-node-122132073 Successfully pulled image "mysql:5.6"
Normal Created 2m43s kubelet, k8s-node-122132073 Created container mysql
Normal Started 2m43s kubelet, k8s-node-122132073 Started container mysql
#查看日志
$kubectl logs mysql-fd9db4d59-hjjrb
2019-10-08 23:11:21 1 [Note] Event Scheduler: Loaded 0 events
2019-10-08 23:11:21 1 [Note] mysqld: ready for connections.
Version: '5.6.45' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
#
可以看到mysql布署在k8s-node-122132073 上。下面通过客户端访问 Service mysql:
#测试mysql
mysql -h 10.10.76.104 -P3306 -ppassword -e "select version()"
或者
$kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
If you don't see a command prompt, try pressing enter.
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.6.45 |
+-----------+
$kubectl get pods |egrep 'NAME|mysql' -i
NAME READY STATUS RESTARTS AGE
mysql-client 1/1 Running 0 5m58s
mysql-fd9db4d59-hjjrb 1/1 Running 0 12m
更新数据
mysql> show databases;
mysql> create database test;
mysql> use test;
Database changed
mysql> create table my_id(id int(4));
Query OK, 0 rows affected (0.00 sec)
mysql> insert into my_id values(111);
Query OK, 1 row affected (0.00 sec)
#查看文件
ls -l /data1/nfsdata/mysql-pv/
total 110600
-rw-rw---- 1 systemd-bus-proxy ssh_keys 12582912 Oct 8 23:40 ibdata1
-rw-rw---- 1 systemd-bus-proxy ssh_keys 50331648 Oct 8 23:40 ib_logfile0
-rw-rw---- 1 systemd-bus-proxy ssh_keys 50331648 Oct 8 23:40 ib_logfile1
drwx------ 2 systemd-bus-proxy ssh_keys 4096 Oct 8 23:40 mysql
drwx------ 2 systemd-bus-proxy ssh_keys 4096 Oct 8 23:40 performance_schema
drwx------ 2 systemd-bus-proxy ssh_keys 10 Oct 8 23:40 test
测试故障迁移
关闭 k8s-node-122132073 上的pod,看是否会进行故障切换。
$ kubectl get pods -o wide |egrep "name|mysql" -i
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-fd9db4d59-hjjrb 1/1 Running 0 19m 10.244.3.10 k8s-node-122132073 <none> <none>
$ kubectl get svc -o wide |egrep "name|mysql" -i
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
mysql ClusterIP 10.10.76.104 <none> 3306/TCP 41m app=mysql
#容器中没有ps要手动安装
apt-get update && apt-get install procps
#k8s-node-122132073 停止pod
[root@k8s-node-122132073 ~]# docker ps |grep mysql
bc771f2776ab mysql "docker-entrypoint.s…" 5 minutes ago Up 5 minutes k8s_mysql_mysql-699d897494-bmfx9_default_11530308-49e9-4f09-ab6c-a5e8e3ba36cb_0
cbc13eb6f7ee registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 5 minutes ago Up 5 minutes k8s_POD_mysql-699d897494-bmfx9_default_11530308-49e9-4f09-ab6c-a5e8e3ba36cb_0
#
$docker stop bc771f2776ab cbc13eb6f7ee
#检查
mysql -h 10.10.76.104 -P 3306 -ppassword test -e 'select * from my_id'