このたびCloudflareは、Pingoraのオープンソース化を発表いたします。Pingoraは、当社が、トラフィックの大部分を支えるサービスを構築するために使用してきたRustフレームワークで、Apache License 2.0でリリースされます。
過去のブログ投稿に記述したように、PingoraはRustの非同期型マルチスレッドフレームワークで、CloudflareでのHTTPプロキシサービスの構築をサポートしています。この投稿の後、Cloudflareのグローバルネットワーク全体で一千兆件近くのインターネットリクエストがPingoraにより処理されてきました。
Cloudflareは、自社のインフラストラクチャを超えて、より良く、より安全なインターネットの構築を支援するために、Pingoraをオープンソース化いたします。私たちは、このオープンソース化により、ユーザーの皆様やその他の方々に、メモリ安全性の高いフレームワークを使って独自のインターネットインフラを構築する際の、ツール、アイデア、ヒントをご提供したいと考えています。業界や米国政府でもメモリ安全性の重要性に対する認識が高まっていることを考えると、このようなフレームワークは特に重要です。この共通の目標の下、CloudflareはInternet Security Research Group(ISRG)のProssimoプロジェクトとともに、インターネットの最も重要なインフラにおけるPingora導入の促進に取り組みます。
前述のブログ投稿では、Pingoraを構築した理由と方法を説明しました。この投稿では、Pingoraを使うべき理由とその方法についてご説明します。
Pingoraは、プロキシだけでなく、クライアントとサーバーにもビルディングブロックを提供します。これらのコンポーネントに加え、イベントカウント、エラー処理、キャッシングなどの共通ロジックが実装されたユーティリティライブラリも提供されます。
詳細
Pingoraは、HTTP/1やHTTP/2、API TLS上、またはTCP/UDPの直上でサービスを構築するためのライブラリとUDPを提供します。プロキシとしては、HTTP/1およびHTTP/2エンドツーエンド、gRPC、WebSocketプロキシをサポートします(HTTP/3のサポートも予定されています)。また、カスタマイズ可能な負荷分散とフェイルオーバーの手法も付随します。コンプライアンスとセキュリティについては、一般的に使用される、FIPSコンプライアンスとポスト量子暗号を備えたOpenSSLとBoringSSLライブラリの両方をサポートします。
これらの機能の提供に加え、Pingoraで提供されるフィルタとコールバックを使用すれば、サービスがリクエストをどのように処理し、変換し、転送するべきかを完全にカスタマイズできます。これらのAPIは、その多くがOpenRestyの「*_by_lua」コールバックに直感的にマッピングされるため、OpenRestyとNGINXユーザーにとっては特に使いやすいはずです。
運用面では、Pingoraはダウンタイムゼロのグレースフルリスタートを提供し、着信リクエストを1つもドロップすることなく自身をアップグレードします。SyslogやPrometheus、Sentry、OpenTelemetry、その他必要な可観測性ツールもPingoraと簡単に統合できます。
Pingoraのメリット
以下のケースでは、Pingora導入の検討が推奨されます。
**セキュリティが最優先事項となるケース:**Pingoraは、C/C++で書かれたサービスにはよりメモリ安全性の高い代替手段です。プログラミング言語におけるメモリ安全性については議論の余地がありますが、私たちは、実際の経験に基づき、メモリ安全性の問題につながるコーディングミスの可能性はかなり低いと考えています。しかも、これらの問題に悩まされる時間が軽減されるため、生産的に新機能の実装に取り組むことができます。
**サービスがパフォーマンス重視であるケース:**Pingoraは、高速かつ効率的です。以前のブログ記事で説明したように、私たちはPingoraのマルチスレッドアーキテクチャで多くのCPUとメモリリソースを節約できました。コストやシステムの速度を重視するワークロードにとっては、時間とリソースの節約は魅力的でしょう。
**広範なカスタマイズが必要なサービスであるケース:**Pingoraプロキシのフレームワークが提供するAPIは、優れたプログラマブル性を有します。カスタマイズされた高度なゲートウェイやロードバランサーを構築するユーザーにとって、Pingoraは強力かつシンプルにそれを実現します。次のセクションで例を見てみましょう。
ロードバランサーを構築してみる
シンプルなロードバランサーを構築して、PingoraのプログラマブルなAPIを試してみましょう。ロードバランサーは、https://1.1.1.1/とhttps://1.0.0.1/の間でラウンドロビン方式で上流を選択します。
まず、空のHTTPプロキシを作成しましょう。
ProxyHttp
trait(C++やJavaのインターフェースの概念に似ている)を実装しているオブジェクトはすべてHTTPプロキシです。唯一必要なメソッドは、すべてのリクエストに対して呼び出されるupstream_peer()
です。この関数は、接続先のオリジンIPと接続方法を含む HttpPeer
を返す必要があります。
pub struct LB();
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
todo!()
}
}
次に、ラウンドロビン方式の選択を実装しましょう。Pingoraフレームワークでは、すでにLoadBalancer
にラウンドロビンやハッシュなどの一般的な選択アルゴリズムが提供されているので、それを使用してみましょう。ユースケースにより、より高度なサーバー選択ロジックやサーバー選択ロジックのカスタマイズが必要とされる場合は、この関数でユーザーが簡単に実装できます。
HTTPSサーバーに接続するので、SNIも設定する必要があります。必要に応じて、証明書、タイムアウト、その他の接続オプションも、HttpPeerオブジェクトで設定することができます。
pub struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
let upstream = self.0
.select(b"", 256) // hash doesn't matter for round robin
.unwrap();
// Set SNI to one.one.one.one
let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
Ok(peer)
}
}
最後に、サービスを実行してみましょう。この例では、オリジンサーバーのIPをハードコードします。実際のワークロードでは、オリジンサーバーのIPは、 upstream_peer()
が呼び出される際、あるいはバックグラウンドで動的に検出することもできます。サービスが作成されたら、LBサービスに127.0.0.1:6188をリッスンするように指示します。これでPingoraサーバーが完成です。このサーバーは、負荷分散サービスを実行するプロセスとなります。
試してみましょう。
fn main() {
let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();
let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
lb.add_tcp("127.0.0.1:6188");
let mut my_server = Server::new(None).unwrap();
my_server.add_service(lb);
my_server.run_forever();
}
プロキシが機能しているのに、オリジンサーバーが403で拒否していることがわかります。これは、curlによって設定されたHostヘッダー(127.0.0.1:6188)を単純にプロキシしていることが原因で、これがオリジンサーバーに不都合を引き起こしています。この問題を修正するために、upstream_request_filter
と呼ばれるフィルタを追加します。このフィルタは、オリジンサーバーに接続された後、HTTPリクエストが送信される前に各リクエストで実行されます。このフィルタ内でHTTPリクエストヘッダーを追加、削除、変更できます。
curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
もう一度試してみます。
async fn upstream_request_filter(…, upstream_request: &mut RequestHeader, …) -> Result<()> {
upstream_request.insert_header("Host", "one.one.one.one")
}
今回はうまくいきました。コードの全体はこちらでご確認ください。
curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK
以下は、この例で使用したコールバックとフィルタを通過するリクエストの流れを非常に簡単に示した図です。現在、Pingoraプロキシフレームワークでは、リクエストのさまざまな段階でより多くのフィルタとコールバックが提供され、リクエストおよびレスポンスの変更、拒否、ルーティング、記録が可能です。
Pingoraプロキシフレームワークが裏で接続プーリング、 TLSハンドシェイク、リクエストの読み取り、書き込み、解析、およびその他の一般的なプロキシタスクを処理するので、ユーザーは重要なロジックに集中できるようになります。
オープンソースの現在と未来
Pingoraはライブラリとツールセットであり、実行可能なバイナリではありません。例えるなら、Pingoraは車を動かすエンジンのことであり、車そのものではないのです。Pingoraは産業利用の本番環境に対応していますが、バッテリー付きでローコードまたはノーコードの設定オプションを備えた、すぐに使えるWebサービスを求める人が多いことも私たちは把握しています。Pingora上にそのアプリケーションを構築することが、Pingoraのリーチ拡大に向けたISRGとの協力のテーマです。このプロジェクトに関する今後の発表に引き続きご注目ください。
その他の注意事項
**現在、APIの安定性は保証されていません。**破壊的な変更を行う頻度は最小限に抑える予定ですが、特にこの1.0以前の期間中は、ライブラリの進化に伴ってリクエストやレスポンスフィルタなどのコンポーネントを追加、削除、変更する権利を留保します。
**Unixベースでないオペレーティングシステムのサポートは、現在予定されていません。**将来的には変更される可能性があります。
ご協力をお願いします
バグレポート、ドキュメントの問題、機能リクエストは、GitHubのIssue Trackerでお気軽にお寄せください。プルリクエストを開く前に、Cloudflareのコントリビュートガイドをご覧いただくことをおすすめします。
まとめ
このブログ投稿では、Pingoraフレームワークのオープンソース化をお知らせして、インターネットエンティティとインフラストラクチャにとって、Pingoraのセキュリティ、パフォーマンス、カスタマイズ可能性が価値となることを説明しました。また、Pingoraの使いやすさやカスタマイズ可能性を実証しました。
本番Webサービスの構築中の方々、あるいはネットワークテクノロジーを試してみようと思っている方々に、Pingoraを利用する価値を感じていただければと思います。長い道のりでしたが、このプロジェクトをオープンソースコミュニティと共有することは、当初からの目標でした。Rustコミュニティに感謝いたします。Pingoraは多くの素晴らしいオープンソースのRustクレートで構築されています。メモリ安全性の高いインターネットへの移行は不可能とも思える道のりかもしれませんが、ご参加いただければ幸いです。