在Kubernetes上部署Redis集群

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Kubernetes上部署Redis集群相关的知识,希望对你有一定的参考价值。

参考技术A Redis的分布式模式有主从模式、哨兵模式和Cluster模式,相对来说Cluster模式的机制更完善,内存利用率更高。因为项目在使用Redis进行存储时碰到了性能瓶颈,所以准备用Cluster模式尝试部署分布式Redis。

ps:Redis分布式的介绍可以参考 https://segmentfault.com/a/1190000022808576

先配置一个ConfigMap,后面会挂载到Redis的实例上,里面有一个脚本和一个redis的配置文件,redis配置文件可以根据需求修改,脚本的作用后面会讲。

这里使用StatefulSet作为工作负载,因为redis集群的节点是有状态的,这个状态会记录在之前配置指定的/data/nodes.conf文件里,节点重启后会根据这个文件的内容恢复节点在集群里的状态,所以需要StatefulSet提供持久化。

加载上面两个配置文件后执行:

这样就创建好了redis集群,redis-server会写入/data/nodes.conf文件,记录集群状态。因为在创建集群的时候是使用固定ip创建的,当节点重启后ip会发生变化,这个文件的信息就不准确了,所以需要在启动时更新/data/nodes.conf。这个就是fix-ip.sh这个脚本的工作,如果文件存在,就更新ip,如果不存在就创建文件。所以k8s的容器配置里有两个点和单机不一样:一个是command里使用"/conf/fix-ip.sh"作为启动项,先更新ip再启动实例;一个是加载pod ip到环境变量POD_IP,方便脚本加载。

创建好redis集群后还需要访问接口

一开始是想用service的域名创建redis集群的,这样节点重启后就不需要更新ip,但是redis不支持使用域名,所以只能绕了一圈又回到固定ip的方法,和容器环境很不协调。

Kubernetes学习总结(16)—— Kubernetes 实战之部署 Redis 集群

一、问题分析

本质上来说在 k8s 上部署一个 redis 集群和部署一个普通应用没有什么太大的区别,但需要注意下面几个问题:

  1. Redis 是一个有状态应用:这是部署 redis 集群时我们最需要注意的问题,当我们把 redis 以 pod 的形式部署在 k8s 中时,每个 pod 里缓存的数据都是不一样的,而且 pod 的 IP 是会随时变化,这时候如果使用普通的 deployment 和 service 来部署 redis-cluster 就会出现很多问题,因此需要改用 StatefulSet + Headless Service 来解决。

  2. 数据持久化:redis 虽然是基于内存的缓存,但还是需要依赖于磁盘进行数据的持久化,以便服务出现问题重启时可以恢复已经缓存的数据。在集群中,我们需要使用共享文件系统 + PV(持久卷)的方式来让整个集群中的所有 pod 都可以共享同一份持久化储存。

二、概念介绍

在开始之前先来详细介绍一下几个概念和原理。

1、Headless Service

简单的说,Headless Service 就是没有指定 Cluster IP 的 Service,相应的,在 k8s 的 dns 映射里,Headless Service 的解析结果不是一个 Cluster IP,而是它所关联的所有 Pod 的IP列表

2、StatefulSet

StatefulSet 是 k8s 中专门用于解决有状态应用部署的一种资源,总的来说可以认为它是 Deployment/RC 的一个变种,它有以下几个特性:

  1. StatefulSet 管理的每个 Pod 都有唯一的文档/网络标识,并且按照数字规律生成,而不是像 Deployment 中那样名称和 IP 都是随机的(比如 StatefulSet 名字为 redis,那么 pod 名就是 redis-0, redis-1 ...)

  2. StatefulSet 中 ReplicaSet 的启停顺序是严格受控的,操作第 N 个 pod 一定要等前 N-1 个执行完才可以

  3. StatefulSet 中的 Pod 采用稳定的持久化储存,并且对应的 PV 不会随着 Pod 的删除而被销毁

另外需要说明的是,StatefulSet 必须要配合 Headless Service 使用,它会在 Headless Service 提供的 DNS 映射上再加一层,最终形成精确到每个 pod 的域名映射,格式如下:

$(podname).$(headless service name)

有了这个映射,就可以在配置集群时使用域名替代 IP,实现有状态应用集群的管理

三、方案

借助 SetatefulSet 和 Headless Service,集群的部署方案设计如下(图片来自参考文章):

配置步骤大概罗列如下:

  1. 配置共享文件系统NFS

  2. 创建PV和PVC

  3. 创建ConfigMap

  4. 创建Headless Service

  5. 创建StatefulSet

  6. 初始化redis集群

四、实际操作

为了简化复杂度,这次先不配置 PV 和 PVC,直接通过普通 Volume 的方式来挂载数据。Kubernetes学习总结(15)—— Kubernetes 实战之部署 Mysql 集群

1、创建 ConfigMap

先创建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

然后 kubectl create configmap redis-conf --from-file=redis.conf 来创建 ConfigMap。

2、创建 HeadlessService

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

3、创建 StatefulSet

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: "registry.cn-qingdao.aliyuncs.com/gold-faas/gold-redis:1.0"
        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"
      - name: "redis-data"
        emptyDir: 

4、初始化 redis 集群

StatefulSet 创建完毕后,可以看到 6 个 pod 已经启动了,但这时候整个 redis 集群还没有初始化,需要使用官方提供的 redis-trib 工具。我们当然可以在任意一个 redis 节点上运行对应的工具来初始化整个集群,但这么做显然有些不太合适,我们希望每个节点的职责尽可能地单一,所以最好单独起一个 pod 来运行整个集群的管理工具。在这里需要先介绍一下 redis-trib,它是官方提供的 redis-cluster 管理工具,可以实现 redis 集群的创建、更新等功能,在早期的 redis 版本中,它是以源码包里 redis-trib.rb 这个 ruby 脚本的方式来运作的(pip 上也可以拉到 python 版本,但我运行失败),现在(我使用的 5.0.3)已经被官方集成进 redis-cli 中。开始初始化集群,首先在 k8s 上创建一个 ubuntu 的 pod,用来作为管理节点:

kubectl run -i --tty redis-cluster-manager --image=ubuntu --restart=Never /bin/bash

进入 pod 内部先安装一些工具,包括 wget,dnsutils,然后下载和安装 redis:

wget http://download.redis.io/releases/redis-5.0.3.tar.gz
tar -xvzf redis-5.0.3.tar.gz
cd redis-5.0.3.tar.gz && make

编译完毕后 redis-cli 会被放置在 src 目录下,把它放进 /usr/local/bin 中方便后续操作。接下来要获取已经创建好的 6 个节点的 host ip,可以通过 nslookup 结合 StatefulSet 的域名规则来查找,举个例子,要查找 redis-app-0 这个 pod 的 ip,运行如下命令:

root@redis-cluster-manager:/# nslookup redis-app-0.redis-service
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	redis-app-0.redis-service.gold.svc.cluster.local
Address: 172.17.0.10

172.17.0.10 就是对应的 ip。这次部署我们使用 0,1,2 作为 Master 节点;3,4,5 作为 Slave 节点,先运行下面的命令来初始化集群的 Master 节点:

redis-cli --cluster create 172.17.0.10:6379 172.17.0.11:6379 172.17.0.12:6379

然后给他们分别附加对应的 Slave 节点,这里的 cluster-master-id 在上一步创建的时候会给出:

redis-cli --cluster add-node 172.17.0.13:6379 172.17.0.10:6379 --cluster-slave --cluster-master-id adf443a4d33c4db2c0d4669d61915ae6faa96b46

redis-cli --cluster add-node 172.17.0.14:6379 172.17.0.11:6379 --cluster-slave --cluster-master-id 6e5adcb56a871a3d78343a38fcdec67be7ae98f8

redis-cli --cluster add-node 172.17.0.16:6379 172.17.0.12:6379 --cluster-slave --cluster-master-id c061e37c5052c22f056fff2a014a9f63c3f47ca0

集群初始化后,随意进入一个节点检查一下集群信息:

至此,集群初始化完毕,我们进入一个节点来试试,注意在集群模式下 redis-cli 必须加上 -c 参数才能够访问其他节点上的数据:

5、创建 Service

现在进入 redis 集群中的任意一个节点都可以直接进行操作了,但是为了能够对集群其他的服务提供访问,还需要建立一个 service 来实现服务发现和负载均衡(注意这里的 service 和我们之前创建的 headless service 不是一个东西)

yaml 文件如下:

apiVersion: v1
kind: Service
metadata:
  name: gold-redis
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    protocol: "TCP"
    port: 6379
    targetPort: 6379
  selector:
    app: redis
    appCluster: redis-cluster

部署完做个测试:

以上是关于在Kubernetes上部署Redis集群的主要内容,如果未能解决你的问题,请参考以下文章

redis cluster模式在kubernetes平台部署方案 的问题

云原生之kubernetes实战在k8s下部署Redis集群

kubernetes 实现redis-statefulset集群

Kubernetes 学习总结(27)—— Kubernetes 安装 Redis 集群的两个方案

Kubernetes 学习总结(27)—— Kubernetes 安装 Redis 集群的两个方案

Docker实战 -- 部署Redis集群与部署微服务项目