在服务器上装Redis这件事,我一开始也是老老实实下载源码编译,处理各种依赖,直到后来一次性接手好几台机器,才彻底转向了Docker方案。Docker安装Redis的好处不只是快,更关键的是环境隔离、部署可复现,出问题随时可以删掉容器重来,不会把宿主机搞乱。这篇内容主要面向刚接触Docker和Redis的开发者,同时也适合已经在用Redis但想从裸机迁移到容器的运维同学,我会把从拉镜像、规划目录、启动容器到踩坑排查的完整过程都过一遍,每一步都给你可以直接复制粘贴的命令,也会解释清楚每个参数为什么必须写。
1. 为什么用Docker跑Redis:从“能跑”到“跑得稳”
1.1 容器化Redis解决的三件事
先说我看到的真实对比。裸机装Redis的常规路径是:下载源码包、编译、configure、make install、再手写systemd service。这套流程本身没什么问题,但一旦服务器多了、版本多了,痛点就来了。第一,编译依赖不统一,同一份源码在不同机器上可能遇到不同版本的gcc或openssl,编译参数稍有差异,行为就不一致。第二,卸载不干净,源码编译出来的二进制散落在 /usr/local 下,时间一长根本记不清哪些文件是Redis留下的。第三,版本切换麻烦,想从7.0升到7.2,基本等于重新走一遍安装流程。
Docker方案把这些全部绕开了。镜像本身就是打包好的运行环境,Redis二进制、运行时库、默认配置都在里面,拉到哪台机器行为都一样。升级版本只需要换镜像标签,一条命令搞定。最重要的是,容器可以随时删掉重建,对宿主机零污染。我现在的习惯是:所有中间件都尽可能容器化,测试环境直接docker run挂个随机端口,生产环境用docker compose编排,省下的时间非常可观。
1.2 镜像选型:官方镜像优先,别乱用“全家桶”
用Docker装Redis,第一步不是敲命令,而是选镜像。虽然Docker Hub上Redis相关镜像很多,但我几乎只推荐官方镜像。为什么?因为官方镜像里的redis-server、redis-cli和Redis源码包完全同源,配置文件格式、命令参数、日志输出风格都和裸机安装一致,遇到问题去搜资料、查官方文档,全部对得上。而第三方镜像经常自作主张改了启动入口、目录结构或环境变量,有时候连redis.conf放哪都不一样,排查起来很费劲。
镜像标签方面,我建议固定主版本,例如 redis:7.2-alpine 或 redis:7-alpine。alpine版本体积小,二十几兆,拉取快、占内存少,在容器里跑得很欢。如果对glibc有执念,或者怀疑某些底层库兼容性,可以用 redis:7.2 这种基于Debian的版本,体积大一些但更“传统”。我最不建议的是直接用 latest 标签,因为你不知道它哪天就从7.0跳到8.0,生产环境最怕这种无声无息的变化。选好镜像后,再确认一下docker compose版本,后面编排用得着。
1.3 先想清楚“容器不是虚拟机”
这是迁移到Docker时最容易犯的认知错误。有人把docker run出来的Redis当云主机用,启动后docker exec进容器,vi手工改配置文件,再装一堆调试工具。这种用法不是不行,但是违背了容器化的初衷。
容器强调不可变性,最佳实践是:配置文件通过挂载卷传进去,启动参数直接写在docker run命令里,数据落在宿主机持久化目录。容器本身只是一个可丢弃的运行壳,哪天想升级、想换参数,直接删掉重建,不要想着“进容器里改一下”。你一旦习惯了这个思路,后面所有操作都会顺很多。
另外还有个细节,Docker容器里只能跑前台进程。Redis的redis-server命令默认是前台运行的,这正好符合容器要求。但如果你从网上复制了一份redis.conf,里面带有daemonize yes,那就麻烦了——Redis会fork一个后台进程,然后容器前台进程认为自己使命完成直接退出,容器马上就Exited。这个问题我见过太多次,后面排查部分会专门说。
2. 动手前的三个关键规划:数据、配置与网络
2.1 数据目录:给Redis一个“不会丢的家”
在跑容器之前,我强烈建议先在宿主机规划好目录,常见结构是这样:
/docker/redis/ ├── conf/ │ └── redis.conf └── data/conf目录放你自己的redis.conf,data目录放RDB快照和AOF文件。为什么要单独建目录而不是随手找个位置?因为后面升级容器、迁移机器,你只需要把这整个目录拷走,新机器上挂载同一份数据,Redis就能无缝“搬家”,所有历史数据都在。如果随手把数据扔在某个临时目录,过两个月你自己都找不到,更别说迁移了。
数据目录建好后,还有一步特别容易被忽略——权限。官方Redis镜像内部有一个名为redis的用户,UID是999,data目录必须让这个用户能读写。如果你直接把宿主机的root权限目录挂载进去,很可能会看到容器日志里报权限错误,或者Redis启动一切正常但持久化时静默失败。这个我放进常见问题里细讲,提前说一句:建议建好目录后执行chown -R 999:999 /docker/redis/data,能少踩一个坑。
2.2 网络与端口:别把Redis裸奔在公网
Redis本身不提供加密传输,默认端口6379,默认没有密码,如果直接暴露到公网,基本上等于裸奔。安全问题不是危言耸听,服务器上扫描端口真的很常见。所以端口规划这件事,一开始就要做对。
最简单的做法是:只让宿主机本机访问Redis,容器端口通过回环地址映射。命令里写成 -p 127.0.0.1:6379:6379,而不是 -p 6379:6379。前者意味着只有服务器本机的程序能连Redis,外网无法直接访问;后者则会监听所有网卡,如果你的服务器有公网IP,Redis就直接暴露了。
还有一个很多人弄混的概念:容器内部的bind地址。如果挂载了自写redis.conf,配置文件里千万别写bind 127.0.0.1。因为容器有独立的网络命名空间,127.0.0.1是容器自己的回环地址,宿主机通过端口映射访问时,流量是从容器网卡进来的。如果Redis只听容器回环,你会发现端口映射看起来没问题,但怎么都连不上。正确做法是在容器内监听所有网卡,即bind 0.0.0.0,然后暴露范围由docker -p参数来控制。记住这句话:容器内部监听范围交给Redis,外部可访问范围交给Docker。
2.3 基础配置:密码、持久化与内存上限
Redis配置需要关心的核心项,我总结为三件套:密码、持久化、内存上限。
密码方面,最快捷的方式就是启动时加 --requirepass 参数。如果你用配置文件,则在redis.conf里写requirepass。注意设置了密码之后,Redis客户端每次连接都要带密码,健康检查也一样,后面验证命令里我会展示正确写法。
持久化方面,Redis默认开启了RDB快照,但更推荐同时把AOF打开。AOF是追加日志文件,每一条写命令都会被记录,相比RDB来说丢失窗口更小。容器场景下,持久化文件本身已经通过卷挂载到宿主机,再加AOF,基本可以做到“容器随便删、数据不丢”。
内存上限也很重要。Redis不管宿主机有多少内存,默认情况下它会无限增长直到撑爆系统。容器里最常见的事故就是Redis占用内存过多导致宿主机OOM。所以建议在配置里设置maxmemory和淘汰策略,例如maxmemory 1gb表示最多使用1GB,maxmemory-policy allkeys-lru表示内存满了以后优先淘汰最久没用的key。这个根据业务量调,但绝对不能省略。
下面是一份我常用的最小配置,直接放到 /docker/redis/conf/redis.conf 里就能用:
bind 0.0.0.0 protected-mode yes port 6379 requirepass 你的强密码 appendonly yes appendfsync everysec maxmemory 1gb maxmemory-policy allkeys-lru这里面的protected-mode yes要解释一下。Redis的protected-mode规则是:如果没有密码,又绑定了非本机地址,Redis会拒绝远程连接。我们配置了requirepass,密码验证通过后就能正常访问,所以protected-mode保持yes是安全的。
3. 实操:一条条命令把Redis跑起来
3.1 检查环境并拉取镜像
正式开始之前,先确认Docker环境正常。我自己通常跑两条命令:
docker --version docker compose versiondocker命令负责单容器操作,docker compose负责多容器编排,后面都会用到。接着拉镜像:
docker pull redis:7.2-alpine这一步会从Docker Hub拉取镜像,耐心等一下。拉完之后可以用 docker images 确认一下镜像已经存在。如果你在服务器上遇到拉取超时,考虑配置镜像加速器,这个不属于Redis范围,就不展开了。
3.2 无配置文件快速启动:适合测试环境
如果是本地测试、临时跑一下,不追求持久化和完整配置,一条命令就够了:
docker run -d \ --name redis-test \ -p 6379:6379 \ -v /docker/redis/data:/data \ redis:7.2-alpine \ redis-server --appendonly yes --requirepass "123456"逐段拆开看。docker run创建并运行容器,-d表示后台运行。--name给容器起名,后面操作都靠这个名字。--name redis-test的意思是容器标识符叫redis-test,删容器、看日志、进容器都用它。如果你不指定,Docker会随机生成一个名字,操作起来非常难受。
-p 6379:6379表示把宿主机的6379端口映射到容器的6379端口。我上面说过,测试环境图方便可以写全地址,生产环境最好写成-p 127.0.0.1:6379:6379。
-v /docker/redis/data:/data表示把宿主机目录挂载到容器的/data目录。Redis默认的持久化路径就是/data,这样容器删除后,RDB和AOF文件仍然留在宿主机上。
最后的redis-server --appendonly yes --requirepass "123456"是在覆盖镜像默认启动命令。注意,官方镜像的默认启动命令本身就是redis-server,但没带参数;我们在这里追加了持久化和密码参数。命令执行完,可以用 docker ps 看容器状态,正常情况STATUS列应该显示Up。
验证连接:
docker exec -it redis-test redis-cli -a 123456 pingdocker exec表示在运行中的容器里执行命令,-it是交互模式。后面跟着redis-cli -a 123456表示用密码认证连接Redis,ping是Redis自带的心跳命令,返回PONG就说明一切正常。
3.3 用配置文件与docker compose启动:生产推荐
测试环境的命令虽然快,但所有参数都堆在命令行里,几周后你自己都看不清有哪些参数,更别说同事接盘了。生产环境我强烈建议用docker compose,配置写进文件里,可读性和可维护性高一个档次。
先在 /docker/redis/conf/redis.conf 写好第2.3小节的配置内容。然后新建 /docker/redis/docker-compose.yml:
services: redis: image: redis:7.2-alpine container_name: redis restart: always ports: - "127.0.0.1:6379:6379" volumes: - /docker/redis/data:/data - /docker/redis/conf/redis.conf:/etc/redis/redis.conf:ro command: ["redis-server", "/etc/redis/redis.conf"] environment: - TZ=Asia/Shanghai这里几个关键点拆开说。restart: always表示容器异常退出后Docker会自动拉起,对生产环境几乎是必需的。volumes里第二行把宿主机redis.conf挂载到容器的/etc/redis/redis.conf,注意后缀:ro表示只读挂载,防止容器内误改配置文件。command指定Redis启动时加载这个配置文件,如果挂载了conf却不通过command加载,配置是不会生效的。
environment里设置TZ=Asia/Shanghai是因为基础镜像默认时区是UTC,Redis日志时间会差8个小时,看着非常别扭。容器内时区对Redis本身没有功能影响,但排查问题看日志时,一个本土时间能省去很多换算的麻烦。
启动:
cd /docker/redis docker compose up -d然后验证:
docker exec -it redis redis-cli -a 你的密码 ping因为restart: always和持久化都已经配置好,这份compose文件基本可以满足日常使用。后续想改配置,只需要修改redis.conf,然后docker restart redis,Redis会平滑重启并加载新配置。
3.4 验证持久化:关键一步别跳过
很多教程到这里就结束了,但我建议你做一次数据写入和重启验证,确认持久化真的生效了。步骤很简单:
docker exec -it redis redis-cli -a 你的密码 set mykey hello docker restart redis docker exec -it redis redis-cli -a 你的密码 get mykey第一行写入一个测试key,第二行重启容器,第三行读回这个key。如果返回hello,说明持久化没问题;如果返回nil或者连接出错,那多半是数据卷没挂好或者AOF没开,回到第2.1小节和2.3小节检查。
这个验证我每次部署都会做。原因很简单,持久化出问题往往是“下次重启才知道”,而重启一般发生在紧急情况下,真到那时候再发现数据丢失就来不及补救了。花一分钟提前验证,是性价比最高的操作。
4. 常见问题与排查实录:七个我踩过的坑
4.1 容器删除后数据全没了
这是最常见的“事故现场”。很多人看到容器在跑,进去写了点测试数据,然后docker rm把容器删了,再重新docker run,数据就全没了,于是惊呼Docker坑人。
其实原因非常明确:你没挂数据卷。Redis默认将RDB快照写到/data目录,如果挂载卷没做,那/data就是容器内部的一个临时目录,容器一删,目录跟着一起没了。解决方式就是我在2.1小节和3.3小节展示的:启动时用-v把宿主机目录挂载到容器的/data路径。只要挂载做好,无论容器删多少次,数据文件都安然躺在宿主机目录里。还有一种情况是挂载了但用的是匿名卷,docker volume ls能看到一堆随机ID的卷,删容器时如果没带-v参数,匿名卷会变成悬空卷,没有实际意义。建议总是挂命名路径。
4.2 Permission denied:日志里的权限错乱
启动容器后,docker logs redis 如果看到类似“Can't open the log file: Permission denied”或者持久化文件创建失败,第一反应应该是目录权限问题。
原因是我在2.1小节提到的:官方镜像内部redis用户UID是999,而宿主机新建目录默认归属root。容器内进程以redis用户运行,写入一个root所有的目录,自然没权限。解决方法是在宿主机执行:
chown -R 999:999 /docker/redis/data如果是配置文件挂载目录也有权限问题,同样的道理,把conf目录一起授权。chown之后重启容器,权限错误就消失了。这个坑特别隐蔽,因为容器能正常启动,只是持久化静默失败或者报错在日志深处,不仔细看根本发现不了。
4.3 日志里那两条WARNING:内核参数没调
如果你docker logs redis,大概率会看到两条WARNING。第一条是:
WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.第二条是:
WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.这两条不影响Redis立即运行,但很危险。somaxconn值太小,高并发下连接队列容易溢出,导致部分客户端连接失败。overcommit_memory为0时,极端内存压力下RDB后台保存可能被系统拒绝。解决方法是调整宿主机内核参数,在宿主机执行:
sysctl -w net.core.somaxconn=1024 sysctl -w vm.overcommit_memory=1想让配置重启后依然生效,把这两行追加到 /etc/sysctl.conf,然后执行sysctl -p。这两条解决了之后,Redis日志会干净很多,高并发场景下的稳定性也有直接提升。
4.4 容器秒退:daemonize yes惹的祸
容器创建后马上退出,docker ps看不到,docker ps -a能看到状态是Exited,查docker logs redis可能只看到一行启动日志甚至什么都没有。这种情况十有八九是配置文件里写了daemonize yes。
原因前面说过:容器是单前台进程模型,Docker判断容器是否存活只看前台进程是否还活着。Redis一旦daemonize,父进程fork完子进程后就退出,容器认为主进程结束了,直接把整个容器停掉,Redis子进程也被回收。解决方法是把daemonize yes改成daemonize no,或者干脆删掉这一行,因为默认就是no。如果是从网上随便复制的redis.conf,这个问题几乎一定会遇到。
4.5 连接不上的排查顺序
容器在运行,但redis-cli就是连不上,这种情况我说一个固定的排查顺序,按照这个顺序走,分分钟定位:
先看端口映射,docker port redis,确认宿主机的端口是不是真的映射到了容器6379。然后再看容器状态,docker ps确认不是Exited状态。之后在宿主机本地测试,redis-cli -h 127.0.0.1 -p 6379 ping,如果宿主机返回PONG,说明Docker和Redis本身都没问题,问题在外部网络。此时需要检查防火墙,firewalld或者ufw是否放行了6379端口。
如果宿主机测试报Connection refused,看Redis是否在监听:docker exec -it redis redis-cli ping。如果容器内能通而宿主机不通,大概率是端口映射没生效或者bind配置不对。最后再排查认证问题,如果你设置了密码,redis-cli没有带-a参数,会报NOAUTH Authentication required,这个报错信息很明显。
我给你整理成一个速查表:
| 现象 | 优先检查项 | 常见原因 |
|---|---|---|
| 宿主机redis-cli连接失败 | docker port、docker ps | 端口映射缺失或容器未启动 |
| 外网连接失败 | 防火墙、安全组 | 6379端口未放行 |
| 容器内能连,宿主机不能 | 端口映射 | -p参数漏写或写错 |
| NOAUTH错误 | 认证参数 | redis-cli未加-a密码 |
| 容器秒退 | docker logs | daemonize yes或启动命令错误 |
4.6 修改密码和配置的最佳姿势
运行中的Redis想改配置,有两种方案。一种是临时生效,用redis-cli直接改,例如:
docker exec -it redis redis-cli -a 旧密码 CONFIG SET requirepass 新密码CONFIG SET是Redis提供的在线配置修改接口,不需要重启就能生效,但缺点是容器重启后会丢失,回到配置文件里的旧值。另一种是永久生效,修改宿主机挂载的redis.conf,然后docker restart redis。生产环境我一般建议走文件修改,这样配置变化全程有记录,可控可回滚。这里有个细节,CONFIG SET后,连接客户端的旧密码就不能用了,需要立刻用新密码连接。在测试环境你可以多试几次“改坏再改回来”的过程,生产环境务必先在文件里改好,再重启。
4.7 宿主机端口被占用
如果启动时报“Bind for 0.0.0.0:6379 failed: port is already allocated”,说明宿主机6379端口已经被占了。常见原因是有另一个Redis实例,或者其他程序占了这个端口。查占用进程:
ss -lntp | grep 6379找到占用者后,要么停掉旧进程,要么换个宿主端口,比如把映射改成 -p 16379:6379,容器内仍是6379,对外用16379。我不建议让容器直接使用host网络模式跑Redis,虽然端口不用映射了,但宿主机还有个防火墙隔离层,host网络直接暴露进程,安全边界弱了不少。
结语
我个人在实际操作中的体会是:用Docker跑Redis,最难的地方根本不在docker run这一条命令,而是前面那几步规划——数据卷挂在哪、密码怎么设置、配置怎么管理、端口暴露到什么范围。这几件事想清楚了,后面就是复制粘贴的体力活。踩过几次坑之后,我现在部署Redis只会用docker compose,永远不会再手敲一长串参数。最后再分享一个小技巧:每次部署完,记得把那份redis.conf和docker-compose.yml提交到代码仓库,机器换了、同事接手了,拉下来就能复现整个环境。这个习惯能帮你省掉很多“当初是怎么配的”这种问题。