podman跨云部署keydb集群替代docker+redis的过程和几个坑

# 升级目的

受阿里云(2023-11-12)故障事件影响,虽然项目一开始都跨云跨地区部署,但是我们的部分业务依旧受到牵连,遭遇重挫。所以此次升级主要是跨云大量零碎小数据储存,更换为网络kv储存(之前是定时合并文件后同步到阿里云oss)。
彻底解决业务对阿里云oss的依赖。阿里云oss和腾讯cos都改为数据备份专用,不再和业务有关联。
本月逐步使用podman 替代docker 使用keydb替代redis。
因为打算上keydb,所以原有redis集群也替换为keydb。 并新建一个集群用于储存海量碎数据。

# 关于podman

podman的好处对我们目前的物联网项目来说主要是可以免root用户部署维护。
在试运行一段时间后,从几台服务器线上使用后没问题到全部服务器都开始替换docker,历时两个月没出现任何问题。

# keydb

而keydb经过一周的反复测试,在实际应用场景下 keydb6.3.x 性能远远超过redis6.2.13和redis7.2.3。因为跨云公网集群,所以tls是必须的,而keydb的tls性能经过测试要比redis7.2.3的qps高出6-10倍。tcp性能高出40%-2倍。
keydb完全兼容redis6.2.x 一周的线上部分节点测试中没发现问题。

# 遇到的几个坑

# 华为云和debian11

腾讯云和阿里云都很顺利,主要是华为云那边。华为云没有提供debian12的镜像,在手动升级到debian12后 容器无法启动。因为时间紧张没有去检查是华为云镜像里面内置的华为组件导致的还是别的的问题。 以下记录 基于debian12 华为云部分基于debian11

# lxc主机下的podman

和docker不太一样,非特权lxc也要解决cgroup2

# su切换用户的丢失XDG_RUNTIME_DIR

1
2
export XDG_RUNTIME_DIR=/run/user/$(id -u)
echo $XDG_RUNTIME_DIR

# keydb集群维护和redis7不一样的地方

keydb 6.2.3 应该是基于redis6.x的,在集群创建和添加节点的,不支持用域名和hostname的方式,需要手动指定ip,否则集群无法通讯。

# 阿里云的域名问题

因为为了隔离实体,这次升级,我们新使用了一个和我个人以及公司不相关的域名(未在阿里云备案)组建redis集群。结果在阿里云那边 tls频繁被重置。 集群也经常卡掉。 反复检查后,并和阿里云技术沟通几次后发现确实是备案的问题。(非web业务域名也要备案)
后更换一个在阿里云备案过的域名 没有再出问题。
我朝这些政策真的是。。。

# ACL

keydb6.3.4 依旧不支持Redis 7.X ACL,部分业务代码需要调整。之前说2023年中支持,好像跳票了。但是目前最新代码中已经有acl相关的内容
unstable 版本未测试

# 其他可选的kv储存

一开始是打算先在部分需要储存的业务上替换目前的s3储存,新方案需要去中心化跨云的集群,加上我们对go-reids sdk针对性的优化过,所以优先考察了几个兼容redis协议的 以及minio集群。
主要是 minio kvrocks tendis redrock Pika

  • minio集群 去中心化,但是扩展需要指数扩展,另外etcd联邦方案过于复杂。我们实际情况是大量小数据段,写入多但是读取也不少,在实际测试后minio集群方式带来的费用可能比直接使用云厂商的对象储存反还要高。
  • tendis 未尝试,因为不支持tls不跨云部署。
  • redrock 个人开发者,作者在知乎,github不活跃,代码已经许久没维护,文档不全。简单测试后性能和redis差不多,低于keydb。
  • kvrocks 花费大量时间测试和权衡后得到的结论:他的集群的管理以及故障转移方式虽然没有gossip开支,需要一个外部节点来自动进行故障转移,另外就是他的热数据是基于rocks的cache来管理在qps比较高的时候的表现一般,在测试过几轮后和redis6/7的差距较大(不知道为什么所有kv数据去vs redis的时候都去和单线程的redis比较…)。
  • pika 看起来不是完全去中心化的,和我们实际需求不相符。
    主要是我们还需要热数据查询速度好一些,虽然另外有一个redis集群,但是本着尽可能独立的原则。剩下也就keydb 和 kvrocks可选了。
    因为线下模拟测试和线上实际情况有出入,而kvrocks和keydb都是兼容redis cluster,我们目前情况是qps较高而节点数量并不多,gossip协议对我们影响不大,所以我们最终决定先逐步在线上用keydb替代redis和kv储存。日后再调研kvrocks

keydb的硬盘存储功能,也就是flash功能,目前还是测试版。但是这是从keydb专业版合并到开源版本的功能,所以简单的 get set 经过一周测试稳定性和性能都没有问题。

# 部署过程

# 系统环境准备

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

wget https://github代理站/https://github.com/rclone/rclone/releases/download/v1.64.2/rclone-v1.64.2-linux-amd64.deb
apt install ./rclone-v1.64.2-linux-amd64.deb
rm ./rclone-v1.64.2-linux-amd64.deb


#apt install docker-compose
apt-get -y install podman crun  slirp4netns fuse-overlayfs
systemctl enable --now podman  # debian11 需要
# 别名 alpine的写到 /etc/profile

cp /etc/containers/registries.conf /etc/containers/registries.conf-bak

cat > /etc/containers/registries.conf<< EOF
unqualified-search-registries =  ['registry.redhat.io', 'docker.io']

[[registry]]
prefix = "docker.io"
location = "docker.io"
[[registry.mirror]]
location = "docker.nju.edu.cn"
[[registry.mirror]]
location = "docker.mirrors.sjtug.sjtu.edu.cn"
[[registry.mirror]]
location = "阿里云容器镜像加速地址.mirror.aliyuncs.com"
EOF




mkdir  -p ~/.config/rclone/
cat > ~/.config/rclone/rclone.conf << EOF
[oss-hz]
type = s3
provider = Alibaba
access_key_id = XXXXX
secret_access_key = XXXX
endpoint = oss-cn-shanghai.aliyuncs.com
acl = private
storage_class = STANDARD
bucket_acl = private
EOF

# 独立用户

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

adduser --shell /bin/bash keydb
loginctl enable-linger keydb # 允许用户常驻

##需要给 检查sudo权限,否则容器的一些目录他没有权限删除,
usermod -aG sudo keydb #部署完成后 移除sudo
sudo -l -U keydb
# 部分云主机(腾讯云)改动过sudoer文件  需要手动添加 sudo用户组
echo "%sudo	ALL=(ALL:ALL) ALL" >  /etc/sudoers.d/sudogrup
# 配置证书
su keydb
mkdir  -p ~/.ssh/ 
cat > ~/.ssh/authorized_keys << \EOF
ssh-rsa AAAXXXXXXXX
EOF
chmod -R 600 ~/.ssh/authorized_keys 

推出ssh,重新用 keydb用户登陆到服务器,后面操作都是基于keydb

# tls证书

用rclone 从oss获取

1
2
rclone  copyto oss-hz:/XXXX/redis_tls  ~/redis_tls
rm -rf ~/.config/rclone/rclone.conf # 用完删掉配置文件

# keydb模板

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
cat > ~/etc/keydb_tpl_all.conf << \EOF
# flash版本
# 主要设置 maxmemory128mb
maxmemory {MaxMem}
# allkeys-lfu allkeys-lru
maxmemory-policy allkeys-lfu
maxmemory-samples 5
# storage-provider flash /path/to/flash/db
storage-provider flash /data/flash
# === 持久化
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir /data
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes

# ======= 密码
requirepass "{password}"
masterauth "{password}"
# ======== tls
tls-port {tlsPort}
tls-cert-file {tlsCert}/redis.crt
tls-key-file {tlsCert}/redis.key
tls-client-cert-file {tlsCert}/client.crt
tls-client-key-file {tlsCert}/client.key
tls-client-key-file-pass {tlsCert}/secret
tls-dh-params-file {tlsCert}/redis.dh
tls-ca-cert-file {tlsCert}/ca.crt
tls-ca-cert-dir {tlsCert}
tls-auth-clients no
tls-auth-clients optional
tls-replication yes
tls-cluster yes
tls-protocols "TLSv1.2 TLSv1.3"
tls-ciphers DEFAULT:!MEDIUM
tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
tls-prefer-server-ciphers yes
tls-session-caching no
tls-session-cache-size 5000
tls-session-cache-timeout 60
#=== 集群
cluster-enabled yes
cluster-config-file "nodes.conf"
cluster-node-timeout 5000
cluster-announce-ip {domian}
cluster-announce-bus-port {PORT_BUS}
# ======= 其他
protected-mode no
bind * -::*  
port {port}
tcp-backlog 511
timeout 0
tcp-keepalive 300
supervised no
pidfile /var/run/keydb.pid
#loglevel notice warning
loglevel notice
databases 16
always-show-logo no
set-proc-title yes
proc-title-template "{title} {listen-addr} {server-mode}"
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes

replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled
repl-disable-tcp-nodelay no
replica-priority 100
acllog-max-len 128
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
lazyfree-lazy-user-flush no
oom-score-adj no
oom-score-adj-values 0 200 800
disable-thp yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

jemalloc-bg-thread yes
server-threads 2
replica-weighting-factor 2
active-client-balancing yes
EOF

# 通过模板创建N个keydb配置文件

下面创建了 5+5 10个,每个实例需要3个端口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38


get_keydb_conf() {
   mkdir -p ~/etc && cd ~/etc
   rm -rf ./keydb$PORT_TCP.conf
  cp ./keydb_tpl_all.conf ./$PORT_TCP.conf # 模板
   sed  -i "s/{port}/$PORT_TCP/g"      ./$PORT_TCP.conf #tcp 端口
   sed  -i "s/{tlsPort}/$PORT_TLS/g"   ./$PORT_TCP.conf #tls 端口
   sed  -i "s/{PORT_BUS}/$PORT_BUS/g"  ./$PORT_TCP.conf #BUS 端口
   sed  -i "s/{domian}/$KEYDB_DOMAIN/g"   ./$PORT_TCP.conf #域名
   sed  -i "s/{tlsCert}/\/redis_tls/g"   ./$PORT_TCP.conf #证书
   sed  -i "s/{password}/$KEYDB_PSW/g"    ./$PORT_TCP.conf #密码
   sed  -i "s/{MaxMem}/$MaxMem/g"    ./$PORT_TCP.conf #最大内存

   if [[ $(uname -m) == arm* || $(uname -m) == aarch* ]]; then
      echo "当前是 ARM 架构 需要添加一行 ARM64-COW-BUG "
      echo "ignore-warnings ARM64-COW-BUG">> ./$PORT_TCP.conf # arm需要这个
   fi
}

KEYDB_PSW=mypasswd
MaxMem="512mb"
KEYDB_DOMAIN="serv-ali-1.leiyanhui.com"
for i in {1..5}; do
  PORT_TCP=$((4000 + i))
  PORT_TLS=$((4100 + i))
  PORT_BUS=$((14100 + i))
  get_keydb_conf
done

MaxMem="1024mb"
for i in {1..5}; do
  PORT_TCP=$((5000 + i))
  PORT_TLS=$((5100 + i))
  PORT_BUS=$((15100 + i))
  get_keydb_conf
done
nano 6001.conf

# 创建容器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
get_podman() {
   podman stop keydb$PORT_TCP&&podman rm keydb$PORT_TCP
   sudo rm -rf ~/storage$PORT_TCP && mkdir ~/storage$PORT_TCP # 先sudo 删除储存目录
   podman run -itd --name keydb$PORT_TCP --hostname keydb$PORT_TCP --network host \
      -v ~/etc:/keydb_etc \
      -v ~/redis_tls:/redis_tls \
      -v ~/storage$PORT_TCP:/data \
      -e TZ=Asia/Shanghai \
      --restart=always  \
      --shm-size 128M \
      -d $DOCKEIMAGE keydb-server /keydb_etc/$PORT_TCP.conf 
  # 处理用户服务
  mkdir -p ~/.config/systemd/user && cd   ~/.config/systemd/user 
  rm -rf ./container-keydb$PORT_TCP.service 
  podman generate systemd --name  keydb$PORT_TCP --files --new
  systemctl --user daemon-reload
  systemctl --user enable container-keydb$PORT_TCP.service

}
export DOCKEIMAGE=docker.io/eqalpha/keydb:alpine_x86_64_v6.3.4

for i in {1..5}; do
  PORT_TCP=$((4000 + i))
  get_podman
done

for i in {1..5}; do
  PORT_TCP=$((5000 + i))
  get_podman
done
ls
podman ps -a

# 开放端口

去各家云主机控制面板 防火墙 开放端口
4101-4105,14101-14105, 5101-5105,15101-15105

# 主集群连接

keydb必须使用ip连接

1
2
3
4
5
6
7
8
9
podman exec -it keydb6001 keydb-cli --tls \
    --cacert /redis_tls/ca.crt \
    -a  密码  \
  --cluster create 18.18.18.18:4101 18.18.18.18:4102 18.18.18.18:4103 18.18.18.18:4104 18.18.18.18:4105

podman exec -it keydb6001 keydb-cli --tls \
    --cacert /redis_tls/ca.crt \
    -a  密码  \
  --cluster create 18.18.18.18:5101 18.18.18.18:5102 18.18.18.18:5103 18.18.18.18:5104 18.18.18.18:5105

# 添加从节点

也必须用ip方式

1
2
3
4
5
podman exec -it keydb6001 keydb-cli --tls \
    --cacert /redis_tls/ca.crt \
    -a  密码 \
    --cluster add-node  新节点:4105 现有任意一个旧节点ip:4101  \
    --cluster-slave --cluster-master-id <本节点的主节点id>

# 手动调整主节点

逐个连接到要提为主节点上执行,这里可以用域名了哈

1
2
3
4
5
podman exec -it keydb6001 keydb-cli --tls \
    --cacert /redis_tls/ca.crt \
    -a  密码 \
    -h 要提为主节点的从节点的ip或者域名 -p 4104 \
    CLUSTER FAILOVER TAKEOVER

# 导入历史数据

方法1 、 https://github.com/joyanhui/redis-back
方法2、 aof 和 rdb文件直接恢复
实际3、 集群未停机,主节点都在线,所以直接接入

# 防止主节点过于集中

某些极端情况下,多数主节点会被集群迁移到其中一台机器上(目前是4台机器,每个集群5个主节点)。如果有3个主节点被集群自动迁移到一台机器上,刚好这台机器宕机。那么整个集群会无法自愈。
需要另外运行一个脚本定时执行CLUSTER NODES检查节点部分情况。
我们是部署在云函数上的golang+shell脚本实现 定时器事件触发,具体实现略

# 防止主从同步带宽占用过大

每个集群是 1主2从 1从和它的主放在同一个云上,修改hosts为内网ip,另外一从跨云。
这样尽可能的降低主从同步的带宽占用。在经济性和高可用性之间找平衡。

# 对redis的一些看法

其实无论是keydb还是dragonfly实际运行环境中,性能都已经远远超过redis,连接数比较多的时候cpu和内存的开支也都比redis小很多。 甚至kvrocks在ramdis或者傲腾下,性能都超过redis 所以如果和我一样是自建的话,尽早切换吧。
redis的设计逻辑有一些固执,或许这也是redis一开始成功的原因。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计