이스티오 컨트롤 플레인은 쿠버네티스 이벤트를 실시간 감지해 서비스 메시 설정을 업데이트한다. 설정 동기화가 지연되면 유령 워크로드가 발생해 삭제된 엔드포인트로 트래픽이 라우팅된다. 이로 인해 요청 실패와 시스템 불안정성이 생기지만, 재시도와 이상값 감지로 단기 지연은 완화된다.

이스티오 데이터 플레인은 궁극적 일관성 설계로 인해 설정 동기화에 잠깐의 지연이 발생할 수 있다.
비정상 워크로드 이벤트 발생 시 컨트롤 플레인(istiod)이 새로운 설정을 생성하지만, 업데이트 지연으로 인해 프록시가 낡은 설정을 유지하면 유령 엔드포인트로 트래픽이 라우팅된다.
이스티오는 **재시도(기본 2회)**와 이상값 감지(실패 엔드포인트 자동 제거) 메커니즘을 통해 단기 지연을 완화하지만, 장기간 지연 시 사용자 영향이 불가피하다.

이스티오 컨트롤 플레인(istiod)은 들어오는 이벤트를 처리할 때 디바운싱스로틀링 기법을 활용한다.
첫째, 이벤트가 발생하면 DiscoveryServer가 이를 수신하고 일정 시간(PILOT_DEBOUNCE_AFTER) 동안 후속 이벤트를 모아 병합한다. 이 과정에서 중복 이벤트는 제거되고, 설정 업데이트 빈도가 줄어든다.
둘째, 디바운싱이 완료되면 병합된 이벤트를 푸시 대기열에 추가한다. 이때 스로틀링(PILOT_PUSH_THROTTLE)으로 동시 처리 가능한 푸시 요청 수를 제한해 CPU 자원 낭비를 방지한다.
셋째, 처리된 이벤트는 엔보이 설정으로 변환되어 워크로드에 전파된다. 디바운싱 시간 단축과 스로틀링 조정을 통해 컨트롤 플레인의 성능을 최적화할 수 있다.

 

 

# 실습 환경 준비
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f ch11/catalog-virtualservice.yaml
kubectl -n istioinaction apply -f ch11/catalog-gateway.yaml

# 확인
kubectl get deploy,gw,vs -n istioinaction

# 반복 설정 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done


# 컨트롤 플레인 메트릭 확인
kubectl exec -it -n istio-system deploy/istiod -- curl localhost:15014/metrics
# HELP citadel_server_csr_count The number of CSRs received by Citadel server.
# TYPE citadel_server_csr_count counter
citadel_server_csr_count 3
...

이스티오 컨트롤 플레인에서 데이터 플레인 업데이트 지연 시간은 pilot_proxy_convergence_time 메트릭으로 측정되며, 이는 이벤트 발생부터 엔보이 프록시에 설정이 동기화되기까지의 전체 시간을 나타낸다.
pilot_proxy_queue_time은 이벤트가 처리 대기열에서 대기하는 시간을, pilot_xds_push_time은 xDS 푸시 작업 자체에 소요되는 시간을 각각 측정해 지연 원인을 세부적으로 진단할 수 있다.
이 메트릭들은 컨트롤 플레인의 동기화 효율성을 모니터링하고, 설정 전파 지연으로 인한 유령 워크로드 문제를 예방하는 데 활용된다.

pilot_proxy_convergence_time 은 프록시 푸시 요청이 대기열에 안착한 순간 부터 워크로드에 배포 되기까지 전체 과정의 지속 시간을 측정한다.

 

- **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))

 

이스티오 컨트롤 플레인(istiod)의 포화도는 주로 CPU 사용률로 측정되며, 90% 이상 도달 시 설정 업데이트 지연으로 인해 데이터 플레인 동기화가 느려져 유령 워크로드 문제가 발생할 수 있다.

핵심 메트릭인 container_cpu_usage_seconds_total(쿠버네티스 컨테이너 CPU)와 process_cpu_seconds_total(istiod 프로세스 CPU)로 모니터링하며, 포화 상태 시 이벤트 처리 최적화(디바운싱/스로틀링) 또는 리소스 스케일업이 필요하다.

 

 

이스티오 컨트롤 플레인의 트래픽 부하수신 트래픽(설정 변경 이벤트)과 송신 트래픽(데이터 플레인 푸시)으로 구성된다.
주요 메트릭인 pilot_xds_pushes는 xDS 푸시 빈도를, pilot_inbound_updates는 초당 설정 업데이트 수를 측정해 부하 정도를 파악한다.
이 메트릭들을 모니터링해 컨트롤 플레인 병목 현상을 식별하고, 디바운싱/스로틀링 설정을 조정해 성능을 최적화한다.

kubectl top pod -n istio-system -l app=istiod --containers=true
POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   3m           62Mi            

kubectl top pod -n istioinaction --containers=true
POD                      NAME          CPU(cores)   MEMORY(bytes)   
catalog-6cf4b97d-5jtzt   catalog       0m           20Mi            
catalog-6cf4b97d-5jtzt   istio-proxy   6m           46Mi

#
kubectl resource-capacity -n istioinaction -c -u -a
kubectl resource-capacity -n istioinaction -c -u   
NODE                  POD                      CONTAINER     CPU REQUESTS   CPU LIMITS    CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL                                                                                                                                       
myk8s-control-plane   *                        *             10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     67Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   *             10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     67Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   catalog       0m (0%)        0m (0%)       0m (0%)    0Mi (0%)          0Mi (0%)        21Mi (0%)
myk8s-control-plane   catalog-6cf4b97d-5jtzt   istio-proxy   10m (0%)       2000m (25%)   7m (0%)    40Mi (0%)         1024Mi (8%)     47Mi (0%)

#
kubectl get pod -n istio-system -l istio.io/rev=default
kubectl resource-capacity -n istio-system -c -u
kubectl resource-capacity -n istio-system -c -u -a -l istio.io/rev=default
kubectl resource-capacity -n istio-system -c -u -l istio.io/rev=default
NODE                  POD                                     CONTAINER     CPU REQUESTS   CPU LIMITS    CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL                                                                                                                                                      
myk8s-control-plane   *                                       *             30m (0%)       4000m (50%)   27m (0%)   180Mi (1%)        2048Mi (17%)    164Mi (1%)
myk8s-control-plane   istio-egressgateway-85df6b84b7-m4699    *             10m (0%)       2000m (25%)   9m (0%)    40Mi (0%)         1024Mi (8%)     49Mi (0%)
myk8s-control-plane   istio-egressgateway-85df6b84b7-m4699    istio-proxy   10m (0%)       2000m (25%)   9m (0%)    40Mi (0%)         1024Mi (8%)     49Mi (0%)
myk8s-control-plane   istio-ingressgateway-6bb8fb6549-k4ln6   *             10m (0%)       2000m (25%)   11m (0%)   40Mi (0%)         1024Mi (8%)     50Mi (0%)
myk8s-control-plane   istio-ingressgateway-6bb8fb6549-k4ln6   istio-proxy   10m (0%)       2000m (25%)   11m (0%)   40Mi (0%)         1024Mi (8%)     50Mi (0%)
myk8s-control-plane   istiod-8d74787f-cqhs2                   *             10m (0%)       0m (0%)       8m (0%)    100Mi (0%)        0Mi (0%)        66Mi (0%)
myk8s-control-plane   istiod-8d74787f-cqhs2                   discovery     10m (0%)       0m (0%)       8m (0%)    100Mi (0%)        0Mi (0%)        66Mi (0%)

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 Pushes: 클러스터, 엔드포인트, 리스너, 라우트별로 초당 설정 푸시 횟수를 보여준다.
  • Pilot Errors: 이스티오 컨트롤 플레인에서 발생한 에러 수를 나타낸다.
  • Proxy Push Time: 프록시로 설정을 푸시하는 데 걸린 시간을 p50, p90, p99, p99.9 백분위수로 보여준다.
  • Conflicts: 설정 충돌 발생 건수를 나타낸다.
  • ADS Monitoring: VirtualService, Service, Connected Endpoint 등 ADS(Envoy와의 연결) 상태를 모니터링한다.
  • Envoy Details: Envoy 관련 세부 이벤트나 동작 횟수를 초당 단위로 보여준다.
  • XDS Active Connections: 활성화된 XDS(Envoy와의 설정 동기화) 연결 수를 나타낸다.
  • XDS Requests Size: XDS 요청/응답의 최대 및 평균 바이트 크기를 시간별로 보여준다.

메트픽 설명

pilot_total_xds_rejects 설정 푸시 거부 횟수
pilot_xds_’cds/lds/rds/cds’_reject pilot_total_xds_rejects 메트릭의 부분집합. 어느 API 푸시가 거부됐는지 수사망을 좁히는 데 유용함
pilot_xds_write_timeout push를 시작할 때 발생한 오류와 타임아웃의 합계
pilot_xds_push_context_errors 엔보이 설정을 생성하는 동안 발생한 이스티오 파일럿 오류 횟수. 주로 이스티오 파일럿의 버그와 관련

 

 

이제 본격적으로 성능 튜닝을 진행해보자.

이스티오 컨트롤 플레인 성능은 클러스터 변경 빈도, 리소스 할당량, 워크로드 수, 설정 크기에 따라 좌우된다.
성능 최적화를 위해 이벤트 배치 처리(디바운싱), Istiod 수평/수직 확장, Sidecar 리소스로 설정 범위 제한 등의 전략을 사용한다.
핵심 메트릭(pilot_proxy_convergence_time, container_cpu_usage_seconds_total)을 모니터링해 병목 현상을 식별하고 조치해야 한다

 

# 실습 환경 준비 : 11.2.1 에서 이미 설정함
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f ch11/catalog-virtualservice.yaml
kubectl -n istioinaction apply -f ch11/catalog-gateway.yaml
kubectl get deploy,gw,vs -n istioinaction

# 반복 설정 해두기
while true; do curl -s http://catalog.istioinaction.io:30000/items ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done

# 모니터링
while true; do kubectl top pod -n istio-system -l app=istiod --containers=true ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   7m           65Mi            
2025-05-11 15:04:34

POD                     NAME        CPU(cores)   MEMORY(bytes)   
istiod-8d74787f-cqhs2   discovery   27m          82Mi            
2025-05-11 15:04:36
...


# 더미 워크로드 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:
      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


# 확인
kubectl get deploy,svc,pod -n istioinaction
...

docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
catalog-6cf4b97d-5jtzt.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
istio-egressgateway-85df6b84b7-m4699.istio-system      Kubernetes     SYNCED     SYNCED     SYNCED     NOT SENT     NOT SENT     istiod-8d74787f-cqhs2     1.17.8
istio-ingressgateway-6bb8fb6549-k4ln6.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
sleep-6f8cfb8c8f-2nfrm.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-cqhs2     1.17.8
...

#
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --fqdn sleep.istioinaction.svc.cluster.local
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
10.10.0.16:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.17:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.18:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
10.10.0.19:80                                           HEALTHY     OK                outbound|80||sleep.istioinaction.svc.cluster.local
...

 

 

 

쪼끔 올라가긴 한다.

## 부하를 더 심하게 줘보자. 200개씩 생성하기

#
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
...

#
docker exec -it myk8s-control-plane istioctl proxy-status
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction

202개씩 생성..

 

이제 테스트는 서비스를 반복적으로 만들어 부하를 생성하고, 프록시에 설정을 업데이트하는 데 걸리는 지연 시간P99 값푸시 개수를 측정한다.

 

#!/bin/bash

main(){
  ## Pass input args for initialization
  init_args "$@"

  SLEEP_POD=$(kubectl -n istioinaction get pod -l app=sleep -o jsonpath={.items..metadata.name} -n istioinaction | cut -d ' ' -f 1)

  PRE_PUSHES=$(kubectl exec -n istio-system deploy/istiod -- curl -s localhost:15014/metrics | grep pilot_xds_pushes | awk '{total += $2} END {print total}') 

  if [[ -z "$PRE_PUSHES" ]]; then
    echo "Failed to query Pilot Pushes from prometheus."
    echo "Have you installed prometheus as shown in chapter 7?"
    exit 1
  fi

  echo "Pre Pushes: $PRE_PUSHES"

  INDEX="0"
  while [[ $INDEX -lt $REPS ]]; do
    SERVICE_NAME="service-`openssl rand -hex 2`-$INDEX" 

    create_random_resource $SERVICE_NAME &
    sleep $DELAY
    INDEX=$[$INDEX+1]
  done

  ## Wait until the last item is distributed
  while [[ "$(curl --max-time .5 -s -o /dev/null -H "Host: $SERVICE_NAME.istioinaction.io" -w ''%{http_code}'' $GATEWAY:30000/items)" != "200" ]]; do 
    # curl --max-time .5 -s -o /dev/null -H "Host: $SERVICE_NAME.istioinaction.io" $GATEWAY/items
    sleep .2
  done

  echo ==============

  sleep 10

  POST_PUSHES=$(kubectl exec -n istio-system deploy/istiod -- curl -s localhost:15014/metrics | grep pilot_xds_pushes | awk '{total += $2} END {print total}')

  echo
  
  LATENCY=$(kubectl -n istioinaction exec -it $SLEEP_POD -c sleep -- curl "$PROM_URL/api/v1/query" --data-urlencode "query=histogram_quantile(0.99, sum(rate(pilot_proxy_convergence_time_bucket[1m])) by (le))" | jq  '.. |."value"? | select(. != null) | .[1]' -r)

  echo "Push count:" `expr $POST_PUSHES - $PRE_PUSHES`
  echo "Latency in the last minute: `printf "%.2f\n" $LATENCY` seconds" 
}

create_random_resource() {
  SERVICE_NAME=$1
  cat <<EOF | kubectl apply -f -
---
kind: Gateway
apiVersion: networking.istio.io/v1alpha3
metadata:
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  servers:
    - hosts:
        - "$SERVICE_NAME.istioinaction.io"
      port:
        name: http
        number: 80
        protocol: HTTP
  selector:
    istio: ingressgateway
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: catalog
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: catalog
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata: 
  name: $SERVICE_NAME
  namespace: $NAMESPACE
spec:
  hosts:
  - "$SERVICE_NAME.istioinaction.io"
  gateways:
  - "$SERVICE_NAME"
  http:
  - route:
    - destination:
        host: $SERVICE_NAME.istioinaction.svc.cluster.local
        port:
          number: 80
---
EOF
}

help() {
    cat <<EOF
Poor Man's Performance Test creates Services, Gateways and VirtualServices and measures Latency and Push Count needed to distribute the updates to the data plane.
       --reps         The number of services that will be created. E.g. --reps 20 creates services [0..19]. Default '20'
       --delay        The time to wait prior to proceeding with another repetition. Default '0'
       --gateway      URL of the ingress gateway. Defaults to 'localhost'
       --namespace    Namespace in which to create the resources. Default 'istioinaction'
       --prom-url     Prometheus URL to query metrics. Defaults to 'prom-kube-prometheus-stack-prometheus.prometheus:9090'
EOF
    exit 1
}

init_args() {
  while [[ $# -gt 0 ]]; do
      case ${1} in
          --reps)
              REPS="$2"
              shift
              ;;
          --delay)
              DELAY="$2"
              shift
              ;;
          --gateway)
              GATEWAY="$2"
              shift
              ;;
          --namespace)
              NAMESPACE="$2"
              shift
              ;;
          --prom-url)
              PROM_URL="$2"
              shift
              ;;
          *)
              help
              ;;
      esac
      shift
  done

  [ -z "${REPS}" ] &&  REPS="20"
  [ -z "${DELAY}" ] &&  DELAY=0
  [ -z "${GATEWAY}" ] &&  GATEWAY=localhost
  [ -z "${NAMESPACE}" ] &&  NAMESPACE=istioinaction
  [ -z "${PROM_URL}" ] &&  PROM_URL="prom-kube-prometheus-stack-prometheus.prometheus.svc.cluster.local:9090"
}

main "$@"
# (참고) 호출
curl -H "Host: catalog.istioinaction.io" localhost:30000/items

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

# :30000 포트 정보 추가해둘것!
cat bin/performance-test.sh
...
Poor Man's Performance Test creates Services, Gateways and VirtualServices and measures Latency and Push Count needed to distribute the updates to the data plane.
       --reps         The number of services that will be created. E.g. --reps 20 creates services [0..19]. Default '20'
       --delay        The time to wait prior to proceeding with another repetition. Default '0'
       --gateway      URL of the ingress gateway. Defaults to 'localhost'
       --namespace    Namespace in which to create the resources. Default 'istioinaction'
       --prom-url     Prometheus URL to query metrics. Defaults to 'prom-kube-prometheus-stack-prometheus.prometheus:9090'
...

# 성능 테스트 스크립트 실행!
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
Pre Pushes: 335
...
ateway.networking.istio.io/service-00a9-9 created
service/service-00a9-9 created
virtualservice.networking.istio.io/service-00a9-9 created
==============

Push count: 510 # 변경 사항을 적용하기 위한 푸시 함수
Latency in the last minute: 0.45 seconds # 마지막 1분 동안의 지연 시간


# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

부하가 발생중이다.

 

 

#딜레이없이 실행

# 성능 테스트 스크립트 실행 : 딜레이 없이
./bin/performance-test.sh --reps 10 --prom-url prometheus.istio-system.svc.cluster.local:9090
Push count: 51
Latency in the last minute: 0.47 seconds

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

 

사이드카를 사용해 푸시 횟수 및 설정 크기 줄이기 REDUCING CONFIGURATION SIZE AND NUMBER OF PUSHES USING SIDECARS

 

이스티오는 기본적으로 모든 서비스 프록시에 메시 내 모든 워크로드 정보를 제공해, 불필요하게 설정 크기가 커지는 문제가 발생한다.

#
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

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction

#
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | wc -l
     275

  • 지금 설정 크키가 대략 2MB 인데, 이는 엄청 많은 것이다!
  • 워크로드가 200개인 중간 클러스터만 돼도 엔보이 설정이 400MB로 늘어나며, 이로 인해 연산 성능, 네트워크 대역폭, 메모리가 더 많이 필요하다.
  • 이 설정이 모든 사이드카 프록시에 저장되기 때문이다.

이스티오 Sidecar 리소스는 특정 워크로드의 트래픽 제어를 세밀하게 설정한다.
workloadSelector로 대상 워크로드를 지정하고, egress 필드로 허용할 외부 서비스를 명시적으로 정의한다.
outboundTrafficPolicy를 REGISTRY_ONLY로 설정하면 등록된 서비스만 접근 가능해 보안이 강화된다.
예시 YAML에서는 app: foo 워크로드가 bar.istioinaction과 istio-system 서비스만 접근하도록 제한한다.
이를 통해 불필요한 설정 전파를 막아 CPU/메모리 사용량과 네트워크 부하를 줄인다.

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

 

 

메시 범위 사이드카 설정으로 더 나은 기본값 정의하기 DEFINING BETTER DEFAULTS WITH A MESH-WIDE SIDECAR CONFIGURATION

 

**Sidecar 리소스**를 사용해 트래픽 송신을 istio-system 및 prometheus 네임스페이스의 서비스로만 제한한다.
이를 통해 프록시에 전달되는 엔보이 설정 크기가 현저히 줄어들어 컨트롤 플레인 부하가 감소한다.
설정 최소화로 CPU/메모리 사용량네트워크 대역폭이 절약되며, 서비스 간 명시적 의존성 정의가 강제되어 운영 안정성이 향상된다.

# 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 # 모드는 사이드카에 설정한 서비스로만 트래픽 송신을 허용한다
    
    
    
# 테스트를 위해 샘플 nginx 배포
cat << EOF | kubectl apply -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

# catalog 에서 nginx 서비스 접속 확인
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction | grep nginx
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep nginx                                        
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local

kubectl exec -it deploy/catalog -n istioinaction -- curl nginx.default | grep title
<title>Welcome to nginx!</title>


# istio-system, prometheus 네임스페이스만 egress 허용 설정
kubectl -n istio-system apply -f ch11/sidecar-mesh-wide.yaml
kubectl get sidecars -A

# catalog 에서 nginx 서비스 접속 확인
docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction | grep nginx
docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep nginx                                        
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep nginx
kubectl exec -it deploy/catalog -n istioinaction -- curl nginx.default | grep title

# envoy config 크기 다시 확인!
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
520K    /tmp/config_dump

 

사이드카 배포 후...

안나온다.

설정파일이 4분의 1로..

 

즉. 모든 구간에서 최적화가 이뤄졌다.

 

# 성능 테스트 스크립트 실행!
./bin/performance-test.sh --reps 10 --delay 2.5 --prom-url prometheus.istio-system.svc.cluster.local:9090
...
Push count: 88 # 변경 사항을 적용하기 위한 푸시 함수
Latency in the last minute: 0.10 seconds # 마지막 1분 동안의 지연 시간

# 확인
kubectl get svc -n istioinaction --no-headers=true | wc -l
kubectl get gw -n istioinaction --no-headers=true | wc -l
kubectl get vs -n istioinaction --no-headers=true | wc -l

 

 

11.3.3 이벤트 무시하기*: 디스커버리 셀렉터로 디스커버리 범위 줄이기 meshConfig.discoverySelectors

이스티오 컨트롤 플레인은 기본적으로 모든 네임스페이스의 파드/서비스 이벤트를 감시해 대규모 클러스터에서 성능 부담이 커진다.
Istio 1.10부터 discovery selector 기능이 도입되어, 라벨 기반으로 감시할 네임스페이스를 선택적으로 지정할 수 있다.
MeshConfig에 discoverySelectors를 설정해 불필요한 워크로드 이벤트를 필터링하면 컨트롤 플레인 부하를 크게 줄일 수 있다.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  meshConfig:
    discoverySelectors: # 디스커버리 셀렉터 활성화
      - matchLabels:
          istio-discovery: enabled # 사용할 레이블 지정
          
          
#
cat ch11/istio-discovery-selector.yaml

#
docker exec -it myk8s-control-plane cat /istiobook/ch11/istio-discovery-selector.yaml
docker exec -it myk8s-control-plane istioctl install -y -f /istiobook/ch11/istio-discovery-selector.yaml

#
kubectl get istiooperators.install.istio.io -A -o json
...
                "meshConfig": {
                    "accessLogEncoding": "JSON",
                    "accessLogFile": "/dev/stdout",
                    "defaultConfig": {
                        "proxyMetadata": {}
                    },
                    "discoverySelectors": [
                        {
                            "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

# 테스트를 위해 샘플 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

# 확인
kubectl get deploy,svc,pod -n new-ns
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local
10.10.0.27:80                                           HEALTHY     OK                outbound|80||nginx.new-ns.svc.cluster.local

# 설정
kubectl label ns new-ns istio-exclude=true
kubectl get ns --show-labels

# 다시 확인
docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | grep nginx
10.10.0.26:80                                           HEALTHY     OK                outbound|80||nginx.default.svc.cluster.local

설정이 빠졌다.

 

11.3.4 이벤트 배치 처리 및 푸시 스로틀링 속성 Event-batching and push-throttling properties

이스티오 컨트롤 플레인은 이벤트를 일정 시간(PILOT_DEBOUNCE_AFTER) 동안 모아 병합해 디바운싱을 수행한다.
이를 통해 중복 이벤트를 제거하고 단일 설정 업데이트로 변환해 푸시 횟수를 줄인다.
PILOT_PUSH_THROTTLE로 동시 처리 가능한 푸시 요청 수를 제한해 CPU 자원 낭비를 방지한다.

그러나 푸시를 너무 미루면 데이터 플레인 설정이 오래돼 최신 상태가 아니게 될 수 있는데, 상술한 것처럼 이런 상황 역시 원하는 바가 아니다.

 

- 배치 기간과 푸시 스로틀링을 정의하는 환경 변수 ENVIRONMENT VARIABLES THAT DEFINE THE BATCHING PERIOD AND PUSH THROTTLING

이스티오 컨트롤 플레인 성능 최적화를 위해 PILOT_DEBOUNCE_AFTERPILOT_DEBOUNCE_MAX로 이벤트 배치 처리 기간을 조정한다.

  • PILOT_DEBOUNCE_AFTER(기본 100ms): 이벤트 발생 후 푸시 대기열 추가 전 대기 시간. 이 기간 내 새 이벤트 발생 시 병합 후 재대기.
  • PILOT_DEBOUNCE_MAX(기본 10s): 최대 배치 처리 허용 시간. 이 시간 초과 시 즉시 푸시.

PILOT_ENABLE_EDS_DEBOUNCE(기본 true)로 엔드포인트 업데이트도 배치 처리 여부를 결정한다.

PILOT_PUSH_THROTTLE(기본 100)로 동시 처리 가능한 푸시 요청 수를 제어한다.

  • 컨트롤 플레인 포화 시: 배치 기간 늘리고 스로틀 감소
  • 빠른 업데이트 필요 시: 배치 기간 줄이고 스로틀 증가.

 

컨트롤 플레인에 리소스 추가 할당하기 ALLOCATING ADDITIONAL RESOURCES TO THE CONTROL PLANE

이스티오 컨트롤 플레인 성능 향상을 위해 스케일 아웃 또는 스케일 업을 선택한다.
송신 트래픽 병목 시 워크로드 분산을 위해 스케일 아웃으로 istiod 인스턴스를 추가한다.
수신 트래픽 병목 시 리소스 처리 능력 강화를 위해 스케일 업으로 기존 인스턴스의 CPU/메모리를 증설한다.

#
kubectl get pod -n istio-system -l app=istiod
kubectl describe pod -n istio-system -l app=istiod
...
    Requests:
      cpu:      10m
      memory:   100Mi
...

kubectl resource-capacity -n istio-system -u -l app=istiod
NODE                  CPU REQUESTS   CPU LIMITS   CPU UTIL   MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL
myk8s-control-plane   10m (0%)       0m (0%)      8m (0%)    100Mi (0%)        0Mi (0%)        90Mi (0%)


# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# demo 프로파일 컨트롤 플레인 배포 시 적용
istioctl install --set profile=demo \
--set values.pilot.resources.requests.cpu=1000m \
--set values.pilot.resources.requests.memory=1Gi \
--set values.pilot.replicaCount=2 -y

exit
-----------------------------------

#
kubectl get pod -n istio-system -l app=istiod
NAME                      READY   STATUS    RESTARTS   AGE
istiod-5485dd8c48-6ngdc   1/1     Running   0          11s
istiod-5485dd8c48-chjsz   1/1     Running   0          11s

kubectl resource-capacity -n istio-system -u -l app=istiod
NODE                  CPU REQUESTS   CPU LIMITS   CPU UTIL    MEMORY REQUESTS   MEMORY LIMITS   MEMORY UTIL
myk8s-control-plane   2000m (25%)    0m (0%)      119m (1%)   2048Mi (17%)      0Mi (0%)        107Mi (0%)

kubectl describe pod -n istio-system -l app=istiod
...
    Requests:
      cpu:      1
      memory:   1Gi
...

 

 

  • 컨트롤 플레인 성능 최적화의 요점은 다음과 같다.
    • 항상 워크로드에 사이드카 설정을 정의하자. 이것만으로도 대부분의 이점을 얻을 수 있다.
    • 컨트롤 플레인이 포화 상태인데 이미 리소스를 많이 할당한 경우에만 이벤트 배치를 수정하자.
    • 병목이 송신 트래픽일 때 istiod 스케일 아웃하자.
    • 병목이 수신 트래픽일 때 istiod 스케일 업하자.

 

Istiod 디플로이먼트 오토스케일링 Autoscaling istiod deployment

이스티오 컨트롤 플레인(istiod)의 오토스케일링은 **30분 유지되는 gRPC 연결(ADS)**로 인해 효율성이 제한된다.
새로 추가된 istiod 복제본은 기존 연결이 만료될 때까지 트래픽을 받지 못해 HPA가 무의미하게 축소되며, 이로 인해 퍼덕거림(flapping) 현상이 발생한다.
MaxServerConnectionAge를 조정해 연결 수명을 단축하거나 ProxyConfig/DestinationRule을 활용하면 연결 재분산을 유도해 오토스케일링 효율을 개선할 수 있다.

  • 현재로서 오토스케일링을 구성하는 가장 좋은 방법은 점진적인 부하 증가에 맞추는 것이다.
  • 며칠, 몇주, 심지어는 몇 달 단위에 걸쳐서 말이다.
  • 이렇게 하면 성능을 지속적으로 모니터링하고 디폴리어먼트 스케일링 결정을 내려야 하는 인적 자원의 부담을 줄일 수 있다.

 

+ Recent posts