こんにちは、@p1assです。
最近、運用していた Web サーバを Nginx から Caddy に移行しました。この記事では Caddy の特徴や移行してみた感想などを書きたいと思います。
What is Caddy?
Caddy はデフォルトで HTTPS に対応している OSS の Web サーバです。
- GitHub : https://github.com/caddyserver/caddy
- 公式ドキュメント : https://caddyserver.com/docs
つい先日(2020/05/04)、v2 にメジャーバージョンアップされアーキテクチャが刷新されました。
Caddy は様々な特徴があるのですがいくつかピックアップして紹介します。(v2 からの新機能というわけではないです。)
デフォルトかつ自動で HTTPS に対応
Caddy は Let's Encrypt の証明書を自動で取得し、適用してくれます。そのため、ユーザ側が証明書のことを気にする必要がありません。 また、80 から 443 へのリダイレクトも自動で行われるのでユーザが設定する必要がありません。
Nginx の場合は、これらを実装するために多くのコンフィグを書いていたことを考えると、かなりの作業を省略できます。
Nginx にあった基本的な機能をカバー
一般的なユースケースで必要となる機能は大体カバーしています。
- リバースプロキシ
- ロードバランス
- 静的ファイルのホスティング
- バーチャルホスト
- リダイレクト
- ヘッダーの付与
- ヘルスチェック
- BASIC 認証
- WebSocket
- アクセスログ
他にも面白い機能として、
- マークダウンのレンダリング
- HTTP/3 (experimental)
- デフォルトで JSON 形式で標準出力にログ出力
などもあります。
シンプルに設定を記述できる Caddyfile
Caddy の設定の反映方法は 2 通り用意されており、ユーザが用途に合わせて選べるようになってます。
1 つ目は Caddyfile
というファイルで設定する方法で、Nginx のコンフィグに似た書き方で記述できます。
# https://hoge.p1ass.comにきたリクエストをlocalhost:8080にプロキシ
hoge.p1ass.com {
reverse_proxy /* localhost:8080
}
# バーチャルホストで複数のドメインを設定できる
fuga.p1ass.com {
reverse_proxy /* localhost:3000
}
後は起動時にファイルを指定すれば完了です。
$ caddy run --config ./Caddyfile --adapter caddyfile
公式で、
Caddyfiles to be ~15% the size of a less capable nginx.conf!
と謳われているように、Nginx と似た文法ながらもシンプルに記述することが可能です。
JSON API 経由での設定変更
2 つ目は API 経由で設定をアタッチする方法です。 Caddy は 80 番と 443 番以外に 2019 番も Listen していて、管理用の API が提供されています。
例えば、現在の設定は GET /config
で取得できます。
$ curl -s localhost:2019/config | jq
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "localhost:8080"
}
]
}
],
"match": [
{
"path": [
"/*"
]
}
]
}
]
}
],
"match": [
{
"host": [
"hoge.p1ass.com"
]
}
],
"terminal": true
},
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "localhost:3000"
}
]
}
],
"match": [
{
"path": [
"/*"
]
}
]
}
]
}
],
"match": [
{
"host": [
"fuga.p1ass.com"
]
}
],
"terminal": true
}
]
}
}
}
}
}
これと同じ形式で POST /load
に JSON を投げれば動的に設定を変更できます。なお、デフォルトでは JSON ですが、Content-Type: text/caddyfile
とすればそのまま Caddyfile
を投げることもできます。
また、この設定の変更は ACID を保証しています。
Atomic: Multiple changes in a single request are treated as a single unit; any failed change aborts all the other changes.
Consistent: No invalid configurations can be loaded; your server will never break if a problem is detected at config load.
Isolated: No config changes rely on another. (It helps that HTTP is a stateless protocol!)
Durable: Caddy automatically persists the current, valid configuration to disk and can safely resume it after a power cycle if the --resume flag is used.
無効な設定が POST されたとしてもサーバが止まる心配はありません。
余談: 設定方法の使い分け
CI・CD パイプラインに組み込みたいなら API 経由での設定がオススメです。
Caddyfile
を使うか、JSON を使うかは悩みどころですが、
- 人間にとっての可視性や書きやすさを重視 →
Caddyfile
- Linter などを CI で実行したい → JSON
かなぁと個人的に思っています。
Nginx から Caddy への移行のモチベーション
このように様々な機能があるのですが、Caddy への移行の最も大きなモチベーションになったのは「HTTPS の自動対応」でした。
今までは Nginx+certbot という組み合わせで運用していたのですが、証明書のためだけにバッチ処理を走らせるのも面倒で、Nginx 単体で完結しないかなぁと思っていました。
その Nginx も HTTPS に対応するには、実際の動作とはあまり関係のない非本質的な記述を大量に書く必要があります。HTTPS が当たり前になってきたこのご時世に合ってないと感じていました。
その点、Caddy は証明書の更新は内部でやってくれて、設定の記述もシンプルです。メンテナンス性を考慮すると Caddy は非常に魅力的でした。
移行してみた感想
Caddy には Nginx のコンフィグをそのまま読み込むアダプターが存在するのですが、今回は1からで Caddyfile を書きました。
動作に関しては概ね問題ないです。セットアップが完了してから一度もプロセスは死んでないし、メモリリークなどもなさそうです。
そして当初の目論見通り、メンテナンスのしやすさは非常に向上しました。バーチャルホストを新しく切るのに 3 行追加するだけ いうのは非常に楽でした。
細かいところで言うと、 caddy fmt
で Caddyfile
のフォーマットができるのも良いポイントでした。
おわりに
- Caddy には便利な機能はたくさん揃っているので、そこまでパフォーマンスが求められない小規模・中規模環境では有用そう。
- 具体的なパフォーマンス計測をしていないので、高負荷環境での導入はちゃんと計測してからのほうが良さそう。
- ここ最近はマネージドなロードバランサーを使うことが多く、Nginx を生で使う機会が減ってきているような気がするが、これはアプリケーション寄りのサーバサイド畑にいるから感じるだけなんだろうか?