이스티오 확장을 통한 맞춤형 네트워킹 구현
기본 기능으로 부족한 조직별 요구사항 충족을 위해 Envoy 프록시의 설정을 직접 수정하거나 확장한다. EnvoyFilter 리소스를 활용해 엔보이의 동작을 세부 제어하며, Lua 스크립트나 WebAssembly(Wasm) 모듈을 적용해 요청/응답 경로를 커스터마이징할 수 있다.
주요 확장 사례
- 외부 인가 서비스 연동, HMAC 서명 검증 등 비표준 프로토콜 처리
- 헤더 조작, 페이로드 보강을 위한 추가 서비스 호출
- 조직별 보안 토큰 처리 로직 구현
이를 통해 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 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind
# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 지정 없이 혹은 지정 해서 사용
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity # IP 지정 실행 시
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps
# kind network 중 컨테이너(노드) 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 network(kind) 내부에서 컨테이너 이름 기반 도메인 통신 가능 확인!
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 설정 : MetalLB 파드(speaker) 배포 정상 완료 후 아래 설정 실행하자.
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}')
# 외부?에서 각 클러스터의 Service(LB)로 접속(인입) 확인 : TCP 80 포트 사용으로 편리하다!
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
# 빠져나오기
exit
-----------------------------------
# 설치 확인 : istiod, istio-ingressgateway, crd 등
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 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
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
# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
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}]}}'
# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001
# Grafana 접속
open http://127.0.0.1:30002
# Kiali 접속 : NodePort
open http://127.0.0.1:30003
# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004
이스티오 고급 설정을 위한 EnvoyFilter 활용
기존 API로 구현 불가한 시나리오에서 EnvoyFilter를 사용해 엔보이 프록시의 리스너·라우트·클러스터·필터를 직접 설정한다. 이 리소스는 이스티오의 고수준 추상화를 벗어나 저수준 엔보이 기능을 제어할 수 있지만, 버전 간 호환성 보장 없어 신중한 검증이 필수다. 잘못된 구성은 데이터 플레인 전체 장애로 이어질 수 있으므로, 반드시 테스트 환경에서 검증 후 배포해야 한다. 비상 시에만 사용하는 것이 권장되며, 일반적인 경우에는 VirtualService·DestinationRule 등 표준 API를 우선 적용한다.
Tap 필터를 적용해 webapp 서비스의 HTTP 트래픽을 샘플링·스트리밍하며, workloadSelector로 특정 워크로드에만 적용한다.
VirtualService/DestinationRule 이후 적용되며, 엔보이 저수준 설정 변경으로 인한 메시 장애 리스크 존재해 신중한 구성이 필수다.
# 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
#
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"
}
}
...
EnvoyFilter 설정 요약
- workloadSelector로 istioinaction 네임스페이스의 특정 워크로드에 적용 대상을 지정한다.
- SIDECAR_INBOUND 리스너의 8080 포트 HTTP 필터 체인에서 envoy.filters.http.router 필터 앞에 tap 필터를 추가한다.
- tap 필터는 envoy.filters.http.tap 타입으로 설정되며, streaming_admin 출력을 통해 실시간 트래픽 디버깅이 가능하다.

# 터미널 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
2025-05-18T05:59:47.704918Z debug envoy http external/envoy/source/common/http/conn_manager_impl.cc:329 [C2100] new stream thread=31
2025-05-18T05:59:47.705028Z debug envoy http external/envoy/source/common/http/conn_manager_impl.cc:1049 [C2100][S11845543740779751481] request headers complete (end_stream=true):
':authority', 'webapp.istioinaction.io'
':path', '/api/catalog'
':method', 'GET'
'user-agent', 'curl/8.7.1'
'accept', '*/*'
'x-app-tap', 'true'
...
2025-05-18T05:59:47.713852Z debug envoy tap external/envoy/source/extensions/filters/http/tap/tap_config_impl.cc:172 submitting buffered trace sink thread=31
2025-05-18T05:59:47.713860Z debug envoy tap external/envoy/source/extensions/common/tap/admin.cc:145 admin submitting buffered trace to main thread thread=31
...
14.3 외부 호출로 요청 속도 제한하기
엔보이 글로벌 속도 제한은 모든 서비스 인스턴스가 동일한 외부 속도 제한 서버(예: Redis 백엔드)를 호출해 요청 허용 여부를 결정하며, 서비스 복제본 수와 무관하게 일관된 트래픽 제한을 적용할 수 있다.
속도 제한 서버 설정
x-loyalty 헤더 값에 따라 골드(10/분), 실버(5/분), 브론즈(3/분) 등급별 요청 제한을 적용하고, 미확인 등급은 1/분으로 제한한다.
Redis 백엔드를 사용해 글로벌 카운터를 관리하며, Envoy의 RateLimitService 설정을 통해 외부 서버와 통합한다.
# 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
요청 경로에 속도 제한 걸기* CONFIGURING THE REQUEST PATH FOR RATE LIMITING
EnvoyFilter를 통해 /items 경로 요청의 x-loyalty 헤더를 포착하는 rate-limit action을 정의해, 특정 로열티 등급별로 속도 제한 서버에 전달할 descriptor 생성을 구성한다.
VirtualService 라우트에 rate_limits 섹션 추가 없이, EnvoyFilter의 header_value_match 액션으로 헤더 기반 속성 추출 및 Redis 기반 글로벌 제한 정책을 적용한다.
# 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
#
tree ch14/rate-limit
ch14/rate-limit
├── catalog-ratelimit-actions.yaml
├── catalog-ratelimit.yaml
├── rls.yaml
└── rlsconfig.yaml
cat ch14/rate-limit/rlsconfig.yaml
cat ch14/rate-limit/rls.yaml
#
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
NAME READY STATUS RESTARTS AGE
ratelimit-99d5d9c5-q4zcm 1/1 Running 2 (25s ago) 34s
redis-6cf4ff9768-x97m8 1/1 Running 0 34s
...
# 기존에 반복 호출은 취소해두자
#
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
NAMESPACE NAME AGE
istioinaction catalog-ratelimit-actions 27s
istioinaction catalog-ratelimit-filter 28s
...
속도 제한 기능을 시험해보기 위해 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 루아로 이스티오의 데이터 플레인 확장하기
Lua를 활용한 이스티오 데이터 플레인 확장
Lua 스크립트를 엔보이 필터 체인에 주입해 요청/응답 경로의 헤더 조작·바디 검사 등 커스텀 로직 구현이 가능하다.
예를 들어 A/B 테스트 그룹 분류를 위해 외부 서비스 호출 후 결과를 헤더에 추가하는 동적 라우팅을 구성할 수 있다.
단, 바디 전체 메모리 적재 등 무거운 연산은 성능 저하를 유발할 수 있으니 주의가 필요하다.
| 일전에 실 예로 내가 운영하던 서비스중 프론트/백 사이에서 데이터가 안들어온다는 개발자 확인 요청으로 Lua 활용해서 바디 전체를 로깅하는 루아 스크립트 작성했다가 뻗었던 기억이 있다.
#
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
...
# 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
...
Lua 스크립트는 envoy_on_request/envoy_on_response 함수로 요청·응답 헤더를 조작하고, httpCall()을 통해 외부 서비스와 논블로킹 통신해 동적 헤더 주입을 구현한다.
# 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
#
kubectl apply -f ch14/lua-filter.yaml
kubectl get envoyfilter -n istioinaction
# istio-proxy config 확인 내용 추가해두자
# 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 웹어셈블리로 이스티오의 데이터 플레인 확장하기
**웹어셈블리(Wasm)**를 통해 Envoy 필터를 동적 로드해 Istio 데이터 플레인을 확장하며, WasmPlugin CRD로 OCI 레지스트리에서 모듈을 배포해 커스텀 인증·트래픽 제어 기능을 구현할 수 있다. **웹어셈블리(Wasm)**는 이식성·고성능·안전성을 갖춘 바이너리 포맷으로, 다양한 언어로 작성된 커스텀 로직을 엔보이 필터에 동적 로드해 이스티오 데이터 플레인을 확장한다.
웹어셈블리(Wasm) 사용 이유
C++ 종속성·정적 빌드 문제 해결: 런타임 동적 로드로 커스텀 필터를 즉시 적용하며, 다양한 언어 지원으로 유연한 확장 가능. 안전한 샌드박스 환경에서 실행되어 메모리/CPU 제어·프록시 장애 격리되며, OCI 레지스트리 통합으로 배포 관리가 간소화된다.
**WebAssembly(Wasm)**로 Envoy 필터를 생성하려면 wasme 도구를 사용해 어셈블리스크립트·러스트 등 지원 언어로 코드 작성 후 빌드하며, 실험적 기능이므로 운영 전 철저한 테스트가 필요하다.
Distributing WebAssembly Modules 실습
Istio의 WasmPlugin을 사용해 OCI 레지스트리에서 원격 Wasm 모듈을 동적으로 로드하며, HTTP 기본 인증 확장 등을 구성할 수 있다.
WasmPlugin 리소스로 필터 단계(phase)와 설정을 정의하면 Istio 에이전트가 자동으로 모듈을 다운로드해 Envoy에 주입한다.
# 설정 전 호출 확인
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 모듈을 확인
# 자격 증명 없이 테스트
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
# 자격 증명으로 테스트
docker exec -it mypc curl -s -H "Authorization: Basic YWRtaW4zOmFkbWluMw==" -H "Host: webapp.istioinaction.io" http://$EXT_IP/api/catalog -v
Istio 에 Coraza (코라자) WAF 적용 - KrBlog
Coraza는 Go 언어로 개발된 **오픈소스 웹 애플리케이션 방화벽(WAF)**으로, **OWASP CRS(핵심 규칙 세트)**와 완벽 호환되며 ModSecurity SecLang 규칙을 지원한다.
#
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
2025-05-18T09:34:21.049522Z critical envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157 wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 1). Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11347"] [id "949111"] [rev ""] [msg "Inbound Anomaly Score Exceeded in phase 1 (Total Score: 20)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog?arg=<script>alert(0)</script>"] [unique_id "ztBHScSiiDnOiwJfmOy"] thread=30
[2025-05-18T09:34:21.014Z] "HEAD /api/catalog?arg=<script>alert(0)</script> HTTP/1.1" 403 - - "-" 0 0 35 - "172.18.0.1" "curl/8.7.1" "27f43c69-359d-9d95-99cd-553081bb9346" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.10:8080 172.18.0.1:65166 - -
# 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
2025-05-18T09:42:40.613124Z critical envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1157 wasm log istio-system.coraza-ingressgateway: [client "172.18.0.1"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 10) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "11358"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 10)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc2"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname "10.10.0.10"] [uri "/api/catalog"] [unique_id "GYSnZlurGTXFtujeNgl"] thread=34
[2025-05-18T09:42:40.575Z] "POST /api/catalog HTTP/1.1" 403 - - "-" 26 0 39 - "172.18.0.1" "curl/8.7.1" "c30d47e1-5631-99ae-91f2-5dfd8174b421" "webapp.istioinaction.io:30000" "10.10.0.16:8080" outbound|80||webapp.istioinaction.svc.cluster.local 10.10.0.10:44518 10.10.0.10:8080 172.18.0.1:63266 - -
...
WAF(WASM)으로 인해 악의적인 접근이 차단된것을 알 수 있다.