0%

kubernetes部署Redis集群

前言

在之前的博客kubernetes集群部署中,我们搭建了一个kubernetes集群;而在博客‘kubernetes部署Nginx集群’中,我们使用Deployment部署了一个无状态的集群;在本文中我们在这个kubernetes集群中使用StatefulSet部署一个有状态的Redis集群。

部署

Redis官方文件推荐的集群为3主3从6节点,我们现在的kubernetes集群中有1主3从4个节点,在资源允许的条件下可以再往kubernetes集群中添加两个worker节点,如果没有资源可以部署3主0从3节点的集群(这里多说一句,经过实际部署,kubernetes是不会在master节点中运行pod的,也就是说要部署3主3从6节点的redis集群,要6个worker节点);同时我们还需要一个虚拟机部署NFS文件服务器,这里简单介绍一下NFS:NFS(Network File System,网络文件系统)是一种分布式文件系统协议,允许客户端通过网络访问远程服务器上的文件,就像访问本地文件一样。我们用NFS来存储redis的数据,这样可以防止redis节点损坏时丢失数据。

部署NFS服务,创建数据共享文件夹

新建一个虚拟机节点,参考博客‘虚拟机的初始准备

(1)资源为:2core、4G内存、50G硬盘,虚拟机安装的镜像为:CentOS-7-x86_64-Minimal-2009.iso;

(2)设置静态ip,我设置的ip为‘192.168.218.138’

(3)安装yum,设置yum源

(4)安装安装 NFS 服务

1
2
# 安装 NFS 服务器软件包
sudo yum install nfs-utils -y

(5)创建数据共享文件夹

1
2
# 创建‘/data/nfs/redis/pv1’到‘/data/nfs/redis/pv6’ 共6个文件件
mkdir -p /data/nfs/redis/pv{1..6}

(6)设置共享文件夹路径,编辑文件‘/etc/exports’,添加如下内容:

1
2
3
4
5
6
/data/nfs/redis/pv1     *(rw,no_root_squash,sync,insecure)
/data/nfs/redis/pv2 *(rw,no_root_squash,sync,insecure)
/data/nfs/redis/pv3 *(rw,no_root_squash,sync,insecure)
/data/nfs/redis/pv4 *(rw,no_root_squash,sync,insecure)
/data/nfs/redis/pv5 *(rw,no_root_squash,sync,insecure)
/data/nfs/redis/pv6 *(rw,no_root_squash,sync,insecure)
  • ‘/data/nfs/redis/pv1’:共享目录
  • *’:允许访问的客户端 IP 范围(192.168.1.0/24),*代表全部ip都可以访问
  • ‘rw’:客户端拥有读和写的权限
  • ‘no_root_squash’:允许客户端以 root 用户身份访问
  • ‘sync’:同步写入
  • ‘insecure’:表示允许客户端使用非特权端口(大于 1024 的端口)进行访问

(7)启动服务

1
2
3
4
5
6
7
8
# 启动nfs-server服务
sudo systemctl start nfs-server
# 设置nfs-server服务开机自启
sudo systemctl enable nfs-server
# 启动rpcbind服务(提供rpc协议)
systemctl start rpcbind
# 设置rpcbind服务开机自启
systemctl enable rpcbind

(8)查看设置是否生效

1
exportfs -v

(9)在客户端(即kubernetes节点)安装nfs-utils

1
yum -y install nfs-utils

(10)在客户端查看共享文件夹是否生效

1
showmount -e <nfs-ip>

创建 Namespace

我们依旧为这个集群创建一个单独的命名空间

1
kubectl create namespace redis-namespace

查看创建的命名空间

1
kubectl get namespace

创建 PV

我们前面已经部署了NFS文件服务器并且创建了6个共享文件夹,现在我们使用这6个共享文件夹,创建6个PV(PersistentVolume);在适当目录创建文件redis-pv.yaml,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv1"

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv2"

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv3"

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv4
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv4"

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv5"

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv6
namespace: redis-namespace
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 192.168.218.138
path: "/data/nfs/redis/pv6"
1
2
3
4
# 应该该文件创建PV
kubectl apply -f redis-pv.yaml
# 查看PV是否已经成功创建
kubectl get pv

创建 ConfigMap

我们之前部署单体redis,redis的配置文件为redis.conf,而现在的ConfigMap就相当于redis在kubernetes中的配置文件;我们合适的目录创建redis-config.yaml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-conf
namespace: redis-namespace
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

这里的|符号表示后面的内容作为一个完整的字符串,这样我们就不需要设置每一个配置用到的key了,在部署redis实例的时候直接使用该配置即可。

1
2
3
4
5
6
# 应用该yaml文件创建ConfigMap
kubectl apply -f redis-config.yaml
# 查看命名空间中的ConfigMap
kubectl get configmap -n redis-namespace
# 查看configmap ‘redis-conf’的详细信息
kubectl describe configmap redis-conf -n redis-namespace

创建 HeadlessService

  • 普通 Service:提供稳定的访问入口和负载均衡,适用于无状态应用。
  • Headless Service:不提供 ClusterIP 和负载均衡,直接返回后端 Pod 的 IP 地址,适用于有状态应用和需要直接访问 Pod 的场景。

因为Redis集群是有状态集群,我们采用无头HeaderLessService方式,不需要有具体的ClusterIp;在合适的目录创建redis-service.yaml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: redis-namespace
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis
appCluster: redis-cluster
1
2
3
4
# 应用该yaml文件创建service
kubectl apply -f redis-service.yaml
# 查看创建的service
kubectl get service -n redis-namespace

使用StatefulSet创建Redis集群节点

创建文件‘redis.yaml’,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-app
namespace: redis-namespace
spec:
serviceName: "redis-service"
replicas: 6
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
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
command:
- "redis-server" #redis启动命令
args:
- "/etc/redis/redis.conf" #redis-server后面跟的参数,换行代表空格
- "--protected-mode" #允许外网访问
- "no"
#resources: #资源
#requests: #请求的资源
#cpu: "100m" #m代表千分之,相当于0.1 个cpu资源
#memory: "100Mi" #内存100m大小
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf" #挂载configmap生成的文件
mountPath: "/etc/redis" #挂载到哪个路径下
- name: "redis-data" #挂载持久卷的路径
mountPath: "/var/lib/redis"
volumes:
- name: "redis-conf"
configMap:
name: "redis-conf"
items:
# configMap中data项指定的名称
- key: "redis.conf"
path: "redis.conf"
volumeClaimTemplates: #进行pvc持久卷声明
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
1
2
3
4
5
6
# 使用命令应用这个配置文件
kubectl apply -f redis.yaml
# 查看集群情况
kubectl get statefulset -n redis-namespace -o wide
# 查看pod情况
kubectl get pods -n redis-namespace -o wide

image-20250315110801132

StatefulSet部署成功

集群编排

测试Pod间的DNS通信

可以看到我们已经成功创建了StatefulSet集群,集群中一共有6个Pod,但是这6个Pod还没有进行编排,也就是说谁是主谁是从还不知道,也不能一起协作;接下来我们要进行编排工作,但是首先,我们要确定这些redis实例之间是否能通信;相信大家注意到了每个pod都有自己的IP(这个IP是kubernetes分配的),那么有的小伙伴就会想,直接用这个IP通信不就行了吗,但是这个IP是动态分配的,也就是说每当Pod重启,kubernetes就会收回IP,下次重新分配到的就不一定是这个IP了;那怎么办呢,不用慌,由于我们采用了HeadlessService,所以各个Pod之间采用DNS通信,每个Pod都具有了DNS格式的IP,具体为:

1
<Pod名称>.<Service名称>.<namespace名称>.svc.cluster.lcoal

我们可以随意进入一个Pod中的redis容器(没错,还是docker容器)

1
kubectl exec -it redis-app-0 -n redis-namespace -- /bin/sh

在其中用DNS格式IP尝试连接其他节点的redis

1
redis-cli -h redis-app-4.redis-service.redis-namespace.svc.cluster.local

image-20250315120934415

成功使用 DNS格式的IP访问其他节点的redis实例
使用redis-trib初始化redis集群

在确定了能Pod间能用DNS格式IP进行通信后,我们就可以进行下一步:集群编排,redis中为我们提供了redis-trib来进行这个工作

(1)创建一个ubuntu临时容器
1
kubectl run -it temp-ubuntu --image=ubuntu:18.04 --restart=Never -- /bin/bash
(2)修改镜像源

接下来我们要修改镜像源,要不然无法安装必要的软件(并不是非要修改,根据你的网络环境选择);使用命令‘cat /etc/os-release’查看你安装的临时容器的版本

image-20250316165356789

查看ubuntu的版本

​ 在阿里云中找到对应版本的内容,将其写入文件‘/etc/apt/sources.list’,这是ubuntu 18.04 LTS 版本的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat > /etc/apt/sources.list << EOF
deb https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse

# deb https://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
# deb-src https://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse

deb https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
EOF
(3)安装必要的软件
1
2
3
apt-get update
# 可以看到这里安装了python环境、dns工具等
apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils
(4)利用python-pip安装redis-trib
1
pip install redis-trib==0.5.1
(5)利用redis-trib创建3个master节点
1
2
3
4
redis-trib.py create \
`dig +short redis-app-0.redis-service.redis-namespace.svc.cluster.local`:6379 \
`dig +short redis-app-1.redis-service.redis-namespace.svc.cluster.local`:6379 \
`dig +short redis-app-2.redis-service.redis-namespace.svc.cluster.local`:6379
(6)为每个master节点配置一个slave节点
1
2
3
4
5
6
7
8
9
10
11
redis-trib.py replicate \
--master-addr `dig +short redis-app-0.redis-service.redis-namespace.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-3.redis-service.redis-namespace.svc.cluster.local`:6379

redis-trib.py replicate \
--master-addr `dig +short redis-app-1.redis-service.redis-namespace.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-4.redis-service.redis-namespace.svc.cluster.local`:6379

redis-trib.py replicate \
--master-addr `dig +short redis-app-2.redis-service.redis-namespace.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-5.redis-service.redis-namespace.svc.cluster.local`:6379
(7)验证

​ 至此,我们的redis集群就初始化完成了,我们可以退出临时ubuntu容器,然后进入其中的一个pod,登录redis-cli,用命令‘cluster nodes’和‘cluster info’查看一下集群信息

1
2
3
4
5
6
7
# 进入2号节点
kubectl exec -it redis-app-2 -n redis-namespace -- /bin/sh
# 登录redis-cli
/usr/local/bin/redis-cli -c
# 查看集群节点和信息
cluster nodes
cluster info

image-20250316171502654

集群完成初始化
  • 注:有人会问,为什么要创建ubuntu临时容器呢,我们直接进入kubernetes集群中建好的redis节点,在redis-cli中使用redis-tools、dnsutils不好吗?我实践后发现容器中有权限问题,你不能安装dnsutils,这大概也是redis官方提供redis-trib的原因吧

创建用于外界访问Service

前面我们创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster Ip,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡;

创建配置文件‘redis-access-service.yaml’,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Service
metadata:
name: redis-access-service
namespace: redis-namespace
labels:
app: redis
spec:
type: NodePort
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
nodePort: 30010
selector:
app: redis
appCluster: redis-cluster

应用该文件,创建Service

1
kubectl apply -f redis-access-service.yaml

查看创建的Service

1
kubectl get service -n redis-namespace -o wide

image-20250316180420394

Service创建成功

此时我们已经可以使用redis集群中的任一节点的IP(并不是kubernetes集群节点的IP,master节点就不在redis集群中)和Service中暴露的端口30010访问这个redis集群

image-20250316181402353

测试连接成功