打开扩展点之后升级变得可怕——EnvoyFilter 与 WasmPlugin
한국어 원문으로 표시합니다.
목표
EnvoyFilter 의 네 자리(applyTo·match.context·patch.operation·priority)를 API 서버가 어떻게 강제하는지 직접 확인하고, 같은 요구를 WasmPlugin 으로 적으면 무엇이 달라지는지 비교한다. 마지막에는 클러스터의 모든 EnvoyFilter 를 훑어 판올림 위험을 코드로 찍어 주는 점검 스크립트를 만든다.
왜 중요한가
메시를 오래 쓰면 표준 API 로 표현되지 않는 요구가 반드시 온다. 그때 EnvoyFilter 를 쓰게 되는데, 이 API 는 Istio 의 추상이 아니라 Envoy 의 내부 설정 구조를 직접 건드린다. 그래서 Istio 를 판올림하면 프록시 안의 필터 이름이나 구조가 바뀌어 패치가 조용히 적용되지 않을 수 있다 — 오류도 나지 않고, 알림도 울리지 않는다. 그렇다고 쓰지 말라는 말은 현실적이지 않다. 대신 세 가지를 습관으로 만든다. 범위를 최대한 좁히고(네임스페이스와 workloadSelector), 상대 위치 패치에는 우선순위를 적고, 어디에 무엇을 끼웠는지 기계가 읽을 수 있는 점검표로 남긴다. 그래야 판올림 전에 무엇을 다시 확인해야 하는지 사람이 기억하지 않아도 된다.
단계
/root/ist-envoyfilter와 네임스페이스ext-lab를 만드세요. 그리고 클러스터에 등록된 확장 API 두 가지를/root/ist-envoyfilter/ext-apis.tsv에<복수형 이름>\t<API 그룹>\t<kind>로 두 줄 적으세요 — EnvoyFilter 와 WasmPlugin 입니다. 이름 오름차순으로 정렬합니다./root/ist-envoyfilter/ef-broken.yaml에ext-lab네임스페이스의 EnvoyFilterinbound-lua를 쓰되 세 곳을 일부러 틀리세요 —applyTo는HTTP_FILTERS,match.context는SIDECAR_IN,patch.operation은INSERT_BEFORE_ALL입니다.workloadSelector는app: checkout으로 둡니다. 이 파일을 적용하지 말고 서버 쪽 시험 적용만 해서 거절 문장을/root/ist-envoyfilter/ef-reject.txt에 저장하세요./root/ist-envoyfilter/ef-inbound.yaml에 고친 EnvoyFilterinbound-lua를 쓰고ext-lab에 실제로 적용하세요 —applyTo는HTTP_FILTER,match.context는SIDECAR_INBOUND,patch.operation은INSERT_BEFORE이고,match.listener.filterChain.filter.name은envoy.filters.network.http_connection_manager입니다.patch.value에는name: envoy.filters.http.lua만 적고priority는 아직 넣지 마세요.- 네임스페이스
istio-system을 만들고 그 안에 EnvoyFiltermesh-access-log를 적용하세요 —workloadSelector는 두지 않고,applyTo는NETWORK_FILTER,match.context는ANY,patch.operation은MERGE로 액세스 로그를/dev/stdout에 붙이는typed_config를 씁니다. 매니페스트는/root/ist-envoyfilter/ef-mesh.yaml에 두세요. 그리고 지금 클러스터의 EnvoyFilter 마다<네임스페이스>/<이름>\t<selector 있음 yes|no>를/root/ist-envoyfilter/ef-scope.tsv에 정렬해 적으세요. istioctl analyze -n ext-lab -o json의 출력을/root/ist-envoyfilter/analyze-before.json에 저장하세요 — 3단계에서 올린inbound-lua에IST0151경고가 붙어 있어야 합니다. 그다음/root/ist-envoyfilter/ef-inbound-priority.yaml에spec.priority를10으로 더한 판을 써서 다시 적용하고, 같은 명령의 출력을/root/ist-envoyfilter/analyze-after.json에 저장하세요. 뒤쪽에는IST0151이 없어야 합니다./root/ist-envoyfilter/wasm.yaml에ext-lab의 WasmPluginheader-check를 쓰고 적용하세요 —selector.matchLabels는app: checkout,url은oci://registry.lab.internal/plugins/header-check:1.0,phase는AUTHZ,priority는 20,pluginConfig.header는x-lab-tier입니다. 그리고/root/ist-envoyfilter/wasm-bad.yaml에header-check-bad를phase: PRE_AUTHZ로 써서 서버 시험 적용에 거절당하게 하고 그 문장을/root/ist-envoyfilter/wasm-reject.txt에 저장하세요.istioctl analyze -n ext-lab -o json을 다시 돌려/root/ist-envoyfilter/findings.tsv에<코드>\t<대상>을 한 줄씩 정렬해 적으세요. 대상은 분석 결과의origin값을 그대로 씁니다. 그리고/root/ist-envoyfilter/ef-inbound.yaml(3단계 파일)을istioctl validate로 검사한 출력을/root/ist-envoyfilter/validate.txt에 저장하세요 — 분석기가 잡는 것과 검증기가 잡는 것이 같지 않다는 것을 눈으로 확인합니다.- 네임스페이스
legacy-lab를 만들고 EnvoyFilterlegacy-ratelimit을 적용하세요 —workloadSelector없음,applyTo는HTTP_FILTER,match.context는SIDECAR_INBOUND,patch.operation은INSERT_AFTER,patch.value는name: envoy.filters.http.ratelimit만 적습니다(매니페스트는/root/ist-envoyfilter/ef-legacy.yaml). 그다음/root/ist-envoyfilter/ef-audit.sh를 만들어 클러스터의 모든 EnvoyFilter 를<네임스페이스>/<이름>\t<위험코드들>로 정렬해 표준출력에만 찍게 하세요. 위험코드는no-selector(workloadSelector 없음)·no-priority(상대 위치 연산인데 priority 없음)·name-only(typed_config 없이 이름만) 세 가지를 쉼표로 이어 사전순으로 쓰고, 하나도 없으면-를 씁니다. 출력을/root/ist-envoyfilter/audit-result.txt에 저장하세요.
참고
- applyTo·context·operation·phase 는 모두 열거형이라 API 서버가 허용 값 목록을 돌려줍니다.
kubectl apply --dry-run=server는 만들지 않고 서버에게 물어만 봅니다.- IST0151 은 우선순위 없는 상대 위치 패치를 가리킵니다.
istioctl analyze -o json으로 코드만 뽑을 수 있습니다. - 흔한 실수: jq 에서
.spec.priority // "없음"처럼 쓰면 0 도 없는 것이 됩니다.== null로 비교하세요. - 흔한 실수: 루트 네임스페이스에 둔 EnvoyFilter 를 네임스페이스용으로 착각합니다. 거긴 메시 전체입니다.
- 참고: https://istio.io/v1.24/docs/reference/config/networking/envoy-filter/
- 참고: https://istio.io/v1.24/docs/reference/config/proxy_extensions/wasm-plugin/
- 참고: https://istio.io/v1.24/docs/reference/config/analysis/
확장 API 두 가지가 어디에 등록돼 있는지 확인한다
/root/ist-envoyfilter 와 네임스페이스 ext-lab 를 만드세요. 그리고 클러스터에 등록된 확장 API 두 가지를 /root/ist-envoyfilter/ext-apis.tsv 에 <복수형 이름>\t<API 그룹>\t<kind> 로 두 줄 적으세요 — EnvoyFilter 와 WasmPlugin 입니다. 이름 오름차순으로 정렬합니다.
kubectl api-resources 가 복수형 이름·API 그룹·kind 를 모두 보여 줍니다. 두 확장 API 는 서로 다른 그룹에 들어 있습니다 — 하나는 트래픽 API 와 같은 그룹, 다른 하나는 확장 전용 그룹입니다.
열거형 세 개를 한꺼번에 틀려 본다
/root/ist-envoyfilter/ef-broken.yaml 에 ext-lab 네임스페이스의 EnvoyFilter inbound-lua 를 쓰되 세 곳을 일부러 틀리세요 — applyTo 는 HTTP_FILTERS, match.context 는 SIDECAR_IN, patch.operation 은 INSERT_BEFORE_ALL 입니다. workloadSelector 는 app: checkout 으로 둡니다. 이 파일을 적용하지 말고 서버 쪽 시험 적용만 해서 거절 문장을 /root/ist-envoyfilter/ef-reject.txt 에 저장하세요.
kubectl apply --dry-run=server 는 API 서버에게 물어만 보고 아무것도 만들지 않습니다. CRD 에 구조적 스키마가 있으면 서버가 허용 값 목록까지 돌려줍니다 — 세 곳이 한 번에 나오는지 세어 보세요.
세 곳을 고쳐 실제로 올린다
/root/ist-envoyfilter/ef-inbound.yaml 에 고친 EnvoyFilter inbound-lua 를 쓰고 ext-lab 에 실제로 적용하세요 — applyTo 는 HTTP_FILTER, match.context 는 SIDECAR_INBOUND, patch.operation 은 INSERT_BEFORE 이고, match.listener.filterChain.filter.name 은 envoy.filters.network.http_connection_manager 입니다. patch.value 에는 name: envoy.filters.http.lua 만 적고 priority 는 아직 넣지 마세요.
HTTP 필터를 끼우려면 어느 네트워크 필터의 필터 체인 안인지도 함께 짚어 줘야 합니다. 그래서 match 에 listener.filterChain.filter.name 이 들어갑니다. priority 는 다음 단계에서 다룹니다.
메시 전체에 걸리는 자리와 워크로드 하나에 걸리는 자리
네임스페이스 istio-system 을 만들고 그 안에 EnvoyFilter mesh-access-log 를 적용하세요 — workloadSelector 는 두지 않고, applyTo 는 NETWORK_FILTER, match.context 는 ANY, patch.operation 은 MERGE 로 액세스 로그를 /dev/stdout 에 붙이는 typed_config 를 씁니다. 매니페스트는 /root/ist-envoyfilter/ef-mesh.yaml 에 두세요. 그리고 지금 클러스터의 EnvoyFilter 마다 <네임스페이스>/<이름>\t<selector 있음 yes|no> 를 /root/ist-envoyfilter/ef-scope.tsv 에 정렬해 적으세요.
루트 네임스페이스(기본값 istio-system)에 둔 EnvoyFilter 는 메시 전체에 걸립니다. 다른 네임스페이스에 두면 그 네임스페이스만, 거기에 workloadSelector 까지 붙이면 라벨이 맞는 워크로드만입니다. 범위는 좁을수록 사고가 작습니다.
상대 위치 패치에 우선순위가 없으면 분석기가 경고한다
istioctl analyze -n ext-lab -o json 의 출력을 /root/ist-envoyfilter/analyze-before.json 에 저장하세요 — 3단계에서 올린 inbound-lua 에 IST0151 경고가 붙어 있어야 합니다. 그다음 /root/ist-envoyfilter/ef-inbound-priority.yaml 에 spec.priority 를 10 으로 더한 판을 써서 다시 적용하고, 같은 명령의 출력을 /root/ist-envoyfilter/analyze-after.json 에 저장하세요. 뒤쪽에는 IST0151 이 없어야 합니다.
IST0151 은 INSERT_BEFORE 같은 상대 위치 연산을 쓰면서 priority 를 적지 않았을 때 나옵니다. 적용 순서가 보장되지 않아 패치가 아예 반영되지 않을 수 있다는 뜻입니다. -o json 을 붙이면 코드와 대상이 기계가 읽을 수 있는 모양으로 나옵니다.
같은 일을 표준 확장 API 로 적으면 무엇이 달라지나
/root/ist-envoyfilter/wasm.yaml 에 ext-lab 의 WasmPlugin header-check 를 쓰고 적용하세요 — selector.matchLabels 는 app: checkout, url 은 oci://registry.lab.internal/plugins/header-check:1.0, phase 는 AUTHZ, priority 는 20, pluginConfig.header 는 x-lab-tier 입니다. 그리고 /root/ist-envoyfilter/wasm-bad.yaml 에 header-check-bad 를 phase: PRE_AUTHZ 로 써서 서버 시험 적용에 거절당하게 하고 그 문장을 /root/ist-envoyfilter/wasm-reject.txt 에 저장하세요.
WasmPlugin 은 Envoy 내부 구조 대신 '어느 단계에 무엇을 끼울지' 만 적습니다. 단계 이름은 네 가지뿐이고 API 서버가 목록을 알려 줍니다. url 이 없으면 만들어지지 않는다는 것도 함께 확인해 보세요.
분석기가 이 네임스페이스에서 무엇을 잡는지 표로 굳힌다
istioctl analyze -n ext-lab -o json 을 다시 돌려 /root/ist-envoyfilter/findings.tsv 에 <코드>\t<대상> 을 한 줄씩 정렬해 적으세요. 대상은 분석 결과의 origin 값을 그대로 씁니다. 그리고 /root/ist-envoyfilter/ef-inbound.yaml(3단계 파일)을 istioctl validate 로 검사한 출력을 /root/ist-envoyfilter/validate.txt 에 저장하세요 — 분석기가 잡는 것과 검증기가 잡는 것이 같지 않다는 것을 눈으로 확인합니다.
jq -r '.[] | .code + "\t" + .origin' 이면 두 칸짜리 표가 바로 나옵니다. validate 는 파일 한 장만 보므로 클러스터 상태에서만 알 수 있는 것은 못 잡습니다. 두 도구는 대체재가 아니라 서로 다른 시점의 검사입니다.
판올림 전에 돌릴 점검표를 스크립트로 만든다
네임스페이스 legacy-lab 를 만들고 EnvoyFilter legacy-ratelimit 을 적용하세요 — workloadSelector 없음, applyTo 는 HTTP_FILTER, match.context 는 SIDECAR_INBOUND, patch.operation 은 INSERT_AFTER, patch.value 는 name: envoy.filters.http.ratelimit 만 적습니다(매니페스트는 /root/ist-envoyfilter/ef-legacy.yaml). 그다음 /root/ist-envoyfilter/ef-audit.sh 를 만들어 클러스터의 모든 EnvoyFilter 를 <네임스페이스>/<이름>\t<위험코드들> 로 정렬해 표준출력에만 찍게 하세요. 위험코드는 no-selector(workloadSelector 없음)·no-priority(상대 위치 연산인데 priority 없음)·name-only(typed_config 없이 이름만) 세 가지를 쉼표로 이어 사전순으로 쓰고, 하나도 없으면 - 를 씁니다. 출력을 /root/ist-envoyfilter/audit-result.txt 에 저장하세요.
kubectl get envoyfilter -A -o json 을 jq 로 훑으면 한 번에 셀 수 있습니다. .spec.priority // "" 처럼 기본값 연산자를 쓰면 0 이 없는 것으로 보이니 == null 로 비교하세요. 상대 위치 연산은 INSERT_BEFORE, INSERT_AFTER, INSERT_FIRST 셋입니다.