文档
一个 项目

架构

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

如果没有动态链接,它如何扩展?Caddy 拥有一个新颖的插件架构,可以扩展其功能,远远超过任何其他 Web 服务器,即使是那些具有外部(动态链接)依赖项的服务器。

我们“减少活动部件”的理念最终导致更可靠、更易于管理、成本更低的网站,尤其是在规模化的情况下。这份半技术文档描述了我们如何通过软件工程实现这一目标。

概述

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

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

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

模块执行所有其他操作。许多模块内置于 Caddy 中,称为标准模块。这些被认为对大多数用户最有帮助。

Caddy 核心

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

一个 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,它由命名空间和该命名空间中的名称组成。例如,http.handlers.reverse_proxy 是一个 HTTP 处理程序,因为它位于 http.handlers 命名空间中,它的名称是 reverse_proxyhttp.handlers 命名空间中的所有模块都满足主机模块已知的相同接口。因此,http 应用程序知道如何加载和使用这些类型的模块。

清理阶段

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

插入

模块(或任何 Caddy 插件)通过添加模块包的 import 来“插入”到 Caddy 中。通过导入包,模块向 Caddy 核心注册自己,因此当 Caddy 进程启动时,它会按名称识别每个模块。它甚至可以在模块值和名称之间进行关联,反之亦然。

管理配置

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

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

您可以 观看有关 Caddy 2 设计的视频

配置重新加载通过配置新模块来工作,如果所有模块都成功,则清理旧模块。在短暂的时间内,两个配置同时运行。

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

但是,有时确实需要全局状态。例如,反向代理可能会跟踪其上游的运行状况;由于每个上游只有一个,如果它每次进行微小的配置更改时就忘记它们,那就太糟糕了。幸运的是,Caddy 提供了类似于语言运行时垃圾收集器的工具 来保持全局状态整洁。

对在线配置更新的一种明显方法是对每个配置参数进行同步访问,即使是在热路径中也是如此。从性能和复杂性的角度来看,这非常糟糕,尤其是在规模化的情况下,因此 Caddy 不使用这种方法。

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