ARTICLE DETAIL

资讯详情

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

Docker Compose实战:用docker-compose.yml解决多容器编排难题

Docker Compose实战:用docker-compose.yml解决多容器编排难题 项目发版前群里最容易出现的一句话是“谁来挂 DC”这句话听起来像在点名实际上真正的问题是一套由 Nginx、后端应用、MySQL、Redis 组成的服务居然没有一个统一的启动方式。每位同学都只知道某个容器应该怎么起但没人能说清容器之间的依赖关系、启动顺序和失败后的处理方式。本文要聊的 DC先做一次限定在单机服务部署场景下通常指 Docker Compose。它擅长解决单机多容器编排问题把零散的docker run命令收敛成可维护的docker-compose.yml让一次up -d就把关联服务全部挂起来。如果你是后端开发、运维或者想在本地用 Docker 拉一套中间件做联调这篇文章都可以直接参考。整篇会覆盖 Docker Compose 的核心概念、docker-compose.yml的配置拆解、依赖等待机制、一个完整可运行的编排示例以及团队落地时经常踩的坑。1. 背景DC 到底是谁为什么总会让人争论1.1 Docker Compose 在部署中扮演什么角色Docker Compose 是一个容器编排工具它的使用范围非常明确单台 Docker 主机内的多容器管理。项目里通常不会只有一个容器常见形态是 Nginx 做入口后端服务处理业务MySQL 存数据Redis 做缓存。如果靠手敲 docker 命令一个个启动每个容器的镜像、端口、网络、数据卷都要反复核对时间一长就会变成非常碎片化的操作。Compose 解决的问题是“用声明式配置替代命令式操作”。你只需要写一份docker-compose.yml把需要启动的服务、镜像、端口、数据卷、网络、依赖关系都描述清楚后续启动一条命令搞定。这份文件本身就是运维文档任何人都可以基于同一份配置重现同样的环境不需要去翻上级的聊天记录。这里要顺带区分一个概念大家经常把 Docker Compose 简写为 Compose也有人直接叫 DC。有的项目组里说的“挂 DC”指的是在服务器上执行docker compose up -d有的团队里则可能被理解成“谁来负责维护编排配置”。这两种理解都说得通但真正要让项目稳定需要同时解决“配置谁维护”和“命令谁执行”两个问题。1.2 使用 Docker Compose 能解决哪些实际问题第一个收益是环境一致性。新同事加入项目后只要按 Readme 安装 Docker再执行一条docker compose up -d本地就能拉起整套依赖。数据库版本、缓存版本、容器端口、网络名称都固定在配置里不会因为不同人的机器差异导致行为不一致。第二个收益是依赖管理能力。Compose 里可以通过depends_on声明服务之间的先后关系。后端服务可以声明“MySQL 起来之后我再启动”MySQL 又可以配置健康检查告诉 Compose 自己是否真正接受了连接。这和“容器进程起来了但数据库初始化还没完成”有本质区别。第三个收益是运维操作更清晰。启动用up -d看状态用ps看日志用logs进入容器用exec停止清理用down。这些命令在所有服务之间是统一的。相比之下直接用 docker run 启动多个服务清理时非常容易漏掉某个无名容器长期运行后留下大量残留。第四个收益是项目关系管理。Compose 会把当前目录或指定项目名下所有资源看作一个整体。只要在同一个 compose 文件里定义默认会在同一个网络中服务名可以互相解析这对后端应用连接数据库特别重要。1.3 一个很容易被忽略的问题容器起来了不代表能用在实际部署里docker compose up -d执行成功只是说明容器被创建并启动了。这离“服务可用”还有距离。MySQL 容器启动后要完成数据库初始化Redis 可能还需要加载持久化数据后端进程要等数据库 TCP 端口能连通Nginx 则需要上游服务已经就绪。如果只依赖“容器进程启动”这一个信号很容易出现一种情况后端容器启动后立刻连数据库结果数据库初始化还没完成进程直接异常退出Compose 发现容器退出了又开始重启形成“启动即重启”的死循环。这种情况非常典型也是后面章节专门讨论healthcheck的原因。想让整套服务真正稳定需要从“进程级启动”迈向“健康检查级就绪”。2. 环境准备与命令基础2.1 部署环境需要准备什么本文示例以 Linux 环境为主Windows 和 macOS 上的 Docker Desktop 也适用命令基本一致。你可以使用一台云服务器、一台虚拟机或者自己的本地开发机。核心要求是已经安装 Docker Engine并且可以正常使用 Docker Compose 插件。不建议在生产环境使用过旧的 Docker 版本。尽量选择受官方长期支持的 Docker Engine 版本Compose 建议使用 v2 版本的docker compose命令。老的docker-composev1 工具目前已经不再维护如果继续使用部分语法表现会和本文示例有差异。具体版本不需要完全照搬本文重点演示配置思路你只需要确认自己的环境支持 Docker Compose v2 即可。2.2 检查 Docker 与 Compose 是否可用执行下面的命令看一下真实环境是否已经具备条件docker --version docker compose version如果docker compose version能正常输出版本信息说明 Compose v2 可用。如果系统提示找不到docker compose可能是安装了旧版独立工具这时候可以试试docker-compose --version。两种写法存在差异使用 Compose v2 时命令中间是空格使用独立工具时命令带有连字符。安装方式这里不展开CentOS、Ubuntu 等系统的安装包不同。唯一建议是不要手动下载来历不明的二进制优先使用镜像源或官方仓库安装。安装完成后可以再执行docker info确认 Docker 守护进程已经正常运行。2.3 示例项目目录规划为了让流程清晰建议单独建一个目录模拟一套独立部署环境。目录放在/opt/dc-demo或任意你有权限的路径都行下面是我在示例中使用到的结构dc-demo/ ├── docker-compose.yml ├── .env.example └── html/ └── index.htmldocker-compose.yml是排编配置主文件.env.example保存环境变量样例实际值建议复制为.env后填写不要把数据库密码直接提交到代码仓库html/目录是 Web 服务的静态页面目录用来验证服务是否真正可以访问。项目结构中真正重要的文件是docker-compose.yml其余两个是辅助文件。3. 核心配置拆解docker-compose 文件怎么读3.1 从 services、networks、volumes 三个维度理解打开一份 compose 文件最需要关注的是三个顶层关键字services、networks、volumes。services描述要启动哪些容器以及每个容器的启动方式包括镜像来源、容器名称、端口映射、启动参数、环境变量、数据卷挂载和健康检查。networks定义服务之间的网络拓扑多个服务放在同一个自定义网络中才能通过服务名互相访问。volumes声明数据卷目的是让容器中的数据在容器删除后仍然保留比如 MySQL 的数据目录和 Redis 的持久化文件。如果只把 compose 文件当成一个容器清单会忽略网络和数据卷对服务稳定性的意义。容器默认是隔离的MySQL 监听 3306 端口这个端口是在容器内部后端应用要连接 MySQL就必须和 MySQL 在同一个 Docker 网络里并通过服务名访问。数据卷也一样如果没有挂载数据卷容器一经删除内部数据全部丢失这对数据库来说不可接受。3.2 services 里最常用的字段说明下面是一段精简但常见的最小配置示例用来理解字段含义services: web: image: nginx:1.27-alpine container_name: dc-demo-web restart: unless-stopped ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html:ro networks: - dc-demo-netimage指定镜像生产环境不建议使用latest因为无法保证回滚到同一版本。container_name给容器固定一个名称如果省略Compose 会使用“项目名 服务名 序号”的命名方式。restart控制容器退出时的重启策略unless-stopped表示在手动停止前容器异常退出后会重启。ports做宿主机和容器之间的端口映射左边是宿主机端口右边是容器端口。volumes挂载文件或数据卷ro表示只读。networks把服务加入指定的自定义网络。这里要注意一个经典误解container_name是否必须唯一。容器名在整个 Docker 主机上是全局唯一的。如果你在别的项目中已经创建了一个同名容器Compose 启动时就会报重名冲突。所以只要当前机器上可能重复就要考虑给容器起带项目特征的名字。3.3 服务启动顺序控制depends_on 与 healthcheck很多初学 Docker Compose 的人会以为只要在depends_on里写了 MySQL后端容器就会等 MySQL 完全可用后再启动。这个理解需要修正一下。depends_on最基本的能力是控制“启动顺序”。它会让 Compose 先启动被依赖的服务再启动当前服务。问题在于MySQL 容器进程启动并不代表 MySQL 初始化完成。如果你只写了depends_on后端容器可能仍然会在 MySQL 还没有监听端口时启动然后连接失败。所以更可靠的做法是配合healthcheck使用。MySQL 服务先自定义一个健康检查命令Compose 会按照指定的周期执行探测当探测结果达到healthy后Compose 再通过这样一段配置启动依赖它的服务depends_on: mysql: condition: service_healthy这个语义和普通依赖不同它表达的是“MySQL 进程已经跑起来还不够要等 MySQL 的健康检查通过”。对于需要等待多个基础服务的应用可以这样声明多个服务依赖。实际上depends_on加healthcheck已经是 Docker Compose 最常见的依赖控制方案。它虽然不能解决“数据库连接池预热完成”这类更精确的业务就绪问题但已经比单纯用容器启动顺序可靠得多。应用自己的启动逻辑里仍然要写连接重试。3.4 环境变量、.env 文件与配置区分Compose 文件里经常出现${MYSQL_PASSWORD}这类变量引用。它的逻辑很简单Compose 读取同目录下的.env文件把变量加载进来替换到 compose 文件里。比如下面这行配置environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}容器里的环境变量值来自宿主机上.env中配置的MYSQL_ROOT_PASSWORD。这样做的最大好处是敏感信息不用直接写在 compose 文件里。仓库中保存一份.env.example开发人员复制成.env再填入本机值生产环境的值则由保持私有的.env维护。需要注意的是.env文件只是给 compose 做变量替换不等于容器内部的完整配置。应用自身的配置文件、Spring Boot 的 application.yml、Nginx 的 conf 文件仍然需要单独管理。如果项目在不同环境有不同参数建议区分“ Compose 负责容器基础设施参数”和“应用负责业务运行参数”两条线。4. 完整实战从零开始拉起来一套服务编排4.1 创建目录和基础文件这套示例的目标很直接用 Compose 启动 MySQL、Redis 和 Nginx 三个服务。MySQL 和 Redis 是典型的基础设施Nginx 在这里模拟一个对外提供 Web 访问的业务入口。真实项目中Nginx 角色可以替换成你的后端镜像但配置思路是一样的。首先创建目录并进入mkdir -p /opt/dc-demo/html cd /opt/dc-demo然后创建一个简单的静态页面用来验证 Nginx 能正常响应。文件路径是html/index.html你可以使用任意编辑器写入下面内容!DOCTYPE html html langzh-CN head meta charsetutf-8 titleDC Demo/title /head body h1Docker Compose is READY/h1 /body /html这个页面不是重点它只用于最后的访问验证。如果不想绑定本地页面也可以让 Nginx 使用默认欢迎页不过把本地目录挂载进去更接近真实环境中的静态资源配置方式。4.2 编写 docker-compose.yml 完整文件在/opt/dc-demo目录下创建docker-compose.yml内容如下。这是一份可以复制到测试环境直接运行的文件services: mysql: image: mysql:8.0 container_name: dc-demo-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: demo_db ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql networks: - dc-demo-net healthcheck: test: [CMD, mysqladmin, ping, -h, 127.0.0.1, -uroot, -proot123] interval: 5s timeout: 3s retries: 20 start_period: 10s redis: image: redis:7-alpine container_name: dc-demo-redis restart: unless-stopped ports: - 6379:6379 volumes: - redis_data:/data networks: - dc-demo-net healthcheck: test: [CMD, redis-cli, ping] interval: 5s timeout: 3s retries: 20 web: image: nginx:1.27-alpine container_name: dc-demo-web restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_healthy ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html:ro networks: - dc-demo-net networks: dc-demo-net: driver: bridge volumes: mysql_data: redis_data:这份文件有几个细节需要解释。MySQL 运行时通过MYSQL_ROOT_PASSWORD和MYSQL_DATABASE初始化 root 密码和默认数据库但这些环境变量只在数据卷首次初始化时生效。如果之后修改了密码但已有的mysql_data卷里已经保存了旧数据新密码不会自动生效。这点务必记住否则排错时会很困惑。Redis 配置相对简单镜像自带redis-cli所以健康检查可以直接用redis-cli ping判断服务是否存活。如果 Redis 返回 PONG说明服务正常否则健康检查失败。Nginx 依赖了 MySQL 和 Redis并且使用condition: service_healthy意思是基础组件通过健康检查后Nginx 才会启动。这里先不引入业务后端只是为了演示依赖等待机制。实际项目中你可以把 Nginx 镜像替换成自己的业务服务并在业务代码里配置spring.redis.host、spring.datasource.url之类的连接项连接地址分别写成服务名redis和mysql而不是localhost。4.3 校验配置并启动服务配置文件写完后建议先检查 YAML 语法和配置有效性而不是直接启动。执行下面命令docker compose config -qconfig命令会把最终生效的配置打印出来-q表示静默模式只返回校验结果。如果配置有问题命令会输出具体的 YAML 错误或字段错误如果没有任何输出说明格式正常可以开始启动。启动服务docker compose up -d-d表示后台运行。首次启动时Docker 会拉取 MySQL、Redis、Nginx 镜像耗时取决于你的网络环境。镜像拉取完成后服务会按依赖关系启动。启动后查看容器状态docker compose ps预期可以看到三个容器都处于running状态MySQL 和 Redis 会在状态列显示healthy或类似标识Nginx 依赖的两个基础组件健康检查通过后也会处于运行状态。MySQL 第一次初始化可能需要一段时间如果执行ps时显示starting或unhealthy属于正常现象需要稍等片刻再观察。4.4 验证服务是否可以访问在宿主机上执行 curl 请求验证 Nginx 是否正常响应curl -I http://localhost:8080预期可以收到HTTP/1.1 200 OK的响应。浏览器访问http://服务器IP:8080可以看到刚才创建的“Docker Compose is READY”页面。接着验证日志输出docker compose logs -f --tail100这里重点看两个信息。第一MySQL 容器在初始化时会有数据库创建日志第二Nginx 没有出现连接依赖服务的异常。日志是真实排障中最重要的入口不要忽略它。如果想体验“删掉容器后数据仍然保留”可以执行docker compose down停止并删除容器然后再执行docker compose up -d启动。由于mysql_data数据卷仍然存在MySQL 数据不会丢失。这里的down不会默认删除卷要注意的是如果加了-v参数数据卷会被一并清理。4.5 观察完整运行效果一套稳定编排的最终表现是一条命令启动一条命令停服所有服务状态可以统一查看。针对本文示例具体表现如下。执行docker compose ps时MySQL 和 Redis 经过健康检查后处于 healthy 状态。Nginx 对外提供 8080 端口访问静态页面返回 200。执行docker compose logs -f可以看到三个服务运行日志汇总输出不用再分别去看每个容器日志。如果要用这套模板部署真实后端只需要把web服务镜像改成项目自己的镜像再补充后端服务需要的环境变量、启动命令和健康检查即可。比如 Java Spring Boot 项目可以增加健康检查判断 actuator
返回列表