Published on

EKS에서 Envoy 503 UF·URX 원인과 해결 10분

Authors

서론

EKS에서 Istio/Envoy(사이드카, 게이트웨이, 혹은 AWS App Mesh Envoy)를 쓰다 보면 503과 함께 액세스 로그에 UF, URX 같은 플래그가 붙는 경우가 있습니다. 애플리케이션 Pod는 Running이고 심지어 readiness도 통과하는데, 클라이언트는 503을 받습니다.

이 글은 “왜 503인가?”를 추상적으로 설명하는 대신, UF/URX가 의미하는 네트워크 이벤트를 기준으로 10분 내에 원인을 좁히는 체크리스트를 제공합니다. (Istio/Envoy 기준이지만 App Mesh에서도 동일하게 적용됩니다.)

관련해서 Ingress 단에서 503을 볼 때의 일반 점검은 아래 글도 함께 보면 좋습니다.


1) Envoy 503과 UF/URX를 “정확히” 해석하기

Envoy access log에는 응답 코드 외에 Response Flags가 찍힙니다. 이 플래그가 핵심 단서입니다.

  • UF (Upstream Connection Failure): 업스트림(백엔드)으로 TCP 연결 자체가 실패했거나, 연결 성립 전에 실패.
  • URX (Upstream Retry Limit Exceeded): 재시도를 수행했지만 재시도 한도를 넘겨 실패(대개 upstream reset/timeout이 반복됨).

즉, UF/URX는 보통 “애플리케이션이 503을 반환했다”가 아니라, Envoy가 백엔드와 통신하지 못해 503을 만들었다는 의미입니다.

자주 같이 보이는 플래그 조합

  • 503 UF: 백엔드로 연결이 안 됨(엔드포인트 없음/네트워크/포트/정책)
  • 503 URX: 재시도는 했는데 계속 실패(리셋/타임아웃/과부하)
  • 504 UT: 업스트림 타임아웃(요청은 갔지만 응답이 제한 시간 내에 안 옴)

2) 10분 진단 로드맵(우선순위)

아래 순서대로 보면 대부분 10분 내에 범위를 좁힐 수 있습니다.

  1. Envoy 액세스 로그에서 UF/URX와 upstream cluster/host 확인
  2. 엔드포인트(EndpointSlice) 존재/ready 여부 확인
  3. Pod IP로 직접 연결 테스트(Envoy 내부에서)
  4. NetworkPolicy / Security Group / NACL / CNI 이슈 확인
  5. mTLS/인증서/ALPN/프로토콜 불일치 확인
  6. 재시도/타임아웃 설정과 백엔드 과부하(리셋) 확인

3) 1분: Envoy 로그에서 “어디로” 가다 실패했는지 잡기

(1) Istio 사이드카/게이트웨이에서 액세스 로그 보기

# 사이드카(istio-proxy) 로그
kubectl -n <ns> logs <pod> -c istio-proxy --tail=200

# Istio ingressgateway 로그
kubectl -n istio-system logs deploy/istio-ingressgateway -c istio-proxy --tail=200

로그 포맷은 환경마다 다르지만, 보통 아래 정보가 들어 있습니다.

  • response_code: 503
  • response_flags: UF 또는 URX
  • upstream_cluster: outbound|8080||svc.ns.svc.cluster.local
  • upstream_host: 10.0.x.y:8080 (있을 수도/없을 수도)

포인트

  • upstream_cluster가 특정 서비스/포트를 가리키는지 확인
  • upstream_host가 비어 있으면(또는 -) 엔드포인트를 못 찾았거나 라우팅 실패 가능성이 큼

(2) Envoy admin 통계로 UF/URX 급증 확인

포트 포워딩 후 stats를 보면 클러스터 단위로 실패 유형을 빠르게 볼 수 있습니다.

kubectl -n <ns> port-forward <pod> 15000:15000

# UF/URX/timeout 관련 통계 필터
curl -s http://127.0.0.1:15000/stats | egrep 'upstream_cx_connect_fail|upstream_rq_503|upstream_rq_timeout|upstream_cx_destroy_remote|upstream_rq_retry'
  • upstream_cx_connect_fail 증가: UF 성격(연결 실패)
  • upstream_rq_retry 증가 + upstream_rq_503 증가: URX 성격(재시도 후 실패)

4) 2분: 엔드포인트(EndpointSlice)와 readiness부터 확인

EKS에서 “Pod는 Running인데 503”의 상당수는 Service가 바라보는 엔드포인트가 비어 있거나 NotReady인 경우입니다. Envoy는 서비스 디스커버리 결과를 기반으로 업스트림을 선택하므로, 엔드포인트가 없으면 UF가 쉽게 발생합니다.

# 서비스 확인
kubectl -n <ns> get svc <svc>

# EndpointSlice 확인(요즘은 endpoints 대신 이게 핵심)
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc> -o wide

# Pod readiness/라벨 셀렉터 정합성 확인
kubectl -n <ns> describe svc <svc>
kubectl -n <ns> get pod -l <selector> -o wide

체크 포인트

  • Service selector가 실제 Pod 라벨과 일치하는가?
  • EndpointSlice에 endpoint가 존재하는가?
  • endpoint가 ready: true 인가?
  • targetPort/port가 실제 컨테이너 리스닝 포트와 맞는가?

이 주제는 아래 글에서 더 깊게 다룹니다.


5) 2분: Envoy “내부에서” 업스트림 Pod IP로 직접 붙어보기

UF는 네트워크/포트/정책 문제일 가능성이 높습니다. 가장 빠른 검증은 Envoy가 있는 네임스페이스/Pod 네트워크 위치에서 업스트림으로 직접 연결해 보는 것입니다.

(1) 임시 디버그 Pod에서 테스트

kubectl -n <ns> run netshoot --rm -it --image=nicolaka/netshoot -- /bin/bash

# DNS 확인
nslookup <svc>.<ns>.svc.cluster.local

# 서비스 VIP로 연결
curl -sv http://<svc>.<ns>.svc.cluster.local:<port>/health

# 특정 Pod IP로 직접 연결(EndpointSlice에서 확인한 IP)
curl -sv http://10.0.x.y:<targetPort>/health

(2) 해석

  • Pod IP로는 잘 되는데 Service로는 실패: kube-proxy/IPVS/EndpointSlice/서비스 포트 매핑 문제
  • Pod IP로도 실패: NetworkPolicy, SG/NACL, 애플리케이션 리스닝 주소(127.0.0.1 바인딩), mTLS/프로토콜 불일치 가능성

kube-proxy 모드 변경(IPVS) 이후 장애 경험이 있다면 이 글도 참고하세요.


6) 2분: UF의 대표 원인 6가지와 해결

원인 A) 엔드포인트 0개(또는 NotReady)

  • 증상: access log에 UF + upstream_host가 비어 있거나, 특정 호스트로 라우팅이 안 됨
  • 해결: readinessProbe, selector, port/targetPort, EndpointSlice 상태 수정

원인 B) 잘못된 포트/프로토콜(HTTP vs HTTPS, h2)

  • 증상: 연결은 되지만 즉시 리셋/핸드셰이크 실패가 섞여 URX로도 번짐
  • 해결: DestinationRule/VirtualService/App Mesh route의 port/protocol 정합성 확인

원인 C) Pod가 127.0.0.1에만 바인딩

  • 증상: Pod 내부에서는 curl localhost:8080 OK, Pod IP로는 실패 → Envoy는 Pod IP로 접근하므로 UF
  • 해결: 애플리케이션을 0.0.0.0로 리스닝

예) Node/Express

app.listen(8080, '0.0.0.0');

원인 D) NetworkPolicy로 egress/ingress 차단

  • 증상: 특정 네임스페이스/워크로드만 UF, tcpdump에서 SYN 재전송
  • 해결: Envoy가 있는 Pod(사이드카 포함)의 egress, 서버 Pod의 ingress 허용

예) 서버 ingress 허용(간단 예시)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-mesh
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          istio-injection: enabled
    ports:
    - protocol: TCP
      port: 8080

원인 E) 보안그룹/노드 간 라우팅/CNI 문제

  • 증상: 특정 노드에 스케줄된 Pod로만 UF, 노드 교체 시 증상 이동
  • 해결: VPC CNI 설정, SG rules(노드/파드 ENI), NACL, 라우팅 테이블 점검

원인 F) DNS 문제로 잘못된 엔드포인트 선택

  • 증상: 간헐적 UF/URX, 특정 시간대/부하에서만 증가
  • 해결: CoreDNS 성능/캐시/업스트림 리졸버 점검

DNS가 의심되면 아래 글의 체크리스트가 빠릅니다.


7) 3분: URX(재시도 초과)의 대표 원인 5가지와 해결

URX는 “한 번 실패”가 아니라 “여러 번 실패”입니다. 즉, 실패가 반복되는 구조(리셋/타임아웃/과부하/불안정)가 있습니다.

원인 1) 백엔드가 연결을 자주 리셋(RST)

  • 증상: URX + upstream_cx_destroy_remote 증가, 애플리케이션 로그에 connection reset, 혹은 서버의 keepalive/idle timeout 짧음
  • 해결: 서버 keepalive/idle timeout 조정, Envoy idle timeout 조정, 커넥션 풀/최대 연결 수 조정

원인 2) Envoy 타임아웃이 너무 짧아 재시도 루프

  • 증상: p99 지연이 살짝 늘면 URX 급증, upstream_rq_timeout 동반
  • 해결: route timeout/per-try timeout을 현실적으로 조정, 재시도 조건 축소

Istio VirtualService 예시

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: backend
  namespace: app
spec:
  hosts:
  - backend.app.svc.cluster.local
  http:
  - route:
    - destination:
        host: backend.app.svc.cluster.local
        port:
          number: 8080
    timeout: 5s
    retries:
      attempts: 2
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream,reset

운영 팁

  • 재시도는 장애 시 트래픽을 더 악화시킬 수 있습니다(특히 백엔드 과부하 상황).
  • attempts를 무작정 늘리기보다, 근본 원인(리셋/과부하)을 해결하는 편이 효과적입니다.

원인 3) HPA 스케일링/롤링 업데이트 중 엔드포인트 플랩

  • 증상: 배포/스케일 시점에만 URX, readiness는 통과하지만 실제로는 warm-up 미완료
  • 해결: readinessProbe를 실제 의존성(DB, 캐시) 포함하도록 강화, preStop/terminationGracePeriod로 드레인 시간 확보

원인 4) mTLS/인증서 문제로 핸드셰이크 실패 반복

  • 증상: 로그에 TLS alert, ssl.handshake 관련 에러, 특정 워크로드 간 통신만 실패
  • 해결: PeerAuthentication/DestinationRule의 TLS 모드 정합성 확인(STRICT/PERMISSIVE), 인증서 만료/시간 동기화 점검

원인 5) 백엔드 리소스 고갈(스레드/커넥션/FD)

  • 증상: 백엔드가 간헐적으로 accept 지연 → 타임아웃/리셋 → URX
  • 해결: 애플리케이션/런타임의 max connections, ulimit, DB 커넥션 풀, 큐잉/서킷브레이커 도입

8) 재발 방지: 관측(Observability) 포인트 5가지

UF/URX는 “한 번의 사건”이 아니라 “구조적 결함”을 드러내는 경우가 많습니다. 아래 5가지를 대시보드/알람으로 묶어두면 재발 시 1~2분 내 감지 가능합니다.

  1. Envoy upstream_cx_connect_fail (UF 계열)
  2. Envoy upstream_rq_retryupstream_rq_503 (URX 계열)
  3. upstream_rq_timeout (UT/504 포함)
  4. Kubernetes EndpointSlice ready endpoint 수
  5. 노드/파드 네트워크 에러(CNI errors, conntrack drops, kube-proxy errors)

Prometheus를 쓴다면 Istio의 istio_requests_total만 보지 말고, Envoy cluster 단위 지표(connect_fail, retry, timeout)를 꼭 같이 보세요.


9) 10분 요약 체크리스트

  • UF: “연결이 안 됨”

    • EndpointSlice 0개/NotReady
    • 포트/targetPort 불일치
    • 앱이 127.0.0.1 바인딩
    • NetworkPolicy/SG/NACL/CNI
    • DNS/서비스 디스커버리 이상
  • URX: “재시도 했는데 계속 실패”

    • 업스트림 리셋(keepalive/idle timeout 불일치)
    • 타임아웃이 짧아 재시도 루프
    • 배포/스케일 중 엔드포인트 플랩
    • mTLS/프로토콜 불일치
    • 백엔드 리소스 고갈

마지막으로, 503이 Envoy/Ingress에서 보이지만 Pod 상태가 정상으로 보일 때는 “Pod 상태”보다 엔드포인트/네트워크/정책/프로토콜을 먼저 의심하는 것이 시간을 가장 아낍니다.