Node.js 服務 Docker 容器化應用實踐

誰不會休息,誰就不會工做。 —— 列寧node

本篇不會講解 Docker 命令的使用、安裝等,由於在以前一篇文章 一文零基礎教你學會 Docker 入門到實踐 中也已經講解的很詳細了,不清楚的能夠點擊連接回頭在從新看下,本篇重點是介紹 Node.js 項目如何進行 Docker 容器化及一些實踐優化,還有一些常見的問題,固然若是還有其它使用上的問題也歡迎你們在評論區進行留言補充。mysql

做者簡介:五月君,Nodejs Developer,熱愛技術、喜歡分享的 90 後青年,公衆號「Nodejs技術棧」,Github 開源項目 www.nodejs.redgit

經過本篇文章能學到什麼?github

  • 學會如何用 Docker 容器化一個 Node.js 服務
  • 動態設置環境變量一份 Dockerfile 文件構建不一樣的版本
  • Node.js 私有 NPM 包在構建鏡像時如何認證
  • Egg.js 框架 Docker 容器化應該注意的問題
  • Docker 鏡像體積與構建時間的優化

Docker 化一個 Node.js 應用程序

在本篇開始咱們先建立一個簡單的 Node.js 應用,而後爲這個應用建立一個 Docker 鏡像,並構建和運行它sql

建立 Node.js 項目

首先咱們須要建立一個 app.js 開啓一個 HTTP 服務,後面會藉助 Docker 來運行這個程序docker

const http = require('http');
const PORT = 30010;

const server = http.createServer((req, res) => {
    res.end('Hello Docker');
})

server.listen(PORT, () => {
    console.log('Running on http://localhost:', PORT, 'NODE_ENV', process.env.NODE_ENV);
});
複製代碼

而後咱們建立一個 package.json 文件,這裏是描述你的應用程序以及須要的依賴,寫過 Node.js 的同窗應該會很熟悉的,這裏我在 scripts 裏面增長了 npm run devnpm run pro 兩個命令,由於我想在這裏介紹如何在構建時傳入參數來動態設置環境變量。npm

{ 
    "name": "hello-docker", 
    "version": "1.0.2",
    "description": "", 
    "author": "May",
    "main": "app.js",   
    "scripts": {
      "dev": "NODE_ENV=dev node app.js",
      "pro": "NODE_ENV=pro node app.js"
    }
}
複製代碼

Dockerfile 文件

這是一個 Dockerfile 文件所包含的信息,這些命令在 Docker 入門與實踐 中也有講解過json

FROM node:10.0-alpine

RUN apk --update add tzdata \ && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && apk del tzdata 
RUN mkdir -p /usr/src/nodejs/ 
WORKDIR /usr/src/nodejs/ 
# add npm package
COPY package.json /usr/src/nodejs/package.json RUN cd /usr/src/nodejs/ RUN npm i 
# copy code
COPY . /usr/src/nodejs/ 
EXPOSE 30010

CMD npm run dev 複製代碼

在 Dockerfile 的同級文件下建立一個 .dockerignore 文件,避免將你本地的調試文件、node_modules 等一些文件放入 Docker 容器中緩存

.git
node_modules
npm-debug.log
複製代碼

此時經過如下命令便可構建一個 Docker 鏡像bash

$ docker image build -t mayjun/hello-docker
複製代碼

再經過 docker run -d -p 30010:30010 mayjun/hello-docker 命令可運行一個 Docker 容器,可是有個疑問我是有生產和測試之分的,按照上面 CMD npm run dev 這樣寫死只能打包一種環境,固然你也能夠在建一個文件來實現或者一些其它的方法。

動態設置環境變量

爲了解決上面的疑問,個人想法是在鏡像構建時傳入參數來動態設置環境變量,對 Dockerfile 文件作下修改,看如下實現:

EXPOSE 30010

ARG node_env # 新增長
ENV NODE_ENV=$node_env  # 新增長
CMD npm run ${NODE_ENV} # 修改 複製代碼

下面對上面的代碼作個解釋

  • 經過 ARG 指令定義了一個變量,用戶能夠在構建時經過使用 --build-arg = 標誌的 docker build 命令將其傳遞給構建器 ARG node_env
  • 在 Dockerfile 中使用 ENV 引用這個變量 ENV NODE_ENV=$node_env
  • 這一步就是使用了 CMD npm run ${NODE_ENV}

剩下的就是在構建鏡像時動態傳入參數了

$ docker image build --build-arg node_env=dev -t mayjun/hello-docker:1.0.2 . # 構建測試環境
$ docker image build --build-arg node_env=pro -t mayjun/hello-docker:1.0.2 . # 構建生產環境
複製代碼

運行容器

$ docker run -d -p 30010:30010 mayjun/hello-docker:1.0.2
$ docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                      NAMES
2bc6e62cd0e8        mayjun/hello-docker:1.0.2   "/bin/sh -c 'npm run…"   3 minutes ago       Up 3 minutes        0.0.0.0:30010->30010/tcp   elastic_bouman
複製代碼

查看容器日誌

docker logs -f 2bc6e62cd0e8

> hello-docker@1.0.0 dev /usr/src/nodejs
> NODE_ENV=dev node app.js

Running on http://localhost: 30010 NODE_ENV dev
複製代碼

我將以上代碼打包成了鏡像 mayjun/hello-docker:1.0.2,能夠拉取查看 docker pull mayjun/hello-docker:1.0.2

Docker 與 Node.js 私有 NPM 包

若是你的項目中使用了私有 NPM 包,在 Dcoker 構建鏡像過程當中會出現 npm 私有包安裝 404 的錯誤,若是是在容器外部咱們能夠 npm login 登錄擁有 NPM 私有包權限的帳戶,來解決這個問題,可是在 Docker 的時候是不能這樣作的。

建立身份驗證令牌

爲了安裝私有包咱們須要 「建立身份驗證令牌」 以便在持續集成環境、Docker 容器內部能訪問咱們的私有 NPM 包,如何建立可參考 docs.npmjs.com/creating-an…

實現方法

咱們在建立 Dockerfile 文件過程當中就須要增長如下兩條命令:

# 528das62-e03e-4dc2-ba67-********** 這個 Token 就爲你建立的身份驗證令牌 token
RUN echo "//registry.npmjs.org/:_authToken=528das62-e03e-4dc2-ba67-**********" > /root/.npmrc RUN cat /root/.npmrc 複製代碼

Egg 框架 Docker 容器化

在 Egg 裏面,若是是 egg-scripts start --daemon去掉 --daemon 直接 egg-scripts start 便可,不然 Docker 容器會沒法啓動。

看如下代碼示例,修改下 package.json 便可,Dockerfile 文件同上面第一個 Docker 化一個 Node.js 應用程序 是同樣的

package.json

{
  "scripts": {
    "start": "egg-scripts start" // 去掉 --daemon
  }
}
複製代碼

也可參考 Egg Issues 「docker容器不能run起來,請問有碰到的嗎?」 github.com/eggjs/egg/i…

Docker 鏡像體積與構建時間優化

若是一個鏡像在不通過優化的狀況下體積一般都是會很大的,如下也是在實踐過程當中作的幾點優化。

RUN/COPY 分層

Dockerfile 中的每條指令都會建立一個鏡像層,Dockerfile 指令或複製的項目文件在沒有修改變更的狀況下,每一個鏡像層是能夠被複用和緩存的。

如下代碼可在 mayjun/hello-docker:latest 鏡像倉庫找到,如下示例中,源碼改變以後,無論 package.json 有沒有改變的狀況下都會從新安裝 NPM 模塊,這樣顯然是很差的,所以下面咱們要改進

# ...

WORKDIR /usr/src/nodejs/hello-docker COPY . /usr/src/nodejs/hello-docker 
RUN npm install 
# ...
複製代碼

改進以後的代碼以下所示,咱們讓 package.json 提早,在 package.json 沒有修改的狀況下是不會從新安裝 NPM 包的,也會減小部署的時間。

# ...

WORKDIR /usr/src/nodejs/ 
# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i 
# copy code
COPY . /usr/src/app/ 
# ...
複製代碼

Node.js Alpine 鏡像優化

mayjun/hello-docker:1.0.0 這個鏡像在 Docker 倉庫也可搜索到,在未優化以前大約在 688MB

$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE mayjun/hello-docker 1.0.0 7217fb3e9daa 5 seconds ago 688MB

使用 Alpine 優化

Alpine 是一個很小的 Linux 發行版,想要大幅度減少鏡像體積選擇 Node.js 的 Alpine 版本也是最簡單的,另外 -alpine 的時區默認不是國內的,須要 Dockerfile 配置時區。

FROM node:10.0-alpine

RUN apk --update add tzdata \ && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone \ && apk del tzdata 
RUN echo "Asia/Shanghai" > /etc/timezone 
RUN mkdir -p /usr/src/nodejs/ 
WORKDIR /usr/src/nodejs/ 
# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i 
# copy code
COPY . /usr/src/app/ 
EXPOSE 30010
CMD npm start 複製代碼

從新打包了一個版本 mayjun/hello-docker:1.1.0 再次查看下效果,能夠看到鏡像文件從 688MB 減小至 85.3MB,這個體積優化仍是很大的

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mayjun/hello-docker   1.1.0               169e05b8197d        3 minutes ago       85.3MB
複製代碼

生產環境不要打包 devDependencies 包

有些測試環境用的包,在進行生產環境打鏡像時不要包含進去,也就是 package.json 文件 devDependencies 對象,經過在 npm i 以後指定 --production 參數過濾

改進以下所示:

FROM node:10.0-alpine

# 省略 ...

# add npm package
COPY package.json /usr/src/app/package.json RUN cd /usr/src/app/ RUN npm i --production # 改變在這了 
# 省略 ...
複製代碼

從新打包了一個版本 mayjun/hello-docker:1.2.0 再次查看下效果,能夠看到鏡像文件從 85.3MB 又減小至 72.3MB

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mayjun/hello-docker   1.2.0               f018aa578711        3 seconds ago       72.3MB
複製代碼

常見問題

Question1

如下命令在刪除鏡像的時候報以下錯誤:

$ docker rmi 6b1c2775591e
Error response from daemon: conflict: unable to delete 6b1c2775591e (must be forced) - image is referenced in multiple repositories
複製代碼

細心的你也許會發現鏡像 ID 6b1c2775591e 同時指向了 hello-docker 和 mayjun/hello-docker 倉庫,這也是形成刪除失敗的緣由

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mysql                 5.7                 383867b75fd2        6 days ago          373MB
hello-docker          latest              6b1c2775591e        7 days ago          675MB
mayjun/hello-docker   latest              6b1c2775591e        7 days ago          675MB
複製代碼

指定 repository 和 tag 來刪除,執行刪除命令以後再次查看 mayjun/hello-docker 倉庫就已經沒有了

$ docker rmi mayjun/hello-docker
$ docker images                 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               5.7                 383867b75fd2        6 days ago          373MB
hello-docker        latest              6b1c2775591e        7 days ago          675MB
複製代碼

Question2

執行刪除鏡像命令報以下錯誤:

$ docker rmi 9be467fd1285
Error response from daemon: conflict: unable to delete 9be467fd1285 (cannot be forced) - image is being used by running container 1febfb05b850
複製代碼

根據提示是有正在運行的容器,需先中止容器、刪除容器以後在刪除鏡像

$ docker container kill 1febfb05b850 # 中止容器
$ docker rm 1febfb05b850 # 刪除容器
$ docker rmi 9be467fd1285 # 刪除鏡像
複製代碼

Question3

設定的工做目錄(WORKDIR)要與下面的要保持一致

...
WORKDIR /usr/src/nodejs/

# add npm package
COPY package.json /usr/src/node/package.json # 目錄不一致
RUN cd /usr/src/node/ # 目錄不一致
RUN npm i
...
複製代碼

例如,如以上配置由於工做目錄與實際 COPY 的目錄不一致,會致使報如下錯誤:

圖片描述

再按照如下方式更改成一致便可

...
WORKDIR /usr/src/nodejs/

# add npm package
COPY package.json /usr/src/nodejs/package.json # 更改成一致
RUN cd /usr/src/nodejs/ # 更改成一致
RUN npm i
...
複製代碼
相關文章
相關標籤/搜索