Docker Compose实战:一键部署微服务项目

前言

之前部署项目都是手动部署,先装MySQL、Redis、Nginx,然后打包jar包上传到服务器

服务少的时候还行,一多起来就特别麻烦,而且容易出错

所以决定用Docker Compose来统一管理,实现一键部署

其实Docker早就用过,但是都是单个容器跑,这次系统性地用Compose编排服务

Docker Compose简介

Docker Compose是一个用于定义和运行多容器Docker应用程序的工具

通过YAML文件配置服务,然后用一个命令就能启动所有服务

安装的话,Docker Desktop for Windows/Mac自带了,Linux的话单独安装就行

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project/
├── docker-compose.yml
├── mysql/
│ └── init.sql
├── nginx/
│ ├── nginx.conf
│ └── html/
├── services/
│ ├── user-service/
│ ├── order-service/
│ └── gateway-service/
└── docker/
├── user-service/Dockerfile
├── order-service/Dockerfile
└── gateway-service/Dockerfile

编写Dockerfile

先为每个微服务编写Dockerfile

基础镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# user-service/Dockerfile
FROM openjdk:17-jdk-slim

LABEL maintainer="zhengru"

WORKDIR /app

# 复制jar包
COPY target/user-service.jar app.jar

# 暴露端口
EXPOSE 8081

# 启动参数
ENTRYPOINT ["java", "-jar", "-Xms512m", "-Xmx512m", "app.jar"]

多阶段构建(推荐)

如果觉得镜像太大,可以用多阶段构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# user-service/Dockerfile
# 构建阶段
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=builder /app/target/user-service.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]

其他服务的Dockerfile类似,就是改一下jar包名和端口

编写docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
version: '3.8'

services:
# MySQL服务
mysql:
image: mysql:8.0
container_name: project-mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: project_db
ports:
- "3306:3306"
volumes:
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
- mysql-data:/var/lib/mysql
networks:
- project-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3

# Redis服务
redis:
image: redis:7-alpine
container_name: project-redis
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- project-network

# Nginx服务
nginx:
image: nginx:alpine
container_name: project-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/html:/usr/share/nginx/html
- nginx-logs:/var/log/nginx
depends_on:
- gateway
networks:
- project-network

# 网关服务
gateway:
build:
context: .
dockerfile: docker/gateway-service/Dockerfile
container_name: project-gateway
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
MYSQL_HOST: mysql
MYSQL_PORT: 3306
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- project-network
restart: always

# 用户服务
user-service:
build:
context: .
dockerfile: docker/user-service/Dockerfile
container_name: project-user-service
ports:
- "8081:8081"
environment:
SPRING_PROFILES_ACTIVE: prod
MYSQL_HOST: mysql
MYSQL_PORT: 3306
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
- mysql
- redis
networks:
- project-network
restart: always

# 订单服务
order-service:
build:
context: .
dockerfile: docker/order-service/Dockerfile
container_name: project-order-service
ports:
- "8082:8082"
environment:
SPRING_PROFILES_ACTIVE: prod
MYSQL_HOST: mysql
MYSQL_PORT: 3306
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
- mysql
- redis
networks:
- project-network
restart: always

networks:
project-network:
driver: bridge

volumes:
mysql-data:
redis-data:
nginx-logs:

Nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
keepalive_timeout 65;

# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# 上游服务
upstream gateway {
server gateway:8080;
}

server {
listen 80;
server_name localhost;

# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

# API代理
location /api/ {
limit_req zone=api_limit burst=20 nodelay;

proxy_pass http://gateway/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}

MySQL初始化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- mysql/init.sql
CREATE DATABASE IF NOT EXISTS project_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE project_db;

CREATE TABLE IF NOT EXISTS user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO user (username, password, email) VALUES
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'admin@example.com');

常用命令

构建并启动所有服务

1
docker-compose up -d --build

查看服务状态

1
docker-compose ps

查看日志

1
2
3
4
5
# 查看所有服务日志
docker-compose logs -f

# 查看某个服务的日志
docker-compose logs -f user-service

停止服务

1
docker-compose stop

停止并删除容器

1
docker-compose down

停止并删除容器和卷

1
docker-compose down -v

重启某个服务

1
docker-compose restart user-service

进入容器

1
2
3
4
5
6
7
8
# 进入MySQL容器
docker-compose exec mysql bash

# 进入Redis容器
docker-compose exec redis sh

# 进入服务容器查看日志
docker-compose exec user-service sh

环境变量管理

如果不想把配置写在docker-compose.yml里,可以用.env文件:

1
2
3
4
5
6
7
8
9
10
# .env
MYSQL_VERSION=8.0
MYSQL_ROOT_PASSWORD=root123456
MYSQL_DATABASE=project_db

REDIS_VERSION=7-alpine

GATEWAY_PORT=8080
USER_SERVICE_PORT=8081
ORDER_SERVICE_PORT=8082

然后在docker-compose.yml里引用:

1
2
3
4
5
6
services:
mysql:
image: mysql:${MYSQL_VERSION}
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}

生产环境部署

生产环境建议:

  1. 使用配置中心管理配置
  2. 敏感信息用secrets管理
  3. 日志集中收集
  4. 监控和告警
  5. 数据定期备份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# docker-compose.prod.yml
version: '3.8'

services:
mysql:
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
secrets:
- mysql_root_password
volumes:
- mysql-backup:/backup

secrets:
mysql_root_password:
external: true

volumes:
mysql-backup:

总结

用Docker Compose部署确实方便很多,特别是微服务项目

一个命令就能把所有服务都起来,而且环境隔离,不容易出问题

主要优势:

  1. 环境一致性
  2. 快速部署
  3. 易于扩展
  4. 配置管理简单

暂时就先记录这么多,后面有其他坑再补充