배포를 기억이 아니라 기록으로 남기는 법

테스트, 이미지, GitOps 상태, migration 순서를 반복 가능한 릴리즈 경로로 맞췄다

혼자 개발할 때 배포는 쉽게 가벼워진다. 로컬에서 테스트가 통과했고, 서버에 올렸고, 화면이 뜨면 끝난 것처럼 느껴진다.

한강자리는 그렇게 두기 어려웠다. App Store에 올라간 앱이 호출하는 API이고, API 뒤에는 worker, DB migration, Redis cache, 예측, push가 함께 움직인다. 배포가 흔들리면 화면 하나가 아니라 데이터를 가져오고 보여주는 일 전체가 흔들릴 수 있다.

그래서 한강자리 백엔드는 CI, container registry, deploy repository, ArgoCD, K3s 실행 환경을 거쳐 배포한다. 중요한 것은 변경이 어떤 확인을 지나 사용자가 쓰는 서버까지 가는지다.

배포는 같은 길로 반복한다

혼자 개발하더라도 사용자가 붙은 API는 “어제 내가 어떤 명령을 쳤더라”에 기대면 안 된다. 배포가 손에 익을수록 작은 실수가 반복되기 쉽다. 그래서 같은 확인, 같은 이미지 만들기, 같은 desired state 업데이트를 한 길로 묶었다.

sequenceDiagram
  autonumber
  participant Dev as 개발자
  participant Git as Git 서버
  participant CI as CI
  participant Registry as 컨테이너 레지스트리
  participant Deploy as 배포 저장소
  participant Argo as ArgoCD
  participant K3s as K3s 실행 환경
  participant Smoke as 스모크 검사

  Dev->>Git: 변경 브랜치 올리기
  Git->>CI: 검증 절차 실행 알림
  CI->>CI: 기본 검사와 테스트, API 약속 확인
  CI->>CI: 배포 manifest 생성과 검증
  CI->>Registry: 버전 붙은 이미지 만들고 게시
  CI->>Deploy: 운영 원하는 상태 갱신
  Argo->>Deploy: 운영 원하는 상태 읽기
  Argo->>K3s: 적용 순서와 훅 실행
  Argo->>K3s: 서버와 작업 프로세스 배포
  Smoke->>K3s: 상태와 핵심 기능 확인

feature branch에서는 확인만 한다. production desired state 갱신은 보호된 기본 브랜치에서만 일어난다. 이 구분이 있어야 실험 브랜치가 실제 배포로 새지 않는다.

이 구분은 마음의 부담도 줄인다. 실험 브랜치에서 테스트가 실패하면 고치면 된다. 하지만 그 실패가 production image나 deploy repository 변경으로 이어지면 복구해야 할 일이 된다. 작은 프로젝트일수록 이런 방화벽이 필요했다.

배포 전에 자동으로 걸러낸 것

CI가 하는 일은 “빌드 성공”보다 넓다. 현재 CI는 대략 다음을 확인한다.

단계목적
lintbackend 코드의 기본 오류 확인
testPostgres, Redis를 포함한 API 로직 테스트
localization validate앱 문자열 catalog와 문서화된 key 검증
API 형식 보존API contract와 만들어진 산출물 보존
manifest validationKustomize/Kubernetes manifest가 빌드되는지 확인
image buildbackend image 만들기
security artifactsSBOM, image scan, signing 같은 공급망 검증
deploy repo updateproduction desired state의 image tag 갱신

개인 프로젝트라도 이 정도 확인은 필요했다. 한강자리는 API만 있는 서비스가 아니다. worker, DB migration, cache, push, forecast가 함께 움직인다. 배포가 실패하면 앱 화면 하나가 아니라 데이터를 만드는 쪽까지 깨질 수 있다.

확인 단계를 늘리는 목적은 겁을 주기 위해서가 아니었다. 배포 전에 물어볼 질문을 자동화해두면, 사람이 마지막에 봐야 할 것은 “이 변경을 지금 사용자 앞에 내보내도 되는가”로 좁아진다.

서버가 따라갈 값은 별도 저장소에 남겼다

애플리케이션 코드 저장소와 production desired state를 분리했다. CI는 이미지를 만들고, deploy repository의 image tag를 갱신한다. ArgoCD는 deploy repository를 보고 K3s 실행 환경을 맞춘다.

flowchart LR
  AppRepo["애플리케이션 저장소"] --> CI["CI 파이프라인"]
  CI --> Image["버전 이미지"]
  CI --> DeployRepo["배포 저장소<br/>원하는 상태"]
  DeployRepo --> Argo["ArgoCD"]
  Argo --> Runtime["K3s 실행 환경"]

이렇게 나누면 좋은 점은 명확하다.

  • 어떤 코드가 어떤 image로 나갔는지 추적하기 쉽다.
  • production manifest 변경과 앱 코드 변경을 분리할 수 있다.
  • rollback은 이전 desired state로 되돌리는 방식이 된다.
  • ArgoCD가 실제 실행 환경과 desired state의 차이를 보여준다.

배포가 기록으로 남으면 회고도 쉬워진다. “언제부터 이상했는가”를 볼 때, 코드 commit과 image, desired state, 실행 환경 rollout을 한 줄로 따라갈 수 있다.

migration과 worker 순서를 확인했다

백엔드 배포는 API deployment만 바꾸는 일이 아니다. DB schema, bootstrap job, API, worker가 순서대로 맞아야 한다.

ArgoCD의 sync wave와 hook은 이 순서를 표현하는 데 사용한다. 공식 문서상 ArgoCD는 sync operation에서 phase와 wave로 리소스 적용 순서를 제어할 수 있다. 한강자리도 이 성격을 이용해 migration/bootstrap과 rollout 순서를 분리한다.

flowchart TB
  Wave1["사전 동기화<br/>마이그레이션 또는 부트스트랩"] --> Wave2["핵심 서비스<br/>Postgres Redis API"]
  Wave2 --> Wave3["워커<br/>주차 나들이 예측 푸시"]
  Wave3 --> Wave4["스모크 검사<br/>상태 · 기능 최신성"]

자동화와 사람이 확인하는 지점은 나눠야 했다. 이미지 만들기와 desired state 업데이트는 CI가 반복해서 맡는다. sync와 smoke 확인은 장애 영향을 고려해 명시적으로 확인한다.

완전 자동 배포가 항상 더 좋은 것은 아니었다. 특히 DB migration, worker rollout, 원천 데이터 수집처럼 한 번에 여러 곳이 함께 움직이는 배포는 사람이 마지막으로 맥락을 확인하는 편이 안전했다. 자동화는 반복 작업을 줄이고, 수동 확인은 어디까지 영향을 줄지 보는 단계로 남겼다.

iOS 릴리즈와 서버 릴리즈의 시간차

iOS 앱 릴리즈는 백엔드와 리듬이 다르다. 서버는 몇 분 안에 바뀔 수 있지만, 앱은 App Store review와 사용자 업데이트 주기를 거친다.

그래서 API contract는 보수적으로 다룬다.

  • 이미 배포된 DTO를 깨지 않는다.
  • optional field 추가와 required field 변경을 구분한다.
  • 앱 버전이 섞여 있는 기간을 고려한다.
  • 서버 변경은 앱 rollout보다 빨리 켜질 수 있다.
  • 위젯은 앱보다 더 오래된 snapshot을 볼 수 있다.

이 점 때문에 generated_at, observed_at, freshness, status 같은 메타데이터를 DTO에 명시했다. 여기서 freshness는 값이 얼마나 최신인지 알려주는 필드다. 클라이언트가 모르는 값을 억지로 성공처럼 해석하면 안 된다.

배포는 기억이 아니라 기록으로 남았다

개인 프로젝트라도 production까지 가는 과정을 문서화해야 했다. CI는 테스트뿐 아니라 API 형식, manifest, image, supply chain까지 확인했고, deploy repository는 코드 배포와 실행 환경의 desired state를 분리해 줬다.

migration과 worker rollout은 API 배포만큼 조심해야 했다. 서버는 빨리 바뀌지만 iOS 앱은 App Store review와 사용자 업데이트 주기를 지나므로, API는 서버보다 긴 호환성 기간을 견뎌야 했다.

한강자리에서 배포는 파일을 올리는 일이 아니라, 변경을 설명 가능한 기록으로 남기는 일이 됐다. 그래야 문제가 생겼을 때 “무엇이 바뀌었는가”를 기억이 아니라 기록으로 확인할 수 있다.

이미지 확대