使用 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 了。

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
2401_857622666 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
正小安7 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
2402_857589367 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没8 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch8 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
雪域迷影8 小时前
PostgreSQL Docker Error – 5432: 地址已被占用
数据库·docker·postgresql