From 3d314c1fb4c02200acc6ad445e1101965295a9b1 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 23 Mar 2026 12:53:34 +0900 Subject: [PATCH] =?UTF-8?q?fix(infra):=20nginx=20=EB=8F=99=EC=A0=81=20DNS?= =?UTF-8?q?=20resolve=20+=20Docker=20=ED=97=AC=EC=8A=A4=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 컨테이너 재생성 시 502 Bad Gateway 방지: - 모든 nginx proxy_pass를 set $upstream 변수 방식으로 전환 (9개 파일, 24개 location) - resolver 127.0.0.11 valid=10s ipv6=off 통합 선언 - ai-api location의 개별 resolver 8.8.8.8 제거 (server-level로 통합) - 10개 API 서비스에 healthcheck 추가 (Node: wget, Python: urllib) - 모든 web/gateway depends_on을 condition: service_healthy로 강화 Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yml | 96 +++++++++++++++++++++++---- gateway/nginx.conf | 8 ++- system1-factory/web/nginx.conf | 16 +++-- system2-report/web/nginx.conf | 23 ++++--- system3-nonconformance/web/nginx.conf | 5 +- tkeg/web/nginx.conf | 5 +- tkpurchase/web/nginx.conf | 4 +- tksafety/web/nginx.conf | 4 +- tksupport/web/nginx.conf | 4 +- user-management/web/nginx.conf | 10 ++- 10 files changed, 139 insertions(+), 36 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d6d48d9..ba48541 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,6 +66,12 @@ services: - SSO_JWT_REFRESH_EXPIRES_IN=${SSO_JWT_REFRESH_EXPIRES_IN:-30d} - REDIS_HOST=redis - REDIS_PORT=6379 + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s depends_on: mariadb: condition: service_healthy @@ -104,6 +110,12 @@ services: - WEATHER_API_URL=${WEATHER_API_URL:-} - WEATHER_API_KEY=${WEATHER_API_KEY:-} - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3005/api/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - system1_uploads:/usr/src/app/uploads - system1_logs:/usr/src/app/logs @@ -126,8 +138,10 @@ services: volumes: - ./system1-factory/web:/usr/share/nginx/html:ro depends_on: - - system1-api - - sso-auth + system1-api: + condition: service_healthy + sso-auth: + condition: service_healthy networks: - tk-network @@ -141,8 +155,15 @@ services: - "30008:8000" environment: - API_BASE_URL=http://system1-api:3005 + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s depends_on: - - system1-api + system1-api: + condition: service_healthy networks: - tk-network @@ -175,6 +196,12 @@ services: - M_PROJECT_PASSWORD=${M_PROJECT_PASSWORD:-} - M_PROJECT_DEFAULT_PROJECT_ID=${M_PROJECT_DEFAULT_PROJECT_ID:-1} - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3005/api/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - system2_uploads:/usr/src/app/uploads - system2_logs:/usr/src/app/logs @@ -195,7 +222,8 @@ services: ports: - "30180:80" depends_on: - - system2-api + system2-api: + condition: service_healthy networks: - tk-network @@ -223,6 +251,12 @@ services: - ADMIN_USERNAME=${SYSTEM3_ADMIN_USERNAME:-hyungi} - TZ=Asia/Seoul - TKUSER_API_URL=http://tkuser-api:3000 + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - system3_uploads:/app/uploads depends_on: @@ -242,7 +276,8 @@ services: volumes: - system3_uploads:/usr/share/nginx/html/uploads depends_on: - - system3-api + system3-api: + condition: service_healthy networks: - tk-network @@ -275,6 +310,12 @@ services: - NTFY_PUBLISH_TOKEN=${NTFY_PUBLISH_TOKEN} - NTFY_EXTERNAL_URL=${NTFY_EXTERNAL_URL:-https://ntfy.technicalkorea.net} - TKFB_BASE_URL=${TKFB_BASE_URL:-https://tkfb.technicalkorea.net} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - system1_uploads:/usr/src/app/uploads depends_on: @@ -292,7 +333,8 @@ services: ports: - "30380:80" depends_on: - - tkuser-api + tkuser-api: + condition: service_healthy networks: - tk-network @@ -318,6 +360,12 @@ services: - DB_NAME=${MYSQL_DATABASE:-hyungi} - SSO_JWT_SECRET=${SSO_JWT_SECRET} - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s depends_on: mariadb: condition: service_healthy @@ -333,7 +381,8 @@ services: ports: - "30480:80" depends_on: - - tkpurchase-api + tkpurchase-api: + condition: service_healthy networks: - tk-network @@ -359,6 +408,12 @@ services: - DB_NAME=${MYSQL_DATABASE:-hyungi} - SSO_JWT_SECRET=${SSO_JWT_SECRET} - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - tksafety_uploads:/usr/src/app/uploads depends_on: @@ -378,7 +433,8 @@ services: volumes: - tksafety_uploads:/usr/share/nginx/html/uploads depends_on: - - tksafety-api + tksafety-api: + condition: service_healthy networks: - tk-network @@ -403,6 +459,12 @@ services: - DB_PASSWORD=${MYSQL_PASSWORD} - DB_NAME=${MYSQL_DATABASE:-hyungi} - SSO_JWT_SECRET=${SSO_JWT_SECRET} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s depends_on: mariadb: condition: service_healthy @@ -418,7 +480,8 @@ services: ports: - "30680:80" depends_on: - - tksupport-api + tksupport-api: + condition: service_healthy networks: - tk-network @@ -461,6 +524,12 @@ services: - TKUSER_API_URL=http://tkuser-api:3000 - ENVIRONMENT=production - TZ=Asia/Seoul + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 30s volumes: - tkeg_uploads:/app/uploads depends_on: @@ -482,7 +551,8 @@ services: ports: - "30780:80" depends_on: - - tkeg-api + tkeg-api: + condition: service_healthy networks: - tk-network @@ -522,8 +592,10 @@ services: ports: - "30000:80" depends_on: - - sso-auth - - system1-api + sso-auth: + condition: service_healthy + system1-api: + condition: service_healthy networks: - tk-network diff --git a/gateway/nginx.conf b/gateway/nginx.conf index ff8fe4e..b4c08ec 100644 --- a/gateway/nginx.conf +++ b/gateway/nginx.conf @@ -1,6 +1,7 @@ server { listen 80; server_name _; + resolver 127.0.0.11 valid=10s ipv6=off; root /usr/share/nginx/html; @@ -33,7 +34,9 @@ server { # SSO Auth 프록시 location /auth/ { - proxy_pass http://sso-auth:3000/api/auth/; + set $upstream http://sso-auth:3000; + rewrite ^/auth/(.*)$ /api/auth/$1 break; + proxy_pass $upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -42,7 +45,8 @@ server { # System1 API 프록시 (대시보드 배너용: 알림/TBM/휴가 카운트) location /api/ { - proxy_pass http://system1-api:3005/api/; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/system1-factory/web/nginx.conf b/system1-factory/web/nginx.conf index eecc4de..b618f12 100644 --- a/system1-factory/web/nginx.conf +++ b/system1-factory/web/nginx.conf @@ -1,6 +1,7 @@ server { listen 80; server_name _; + resolver 127.0.0.11 valid=10s ipv6=off; client_max_body_size 50M; @@ -25,7 +26,8 @@ server { # API 프록시 (System 1 API) location /api/ { - proxy_pass http://system1-api:3005/api/; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -35,14 +37,17 @@ server { # 업로드 파일 프록시 (^~ 로 regex location보다 우선 매칭) location ^~ /uploads/ { - proxy_pass http://system1-api:3005/uploads/; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # FastAPI Bridge 프록시 location /fastapi/ { - proxy_pass http://system1-fastapi:8000/; + set $upstream http://system1-fastapi:8000; + rewrite ^/fastapi/(.*)$ /$1 break; + proxy_pass $upstream; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -52,7 +57,9 @@ server { # SSO Auth 프록시 (gateway에서 이관) location /auth/ { - proxy_pass http://sso-auth:3000/api/auth/; + set $upstream http://sso-auth:3000; + rewrite ^/auth/(.*)$ /api/auth/$1 break; + proxy_pass $upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -61,7 +68,6 @@ server { # AI Service 프록시 (gateway에서 이관) location /ai-api/ { - resolver 8.8.8.8 valid=300s ipv6=off; set $ai_upstream https://ai.hyungi.net; rewrite ^/ai-api/(.*) /api/ai/$1 break; proxy_pass $ai_upstream; diff --git a/system2-report/web/nginx.conf b/system2-report/web/nginx.conf index dbff11e..c8150c0 100644 --- a/system2-report/web/nginx.conf +++ b/system2-report/web/nginx.conf @@ -1,6 +1,7 @@ server { listen 80; server_name _; + resolver 127.0.0.11 valid=10s ipv6=off; root /usr/share/nginx/html; index pages/safety/issue-report.html; @@ -26,7 +27,8 @@ server { # System 1 API 프록시 (공장/작업장, TBM, 출입관리, 프로젝트) location /api/workplaces/ { - proxy_pass http://system1-api:3005; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -34,7 +36,8 @@ server { } location /api/projects/ { - proxy_pass http://system1-api:3005; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -42,7 +45,8 @@ server { } location /api/tbm/ { - proxy_pass http://system1-api:3005; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -50,7 +54,8 @@ server { } location /api/workplace-visits/ { - proxy_pass http://system1-api:3005; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -59,7 +64,6 @@ server { # AI Service 프록시 (맥미니 home-service-proxy 경유) location /ai-api/ { - resolver 8.8.8.8 valid=300s ipv6=off; set $ai_upstream https://ai.hyungi.net; rewrite ^/ai-api/(.*) /api/ai/$1 break; proxy_pass $ai_upstream; @@ -75,7 +79,8 @@ server { # System 2 API 프록시 (신고 관련) location /api/ { - proxy_pass http://system2-api:3005; + set $upstream http://system2-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -84,7 +89,8 @@ server { # System 2 uploads (신고 사진 등) location ^~ /uploads/issues/ { - proxy_pass http://system2-api:3005/uploads/issues/; + set $upstream http://system2-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -93,7 +99,8 @@ server { # System 1 uploads 프록시 (작업장 레이아웃 이미지 등) location ^~ /uploads/ { - proxy_pass http://system1-api:3005/uploads/; + set $upstream http://system1-api:3005; + proxy_pass $upstream$request_uri; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/system3-nonconformance/web/nginx.conf b/system3-nonconformance/web/nginx.conf index dd57e19..3bfc72b 100644 --- a/system3-nonconformance/web/nginx.conf +++ b/system3-nonconformance/web/nginx.conf @@ -1,6 +1,7 @@ server { listen 80; server_name _; + resolver 127.0.0.11 valid=10s ipv6=off; client_max_body_size 10M; @@ -36,7 +37,8 @@ server { # API 프록시 (System 3 API) location /api/ { - proxy_pass http://system3-api:8000/api/; + set $upstream http://system3-api:8000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; @@ -54,7 +56,6 @@ server { # AI API 프록시 (맥미니 home-service-proxy 경유) location /ai-api/ { - resolver 8.8.8.8 valid=300s ipv6=off; set $ai_upstream https://ai.hyungi.net; rewrite ^/ai-api/(.*) /api/ai/$1 break; proxy_pass $ai_upstream; diff --git a/tkeg/web/nginx.conf b/tkeg/web/nginx.conf index 82e62ab..cd2cded 100644 --- a/tkeg/web/nginx.conf +++ b/tkeg/web/nginx.conf @@ -1,13 +1,16 @@ server { listen 80; server_name localhost; + resolver 127.0.0.11 valid=10s ipv6=off; root /usr/share/nginx/html; index index.html; client_max_body_size 100M; location /api/ { - proxy_pass http://tkeg-api:8000/; + set $upstream http://tkeg-api:8000; + rewrite ^/api/(.*)$ /$1 break; + proxy_pass $upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/tkpurchase/web/nginx.conf b/tkpurchase/web/nginx.conf index 51a5d65..09c4f81 100644 --- a/tkpurchase/web/nginx.conf +++ b/tkpurchase/web/nginx.conf @@ -2,6 +2,7 @@ server { listen 80; server_name _; charset utf-8; + resolver 127.0.0.11 valid=10s ipv6=off; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; @@ -25,7 +26,8 @@ server { } location /api/ { - proxy_pass http://tkpurchase-api:3000/api/; + set $upstream http://tkpurchase-api:3000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/tksafety/web/nginx.conf b/tksafety/web/nginx.conf index 2ac105f..ea51059 100644 --- a/tksafety/web/nginx.conf +++ b/tksafety/web/nginx.conf @@ -2,6 +2,7 @@ server { listen 80; server_name _; charset utf-8; + resolver 127.0.0.11 valid=10s ipv6=off; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; @@ -25,7 +26,8 @@ server { } location /api/ { - proxy_pass http://tksafety-api:3000/api/; + set $upstream http://tksafety-api:3000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/tksupport/web/nginx.conf b/tksupport/web/nginx.conf index f503a42..34cbf6c 100644 --- a/tksupport/web/nginx.conf +++ b/tksupport/web/nginx.conf @@ -2,6 +2,7 @@ server { listen 80; server_name _; charset utf-8; + resolver 127.0.0.11 valid=10s ipv6=off; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; @@ -25,7 +26,8 @@ server { } location /api/ { - proxy_pass http://tksupport-api:3000/api/; + set $upstream http://tksupport-api:3000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/user-management/web/nginx.conf b/user-management/web/nginx.conf index 559bea9..064de19 100644 --- a/user-management/web/nginx.conf +++ b/user-management/web/nginx.conf @@ -2,6 +2,7 @@ server { listen 80; server_name _; charset utf-8; + resolver 127.0.0.11 valid=10s ipv6=off; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; @@ -40,7 +41,8 @@ server { # 업로드 파일 프록시 (^~ 로 regex location보다 우선 매칭) location ^~ /uploads/ { - proxy_pass http://tkuser-api:3000/uploads/; + set $upstream http://tkuser-api:3000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -48,7 +50,8 @@ server { # work-issues API 프록시 → system2-api location /api/work-issues/ { - proxy_pass http://system2-api:3005; + set $upstream http://system2-api:3005; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -58,7 +61,8 @@ server { # API 프록시 → tkuser-api location /api/ { - proxy_pass http://tkuser-api:3000; + set $upstream http://tkuser-api:3000; + proxy_pass $upstream$request_uri; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade';