单机下用Redis二进制程序包搭建Redis集群的案例很多,用docker在单节点上搭集群的也很多,但是在k8s下单节点搭集群的就很少了,有的只是挂载一个临时目录,数据无法持久化,pod销毁后,数据就没了。在k8s环境下测试机又不够的情况下使用Redis集群就不太方便了,本文就是笔者的根据自身需要实践出来的,期间也找了很多网上资料,最后自己综合琢磨出的解决方案,由于对docker和k8s不是很精通,可能有其他更简单的方案,欢迎大家交流。
一、所需的背景知识
1、对docker,k8s有一定使用经验:会编写yaml文件,知道如何排查pod不能running的问题
2、在单机环境下搭建过Redis集群
3、如果以上的知识都不知道也没关系,本文尽量保证你按照步骤执行不出错。但是k8s环境你必须得有,基本的linux命令知识得有
二、软件版本
k8s v1.10.2
docker 18.03.1-ce, build 9ee9f40
Redis5.0.5
三、准备工作
docker pull redis:5.0.5
mkdir redis-cluster
cd redis-cluster/
mkdir data
创建多个节点的数据存储目录,避免Redis实例启动时配置文件冲突导致无法启动,在单台物理机上搭建过Redis集群的应该知道。建好6个节点的目录,后面会用到,这里的目录要注意权限问题,k8s启动的pod需要读写这里的文件夹
for port in `seq 7001 7006`; do \
mkdir -p ./${port}/
done
创建完成后目录结构
目录结构四、编辑yaml文件
创建k8s的yaml文件
回到redis-cluster目录下
1、创建ConfigMap
先创建redis.conf配置文件
vi redis-cm.yaml
将以下内容贴到文件中,保存退出
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-conf
data:
redis.conf: |
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
2、创建statefulset
vi redis-statefulset.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
nodeSelector:
node: mfc # 这里需要根据自己的k8s节点情况修改,本案例需修改成node-222
terminationGracePeriodSeconds: 20
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: "redis:latest"
imagePullPolicy: IfNotPresent #默认情况是会根据配置文件中的镜像地址去拉取镜像,配置为本地有镜像不拉取远程,避免你的环境不能访问外网,拉取镜像失败
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "--protected-mode"
- "no"
resources:
requests:
cpu: "100m"
memory: "100Mi"
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf"
mountPath: "/etc/redis"
- name: "redis-data"
mountPath: "/var/lib/redis"
volumes:
- name: "redis-conf"
configMap:
name: "redis-conf"
items:
- key: "redis.conf"
path: "redis.conf"
volumeClaimTemplates: #可看作pvc的模板
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
3、创建pv的yaml文件
vi pv1.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: redis-pv-volume1
labels:
type: local
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /home/bboss/redis-cluster/data/7001/
/home/bboss/redis-cluster/data/7001/此目录为前面我们创建的redis数据存储的6个目录
将此文件复制5份,依次命名为pv2.yaml 到pv6.yaml
在对应的文件中将
name: redis-pv-volume1
path: /home/bboss/redis-cluster/data/7001/
这2处修改为对应文件编号的序号,例如pv2.yaml修改为
name: redis-pv-volume2
path: /home/bboss/redis-cluster/data/7002/
五、开始搭建集群
1、创建cm和pv如下图
km create -f redis-cm.yaml
km create -f pv1.yaml
image.png
2、创建statefulset
确定当前宿主机在k8s集群中的label
kubectl get nodes --show-labels
k8s集群机器ip和对应的label
如图,我当前部署的机器ip是222对应的node标签名为node-222
将redis-statefulset.yaml文件中node: mfc 修改为 node: node-222,稍后启动redis的pod是都会选择在node-222这个节点的机器上启动,这正是我们需要的,修改正确后
km create -f redis-statefulset.yaml
[注:我这里km是做了配置,km相当于是kubectl -n mfc-namespace 这是指定k8s的namespace的,我的namespace名是mfc-namespace,kubectl命令不指定-n 默认是使用default为名的namespace]
kubectl get pods -n mfc-namespace | grep redis
查看6个redis节点是否都启动完成
查看一下pod对应的ip,下一步配置集群需要知道各节点的ip,这ip是k8s分配的
kubectl get pods -o wide -n mfc-namespace | grep redis
验证一下pvc是否都bound了
kubectl get pvc -o wide -n mfc-namespace
redis节点的ip
3、初始化Redis集群
启动一个独立的redis
docker run -it redis:latest bash
redis-cli --cluster create 10.254.79.20:6379 10.254.79.21:6379 10.254.79.22:6379 10.254.79.23:6379 10.254.79.24:6379 10.254.79.25:6379 --cluster-replicas 1
初始化Redis集群
接上图
4、验证Redis集群
日志显示集群已经初始化好,slots也分配完成,验证一下集群
用redis-cli随便连接某个redis节点
/usr/local/bin/redis-cli -c -h 10.254.79.20 -p 6379
验证集群
搭建完成了,下面来看看本地存储的数据
随便进到某个目录下看看,如下图redis集群的数据已经落到物理机上了。
验证一下pod重启后集群数据是否还在
重启Redis集群看ip还是原来的ip,这就是k8s定义statefulset的作用
通过刚才启动的独立redis再次连接集群看数据还在不在,如下图数据都还在
5、创建headlessService
到这里我们的Redis集群已经是完全可用的了,但是我们的应用要使用该集群时配置ip不好记,这时就需要用到k8s中的Service
vi redis-headlessService.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis
appCluster: redis-cluster
执行 km create -f redis-headlessService.yaml 创建服务
查看服务
另外用k8s启一个有nslookup或者ping工具的pod,验证一下服务名是否可以访问
验证服务名k8s是自带dns功能的,k8s会根据serviceName生成对应的域名
如上图,可以看到节点1 (10.254.79.20)的完整域名是redis-app-0.redis-service.mfc-namespace.svc.cluster.local,在k8s中同一个namespace的服务只需要前面的子域名即可,后面的会默认补齐,本例中的serviceName是redis-app-0.redis-service,这是一个节点的服务名。对于整个集群的serviceName是redis-service
用刚才的docker启动的redis验证一下域名是否可以连接,如下图用域名连接失败,因为我们这个是用docker启动的不在k8s中,用pod的ip是可以连接的,因为pod的ip访问是走docker的网络。因为服务名是k8s做的路由,所以必须在k8s集群中注册才可以使用服务名互相访问。serviceName其实是在操作系统中做了一个转发的动作,通过iptales做策略实现的。
用域名连接失败用redis集群中的任意一个节点验证,域名是可以访问的
用redis集群中的节点验证六、总结
网上大部分文章都是类似的方案,使用pv,pvc做存储,有的pv是绑定的nfs,我也试过nfs碰到问题没解决只能想其他办法,但是我理解的是如果6个redis节点的配置文件nodes.conf和数据文件都在共享一个目录,那还是会有冲突,导致redis只能启动一个节点,而其他节点无法启动,除非能把文件命名定义成不同节点对应不同的文件名,因为没搭成功基于nfs的pvc存储,不知道是不是每个节点一个隔离的pvc,所以无法知道以后会再实践。所以本例主要是通过定义不同的pv把目录隔离开,但是定义StatefulSet时不好指定pvc,看到有文章用到volumeClaimTemplates(可看作pvc的模板)传输门于是问题就应然而解了,StatefulSet在创建pod时是前一个创建成功才继续下一个是有顺序的,同时创建pvc也是有顺序的,每个pvc绑定一个pv,这样6个节点的数据文件就自动绑定到各自的目录下了。
另外redis集群初始化不需要Ruby了,避免了安装ruby的麻烦。
七、参考资料
[https://www.cnblogs.com/tylerzhou/p/11027559.html]
[https://v1-12.docs.kubernetes.io/zh/docs/tasks/run-application/force-delete-stateful-set-pod/]
[https://juejin.im/post/5d206b1e5188252f275fdc95]
[https://www.cnblogs.com/breezey/p/6582082.html]
[https://juejin.im/post/5c989ff2f265da60f206ffe4#heading-7]
[https://www.jianshu.com/p/a5172b0eeae4]
https://www.cnblogs.com/xiaochangwei/p/7993065.html
网友评论