本项目记录了开发环境从本机配置到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/ │ ├── redis/ │ ├── 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-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 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 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/ │ └── ... └── 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
| FROM node:18
WORKDIR /app
|
没有在Dockerfile里安装依赖,因为考虑到在开发过程中,依赖可能会变化,因此该步骤实际上在启动过程中进行安装(init.sh)
设置好Dockerfile后,在前端环境中引入init脚本,用于在启动时安装依赖,设置环境等操作,位置
1 2 3
| clasp/ └── web/ └── init.sh
|
内容主要是在文件复制完以后要执行的操作
1 2 3 4 5 6
| yarn install
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 - /app/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 });
|
然后在docker中编写测试,测试成功