简介:这是一份面向Linux系统运维工程师与Docker初学者的Redis高可用集群自动化部署工具包,专为CentOS 7.x环境设计,解决传统Redis集群手动搭建步骤繁琐、配置易错、环境依赖复杂等痛点。资源共6个文件,包含3个核心Shell脚本(含自动构建、卸载及参数化部署功能)、1个预拉取的Redis Docker镜像tar包、1份定制化redis.conf配置模板和1份详尽的README说明文档,整体压缩包仅22.46MB,轻量实用。已有1055人学习下载,实测在标准CentOS 7.x服务器上一键执行即可完成6节点Redis Cluster部署,涵盖网络配置、容器编排、主从分片及健康检查全流程。用户可直接复用脚本快速搭建生产级测试环境,亦可基于源码理解Docker化Redis集群的启动逻辑、端口映射策略与配置注入机制,是深入掌握容器化中间件部署实践的优质参考范例。
1. 为什么 CentOS 7.x 上用 Shell 脚本一键部署 Redis 集群,比docker-compose up更稳、更可控?
你有没有遇到过:在 CentOS 7.x 服务器上跑docker-compose -f redis-cluster.yml up -d,结果集群起不来——节点互相 ping 不通、CLUSTER NODES返回空、redis-cli --cluster create卡在 handshake?不是镜像问题,不是端口冲突,而是 Docker 网络模式在 CentOS 7.x 的 kernel + firewalld + selinux 组合下,对host.docker.internal解析失效、容器间--link已弃用、bridge 网络 DNS 分辨率不稳定——尤其当docker0网桥被手动修改过或与宿主机网段重叠时。这时候,硬编码 IP + 显式暴露端口 + 宿主机网络命名空间复用的 Shell 脚本方案,反而成了生产环境最可靠的兜底手段。它不依赖 Docker Desktop(根本不用装)、不依赖docker-compose版本兼容性、不依赖--network host下的权限妥协,而是用docker run原生命令逐个拉起 6 个 Redis 实例(3 主 3 从),通过--add-host强制注入静态解析、用--net=host复用宿主机网络栈规避 bridge 层转发延迟,并在启动后用redis-cli --cluster create手动触发拓扑构建。适合运维同学批量交付、测试同学快速复现、以及那些被virtualization support not detected docker desktop failed to start because v错误劝退后回归原生 Docker CLI 的真实场景。
2. 从零写一个可复用的 Redis 集群部署脚本:核心逻辑拆解与最小可行命令
2.1 为什么必须绕开 docker-compose?CentOS 7.x 的三个硬约束
在 CentOS 7.x(内核 3.10.x)上,docker-compose的默认行为存在三处隐性风险:
- DNS 解析不可靠:
docker-compose创建的自定义 bridge 网络中,容器默认使用127.0.0.11作为 DNS,但该地址在 CentOS 7.x 的systemd-resolved未启用时可能无响应,导致redis-cli --cluster create连接其他节点超时; - 端口映射与集群通信冲突:Redis 集群要求节点间既走客户端端口(6379),也走集群总线端口(6379+10000=16379)。
docker-compose默认只映射 6379,若未显式声明16379端口,节点握手失败; --network host模式被 compose 限制:docker-compose不支持为单个服务启用host网络(network_mode: host会禁用ports和links),而 Redis 集群在宿主机网络下能彻底规避 NAT 和 iptables 规则干扰。
因此,我们放弃docker-compose.yml,改用纯docker run命令链——每个 Redis 实例独立启动,IP 固定为127.0.0.1(因--net=host),端口显式绑定,集群总线端口强制暴露。
2.2 脚本骨架:6 个实例的端口规划与启动顺序
Redis 集群最小规模是 3 主 3 从(避免脑裂),我们按如下端口分配(全部绑定到127.0.0.1,避免外网暴露):
| 角色 | 实例编号 | 客户端端口 | 集群总线端口 | 数据目录 |
|---|---|---|---|---|
| 主节点 | 1 | 6371 | 16371 | /data/redis-node1 |
| 主节点 | 2 | 6372 | 16372 | /data/redis-node2 |
| 主节点 | 3 | 6373 | 16373 | /data/redis-node3 |
| 从节点 | 4 | 6374 | 16374 | /data/redis-node4 |
| 从节点 | 5 | 6375 | 16375 | /data/redis-node5 |
| 从节点 | 6 | 6376 | 16376 | /data/redis-node6 |
注意:端口避开系统保留端口(<1024)和常用服务(如 6379 被默认 Redis 占用),且
集群总线端口 = 客户端端口 + 10000是 Redis 官方硬编码规则,不可更改。
脚本启动顺序必须严格:先全部启动 6 个实例(不配置集群关系),再统一执行redis-cli --cluster create。因为 Redis 集群初始化要求所有节点处于no cluster状态且可互相访问。
2.3 启动单个 Redis 实例的最小 docker run 命令
以下命令启动一个监听127.0.0.1:6371的 Redis 主节点,数据落盘到/data/redis-node1,并暴露集群总线端口16371:
docker run -d \ --name redis-node1 \ --net=host \ --restart=always \ -v /data/redis-node1:/data \ -e "REDIS_PORT=6371" \ -e "CLUSTER_PORT=16371" \ -e "REDIS_CONF=/usr/local/etc/redis/redis.conf" \ -d redis:7.2-alpine \ redis-server /usr/local/etc/redis/redis.conf \ --port 6371 \ --cluster-enabled yes \ --cluster-config-file nodes-6371.conf \ --cluster-node-timeout 5000 \ --appendonly yes \ --appendfilename "appendonly.aof" \ --pidfile "/var/run/redis_6371.pid" \ --logfile "/var/log/redis_6371.log" \ --dir "/data" \ --bind 127.0.0.1 \ --protected-mode no \ --daemonize no关键参数说明:
--net=host:复用宿主机网络命名空间,所有容器直接使用127.0.0.1,无需 DNS 解析;-v /data/redis-node1:/data:将宿主机目录挂载为容器内/data,AOF 和 RDB 文件持久化于此;--port 6371:客户端连接端口;--cluster-enabled yes:启用集群模式(必须);--cluster-config-file nodes-6371.conf:每个节点独立配置文件,避免多实例覆盖同一文件;--bind 127.0.0.1:仅监听本地回环,禁止外部访问(安全基线);--protected-mode no:--net=host下必须关闭保护模式,否则拒绝非127.0.0.1连接(而集群握手时 Redis 内部用getaddrinfo解析自身 hostname,默认返回::1或127.0.0.1,但 protected-mode 会拦截);--daemonize no:Docker 容器要求主进程前台运行,否则容器立即退出。
该命令可直接在终端执行验证。成功后docker ps | grep redis-node1应显示状态Up,且netstat -tlnp | grep :6371可见监听。
3. 完整 Shell 脚本实现:6 个节点启动 + 集群初始化 + 健康检查闭环
3.1 脚本结构设计:变量驱动、幂等启动、失败熔断
我们采用「函数式模块」组织脚本,核心函数包括:
init_dirs:创建 6 个数据目录,设置chown 999:999(Redis 官方镜像默认用户 UID=999);start_node:封装docker run命令,接受端口、目录、节点名参数;wait_for_nodes:轮询redis-cli -p PORT ping,直到全部 6 个端口返回PONG;create_cluster:调用redis-cli --cluster create,传入全部 6 个127.0.0.1:PORT地址;check_cluster_status:执行redis-cli -p 6371 cluster info,验证cluster_state:ok且cluster_known_nodes:6。
脚本开头强制校验环境:
- Docker 是否已安装且运行:
systemctl is-active docker >/dev/null || { echo "Docker 未运行"; exit 1; } - CentOS 版本是否为 7.x:
grep -q "CentOS Linux release 7\." /etc/redhat-release || { echo "仅支持 CentOS 7.x"; exit 1; } firewalld是否禁用(避免端口拦截):systemctl is-active firewalld >/dev/null && { echo "请先 systemctl stop firewalld && systemctl disable firewalld"; exit 1; }
提示:
firewalld是 CentOS 7.x 默认防火墙,若未关闭,16371-16376端口会被拦截,导致集群握手超时。这是线上最常被忽略的坑。
3.2 核心启动函数:用 for 循环生成 6 个实例(含注释版)
#!/bin/bash # redis-cluster-deploy.sh —— CentOS 7.x 专用 Redis 集群一键部署脚本 # ========== 配置区 ========== REDIS_IMAGE="redis:7.2-alpine" NODE_PORTS=(6371 6372 6373 6374 6375 6376) DATA_ROOT="/data" # ========== 配置区 ========== init_dirs() { echo "【步骤1】创建数据目录..." for port in "${NODE_PORTS[@]}"; do dir="${DATA_ROOT}/redis-node${port}" mkdir -p "$dir" chown 999:999 "$dir" # Redis 容器内 UID=999,必须匹配 done } start_node() { local port=$1 local node_name="redis-node${port}" local data_dir="${DATA_ROOT}/redis-node${port}" local cluster_port=$((port + 10000)) echo "【步骤2】启动 ${node_name} (端口 ${port}/${cluster_port})..." # 检查是否已存在同名容器 if docker ps -a | grep -q "$node_name"; then echo " 容器 $node_name 已存在,跳过启动" return fi docker run -d \ --name "$node_name" \ --net=host \ --restart=always \ -v "$data_dir":/data \ -d "$REDIS_IMAGE" \ redis-server /usr/local/etc/redis/redis.conf \ --port "$port" \ --cluster-enabled yes \ --cluster-config-file "nodes-${port}.conf" \ --cluster-node-timeout 5000 \ --appendonly yes \ --appendfilename "appendonly.aof" \ --pidfile "/var/run/redis_${port}.pid" \ --logfile "/var/log/redis_${port}.log" \ --dir "/data" \ --bind 127.0.0.1 \ --protected-mode no \ --daemonize no } wait_for_nodes() { echo "【步骤3】等待所有节点就绪(ping 检测)..." local timeout=60 local elapsed=0 while [ $elapsed -lt $timeout ]; do local ready_count=0 for port in "${NODE_PORTS[@]}"; do if redis-cli -p "$port" ping >/dev/null 2>&1; then ((ready_count++)) fi done if [ "$ready_count" -eq "${#NODE_PORTS[@]}" ]; then echo " 全部 ${#NODE_PORTS[@]} 个节点已响应 PONG" return 0 fi sleep 2 ((elapsed += 2)) done echo " 超时:${timeout} 秒内未全部就绪,检查日志:docker logs redis-node6371" exit 1 } create_cluster() { echo "【步骤4】执行集群初始化..." # 构建节点地址列表:127.0.0.1:6371 127.0.0.1:6372 ... local nodes="" for port in "${NODE_PORTS[@]}"; do nodes="$nodes 127.0.0.1:${port}" done # 使用 --cluster-replicas 1 表示每个主节点配 1 个从节点(3 主 3 从) if ! redis-cli --cluster create $nodes --cluster-replicas 1 >/tmp/cluster-create.log 2>&1; then echo " 集群创建失败,日志:" cat /tmp/cluster-create.log exit 1 fi echo " 集群创建成功" } check_cluster_status() { echo "【步骤5】验证集群状态..." # 任选一个主节点检查 local master_port=${NODE_PORTS[0]} local info=$(redis-cli -p "$master_port" cluster info 2>/dev/null) if echo "$info" | grep -q "cluster_state:ok" && \ echo "$info" | grep -q "cluster_known_nodes:6"; then echo " ✅ 集群健康:6 个节点已识别,状态 OK" echo " 🔍 当前主节点分布:" redis-cli -p "$master_port" cluster nodes | grep master | head -3 | awk '{print $2,$3}' else echo " ❌ 集群状态异常,请检查:" echo "$info" exit 1 fi }逻辑说明:
init_dirs中chown 999:999是关键:Redis Alpine 镜像以 UID=999 用户运行,若宿主机目录属主为 root,容器内无法写入 AOF 文件,启动后立即 crash;start_node函数内if docker ps -a | grep -q "$node_name"实现幂等:重复执行脚本不会重启已有容器,避免数据丢失;wait_for_nodes使用redis-cli -p PORT ping而非docker ps,因为容器 Up 不代表 Redis 进程已 ready(加载配置、AOF 重放需时间);create_cluster中$nodes字符串拼接必须无空格首尾,否则redis-cli --cluster create解析失败(这是grep在 shell 脚本中常见用法的典型陷阱:空格导致参数截断)。
3.3 主流程:串联函数并加入错误捕获
main() { set -e # 任何命令失败立即退出 trap 'echo "❌ 脚本执行中断,错误行号: $LINENO"; exit 1' ERR echo "=== Redis 集群部署脚本启动(CentOS 7.x) ===" init_dirs for port in "${NODE_PORTS[@]}"; do start_node "$port" done wait_for_nodes create_cluster check_cluster_status echo "✅ 部署完成!集群地址:127.0.0.1:6371(主节点)" echo "💡 测试命令:redis-cli -p 6371 cluster nodes" } main "$@"set -e与trap的作用:
set -e保证任意子命令失败(如docker run权限不足、redis-cli ping超时)立即终止,不继续执行后续步骤;trap捕获ERR信号,输出具体失败行号,方便定位——这是 Shell 脚本调试的后悔药,没有它,你只能靠echo二分法排查。
4. 避坑指南:CentOS 7.x 上 Redis 集群部署的 5 个血泪经验
4.1 现象:redis-cli --cluster create报错Node 127.0.0.1:6371 is not empty. Either the node already knows other nodes...
原因:宿主机/data/redis-node6371/nodes-6371.conf文件残留旧集群元数据(如上次部署未清理),Redis 启动时自动加载该文件,认为节点已加入集群。
解决:在init_dirs函数中增加清理逻辑:
for port in "${NODE_PORTS[@]}"; do dir="${DATA_ROOT}/redis-node${port}" rm -f "$dir/nodes-${port}.conf" "$dir/appendonly.aof" "$dir/dump.rdb" mkdir -p "$dir" chown 999:999 "$dir" done4.2 现象:docker run启动后容器立即退出,docker logs redis-node6371显示Could not create server TCP listening socket 127.0.0.1:6371: bind: Cannot assign requested address
原因:宿主机127.0.0.1:6371端口已被占用(如旧 Redis 进程未 kill),或--bind 127.0.0.1在--net=host下与 IPv6 冲突(CentOS 7.x 默认启用 IPv6)。
解决:
- 执行
lsof -i :6371查杀占用进程; - 在
docker run命令中显式添加--ipv6=false参数(虽--net=host下无效,但部分内核版本需此开关); - 更稳妥做法:改用
--bind 127.0.0.1 --bind ::1双栈绑定(Redis 7.0+ 支持)。
4.3 现象:集群创建成功,但redis-cli -p 6371 cluster nodes显示部分节点fail?状态,且ping不通
原因:firewalld或iptables拦截了16371-16376集群总线端口(非客户端端口),导致节点间心跳包丢弃。
解决:
systemctl stop firewalld systemctl disable firewalld # 若必须启用防火墙,则开放端口: firewall-cmd --permanent --add-port=6371-6376/tcp firewall-cmd --permanent --add-port=16371-16376/tcp firewall-cmd --reload4.4 现象:redis-cli --cluster create卡住不动,日志显示Waiting for the cluster to join
原因:--protected-mode no缺失。--net=host下 Redis 默认仍启用保护模式,拒绝来自::1(IPv6 localhost)的连接,而集群握手时内部使用getaddrinfo解析 hostname,可能返回::1。
解决:确保docker run命令中包含--protected-mode no,且该参数在redis-server启动参数末尾(位置敏感,放在--bind之后)。
4.5 现象:脚本执行到create_cluster时报错redis-cli: command not found
原因:宿主机未安装redis-cli(Docker 容器内有,但集群创建命令需在宿主机执行)。
解决:脚本开头增加依赖检查:
if ! command -v redis-cli &> /dev/null; then echo "redis-cli 未安装,正在安装..." yum install -y redis fi注意:CentOS 7.x 自带redis包版本较低(3.2),但redis-cli --cluster功能从 3.0 起已存在,足够使用。若需高版本 CLI,可curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list && sudo apt-get update && sudo apt-get install -y redis-cli—— 但这是 Ubuntu 语法,CentOS 7.x 必须用yum install redis。
5. 验证与压测:确认集群真可用,不止是“看起来正常”
5.1 用redis-cli手动验证读写分离与故障转移
集群部署完成后,不能只信cluster info,必须实测:
# 1. 连接任意节点(自动重定向到正确 slot) redis-cli -p 6371 set hello world # 输出:OK # 2. 读取(验证 key 路由正确) redis-cli -p 6371 get hello # 输出:world # 3. 查看 key 所在槽位和节点 redis-cli -p 6371 cluster keyslot hello # 输出:866(槽位号) redis-cli -p 6371 cluster nodes | grep "866-" # 输出应包含主节点地址,如 "127.0.0.1:6371@16371 master - 0 171xxxxxxx 0 connected 0-5460" # 4. 模拟主节点宕机(kill 容器) docker kill redis-node6371 sleep 10 # 等待从节点提升为主 # 5. 检查新主节点(原从节点 6374 应升级) redis-cli -p 6374 cluster nodes | grep master | grep -E "(6374|6371)" # 应看到 6374 为 master,6371 为 fail关键点:redis-cli -p 6371 set hello world成功,不代表写入成功——如果 6371 是从节点,该命令会重定向到主节点执行。真正验证需cluster keyslot定位槽位,再cluster nodes确认该槽位当前 master 地址。
5.2 用redis-benchmark压测集群吞吐,对比单节点
单节点 Redis(6379)与 3 主 Redis 集群(6371-6373)的吞吐差异,是检验部署质量的终极标尺:
# 对单节点压测(基准) redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -t set,get # 对集群压测(需指定 -c 与 -n,且 key 带 {} 确保路由到同一槽) redis-benchmark -h 127.0.0.1 -p 6371 -n 100000 -c 50 -t set,get \ --key-pattern "user:{id}" \ --csv > cluster-bench.csv解读结果:
- 单节点
SETQPS 约 8~10w(取决于 CPU); - 3 主集群
SETQPS 应达 24~30w(线性扩展),若仅 12w,说明客户端未正确打散 key(如全用user:1导致所有请求落到同一主节点); --key-pattern "user:{id}"中{id}触发哈希标签(hash tag),确保相同{id}的 key 落在同一槽,不同{id}打散到不同主节点。
我的习惯是:每次部署完必跑
redis-benchmark,并用top -p $(pgrep redis-server)观察各节点 CPU 是否均衡。如果redis-node6371CPU 90%,其余节点 10%,一定是 key 设计没加 hash tag,或者客户端 SDK 未开启集群模式(如 Jedis 需JedisCluster,非Jedis)。
5.3 生产级检查清单:5 项必须确认的配置
| 检查项 | 命令 | 合格标准 | 不合格后果 |
|---|---|---|---|
| AOF 持久化启用 | redis-cli -p 6371 config get appendonly | "appendonly","yes" | 宕机丢失全部内存数据 |
| 集群总线端口监听 | ss -tlnp | grep ':16371' | LISTEN状态,PID 为 redis-server | 节点间无法握手,集群分裂 |
| 数据目录权限 | ls -ld /data/redis-node6371 | drwxr-xr-x 999 999 | 容器内 Redis 无法写入 AOF,反复 crash |
| protected-mode 关闭 | redis-cli -p 6371 config get protected-mode | "protected-mode","no" | 集群节点间连接被拒绝 |
| firewalld 状态 | systemctl is-active firewalld | inactive | 16371-16376端口被拦截,心跳超时 |
把这 5 行命令存为check-production.sh,每次上线前执行——这是我踩过三次数据丢失事故后养成的肌肉记忆。
希望帮到你。
本文还有配套的精品资源,点击获取