Skip to content

Latest commit

ย 

History

History
264 lines (194 loc) ยท 15.9 KB

File metadata and controls

264 lines (194 loc) ยท 15.9 KB

TripFit ๋ฐฐํฌ (deploy/)

TripFit ์„œ๋ฒ„์˜ ๋กœ์ปฌ ๊ฐœ๋ฐœ ๊ตฌ๋™, EC2 4๋Œ€ ์ธํ”„๋ผ ๋ฐฐํฌ ์ ˆ์ฐจ, HTTPS ์ธ์ฆ์„œ ๋ฐœ๊ธ‰ ๋ฐ ์šด์˜ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰๋ฒ•์„ ๋‹ค๋ฃจ๋Š” ๊ฐ€์ด๋“œ๋‹ค. ๊ฐ ์„œ๋ฒ„๋ณ„ ๊ธฐ๋™ ์ˆœ์„œ, Let's Encrypt TLS ๋ฐœ๊ธ‰, CI/CD ์ž๋™ ๋ฐฐํฌ ํ๋ฆ„, ์Šคํ† ์–ด ์‹ฌ์‚ฌ ์ „ OAuth ์ฝ˜์†” ๋“ฑ๋ก ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ์„ค๋ช…ํ•œ๋‹ค. ์ƒ์„ธ ์ธํ”„๋ผ ์‚ฌ์–‘ ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜(GitHub Secrets) ์ „์ฒด ๋ชฉ๋ก์€ deploy/environment-reference.md๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค.

๊ด€๋ จ ๋ฌธ์„œ ์šฉ๋„
environment-reference.md ์ธํ”„๋ผ ์‚ฌ์–‘ ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜(Secrets) ์ „์ฒด ํ‘œ (SSOT)
docs/decisions/002-domain-split-vercel-api.md ๋„๋ฉ”์ธ ๋ถ„๋ฆฌ ํ™•์ •
docs/architecture.md ํ”„๋กœํ•„ยทddl-autoยท๋ ˆ์ด์–ด
ec2-split-deployment.md VPCยทSGยท1โ†’2 EC2 ์‹ฌํ™”
holiday-api-setup.md ๊ณตํœด์ผ API ์ธ์ฆํ‚ค ๋ฐœ๊ธ‰ยท๋“ฑ๋กยท๊ฒ€์ฆ
.claude/rules/deployment.md ์—์ด์ „ํŠธ ๋ฐฐํฌ ๊ฐ€๋“œ๋ ˆ์ผ

์—ญํ• ๋ณ„๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋„๋ฉ”์ธ ๊ตฌ์กฐ (ํ™•์ •)

๋„๋ฉ”์ธ ํ˜ธ์ŠคํŒ… ์ด repo
tripfit.online Vercel (React/Next.js) ํ”„๋ก ํŠธ ์ €์žฅ์†Œ โ€” EC2์— frontend ์ปจํ…Œ์ด๋„ˆ ์—†์Œ
api.tripfit.online EC2 A (Nginx + Spring Boot) deploy/app/, deploy/nginx/
grafana.tripfit.online EC2 C (Nginx + Grafana) deploy/monitoring/ โ€” ์šด์˜์šฉ, ํด๋ผ์ด์–ธํŠธ ์•ฑ๊ณผ ๋ฌด๊ด€

Route 53

  • tripfit.online โ†’ Vercel DNS (CNAME/A, Vercel ๋Œ€์‹œ๋ณด๋“œ ์•ˆ๋‚ด ๋”ฐ๋ฆ„)
  • api.tripfit.online โ†’ EC2 A Elastic IP (A ๋ ˆ์ฝ”๋“œ)
  • grafana.tripfit.online โ†’ EC2 C Elastic IP (A ๋ ˆ์ฝ”๋“œ)

ํ”„๋ก ํŠธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (Vercel)

NEXT_PUBLIC_API_BASE_URL=https://api.tripfit.online

์ƒ์„ธ: docs/decisions/002-domain-split-vercel-api.md

๊ตฌ์กฐ

๊ฒฝ๋กœ ์„œ๋ฒ„ ์„ค๋ช…
app/ EC2 A Nginx(:80/443) + Certbot + Spring Boot API
nginx/ EC2 A api.tripfit.online ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ
mysql/ EC2 B MySQL 8.0 ์ „์šฉ
monitoring/ EC2 C Loki(:3100) + Grafana(:3000) โ€” A/B ์ปจํ…Œ์ด๋„ˆ ๋กœ๊ทธ ์ˆ˜์ง‘ยท์กฐํšŒ
redis/ EC2 D Redis(:6379) โ€” refresh token ์ €์žฅ์†Œ + ๊ณตํœด์ผ ์บ์‹œ, sg-app์—์„œ๋งŒ ์ ‘๊ทผ
../docker-compose.yml ๋กœ์ปฌ App build + MySQL (--profile edge ์‹œ API Nginx)

์ƒ์„ธ ๋„คํŠธ์›ŒํฌยทSG: ec2-split-deployment.md

๋น ๋ฅธ ์‹œ์ž‘

๋กœ์ปฌ (MySQL Docker + bootRun)

cp .env.example .env
docker compose up -d              # MySQL๋งŒ
./gradlew bootRun                 # Spring (localhost:8080, .env ๋กœ๋“œ)

Spring๊นŒ์ง€ Docker๋กœ ๊ฒ€์ฆํ•  ๋•Œ๋งŒ:

docker compose --profile app up -d --build
./scripts/verify-deploy.sh

EC2 B โ€” MySQL

cd deploy/mysql
cp .env.example .env
docker compose up -d

EC2 D โ€” Redis (docs/decisions/010-redis-infra.md)

ํ”„๋กœ๋น„์ €๋‹ ์™„๋ฃŒ(2026-08-10) โ€” TP-redis(i-06fb8540484834192), A/B/C์™€ ๋™์ผ VPCยท์„œ๋ธŒ๋„ทยทํ‚คํŽ˜์–ด, t3.micro. Private IP 172.31.38.246, ๋ณด์•ˆ ๊ทธ๋ฃน TP-redis๊ฐ€ 6379๋ฅผ sg-app(TP-server)์—์„œ๋งŒ ํ—ˆ์šฉ. Elastic IP ์—†์Œ โ€” App๋งŒ ์‚ฌ์„ค IP๋กœ ์ ‘๊ทผํ•˜๊ณ  ์™ธ๋ถ€ ๋„๋ฉ”์ธ์ด ํ•„์š” ์—†๋Š” ๋‚ด๋ถ€ ์ธํ”„๋ผ๋ผ EC2 A/C์™€ ๋‹ค๋ฆ„.

cd deploy/redis
cp .env.example .env
docker compose up -d

EC2 A โ€” API + HTTPS (Route 53 api.tripfit.online ์—ฐ๊ฒฐ ํ›„)

์ตœ์ดˆ 1ํšŒ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ „์šฉ โ€” TLS ๋ฐœ๊ธ‰๊นŒ์ง€ ๋๋‚ด๊ณ  ๋‚˜๋ฉด ์ดํ›„ ๋ฐฐํฌ๋Š” main push โ†’ CI/CD๊ฐ€ GitHub Secrets๋งŒ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค (์•„๋ž˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ ˆ). .env๋Š” ์ด ์ตœ์ดˆ ์„ค์ •๊ณผ ์ˆ˜๋™ ์žฌ๋ฐฐํฌ(ec2-deploy-app.sh ๋‹จ๋… ์‹คํ–‰) ๋•Œ๋งŒ ์“ด๋‹ค.

cd deploy/app
cp .env.example .env
# .env: MYSQL_HOST, GHCR_IMAGE
export SPRING_DATASOURCE_USERNAME=...
export SPRING_DATASOURCE_PASSWORD=...
export CERTBOT_EMAIL=codus5068@naver.com
# private GHCR: export GHCR_PAT=... GHCR_USERNAME=...

../../scripts/setup-api-https.sh

๋˜๋Š” ๋‹จ๊ณ„๋ณ„:

../../scripts/ec2-deploy-app.sh          # nginx + certbot + app (์ž„์‹œ self-signed)
../../scripts/init-letsencrypt.sh        # Let's Encrypt ์‹ค์ œ ์ธ์ฆ์„œ
VERIFY_TLS=true ../../scripts/verify-deploy-app.sh

๊ฒ€์ฆ:

curl -fsSI https://api.tripfit.online/health
curl -fsSI https://api.tripfit.online/api/v1/...   # API ๊ตฌํ˜„ ํ›„

์Šคํƒ ๊ตฌ์„ฑ (deploy/app/docker-compose.yml)

์„œ๋น„์Šค ์—ญํ• 
nginx api.tripfit.online :80/:443 โ†’ app:8080
certbot LE ๋ฐœ๊ธ‰ยท12h ๊ฐฑ์‹  ์‹œ๋„
app Spring Boot (GHCR), 127.0.0.1:8080๋งŒ ๋ฐ”์ธ๋”ฉ

cron ๊ฐฑ์‹  (๊ฐฑ์‹  ์‹œ nginx reload ํฌํ•จ):

0 3 * * * cd /path/to/TripFit-server/deploy/app && /path/to/TripFit-server/scripts/renew-letsencrypt.sh

EC2 A โ€” API (HTTPS ์ƒ๋žต, dev๋งŒ)

../../scripts/setup-api-https.sh --skip-tls

EC2 C โ€” ๋ชจ๋‹ˆํ„ฐ๋ง (Loki + Grafana, https://grafana.tripfit.online)

๊ฒฐ์ • ๊ทผ๊ฑฐ: docs/decisions/009-observability-logging.md (Issue #77). A/B์™€ ๊ฐ™์€ VPCยท์„œ๋ธŒ๋„ทยทํ‚คํŽ˜์–ด ์žฌ์‚ฌ์šฉ, ๋ณ„๋„ t3.micro. Elastic IP ๋ณด์œ (EC2 A์™€ ๋™์ผ ์ด์œ  โ€” Grafana URLยทSSH๊ฐ€ ์ธ์Šคํ„ด์Šค ์žฌ์‹œ์ž‘์— ํ”๋“ค๋ฆฌ์ง€ ์•Š๋„๋ก).

cd deploy/monitoring
cp .env.example .env   # GRAFANA_ADMIN_PASSWORD ์ฑ„์šฐ๊ธฐ
docker compose up -d

HTTPS (EC2 A deploy/app์™€ ๋™์ผ ํŒจํ„ด โ€” nginx+certbot, Let's Encrypt)

export DEPLOY_DIR=$(pwd)/deploy/monitoring
export CERTBOT_DOMAIN=grafana.tripfit.online
../../scripts/ensure-ssl-certs.sh   # ์ž„์‹œ self-signed (nginx 443 ๊ธฐ๋™์šฉ)
docker compose up -d
CERTBOT_EMAIL=codus5068@naver.com ../../scripts/init-letsencrypt.sh
  • ์Šคํƒ: nginx(:80/:443) โ†’ grafana(๋‚ด๋ถ€ 127.0.0.1:3000๋งŒ ๋ฐ”์ธ๋”ฉ, ๊ณต๊ฐœ ๋…ธ์ถœ ์—†์Œ), certbot(๋ฐœ๊ธ‰ยท๊ฐฑ์‹ ), loki(:3100, A/B๊ฐ€ pushํ•˜๋Š” ๋Œ€์ƒ โ€” ์ง์ ‘ ๋ธŒ๋ผ์šฐ์ € ์กฐํšŒ ๋Œ€์ƒ ์•„๋‹˜).
  • Route 53: grafana.tripfit.online A โ†’ EC2 C Elastic IP. SG๋Š” 22/80/443๋งŒ ๊ณต๊ฐœ, 3100์€ A/B SG๋กœ๋งŒ ์ œํ•œ.
  • cron ๊ฐฑ์‹ (A์™€ ๋™์ผ ์Šคํฌ๋ฆฝํŠธ, ๋„๋ฉ”์ธ๋งŒ ๋‹ค๋ฆ„): 0 3 * * * DEPLOY_DIR=/home/ubuntu/monitoring CERTBOT_DOMAIN=grafana.tripfit.online /home/ubuntu/scripts/renew-letsencrypt.sh
  • A/B ์ชฝ ์ตœ์ดˆ 1ํšŒ ํ•„์ˆ˜ ์ž‘์—… โ€” Loki๊ฐ€ ๋œจ๊ธฐ ์ „์— AยทB์—์„œ ๊ฐ๊ฐ ์‹คํ–‰ํ•ด์•ผ docker compose up -d๊ฐ€ driver: loki๋กœ ์„ฑ๊ณตํ•œ๋‹ค:
    docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
    (์ด๋ฏธ deploy/app/docker-compose.yml, deploy/mysql/docker-compose.yml ์ƒ๋‹จ ์ฃผ์„์—๋„ ๋™์ผ ์•ˆ๋‚ด๊ฐ€ ์žˆ์Œ.)
  • ๋กœ๊ทธ ๋ณด์กด: deploy/monitoring/loki-config.yaml์˜ retention_period(ํ™•์ • 3์ผ, ๋””์Šคํฌ 6.7GB ์ œ์•ฝ) + compactor๊ฐ€ ์ž๋™ ์‚ญ์ œ โ€” ๋ณ„๋„ cron ๋ถˆํ•„์š”.
  • ํ”„๋ก ํŠธ ๊ณต์œ  ๊ณ„์ •: IP ์ œํ•œ ๋Œ€์‹  Grafana Viewer role ์ฝ๊ธฐ ์ „์šฉ ๊ณ„์ •์„ ๋ฐœ๊ธ‰ํ–ˆ๋‹ค(์žฌํƒยท์นดํŽ˜ ๋“ฑ IP๊ฐ€ ๋งค๋ฒˆ ๋ฐ”๋€Œ๋Š” ํ”„๋ก ํŠธ ํ™˜๊ฒฝ์— IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ๋Š” ๋งž์ง€ ์•Š์Œ). ๊ณ„์ • ์ถ”๊ฐ€ ๋ฐœ๊ธ‰์€ admin์œผ๋กœ ๋กœ๊ทธ์ธ ํ›„ Administration โ†’ Users ๋˜๋Š”:
    curl -u admin:$GRAFANA_ADMIN_PASSWORD -X POST https://grafana.tripfit.online/api/admin/users \
      -H 'Content-Type: application/json' \
      -d '{"name":"<์ด๋ฆ„>","login":"<์•„์ด๋””>","email":"<์•„์ด๋””>@tripfit.local","password":"<๋น„๋ฐ€๋ฒˆํ˜ธ>"}'
    # ๊ธฐ๋ณธ org role์ด Viewer๋ผ ๋ณ„๋„ ์„ค์ • ์—†์ด๋„ ์ฝ๊ธฐ ์ „์šฉ์ด์ง€๋งŒ, ๋ช…์‹œ์ ์œผ๋กœ ํ™•์ธํ•˜๋ ค๋ฉด:
    curl -u admin:$GRAFANA_ADMIN_PASSWORD -X PATCH https://grafana.tripfit.online/api/org/users/<id> \
      -H 'Content-Type: application/json' -d '{"role":"Viewer"}'
  • ๊ธฐ๋ณธ ๋Œ€์‹œ๋ณด๋“œ "TripFit ์„œ๋น„์Šค ๋กœ๊ทธ ๊ฐœ์š”"(uid: tripfit-overview)๊ฐ€ ํ™ˆ ๋Œ€์‹œ๋ณด๋“œ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค โ€” ์—๋Ÿฌ/๊ฒฝ๊ณ  ๊ฑด์ˆ˜, nginx 4xx/5xx, app ์—๋Ÿฌยท๊ฒฝ๊ณ ยท์ „์ฒด ๋กœ๊ทธ ์ŠคํŠธ๋ฆผ, nginx ์˜ค๋ฅ˜ ์‘๋‹ต ๋กœ๊ทธ. Explore ํƒญ์—์„œ ์ง์ ‘ LogQL ์กฐํšŒ๋„ ๊ฐ€๋Šฅ.
  • CI/CD ๋Œ€์ƒ ์•„๋‹˜ โ€” main push ์ž๋™ ๋ฐฐํฌ๋Š” EC2 A(app)๋งŒ ๋‹ค๋ฃฌ๋‹ค. EC2 C๋Š” ์œ„ ๋ช…๋ น์œผ๋กœ ์ˆ˜๋™ ๋ฐฐํฌยท๊ฐฑ์‹ .
  • ์ด ์„œ๋ฒ„๋Š” A/B์™€ ๋‹ฌ๋ฆฌ git ์ฒดํฌ์•„์›ƒ์ด ์•„๋‹ˆ๋ผ ~/monitoringยท~/scripts์— ํŒŒ์ผ์„ ์ง์ ‘ ์˜ฌ๋ฆฐ ์ƒํƒœ โ€” ํ–ฅํ›„ git ์ฒดํฌ์•„์›ƒ์œผ๋กœ ์ „ํ™˜ ๊ถŒ์žฅ(009 ํ›„์† ์ž‘์—…).

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ๋ฐ ๋ฐฐํฌ์— ํ•„์š”ํ•œ GitHub Actions Secrets 32๊ฐœ ์ „์ฒด ๋ชฉ๋ก๊ณผ ๊ธฐ๋ณธ๊ฐ’, ํ•„์ˆ˜ ์—ฌ๋ถ€๋Š” deploy/environment-reference.md์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ๊ด€๋ฆฌ ์›์น™

  • ์ž๋™ ์ฃผ์ž…: main push โ†’ CI/CD deploy ๋‹จ๊ณ„์—์„œ GitHub Secrets๋ฅผ SSH ์„ธ์…˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…๋ฐ›์•„ EC2์—์„œ export ํ›„ docker compose up -d๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ ๋“ฑ๋กํ•ด๋‘๋ฉด ์ดํ›„ ๋ฐฐํฌ๋Š” GitHub Secrets๋งŒ ๊ฐฑ์‹ ํ•˜๋ฉด ๋˜๋ฉฐ, EC2์— SSH๋กœ ์ ‘์†ํ•ด deploy/app/.env๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • Fail-Fast ํ•„์ˆ˜ ๋ณ€์ˆ˜: ํ•„์ˆ˜๋กœ ์ง€์ •๋œ Secret์ด ๋ฏธ๋“ฑ๋ก์ธ ์ฑ„๋กœ push๋˜๋ฉด CI/CD ๋ฐฐํฌ ๋‹จ๊ณ„๊ฐ€ ์ฆ‰์‹œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ๋ณ€์ˆ˜๊ฐ€ ํ•„์ˆ˜์ธ์ง€๋Š” environment-reference.md์˜ ํ‘œ๊ฐ€ SSOT โ€” ๋ชฉ๋ก์„ ์ด ๋ฌธ์„œ์— ์˜ฎ๊ฒจ ์ ์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์–‘์ชฝ์— ๋‘๋ฉด ํ•œ์ชฝ๋งŒ ๊ณ ์ณค์„ ๋•Œ ์–ด๊ธ‹๋‚ฉ๋‹ˆ๋‹ค).
  • Fail-Safe ํ˜ธ์ŠคํŠธ ๋ณ€์ˆ˜: ๋ชจ๋‹ˆํ„ฐ๋ง(EC2 C)ยทRedis(EC2 D) ํ˜ธ์ŠคํŠธ ๋ณ€์ˆ˜๋Š” Secret ๋ฏธ์„ค์ • ์‹œ์—๋„ compose ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ •์ƒ ๊ธฐ๋™๋˜๋„๋ก ์ฒ˜๋ฆฌ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์„ค IP๊ฐ€ ๋ฐ”๋€Œ๋ฉด GitHub Secret๊ณผ compose ๊ธฐ๋ณธ๊ฐ’์„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค โ€” ๋ณ€์ˆ˜๋ช…ยทํ˜„์žฌ ๊ธฐ๋ณธ๊ฐ’์€ environment-reference.md ์ฐธ์กฐ.
  • FRONTEND_IMAGE ์‚ฌ์šฉ ๊ธˆ์ง€: FRONTEND_IMAGE ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ โ€” ํ”„๋ก ํŠธ๋Š” Vercel. EC2์— ํ”„๋ก ํŠธ์—”๋“œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ˆ˜๋™ ๋ฐฐํฌ ๋ฐ ๋กœ์ปฌ: scripts/ec2-deploy-app.sh ์ˆ˜๋™ ์‹คํ–‰์ด๋‚˜ ๋กœ์ปฌ docker compose ์‹œ์—๋Š” .env ๋˜๋Š” export๋กœ ๊ฐ’์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค (deploy/app/.env.example ๋ฐ ๋ฃจํŠธ .env ์ฐธ๊ณ ).
  • ์ „์ฒด ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ‘œ ๋ฐ ์ƒ์„ธ ์„ค๋ช…: deploy/environment-reference.md ์ฐธ์กฐ

์Šคํ† ์–ด ์ œ์ถœ ์ „ OAuth ์ฝ˜์†” ์„ค์ • ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์œ„ GOOGLE_CLIENT_ID๋ฅ˜ยทKAKAO_ADMIN_KEY๋Š” env(GitHub Secrets)์— ๊ฐ’๋งŒ ๋“ฑ๋กํ•˜๋ฉด ๋˜์ง€๋งŒ, ๊ฐ ์†Œ์…œ ๋กœ๊ทธ์ธ ์ฝ˜์†”(Google Cloud ConsoleยทApple DeveloperยทKakao Developers ๋“ฑ) ์ชฝ ์„ค์ •์€ ๋ณ„๋„๋กœ ์ฑ„์›Œ์•ผ ์‹ค์ œ ๋กœ๊ทธ์ธ์ด ๋™์ž‘ํ•œ๋‹ค. ์ฝ”๋“œยท๊ฒ€์ฆ ๋กœ์ง(GoogleTokenVerifierยทAppleTokenVerifierยทKakaoTokenVerifierยทOAuthProperties)์€ ์ด๋ฏธ ์™„๋ฃŒ๋œ ์ƒํƒœ โ€” ์•„๋ž˜๋Š” ์ฝ˜์†” ๋‹ด๋‹น์ž๊ฐ€ ์ฝ˜์†” ํ™”๋ฉด์—์„œ ์ง์ ‘ ๋“ฑ๋กํ•˜๋ฉด ๋˜๋Š” ํ•ญ๋ชฉ๋ฟ์ด๋‹ค.

๋ฏธ๋“ฑ๋ก ์ƒํƒœ๋กœ๋Š” redirect_uri_mismatchยทDEVELOPER_ERROR ๋“ฑ์œผ๋กœ ๋กœ๊ทธ์ธ ์ž์ฒด๊ฐ€ ์‹คํŒจํ•˜๋ฏ€๋กœ ์Šคํ† ์–ด ์‹ฌ์‚ฌ ์ œ์ถœ ์ „ ๋ฐ˜๋“œ์‹œ ํ™•์ธ. (๊ณผ๊ฑฐ GitHub Issue #62๋กœ ์ถ”์ ํ•˜๋˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์˜€์œผ๋‚˜ 2026-08-02๋ถ€๋กœ ์ด์Šˆ๋ฅผ ๋‹ซ๊ณ  ์ด ๋ฌธ์„œ๋ฅผ SSOT๋กœ ํ†ตํ•ฉํ–ˆ๋‹ค โ€” ์•„๋ž˜๊ฐ€ ์ตœ์‹  ์ƒํƒœ.)

๋‹ด๋‹น ๊ตฌ๋ถ„:

๋‹ด๋‹น ํ•„์š”ํ•œ ๊ฒƒ
๋ฐฑ์—”๋“œ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์—†์Œ โ€” ๊ฐ ์ฝ˜์†”์— ์‹ค์ œ ๊ฐ’ ์ž…๋ ฅ๋งŒ(๊ณ„์ • ์ ‘๊ทผ ๊ถŒํ•œ ํ•„์š”)
ํ”„๋ก ํŠธ ์ตœ์ข… ์„œ๋น„์Šค ๋„๋ฉ”์ธยท์ฝœ๋ฐฑ ๋ผ์šฐํŠธ(๋ฆฌ๋‹ค์ด๋ ‰์…˜ URI๋กœ ๋“ฑ๋ก๋  ๊ฒฝ๋กœ) ํ™•์ •ํ•ด์„œ ์ „๋‹ฌ โ€” Google/Kakao Redirect URIยท๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ํ‚คยทํŒจํ‚ค์ง€๋ช…์€ ์ด๋ฏธ ์ฝ”๋“œ ํ™•์ธ์œผ๋กœ ์ „๋‹ฌ ์™„๋ฃŒ
๋นŒ๋“œ ํ™˜๊ฒฝ ๋‹ด๋‹น์ž Android ๋””๋ฒ„๊ทธยท๋ฆด๋ฆฌ์ฆˆ keystore์˜ SHA-1(Google)ยทํ‚ค ํ•ด์‹œ(Kakao) ์ถ”์ถœ โ€” ๋ฆฌํฌ์— keystore๊ฐ€ ์—†์–ด ์ฝ˜์†” ๋‹ด๋‹น์ž๊ฐ€ ๋Œ€์‹ ํ•  ์ˆ˜ ์—†์Œ
Apple/Google ๊ณ„์ • ๋‹ด๋‹น์ž App Store ID๋Š” App Store Connect ์•ฑ ๋“ฑ๋ก ์™„๋ฃŒ ํ›„์—๋งŒ ๋ฐœ๊ธ‰ โ€” ๊ฒŒ์‹œ ๋‹ด๋‹น์ž๊ฐ€ ๊ฐ’ ์ „๋‹ฌ

Google Cloud Console

๊ธฐ์กด ๋กœ๊ทธ์ธ์šฉ GCP ํ”„๋กœ์ ํŠธ์˜ APIs & Services โ†’ Credentials์—์„œ ์•„๋ž˜ 3๊ฐœ Client(์ด๋ฏธ ๋ฐœ๊ธ‰๋ผ ์žˆ์Œ)๋ฅผ ๊ฐ๊ฐ ์—ฐ๋‹ค.

Client ํƒ€์ž… ๋Œ€์‘ env ์ง€๊ธˆ ๋“ฑ๋กํ•  ๊ฒƒ ์‹œ์ 
Web application GOOGLE_CLIENT_ID ์Šน์ธ๋œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์›๋ณธ(https://tripfit.online) + ์Šน์ธ๋œ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ URI(ํ”„๋ก ํŠธ ์ฝœ๋ฐฑ ๊ฒฝ๋กœ) ํ”„๋ก ํŠธ ์ตœ์ข… ๋„๋ฉ”์ธยท์ฝœ๋ฐฑ ๋ผ์šฐํŠธ ํ™•์ • ํ›„
Android GOOGLE_CLIENT_ID_ANDROID ํŒจํ‚ค์ง€๋ช… com.tripfit.app + SHA-1 ์ธ์ฆ์„œ ์ง€๋ฌธ(๋””๋ฒ„๊ทธยท๋ฆด๋ฆฌ์ฆˆ ๊ฐ๊ฐ) SHA-1 ํ™•๋ณด ์ฆ‰์‹œ
iOS GOOGLE_CLIENT_ID_IOS ๋ฒˆ๋“ค ID com.tripfit.app + App Store ID(์„ ํƒ ํ•„๋“œ) ์•ฑ App Store ๊ฒŒ์‹œ ํ›„

SHA-1 ํ™•๋ณด ๋ฐฉ๋ฒ• โ€” ๋ฆฌํฌ์— keystore๊ฐ€ ์—†์–ด ์ฝ˜์†” ๋‹ด๋‹น์ž๊ฐ€ ์ง์ ‘ ๋ฝ‘์„ ์ˆ˜ ์—†๋‹ค. ๋นŒ๋“œ ํ™˜๊ฒฝ(Android Studio/CI) ๋‹ด๋‹น์ž์—๊ฒŒ ์•„๋ž˜ ๋ช…๋ น ์‹คํ–‰์„ ์š”์ฒญ:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android | grep SHA1

๋ฆด๋ฆฌ์ฆˆ keystore๋Š” ๋ณ„๋„ ๋ณด์œ ์ž๊ฐ€ ๋™์ผ ๋ช…๋ น์œผ๋กœ ์ถ”์ถœ(Google Play App Signing์„ ์“ฐ๋ฉด Play Console์˜ "์•ฑ ๋ฌด๊ฒฐ์„ฑ โ†’ ์•ฑ ์„œ๋ช…" ๋ฉ”๋‰ด์—์„œ๋„ ํ™•์ธ ๊ฐ€๋Šฅ).

๊ฐ’ ์ผ์น˜ ํ™•์ธ: apps/app(๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ)์˜ GOOGLE_WEB_CLIENT_ID, apps/web์˜ NEXT_PUBLIC_GOOGLE_CLIENT_ID, ๋ฐฑ์—”๋“œ GOOGLE_CLIENT_ID ์„ธ ๊ฐ’์ด ์ „๋ถ€ ๊ฐ™์€ Web Client ID๋ฅผ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•œ๋‹ค โ€” ํ•˜๋‚˜๋ผ๋„ ๋‹ค๋ฅด๋ฉด ๊ทธ ๊ฒฝ๋กœ๋งŒ aud mismatch๋กœ ๋กœ๊ทธ์ธ์ด ์‹คํŒจํ•œ๋‹ค.

Calendar ์ „์šฉ Client(GOOGLE_CALENDAR_CLIENT_ID) ๋ฐœ๊ธ‰์€ ์ง€๊ธˆ ์•ˆ ํ•ด๋„ ๋œ๋‹ค โ€” Calendar FE ์ฐฉ์ˆ˜ ์‹œ์ ์— google-calendar-client-id-separation.md "GCP ์ฝ˜์†” ๊ฐ€์ด๋“œ" ์ ˆ ์ฐธ๊ณ .

Kakao Developers Console

์•ฑ ๋Œ€์‹œ๋ณด๋“œ(developers.kakao.com โ†’ ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ โ†’ ํ•ด๋‹น ์•ฑ)์—์„œ:

๋ฉ”๋‰ด ๋“ฑ๋กํ•  ๊ฒƒ
์•ฑ ์„ค์ • โ†’ ํ”Œ๋žซํผ Android ํŒจํ‚ค์ง€๋ช… com.tripfit.app + ํ‚ค ํ•ด์‹œ(์•„๋ž˜ ๋ช…๋ น), iOS ๋ฒˆ๋“ค ID com.tripfit.app, ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ ํ‚ค๊ฐ€ 6a686bacf5c3efba84a1cc8d0e655cd5์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
์นด์นด์˜ค ๋กœ๊ทธ์ธ โ†’ ํ™œ์„ฑํ™” ์„ค์ • ON
์นด์นด์˜ค ๋กœ๊ทธ์ธ โ†’ Redirect URI https://tripfit.online/auth/kakao/callback(์šด์˜) + http://localhost:3000/auth/kakao/callback(๋กœ์ปฌ) โ€” ํ™˜๊ฒฝ B(๋ชจ๋ฐ”์ผ ์›น/๋ฐ์Šคํฌํ†ฑ ๋ธŒ๋ผ์šฐ์ €) JS SDK ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ”Œ๋กœ์šฐ์šฉ
์นด์นด์˜ค ๋กœ๊ทธ์ธ โ†’ ๋™์˜ํ•ญ๋ชฉ ๋‹‰๋„ค์ž„ยทํ”„๋กœํ•„ ์‚ฌ์ง„ ํ™œ์„ฑํ™”(์„ ํƒ ๋™์˜๋กœ ์ถฉ๋ถ„), ์ด๋ฉ”์ผ์€ ํ•„์š” ์‹œ ํ™œ์„ฑํ™” โ€” ๊บผ์ ธ ์žˆ์œผ๋ฉด ๋ฐฑ์—”๋“œ๋Š” ํ•ด๋‹น ํ•„๋“œ๋ฅผ null๋กœ ๋ฐ›์Œ(๋กœ๊ทธ์ธ ์ž์ฒด๋Š” ์•ˆ ๊นจ์ง)
๋ณด์•ˆ โ†’ Admin Key ์ด๋ฏธ ๋ฐœ๊ธ‰ ์™„๋ฃŒ ์ƒํƒœ โ€” KAKAO_ADMIN_KEY GitHub Secret์— ๋“ฑ๋ก๋ผ ์žˆ๋Š”์ง€๋งŒ ์žฌํ™•์ธ

ํ‚ค ํ•ด์‹œ ํ™•๋ณด ๋ฐฉ๋ฒ• (Google SHA-1๊ณผ ๊ฐ™์€ keystore ํ•„์š”):

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android | openssl sha1 -binary | openssl base64

๋ฆด๋ฆฌ์ฆˆ์šฉ์€ ๋ฆด๋ฆฌ์ฆˆ keystore๋กœ ๋™์ผ ๋ช…๋ น์„ ์‹คํ–‰ํ•ด ๋ณ„๋„ ๋“ฑ๋ก.

CI/CD

.github/workflows/ci-cd.yml โ€” main push โ†’ GHCR push โ†’ EC2 A deploy (app + nginx + certbot) deploy ์‹œ ์œ„ GitHub Secrets ์ „๋ถ€๊ฐ€ app ์ปจํ…Œ์ด๋„ˆ์— ์ž๋™ ์ฃผ์ž…๋œ๋‹ค โ€” EC2 ์ชฝ .env ์œ ๋ฌด์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. EC2 B(MySQL)ยทEC2 C(๋ชจ๋‹ˆํ„ฐ๋ง)๋Š” ์ด ํŒŒ์ดํ”„๋ผ์ธ ๋ฐ– โ€” ์ตœ์ดˆ ์„ค์ •ยท์žฌ๋ฐฐํฌ ๋ชจ๋‘ ์ˆ˜๋™(deploy/mysql/, deploy/monitoring/ ์ ˆ ์ฐธ๊ณ ).

๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ

์Šคํฌ๋ฆฝํŠธ ์‚ฌ์šฉ ์‹œ์ 
scripts/verify-deploy.sh ๋กœ์ปฌ compose
scripts/setup-api-https.sh ๋ฐฐํฌ + LE ๋ฐœ๊ธ‰ ์ผ๊ด„ (๊ถŒ์žฅ)
scripts/ec2-deploy-app.sh pull + up (์ž„์‹œ self-signed)
scripts/init-letsencrypt.sh LE ์ตœ์ดˆ ๋ฐœ๊ธ‰ (api.tripfit.online)
scripts/renew-letsencrypt.sh ๊ฐฑ์‹  + nginx reload (cron)
scripts/verify-deploy-app.sh EC2 A ๊ฒ€์ฆ (VERIFY_TLS=true)