Search
📌

Istio resiliency 기반 장애 대응: feature 별 상세

Category
as S/W 엔지니어
Tags
Istio
resiliency
timeout
retry
circuit breaker
rate limit
Envoy
outlier detection
회복탄력성
Created time
2025/05/24

Introduction

Istio resiliency 기반 장애 대응: overview 앞선 overview에 대한 상세 설명과 대응 방법, 추천 설정이다.  유의 사항으로 모든 추천 설정은 일반적 웹 서비스를 가정한 값이므로 적정 값은 각 서비스에 따라 상당히 달라질 수 있다. 쉽지 않지만 반복적 테스트를 통해 얻기를 강력 추천한다(특히, Circuit Breaker, Rate Limiting).

Timeout

Timeout은 최대 응답 지연을 보장함과 동시에, 클라이언트 자원 고갈 문제를 방지하는 fast fail 기법으로, Circuit Breaker와 함께 클라이언트 자원 고갈 문제를 해결하는 주요 수단이다.

대응 문제

높은 지연 ( 주요 대응): 클라이언트의 오랜 대기를 방지한다. 최대 응답 지연 보장 효과이다.
클라이언트 자원 고갈 ( 주요 대응): 오랜 대기로 인한 자원 고갈 문제를 제거한다. 마이크로서비스의 클라이언트는 받은 요청 처리를 위해 서버를 호출하기 마련이다. 즉, 받은 요청 만큼 서버 호출을 위한 자원 소비량은 늘어난다.
연쇄적 장애 ( 부분적 대응): 클라이언트 자원 고갈로 인한 장애로의 발전을 사전에 차단한다. 하지만 서버 자원 고갈로 인한 장애는 해결하지 못하기에 한계가 있다. 서버 자원 고갈 대응안은 Rate Limiting이다.

유의사항, 설정 방법, 추천 설정

 호출 대상 API의 latency 고려: 호출 대상 API의 latency을 고려하여 설정하지 않으면 오탐(false positive)로 이어진다. 당연스럽게도 너무 길게 설정하면 설정 자체가 무의미해진다.
설정 방법: https://istio.io/latest/docs/reference/config/networking/virtual-service/timeout field 및 예제 참고한다.
추천 설정: 호출 대상 API의 latency을 고려하는 것이 가장 중요하다. 참고로, 웹 사용자는 일반적으로 2~3초 내 응답을 기대한다고(AI 피셜). 또한 AWS Lambda function 역시 default timeout은 3초이다.
timeout: 3s # 무엇보다 호출 대상 API의 latency를 고려해야 한다.
YAML
복사

Retry

Retry는 일시적인 장애(네트워크 오류, 일시적 서비스 장애 등)를 극복하기 위한 수단으로, 클라이언트의 불필요한 오류 조우 및 후속 조치를 방지하는 효과를 낸다. 데이터 유실에 민감한 request일 경우 특히 유용하다.

대응 문제

일시적 장애 ( 주요 대응): 일시적 서비스 오류 뿐 아니라 네트워크의 불안정성 특성 상, 네트워크 상에 일시적 오류가 발생 가능하며, 이때 재시도를 해보면 해결되는 경우가 상당하다.

유의 사항, 설정 방법, 추천 설정

 대상 API의 멱등성(idempotency) 보장 필요: 재시도 대상 API가 멱등성이 없는 경우 무결성을 해칠 수 있으므로, CUD(Create, Update, Delete) 연산에는 특히 적용에 유의해야 한다.
 과도한 재시도는 부수적 장애 야기 가능: 과도한 재시도는 서버의 반복 장애부터 클라이언트 자원 고갈까지 전반의 부수적 장애(collateral damage)를 야기한다.
 호출 대상 API의 latency 고려: Timeout 과 마찬가지로, 호출 대상 API의 latency을 고려하지 않고 perTryTimeout 을 설정하면 오탐(false positive)로 이어진다.
추천 설정: 대부분의 일시적 실패(네트워크 지연, 짧은 서비스 불안정)는 2~3회 내 해결된다. 재시도 전체 기간을 Timeout 에 맞춰 횟수와 재시도 내 timeout을 고려하는 것도 유의미하다.
retries: attempts: 2 # (attempts + 1) * perTryTimeout으로 전체 최대 지연을 고려(2는 최대 3번을 의미. 0이면 재시도 없음, 즉 1번만 호출을 의미) perTryTimeout: 1s # 호출 대상 API의 latency 고려 필요 retryOn: "5xx, connect-failure,refused-stream,unavailable,cancelled" # default 값에 5xx 추가
YAML
복사

Circuit Breaker

Circuit Breaker(CB)는 반복적, 연쇄적 장애 방지의 대표적 장치이자 Timeout과 함께 클라이언트 관점의 안정성 유지의 주요 수단으로, 서비스의 반복적 장애를 감지해 요청을 차단한다.
outlierDetection vs connectionPool
Envoy 맥락에서 Circuit Breaker는 일종의 연결 제한 기법인 connectionPool 을 의미하여, 일반적으로 알려진 Circuit Breaker 패턴은 outlierDetection 에서 다루는 반면, Istio는 이들 둘 모두를 Circuit Breaker로 통칭한다.
본 문서에서는 outlierDetection 만 다루고, connectionPool 은 고려할 사항이 수준 이상이라 일단 범위에서 제외한다. 기본값은 모두 제한 없음이다.

대응 문제

반복적, 연쇄적 장애 ( 주요 대응): 반복적 장애 서버 workload로의 트래픽을 제거(destination pod ejection)함으로, 클라이언트, 서버 모두에서 자원 소진을 최소화한다.
클라이언트 자원 고갈 ( 주요 대응): 장애 pod로의 요청을 차단함으로 클라이언트 자원 소모를 방지한다.
서버 자원 고갈 ( 부분적 대응): 서버 자원 고갈 역시 트래픽 차단으로 얻는 효과이다.
Kubernetes readiness probe에 의한 routing 대상 제외도 Circuit Breaker?
Istio는 k8s readiness probing 실패 시 해당 pod를 routing 대상에서 제외하며, 이는 기본 동작이다(kube-proxy와 동일). 따라서 ‘장애 pod의 routing 대상 제외’란 관점에서만 보자면 Circuit Breaker 전용 Istio field인 outlierDetection 기반 CB의 필요성에 의문을 갖게 한다.
Circuit Breaker 관점에서 둘 간의 차이는 동작 시점이다.
outlierDetection 기반 CB: 서비스 traffic 기반 즉시적 동작
Readiness probe 기반 CB(?): health checking 주기에 따른 주기적 동작
즉시적 동작 주기적 동작의 차이가 크지 않아 보일 수 있는데, 설정에 따라 다르지만 일반적으로 전자는 대응 시간이 밀리 초~수 초인 반면 후자는 수십 초가 걸리는 큰 차이를 보이며, 수십 초는 장애 전파에 무시하기 어려운 시간이다(AI 피셜. 장애 전파에 ChatGPT은 수초 이내, Grok은 밀리 초 ~ 수초를 논함).
굳이 따지자면 outlierDetection 기반 CB는 선제적 장애 예방 수단이고 readiness probe 기반 CB는 장애 사후 대응 수단이 될 듯 하다.

유의 사항

오설정 시 악화: 예기치 못한 서비스 중단 또는 연쇄적 장애 발생이 가능하다. 하기 오탐 대응안 참조.
Circuit Breaking 단위는 pod
즉, service(pod의 집합) 단위가 아니다(근거: 공식 문서는 field 설명 전반으로 ‘individual host’를 기준으로 논의. host는 k8s 환경에서 pod 또는 endpoint에 해당).
정확히는 pod가 아니라 endpoint 단위이다. 일반적으로 endpoint와 pod 단위가 같고 pod가 이해에 용이하기에 pod로 논한다.
이에 따라, consecutive5xxErrors 등 ejection 결정은 pod 단위로 발생하고, ejection 역시 해당 결정에 따라 해당 pod에서 발생한다. 이는 클라이언트 관점, 즉 서버 전체 관점에서는 consecutive5xxErrors 값을 한참 넘어서야 pod ejection이 발생할 수 있음을 의미하며, 서버 pod 갯수가 많을 수록 그렇다.
maxEjectionPercent, minHealthPercent 는 오탐으로 인한 장애 방지책
한편, maxEjectionPercent, minHealthPercent 는 service 단위이다. 각기 장애 방지를 위한 ejection pod 상한과 healthy pod 하한으로, 양자 모두 설정 중 충돌이 있을 경우 minHealthPercent 이 적용된다. 참고로 k8s에서 minHealthPercent 의 default 값이 0으로 이는 해당 기능이 꺼져있어 pod 갯수가 적을 경우를 대비한다고 공식 문서에서 언급한다. 특별한 일이 없는 이상, 혼선 방지를 위해 maxEjectionPercent 단독 사용을 추천한다.
 오탐(false positive; 정상 pod의 ejection)으로 인한 연쇄적 장애 가능 / 대응안
Ejection 판단은 통계적 heuristic 기반이기에 본질적으로 오탐 가능성이 존재하여, 이는 interval, consecutive5xxErrors 이 작을 수록 커지고, baseEjectionTime, maxEjectionPercent이 클수록 오탐 여파가 커진다.
문제는 이로 인해서도 (서비스 내) 연쇄적 장애가 발생 가능하다는 점이다. 이 경우 연쇄적 장애 방지가 아니라 악화가 되어버린다.
오탐 대응안
Ejection을 고려한 충분한 가용 자원량 설정: 하지만 자원 낭비로 이어질 수 있다. 따라서 소규모 서비스일 경우에 적용해볼만하다(아님 돈이 많거나).
Rate Limiting 설정: 설정 값을 찾는게 쉽지 않다는 단점이 있다. 하지만 설정하면 규모에 상관 없이 든든하다.
HPA등의 autoscaling: autoscaling의 자체 지연 시간이 장애 전파 시간보다 짧아야 한다는 풀기 어려운 제약이 있다.

설정 방법, 추천 설정

추천 설정: 빠른 감지와 빠른 재투입 전략으로 ejection 효과 최대한, 오탐 부작용 최소화 전략이다. 아래는 예로 역시나 서비스마다 달라질 것이기에 서비스 별 적정 값을 찾는 것이 중요하다. 아래 AI 추천의 소규모 클러스터 경우와 유사하다.
# 오탐 대응안 마련 수준이 낮을 경우 http: outlierDetection: consecutive5xxErrors: 5 # default 값. 작을 수록 민감하지만 그만큼 오탐 위험도 커진다. 서비스 별 특성에 따라 적절한 값 찾는 것이 중요. interval: 5s # default는 10s. 빠른 감지를 위해 줄였다. baseEjectionTime: 10s # default는 30s. 오탐 여파 최소화 목적으로 줄였다. 오탐이 아니면 ejection 효과는 자연히 늘어난다(ejection time = baseEjectionTime * 연속 ejection 횟수) maxEjectionPercent: 20 # default는 10. ejection 효과 증대를 위해 높혔다. 소규모에서는 너무 작으면 아예 ejection이 안되기에 높혀야 한다. minHealthPercent: 0 # default 값(기능 off). maxEjectionPercent만 사용함으로 혼선 제거 # 오탐 대응안 마련 수준이 높을 경우 http: outlierDetection: ... # 나머지는 동일 maxEjectionPercent: 50 # 100을 설정할 수도 있겠으나 오탐 시 서비스 전면 중단 위험으로 인해 줄였다. 실제 전체 장애 상황이라면 CB에 의해서가 아니더라도 ejection은 발생한다(via readiness probe).
YAML
복사
대규모 클러스터(대량의 pod 운용)에는?
AI는 아래와 같이 클러스터 규모에 따라 다른 기준을 제시하여, 작을 때는 빠른 감지와 빠른 재투입을, 클 때는 안정성과 오탐 방지에 초점을 두는 것을 추천한다.
대규모 클러스터에 빠른 감지/재투입 전략이 적절하지 않은 근본적 이유는, 대량의 빠른 감지/재투입 pods로 인한 Istio-proxy와 Istiod 간의 XDS(xDS API) 대규모 통신량, 운영 피로도 때문이다. 이는 부수적 장애 위험을 높인다.
Istiod 과부하: 설정 동기화 지연, 신규 pod 온보딩 지연 유발 가능
Istio-proxy의 대규모 endpoint 갱신(기존 연결 갱신 연산 포함): node level의 총 자원 소모 증가 → 노드 자원 고갈 유발, 타 pod로의 악영향 가능
운영 피로도 증가: 모니터링 및 디버깅의 복잡성 증대
소규모(pod 2~5개)
대규모(pod 10개 이상)
인스턴스 수와의 관계
maxEjectionPercent
50~100% (빠른 제외)
10~20% (오탐 방지)
적을 수록 높게, 많을 수록 낮게
interval
1~3초(빠른 감지)
5~10초 (안정성)
적을 수록 짧게, 많을 수록 길게
baseEjectionTime
15~60초(빠른 재투입)
120~300초 (안정성)
적을 수록 짧게, 많을 수록 길게
consecutive5xxErrors
1~3 (민감한 감지)
3~5 (오탐 방지)
적을 수록 낮게, 많을 수록 높게

Rate Limiting

Rate Limiting 은 load shedding의 주요 기법 중 하나이자 사실 상 서버 관점의 안정성 유지를 위한 최후의 보루로, 트래픽 부하가 서버 한계치에 도달 시 429 응답을 즉시 반환함으로 장애를 방지한다.
Local rate limiting vs Global rate limiting 한계치 측정 및 오류 반환 결정 위치가 부하 중인 workload와 동일할 경우 Local Rate Limiting, 다를 경우 Global Rate Limiting이라 하며, 후자는 과금을 위한 수단으로도 사용된다.

대응 문제

서버 자원 고갈 ( 주요 대응): 서버 자원 고갈 문제의 근본적 대응안. 과부하를 근본적으로 차단한다.
연쇄적 장애 ( 부분적 대응): 서버 자원 고갈은 연쇄적 장애 주요 원인 중 하나. 다만 자체 해결안이 아닌 서버 의존적(서버에 rate limiting이 있을 때 유효)으로 한계가 있다.
클라이언트 자원 고갈 ( 부분적 대응): 빠른 실패(429 Too Many Requests) 응답으로 클라이언트 자원 소모를 간접적으로 줄인다.

유의 사항, 설정 방법

Rate Limiter 적용 자체는 쉽지만 정작 어려움은 자원 대비 한계 처리량 산출이 쉽지 않다는 데 있다. 해당 값 산출은 다른 어떤 해결안보다 workload 사용 패턴에 맞춘 부하 테스트를 요구하기 때문이다. 하지만 서버 과부하 대응의 대표적 exercise란 점을 고려하면 그만한 가치가 있는 활동임에 틀림 없다(Google SRE book 역시 다양한 exercise 중 이를 첫 번째로 꼽는다: This is the most important exercise you should conduct in order to prevent server overload).
 Autoscaling threshold 보다 큰 자원 사용량에서 rate limiting이 발생하도록: 같거나 낮을 경우 autoscaling이 발생하지 않을 수 있기 때문이다. Autoscaling threshold는 자원 사용 평균치를 기준으로, Rate Limiting은 자원 사용 한계치를 기준으로 삼는 것을 추천한다.
설정 방법
개요: Istio에서는 token bucket 알고리즘 기반의 HTTP local rate limit Envoy filter를 EnvoyFilter 를 통해 적용 가능하다.
참조 가이드 문서
권혁 블로그 (path 별로 처리하는 로직에 도움을 받았다).
적용 방법: 하기 적용 코드 중 comment 부분 만을 적절히 수정하여 적용한다. 아래는 comment에 있는 설정 fields에 대한 설명으로, path, method 별로 rate limiting 설정이 가능하고 default 설정 역시 가능하다.
Path, method 매칭: header_value_match 부분에서 path, method 설정의 모습을 볼 수 있다. 해당 match에 대한 token bucket 설정은 하단에서 각 descriptor_value 값에 해당하는 섹션에서 별도 설정하는 구조이다.
token buckets 설정
max_tokens : 토큰 버킷이 최대 저장할 수 있는 토큰 수. 즉, 버킷의 용량.
fill_interval: 버킷에 토큰이 추가되는 주기를 나타낸다.
tokens_per_fill: fill_interval 마다 버킷에 추가되는 토큰 수. 즉, 리필되는 토큰량. max_tokens 보다 크면 max_tokens 갯수 외 나머지는 무시된다.
기타
response_headers_to_add: limiting 발생 시 x-local-rate-limit: true 를 429 response header를 추가한다.
적용 코드
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: local-ratelimit-dockebi-v1-frontend # (권장) local-ratelimit-{workload 이름} namespace: service # 적용 대상 workload의 namespace spec: workloadSelector: labels: # 적용 대상 pod 식별 가능한 label app: dockebi version: v1 mode: frontend configPatches: - applyTo: HTTP_FILTER match: context: SIDECAR_INBOUND listener: filterChain: filter: name: "envoy.filters.network.http_connection_manager" patch: operation: INSERT_BEFORE value: name: envoy.filters.http.local_ratelimit typed_config: "@type": type.googleapis.com/udpa.type.v1.TypedStruct type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit value: stat_prefix: dummy # `dummy` token이 들어간 metric은 사용하지 않음 - applyTo: HTTP_ROUTE match: context: SIDECAR_INBOUND routeConfiguration: vhost: name: "inbound|http|8080" route: action: ANY patch: operation: MERGE value: route: rate_limits: - actions: - header_value_match: # exact match 사용 경우 예. method도 포함 descriptor_value: "stuff_exact" # 아래 token 값 설정 descriptor 섹션에 대한 label. expect_match: true headers: - name: :path string_match: exact: "/v1/stuff" - name: :method string_match: exact: "GET" - actions: - header_value_match: # prefix match 사용 경우 descriptor_value: "stuff_prefix" expect_match: true headers: - name: :path string_match: prefix: "/v1/stuff/" - actions: - header_value_match: # regex match 사용 경우 descriptor_value: "error" expect_match: true headers: - name: :path string_match: safe_regex: google_re2: {} regex: "/v1/error/.*(\\?.*)?" typed_per_filter_config: envoy.filters.http.local_ratelimit: "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit stat_prefix: actual # `actual` token이 들어간 rate limit metric을 사용 token_bucket: # 별도 path, method 설정이 없는 경우에 대한 rate limiting default 설정 max_tokens: 1024 # 초기 burst 허용치 tokens_per_fill: 1024 # token이 버킷에 채워진 직후의 token 갯수 fill_interval: 10s # token이 버킷에 채워지는 주기 filter_enabled: runtime_key: test_enabled default_value: numerator: 100 denominator: HUNDRED filter_enforced: runtime_key: test_enabled default_value: numerator: 100 denominator: HUNDRED response_headers_to_add: # rate limiting 발생 시 x-local-rate-limit: true header 추가 여부. 불필요하면 본 block 제거 - append: false header: key: x-local-rate-limit value: "true" descriptors: - entries: # 상기 stuff_exact 설정에 대한 rate limiting 설정 - key: header_match value: stuff_exact token_bucket: max_tokens: 5 tokens_per_fill: 5 fill_interval: 10s - entries: # 상기 stuff_prefix 설정에 대한 rate limiting 설정 - key: header_match value: stuff_prefix token_bucket: max_tokens: 3 tokens_per_fill: 3 fill_interval: 10s - entries: # 상기 error 설정에 대한 rate limiting 설정 - key: header_match value: error token_bucket: max_tokens: 2 tokens_per_fill: 2 fill_interval: 10s
YAML
복사
 Istio resiliency 기반 장애 대응: 모니터링 으로 이어집니다.