容器對前端開發真的有用嗎?答案是肯定的。
最初當我向公司的前端同學「安利」容器技術的時候,很多人都會說:「容器?這不是用在後端的技術嗎?我不懂啊,而且前端開發用不上吧。」
但其實,今天我們討論的「前端」已經不是傳統意義上的「前端」, 首先體現在終端型別的多樣性,比如 iOS,Android,小程式等;另外,伴隨著 Node.js 等技術的興起,前端開發的邊界也在逐漸服務端延伸。來到大前端時代,如何以工程化、服務化和自動化的方式來進行應用開發,實現業務的持續迭代、高可用、高併發是每一個成功的網際網路產品不斷探索的事情,而漸為成熟的容器技術大大提高了這個過程的效率。
本文將結合馬蜂窩容器化平臺賦能前端應用構建的實踐經驗,介紹整個平臺背後的設計和實現原理,取得的一些效果及問題的優化方案。
容器與前端的結合點
一般來說前端的開發流程是這樣的:建立服務/專案 → 本地開發 → 開發環境測試 → 生產環境測試 → 生產灰度 → 上線。
基於容器化平臺進行前端開發的優勢在於,前端和後端完全分離,我們只需要關注前端的專案構建,而不需要和後端程式碼一起打包。每個構建版本及每個訪問規則也都是獨立的,一個版本構建失敗並不影響其他版本的構建及訪問。
那麼,容器和前端的結合點在哪裡?容器的優勢在前端應用研發的哪個環節發生作用?我們可以從開發、測試、生產這三個階段分別來看。
開發環節
容器消除了線上線下的環境差異,保證了應用生命週期的環境一致性標準化。而對於前端開發來說,要完成的任務往往是完成內容的呈現和響應使用者的輸入,處理的是 HTML、JS、CSS 等靜態資源,檔案直接傳送到客戶端,不需要一個執行環境,這裡好像用不上容器。
那 Build 的時候呢?畢竟不同的專案是用不同的 Node 版本在做構建,不同的容器可以進入不同的 Node 版本,這樣就不會汙染本機的 Node 環境。但其實沒有容器,前端還可以用 NVM 去管理 Node 版本,切換起來很隨意,也就是一兩行命令就能搞定的事情。而且本地開發很方便,看起來真的沒有必要用容器。
可以說,容器本身並沒有幫助前端在開發階段變得更加便利。因此如果對容器技術不熟悉,開發階段沒有必要非要用容器。
測試環節
過去我們用虛擬機器進行測試的一個常見的方案是,前端研發把自己的程式碼上傳到虛擬機器的一個目錄下,QA 可以直接通過域名進行測試。但問題是,公司有很多的產品線,可能會存在很多專案同時提測的情況。虛擬機器對系統資源的消耗比較大,數量有限,並且難擴容,影響測試效率。
如果使用容器化平臺就不會出現這方面的擔憂。因為容器非常輕量,消耗低、啟動快,可以迅速擴容,不用擔心不夠用的問題。
生產環節
容器的另一個優勢是它可以實現應用程式的版本控制。比如我們在上線之後發現版本有問題需要回滾,這種情況不可避免,傳統的做法是通過 Git 或者 SVN 回滾,一旦合入的程式碼想回退或者拆分就很難操作,而且重新部署也很耗時。
基於容器化的平臺,我們可以直接通過流控,把流量切到舊的版本上去,幾需要幾秒鐘的時間,回滾效率大大提升。
再如,前端效能的一個重要指標是頁面載入時間,如果出現首頁白屏是非常破壞使用者體驗的,特別是在做活動的時候,我們把幾乎所有流量都引導到活動頁,出現白屏會非常讓人抓狂。找到運維排查之後發現有臺伺服器掛了,只能通過重啟來解決。但是重啟機器存在很多不確定性,有可能這臺機器就起不來了,這種情況很常見。
但如果執行在容器化平臺上,一個容器就是一個程式,一臺機器如果當機,叢集會快速從另外一個節點把服務拉起,而且是秒級的,基本不用擔心使用者的訪問會出現問題。
總結來看,容器與虛擬機器相比主要的優勢體現在可以實現快速擴容、秒級回滾和穩定保活。因此容器化對於前端開發來說,更重要的意義是能夠保證服務的快速迭代,以及線上服務的穩定性。
前端需要了解的容器知識點
通過上面的介紹,相信大家已經對容器技術為前端開發帶來了哪些變化有了一些感受。那麼為了更好地應用這項技術,前端同學也應該掌握一些容器的基礎知識。
容器是什麼
首先我們來看容器到底是什麼,它為什麼輕量、高效能。通過下面這張圖片,我們可以將虛擬機器和容器進行一個更加直觀的對比:
虛擬機器通過在物理伺服器上層通過執行 Hypervisor 模擬硬體系統,來提升伺服器的能力和容量。每個虛擬機器中有一個核心,執行著不同的作業系統,啟動之後會做程式管理、記憶體管理之類的事情。但對於前端應用的構建來說,可能只是需要一個 Nginx 做靜態伺服器,這種場景下使用虛擬機器就太重了。
容器之所以輕量,是因為容器沒有 Hypervisor 層和核心層,每個容器都共享宿主機的核心和系統呼叫。因此一個容器內包含的僅僅是一個程式執行所需要的最少檔案,啟動容器就是啟動程式,對資源的開銷更小,維護起來更簡單。
映象、容器和 Docker
這是大家在聊到容器技術的時候經常會提到的三個詞,下面來說下它們各自的概念以及之間的聯絡是什麼。
映象:可以簡單理解為一層層檔案系統的集合,或者說一些目錄的集合。比如對於我們的前端程式碼,最下面那層目錄可能是 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 配置開放給應用有這麼幾點好處:
- 前端同學可以自己去配置 history 模式,不需要再去找服務端來配合。
- 自定義多個 location。在面對多頁應用時,可以通過配置 Nginx 把請求轉發到指定的入口檔案,實現指定路由。
- 自定義 cache 快取策略。快取策略選擇更靈活,提升使用者體驗,降低伺服器處理請求的壓力。
(2)部署執行配置
部署執行配置是要告訴系統平臺要如何執行版本包。這裡其實也就為後續部署到 Kubernetes KVM 宿主機等多種平臺留好了擴充套件。
總結來看,在版本管理的部分我們實現了以下幾點能力:
- 配置檔案驅動,一個應用多份靈活好擴充套件
- Nginx 配置等開放給應用,遵循 DevOps 思想,高效賦能
- 標準化版本產物,一處構建,處處執行
部署管理
接下來我們需要把已經構建好的版本包部署到叢集上去執行。
線上上可能會有許多臺機器,V1、V2、V3 指的是各種版本。這個版本可以有多個例項。如果服務出現故障,我們主要通過兩種方式來保證穩定高活:
- 高效排程:通過 Kubernetes 排程器將指定執行的容器排程到資源滿足要求、最合適的節點上去
- 多副本支撐:自動部署一個容器應用的多份副本,並持續監控。如果容器掛掉自動啟動副本
結合我們之前說到的主頁白頁的例子具體說明,我們會在容器化平臺上持續看管容器,如果服務掛了,就在迅速在別的節點上啟動起來。這裡需要注意的是,「多份」不僅僅是說在兩臺機器上啟動就叫多份,如果兩臺機器都在一個機櫃上,甚至在一個機房裡,那麼啟動多份也沒有意義。
到這裡,我們已經把服務部署到線上,並且實現穩定執行。但是完成部署,不代表使用者就能訪問,也不代表就能訪問到正確的版本,所以接下來就到服務治理的環節。
服務治理
服務治理是一個比較大的概念,可以應用的場景也很多。它的其中一個內容是讓使用者訪問到指定的一線上版本。
技術方案
首先介紹下實現原理:
我們採用的是一個 支援 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 的操作,就已經打好版本並部署到開發環境甚至是生產環境,而整個過程對於平臺的使用者來說是無感知的:
- 自動化部署訪問規則,完整 CI/CD
- 靈活的分流策略,帶來秒級回滾,灰度,abtest 等功能
- 結合 chrome 外掛,體驗流暢
以上介紹了基於容器化雲平臺我們可以為前端賦予哪些能力。經過一些時間的探索,目前我們的流程已經比較通暢,但不可避免還是會遇到一些問題。
那些年我們遇到的 404
1. 上線後,發現 js 訪問404
這種情況對使用者體驗來說非常糟糕。經過排查後我們發現問題出現在為了做到高可用,我們的閘道器配置了多個。
因為閘道器的轉發配置是通過推送下發的,多個閘道器之前就會存在時間差。有的閘道器先收到新的推送,有的後收到。當使用者的請求打到了其中一個閘道器拿到了一個 html,會告訴它應該訪問哪個 hash 的 js。但如果不巧的是 hash 的 js 卻訪問到了另外一個閘道器,然後轉發到另外一個版本,也就是另外一個容器,那麼 hash 值肯定就不一樣了,找不到對應的檔案,導致 404。
這個問題不僅雲平臺會存在,只要是分散式的部署方案都可能存在時差的問題。我們的解決方案是讓所有閘道器都連線到同一個 Pilot。因為閘道器的數量是有限的,這時配置的下發就是由一個元件去負責推送所有的閘道器,因為 xds 協議本身是基於 GRPC 實現的,是一個長連線的操作,所以速度非常快。當由一個節點去做推送,所有閘道器接收到配置的時差可以控制在在毫秒間,幾乎沒有影響。也就是 A 閘道器接收到新配置的同時,基本上 B 閘道器也已經接收到新配置,這時候所有請求無論打到哪個閘道器,他們都會指向同一個版本,這個時候線上就不會再出現 404 的請求。
2. 灰度環境,js 訪問 404
之前說到,我們的灰度方案是應用外掛做 cookie,理論上來說只要 cookie 的配置正確,就可以轉發到指定的版本上去。那麼既然我的 html 已經沒問題了,為什麼 js 還會出現 404?
排查後發現,因為 js 請求的時候有一個標籤叫「匿名標籤」,如果我們在用 js 的時候打了匿名的標籤,瀏覽器在發 js 請求時就不會攜帶任何身份的標識,閘道器就會認為訪問到一個預設版本,也就是線上的版本,這個時候如果請求再到 V2 版本就會 404。
近期規劃
1. 儘可能釋放構建 Pipeline
目前我們構建映象的方式主要是用 npm install 和 npm run build 兩個命令。之後我們會盡可能去釋放 Pipeline,包括基礎映象、Node 版本等,讓前端同學可以實現更多自定義的需求。
2. 優化構建和部署時間
目前我們構建映象的方案沒有很好地利用 Docker 的快取機制,因此會影響構建的時間。我們目前也在做優化,儘可能減少甚至消滅大部分 npm install 的時間和 build 的時間。
3. 釋放監控告警能力
目前我們已經完成了一部分監控告警能力的建設,主要是由平臺維護團隊在使用,去監控 QPS 狀況、服務是否穩定,有沒有重啟等,團隊內部也會收到很多告警。但我們認為這種報警其實更應該傳送給服務的負責人,後面我們慢慢要將這部分能力釋放出來,並且不斷完善和優化告警規則。
總結
最後簡單總結:
容器化之後到底給前端賦能了什麼?
- 提高測試效率
- 服務更加穩定,運維高效
馬蜂窩雲平臺如何進一步給前端賦能?
- 應用中心:一步上雲,無差別享受雲平臺帶來的服務
- 版本管理:實踐 DevOps思想,賦能 Nginx 配置;配置驅動,靈活好擴充套件
- 部署管理:智慧排程,穩定高活
- 服務治理:秒級回滾,秒級恢復,灰度訪問,ABTest等眾多功能
目前我們在如何通過容器化的方式幫助前端完成應用研發有了一定的探索,並且通過雲平臺的方式上做到更進一步的賦能,希望能帶給大家一些技術思維上的啟發。
本文作者:周磊,馬蜂窩旅遊網基礎平臺服務化研發工程師。
(題圖來源於網路)
關注馬蜂窩技術,找到更多你想要的內容