배포 가이드 - Team-likelion-3rd-Project/likelion-devops-7th-3rd-team01 GitHub Wiki

배포 가이드

온프레미스 설계

호스트

항목 값 이유
장비 노트북 1대 (AMD Ryzen 5, 6코어 12스레드, 32GB, Windows) VM 3대와 모니터링까지 올릴 메모리 여유
하이퍼바이저 VirtualBox 4주차 실습에서 사용, Hyper-V 비활성 상태라 직접 실행
VM 저장 위치 시스템 드라이브와 분리된 데이터 드라이브 VM 디스크가 커져도 OS에 영향 없음
CPU 아키텍처 x86 (amd64) AWS EC2와 같아 이미지를 하나만 빌드하면 된다
운영 전원 연결 시 절전 해제 꺼지거나 잠들면 개발 환경과 self-hosted runner가 함께 멈춘다

VM 구성

노드 역할 vCPU RAM 디스크 IP
k8s-cp control plane, self-hosted runner 2 4GB 40GB 192.168.56.10
k8s-worker1 worker 2 6GB 40GB 192.168.56.11
k8s-worker2 worker 2 6GB 40GB 192.168.56.12
  • OS: Ubuntu Server 24.04 LTS (amd64), LVM 미사용으로 디스크 전체를 루트에 할당
  • control plane에 공통 설정까지 마친 뒤 완전 복제로 워커를 만들고, 복제 시 MAC 주소·하드웨어 UUID·machine-id·SSH 호스트 키를 새로 생성했다 (노드마다 고유해야 kubeadm이 구분한다).
  • 앱·DB·모니터링은 워커에 올라가므로 워커에 RAM을 더 배정한다. 디스크는 사용한 만큼만 커지는 가변 크기다.

네트워크

항목 값 이유
어댑터 1 NAT VM의 인터넷 접속 (패키지 설치, 이미지 다운로드)
어댑터 2 호스트 전용 192.168.56.0/24, DHCP 비활성, 고정 IP 노드 간 통신. 공유기와 무관해 장소가 바뀌어도 IP가 유지된다
kubelet 노드 IP --node-ip로 호스트 전용 IP 지정 VirtualBox NAT는 모든 VM에 같은 IP(10.0.2.15)를 줘서 지정하지 않으면 노드 주소가 겹친다
파드 대역 10.244.0.0/16 노드 대역(192.168.56.x)·AWS VPC(10.20.0.0/16)와 겹치지 않게
서비스 대역 10.96.0.0/12 kubeadm 기본값
외부 노출 Traefik(NodePort 30080). / → 프론트, /api → API 온프렘에는 클라우드 로드밸런서가 없다. ingress-nginx는 지원 종료(2026-03)
팀원 접속 Tailscale (control plane VM에만 설치, 웹 접속만) 호스트 전용 네트워크는 노트북 밖에서 접근할 수 없다
  • /actuator/*는 Ingress에 연결하지 않고 클러스터 내부에서만 접근한다.
  • 접속 주소와 Tailscale 공유 링크는 팀 채팅에서만 공유한다.
  • 화면과 API가 같은 출처라 온프렘에서는 CORS가 필요 없다. HTTP로 서비스하므로 COOKIE_SECURE=false를 주입한다.

런타임·CNI

항목 선택 이유
컨테이너 런타임 containerd 2.4 (SystemdCgroup = true) kubeadm 표준 런타임. cgroup 관리를 systemd로 통일해 노드를 안정적으로 유지
Kubernetes v1.35 (kubeadm·kubelet·kubectl apt-mark hold) 지원 범위 안의 안정 버전, EKS와 버전을 맞출 수 있다. 자동 업그레이드로 노드 간 버전이 어긋나지 않게 고정
CNI Calico 4주차 실습에서 검증, NetworkPolicy 지원
스토리지 local-path-provisioner kubeadm에는 기본 스토리지가 없어 MySQL PVC를 제공하기 위해
지표 metrics-server HPA·kubectl top에 필요
Ingress Traefik v3 (Helm) 표준 Ingress 리소스 사용, 설정은 infra 저장소 onprem/traefik-values.yaml
  • 공통 설정: swap 비활성화, overlay·br_netfilter 모듈, ip_forward·bridge-nf-call-iptables 활성화

앱 구성 (namespace news)

리소스 종류 개수
api / worker Deployment (APP_ROLE로 역할 구분, 같은 이미지) 2 / 1
batch CronJob (*/30, RSS 수집 → 요약) -
frontend Deployment (Nginx) 2
mysql StatefulSet + PVC (MySQL 8.4) 1
redis Deployment (Redis 7.4, 영속화 없음) 1
  • 테이블은 API 시작 시 Flyway가 자동 생성한다. Secret 값은 VM에서 직접 입력하고 저장소에 올리지 않는다.

백업 대상

대상 방식 주기·보관 이유
etcd etcdctl snapshot save 하루 1회, 7일 클러스터 설정·상태 전체가 저장된 곳. 손상되면 클러스터가 기억을 잃는다
MySQL mysqldump CronJob 하루 1회, 7일 회원·질문 기록처럼 다시 만들 수 없는 사용자 데이터
  • 5주차부터 백업 파일을 S3로 전송한다 (온프렘과 AWS의 접점).
  • 복구 절차와 검증은 장애 시나리오에서 관리한다.

환경별 배포 대상

환경 브랜치 인프라 트리거
dev develop 온프레미스 VM 3대 kubeadm 클러스터. API·워커·요약 갱신 CronJob·프론트(Nginx)·MySQL·Redis를 클러스터 안에서 실행 develop 머지(push) 시 GitHub Actions + self-hosted runner로 자동 배포
prod (M4) main AWS EC2 kubeadm + RDS + ElastiCache, ALB, 프론트는 S3 + CloudFront main 머지(화·목 배포일) 시 GitHub Actions(OIDC 인증)로 자동 배포
prod (M5) main Terraform으로 생성한 AWS 환경 + RDS + ElastiCache, ALB, S3 + CloudFront main 머지 시 Terraform + Helm으로 완전 자동 배포

배포 정책

  • 배포 전략: Rolling (maxUnavailable: 0, maxSurge: 1) — 선택 이유: 쿠버네티스 기본 기능만으로 무중단 배포가 가능하다. Blue-Green은 운영 자원이 두 배로 들고, Canary는 트래픽 분할 도구가 추가로 필요해 이번 규모·기간에 맞지 않는다. 새 파드는 readiness(/actuator/health/readiness, DB 연결 기준·Redis 제외) 통과 후에만 트래픽을 받는다.
  • 이미지 태그 규칙: 커밋 SHA (ghcr.io/<조직>/<저장소>-backend:<SHA>). latest는 쓰지 않는다. 배포 버전 추적과 정확한 롤백을 위해서다.
  • GHCR 패키지가 private이라 클러스터는 ghcr-pull Secret(imagePullSecret)으로 이미지를 받는다.
  • 배포 승인: dev는 PR 리뷰 1명 + CI 테스트 통과 시 머지(머지 즉시 자동 배포). prod는 dev에서 검증된 커밋만 화·목 배포일에 develop → main PR, 팀장 + 변경 담당 1명 승인. 긴급 수정은 팀 채널 공지 후 진행.
  • DB 스키마 변경: 롤링 배포 중 옛 버전과 새 버전이 함께 돌 수 있으므로 하위 호환 변경만 Expand → Deploy → Contract 순서로 적용한다.
  • 설정·비밀 값: 환경별 설정은 ConfigMap, API 키·DB 비밀번호는 Secret. 코드와 이미지에 넣지 않는다.

롤백 정책

  • 롤백 조건: 배포 후 10분 안에 5xx 에러율 5% 초과(M4부터 Grafana 기준), 배포 후 점검(요약 조회·로그인·질문 접수) 실패, DB 마이그레이션 실패로 앱 기동 불가. readiness 실패로 5분 안에 배포가 끝나지 않으면 기존 파드가 계속 서비스하므로 배포를 중단하고 이전 버전으로 정리한다. 목표: 장애 감지부터 복구까지 2분 이내.
  • 롤백 방법: 기본은 이전 커밋 SHA로 배포 workflow 재실행. workflow를 쓸 수 없을 때만 kubectl rollout undo (M5부터는 helm rollback). 이 명령들은 앱만 되돌리고 DB는 되돌리지 않으므로, Expand → Deploy → Contract를 지켜 앱만 롤백하고 데이터 손상 시 장애 시나리오의 백업 복원 절차를 따른다.
  kubectl rollout history deployment/api -n news
  kubectl rollout undo deployment/api -n news
  kubectl rollout status deployment/api -n news
  • 롤백 확인: kubectl rollout status 완료, 클러스터 내부에서 조회한 /actuator/info 커밋 SHA가 이전 버전과 일치, 배포 후 점검 통과·에러율 정상화. 롤백 후 이슈에 원인·영향 범위·걸린 시간을 기록한다 (실제 복구 시간은 M6에서 측정).

배포 절차

공통: CI (모든 환경)

  1. feature 브랜치에서 작업 후 PR 생성
  2. GitHub Actions: 테스트 → 이미지 빌드 → GHCR에 커밋 SHA 태그로 push
  3. 테스트 실패 시 머지 불가 (CI 게이트)

1. 온프레미스 (develop)

M2 (수동): control plane VM에서 infra 저장소 매니페스트의 IMAGE_TAG를 커밋 SHA로 바꿔 적용한다.

cd ~/infra && git pull
sed "s/IMAGE_TAG/<SHA>/" k8s/base/backend.yaml | kubectl apply -f -
kubectl rollout status deployment/api -n news

M3 (자동):

  1. develop으로 PR, 리뷰 승인 후 머지
  2. 앱 CI가 이미지를 올린 뒤 infra 저장소 배포 workflow 호출
  3. control plane VM의 self-hosted runner가 작업을 받아 실행 (온프렘 VM은 외부에서 접근할 수 없어 runner가 GitHub에서 작업을 가져오는 방식)
  4. 배포 전용 ServiceAccount 권한으로 새 이미지 적용: kubectl set image (M5부터 helm upgrade -f values-onprem.yaml)
  5. kubectl rollout status로 완료 확인, 실패 시 workflow 실패 + 팀 채널 알림
  6. 배포 후 점검

2. AWS EC2 (main, M4)

  1. 화·목 배포일에 develop → main PR, 승인 후 머지
  2. GitHub Actions가 OIDC로 AWS 인증 (액세스 키 저장 없음)
  3. EC2 kubeadm 클러스터에 새 이미지 적용
  4. 프론트: 빌드 결과를 S3에 업로드 → CloudFront 캐시 무효화
  5. kubectl rollout status 확인 후 배포 후 점검 (+ 10분간 5xx 에러율 5% 이하)

3. AWS EKS (main, M5)

  1. 인프라 변경 PR: Terraform plan 결과를 PR 댓글로 확인 → 승인 후 머지 → terraform apply
  2. 앱 변경 PR: 머지 시 GitHub Actions가 OIDC 인증 → aws eks update-kubeconfig → helm upgrade -f values-cloud.yaml
  3. 프론트: S3 업로드 → CloudFront 무효화 자동 실행
  4. kubectl rollout status 확인 후 배포 후 점검
  5. 완료 기준: push부터 배포까지 머지 버튼 외 수동 작업 없음, terraform destroy 후 apply로 운영 환경 재생성 가능

배포 후 점검 (모든 환경)

  • 클러스터 내부 /actuator/info 커밋 SHA가 이번 배포와 일치
  • 홈에서 6개 분야 요약 표시
  • 로그인 성공
  • 질문 접수 후 결과 표시
  • 10분간 5xx 에러율 5% 이하 (M4부터)
⚠️ **GitHub.com Fallback** ⚠️