From b80c379310809f056e589916e4b4bff5af5c331d Mon Sep 17 00:00:00 2001 From: qorgh529 Date: Mon, 6 Apr 2026 23:48:26 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20README=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 423 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 378 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7a296d1..ea23214 100755 --- a/README.md +++ b/README.md @@ -1,42 +1,74 @@ -# Web Portal - Kubernetes 배포 가이드 +# Web Portal - Kubernetes + GitOps 배포 가이드 + +## 📋 프로젝트 개요 + +조직 내부 웹페이지를 통합 관리하는 포털 시스템입니다. +로그인 후 본인에게 할당된 웹페이지 목록을 확인하고 접속할 수 있으며, +관리자는 페이지 및 사용자 권한을 관리할 수 있습니다. + +--- + +## 🏗️ 전체 아키텍처 + +``` +개발자 (git push) + ↓ +Gitea (192.168.10.101:30000) + └── Container Registry (이미지 저장) + ↓ +ArgoCD 자동 감지 & 배포 (192.168.10.101:30080) + ↓ +Kubernetes - web-portal 네임스페이스 + ├── Nginx Frontend (NodePort: 30090) + ├── FastAPI Backend (ClusterIP: 8000) + └── PostgreSQL DB (ClusterIP: 5432) +``` + +--- + +## 🛠️ 기술 스택 + +| 구분 | 기술 | +|------|------| +| Frontend | Nginx + HTML/CSS/JS (SPA) | +| Backend | Python FastAPI | +| Database | PostgreSQL | +| Container | Docker Desktop | +| Orchestration | Kubernetes (Docker Desktop 내장) | +| GitOps | Gitea + ArgoCD | +| Image Registry | Gitea Container Registry | + +--- ## 📁 프로젝트 구조 ``` -k8s-portal/ +nginx-portal/ +├── .gitea/ +│ └── workflows/ +│ └── build-and-push.yaml # Gitea Actions CI (선택사항) ├── backend/ -│ ├── main.py # FastAPI 백엔드 (전체 API) +│ ├── main.py # FastAPI 전체 API 로직 │ ├── requirements.txt │ └── Dockerfile ├── frontend/ -│ ├── index.html # 싱글 페이지 앱 (SPA) -│ ├── nginx.conf # Nginx 설정 (API 프록시 포함) +│ ├── index.html # 싱글 페이지 앱 (SPA) +│ ├── nginx.conf # Nginx 설정 + /api/* 프록시 │ └── Dockerfile ├── k8s/ -│ ├── 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(30080) -├── build-and-deploy.sh # 원클릭 빌드 & 배포 -├── cleanup.sh # 전체 삭제 +│ ├── 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) +├── 06-argocd-app.yaml # ArgoCD Application 정의 (k8s 폴더 밖에 위치) └── README.md ``` -## 🚀 설치 및 실행 +> ⚠️ `06-argocd-app.yaml` 은 반드시 `k8s/` 폴더 **밖**에 위치해야 합니다. +> ArgoCD가 `k8s/` 폴더를 감시하므로 해당 파일이 안에 있으면 순환 참조 문제가 발생합니다. -### 전제 조건 -- Docker Desktop 설치 및 실행 -- Docker Desktop > Settings > Kubernetes > Enable Kubernetes ✅ - -### 배포 (원클릭) -```bash -chmod +x build-and-deploy.sh -./build-and-deploy.sh -``` - -### 접속 -브라우저에서: **http://localhost:30080** +--- ## 🔑 기본 계정 @@ -45,17 +77,119 @@ chmod +x build-and-deploy.sh | 관리자 | `admin` | `admin1234` | | 일반사용자 | `user1` | `user1234` | +--- + ## ✨ 기능 설명 ### 일반 사용자 -- 로그인 후 **본인에게 할당된 웹페이지 목록** 확인 +- 로그인 후 **본인에게 할당된 웹페이지 목록** 카드 형태로 확인 - 카드 클릭 시 **새 탭에서 해당 URL로 이동** ### 관리자 일반 사용자 기능 + 추가: -- **페이지 관리**: 웹페이지 추가/수정/삭제 -- **사용자 관리**: 계정 생성/삭제 -- **권한 설정**: 사용자별 접근 가능 페이지 체크박스로 지정 +- **페이지 관리**: 웹페이지 추가 / 수정 / 삭제 +- **사용자 관리**: 계정 생성 / 삭제 +- **권한 설정**: 사용자별 접근 가능 페이지를 체크박스로 지정 + +--- + +## 🚀 최초 배포 순서 + +### 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 +# Registry 로그인 +docker login 192.168.10.101:30000 -u + +# 백엔드 이미지 빌드 & Push +docker build -t 192.168.10.101:30000/<계정>/portal-backend:latest ./backend/ +docker push 192.168.10.101:30000/<계정>/portal-backend:latest + +# 프론트엔드 이미지 빌드 & Push +docker build -t 192.168.10.101:30000/<계정>/portal-frontend:latest ./frontend/ +docker push 192.168.10.101:30000/<계정>/portal-frontend:latest +``` + +### 3단계. K8s Registry Secret 생성 +```bash +kubectl create namespace 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= +``` + +### 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 +``` + +### 6단계. 접속 확인 +``` +http://192.168.10.101:30090 +``` + +--- + +## 🔄 이후 배포 방법 (코드 수정 시) + +### 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 +``` + +--- ## 🔧 운영 명령어 @@ -69,34 +203,233 @@ 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 # 전체 삭제 -./cleanup.sh +kubectl delete namespace web-portal ``` +--- + ## 🔐 운영 시 보안 설정 -`k8s/03-secrets.yaml`에서 반드시 변경: +`k8s/03-secrets.yaml` 에서 반드시 변경: ```yaml stringData: - db-password: "강력한패스워드로변경" # PostgreSQL 패스워드 - jwt-secret: "64자이상의랜덤문자열로변경" # JWT 서명 키 + db-password: "강력한패스워드로변경" + jwt-secret: "64자이상의랜덤문자열로변경" ``` -## 🏗️ 아키텍처 +--- +## ❗ 트러블슈팅 + +### 1. NodePort 포트 충돌 +**증상** ``` -브라우저 - │ - ▼ :30080 (NodePort) -[Nginx Frontend] ─── 정적 HTML/JS 제공 - │ /api/* 프록시 - ▼ -[FastAPI Backend] ─── JWT 인증, REST API - │ - ▼ -[PostgreSQL] ─── 사용자, 페이지, 권한 데이터 +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 ```