【docker基础】 第七课:Docker Compose 多容器实战

📚前言

👀回顾,系统学习docker系列已发布内容:

【docker基础】0、系统学习docker之总计划

【docker基础】第一课、从零开始理解容器技术

【docker基础】第二课:安装、配置与基础命令

【docker基础】第三课:镜像管理与Dockerfile基础

【docker基础】第四课:容器操作与数据管理

【docker基础】第五课:Docker网络详解-CSDN博客

【docker基础】第六课:Web应用与数据库容器部署-CSDN博客

🔗相关文档:

windows下安装docker

【docker基础】Ubuntu 安装 Docker 超详细小白教程

📒本课学习目标:

  1. Docker Compose 基础:安装和基本概念

  2. YAML 配置文件:语法基础和 docker-compose.yml 结构

  3. 服务定义与依赖:环境变量、数据卷、网络配置

  4. 多服务应用部署:前端 + 后端 + 数据库的完整配置

  5. 服务间通信:容器名称解析和网络连接

  6. 常用命令:up/down/ps/logs/exec 等


🌍Docker 第七周学习教程:Docker Compose 实战

欢迎来到 Docker 第七周的学习!本周我们将深入学习 Docker Compose,这是 Docker 官方提供的用于定义和运行多容器应用的工具。通过 Docker Compose,您可以用一个 YAML 文件来配置应用的所有服务,然后使用一个命令即可启动所有服务。


第一章:Docker Compose 基础

1.1 什么是 Docker Compose

Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。它允许您通过一个 docker-compose.yml 文件来配置应用所需的所有服务,然后使用单个命令即可启动、停止和管理整个应用栈。

为什么需要 Docker Compose?

在之前的学习中,我们部署一个完整的应用需要:

  1. 启动 MySQL 容器

  2. 启动后端 API 容器

  3. 启动前端 Nginx 容器

  4. 配置容器之间的网络连接

  5. 配置数据持久化

这个过程非常繁琐,需要执行多个命令。而 Docker Compose 可以让这一切变得简单!

1.2 Docker Compose 的安装

检查是否已安装

首先检查系统中是否已经安装了 Docker Compose:

复制代码
docker-compose --version

预期输出:

复制代码
docker-compose version 1.29.2, build 5becea4c

如果显示版本信息,说明已经安装。如果提示命令不存在,请继续下面的安装步骤。

在 Linux 系统上安装
复制代码
# 下载 Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 创建软链接
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
在 Windows 和 macOS 上安装

Docker Desktop for Windows 和 macOS 已经包含了 Docker Compose,安装 Docker Desktop 后即可使用。

1.3 Docker Compose 的基本工作流程

使用 Docker Compose 的典型流程:

  1. 创建 docker-compose.yml 文件,定义所有服务

  2. 运行 docker-compose up 启动所有服务

  3. 运行 docker-compose down 停止并移除所有服务


第二章:YAML 配置文件基础

2.1 什么是 YAML

YAML 是一种人类可读的数据序列化格式,常用于配置文件。它使用缩进(通常是 2 个空格)来表示层级结构。

YAML 的基本语法规则:

  • 使用缩进表示层级(不要使用 Tab 键

  • 键值对使用冒号 : 分隔,冒号后必须有空格

  • 列表使用短横线 - 开头

  • 字符串不需要引号(除非包含特殊字符)

2.2 YAML 示例

复制代码
# 这是注释

# 键值对
name: myapp
version: "1.0"
debug: false

# 列表
services:
  - web
  - db
  - redis

# 嵌套结构
database:
  host: localhost
  port: 3306
  credentials:
    username: admin
    password: secret

2.3 docker-compose.yml 文件结构

docker-compose.yml 文件的基本结构如下:

复制代码
# 指定 Compose 文件格式版本
version: '3.8'

# 定义所有服务
services:
  # 服务名称(自定义)
  service_name:
    # 使用的镜像
    image: image_name:tag
    # 或者构建本地 Dockerfile
    build: ./path/to/dockerfile
    # 端口映射
    ports:
      - "host_port:container_port"
    # 环境变量
    environment:
      - ENV_VAR=value
    # 数据卷挂载
    volumes:
      - host_path:container_path
    # 依赖关系
    depends_on:
      - another_service

第三章:服务定义与依赖

3.1 服务配置详解

让我们通过一个完整的示例来理解服务配置:

复制代码
version: '3.8'

services:
  # 定义 MySQL 服务
  db:
    # 使用 MySQL 5.7 镜像
    image: mysql:5.7
    # 容器名称
    container_name: mymysql
    # 环境变量
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: myappdb
    # 数据卷挂载(持久化)
    volumes:
      - ./mysql/data:/var/lib/mysql
    # 端口映射
    ports:
      - "3306:3306"
    # 重启策略
    restart: always

配置项说明:

配置项 说明 示例
image 使用的 Docker 镜像 mysql:5.7
container_name 容器名称 mymysql
environment 环境变量 MYSQL_ROOT_PASSWORD: 123456
volumes 数据卷挂载 ./mysql/data:/var/lib/mysql
ports 端口映射 "3306:3306"
restart 重启策略 always(总是重启)

3.2 环境变量配置

环境变量有三种配置方式:

方式一:直接键值对

复制代码
environment:
  MYSQL_ROOT_PASSWORD: 123456
  MYSQL_DATABASE: myappdb

方式二:列表形式

复制代码
environment:
  - MYSQL_ROOT_PASSWORD=123456
  - MYSQL_DATABASE=myappdb

方式三:从文件加载

复制代码
env_file:
  - .env

.env 文件内容:

复制代码
MYSQL_ROOT_PASSWORD=123456
MYSQL_DATABASE=myappdb

3.3 数据卷挂载

数据卷挂载用于实现数据持久化,有两种方式:

方式一:绑定挂载(主机目录)

复制代码
volumes:
  - ./mysql/data:/var/lib/mysql
  - ./frontend:/usr/share/nginx/html

方式二:命名卷

复制代码
volumes:
  db_data:

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql

区别:

  • 绑定挂载:将主机的具体目录挂载到容器

  • 命名卷:由 Docker 管理,存储在 Docker 指定的目录

3.4 依赖关系配置

使用 depends_on 可以指定服务的启动顺序:

复制代码
services:
  backend:
    build: ./backend
    depends_on:
      - db  # 等待 db 服务启动后再启动

注意: depends_on 只会等待容器启动,不会等待服务完全就绪(如数据库启动完成)。


第四章:多服务应用部署实战

4.1 项目架构

我们将部署一个完整的 Web 应用:

  • 前端: Nginx 托管静态页面

  • 后端: Python Flask API

  • 数据库: MySQL

4.2 创建项目结构

复制代码
mkdir -p ~/compose-demo/{backend,frontend}
cd ~/compose-demo

4.3 创建后端 API (Flask)

创建 backend/app.py

复制代码
from flask import Flask, jsonify, request
from flask_cors import CORS
import pymysql
import os

app = Flask(__name__)
CORS(app)

# 从环境变量获取数据库配置
db_host = os.environ.get('DB_HOST', 'localhost')
db_user = os.environ.get('DB_USER', 'root')
db_password = os.environ.get('DB_PASSWORD', '123456')
db_name = os.environ.get('DB_NAME', 'myappdb')

# 数据库连接
db = pymysql.connect(
    host=db_host,
    user=db_user,
    password=db_password,
    database=db_name
)

@app.route('/api/users', methods=['GET'])
def get_users():
    cursor = db.cursor()
    cursor.execute("SELECT * FROM users")
    users = cursor.fetchall()
    cursor.close()
    
    result = []
    for user in users:
        result.append({
            'id': user[0],
            'name': user[1],
            'email': user[2],
            'created_at': str(user[3])
        })
    return jsonify(result)

@app.route('/api/users', methods=['POST'])
def add_user():
    data = request.json
    name = data.get('name')
    email = data.get('email')
    
    cursor = db.cursor()
    sql = "INSERT INTO users (name, email) VALUES (%s, %s)"
    cursor.execute(sql, (name, email))
    db.commit()
    cursor.close()
    
    return jsonify({'message': '用户添加成功', 'id': cursor.lastrowid}), 201

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

创建 backend/requirements.txt

复制代码
Flask==2.0.1
Flask-CORS==3.0.10
PyMySQL==1.0.2

创建 backend/Dockerfile

复制代码
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

4.4 创建前端页面

创建 frontend/index.html

复制代码
<!DOCTYPE html>
<html>
<head>
    <title>用户管理系统 - Compose 版</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background: #f5f5f5;
        }
        h1 {
            color: #2c3e50;
            text-align: center;
        }
        .form-container {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        input {
            width: 100%;
            padding: 10px;
            margin: 10px 0;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
        }
        button {
            background: #3498db;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #2980b9;
        }
        .user-card {
            background: white;
            padding: 15px;
            margin: 10px 0;
            border-radius: 8px;
            box-shadow: 0 1px 5px rgba(0,0,0,0.05);
            border-left: 4px solid #3498db;
        }
    </style>
</head>
<body>
    <h1>👥 用户管理系统</h1>
    
    <div class="form-container">
        <h3>添加新用户</h3>
        <input type="text" id="name" placeholder="姓名">
        <input type="email" id="email" placeholder="邮箱">
        <button onclick="addUser()">添加用户</button>
    </div>
    
    <div id="userList">
        <!-- 用户列表 -->
    </div>

    <script>
        async function loadUsers() {
            const response = await fetch('http://localhost:5000/api/users');
            const users = await response.json();
            displayUsers(users);
        }

        function displayUsers(users) {
            const userList = document.getElementById('userList');
            userList.innerHTML = '';
            
            users.forEach(user => {
                const card = document.createElement('div');
                card.className = 'user-card';
                card.innerHTML = `
                    <h4>${user.name}</h4>
                    <p>邮箱: ${user.email}</p>
                    <p>创建时间: ${user.created_at}</p>
                `;
                userList.appendChild(card);
            });
        }

        async function addUser() {
            const name = document.getElementById('name').value;
            const email = document.getElementById('email').value;
            
            await fetch('http://localhost:5000/api/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name, email })
            });
            
            document.getElementById('name').value = '';
            document.getElementById('email').value = '';
            loadUsers();
        }

        window.onload = loadUsers;
    </script>
</body>
</html>

4.5 创建 docker-compose.yml

创建 docker-compose.yml 文件:

复制代码
# 指定 Compose 文件版本
version: '3.8'

# 定义服务
services:
  # MySQL 数据库服务
  db:
    image: mysql:5.7
    container_name: mymysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: myappdb
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: always

  # Flask 后端服务
  backend:
    build: ./backend
    container_name: mybackend
    ports:
      - "5000:5000"
    depends_on:
      - db
    environment:
      - DB_HOST=db
      - DB_USER=root
      - DB_PASSWORD=123456
      - DB_NAME=myappdb
    restart: always

  # Nginx 前端服务
  frontend:
    image: nginx:latest
    container_name: myfrontend
    ports:
      - "80:80"
    volumes:
      - ./frontend:/usr/share/nginx/html
    restart: always

# 定义命名卷
volumes:
  mysql_data:

4.6 启动应用

在项目目录下执行:

复制代码
docker-compose up -d

命令解释:

  • docker-compose up: 启动所有服务

  • -d: 后台运行(detached 模式)

预期输出:

复制代码
Creating network "compose-demo_default" with the default driver
Creating volume "compose-demo_mysql_data" with default driver
Building backend
Step 1/6 : FROM python:3.9-slim
...
Successfully built abc123def456
Successfully tagged compose-demo_backend:latest
Creating mymysql ... done
Creating mybackend ... done
Creating myfrontend ... done

4.7 验证服务

查看正在运行的容器:

复制代码
docker-compose ps

预期输出:

复制代码
    Name                   Command               State                 Ports
-----------------------------------------------------------------------------------
mybackend   python app.py                        Up      0.0.0.0:5000->5000/tcp
myfrontend  /docker-entrypoint.sh ngin ...       Up      0.0.0.0:80->80/tcp
mymysql     docker-entrypoint.sh mysqld          Up      0.0.0.0:3306->3306/tcp

打开浏览器访问 http://localhost,您会看到用户管理系统页面!


第五章:服务间通信

5.1 容器名称解析

Docker Compose 会自动创建一个专用网络,所有服务都连接到这个网络上。在这个网络中,容器可以通过服务名称进行通信。

例如,在我们的应用中:

  • backend 服务可以通过 db 访问 MySQL 数据库

  • frontend 服务可以通过 backendlocalhost:5000 访问后端 API

5.2 网络配置

Docker Compose 默认会创建一个名为 项目名_default 的网络。您也可以自定义网络:

复制代码
version: '3.8'

networks:
  mynetwork:
    driver: bridge

services:
  db:
    image: mysql:5.7
    networks:
      - mynetwork
  
  backend:
    build: ./backend
    networks:
      - mynetwork

5.3 服务间通信示例

在后端代码中,我们使用 db 作为数据库主机名:

复制代码
db = pymysql.connect(
    host='db',  # 服务名称作为主机名
    user='root',
    password='123456',
    database='myappdb'
)

这是因为 Docker Compose 会在内部 DNS 中注册服务名称,使得容器之间可以通过服务名互相访问。


第六章:Docker Compose 常用命令

6.1 启动服务

复制代码
# 前台启动(会显示所有日志)
docker-compose up

# 后台启动
docker-compose up -d

# 启动指定服务
docker-compose up -d backend

6.2 查看服务状态

复制代码
docker-compose ps

输出示例:

复制代码
    Name                   Command               State                 Ports
-----------------------------------------------------------------------------------
mybackend   python app.py                        Up      0.0.0.0:5000->5000/tcp
myfrontend  /docker-entrypoint.sh ngin ...       Up      0.0.0.0:80->80/tcp
mymysql     docker-entrypoint.sh mysqld          Up      0.0.0.0:3306->3306/tcp

6.3 查看日志

复制代码
# 查看所有服务日志
docker-compose logs

# 查看指定服务日志
docker-compose logs backend

# 实时查看日志(类似 tail -f)
docker-compose logs -f backend

6.4 进入容器

复制代码
docker-compose exec <服务名> <命令>

# 进入 MySQL 容器
docker-compose exec db mysql -uroot -p123456

# 进入后端容器
docker-compose exec backend /bin/bash

6.5 停止服务

复制代码
# 停止服务但保留容器
docker-compose stop

# 停止并移除容器(但保留数据卷)
docker-compose down

# 停止并移除容器和数据卷
docker-compose down -v

6.6 构建镜像

复制代码
# 构建所有服务的镜像
docker-compose build

# 构建指定服务的镜像
docker-compose build backend

# 强制重新构建
docker-compose build --no-cache backend

6.7 重启服务

复制代码
# 重启所有服务
docker-compose restart

# 重启指定服务
docker-compose restart backend

6.8 查看服务配置

复制代码
# 查看服务的运行配置
docker-compose config

# 验证配置文件是否正确
docker-compose config -q

第七章:实战练习 - 部署带 Redis 的应用

📌Redis (Remote Dictionary Server) 是一个开源的 内存数据结构存储系统 ,可以用作数据库、缓存、消息代理和队列:

  • 数据主要存储在 内存 中,读写速度极快

  • 支持数据持久化到磁盘(可选)

7.1 添加 Redis 服务

修改 docker-compose.yml,添加 Redis 服务:

复制代码
version: '3.8'

services:
  db:
    image: mysql:5.7
    container_name: mymysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: myappdb
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: always

  redis:
    image: redis:6
    container_name: myredis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: always

  backend:
    build: ./backend
    container_name: mybackend
    ports:
      - "5000:5000"
    depends_on:
      - db
      - redis
    environment:
      - DB_HOST=db
      - DB_USER=root
      - DB_PASSWORD=123456
      - DB_NAME=myappdb
      - REDIS_HOST=redis
    restart: always

  frontend:
    image: nginx:latest
    container_name: myfrontend
    ports:
      - "80:80"
    volumes:
      - ./frontend:/usr/share/nginx/html
    restart: always

volumes:
  mysql_data:
  redis_data:

7.2 更新后端代码使用 Redis

更新 backend/app.py

复制代码
from flask import Flask, jsonify, request
from flask_cors import CORS
import pymysql
import redis
import os

app = Flask(__name__)
CORS(app)

# 数据库配置
db_host = os.environ.get('DB_HOST', 'localhost')
db_user = os.environ.get('DB_USER', 'root')
db_password = os.environ.get('DB_PASSWORD', '123456')
db_name = os.environ.get('DB_NAME', 'myappdb')

# Redis 配置
redis_host = os.environ.get('REDIS_HOST', 'localhost')

# 数据库连接
db = pymysql.connect(host=db_host, user=db_user, password=db_password, database=db_name)

# Redis 连接
r = redis.Redis(host=redis_host, port=6379, decode_responses=True)

@app.route('/api/users', methods=['GET'])
def get_users():
    # 先从 Redis 缓存获取
    cache_key = 'users_cache'
    cached_users = r.get(cache_key)
    
    if cached_users:
        import json
        return jsonify(json.loads(cached_users))
    
    # 从数据库获取
    cursor = db.cursor()
    cursor.execute("SELECT * FROM users")
    users = cursor.fetchall()
    cursor.close()
    
    result = []
    for user in users:
        result.append({
            'id': user[0],
            'name': user[1],
            'email': user[2],
            'created_at': str(user[3])
        })
    
    # 存入 Redis 缓存,有效期 60 秒
    r.setex(cache_key, 60, str(result))
    
    return jsonify(result)

@app.route('/api/users', methods=['POST'])
def add_user():
    data = request.json
    name = data.get('name')
    email = data.get('email')
    
    cursor = db.cursor()
    sql = "INSERT INTO users (name, email) VALUES (%s, %s)"
    cursor.execute(sql, (name, email))
    db.commit()
    cursor.close()
    
    # 清除缓存
    r.delete('users_cache')
    
    return jsonify({'message': '用户添加成功', 'id': cursor.lastrowid}), 201

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

更新 backend/requirements.txt

复制代码
Flask==2.0.1
Flask-CORS==3.0.10
PyMySQL==1.0.2
redis==4.3.4

7.3 重新启动服务

复制代码
# 停止旧服务
docker-compose down

# 重新构建并启动
docker-compose up -d --build

7.4 验证 Redis 缓存

复制代码
# 进入 Redis 容器
docker-compose exec redis redis-cli

# 查看缓存键
KEYS *

# 获取缓存内容
GET users_cache

第八章:常见问题与解决方案

8.1 服务启动顺序问题

问题: 后端服务启动时,数据库可能还未就绪,导致连接失败。

解决方案: 使用 depends_on 配合健康检查:

复制代码
services:
  db:
    image: mysql:5.7
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p123456"]
      interval: 5s
      timeout: 5s
      retries: 5

  backend:
    build: ./backend
    depends_on:
      db:
        condition: service_healthy

8.2 端口冲突问题

问题: 端口已被占用。

解决方案: 修改 docker-compose.yml 中的端口映射:

复制代码
services:
  frontend:
    image: nginx
    ports:
      - "8080:80"  # 将 8080 改为其他未被占用的端口

8.3 数据卷权限问题

问题: 挂载的目录权限不足。

解决方案: 在 Dockerfile 中设置正确的用户权限,或使用命名卷。

8.4 配置文件热更新

问题: 修改配置后需要重启容器。

解决方案: 对于开发环境,可以使用绑定挂载实现热更新:

复制代码
services:
  backend:
    build: ./backend
    volumes:
      - ./backend:/app  # 挂载代码目录

本周总结

本周我们学习了:

  1. Docker Compose 基础:安装和基本概念

  2. YAML 配置文件:语法基础和 docker-compose.yml 结构

  3. 服务定义与依赖:环境变量、数据卷、网络配置

  4. 多服务应用部署:前端 + 后端 + 数据库的完整配置

  5. 服务间通信:容器名称解析和网络连接

  6. 常用命令:up/down/ps/logs/exec 等

练习作业:

  1. 使用 Docker Compose 部署一个 WordPress 博客(提示:需要 WordPress 镜像和 MySQL)

  2. 尝试添加 Redis 缓存到您的应用中

  3. 学习使用 Docker Compose 的健康检查功能


命令速查表

操作 命令
启动服务 docker-compose up -d
停止服务 docker-compose down
查看状态 docker-compose ps
查看日志 docker-compose logs -f <服务名>
进入容器 docker-compose exec <服务名> /bin/bash
构建镜像 docker-compose build
重启服务 docker-compose restart
验证配置 docker-compose config -q
相关推荐
翼龙云_cloud1 小时前
阿里云代理商:部署 DeepSeek V4-Flash解析 快速部署与性能优化
运维·阿里云·性能优化·云计算·ai智能体
ElevenS_it1881 小时前
网络设备配置合规审计自动化实战:用Nornir+Netmiko自动比对华为/Cisco/H3C配置基线+合规报告自动生成
运维·网络·自动化
正经教主1 小时前
【docker基础】Redis的docker部署
redis·docker·容器
施努卡机器视觉1 小时前
SNK施努卡 | 电子油泵自动化生产线:精密制造的技术跃迁与产业价值
运维·自动化·制造
ShyanZh1 小时前
【skill】Agent-Browser:AI代理的浏览器自动化实战指南
运维·人工智能·自动化·skill·agent-browser
KKKlucifer1 小时前
智能研判、本地运算、一键运维:新一代安全管控产品的三大核心能力
运维·安全
MXsoft6181 小时前
##务健康度评分:将运维指标转化为业务价值的实践指南
运维
難釋懷2 小时前
Nginx使用sticky模块完成对Nginx的负载均衡
运维·nginx·负载均衡
ShirleyWang0122 小时前
win11运行ubuntu报错
linux·运维·ubuntu