배포 자동화 개념도

개발자라면 누구나 반복적인 배포 작업에서 벗어나고 싶어합니다. 코드를 작성하고 테스트한 후 매번 수동으로 빌드하고 배포하는 과정은 지루하고 시간 소모적입니다. 특히 팀 단위로 개발할 때는 배포 실수로 인한 문제가 발생할 수도 있죠.

이 글에서는 GitHub Actions를 활용하여 프론트엔드 애플리케이션을 AWS S3에 자동으로 배포하고, CloudFront CDN의 캐시를 무효화하는 배포 파이프라인을 구축하는 방법을 상세히 알아보겠습니다.

자동화 배포의 필요성

프론트엔드 애플리케이션을 배포할 때 다음과 같은 과정을 반복하게 됩니다:

  1. 코드 변경
  2. 의존성 설치
  3. 빌드
  4. AWS S3에 파일 업로드
  5. CloudFront 캐시 무효화

이 과정을 매번 수동으로 진행한다면 많은 시간이 낭비되고, human error가 발생할 가능성도 높아집니다. GitHub Actions를 사용하면 이 모든 과정을 자동화할 수 있으며, 코드를 Push하거나 PR이 Merge될 때마다 배포가 자동으로 이루어지게 할 수 있습니다.

사전 준비

자동화 배포 파이프라인을 구축하기 위해 필요한 것들은 다음과 같습니다:

  1. GitHub 계정과 리포지토리: 배포할 코드가 저장된 GitHub 리포지토리
  2. AWS 계정: S3와 CloudFront 서비스를 사용하기 위한 계정
  3. 배포용 S3 버킷: 웹사이트 호스팅으로 설정된 S3 버킷
  4. CloudFront 배포: S3 버킷에 연결된 CloudFront 배포 설정
  5. IAM 사용자: GitHub Actions가 AWS 리소스에 접근하기 위한 IAM 사용자

AWS IAM 설정

GitHub Actions가 AWS 리소스에 접근하기 위해서는 적절한 권한을 가진 IAM 사용자가 필요합니다.

  1. AWS 콘솔에서 IAM 서비스로 이동합니다.
  2. '사용자 추가'를 클릭하여 새 사용자를 생성합니다 (예: github-actions).
  3. 프로그래밍 방식 액세스 (Access key - Programmatic access)를 선택합니다.
  4. 다음과 같은 권한 정책을 연결합니다:
    • AmazonS3FullAccess: S3 버킷 접근 및 파일 업로드 권한
    • CloudFrontFullAccess: CloudFront 배포 관리 및 캐시 무효화 권한

참고: 보안을 강화하려면 전체 액세스 대신 특정 S3 버킷과 CloudFront 배포에만 제한된 커스텀 정책을 생성하는 것이 좋습니다.

사용자 생성이 완료되면 액세스 키 ID와 비밀 액세스 키가 제공됩니다. 이 정보는 GitHub Secret에 저장해야 합니다.

S3 버킷 설정

배포 대상이 되는 S3 버킷에서 다음 설정이 필요합니다:

  1. 정적 웹사이트 호스팅 활성화
  2. 객체 소유권 설정:
    • S3 > 버킷 선택 > 권한 > 객체 소유권
    • ACL 활성화 & 객체 소유권을 "버킷 소유자 선호"로 설정

이 설정은 GitHub Actions가 --acl public-read 옵션을 사용하여 파일을 업로드할 때 필요합니다.

GitHub Secrets 설정

GitHub Actions 워크플로우에서 AWS 계정 정보를 안전하게 사용하기 위해 GitHub Secrets를 설정합니다:

  1. GitHub 리포지토리 페이지로 이동합니다.
  2. Settings > Secrets > Actions로 이동합니다.
  3. 다음 비밀을 추가합니다:
    • AWS_ACCESS_KEY_ID: IAM 사용자의 액세스 키 ID
    • AWS_SECRET_ACCESS_KEY: IAM 사용자의 비밀 액세스 키
    • AWS_REGION: AWS 리전 (예: ap-northeast-2)
    • PROD_S3_BUCKET_NAME: 배포 대상 S3 버킷 이름
    • AWS_CLOUDFRONT_DISTRIBUTION_ID: CloudFront 배포 ID

GitHub Actions 워크플로우 작성

먼저 프로젝트 루트 디렉토리에 .github/workflows 폴더를 생성하고, 그 안에 워크플로우 파일(예: deploy.yml)을 작성합니다.

기본적인 워크플로우 파일은 다음과 같습니다:

name: CI dev
on:
  workflow_dispatch:  # 수동 트리거 가능
  # 필요에 따라 push나 pull request 트리거 추가 가능
  # push:
  #   branches: [ main ]

jobs:
  build-dev:
    name: Build Image
    runs-on: ubuntu-20.04
    strategy:
      matrix:
        node-version: [16.x]
    
    steps:
      - name: Check out branch
        uses: actions/checkout@v2
      
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: node_modules
          key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-
      
      - name: Install Dependencies
        run: npm install
      
      - name: Build
        run: npm run build
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      
      - name: Deploy to S3
        run: |
          aws s3 sync ./dist s3://${{ secrets.PROD_S3_BUCKET_NAME }} --acl public-read --delete
      
      - name: Invalidate cache CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

전체 워크플로우 설명

작성한 GitHub Actions 워크플로우는 다음과 같은 단계로 진행됩니다:

  1. 체크아웃: 최신 코드를 가져옵니다.
  2. 노드 모듈 캐싱: 빌드 시간을 단축하기 위해 node_modules를 캐싱합니다.
  3. 의존성 설치: npm install로 필요한 패키지를 설치합니다.
  4. 빌드: npm run build로 프로젝트를 빌드합니다.
  5. AWS 인증 설정: AWS 자격 증명을 설정합니다.
  6. S3 배포: 빌드된 파일을 S3 버킷에 동기화합니다.
    • --acl public-read: 업로드된 파일을 공개적으로 읽을 수 있게 설정
    • --delete: S3 버킷에 있지만 로컬에 없는 파일 삭제
  7. CloudFront 캐시 무효화: 배포된 파일의 캐시를 무효화하여 최신 버전이 사용자에게 제공되도록 합니다.

문제 해결 및 팁

1. S3 업로드 권한 문제

An error occurred (AccessDenied) when calling the PutObject operation

위와 같은 오류가 발생하면 다음을 확인하세요:

  • IAM 사용자에 필요한 권한이 있는지 확인
  • S3 버킷의 객체 소유권 설정이 올바른지 확인
  • 버킷 정책에서 해당 IAM 사용자의 액세스를 허용하는지 확인

2. 빌드 출력 경로 조정

워크플로우 파일에서 S3 동기화 명령어의 소스 경로(./dist)가 프로젝트의 빌드 출력 디렉토리와 일치하는지 확인하세요. React는 보통 build, Vue.js는 dist, Next.js는 .next 등 프레임워크별로 다른 출력 경로를 사용합니다.

3. 배포 최적화

대규모 프로젝트에서는 전체 파일을 매번 동기화하는 대신 변경된 파일만 업로드하는 전략을 고려할 수 있습니다. 하지만 --delete 옵션을 사용하여 전체 동기화를 유지하는 것이 보통 더 안전합니다.

4. 비용 고려

CloudFront 캐시 무효화는 월별 특정 횟수 이상 사용 시 비용이 발생할 수 있습니다. 개발 중 빈번한 배포가 필요한 경우, 비용 절감을 위해 특정 경로만 무효화하는 방법을 고려하세요.

마무리

GitHub Actions를 활용한 AWS S3 및 CloudFront 배포 자동화 파이프라인 구축 방법에 대해 알아보았습니다. 이 자동화 프로세스를 통해 개발자는 반복적인 배포 작업에서 벗어나 더 중요한 코드 개발에 집중할 수 있습니다.

더 복잡한 시나리오에서는 다음과 같은 추가 기능을 고려해볼 수 있습니다:

  • 환경별(개발, 스테이징, 프로덕션) 배포 설정
  • 배포 전 자동 테스트 실행
  • Slack 또는 이메일을 통한 배포 알림
  • 배포 롤백 전략

CI/CD 파이프라인은 개발 생산성을 크게 향상시키는 중요한 도구입니다. 한 번 설정해두면 배포 과정의 일관성과 안정성이 크게 향상되므로, 팀 프로젝트에서는 반드시 고려해야 할 요소입니다.


참고 자료: