Skip to content

Deployment

JinmuGo edited this page Aug 19, 2026 · 10 revisions

Deployment

백엔드는 OCI 위의 k3s 클러스터에 GitOps 방식으로 배포한다.

k3s는 경량 Kubernetes 배포판이다. 따라서 Deployment, Service, Ingress, Kustomize 같은 표준 Kubernetes API를 쓰지만, 실제 운영 클러스터와 네트워크 운영 주체는 k3s이다.

역할 분리

위치 담당
dnd-15th-3-backend Dockerfile, GHCR 이미지, deploy/k8s 매니페스트, 환경별 이미지 태그
ArgoCD 이 레포의 배포 매니페스트 감시 및 k3s 반영
인프라 관리자 전용 레포 k3s namespace, ArgoCD Project/RBAC, Cloudflare Tunnel/Access 설정

팀원은 이 백엔드 레포만 수정한다. 인프라 관리자 레포나 운영 클러스터에 직접 kubectl apply하지 않는다.

환경

Git branch k3s namespace Kustomize overlay API hostname 용도
develop dnd-15th-3-dev deploy/k8s/overlays/develop momo-dev.jinmu.me 팀 통합 개발 환경
main dnd-15th-3-prod deploy/k8s/overlays/main momo.jinmu.me 안정 배포 환경

기능 브랜치는 develop으로 PR을 보내고, 검증된 developmain으로 PR을 보낸다.

GitOps 배포 구조

flowchart LR
    infra["atlas-infra<br/>main"]
    backend["dnd-15th-3-backend<br/>develop / main"]
    appConfig["deploy/argocd<br/>Application definitions"]
    devOverlay["deploy/k8s/overlays/develop"]
    prodOverlay["deploy/k8s/overlays/main"]
    ghcr["GHCR<br/>linux/arm64 image"]

    root["Argo CD Application: apps"]
    pointer["Argo CD Application: dnd-15th-3"]
    devApp["Application: dnd-15th-3-dev"]
    prodApp["Application: dnd-15th-3-prod"]
    updater["Image Updater<br/>develop only"]

    devNamespace["Namespace: dnd-15th-3-dev<br/>momo-dev.jinmu.me"]
    prodNamespace["Namespace: dnd-15th-3-prod<br/>momo.jinmu.me"]

    infra -->|root App of Apps| root
    root -->|creates| pointer
    pointer -->|reads deploy/argocd| appConfig
    appConfig -->|defines| devApp
    appConfig -->|defines| prodApp
    devApp -->|reads| devOverlay
    prodApp -->|reads| prodOverlay
    devOverlay -->|sync and selfHeal| devNamespace
    prodOverlay -->|sync and selfHeal| prodNamespace
    backend -->|push and workflow| ghcr
    ghcr -->|new develop tag| updater
    updater -->|live image parameter| devApp
Loading

런타임 및 데이터 흐름

flowchart TB
    client["Client"]
    cloudflare["Cloudflare Edge<br/>momo-dev.jinmu.me / momo.jinmu.me"]
    tunnel["Cloudflare Tunnel<br/>cloudflared"]
    traefik["Traefik<br/>NodePort 30080"]
    ingress["Ingress: momo-api<br/>path /"]
    apiService["Service: momo-api<br/>port 80 → 3000"]
    api["Deployment: momo-api<br/>1 replica per environment<br/>dnd-15th-3-dev / dnd-15th-3-prod"]
    dbService["Service: momo-postgres<br/>port 5432"]
    postgres["StatefulSet: momo-postgres<br/>1 replica"]
    pvc["local-path PVC<br/>dev 5Gi / prod 10Gi"]
    backup["CronJob: momo-postgres-backup<br/>04:00 KST"]
    objectStorage["OCI Object Storage<br/>PostgreSQL dump"]

    client --> cloudflare
    cloudflare -->|public hostname route| tunnel
    tunnel -->|HTTP to node port| traefik
    traefik --> ingress
    ingress --> apiService
    apiService --> api
    api --> dbService
    dbService --> postgres
    postgres --> pvc
    backup --> dbService
    backup --> objectStorage
Loading

현재 API와 PostgreSQL은 각 환경에서 1 replica로 실행된다. API replica를 늘려도 PostgreSQL은 단일 인스턴스이므로 전체 구조가 고가용성이 되는 것은 아니다.

배포 흐름

feature branch PR → develop merge
  → GitHub Actions: linux/arm64 이미지 build
  → GHCR: develop-<commit-sha> push
  → deploy/k8s/overlays/develop 이미지 태그 commit
  → ArgoCD가 변경 감지
  → k3s dnd-15th-3-dev namespace rollout

develop → main PR도 같은 흐름으로 main 환경에 배포

이미지 태그는 develop-<sha> 또는 main-<sha>의 불변 태그다. latest를 사용하지 않으므로, 어느 코드가 실행 중인지 Git과 ArgoCD에서 추적하고 이전 SHA로 롤백할 수 있다.

팀원이 수정하는 파일

Dockerfile                         # 애플리케이션 컨테이너
.github/workflows/image.yml        # ARM build + 배포 태그 갱신
deploy/k8s/base/                   # 공통 Deployment, Service, Ingress
deploy/k8s/overlays/develop/       # develop 환경 차이
deploy/k8s/overlays/main/          # main 환경 차이
deploy/sealed-secrets.pub.pem      # Secret 암호화용 공개 인증서

배포된 앱은 /api/docs에서 Swagger UI를 제공한다. API 명세는 배포 후 해당 경로에서 확인한다.

현재 readiness/liveness/startup probe는 다음 health endpoint를 사용한다.

엔드포인트 용도 확인 항목
GET /health (dev/prod) App level 종합 상태 Startup/readiness probe, DB + PostGIS
GET /health/storage (dev/prod) Object Storage 진단 OCI media bucket
GET /health/live Liveness probe 프로세스 생존

Kubernetes의 startupreadiness probe는 /health, liveness probe는 /health/live를 사용한다. 두 환경의 /healthdatabasepostgis를 확인하고, OCI media 진단은 /health/storage에서 분리한다. PostGIS extension이 없거나 버전 쿼리가 실패하면 503을 반환한다. /health/live는 두 환경 모두 외부 의존성과 무관하게 프로세스 생존 여부만 확인한다.

새 기능이 시작 의존성(DB 등)을 추가하면 readiness/health 기준도 함께 갱신한다.

시크릿

평문 .env, API 키, DB 비밀번호는 commit/PR/채팅에 올리지 않는다. 필요한 값은 공개 인증서로 SealedSecret을 만들고 암호화된 파일만 환경 overlay에 추가한다.

kubeseal --format=yaml --cert deploy/sealed-secrets.pub.pem \
  < secret.plain.yaml > deploy/k8s/overlays/develop/secret.sealed.yaml
rm secret.plain.yaml

공개 인증서는 암호화만 가능하고 복호화는 불가하다. 실제 Secret 값은 k3s의 sealed-secrets controller만 복호화한다.

개발자 DB 접근

cluster-admin kubeconfig와 애플리케이션 DB 자격증명은 인프라 관리자만 보유한다. 클러스터 DB 접근은 목적에 따라 다음 두 경로를 분리한다.

목적 환경 정책
테이블 조회 dev / prod 개발자별 DB read-only 계정 + 7일 kubeconfig
로컬 API를 클러스터 DB에 연결 dev만 db-developer 제한 kubeconfig
애플리케이션 DB 쓰기 또는 prod 운영 변경 prod 직접 권한 미발급, 인프라 관리자 수행

테이블 조회 전용 접근

deploy/k8s/base/db-readonly-access.yamldb-reader Role은 momo-postgres-0 Pod 조회와 port-forward만 허용한다. Pod 목록, Secret 읽기, 다른 Pod port-forward, Kubernetes 쓰기 권한은 없다. 개발자별 ServiceAccount와 RoleBinding을 Git에 추가해 토큰 사용자를 구분한다.

인프라 관리자는 다음 스크립트로 PostgreSQL read-only 역할과 전달 bundle을 발급한다.

bash scripts/provision-db-reader.sh dev shname /secure/output/shname-dev
bash scripts/provision-db-reader.sh prod shname /secure/output/shname-prod

DB 역할에는 CONNECT, public schema USAGE, 기존·향후 테이블과 sequence의 SELECT만 부여한다. 쓰기 권한, role/database 생성 권한, RLS 우회 권한은 없으며 기본 트랜잭션은 read-only다. 스크립트가 로그인·SELECT 성공과 CREATE 거부를 실제로 확인한 뒤 다음 파일을 0600 권한으로 만든다.

  • 환경별 short-lived kubeconfig (기본 7일)
  • 환경별 .pgpass
  • port-forward와 psql 사용법이 담긴 README.txt
  • 안전한 전달을 위한 .tar.gz

bundle은 commit하거나 채팅에 붙여넣지 않고 승인된 암호화 채널로 전달한다. Tailscale은 전체 개인 tailnet 초대 대신 Kubernetes API가 있는 atlas1 장비만 공유하며, 공유 링크와 DB bundle은 서로 다른 채널로 보낸다.

dev API 진단 접근

로컬 API를 dev 클러스터 DB에 연결해야 할 때만 기존 db-developer kubeconfig를 사용한다. 이 권한은 deploy/k8s/overlays/develop/db-access.yaml에 정의되어 있으며, dev의 momo-postgres-auth / momo-statistics-postgres-auth Secret 읽기와 DB port-forward에 필요한 조회만 허용한다. 단순 테이블 조회자에게는 db-developer 파일을 전달하지 않는다.

# 인프라 관리자
bash scripts/make-db-kubeconfig.sh

# 개발자
export KUBECONFIG=$HOME/.kube/db-developer.yaml
mise run cluster-check
mise run dev-api

토큰 유출이 의심되면 해당 ServiceAccount를 삭제·재생성해 outstanding token을 즉시 무효화하고, PostgreSQL 역할의 로그인을 중지하거나 역할을 삭제한다.

확인과 장애 전달

  • PostgreSQL/PostGIS 이미지 pull, migration, backup, 노드 DNS와 DB 복구는 PostGIS K3s Operations 런북을 따른다.
  • ArgoCD dnd-viewer 계정은 DND 앱과 로그만 read-only로 볼 수 있다.
  • dev/prod 배포에서는 Sync operation의 PreSync 단계에서 momo-api-migration Hook이 성공했는지 확인한다. 성공한 Hook Job은 자동 삭제될 수 있으므로 Pod/Job 목록에 없으면 ArgoCD operation history에서 실행 결과와 로그를 확인한다.
  • 각 환경의 /health에서 database·postgis, /health/storage에서 storage가 모두 status: "up"인지 확인한다.
  • ImagePullBackOff, CrashLoopBackOff, PVC Pending, Secret 누락은 ArgoCD 상태와 이벤트를 첨부해 인프라 관리자에게 전달한다.
  • 앱을 멈추려면 Deployment replica 또는 CronJob suspend를 Git PR로 변경한다. UI에서 Delete/Sync하지 않는다.
  • 영구 삭제는 PVC까지 지울 수 있으므로 팀원이 단독으로 진행하지 않는다.

첫 배포 전 관리자 체크

  1. GitHub Actions의 GHCR package write 권한과 public package visibility 확인
  2. ArgoCD Application이 develop/main overlay를 각각 감시하도록 등록
  3. Cloudflare Tunnel에 momo-dev.jinmu.me, momo.jinmu.me hostname 추가
  4. ArgoCD dnd-viewer 계정과 Cloudflare Access 이메일 allowlist 설정

Clone this wiki locally