1. Docker到底解决了什么问题
先用大白话把核心讲清楚:Docker是一款开源的容器化平台,它把你的应用连同运行环境一起打包成一个标准化的“镜像”,然后通过“容器”这个隔离环境跑起来。以前你最头疼的“在我电脑上明明能跑,怎么换个环境就崩了”,在Docker里基本不存在。
我最早被Docker吸引,就是因为一次线上事故。本地开发的时候接口全通,结果测试环境一部署,Redis版本不一样、MySQL字符集少了配置、Nginx的路径不对,整个联调直接卡死。后来团队引入Docker,所有依赖全部写进镜像和编排文件里,环境问题几乎一夜之间消失。对个人开发者来说,Docker还有另一层价值:想折腾新软件(比如GitLab、OpenObserve、Diskover这类重型服务)不用再担心污染自己的电脑系统,装完就删,干净利落。
说几个最典型的应用场景,你先感受一下它到底能干嘛:
- 环境一致性:把应用、依赖、配置文件全部锁进镜像,任何机器上跑出来的行为一致。
- 隔离性:容器之间互不干扰。一个容器是Nginx,一个是MySQL,一个是Python爬虫,哪怕依赖库互相冲突,各跑各的也没事。
- 秒级启动:相比传统虚拟机分钟级启动,容器因为共享宿主机内核,启动通常以秒计算。
- 快速伸缩:需要3个实例就启动3个容器,负载均衡层面分一下流量,不需要来回改系统配置。
适合谁来学?如果你正在学后端开发、做个人项目部署、或者在公司里负责搭建公共组件(数据库、缓存、对象存储),都不该跳过Docker。就算你完全没接触过虚拟化,只要照着下面流程走一遍,也能顺利把环境跑起来。
注意一个概念区分:Docker不等于虚拟机。虚拟机走的是硬件级虚拟化,启动要模拟整个操作系统;容器走的是操作系统级虚拟化,共享宿主机内核,只隔离用户空间。所以容器更轻、更快,但内核版本要求也更敏感。
2. 安装Docker的完整实操
2.1 Windows下用Docker Desktop安装
Windows用户最省事的方式是装Docker Desktop。这玩意儿装上之后自带图形界面,容器状态、日志、挂载目录都可视化,对新手非常友好。安装过程其实没什么难度,但有三个前置条件必须先确认:
- Windows 10 64位(2004版本以上)或Windows 11。
- 必须在BIOS里开启CPU虚拟化(Intel的VT-x或AMD的SVM)。
- 建议开启WSL 2(Windows Subsystem for Linux),Docker Desktop默认用它作为后端引擎,性能比老的Hyper-V方案好很多,而且内存占用更可控。
第一步检查虚拟化是否开启。打开任务管理器,切到“性能”标签页,看底部有没有“虚拟化:已启用”字样。如果显示未启用,重启电脑进BIOS,在CPU Configuration或Security菜单里找到Intel Virtualization Technology(或者AMD SVM),设为Enabled,保存重启。
第二步开启WSL 2。以管理员身份打开PowerShell,执行:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart然后重启系统。重启后用管理员身份打开PowerShell,设置WSL 2为默认版本:
wsl --set-default-version 2如果提示需要下载内核更新包,直接根据提示下载安装即可。WSL 2的官方内核更新包在微软文档里能找到,每次版本大更新后最好同步升级一下。
常见报错“Docker Desktop failed to start because virtualisation support wasn't detected”,九成情况就是虚拟化没开,或者开了没生效。有个细节:有些电脑的BIOS里叫“SVM Mode”,有些叫“VT-d”,带Hyper-V的机器还要检查Windows功能里的“虚拟机平台”是否勾选。如果你CPU虚拟化明明开了,仍然报这个错,就去“启用或关闭Windows功能”里确认“Virtual Machine Platform”和“适用于Linux的Windows子系统”两个都打勾。
第三步下载Docker Desktop安装包。从Docker官网的Downloads页面拿稳定版,安装过程一路Next即可。装完启动,首次启动会要求接受协议并登录Docker账号,建议注册一个,后续拉取公有镜像时配额更大。
安装路径默认在C盘,如果你不想占用系统盘,可以安装时选自定义路径,或者在安装完成后,通过设置里的“Resources”调整镜像存储位置到D盘。对个人开发机来说,镜像会越拉越多,建议一开始就把数据目录放到空间充足的磁盘。
2.2 Linux下用命令行安装
Linux服务器没有图形界面,也没必要装Docker Desktop,直接用命令行就行。以Ubuntu为例(这个最常用),安装前先把老版本清理干净:
sudo apt-get remove docker docker-engine docker.io containerd runc然后更新apt索引,并安装依赖工具:
sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release -y添加Docker官方GPG密钥和软件源(这里用清华或阿里的镜像站比官方源快很多,尤其在国内服务器上):
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \ $(lsb_release -cs) 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 -y安装完成后,用sudo docker version确认客户端和服务端都正常。此刻还需要处理一个使用上的常见问题:普通用户执行docker命令会报权限错误。这是因为docker客户端需要访问/var/run/docker.sock,这个socket文件默认属于root组。解决办法是把当前用户加入docker组:
sudo usermod -aG docker $USER newgrp docker之后就不需要每句命令都加sudo了。注意,加了docker组的用户等于拿到root权限,生产环境务必谨慎管理该组成员。如果你真的遇到“permission denied while trying to connect to the Docker daemon socket”这类错误,先检查就是不是这一步没做。
再说一下CentOS环境。CentOS 7自带的内核版本偏低,Docker要求的内核特性可能出现兼容问题,尤其老版本Docker对cgroup v2支持不完整。建议先把内核升级到4.18以上(或者直接迁移到CentOS 8+),再按官网文档安装。很多人在CentOS 7上装新Docker失败,最后发现是yum源里默认的containerd版本太老,优先手动指定版本安装就好。
2.3 Docker服务启动失败的处理
Linux装好Docker后,正常启动命令是:
sudo systemctl start docker sudo systemctl enable docker如果你执行启动后,服务状态显示failed,先别急着删了重装,按顺序排查:
- 查看具体错误日志:
journalctl -u docker --no-pager | tail -50 - 最常见的原因是iptables规则冲突:如果你装了Firewalld且配置不当,Docker的网络规则会加载失败。此时停掉Firewalld试试,或者在Firewalld里放行Docker所需的网段。
- 其次是存储驱动问题:默认数据库目录
/var/lib/docker所在的文件系统不支持overlay2,可以尝试改成vfs驱动(不推荐,性能差),但最好还是把docker数据目录迁移到ext4或xfs分区上。 - selinux也可能捣乱:检查
/etc/selinux/config,如果当前是 enforcing,先临时设为setenforce 0测试。
我踩过的一个典型坑是装完Docker后发现docker ps报“Cannot connect to the Docker daemon at unix:///var/run/docker.sock”。当时第一反应觉得是服务没起,一查服务其实正常,问题出在DOCKER_HOST环境变量被某个脚本改了,指向了一个不存在的地址。所以遇到连接问题,先echo $DOCKER_HOST检查是否存在残留配置。类似的问题也可能发生在jenkins等CI工具里,构建脚本继承了被污染的变量,就会报cannot run program "docker": createprocess error=2,那个是IDE或CI环境找不到docker客户端路径,本质上是PATH变量不对,把Docker安装目录加入系统PATH即可。
2.4 镜像下载速度慢怎么解决
镜像仓库在国外,国内直连拉镜像经常卡到怀疑人生,尤其是比较大的基础镜像如pytorch/pytorch、mysql:8这种。解决办法是配镜像加速器。
国内可用的加速器地址包括阿里云(需要登录后从容器镜像服务控制台获取个人专属加速地址)、腾讯云、中科大、网易等。配置方式:
对Docker Desktop用户,打开设置,找到Docker Engine,在JSON配置里加一条:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.nju.edu.cn" ] }保存并重启Docker Desktop。Linux用户则编辑/etc/docker/daemon.json:
{ "registry-mirrors": ["https://docker.m.daocloud.io", "https://docker.nju.edu.cn"] }然后重启docker服务:
sudo systemctl daemon-reload sudo systemctl restart docker加速器属于社区资源,可用性和速度会随时间变化。建议多配几个,万一某个失效,会自动尝试下一个。另外,docker pull时尽量拉带明确版本号的tag,避免每次
latest都有不确定性。
3. 第一台容器:能跑起来才算入门
3.1 跑一个Nginx容器练手
理论讲再多都不如实际操作一把。最简单的入门练习是跑一个Nginx,因为它无需额外依赖,启动后立刻就能通过浏览器访问。
先拉镜像:
docker pull nginx:alpine用alpine版本是因为体积小(比官方版小一半),也能让你体会到容器“精简”的价值。启动:
docker run -d --name my-nginx -p 8080:80 nginx:alpine这条命令解释一下:
-d:后台运行(detached)。--name my-nginx:给容器起个名字,后续操作都用这个名字。-p 8080:80:端口映射,把宿主机的8080端口映射到容器内部的80端口。因为容器内有自己的网络栈,不开映射的话外部访问不到。nginx:alpine:使用哪个镜像启动容器。
执行完后访问http://localhost:8080,看到Nginx欢迎页说明容器跑通了。
然后把容器停掉、删掉:
docker stop my-nginx docker rm my-nginx这一停一删,整个过程只是几秒的事。你体会一下跟传统安装Nginx、配置进程管理的区别:没有污染系统、没有注册服务、没有手动处理端口冲突,想扔就扔,想再起一个就是一条命令的事。这就是容器化最核心的魅力。
3.2 理解镜像、容器和仓库的关系
上手之后必须把三个核心概念理清楚,否则后面装MySQL、Redis你会觉得命令就是死记硬背。
- 镜像(Image):一个只读模板,里面打包了应用程序、代码、运行时、系统库和配置。类似于菜谱,做出来的菜口味完全一致,因为每一步都已经定死了。
- 容器(Container):镜像的运行实例,是一个隔离开的进程环境。容器可以启动、停止、删除,对容器的任何修改不影响镜像本身。就像按照菜谱做出来的菜,吃掉了也不影响菜谱。
- 仓库(Repository):存放镜像的地方。Docker Hub是最大的公共仓库,往里拉镜像就是
docker pull,推镜像就是docker push。公司内部通常会搭私有仓库,避免所有镜像都依赖公网。
用一句话串联:从仓库拉镜像,用镜像创建容器,容器跑起来就是你的应用实例。
理解这个模型后,你会知道为什么容器适合“任意环境一致运行”:镜像就是环境本身,环境跟着镜像走,跑到哪都一样。
3.3 数据持久化:别让容器一删数据全没
容器是临时性的,删掉容器等于把容器里所有写入的文件一起删掉。这对数据库这种需要持久化数据的应用来说是致命的。解决方案是挂载卷(Volume)。
挂载卷就是把宿主机的一个目录“映射”到容器内部。容器写文件到挂载路径,实际写的是宿主机目录;删掉容器重新创建,只要挂载同一个宿主机目录,数据依然还在。
举个实际场景:你部署了一个MySQL容器,不挂卷直接docker run -d mysql:8.0,跑了一周数据都在。某天手滑执行docker rm mysql,然后重新拉起一个mysql容器,你大概率会发现原来的库表全没了。这就是最经典的“容器数据丢光”案例。所以从第一天使用Docker开始,但凡涉及有状态服务(数据库、缓存、消息队列、文件存储),一律挂卷。
挂载命令的格式是-v 宿主机目录:容器目录:
docker run -d \ --name mysql-demo \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=yourpassword \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这里/data/mysql是宿主机目录(不存在会自动创建),/var/lib/mysql是MySQL在容器内存储数据的默认目录。以后无论容器怎么删除重建,只要还挂/data/mysql,数据都在。
关于挂载还有一个高级用法叫具名卷。相比路径挂载,具名卷由Docker管理,不暴露具体宿主机路径:
docker volume create mysql-data docker run -d -v mysql-data:/var/lib/mysql mysql:8.0具名卷的好处是跨主机迁移更灵活,但新手直接用路径挂载更直观,能看到宿主机上的实际文件,心理上也更踏实。两条路都行,核心是记住一句话:有状态数据必须挂卷。
4. 实战一:MySQL 8.0的容器化部署
4.1 设置环境变量和初始化参数
MySQL是后端开发躲不开的组件。用Docker装MySQL8.0比传统的apt/yum安装麻烦一点,但胜在干净、可复现。完整的启动命令长这样:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=Root@123456 \ -e TZ=Asia/Shanghai \ -e MYSQL_DATABASE=app_db \ -e MYSQL_USER=app_user \ -e MYSQL_PASSWORD=App@123456 \ -v /data/mysql8/conf:/etc/mysql/conf.d \ -v /data/mysql8/data:/var/lib/mysql \ --restart=always \ mysql:8.0环境变量的含义:
MYSQL_ROOT_PASSWORD:root用户的密码,必填,否则容器初始化会失败。MYSQL_DATABASE:自动创建的空数据库。MYSQL_USER和MYSQL_PASSWORD:创建一个普通用户,并对MYSQL_DATABASE授权。TZ:时区,不设的话MySQL默认UTC时间,日志和NOW()函数会有8小时偏移。--restart=always:容器异常退出或服务器重启后自动拉起MySQL。生产环境必须加,否则服务器重启后你的应用依赖数据库没起来,连锁故障。
如果你需要自定义MySQL配置,比如调整max_connections或innodb_buffer_pool_size,在宿主机/data/mysql8/conf下新建一个my.cnf文件,然后重启容器即可。注意,容器内的默认配置优先级低于/etc/mysql/conf.d下的自定义配置,所以放这里安全。
有个经验:如果docker run后MySQL容器一直重启(状态为Restarting),用docker logs mysql8看日志。常见原因是权限问题:宿主机挂载目录的属主跟容器内MySQL进程的uid不一致。传统解决办法是chown -R 999:999 /data/mysql8(MySQL官方镜像里运行用户uid是999),简单粗暴但有效。如果你用的是比较新的Docker版本,加参数--user 999:999也一样能解决。
4.2 从客户端连接容器的MySQL
容器跑起来后,最难理解的是“怎么连”。很多初学者以为数据库在容器里,再用localhost:3306连接就应该通。其实这里有个微妙点:本机连接容器需要考虑连接的是宿主机端口还是容器网络。
- 同一台机器上,用宿主机IP或
127.0.0.1:3306连接,是经过端口映射访问容器内的MySQL。 - 从另一台机器连接,使用宿主机的IP:3306。
- 从容器内部连接,用容器IP或容器名。
最常见的外网访问方式就是通过宿主机IP加映射端口。用客户端工具(Navicat、DataGrip、DBeaver)连127.0.0.1:3306,账号用刚才设置的app_user,密码App@123456,就能连上。
如果你连不上,按顺序排查:
- 宿主机的3306端口是否被占:
netstat -anp | grep 3306 - MySQL容器是否在运行:
docker ps - 防火墙是否放行了3306:
firewall-cmd --list-ports(CentOS)或ufw status(Ubuntu) - MySQL是否开启了远程访问权限:镜像默认
mysql_native_password插件和账号host可能限定。
遇到“Host is not allowed to connect to this MySQL server”,说明账号权限host不对。在容器内执行:
docker exec -it mysql8 mysql -uroot -pRoot@123456 GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%' IDENTIFIED BY 'App@123456'; FLUSH PRIVILEGES;%表示允许任意主机连接。实操下来,这是个人开发者部署MySQL时遇到最多的权限问题,提前把host设为%能省掉后续非常多的麻烦。
4.3 字符集和排序规则的细节
用Docker部署MySQL8.0,有一个必须调整的细节:默认字符集。
MySQL 8.0镜像的默认字符集是utf8mb4,排序规则是utf8mb4_0900_ai_ci。大部分场景没问题,但如果你要兼容老项目(从MySQL 5.7迁移过来的),排序规则里utf8mb4_general_ci会更稳妥。在挂载配置目录下新建my.cnf:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_general_ci default-time-zone=+08:00保存后重启容器:
docker restart mysql8需要注意的是,修改字符集只对新建的表生效,已建表的字符集和排序规则需要单独ALTER。所以最好在初始化数据之前就把配置写好,否则后面迁移数据又要折腾半天。
实操经验:如果表里要存emoji或者特殊生僻字(比如人名),排序规则选
utf8mb4_unicode_ci或utf8mb4_0900_ai_ci都行。如果只是普通业务,utf8mb4_general_ci速度快一丁点。这个差异在数据量大到千万级时会略明显,平时基本无感。
5. 实战二:Redis主从——用Docker Compose编排
5.1 为什么需要Docker Compose
当你需要同时启动多个容器,并且它们之间有依赖关系(比如应用连Redis、连MySQL),再用一条条docker run命令去拼,维护成本会迅速失控。Docker Compose的作用就是把这些容器的配置统一写进一个docker-compose.yml文件,一条命令全量启动,一条命令全量停止。
Redis主从是一个特别适合用Compose演示的场景:需要至少两个Redis容器,分别配置主从关系,还要考虑网络互通和持久化。用Compose管理,整个拓扑一目了然。
5.2 Compose文件的编写实战
先准备目录结构:
redis-cluster/ ├── docker-compose.yml ├── master/ │ └── redis.conf └── slave/ └── redis.confdocker-compose.yml内容:
version: "3.8" services: redis-master: image: redis:7.0-alpine container_name: redis-master ports: - "6379:6379" volumes: - ./master/redis.conf:/etc/redis/redis.conf - ./master/data:/data command: ["redis-server", "/etc/redis/redis.conf"] networks: - redis-net redis-slave: image: redis:7.0-alpine container_name: redis-slave depends_on: - redis-master ports: - "6380:6379" volumes: - ./slave/redis.conf:/etc/redis/redis.conf - ./slave/data:/data command: ["redis-server", "/etc/redis/redis.conf"] networks: - redis-net networks: redis-net: driver: bridge解释几个关键点:
depends_on确保从节点容器在主节点启动之后启动,但Redis自身需要几秒初始化,所以真正的数据同步靠从节点配置里的replicaof指令实现,启动顺序只是兜底。command覆盖镜像默认的启动命令,指定加载我们挂载的配置文件。- 端口映射:主节点映射6379,从节点映射6380,避免端口冲突。
- 网络
redis-net:Compose默认会创建同名网络,但自定义网络名更清晰,服务名会注册为网络DNS名,容器之间可以直接用服务名互相访问。
主节点redis.conf(master目录下):
bind 0.0.0.0 protected-mode no appendonly yes dir /data从节点redis.conf(slave目录下):
bind 0.0.0.0 protected-mode no appendonly yes dir /data replicaof redis-master 6379replicaof redis-master 6379是关键:告诉从节点去连接名为redis-master的主节点(Compose网络内,服务名就是可解析的主机名),端口是主节点容器内部的6379,不是宿主机映射端口。
启动:
docker compose up -d查看状态:
docker compose ps进入从节点验证同步:
docker exec -it redis-slave redis-cli -p 6379 info replication看到role:slave和master_link_status:up,说明主从同步已经建立。
5.3 高级配置套路
上面的基础主从只覆盖了数据读写分离。生产环境的Redis容器化部署,一般还会加这四样东西:密码认证、持久化策略、资源限制、健康检查。
密码认证:主从都配置requirepass yourpassword,从节点还需要额外配置masterauth yourpassword,否则从节点连主节点会被拒。
持久化策略:Redis默认只做内存操作,要防重启丢数据,必须开启AOF或RDB。在配置里已经写了appendonly yes,这是最稳妥的默认选择。大数据量场景还可以配合RDB快照,具体看业务容忍度。
资源限制:Compose里可以给容器设定内存和CPU上限:
deploy: resources: limits: cpus: "1.0" memory: 512M防止某个失控容器把整台服务器的内存吃光。Docker默认不做限制,不设资源上限等于允许单容器消耗宿主机全部资源,这个在多人共用的机器上是很危险的习惯。
健康检查:
healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3健康检查的意义在于编排工具能感知容器真实状态。如果Redis容器活着但无法响应,健康检查会标记为unhealthy,后续依赖它的服务(比如应用容器)就能做出应对。
使用Compose的另一个好处是环境可复用。你写好一个redis主从的Compose文件,换一台新服务器,只需要把整个目录拷过去,改一下密码,一条
docker compose up -d就全部搞定。
6. 从容器到微服务:打包自己的镜像
6.1 Dockerfile基础排布
用别人的镜像只是入门,把自己的应用做成镜像才是Docker的核心能力。做一个应用镜像,核心是写一个Dockerfile,它描述了镜像的每一层内容。
以Java后端项目为例:
FROM openjdk:17-jdk-slim LABEL maintainer="yourname@example.com" WORKDIR /app COPY target/app.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]以Python项目为例:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "main.py"]关键指令解释:
FROM:基础镜像,你的应用依赖的环境都从这里来。选体积小的slim版本或alpine版本,能显著减小镜像体积。WORKDIR:设置工作目录,后续所有命令都会在这个目录下执行。COPY:把宿主机文件复制进镜像。RUN:构建镜像时执行的命令,一般用来安装依赖。EXPOSE:声明容器对外端口。注意它只是声明,真正映射端口仍然靠docker run -p。ENTRYPOINT和CMD:指定容器启动时执行的命令。两者可以配合:ENTRYPOINT固定不可覆盖,CMD作为默认参数可被覆盖。
构建:
docker build -t my-app:1.0 .-t指定镜像名和标签,末尾的.指构建上下文目录,也就是Docker把哪个目录的内容发给Docker引擎。
6.2 镜像瘦身的实用技巧
新手容易忽略镜像体积,随便一打就是几个GB。镜像体积大,构建慢、推送慢、拉取更慢,宿主机磁盘压力也大。
经验技巧如下:
- 优先使用alpine或slim基础镜像:比如
python:3.11-alpine比python:3.11小一半以上。不过需要注意,alpine使用musl libc,部分依赖编译会出问题,遇到就换slim。 - 多阶段构建:把编译阶段和运行阶段分开。以Java为例,先用一个包含Maven的镜像把代码编译成jar包,再复制到运行镜像中:
FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=builder /build/target/app.jar . EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]最终镜像里只有运行所需的JRE和jar包,编译工具全部隔离在builder阶段,体积能减少50%到70%。
- 合并RUN命令:每个RUN指令都会增加一层,层数越多镜像越大且构建缓存越难命中。把多行命令用
&&连接并清理缓存:
RUN apt-get update && \ apt-get install -y some-package --no-install-recommends && \ rm -rf /var/lib/apt/lists/*6.3 部署微服务项目的组合套路
实际微服务项目不会单独跑一个容器。常见的部署套路是:
- 网关服务(如Nginx、Kong)一个容器
- 后端服务若干个容器(Java/Python/Go各一个)
- 数据库、缓存、消息队列各一个容器
- 通过Compose把所有服务串联
举个简化版Compose编排:
version: "3.8" services: api-gateway: build: ./gateway ports: - "80:80" depends_on: - user-service - order-service user-service: build: ./user-service environment: - DB_HOST=mysql8 - REDIS_HOST=redis-master depends_on: - mysql8 - redis-master order-service: build: ./order-service environment: - DB_HOST=mysql8 - REDIS_HOST=redis-master depends_on: - mysql8 - redis-master mysql8: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass volumes: - mysql-data:/var/lib/mysql redis-master: image: redis:7.0-alpine volumes: - redis-data:/data volumes: mysql-data: redis-data:服务之间通过服务名互相访问,Compose网络自带DNS解析。环境变量注入让同一个镜像可以跑在开发、测试、生产不同环境,而无需修改镜像本身。
如果你所在团队用IDEA开发Java项目,IDEA自带Docker插件,可以在运行配置里直接选择“Dockerfile”并一键构建镜像,省去命令行操作。但底层原理不变:构建镜像、启动容器、端口映射。会用命令行之后再用IDE辅助,才能建立真正的问题排查能力。
6.4 微服务与Docker网络互通的注意事项
微服务场景下,容器跨主机通信、容器间网络不通,是比较高发的坑。Docker网络默认是bridge模式,同一台宿主机上的容器可以互通,但有两个前提:在同一个自定义网络里,或者通过名称解析到对方。
如果你在Compose编排里发现A服务访问B服务不通,先检查是不是两个服务被分配到了不同的网络。默认情况下,Compose会为每个docker-compose.yml创建独立网络,跨Compose文件的服务默认不通。解决办法是给网络指定外部网络,或者在两个Compose里声明同一个name。
另一个常见问题是端口冲突。多个容器都映射到宿主机的3306或6379时会启动失败,错误提示一般是“port is already allocated”。这时候修改端口映射,或者让某个容器只内部监听不暴露到宿主机。
真正生产环境要跨多台物理机通信,不能依赖简单bridge网络,得用Swarm或Kubernetes来编排。这部分属于进阶范畴,但底层思路是一样的:容器间通过额外的overlay网络通信,应用层不关心物理机在哪,只认服务名。
7. 高频坑与快速排查
这部分把所有新手经常踩的坑集中整理成速查表,遇到问题可以直接对号入座。
| 症状 | 原因 | 解决方法 |
|---|---|---|
| Docker Desktop启动报virtualization support not detected | BIOS虚拟化没开或Windows功能未启用 | BIOS开启VT-x/SVM;勾选Virtual Machine Platform和WSL功能 |
| docker命令提示权限拒绝 | 当前用户不在docker组 | sudo usermod -aG docker $USER,重新登录 |
| docker run后容器立即退出 | 应用前台进程退出或配置错误 | docker logs 容器名查看启动日志 |
| 容器启动成功但本地无法访问 | 端口映射错误或防火墙拦截 | 检查docker ps的端口映射;检查宿主机防火墙 |
| 拉取镜像速度极慢 | 未配置镜像加速器 | 按2.4节配置registry-mirrors |
| 数据库容器重启后数据全没 | 未挂载数据卷 | 重建容器时加-v挂载宿主机目录 |
| Compose启动报“network not found” | 引用了不存在的网络 | 检查外部网络是否已创建,或移除external声明 |
| MySQL容器反复重启 | 挂载目录权限不对 | chown -R 999:999 /data/mysql8 |
| Redis从节点master_link_status:down | 密码不匹配或主节点地址错误 | 检查requirepass和masterauth配置 |
| 端口冲突导致容器启动失败 | 宿主机端口已被占用 | ss -tlnp查端口占用,换端口映射 |
| Docker服务启动失败 | iptables、存储驱动或selinux问题 | journalctl -u docker查看真实日志,逐步定位 |
| Windows下docker build路径找不到 | 共享目录权限问题 | Docker Desktop设置中把代码目录加入文件共享列表 |
排查原则总结成一句话:任何容器问题先看日志。docker logs 容器名能解决80%的疑惑;不看日志直接盲改配置,只会把问题搞得更乱。
我在本地Windows机器上踩得最深的一个坑是WSL 2内存占用。Docker Desktop默认动态分配内存,但有时会吃满整个WSL虚拟机的上限,导致开发环境卡顿。解决办法是在用户目录新建.wslconfig:
[wsl2] memory=4GB processors=4 swap=0这个配置能限制WSL 2的资源占用,让Docker Desktop在个人工作机上跑起来不拖垮系统。但每次改完.wslconfig需要执行wsl --shutdown才能生效,注意重启前保存好其他WSL发行版里的未保存内容。
再说一个很多人问的问题:为什么docker pull的时候有时候会卡在“waiting”?这通常是因为并发拉取层数遇到限流,或者上一个拉取任务还没完成。用Ctrl+C取消后重新docker pull一般能恢复;频繁遇到的话就把镜像源切换成配置好的加速器。
最后再分享几个实操心得
从最初在Windows上折腾Docker Desktop,到后来在Ubuntu服务器上部署整套环境,Docker给开发工作流带来的变化是质变级的。个人最大的体会是:用Docker不是多了一个工具,而是换了一种部署思维。以前配置MySQL要下载安装包、初始化数据目录、写启动脚本、设置开机自启;现在写一段Compose配置,环境跟着项目走,换机器不过几分钟。
给新入门的几个建议:
- 不要在概念上纠结太久。镜像、容器、仓库三个词有基本理解就够,立刻动手跑一个Nginx。概念是在使用中加深的,不是研究出来的。
- 老老实实用挂载卷存数据库数据。每个部署有状态服务的项目,都在目录结构里预留data目录和conf目录,养成习惯后几乎不会遇到“数据丢了怎么办”的灾难问题。
- 生产环境不要随便用
latest标签。锁定精确版本号,比如mysql:8.0.33,保证每次拉到的镜像是完全一致的,可重复部署才有意义。
我现在的习惯是把所有个人项目都配一个docker-compose.yml放在根目录,数据库、Redis、Nginx全写在里面,拉到任何机器上一条命令整套环境起来。这套方法帮你省下的时间,不是按小时算,是按天算的。