📌 Notice
Istio Hands-on Study (=Istio)
직접 실습을 통해 Isito를 배포 및 설정하는 내용을 정리한 블로그입니다.
CloudNet@
에서 스터디를 진행하고 있습니다.
Gasida
님께 다시한번 🙇 감사드립니다.
EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
이번 장에서는 Istio의 요청 처리 기능을 확장하는 방법을 실습을 통해 알아봅니다.
조직의 고유한 요구사항을 반영하려면 기본 Istio 기능만으로는 부족할 수 있으며, 이때 Envoy 프록시의 필터 체인을 직접 커스터마이징하는 방식이 유용하게 쓰입니다.
실습은 EnvoyFilter 리소스를 활용한 Tap 필터 적용을 시작으로,
특히 Coraza WAF를 이용한 보안 강화와 WasmPlugin 리소스를 통한 외부 모듈 배포 방식은 운영 환경에서도 바로 활용 가능한 프로덕션 수준의 확장 기법으로, 매우 실용적인 경험이 될 것입니다.
또한 실습 과정에서는 Kind 기반의 완전한 Istio 구성 환경을 준비하고 디버깅 및 검증을 위한 도구 사용법까지 익혀 서비스 메시 확장에 필요한 실전 감각을 종합적으로 습득할 수 있도록 구성되어 있습니다.
이번 장에서는 요청 경로(Request Path) 상에서 이스티오를 확장하는 방법에 대해 다룹니다. 이는 곧 엔보이(Envoy) 프록시를 확장하는 것이며, 맞춤형 기능을 통해 조직의 요구에 더욱 정밀하게 대응할 수 있게 됩니다.
앞서 살펴본 바와 같이, 이스티오는 기본적으로 강력한 애플리케이션 네트워킹 기능을 제공하며, 많은 조직이 이를 통해 다양한 문제를 해결할 수 있습니다. 그러나 실제 운영 환경에서는 이스티오의 기본 기능만으로는 충족되지 않는 제약 조건이나 특별한 요구사항이 존재할 수 있습니다. 이러한 상황에서는 이스티오의 기능을 확장하여 보다 유연하고 정밀한 네트워킹 처리를 구현해야 합니다.
이스티오는 서비스 메시 내부에서 요청을 처리할 때 엔보이 프록시를 핵심 구성 요소로 사용합니다. 이 엔보이는 각 서비스 인스턴스와 함께 배포되며, 트래픽을 제어하고 관찰할 수 있는 다양한 기능을 제공합니다. 하지만 조직 고유의 요구사항을 충족하기 위해서는 엔보이의 기본 기능을 넘어서 맞춤형 확장(Custom Extension) 이 필요할 수 있습니다.
예를 들어, 다음과 같은 시나리오를 생각해볼 수 있습니다.
이러한 기능들은 엔보이의 직접적인 확장을 통해 구현될 수 있으며, 이스티오는 이를 위한 EnvoyFilter 리소스를 제공합니다. EnvoyFilter를 활용하면, 사용자가 직접 엔보이의 구성 요소를 수정하거나 추가할 수 있어 매우 유연한 동작 제어가 가능합니다.
또한, 더 정교한 확장을 위해서는 루아(Lua) 스크립트를 활용해 요청 경로의 처리 로직을 작성하거나, 웹어셈블리(WebAssembly, WASM) 모듈을 통해 보다 강력한 기능 확장을 구현할 수도 있습니다. 이처럼 이스티오의 기본 틀 안에서 다양한 확장 방식을 통해 조직의 네트워킹 요구에 맞는 맞춤형 구조를 만들 수 있습니다.
이번 장에서는 이러한 확장 방법들을 실제로 어떻게 구현할 수 있는지 구체적으로 살펴보며, 이스티오의 유연한 확장성을 경험해보게 될 것입니다.
엔보이(Envoy)의 가장 큰 강점 중 하나는 확장성입니다. 다양한 요구사항을 가진 서비스 환경에서 엔보이는 유연하게 대응할 수 있도록 설계되어 있으며, 특히 필터(Filter)를 통해 강력한 확장 기능을 제공합니다.
엔보이는 고도로 구조화된 API를 기반으로 설계되었고, 이러한 구조는 타사 또는 커뮤니티 사용자들이 작성한 확장 기능이 실제 환경에서도 쉽게 적용되고 널리 사용될 수 있도록 도와줍니다. 엔보이를 효과적으로 확장하기 위해서는 어디서 확장 가능한지, 그리고 어떤 지점이 애플리케이션에 가장 큰 영향을 줄 수 있는지를 파악하는 것이 중요합니다. 이를 위해서는 먼저 엔보이의 아키텍처와 요청 흐름(Request Flow)을 이해해야 합니다.
엔보이에서 하나의 요청이 처리되는 과정은 다음과 같이 구성됩니다:
- 다운스트림 클라이언트로부터의 TCP 연결을 리스너(Listener)가 수락합니다.
- 리스너 필터 체인이 실행되며, TLS 설정 등을 처리한 뒤 해당 요청에 적용할 네트워크 필터 체인을 선택합니다.
- TLS 트랜스포트 소켓이 데이터를 복호화하고, 이를 이후 필터에서 처리할 수 있는 스트림으로 변환합니다.
- 네트워크 필터 체인이 실행되며, 가장 마지막에 있는 필터가 HTTP 커넥션 매니저입니다.
- HTTP 커넥션 매니저가 TLS로부터 전달받은 데이터를 HTTP 스트림으로 변환하여 요청을 분기합니다.
- 각 HTTP 스트림마다 다운스트림 HTTP 필터 체인이 생성되어 실행됩니다.
CustomFilter
와 같은 사용자 정의 필터가 먼저 실행되고, 마지막에 라우터 필터가 위치해 요청을 업스트림으로 전달합니다.- 라우터 필터는 요청 헤더를 기반으로 라우팅을 수행하고, 해당 클러스터의 엔드포인트로 요청을 전달합니다.
- 선택된 클러스터의 로드 밸런싱 정책에 따라 적절한 엔드포인트가 결정되고, 연결 풀을 통해 연결이 이루어집니다.
- 업스트림 연결에서도 HTTP 필터 체인이 동작할 수 있으며, 경우에 따라 리트라이나 섀도잉 요청 처리를 포함합니다.
- 업스트림 요청은 TLS로 암호화되어 전송되며, 응답도 반대 방향으로 같은 필터 체인을 거쳐 클라이언트로 전달됩니다.
- 요청과 응답이 모두 완료되면 필터 체인은 정리되고, 로깅 및 트레이싱 정보가 갱신됩니다.
이 구조 덕분에 엔보이는 다양한 위치에서 요청을 수정하거나 보강할 수 있는 확장 포인트를 제공합니다. 이 중에서도 가장 일반적인 확장 수단은 HTTP 필터 체인의 Custom Filter를 삽입하거나, 라우터 이전/이후에 필터를 배치하는 방식입니다.
다음 단계에서는 이러한 구조를 바탕으로, 실제 이스티오에서 엔보이를 어떻게 확장할 수 있는지 실습을 통해 살펴보겠습니다.
3장에서 이미 간략히 살펴본 바와 같이, 엔보이는 리스너, 라우트(Route), 클러스터라는 고수준 개념으로 구성되어 있습니다. 이번 섹션에서는 그중에서도 리스너와 필터, 그리고 이를 활용한 필터 체인(Filter Chain) 구조에 중점을 두고 살펴보겠습니다.
엔보이에서 리스너(listener)란, 네트워크 인터페이스의 특정 포트를 열고 들어오는 트래픽을 수신하기 위한 엔티티입니다. 본질적으로 엔보이는 L3/L4 계층의 프록시로, 네트워크 연결에서 바이트 스트림을 가져와 이를 다양한 방식으로 처리합니다. 이 때 핵심 역할을 담당하는 구성 요소가 바로 필터(filter)입니다.
리스너는 네트워크 스트림에서 바이트를 읽어들이고, 이를 여러 필터 또는 기능 처리 단계를 거쳐 가공합니다.
가장 기본적인 필터는 네트워크 필터이며, 인코딩 또는 디코딩을 수행하며 바이트 스트림을 처리합니다. 이러한 필터는 여러 개를 순서대로 연결하여 구성할 수 있으며, 이를 필터 체인이라고 부릅니다. 이 필터 체인은 프록시의 실제 기능을 구현하는 수단이 됩니다.
엔보이는 다양한 프로토콜에 특화된 네트워크 필터를 제공합니다. 대표적인 예시는 다음과 같습니다.
이 중 가장 일반적이고 중요한 네트워크 필터는 HttpConnectionManager
(HCM)입니다. HCM은 바이트 스트림을 HTTP 기반 프로토콜(HTTP/1.1, HTTP/2, gRPC, HTTP/3 등)의 요청으로 변환하는 복잡한 작업을 추상화합니다.
이 필터는 단순히 변환 작업을 넘어, 요청의 헤더나 경로 접두사(path prefix)를 기반으로 라우팅, 액세스 로깅, 헤더 조작까지 처리할 수 있습니다. 또한 HCM 내부 역시 필터 기반 아키텍처로 구성되어 있으며, HTTP 요청을 처리하는 HTTP 필터 체인을 설정할 수 있습니다.
HTTP 필터는 다음과 같은 방식으로 확장할 수 있으며, 일부 주요 필터는 다음과 같습니다. 전체 목록은 공식 문서에서 확인할 수 있습니다.
이러한 필터는 순서대로 구성되며, 터미널 필터(terminal filter)로 끝나야 합니다. 보통 이는 라우터 필터(Router Filter)가 담당하며, 요청을 업스트림 클러스터로 전달합니다.
라우터 필터는 라우팅 대상 클러스터를 매칭하고, 타임아웃이나 재시도와 같은 파라미터도 설정할 수 있습니다. 관련 문서는 여기에서 확인할 수 있습니다.
마지막으로, 개발자는 엔보이의 핵심 코드를 수정하지 않고도 자신만의 필터를 작성하여 프록시 위에 계층적으로 쌓을 수 있습니다. 이스티오 또한 이러한 방식으로 엔보이 위에 자체적인 필터를 추가해 커스텀 엔보이를 빌드하고 있습니다.
출처 - https://www.anyflow.net/sw-engineer/istio-overview-4
하지만 이렇게 커스텀 엔보이 프록시를 도입하면, 유지보수가 복잡해지고 필터 개발에 C++ 같은 저수준 언어가 필요하다는 부담도 따르게 됩니다. 따라서 이러한 구조와 제약을 잘 이해하고, 확장이 필요한 범위에서 적절한 방식을 선택하는 것이 중요합니다.
엔보이는 기본적으로 C++ 기반의 필터를 직접 작성하여 프록시에 내장할 수 있지만, 이러한 방식은 복잡성과 유지보수 부담이 크기 때문에 일반적인 사용 사례에는 적합하지 않습니다. 대신, 엔보이 바이너리를 다시 컴파일하지 않고도 HTTP 기능을 확장할 수 있는 확장용 HTTP 필터들이 존재합니다.
대표적인 확장 필터는 다음과 같습니다:
이러한 필터들을 활용하면 다음과 같은 확장이 가능합니다:
이 확장 방식들은 모두 HttpConnectionManager(HCM)의 기능을 보완하는 구조로 동작합니다. 예를 들어 요청을 필터 체인에서 가공한 후 외부 시스템에 위임하거나, 엔보이 내부에서 특정 규칙을 적용하여 응답을 수정하는 등의 작업이 가능합니다.
특히 외부 서비스 호출에 대해서는 속도 제한(Rate Limiting) 필터를 중심으로 설명할 예정이며, 9장에서 다룬 외부 인가 서비스와의 연동 역시 대표적인 활용 사례입니다.
👉🏻 참고로, 엔보이에는 외부 처리 필터(External Processing Filter)가 존재하며, 이는 범용 처리를 위해 외부 서비스를 호출하는 기능을 제공합니다. 하지만 이 필터는 코드베이스에는 포함되어 있으나, 저술 시점에는 기본적으로 아무 동작도 하지 않는 상태입니다. 이 책에서는 보다 실용적인 방식인 전역 속도 제한 필터 등 다른 확장 방법에 초점을 맞춰 설명합니다.
이러한 확장용 필터는 이스티오를 통해 손쉽게 설정 가능하며, 사용자 정의 기능을 안정적이고 유지보수하기 쉬운 방식으로 구현할 수 있도록 돕습니다.
앞에서 살펴본 엔보이의 필터 아키텍처에 대한 이해를 바탕으로, 이번 절부터는 이스티오의 데이터 플레인을 실제로 어떻게 커스터마이징할 수 있는지에 대해 다룹니다. 이스티오에서는 다음과 같은 주요 방법들을 통해 엔보이의 기능을 확장할 수 있습니다:
이러한 확장 방식을 사용하면 이스티오 데이터 플레인의 작동 방식을 애플리케이션 요구사항에 맞춰 보다 세밀하게 조정할 수 있습니다.
특히 이 장에서는 EnvoyFilter 리소스를 중점적으로 다루게 됩니다. 이 리소스를 통해 엔보이에 원하는 HTTP 필터를 삽입하거나 기존 필터 체인을 수정할 수 있으며, 앞서 다른 장들에서도 간단히 사용해 본 바 있습니다. 이번에는 이를 더 구체적이고 실전적인 형태로 자세히 살펴보게 됩니다.
즉, 이 절은 이스티오가 제공하는 확장 메커니즘 중 데이터 플레인을 조작하는 가장 직접적인 방법들을 체계적으로 익히는 출발점이라 할 수 있습니다.
이번 단계에서는 이스티오의 EnvoyFilter
리소스를 이용해 데이터 플레인에 직접적으로 필터를 삽입하고 설정하는 실습을 진행합니다.
이를 위해 다양한 실습 환경을 사전에 구성해야 하며, 이 장에서는 Kubernetes 클러스터 구성부터 Istio 설치까지 실습에 필요한 모든 환경을 차근차근 준비합니다.
#
git clone https://github.com/AcornPublishing/istio-in-action
cd istio-in-action/book-source-code-master
pwd # 각자 자신의 pwd 경로
code .
# 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
hostPort: 30000
- containerPort: 30001 # Prometheus
hostPort: 30001
- containerPort: 30002 # Grafana
hostPort: 30002
- containerPort: 30003 # Kiali
hostPort: 30003
- containerPort: 30004 # Tracing
hostPort: 30004
- containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
hostPort: 30005
- containerPort: 30006 # TCP Route
hostPort: 30006
- containerPort: 30007 # kube-ops-view
hostPort: 30007
extraMounts: # 해당 부분 생략 가능
- hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
containerPath: /istiobook
networking:
podSubnet: 10.10.0.0/16
serviceSubnet: 10.200.0.0/22
EOF
# 설치 확인
docker ps
# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
# (옵션) kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
## kube-ops-view 접속 URL 확인
open "http://localhost:30007/#scale=1.5"
open "http://localhost:30007/#scale=1.3"
# (옵션) metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server
# kind docker network 에 테스트용 PC(컨테이너) 배포
docker network ls
docker inspect kind
# mypc 컨테이너 기동
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
# 혹은 IP 지정 없이
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity
docker ps
# 컨테이너 IP 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
#/mypc 172.18.0.100
#/myk8s-control-plane 172.18.0.2
# 내부 통신 확인
docker exec -it mypc ping -c 1 172.18.0.2
docker exec -it mypc ping -c 1 myk8s-control-plane
docker exec -it myk8s-control-plane ping -c 1 mypc
# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
# 확인
kubectl get crd
kubectl get pod -n metallb-system
# IPAddressPool, L2Advertisement 설정
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 172.18.255.101-172.18.255.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
spec:
ipAddressPools:
- default
EOF
# 확인
kubectl get IPAddressPool,L2Advertisement -A
# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: LoadBalancer
EOF
# 확인
kubectl get deploy,pod,svc,ep
kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
LBIP=$(kubectl get svc nginx-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl -s $LBIP
docker exec -it mypc curl -s $LBIP -v -I
# 삭제
kubectl delete deploy,svc --all
# myk8s-control-plane 컨테이너 진입 후
docker exec -it myk8s-control-plane bash
-----------------------------------
# (옵션) 코드 파일 확인
tree /istiobook/ -L 1
혹은 git clone ... /istiobook
# istioctl 설치
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc
curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
# demo 프로파일 설치
istioctl install --set profile=demo --set values.global.proxy.privileged=true -y
# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
# bash 빠져나오기
exit
-----------------------------------
# 설치 확인
kubectl get istiooperators -n istio-system -o yaml
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get cm -n istio-system istio -o yaml
kubectl get crd | grep istio.io | sort
# 네임스페이스 구성
kubectl create ns istioinaction
kubectl label namespace istioinaction istio-injection=enabled
kubectl get ns --show-labels
# istio-ingressgateway NodePort 및 트래픽 정책 수정
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl describe svc -n istio-system istio-ingressgateway
# observability 구성(NodePort 변경)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'
# 접속 확인
open http://127.0.0.1:30001 # Prometheus
open http://127.0.0.1:30002 # Grafana
open http://127.0.0.1:30003 # Kiali
open http://127.0.0.1:30004 # Tracing
이스티오의 데이터 플레인을 확장하는 첫 번째 단계는 기존의 엔보이 필터로 원하는 기능이 가능한지 판단하는 것입니다. 만약 원하는 기능을 제공하는 필터가 존재한다면, 이스티오의 고급 리소스인 EnvoyFilter를 사용하여 이를 설정할 수 있습니다.
일반적인 이스티오 리소스(VirtualService, DestinationRule, AuthorizationPolicy 등)는 엔보이 설정을 추상화한 고수준 API이지만, 일부 고급 기능은 노출되지 않아 엔보이를 직접 설정해야 할 필요가 있습니다. 이럴 때 EnvoyFilter 리소스를 사용하면, 리스너, 라우트, 클러스터, 필터 등 거의 모든 엔보이 컴포넌트를 설정할 수 있습니다.
단, 이 리소스는 이스티오 고급 사용자를 위한 "비상 수단"에 가깝습니다. 설정 오류는 전체 메시를 중단시킬 수 있으므로, 사용 시 주의가 필요합니다.
이번 실습에서는 webapp 워크로드의 요청 흐름을 실시간으로 디버깅하기 위해 Envoy의 tap 필터를 적용합니다. 이 필터는 이스티오의 고수준 API에는 노출되어 있지 않기 때문에, EnvoyFilter 리소스를 통해 설정합니다.
# 기본 EnvoyFilter 목록 확인
kubectl get envoyfilter -A
# 서비스 배포
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
# 호출 확인
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
# 반복 호출
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
# 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: tap 시작
kubectl port-forward -n istioinaction deploy/webapp 15000 &
curl -X POST -d @./ch14/tap-config.json localhost:15000/tap
# 터미널 2: x-app-tap 헤더 포함 호출
EXT_IP=$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
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 필터를 통해 Istio 데이터 플레인에서 발생하는 요청의 흐름을 실시간으로 모니터링하고, 다양한 디버깅 도구로 활용할 수 있습니다.
앞 절에서는 이스티오의 기본 HTTP 필터 중 하나인 tap 필터를 이용해 요청을 실시간으로 관찰하고 데이터 플레인을 확장해보았습니다. 이번에는 외부 호출(call-out) 기능을 사용하는 속도 제한(rate-limiting) 필터를 활용하여 이스티오 데이터 플레인을 확장하는 방법을 알아봅니다.
이러한 필터들은 엔보이 내부에서 직접 처리하지 않고, 외부의 별도 서비스에 요청을 위임하는 구조로 작동합니다. 이 구조를 통해 요청을 계속 처리할지 말지를 외부 서비스의 판단에 맡기고, 다양한 정책을 유연하게 적용할 수 있습니다.
이번 절에서는 서비스 측 속도 제한(service-side rate limiting)을 구현하기 위해, 이스티오 데이터 플레인이 속도 제한 서버(rate-limit server)를 호출하도록 구성합니다.
동일한 서비스를 여러 개의 복제본이 운영 중인 경우에도, 하나의 전역 속도 제한 정책이 일관되게 적용될 수 있어야 합니다. 이를 위해 각 엔보이 프록시가 공통의 속도 제한 서버에 요청을 보냄으로써 글로벌 속도 제한이 가능해집니다.
이스티오는 데이터 플레인으로 엔보이를 사용하고 있으며, 이러한 속도 제한 요청 역시 Envoy HTTP 필터를 통해 이루어집니다.
엔보이에서 속도 제한을 구현하는 방식은 여러 가지가 있습니다:
이번 실습에서는 특히 글로벌 속도 제한 방식을 사용합니다.
이 구조에서 각 프록시는 요청의 속성을 포함한 정보를 속도 제한 서버에 전달하고, 서버는 이 요청을 수락할지 거부할지를 응답으로 돌려줍니다. 이 방식은 서비스 복제본이 많아지더라도 일관된 제어가 가능하다는 장점이 있습니다.
속도 제한 기능을 구성하려면 다음 요소들이 필요합니다:
속도 제한 서버는 Envoy에 의해 외부 호출 형태로 연동되며, 이때 설정된 정책에 따라 요청 흐름을 제한합니다.
실습을 통해 이 아키텍처가 어떻게 작동하는지, 그리고 실제로 이스티오에서 어떻게 연동하는지를 차례대로 확인해보겠습니다.
속도 제한 기능을 실제로 구성하기 전에, 먼저 Envoy의 속도 제한 동작 방식을 이해하는 것이 중요합니다. 특히 이번 절에서는 HTTP 기반 글로벌 속도 제한(Global Rate Limiting)의 구조와 흐름을 중점적으로 살펴봅니다.
엔보이에서 글로벌 속도 제한은 HTTP 필터의 형태로 존재하며, HttpConnectionManager(HCM)의 HTTP 필터 체인에 설정되어야 합니다. 이 필터는 각 요청을 처리할 때 특정 속성(예: 헤더, IP 등)을 추출하여 속도 제한 서버(RLS: Rate-Limit Server)로 전송하고, 서버의 응답을 기반으로 요청을 허용하거나 차단하게 됩니다.
이때 사용되는 요청 속성 또는 속성의 조합은 디스크립터(descriptors)라고 부르며, 이는 속도 제한 정책에서 기준이 되는 항목입니다.
디스크립터는 다음과 같은 정보일 수 있습니다:
- 클라이언트의 IP 주소 (remote address)
- 요청 헤더 값
- 요청의 목적지(destination)
- 기타 일반적인 요청 속성
속도 제한 서버(RLS)는 요청이 들어올 때마다 이 디스크립터 정보를 받아 미리 정의된 속성 집합과 비교하고, 일치하는 항목이 있다면 그 항목에 대한 요청 카운터를 증가시킵니다. 이 카운터가 설정한 임계값(threshold)을 초과하게 되면 해당 요청은 속도 제한 처리(rate-limited)되어 거부됩니다.
이 구조는 다음과 같은 장점을 제공합니다:
즉, 이 절의 핵심은 속도 제한의 의사결정 주체는 엔보이가 아니라 외부의 RLS이며, Envoy는 속성 추출 및 판단 위임 역할만을 수행한다는 점입니다.
이 개념을 정확히 이해하면 다음 단계의 실습에서도 더욱 명확하게 동작 흐름을 파악할 수 있습니다.
이제 Envoy에서 사용할 속도 제한 서버(RLS: Rate-Limit Server)를 실제로 구성해보겠습니다. 이 설정은 요청 속성을 기반으로 한 카운터와 한도(limit)를 정의하고, 그에 따라 요청의 흐름을 제어하는 역할을 합니다.
이번 예제에서는 요청에 포함된 x-loyalty
헤더를 기반으로 사용자 로열티 등급에 따라 다른 속도 제한 정책을 적용해보겠습니다.
이를 통해 조직은 사용자 등급에 따른 차등 서비스 제공이 가능해지고, 무분별한 요청을 제한할 수 있습니다.
아래는 이러한 정책을 담고 있는 속도 제한 서버의 설정 예시입니다:
# 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
이 설정은 다음과 같은 방식으로 작동합니다:
- Envoy의 속도 제한 필터가 HTTP 요청에서 디스크립터(descriptor) 속성을 추출합니다.
- 해당 디스크립터는 설정된 domain 하위의 정책과 비교됩니다.
- 일치하는 항목이 있을 경우, Redis 등 백엔드 저장소의 카운터를 증가시키고,
- 정의된 요청 허용 횟수를 초과하면, 해당 요청은 rate-limited 상태로 처리됩니다.
❗️ 참고: 위 설정은 실제 HTTP 요청 헤더를 직접 매칭하지 않습니다. 이 값들은 이후에 별도로 정의될 매핑 규칙에 의해 요청의 속성에서 파생됩니다. 이 매핑 정의는 다음 절에서 다루게 됩니다.
이렇게 구성된 RLS는 Envoy와 연동되어 요청 흐름 제어를 담당하며, 고급 서비스 정책 구현의 핵심이 됩니다.
이제 속도 제한 서버 설정을 마쳤다면, 실제로 어떤 요청에 대해 어떤 속성(descriptor)을 전송할 것인지를 엔보이에서 정의해야 합니다. 이 설정은 rate-limit action이라 불리며, 특정 요청 경로에 대해 어떤 디스크립터를 사용할지를 지정합니다.
이번 예제에서는 catalog 서비스의 /items
경로에 대한 요청에서 x-loyalty
헤더를 기반으로 사용자 등급을 분류하고, 등급별로 다른 속도 제한을 적용합니다.
속도 제한 동작을 활성화하려면, EnvoyFilter 리소스를 통해 특정 루트(route)에 대해 rate_limit actions를 명시해야 합니다. 다음은 catalog 서비스에 적용할 EnvoyFilter 예제입니다:
# 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
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
# 구성 확인
tree ch14/rate-limit
cat ch14/rate-limit/rlsconfig.yaml
cat ch14/rate-limit/rls.yaml
# ConfigMap 및 속도 제한 서버 배포
kubectl apply -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/rls.yaml -n istioinaction
# 배포 확인
kubectl get cm -n istioinaction catalog-ratelimit-config
kubectl get pod -n istioinaction
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
# 헤더 없이 호출: 분당 1회만 허용됨
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
# 429 Too Many Requests 응답 예시
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
# silver 등급으로 요청: 분당 5회까지 허용
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
# 여러 번 반복 호출 후 제한 적용 확인
# Envoy에 설정된 route 내 rate_limit actions 확인
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
# EnvoyFilter 삭제
kubectl delete envoyfilter -n istioinaction --all
kubectl get envoyfilter -A
# RLS 설정 및 배포 제거
kubectl delete -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl delete -f ch14/rate-limit/rls.yaml -n istioinaction
이 실습을 통해 요청 경로에 따라 속성 기반의 속도 제한 정책을 설정하고, EnvoyFilter로 이를 적용하는 전체 과정을 확인할 수 있습니다.
이처럼 Envoy + Istio의 조합은 매우 유연한 정책 기반 제어를 가능하게 합니다.
기존의 엔보이 필터를 구성하여 이스티오 데이터 플레인을 확장하는 것은 비교적 간단한 방법입니다. 그러나 추가하려는 기능이 기본 엔보이 필터에 존재하지 않는 경우, 직접 커스텀 로직을 구현해야 합니다. 이때 유용하게 사용할 수 있는 도구가 바로 루아(Lua)입니다.
Lua는 가볍고 임베디드 가능한 명령형/절차적 스크립트 언어로, 시스템 기능 확장에 최적화되어 있습니다. 동적 타입 언어이며, 인터프리터 방식으로 실행되며 메모리 자동 관리 기능을 제공합니다.
엔보이에서는 LuaJIT 기반의 가상 머신 위에서 실행되며, 요청 또는 응답 흐름 중 헤더 조작, 바디 검사, 조건 분기 등 다양한 처리를 커스텀할 수 있습니다.
👉🏻 **루아 공식 문서**: [https://lua.org](https://lua.org), [https://luajit.org](https://luajit.org) 엔보이에서 Lua 필터 사용 문서: [Envoy Lua Filter Docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter)이번 절에서 사용할 루아 필터(Lua Filter)는 엔보이의 HTTP 필터 체인에 삽입되어 작동합니다. 즉, 요청이 프록시를 통과할 때 Lua 스크립트가 실행되며, 사용자 정의 로직에 따라 요청을 가공하거나 검사할 수 있습니다.
이러한 구조는 다음과 같은 작업에 유용합니다:
요청 바디를 검사할 경우, 프록시는 전체 바디를 메모리에 적재해야 하기 때문에 성능에 영향을 줄 수 있습니다. 반드시 필요한 경우에만 바디 검사 기능을 사용하도록 하며, 성능 고려가 필요합니다.
앞으로의 실습에서는 EnvoyFilter 리소스를 사용하여, Lua 스크립트를 데이터 플레인에 주입하고 요청 경로에서 동작하는 커스텀 로직을 구현해볼 것입니다.
이번 실습에서는 루아 스크립트를 사용하여 요청 경로에서의 동작을 커스터마이징해보았습니다. 시나리오는 다음과 같습니다:
모든 요청을 A/B 테스트 그룹의 일부로 분류하고, 해당 그룹 정보를 요청 헤더에 삽입하여 업스트림 서비스가 이를 기반으로 라우팅 판단을 하도록 합니다.
이를 위해 다음과 같은 구성을 수행했습니다:
httpbin
서비스: 요청 헤더를 응답으로 되돌리는 테스트용 백엔드bucket-tester
서비스: A/B 그룹명을 반환하는 샘플 API (ex. dark-launch-7
)Lua 필터
: 요청 시 httpCall()
을 통해 bucket-tester
를 호출하고, 응답값을 x-test-cohort
헤더로 추가kubectl apply -f ch14/httpbin.yaml -n istioinaction
kubectl apply -f ch14/bucket-tester-service.yaml -n istioinaction
kubectl get pod -n istioinaction
다음은 실제로 적용한 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:
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
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/ -v
kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://httpbin.istioinaction:8000/headers
{
"headers": {
...
"X-Test-Cohort": "dark-launch-7",
"Istioinaction": "it works!"
}
}
x-test-cohort
값이 비정상적 문자열로 삽입되어 invalid header value for: x-test-cohort
오류 발생💡 원인 추정:
httpCall()
의 응답값이 문자열이 아닌 binary buffer로 처리되었을 가능성이 있으며, 이를 문자열로 변환하지 않고 헤더에 추가하면서 유효하지 않은 값이 삽입된 것으로 보입니다.
kubectl delete envoyfilter -n istioinaction --all
kubectl delete -f ch14/httpbin.yaml -n istioinaction
kubectl delete -f ch14/bucket-tester-service.yaml -n istioinaction
이번 실습에서는 EnvoyFilter를 통해 Lua 스크립트를 삽입하고, 동적으로 외부 호출을 수행한 결과를 요청 헤더에 반영하는 구조를 실험해보았습니다. 실습 자체는 완전한 성공은 아니었지만, 이 과정을 통해 다음과 같은 중요한 개념을 확인할 수 있었습니다:
httpCall()
함수로 외부 호출 가능이번 절에서는 Istio의 데이터 플레인을 확장하는 세 번째 방법으로, 직접 WebAssembly(웹어셈블리, Wasm)를 사용해 커스텀 Envoy 필터를 작성하고 주입하는 방법을 소개합니다.
이전 절들에서는 다음과 같은 방식으로 확장을 시도했습니다:
하지만 이제는 보다 복잡하고 언어적인 유연성이 요구되는 확장 시나리오를 위해, 직접 Wasm 기반의 필터를 작성하고 Istio에 배포하는 구조를 사용합니다.
📘 관련 문서:
WebAssembly(Wasm)는 여러 언어로 작성한 코드를 플랫폼 독립적인 바이너리 형식으로 컴파일하여 가상머신 상에서 실행할 수 있게 만든 기술입니다.
이러한 특성 덕분에 WebAssembly는 브라우저 외에도 다양한 시스템(예: Envoy 프록시, 블록체인, 서버리스)에서 플러그인 기반 확장 모델로 활용되고 있습니다.
엔보이 필터를 커스터마이징하는 기존 방식은 다음과 같은 중대한 단점이 있었습니다:
즉, 단순한 로직을 반영하기 위해 무거운 빌드 파이프라인을 구축해야 했고, 이는 유지보수와 배포에 있어 큰 부담이었습니다.
다행히도 Envoy는 웹어셈블리 실행 엔진을 내장하고 있으며, 이를 활용해 커스텀 HTTP 필터를 포함한 다양한 확장 기능을 구현할 수 있습니다.
Istio는 다음과 같은 절차로 Wasm 기반 필터를 배포합니다:
EnvoyFilter
리소스로 해당 Wasm 필터를 Envoy 필터 체인에 삽입출처 - https://istio.io/latest/blog/2020/deploy-wasm-declarative/
즉, 이제는 다음처럼 쉽게 확장 가능합니다:
출처 - https://istio.io/latest/blog/2021/wasm-progress
출처 - https://istio.io/latest/blog/2021/wasm-progress
이 구조는 다음 문제를 해결합니다:
📘 참고 문서:
요약하면, WebAssembly는 Envoy 및 Istio 생태계에서 가볍고 안전하며 유연한 확장 방법으로 자리 잡고 있습니다.
다음 절에서는 Wasm 필터를 실제로 작성하고 배포하는 과정을 실습합니다.
WebAssembly(Wasm) 기반의 커스텀 Envoy 필터를 직접 만들기 위해선 몇 가지 고려사항이 있습니다:
- 어떤 언어를 사용할 것인가?
Wasm은 다양한 언어로 작성할 수 있지만, Envoy와의 호환성을 위해 반드시 지원되는 ABI(추상 이진 인터페이스)를 확인해야 합니다.
- 어떤 Envoy 버전을 사용하는가?
사용 중인 Envoy 버전이 어떤 ABI를 지원하는지 파악하는 것이 중요합니다. 각 버전은 ABI 호환성 차이가 있을 수 있습니다.
- 적절한 언어 SDK 및 빌드 환경을 준비해야 합니다.
언어에 따라 SDK, 컴파일러, 패키지 관리자가 다르므로 이에 맞춰 개발 환경을 세팅해야 합니다.
이번 절에서는 Solo.io에서 제공하는 오픈소스 도구인 wasme
를 사용해 필터를 생성해봅니다.
wasme란?
wasme
는 Envoy 필터용 WebAssembly 개발을 빠르게 시작할 수 있도록 도와주는 CLI 도구입니다.
- 사용 가능한 언어 SDK (2025년 기준):
이번 예제에서는 AssemblyScript를 사용합니다.
AssemblyScript는 TypeScript 기반으로 설계된 Wasm 전용 언어로, 자바스크립트/타입스크립트 개발자에게 친숙하며
C++보다 훨씬 간단한 문법으로 빠르게 Wasm 필터를 작성할 수 있는 장점이 있습니다.
👉🏻 엔보이의 Wasm 필터는 아직 실험적(experimental)입니다.
운영 환경에서 사용하기 전 반드시 철저히 테스트해야 합니다.
이번 실습에서는 Istio의 WasmPlugin 리소스를 활용하여 WebAssembly 모듈을 외부에서 불러와 Istio에 동적으로 적용하는 방법을 실습합니다.
실습 목적: WebAssembly 필터를 외부 OCI 이미지 레지스트리에서 불러와, Istio ingress gateway에 적용하여
/api
경로에 Basic 인증을 요구하도록 구성
📚 참고:
/api
경로에 대해 GET/POST 요청 시 인증을 수행하도록 구성합니다.# 설정 전 호출 확인 (자격 증명 없음)
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
# WasmPlugin 리소스 적용
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
# 자격 증명 없이 테스트 (401 Unauthorized 기대)
docker exec -it mypc curl -s -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v
# 자격 증명 포함 요청 (정상 응답 기대)
docker exec -it mypc curl -s \
-H "Authorization: Basic YWRtaW4zOmFkbWluMw==" \
-H "Host: webapp.istioinaction.io" \
http://$EXT_IP/api/catalog -v
kubectl delete WasmPlugin -n istio-system basic-auth
이 실습을 통해, 외부 레지스트리에 배포된 WebAssembly 모듈을 Istio 환경에 동적으로 적용하는 방법을 이해하고 테스트할 수 있었습니다.
이는 운영 중인 프록시를 중단하지 않고도 기능을 확장할 수 있는 유연한 방법을 제공하며, 특히 보안, 라우팅, 관측 등 다양한 영역에서 활용할 수 있습니다.
이번 절에서는 Istio ingress gateway에 Coraza 기반 Web Application Firewall(WAF)을 적용하여 OWASP CRS 기반의 보안 필터링을 수행하는 방법을 실습합니다.
Coraza는 proxy-wasm
기반 WasmPlugin 형태로 Istio에 직접 연동 가능합니다.
# Coraza WAF 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
# Plugin 확인
kubectl get WasmPlugin -A
# 정상 호출
curl -s http://webapp.istioinaction.io:30000/api/catalog
# 📛 XSS 공격 시도
curl -s 'http://webapp.istioinaction.io:30000/api/catalog?arg=<script>alert(0)</script>' -IL
# => HTTP/1.1 403 Forbidden
# 📛 SQL Injection 시도
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
# => WAF 로그 (Coraza 로그 및 Anomaly Score 확인)
# 프로메테우스 메트릭 확인
curl -s localhost:8082/stats/prometheus | grep waf_filter
XSS 탐지 및 차단
SQLi 차단 로그
Coraza는 Istio 환경에서 WasmPlugin으로 쉽게 통합될 수 있는 강력한 보안 계층입니다.
다만, 운영 적용 시에는 규칙 세트에 대한 튜닝 및 오탐/과탐 관리가 필수입니다.
이번 실습에서는 Istio 데이터 플레인을 다양한 방식으로 확장하는 방법들을 단계적으로 탐색해보았습니다.
EnvoyFilter를 통한 Tap 필터 설정에서 시작하여 외부 속도 제한 서버 연동, Lua 스크립트 기반 동적 처리, 그리고 WebAssembly 모듈 배포까지 이어지는 흐름을 통해, 각 확장 방식의 특징과 실용성을 비교하며 이해할 수 있었습니다.
가장 인상 깊었던 부분은 WasmPlugin 리소스를 활용한 동적 확장성이었습니다.
OCI 레지스트리에서 WebAssembly 모듈을 런타임에 불러와, 프록시 재시작 없이도 기본 인증이나 WAF 같은 기능을 손쉽게 배포할 수 있다는 점은 운영 측면에서 매우 유용했습니다.
속도 제한 서버 연동 실습을 통해서는 디스크립터 기반의 세밀한 제어와 전역 정책 적용 방식을 실제로 확인해볼 수 있었고,
Lua 스크립트 활용에서는 외부 호출 처리와 헤더 조작의 복잡성을 겪으면서도, Envoy 필터 체인의 유연성을 체감할 수 있었습니다.
특히 Coraza WAF와 OWASP CRS 기반 보안 필터링은 프로덕션 환경에서도 바로 활용 가능한 강력한 보안 계층으로, 실전 배포 가능성까지 엿볼 수 있었습니다.