homelab89 Docs Logs Legacy Files ☰ TOC 🌓
runbookistio 2026-06-07istiograceful-terminationenvoyhaproxyrunbook

Istio Graceful Termination 사내 도입 런북 (홈랩 실험 → 사내 적용 가이드)

ABSTRACT

홈랩 graceful-termination 실험 결과를 프로덕션 IGW 환경에 이식하는 6단계 런북이다. 머릿속에 담을 한 장면: LB는 backend의 살아있음 여부를 오직 health check 응답으로만 판정한다 — 그래서 LB를 직접 명령할 권한이 없어도, hc 사이드카가 preStop 동안 /health_check.html의 200/503 타이밍을 단계적으로 바꾸면 LB의 “이 backend를 DOWN 마킹할지"를 간접 조종할 수 있다. 이 한 줄이 6단계 전부를 푼다. 본 문서는 각 단계가 무엇을 검증·제어하는지 + 어디서 깨지는지에 집중하고, FSM 상세는 HC FSM 정본, grace period 산정은 프로덕션 적용을 참조한다.

대상환경: Istio 1.30 IGW(istio-ingressgateway) + 외부 L4/L7 LB(Citrix NetScaler 또는 HAProxy). 대상독자: rolling update 중 5xx/connection drop을 0으로 만들려는 SRE. 선행개념: hc 사이드카 FSM(OPEN/DRAINING/CLOSING/CLOSED/FAULT), Envoy drain. FSM 상태명은 게이트 비유를 사용한다.

1. 왜 이 런북이 존재하나 — 문제와 전제

평범한 rolling update에서 IGW pod 하나가 종료될 때 무슨 일이 벌어지는지 보자. kubelet이 SIGTERM을 보내고, 거의 동시에 Service의 endpoint에서 빠지지만, 외부 LB는 그 사실을 즉시 모른다. LB는 자기 health check 주기(inter × fall)가 돌아 backend를 DOWN으로 마킹할 때까지 계속 신규 요청을 그 pod로 보낸다. 더 나쁜 건, LB가 DOWN을 마킹하는 바로 그 순간 downStateFlush ENABLED(HAProxy로 치면 on-marked-down shutdown-sessions) 옵션이 켜져 있으면, 아직 처리 중이던 in-flight 요청까지 즉시 RST로 끊어버린다. 클라이언트 입장에서는 502/connection reset이다.

여기서 근본 제약이 하나 더 있다: 서비스 팀에는 LB에 “이 backend를 빼라"고 명령할 API 권한이 없다. LB는 인프라 팀 소유다. 그러면 어떻게 무중단 종료를 만드나?

핵심 관찰은 이것이다 — LB가 backend의 생사를 판정하는 유일한 입력은 health check 응답 코드다. 즉 우리가 health 응답을 쥐고 있으면, LB의 backend pool membership을 간접적으로 제어할 수 있다. 이 레버를 쥐는 주체가 IGW pod 안의 hc 사이드카이고, preStop 동안 응답을 어떤 순서로 바꾸느냐가 graceful termination의 전부다. 이 런북은 그 레버를 사내 LB(Citrix)·사내 Helm chart·사내 Prometheus에 안전하게 배선하는 절차다.

원본은 행동·검증·위험신호 체크리스트 중심이므로, 여기서는 각 단계가 어떤 메커니즘을 검증·제어하는지와 LB 동작 모델(HAProxy ↔ Citrix 대응)을 보충한다.

2. 핵심 메커니즘 — health 응답 순서가 곧 LB 제어 레버

앵커: “먼저 다 흘려보내고, 그 다음에 503”

런북에서 알아야 할 인과는 단 하나다 — LB의 backend 판정은 health check 응답에만 의존한다. 그래서 hc 사이드카는 preStop 동안 /health_check.html 응답을 단계적으로 바꿔 LB를 간접 제어한다. 정정된 시퀀스의 핵심은 직관과 정반대다:

“503을 먼저 띄우지 말고, in-flight를 다 흘려보낸 뒤(active=0)에야 503을 띄운다.”

왜? 503을 먼저 띄우면 downStateFlush ENABLED LB가 backend를 DOWN 마킹하면서 in-flight를 즉시 RST하기 때문이다. 순서를 뒤집는 순간 graceful이 깨진다. 그래서 graceful의 진짜 안전 구간은 health 200을 유지하는 DRAINING이고, 503 flip은 보호할 게 없어진 뒤에만 일어난다.

3단계 응답표 — 각 단계가 답하는 질문

단계 /health_check.html hc가 하는 일 LB 거동 이 단계가 보장하는 것
DRAINING 200 유지 downstream_rq_active + upstream_rq_active == 0을 폴링 UP 유지 — 기존 요청 정상 종료 in-flight 보호 (RST 없음)
CLOSING active=0 확인 후 503 flip(/close-lb) LB가 DOWN 마킹하도록 신호 inter × fall(예: 2s×2=4s) 만에 DOWN → 신규 차단 신규 유입 차단
CLOSED /health(K8s readiness) 503 LB_BUFFER 대기 후 readiness까지 내림 → pod 종료 per-node 판정도 DOWN endpoint 완전 제거 후 SIGTERM

읽는 법: 각 행은 “이 단계가 어떤 위험을 막는가"로 보면 된다. DRAINING은 RST를, CLOSING은 신규 요청 유입을, CLOSED는 조기 종료를 막는다. 세 위험이 서로 다른 시점에 발생하므로 단계가 셋으로 나뉜다.

즉 graceful의 심장은 DRAINING(health 200 유지) 이며, 이 200 유지가 본 문서 핵심 결론(“health 200 유지로 in-flight 보호”)의 실제 메커니즘이다. OPEN(정상 200)과 FAULT(drain timeout 초과 등 비정상)를 포함한 상태별 health 응답표·전이 제약, 그리고 POST /reopen abort 경로와 Warning: 199 헤더(CLOSING→OPEN 재투입) 메커니즘은 HC FSM 정본이 정본이다.

sequenceDiagram
    participant K as kubelet
    participant H as hc sidecar
    participant LB as LB (Citrix/HAProxy)
    K->>H: SIGTERM / preStop graceful-drain.sh
    Note over H,LB: DRAINING — /health_check.html = 200
    loop until active == 0 (or DRAIN_TIMEOUT)
        H->>H: poll downstream+upstream_rq_active
        LB->>H: GET /health_check.html -> 200 (stay UP)
    end
    Note over H,LB: CLOSING — /close-lb flips 503
    H->>LB: /health_check.html = 503
    LB->>LB: mark DOWN after inter x fall
    Note over H,LB: LB_BUFFER wait
    Note over H: CLOSED — /close flips /health (readiness) 503
    H->>K: exit -> pod terminates

왜 두 종류의 timeout이 필요한가

이 메커니즘에는 시간 변수가 둘 있고, 혼동하면 그대로 장애가 된다.

  • terminationDrainDuration — Envoy(ProxyConfig)가 drain을 끝낼 때까지 기다리는 시간. Envoy가 drain.sh보다 먼저 죽으면 보호할 데이터 경로가 사라진다.
  • terminationGracePeriodSeconds — kubelet이 SIGKILL을 보내기 전 유예. 이게 drain·LB_BUFFER 합보다 작으면, drain이 끝나기 전에 pod가 강제로 죽는다.

핵심 불변식: terminationGracePeriodSeconds는 항상 terminationDrainDurationDRAIN_TIMEOUT + LB_BUFFER 둘 다보다 커야 한다. 이 산정식이 §5 단계 4의 본체다.

3. 6단계 도입 런북

단계 1 — Staging LB의 downStateFlush 동등 옵션 검증

이 런북의 모든 결론은 “사내 LB가 backend disable 시 in-flight를 즉시 RST한다"는 전제에 걸려 있다. 먼저 그 전제부터 확인한다. backend disable 시 in-flight를 즉시 RST하는지(=current 모드 현상), 아니면 graceful drain하는지 본다.

# long request 중에 backend를 LB에서 수동 disable
curl -o /dev/null -w "http=%{http_code} t=%{time_total}\n" "https://<staging>/sleep?seconds=60" &
# (Citrix GUI: LB Vserver → Service → Disable)
sudo tcpdump -n -i <iface> 'tcp[tcpflags] & tcp-rst != 0' -tttt -w /tmp/rst-staging.pcap
  • 즉시 502/RST → downStateFlush ENABLED → drain.sh의 health 조작이 유효(이 런북이 의미 있음).
  • 60s 후 정상 응답 → 이미 graceful → drain.sh 역할이 “pool에서 먼저 제외"로 축소됨.
  • HTTP keepalive가 켜져 있으면 TCP session 재사용으로 RST 거동이 달라질 수 있음(위험 신호).

단계 2 — IGW Helm chart에 hc 사이드카 주입

레버를 쥘 주체(hc)를 IGW pod 안에 넣는다. 사이드카가 들어가야 health 응답을 우리가 제어할 수 있다.

값 경로는 게이트웨이 설치 방식에 따라 다르다

아래 gateways: istio-ingressgateway: additionalContainers: [...] 중첩 구조는 IstioOperator 기반(spec.values.gateways.istio-ingressgateway.*, 구형 in-cluster gateway 컴포넌트) values 오버레이에서만 유효한 경로다. Istio는 오래전부터 게이트웨이를 컨트롤 플레인과 분리해 설치하도록 권장해 왔고, 1.30 기준 공식 권장 설치 경로는 독립 istio/gateway Helm chart(helm install istio-ingressgateway istio/gateway -n istio-ingress)다 — 이 chart의 values.yaml에서 additionalContainers는 중첩 없이 최상위 키다. 사내 Helm chart가 어느 쪽 구조인지 먼저 확인한 뒤 값 경로를 맞출 것.

gateways:
  istio-ingressgateway:
    additionalContainers:
      - name: hc
        image: <사내-registry>/service-a-hc:<tag>
        ports: [{ containerPort: 18180 }]
        env:
          - { name: DRAIN_TIMEOUT, value: "120" }
          - { name: LB_BUFFER,     value: "10" }
          - { name: POLL_INTERVAL, value: "2" }
        readinessProbe: { httpGet: { path: /health, port: 18180 }, initialDelaySeconds: 3, periodSeconds: 5 }
        livenessProbe:  { httpGet: { path: /live,   port: 18180 }, initialDelaySeconds: 10, periodSeconds: 10 }
        lifecycle:
          preStop: { exec: { command: ["/opt/hc/graceful-drain.sh"] } }

검증: kubectl get pod -l app=istio-ingressgateway -o jsonpath 로 2-container(istio-proxy hc) 확인 + hc /health_check.html → 200. 위험 신호: container 없음 → additionalContainers 병합 우선순위 / ImagePullBackOff → imagePullSecret.

단계 3 — LB → hc health endpoint 도달 경로

LB가 우리 health 응답을 실제로 읽을 경로를 깐다. 그리고 이 경로가 “그 노드의 pod 상태"를 정확히 반영해야 한다.

spec:
  type: NodePort
  externalTrafficPolicy: Local   # 노드↔pod 1:1, source IP 보존 + 해당 노드 ready pod 없으면 LB가 그 노드 DOWN 판정
  ports:
    - { name: http2, port: 80,    nodePort: 30080, targetPort: 8080 }
    - { name: hc,    port: 18180, nodePort: 30180, targetPort: 18180 }

externalTrafficPolicy: Local이 중요한 이유: Cluster면 NodePort가 어느 노드로 들어와도 kube-proxy가 임의 pod로 분산해, health check가 “그 노드의 pod 상태"를 반영하지 못한다 — 즉 우리가 한 pod에서 503을 띄워도 LB는 다른 노드의 200을 받아 계속 UP으로 보고 레버가 먹히지 않는다. Local은 해당 노드의 pod만 응답하므로 LB의 per-node 판정이 정확해진다. 위험 신호: 30180 timeout → 그 노드에 ready pod 없음(Local 특성) → IGW readiness 확인.

단계 4 — terminationGracePeriodSeconds 산정

§2에서 본 두 timeout 불변식을 실제 숫자로 푼다. long request p99을 측정해 drain window를 정한다. 산정식은 프로덕션 적용 정본 §3-4와 통일한다.

DRAIN_TIMEOUT            = ceil(p99 × 1.2)
LB_BUFFER                = <monitor interval> × <fall count> + 5s
# terminationDrainDuration: Envoy가 drain.sh보다 먼저 죽지 않도록,
# Envoy drain 완료 최대 대기를 >= DRAIN_TIMEOUT로 둔다(고정 마진 임의 가산 X).
terminationDrainDuration >= DRAIN_TIMEOUT      # 최소 여유만 추가 가능
terminationGracePeriodSeconds = max(DRAIN_TIMEOUT + LB_BUFFER, terminationDrainDuration) + 30

예: p99=30s → DRAIN_TIMEOUT=36, LB_BUFFER=2×2+5=9, terminationDrainDuration=36
    → max(36+9, 36)+30 = max(45,36)+30 = 75s
(w6 실측은 DRAIN_TIMEOUT 대비 terminationDrainDuration=150을 사용 — 실측 p99에 맞춰 키운 사례)

terminationDrainDuration을 어디에 설정하나 (Istio 1.30, ProxyConfig 필드):

  • gateway Deployment의 pod annotation으로 개별 설정: proxy.istio.io/config: '{"terminationDrainDuration":"150s"}'
  • 또는 mesh 전역 MeshConfig.defaultConfig.terminationDrainDuration: 150s.
  • 커스텀 Deployment/Helm 사이드카 주입 시에는 annotation 방식이 게이트웨이 단위로 명시적이라 권장.

WebSocket/long-lived connection이 p99을 끌어올리면 별도 처리 전략 필요(단계 6 참고).

단계 5 — Observability 연결

레버가 잘 먹는지 눈에 보이게 한다. drain이 시간 안에 수렴하는지, 안 되면 알람이 뜨는지.

Envoy 15090(또는 istio-agent 병합 엔드포인트 15020 /stats/prometheus)이 이미 Prometheus로 scrape 중이더라도, 아래에서 쓸 envoy_http_downstream_rq_active·envoy_cluster_upstream_rq_active는 Istio 기본(minimal) Envoy stats 집합에 포함되지 않아 추가 설정 없이는 노출되지 않는다proxyStatsMatcher.inclusionRegexps(mesh 전역이면 MeshConfig.defaultConfig, 워크로드 단위면 proxy.istio.io/config annotation)로 해당 stat 이름 패턴을 명시적으로 추가해야 /stats/prometheus에 나타난다. Istio 표준 scrape 대상은 보통 15020(병합)이고 15090은 Envoy 자체 prometheus 포트이므로, 사내가 어느 포트를 긁는지 + proxyStatsMatcher가 설정돼 있는지를 함께 확인한다. 실제 수집할 메트릭 목록은 §6 모니터링 메트릭 표를 정본으로 두고, 여기서는 배선(어디서 무엇을 긁어 어떤 alert로 묶나) 만 다룬다.

drain timeout 초과를 잡는 alert:

- alert: IGWDrainTimeoutExceeded
  # 발화 조건: pod 삭제 후 DRAIN_TIMEOUT(=120) 초과해도 in-flight가 남아 있음
  expr: |
    (envoy_http_downstream_rq_active > 0)
    and on(pod) (time() - kube_pod_deletion_timestamp > 120)
  for: 30s
  • <DRAIN_TIMEOUT>은 단계 4에서 산정한 실제 초(예: 120)로 치환.
  • kube_pod_deletion_timestampkube-state-metrics가 켜져 있어야 존재하는 메트릭이다(미설치 시 expr가 항상 빈 결과 → alert 무력화).
  • 검증: pod 삭제 직후 Grafana에서 envoy_http_downstream_rq_active 시계열이 DRAIN_TIMEOUT 안에 0으로 수렴하는지 패널로 확인.

단계 6 — Rollout 정책 + canary

레버가 한 pod에서 동작해도, rollout 정책이 잘못되면 전체 IGW가 동시에 빠져 무용지물이 된다.

spec:
  strategy:
    rollingUpdate: { maxUnavailable: 1, maxSurge: 1 }   # 0→1 (deadlock 방지)
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:   # required는 노드수≥replicas 보장 시만
          - weight: 100
            podAffinityTerm:
              topologyKey: kubernetes.io/hostname
              labelSelector: { matchLabels: { app: istio-ingressgateway } }

주의: maxUnavailable=0 + anti-affinity required + replicas=N노드면 새 pod가 앉을 노드가 없어 rollout이 deadlock된다. maxUnavailable=1로 두되, in-flight 보호는 graceful-drain.sh가 담당한다(maxUnavailable은 “동시 종료 수” 제약이지 “in-flight 보호” 제약이 아니다).

canary: staging에서 S1/S3 재현(connection_err=0 확인) → 프로덕션 1% → 10% → 50% → 100%. Flagger/Argo Rollouts로 weight 기반 가능.

4. 실험이 입증한 결과 (이 런북이 작동한다는 증거)

홈랩에서 이 레버를 켰을 때(improved)와 껐을 때(current)의 측정값이다. 추상적 주장 대신 숫자로 본다.

시나리오 current improved
S1 long-request (replicas=1) delete@T+5s → T+9s HAProxy DOWN → RST → 502 / 8.25s drain.sh가 active=0까지 health 200 유지 → DOWN 안 됨 → 200 / 60.01s
S3 continuous (replicas=2→3, rollout) 5xx=0, connection_err=9 (retries 3이 5xx 흡수, RST는 기록) 5xx=0, connection_err=0
S4 streaming (replicas=1) chunks=12/60, curl_exit=92 (HTTP/2 스트림 에러 — CANCEL 여부는 verbose 메시지로 별도 확인 필요) chunks=59/60, curl_exit=0

읽는 법:

  • S1이 메커니즘을 가장 깨끗하게 보여준다. current는 health 200을 유지하지 않아 T+9s에 DOWN→RST→502(8.25s에 끊김). improved는 active=0까지 health 200을 잡아 60s 요청을 끝까지 흘려보낸다(60.01s, 200).
  • S3은 “5xx=0이 곧 무중단이 아니다"의 증거다. current에서도 5xx=0이지만 connection_err=9 — HAProxy retries 3이 backend RST를 재시도로 흡수해 클라이언트 5xx는 0이 됐을 뿐, 그 사이 RST가 9번 발생했다. improved에서 connection_err=0.
  • S4 streaming은 별개 세계 신호다. improved도 chunks 59/60(완벽 60은 아님) — downstream_rq_active가 connection 수명 내내 >0인 무한 stream은 DRAIN_TIMEOUT으로 못 덮는다는 한계를 드러낸다(단계 6/§What 참조).

근본 결론: LB에 명령 권한이 없어도 health check 응답을 조작하면 LB 동작을 간접 제어할 수 있다. 사내 Citrix가 downStateFlush ENABLED(= backend DOWN 시 in-flight 즉시 RST, HAProxy on-marked-down shutdown-sessions와 동치)라면 이 결론이 직접 적용된다.

5. 떴는지 한 번 확인 (장애 대응 점검 순서: rolling update 중 5xx burst)

레버가 어디서 풀렸는지 위에서 아래로 좁힌다. 각 줄은 §2~§3의 어느 단계가 깨졌는지로 매핑된다.

  1. echo show stat | sudo socat /run/haproxy/admin.sock stdio | awk -F, '{print $1,$2,$18,$19}' → backend DOWN/UP. worker 전부 DOWN이면 hc endpoint 문제(단계 3).
  2. kubectl logs <igw-pod> -c hc | grep "event=transition" → DRAINING→CLOSING이 너무 빠르면 current 모드(drain.sh 미사용, 단계 2).
  3. Envoy /stats?filter=downstream_rq_active|upstream_rq_active → drain 중 active>0이면 아직 처리 중(DRAINING 정상 동작).
  4. tcpdump 'tcp[tcpflags] & tcp-rst != 0' -tttt → RST 시점 ↔ HAProxy DOWN 마킹 시점 일치 여부(503 선행 여부, §2 앵커).
  5. kubectl describe pod → preStop 실행 여부, grace period 초과 여부(단계 4).

drain timeout 초과(active>0가 DRAIN_TIMEOUT 넘김) 시: DRAIN_TIMEOUT 증가(grace period도 함께) vs 강제 종료 허용(초과 in-flight RST 감수). WebSocket이 원인이면 별도 전략.

6. 모니터링 메트릭 (disruption 지표 수집 원칙)

메트릭 소스 의미
envoy_http_downstream_rq_active Envoy 15090 IGW in-flight 수
envoy_cluster_upstream_rq_active Envoy 15090 IGW→backend in-flight
haproxy_server_status haproxy_exporter 1=UP, 0=DOWN
haproxy_backend_connection_errors_total haproxy_exporter TCP 연결 에러 (5xx보다 민감)

핵심 함정: 5xx rate만 보면 disruption이 invisible할 수 있다. HAProxy retries 3 같은 LB retry가 backend RST를 재시도로 흡수하면 클라이언트는 5xx를 안 받지만, 그 사이 connection error/지연이 발생한다(S3에서 5xx=0, connection_err=9로 입증). 그래서 5xx + connection error rate를 둘 다 수집해야 한다. 클라이언트 측에서는 curl exit 7(connection refused), 92(HTTP/2 스트림 에러 — 정확한 원인은 curl -v 메시지로 확인) 같은 non-200·non-5xx도 별도 집계.

핵심 정리

  • LB의 backend 판정 = health check 응답 한 입력뿐. 그래서 LB 제어권 없이도 health 200/503 타이밍으로 backend 판정을 제어 = graceful termination의 핵심 레버. Citrix downStateFlush ENABLED 환경에 직접 이식 가능.
  • 순서가 곧 메커니즘: DRAINING(health 200 유지로 in-flight 보호) → CLOSING(active=0 후 503 flip) → CLOSED(readiness 503). 503을 먼저 띄우면 in-flight가 RST된다.
  • externalTrafficPolicy: Local은 per-node health 판정 정확성의 전제. Cluster면 분산 때문에 health가 노드 상태를 반영 못 함(다른 노드 200을 LB가 받아 레버 무력화).
  • 두 timeout을 구분하라: terminationGracePeriodSeconds(kubelet SIGKILL 유예)는 항상 terminationDrainDuration(Envoy drain 대기)·DRAIN_TIMEOUT+LB_BUFFER보다 커야 한다.
  • maxUnavailable=0 + anti-affinity required + replicas=N노드 = rollout deadlock. maxUnavailable=1 + graceful-drain.sh면 disruption 0 유지(maxUnavailable은 “동시 종료 수” 제약이지 “in-flight 보호” 제약이 아니다).
  • disruption은 5xx만으로 안 보인다. LB retry가 흡수하므로 connection error rate를 반드시 병행 수집.

What you might be missing

  • 상태 순서를 거꾸로 외우기 쉽다. “drain하면 바로 LB에서 빼야지"라는 직관과 달리, DRAINING에서는 health 200을 유지해 in-flight를 끝까지 흘려보낸 뒤(active=0) CLOSING에서야 503으로 flip한다. 503 선행 = downStateFlush ENABLED LB가 in-flight 즉시 RST = graceful 실패.
  • terminationDrainDuration vs terminationGracePeriodSeconds 혼동. 전자는 Envoy(ProxyConfig)의 drain 대기, 후자는 kubelet의 강제 kill 전 유예. terminationGracePeriodSeconds < terminationDrainDuration이면 Envoy가 drain을 끝내기 전에 SIGKILL 당한다. 그래서 grace는 항상 drain·LB_BUFFER 합보다 크게 잡는다.
  • alert가 조용히 무력화될 수 있다. kube_pod_deletion_timestamp는 kube-state-metrics 의존 메트릭이라, 미설치 환경에선 expr가 빈 결과를 내며 IGWDrainTimeoutExceeded가 절대 발화하지 않는다. alert “정상(미발화)“과 “메트릭 부재"를 구분하려면 absent() 보조 alert를 함께 둔다.
  • 5xx=0이 곧 무중단은 아니다. LB retry(HAProxy retries 3)가 backend RST를 흡수하면 클라이언트 5xx는 0이지만 connection error·지연은 발생한다(S3 실측). disruption SLO는 5xx + connection error rate를 함께 본다.
  • streaming은 이 레버의 사각지대다. downstream_rq_active가 connection 수명 내내 >0인 WebSocket/gRPC bidi는 active=0 폴링이 끝나지 않아 DRAIN_TIMEOUT으로 못 덮는다(S4의 59/60이 그 경계). 무한 stream은 IGW 분리나 max_stream_duration 같은 별도 전략이 필요 — grace period를 키우는 건 답이 아니다.

검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6)

검증 방법: 공식 문서 대조(Istio/Envoy/HAProxy/curl 자료) + homelab 클러스터 실측(terminationDrainDuration/proxyStatsMatcher 동작 검증).

주장 판정 근거
C1. LB backend 판정은 health check 응답이 유일한 입력이며, HAProxy on-marked-down shutdown-sessions(Citrix downStateFlush ENABLED와 동치)는 DOWN 마킹 시 in-flight를 즉시 RST ✅ 실측 확인 haproxy.com/…/shutdown-sessions-server · T30 실측
C2. terminationDrainDuration은 SIGTERM~실제 proxy 종료 사이 유예이며, istio-agent가 Envoy drain을 지시·대기 후 kill ✅ 실측 확인 istio.io/…/istio.mesh.v1alpha1 · T30 실측
C3. terminationGracePeriodSeconds는 항상 terminationDrainDuration·DRAIN_TIMEOUT+LB_BUFFER 둘 다보다 커야 함 ✅ 실측 확인 istio.io/…/istio.mesh.v1alpha1 · T30 실측
C4. terminationDrainDurationproxy.istio.io/config annotation(JSON 형식)으로 워크로드 단위 개별 설정 가능 ✅ 실측 확인 istio.io/…/annotations · T30 실측
C5. terminationDrainDurationMeshConfig.defaultConfig로 mesh 전역 설정도 가능 ✅ 문헌 확인 istio.io/…/istio.mesh.v1alpha1
C6. terminationDrainDuration 미설정 시 기본값 5초 ✅ 문헌 확인 istio.io/…/istio.mesh.v1alpha1
C7. Envoy 자체 prometheus 엔드포인트는 15090, istio-agent 병합 엔드포인트는 15020이며 표준 scrape 대상은 15020 ✅ 문헌 확인 istio.io/…/prometheus
C8. 15090/15020을 이미 scrape 중이면 envoy_http_downstream_rq_active·envoy_cluster_upstream_rq_active가 추가 설정 없이 수집됨 ❌ 오류 — 본문 교정 istio.io/…/envoy-stats · T31 실측
C9. IGW Helm chart에 hc 사이드카 주입 시 gateways: istio-ingressgateway: additionalContainers: [...] 구조 사용 ⚠️ 구버전 서술 — 갱신 istio.io/…/gateway
C10. curl exit 92는 ‘HTTP/2 CANCEL’을 의미 ❌ 오류 — 본문 교정 curl.se/libcurl/c/libcurl-errors.html
C11. Envoy admin /stats?filter=<regex>는 이름이 정규식에 매치되는 통계만 반환하며 기본은 부분 매치 ✅ 문헌 확인 envoyproxy.io/…/admin

Files