天天看點

使用 Docker 部署 Node 應用

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

建立示例應用

以 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 (npm@5+)
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 (npm@5+)
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      
如果看到

STATUS

Exited

,原因就是啟動失敗了。此時需要 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      

相關資源

  • Dockerizing a Node.js web app
  • Using Docker and Yarn for Development
  • Docker on Mac with Homebrew: A Step-by-Step Tutorial
  • Cannot connect to the Docker daemon on macOS
  • Run container but exited immediately
  • Docker look at the log of an exited container
  • Dockerfile reference
  • How We Reduce Node Docker Image Size In 3 Steps
  • Use multi-stage builds
The text was updated successfully, but these errors were encountered:
使用 Docker 部署 Node 應用

CC BY-NC-SA 署名-非商業性使用-相同方式共享