From 72689d86471ee165cf7a7e31dff4a5843aefb653 Mon Sep 17 00:00:00 2001 From: qorgh529 Date: Wed, 10 Jun 2026 18:15:14 +0900 Subject: [PATCH] =?UTF-8?q?docs:=202026-05=20=ED=97=88=EB=B8=8C=20?= =?UTF-8?q?=ED=99=88=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=EC=B6=95=20?= =?UTF-8?q?=EB=B0=8F=20URL=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=ED=8E=B8=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1166 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 898 insertions(+), 268 deletions(-) diff --git a/README.md b/README.md index cc13894..83b881d 100755 --- a/README.md +++ b/README.md @@ -1,24 +1,10 @@ -# ๐ŸŒ Web Portal โ€” Kubernetes + GitOps ํ™ˆ๋žฉ ํ”„๋กœ์ ํŠธ +# Web Portal - Kubernetes + GitOps ๋ฐฐํฌ ๊ฐ€์ด๋“œ -> ์กฐ์ง ๋‚ด๋ถ€ ์›นํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•˜๋Š” ํฌํ„ธ ์‹œ์Šคํ…œ์„ **์˜จํ”„๋ ˆ๋ฏธ์Šค Kubernetes ํ™˜๊ฒฝ**์—์„œ ์ง์ ‘ ์„ค๊ณ„ยท๊ตฌ์ถ•ยท์šด์˜ํ•œ ํ™ˆ๋žฉ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. +## ๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๊ฐœ์š” -[![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?style=flat&logo=kubernetes&logoColor=white)](https://kubernetes.io) -[![ArgoCD](https://img.shields.io/badge/ArgoCD-EF7B4D?style=flat&logo=argo&logoColor=white)](https://argoproj.github.io/cd) -[![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com) -[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?style=flat&logo=postgresql&logoColor=white)](https://www.postgresql.org) -[![Docker](https://img.shields.io/badge/Docker-2496ED?style=flat&logo=docker&logoColor=white)](https://www.docker.com) -[![Let's Encrypt](https://img.shields.io/badge/Let's_Encrypt-003A70?style=flat&logo=letsencrypt&logoColor=white)](https://letsencrypt.org) - ---- - -## ๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ๋ชฉ์  - -๋‹จ์ˆœํžˆ ๋”ฐ๋ผํ•˜๋Š” ํŠœํ† ๋ฆฌ์–ผ์ด ์•„๋‹Œ, **์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค์„ ์ง์ ‘ ๋งž๋‹ฅ๋œจ๋ฆฌ๊ณ  ํ•ด๊ฒฐ**ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. - -- ์‹ค์ œ ๋„๋ฉ”์ธ(`cyanburu.com`) + HTTPS ์ ์šฉ์œผ๋กœ ์™ธ๋ถ€ ์ธํ„ฐ๋„ท์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์„œ๋น„์Šค ์šด์˜ -- GitOps ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ๋ฐฐํฌ๋˜๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ -- Pod ์ด์ƒ ๊ฐ์ง€, ์ธ์ฆ์„œ ๋งŒ๋ฃŒ ์ž„๋ฐ• ์‹œ DiscordยทGmail ์ž๋™ ์•Œ๋ฆผ ๊ตฌํ˜„ -- ๋ฐœ์ƒํ•œ ์žฅ์•  14๊ฑด์„ ๋ชจ๋‘ ๋ฌธ์„œํ™”ํ•˜์—ฌ ์žฌํ˜„ยทํ•ด๊ฒฐ ๊ณผ์ • ๊ธฐ๋ก +์กฐ์ง ๋‚ด๋ถ€ ์›นํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•˜๋Š” ํฌํ„ธ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. +๋กœ๊ทธ์ธ ํ›„ ๋ณธ์ธ์—๊ฒŒ ํ• ๋‹น๋œ ์›นํŽ˜์ด์ง€ ๋ชฉ๋ก์„ ํ™•์ธํ•˜๊ณ  ์ ‘์†ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, +๊ด€๋ฆฌ์ž๋Š” ํŽ˜์ด์ง€ ๋ฐ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- @@ -26,207 +12,53 @@ ``` ์‚ฌ์šฉ์ž (์™ธ๋ถ€ ์ธํ„ฐ๋„ท) - โ”œโ”€โ”€ https://cyanburu.com โ†’ Web Portal - โ”œโ”€โ”€ https://gitea.cyanburu.com โ†’ Gitea (Self-hosted Git) - โ””โ”€โ”€ https://argo.cyanburu.com โ†’ ArgoCD (GitOps) - โ†“ - MSI ๋ผ์šฐํ„ฐ (ํฌํŠธํฌ์›Œ๋”ฉ 80/443) - โ†“ - Nginx Ingress Controller โ† TLS ์ข…๋ฃŒ, ๋„๋ฉ”์ธ๋ณ„ ๋ผ์šฐํŒ… - โ†“ - cert-manager โ† Let's Encrypt ์ธ์ฆ์„œ ์ž๋™ ๋ฐœ๊ธ‰/๊ฐฑ์‹  - โ†“ - Kubernetes ๋„ค์ž„์ŠคํŽ˜์ด์Šค - โ”œโ”€โ”€ web-portal - โ”‚ โ”œโ”€โ”€ Nginx Frontend (SPA) - โ”‚ โ”œโ”€โ”€ FastAPI Backend (REST API) - โ”‚ โ””โ”€โ”€ PostgreSQL DB (PVC ์˜๊ตฌ ์ €์žฅ) - โ”œโ”€โ”€ gitea โ†’ Self-hosted Git + Container Registry - โ””โ”€โ”€ argocd โ†’ GitOps ์ž๋™ ๋ฐฐํฌ ์—”์ง„ - โ†‘ - ๊ฐœ๋ฐœ์ž git push โ†’ Gitea โ†’ ArgoCD ์ž๋™ ๊ฐ์ง€ & ๋ฐฐํฌ + โ”œโ”€โ”€ https://cyanburu.com โ†’ Hub ํ™ˆํŽ˜์ด์ง€ (ํฌํŠธํด๋ฆฌ์˜ค/์„œ๋น„์Šค ํ—ˆ๋ธŒ) + โ”œโ”€โ”€ https://cyanburu.com/portal โ†’ Web Portal + โ”œโ”€โ”€ https://cyanburu.com/kingscup โ†’ King's Cup ๊ฒŒ์ž„ (๊ฐœ๋ฐœ ์˜ˆ์ •) + โ”œโ”€โ”€ https://gitea.cyanburu.com โ†’ Gitea + โ””โ”€โ”€ https://argo.cyanburu.com โ†’ ArgoCD + โ†“ +MSI ๋ผ์šฐํ„ฐ (ํฌํŠธํฌ์›Œ๋”ฉ 80/443) + โ†“ +Nginx Ingress Controller โ† TLS ์ข…๋ฃŒ, ๋„๋ฉ”์ธ/๊ฒฝ๋กœ๋ณ„ ๋ผ์šฐํŒ… + โ†“ +cert-manager โ† Let's Encrypt ์ธ์ฆ์„œ ์ž๋™ ๋ฐœ๊ธ‰/๊ฐฑ์‹  + โ†“ +Kubernetes ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„ ์„œ๋น„์Šค + โ”œโ”€โ”€ hub + โ”‚ โ””โ”€โ”€ Nginx (์ •์  HTML) โ† cyanburu.com/ ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ + โ”œโ”€โ”€ web-portal + โ”‚ โ”œโ”€โ”€ Nginx Frontend (ClusterIP: 80) + โ”‚ โ”œโ”€โ”€ FastAPI Backend (ClusterIP: 8000) + โ”‚ โ””โ”€โ”€ PostgreSQL DB (ClusterIP: 5432) + โ”œโ”€โ”€ gitea + โ”‚ โ””โ”€โ”€ Gitea (ClusterIP: 3000) + โ””โ”€โ”€ argocd + โ””โ”€โ”€ ArgoCD Server (ClusterIP: 443) + โ†‘ +๊ฐœ๋ฐœ์ž (git push) + โ†“ +Gitea โ†’ ArgoCD ์ž๋™ ๊ฐ์ง€ & ๋ฐฐํฌ ``` --- ## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ -| ๊ตฌ๋ถ„ | ๊ธฐ์ˆ  | ์„ ํƒ ์ด์œ  | -|------|------|-----------| -| **Container Orchestration** | Kubernetes (Docker Desktop ๋‚ด์žฅ) | ์˜จํ”„๋ ˆ๋ฏธ์Šค ํ™˜๊ฒฝ์—์„œ ํ”„๋กœ๋•์…˜๊ณผ ๋™์ผํ•œ ๊ตฌ์กฐ ๊ตฌํ˜„ | -| **GitOps** | Gitea + ArgoCD | Git์„ ๋‹จ์ผ ์ง„์‹ค ์†Œ์Šค๋กœ, ์„ ์–ธ์  ๋ฐฐํฌ ์ž๋™ํ™” | -| **Image Registry** | Gitea Container Registry | ์™ธ๋ถ€ ์˜์กด ์—†์ด ์‚ฌ๋‚ด ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ž์ฒด ์šด์˜ | -| **Ingress** | Nginx Ingress Controller | ๋„๋ฉ”์ธ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…, TLS ์ข…๋ฃŒ ์ฒ˜๋ฆฌ | -| **TLS** | cert-manager + Let's Encrypt | ์ธ์ฆ์„œ ๋ฐœ๊ธ‰ยท๊ฐฑ์‹  ์™„์ „ ์ž๋™ํ™” | -| **Backend** | Python FastAPI | ๋น„๋™๊ธฐ REST API, JWT ์ธ์ฆ | -| **Database** | PostgreSQL + PVC | ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ ํ›„์—๋„ ๋ฐ์ดํ„ฐ ์˜์† | -| **Monitoring** | APScheduler (์ปค์Šคํ…€) | Pod ์ƒํƒœ 1๋ถ„ ์ฃผ๊ธฐ, ์ธ์ฆ์„œ ๋งŒ๋ฃŒ 24์‹œ๊ฐ„ ์ฃผ๊ธฐ ์ฒดํฌ | -| **Alerting** | Discord Webhook + Gmail | ์žฅ์•  ๋ฐœ์ƒ ์‹œ ์ฆ‰์‹œ ์•Œ๋ฆผ | - ---- - -## โœจ ๊ตฌํ˜„ ๊ธฐ๋Šฅ - -### ๋ณด์•ˆ (Security) -- JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ / ์„ธ์…˜ ๊ด€๋ฆฌ -- **๋น„๋ฐ€๋ฒˆํ˜ธ 5ํšŒ ์˜ค๋ฅ˜ ์‹œ ๊ณ„์ • ์ž๋™ ์ž ๊ธˆ** โ†’ ๊ด€๋ฆฌ์ž ํ•ด์ œ -- **์ตœ์ดˆ ๋กœ๊ทธ์ธ ๊ฐ•์ œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ** (์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰ ํฌํ•จ) -- **Nginx Rate Limiting** โ€” ๋กœ๊ทธ์ธ API ๋ถ„๋‹น 5ํšŒ ์ œํ•œ (Brute Force ๋ฐฉ์–ด) -- HTTPS ์ž๋™ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (`ssl-redirect: "true"`) - -### ๋ชจ๋‹ˆํ„ฐ๋ง & ์•Œ๋ฆผ (Observability) -- Pod ์ƒํƒœ 1๋ถ„๋งˆ๋‹ค ์ž๋™ ์ฒดํฌ โ†’ ์ด์ƒ/๋ณต๊ตฌ ์‹œ **Discord + Gmail ์•Œ๋ฆผ** -- ์ธ์ฆ์„œ ๋งŒ๋ฃŒ 24์‹œ๊ฐ„ ์ฃผ๊ธฐ ์ฒดํฌ โ†’ ๋งŒ๋ฃŒ ์ž„๋ฐ• ์‹œ Gmail ์•Œ๋ฆผ -- **์›น UI์—์„œ ์•Œ๋ฆผ ์ฑ„๋„ ๊ด€๋ฆฌ** (Discord/Gmail/ํ˜ผํ•ฉ ์ฑ„๋„ ๋‹ค์ค‘ ๋“ฑ๋ก) -- DB ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ์„ค์ • โ†’ Pod ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์„ค์ • ์œ ์ง€ - -### ๊ด€๋ฆฌ์ž ๊ธฐ๋Šฅ -- ์‚ฌ์šฉ์ž ๊ณ„์ • ์ƒ์„ฑ/์‚ญ์ œ, ์ ‘๊ทผ ๊ถŒํ•œ ์„ค์ • (์ฒดํฌ๋ฐ•์Šค UI) -- ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž๋™ ์ƒ์„ฑ ๋ฐ ๋ฐœ๊ธ‰ -- ๊ณต์ง€์‚ฌํ•ญ / ๊ด€๋ฆฌ์ž ์š”์ฒญ ๊ฒŒ์‹œํŒ (๋Œ“๊ธ€ยท๋‹ต๊ธ€ ํฌํ•จ) -- ์‚ฌ์šฉ์ž ์ƒํƒœ ํƒœ๊ทธ: `์ •์ƒ` / `๐Ÿ”’์ž ๊น€` / `์ดˆ๊ธฐPW` / `๋ณ€๊ฒฝ์š”์ฒญ` - ---- - -## ๐Ÿ“… ๊ตฌ์ถ• ์ด๋ ฅ - -| ๋‚ ์งœ | ์ฃผ์š” ์ž‘์—… | -|------|-----------| -| 2026-04-06 | K8s ์ดˆ๊ธฐ ๊ตฌ์ถ•, FastAPI + Nginx + PostgreSQL ๋ฐฐํฌ, Gitea + ArgoCD GitOps ๊ตฌ์„ฑ | -| 2026-04-10 | ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ (HTTPS), cert-manager, CoreDNS ํ—ค์–ดํ•€ NAT ์šฐํšŒ, ๋ณด์•ˆ ๊ธฐ๋Šฅ ๊ฐ•ํ™” | -| 2026-04-27 | Discord/Gmail ์•Œ๋ฆผ ์ถ”๊ฐ€, APScheduler ๋ชจ๋‹ˆํ„ฐ๋ง, ์•Œ๋ฆผ ์ฑ„๋„ ๊ด€๋ฆฌ UI | - ---- - -## ๐Ÿ”ฅ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… (14๊ฑด ํ•ด๊ฒฐ) - -์‹ค์ œ ๊ตฌ์ถ• ๊ณผ์ •์—์„œ ๊ฒช์€ ์žฅ์• ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. - -
-1. NodePort ํฌํŠธ ์ถฉ๋Œ - -**์ฆ์ƒ** `provided port is already allocated` -**์›์ธ** ํ•ด๋‹น NodePort๊ฐ€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘ -**ํ•ด๊ฒฐ** `k8s/05-frontend.yaml`์—์„œ NodePort ๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํ›„ ์„œ๋น„์Šค ์žฌ์ ์šฉ - -
- -
-2. Backend CrashLoopBackOff โ€” Liveness Probe ์‹คํŒจ - -**์ฆ์ƒ** `Liveness probe failed: HTTP probe failed with statuscode: 404` -**์›์ธ** DB ์—ฐ๊ฒฐ ๋Œ€๊ธฐ ์ค‘ liveness probe๊ฐ€ ๋จผ์ € ์‹คํŒจํ•ด K8s๊ฐ€ ๊ฐ•์ œ ์žฌ์‹œ์ž‘ -**ํ•ด๊ฒฐ** `initialDelaySeconds: 60`, `failureThreshold: 5`๋กœ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€ - -
- -
-3. Nginx API ํ”„๋ก์‹œ ์‹คํŒจ โ€” JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜ - -**์ฆ์ƒ** ๋กœ๊ทธ์ธ ์‹œ `Unexpected token '<', "..." is not valid JSON` -**์›์ธ** Nginx๊ฐ€ `/api/` ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ๋กœ ์ „๋‹ฌํ•˜์ง€ ๋ชปํ•˜๊ณ  HTML ๋ฐ˜ํ™˜ -**ํ•ด๊ฒฐ** `proxy_pass`๋ฅผ K8s ๋‚ด๋ถ€ FQDN(`backend-service.web-portal.svc.cluster.local`)์œผ๋กœ ๋ณ€๊ฒฝ - -
- -
-4. ArgoCD โ€” authentication required: Unauthorized - -**์ฆ์ƒ** `failed to list refs: authentication required` -**์›์ธ** ArgoCD๊ฐ€ Gitea ์ €์žฅ์†Œ ์ธ์ฆ ์ •๋ณด ์—†์Œ -**ํ•ด๊ฒฐ** `argocd.argoproj.io/secret-type=repository` ๋ ˆ์ด๋ธ”์ด ์žˆ๋Š” Secret ์ง์ ‘ ์ƒ์„ฑ - -
- -
-5. RepeatedResourceWarning โ€” ๋ฆฌ์†Œ์Šค ์ค‘๋ณต - -**์ฆ์ƒ** `Resource appeared 2 times among application resources` -**์›์ธ** `k8s/` ํด๋” ๋‚ด ๋™์ผ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์˜ํ•˜๋Š” YAML ์ค‘๋ณต ์กด์žฌ -**ํ•ด๊ฒฐ** ์ค‘๋ณต ํŒŒ์ผ ์‚ญ์ œ ํ›„ push - -
- -
-6. ImagePullBackOff โ€” Gitea Registry HTTP ์ ‘๊ทผ ์˜ค๋ฅ˜ - -**์ฆ์ƒ** `server gave HTTP response to HTTPS client` -**์›์ธ** Gitea Registry๊ฐ€ HTTP์ธ๋ฐ Docker๊ฐ€ HTTPS๋กœ ์ ‘๊ทผ ์‹œ๋„ -**ํ•ด๊ฒฐ** Docker Desktop `insecure-registries` ์„ค์ • ์ถ”๊ฐ€ - -
- -
-7. Gitea Container Registry ๋กœ๊ทธ์ธ ์‹คํŒจ (context deadline exceeded) - -**์ฆ์ƒ** `Get "http://...:30000/v2/": context deadline exceeded` -**์›์ธ** Gitea ROOT_URL์ด `localhost`๋กœ ์„ค์ •๋˜์–ด token ์š”์ฒญ์ด ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ๋ชปํ•จ -**ํ•ด๊ฒฐ** Helm upgrade๋กœ `ROOT_URL`, `DOMAIN`, Packages ํ™œ์„ฑํ™” ์„ค์ • ๋ณ€๊ฒฝ - -
- -
-8. K8s ๋‚ด๋ถ€ Gitea Registry ์ด๋ฏธ์ง€ Pull ์‹คํŒจ - -**์ฆ์ƒ** ์™ธ๋ถ€์—์„œ๋Š” push ์„ฑ๊ณต, Pod๋Š” `ImagePullBackOff` -**์›์ธ** K8s Pod โ†’ ์™ธ๋ถ€ IP ๊ฒฝ๋กœ ๋ถˆ์•ˆ์ • -**ํ•ด๊ฒฐ** image ์ฃผ์†Œ๋ฅผ K8s ๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…(`gitea-http.gitea.svc.cluster.local:3000`)์œผ๋กœ ๋ณ€๊ฒฝ - -
- -
-9. ๋กœ๊ทธ์ธ ์‹คํŒจ โ€” ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ ์˜ค์—ผ - -**์ฆ์ƒ** ์˜ฌ๋ฐ”๋ฅธ ๊ณ„์ •์œผ๋กœ๋„ ๋กœ๊ทธ์ธ ์‹คํŒจ -**์›์ธ** ํ„ฐ๋ฏธ๋„ ์ƒ‰์ƒ ์ฝ”๋“œ(`\x1B[0m`)๊ฐ€ bcrypt ํ•ด์‹œ์— ์„ž์—ฌ DB์— ์ €์žฅ๋จ -**ํ•ด๊ฒฐ** Backend Pod์—์„œ Python์œผ๋กœ ํ•ด์‹œ ์žฌ์ƒ์„ฑ ํ›„ DB ์ง์ ‘ ์—…๋ฐ์ดํŠธ - -
- -
-10. git push ๊ฑฐ์ ˆ โ€” non-fast-forward - -**์ฆ์ƒ** `Updates were rejected because the tip of your current branch is behind` -**์›์ธ** Gitea UI์—์„œ ์ง์ ‘ ํŒŒ์ผ ์ˆ˜์ •์œผ๋กœ ๋กœ์ปฌ-์›๊ฒฉ ๋ธŒ๋žœ์น˜ diverge -**ํ•ด๊ฒฐ** `git pull origin main --rebase` ํ›„ push - -
- -
-11. cert-manager HTTP01 Challenge Pending โ€” ํ—ค์–ดํ•€ NAT - -**์ฆ์ƒ** `propagation check failed: context deadline exceeded` -**์›์ธ** cert-manager๊ฐ€ K8s ๋‚ด๋ถ€์—์„œ ์™ธ๋ถ€ ๋„๋ฉ”์ธ์œผ๋กœ self-check ์‹œ ํ—ค์–ดํ•€ NAT ๋ฏธ์ง€์›์œผ๋กœ ํƒ€์ž„์•„์›ƒ -**ํ•ด๊ฒฐ** CoreDNS์— ๋‚ด๋ถ€ ๋„๋ฉ”์ธ์„ ์ง์ ‘ ๋“ฑ๋ก โ†’ K8s ๋‚ด๋ถ€์—์„œ ๋„๋ฉ”์ธ์„ ๋‚ด๋ถ€ IP๋กœ ํ•ด์„ - -
- -
-12. Ingress Controller EXTERNAL-IP๊ฐ€ localhost๋กœ ํ‘œ์‹œ - -**์ฆ์ƒ** `kubectl get svc -n ingress-nginx` โ†’ EXTERNAL-IP: `localhost` -**์›์ธ** Docker Desktop ํ™˜๊ฒฝ์˜ ์ •์ƒ ๋™์ž‘ (`localhost` = ์‹ค์ œ PC) -**ํ•ด๊ฒฐ** ํฌํŠธํฌ์›Œ๋”ฉ์„ NodePort๊ฐ€ ์•„๋‹Œ **80/443 โ†’ PC ๋‚ด๋ถ€ IP:80/443**์œผ๋กœ ์ง์ ‘ ์„ค์ • - -
- -
-13. git commit ์‹คํŒจ โ€” Author identity unknown - -**์ฆ์ƒ** `fatal: unable to auto-detect email address` -**์›์ธ** Git ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฏธ์„ค์ • -**ํ•ด๊ฒฐ** `git config --global user.email`, `user.name` ์„ค์ • - -
- -
-14. Gitea Registry ImagePullBackOff โ€” ํ† ํฐ ์ธ์ฆ ํ—ค์–ดํ•€ NAT - -**์ฆ์ƒ** `Get "http://gitea-http...svc.cluster.local:3000/v2/token?...": context deadline exceeded` -**์›์ธ** Docker ๋ฐ๋ชฌ์ด K8s ๋‚ด๋ถ€ DNS๋ฅผ ํ•ด์„ํ•˜์ง€ ๋ชปํ•ด ํ† ํฐ ์ธ์ฆ ์‹คํŒจ -**ํ•ด๊ฒฐ** image ์ฃผ์†Œ์™€ Registry Secret์„ ์™ธ๋ถ€ IP(`192.168.10.101:30000`)๋กœ ํ†ต์ผ - -
+| ๊ตฌ๋ถ„ | ๊ธฐ์ˆ  | +|------|------| +| Frontend | Nginx + HTML/CSS/JS (SPA) | +| Backend | Python FastAPI | +| Database | PostgreSQL | +| Cache | Redis (King's Cup ์„ธ์…˜) | +| Container | Docker Desktop | +| Orchestration | Kubernetes (Docker Desktop ๋‚ด์žฅ) | +| GitOps | Gitea + ArgoCD | +| Image Registry | Gitea Container Registry | +| Ingress | Nginx Ingress Controller | +| TLS | cert-manager + Let's Encrypt | +| Domain | cyanburu.com (ํ›„์ด์ฆˆ) | +| ์„œ๋ธŒ๋„๋ฉ”์ธ | gitea.cyanburu.com, argo.cyanburu.com | --- @@ -234,92 +66,890 @@ ``` nginx-portal/ +โ”œโ”€โ”€ .gitea/ +โ”‚ โ””โ”€โ”€ workflows/ +โ”‚ โ””โ”€โ”€ build-and-push.yaml # Gitea Actions CI (์„ ํƒ์‚ฌํ•ญ) โ”œโ”€โ”€ backend/ -โ”‚ โ”œโ”€โ”€ main.py # FastAPI ์ „์ฒด API ๋กœ์ง (์ธ์ฆ, ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ, ๊ฒŒ์‹œํŒ) -โ”‚ โ”œโ”€โ”€ notifier.py # Discord/Gmail ์•Œ๋ฆผ ๋ชจ๋“ˆ (DB ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ ์ฑ„๋„) -โ”‚ โ”œโ”€โ”€ monitor.py # APScheduler ๋ชจ๋‹ˆํ„ฐ๋ง (Pod ์ƒํƒœ, ์ธ์ฆ์„œ ๋งŒ๋ฃŒ) +โ”‚ โ”œโ”€โ”€ main.py # FastAPI ์ „์ฒด API ๋กœ์ง โ”‚ โ”œโ”€โ”€ requirements.txt โ”‚ โ””โ”€โ”€ Dockerfile โ”œโ”€โ”€ frontend/ -โ”‚ โ”œโ”€โ”€ index.html # ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์•ฑ (SPA) -โ”‚ โ”œโ”€โ”€ nginx.conf # Nginx ์„ค์ • + Rate Limiting + /api/* ํ”„๋ก์‹œ +โ”‚ โ”œโ”€โ”€ index.html # ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์•ฑ (SPA) +โ”‚ โ”œโ”€โ”€ nginx.conf # Nginx ์„ค์ • + /api/* ํ”„๋ก์‹œ (/portal subpath ๋Œ€์‘) +โ”‚ โ””โ”€โ”€ Dockerfile +โ”œโ”€โ”€ hub/ # ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ (cyanburu.com/) +โ”‚ โ”œโ”€โ”€ index.html # ํฌํŠธํด๋ฆฌ์˜ค/์„œ๋น„์Šค ํ—ˆ๋ธŒ ์ •์  ํŽ˜์ด์ง€ โ”‚ โ””โ”€โ”€ Dockerfile โ”œโ”€โ”€ k8s/ -โ”‚ โ”œโ”€โ”€ 01-namespace.yaml -โ”‚ โ”œโ”€โ”€ 02-postgres.yaml # PostgreSQL + PVC -โ”‚ โ”œโ”€โ”€ 03-secrets.yaml # DB / JWT ์‹œํฌ๋ฆฟ -โ”‚ โ”œโ”€โ”€ 04-backend.yaml # FastAPI Deployment -โ”‚ โ”œโ”€โ”€ 05-frontend.yaml # Nginx Deployment -โ”‚ โ”œโ”€โ”€ 07-clusterissuer.yaml # Let's Encrypt ClusterIssuer -โ”‚ โ”œโ”€โ”€ 08-ingress.yaml # cyanburu.com -โ”‚ โ”œโ”€โ”€ 09-ingress-gitea.yaml -โ”‚ โ””โ”€โ”€ 10-ingress-argocd.yaml -โ”œโ”€โ”€ 06-argocd-app.yaml # ArgoCD Application (k8s/ ํด๋” ๋ฐ– โ€” ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€) +โ”‚ โ”œโ”€โ”€ 00-hub.yaml # hub ๋„ค์ž„์ŠคํŽ˜์ด์Šค + Deployment + Service +โ”‚ โ”œโ”€โ”€ 01-namespace.yaml # web-portal ๋„ค์ž„์ŠคํŽ˜์ด์Šค +โ”‚ โ”œโ”€โ”€ 02-postgres.yaml # PostgreSQL + PVC + Service +โ”‚ โ”œโ”€โ”€ 03-secrets.yaml # DB/JWT ์‹œํฌ๋ฆฟ +โ”‚ โ”œโ”€โ”€ 04-backend.yaml # FastAPI Deployment + Service +โ”‚ โ”œโ”€โ”€ 05-frontend.yaml # Nginx Deployment + NodePort(30090) +โ”‚ โ”œโ”€โ”€ 07-clusterissuer.yaml # Let's Encrypt ClusterIssuer +โ”‚ โ”œโ”€โ”€ 08-ingress.yaml # Web Portal Ingress (cyanburu.com/portal) +โ”‚ โ”œโ”€โ”€ 09-ingress-gitea.yaml # Gitea Ingress (gitea.cyanburu.com) +โ”‚ โ”œโ”€โ”€ 10-ingress-argocd.yaml # ArgoCD Ingress (argo.cyanburu.com) +โ”‚ โ”œโ”€โ”€ 11-notify-secrets.yaml # Discord Webhook / Gmail Secret +โ”‚ โ”œโ”€โ”€ 12-???.yaml # ๊ธฐ์กด ํŒŒ์ผ +โ”‚ โ””โ”€โ”€ 13-ingress-hub.yaml # Hub Ingress (cyanburu.com/) +โ”œโ”€โ”€ 06-argocd-app.yaml # ArgoCD Application ์ •์˜ (k8s ํด๋” ๋ฐ–์— ์œ„์น˜) โ””โ”€โ”€ README.md ``` +> โš ๏ธ `06-argocd-app.yaml` ์€ ๋ฐ˜๋“œ์‹œ `k8s/` ํด๋” **๋ฐ–**์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +> ArgoCD๊ฐ€ `k8s/` ํด๋”๋ฅผ ๊ฐ์‹œํ•˜๋ฏ€๋กœ ํ•ด๋‹น ํŒŒ์ผ์ด ์•ˆ์— ์žˆ์œผ๋ฉด ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + --- -## ๐Ÿš€ ๋ฐฐํฌ ๋ฐฉ๋ฒ• +## ๐Ÿ”‘ ๊ธฐ๋ณธ ๊ณ„์ • -### ์‚ฌ์ „ ์š”๊ตฌ ์‚ฌํ•ญ -- Docker Desktop (Kubernetes ํ™œ์„ฑํ™”) -- kubectl, helm ์„ค์น˜ -- ๊ณต์ธ ๋„๋ฉ”์ธ (A ๋ ˆ์ฝ”๋“œ โ†’ ๊ณต์ธ IP ์—ฐ๊ฒฐ) -- ๋ผ์šฐํ„ฐ ํฌํŠธํฌ์›Œ๋”ฉ ์„ค์ • (80, 443) +| ๊ตฌ๋ถ„ | ID | Password | +|------|-----|----------| +| ๊ด€๋ฆฌ์ž | `admin` | `admin1234` | +| ์ผ๋ฐ˜์‚ฌ์šฉ์ž | `user1` | `user1234` | -### ํ•ต์‹ฌ ๋ฐฐํฌ ์ˆœ์„œ +--- +## โœจ ๊ธฐ๋Šฅ ์„ค๋ช… + +### ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž +- ๋กœ๊ทธ์ธ ํ›„ **MY Page List** ์—์„œ ๋ณธ์ธ์—๊ฒŒ ํ• ๋‹น๋œ ์›นํŽ˜์ด์ง€๋ฅผ ์นด๋“œ ํ˜•ํƒœ๋กœ ํ™•์ธ +- ์นด๋“œ์˜ Favicon ์ž๋™ ํ‘œ์‹œ (์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ์•„์ด์ฝ˜) +- ์นด๋“œ ํด๋ฆญ ์‹œ **์ƒˆ ํƒญ์—์„œ ํ•ด๋‹น URL๋กœ ์ด๋™** +- **๊ณต์ง€์‚ฌํ•ญ** ํƒญ์—์„œ ๊ด€๋ฆฌ์ž๊ฐ€ ๋“ฑ๋กํ•œ ๊ณต์ง€ ํ™•์ธ ๋ฐ ๋Œ“๊ธ€ ์ž‘์„ฑ +- **๊ด€๋ฆฌ์ž ์š”์ฒญ** ํƒญ์—์„œ ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ๋ฐ ๋‹ต๊ธ€ ์ž‘์„ฑ +- ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ‘œ์‹œ/์ˆจ๊น€ ํ† ๊ธ€ ๋ฒ„ํŠผ +- ๋กœ๊ทธ์ธ ์‹คํŒจ ์‹œ ์•„์ด๋”” ์œ ์ง€ (๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ์ดˆ๊ธฐํ™”) +- **๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ** ๋ฉ”๋‰ด (ํ—ค๋”์—์„œ ์–ธ์ œ๋“  ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ) +- **์ตœ์ดˆ ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•์ œ ๋ณ€๊ฒฝ** (๋ณ€๊ฒฝ ์ „๊นŒ์ง€ ์„œ๋น„์Šค ์ด์šฉ ๋ถˆ๊ฐ€) +- **๋น„๋ฐ€๋ฒˆํ˜ธ 5ํšŒ ์˜ค๋ฅ˜ ์‹œ ๊ณ„์ • ์ž๋™ ์ž ๊ธˆ** โ†’ ๊ด€๋ฆฌ์ž์—๊ฒŒ ์ž ๊ธˆ ํ•ด์ œ ์š”์ฒญ ํ•„์š” + +### ๊ด€๋ฆฌ์ž +์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ๊ธฐ๋Šฅ + ์ถ”๊ฐ€: +- **ํŽ˜์ด์ง€ ๊ด€๋ฆฌ**: ์›นํŽ˜์ด์ง€ ์ถ”๊ฐ€ / ์ˆ˜์ • / ์‚ญ์ œ +- **์‚ฌ์šฉ์ž ๊ด€๋ฆฌ**: ๊ณ„์ • ์ƒ์„ฑ / ์‚ญ์ œ +- **๊ถŒํ•œ ์„ค์ •**: ์‚ฌ์šฉ์ž๋ณ„ ์ ‘๊ทผ ๊ฐ€๋Šฅ ํŽ˜์ด์ง€๋ฅผ ์ฒดํฌ๋ฐ•์Šค๋กœ ์ง€์ • +- **๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ**: ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ง์ ‘ ๋ณ€๊ฒฝ (๋ณ€๊ฒฝ ํ›„ ํ•ด๋‹น ์‚ฌ์šฉ์ž ๊ฐ•์ œ ๋ณ€๊ฒฝ ์ ์šฉ) +- **์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰**: ๋žœ๋ค ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž๋™ ์ƒ์„ฑ ํ›„ ํ™”๋ฉด์— ํ‘œ์‹œ +- **๊ณ„์ • ์ž ๊ธˆ ํ•ด์ œ**: ์ž ๊ธด ๊ณ„์ •์„ ๋ฒ„ํŠผ ํ•˜๋‚˜๋กœ ํ•ด์ œ +- **์‚ฌ์šฉ์ž ์ƒํƒœ ํ™•์ธ**: ์ •์ƒ / ๐Ÿ”’์ž ๊น€ / ์ดˆ๊ธฐPW / ๋ณ€๊ฒฝ์š”์ฒญ ํƒœ๊ทธ๋กœ ํ•œ๋ˆˆ์— ํ™•์ธ +- **๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ**: ๊ณต์ง€ ํƒญ์—์„œ ์ „์ฒด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต์ง€ ๋“ฑ๋ก / ์‚ญ์ œ +- **๐Ÿ  ํ™ˆ ์นด๋“œ ๊ด€๋ฆฌ**: `cyanburu.com` ๋ฉ”์ธ ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€์— ํ‘œ์‹œ๋  ์นด๋“œ ์ถ”๊ฐ€ / ์ˆ˜์ • / ์‚ญ์ œ / ์ˆœ์„œ ๋ณ€๊ฒฝ + +### ๊ฒŒ์‹œํŒ (๊ณต์ง€ / ๊ด€๋ฆฌ์ž ์š”์ฒญ) + +| ๊ตฌ๋ถ„ | ๊ณต์ง€ | ๊ด€๋ฆฌ์ž ์š”์ฒญ | +|------|------|------------| +| ๊ธ€ ์ž‘์„ฑ | ๊ด€๋ฆฌ์ž๋งŒ ๊ฐ€๋Šฅ | ๋ชจ๋“  ์‚ฌ์šฉ์ž ๊ฐ€๋Šฅ | +| ๋Œ“๊ธ€/๋‹ต๊ธ€ | ๋ชจ๋“  ์‚ฌ์šฉ์ž ๊ฐ€๋Šฅ | ๋ชจ๋“  ์‚ฌ์šฉ์ž ๊ฐ€๋Šฅ | +| ๊ธ€ ์‚ญ์ œ | ๊ด€๋ฆฌ์ž๋งŒ ๊ฐ€๋Šฅ | ๋ณธ์ธ ๋˜๋Š” ๊ด€๋ฆฌ์ž | +| ๋Œ“๊ธ€ ์‚ญ์ œ | ๋ณธ์ธ ๋˜๋Š” ๊ด€๋ฆฌ์ž | ๋ณธ์ธ ๋˜๋Š” ๊ด€๋ฆฌ์ž | +| ๋ชฉ๋ก ํ•ญ๋ชฉ | ๋ฒˆํ˜ธ, ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ(์‹œ๊ฐ„ํฌํ•จ) | ๋ฒˆํ˜ธ, ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ(์‹œ๊ฐ„ํฌํ•จ) | + +--- + +## ๐Ÿš€ ์ตœ์ดˆ ๋ฐฐํฌ ์ˆœ์„œ + +### 1๋‹จ๊ณ„. Docker Desktop insecure-registry ์„ค์ • +Gitea Registry๊ฐ€ HTTP์ด๋ฏ€๋กœ Docker Desktop์—์„œ ํ—ˆ์šฉ ์„ค์ • ํ•„์š”. + +Docker Desktop โ†’ Settings โ†’ Docker Engine: +```json +{ + "builder": { + "gc": { + "defaultKeepStorage": "20GB", + "enabled": true + } + }, + "experimental": false, + "insecure-registries": ["192.168.10.101:30000"] +} +``` +**Apply & Restart** ํด๋ฆญ + +### 2๋‹จ๊ณ„. Gitea Registry ๋กœ๊ทธ์ธ ๋ฐ ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Push ```bash -# 1. Nginx Ingress Controller -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml +# Registry ๋กœ๊ทธ์ธ +docker login 192.168.10.101:30000 -u -# 2. cert-manager -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml +# ๋ฐฑ์—”๋“œ ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Push +docker build -t 192.168.10.101:30000/<๊ณ„์ •>/portal-backend:latest ./backend/ +docker push 192.168.10.101:30000/<๊ณ„์ •>/portal-backend:latest -# 3. CoreDNS ํ—ค์–ดํ•€ NAT ์šฐํšŒ ์„ค์ • -kubectl patch configmap coredns -n kube-system --patch-file coredns-patch.yaml -kubectl rollout restart deployment/coredns -n kube-system +# ํ”„๋ก ํŠธ์—”๋“œ ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Push +docker build -t 192.168.10.101:30000/<๊ณ„์ •>/portal-frontend:latest ./frontend/ +docker push 192.168.10.101:30000/<๊ณ„์ •>/portal-frontend:latest +``` -# 4. ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Push -docker build -t /portal-backend:latest ./backend/ -docker build -t /portal-frontend:latest ./frontend/ -docker push /portal-backend:latest -docker push /portal-frontend:latest +### 3๋‹จ๊ณ„. K8s Registry Secret ์ƒ์„ฑ +```bash +kubectl create namespace web-portal -# 5. K8s ๋ฆฌ์†Œ์Šค ๋ฐฐํฌ -kubectl apply -f k8s/ +kubectl create secret docker-registry gitea-registry-secret \ + --namespace=web-portal \ + --docker-server=gitea-http.gitea.svc.cluster.local:3000 \ + --docker-username= \ + --docker-password= +``` -# 6. ArgoCD Application ๋“ฑ๋ก +### 4๋‹จ๊ณ„. ArgoCD์— Gitea ์ €์žฅ์†Œ ์ธ์ฆ ๋“ฑ๋ก +```bash +kubectl create secret generic gitea-repo-secret \ + --namespace=argocd \ + --from-literal=type=git \ + --from-literal=url=http://192.168.10.101:30000/<๊ณ„์ •>/nginx-portal.git \ + --from-literal=username= \ + --from-literal=password= + +kubectl label secret gitea-repo-secret \ + -n argocd \ + argocd.argoproj.io/secret-type=repository +``` + +### 5๋‹จ๊ณ„. ArgoCD Application ๋“ฑ๋ก +```bash kubectl apply -f 06-argocd-app.yaml ``` -์ž์„ธํ•œ ๋ฐฐํฌ ๊ฐ€์ด๋“œ๋Š” [๋ฐฐํฌ ๋ฌธ์„œ](docs/deployment.md)๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. +### 6๋‹จ๊ณ„. Nginx Ingress Controller ์„ค์น˜ (์ตœ์ดˆ 1ํšŒ) +```bash +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml +kubectl get pods -n ingress-nginx +``` + +### 7๋‹จ๊ณ„. cert-manager ์„ค์น˜ (์ตœ์ดˆ 1ํšŒ) +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml +kubectl get pods -n cert-manager +``` + +### 8๋‹จ๊ณ„. CoreDNS ๋‚ด๋ถ€ ๋„๋ฉ”์ธ ๋“ฑ๋ก (ํ—ค์–ดํ•€ NAT ์šฐํšŒ) +```bash +kubectl patch configmap coredns -n kube-system --patch-file coredns-patch.yaml +kubectl rollout restart deployment/coredns -n kube-system +``` + +> `coredns-patch.yaml` ๋‚ด์šฉ: +> ```yaml +> data: +> Corefile: | +> cyanburu.com { +> hosts { +> 192.168.10.101 cyanburu.com +> fallthrough +> } +> cache 30 +> } +> .:53 { +> ... (๊ธฐ์กด ๋‚ด์šฉ ์œ ์ง€) +> } +> ``` + +### 9๋‹จ๊ณ„. ๋ผ์šฐํ„ฐ ํฌํŠธํฌ์›Œ๋”ฉ ์„ค์ • +MSI ๋ผ์šฐํ„ฐ์—์„œ ์„ค์ •: + +| ๊ณต์šฉ ํฌํŠธ | ๋‚ด๋ถ€ IP | ๋น„๊ณต๊ฐœ ํฌํŠธ | +|-----------|---------|-------------| +| 80 | 192.168.10.101 | 80 | +| 443 | 192.168.10.101 | 443 | + +### 10๋‹จ๊ณ„. Ingress + ClusterIssuer ๋ฐฐํฌ +```bash +git add k8s/07-clusterissuer.yaml k8s/08-ingress.yaml k8s/05-frontend.yaml +git commit -m "feat: Ingress + cert-manager HTTPS ์„ค์ •" +git push origin main +# ArgoCD๊ฐ€ ์ž๋™ ๋ฐฐํฌ +``` + +### 11๋‹จ๊ณ„. ์ธ์ฆ์„œ ๋ฐœ๊ธ‰ ํ™•์ธ +```bash +kubectl get certificate -n web-portal +# READY: True ํ™•์ธ +``` + +### 12๋‹จ๊ณ„. ์„œ๋ธŒ๋„๋ฉ”์ธ Ingress ์ ์šฉ (Gitea, ArgoCD) +```bash +kubectl apply -f 09-ingress-gitea.yaml +kubectl apply -f 10-ingress-argocd.yaml + +# ์ธ์ฆ์„œ ๋ฐœ๊ธ‰ ํ™•์ธ +kubectl get certificate -n gitea +kubectl get certificate -n argocd +``` + +### 13๋‹จ๊ณ„. ์ ‘์† ํ™•์ธ + +| ์„œ๋น„์Šค | URL | +|--------|-----| +| Hub ํ™ˆํŽ˜์ด์ง€ | `https://cyanburu.com` | +| Web Portal | `https://cyanburu.com/portal` | +| Gitea | `https://gitea.cyanburu.com` | +| ArgoCD | `https://argo.cyanburu.com` | + +### 14๋‹จ๊ณ„. Hub ํ™ˆํŽ˜์ด์ง€ ๋ฐฐํฌ (์ตœ์ดˆ 1ํšŒ) +```bash +# hub ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ +kubectl create namespace hub + +# Registry Secret ์ƒ์„ฑ +kubectl create secret docker-registry gitea-registry-secret \ + --namespace=hub \ + --docker-server=192.168.10.101:30000 \ + --docker-username=<๊ณ„์ •> \ + --docker-password=<ํŒจ์Šค์›Œ๋“œ> + +# ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Push +docker build -t 192.168.10.101:30000/<๊ณ„์ •>/hub:latest ./hub/ +docker push 192.168.10.101:30000/<๊ณ„์ •>/hub:latest + +# ๋ฐฐํฌ +kubectl apply -f k8s/00-hub.yaml +kubectl apply -f k8s/13-ingress-hub.yaml +``` --- -## ๐Ÿ“Š ๋ฐฐ์šด ์  / ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ +## ๐Ÿ”„ ์ดํ›„ ๋ฐฐํฌ ๋ฐฉ๋ฒ• (์ฝ”๋“œ ์ˆ˜์ • ์‹œ) -| ๋ฌธ์ œ ์œ ํ˜• | ๋ฐฐ์šด ์  | -|-----------|---------| -| **ํ—ค์–ดํ•€ NAT** | K8s ๋‚ด๋ถ€์—์„œ ์™ธ๋ถ€ ๋„๋ฉ”์ธ์œผ๋กœ self-check ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋„คํŠธ์›Œํฌ ๋ฃจํ”„ โ†’ CoreDNS ๋‚ด๋ถ€ ์˜ค๋ฒ„๋ผ์ด๋“œ๋กœ ํ•ด๊ฒฐ | -| **Docker ๋ฐ๋ชฌ vs kubelet DNS** | kubelet์€ K8s ๋‚ด๋ถ€ DNS ์‚ฌ์šฉ ๊ฐ€๋Šฅ, Docker ๋ฐ๋ชฌ์€ ํ˜ธ์ŠคํŠธ DNS๋งŒ ์‚ฌ์šฉ โ†’ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ฃผ์†Œ ํ†ต์ผ ํ•„์š” | -| **GitOps ์ˆœํ™˜ ์ฐธ์กฐ** | ArgoCD Application YAML์„ ๊ฐ์‹œ ๋Œ€์ƒ ํด๋” ์•ˆ์— ๋„ฃ์œผ๋ฉด ๋ฌดํ•œ ๋ฃจํ”„ โ†’ ํด๋” ๋ฐ– ๋ณ„๋„ ๊ด€๋ฆฌ | -| **์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ ์ˆœ์„œ** | DB ์ค€๋น„ ์ „ ๋ฐฑ์—”๋“œ probe ์‹คํ–‰ โ†’ `initialDelaySeconds`๋กœ ์˜์กด์„ฑ ์ˆœ์„œ ์ œ์–ด | -| **bcrypt ํ•ด์‹œ ์˜ค์—ผ** | ํ„ฐ๋ฏธ๋„ ANSI ์ƒ‰์ƒ ์ฝ”๋“œ๊ฐ€ ํ•ด์‹œ์— ์„ž์ด๋Š” ์—ฃ์ง€ ์ผ€์ด์Šค โ†’ ์ถœ๋ ฅ๊ฐ’ ์ง์ ‘ ๊ฒ€์ฆ ํ•„์š” | +### yaml๋งŒ ๋ณ€๊ฒฝํ•œ ๊ฒฝ์šฐ (์„ค์ • ๋ณ€๊ฒฝ) +```bash +git add . +git commit -m "fix: ๋ณ€๊ฒฝ๋‚ด์šฉ" +git push origin main +# โ†’ ArgoCD๊ฐ€ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ด์„œ ์žฌ๋ฐฐํฌ +``` + +### ์ด๋ฏธ์ง€๋„ ๋ณ€๊ฒฝํ•œ ๊ฒฝ์šฐ (์ฝ”๋“œ ๋ณ€๊ฒฝ) +```bash +# ์ด๋ฏธ์ง€ ์žฌ๋นŒ๋“œ & Push +docker build -t 192.168.10.101:30000/<๊ณ„์ •>/portal-backend:latest ./backend/ +docker push 192.168.10.101:30000/<๊ณ„์ •>/portal-backend:latest + +# Pod ์žฌ์‹œ์ž‘ +kubectl rollout restart deployment/backend -n web-portal + +# yaml๋„ ๋ณ€๊ฒฝํ–ˆ๋‹ค๋ฉด +git add . +git commit -m "feat: ๋ณ€๊ฒฝ๋‚ด์šฉ" +git push origin main +``` --- -## ๐Ÿ”œ ๊ฐœ์„  ์˜ˆ์ • (Next Steps) +## ๐Ÿ”ง ์šด์˜ ๋ช…๋ น์–ด -- [ ] GitHub Actions CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ (์ž๋™ ๋นŒ๋“œ โ†’ Push โ†’ ArgoCD sync) -- [ ] Helm Chart๋กœ ํŒจํ‚ค์ง• (values.yaml ํ™˜๊ฒฝ๋ณ„ ๋ถ„๋ฆฌ) -- [ ] Prometheus + Grafana ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํƒ ๋„์ž… -- [ ] Terraform์œผ๋กœ Azure ์ธํ”„๋ผ IaC ๊ด€๋ฆฌ -- [ ] Trivy ์ด๋ฏธ์ง€ ์ทจ์•ฝ์  ์Šค์บ” โ†’ GitHub Actions ํ†ตํ•ฉ +```bash +# ์ „์ฒด ๋ฆฌ์†Œ์Šค ์ƒํƒœ ํ™•์ธ +kubectl get all -n web-portal + +# ๋ฐฑ์—”๋“œ ๋กœ๊ทธ ํ™•์ธ +kubectl logs -n web-portal deployment/backend -f + +# ํ”„๋ก ํŠธ์—”๋“œ ๋กœ๊ทธ ํ™•์ธ +kubectl logs -n web-portal deployment/frontend -f + +# Pod ์žฌ์‹œ์ž‘ +kubectl rollout restart deployment/backend -n web-portal +kubectl rollout restart deployment/frontend -n web-portal + +# ์ „์ฒด ์‚ญ์ œ +kubectl delete namespace web-portal +``` --- -## ๐Ÿ“„ ๋ผ์ด์„ ์Šค +## ๐Ÿ” ์šด์˜ ์‹œ ๋ณด์•ˆ ์„ค์ • -MIT License +### K8s Secret ๋ณ€๊ฒฝ +`k8s/03-secrets.yaml` ์—์„œ ๋ฐ˜๋“œ์‹œ ๋ณ€๊ฒฝ: +```yaml +stringData: + db-password: "๊ฐ•๋ ฅํ•œํŒจ์Šค์›Œ๋“œ๋กœ๋ณ€๊ฒฝ" + jwt-secret: "64์ž์ด์ƒ์˜๋žœ๋ค๋ฌธ์ž์—ด๋กœ๋ณ€๊ฒฝ" +``` + +### Nginx Rate Limiting (Brute Force ๋ฐฉ์–ด) +`frontend/nginx.conf` ์ƒ๋‹จ์— ์ถ”๊ฐ€: +```nginx +limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m; +``` +๋กœ๊ทธ์ธ location์— ์ ์šฉ: +```nginx +location /api/auth/login { + limit_req zone=login_limit burst=3 nodelay; + limit_req_status 429; + proxy_pass http://backend-service.web-portal.svc.cluster.local:8000/api/auth/login; + ... +} +``` +๊ฐ™์€ IP์—์„œ ๋ถ„๋‹น 5ํšŒ ์ดˆ๊ณผ ์‹œ 429 ์‘๋‹ต ๋ฐ˜ํ™˜. ๊ณ„์ • ์ž ๊ธˆ(5ํšŒ ์‹คํŒจ)๊ณผ ํ•จ๊ป˜ ์ด์ค‘์œผ๋กœ Brute Force๋ฅผ ๋ฐฉ์–ดํ•ฉ๋‹ˆ๋‹ค. + +### ์ถ”๊ฐ€ ๋ณด์•ˆ ๊ถŒ๊ณ ์‚ฌํ•ญ +- **Fail2ban**: Nginx ๋กœ๊ทธ ๊ฐ์‹œ ํ›„ ๋ฐ˜๋ณต ์‹คํŒจ IP๋ฅผ ๋ฐฉํ™”๋ฒฝ์œผ๋กœ ์ž๋™ ์ฐจ๋‹จ (์˜จํ”„๋ ˆ๋ฏธ์Šค ํ™˜๊ฒฝ ๊ถŒ์žฅ) +- **CAPTCHA**: ๋กœ๊ทธ์ธ 3ํšŒ ์‹คํŒจ ์‹œ Google reCAPTCHA ํ‘œ์‹œ (์ถ”๊ฐ€ ๊ฐœ๋ฐœ ํ•„์š”) +- **JWT ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋‹จ์ถ•**: `main.py` ์—์„œ `timedelta(hours=8)` โ†’ `timedelta(hours=2)` ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ + +### HTTPS / TLS +- cert-manager๊ฐ€ Let's Encrypt ์ธ์ฆ์„œ๋ฅผ **์ž๋™์œผ๋กœ ๊ฐฑ์‹ ** (๋งŒ๋ฃŒ 30์ผ ์ „) +- HTTP ์ ‘์† ์‹œ ์ž๋™์œผ๋กœ HTTPS๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (`ssl-redirect: "true"`) +- ์ธ์ฆ์„œ ์ƒํƒœ ํ™•์ธ: +```bash +kubectl get certificate -n web-portal # cyanburu.com +kubectl get certificate -n gitea # gitea.cyanburu.com +kubectl get certificate -n argocd # argo.cyanburu.com +``` + +--- + +## โ— ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… + +### 1. NodePort ํฌํŠธ ์ถฉ๋Œ +**์ฆ์ƒ** +``` +Service "frontend-service" is invalid: spec.ports[0].nodePort: +Invalid value: 30080: provided port is already allocated +``` +**์›์ธ** ํ•ด๋‹น NodePort๊ฐ€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘. + +**ํ•ด๊ฒฐ** +`k8s/05-frontend.yaml` ์—์„œ nodePort๋ฅผ ๋‹ค๋ฅธ ๋ฒˆํ˜ธ๋กœ ๋ณ€๊ฒฝ (30000~32767 ๋ฒ”์œ„): +```yaml +nodePort: 30090 +``` +๋ณ€๊ฒฝ ํ›„ ๊ธฐ์กด ์„œ๋น„์Šค ์‚ญ์ œ ๋ฐ ์žฌ์ ์šฉ: +```bash +kubectl delete service frontend-service -n web-portal +kubectl apply -f k8s/05-frontend.yaml +``` + +--- + +### 2. Backend Liveness Probe ์‹คํŒจ๋กœ ์ธํ•œ CrashLoopBackOff +**์ฆ์ƒ** +``` +Liveness probe failed: HTTP probe failed with statuscode: 404 +Back-off restarting failed container +``` +**์›์ธ** Backend๊ฐ€ DB ์—ฐ๊ฒฐ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ liveness probe๊ฐ€ ๋จผ์ € ์‹คํŒจํ•ด์„œ K8s๊ฐ€ ๊ฐ•์ œ ์žฌ์‹œ์ž‘. + +**ํ•ด๊ฒฐ** +`k8s/04-backend.yaml` ์˜ probe ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€: +```yaml +readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + failureThreshold: 6 +livenessProbe: + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 5 +``` + +--- + +### 3. Nginx๊ฐ€ API ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ๋กœ ํ”„๋ก์‹œ ๋ชปํ•จ +**์ฆ์ƒ** ๋กœ๊ทธ์ธ ์‹œ `Unexpected token '<', "..." is not valid JSON` ์—๋Ÿฌ. + +**์›์ธ** Nginx๊ฐ€ `/api/` ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ๋กœ ์ „๋‹ฌํ•˜์ง€ ๋ชปํ•˜๊ณ  HTML์„ ๋ฐ˜ํ™˜. + +**ํ•ด๊ฒฐ** +`frontend/nginx.conf` ์—์„œ ๋ฐฑ์—”๋“œ ์ฃผ์†Œ๋ฅผ FQDN์œผ๋กœ ๋ณ€๊ฒฝ: +```nginx +location /api/ { + proxy_pass http://backend-service.web-portal.svc.cluster.local:8000/api/; +} +``` + +--- + +### 4. ArgoCD - authentication required: Unauthorized +**์ฆ์ƒ** +``` +failed to list refs: authentication required: Unauthorized +``` +**์›์ธ** ArgoCD๊ฐ€ Gitea ์ €์žฅ์†Œ์— ์ ‘๊ทผํ•  ์ธ์ฆ ์ •๋ณด๊ฐ€ ์—†์Œ. + +**ํ•ด๊ฒฐ** +kubectl๋กœ ์ง์ ‘ ์ธ์ฆ Secret ์ƒ์„ฑ: +```bash +kubectl create secret generic gitea-repo-secret \ + --namespace=argocd \ + --from-literal=type=git \ + --from-literal=url=http://192.168.10.101:30000/<๊ณ„์ •>/nginx-portal.git \ + --from-literal=username=<๊ณ„์ •> \ + --from-literal=password=<ํŒจ์Šค์›Œ๋“œ> + +kubectl label secret gitea-repo-secret \ + -n argocd \ + argocd.argoproj.io/secret-type=repository +``` + +--- + +### 5. RepeatedResourceWarning - ๋ฆฌ์†Œ์Šค ์ค‘๋ณต +**์ฆ์ƒ** +``` +Resource apps/Deployment/web-portal/backend appeared 2 times among application resources +``` +**์›์ธ** `k8s/` ํด๋” ์•ˆ์— ๋™์ผํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์˜ํ•˜๋Š” yaml ํŒŒ์ผ์ด ์ค‘๋ณต ์กด์žฌ (`portal.yaml` ๋“ฑ). + +**ํ•ด๊ฒฐ** +์ค‘๋ณต ํŒŒ์ผ ์‚ญ์ œ ํ›„ push: +```bash +rm k8s/portal.yaml +git add . +git commit -m "fix: ์ค‘๋ณต yaml ํŒŒ์ผ ์ œ๊ฑฐ" +git push origin main +``` + +--- + +### 6. ImagePullBackOff - Gitea Registry HTTP ์ ‘๊ทผ ์˜ค๋ฅ˜ +**์ฆ์ƒ** +``` +Failed to pull image: server gave HTTP response to HTTPS client +``` +**์›์ธ** Gitea Registry๊ฐ€ HTTP์ธ๋ฐ Docker๊ฐ€ HTTPS๋กœ ์ ‘๊ทผ ์‹œ๋„. + +**ํ•ด๊ฒฐ** +Docker Desktop โ†’ Settings โ†’ Docker Engine์— insecure-registry ์ถ”๊ฐ€: +```json +{ + "insecure-registries": ["192.168.10.101:30000"] +} +``` +Apply & Restart ํ›„ ์ด๋ฏธ์ง€ ์žฌ๋นŒ๋“œ & Push. + +--- + +### 7. Gitea Container Registry ๋กœ๊ทธ์ธ ์‹คํŒจ (context deadline exceeded) +**์ฆ์ƒ** +``` +Error response from daemon: Get "http://192.168.10.101:30000/v2/": +context deadline exceeded +``` +**์›์ธ** Gitea์˜ ROOT_URL์ด `localhost` ๋กœ ์„ค์ •๋˜์–ด ์žˆ์–ด token ์š”์ฒญ์ด ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ๋ชปํ•จ. +๋˜ํ•œ Packages(Container Registry) ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋œ ์ƒํƒœ. + +**ํ•ด๊ฒฐ** +Helm upgrade๋กœ Gitea ์„ค์ • ์˜๊ตฌ ๋ณ€๊ฒฝ: +```bash +helm repo add gitea https://dl.gitea.com/charts/ +helm repo update + +helm upgrade gitea gitea/gitea -n gitea \ + --set gitea.config.server.DOMAIN=192.168.10.101 \ + --set gitea.config.server.ROOT_URL=http://192.168.10.101:30000 \ + --set gitea.config.server.HTTP_PORT=3000 \ + --set gitea.config.packages.ENABLED=true \ + --set service.http.type=NodePort \ + --set service.http.nodePort=30000 \ + --reuse-values +``` + +--- + +### 8. K8s ๋‚ด๋ถ€์—์„œ Gitea Registry ์ด๋ฏธ์ง€ Pull ์‹คํŒจ +**์ฆ์ƒ** Pod๊ฐ€ `ImagePullBackOff` ์ƒํƒœ. ์™ธ๋ถ€์—์„œ๋Š” push๊ฐ€ ๋˜์ง€๋งŒ K8s Pod๋Š” ์ด๋ฏธ์ง€๋ฅผ ๋ชป ๊ฐ€์ ธ์˜ด. + +**์›์ธ** K8s Pod๋Š” ์™ธ๋ถ€ IP(`192.168.10.101:30000`)๋กœ ์ ‘๊ทผ์ด ๋ถˆ์•ˆ์ •ํ•˜๋ฏ€๋กœ +๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•จ. + +**ํ•ด๊ฒฐ** +yaml์˜ image ์ฃผ์†Œ๋ฅผ K8s ๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…์œผ๋กœ ๋ณ€๊ฒฝ: +```bash +sed -i "s|192.168.10.101:30000|gitea-http.gitea.svc.cluster.local:3000|g" k8s/04-backend.yaml +sed -i "s|192.168.10.101:30000|gitea-http.gitea.svc.cluster.local:3000|g" k8s/05-frontend.yaml +``` +Registry Secret๋„ ๋‚ด๋ถ€ ์ฃผ์†Œ๋กœ ์žฌ์ƒ์„ฑ: +```bash +kubectl delete secret gitea-registry-secret -n web-portal + +kubectl create secret docker-registry gitea-registry-secret \ + --namespace=web-portal \ + --docker-server=gitea-http.gitea.svc.cluster.local:3000 \ + --docker-username=<๊ณ„์ •> \ + --docker-password=<ํŒจ์Šค์›Œ๋“œ> +``` + +--- + +### 9. ๋กœ๊ทธ์ธ ์‹คํŒจ - ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ ์˜ค์—ผ +**์ฆ์ƒ** ์˜ฌ๋ฐ”๋ฅธ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด๋„ `์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค` ์ถœ๋ ฅ. + +**์›์ธ** ํ„ฐ๋ฏธ๋„ ์ƒ‰์ƒ ์ฝ”๋“œ(`\x1B[0m` ๋“ฑ)๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ์— ์„ž์—ฌ DB์— ์ €์žฅ๋จ. + +**ํ•ด๊ฒฐ** +Backend Pod์—์„œ ์ง์ ‘ Python์œผ๋กœ ํ•ด์‹œ ์žฌ์ƒ์„ฑ ํ›„ DB ์—…๋ฐ์ดํŠธ: +```bash +kubectl exec -n web-portal deployment/backend -- python3 -c " +import bcrypt, psycopg2 +conn = psycopg2.connect(host='postgres-service', database='portaldb', user='portaluser', password='portalpass') +cur = conn.cursor() +h1 = bcrypt.hashpw('admin1234'.encode(), bcrypt.gensalt()).decode() +h2 = bcrypt.hashpw('user1234'.encode(), bcrypt.gensalt()).decode() +cur.execute('UPDATE users SET password_hash=%s WHERE username=%s', (h1, 'admin')) +cur.execute('UPDATE users SET password_hash=%s WHERE username=%s', (h2, 'user1')) +conn.commit() +print('์™„๋ฃŒ') +" +``` + +--- + +### 10. git push ๊ฑฐ์ ˆ (non-fast-forward) +**์ฆ์ƒ** +``` +error: failed to push some refs +hint: Updates were rejected because the tip of your current branch is behind +``` +**์›์ธ** Gitea UI์—์„œ ์ง์ ‘ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ด์„œ ๋กœ์ปฌ๊ณผ ์›๊ฒฉ ๋ธŒ๋žœ์น˜๊ฐ€ diverge๋œ ์ƒํƒœ. + +**ํ•ด๊ฒฐ** +```bash +git pull origin main --rebase +git push origin main +``` + +--- + +### 11. cert-manager HTTP01 Challenge pending (ํ—ค์–ดํ•€ NAT) +**์ฆ์ƒ** +``` +propagation check failed: failed to perform self check GET request +context deadline exceeded (Client.Timeout exceeded while awaiting headers) +``` +**์›์ธ** cert-manager๊ฐ€ K8s ๋‚ด๋ถ€์—์„œ ์™ธ๋ถ€ ๋„๋ฉ”์ธ(`cyanburu.com`)์œผ๋กœ self-check ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, +๊ณต์ธ IP โ†’ ๋ผ์šฐํ„ฐ โ†’ ๋‚ด๋ถ€ PC๋กœ ๋Œ์•„์˜ค๋Š” ํ—ค์–ดํ•€ NAT์ด ์ง€์›๋˜์ง€ ์•Š์•„ ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ. + +**ํ•ด๊ฒฐ** +CoreDNS์— ๋‚ด๋ถ€ ๋„๋ฉ”์ธ์„ ์ง์ ‘ ๋“ฑ๋กํ•ด์„œ K8s ๋‚ด๋ถ€์—์„œ ๋„๋ฉ”์ธ์„ ๋‚ด๋ถ€ IP๋กœ ํ•ด์„ํ•˜๊ฒŒ ์„ค์ •: +```bash +kubectl patch configmap coredns -n kube-system --patch-file coredns-patch.yaml +kubectl rollout restart deployment/coredns -n kube-system +``` + +--- + +### 12. Ingress Controller EXTERNAL-IP๊ฐ€ localhost๋กœ ํ‘œ์‹œ +**์ฆ์ƒ** `kubectl get svc -n ingress-nginx` ์—์„œ EXTERNAL-IP๊ฐ€ `localhost` ๋กœ ํ‘œ์‹œ๋จ. + +**์›์ธ** Docker Desktop ํ™˜๊ฒฝ์˜ ์ •์ƒ์ ์ธ ๋™์ž‘. `localhost` = ์‹ค์ œ PC๋ฅผ ์˜๋ฏธ. + +**ํ•ด๊ฒฐ** ํฌํŠธํฌ์›Œ๋”ฉ์„ NodePort(30118, 30963)๊ฐ€ ์•„๋‹Œ **80, 443 โ†’ PC๋‚ด๋ถ€IP:80, 443** ์œผ๋กœ ์„ค์ •. +Docker Desktop์ด 80/443์„ ๋ฐ›์•„์„œ Ingress Controller๋กœ ์ž๋™ ์ „๋‹ฌ. + +--- + +### 13. git commit ์‹œ Author identity unknown +**์ฆ์ƒ** +``` +Author identity unknown +fatal: unable to auto-detect email address +``` +**์›์ธ** Git ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ์ƒํƒœ. + +**ํ•ด๊ฒฐ** +```bash +git config --global user.email "๊ณ„์ •@gitea.com" +git config --global user.name "๊ณ„์ •๋ช…" +``` + +--- + +### 14. Gitea Registry ImagePullBackOff โ€” ํ† ํฐ ์ธ์ฆ ํ—ค์–ดํ•€ NAT ๋ฌธ์ œ +**์ฆ์ƒ** +``` +Failed to pull image: Error response from daemon: +Get "http://gitea-http.gitea.svc.cluster.local:3000/v2/token?...": context deadline exceeded +``` +**์›์ธ** Docker Desktop ํ™˜๊ฒฝ์—์„œ kubelet์ด ์ด๋ฏธ์ง€๋ฅผ Pullํ•  ๋•Œ Docker ๋ฐ๋ชฌ์„ ํ†ตํ•ด ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ, +Docker ๋ฐ๋ชฌ์€ K8s ๋‚ด๋ถ€ DNS(`gitea-http.gitea.svc.cluster.local`)๋ฅผ ํ•ด์„ํ•˜์ง€ ๋ชปํ•จ. +Gitea Registry๊ฐ€ ํ† ํฐ ์ธ์ฆ์„ ๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋ฉด Docker ๋ฐ๋ชฌ์ด ์ ‘๊ทผ ๋ถˆ๊ฐ€ โ†’ timeout ๋ฐœ์ƒ. + +**ํ•ด๊ฒฐ** +์ด๋ฏธ์ง€ ์ฃผ์†Œ์™€ Registry Secret์„ ์™ธ๋ถ€ IP๋กœ ํ†ต์ผ: +```bash +# yaml ์ด๋ฏธ์ง€ ์ฃผ์†Œ ๋ณ€๊ฒฝ +sed -i "s|gitea-http.gitea.svc.cluster.local:3000|192.168.10.101:30000|g" k8s/04-backend.yaml +sed -i "s|gitea-http.gitea.svc.cluster.local:3000|192.168.10.101:30000|g" k8s/05-frontend.yaml + +# Registry Secret ์žฌ์ƒ์„ฑ (์™ธ๋ถ€ IP ๊ธฐ์ค€) +kubectl delete secret gitea-registry-secret -n web-portal +kubectl create secret docker-registry gitea-registry-secret \ + --namespace=web-portal \ + --docker-server=192.168.10.101:30000 \ + --docker-username=<๊ณ„์ •> \ + --docker-password=<ํŒจ์Šค์›Œ๋“œ> + +# ์ ์šฉ +kubectl apply -f k8s/04-backend.yaml +kubectl apply -f k8s/05-frontend.yaml +kubectl rollout restart deployment/backend -n web-portal +kubectl rollout restart deployment/frontend -n web-portal +``` + +> โš ๏ธ Gitea ROOT_URL์„ ๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…์œผ๋กœ ๋ณ€๊ฒฝํ•ด๋„ Docker ๋ฐ๋ชฌ์€ K8s ๋‚ด๋ถ€ DNS๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ +> Docker Desktop ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์™ธ๋ถ€ IP(`192.168.10.101:30000`)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ. + +--- + +### 15. ๋ชจ๋‹ˆํ„ฐ๋ง ์•Œ๋ฆผ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ฏธ์ ์šฉ (notifier skipping) +**์ฆ์ƒ** +``` +[NOTIFIER] Discord webhook URL not set, skipping +[NOTIFIER] Gmail config not set, skipping +``` +**์›์ธ** `kubectl set image` ๋ช…๋ น์–ด๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด deployment์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •์ด ์ดˆ๊ธฐํ™”๋จ. +๋˜๋Š” Secret ์ด๋ฆ„์ด yaml๊ณผ ๋‹ค๋ฅด๊ฒŒ ์ƒ์„ฑ๋œ ๊ฒฝ์šฐ. + +**ํ•ด๊ฒฐ** +yaml์˜ Secret ์ด๋ฆ„(`notify-secrets`)๊ณผ key ์ด๋ฆ„์„ ํ™•์ธ ํ›„ ๋™์ผํ•˜๊ฒŒ Secret ์žฌ์ƒ์„ฑ: +```bash +kubectl delete secret notify-secrets -n web-portal +kubectl create secret generic notify-secrets \ + --namespace=web-portal \ + --from-literal=discord-webhook-url="https://discord.com/api/webhooks/..." \ + --from-literal=gmail-user="๋ฐœ์†ก๊ณ„์ •@gmail.com" \ + --from-literal=gmail-app-password="xxxx xxxx xxxx xxxx" \ + --from-literal=alert-email-to="์ˆ˜์‹ ๊ณ„์ •@gmail.com" + +kubectl rollout restart deployment/backend -n web-portal +``` +yaml ๋ณ€๊ฒฝ ํ›„์—๋Š” ๋ฐ˜๋“œ์‹œ `kubectl apply -f k8s/04-backend.yaml` ๋กœ ์žฌ์ ์šฉํ•  ๊ฒƒ. + +--- + +## ๐Ÿ“… ๋ณ€๊ฒฝ ์ด๋ ฅ + +### 2026-04-06 (์ดˆ๊ธฐ ๊ตฌ์ถ•) +- Kubernetes ํ™˜๊ฒฝ ๊ตฌ์„ฑ (Docker Desktop) +- FastAPI ๋ฐฑ์—”๋“œ + Nginx ํ”„๋ก ํŠธ์—”๋“œ + PostgreSQL ๋ฐฐํฌ +- Gitea + ArgoCD GitOps ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ +- Gitea Container Registry ์—ฐ๋™ + +### 2026-04-10 (๊ธฐ๋Šฅ ์ถ”๊ฐ€ + ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ) +#### ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- **MY Page**: ํƒญ๋ช…/๋ชฉ๋ก ์ œ๋ชฉ ์˜๋ฌธ ๋ณ€๊ฒฝ, URL ๋ฏธํ‘œ๊ธฐ, Favicon ์ž๋™ ํ‘œ์‹œ +- **๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์•ˆ ๊ฐ•ํ™”** + - ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ‘œ์‹œ/์ˆจ๊น€ ํ† ๊ธ€ ๋ฒ„ํŠผ + - ๋กœ๊ทธ์ธ ์‹คํŒจ ์‹œ ์•„์ด๋”” ์œ ์ง€ (๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ์ดˆ๊ธฐํ™”) + - ์ตœ์ดˆ ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•์ œ ๋ณ€๊ฒฝ + - ๋น„๋ฐ€๋ฒˆํ˜ธ 5ํšŒ ์˜ค๋ฅ˜ ์‹œ ๊ณ„์ • ์ž๋™ ์ž ๊ธˆ + - ๊ด€๋ฆฌ์ž์˜ ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ / ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰ / ์ž ๊ธˆ ํ•ด์ œ +- **๊ณต์ง€์‚ฌํ•ญ ํƒญ**: ๊ด€๋ฆฌ์ž ์ž‘์„ฑ ์ „์šฉ, ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋Œ“๊ธ€ ๊ฐ€๋Šฅ +- **๊ด€๋ฆฌ์ž ์š”์ฒญ ํƒญ**: ๊ฒŒ์‹œํŒ ํ˜•ํƒœ, ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ž‘์„ฑ/๋‹ต๊ธ€ ๊ฐ€๋Šฅ +- **Nginx Rate Limiting**: ๋กœ๊ทธ์ธ API ๋ถ„๋‹น 5ํšŒ ์ œํ•œ (Brute Force ๋ฐฉ์–ด) + +#### ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ (HTTPS) +- **Nginx Ingress Controller** ์„ค์น˜ ๋ฐ ๊ตฌ์„ฑ +- **cert-manager** ์„ค์น˜ + Let's Encrypt ์ธ์ฆ์„œ ์ž๋™ ๋ฐœ๊ธ‰ +- **cyanburu.com** ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ (ํ›„์ด์ฆˆ) +- **MSI ๋ผ์šฐํ„ฐ** ํฌํŠธํฌ์›Œ๋”ฉ ์„ค์ • (80/443) +- **CoreDNS** ๋‚ด๋ถ€ ๋„๋ฉ”์ธ ๋“ฑ๋ก (ํ—ค์–ดํ•€ NAT ์šฐํšŒ) +- **HTTPS ์ž๋™ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ** ์ ์šฉ +- ์ตœ์ข… ์ ‘์† URL: `https://cyanburu.com` + +#### ์„œ๋ธŒ๋„๋ฉ”์ธ ์—ฐ๊ฒฐ +- **gitea.cyanburu.com** โ†’ Gitea (Let's Encrypt ์ธ์ฆ์„œ ์ž๋™ ๋ฐœ๊ธ‰) +- **argo.cyanburu.com** โ†’ ArgoCD (Let's Encrypt ์ธ์ฆ์„œ ์ž๋™ ๋ฐœ๊ธ‰) +- CoreDNS์— ์„œ๋ธŒ๋„๋ฉ”์ธ ๋‚ด๋ถ€ IP ๋“ฑ๋ก (ํ—ค์–ดํ•€ NAT ์šฐํšŒ) + +### 2026-04-27 (๋ชจ๋‹ˆํ„ฐ๋ง ์•Œ๋ฆผ ์ถ”๊ฐ€ + ์žฅ์•  ๋ณต๊ตฌ) + +#### ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- **Discord ์•Œ๋ฆผ**: Pod ์ด์ƒ/๋ณต๊ตฌ, ๊ณ„์ • ์ž ๊ธˆ, ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰ ์‹œ Discord Webhook ์•Œ๋ฆผ +- **Gmail ์•Œ๋ฆผ**: Pod ์ด์ƒ/๋ณต๊ตฌ, ์ธ์ฆ์„œ ๋งŒ๋ฃŒ ์ž„๋ฐ• ์‹œ ์ด๋ฉ”์ผ ์•Œ๋ฆผ +- **์ž๋™ ๋ชจ๋‹ˆํ„ฐ๋ง ์Šค์ผ€์ค„๋Ÿฌ**: Pod ์ƒํƒœ 1๋ถ„๋งˆ๋‹ค ์ฒดํฌ, ์ธ์ฆ์„œ ๋งŒ๋ฃŒ 24์‹œ๊ฐ„๋งˆ๋‹ค ์ฒดํฌ +- `notifier.py` โ€” Discord/Gmail ์•Œ๋ฆผ ๋ชจ๋“ˆ ์ถ”๊ฐ€ +- `monitor.py` โ€” APScheduler ๊ธฐ๋ฐ˜ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ชจ๋“ˆ ์ถ”๊ฐ€ +- `main.py` โ€” `asyncio.create_task()` โ†’ `await` ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ • (๋™๊ธฐํ•จ์ˆ˜ ๋‚ด ๋น„๋™๊ธฐ ํ˜ธ์ถœ ์˜ค๋ฅ˜ ์ˆ˜์ •) + +#### ์•Œ๋ฆผ ์„ค์ • ๋ฐฉ๋ฒ• +**1. Discord Webhook URL ๋ฐœ๊ธ‰** +Discord ์„œ๋ฒ„ โ†’ ์•Œ๋ฆผ๋ฐ›์„ ์ฑ„๋„ โ†’ โš™๏ธ ์ฑ„๋„ ์„ค์ • โ†’ ์—ฐ๋™ โ†’ ์›นํ›„ํฌ โ†’ ์ƒˆ ์›นํ›„ํฌ ์ƒ์„ฑ โ†’ URL ๋ณต์‚ฌ + +**2. Gmail ์•ฑ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐœ๊ธ‰** +- Google ๊ณ„์ • โ†’ ๋ณด์•ˆ โ†’ 2๋‹จ๊ณ„ ์ธ์ฆ ํ™œ์„ฑํ™” (ํ•„์ˆ˜) +- `https://myaccount.google.com/apppasswords` โ†’ ์•ฑ ์ด๋ฆ„ ์ž…๋ ฅ โ†’ 16์ž๋ฆฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณต์‚ฌ + +**3. K8s Secret ๋“ฑ๋ก** +```bash +kubectl create secret generic notify-secrets \ + --namespace=web-portal \ + --from-literal=discord-webhook-url="https://discord.com/api/webhooks/..." \ + --from-literal=gmail-user="๋ฐœ์†ก๊ณ„์ •@gmail.com" \ + --from-literal=gmail-app-password="xxxx xxxx xxxx xxxx" \ + --from-literal=alert-email-to="์ˆ˜์‹ ๊ณ„์ •@gmail.com" +``` + +**4. ์•Œ๋ฆผ ํ…Œ์ŠคํŠธ** +๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”(F12)์—์„œ ์‹คํ–‰: +```javascript +fetch('/api/admin/notify-test', { + headers: { 'Authorization': 'Bearer ' + localStorage.getItem('portal_token') } +}).then(r => r.json()).then(console.log) +``` + +#### ์žฅ์•  ๋ณต๊ตฌ ๋‚ด์šฉ +- **backend ImagePullBackOff** โ€” Gitea Registry ํ† ํฐ ์ธ์ฆ ๋ฌธ์ œ๋กœ ๋ฐœ์ƒ, ์™ธ๋ถ€ IP ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ +- **backend/frontend ์ด๋ฏธ์ง€ ์ฃผ์†Œ** โ€” ๋‚ด๋ถ€ ์„œ๋น„์Šค๋ช…(`gitea-http.gitea.svc.cluster.local:3000`) โ†’ ์™ธ๋ถ€ IP(`192.168.10.101:30000`)๋กœ ๋ณ€๊ฒฝ +- **notifier.py, monitor.py ๋ˆ„๋ฝ** โ€” Dockerfile์— ํŒŒ์ผ์ด ์žˆ์—ˆ์œผ๋‚˜ `--no-cache` ์žฌ๋นŒ๋“œ๋กœ ํ•ด๊ฒฐ +- **notify-secrets** โ€” ๊ธฐ์กด Secret์ด ์ž˜๋ชป๋œ ๊ฐ’์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์–ด ์žฌ์ƒ์„ฑ + +### 2026-04-27 (์•Œ๋ฆผ ์ฑ„๋„ ๊ด€๋ฆฌ UI ์ถ”๊ฐ€) + +#### ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- **๐Ÿ”” ์•Œ๋ฆผ ์ฑ„๋„ ๊ด€๋ฆฌ ํŽ˜์ด์ง€** โ€” ๊ด€๋ฆฌ์ž ํƒญ์— ์ถ”๊ฐ€, ์›น์—์„œ ์ง์ ‘ ์•Œ๋ฆผ ์ฑ„๋„ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ +- **๋‹ค์ค‘ ์ฑ„๋„ ์ง€์›** โ€” Discord ์ „์šฉ / Gmail ์ „์šฉ / Discord+Gmail ํ˜ผํ•ฉ ์ฑ„๋„์„ ๋ณต์ˆ˜๋กœ ๋“ฑ๋ก ๊ฐ€๋Šฅ +- **์ฑ„๋„ ์œ ํ˜•๋ณ„ ๋ฐœ์†ก (B๋ฐฉ์‹)** โ€” ์ฑ„๋„ ์œ ํ˜•๊ณผ ์•Œ๋ฆผ ์ข…๋ฅ˜์˜ ๊ต์ง‘ํ•ฉ์œผ๋กœ ๋ฐœ์†ก ๋Œ€์ƒ ์ž๋™ ๊ฒฐ์ • +- **DB ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ์„ค์ •** โ€” `notify_channels` ํ…Œ์ด๋ธ”์— ์ €์žฅ, Pod ์žฌ์‹œ์ž‘ ํ›„์—๋„ ์„ค์ • ์œ ์ง€ +- **ํ™˜๊ฒฝ๋ณ€์ˆ˜ fallback** โ€” DB ์ฑ„๋„ ๋ฏธ๋“ฑ๋ก ์‹œ K8s Secret ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ž๋™ ๋Œ€์ฒด +- **notifier.py ์ „๋ฉด ๊ฐœ์„ ** โ€” ๋งค ๋ฐœ์†ก ์‹œ DB์—์„œ ํ™œ์„ฑ ์ฑ„๋„ ๋ชฉ๋ก ์ง์ ‘ ์กฐํšŒ ํ›„ ๋ฐœ์†ก + +#### ์ฑ„๋„ ์œ ํ˜•๋ณ„ ๋ฐœ์†ก ๊ทœ์น™ + +| ์ฑ„๋„ ์œ ํ˜• | notify_both (Pod ์ด์ƒ/๋ณต๊ตฌ) | notify_discord_only (๊ณ„์ •์ž ๊ธˆ/์ž„์‹œPW) | notify_email_only (์ธ์ฆ์„œ๋งŒ๋ฃŒ) | +|---------|--------------------------|--------------------------------------|-------------------------------| +| Discord+Gmail | Discord + Gmail | Discord๋งŒ | Gmail๋งŒ | +| Discord ์ „์šฉ | Discord๋งŒ | Discord๋งŒ | ๋ฐœ์†ก ์•ˆ ํ•จ | +| Gmail ์ „์šฉ | Gmail๋งŒ | ๋ฐœ์†ก ์•ˆ ํ•จ | Gmail๋งŒ | + +#### ์•Œ๋ฆผ ์ฑ„๋„ ๋“ฑ๋ก ๋ฐฉ๋ฒ• (์›น UI) +1. ๊ด€๋ฆฌ์ž ๋กœ๊ทธ์ธ โ†’ **๐Ÿ”” ์•Œ๋ฆผ ์„ค์ •** ํƒญ +2. ์ขŒ์ธก ํผ์—์„œ ์ฑ„๋„ ์ด๋ฆ„, ์œ ํ˜•, Webhook URL / Gmail ์ •๋ณด ์ž…๋ ฅ +3. **๐Ÿ’พ ์ €์žฅ** ํด๋ฆญ โ†’ ์šฐ์ธก ๋ชฉ๋ก์— ์ถ”๊ฐ€๋จ +4. ๋ชฉ๋ก์—์„œ โœ๏ธ ํด๋ฆญ โ†’ ํผ์— ๊ฐ’ ์ž๋™ ์ž…๋ ฅ ํ›„ ์ˆ˜์ •/์ €์žฅ +5. ๐Ÿ—‘๏ธ ํด๋ฆญ โ†’ ์ฑ„๋„ ์‚ญ์ œ +6. **๐Ÿ“จ ํ…Œ์ŠคํŠธ ๋ฐœ์†ก** ๋ฒ„ํŠผ์œผ๋กœ ๋“ฑ๋ก๋œ ๋ชจ๋“  ์ฑ„๋„์— ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ ๋ฐœ์†ก + +#### DB ํ…Œ์ด๋ธ” ์ˆ˜๋™ ์ƒ์„ฑ (์‹ ๊ทœ ๋ฐฐํฌ ์ „ ํ•„์š” ์‹œ) +```bash +kubectl exec -n web-portal deployment/backend -- python3 -c " +import psycopg2 +conn = psycopg2.connect(host='postgres-service', database='portaldb', user='portaluser', password='portalpass') +cur = conn.cursor() +cur.execute('''CREATE TABLE IF NOT EXISTS notify_channels ( + id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, type VARCHAR(20) NOT NULL, + discord_webhook_url TEXT DEFAULT chr(0), gmail_user VARCHAR(200) DEFAULT chr(0), + gmail_app_password TEXT DEFAULT chr(0), alert_email_to VARCHAR(200) DEFAULT chr(0), + enabled BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW() +)''') +conn.commit() +print('์™„๋ฃŒ') +" +``` + +> โ„น๏ธ ์ƒˆ `main.py` ๋ฐฐํฌ ํ›„์—๋Š” startup ์‹œ ์ž๋™์œผ๋กœ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๋ฏ€๋กœ ์ˆ˜๋™ ์ƒ์„ฑ ๋ถˆํ•„์š”. + +#### ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ณ€๊ฒฝ +``` +backend/ +โ”œโ”€โ”€ main.py โ† ์•Œ๋ฆผ ์ฑ„๋„ CRUD API ์ถ”๊ฐ€, notify_channels ํ…Œ์ด๋ธ” ์ž๋™ ์ƒ์„ฑ +โ”œโ”€โ”€ notifier.py โ† DB ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ ์ฑ„๋„ ๋ฐœ์†ก์œผ๋กœ ์ „๋ฉด ์žฌ์ž‘์„ฑ +โ””โ”€โ”€ monitor.py โ† ๋ณ€๊ฒฝ ์—†์Œ (notifier ์ธํ„ฐํŽ˜์ด์Šค ๋™์ผ) + +frontend/ +โ””โ”€โ”€ index.html โ† ๐Ÿ”” ์•Œ๋ฆผ ์„ค์ • ํƒญ ์ถ”๊ฐ€ (์ขŒ์ธก ํผ + ์šฐ์ธก ๋ฆฌ์ŠคํŠธ UI) +``` + +--- + +### 2026-05-19 (ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ ๊ตฌ์ถ• + URL ๊ตฌ์กฐ ๊ฐœํŽธ) + +#### URL ๊ตฌ์กฐ ๋ณ€๊ฒฝ + +| ๋ณ€๊ฒฝ ์ „ | ๋ณ€๊ฒฝ ํ›„ | ๋‚ด์šฉ | +|---------|---------|------| +| `cyanburu.com/` | `cyanburu.com/portal` | ๊ธฐ์กด ์›น ํฌํ„ธ ๊ฒฝ๋กœ ๋ณ€๊ฒฝ | +| โ€” | `cyanburu.com/` | ์‹ ๊ทœ ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ (๋ฃจํŠธ) | +| โ€” | `cyanburu.com/kingscup` | ํ‚น์ปต ๊ฒŒ์ž„ (๊ฐœ๋ฐœ ์˜ˆ์ •) | + +#### ๊ธฐ๋Šฅ ์ถ”๊ฐ€ + +**ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ (`cyanburu.com/`)** +- ํฌํŠธํด๋ฆฌ์˜ค ๊ฒธ ์„œ๋น„์Šค ํ—ˆ๋ธŒ ํŽ˜์ด์ง€ ์‹ ๊ทœ ๊ตฌ์ถ• +- ์—๋””ํ† ๋ฆฌ์–ผ ๋งค๊ฑฐ์ง„ + ์‚ฌ์ด๋ฒ„ํŽ‘ํฌ ๋ฏน์Šค ๋””์ž์ธ + - `Cormorant Garamond` ์„ธ๋ฆฌํ”„ ํฐํŠธ + ํฌ๋ฆผ ๋ฐฐ๊ฒฝ (์—๋””ํ† ๋ฆฌ์–ผ) + - ๋ฐฐ๊ฒฝ ๊ฒฉ์ž ๊ทธ๋ฆฌ๋“œ, ๋ฏผํŠธ ๋„ค์˜จ ํ˜ธ๋ฒ„ ํšจ๊ณผ (์‚ฌ์ด๋ฒ„ํŽ‘ํฌ) + - ํ„ฐ๋ฏธ๋„ ์Šคํƒ€์ผ ํด๋Ÿฌ์Šคํ„ฐ ์ƒํƒœ ํ‘œ์‹œ + ์‹ค์‹œ๊ฐ„ ์—…ํƒ€์ž„ ์นด์šดํ„ฐ + - ์ปค์Šคํ…€ ์ปค์„œ (๋ฏผํŠธ ์  + ๋ง) +- DB ๊ธฐ๋ฐ˜ ๋™์  ์นด๋“œ ๋กœ๋”ฉ โ€” `/portal/api/homepage/cards` API ํ˜ธ์ถœ +- API ์‹คํŒจ ์‹œ ์ •์  ์นด๋“œ๋กœ ์ž๋™ ํด๋ฐฑ +- `hub` ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๋…๋ฆฝ ๋ฐฐํฌ (nginx ์ •์  ์„œ๋น™) + +**ํ™ˆ ์นด๋“œ ๊ด€๋ฆฌ (๊ด€๋ฆฌ์ž)** +- `cyanburu.com/portal` ๊ด€๋ฆฌ์ž ํƒญ์— `๐Ÿ  ํ™ˆ ์นด๋“œ ๊ด€๋ฆฌ` ์ถ”๊ฐ€ +- ์นด๋“œ ์ถ”๊ฐ€ / ์ˆ˜์ • / ์‚ญ์ œ / ์ˆœ์„œ ๋ณ€๊ฒฝ / ๊ณต๊ฐœ ์—ฌ๋ถ€ ์„ค์ • +- ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€์— ์ฆ‰์‹œ ๋ฐ˜์˜ (์žฌ๋ฐฐํฌ ๋ถˆํ•„์š”) +- `homepage_cards` ํ…Œ์ด๋ธ” (PostgreSQL) ์‹ ๊ทœ ์ถ”๊ฐ€ + +**web-portal ๊ฒฝ๋กœ ๋ณ€๊ฒฝ (`/` โ†’ `/portal`)** +- Nginx Ingress `rewrite-target` ์œผ๋กœ `/portal` prefix strip ์ฒ˜๋ฆฌ +- `frontend/nginx.conf` subpath ๋Œ€์‘ ์ˆ˜์ • +- `frontend/index.html` API ๊ฒฝ๋กœ `/api` โ†’ `/portal/api` ์ˆ˜์ • +- `backend/main.py` `root_path="/portal"` ์ถ”๊ฐ€ + +#### ์‹ ๊ทœ k8s ํŒŒ์ผ + +| ํŒŒ์ผ | ๋‚ด์šฉ | +|------|------| +| `k8s/00-hub.yaml` | hub ๋„ค์ž„์ŠคํŽ˜์ด์Šค + Deployment + Service | +| `k8s/13-ingress-hub.yaml` | Hub Ingress (`cyanburu.com/`) | + +#### DB ํ…Œ์ด๋ธ” ์ถ”๊ฐ€ +```sql +CREATE TABLE IF NOT EXISTS homepage_cards ( + id SERIAL PRIMARY KEY, + title VARCHAR(100) NOT NULL, + subtitle VARCHAR(200), + description TEXT, + url VARCHAR(500) NOT NULL, + tag VARCHAR(20) DEFAULT 'LIVE', + sort_order INTEGER DEFAULT 0, + visible BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW() +); +``` + +> โ„น๏ธ ์‹ ๊ทœ ๋ฐฐํฌ ์‹œ `main.py` startup ์ด๋ฒคํŠธ์—์„œ ์ž๋™ ์ƒ์„ฑ. ์‹คํŒจ ์‹œ psql์—์„œ ์ง์ ‘ ์‹คํ–‰. + +#### ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ณ€๊ฒฝ +``` +hub/ โ† ์‹ ๊ทœ ์ถ”๊ฐ€ +โ”œโ”€โ”€ index.html โ† ํ—ˆ๋ธŒ ํ™ˆํŽ˜์ด์ง€ (๋™์  ์นด๋“œ ๋กœ๋”ฉ) +โ””โ”€โ”€ Dockerfile โ† nginx:alpine ๊ธฐ๋ฐ˜ ์ •์  ์„œ๋น™ + +backend/ +โ””โ”€โ”€ main.py โ† homepage_cards CRUD API ์ถ”๊ฐ€, root_path="/portal" ์„ค์ • + +frontend/ +โ”œโ”€โ”€ index.html โ† API ๊ฒฝ๋กœ /portal/api ๋กœ ์ˆ˜์ •, ๐Ÿ  ํ™ˆ ์นด๋“œ ๊ด€๋ฆฌ ํƒญ ์ถ”๊ฐ€ +โ””โ”€โ”€ nginx.conf โ† /portal subpath ๋Œ€์‘ ์ˆ˜์ • + +k8s/ +โ”œโ”€โ”€ 00-hub.yaml โ† ์‹ ๊ทœ ์ถ”๊ฐ€ +โ”œโ”€โ”€ 08-ingress.yaml โ† /portal ๊ฒฝ๋กœ๋กœ ๋ณ€๊ฒฝ +โ””โ”€โ”€ 13-ingress-hub.yaml โ† ์‹ ๊ทœ ์ถ”๊ฐ€ (cyanburu.com/ โ†’ hub) +``` + +#### ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… (5์›”) + +**hub namespace not found** +- Registry Secret ์ƒ์„ฑ ์ „ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ์—†์–ด์„œ ๋ฐœ์ƒ +- ํ•ด๊ฒฐ: `kubectl create namespace hub` ๋จผ์ € ์‹คํ–‰ ํ›„ Secret ์ƒ์„ฑ + +**Ingress host+path ์ถฉ๋Œ (BadRequest)** +- ๊ธฐ์กด web-portal-ingress๊ฐ€ `cyanburu.com /` ๋ฅผ ์ ์œ ํ•˜๊ณ  ์žˆ์–ด์„œ hub-ingress ์ƒ์„ฑ ๋ถˆ๊ฐ€ +- ํ•ด๊ฒฐ: `08-ingress.yaml`์„ `/portal` ๊ฒฝ๋กœ๋กœ ๋จผ์ € apply ํ›„ `13-ingress-hub.yaml` apply + +**ํ™ˆ ์นด๋“œ ๊ด€๋ฆฌ ํƒญ ๋‚ด์šฉ ๋ฏธํ‘œ์‹œ** +- `page-admin-hub-cards` div๊ฐ€ `` ๋ฐ–์— ์œ„์น˜ํ•ด์„œ JS๊ฐ€ null ๋ฐ˜ํ™˜ +- ํ•ด๊ฒฐ: sed ๋ช…๋ น์œผ๋กœ div๋ฅผ `` ์•ˆ์œผ๋กœ ์ด๋™ + +**hub ํ™ˆํŽ˜์ด์ง€ ์นด๋“œ ๋ฏธ๋ฐ˜์˜** +- `hub/index.html`์— ๋™์  ๋กœ๋”ฉ ์ฝ”๋“œ๊ฐ€ ์—†๋Š” ๊ตฌ๋ฒ„์ „ ํŒŒ์ผ์ด ๋ฐฐํฌ๋จ +- ํ•ด๊ฒฐ: ๋™์  ์นด๋“œ ๋กœ๋”ฉ ๋ฒ„์ „์œผ๋กœ `hub/index.html` ๊ต์ฒด ํ›„ ์žฌ๋นŒ๋“œ