開篇,我們先來看一下遠古時代的構建部署流程。想必大家對這個都不陌生:
- 開發將原始碼經過編譯、壓縮打包生成打包檔案
- 將打包生成的檔案上傳伺服器
顯然這個流程不僅繁瑣,而且效率也不高,開發每次釋出都要耗費很長的時間在部署構建上面。
後面為了解決這個問題,就出現了CI/CD
。
接下來我們來聊一下什麼是CI/CD?
CI/CD
是 Continuous Intergration/Continuous Deploy
的簡稱,翻譯過來就是持續整合/持續部署
。CD
也會被解釋為持續交付(Continuous Delivery
)
再具體一點就是:
持續整合
:當程式碼倉庫程式碼發生變更,就會自動對程式碼進行測試和構建,反饋執行結果。持續交付
:持續交付是在持續整合的基礎上,可以將整合後的程式碼依次部署到測試環境、預釋出環境、生產環境中
聊了這麼多,相信很多同學一定會說:
- 這一般不都是運維搞的嗎?
- 和業務也不相關啊,瞭解它有什麼用?
- 全是伺服器相關的東西,
docker
、nginx
、雲伺服器啥的,我該怎麼學習呢?
很早之前,我也是這麼想的,感覺與自己的業務也沒啥關係,沒有太大的必要去了解。
但是最近我在搞一個全棧專案
(做這個專案是為了突破自己的瓶頸)時,就遇到了這些問題,發現陷入了知識盲區。
沒辦法,只能一頓惡補。
但是當我通過學習這些知識和在專案中實踐這些流程後,我在知識面上得到了很大的擴充套件。對作業系統,對實際的構建部署,甚至對工程化擁有了全新的認識。
這裡也放下前面提到的全棧專案
的架構圖吧:
這個大的專案以low code
為核心,囊括了編輯器前端
、編輯器後端
、C端H5
、元件庫
、元件平臺
、後臺管理系統前端
、後臺管理系統後臺
、統計服務
、自研CLI
九大系統。
其中的編輯器前端
在如何設計實現 H5 營銷頁面搭建系統文章中已經有很詳細的說明。
目前整個專案做了 70%左右,過程中遇到了很多問題,也得到了很大的提升。後續會有一波文章是關於專案中的一個個小點展開的,也都是滿滿的乾貨。
回到本篇文章的主題:使用Docker Compose、Nginx、SSH和Github Actions實現前端自動化部署測試機
。本文是以後臺管理系統前端
為依託詳細說明了如何藉助Docker
、nginx
、Github CI/CD
能力自動化釋出一個純前端專案
。選這個專案來講解自動化釋出測試機有兩個出發點:
- 後臺管理系統業務較簡單,可將重心放在自動化部署流程上
- 純前端專案更適用於大部分前端同學現狀,拿去即用
整體思路
前端程式碼,打包出來的是靜態檔案,可用nginx
做服務。思路:
- 構建一個
Docker
容器(有nginx
) - 將
dist/
目錄拷貝到Docker
容器中 - 啟動
nginx
服務 - 宿主機埠,對應到
Docker
容器埠中,即可訪問
核心程式碼變動:
nginx.conf
(給Docker
容器的nginx
使用)Dockerfile
docker-compose.yml
⚠️ 本文將採用理論知識和實際相結合的方式,即先講述一下對應知識點,同時會放一下與此知識點相關的專案程式碼或配置檔案。
下面會依次講解Docker
、docker-compose
、ssh
、github 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、我們用了 Nginx
的 Docker image
作為 base image
。
2、把打包生成的資料夾dist/
的全部內容放進 Nginx Docke
r 的預設 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
容器)較多的場景,例如同時依賴於nodejs
、mysql
、mongodb
、redis
等。
這裡放下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
的條件,通常是push
、pull_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️⃣ 整個流程在程式碼push
到feature_dev
分支時觸發。
2️⃣ 只有一個job
,執行在虛擬機器環境ubuntu-latest
。
3️⃣ 第一步使用的是最基礎的action
,即actions/checkout@v2
,它的作用就是讓我們的workflow
可以訪問到我們的repo
。
4️⃣ 第二步是在執行工作流的機器中安裝node
,這裡使用的action
是actions/setup-node@v1
。
5️⃣ 第三步是執行lint
和test
。
6️⃣ 第四步是臨時設定 ssh key
,這也是為了下一步登入伺服器做準備。
7️⃣ 第五步是部署,這裡面先是ssh
登入伺服器,拉取了最新分支程式碼,然後安裝依賴、打包,最後啟動docker
,生成映象。到這裡測試機上就有了Docker
服務。
8️⃣ 最後一步是刪除ssh key
。
最後來github
看一下完整的流程:
其中deploy
階段算是核心了:
總結
洋洋灑灑寫了這麼多,也不知道你看明白了不 ?
如果有任何問題,歡迎評論區留言,看到後會第一時間解答 ?
後續會有很多關於這個專案的文章,也請大家多多關注~