转载

如何以 Vue + Nginx + Node 實現 Microservice ?

若要使用 Microservice 架構,則會各自將 Vue 與 Node 包成 Docker Image,然後使用 Docker Compose 一次啟動 Vue 與 Node,此時 Node 會包在 Docker 內部網路,Vue 需使用 Nginx 的 Reverse Proxy 才能連上 Node。

Version

macOS Mojave 10.14.6

Docker Desktop for macOS 2.1.0.2 (37199)

WebStorm 2019.2.1

Vue 2.6.10

Vue CLI 3.11.0

Nginx 1.17.2

Node 12.9.1

Express 4.17.1

Vue

使用 Vue CLI 建立 Vue project,並自行在根目錄新增或修改以下檔案:

  • dockerfile
  • default.conf
  • package.json
  • docker-compose.yml
  • .env
  • vue.config.js

dockerfile

FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY default.conf /etc/nginx/conf.d/
EXPOSE 80

須先建立 dockerfile ,才能產生 Vue 的 image。

第 1 行

FROM nginx:alpine

使用最新版 nginx:alpine 為基底建立 image。

nginx:apline 為最新版為 Docker 最佳化的 image,size 較小

第 2 行

COPY dist /usr/share/nginx/html

dist 目錄下所有檔案複製到 image 內的 /usr/share/nginx/html 目錄下,此為 Nginx 放 HTML 之處。

dist 為 Vue CLI yarn build 編譯後最後結果,稍後會建立

第 3 行

COPY default.conf /etc/nginx/conf.d/

將 Nginx 設定檔 default.conf 複製到 image 內的 /etc/nginx/conf.d/ 目錄下。

第 4 行

EXPOSE 80

宣告此 image 使用 80 port,未來 docker-compose.yml 較易整合,一看 dockerfile 就知道使用 80 port 。

default.conf

server {
  listen       80;
  server_name  localhost;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }

  location /api/ {
    proxy_pass http://express:3000/;
  }
}

Nginx 的設定檔,會複製進 docker image 內。

第 2 行

listen       80;
server_name  localhost;

location / {
  root   /usr/share/nginx/html;
  index  index.html index.htm;
}

error_page   500 502 503 504  /50x.html;
location = /50x.html {
  root   /usr/share/nginx/html;
}

為 Nginx 預設的設定,就不加以修改。

listen 80;

Nginx 使用 80 port,與 dockerfileEXPOSE 80 相互對應。

default.conf 所設定的 port 才是 Nginx 實際使用的 port, dockerfileEXPOSE 只是宣告用,方便日後 docker-compose.yml 維護

15 行

location /api/ {
  proxy_pass http://express:3000/;
}

當 Vue 以 /api/hello-world 呼叫 API 時,會由 Nginx 的 reverse proxy 轉成 http://express:3000/hello-world

也就是對 Nginx 而言, /api 之後傳的部分,會接在 http://express:3000/ 之後,其中 express 為 Docker 內部 service 名稱,而 http://express:3000 也只有 Docker 內部可訪問。

package.json

{
  "name": "vue-microservice-nginx",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "serve": "node server/app.js & vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build-vue": "yarn build && docker build -t vue-nginx:$npm_package_version .",
    "build-node": "docker build -t node-express:$npm_package_version ./server",
    "all": "yarn build-vue && yarn build-node",
    "up": "docker-compose up -d",
    "down": "docker-compose down"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "core-js": "^2.6.5",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.11.0",
    "@vue/cli-plugin-eslint": "^3.11.0",
    "@vue/cli-service": "^3.11.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.6.10"
  }
}

Vue 的 package.json ,除了紀錄 Vue 所使用的 dependency 外,還使用 Yarn Script 來管理 Docker。

第 6 行

"serve": "node server/app.js & vue-cli-service serve",

只要執行 yarn serve 就同時啟動 Node 與 Vue 的 Dev Server。

第 9 行

"build-vue": "yarn build && docker build -t vue-nginx:$npm_package_version .",

新增 Yarn script,只要執行 yarn build-vue ,就會先執行 yarn build ,然後執行 docker build 建立 Vue 的 Docker image,且自動抓 version 版號。

第 10 行

"build-node": "docker build -t node-express:$npm_package_version ./server",

新增 Yarn script,只要執行 yarn build-node 就會執行 docker build 建立 Node 的 Docker image,且自動抓 version 版號。

11 行

"all": "yarn build-vue && yarn build-node",

新增 Yarn script,只要執行 yarn all 就會執行 yarn build-vueyarn build-node 一次建立 Vue 與 Node 的 Docker image。

12 行

"up": "docker-compose up -d",

新增 Yarn script,只要執行 yarn up 就會執行 docker-compose up -d 同時啟動 Vue 與 Node 兩個 container。

13 行

"down": "docker-compose down"

新增 Yarn script,只要執行 yarn down 就會執行 docker-compose down 同時停止 Vue 與 Node 兩個 container。

docker-compose.yml

version: "3"
services:
  nginx:
    image: vue-nginx:${VUE_NGINX_TAG}
    restart: always
    ports:
      - "80:80"

  express:
    image: node-express:${NODE_EXPRESS_TAG}
    restart: always

nginxexpress 兩個 service 整合在同一個 Docker 內。

第 1 行

version: "3"
services:
  nginx:
    ...
  express:
    ...

docker-compose.yml 一共啟動兩個 service:

nginx
express

第 3 行

nginx:
  image: vue-nginx:${VUE_NGINX_TAG}
  restart: always
  ports:
    - "80:80"

設定 nginx service:

  • image :使用剛建立的 vue-nginx image,版本則由 .envVUE_NGINX_TAG 變數決定
  • restart :當 container crash 時,會自動重啟
  • ports :container 內的 80 port,對應到外部的 80 port

第 9 行

express:
  image: node-express:${NODE_EXPRESS_TAG}
  restart: always

設定 express service:

  • image :使用剛建立的 node-express image,版本則由 .envNODE_EXPRESS_TAG 變數決定
  • restart :當 container crash 時,會自動重啟

express service 並沒有對 container 外部開放 port,因此 Vue 無法使用 express 提供的 API 服務,必須靠 Nginx 的 reverse proxy 才能使用

.env

VUE_NGINX_TAG=0.0.1
NODE_EXPRESS_TAG=0.0.1

設定 Docker image 版本,這樣的好處是 image 版本更新時,不用去修改 docker-compose.yml ,直接修改 .env 即可

實務上若 docker-compose.yml 內的資料需經常變動,建議獨立到 .env 設定

vue.config.js

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: { '^/api': '' }
      }
    }
  },
};

之前都是以 Docker 考慮,但畢竟最後 production 才會使用 Nginx 包 image,平常開發時一樣使用 Vue CLI 的 Dev Server,此時因為 Dev Server 在 8080 port,而 Node 在 3000 port,直接打 API 會違反 browser 的 same-origin policy,因此必須透過 Dev Server 的 reverse proxy 做 rewrite。

當 Vue 以 /api/hello-world 呼叫 API 時,會由 Dev Server 的 reverse proxy 轉成 http://express:3000/hello-world

需注意 Nginx 與 Dev Server 在 reverse proxy 設定方式不太一樣

App.vue

<template>
  <div>
    <div>{{ msg }}</div>
  </div>
</template>

<script>
import axios from 'axios';

let mounted = function() {
  axios.get('/api/hello-world')
    .then(res => this.msg = res.data);
};

export default {
  name: 'app',
  mounted,
  data: () => ({
    msg: ''
  })
}
</script>

10 行

let mounted = function() {
  axios.get('/api/hello-world')
    .then(res => this.msg = res.data);
};

在 Vue 打 Node API 時,並不是直接對 http://localhost:3000/hello-world 打,而是打自已的 /api/hello-world ,再由 Nginx 或 Dev Server 的 reverse proxy 做轉換。

Node

server 目錄下新增以下檔案:

  • dockerfile
  • app.js
  • Package.json

dockerfile

FROM node:lts-alpine
WORKDIR /usr/src/app
COPY package.json ./package.json
RUN yarn install
COPY app.js .
EXPOSE 3000
CMD [ "node", "app.js" ]

須先建立 dockerfile ,才能產生 Node 的 image。

第 1 行

FROM node:lts-alpine

使用 LTS 版的 node:alpine 為基底建立 image。

Production 建議使用 node:lts-alpine 為 production image,LTS 較為穩定, alpine size 會小很多

第 2 行

WORKDIR /usr/src/app

設定 image 內的 /usr/src/app 為工作目錄。

第 3 行

COPY package.json ./package.json

package.json 複製進 image。

第 4 行

RUN yarn install

根據 image 內的 package.json 執行 yarn install 安裝 Node 所需的 Express。

第 5 行

COPY app.js .

app.js 複製進 image。

第 6 行

EXPOSE 3000

宣告此 image 使用 3000 port,未來 docker-compose.yml 較易整合,一看 dockerfile 就知道使用 3000 port 。

第 7 行

CMD [ "node", "app.js" ]

最後將使用 Node 執行 app.js 啟動 Express。

app.js

let express = require('express');

let app = express();
let port = 3000;

app.get('/hello-world', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`Node listening on port ${port}!`));

Node 的啟動檔,由此啟動 Express。

第 1 行

let express = require('express');

Import express module。

第 3 行

let app = express();

建立 app Express instance。

第 5 行

app.get('/api/hello-world', (req, res) => res.send('Hello World'));

建立 /api/hello-world GET,回傳 Hello World

第 7 行

app.listen(3000, () => console.log('app listening on port 3000!'));

啟動 Express 在 3000 port。

package.json

{
  "name": "node-express",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "^4.17.1"
  }
}

Node 的 package.json ,紀錄 Node 所使用的 dependency 。

第 5 行

"dependencies": {
  "express": "^4.17.1"
}

安裝 express package。

Conclusion

  • Docker 與 Microservice 都只能算技術,至於要如何管理與整合,則有各種方式,本文以 Vue CLI 所建立的 project 為基礎,Node 退化成 Vue 的一個目錄,且以 Yarn Script 管理 Docker 各種動作
  • 在 Microservice 下,Node 是躲在 Docker 內,因此必須使用 Nginx 的 reverse proxy 才能使 Vue 看得到 API,也順便解決 browser 的 same-origin policy 問題
原文  https://oomusou.io/docker/vue/microservice/nginx-node/
正文到此结束
Loading...