- Published on
EKS Pod→RDS 504 타임아웃 - SG·NACL·NAT 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버/애플리케이션은 멀쩡한데 EKS Pod → RDS 구간에서만 504 Gateway Timeout(혹은 앱 레벨의 connect timeout, i/o timeout)이 터지면, 대부분 원인은 “DB 자체”가 아니라 **네트워크 경로(보안그룹/서브넷 라우팅/NACL/NAT/엔드포인트)**에 있습니다. 특히 EKS는 노드/파드 IP, CNI 모드, 서브넷 분리(퍼블릭/프라이빗), NAT 유무에 따라 같은 VPC 안에서도 트래픽이 전혀 다른 길로 흐를 수 있어요.
이 글은 10분 안에 원인을 좁히는 것을 목표로, SG → NACL → NAT/라우팅 → DNS/엔드포인트 → 관측(로그/플로우로그) 순서로 진단합니다.
관련 네트워크 진단 글도 함께 보면 좋습니다: EKS Pod는 뜨는데 트래픽 0 - NetPol·SG·CNI 10분 진단, EKS ALB Ingress 502 Target reset 원인과 해결
0) 먼저 “504”의 정체부터 분리하기
504는 보통 프록시/게이트웨이(예: ALB/Nginx/Envoy) 가 업스트림(여기서는 앱 또는 앱→DB) 응답을 못 받아서 내는 코드입니다. 즉, “DB가 504를 준다”가 아니라:
- ALB → Ingress → Service → Pod 까지는 됐는데 Pod 내부에서 DB 연결이 막혀서 응답이 늦음
- 혹은 애플리케이션이 DB 연결을 못 잡아 요청 처리 자체가 지연되어 게이트웨이가 타임아웃
따라서 DB 연결 실패를 앱 로그에서 먼저 확인하세요.
앱 로그에서 흔한 패턴
dial tcp <ip>:5432: i/o timeoutconnect ETIMEDOUT <host>:3306timeout expired(JDBC)could not translate host name(DNS)
이 중 could not translate host name이면 네트워크보다 DNS/리졸버부터 봐야 합니다(뒤에서 다룹니다).
1) Pod 안에서 30초 내 1차 확인(가장 싸고 빠름)
우선 “정말로 Pod → RDS TCP가 안 열리는지”를 확인합니다.
임시 디버그 Pod 실행
kubectl run -it --rm netdebug \
--image=public.ecr.aws/amazonlinux/amazonlinux:2023 \
--restart=Never -- bash
DNS 확인
# RDS 엔드포인트가 제대로 해석되는지
getent hosts mydb.abc123.ap-northeast-2.rds.amazonaws.com
# CoreDNS 경유 확인
cat /etc/resolv.conf
TCP 포트 연결 확인
PostgreSQL(5432) 예시:
# nc가 없으면 설치
dnf -y install nmap-ncat bind-utils
# 포트 오픈 여부
nc -vz mydb.abc123.ap-northeast-2.rds.amazonaws.com 5432
MySQL(3306) 예시:
nc -vz mydb.abc123.ap-northeast-2.rds.amazonaws.com 3306
결과 해석
succeeded→ 네트워크는 대체로 OK. 앱 커넥션풀/타임아웃/SSL 쪽을 의심timed out→ SG/NACL/라우팅/NAT/DNS 중 하나no route to host→ 라우팅/서브넷/보안장비(희귀) 가능성 큼
2) 보안그룹(SG): “RDS 인바운드”가 핵심
EKS→RDS에서 가장 흔한 실수는 RDS SG 인바운드에 Pod/Node 소스가 허용되지 않은 것입니다.
체크 포인트 1: RDS SG 인바운드 소스가 무엇인가?
RDS는 보통 다음 중 하나로 허용합니다.
- 노드 SG를 소스로 허용(권장: 관리가 쉬움)
- RDS SG 인바운드:
TCP 5432source =EKS Node SG
- Pod IP 대역(CIDR)을 소스로 허용
- VPC CNI로 Pod가 VPC IP를 직접 쓰는 경우에만 의미가 있고, 서브넷 변경/확장 시 관리가 어려움
- (비권장)
0.0.0.0/0허용
흔한 함정: “Pod SG”를 쓰는 줄 알았는데 실제로는 Node SG로 나감
EKS의 Security Groups for Pods(SGP)를 쓰지 않으면, Pod 트래픽은 기본적으로 노드 ENI의 SG 영향을 받습니다. 즉:
- RDS SG 인바운드에 “Pod용 SG”를 넣어놨는데
- 실제 트래픽은 Node SG로 나가서
- 결과적으로 RDS에서 drop
체크 포인트 2: RDS SG 아웃바운드는 보통 문제 없음
SG는 stateful이라 인바운드만 맞으면 응답은 자동 허용됩니다. 다만 조직 정책으로 아웃바운드 제한을 걸어둔 경우가 있으니 RDS SG 아웃바운드가 “모두 허용”이 아닌 환경이면 같이 확인하세요.
AWS CLI로 빠르게 확인(예시)
aws ec2 describe-security-groups \
--group-ids sg-xxxxxxxx \
--query 'SecurityGroups[0].IpPermissions'
3) NACL: stateless라 “양방향 포트”를 함께 봐야 함
NACL(Network ACL)은 stateless라서 인바운드만 열어도 안 됩니다.
NACL에서 자주 막히는 지점
- RDS 서브넷 NACL이 인바운드
5432만 열고, 아웃바운드 ephemeral(대개 1024-65535) 미허용 - EKS 노드 서브넷 NACL이 아웃바운드
5432는 열었는데, 인바운드 ephemeral이 막힘
최소 규칙(일반적인 케이스)
- EKS 서브넷 NACL
- Outbound: TCP 5432(또는 3306) to RDS subnet CIDR 허용
- Inbound: TCP 1024-65535 from RDS subnet CIDR 허용
- RDS 서브넷 NACL
- Inbound: TCP 5432 from EKS subnet CIDR 허용
- Outbound: TCP 1024-65535 to EKS subnet CIDR 허용
> 참고: 정확한 ephemeral 범위는 OS/설정에 따라 다를 수 있으나, 보통 1024-65535로 열어둡니다(서브넷 CIDR로 제한).
4) 라우팅/NAT: “같은 VPC인데 왜 NAT?”를 구분
EKS Pod→RDS가 같은 VPC 내부 프라이빗 통신이면 원칙적으로 NAT가 필요 없습니다. 그런데 다음 상황에서는 NAT/라우팅 문제가 504로 보일 수 있습니다.
케이스 A: RDS가 다른 VPC(피어링/Transit Gateway)
- VPC Peering/TGW 라우팅 테이블에 상호 CIDR 경로가 없으면
timeout - 피어링은 transitive routing 불가(A↔B↔C 형태 안 됨)
케이스 B: RDS 엔드포인트가 퍼블릭으로 해석/접근되는 경우
- RDS Publicly accessible = true 이고
- Pod에서 DNS가 퍼블릭 IP로 해석되거나(드묾)
- 또는 프록시/환경변수로 퍼블릭 엔드포인트를 바라보는 경우 → 이때 프라이빗 서브넷 Pod는 NAT를 통해 인터넷으로 나가야 연결됩니다.
빠른 확인
# RDS 엔드포인트가 어떤 IP로 해석되는지
getent hosts mydb.abc123.ap-northeast-2.rds.amazonaws.com
# 나온 IP가 VPC CIDR 대역인지 확인
# (예: VPC가 10.0.0.0/16인데 10.x면 내부일 가능성 큼)
케이스 C: 노드는 프라이빗인데 NAT Gateway가 없거나 라우팅이 깨짐
이 경우는 주로 “DB뿐 아니라 외부도 안 됨”으로 드러납니다. 하지만 앱이 DB 연결 전에 외부 의존성(Secrets Manager, 인증서 OCSP, 외부 API)을 호출하면 결과적으로 504처럼 보일 수 있어요.
프라이빗 서브넷 라우팅 테이블에서:
0.0.0.0/0 -> nat-xxxx가 있어야 외부로 나갑니다.
5) DNS/CoreDNS: 타임아웃처럼 보이는 이름 해석 문제
RDS는 엔드포인트가 DNS 기반입니다. CoreDNS 장애/정책으로 DNS가 느려지면 DB 연결도 같이 느려져 504를 유발합니다.
CoreDNS 상태 확인
kubectl -n kube-system get deploy coredns
kubectl -n kube-system logs deploy/coredns --tail=200
Pod에서 직접 DNS 질의
dnf -y install bind-utils
dig +time=2 +tries=1 mydb.abc123.ap-northeast-2.rds.amazonaws.com
SERVFAIL,timeout이 보이면 CoreDNS/노드 DNS 경로를 우선 해결해야 합니다.
6) “관측”으로 3분 안에 범인 찾기: VPC Flow Logs
SG/NACL/라우팅 중 무엇이 막는지 가장 빨리 알려주는 건 VPC Flow Logs입니다.
Flow Logs에서 보는 키워드
REJECT가 찍히면 NACL/SG 쪽에서 드롭ACCEPT인데도 앱이 타임아웃이면- 대상 포트/대상 IP가 맞는지
- 응답 트래픽도 있는지(양방향)
- DB 자체의 connection limit/CPU 스파이크 등 상위 레이어를 의심
Flow Logs를 CloudWatch Logs로 보내는 경우 비용이 급증할 수 있으니, 장기 활성화는 주의하세요: CloudWatch Logs 비용 폭증 원인과 절감 10가지
7) 실전 체크리스트(10분 루틴)
아래 순서대로 하면 대부분 10분 내 “네트워크인지/앱인지”가 갈립니다.
1분: Pod에서 TCP 테스트
getent hosts <rds-endpoint>nc -vz <rds-endpoint> 5432/3306
2분: RDS SG 인바운드
- 소스가 EKS Node SG(또는 실제 egress SG)인지 확인
- 포트(5432/3306) 정확한지 확인
2분: NACL 양방향
- EKS 서브넷, RDS 서브넷 각각
- DB 포트 + ephemeral 포트 양방향 허용 여부
2분: 라우팅
- 같은 VPC면 각 서브넷 route table에서 서로의 CIDR이
local로 보이는지 - 다른 VPC면 Peering/TGW 라우팅이 양쪽에 있는지
- 퍼블릭 엔드포인트 접근이면 NAT 경로가 있는지
3분: Flow Logs/로그로 확증
REJECT면 SG/NACL로 역추적ACCEPT면 DNS/앱/DB 리소스(커넥션 제한)로 이동
8) 코드 예제: 앱 레벨에서 “타임아웃을 진단 가능하게” 만들기
네트워크 문제가 맞더라도, 앱에서 타임아웃/재시도/로그가 부실하면 504로만 보이고 원인 파악이 오래 걸립니다.
(Node.js) PostgreSQL 연결 타임아웃/에러 로깅
import pg from 'pg';
const { Client } = pg;
const client = new Client({
host: process.env.PGHOST,
port: Number(process.env.PGPORT || 5432),
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
// 연결이 무한정 걸리지 않게
connectionTimeoutMillis: 3000,
statement_timeout: 5000,
});
async function main() {
const start = Date.now();
try {
await client.connect();
const res = await client.query('select 1 as ok');
console.log({ ok: res.rows[0].ok, ms: Date.now() - start });
} catch (e) {
console.error('DB_CONNECT_ERROR', {
message: e.message,
code: e.code,
ms: Date.now() - start,
});
process.exitCode = 1;
} finally {
await client.end().catch(() => {});
}
}
main();
(Python) 간단 TCP 헬스체크(앱 컨테이너에 넣어도 좋음)
import os, socket, time
host = os.environ["DB_HOST"]
port = int(os.environ.get("DB_PORT", "5432"))
t0 = time.time()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
try:
s.connect((host, port))
print({"status": "connected", "ms": int((time.time()-t0)*1000)})
except Exception as e:
print({"status": "failed", "error": str(e), "ms": int((time.time()-t0)*1000)})
finally:
s.close()
이런 “짧은 타임아웃 + 명확한 에러”를 남기면, 504가 나와도 네트워크 타임아웃인지 DNS인지 인증 실패인지가 빠르게 갈립니다.
9) 마무리: 가장 흔한 원인 TOP 3
정리하면 EKS Pod→RDS 타임아웃의 빈도 높은 원인은 다음 순서입니다.
- RDS SG 인바운드 소스가 잘못됨(Pod SG로 착각, 실제는 Node SG)
- NACL에서 ephemeral 포트가 막힘(stateless 특성 간과)
- 라우팅/피어링/TGW 경로 누락(특히 멀티 VPC)
위 10분 루틴으로도 원인이 안 잡히면, 그 다음은 “네트워크는 통과(ACCEPT)인데 DB가 느림” 범주입니다. 이때는 RDS의 max_connections, 파라미터 그룹, 커넥션 풀 고갈, DB CPU/IO, TLS 핸드셰이크, 프록시(RDS Proxy) 등을 이어서 봐야 합니다.