← 포스트 목록
GitHub ActionsCI/CDAWSIAM Role

"다음엔 누가 배포하죠?" — 인수인계 없이 버튼 하나로 끝나는 GitHub Actions 배포 파이프라인

2026-07-10 · 약 12분 소요

요약

문제
배포가 담당자 손에 달린 수동 작업이라 사람이 바뀔 때마다 절차를 처음부터 인수인계해야 했고, 롤백도 사실상 직전 버전 한 걸음까지만 가능
실행
배포 절차를 사람 머릿속이 아니라 파이프라인 코드에 두기로 하고 GitHub Actions로 옮김. 자격증명은 장기 Access Key 대신 OIDC 기반 IAM Role로 발급. 결과물을 덮어쓰지 않고 S3에 버전별 폴더로 쌓고, 워크플로우를 build·release·validation 셋으로 분리
결과
절차를 외우거나 인수인계받지 않아도 버튼 하나로 배포가 끝나고, 릴리즈된 버전에서 문제가 생겨도 원하는 버전으로 롤백 가능하도록 설계함. 빌드와 배포를 분리해, 미리 빌드해둔 결과물을 재빌드 없이 원하는 시각에 정확히 맞춰 올릴 수 있게 됨

이 배포, 오늘은 제가 했지만 다음엔 누가 할 수 있을까요? 제가 휴가 간 사이에 급하게 배포해야 하는 일이 생기면, 다른 팀원이 저 없이도 그대로 따라 할 수 있을까요? 로컬에서 명령어 몇 개를 순서대로 실행하는 이 절차를, 새로 합류한 사람에게는 매번 어떻게 처음부터 설명해야 할까요?

저희 팀이 실제로 마주했던 상황입니다. KOKKOK 플랫폼 안에서 운영하는 서비스가 계속 늘어나면서 배포 빈도도 함께 늘었는데, 배포 방식은 여전히 사람이 직접 하는 수동 작업이었습니다. 담당자가 로컬에서 npm run build를 돌리고, 결과물을 직접 S3에 올린 뒤 CloudFront 캐시를 무효화하는 식이었습니다. 이 과정을 GitHub Actions로 자동화하면서 겪은 문제와 해결 과정을 정리합니다. 비슷한 규모의 모노레포를 운영하는 팀에게 도움이 되길 바랍니다.

앱이 늘어날수록 수동 배포의 비용은 배포 한 번에 걸리는 시간이 아니라, 이 절차를 계속 사람이 반복하고 또 다른 사람에게 그대로 넘겨줘야 한다는 데 있었습니다. 담당자마다 배포 순서를 조금씩 다르게 기억하고 있었고, 담당자가 바뀌거나 새로 합류한 팀원에게 인수인계를 할 때마다 순서를 처음부터 다시 설명해야 했습니다. 문서로 절차를 정리해서 실제 상황에 맞게 계속 반영은 했지만, 그 문서를 받아본 사람이 내용을 100% 이해하지 못하는 게 더 큰 문제였습니다.

여기서 세운 원칙은 두 가지였습니다.

무엇을 비교했고 왜 IAM Role이었나

자동화 도구는 GitHub Actions로 정했습니다. 이미 코드 저장소로 GitHub를 쓰고 있어서 별도 CI/CD 플랫폼을 새로 들이지 않고 바로 워크플로우를 붙일 수 있었기 때문입니다. 대신 따로 정해야 했던 건 AWS 인증 방식이었습니다. 워크플로우가 배포 때마다 S3·CloudFront에 접근하려면 자격증명이 필요한데, 이 자격증명을 어떻게 다룰지가 문제였습니다.

방식설명비고
Access Key / Secret장기 Access Key를 발급받아 GitHub Secrets에 저장해두고 계속 사용키가 유출되면 만료 전까지 계속 악용될 수 있고, 서비스가 늘어날 때마다 키를 새로 발급·보관해야 함
IAM Role, OIDC (선택)워크플로우 실행 시점에 GitHub과 AWS가 OIDC로 신뢰 관계를 맺고, 그때그때 단기 자격증명만 발급받음키 자체를 저장소에 저장할 필요가 없고, 탈취되더라도 유효 시간이 짧아 위험 범위가 줄어듦

장기 Access Key를 서비스 수만큼 계속 관리하는 부담과, 유출됐을 때의 위험 범위를 줄이기 위해 IAM Role 방식을 선택했습니다.

이 두 원칙에 따라 처음 만든 파이프라인은 이랬습니다. 10개 서비스를 한 워크플로우에서 한꺼번에 빌드하는 대신, 실행할 때 서비스 하나를 골라 그 서비스만 빌드하도록 했습니다. 그리고 "버튼 한 번으로 끝나는" 편의성을 위해, 빌드부터 S3 업로드·배포 대상 전환·CloudFront 캐시 무효화까지를 워크플로우 실행 한 번에 전부 처리하도록 만들었습니다. 배포를 실행할 수 있는 사람과 빌드를 허용할 브랜치도 처음부터 검증 단계에 넣어뒀습니다.

1차 문제: 버튼 한 번으로 묶어두니 버전 관리가 안 됐다

빌드와 배포를 한 워크플로우로 묶어서 처리하다 보니, 배포본을 버전별로 관리한다는 개념 자체가 없었습니다. S3에는 방금 올라가 바로 서비스에 반영되는 폴더와, 그 직전에 빌드됐던 폴더 딱 2개만 존재했습니다. 문제가 생겨서 두 단계 전 버전으로 되돌리고 싶어도 이미 지워지고 없어서 되돌릴 방법이 없었고, 사실상 롤백이 가능한 범위는 "바로 직전"으로 딱 한 걸음뿐이었습니다.

해결: S3에 결과물을 덮어쓰는 대신, 시맨틱 버전을 폴더명에 포함해 버전별로 쌓아두도록 바꿨습니다.

# 빌드 결과물을 버전별 폴더에 쌓아둔다 (덮어쓰지 않는다)
aws s3 cp --recursive "$BUILD_DIR" \
  "s3://$BUCKET/__${ENV}-${SERVICE_NAME}-${VERSION}/"

이제 S3에는 __prod-serviceA-v1.2.0, __prod-serviceA-v1.2.1처럼 배포됐던 버전이 폴더 단위로 계속 쌓입니다. 직전 버전뿐 아니라, 필요하면 몇 단계 전 버전으로도 돌아갈 수 있는 발판이 마련된 셈입니다.

2차 문제: 버저닝을 해도 릴리즈 시점을 맞추기 어려웠다

버전별로 결과물을 쌓아두기 시작했지만, 빌드와 배포가 여전히 한 워크플로우에 붙어 있었습니다. 서비스 하나 기준으로 빌드에만 5분 넘게 걸렸는데, 이 말은 특정 시각에 정확히 반영하려면 그보다 한참 전에 워크플로우를 실행해야 했고, 그마저도 정확한 반영 시점을 보장할 수 없었다는 뜻이었습니다. 버저닝을 도입해도, 원하는 버전을 원하는 시점에 올리는 일 자체는 여전히 "처음부터 다시 빌드해서 배포"하는 방법밖에 없었습니다.

해결: 워크플로우를 build(빌드 & 업로드) / release(배포 & 무효화) / validation(공용 검증) 세 개로 나눴습니다. build는 1차 문제에서 만든 버전별 폴더에 결과물을 쌓기만 하고, release는 그중 원하는 버전 폴더를 골라 S3 루트로 복사하고 CloudFront 캐시를 무효화하는 일만 합니다.

# release — 원하는 버전 폴더를 S3 루트로 복사한다 (재빌드 없음)
aws s3 cp --recursive \
  "s3://$BUCKET/__${ENV}-${SERVICE_NAME}-${VERSION}/" "s3://$BUCKET/"
aws cloudfront create-invalidation --distribution-id "$CF_ID" --paths "/*"

release는 복사 전 대상 버전 폴더에 index.html이 실제로 존재하는지, 용량이 비정상적으로 작지 않은지를 확인하는 무결성 검증도 거칩니다. 복사 도중 실패하면 직전 버전 폴더에서 자동으로 다시 복사해, S3 루트가 반쯤 비워진 상태로 남지 않도록 했습니다. 검증 로직도 build·release 워크플로우 각각에 넣는 대신 validation 워크플로우 하나로 뽑아 workflow_call로 재사용하게 했습니다.

빌드와 배포를 분리하면서 job 구성도 다시 손봤는데, 서비스 하나를 기준으로 실측한 결과 다음과 같이 줄었습니다.

워크플로우개선 전개선 후개선율
build (빌드 & 업로드)5m 27s2m 21s약 43.1% ↓
release (배포)1m 41s1m 15s약 25.7% ↓

이제 원하는 시각에 정확히 반영하고 싶으면, 미리 build를 실행해 원하는 버전을 S3에 쌓아두고, 반영하고 싶은 시각에 release만 짧게 실행하면 됩니다. 롤백도 "예전 버전으로 다시 빌드하는 일"이 아니라, release 워크플로우를 예전 버전 번호로 다시 실행하는 일이 됐습니다.

종합 결과

결과적으로 배포는 담당자가 절차를 따로 익히거나 인수인계받을 필요 없이, 정해진 버튼(workflow_dispatch 실행) 하나만 누르면 끝나는 일이 됐습니다. 이게 가능해진 건 아래 장치들이 사람이 하던 판단과 기억을 대신 떠맡았기 때문입니다.

배운 점

남은 이슈

지금 구조는 배포 전 검증까지는 자동화했지만, 배포 후 상태를 확인하는 건 아직 사람의 몫입니다. 배포가 끝난 뒤 서비스가 실제로 정상 동작하는지는 담당자가 직접 확인하고 있고, 이상이 있으면 그제서야 이전 버전으로 다시 release 워크플로우를 실행합니다. 다음 단계로는 배포 직후 헬스체크를 자동으로 돌리고, 헬스체크가 실패하면 이전 버전으로 자동 롤백하는 흐름까지 포함시키려 합니다.

여러분의 모노레포도 서비스가 늘어날수록 배포가 점점 무거워지고 있나요? 그렇다면 편의성을 위해 하나로 묶어둔 단계가 있는지부터 점검해보시길 권합니다. 저희도 그 지점이 버전 관리와 릴리즈 타이밍을 동시에 발목 잡고 있었습니다.