LabHub
배우기 러닝패스 코스

Istio Deep Dive — Why It Flows That Way

Insert a patch and tell istioctl's verdict from Envoy's

LabHub 에서 이어서 보기

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

목표

EnvoyFilter 로 fault 필터를 router 앞에 끼우고, 그 패치가 Envoy 설정의 어디가 되는지 손으로 재현한다. router 뒤에 끼운 것과 값이 틀린 것을 만들어 istioctl 과 Envoy 의 판정이 갈리는 것을 보고, 판 묶기와 적용 범위를 확인한다.

왜 중요한가

EnvoyFilter 는 메시에서 가장 힘센 리소스이면서 가장 쉽게 조용히 깨지는 리소스다. istioctl 이 통과시켜도 Envoy 가 거절하면 운영에서는 '설정이 안 바뀐다' 로만 보인다. 패치가 닿는 자리와 검사의 한계를 알면 머지하기 전에 걸러 낼 수 있다.

단계

  1. /root/ist2-ef 를 만들고 /root/ist2-ef/ef.yaml 에 EnvoyFilter 를 쓰세요 — apiVersion: networking.istio.io/v1alpha3, 이름 reviews-fault, 네임스페이스 default, workloadSelectorapp: reviews. 패치는 하나로, applyTo: HTTP_FILTER, match.context: SIDECAR_INBOUND, match.listener.filterChain.filter.nameenvoy.filters.network.http_connection_manager, 그 subFilter.nameenvoy.filters.http.router, patch.operation: INSERT_BEFORE. 끼울 값은 이름 envoy.filters.http.faulttyped_config(@typetype.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault)로 abort.http_status: 418, abort.percentagenumerator: 100·denominator: HUNDRED 입니다. istioctl validate -f /root/ist2-ef/ef.yaml 의 출력(표준 오류 포함)과 종료 코드를 /root/ist2-ef/01-validate.txt 에 담으세요(마지막 줄 rc=).
  2. /root/ist2-ef/ef.yaml 의 첫 패치에서 다섯 값을 yq 로 뽑아 /root/ist2-ef/02-fields.txt 에 적으세요 — applyTo=, context=(match.context), operation=(patch.operation), anchor=(match 가 기준으로 삼는 HTTP 필터, 곧 subFilter.name), filter=(끼워 넣는 값의 name).
  3. /root/ist2-ef/envoy-before.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9989, 리스너 virtualInbound127.0.0.1:10089 에서 듣고, HTTP 연결 관리자의 stat_prefixinbound_0.0.0.0_9080, 모든 경로를 클러스터 inbound|9080||(127.0.0.1:8112)로 보냅니다. http_filtersef.yaml 의 패치 값과 똑같은 fault 필터 항목, 그다음 router 순서입니다. 업스트림을 8112ok 로 띄우고 Envoy 를 띄운 뒤 curl localhost:10089/reviews 를 세 번 보내, /root/ist2-ef/03-before.txt 에 세 줄을 적으세요 — codes=(세 응답 코드를 쉼표로), stat_name=(fault 가 중단시킨 요청 수를 세는 통계의 전체 이름), aborts_injected=(그 통계의 값).
  4. /root/ist2-ef/ef.yaml/root/ist2-ef/ef-after.yaml 로 복사해 patch.operationINSERT_AFTER 로 바꾸세요. 그리고 /root/ist2-ef/envoy-before.yaml/root/ist2-ef/envoy-after.yaml 로 복사해 http_filters 의 순서만 router, fault 로 뒤집으세요(그 패치가 적용된 결과). 두 파일을 각각 istioctl validate -fenvoy --mode validate -c 로 검사해 /root/ist2-ef/04-after.txt 에 적으세요 — 첫 줄 istioctl_rc=, 둘째 줄 envoy_rc=, 그 아래에 Envoy 가 거절한 이유가 담긴 출력 줄을 그대로.
  5. /root/ist2-ef/ef.yaml/root/ist2-ef/ef-badfield.yaml 로, /root/ist2-ef/envoy-before.yaml/root/ist2-ef/envoy-badfield.yaml 로 복사한 뒤 두 파일의 fault abort.percentageVirtualService 에서 쓰던 모양 { value: 100 } 으로 바꾸세요(나머지는 그대로). istioctl validate -fenvoy --mode validate -c 로 각각 검사해 /root/ist2-ef/05-gap.txt 에 적으세요 — 첫 줄 istioctl_rc=, 둘째 줄 envoy_rc=, 그 아래에 istioctl 이 이 필드를 문제 삼은 경고 줄과 Envoy 가 거절한 이유 줄을 그대로.
  6. /root/ist2-ef/ef.yaml/root/ist2-ef/ef-pinned.yaml 로 복사해 첫 패치의 match.proxy.proxyVersion 에 정규식 ^1\.24.* 을 더하세요(나머지는 그대로). istioctl validate 로 통과하는지 확인하고, 이 실습의 프록시 판을 알아내기 위해 /root/ist2-ef 에서 istioctl kube-inject/opt/lab/fixtures/istio/inject-target.yaml 을 주입해 /root/ist2-ef/inject.yaml 로 저장하세요(주입 설정 세 파일 /opt/istio/inject-config.yaml·mesh-config.yaml·values-config.yaml 을 모두 넘깁니다). /root/ist2-ef/06-version.txt 에 네 줄을 적으세요 — regex=(ef-pinned.yaml 에 적은 정규식 그대로), proxy_version=(istio-proxy 이미지의 태그), matches_proxy=(그 판이 정규식에 맞으면 yes, 아니면 no), matches_1_25_0=(가상의 판 1.25.0 이 맞으면 yes, 아니면 no).
  7. /root/ist2-ef/ef-ratelimit.yaml 에 EnvoyFilter 를 쓰세요 — 이름 inbound-ratelimit, 네임스페이스 default, workloadSelector 없이, 패치 모양은 ef.yaml 과 같되(HTTP_FILTER · SIDECAR_INBOUND · router 앞 INSERT_BEFORE) 값은 이름 envoy.filters.http.local_ratelimit, @type type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit, stat_prefix: http_local_rate_limiter, token_bucketmax_tokens: 1·tokens_per_fill: 1·fill_interval: 300s, filter_enabledfilter_enforced 는 둘 다 default_valuenumerator: 100·denominator: HUNDRED 입니다. istioctl validate 로 값 경고가 없는지 확인한 뒤, 그 값을 router 앞에 둔 Envoy 설정을 /root/ist2-ef/envoy-rl.yaml 에 쓰세요(관리 포트·리스너·클러스터는 3단계와 같습니다). 띄우고 curl localhost:10089/reviews 를 세 번 보낸 뒤 /root/ist2-ef/07-ratelimit.txt 에 네 줄을 적으세요 — codes=(세 응답 코드를 쉼표로), rate_limited=(통계 http_local_rate_limit.rate_limited 의 값), scope=(이 EnvoyFilter 가 닿는 범위: workload·namespace·mesh 중 하나), mesh_wide_namespace=(같은 파일을 옮겨 두면 메시 전체에 닿는 네임스페이스, /opt/istio/mesh-config.yaml 에서).
  8. /root/ist2-ef/08-report.mdbefore_status=, after_envoy_rc=, badfield_istioctl_rc=, ratelimit_codes= 네 줄을 적고(각각 3단계에서 fault 가 돌려준 상태 코드, 4단계에서 Envoy 의 종료 코드, 5단계에서 istioctl 의 종료 코드, 7단계의 세 응답 코드), 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

참고

router 앞에 fault 를 끼우는 EnvoyFilter 를 쓴다

/root/ist2-ef 를 만들고 /root/ist2-ef/ef.yaml 에 EnvoyFilter 를 쓰세요 — apiVersion: networking.istio.io/v1alpha3, 이름 reviews-fault, 네임스페이스 default, workloadSelectorapp: reviews. 패치는 하나로, applyTo: HTTP_FILTER, match.context: SIDECAR_INBOUND, match.listener.filterChain.filter.nameenvoy.filters.network.http_connection_manager, 그 subFilter.nameenvoy.filters.http.router, patch.operation: INSERT_BEFORE. 끼울 값은 이름 envoy.filters.http.faulttyped_config(@typetype.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault)로 abort.http_status: 418, abort.percentagenumerator: 100·denominator: HUNDRED 입니다. istioctl validate -f /root/ist2-ef/ef.yaml 의 출력(표준 오류 포함)과 종료 코드를 /root/ist2-ef/01-validate.txt 에 담으세요(마지막 줄 rc=).

EnvoyFilter 의 패치 하나는 '어디에'(applyTo·match)와 '무엇을'(operation·value)의 짝입니다. HTTP_FILTERsubFilter 로 기준 필터를 고르면, 그 필터가 든 HTTP 연결 관리자의 필터 목록이 무대가 됩니다. value 는 Istio 문법이 아니라 Envoy 설정 그대로라서, 퍼센트도 Envoy 의 FractionalPercent (분자·분모)로 적어야 합니다. 올바른 파일이어도 istioctl 은 EnvoyFilter 자체에 대한 경고 한 줄을 냅니다 — 그 경고는 정상이고, 값 안의 필드를 문제 삼는 경고가 더 나오면 고쳐야 합니다.

패치의 '어디에' 와 '무엇을' 을 네 칸으로 읽는다

/root/ist2-ef/ef.yaml 의 첫 패치에서 다섯 값을 yq 로 뽑아 /root/ist2-ef/02-fields.txt 에 적으세요 — applyTo=, context=(match.context), operation=(patch.operation), anchor=(match 가 기준으로 삼는 HTTP 필터, 곧 subFilter.name), filter=(끼워 넣는 값의 name).

applyTo 는 패치가 닿는 Envoy 객체의 종류(리스너·필터 체인·네트워크 필터·HTTP 필터·클러스터·라우트…), context 는 어느 방향의 프록시인가(사이드카의 들어오는 쪽·나가는 쪽, 게이트웨이), match 는 그 종류 안에서 어느 하나를 고르는 조건입니다. INSERT_BEFORE 같은 상대 위치 연산은 이 기준 필터가 있어야 뜻이 있습니다. filter.namesubFilter.name 을 헷갈리지 마세요 — 앞의 것은 네트워크 필터, 뒤의 것이 HTTP 필터입니다.

패치가 적용된 모습을 Envoy 로 세워 418 을 받는다

/root/ist2-ef/envoy-before.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9989, 리스너 virtualInbound127.0.0.1:10089 에서 듣고, HTTP 연결 관리자의 stat_prefixinbound_0.0.0.0_9080, 모든 경로를 클러스터 inbound|9080||(127.0.0.1:8112)로 보냅니다. http_filtersef.yaml 의 패치 값과 똑같은 fault 필터 항목, 그다음 router 순서입니다. 업스트림을 8112ok 로 띄우고 Envoy 를 띄운 뒤 curl localhost:10089/reviews 를 세 번 보내, /root/ist2-ef/03-before.txt 에 세 줄을 적으세요 — codes=(세 응답 코드를 쉼표로), stat_name=(fault 가 중단시킨 요청 수를 세는 통계의 전체 이름), aborts_injected=(그 통계의 값).

istiod 가 EnvoyFilter 를 적용하면 이 사이드카의 들어오는 쪽 리스너에서 HTTP 필터 목록이 [..., fault, router] 가 됩니다. 이 파드에는 istiod 가 없으니 그 결과를 손으로 만드는 것입니다. 패치의 value그대로 목록의 항목 하나가 된다는 점이 핵심입니다 — 옮기면서 값을 바꾸지 마세요. HTTP 필터의 통계는 http.<stat_prefix>. 아래에 쌓입니다. 관리 포트의 /stats?filter= 로 좁혀 보세요.

router 뒤에 끼우면 istioctl 은 통과, Envoy 는 거절

/root/ist2-ef/ef.yaml/root/ist2-ef/ef-after.yaml 로 복사해 patch.operationINSERT_AFTER 로 바꾸세요. 그리고 /root/ist2-ef/envoy-before.yaml/root/ist2-ef/envoy-after.yaml 로 복사해 http_filters 의 순서만 router, fault 로 뒤집으세요(그 패치가 적용된 결과). 두 파일을 각각 istioctl validate -fenvoy --mode validate -c 로 검사해 /root/ist2-ef/04-after.txt 에 적으세요 — 첫 줄 istioctl_rc=, 둘째 줄 envoy_rc=, 그 아래에 Envoy 가 거절한 이유가 담긴 출력 줄을 그대로.

router 는 요청을 업스트림으로 보내고 끝내는 필터라서 Envoy 는 그 뒤에 필터가 오는 것을 설정 단계에서 막습니다. istioctl 은 EnvoyFilter 의 겉 구조만 알고 Envoy 의 필터 순서 규칙은 모르므로 이 패치를 통과시킵니다. 운영에서는 istiod 가 그대로 밀어 넣고 Envoy 가 그 리스너 갱신을 거절(NACK)합니다 — 파드는 멀쩡해 보이는데 설정이 안 바뀌는 모습입니다. 종료 코드는 명령 바로 뒤의 $? 로 받으세요.

VirtualService 식 퍼센트를 옮겨 적으면 경고만 나온다

/root/ist2-ef/ef.yaml/root/ist2-ef/ef-badfield.yaml 로, /root/ist2-ef/envoy-before.yaml/root/ist2-ef/envoy-badfield.yaml 로 복사한 뒤 두 파일의 fault abort.percentageVirtualService 에서 쓰던 모양 { value: 100 } 으로 바꾸세요(나머지는 그대로). istioctl validate -fenvoy --mode validate -c 로 각각 검사해 /root/ist2-ef/05-gap.txt 에 적으세요 — 첫 줄 istioctl_rc=, 둘째 줄 envoy_rc=, 그 아래에 istioctl 이 이 필드를 문제 삼은 경고 줄과 Envoy 가 거절한 이유 줄을 그대로.

VirtualService 의 fault.abort.percentage.value 는 Istio 의 퍼센트 타입이고, Envoy 의 fault 필터는 분자·분모로 된 FractionalPercent 를 씁니다. 사람이 흔히 옮겨 적는 실수입니다. istioctl 은 값을 Envoy 타입으로 풀어 보기는 하지만 모르는 필드를 경고로만 알리고 종료 코드는 0 입니다 — CI 가 종료 코드만 본다면 그대로 배포됩니다. 반면 @type 이름 자체가 틀리면 istioctl 도 오류로 막습니다. 어느 쪽을 믿을지 기준이 여기서 갈립니다. yq 로 한 필드만 바꾸면 나머지를 건드리지 않습니다.

proxyVersion 으로 패치를 한 판에 묶는다

/root/ist2-ef/ef.yaml/root/ist2-ef/ef-pinned.yaml 로 복사해 첫 패치의 match.proxy.proxyVersion 에 정규식 ^1\.24.* 을 더하세요(나머지는 그대로). istioctl validate 로 통과하는지 확인하고, 이 실습의 프록시 판을 알아내기 위해 /root/ist2-ef 에서 istioctl kube-inject/opt/lab/fixtures/istio/inject-target.yaml 을 주입해 /root/ist2-ef/inject.yaml 로 저장하세요(주입 설정 세 파일 /opt/istio/inject-config.yaml·mesh-config.yaml·values-config.yaml 을 모두 넘깁니다). /root/ist2-ef/06-version.txt 에 네 줄을 적으세요 — regex=(ef-pinned.yaml 에 적은 정규식 그대로), proxy_version=(istio-proxy 이미지의 태그), matches_proxy=(그 판이 정규식에 맞으면 yes, 아니면 no), matches_1_25_0=(가상의 판 1.25.0 이 맞으면 yes, 아니면 no).

istiod 는 프록시가 접속할 때 알려 온 자기 판(ISTIO_VERSION 메타데이터)을 이 정규식에 대 보고, 맞을 때만 패치를 붙입니다. 업그레이드로 Envoy 의 필터 이름이나 설정 모양이 바뀌면 옛 패치가 새 프록시를 깨뜨릴 수 있는데, 판을 묶어 두면 새 프록시에는 패치가 아예 붙지 않습니다. 점은 정규식에서 아무 글자이므로 \. 로 막고, 앞을 ^ 로 고정하세요. 판별은 grep -E 로 해 볼 수 있습니다. 주입 산출물 끝의 빈 문서 때문에 yqselect(.kind=="Deployment") 로 거르세요.

선택자 없는 EnvoyFilter 로 네임스페이스 전체에 요청 제한을 건다

/root/ist2-ef/ef-ratelimit.yaml 에 EnvoyFilter 를 쓰세요 — 이름 inbound-ratelimit, 네임스페이스 default, workloadSelector 없이, 패치 모양은 ef.yaml 과 같되(HTTP_FILTER · SIDECAR_INBOUND · router 앞 INSERT_BEFORE) 값은 이름 envoy.filters.http.local_ratelimit, @type type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit, stat_prefix: http_local_rate_limiter, token_bucketmax_tokens: 1·tokens_per_fill: 1·fill_interval: 300s, filter_enabledfilter_enforced 는 둘 다 default_valuenumerator: 100·denominator: HUNDRED 입니다. istioctl validate 로 값 경고가 없는지 확인한 뒤, 그 값을 router 앞에 둔 Envoy 설정을 /root/ist2-ef/envoy-rl.yaml 에 쓰세요(관리 포트·리스너·클러스터는 3단계와 같습니다). 띄우고 curl localhost:10089/reviews 를 세 번 보낸 뒤 /root/ist2-ef/07-ratelimit.txt 에 네 줄을 적으세요 — codes=(세 응답 코드를 쉼표로), rate_limited=(통계 http_local_rate_limit.rate_limited 의 값), scope=(이 EnvoyFilter 가 닿는 범위: workload·namespace·mesh 중 하나), mesh_wide_namespace=(같은 파일을 옮겨 두면 메시 전체에 닿는 네임스페이스, /opt/istio/mesh-config.yaml 에서).

workloadSelector 가 있으면 라벨이 맞는 워크로드에만, 없으면 그 네임스페이스의 모든 워크로드에, 그리고 메시 설정의 루트 네임스페이스에 두면 메시 전체에 붙습니다. 루트 네임스페이스의 것이 먼저, 워크로드 네임스페이스의 것이 나중에 적용됩니다. 요청 제한 값 안에 runtime_key 는 넣지 마세요 — 이 판의 Envoy 가 거절합니다. 토큰이 한 개이고 채워지는 간격이 길면 첫 요청만 지나갑니다. 제한된 응답의 코드가 무엇인지 직접 보세요.

EnvoyFilter 를 쓰기 전의 점검표로 정리한다

/root/ist2-ef/08-report.mdbefore_status=, after_envoy_rc=, badfield_istioctl_rc=, ratelimit_codes= 네 줄을 적고(각각 3단계에서 fault 가 돌려준 상태 코드, 4단계에서 Envoy 의 종료 코드, 5단계에서 istioctl 의 종료 코드, 7단계의 세 응답 코드), 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

값은 앞 단계 파일에서 옮기세요. 설명 줄에는 'EnvoyFilter 를 머지하기 전에 무엇을 확인하는가' 를 적으면 이 표가 리뷰 체크리스트가 됩니다 — 패치가 닿는 자리, router 의 위치, istioctl 이 못 잡는 것, 판 묶기, 범위.