资讯动态

【FDE系列】阶段2:Day 43:Docker Compose — 多容器一键编排

发布时间:2026/10/29 19:47:21 来源:尧图企业网站定制
前言FDE系列内容总纲【大纲】FDE 前沿部署工程师学习系列教程-CSDN博客前置课程列表见文档结尾附录。阶段2·Day 43Docker Compose — 多容器一键编排FDE 学习系列教程 · 第二阶段 · 第 5 周 · Day 3预计时长3 小时 | 难度★★★☆☆ | 前置知识Day 41-42 容器操作与 Dockerfile一句话目标看懂并编写 docker-compose.yml用 Docker Compose 一键编排 FastAPI MySQL 多容器应用掌握服务依赖、环境变量、数据卷和常用管理命令。‍‍ 开场一个容器好养一套容器难带昨天你能把自己的 FastAPI 打成镜像了。但真实项目从来不是一个容器包打天下。回想你的工单系统工单系统需要 ┌──────────┐ 连接 ┌──────────┐ │ FastAPI │ ──────► │ MySQL │ │ 容器 │ │ 容器 │ └──────────┘ └──────────┘ 明天还要加Nginx 容器反向代理、Redis 容器缓存……如果用昨天的docker run手搓你得# ① 先建个网络让容器互通 docker network create fde-net # ② 启动 MySQL记一堆 -e、-v、--network 参数 docker run -d --name db --network fde-net \ -e MYSQL_ROOT_PASSWORDroot123 -e MYSQL_DATABASEfde_db \ -v db_data:/var/lib/mysql mysql:8.0 # ③ 等 MySQL 就绪多久只能靠猜 # ④ 启动 API又是一堆参数还得保证数据库先起来 docker run -d --name api --network fde-net -p 8000:8000 \ -e DB_HOSTdb ... fde-api:1.0三个容器就要敲四五个长命令换台机器全部重来一遍顺序错了还连不上。Docker Compose就是来终结这种手工活的把所有容器的配置写进一个 YAML 文件一条命令让整套环境按正确的姿势全部就位。手工 docker run Docker Compose 一条条命令参数靠脑子记 一个 docker-compose.yml 描述全部 顺序、网络、卷手动管 up 一键启动 / down 一键销毁 换机器要重新敲一遍 文件拷过去up 一下原样复现 一、Compose 是什么 版本说明Docker Compose 是 Docker 官方的多容器编排工具。你用一个 YAML 文件声明系统由哪些服务组成、每个服务用什么镜像、开什么端口、挂什么卷、彼此什么关系Compose 负责把它们全部创建好、连进同一个网络。┌────────────────────────────────────────────────────────┐ │ docker-compose.yml 一份声明描述整套系统 │ │ │ │ services: │ │ api: ← 服务1FastAPI 容器 │ │ db: ← 服务2MySQL 容器 │ │ nginx: ← 服务3Nginx 容器明天加 │ │ │ │ docker compose up -d → 三个容器 网络 数据卷 │ │ 一次性全部创建 │ └────────────────────────────────────────────────────────┘命令的新旧写法新版 Docker带 Compose V2 插件用docker compose中间空格老版本 V1 用docker-compose中间横杠独立程序。你昨天验证过docker compose version有输出所以本教程统一用新写法docker compose。两种写法参数基本一致看到老教程的docker-compose别慌等价。YAML 语法速览30 秒入门Compose 文件是 YAML 格式核心就两条规矩靠缩进表示层级每层通常 2 个空格不能用 Tab键值对用冒号加空格key: value冒号后那个空格不能少services: # 顶层 api: # 缩进 2 格services 的子项 image: nginx # 再缩进 2 格api 的配置 ports: # 列表用 - 开头 - 80:80️ 二、最小例子用 Compose 跑一个 Nginx先跑个最小的建立yml → 容器的直观感受。mkdir -p ~/docker_lab/compose_demo cd ~/docker_lab/compose_demo新建docker-compose.ymlservices: web: image: nginx:alpine container_name: demo-nginx ports: - 8080:80 restart: unless-stopped逐行对应到你昨天学的docker run参数docker run 写法compose 写法nginx:alpine镜像image: nginx:alpine--name demo-nginxcontainer_name: demo-nginx-p 8080:80ports: - 8080:80--restart unless-stoppedrestart: unless-stopped启动docker compose up -d # [] Running 2/2 # ✔ Network compose_demo_default Created # ✔ Container demo-nginx Started注意它自动建了一个网络compose_demo_default——这就是 Compose 的省心之处。浏览器访问 http://localhost:8080 看到欢迎页。常用管理命令先混个眼熟docker compose ps # 看这套服务的容器状态 docker compose logs -f # 看所有服务日志-f 实时 docker compose stop # 停止容器保留 docker compose start # 再启动 docker compose down # 停止并删除容器网络数据卷默认保留 docker compose up -d # 改了配置后重新生效先docker compose down清掉进入正题。️ 三、实战FastAPI MySQL 两容器编排现在把昨天的 fde-api 和一个 MySQL 数据库编排起来。注意容器之间通过服务名互相访问这是 Compose 最巧妙的地方先看项目结构~/docker_lab/fde_stack/ ├── main.py ├── requirements.txt ├── Dockerfile ← 昨天写的构建 api 镜像用 ├── .dockerignore ├── init.sql ← 数据库初始化脚本待会写 └── docker-compose.yml ← 今天的主角第 1 步main.py连上数据库的版本from fastapi import FastAPI import os import mysql.connector import uvicorn app FastAPI(titleFDE API MySQL, version1.0) def get_db(): 从环境变量读取数据库连接信息配置不写死 return mysql.connector.connect( hostos.getenv(DB_HOST, db), portint(os.getenv(DB_PORT, 3306)), useros.getenv(DB_USER, root), passwordos.getenv(DB_PASSWORD, root123), databaseos.getenv(DB_NAME, fde_db), ) app.get(/) def root(): return {msg: FDE Stack, services: [api, mysql]} app.get(/health) def health(): 健康检查API 活着 数据库连得上 try: conn get_db() conn.ping() conn.close() return {status: ok, db: connected} except Exception as e: return {status: degraded, db: str(e)} app.get(/devices) def list_devices(): conn get_db() cursor conn.cursor(dictionaryTrue) cursor.execute(SELECT id, name, type, temperature FROM devices LIMIT 10) rows cursor.fetchall() conn.close() return {devices: rows, count: len(rows)} if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)requirements.txtfastapi0.115.0 uvicorn0.30.0 mysql-connector-python9.1.0Dockerfile昨天的直接用FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt \ -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8000 CMD [python, main.py]第 2 步init.sql数据库建表 初始数据MySQL 官方镜像有个贴心机制容器第一次启动时会自动执行/docker-entrypoint-initdb.d/目录下的.sql文件。我们把建表语句放进去数据库一就绪就自动初始化。-- init.sql首次启动自动执行 CREATE TABLE IF NOT EXISTS devices ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, type VARCHAR(20) NOT NULL, temperature DECIMAL(5,1) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); INSERT INTO devices (name, type, temperature) VALUES (注塑机A1, 注塑, 65.0), (注塑机A2, 注塑, 82.0), (注塑机A3, 注塑, 91.0), (冲压机B1, 冲压, 75.0), (冲压机B2, 冲压, 88.0);第 3 步docker-compose.yml核心# # FDE 技术栈FastAPI MySQL # services: # ---------- 服务 1API ---------- api: build: . # 不用现成镜像用当前目录 Dockerfile 现场构建 container_name: fde-api ports: - 8000:8000 environment: # 注入容器的环境变量 DB_HOST: db # ⭐ 主机名直接写服务名 db DB_PORT: 3306 DB_USER: root DB_PASSWORD: root123 DB_NAME: fde_db depends_on: # 依赖先启动 db再启动 api - db restart: unless-stopped # 容器挂了/机器重启后自动拉起 # ---------- 服务 2MySQL 数据库 ---------- db: image: mysql:8.0 # 直接用官方镜像不用自己 build container_name: fde-mysql environment: MYSQL_ROOT_PASSWORD: root123 # root 密码必填 MYSQL_DATABASE: fde_db # 启动时自动创建这个数据库 ports: - 3306:3306 volumes: # 命名卷数据库数据持久化容器删了数据不丢 - db_data:/var/lib/mysql # 绑定挂载把初始化脚本放进镜像规定的目录 - ./init.sql:/docker-entrypoint-initdb.d/init.sql restart: unless-stopped # 顶层声明用到的命名卷Compose 会自动创建 volumes: db_data:第 4 步一键启动docker compose up -d --build # --build启动前先根据 Dockerfile 构建 api 镜像 # 首次会拉 mysql:8.0 镜像几百MB耐心等观察启动过程[] Running 4/4 ✔ Network fde_stack_default Created ✔ Volume fde_stack_db_data Created ✔ Container fde-mysql Started ✔ Container fde-api Started看状态docker compose ps # NAME STATUS PORTS # fde-api Up 5 seconds 0.0.0.0:8000-8000/tcp # fde-mysql Up 8 seconds 0.0.0.0:3306-3306/tcp⚠️ 第一次启动 MySQL 需要 10~30 秒初始化。API 可能比数据库先就绪而连接失败下一节专门讲怎么处理。先等半分钟再验证。第 5 步验证整条链路# ① API 自己活着 curl http://localhost:8000/ # {msg:FDE Stack,services:[api,mysql]} # ② API 能连上数据库 curl http://localhost:8000/health # {status:ok,db:connected} # ③ 数据库里的初始数据能通过 API 查出来 curl http://localhost:8000/devices # {devices:[{id:1,name:注塑机A1,...},...],count:5}通了一条docker compose up应用和数据库两个容器全部就位、自动建库建表、网络互通。这就是编排的威力。 四、核心机制服务名就是主机名上面配置里最神奇的一行是DB_HOST: db——为什么主机名写db就行IP 都不用知道┌────────────────────────────────────────────────────────┐ │ Compose 自动创建一个专用网络把所有服务接进去 │ │ 并提供一个内置 DNS服务名 ↔ 容器 IP 自动解析 │ │ │ │ 容器 api 想连数据库 │ │ 代码写 hostdb │ │ │ │ │ ▼ │ │ 内置 DNSdb 是哪个容器→ fde-mysql 的内网 IP │ │ │ │ │ ▼ │ │ api ───────────────────────────────► fde-mysql:3306 │ │ │ │ 服务名 db 来自 compose 文件里 db: 这个 key │ └────────────────────────────────────────────────────────┘关键认知同一 Compose 网络里的容器互相访问时主机名直接写服务名db、api端口写容器内端口3306不是映射到宿主机的那个。容器 IP 会变但服务名永不过期——这比写死 IP 靠谱一万倍。注意区分你在Windows 浏览器访问 API 用localhost:8000走端口映射容器之间互通用服务名:容器端口走内部网络不需要 ports 映射。 五、depends_on 的坑启动了 ≠ 就绪了你可能发现了刚up的头几秒/health可能报数据库连不上。原因在于depends_on的真实语义depends_on: - db 它只保证【启动顺序】db 容器先启动api 容器后启动 它【不保证】db 已经可以接受连接 时间线 0s db 容器启动mysqld 进程开始初始化要 20 秒 1s api 容器启动立刻去连 db:3306 → 拒绝连接 ❌ 20s MySQL 才真正就绪三种应对方法从简到优方法 1API 加重试最实用推荐让应用连接数据库时带重试逻辑——连不上等几秒再试。生产应用本来就该有这个容错。可以用restart: unless-stopped让 api 容器崩溃后自动重启直到数据库就绪。方法 2Compose 的健康检查条件V2 支持给 db 配 healthcheck让 api 等数据库真正健康再启动db: image: mysql:8.0 # ... 其他配置 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -proot123] interval: 5s timeout: 5s retries: 10 api: # ... 其他配置 depends_on: db: condition: service_healthy # 等 db 健康检查通过才启动方法 3自己 sleep土办法不推荐api: command: sh -c sleep 20 python main.py FDE 实操建议学习阶段用方法 1重启策略 应用重试最简单正式交付推荐方法 2healthcheck condition语义最准确。 六、docker-compose.yml 配置项速查配置项作用对应 docker runimage用现成镜像直接写镜像名build: .从 Dockerfile 构建先 build 再 runcontainer_name容器名--nameports宿主机:容器端口映射-pvolumes数据卷/绑定挂载-venvironment环境变量键值对-eenv_file从文件批量读环境变量--env-filedepends_on启动顺序/就绪依赖无手工控制restart重启策略--restartnetworks自定义网络--networkcommand覆盖镜像默认启动命令镜像名后的命令working_dir工作目录-w环境变量的两种安全写法写法 A直接写在 yml 里学习用简单直观就是你现在用的environment: DB_PASSWORD: root123写法 B敏感信息用 .env 文件别提交到 Git生产推荐项目根目录建.envDB_PASSWORDroot123 MYSQL_ROOT_PASSWORDroot123compose 文件里用${变量名}引用db: environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}.env加进.gitignore和.dockerignore密码就不会进版本库。再提供一个.env.example不含真实密码的模板给同事参考。12-Factor 原则配置尤其是密码、地址等随环境变化的东西通过环境变量注入而不是写死在代码里。这样同一份镜像开发/测试/生产环境靠不同环境变量切换镜像本身一个字都不用改。️ 七、Compose 常用命令全掌握# ---------- 生命周期 ---------- docker compose up -d # 构建后台启动整套服务 docker compose up -d --build # 强制重新构建镜像后启动代码更新后用 docker compose down # 停止并删除容器网络卷保留数据还在 docker compose down -v # ⚠️ 连数据卷一起删数据库数据清空 docker compose stop # 停止容器保留可 start docker compose start # 启动已停止的 docker compose restart # 重启所有服务 docker compose restart api # 只重启 api 一个服务 # ---------- 查看 ---------- docker compose ps # 服务状态 docker compose logs -f # 所有服务实时日志 docker compose logs -f api # 只看 api docker compose logs --tail20 db # db 最后 20 行 docker compose top # 各容器里跑的进程 # ---------- 执行 ---------- docker compose exec api bash # 进入 api 容器 docker compose exec db mysql -uroot -proot123 fde_db # 直接进 MySQL 命令行 docker compose build # 只构建镜像不启动数据持久化验证重要实验# ① 通过 API 插入一条数据用 mysql 客户端或 exec 进去插 docker compose exec db mysql -uroot -proot123 fde_db \ -e INSERT INTO devices (name,type,temperature) VALUES (测试机X,测试,50); # ② down 销毁容器注意不加 -v docker compose down # ③ 重新 up docker compose up -d # 等数据库就绪后查询 curl http://localhost:8000/devices # 测试机X 还在数据活在 db_data 卷里与容器生死无关 ✅这个实验一定要亲手做——它会让你彻底理解容器是临时的数据是持久的。 本课小结知识点一句话记住Compose 作用一个 yml 编排多个容器up 一键起 down 一键灭services每个服务对应一个容器api/db/...image vs build现成镜像用 image自己的应用用 build: .ports宿主机:容器 端口映射volumes命名卷持久化数据绑定挂载挂配置/脚本environment注入环境变量配置与代码分离depends_on只管启动顺序不管是否就绪服务名即主机名容器互访写服务名:容器端口自动网络Compose 自动建网络和 DNS数据初始化MySQL 自动执行 initdb.d 下的 .sqlhealthcheck配合 condition: service_healthy 等就绪down vs down -v前者保留数据后者清空数据卷更新部署改代码 → up -d --build 重建变动的服务 核心认知Compose 把一套系统作为管理单元。docker-compose.yml就是你这套环境的唯一事实来源——它把用哪些镜像、怎么连、数据放哪、什么顺序全部声明清楚纳入 Git 管理后团队任何人、任何机器一条up就能得到一模一样的环境。这就是基础设施即代码思想的入门。 课后练习练习 1独立搭起双容器栈约 40 分钟不看教程新建目录完成 FastAPI MySQL 的 Compose 编排准备 5 个文件main.py / requirements.txt / Dockerfile / init.sql / docker-compose.ymldocker compose up -d --build启动用/health和/devices验证链路数据库没就绪就稍等或重启 apidocker compose exec db mysql -uroot -p...进数据库手动插一条设备再通过 API 查到它练习 2持久化实验约 15 分钟插入一条新设备docker compose down不加 -v再up确认数据还在docker volume ls找到这个项目的数据卷说出它的命名规律通常是目录名_卷名思考什么情况下才该用down -v练习 3加一个 phpMyAdmin 服务约 25 分钟在 compose 文件里加第三个服务用浏览器图形化管理 MySQLpma: image: phpmyadmin:latest container_name: fde-pma ports: - 8080:80 environment: PMA_HOST: db # 指向数据库的服务名 PMA_PORT: 3306 depends_on: - db restart: unless-stopped启动后浏览器打开 http://localhost:8080用 root / root123 登录图形界面查看 fde_db 里的 devices 表。 下节预告现在你的整套服务在 8000、3306、8080 各种端口上跑。明天补两大块拼图Nginx 反向代理让用户访问标准 80 端口就能打到你的 API顺便学会负载均衡的原理Git 版本控制你的代码、Dockerfile、compose 文件都需要版本管理——提交历史、分支、回滚、推送远程仓库告别文件夹复制改名式备份学完明天部署和代码管理两件事的工具链就齐了。明天见附录前置课程列表阶段一认知启蒙AI 认知与 FDE 角色AI 认知【FDE系列】阶段1Day 1AI 层级关系 — 四个嵌套的圈-CSDN博客【FDE系列】阶段1Day 2AI 三阶段发展史 — 会认 → 会判断 → 会创造-CSDN博客【FDE系列】阶段1Day 3符号 AI vs 机器学习 — 两条路线的本质区别-CSDN博客【FDE系列】阶段1Day 4Transformer 的历史意义 — 2017 年的分水岭-CSDN博客【FDE系列】阶段1Day 5本周复习与自测 — 检验你的 AI 认知地基-CSDN博客【FDE系列】阶段1Day 6Transformer 架构 — 一张图纸盖出千千万万栋楼-CSDN博客【FDE系列】阶段1Day 7LLM 本质 — 文字接龙机器-CSDN博客【FDE系列】阶段1Day 8Token — 模型眼中的最小单位-CSDN博客【FDE系列】阶段1Day 9AI 幻觉 — 为什么会一本正经地胡说八道-CSDN博客【FDE系列】阶段1Day 10上下文窗口 — 模型的记忆力上限 本周复习-CSDN博客【FDE系列】阶段1Day 11Prompt — 给模型立规矩-CSDN博客【FDE系列】阶段1Day 12Memory — 让模型记住上下文【FDE系列】阶段1Day 13RAG — 给模型配图书管理员-CSDN博客【FDE系列】阶段1Day 14Tool Use — 让模型动手操作-CSDN博客【FDE系列】阶段1Day 15MCP — 统一的工具接口标准 第三周复习-CSDN博客FDE 基础概念【FDE系列】阶段1Day 16什么是 FDE — 把 AI 变成客户结果的人-CSDN博客【FDE系列】阶段1Day 17FDE vs 传统实施 — 三大本质区别-CSDN博客【FDE系列】阶段1Day 18FDE 三重身份 C6 胜任力模型-CSDN博客【FDE系列】阶段1Day 19七阶段行动路径 行业经验的价值-CSDN博客【FDE系列】阶段1Day 20阶段总结与产出物 — 第一阶段收官-CSDN博客阶段二技术地基Python FastAPI SQL Docker API 集成Python基础【FDE系列】阶段2Day 21Python 环境搭建 — 写出你的第一行代码-CSDN博客【FDE系列】阶段2Day 22变量、数据类型、条件判断 — Python 的“记忆“和“判断“-CSDN博客【FDE系列】阶段2Day 23循环与函数 — 让代码跑 100 遍、把逻辑打包复用-CSDN博客【FDE系列】阶段2Day 24数据结构 — 列表、字典、集合、元组-CSDN博客【FDE系列】阶段2Day 25文件读写与 JSON — 让程序连通外部数据第一周收官-CSDN博客【FDE系列】阶段2Day 26模块化编程 — 把代码拆成“抽屉柜“-CSDN博客【FDE系列】阶段2Day 27异常处理与日志 — 让程序“摔不烂、查得到“-CSDN博客FastAPI入门到进阶【FDE系列】阶段2Day 28FastAPI 入门 — 把你的函数变成 API 服务-CSDN博客【FDE系列】阶段2Day 29FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战-CSDN博客【FDE系列】阶段2Day 30生产代码规范 — 测试、类型注解、配置管理第二周收官-CSDN博客SQL基础【FDE系列】阶段2Day 31SQL 基础 — 增删改查一把梭-CSDN博客【FDE系列】阶段2Day 32多表查询 — JOIN 与聚合-CSDN博客【FDE系列】阶段2Day 33进阶查询 — 窗口函数与 CTE-CSDN博客【FDE系列】阶段2Day 34数据清洗 — 把脏数据捋干净-CSDN博客【FDE系列】阶段2Day 35Python SQL — 工单接入 MySQL 本周收官-CSDN博客Linux基础【FDE系列】阶段2Day 36Linux 入门与文件操作 — 扔掉鼠标的第一天-CSDN博客【FDE系列】阶段2Day 37权限、进程与文本三剑客-CSDN博客【FDE系列】阶段2Day 38Shell 脚本 — 把命令串起来自动跑-CSDN博客【FDE系列】阶段2Day 39Linux 综合实战 — 让服务无人值守-CSDN博客【FDE系列】阶段2Day 40Shell 进阶 — 生产级脚本与本周收官-CSDN博客Docker【FDE系列】阶段2Day 41Docker 入门 — 把环境装进盒子-CSDN博客【FDE系列】阶段2Day 41up详细版Docker 入门 — 把环境装进盒子-CSDN博客【FDE系列】阶段2Day 42Dockerfile 实战 — 把你的应用打包成镜像-CSDN博客【FDE系列】阶段2Day 42UP详细版Dockerfile 实战 — 把你的应用打包成镜像-CSDN博客

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑