使用 Docker 部署 Node 應用

劉哇勇 發表於 2021-06-18
Docker Node.js

容器將應用與環境打包整合,解決了應用外部依賴的痛點,打包後通過視窗可方便地部署到任意環境,用過就知道很香。

建立示例應用

NestJS 為例,先建立一個示例應用。

$ npm i -g @nestjs/cli
$ nest new my-app
$ cd my-app
$ yarn && yarn start
 

然後 app.controller.ts 中新增如下 action:

  @Get('ping')
  async ping() {
    return 'pong';
  }
 

測試一把會得到如下返回,證明我們的 app 一切正常:

$ curl localhost:3000/ping
pong
 

Docker 介紹

先了解 Docker 的兩個核心概念:

  • 映象/image: 本質上是一個檔案,裡面包含建立容器的指令,可通過 docker images 檢視已有的映象。
  • 容器/container: 通過映象建立出來執行中的例項即容器,可通過 docker ps 命令檢視執行中的容器。

Docker 安裝

$ brew install --cask docker
 

如果已經安裝過,升級可使用如下命令:

$ brew install --cask docker
 

然後在程式目錄或 Spotlight 中找到並啟動 Docker,系統狀態列中會有個鯊魚圖示。

啟動後命令列工具已經可用,檢查安裝:

$ docker —version
Docker version 20.10.6, build 370c289
 

使用

通過 docker help 檢視幫助。

$ docker help
 

檢視具體命令的幫助可在 help 後加上該命令:

$ docker help run
 

打包生成映象

Docker 中打包後的應用存在於映象中,其中便包含了應用及依賴的環境。將這個映象檔案進行分發就可以在其他地方載入執行,實現了在新環境中方便部署,無須再關心外部依賴。

建立 Dockerfile

使用 Docker 打包應用需先建立 Dockerfile,其中包含指導 Docker 如何打包的指令。

$ touch Dockerfile
 

一般我們會基於已有映象來建立自己的映象,比如這裡打包 Node 應用,我們會使用一個已經包含 Node 環境的映象作為源。通過如下 FROM 語句完成:

FROM node:14
 

建立應用所在的目錄:

# Create app directory
WORKDIR /usr/src/app
 

將檔案複製到目標路徑,然後進行 npm 包依賴的安裝:

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available ([email protected]+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
 

複製應用中的原始碼檔案:

# Bundle app source
COPY . .
 

依賴和原始碼都好後,可以編譯 Nest 應用,生成 dist 目錄了:

npm run build
 

可以把映象看作一個封閉環境,外界要與其中的應用進行互動,比如這裡打包的是 Nest 服務,要能正常訪問 Nest 中我們編寫的 HTTP 介面,就需要 image 向外暴露埠。

因為預設 Nest 應用起的 3000 埠,這裡就將其暴露,

EXPOSE 3000
 

最後一條指令,指導 Docker 啟動 Nest 應用:

CMD [ "node", "dist/main" ]
 

所以完整的 Dockerfile 目前長這樣了:

FROM node:14

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available ([email protected]+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
npm run build

# Bundle app source
COPY . .

EXPOSE 8080
CMD [ "node", "dist/main" ]
 

.dockerignore 檔案

可建立 .dockerignore 檔案,將日誌,本地無用檔案排除在複製的檔案列表之外。

node_modules
npm-debug.log
 

生成映象

通過如下命令根據前面建立的 Dockerfile 生成映象:

$ docker build . -t wayou/my-app
 

其中 -t 指定映象名稱,一般為 <username>/<image_name> 形式,其中 username 與你在 Docker Hub 中的使用者名稱一致。前面提到映象可進行分發,當然也能分享,同時我們的 Dockerfile 也是基於名為 node:14 的映象進行建立的,Docker Hub 則是官方一個分享 image 的平臺。

生成映象過程中如果出現如下錯誤:

Error response from daemon: dial unix docker.raw.sock: connect: connection refused
 

重啟一下 Docker 服務即可。

檢視映象

正常的話,可通過如下命令檢視到剛剛生成的映象:

$ docker images
REPOSITORY            TAG       IMAGE ID       CREATED       SIZE
wayou/my-app   latest    6ba2f1f74d8b   7 hours ago   1.44GB
 

執行映象

通過如下命令執行映象:

$ docker run -p 8000:3000 -d wayou/my-app
 

其中 -p 部分前面為外部環境使用的埠,而 3000 為容器對外 暴露的埠。實際使用時則是使用外部這個 8000。

$ curl localhost:8000/ping
pong
 

通過 docker ps 檢視執行中的例項。

映象啟動失敗的排查

這裡展示下如下 Debug 找出映象啟動失敗的原因,即沒有生成執行中的容器。

前面啟動應用的指令是 CMD [ "node", "dist/main" ],而 dist 目錄是通過 npm run build 而來,假如我們的 Dockerfile 中沒有 build 這個步驟,很明顯就沒有 dist 目錄所以會導致應用啟動失敗。

啟動失敗的話,docker ps 輸出為空。

此時可加上 -a 引數,它會列出所有容器,包含停止的例項,以檢視其狀態。

$ docker ps -a
 

如果看到 STATUSExited,原因就是啟動失敗了。此時需要 Debug 一下看看啟動失敗的具體原因。

重新啟動,並指定名稱,方便後面檢視日誌:

$ docker run -p 8000:3000 -d --name test wayou/my-app
 

現在檢視時可能看一個指定名稱為 test 的容器:

$ docker ps -a                                                                                                                                                                                                         
CONTAINER ID   IMAGE                 COMMAND                  CREATED             STATUS                      PORTS     NAMES
a9d187c0d665   wayou/my-app   "docker-entrypoint.s…"   5 seconds ago       Exited (1) 3 seconds ago              test
 

然後通過 docker logs 檢視其日誌:

$ docker logs -t test                                                                                                                                                                                                   
2021-05-21T09:58:56.706680291Z internal/modules/cjs/loader.js:888
2021-05-21T09:58:56.706727664Z   throw err;
2021-05-21T09:58:56.706735472Z   ^
2021-05-21T09:58:56.706739801Z
2021-05-21T09:58:56.706743086Z Error: Cannot find module '/usr/src/app/node dist/main'
2021-05-21T09:58:56.706746265Z     at Function.Module._resolveFilename (internal/modules/cjs/loader.js:885:15)
2021-05-21T09:58:56.706751604Z     at Function.Module._load (internal/modules/cjs/loader.js:730:27)
2021-05-21T09:58:56.706755609Z     at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
2021-05-21T09:58:56.706759645Z     at internal/main/run_main_module.js:17:47 {
2021-05-21T09:58:56.706762133Z   code: 'MODULE_NOT_FOUND',
2021-05-21T09:58:56.706764372Z   requireStack: []
2021-05-21T09:58:56.706766508Z }
 

從日誌中就清晰地看到原因了。

修正後成功執行的話,通過 docker ps 看到正常執行的容器了。

$ docker ps         
CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS         PORTS                                       NAMES
743cb8b9d604   wayou/my-app   "docker-entrypoint.s…"   5 seconds ago   Up 3 seconds   0.0.0.0:8000->3000/tcp, :::8000->3000/tcp   test
 

映象及容器的清除

除錯過程難免會生成很多無用的測試資料,可通過如下命令進行清除。

單個清除

通過各自對應的 rm 命令來完成。

映象的刪除

$ docker image rm [OPTIONS] IMAGE [IMAGE...]
 

容器的刪除

$  docker rm [OPTIONS] CONTAINER [CONTAINER...] 
 

批量清除

也可通過 docker container prune 將全部容器清除掉。

進入容器內

通過如下命令可在容器中開啟一個 shell,在 shell 中可檢視其中的檔案等。

$ docker exec -it <container id> /bin/bash
 

相關資源

The text was updated successfully, but these errors were encountered: