前言
容器是一种虚拟化技术,它将应用程序及其整个运行时环境(所有运行所需的文件)进行打包和隔离。优势是轻量、可移植、快速部署、确保了应用运行环境的一致性。其中 Docker 是最为流行的容器平台之一,提供了一套完整的容器生态系统,包括容器的构建、管理、发布等功能。
本文将使用 Docker 来搭建一个全栈 demo 项目的开发环境和生产环境,并利用 Docker Compose 工具来管理多个容器服务。
项目准备
我们的重点在于 Docker Compose 的使用,所以这里创建一个 Demo 项目作为示例。采用 Node.js 作为后端,使用 koa-generator 工具来生成一个基于 koa 框架的脚手架项目;数据库采用 MySQL,直接使用镜像来运行。
bash
# 安装 koa 模板生成工具
$ npm i koa-generator -g
# 创建脚手架项目
$ koa2 backend
# 进入项目,安装所需依赖,mysql 驱动和 ORM 框架
$ cd backend
$ npm i mysql2 sequelize
前端项目使用 create-vue 工具创建基于 vite 的 vue 项目
bash
# 创建基于 vite vue 项目,目录名 frontend
$ npm create vue@latest frontend
整体目录结构:
bash
|--- demo
|--- backend # 后端项目
|- frontend # 前端项目
|--- docker-compose.yml # docker compose 配置文件
接下来利用 Docker Compose 来搭建相应环境,编写 YML 文件配置。
开发环境搭建
按照 demo 示例,总共可以分为三个服务:node 项目服务、mysql 服务、前端项目服务。
在根目录新建 docker-compose.yml
配置文件,一步步来配置服务。配置参考 compose 文件规范
node 服务配置
-
docker-compose.yml
yaml# docker-compose.yml services: # node 服务 node-server: image: node:18-alpine working_dir: /backend volumes: - ./backend:/backend ports: - "3000:3000" env_file: .env command: npm run dev depends_on: - db-server
在 services
下定义了一个名为 node-server
的服务,一行行看配置:
yaml
image: node:18-alpine
image
字段;由于后端是基于 node 运行,所以需要指定镜像 image
为 node 来启动项目;
yaml
working_dir: /backend
working_dir
字段;数据卷映射,格式为 <宿主机路径>:<容器路径>
,确保容器内部的命令和操作都在正确的工作目录下执行;
yaml
volumes:
- ./backend:/backend
volumes
字段;用路径映射方式将 ./backend
目录映射到容器中的 /backend
,这样容器在启动是运行 command 才找得到项目;
关于
volumes
:可用于定义容器与宿主机之间的数据卷(volumes)映射关系。通过这个字段,可以将宿主机上的目录或文件与容器内部的目录或文件进行关联,从而实现数据的持久化存储或共享。
yaml
ports:
- "3000:3000"
ports
字段;指定宿主机和容器的端口映射,格式 <宿主机端口>:<容器端口>
;
yaml
env_file: .env
env_file
字段;环境变量配置文件,将数据库相关的配置抽成环境变量,方便管理维护,如下;
-
.env
yamlMYSQL_HOST=db-server MYSQL_DATABASE=travel MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=20240101
注意环境变量中 MYSQL_HOST
填容器中运行的数据库服务名,之后在代码中去读取环境变量;
-
db.js
jsx// db.js const Sequelize = require("sequelize"); const MYSQL_HOST = process.env.MYSQL_HOST; const MYSQL_DATABASE = process.env.MYSQL_DATABASE; const MYSQL_PORT = process.env.MYSQL_PORT; const MYSQL_ROOT_PASSWORD = process.env.MYSQL_ROOT_PASSWORD; const sequelize = new Sequelize(MYSQL_DATABASE, "root", MYSQL_ROOT_PASSWORD, { host: MYSQL_HOST, port: MYSQL_PORT, dialect: "mysql", charset: "utf8mb4", // 设置默认字符集 collate: "utf8mb4_unicode_ci", // 设置默认校对规则 });
yaml
command: npm run dev
command
字段;指定容器启动时要执行的命令;
yaml
depends_on:
- db-server
depends_on
字段;定义服务之间的依赖关系,确保在启动 node-server
服务之前,所依赖的 mysql-server
服务已经启动。
mysql 服务配置
-
docker-compose.yml
yamlservices: # 省略其他服务配置... # mysql 服务 db-server: image: mysql:5.6 volumes: - ./db-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "3300:3306" env_file: .env
数据库服务直接使用 mysql 镜像来启动,volumns
数据卷映射了两个路径,一个将宿主机的 db-data
目录映射到容器中的 /var/lib/mysql
目录,因为容器销毁数据也会跟着销毁,所以通过数据卷映射将 mysql 中产生的数据保存下来;另一个 init.sql
是用于初始化数据库,它会在容器启动时执行。
sql
# init.sql
# 如果不存在数据库 travel 则创建
CREATE DATABASE IF NOT EXISTS travel;
以上就完成一个简单的 node.js + mysql 后端的 compose 开发环境配置,完整配置如下:
-
docker-compose.yml
yaml# docs https://docs.docker.com/compose/compose-file # 服务配置 services: # node 服务 node-server: image: node:18-alpine working_dir: /backend volumes: - ./backend:/backend ports: - "3000:3000" env_file: .env command: npm run dev depends_on: - db-server # 数据库服务 db-server: image: mysql:5.6 volumes: - ./db-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "3300:3306" env_file: .env
通过运行命令 docker compose up -d
启动容器,可以在 docker desktop 中查看运行的服务信息。
成功运行之后,访问接口 http://localhost:3000/api/db
看看是否正常响应。
前端服务配置
在本地开发前端项目,一般会启动一个开发服务器,所以直接单独运行项目就行。
生产环境搭建
生产跟开发的 compose 配置通常都不一样,需要按实际情况考虑,有的可能需要先打包输出产物再构建成镜像。这里以我们 demo 项目为例,在根目录新建 docker-compose-prod.yml
配置文件,一步步来配置服务。
node 服务配置
生产中我们一般需要先构建成镜像推送到镜像仓库中,在服务器部署时直接使用镜像启动。首先在 backend
根目录下新建配置文件Dockerfile
;
-
Dockerfile
配置dockerFROM node:18-alpine WORKDIR /backend COPY package.json ./ COPY .npmrc ./ RUN npm install --only=production COPY . . EXPOSE 3000 CMD ["npm", "run", "prd"]
因为 koa 项目不需要打包,直接用源码运行就行,区别在于运行的脚本命令不同,在开发环境我们运行的是 dev
命令,用的 nodemon
运行;在生产改用 prd
命令,用 pm2
运行,需要注意在容器中使用 pm2 要用 pm2-runtime
命令。
json
// package.json
"scripts": {
"dev": "./node_modules/.bin/nodemon bin/www",
"prd": "pm2-runtime start bin/www",
},
在安装依赖时指定 --only=production
,安装生产环境依赖(即 dependencies
下的依赖),因为生产环境只需要运行时相关的依赖,所以平时在安装依赖时要注意区分开。而在前端项目则无所谓,放在 dependencies
或 devDependencies
都行,因为依赖一般都会合进产物中。
bash
RUN npm install --only=production
构建镜像也可以用 docker compose 来管理,构建镜像运行命令 docker compose -f docker-compose-prod.yml build
;compose 配置如下:
-
docker-compose-prod.yml
yamlservices: node-server: # image 指定构建的镜像名 image : jizai/node-server build: context: ./backend dockerfile: ./Dockerfile
context
字段;指定构建上下文路径**,**指定构建镜像时 Docker 客户端会传递给 Docker 引擎的目录路径。Docker 引擎会将该目录及其所有内容发送给 Docker Daemon,用于构建镜像。
dockerfile
字段;指定要使用的 Dockerfile 文件的路径。如果未指定,则默认使用构建上下文路径中的 Dockerfile 文件。路径可以是相对于构建上下文的路径,也可以是绝对路径。
mysql 服务配置
因为直接使用的镜像,也不需要做什么配置,需要修改的可能就是部署时 env
文件中数据库相关的环境变量。
前端服务配置
前端项目一般也需要先打包输出静态产物,再基于产物和 nginx 构建成镜像,这样就能运行为一个服务了。
-
Dockerfile
配置dockerFROM node:18-alpine as development WORKDIR /frontend COPY package.json ./ COPY .npmrc ./ RUN npm install COPY . . RUN npm run build FROM nginx:1.23-alpine as production WORKDIR /frontend COPY --from=development /frontend/dist /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
加个 .dockerignore
文件,忽略 node_modules
的复制。
bash
# .dockerignore
node_modules
这里用到了多阶段构建,首先基于 node 镜像运行前端打包命令 npm run build
输出静态资源产物,再基于 nginx 镜像, 使用 COPY --from=development
命令从 development
阶段复制产物(/frontend/dist
目录下)到 nginx 的默认 HTML 目录 /usr/share/nginx/html/
下。
使用多阶段构建的好处是最终生成的镜像更加轻量化,只包含了必要的文件和依赖项。了解更多多阶段构建 Multi-stage builds
同样构建镜像也可以用 docker compose 来管理,构建镜像运行命令 docker compose -f docker-compose-prod.yml build
;compose 配置如下:
-
docker-compose-prod.yml
yamlservices: # 省略其他服务配置... frontend: image: jizai/frontend build: context: ./frontend dockerfile: ./Dockerfile
部署
部署时,依旧通过 docker compose 来管理,在服务器上新建一个目录来存放相关数据或配置,例如 demo
,在该目录下新建一个 compose 配置文件 docker-compose-deploy.yml
,
整体目录结构:
bash
|--- demo
|--- db-data # 数据库数据卷,存放相关数据
|- nginx # nginx 数据卷,存在相关配置
|--- docker-compose-deploy.yml # compose 配置文件
compose 配置如下:
-
docker-compose-deploy.yml
yamlservices: node-server: image: jizai/node-server ports: - "3000:3000" env_file: .env depends_on: - db-server restart: always # 数据库服务 db-server: image: mysql:5.6 volumes: - ./db-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "3300:3306" restart: always # 前端服务 frontend: image: jizai/frontend ports: - 80:80 volumes: - ./nginx/conf/default.conf:/etc/nginx/conf.d/default.conf - ./nginx/cert/:/etc/cert/ - ./nginx/logs/:/var/log/nginx/ restart: always
同开发环境 compse 配置相比,部署时都是直接使用的镜像,数据库服务配置不变,新增了前端服务 frontend
,使用数据卷挂载宿主机中的 Nginx 配置文件、SSL 证书和日志目录,方便配置和查看。
最后,一行命令启动所有服务:docker-compose -f docker-compose-deploy.yml up -d
总结
我们首先创建了一个 Node.js 和 MySQL 的 Demo 项目,通过 compose 使用 mysql 镜像运行一个数据库服务,省去了麻烦的环境配置。在构建生产镜像时,使用 Dockerfile 以及多阶段构建来创建更轻量化的镜像。最后,我们通过 Docker Compose 部署服务器,大大简化了服务的管理和部署流程,使我们可以更快速、更高效地进行项目开发和部署。