Midway 提供了一个轻量的启动器,用于启动你的应用。我们为应用提供了多种部署模式,你既可以将应用按照传统的样子,部署到任意的服务上(比如自己购买的服务器),也可以将应用构建为一个 Serverless 应用,Midway 提供跨多云的部署方式。

# 本地开发


这里列举的主要是本地使用 dev 命令开发的方式,有两种。

# 快速启动单个服务


在本地研发时,Midway 在 package.json 中提供了一个 dev 命令启动框架,比如:

{
	"script": {
    "dev": "midway-bin dev --ts"
  }
}

这是一个最精简的命令,他有如下特性:

  • 1、使用 --ts 指定 TypeScript(ts-node)环境启动
  • 2、使用内置的(@midwayjs/mock 的 createApp)创建一个**匹配当前框架 **的服务,并返回 app


所谓匹配当前框架,指的是根据内部的框架列表,和 pkg 的依赖匹配查找到最符合当前的框架并启动。

内部的框架列表如下:

const currentFramework = [
  '@midwayjs/web',
  '@midwayjs/koa',
  '@midwayjs/express',
  '@midwayjs/serverless-app',
  '@midwayjs/grpc',
  '@midwayjs/rabbitmq',
  '@midwayjs/socketio',
  '@midwayjs/faas',
];

这样启动的服务用于本地快速开发测试,使用的是 框架初始化的默认值

比如,你的 package.json 中依赖如下;

{
	"@midwayjs/grpc": "xxx",
  "@midwayjs/web": "xxx"
}

按照优先级顺序,默认的 dev 依旧会启动 @midwayjs/web 服务。

# 指定入口启动服务


由于本地的 dev 命令普通情况下和 bootstrap.js 启动文件初始化参数不同,有些用户担心本地开发和线上开发不一致,或者希望一次启动多个框架(多种协议)。

这个时候我们可以直接传递一个入口文件给 dev 命令,直接使用入口文件启动服务。

{
	"script": {
    "dev": "midway-bin dev --ts --entryFile=bootstrap.js"
  }
}

TIP

这种情况下,会忽略其余的参数,比如 --port。

# 部署到普通服务器

# 部署后和本地开发的区别


在部署后,有些地方和本地开发有所区别。

1、node 环境的变化

最大的不同是,服务器部署后,会直接使用 node 来启动项目,而不是 ts-node,这意味着不再读取 *.ts 文件。

2、加载目录的变化

服务器部署后,只会加载构建后的 dist 目录,而本地开发则是加载 src 目录。

本地 服务器
appDir 项目根目录 项目根目录
baseDir 项目根目录下的 src 目录 项目根目录下的 dist 目录

3、环境的变化

服务器环境,一般使用 NODE_ENV=production ,很多库都会在这个环境下提供性能更好的方式,例如启用缓存,报错处理等。

4、日志文件

一般服务器环境,日志不再打印到项目的 logs 目录下,而是其他不会受到项目更新影响的目录,比如 home/admin/logs 等等,这样固定的目录,也方便其他工具采集日志。

# 部署的流程


整个部署分为几个部分,由于 Midway 是 TypeScript 编写,比传统 JavaScript 代码增加了一个构建的步骤,整个部署的过程如下。

image.png
由于部署和平台、环境非常相关,下面我们都将以 Linux 来演示,其他平台可以视情况参考。

# 编译代码和安装依赖


由于 Midway 项目是 TypeScript 编写,在部署前,我们先进行编译。在示例中,我们预先写好了构建脚本,执行 npm run build 即可,如果没有,在 package.json 中添加下面的 build 命令即可。

// package.json
{
  "scripts": {
    "build": "midway-bin build -c"
  },
}

TIP

虽然不是必须,但是推荐大家先执行测试和 lint。


一般来说,部署构建的环境和本地开发的环境是两套,我们推荐在一个干净的环境中构建你的应用。

下面的代码,是一个示例脚本,你可以保存为 build.sh 执行。


## 服务器构建(已经下载好代码)
$ npm install                                       # 安装开发期依赖
$ npm run build																			# 构建项目
$ rm -rf node_modules && rm package-lock.json 			# 删除开发期依赖
$ npm install --production													# 安装生产环境依赖


## 本地构建(已经安装好 dev 依赖)
$ npm run build
$ rm -rf node_modules && rm package-lock.json
$ npm install --production

TIP

一般安装依赖会指定 NODE_ENV=productionnpm install --production ,在构建正式包的时候只安装 dependencies 的依赖。因为 devDependencies 中的模块过大而且在生产环境不会使用,安装后也可能遇到未知问题。


执行完构建后,会出现 Midway 构建产物 dist 目录。

➜  my_midway_app tree
.
├── src
├── dist                # Midway 构建产物目录
├── test  
├── package.json  
└── tsconfig.json

# 打包压缩


构建完成后,你可以简单的打包压缩,上传到待发布的环境。

# 上传和解压


有很多中方式可以上传到服务器,比如常见的 ssh/FTP/git 等。也可以使用 OSS (opens new window) 等在线服务进行中转。

# 启动方式一:使用纯 Node.js 或者 pm2 等工具启动


Midway 构建出来的项目是单进程的,不管是采用 fork 模式还是 cluster 模式,单进程的代码总是很容易的兼容到不同的体系中,因此非常容易被社区现有的 pm2/forever 等工具所加载,

我们这里以 pm2 来演示如何部署。

项目一般都需要一个入口文件,比如,我们在根目录创建一个 bootstrap.js 作为我们的部署文件。

➜  my_midway_app tree
.
├── src
├── dist                # Midway 构建产物目录
├── test  
├── server.js						# 部署启动文件  
├── package.json  
└── tsconfig.json


Midway 提供了一个简单方式以满足不同场景的启动方式,只需要安装我们提供的 @midwayjs/bootstrap 模块。

$ npm install @midwayjs/bootstrap --save

然后在入口文件中写入代码,注意,这里的代码使用的是 JavaScript 。

// 获取框架
const WebFramework = require('@midwayjs/web').Framework;
// 初始化 web 框架并传入启动参数
const web = new WebFramework().configure({
  port: 7001
});

const { Bootstrap } = require('@midwayjs/bootstrap');

// 加载框架并执行
Bootstrap
  .load(web)
  .run();

我们提供的每个上层框架都将会导出一个 Framework 类,而 Bootstrap 的作用则是加载这些框架,传入启动参数,运行他们。

TIP

启动参数,你可以在不同的框架处查询到。


这个时候,你已经可以直接使用 NODE_ENV=production node bootstrap.js 来启动代码了,也可以使用 pm2 来执行启动。

pm2 启动可以参考 pm2 使用文档

如果你希望把 bootstrap.js 文件放到不同的目录,比如 bin/bootstrap.js ,你可以修改 Bootstrap 的参数。

// bin/bootstrap.js
const { join } = require('path');

// 获取框架
const WebFramework = require('@midwayjs/web').Framework;
// 初始化 web 框架并传入启动参数
const web = new WebFramework().configure({
  port: 7001
});

const { Bootstrap } = require('@midwayjs/bootstrap');

// 加载框架并执行
Bootstrap
	.configure({
		appDir: join(__dirname, '../')
	})
  .load(web)
  .run();



# 启动方式二:EggJS 特有的启动形式


由于 EggJS 提供了默认的多进程部署工具 egg-scripts ,Midway 也继续支持这种方式,如果上层是 EggJS,推荐这种部署方式。

首先在依赖中,确保安装 egg-scripts 包和 midway 包。

$ npm i egg-scripts --save

添加 npm scriptspackage.json

在上面的代码构建之后,使用我们的 start 和 stop 命令即可完成启动和停止。

"scripts": {
	"start": "egg-scripts start --daemon --title=********* --framework=@midwayjs/web",
	"stop": "egg-scripts stop --title=*********",
}

TIP

********* 的地方是你的项目名。

注意:egg-scripts 对 Windows 系统的支持有限,参见 #22 (opens new window)

#

启动参数

$ egg-scripts start --port=7001 --daemon --title=egg-server-showcase


如上示例,支持以下参数:

  • --port=7001 端口号,默认会读取环境变量 process.env.PORT,如未传递将使用框架内置端口 7001。
  • --daemon 是否允许在后台模式,无需 nohup。若使用 Docker 建议直接前台运行。
  • --env=prod 框架运行环境,默认会读取环境变量 process.env.EGG_SERVER_ENV, 如未传递将使用框架内置环境 prod。
  • --workers=2 框架 worker 线程数,默认会创建和 CPU 核数相当的 app worker 数,可以充分的利用 CPU 资源。
  • --title=egg-server-showcase 用于方便 ps 进程时 grep 用,默认为 egg-server-${appname}。
  • --framework=yadan 如果应用使用了自定义框架 (opens new window),可以配置 package.json 的 egg.framework 或指定该参数。
  • --ignore-stderr 忽略启动期的报错。
  • --https.key 指定 HTTPS 所需密钥文件的完整路径。
  • --https.cert 指定 HTTPS 所需证书文件的完整路径。
  • 所有 egg-cluster (opens new window) 的 Options 都支持透传,如 --port 等。


更多参数可查看 egg-scripts (opens new window)egg-cluster (opens new window) 文档。

TIP

使用 egg-scripts 部署的日志会存放在 **用户目录 ,**比如 /home/xxxx/logs

# 部署为 Serverless 应用


Midway 可以将现有的 Web 项目部署为 Serverless 应用,这里以部署到阿里云函数计算作为示例。

# 部署到 Serverless 环境


1、添加 f.yml 文件到你的项目根目录。

➜  my_midway_app tree
.
├── src
├── dist                
├── f.yml  								# Midway Serverless 部署配置文件
├── package.json  
└── tsconfig.json

service: my-midway-app  ## 应用发布到云平台的名字,一般指应用名

provider:
  name: aliyun        ## 发布的云平台,aliyun,tencent 等

deployType: egg       ## 部署的应用类型

应用类型选项如下:

@midwayjs/web 项目 egg
@midwayjs/experss 项目 express
@midwayjs/koa 项目 koa

2、添加发布时的构建钩子

package.json 加入下面的这段,用于在发布时自动执行 npm run build 。

  "midway-integration": {
    "lifecycle": {
      "before:package:cleanup": "npm run build"
    }
  },
	"scripts": {
  	"deploy": "midway-bin deploy"
  },
  "egg":{
    "framework": "@midwayjs/web"
  }

TIP

如果使用了自己的 egg 上层框架,这里的 egg.framework 可以变为自己的包名。


3、执行 npm run deploy 即可,发布后,阿里云会输出一个临时可用的域名,打开浏览器访问即可。
image.png
如需更详细的发布文档,请查阅 Serverless 发布 FAQ (opens new window)

# 部署到 Serverless 平台的限制

  • 1、不支持 egg-socketio 等网关不支持的协议
  • 2、不支持 文件上传** **等网关无法支持的能力
  • 3、不支持 定时任务(可以使用组合 Timer 触发器的方式)
  • 3、还有一些,请参考 应用迁移 faq (opens new window)


如需发布到腾讯云环境,请查看 发布到腾讯云

另外这里还有一些 常见问题,请查阅。

# 使用 Docker 部署

# 编写 Dockerfile,构建镜像


步骤一:在当前目录下新增Dockerfile:

FROM node:12

WORKDIR /home

COPY . .


# 如果各公司有自己的私有源,可以替换registry地址
RUN npm install --registry=https://registry.npm.taobao.org

RUN npm run build


# 如果端口更换,这边可以更新一下
EXPOSE 7001

CMD ["npm", "run", "online"]


步骤二: 新增 .dockerignore 文件(类似 git 的 ignore 文件),可以把 .gitignore  的内容拷贝到 .dockerignore 里面

步骤三:package.json 文件的 scripts 里面新增 online,对比 start,把 --daemon 去掉。如下图
image.png

这里使用的是 egg-scripts 部署,当使用 pm2 部署时,请将命令修改为 pm2-runtime start ,pm2 行为请参考 pm2 容器部署说明 (opens new window)

步骤四:构建docker镜像

docker build -t helloworld .


步骤五:运行docker镜像

docker run -itd -P helloworld 

运行效果如下:
image.png
然后大写的 -P 由于给我们默认分配了一个端口,所以我们访问可以访问 32791  端口(这个 -P 是随机分配,我们也可以使用 -p 7001:7001 指定特定端口)
image.png
关于别的推送到 dockerhub 或者 docker 的 registry,可以大家搜索对应的方法。

# 结合 Docker-Compose 运行


在 docker 部署的基础上,还可以结合 docker-compose 部署一些跟自己服务相关的服务。

步骤一

按照 Docker 方式部署的方式新增 dockerfile

步骤二

新增docker-compose.yml文件,内容如下:(此处我们模拟我们的midway项目需要使用redis)

version: "3" 
services:
  web:
    build: .
    ports:
      - "7001:7001"
    links:
      - redis
  redis:
    image: redis



步骤三:构建
**
使用命令:

docker-compose build


步骤四:运行

docker-compose up -d

image.png
那么redis比如怎么用,因为 docker-compose 里面加了一个 redis,并且 link 了,所以我们代码里面如下写:

在 service 目录下添加 redis.service.ts 文件,代码如下:

import { Provide, Scope, ScopeEnum, Init } from "@midwayjs/decorator";
import * as Redis from 'ioredis'

@Provide()
@Scope(ScopeEnum.Singleton)
export class RedisService{
  redis: Redis.Redis = null;

  @Init()
  async init(){
    this.redis = new Redis({
      host: 'redis'
    });
  }

  async setValue(key, value){
    return await this.redis.set(key, value);
  }

  async getValue(key){
    return await this.redis.get(key);
  }
}

然后在 controller/home.ts 里面添加一个接口如下:

import { Controller, Get, Inject, Provide } from '@midwayjs/decorator';
import { RedisService } from '../service/redis.service';

@Provide()
@Controller('/')
export class HomeController {

  @Inject()
  redisService: RedisService;

  @Get('/')
  async home() {
    let res = await this.redisService.getValue("foo")
    return 'Hello Midwayjs!' + res;
  }

  @Get("/update")
  async update(){
    let res = await this.redisService.setValue("foo", "hello world");
    return res;
  }
}

这个代码比较好理解,相当于访问 127.0.0.1:7001/update 接口,会去调用 redisService 新增一个 key,对应的 value 为 hello world

然后访问 127.0.0.1:7001  ,会调用redisService获取key为foo的值,并返回给页面。
如下:
image.png
关于更多关于 docker-compose 的详情,可以查看网上关于 docker-compose 的使用方法。