Skip to content

History

BcKmini edited this page Aug 6, 2026 · 6 revisions

History

인프라 작업을 날짜별로 정리한 기록. 세부 트러블슈팅 로그는 팀 공유 문서(Notion 예정) 참고.

2026-07-22 — 프로젝트 파악

  • ERD 공유 (company/user_account/worker/task/document/ticket/access_audit_log)

2026-07-23 — 인프라 방향 논의 시작

  • OpenStack으로 직접 관리하고 싶다는 요청 → 검토 결과 OpenStack은 소프트웨어일 뿐 서버 자체를 제공하지 않고, 오히려 DevStack 기준 최소 8vCPU/16GB급 서버가 추가로 필요해 소규모 데모엔 과함 → **k3s(경량 쿠버네티스)**로 결정
  • Oracle Cloud Always Free(ARM, 완전 무료)로 k3s 2노드 구성 계획, VCN·서브넷·API 키·CLI 세팅 진행
  • client(#99)·server(#31)에 임시 Dockerfile PR 올림

2026-07-27~28 — Oracle 실패, AWS로 전환

  • 도쿄 리전에서 Out of host capacity 반복 — 수동+자동 재시도 합산 138회 이상, 5일간 인스턴스 생성 실패
  • Oracle이 2026-06-15부로 Always Free ARM 할당량을 4→2 OCPU(12GB)로 공지 없이 축소한 사실 확인
  • 춘천 리전으로 우회하는 방안도 검토했으나, 춘천은 애초에 ARM Always Free 제외 대상이라 처음부터 막힌 경로였음을 확인
  • 클라우드별 가격 재조사(Oracle/GCP/AWS/Hetzner/DigitalOcean/Vultr/NHN/네이버) → 한 차례 Vultr(서울)으로 결정했다가, AWS 신규계정 $200 크레딧 + 재고 문제 없음을 재확인하고 최종적으로 AWS로 재결정
  • KT 에이블스쿨 크레딧 지원 여부 확인 → 없음

2026-07-28 — AWS 구축, infra 저장소·Wiki 세팅

  • IAM 사용자(fowoco-admin)·보안그룹·EC2 인스턴스(fowoco-node-1, m7i-flex.large) 생성 → 당일 완료 (Oracle 5일 대비 극적으로 빠름)
  • k3s 싱글 노드 설치, 로컬 kubectl 연결 확인
  • fowoco/infra 저장소에 k8s/ 매니페스트(namespace/postgres/server/ai/client/ingress) 작성 → PR #1
  • 작업 범위를 infra 저장소로 한정하기로 원칙 정함 (client/server/ai는 팀원이 매일 커밋하는 활성 저장소라 부수적으로 건드리지 않기로)
  • 문서를 저장소 README 대신 GitHub Wiki로 이전 (Home / Architecture / Deployment Guide / Deployment Plan)

2026-07-29 — 배포 전 점검, 실제 CI/CD 파이프라인 구축

  • 용량 기준선(서버 1대로 데모 트래픽 충분한지)과 향후 업그레이드 조건·절차를 문서화
  • 배포 전 재점검에서 문제 2건 발견:
    • 퍼블릭 IP가 고정이 아니었음 → Elastic IP 할당해 즉시 고정 (3.35.21.433.35.105.80), k3s 인증서·kubeconfig·Ingress 매니페스트 갱신
    • 보안그룹이 SSH(22)·k3s API(6443)를 0.0.0.0/0으로 전체 공개 — 접속 IP 확정되면 좁힐 예정 (아직 미조치)
  • client/server/ai실제 Dockerfile + GitHub Actions 배포 워크플로우를 PR로 올림
    • client #179 — 신규 Dockerfile (기존 #99는 팀에서 close됨)
    • server #66 — 기존 임시 Dockerfile의 Gradle 버전 버그 발견 및 수정 (gradle:8.10 고정 이미지가 Spring Boot 4.1.0이 요구하는 Gradle 8.14+를 만족 못 해 빌드 실패하던 것을 프로젝트 ./gradlew(9.5.1) 사용으로 수정, 로컬 빌드 성공 확인)
    • ai #5 — 기존 Dockerfile 그대로, 워크플로우만 추가
  • KUBE_CONFIG는 조직 레벨 대신 3개 저장소 개별 시크릿으로 등록 (조직 레벨은 admin:org라는 더 넓은 권한이 필요해서 최소 권한 원칙에 따라 변경)

2026-08-05 — 배포 파이프라인 실전 점검, client 배포 실제로 살아남

  • 그동안 3개 저장소 deploy.ymlkubectl set image 스텝 실패가 || echo "..." fallback으로 조용히 가려져 있어서, CI는 계속 초록불이었지만 실제로는 한 번도 배포에 성공한 적이 없었음을 확인 (클러스터에 kubectl apply -f k8s/가 실제로 실행된 적이 없었음 — Deployment Guide의 최초 배포 순서가 미실행 상태로 남아있었던 것)
  • client부터 self-heal 방식으로 전환 — deploy.yml이 매 배포마다 이 infra 저장소를 체크아웃해서 00-namespace.yaml/04-client.yaml/05-ingress.yaml을 자동 kubectl applyset image/rollout status 진행하도록 수정 (client#261client#262)
  • 첫 실행에서 bootstrap은 성공했지만 Rollout이 타임아웃 → 진단 로그 스텝 추가 후(client#263client#264) 원인이 **readiness probe가 아니라 ImagePullBackOff**였음을 확인
  • 근본 원인: GHCR client 이미지 패키지가 기본 private 상태였고 클러스터엔 imagePullSecrets가 전혀 없었음. Deployment Plan에 이미 "GHCR 패키지는 Public 전환 권장"이라고 적어뒀던 항목인데 client에 대해 실제로는 실행이 안 되어 있던 상태였음
  • 해결: GHCR org 정책(Settings → Packages)에서 Public 허용 활성화 → 패키지 설정에서 client 패키지를 Public으로 전환 (private→public 전환은 GitHub API로는 막혀있어서 웹 UI로만 가능)
  • 재배포 후 검증 완료: curl http://fowoco.3.35.105.80.nip.io/HTTP 200. client는 이제 main에 push하면 실제로 배포까지 확인되는 상태
  • https는 아직 000 — nip.io 임시 도메인이라 TLS 인증서 미설정 (별도 사안, 도메인 확정 후 처리 예정)
  • 남은 일: server/ai도 같은 패턴(self-heal bootstrap + GHCR 패키지 private 여부 확인 후 필요시 public 전환) 적용 예정. server는 추가로 Deployment Guidepostgres-secret/server-env Secret이 클러스터에 실제로 생성되어 있는지부터 확인 필요 (미확인 상태)

이어서 (같은 날) — client CI/거버넌스 정비

배포 자체는 살아났지만, PR 검증·리소스 제한·머지 규칙이 전혀 없다는 걸 확인해서 마저 정비:

  • client.github/workflows/deploy.yml밖에 없었음 — PR을 아무 검증 없이 머지할 수 있는 상태. pull_request 트리거로 lint/test/build를 도는 ci.yml 추가 (client#265client#266)
  • 이 CI를 처음 돌리자마자 pre-existing flaky 테스트를 잡음 — LinkRequestPage.test.tsx가 클릭 핸들러의 비동기 체인(fetch→navigate)이 끝나기 전에 getByText로 동기 단언해서 타이밍에 따라 실패하던 문제. findByText로 교체해서 같은 PR에 반영
  • main branch protection이 전혀 없어서 CI가 강제력 없는 상태였음 — verify(CI) 통과를 required status check로 설정 (리뷰 승인은 미요구, force-push/삭제 금지)
  • k8s/04-client.yaml의 client Deployment에 resource requests/limits이 없어서 단일 노드에서 server/ai/postgres와 무제한 경쟁 중이었음 — requests 50m cpu/64Mi mem, limits 200m cpu/128Mi mem 추가 (infra #4#5), client 재배포로 클러스터에 바로 반영·검증
  • 결과: client는 이제 배포도 되고, PR 검증도 강제되고, 리소스 제한도 걸린 상태. server/ai는 이 세 가지(CI/branch protection/resource limits) 전부 아직 없음

2026-08-06 — server/ai 실배포, 인프라 정리

저장소 전체 점검

5개 저장소 전부 오늘 활동이 있어서 훑어봄. client는 근로자/업무함 API 연동(#269/#271) 정상 진행 중. server가 예상보다 훨씬 활발했음 — AI 연동(OCR 계약 검증, AI 후보 결정→Task 생성, AiRun SSE, RLS 강화 등 다수 merge)과 별개로, PR #97("데모 배포 환경과 운영 검증 절차를 구성")이 client와 거의 동일한 패턴(Dockerfile, deploy.yml, runbook)을 이미 준비해놓고 명시적으로 "infra 저장소에서 namespace/Secret/PostgreSQL/deployment 최초 배포"를 기다리는 상태였음. 클러스터 확인 결과 실제로 postgres-secret/server-env 둘 다 없고 client만 떠 있었음.

ai self-heal 적용

aideploy.yml도 client의 원래 문제(deployment/ai 없으면 그냥 실패)를 그대로 갖고 있어서 client와 동일한 self-heal 패턴 적용: ai#21 → [ai#22](https://github.com/fowoco/ai/pull/22, 팀에서 직접 머지). server는 PR #97이 이미 deploy.yml을 다루고 있어서 충돌 방지 차원에서 코드는 안 건드리고 server#104로 필요한 것(Secret 키 목록 등)만 트래킹.

AWS 보안그룹 정리

fowoco-sg(sg-02aef781851a7cde2)의 SSH(22)가 0.0.0.0/0으로 전체 공개된 채 2026-07-29부터 방치돼 있었음(당시 "아직 미조치"로 기록). 실제 SSH 사용자가 아무도 없음을 확인(kubectlKUBE_CONFIG로 직접 붙는 구조라 SSH 자체가 불필요)하고 규칙을 완전히 삭제(revoke)함. k3s API(6443)는 그대로 0.0.0.0/0 유지 — GitHub Actions가 매 배포마다 이 포트로 직접 kubectl을 실행하는데 러너 IP 대역이 7,300개 이상이라 allowlist가 불가능함. TLS client-cert 인증이 실질적 방어선.

server 최초 배포 (ai가 계획보다 먼저 실전 배포되면서 순서가 당겨짐)

ai PR #22가 머지되고 실제로 rollout되면서 client와 똑같은 **GHCR private → ImagePullBackOff**가 재현됨. 조직 정책은 이미 켜져있어서 패키지 개별 Public 전환만으로 해결 — 다만 전환 직후 익명 pull 토큰 발급이 짧게(약 1분) 전파 지연을 겪어서 pod를 강제로 재시작시켜 바로 해결.

이 흐름을 보고 "server도 이번에 같이 하자"로 결정, 수동으로 최초 배포 진행:

  • postgres-secret 생성, 01-postgres.yaml 적용
  • PostgreSQL role을 fowoco_migration(테이블 소유, Flyway 전용)/fowoco_runtime(DML만, NOSUPERUSER NOBYPASSRLS)으로 분리 생성 — docs/database/postgresql-rls-rollout.md가 이 role 분리를 명시적으로 infra 책임으로 못박아둔 부분을 그대로 따름. ALTER DEFAULT PRIVILEGES로 runtime role이 migration role이 만드는 테이블에 자동으로 DML 권한을 받도록 설정
  • server-env Secret 생성. SPRING_PROFILES_ACTIVE=dev로 띄움(prod는 TLS + 릴리즈된 Workflow Catalog가 필수인데 아직 없음 — server 자체 runbook도 이 단계는 dev+DRAFT 카탈로그로 하라고 명시)
  • ghcr.io/fowoco/server도 역시 private → GHCR private 문제가 3/3(client/ai/server) 전부에서 재현, Public 전환으로 동일하게 해결. 이제 새 fowoco 패키지는 기본적으로 이 전환이 필요하다고 가정할 것

배포 중 새로 발견한 버그 2개:

  1. SERVER_PORT 충돌 — k8s가 네임스페이스 내 모든 Service에 대해 <SVC이름>_PORT 형태 env var를 자동 주입하는데(레거시 Docker links 호환 기능, 기본 켜짐), Service 이름이 하필 server라서 SERVER_PORT=tcp://<clusterIP>:8080이 주입되고 Spring Boot의 server.port 프로퍼티랑 이름이 충돌해서 부팅 자체가 실패함. enableServiceLinks: false를 pod spec에 추가하면 이 자동 주입이 꺼짐 — server뿐 아니라 postgres/ai/client도 같은 클래스의 버그를 예방 차원에서 동일 적용 (infra#7, 머지 후 클러스터에도 바로 반영)
  2. AI_RUNTIME_SERVICE_CREDENTIAL 필수 검증AI_RUNTIME_ENABLED=true일 때 이 값이 비어있으면 Spring 기동 자체가 실패하도록 server가 자체 검증하고 있었음(.env.example엔 선택 항목처럼 주석 처리돼 있어서 놓치기 쉬움). 랜덤 값 생성해서 채움 — ai는 아직 이 값을 검증하지 않아서 당장은 문제없음

최종 확인

kubectl -n fowoco get podsclient/server/ai/postgres 전부 1/1 Running. /actuator/health·/health 둘 다 200. Flyway가 30개 테이블을 fowoco_migration 소유로 정상 생성 (\dt로 확인). POST /api/v1/auth/signup 스모크 테스트 — 구조화된 400 INVALID_REQUEST 응답(테스트 payload가 틀렸을 뿐 앱은 정상 서빙 중). 외부 도메인 경유도 확인: http://fowoco.3.35.105.80.nip.io/api/v1/auth/signup → 400 (ingress의 /apiserver 라우팅이 실제로 동작). client/server/ai 세 앱이 전부 같은 도메인 하나로 붙는 상태가 처음으로 완성됨.

실제 비밀값(DB 비밀번호, JWT 시크릿, AI 서비스 credential)은 k8s Secret에만 존재 — git이나 Wiki 어디에도 평문으로 남기지 않음.

남은 일

  • serverdeploy.yml에 self-heal 패턴 적용 — PR #97이 아직 열려있어서 머지된 뒤에 진행 (지금 건드리면 같은 파일 충돌)
  • server의 CI 워크플로우/branch protection/resource limits — client에 했던 것과 동일하게 아직 안 함
  • prod 프로필 전환은 TLS + 릴리즈된 Workflow Catalog 준비 후

참고

Clone this wiki locally