[Express+Vue 搭建電商網站] 22 使用 Docker 將專案容器化
使用 Express + Vue 搭建一個電商網站 - 使用 Docker 將專案容器化
接著我們會把整個專案使用 Docker
來將服務容器化,也會在 MongoDB 設定身份驗證機制
如果對 Docker 不熟的話必須先去了解,才有辦法進行
或是直接跳過這個章節 交給熱心的攤主處理
在這邊我們會用到三個容器
- nginx 伺服器:處理 Vue 框架實現的前端靜態頁面
- api:運行我們使用 Express 建立的 API 服務
- db:就是一個運行 MongoDB 資料庫的容器
流程會變成
資料進來由 nginx 分派,如果是前端或是靜態資源就直接從 nginx 回傳
若是 API 類型的請求則轉交給 API 容器處理後再回傳資料。
這種架構有以下優勢
- 透過 nginx 過濾非法請求
- 解決前後端跨域問題,因為兩者間都透過相同端點訪問
- 可以輕鬆擴增伺服器大小,並且使用 Nginx 做負載平衡
前端容器化
首先容器化的是先前用 Vue 寫的前端專案,將專案打包成靜態頁面
npm run build
接著增加 nginx 設定檔 client/config/nginx.conf
server {
listen 80;
root /www;
index index.html;
sendfile on;
sendfile_max_chunk 1M;
tcp_nopush on;
gzip_static on;
location /api/v1 {
proxy_pass http://api:3000;
}
location / {
try_files $uri $uri/ /index.html;
}
}
其中要特別注意的是 location
規則
- 請求目標如果是
/api/v1
,那就把請求傳到api
容器裡 - 請求目標
/
,則直接回傳靜態頁面index.html
然後要回頭稍微修改一下前端訪問後端的網址,打開 client/src/store/actions.js
修改 API_BASE
成
const API_BASE = '/api/v1';
這樣改了之後前端在對 API 發起請求時就是取決於當前頁面的網址,而不是固定的 http://localhost:3000/api/v1
接著就是要撰寫 Docker 相關設定
首先建立 client/src/Dockerfile
FROM nginx:1.13
# 刪除預設的 Nginc 設定
RUN rm /etc/nginx/conf.d/default.conf
# 加入自己定義的 Nginx 設定檔
COPY config/nginx.conf /etc/nginx/conf.d/
# 將前端靜態檔案映射到容器的 /www 目錄下
COPY dist /www
接著建立 client/.dockerignore
,裡面只有短短一行
node_modules
代表不 mount 進 docker 的資料夾
後端容器化
前端容器化之後,接著準備進行後端的容器化。首先把寫死的 MongoDB 連線字串透過環境變數注入,修改 server/app.js
連線資料庫的部分成
mongoose.connect(process.env.MONGO_URI || `mongodb://localhost:27017/test`);
接著一樣新建 server/Dockerfile
FROM node:10
# 指定工作目錄為 /usr/src/app,接下来的指令全部在這個路徑下操作
WORKDIR /usr/src/app
# 將 package.json 複製到根目錄
COPY package*.json ./
# 安裝 npm 依賴
RUN npm install
# 複製全部程式內容
COPY . .
# 設定環境變數
ENV NODE_ENV=production
ENV MONGO_URI=mongodb://db:27017/test
ENV HOST=0.0.0.0
ENV PORT=3000
# 曝露出 3000 port
EXPOSE 3000
# 設定映像檔內執行的指令
CMD ["npm", "start"]
和前端一樣建立 server/.dockerignore
確定不會把 node_modules
給映射進容器裡
裡面也只有一行:
node_modules
Docker Compose
Docker Compose 可以讓我們很好的管理 docker 容器,只需要透過一個 YAML 檔就可以修改設定。
在專案的根目錄下建立(跟 client 和 server 兩個資料夾同層)docker-compose.yml
version: '3'
services:
db:
image: mongo
restart: always
api:
build: server
restart: always
ports:
- 3000:3000
nginx:
build: client
restart: always
ports:
- 8080:80
可以看到我們建立了三個 service
,對應到前面說的 db
、api
、nginx
- db:指定使用 mongo 映像檔,然後當服務意外停止則總是重啟
- api:映像檔透過
server
資料夾內來建立,對外 port 為3000:3000
- nginx:映像檔透過
client
資料夾內來建立,對外 port 為8080:80
,外部的 8080 port 會指向到內部 80 port
如果 docker-compose.yml 中的 service 使用 image 指定映像檔,則會去Docker Hub 上拉回指定的映像檔
若是使用build
則會根據指定目錄下的Dockerfile
來建立映像檔。
接著就是測試的時間!打開 Docker 之後,在終端機輸入
docker-compose up --build
第一次執行會多花一些時間,因為先前提過的要從Docker Hub
上拉回指定的映像檔,如下圖
可以藉由
docker ps
來觀察目前所有的容器狀態,看起來沒問題的話就可以開啟 localhost:8080
來看看專案是不是跟原本一樣
MongoDB 身份驗證
在之前的設定中我們的 MongoDB 資料庫並沒定任何身份驗證
所以任何人只要能夠發送請求到資料庫都可以對資料庫做修改,這是一個可怕的資安問題!
接著我們要來搞定 MongoDB 的身份驗證,增加系統的安全性
修改 MongoDB 連線設定
打開 server/app.js
,修改連線資料庫的部分成以下
ongoose.connect(process.env.MONGO_URI || `mongodb://localhost:27017/test`, {
useNewUrlParser: true,
useUnifiedTopology: true,
user: process.env.MONGO_USER,
pass: process.env.MONGO_PASSWORD,
});
這代表
- useNewUrlParser:使用新的 MongoDB 驅動 URL 解析器
- useUnifiedTopology:使用新的連線管理引擎,支持重新連線,這樣可以大大提升連線穩定性
- user:連線的使用者名稱,通過環境變數注入
- pass:連線使用的密碼,通過環境變數注入
Dockerfile 中注入環境變數
在 server/Dockerfile
中加入下面的環境變數:
ENV NODE_ENV=production
ENV MONGO_URI=mongodb://db:27017/admin
ENV MONGO_USER=mongoadmin
ENV MONGO_PASSWORD=secret
ENV HOST=0.0.0.0
ENV PORT=3000
可以發現一些不同的地方,調整了 MONGO_URI
,把預設的 test
換成 admin
這是為了啟用驗證功能並且使用 admin
作為 Authentication Database 的使用者
設定 Docker Compose 預設密碼
接著在 docker-compose.yml
裡面幫 db
服務加入預設密碼的環境變數
db:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: mongoadmin
MONGO_INITDB_ROOT_PASSWORD: secret
測試
首先使用
docker-compose down --volumes
不只關閉了原本運行中的 docker 容器,還透過 --volumes
把原本的建立 MongoDB 的容器徹底刪除
若不這麼做,之後重啟容器會跳過初始化使用者的過程,這樣我們的驗證資料庫就沒辦法被建立,而會載入之前的資料
接著重新啟動容器
docker-compose up --build
應該會看到網頁一切正常,不過資料庫已經有了驗證,不是「裸奔」的狀態了
專案範例程式碼 GitHub 網址:ray247k/mini-E-commerce