用Docker簡化Nodejs開發1——開發環境

開發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首先需要了解imagecontainer的概念,簡單說,image是運行環境的模版,container是根據模版創建的實例。imageDockerfile進行定義,可以認為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_modulesconfig這些內容不需要復制,因此要建立.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,指定端口要和DockerfileEXPOSE的端口一致,這樣不論是否在容器中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應用的部署。

本系列其他文章

用Docker簡化Nodejs開發2——開發環境到測試環境

用Docker簡化Nodejs開發3——用webhook實現自動更新

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容