3단계. CD(지속적 배포) 파이프라인 설계 - 100-hours-a-week/KTB4-17th-wiki GitHub Wiki
3단계 CD 지속적 배포 파이프라인 설계
1. CD 지속적 배포 파이프라인 설계
CD의 목적 : 안정성보다 기능을 빠르게 배포하고 배포된 화면을 사람이 직접 확인하는 수동 검증 방식을 사용한다.
현재 CD의 한계
- 검증을 사람이 수행하기 때문에 기능적인 오류를 발견하지 못할 수 있다.
- Docker Compose를 사용하기 때문에 AI와 backend가 의도하지 않은 이미지로 실행될 수 있다.
1-1. CD 적용 범위 및 방식 결정
운영 전 배포 승인 단계 제외 : 자동배포
V1에서는 CI/CD의 자동 검증에 의존하기보다 배포한 뒤 결과를 직접 확인하는 수동 검증 방식으로 진행한다. 이 상황에서 reviewer 승인 단계를 추가하면 배포할 때마다 팀원이 승인을 요청하고 기다려야 하므로 배포 자체가 병목이 될 수 있다.
따라서 출시 전에는 승인 단계 없이 main에 merge되는 순간 CD 파이프라인이 실행되는 자동 배포 방식을 사용하고 이 동작 방식을 팀원과 공유하여 main merge에 신중하고 한다.
Runner선택 : github hosted
- 예상 사용량 : CD는 1회에 5분 기준- 2주에 11회로 55분,CI 매일 5회씩 2주에 350분이 소요된다. (정확한 계산시간은 아니고 평균적인 수치를 참고 했다.)
- github hosted : private 저장소 기준 월 2000분 무료이다. 800분으로 생각해도 널널한 수치이다. (출처: https://docs.github.com/ko/get-started/learning-about-github/githubs-plans)
- self-hosted를 구현하기 위한 추가적인 EC2비용애 micro로 했을떄 3.4달러가 소모되지만 프리티어를 사용한다면 비용 부담은 없다.
→ 비용 차이는 없지만 EC2를 생성하고 추가적인 권한 설정, runner프로그램 세팅시간을 고려했을때 selt-hosted가 v1에서는 CD의 주목적인 빠른 배포에 오히려 부담이 된다고 판단하여 github hosted 를 사용하기로 했다.
1-2. EC2 + Docker Compose CD Pipeline 설계
1-3. 배포 전략 결정
배포 전략, 방식 : blue-green
[무중단 선택이유]
-
V1에서는 풀스택이 전체 기능의 70%를 개발해야 하므로 안정성보다 빠른 개발과 배포에 초점을 맞췄다. 안정성을 어느 정도 포기한 만큼 장애 발생 가능성도 높아진다고 판단했다.
-
운영 중 피크 타임 직전에 치명적인 에러가 발생해 코드 수정 후 CI/CD를 거쳐 배포하는 시점이 피크 타임과 겹친다고 가정하면:
- 피크 시간대 사용자: 1775명 * 45% = 798명
- 중단배포 시간 5분동안 실패하는 요청: 33.7 RPS X 300초= 10110건의 요청이 실패하고
- 장애를 동시에 겪는 접속자: 789명X(12분/120분)= 78명의 동시 접속자가 장애를 겪는다.
결론적으로 장애 가능성이 높은 서비스인 만큼 오류가 피크 시간대에 발생했을 때 사용자의 이탈을 최소화하기 위해 무중단 배포 방식을 선택했다.
[blue green선택이유]
사람-사람과의 1:1 채팅은 연결을 유지되어야 하는 실시간 서비스이다. rolling update를 하면 v1과 v2가 공존하는 동안 서로 다른 버전에서 채팅에 접속하면 세션이 공유되지 않아 메시지가 전달되지 않는다. 이걸 해결하려면 redis의 활용이 필요한데 현재 v1에서 사용하지 않았다. 따라서 트래픽을 한쪽으로만 보내는blue-green방식을 선택했다.
예상하는 blue-green의 채팅 연결 과정
- Green 배포 , health check통과후
- nignx로 새 WebSocket 연결은 모두 Green으로 전달함
- Blue가 기존 WebSocket에 새로운 연결은 받지 않고 기존 연결은 시간 설정하고 기다림(10. 시간이 지나거나 더이상 연결이 없을경우 blue종료함.
- 시간이 지나 종료된 연결의 경우 재연결 요청을 보내 Green으로 연결함
1-5.배포 검증 및 Rollback 설계
배포검증 : health check
- api 테스트, smoke테스트를 진행하게 되면 안정성은 높아지지만 고려해야할 부분이 많아지고 배포 과정에서 소요되는 시간이 늘어나 도입하지 않았습니다.
Rollback : 수동
- V1에서는 빠른 배포 주기를 우선으로 고려했다. API와 Smoke test를 통한 자동 검증 대신 배포 후 개발자가 주요 기능을 직접 검수하는 방식을 선택했다.따라서 배포 후 기능 오류가 확인되면 롤백 스크립트를 실행하여 직전 정상 SHA 이미지로 수동 롤백한다. 자동화된 기능 검증이 없는 상황에서 모든 오류를 자동 롤백 조건으로 설정하면 불필요한 롤백이 발생할 수 있으므로, 실제 사용자 기능과 영향 범위를 확인한 뒤 롤백 여부를 결정한다.
1-6.배포 보안 설계
시크릿 관리,access key,서버 ssh 의존성을 줄일 방법
-
secret : 현재는 env파일을 EC2안에서 수동으로 관리하기 떄문에 github action을 사용하면 git에 .env파일이 없어서 에러 발생한다. 현재 github actions를 사용하고 있기 때문에 추가적인 관리 포인트를 없애려고 github secret을 사용했다.
-
OIDC: OIDC를 사용하면 따로 access key나 비밀번호를 발급안하고도 github action가 aws에 접근가능하다. access key의 단점은 삭제하기 전까지 만료가 없어 한번 탈취당하면 계정이 위험해진다는 단점이 있는데 OIDC는 저장되는 값이 없이 1회성으로 매번 시간이 지나면 만료된다. (github→aws)
-
SSM : ssh키 보관 하지 않고 EC2의 SSM이 아웃 바운드 열어둬서 접근할 수 있게 해준다. github action이 OIDC로 인증하고 들어오면 EC2로 연결할 수 있게 해주는 건 SSM이다. (aws→EC2)
CD.yml
name: CD
on:
push:
branches: ["main"]
permissions:
contents: read
id-token: write
jobs:
# Frontend Deploy
frontend_deploy:
runs-on: ubuntu-latest
steps:
- name: Check source
uses: actions/checkout@v6
- name: AWS OIDC Authentication
uses:
with:
- name: Frontend Build
run:
# npm install
# npm run build
- name: Upload to S3
run:
# 빌드 결과물을 S3에 업로드
# aws s3 sync ...
- name: CloudFront Cache Invalidation
run:
# 새 정적 파일이 바로 반영되도록
# CloudFront 캐시 무효화
# Backend,AI Image Build
build_image:
runs-on: ubuntu-latest
steps:
- name: Check source
uses: actions/checkout@v6
- name: AWS OIDC Authentication
uses:
with:
- name: Login to ECR
run:
- name: Docker Image Build
run:
- name: ECR Push
run:
# Backend, AI Blue-Green
deploy:
needs: build_image
runs-on: ubuntu-latest
steps:
- name: AWS OIDC Authentication
uses:
with:
- name: Deploy with SSM
run: |
# SSM으로 EC2에 배포 명령 전달
# 현재 활성 컨테이너 확인
# blue / green 중 신규 배포 대상 결정
# ECR 최신 이미지 Pull
docker compose pull
# 비활성 상태의 신규 컨테이너 실행
docker compose up -d <new-color-service>
- name: Health Check
id: health_check
run: |
# 신규 컨테이너 Health Check
- name: Rollback
if: failure()
run: |
# 신규 컨테이너 종료 및 제거
docker compose stop <new-color-service>
docker compose rm -f <new-color-service>
# 기존 컨테이너 유지
- name: Switch Traffic
if: success()
run: |
# reverse proxy 대상 전환
# blue -> green
# 또는
# green -> blue
- name: Connection Drain
if: success()
run: |
# 기존 요청 / 채팅 연결 처리를 위해 대기
sleep 600
- name: Stop Old Container
if: success()
run: |
# 기존 컨테이너 종료 및 제거
docker compose stop <old-color-service>
docker compose rm -f <old-color-service>