容器對前端開發真的有用嗎?答案是確定的。html
最初當我向公司的前端同窗「安利」容器技術的時候,不少人都會說:「容器?這不是用在後端的技術嗎?我不懂啊,並且前端開發用不上吧。」前端
但其實,今天咱們討論的「前端」已經不是傳統意義上的「前端」, 首先體如今終端類型的多樣性,好比 iOS,Android,小程序等;另外,伴隨着 Node.js 等技術的興起,前端開發的邊界也在逐漸服務端延伸。來到大前端時代,如何以工程化、服務化和自動化的方式來進行應用開發,實現業務的持續迭代、高可用、高併發是每個成功的互聯網產品不斷探索的事情,而漸爲成熟的容器技術大大提升了這個過程的效率。git
本文將結合馬蜂窩容器化平臺賦能前端應用構建的實踐經驗,介紹整個平臺背後的設計和實現原理,取得的一些效果及問題的優化方案。chrome
通常來講前端的開發流程是這樣的:建立服務/項目 → 本地開發 → 開發環境測試 → 生產環境測試 → 生產灰度 → 上線。npm
基於容器化平臺進行前端開發的優點在於,前端和後端徹底分離,咱們只須要關注前端的項目構建,而不須要和後端代碼一塊兒打包。每一個構建版本及每一個訪問規則也都是獨立的,一個版本構建失敗並不影響其餘版本的構建及訪問。小程序
那麼,容器和前端的結合點在哪裏?容器的優點在前端應用研發的哪一個環節發生做用?咱們能夠從開發、測試、生產這三個階段分別來看。後端
容器消除了線上線下的環境差別,保證了應用生命週期的環境一致性標準化。而對於前端開發來講,要完成的任務每每是完成內容的呈現和響應用戶的輸入,處理的是 HTML、JS、CSS 等靜態資源,文件直接發送到客戶端,不須要一個運行環境,這裏好像用不上容器。瀏覽器
那 Build 的時候呢?畢竟不一樣的項目是用不一樣的 Node 版本在作構建,不一樣的容器能夠進入不一樣的 Node 版本,這樣就不會污染本機的 Node 環境。但其實沒有容器,前端還能夠用 NVM 去管理 Node 版本,切換起來很隨意,也就是一兩行命令就能搞定的事情。並且本地開發很方便,看起來真的沒有必要用容器。緩存
能夠說,容器自己並無幫助前端在開發階段變得更加便利。所以若是對容器技術不熟悉,開發階段沒有必要非要用容器。服務器
過去咱們用虛擬機進行測試的一個常見的方案是,前端研發把本身的代碼上傳到虛擬機的一個目錄下,QA 能夠直接經過域名進行測試。但問題是,公司有不少的產品線,可能會存在不少項目同時提測的狀況。虛擬機對系統資源的消耗比較大,數量有限,而且難擴容,影響測試效率。
若是使用容器化平臺就不會出現這方面的擔心。由於容器很是輕量,消耗低、啓動快,能夠迅速擴容,不用擔憂不夠用的問題。
容器的另外一個優點是它能夠實現應用程序的版本控制。好比咱們在上線以後發現版本有問題須要回滾,這種狀況不可避免,傳統的作法是經過 Git 或者 SVN 回滾,一旦合入的代碼想回退或者拆分就很難操做,並且從新部署也很耗時。
基於容器化的平臺,咱們能夠直接經過流控,把流量切到舊的版本上去,幾須要幾秒鐘的時間,回滾效率大大提高。
再如,前端性能的一個重要指標是頁面加載時間,若是出現首頁白屏是很是破壞用戶體驗的,特別是在作活動的時候,咱們把幾乎全部流量都引導到活動頁,出現白屏會很是讓人抓狂。找到運維排查以後發現有臺服務器掛了,只能經過重啓來解決。可是重啓機器存在不少不肯定性,有可能這臺機器就起不來了,這種狀況很常見。
但若是運行在容器化平臺上,一個容器就是一個進程,一臺機器若是宕機,集羣會快速從另一個節點把服務拉起,並且是秒級的,基本不用擔憂用戶的訪問會出現問題。
總結來看,容器與虛擬機相比主要的優點體如今能夠實現快速擴容、秒級回滾和穩定保活。所以容器化對於前端開發來講,更重要的意義是可以保證服務的快速迭代,以及線上服務的穩定性。
經過上面的介紹,相信你們已經對容器技術爲前端開發帶來了哪些變化有了一些感覺。那麼爲了更好地應用這項技術,前端同窗也應該掌握一些容器的基礎知識。
首先咱們來看容器究竟是什麼,它爲何輕量、高性能。經過下面這張圖片,咱們能夠將虛擬機和容器進行一個更加直觀的對比:
虛擬機經過在物理服務器上層經過運行 Hypervisor 模擬硬件系統,來提高服務器的能力和容量。每一個虛擬機中有一個內核,運行着不一樣的操做系統,啓動以後會作進程管理、內存管理之類的事情。但對於前端應用的構建來講,可能只是須要一個 Nginx 作靜態服務器,這種場景下使用虛擬機就過重了。
容器之因此輕量,是由於容器沒有 Hypervisor 層和內核層,每一個容器都共享宿主機的內核和系統調用。所以一個容器內包含的僅僅是一個程序運行所須要的最少文件,啓動容器就是啓動進程,對資源的開銷更小,維護起來更簡單。
這是你們在聊到容器技術的時候常常會提到的三個詞,下面來講下它們各自的概念以及之間的聯繫是什麼。
鏡像:能夠簡單理解爲一層層文件系統的集合,或者說一些目錄的集合。好比對於咱們的前端代碼,最下面那層目錄多是 Nginx 運行所須要的二進制,而後在上面再加一層目錄是咱們的代碼,好比說 index.html。這個鏡像分層全部的分層生成之後,都是隻讀的,每一層文件不可修改。
容器:其實就是在上面的目錄上再加一層目錄。但它實際上是一個空目錄,區別就在於容器最上面一層是可讀可寫的,也就是說容器 = 鏡像 + 讀寫層。
好比我若是想修改以前的 index.html ,是經過把新的版本累加在以前的鏡像上。也就是說生成容器之後,全部的變動都發生在頂層的鏡像可寫層,下面的這些層是不容許往裏面寫東西的,可是能夠累加,就像堆積木同樣,一直加上去,而原來的鏡像不會被容器修改,這也是鏡像能夠被多個容器共享的緣由。
Docker:容器技術其實早就存在,Docker 是用來實現容器化技術的一種工具,也是目前業界最通用的一種方式,來幫咱們製做鏡像,而後把鏡像運行成爲容器並管理起來。
介紹完簡單的概念,咱們就和你們一塊兒來看馬蜂窩容器化平臺的總體架構,咱們是如何爲前端賦能,以及賦予什麼樣的能力。
咱們基於 Docker 和 Kubernetes 搭建了容器雲平臺,將應用的構建、部署、資源調度、應用管理等能力抽象出來,以服務的方式提供給研發人員,提高線上服務的穩定性和研發效率。下圖從應用的角度出發,展現了前端應用在容器化平臺的生命週期:
應用是容器雲平臺的基本操做對象。雲平臺一個很是大的好處是屏蔽了項目的類型,不分前端或後端。因而在應用的外殼下,無論是前端的代碼,仍是後端的代碼,均可以享受一樣的服務。好比傳統意義上應用在後端的限流、熔斷、服務治理等能力同樣能夠賦予前端,使前端同窗聚焦在業務開發上,而不須要關注底層的實現。
這是應用中心的一個建立頁面,只須要幾步,一個應用就能夠建立完成,而且託管到咱們的雲平臺上:
建立完應用以後就要開始構建版本。經過使用容器,咱們將應用程序、配置和依賴關係等打包成一個個代碼鏡像,而後去告訴線上服務器怎麼讓它們用容器化的方式運行起來。所以版本管理包含代碼鏡像和運行時配置兩部份內容。
1. 代碼鏡像
咱們使用基於 Pipeline + Docker 的 Drone 做爲 CI 工具,它很是靈活,容易擴展。Drone 的靈活性體如今 Pipeline 的配置上,能夠經過設置 .drone.yml 文件的方式在項目中控制構建鏡像的過程。
爲了更好地支持公司級別的應用,咱們向鏡像注入一些內部常常用到的包來構建一個通用的基礎鏡像。在構建的同時會作一些 CI,好比單元測試、漏洞檢測等。
2. 運行時配置
運行時配置分紅 Nginx 配置和部署運行時的配置兩個部分
(1)Nginx 配置
Nginx 配置主要針對 Node 前端項目來講。將 Nginx 配置開放給應用有這麼幾點好處:
(2)部署運行配置
部署運行配置是要告訴系統平臺要如何運行版本包。這裏其實也就爲後續部署到 Kubernetes KVM 宿主機等多種平臺留好了擴展。
總結來看,在版本管理的部分咱們實現瞭如下幾點能力:
接下來咱們須要把已經構建好的版本包部署到集羣上去運行。
在線上可能會有許多臺機器,V一、V二、V3 指的是各類版本。這個版本能夠有多個實例。若是服務出現故障,咱們主要經過兩種方式來保證穩定高活:
結合咱們以前說到的主頁白頁的例子具體說明,咱們會在容器化平臺上持續看管容器,若是服務掛了,就在迅速在別的節點上啓動起來。這裏須要注意的是,「多份」不只僅是說在兩臺機器上啓動就叫多份,若是兩臺機器都在一個機櫃上,甚至在一個機房裏,那麼啓動多份也沒有意義。
到這裏,咱們已經把服務部署到線上,而且實現穩定運行。可是完成部署,不表明用戶就能訪問,也不表明就能訪問到正確的版本,因此接下來就到服務治理的環節。
服務治理是一個比較大的概念,能夠應用的場景也不少。它的其中一個內容是讓用戶訪問到指定的一線上版本。
技術方案
首先介紹下實現原理:
咱們採用的是一個 支持 xds 協議的網關。當新的配置經過 xds 協議推送給網關時,它就會自動進行熱更新、熱重啓,而後去適應新的配置。好比說開始網關指向的是 V1 版本,若是咱們如今但願指向 V2 版本,只須要把最新的配置經過 xds 協議推送給網關,它就會應用新的配置,經過這種方式就能夠將指定版本部署到線上。
推送這裏咱們用的是 Pilot 組件,並針對推送速度進行了優化。Pilot 組件會不斷監聽數據,發現有變動後就會取出。
應用場景
針對這種設計,咱們主要將其應用在三個場景中:回滾、分流和 ABTest。
1. 回滾
所謂回滾其實就是流控,好比一開始網關指向的是 V2 版本:
若是發現有問題,我只須要給網關推送一個新的配置,它就能夠指向以前那個版本,很是快速:
2. 分流
分流主要應用在文章開始說到的提測場景中。過去使用虛擬機,因爲不一樣的虛擬機有不一樣的域名,前端同窗在測試的時候要麼就是爲了適配虛擬機去修改代碼,要麼就是須要測試同窗或者產品同窗本身去修改本身本機的 host,很是不方便。
而使用容器化的方式,如說如今默認訪問的是 V2 版本,但咱們如今須要測試 V1 或 V3 版本,就能夠推出一個配置給網關,告訴它說若是請求裏面的 cookie 含有標識 V=V1,就把請求轉發至 V1 版本;一樣若是 cookie 包含 V=V3,就將請求轉發到 V3, 全部的轉發都在網關層完成。
爲了使服務更易用,咱們提供了一個插件去自動識別雲平臺部署的服務和版本。QA 和 產品同窗在測試的時候,只須要點選版本就能夠,系統會自動完成 cookie 注入。而後向服務端發送請求時,網關就會發現這個攜帶了某個版本的 cookie,自動完成轉發:
3. ABTest
一樣的原理,咱們能夠經過配置指定用戶的 UID,控制用戶去訪問 ABTest 中的不一樣版本,這裏支持的方式有不少,好比注入 cookie、不一樣的 head 頭、不一樣的請求方式等等,很是靈活。
以上是服務治理的內容。總的來講,咱們可以自動化部署訪問規則,可能只須要前端同窗作一個 git-push tag 的操做,就已經打好版本並部署到開發環境甚至是生產環境,而整個過程對於平臺的使用者來講是無感知的:
以上介紹了基於容器化雲平臺咱們能夠爲前端賦予哪些能力。通過一些時間的探索,目前咱們的流程已經比較通暢,但不可避免仍是會遇到一些問題。
這種狀況對用戶體驗來講很是糟糕。通過排查後咱們發現問題出如今爲了作到高可用,咱們的網關配置了多個。
由於網關的轉發配置是經過推送下發的,多個網關以前就會存在時間差。有的網關先收到新的推送,有的後收到。當用戶的請求打到了其中一個網關拿到了一個 html,會告訴它應該訪問哪一個 hash 的 js。但若是不巧的是 hash 的 js 卻訪問到了另一個網關,而後轉發到另一個版本,也就是另一個容器,那麼 hash 值確定就不同了,找不到對應的文件,致使 404。
這個問題不只雲平臺會存在,只要是分佈式的部署方案均可能存在時差的問題。咱們的解決方案是讓全部網關都鏈接到同一個 Pilot。由於網關的數量是有限的,這時配置的下發就是由一個組件去負責推送全部的網關,由於 xds 協議自己是基於 GRPC 實現的,是一個長鏈接的操做,因此速度很是快。當由一個節點去作推送,全部網關接收到配置的時差能夠控制在在毫秒間,幾乎沒有影響。也就是 A 網關接收到新配置的同時,基本上 B 網關也已經接收到新配置,這時候全部請求不管打到哪一個網關,他們都會指向同一個版本,這個時候線上就不會再出現 404 的請求。
以前說到,咱們的灰度方案是應用插件作 cookie,理論上來講只要 cookie 的配置正確,就能夠轉發到指定的版本上去。那麼既然個人 html 已經沒問題了,爲何 js 還會出現 404?
排查後發現,由於 js 請求的時候有一個標籤叫「匿名標籤」,若是咱們在用 js 的時候打了匿名的標籤,瀏覽器在發 js 請求時就不會攜帶任何身份的標識,網關就會認爲訪問到一個默認版本,也就是線上的版本,這個時候若是請求再到 V2 版本就會 404。
目前咱們構建鏡像的方式主要是用 npm install 和 npm run build 兩個命令。以後咱們會盡量去釋放 Pipeline,包括基礎鏡像、Node 版本等,讓前端同窗能夠實現更多自定義的需求。
目前咱們構建鏡像的方案沒有很好地利用 Docker 的緩存機制,所以會影響構建的時間。咱們目前也在作優化,儘量減小甚至消滅大部分 npm install 的時間和 build 的時間。
目前咱們已經完成了一部分監控告警能力的建設,主要是由平臺維護團隊在使用,去監控 QPS 情況、服務是否穩定,有沒有重啓等,團隊內部也會收到不少告警。但咱們認爲這種報警其實更應該發送給服務的負責人,後面咱們慢慢要將這部分能力釋放出來,而且不斷完善和優化告警規則。
最後簡單總結:
容器化以後到底給前端賦能了什麼?
馬蜂窩雲平臺如何進一步給前端賦能?
目前咱們在如何經過容器化的方式幫助前端完成應用研發有了必定的探索,而且經過雲平臺的方式上作到更進一步的賦能,但願能帶給你們一些技術思惟上的啓發。
本文做者:周磊,馬蜂窩旅遊網基礎平臺服務化研發工程師。
(題圖來源於網絡)
關注馬蜂窩技術,找到更多你想要的內容