使用Docker Compose、Nginx、SSH和Github Actions實現前端自動化部署測試機

前端森林發表於2021-11-09

開篇,我們先來看一下遠古時代的構建部署流程。想必大家對這個都不陌生:

  • 開發將原始碼經過編譯、壓縮打包生成打包檔案
  • 將打包生成的檔案上傳伺服器

顯然這個流程不僅繁瑣,而且效率也不高,開發每次釋出都要耗費很長的時間在部署構建上面。

後面為了解決這個問題,就出現了CI/CD

接下來我們來聊一下什麼是CI/CD?

CI/CDContinuous Intergration/Continuous Deploy 的簡稱,翻譯過來就是持續整合/持續部署CD 也會被解釋為持續交付(Continuous Delivery

再具體一點就是:

  • 持續整合:當程式碼倉庫程式碼發生變更,就會自動對程式碼進行測試和構建,反饋執行結果。
  • 持續交付:持續交付是在持續整合的基礎上,可以將整合後的程式碼依次部署到測試環境、預釋出環境、生產環境中

聊了這麼多,相信很多同學一定會說:

  • 這一般不都是運維搞的嗎?
  • 和業務也不相關啊,瞭解它有什麼用?
  • 全是伺服器相關的東西,dockernginx、雲伺服器啥的,我該怎麼學習呢?

很早之前,我也是這麼想的,感覺與自己的業務也沒啥關係,沒有太大的必要去了解。

但是最近我在搞一個全棧專案(做這個專案是為了突破自己的瓶頸)時,就遇到了這些問題,發現陷入了知識盲區。

沒辦法,只能一頓惡補。

但是當我通過學習這些知識和在專案中實踐這些流程後,我在知識面上得到了很大的擴充套件。對作業系統,對實際的構建部署,甚至對工程化擁有了全新的認識。

這裡也放下前面提到的全棧專案的架構圖吧:

這個大的專案以low code為核心,囊括了編輯器前端編輯器後端C端H5元件庫元件平臺後臺管理系統前端後臺管理系統後臺統計服務自研CLI九大系統。

其中的編輯器前端如何設計實現 H5 營銷頁面搭建系統文章中已經有很詳細的說明。

目前整個專案做了 70%左右,過程中遇到了很多問題,也得到了很大的提升。後續會有一波文章是關於專案中的一個個小點展開的,也都是滿滿的乾貨。

回到本篇文章的主題:使用Docker Compose、Nginx、SSH和Github Actions實現前端自動化部署測試機。本文是以後臺管理系統前端為依託詳細說明了如何藉助DockernginxGithub CI/CD能力自動化釋出一個純前端專案。選這個專案來講解自動化釋出測試機有兩個出發點:

  • 後臺管理系統業務較簡單,可將重心放在自動化部署流程上
  • 純前端專案更適用於大部分前端同學現狀,拿去即用

整體思路

前端程式碼,打包出來的是靜態檔案,可用nginx做服務。思路:

  • 構建一個Docker容器(有nginx
  • dist/目錄拷貝到Docker容器中
  • 啟動nginx服務
  • 宿主機埠,對應到Docker容器埠中,即可訪問

核心程式碼變動:

  • nginx.conf(給Docker容器的nginx使用)
  • Dockerfile
  • docker-compose.yml
⚠️ 本文將採用理論知識和實際相結合的方式,即先講述一下對應知識點,同時會放一下與此知識點相關的專案程式碼或配置檔案。

下面會依次講解Dockerdocker-composesshgithub actions等知識點。

Docker

Docker很早之前,在公眾號的一篇文章誰說前端不需要學習 docker?就有過詳細說明。這裡簡單再闡述下。

docker 可以看成是一個高效能的虛擬機器,主要用於 linux 環境的虛擬化。開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然後釋出到任何流行的 linux 機器上。容器完全使用沙箱機制,相互之間不會有任何介面。

在容器中你可以做任何伺服器可以做的事,例如在有 node 環境的容器中執行 npm run build 打包專案,在有 nginx 環境的容器中部署專案等等。

centos 上安裝 docker

由於這次的雲伺服器是centos的,所以這裡就提一下如何在 centos 上安裝 docker


$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2

$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

$ sudo yum install docker-ce docker-ce-cli containerd.io

$ sudo systemctl start docker

$ sudo docker run hello-world

dockerfile

docker 使用 Dockerfile 作為配置檔案進行映象的構建,簡單看一個 node 應用構建的 dockerfile

FROM node:12.10.0

WORKDIR /usr/app

COPY package*.json ./

RUN npm ci -qy

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

說明一下每個關鍵字對應的含義。

FROM

基於這個 Image 開始

WORKDIR

設定工作目錄

COPY

複製檔案

RUN

新層中執行命令

EXPOSE

宣告容器監聽埠

CMD

容器啟動時執行指令預設值

看下專案中的Dockerfile檔案:

# Dockerfile
FROM nginx

# 將 dist 檔案中的內容複製到 /usr/share/nginx/html/ 這個目錄下面
# 所以,之前必須執行 npm run build 來打包出 dist 目錄,重要!!!
COPY dist/ /usr/share/nginx/html/

# 拷貝 nginx 配置檔案
COPY nginx.conf /etc/nginx/nginx.conf

# 設定時區
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

# 建立 /admin-fe-access.log ,對應到 nginx.conf
CMD touch /admin-fe-access.log && nginx && tail -f /admin-fe-access.log

在這個檔案裡面,我們做了下面幾件事:

1、我們用了 NginxDocker image 作為 base image

2、把打包生成的資料夾dist/的全部內容放進 Nginx Docker 的預設 HTML 資料夾,也就是/usr/share/nginx/html/裡面。

3、把自定義的 Nginx 配置檔案nginx.conf放進 Nginx Docker 的配置資料夾/etc/nginx/nginx.conf中。

4、設定時區。

5、建立 /admin-fe-access.log,啟動nginx並使用tail -f模擬類似pm2的阻塞式程式。

這裡提到了nginx.conf檔案:

#nginx程式數,通常設定成和cpu的數量相等
worker_processes auto;

#全域性錯誤日誌定義型別
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#程式pid檔案
#pid        logs/nginx.pid;

#參考事件模型
events {
    #單個程式最大連線數(最大連線數=連線數+程式數)
    worker_connections  1024;
}

#設定http伺服器
http {
    #副檔名與檔案型別對映表
    include       mime.types;
    #預設檔案型別
    default_type  application/octet-stream;

    #日誌格式設定
    #$remote_addr與 $http_x_forwarded_for用以記錄客戶端的ip地址;
    #$remote_user:用來記錄客戶端使用者名稱稱;
    #$time_local: 用來記錄訪問時間與時區;
    #$request: 用來記錄請求的url與http協議;
    #$status: 用來記錄請求狀態;成功是200,
    #$body_bytes_sent :記錄傳送給客戶端檔案主體內容大小;
    #$http_referer:用來記錄從那個頁面連結訪問過來的;
    #$http_user_agent:記錄客戶瀏覽器的相關資訊;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

    # access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    #長連線超時時間,單位是秒
    keepalive_timeout  65;

    #gzip  on;

    #設定通過nginx上傳檔案的大小
    client_max_body_size   20m;

    #虛擬主機的配置
    server {
        #監聽埠
        listen       80;
        #域名可以有多個,用空格隔開
        server_name  admin-fe;

        #charset koi8-r;

        #定義本虛擬主機的訪問日誌
        access_log  /admin-fe-access.log  main; # 注意,在 Dockerfile 中建立 /admin-fe-access.log

        #入口檔案的設定
        location / {
            root   /usr/share/nginx/html;   #入口檔案的所在目錄
            index  index.html index.htm;    #預設入口檔名稱
            try_files $uri $uri/ /index.html;
        }
        #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   html;
        }
    }
}

核心點就是監聽80埠,定義日誌檔案為admin-fe-access.log,入口檔案根目錄為/usr/share/nginx/html,這些都是與Dockerfile中一一對應的。

說完了Dockerfile及其相關的配置檔案,下面接著來看下docker中幾個核心的概念。

docker 核心概念

docker中有三個非常重要的概念:

  • 映象(image)
  • 容器(container)
  • 倉庫(repository)

一張圖來表明其中的關係:

如果把容器比作輕量的伺服器,那麼映象就是建立它的模版,一個 docker映象可以建立多個容器,它們的關係好比 JavaScript例項的關係。

映象(image)常用命令:

  • 下載映象:docker pull <image-name>:<tag>
  • 檢視所有映象:docker images
  • 刪除映象:docker rmi <image-id>
  • 上傳映象:docker push <username>/<repository>:<tag>
如果docker images出現repository<none>的情況,可以執行docker image prune刪除

容器(container)常用命令

  • 啟動容器:docker run -p xxx:xxx -v=hostPath:containerPath -d --name <container-name> <image-name>

    • -p 埠對映
    • -v 資料卷,檔案對映
    • -d 後臺執行
    • --name 定義容器名稱
  • 檢視所有容器:docker ps(加-a顯示隱藏的容器)
  • 停止容器:docker stop <container-id>
  • 刪除容器:docker rm <container-id>(加-f強制刪除)
  • 檢視容器資訊(如 IP 地址等):docker inspect <container-id>
  • 檢視容器日誌:docker logs <container-id>
  • 進入容器控制檯:docker exec -it <container-id> /bin/sh

映象構建完成後,可以很容易的在當前宿主上執行,但是, 如果需要在其它伺服器上使用這個映象,我們就需要一個集中的儲存、分發映象的服務,Docker Registry 就是這樣的服務。

一個 Docker Registry 中可以包含多個倉庫(Repository);每個倉庫可以包含多個標籤(Tag);每個標籤對應一個映象。所以說:映象倉庫是 Docker 用來集中存放映象檔案的地方,類似於我們之前常用的程式碼倉庫。

docker-compose

docker-compose專案是Docker官方的開源專案,負責實現對Docker容器叢集的快速編排。允許使用者通過一個單獨的docker-compose.yml模板檔案(YAML 格式)來定義一組相關聯的應用容器為一個專案(project)。

使用 compose 的最大優點是你只需在一個檔案中定義自己的應用程式棧(即應用程式需要用到的所有服務),然後把這個 YAML 檔案放在專案的根目錄下,與原始碼一起受版本控制。其他人只需 clone 你的專案原始碼之後就可以快速啟動服務。

通常適用於專案所需執行環境(對應多個docker容器)較多的場景,例如同時依賴於nodejsmysqlmongodbredis等。

這裡放下docker-compose.yml檔案:

version: '3'
services:
  admin-fe:
    build:
      context: .
      dockerfile: Dockerfile
    image: admin-fe # 引用官網 nginx 映象
    container_name: admin-fe
    ports:
      - 8085:80 # 宿主機可以用 127.0.0.1:8085 即可連線容器中的資料庫

基於上文的Dockerfile建立映象,埠對映是8085:80,這裡的8085是宿主機埠,80對應的是nginx暴露的 80 埠

常用命令

  • 構建容器:docker-compose build <service-name>
  • 啟動所有伺服器:docker-compose up -d(後臺啟動)
  • 停止所有服務:docker-compose down
  • 檢視服務:docker-compose ps

ssh 及雲伺服器

首先說下雲伺服器,既然要一鍵部署測試機,那麼肯定要有臺測試機,也就是雲伺服器,這裡我用的是阿里雲CentOS 8.4 64位的作業系統。

有了伺服器,那怎麼登陸呢?

本地登陸雲伺服器的方式一般有兩種,密碼登陸和 ssh 登陸。但是如果採用密碼登陸的話,每次都要輸入密碼,比較麻煩。這裡採用ssh登陸的方式。關於如何免密登入遠端伺服器,可以參考SSH 免密登陸配置

此後每次登陸都可以通過ssh <username>@<IP>的方式直接免密登陸了。

雲伺服器安裝指定包

接著要給雲伺服器安裝基礎包,在CentOS安裝指定包一般用的是yum,這個不同於npm

docker

# Step 1: 解除安裝舊版本
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
# Step 2: 安裝必要的一些系統工具
sudo yum install -y yum-utils
# Step 3: 新增軟體源資訊,使用阿里雲映象
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# Step 4: 安裝 docker-ce
sudo yum install docker-ce docker-ce-cli containerd.io
# Step 5: 開啟 docker服務
sudo systemctl start docker
# Step 6: 執行 hello-world 專案
sudo docker run hello-world

如果你像我一樣,有Hello from Docker!的話那麼Docker就安裝成功了!

docker-compose

通過訪問 https://github.com/docker/compose/releases/latest 得到最新的 docker-compose 版本(例如:1.27.4),然後執行一下命令安裝 docker-compose

# 下載最新版本的 docker-compose 到 /usr/bin 目錄下
curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose

# 給 docker-compose 授權
chmod +x /usr/bin/docker-compose

安裝完,命令列輸入docker-compose version來驗證是否安裝成功:

node

首先確保可以訪問到EPEL庫,通過執行以下命令來安裝:

sudo yum install epel-release

現在可以使用yum命令安裝Node.js了:

sudo yum install nodejs

驗證一下:

nginx

yum 安裝 nginx 非常簡單,輸入一條命令即可:

$ sudo yum -y install nginx   # 安裝 nginx

git

同樣也是使用yum來安裝:

yum install git

最後來看一下github actions,也是串聯起了上面提到的各個點。

github actions

大家知道,持續整合由很多操作組成,比如拉取程式碼、執行測試用例、登入遠端伺服器,釋出到第三方服務等等。GitHub 把這些操作就稱為 actions

我們先來了解一下一些術語

  • workflow(工作流程):持續整合一次執行的過程,就是一個 workflow。
  • job(任務):一個 workflow 由一個或多個 jobs 構成,含義是一次持續整合的執行,可以完成多個任務。
  • step(步驟):每個 job 由多個 step 構成,一步步完成。
  • action(動作):每個 step 可以依次執行一個或多個命令(action)。

workflow 檔案

GitHub Actions 的配置檔案叫做 workflow 檔案,存放在程式碼倉庫的.github/workflows目錄。

workflow 檔案採用 YAML 格式,檔名可以任意取,但是字尾名統一為.yml,比如deploy.yml。一個庫可以有多個 workflow 檔案。GitHub 只要發現.github/workflows目錄裡面有.yml檔案,就會自動執行該檔案。

workflow 檔案的配置欄位非常多,這裡列舉一些基本欄位。

name

name欄位是 workflow 的名稱。

如果省略該欄位,預設為當前 workflow 的檔名。
name: deploy for feature_dev

on

on欄位指定觸發 workflow 的條件,通常是pushpull_request

指定觸發事件時,可以限定分支或標籤。

on:
  push:
    branches:
      - master

上面程式碼指定,只有master分支發生push事件時,才會觸發 workflow

jobs

jobs欄位,表示要執行的一項或多項任務。其中的runs-on欄位指定執行所需要的虛擬機器環境。

runs-on: ubuntu-latest

steps

steps欄位指定每個 Job 的執行步驟,可以包含一個或多個步驟。每個步驟都可以指定以下三個欄位。

  • jobs.<job_id>.steps.name:步驟名稱。
  • jobs.<job_id>.steps.run:該步驟執行的命令或者 action。
  • jobs.<job_id>.steps.env:該步驟所需的環境變數。

下面放一下專案中的.github/workflows/deploy-dev.yml檔案:

name: deploy for feature_dev

on:
  push:
    branches:
      - 'feature_dev'
    paths:
      - '.github/workflows/*'
      - '__test__/**'
      - 'src/**'
      - 'config/*'
      - 'Dockerfile'
      - 'docker-compose.yml'
      - 'nginx.conf'

jobs:
  deploy-dev:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 14
      - name: lint and test # 測試
         run: |
           npm i
           npm run lint
           npm run test:local
      - name: set ssh key # 臨時設定 ssh key
        run: |
          mkdir -p ~/.ssh/
          echo "${{secrets.COSEN_ID_RSA}}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan "106.xx.xx.xx" >> ~/.ssh/known_hosts
      - name: deploy
        run: |
          ssh work@106.xx.xx.xx "
            cd /home/work/choba-lego/admin-fe;
            git remote add origin https://Cosen95:${{secrets.COSEN_TOKEN}}@github.com/Choba-lego/admin-fe.git;
            git checkout feature_dev;
            git config pull.rebase false;
            git pull origin feature_dev;
            git remote remove origin;

            # 構建 prd-dev
            # npm i;
            # npm run build-dev;

            # 啟動 docker
            docker-compose build admin-fe; # 和 docker-compose.yml service 名字一致
            docker-compose up -d;
          "
      - name: delete ssh key
        run: rm -rf ~/.ssh/id_rsa

這裡概述一下:

1️⃣ 整個流程在程式碼pushfeature_dev分支時觸發。

2️⃣ 只有一個job,執行在虛擬機器環境ubuntu-latest

3️⃣ 第一步使用的是最基礎的action,即actions/checkout@v2,它的作用就是讓我們的workflow可以訪問到我們的repo

4️⃣ 第二步是在執行工作流的機器中安裝node,這裡使用的actionactions/setup-node@v1

5️⃣ 第三步是執行linttest

6️⃣ 第四步是臨時設定 ssh key,這也是為了下一步登入伺服器做準備。

7️⃣ 第五步是部署,這裡面先是ssh登入伺服器,拉取了最新分支程式碼,然後安裝依賴、打包,最後啟動docker,生成映象。到這裡測試機上就有了Docker服務。

8️⃣ 最後一步是刪除ssh key

最後來github看一下完整的流程:

其中deploy階段算是核心了:

總結

洋洋灑灑寫了這麼多,也不知道你看明白了不 ?

如果有任何問題,歡迎評論區留言,看到後會第一時間解答 ?

後續會有很多關於這個專案的文章,也請大家多多關注~

相關文章