Docker实战指南:从容器基础到MySQL与Redis主从部署

发布时间:2026/9/9 5:37:14
Docker实战指南:从容器基础到MySQL与Redis主从部署 干运维快十年了见过太多团队被“环境配置”这件事折磨得死去活来本地能跑、线上崩开发说在我电脑上是好的测试环境一装就是一下午。直到公司全面切换 Docker我才真正体会到什么叫“一次构建处处运行”。如果你是刚接触 Docker 的开发者或者运维或者你已经会用 docker run 但对镜像、数据卷、容器网络这些概念还比较模糊这篇内容就是按我实际踩坑的顺序来写的先搞懂 Docker 到底是什么然后从零搭好环境、解决镜像下载慢再用 MySQL 8.0 和 Redis 主从这两个高频场景把最常用的命令和配置讲透最后用 Docker Compose 把整套环境一键拉起来顺带把我在生产环境里踩过的坑也一并整理出来。1. Docker 到底是什么又能帮我们解决什么1.1 从一次环境配置混乱说起我接手过一个老项目要同时跑 PHP 5.6、Node 8、MySQL 5.7 和一个用 Python 2.7 写的定时任务。新来的同事按文档装环境光编译扩展就折腾了两天最后系统里一堆依赖冲突连系统的 Python 3 都被搞坏了。这种问题不是个例而是所有团队在规模化后面临的共同痛点机器环境差异太大。Docker 解决的正是这个问题。它用一套“镜像”把应用代码连同操作系统级别的依赖、配置、环境变量全部打包然后在任何装了 Docker 的机器上以“容器”的形式启动。容器之间相互隔离不会污染宿主机也不会互相干扰。开发环境、测试环境、生产环境跑的是同一个镜像从源头杜绝了“环境不一致”引发的各种诡异问题。1.2 镜像、容器、数据卷、网络——4 个必须搞懂的基本概念刚接触 Docker 的人最容易混淆的是镜像和容器的关系。我用一个生活化的类比镜像就好比一个“安装光盘”里面刻好了完整的系统环境和应用代码而容器是由这张“光盘”启动出来的“正在运行的进程实例”。同一个镜像可以同时启动多个容器互不影响。数据卷是另一个重要概念。容器默认是“无状态”的一旦删除容器里产生的数据就全没了。数据卷相当于把宿主机上的一个目录或一块磁盘“挂载”进容器容器往这个目录里写文件就是直接写宿主机。这样即使容器被删、被重建数据依然还在。我后面讲 MySQL 部署时会重点演示。容器网络相对抽象一点但理解起来也不难。每个容器默认有自己的独立 IP容器之间可以通过网络互相访问。Docker 提供了多种网络模式最常见的是 bridge 网桥模式和自定义网络。自定义网络有个特别实用的能力允许用容器名做 DNS 解析也就是说你只需要写容器名而不是去查动态变化的 IP。1.3 Docker 能替换传统虚拟机吗很多人会把 Docker 和虚拟机放一起比较。虚拟机是虚拟出一整套硬件设备再在虚拟的硬件上跑一个完整的操作系统所以启动慢、体积大、占用高而容器直接共享宿主机的操作系统内核只是隔离了进程、文件系统、网络等资源所以秒级启动、资源占用小得多。但这并不意味着 Docker 能完全替代虚拟机。如果你的场景需要运行 Windows、需要高度隔离内核、或者有严格的安全审计要求虚拟机仍然是更合适的选择。Docker 更擅长的是应用交付和微服务部署两者是互补关系不是替代关系。我个人的选型标准很简单纯 Linux 应用、无内核改动、且要频繁部署的优先 Docker需要不同内核或强隔离的老老实实用虚拟机。2. Docker 环境搭建与镜像加速配置2.1 Windows 和 Mac 上安装 Docker Desktop 的完整步骤如果你是本地开发Windows 或 Mac 上最省事的方式就是装 Docker Desktop。Windows 现在默认走 WSL2 后端安装前建议先在 PowerShell 里执行 wsl --status 确认 WSL2 已经启用没启用的话先执行 wsl --install。然后去 Docker 官网下载 Docker Desktop 安装包一路 Next 就行。装完重启后Docker Desktop 会自动启动右下角鲸鱼图标变绿就说明引擎跑起来了。Mac 用户注意区分 Apple Silicon 和 Intel 芯片官方网站会自动识别。M 系列芯片的机器跑 x86 镜像时如果发现拉取后无法启动可以在 Docker Desktop 设置里打开 Rosetta 模拟或者用 docker buildx build --platform linux/amd64 来构建多架构镜像。这是我在 M 系列 Mac 上踩过比较典型的坑。安装完成后打开终端执行 docker version能同时显示 Client 和 Server 两段信息就说明客户端和服务端都正常。如果只显示 Client 而 Server 报错多半是 Docker Desktop 没有真正启动或者是 Windows 的 Hyper-V/WSL2 虚拟化服务没开。2.2 服务器上通过命令行安装 Docker服务器上装 Docker 建议用官方源。Ubuntu 和 Debian 系的核心就四步更新 apt 索引、安装依赖包、添加 Docker 官方 GPG 密钥和仓库、再安装 docker-ce。CentOS / Rocky 系统则是先装 yum-utils再添加 Docker 仓库然后 yum install docker-ce docker-ce-cli containerd.io。安装完第一件事不是着急拉镜像而是把当前用户加入 docker 组否则每次执行 docker 命令都要加 sudosudo groupadd docker sudo usermod -aG docker $USER newgrp docker这里有一个容易忽略的点修改用户组之后必须重新登录或者执行 newgrp docker 让它生效不然明明加了组却还是报 /var/run/docker.sock 权限不足。我经常看到有人卡在这一步。装完顺手执行 sudo systemctl enable --now docker 把服务设为开机自启再执行 docker run hello-world 验证。如果你能看到一段 “Hello from Docker!” 输出环境就基本没问题了。2.3 镜像加速配置解决拉取镜像慢的问题镜像下载慢几乎是所有人都会遇到的问题。默认的公共镜像仓库服务器在国外拉一个几个 GB 的镜像可能要等很久。我的解决方案是配置镜像加速器。编辑/etc/docker/daemon.json 文件加上 registry-mirrors 配置{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.mirrors.ustc.edu.cn ] }配置完成后执行sudo systemctl daemon-reload sudo systemctl restart docker然后用 docker info 查看 Registry Mirrors 字段是否生效。这里我要提醒一句公共加速器的地址变动比较频繁如果某个地址拉不动了优先在搜索引擎里找“docker 镜像加速器”的最新可用地址或者直接用你云服务商自带的容器加速服务。加速器地址本身没有严格统一标准能用、稳定就是好的。2.4 安装完成后的基础配置除了加速器我一般还会顺手在 daemon.json 里加上日志和存储配置避免后期踩坑{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, data-root: /data/docker }log-opts 是直接限制单个容器日志文件的大小和个数防止日志把磁盘写爆data-root 是把 Docker 的数据目录迁移到更大的磁盘分区。这两个配置我在后面“常见问题”章节还会展开说先埋个伏笔。3. 用 Docker 部署 MySQL 8.0从拉镜像到真实使用3.1 确定镜像版本为什么选 mysql:8.0 而不是 latest很多人拉镜像习惯直接拉 latest图省事。但在生产或稍微正式的开发环境里我强烈建议指定明确的版本号。latest 标签含义是“当前最新稳定版”但 Docker 官方镜像更新时latest 会跟着变你无法预测它什么时候从一个小版本跳到另一个大版本。MySQL 的话直接用 mysql:8.0 是个稳妥选择。8.0 是当前应用最广的大版本相对成熟生态和工具链都完善。如果你在用一些老框架或老项目才需要去考虑 mysql:5.7。至于 mysql:latest我见过有人拉完之后才发现默认认证插件变了导致老客户端连不上白白浪费时间排查。3.2 数据卷、端口、环境变量一次讲透 docker run 关键参数部署 MySQL 8.0 的命令我建议认认真真拆开看一遍docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyStr0ng!Pass \ -e MYSQL_DATABASEapp_db \ -e MYSQL_USERapp_user \ -e MYSQL_PASSWORDApp123456 \ -v mysql_data:/var/lib/mysql \ -v /etc/mysql/conf.d:/etc/mysql/conf.d:ro \ mysql:8.0逐条解释一下-d 表示后台运行不加的话 CtrlC 容器就停了。--name mysql8 给容器起个稳定名字后面 docker exec 和查看日志都直接用名字。-p 3306:3306 做端口映射宿主机 3306 端口映射到容器内 3306。左边是宿主机端口右边是容器端口。-e MYSQL_ROOT_PASSWORD 必须设置不设置容器直接启动失败这是官方镜像的安全设计。-e MYSQL_DATABASE 会在容器第一次初始化时自动创建一个数据库。-e MYSQL_USER / MYSQL_PASSWORD 会自动创建一个普通用户并授权访问 MYSQL_DATABASE这个比直接用 root 更安全。-v mysql_data:/var/lib/mysql 是数据卷挂载mysql_data 是 Docker 管理的命名卷不指定绝对路径也不怕丢。-v /etc/mysql/conf.d:/etc/mysql/conf.d:ro 是挂载宿主机上的自定义配置目录到容器内:ro 代表只读。这里最关键的是数据卷。我接手过不少“MySQL 容器一删数据全没”的案例根源就是图省事没有挂载数据卷。记住任何有状态应用数据库、消息队列、缓存部署在容器里第一个动作永远是先规划好数据卷挂载再考虑怎么跑起来。3.3 初始化账号、远程访问和备份恢复容器启动后执行 docker exec -it mysql8 mysql -uroot -p 进入 MySQL 命令行输入密码即可。默认的 root 用户只允许本地连接如果你需要从宿主机或者远程工具连接需要先创建一个允许任意主机访问的用户或者修改 root 的连接限制。更推荐创建独立用户CREATE USER app_user% IDENTIFIED BY App123456; GRANT ALL PRIVILEGES ON app_db.* TO app_user%; FLUSH PRIVILEGES;到这里用 Navicat、DataGrip 或者 Sequel Pro 这类客户端连接宿主机 IP 的 3306 端口就能正常访问了。如果连不上先检查防火墙有没有放行 3306再用 docker logs mysql8 查看容器日志大概率是认证插件或端口映射的配置问题。备份恢复也是数据库日常的一部分。用 docker exec 加上 mysqldump 就能实现在线备份docker exec mysql8 sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD app_db backup.sql恢复时docker exec -i mysql8 sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD app_db backup.sql我平时会把备份命令写成脚本每天凌晨跑一次再加上对象存储同步几行代码就能解决数据库备份这个让人头疼的问题。3.4 容器里改配置的正确姿势有时候需要调整 MySQL 的字符集、时区或者最大连接数正确的做法不是在运行中的容器里直接改 my.cnf——容器重建后修改就丢了。正确姿势是做一次配置文件挂载。在宿主机建一个目录比如 /etc/mysql/conf.d在里面新建 my-custom.cnf写入[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 max_connections500然后启动容器时加 -v /etc/mysql/conf.d:/etc/mysql/conf.d:ro 参数重启容器即可生效。之后想改配置直接改宿主机上的文件再重启容器既清晰又持久。这个思路适用于几乎所有需要自定义配置的镜像比如 Nginx、Redis、Elasticsearch本质都是“把配置文件外置”。4. 用 Docker 搭建 Redis 主从从零开始的无坑实战4.1 先搞清楚 Redis 主从是干什么的Redis 主从复制是 Redis 高可用体系的基础简单说就是一个主节点负责写多个从节点同步主节点的数据负责读。这样做的好处是读写分离把读压力分散到从节点同时做主节点宕机时的手动或自动切换。在 Docker 环境里搭主从比裸机部署多了一个关键点容器 IP 是动态的每次重启都可能变。好在 Docker 自定义网络支持容器名解析从节点配置里直接写主节点的容器名就能连上避免了 IP 漂移问题。4.2 自定义网络让容器稳定互访的关键先创建一个自定义网络docker network create redis-net然后启动主节点和从节点时都用 --network redis-net 指定网络。这样做的好处有两个容器之间可以用名字访问即使容器重建、IP 变化服务名解析依然有效。如果不创建自定义网络直接用默认 bridge两个容器之间虽然也能互访但每次都需要去 docker inspect 查 IP然后在配置文件里写死容器一重建就失效。我一开始图省事写过容器 IP后来重建了一次容器发现从节点完全连不上主节点排查了半天才意识到是这个问题。自定义网络这一步省掉一时爽排障火葬场强烈不建议省。4.3 主从配置文件的编写与挂载Redis 官方镜像默认是不带 redis.conf 启动的这样只能跑默认配置。要开启持久化和主从复制就得自己准备配置文件并挂载进去。我的习惯是把配置都放在宿主机 /etc/redis 目录下。redis-master.confport 6379 protected-mode no appendonly yes appendfilename appendonly.aofredis-slave.confport 6379 protected-mode no replicaof redis-master 6379 appendonly yes appendfilename appendonly.aofRedis 5.0 之后官方推荐用 replicaof 代替老的 slaveof不过两者目前都兼容。protected-mode no 是关闭保护模式允许容器外网络访问生产环境谨慎使用我这里是为了演示方便。启动主节点docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /etc/redis/redis-master.conf:/usr/local/etc/redis/redis.conf:ro \ -v redis_master_data:/data \ redis:7 \ redis-server /usr/local/etc/redis/redis.conf启动从节点docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /etc/redis/redis-slave.conf:/usr/local/etc/redis/redis.conf:ro \ -v redis_slave_data:/data \ redis:7 \ redis-server /usr/local/etc/redis/redis.conf注意几条细节镜像里的 redis-server 命令后面要跟配置文件的容器内路径不跟路径它就按默认配置启动从节点的端口映射成宿主机 6380避免和主节点的 6379 冲突配置文件挂载加了 :ro 只读防止容器内篡改。4.4 验证主从同步关键细节一定要看部署完成后执行docker exec redis-master redis-cli info replication重点看 Role:master 和 Connected_slaves:1 这两项以及从节点的 IP 地址是否正确。如果 Connected_slaves 是 0大概率是 replicaof 配置的主节点名解析不了先确认两个容器是否在同一个自定义网络里再确认从节点的 /etc/redis/redis-slave.conf 是否真的挂载进去了。我实测下来容器名的解析偶发性出现问题这时候可以先在从节点容器里执行 getent hosts redis-master 看看能不能解析出 IP。解析不了就检查网络创建时是不是都用了同一个 --network 参数。另外建议在新装的 Redis 里从节点上执行 redis-cli info persistence确认 aof_enabled 为 1确保重启后数据还在。5. Docker Compose一键把 MySQL 和 Redis 全部拉起来5.1 什么时候该用 Composedocker run 适合单容器简单场景但一旦有多个容器要协同工作比如我们的 MySQL 加 Redis 主从再加应用服务命令串会越来越长且容器之间的启停顺序、网络配置很难统一管理。Docker Compose 就是解决这个问题的用一个 YAML 文件描述整个服务栈一条命令全部拉起。我的体验是凡是超过两个容器的项目直接用 Compose 管理哪怕前期多花十分钟写配置后面每次启动、停服务、看日志都能省下不少时间。值得注意现在的 Docker Compose 已经内置在 Docker CLI 中执行 docker compose 即可别再单独装那个带横线的 docker-compose 老版本了。5.2 编写 docker-compose.yml 的完整示例我把前面的 MySQL 和 Redis 主从全部搬进一个 docker-compose.ymlversion: 3.8 services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: MyStr0ng!Pass MYSQL_DATABASE: app_db MYSQL_USER: app_user MYSQL_PASSWORD: App123456 TZ: Asia/Shanghai volumes: - mysql_data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d:ro healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -pMyStr0ng!Pass] interval: 10s timeout: 5s retries: 5 redis-master: image: redis:7 container_name: redis-master restart: always volumes: - ./redis/redis-master.conf:/usr/local/etc/redis/redis.conf:ro - redis_master_data:/data ports: - 6379:6379 command: [redis-server, /usr/local/etc/redis/redis.conf] redis-slave: image: redis:7 container_name: redis-slave restart: always depends_on: redis-master: condition: service_started volumes: - ./redis/redis-slave.conf:/usr/local/etc/redis/redis.conf:ro - redis_slave_data:/data ports: - 6380:6379 command: [redis-server, /usr/local/etc/redis/redis.conf] volumes: mysql_data: redis_master_data: redis_slave_data:Compose 文件里每个服务名默认就是网络内的 DNS 名字。因为 redis-slave 的配置里写的是 replicaof redis-master 6379Compose 自动创建的网络里 redis-master 是服务名直接就解析通了不需要我再手动创建网络。healthcheck 这一段是新手最容易忽略的配置。它让 Compose 周期性地检查容器是否真正就绪而不是只看进程有没有启动。MySQL 容器刚启动时可能还在初始化如果你不加健康检查就立刻启动依赖它的服务大概率会连接失败。5.3 常用命令与操作技巧在 docker-compose.yml 所在目录执行docker compose up -d 启动全部服务-d 表示后台运行。docker compose ps 查看服务状态和端口映射。docker compose logs -f mysql8 实时查看某个服务的日志。docker compose down 停止并删除容器想同时删数据卷就执行 docker compose down -v注意这个操作会清空所有挂载卷的数据。docker compose restart mysql8 只重启单个服务。写 Compose 配置几个小习惯本地开发路径用相对路径 ./别写死绝对路径方便换机器密码这类敏感信息优先用环境变量或者 .env 文件别直接硬编码在 YAML 里文件缩进必须严格YAML 对空格敏感一个缩进错就解析失败。6. 常见问题和避坑指南6.1 镜像下载慢与加速源失效怎么处理镜像下载慢除了配置加速器还有几个立竿见影的手段。第一优先使用瘦身版镜像比如 node:20-alpine、python:3.12-slim体积能小好几倍下载和传输都省时间。第二尽量在 Dockerfile 里把最不常变动的层放在前面利用层缓存避免重复下载已完成的部分。第三如果某个加速源突然拉不动了用 docker pull 加上私有仓库地址或者换一个加速源通常能解决。遇到 docker pull 卡住不动按 CtrlC 重试不一定有效。我一般先排查 DNS执行 docker run --dns 8.8.8.8 hello-world 看看能否正常拉取能拉动说明是 DNS 问题。也可以直接清掉 Docker 的缓存再重新拉一遍镜像。另外一个容易忽略的是网络代理和防火墙公司内网环境经常有白名单限制需要你把镜像仓库地址加入白名单。6.2 日志爆满问题与处理策略容器日志是磁盘杀手。默认 json-file 日志驱动会把容器输出的所有 stdout/stderr 日志都累积写入宿主机长时间运行不对其进行限制一个日志文件能涨到几十 GB。我遇到过磁盘被日志写满导致整个服务器宕机的生产事故教训非常深刻。接前面 daemon.json 的配置限制新容器的日志{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }对已经产生的超大日志文件直接在宿主机上清空。先找到容器 IDdocker inspect --format{{.LogPath}} mysql8然后执行truncate -s 0 /var/lib/docker/containers/xxx/xxx-json.log千万不用 rm 删除日志文件否则 Docker 会因为文件句柄还开着继续往那个已删除的 inode 里写数据磁盘空间也不会真正释放。这个坑我踩过一次海量数据明明删了空间却还是满的。6.3 数据与权限的各种坑容器内进程默认以 root 身份跑但很多官方镜像里实际是以非 root 用户运行的比如 MySQL 镜像内部以 uid 999 运行。这意味着容器在宿主机挂载目录中创建的文件属主会是 999如果你用宿主机 root 或其他用户去读这些文件会出现权限不足的问题。解决办法是给挂载目录设置合适的属主sudo chown -R 999:999 /data/mysql或者把宿主机当前用户 ID 传给容器比如 docker run 的时候加 --user $(id -u):$(id -g)。在 Compose 里也可以用 user: 1000:1000 字段。如果是开发环境不怕麻烦更建议直接查官方镜像文档里写的 UID 是多少再统一设置。另一个数据相关的坑是 bind mount 和命名卷的区别。bind mount 是挂载宿主机一个具体目录比如 -v /data/mysql:/var/lib/mysql宿主机上直接能看到文件方便调试命名卷是 Docker 管理的目录直接用 -v mysql_data:/var/lib/mysql数据实际在 /var/lib/docker/volumes/mysql_data 下面跨主机迁移更安全。有状态应用优先用命名卷无状态应用用 bind mount 方便配置修改。6.4 容器网络不通的快速排查容器网络问题我总结了一套四步排查法按顺序执行能解决大多数异常。第一步确认容器是否在同一网络docker inspect 容器名 查看 NetworkMode 字段两个容器要为同一个网络。第二步在容器内部直接 ping 对方容器名docker exec 容器名 ping 另一个容器名ping 不通就检查自定义网络的 DNS 解析。第三步检查端口映射和防火墙宿主机上执行 ss -lntp | grep 端口确认端口有被监听再检查云安全组和 iptables 规则。第四步看应用本身有没有监听正确地址以 MySQL 为例默认监听 0.0.0.0 没问题但有些应用只监听了 127.0.0.1容器外自然就连不上。如果排查完还是不通直接在宿主机上执行 docker network ls 看看是不是有多个网络互相隔离再确认容器是否误加到了新网络。Compose 项目默认会创建 project_default 网络如果手贱额外指定了别的网络容器之间就会因为网络隔离而互访失败。7. 最后的实际经验补充这套环境我从本地开发一直用到生产环境整体节奏是先理解容器与镜像的区别再熟悉 docker run 的几个核心参数最后用 Compose 把服务编排起来。MySQL 8.0 的数据卷挂载、Redis 主从的自定义网络配置这两个案例基本覆盖了 Docker 日常使用中 80% 的场景。数据卷是容器持久化的底线网络是容器间互访的前提把这两条主线搞清楚了Docker 用起来会顺手很多。最后再分享一个小技巧我习惯在每一个生产容器启动之前先在本地用相同镜像和参数跑一遍观察日志确认无误后再放到服务器上。这个习惯帮我避免了不少低级失误。Docker 这个东西看起来命令不多但真正要学好把每个参数背后的意义和每个配置的持久化方式想清楚比背一百条命令都有用。