アーキテクチャ
CaddyはGoで記述されているため、外部依存関係のない、単一で自己完結型の静的バイナリです。これらの価値観は、デプロイを簡素化し、本番環境での面倒なトラブルシューティングを減らすため、プロジェクトのビジョンの重要な部分を構成しています。
動的リンクがない場合、どのように拡張できるのでしょうか?Caddyは、外部(動的リンク)依存関係を持つサーバーでさえ、他のどのWebサーバーよりもはるかにその機能を拡張する斬新なプラグインアーキテクチャを備えています。
私たちの「可動部分を減らす」という哲学は、最終的には、特に大規模なサイトにおいて、より信頼性が高く、より管理しやすく、より安価なサイトにつながります。この半技術的なドキュメントでは、ソフトウェアエンジニアリングを通じて、その目標をどのように達成するかを説明します。
概要
Caddyは、コマンド、コアライブラリ、およびモジュールで構成されています。
コマンドは、おなじみであろうコマンドラインインターフェースを提供します。これは、オペレーティングシステムからプロセスを起動する方法です。ここにあるコードとロジックの量はかなり最小限であり、ユーザーが望む方法でコアをブートストラップするために必要なものだけが含まれています。設定のブートストラップに関連する場合を除き、フラグと環境変数を設定に使用することは意図的に避けています。
コアライブラリ、またはCaddyの「コア」は、主に設定を管理します。新しい設定をRun()
したり、実行中の設定をStop()
したりできます。また、モジュールが使用するためのさまざまなユーティリティ、型、および値も提供します。
モジュールは他のすべてを行います。Caddyには多くのモジュールが組み込まれており、これらは標準モジュールと呼ばれています。これらは、ほとんどのユーザーにとって最も役立つと判断されています。
Caddyコア
Caddyは、そのコアでは、初期構成(「設定」)をロードするか、存在しない場合は、後で新しい構成を受け入れるためのソケットを開くだけです。
Caddy設定はJSONドキュメントであり、最上位にいくつかのフィールドがあります。
{
"admin": {},
"logging": {},
"apps": {•••},
...
}
Caddyのコアは、これらのフィールドの一部をネイティブに処理する方法を知っています。
ただし、他の最上位フィールド(apps
など)は、Caddyのコアにとっては不透明です。実際、Caddyがapps
内のバイトに対して行うことを知っているのは、2つのメソッドを呼び出すことができるインターフェース型にそれらをデシリアライズすることだけです。
Start()
Stop()
...以上です。設定がロードされると、各アプリでStart()
を呼び出し、設定がアンロードされると、各アプリでStop()
を呼び出します。
アプリモジュールが開始されると、アプリのモジュールライフサイクルが開始されます。
モジュールライフサイクル
モジュールには、ホストモジュールとゲストモジュールの2種類があります。
ホストモジュール(または「親」モジュール)は、他のモジュールをロードするモジュールです。
ゲストモジュール(または「子」モジュール)は、ロードされるモジュールです。すべてのモジュールは、アプリモジュールでさえ、ゲストモジュールです。
モジュールは、このシーケンスでロードされ、プロビジョニングおよび検証され、使用され、次にクリーンアップされます。
- ロード済み
- プロビジョニングおよび検証済み
- 使用済み
- クリーンアップ済み
Caddyは、構成されたすべてのアプリモジュールを最初に初期化することにより、構成がロードされたときにモジュールライフサイクルを開始します。そこから、各アプリモジュールが残りの処理を行うため、最後まで連鎖していきます。
ロードフェーズ
モジュールをロードするには、そのJSONバイトをメモリ内の型付きの値にデシリアライズする必要があります。それは...基本的にそれだけです。JSONを値にデコードするだけです。
プロビジョニングフェーズ
このフェーズでは、ほとんどのセットアップ作業が行われます。すべてのモジュールは、ロードされた後に自身をプロビジョニングする機会を得ます。
JSONエンコーディングからのプロパティはすべてすでにデコードされているため、ここで行う必要があるのは追加のセットアップのみです。プロビジョニング中の最も一般的なタスクは、ゲストモジュールをセットアップすることです。言い換えれば、ホストモジュールをプロビジョニングすると、そのゲストモジュールもすべて下位にプロビジョニングされます。
ドキュメントでCaddyのJSON構造を調べて、これを理解できます。{•••}
が表示されている場所は、ゲストモジュールを使用できる場所です。クリックすると、ゲストモジュールがなくなるまですべて下位に調べることができます。
その他の一般的なプロビジョニングタスクは、モジュールのライフサイクル中に使用される内部値を設定したり、入力を標準化したりすることです。たとえば、http.matchers.remote_ip
モジュールは、プロビジョニングフェーズを使用して、JSONから受け取った文字列入力からCIDR値を解析します。これにより、すべてのHTTPリクエスト中にこれを行う必要がなくなり、結果として効率が向上します。
検証もプロビジョニングフェーズで実行できます。モジュールの結果の設定が無効な場合、ここでエラーが返され、構成全体のロードプロセスが中止される可能性があります。
使用フェーズ
ゲストモジュールがプロビジョニングおよび検証されると、そのホストモジュールで使用できます。これが正確に何を意味するかは、各ホストモジュール次第です。
各モジュールには、ネームスペースとそのネームスペース内の名前で構成されるIDがあります。たとえば、http.handlers.reverse_proxy
は、http.handlers
ネームスペースにあり、名前がreverse_proxy
であるため、HTTPハンドラーです。http.handlers
ネームスペースのすべてのモジュールは、ホストモジュールに知られている同じインターフェースを満たしています。したがって、http
アプリは、これらの種類のモジュールをロードして使用する方法を知っています。
クリーンアップフェーズ
設定を停止する時期が来ると、すべてのモジュールがアンロードされます。モジュールが解放する必要があるリソースを割り当てた場合、クリーンアップフェーズでそれを行う機会があります。
プラグイン
モジュール(またはCaddyプラグイン)は、モジュールのパッケージのimport
を追加することにより、Caddyに「プラグイン」されます。パッケージをインポートすることにより、モジュールはCaddyコアに自身を登録するため、Caddyプロセスが開始されると、各モジュールを名前で認識します。モジュールの値と名前、およびその逆の関連付けもできます。
構成の管理
実行中のサーバーのアクティブな構成の変更(多くの場合「リロード」と呼ばれます)は、サーバーが必要とする高レベルの並行性と数千のパラメーターがあるため、トリッキーになる可能性があります。Caddyは、多くの利点がある設計を使用して、この問題をエレガントに解決します。
- 実行中のサービスを中断しない
- 詳細な構成変更が可能
- 必要なロックは1つだけ(バックグラウンド)
- すべてのリロードは、アトミック、一貫性があり、隔離されており、ほぼ永続的(「ACID」)
- 最小限のグローバル状態
構成のリロードは、新しいモジュールをプロビジョニングし、すべてが成功した場合、古いモジュールがクリーンアップされることによって機能します。短時間の間、2つの構成が同時に動作します。
各構成は、すべてのモジュール状態を保持するコンテキストに関連付けられているため、ほとんどの状態が構成のスコープから抜け出すことはありません。これは、正確性、パフォーマンス、およびシンプルさにとって朗報です!
ただし、真にグローバルな状態が必要になる場合があります。たとえば、リバースプロキシはアップストリームのヘルスを追跡している場合があります。各アップストリームはグローバルに1つだけであるため、マイナーな構成変更が行われるたびにそれらを忘れてしまうと、問題が発生します。幸い、Caddyは、言語ランタイムのガベージコレクターに似た機能を提供して、グローバル状態を整理します。
オンライン構成更新への1つの明らかなアプローチは、ホットパスでさえ、すべての構成パラメーターへのアクセスを同期することです。これは、パフォーマンスと複雑さの点で非常に悪いものです(特に大規模な場合)ので、Caddyはこのアプローチを使用しません。
代わりに、構成は不変のアトミックユニットとして扱われます。全体が置き換えられるか、何も変更されないかのどちらかです。管理APIエンドポイント(構造を調べて詳細な変更を許可します)は、構成のインメモリ表現のみを変更します。そこから、新しい構成ドキュメント全体が生成されてロードされます。このアプローチは、シンプルさ、パフォーマンス、および一貫性の点で大きな利点があります。ロックが1つしかないため、Caddyは高速なリロードを簡単に処理できます。