摘要:计划通过解决持久化的问题通过带起个实例,它们将有稳定的主机名线上一个部署单元是个实例通过和注入配置文件和敏感信息因为线上系统的特性,我们底层的实例是不需要顺序启动或停止的,将采用创建集群参考文章,快速创建一个集群。
缘起
线上有一个 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无头服务 headless service443/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 了,通过 服务也能访问。服务随便宕还能自愈,厉害了。
有时不需要或不想要负载均衡,以及多带带的 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
摘要:集群中的每个成员,无论是主副本还是次级副本,都管理哈希槽的一个子集。在由三个主节点组成的最小的集群中,每个主节点都有一个从属节点为了至少能保证最低程度的故障转移,每个主节点分配一个范围在至之间的哈希槽。 showImg(https://segmentfault.com/img/remote/1460000018405753?w=2350&h=1000); 介 绍 Redis(REmo...
摘要:使用导出端口,使用挂载数据卷。清理应用使用一键清理应用总结已经实现了容器扩容自动挡更直观的控制容器启动顺序及依赖。从部署到编排,单字面理解,看起来能够维护的容器量都增长了。推荐应用包括多个服务,推荐部署方式就是。前言 容器化,云原生越演越烈,新概念非常之多。信息爆炸的同时,带来层层迷雾。我尝试从扩容出发理解其脉路,经过实践探索,整理形成一个入门教程,包括下面四篇文章。 容器化实践之路-从d...
摘要:部署环境及架构操作系统版本版本版本服务器信息在详细介绍部署集群前,先给大家展示下集群的逻辑架构。其他操作更新删除查看删除除此之外,你可以删除,如删除上的格式为服务名字,不必关心从哪个上删除了。 本文通过实际操作来演示Kubernetes的使用,因为环境有限,集群部署在本地3个ubuntu上,主要包括如下内容: 部署环境介绍,以及Kubernetes集群逻辑架构 安装部署Open v...
阅读 1612·2021-11-23 09:51
阅读 2631·2021-11-22 09:34
阅读 1292·2021-10-14 09:43
阅读 3643·2021-09-08 09:36
阅读 3183·2019-08-30 12:57
阅读 2006·2019-08-30 12:44
阅读 2499·2019-08-29 17:15
阅读 2955·2019-08-29 16:08