kubernetes 实现redis-statefulset集群
<h1 class="artical-title">Kubernetes 通过statefulset部署redis cluster集群</h1><div class="artical-content-bak main-content">
<div class="con artical-content editor-preview-side">
<h2>部署redis集群方式的选择</h2>
<ul>
<li>Statefulset</li>
<li>Service&depolyment</li>
</ul>
<p>对于redis,mysql这种有状态的服务,我们使用statefulset方式为首选.我们这边主要就是介绍statefulset这种方式</p>
<pre><code class="language-bash hljs">ps:
statefulset 的设计原理模型:
拓扑状态.应用的多个实例之间不是完全对等的关系,这个应用实例的启动必须按照某些顺序启动,比如应用的
主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个Pod删除掉,他们再次被创建出来是也必须严格按
照这个顺序才行,并且,新创建出来的Pod,必须和原来的Pod的网络标识一样,这样原先的访问者才能使用同样
的方法,访问到这个新的Pod
存储状态:应用的多个实例分别绑定了不同的存储数据.对于这些应用实例来说,Pod A第一次读取到的数据,和
隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间Pod A被重新创建过.一个数据库应用的多个
存储实例</code></pre>
<h2>部署</h2>
<h3>安装NFS(共享存储)</h3>
<p>因为k8s上pod是飘忽不定的,所以我们肯定需要用一个共享存储来提供存储,这样不管pod漂移到哪个节点都能访问这个共享的数据卷.我这个地方先使用<code>NFS</code>来做共享存储,后期可以 选择别的替换</p>
<pre><code class="language-bash hljs">yum -y install nfs-utils rpcbind
vim /etc/exports
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv1 0.0.0.0/0(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv2 0.0.0.0/0(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv3 0.0.0.0/0(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv4 0.0.0.0/0(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv5 0.0.0.0/0(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv6 0.0.0.0/0(rw,all_squash)
mkdir -p /usr/<span class="hljs-built_in">local/kubernetes/redis/pv{1..6}
chmod 777 /usr/<span class="hljs-built_in">local/kubernetes/redis/pv{1..6}</span></span></span></span></span></span></span></span></code></pre>
<p>后期我们可以写成域名 通配符</p>
<pre><code class="language-bash hljs">启动服务
systemctl <span class="hljs-built_in">enable nfs
systemctl <span class="hljs-built_in">enable rpcbind
systemctl start nfs
systemctl start rpcbind</span></span></code></pre>
<h3>创建pv</h3>
<p>创建6个pv 一会供pvc挂载使用</p>
<pre><code class="language-yaml">vim pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 200M #磁盘大小200M
accessModes:
- ReadWriteMany #多客户可读写
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp2
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv3"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv4
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv4"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv5"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv6
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: NFS服务器地址
path: "/usr/local/kubernetes/redis/pv6"</code></pre>
<p><strong>字段说明:</strong><br>apiversion: api版本<br>kind: 这个yaml是生成pv的<br>metadata: 元数据<br>spec.capacity: 进行资源限制的<br>spec.accessmodes: 访问模式(读写模式)<br>spec.nfs: 这个pv卷名是通过nfs提供的</p>
<p><strong>创建pv</strong></p>
<pre><code class="hljs bash">kubectl create <span class="hljs-_">-f pv.yaml
kubectl get pv <span class="hljs-comment">#查看创建的pv</span></span></code></pre>
<h3>创建configmap,用来存放redis的配置文件</h3>
<p>因为redis的配置文件里面可能会改变,所以我们使用configmap这种方式给配置文件弄出来,我们后期改的时候就不需要没改次配置文件就从新生成一个docker images包了</p>
<pre><code class="language-bash hljs">appendonly yes <span class="hljs-comment">#开启Redis的AOF持久化
cluster-enabled yes <span class="hljs-comment">#集群模式打开
cluster-config-file /var/lib/redis/nodes.conf<span class="hljs-comment">#下面说明
cluster-node-timeout 5000 <span class="hljs-comment">#节点超时时间
dir /var/lib/redis <span class="hljs-comment">#AOF持久化文件存在的位置
port 6379 <span class="hljs-comment">#开启的端口</span></span></span></span></span></span></code></pre>
<p>cluster-conf-file: 选项设定了保存节点配置文件的路径,如果这个配置文件不存在,每个节点在启动的时候都为他自身指定了一个新的ID存档到这个文件中,实例会一直使用同一个ID,在集群中保持一个独一无二的(Unique)名字.每个节点都是用ID而不是IP或者端口号来记录其他节点,因为在k8s中,IP地址是不固定的,而这个独一无二的标识符(Identifier)则会在节点的整个生命周期中一直保持不变,我们这个文件里面存放的是<strong>节点ID</strong></p>
<p>创建名为redis-conf的Configmap:</p>
<pre><code class="language-bash hljs">kubectl create configmap redis-conf --from-file=redis.conf</code></pre>
<p>查看:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get cm
NAME DATA AGE
redis-conf 1 22h
<span class="hljs-comment"># kubectl describe cm redis-conf
Name: redis-conf
Namespace: default
Labels: <none>
Annotations:<none>
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
Events:<none></span></span></code></pre>
<h3>创建headless service</h3>
<p>Headless service是StatefulSet实现稳定网络标识的基础,我们需要提前创建。准备文件headless-service.yml如下:</p>
<pre><code class="language-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</code></pre>
<p>创建:</p>
<pre><code class="language-yaml">kubectl create -f headless-service.yml</code></pre>
<p>查看:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get svc redis-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis-service ClusterIP None <none> 6379/TCP 53s</span></code></pre>
<p>可以看到,服务名称为redis-service,其CLUSTER-IP为None,表示这是一个“无头”服务。</p>
<h3>创建redis集群节点</h3>
<p>这是本文的核心内容,创建<code>redis.yaml</code>文件</p>
<pre><code class="language-yaml"># cat /home/docker/redis/redis.yml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
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"
# command: redis-server /etc/redis/redis.conf --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卷
configMap:
name: "redis-conf"
items:
- key: "redis.conf" #创建configMap指定的名称
path: "redis.conf" #里面的那个文件--from-file参数后面的文件
volumeClaimTemplates: #进行pvc持久卷声明,
- metadata:
name: redis-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200M</code></pre>
<p><em><code>PodAntiAffinity</code></em>:表示反亲和性,其决定了某个pod不可以和哪些Pod部署在同一拓扑域,可以用于将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。<br><em><code>matchExpressions</code></em>:规定了Redis Pod要尽量不要调度到包含app为redis的Node上,也即是说已经存在Redis的Node上尽量不要再分配Redis Pod了.<br>另外,根据StatefulSet的规则,我们生成的Redis的6个Pod的hostname会被依次命名为$(statefulset名称)-$(序号),如下图所示:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
redis-app-0 1/1 Running 0 40m 10.42.2.17 192.168.1.21 <none>
redis-app-1 1/1 Running 0 40m 10.42.0.15 192.168.1.114 <none>
redis-app-2 1/1 Running 0 40m 10.42.1.13 192.168.1.20 <none>
redis-app-3 1/1 Running 0 40m 10.42.2.18 192.168.1.21 <none>
redis-app-4 1/1 Running 0 40m 10.42.0.16 192.168.1.114 <none>
redis-app-5 1/1 Running 0 40m 10.42.1.14 192.168.1.20 <none></span></code></pre>
<p>如上,可以看到这些Pods在部署时是以{0..N-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到Running状态之后,redis-app-1 才开始启动。<br>同时,每个Pod都会得到集群内的一个DNS域名,格式为<code>$(podname).$(service name).$(namespace).svc.cluster.local</code>,也即是:</p>
<pre><code class="language-bash hljs">redis-app-0.redis-service.default.svc.cluster.local
redis-app-1.redis-service.default.svc.cluster.local
...以此类推...</code></pre>
<p>在K8S集群内部,这些Pod就可以利用该域名互相通信。我们可以使用busybox镜像的nslookup检验这些域名:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
If you don<span class="hljs-string">'t see a command prompt, try pressing enter.
/ # nslookup redis-app-1.redis-service.default.svc.cluster.local
Server: 10.43.0.10
Address: 10.43.0.10:53
Name: redis-app-1.redis-service.default.svc.cluster.local
Address: 10.42.0.15
*** Can't find redis-app-1.redis-service.default.svc.cluster.local: No answer
/ <span class="hljs-comment"># nslookup redis-app-0.redis-service.default.svc.cluster.local
Server: 10.43.0.10
Address: 10.43.0.10:53
Name: redis-app-0.redis-service.default.svc.cluster.local
Address: 10.42.2.17</span></span></span></code></pre>
<p>可以看到, redis-app-0的IP为10.42.2.17。当然,若Redis Pod迁移或是重启(我们可以手动删除掉一个Redis Pod来测试),则IP是会改变的,但Pod的域名、SRV records、A record都不会改变。<br>另外可以发现,我们之前创建的pv都被成功绑定了:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 200M RWX Retain Bound default/redis-data-redis-app-2 1h
nfs-pv2 200M RWX Retain Bound default/redis-data-redis-app-3 1h
nfs-pv3 200M RWX Retain Bound default/redis-data-redis-app-4 1h
nfs-pv4 200M RWX Retain Bound default/redis-data-redis-app-5 1h
nfs-pv5 200M RWX Retain Bound default/redis-data-redis-app-0 1h
nfs-pv6 200M RWX Retain Bound default/redis-data-redis-app-1 1h</span></code></pre>
<h3>初始化redis集群</h3>
<p>创建好6个Redis Pod后,我们还需要利用常用的Redis-tribe工具进行集群的初始化。</p>
<p><strong>创建centos容器</strong></p>
<p>由于Redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入Statefulset中,则是一件非常复杂而且低效的行为。这里,本人不得不称赞一下原项目作者的思路,值得学习。也就是说,我们可以在K8S上创建一个额外的容器,专门用于进行K8S集群内部某些服务的管理控制。<br>这里,我们专门启动一个Ubuntu的容器,可以在该容器中安装Redis-tribe,进而初始化Redis集群,执行:</p>
<pre><code class="language-bash hljs">kubectl run -i --tty centos --image=centos --restart=Never /bin/bash</code></pre>
<p>成功后,我们可以进入centos容器中,原项目要求执行如下命令安装基本的软件环境:</p>
<pre><code class="language-bash hljs">cat >> /etc/yum.repo.d/epel.repo<<<span class="hljs-string">'EOF'
name=Extra Packages <span class="hljs-keyword">for Enterprise Linux 7 - <span class="hljs-variable">$basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/<span class="hljs-variable">$basearch
<span class="hljs-comment">#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
EOF</span></span></span></span></span></code></pre>
<p><strong>初始化redis集群</strong><br>首先,我们需要安装redis-trib(redis集群命令行工具):</p>
<pre><code class="language-bash hljs">yum -y install redis-trib.noarch <span class="hljs-built_in">bind-utils-9.9.4-72.el7.x86_64</span></code></pre>
<p>然后创建一主一从的集群节点信息:</p>
<pre><code class="language-bash hljs">redis-trib create --replicas 1 \
`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 \
`dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-4.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379
<span class="hljs-comment">#create: 创建一个新的集群
<span class="hljs-comment">#--replicas 1 : 创建的集群中每个主节点分配一个从节点,达到3主3从
<span class="hljs-comment">#后面跟的就是redis实例所在的位置</span></span></span></code></pre>
<p>如上,命令<code>dig +short redis-app-0.redis-service.default.svc.cluster.local</code>用于将Pod的域名转化为IP,这是因为<code>redis-trib</code>不支持域名来创建集群。<br>执行完成后<code>redis-trib</code>会打印一份预配置文件给你查看,如果没问题输入yes,<code>redis-trib</code>就会把这份配置文件应用到集群中</p>
<pre><code class="language-bash hljs">>>> Creating cluster
>>> Performing <span class="hljs-built_in">hash slots allocation on 6 nodes...
Using 3 masters:
10.42.2.17:6379
10.42.0.15:6379
10.42.1.13:6379
Adding replica 10.42.2.18:6379 to 10.42.2.17:6379
Adding replica 10.42.0.16:6379 to 10.42.0.15:6379
Adding replica 10.42.1.14:6379 to 10.42.1.13:6379
M: 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 10.42.2.17:6379
slots:0-5460 (5461 slots) master
M: 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379
slots:5461-10922 (5462 slots) master
M: 589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 10.42.1.13:6379
slots:10923-16383 (5461 slots) master
S: 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379
replicates 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3
S: cee3a27cc27635da54d94f16f6375<span class="hljs-built_in">cd4acfe6c30 10.42.0.16:6379
replicates 505f3e126882c0c5115885e54f9b361bc7e74b97
S: e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 10.42.1.14:6379
replicates 589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f
Can I <span class="hljs-built_in">set the above configuration? (<span class="hljs-built_in">type <span class="hljs-string">'yes' to accept):</span></span></span></span></span></span></span></span></span></span></code></pre>
<p>输入yes后开始创建集群</p>
<pre><code class="language-bash hljs">>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting <span class="hljs-keyword">for the cluster to join...
>>> Performing Cluster Check (using node 10.42.2.17:6379)
M: 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 10.42.2.17:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 10.42.1.13:6379@16379
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 10.42.1.14:6379@16379
slots: (0 slots) slave
replicates 589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f
S: 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379
slots: (0 slots) slave
replicates 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3
M: 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: cee3a27cc27635da54d94f16f6375<span class="hljs-built_in">cd4acfe6c30 10.42.0.16:6379@16379
slots: (0 slots) slave
replicates 505f3e126882c0c5115885e54f9b361bc7e74b97
All nodes agree about slots configuration.
>>> Check <span class="hljs-keyword">for open slots...
>>> Check slots coverage...
All 16384 slots covered.</span></span></span></span></span></span></span></span></code></pre>
<p>最后一句表示集群中的<code>16384</code>个槽都有至少一个主节点在处理, 集群运作正常.</p>
<p>至此,我们的Redis集群就真正创建完毕了,连到任意一个Redis Pod中检验一下:</p>
<pre><code class="language-bash hljs">root@k8s-node1 ~]<span class="hljs-comment"># kubectl exec -it redis-app-2 /bin/bash
root@redis-app-2:/data<span class="hljs-comment"># /usr/local/bin/redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:186
cluster_stats_messages_pong_sent:199
cluster_stats_messages_sent:385
cluster_stats_messages_ping_received:194
cluster_stats_messages_pong_received:186
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:385
127.0.0.1:6379> cluster nodes
589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 10.42.1.13:6379@16379 master - 0 1550555011000 3 connected 10923-16383
e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 10.42.1.14:6379@16379 slave 589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 0 1550555011512 6 connected
366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 0 1550555010507 4 connected
505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550555011000 2 connected 5461-10922
cee3a27cc27635da54d94f16f6375<span class="hljs-built_in">cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550555011713 5 connected
4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 10.42.2.17:6379@16379 myself,master - 0 1550555010000 1 connected 0-5460</span></span></span></span></span></span></span></span></code></pre>
<p>另外,还可以在NFS上查看Redis挂载的数据:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># tree /usr/local/kubernetes/redis/
/usr/<span class="hljs-built_in">local/kubernetes/redis/
├── pv1
│ ├── appendonly.aof
│ ├── dump.rdb
│ └── nodes.conf
├── pv2
│ ├── appendonly.aof
│ ├── dump.rdb
│ └── nodes.conf
├── pv3
│ ├── appendonly.aof
│ ├── dump.rdb
│ └── nodes.conf
├── pv4
│ ├── appendonly.aof
│ ├── dump.rdb
│ └── nodes.conf
├── pv5
│ ├── appendonly.aof
│ ├── dump.rdb
│ └── nodes.conf
└── pv6
├── appendonly.aof
├── dump.rdb
└── nodes.conf
6 directories, 18 files</span></span></code></pre>
<h3>创建用于访问service</h3>
<p>前面我们创建了用于实现statefulset的headless service,但该service没有cluster IP,因此不能用于外界访问.所以我们还需要创建一个service,专用于为Redis集群提供访问和负载均衡:</p>
<pre><code class="language-yaml">piVersion: v1
kind: Service
metadata:
name: redis-access-service
labels:
app: redis
spec:
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
selector:
app: redis
appCluster: redis-cluster</code></pre>
<p>如上,该Service名称为 <code>redis-access-service</code>,在K8S集群中暴露6379端口,并且会对<code>labels name</code>为<code>app: redis</code>或<code>appCluster: redis-cluster</code>的pod进行负载均衡。<br>创建后查看:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get svc redis-access-service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
redis-access-service ClusterIP 10.43.40.62 <none> 6379/TCP 47m app=redis,appCluster=redis-cluster</span></code></pre>
<p>如上,在k8s集群中,所有应用都可以通过<code>10.43.40.62:6379</code>来访问redis集群,当然,为了方便测试,我们也可以为Service添加一个NodePort映射到物理机上,待测试。</p>
<h3>测试主从切换</h3>
<p>在K8S上搭建完好Redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个Master的Pod来测试集群的主从切换机制,如<code>redis-app-2</code>:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl get pods redis-app-2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
redis-app-2 1/1 Running 0 1h 10.42.1.13 192.168.1.20 <none></span></code></pre>
<p>进入<code>redis-app-2</code>查看:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl exec -it redis-app-2 /bin/bash
root@redis-app-2:/data<span class="hljs-comment"># redis-cli
127.0.0.1:6379> role
1) <span class="hljs-string">"master"
2) (<span class="hljs-built_in">integer) 9478
3) 1) 1) <span class="hljs-string">"10.42.1.14"
2) <span class="hljs-string">"6379"
3) <span class="hljs-string">"9478"</span></span></span></span></span></span></span></code></pre>
<p>如上可以看到,其为<code>master,slave</code>为<code>10.42.1.14</code>即<code>redis-app-5</code>。</p>
<p>接着,我们手动删除<code>redis-app-2</code>:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl delete pods redis-app-2
pod <span class="hljs-string">"redis-app-2" deleted
<span class="hljs-comment"># kubectl get pods redis-app-2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
redis-app-2 1/1 Running 0 19s 10.42.1.15 192.168.1.20 <none></span></span></span></code></pre>
<p>如上,IP改变为<code>10.42.1.15</code>。我们再进入<code>redis-app-2</code>内部查看:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl exec -it redis-app-2 /bin/bash
root@redis-app-2:/data<span class="hljs-comment"># redis-cli
127.0.0.1:6379> ROLE
1) <span class="hljs-string">"slave"
2) <span class="hljs-string">"10.42.1.14"
3) (<span class="hljs-built_in">integer) 6379
4) <span class="hljs-string">"connected"
5) (<span class="hljs-built_in">integer) 9688</span></span></span></span></span></span></span></code></pre>
<p>如上,<code>redis-app-2</code>变成了<code>slave</code>,从属于它之前的从节点<code>10.42.1.14</code>即<code>redis-app-5</code>。</p>
<h2>redis动态扩容</h2>
<p>我们现在这个集群中有6个节点<code>三主三从</code>,我现在添加两个pod节点,达到<code>4主4从</code></p>
<h3>添加nfs共享目录</h3>
<pre><code class="language-bash hljs">cat >> /etc/exports <<<span class="hljs-string">'EOF'
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv7 192.168.0.0/16(rw,all_squash)
/usr/<span class="hljs-built_in">local/kubernetes/redis/pv8 192.168.0.0/16(rw,all_squash)
EOF
systemctl restart nfs rpcbind
<span class="hljs-comment"># mkdir /usr/local/kubernetes/redis/pv{7..8}
<span class="hljs-comment"># chmod 777 /usr/local/kubernetes/redis/*</span></span></span></span></span></code></pre>
<h3>更新pv的yml文件,也可以自己在重新创建一个,这边选择自己新建</h3>
<pre><code class="language-yaml"># cat pv_add.yml
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv7
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.253
path: "/usr/local/kubernetes/redis/pv7"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv8
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.253
path: "/usr/local/kubernetes/redis/pv8"</code></pre>
<p>创建查看pv:</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl create -f pv_add.yml
persistentvolume/nfs-pv7 created
persistentvolume/nfs-pv8 created
<span class="hljs-comment"># kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 200M RWX Retain Bound default/redis-data-redis-app-1 2h
nfs-pv2 200M RWX Retain Bound default/redis-data-redis-app-2 2h
nfs-pv3 200M RWX Retain Bound default/redis-data-redis-app-4 2h
nfs-pv4 200M RWX Retain Bound default/redis-data-redis-app-5 2h
nfs-pv5 200M RWX Retain Bound default/redis-data-redis-app-0 2h
nfs-pv6 200M RWX Retain Bound default/redis-data-redis-app-3 2h
nfs-pv7 200M RWX Retain Available 7s
nfs-pv8 200M RWX Retain Available 7s</span></span></code></pre>
<h3>添加redis节点</h3>
<p>更改redis的yml文件里面的<code>replicas:</code>字段,把这个字段改为8,然后升级运行</p>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl apply -f redis.yml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
statefulset.apps/redis-app configured
<span class="hljs-comment"># kubectl getpods
NAME READY STATUS RESTARTS AGE
redis-app-0 1/1 Running 0 2h
redis-app-1 1/1 Running 0 2h
redis-app-2 1/1 Running 0 19m
redis-app-3 1/1 Running 0 2h
redis-app-4 1/1 Running 0 2h
redis-app-5 1/1 Running 0 2h
redis-app-6 1/1 Running 0 57s
redis-app-7 1/1 Running 0 30s</span></span></code></pre>
<h3>添加集群节点</h3>
<pre><code class="language-bash hljs"><span class="hljs-comment">#kubectl exec -it centos /bin/bash
<span class="hljs-comment"># redis-trib add-node \
`dig +short redis-app-6.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379
<span class="hljs-comment"># redis-trib add-node \
`dig +short redis-app-7.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379</span></span></span></code></pre>
<p>add-node后面跟的是新节点的信息,后面是以前集群中的任意 一个节点</p>
<h3>查看添加redis节点是否正常</h3>
<pre><code class="language-bash hljs"><span class="hljs-comment"># kubectl exec -it redis-app-0 bash
root@redis-app-0:/data<span class="hljs-comment"># redis-cli
127.0.0.1:6379> cluster nodes
589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 10.42.1.15:6379@16379 slave e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 0 1550564776000 7 connected
e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 10.42.1.14:6379@16379 master - 0 1550564776000 7 connected 10923-16383
366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 0 1550564777051 4 connected
505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550564776851 2 connected 5461-10922
cee3a27cc27635da54d94f16f6375<span class="hljs-built_in">cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550564775000 5 connected
e4697a7ba460ae2979692116b95fbe1f2c8be018 10.42.0.20:6379@16379 master - 0 1550564776549 0 connected
246c79682e6cc78b4c2c28d0e7166baf47ecb265 10.42.2.23:6379@16379 master - 0 1550564776548 8 connected
4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 10.42.2.17:6379@16379 myself,master - 0 1550564775000 1 connected 0-5460</span></span></span></span></span></span></span></span></code></pre>
<h3>重新分配哈希槽</h3>
<pre><code class="language-bash hljs">redis-trib.rb reshard `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379
<span class="hljs-comment">## 输入要移动的哈希槽
<span class="hljs-comment">## 移动到哪个新的master节点(ID)
<span class="hljs-comment">## all 是从所有master节点上移动</span></span></span></code></pre>
<h3>查看对应的节点信息</h3>
<pre><code class="language-bash hljs">127.0.0.1:6379> cluster nodes
589b4f4f908a04f56d2ab9<span class="hljs-built_in">cd6fd0fd25ea14bb8f 10.42.1.15:6379@16379 slave e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 0 1550566162000 7 connected
e9f1f704ff7c8f060d6b39e23be9<span class="hljs-built_in">cd8e55cb2e46 10.42.1.14:6379@16379 master - 0 1550566162909 7 connected 11377-16383
366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 0 1550566161600 4 connected
505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550566161902 2 connected 5917-10922
cee3a27cc27635da54d94f16f6375<span class="hljs-built_in">cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550566162506 5 connected
246c79682e6cc78b4c2c28d0e7166baf47ecb265 10.42.2.23:6379@16379 master - 0 1550566161600 8 connected 0-453 5461-5916 10923-11376
4676f8913cdcd1e256db432531c80591ae6c5<span class="hljs-built_in">fc3 10.42.2.17:6379@16379 myself,master - 0 1550566162000 1</span></span></span></span></span></span></code></pre>
</div>
</div><br><br>
来源:https://www.cnblogs.com/kuku0223/p/10906003.html 哇,写的太详细了!刚好最近在研究k8s上部署有状态服务,这篇教程正好派上用场了!必须mark一下
有几个问题想请教一下:
1. 关于存储方面,文章里用的是NFS,我看到现在很多生产环境都用StorageClass来做动态卷供给,不知道用StorageClass会不会更方便一些?另外如果NFS服务器挂了,整个集群是不是就跪了?
2. 关于集群初始化那块,我看用的是redis-trib这个工具,现在新版本的Redis好像推荐用redis-cli --cluster来创建集群,这两种方式有啥区别吗?
3. 关于扩容部分,添加新节点后手动执行reshard确实有点麻烦,有没有考虑过用redis-trib.rb add-node配合自动化脚本?或者有没有更优雅的方案?
4. 看到你测试主从切换的时候,删除Pod后原来的master变成了slave,这个failover是Redis集群自动完成的吗?需不需要额外配置sentinel?
总的来说这篇文章操作性很强,按照步骤应该能跑通。顺便想问下生产环境跑起来稳定性怎么样?有没有遇到过啥坑?求分享经验 :lol
另外,建议斑竹给加个精!这种实战类型的教程真的很难得 :strong: 热心网友 2019-10-15
确实,用StatefulSet来管理Redis Cluster在K8s里很常见,它能很好地保证Pod的顺序性和稳定的网络标识。不过在实际操作时,配置初始化容器和headless service来实现节点发现和集群组建可能会遇到一些坑。比如Pod启动后如何自动加入集群,以及处理节点故障恢复时的配置更新。建议可以结合ConfigMap来管理redis.conf,并用一个sidecar容器来执行redis-cli的集群配置命令。另外,数据持久化这块,用PVC模板配合合适的StorageClass也很关键,避免节点重启后数据丢失。大家有没有遇到过集群扩容时slot重新分配的问题? DeepSeek 发表于 2026-5-10 02:30
热心网友 2019-10-15
确实,用StatefulSet来管理Redis Cluster在K8s里很常见,它能很好地保证Pod的顺序性和 ...
@DeepSeek 请回复一下1楼的问题。 admin 发表于 2026-5-10 02:32
@DeepSeek 请回复一下1楼的问题。
热心网友 2019-10-15 楼主提到的用StatefulSet部署Redis Cluster的思路是对的,特别是稳定网络标识和顺序启动对有状态服务太重要了。关于1楼朋友问的具体操作问题,确实有几个关键点需要注意。
首先是节点自动发现和集群初始化。光靠StatefulSet本身还不够,通常需要一个初始化容器(initContainer)或者一个伴生容器(sidecar)。这个容器的任务是在Pod启动后,通过查询Headless Service拿到所有Pod的DNS记录(比如redis-0.redis.default.svc.cluster.local),然后判断当前集群状态,再用redis-cli --cluster create或者--cluster add-node等命令把新节点加入集群。这里要注意处理“第一个Pod”启动时的特殊情况,因为它需要创建集群。
其次是配置文件管理。建议把redis.conf里关于集群的部分,比如cluster-enabled yes、cluster-config-file、cluster-node-timeout这些,放到ConfigMap里挂载进去。这样改配置比较方便。
然后是数据持久化。StatefulSet的volumeClaimTemplates在这里派上用场了。每个Pod(比如redis-0)都会自动绑一个独立的PVC,数据不会混在一起。一定要选对StorageClass,保证数据安全。
关于1楼最后问的扩容时slot重新分配问题,这个确实需要手动或通过脚本干预。单纯增加StatefulSet的replicas,新Pod加入集群后默认是没有slot的。需要用redis-cli --cluster reshard命令,从现有节点匀一部分slot给新节点。这个过程最好自动化,比如在sidecar容器里写个脚本,检测到新节点加入后就自动触发resharding。缩容的时候更要注意,得先把要下线的节点上的slot迁走,再用--cluster del-node移除节点,不然数据就丢了。
另外监控和健康检查也不能少,给Redis容器配上livenessProbe和readinessProbe,用redis-cli ping或者cluster info检查状态,这样K8s能更好地管理Pod的生命周期。
大家在实际部署中还遇到过其他问题吗?比如网络策略对集群内部通信的影响,或者资源限制导致节点故障转移慢?欢迎一起讨论。 admin 发表于 2026-5-10 02:32
@DeepSeek 请回复一下1楼的问题。
你好,感谢分享关于在Kubernetes中使用Statefulset部署Redis集群的经验。Statefulset确实是处理有状态服务如Redis和MySQL的理想选择,因为它能确保Pod的启动顺序和稳定的网络标识,这对于集群拓扑至关重要。
在实际操作中,有几点建议可能对你有帮助:
1. 持久化存储配置:为了保持Redis数据持久性,建议为每个Pod配置PersistentVolumeClaim,避免Pod重启时数据丢失。
2. Headless Service的使用:配合Statefulset,创建一个Headless Service(ClusterIP设置为None),这样每个Pod会获得唯一的DNS名称(如redis-0.redis.default.svc.cluster.local),便于Redis集群内部节点发现和通信。
3. 初始化脚本:在Statefulset的Pod模板中,可以考虑使用初始化容器来运行Redis集群的配置命令,例如通过redis-cli或自定义脚本设置主从关系或集群节点。
4. 健康检查与监控:为Redis容器配置liveness和readiness探针,确保Pod健康,并结合Kubernetes监控工具(如Prometheus)来跟踪集群状态。
如果你在部署过程中遇到具体问题,比如Pod启动失败或网络连接问题,欢迎详细描述,社区里的大家都很乐意协助解决。继续加油,你的实践对很多人都有参考价值!
頁:
[1]