Search
Duplicate
📌

Istio EnvoyFilter via mockserver

Category
as S/W 엔지니어
Tags
Istio
EnvoyFIlter
Lua
Kubernetes
Service Mesh
Created time
2024/05/06

Introduction

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

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를 쓸 필요 없이 VirtualServicedirectResponse 로도 가능하다.
하지만 directResponse 는 delay를 지원하지 않아 VirtualServicefault.delay 를 자연스럽게 고려하게 되는데, 이 경우 Istio는 directResponsefault 는 동시 사용이 불가능하다는 오류를 뱉는다.

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