使用 Docker Compose 构建 NestJS 应用并与 MySQL 和 Redis 服务集成

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。

它允许用户通过一个 YAML 文件来配置应用程序的服务,这个文件称为 docker-compose.yml。

使用 Docker Compose,您可以轻松地管理多个 Docker 容器的部署,包括启动、停止和重启服务,以及查看运行状态等。

基本组成

  • Services:在 docker-compose.yml 文件中,可以定义多个服务,每个服务都将运行在一个或多个容器中。
  • Networks:可以为服务指定网络,以控制容器之间的通信方式。
  • Volumes:可以定义数据卷,以便在容器之间共享数据或持久化数据。

创建 Nest 引入 mysql 服务

创建 Nest 项目试试:

bash 复制代码
nest new docker-compose-test -p npm

安装 tyeporm、mysql2:

bash 复制代码
npm install @nestjs/typeorm typeorm mysql2

在 mysql workbench 里创建个 database:

bash 复制代码
CREATE DATABASE `test` DEFAULT CHARACTER SET utf8mb4;

在 AppModule 引入 TypeOrmModule:

typescript 复制代码
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'xxx',
      database: 'test',
      synchronize: true,
      logging: true,
      entities: [],
      poolSize: 10,
      connectorPackage: 'mysql2',
      extra: {
        authPlugin: 'sha256_password',
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

src 目录下创建 test.entity.ts:

typescript 复制代码
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Test {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

在 entities 里注册下:

启动 Nest 服务:

bash 复制代码
npm run start:dev

mysql 服务没问题。

引入 redis 服务

bash 复制代码
npm install redis

AppModule 添加一个 redis:

typescript 复制代码
{
  provide: 'REDIS_CLIENT',
  async useFactory() {
    const client = createClient({
      socket: {
        host: 'localhost',
        port: 6379,
      },
    });
    await client.connect();
    return client;
  },
},

在 AppControll 里注入下:

然后访问下 http://localhost:3000 后。

服务端打印了 redis 里的 key:

这就说明 redis 服务也连接成功了。

没有 docker compose

假设我们 nest 服务开发完了,想部署,那就要写这样的 dockerfile:

dockerfile 复制代码
# Step 1: 使用带有 Node.js 的基础镜像
FROM node:18-alpine as builder

# 设置工作目录
WORKDIR /usr/src/app

# 复制 package.json 和 package-lock.json(如果可用)
COPY package*.json ./

# 安装项目依赖
RUN npm install --only=production

# 安装 nest CLI 工具(确保它作为项目依赖被安装)  
RUN npm install @nestjs/cli -g

# 复制所有文件到容器中
COPY . .

# 构建应用程序
RUN npm run build

# Step 2: 运行时使用更精简的基础镜像
FROM node:18-alpine

# 创建 runc 的符号链接
RUN ln -s /sbin/runc /usr/bin/runc

WORKDIR /usr/src/app

# 从 builder 阶段复制构建好的文件
COPY --from=builder /usr/src/app/dist ./dist
COPY --from=builder /usr/src/app/node_modules ./node_modules

# 暴露 3000 端口
EXPOSE 3000

# 运行 Nest.js 应用程序
CMD ["node", "dist/main"]

因为我们项目依赖 mysql,redis,所以我们要先运行 mysql、redis 镜像,最后才能运行 nest 镜像。

我们运行之后还会报错:

说是 127.0.0.1 的 6379 端口连不上。

注意 nest 容器里需要使用宿主机 ip 来访问 mysql、redis 服务,所以我们在 nest 应用里面要把 mysql 和 redis 的 host 全改写宿主机 ip。

终端输入 ifconfig,在 en0 找到自己的 ip。

之后 dockerfile build 一个 nest 镜像,分别按顺序运行 mysql、redis 和 nest 镜像即可。

引入 docker compose

**编写 docker-compose.yml 文件:**根目录添加一个 docker-compose.yml,定义服务、网络和数据卷等:

yaml 复制代码
version: '1.0'
# 定义服务,即需要运行的容器集合  
services:  
  # 定义一个名为'nest-app'的服务  
  nest-app:  
    # 构建配置,指定Dockerfile的路径和上下文  
    build:  
      # 指定Docker构建上下文的路径,通常是Dockerfile所在的目录  
      context: ./  
      # 指定Dockerfile的路径,相对于构建上下文  
      dockerfile: ./Dockerfile  
    # 定义该服务所依赖的其他服务,它们将按照依赖顺序启动  
    depends_on:  
      # 依赖名为'mysql-container'的服务  
      - mysql-container  
      # 依赖名为'redis-container'的服务  
      - redis-container  
    # 端口映射,将宿主机的3000端口映射到容器的3000端口  
    ports:  
      - '3000:3000'
    
  # 定义一个名为'mysql-container'的服务,使用mysql镜像  
  mysql-container:  
    # 指定使用mysql官方Docker镜像  
    image: mysql  
    # 端口映射,将宿主机的3306端口映射到容器的3306端口  
    ports:  
      - '3306:3306'  
    # 数据卷配置,用于持久化存储  
    volumes:  
      # 将宿主机的/Users/yunmu/Desktop/mysql目录映射到容器的/var/lib/mysql目录  
      - /Users/yunmu/Desktop/mysql:/var/lib/mysql
    environment:
      MYSQL_DATABASE: test
      MYSQL_ROOT_PASSWORD: xxx
    
  # 定义一个名为'redis-container'的服务,使用redis镜像  
  redis-container:  
    # 指定使用redis官方Docker镜像  
    image: redis  
    # 端口映射,将宿主机的6379端口映射到容器的6379端口  
    ports:  
      - '6379:6379'  
    # 数据卷配置,用于持久化存储  
    volumes:  
      # 将宿主机的/Users/yunmu/Desktop/redis目录映射到容器的/data目录  
      - /Users/yunmu/Desktop/redis:/data

每个 services 都是一个 docker 容器,名字随便指定。

启动服务 :运行以下命令来启动所有定义的服务:

bash 复制代码
docker-compose up

它会把所有容器的日志一起输出,先跑的 mysql、redis,再跑的 nest。

也可以运行这个命令:

bash 复制代码
docker-compose up -d --build --force-recreate
  • -d:Detached mode,意味着 Docker Compose 会在后台运行容器。
  • --build:在启动服务之前强制重构建服务关联的镜像。
  • --force-recreate:即使容器的配置没有改变,也强制重新创建容器。

成功创建。

浏览器访问下 http://localhost:3000/

nest 容器内打印了 redis 的 key。

停止移除 Docker Compose 启动的容器:

bash 复制代码
docker-compose down

如果还想移除对应的镜像,请使用:

bash 复制代码
docker-compose down --rmi all

桥接网络

Docker 桥接网络是一种网络模式,允许容器在同一个宿主机上进行通信,同时与宿主机以及其他未连接到该桥接网络的容器隔离。

在 Docker 桥接网络中,每个容器都会被分配一个独立的IP地址,并且这些IP地址都是与宿主机不同的。

Docker 通过桥接器(bridge)将这些容器连接起来,形成一个虚拟的网络环境。

容器之间可以通过这个网络环境进行通信,而宿主机也可以通过桥接器与容器进行通信。

Docker桥接网络适用于在同一个宿主机上运行的容器之间的通信。如果需要在不同宿主机上的容器之间进行通信,则可以使用Docker的覆盖网络(overlay network)。

当启动一个Docker容器时,如果没有指定网络模式,那么默认会使用桥接网络。可以通过Docker命令或Docker Compose文件来配置容器的网络模式。

通过 docker network 来创建叫桥接网络:

bash 复制代码
docker network create common-network


通过 networks 指定创建的 common-network 桥接网络,网络驱动程序指定为 bridge。

其实我们一直用的网络驱动程序都是 bridge,它的含义是容器的网络和宿主机网络是隔离开的,但是可以做端口映射。比如 -p 3000:3000、-p 3306:3306 这样。

修改 AppModule 的代码,改成用容器名来访问:

先删除之前的容器镜像:

bash 复制代码
docker-compose down --rmi all

运行 compose:

成功运行。

其实不指定 networks 也可以,docker-compose 会创建个默认的:

运行 docker-compose up , 你会发现它创建了一个默认的 network:

所以,不手动指定 networks,也是可以用桥接网络的。

我们如果不用 docker compose,可以在 docker run 的时候指定 --network,这样 3 个容器通过容器名也能互相访问。

比如:

bash 复制代码
docker run -d --network common-network -v /Users/yunmu/Desktop/mysql:/var/lib/mysql --name mysql-container mysql
docker run -d --network common-network -v /Users/yunmu/Desktop/redis:/data --name redis-container redis
docker run -d --network common-network -p 3000:3000 --name nest-container nest-image

其实本质就是对 Network Namespace 的处理,本来是 3 个独立的 Namespace,当指定了 network 桥接网络,就可以在自己 Namespace 下访问别的 Namespace 了。

相关推荐
小奏技术15 分钟前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术33 分钟前
Spring AI存储向量数据
后端
苏三的开发日记34 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
折果34 分钟前
如何在vue项目中封装自己的全局message组件?一步教会你!
前端·面试
苏三的开发日记35 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
不死鸟.亚历山大.狼崽子38 分钟前
Syntax Error: Error: PostCSS received undefined instead of CSS string
前端·css·postcss
汪子熙38 分钟前
Vite 极速时代的构建范式
前端·javascript
跟橙姐学代码38 分钟前
一文读懂 Python 的 JSON 模块:从零到高手的进阶之路
前端·python
陈三一40 分钟前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure41 分钟前
万字详解JVM
java·jvm·后端