1. 先聊为什么Docker成了部署标配
1.1 "在我机器上是好的"这个老大难问题
我上个月帮一个朋友排查Java服务起不来的问题,他在自己笔记本上跑得好好的,一部署到服务器就各种报错。先是不认识JDK版本,后来缺了系统库,装完库又发现Tomcat配置写死了本机路径,折腾了一整天。最后我把整个服务连同运行环境一起塞进Docker容器,五分钟跑通,世界瞬间清净了。
这个场景你是不是也遇到过?项目换个人接手,环境装不装得起来全靠缘分;代码合并到新分支,本地依赖跟CI里的版本对不上;数据库、缓存、消息队列这些中间件,每台机器装一遍配置还不一样。Docker解决的就是这类"环境漂移"问题——它把应用和应用的运行环境一起打包成一个镜像,无论在本地、测试机还是生产服务器上,运行效果完全一致。
Docker的出现让"部署"这件事从"在一台裸机器上手动装环境"变成了"把镜像跑起来"。你不再需要关心目标机器里装的是CentOS还是Ubuntu,装没装特定版本的依赖,只要它有Docker引擎,就能把容器拉起来。这个思路上的转变,是这几年容器化成为主流的最根本原因。
1.2 容器和虚拟机的本质区别
很多人刚接触Docker时会问,这不就是轻量级虚拟机吗?还真不是。虚拟机是在宿主机上用Hypervisor模拟出一整套硬件,然后在虚拟硬件上装完整操作系统,启动以分钟计,占用磁盘以GB计。容器则完全不一样,它直接共享宿主机的操作系统内核,只是在用户空间层面做了隔离。
如果把虚拟机比作搬家,你得把整套房子连同家具电器都搬到新地方;那容器就是标准集装箱,货物装好箱,到哪个港口都能直接吊装走,码头设施是公用的。Docker利用Linux内核的namespace做隔离、cgroup做资源限制,再通过UnionFS做镜像分层,让多个容器共享底层只读文件层,所以镜像几百MB、容器启动秒级、单机跑几十个容器都不是问题。
这带来的直接好处有两层。第一层是资源利用率,同样的机器能跑的实例数量远超虚拟机;第二层是交付效率,打完镜像随便扔到哪台机器上都能跑,部署从"小时级"变成"分钟级"。理解了这层区别,你就明白为什么现在CI/CD流水线里,构建产物普遍是Docker镜像而不是jar包加部署文档。
1.3 Docker到底帮我们省了哪些事
落到实际开发里,Docker至少帮我们解决了四类痛点:
- 环境一致性:镜像在哪构建,容器在哪运行,结果都一样,彻底告别"我本地好的呀"。
- 依赖隔离:不同项目用同一个中间件的不同版本,各跑各的容器互不干扰,不用再为版本冲突头疼。
- 快速交付与回滚:镜像打好标签推到仓库,发布就是换容器版本,出问题秒级回滚到上一个镜像。
- 团队协作成本降低:新人入职,拉下代码和一份compose文件,一条命令起来整套开发环境,不用再写几十页的环境搭建文档。
接下来我从头讲起,把Docker的安装、镜像加速、核心概念,再到MySQL和Redis的实战部署,一条线串下来。这篇文章的目标读者是刚接触Docker、想在真实项目里落地容器化的朋友,我会把每一步的命令和原理都拆开讲,你照着操作即可跑通。
2. 各平台安装Docker全流程:引擎和客户端分开看
Docker的安装在不同操作系统上差别很大。搞清楚"Docker引擎"和"Docker客户端"这两个概念会省很多事:Linux上通常只装引擎加命令行客户端;Windows和macOS上装的是Docker Desktop,它自带图形界面、引擎和命令行工具,本质上是在你的系统里跑一个轻量Linux虚拟机来承载容器。
2.1 Linux:命令行安装最直接
先以Ubuntu 22.04为例,这是目前用的最多的服务器系统。安装Docker引擎官方推荐用apt源,不推荐直接用apt install docker,因为旧版Docker包可能和containerd冲突。标准流程是添加Docker官方GPG密钥和软件源,然后安装。
sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release 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 a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完成后启动服务并设置开机自启:
sudo systemctl enable docker --now sudo systemctl status docker注意最后还装了一个docker-compose-plugin,这是Docker Compose v2的官方插件,后面编排多容器时会用到。CentOS/RHEL系列则用yum仓库安装,思路一样,这里不展开。
很多新人在Linux上装完Docker后执行命令会报permission denied,这是因为当前用户不在docker组里。解决方式是把用户加进docker组:
sudo usermod -aG docker $USER newgrp docker之后不需要sudo就能直接跑docker命令。这里有个安全提醒:docker组等同于root权限,生产环境的服务器要慎重给用户加这个组。
2.2 Windows:Docker Desktop + WSL2的方案
Windows上最省事的方式是安装Docker Desktop,现在主流使用WSL2后端。前置条件有三个:系统是Win10 64位专业版/企业版/教育版或Win11,CPU支持虚拟化且在BIOS里开启了VT-x或AMD-V,内存建议8GB以上。
确认虚拟化是否开启,可以在任务管理器-性能-CPU里看"虚拟化"一栏。没开启的话需要重启进BIOS,在Security或Advanced菜单里找到Intel Virtualization Technology或SVM Mode,设置为Enabled。
然后安装WSL2运行环境,这里给两组命令供选:
wsl --install # 或者单独启用 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart装完重启,再执行wsl --set-default-version 2把默认版本切到WSL2。随后从Docker官网下载Docker Desktop安装包,一路Next即可。安装过程中会提示选择后端,勾选"Use WSL 2 based engine",之后Docker Desktop会自动创建一个用于容器运行的后台发行版。
2.3 macOS:Desktop依然是首选
macOS上同样装Docker Desktop。需要注意的是Apple Silicon芯片(M1/M2/M3)和Intel芯片要下载对应架构的安装包,Docker官网的下载页会自动识别架构。Intel Mac在系统设置里可以把内存分配到4GB以上,Apple Silicon机型默认配置就很够用。
装完Docker Desktop后,在顶栏的鲸鱼图标里能看到资源占用情况。如果发现容器启动慢,优先检查内存分配是否充足。在Settings -> Resources里可以调整CPU和内存配额,我一般给容器分配系统内存的一半。
2.4 安装完怎么验证环境
无论哪个平台,装完后打开终端执行两条命令验证:
docker version docker infodocker version能看到客户端和引擎的版本号,docker info能看到存储驱动、网络插件、镜像加速器配置等运行时信息。如果引擎没启动,会报"Cannot connect to the Docker daemon",这时候去把Docker Desktop打开,或检查Linux上dockerd进程是否存在。
踩坑最多的就是Windows下Docker Desktop能装但起不来,九成原因是虚拟化没开或WSL2没正确启用。打开PowerShell执行wsl --status,如果显示内核版本过旧,执行wsl --update更新内核即可。还有一个细节:如果你电脑上装过VirtualBox或VMware,它们和Hyper-V会冲突,需要关掉第三方虚拟化软件才能正常使用WSL2后端。
3. 镜像下载慢的根源和加速方案配置
3.1 为什么docker pull总是慢得让人抓狂
刚装好Docker的人,第一件事肯定是docker pull hello-world试试水,然后发现进度条像蜗牛。Docker默认从Docker Hub官方仓库拉取镜像,这个仓库的服务器节点离我们远、链路长,加上网络环境波动,实际下载速度经常只有几十KB/s。一个几百MB的镜像,卡上半小时都是常事。
这个问题的原因不是你的带宽不够,而是从你的网络到Docker Hub之间的链路不稳定。有人会想到改hosts文件,把registry-1.docker.io解析到更快IP,实测效果不稳定,因为Docker Hub背后有CDN,IP会动态变化。正确且官方支持的方案是配置镜像加速器(registry mirror)。
镜像加速器的原理不复杂:它是一个Docker Hub的缓存代理,Docker在拉取镜像时会先访问你配置的加速器地址,如果加速器缓存里有这个镜像就直接返回,没有的话它会去Docker Hub拉取后缓存下来再返回给你。由于加速器的服务器和被拉取方之间的网络链路快,整体体验会好非常多。
3.2 在daemon.json里配置registry-mirrors
Docker引擎的配置集中在daemon.json,默认路径是Linux的/etc/docker/daemon.json,Windows和macOS的Docker Desktop可以在Settings -> Docker Engine里直接编辑界面下的JSON配置。
先把基本的加速配置写进去。Linux上执行:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://hub-mirror.c.163.com" ] } EOF sudo systemctl daemon-reload sudo systemctl restart dockerDocker Desktop用户则在Docker Engine设置页面把JSON内容改掉后点Apply & Restart即可。这里列的是公共镜像加速地址,可以多配几个,Docker会按顺序尝试,某个失效时自动切换。
3.3 更稳的方案:用云厂商的专属加速器
公共加速器经常变更,时好时坏。我更推荐用云厂商提供的专属加速地址,操作路径是:注册阿里云账号,进入控制台,搜索"容器镜像服务",在"镜像加速器"页面,你会看到一个专属地址,形如https://xxxx.mirror.aliyuncs.com。这个地址和你的账号绑定,使用中不会和公共用户争抢带宽,稳定性要高很多。
拿到地址后把它填进daemon.json的registry-mirrors列表里,重启Docker即可。腾讯云、华为云、百度云也有类似服务,在各自控制台搜"容器镜像"或"镜像加速器"就能找到。如果你所在的公司有内部镜像仓库,比如搭建了Harbor,也可以把Harbor作为镜像仓库源,不过那属于企业级环境的话题,这里先不展开。
顺便说一句"镜像仓库"的概念。Docker Hub是最知名的公共仓库,但公共仓库不等于唯一仓库。一个完整镜像地址的格式是仓库地址/命名空间/镜像名:标签,比如registry.cn-hangzhou.aliyuncs.com/xxx/myapp:v1。理解了地址结构,你就知道从哪个仓库拉取完全由地址前缀决定,因此加速器本质上是让你在拉取Docker Hub镜像时走一条更快的路。
3.4 配置生效后怎么确认
配置完重启Docker后,执行docker info,在输出里找到Registry Mirrors这一节,能看到你配置的加速器地址列表,说明配置生效了。然后随便拉一个镜像试试:
docker pull nginx:alpine如果速度还是慢,先确认daemon.json格式是不是合法JSON,最容易犯的错误是多个配置项之间缺逗号。再确认你重启的不是某个容器而是整个Docker引擎。实在不行就多配几个加速地址,因为在网络环境复杂的情况下,单一加速器确实可能抽风。
3.5 几个改善拉取体验的小习惯
除了靠加速器,平时的使用习惯也能少踩很多坑:
- 不要无脑用latest标签。latest指向的版本会漂移,且体积通常不小。固定到具体版本号,比如
mysql:8.0,可复现性更好。 - 优先选体积小的基础镜像。同一个软件的镜像,基于Alpine的版本比Debian版本小一半以上,比如
redis:7.0-alpine。生产环境镜像越小,拉取和启动越快,攻击面也越小。 - 利用多阶段构建。在Dockerfile里先用完整版编译,再把产物复制到精简运行镜像中,最终镜像只有运行所需内容。
- 做好镜像分发。内网环境或跨机器传递镜像时,用
docker save和docker load打包本地镜像,比现场pull快得多。
docker save myapp:v1 | gzip > myapp.tar.gz docker load -i myapp.tar.gz4. 玩转Docker必须搞懂的四个核心概念
在进入MySQL和Redis实战之前,有必要把Docker的四个核心概念讲透。刚上手的人最容易在这上面绕晕,概念通了,后面所有命令不过是参数组合。
4.1 镜像和容器:软件模板与运行实例
镜像是一个只读的模板,里面包含了应用代码、运行时、系统库、依赖配置等一切内容。容器则是镜像运行后产生的实例,它有自己的文件系统读写层、进程、网络命名空间。用面向对象的话说,镜像是类,容器是对象;用日常生活的话说,镜像是菜谱,容器是照着菜谱做出来的一盘菜。
Dockerfile是构建镜像的脚本,一个最简单的例子:
FROM alpine:3.18 RUN apk add --no-cache curl CMD ["curl", "-s", "https://example.com"]构建命令是docker build -t my-curl:v1 .,然后就能docker run my-curl:v1运行。理解镜像分层是进阶的关键:镜像由多层只读层叠加而成,每一层对应Dockerfile里的一条指令。构建时缓存命中的层直接复用,所以把不变的分层放前面、变化频繁的分层放后面,能大幅加快构建速度。
4.2 数据卷:容器删了数据不能丢
容器是临时性的,删掉容器后,容器内部写入的文件全部消失。这是新手最容易踩的坑之一:往容器里放了配置文件、数据库文件,容器一重建,什么都没了。
解决这个问题的标准方案是数据卷。Docker支持两种挂载方式:
- 绑定挂载:直接把宿主机目录映射进容器,比如
-v /data/mysql:/var/lib/mysql。 - 具名卷:由Docker管理的卷存储,指定
-v mysql-data:/var/lib/mysql,实际数据存放在Docker数据目录下。
我个人的习惯是:数据库数据用绑定挂载,方便备份和迁移;应用生成的临时文件用具名卷,避免和宿主机文件系统耦合。还有一点,挂载目录的权限问题很常见,容器内进程以指定用户运行时,可能没有宿主挂载目录的写权限,这时候需要先给目录正确的属主和权限,否则会出现奇奇怪怪的权限报错。
4.3 网络模式:容器之间怎么通信
Docker的网络模式有bridge、host、none、container四种,默认是bridge。bridge模式下,Docker引擎会创建一个虚拟网桥,容器通过虚拟网卡接入这个网桥,容器之间通过内网IP通信,通过-p 宿主机端口:容器端口把容器端口暴露到宿主机。host模式直接共享宿主机网络栈,端口不用映射但少了隔离性。
在同一个bridge网络里的容器,可以通过容器名互相访问,不需要知道对方IP。这一点非常关键,比如应用容器要连数据库容器,只需要在连接串里写mysql:3306,Docker内置的DNS会把mysql解析成对应容器的IP。因此实战中我都是先docker network create app-net创建自定义网络,再把所有相关容器都放进这个网络里,互相用服务名访问。
4.4 docker run常用参数速查
容器启动的核心命令是docker run,我把常用参数整理成一张表,后面实战会大量用到:
| 参数 | 作用 | 示例 |
|---|---|---|
| -d | 后台运行 | docker run -d nginx |
| --name | 指定容器名 | --name web1 |
| -p | 端口映射 | -p 8080:80 |
| -v | 挂载数据卷 | -v /data:/var/lib/mysql |
| -e | 传入环境变量 | -e MYSQL_ROOT_PASSWORD=xxx |
| --restart | 设置重启策略 | --restart always |
| --network | 指定网络 | --network app-net |
| --rm | 停止后自动删除 | 调试时常用 |
| -it | 交互式终端 | -it bash |
--restart always在实战中几乎是必须的,主机重启或Docker重启后容器会自动拉起,少了很多人工介入。它是Docker提供的最基础的自愈能力,还没有别的高级编排工具时,这个参数能保底。
5. 实战一:Docker安装MySQL 8.0并完成初始化配置
MySQL是Docker化最频繁的中间件之一,这个实战会覆盖镜像选择、容器启动、数据持久化、字符集时区配置、远程连接几个关键环节。
5.1 选镜像:版本固定到8.0
先拉取镜像:
docker pull mysql:8.0指定8.0而不是latest,是为了镜像可复现。MySQL的latest标签现在已经指向8.x,但等到9.x发布后latest就会漂移,绑定8.0则能保证任何时候拉取都是这个系列的最新补丁版本。
5.2 启动容器并挂载数据卷
执行如下命令启动一个MySQL 8.0容器:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD='Root@123456' \ -e TZ=Asia/Shanghai \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v /data/mysql/logs:/var/log/mysql \ --restart always \ mysql:8.0逐个参数解释一下。-e MYSQL_ROOT_PASSWORD是MySQL官方镜像要求的环境变量,首次初始化数据库时会把root密码设置成这个值;-e TZ=Asia/Shanghai设置容器时区;三个-v挂载分别对应数据文件目录、自定义配置目录、日志目录,这样容器怎么删数据都还在。--restart always保证服务器重启后数据库自动恢复。
启动后查看容器的初始化日志,看到"ready for connections"字样就说明成功:
docker logs -f mysql8这里我说一下为什么挂载配置目录而非直接改容器内配置文件。官方镜像的入口脚本会读取/etc/mysql/conf.d下的所有.cnf文件来补充配置,把自定义配置放这个目录里,既不影响镜像默认配置,又能灵活覆盖参数。直接改容器内的文件一旦重建容器配置就丢了,属于新手时期踩过的坑。
5.3 初始化配置:字符集、时区和远程连接
MySQL 8.0默认的字符集是utf8mb4吗?官方默认配置其实是latin1选项之一,实际使用中经常出现中文乱码问题。更稳妥的做法是在挂载目录/data/mysql/conf下新建一个custom.cnf:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-time-zone=+08:00 lower_case_table_names=1 max_connections=500修改配置后需要重启容器生效:
docker restart mysql8进入容器验证配置:
docker exec -it mysql8 mysql -uroot -p'Root@123456'在MySQL命令行里执行:
SHOW VARIABLES LIKE 'character%'; SHOW VARIABLES LIKE 'time_zone';字符集相关变量都显示utf8mb4,时区显示+08:00,说明配置已经生效。
接下来要解决远程连接问题。我实测过很多次,容器端口映射都做好了,宿主机用工具连不上,根本原因在于镜像初始化时root用户默认只允许localhost登录。所以需要创建一个允许任意主机访问的专用账号:
CREATE USER 'app'@'%' IDENTIFIED BY 'App@123456'; GRANT ALL PRIVILEGES ON app_db.* TO 'app'@'%'; FLUSH PRIVILEGES;之后在宿主机上执行mysql -h127.0.0.1 -uapp -p'App@123456'就能连上了。这里明确一点:正经项目不要开放root远程访问,创建最小权限的业务账号才是底线。
5.4 踩坑记录:客户端认证插件不兼容
MySQL 8.0默认的认证插件是caching_sha2_password,安全性很强,但部分老客户端兼容性差。我遇到过Navicat 11连接时报"Authentication plugin 'caching_sha2_password' cannot be loaded",原因就是客户端不支持新认证插件。
解决方式有两种。第一种是把用户的认证插件改回mysql_native_password:
ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'App@123456'; FLUSH PRIVILEGES;第二种是更新客户端到支持caching_sha2_password的版本。我的建议是优先升级客户端,实在升级不了再用第一种方式过渡。顺便记住:容器内修改root密码后,之前创建的账号不受影响;如果忘记root密码,可以通过跳过权限表的方式进容器重置,网上教程很多,这里不展开。
生产环境里,我还会在custom.cnf里加一行skip-name-resolve,跳过反向DNS解析,能明显减少登录耗时。代价是grant语句里就不能用主机名,只能用IP或%,看你的网络情况权衡。
6. 实战二:Docker搭建Redis主从复制
Redis主从是缓存高可用的基础形态,用Docker搭建能省去在一台机器上配置多实例的麻烦,几分钟就能跑起来一套主从环境。
6.1 主从架构的基本思路
Redis主从复制的核心逻辑是:一个主节点负责写,一个或多个从节点同步主节点的数据,负责读或作为冷备。当主节点宕机,可以把某个从节点提升为主节点继续服务。在Docker里实现这个架构,关键是让主从节点在同一个自定义网络里,互相通过容器名通信。
先创建网络并拉镜像:
docker network create redis-net docker pull redis:7.06.2 编写主从配置文件
不建议一条命令传一堆参数,配置一多就乱套。我的做法是把配置文件放在宿主机固定目录,通过挂载方式传入容器。先建主节点配置/opt/redis/master/redis.conf:
port 6379 dir /data appendonly yes appendfsync everysec再建从节点配置/opt/redis/slave/redis.conf:
port 6379 dir /data appendonly yes appendfsync everysec replicaof redis-master 6379这里的关键一行是replicaof redis-master 6379,redis-master是随后启动的主节点容器名。Redis 5.0起官方推荐用replicaof替代旧的slaveof,虽然旧命令还能用,新项目就别再用旧写法了。dir /data配合数据卷挂载,让持久化的AOF文件落在宿主机,容器删了数据不丢。
6.3 启动主从容器并验证
启动主节点:
docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /opt/redis/master/redis.conf:/etc/redis/redis.conf \ -v /opt/redis/master/data:/data \ redis:7.0 redis-server /etc/redis/redis.conf启动从节点:
docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /opt/redis/slave/redis.conf:/etc/redis/redis.conf \ -v /opt/redis/slave/data:/data \ redis:7.0 redis-server /etc/redis/redis.conf注意两个地方:从节点的宿主机端口映射成6380,避免和本机6379冲突;容器挂载的配置文件路径里,redis.conf是宿主机路径,/etc/redis/redis.conf是容器内路径,启动命令redis-server /etc/redis/redis.conf明确告诉Redis加载哪个配置。
验证主从关系是否建立,进入从节点执行:
docker exec -it redis-slave redis-cli info replication输出中role:slave、master_host:redis-master、master_link_status:up,说明主从连接正常,数据正在同步。
再做一个写入验证。在主节点写一个key,去从节点读:
docker exec -it redis-master redis-cli set foo bar docker exec -it redis-slave redis-cli get foo能读到"bar",整个主从链路就通了。到这里你已经拥有一个生产可用的Redis主从环境,比在物理机上手工编译配置Redis多实例不知道快了多少。
6.4 关于密码、持久化和哨兵的补充
上面配置里我没有设置密码,是考虑到在隔离的docker网络里演示更简洁。真实生产环境至少要加两行:主节点设置requirepass,从节点设置masterauth,否则主从同步时从节点无法通过主节点的密码认证。如果从节点要支持外部客户端访问,还需要protected-mode yes并显式设置bind 0.0.0.0之类,注意这要在密码保护的前提下开放,否则有被外部扫描爆破的风险,这是我见过最多的Redis安全事故原因。
持久化方面,appendonly yes已经开启了AOF,配合dir /data和宿主机挂载,能做到秒级数据丢失容忍度。如果要更高可靠性,可以再开RDB快照,两者可以共存。
这套主从架构在单机容器场景够用,但还缺少故障自动切换能力。生产环境建议再往上加Redis Sentinel哨兵做自动故障转移,或者直接用Redis Cluster集群模式。这套用Docker Compose部署哨兵的完整方案以后可以专门写一篇,这里先把主从的地基打牢。
7. 多容器编排:用Docker Compose统一管理MySQL和Redis
一条条docker run命令在容器少的时候还行,一旦容器多起来,问题就暴露了:参数容易漏、网络容易配错、其他人接手不知道整个环境怎么搭。Docker Compose就是来解决这个问题的,它用一份YAML文件声明整个应用包含哪些容器、各自怎么启动、如何互联,一条命令拉起全部。
7.1 Compose到底解决了什么
Compose的价值是"声明式编排"。你把容器定义从命令变成了配置文件,这份文件提交到Git仓库里,任何人拉下来执行docker compose up -d,就能得到一套完全一致的环境。它天然是文档、是配置、是可执行的部署脚本,三合一。
Docker Desktop和装了docker-compose-plugin的Linux环境都自带Compose v2,直接使用docker compose命令即可。老项目里可能还在用docker-compose这个独立二进制,语法基本兼容,新环境直接用v2不用纠结。
7.2 把前面的MySQL和Redis编排成一份compse文件
我把前面两节的MySQL、Redis主从写进同一个docker-compose.yml:
services: mysql: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: Root@123456 TZ: Asia/Shanghai ports: - "3306:3306" volumes: - /data/mysql/data:/var/lib/mysql - /data/mysql/conf:/etc/mysql/conf.d - /data/mysql/logs:/var/log/mysql networks: - app-net redis-master: image: redis:7.0 container_name: redis-master restart: always command: ["redis-server", "/etc/redis/redis.conf"] ports: - "6379:6379" volumes: - /opt/redis/master/redis.conf:/etc/redis/redis.conf - /opt/redis/master/data:/data networks: - app-net redis-slave: image: redis:7.0 container_name: redis-slave restart: always command: ["redis-server", "/etc/redis/redis.conf"] depends_on: - redis-master ports: - "6380:6379" volumes: - /opt/redis/slave/redis.conf:/etc/redis/redis.conf - /opt/redis/slave/data:/data networks: - app-net networks: app-net: driver: bridge在这个文件里,原来的--name变成container_name,-e变成environment,-v变成volumes,-p变成ports,--network由底部的networks定义,每个服务用networks字段声明归属。depends_on表示从节点等主节点起来了再启动,避免因主节点未就绪导致从节点首次同步失败。
启动命令:
docker compose up -d查看状态:
docker compose ps查看日志:
docker compose logs -f mysql进入容器执行命令:
docker compose exec mysql mysql -uroot -p停止整个环境:
docker compose down这里有一个我必须反复提醒的细节:down命令默认会删除容器和网络,但不会删除数据卷,数据还在。如果手贱加了-v参数,也就是docker compose down -v,会把挂载的具名卷一并删除,虽然我例子里的绑定挂载不会受影响,但这个命令对使用具名卷的配置来说是毁灭性的,执行前务必三思。
7.3 Compose里我推荐养成的几个习惯
第一,用项目目录区分环境。compose文件在哪个目录执行,默认项目名就是目录名,不同环境的服务因此天然隔离。如果你想给项目起个新名字,在文件顶部加name: app_stack即可。
第二,敏感信息不要硬编码。上面的密码我直接写在YAML里是为了演示方便,真实项目建议用环境变量替换:${MYSQL_ROOT_PASSWORD},再在同目录的.env文件里定义变量值,并把这个文件加入.gitignore。
第三,善用override文件。Compose支持同名override文件自动合并配置,比如docker-compose.override.yml里可以只写开发环境的端口和调试参数,生产环境用docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d覆盖运行,不需要维护两份完整配置。
第四,镜像升级要"先拉后起"。更新依赖镜像时,先执行docker compose pull拉取新镜像,再docker compose up -d重建容器。这样即使新版本起不来,旧容器也能继续提供服务,缩短不可用时间。
7.4 实际项目里的一点点编排心得
用Docker这几年,我最深的体会是:容器化的收益不是装上那一刻体现的,而是在后续的每次部署、每台新机器、每个新人加入时体现的。一套写好的compose文件放到Git仓库,新人克隆下来一条命令就能得到完整开发环境,这比任何环境搭建文档都可靠。
再说一个我常被问到的点:容器里能不能直接用IP地址互相访问?技术上可以,但不推荐。IP会随容器重建而变化,用服务名才是稳定解。Compose网络里服务名就是DNS名,应用配置里连数据库,直接写mysql:3306,连Redis主从,直接写redis-master:6379,容器怎么重建都不影响配置。
最后分享一个我实测后一直保留的习惯:给每个容器都设置restart: always,同时把时区TZ=Asia/Shanghai作为标准环境变量写进每个服务。这样服务器断电重启后,整套环境自动恢复,日志时间也永远和本地对齐。这类小习惯看似不起眼,真到线上出问题时能省去大量排查时间。Docker的学习曲线其实不算陡,把这套安装、加速、核心概念、中间件实战、Compose编排走一遍,日常开发部署的大多数场景你都能从容应对了。