ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Docker Compose实战指南:从容器部署到微服务编排完整流程

Docker Compose实战指南:从容器部署到微服务编排完整流程 前段时间好几个朋友问我Docker到底该怎么入门。市面上的教程要么太零散要么只甩一堆命令不讲原因照着敲完还是一头雾水。我自己从Windows上折腾Docker Desktop开始到用Docker Compose一键拉起MySQL、Redis、RabbitMQ再到给微服务项目写编排文件中间踩过的坑确实不少。这篇教程把完整的Docker Docker Compose部署流程串起来所有命令都在Windows和Linux上亲测跑通按安装、概念、命令、编排、实战、排查这条线走下来目的就是让你照着敲一遍就能把常用服务部署起来。内容适合两类人一是刚接触容器的小白需要有人告诉你哪些步骤容易翻车二是已经用了几天Docker但对Compose配置、数据卷、生产环境参数还不太清楚的同学。这篇不写虚的全是实际能落地的操作和参数文末还附了常见问题的排查思路遇到报错直接翻到对应章节就行。1. 环境准备Docker安装全流程与Windows高频报错1.1 Windows上安装Docker Desktop先把虚拟化检查一遍Windows上安装Docker Desktop其实不难难的是装完点开直接报错。最常见的错误长这样“Docker Desktop failed to start because virtualisation support wasnt detected.”看到这个先别急着卸载重装按顺序排查确认CPU虚拟化已经开启。打开任务管理器 - 性能 - CPU看右下角“虚拟化”是否显示“已启用”。如果显示“已禁用”需要进BIOS把Intel VT-xAMD机器是SVM Mode打开这个选项一般在Advanced或CPU Configuration里。开启WSL2和虚拟机平台。以管理员身份打开PowerShell执行下面两条命令然后重启电脑dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart确认WSL2内核是新的。执行wsl --update或者wsl --set-default-version 2。如果机器里的WSL内核太老Docker Desktop连不上WSL后端照样启动失败。装完Docker Desktop后如果提示需要注销或重启就按它说的做这一步千万别跳。还有另一个高频报错“Failed to connect to the Docker API at npipe:////./pipe/docker-desktop...”这个通常是Docker引擎没起来。处理方式右下角鲸鱼图标右键选Restart不行就退出Docker Desktop打开任务管理器结束掉所有Docker相关进程再重新打开再不行就在PowerShell里执行wsl --shutdown等几秒重新启动Docker Desktop。绝大多数情况走完这三步就恢复了。提示Windows上装Docker Desktop建议选择使用WSL2后端的版本因为Hyper-V后端在部分老系统上问题更多。另外要明确一点Docker Desktop更适合本地开发调试生产环境还是用Linux服务器上的Docker引擎更稳。1.2 Ubuntu上安装Docker官方源流程Linux服务器上安装Docker我推荐用Docker官方apt源别直接用系统自带的docker.io那个版本太老Compose插件也没法方便地一起装。以下命令在Ubuntu 20.04、22.04、24.04上都实测过# 安装依赖 sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 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 \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装Docker引擎、CLI和Compose插件 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装完分别验证三个命令sudo docker --version sudo docker compose version sudo systemctl enable docker sudo systemctl start docker注意Compose这里的命令是docker compose中间是空格这是新版Compose v2插件的用法。很多老教程里写的是docker-compose带横杠那是Python版独立程序两者理念一样但命令名和安装方式不同千万别混用。1.3 处理docker权限错误别再每次加sudo用非root用户执行docker ps如果出现“permission denied”是因为当前用户不在docker组里。解决办法sudo usermod -aG docker $USER newgrp docker然后重新执行docker ps验证。这里多说一句加入docker组的用户等同于拥有root权限个人开发机这么干没问题但生产服务器上给哪些账号加docker组最好走正规审批流程别图省事一把梭。2. 核心概念镜像、容器、数据卷、网络是怎么配合的2.1 镜像和容器的关系模板与实例Docker里最基础的一对概念就是镜像和容器。我习惯把这两者类比成“类与对象”镜像是模板容器是模板跑起来的实例。同一个镜像你可以同时启动多个容器彼此互不影响。比如mysql:8.0这个镜像可以拉起一个测试库再拉起一个正式库只要端口、数据目录、配置分清楚就行。镜像本身是分层存储的。每一条RUN、COPY指令都会生成一层所以构建过镜像的机器再次构建时会走缓存速度很快。反过来如果你把COPY文件放在Dockerfile很靠前的位置文件一改动后面所有层的缓存会全部失效这就是为什么写Dockerfile时要把改动频繁的操作放后面。2.2 数据卷与端口映射数据不能丢外面要能访问容器默认是隔离的直接导致两个问题容器删了里面的数据就没了容器重启后写入的文件也可能丢。解决办法是挂载数据卷。数据卷有两种常用方式命名卷比如Compose文件里volumes: mysql-data数据由Docker统一管理适合数据库这类场景。绑定挂载直接把宿主机目录映射进容器比如./mysql/conf:/etc/mysql/conf.d特点是你直接能看到和修改文件适合配置文件、代码目录。端口映射则是把容器的端口暴露到宿主机。命令里-p 3306:3306左边是宿主机端口右边是容器端口。为什么右边不能随便改因为容器里的MySQL默认监听3306你映射宿主机3307外面就连3307容器里依然是3306。注意一条常用但容易踩坑的命令——docker run --rm。加上这个参数容器退出后会自动删除临时调试非常方便但如果你是想长期运行的服务千万别加否则容器一停就没了。2.3 网络模型容器间通信别用IP用服务名容器每次重启IP都可能变所以容器之间互相访问时不要写死IP而应该通过服务名或容器名解析。单容器用docker run不指定网络时会加入默认的bridge网。Docker Compose的方便之处在于同一个Compose项目下的所有service会自动加入同一个自定义网络service名就是hostname。比如编排文件里定义了一个叫mysql的service应用容器里连数据库就可以写host: mysql端口3306这个“mysql”就是Docker内置DNS解析出来的。这一步理解透了后面部署微服务、连中间件就顺了。很多人在单容器阶段觉得Docker没什么用其实就是因为没体会到网络编排带来的便利。3. Docker常用命令命令用熟了才谈得上部署3.1 镜像命令拉取、查看、删除、打Tag镜像相关的命令日常用的就是这几个# 拉取镜像不写tag默认latest docker pull nginx:1.25 # 查看本地镜像 docker images # 给镜像打tag方便推到私有仓库 docker tag nginx:1.25 registry.example.com/nginx:1.25 # 删除镜像注意没有容器在用它才能删 docker rmi nginx:1.25 # 清理悬空镜像也就是出现none的中间产物 docker image prune说到latest我以前图省事经常pull latest后来被坑过一次某天重新部署镜像拉下来直接版本大升级行为全变了。生产环境一定把tag写死比如mysql:8.0、redis:7.2不要用mysql:latest。3.2 容器命令run、ps、logs、exec、cp容器操作是日常用得最多的# 启动一个nginx容器挂载目录并映射端口 docker run -d --name web -p 8080:80 -v /opt/html:/usr/share/nginx/html nginx:1.25 # 查看运行中的容器 docker ps # 查看所有容器包括已退出的 docker ps -a # 查看日志-f表示跟随输出 docker logs -f web # 进入容器内部推荐用exec而不是attach docker exec -it web bash # 把宿主机文件拷进容器 docker cp app.jar web:/app/ # 强制删除容器 docker rm -f web这里专门提一下docker run -d是后台执行-it通常用于交互两者一般不同时用。想进容器调试用docker exec -it别用docker attachattach会把容器主进程的输出刷你一脸用过一次的人基本都不想再用。3.3 启动参数里藏着三个容易忽略的细节docker run参数很多但有三个我建议每次都检查--restart unless-stopped容器异常退出会自动重启服务器重启也会自动拉起来。没有这个参数机器一重启服务全挂。--name给容器起个有意义的名字看日志和删除时非常方便不然会冒出一串随机字符。--network多个容器要互相访问就放到同一个自定义网络先docker network create建一个再启动容器时指定进去。4. Docker Compose多容器部署的正确姿势4.1 为什么需要Compose什么时候别硬用命令单容器用docker run就够了但真实项目从来不止一个容器。一个典型的后端服务至少包含应用、MySQL、Redis再加上队列和前端静态页面如果每个都用docker run手敲命令会越来越长参数也不统一同事接手看半天也理不清。Compose的价值就是把“一组容器的定义”写成一份声明式配置文件docker compose up -d一条命令全起docker compose down一键全停。配置纳入git管理换台机器clone下来直接部署这才是可复现的部署方式。Dify、GitLab这些开源项目自带的docker-compose.yml本质上也是这么一回事。4.2 Compose安装方式与版本确认Ubuntu官方docker-ce源里已经带了docker-compose-plugin插件装完就是docker compose命令。验证一下docker compose version如果服务器上只有旧版docker-compose独立二进制也可以手动装sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose docker-compose --version两种方式二选一我更建议用插件方式命令更统一。看资料时注意区分新版是docker compose旧版是docker-compose看到老教程里的横杠命令别直接复制。4.3 compose.yml的核心字段逐个拆解一份最小的Compose文件长这样version: 3.8 services: web: image: nginx:1.25 container_name: web ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html restart: unless-stopped逐个字段说明version声明Compose文件格式版本Compose v2已经不怎么强制了但写“3.8”能兼容更广泛的工具链。services定义所有容器每个service一个名字。image用哪个镜像。build如果要用Dockerfile本地构建就指定构建上下文。container_name容器名不写则Compose按“项目名-服务名-序号”自动生成。ports端口映射字符串形式要加引号不然YAML里8080:80可能被解析成数字。volumes挂载。environment环境变量敏感信息建议改用env_file。depends_on声明服务启动顺序。restart重启策略。networks指定加入哪个网络。运行几个关键命令# 校验compose文件语法强烈建议每次改完先跑一遍 docker compose config # 后台启动 docker compose up -d # 查看状态 docker compose ps # 看日志 docker compose logs -f web # 停止并删除容器网络会保留数据卷不会删 docker compose down # 连数据卷一起删慎用 docker compose down -v注意docker compose down不会删除命名卷但加上-v会把volumes里定义的数据也一并清掉。我自己有一次执行down -v本意是清理测试环境结果把本地开发数据库也删了。教训就是手别快先看清里面有没有业务数据。5. 实战部署用Compose一键拉起常用服务与微服务5.1 MySQL 8.0字符集、时区与数据持久化先给一份我在开发和测试环境都在用的MySQL Compose配置services: mysql: image: mysql:8.0 container_name: mysql8 restart: unless-stopped ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: demo TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --lower_case_table_names1 - --default-time-zone08:00 volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -p$$MYSQL_ROOT_PASSWORD] interval: 10s timeout: 5s retries: 5几个容易踩的点字符集不提前指定容器里默认是latin1插入中文直接乱码。所以command参数里charset必须显式指定utf8mb4。lower_case_table_names1是让表名不区分大小写。如果你在Windows上开发、Linux服务器上部署这个参数建议两边统一不然表和索引名大小写不一致会排错老半天。注意MySQL 8.0里这个参数在初始化时才能改所以第一次启动就要带上。数据目录挂载到宿主机./mysql/data容器删除、重建数据都还在。健康检查里写的是$$MYSQL_ROOT_PASSWORD两个美元符号是Compose的转义写法如果只写一个$YAML环境变量解析时会变成空值。root密码直接放Compose文件里只适合测试环境生产环境至少要用env_file配合.gitignore不要把env文件提交到git。5.2 Redis开启AOF和主从别让人踩了生产环境的坑Redis的单容器配置很简单services: redis: image: redis:7.2 container_name: redis restart: unless-stopped ports: - 6379:6379 command: redis-server --appendonly yes --requirepass Redis123 --bind 0.0.0.0 volumes: - ./redis/data:/data这里三个点appendonly yes开启AOF持久化。Redis容器默认配置是不持久化的机器重启等于数据全部清零。不是所有业务都能接受这个所以一定要提前开启。requirepass设置密码。注意设了密码之后主从模式下从库还要配masterauth否则从库连不上主库。bind 0.0.0.0让容器外能访问。生产环境别把6379直接对公网开放尽量只让内网或同一Compose网络里的应用访问需要对外就加防火墙策略。主从配置也不难一个master一个replica。replica服务的command加一行redis-server --slaveof redis-master 6379 --masterauth Redis123 --appendonly yes --requirepass Redis123服务名redis-master要能解析同一个Compose网络下直接把service名当hostname用就行。如果还要做故障自动切换就得引入哨兵或者直接上Redis Cluster那又是另一个话题了。5.3 RabbitMQ默认账号的坑与可视化面板RabbitMQ的Compose配置我常用这个services: rabbitmq: image: rabbitmq:3-management container_name: rabbitmq restart: unless-stopped ports: - 5672:5672 - 15672:15672 environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: Admin123 volumes: - ./rabbitmq/data:/var/lib/rabbitmq选带-management后缀的镜像自带Web管理面板。这里最大的坑是RabbitMQ默认的guest账号只能在localhost登录你用宿主机IP去访问15672会一直提示登录失败。解决办法就是像上面一样启动时通过RABBITMQ_DEFAULT_USER和RABBITMQ_DEFAULT_PASS创建自己的管理员账号。5672是amqp协议端口应用连消息队列用的15672是管理界面端口。端口映射出去之前想清楚管理界面暴露到公网等于把密码爆破面也暴露了测试环境还好生产环境要收敛。5.4 微服务项目的Dockerfile与Compose编排针对Java微服务我习惯用多阶段构建既能保证构建环境完整又不让最终镜像带上maven和源码。以Spring Boot项目为例# 第一阶段构建 FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # 第二阶段运行 FROM openjdk:17-jdk-slim WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]然后Compose里这样引用services: order-service: build: ./order-service container_name: order-service restart: unless-stopped ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: dev depends_on: - mysql - redis - rabbitmq关于depends_on这里必须说清楚它只保证服务启动顺序不保证依赖服务已经可以接受连接。实际使用中应用连不上数据库大概率会自己重试但更稳妥的做法是给mysql、redis配healthcheck然后在order-service里设置condition: service_healthy这样的编排才算真正“健康”的编排。如果你要部署的不是自研微服务而是Dify这类开源项目读完这篇以后再去看它自带的docker-compose.yml会发现已经能看懂80%的配置了剩下20%无非是env文件、外部卷和自定义网络这些我们已经聊过的东西。6. 常见问题与排查技巧实录6.1 容器启动后秒退先看exit code容器一上来就退这在刚接触Docker时几乎必遇。正确姿势是# 看容器状态和退出码 docker ps -a # 看完整日志 docker logs --tail 200 容器名常见的几种情况端口被占用日志里出现“bind: address already in use”把宿主机对应端口空出来就行。配置不对比如MySQL初始化参数写错容器直接退出看日志就能明白。命令问题自己写的Dockerfile最后一条命令执行完前台进程退出容器就跟着退。所以启动命令要写成前台驻留形式Redis是redis-serverNginx是nginx -g daemon off;Java应用就正常执行java -jar。6.2 端口被占用address already in use这个错误我碰到过好多次尤其是3306、6379这类常用端口宿主机上可能已经有一个原生MySQL或Redis占用了。排查命令sudo lsof -i:3306 # 或者 sudo ss -lntp | grep 3306处理方式有三种停掉宿主机原有服务把Docker映射端口改成3307、6380这种不冲突的端口或者反过来保留Docker服务给宿主机原生服务换端口。对我个人来说能不用原生服务就尽量不用Docker环境隔离得太好卸载重装也不脏系统。6.3 容器时间和宿主机不一致时区问题容器默认时区是UTC你看到日志里时间少了8个小时大概率就是时区没设置。解决方案是启动时加环境变量TZenvironment: TZ: Asia/Shanghai写代码读取数据库时间时也留个心眼MySQL的时区配置、Java应用里JDBC连接串的serverTimezone参数三处时区不一致就会出现“怎么差了8小时”这种疑难杂症。排查思路先date查看容器时间再查应用日志最后看数据库当前时间哪一环偏了就改哪一环。6.4 磁盘被镜像和日志占满清理与预防Docker用久了磁盘被撑爆是早晚的事。先体检docker system df看到一堆悬空镜像、构建缓存、无用网络之后执行# 删除所有停止的容器、悬空镜像、无用网络、构建缓存 docker system prune -af # 单独清理没有被任何容器使用的数据卷慎用 docker volume prune注意docker system prune -af不会删数据卷但docker volume prune会把没有被容器引用的数据卷全部删干净。如果里面有你想留的数据提前确认再执行。预防方案Compose里给日志加尺寸限制每个服务都加这段logging: driver: json-file options: max-size: 10m max-file: 3这个配置很容易被忽略不加的话容器日志会无限增长。我亲眼见过一台机器被一个日积月累的容器日志塞满清理起来特别狼狈。6.5 快速定位挂载、环境变量和IP问题想把容器的问题一次看明白最直接的是docker inspect# 查看容器的完整配置、挂载、环境变量、网络IP docker inspect 容器名如果容器起不来又想验证Compose写得对不对先docker compose config输出解析后的最终配置很多手滑写错的地方一眼就能看出来。最后把这一章的排查思路浓缩成一张速查表贴到笔记里随手能翻现象排查方向处理建议容器秒退退出码与日志docker ps -a docker logs --tail 200端口冲突宿主机端口占用lsof -i:端口换映射端口或停占用的服务数据丢失未挂载数据卷持久化卷或绑定挂载容器内文件不符合预期挂载未生效docker inspect 查看Mounts服务间无法通信网络不一致同Compose网络用service名访问磁盘爆满镜像、日志、构建缓存docker system df prune最后的一点实践建议教程写到这里基本把Docker和Docker Compose从安装到实战的流程走完了。最后说几个我自己的使用习惯算是一个补充。第一生产环境所有镜像tag都固定绝对不用latest。第二每次写Compose文件前先想清楚数据卷规划哪些要持久化、哪些可以随容器销毁数据库和消息队列这类有状态服务一定要持久化。第三修改Compose文件后先docker compose config校验一遍再up能省掉很多低级错误。第四如果机器配置一般给每个服务加上资源限制避免一个容器把宿主机内存吃满。这几点听着琐碎但大多数容器翻车现场都跟它们有关。你把这套流程跑熟之后不管是部署中间件、开源项目还是自己的微服务思路都是相通的——网上任何带docker-compose.yml的项目你都能一眼看明白它干了什么哪里要改心里也有数。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表