--- title: Envoy는 listener의 network filter와 HCM의 HTTP filter chain으로 요청을 처리하며, Lua·Wasm·external processing으로 재컴파일 없이 확장된다 date: 2026-06-07 type: note domain: istio tags: [envoy, filter-chain, hcm, wasm, envoyfilter] --- > [!abstract] 이 문서가 다루는 것 > Envoy의 요청 처리는 **두 겹의 filter chain**이다 — L4(connection) 단의 **network filter chain**과, 그 안의 HTTP connection manager(HCM)가 여는 **L7 HTTP filter chain**. 이 한 장의 그림만 잡으면 두 가지가 동시에 풀린다: ① "L7 정책(JWT·CORS·L7 RBAC·path 라우팅)이 왜 안 먹는가" 같은 진단, ② Lua·Wasm·external processing이라는 세 확장 경로가 사실은 *같은 L7 chain에 필터 하나를 더 끼우는 같은 일*이라는 것. 결론부터: 확장은 별개 메커니즘이 아니라 전부 HCM의 L7 chain 조작이고, Istio에서 그 chain을 직접 건드리는 escape hatch가 `EnvoyFilter`다. 운영 가드레일·우선순위 사다리는 [xDS 5계층과 진단](/docs/istio/xds-envoy/xds-layers-and-diagnosis/) §07로 위임하고, 이 note는 **멘탈모델**에 집중한다. > > 대상환경 Istio 1.30 / Envoy · 대상독자 L7 정책·확장이 "조용히 무시되는" 이유를 메커니즘으로 알고 싶은 DevOps/SRE · 범위 filter chain 구조와 확장 주입 메커니즘(가드레일은 링크) · 선행개념 [xDS layer 개관](/docs/istio/xds-envoy/xds-api-layers/), listener/route의 LDS/RDS 관계. --- ## 01. 배경 — 왜 "두 겹"이어야 했나 프록시가 풀어야 하는 근본 긴장은 이거다: 한쪽 끝에는 **raw TCP 바이트**(커널이 주는 건 이게 전부다)가 있고, 다른 쪽 끝에는 **HTTP 의미**(이 요청의 path는 뭐고 JWT는 유효한가, 이 method를 이 principal이 호출해도 되는가)로만 쓸 수 있는 정책이 있다. 바이트 단에서는 "GET /admin"이라는 개념 자체가 없으니 path 기반 RBAC을 적용할 방법이 없다. 그렇다고 모든 커넥션을 HTTP로 파싱하면, 순수 TCP(DB, Redis, mTLS passthrough)까지 깨진다. Envoy의 답은 **두 레이어를 중첩**시키는 것이다. 바깥은 바이트를 다루는 **L4 network filter chain**, 안쪽은 그것을 HTTP 객체로 승격시킨 뒤 도는 **L7 HTTP filter chain**. 그리고 두 레이어를 잇는 경첩이 단 하나의 컴포넌트 — **HCM(`http_connection_manager`)** 이다. HCM이 chain에 놓이면 그 지점부터 L7 세계가 열리고, 없으면 그 커넥션은 영원히 바이트 단에 머문다. 이 구조를 모르면 Istio에서 가장 흔한 함정에 그대로 빠진다: AuthorizationPolicy를 정확히 썼는데 path match가 무시된다, VirtualService 라우팅이 안 먹는다 — 정책 오타가 아니라 **그 포트에 HCM이 안 깔린 것**(L7 chain 자체가 없는 것)이 원인인 경우다. 그래서 이 note는 진단의 토대이기도 하다. ## 02. 핵심 그림 — L4 chain ⊃ HCM ⊃ L7 chain 머릿속에 박을 단 하나의 그림: **HCM은 "또 하나의 network filter"다.** L4와 L7은 별도 시스템이 아니라, L4 chain의 한 슬롯에 HCM이 들어앉으면 거기서부터 L7 chain이 *열리는* 중첩 구조다. 나머지는 전부 여기서 따라 나온다. 커넥션이 listener에 도착하면 바이트는 곧장 HTTP가 되지 않고 먼저 **L4 network filter chain**을 통과한다. network filter는 raw TCP 스트림을 다루는 필터다 — `tls_inspector`(ClientHello에서 SNI/ALPN 추출), `http_connection_manager`(HCM), `tcp_proxy`(L4 그대로 upstream에 전달) 등. L4 chain의 슬롯에 무엇이 들어가느냐가 그 커넥션의 운명을 가른다. - 슬롯에 **HCM**이 들어가면 → 바이트가 request line/header/body로 파싱되고, 그 안에서 **별도의 L7 HTTP filter chain**이 돈다. path·header·JWT·L7 RBAC이 전부 이때 적용된다. - 슬롯에 **`tcp_proxy`**만 있으면 → L7 해석 없이 통과(SNI 기반 passthrough, mTLS, L4 RBAC까지만). 이것이 "포트가 HTTP로 인식 안 되면 라우팅/정책이 안 먹는" 증상의 근본 이유다. ```mermaid flowchart TB CONN["TCP connection on listener"] subgraph L4["L4 network filter chain"] INSPECT["tls_inspector / http_inspector"] PICK["filter_chain_match (SNI/ALPN/port)"] HCM["http_connection_manager"] TCPPROXY["tcp_proxy (non-HTTP path)"] end subgraph L7["L7 HTTP filter chain (inside HCM)"] CORS["cors"] JWT["jwt_authn"] RBAC["rbac (AuthorizationPolicy)"] LUA["lua / wasm / ext_proc"] ROUTER["router (terminal)"] end CONN --> INSPECT --> PICK PICK --> HCM PICK --> TCPPROXY HCM --> CORS --> JWT --> RBAC --> LUA --> ROUTER ``` 이 그림이 xDS와 어떻게 맞물리는지도 한 줄로 잡아두면 진단이 쉬워진다: L4 network filter chain은 [LDS](/docs/istio/xds-envoy/xds-layers-and-diagnosis/)로 내려오는 listener 설정의 일부이고, HCM 안의 route 결정은 [RDS](/docs/istio/xds-envoy/envoy-routing-chain-debugging/)가 채운다. 즉 한 listener = "L4 chain + (HCM이면) L7 chain + route table"의 합성이다. ## 03. HCM이라는 경첩 — 바이트→HTTP 추상화 경계 HCM이 왜 그렇게 중요한지는 "그 하나가 무엇을 책임지는가"를 보면 분명해진다. HCM은 Envoy에서 **L4와 L7을 가르는 단일 컴포넌트**이고, 그 책임이 곧 L7 세계의 존재 조건이다. - **프로토콜 파싱/협상**: HTTP/1.1, HTTP/2, (설정 시) HTTP/3를 인식해 바이트를 request/response 객체로 변환. ALPN·`http2_protocol_options`로 결정. - **HTTP filter chain 실행**: 파싱된 요청을 §04의 L7 필터들에 순서대로 통과시킴. 마지막 `router` 필터가 route table을 보고 upstream cluster를 선택. - **route table 연결**: 인라인(`route_config`) 또는 RDS(`rds`)로 받은 route configuration을 참조 — virtual host → route entry → cluster. - **공통 L7 처리**: access logging, request ID 생성/전파(`x-request-id`), tracing span, timeout/retry의 HTTP 의미 적용, header 조작. 여기서 "왜"가 나온다: 이 네 책임이 **전부 HCM 안에서만** 일어나기 때문에, path 라우팅·JWT 검증·CORS 같은 순수 L7 처리는 HCM이 없으면 애초에 성립하지 않는다. Istio가 포트를 HTTP로 식별하지 못하면(이름 없는 포트, `appProtocol`/포트명 prefix 미지정 등) HCM 대신 `tcp_proxy`가 깔리고, 이 경우 라우팅·JWT·CORS는 조용히 동작하지 않는다. 다만 **AuthorizationPolicy는 예외다** — path 등 HTTP 속성만으로 조건을 건 DENY 정책이 이런 비-HTTP 포트에 걸려 있으면, Istio는 이를 network 레벨 RBAC 필터로 내려보내는데 path 조건을 L4에서 판정할 수 없어 **그 포트 전체를 fail-closed로 차단**한다. 실측(T63)에서도 `kubectl apply` 시점에 Istio 스스로가 `AuthorizationPolicy will deny all traffic to TCP ports ... due to the use of only HTTP attributes in a DENY rule`라고 경고했고, 실제로 경로 유무와 무관하게 4번의 요청 모두 HTTP 에러 코드 없이 연결이 끊겼다(empty reply, 액세스 로그의 `rbac_access_denied_matched_policy`). 즉 "무시냐 거부냐"가 문제가 아니라, **HTTP 조건의 AuthorizationPolicy는 비-HTTP 포트에서 오히려 전체 차단으로 fail-closed 된다는 비대칭**이 진짜 함정이다. ```text L4만 (tcp_proxy) : SNI 기반 passthrough, mTLS, L4 AuthorizationPolicy까지 L4 + HCM (L7 filters) : path/header 라우팅, JWT, CORS, L7 RBAC, retry, Lua/Wasm/ext_proc ``` ## 04. L7 chain의 순서와 종결성 — 위치가 곧 정책 의미 HCM 안의 HTTP filter chain은 **순서가 있는 파이프라인**이다. 요청은 위에서 아래로(decoder 방향), 응답은 아래에서 위로(encoder 방향) 흐른다. 각 필터는 `Continue`(다음 필터로)·`StopIteration`(보류, 비동기 처리 후 재개)·직접 응답(예: RBAC deny → 403) 중 하나를 반환한다. 체인의 **마지막은 항상 `router` 필터**다 — 이것이 terminal filter로, route를 평가해 upstream으로 요청을 보낸다. 한번 router를 지나면 요청은 이미 떠난 뒤다. 따라서 **모든 확장 필터는 반드시 `router` 앞에** 위치해야 한다. router 뒤에 필터를 끼우면 '호출은 되지만 아무 효과 없이 통과'되는 게 아니다 — Envoy는 config validation 단계에서 그 listener 설정 자체를 거부(reject)한다. 실제 에러는 `terminal filter named envoy.filters.http.router ... must be the last filter in a http filter chain`이며, 이 검증은 Envoy PR #7779(2019-08-13 머지)로 도입돼 Istio 1.30이 쓰는 Envoy 버전에도 그대로 남아 있다. 즉 증상은 '조용한 무효화'가 아니라, 그 설정이 istiod→Envoy 반영 단계에서 애초에 거부(NACK)되는 것으로 나타난다. Istio가 자동으로 까는 대표 L7 필터(요청 방향 순서, 대략): ```text istio.metadata_exchange → cors → fault → (jwt_authn) → (rbac: AuthorizationPolicy) → (확장: lua/wasm/ext_proc) → istio.stats → router (terminal) ``` (참고: 과거 있던 `istio_authn` 필터는 Istio 1.20 전후로 완전히 제거됐다 — Istio 1.30 기본 체인에는 등장하지 않으며, mTLS/PeerAuthentication 처리는 이 필터가 아니라 다른 경로로 이뤄진다.) 여기가 이 note의 보안적 핵심이다 — **확장 필터를 어디에 끼우느냐가 곧 정책 의미를 바꾼다.** `INSERT_BEFORE`/`INSERT_AFTER`/`INSERT_FIRST`로 router 또는 rbac 같은 특정 필터 기준 상대 위치를 정하는데, 인증/인가 필터보다 **앞**에 둔 Wasm은 미인증·미허가 요청도 그대로 보게 되고, **뒤**에 두면 통과된 요청만 본다. 즉 같은 코드라도 위치 하나로 보안 우회가 생기거나 막힌다. "router 앞에"만 외우면 안 되고, rbac/jwt_authn 기준 상대 위치를 의식해야 한다. ## 05. 재컴파일 없는 3가지 확장 경로 — 같은 chain, 다른 실행 장소 이제 핵심 통찰로 묶인다: Envoy를 fork·재빌드하지 않고 로직을 주입하는 정식 경로는 셋인데, **모두 HCM의 L7 chain에 새 필터를 하나 추가하는 같은 일**이다. 다른 건 메커니즘이 아니라 *그 필터의 코드가 어디서 실행되는가*뿐이다 — Envoy 프로세스 안인가, sandbox VM인가, 네트워크 건너 별도 서버인가. 이 한 축(실행 장소)으로 트레이드오프 전체가 결정된다. | 경로 | 실행 위치 | Istio 표면 | 적합 / 한계 | |---|---|---|---| | **Lua** | Envoy 프로세스 내(인라인 스크립트) | `EnvoyFilter`의 `envoy.filters.http.lua` | 가벼운 header/body 조작·간단 분기. 무거운 로직·외부 호출엔 부적합(이벤트루프 블로킹 위험) | | **Wasm** | Envoy 내 sandbox VM (proxy-wasm ABI) | **`WasmPlugin`** (정식 API) + ECDS push | 격리된 커스텀 로직, 언어 자유(Rust/Go/C++→wasm). VM 오버헤드·디버깅 난이도 존재 | | **External processing** | **별도 gRPC 서버**(보통 sidecar/외부 서비스) | `EnvoyFilter`의 `ext_proc`(또는 `ext_authz`) | 무겁거나 외부 의존(레거시 auth, DLP)을 프로세스 밖으로. 네트워크 RTT·가용성이 데이터패스에 들어옴 | ```mermaid flowchart LR REQ["HTTP request in HCM chain"] LUA["Lua\n(in-process script)"] WASM["Wasm VM\n(sandbox, ECDS)"] EXT["ext_proc / ext_authz\nexternal gRPC"] UP["router → upstream cluster"] REQ --> LUA --> UP REQ --> WASM --> UP REQ --> EXT -. gRPC call .-> SVC["processing service"] EXT --> UP ``` 선택 기준의 멘탈모델은 "실행 장소" 축에서 바로 읽힌다: **로직이 가볍고 동기면 Lua(in-process), 커스텀·격리·이식성이 필요하면 Wasm(`WasmPlugin`), 외부 시스템/무거운 처리면 external processing**. in-process(Lua/Wasm)는 네트워크 의존이 없는 대신 무거운 로직이 Envoy worker thread를 블로킹하고, ext_proc는 그 부담을 프로세스 밖으로 내보내는 대신 외부 서버의 RTT·가용성이 데이터패스에 들어온다. Wasm에는 추가로 알아야 할 채널 디테일이 하나 있다. Istio 1.30에서 Wasm은 `WasmPlugin` CR로 1급 지원되며, 그 확장 설정은 일반 LDS/RDS가 아니라 **ECDS(Extension Config Discovery Service)** 로 별도 push된다. listener는 "여기에 확장 필터가 들어간다"는 placeholder만 받고, 실제 필터 설정(wasm 바이너리 참조 등)은 ECDS로 따로 내려오는 구조다 — 그래서 `WasmPlugin`이 안 먹을 때 listener만 봐선 안 된다. 다만 `istioctl proxy-status`의 `ECDS` 컬럼을 보라는 것은 옛 이야기다: 실측(T18, Istio 1.30.0) 기준 `proxy-status`는 CDS/LDS/EDS/RDS/ECDS 개별 컬럼이 아니라 **`SUBSCRIBED TYPES`** 요약 컬럼 하나만 보여주고(예: `4 (CDS,LDS,EDS,RDS)`), ECDS를 안 쓰면 `NOT SENT`로 찍히는 게 아니라 그 목록에서 **아예 빠진다** — `NOT SENT`/`ECDS`라는 문자열 자체가 현재 출력에 없다([proxy-status 해석](/docs/istio/xds-envoy/xds-layers-and-diagnosis/) §05). ## 06. EnvoyFilter — chain에 patch를 직접 삽입하는 메커니즘 `WasmPlugin`/`Telemetry` 같은 1급 API로 표현되지 않는 변경은 `EnvoyFilter`로 직접 chain을 patch한다. 멘탈모델은 단순하다: `EnvoyFilter`는 새 설정을 만드는 게 아니라 **istiod가 이미 만든 Envoy 설정 위에 patch 연산을 얹는다.** 그래서 강력하지만 깨지기 쉽다 — patch 대상이 istiod가 만든 내부 구조이고, 그 구조는 upgrade마다 바뀔 수 있기 때문이다. patch는 4요소로 "어디에 / 무엇을 / 어떻게 / 무슨 값"을 지정한다: ```text applyTo : LISTENER | FILTER_CHAIN | NETWORK_FILTER | HTTP_FILTER | CLUSTER | ROUTE_CONFIGURATION ... match : context(SIDECAR_INBOUND/OUTBOUND/GATEWAY) + listener/filterChain/filter 이름·포트 patch.operation : INSERT_BEFORE | INSERT_AFTER | INSERT_FIRST | MERGE | ADD | REMOVE patch.value : 끼워 넣거나 병합할 raw Envoy config (proto JSON) ``` HTTP filter를 추가하는 전형적 형태는 `applyTo: HTTP_FILTER` + `match`로 HCM 안 `envoy.filters.http.router`를 지목하고 `INSERT_BEFORE`로 새 필터를 그 앞에 넣는 것이다 — §04에서 본 대로 **router 앞 삽입이 거의 모든 확장의 정석 위치**다. 적용 우선순위/순서를 결정하는 요인은 셋이고, 셋 다 "의도한 곳에만, 충돌 없이" 들어가게 하는 장치다: - **`patch.match`의 구체성**: 이름·포트·context로 좁힐수록 의도한 listener/chain에만 적용. 매칭이 헐거우면 의도치 않은 chain까지 patch된다. - **여러 EnvoyFilter 간 순서**: `metadata.name` 하나로 정해지는 게 아니라 `priority` 필드 → creation timestamp → fully qualified resource name(`namespace/name`) 순 3단 우선순위로 정해지고(이름은 앞 두 기준이 동률일 때만 쓰는 최후 tie-breaker), root namespace의 EnvoyFilter가 workload namespace보다 먼저 적용된다. 그럼에도 같은 지점을 여러 EnvoyFilter가 건드리면 **결과는 사실상 undefined** — 충돌 시 동작이 보장되지 않는다. - **`workloadSelector`**: 없으면 namespace(또는 root namespace면 mesh) 전체에 적용 → blast radius가 커진다. > [!warning] 이 note의 범위 경계 > `EnvoyFilter`의 **운영 가드레일**(누가 merge하나, root namespace 금지 규칙, upgrade마다 재검증, config_dump diff, 우선순위 사다리 `VS→DR→Telemetry→WasmPlugin→EnvoyFilter`)은 여기서 반복하지 않는다 — [xDS 5계층과 진단](/docs/istio/xds-envoy/xds-layers-and-diagnosis/) §07에 정리됨. 이 note는 "chain에 어떻게/어디에 들어가는가"라는 메커니즘만 다룬다. ## 07. 예시 — 실제 chain을 눈으로 확인하기 추상을 믿지 말고 Envoy가 받은 진짜 filter chain을 봐야 한다. listener 안의 network filter와 HTTP filter는 `proxy-config listener -o json`에 그대로 드러난다. ```bash # HCM 안의 HTTP filter 순서 확인 (lua/wasm/ext_proc가 router 앞에 있는지) istioctl proxy-config listener -n -o json \ | jq '.. | objects | select(.name=="envoy.filters.network.http_connection_manager") | .typedConfig.httpFilters[].name' ``` 기대 출력(예시 — Istio가 까는 기본 + 확장 1개): ```text "istio.metadata_exchange" "envoy.filters.http.cors" "envoy.filters.http.fault" "istio.alpn" "envoy.filters.http.rbac" "example.my-wasm-plugin" ← WasmPlugin/EnvoyFilter로 끼운 확장 "istio.stats" "envoy.filters.http.router" ← 항상 마지막 ``` 이 출력 하나로 §02~§06이 전부 검증된다: ① 이 리스트가 나온다는 것 = HCM이 깔렸다는 것(L7 chain 존재), ② 확장 필터(`example.my-wasm-plugin`)가 `router` 앞에 있다는 것 = §04의 종결성 규칙 충족, ③ rbac 뒤·router 앞에 있다는 것 = 인가된 요청만 보는 위치. **확장이 보이지 않으면** 다음 순서로 의심한다: 1. `WasmPlugin`/`EnvoyFilter`의 `match`/`workloadSelector`가 이 pod를 안 잡았다 → selector/namespace 확인. 2. ECDS sync 실패 → §05의 별도 채널 문제. (단 Istio 1.30의 `istioctl proxy-status`엔 별도 `ECDS` 컬럼이 없다 — `SUBSCRIBED TYPES` 컬럼과 `proxy-status ` 상세 sync-diff로 SYNCED 여부를 확인한다.) 3. HCM이 아예 없다(포트가 HTTP로 인식 안 됨) → 위 jq가 빈 결과 → §03 silent fallback. 이게 L7 정책이 통째로 안 먹는 1번 원인이다. config_dump·clusters 단의 1차 진단은 [Envoy Admin API 진단](/docs/istio/xds-envoy/envoy-admin-api-diagnosis/) 참조. apply 전후를 diff하면 "내가 끼운 필터가 정말 router 앞에 추가됐는지"를 한눈에 확인할 수 있다: ```bash istioctl proxy-config listener -n -o json > before.json kubectl apply -f wasmplugin.yaml istioctl proxy-config listener -n -o json > after.json diff -u before.json after.json # 새 HTTP filter가 router 앞에 추가됐는지 ``` ## 핵심 정리 ```text 2겹 chain : L4 network filter chain(tls_inspector/HCM/tcp_proxy) ⊃ HCM이 여는 L7 HTTP filter chain HCM : 바이트→HTTP 추상화 경계 = L7 세계의 경첩. 없으면(tcp_proxy) path/JWT/CORS는 아예 작동 안 함; 단 HTTP 조건의 AuthorizationPolicy는 예외로 network RBAC가 fail-closed 전체 차단(실측 T63) 순서 : HTTP filter는 파이프라인, 마지막은 항상 router(terminal). 확장은 router 앞 + 인증/인가 필터 기준 상대 위치가 곧 정책 의미 확장 3경로 : 차이는 "실행 장소" 하나 — Lua(in-process 경량) / Wasm=WasmPlugin(sandbox VM, ECDS push) / ext_proc(외부 gRPC) EnvoyFilter: istiod 설정 위에 patch 연산. applyTo+match+operation(INSERT_BEFORE router가 정석)+value 충돌 : 같은 지점 다중 EnvoyFilter = undefined / workloadSelector 없으면 blast radius 큼 확인 : proxy-config listener -o json 의 httpFilters 순서 / proxy-status SUBSCRIBED TYPES ``` 한 문장 요약: **모든 확장은 별개 메커니즘이 아니라 HCM의 L7 filter chain에 필터를 추가하는 같은 일이며, 차이는 그 필터가 어디서 실행되느냐(in-process / sandbox / 외부)뿐이다.** 운영 가드레일은 [xDS 5계층과 진단](/docs/istio/xds-envoy/xds-layers-and-diagnosis/) §07. ## What you might be missing - **HCM은 "또 하나의 network filter"다.** L4와 L7이 별도 시스템이 아니라, L4 chain 안에 HCM이 놓이면 그 지점부터 L7 chain이 열리는 **중첩 구조**다. 그래서 "L7 정책이 안 먹는다"의 1번 원인은 정책 오타가 아니라 **HCM이 안 깔린 것**(포트가 HTTP로 인식 안 됨)이다 — listener를 먼저 보라. - **확장 필터의 위치가 곧 정책 의미다.** RBAC 앞에 둔 Wasm은 미인증 요청도 처리하고, 뒤에 두면 허가된 것만 본다. `INSERT_BEFORE router`만 외우지 말고 **인증/인가 필터 기준 상대 위치**를 의식해야 보안 우회가 안 생긴다. - **Wasm의 설정 채널은 LDS/RDS가 아니라 ECDS다.** `WasmPlugin`이 안 먹으면 listener/route만 보지 말고 ECDS sync 상태도 봐야 한다 — 다만 Istio 1.30의 `istioctl proxy-status`엔 더 이상 별도 `ECDS` 컬럼이 없다(실측 T18). `SUBSCRIBED TYPES` 컬럼에 ECDS가 나열되는지, 그리고 `proxy-status ` 상세 sync-diff로 확인하는 게 지금 맞는 방법이다. - **external processing은 데이터패스에 외부 의존을 들인다.** ext_proc/ext_authz 서버가 느리거나 죽으면 그 요청 경로 전체의 latency·가용성이 그 서버에 종속된다. Lua/Wasm은 in-process라 이 리스크가 없는 대신, 무거운 로직은 Envoy worker thread를 블로킹한다 — 경량은 in-process, 무거운/외부 의존은 ext_proc라는 트레이드오프를 의식할 것. - **여러 EnvoyFilter가 같은 지점을 patch하면 동작은 undefined다.** "잘 되던 EnvoyFilter가 다른 팀 것 추가 후 깨졌다"의 전형. 가능하면 `WasmPlugin`/`Telemetry` 같은 1급 API로 올리고, EnvoyFilter는 최후의 수단으로 남길 것([우선순위 사다리](/docs/istio/xds-envoy/xds-layers-and-diagnosis/) §07). --- ## 검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6) 검증 방법: Envoy/Istio 공식 문서 대조 + homelab k8s 클러스터(istio-vt-t63/t64/t18 네임스페이스) 실측을 병행. 15개 주장 중 2건(router 뒤 삽입 효과, EnvoyFilter 정렬 기준)은 문헌만으로 오류가 확정돼 본문을 교정했고, 1건(istio_authn 필터)은 구버전 서술로 갱신했으며, 2건(비-HTTP 포트에서 L7 AuthorizationPolicy 처리, ECDS 컬럼 표시)은 실측이 문헌 확인 내용을 반증해 본문을 실측 기준으로 교정했다. 나머지는 실측 재현(2건) 또는 1차 문헌 대조로 확인됐다. | 주장 | 판정 | 근거 | |---|---|---| | C1. Envoy 요청 처리는 L4 network filter chain과 HCM이 여는 L7 HTTP filter chain의 중첩 구조다 | ✅ 실측 확인 | [envoyproxy.io/…/http_connection_management](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_connection_management) · [T63 실측](files/verify/T63/result.txt) | | C2. tls_inspector는 ClientHello에서 SNI/ALPN을 추출하는 L4 listener filter다 | ✅ 문헌 확인 | [envoyproxy.io/…/tls_inspector](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/tls_inspector) | | C3. (원문) 비-HTTP 포트에서 HCM 대신 tcp_proxy가 깔리면 L7 정책은 에러 없이 조용히 무시된다 | 🔬 실측 반증 — 본문 교정 | [istio.io/…/protocol-selection](https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/) · [T63 실측](files/verify/T63/result.txt) | | C4. HTTP filter chain은 순서가 있는 파이프라인이며 각 필터는 Continue/StopIteration/직접 응답을 반환한다 | ✅ 문헌 확인 | [envoyproxy.io/…/http_filters](https://github.com/envoyproxy/envoy/blob/main/docs/root/intro/arch_overview/http/http_filters.rst) | | C5. (원문) router 뒤에 필터를 끼우면 호출은 되지만 효과 없이 통과한다 | ❌ 오류 — 본문 교정 | [github.com/envoyproxy/envoy/pull/7779](https://github.com/envoyproxy/envoy/pull/7779) | | C6. 확장 필터의 위치(rbac 기준 전/후)가 인가 판단 결과를 바꾼다 | ✅ 실측 확인 | [T64 실측](files/verify/T64/result.txt) | | C7. 재컴파일 없는 3경로는 Lua/Wasm/external processing이고 Lua는 블로킹 연산 금지다 | ✅ 문헌 확인 | [envoyproxy.io/…/lua_filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter) | | C8. Wasm은 WasmPlugin CR + ECDS로 별도 push되며 listener는 placeholder만 받는다 | ✅ 문헌 확인 | [istio.io/blog/2021/wasm-progress](https://istio.io/latest/blog/2021/wasm-progress/) | | C9. (원문) proxy-status의 ECDS 컬럼에서 NOT SENT는 미사용 시 정상이다 | 🔬 실측 반증 — 본문 교정 | [T18 실측](files/verify/T18/result.txt) | | C10. EnvoyFilter patch는 applyTo+match+operation+value 4요소로 구성된다 | ✅ 문헌 확인 | [istio.io/…/envoy-filter](https://istio.io/latest/docs/reference/config/networking/envoy-filter/) | | C11. (원문) 여러 EnvoyFilter 간 순서는 metadata.name 기준이며 충돌 시 undefined다 | ❌ 오류 — 본문 교정 | [istio.io/…/envoy-filter](https://istio.io/latest/docs/reference/config/networking/envoy-filter/) | | C12. workloadSelector가 없으면 namespace(또는 root면 mesh) 전체에 적용된다 | ✅ 문헌 확인 | [istio.io/…/envoy-filter](https://istio.io/latest/docs/reference/config/networking/envoy-filter/) | | C13. (원문) 기본 L7 필터 순서 예시에 (istio_authn)이 포함된다 | ⚠️ 구버전 서술 — 갱신 | [github.com/istio/istio/issues/51459](https://github.com/istio/istio/issues/51459) | | C14. EnvoyFilter는 새 설정이 아니라 istiod가 생성한 설정 위에 patch를 얹는다 | ✅ 문헌 확인 | [istio.io/…/envoy-filter](https://istio.io/latest/docs/reference/config/networking/envoy-filter/) | | C15. external processing은 외부 서버의 RTT·가용성을 데이터패스에 들이고, in-process는 그 대신 worker thread 블로킹 위험을 진다 | ✅ 문헌 확인 | [envoyproxy.io/…/ext_proc_filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_proc_filter) |