본문 바로가기
DevOps/Istio

istio EnvoyFilter에 대해 알아보자.

by 비어원 2025. 3. 3.
728x90

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 제공된 구성을 기존 항목에 추가한다. 해당 연산은 applyToROUTE_CONFIGURATION 또는 HTTP_ROUTE인 경우에는 무시된다.
REMOVE 선택된 객체를 항목에서 제거한다. value가 필요하지 않으며 해당 연산은 applyToROUTE_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 MutationRouter 에서의 HTTP response header 관련 구성이 있다. 그 중에 Header Mutation을 사용해보자.

 

문서를 보면 HeaderMutation의 필드 구조를 알 수 있는데 변경에 대한 내용은 mutations 에 기입하고 mutations 안에는 요청, 응답에 대한 헤더 변경을 config.common.mutation_rules.v3.HeaderMutation 타입의 리스트로 구성된다. 여기서 repeated 는 리스트 형식이라고 생각하면 된다.

 

 

config.common.mutation_rules.v3.HeaderMutation 타입을 보면 appendremove로 되어있는데 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.contextGATEWAY로 둔 이유는 어차피 응답은 gateway를 통해 나가기 때문이다. spec.workloadSelector 에서 이미 ingressgateway만 적용하도록 되어있긴 하지만 명시적으로 GATEWAY로 하였다.
  • match.listener.filterChain.filter.name 은 HTTP Connection Manager filter인데 HTTP filter chain을 소유하고 있는 필터이기 때문에 해당 필터를 match 조건으로 걸었다.
  • patch.operationINSERT_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
    ... # 헤더 삭제 필터 포함
  • applyToNETWORK_FILTER인 이유는 HTTP Connection Manager filter의 옵션을 변경 하기 위해서이고 HTTP Connection Manager filter는 Network filter chain 안에 있기 때문이다.
  • match 에서는 변경할 필터인 HTTP Connection Manager filter가 지정된다.
  • HTTP Connection Manager filter의 옵션을 변경 하기 위해 patch.oeprationMERGE 로 두자.
  • 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_transformationPASS_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.nameenvoy.filters.http.router 으로 잡으면 match에 걸리는 타깃이 http connection manager filter가 아니라 router filter가 된다.
  • patch.operationINSERT_BEFORE 으로 하면 타깃인 router filter 앞에 새로운 필터가 추가된다.

 

EnvoyFilter를 위와 같이 변경한 후 proxy-config를 확인해보면 beer1_global_http_header_remove 필터가 router filter 앞에 있는 것을 확인할 수 있다.

728x90

댓글