架構
Caddy 是單一、獨立、靜態的二進位檔,沒有任何外部依賴,因為它是使用 Go 編寫的。這些價值觀是專案願景的重要部分,因為它們簡化了部署,並減少了生產環境中繁瑣的疑難排解。
如果沒有動態連結,那麼如何擴充呢?Caddy 採用新穎的外掛程式架構,將其功能擴充到遠遠超過其他任何網路伺服器,即使是那些具有外部(動態連結)依賴項的伺服器。
我們「減少活動部分」的理念最終會帶來更可靠、更易於管理、成本更低的網站,特別是在擴充時。這份半技術文件說明了我們如何透過軟體工程來達成此目標。
概述
Caddy 包含一個命令、核心函式庫和模組。
命令提供 命令列介面,您應該很熟悉。這是您從作業系統啟動處理程序的方式。這裡的程式碼和邏輯數量相當少,只有引導核心以使用者所需方式所需的內容。我們故意避免使用旗標和環境變數進行設定,除非它們與引導設定有關。
核心函式庫,或 Caddy 的「核心」,主要管理組態。它可以 Run()
新組態或 Stop()
正在執行的組態。它也提供各種實用程式、類型和值供模組使用。
模組執行所有其他工作。許多模組內建於 Caddy 中,稱為標準模組。這些模組被認為對大多數使用者最有幫助。
Caddy 核心
在核心方面,Caddy 僅載入初始組態(「config」),或者,如果沒有初始組態,則開啟一個 socket 以便稍後接受新組態。
Caddy 組態 是 JSON 文件,其頂層有一些欄位
{
"admin": {},
"logging": {},
"apps": {•••},
...
}
Caddy 核心知道如何原生處理其中一些欄位
但是其他頂層欄位(例如 apps
)對 Caddy 核心來說是不透明的。事實上,Caddy 唯一知道如何處理 apps
中的位元組的方式是將它們反序列化為介面類型,以便在該類型上呼叫兩個方法
Start()
Stop()
... 僅此而已。當載入組態時,它會對每個應用程式呼叫 Start()
,當卸載組態時,它會對每個應用程式呼叫 Stop()
。
當應用程式模組啟動時,它會啟動應用程式的模組生命週期。
模組生命週期
有兩種模組:主機模組和訪客模組。
主機模組(或「父代」模組)是載入其他模組的模組。
訪客模組(或「子代」模組)是會被載入的模組。所有模組都是訪客模組,即使是應用程式模組也是如此。
模組會依此順序載入、配置和驗證、使用,然後清理
- 載入
- 配置和驗證
- 使用
- 清理
當組態首次載入時,Caddy 會透過初始化所有已設定的應用程式模組來啟動模組生命週期。從那裡開始,每個應用程式模組都會一路執行下去,就像烏龜一樣。
載入階段
載入模組包括將其 JSON 位元組反序列化為記憶體中的類型化值。基本上就是這樣。它只是將 JSON 解碼為一個值。
準備階段
此階段是進行大部分設定工作的地方。所有模組在載入後都會有機會準備自己。
由於 JSON 編碼中的任何屬性都已經被解碼,因此這裡只需要進行額外的設定。準備過程中最常見的任務是設定 guest 模組。換句話說,準備 host 模組也會準備其 guest 模組,一直到最底層。
您可以透過在我們的文件瀏覽 Caddy 的 JSON 結構來了解這一點。在任何您看到 {•••}
的地方都可以使用 guest 模組;當您點選其中一個時,您可以繼續探索,直到沒有更多 guest 模組為止。
其他常見的準備任務是設定模組生命週期中將使用的內部值,或標準化輸入。例如,http.matchers.remote_ip
模組使用準備階段從 JSON 接收的字串輸入中解析 CIDR 值。這樣,它就不必在每個 HTTP 請求期間執行此操作,因此效率更高。
驗證也可以在準備階段進行。如果模組產生的設定無效,則可能會在此處傳回錯誤,這會中止整個設定載入程序。
使用階段
一旦 guest 模組準備好並驗證,它就可以被其 host 模組使用。這具體是什麼意思取決於每個 host 模組。
每個模組都有 ID,它包含一個名稱空間和該名稱空間中的名稱。例如,http.handlers.reverse_proxy
是 HTTP 處理器,因為它在 http.handlers
名稱空間中,其名稱為 reverse_proxy
。http.handlers
名稱空間中的所有模組都滿足 host 模組所知的相同介面。因此,http
應用程式知道如何載入和使用這些類型的模組。
清除階段
當設定需要停止時,所有模組都會卸載。如果模組配置了任何應釋放的資源,它可以在清理階段執行此操作。
外掛
模組(或任何 Caddy 外掛程式)透過新增模組套件的 import
來「外掛」到 Caddy。透過匯入套件,模組會向 Caddy 核心註冊自身,因此當 Caddy 程序啟動時,它會以名稱認識每個模組。它甚至可以在模組值與名稱之間建立關聯,反之亦然。
管理設定
變更正在執行的伺服器之活動設定(通常稱為「重新載入」)可能會很棘手,因為伺服器需要高度並行性和數千個參數。Caddy 使用具有許多好處的設計優雅地解決了這個問題
- 不中斷正在執行的服務
- 可以進行細微的設定變更
- 只需要一個鎖定(在背景中)
- 所有重新載入都是原子性的、一致的、孤立的,而且大多數都是持久的(「ACID」)
- 最小化全域狀態
你可以 觀看 Caddy 2 設計的影片。
設定重新載入透過配置新模組來運作,如果全部成功,舊模組就會清理乾淨。在短暫的時間內,兩個設定會同時運作。
每個設定都與 context 關聯,其中包含所有模組狀態,因此大多數狀態都不會超出設定的範圍。這對於正確性、效能和簡潔性來說都是好消息!
然而,有時確實需要全域狀態。例如,反向代理可能會追蹤其上游的健康狀況;由於每個上游在全球只有一個,因此如果每次進行微小的設定變更時都忘記它們,這將很糟糕。幸運的是,Caddy 提供了類似於語言執行時期的垃圾收集器的設施,以保持全域狀態井然有序。
線上設定更新的一個顯而易見的方法是同步存取每個設定參數,即使在熱路徑中也是如此。這在效能和複雜性方面令人難以置信的糟糕,尤其是在規模方面,因此 Caddy 不使用這種方法。
相反地,設定被視為不可變的原子單元:要嘛全部替換,要嘛什麼都不變更。 管理 API 端點(透過深入結構允許細微變更)只會變異設定的記憶體中表示,從中會產生並載入一個全新的設定文件。這種方法在簡潔性、效能和一致性方面具有巨大的好處。由於只有一個鎖定,因此 Caddy 可以輕鬆處理快速重新載入。