Kubernetes与GlusterFS的爱恨情仇
用Kubernetes部署的应用可以分为无状态的和有状态的,无状态的应用没有数据,Pod(一个或若干容器的集合)挂了被重新拉起,或者在Kubernetes集群不同的Node节点(可以认为是一台物理机或虚拟机)之间飘来飘去,都没有关系;有状态的应用有数据需要保存,如果容器挂了被重新拉起,容器里面保存的数据就没了。这时候我们自然而然的想到可以把数据映射到容器所在主机上,就像我们使用Docker时经常做的一样,可是这时候有个问题是,Kubernetes集群一般有多个Node节点,如果容器在挂了被重新拉起的时候被调度到其他的Node节点,那映射在原先主机上的数据还是在原先主机上,新的容器还是没有原来的数据。
怎么办呢?
这时候我们的另一位主角就要出场了——对,就是把数据存储在分布式存储GlusterFS上,Pod通过网络连接到分布式存储,这样不管Pod怎么在不同的Node节点间飘,连接的都是同一个分布式存储,数据都还在。
这么说是Kubernetes需要GlusterFS了,都说“被需要的都有恃无恐”,那么GlusterFS是不是可以有恃无恐了呢?–当然不是,毕竟在这个残酷的世界,没有人是可以活得那么舒服的。事实上,Kubernetes的选择很多,目前Kubernetes支持的存储有下面这些:
- gcePersistentDisk
- awsElasticBlockStore
- AzureFile
- AzureDisk
- FC(Fibre Channel)
- FlexVolume
- Flocker
- NFS
- iSCSI
- RBD(Ceph Block Device)
- CephFS
- Cinder(OpenStack Block Storage)
- Glusterfs
- VsphereVolume
- Quobyte Volumes
- HostPath(就是刚才说的映射到主机的方式,多个Node节点会有问题)
- VMware Photon
- Portworx Volumes
- ScaleIO Volumes
- StorageOS
Kubernetes有这么多选择,GlusterFS只是其中之一,GlusterFS也有自己的优点,它是一个开源的分布式文件系统,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS的Volume有多种模式,复制模式可以保证数据的高可靠性,条带模式可以提高数据的存取速度,分布模式可以提供横向扩容支持,几种模式可以组合使用实现优势互补。
下面就来看看Kubernetes和GlusterFS是怎么结合起来的吧,实战开始了。
部署Kubernetes
部署方法可参考Kubernetes官网。
假设Kubernetes部署在
Master:
192.168.XX.A
Node:
192.168.XX.A 192.168.XX.B 192.168.XX.C
Kubernetes版本:
# kubectl --version Kubernetes v1.5.2
部署GlusterFS
部署机器(这里跟Kubernetes部署在同样的机器):
192.168.XX.A 192.168.XX.B 192.168.XX.C
在每台机器的/etc/hosts加上:
192.168.XX.A glusterfs1 192.168.XX.B glusterfs2 192.168.XX.C glusterfs3
安装yum源(每台机器执行):
yum install centos-release-gluster
安装GlusterFS(每台机器执行):
yum -y install glusterfs glusterfs-fuse glusterfs-server
安装结束。
启动GlusterFS(每台机器执行):
systemctl start glusterd.service systemctl enable glusterd.service
组建集群(192.168.XX.A 机器执行):
gluster peer probe glusterfs2 gluster peer probe glusterfs3
验证(192.168.XX.A 机器执行):
# gluster peer status Number of Peers: 2 Hostname: glusterfs2 Uuid: 30efc726-35b5-4502-8f7f-f238ea44f3aa State: Peer in Cluster (Connected) Other names: 192.168.XX.B Hostname: glusterfs3 Uuid: 2c7aaa1b-4d51-4560-88be-cbe42e30b7a3 State: Peer in Cluster (Connected) Other names: 192.168.XX.C
看到其他两个点的信息即代表GlusterFS集群组建成功。
Kubernetes使用GlusterFS
有两种方式,手动和自动,手动需要每次使用存储时自己创建GlusterFS的卷(GlusterFS的数据存储在卷Volume上);自动利用Kubernetes的 Dynamic Provisioning 特性,可以由Kubernetes自动创建GlusterFS卷,但是需要先部署Heketi软件,并且安装GlusterFS的机器上还要有裸磁盘。
手动方式:
1)创建GlusterFS卷
新建卷(3个副本的复制模式):
(在每台机器执行:) mkdir -p /data/brick1/gv0 (在一台机器执行:) gluster volume create gv0 replica 3 glusterfs1:/data/brick1/gv0 glusterfs2:/data/brick1/gv0 glusterfs3:/data/brick1/gv0 force
启动卷:
(在一台机器执行:) gluster volume start gv0
查看卷:
(在一台机器执行:) # gluster volume info Volume Name: gv0 Type: Replicate Volume ID: 2f8147de-fcb6-4219-81a3-71d6cfcaa609 Status: Started Snapshot Count: 0 Number of Bricks: 1 x 3 = 3 Transport-type: tcp Bricks: Brick1: glusterfs1:/data/brick1/gv0 Brick2: glusterfs2:/data/brick1/gv0 Brick3: glusterfs3:/data/brick1/gv0 Options Reconfigured: transport.address-family: inet nfs.disable: on
可以看到所创建的卷的信息。
2)Kubernetes创建PV等存储
Kubernetes用PV(PersistentVolume)、PVC(PersistentVolumeClaim)来使用GlusterFS的存储,PV与GlusterFS的Volume相连,相当于提供存储设备,所以需要由知道GlusterFS Volume的系统管理员创建(这里我们自己就是系统管理员);PVC消耗PV提供的存储,由应用部署人员创建,应用直接使用PVC进而使用PV的存储。
以下操作在Kubernetes Master节点执行。
系统管理员创建Endpoint、Service、PV(Endpoint和Service不用每次都建,可以复用):
先创建三个文件:
glusterfs-endpoints.json:
{ "kind": "Endpoints", "apiVersion": "v1", "metadata": { "name": "glusterfs-cluster" }, "subsets": [ { "addresses": [ { "ip": "192.168.XX.A" } ], "ports": [ { "port": 1 } ] }, { "addresses": [ { "ip": "192.168.XX.B" } ], "ports": [ { "port": 1 } ] }, { "addresses": [ { "ip": "192.168.XX.C" } ], "ports": [ { "port": 1 } ] } ] }
glusterfs-service.json:
{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "glusterfs-cluster" }, "spec": { "ports": [ {"port": 1} ] } }
glusterfs-pv.yaml:
apiVersion: v1 kind: PersistentVolume metadata: name: gluster-dev-volume1 labels: name: mysql1 spec: capacity: storage: 1Gi accessModes: - ReadWriteMany glusterfs: endpoints: "glusterfs-cluster" path: "gv0" readOnly: false
执行命令创建:
kubectl apply -f glusterfs-endpoints.json kubectl apply -f glusterfs-service.json kubectl apply -f glusterfs-pv.yaml
查看Endpoint、Service、PV:
kubectl get ep kubectl get svc kubectl get pv
3)Kubernetes创建应用
应用部署人员创建PVC及应用(这里以MySQL为例):
创建两个文件:
glusterfs-pvc.yaml:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: glusterfs-mysql1 spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi selector: matchLabels: name: "mysql1"
mysql-deployment.yaml:
apiVersion: v1 kind: Service metadata: name: mysql labels: name: mysql spec: type: NodePort ports: - name: mysqlport port: 3306 nodePort: 31016 selector: name: mysql --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mysql spec: replicas: 1 template: metadata: labels: name: mysql spec: containers: - name: mysqlcontainer image: registry.hundsun.com/library/mysql:5.7.12 imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD value: root12345 ports: - containerPort: 3306 volumeMounts: - name: gluster-mysql-data mountPath: "/var/lib/mysql" volumes: - name: gluster-mysql-data persistentVolumeClaim: claimName: glusterfs-mysql1
执行命令创建:
kubectl apply -f glusterfs-pvc.yaml kubectl apply -f mysql-deployment.yaml
查看PVC、Service:
kubectl get pvc kubectl get svc
查看Pod:
# kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE mysql-1858843218-68cts 1/1 Running 0 30m 10.254.39.9 192.168.XX.C
看到MySQL Pod是Running状态,表示容器创建成功,运行在 192.168.XX.C 节点上。
4)测试
可以用MySQL客户端连接(连接的IP是任意Kubernetes Node节点IP,31016和root12345是mysql-deployment.yaml里指定的映射端口和MySQL root用户密码),连接后我们建一个数据库liao:
# ./mysql -h 192.168.XX.A -P 31016 -uroot -proot12345 Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.12 MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> MySQL [(none)]> create database liao; Query OK, 1 row affected (0.02 sec) MySQL [(none)]> MySQL [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | liao | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.01 sec) MySQL [(none)]>
可以看到已经成功创建了一个数据库liao。
现在我们测试一下Pod重新调度后数据还在不在:
删除Pod :
kubectl delete po mysql-1858843218-68cts
Pod被删除后Kubernetes会自动把它重新拉起,在哪个Node节点上拉起由Kubernetes自己调度决定。
过一小会儿重新查看Pod:
# kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE mysql-1858843218-v1fvf 1/1 Running 0 28s 10.254.34.5 192.168.XX.A
可以看到Pod已经被调度到192.168.XX.A上。
MySQL客户端连接并查看数据库:
# ./mysql -h 192.168.XX.A -P 31016 -uroot -proot12345 Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 2 Server version: 5.7.12 MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> MySQL [(none)]> MySQL [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | liao | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.03 sec) MySQL [(none)]>
可以看到我们建的liao数据库还在,说明虽然Pod重新调度,但数据还在。
有人会疑问这里两次连接的都是192.168.XX.A的31016端口,为什么说连接到了不同的Pod,这是因为Kubernetes的kube-proxy会把我们配置的Service里映射的端口在每个Kubernetes Node上都开出来,也就是在任何一个Kubernetes Node上都能连接到Pod,Pod重新调度后,还是在任何一个Kubernetes Node上都能连接,但后面的Pod其实已经是新的了。
自动方式
自动方式需要先部署Heketi软件,Heketi用来管理GlusterFS,并提供RESTful API接口供Kubernetes调用。Heketi需要使用裸磁盘,假设三个GlusterFS节点上都挂了一块裸磁盘 /dev/xvde。
1)部署Heketi
部署在:
192.168.XX.A
安装:
yum install heketi heketi-client -y
修改/etc/heketi/heketi.json(省略了没有修改的部分):
{ ... "port": "8083", ... "_glusterfs_comment": "GlusterFS Configuration", "glusterfs": { "_executor_comment": [ "Execute plugin. Possible choices: mock, ssh", "mock: This setting is used for testing and development.", " It will not send commands to any node.", "ssh: This setting will notify Heketi to ssh to the nodes.", " It will need the values in sshexec to be configured.", "kubernetes: Communicate with GlusterFS containers over", " Kubernetes exec api." ], "executor": "ssh", "_sshexec_comment": "SSH username and private key file information", "sshexec": { "keyfile": "/home/liao/key/key", "user": "root", "port": "22", "fstab": "/etc/fstab" }, ... } }
这里主要把端口改为8083了(防止冲突),executor改为ssh, sshexec的各项配置也做了相应修改。
其中的Keyfile制作方法:
ssh-keygen -t rsa
输入key(随便起的名字),一直回车。
制作完成后会在当前目录下生成key、key.pub,把 key.pub上传到GlusterFS三台服务器的/root/.ssh/下面,并重命名为authorized_keys,/etc/heketi/heketi.json中的Keyfile指向生成的key(包含路径)。
启动:
systemctl enable heketi systemctl start heketi
看日志:
journalctl -u heketi
(Heketi数据目录: /var/lib/heketi)
验证:
curl http://192.168.XX.A:8083/hello
或:
heketi-cli --server http://192.168.XX.A:8083 cluster list
配置节点:
新建topology.json:
{ "clusters": [ { "nodes": [ { "node": { "hostnames": { "manage": [ "glusterfs1" ], "storage": [ "192.168.XX.A" ] }, "zone": 1 }, "devices": [ "/dev/xvde" ] }, { "node": { "hostnames": { "manage": [ "glusterfs2" ], "storage": [ "192.168.XX.B" ] }, "zone": 2 }, "devices": [ "/dev/xvde" ] }, { "node": { "hostnames": { "manage": [ "glusterfs3" ], "storage": [ "192.168.XX.C" ] }, "zone": 1 }, "devices": [ "/dev/xvde" ] } ] } ] }
载入配置:
export HEKETI_CLI_SERVER=http://192.168.XX.A:8083 heketi-cli topology load --json=topology.json
查看拓扑:
heketi-cli topology info
建个大小为2G的volume试试:
heketi-cli volume create --size=2
查看:
heketi-cli volume list
删除:
heketi-cli volume delete
2)Kubernetes创建StorageClass
Kubernetes通过创建StorageClass来使用 Dynamic Provisioning 特性,StorageClass连接Heketi,可以根据需要自动创建GluserFS的Volume,StorageClass还是要系统管理员创建,不过StorageClass不需要每次创建,因为这个不需要很多,不同的PVC可以用同一个StorageClass。
新建文件:
glusterfs-storageclass.yaml:
apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: slow provisioner: kubernetes.io/glusterfs parameters: resturl: "http://192.168.XX.A:8083" volumetype: "replicate:3"
replicate:3代表会创建三个副本复制模式的GluserFS Volume。
执行命令创建:
kubectl apply -f glusterfs-storageclass.yaml
查看:
# kubectl get storageclass NAME TYPE slow kubernetes.io/glusterfs
3)Kubernetes创建应用
应用部署人员创建PVC及应用(还是以MySQL为例)。
创建两个文件:
glusterfs-pvc.yaml:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: glusterfs-mysql1 annotations: volume.beta.kubernetes.io/storage-class: "slow" spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi
mysql-deployment.yaml:
apiVersion: v1 kind: Service metadata: name: mysql labels: name: mysql spec: type: NodePort ports: - name: mysqlport port: 3306 nodePort: 31016 selector: name: mysql --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: mysql spec: replicas: 1 template: metadata: labels: name: mysql spec: containers: - name: mysqlcontainer image: registry.hundsun.com/library/mysql:5.7.12 imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD value: root12345 ports: - containerPort: 3306 volumeMounts: - name: gluster-mysql-data mountPath: "/var/lib/mysql" volumes: - name: gluster-mysql-data persistentVolumeClaim: claimName: glusterfs-mysql1
执行命令创建:
kubectl apply -f glusterfs-pvc.yaml kubectl apply -f mysql-deployment.yaml
查看Endpoint、Service、PV,可以发现这些都自动建好了:
# kubectl get ep NAME ENDPOINTS AGE glusterfs-dynamic-glusterfs-mysql1 192.168.XX.A:1,192.168.XX.B:1,192.168.XX.C:1 9s
# kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE glusterfs-dynamic-glusterfs-mysql1 10.254.132.6 1/TCP 42s
# kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE pvc-f81121a0-ae8b-11e7-a91a-286ed488c82a 1Gi RWX Delete Bound default/glusterfs-mysql1 4m
查看PVC:
# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESSMODES AGE glusterfs-mysql1 Bound pvc-f81121a0-ae8b-11e7-a91a-286ed488c82a 1Gi RWX 5m
可以看到PV和PVC已经绑定好。
还是可以用刚才的命令连接到MySQL:
mysql -h 192.168.XX.A -P 31016 -uroot -proot12345
按刚才的方式测试MySQL Pod重新调度后数据还在不在,可以发现数据还在。
从上面可以看到手动方式需要系统管理员每次手动建GlusterFS的Volume和Kubernetes的PV,或者系统管理员事先建好一批Volume和PV;而自动方式不需要,Kubernetes可以根据应用部署人员的需要动态创建Volume和PV,节省了很多工作量,推荐使用自动方式。
原文链接:
作者:廖林荣