使用docker-compose来重建开发环境

本项目记录了开发环境从本机配置到docker化的流程,通过docker-compose进行集成

写在前面:为什么要用docker-compose来集成开发环境

当面对全栈的开发、部署时,往往有多种环境的需求(前端+后端+数据库/开发环境+生产部署环境等等)
这时候 当:

  • 换电脑
  • 换服务器
  • 项目组增加新成员

的时候,每次都需要重新配置环境(即使只使用docker也是一个非常大的工作量),而且很可能在配置过程中出现问题

例如一个典型场景:前端(nodejs开发环境/nginx部署环境)+后端(开发环境/部署环境)+ 数据库(mongo+mysql+redis),一个一个部署是非常痛苦的

我们可以使用docker-compose,来解决这个问题
虽然编写compose文件是一个需要精心考虑的过程,但是以后面对:

  • 开发环境部署
  • 生产环境部署
  • 更换电脑、迁移

等场景时,只需要一个命令就能够解决所有问题,可谓一劳永逸,因此我要将全栈项目从开发环境到部署环境,全部迁移成docker-compose,以方便未来的开发与部署
此外,docker-compose集成的环境也更能够保证开发环境与生产环境的一致性,对未来潜在的架构扩充也有充分的空间。

本项目的演进之路 / 提纲

本项目首先从一个单体全栈项目,首先进行容器化,然后逐步演进到一个微服务架构的项目

  • V1:容器化 <- 现在正在进行这个位置
    • ⚠️环境容器化
      • ✅数据库容器化
      • ✅前端容器化(nodejs)
      • ⚠️后端容器化(go)
      • ⚠️爬虫端容器化(python)
    • ✅虚拟网络配置
  • V2:服务拆分、实现微服务架构(饼)
  • V3:容器动态编排、服务发现、负载均衡(k8s、consul)(饼)

1. 什么是docker-compose

Docker Compose是一个用于定义和运行多容器Docker应用程序的工具。它使用一个YAML文件来配置应用程序的各个服务,并使用单个命令启动、停止和管理整个应用程序。
Docker Compose的主要目的是简化多容器应用程序的开发、部署和管理。使用Docker Compose,您可以轻松地定义和配置应用程序的各个服务,并将它们连接在一起,以便它们可以相互通信和协作。Docker Compose还提供了一些实用工具,如容器日志记录、容器重启、容器重建等,使开发人员可以更轻松地管理应用程序的生命周期。

2. V1: 使用docker-compose来重建开发环境(集成环境,单机系统)

2.1 项目结构简介

目前而言,我的个人项目包含一个前端、一个后端、一个爬虫端,爬虫端依赖mongodb,后端依赖mongodb、redis、mysql
在进行改造前,项目结构大致如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
clasp/
├── web/
│ ├── package.json
│ ├── app.vue
│ └── ...
├── backend/
│ ├── main.go
│ ├── go.mod
│ └── ...
└── clasp/ #爬虫端
├── requirements.txt
├── main.py
└── ...

2.2 开始实际上的配置-引入.env文件

实际上的配置和理想中不太一样,因为无论是Dockerfile还是docker-compose.yml,都要按照实际使用情况进行配置
一方面,docker实际上的配置非常简单,另一方面,实际开发中使用git等协作工具,需要考虑到多人协作的情况(比如说隐藏密码等情况),需要把模板模式以及配置内容分离,因此需要使用.env文件来进行配置。
因此先引入.env文件,将配置文件分离出来,方便后续的配置
首先要设计项目结构,预计通过使用docker的卷映射功能,把docker中数据库的数据映射到本地文件系统中,这样可以保证docker中的数据不会丢失
预想的工程结构会变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
clasp/
├── web/
│ ├── package.json
│ ├── app.vue
│ └── ...
├── backend/
│ ├── main.go
│ ├── go.mod
│ └── ...
├── clasp/ #爬虫端
│ ├── requirements.txt
│ ├── main.py
│ └── ...
├── data/ #各种环境/数据库的数据、卷映射
│ ├── mongo/ # mongo的数据
│ ├── redis/ # redis的数据
│ ├── mysql/ # mysql的数据
│ └── ...
└── docker-compose.yml

在工程的根目录创建.env.template文件,内容如下:
其中包括了设计的端口、用户名、密码等信息,这些信息可以在.env文件中进行配置
实际开发时,把env.template文件复制、重命名为.env文件,然后在.env文件中进行配置
注意:不要把.env文件也增加到git中,因为.env文件中包含了敏感信息,应该在.gitignore中进行忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ENABLE_CLASP_MONGO=true
CLASP_MONGO_ROOT_USERNAME=root
CLASP_MONGO_ROOT_PASSWORD=password
CLASP_MONGO_USERNAME=username
CLASP_MONGO_PASSWORD=password
CLASP_MONGO_PORT=27017
CLASP_MONGO_VOLUMES_CONF=./data/mongo/mongod.conf
CLASP_MONGO_VOLUMES_DATA=./data/mongo/db

SKIN_PULSE_DEV_PORT=3000

ENABLE_REDIS=true
REDIS_PORT=6379
REDIS_VOLUMES_DATA=./data/redis/db
REDIS_VOLUMES_CONF=./data/redis/redis.conf
REDIS_PASSWORD=test123

ENABLE_MYSQL=true
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpasswd
MYSQL_NORMAL_USR_USERNAME=mysqlusr
MYSQL_NORMAL_USR_PASSWORD=mysqlpswd
MYSQL_VOLUMES_DATA=./data/mysql/db
MYSQL_VOLUMES_CONF=./data/mysql/conf

2.3 在docker-compose中集成数据库环境

首先,确保env文件的存在,然后在docker-compose.yml中引入env文件
在项目根目录中新建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
version: '1.0'
services:
# 数据库环境
# 爬虫需要的mongodb
mongodb-clasp:
image: mongo
container_name: mongodb-clasp
restart: always
ports:
- "$CLASP_MONGO_PORT:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: $CLASP_MONGO_ROOT_USERNAME
MONGO_INITDB_ROOT_PASSWORD: $CLASP_MONGO_ROOT_PASSWORD
MONGO_INITDB_USERNAME: $CLASP_MONGO_USERNAME
MONGO_INITDB_PASSWORD: $CLASP_MONGO_PASSWORD
volumes:
- $CLASP_MONGO_VOLUMES_CONF:/etc/mongod.conf
- $CLASP_MONGO_VOLUMES_DATA:/data/db
# 后端需要的redis
cs-redis:
image: redis:latest
container_name: cs-redis
restart: always
ports:
- "$REDIS_PORT:6379"
volumes:
- $REDIS_VOLUMES_DATA:/data
- $REDIS_VOLUMES_CONF:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf --requirepass $REDIS_PASSWORD
# 后端需要的mysql
cs-mysql:
image: mysql:latest
container_name: cs-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
MYSQL_USER: $MYSQL_NORMAL_USR_USERNAME
MYSQL_PASSWORD: $MYSQL_NORMAL_USR_PASSWORD
ports:
- "$MYSQL_PORT:3306"
volumes:
- $MYSQL_VOLUMES_DATA:/var/lib/mysql
- $MYSQL_VOLUMES_CONF:/etc/mysql/conf.d

这样就可以通过命令一键运行三个数据库环境,至此单机开发环境的数据库环境就搭建好了

2.4 对开发环境进行引入

首先要明确一点,就是开发环境需要在docker镜象的基础上自己打包镜像,因此首先要调整项目结构,开辟一个专门放置Dockerfile的目录
预想的项目结构会变为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
clasp/
├── web/
│ └── ...
├── backend/
│ └── ...
├── clasp/ #爬虫端
│ └── ...
├── docker/ #自定义镜像
│ ├── nuxt/ # 前端的开发环境
│ │ ├── Dockerfile
│ ├── backend/ # 后端的开发环境
│ │ ├── Dockerfile
│ └── ...
├── data/ #各种环境/数据库的数据、卷映射
│ ├── mongo/ # mongo的数据
│ └── ...
└── docker-compose.yml

2.4.1 前端环境 - 对于Nodejs项目的docker化改造

首先新建Dockerfile,位置:

1
2
3
4
clasp/
└── docker/ #自定义镜像
└── nuxt/ # 前端的开发环境
└── Dockerfile

因为要基于nodejs做基础镜像,然后直接做一些最基础的配置

1
2
3
4
5
6
7
# 使用官方 Node.js 基础镜像
FROM node:18

# 设置工作目录
WORKDIR /app

# ... 其他操作

没有在Dockerfile里安装依赖,因为考虑到在开发过程中,依赖可能会变化,因此该步骤实际上在启动过程中进行安装(init.sh
设置好Dockerfile后,在前端环境中引入init脚本,用于在启动时安装依赖,设置环境等操作,位置

1
2
3
clasp/
└── web/
└── init.sh

内容主要是在文件复制完以后要执行的操作

1
2
3
4
5
6
# init shell for docker
yarn install
#yarn global add pnpm
#pnpm i --shamefully-hoist
#pnpm dev
yarn dev

我的文件里包含了依赖安装以及启动开发环境功能
在创建完成Dockerfile(开发环境)、init.sh(每次启动开发环境时的逻辑)以后,就可以在docker-compose.yml中集成环境了

1
2
3
4
5
6
7
8
9
10
11
12
# 前端环境
nuxt:
container_name: nuxt
build: ./docker/nuxt
ports:
- "$SKIN_PULSE_DEV_PORT:3000"
volumes:
- ./skin-pulse:/app:cached # 挂载项目目录,注意这里的:cached,这是为了提高性能,同时nuxt可以自动监听文件变化
- /app/node_modules #注意!这里使用匿名卷来挂载node_modules,因为不需要让开发环境的node_modules来污染主目录
environment:
- CHOKIDAR_USEPOLLING=true
command: 'bash init.sh'

至此,前端环境集成完毕,通过docker-compose up -d nuxt即可启动前端环境

2.5 虚拟网环境 - 让前端、后端、数据库处于同一虚拟网络中,能够互相访问

在前面的步骤中,我们已经将前端、后端、数据库的环境都集成到了docker-compose中,但是这些环境都是处于宿主机的网络中,因此无法通过容器名来访问,这里我们需要将这些环境放置在同一个虚拟网络中,这样就可以通过容器名来访问了
首先,我们需要在docker-compose.yml中添加一个网络配置(在文件末尾添加)

1
2
3
networks:
clasp_virtual_network:
driver: bridge

(小知识)不同driver类型:

  • Bridge (默认):用于单主机上的容器之间的通信。不同的bridge网络可以通过特定的配置进行通信,如使用IP路由等。
  • Host:容器与主机共享网络堆栈,使容器可以直接访问主机网络。Host网络驱动程序主要用于性能敏感的应用程序,但可能带来安全风险。
  • Overlay:适用于跨多个Docker守护程序(Docker daemon)或跨多个主机上的容器之间的通信。在Docker集群(如Docker Swarm)中使用。
  • Macvlan:使容器能够直接连接到主机网络,而不是通过虚拟网络。这种驱动程序用于高性能场景,但要求主机网络支持VLANs。

然后,在yml中为每个镜像增加networks

1
2
3
4
5
6
7

services:
mongodb-clasp:
image: mongo
#...
networks:
- clasp_virtual_network

在使用上述 docker-compose.yml 配置时,可以通过容器名称来实现服务之间的通信。
以 Nuxt 访问 MongoDB 为例,需要在 Nuxt 应用的配置中使用 MongoDB 容器的名称(在这里是 mongodb-clasp)作为 MongoDB 的主机名。

1
2
3
4
5
6
7
8
9
10
11
12
13
const mongoose = require('mongoose');

const MONGODB_USERNAME = process.env.CLASP_MONGO_USERNAME;
const MONGODB_PASSWORD = process.env.CLASP_MONGO_PASSWORD;
const MONGODB_HOST = 'mongodb-clasp';
const MONGODB_PORT = process.env.CLASP_MONGO_PORT;
const MONGODB_DATABASE = 'your_database_name';

const connectionString = `mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}?authSource=admin`;

mongoose.connect(connectionString, { useNewUrlParser: true, useUnifiedTopology: true });

// ... 其他 Mongoose 配置

然后在docker中编写测试,测试成功


使用docker-compose来重建开发环境
https://tech.jasonczc.cn/2023/guide/guide-use-docker-compose-to-rebuild-environment/
作者
CZY
发布于
2023年5月2日
许可协议