文档
一个 项目

架构

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

清理阶段

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

插入

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

管理配置

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

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

你可以在 这里观看关于 Caddy 2 设计的视频

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

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

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

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

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