LabHub
배우기 러닝패스 코스

Istio Service Mesh

You opened an extension point and now upgrades are scary: EnvoyFilter and WasmPlugin

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

EnvoyFilter 의 네 자리(applyTo·match.context·patch.operation·priority)를 API 서버가 어떻게 강제하는지 직접 확인하고, 같은 요구를 WasmPlugin 으로 적으면 무엇이 달라지는지 비교한다. 마지막에는 클러스터의 모든 EnvoyFilter 를 훑어 판올림 위험을 코드로 찍어 주는 점검 스크립트를 만든다.

왜 중요한가

메시를 오래 쓰면 표준 API 로 표현되지 않는 요구가 반드시 온다. 그때 EnvoyFilter 를 쓰게 되는데, 이 API 는 Istio 의 추상이 아니라 Envoy 의 내부 설정 구조를 직접 건드린다. 그래서 Istio 를 판올림하면 프록시 안의 필터 이름이나 구조가 바뀌어 패치가 조용히 적용되지 않을 수 있다 — 오류도 나지 않고, 알림도 울리지 않는다. 그렇다고 쓰지 말라는 말은 현실적이지 않다. 대신 세 가지를 습관으로 만든다. 범위를 최대한 좁히고(네임스페이스와 workloadSelector), 상대 위치 패치에는 우선순위를 적고, 어디에 무엇을 끼웠는지 기계가 읽을 수 있는 점검표로 남긴다. 그래야 판올림 전에 무엇을 다시 확인해야 하는지 사람이 기억하지 않아도 된다.

단계

  1. /root/ist-envoyfilter 와 네임스페이스 ext-lab 를 만드세요. 그리고 클러스터에 등록된 확장 API 두 가지를 /root/ist-envoyfilter/ext-apis.tsv<복수형 이름>\t<API 그룹>\t<kind> 로 두 줄 적으세요 — EnvoyFilter 와 WasmPlugin 입니다. 이름 오름차순으로 정렬합니다.
  2. /root/ist-envoyfilter/ef-broken.yamlext-lab 네임스페이스의 EnvoyFilter inbound-lua 를 쓰되 세 곳을 일부러 틀리세요 — applyToHTTP_FILTERS, match.contextSIDECAR_IN, patch.operationINSERT_BEFORE_ALL 입니다. workloadSelectorapp: checkout 으로 둡니다. 이 파일을 적용하지 말고 서버 쪽 시험 적용만 해서 거절 문장을 /root/ist-envoyfilter/ef-reject.txt 에 저장하세요.
  3. /root/ist-envoyfilter/ef-inbound.yaml 에 고친 EnvoyFilter inbound-lua 를 쓰고 ext-lab 에 실제로 적용하세요 — applyToHTTP_FILTER, match.contextSIDECAR_INBOUND, patch.operationINSERT_BEFORE 이고, match.listener.filterChain.filter.nameenvoy.filters.network.http_connection_manager 입니다. patch.value 에는 name: envoy.filters.http.lua 만 적고 priority아직 넣지 마세요.
  4. 네임스페이스 istio-system 을 만들고 그 안에 EnvoyFilter mesh-access-log 를 적용하세요 — workloadSelector두지 않고, applyToNETWORK_FILTER, match.contextANY, patch.operationMERGE 로 액세스 로그를 /dev/stdout 에 붙이는 typed_config 를 씁니다. 매니페스트는 /root/ist-envoyfilter/ef-mesh.yaml 에 두세요. 그리고 지금 클러스터의 EnvoyFilter 마다 <네임스페이스>/<이름>\t<selector 있음 yes|no>/root/ist-envoyfilter/ef-scope.tsv 에 정렬해 적으세요.
  5. istioctl analyze -n ext-lab -o json 의 출력을 /root/ist-envoyfilter/analyze-before.json 에 저장하세요 — 3단계에서 올린 inbound-luaIST0151 경고가 붙어 있어야 합니다. 그다음 /root/ist-envoyfilter/ef-inbound-priority.yamlspec.priority10 으로 더한 판을 써서 다시 적용하고, 같은 명령의 출력을 /root/ist-envoyfilter/analyze-after.json 에 저장하세요. 뒤쪽에는 IST0151 이 없어야 합니다.
  6. /root/ist-envoyfilter/wasm.yamlext-lab 의 WasmPlugin header-check 를 쓰고 적용하세요 — selector.matchLabelsapp: checkout, urloci://registry.lab.internal/plugins/header-check:1.0, phaseAUTHZ, priority 는 20, pluginConfig.headerx-lab-tier 입니다. 그리고 /root/ist-envoyfilter/wasm-bad.yamlheader-check-badphase: PRE_AUTHZ 로 써서 서버 시험 적용에 거절당하게 하고 그 문장을 /root/ist-envoyfilter/wasm-reject.txt 에 저장하세요.
  7. 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 에 저장하세요 — 분석기가 잡는 것과 검증기가 잡는 것이 같지 않다는 것을 눈으로 확인합니다.
  8. 네임스페이스 legacy-lab 를 만들고 EnvoyFilter legacy-ratelimit 을 적용하세요 — workloadSelector 없음, applyToHTTP_FILTER, match.contextSIDECAR_INBOUND, patch.operationINSERT_AFTER, patch.valuename: 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 에 저장하세요.

참고

확장 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.yamlext-lab 네임스페이스의 EnvoyFilter inbound-lua 를 쓰되 세 곳을 일부러 틀리세요 — applyToHTTP_FILTERS, match.contextSIDECAR_IN, patch.operationINSERT_BEFORE_ALL 입니다. workloadSelectorapp: 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 에 실제로 적용하세요 — applyToHTTP_FILTER, match.contextSIDECAR_INBOUND, patch.operationINSERT_BEFORE 이고, match.listener.filterChain.filter.nameenvoy.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두지 않고, applyToNETWORK_FILTER, match.contextANY, patch.operationMERGE 로 액세스 로그를 /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-luaIST0151 경고가 붙어 있어야 합니다. 그다음 /root/ist-envoyfilter/ef-inbound-priority.yamlspec.priority10 으로 더한 판을 써서 다시 적용하고, 같은 명령의 출력을 /root/ist-envoyfilter/analyze-after.json 에 저장하세요. 뒤쪽에는 IST0151 이 없어야 합니다.

IST0151 은 INSERT_BEFORE 같은 상대 위치 연산을 쓰면서 priority 를 적지 않았을 때 나옵니다. 적용 순서가 보장되지 않아 패치가 아예 반영되지 않을 수 있다는 뜻입니다. -o json 을 붙이면 코드와 대상이 기계가 읽을 수 있는 모양으로 나옵니다.

같은 일을 표준 확장 API 로 적으면 무엇이 달라지나

/root/ist-envoyfilter/wasm.yamlext-lab 의 WasmPlugin header-check 를 쓰고 적용하세요 — selector.matchLabelsapp: checkout, urloci://registry.lab.internal/plugins/header-check:1.0, phaseAUTHZ, priority 는 20, pluginConfig.headerx-lab-tier 입니다. 그리고 /root/ist-envoyfilter/wasm-bad.yamlheader-check-badphase: 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 없음, applyToHTTP_FILTER, match.contextSIDECAR_INBOUND, patch.operationINSERT_AFTER, patch.valuename: 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 셋입니다.