容器將應用與環境打包整合,解決了應用外部依賴的痛點,打包后通過窗口可方便地部署到任意環境,用過就知道很香。
創建示例應用
以 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: