【W(wǎng)eb技術(shù)】743- 手把手教你搭建一個(gè)灰度發(fā)布環(huán)境

https://segmentfault.com/a/1190000022612488
引言
灰度發(fā)布,又稱金絲雀發(fā)布。
金絲雀發(fā)布這一術(shù)語源于煤礦工人把籠養(yǎng)的金絲雀帶入礦井的傳統(tǒng)。礦工通過金絲雀來了解礦井中一氧化碳的濃度,如果一氧化碳的濃度過高,金絲雀就會(huì)中毒,從而使礦工知道應(yīng)該立刻撤離。——《DevOps實(shí)踐指南》
對(duì)應(yīng)到軟件開中,則是指在發(fā)布新的產(chǎn)品特性時(shí)通過少量的用戶試點(diǎn)確認(rèn)新特性沒有問題,確保無誤后推廣到更大的用戶使用群體。
集成灰度發(fā)布的流水線在DevOps中是一個(gè)非常重要的工具和高效的實(shí)踐,然而筆者在入職以前對(duì)流水線和灰度發(fā)布知之甚少。在了解一個(gè)新東西時(shí),先從邏輯上打通所有的關(guān)鍵環(huán)節(jié),然后再完成一個(gè)最簡(jiǎn)單的Demo,對(duì)于我們來說是比較有意思的學(xué)習(xí)路徑,因此便有了這篇文章。
本文理論內(nèi)容較少,主要是從零到一的搭建流程實(shí)踐,適合對(duì)工程化感興趣的初級(jí)前端開發(fā)者。
01 服務(wù)器準(zhǔn)備
獲取服務(wù)器
上面提到,灰度發(fā)布是通過少量的用戶試點(diǎn)來驗(yàn)證新功能有沒有問題。所以要保證有兩批用戶能在同一時(shí)間體驗(yàn)到不同的功能。這就要求我們準(zhǔn)備兩臺(tái)服務(wù)器,分別部署不同的代碼版本。
如果你已經(jīng)有了一臺(tái)服務(wù)器,也可以通過在不同端口部署服務(wù)的方式來模擬兩臺(tái)服務(wù)器。如果你還一臺(tái)服務(wù)器都沒有,那么可以參考這個(gè)過程購買兩臺(tái)云服務(wù)器,如果是按需購買,完成本文的Demo,大概要花費(fèi)20塊錢。
獲取云服務(wù)器教程:github.com/TerminatorS…
工具安裝
Git
首先,確保你的服務(wù)器上已經(jīng)安裝了git,如果沒有的話使用以下命令進(jìn)行安裝,安裝好了以后生成ssh 公鑰,放到你的github 里,后面拉取代碼的時(shí)候會(huì)用到。
yum?install?git
Nginx
如果你的服務(wù)器沒有Nginx,先按照以下操作進(jìn)行安裝,Linux 下安裝Nginx非常簡(jiǎn)單:
sudo?yum?install?nginx
安裝完了,在終端輸入nginx -t檢查一下是否安裝成功。如果安裝成功,它會(huì)顯示Nginx 配置文件的狀態(tài),以及位置。

此時(shí)nginx還沒有啟動(dòng),在終端中輸入nginx 或nginx \-s reload 命令即可啟動(dòng),此時(shí)看到的nginx相關(guān)進(jìn)程如下,表明已經(jīng)啟動(dòng)成功。

在瀏覽器里訪問你的服務(wù)器公網(wǎng)IP,如果能看到下面的頁面說明Nginx 可以正常工作。

Jenkins (耗時(shí)比較久)
第一次接觸Jenkins 可能會(huì)有很多疑問,Jenkins 是什么?能完成什么事情?我為什么要使用Jenkins 等諸如此類。很難講清楚Jenkins 是什么東西,所以這里簡(jiǎn)單介紹一下Jenkins 可以做什么。簡(jiǎn)單來講,你在任何一臺(tái)服務(wù)器上進(jìn)行的任何操作命令,Jenkins 都可以幫你完成,只要你提前在Jenkins上創(chuàng)建好任務(wù),指定任務(wù)內(nèi)容和觸發(fā)時(shí)機(jī),比如定時(shí)觸發(fā)或者在特定的情況下觸發(fā)。
(1)安裝
Jenkins穩(wěn)定版本list:pkg.jenkins-ci.org/redhat-stab…
//?科學(xué)上網(wǎng)會(huì)快一些,記得留意網(wǎng)站上java和jenkins版本匹配信息,別下錯(cuò)了
wget?http://pkg.jenkins-ci.org/redhat-stable/jenkins-2.204.5-1.1.noarch.rpm
rpm?-ivh?jenkins-2.204.5-1.1.noarch.rpm
修改Jenkins端口,不沖突可不修改
//?line?56?JENKINS_PORT
vi?/etc/sysconfig/jenkins
(2)啟動(dòng)
啟動(dòng)jenkins
service?jenkins?start/stop/restart
//?密碼位置
/var/lib/jenkins/secrets/initialAdminPassword
(3)訪問
訪問服務(wù)器的8080端口,輸入從上述位置獲取的密碼,點(diǎn)擊繼續(xù)

創(chuàng)建一個(gè)賬戶然后登錄

看到Jenkins 已就緒的頁面表示安裝已經(jīng)完成,服務(wù)器準(zhǔn)備工作到此結(jié)束。

02 代碼準(zhǔn)備
準(zhǔn)備兩份代碼
因?yàn)橐龌叶炔渴?,所以需要?zhǔn)備兩份不一樣的代碼,以驗(yàn)證我們實(shí)施的灰度操作是否生效。這里選擇使用Angular 的Angular-CLI 來創(chuàng)建代碼。創(chuàng)建的項(xiàng)目并不簡(jiǎn)潔,但是勝在操作簡(jiǎn)單。我們一次性把兩份代碼準(zhǔn)備好,簡(jiǎn)化開發(fā)側(cè)工作。
//?安裝angular-cli,前提是已經(jīng)安裝了node,如果沒有node真的要去自行百度了...
npm?install?-g?@angular/cli
//?快速創(chuàng)建一個(gè)新項(xiàng)目,一路回車
ng?new?canaryDemocd?canaryDemo
//?運(yùn)行完這個(gè)命令后訪問http://localhost:4200?查看頁面信息
ng?serve
訪問localhost 的4200 端口查看頁面,然后把項(xiàng)目根目錄下src 中的index.html 的title 改成A-CanaryDemo,可以看到頁面會(huì)進(jìn)行實(shí)時(shí)地刷新。在這個(gè)例子中,我們用title 來標(biāo)識(shí)灰度發(fā)布過程中兩邊不同的服務(wù)需要部署的代碼。

接下來,我們進(jìn)行兩次打包,兩次打包的title 分別為A-CanaryDemo 和 B-CanaryDemo, 把這兩個(gè)文件夾放好備用,作為一會(huì)灰度發(fā)布的新老代碼。
ng?build?--prod

配置Nginx
在上述完成Nginx 的安裝操作時(shí),我們?cè)L問服務(wù)器的IP 看到的是Nginx 的頁面,現(xiàn)在我們想訪問到自己的頁面,首先把上面打包得到的A-CanaryDemo 發(fā)送到兩臺(tái)服務(wù)器上任意位置,這里我們把它放到/var/canaryDemo。
//?將A-CanaryDemo?文件夾復(fù)制到你的公網(wǎng)服務(wù)器上,xx部分是你的服務(wù)器公網(wǎng)ip
scp?-r?./dist/[email protected]:/var/canaryDemo
去服務(wù)器上/var 的位置上看一下,是否已經(jīng)有了這個(gè)文件,如果有了的話,接著到下一步。即修改Nginx 配置把訪問該服務(wù)器IP 的請(qǐng)求轉(zhuǎn)發(fā)到我們剛剛上傳上來的頁面上。上面提到過可以通過nginx -t 這個(gè)命令來查看Nginx 配置文件的位置,在這一步,我們要去編輯那個(gè)文件。
vi?/etc/nginx/nginx.conf
修改47-50行添加下圖相關(guān)的內(nèi)容,即將訪問到該服務(wù)器IP 的流量轉(zhuǎn)發(fā)到/var/canaryDemo 下的index.html.

修改完畢,保存退出,重啟一下nginx
nginx?-s?reload
這時(shí)候去訪問我們服務(wù)器的IP 地址可以看到頁面已經(jīng)變成了剛剛我們?cè)诒镜馗牡捻撁?,而且title 確實(shí)是A-CanaryDemo。兩臺(tái)服務(wù)器都操作完成后,兩邊都可以訪問到title 為A-CanaryDemo 的頁面。此時(shí)的狀態(tài)相當(dāng)于生產(chǎn)環(huán)境已經(jīng)在提供穩(wěn)定服務(wù)的兩臺(tái)機(jī)器。

03 定義灰度策略
接下來,我們要開始進(jìn)行灰度發(fā)布的部分,在進(jìn)行相關(guān)操作之前,我們需要定義一個(gè)灰度策略,即滿足什么情況下的流量會(huì)走到灰度邊,而其他流量走向正常邊。這里為了簡(jiǎn)單起見,我們使用名字為canary 的cookie 來區(qū)分,如果檢測(cè)到這個(gè)cookie 的值為devui,就訪問灰度邊機(jī)器,否則就訪問正常邊機(jī)器。按照此規(guī)則配置Nginx 結(jié)果如下,此處分別使用11.11.11.11和22.22.22.22代表兩臺(tái)服務(wù)器的IP地址:
#?Canary?Deployment
map?$COOKIE_canary?$group?{
??#?canary?account
??~*devui$?server_canary;
??default?server_default;
}
upstream?server_canary?{
??#?兩臺(tái)機(jī)器的IP,第一臺(tái)設(shè)置端口號(hào)8000是為了防止nginx轉(zhuǎn)發(fā)出現(xiàn)死循環(huán)導(dǎo)致頁面報(bào)錯(cuò)
??server?11.11.11.11:8000?weight=1?max_fails=1?fail_timeout=30s;
??server?22.22.22.22?weight=1?max_fails=1?fail_timeout=30s;
}
upstream?server_default?{
??server?11.11.11.11:8000?weight=2?max_fails=1?fail_timeout=30s;
??server?22.22.22.22?weight=2?max_fails=1?fail_timeout=30s;
}
#?相應(yīng)地,要配置8000端口的轉(zhuǎn)發(fā)規(guī)則,8000端口默認(rèn)不開啟訪問,需要去云服務(wù)器控制臺(tái)安全組新增8000
server?{
??listen?8000;
??server_name?_;
??root?/var/canaryDemo;
??#?Load?configuration?files?for?the?default?server?block.
??include?/etc/nginx/default.d/*.conf;
??location?/?{
????root?/var/canaryDemo;
????index?index.html;
??}
}
server?{
??listen?80?default_server;
??listen?[::]:80?default_server;
??server_name?_;
??#?root?/usr/share/nginx/html;
??root?/var/canaryDemo;
??#?Load?configuration?files?for?the?default?server?block.
??include?/etc/nginx/default.d/*.conf;
??location?/?{
????proxy_pass?http://$group;
????#?root?/var/canaryDemo;
????#?index?index.html;
??}
??error_page?404?/404.html;
????location?=?/40x.html?{
??}
??error_page?500?502?503?504?/50x.html;
??location?=?/50x.h
}
此時(shí),灰度流量和正常流量都會(huì)隨機(jī)分配到AB兩邊的機(jī)器。下面,我們通過建立Jenkins 任務(wù)執(zhí)行Nginx 文件修改的方式實(shí)現(xiàn)灰度發(fā)布。
04 實(shí)現(xiàn)灰度發(fā)布
流程梳理
在創(chuàng)建用于實(shí)現(xiàn)灰度發(fā)布的Jenkins任務(wù)之前我們先梳理一下要達(dá)到灰度發(fā)布的目標(biāo)需要哪幾個(gè)任務(wù),以及每個(gè)任務(wù)負(fù)責(zé)完成什么事情?;叶劝l(fā)布一般遵循這樣的流程(假設(shè)我們有AB兩臺(tái)服務(wù)器用于提供生產(chǎn)環(huán)境的服務(wù),我們稱之為AB邊):
(1)新代碼部署到A邊
(2)符合灰度策略的小部分流量切到A邊,剩余大部分流量仍去往B邊
(3)手動(dòng)驗(yàn)證A邊功能是否正??捎?/p>
(4)驗(yàn)證無誤后,大部分流量轉(zhuǎn)到A邊,灰度流量去往B邊
(5)手動(dòng)驗(yàn)證B邊功能是否正常可用
(6)驗(yàn)證無誤后,流量像往常一樣均分到AB邊
任務(wù)拆解
通過上述的拆解,我們得出灰度發(fā)布的6個(gè)步驟,其中(3)和(5)是需要手動(dòng)驗(yàn)證的環(huán)節(jié),所以我們以這兩個(gè)任務(wù)為分割點(diǎn),建立三個(gè)Jenkins 任務(wù)(Jenkins 任務(wù)建立在A 邊機(jī)器上)如下:
(1)Canary_A(灰度測(cè)試A),這個(gè)任務(wù)又包含兩個(gè)部分,更新A邊的代碼,然后修改流量分發(fā)策略使得灰度流量到達(dá)A,其他流量到達(dá)B
(2)Canary_AB(上線A灰度測(cè)試B),更新B邊代碼,灰度流量達(dá)到B,其他流量到達(dá)A
(3)Canary_B(上線B),所有流量均分到AB

創(chuàng)建任務(wù)
先按照任務(wù)拆解部分的設(shè)定創(chuàng)建三個(gè)FreeStyle 類型的Jenkins 任務(wù),記得使用英文名字,中文名字后面建文件夾比較麻煩。任務(wù)詳情信息可以不填,直接保存就好,下一步我們?cè)賮砼渲妹總€(gè)任務(wù)的具體信息。

配置任務(wù)
現(xiàn)在已經(jīng)創(chuàng)建好了三個(gè)任務(wù),先點(diǎn)擊進(jìn)入每一個(gè)任務(wù)進(jìn)行一次空的構(gòu)建(否則后面可能導(dǎo)致修改后的構(gòu)建任務(wù)無法啟動(dòng)),然后我們來對(duì)每個(gè)任務(wù)進(jìn)行詳細(xì)的配置。

現(xiàn)代前端項(xiàng)目都要進(jìn)行構(gòu)建打包這一步。但是廉價(jià)的云服務(wù)器在完成構(gòu)建方面有些力不從心,CPU 經(jīng)常爆表。所以我們?cè)谶@里把打包出得出的生產(chǎn)包納入git 管理,每次的代碼更新會(huì)同步最新的生產(chǎn)包到github,因此Jenkins 任務(wù)把生產(chǎn)包拉下來,放在指定位置即可完成一次新代碼的部署。
這一步操作,其實(shí)我們?cè)谥熬鸵呀?jīng)完成了,我們?cè)谏厦娲蛄藘煞輙ilte 不一樣的生產(chǎn)包,此時(shí)可以派上用場(chǎng)了。
首先來配置灰度測(cè)試A,這個(gè)任務(wù)內(nèi)容上面也基本講清楚了,首先要關(guān)聯(lián)該任務(wù)到遠(yuǎn)程的github 倉庫(需要手動(dòng)創(chuàng)建一個(gè),存放上面打包的B-CanaryDemo,并命名為dist)讓它知道可以去哪里拉取最新代碼。

執(zhí)行一次構(gòu)建任務(wù)(在git fetch 那一步耗時(shí)不穩(wěn)定,有時(shí)比較久),然后點(diǎn)擊本次構(gòu)建進(jìn)去查看Console Output,可以確定執(zhí)行Jenkins 任務(wù)的位置是位于服務(wù)器上的/var/lib/jenkins/workspace/Canary_A


繼續(xù)編輯灰度測(cè)試A 任務(wù),添加build shell,也就是每次任務(wù)執(zhí)行時(shí)要執(zhí)行的命令:
(1)先拉取最新的代碼
(2)把代碼根目錄下的dist目錄復(fù)制到部署代碼的位置,這里我們指定的位置是/var/canaryDemo
(3)修改Nginx 配置使灰度流量到達(dá)A邊
就步驟(3)而言,修改灰度流量的方式其實(shí)就是選擇性注釋Nginx 配置文件中的內(nèi)容,注釋方式如下即可實(shí)現(xiàn)灰度測(cè)試A。
upstream?server_canary?{
??#?灰度流量訪問A?邊
??server?11.11.11.11:8080?weight=1?max_fails=1?fail_timeout=30s;
??#?server?22.22.22.22?weight=1?max_fails=1?fail_timeout=30s;
}
upstream?server_default?{
??#?正常流量訪問B?邊,為了在修改文件的時(shí)候把這段的配置和上面的server_canary?區(qū)分開,我們把這里的weight?設(shè)為2
??#?server?11.11.11.11:8080?weight=2?max_fails=1?fail_timeout=30s;
??server?22.22.22.22?weight=2?max_fails=1?fail_timeout=30s;
}
這一步填寫的shell 命令在使用jenkins 用戶執(zhí)行時(shí)可能會(huì)遇到權(quán)限問題,可以先用root 用戶登錄,把/var 目錄的歸屬改為jenkins 用戶,/etc/nginx/ngix.conf也需要新增可寫權(quán)限。由此,最終得到的shell 命令如下:
git?pull
rm?-rf?/var/canaryDemo
scp?-r?dist?/var/canaryDemo
sed?-i?'s/server?22.22.22.22?weight=1/#?server?22.22.22.22?weight=1/'?/etc/nginx/nginx.conf
sed?-i?'s/server?11.11.11.11:8000?weight=2/#?server?11.11.11.11:8000?weight=2/'?/etc/nginx/nginx.conf
nginx?-s?reload

灰度測(cè)試A 任務(wù)內(nèi)容配置完成,接下來依次配置上線A 灰度測(cè)試B 和上線B。
灰度測(cè)試B 的要執(zhí)行的任務(wù)是把最新的代碼拉到A 邊(因?yàn)槲覀兊腏enkins 任務(wù)都是建立在A 邊的),復(fù)制dist 下的代碼到B 邊Nginx 指定訪問位置,然后修改A 邊Nginx 配置,使灰度流量到達(dá)B 邊。
git?pull
rm?-rf?canaryDemo
mv?dist?canaryDemo
[email protected]:/var
sed?-i?'s/#?server?22.22.22.22?weight=1/server?22.22.22.22?weight=1/'?/etc/nginx/nginx.conf
sed?-i?'s/#?server?11.11.11.11:8000?weight=2/server?11.11.11.11:8000?weight=2/'?/etc/nginx/nginx.conf
sed?-i?'s/server?22.22.22.22?weight=2/#?server?22.22.22.22?weight=2/'?/etc/nginx/nginx.conf
sed?-i?'s/server?11.11.11.11:8000?weight=1/#?server?11.11.11.11:8000?weight=1/'?/etc/nginx/nginx.conf
nginx?-s?reload
這一步的任務(wù)內(nèi)容涉及到從A 邊服務(wù)器向B 邊服務(wù)器發(fā)送代碼,這個(gè)過程一般來說需要輸入B 邊服務(wù)器的密碼。我們想要做到免密發(fā)送,因此要通過把A 邊機(jī)器~/.ssh/id_rsa.pub 中的內(nèi)容添加到B 邊服務(wù)器~/.ssh/authorized_keys 中使得A 獲得免密像B 發(fā)送文件的權(quán)限。
上線B 則是通過取消對(duì)A 邊Nginx 配置的注釋使所有流量均分到AB 邊.
sed?-i?'s/#?server?22.22.22.22?weight=2/server?22.22.22.22?weight=2/'?/etc/nginx/nginx.conf
sed?-i?'s/#?server?11.11.11.11:8000?weight=1/server?11.11.11.11:8000?weight=1/'?/etc/nginx/nginx.conf
nginx?-s?reload
至此,我們就從零到一搭建了一個(gè)灰度發(fā)布環(huán)境。在代碼更新后,通過手動(dòng)執(zhí)行Jenkins 任務(wù)的方式實(shí)現(xiàn)灰度部署和手工測(cè)試,保證新功能平滑上線。
總結(jié)
本文從服務(wù)器準(zhǔn)備、代碼準(zhǔn)備、灰度策略制定和實(shí)現(xiàn)灰度發(fā)布四個(gè)方面介紹了從零搭建一個(gè)灰度發(fā)布環(huán)境的必備流程?;叶劝l(fā)布的核心其實(shí)就是通過對(duì)Nginx 文件的修改實(shí)現(xiàn)流量的定向分發(fā)。內(nèi)容頗為簡(jiǎn)單,但是從零到一的整個(gè)流程操作下來還是比較繁瑣,希望各位看官能夠有所收獲。
另外,這只是一個(gè)最簡(jiǎn)易的Demo,在真正的DevOps 開發(fā)過程中,還需要集成編譯構(gòu)建、代碼檢查、安全掃描和自動(dòng)化測(cè)試用例等其他操作,期待后續(xù)團(tuán)隊(duì)的其他成員進(jìn)行更多的專項(xiàng)擴(kuò)展!

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 80+ 篇原創(chuàng)文章
