들어가며
Istio는 서비스 메시의 강력한 기능을 제공하지만, 기본 설정만으로는 모든 환경에서 최적의 성능을 보장하지 않는다. 특히 대규모 트래픽, 빠르게 변화하는 마이크로서비스 환경, 복잡한 리소스 구조에서는 컨트롤 플레인과 데이터 플레인 모두에서 성능 병목이 발생할 수 있다.
Istio의 성능을 튜닝한다는 것은 단순히 리소스를 늘리는 것이 아니라, 어떤 설정이 불필요하게 부하를 주고 있는지, 어디서 병목이 생기고 있는지를 정확히 진단하고, 이에 맞는 전략을 적용해야 한다.
Istio 성능 저하의 주요 원인은 다음과 같다:
- 컨트롤 플레인의 설정 생성 및 푸시 처리 병목
- 모든 프록시에 과도하게 전달되는 설정
- 필요 없는 네임스페이스나 워크로드까지 감지하는 디스커버리 범위
- 짧은 간격으로 반복되는 설정 이벤트에 의한 푸시 과부하
- 리소스 부족으로 인한 CPU/메모리 포화
이번 포스팅에서는 Istio의 성능 튜닝 방법에 대해 자세히 다뤄본다.
지표로 보는 Istio 컨트롤 플레인 성능
Istio의 제어 플레인인 istiod는 여러 핵심 메트릭을 통해 상태와 성능을 노출한다. 여기엔 리소스 사용률, 트래픽 부하, 오류 비율 같은 항목들이 포함된다. 이 메트릭들은 단순한 수치 이상의 정보를 제공하며, 장애 발생 전 징후 감지, 문제 위치 파악, 리소스 병목 진단 등에 큰 도움이 된다.
Istio는 공식 문서에서 수십 가지 메트릭을 제공하지만 그걸 전부 다 확인하기에는 무리가 있어, 정말 중요한 핵심 메트릭 위주로 보는 게 더 효율적이다.
특히 SRE(사이트 신뢰성 엔지니어링) 분야에서는 서비스 상태를 파악하는 데 꼭 봐야 할 네 가지 지표를 강조하는데,
이걸 황금 신호(Golden Signals)라고 부른다.
컨트롤 플레인의 4가지 황금신호
- 지연 시간: 데이터플레인을 업데이트하는데 필요한 시간
- 포화도: 컨트롤 플레인이 얼마나(CPU, MEM 리소스) 가득 차 있는가
- 트래픽: 컨트롤 플레인의 부하는 어느 정도인가?
- 오류: 컨트롤 플레인의 실패율은 어떻게 되는가?
이 네 가지 지표는 서비스가 잘 작동하는지 외부 관점에서 살펴보는 데 도움이 되고,
문제가 생겼을 때 SLO(서비스 수준 목표)를 기준으로 원인을 빠르게 파악할 수 있는 단서가 된다.
1. 지연 시간(LATENCY)
컨트롤 플레인에서의 지연 시간은 Envoy 프록시로 설정을 얼마나 빠르게 푸시하느냐로 해석할 수 있다. 레이턴시가 커지면, 설정 변경이 데이터 플레인에 반영되기까지 걸리는 시간이 늘어나고, 이는 전체 시스템의 응답성과 일관성에 영향을 준다.
- pilot_proxy_convergence_time
: 프록시 푸시 요청이 대기열에 안착한 순간부터 워크로드에 배포되기까지 전체 과정의 지속 시간을 측정
- pilot_proxy_queue_time
: 워커가 처리할 때까지 푸시 요청을 대기열(queue)에서 기다린 시간을 측정.
- pilot_xds_push_time
: 엔보이 설정을 워크로드로 푸시하는데 필요한 시간을 측정
해당 메트릭 쿼리로 지연시간 메트릭에 대한 그라파나 대시보드의 패널을 구성할 수 있다.
# Proxy Push Time : PromQL - pilot_proxy_convergence_time_bucket
histogram_quantile(0.5, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))
histogram_quantile(0.9, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))
histogram_quantile(0.99, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))
histogram_quantile(0.999, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))
# Proxy Queue Time : PromQL - pilot_proxy_queue_time
histogram_quantile(0.5, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
histogram_quantile(0.9, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
histogram_quantile(0.99, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
histogram_quantile(0.999, sum(rate(pilot_proxy_queue_time_bucket[1m])) by (le))
# XDS Push Time : PromQL - pilot_xds_push_time_bucket
histogram_quantile(0.5, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
histogram_quantile(0.9, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
histogram_quantile(0.99, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
histogram_quantile(0.999, sum(rate(pilot_xds_push_time_bucket[1m])) by (le))
2. 포화도(SATURATION)
포화도는 istiod가 사용하는 리소스의 정도를 나타낸다. 일반적으로 CPU와 메모리 사용률이 90% 이상이면 포화 상태로 간주한다. 이 상태에서는 설정 전파 속도가 늦어지고, 장애로 이어질 가능성도 높다.
container_cpu_usage_seconds_total
: 컨테이너 단위로 보고되는 CPU 사용량
# Cumulative cpu time consumed by the container in core-seconds
container_cpu_usage_seconds_total
container_cpu_usage_seconds_total{container="discovery"}
container_cpu_usage_seconds_total{container="discovery", pod=~"istiod-.*|istio-pilot-.*"}
sum(irate(container_cpu_usage_seconds_total{container="discovery", pod=~"istiod-.*|istio-pilot-.*"}[1m]))
process_cpu_seconds_total
: 프로세스 단위로 집계된 CPU 사용량
# Total user and system CPU time spent in seconds
process_cpu_seconds_total{app="istiod"}
irate(process_cpu_seconds_total{app="istiod"}[1m])
서비스 배포 시에는 설정 생성 및 전파로 인해 istiod의 리소스 사용률이 일시적으로 급증할 수 있다. 성능 튜닝을 고려하기 전에 불필요한 푸시가 발생하고 있지는 않은지, 리소스 할당이 적정한지 먼저 확인하자.
3. 트래픽(TRAFFIC)
컨트롤 플레인은 설정 변경을 수신하는 트래픽(1)과, 데이터 플레인으로 푸시하는 트래픽(2) 두 가지로 나뉜다. 이 둘을 각각 모니터링하면 병목 원인을 훨씬 명확하게 파악할 수 있다.
1. 수신 트래픽에 대한 메트릭pilot_inbound_updates
- 각 Istiod 인스턴스가 설정 변경 수신 횟수를 보여준다.
pilot_push_triggers
- 푸시를 유발한 전체 이벤트 횟수.
- 푸시 원인은 서비스, 엔드포인트, 설정 중 한다. 여기서 설정이란 Gateway나 VirtualService 같은 이스티오 커스텀 리소스를 말한다.
pilot_service
- 파일럿이 인지하고 있는 서비스 개수를 측정.
- 파일럿이 인지하는 서비스 갯수가 증가할수록, 이벤트 수신할 때, 엔보이 설정을 만들어내는 필요한 처리가 더 많아진다.
- 따라서, 이 수치는 istiod가 수신 트래픽 때문에 받는 부하량이 결졍되는 중요한 역할을 한다.
avg(pilot_virt_services{app="istiod"}) # istio vs 개수: kubectl get vs -A --no-headers=true | wc -l
avg(pilot_services{app="istiod"}) # k8s service 개수: kubectl get svc -A --no-headers=true | wc -l
2.송신 트래픽에 대한 메트릭pilot_xds_pushes
컨트롤 플레인이 수행하는 모든 유형의 xDS 푸시를 측정한다.
sum(irate(pilot_xds_pushes{type="cds"}[1m]))
sum(irate(pilot_xds_pushes{type="eds"}[1m]))
sum(irate(pilot_xds_pushes{type="lds"}[1m]))
sum(irate(pilot_xds_pushes{type="rds"}[1m]))
pilot_xds
워크로드로의 전체 커넥션 개수를 파일럿 인스턴스별로 보여준다(Istiod가 관리하는 워크로드의 개수)
envoy_cluster_upstream_cx_tx_bytes_total
- 네트워크로 전송될 설정 크기를 측정
# rx
max(rate(envoy_cluster_upstream_cx_rx_bytes_total{cluster_name="xds-grpc"}[1m]))
quantile(0.5, rate(envoy_cluster_upstream_cx_rx_bytes_total{cluster_name="xds-grpc"}[1m]))
# tx
max(rate(envoy_cluster_upstream_cx_tx_bytes_total{cluster_name="xds-grpc"}[1m]))
quantile(.5, rate(envoy_cluster_upstream_cx_tx_bytes_total{cluster_name="xds-grpc"}[1m]))
4. 오류(ERRORS)
오류는 istiod가 푸시 실패, 설정 거부, 내부 처리 실패 등을 얼마나 자주 겪는지를 보여준다. 대부분 리소스가 포화된 경우 발생하며, 즉시 확인해야 할 중요한 지표다.
이 오류 메트릭들은 대부분 Grafana 대시보드에 Pilot Errors 영역으로 자동 집계되며, 한눈에 이상 징후를 확인할 수 있다.
이 오류 메트릭들은 대부분 Grafana 대시보드에 Pilot Errors 영역으로 자동 집계되며, 한눈에 이상 징후를 확인할 수 있다
# 각 쿼리 패턴에 Legend 확인
Legend(Rejected CDS Configs) : sum(pilot_xds_cds_reject{app="istiod"}) or (absent(pilot_xds_cds_reject{app="istiod"}) - 1)
Legend(Rejected EDS Configs) : sum(pilot_xds_eds_reject{app="istiod"}) or (absent(pilot_xds_eds_reject{app="istiod"}) - 1)
Legend(Rejected RDS Configs) : sum(pilot_xds_rds_reject{app="istiod"}) or (absent(pilot_xds_rds_reject{app="istiod"}) - 1)
Legend(Rejected LDS Configs) : sum(pilot_xds_lds_reject{app="istiod"}) or (absent(pilot_xds_lds_reject{app="istiod"}) - 1)
Legend(Write Timeouts) : sum(rate(pilot_xds_write_timeout{app="istiod"}[1m]))
Legend(Internal Errors) : sum(rate(pilot_total_xds_internal_errors{app="istiod"}[1m]))
Legend(Config Rejection Rate) : sum(rate(pilot_total_xds_rejects{app="istiod"}[1m]))
Legend(Push Context Errors) : sum(rate(pilot_xds_push_context_errors{app="istiod"}[1m]))
Legend(Push Timeouts) : sum(rate(pilot_xds_write_timeout{app="istiod"}[1m]))
각 오류에 대한 메트릭 쿼리가 레전드로 Legend로 설정이 되어있다.
Grafana에서 Legend는 그래프에 표시되는 라인(또는 시계열)의 이름 라벨로, 즉, 해당 쿼리의 결과가 어떤 의미를 갖는지를 사람이 알아볼 수 있도록 표시하는 텍스트이다.
메트픽 | 설명 |
---|---|
pilot_total_xds_rejects | 설정 푸시 거부 횟수 |
pilotxds’cds/lds/rds/cds’_reject | pilot_total_xds_rejects 메트릭의 부분집합. 어느 API 푸시가 거부됐는지 수사망을 좁히는 데 유용함 |
pilot_xds_write_timeout | push를 시작할 때 발생한 오류와 타임아웃의 합계 |
pilot_xds_push_context_errors | 엔보이 설정을 생성하는 동안 발생한 이스티오 파일럿 오류 횟수. 주로 이스티오 파일럿의 버그와 관련 |
Istio 성능 튜닝하기
Istio의 컨트롤 플레인 성능은 단순히 CPU나 메모리 같은 리소스 스펙만으로 결정되지 않는다. 다음과 같은 요소들이 종합적으로 영향을 준다:
- 클러스터/네임스페이스 구성 변경 빈도
- Istiod에 할당된 리소스
- Istiod가 관리하는 워크로드 수
- 각 워크로드에 전파해야 할 Envoy 설정의 크기
즉, 워크로드 수가 많아지거나 VirtualService, Gateway, Service 같은 리소스가 과도하게 늘어나면, Istiod는 이 설정들을 모두 계산하고 푸시해야 하므로 점점 병목이 생기기 시작한다.
0. 워크스페이스 준비하기 : 실습 환경 준비 - 더미 워크로드와 서비스 생성
컨트롤 플레인의 병목을 직접 실험해 보기 위해 더미 워크로드와 대량의 리소스를 생성해 보자. 아래는 실제 테스트에 사용할 실습용 매니페스트이다.
STEP 1, 더미 워크로드 10개 생성
먼저 sleep이라는 이름의 워크로드를 10개 배포해서 기본적인 부하를 구성한다.
# 더미 워크로드 10개(replicas 10개)
cat ch11/sleep-dummy-workloads.yaml
...
apiVersion: v1
kind: Service
...
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 10
...
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: governmentpaas/curl-ssl
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
...
kubectl -n istioinaction apply -f ch11/sleep-dummy-workloads.yaml
(급격히 증가하는 대시보드 수치.)
STEP 2, 서비스 리소스 600개 추가
이제 상황을 극단적으로 몰아가기 위해 Service, VirtualService, Gateway 각각 200개씩 생성한다. 이로 인해 Istiod는 총 600개의 설정 리소스를 인지하게 된다.
#
cat ch11/resources-600.yaml
cat ch11/resources-600.yaml | wc -l
9200
# 각각 200개
cat ch11/resources-600.yaml | grep 'kind: Service' | wc -l
cat ch11/resources-600.yaml | grep 'kind: Gateway' | wc -l
cat ch11/resources-600.yaml | grep 'kind: VirtualService' | wc -l
200
# 배포 : svc 200개, vs 200개, gw 200개
kubectl -n istioinaction apply -f ch11/resources-600.yaml
# 확인
kubectl get deploy,svc,pod -n istioinaction
...
# k8s service 개수 202개
kubectl get svc -n istioinaction --no-headers=true | wc -l
202
kubectl get gw,vs -n istioinaction
..
이런 설정을 적용하면 Envoy 설정 계산량과 전파 트래픽이 급증하며, 이는 실시간 대시보드에서 즉각적으로 감지된다.
- 그래서 이제 istiod 인스턴스 하나가 인그레스 및 이그레스 게이트웨이를 포함해 워크로드(istio-proxy 동작)를 13개 관리하며, 서비스는 총 600개(svc + vs + gw) 인지하고 있다.
- 서비스 개수는 엔보이 설정을 만드는 데 필요한 처리량을 늘리고, 워크로드로 보내야 하는 설정을 부풀린다.
STEP 3, 최적화 전 성능 측정하기
Istio 컨트롤 플레인의 성능을 최적화하기 전에, 현재 상태에서 어떤 병목이 있는지부터 먼저 확인해야 한다. 특히 xDS 설정이 프록시에 얼마나 빠르게 푸시되는지, 그 과정에서 몇 번이나 Push가 발생하는지, 지연 시간의 P99 값이 얼마나 되는지를 측정하는 것이 핵심이다.
테스트 스크립트: bin/performance-test.sh
이 스크립트는 여러 개의 Service, Gateway, VirtualService를 생성하고, Prometheus를 통해 푸시 성능을 측정해 주는 기능을 제공한다.
cat bin/performance-test.sh
# 옵션 확인
...
--reps # 생성할 서비스 수 (기본값: 20)
--delay # 각 반복 간의 지연 시간 (기본값: 0초)
--namespace # 리소스를 생성할 네임스페이스 (기본값: istioinaction)
--prom-url # Prometheus 메트릭을 수집할 주소
Try 1: 기본 조건: 10회 반복, 2.5초 간격
첫 테스트는 총 10회의 리소스 생성 반복을 실행하되, 반복 사이에 2.5초의 deploy를 설정한다.
Try 2: 딜레이 없이 실행
이번에는 반복 사이에 딜레이 없이, 즉 한 번에 빠르게 리소스를 생성해 본다.
푸시 횟수가 59개로 감소한 것을 확인할 수 있다. 이 결과는 이벤트가 배치 처리(batch) 되어, 컨트롤 플레인이 한꺼번에 여러 리소스를 처리했기 때문에 오히려 더 효율적이었다는 것을 보여준다.
Try 3: 딜레이를 더 늘려서 실행
마지막으로, 반복 사이의 Delay를 5초로 늘려서 테스트한다.
결과는 다시 지연 시간과 푸시 횟수가 증가한다.
왜 이러한 결과가 나오는 걸까?
짧은 시간 안에 설정 변경이 몰리면, Istio는 이를 효율적으로 처리하기 위해, 하나의 인식하고 한 번에 처리하게 된다. 여러 변경 사항을 한 번에 푸시하므로 불필요한 중복 작업이 줄고 효율이 높아진다.
반대로 변경이 일정한 간격으로 천천히 발생하면, 각각의 이벤트가 별도로 푸시되기 쉬워진다.
이 경우 컨트롤 플레인은 매번 설정을 생성하고 푸시해야 하므로 푸시 횟수와 리소스 사용량이 증가하고, 지연 시간도 길어질 수 있다.
이처럼 변경이 얼마나 자주 발생하느냐(변화율)는 Istiod의 성능에 직접적인 영향을 준다.
이벤트가 많다고 해서 항상 나쁜 것은 아니다. 오히려 짧은 시간에 몰려 처리되면, 배치작업으로 인해 훨씬 효율적으로 처리될 수 있다.
이런 성능 차이를 어떻게 최적화할 수 있을지는, 뒤에서 더 자세히 다룰 예정이다.(디바운스 최적화)
1. 사이드카 리소스를 활용해 푸시 횟수와 설정 크기 줄이기
기본적으로 Istio는 각 서비스 프록시가 메시 내 모든 워크로드와 통신할 수 있도록 설정을 생성한다. 워크로드는 일부 서비스에만 트래픽을 주고받기 때문에, 각 서비스 프록시가 모든 워크로드의 설정 정보를 생성한다는 것은 불필요하다.
이런 구조로 인해 프록시에 전파되는 설정이 불필요하게 비대해지고, 이로 인한 CPU, 메모리, 네트워크 대역폭 소모도 함께 증가한다.
실제 catalog 워크로드의 Envoy 설정 크기를 확인해 보면 약 1.8MB에 달한다. 대부분 텍스트 형식임을 감안하면 굉장히 큰 수치다.
CATALOG_POD=$(kubectl -n istioinaction get pod -l app=catalog -o jsonpath={.items..metadata.name} | cut -d ' ' -f 1)
kubectl -n istioinaction exec -ti $CATALOG_POD -c catalog -- curl -s localhost:15000/config_dump > /tmp/config_dump
du -sh /tmp/config_dump
1.8M /tmp/config_dump
워크로드가 200개만 돼도 전체 설정 크기가 수백 MB에 이를 수 있다. 이 설정은 모든 프록시에 배포되기 때문에 컨트롤 플레인의 푸시 부담이 가중되고, 성능 병목으로 이어질 수 있다.
이 문제를 해결하기 위해 사용하는 것이 바로 Sidecar 리소스다. 이 리소스를 사용하면 사이드카 프록시가 어떤 트래픽만 허용할지 명시적으로 정의할 수 있다.
# Sidecar 예시 리소스
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default
namespace: istioinaction
spec:
workloadSelector:
labels:
app: foo
egress:
-hosts:
- "./bar.istioinaction.svc.cluster.local"
- "istio-system/*"
outboundTrafficPolicy:
mode: REGISTRY_ONLY
workloadSelector
- 사이드카 설정을 적용할 워크로드를 제한한다.
ingress
- 애플리케이션에 들어오는 트래픽 처리를 지정한다.
- 생략하면, 이스티오는 파드 정의를 조회해 서비스 프록시를 자동으로 설정한다.
egress
- 사이드카를 거치는 외부 서비스로의 송신 트래픽 처리를 지정한다.
- 생략되면, 설정은 좀 더 일반적인 사이드카에서 egress 설정을 상속한다 (있는 경우).
- 없으면, 다른 모든 서비스에 접근할 수 있도록 설정하는 기본 동작으로 대처한다.
outboundTrafficPolicy
: 송신 트래픽 처리 시 모드 지정.- REGISTRY_ONLY 모드 : 워크로드가 설정한 서비스에만 트래픽을 보낼 수 있게 한다.
- ALLOW_ANY 모드 : 어디로든 트래픽 송신을 허용한다.
이 설정을 통해 컨트롤 플레인은 해당 워크로드가 어떤 서비스에 접근하는지 미리 알고, 해당 설정만 전달하게 된다
메시 전체에 적용하는 기본 사이드카 설정
모든 워크로드에 대해 명시적인 Sidecar 설정을 적용하기 어렵다면, default 사이드카 리소스를 생성하여, 메시 전체에 적용할 수 있다.
# cat ch11/sidecar-mesh-wide.yaml
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default # istio-system 네임스페이스의 사이드카는 메시 전체에 적용된다.
namespace: istio-system # 위 설명 동일.
spec:
egress:
- hosts:
- "istio-system/*" # istio-system 네임스페이스의 워크로드만 트래픽 송신을 할 수 있게 설정한다.
- "prometheus/*" # 프로메테우스 네임스페이스도 트래픽 송신을 할 수 있게 설정한다.
outboundTrafficPolicy:
mode: REGISTRY_ONLY # 모드는 사이드카에 설정한 서비스로만 트래픽 송신을 허용한다
이 설정은 모든 사이드카가 istio-system, prometheus 네임스페이스로만 트래픽을 보낼 수 있도록 제한한다.
즉, 메시 내 모든 프록시가 컨트롤 플레인에만 연결되도록 제한하고, 다른 서비스로의 연결 설정은 모두 제거할 수 있다.
사이드카를 설정 후, catalog 워크로드에서 다시 Envoy 설정 크기를 확인해 보면, 약 516KB로 줄어든 것을 볼 수 있다. 기존 2MB에서 1/4 이하로 감소했다.
kubectl -n istioinaction exec -ti $CATALOG_POD -c catalog -- curl -s localhost:15000/config_dump > /tmp/config_dump
du -sh /tmp/config_dump
# 결과: 516K
뿐만 아니라, 동일한 조건으로 성능 테스트를 다시 실행해 보면 xDS Push 횟수까지 줄어든다.
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
딜레이가 있음에도 불구하고 푸시 횟수가 눈에 띄게 감소한 걸 보면, 사이드카가 설정 범위를 줄임으로써 실제로 컨트롤 플레인이 푸시할 양을 줄였다는 것을 확인할 수 있다
Sidecar 설정은 성능 향상뿐 아니라, 워크로드가 어떤 서비스와 통신할 수 있는지를 코드로 명시하여 관리하다 보니, 불필요한 연결을 줄이고 꼭 필요한 서비스만 명확하게 지정하여 운영 효율성에 있어서도 장점을 가진다.
정리하자면, Sidecar 리소스는 단순한 설정이 아닌 성능과 안정성 모두를 개선할 수 있는 핵심 도구다. 메시 전체의 리소스 소비를 줄이고, 프록시 설정 최적화를 통해 Istio 운영의 효율성을 극대화할 수 있다. 다만, 기존 클러스터에 적용할 때는 서비스 중단 없이 점진적으로 적용할 수 있도록 주의해야 한다.
2. 디스커버리 범위 줄이기 (불필요한 이벤트 무시)
Istio의 컨트롤 플레인은 기본적으로 Kubernetes의 모든 네임스페이스에서 발생하는 파드, 서비스, 엔드포인트 생성 이벤트를 감지한다. 이는 메시 외부에 있는 리소스들까지 모두 포함되기 때문에, 대규모 클러스터에서는 꽤 부담스러운 동작이다. 모든 변경 이벤트에 대해 Envoy 설정을 다시 계산하고 푸시하기 때문에, 실질적으로 컨트롤 플레인의 부하가 크게 증가한다.
IstioOperator에 discoverySelectors 설정하기
Istio 1.10부터 추가된 discoverySelectors 기능을 활용할 수 있다. 이 기능을 이용하면, Istiod가 감시할 네임스페이스를 명시적으로 지정할 수 있다. 디스커버리 범위를 제한하려면, IstioOperator 파일에서 다음처럼 설정한다.
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
namespace: istio-system
spec:
meshConfig:
discoverySelectors: # 디스커버리 셀렉터 활성화
- matchLabels:
istio-discovery: enabled # 사용할 레이블 지정
- 이 설정은 istio-discovery=enabled 레이블이 붙은 네임스페이스만 감시하도록 제한한다.
- 레이블이 없는 네임스페이스의 이벤트는 무시된다.
메시 외부 워크로드가 많고 절대 라우팅 되지 않을 경우, Spark Job처럼 짧은 시간에 자주 생성되고 삭제되는 워크로드가 있을 경우 이런 이벤트들을 무시하게 만들면 성능과 안정성 모두 향상된다.
matchExpressions를 활용하면, 모든 네임스페이스를 감시하되, 해당 레이블이 붙은 네임스페이스만 감시 대상에서 제외할 수 있다.
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
namespace: istio-system
spec:
meshConfig:
discoverySelectors:
# 제외할 네임스페이스 지정하기 (NotIn 연산자)
- matchExpressions:
- key: istio-exclude
operator: NotIn
values:
- "true"
실습을 통해, 디스커버리 범위 줄여보도록 하자.
- 테스트용 네임스페이스 생성
kubectl create ns new-ns
kubectl label namespace new-ns istio-injection=enabled
kubectl get ns --show-labels
...
NAME STATUS AGE LABELS
new-ns Active 0s istio-injection=enabled,kubernetes.io/metadata.name=new-ns
- nginx 배포 및 서비스 생성
cat << EOF | kubectl apply -n new-ns -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
EOF
- 설정 전, Ingress Gateway가 해당 nginx의 엔드포인트를 감지하는지 확인
# 새로 생성한 nginx의 엔드포인트를 감지.
istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.28:80 HEALTHY OK outbound|80||nginx.istioinaction.svc.cluster.local
10.10.0.29:80 HEALTHY OK outbound|80||nginx.new-ns.svc.cluster.local
- new-ns에 istio-exclude=true 레이블 추가 후, 다시 확인
kubectl label ns new-ns istio-exclude=true
namespace/new-ns labeled
# 라벨을 추가한 new-ns의 nginx 엔드포인트가 보이지 않음.
istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.28:80 HEALTHY OK outbound|80||nginx.istioinaction.svc.cluster.loca
이 과정을 통해 Istiod가 감시하는 네임스페이스를 효과적으로 줄일 수 있고, 불필요한 리소스 처리 비용을 줄일 수 있다
정리하자면, discoverySelectors는 Istio 컨트롤 플레인의 감시 범위를 줄여 불필요한 이벤트를 차단하고, 실제로 메시에서 필요한 리소스만 처리하게 만들어주는 최적화 기법이다. 메시 외부 리소스가 많거나 클러스터 규모가 크다면 꼭 적용해 보자.
3. 이벤트 배치 처리와 푸시 스로틀링 설정
Istio에서 프록시 설정은 클러스터 내의 수많은 이벤트에 의해 계속해서 갱신된다. 새로운 서비스가 생성되거나, 파드가 스케일되거나, 장애가 발생해 재시작되는 등 대부분의 상황은 운영자가 직접 통제할 수 없는 이벤트들이다. 컨트롤 플레인은 이러한 변화들을 감지해 프록시 설정을 만들고 워크로드에 푸시하지만, 이 과정을 매번 개별적으로 처리하게 되면 과도한 리소스 소모로 이어진다.
디바운스(Debounce) 기반의 배치 처리
이 문제를 완화하기 위한 핵심 전략이 바로 이벤트를 일정 시간 동안 모아서 한 번에 처리하는 “디바운스(batch)” 메커니즘이다. 디바운스란, 일정 시간 동안 수신된 이벤트들을 하나의 묶음(batch)으로 만들어, 해당 시간 이후에 한 번만 처리하는 방식이다.
- 이벤트 수신: Kubernetes에서 리소스 변경 이벤트 발생
- 디바운스 (Debounce) : 여러 이벤트를 잠시 모아서 하나로 묶는 과정
- 큐에 추가 (Add to queue): 디바운스 된 이벤트가 Push 대기열에 등록됨
- 스로틀링 (Throttle): 컨트롤 플레인이 너무 자주, 너무 많이 푸시하지 않도록 일종의 내부 제한(속도 조절) 로직이 동작
- 푸시 (Push): 최종적으로 Envoy 프록시에 설정 전파
이벤트가 들어올 때마다 바로 푸시하지 않고, 디바운스 기간을 설정해서 짧은 시간 동안 발생한 이벤트들을 하나로 묶는다면 다음과 같은 효과를 얻을 수 있다:
- 푸시 횟수 감소
- Envoy 설정 생성 비용 절감
- 컨트롤 플레인 CPU 사용량, 네트워크 대역폭 사용량 절감
앞서, 성능 측정 테스트 스크립트에서 딜레이 시간에 따라 푸시 횟수가 달라지는 것이, 바로 Istio 디바운스 메커니즘에 의해서다.
디바운스 구성하기 – 환경 변수 설정
Istio는 디바운스와 관련된 환경 변수(istiod)를 몇 가지 제공한다.
- PILOT_DEBOUNCE_AFTER
: 디바운스 대기 시간(기본값: 100ms)
- PILOT_DEBOUCE_MAX
: 최대 대기 시간, 이 값을 넘기면 무조건 푸시됨
실습: 디바운스 시간 극단적으로 늘려보기
실습을 위해 PILOT_DEBOUNCE_AFTER
값을 말도 안 되게 높은 2.5초로 설정해 보자.
(주의: 이는 실무 환경에서는 권장되지 않으며, 테스트 목적임)
istioctl install --set profile=demo --set values.pilot.env.PILOT_DEBOUNCE_AFTER="2500ms" --set values.global.proxy.privileged=true --set meshConfig.accessLogEncoding=JSON -y
exit
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Egress gateways installed
✔ Installation complete Making this installation the default for injection and validation.
Thank you for installing Istio 1.17. Please take a few minutes to tell us about your install/upgrade experience! https://forms.gle/hMHGiwZHPU7UQRWe9
그다음 성능 테스트 스크립트를 실행해 보면:
# 성능 테스트 스크립트 실행!
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
확연히 Push 횟수가 줄어든 것 을 확인할 수 있다. 이는 이벤트들이 한 번에 묶여 배치 처리되었기 때문이다. 그 결과, 컨트롤 플레인은 더 적은 횟수로 설정을 생성하고 푸시하게 된다.
디바운스 설정 시 주의할 점
지연시간에 대한 메트릭은 디바운스의 대기 시간을 포함하지 않기 때문에, 실제 푸시까지 2.5초가 걸렸어도, 메트릭에는 10ms처럼 짧게 나타날 수 있다. 지연시간만 보고, 설정이 빨리 전파되었다고 착각할 수 있으니 주의하자.
또한 이벤트가 잦은 환경에서는 PILOT_DEBOUNCE_MAX
로 최대 지연 시간을 제한하여, 푸시 지연이 무한정 길어지는 것을 방지해야 한다.
설정을 늦게 푸시하면, 푸시 횟수는 줄지만, 반대로 서비스가 오래된 설정으로 동작할 수 있다. 트레이드오프를 고려하여 적절한 설정값을 조절하는 것이 중요하다.
4. 컨트롤 플레인에 리소스 추가 할당하기
Sidecar, DiscoverySelector, 이벤트 배치 설정까지 모두 적용했는데도 컨트롤 플레인에 여전히 병목이 발생한다면, 결국, Istio 컨트롤플레인의 리소스를 추가 할당해야 한다.
컨트롤 플레인 리소스 할당에는 두 가지 방식이 있다:
- 스케일 업 (Scale Up) 은 하나의 istiod 인스턴스에 CPU, 메모리를 더 할당
- 스케일 아웃 (Scale Out) istiod 인스턴스를 여러 개로 늘려서 부하 분산
스케일 업 vs 스케일 아웃 어떤 걸 선택해야 할까?
Istio 컨트롤 플레인이 어디에서 병목이 발생하느냐에 따라 적합한 스케일 방법을 결정해야 한다.
1. 송신 트래픽이 명목일 때 -> 스케일 아웃
워크로드의 수가 많아서, 하나의 Istiod 인스턴스가 너무 많은 프록시 설정을 푸시하고 있다면. 이때는 istiod를 여러 개로 늘려서 워크로드를 분산시키는 게 효과적이다.
# 배포 시 리소스 및 복제 수 지정
istioctl install --set profile=demo \
--set values.pilot.resources.requests.cpu=1000m \
--set values.pilot.resources.requests.memory=1Gi \
# 확인
kubectl get pod -n istio-system -l app=istiod
kubectl describe pod -n istio-system -l app=istiod
...
Requests:
cpu: 1
memory: 1Gi
...
2. 수신 트래픽이 병목일 때 → 스케일 업
컨트롤 플레인이 많은 리소스(VirtualService, Service 등)를 처리하고 설정을 생성하는 데 시간이 오래 걸릴 때는, 각 Istiod 인스턴스에 더 많은 CPU/Memory를 할당해야 한다.
# istiod 레플리카 수 증가
istioctl install --set profile=demo \
--set values.pilot.replicaCount=2 -y
kubectl get pod -n istio-system -l app=istiod
NAME READY STATUS RESTARTS AGE
istiod-5485dd8c48-brlvl 1/1 Running 0 15s
istiod-5485dd8c48-hv9pj 1/1 Running 0 15s
성능 최적화 방법을 요약해 보자.
- Sidecar 리소스를 명확하게 정의
: 이것만으로도 대부분의 불필요한 설정 부하를 줄일 수 있다 - 컨트롤 플레인이 포화 상태라면 이벤트 배치 설정을 조정
: 하지만 너무 빠르게 값을 바꾸기보다는 점진적으로 적용할 것 - 푸시 트래픽이 병목이라면 스케일 아웃(Replica 증가)
: 각 인스턴스가 더 적은 워크로드를 담당하도록 분산 - 리소스 생성/계산이 병목이라면 스케일 업(CPU, 메모리 증가)
: 설정 계산 자체에 더 많은 리소스를 투입
'Kubernetes > Istio' 카테고리의 다른 글
Istio 시리즈 # 11 - EnvoyFilter로 요청 처리 로직 확장하기 (0) | 2025.05.25 |
---|---|
Istio 시리즈 # 10 - Istio로 구현하는 다중 클러스터 서비스 메시 (Multi-Cluster) (0) | 2025.05.25 |
Istio 시리즈 # 8 - Istio 트러블 슈팅 가이드(Istio Troubleshooting) (1) | 2025.05.17 |
Istio 시리즈 # 7 – Istio Security로 살펴보는 마이크로서비스 통신 보안 (1) | 2025.05.11 |
Istio 시리즈 # 6 – 메트릭과 트레이싱으로 살펴보는 Istio Observability (1) | 2025.05.04 |