如何在K8s上部署Redis集群
1.前言
架构原理:每个Master可以有多个Slave。当Master离线时,Redis集群会从多个Slave中选出一个新的Master作为替代,老Master上线后会成为新Master的Slave。
2.准备工作
本次部署主要基于这个项目:
https://github.com/zuxqoj/kubernetes-redis- cluster
包含两种部署Redis集群的方式:
StatefulSet
Service&Deployment
两种方式各有优缺点。对于Redis、Mongodb、Zookeeper等有状态服务,使用StatefulSet是首选方法。本文将主要介绍如何使用StatefulSet部署Redis集群。
3. StatefulSet简介
RC、Deployment、DaemonSet都是无状态服务。它们管理的Pod的IP、名称、启动和停止顺序都是随机的,什么是StatefulSet?顾名思义,有状态集合管理所有有状态服务,例如 MySQL、MongoDB 集群等。
StatefulSet 本质上是 Deployment 的变体,在 v1.9 中已经成为 GA 版本。为了解决有状态服务的问题,它管理的Pod有固定的Pod名称、启动和停止序列,在StatefulSet中,Pod名称称为网络标识符(主机名),必须使用共享存储。
在Deployment中对应的服务是service,在StatefulSet中对应的是headless service,无头服务,即无头服务。与服务的区别在于它没有集群IP。解析该 Headl 对应的所有 Pod 的端点列表
另外,StatefulSet 基于 Headless Service 为 StatefulSet 控制的每个 Pod 副本创建一个 DNS 域名。该域名的格式为:
$(podname).(无头服务器名称) FQDN: $(podname).(无头服务器名称).namespace .svc.cluster.local
也就是说对于有状态服务,最好使用固定的网络标识符(例如域名信息)来标记节点。当然,这也需要应用程序的支持(例如Zookeeper支持在配置文件中写入主机域名)。
StatefulSet基于Headless Service(即没有Cluster IP的Service)为Pod实现了稳定的网络标志(包括Pod的主机名和DNS记录),在Pod重新调度后保持不变。同时StatefulSet与PV/PVC结合,可以实现稳定的持久化存储。即使 Pod 重新安排后,原始持久数据仍然可以访问。
下面是使用StatefulSet部署Redis的架构。无论是Master还是Slave,都是StatefulSet的一个副本,通过PV来持久化数据,并暴露为Service来接受客户端请求。
4.部署流程
文章参考项目的README简单介绍了基于StatefulSet创建Redis的步骤:
1.创建NFS存储
2.创建PV
3.创建PVC
4.创建Configmap
5.创建headless服务
6.创建Redis StatefulSet
7初始化Redis集群
这里我就参考上面的步骤,练习一下操作,详细介绍一下Redis集群的部署过程。本文会涉及到很多K8S的概念。希望大家提前了解和学习
1.创建NFS存储
创建NFS存储主要是为了给Redis提供稳定的后端。结束存储,当重新说明: OD重启或迁移后,仍能获取原始数据。这里,我们首先创建NFS,然后使用PV挂载Redis的远程NFS路径。
安装NFS
yum -y install nfs-utils(主包提供文件系统) yum - y install rpcbind(提供rpc协议)
然后添加/etc/exports文件,设置要共享的路径:
创建对应目录[root@ftp quizii]# mkdir -p /usr/local/k8s/redis/pv{1. .6}接下来,启动NFS和rpcbind服务:
systemctl restart rpcbindsystemctl restart nfssystemctl启用nfs[root@ftp pv3]# exportfs -v/usr/local/k8s/redis/pv1192.168.0.0/24( 同步,wdelay,隐藏,no_subtree_check,sec=sys ,rw,安全,no_root_squash,no_all_squash)/ usr / local / k8s / redis / pv2192.168.0.0/24(同步,wdelay,隐藏,no_subtree_check,sec = sys,rw,安全,no_root_squash,no_all_squash)/ usr / local /k8s/redis/pv3192.168.0.0/24(同步,wdelay,隐藏,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s /redis/pv4192.168.0.0/24(同步,wdelay,隐藏,no_subtree_check,sec = sys,rw,安全,no_root_squash,no_all_squash)/ usr / local / k8s / redis / pv5192.168.0.0/24(同步,wdelay,隐藏,no_subtree_check,sec = sys,rw ,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv6192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)客户
yum -y install nfs-utils查看存储共享
[root @node2 ~]# showmount -e 192.168.0.222导出 192.168.0.222 的列表:/usr/local/k8s/redis/pv6 192.168.0.0/24/usr/local/k8s/ redis/pv5 192.168.0.0/24/usr /local/k8s/redis/pv4 192.168.0.0/24/usr/local/k8s/redis/pv3 192.168.0.0/24/usr/local/k8s/redis/pv2 192.168。 0.0/24/usr/local/k8s/redis/pv1 192.168.0.0/24创建PV
每个Redis Pod都需要一个独立的PV来存储自己的数据,因此可以创建一个pv .yaml 文件,包含 6 个 PV:[root@master redis]# cat pv.yaml apiVersion: v1kind: PersistentVolumemetadata: name: nfs- PV1Spec: CapAcity :存储:200M 访问模式:-Radwritemany NFS:服务器:192.168.0.222 路径:“/USR/LOCAL/K8S/PV1” --- APIVERSION:V1Kind:P Ersistitvolumemetadata:名称:nfs-vp2spec:容量:存储:200M 访问模式: - ReadWriteMany nfs:服务器:192.168.0.222 路径:“/usr/local/k8s/redis/pv2"---apiVersion: v1kind: PersistentVolumemetadata: 名称: nfs-pv3spec: 容量: 存储: 200M 访问模式: - ReadWriteMany nfs: 服务器: 192.168.0.222 路径: "/usr/local/k8s/redis/pv3"-- -apiVersion:v1kind:PersistentVolumemetadata:名称:nfs-pv4spec:容量:存储:200M accessModes:-ReadWriteMany nfs:服务器:192.168.0.222 路径:“/usr/local/k8s/redis/pv4”---apiVersion:v1kind: PersistentVolumemetadata: 名称: nfs-pv5spec: 容量: 存储: 200M accessModes: - ReadWriteMany nfs: 服务器: 192.168.0.222 路径: "/usr/local/k8s/redis/ pv5"---apiVersion : v1kind: PersistentVolumemetadata: 名称: nfs -pv6spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:“/usr/local/k8s/redis/pv6”如上,可以看到所有PV都是除了名称和挂载路径之外基本相同,执行创建即可:
[root@master redis]#kubectl create -f pv.yaml permanentvolume " nfs-pv1" 创建持久entvolume“nfs-pv2”创建了持久卷“nfs-pv3”创建了持久卷“nfs-pv4”创建了持久卷“nfs-pv5”创建了持久卷“nfs-pv6”创建了2.创建Configmap
在这里,我们可以将Redis配置文件直接转换成Configmap,这是一种更方便的读取配置的方式。配置文件redis.conf如下[root@master redis]# cat redis.confappendonly yescluster-enabled yescluster-config-file /var/ lib/redis /nodes.confcluster-node-timeout 5000dir /var/lib/redisport 6379创建一个名为 redis-conf 的 Configmap:
kubectl create configmap redis-conf --from-file=redis.conf查看创建的configmap:
[root@ master redis ]# kubectl 描述 cm redis-confName: redis-confNamespace: defaultLabels:注释: Data====redis.conf:----appendonly yescluster-enabledyescluster-config-file /var/lib/redis/nodes.confcluster-node-timeout 5000dir /var/ lib/redisport 6379Events: 同上,保存redis.conf中的所有配置项到 Configmap redis-conf。
3.创建Headless服务
Headless服务是StatefulSet实现稳定网络识别的基础,我们需要提前创建它。准备文件 headless-service.yml 如下:[root@master redis]# cat headless-service.yaml apiVersion: v1kind: Servicemetadata: name: redis- 服务标签: app: redisspec: ports: - name: redis-port 端口: 6379 clusterIP: None 选择器: app: redis appCluster: redis-cluster创建:
kubectl create -f headless-service.yml查看:
可以看到服务名称为redis-service,其CLUSTER-IP 为 None,这意味着这是一个“无头”服务。4.创建Redis集群节点
创建Headless服务后,可以使用StatefulSet创建Redis集群节点。这也是本文的核心内容。我们首先创建redis.yml文件:[root@master redis]# cat redis.yaml apiVersion: apps/v1beta1kind: StatefulSetmetadata: name: redis- appspec:serviceName:“redis-service”副本:6模板:元数据:标签:app:redis appCluster:redis-cluster规范:terminationGracePeriodSeconds:20亲和力:podAntiAffinity:PreferredDuringSchedulingIgnoredDuringExecution:-权重:100 podAffinityTerm:labelSelector:matchExpressions:-key:应用程序运算符:在值中: - redis 拓扑密钥:kubernetes.io/hostname 容器: - 名称:redis 映像:redis 命令: - “redis-server” args: - “/etc/redis/re dis.conf” - “--保护模式” - “无” 资源:“ 请求:” cpu:“100m” 内存:“100Mi” 端口: - 名称:redis 容器端口:6379 协议:“TCP” - 名称:集群容器端口:16379 协议:“TCP“volumeMounts:-名称:“redis-conf”挂载路径:“/etc/redis”-名称:“redis-data”挂载路径:“/var/lib/redis”卷:-名称:“redis-conf”configMap:名称:“redis-conf”项目:项目-键:“redis.conf”路径:“redis.conf”volumeClaimTemplates:-元数据:名称:redis-data规范:accessModes:[“ReadWriteMany”]资源:请求:存储:200M如上,一共创建了6个Redis节点(Pod),其中3个作为master,另外3个作为master的slave;Redis配置挂载了之前生成的redis-conf Configmap通过volume到容器的/etc/redis/redis.conf;使用volumeClaimTemplates(即PVC)声明Redis数据存储路径,它将绑定到我们之前创建的文件PV上
这里有一个关键的概念——亲和力,具体请参考官方文档,其中podAntiAffinity代表反亲和力,它决定了某个pod不能被哪些pod访问部署在同一拓扑域中。可以用来将服务的pod分散在不同的主机或者拓扑域中,以提高服务本身的稳定性。 。
PreferredDuringSchedulingIgnoredDuringExecution 表示在调度期间应尽可能满足亲和性或反亲和性规则。如果不能满足规则,POD也可能被调度到相应的主机上。在后续运行过程中,系统将不再检查是否满足这些规则。
这里,matchExpressions规定Redis Pod应该被调度到尽可能不分配给包含app redis的Node,也就是说Redis Pod应该尽可能少的分配给已经有Redis的Node。不过,由于我们只有3个Node和6个副本,根据PreferredDuringSchedulingIgnoredDuringExecution,这些豌豆必须被挤掉,这样更健康~
另外,根据StatefulSet的规则,6个Red我们生成的 Pod 的主机名将命名为$(statefulset name)-$(serial number)
如下所示:[root@Master Redis]#Kubectl get pods -O Wide name Ready Status Restarts Age IP NODE NODEREDIS-APP 0 1/1 Running0 172.17.24.3 192.168.0.144 2小时 172.17.63.8 192.168.0.14 8 <无>redis-app- 2 1/1 运行 0 2h 172.17.24.8 192.168.0.144 <无>redis-app-3 1/1 运行 0 172.17. 63.9 192.168.0.148 bsp; 0 2小时172.17.24.9 192.168.0.144redis-app-5 1/1 Running 0 2h 172.17.63.10 192.168.0.148 如上,可以看到部署时部署了这些Pod,它们按照 {0...N-1} 的顺序依次创建。需要注意的是,redis-app-1启动后直到redis-app-0状态达到Running状态时才会启动。
同时,每个Pod都会获得集群中的一个DNS域名,格式为$(podname).$(service name).$(namespace).svc.cluster.local< /code>,也就是:
redis-app-0.redis-service.default.svc.cluster.localredis-app-1.redis- service.default.svc.cluster.local...等等...在K8S集群内,这些Pod之间可以使用这个域名进行通信。我们可以使用busybox镜像的nslookup来查看这些域名:
[root@master redis]# kubectl exec -ti busybox -- nslookup redis-应用程序-0。 redis-service服务器:10.0.0.2地址1:10.0.0.2 kube-dns.kube-system.svc.cluster.localName: redis-app-0.redis-serviceAddress 1: 172.17.24.3可以看到,redis-的IP app-0 是 172.17.24.3。 当然,如果Redis Pod迁移或者重启(我们可以手动删除一个Redis Pod来测试),IP会改变,但是Pod的域名、SRV记录、A记录不会改变。
另外我们可以发现我们之前创建的pv已经绑定成功了:
[root@master redis ]# kubectl get pvNAME 容量访问模式回收策略状态声明存储类原因 AGenfs-pv1 200M RWX 保留绑定默认/redis-data-redis-app-2 3hnfs-pv3 200M RWX 保留绑定默认/redis-data-redis-app- 4 3hnfs-pv4 200M RWX 保留绑定默认/redis-data-redis-app-5 3hnf s-pv5 200M RWX 保留绑定默认/redis-data-redis-app-1 3hnfs-pv6 200M RWX 保留绑定默认/redis-data -redis-app-0 3hnfs-vp2 200M RWX 保留绑定默认/红色is-data-redis-app-3 3小时5。初始化Redis集群
创建6个Redis Pod后,我们还需要使用常用的Redis-tribe工具。集群初始化
创建Ubuntu由于Redis集群要等到所有节点启动后才能初始化,而如果将初始化逻辑写入Statefulset中,是一个非常复杂且低效的行为。在这里,不得不赞一下原项目作者的想法,值得学习。也就是说,我们可以在K8S上额外创建一个容器,专门用于管理和控制K8S集群内的某些服务。
这里,我们专门启动一个Ubuntu容器,在容器中安装Redis-tribe,然后初始化Redis集群,执行:kubectl run -it ubuntu --image=ubuntu --restart=Never /bin/bash我们使用阿里云的Ubuntu源,执行:
root@ubuntu:/# cat > /etc/apt/sources.list << EOFdeb http://mirrors.aliyun.com/ubuntu/ bionic 主受限宇宙 multiversedeb-src http://mirrors.aliyun .com /ubuntu/ bionic 主受限宇宙 multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-security 主受限宇宙 multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-security 主受限宇宙 multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-updates 主要受限宇宙 multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates 主要受限宇宙 multiversedeb http://mirrors.aliyun.com/ ubuntu/ bionic-propose 主要受限宇宙多元宇宙 deb-src http://mirrors.aliyun.com/ubuntu/ bionic-propose 主要受限宇宙多元宇宙 deb http://mirrors.aliyun.com/ubuntu/ bionic-backports 主要受限宇宙multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports 主受限宇宙多元宇宙>EOF成功后,原project需要执行以下命令安装基础软件环境:
apt-get updateapt-get install -y vim wget python2.7 python-pip redis- tools dnsutils初始化集群
首先,我们需要安装redis-trib
:pip install redis-trib==0.5.1然后,创建一个只有Master节点的集群:
redis-trib。 py create \ `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \ `dig + Short redis-app-1.redis-service.default.svc.cluster.local` :6379 \ `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379其次,为每个Master添加Slave
redis-trib.py 复制 \ --master-addr `dig +short redis-app-0 .redis-service.default.svc.cluster.local`:6379 \ --slave- addr `dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379redis-trib.py 重新plicate \ --master-addr `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \ --slave-addr `dig +shortredis-app-4.redis-service. default.svc.cluster.local`:6379redis-trib.py 复制 \ --master-addr `dig +short redis-app-2.redis-service.default.svc.cluster .local`:6379 \ --slave- addr `dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379至此,我们的Redis集群才真正创建完成,连接到任意一个Redis Pod即可检查:
[root@master redis]# kubectl exec -it redis-app-2 /bin /bashroot@redis-app-2:/data# /usr/local/bin/redis-cli -c127.0.0.1:6379> 集群节点5d3e77f6131c6f272576530b23d1cd7592942eec 172.17.24.3:6379@16379 master - 0 1559628533000 1 个已连接 0-5461a4b 529c40a920da314c6c93d17dc603625d6412c 172.17 .63.10:6379@16379 主站 - 0 1559628531670 6 已连接10923-16383368971dc8916611a86577a8726e4f1f3a69c5eb7 172.17.24.9:6379@16379从站0025e6140f85cb243c60c214467b7e77bf819ae3 0 15596 28533672 4 已连接0025e6140f85cb243c60c214467b7e77bf819ae3 172.17.63.8:6379@16379 主站 - 0 1559628533000 2 已连接 5462-109226d5ee94b78b279e7d3c77a55 437695662e8c039e 172.17.24.8:6379@16379我自己,奴隶a4b529c40a920da314c6c93 d17dc603625d6412c 0 1559628532000 5已连接2eb3e06ce914e0e285d6284 c4df32573e318bc01 172.17.63.9:6379@16379从属5d3e77f6131c6f27257 6530b23d1cd7592942eec 0 1559628533000 3已连接127。 0.0.1:6379> 集群 infocluster_state:okcluster_slots_signed:16384cluster_slots_ok:16384cluster_slots_pfail:0cluster_slots_fail:0cluster_known_nodes:6cluster_size:3cluster_current_epoch:6cluster_my_epoch:6cluster_stats_messages_ping_s ent:1 4910cluster_stats_messages_pong_sent:15139cluster_stats_messages_sent:30049cluster_stats_messages_ping_received:15139cluster_stats_messages_pong_received:14910cluster_stats_messages_received:30049127.0.0.1:6379> 前>另外,还可以在NFS上查看Redis挂载的数据:
[root@ftp pv3]#ll/usr/local/k8s/redis/pv3total 12-rw-r--r-- 1 root root 92 Jun 4 11:36appendonly.aof-rw-r--r-- 1 root root 175 Jun 4 11:36 dump.rdb-rw-r--r-- 1根根794年6月4日11:49nodes.conf6。创建用于访问的Service
前面我们创建了一个Headless Service用于实现StatefulSet,但是这个Service没有Cluster IP,所以无法用于外部访问。因此,我们还需要创建一个Service,专门为Redis集群提供访问和负载均衡:[root@master redis]# cat redis-access - service.yaml apiVersion: v1kind: Servicemetadata: name: redis-access-service labels: app: redisspec: ports: - name: redis-port 协议: "TCP" 端口: 6379 targetPort: 6379 选择器: app: redis appCluster: redis -cluster如上,服务名称为
redis-access-service
。 K8S集群暴露6379端口,labels name
为app:redis
的Pod orappCluster:redis-cluster
将进行负载平衡。创建后查看:
[root@master redis]# kubectl get svc redis-access-service -o WideNAME TYPE CLUSTER- IP EXTERNAL-IP PORT(S) AGE SELECTORredis-access-service ClusterIP 10.0.0.646379/TCP 2h app=redis,appCluster=redis-cluster 如上,在 K8S 集群中,所有应用程序可以通过
10.0.0.64:6379
访问 Redis 集群。当然,为了方便测试,我们也可以在Service中添加一个NodePort,映射到物理机上,这里就不详细介绍了。5.测试主从切换
在K8S上搭建了完整的Redis集群后,我们最关心的就是它原有的高可用机制是否正常。这里,我们可以任意选择一个Master Pod来测试集群的主从切换机制,如redis-app-0
:[root@master redis]# kubectl get pods redis-app-0 -o WideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODEredis-app-1 1/1 Running0 3h 172.17.24.3 192.168。 0.144输入
redis-app-0
查看:[root@master redis ]# kubectl exec -it redis-app-0 /bin/bashroot@redis-app-0:/data# /usr/local/bin/redis-cli -c127.0.0.1:6379> 角色1) "master"2 ) (整数)133703) 1) 1) "172.17.63.9" 2) "6379" 3) "13370"127.0.0.1:6379>如上所示,
app-0
是master,slave是172.17.63.9
,也就是redis-app-3
。接下来,我们手动删除
redis-app-0
:[root@master redis]# kubectl delete pod redis-app-0pod“redis-app-0”已删除[root@master redis]# kubectl get pod redis-app-0 -o WideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODEredis-app-0 1/1 Running 0 4m 172.17.24.3 192.168.0.144 <无>我们进入
redis-app-0
里面查看:[root@master redis]# kubectl exec -it redis-app -0 /bin/bashroot@redis-app-0:/data# /usr/local/bin/redis-cli -c127.0.0.1:6379> 角色1) “从站”2) “172.17. 63.9"3) (integer) 63794) "connected"5) (integer) 13958如上,
redis-app-0
变了,成为了slave,属于它的上一个从节点172.17.63.9
,即redis-app-3
。6.问题
此时,您可能会想,为什么在不使用 stable 标志的情况下,Redis Pod 故障转移能够正常进行?这就涉及到Redis本身的机制了。因为Redis集群中的每个节点都有自己的NodeId(保存在自动生成的nodes.conf中),并且NodeId不会随着IP的变化而变化。这实际上是一个固定的网络符号。换句话说,即使Redis Pod重新启动,Pod仍然会加载保存的NodeId以维持其身份。我们可以查看NFS上redis-app-1的nodes.conf文件:[root@k8s-node2 ~]# cat /usr/local/k8s/redis/pv1/nodes.conf 96689f2023089173e528d3a71c4ef10af68ee462 192.168 .169.209:6379 @16379 从站 d884c4971de9748f99b10d14678d864187a9e5d3 0 1526460952651 4 连接237d46046d9b75a6822f02523ab894928e2300e6 19 2.168.169.200 :6379@16379 从机 c15f378a604ee5b200f06cc23e9371cbc04f4559 0 1526460952651 1 已连接
c15f378a604ee5b200f06cc23e9371cbc04f455 9 192.168.169.1 97:6379@16379 主站 - 0 1526460952651 1 连接 10923-16383d884c4971de9748f99b1192.168 .169.198:6379 @16379我自己,从机c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 0 1526460952565 3已连接
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 192.168.169。 201:6379@16379 master - 0 1526460952651 6 linked 0-5461vars currentEpoch 6 lastVoteEpoch 4
如上,第一列是NodeId,即稳定的;第二列是IP和端口信息,可能会发生变化。这里介绍NodeId的两种使用场景:
当Slave Pod时断线重连,IP发生变化,但是Master发现自己的NodeId还是一样,所以认为Slave还是之前的Slave。
当 Master Pod 离线时,集群会在其 Slave 中选举一个新的 Master。旧Master上线后,集群发现自己的NodeId还是一样,旧Master就成为新Master的Slave。
对于这两种场景,如果有兴趣可以自己测试一下,并注意观察Redis日志。
这篇《如何在K8s上部署Redis集群》的文章就分享到这里。希望以上内容能够对大家有所帮助,让大家能够学到更多的知识。 ,如果您觉得文章不错,请转发,让更多人看到。
2. 本站积分货币获取途径以及用途的解读,想在本站混的好,请务必认真阅读!
3. 本站强烈打击盗版/破解等有损他人权益和违法作为,请各位会员支持正版!
4. 编程技术 > 如何在K8s上部署Redis集群