์ธ ์ ์ฅ์(์ฌ์ฉ์ API ยท ๊ด๋ฆฌ์ ์ฑ ยท ์ฌ์ฉ์ SPA)๋ฅผ VM ํ ๋์ Docker Compose ๋ก ์ฌ๋ฆฌ๋ ๋ฐฉ๋ฒ์
๋๋ค.
๋๋ฉ์ธ ์์ด ๊ณต์ธ IP ๊ธฐ๋ฐ sslip.io ์ด๋ฆ๊ณผ Let's Encrypt HTTPS ๋ฅผ ์๋๋ค.
| ํญ๋ชฉ | ๊ฐ |
|---|---|
| ์๋ฒ | Oracle Cloud Always Free ยท Ampere A1 (aarch64) ยท 1 OCPU / 6GB (์ต๋ 4 OCPU / 24GB ๊น์ง ๋ฌด๋ฃ๋ก ๋๋ฆด ์ ์์) |
| OS | Ubuntu 26.04 Minimal (aarch64) ยท ๋ฆฌ์ : Tokyo |
| ์ฌ์ฉ์ ์ฌ์ดํธ | https://<IP-ํ์ดํ>.sslip.io (์: https://152-70-12-34.sslip.io) |
| ๊ด๋ฆฌ์ ์ฌ์ดํธ | https://admin.<IP-ํ์ดํ>.sslip.io |
| ๋ฐฐํฌ ํ์ผ | ์ด ์ ์ฅ์์ deploy/ |
flowchart LR
B["๋ธ๋ผ์ฐ์ "] -- "HTTPS 443" --> C
subgraph VM["OCI VM (Docker Compose)"]
C["caddy<br/>์๋ HTTPS ยท SPA ์ ์ ํ์ผ(/srv)"]
U["user-api :8080<br/>Spring Boot ยท Flyway"]
H["host-admin :8081<br/>Spring Boot ยท Thymeleaf"]
M[("mysql 8.0<br/>๋ณผ๋ฅจ mysql-data")]
R[("redis 7<br/>์์ํ ์์")]
V[("๋ณผ๋ฅจ uploads<br/>/app/uploads")]
C -- "<๋๋ฉ์ธ>/api/*, /uploads/*,<br/>/oauth2/*, /login/oauth2/*" --> U
C -- "admin.<๋๋ฉ์ธ>" --> H
U --> M & R & V
H --> M & R & V
end
- ๊ฐ์ Origin: SPA ์ API ๊ฐ
https://<๋๋ฉ์ธ>ํ๋์ ์์ต๋๋ค. SPA ๋VITE_BACKEND_URL์ ๋น์ด ์ฑ ๋น๋๋์ด/api/...์๋ ๊ฒฝ๋ก๋ก ์์ฒญํ๊ณ Caddy ๊ฐuser-api๋ก ๋๊น๋๋ค โ CORSยทSameSite ๋ฌธ์ ์์ (๋ฆฌํ๋ ์ ํ ํฐ ์ฟ ํคSecure; HttpOnly; SameSite=Strict). - SPA ๋ผ์ฐํ
: ํ์ผ์ด ์๋ ๊ฒฝ๋ก(
/reserve,/user/mypage/...)๋index.html๋ก ๋์ฒด(try_files)./login/google/callback๊ฐ์ ์์ ์ฝ๋ฐฑ๋ SPA ๋ผ์ฐํธ์ ๋๋ค. - ์คํค๋ง:
user-api๊ฐ ๊ธฐ๋ํ๋ฉฐ Flyway V1~์ต์ ์ ์ ์ฉํฉ๋๋ค.host-admin์ddl-auto=validate๋ผ์user-api๊ฐ healthy ๊ฐ ๋ ๋ค์ ๋น๋๋ค(depends_on: service_healthy). - ์
๋ก๋ ์ด๋ฏธ์ง: ๋ ์ฑ์ด ๊ฐ์
uploads๋ณผ๋ฅจ์ ์๋๋ค. ๊ด๋ฆฌ์๊ฐ ์ฌ๋ฆฐ ์์ ์ด๋ฏธ์ง(/uploads/...)๊ฐ ์ฌ์ฉ์ ์ฌ์ดํธ์๋ ๊ฐ์ ๊ฒฝ๋ก๋ก ๋ณด์ ๋๋ค. - ํฌํธ: ์ธ๋ถ์๋ Caddy ์ 80/443 ๋ง ์ด๋ฆฝ๋๋ค. MySQLยทRedisยท๋ ์ฑ์ Docker ๋ด๋ถ ๋คํธ์ํฌ์๋ง ์์ต๋๋ค.
- ์ด์ ํ๋กํ: ๋ ๋ฐฑ์๋ ๋ชจ๋
application-prod.properties(๋น๋ฐ๊ฐ ์์, ์ ๋ถ ํ๊ฒฝ๋ณ์). ๋ฉ์ผยท๊ฒฐ์ ยท์์ ๋ก๊ทธ์ธยท๋ ์จ ํค๊ฐ ์์ด๋ ๊ธฐ๋์ ์ฑ๊ณตํ๊ณ ๊ทธ ๊ธฐ๋ฅ๋ง ๋์ํ์ง ์์ต๋๋ค.
๋ฉ๋ชจ๋ฆฌ ์์ฐ(6GB ๊ธฐ์ค, deploy/.env ์์ ์กฐ์ ): MySQL 768MB ยท Redis 160MB ยท user-api 1GB ยท host-admin 900MB ยท Caddy 192MB โ 3GB.
์ค์ธก(์๋ ํ ์ ํด): ์ฝ 1.1GB. ๋๋จธ์ง๋ OSยท์ด๋ฏธ์ง ๋น๋์ฉ์ด๋ฉฐ, setup.sh ๊ฐ 4GB ์ค์์ ์ถ๊ฐํฉ๋๋ค.
| ํ์ผ | ์ญํ |
|---|---|
deploy/docker-compose.yml |
mysql ยท redis ยท user-api ยท host-admin ยท caddy, ํฌ์ค ์ฒดํฌยท๊ธฐ๋ ์์ยท๋ฉ๋ชจ๋ฆฌ ์ํ |
deploy/Caddyfile |
์ฌ์ฉ์/๊ด๋ฆฌ์ ํธ์คํธ ๋ผ์ฐํ , ์๋ HTTPS, SPA ๋์ฒด, ์บ์ ํค๋ |
deploy/caddy/Dockerfile |
Node 20 ์ผ๋ก SPA ๋น๋ โ caddy:2-alpine ์ /srv (VITE_E2E ์ฝ๋๊ฐ ์์ด๋ฉด ๋น๋ ์คํจ) |
deploy/.env.example |
๋ชจ๋ ํ๊ฒฝ๋ณ์ ์ค๋ช ([ํ์]/[๋น๋ฐ]/[์ ํ]/[๋น๋] ํ๊ธฐ) |
deploy/setup.sh |
์ VM ์ค์น: ํจํค์งยท์ค์ยท๋ฐฉํ๋ฒฝยทDockerยทํด๋ก ยท.env ์์ฑยท๋น๋ยท๊ธฐ๋ยท์๋ยทcron |
deploy/seed.sh |
์ด๊ธฐ ๋ฐ์ดํฐ (๊ธฐ์ด ๋ฐ์ดํฐ๋ ํ ๋ฒ๋ง, ๋๋จธ์ง๋ ๋ฐ๋ณต ์คํ ์์ ) |
deploy/update.sh |
๋ฐฑ์
โ git pull โ ๋น๋ โ ์ฌ๊ธฐ๋ โ ์ด๋ฏธ์ง ์ ๋ฆฌ |
deploy/rollback.sh |
์ง์ ์ด๋ฏธ์ง(:previous)๋ก ๋๋๋ฆฌ๊ธฐ |
deploy/backup.sh |
MySQL ๋คํ + uploads โ /opt/honeyrest/backups (์ต๊ทผ 7๊ฐ) |
Dockerfile (๊ฐ ๋ฐฑ์๋ ์ ์ฅ์ ๋ฃจํธ) |
JDK 17 ๋น๋ โ JRE 17 ์คํ, ๋น๋ฃจํธ ์ฌ์ฉ์, arm64 ์ง์ |
-
์ธ์คํด์ค ์์ฑ: Compute โ Instances โ Create
- Image: Canonical Ubuntu 26.04 Minimal (aarch64)
- Shape: VM.Standard.A1.Flex, 1 OCPU / 6GB (๋์ค์ 4 / 24 ๊น์ง ๋๋ฆด ์ ์์)
- ๋ถํธ ๋ณผ๋ฅจ ๊ธฐ๋ณธ๊ฐ(47GB)์ผ๋ก ์ถฉ๋ถํฉ๋๋ค. SSH ๊ณต๊ฐ ํค๋ฅผ ๋ฑ๋กํฉ๋๋ค.
-
๊ณต์ธ IP ๊ณ ์ (๊ถ์ฅ): sslip.io ์ฃผ์๊ฐ IP ์์ ๋ง๋ค์ด์ง๋ฏ๋ก IP ๊ฐ ๋ฐ๋๋ฉด ์ฃผ์๋ ๋ฐ๋๋๋ค. ์ธ์คํด์ค โ Attached VNICs โ IPv4 Addresses โ ๊ณต์ธ IP ๋ฅผ Reserved public IP ๋ก ๋ฐ๊ฟ ๋ก๋๋ค.
-
์ธ๊ทธ๋ ์ค ๊ท์น (์ง์ ํด์ผ ํจ): Networking โ Virtual Cloud Networks โ (VCN) โ Subnet โ Security List โ Add Ingress Rules
Source CIDR IP Protocol Destination Port 0.0.0.0/0TCP 800.0.0.0/0TCP 443(๋คํธ์ํฌ ๋ณด์ ๊ทธ๋ฃน(NSG)์ ์ฐ๊ณ ์๋ค๋ฉด NSG ์ ๊ฐ์ ๊ท์น์ ๋ฃ์ต๋๋ค.) 80 ์ Let's Encrypt ์ธ์ฆ(HTTP-01)๊ณผ HTTPโHTTPS ๋ฆฌ๋ค์ด๋ ํธ์ ํ์ํฉ๋๋ค. OS ์์ ๋ฐฉํ๋ฒฝ(iptables)์
setup.sh๊ฐ ์ฝ๋๋ค.
# ๋ก์ปฌ PC ์์
ssh -i ~/.ssh/<ํคํ์ผ> ubuntu@<๊ณต์ธIP>
# ์๋ฒ์์
sudo apt-get update && sudo apt-get install -y git
sudo mkdir -p /opt/honeyrest && sudo chown "$USER:$USER" /opt/honeyrest
git clone https://github.com/Seongwonp/honeyRest_user.git /opt/honeyrest/honeyRest_user
sudo /opt/honeyrest/honeyRest_user/deploy/setup.shsetup.sh ๊ฐ ํ๋ ์ผ (๋ค์ ์คํํด๋ ์์ ํฉ๋๋ค):
- ๊ธฐ๋ณธ ํจํค์ง(curlยทgitยทopensslยทcron), ์๊ฐ๋
Asia/Seoul - ์ค์ 4GB (
SWAP_SIZE=2G์ฒ๋ผ ๋ฐ๊ฟ ์ ์์),vm.swappiness=10 - OS ๋ฐฉํ๋ฒฝ TCP 80/443 ํ์ฉ โ Oracle ์ด๋ฏธ์ง์
iptables ... REJECT์์ ๊ท์น์ ๋ฃ๊ณ/etc/iptables/rules.v4์๋ ๋ฐ์. ufw ๊ฐ ์ผ์ ธ ์์ผ๋ฉด ufw ๋ก ์ฒ๋ฆฌ - Docker Engine + compose/buildx ํ๋ฌ๊ทธ์ธ (๊ณต์ ์ ์ฅ์, arm64)
/opt/honeyrest์๋์honeyRest_host(์๋ธ๋ชจ๋ ํฌํจ)ยทhoneyrest_user_reactํด๋กdeploy/.env์์ฑ โ DBยทJWTยท๋ฐ๋ชจ ๊ณ์ ๋น๋ฐ๋ฒํธ๋ฅผopenssl rand๋ก ๋ง๋ค๊ณ , ๊ณต์ธ IP ๋กSITE_DOMAIN=<IP-ํ์ดํ>.sslip.io์ค์ (๊ถํ 600)- ์ด๋ฏธ์ง ๋น๋(์๋น์ค๋ณ ์์ฐจ) โ
docker compose up -dโ healthy ๋๊ธฐ ์ฒซ ๋น๋๋ 1 OCPU ์์ 15~30๋ถ ๊ฑธ๋ฆด ์ ์์ต๋๋ค (Gradle 2ํ + npm). seed.shโ ์์ 33๊ฐยท๊ฐ์ค 101๊ฐยท์ง์ญยทํ๊ทธยท๋ฐ๋ชจ ๋ฆฌ๋ทฐ, ์ด๋ฏธ์ง ํ๋ ์ด์คํ๋- cron
/etc/cron.d/honeyrestโ 5๋ถ๋ง๋ค ํฌ์ค ์ฒดํฌ, ๋งค์ผ 04:30 ๋ฐฑ์
๋๋๋ฉด ์ฃผ์์ ๊ด๋ฆฌ์ ๋ฐ๋ชจ ๊ณ์ ์ด ์ถ๋ ฅ๋ฉ๋๋ค.
์ฌ์ฉ์ ์ฌ์ดํธ : https://152-70-12-34.sslip.io
๊ด๋ฆฌ์ ์ฌ์ดํธ : https://admin.152-70-12-34.sslip.io
์
์ฒด ๊ด๋ฆฌ์ : contact@honeyrest.com / <DEMO_COMPANY_PASSWORD>
์ด๊ด๋ฆฌ์ : admin@honeyrest.com / <DEMO_ADMIN_PASSWORD>
ํ์ธ:
cd /opt/honeyrest/honeyRest_user/deploy
sudo docker compose ps # 5๊ฐ ์๋น์ค Up, user-api/host-admin (healthy)
curl -I https://$(grep ^SITE_DOMAIN= .env | cut -d= -f2)/์ฒซ HTTPS ์ ์ ์ง์ ์ Caddy ๊ฐ ์ธ์ฆ์๋ฅผ ๋ฐ๊ธํฉ๋๋ค(์์ญ ์ด). ๋ธ๋ผ์ฐ์ ์์ ์ธ์ฆ์ ์ค๋ฅ๊ฐ ๋๋ฉด 1๋ถ์ฏค ๋ค ๋ค์ ์ด์ด ๋ณด๊ณ , ๊ณ์๋๋ฉด ๋ฌธ์ ํด๊ฒฐ์ HTTPS ํญ๋ชฉ์ ๋ด ๋๋ค.
setup.sh์งํdocker๋ฅผ sudo ์์ด ์ฐ๋ ค๋ฉด SSH ์ ๋ค์ ๋ก๊ทธ์ธํฉ๋๋ค(docker ๊ทธ๋ฃน ๋ฐ์).
deploy/.env ๋ฅผ ํธ์งํ ๋ค ๋ฐ์ํฉ๋๋ค.
cd /opt/honeyrest/honeyRest_user/deploy
nano .env
sudo ./update.sh --no-pull # [๋น๋] ๊ฐ(SITE_DOMAINยทTOSS_CLIENT_KEYยท์์
IDยท์ง๋ ํค)์ด ๋ฐ๋์์ผ๋ฉด SPA ๋ฅผ ๋ค์ ๋น๋| ๊ธฐ๋ฅ | ์ค์ | ๋น์ ๋๋ฉด |
|---|---|---|
| ๊ฒฐ์ | TOSS_CLIENT_KEY(test_gck_...), TOSS_SECRET_KEY(test_gsk_...) โ ํ ์คํ์ด๋จผ์ธ ๊ฐ๋ฐ์์ผํฐ์ ํ
์คํธ ํค |
๊ฒฐ์ ์น์ธ๋ง ์คํจ |
| ๋ฉ์ผ | MAIL_USERNAME(Gmail ์ฃผ์), MAIL_PASSWORD(Google ๊ณ์ โ ๋ณด์ โ 2๋จ๊ณ ์ธ์ฆ โ ์ฑ ๋น๋ฐ๋ฒํธ 16์๋ฆฌ) |
๋ฉ์ผ ๋ฐ์ก ์๋ต(no-op). ํ์๊ฐ์ ์ธ์ฆ ๋ฉ์ผ์ด ๊ฐ์ง ์์ ์ผ๋ฐ ๊ฐ์ ์๊ฐ ๋ก๊ทธ์ธํ ์ ์์ โ ์๋ ๋ฌธ์ ํด๊ฒฐ ์ฐธ๊ณ |
| ์นด์นด์ค ๋ก๊ทธ์ธ | KAKAO_CLIENT_ID(REST API ํค), KAKAO_CLIENT_SECRET ยท ์นด์นด์ค ๊ฐ๋ฐ์ ์ฝ์ Redirect URI: https://<๋๋ฉ์ธ>/login/kakao/callback |
์์ ๋ก๊ทธ์ธ๋ง ์คํจ |
| ๊ตฌ๊ธ ๋ก๊ทธ์ธ | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET ยท ์น์ธ๋ ๋ฆฌ๋๋ ์
URI: https://<๋๋ฉ์ธ>/login/google/callback (Google ์ sslip.io ๊ฐ์ ๊ณต์ฉ ์ ๋ฏธ์ฌ ๋๋ฉ์ธ์ ํ์ฉํ์ง ์์ ์ ์์ด ์ค์ ๋๋ฉ์ธ ๊ถ์ฅ) |
์์ ๋ก๊ทธ์ธ๋ง ์คํจ |
| ๋ ์จ | WEATHER_API_KEY (OpenWeather) |
๋ ์จ ์์ ฏ๋ง ์คํจ |
| ์ง๋ / ๊ณตํด์ผ | GOOGLE_MAPS_API_KEY, GOOGLE_MAP_ID, HOLIDAY_API_KEY |
์ง๋ยท๊ณตํด์ผ ํ์๋ง ์ ๋จ |
๋ฐ๋ชจ ๊ณ์ ์ด ํ์ ์์ผ๋ฉด HOST_SPRING_PROFILES=prod ๋ก ๋ฐ๊ฟ๋๋ค. ๋ฐ๋ชจ ๋น๋ฐ๋ฒํธ๋ ๊ณ์ ์ด ์ฒ์ ๋ง๋ค์ด์ง ๋๋ง ์ฐ์
๋๋ค(์ดํ .env ๋ฅผ ๋ฐ๊ฟ๋ ๊ธฐ์กด ๋น๋ฐ๋ฒํธ ์ ์ง).
๋ชจ๋ ๋ช
๋ น์ /opt/honeyrest/honeyRest_user/deploy ์์ ์คํํฉ๋๋ค (docker ๊ทธ๋ฃน์ด ์๋๋ฉด ์์ sudo).
docker compose ps # ์ํยทํฌ์ค
docker compose logs -f --tail=200 user-api # ์ฌ์ฉ์ API (Flyway, ์์ธ)
docker compose logs -f --tail=200 host-admin # ๊ด๋ฆฌ์ ์ฑ
docker compose logs -f --tail=100 caddy # ์ ์ ๋ก๊ทธยท์ธ์ฆ์ ๋ฐ๊ธ
docker compose logs --since 1h mysql
docker stats --no-stream # ์ปจํ
์ด๋๋ณ ๋ฉ๋ชจ๋ฆฌ
free -h && swapon --show # ์๋ฒ ๋ฉ๋ชจ๋ฆฌยท์ค์
journalctl -t honeyrest-health --since today # cron ํฌ์ค ์ฒดํฌ ์คํจ ๊ธฐ๋ก
tail -n 50 /var/log/honeyrest-backup.log # ๋ฐฑ์
๊ธฐ๋ก์ปจํ ์ด๋ ๋ก๊ทธ๋ ์๋น์ค๋ง๋ค 10MB ร 3๊ฐ๋ก ํ์ ํฉ๋๋ค.
์ฌ์์: docker compose restart user-api ยท ์ ์ฒด ์ค์ง/์์: docker compose stop / docker compose up -d
docker compose down -v๋ ์ ๋ ์ฐ์ง ๋ง์ธ์.-v๋ DB(mysql-data)ยท์ ๋ก๋ยท์ธ์ฆ์(caddy-data) ๋ณผ๋ฅจ๊น์ง ์ง์๋๋ค. ์ธ์ฆ์๋ฅผ ์ง์ฐ๊ณ ์ฌ๋ฐ๊ธ์ ๋ฐ๋ณตํ๋ฉด Let's Encrypt ๋ฐ๊ธ ํ๋์ ๊ฑธ๋ฆฝ๋๋ค.
DB ์ฝ์: docker compose exec mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"'
sudo /opt/honeyrest/honeyRest_user/deploy/update.shbackup.sh๋ก DBยท์ ๋ก๋ ๋ฐฑ์ (์คํจํ๋ฉด ์ค๋จ)- ์ธ ์ ์ฅ์
git pull --ff-only, ๊ด๋ฆฌ์ ์ ์ฅ์ ์๋ธ๋ชจ๋์ ๊ณ ์ ์ปค๋ฐ์ผ๋ก ๋ง์ถค(submodule update --init --recursive) user-apiโhost-adminโcaddy์์๋ก ํ๋์ฉ ๋น๋. BuildKit ์บ์ ๋๋ถ์ ๋ฐ๋์ง ์์ ์๋น์ค๋ ๋ช ์ด ๋ง์ ๋๋๊ณ ์ด๋ฏธ์ง๋ ๊ทธ๋๋ก์ ๋๋ค- ์ด๋ฏธ์ง๊ฐ ๋ฐ๋ ์๋น์ค๋ ์ง์ ์ด๋ฏธ์ง๋ฅผ
:previous๋ก ๋จ๊น โdocker compose up -d๊ฐ ๋ฐ๋ ์ปจํ ์ด๋๋ง ๋ค์ ๋ง๋ฆ โ healthy ๋๊ธฐ Caddyfile์ด ๋ฐ๋์์ผ๋ฉด caddy ์ปจํ ์ด๋๋ง ๋ค์ ๋ง๋ฆ (์ธ์ฆ์ ์ ์ง)- ์ด๋ฆ ์๋ ์ด๋ฏธ์ง ์ ๋ฆฌ(
docker image prune -f). Gradle/npm ๋น๋ ์บ์๋ ๋จ๊น๋๋ค
์ต์
: --no-pull(.env ๋ง ๋ฐ๊ฟจ์ ๋) ยท --no-backup
์ฌ์ฉ์ API ์ ์ Flyway ๋ง์ด๊ทธ๋ ์ด์ ์ด ์์ผ๋ฉด ์ฌ๊ธฐ๋ ๋ ์๋ ์ ์ฉ๋ฉ๋๋ค. ๋ง์ด๊ทธ๋ ์ด์ ์ ๋๋๋ฆด ์ ์์ผ๋ฏ๋ก 1๋จ๊ณ ๋ฐฑ์ ์ ์ง์ฐ์ง ๋ง์ธ์.
์๋ฒ๋ณ ์กฐ์ (์: ๋ฉ๋ชจ๋ฆฌ ์ํ์ ๋ ์ธ๋ฐํ๊ฒ)์ ์ถ์ ๋์ง ์๋ deploy/docker-compose.override.yml ์ ๋๋ฉด ์คํฌ๋ฆฝํธ์ docker compose ๋ช
๋ น ๋ชจ๋์ ํจ๊ป ์ ์ฉ๋ฉ๋๋ค.
sudo /opt/honeyrest/honeyRest_user/deploy/rollback.sh user-api # ๋๋ host-admin, caddy (์ฌ๋ฌ ๊ฐ ๊ฐ๋ฅ):previous ์ด๋ฏธ์ง๋ฅผ :latest ๋ก ๋๋๋ฆฌ๊ณ ๊ทธ ์ปจํ
์ด๋๋ง ๋ค์ ๋ง๋ญ๋๋ค(๋ฌธ์ ์ด๋ฏธ์ง๋ :rolled-back ์ผ๋ก ๋ณด๊ด).
์ ์ฅ์๋ ์ ์ปค๋ฐ์ ๊ทธ๋๋ก ์์ผ๋ฏ๋ก, ์์ธ์ ๊ณ ์น๊ธฐ ์ ์๋ update.sh ๋ฅผ ๋ค์ ์คํํ์ง ์์ต๋๋ค(๋ค์ ์ ์ฝ๋๋ก ๋น๋๋จ).
cd /opt/honeyrest/honeyRest_user && git log --oneline -5
git checkout <์ ์ ์ปค๋ฐ> # ํ์ธ์ด ๋๋๋ฉด git checkout main ์ผ๋ก ๋ณต๊ท
sudo ./deploy/update.sh --no-pull์ ๋ง์ด๊ทธ๋ ์ด์
์ด ์ด๋ฏธ ์ ์ฉ๋ ๋ค ์์ ์ด๋ฏธ์ง๋ก ๋์๊ฐ๋ฉด ddl-auto=validate ์ ์คํจํ ์ ์์ต๋๋ค.
๊ทธ๋๋ ์
๋ฐ์ดํธ ์ง์ ๋ฐฑ์
์ผ๋ก DB ๋ฅผ ๋ณต์ํ ๋ค ์ด๋ฏธ์ง ๋กค๋ฐฑ์ ํฉ๋๋ค โ ๋ฐฑ์
๊ณผ ๋ณต์.
setup.sh ๊ฐ ๋งค์ผ 04:30(์๋ฒ ์๊ฐ Asia/Seoul) ๋ฐฑ์
์ cron ์ ๋ฑ๋กํฉ๋๋ค. ์ง์ ๋ฑ๋กํ๋ ค๋ฉด:
# /etc/cron.d/honeyrest
30 4 * * * root /opt/honeyrest/honeyRest_user/deploy/backup.sh >> /var/log/honeyrest-backup.log 2>&1- ๊ฒฐ๊ณผ:
/opt/honeyrest/backups/honeyrest_db-YYYYMMDD-HHMMSS.sql.gz,uploads-YYYYMMDD-HHMMSS.tar.gz(๊ฐ๊ฐ ์ต๊ทผ 7๊ฐ,KEEP=14๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ) mysqldump --single-transaction์ด๋ผ ์๋น์ค ์ค์๋ ์ ๊ธ ์์ด ์ผ๊ด๋ ์ค๋ ์ท์ ๋๋ค- ํ์ ์ ๋ณด๊ฐ ๋ค์ด ์์ผ๋ฏ๋ก ๊ถํ 600. ์๋ฒ ๋ฐ์๋ ๋ณด๊ดํ์ธ์:
scp ubuntu@<IP>:/opt/honeyrest/backups/*.gz ./
๋ณต์:
cd /opt/honeyrest/honeyRest_user/deploy
sudo docker compose stop user-api host-admin
gunzip -c /opt/honeyrest/backups/honeyrest_db-<์๊ฐ>.sql.gz | \
sudo docker compose exec -T mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql -uroot "$MYSQL_DATABASE"'
# ์
๋ก๋ ํ์ผ๊น์ง ๋๋๋ฆด ๋
sudo docker compose start user-api
sudo docker compose exec -T user-api tar -C /app -xzf - < /opt/honeyrest/backups/uploads-<์๊ฐ>.tar.gz
sudo docker compose up -dsetup.sh ๊ฐ ์ฒ์ ํ ๋ฒ ์คํํฉ๋๋ค. ์ง์ ์คํ:
sudo /opt/honeyrest/honeyRest_user/deploy/seed.sh # ์ ์ฒด (๊ธฐ์ด ๋ฐ์ดํฐ๋ ํ ๋ฒ๋ง)
sudo /opt/honeyrest/honeyRest_user/deploy/seed.sh --demo-refresh # ๋ฐ๋ชจ ๋ฐ์ดํฐ๋ง| ์์ | ํ์ผ | ๋ฐ๋ณต ์คํ |
|---|---|---|
| 1 | ๊ด๋ฆฌ์ ์ ์ฅ์ db/seed/insert.sql โ insert_pk_1-20.sql โ insert_pk_21-30.sql โ insert_pk_31-33.sql (FK ์์: ์ง์ญยท์นดํ
๊ณ ๋ฆฌยทํ๊ทธยท์
์ฒด โ ์์ยท๊ฐ์ค) |
ํ ๋ฒ๋ง. ๋ค ํ์ผ๊ณผ ์๋ฃ ํ์(deploy_seed_history)๋ฅผ ํ ํธ๋์ญ์
์ผ๋ก ์ ์ฌ. ํ์๊ฐ ์๊ฑฐ๋ ์์๊ฐ ์ด๋ฏธ ์์ผ๋ฉด ๊ฑด๋๋ |
| 2 | scripts/seed-demo-data.sql โ ๋ฐ๋ชจ ๋ฆฌ๋ทฐ 12๊ฑด, ์ค๋๋ถํฐ 90์ผ ๊ฐ๊ฒฉ ๋ฌ๋ ฅ, min_priceยทrating ์ฌ๊ณ์ฐ |
์์ |
| 3 | scripts/seed-local-images.sql โ ๋ง๋ฃ๋ Firebase ์ด๋ฏธ์ง URL โ /uploads/placeholder/*.svg |
์์ |
| 4 | uploads/placeholder/*.svg โ uploads ๋ณผ๋ฅจ |
์์ |
| 5 | Redis ๊ฒ์ ์บ์ ์ธ๋ ๋ฒํธ ์ฆ๊ฐ ยท ๋ฐฐ๋ ์บ์ ์ญ์ | ์์ |
| 6 | scripts/audit-data-quality.sql ๊ฒฐ๊ณผ ์ถ๋ ฅ (*_mismatch ๊ฐ 0 ์ด๋ฉด ์ ์) |
์ฝ๊ธฐ ์ ์ฉ |
๊ฐ๊ฒฉ ๋ฌ๋ ฅ์ ์๋ ์์ ๋ถํฐ 90์ผ์น์
๋๋ค(๋ฌ๋ ฅ์ด ์๋ ๋ ์ง๋ ๊ฐ์ค ๊ธฐ๋ณธ๊ฐ๋ก ๊ณ์ฐ๋๋ฏ๋ก ์์ฝ์ ๊ณ์ ๋ฉ๋๋ค).
๋ฐ๋ชจ ๋ฌ๋ ฅ์ ๋๋ฆฌ๋ ค๋ฉด ๋ช ๋ฌ์ ํ ๋ฒ seed.sh --demo-refresh ๋ฅผ ์คํํ๊ฑฐ๋ cron ์ ์ถ๊ฐํฉ๋๋ค.
docker compose logs caddy | grep -iE 'error|challenge|acme'
- 80/443 ์ด ๋งํ: OCI ๋ณด์ ๋ชฉ๋ก ์ธ๊ทธ๋ ์ค(2์ฅ)์ OS ๋ฐฉํ๋ฒฝ์ ํ์ธํฉ๋๋ค.
์ธ๋ถ์์ ํ์ธ: ๋ก์ปฌ PC ์์
sudo iptables -L INPUT -n --line-numbers | head -20 # 80/443 ACCEPT ๊ฐ REJECT ๋ณด๋ค ์์ ์์ด์ผ ํจ sudo ufw status # ufw ๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ
curl -I http://<IP-ํ์ดํ>.sslip.ioโ308์ด๋ฉด 80 ์ ์ด๋ ค ์์ต๋๋ค. - Let's Encrypt ๋ฐ๊ธ ํ๋: ๊ฐ์ ์ด๋ฆ์ ์ธ์ฆ์๋ ์ฃผ 5ํ, ๋ฑ๋ก ๋๋ฉ์ธ๋น ์ฃผ 50๊ฐ, ์คํจํ ๊ฒ์ฆ์ ์๊ฐ๋น 5ํ๋ก ์ ํ๋ฉ๋๋ค.
sslip.io๋ ๋ง์ ์ฌ๋์ด ํจ๊ป ์ฐ๋ ๋๋ฉ์ธ์ด๋ผ ํ๋์ ๊ฑธ๋ฆด ๊ฐ๋ฅ์ฑ์ด ์๋์ ์ผ๋ก ๋์ต๋๋ค.- ์ธ์ฆ์๋
caddy-data๋ณผ๋ฅจ์ ์์ผ๋ฏ๋ก ์ปจํ ์ด๋๋ฅผ ๋ค์ ๋ง๋ค์ด๋ ์ฌ๋ฐ๊ธํ์ง ์์ต๋๋ค. ๋ณผ๋ฅจ์ ์ง์ฐ์ง ๋ง์ธ์. - Caddy ๋ ์คํจํ๋ฉด ๊ฐ๊ฒฉ์ ๋๋ ค ๊ฐ๋ฉฐ ์๋ ์ฌ์๋ํฉ๋๋ค. ํ๋์ ๊ฑธ๋ ธ๋ค๋ฉด ๊ธฐ๋ค๋ฆฌ๊ฑฐ๋ ์ค์ ๋๋ฉ์ธ์ผ๋ก ๋ฐ๊ฟ๋๋ค.
- ์ค์ ์ ์คํํ ๋๋
Caddyfile์ ์ญ ๋ธ๋ก์acme_ca https://acme-staging-v02.api.letsencrypt.org/directory๋ฅผ ์ ์ ๋ฃ์ด ์คํ ์ด์ง CA ๋ก ํ์ธํฉ๋๋ค(๋ธ๋ผ์ฐ์ ๊ฒฝ๊ณ ๋ ์ ์). ๋๋๋ฉด ์ง์ฐ๊ณdocker compose up -d --force-recreate caddy.
- ์ธ์ฆ์๋
- IP ๊ฐ ๋ฐ๋:
.env์SITE_DOMAIN์ ์ IP ๋ก ๊ณ ์น ๋คsudo ./update.sh --no-pull(SPA ์ ๊ด๋ฆฌ์ ๋งํฌยทOAuth ์ฃผ์๊ฐ ๋น๋ ์์ ์ ๋ค์ด๊ฐ๋ฏ๋ก ๋ค์ ๋น๋ ํ์).
- ์ฆ์: ์ปจํ
์ด๋๊ฐ ๋ฐ๋ณต ์ฌ์์(
docker compose ps์Restarting),docker inspect <์ปจํ ์ด๋> --format '{{.State.OOMKilled}}'๊ฐtrue,dmesg | grep -i oom. - JVM ์
-XX:+ExitOnOutOfMemoryError๋ก ๋ฐ๋ก ์ข ๋ฃ๋๊ณrestart: unless-stopped๋ก ๋ค์ ๋น๋๋ค. - ์กฐ์น:
.env์USER_API_MEM_LIMITยทHOST_ADMIN_MEM_LIMIT์ ์ฌ๋ฆฌ๊ฑฐ๋(ํ์ ์ํ์ 70%), ๋น๋ ์ค์ด๋ผ๋ฉด ์ค์์ ํ์ธํฉ๋๋ค(swapon --show). - ๋น๋๊ฐ ๋๋ฆฌ๊ฑฐ๋ ๋ฉ์ถ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ฉด ๋๊ฐ ์ค์์ ์ฐ๋ ์ค์
๋๋ค. ์๋น์ค๋ฅผ ์ ์ ๋ฉ์ถ๊ณ ๋น๋ํ๋ฉด ๋นจ๋ผ์ง๋๋ค:
docker compose stop host-admin && sudo ./update.sh. - ์ธ์คํด์ค๋ฅผ 4 OCPU / 24GB ๋ก ๋๋ ธ๋ค๋ฉด:
MYSQL_INNODB_BUFFER_POOL_SIZE=1G,MYSQL_MEM_LIMIT=2g,USER_API_MEM_LIMIT=2g,HOST_ADMIN_MEM_LIMIT=1536m์ ๋๋ก ์ฌ๋ฆฌ๊ณdocker compose up -d.
docker compose logs user-api | grep -iE 'flyway|error|caused'โ ๋ง์ด๊ทธ๋ ์ด์ ์คํจ, DB ๋น๋ฐ๋ฒํธ ๋ถ์ผ์น ๋ฑ..env์MYSQL_PASSWORD๋ฅผ MySQL ๋ณผ๋ฅจ์ด ๋ง๋ค์ด์ง ๋ค์ ๋ฐ๊ฟจ๋ค๋ฉด DB ๊ณ์ ๋น๋ฐ๋ฒํธ๋ ๊ทธ๋๋ก์ ๋๋ค. ์๋ ๊ฐ์ผ๋ก ๋๋ฆฌ๊ฑฐ๋ MySQL ์์ALTER USERํฉ๋๋ค.- ์ฒซ ๊ธฐ๋์ 1 OCPU ์์ 1~3๋ถ ๊ฑธ๋ฆฝ๋๋ค(ํฌ์ค ์ฒดํฌ ์ ์ 240์ด).
๊ด๋ฆฌ์ ์ฑ์ ์คํค๋ง๋ฅผ ๋ง๋ค์ง ์๊ณ ๊ฒ์ฆ๋ง ํฉ๋๋ค. ๊ด๋ฆฌ์ ์ ์ฅ์์ ์๋ธ๋ชจ๋(๊ณต์ ๋๋ฉ์ธ ๋ชจ๋)์ด ์ฌ์ฉ์ API ์ ๋ง์ด๊ทธ๋ ์ด์
๋ณด๋ค ์์ ์ํฐํฐ๋ฅผ ๋ด๊ณ ์์ผ๋ฉด ์คํจํฉ๋๋ค.
์ฌ์ฉ์ API ๋ฅผ ๋จผ์ ์ต์ ์ผ๋ก ์ฌ๋ฆฌ๊ณ , ๊ด๋ฆฌ์ ์ ์ฅ์์ ์๋ธ๋ชจ๋ ๊ณ ์ ์ปค๋ฐ์ด ๋ง๋์ง ํ์ธํฉ๋๋ค(git -C /opt/honeyrest/honeyRest_host submodule status).
๋ฉ์ผ ์ค์ (MAIL_*)์ด ๋น์ด ์์ผ๋ฉด ๊ฐ์
์ธ์ฆ ๋ฉ์ผ์ด ๋ฐ์ก๋์ง ์์ต๋๋ค(๋ก๊ทธ์ [mail-disabled]). ๋ฐ๋ชจ ๋ชฉ์ ์ผ๋ก ์ง์ ์ธ์ฆ ์ฒ๋ฆฌํ๋ ค๋ฉด:
sudo docker compose exec mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" \
-e "UPDATE user SET is_verified = 1 WHERE email = '\''๊ฐ์
ํ@์ด๋ฉ์ผ'\''"'๊ณต๊ฐ ๋ฐ๋ชจ๋ผ๋ฉด Gmail ์ฑ ๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
setup.sh ๋ ์คํ ์ค์ธ ๊ท์น๊ณผ /etc/iptables/rules.v4 ๋ฅผ ํจ๊ป ๊ณ ์นฉ๋๋ค. netfilter-persistent save ๋ Docker ๊ฐ ๋ง๋ ๊ท์น๊น์ง ์ ์ฅํ๋ฏ๋ก ์ฐ์ง ์์ต๋๋ค.
ํ์ธ: grep -E 'dport (80|443)' /etc/iptables/rules.v4
df -h /
docker system df
docker builder prune -f --filter until=168h # 1์ฃผ ๋์ ๋น๋ ์บ์ (๋ค์ ๋น๋๊ฐ ๋๋ ค์ง ์ ์์)
docker image rm honeyrest/user-api:rolled-back # ๋กค๋ฐฑ ํ ๋จ์ ์ด๋ฏธ์งOracle ์ Always Free ์ธ์คํด์ค๊ฐ 7์ผ ๋์ ๋ค์์ ๋ชจ๋ ๋ง์กฑํ๋ฉด ์ ํด๋ก ๋ณด๊ณ ํ์(์ค์ง)ํ ์ ์์ต๋๋ค: CPU ์ฌ์ฉ๋ฅ 95๋ฒ์งธ ๋ฐฑ๋ถ์ 20% ๋ฏธ๋ง ยท ๋คํธ์ํฌ ์ฌ์ฉ๋ฅ 20% ๋ฏธ๋ง ยท ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ 20% ๋ฏธ๋ง(A1 ์ ฐ์ดํ).
- ์ด ์คํ์ ์ ํด ์ํ์์๋ ์ฝ 1.1GB(6GB ์ ์ฝ 18%)๋ฅผ ์ฐ๊ณ JVM ํ์ด ์ปค์ง๋ฉด์ ๋ ๋์ด๋ฉ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๊ธฐ์ค์ ๊ฑธ๋ฆด ๊ฐ๋ฅ์ฑ์ ๋ฎ์ง๋ง ์ฅ๋ดํ ์๋ ์์ต๋๋ค.
setup.sh์ cron ํฌ์ค ์ฒดํฌ(5๋ถ๋ง๋ค ๊ณต๊ฐ ์ฃผ์๋ก APIยท๊ด๋ฆฌ์ ์ฑ ํธ์ถ)๋ ์ฅ์ ๋ฅผ ๊ธฐ๋กํ๊ณ ์๋น์ค๋ฅผ ๊นจ์ ๋๋ ๊ฐ๋ฒผ์ด ํ์ ๋๋ค. CPU ๋ฅผ ์ผ๋ถ๋ฌ ์๋ชจํ์ง๋ ์์ผ๋ฏ๋ก ํ์ ๊ธฐ์ค ์์ฒด๋ฅผ ๋ง์ง๋ ์์ต๋๋ค.- ํ์คํ ํผํ๋ ค๋ฉด ๊ณ์ ์ Pay As You Go ๋ก ์ ๊ทธ๋ ์ด๋ํฉ๋๋ค. Always Free ํ๋ ์์ ๋ฆฌ์์ค๋ ๊ณ์ ๋ฌด๋ฃ์ด๋ฉฐ ์ ํด ํ์ ๋์์์ ๋น ์ง๋๋ค.
- ํ์๋์ด ์ค์ง๋์๋ค๋ฉด ์ฝ์์์ ์ธ์คํด์ค๋ฅผ ์์ํ๋ฉด ๋ฉ๋๋ค. ๋ชจ๋ ์ปจํ
์ด๋๋
restart: unless-stopped๋ผ Docker ์ ํจ๊ป ์๋์ผ๋ก ๋ค์ ๋น๋๋ค(์์ฝ ๊ณต์ธ IP ๋ฅผ ์ฐ๋ฉด ์ฃผ์๋ ๊ทธ๋๋ก).
- DNS A ๋ ์ฝ๋ 2๊ฐ:
@โ ์๋ฒ IP,adminโ ์๋ฒ IP .env:SITE_DOMAIN=example.comsudo ./update.sh --no-pullโ SPA ๋ค์ ๋น๋, Caddy ๊ฐ ์ ์ด๋ฆ์ผ๋ก ์ธ์ฆ์ ๋ฐ๊ธ- ์นด์นด์ค/๊ตฌ๊ธ ์ฝ์์ Redirect URI ๋ฅผ ์ ์ฃผ์๋ก ๋ฐ๊ฟ๋๋ค.
deploy/.env์ ๋ชจ๋ ๋น๋ฐ๊ฐ์ด ์์ต๋๋ค(๊ถํ 600, git ์ ์ธ). ์๋ฒ ๋ฐ์ ์์ ํ๊ฒ ๋ฐ๋ก ๋ณด๊ดํ์ธ์.- ๋น๋ฐ๊ฐ ํ์ผ(
application_security.properties)ยทFirebase ํค๋.dockerignore๋ก ์ด๋ฏธ์ง์ ๋ค์ด๊ฐ์ง ์์ต๋๋ค. - ํ ์ค๋ ํ ์คํธ ํค๋ง ์ฐ์ธ์. ๋ผ์ด๋ธ ํค๋ฅผ ๋ฃ์ผ๋ฉด ์ค์ ๊ฒฐ์ ๊ฐ ์ผ์ด๋ฉ๋๋ค.
- ๊ด๋ฆฌ์ ์ฌ์ดํธ๋ ๊ณต๊ฐ ์ฃผ์์ ์์ต๋๋ค. ๋ฐ๋ชจ ๊ณ์ ๋น๋ฐ๋ฒํธ๋
setup.sh๊ฐ ๋ฌด์์๋ก ๋ง๋ ๊ฐ์ ์ฐ๊ณ , ์ธ๋ถ์ ๊ณต๊ฐํ ๋๋ ์ ์ฒด ๊ด๋ฆฌ์ ๊ณ์ ๋ง ์๋ ค ์ฃผ์ธ์. - SSH ๋ ํค ์ธ์ฆ๋ง ์ฐ๊ณ , MySQL/Redis ํฌํธ๋ ์ธ๋ถ์ ์ด์ง ์์ต๋๋ค(compose ์
ports์์).