본문 바로가기

Git

Github Actions를 이용해서 S3, CloudFront 배포 하기

문제 상황

회사에서 사용하는 프론트엔드 배포 환경은 총 4가지가 있다.

 

1. dev - 내부 개발용

2. test, stage - QA용(회사 특성상 더미 데이터를 가지고 있는 test 환경, 실제 라이브 데이터를 가지고있는 stage 두가지로 나뉜다.)

3. prod - 실제 라이브 환경

 

브랜치는 dev, test, master로 나누어져있었으며, 일반적으로 dev브랜치에서 새로운 브랜치를 따서 기능개발이 이루어지면 dev로 머지 → dev에서 test로 머지 → test, stage 환경에 배포 → QA 테스트 → QA 후 master로 머지되어 라이브로 배포된다.

 

특히나 한창 QA 돌릴 시기에는 하루에도 몇번씩 배포를 했어야했는데 가내수공업으로 스크립트를 돌리고, 빌드 후 배포가 완료될때까지 다른 작업들을 못하는 것이 너무 너무 너무 귀찮았다.

 

사실 진작에 구축해뒀더라면 훨씬 더 시간을 절약하고 귀찮음을 덜 수있었겠지만 기능 개발하느라 도저히 시간 짬이 안나서 좀 여유로워졌을 때 구축해볼 수 있었다. 이 점이 좀 아쉽다...

 

해결방법

배포 환경을 3가지로 나누고 각 배포환경별로 workflow 파일을 따로 만들었다.

 

1. dev 브랜치에 push가 일어나면 dev 환경에 배포한다.

2. test 브랜치에 push가 일어나면 test, stage환경에 배포한다.

3. master 브랜치에 push가 일어나면 production 환경에 배포한다.

 

루트 디렉토리에 .github/workflows폴더를 만들고 dev-deploy.yml, qa-deploy.yml, prod-deploy.yml파일을 각각 만들었다. qa-deploy.yml에서 test, stage 환경으로 두 번 배포하는 것을 제외하면 세 파일 모두 내용은 비슷하다.

 

아래는 dev-deploy.yml파일의 코드이다. 자세한 syntax는 github-actions docs를 보면 잘 나와있다.

name: dev-deploy-actions
on:
  push:
    branches:
      - dev
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source code
        uses: actions/checkout@v2
      - name: Setup Python for AWS CLI
        uses: actions/setup-python@v1
        with:
          python-version: '3.x'
      - name: Install AWS CLI
        run: pip3 install awscli --upgrade --user
      - 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 }}
          mask-aws-account-id: true
      - name: Install dependencies
        run: npm install
      - name: Create env file
        run: |
          touch .env
          echo FIREBASE_API_KEY=${{secrets.FIREBASE_API_KEY}} >> .env
          echo FIREBASE_PROJECT_ID=${{secrets.FIREBASE_PROJECT_ID}} >> .env
          echo FIREBASE_SENDER_ID=${{secrets.FIREBASE_SENDER_ID}} >> .env
          echo FIREBASE_APP_ID=${{secrets.FIREBASE_APP_ID}} >> .env
          echo FIREBASE_MEASUREMENT_ID=${{secrets.FIREBASE_MEASUREMENT_ID}} >> .env
          cat .env
      - name: Build
        run: npm run dev
      - name: Deploy to S3
        env:
          AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
          AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
        run: |
          aws s3 sync ./dist [[S3버킷 주소]]
      - name: Invalidate CloudFront Cache
        run: aws cloudfront create-invalidation --distribution-id ${{secrets.DEV_DISTRIBUTION_ID}} --paths "/*"

 

위의 ${{secrets.}}로 사용하는 변수는 깃허브 레파지토리의 settings-secrets 메뉴에 들어가서 env 변수들을 생성할 수 있다.
secrets에서 먼저 사용할 변수들을 생성해놓고 .yml에 파일에서 가져다가 쓰면된다.

 

하나씩 뜯어서 살펴보자.

만들고자 하는 워크플로우의 이름을 정해준다.

name: dev-deploy-actions

 

다음은 어떤 브랜치에서 어떤 경우에 해당 워크플로우를 실행시킬지 정의한다. dev 브랜치에서 push가 일어날 경우에 실행시키도록 정의하였다.

on:
  push:
    branches:
      - dev

 

jobs는 어떤 일을 실행할지이고 deploy는 내가 지정한 job의 이름이다. job1 job2 이런 식으로 정하고 싶은 이름으로 지정하면된다.

가상 환경의 OS는 ubuntu로 선택했다.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
        #...

 

그 후 steps 는 하나의 job안에서 잘게 쪼개진 단계들을 뜻한다. 한 단계씩 살펴보자.

 

1. 소스코드를 가상환경에 올린다.

steps:
      - name: Checkout source code
        uses: actions/checkout@v2

 

2. AWS CLI를 사용하기 위해 파이썬을 다운로드 받는다.

      - name: Setup Python for AWS CLI
        uses: actions/setup-python@v1
        with:
          python-version: '3.x'

 

3. AWS CLI를 install 한다.

      - name: Install AWS CLI
        run: pip3 install awscli --upgrade --user

 

4. AWS 계정 정보를 넣어준다.

      - 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 }}
          mask-aws-account-id: true

 

5. npm install로 dependency 파일들을 다운로드한다.

      - name: Install dependencies
        run: npm install

 

6. 만약 사용할 코드에서 .env파일이 필요하다면 만들어준다. 내 경우에는 서비스에서 firebase를 사용하고 있어서 관련 변수들을 사용하기 위해 .env파일을 만들어주었다.

      - name: Create env file
        run: |
          touch .env
          echo FIREBASE_API_KEY=${{secrets.FIREBASE_API_KEY}} >> .env
          echo FIREBASE_PROJECT_ID=${{secrets.FIREBASE_PROJECT_ID}} >> .env
          echo FIREBASE_SENDER_ID=${{secrets.FIREBASE_SENDER_ID}} >> .env
          echo FIREBASE_APP_ID=${{secrets.FIREBASE_APP_ID}} >> .env
          echo FIREBASE_MEASUREMENT_ID=${{secrets.FIREBASE_MEASUREMENT_ID}} >> .env
          cat .env

 

7. 소스코드를 빌드한다.

      - name: Build
        run: npm run dev

 

8. 빌드한 파일들을 S3에 올린다.

      - name: Deploy to S3
        env:
          AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
          AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
        run: |
          aws s3 sync ./dist [[S3버킷 주소]]

 

9. 새롭게 올라간 파일들을 CloudFront를 통해 바로 볼 수 있도록 invalidation 시켜준다.

- name: Invalidate CloudFront Cache
        run: aws cloudfront create-invalidation --distribution-id ${{secrets.DEV_DISTRIBUTION_ID}} --paths "/*"

 

오 이제 됐나? 👀 하고 actions에 들어가서 보니 빌드할때 크래시나는 것을 발견했다. 로컬에서는 분명히 잘됐는데...뭐지?!

 

 

보니까 컴포넌트를 파일명의 대소문자를 무시하고 import해오는 코드들에서 에러가 나는 것이었다.

예를 들면 실제 디렉토리에 존재하는 파일명은 List.jsx인데 아래 처럼 list로 불러올 경우에 에러가 발생한다.

 

❌ Error

import List from './list'

✅ Correct

import List from './List'

 

로컬에서 빌드시 문제가 없었던 이유는, Mac OS는 case-insensitive해서 영문 대소문자를 무시하고 가져오기때문에 에러가 나지 않았다. 하지만 workflow가 실행되는 우분투는 case-sensitive한 파일 시스템이므로 에러가 발생한다.

 

따라서 에러로 찍힌 곳들을 찾아가서 올바르게 파일명을 고쳐준 뒤, 대 소문자를 다르게 처리하는 파일 시스템에서도 동일하게 실행될 수있도록 웹팩 플러그인이 있을 것 같아 찾아보았는데 역쉬나 있었다.

 

case-sensitive-paths-webpack-plugin이라는 플러그인을 설치한 뒤 webpack config 파일에 추가해주었다.

테스트로 일부러 대소문자를 다르게해서 import 한 뒤 빌드해보면 아래 처럼 에러를 뱉어준다.

 

 

후후 다시 확인해보니 배포가 잘된 것을 확인할 수 있었다.

 

 

참고 링크

https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#name

https://www.npmjs.com/package/case-sensitive-paths-webpack-plugin