资讯专栏INFORMATION COLUMN

redis on kubernetes

singerye / 1844人阅读

摘要:计划通过解决持久化的问题通过带起个实例,它们将有稳定的主机名线上一个部署单元是个实例通过和注入配置文件和敏感信息因为线上系统的特性,我们底层的实例是不需要顺序启动或停止的,将采用创建集群参考文章,快速创建一个集群。

缘起

线上有一个 redis 集群,因为当时 redis 自带的集群还不成熟,而且我们项目上的需求和应用的场景比较简单,redis 集群是通过 twemproxy + redis 进行搭建的。这个集群其实有很多的不足

单节点虽然设置了持久化,但是没有使用主从模式,没有哨兵 sentinel 负责主从切换

twemproxy 没有 HA,存在单点故障问题

集群的伸缩时(添加节点,删除节点),集群中的数据不能自动平衡

如果需求放在现在,可以使用 reids 3.x 以后自带的集群特性,另外也可以选用 codis 这类开源方案。

正好最近在研究和实践 kubernetes,打算尝试将线上的这个集群迁移到 kubernetes,毕竟 kubernetes 能够保证集群的实际状态与用户的期望一致,特别是线上的环境是可能出现主机重启,多个 redis 实例宕掉的情况,利用 kubernetes 就能提高集群的可用性。

初步分析了一下,要迁移线上这个集群,需要使用 statefulset 来实现,因为这里面

每个 redis 实例需要持久化,线上都是持久化到自己主机的某个目录,每个实例和持久化目录是紧密耦合的

twemproxy 的配置文件又和每个 redis 实例的 IP 是紧耦合的,要求 redis 的服务暴露在稳定的地址和端口

于是有了下面的实验。计划

通过 pv/pvc 解决 redis 持久化的问题

通过 statefulset 带起 N 个实例,它们将有稳定的主机名(线上一个部署单元是 108 个 redis 实例)

通过 configmap 和 secret 注入配置文件和敏感信息

因为线上系统的特性,我们底层的 redis 实例是不需要顺序启动或停止的,podManagementPolicy 将采用 Parallel

创建 kubernetes 集群

参考 setting-up-a-kubernetes-cluster-with-vagrant 文章,快速创建一个 kubernetes 集群。实际上,因为我在公司使用 windows 操作系统,实际使用的 Vagrantfile 我做了少量的修改。

创建 pv/pvc

简单起见,本次实验的目的主要是为了验证想法,所以简单地使用基于 nfs 的 PV 和 PVC。首先在 kubernetes 的集群的节点中搭建 nfs 服务。

# 每个节点
yum -y install nfs-server nfs-utils rpcbind

# 选 node1 提供服务
systemctl enable nfs rpcbind
systemctl start nfs rpcbind

# 其他节点开启 
systemctl enable rpcbind
systemctl start rpcbind

# node1 配置 nfs
mkdir /root/data
vi /etc/exports
/root/data    172.17.8.0/24(rw,sync,no_root_squash)

# node1 重启服务,使配置生效
systemctl restart nfs

# node1 检验
showmount -e localhost
/root/data 172.17.8.0/24

# nodex 检验
mount -t nfs 172.17.8.101:/root/data /mnt

然后创建 pv/pvc

# create pv
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 172.17.8.101
    path: "/root/data"
    
# create pvc
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-nfs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
创建 redis 镜像

本来没想自定义 redis 镜像,打算直接使用 hub 上的 redis 镜像,然后用 configmap 注入 redis 的配置 redis.conf,但是只能使用同一个 configmap,这样 pod 中 redis 持久化的位置会是同一个位置,不是期望的。后来想到可以让 redis.conf 中的 dir 和 hostname 关联,利用每个 pod 的 hostname 不同来实现持久化到不同的位置上,按照这个想法做了2个实验

通过 spec 里通过 inti-container 执行个 shell 来修改注入的 redis.conf

通过 sepc.lifecycle 通过 poststart 执行个 shell 来修改注入的 redis.conf

这两个想法都没有实验成功。于是打算还是自定义一个 redis 镜像吧,毕竟这样会通用很多。

参考文章 https://www.kubernetes.org.cn... 以及 文章中提到的 https://github.com/kubernetes... 。很受启发,但是相对我的实验目标都比较复杂,我只需要一个简单的 redis 镜像,于是做了一番改造:具体的内容放在了 https://github.com/arashicage...

这里主要讲一下 run.sh,脚本里通过 statefulset 中 pod 的 hostname 是稳定的特定,将其用在了持久化目录配置里

if [[ ! -e /data/$(hostname) ]]; then
  echo "Redis data dir doesn"t exist, data won"t be persistent!"
  mkdir -p /data/$(hostname)
fi
echo dir /data/$(hostname) >> /usr/local/etc/redis/redis.conf
redis-server /usr/local/etc/redis/redis.conf --protected-mode no
创建 statefulset
---
apiVersion: v1
kind: Service
metadata:
  name: svc-redis
  labels:
    app: redis
spec:
  ports:
  - port: 6379
    name: redis
  clusterIP: None
  selector:
    app: redis
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: stateful-redis
spec:
  podManagementPolicy: Parallel
  serviceName: "redis"
  replicas: 4
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: arashicage/redis-glibc-slim
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: nfs
          mountPath: "/data"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: pvc-nfs

将上面的清单提交到 kubernetes 集群,等待其创建完成并验证(图如果看不清,拖到新的标签页里看大图)

然后可以进 shell 里看看

检查一下 nfs 目录,statefulset 成功创建了各个 pod 使用的持久化目录

[root@node1 ~]# ll /root/data
total 0
drwxr-xr-x. 2 root root 28 Apr 17 14:38 stateful-redis-0
drwxr-xr-x. 2 root root 28 Apr 17 14:38 stateful-redis-1
drwxr-xr-x. 2 root root 28 Apr 17 14:38 stateful-redis-2
drwxr-xr-x. 2 root root 28 Apr 17 14:38 stateful-redis-3
测试 redis pod

查看 stateful-redis-x 的 ip 并用 redis-cli 连接测试

创建 twemproxy 的服务

这一步,因为 twemproxy 是无状态的打算创建一个 deployment 和一个 service,在 hub 上找了一下,拉取数量比较多的都从 twemproxy 都从外部的 etcd 中通过 confd 来获取 twemproxy 的配置,(我第一次听说 confd 是从我青云的一个朋友哪里,他们在 confd 上做了些改造,很不错的软件),想法很不错,但是对于我目前的实验加大了难度,我还是找一个纯粹点的 twemproxy 吧。最后选择了 zapier/twemproxy ,不过也是4年前的了,使用的 twemproxy 是v0.3.0,最目前最新 v0.4.1 支持 Authentication,而且是用在 aws 云上的,影响实验,本想需要改造一下(去掉了 python 相关的,去掉了 memcached 相关的)。后来找到一个 fblgit/twemproxy-nutcracker 比较贴合自己的需求,但是这个镜像也是有问题的(Dockerfile 里的 chmod 755 实际上没起作用,运行的时候报 Permission deny,https://github.com/moby/moby/... 另外这个镜像将 nutcracker 的配置文件和二进制文件都放在了一起 /scripts,我这需要在运行的时候挂载或在 kubernetes 中通过configmap 注入,也修改了配置文件的位置)。修改后是这样的

# ref https://hub.docker.com/r/zapier/twemproxy/~/dockerfile/
# ref https://hub.docker.com/r/jgoodall/twemproxy/~/dockerfile/
# ref https://hub.docker.com/r/fblgit/twemproxy-nutcracker/~/dockerfile/

FROM ubuntu:16.04

MAINTAINER arashicage@yeah.net

ENV DEBIAN_FRONTEND=noninteractive

ENV VERSION=v0.4.1

RUN apt-get update && DEBIAN_FRONTEND=noninteractive && apt-get install -qy gcc autoconf make libtool binutils wget

RUN cd /root && wget https://github.com/twitter/twemproxy/archive/${VERSION}.tar.gz && tar zxf ${VERSION}.tar.gz && cd twemproxy-* && 
    autoreconf -fvi && ./configure --prefix=/usr && make -j4 && make install

ADD start.sh /start.sh
RUN chmod 755 /start.sh

CMD ["/start.sh"]

将文件上传到 github,通过 hub.docker 的自动构建,最后拉取下来进行了测试:

# /root/config/ 包含了 nutcracker.yml 文件,内容见后面
docker run -it --name xxx -d -v /root/config/:/usr/local/etc/nutcracker/ docker.io/arashicage/twemproxy:0.4.1

查找容器的 IP 并检测服务是否可用

# 查找 ip
docker inspect xxx |grep IPAddress
172.33.96.3

# 检测 nutcracker 服务
curl 172.33.96.3:22222
{"service":"nutcracker", "source":"34a2f6582378", "version":"0.4.1", "uptime":61, "timestamp":1524019442, "total_connections":2, "curr_connections":1, "alpha": {"client_eof":0, "client_err":0, "client_connections":1, "server_ejects":0, "forward_error":0, "fragments":0, "server0": {"server_eof":0, "server_err":0, "server_timedout":0, "server_connections":0, "server_ejected_at":0, "requests":0, "request_bytes":0, "responses":0, "response_bytes":0, "in_queue":0, "in_queue_bytes":0, "out_queue":0, "out_queue_bytes":0},"server1": {"server_eof":0, "server_err":0, "server_timedout":0, "server_connections":0, "server_ejected_at":0, "requests":0, "request_bytes":0, "responses":0, "response_bytes":0, "in_queue":0, "in_queue_bytes":0, "out_queue":0, "out_queue_bytes":0},"server2": {"server_eof":0, "server_err":0, "server_timedout":0, "server_connections":0, "server_ejected_at":0, "requests":0, "request_bytes":0, "responses":0, "response_bytes":0, "in_queue":0, "in_queue_bytes":0, "out_queue":0, "out_queue_bytes":0},"server3": {"server_eof":0, "server_err":0, "server_timedout":0, "server_connections":0, "server_ejected_at":0, "requests":0, "request_bytes":0, "responses":0, "response_bytes":0, "in_queue":0, "in_queue_bytes":0, "out_queue":0, "out_queue_bytes":0}}}

上面这个镜像 nutcracker 的配置文件(参考 nutcracker)路径是 /usr/local/etc/nutcracker/nutcracker.yml,通过 configmap 来注入

# nutcracker.yml
alpha:
  listen: 0.0.0.0:22121
  hash: fnv1a_64
  hash_tag: "{}"
  distribution: ketama
  auto_eject_hosts: false
  timeout: 400
  redis: true
  redis_auth: foobar
  servers:
   - stateful-redis-0:6379:1 server0
   - stateful-redis-1:6379:1 server1
   - stateful-redis-2:6379:1 server2
   - stateful-redis-3:6379:1 server3
2018-05-03 补充:上面的 nutcracker.yml 中,应当使用 svc-redis.stateful-redis-0 的形式。

创建 configmap

mv nutcracker.yml /root/config
kubectl create configmap twemproxy-config --from-file=config/

# 结果
key=nutcracker.yml
val=文件内容

然后,创建 deployment

---
kind: Service
apiVersion: v1
metadata:
  name: svc-twemproxy
spec:
  selector:
    app: twemproxy
  ports:
  - name: proxy
    protocol: TCP
    port: 22121
    targetPort: 22121
  - name: state
    protocol: TCP
    port: 22122
    targetPort: 22122
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: twemproxy-deployment
  labels:
    app: twemproxy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: twemproxy
  template:
    metadata:
      labels:
        app: twemproxy
    spec:
      containers:
      - name: twemproxy
        image: arashicage/twemproxy:0.4.1
        ports:
        - containerPort: 22121
          name: proxy
        - containerPort: 22122
          name: state
        volumeMounts:
        - name: config-volume
          mountPath: "/usr/local/etc/nutcracker"
      volumes:
        - name: config-volume
          configMap:
            name: twemproxy-config
            items:
            - key: nutcracker.yml
              path: nutcracker.yml
测试 twemproxy 到 stateful-redis-x

没通啊,根据以往的经验,说明 nutcracker 不能连到后面的 redis 实例(可能 redis 宕掉,可能主机宕掉,但现在情况不是这样),估计是 nutcracker Pod 的不能通过 stateful-redis-x 解析到正确的地址,验证一下(从 dashboard 的exec 进去):

root@twemproxy-deployment-545c7dcbfd-k2h52:/# ping stateful-redis-0
bash: ping: command not found

可惜镜像里缺少 ping,nlslookup 等实用工具。只好通过其他方式了:

# 先拉个 busybox
docker pull busybox
# 再查一下 twemproxy pod 的容器 id(在stateful-redis-0 的节点上查,根据 pod 名称判断,找 pause 的 id)
docker ps -a 
# 找到 df4af96008ed

# 启动 busybox 连入 pause 的网络空间
docker run -it --name busybox --rm --network:container:df4af96008ed busybox

# ping 主机名不同,ping ip 是通的,ping svc-redis 也是通的
/ # ping stateful-redis-0
ping: bad address "stateful-redis-0"
/ # ping 172.33.57.3
PING 172.33.57.3 (172.33.57.3): 56 data bytes
64 bytes from 172.33.57.3: seq=0 ttl=62 time=1.274 ms
/ # ping svc-redis
PING svc-redis (172.33.57.2): 56 data bytes
64 bytes from 172.33.57.2: seq=0 ttl=62 time=0.965 ms

也就是说,这里 nutcracker.yml 里不能直接使用 statefulset 的主机名,因为无法进行域名解析(ping ip 或 svc-redis 能通是因为 dns 的缘故)。要解决这个问题,需要修改 nutcracker.yml 将它改为 ip 地址。虽然statefulset 的 ip 地址是不变的,但是显式的设定感觉还是不够通用,回头通过 confd 来解决吧。

configmap 修改为

# nutcracker.yml
alpha:
  listen: 0.0.0.0:22121
  hash: fnv1a_64
  hash_tag: "{}"
  distribution: ketama
  auto_eject_hosts: false
  timeout: 400
  redis: true
  redis_auth: foobar
  servers:
   - 172.33.57.3:6379:1 server0
   - 172.33.57.2:6379:1 server1
   - 172.33.96.2:6379:1 server2
   - 172.33.92.4:6379:1 server3

重建 configmap,deployment,svc,检验

[root@node1 ~]# kubectl get pods -o wide
NAME                                    READY     STATUS    RESTARTS   AGE       IP            NODE
stateful-redis-0                        1/1       Running   0          20h       172.33.57.3   node2
stateful-redis-1                        1/1       Running   0          22h       172.33.57.2   node2
stateful-redis-2                        1/1       Running   0          22h       172.33.96.2   node1
stateful-redis-3                        1/1       Running   0          22h       172.33.92.4   node3
twemproxy-deployment-545c7dcbfd-5k2xh   1/1       Running   0          35s       172.33.92.3   node3
twemproxy-deployment-545c7dcbfd-r7d6h   1/1       Running   0          35s       172.33.96.4   node1
[root@node1 ~]# 
[root@node1 ~]# 
[root@node1 ~]# 
[root@node1 ~]# redis-cli -h 172.33.92.3 -p 22121 -a foobar
172.33.92.3:22121> set a b
OK
172.33.92.3:22121> exit
[root@node1 ~]# redis-cli -h 172.33.96.4 -p 22121 -a foobar
172.33.96.4:22121> get a
"b"
172.33.96.4:22121> 

[root@node1 ~]# kubectl get svc -o wide
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)               AGE       SELECTOR
kubernetes      ClusterIP   10.254.0.1             443/TCP               1d        
svc-redis       ClusterIP   None                   6379/TCP              23h       app=redis
svc-twemproxy   ClusterIP   10.254.68.39           22121/TCP,22122/TCP   4m        app=twemproxy
[root@node1 ~]# redis-cli -h 10.254.68.39 -p 22121 -a foobar
10.254.68.39:22121> get a
"b"
10.254.68.39:22121> 


# twemproxy 也自带 HA 了,通过 服务也能访问。服务随便宕还能自愈,厉害了。
无头服务 headless service
有时不需要或不想要负载均衡,以及多带带的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。

这个选项允许开发人员自由地寻找他们想要的方式,从而降低与 Kubernetes 系统的耦合性。 应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个 API 来构建。

对这类 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。

有 selector 创建 Endpoints; 无 selector 不会 Endpoints 对象。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/32663.html

相关文章

  • 详细教程丨如何在Kubernetes上部署Redis集群

    摘要:集群中的每个成员,无论是主副本还是次级副本,都管理哈希槽的一个子集。在由三个主节点组成的最小的集群中,每个主节点都有一个从属节点为了至少能保证最低程度的故障转移,每个主节点分配一个范围在至之间的哈希槽。 showImg(https://segmentfault.com/img/remote/1460000018405753?w=2350&h=1000); 介 绍 Redis(REmo...

    laoLiueizo 评论0 收藏0
  • 从docker到istio之二 - 使用compose部署应用

    摘要:使用导出端口,使用挂载数据卷。清理应用使用一键清理应用总结已经实现了容器扩容自动挡更直观的控制容器启动顺序及依赖。从部署到编排,单字面理解,看起来能够维护的容器量都增长了。推荐应用包括多个服务,推荐部署方式就是。前言 容器化,云原生越演越烈,新概念非常之多。信息爆炸的同时,带来层层迷雾。我尝试从扩容出发理解其脉路,经过实践探索,整理形成一个入门教程,包括下面四篇文章。 容器化实践之路-从d...

    yy13818512006 评论0 收藏0
  • 在ubuntu上部署Kubernetes管理docker集群示例

    摘要:部署环境及架构操作系统版本版本版本服务器信息在详细介绍部署集群前,先给大家展示下集群的逻辑架构。其他操作更新删除查看删除除此之外,你可以删除,如删除上的格式为服务名字,不必关心从哪个上删除了。 本文通过实际操作来演示Kubernetes的使用,因为环境有限,集群部署在本地3个ubuntu上,主要包括如下内容: 部署环境介绍,以及Kubernetes集群逻辑架构 安装部署Open v...

    BingqiChen 评论0 收藏0

发表评论

0条评论

singerye

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<