使用Docker Compose、Nginx、SSH和Github Actions實現(xiàn)前端自動化部署測試機
開篇,我們先來看一下遠古時代的構(gòu)建部署流程。想必大家對這個都不陌生:
開發(fā)將源碼經(jīng)過編譯、壓縮打包生成打包文件 將打包生成的文件上傳服務器
顯然這個流程不僅繁瑣,而且效率也不高,開發(fā)每次發(fā)布都要耗費很長的時間在部署構(gòu)建上面。
后面為了解決這個問題,就出現(xiàn)了CI/CD。
接下來我們來聊一下什么是CI/CD?
CI/CD 是 Continuous Intergration/Continuous Deploy 的簡稱,翻譯過來就是持續(xù)集成/持續(xù)部署。CD 也會被解釋為持續(xù)交付(Continuous Delivery)
再具體一點就是:
持續(xù)集成:當代碼倉庫代碼發(fā)生變更,就會自動對代碼進行測試和構(gòu)建,反饋運行結(jié)果。持續(xù)交付:持續(xù)交付是在持續(xù)集成的基礎(chǔ)上,可以將集成后的代碼依次部署到測試環(huán)境、預發(fā)布環(huán)境、生產(chǎn)環(huán)境中
聊了這么多,相信很多同學一定會說:
這一般不都是運維搞的嗎? 和業(yè)務也不相關(guān)啊,了解它有什么用? 全是服務器相關(guān)的東西, docker、nginx、云服務器啥的,我該怎么學習呢?
很早之前,我也是這么想的,感覺與自己的業(yè)務也沒啥關(guān)系,沒有太大的必要去了解。
但是最近我在搞一個全棧項目(做這個項目是為了突破自己的瓶頸)時,就遇到了這些問題,發(fā)現(xiàn)陷入了知識盲區(qū)。
沒辦法,只能一頓惡補。
但是當我通過學習這些知識和在項目中實踐這些流程后,我在知識面上得到了很大的擴展。對操作系統(tǒng),對實際的構(gòu)建部署,甚至對工程化擁有了全新的認識。
這里也放下前面提到的全棧項目的架構(gòu)圖吧:
這個大的項目以
low code為核心,囊括了編輯器前端、編輯器后端、C端H5、組件庫、組件平臺、后臺管理系統(tǒng)前端、后臺管理系統(tǒng)后臺、統(tǒng)計服務、自研CLI九大系統(tǒng)。
其中的編輯器前端在如何設(shè)計實現(xiàn) H5 營銷頁面搭建系統(tǒng)文章中已經(jīng)有很詳細的說明。
目前整個項目做了 70%左右,過程中遇到了很多問題,也得到了很大的提升。后續(xù)會有一波文章是關(guān)于項目中的一個個小點展開的,也都是滿滿的干貨。
回到本篇文章的主題:使用Docker Compose、Nginx、SSH和Github Actions實現(xiàn)前端自動化部署測試機。本文是以后臺管理系統(tǒng)前端為依托詳細說明了如何借助Docker、nginx、Github CI/CD能力自動化發(fā)布一個純前端項目。選這個項目來講解自動化發(fā)布測試機有兩個出發(fā)點:
后臺管理系統(tǒng)業(yè)務較簡單,可將重心放在自動化部署流程上 純前端項目更適用于大部分前端同學現(xiàn)狀,拿去即用
整體思路

前端代碼,打包出來的是靜態(tài)文件,可用nginx做服務。思路:
構(gòu)建一個 Docker容器(有nginx)將 dist/目錄拷貝到Docker容器中啟動 nginx服務宿主機端口,對應到 Docker容器端口中,即可訪問
核心代碼變動:
nginx.conf(給Docker容器的nginx使用)Dockerfiledocker-compose.yml
?? 本文將采用理論知識和實際相結(jié)合的方式,即先講述一下對應知識點,同時會放一下與此知識點相關(guān)的項目代碼或配置文件。
下面會依次講解Docker、docker-compose、ssh、github actions等知識點。
Docker

Docker很早之前,在公眾號的一篇文章誰說前端不需要學習 docker?就有過詳細說明。這里簡單再闡述下。
docker 可以看成是一個高性能的虛擬機,主要用于 linux 環(huán)境的虛擬化。開發(fā)者可以打包他們的應用以及依賴包到一個可移植的容器中,然后發(fā)布到任何流行的 linux 機器上。容器完全使用沙箱機制,相互之間不會有任何接口。
在容器中你可以做任何服務器可以做的事,例如在有 node 環(huán)境的容器中運行 npm run build 打包項目,在有 nginx 環(huán)境的容器中部署項目等等。
在 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 作為配置文件進行鏡像的構(gòu)建,簡單看一個 node 應用構(gòu)建的 dockerfile:
FROM?node:12.10.0
WORKDIR?/usr/app
COPY?package*.json?./
RUN?npm?ci?-qy
COPY?.?.
EXPOSE?3000
CMD?["npm",?"start"]
說明一下每個關(guān)鍵字對應的含義。
FROM
基于這個 Image 開始
WORKDIR
設(shè)置工作目錄
COPY
復制文件
RUN
新層中執(zhí)行命令
EXPOSE
聲明容器監(jiān)聽端口
CMD
容器啟動時執(zhí)行指令默認值
看下項目中的Dockerfile文件:
#?Dockerfile
FROM?nginx
#?將?dist?文件中的內(nèi)容復制到?/usr/share/nginx/html/?這個目錄下面
#?所以,之前必須執(zhí)行 npm run build 來打包出 dist 目錄,重要!!!
COPY?dist/?/usr/share/nginx/html/
#?拷貝?nginx?配置文件
COPY?nginx.conf?/etc/nginx/nginx.conf
#?設(shè)置時區(qū)
RUN?ln?-sf?/usr/share/zoneinfo/Asia/Shanghai?/etc/localtime?&&?echo?'Asia/Shanghai'?>/etc/timezone
#?創(chuàng)建?/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/的全部內(nèi)容放進 Nginx Docker 的默認 HTML 文件夾,也就是/usr/share/nginx/html/里面。
3、把自定義的 Nginx 配置文件nginx.conf放進 Nginx Docker 的配置文件夾/etc/nginx/nginx.conf中。
4、設(shè)置時區(qū)。
5、創(chuàng)建 /admin-fe-access.log,啟動nginx并使用tail -f模擬類似pm2的阻塞式進程。
這里提到了nginx.conf文件:
#nginx進程數(shù),通常設(shè)置成和cpu的數(shù)量相等
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?{
????#單個進程最大連接數(shù)(最大連接數(shù)=連接數(shù)+進程數(shù))
????worker_connections??1024;
}
#設(shè)定http服務器
http?{
????#文件擴展名與文件類型映射表
????include???????mime.types;
????#默認文件類型
????default_type??application/octet-stream;
????#日志格式設(shè)定
????#$remote_addr與?$http_x_forwarded_for用以記錄客戶端的ip地址;
????#$remote_user:用來記錄客戶端用戶名稱;
????#$time_local:?用來記錄訪問時間與時區(qū);
????#$request:?用來記錄請求的url與http協(xié)議;
????#$status:?用來記錄請求狀態(tài);成功是200,
????#$body_bytes_sent :記錄發(fā)送給客戶端文件主體內(nèi)容大小;
????#$http_referer:用來記錄從那個頁面鏈接訪問過來的;
????#$http_user_agent:記錄客戶瀏覽器的相關(guān)信息;
????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;
????#設(shè)定通過nginx上傳文件的大小
????client_max_body_size???20m;
????#虛擬主機的配置
????server?{
????????#監(jiān)聽端口
????????listen???????80;
????????#域名可以有多個,用空格隔開
????????server_name??admin-fe;
????????#charset?koi8-r;
????????#定義本虛擬主機的訪問日志
????????access_log??/admin-fe-access.log??main;?#?注意,在?Dockerfile?中創(chuàng)建?/admin-fe-access.log
????????#入口文件的設(shè)置
????????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;
????????}
????}
}
核心點就是監(jiān)聽80端口,定義日志文件為admin-fe-access.log,入口文件根目錄為/usr/share/nginx/html,這些都是與Dockerfile中一一對應的。
說完了Dockerfile及其相關(guān)的配置文件,下面接著來看下docker中幾個核心的概念。
docker 核心概念
docker中有三個非常重要的概念:
鏡像(image) 容器(container) 倉庫(repository)
一張圖來表明其中的關(guān)系:
如果把容器比作輕量的服務器,那么鏡像就是創(chuàng)建它的模版,一個 docker鏡像可以創(chuàng)建多個容器,它們的關(guān)系好比 JavaScript 中類和實例的關(guān)系。
鏡像(image)常用命令:
下載鏡像: docker pull: 查看所有鏡像: docker images刪除鏡像: docker rmi上傳鏡像: docker push/ :
如果
docker images出現(xiàn)repository是的情況,可以運行docker image prune刪除
容器(container)常用命令
啟動容器: docker run -p xxx:xxx -v=hostPath:containerPath -d --name-p 端口映射 -v 數(shù)據(jù)卷,文件映射 -d 后臺運行 --name 定義容器名稱 查看所有容器: docker ps(加-a顯示隱藏的容器)停止容器: docker stop刪除容器: docker rm(加-f強制刪除)查看容器信息(如 IP 地址等): docker inspect查看容器日志: docker logs進入容器控制臺: docker exec -it/bin/sh
鏡像構(gòu)建完成后,可以很容易的在當前宿主上運行,但是, 如果需要在其它服務器上使用這個鏡像,我們就需要一個集中的存儲、分發(fā)鏡像的服務,Docker Registry 就是這樣的服務。
一個 Docker Registry 中可以包含多個倉庫(Repository);每個倉庫可以包含多個標簽(Tag);每個標簽對應一個鏡像。所以說:鏡像倉庫是 Docker 用來集中存放鏡像文件的地方,類似于我們之前常用的代碼倉庫。
docker-compose

docker-compose項目是Docker官方的開源項目,負責實現(xiàn)對Docker容器集群的快速編排。允許用戶通過一個單獨的docker-compose.yml模板文件(YAML 格式)來定義一組相關(guān)聯(lián)的應用容器為一個項目(project)。
使用 compose 的最大優(yōu)點是你只需在一個文件中定義自己的應用程序棧(即應用程序需要用到的所有服務),然后把這個 YAML 文件放在項目的根目錄下,與源碼一起受版本控制。其他人只需 clone 你的項目源碼之后就可以快速啟動服務。
通常適用于項目所需運行環(huán)境(對應多個docker容器)較多的場景,例如同時依賴于nodejs、mysql、mongodb、redis等。
這里放下docker-compose.yml文件:
version:?'3'
services:
??admin-fe:
????build:
??????context:?.
??????dockerfile:?Dockerfile
????image:?admin-fe?#?引用官網(wǎng)?nginx?鏡像
????container_name:?admin-fe
????ports:
??????-?8085:80?#?宿主機可以用?127.0.0.1:8085?即可連接容器中的數(shù)據(jù)庫
基于上文的Dockerfile創(chuàng)建鏡像,端口映射是8085:80,這里的8085是宿主機端口,80對應的是nginx暴露的 80 端口
常用命令
構(gòu)建容器: docker-compose build啟動所有服務器: docker-compose up -d(后臺啟動)停止所有服務: docker-compose down查看服務: docker-compose ps
ssh 及云服務器

首先說下云服務器,既然要一鍵部署測試機,那么肯定要有臺測試機,也就是云服務器,這里我用的是阿里云CentOS 8.4 64位的操作系統(tǒng)。
有了服務器,那怎么登陸呢?
本地登陸云服務器的方式一般有兩種,密碼登陸和 ssh 登陸。但是如果采用密碼登陸的話,每次都要輸入密碼,比較麻煩。這里采用ssh登陸的方式。關(guān)于如何免密登錄遠程服務器,可以參考SSH 免密登陸配置[1]
此后每次登陸都可以通過ssh 的方式直接免密登陸了。
云服務器安裝指定包
接著要給云服務器安裝基礎(chǔ)包,在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:?安裝必要的一些系統(tǒng)工具
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),然后執(zhí)行一下命令安裝 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?授權(quán)
chmod?+x?/usr/bin/docker-compose
安裝完,命令行輸入docker-compose version來驗證是否安裝成功:
node
首先確保可以訪問到EPEL庫,通過運行以下命令來安裝:
sudo?yum?install?epel-release
現(xiàn)在可以使用yum命令安裝Node.js了:
sudo?yum?install?nodejs
驗證一下:
nginx
yum 安裝 nginx 非常簡單,輸入一條命令即可:
$?sudo?yum?-y?install?nginx???#?安裝?nginx
git
同樣也是使用yum來安裝:
yum?install?git
最后來看一下github actions,也是串聯(lián)起了上面提到的各個點。
github actions

大家知道,持續(xù)集成由很多操作組成,比如拉取代碼、執(zhí)行測試用例、登錄遠程服務器,發(fā)布到第三方服務等等。GitHub 把這些操作就稱為 actions。
我們先來了解一下一些術(shù)語:
workflow(工作流程):持續(xù)集成一次運行的過程,就是一個 workflow。job(任務):一個 workflow 由一個或多個 jobs 構(gòu)成,含義是一次持續(xù)集成的運行,可以完成多個任務。step(步驟):每個 job 由多個 step 構(gòu)成,一步步完成。action(動作):每個 step 可以依次執(zhí)行一個或多個命令(action)。
workflow 文件
GitHub Actions 的配置文件叫做 workflow 文件,存放在代碼倉庫的.github/workflows目錄。
workflow文件采用YAML格式,文件名可以任意取,但是后綴名統(tǒng)一為.yml,比如deploy.yml。一個庫可以有多個workflow文件。GitHub只要發(fā)現(xiàn).github/workflows目錄里面有.yml文件,就會自動運行該文件。
workflow 文件的配置字段非常多,這里列舉一些基本字段。
name
name字段是 workflow 的名稱。
如果省略該字段,默認為當前
workflow的文件名。
name:?deploy?for?feature_dev
on
on字段指定觸發(fā) workflow 的條件,通常是push、pull_request。
指定觸發(fā)事件時,可以限定分支或標簽。
on:
??push:
????branches:
??????-?master
上面代碼指定,只有master分支發(fā)生push事件時,才會觸發(fā) workflow。
jobs
jobs字段,表示要執(zhí)行的一項或多項任務。其中的runs-on字段指定運行所需要的虛擬機環(huán)境。
runs-on:?ubuntu-latest
steps
steps字段指定每個 Job 的運行步驟,可以包含一個或多個步驟。每個步驟都可以指定以下三個字段。
jobs.:步驟名稱。.steps.name jobs.:該步驟運行的命令或者 action。.steps.run jobs.:該步驟所需的環(huán)境變量。.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?#?臨時設(shè)置?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:?|
[email protected]?"
????????????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;
????????????#?構(gòu)建?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分支時觸發(fā)。
2?? 只有一個job,運行在虛擬機環(huán)境ubuntu-latest。
3?? 第一步使用的是最基礎(chǔ)的action,即actions/checkout@v2,它的作用就是讓我們的workflow可以訪問到我們的repo。
4?? 第二步是在執(zhí)行工作流的機器中安裝node,這里使用的action是actions/setup-node@v1。
5?? 第三步是執(zhí)行lint和test。
6?? 第四步是臨時設(shè)置 ssh key,這也是為了下一步登錄服務器做準備。
7?? 第五步是部署,這里面先是ssh登錄服務器,拉取了最新分支代碼,然后安裝依賴、打包,最后啟動docker,生成鏡像。到這里測試機上就有了Docker服務。
8?? 最后一步是刪除ssh key。
最后來github看一下完整的流程:
其中deploy階段算是核心了:
總結(jié)
洋洋灑灑寫了這么多,也不知道你看明白了不 ??
如果有任何問題,歡迎評論區(qū)留言,看到后會第一時間解答 ??
后續(xù)會有很多關(guān)于這個項目的文章,也請大家多多關(guān)注~
參考資料
SSH 免密登陸配置: https://segmentfault.com/a/1190000021000360
