Redis:Kubernetes 带我飞!
Posted K8S中文社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis:Kubernetes 带我飞!相关的知识,希望对你有一定的参考价值。
缘起
线上有一个 redis 集群,因为当时 redis 自带的集群还不成熟,而且我们项目上的需求和应用的场景比较简单,redis 集群是通过 twemproxy + redis 进行搭建的。这个集群其实有很多的不足
单节点虽然设置了持久化,但是没有使用主从模式,没有哨兵 sentinel 负责主从切换
twemproxy 没有 HA,存在单点故障问题
集群的伸缩时(添加节点,删除节点),集群中的数据不能自动平衡
如果需求放在现在,可以使用 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 集群,等待其创建完成并验证(图如果看不清,拖到新的标签页里看大图)
后可以进 shell 里看看
查一下 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 连接测试
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
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 带我飞!的主要内容,如果未能解决你的问题,请参考以下文章