Introduction

mockserver 예제을 통해 EnvoyFilter API를 요약한다. 예제는 EnvoyFilter 속성의 아주 일부만 다루지만, 전반의 구조를 다루고 있기에 나머지는 공식 문서를 통해 응용이 가능하리라 생각한다.

글 전반으로 아래 Istio 공식 문서의 것을 참고했다.

Envoy FilterCustomizing Envoy configuration generated by Istio.
https://istio.io/latest/docs/reference/config/networking/envoy-filter/

EnvoyFilter 란

istiod에 의해 생성된 Envoy configuration에 대해 customization을 가능하게 하는 Istio API. configuration의 특정 field 수정 뿐 아니라 Envoy의 새로운 Listener, Cluster마저 생성 가능하다고. 주로 traffic 전처리를 위한 Envoy network filter chain에 custom filter 삽입을 위한 용도로 사용될 듯. 이 글의 mock server가 딱 이 경우에 해당한다.

VirtualService 를 통한 mockserver 구현에 대해
단순 mockserver라면, 복잡한 EnvoyFilter를 쓸 필요 없이 VirtualService 의 directResponse 로도 가능하다.

하지만 directResponse 는 delay를 지원하지 않아 VirtualService의 fault.delay 를 자연스럽게 고려하게 되는데, 이 경우 Istio는 directResponse 와 fault 는 동시 사용이 불가능하다는 오류를 뱉는다.

EnvoyFilter 특징

  • sidecar 및 gateway 전체에 적용하려면 root namespace(e.g. istio-system)에 workloadSelector 지정 없이 생성한다. 이 말은 workload(e.g. pod)의 namespace 별로 적용이 가능하다는 뜻임과 동시에, 특정 workload 별로도 적용이 가능하다는 뜻이다.
  • 타 Istio object와 달리 (override 가 아닌) 추가적인 방식으로 동작하여 여러 EnvoyFilter를 적용 가능하다.
  • 여러 EnvoyFilter 적용 시 root namespace의 EnvoyFilter 가 workload namespace의 것보다 앞서 적용되며, 동일 namespace에서는 생성 시간 순서를 따른다.
  • EnvoyFilter 간 충돌을 일으키는 설정이 있을 경우 동작은 정의되지 않는다.
  • 하위 호환을 지원하나, (그래도) Istio proxy version 업그레이드 시 deprecated field 확인이 필요하다.

mockserver 예제

EnvoyFilter 를 사용하면 별도의 구축 없이 mockserver 생성이 가능하다. 아래는 이에 대한 예제로 이 mockserver는 다음과 같은 특징을 갖는다.

  • 적용 대상: cluster namespace에 위치한 app: dockebi 란 label을 가진 모든 pod

  • 적용 조건: path가 /mock 인 모든 traffic

  • 적용 효과: 해당 traffic을 pod로 보내지 않고, x-envoy-fault-delay-request header에 지정된 시간(ms 단위) 만큼 지연을 일으킨 후 client로 응답. 다음은 Response 주요 속성이다.

    • Status code: 200

    • Header 및 value

      • Content-Type: application/json
      • x-mock-header: mock response header!
    • Body

      • {"message": "Delayed mock response works!"}

아래는 mocserver EnvoyFilter manifest로, 주요 field에 대한 설명은 주석으로 넣었다. Listener configuration(LDS) 구조를 배경 지식으로 요구하는데, 이에 대해서는 Listener discovery service (LDS) 참고.

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: dockebi-test
  namespace: cluster             # [적용 대상 식별] cluster namespace에 한정하여 적용
spec:
  workloadSelector:
    labels:
      app: dockebi               # [적용 대상 식별] app: dockeb인 workload에만 적용
      
  configPatches:
	  # 하기 적용 후 filter chain: ... -> envoy.filters.http.fault -> envoy.filters.http.lua -> envoy.filters.http.router
    - applyTo: HTTP_FILTER       # [적용 위치 식별] HTTP_FILTER는 적용 위치가 http connection manager의 HTTP filter chain임을 나타냄
      match: 
        context: SIDECAR_INBOUND # [적용 위치 식별] sidecar의 Inbound listener/route/cluster
        listener:                # ListenerMatch 사용
          portNumber: 8080       # 8080 port의 Listener에만 적용. 없으면 모든 Listener에 적용됨.
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
              subFilter:
                name: "envoy.filters.http.router"
      patch:
        operation: INSERT_BEFORE # match에 정의된 envoy.filters.http.router 앞에 하기 내용을 적용
        value:
          name: envoy.filters.http.fault # filter 이름
          typed_config:
            "@type": "type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault" # HTTPFault type의 filter
            max_active_faults: 100       # 최대 동시 fault(이 경우 delay) 적용 percentage
            delay:
              header_delay: {}           # x-envoy-fault-delay-request header를 통한 delay 적용 시 필수
              percentage:
                numerator: 100           # delay가 적용될 traffic percentage
                
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          portNumber: 8080
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
              subFilter:
                name: "envoy.filters.http.router"
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.lua
          typed_config:
            "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" # Lua type의 filter
            defaultSourceCode:           # Lua source code.
              inlineString: |
                function envoy_on_request(request_handle) 
                  headers = request_handle:headers()
                  
                  -- /mock path traffic 이외는 무시
                  if headers:get(":path") ~= "/mock" then
                    return
                  end
                  
                  -- Info level로 logging.
                  request_handle:logInfo("x-envoy-fault-delay-request: " .. headers:get("x-envoy-fault-delay-request"))
                  
                  -- Responsing. 1st param은 header, 2nd param은 body. :status는 status code를 나타냄.
                  request_handle:respond({
                      [":status"] = "200",
                      ["content-type"] = "application/json",
                      ["x-mock-header"] = "mock response header!"
                    },
                    "{\"message\": \"Delayed mock response works!\"}")
                end
YAML

적용 방법은 kubectl apply -f envoyfilter.yaml 와 같이 일반적인 Kubernetes CR 적용 방법과 동일하다.

아래는 위 EnvoyFilter 를 적용한 후의 /mock endpoint 호출 및 응답 결과이다.

# prefix /dockebi/mock은 /mock endpoint에 대한 외부 주소이다.
# Gateway가 /dockebi를 제거하고 dockebi app으로 traffic을 전달한다.
❯ curl https://api.anyflow.net/dockebi/mock -H "X-Color: blue" -H "x-envoy-fault-delay-request: 100" -i
...
HTTP/2 200 
content-type: application/json
x-mock-header: mock response header!
content-length: 43
date: Sun, 05 May 2024 17:04:09 GMT
server: istio-envoy
x-envoy-upstream-service-time: 103

{"message": "Delayed mock response works!"}
BASH

Debugging 방법

EnvoyFilter 를 debugging하려면 적용 대상에 정상적으로 위치했는지, 그리고 traffic 발생 시 실행되는 Lua source code에 대한 log를 확인해야 한다.

적용 대상에 정상적으로 위치했는지 확인하기 위해서는 해당 pod의 Istio proxy configuration을 확인한다. 위 mockserver의 경우 Listener에 위치하며, x-mock-header 란 token이 사용되므로 아래와 같이 확인할 수 있다.

❯ istioctl proxy-config listener {pod name of app:dockebi}.{namespace} -o yaml | grep x-mock-header
...
                      ["x-mock-header"] = "mock response header!"
                      ["x-mock-header"] = "mock response header!"
BASH

또한, Lua script 동작에 대해서는 Istio proxy의 로그를 통한다. Istio proxy에는 다양한 logger가 있어, 이 중 Lua code에 대한 logger 식별자는 lua 인데, default level이 warning 이기에 위 EnvoyFilter 내 코드의 로그는 볼 수 없다. 다행히도 실시간으로 logger level을 조절 가능하므로 아래 istioctl command를 통해 info level로 변경한다.

❯ istioctl proxy-config log {pod name of app:dockebi}.{namespace} --level lua:info
BASH

이후 kubectl logs command를 사용하여 Istio proxy의 로그를 확인한다. 참고로 syntax error 등 이슈가 있을 경우 listener에 code가 loading이 안되는데, 이 오류 또한 로그를 통해 확인 가능하다.

❯ kubectl logs {pod name of app:dockebi}.{namespace} -c istio-proxy -f | grep "script log"
...
2024-05-05T17:04:09.504889Z     info    envoy lua external/envoy/source/extensions/filters/http/lua/lua_filter.cc:920     script log: x-envoy-fault-delay-request: 100    thread=36
BASH

References

Envoy FilterCustomizing Envoy configuration generated by Istio.
https://istio.io/latest/docs/reference/config/networking/envoy-filter/
Lua — envoy 1.31.0-dev-8d4cf0 documentationThe HTTP Lua filter allows Lua scripts to be run during both the request and response flows. LuaJIT is used as the runtime. Because of this, the supported Lua version is mostly 5.1 with some 5.2 features. See the LuaJIT documentation for more details.
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter
Fault Injection — envoy 1.31.0-dev-29d30a documentationThe fault injection filter can be used to test the resiliency of microservices to different forms of failures. The filter can be used to inject delays and abort requests with user-specified error codes, thereby providing the ability to stage different failure scenarios such as service failures, service overloads, high network latency, network partitions, etc. Faults injection can be limited to a specific set of requests based on the (destination) upstream cluster of a request and/or a set of pre-defined request headers.
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/fault_filter

Comments