文档
一个 项目,由 zerossl.com 提供支持

架构

Caddy 是一个独立的、自包含的静态二进制文件,零外部依赖,因为它使用 Go 语言编写。这些特性构成了项目愿景的重要部分,因为它们简化了部署,并减少了生产环境中繁琐的故障排除。

如果没有动态链接,那么如何扩展它呢?Caddy 采用了一种新颖的插件架构,使其功能远远超出任何其他 Web 服务器,甚至包括那些具有外部(动态链接)依赖项的服务器。

我们“更少活动部件”的理念最终带来了更可靠、更易于管理、成本更低的站点——尤其是在大规模部署时。这份半技术性文档描述了我们如何通过软件工程实现这一目标。

概述

Caddy 由命令、核心库和模块组成。

命令提供了您可能熟悉的命令行界面。它是您从操作系统启动进程的方式。这里的代码量和逻辑相当少,只包含以用户期望的方式引导核心所需的内容。我们有意避免使用标志和环境变量进行配置,除非它们与引导配置有关。

核心库,或 Caddy 的“核心”,主要管理配置。它可以 Run() 一个新配置或 Stop() 一个运行中的配置。它还为模块提供各种实用程序、类型和值以供使用。

模块完成所有其他工作。许多模块内置于 Caddy 中,这些模块被称为标准模块。这些模块被认为是对于大多数用户最有用的。

Caddy 核心

在其核心,Caddy 仅仅加载一个初始配置(“config”),或者,如果没有配置,则打开一个套接字以稍后接受新配置。

Caddy 配置是一个 JSON 文档,在其顶层有一些字段

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

Caddy 核心知道如何本地处理其中一些字段

但其他顶层字段(例如 apps)对于 Caddy 核心是不透明的。实际上,Caddy 对 apps 中的字节所做的全部操作就是将它们反序列化为接口类型,然后可以对该接口类型调用两个方法

  1. Start()
  2. Stop()

... 就这些。当加载配置时,它在每个应用上调用 Start(),当卸载配置时,它在每个应用上调用 Stop()

当应用模块启动时,它会启动应用的模块生命周期。

模块生命周期

模块有两种类型:宿主模块访客模块

宿主模块(或“父”模块)是那些加载其他模块的模块。

访客模块(或“子”模块)是那些被加载的模块。所有模块都是访客模块——甚至应用模块也是。

模块按照以下顺序加载、配置和验证、使用,然后清理

  1. 加载
  2. 配置和验证
  3. 使用
  4. 清理

当加载配置时,Caddy 首先通过初始化所有配置的应用模块来启动模块生命周期。从那里开始,就像“乌龟叠罗汉”一样,每个应用模块都负责其余部分。

加载阶段

加载模块涉及将其 JSON 字节反序列化为内存中的类型化值。基本上就是这样。它只是将 JSON 解码为值。

配置阶段

此阶段是大部分设置工作进行的地方。所有模块在加载后都有机会配置自己。

由于 JSON 编码中的任何属性都已被解码,因此只需要进行额外的设置。配置期间最常见的任务是设置访客模块。换句话说,配置宿主模块也会导致配置其访客模块,一直向下。

您可以通过浏览我们文档中的 Caddy JSON 结构来了解这一点。您看到的任何 {•••} 都是可能使用访客模块的地方;当您点击进入一个时,您可以继续向下探索,直到没有更多访客模块为止。

其他常见的配置任务是设置将在模块生命周期内使用的内部值,或标准化输入。例如,http.matchers.remote_ip 模块使用配置阶段从其从 JSON 接收的字符串输入中解析 CIDR 值。这样,它不必在每个 HTTP 请求期间都执行此操作,因此效率更高。

验证也可以在配置阶段进行。如果模块的最终配置无效,则可以在此处返回错误,这将中止整个配置加载过程。

使用阶段

一旦访客模块被配置和验证,它就可以被其宿主模块使用。这具体意味着什么取决于每个宿主模块。

每个模块都有一个 ID,该 ID 由命名空间和该命名空间中的名称组成。例如,http.handlers.reverse_proxy 是一个 HTTP 处理程序,因为它位于 http.handlers 命名空间中,并且其名称为 reverse_proxyhttp.handlers 命名空间中的所有模块都满足相同接口,宿主模块知道该接口。因此,http 应用知道如何加载和使用这些类型的模块。

清理阶段

当需要停止配置时,所有模块都会被卸载。如果模块分配了任何应该释放的资源,它有机会在清理阶段执行此操作。

插入

模块——或任何 Caddy 插件——通过为模块的包添加 import 来“插入”到 Caddy 中。通过导入包,模块将其自身注册到 Caddy 核心,因此当 Caddy 进程启动时,它会按名称知道每个模块。它甚至可以在模块值和名称之间以及反之亦然地进行关联。

管理配置

更改运行中服务器的活动配置(通常称为“重新加载”)对于服务器所需的高并发性和数千个参数来说可能很棘手。Caddy 使用一种具有许多优点的设计优雅地解决了这个问题

  • 运行中服务不会中断
  • 可以进行细粒度的配置更改
  • 只需要一个锁(在后台)
  • 所有重新加载都是原子性、一致性、隔离性和大部分持久性 (“ACID”) 的
  • 最小的全局状态

您可以在 此处观看关于 Caddy 2 设计的视频

配置重新加载的工作原理是配置新模块,如果一切成功,则清理旧模块。在短暂的时间内,两个配置同时运行。

每个配置都与一个 上下文 相关联,该上下文保存所有模块状态,因此大多数状态永远不会逃脱配置的范围。这对正确性、性能和简洁性来说是个好消息!

但是,有时真正需要全局状态。例如,反向代理可能会跟踪其上游的健康状况;由于每个上游在全球范围内只有一个,因此如果每次进行微小的配置更改都忘记它们,那将是不好的。幸运的是,Caddy 提供了类似于 语言运行时垃圾回收器的工具来保持全局状态整洁。

在线配置更新的一种显而易见的方法是同步对每个配置参数的访问,即使在热路径中也是如此。这在性能和复杂性方面都非常糟糕——尤其是在大规模部署时——因此 Caddy 不使用这种方法。

相反,配置被视为不可变的原子单元:要么替换整个配置,要么什么都不改变。admin API 端点——允许通过遍历结构进行细粒度更改——仅改变配置的内存表示,从中生成并加载全新的配置文档。这种方法在简洁性、性能和一致性方面具有巨大的优势。由于只有一个锁,Caddy 可以轻松处理快速重新加载。