| 항목 |
값 |
이유 |
| 장비 |
노트북 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가 함께 멈춘다 |
| 노드 |
역할 |
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를 주입한다.
| 항목 |
선택 |
이유 |
| 컨테이너 런타임 |
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 활성화
| 리소스 |
종류 |
개수 |
| 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에서 측정).
- feature 브랜치에서 작업 후 PR 생성
- GitHub Actions: 테스트 → 이미지 빌드 → GHCR에 커밋 SHA 태그로 push
- 테스트 실패 시 머지 불가 (CI 게이트)
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 (자동):
-
develop으로 PR, 리뷰 승인 후 머지
- 앱 CI가 이미지를 올린 뒤 infra 저장소 배포 workflow 호출
- control plane VM의 self-hosted runner가 작업을 받아 실행 (온프렘 VM은 외부에서 접근할 수 없어 runner가 GitHub에서 작업을 가져오는 방식)
- 배포 전용 ServiceAccount 권한으로 새 이미지 적용:
kubectl set image (M5부터 helm upgrade -f values-onprem.yaml)
-
kubectl rollout status로 완료 확인, 실패 시 workflow 실패 + 팀 채널 알림
- 배포 후 점검
- 화·목 배포일에
develop → main PR, 승인 후 머지
- GitHub Actions가 OIDC로 AWS 인증 (액세스 키 저장 없음)
- EC2 kubeadm 클러스터에 새 이미지 적용
- 프론트: 빌드 결과를 S3에 업로드 → CloudFront 캐시 무효화
-
kubectl rollout status 확인 후 배포 후 점검 (+ 10분간 5xx 에러율 5% 이하)
- 인프라 변경 PR: Terraform
plan 결과를 PR 댓글로 확인 → 승인 후 머지 → terraform apply
- 앱 변경 PR: 머지 시 GitHub Actions가 OIDC 인증 →
aws eks update-kubeconfig → helm upgrade -f values-cloud.yaml
- 프론트: S3 업로드 → CloudFront 무효화 자동 실행
-
kubectl rollout status 확인 후 배포 후 점검
- 완료 기준: push부터 배포까지 머지 버튼 외 수동 작업 없음,
terraform destroy 후 apply로 운영 환경 재생성 가능