nestjs项目用docker部署,但是因为是从前端过来的刚接触后端,所以项目开发完了想先在本地电脑上面测试一下用docker。
项目地址 nestmall,下载完项目以后,先在本地运行一下,项目的运行不了解的可以参考另一篇教程 【ToDo】
说说我对docker的理解,docker可以理解成一个VMware的集合,以前我弄过VMware虚拟机系统,docker就是可以很方便的创建多个虚拟机系统,docker里面最重要的可能就是container
和image
这两个概念了,image
可以理解成系统的包,比如装window系统的时候,需要大白菜的iso镜像(其实我认为就是压缩包),然后呢在镜像的基础上安装系统,docker的image
就是这个东西,而container
就是对应的系统,我们是可以进入这个容器里面做一下Linux系统的一下命令操作,当然这个是很微型的系统,很多命令没有,比如我这个MySQL的容器,里面有MySQL的命令,里面的目录也和Linux很像。
好了,上面的废话说完了,下面进入正题,我的电脑是Mac的,所以只能基于我电脑来说了。
安装
从官网下载,安装后 Terminal 中敲下 docker,有使用说明出来的话大多情况下说明已经安装成功了。
docker -v
Docker version 20.10.14, build a224086
Dockerfile
在项目的根目录下创建
Dockerfile
文件,里面的内容如下
FROM node:14-alpine
# 初始化工作目录
RUN mkdir -p /app/server
WORKDIR /app/server
# 复制 package.json
COPY package*.json /app/server/
# 安装依赖
RUN npm install
# 复制文件
COPY . /app/server/
RUN npm run build
# 开启 Dev
CMD ["npm", "run", "start:prod"]
简单解释一下
- FROM 为这次构建流程指定基础镜像,同时必须是第一条指令,因为后续所有的操作都必须基于基础镜像之上
- WORKDIR 此命令是设置当前工作目录,如果目录不存在,则会直接创建,设置完毕之后,所有操作的路径都将处于当前指定路径之下,可以理解成CD命令
- COPY 顾名思义,这是一个复制的命令,但是 COPY 只能复制宿主机本地的文件。
- RUN 在构建镜像过程中执行的命令
- CMD 在容器启动时候执行的命令
当然还有许多的参数,我项目里面只用了这些。
另外还有两个地方需要说明一下EXPOSE
:是暴露端口给宿主机(也就是我电脑)比如80或者3000什么的,我这里之所以没用是因为在docker-compose
文件里面配置了CMD ["npm", "run", "start:prod"]
运行这个命令是因为项目里面配置了环境变量,需要cross-env
按环境变量来取值
docker-compose
Docker Compose 允许我们在一个文件里描述应用需要的服务(容器),比如我项目里面用了MySQL和Redis容器,还有上面创建的项目镜像,如何把这些容器打包一起运行呢,这就需要docker-compose了
这里必须指定版本
version: '3.0'
services:
# docker容器启动的redis
redis: # 服务名称
container_name: redis # 容器名称
image: redis:6.2 # 使用官方镜像
# 配置redis.conf方式启动
command: redis-server /usr/local/etc/redis/redis.conf --requirepass 123456 --appendonly yes # 设置redis登录密码 123456、--appendonly yes:这个命令是用于开启redis数据持久化
# 无需配置文件方式启动
# command: redis-server --appendonly yes # 设置redis登录密码 123456
ports:
- 6389:6379 # 本机端口:容器端口
restart: on-failure # 自动重启
volumes:
- /Users/jackwang/Documents/nodejserve/deploy/redis/redis.conf:/usr/local/etc/redis/redis.conf # 把redis的配置文件挂载到宿主机
- /Users/jackwang/Documents/nodejserve/deploy/redis/db:/usr/local/var/db/redis/ # 把redis的配置文件挂载到宿主机
environment:
- TZ=Asia/Shanghai # 解决容器 时区的问题
networks:
- my-server
mysql:
container_name: mysql
image: mysql:8.0.20 # 使用官方镜像
ports:
- 3307:3306 # 本机端口:容器端口
restart: on-failure
environment:
- MYSQL_ROOT_PASSWORD=123123qq # root用户密码
networks:
- my-server
my-mall: # nest服务
container_name: my-mall
build: # 根据Dockerfile构建镜像
context: .
dockerfile: Dockerfile
ports:
- 3000:3000
restart: on-failure # 设置自动重启,这一步必须设置,主要是存在mysql还没有启动完成就启动了node服务
networks:
- my-server
depends_on: # node服务依赖于mysql和redis
- redis
- mysql
# 声明一下网桥 my-server。
# 重要:将所有服务都挂载在同一网桥即可通过容器名来互相通信了
# 如egg连接mysql和redis,可以通过容器名来互相通信
networks:
my-server:
解释一下上面的东西,大部分我觉得应该都是理解
ports
: 这里,本机端口:容器端口 比如MySQL3307:3306
,为啥,因为容器就相当于小型系统了,这个系统咋和我电脑通信呢,比如我现在要连接容器的MySQL数据库,我链接localhost@3306就是链接我电脑的MySQL了,跟容器没关系了,所以需要端口映射一下,当我访问localhost@3307连接数据库的时候,就知道应该是链接容器的MySQL数据库了,用navicate链接3307端口就连上了(这时候MySQL得运行着呢才行)
networks
这个就是容器之间通信需要的,我理解就是局域网,比如一个公司,多台电脑就是多个容器,那容器之间怎么交流通信呢,都放在一个网段里面,这个参数就是这样,my-server
是名字
比如我在Redis容器里面ping一下MySQL,
mysql
就是容器名字
ping mysql
PING mysql (172.19.0.3) 56(84) bytes of data.
64 bytes from mysql.projec-nest_my-server (172.19.0.3): icmp_seq=1 ttl=64 time=0.126 ms
64 bytes from mysql.projec-nest_my-server (172.19.0.3): icmp_seq=2 ttl=64 time=0.085 ms
64 bytes from mysql.projec-nest_my-server (172.19.0.3): icmp_seq=3 ttl=64 time=0.152 ms
64 bytes from mysql.projec-nest_my-server (172.19.0.3): icmp_seq=4 ttl=64 time=0.116 ms
64 bytes from mysql.projec-nest_my-server (172.19.0.3): icmp_seq=5 ttl=64 time=0.064 ms
.dockerignore
这个文件其实没啥说的,就是忽略那些文件到容器里面,比如上面copy命令,如果不弄这个文件,就把所有的文件都copy到工作目录
.git
node_modules
npm-debug
build
最后在项目终端运行docker-compose up
一下正常的话,项目就应该运行
这是我电脑客户端运行成功样子
QA 下面是我开发中的遇到的问题和经验,本来想写在文章里面的,但是感觉有点乱,所以写在最后把,因为有的人可能不会遇到,所以就单独总结一下吧
- docker image ls 查看所有镜像
- docker container ls 查看所有容器
- docker start/stop mysql 启动或停止服务
- docker container stop 容器ID 停止容器
- docker container rm 容器ID 删除容器
- docker image rm 镜像ID 如果要删除镜像,必须先停止容器,删除容器
- docker-compose up 服务(比如MySQL) 这条命令就是执行docker-compose文件,如果直接docker-compose up就是构建所有的镜像和运行容器
ERROR [ExceptionHandler] connect ECONNREFUSED 127.0.0.1:6379
错误通过docker-compose文件其实也能看出来,我配置的本机端口是6389,host是redis,但是报错也是提示上面,当时没看出,后来经人提醒才反应过来,我配置的参数根据没起作用,他还是默认值
如果按照官网文档来写,也是不行,他会提示类型错误
下面是官方文档的写法,这种写法连本地运行都报错。后来我根据RedisClientOptions
这个参数修改了代码
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class ApplicationModule {}
下面是我的代码,文件位置在src/modules/redis-cache/redis-cache.module.ts
,configService
就是配置的变量
CacheModule.registerAsync({
isGlobal: true,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
console.log(
'configService.get',
`redis://${configService.get('redis.host')}:${configService.get(
'redis.port',
)}/${configService.get('redis.db')}`,
);
return {
store: redisStore,
...configService.get('redis'),
} as RedisClientOptions;
},
}),
文件位置src/config/production.yml
# redis 配置
redis:
host: redis
port: 6379
db: 0
password: '123456'
token_expire: 1800000
通过上面的配置,就可以链接redis了,但这里有我遇到的一个问题,就是Redis里面我开始配置的port是6389,因为我觉得端口映射的就是6389啊,但其实到了容器中,真正链接已经变了容器之间的IP和端口了,实际上已经不通过宿主机了,所以端口还是6379,如果是用宿主机客户端链接,那就得用6389
./dto/RegisterDto'
应该大写,当让我纠结的是为什么本地运行不报错呢