- Published on
EKS TLS handshake timeout 해결 - IRSA·VPC·CoreDNS
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 온프레미스에서는 잘 되던 외부 HTTPS 호출이 EKS로 옮기자마자 TLS handshake timeout으로 터지는 경우가 있습니다. 이 에러는 “TLS가 느리다”가 아니라, 핸드셰이크를 시작/완료하기 전에 네트워크 경로 어딘가가 막히거나 지연된 신호인 경우가 대부분입니다. 특히 EKS에서는 다음 3축에서 자주 발생합니다.
- IRSA(STS) 경로 문제: Pod가 AWS API를 쓰기 위해 STS(또는 OIDC/JWKS)로 나가야 하는데 egress/DNS가 깨져 토큰 교환이 지연/실패
- VPC egress/NAT/라우팅 문제: 프라이빗 서브넷에서 인터넷으로 나가는 경로가 없거나, NAT/SG/NACL/프록시에서 드롭
- CoreDNS/노드 DNS 구성 문제: DNS가 느리거나 NXDOMAIN/timeout이 발생해 TLS 연결 자체가 시작되지 못함
이 글은 “증상 → 원인 분리 → 최소 재현 → 해결 → 재발 방지” 순서로 정리합니다.
1) 먼저 에러를 분해하자: TLS vs DNS vs 라우팅
TLS handshake timeout은 애플리케이션 라이브러리(Go, Java, Python requests 등)가 연결/핸드셰이크 단계에서 타임아웃을 냈다는 뜻입니다. 실제 원인은 대개 아래 중 하나입니다.
- DNS 조회가 느리거나 실패 → 결국 IP를 못 얻어 handshake가 시작도 안 됨
- TCP 443 연결이 안 됨(SYN drop) → NAT/SG/NACL/라우팅/프록시
- SNI/인증서 체인/MTU 문제로 handshake 중간에 끊김
- 프록시/방화벽이 TLS를 가로채거나 특정 도메인만 차단
가장 빠른 분리 방법은 Pod 안에서 다음 3가지를 각각 확인하는 것입니다.
Pod에서 즉시 확인할 체크(원인 분리)
아래는 임시 디버그 Pod를 띄우는 예시입니다.
kubectl run -it --rm net-debug \
--image=public.ecr.aws/docker/library/alpine:3.19 \
--restart=Never -- sh
# alpine inside
apk add --no-cache curl bind-tools openssl ca-certificates
- DNS 확인
# DNS가 느리면 여기서 이미 지연/timeout이 보입니다.
time nslookup sts.amazonaws.com
time nslookup api.github.com
- TCP 연결(443) 확인
# TCP connect 단계 확인
curl -v --connect-timeout 5 https://sts.amazonaws.com/ 2>&1 | sed -n '1,80p'
- TLS handshake 자체 확인
# SNI 포함 TLS 핸드셰이크 확인
openssl s_client -connect sts.amazonaws.com:443 -servername sts.amazonaws.com -brief < /dev/null
nslookup이 timeout이면 CoreDNS/노드 DNS/라우팅 문제로 시작합니다.nslookup은 빠른데curl이 connect에서 멈추면 egress(NAT/SG/NACL/route) 문제입니다.- connect는 되는데
openssl s_client에서 멈추면 MTU/프록시/중간 장비/TLS inspection 가능성을 봅니다.
2) IRSA가 얽히면: STS 호출이 “필수 egress”가 된다
IRSA(IAM Roles for Service Accounts)를 쓰는 Pod는 AWS SDK가 내부적으로 대략 이런 흐름을 탑니다.
- 서비스어카운트 토큰(JWT) 읽기
- STS AssumeRoleWithWebIdentity 호출
- 임시 자격증명으로 AWS API 호출
즉, 애플리케이션이 S3/DynamoDB만 호출한다고 해도, 그 전에 STS로 반드시 나가야 합니다. 이때 STS로의 HTTPS가 TLS handshake timeout으로 막히면, 애플리케이션은 “AWS API가 느리다/죽었다”처럼 보이지만 실제론 클러스터 egress가 원인입니다.
IRSA 문제를 빠르게 확인하는 방법
Pod에 AWS CLI가 없더라도, 다음을 보면 단서가 나옵니다.
# IRSA 환경변수/웹아이덴티티 토큰 경로 확인
kubectl exec -it deploy/<your-app> -- sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION"'
# 토큰 파일 존재 확인
kubectl exec -it deploy/<your-app> -- sh -c 'ls -al $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 50 $AWS_WEB_IDENTITY_TOKEN_FILE'
그리고 디버그 Pod에서 STS 엔드포인트로 직접 호출해봅니다.
curl -v https://sts.amazonaws.com/ -o /dev/null
- 여기서 handshake timeout이면 IRSA 이전에 VPC egress/DNS부터 해결해야 합니다.
STS를 PrivateLink로 쓰는 경우의 함정
보안 요구로 STS/S3 등을 VPC Endpoint(PrivateLink)로 붙였다면, 다음이 흔한 함정입니다.
- Interface Endpoint의 SG가 443 인바운드를 허용하지 않음
- Private DNS 미설정/충돌로 퍼블릭으로 나가려다 NAT가 없어서 실패
- 엔드포인트가 특정 AZ에만 있고, 서브넷 라우팅/노드 배치로 지연
확인 포인트:
# VPC 엔드포인트 DNS가 내부 IP로 해석되는지
nslookup sts.amazonaws.com
# 결과가 10.x/172.16~31/192.168 대역이면 PrivateLink일 가능성이 큼
3) VPC egress: NAT/라우팅/보안그룹/NACL을 “패킷 흐름”으로 본다
프라이빗 서브넷의 노드/Pod가 인터넷으로 나가려면 일반적으로 NAT Gateway(또는 NAT 인스턴스) 가 필요합니다.
가장 흔한 시나리오
- 노드는 프라이빗 서브넷
- 라우트 테이블에
0.0.0.0/0 -> NAT Gateway가 없음 - 혹은 NAT는 있는데, NAT가 있는 퍼블릭 서브넷의 라우트/IGW 구성이 잘못됨
이 경우 외부 HTTPS는 전부 실패하고, 애플리케이션은 종종 TLS handshake timeout 또는 i/o timeout으로만 보입니다.
라우팅 체크리스트(핵심만)
- 노드가 속한 서브넷 라우트 테이블에
0.0.0.0/0이 NAT로 향하는가? - NAT가 있는 퍼블릭 서브넷은
0.0.0.0/0 -> IGW인가? - NACL이 ephemeral port(1024-65535) 왕복을 막지 않는가?
NAT 비용/트래픽 폭증까지 같이 겪고 있다면, 원인 진단과 절감은 별도 관점이 필요합니다. 이 경우는 VPC NAT Gateway 비용 폭증 10분 진단·절감도 함께 참고하면 좋습니다.
Security Group/NACL에서 놓치는 포인트
- 노드 SG: 아웃바운드 443이 열려 있어야 함(기본 all open이 아닌 경우)
- 엔드포인트 SG(PrivateLink): 인바운드 443을 노드 SG에서 허용해야 함
- NACL: 인바운드/아웃바운드 모두 ephemeral 포트 범위를 허용해야 return traffic이 정상
4) CoreDNS: “DNS가 느리면 모든 게 TLS timeout처럼 보인다”
EKS에서 외부 호출 장애의 절반은 DNS에서 시작합니다. CoreDNS가 느려지면 애플리케이션 로그에는 TLS/HTTP timeout만 남고, 실제로는 도메인 해석이 안 되어 연결을 못 하는 경우가 많습니다.
CoreDNS 상태/로그 확인
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200
kubectl -n kube-system describe configmap coredns
로그에서 자주 보이는 신호:
i/o timeout(upstream resolver로 못 나감)SERVFAIL증가- 특정 도메인 쿼리만 지연
NodeLocal DNSCache 사용 고려
트래픽이 많거나 노드 수가 늘면 CoreDNS가 병목이 될 수 있습니다. 이때 NodeLocal DNSCache를 쓰면:
- Pod → 노드 로컬 캐시로 질의(지연 감소)
- CoreDNS 부하 감소
- 네트워크 변동에 더 견고
CoreDNS 업스트림(노드의 /etc/resolv.conf) 확인
EKS는 보통 VPC DNS(예: 169.254.169.253 또는 VPC CIDR+2)를 사용합니다. 노드의 resolv.conf가 커스텀으로 바뀌었거나, 회사 DNS로 강제되어 외부 해석이 느리면 문제가 됩니다.
디버그 Pod에서 resolv.conf를 확인하세요.
cat /etc/resolv.conf
nameserver가 사내 DNS로 향하고 있고, 그 DNS가 VPC에서 접근 불가하면 timeout이 납니다.
5) MTU/경로 MTU 문제: connect는 되는데 handshake가 멈춘다
특히 다음 조합에서 MTU 문제가 터지기 쉽습니다.
- VPC CNI + 특정 인스턴스 타입/ENI 설정
- Transit Gateway, VPN, 온프레미스 연동
- 프록시/방화벽 경유
증상 패턴:
- DNS OK
- TCP connect OK
- TLS handshake에서 ClientHello/ServerHello 중간에 멈춤
간단한 힌트는 큰 패킷이 드롭되는지 보는 것입니다. 완전한 진단은 VPC Flow Logs, 노드 tcpdump가 필요하지만, 우선은 CNI/네트워크 경로 변경 직후부터 발생했는지 확인하세요.
또한 CNI 레벨에서 IP 부족/네트워크 압박이 있으면 간헐적 타임아웃으로 나타나기도 합니다. Pod가 Pending에 걸리거나 IP 할당이 불안정한 조짐이 있다면 Kubernetes CNI IP 부족으로 Pod Pending 해결 가이드도 같이 확인해두면 원인 범위를 줄일 수 있습니다.
6) 재현 가능한 “최소 테스트”로 CI처럼 검증하기
장애를 해결했는지 확신하려면, 애플리케이션이 아니라 네트워크/IRSA 경로를 직접 테스트하는 Job을 만들어두는 게 좋습니다.
예: STS + DNS + HTTPS를 한 번에 검사하는 Kubernetes Job
apiVersion: batch/v1
kind: Job
metadata:
name: egress-tls-smoke
namespace: default
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: smoke
image: public.ecr.aws/docker/library/alpine:3.19
command: ["/bin/sh", "-lc"]
args:
- |
apk add --no-cache curl bind-tools openssl ca-certificates >/dev/null
echo "== DNS ==";
time nslookup sts.amazonaws.com;
echo "== TCP/TLS ==";
curl -sS -v --connect-timeout 5 https://sts.amazonaws.com/ -o /dev/null;
echo "== OpenSSL handshake ==";
echo | openssl s_client -connect sts.amazonaws.com:443 -servername sts.amazonaws.com -brief
- 이 Job이 성공하면, 최소한 “DNS→443→TLS handshake” 경로는 정상입니다.
- 실패 시 로그를 그대로 증거로 남겨 네트워크/플랫폼 팀과 합의하기 쉬워집니다.
7) 해결책을 원인별로 정리(실전 처방전)
A. IRSA/STS 관련
- STS를 퍼블릭으로 쓸 경우: 프라이빗 노드면 NAT egress 필수
- STS를 PrivateLink로 쓸 경우:
- Interface Endpoint SG 인바운드 443을 노드 SG에서 허용
- Private DNS 설정 확인(원치 않게 퍼블릭으로 나가려 하지 않게)
- 엔드포인트 서브넷/AZ 분산 확인
B. VPC egress/NAT
- 프라이빗 서브넷 라우트 테이블에
0.0.0.0/0 -> NAT추가 - 퍼블릭 서브넷 라우트 테이블에
0.0.0.0/0 -> IGW확인 - NACL에서 ephemeral port 왕복 허용
- 회사 프록시 강제라면
HTTPS_PROXY/NO_PROXY를 Pod에 명확히 설정(특히 AWS 서비스 도메인)
C. CoreDNS
- CoreDNS Pod 리소스 상향(CPU throttling이면 DNS 지연이 급증)
- NodeLocal DNSCache 도입
- CoreDNS ConfigMap에서 불필요한
forward체인/외부 DNS를 제거하고 VPC DNS로 단순화
8) 운영 팁: 관측을 붙이면 다음 장애가 빨라진다
- 애플리케이션에선
dns lookup time,tcp connect time,tls handshake time을 분리해 로깅/메트릭화(예: Envoy, OpenTelemetry) - CoreDNS metrics(
coredns_dns_request_duration_seconds)를 Prometheus로 수집 - VPC Flow Logs로 443 드롭/REJECT 확인
EKS의 TLS handshake timeout은 결론적으로 TLS 자체 문제인 경우가 드물고, 대부분은 IRSA(STS)·VPC egress·CoreDNS 중 하나에서 “외부로 나가는 기본 경로”가 깨진 결과입니다. 위 순서대로 DNS → 443 connect → TLS handshake → IRSA(STS) 호출을 분리해 확인하면, 감으로 고치지 않고도 30분 내에 원인 축을 특정할 수 있습니다.