4. Redis镜像构建与容器化基础
把 Redis 放进容器,并不是简单执行一条 docker run redis 就结束了。生产环境中的 Redis 容器镜像需要回答四个问题:基础镜像从哪里来、配置如何注入、进程是否健康、容器启动时到底执行了什么。本节围绕官方镜像分析、自定义 Dockerfile、配置注入、健康检查与启动脚本设计展开,目标是让读者能够独立构建一个可配置、可观测、可复现的 Redis 镜像。
一、官方镜像的结构分析
Redis 官方镜像(redis:7.2 等)通常基于 Debian 或 Alpine 构建,其 Dockerfile 的核心逻辑可以概括为:编译或安装 redis-server 与 redis-cli,创建 redis 用户,暴露 6379 端口,设置 WORKDIR /data,并以 redis-server 作为入口。官方镜像的 ENTRYPOINT 是 docker-entrypoint.sh,它承担了一个关键职责:判断第一个参数是否以 - 开头。如果是,则自动补上 redis-server;如果不是,则直接执行用户传入的命令。
这意味着 docker run redis -h 10.0.0.5 会被解释为 redis-server -h 10.0.0.5,而 docker run redis redis-cli ping 则会直接执行 redis-cli。理解这一点,是后续自定义启动脚本的基础。官方镜像默认不加载外部配置文件,只使用内置默认值,因此直接用于生产会缺少持久化、密码、内存上限等关键设置。
二、自定义 Dockerfile 的构建策略
自定义镜像有两种常见路线:一是基于官方镜像叠加配置与脚本,二是从基础系统镜像自行编译 Redis。前者构建快、体积可控、升级方便,适合大多数云化部署场景;后者适合需要特定编译参数或裁剪模块的场景。下面给出一个基于官方镜像的典型 Dockerfile 示例:
FROM redis:7.2-bookworm
# 以 root 安装必要工具后切回 redis 用户
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
curl && rm -rf /var/lib/apt/lists/*
# 复制配置模板与启动脚本
COPY redis.conf.tmpl /usr/local/etc/redis/redis.conf.tmpl
COPY docker-entrypoint-custom.sh /usr/local/bin/docker-entrypoint-custom.sh
RUN chmod +x /usr/local/bin/docker-entrypoint-custom.sh \
&& chown -R redis:redis /usr/local/etc/redis
USER redis
WORKDIR /data
EXPOSE 6379
ENTRYPOINT ["/usr/local/bin/docker-entrypoint-custom.sh"]
CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]
这里的关键点有三个:第一,配置文件以模板形式复制,运行时再渲染,避免把环境相关参数写死;第二,启动脚本作为 ENTRYPOINT,负责配置注入与权限检查;第三,CMD 提供默认启动命令,允许运行时覆盖。镜像层顺序上,把不常变化的依赖安装放在前面,把配置和脚本放在后面,可以提升构建缓存命中率。
三、配置注入:从环境变量到 redis.conf
Redis 本身不读取环境变量,因此配置注入需要在启动脚本中完成。常见做法是使用 envsubst 或 sed 将模板中的占位符替换为环境变量值。例如模板中可以写:
bind 0.0.0.0
port ${REDIS_PORT}
requirepass ${REDIS_PASSWORD}
maxmemory ${REDIS_MAXMEMORY}
maxmemory-policy ${REDIS_MAXMEMORY_POLICY}
appendonly yes
dir /data
启动脚本中执行 envsubst < redis.conf.tmpl > /usr/local/etc/redis/redis.conf,即可生成最终配置。需要注意两点:一是必须为每个变量提供默认值,否则 envsubst 会替换为空字符串,导致配置项缺失;二是密码等敏感信息不应写入镜像层,而应通过 Secret 或运行时环境变量注入。下面这张图展示了从镜像构建到容器启动的配置注入与健康检查链路。
四、健康检查与启动脚本设计
健康检查的核心是回答“这个 Redis 实例现在能不能正常服务”。最基础的检查是 redis-cli ping,返回 PONG 表示进程存活且能响应命令。但在设置了密码的场景下,必须带上认证参数,否则会返回 NOAUTH Authentication required,导致健康检查误判。因此脚本中应使用 redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping。
更严格的就绪检查可以读取 info server 中的 redis_version 字段,确认实例已完成初始化。在 Kubernetes 中,livenessProbe 用于判断是否需要重启容器,readinessProbe 用于判断是否可以把流量导入。两者可以使用同一个命令,但阈值不同:liveness 的 failureThreshold 可以设为 3,periodSeconds 设为 10;readiness 的 initialDelaySeconds 可以设为 5,让实例有时间加载 AOF 或 RDB 文件。
启动脚本还需要处理几个边界情况。第一,检查 /data 目录是否可写,如果不可写则直接退出并打印明确错误,避免 Redis 启动后无法持久化。第二,如果 REDIS_PASSWORD 为空,应决定是拒绝启动还是允许无密码运行,生产环境建议拒绝启动。第三,使用 exec 替换当前 shell 进程,使 redis-server 成为 PID 1,从而正确接收 SIGTERM 信号并优雅关闭。下面给出一个可参考的启动脚本骨架:
#!/bin/sh
set -e
: "${REDIS_PORT:=6379}"
: "${REDIS_MAXMEMORY:=512mb}"
: "${REDIS_MAXMEMORY_POLICY:=allkeys-lru}"
if [ -z "$REDIS_PASSWORD" ]; then
echo "ERROR: REDIS_PASSWORD is required" >&2
exit 1
fi
if [ ! -w /data ]; then
echo "ERROR: /data is not writable" >&2
exit 1
fi
envsubst < /usr/local/etc/redis/redis.conf.tmpl \
> /usr/local/etc/redis/redis.conf
exec "$@"
五、构建与验证的完整闭环
完成上述文件后,执行 docker build -t redis-custom:7.2 . 构建镜像。启动容器时传入环境变量:
docker run -d --name redis-test \
-e REDIS_PASSWORD=devpass123 \
-e REDIS_MAXMEMORY=256mb \
-p 6379:6379 \
redis-custom:7.2
验证分三步。第一步,查看容器状态是否为 healthy:docker inspect --format='{{.State.Health.Status}}' redis-test。第二步,进入容器确认配置文件已正确渲染:docker exec redis-test cat /usr/local/etc/redis/redis.conf,重点检查 requirepass 和 maxmemory 是否与传入值一致。第三步,使用 redis-cli 连接并执行 CONFIG GET maxmemory,确认运行时参数生效。如果健康检查一直处于 starting 或 unhealthy,优先检查密码是否传入、/data 权限是否正确、以及 envsubst 是否已安装。
envsubst 由 gettext 包提供,需要额外安装;Debian 系镜像通常已包含。若不想引入额外依赖,也可以用 sed 逐项替换,但要注意特殊字符转义。至此,一个具备配置注入、健康检查和可控启动流程的 Redis 镜像已经成型。它的价值不在于镜像本身有多复杂,而在于把“环境差异”从镜像中剥离出来,交给运行时参数决定,从而使同一镜像可以在开发、测试、生产等多环境中复用。