ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker实战教程:从镜像容器到MySQL与Redis主从部署

Docker实战教程:从镜像容器到MySQL与Redis主从部署 写这篇Docker教程的起因很实际上周帮朋友远程装 Docker Desktop屏幕上弹出Virtualization support not detected他第一反应是“是不是电脑坏了”。搞完虚拟化这一关他又在群里问为什么docker pull mysql:8.0一直转圈为什么容器成功启动但网页打不开为什么加了sudo还是没权限。这些问题的共性是Docker 本身的命令并不复杂难的是安装环境、镜像源、网络、权限这些隐藏前提。所以这篇我不打算把官方文档复读一遍而是按实际踩坑的顺序来写先建立镜像和容器的基本认知再把 Windows 和 Linux 环境装好接着解决镜像慢、权限、网络三个高频问题最后用 MySQL 8.0 和 Redis 主从两个例子把 Docker 从“能跑”带到“能用”。不管你是刚接触容器还是已经被报错卡了一下午这条链路应该都能给你省不少时间。1. 为什么我把Docker当成“搬运行李箱”而不是虚拟机1.1 镜像、容器和数据卷到底是什么关系很多人第一次接触 Docker 就背一堆命令结果连镜像和容器都分不清。我的理解方式是镜像就是行李箱模板容器就是“正在被你使用的行李箱”。docker pull是把行李箱模板下载到本地docker run是照着模板打开一个新箱子你在里面放的东西不会污染模板本身。这个比喻虽然粗但能解释后面所有行为。从技术上说镜像是只读的分层文件系统。拉取 mysql:8.0 时看到的一层层下载就是它的各个只读层启动容器后Docker 在最上面加一个可写层容器内所有写入都发生在这里。所以当你删掉一个容器那个可写层也没了——数据会丢。这也是为什么官方文档反复强调要用数据卷volume或绑定挂载bind mount把重要数据放到容器外。我第一次真正理解这一点是亲手把 MySQL 容器删了之后数据库“消失”。当时我以为数据在镜像里其实镜像只是程序文件数据库文件写在了容器层。教训很简单凡是需要保留的数据启动容器时就要挂载到宿主机目录或命名卷里。1.2 为什么Docker比虚拟机轻这么多虚拟机的思路是模拟一整台电脑每个虚拟机都要装完整的 Guest OS开机以分钟计占用几个 GB 内存都很正常。Docker 则不一样它直接调用宿主机内核容器本质上是宿主机上的进程只不过通过 namespace、cgroup 做了资源隔离和限制。所以容器启动能到秒级同一个宿主机能跑几十上百个容器资源开销小得多。代价是隔离性不如虚拟机容器共享内核没法在里面跑另一个内核版本的系统。如果你需要“一台完全不同的操作系统”或者运行底层内核模块Docker 不合适但如果只是想让应用和它的依赖保持一致Docker 几乎是首选。1.3 这个“心智模型”能帮你定位问题有了上面的模型遇到问题就不会瞎猜。比如docker run起不来先想镜像有没有拉下来、端口有没有被占用、启动命令有没有写错数据不见了第一时间想数据卷有没有挂对两个容器连不上第一时间想它们是不是在同一个自定义网络里。这些思路贯穿整篇后面每个排障过程都会再展开。提示学习 Docker 的最大误区是只记命令不理解“镜像—容器—数据卷—网络”四者的边界。命令可以随时查边界不清楚会让你无数次重复踩坑。2. 第一次安装DockerWindows和Linux分别该怎么避坑2.1 WindowsDocker Desktop跑不起来九成卡在虚拟化检测Windows 上最常见的安装问题是启动时直接弹Docker Desktop failed to start because virtualisation support was not detected。这个报错的核心是Docker Desktop 需要借助 WSL2 或 Hyper-V 来运行 Linux 容器而底层依赖 CPU 虚拟化能力。报这个错不代表 Docker 有问题而是 Windows 环境还没准备好。先打开任务管理器切到“性能”标签点击 CPU看右下角“虚拟化”状态。如果显示“已启用”说明 BIOS 没问题如果显示“已禁用”需要重启进 BIOS找到 Intel Virtualization TechnologyIntel 平台或 SVM ModeAMD 平台把它设为 Enabled。品牌不同位置不同一般藏在 Advanced 或 Security 菜单里保存退出再进 Windows。虚拟化状态正常但还是报错那就检查 WSL以管理员身份打开 PowerShell执行wsl --status和wsl --install。Win10 较老的版本可能要先启用“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个 Windows 功能然后重启。安装完成后建议再跑一遍wsl --set-default-version 2确保用 WSL2 而不是 WSL1。上述都做完再重新打开 Docker Desktop大多数情况就过了。还有一个容易忽略的点如果你电脑里同时装了 VMware、VirtualBox 这类虚拟化软件它们和 Hyper-V/WSL2 的虚拟化层可能冲突。临时关掉其中一个或者卸载而不是只关闭能减少“明明显示已启用Docker 还是起不来”的玄学问题。我自己在旧笔记本上遇到过类似问题最后是更新了 BIOS 固件才稳定。装好之后运行docker run hello-world能输出欢迎语就说明 Docker Desktop 整个链路通了。2.2 Linux安装Docker引擎的三种方式和权限问题Linux 没有 Docker Desktop 那层图形界面直接装 Docker Engine 就行。以 Ubuntu/Debian 为例我推荐走官方仓库安装而不是直接执行网上的curl ... | sh。后者虽然快但你不知道脚本到底改了什么生产环境里风险不小。官方仓库流程比较长但每一步都透明出了问题也容易回滚。sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCentOS/RHEL 一类系统也有对应的 yum 仓库配置新版 CentOS Stream 和旧版 7/8 在命令细节上有差异网上教程很多会混用导致装了一半源配置错。如果项目对系统版本不敏感Ubuntu 这条路径踩坑最少。装完 Docker 之后马上就有一个坑直接执行docker ps会看到permission denied报错内容是dial unix /var/run/docker.sock: connect: permission denied。这不是 Docker 坏了而是 Docker CLI 默认通过 unix socket 和守护进程通信这个 socket 只有 root 和 docker 组能访问。解决方法是把当前用户加入 docker 组然后重新登录或执行newgrp docker让组信息立刻生效。sudo usermod -aG docker $USER newgrp docker docker ps注意如果加入 docker 组之后没重新登录就执行可能还是同样的报错这是会话缓存问题不是操作无效。另外加入 docker 组相当于让用户有了接近 root 的能力个人电脑没问题多用户生产环境要谨慎别随便给所有人加这个组。提示安装完成后建议先跑docker version看 Client 和 Server 两段都正常再继续。只有 Client 没 Server说明守护进程没起来先执行sudo systemctl start docker或去看journalctl -u docker。3. 镜像始终是新手第一个拦路虎慢、权限、网络不通逐个拆3.1 拉镜像慢先确认是网络问题还是源的问题很多人的第一个 Docker 命令是docker pull mysql:8.0然后就看到进度条半天不动。原因很简单默认 Docker Hub 的服务器不在本地网络链路不稳定。解决办法不是反复重试而是配置镜像加速器registry mirror让 pull 请求先走一个更快的公共仓库节点。Docker Desktop 用户打开 Settings找到 Docker Engine在 JSON 配置里加registry-mirrorsLinux 用户编辑/etc/docker/daemon.json没有就新建。配置内容类似下面这样{ registry-mirrors: [ https://docker.m.daocloud.io ] }保存后重启 Docker。Linux 下执行sudo systemctl restart dockerDocker Desktop 直接 Restart 即可。验证配置是否生效用docker info输出里能看到 Registry Mirrors 列表。如果拉取还是慢大概率是那个加速器地址不稳定换一个当前可用的公共加速地址再试。我习惯多配两三个Docker 会在拉取失败时自动尝试下一个不会因为单点故障卡死。顺便说一句镜像加速器只影响docker pull不影响容器运行和已有镜像。不要以为配了加速器所有问题都能解决如果镜像本身有几 GB首次拉取慢是正常的耐心等就好。另外不同镜像源对 tag 的同步速度不同刚发布的新 tag 可能在加速器上还没有这种时候可以先用官方源拉或改用一个更稳定的版本号。3.2 权限错误docker命令要sudo不是Docker设计缺陷权限错误除了上一节说的 socket 权限还有一种更隐蔽的情况容器启动成功但容器内的进程没权限写宿主机挂载目录。典型场景是 MySQL 容器挂载一个空目录到/var/lib/mysql启动后日志里出现Cant create/write to file或chown: invalid user。原因是容器内进程是以某个系统用户跑的比如 MySQL 官方镜像里的 mysql 用户 UID 是 999而宿主机挂载目录的 owner 通常是 root。两边 UID 对不上容器自然写不进去。解决方式分两种简单粗暴的是sudo chown -R 999:999 /opt/mysql-data把目录 owner 改成容器内的 UID更推荐的是直接用 Docker 命名卷比如-v mysql_data:/var/lib/mysql命名卷由 Docker 管理权限第一次挂载时会自动初始化成容器需要的 owner能省掉大量宿主目录权限的问题。排查这类问题我一般先docker logs看是不是文件系统权限再docker inspect 容器名找到 Mounts 字段确认宿主机路径和容器路径到底是不是预期的那两个。很多时候不是 Docker 的错而是自己把路径写错了比如把数据卷挂到了别的地方。养成docker inspect的习惯能少走很多弯路。3.3 网络不通容器之间互访失败的排查链路“容器起来了但里面 ping 不通外网”和“两个容器互相访问不了”是两类问题别混在一起处理。先看外网不通。在宿主机上先确认外网本身通不通通了之后再进容器执行docker exec -it 容器名 ping 8.8.8.8。如果能通但ping google.com不通是 DNS 配置问题检查容器内/etc/resolv.conf必要时在docker run时加--dns 8.8.8.8。如果连 IP 都不通最常见的原因是宿主机防火墙把 FORWARD 链丢弃了或者 Docker 服务启动时没把 iptables 规则正确写入。可以先看iptables -L FORWARD再sudo systemctl restart docker让 Docker 重建规则。很多时候重启 Docker 就好了因为规则已经被其他工具覆盖或清除。再看容器互访。默认情况下两个容器同在一个宿主机上按理说能通过 IP 直接访问但容器重启后 IP 会变化而且默认 bridge 网络对服务名解析支持很差。正确做法是创建一个自定义网络让容器用服务名互相访问。docker network create mynet docker run -d --name mysql8 --network mynet -e MYSQL_ROOT_PASSWORDroot123 mysql:8.0 docker run -d --name app --network mynet -p 8080:80 myapp:latest此时 app 容器里连接数据库直接用主机名mysql8而不是127.0.0.1。Docker 内置 DNS 会把mysql8解析成对应容器 IP。用docker network inspect mynet能看到两个容器都在同一个子网段IP 也一目了然。这个设计是解决“容器连接不上数据库”的最常用方案后面 Redis 主从配置还会再用到。如果端口映射后还是从外面访问不到先检查容器里的服务是不是只监听了127.0.0.1。比如 Nginx 配置写成listen 127.0.0.1:80即使 Docker 做了-p 8080:80宿主机访问 8080 也可能失败。再检查宿主机防火墙有没有放行对应端口很多云服务器默认安全组没开也会让你误以为 Docker 网络坏了。4. 从MySQL 8.0到Redis主从把真实服务用Docker跑一遍4.1 MySQL 8.0一个最小可用的部署命令光说不练没用。下面这个命令是典型的 MySQL 8.0 单节点部署几乎每一条参数都会在真实场景里用到。docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEappdb \ -v mysql_data:/var/lib/mysql \ mysql:8.0逐个拆一下-d是后台运行不加的话日志会铺满终端关掉终端容器也容易被中断--name给容器固定名字方便后面用docker exec -it mysql8 ...-p 3306:3306把宿主机 3306 映射到容器 3306否则外部访问不到-e设置环境变量这里指定了 root 密码和初始化数据库-v mysql_data:/var/lib/mysql是核心中的核心把 MySQL 数据目录放到命名卷里容器删了数据还在。启动之后先看日志docker logs mysql8看到ready for connections就说明起来了。然后进容器验证docker exec -it mysql8 mysql -uroot -p在小规模学习和测试环境里把密码写在命令行可以接受但生产环境建议用env_file或者 Docker Secrets不要让密码出现在历史记录中。还有一个老生常谈的坑MySQL 8.0 默认认证插件是caching_sha2_password一些老版本客户端连接时会直接报Authentication plugin错误。升级客户端或者在初始化时指定--default-authentication-pluginmysql_native_password都能解决。别一上来就改数据先确认是客户端版本问题。4.2 Redis主从用容器名互相访问才是Docker的正确姿势Redis 主从是经常被拿来练手的方案因为它结构简单又能体现容器网络的价值。先建一个自定义网络然后把主从两个容器都放进去。docker network create redis-net docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7 \ redis-server --bind 0.0.0.0 --appendonly yes docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7 \ redis-server --bind 0.0.0.0 --replicaof redis-master 6379从库的复刻命令用的是--replicaof不是老文档里的--slaveof。Redis 5 开始slaveof被标记为废弃新版本里部分镜像已经不识别直接用--replicaof更稳。主库开启--appendonly yes开启 AOF 持久化从库则把replicaof指向主库的容器名redis-masterDocker 自定义网络会把这个名字解析成主库的容器 IP。验证主从状态进入主库看 replication 信息docker exec -it redis-master redis-cli info replication只要能看到的role:master和connected_slaves:1以及从库role:slave就说明主从已经通了。常见失败原因有两个一是两个容器不在同一个自定义网络里容器名解析不到二是 Redis 默认只监听127.0.0.1容器外部根本连不进来所以命令里要显式加--bind 0.0.0.0。这两个坑都不难查但初学者容易忽略。4.3 Docker Compose把MySQL和Redis主从收编成一套配置命令一多维护成本直线上升。尤其像上面 MySQL 加 Redis 主从三部曲两三组docker run还能忍项目一复杂就该上 Docker Compose。Compose 的核心是把容器列表、网络、卷声明写进一个 YAML 文件之后docker compose up -d一键拉起。services: mysql: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql redis-master: image: redis:7 container_name: redis-master command: redis-server --bind 0.0.0.0 --appendonly yes ports: - 6379:6379 redis-slave: image: redis:7 container_name: redis-slave command: redis-server --bind 0.0.0.0 --replicaof redis-master 6379 depends_on: - redis-master volumes: mysql_data:docker compose up -d启动后Compose 会默认创建一个 project 网络所有 service 都能通过服务名互相访问所以从库配置里的redis-master依然有效。平时最常用的三个命令是docker compose logs -f看所有服务日志、docker compose ps看运行状态、docker compose down停止并删除资源。特别提醒down默认不会删命名卷但如果加了-v会把所有容器依赖的卷一起删掉生产环境手抖代价很大慎用。5. 进入日常使用后这几个习惯帮我少踩了很多坑5.1 固定容器名并设置重启策略容器启动时不指定--nameDocker 会随机生成一个类似focused_mestorf的名字。日志和 inspect 的时候看到这种名字你根本不知道它是什么服务。所以不管多简单的容器都养成指定--name的习惯。重启策略往往被忽略。服务器重启后如果容器没有设置restartDocker 不会自动把它拉起来服务就悄悄“失联”了。推荐用--restart unless-stopped无论是机器重启还是 Docker 守护进程重启只要容器不是被人为docker stop它都会自动拉起来。Compose 里对应配置是restart: unless-stopped。注意always和unless-stopped的区别always会在你手动 stop 后仍尝试重启反而容易让人措手不及所以我一般默认用unless-stopped。5.2 镜像版本别用latest升级前先备份latest标签看起来方便其实是最不可控的版本。你一个月前用mysql:latest部署今天重新拉镜像可能就变成 MySQL 8.1、8.2 甚至更大的版本升级导致的不兼容问题会把你逼疯。生产环境固定到具体版本比如mysql:8.0.36如果还担心上游重新构建同一个 tag可以用docker inspect记录镜像的 digest锁到不可变内容。升级流程也不复杂先docker pull新版本镜像再启动一个临时容器把旧容器数据卷挂进去做检查确认无误后备份数据卷MySQL 用mysqldump文件服务直接拷贝最后停旧起新。不要在线上“先删旧再拉新”万一新镜像拉不下来旧服务已经被你删了现场会很难看。5.3 定期清理但千万别乱清卷Docker 跑久了镜像、容器、构建缓存会占掉大量磁盘。先用docker system df看看每个类别到底占了多少再决定清什么。docker system df docker system prune -a -fdocker system prune -a会把没有被使用的镜像、停止的容器、网络和构建缓存一次性清掉适合快速释放空间。但它不会删除数据卷因为 Docker 认为卷可能承载重要数据。相应地docker volume prune可以清掉“没有容器引用”的数据卷这个命令看起来安全实际上最危险。我曾经清理完才发现一个重要的数据库卷没被任何容器引用但数据还在里面差点救不回来。所以清卷之前先执行docker volume ls过一遍确认没有需要保留的备份卷再动手。数据卷不是构建缓存删了就是删了无法恢复。最后再分享一个我自己的使用习惯遇到任何奇怪的容器行为先看docker logs日志里没有头绪再看docker inspect——里面的 Mounts、NetworkMode、RestartPolicy 几乎能还原容器当初是怎么创建的。把读日志变成条件反射比背任何命令都管用。希望这份 Docker 教程能帮你把环境问题变成配置文件的问题把报错变成可排查的线索。
返回列表