Deallo

인프라 · 배포

릴리스·배포 파이프라인 자동화 — 9단계를 2단계로

매주 1~2회, 릴리스마다 9단계를 손으로 반복했다. 이걸 GitHub Actions 체인으로 묶어 2단계로 줄이기까지 20일이 걸렸다. 코드를 짠 시간보다 "왜 안 되지"를 파고든 시간이 훨씬 길었다.

릴리스 한 번에 9단계, 12~17분

릴리스 한 번에 이걸 손으로 했다.

# 릴리스 PR 만들기 (5단계)
git worktree add ../release-temp develop
git checkout -b release/v26.04.30 origin/develop
git merge origin/main -s ours      # ← 빼먹으면 phantom conflict
git push -u origin release/v26.04.30
# GitHub 웹에서 PR 생성
# 머지 후 (4단계)
6. PR 머지 → CodeBuild 자동 빌드 (몇 분 대기)
7. Slack에서 새 이미지 태그 복사
8. service-config 리포 yaml의 image 태그 손으로 교체 → main 직접 push
9. ArgoCD UI에서 SYNC 클릭

그중에서도 사고가 잦던 지점들:

  • -s ours를 빼먹으면 develop과 main이 충돌도 없는데 git이 충돌이라 우기는 _phantom conflict_가 처음부터 다시 시작됐다
  • 같은 날 두 번째 배포면 v26.04.30.1 같은 suffix를 머릿속으로 카운트했다
  • yaml 태그를 손으로 갈아끼우다 다른 라인을 건드릴 위험
  • service-config를 PR 없이 main에 직접 push — 리뷰 우회
  • 브랜치명 오타·옵션 누락·태그 오타 — 휴먼 에러 지점이 5곳 이상

무엇이 좋아졌나 (정량)

지표BeforeAfter
사람 액션 단계92 (78%↓)
1회 소요12~17분~30초 + 버튼 클릭 (95%↓)
외울 명령어5개 + -s ours0개
휴먼 에러 지점5곳+0

연간 약 16시간(개인), 팀 5인 확산 시 약 80시간. 덤으로 service-config를 main에 직접 push하던 패턴이 사라져 감사 추적성도 올랐다.

결정의 흔적 — 막힐 때마다 갈아엎었다

자동화는 한 번에 안 됐다. 막힐 때마다 원인을 파고 결정을 바꿨다.

① "PR은 만들어지는데 CI가 안 도네" — GITHUB_TOKEN의 함정

차수시도결과
1차GITHUB_TOKEN으로 PR 자동 생성PR은 생성되나 CI가 트리거 안 됨
2차PR 자동 생성 포기, 생성 링크만 출력 → 사람이 클릭CI 정상 (반자동)
3차RELEASE_PR_PAT(user-context) 발급CI 트리거 + 완전 자동

원인: GitHub는 GITHUB_TOKEN으로 만든 PR이 다른 워크플로를 트리거하지 않게 막는다(자동화 무한 루프 방지). 이걸 모르고 한참 헤맸다. 토큰 3종(GITHUB_TOKEN / PAT / GitHub App)의 트레이드오프를 따져 — 개인·소규모엔 PAT, 조직 단위엔 App — PAT로 갔다.

② "같은 버전이 두 번 만들어지네" — N차 카운트를 두 번 갈아엎다

차수방식문제
1차release 브랜치가 있으면 .1 붙이기머지 후 브랜치 자동삭제 룰 때문에 이미 머지됐어도 브랜치가 없어 같은 버전 재생성
2차브랜치가 아니라 PR 제목으로 카운트 (OPEN + MERGED 합산)해결
OPEN=$(gh pr list --state open --search "[RELEASE] v26.04.30 in:title" --jq length)
MERGED=$(gh pr list --state merged --search "[RELEASE] v26.04.30 in:title" --jq length)
COUNT=$((OPEN + MERGED))   # closed-without-merge는 제외 — 취소된 시도 이름 재사용 OK

상태(브랜치 존재)가 아니라 PR 이력(기록)으로 카운트해야 삭제 룰에 안 깨진다는 걸 배웠다.

③ "둘이 동시에 누르면?" — concurrency

영희와 철수가 같은 순간 "Run workflow"를 누르면, 둘 다 "오늘 버전 없네" 카운트 → 같은 브랜치명으로 push 충돌. concurrency 그룹으로 한 번에 하나만 돌게 큐잉했다.

concurrency: { group: release-pr, cancel-in-progress: false }

④ "전체를 자동 sync하면 위험" — 자원 한정 배포

ArgoCD의 deallo-prod는 web/api/ws가 한 묶음이라, 전체 자동 sync를 켜면 web 배포가 api·ws의 수동 변경(drift)까지 건드린다. 그래서 자동 sync 토글을 켜지 않고 web Deployment만 자원 한정 sync로 좁혔다.

argocd app sync deallo-prod --resource apps:Deployment:web-deallo

절반은 레포 밖이었다 — 권한을 하나씩 얻어내며

이 자동화는 deallo 웹 레포 안에서 끝나는 일이 아니었다. 체인이 지나가는 시스템이 넷 — 앱 레포, 배포 설정 레포, AWS(CodeBuild·Secrets Manager·IAM), 그리고 ArgoCD/k8s 클러스터 — 인데, FE에겐 그 어디에도 기본 권한이 없었다. 그래서 코드만큼 권한을 짰다.

  • 자동 PR 생성용 fine-grained PAT는 배포 설정 레포 한정 권한으로 조직 owner의 승인을 받아 발급
  • 그 토큰을 AWS Secrets Manager에 등록하고, CodeBuild 서비스 롤에는 그 시크릿만 읽을 수 있는 IAM 인라인 정책을 추가
  • 마지막 자동 sync는 백엔드 인프라 담당과 협의해 ArgoCD 계정·토큰을 새로 만들고, 빌드 환경에서 ArgoCD 서버로 네트워크가 열리는지부터 확인
  • 검증하려고 프로덕션 클러스터를 kubectl로 직접 조회할 IAM 접근도 이때 얻었다

코드 한 줄보다 승인 하나가 오래 걸리는 날이 많았다. 대신 권한을 빌려 쓰지 않고 직접 얻어둔 덕에 — 몇 주 뒤 상용 OOM 사건이 터졌을 때, 이때 뚫어둔 kubectl·관측 도구 접근이 그대로 추적 무기가 됐다.

검증이 진짜 사투였다

코드를 다 짜고도 실제 릴리스에서 세 번 깨졌다. 자동화의 비용은 "짜기"가 아니라 "정말 도나"였다.

  1. argocd-server를 재시작 안 해 설정이 안 먹은 걸 첫 릴리스에서 발견 → 재시작
  2. RBAC 권한 객체 형식 오류deallo-prod/*로 줬는데 실제 포맷은 default/deallo-prod였다
  3. 권한 시뮬레이션 argocd admin settings rbac can ... → Yes사전 검증을 못 박은 뒤에야, 실제 release(2026-05-19)에서 자동 sync가 도는 걸 확인하고 종료했다

배운 점

  • 자동화의 본체는 "짜기"가 아니라 "프로덕션에서 검증"이다. 권한·재시작·환경 같은 보이지 않는 전제가 실제 릴리스에서야 드러난다 → 이제는 권한 시뮬레이션으로 사전에 못 박는다
  • 막힐 때마다 결정을 갈아엎은 게 자산이 됐다. GITHUB_TOKEN이 왜 CI를 안 깨우는지, 카운트를 왜 "기록" 기준으로 해야 하는지는 막혀봐야 안다
  • "전체 자동화"가 늘 정답은 아니다. web·api·ws가 묶인 구조에선 자원 한정이 더 안전했다