Introduction
Istio/Envoy custom plugin 제작을 위한 Proxy-Wasm 검토로서, Proxy-Wasm은 Istio/Envoy와 같은 proxy 환경을 위한 WebAssembly(WASM) runtime 개방형 표준 인터페이스이다. Proxy-Wasm은 WASM을 이끄는 Bytecode Alliance와 Envoy 커뮤니티가 주도한다고.
2025.03.01 현재, 공식 spec repo가 stable latest 버전인 v0.2.1 기준 7개월 전에 commit된 것으로 보아(다음 버전은 2개월 전) 현재까지도 잘 지원이 이루어지는 듯 보인다. 이외에 Istio, Envoy 뿐 아니라 NGNIX를 포함한 유명 proxy 제품이 이를 지원 중(ATS, Kong, APISIX 등).
참고로, 다음은 본 문서의 내용을 기반으로 작성한 Proxy-Wasm Istio/Envoy filter으로, 본 문서에서는 구현 예로 언급한다. WASM 적용 방법 역시 아래 링크에 기술되어 있다.
•
path-template-filter: Request path에 대한 OpenAPI 기반 path template을 request header 에 주입하여 Istio 메트릭 레이블의 값으로 사용 가능케 하는 plugin.
•
baggage-filter: 지정한 request header의 key, value를 W3C Trace Context의 baggage header의 항목으로 넣는 plugin.
왜 Lua 대신 WASM?
Lua를 쓰면 EnvoyFilter 등을 통해 WASM 보다 빠르고 간편하게 custom logic을 추가 가능하다(
Istio EnvoyFilter via mockserver 참고). 하지만 아무리 Lua가 빠르게 동작한다 하더라도 script 기반이므로 proxy처럼 성능에 민감한 영역에는 부담이다. AI 피셜 Rust WASM 기준 2~5배 처리량이 떨어진다 하며 지연이나 리소스 overhead 역시 마찬가지. 기능적으로도 그러하여 대표적으로 Lua로는 metric 처리가 불가능하다. 즉, Lua는 ad-hoc 요구가 주 용도란 뜻. 아래 링크에는 WASM으로 처리 가능한 다수의 Envoy의 영역이 기술되어 있다.
Context
•
WASM(WebAssembly): 고성능 실행을 위한 브라우저 및 서버용 portable binary 표준으로, 여러 언어 지원, 운영체제 독립적, sandbox 환경이 특징. 참고로, WASI(WebAssembly System Interface)는 WASM이 OS를 호출 가능하게 하는 표준 인터페이스로 파일시스템, 네트워크, 시스템 호출 등을 지원한다. WASM + WASI가 어찌나도 중요해보이는지, docker의 아버지인 Solomon Hykes는 이거 있음 docker 안만들었고, WASM이 미래라고 하며, 실제 container 대체를 위한 많은 움직임이 있다(e.g. k8s용 WASM - runwasi, krustlet 등)
•
ABI(Application Binary Interface): binary 수준에서의 모듈간 통신을 위한 인터페이스로, 함수 호출, 메모리 정렬, 바이너리 형식 등을 정의. Proxy-Wasm spec은 사실 상 ABI에 대한 spec이다.
•
Envoy는 비동기 이벤트 기반 아키텍처: 근거는 공식 아키텍처 문서, libevent 사용 코드로서, 현대의 proxy 중 이 모델을 안쓰는 proxy가 있을까 싶다(자체 경량 thread를 쓰는 golang만 예외인 줄 알았더니만 찾아보니 꽤 많다. 경량 thread가 의미있게 뜨는 중인 듯). runtime 아키텍처를 이해하기 위한 context로서, 일정 갯수(e.g. CPU core 갯수)의 worker thread가 만들어진다는 의미이다. 참고로, 공식 문서는 Envoy (란 특정 제품) 를 직접 언급한다.
•
Envoy Filter: 결국 WASM은 Envoy filter로 쓰기 위해서다. Envoy는 filter chain을 운용하고 filter는 host(Envoy 본체) 및 타 filter와 서로 통신하기 마련이므로 Envoy의 filter chain에 대한 이해가 필요하다. Envoy filter chain 은 이를 상세히 다룬다.
Runtime Architecture
Proxy-Wasm 공식 문서인 WebAssembly in Envoy가 근거의 대부분이며, 이외에
Wasm — envoy 1.34.0-dev-68e64b documentation,
IstioWasm Plugin을 함께 참조하였다.

내부 동작 구조, 특히 runtime 하에서 어떻게 동작하는지를 알아야 기능 뿐 아니라 성능을 고려한 설계가 가능하다. 그런 의미에서 아래 그림부터 이해하고 가는게 좋은데, 이 중 worker thread, Wasm runtime, Wasm VM, Proxy-Wasm 간의 관계가 특히 중요하다. 나머지는 그냥 Envoy의 이벤트 기반 아키텍처에 의한 이벤트 발생 시 wasm을 호출하는 (뻔한) 구조를 보여줄 뿐이다.
단일 Thread 내의 Wasm runtime, 그 안의 Wasm VM w/ Proxy-Wasm의 관계가 보인다. 출처: WebAssembly in Envoy
•
Wasm runtime: Wasm VM의 실행 환경으로 다수의 Wasm VM을 포함한다. Envoy는 V8 기본으로 사용하나 딴 제품도 지원하는 듯(참조). 무엇보다 이 환경에서는 single thread로만 동작하기에 thread 동기화 걱정을 안해도 된다. Wasm VM 간 통신 방법으로는 아래 Feature list에 언급된 shared data, queue 이외에도 header 값을 통한 전달이 있다.
•
Wasm VM: Wasm bytecode의 실행 인스턴스로서 sandbox 내에서 메모리 관리나 byte code 실행 등을 담당한다. Proxy-Wasm과 1:1 관계에 있다.
중요한 것은 VM을 나누는 단위인데, VM 갯수에 따라 WASM의 startup 지연, resource overhead 및 격리 수준이 달라지기 때문이다. 공식 문서는 이에 대해 5개 모델을 제시하며, Envoy는 wasm module 간 Wasm VM 공유도 가능하도록 한다.
하지만 Istio의 WasmPlugin 은 Envoy의 해당 공유 모델을 지원하지 않으며, 사실 상 module 단위가 VM 단위로 보인다(각기 다른 wasm module에서 plugin_vm_id (링크 참고)를 확인하면 되지만, 알 수 없는 이유로 plugin_name 만 조회 가능하다).
공식 문서의 5개 모델 + Envoy의 추가 모델 상세 내용
오류 대응에 관하여
Proxy 속성 상 가용성이 특히나 중요한데, custom code의 가용성을 Proxy Wasm이 보장할 수는 없다. 이에 대해 안전 장치로, Proxy Wasm 규격은 custom code이 포함된 VM 오류에 대한 동작을 규정한다(링크).
상기 규정에 따라, Istio/Envoy는 plugin에 오류 발생 시 Envoy의 대응 옵션을 제공한다(Istio WasmPlugin 은 적어도 문서 상으로는 Envoy와는 달리 Fail_RELOAD 가 없다. 상기 링크 참조). FAIL_CLOSE 가 default인데 이 경우 proxy가 의도적으로 오동작을 일으키므로 유의해서 사용할 필요가 있다. 문서는 오류 발생 시 단순히 5XX를 반환한다고 하지만, 아래 테스트 결과와 같이 경우에 따라 각기 다른 오동작 결과를 보인다.
FAIL_CLOSE | FAIL_OPEN | |
image 없음 | 403 RBAC denied | bypass |
on_configure() 중 오류 | 403 RBAC denied | bypass |
on_http_request_headers() 중 오류 | request 시 no response | bypass |
Proxy-Wasm features
Proxy-Wasm ABI v0.2.1 specification 과 rust SDK source code가 주된 출처로, Proxy-Wasm이 구체적으로 어떤 feature를 제공하는지를 나열한다. 아래 내용은 feature 제공 수준을 감잡기 위한 용도로, 구현 시에는 결국 spec 문서를 보아야 할 것이다. 앞서 잠시 소개한
path-template-filter의 코드를 함께 참고하자.

주요 용어/개념
•
Plugin: Proxy-Wasm ABI를 구현하는 WebAssembly 모듈
•
Host: plugin을 운용하는 proxy 자체
•
Hostcall/Callback: Hostcall은 plugin에서의 host 호출을 의미하고 callback은 그 반대를 의미한다. Rust SDK의 경우 아래 Context의 method 절대 대수가 hostcall/callback의 wrapper이다. 접두어 on_ 이 붙으면 callback, 아니면 hostcall 이다.
# hostcall
fn set_http_request_headers(&self, headers: Vec<(&str, &str)>) {
hostcalls::set_map(MapType::HttpRequestHeaders, headers).unwrap()
}
# callback
fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
Action::Continue
}
Rust
복사
Stream Context 중 하나인 HttpContext의 method 예
•
Context: Context Root, Stream 둘로 나뉘는데, 구현 관점에서는 그냥 hostcall/callback이 plugin 관점과 특정 stream 또는 request 관점으로 나뉜 것일 뿐이다. Rust에서는 이들을 재정의하여 사용한다.
◦
Root(Plugin) Context: 사실 상 plugin 자체를 의미. plugin 수준에서의 method를 노출한다. configuration 초기화에 주로 사용된다.
◦
Stream Context: 사실 상 특정 request(HTTP)나 stream(TCP)를 의미. request/stream 수준에서의 method를 노출한다. 사실 상 plugin biz logic이 동작하는 위치이다.
Feature list
대강 function 명만으로도 어떤 기능을 담당하는지 짐작 가능하다. 앞선 코드와는 달리 callback method prefix에 proxy_on 이 붙는다. Rust의 경우, 앞서 논했듯 구현 관점에서는 상당 수가 Context로 wrapping되어 제공된다. Context로 제공되지 않아도 hostcall module을 통해 직접 접근 가능하다.
Istio 공식 문서에서의 WASM filter extension 설명. 사실 상 아래와 동일 내용이다. 출처: https://istio.io/latest/docs/concepts/wasm/
•
Context Lifecycle: proxy_on_context_create, proxy_on_done, proxy_on_log, proxy_on_delete, proxy_done, proxy_set_effective_context
•
Configuration: proxy_on_vm_start, proxy_on_configure
•
Logging: proxy_log, proxy_get_log_level
•
Clock/Timer: proxy_get_current_time_nanoseconds, proxy_set_tick_period_milliseconds, proxy_on_tick
•
Buffer: proxy_set_buffer_bytes, proxy_get_buffer_bytes, proxy_get_buffer_status
•
HTTP fields: proxy_get_header_map_size, proxy_get_header_map_pairs, proxy_set_header_map_pairs, proxy_get_header_map_value, proxy_add_header_map_value, proxy_replace_header_map_value, proxy_remove_header_map_value
•
HTTP/TCP 공통: proxy_continue_stream, proxy_close_stream, proxy_get_status
•
TCP streams: proxy_on_new_connection, proxy_on_downstream_data, proxy_on_downstream_connection_close, proxy_on_upstream_data, proxy_on_upstream_connection_close
•
HTTP streams: proxy_on_request_headers, proxy_on_request_body, proxy_on_request_trailers, proxy_on_response_headers, proxy_on_response_body, proxy_on_response_trailers, proxy_send_local_response
•
HTTP calls: proxy_http_call, proxy_on_http_call_response
•
gRPC calls: proxy_grpc_call, proxy_grpc_stream, proxy_grpc_send, proxy_grpc_cancel, proxy_grpc_close, proxy_on_grpc_receive_initial_metadata, proxy_on_grpc_receive, proxy_on_grpc_receive_trailing_metadata, proxy_on_grpc_close
•
공유 key-value store: proxy_set_shared_data, proxy_get_shared_data
•
공유 queue: proxy_register_shared_queue, proxy_resolve_shared_queue, proxy_enqueue_shared_queue, proxy_dequeue_shared_queue, proxy_on_queue_ready
•
Metrics: proxy_define_metric, proxy_record_metric, proxy_increment_metric, proxy_get_metric,
•
Properties: proxy_get_property, proxy_set_property
•
Foreign function interface(FFI): proxy_call_foreign_function, proxy_on_foreign_function
Envoy filter chain 상 Proxy-Wasm 적용 위치
아래는 일반적인 Istio/Envoy의 filter chain 구성으로, 인입된 request는 아래 순서대로 filter가 적용된다. 적용 순서는 선후 관계에 따라 최종 결과 달라질 수 있기 때문에 중요하다. wasm은 적용 위치 설정에 따라 이들 filter 중 하나를 중심으로 앞 또는 뒤로 적용할 수 있는데, EnvoyFilter 와는 달리
IstioWasm Plugin 의 phase 는 적용 위치가 몇몇 개로 한정된다.


1.
(listener) envoy.filters.listener.tls_inspector: SNI 및 ALPN(Application-Layer Protocol Negotiation; TLS handshaking 시 client/server 간의 프로토콜 협상 메커니즘) 결과를 식별하여, filter chain을 선택
2.
(HTTP) istio.metadata_exchange
3.
(HTTP) envoy.filters.http.grpc_stats
4.
(HTTP) istio.alpn
5.
(HTTP) envoy.filters.http.fault
6.
(HTTP) envoy.filters.http.cors
7.
(HTTP) istio.stats
8.
(HTTP) envoy.filters.http.router