文件
一個 專案

架構

Caddy 是單一、獨立、靜態的二進位檔,沒有任何外部依賴,因為它是使用 Go 編寫的。這些價值觀是專案願景的重要部分,因為它們簡化了部署,並減少了生產環境中繁瑣的疑難排解。

如果沒有動態連結,那麼如何擴充呢?Caddy 採用新穎的外掛程式架構,將其功能擴充到遠遠超過其他任何網路伺服器,即使是那些具有外部(動態連結)依賴項的伺服器。

我們「減少活動部分」的理念最終會帶來更可靠、更易於管理、成本更低的網站,特別是在擴充時。這份半技術文件說明了我們如何透過軟體工程來達成此目標。

概述

Caddy 包含一個命令、核心函式庫和模組。

命令提供 命令列介面,您應該很熟悉。這是您從作業系統啟動處理程序的方式。這裡的程式碼和邏輯數量相當少,只有引導核心以使用者所需方式所需的內容。我們故意避免使用旗標和環境變數進行設定,除非它們與引導設定有關。

核心函式庫,或 Caddy 的「核心」,主要管理組態。它可以 Run() 新組態或 Stop() 正在執行的組態。它也提供各種實用程式、類型和值供模組使用。

模組執行所有其他工作。許多模組內建於 Caddy 中,稱為標準模組。這些模組被認為對大多數使用者最有幫助。

Caddy 核心

在核心方面,Caddy 僅載入初始組態(「config」),或者,如果沒有初始組態,則開啟一個 socket 以便稍後接受新組態。

Caddy 組態 是 JSON 文件,其頂層有一些欄位

{
	"admin": {},
	"logging": {},
	"apps": {•••},
	...
}

Caddy 核心知道如何原生處理其中一些欄位

但是其他頂層欄位(例如 apps)對 Caddy 核心來說是不透明的。事實上,Caddy 唯一知道如何處理 apps 中的位元組的方式是將它們反序列化為介面類型,以便在該類型上呼叫兩個方法

  1. Start()
  2. Stop()

... 僅此而已。當載入組態時,它會對每個應用程式呼叫 Start(),當卸載組態時,它會對每個應用程式呼叫 Stop()

當應用程式模組啟動時,它會啟動應用程式的模組生命週期。

模組生命週期

有兩種模組:主機模組訪客模組

主機模組(或「父代」模組)是載入其他模組的模組。

訪客模組(或「子代」模組)是會被載入的模組。所有模組都是訪客模組,即使是應用程式模組也是如此。

模組會依此順序載入、配置和驗證、使用,然後清理

  1. 載入
  2. 配置和驗證
  3. 使用
  4. 清理

當組態首次載入時,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_proxyhttp.handlers 名稱空間中的所有模組都滿足 host 模組所知的相同介面。因此,http 應用程式知道如何載入和使用這些類型的模組。

清除階段

當設定需要停止時,所有模組都會卸載。如果模組配置了任何應釋放的資源,它可以在清理階段執行此操作。

外掛

模組(或任何 Caddy 外掛程式)透過新增模組套件的 import 來「外掛」到 Caddy。透過匯入套件,模組會向 Caddy 核心註冊自身,因此當 Caddy 程序啟動時,它會以名稱認識每個模組。它甚至可以在模組值與名稱之間建立關聯,反之亦然。

管理設定

變更正在執行的伺服器之活動設定(通常稱為「重新載入」)可能會很棘手,因為伺服器需要高度並行性和數千個參數。Caddy 使用具有許多好處的設計優雅地解決了這個問題

  • 不中斷正在執行的服務
  • 可以進行細微的設定變更
  • 只需要一個鎖定(在背景中)
  • 所有重新載入都是原子性的、一致的、孤立的,而且大多數都是持久的(「ACID」)
  • 最小化全域狀態

你可以 觀看 Caddy 2 設計的影片

設定重新載入透過配置新模組來運作,如果全部成功,舊模組就會清理乾淨。在短暫的時間內,兩個設定會同時運作。

每個設定都與 context 關聯,其中包含所有模組狀態,因此大多數狀態都不會超出設定的範圍。這對於正確性、效能和簡潔性來說都是好消息!

然而,有時確實需要全域狀態。例如,反向代理可能會追蹤其上游的健康狀況;由於每個上游在全球只有一個,因此如果每次進行微小的設定變更時都忘記它們,這將很糟糕。幸運的是,Caddy 提供了類似於語言執行時期的垃圾收集器的設施,以保持全域狀態井然有序。

線上設定更新的一個顯而易見的方法是同步存取每個設定參數,即使在熱路徑中也是如此。這在效能和複雜性方面令人難以置信的糟糕,尤其是在規模方面,因此 Caddy 不使用這種方法。

相反地,設定被視為不可變的原子單元:要嘛全部替換,要嘛什麼都不變更。 管理 API 端點(透過深入結構允許細微變更)只會變異設定的記憶體中表示,從中會產生並載入一個全新的設定文件。這種方法在簡潔性、效能和一致性方面具有巨大的好處。由於只有一個鎖定,因此 Caddy 可以輕鬆處理快速重新載入。