14장 Extending Istio on the request path

김진원·2025년 5월 24일

Istio

목록 보기
13/16

CloudNet@에서 진행하는 Istio Study 7주차 14장 내용입니다.

📕 This chapter cover

  • Using Istio’s EnvoyFilter resource to configure Envoy directly
  • Using Lua to customize the request path
  • Using WebAssembly to customize the request path

14.1 Envoy’s extension capabilities

14.1.1 Understanding Envoy’s filter chaining

  • 엔보이의 리스너는 네트워킹 인터페이스에 포트를 열고 들어오는 트래픽 수신을 시작하는 방법이다.
  • 궁극적으로 엔보이네트워크 커넥션에서 바이트를 가져와 어떤 방식으로 처리하는 3계층과 4계층(L3/L4) 프록시다.
  • 이것이 아키텍처의 첫 번째 중요 부분인 필터로 이어진다
  • 리스너는 네트워크 스트림에서 바이트를 읽어들인 후 아래 그림 14.2와 같이 다양한 필터 혹은 기능 단계들을 거쳐 처리한다.

  • 엔보이에서 가장 기본적인 필터는 네트워크 필터로, 바이트 스트림에서 인코딩/디코딩 작업을 한다.

  • 스트림에서 필터가 둘 이상 순서대로 동작하도록 설정할 수 있는데, 이를 필터 체인이라고 부른다. 필터 체인을 사용하면 프록시의 기능을 구현할 수 있다.

  • 대표적인 엔보이 프로토콜용 네트워크 필터

    • MongoDB
    • Redis
    • Thrift
    • Kafka
    • HTTP Connection Manager (HCM)

가장 많이 사용하는 네트워크 필터 중 하나가 HttpConnectionManager 이다.

이 필터는 바이트 스트림을 HTTP 기반 프로토콜(즉, HTTP 1.1, HTTP 2, gRPC, 최근에는 HTTP 3 등)의 HTTP 헤더, 바디, 트레일러로 변환하는 것에 관한 세부 사항을 추상화하는 역할을 하며, 그림 14.3에서 볼 수 있다.

  • HttpConnectionManager 는 HTTP 요청을 처리할 뿐 아니라 헤더, 경로 접두사 path prefix 등 요청 속성을 바탕으로 한 요청 라우팅, 액세스 로깅, 헤더 조작도 처리한다.
  • 또 HCM에는 필터 기반 아키텍처가 있어 HTTP 요청에서 동작하는 HTTP 필터를 순서대로 또는 연쇄로 설정하거나 구축할 수 있다.

기본 HTTP 필터의 예시

  • Cross-origin resource sharing (CORS)
  • Cross-site request forgery prevention (CSRF)
  • ExternalAuth 외부 인증
  • RateLimit 속도 제한
  • Fault injection 결합 주입
  • gRPC/JSON transcoding 트랜스코딩
  • Gzip
  • Lua 루아
  • Role-based access control (RBAC) 역할 기반 접근 제어
  • Tap
  • Router 라우터
  • WebAssembly (Wasm) 웹어셈블리

14.1.2 Filters intended for extension

필터 작성 등과 같이 엔보이 바이너리 자체에 변경 사항을 컴파일하지 않고도 엔보이의 HTTP 기능을 확장하는 방법이 있는데, 이 경우 다음 HTTP 필터들을 사용한다.

  • 외부 처리 External processing
  • 루아 Lua
  • 웹어셈블리 Wasm (WebAssembly)

이런 필터를 사용하면 외부 서비스를 호출하도록 설정하거나, 루아 스크립트를 실행하거나, 커스텀 코드를 실행해 HTTP 요청이나 응답을 처리할 때 HCM의 기능을 강화할 수 있다.


14.1.3 Customizing Istio’s data plane

다음 방법 중 하나를 사용해 엔보이 데이터 플레인의 기능을 확장한다.
- 엔보이 HTTP 필터를 이스티오 API에서 EnvoyFilter 리소스로 설정하기
- 속도 제한 서버 RLS, Rate-Limit Server 호출하기
- 루아 스크립트를 구현해 루아 HTTP 필터에 불러오기
- 웹어셈블리 HTTP 필터용 웹어셈블리 모듈(Wasm 모듈) 구현하기



14.2 Configuring an Envoy filter with the EnvoyFilter resource

Tap 필터

  • Istio의 데이터 플레인을 확장하는 첫 번째 단계는 엔보이의 기존 필터가 우리가 찾고 있는 확장 유형을 달성하는 데 충분하지 파악하는 것이다.

  • 필터가 존재하는 경우, EnvoyFilter 리소스를 사용해 이스티오의 데이터 플레인을 직접 설정할 수 있다.

    Istio의 EnvoyFilter 리소스는 Istio의 고수준 API에서 노출하지 않는 엔보이의 일부를 설정하거나 조정해야 하는 고급 사용 사례를 위한 것
    이 리소스는 엔보이의 거의 모든 것을 설정할 수 있으며(일부 제한 있음) 리스너, 루트, 클러스터, 필터가 포함된다.

  • 기저 엔보이 API는 이스티오 버전 간에 언제든 달라질 수 있으니, 배포하는 모든 EnvoyFilter 의 유효성을 반드시 확인해야 한다.

  • 이전 버전과의 호환성은 어떤 것도 가정해서는 안 된다. 이 API를 잘못 설정하면 이스티오 데이터 플레인 전체가 멈출 수도 있다.


예제 서비스 배포

kubectl get envoyfilter -A
NAMESPACE      NAME                    AGE
istio-system   stats-filter-1.13       12m
istio-system   stats-filter-1.14       12m
istio-system   stats-filter-1.15       12m
istio-system   stats-filter-1.16       12m
istio-system   stats-filter-1.17       12m
istio-system   tcp-stats-filter-1.13   12m
istio-system   tcp-stats-filter-1.14   12m
istio-system   tcp-stats-filter-1.15   12m
istio-system   tcp-stats-filter-1.16   12m
istio-system   tcp-stats-filter-1.17   12m

# 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
kubectl apply -f ch9/sleep.yaml -n istioinaction

# 확인
kubectl get gw,vs,dr -n istioinaction
NAME                                            AGE
gateway.networking.istio.io/coolstore-gateway   50s

NAME                                                       GATEWAYS                HOSTS                         AGE
virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   50s


# 호출 확인 : mypc
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog

# 신규 터미널 : mypc 에서 반복 접속 
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; echo ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 호출 확인 : 자신의 호스트 PC
curl -s http://webapp.istioinaction.io:30000
curl -s http://webapp.istioinaction.io:30000/api/catalog

EnvoyFilter 리소스에 대해 알아야 할 첫 번째 사항은, 달리 지정하지 않은 한 네임스페이스의 모든 워크로드에 적용된다는 것이다.

  • istio-system 네임스페이스에 EnvoyFilter 리소스를 만들었다면 메시의 모든 워크로드에 적용된다.
  • 네임스페이스 내에서 커스텀 EnvoyFilter 설정을 적용할 워크로드를 좀 더 특정하고 싶다면, 예제에서 보겠지만 workloadSelector 를 사용하면 된다.

EnvoyFilter 리소스에 대해 알아야 할 두 번째 사항은, 다른 이스티오 리소스가 모두 변환되고 설정된 후에야 적용된다는 것이다.

  • 예를 들어 VirtualService or DestinationRule 리소스가 있다면, 이 설정들이 먼저 데이터 플레인에 적용된다.

마지막으로, EnvoyFilter 리소스로 워크로드를 설정할 때 각별히 주의해야 한다.

  • 엔보이 명명 규칙과 설정 세부 사항을 잘 알아두자. 이는 정말 이스티오 API의 고급 사용법으로, 잘못 설정하면 메시를 마비시킬 수 있다.

예제에서는 그림 14.5와 같이 webapp 워크로드의 데이터 플레인을 거치는 메시지샘플링하도록 엔보이의 tap 필터를 설정하려고 한다 - Docs
요청이나 응답이 tap 필터를 지나 흐를 때마다 tap 필터는 그 요청/응답을 어떤 수신 에이전트로 스트리밍한다. 이 예제에서는 콘솔/CLI로 스트리밍한다.


  • EvnoyFilter 리소스
# cat ch14/tap-envoy-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: tap-filter
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: webapp # 워크로드 셀렉터
  configPatches:
  - 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.tap
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap"
          commonConfig:
            adminConfig:
              configId: tap_config
  1. EnvoyFilter 를 istioinaction 네임스페이스에 배포했다.
  2. 다음으로 엔보이 설정에서 패치할 위치를 지정해야 한다.
  3. EnvoyFilter 리소스의 patch 부분에서 설정을 어떻게 패치할지 지정한다.
#
cat ch14/tap-envoy-filter.yaml
kubectl apply -f ch14/tap-envoy-filter.yaml
kubectl get envoyfilter -n istioinaction
NAME         AGE
tap-filter   10s

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
                                {
                                    "name": "envoy.filters.http.tap",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.tap.v3.Tap",
                                        "commonConfig": {
                                            "adminConfig": {
                                                "configId": "tap_config"
                                            }
                                        }
                                    }
                                },
                                {
                                    "name": "envoy.filters.http.router",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
                                    }
                                }
...
  • tap 기능이 동작하는지 확인해보자. 2개의 터미널 창이 필요한다. 창 하나에서는 curl 로 tap 설정을 전달해 webapp 워크로드에서 tap을 시작
# 터미널 1 : 포트 포워딩 설정 후 tap 시작
kubectl port-forward -n istioinaction deploy/webapp 15000 &
curl -X POST -d @./ch14/tap-config.json localhost:15000/tap

# 터미널 2 : 기존 반복 접속하는 것 활용
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog
while true; do docker exec -it mypc curl -s -H "x-app-tap: true" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog ; echo ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 터미널 3 : 로그 확인
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level http:debug
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level tap:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

tap 을 시작한 창에 tap 출력이 표시돼야 한다. 이 출력은 헤더, 바디, 트레일러 등 요청에 대한 모드 정보를 준다.



14.3 Rate-limiting requests with external call-out

외부 호출 기능으로 데이터 플레인을 확장하는 기본 필터들도 있다.

Istio가 데이터 플레인에 엔보이를 사용하는 것처럼, 속도 제한에 대한 특정 호출은 엔보이 HTTP 필터에서 나온다.
엔보이에서 속도 제한을 하는 방법은 여러 가지가 있지만 (네트워크 필터, 로컬 속도 제한, 글로벌 속도 제한), 우리는 특히 글로벌 속도 제한 기능을 확인한다.

  • 글로벌 속도 제한을 사용하면 특정 워크로드의 모든 엔보이 프록시가 동일한 속도 제한 서비스를 호출하며, 이 속도 제한 서비스는 백엔드 글로벌 키-값 저장소를 호출한다.
  • 이 아키텍처를 사용하면 서비스의 복제본 개수에 상관없이 속도 제한이 적용되도록 할 수 있다.

  • 속도 제한을 하려면 엔보이 커뮤니티에서 제공하는 속도 제한 서버를 - Github 배포하거나, 더 정확히는 엔보이 속도 제한 API - Code구현하는 속도 제한 서버를 배포해야 한다.
  • 이 서버는 백엔드 레디스 캐시와 통신하도록 설정돼 있으며, 레디스에 속도 제한 카운터를 저장한다 (원한다면 Memcached 를 사용할 수 있다).

14.3.1 Understanding Envoy rate limiting

엔보이 속도 제한 서버 설정하기

  • 예제 조직에서 보유한 로열티 등급에 따라 특정 사용자 집단을 제한하고자 한다.
  • 요청의 로열티 등급은 x-loyalty 헤더를 검사해 판단할 수 있다.
    • 골드 등급(x-loyalty: gold)의 사용자 그룹에는 요청을 분당 10개까지 허용
    • 실버 등급(x-loyalty: silver)에는 요청을 분당 5개까지 허용
    • 브론즈 등급(x-loyalty: bronze)에는 요청을 분당 3개까지 허용
  • 식별할 수 없는 로열티 등급의 경우, 분당 요청이 하나를 넘어가면 속도 제한이 처리를 제한한다.
# cat ch14/rate-limit/rlsconfig.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-ratelimit-config
  namespace: istioinaction
data:
  config.yaml: |
    domain: catalog-ratelimit
    descriptors:
      - key: header_match
        value: no_loyalty
        rate_limit:
          unit: MINUTE
          requests_per_unit: 1
      - key: header_match
        value: gold_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 10
      - key: header_match
        value: silver_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 5
      - key: header_match
        value: bronze_request
        rate_limit:
          unit: MINUTE
          requests_per_unit: 3

요청 경로에 속도 제한 걸기

  • 속도 제한 서버 설정을 만들고 나면, 특정 요청에 대해 어떤 속성을 전송할 것인지 엔보이를 설정해야 한다.
  • 엔보이 용어로는 이 설정을 특정 요청 경로에 취하는 속도 제한 조치 rate-limit action 라고 부른다.
  • catalog 서비스의 모든 경로에 속도 제한 조치를 지정하는 방법
# cat ch14/rate-limit/catalog-ratelimit-actions.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: catalog-ratelimit-actions
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: catalog
  configPatches:
    - applyTo: VIRTUAL_HOST
      match:
        context: SIDECAR_INBOUND
        routeConfiguration:
          vhost:
            route:
              action: ANY
      patch:
        operation: MERGE
        # Applies the rate limit rules.
        value:
          rate_limits: # 속도 제한 조치
            - actions:
              - header_value_match:
                  descriptor_value: no_loyalty
                  expect_match: false
                  headers:
                  - name: "x-loyalty"
            - actions:
              - header_value_match:
                  descriptor_value: bronze_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: bronze
            - actions:
              - header_value_match:
                  descriptor_value: silver_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: silver
            - actions:
              - header_value_match:
                  descriptor_value: gold_request
                  headers:
                  - name: "x-loyalty"
                    exact_match: gold

  • EnvoyFilter 리소스 적용
cat ch14/rate-limit/catalog-ratelimit.yaml
cat ch14/rate-limit/catalog-ratelimit-actions.yaml
kubectl apply -f ch14/rate-limit/catalog-ratelimit.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/catalog-ratelimit-actions.yaml -n istioinaction
kubectl get envoyfilter -A


  • 속도 제한 기능을 시험해보기 위해 sleep 앱을 배포하고 catalog 서비스를 호출하는 클라이언트를 시뮬레이션
# sleep 앱에서 catalog 서비스를 호출 시도 : 대략 1분에 한 번 정도 호출 성공! >> x-loyalty 헤더가 없을 때 속도 제한 값!
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
...

# silver 헤더는? 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
...


#
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'InboundPassthroughClusterIpv4' -o json | grep actions

docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||'
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json | grep actions
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json
...
                "rateLimits": [
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "no_loyalty",
                                    "expectMatch": false,
                                    "headers": [
                                        {
                                            "name": "x-loyalty"
                                        }
                                    ]
                                }
                            }
                        ]
                    },
                    {
                        "actions": [
                            {
                                "headerValueMatch": {
                                    "descriptorValue": "bronze_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "bronze"
...



14.4 Extending Istio’s data plane with Lua

Lua 소개 : 가벼운 명령형/절차적 언어로, 확장 언어로 쓰일 수 있는 스크립팅 언어를 주 목적으로 설계

  • 기본 필터인 Lua 필터를 사용하면 Lua 스크립트를 작성해 프록시에 주입함으로써 요청/응답 경로의 동작을 커스텀할 수 있다.
  • 이런 스크립트는 요청이나 응답의 헤더를 조작하고 바디를 조사하는 데 사용할 수 있다. 계속해서 Lua 스크립트를 주입해 요청 경로의 처리를 변경하도록 EnvoyFilter 리소스를 사용해 데이터 플레인을 설정할 것이다.

Lua Programming Language
Lua는 시스템 기능을 강화할 수 있는 강력하면서도 내장 가능한 스크립트 언어다.
Lua는 동적 타입의 인터프리터 언어로, Lua 가상머신이 제공하는 메모리 자동 관리 기능을 갖추고 있다 (엔보이에서는 LuaJIT이다).

요청 바디를 검사하면 프록시에서 스트림을 처리하는 방식에 영향을 줄 수 있다.
예를 들어 바디 전체를 메모리에 적재하는 작업을 수행할 수도 있는데, 이는 성능에 영향을 줄 수 있다.


Lua 실습

  • 특정 요청이 어느 그룹에 속했는지 판단하려면 A/B 테스트 엔진을 호출해야 한다.
  • 이 호출의 응답은 요청의 헤더로 추가해야 하며, 업스트림 서비스는 이 헤더를 사용해 A/B 테스트 목적에 맞는 라우팅 결정을 내릴 수 있다.
  • 이 예제를 위해 몇 가지 보조 서비스를 배포해보자. 받은 요청의 헤더를 되돌려 보내는 샘플 httpbin 서비스를 배포한다.
#
cat ch14/bucket-tester-service.yaml
...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bucket-tester
    version: v1
  name: bucket-tester
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bucket-tester
      version: v1
  template:
    metadata:
      labels:
        app: bucket-tester
        version: v1
    spec:
      containers:
      - image: hashicorp/http-echo:1.0 # 수정 https://hub.docker.com/r/hashicorp/http-echo/tags
        imagePullPolicy: IfNotPresent
        name: bucket-tester
        args:
        - "-text=dark-launch-7"        
        ports:
        - containerPort: 5678
          name: http
          protocol: TCP
        securityContext:
          privileged: false

#
kubectl apply -f ch14/httpbin.yaml -n istioinaction
kubectl apply -f ch14/bucket-tester-service.yaml -n istioinaction

# 확인
kubectl get pod -n istioinaction
NAME                             READY   STATUS    RESTARTS      AGE
bucket-tester-688c598b47-86fbr   2/2     Running   0             25s
httpbin-85d76b4bb6-dz6b5         2/2     Running   0             2m56s
...

엔보이에서는 루아 함수 envoy_on_reqest() 혹은 envoy_on_response() 를 구현해 요청과 응답 각각을 확인하고 조작할 수 있다.
httpCall() 함수를 사용하면 외부 서비스와 통신할 수 있다. 다음 스크립트는 우리의 사용 사례를 구현한 것이다.

# cat ch14/lua-filter.yaml
...
            function envoy_on_request(request_handle)
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle)
              response_handle:headers():add("istioinaction", "it works!")
            end
...
  • envoy_on_reqest() 함수를 구현하고, httpCall() 내장 함수를 사용해 외부 서비스와 통신하고 있다.
  • 또한 응답을 받아 x-test-cohort 라는 헤더를 추가하고 있다.

이 스크립트를 EnvoyFilter 리소스에 추가할 수 있다.

# cat ch14/lua-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: httpbin-lua-extension
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value: 
       name: envoy.lua
       typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |
            function envoy_on_request(request_handle) # 아래 줄에 코드 입력
              local headers, test_bucket = request_handle:httpCall(
              "bucket_tester",
              {
                [":method"] = "GET",
                [":path"] = "/",
                [":scheme"] = "http",
                [":authority"] = "bucket-tester.istioinaction.svc.cluster.local",
                ["accept"] = "*/*"
              }, "", 5000) 

              request_handle:headers():add("x-test-cohort", test_bucket)               
            end          
            function envoy_on_response(response_handle) # 아래 줄에 코드 입력
              response_handle:headers():add("istioinaction", "it works!")
            end
  - applyTo: CLUSTER
    match:
      context: SIDECAR_OUTBOUND
    patch:
      operation: ADD
      value: # cluster specification
        name: bucket_tester
        type: STRICT_DNS
        connect_timeout: 0.5s
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: bucket_tester
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    protocol: TCP
                    address: bucket-tester.istioinaction.svc.cluster.local
                    port_value: 80

  • workloadSelector 정의한 대로 이 필터를 httpbin 워크로드에 적용 후 httpbin 서비스 호출 확인(실습은 No)
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction

# httpbin 서비스 호출 확인! 
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/ -v
...
< HTTP/1.1 503 Service Unavailable
< content-length: 39
< content-type: text/plain
< istioinaction: it works!
< date: Sun, 18 May 2025 07:59:34 GMT
< server: envoy
< x-envoy-upstream-service-time: 51
...
invalid header value for: x-test-cohort

kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/headers
...
invalid header value for: x-test-cohort


# 정상 실습 시..
{
    "headers": {
        "Accept": "*/*",
        "Content-Length": "0",
        "Host": "httpbin.istioinaction:8000",
        "User-Agent": "curl/7.69.1",
        "X-B3-Sampled": "1",
        "X-B3-Spanid": "1d066f4b17ee147b",
        "X-B3-Traceid": "1ec27110e4141e131d066f4b17ee147b",
        "X-Test-Cohort": "dark-launch-7" # A/B 테스트 서비스를 호출할 때 덧붙이는 새 헤더 x-test-cohort 가 보임
    }
}



14.5 Extending Istio’s data plane with WebAssembly

14.5.1 Introducing WebAssembly

  • 웹어셈블리 Wasm, WebAssembly 란 바이너리 명령 형식으로, 여러 가지 환경 간에 이식할 수 있는 것을 목표로 했으며 여러 프로그래밍 언어로 컴파일해 가상머신에서 실행할 수 있다.
  • 본래 웹어셈블리는 브라우저의 웹 애플리케이션에서 CPU 집약적인 작업의 실행 속도를 높이고, 브라우저 기반 애플리케이션 지원을 자바스크립트 외의 언어로도 확장하기 위해 개발됐다 (그림 14.10 참조).
  • 웹어셈블리는 2019년에 W3C 권고 사항이 됐으며 모든 주요 브라우저에서 지원하고 있다.

웹어셈블리는 모듈로 패키징된 커스텀 코드로, 웹 브라우저 같은 대상 호스트 안의 격리된 가상머신에서 안전하게 실행할 수 있다.

  • 웹어셈블리는 저장 공간과 메모리를 적게 차지하고 거의 네이티브에 가까운 속도로 실행되도록 설계됐다.
  • 또한 호스트 애플리케이션(즉, 브라우저)에 내장돼도 안전한데, 메모리 안전 memory safe 하고 격리된 sandboxed 실행 환경(가상머신)에서 실행되기 때문이다.
  • 웹어셈블리 모듈은 호스트 시스템이 허용하는 메모리와 기능에만 접근할 수 있다.

14.5.2 Why WebAssembly for Envoy?

  • 네이티브 엔보이 필터를 직접 작성하는 데는 크게 두 가지 단점이 있다
    • C++ 여야 한다.
    • 변경 사항을 엔보이의 새 바이너리로 정적으로 빌드해야 하는데, 이는 사실상 엔보이 ‘커스텀’ 빌드다.
  • 엔보이웹어셈블리 실행 엔진을 내장하고 있으므로, HTTP 필터를 포함해 엔보이의 다양한 영역을 커스터마이징하고 확장하는 데 사용할 수 있다.
  • 웹어셈블리가 지원하는 언어라면 어느 것이든 엔보이 필터로 작성해 그림 14.11 처럼 런타임에 프록시로 동적으로 불러올 수 있다.
  • 즉, 이스티오에서 기본 엔보이 프록시를 계속 사용하면서 커스텀 필터를 런타임에 동적으로 불러올 수 있다는 것이다.


How it works

  • Set up local cache of Wasm extensions
  • Pull desired Wasm extension into the local cache
  • Mount the wasm-cache into appropriate workloads
  • Configure Envoy with EnvoyFilter CRD to use the Wasm filter

Image fetcher in Istio agent

  • Istio 1.9부터 Istio-agent는 EnvoyFiltersistio-agent 내부의 xDS 프록시와 Envoy의 확장 구성 검색 서비스(ECDS)를 활용하여 에 구성된 원격 HTTP 소스에서 가져온 Wasm 바이너리를 로드하는 안정적인 솔루션을 제공해 왔습니다. Istio 1.12의 새로운 Wasm API 구현에도 동일한 메커니즘이 적용됩니다. 원격 가져오기가 실패할 경우 Envoy가 잘못된 구성으로 인해 중단될 염려 없이 HTTP 원격 리소스를 안정적으로 사용할 수 있습니다.
  • 또한, Istio 1.12는 이 기능을 Wasm OCI 이미지로 확장합니다. 즉, Istio 에이전트는 이제 Docker Hub, Google Container Registry(GCR), Amazon Elastic Container Registry(ECR) 등 모든 OCI 레지스트리에서 Wasm 이미지를 가져올 수 있습니다. 이미지를 가져온 후, Istio 에이전트는 Wasm 바이너리를 추출하여 캐시한 다음 Envoy 필터 체인에 삽입합니다.

Wasm module distribution via the Istio Agent

  • Istio 1.9 이전에는 원격 Wasm 모듈을 프록시에 배포하기 위해 Envoy 원격 데이터 소스가 필요했습니다. 
    예시에서는 두 가지 EnvoyFilter리소스가 정의되어 있습니다. 하나는 원격 가져오기 Envoy 클러스터를 추가하는 리소스이고, 다른 하나는 HTTP 필터 체인에 Wasm 필터를 삽입하는 리소스입니다. 이 방법에는 단점이 있습니다. 잘못된 구성이나 일시적인 오류로 인해 원격 가져오기가 실패하면 Envoy가 잘못된 구성에 고정됩니다. Wasm 확장 프로그램이 fail closed 로 구성된 경우 , 잘못된 원격 가져오기로 인해 Envoy 서비스가 중단됩니다. 이 문제를 해결하려면 Envoy xDS 프로토콜을 근본적으로 변경 하여 비동기 xDS 응답을 허용해야 합니다.
  • Istio 1.9는 istio-agent 내부의 xDS 프록시와 Envoy의 확장 구성 검색 서비스 (ECDS) 를 활용하여 기본적으로 안정적인 배포 메커니즘을 제공합니다 .
  • istio-agent는 istiod에서 확장 구성 리소스 업데이트를 가로채고, 원격 페치 힌트를 읽어와 Wasm 모듈을 다운로드한 후, 다운로드된 Wasm 모듈의 경로로 ECDS 구성을 다시 작성합니다. 다운로드가 실패하면 istio-agent는 ECDS 업데이트를 거부하고 잘못된 구성이 Envoy에 도달하는 것을 방지합니다

14.5.3 Building a new Envoy filter with WebAssembly

웹어셈블리로 엔보이 필터를 빌드하려면 어떤 언어를 사용하고 싶은지, 어떤 엔보이 버전을 사용 중인지, 그 버전에서 어떤 엔보이 ABI Abstract Binary Interace 를 지원하는지 등을 알아야 한다.

  • 이 절에서는 어셈블리스크립트 https://www.assemblyscript.org/ 를 사용해 웹어셈블리로 새 엔보이 필터를 빌드해본다.
  • 어셈블리스크립트는 엔보이 필터를 구축하는 데 있어 C++의 훌륭한 대체재다.

엔보이의 웹어셈블리의 지원은 실험적인 것으로 간주되므로 바뀔 수 있다.
따라서 직접 만들어 엔보이에 배포하는 웹어셈블리 모듈은 운영 환경에 들어가기 전에 모두 철저히 테스트해볼 것을 권장한다.


Wasm 모듈 구성

  • 이 예시에서는 메시에 HTTP 기본 인증 확장을 추가합니다.
  • 원격 이미지 레지스트리에서 기본 인증 모듈을 가져와 로드하도록 Istio를 구성합니다.
  • 이 모듈은 API 호출 시 실행되도록 구성됩니다 /api.
  • 원격 Wasm 모듈로 WebAssembly 필터를 구성하려면 WasmPlugin리소스를 생성하세요.
# 설정 전 호출 확인
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v


#
kubectl explain wasmplugins.extensions.istio.io
...

# 
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: basic-auth
  namespace: istio-system # 모든 네임스페이스에 영향
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/istio-ecosystem/wasm-extensions/basic_auth:1.12.0
  phase: AUTHN
  pluginConfig:
    basic_auth_rules:
      - prefix: "/api"
        request_methods:
          - "GET"
          - "POST"
        credentials:
          - "ok:test"
          - "YWRtaW4zOmFkbWluMw=="
EOF

#
kubectl get WasmPlugin -A
NAMESPACE      NAME         AGE
istio-system   basic-auth   21s

구성된 Wasm 모듈을 확인

  • 자격 증명 없이 테스트

  • 자격 증명으로 테스트


Istio에 Coraza WAF 적용

  • Coraza is an open source, high performance, Web Application Firewall ready to protect your beloved applications - Home , Link
    • Go 언어로 작성되었으며, ModSecurity SecLang 규칙 세트를 지원하고 OWASP 핵심 규칙 세트와 100% 호환됩니다.
  • proxy-wasm filter based on Coraza WAF - Github
    • Coraza 기반으로 구축되고 proxy-wasm ABI를 구현하는 웹 애플리케이션 방화벽 WASM 필터입니다.
    • Envoy에서 직접 로드하거나 Istio 플러그인으로 사용할 수도 있습니다.
    • WAF는 플러그 앤 플레이 방식의 보안 솔루션이 아닙니다. WAF가 효과적으로 작동하려면 보호하려는 환경과 트래픽에 맞춰 구성 및 튜닝이 필요합니다.
    • 실제 운영 환경에서는 배포된 구성( @recommended-conf 및 @crs-setup-conf 참조 )을 완전히 숙지하고 사용된 규칙 세트에 대한 튜닝 단계를 수행하는 것이 좋습니다.

  • Istio WasmPlugin 설정
#
kubectl apply -f - <<EOF
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: coraza-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://ghcr.io/corazawaf/coraza-proxy-wasm
  phase: AUTHN
  pluginConfig:
    default_directives: default
    directives_map:
      default:
      - Include @demo-conf
      - SecDebugLogLevel 9
      - SecRuleEngine On
      - Include @crs-setup-conf
      - Include @owasp_crs/*.conf
EOF

#
kubectl logs -n istio-system -l app=istio-ingressgateway | grep -i wasm 
2025-05-18T09:22:39.344842Z     info    wasm    fetching image corazawaf/coraza-proxy-wasm from registry ghcr.io with tag latest

#
kubectl get WasmPlugin -A
NAMESPACE      NAME                    AGE
istio-system   coraza-ingressgateway   9s

  • 동작 확인
#
curl -s http://webapp.istioinaction.io:30000/api/catalog

# XSS (Cross-Site Scripting) 공격 시도 : REQUEST-941-APPLICATION-ATTACK-XSS
curl -s 'http://webapp.istioinaction.io:30000/api/catalog?arg=<script>alert(0)</script>' -IL
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f


# SQLI phase 2 (reading the body request)
curl -i -X POST 'http://webapp.istioinaction.io:30000/api/catalog' --data "1%27%20ORDER%20BY%203--%2B"
HTTP/1.1 403 Forbidden

# 로그 모니터링
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...

  • 프로메테우스 메트릭 확인



Summary

  • 엔보이의 내부 아키텍처는 리스너와 필터를 중심으로 구축됐다.
  • 즉시 사용할 수 있는 엔보이 필터가 많다.
  • 이스티오의 데이터 플레인(엔보이 프록시)을 확장할 수 있다.
  • 이스티오의 EnvoyFilter 리소스를 사용해 엔보이의 HTTP 필터 구조를 직접 구성하면 더 세밀한 설정이 가능하며, 이스티오의 API로는 노출되지 않는 엔보이의 부분들도 설정할 수 있다.
  • 속도 제한이나 tap 필터 같은 기능으로 서비스 간 통신에서 엔보이의 요청 경로를 확장할 수 있다.
  • 루아와 웹어셈블리를 사용하면 엔보이를 다시 빌드하지 않고도 데이터 플레인을 고급 수준에서 커스터마이징할 수 있다.

0개의 댓글