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

参考

相关推荐
zhd15306915625ff16 分钟前
化工厂主要涉及的自动化备件有哪些?
运维·自动化·化工厂
Jason-河山16 分钟前
利用API返回值实现商品信息自动化更新:技术与实践
运维·自动化
wowocpp1 小时前
查看 linux ubuntu 分区 和 挂载 情况 lsblk
linux·运维·ubuntu
_.Switch5 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_850410835 小时前
文件系统和日志管理
linux·运维·服务器
JokerSZ.5 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
芯盾时代5 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
心灵彼岸-诗和远方6 小时前
DevOps业务价值流:架构设计最佳实践
运维·产品经理·devops
一只哒布刘6 小时前
NFS服务器
运维·服务器
南猿北者7 小时前
docker容器
docker·容器