fix: Harden CI against Nginx misconfiguration that caused prod 502/404
TaxBaik CI/CD / build-and-deploy (push) Failing after 3m5s
TaxBaik CI/CD / build-and-deploy (push) Failing after 3m5s
Today's incident: CI reported successful deploys while the real site
returned 502 (root) then 404 (/taxbaik/) to users. Root cause was three
compounding Nginx issues, none of which the previous CI checks could see
because they only ever curled 127.0.0.1:5001 directly, bypassing Nginx:
1. Two Nginx config files existed. sites-available/default (documented,
but NOT symlinked into sites-enabled/) was being edited repeatedly with
zero effect. The file actually loaded was
sites-available/taxbaik-domains.conf (-> sites-enabled/), undocumented.
2. That real file hardcoded the Green-Blue app port (5003) directly in
both `location /` and `location /taxbaik`, instead of the persistent
TaxBaik.Proxy on 5001. When the active port flipped to 5004, Nginx kept
pointing at the dead 5003 -> 502.
3. Fixing the port to 5001 with a trailing slash on proxy_pass triggered
Nginx URI rewriting, sending a double slash ("//") to the backend,
which 404'd. Confirmed via `curl http://backend//` -> 404.
Changes:
- deploy.yml: replace the old blind `grep sites-available/default` check
(checked the wrong, unloaded file) with a hard-failing check that (a)
resolves the actual file via sites-enabled/ symlinks, (b) fails the
deploy if either location block hardcodes 5003/5004 instead of 5001,
(c) fails if /taxbaik's proxy_pass carries a stray trailing slash.
- deploy.yml: add an external, post-deploy check that curls the real
public domain (www.taxbaik.com root, /taxbaik/, /taxbaik/admin/login)
through Cloudflare + Nginx, with retries — this is what would have
caught the whole incident on the very first broken deploy instead of
requiring live user reports.
- deploy_gb.sh: drop the stale comment implying Nginx needs updating
per-deploy; it never should, since Nginx always points at the
persistent 5001 proxy which reads taxbaik_port itself.
- CLAUDE.md: document the real config file, the 5001-only invariant, the
proxy_pass trailing-slash gotcha, and the Host-header/SNI trick for
testing domain-based server blocks locally; record the incident in the
CI troubleshooting harness section.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -194,11 +194,43 @@ jobs:
|
||||
"\$DEPLOY_DIR/deploy_gb.sh" "\$DEPLOY_DIR"
|
||||
|
||||
echo "--- [4.5/5] Nginx 설정 검증 ---"
|
||||
# Nginx는 항상 포트 5001 (TaxBaik.Proxy)을 가리켜야 함
|
||||
# 프록시가 taxbaik_port 파일을 읽어서 자동으로 라우팅
|
||||
EXPECTED_PROXY="http://127.0.0.1:5001;"
|
||||
grep "proxy_pass.*5001" /etc/nginx/sites-available/default > /dev/null 2>&1 || \
|
||||
echo "⚠️ Warning: Nginx may not be configured for port 5001 (TaxBaik.Proxy). Manual intervention may be needed."
|
||||
# 실제 로드되는 파일은 sites-enabled/의 심볼릭 링크 대상만이다.
|
||||
# sites-available/에 다른 파일(예: default)이 있어도 sites-enabled에
|
||||
# 링크되어 있지 않으면 nginx는 그 내용을 절대 읽지 않는다.
|
||||
NGINX_CONF=""
|
||||
for f in /etc/nginx/sites-enabled/*; do
|
||||
if [ -e "\$f" ] && grep -q "location /taxbaik" "\$f" 2>/dev/null; then
|
||||
NGINX_CONF=\$(readlink -f "\$f")
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "\$NGINX_CONF" ]; then
|
||||
echo "❌ FATAL: sites-enabled/ 안에서 'location /taxbaik'를 정의한 파일을 찾을 수 없음" >&2
|
||||
echo " sites-available/에 파일을 수정해도 sites-enabled에 심볼릭 링크되어 있지 않으면 반영되지 않는다." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "실제 로드되는 설정 파일: \$NGINX_CONF"
|
||||
|
||||
# 불변식: '/'와 '/taxbaik' location 모두 반드시 127.0.0.1:5001 (TaxBaik.Proxy)을
|
||||
# 가리켜야 한다. 5003/5004를 직접 하드코딩하면 Green-Blue 포트 전환 시
|
||||
# 죽은 포트를 가리키게 되어 502/404가 발생한다 (실제 발생했던 장애).
|
||||
if grep -E "proxy_pass\s+http://127\.0\.0\.1:500[34]" "\$NGINX_CONF" > /dev/null 2>&1; then
|
||||
echo "❌ FATAL: \$NGINX_CONF 가 포트 5003/5004를 직접 참조함 (Green-Blue 전환 시 502 발생)" >&2
|
||||
echo " 수정: sudo sed -i 's|127.0.0.1:500[34]|127.0.0.1:5001|g' \$NGINX_CONF && sudo nginx -t && sudo systemctl reload nginx" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# proxy_pass에 URI(끝 슬래시)가 있으면 nginx가 요청 경로를 재작성하며,
|
||||
# location 접두사와 슬래시 개수가 안 맞으면 백엔드로 이중 슬래시(//)가
|
||||
# 전달되어 404가 발생한다 (실제 발생했던 장애). 접두사 location에서는
|
||||
# proxy_pass에 URI를 붙이지 않는다.
|
||||
if grep -E "location\s+/taxbaik\s*\{" -A 1 "\$NGINX_CONF" | grep -qE "proxy_pass\s+http://127\.0\.0\.1:5001/;"; then
|
||||
echo "❌ FATAL: location /taxbaik 의 proxy_pass 에 불필요한 trailing slash가 있음 (이중 슬래시로 인한 404 위험)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Nginx 설정 검증 통과 (실제 로드 파일 확인 + 포트 5001 고정 + trailing slash 없음)"
|
||||
|
||||
echo "--- [5/5] 헬스 체크 (최대 60초) ---"
|
||||
ATTEMPTS=20
|
||||
@@ -257,6 +289,42 @@ jobs:
|
||||
REMOTE
|
||||
|
||||
echo "✓ 배포 완료: taxbaik_${TIMESTAMP} @ $DEPLOY_HOST"
|
||||
|
||||
# 내부 127.0.0.1:5001 헬스 체크는 Nginx/Cloudflare를 거치지 않으므로
|
||||
# Nginx 설정 오류(잘못된 파일 수정, 죽은 포트 하드코딩 등)를 잡지 못한다.
|
||||
# 실제 사용자가 접속하는 경로 그대로 외부에서 검증해야 이런 장애를 CI가 스스로 잡는다.
|
||||
check_public() {
|
||||
local url="$1"
|
||||
local status
|
||||
status=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$url" || echo "000")
|
||||
if [ "$status" != "200" ]; then
|
||||
echo " ✗ $url → HTTP $status" >&2
|
||||
return 1
|
||||
fi
|
||||
echo " ✓ $url → HTTP $status"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "--- 실제 공개 도메인 종단 간 검증 (Nginx/Cloudflare 경유, 최대 3회 재시도) ---"
|
||||
PUBLIC_OK=false
|
||||
for i in 1 2 3; do
|
||||
if check_public "https://www.taxbaik.com/" \
|
||||
&& check_public "https://www.taxbaik.com/taxbaik/" \
|
||||
&& check_public "https://www.taxbaik.com/taxbaik/admin/login"; then
|
||||
PUBLIC_OK=true
|
||||
break
|
||||
fi
|
||||
echo " 재시도 대기 중... ($i/3)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ "$PUBLIC_OK" != "true" ]; then
|
||||
echo "❌ FATAL: 실제 공개 도메인 검증 실패. Nginx가 죽은 포트를 가리키거나 잘못된 파일을 수정했을 가능성이 높다." >&2
|
||||
echo " 확인: sites-enabled/의 실제 파일에서 location / 와 location /taxbaik 모두 127.0.0.1:5001을 가리키는지 점검" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ 실제 공개 도메인 전체 정상"
|
||||
|
||||
send_telegram "✅ <b>TaxBaik 배포 완료</b>
|
||||
|
||||
커밋: <code>${COMMIT}</code>
|
||||
|
||||
Reference in New Issue
Block a user