学会用 Docker Compose 开发部署项目

前言

容器是一种虚拟化技术,它将应用程序及其整个运行时环境(所有运行所需的文件)进行打包和隔离。优势是轻量、可移植、快速部署、确保了应用运行环境的一致性。其中 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

    yaml 复制代码
    MYSQL_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

    yaml 复制代码
    services:
    	# 省略其他服务配置...
    	# 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 配置

    docker 复制代码
    FROM 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 下的依赖),因为生产环境只需要运行时相关的依赖,所以平时在安装依赖时要注意区分开。而在前端项目则无所谓,放在 dependenciesdevDependencies 都行,因为依赖一般都会合进产物中。

bash 复制代码
RUN npm install --only=production

构建镜像也可以用 docker compose 来管理,构建镜像运行命令 docker compose -f docker-compose-prod.yml build;compose 配置如下:

  • docker-compose-prod.yml

    yaml 复制代码
    services:
      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 配置

    docker 复制代码
    FROM 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

    yaml 复制代码
    services:
      # 省略其他服务配置...
      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

    yaml 复制代码
    services:
      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 部署服务器,大大简化了服务的管理和部署流程,使我们可以更快速、更高效地进行项目开发和部署。

参考

相关推荐
秋名山小桃子12 分钟前
Kunlun 2280服务器(ARM)Raid卡磁盘盘符漂移问题解决
运维·服务器
与君共勉1213813 分钟前
Nginx 负载均衡的实现
运维·服务器·nginx·负载均衡
MZWeiei23 分钟前
Zookeeper基本命令解析
大数据·linux·运维·服务器·zookeeper
Arenaschi42 分钟前
在Tomcat中部署应用时,如何通过域名访问而不加端口号
运维·服务器
waicsdn_haha1 小时前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
蜜獾云1 小时前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小屁不止是运维1 小时前
麒麟操作系统服务架构保姆级教程(五)NGINX中间件详解
linux·运维·服务器·nginx·中间件·架构
Hacker_Oldv1 小时前
WPS 认证机制
运维·服务器·wps
bitcsljl1 小时前
Linux 命令行快捷键
linux·运维·服务器
ac.char2 小时前
在 Ubuntu 下使用 Tauri 打包 EXE 应用
linux·运维·ubuntu