[手把手系列之]Docker 部署 vue 專案

快狗叫車前端團隊發表於2019-05-05

Docker 部署 vue 專案

1.寫在前面:

Docker 作為輕量級虛擬化技術,擁有持續整合、版本控制、可移植性、隔離性和安全性等優勢。本文使用Docker來部署一個vue的前端應用,並儘可能詳盡的介紹了實現思路和具體步驟,以方便有類似需要的同學參考。

Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,該容器包含了應用程式的程式碼、執行環境、依賴庫、配置檔案等必需的資源,通過容器就可以實現方便快速並且與平臺解耦的自動化部署方式,無論你部署時的環境如何,容器中的應用程式都會執行在同一種環境下。(更多詳情請移步docker官網檢視docker

預設已經安裝了 docker,@vue/cli

相關版本:

  • Docker version 18.09.2, build 6247962

  • vue cli --version 3.3.0

  • macOS Mojave Verison 10.14.1

執行環境為macOS,如果與閱讀者作業系統之間存在差異,請自行調整

相關映象:

  • nginx:latest
  • node:latest

2.具體實現:

  1. 用 vue cli 建立一個vue專案,修改一下建立出來的專案,在頁面上寫一個前端介面請求,構建一版線上資源 ,基於nginx docker映象構建成一個前端工程映象,然後基於這個前端工程映象,啟動一個容器 vuenginxcontainer。
  2. 啟動一個基於 node 映象的容器 nodewebserver,提供後端介面。
  3. 修改 vuenginxcontainer 的 nginx 配置,使前端頁面的介面請求轉發到 nodewebserver 上。
  4. 稍作優化和改進。

3 建立 vue 應用

3.1 vue cli 建立一個vue專案

vue create vueclidemo
執行命令

yarn serve / npm run serve
複製程式碼

yarn serve
訪問 http://localhost:8081

vue app main page

3.2 改寫

稍微改寫一下頁面,在App.vue中 傳入HelloWorld 元件中的 msg 改為Hello Docker ; created 生命週期中加入一個介面請求

import axios from 'axios';

……

axios.get('/api/json', {
  params: {}
}).then(
  res => {
    console.log(res);
  }
).catch(
  error => {
      console.log(error);
  }
)

……

複製程式碼

這時候會在頁面控制檯看到一個報錯資訊:

console 404 error
/api/json 介面 404,當然此時這個介面還不存在,暫時寫到這裡,一會再調這個介面。

3.3 構建vue專案

執行命令

yarn build / npm run build
複製程式碼

vue build output
此時工程根目錄下多出一個dist資料夾

out put dist dir and files

如果將該dist目錄整個傳到伺服器上,部署成靜態資源站點就能直接訪問到該專案。

接下來就來構建一個這樣的靜態資源站點。

4 構建vue應用映象

nginx 是一個高效能的HTTP和反向代理伺服器,此處我們選用 nginx 映象作為基礎來構建我們的vue應用映象。

4.1 獲取 nginx 映象

docker pull nginx
複製程式碼
  • docker映象(Image)一個特殊的檔案系統。Docker映象是一個特殊的檔案系統,除了提供容器執行時所需的程式、庫、資源、配置等檔案外,還包含了一些為執行時準備的一些配置引數(如匿名卷、環境變數、使用者等)。 映象不包含任何動態資料,其內容在構建之後也不會被改變。
  • docker 映象相關操作有: 搜尋映象docker search [REPOSITORY[:TAG]]、拉取映象docker pull [REPOSITORY[:TAG]] 、檢視映象列表docker image ls、刪除映象:docker image rm [REPOSITORY[:TAG]] / docker rmi [REPOSITORY[:TAG]] 等等。
  • docker 映象名稱由REPOSITORY和TAG組成 [REPOSITORY[:TAG]],TAG預設為latest

4.2 建立 nginx config配置檔案

在專案根目錄下建立nginx資料夾,該資料夾下新建檔案default.conf

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    access_log  /var/log/nginx/host.access.log  main;
    error_log  /var/log/nginx/error.log  error;

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

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
複製程式碼

該配置檔案定義了首頁的指向為 /usr/share/nginx/html/index.html, 所以我們可以一會把構建出來的index.html檔案和相關的靜態資源放到/usr/share/nginx/html目錄下。

4.3 建立 Dockerfile 檔案

FROM nginx
COPY dist/ /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
複製程式碼
  • 自定義構建映象的時候基於Dockerfile來構建。
  • FROM nginx 命令的意思該映象是基於 nginx:latest 映象而構建的。
  • COPY dist/ /usr/share/nginx/html/ 命令的意思是將專案根目錄下dist資料夾下的所有檔案複製到映象中 /usr/share/nginx/html/ 目錄下。
  • COPY nginx/default.conf /etc/nginx/conf.d/default.conf 命令的意思是將nginx目錄下的default.conf 複製到 etc/nginx/conf.d/default.conf,用本地的 default.conf 配置來替換nginx映象裡的預設配置。

4.4 基於該Dockerfile構建vue應用映象

執行命令(注意不要少了最後的 “.” )

docker build -t vuenginxcontainer .
複製程式碼

-t 是給映象命名 . 是基於當前目錄的Dockerfile來構建映象

docker build vuenginxcontainer image
檢視本地映象,執行命令

docker image ls | grep vuenginxcontainer
複製程式碼

docker grep image vuenginxcontainer
到此時我們的 vue 應用映象 vuenginxcontainer 已經成功建立。接下來,我們基於該映象啟動一個docker容器。

4.5 啟動 vue app 容器

Docker 容器Container: 映象執行時的實體。映象(Image)和容器(Container)的關係,就像是物件導向程式設計中的類和例項一樣,映象是靜態的定義,容器是映象執行時的實體。容器可以被建立、啟動、停止、刪除、暫停等 。

基於 vuenginxcontainer 映象啟動容器,執行命令:

docker run \
-p 3000:80 \
-d --name vueApp \
vuenginxcontainer
複製程式碼
  • docker run 基於映象啟動一個容器
  • -p 3000:80 埠對映,將宿主的3000埠對映到容器的80埠
  • -d 後臺方式執行
  • --name 容器名 檢視 docker 程式
docker ps
複製程式碼

vueApp container
可以發現名為 vueApp的容器已經執行起來。此時訪問 http://localhost:3000 應該就能訪問到該vue應用:
vueApp index
目前為止,已經通過docker容器部署了一個靜態資源服務,可以訪問到靜態資原始檔。還有 /api/json這個介面資料沒有,接下來我們來解決一下這個問題。

5 介面服務

再部署一個 node 的容器來提供介面服務

5.1 express 服務

用 node web 框架 express 來寫一個服務,註冊一個返回json資料格式的路由 server.js:

'use strict';

const express = require('express');

const PORT = 8080;
const HOST = '0.0.0.0';

const app = express();
app.get('/', (req, res) => {
    res.send('Hello world\n');
});

app.get('/json', (req, res) => {
    res.json({
        code: 0,
        data :'This is message from node container'
    })
});

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
複製程式碼

執行該 express 應用需要 node 環境,我們基於 node 映象來構建一個新映象

5.2 獲取 node 映象

docker pull node
複製程式碼

5.3 編寫 Dockerfile 將 express 應用 docker

FROM node

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8080
CMD [ "npm", "start" ]
複製程式碼

構建映象的時候 node_modules 的依賴直接通過 RUN npm install 來安裝,專案中建立一個 .dockerignore檔案來忽略一些直接跳過的檔案:

node_modules
npm-debug.log
複製程式碼

5.4 構建 nodewebserver 映象

執行構建命令:

  docker build -t nodewebserver .
複製程式碼

docker build nodewebserver

5.5 啟動 nodeserver 容器

基於剛剛構建的 nodewebserver 映象 啟動一個名為 nodeserver 的容器來提供介面服務8080埠,並對映宿主的5000埠

docker run \
-p 5000:8080 \
-d --name nodeserver \
nodewebserver
複製程式碼

檢視當前docker程式

docker ps
複製程式碼

[手把手系列之]Docker 部署 vue 專案
可以發現 nodeserver 的容器也正常的執行起來。訪問以下 http://localhost:5000/json 能訪問到前面寫的json資料
api json
到目前為止,後端介面服務也正常啟動了。只需最後把頁面請求的介面轉發到後端介面服務就能調通介面。

6. 跨域轉發

想要將 vueApp 容器 上的請求轉發到 nodeserver 容器上。首先需要知道 nodeserver 容器的ip地址和埠,目前已知 nodeserver 容器內部服務監聽在 8080 埠,還需要知道ip即可。

6.1 檢視 nodeserver 容器的 ip 地址:

檢視容器內部 ip 有多種方式,這裡提供兩種:

  • 進入容器內部檢視
docker exect -it 02277acc3efc bash
複製程式碼
cat /etc/hosts
複製程式碼

nodeserver ip

  • docker inspect [ containerId ] 直接檢視容器資訊:
docker inspect 02277acc3efc
複製程式碼

在其中找到 Networks 相關配置資訊:

nodeserver networks
記錄下node服務容器對應的ip,一會兒配置nginx轉發的時候會用到。

6.2 修改 nginx 配置

  • Nginx 配置 location 指向 node 服務 default.conf (前端想要了解的Nginx,關於Nginx的配置已經 location 的具體寫法可以參考(一文弄懂Nginx的location匹配))
  • 新增一條重寫規則,將 /api/{path} 轉到目標服務的 /{path} 介面上。 在前面的nginx/default.conf檔案中加入:
location /api/ {
  rewrite  /api/(.*)  /$1  break;
  proxy_pass http://172.17.0.2:8080;
}
複製程式碼

修改完了之後意識到一個問題:vueApp 容器是基於 vuenginxcontainer 這個映象執行的,而在一開始構建映象的時候是將 nginx配置 default.conf 直接構建進去了。因此如果需要修改 default.conf 還得再重新構建一個新的映象,再基於新映象來執行新的容器。

7. 改進

能不能每次修改配置檔案後直接重啟容器就能讓新配置生效,答案當然是有。

在構建映象的時候 不把 Nginx 配置複製到映象中,而是直接掛載到宿主機上,每次修改配置後,直接重啟容器即可。

7.1 修改 Dockerfile 檔案

把 vueclidemo 專案下的 Dockerfile 修改一下

FROM nginx
COPY dist/  /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
複製程式碼

COPY nginx/default.conf /etc/nginx/conf.d/default.conf命令刪除,nginx配置都通過掛載命令掛載在宿主機上。再看 COPY dist/ /usr/share/nginx/html/ 命令,如果每次構建的專案dist/下的內容變動都需要重新走一遍構建新映象再啟動新容器的操作,因此這條命令也可以刪除,使用掛載的方式來啟動容器。

7.2 重新執行vue應用容器

直接基於nginx映象來啟動容器 vuenginxnew ,執行命令:

docker run \
-p 3000:80 \
-d --name vuenginxnew \
--mount type=bind,source=$HOME/SelfWork/docker/vueclidemo/nginx,target=/etc/nginx/conf.d \
--mount type=bind,source=$HOME/SelfWork/docker/vueclidemo/dist,target=/usr/share/nginx/html \
nginx
複製程式碼
  • --mount type=bind,source={sourceDir},target={targetDir} 將宿主機的sourceDir 掛載到容器的 targetDir 目錄上。
  • 此處執行的命令較長,如果每次重新輸入難免麻煩,我們可以將完整的命令儲存到一個shell檔案 vueapp.sh 中,然後直接執行 sh vueapp.sh

這樣就能每次修改了nginx配置或者 重新構建了vue應用的時候,只需重啟容器就能立馬生效。 此時我們再訪問 http://localhost:3000/api/json 能看到介面能正常返回,說明轉發生效了。

[手把手系列之]Docker 部署 vue 專案
至此介面服務的轉發也調通了。

7.3 配置負載均衡

後端服務一般都是雙機或者多機以確保服務的穩定性。我們可以再啟動一個後端服務容器,並修改nginx的配置 來優化資源利用率,最大化吞吐量,減少延遲,確保容錯配置。

基於前面 4.5 節的類似操作,新啟動一個容器,並基於 5.1 節類似的操作,檢視到 新容器的 IP (172.17.0.3)

修改一下 nginx/default.conf(新增 upstream ,修改 location /api/ 中的 proxy_pass):


  upstream backend {
      server 172.17.0.2:8080;
      server 172.17.0.3:8080;
  }

  ……

  location /api/ {
      rewrite  /api/(.*)  /$1  break;
      proxy_pass backend;
  }

複製程式碼

8. 寫在後面

不習慣命令列的同學可以選用 Kitematic 來管理docker容器的狀態、資料目錄和網路。所有對容量的操作都可以視覺化的操作,這裡就不做過多介紹了,有興趣的同學可以自行體驗下。

docker kitematic

9 總結

docker提供了非常強大的自動化部署方式與靈活性,對多個應用程式之間做到了解耦,提供了開發上的敏捷性、可控性以及可移植性。本文以vue專案為例實現一個前後分離專案使用docker部署的完整步驟,希望能給想要擁抱 docker 的同學帶來一點幫助。

10 Tail by hand

快狗叫車前端團隊專注前端技術分享,定期推送高質量文章,歡迎關注點贊。

參考資源

docker 官網

nginx 官網

docker 從入門到實踐

Kitematic user guide

前端想要了解的Nginx

一文弄懂Nginx的location匹配

相關文章