Docker Compose 紧急救援:凌晨3点服务器报警的解决方案
凌晨3点,你还躺在床上,手机突然响了。是服务器报警短信,提示网站因 MySQL 服务异常而无法访问。你迅速爬起来,打开笔记本,心想如果能快速恢复服务,该多好。这时,你记起 Docker Compose 是处理这种情况的理想工具,因为可以轻松地重新部署整个应用栈。
想象一下,如果每次服务出现问题,都需要手动启动每个容器,检查日志,处理异常,不仅耗时,还容易出错。Docker Compose 通过一个 docker-compose.yml 文件,可以一口气管理多个 Docker 容器,让应用的部署和恢复变得简单、高效。今天,我们就从这个深夜报警的场景开始,一起看看如何使用 Docker Compose 解决问题。
场景设定
你的网站由两个主要部分组成:一个基于 Node.js 的 Web 应用,和一个 MySQL 数据库。两个部分分别运行在不同的 Docker 容器中。在 docker-compose.yml 文件的帮助下,可以快速启动、停止和重启整个应用栈,而无需分别管理每个容器。
准备 docker-compose.yml 文件
在一个平静的白天,你需要先准备好 docker-compose.yml 文件,确保在紧急情况时可以立即使用。假设你的项目目录结构如下:
csharp
myapp/
├── docker-compose.yml
├── web/
│ └── Dockerfile
└── db/
└── init.sql
docker-compose.yml 文件内容如下:
yaml
version: '3.8' # 指定 compose 文件版本
services: # 定义服务
web: # Web 应用服务
build: ./web # 指定构建目录
ports: # 映射端口
- "4000:3000" # 将主机的 4000 端口映射到容器的 3000 端口
depends_on: # 依赖的服务
- db # 依赖 db 服务
environment: # 环境变量
MYSQL_HOST: db # 指定 MySQL 主机名
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: appdb
db: # 数据库服务
image: mysql:5.7 # 使用官方的 MySQL 镜像
volumes: # 挂载卷
- db_data:/var/lib/mysql # 持久化数据
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化数据库脚本
environment: # 环境变量
MYSQL_ROOT_PASSWORD: example # 设置 root 用户的密码
MYSQL_DATABASE: appdb # 创建数据库
volumes: # 定义卷
db_data: # 数据卷名称
构建和启动应用
在白天,你可以构建和启动你的应用,确保一切正常。打开终端,进入项目目录,运行以下命令:
bash
docker-compose up --build # 构建并启动所有服务
这条命令会先构建 web 服务的 Docker 镜像,然后启动 web 和 db 服务。--build 选项表示强制重新构建镜像,确保最新代码被使用。
检查服务状态
启动服务后,可以使用以下命令检查服务状态:
bash
docker-compose ps # 查看所有服务的运行状态
如果一切正常,你会看到 web 和 db 服务都在运行中。如果有问题,可以通过 docker-compose logs 查看日志,定位问题:
bash
docker-compose logs # 查看所有服务的日志
docker-compose logs web # 仅查看 web 服务的日志
服务异常处理
回到那个不眠之夜,你发现 MySQL 服务崩溃了。这个问题可能是因为磁盘空间不足、配置错误或其他原因。使用 Docker Compose,你可以迅速停止并重启数据库服务,而无需重新启动整个应用:
bash
docker-compose stop db # 停止 db 服务
docker-compose start db # 启动 db 服务
如果重启服务后问题依然存在,可以尝试重新构建数据库服务:
bash
docker-compose up --build db # 重新构建并启动 db 服务
数据库初始化
假设你新添加了一张表,需要重新初始化数据库。你可以通过修改 init.sql 文件,然后重新启动数据库服务来实现:
sql
-- db/init.sql
CREATE DATABASE appdb;
USE appdb;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
保存文件后,重新启动数据库服务:
bash
docker-compose up --build db # 重新构建并启动 db 服务
这次启动时,Docker 会执行 init.sql 文件中的 SQL 命令,完成数据库的初始化。
持久化数据
为了避免数据库数据丢失,Docker Compose 使用卷来持久化数据。在 docker-compose.yml 文件中,我们定义了一个名为 db_data 的卷,并将其挂载到 MySQL 容器的数据目录。这样,即使容器被删除,数据依然保留。
环境变量管理
在 docker-compose.yml 文件中,我们通过 environment 部分设置了环境变量。这些环境变量可以在应用中使用,例如在 Node.js 应用中,可以通过 process.env.MYSQL_HOST 获取 MySQL 服务的主机名。
网络配置
默认情况下,Docker Compose 会为每个项目创建一个默认网络,所有服务都在这个网络中通信。你也可以自定义网络,例如:
yaml
version: '3.8'
services:
web:
build: ./web
ports:
- "4000:3000"
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: appdb
networks: # 指定网络
- appnet # 使用 appnet 网络
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
networks:
appnet: # 定义 appnet 网络
通过自定义网络,你可以更好地控制服务之间的通信。
高级用法:服务扩展
假设你的 Web 应用需要扩展,以应对突然增加的流量。Docker Compose 支持服务扩展,你只需要在 docker-compose.yml 文件中添加 deploy 部分:
yaml
version: '3.8'
services:
web:
build: ./web
ports:
- "4000:3000"
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
deploy: # 部署配置
replicas: 3 # 设置副本数量
restart_policy: # 重启策略
condition: on-failure # 只在失败时重启
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
networks:
appnet:
volumes:
db_data:
使用 docker-compose up --scale web=3 命令可以启动 3 个 web 服务的副本,实现负载均衡。
服务配置更新
假设你发现 Web 应用的端口需要从 3000 改为 8080。只需修改 docker-compose.yml 文件中的 ports 部分,然后重启服务:
yaml
version: '3.8'
services:
web:
build: ./web
ports:
- "4000:8080" # 修改端口映射
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
networks:
appnet:
volumes:
db_data:
保存文件后,运行以下命令更新服务配置:
bash
docker-compose up --force-recreate # 强制重新创建并启动所有服务
定时任务
有时候,你可能需要定期执行一些维护任务,例如备份数据库。Docker Compose 本身不提供定时任务功能,但你可以使用一个定时任务容器来实现。创建一个新的 backup 服务,并使用 Cron 表达式来定义任务调度:
yaml
version: '3.8'
services:
web:
build: ./web
ports:
- "4000:8080"
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: appdb
networks:
- appnet
backup:
image: alpine:latest
volumes:
- ./backup:/backup # 挂载备份脚本
command: sh -c "crond && tail -f /dev/null" # 启动 Cron 守护进程
environment:
CRON_SCHEDULE: "0 2 * * *" # 每天凌晨2点执行
BACKUP_COMMAND: "mysqldump -h db -u root -pexample appdb > /backup/appdb.sql" # 备份命令
networks:
- appnet
networks:
appnet:
volumes:
db_data:
在这个例子中,backup 服务使用 Alpine 镜像,挂载了一个备份脚本目录,并通过环境变量 CRON_SCHEDULE 和 BACKUP_COMMAND 来定义定时任务和备份命令。你可以在 ./backup 目录中创建一个 crontab 文件来配置定时任务:
bash
# ./backup/crontab
0 2 * * * mysqldump -h db -u root -pexample appdb > /backup/appdb.sql # 每天凌晨2点备份数据库
然后运行以下命令启动所有服务:
bash
docker-compose up --build # 构建并启动所有服务
常用命令汇总
为了方便你在紧急情况下快速操作,这里列出一些常用的 Docker Compose 命令:
docker-compose up --build:构建并启动所有服务docker-compose ps:查看服务状态docker-compose logs [service]:查看服务日志docker-compose stop [service]:停止指定服务docker-compose start [service]:启动指定服务docker-compose up --force-recreate:强制重新创建并启动所有服务docker-compose down:停止并删除所有服务及其网络和卷
自然推荐 Hey Cron
在处理定时任务时,如果你不熟悉 Cron 表达式或者需要生成复杂的定时任务,可以考虑使用 Hey Cron。Hey Cron 是一个免费在线工具网站,提供中文描述秒转 Cron 的功能,让你轻松生成 Cron 表达式。它还支持正则表达式生成器、中英互译、JSON 格式化、Base64 编码解码、时间戳转换和 JWT 解析等多种实用工具,非常适合开发者的日常使用。
现在,你可以回到床上,安心睡觉了。Docker Compose 和 Hey Cron 都已经准备好,下次再遇到这种紧急情况,你可以迅速解决问题,继续享受你的美梦。