비교적 짧은 역사 동안, 프로그래밍 언어 Rust는 풍부하고 성숙한 생태계와 더불어 뛰어난 찬사를 얻었습니다. Rust와 Cargo(빌드 시스템, 툴체인 인터페이스, 패키지 관리자)는 둘 다 이 분야에서 널리 사랑받고 바람직한 기술이며, Rust는 RedMonk의 프로그래밍 언어 순위 에서 상위 20개 언어 가운데 안정적으로 자리를 지키고 있습니다. 게다가 Rust를 도입한 프로젝트는 안정성과 보안 관련 프로그래밍 오류에서 개선이 나타나는 경우가 많습니다. 예를 들어, Android 개발자는 단속적인 개선에 대한 설득력 있는 이야기를 들려줍니다.
F5는 Rust와 Rustaceans 커뮤니티에서 일어나는 이러한 발전을 한동안 흥미롭게 지켜보았습니다. 우리는 이 언어와 툴체인을 적극적으로 옹호하고 채택을 확대해 나가면서 이를 주목하고 있습니다.
NGINX에서는 점점 디지털화되고 보안이 중요해지는 세상에서 개발자의 요구와 필요를 충족시키기 위해 노력하고 있습니다. Rust 언어로 NGINX 모듈을 작성하는 새로운 방법인 ngx-rust 프로젝트를 발표하게 되어 기쁩니다. 루스테아키아인 여러분, 이건 여러분을 위한 거예요!
NGINX와 GitHub을 열렬히 팔로우하는 분이라면 이것이 Rust 기반 모듈의 첫 번째 구현이 아니라는 걸 알고 계실 겁니다. 쿠버네티스 의 초창기와 서비스 메시 의 초기 단계에는 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 예제를 살펴보세요.
바인딩은 두 개의 상자에 정리됩니다.
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-베타"
또는 읽는 동안 완성된 모듈을 보고 싶다면 Git에서 복제할 수 있습니다.
cd $YOUR_DEV_ARENA
git 복제 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 모듈을 생성할 수 있습니다. 모듈 작성자는 해당 작업에 필요한 기능만 구현하면 됩니다. 이 모듈은 요청 핸들러를 설치하기 위해 사후 구성 방법을 구현합니다.
메모: ngx-rust-howto 저장소를 복제하지 않았다면 cargo init
이 생성한 src/lib.rs
파일을 편집할 수 있습니다.
struct 모듈;
모듈에 대한 http::HTTPModule 구현 {
유형 MainConf = ();
유형 SrvConf = ();
유형 LocConf = 모듈 구성;
안전하지 않은 외부 "C" fn 사후 구성(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
액세스 단계에서 사후 구성
후크만 필요합니다. 모듈은 HTTP 요청의 다양한 단계에 대한 핸들러를 등록할 수 있습니다. 자세한 내용은 개발 가이드 를 참조하세요.
함수가 반환되기 직전에 howto_access_handler라는
단계 핸들러가 추가된 것을 볼 수 있습니다. 이 내용은 나중에 다시 다루겠습니다. 지금은 요청 체인 중에 처리 로직을 수행하는 함수라는 점만 기억해 두세요.
모듈 유형과 요구 사항에 따라 사용 가능한 등록 후크는 다음과 같습니다.
사전 구성
사후 구성
메인_컨퍼런스_생성
init_main_conf
SRV_conf를 만듭니다.
병합_SRV_컨퍼런스
생성_위치_설정
병합_위치_설정
이제 모듈에 대한 저장소를 만들 차례입니다. 이 데이터에는 필요한 모든 구성 매개변수나 요청을 처리하거나 동작을 변경하는 데 사용되는 내부 상태가 포함됩니다. 기본적으로 모듈이 지속하는 데 필요한 정보는 모두 구조에 넣어 저장할 수 있습니다. 이 Rust 모듈은 위치 구성 수준에서 ModuleConfig
구조를 사용합니다. 구성 저장소는 Merge 및 Default
특성을 구현해야 합니다.
위의 단계에서 모듈을 정의할 때 기본, 서버, 위치 구성에 대한 유형을 설정할 수 있습니다. 여기서 개발하고 있는 Rust 모듈은 위치만 지원하므로 LocConf
유형만 설정됩니다.
모듈에 대한 상태 및 구성 저장소를 생성하려면 구조를 정의하고 Merge 특성을 구현합니다.
#[derive(디버그, 기본값)]
struct ModuleConfig {
활성화됨: bool,
메서드: 문자열,
}
모듈 구성에 대한 http::Merge 구현 {
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
이전이 활성화된 경우 {
self.enabled = true;
}
self.method.is_empty()인 경우 {
self.method = String::from(!prev.method.is_empty()인 경우 {
&prev.method
} 그렇지 않은 경우 {
""
});
}
self.enabled && self.method.is_empty()인 경우 {
Err(MergeConfigError::NoValue)를 반환합니다.
}
Ok(())
}
}
ModuleConfig는
HTTP 요청 메서드와 함께 활성화된 필드에 켜짐/꺼짐 상태를 저장합니다. 핸들러는 이 메서드를 검사하여 요청을 허용하거나 금지합니다.
저장소가 정의되면 모듈은 사용자가 직접 설정할 수 있는 지침과 구성 규칙을 만들 수 있습니다. NGINX는 ngx_command_t
유형과 배열을 사용하여 모듈에서 정의한 지침을 코어 시스템에 등록합니다.
FFI 바인딩을 통해 Rust 모듈 작성자는 ngx_command_t 유형
에 액세스하고 C에서와 마찬가지로 지시문을 등록할 수 있습니다 . ngx-rust-howto
모듈은 문자열 값을 허용하는 howto
지시문을 정의합니다. 이 경우, 하나의 명령을 정의하고, 세터 함수를 구현한 뒤 (다음 섹션에서) 해당 명령을 핵심 시스템에 연결합니다. 제공된 ngx_command_null!
매크로로 명령 배열을 종료하는 것을 잊지 마세요.
NGINX 명령을 사용하여 간단한 지침을 만드는 방법은 다음과 같습니다.
#[no_mangle]
정적 mut ngx_http_howto_commands: [ngx_command_t; 2] = [
ngx_command_t {
이름: ngx_string!("howto"),
유형: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) ngx_uint_t로,
설정: 일부(ngx_http_howto_commands_set_method),
conf: NGX_RS_HTTP_LOC_CONF_OFFSET,
오프셋: 0,
게시물: std::ptr::null_mut(),
},
ngx_null_command!(),
];
#[no_mangle]
외부 "C" fn ngx_http_howto_commands_set_method(
참조: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut c_void,
) -> *mut c_char {
안전하지 않음 {
conf = &mut *(conf as *mut ModuleConfig);
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!
매크로를 사용하여 이를 노출합니다. 아래 예에서는 명령이
commands 필드에 어떻게 설정되는지, 그리고 모듈 등록 기능을 참조하는 컨텍스트가 ctx
필드에 어떻게 설정되는지 볼 수 있습니다. 이 모듈의 경우 다른 모든 필드는 기본적으로 사용됩니다.
#[no_mangle]
정적 ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t {
사전 구성: 일부(모듈::사전 구성),
사후 구성: 일부(모듈::사후 구성),
create_main_conf: 일부(모듈::create_main_conf),
init_main_conf: 일부(모듈::init_main_conf),
create_srv_conf: 일부(모듈::create_srv_conf),
merge_srv_conf: 일부(모듈::merge_srv_conf),
create_loc_conf: 일부(모듈::create_loc_conf),
merge_loc_conf: 일부(모듈::merge_loc_conf),
};
ngx_모듈!(ngx_http_howto_module);
#[no_mangle]
pub 정적 mut ngx_http_howto_module: ngx_module_t = ngx_module_t {
ctx_index: ngx_uint_t::max_value(),
인덱스: ngx_uint_t::max_value(),
이름: std::ptr::null_mut(),
예비 0: 0,
여분1: 0,
버전: nginx_version as ngx_uint_t,
서명: NGX_RS_MODULE_SIGNATURE.as_ptr()를 *const c_char로,
ctx: &ngx_http_howto_module_ctx를 *const _로 *mut _로,
명령: 안전하지 않음 { &ngx_http_howto_commands[0]를 *const _로 *mut _로 },
유형: NGX_HTTP_MODULE을 ngx_uint_t로,
init_master: 없음,
init_module: 없음,
init_process: 없음,
init_thread: 없음,
종료 스레드: 없음,
종료 프로세스: 없음,
exit_master: 없음,
spare_hook0: 0,
예비 후크1: 0,
예비 후크2: 0,
예비 후크 3: 0,
예비_후크4: 0,
예비 후크5: 0,
예비_후크6: 0,
예비_후크7: 0,
};
이제 새로운 Rust 모듈을 설정하고 등록하는 데 필요한 단계가 거의 완료되었습니다. 그렇긴 하지만, 여전히 사후 구성
후크에 설정된 단계 핸들러 (howto_access_handler)
를 구현해야 합니다.
핸들러는 들어오는 각 요청에 대해 호출되어 모듈의 대부분의 작업을 수행합니다. 요청 핸들러는 ngx-rust 팀의 초점이었으며 대부분의 초기 인체공학적 개선이 이루어진 부분입니다. 이전 설정 단계에서는 C와 비슷한 스타일로 Rust를 작성해야 했지만, ngx-rust는 요청 처리기에 더 많은 편의성과 유틸리티를 제공합니다.
아래 예에서 볼 수 있듯이 ngx-rust는 Request
인스턴스로 호출되는 Rust 클로저를 허용하는 매크로 http_request_handler!
를 제공합니다. 또한 구성 및 변수를 얻고, 해당 변수를 설정하고, 메모리, 다른 NGINX 기본 요소 및 API에 액세스하는 유틸리티도 제공합니다.
핸들러 프로시저를 시작하려면 매크로를 호출하고 비즈니스 로직을 Rust 클로저로 제공합니다. ngx-rust-howto 모듈의 경우 요청의 메서드를 확인하여 요청이 계속 처리되도록 허용합니다.
http_request_handler!(howto_access_handler, |요청: &mut http::Request| {
let co = unsafe { request.get_module_loc_conf::(&ngx_http_howto_module) };
let co = co.expect("모듈 구성이 없습니다");
ngx_log_debug_http!(요청, "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는 수년간 축적된 기능과 사용 사례를 갖춘 성숙한 소프트웨어 시스템입니다. 이는 유능한 프록시이자 로드 밸런서이며 세계적인 수준의 웹 서버입니다. 이 앱은 앞으로도 수년간 시장에서 존재감을 유지할 것이 확실시되며, 이를 통해 우리는 이 앱의 역량을 강화하고 사용자에게 앱과 상호작용할 수 있는 새로운 방법을 제공하려는 동기를 갖게 되었습니다. 개발자들 사이에서 Rust의 인기와 향상된 안전 제약으로 인해, 우리는 세계 최고의 웹 서버와 함께 Rust를 사용할 수 있는 옵션을 제공하게 되어 기쁩니다.
하지만 NGINX의 성숙도와 기능이 풍부한 생태계는 모두 광범위한 API 표면적을 만들어내고 ngx-rust는 아직 표면만 긁은 상태입니다. 이 프로젝트는 보다 관용적인 Rust 인터페이스를 추가하고, 추가적인 참조 모듈을 구축하고, 쓰기 모듈의 인간공학을 발전시켜 개선하고 확장하는 것을 목표로 합니다.
여기서 당신이 등장합니다! ngx-rust 프로젝트는 모두에게 공개되어 있으며 GitHub에서 이용할 수 있습니다 . 우리는 NGINX 커뮤니티와 협력하여 모듈의 기능과 사용 편의성을 지속적으로 개선하고 싶습니다. 확인하고 바인딩을 직접 실험해 보세요! NGINX 커뮤니티 Slack 채널 에서 문제나 PR을 제출하고 저희에게 참여해 주세요.
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."