基礎設施

拆解多租戶 SaaS 並植入氣隙隔離箱

將雲端 SaaS 作為單一地端應用裝置來交付,並非部署上的變更。這意味著要在正確的位置移除租戶、點數、受控認證,以及每一個雲端相依性。

本文由 AI 模型從英文原文翻譯而來,用字可能與原文有所出入。 閱讀英文原文

客戶希望將您的多租戶媒體 SaaS 作為一個單一設備,部署在他們自己的網路上,無需網際網路、無按用量計費,並為其員工提供無限的處理量。這聽起來像是一項打包工作。但事實並非如此。該產品在每一層都假設了租戶、點數、受控登入和雲端控制平面的存在,而這些假設中的每一個都必須從其所在的確切位置被移除。只要漏掉一個,該設備就會洩漏雲端呼叫,或無法滿足其銷售時所承諾的「無限」要求。

陷阱:隱藏 UI 並不等於移除限制

需求是無限處理量。第一個直覺是隱藏升級頁面和點數計量器。這完全是錯的。點數檢查並非只存在於一處。它在三個地方運行:

  • 前端在餘額為零時會隱藏操作。
  • API 會拒絕將會超過餘額的請求。
  • worker 在工作完成時會扣減帳本。

在示範中只會看到第一項,而它也是唯一不具權威性的一項。API 的每個其他客戶端,例如管理員主控台、較舊的行動應用程式版本、從佇列中取出的重試,都會在不載入您修補過的頁面的情況下,連到相同的端點。

所以限制無論如何都會洩漏出來。一個長時間的工作在餘額仍為正數時開始,一小時後 worker 將帳本扣減到零以下,然後下一個請求就在一個沒有帳單頁面也無法上網的機器上,回傳一個關於購買點數的「需要付款」錯誤。接著,夜間的對帳工作會看到負數餘額並暫停該帳戶。

實現「無限」意味著要同時移除所有三層的檢查:沒有 UI 門檻、沒有 API 拒絕,並且將帳本扣減變成一個空操作 (no-op)。有兩個細節決定了這是否能成立。將計量與強制執行分開,而不是兩者都刪除,因為客戶仍然想知道他們的團隊上個月處理了多少分鐘。並且將開關放在設定檔 (config) 中,而不是在 fork 裡:一個刪除了檢查的 branch 會在一個發行週期內就與雲端建置版本產生差異,而直到客戶升級前,沒有人會發現這個差異。

每個雲端相依性都需要一個地端替代方案

氣隙(Air-gapped)意味著該設備不會發出任何您未明確允許的呼叫。該 SaaS 在啟動時以及幾乎每個請求上都會聯繫一個雲端服務,而這些中的每一個都變成了一個本地替代方案:

氣隙邊界:允許的出口 vs 移除的雲端依賴 氣隙主機 app + worker 本機 db、儲存 1 2 翻譯 API TTS 廠商 // 只允許兩個對外呼叫;登入、CDN、計費、設定存放區已移除

具體的置換如下:

  • 設定檔在開機時來自雲端參數儲存庫。替換為由安裝程式寫入、預先植入的本機 .env 檔案。
  • 簽署的媒體 URL 由受管理的邊緣工作器產生。替換為一個 nginx 反向代理,它會驗證路徑上的 HMAC。
  • 登入是雲端單一登入。替換為由管理員預先建立的本機帳號密碼,不提供自助註冊。
  • 物件儲存是雲端儲存桶。替換為本機與 S3 相容的伺服器,重複使用相同的客戶端,但端點不同。
  • 日誌和指標傳送到受管理的後端。替換為本機磁碟上的檔案,並在達到大小上限時輪替。

每一行都隱藏了一個你原本免費獲得的特性。參數儲存庫是具有熱重載和變更歷史的單一事實來源,因此,磁碟上的檔案意味著每次設定變更都需重新啟動,並在開機日誌中印出設定版本。儲存保留了相同的客戶端,只變更端點,這看起來是免費的,但雲端儲存桶永遠不會滿,而磁碟會。那次置換讓你必須增加一個保留作業、一個在產品內部浮現的磁碟警告(因為現場沒有人會看儀表板),以及一個針對磁碟區已滿的明確行為。

單一登入原本提供了密碼政策、MFA、會話撤銷和稽核軌跡,而本機帳戶意味著要自己負責這四項。它也移除了電子郵件:密碼重設變成在機上主控台中的管理員操作,而安裝程式會建立第一個管理員,其憑證客戶必須在首次登入時變更。

對外呼叫獲得了一份只有兩個項目的允許清單:翻譯 API 和文字轉語音供應商。其他所有東西都是機上的本機程序。提供給網路團隊主機名稱和埠號而不是 IP,因為供應商的地址會變動,並讓這兩個呼叫都能快速失敗並顯示可見的錯誤。一個鎖定的網路會默默地丟棄非預期的連線,而當機看起來會像是產品損壞,而不是防火牆規則的問題。

拒絕啟動的環境閘門

該服務有一項啟動檢查,會拒絕在已知的雲端環境之外運行,這是一種防止有人意外啟動生產環境設定的保護機制。在一台氣隙隔離的機器上,該保護機制每次啟動時都會觸發。解決方法是新增一個名為 "on-prem" 的環境模式,設定驗證器會接受此模式,如此一來,同一個二進位檔便能針對本地基礎設施啟動,而不需進行其永遠無法滿足的雲端檢查。這比刪除該保護機制更為簡潔,因為該保護機制仍然保護著雲端建置版本。

拒絕啟動正是重點所在。一個在半設定狀態下啟動的應用裝置,稍後會在您無法連上的網路上、在無法讀取您日誌的客戶面前發生故障。在啟動時因指明缺少的設定而停止,是在安裝過程中、當工程師還在現場時就發生失敗。因此 "on-prem" 模式新增了自己的斷言,而非僅僅跳過雲端的斷言:儲存端點可連線、資料目錄可寫入、授權檔案存在、密鑰非空。

代價是產生了第三條程式碼路徑,而沒有人針對其開發的模式,就是會腐壞的模式。一個表格式驅動的測試,讓驗證器在每個模式下都運行一次,是個低成本的防禦方法:一個新增的必要設定若沒有 "on-prem" 的值,將會中斷建置,而不是中斷應用裝置。

在相信自己的架構圖之前,先閱讀程式碼

遷移工作的一部分是從每個服務中移除雲端設定客戶端。某個 worker 被列入名單,因為「它在執行期間會從雲端讀取設定」。但它並不會。閱讀程式碼後發現,它一直以來都只讀取一個普通的本地檔案,而雲端參數存放區只出現在其部署腳本中,從未出現在執行中的程序裡。那個 worker 不需要任何程式碼變更,只需要一個不同的種子檔案。

反方向的錯誤也同樣會發生。一個大家都當作是本地執行的媒體處理步驟,結果被發現在首次使用時會從儲存桶(bucket)中擷取一個資產包。它通過了所有的煙霧測試,因為那些機器在幾個月前就已經快取了那個資產包,但在第一天部署到一台全新的機器上時就失敗了。

有效的方法是雙管齊下。閱讀程式碼,找出你能叫出名字的呼叫:SDK 匯入、客戶端建構子、環境變數名稱、設定檔中的主機名稱字面值。然後讓網路來回答。在一個預設拒絕的出口規則後方啟動機器,並記錄被封鎖的連線和 DNS 查詢,執行一次驗收測試,然後閱讀拒絕日誌。閱讀程式碼可以找到你懷疑的依賴項,而防火牆則能找到你沒想到的。

「氣隙」的實際代價

客戶獲得了隔離。而帳單則體現在四個地方。

更新不再是持續性的。發布版本變成由某人攜帶進入的已簽署離線套件包,客戶運行的版本會落後好幾個版本,而資料遷移必須能夠續傳,因為沒有人在監看,也沒有回滾按鈕。

授權必須能離線運作。授權金鑰變成一個已簽署的檔案,並根據客戶控制的時鐘進行檢查,所以在交付前就要決定好過期後的行為。拒絕在運行生產工作流程的機器上啟動,會換來一個支援事件;過期後轉為唯讀,則能在不中斷業務的情況下保持壓力。

支援失去了它的工具。沒有傳送的日誌、沒有指標、通常也沒有 shell,因此每張支援單都始於一通電話,請求某人朗讀螢幕上的內容,除非你提供一個套件包指令,能將日誌、經過編輯的設定檔、版本資訊和磁碟狀態打包成單一檔案。

除錯失去了它的速度。重現問題需要一台運行客戶確切版本的實驗室設備,而修復程式則要搭著下一個套件包,在維護視窗期間才能部署。

將 SaaS 轉變為應用裝置,主要是在做減法,且需謹慎為之:

  • 一併移除 frontend、API 和 worker 中的按租戶及按用量限制,否則「無限制」的承諾會在展示中看不到的地方破功。
  • 為每個雲端依賴項提供一個具名的本地替代方案,並對對外呼叫設置嚴格的允許清單,這樣被遺忘的呼叫就會大聲地失敗,而不是暗中回傳資訊。
  • 新增一個環境模式,而不是刪除保護你雲端建置的防護措施。
  • 根據原始碼驗證每個依賴項,因為那些看起來有風險的通常只在部署時使用,而真正的風險則隱藏在請求路徑中。

關於氣隙隔離設備的常見問題

設備應該是獨立分支還是同一個程式碼庫?

同一個程式碼庫,搭配環境模式和設定旗標。刪除檢查的分支在單一版本中看起來很整潔,但之後會產生差異,而這些差異會在客戶升級時浮現。代價是 CI 也必須在本地部署模式下執行設定驗證器。

如果客戶允許部分對外存取權限怎麼辦?

那麼允許清單就成了協商的結果,而非硬性限制。盡可能縮短清單到你能辯護的程度,指定主機名稱和連接埠,並將每個允許的呼叫都視為下一季可能被撤銷。基於其上建構的功能在失效時應顯示明確訊息,絕不能當機。

如何修復無法連線的機器上的錯誤?

支援套件包傳入,修補後的套件包傳出。在每個支援的版本上都保留一台實驗室設備,這樣重現問題就不依賴客戶,並讓遷移可續行,以便在客戶自己的時間視窗內套用修復。這個循環以天為單位計算,這就是為什麼設備版本需要比雲端版本更嚴格的品質標準。