2단계 CI(지속적 통합) 파이프라인 설계 - 100-hours-a-week/KTB4-3rd-CLOUD GitHub Wiki
- Dev : 개발자들의 공동 테스트환경으로 정기 배포 전 새로운 기능을 테스트 하는 용도나 여러 팀에서 개발한 기능을 통합 테스트한다.
- Prod : V1 개발 기간에는 주 1-2회 정도 되는 정기 배포를 하는 환경으로 최종적으로 기능 테스트가 완료되었다면, main 에 머지하고 QA이후 수동 승인을 통해 배포한다.
프론트엔드, 백엔드, AI는 독립 레포에서 PR 기반 CI를 운영한다. 단 CI 실행 범위는 레포 단위가 아니라 변경 영향도에 따라 결정한다.
모여타 V1은 핵심 기능 수가 많고 프론트엔드, 백엔드, AI 기능이 서로 연결되어 있다. 따라서 각 레포의 테스트가 개별적으로 통과하더라도 실제 서비스 조합에서는 여러 처리 로직 흐름에서 문제가 발생할 수 있다. 이러한 호환성 문제를 기능 별 개발 후반이나 배포 직전에 발견할 수록 수정 비용이 커진다. 따라서 PR 단계에서 빠르게 발견할 수 있도록 CI 범위를 분리해서 운영한다.
모든 PR에서 전체 테스트를 실행하면 CI 대기 기간이 길어지고 개발자들이 CI를 우회하려 하기 때문에 빌드 시간은 10분이내로 유지하도록 한다. 테스트와 피드백 주기를 짧게 가져가는 것이 필요하다.
| 브랜치 | 용도 | 생성 기준 | PR 대상 |
|---|---|---|---|
feat/* |
신규 기능 개발 | dev |
dev |
fix/* |
Dev 또는 테스트에서 발견된 오류 수정 | dev |
dev |
refactor/* |
기능 변경 없는 구조 개선 | dev |
dev |
chore/* |
설정·의존성·빌드 작업 | dev |
dev |
test/* |
테스트 추가·수정 | dev |
dev |
hotfix/* |
Prod 긴급 장애 수정 | main |
main, 이후 dev 동기화 |
Callout
fix와hotfix의 구분fix/*와hotfix/*는 구분하는 것이 좋습니다.
fix/*: Dev, QA, 테스트 과정에서 발견한 오류 수정hotfix/*: 이미 Prod에 반영된 장애를 긴급 수정 hotfix는 main에서 생성한다. 브랜치에 대한 직접 push는 제한하고, 가능한 한 PR과 자동화된 merge queue를 통해 변경을 반영한다. 각 작업 브랜치는 dev를 대상으로 PR을 생성한다.
1. dev에서 작업 브랜치 생성
2. 작업 브랜치에 push
3. PR 생성
4. 작업 레포의 기본 CI 실행
5. API·DB·AI 계약 변경 시 추가 통합 테스트
6. CI와 리뷰 통과
7. dev merge queue에서 누적 상태 검증 - conflict 방지
8. dev 자동 병합
9. dev 환경 자동 배포
위 단계에서 고려한 지점은 **작업 브랜치의 CI가 성공했다고 바로 ****dev****에 병합하지 않도록 merge queue에서 실제 누적 변경 상태를 검증한 후 병합한다. **
V1애서는 개발과 피드백 속도가 중요하기 때문에 브랜치 기반으로 운영하는 것이 중요하다.
작업 브랜치에서는 빠른 CI를 실행하도록 한다.
- lint
- 단위 테스트
- 타입·컴파일 검사
- 빌드
- 기본 startup smoke testdev 병합 시에는 기본 CI 테스트에 아래 내용을 추가 테스트한다.
- 프론트엔드·백엔드·AI 통합 테스트
- health/readiness 확인
- 핵심 사용자 시나리오 smoke test
- 변경된 API·AI 응답 계약 검증작업 브랜치마다 공용 Dev 환경에 배포하면 여러 개발자의 변경이 서로 덮어쓰일 수 있다.
| 브랜치 | 목적 | 주요 트리거 |
|---|---|---|
dev |
여러 기능을 통합하는 개발 환경 | PR merge, dev 배포 |
main |
릴리즈 후보 및 운영 배포 기준 | release PR merge, prod 정기 배포 |
모든 파트의 CI는 PR 이벤트를 기준으로 실행한다. 작업 브랜치에 push할 때도 CI를 실행할 수 있지만, 실제 병합 가능 여부를 판단하는 기준은 dev 또는 main을 대상으로 생성된 PR의 CI 결과로 한다.
파트별 CI 결과는 다음과 같이 구분한다.
- 자체 코드 검증: 각 파트의 필수 CI
- 레포 간 계약 검증: API, 이벤트, AI 응답 변경 시 실행
- 전체 통합 검증:
dev병합 또는 핵심 기능 변경 시 실행 - 운영 배포 검증:
main병합 및 릴리즈 태그 기준 실행
백엔드는 프론트엔드와 AI가 사용하는 API와 데이터 구조를 제공한다. 기본 CI는 dev 또는 main으로 PR을 생성하거나 새로운 커밋을 push하면 기본 CI를 가능한 작업으로 나눠 병렬로 실행한다.
- 코드 포맷 및 정적 분석
- 단위 테스트
- API 단위·통합 테스트
- 컴파일 및 빌드
- 테스트 환경 변수 기반 애플리케이션 실행
- health/readiness 확인
- 주요 API startup smoke testdev에 병합된 이후에는 Dev 배포용 이미지를 생성한다.
dev 병합
→ 백엔드 이미지 빌드
→ dev-<commit-sha> 태그 부여
→ Dev 환경 배포
→ health/readiness 확인
→ 핵심 API smoke test
main에 병합될 때는 Prod 배포 후보 이미지를 생성하되, 즉시 운영 환경에 배포하지 않는다. QA가 승인한 이후 릴리즈 태그와 연결해 Prod 배포 대상으로 확정한다.
프론트엔드는 사용자 핵심 흐름을 직접 구성하므로, 단순한 화면 변경과 백엔드·AI 연동 변경을 구분한다.
프론트엔드 PR에서는 다음 작업을 기본으로 실행한다.
- 코드 포맷 및 lint
- 타입 검사
- 단위 테스트
- 번들 및 애플리케이션 빌드
- 환경 변수 및 설정 검증
- 주요 화면 렌더링 smoke test
단순한 스타일 변경이나 백엔드와 무관한 화면 수정은 기본 CI만 실행한다. 반면 다음 변경은 추가 통합 테스트 대상으로 분류한다.
- API 호출 방식 변경
- API 응답 필드 사용 방식 변경
- 로그인·로그아웃·권한 처리 변경
- AI 결과 표시 방식 변경
- 파일 업로드·다운로드 변경
- 핵심 사용자 플로우 변경
dev 병합 이후 프론트엔드 이미지를 생성하고 Dev 환경에 배포한다.
dev 병합
→ 프론트엔드 빌드
→ dev-<commit-sha> 이미지 생성
→ Dev 환경 배포
→ 주요 사용자 화면 접속 확인
→ 핵심 사용자 시나리오 smoke test
프론트엔드에서 실제 백엔드와 AI를 호출하는 테스트는 모든 PR에서 실행하지 않는다. API, 인증, AI 연동과 관련된 변경이 있는 경우에만 테스트 범위를 확장한다.
CI 실패 시 자동으로 실패를 무시하거나 dev에 병합하지 않는다. 실패한 변경은 원인이 확인되고 필요한 검증이 완료될 때까지 병합 대상에서 제외한다.
| 실패 유형 | 처리 |
|---|---|
| 코드·단위 테스트 실패 | PR 병합 차단, 담당자가 수정 |
| 빌드 실패 | PR 병합 및 Dev 배포 차단 |
| API 계약 테스트 실패 | 제공 팀과 소비 팀이 공동 확인 |
| 통합 테스트 실패 | 해당 merge queue 중단, 문제 조합 기록 |
| 외부 API 일시 장애 | 제한된 횟수로 재시도 후 인프라 장애로 분류 |
| Dev 배포 후 smoke test 실패 | 다음 병합 중단, 이전 이미지로 Dev 롤백 |
| Main 릴리즈 테스트 실패 | QA 승인 및 Prod 배포 차단 |
| 보안·의존성 검사 실패 | 예외 승인 전까지 병합 차단 |
기존 팀은 깃허브와 디스코드 기반으로 협업 환경을 구축하였기 때문에 CI 테스트에 대한 알림을 디스코드 웹 훅을 통해 전달하는 것을 고려했다. 모여타 V1은 Prod에 매일 배포하는 구조가 아닌 주 1~2회 정기 배포하고 QA 승인 후 수동 배포한다. 실시간 장애 대응 시스템이 아니기 때문에 아래 정도의 알림이 적절하다고 판단된다.
Dev 배포 실패 알림
Dev smoke test 실패 알림
Main 릴리즈 검증 실패 알림
Prod 배포 시작·성공·실패·롤백 알림
전체 통합 테스트 실패 알림PR 단위 테스트 실패까지 디스코드로 보내면 알림이 지나치게 많아 지기 때문에 서비스 환경에 영향을 주는 실패만 전달한다.
모여타 V1은 프론트엔드, 백엔드, AI가 독립적으로 개발되지만 최종적으로는 하나의 사용자 흐름을 구성한다. 따라서 이미지 관리의 핵심은 단순히 이미지를 저장하는 것이 아니라 다음을 보장하는 것이다.
- 어떤 소스 커밋에서 이미지가 생성되었는지 추적
- Dev에서 검증한 이미지와 Prod에 배포된 이미지의 동일성 보장
- 여러 파트에서 생성되는 이미지의 구분
- 장애 발생 시 이전 정상 이미지로 빠른 롤백
- 1인이 운영해도 수동 관리가 누적되지 않는 구조 특히 V1은 기능 간 의존성이 높기 때문에 환경마다 이미지를 다시 빌드하면 테스트한 코드와 실제 배포된 코드가 달라질 수 있다. 따라서 이미지는 한 번 빌드하고, 환경별로 동일한 이미지를 승격하는 방식을 기본으로 한다.
팀의 개발 환경은 Github와 AWS로 관리되고 있다. 또한 각각의 레포지토리로 분리된다. ECR은 Private Repository, 태그 변경 방지, 이미지 스캔, Lifecycle Policy, IAM 기반 접근 제어를 제공하므로 AWS 기반 환경에서 별도의 레지스트리 서버를 직접 운영하지 않아도 된다.
서비스별로 배포 단위를 분리하여 Repository를 생성한다.
moyeota/fe
moyeota/be
moyeota/ai
서비스별 Repository를 분리하면 다음 장점이 있다.
- 프론트엔드·백엔드·AI 이미지의 혼동 방지
- 서비스별 IAM 권한 관리
- 서비스별 이미지 삭제 정책 적용
- 서비스별 이미지 사용량과 장애 원인 추적
- 특정 파트의 이미지 관리 문제가 다른 파트에 전파되는 것을 방지 1인 운영 환경에서는 Repository를 지나치게 세분화하지 않고, 실제 배포되는 서비스 단위로만 분리한다.
Git 태그와 ECR 이미지 태그는 서로 다른 목적으로 사용한다.
- Git 태그: 릴리즈와 Prod 배포 의도 표현
- ECR 이미지 태그: 이미지의 환경·목적·커밋 식별
- 이미지 digest: 실제 배포 대상의 최종 식별자 ECR 태그는 변경할 수 없도록 Immutable 설정을 적용한다. 동일한 태그를 다른 이미지로 덮어쓸 수 없게 해야 이미지 재현성과 롤백 기준을 보장할 수 있다. ECR Image Tag Mutability 문서
pr-<pr-number>-<sha7> # 통합 테스트가 필요한 PR 이미지
dev-<sha7> # Dev 배포 이미지
rc-v1.0.1-<sha7> # QA 검증용 릴리즈 후보
prod-v1.0.1-<sha7> # Prod 승격 이미지
예시는 다음과 같다.
moyeota/backend:dev-3f2a9c1
moyeota/frontend:rc-v1.0.1-3f2a9c1
moyeota/ai:prod-v1.0.1-3f2a9c1
latest, dev, prod와 같이 계속 덮어쓸 수 있는 태그는 실제 배포 기준으로 사용하지 않는다. 배포 시에는 반드시 커밋 SHA 또는 이미지 digest를 사용한다.
Semantic Versioning은 사람이 릴리즈 변경 내용을 이해하기 위한 식별자로 사용한다.
major.minor.patch
예시: v1.0.1
-
major: 기존 API, 이벤트, AI 응답 구조와 호환되지 않는 변경 -
minor: 기존 기능과 호환되면서 기능이나 API가 추가되는 변경 -
patch: 기존 계약을 유지하면서 버그나 설정을 수정하는 변경 DB 변경이 발생했다고 항상major가 되는 것은 아니다. 기존 서비스와 호환되는 컬럼 추가나 단계적 마이그레이션은minor또는patch로 관리할 수 있다. 반대로 기존 컬럼 제거, API 필드 삭제처럼 구버전 애플리케이션과 호환되지 않는 변경은major로 분류한다. 또한 이미지마다 Semantic Version을 매번 증가시키기보다는 다음과 같이 역할을 구분한다.
커밋 SHA 또는 digest → 실제 이미지 식별
Semantic Version → 사람이 이해하는 릴리즈 식별
프론트엔드·백엔드·AI가 하나의 릴리즈로 묶이는 경우에는 릴리즈 버전과 각 이미지 digest를 함께 기록한다.
release: v1.0.1
frontend: sha256:...
backend: sha256:...
ai: sha256:...
변경되지 않은 파트는 이미 존재하는 이전 digest를 그대로 사용할 수 있다. 모든 파트의 이미지를 불필요하게 다시 빌드하지 않는다.
dev merge queue와 통합 CI가 성공하면 이미지를 ECR에 업로드한다.
dev 병합
→ 이미지 빌드
→ dev-<sha7> 태그 부여
→ ECR Push
→ Dev 환경에 동일 이미지 배포
→ health/readiness 확인
→ 핵심 사용자 시나리오 smoke test
Dev 환경에 배포할 때는 다시 빌드하지 않고, 방금 생성된 이미지의 digest를 사용한다.
main 병합 시 Prod 배포 후보 이미지를 생성한다.
main 병합
→ 릴리즈 후보 이미지 생성
→ rc-v1.0.1-<sha7> 태그 부여
→ QA 테스트
→ QA 승인
→ v1.0.1 Git 태그 생성
→ 동일 이미지 digest를 Prod로 승격
Prod 배포 시 새 이미지를 다시 빌드하지 않는다. Dev 또는 QA에서 검증한 동일한 이미지 digest를 Prod에 배포해야 환경별 결과 차이를 방지할 수 있다.
ECR Lifecycle Policy를 사용해 자동으로 오래된 이미지를 정리한다. ECR은 태그 패턴이나 이미지 보존 개수를 기준으로 Lifecycle Policy를 설정할 수 있다. 초기 운영 정책은 다음과 같이 설정할 수 있다.
| 이미지 유형 | 초기 보존 기준 | 이유 |
|---|---|---|
pr-* |
7일 또는 최근 20개 | 통합 테스트용 임시 이미지 |
dev-* |
최근 20~30개 | Dev 장애 롤백 |
rc-* |
최근 10개 또는 30일 | QA 재검증 |
prod-* |
최근 10개 이상 | 운영 롤백 |
| 현재 운영 중인 이미지 | 삭제 금지 | 장애 대응 보장 |
| 위 보존 수치는 부하 테스트 전의 초기 권장값이며, 실제 이미지 생성 빈도와 롤백 요구에 따라 조정한다. | ||
| Prod 이미지와 현재 Dev에서 사용 중인 이미지는 Lifecycle Policy로 삭제되지 않도록 별도로 보호한다. 이미지 정리 전에는 현재 배포 중인 digest와 삭제 대상이 아닌지 확인해야 한다. |
- 커밋 SHA 기준 소스 체크아웃
- 의존성 캐시 확인
- 의존성 설치
- 정적 분석
- 컴파일 또는 애플리케이션 빌드
빌드 시간을 줄이기 위해 다음을 적용한다.
- lock 파일 기반 의존성 캐시
- Docker multi-stage build
- 변경되지 않은 Docker layer 재사용
- lint, 단위 테스트, 컴파일 작업 병렬 실행
- PR마다 불필요한 전체 이미지 Push 생략
- 단위 테스트
- API·계약 테스트
- AI 응답 스키마 및 평가 테스트
- 테스트 환경 변수 기반 startup test
- health/readiness 확인
- 핵심 기능 smoke test
모든 PR에서 전체 통합 테스트를 수행하지 않는다. API, DB, AI 응답 계약이나 핵심 사용자 흐름이 변경된 경우에만 테스트 범위를 확장한다. 외부 AI API나 외부 서비스는 PR 단계에서 mock 또는 고정 fixture를 사용한다. 실제 외부 API 호출은 비용과 응답 변동성이 있으므로 Dev 배포 후, 야간 또는 릴리즈 전에 별도로 검증한다.
- 테스트 통과 후 Docker 이미지 생성
- 이미지에 커밋 SHA와 빌드 시간 메타데이터 추가
- 이미지 내부에 민감정보 포함 여부 확인
- 기본 이미지와 의존성 취약점 검사
- 이미지 실행 후 startup smoke test
이미지에는 다음과 같은 식별 정보를 기록한다.
org.opencontainers.image.source
org.opencontainers.image.revision
org.opencontainers.image.version
org.opencontainers.image.created
이미지는 가능하면 non-root 사용자로 실행하고, 운영 환경에서는 버전이 고정된 기본 이미지를 사용한다.
- ECR 인증
- 이미지 Push
- 이미지 digest 기록
- Dev 또는 Prod 배포 대상에 digest 지정
- 배포 후 smoke test
ECR 접근 권한은 CI에 장기 AWS Access Key를 저장하기보다 단기 권한을 사용하는 방식으로 관리한다. Repository 정책과 IAM을 통해 서비스별 Push·Pull 권한을 분리한다.
모여타 V1에서는 이미지 관리 정책이 다음 운영 문제를 해결해야 한다.
- 여러 파트의 이미지를 구분하면서도 하나의 서비스 릴리즈로 묶을 수 있어야 한다.
- Dev에서 검증한 결과를 Prod에서도 동일하게 재현할 수 있어야 한다.
- 여러 기능 브랜치에서 이미지가 생성되어도 수동 삭제 작업이 발생하지 않아야 한다.
- 장애 발생 시 사람이 이미지의 생성 커밋과 이전 정상 버전을 즉시 확인할 수 있어야 한다.
- 1인이 운영하더라도 별도의 이미지 관리 서버나 수동 배포 스크립트가 필요하지 않아야 한다. 따라서 모여타 V1에서는 ECR Private Repository + 커밋 SHA 기반 Immutable 이미지 + Semantic Version 릴리즈 태그 + 자동 Lifecycle Policy 조합을 사용한다.