容器将應用與環境打包整合,解決了應用外部依賴的痛點,打包後通過視窗可友善地部署到任意環境,用過就知道很香。 建立示例應用 以 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
|