我第一次在生产服务器上部署 Java 应用的时候,光环境就折腾了三天。JDK 版本不对、系统底层库版本太老、中间件配置路径记错——每换一台机器,整套流程就得重来一遍。后来我把整套运行环境连同应用一起塞进 Docker 容器里,整个部署时间从三天变成三分钟。这就是 Docker 最核心的价值:把环境、依赖、配置和应用一起打包,搬到哪台机器上都能原样跑起来。
这篇文章我把 Doker 里最该先搞明白的知识点串一遍,包括镜像、容器、仓库这几个基础概念的内在关系,Windows 和 Linux 两条安装路线的完整走法,日常用得最多的生命周期命令,还有数据卷、网络、编排和排错。不打算把文档里所有参数都堆给你,只讲你在真实项目里真正会用到的那部分。刚接触 Docker 的同学可以直接照着做,已经用了一段时间但总在报错里打转的人,也能从排查链路里找到对症的思路。
1. 从"打包一切"说起:镜像、容器、仓库到底是什么关系
1.1 镜像:把应用和环境一起烤进一张"光盘"
很多人第一次接触 Docker 都被"镜像"这个词绕晕了,其实它一点都不神秘。你可以把一个镜像理解成一张已经做好的系统安装光盘:光盘里不仅有操作系统的基础文件,还预装了某个软件以及它跑起来需要的全部依赖。MySQL 8.0 镜像里就包含了精简版 Linux 系统、MySQL 的可执行文件、默认配置和运行脚本,你用这个镜像启动容器,得到的就是一台开箱即用的 MySQL 服务器。
这个设计解决了一个很现实的问题:以前部署应用,要先在服务器上装系统、装运行时、装数据库、调依赖版本,任何一步出错都会影响后面所有步骤。用 Docker 之后,这些全部固化在镜像里,镜像是什么样,容器启动后就是什么样。同一个镜像在开发机、测试机、生产机上跑出来的结果是完全一致的,不存在"在我电脑上是好的"这种说法。
镜像还有一个特性是分层。Dockerfile 里每一条指令都会生成一个只读层,拉取镜像时如果本地已经有相同层,就会直接复用,不会重复下载。这也是为什么你拉了一个 Ubuntu 镜像之后再拉基于它的 MySQL 镜像,速度会明显快很多——公共的底层系统层被缓存了。
1.2 容器:同一张光盘可以复制出多个运行实例
镜像本身是静止的,运行时需要"实例化",这个运行中的实例就是容器。容器和虚拟机最直观的区别在于,容器不虚拟化硬件,它直接共享宿主机的操作系统内核,只是通过命名空间和各种隔离机制让里面的进程以为自己在独立系统里。
用光盘来类比:虚拟机像是把整台电脑搬进虚拟环境里再装系统,每个虚拟机都有一份完整独立的操作系统,占用资源大;容器更像是在宿主机系统上开出来的一个个隔离环境,每个容器只打包了应用和它需要的文件系统,启动就是一个进程的事,秒级起步。
这个区别带来了两个直接好处。第一是资源效率高,一台 8G 内存的云服务器跑三四个虚拟机可能就捉襟见肘了,但跑几十个容器没有问题,热搜里提到的"一台 N100 小主机跑 20 个 Docker"就是这种玩法。第二是可移植性强,容器不依赖底层操作系统的具体版本,只要宿主机能跑 Docker,就能跑任意镜像的容器。
1.3 仓库与 Dockerfile:从哪里拿镜像,怎么自制镜像
镜像不是凭空产生的。公开镜像都存放在镜像仓库里,最常用的就是官方维护的 Docker Hub,里面有 MySQL、Redis、Nginx、Hadoop 等海量现成镜像。你可以把仓库当成应用商店,docker pull就是下载安装包。公司内部一般会自建私有仓库存放自制镜像,避免把内部代码和配置暴露到公共平台上。
自制镜像靠 Dockerfile,它是一份描述"如何构建镜像"的清单。一个最简单的 Dockerfile 大概长这样:
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y nginx COPY index.html /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]这段内容的意思是:以 Ubuntu 22.04 为基础,安装 Nginx,把本地文件复制进镜像,暴露 80 端口,启动时执行 Nginx。你可能会问,这和写一份部署文档有什么区别?区别在于这份清单是可执行的,docker build能严格按步骤生成一个标准镜像,任何人拿到这个镜像得到的环境都一样,不存在漏装依赖或版本漂移的问题。
2. 环境搭建的两条路线:Windows 桌面版与 Linux 命令行安装
2.1 Windows 路线:Docker Desktop 的安装与虚拟化检查
在 Windows 上装 Docker,多数人走的是 Docker Desktop 这条路。但装之前必须先确认两件事:CPU 虚拟化有没有开启,以及系统是否支持 WSL2。
"virtualization support not detected" 这个报错基本是 Docker Desktop 启动失败的标配,九成原因是 BIOS 里的虚拟化开关没打开。开机时进 BIOS 设置(不同品牌按键不同,常见 F2、Del、F10),找到 Intel Virtualization Technology 或者 AMD SVM Mode,改成 Enabled 保存退出。进系统后打开任务管理器,在性能标签页可以看到"虚拟化"是否显示已启用。
确认虚拟化开启后,还需要确保 WSL2 可用。Docker Desktop 在 Windows 上依赖 WSL2 作为运行后端,没装的话桌面版会提示你安装。打开管理员权限的 PowerShell 执行:
wsl --install装完后重启系统。之后再下载 Docker Desktop 安装包,一路默认安装。装完打开,如果右下角的小鲸鱼图标稳定不动,说明运行正常。
这里踩过的坑是:有些机器明明 BIOS 里开了虚拟化,Docker Desktop 还是提示找不到虚拟化。这种情况多半是 Windows 自带的 Hyper-V 组件没启用。去"控制面板 - 程序 - 启用或关闭 Windows 功能",勾选"Hyper-V"和"适用于 Linux 的 Windows 子系统"两项,重启后再启动 Docker Desktop 就正常了。
2.2 Linux 路线:CentOS 与 Ubuntu 的安装操作
Linux 服务器上安装 Docker 没有图形界面,步骤更直白。Ubuntu 系和 CentOS 系略有区别,但大思路一样:先卸载可能存在的旧版本(尤其 CentOS 自带的 docker),再配置官方软件源,然后安装并启动服务。
Ubuntu 22.04 的完整流程:
sudo apt-get update sudo apt-get install -y 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 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 -y docker-ce docker-ce-cli containerd.ioCentOS 7 因为系统较老,官方源里默认没有 Docker CE,需要先设置软件源路径,再执行 yum 安装。装完之后执行sudo systemctl start docker和sudo systemctl enable docker,把服务启动并设为开机自启。验证是否装好,跑一下docker version,能同时看到 Client 和 Server 两段信息就说明服务正常。
2.3 镜像加速源配置:拉取慢的缓解办法
"镜像下载慢"是搜索里出现频率特别高的问题。默认 Docker Hub 在国外,国内网络访问自然慢,还容易超时中断。解决办法是配置镜像加速源,相当于给docker pull设置一个代理仓库,它会缓存公共镜像再分发给你。
配置方法很简单。修改/etc/docker/daemon.json(Windows 版 Docker Desktop 在设置里找到 Docker Engine 选项编辑同样文件),填入:
{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn" ] }改完执行sudo systemctl daemon-reload和sudo systemctl restart docker,让配置生效。加速源虽然不能保证所有镜像都飞快,但能把最常用的操作系统镜像和中间件镜像的拉取时间从几十分钟压到几分钟。需要提醒的是,加速源提供的镜像版本可能与官方实时同步存在轻微延迟,生产环境建议锁定具体版本号,不要用 latest 标签。
3. 每天都要用的容器生命周期操作
3.1 镜像拉取、查看与清理
镜像拉取是 Docker 使用频次最高的操作。docker pull支持指定标签拉取特定版本,比如docker pull mysql:8.0而不是docker pull mysql,后者拿到的 latest 标签未来会随官方更新变动,可能导致环境不一致。
查看本地已有镜像:
docker images这个命令会列出仓库名、标签、镜像 ID、创建时间和大小。镜像用多了占用空间很大,清理无用镜像用:
docker image prune加-a参数会连同没有被容器使用的镜像一起清理,谨慎使用,因为重建镜像可能要重新拉取基础层。
3.2 docker run 的核心参数逐个拆解
docker run是一条把镜像变成容器的命令,参数较多,但核心就这么几个,我拆开解释:
docker run -d \ --name my-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0-d表示后台运行,不加的话终端会被前台进程占住,Ctrl+C 容器就停了。--name给容器起名字,后续操作直接引名字,不用记一长串容器 ID。-p 3306:3306是端口映射,宿主机 3306 端口映射到容器内 3306 端口,这样外部程序访问宿主机 IP 的 3306 就能连上容器里的数据库,冒号左边是宿主机端口,右边是容器内端口。-e设置环境变量,MySQL 镜像要求必须设置 root 密码,这个参数就是干这个的。-v是数据卷挂载,把宿主机的/data/mysql目录挂载到容器内的数据目录,容器删了数据还在宿主机上。
一个很容易忽略但很实用的参数是--restart,它控制容器在异常退出或者宿主机重启时的行为。线上服务建议加上--restart=always,意思是只要 Docker 服务起来了就自动启动这个容器,不用手动去拉。排查问题时用--restart=no避免容器反复重启,方便看日志。
3.3 进入容器、看日志、查状态:排障基本功
容器里出了问题,第一步是看日志,而不是进容器重建。查看最近日志:
docker logs my-mysql指定日志条数和时间范围:
docker logs --tail 50 my-mysql docker logs --since 10m my-mysql--tail 50只看末尾 50 行,--since 10m看最近 10 分钟的日志。想看实时输出加-f。
要真正进入容器操作,用docker exec:
docker exec -it my-mysql bash-it是交互式终端的组合参数,容器里没有 bash 时可以换sh。进入容器后你就是容器里的 root 用户,可以查看目录结构、运行命令、检查配置,所有操作在容器重启后会失效。
查看容器状态:
docker ps docker ps -a第一条可能让你吓一跳:跑着的容器好像没几个。别急,docker ps默认只显示运行中的容器,-a才会显示包括已退出在内的所有容器。安装 MySQL 之后发现容器反复启动失败,先docker ps -a看状态那一列,再docker logs看具体报错,这是排查的基本顺序。
docker inspect能查看容器的完整配置和状态信息,包括挂载路径、网络配置、环境变量等,排障时很常用。你会看到 JSON 格式的输出,内容冗长,配合docker inspect my-mysql | grep -i ip之类的小技巧能快速定位容器 IP 之类的信息。
3.4 容器端口、名字、重启策略的规划经验
规划容器时有一个铁律:容器创建后,端口映射和挂载路径基本改不了。想改就得删容器重建,而之前产生的数据如果没有挂载出来就全没了。所以第一次docker run之前,一定要把端口、目录、环境变量都想清楚。
给容器命名也讲究规范。我习惯按"环境-项目-角色"的方式命名,比如prod-order-mysql、dev-user-redis,看起来直观也好维护。容器多了以后,docker ps的输出本来就长,名字起得好一眼就能分辨哪个是哪个。
重启策略和端口规划容易被人忽略的是防火墙问题。容器端口映射出去了,宿主机防火墙没放行,外部还是连不上。很多人排查半天容器本身没问题,最后发现是防火墙把端口挡了。遇到"外部访问不上、容器里一切正常"的情况,先查宿主机防火墙规则。
4. 数据卷、端口与网络:容器内外沟通的三件套
4.1 数据卷和绑定挂载:数据为什么必须"挂"出来
容器是临时性的,删掉之后写入的文件系统内容全没。但数据库的数据、应用的日志、上传的文件都是不能丢的,所以 Docker 提供了数据卷机制。
数据卷分两种。一种叫 bind mount,就是前面提到的-v /data/mysql:/var/lib/mysql,直接把宿主机目录挂进容器,路径你指定,备份和迁移都直观。另一种叫 named volume,写法是不带宿主机路径,只给卷起名字:
docker run -d --name my-redis -v redis-data:/data redisDocker 会在自己的数据目录下创建一个卷,你用docker volume ls能看到它,数据实际存储位置由 Docker 管理。我的经验是,单机场景用 bind mount 更直观,生产环境或需要跨主机迁移的场景用 named volume 更方便,因为不用关心具体物理路径,交给 Docker 编排体系处理。
速度上 bind mount 在 Docker Desktop(尤其是文件较多较复杂时)会有明显的 IO 损耗,因为 Windows 和 macOS 的文件系统与 Linux 容器文件系统之间有一层转换。大数据量读写的应用,如果追求性能,优先考虑 named volume 或者直接容器内存储,再定期备份出来。
4.2 三个网络模式:bridge、host、自定义网络
Docker 默认的网络模式是 bridge,容器会通过一个虚拟网桥 NAT 出去访问外部网络,宿主机以外的设备不能直接访问容器 IP。这相当于每个容器住在内网里,对外得靠端口映射开门。
另一种是 host 模式,容器直接使用宿主机的网络栈,没有独立 IP,也没有 NAT 转换。好处是网络性能几乎零损耗,坏处是端口直接占用宿主机端口,每个容器都需要独立端口。生产环境如果用 host 模式,要注意端口冲突和安全性。
还有一种 none 模式,容器只有回环接口,没有外部网络,适合对隔离要求极高的任务。不过实际操作中用得最少,更多是用自定义网络来精细控制容器间通信。
自定义网络是实际项目中最应该掌握的。创建一个自定义网络:
docker network create app-network然后把容器加入这个网络:
docker run -d --name user-api --network app-network user-service:1.0 docker run -d --name user-mysql --network app-network mysql:8.0同一个自定义网络里的容器可以通过容器名直接互相访问,比如 user-api 里连接数据库,主机名直接填user-mysql就行,不需要查 IP。这个特性省掉了维护容器 IP 的麻烦,容器重启 IP 变了也不会影响服务间通信。
4.3 容器间通信:推荐的自定义网络方案
单机多容器部署,我推荐一律走自定义网络,原因有三个。
第一是服务发现友好。默认 bridge 网络里,容器之间只能通过 IP 通信,而容器重建 IP 会变,这就得动代码或配置。自定义网络里直接用容器名当主机名,稳定可靠。
第二是隔离性可控。自定义网络可以做更细粒度的访问控制,比如把数据库放在一个内部网络,不暴露端口到宿主机,只有应用容器能通过自定义网络访问它。这是安全上很实用的做法。
第三是方便编排。如果你后面用 docker compose,它自动创建的网络就是你容器间通信的桥梁,配置方式是一致的。提前熟悉自定义网络的理念,迁移到 compose 是无缝的。
试过这个方案之后,我基本没用过默认 bridge 网络的默认模式来跑多服务应用。讲个实际场景:应用容器和数据库容器都在 app-network 里,应用连接数据库的地址写app-mysql:3306,安全上数据库不用映射宿主端口,外部除了应用谁也连不上。
5. 用 docker compose 串起一个真实项目:MySQL 8.0 + Redis 主从
5.1 为什么单条 docker run 不够用
服务一多,每条docker run手动敲的问题就暴露了。一是命令太长,容易写错参数;二是服务之间的网络关联要手动维护;三是环境迁移时,你得保证每台机器上都敲一模一样的命令,这本身就是一个巨大的坑。
docker compose 就是来解决这个问题的。它通过一个 YAML 文件声明所有容器的镜像、端口、挂载、网络和依赖关系,一条docker compose up -d全部搞定。我把 docker compose 理解成"容器项目的施工图纸",所有服务怎么搭、怎么连,都在图纸里写清楚。
对开发环境、测试环境、小规模生产环境来说,docker compose 足够好用。网上各种教程里最常见的场景——安装 MySQL、搭建 Redis 主从、部署微服务项目——基本都能用 compose 一套文件完成,这也是这个命令搜索量大的原因。
5.2 docker-compose.yml 的写法与参数含义
拿部署 MySQL 8.0 加 Redis 主从举例,目录结构大概这样:
redis-master/ redis.conf redis-slave/ redis.conf docker-compose.ymldocker-compose.yml 文件内容:
services: mysql: image: mysql:8.0 container_name: app-mysql restart: always ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: "root123" MYSQL_DATABASE: "appdb" volumes: - mysql-data:/var/lib/mysql networks: - app-net redis-master: image: redis:7 container_name: redis-master restart: always ports: - "6379:6379" command: ["redis-server", "--appendonly", "yes"] volumes: - redis-master-data:/data networks: - app-net redis-slave: image: redis:7 container_name: redis-slave restart: always ports: - "6380:6379" command: ["redis-server", "--slaveof", "redis-master", "6379"] volumes: - redis-slave-data:/data depends_on: - redis-master networks: - app-net volumes: mysql-data: redis-master-data: redis-slave-data: networks: app-net:逐个解释关键配置。services是顶层的关键字,下面每个一级项是一个服务容器。image指定镜像和版本,别省略版本号。container_name给容器固定名字,这个容器名会被写入 Docker 的 DNS 解析,服务之间用这个名字互相访问。restart: always让容器伴随 Docker 自动拉起,这比在docker run里加--restart更清晰。ports是端口映射,格式和docker run的-p一致。environment对应-e。volumes里的mysql-data:/var/lib/mysql是 named volume 的写法,映射关系是"卷名:容器内路径"。networks把当前服务放进app-net网络。最后在文件底部声明卷和网络的名称,Docker 会创建并管理它们。
Redis 主从的关键在第 20 行处的command参数。它覆盖镜像默认的启动命令,直接用redis-server --slaveof redis-master 6379让从节点指向主节点。因为主从两个容器都部署在 app-net 自定义网络里,redis-master这个名字可以直接被解析成主节点的容器 IP。depends_on只是控制启动顺序,确保从节点在主节点起来之后再启动,减少首次同步的偶发连接失败。
5.3 启动、验证与后续维护命令
在 docker-compose.yml 所在目录执行:
docker compose up -d-d后台运行。Docker 会先拉取缺失镜像,创建网络、卷和容器,然后启动所有服务。检查状态:
docker compose ps看到所有服务状态为 Up 就基本成功。验证 MySQL 连接:
docker compose exec mysql mysql -uroot -proot123 -e "SELECT 1"如果提示找不到 mysql 客户端,直接进容器验证:
docker compose exec mysql bash验证 Redis 主从关系,进主节点写一个键:
docker compose exec redis-master redis-cli set foo bar再进从节点查看:
docker compose exec redis-slave redis-cli get foo如果同步正常,从节点能读到这个键的值,说明主从链路通了。日常维护命令里,改动配置后需要重新创建容器才能生效,执行:
docker compose down docker compose up -ddown会停止并删除容器,但不会删除声明过的数据卷,所以数据不会丢。这是 compose 比手动 docker run 更高效的核心原因:配置即代码,环境可重建且数据可延续。
6. 高频报错排查实录:从启动失败到容器网络不通
6.1 Docker Desktop 启动失败与 virtualization 报错排查链路
Docker Desktop 在 Windows 上启动失败,最容易碰到的就是前面提到过的 "virtualization support wasn't detected" 报错。直接的排查链路是这样:
第一步,检查 BIOS 虚拟化开关。重启进 BIOS,把 Intel VT-x 或 AMD SVM 开启,保存退出。这是最容易被漏掉的步骤,很多新电脑默认并不开启。
第二步,确认 Windows 功能里有没有启用 Hyper-V 和 WSL 子系统。控制面板 - 程序 - 启用或关闭 Windows 功能,勾选这两项,重启。注意这一步在 Windows 11 家庭版上可能看不到 Hyper-V 选项,但 WSL2 方式本身不依赖 Hyper-V,所以你更需要关注 WSL 功能是否正常。
第三步,在 PowerShell 执行wsl --status,确认默认版本是 2 而不是 1。如果默认是 1,执行wsl --set-default-version 2。
第四步,检查 Docker Desktop 设置里的 Use the WSL 2 based engine 是否勾选。勾选后它才能借助 WSL2 内核运行 Linux 容器。
按这个顺序排查,基本能把 Docker Desktop 启动失败的 90% 原因覆盖掉。如果都不行,卸载重装 Docker Desktop,并且卸载时选择清理 WSL 数据,再重装一次,十有八九能解决。
6.2 permission denied while trying to connect to the Docker API
"permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock" 这个报错在 Linux 上特别常见。原因是当前用户没有访问 Docker 服务端 socket 的权限。
Docker 的客户端和服务端通过一个 Unix socket 通信,这个 socket 默认只允许 root 用户访问。你不在 root 下执行docker ps,就会报这个错。很多人第一反应是用 sudo,但每次都 sudo 很麻烦。
正规做法是把当前用户加入 docker 用户组:
sudo usermod -aG docker $USER改完需要重新登录或执行newgrp docker让组权限生效。之后就能直接执行docker ps了,不用 sudo。
需要注意一个安全细节:加入 docker 组的用户等价于拥有 root 权限。因为 docker 组用户可以随意挂载宿主机目录,也就意味着能读取宿主机文件系统。所以在服务器上给用户授予 docker 组权限时要想清楚,不要给不信任的账号随便加。
6.3 服务启动失败与旧版本残留
Linux 上docker命令找不到,或者systemctl start docker失败,很大概率是旧版本残留或者内核模块没加载。
CentOS 7 上如果执行 docker 命令提示命令不存在,先确认有没有装 docker-ce。CentOS 自带的 docker 是旧版本,与新版命令兼容性不好。建议先彻底移除旧版本:
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine再按官方源装新的 docker-ce。安装后如果systemctl start docker还是失败,执行journalctl -u docker看详细日志。比较常见的原因是系统内核版本太老,Docker 需要的内核特性不支持。CentOS 7 的内核版本可能不够新,升级内核或换一台较新的系统是治本方案。
还有一类问题是/etc/docker/daemon.json配置写错,比如 JSON 语法错误、镜像加速源地址拼错。Docker 服务启动时会加载这个文件,语法错误会导致整个服务起不来。排查方式是查看服务状态:
systemctl status docker如果是 daemon.json 导致的,日志里会有明确的语法错误提示,把文件修正重启即可。
6.4 容器网络不通的快速定位
"容器网络不通"是个宽泛的问题,要分场景排查。最常见的是外部访问不了容器内服务,其次是容器之间互相访问不了。
外部访问不了容器内服务,先确认端口映射是否生效。docker ps的 PORTS 列会显示宿主机端口到容器端口的映射,如果显示0.0.0.0:3306->3306/tcp说明映射成功。没看到映射就要检查创建容器时有没有写-p参数。端口没映射,容器外部就完全访问不到。
映射没问题但访问还是不通,就在宿主机上用curl 127.0.0.1:3306测试,能通说明容器没问题,问题在宿主机防火墙或者云厂商安全组。防火墙放行对应端口,安全组添加入方向规则。
容器之间访问不了,先确认两个容器是否在同一网络里。docker inspect 容器名看 Networks 部分,如果不在同一个网络就用docker network connect app-net 容器名把它加进去。在同一个网络还连不上,确定用的是容器名而不是 IP 去访问,并且确认目标端口在容器内确实监听中。
还有一个隐蔽因素:容器内的服务绑定的地址。有些镜像默认绑定 127.0.0.1,比如某些配置默认的 Redis,只监听本机回环地址,外部请求都进不来。这种情况要在容器配置里把监听地址改成 0.0.0.0,再重启容器。
我自己在刚学 Docker 的那段时间,老觉得报错就是自己操作不对,后来发现很多问题其实用docker logs加docker inspect就能定位。排查问题的时候别急着删容器重建,先按日志和状态信息一步步推断,多数时候比盲目重建要省时间。Docker 的知识点多而杂,但如果先把镜像、容器、数据卷、网络和编排这几块搞透,日常使用和问题排查基本都能覆盖到。上手阶段也不用刻意背命令,装一个 MySQL、部署一个 Redis 主从、用 compose 把服务编排起来,这些场景走一遍,比看十遍文档都管用。