EnvoyFilter는 istiod에 의해 생성된 envoy 구성을 커스터마이징할 때 사용한다. EnvoyFilter를 사용하면 특정 필드의 값을 변경하거나 특정 필터를 추가하거나 새로운 리스너나 클러스터를 추가할 수 있다.
보통 istio에서는 Envoy 구성을 Gateway
, VirtualService
, DestionationRule
등을 사용하여 Envoy의 지식이 없어도 트래픽 정책에 대한 제한된 구성을 쉽게 구성할 수 있다면 EnvoyFilter를 사용하면 istio에서 제공하는 Custom Resource에 대한 기능 외에도 다양한 Envoy 기능을 자유롭게 사용할 수 있다.
그래서 istio Custom Resource에서 제공하고 있지는 않지만 Envoy에서 제공하는 기능을 사용해야 할 때 EnvoyFilter를 사용하면 된다.
Envoy의 filter 구조
EnvoyFilter를 사용하려면 Envoy에서의 filter 구조에 대해 알아야 한다.
Envoy를 내부적으로 보면 한 워커쓰레드당 크게 세가지 Filter chain으로 구성되어있으며 요청은 순차적으로 해당 Filter chain을 통과한다. 각 Filter chain에 대해 간략하게 알아보자면 다음과 같다. (envoy가 HTTP만을 처리한다고 가정하자.)
- Listener filter chain : SNI 및 TLS 감지 후 처리 (with TLS transport socket)
- Network filter chain : L3/L4 수준의 필터들로 이루어져 있으며 대표적으로 HTTP, Rate Limit, Redis, Kafka와 관련된 필터들을 위치시킬 수 있다. (여러 종류가 있다.) HTTP를 처리하는 경우, HTTP Connection filter가 가장 마지막에 위치해있는다.
- HTTP filter chain : HTTP L7 수준의 필터들로 이루어져 있다. 보통 Router filter가 filter chain의 가장 마지막에 위치해있는다.
보통 VirtualService나 Gateway를 사용하면 istiod가 적절한 enovy proxy (ingress/egress gateway 또는 sidecar)의 Filter chain을 변경하도록 되어있는데, VirtualService나 Gateway에서 구성할 수 있는 기능이 제한되어있다.
- Gateway를 사용하면 특정 포트에 대한 리스너를 열고, 특정 호스트에 대한 TLS를 처리하는 Filter chain을 만들 수 있다.
- VirtualService를 사용하면 HTTP filter 중 Router, Header mutation, cors 등을 처리하는 filter chain을 만들 수 있다.
기본적으로 Gateway로 특정 호스트에 대해 요청을 받을 포트를 지정하고 VirtualService로 기본적인 라우팅룰만 추가한다면 Network filter chain, HTTP filter chain에는 각각 다음과 같은 filter가 존재한다.
- Network filter chain : HTTP connection filter
- HTTP filter chain : metadata_exchange, http.grpc_stats, alpn, http.failt, http.cors, stats, http.router
직접 확인하고 싶다면 다음과 같이 Gateway, VirtualService를 구성하고 proxy-config를 확인하면 된다.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: beer1-com
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*.beer1.com"
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "*.beer1.com"
tls:
mode: SIMPLE
credentialName: bookinfo
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo-ingress
namespace: istio-system
spec:
hosts:
- bookinfo.beer1.com
gateways:
- beer1-com
http:
- route:
- destination:
host: productpage.bookinfo.svc.cluster.local
port:
number: 9080
$ istioctl pc all ingressgateway-845969879c-frb6g -o yaml
그리고 envoy에는 이 외에도 다양한 기능을 가지는 filter를 제공한다. EnvoyFilter를 사용하면 envoy에서 제공하는 다양한 filter를 직접 구성할 수 있다. Network filter와 HTTP filter의 종류는 아래 문서를 참고하면 좋을 것 같다.
Network filters — envoy 1.34.0-dev-68e64b documentation
© Copyright 2016-2025, Envoy Project Authors.
www.envoyproxy.io
HTTP filters — envoy 1.34.0-dev-68e64b documentation
© Copyright 2016-2025, Envoy Project Authors.
www.envoyproxy.io
Envoy의 Filter 구조에 대한 대략적인 흐름을 알아야 실제로 EnvoyFilter를 구현할 때 도움이 되어 간략하게 소개를 하였다. 이제 EnvoyFilter API 필드 구조에 대해 알아보자.
EnvoyFilter API Field
EnvoyFilter는 Filter를 적용시킬 대상과 실제 Filter 내용 및 우선순위로 크게 3가지 필드로 나뉜다.
- 패치를 적용시킬 대상: workloadSelector 또는 targetRefs
- 실제 패치 내용: configPatches
- EnvoyFilter 우선 순위: priority
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-name
namespace: istio-system
spec:
workloadSelector:
labels:
...
targetRefs:
...
configPatches:
...
priority: 1
workloadSelector
EnvoyFilter를 어떤 워크로드에 적용시킬지에 대한 구성으로 envoy-proxy가 들어있는 파드의 레이블을 지정하면 된다. 생략하는 경우에는 동일한 네임스페이스의 모든 워크로드에 적용된다. 만약에 EnvoyFilter가 root namespace에 적용되는 경우에는 모든 네임스페이스에 있는 모든 워크로드에 적용된다.
targetRefs
규칙이 적용될 리소스 항목을 지정한다. 지정된 대상 리소스에 다라 정책이 적용되는 워크로드가 결정된다. 현재는 EnvoyFilter를 적용시킬 수 있는 리소스는 Gateway와 Service가 있다.
targetRefs를 설정하지 않는다면 정책은 selector에 의해 정의된대로 적용된다. workloadSelector 또는 targetRefs 둘 중에 하나만 선택할 수 있다.
priority
priority는 컨텍스트 내에서 패치 세트가 적용되는 순서를 정의한다. 하나의 패치가 다른 패치에 의존한다면 순서는 매우 중요하다.
configPatches
일치 조건을 갖춘 하나 이상의 수정사항을 선언하는 부분으로 실제 필터 내용이 여기에 들어있다.
여기서는 configPatches의 내용이 가장 중요하다. 실제 Filter의 내용을 담고있는 configPatches에 대해 자세히 알아보자.
configPatches
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-name
namespace: istio-system
spec:
configPatches:
applyTo:
match:
content:
proxy:
listener:
routeConfiguration:
cluster:
patch:
operation:
value:
filterClass:
applyTo
패치가 적용되어야 할 Envoy 구성요소를 지정한다. 설정할 수 있는 값은 다음과 같다.
이름 | 설명 |
LISTENER | 리스너에게 패치를 적용시킨다. |
FILTER_CHAIN | 필터 체인에게 패치를 적용시킨다. |
NETWORK_FILTER | 네트워크 필터체인에 패치를 적용시킨다. 기존 필터를 변경하거나 새로운 필터를 추가하기 위해 사용한다. |
HTTP_FILTER | http connection manager에 있는 HTTP 필터체인에 패치를 적용시킨다. 기존 필터를 변경하거나 새로운 필터를 추가하기 위해 사용한다. |
ROUTE_CONFIGURATION | http connection manager에 있는 Route 구성에 패치를 적용시킨다. 지금은 MERGE 연산자만 허용된다. |
VIRTUAL_HOST | Route 구성의 virtual host에 패치를 적용시킨다. |
HTTP_ROUTE | Route 구성에서 일치하는 virtual host의 Route 객체에 패치를 적용시킨다. |
CLUSTER | CDS 결과의 클러스터에 패치를 적용시키거나 새로운 클러스터를 추가할 때 사용한다. |
EXTENSION_CONFIG | ECDS 결과의 Extension config를 추가하거나 수정하기 위해 사용한다. |
BOOTSTRAP | bootstrap 구성에 패치를 적용시킨다. (deprecated) |
LISTENER_FILTER | Listener filter에 패치를 적용시킨다. |
match
match는 applyTo를 기준으로 적절한 객체를 선택한다. match에서는 지정된 프록시에 대해 patch를 적용하기 전에 충족해야 하는 하나 이상의 일치조건을 설정한다. 종류로는 다음과 같다.
이름 | 설명 |
context | 일치시킬 특정 구성 생성 컨텍스트, istiod는 gateway 컨텍스트에서 envoy 구성, 사이드카로의 인바운드 트래픽, 아웃바운드 트래픽을 생성한다. |
proxy | 프록시와 연결된 속성과 일치시킨다. |
listener | envoy 리스너 속성과 일치시킨다. |
routeConfiguration | HTTP route configuration 속성과 일치시킨다. |
cluster | envoy 클러스터 속성과 일치시킨다. |
예를 들어 applyTo가 HTTP_FILTER
로 되어 있다면 리스너에 대한 일치 조건이 있어야 하며, 네트워크 필터 선택은 envoy.filters.network.http_connection_manager
에서, 하위 필터 선택은 삽입이 수행되어야 하는 HTTP filter 상대에서 이루어져야 한다. applyTo가 ClUSTER
로 되어있다면 listener
가 아닌 cluster
에서 일치조건을 작성해야 한다.
patch
patch는 match에서 선택한 객체를 수정하는 방법을 설정한다. patch에 들어있는 필드의 종류는 다음과 같다.
oepration
: patch를 어떻게 적용할지 결정한다.value
: patch되는 객체의 JSON 구성이며, 이는 경로에 있는 기존 proto와 proto merge semantics을 사용하여 병합된다.filterClass
: 필터 삽입 순서를 결정한다.
Operation 종류로는 다음과 같다.
이름 | 설명 |
MERGE | 제공된 구성을 proto merge semantics을 사용하여 생성된 구성과 병합한다. 구성을 전체적으로 적용하려면 REPLACE를 대신 사용하자. |
REPLACE | 명명된 필터의 내용을 새로운 내용으로 변경한다. REPLACE 연산은 applyTo 가 오직 HTTP_FILTER 또는 NETWORK_FILTER 에서만 유효하다. |
ADD | 제공된 구성을 기존 항목에 추가한다. 해당 연산은 applyTo 가 ROUTE_CONFIGURATION 또는 HTTP_ROUTE인 경우에는 무시된다. |
REMOVE | 선택된 객체를 항목에서 제거한다. value가 필요하지 않으며 해당 연산은 applyTo 가 ROUTE_CONFIGURATION 또는 HTTP_ROUTE 인 경우 무시된다. |
INSERT_BEFORE | 명명된 객체 배열에 대한 삽입 연산으로, 일반적으로 순서가 중요한 filter나 route context에서만 유용하다. 해당 연산은 선택한 filter 또는 subfilter 앞에 삽입한다. filter를 선택하지 않으면 지정된 filter가 목록의 맨 앞에 삽입된다. |
INSERT_AFTER | 선택한 filter 또는 subfilter 뒤에 삽입한다. filter를 선택하지 않으면 지정된 filter가 목록의 맨 뒤에 삽입된다. |
INSERT_FIRST | 지정된 filter가 목록의 맨 앞에 삽입된다 |
FilterClass 종류로는 다음과 같다.
이름 | 설명 |
UNSPECIFIED | 컨트롤 플레인은 filter를 삽입할 위치를 결정한다. filter가 다른 filter와 독립적인 경우, filterClass를 지정하지 않아야 한다. |
AUTHN | Istio authentication filters 앞에 삽입 |
AUTHZ | Istio authorization filters 앞에 삽입 |
STATS | Istio stats filters 앞에 삽입 |
아마 authentication filter와 authorization filter는 istio의 authentication, authorization 기능을 사용하면 관련 필터가 생기는 듯 하다.
Authentication
Controlling mutual TLS and end-user authentication for mesh services.
istio.io
Authorization
Shows how to control access to Istio services.
istio.io
예제
사실 EnvoyFilter를 처음 접하면 내용이 어려워서 문서를 봐도 이해하기 쉬운 수준이 아니다. 그래서 보통은 대충 적용 방법 검색한 후 복붙해서 적용하기 마련이다. 하지만 이렇게 끝내면 남는게 없기 떄문에 어떻게 EnvoyFilter를 적용하면 되는지, 구성이 어떻게 바뀌는지 자세히 뜯어보면서 익히면 좋을 것 같아서 정리를 해보았다.
Response Header 삭제
기본적으로 istio로 서비스를 노출하게 되면 다음과 같이 istio와 관련된 response header가 자동으로 만들어진다. 하지만 이렇게 헤더를 노출시키면 보안에 좋지 않을 수 있다. 어떤 서버를 사용하고 있는지 공개한다면 해커 입장에서 보안 위협을 하기가 더 쉬워지기 때문이다. 그래서 이런 헤더를 지워주는 것이 좋다.
VirtualService를 사용하면 response header를 삭제할 수 있다.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo-ingress
namespace: istio-system
spec:
hosts:
- bookinfo.beer1.com
gateways:
- beer1-com
http:
- route:
- destination:
host: productpage.bookinfo.svc.cluster.local
port:
number: 9080
headers:
response:
remove:
- "x-envoy-upstream-service-time"
- "server"
하지만 이 방식은 그렇게 좋은 방법은 아닐 수 있다. istio로 여러 도메인을 노출시킬 수 있는데, 각 도메인마다 VirtualService 구성이 다를 수 있고, headers 구성 depth를 보면 route 하위임을 알 수 있다. 따라서 각 호스트마다의 각 라우팅 룰 별로 헤더를 제거해줘야 하기 때문에 호스트 또는 라우팅 룰이 추가된다면 헤더 삭제 구성도 계속 추가해줘야 한다.
사라져야 할 헤더가 명확한 경우 이를 전역적으로 없애주고 싶을 때 EnvoyFilter를 활용할 수 있다. 헤더를 전역적으로 없애주는 필터를 하나 생성해보자.
1. 적절한 Filter 찾기
HTTP header를 삭제하는 기능을 찾고싶기 때문에 envoy HTTP filter 항목을 찾아보면 된다. 여기서 눈에 띄는 것은 Header Mutation
과 Router
에서의 HTTP response header 관련 구성이 있다. 그 중에 Header Mutation
을 사용해보자.
문서를 보면 HeaderMutation의 필드 구조를 알 수 있는데 변경에 대한 내용은 mutations
에 기입하고 mutations
안에는 요청, 응답에 대한 헤더 변경을 config.common.mutation_rules.v3.HeaderMutation
타입의 리스트로 구성된다. 여기서 repeated
는 리스트 형식이라고 생각하면 된다.
config.common.mutation_rules.v3.HeaderMutation
타입을 보면 append
와 remove
로 되어있는데 remove
에는 삭제할 헤더 이름을 문자열로 나타내면 된다.
일단 기본적으로는 Header Mutation filter는 구성에 없기 때문에 필터를 추가해야 한다. 필터를 추가할지, 변경할지, 삭제할지도 염두해야 한다.
일단 문서를 보고 patch
의 내용을 구성할 수 있다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoy-header-remove
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
# match: TODO
patch:
# operation: TODO
value:
name: beer1_global_http_header_remove
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation"
mutations:
response_mutations:
- remove: "x-envoy-upstream-service-time"
- remove: "server"
- http filter를 추가할 것이기 때문에
applyTo
에는HTTP_FILTER
가 들어간다. patch.value.name
에는 새로운 필터를 추가하는 경우 필터의 이름을 지정할 수 있다. 직접 필터를 추가하는 경우, 다른 필터와 이름이 겹치지 않게 하기 위해서 beer1이라는 prefix를 넣었다.patch.value.typed_config
에 실제 envoy filter의 내용이 들어가고,@type
에는 문서에서 제공하는 type URL을 기입하면 된다. (아래 그림 참조)patch.value.typed_config.@type
외의 필드는 사용할 envoy filter의 필드를 넣으면 된다. 지금은 response header를 삭제하는 설정만 추가하였다.
2. 어디에 넣을지 결정
필터를 추가한다면 해당 필터를 어느 위치에 넣을지 결정해야 한다. 추가할 필터는 HTTP filter chain의 어디에도 들어갈 수는 있지만 router filter보다는 앞에 있어야 한다. 하지만 필요없는 헤더를 삭제하는 기능이기 때문에 HTTP filter chain의 가장 앞자리에 있는 것이 바람직하다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoy-header-remove
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_FIRST
value:
name: beer1_global_http_header_remove
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation"
mutations:
response_mutations:
- remove: "x-envoy-upstream-service-time"
- remove: "server"
match.context
를GATEWAY
로 둔 이유는 어차피 응답은 gateway를 통해 나가기 때문이다.spec.workloadSelector
에서 이미 ingressgateway만 적용하도록 되어있긴 하지만 명시적으로 GATEWAY로 하였다.match.listener.filterChain.filter.name
은 HTTP Connection Manager filter인데 HTTP filter chain을 소유하고 있는 필터이기 때문에 해당 필터를 match 조건으로 걸었다.patch.operation
을INSERT_FIRST
로 두면 HTTP Connection Manager filter의 HTTP filter chain 맨 앞에 해당 필터가 추가된다.
이렇게 EnvoyFilter를 적용시키면 전역적으로 노출시키지 말아야 할 헤더가 사라진다. 하지만 실제로 적용해보면 server 헤더가 사라지지 않는다.
번외: 예외 해결
가끔 예외가 있기 마련이다. 이 때는 검색의 힘을 빌려서 해결하는 수 밖에 없을 것 같다. 검색에서 얻은 결론은 Server
헤더는 HTTP Connection Manager filter에서 server_header_transformation
옵션이 있는데 기본적으로 Server
헤더를 istio-envoy
로 overwrite 하고 있다고 한다. ( server_header_transformation
= OVERWRITE
) 해당 옵션을 PASS_THROUGH
로 변경하면 Server
헤더를 완전히 지울 수 있다고 한다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoy-header-remove
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
server_header_transformation: PASS_THROUGH
- applyTo: HTTP_FILTER
... # 헤더 삭제 필터 포함
applyTo
가NETWORK_FILTER
인 이유는 HTTP Connection Manager filter의 옵션을 변경 하기 위해서이고 HTTP Connection Manager filter는 Network filter chain 안에 있기 때문이다.match
에서는 변경할 필터인 HTTP Connection Manager filter가 지정된다.- HTTP Connection Manager filter의 옵션을 변경 하기 위해
patch.oepration
을MERGE
로 두자. patch.value.typed_config
는 문서를 보고 잘 변경하면 된다.
3. 적용 결과 확인
필터를 모두 적용한 후에는 실제로 server 헤더까지 모두 사라진 것을 확인할 수 있다.
이제 실제로 EnvoyFilter 구성이 어떻게 추가되었는지 확인해보자.
$ istioctl pc all ingressgateway-845969879c-frb6g -o yaml
- 새로 추가된 필터(
beer1_global_http_header_remove
) 가 http filter chain 맨 앞에 위치해있음을 확인할 수 있다. http_connection_manager의 server_header_transformation
이PASS_THROUGH
로 변경됨을 확인할 수 있다.
4. 설정 변경해보기
만약 beer1_global_http_header_remove
필터를 router filter 앞에 위치시키고 싶다면 어떻게 하면 될까? EnvoyFilter 설정을 다음과 같이 변경하면 된다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoy-header-remove
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: beer1_global_http_header_remove
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation"
mutations:
response_mutations:
- remove: "x-envoy-upstream-service-time"
- remove: "server"
match.listener.filterChain.filter.subFilter.name
을envoy.filters.http.router
으로 잡으면match
에 걸리는 타깃이http connection manager filter
가 아니라router filter
가 된다.patch.operation
을INSERT_BEFORE
으로 하면 타깃인router filter
앞에 새로운 필터가 추가된다.
EnvoyFilter를 위와 같이 변경한 후 proxy-config를 확인해보면 beer1_global_http_header_remove
필터가 router filter 앞에 있는 것을 확인할 수 있다.
'DevOps > Istio' 카테고리의 다른 글
istio 버전 업그레이드 (0) | 2025.03.03 |
---|---|
istio와 envoy proxy 세부적으로 파악해보기 (0) | 2025.02.20 |
istio traffic 관리 (3) [Egress Gateway] (0) | 2025.01.18 |
istio traffic 관리 (2) [Gateway] (0) | 2025.01.13 |
istio traffic 관리 (1) [VirtualService & DestinationRule] (0) | 2025.01.12 |
댓글