開發Nodejs應用通常要使用多個中間件,開發人員要把代碼跑起來就要在自己的機器上把中間件安裝一遍,費時費力,如果同時開發多個項目就更麻煩了,經常要改來改去。本文以一個Nodejs+MongoDB項目為例,展示Docker的基本使用方法,同時提供了編寫對Docker友好代碼的方法。
項目說明
tms-api-gw是一個API網關項目,功能是將收到的http請求根據業務規則轉發到對應的服務,每次收到的請求都要記錄日志,并進行計數,結果保存到mongodb中。
希望通過Docker解決如下幾個問題:
- 簡化mongodb的部署,方便開發人員在本地運行應用。
- 對nodejs應用進行打包,實現整體發布,方便運維人員部署。
Docker
首先請按照官網說明安裝Docker。
參考:https://docs.docker.com/get-started/#install-docker-desktop
使用Docker首先需要了解image
和container
的概念,簡單說,image
是運行環境的模版,container
是根據模版創建的實例。image
用Dockerfile
進行定義,可以認為Dockerfile
是一個批處理命令,通過執行命令,在鏡像上安裝包、復制文件、設置參數。有了image
就可以通過run
命令生成容器,生成的時候可以指定運行時的參數。如果有多個相關聯的容器,可以通過docker-compose
進行整體管理。docker-compose
根據編排文件docker-compose.yml
描述被管理的容器,通過docker-compose up
命令啟動,docker-compose down
命令關閉,這樣就不用對著每個容器單獨執行命令。
參考:https://docs.docker.com/reference/
通常我們需要的image
都已經有基礎版本,可以在hub.docker.com
上查找。每個鏡像通常都有一堆版本,最重要的區別在于image
是在哪個linux的版本上建的,建議使用alpine
,因為這個版本最小。
下面我們結合項目需求具體跑一遍Docker。
MongoDB
查找基礎鏡像。
docker search mongo
將鏡像拉到本地。
docker pull mongo
生成并運行容器。
docker run --rm --name tms-api-gw-mongo -p 27017:27017 -d mongo:latest
--rm
當容器退出時自動刪除。
--name tms-api-gw-mongo
指定容器的名字,后面操作容器時用的到。
-p 27017:27017
將容器內部的27017端口映射到主機的27017 端口。
-d
是指定在后臺運行。
進入容器查看數據。
docker exec -it tms-api-gw-mongo /bin/bash
在容器中用exit
命令退出容器。
這時在本地就有了個可用的MongoDB實例,數據保存在容器中,每次刪除容器,數據就會清除,這樣就總能用一個“干凈”的MongoDB進行開發。
如果需要持久保留MongoDB中的數據,可以讓容器將數據寫到本地目錄中,執行run
命令時指定參數。
docker run --rm --name tms-api-gw-mongo -p 27017:27017 -v $PWD/storage/mongodb:/data/db -d mongo:latest
-v $PWD/db:/data/db
將主機中當前目錄下的db掛載到容器的/data/db,作為mongo數據存儲目錄。
為了管理容器需要用到幾條命令:
docker ps -a #查看全部容器,不加-a參數只顯示運行的。
docker stop container_name # 停止指定的容器
docker rm container_name # 刪除指定的容器
Docker命令參考:https://docs.docker.com/engine/reference/commandline/cli/
Nodejs
先看看Nodejs官網的這篇文章:https://nodejs.org/zh-cn/docs/guides/nodejs-docker-webapp/,下面是以該文章為基礎進行調整。
制作docker鏡像。
在項目根目錄新建Dockerfile
文件,文件內容如下,和Nodejs官網文章不一致的地方加了注釋。
FROM node:alpine
# 安裝cnpm
RUN npm install cnpm -g
WORKDIR /usr/src/app
COPY package*.json ./
# 只安裝dependencies的包,不安裝devDependencies的包;額外安裝包。
RUN cnpm install --production \
&& cnpm install log4js
COPY . .
# 創建放配置文件的目錄
RUN mkdir config
# 設置應用的環境變量
ENV TMS_API_GW_ENV='docker'
EXPOSE 3000
CMD [ "node", "./app.js" ]
COPY . .
是把本地當前目錄下的內容復制到鏡像的工作目錄下/usr/src/app
,但是,node_modules
,config
這些內容不需要復制,因此要建立.dockerignore
文件,指定不需要復制的內容。
.*
node_modules
config
example
創建鏡像,注意不要丟了最后面的點。
docker build -t tms-api-gw-node .
用docker images可以查看已有鏡像。
運行容器
docker run --rm --name tms-api-gw-node -p 5678:3000 -v $PWD/config:/usr/src/app/config -d tms-api-gw-node
如果我們同時開發多個項目,經常會發生端口沖突的問題,通過-p
參數就可以在啟動容器時指定端口了。
同一份代碼需要在多個環境中運行,包括:開發,測試,生產等,我們通常是采用配置文件讓代碼和運行環境解耦。利用Docker,可以把代碼和代碼依賴的標準環境制作成鏡像,在生成容器時再指定和環境相關的配置文件,這樣Docker鏡像的整體就變成了一個發布單元,可以極大簡化運維工作。因此,在Dockerfile中我們創建了空的config
目錄,通過參數-v $PWD/config:/usr/src/app/config
指定使用運行環境本地的配置文件。
前面介紹項目基本情況時提到需要在mongodb中存儲api訪問數據,當應用和mongodb都在容器中運行時就產生了一個問題:mongodb的地址是什么?
在本地開發環境我們通常寫個localhost
,但是容器中的localhost
是容器并不是宿主機,應用無法訪問到mongodb。為了解決這個問題,在項目中引入了環境變量,看代碼config/gateway.sample.js
。
let host, port
if (process.env.TMS_API_GW_ENV === 'docker') {
host = 'docker.for.mac.host.internal'
port = 3000
} else {
host = 'localhost'
port = 5678
}
module.exports = {
...
}
前面Dockerfile
中指定了環境變量ENV TMS_API_GW_ENV='docker'
,代碼中可以根據這個環境變量進行相應的設置,在容器中docker.for.mac.host.internal
代表了宿主機的地址,否則還是用localhost
,指定端口要和Dockerfile
中EXPOSE
的端口一致,這樣不論是否在容器中Nodejs應用都可以訪問到MongoDB。
如果容器是在后臺運行,想查看Nodejs應用輸出的日志,使用如下命令:
docker logs tms-api-gw-node
因為鏡像是以alpine
為基礎制作,進入容器的命令需要調整,將bash
改為sh
。
docker exec -it tms-api-gw-node /bin/sh
至此我們已經可以在容器中運行Nodejs應用。
參考:https://github.com/nodejs/docker-node/blob/master/README.md#how-to-use-this-image
參考:一篇關于Nodejs使用Docker的最佳實踐,https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md
docker-compose
雖然已經可以用容器把Nodejs應用跑起來,但是還是不夠方便,mongodb和nodejs容器要分別啟停,命令還都挺長。能不能更簡單呢?可以,用docker-compose。
參考:https://docs.docker.com/compose/compose-file/
Docker for Mac已經包含了Compose了,所以Mac用戶不用單獨安裝Compose了。
創建docker-compose.yml
文件。
version: '3.7'
services:
app:
build: ./
image: tms-api-gw-node:latest
container_name: tms-api-gw-node
ports:
- '5678:3000'
volumes:
- ./config:/usr/src/app/config
depends_on:
- mongodb
mongodb:
image: mongo:latest
container_name: tms-api-gw-mongo
ports:
- '27017:27017'
logging:
driver: none
上面這個文件中指定的邏輯和前面通過run
命令分別運行容器是完全等效的。
啟動容器
docker-compose up -d
關閉容器
docker-compose down
更新鏡像
docker-compose build
反復更新鏡像會導致產生無效的鏡像,通過下面的命令刪除這些無用鏡像。
docker images
<none> <none> cb7a87c0359b 22 minutes ago 170MB
docker rmi $(docker images | grep "^<none>" | awk "{print $3}")
提示:實際在本機寫代碼時并不需要將Nodejs應用做成鏡像再運行,因為這樣每次修改代碼都要重新build,既花費時間又產生許多無用鏡像。這里演示Nodejs應用鏡像主要是為了下一步進行發布做準備。
總結
雖然Docker整體比較復雜,但是作為開發人員只需要掌握基本概念和常用命令就可以把Docker跑起來,可以極大簡化本地開發環境的管理工作,建議每個開發人員都嘗試一下。
下一篇研究如何利用Docker進行Nodejs應用的部署。