Redis:Kubernetes 带我飞!

Posted K8S中文社区

tags:

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

缘起

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

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

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

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

Redis:Kubernetes 带我飞!

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

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

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

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

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

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

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

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

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

创建 kubernetes 集群

参考 setting-up-a-kubernetes-cluster-with-vagrant (https://jimmysong.io/posts/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 rpcbindsystemctl start nfs rpcbind# 其他节点开启 systemctl enable rpcbindsystemctl start rpcbind# node1 配置 nfsmkdir /root/datavi /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 pvapiVersion: v1kind: PersistentVolumemetadata:  name: pv-nfsspec:  capacity:    storage: 2Gi  accessModes:    - ReadWriteMany  nfs:    server: 172.17.8.101    path: "/root/data"
    # create pvckind: PersistentVolumeClaimapiVersion: v1metadata:  name: pvc-nfsspec:  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/2516.html 以及 文章中提到的 https://github.com/kubernetes/kubernetes/tree/master/examples/storage/redis/image。很受启发,但是相对我的实验目标都比较复杂,我只需要一个简单的 redis 镜像,于是做了一番改造:具体的内容放在了 https://github.com/arashicage/docker-image-river/tree/master/redis-glibc-slim

这里主要讲一下 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)fiecho dir /data/$(hostname) >> /usr/local/etc/redis/redis.confredis-server /usr/local/etc/redis/redis.conf --protected-mode no

创建 statefulset

---apiVersion: v1kind: Servicemetadata:  name: svc-redis  labels:    app: redisspec:  ports:  - port: 6379    name: redis  clusterIP: None  selector:    app: redis---apiVersion: apps/v1kind: StatefulSetmetadata:  name: stateful-redisspec:  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 集群,等待其创建完成并验证(图如果看不清,拖到新的标签页里看大图)

Redis:Kubernetes 带我飞!

后可以进 shell 里看看

Redis:Kubernetes 带我飞!

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

[root@node1 ~]# ll /root/datatotal 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 连接测试

Redis:Kubernetes 带我飞!

 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/issues/12779 另外这个镜像将 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.04MAINTAINER arashicage@yeah.netENV DEBIAN_FRONTEND=noninteractiveENV VERSION=v0.4.1RUN apt-get update && DEBIAN_FRONTEND=noninteractive && apt-get install -qy gcc autoconf make libtool binutils wgetRUN 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 installADD start.sh /start.shRUN chmod 755 /start.shCMD ["/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 并检测服务是否可用

# 查找 ipdocker inspect xxx |grep IPAddress172.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.ymlalpha:  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/configkubectl create configmap twemproxy-config --from-file=config/# 结果
key=nutcracker.yml
val=文件内容

然后,创建 deployment

---kind: ServiceapiVersion: v1metadata:  name: svc-twemproxyspec:  selector:    app: twemproxy  ports:  - name: proxy    protocol: TCP    port: 22121    targetPort: 22121  - name: state    protocol: TCP    port: 22122    targetPort: 22122---apiVersion: apps/v1kind: Deploymentmetadata:  name: twemproxy-deployment  labels:    app: twemproxyspec:  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

Redis:Kubernetes 带我飞!

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

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

# 先拉个 busyboxdocker 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-0ping: bad address 'stateful-redis-0'/ # ping 172.33.57.3PING 172.33.57.3 (172.33.57.3): 56 data bytes64 bytes from 172.33.57.3: seq=0 ttl=62 time=1.274 ms
/ # ping svc-redisPING svc-redis (172.33.57.2): 56 data bytes64 bytes from 172.33.57.2: seq=0 ttl=62 time=0.965 ms

configmap 修改为

# nutcracker.ymlalpha:  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 foobar172.33.92.3:22121> set a b
OK172.33.92.3:22121> exit
[root@node1 ~]# redis-cli -h 172.33.96.4 -p 22121 -a foobar172.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     <none>        443/TCP               1d        <none>
svc-redis       ClusterIP   None           <none>        6379/TCP              23h       app=redis
svc-twemproxy   ClusterIP   10.254.68.39   <none>        22121/TCP,22122/TCP   4m        app=twemproxy
[root@node1 ~]# redis-cli -h 10.254.68.39 -p 22121 -a foobar10.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://segmentfault.com/a/1190000014453291


推荐阅读



以上是关于Redis:Kubernetes 带我飞!的主要内容,如果未能解决你的问题,请参考以下文章

kuangbin带我飞QAQ 线段树

kuangbin带我飞QAQ DLX之一脸懵逼

python中安装matplotlib出现问题,我是个菜鸟,求大神带我飞

Python脚本得王者金币,王者大佬快来带我飞

2017个人训练记录

个人训练记录1