App Runner에서 외부 API 호출이 10초 만에 죽는다
증상
App Runner (VPC Egress 모드)에서 GCP Cloud Run 엔드포인트를 호출할 때마다 정확히 10초 후에 다음 에러가 찍히고 실패한다.
ERROR c.s.i.report.ReportServiceClient : [report-service] survey-report 호출 실패
body=Did not observe any item or terminal signal within 10000ms in 'flatMap'
이상한 점은 NAT Instance에서 직접 curl하면 0.14초 만에 202가 온다는 것이다. 로컬에서도, NAT Instance에서도 멀쩡한데 App Runner에서만 죽는다.
구조와 원인
App Runner VPC Egress 모드에서 외부 인터넷으로 나가는 경로는 다음과 같다.
문제가 된 지점은 NAT Instance의 iptables FORWARD 체인이었다.
NAT Instance의 user_data에는 POSTROUTING MASQUERADE 규칙만 있었고,
FORWARD 체인에 VPC 트래픽을 허용하는 규칙이 없었다.
# iptables -L FORWARD -n -v
Chain FORWARD (policy DROP 107K packets, 6438K bytes)
pkts bytes target prot opt in out source destination
107K 6438K DOCKER-USER all -- ...
107K 6438K DOCKER-ISOLATION-STAGE-1 all -- ...
FORWARD 체인에는 DOCKER-USER, DOCKER-ISOLATION-STAGE-1 체인만 있었다.
VPC 트래픽을 ACCEPT하는 규칙이 없는 상태에서 default policy가 DROP이므로,
App Runner에서 오는 패킷이 모두 조용히 드랍되고 있었다.
OUTPUT 체인을 거친다 — FORWARD 체인을 거치지 않는다.
다른 호스트를 위해 포워딩하는 트래픽만 FORWARD 체인을 통과한다.
그래서 NAT Instance에서 curl이 성공해도, App Runner의 패킷은 드랍될 수 있다.
디버깅 과정
라우팅은 정상
Private Subnet 라우트 테이블에 0.0.0.0/0 → NAT Instance 설정 확인. Security Group도 443 포트 허용. source/dest check도 비활성화.
NAT Instance에서 curl 성공 → "네트워크 문제 아닌 것 같은데?"
NAT Instance에서 curl -o /dev/null -w "%{http_code}" ... GCP_URL 실행 → 0.14초 만에 202. 하지만 이건 NAT Instance 자신이 보낸 요청이라 무관하다.
iptables FORWARD chain 확인
NAT Instance에서 iptables -L FORWARD -n -v 실행. 107K 패킷이 DROP된 것 확인. ACCEPT 규칙이 전혀 없음.
FORWARD ACCEPT 규칙 추가
VPC 대역(10.0.0.0/16) 발신 트래픽과 그 응답 패킷을 ACCEPT하도록 규칙 추가 → 즉시 해결.
해결책
임시 조치 (재부팅 시 초기화):
sudo iptables -I FORWARD 1 -s 10.0.0.0/16 -j ACCEPT
sudo iptables -I FORWARD 2 -d 10.0.0.0/16 -m state --state RELATED,ESTABLISHED -j ACCEPT
영구 적용 — Terraform user_data에 추가:
user_data = <<-EOF
#!/bin/bash
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
dnf install -y iptables-services
iptables -t nat -A POSTROUTING -o ens5 -s ${var.vpc_cidr} -j MASQUERADE
# VPC 트래픽 포워딩 허용 (Docker 설치로 인한 FORWARD DROP 대응)
iptables -I FORWARD 1 -s ${var.vpc_cidr} -j ACCEPT
iptables -I FORWARD 2 -d ${var.vpc_cidr} -m state --state RELATED,ESTABLISHED -j ACCEPT
service iptables save
systemctl enable iptables
EOF
DOCKER-USER, DOCKER-ISOLATION-STAGE-1 규칙이 존재했다.
이 체인은 Docker daemon이 iptables를 수정할 때 삽입된다.
정확한 경위는 확인하지 못했지만, 결과적으로 VPC 트래픽을 허용하는 ACCEPT 규칙은 없고
policy만 DROP인 상태가 만들어져 있었다.
교훈
| 주제 | 배운 것 |
|---|---|
| iptables 체인 | 자신이 보내는 트래픽(OUTPUT)과 포워딩 트래픽(FORWARD)은 다른 체인을 거친다. NAT Instance가 직접 통신되더라도 포워딩이 안 될 수 있다 |
| DOCKER-USER 체인 | Docker daemon은 iptables FORWARD 체인에 DOCKER-USER, DOCKER-ISOLATION 규칙을 삽입한다. VPC ACCEPT 규칙 없이 policy가 DROP이면 포워딩이 조용히 깨진다 |
| NAT Instance 디버깅 | iptables -L FORWARD -n -v의 DROP 카운터를 확인하면 패킷 드랍을 빠르게 발견할 수 있다 |
| user_data 영속성 | iptables 규칙은 재부팅 시 초기화된다. service iptables save + systemctl enable iptables로 영구화 필요 |