一次頭禿的 node + mongo docker 實踐

玩轉 docker

原文連接html

背景

最近在捯飭一個前端性能上報分析的項目,前端由 react 全家桶,打包部署公司有專門的發佈系統,這塊就沒什麼顧慮。前端

前端團隊的後端沒有什麼規範或通用流程,就想本身先技術選型從0到1,決定使用 egg + mongodb,後續也許會追加 nginx + redis + Kafka 相關配置。node

  • 問題來了:怎麼簡化部署配置流程?
  • 答: docker

目標

整體目標不外乎幾點:簡單、快速、安全。react

  • 簡單
  1. 一次配置,不一樣環境都可執行,這也是 docker 的優點。
  2. 部署簡單,可能就幾行甚至一行命令行,方便接入 CI
  3. 本地開發方便。
  • 快速
  1. 開發編譯熱重載要快。
  2. 鏡像包、部署包等要小,上傳下載部署纔會快。
  3. 快速回滾。
  • 安全
  1. 源碼無泄漏風險。
  2. MongoDB 開啓安全驗證。

行動

PS:搜索搜的頭禿,如下不少關鍵知識點收集自 google + github issue + Stack Overflow + docker 官方文檔 + 英文博客。英文有障礙真是影響效率。nginx

接下來講說實踐中遇到的問題。git

無 docker

步驟:github

  1. 下載 node、mongodb等。
  2. 配置 node、mongodb等。
  3. 啓動 egg 開發。

換臺電腦或協同其餘小夥伴開發時,得把你的動做重複一遍。不一樣的操做系統和下的不一樣 node 或 db 版本,都有可能致使系統運行不起來。web

你確定聽過這句話:個人電腦上是好的啊。redis

初探 docker

約束不了團隊衆多軟件的安裝和版本的控制,要求安裝必定範圍的 docker 仍是簡單的吧。mongodb

例如啓動 mongodb 服務

docker run -p 27017:27017 -v <LocalDirectoryPath>:/data/db --name docker_mongodb -d mongo:4.2.6
複製代碼

這裏咱們啓動了一個 mongo 最新穩定版本的 docker 容器。簡單說明下:

  • run 運行鏡像,本地沒有會自動拉取。
  • -p 端口映射,本地 27017 映射容器 27017 端口,就能夠經過訪問本地端口而訪問 docker mongo 的服務了。
  • -v 本地 <LocalDirectoryPath>映射容器目錄 /data/db,用來持久化數據庫,否則容器刪除數據也丟失了。
  • --name 給容器取個名字,匿名容器能夠經過 docker container prune 刪除。
  • -d 後臺運行
  • mongo:4.2.6 docker hub官方鏡像:版本

接下來本地啓動跟無 docker 效果是同樣的。

egg 鏡像化

編寫 Dockerfile 文件

# 基於的基礎鏡像
FROM node:12.16.3
 # 踩坑1:注意目錄使用前,保證存在 RUN mkdir -p /usr/src/egg  # 注意不要用 cd,須要瞭解 docker 分層構建的概念,須要改變上下文的 pwd,使用 WORKDIR WORKDIR /usr/src/egg  # 複製 Dockerfile 同級內容到容器的 /usr/src/egg 目錄下 COPY . .  # 安裝 npm 包 RUN npm install  # 暴露端口,這裏只是聲明便於理解維護,實際映射使用須要 -p xxx:7001 EXPOSE 7001  # 啓動容器後默認執行的命令行 CMD [ "npm", "run", "start" ] 複製代碼

編寫 .dockerignore 文件

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

忽略 node_modules

  1. 構建時會把目錄內容發送給 docker 進程,減小 I/O。
  2. 本地操做系統和版本安裝的 npm 包未必適合 docker 環境運行,避免衝突。
  • 構建
docker build -t node:egg .
複製代碼
  • 查看 image
docker images
複製代碼
REPOSITORY  TAG  IMAGE ID      CREATED         SIZE
node        egg  ae65b8012120  28 seconds ago  1.12GB
複製代碼
  • 運行
docker run -p 7001:7001 -d node:egg
複製代碼
  • 查看運行容器
docker ps # 查看運行容器,獲取CONTAINER ID,-a 能夠查看全部包括中止的容器
複製代碼
  • 查看容器 log
docker logs b0d0c3df5eed
複製代碼
  • 進入容器
docker exec -it b0d0c3df5eed bash
du -a -d 1 -h # 查看容器目錄文件大小
複製代碼

踩坑2:在 docker 中運行記得把 package.json 中的 egg-scripts start --daemon 中的 --daemon 刪掉。 須要理解前臺、後臺運行進程的概念,docker 中的 shell 腳本必須之前臺方式運行。

優化鏡像大小

上文看到,可能源代碼就幾百K,鏡像包卻超過1G。看看能有哪些優化手段。

  • 基礎鏡像下手

也許你並不須要 docker node 提供完整的例如 bash、git 等工具。只須要基本的 node 運行環境便可,則可使用 alpine 鏡像。

- FROM node:12.16.3
+ FROM node:12.16.3-alpine
複製代碼
  • npm 包優化
- RUN npm install
+ # 無關運行的開發依賴包都該歸屬 devDependencies
+ RUN npm install --production
複製代碼
  • 打包
docker build -t node:simplfy .
複製代碼
  • 效果
REPOSITORY  TAG      IMAGE ID      CREATED         SIZE
node        simplfy  8ccafec91d90  28 seconds ago  132MB
複製代碼

鏡像包從 1.12G 降到了 132MBnode_modules214MB 降到了 44.5M

踩坑3:alpine 鏡像容器不支持 bash

你若是須要 bash、git 能夠這麼作 issue

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh
複製代碼

或不想臃腫你的鏡像 issue,其實你可使用 sh

docker exec -it container_id sh
複製代碼

你真的須要 egg 鏡像嗎?

在無 docker 本地開發時,你可使用 egg-mongoose 這樣鏈接數據庫

`mongodb://127.0.0.1/your-database`
複製代碼

使用 docker 後,容器的 127.0.0.1localhost 與你本地的環境是不通的。

有兩種方式鏈接 docker mongo:

  1. 使用可訪問的真實IP地址,例如: mongodb://192.1.2.3/your-database
  2. docker networks 容器間的的通訊。

例如在 Dockerfile 中設置真實IP

# 設置數據庫IP
ENV docker_db=192.1.2.3
複製代碼

鏈接 url

`mongodb://${process.env.docker_db}/your-database`
複製代碼

對於每個開發都得不停更換網絡IP,對開發不友好。

思考:

  1. 如何自動區分本地與 docker 起的環境?
  2. 如何隔離本地與 docker 例如 node_modules 衝突?
  3. 如何既能享受本地環境和工具帶來的開發效率,又能快速切入 docker 查看部署效果。

鏡像包還面臨一個存儲問題,不當心發到開源 docker hub 倉庫,可能致使源碼泄漏。

自建倉庫?

大多數教程一上來,必然或大篇章都是 Dockerfile 構建鏡像。介於以上種種,能不能換種思路,放棄構建 image。

docker-compose

docker-compose 用來編排多容器的啓動部署。

mongo 配置

  • 新建 docker-compose.yml 文件。
version: "3"
 services:  db:  image: mongo:4.2.6 # 鏡像:版本  environment:  - MONGO_INITDB_ROOT_USERNAME=super # 默認開啓受權,並建立超管用戶 mongo -u godis -p godis@admin --authenticationDatabase admin  - MONGO_INITDB_ROOT_PASSWORD=xxx # 超管密碼,敏感數據也可使用 `secrets`,不贅述。  - MONGO_INITDB_DATABASE=admin # *.js 中默認的數據庫  volumes:  - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro  - ./mongo-volume:/data/db  ports:  - "27017:27017"  restart: always 複製代碼

簡單說明

  • version: "3",不是指你的應用配置版本,而是指 docker 支持的版本,詳情說明

  • MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD 環境變量用來開啓受權,docker 自動建立一個數據庫超管角色。

  • docker mongo 啓動容器時會執行 /docker-entrypoint-initdb.d/ 中的 *.js 腳本,例如這裏 init-mongo.js 來初始化數據庫角色。

  • MONGO_INITDB_DATABASE 數據庫就是 *.js 中默認的 db 對象,這裏指向 admin

  • ./mongo-volume:/data/db 映射目錄或卷,持久化數據庫文件。

  • init-mongo.js

// https://stackoverflow.com/questions/42912755/how-to-create-a-db-for-mongodb-container-on-start-up
// 分別在 user、staff 數據庫上建立訪問角色。
// 這裏 db 是 MONGO_INITDB_DATABASE 指定的數據庫
db.getSiblingDB('user')
  .createUser(
    {
      user: 'user',
      pwd: 'xx',
      roles: [ 'readWrite', 'dbAdmin' ],
    }
  );
 db.getSiblingDB('staff') .createUser( { user: 'staff', pwd: 'yy', roles: [ 'readWrite', 'dbAdmin' ], } ); 複製代碼
  • 密碼配置有點散亂,如何跟應用一起存取(docker-compose 設置 secrets 文件,node 也讀取改文件?),有點麻煩,讀者有更好的方案還望不吝賜教。

node 配置

services:
  ...
 server:
 image: node:12.16.3-alpine
 depends_on:
 - db
 volumes:
 - ./:/usr/src/egg
 environment:
 - NODE_ENV=production
 - docker_db=db
 working_dir: /usr/src/egg
 command: /bin/sh -c "npm i && npm run start" # not works: npm i && npm run start and not support bash
 ports:
 - "7001:7001"
 volumes:  nodemodules: 複製代碼

說明:

  • depends_on 表示依賴的容器,docker 會等待依賴項先啓動。
  • volumes 映射本地目錄到容器,這樣本地修改了也能影響到容器。
  • environment 能夠在 process.env 拿到。
  • working_dir 設置 pwd,不像 Dockerfile,不存在是會自動建立。
  • command 啓動容器後執行的命令行。

踩坑4:command: npm i && npm run start 不支持 &&alpine 鏡像不支持 bashegg-bin dev 會報錯 Error: Cannot find module '/bin/bash'

  • 注意:docker node 是如何與 docker mongo 通訊的?
environment:
  ...
 - docker_db=db # db 就是 services 中定義 mongo 的名稱
複製代碼
`mongodb://${process.env.docker_db}/your-database`
複製代碼

大部分教程都是用 links 來解決,但官方不推薦並準備廢棄。推薦使用 networks。這裏並無配置 networks。 這是由於 docker 會默認建立名稱爲 projectname_defaultnetworks,用來 docker-compose 容器間的通訊。

  • 如何隔離本地與 docker node 映射中 node_modules?
services:
  ...
  server:
    image: node:12.16.3-alpine
    depends_on:
      - db
    volumes:
+ - nodemodules:/usr/src/egg/node_modules
      - ./:/usr/src/egg
    environment:
      - NODE_ENV=production
      - docker_db=db
    working_dir: /usr/src/egg
    command: /bin/sh -c "npm i && npm run start" # not works: npm i && npm run start and not support bash
    ports:
      - "7001:7001"
 + volumes: + nodemodules: 複製代碼
  • docker-compose.yml 文件目錄下文件運行 docker-compose
docker-compose up -d
複製代碼
  • 爲何不直接使用匿名卷?
volumes:
 - :/usr/src/egg/node_modules
 - ./:/usr/src/egg
複製代碼
  • 答:若是須要多 docker-compose 文件來區分環境,例如開發時,沒有必要每次啓動時執行一次 npm i

建立 docker-compose.notinstall.yml 文件

version: "3"
 services:  server:  environment:  - NODE_ENV=development # 覆蓋  - NEW_ENV=add # 新增  command: npm run start # 覆蓋 複製代碼
  • 第二次,你能夠執行如下命令,減小 npm i 帶來的消耗。若是你使用匿名卷,則 node_modules 每一個容器相互獨立沒法共享,致使報錯。
docker-compose -f docker-compose.yml -f docker-compose.notinstall.yml up -d
複製代碼

更多多文件使用,查看文檔 Share Compose configurations between files and projects

最後藉助 package.json scripts 優化記憶命令行。

結果

開發部署問題暫時告一段落,項目還在開發當中,線上運行一段時間後再來分享。水平有限,有錯誤歡迎指出,有更好的建議也歡迎補充。

參考

  1. A Better Way to Develop Node.js with Docker
  2. Dockerizing a Node.js web app
  3. docker_practice
  4. YAML
  5. Managing MongoDB on docker with docker-compose
  6. mongoosejs
  7. docker mongo
  8. egg-docker-template
  9. 只需簡單兩步,輕鬆縮減 Node.js 應用的鏡像大小
  10. Cannot pass any ENV variables from docker to Node process.env
相關文章
相關標籤/搜索