プログラミング言語Rust は、比較的短い歴史の中で、豊かで成熟したエコシステムとともに、並外れた称賛を獲得してきました。 Rust と Cargo (そのビルド システム、ツールチェーン インターフェイス、パッケージ マネージャー) はどちらも、業界で高く評価され、望まれているテクノロジーであり、Rust はRedMonk のプログラミング言語ランキングの上位 20 言語で安定した位置を占めています。 さらに、Rust を採用したプロジェクトでは、安定性とセキュリティ関連のプログラミング エラーが改善されることがよくあります (例として、Android 開発者は、目覚ましい改善の説得力のあるストーリーを語っています)。
F5 は、Rust とその Rustaceans コミュニティをめぐるこうした進展を、しばらくの間、興奮しながら見守ってきました。 私たちは、この言語、そのツールチェーン、そして今後の採用を積極的に推進することで注目してきました。
NGINX では、ますますデジタル化が進み、セキュリティ意識が高まる世界において、開発者の要望とニーズを満たすために積極的に取り組んでいます。 Rust 言語を使用して NGINX モジュールを記述する新しい方法であるngx-rust プロジェクトを発表できることを嬉しく思います。 Rustaceans さん、これはあなたのためのものです!
NGINX とGitHubをよくご存知の方は、これが Rust ベースのモジュールの最初の形ではないことに気付くかもしれません。 Kubernetesの初期の頃とサービス メッシュの初期の頃に、Rust を中心にいくつかの作業が行われ、ngx-rust プロジェクトの基盤が構築されました。
もともと、ngx-rust は、NGINX を使用した Istio 互換のサービス メッシュ製品の開発を加速するための手段として機能していました。最初のプロトタイプの開発後、このプロジェクトは長年にわたって変更されずに残されていました。 その間、多くのコミュニティ メンバーがリポジトリをフォークしたり、ngx-rust で提供されているオリジナルの Rust バインディングの例に触発されてプロジェクトを作成したりしました。
その後、 F5 Distributed Cloud Bot Defenseチームは、NGINX プロキシを保護サービスに統合する必要がありました。 これには新しいモジュールの構築が必要でした。
また、開発者のエクスペリエンスを向上させ、顧客の進化するニーズを満たしながら、Rust ポートフォリオを継続的に拡大していきたいと考えていました。 そこで、私たちは社内のイノベーション スポンサーシップを活用し、オリジナルの ngx-rust 作成者と協力して、新しく改良された Rust バインディング プロジェクトを開発しました。 長い休止期間を経て、コミュニティでの使用に適した人間工学を構築するための強化されたドキュメントと改善を加えた ngx-rust クレートの公開を再開しました。
モジュールは NGINX のコア構成要素であり、その機能のほとんどを実装します。 モジュールは、NGINX ユーザーがその機能をカスタマイズし、特定のユースケースのサポートを構築できる最も強力な方法でもあります。
NGINX は従来、C で記述されたモジュールのみをサポートしていました (C で記述されたプロジェクトであるため、ホスト言語でモジュール バインディングをサポートすることは明確で簡単な選択でした)。 しかし、コンピュータサイエンスとプログラミング言語理論の進歩により、特にメモリの安全性と正確性に関して、過去のパラダイムは改善されました。 これにより、Rust などの言語が NGINX モジュール開発に利用できるようになりました。
NGINX と Rust の歴史について少し説明したので、モジュールの構築を始めましょう。 ソースからビルドしてモジュールをローカルで開発したり、 ngx-rustソースをプルしてより優れたバインディングの構築に協力したり、または単にcrates.ioからクレートをプルしたりすることができます。
ngx-rust READMEには、貢献ガイドラインと開始するためのローカル ビルド要件が記載されています。 まだ初期段階であり、開発中ですが、コミュニティのサポートを受けて品質と機能を向上させることを目指しています。 このチュートリアルでは、単純な独立したモジュールの作成に焦点を当てます。 より複雑なレッスンについては、 ngx-rust の例を参照することもできます。
バインディングは 2 つのクレートに編成されます。
nginx-sys は、
NGINX ソース コードからバインディングを生成するクレートです。 このファイルは、NGINX ソース コードと依存関係をダウンロードし、 bindgen
コード自動化を使用して外部関数インターフェイス (FFI) バインディングを作成します。以下の手順では、スケルトン ワークスペースを初期化します。 まず作業ディレクトリを作成し、Rust プロジェクトを初期化します。
cd $YOUR_DEV_ARENA
mkdir ngx-rust-howto
cd ngx-rust-howto
cargo init --lib
次に、Cargo.toml ファイルを開き、次のセクションを追加します。
[lib]
crate-type = ["cdylib"]
[依存関係]
ngx = "0.3.0-beta"
あるいは、読みながら完成したモジュールを確認したい場合は、Git からクローンすることもできます。
cd $YOUR_DEV_ARENA
git clone git@github.com:f5yacobucci/ngx-rust-howto.git
これで、最初の NGINX Rust モジュールの開発を開始する準備が整いました。 モジュールを構築するための構造、セマンティクス、および一般的なアプローチは、C を使用する場合に必要なものとあまり変わりません。現時点では、バインディングを生成して使用可能にし、開発者が独創的な製品を作成できるようにするために、反復的なアプローチで NGINX バインディングを提供することに着手しました。 今後は、より優れた、より慣用的な Rust エクスペリエンスの構築に取り組んでいきます。
つまり、最初のステップは、NGINX にインストールして実行するために必要なディレクティブ、コンテキスト、その他の側面と連携してモジュールを構築することです。モジュールは、HTTP メソッドに基づいてリクエストを受け入れたり拒否したりできるシンプルなハンドラーになり、単一の引数を受け入れる新しいディレクティブを作成します。 これについては手順ごとに説明しますが、完全なコードは GitHub のngx-rust-howtoリポジトリで参照できます。
注記: このブログでは、NGINX モジュールの一般的な構築方法ではなく、Rust の詳細を概説することに重点を置いています。 他の NGINX モジュールの構築に興味がある場合は、コミュニティ内の多くの優れたディスカッションを参照してください。 これらのディスカッションでは、NGINX を拡張する方法についてもより基本的な説明が提供されます (詳細については、以下のリソースセクションを参照してください)。
すべての NGINX エントリ ポイント ( postconfiguration
、 preconfiguration
、 create_main_conf
など) を定義するHTTPModule
特性を実装することで、Rust モジュールを作成できます。 モジュール作成者は、そのタスクに必要な関数を実装するだけで済みます。 このモジュールは、リクエスト ハンドラーをインストールするための postconfiguration メソッドを実装します。
注記: ngx-rust-howtoリポジトリをクローンしていない場合は、 cargo init
によって作成されたsrc/lib.rs
ファイルの編集を開始できます。
struct Module;
module に http::HTTPModule を実装します {
type MainConf = ();
type SrvConf = ();
type LocConf = ModuleConfig;
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
let htcf = http::ngx_http_conf_get_module_main_conf(cf, &ngx_http_core_module);
let h = ngx_array_push(
&mut (*htcf).phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
) as *mut ngx_http_handler_pt;
if h.is_null() {
return core::Status::NGX_ERROR.into();
}
// アクセス フェーズ ハンドラーを設定します
*h = Some(howto_access_handler);
core::Status::NGX_OK.into()
}
}
Rust モジュールには、アクセス フェーズNGX_HTTP_ACCESS_PHASE
でのpostconfiguration
フックのみが必要です。 モジュールは、HTTP リクエストのさまざまなフェーズのハンドラーを登録できます。 詳細については、開発ガイドの詳細を参照してください。
関数が返される直前に、フェーズ ハンドラーhowto_access_handler
が追加されていることがわかります。 これについては後でまた取り上げます。 現時点では、リクエスト チェーン中に処理ロジックを実行する関数であることに注意してください。
モジュールの種類とそのニーズに応じて、次の登録フックが利用できます。
事前設定
構成後
メイン設定を作成します
初期化メイン
作成_srv_conf
マージ_srv_conf
作成場所設定
マージ_loc_conf
次に、モジュール用のストレージを作成します。 このデータには、必要な構成パラメータや、リクエストの処理や動作の変更に使用される内部状態が含まれます。 基本的に、モジュールが保持する必要がある情報はすべて構造体に入れて保存できます。 この Rust モジュールは、場所構成レベルでModuleConfig
構造を使用します。 構成ストレージはMerge 特性と Default
特性を実装する必要があります。
上記の手順でモジュールを定義するときに、メイン、サーバー、および場所の構成のタイプを設定できます。 ここで開発している Rust モジュールは場所のみをサポートしているため、 LocConf
タイプのみが設定されます。
モジュールの状態と構成のストレージを作成するには、構造を定義し、 Merge特性を実装します。
#[derive(Debug, Default)]
struct ModuleConfig {
enabled: bool,
method: 文字列、
}
ModuleConfig の http::Merge を実装します {
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
if prev.enabled {
self.enabled = true;
}
if self.method.is_empty() {
self.method = String::from(if !prev.method.is_empty() {
&prev.method
} else {
""
});
}
if self.enabled && self.method.is_empty() {
return Err(MergeConfigError::NoValue);
}
Ok(())
}
}
ModuleConfig は
、HTTP リクエスト メソッドとともに、有効フィールドにオン/オフ状態を保存します。 ハンドラーはこのメソッドをチェックし、リクエストを許可または禁止します。
ストレージが定義されると、モジュールはユーザーが自分で設定できるディレクティブと構成ルールを作成できます。 NGINX は、 ngx_command_t
型と配列を使用して、モジュール定義のディレクティブをコア システムに登録します。
FFI バインディングを通じて、Rust モジュール作成者はngx_command_t 型
にアクセスし、C の場合と同じようにディレクティブを登録できます。ngx-rust-howto
モジュールは、文字列値を受け入れるhowto
ディレクティブを定義します。 この場合、1 つのコマンドを定義し、セッター関数を実装し、(次のセクションで) それらのコマンドをコア システムにフックします。 提供されているngx_command_null!
マクロを使用してコマンド配列を終了することを忘れないでください。
NGINX コマンドを使用して簡単なディレクティブを作成する方法は次のとおりです。
#[no_mangle]
static mut ngx_http_howto_commands: [ngx_command_t; 2] = [
ngx_command_t {
name: ngx_string!("howto"),
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) を ngx_uint_t として設定します: いくつか(ngx_http_howto_commands_set_method)、
conf: NGX_RS_HTTP_LOC_CONF_OFFSET、
オフセット: 0,
post: std::ptr::null_mut(),
},
ngx_null_command!(),
];
#[no_mangle]
extern "C" fn ngx_http_howto_commands_set_method(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut c_void,
) -> *mut c_char {
unsafe {
let conf = &mut *(conf as *mut ModuleConfig);
let args = (*(*cf).args).elts as *mut ngx_str_t;
conf.enabled = true;
conf.method = (*args.add(1)).to_string();
};
std::ptr::null_mut()
}
登録関数、フェーズ ハンドラー、および構成用のコマンドができたので、すべてをまとめて関数をコア システムに公開できます。 登録関数、フェーズ ハンドラー、およびディレクティブ コマンドへの参照を含む静的ngx_module_t
構造体を作成します。 すべてのモジュールには、 ngx_module_t
型のグローバル変数が含まれている必要があります。
次に、コンテキストと静的モジュール タイプを作成し、 ngx_modules!
マクロで公開します。 以下の例では、コマンド
がコマンド フィールドにどのように設定され、モジュール登録関数を参照するコンテキストがctx
フィールドに設定されているかがわかります。 このモジュールの場合、他のすべてのフィールドは事実上デフォルトになります。
#[no_mangle]
static ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t {
事前設定: Some(Module::preconfiguration)、
postconfiguration: いくつか(Module::postconfiguration)、
create_main_conf: いくつか(Module::create_main_conf)、
init_main_conf: いくつか(Module::init_main_conf)、
create_srv_conf: いくつか(Module::create_srv_conf)、
merge_srv_conf: いくつか(Module::merge_srv_conf)、
create_loc_conf: いくつか(Module::create_loc_conf)、
merge_loc_conf: Some(Module::merge_loc_conf),
};
ngx_modules!(ngx_http_howto_module);
#[no_mangle]
pub static mut ngx_http_howto_module: ngx_module_t = ngx_module_t {
ctx_index: ngx_uint_t::max_value(),
index: ngx_uint_t::max_value(),
name: std::ptr::null_mut(),
Spare0: 0,
スペア1: 0、
バージョン: nginx_version を ngx_uint_t として、
署名: NGX_RS_MODULE_SIGNATURE.as_ptr() を *const c_char として、
ctx: &ngx_http_howto_module_ctx を *const _ として *mut _ として、
コマンド: unsafe { &ngx_http_howto_commands[0] を *const _ として *mut _ として }、
type_: NGX_HTTP_MODULE を ngx_uint_t として、
init_master: なし、
init_module: なし、
init_process: なし、
init_thread: なし、
exit_thread: なし、
exit_process: なし、
exit_master: なし、
spare_hook0: 0,
スペアフック1: 0,
スペアフック2: 0,
スペアフック3: 0,
スペアフック4: 0,
スペアフック5: 0,
スペアフック6: 0,
スペアフック7: 0,
};
これで、新しい Rust モジュールを設定して登録するために必要な手順が実質的に完了しました。 ただし、 postconfiguration
フックで設定されたフェーズ ハンドラー(howto_access_handler)
を実装する必要があります。
ハンドラは受信リクエストごとに呼び出され、モジュールの作業の大部分を実行します。 リクエスト ハンドラーは ngx-rust チームの焦点であり、初期の人間工学的改善の大部分がここで行われました。 これまでのセットアップ手順では Rust を C のようなスタイルで記述する必要がありましたが、ngx-rust はリクエスト ハンドラーにさらなる利便性とユーティリティを提供します。
以下の例に示すように、ngx-rust は、 Request
インスタンスで呼び出される Rust クロージャを受け入れるためのマクロhttp_request_handler!
を提供します。 また、設定と変数を取得し、それらの変数を設定し、メモリ、その他の NGINX プリミティブ、および API にアクセスするためのユーティリティも提供します。
ハンドラー プロシージャを開始するには、マクロを呼び出して、ビジネス ロジックを Rust クロージャとして提供します。 ngx-rust-howto モジュールの場合、リクエストの処理を続行できるようにリクエストのメソッドを確認します。
http_request_handler!(howto_access_handler, |request: &mut http::Request| {
let co = unsafe { request.get_module_loc_conf::(&ngx_http_howto_module) };
let co = co.expect("モジュール構成がなし");
ngx_log_debug_http!(request, "howto モジュールの有効化が呼び出されました");
match co.enabled {
true => {
let method = request.method();
if method.as_str() == co.method {
return core::Status::NGX_OK;
}
http::HTTPStatus::FORBIDDEN.into()
}
false => core::Status::NGX_OK,
}
});
これで、最初の Rust モジュールが完成しました。
GitHub のngx-rust-howtoリポジトリには、conf ディレクトリに NGINX 構成ファイルが含まれています。 また、ビルド ( cargo build
を使用) し、モジュール バイナリをローカル nginx.conf のload_module
ディレクティブに追加し、NGINX のインスタンスを使用して実行することもできます。このチュートリアルの作成では、ngx-rust でサポートされているデフォルトの NGINX_VERSION である NGINX v1.23.3 を使用しました。 動的モジュールをビルドして実行するときは、マシン上で実行している NGINX インスタンスと同じNGINX_VERSION を
ngx-rust ビルドに使用するようにしてください。
NGINX は、長年にわたる機能とユースケースが組み込まれた成熟したソフトウェア システムです。 これは、優れたプロキシ、ロードバランサ、そして世界クラスの Web サーバーです。 今後何年もの間、この製品が市場に存在することは確実であり、それが、この製品の機能をさらに強化し、ユーザーに新しい方法で製品と対話できるようにするという当社の意欲を高めています。 Rust は開発者の間で人気があり、安全性の制約も改善されているため、世界最高の Web サーバーと併せて Rust を使用するオプションを提供できることを嬉しく思います。
ただし、NGINX の成熟度と機能豊富なエコシステムにより、API の表面領域が拡大しており、ngx-rust はまだその表面に触れたにすぎません。 このプロジェクトは、より慣用的な Rust インターフェースの追加、追加のリファレンス モジュールの構築、モジュール作成の人間工学の向上を通じて、改善と拡張を目指しています。
ここであなたの出番です! ngx-rust プロジェクトは誰でも参加でき、 GitHub で入手できます。 私たちは、NGINX コミュニティと協力して、モジュールの機能と使いやすさを継続的に改善していきたいと考えています。 ぜひチェックして、バインディングを自分で試してみてください。 ぜひ、NGINX コミュニティ Slack チャネルにご連絡いただき、問題や PR を提出し、ご参加ください。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"