环境说明:MacBook(ARM架构)、本地Podman容器化部署
架构说明:结合生产环境最佳实践,本地部署将搭建3个Redis容器实例(一主二从)和3个Sentinel容器实例,用6个独立的容器实现Redis哨兵模式。
Podman容器部署细节:
6个容器在同一个共享网络中(IP不同但能够相互访问)
6个容器分别挂载到podman管理的持久化卷
Redis主节点分配固定的IP,从节点配置只读(读写分离)
挂载Redis容器的配置文件到本地(只需要读,不涉及权限问题)
Sentinel容器基于Redis镜像,用dockerfile构建自己的镜像,把配置文件写入到镜像中,避免写权限 问题
在启动时指定资源大小,Redis容器 0.2C/100M,Sentinel 容器0.1C/20M(Mac本地环境,资源有限)
日志文件可以通过podman logs查看,不需要挂载到本地
搭建官方推荐工具redisinsight容器访问Redis节点
使用密钥管理工具Vault(可选)
配置热重载(无需重启)(可选)
一、部署Redis(一主二从) Redis主从模式,配从不配主。
(一)创建持久化卷和共享网络 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for vol in redis_master_data redis_slave1_data redis_slave2_data sentinel1_data sentinel2_data sentinel3_data; do podman volume create $vol done podman network create redis_sentinel_net for vol in redis_master_data redis_slave1_data redis_slave2_data sentinel1_data sentinel2_data sentinel3_data; do podman volume rm $vol done podman network rm redis_sentinel_net
(二)准备好配置文件 在本地目录创建配置文件,内容见附件(redis-master.conf、redis-slave.conf),标准配置文件(以 Redis6 为例)见附件(redis-default.conf)。
1 2 3 4 5 # 在本地目录创建配置文件 /Users/zoran/dev/podman/redis&sentinel ├── conf │ ├── redis-master.conf │ ├── redis-slave.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 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 podman run -d \ --name redis-master \ -p 6379:6379 \ --ip 10.89.1.35 \ --restart always \ -v redis_master_data:/data \ -v ~/dev/podman/redis\&sentinel/conf/redis-master.conf:/etc/redis.conf:z \ --network redis_sentinel_net \ --cpus=0.2 \ --memory=100m \ -e TZ=Asia/Shanghai \ docker.io/arm64v8/redis:latest \ redis-server /etc/redis.conf MASTER_IP=$(podman inspect -f '{{.NetworkSettings.Networks.redis_sentinel_net.IPAddress}}' redis-master) echo "Master IP: $MASTER_IP " podman exec -it redis-master /bin/bash redis-cli auth zoran.wang@redis! INFO replication ping set hello redis!get hello exit podman run -d \ --name redis-slave1 \ -p 6389:6389 \ -v redis_slave1_data:/data \ -v ~/dev/podman/redis\&sentinel/conf/redis-slave.conf:/etc/redis.conf:z \ --network redis_sentinel_net \ --cpus=0.2 \ --memory=100m \ -e TZ=Asia/Shanghai \ docker.io/arm64v8/redis:latest \ redis-server /etc/redis.conf podman run -d \ --name redis-slave2 \ -p 6399:6389 \ -v redis_slave2_data:/data \ -v ~/dev/podman/redis\&sentinel/conf/redis-slave.conf:/etc/redis.conf:z \ --network redis_sentinel_net \ --cpus=0.2 \ --memory=100m \ -e TZ=Asia/Shanghai \ docker.io/arm64v8/redis:latest \ redis-server /etc/redis.conf
(四)脚本(生产环境推荐) 以上的脚本可以用 DeepSeek 生成为 shell 脚本,脚本见附件(redis-master.sh、redis-slave.sh),可以实现生产环境的全自动部署。
(五)验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 podman ps -a podman logs -f redis-master podman logs -f redis-slave1 podman logs -f redis-slave2 podman exec redis-slave1 redis-cli -a 'zoran.wang@redis!' INFO replication podman exec -it redis-slave1 redis-cli -a 'zoran.wang@redis!' keys * get hello
二、部署Sentinel哨兵(三个实例) (一)准备好配置文件 新建配置文件(sentinel.conf),内容见附件(sentinel.conf)。
(二)哨兵的权限问题
文件权限问题:
容器内的 Redis Sentinel 进程默认以 redis 用户(UID 999)运行。
挂载的宿主机文件 ~/dev/podman/redis&sentinel/conf/sentinel.conf 未赋予 redis 用户足够的写权限。
在MacOS系统中,podman本质上是个虚拟机,MacOS用户根本看不到虚拟机中的redis用户,属于不同层面,没办法赋予权限;
而Linux系统不一样,可以理解成Linux系统本身就是一个容器,与podman其他容器是同级别的,所以权限正常就能写。
当 Sentinel 尝试更新配置(如选举新主节点)时,会触发写入操作,导致权限错误。
SELinux 限制(如果宿主机启用 SELinux):
即使文件权限正确,SELinux 也可能阻止容器进程写入宿主机文件系统。
总结 :需要理解容器 的概念,容器中的进程对宿主机的文件很多情况只能在启动的时候读一次,不能进行写操作。
Linux解决方案 修改文件权限&调整安全策略即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 mkdir -p ~/dev/podman/redis\&sentinel/conf/sudo chown 999:999 ~/dev/podman/redis\&sentinel/conf/sentinel.conf chmod 644 ~/dev/podman/redis\&sentinel/conf/sentinel.confpodman run ... \ -v ~/dev/podman/redis\&sentinel/conf/sentinel.conf:/etc/sentinel.conf:Z \ ...
MacOS解决方案 MacBook上的podman实际上是运行在系统上的一个虚拟机,podman里面的容器无法对宿主机的文件进行写操作,只能在启动的时候读一次。
podman创建的数据卷是在虚拟机中的,我们可以把sentinel.conf挂载到数据卷sentinel_conf_data上,然后在创建容器时,用数据卷文件挂载取代本地文件挂载,sentinel进程(redis用户)就可以进行读写。
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 podman volume create sentinel_conf_data podman pull docker.io/arm64v8/busybox:latest podman run --rm \ -v ~/dev/podman/redis\&sentinel/conf:/source:ro \ -v sentinel_conf_data:/config \ docker.io/arm64v8/busybox:latest \ cp /source/sentinel.conf /config/sentinel.conf podman machine ssh sudo ls -l /var/lib/containers/storage/volumes/sentinel_conf_data chmod -x sentinel_conf_datapodman run -d --name sentinel \ --network redis_sentinel_net \ -v sentinel_data:/data \ -v sentinel_conf_data:/etc/sentinel_conf_data:Z \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ docker.io/arm64v8/redis:latest \ redis-sentinel /etc/sentinel_conf_data/sentinel-node1.conf
如果需要部署三个 Sentinel 容器,需要三个独立的 sentinel.conf!!!Sentinel(Redis用户)会对自己的配置文件进行写操作,千万不能使用持久化卷中的同一份配置文件!
通用方案——Dockerfile 通过 Dockerfile 构建自定义 Sentinel 镜像,使用环境变量动态配置 Redis 主节点 IP、密码等可变参数,既能保证配置的版本控制,又能保持部署的灵活性。
由于篇幅较长,我们把这段单独移到第三节进行讲解。
三、云原生最佳实践——Dockerfile (一)准备配置文件 sentinel.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 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 port ${SENTINEL_PORT} daemonize no logfile "" dir "/data" sentinel monitor ${MASTER_NAME} ${MYMASTER_IP} 6379 ${QUORUM} sentinel auth-pass ${MASTER_NAME} ${MYMASTER_PASSWORD} sentinel down-after-milliseconds mymaster ${DOWN_AFTER_MS} sentinel failover-timeout ${MASTER_NAME} ${FAILOVER_TIMEOUT} sentinel parallel-syncs ${MASTER_NAME} ${PARALLEL_SYNCS} sentinel sentinel-user ${SENTINEL_USERNAME} sentinel sentinel-pass ${SENTINEL_PASSWORD} sentinel deny-scripts-reconfig yes loglevel notice logfile "" maxclients 10000 protected-mode no
(二)准备容器启动脚本entrypoint.sh 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 #!/bin/bash temp_file=$(mktemp ) while IFS= read -r linedo while [[ "$line " =~ (\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}) ]]; do var_name="${BASH_REMATCH[2]} " var_value="${!var_name} " line="${line//${BASH_REMATCH[1]} /$var_value} " done echo "$line " >> "$temp_file " done < /etc/redis/sentinel-default.confmv "$temp_file " /etc/redis/sentinel.confchown redis:redis /etc/redis/sentinel.confchmod u+w /etc/redis/sentinel.confexec redis-sentinel /etc/redis/sentinel.conf
(三)创建 Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 FROM docker.io/arm64v8/redisENV MYMASTER_IP=10.89 .1.35 ENV MYMASTER_PASSWORD=zoran.wang@redis!ENV SENTINEL_USERNAME=defaultENV SENTINEL_PASSWORD=zoran.wang@sentinel!ENV SENTINEL_PORT=26379 ENV MASTER_NAME=mymasterENV QUORUM=2 ENV DOWN_AFTER_MS=5000 ENV FAILOVER_TIMEOUT=10000 ENV PARALLEL_SYNCS=1 COPY conf/sentinel-default.conf /etc/redis/sentinel-default.conf COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh" ]
(四)构建镜像 1 2 3 4 podman build -t my-sentinel:1.0 podman images
(五)运行容器 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 podman run -d --name sentinel-node1 \ -p 26379:26379 \ -v sentinel1_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0 podman run -d --name sentinel-node2 \ -p 26389:26379 \ -v sentinel2_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0 podman run -d --name sentinel-node3 \ -p 26399:26379 \ -v sentinel3_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0
四、故障转移验证 (一)确认初始状态 1 2 3 4 5 6 7 8 9 podman exec sentinel-node1 redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster podman exec redis-master redis-cli -a 'zoran.wang@redis!' INFO replication podman exec redis-slave1 redis-cli -a 'zoran.wang@redis!' INFO replication | grep -E "role|master_host" podman exec redis-slave2 redis-cli -a 'zoran.wang@redis!' INFO replication | grep -E "role|master_host"
(二)模拟主节点故障 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 podman logs -f sentinel-node1 podman logs -f sentinel-node2 podman logs -f sentinel-node3 podman logs -f redis-slave1 podman stop redis-master podman exec redis-slave1 redis-cli -a 'zoran.wang@redis!' INFO replication podman exec redis-slave2 redis-cli -a 'zoran.wang@redis!' INFO replication podman exec sentinel-node1 redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster podman start redis-master podman exec redis-master redis-cli -a 'zoran.wang@redis!' INFO replication podman stop redis-slave1 podman start redis-slave1
(三)重新搭建哨兵模式 1 2 3 4 5 6 7 8 9 10 11 12 sh redis-master.sh sh redis-slave.sh for i in 1 2 3; do podman stop sentinel-node${i} done for i in 1 2 3; do podman rm sentinel-node${i} done
五、官方可视化工具 1 2 3 4 5 6 7 8 9 podman volume create redisinsight_sentinel_data podman run -d \ --name redisinsight-sentinel \ -p 5540:5540 \ -v redisinsight_sentinel_data:/db \ --network redis_sentinel_net \ docker.io/redislabs/redisinsight:latest
注意登录的时候,IP不是 localhost,而是下面的命令获取的IP(站在 redisinsight 的视角,与 redis-master 都属于同层容器,访问的是 podman 中 redis-master 容器的 IP)
1 2 podman exec sentinel-node1 redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
======附件====== 文档树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ➜ redis&sentinel pwd /Users/zoran/dev/podman/redis&sentinel ➜ redis&sentinel tree . ├── conf │ ├── redis-default.conf │ ├── redis-master.conf │ ├── redis-slave.conf │ └── sentinel-default.conf ├── crt_redis.sh ├── crt_sentinel.sh ├── Dockerfile ├── entrypoint.sh ├── README.md └── shell ├── redis-master.sh └── redis-slave.sh 3 directories, 11 files
conf redis-default.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 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 bind 0.0.0.0 port 6379 protected-mode no daemonize no requirepass "zoran.wang@redis!" masterauth "zoran.wang@redis!" save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /data appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes replica-read-only yes repl-timeout 60 repl-diskless-sync no repl-disable-tcp-nodelay no repl-backlog-size 128mb repl-backlog-ttl 3600 requirepass "your_strong_password_here" rename-command FLUSHDB "" rename-command FLUSHALL "" rename-command CONFIG "REDIS-CONFIG" maxclients 10000 maxmemory 2gb maxmemory-policy allkeys-lru loglevel notice logfile "" databases 16 slowlog-log-slower-than 10000 slowlog-max-len 128 timeout 30 tcp-keepalive 300 protocol-version 3 oom-score-adj no dynamic-hz yes linux-tcp-backlog 511 replica-ignore-maxmemory yes disable-thp yes bgrewriteaof-schedule-delay 100 bgsave-schedule-delay 100 slowclock-source tsc
redis-master.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 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 bind 0.0.0.0port 6379 requirepass "zoran.wang@redis!" masterauth "zoran.wang@redis!" protected-mode no dir /data logfile "" daemonize no save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes
redis-slave.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 replicaof redis-master 6379 replica-read-only yes repl-timeout 60 repl-diskless-sync no
sentinel-default.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 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 port ${SENTINEL_PORT} daemonize no logfile "" dir "/data" sentinel monitor ${MASTER_NAME} ${MYMASTER_IP} 6379 ${QUORUM} sentinel auth-pass ${MASTER_NAME} ${MYMASTER_PASSWORD} sentinel down-after-milliseconds mymaster ${DOWN_AFTER_MS} sentinel failover-timeout ${MASTER_NAME} ${FAILOVER_TIMEOUT} sentinel parallel-syncs ${MASTER_NAME} ${PARALLEL_SYNCS} sentinel sentinel-user ${SENTINEL_USERNAME} sentinel sentinel-pass ${SENTINEL_PASSWORD} sentinel deny-scripts-reconfig yes loglevel notice logfile "" maxclients 10000 protected-mode no
crt_redis.sh 1 2 3 4 cd shellsh redis-master.sh sh redis-slave.sh
crt_sentinel.sh 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 podman run -d --name sentinel-node1 \ -p 26379:26379 \ -v sentinel1_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0; podman run -d --name sentinel-node2 \ -p 26389:26379 \ -v sentinel2_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0; podman run -d --name sentinel-node3 \ -p 26399:26379 \ -v sentinel3_data:/data \ --network redis_sentinel_net \ --cpus=0.1 \ --memory=20m \ -e TZ=Asia/Shanghai \ my-sentinel:1.0;
Dockerfile 注意首字母大写
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 FROM docker.io/arm64v8/redis ENV MYMASTER_IP=10.89.1.35 ENV MYMASTER_PASSWORD=zoran.wang@redis! ENV SENTINEL_USERNAME=default ENV SENTINEL_PASSWORD=zoran.wang@sentinel! ENV SENTINEL_PORT=26379 ENV MASTER_NAME=mymaster ENV QUORUM=2 ENV DOWN_AFTER_MS=5000 ENV FAILOVER_TIMEOUT=10000 ENV PARALLEL_SYNCS=1 COPY conf/sentinel-default.conf /etc/redis/sentinel-default.conf COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh" ]
entrypoint.sh 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 #!/bin/bash temp_file=$(mktemp ) while IFS= read -r linedo while [[ "$line " =~ (\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}) ]]; do var_name="${BASH_REMATCH[2]} " var_value="${!var_name} " line="${line//${BASH_REMATCH[1]} /$var_value} " done echo "$line " >> "$temp_file " done < /etc/redis/sentinel-default.confmv "$temp_file " /etc/redis/sentinel.confchown redis:redis /etc/redis/sentinel.confchmod u+w /etc/redis/sentinel.confexec redis-sentinel /etc/redis/sentinel.conf
README.md 1 2 3 4 5 6 7 8 # Redis哨兵模式部署大胜利! 部署步骤: 1. 检查并执行`crt_redis.sh` ,会自动创建 Redis 一主二从容器;2. 执行`podman images` 查看镜像,确认存在`my-sentinel` 镜像;3. (如果没有)执行`podman build -t my-sentinel:1.0` 构建镜像;4. 检查并执行`crt_sentinel.sh` ,会创建 Sentinel 三个哨兵容器。
shell redis-master.sh 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 # !/bin/bash # Redis 主节点部署脚本 # 作者:王卓 # 日期:$(date +%Y-%m-%d) set -euo pipefail # 启用严格错误检查 # 配置参数 CONTAINER_NAME="redis-master" NETWORK_NAME="redis_sentinel_net" VOLUME_NAME="redis_master_data" CONFIG_PATH="$HOME/dev/podman/redis&sentinel/conf/redis-master.conf" IMAGE="docker.io/arm64v8/redis:latest" CPU="0.2" MAX_SIZE="100M" PORT=6379 # 检查必要文件是否存在 check_dependencies() { # 检查配置文件是否存在 if [[ ! -f "$CONFIG_PATH" ]]; then echo "错误:Redis 配置文件不存在: $CONFIG_PATH" exit 1 fi # 检查镜像是否可用 if ! podman image exists "$IMAGE"; then echo "正在拉取 Redis 镜像..." podman pull "$IMAGE" || { echo "错误:无法拉取 Redis 镜像" exit 1 } fi } # 创建网络(如果不存在) create_network() { if ! podman network exists "$NETWORK_NAME"; then echo "创建网络 $NETWORK_NAME..." podman network create "$NETWORK_NAME" fi } # 主部署函数 deploy_redis_master() { echo "正在部署 Redis 主节点..." podman run -d \ --name "$CONTAINER_NAME" \ --ip 10.89.1.35 \ -p ${PORT}:6379 \ --restart always \ -v "${VOLUME_NAME}:/data" \ -v "${CONFIG_PATH}:/etc/redis.conf:z" \ --network "$NETWORK_NAME" \ --cpus=${CPU} \ --memory=${MAX_SIZE} \ -e TZ=Asia/Shanghai \ "$IMAGE" \ redis-server /etc/redis.conf echo "✅ Redis 主节点已成功部署" echo "容器名称: $CONTAINER_NAME" echo "访问端口: $PORT" } # 执行部署 main() { check_dependencies create_network # 检查容器是否已存在 if podman container exists "$CONTAINER_NAME"; then read -rp "容器 $CONTAINER_NAME 已存在,是否重新创建?[y/N] " response if [[ "$response" =~ ^[Yy]$ ]]; then echo "删除现有容器..." podman rm -f "$CONTAINER_NAME" else echo "操作已取消" exit 0 fi fi deploy_redis_master } # 执行主函数 main # 获取主节点IP IP=$(podman inspect -f '{{.NetworkSettings.Networks.redis_sentinel_net.IPAddress}}' redis-master) echo "容器内IP: $IP"
redis-slave.sh 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 121 122 123 124 # !/bin/bash # Redis 从节点部署脚本 # 支持部署多个从节点实例 # 修复了 POSIX shell 兼容性问题 set -euo pipefail # 启用严格错误检查 # 全局配置 NETWORK_NAME="redis_sentinel_net" IMAGE="docker.io/arm64v8/redis:latest" CONFIG_BASE_DIR="$HOME/dev/podman/redis&sentinel/conf" # 从节点配置 - 使用索引数组代替关联数组 SLAVE_NAMES=("redis-slave1" "redis-slave2") SLAVE_VOLUMES=("redis_slave1_data" "redis_slave2_data") SLAVE_CONFIGS=("redis-slave.conf" "redis-slave.conf") SLAVE_PORTS=("6389" "6399") # 检查依赖项 check_dependencies() { # 检查镜像是否可用 if ! podman image exists "$IMAGE"; then echo "正在拉取 Redis 镜像..." podman pull "$IMAGE" || { echo "错误:无法拉取 Redis 镜像" exit 1 } fi # 检查配置目录 if [[ ! -d "$CONFIG_BASE_DIR" ]]; then echo "错误:配置目录不存在 $CONFIG_BASE_DIR" exit 1 fi } # 创建网络(如果不存在) create_network() { if ! podman network exists "$NETWORK_NAME"; then echo "创建网络 $NETWORK_NAME..." podman network create "$NETWORK_NAME" fi } # 检查容器是否存在并处理 handle_existing_container() { local container_name=$1 if podman container exists "$container_name"; then read -rp "容器 $container_name 已存在,是否重新创建?[y/N] " response if [[ "$response" =~ ^[Yy]$ ]]; then echo "删除现有容器 $container_name..." podman rm -f "$container_name" return 0 else echo "跳过 $container_name 部署" return 1 fi fi return 0 } # 部署单个从节点 deploy_redis_slave() { local index=$1 local name=${SLAVE_NAMES[$index]} local volume=${SLAVE_VOLUMES[$index]} local config=${SLAVE_CONFIGS[$index]} local port=${SLAVE_PORTS[$index]} local config_path="$CONFIG_BASE_DIR/$config" # 检查配置文件 if [[ ! -f "$config_path" ]]; then echo "警告:配置文件不存在 $config_path,使用默认配置" config_path="/dev/null" # 使用镜像内默认配置 fi echo "正在部署从节点 $name..." podman run -d \ --name "$name" \ -p "${port}:6379" \ -v "${volume}:/data" \ -v "${config_path}:/etc/redis.conf:z" \ --network "$NETWORK_NAME" \ --cpus=0.2 \ --memory=100m \ -e TZ=Asia/Shanghai \ "$IMAGE" \ redis-server /etc/redis.conf echo "✅ Redis 从节点 $name 已部署" echo " - 数据卷: $volume" echo " - 外部端口: $port" echo " - 配置文件: $config_path" } # 主部署函数 deploy_all_slaves() { for index in "${!SLAVE_NAMES[@]}"; do echo "" echo "===== 处理从节点 ${SLAVE_NAMES[$index]} =====" if handle_existing_container "${SLAVE_NAMES[$index]}"; then deploy_redis_slave "$index" fi done } # 主执行流程 main() { echo "====== 开始部署 Redis 从节点 ======" check_dependencies create_network deploy_all_slaves echo "" echo "====== 部署完成 ======" podman ps --filter "name=redis-slave*" --format "table {{.Names}}\t{{.Ports}}\t{{.Status}}" } # 执行主函数 main