小型服務的兩到三台主機配置:第二台與第三台主機的用法、監控主機的位置與連線、兩台主機互相監看,以及雲端業者負責的範圍
一個小型服務(一組應用程式、一個資料庫、一個快取,由一個小團隊維運)從一台主機擴充到兩到三台時,每一台主機放什麼、每一種分法換到什麼,以及哪些故障與攻擊要由雲端業者或入口的設定處理,是這一篇的範圍。內容是設計與規劃:監控主機跨業者讀取服務主機、兩台主機互相監看的配置,本篇沒有在兩台實體主機上部署驗證,這兩部分寫的是要決定的事與它們的前提,沒有附操作指令。單一主機上監控看不到的故障見 4.28 單台主機的監控盲區與失效域以外的觀察者,冗餘的一般模式見 冗餘設計模式。
一台主機承擔的風險
一台主機上跑著入口、應用程式、資料庫、快取與整組監控時,所有元件共用同一個失效域(failure domain),風險有三種。中斷沒人通知:主機停掉時服務中斷,而負責通知的監控同時停掉,要等使用者回報才知道。監控與服務搶資源:主機過載時,應用程式、資料庫與監控搶同一組 CPU 與記憶體,監控量到的延遲混進它自己的等待(見 4.27 主機過載時的監控告警)。資料與備份一起消失:資料與它的備份放在同一台主機的磁碟上時,磁碟損壞或帳號被停權會讓兩者一起消失。
資料與備份一起消失是三種裡唯一不可逆的,也不需要第二台主機:備份放到另一個業者的物件儲存或另一個帳號,就不再與資料一起消失(備份與資料放在同一個帳號時,帳號被停權、或存取憑證外洩後被人刪除,兩者一起消失),所以它排在加主機之前做(備份的驗證見 備份與還原的地基)。分開帳號擋的是資料所在帳號的憑證外洩;寫入備份的那把存取金鑰仍放在服務主機上,它外洩之後備份會不會被刪除或覆寫,見 7.43 兩個網路之間的連線安全。第二台主機處理的是中斷沒人通知、監控與服務搶資源,處理哪一種取決於它放什麼。
第二台主機的用法:給監控、給應用程式的副本或給資料庫
給監控。 Prometheus、Alertmanager 與 Grafana 搬到第二台,服務主機只留 exporter(輸出指標給 Prometheus 讀取的程式)。原本跑在同一台的自架通知服務(ntfy、郵件轉送)一起搬過去,或改用外部的服務;留在服務主機上的話,「服務主機停了」這則告警要經過那台已經停掉的主機。服務主機停掉時,監控主機讀不到它的指標而發出告警;監控主機停掉時,服務照常運作,由服務主機上的心跳監看程式發現(見〈兩台主機互相監看〉)。服務本身的可用性沒有提高,主機停掉時服務照樣中斷;換到的是中斷有人通知,以及監控不再與服務搶資源。應用程式不必改,監控主機的資源需求也小(指標量的估算見 Prometheus 容量規劃與故障模式)。搬的時候要決定既有的指標歷史帶不帶過去:不帶的話新主機從零累積,搬移前後的趨勢在面板上接不起來;要帶的話把 Prometheus 的資料目錄一起搬,停掉舊的 Prometheus 再複製,避免複製到寫到一半的資料。
給應用程式的副本。 兩台主機各跑一份應用程式,前面以負載平衡器分流,一台停掉時另一台接手。換到的是服務的可用性,前提有四個:應用程式是無狀態的(session 不存在單台主機的記憶體或磁碟裡,見 無狀態設計);資料庫不在任何一台應用主機上,否則放著資料庫的那一台停掉時,另一台的應用程式也沒有資料可讀;負載平衡器本身不是單點;應用程式內建的排程工作(清理過期的短網址、寄信、結算)只在一台執行,否則兩台各跑一份,副作用跟著重複,做法是加鎖、選出一台負責,或把排程工作獨立出來;固定指定一台時,那一台停掉排程就停了,以排程工作的心跳發現(見 4.28 單台主機的監控盲區與失效域以外的觀察者 的〈心跳監看:監控與送出心跳的那條路還在不在運作〉)。無狀態這一條違反時,平時看不出來:負載平衡器若以 sticky session 把同一個使用者固定送到同一台,兩台都在時每個人都正常;直到一台停掉的那一刻,那台上的使用者一起被登出、上傳到一半的檔案一起消失,而這是這個配置第一次真正派上用場的時候。資料庫因此要搬到託管服務或第三台主機,這個配置需要兩台應用主機加上另外放置的資料庫。
給資料庫。 資料庫搬到第二台,應用程式與資料庫各自有一台主機的資源。換到的是效能的隔離:資料庫的大查詢不再拖慢轉址這類延遲敏感的請求,應用程式與資料庫的容量也能分開調整。可用性沒有提高,反而多了一個會讓服務中斷的主機:兩台任一台停掉,服務都中斷。
目標只有「中斷有人通知」時,不需要第二台主機:第三方的心跳監看與外部探測服務幾乎不花錢就做得到,挑選與接法見 第三方心跳監看與外部探測服務。第二台給監控多換到的是三件事:監控不再與服務搶資源、監控資料與告警內容留在自己手上、心跳的接收端不依賴第三方的服務。通知的寄送與第三個位置的外部探測仍然依賴第三方:用來分辨兩台之間連線中斷的外部探測要放在兩台以外(見〈監控主機與服務主機之間的連線與連線中斷的誤判〉)。所以小型服務在要擴充主機時,第二台先給監控:中斷沒人通知與監控搶資源在一台主機的階段就存在,給監控的第二台同時處理這兩種風險,也不需要改應用程式;應用程式的副本要先處理無狀態化與資料庫的位置,資料庫的分離處理的是效能而不是可用性。停機代價高、主機故障又頻繁的服務,例外地先做應用程式的副本,判斷方式見 高可用的成本。
監控主機的位置:另一個業者或地端
監控主機要與服務主機分屬不同的失效域。兩台虛擬機租在同一個業者、同一個區域時,業者那個區域的網路故障或帳號被停權會讓兩台一起受影響;所以監控主機放在另一個雲端業者,或放在地端(公司或家裡長時間開著的一台主機)。多台主機的指標收集 的〈監控主機放在哪裡〉處理的是同一個私有網路裡分出一台監控主機的情形,本篇多加的是失效域的條件。
監控是內部服務,地端是合理的位置:Grafana 只給團隊看,連線經由 SSH 或 VPN(存取方式見 Grafana 的對外存取),不必對網際網路開放 Grafana。地端主機的代價是它自己的可靠度:停電、網路斷線、機器重開時監控就停了,停掉的那段時間沒有指標。這段時間由服務主機上的心跳監看程式發現;監控資料的連續性要求高時,改租另一個業者的小型 VPS。
監控主機與服務主機之間的連線與連線中斷的誤判
Prometheus 主動讀取各主機的 exporter,所以監控主機要連得到服務主機上的 exporter 連接埠。這些連接埠不對網際網路開放:exporter 預設沒有認證,輸出的指標透露主機的資源、服務的名稱與版本。同一個雲端業者內,exporter 綁在私有網路的位址上(多台主機的指標收集 以 EXPORTER_BIND_ADDR 設定);跨業者或跨地端時沒有共用的私有網路,要在兩台之間建一條 VPN,例如 WireGuard 或 Tailscale(運作方式見 Tailscale 深入),exporter 改綁在 VPN 介面的位址上。VPN 預設兩個方向都互通,雲端主機被入侵時攻擊者也能經由它連回地端;隧道兩端各自連得到什麼、限制要放在哪一端,見 7.43 兩個網路之間的連線安全。
地端在 NAT 後面、對外 IP 會變動時,由地端主動建立 VPN:WireGuard 在地端那一側設定 PersistentKeepalive、主動連到雲端主機,雲端主機在業者的防火牆或安全群組開放一個 UDP 連接埠給它;Tailscale 由它的 NAT 穿越與中繼伺服器建立連線,兩端都不必開放連接埠。隧道建立之後兩個方向都連得到,所以 Prometheus 從監控主機讀取,或服務主機以 remote write 把指標推到監控主機,在網路上都走得通,兩者的取捨見〈多台主機的指標收集〉的〈指標回到中央的架構:中央直接讀取與各主機推送〉。安全上兩者的差別在誰要接受對方發起的連線:讀取時地端只發起連線,推送時地端要開放接收端點,見 7.43 兩個網路之間的連線安全。
跨網路讀取多了一種誤判:兩地之間的 VPN 斷掉時,監控主機讀不到服務主機,在監控看來與服務主機停了相同,up(Prometheus 記錄每個讀取對象是否讀取成功的序列)變成 0,主機停機的告警發出。分辨連線中斷與服務主機停機要靠第三個位置的觀察者:從另一個網路對服務的公開網址發請求的外部探測,放在服務主機與監控主機以外(通常是第三方的外部探測服務)。外部探測成功而監控讀不到,指向兩地之間的連線;兩者都失敗,指向服務主機。這個配置裡量使用者路徑的也是這個外部探測:監控主機經由 VPN 讀取,不走使用者用的公開網址(見 4.28 單台主機的監控盲區與失效域以外的觀察者 的〈外部探測:使用者看到的結果〉)。主機停機告警的說明因此寫上「也可能是監控主機連不過去」,值班的人收到時就知道有這兩種成因。
兩台主機互相監看
監控主機發現服務主機停掉;監控主機自己停掉時,要由服務主機發現。服務主機上跑一個心跳監看程式(dead man’s switch,例如自架的 healthchecks,或一個記下最後一次心跳、超過週期加寬限就通知的小程式),接收監控主機上 Alertmanager 送出的 Watchdog 心跳。自架 healthchecks 時,ping 網址、/fail、Alertmanager 的路由與暫停的用法與第三方版相同,見 第三方心跳監看與外部探測服務,網址換成服務主機的 VPN 位址。任一台停掉,另一台都會發現,心跳監看不必用第三方服務。
心跳監看本身的條件(見 4.28 單台主機的監控盲區與失效域以外的觀察者 的〈心跳監看:監控與送出心跳的那條路還在不在運作〉,例如送出間隔小於週期加寬限)之外,互相監看另外要滿足三個條件:
- 心跳的路徑也走 VPN。 監看程式的接收端點不對網際網路開放;知道心跳網址的人可以送出假的心跳,也可以送出假的失敗。代價是 VPN 斷線時這裡也會誤報:監看程式收不到心跳,寄出「監控主機停了」,同時監控主機讀不到服務主機,發出「服務主機停了」。兩邊的停機通知同時出現,指向的是兩地之間的連線;兩台都停掉時,第三方的外部探測也會失敗,外部探測成功就是連線的問題。
- 監看程式的通知不經過監控主機。 它直接寄 email(經由業者的郵件服務或一個寄送系統通知信的郵件服務,transactional email),或送到另一個推播服務,不送給監控主機上的 Alertmanager 或 ntfy。監控主機停掉時,經過它的通知一起停。
- 監看程式自己有人盯著。 它停掉時不會有任何訊號。它跑在服務主機上,監控主機以 blackbox_exporter 這類探測程式對它的網頁發 HTTP 請求,它停掉時由監控主機的告警通知;Prometheus 讀取 exporter 的方式讀的是指標格式,讀不了一般的網頁或健康端點。這條告警要同時涵蓋探測程式自己讀不到的情形:只寫
probe_success == 0時,blackbox_exporter 自己停掉,這個序列跟著消失,告警不會成立,所以另外以up == 0(或序列消失)告警。
剩下的風險是兩台同時停掉,這時沒有任何一方送得出通知。兩台在不同的業者時,同時停掉的機會小;兩台主機共用的依賴會讓這種情形比預期常見,例如兩台的通知都經由同一個郵件服務寄出、兩台的網域都在同一個 DNS 業者。兩台是否分屬不同失效域,看逐項比對依賴之後還剩下哪些共用項(比對方式見 Failure Domain 的〈判讀方式〉)。要涵蓋兩台同時停掉,〈監控主機與服務主機之間的連線與連線中斷的誤判〉裡用來分辨連線中斷的第三方外部探測服務,同時就是兩台主機以外的觀察者。
第三台主機的用法:給資料庫,或兩台應用主機搭配託管資料庫
應用主機、資料庫主機與監控主機。 第三台給資料庫,應用程式與資料庫各一台,兩台在同一個業者、同一個區域,以私有網路連線;監控主機維持在另一個業者或地端。這個配置處理資源競爭,可用性與只有服務主機加監控主機時相同:應用主機或資料庫主機任一台停掉,服務中斷。適合的情形是監控已經分出去、而效能問題來自應用程式與資料庫搶資源。
兩台應用主機加監控主機。 兩台各跑一份應用程式,前面放業者的負載平衡器,監控主機在另一個失效域。資料庫不能放在任何一台應用主機上,所以要用業者的託管資料庫(自架與託管的取捨見 自架還是託管:以資料庫為例)。這個配置提高可用性:一台應用主機停掉時服務照常,監控主機發出告警,通知值班的人有一台應用主機停了。代價是託管資料庫的費用,以及應用程式要先做到無狀態、排程工作只在一台執行。兩台應用主機放在同一個業者的不同可用區,才不會因為同一個機房的故障一起停;它們仍共用同一個帳號、業者的負載平衡器、同一條部署管線(一次壞掉的部署同時打到兩台,見 Correlated Failure)與同一組 DNS,這幾項是可用區分開之後剩下的共用依賴;託管資料庫本身也落在某個可用區,只開在單一可用區時,那個機房故障仍會讓服務中斷,跨可用區的設定與費用查自己那一家的文件。active-passive(備援平時不接流量)與 active-active(兩份同時接流量)的選擇見 冗餘設計模式。
不論第三台怎麼用,備份都維持在應用與資料庫的失效域以外(見〈一台主機承擔的風險〉):資料庫搬到第三台或託管服務之後,備份的來源跟著換,備份的目的地仍是另一個業者或另一個帳號。
雲端業者負責的範圍與自己負責的範圍
主機數處理的是主機層的故障:一台停掉時另一台接手、監控不與服務一起停。業者對硬體與電力的故障處理到「讓虛擬機恢復」為止:在另一台實體機上重新啟動虛擬機,中間的停機仍由使用者處理:要不中斷就要另一台實體機上的副本(機房層級的故障要放在另一個可用區),那正是第二台主機給應用程式的副本時換到的保護(可用區見〈第三台主機的用法:給資料庫,或兩台應用主機搭配託管資料庫〉)。應用層的攻擊則是加主機也換不到保護的一類:它打在每一台都相同的入口上,主機再多也一樣進得來。這兩類由雲端業者與使用者之間的責任分擔模型(shared responsibility model)分工:業者處理所有客戶共用的部分,依應用而不同的部分留給使用者。
| 項目 | 業者處理的部分 | 使用者處理的部分 |
|---|---|---|
| 實體機房、硬體、電力 | 硬體故障時把虛擬機搬到其他實體機重新啟動 | 服務能自己啟動回來(容器的重啟策略、開機自動啟動);恢復期間的中斷靠另一台實體機上的副本;本機磁碟的資料是否保留依業者與磁碟種類而定,查自己那一家 |
| 網路層的大流量攻擊(L3/L4 的 DDoS) | 主要的雲端業者在網路邊緣提供基本防護(例如 AWS Shield Standard 對所有客戶生效) | 查清楚自己那一家超過防護容量時怎麼處置(部分業者會把被攻擊的 IP 暫時移出網路);需要更強的防護時另外購買或加一層 CDN |
| 應用層的攻擊(HTTP 洪水、掃描、撞密碼 credential stuffing) | 預設不處理 | 入口的限流、CDN 與 WAF、應用程式本身的偵測 |
| 流量突增 | 用了自動擴充群組或託管服務時,依使用者設定的策略擴充 | 設定擴充的策略與上下限;單台虛擬機不會自己變大,沒有擴充時要預留容量 |
| 作業系統、應用程式、設定、資料、帳號(付款、權限、停權的風險)與金鑰 | 不處理 | 全部 |
表裡「服務能自己啟動回來」這一項,業者在硬體維護後重新啟動虛擬機時要成立,主機裝了核心更新、排程自動重開機時也要成立。要確認的是服務的 unit 處在 enabled、容器設了 restart policy、主機防火牆的規則寫進了永久設定,最可靠的確認是在有人在場的維護時段手動重開一次,見 長期上線主機的維護項目:會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算〈會老化的軟體:各條更新管道與更新後的重開機〉。
網路層攻擊的處置(流量清洗、邊緣網路)見 9.15 無預警瞬時大流量 的〈體積型攻擊(DDoS)的處置:流量清洗與邊緣網路〉。以短網址服務為例,最可能遇到的攻擊是以正常的 HTTPS 請求大量掃描短碼、對轉址路徑灌請求。這些請求在網路層與一般使用者相同,業者的網路層防護不會攔下,要由入口的限流與掃描偵測處理;前面加一層 CDN 或 WAF,是把應用層的第一道過濾交給 CDN 業者,規則仍然要自己設定。各業者的防護範圍與處置方式寫在各自的服務條款與文件裡,選用前查自己那一家。
延伸閱讀:主機只有一台時、觀察者改由第三方提供時、exporter 的接法與冗餘的選擇
- 還沒有第二台主機、服務與監控在同一台時看不到哪些故障,心跳監看、外部探測與主機內部探測並排時怎麼判讀,見 4.28 單台主機的監控盲區與失效域以外的觀察者。
- 心跳監看或外部探測改由第三方服務提供時,挑選條件與 Alertmanager 的接法,見 第三方心跳監看與外部探測服務;自架 healthchecks 時同一篇的 ping 網址、
/fail與暫停也適用。 - 監控主機與服務主機之間的 VPN 要開到多寬、限制放在哪一端才在雲端主機被入侵之後仍然成立、異地備份的存取金鑰要拿掉哪些權限,見 7.43 兩個網路之間的連線安全。
- 監控主機已經分出去、要把各主機的 exporter 接上時,綁定位址、目標清單、主機標籤與主機消失的偵測,見 多台主機的指標收集。
- 決定做應用程式的副本之後,要選 active-passive 還是 active-active、資料怎麼同步,見 冗餘設計模式;每多一台要花多少、值不值得,見 高可用的成本。