3. Caddy使用手册
在过去几年里,caddy 取代了 nginx,成为了我的反向代理心头好。起因是当时给一个内部服务做反向代理时,nginx 跑满 CPU,定位下来发现 nginx 使用 OpenSSL 提供 TLS 加解密,而且 OpenSSL 在 TLS 1.3 中硬编码优先使用 AES-GCM,恰巧 nginx 宿主机中未提供 AES 指令集,软件实现的 AES 资源消耗过大导致 CPU 满载,要继续使用 nginx 的话只能修改 OpenSSL 来设置 ChaCha20-Poly1305 为首选算法。不过当时测试 caddy 时未出现 CPU 满载现象,于是拿它替换了 nginx 解决问题。后面研究 TLS 1.3 时,确定是 golang 内置 TLS 组件会检测 CPU 指令集来修改 TLS 1.3 使用的加密算法,AES 指令集缺失时,优先使用软件实现效率较高的 ChaCha20-Poly1305。
在使用 caddy 的这几年中,积累了一些实践,这里就将一些常用操作记录下来,以下配置以 caddy 2.8.4 版本为准。
我习惯使用容器部署 caddy,开启宿主机网络模式,并添加 NET_ADMIN 权限,如下是一个运行 caddy 的 run.sh 文件:
#!/bin/bash
# 容器镜像
image="wbuntu/caddy:2.8.4"
# 容器名称
container="caddy"
# 检查容器是否存在
if [ "$(docker ps -a -q -f name=$container)" ]; then
# 如果容器未处于停止状态,则停止容器
if [ "$(docker ps -q -f name=$container)" ]; then
docker stop $container
fi
# 删除容器
docker rm $container
fi
# 启动容器: 容器名称-> 是否后台运行 -> 重启策略 -> 网络类型 -> 环境变量 -> 挂载项 -> 镜像名
docker run --name $container -d \
--restart unless-stopped \
--network host \
--cap-add=NET_ADMIN \
-v $PWD/Caddyfile:/etc/caddy/Caddyfile \
-v $PWD/root:/usr/share/caddy \
-v $PWD/log:/var/log \
-v $PWD/config:/config \
-v $PWD/data:/data \
$image
这里有以下几点考虑:
- 使用宿主机网络,并启用 NET_ADMIN:避免 NAT 转换,caddy 默认占用 80 TCP、443 TCP、443 UDP 端口,其中 443 UDP 端口用于 HTTP/3,需要 NET_ADMIN 权限来修改系统的 UDP 缓存限制,如果不需要 HTTP/3 的话可以不添加这个权限
- 将当前目录下的文件和文件夹映射到容器内,主要有:
- Caddyfile:caddy 配置文件,直接映射到容器内的 /etc/caddy/Caddyfile 方便修改
- root:web 服务默认根目录,可以用来存储公共文件,执行这个命令拷贝容器内目录下的 index.html 到本地目录
docker run --rm -v $PWD/root:/host_root caddy:2.8.4 sh -c "cp /usr/share/caddy/index.html /host_root/index.html"
- log:日志目录
- config:JSON 格式的配置文件保存目录,Caddyfile 验证有效后,caddy 才会转换配置再保存到该目录下
- data:数据存储,存放实例 ID、证书等文件,建议持久化,避免 caddy 重启时反复申请证书
另一个 reload.sh 脚本可用于格式化并重载配置文件:
#!/bin/bash
# 容器名称
container="caddy"
# 格式化命令
fmt="caddy fmt --overwrite /etc/caddy/Caddyfile"
# 重载命令
reload="caddy reload --config /etc/caddy/Caddyfile --force"
# 执行命令
docker exec $container $fmt
docker exec $container $reload
caddy 使用名为 Caddyfile 的配置文件,它采用简洁的声明式语法,熟悉 nginx 的人应该不会感到陌生。
我常用的配置文件如下:
{
servers :443 {
protocols h1 h2
}
}
test.wbuntu.com {
tls /etc/cert/wbuntu.crt /etc/cert/wbuntu.key
log {
output file /var/log/test.log
}
root * /usr/share/caddy
file_server
}
首先在全局配置中定义 443 端口的服务只使用 HTTP/1.1 和 HTTP/2 协议(因为 HTTP/3 使用 UDP 协议,在国内访问不太稳定,而且会占用宿主机 443 UDP 端口),然后定义了一个站点 test.wbuntu.com,使用自己提供的 HTTPS 证书和密钥,站点日志输出到 /var/log/test.log,web 服务目录使用 /usr/share/caddy。我常用这样的配置来创建一个用于中转文件的静态文件服务器。
完整的配置参数较多,参考文档如下:
- 全局配置参数:Global options。
- 站点配置:Caddyfile Directives。
- 需要匹配请求做特殊处理时:Request Matchers。
- 常用的 Caddyfile 模板:Common Caddyfile Patterns。
caddy 一直提倡模块化,本体代码和第三方插件都是模块化实现的,我们可以使用容器编译 caddy,添加自己需要的模块,例如下面基于 caddy 2.8.4 版本添加支持 WebDAV 的模块:
ARG TARGETARCH
ARG VERSION
FROM --platform=$BUILDPLATFORM caddy:${VERSION}-builder AS builder
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=${TARGETARCH}
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} xcaddy build ${VERSION} \
--with github.com/mholt/caddy-webdav
FROM --platform=$TARGETPLATFORM caddy:${VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
使用 buildx 构建双架构镜像:
docker buildx build --platform linux/amd64,linux/arm64 --build-arg VERSION=2.8.4 -t wbuntu/caddy:2.8.4 .
运行容器可以检查版本与模块:
➜ ~ docker run --rm wbuntu/caddy:2.8.4 caddy --version
v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=
➜ ~ docker run --rm wbuntu/caddy:2.8.4 caddy list-modules | grep dav
http.handlers.webdav
记得在 caddy 最初做推广的时候,自动证书管理就是一个亮点功能,假如我们已经将域名解析到服务器 IP,然后使用以下配置就能自动触发证书签发:
test.wbuntu.com {
log {
output file /var/log/test.log
}
root * /usr/share/caddy
file_server
}
签发的证书默认单域名,有效期 90 天,容易踩坑的点是:先启动 caddy,然后再去做域名解析(或触发其他导致无法解析域名的问题),当 caddy 多次请求证书机构签发证书失败后,服务器会被证书签发机构限制请求。
建议手动申请通配符证书:CNCF笔记 / 使用K3s部署容器化应用 / 2. 基础配置 / 2.1 通配符证书。
参考文档:
当我们需要一个简单的静态文件服务器时,使用以下配置即可:
test.wbuntu.com {
log {
output file /var/log/test.log
}
root * /usr/share/caddy
file_server browse
}
file_server 指令增加 browse,允许列出文件,这样访问网页时会展示一个默认的文件浏览器:
为了安全起见,可以添加 basic_auth 要求使用账号密码访问:
test.wbuntu.com {
log {
output file /var/log/test.log
}
root * /usr/share/caddy
basic_auth {
username hashed_password
}
file_server browse
}
basic_auth 指令中可以设置多个用户,每一行的格式为 用户名 密码的哈希
,假设用户名为 user,密码为 password,可以使用 caddy hash-password
生成密码哈希值:
➜ docker run --rm wbuntu/caddy:2.8.4 caddy hash-password -p password
$2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2
得到 password 字符串的哈希值 $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2,完整配置如下:
test.wbuntu.com {
log {
output file /var/log/test.log
}
root * /usr/share/caddy
basic_auth {
username $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2
}
file_server browse
}
重载配置后再访问网页就需要填写用户名和密码:
参考文档:
caddy 使用 reverse_proxy 指令提供反向代理功能,常用的配置如下:
test.wbuntu.com {
log {
output file /var/log/test.log
}
root * /usr/share/caddy
file_server browse
reverse_proxy /HTTPEndpoint http://localhost:8080
reverse_proxy /HTTPEndpointWildcard/* http://localhost:8081
reverse_proxy /HTTPSEndpoint https://localhost:8082
reverse_proxy /HTTPSEndpointInsecure https://localhost:8083 {
transport http {
tls_insecure_skip_verify
}
}
reverse_proxy /H2CEndpoint h2c://localhost:8084
}
reverse_proxy 指令可以按 Request Matchers 匹配请求再转发到指定后端,我常用 URL 中的 PATH 来匹配请求,上面的配置中包含了常用的 HTTP 后端、HTTPS 后端、忽略证书验证的 HTTPS 后端以及 H2C 后端。
我们还可以针对后端设置头部和 PATH 重写、健康检查策略、负载均衡策略等,将 caddy 制作成一个七层负载均衡器,详细内容可以翻阅参考文档。
参考文档:
WebDAV(Web Distributed Authoring and Versioning)是一种基于 HTTP 协议的扩展,让我们可以通过 HTTP 协议挂载网络存储来共享文件,我常用的 chatgpt-next-web 也支持通过 webdav 备份配置。
我们可以编译 github.com/mholt/caddy-webdav 模块来为 caddy 添加 WebDAV 支持,下面使用一个提前编译好的容器镜像:wbuntu/caddy:2.8.4,这里会创建一个 Caddyfile,同时启用 file_browser 与 webdav,支持文件管理器和浏览器访问网络存储,并使用 basic_auth 认证用户:
{
order webdav before file_server
}
test.wbuntu.com {
encode zstd gzip
tls /etc/cert/wbuntu.crt /etc/cert/wbuntu.key
log {
output file /var/log/test.log
}
root * /webdav
basic_auth {
# username password
username $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2
}
@notget not method GET
route @notget {
webdav
}
file_server browse
}
在全局设置中,让 webdav 先于 file_server 生效,然后在站点设置中指定 /webdav 目录为根目录,接着添加用户名密码认证,然后使用 route 指令将所有非 GET 请求分流给 webdav 模块,最后通过 file_server 模块启用文件浏览。
配置完成后,在 macOS 上可以同时使用 Finder 和浏览器访问共享存储,效果如下: