- Published on
EKS에서 Envoy 503 UF·URX 원인과 해결 10분
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
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분 내에 범위를 좁힐 수 있습니다.
- Envoy 액세스 로그에서 UF/URX와 upstream cluster/host 확인
- 엔드포인트(EndpointSlice) 존재/ready 여부 확인
- Pod IP로 직접 연결 테스트(Envoy 내부에서)
- NetworkPolicy / Security Group / NACL / CNI 이슈 확인
- mTLS/인증서/ALPN/프로토콜 불일치 확인
- 재시도/타임아웃 설정과 백엔드 과부하(리셋) 확인
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:8080OK, 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분 내 감지 가능합니다.
- Envoy
upstream_cx_connect_fail(UF 계열) - Envoy
upstream_rq_retry및upstream_rq_503(URX 계열) upstream_rq_timeout(UT/504 포함)- Kubernetes EndpointSlice ready endpoint 수
- 노드/파드 네트워크 에러(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 상태”보다 엔드포인트/네트워크/정책/프로토콜을 먼저 의심하는 것이 시간을 가장 아낍니다.