一、环境准备
1.1 环境说明
本文搭建mongodb,基于wmware虚拟机,操作系统centos 8,且已经基于kubeadm搭好了k8s集群,k8s节点信息如下:
| 服务器 | ip地址 |
| master | 192.168.31.80 |
| node1 | 192.168.31.8 |
| node2 | 192.168.31.9 |
如需知道k8s集群搭建,可跳转我的文章查看。
1.2 安装说明
redis ( remote dictionary server),即远程字典服务。
是一个开源的使用ansi c语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的api。
它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、lua脚本、lru收回、事务以及不同级别磁盘持久化功能,同时通过redis sentinel提供高可用,通过redis cluster提供自动分区。
1.3 redis集群说明
一般来说,redis部署有三种模式。
1)单实例模式
一般用于测试环境。
2)哨兵模式
在redis3.0以前,要实现集群一般是借助哨兵sentinel工具来监控master节点的状态。如果master节点异常,则会做主从切换,将某一台slave作为master。引入了哨兵节点,部署更复杂,维护成本也比较高,并且性能和高可用性等各方面表现一般。
3)集群模式
3.0 后推出的 redis 分布式集群解决方案;主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用;如果master节点异常,也是会自动做主从切换,将slave切换为master。
后两者用于生产部署,但总的来说,集群模式明显优于哨兵模式。那么今天我们就来讲解下:k8s环境下,如何部署redis集群(三主三从)。
二、安装nfs
2.1 安装nfs
我选择在 master 节点创建 nfs 存储,首先执行如下命令安装 nfs:
yum -y install nfs-utils rpcbind
2.2 创建nfs共享文件夹
mkdir -p /var/nfs/redis/pv{1..6}2.3 配置共享文件夹
vim /etc/exports /var/nfs/redis/pv1 *(rw,sync,no_root_squash) /var/nfs/redis/pv2 *(rw,sync,no_root_squash) /var/nfs/redis/pv3 *(rw,sync,no_root_squash) /var/nfs/redis/pv4 *(rw,sync,no_root_squash) /var/nfs/redis/pv5 *(rw,sync,no_root_squash) /var/nfs/redis/pv5 *(rw,sync,no_root_squash)
2.4 使配置生效
exportfs -r
2.5 查看所有共享目录
exportfs -v

2.6 启动nfs
systemctl start nfs-server systemctl enabled nfs-server systemctl start rpcbind systemctl enabled rpcbind
2.7 其他节点安装nfs-utils
yum -y install nfs-utils
三、创建pv卷
persistentvolume简称pv,持久化存储,是k8s为云原生应用提供一种拥有独立生命周期的、用户可管理的存储的抽象设计。
在创建pv卷之前,需要创建nfs客户端、nfs 客户端sa授权和storeclass存储类。
3.1 创建namespace
kubectl create ns redis-cluster
3.2 创建nfs 客户端sa授权
cat > redis-nfs-client-sa.yaml
apiversion: v1
kind: serviceaccount
metadata:
name: redis-nfs-client
namespace: redis-cluster
---
kind: clusterrole
apiversion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-runner
namespace: redis-cluster
rules:
- apigroups: [""]
resources: ["persistentvolumes"]
verbs: ["get","list","watch","create","delete"]
- apigroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get","list","watch","create","delete"]
- apigroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get","list","watch"]
- apigroups: [""]
resources: ["events"]
verbs: ["get","list","watch","create","update","patch"]
- apigroups: [""]
resources: ["endpoints"]
verbs: ["create","delete","get","list","watch","patch","update"]
---
kind: clusterrolebinding
apiversion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
namespace: redis-cluster
subjects:
- kind: serviceaccount
name: redis-nfs-client
namespace: redis-cluster
roleref:
kind: clusterrole
name: nfs-client-runner
apigroup: rbac.authorization.k8s.io3.3 创建nfs 客户端
cat > redis-nfs-client.yaml
apiversion: apps/v1
kind: deployment
metadata:
name: redis-nfs-client
labels:
app: redis-nfs-client
# replace with namespace where provisioner is deployed
namespace: redis-cluster
spec:
replicas: 1
strategy:
type: recreate
selector:
matchlabels:
app: redis-nfs-client
template:
metadata:
labels:
app: redis-nfs-client
spec:
serviceaccountname: redis-nfs-client
containers:
- name: redis-nfs-client
image: quay.io/external_storage/nfs-client-provisioner:latest
volumemounts:
- name: redis-nfs-client-root
mountpath: /persistentvolumes
env:
- name: provisioner_name ## 这个名字必须与storegeclass里面的名字一致
value: my-redis-nfs
- name: enable_leader_election ## 设置高可用允许选举,如果replicas参数等于1,可不用
value: "true"
- name: nfs_server
value: 192.168.31.80 #修改为自己的ip(部署nfs的机器ip)
- name: nfs_path
value: /var/nfs/redis #修改为自己的nfs安装目录
volumes:
- name: redis-nfs-client-root
nfs:
server: 192.168.31.80 #修改为自己的ip(部署nfs的机器ip)
path: /var/nfs/redis #修改为自己的nfs安装目录3.4 创建storeclass
storageclass:简称sc,存储类,是k8s平台为存储提供商提供存储接入的一种声明。通过sc和相应的存储插件(csi)为容器应用提供持久存储卷的能力。
cat > redis-storeclass.yaml apiversion: storage.k8s.io/v1 kind: storageclass metadata: name: redis-nfs-storage namespace: redis-cluster provisioner: my-redis-nfs
3.5 执行命令创建nfs sa授权、nfs客户端以及storeclass
kubectl apply -f redis-nfs-client-sa.yaml kubectl apply -f redis-nfs-client.yaml kubectl apply -f redis-storeclass.yaml
3.6 创建pv
这里我们创建6个pv卷,分别对应redis的三主三从。
cat > redis-pv.yaml
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv1
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv1"
---
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv2
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv2"
---
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv3
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv3"
---
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv4
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv4"
---
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv5
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv5"
---
apiversion: v1
kind: persistentvolume
metadata:
name: redis-nfs-pv6
namespace: redis-cluster
spec:
storageclassname: redis-nfs-storage
capacity:
storage: 500m
accessmodes:
- readwritemany
nfs:
server: 192.168.31.80
path: "/var/nfs/redis/pv6"3.7 执行命令创建pv
kubectl apply -f redis-pv.yaml
3.8 pv卷知识普及(解析为什么需要sc、pv)
这里说一下,为什么要创建sc,pv?
因为redis集群,最终需要对应的文件有,redis.conf、nodes.conf、data,由此可见,这些文件每个节点,都得对应有自己得文件夹。
当然redis.conf可以是一个相同得,其他两个,就肯定是不一样得。如果使用挂载文件夹即是 volume 的情况部署一个pod,很明显,是不能满足的。
当然,你部署多个不一样的pod,也是可以做到,但是就得写6个部署yaml文件,后期维护也很复杂。最好的效果是,写一个部署yaml文件,然后有6个replicas副本,就对应了我们redis集群(三主三从)。
那一个pod,再使用volume挂载文件夹,这个只能是一个文件夹,是无法做到6个pod对应不同的文件夹。
所以这里,就引出了sc、pv了。使用sc、pv就可以实现,这6个pod启动,就对应上我们创建的6个pv,那就实现了redis.conf、nodes.conf、data,这三个文件,存放的路径,就是不一样的路径了。
四、搭建redis集群
4.1 普及知识
rc、deployment、daemonset都是面向无状态的服务,它们所管理的pod的ip、名字,启停顺序等都是随机的,而statefulset是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如mysql、mongodb集群等。
statefulset本质上是deployment的一种变体,在v1.9版本中已成为ga版本,它为了解决有状态服务的问题,它所管理的pod拥有固定的pod名称,启停顺序,在statefulset中,pod名字称为网络标识(hostname),还必须要用到共享存储。
在deployment中,与之对应的服务是service,而在statefulset中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有cluster ip,解析它的名称时将返回该headless service对应的全部pod的endpoint列表。
除此之外,statefulset在headless service的基础上又为statefulset控制的每个pod副本创建了一个dns域名,这个域名的格式为:
$(pod.name).$(headless server.name).${namespace}.svc.cluster.local也即是说,对于有状态服务,我们最好使用固定的网络标识(如域名信息)来标记节点,当然这也需要应用程序的支持(如zookeeper就支持在配置文件中写入主机域名)。
statefulset基于headless service(即没有cluster ip的service)为pod实现了稳定的网络标志(包括pod的hostname和dns records),在pod重新调度后也保持不变。同时,结合pv/pvc,statefulset可以实现稳定的持久化存储,就算pod重新调度后,还是能访问到原先的持久化数据。
以下为使用statefulset部署redis的架构,无论是master还是slave,都作为statefulset的一个副本,并且数据通过pv进行持久化,对外暴露为一个service,接受客户端请求。
4.2 创建headless服务
cat > redis-hs.yaml
apiversion: v1
kind: service
metadata:
labels:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis
name: redis-hs
namespace: redis-cluster
spec:
ports:
- name: nnbary
port: 6379
protocol: tcp
targetport: 6379
selector:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis
clusterip: noneheadless service是statefulset实现稳定网络标识的基础。
网络访问:pod名称.headless名称.namespace名称.svc.cluster.local 即:pod名称.redis-hs.redis-cluster.svc.cluster.local。
4.3 创建redis.conf配置
/opt/redis/conf 目录下的redis.conf配置,待会创建configmap需要用到
bind 0.0.0.0 port 6379 daemonize no # requirepass redis-cluster # 集群配置 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000
requirepass redis-cluster 设置的是集群密码,注释掉是因为通过redis-trib实现集群初始化,有密码会初始化不了,暂时不知道该怎么解决,只能先把密码关掉,哈哈哈。
4.4 创建名称为redis-conf的configmap
kubectl create configmap redis-conf --from-file=redis.conf -n redis-cluster
4.5 创建redis 对应pod集群
cat > redis.yaml
apiversion: apps/v1
kind: statefulset
metadata:
name: redis
namespace: redis-cluster
labels:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis
spec:
replicas: 6
selector:
matchlabels:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis
servicename: redis
template:
metadata:
labels:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis
spec:
terminationgraceperiodseconds: 20
containers:
- name: redis
image: redis
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "--protected-mode"
- "no"
ports:
- name: redis
containerport: 6379
protocol: "tcp"
- name: cluster
containerport: 16379
protocol: "tcp"
volumemounts:
- name: "redis-conf"
mountpath: "/etc/redis"
- name: "redis-data"
mountpath: "/data"
volumes:
- name: "redis-conf"
configmap:
name: "redis-conf"
items:
- key: "redis.conf"
path: "redis.conf"
volumeclaimtemplates:
- metadata:
name: redis-data
spec:
accessmodes: [ "readwritemany" ]
resources:
requests:
storage: 200m
storageclassname: redis-nfs-storage4.6 执行命令创建service和redis 集群pod
kubectl apply -f redis-hs.yaml kubectl apply -f redis.yaml
4.7 查看集群状态
我们经过以上步骤之后,redis的6个pod 已经启动起来了,但是我们还没做集群初始化操作,此刻redis还不是主从集群状态,我们可以检查验证下:
1、 进入容器(可以通过k8s页面,选中节点,通过base或sh进来),然后 cd /usr/local/bin/
2、连接上redis节点: redis-cli -c
3、查看集群状态:cluster info

五、集群初始化
redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入statefulset中,则是一件非常复杂而且低效的行为。
我们可以在k8s上创建一个额外的容器,专门用于进行k8s集群内 部某些服务的管理控制。
这里,我们专门启动一个ubuntu的容器,可以在该容器中安装redis-tribe,进而初始化redis集群。
5.1 启动容器
kubectl run -it ubuntu --image=ubuntu --namespace=redis-cluster --restart=never /bin/bash
5.2 替换ubuntu镜像源
cat > /etc/apt/sources.list << eof deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse eof
5.3 安装软件环境
apt-get update apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils
5.4 安装redis-trib
pip install redis-trib==0.5.1
5.5 创建redis集群
redis-trib.py create \ `dig +short redis-0.redis-hs.redis-cluster.svc.cluster.local`:6379 \ `dig +short redis-1.redis-hs.redis-cluster.svc.cluster.local`:6379 \ `dig +short redis-2.redis-hs.redis-cluster.svc.cluster.local`:6379
5.6 为master添加slave
redis-trib.py replicate \ --master-addr `dig +short redis-0.redis-hs.redis-cluster.svc.cluster.local`:6379 \ --slave-addr `dig +short redis-3.redis-hs.redis-cluster.svc.cluster.local`:6379 redis-trib.py replicate \ --master-addr `dig +short redis-1.redis-hs.redis-cluster.svc.cluster.local`:6379 \ --slave-addr `dig +short redis-4.redis-hs.redis-cluster.svc.cluster.local`:6379 redis-trib.py replicate \ --master-addr `dig +short redis-2.redis-hs.redis-cluster.svc.cluster.local`:6379 \ --slave-addr `dig +short redis-5.redis-hs.redis-cluster.svc.cluster.local`:6379
5.7 查看集群状态
cluster info

5.8 查看集群节点情况
cluster nodes

六、测试主从切换
在k8s上搭建完好redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个master的pod来测试集群的主从切换机制。
这里我以我的redis-0节点做演示。
6.1 查看节点的角色
1、 进入容器(可以通过k8s页面,选中节点,通过base或sh进来),然后 cd /usr/local/bin/
2、连接上redis节点: redis-cli -c
3、查看节点角色:role

可以看出,我们的redis-0节点,是master节点,他的slave 节点ip是10.244.1.17,即我们的redis-3节点。

进入redis-3节点,验证,发现确实是这样的:

6.2 删掉redis-0 mster节点,观察情况
首先查看redis-o节点
kubectl get pod redis-0 -n redis-cluster -o wide

接着删除该pod
kubectl delete pod redis-0 -n redis-cluster


再次进入redis-0节点查看它的角色信息

发现redis-0节点变成了slave节点,而他的master节点,变成了10.244.1.17,即redis-3节点。证明了我们的redis 主从集群是可用的。
七、开放外网端口
前面我们创建了用于实现statefulset的headless service,但该service没有cluster ip,因此不能用于外界访问。所以,我们还需要创建一个service,专用于为redis集群提供访问和负载均衡。
7.1 创建用于外部访问的service
cat > redis-access-service.yaml
apiversion: v1
kind: service
metadata:
name: redis-access-service
labels:
app: redis-outip
spec:
ports:
- name: redis-port
protocol: "tcp"
port: 6379
targetport: 6379
selector:
k8s.kuboard.cn/layer: db
k8s.kuboard.cn/name: redis八、redis故障转移疑问解答
大家可能会疑惑,那为什么没有使用稳定的标志,redis pod也能正常进行故障转移呢?这涉及了redis本身的机制。
因为,redis集群中每个节点都有自己的nodeid(保存在自动生成的 nodes.conf中),并且该nodeid不会随着ip的变化和变化,这其实也是一种固定的网络标志。
也就 是说,就算某个redis pod重启了,该pod依然会加载保存的nodeid来维持自己的身份。我们可以在进入redis-0容器,看的nodes.conf文件:

如上,第一列为nodeid,稳定不变;第二列为ip和端口信息,可能会改变。
这里,我们介绍nodeid的两种使用场景: 当某个slave pod断线重连后ip改变,但是master发现其nodeid依旧, 就认为该slave还是之前的 slave。
当某个master pod下线后,集群在其slave中选举重新的master。待旧master上线后,集群发现其 nodeid依旧,会让旧master变成新master的slave。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论