본문 바로가기
👾 Server/☁️AWS

GitHub Actions를 활용해 React를 S3, CloudFront를 사용해 배포하기

by kukim 2022. 6. 14.

이 글은 AWS의 S3, CloudFront를 사용해 리액트 프로젝트를 배포한다. 이 과정을 Github Actions를 활용해 자동 배포를 소개하고자 한다.

잘못된 내용이 있다면 편하게 말씀해주세요 🙏🏻

 

 


들어가기에 앞서

배포 방법은 다음과 같다. React 프로젝트의 빌드 결과는 정적 리소스 파일(.html, .js, .css ...)이다. 이를 S3에 올린다. 정적 리소스 파일이기에 S3의 baseURL에 index.html를 연결시켜주면 클라이언트는 baseURL에 들어왔을 때 index.html를 받게 된다. 이때 보통 CSR(클라이언트 사이드 렌더링)로 작동하기에 클라이언트의 브라우저는 S3에 필요한 리소스(. js,. css...)를 요청한다. 마치 nginx가 정적 리소스 전달하는 것처럼 보인다. (직접 전송하지만) S3에 CloudFront의 기능을 추가하여 보다 멋지게 만들 수 있다. S3에 커스텀 도메인과 https를 지원한다. 요청마다 별도의 상태 코드 지정할 수 있고, 전 세계 AWS CDN에 정적 리소스를 보내 클라이언트들은 가까운 CDN으로 부터 더 빠르게 페이지를 받을 수 있다.

 

배포 방법은 다음과 같다. (예제 소스코드, 프로젝트에 적용한 소스코드)

0. Github Actions 기초 설명

1. S3의 버킷 생성과 정적 웹사이트 호스팅 설정

2. IAM을 활용해 Github Actions이 CLI로 AWS 접속할 사용자 생성

3. react 프로젝트가 업데이트될 때마다 빌드하고 빌드한(/build) 정적 리소스를 Github Actions를 활용해 자동으로 S3 버킷에 올린다.

4. CloudFront 생성과 설정, 연결 확인

5. CloudFront Invalidation 갱신과 Github Actions 정리

6. 아키텍처

7. 주의


0. Github Actions 기초 설명

예제 : github actions 단순 작동. yaml

name: Test Front build workflow # workflow 이름

on:  # workflow 실행 조건
  push:
    branches:
      - main # 메인 브랜치가 푸쉬된 경우


jobs: # job 설정
  test: # job id
    name: Hello test job! # job 이름
    runs-on: ubuntu-20.04 # job 가상환경
    steps:
      - name: check ls
        run: ls -al # checkout 하기전 확인
      - name: checkout Github Action # step 이름
        uses: actions/checkout@v3 # github actions 가상 환경에 해당 레포 소스 가져오기
      - name: after actions/checkout ls
        run: ls -al # checkout 후 확인
      - name: job - test echo 
        run: echo "Hello test job!"

# 생략

github actions는 저장소의. github/workflows에. yaml 파일을 인식하여 작동한다. 위 파일은. github/workflows/front-build.yaml이다. 위 github actions는 github이 CI/CD 하는 서버를 제공해준다고 생각하면 좋다. 해당. yml 파일을 github가 제공해주는 서버에서 작동한다. 

job id가 test 인 경우를 알아보자.

test job은 ubuntu-20.04의 서버에서 실행된다.

서버에서 실행할 명령어들은 steps의 -name으로 구분되어 실행된다.

 

steps의 name: check ls step 명령은 ls -al 명령어이다.  github가 제공해준 CI/CD 서버의 현재 파일을 확인한다.

name: check ls 의 ls -al의 결과

그다음 steps는 checkout gGithub Action으로 actions/checkout@v3이라는 이미 만들어진 플러그인(?, 스크립트라고 생각해도 좋다)을 사용한다. 이 기능은 서버에 CI/CD를 사용하고 있는 저장소를 CI/CD 서버에 클론 한다.

 

그다음 after actions/checkout ls 명령의 결과는 아래와 같다.

name: after actions/checkout ls의 ls -al 결과

이처럼 Github Actions를 활용해 제공되는 CI/CD 서버에서 원하는 명령, 스크립트, 다른 사람들이 올려놓은 기능들을 실행하며 다양하게 활용할 수 있다.(테스트, 빌드, 배포 등) 

job test의 결과


1. S3의 버킷 생성과 정적 웹사이트 호스팅 설정한다.

S3 버킷 생성

AWS 로그인 후 S3 - 버킷 만들기
버킷 이름, 리전 선택 (자유)
S3로 정적 웹사이트 호스팅을 위해 모든 퍼블릭 엑세스 차단을 해제한다.(접근 가능하게)
위 옵션으로 버킷을 만든다.

버킷 정책 편집

버킷에 대한 접속을 누구나 할 수 있도록 정책을 변경해줘야 한다.

권한 -> 버킷정책 -> 편집
정책을 변경해준다.

# 기존
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Principal": {},
			"Effect": "Allow",
			"Action": [],
			"Resource": []
		}
	]
}


# 변경
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Principal": "*", # 특정 사용자에 대해 권한을 제어하고 싶다면 입력한다. (전체 : * / 특정 사용자 : arn:aws:iam:AWS-account-ID:user/IAMID)			"Effect": "Allow",
            "Action": "s3:GetObject", # Actions : 버킷에 대해 어떤 작업을 허용(또는 거부)할 것인지 선택하는 옵션
            "Resource": "arn:aws:s3:::team-35-issues-tracker/*" # 접근 권한을 주고자하는 버킷을 입력한다. (입력 포맷 : arn:aws:s3:::버킷명/*)
		}
	]
}

버킷 정책 편집 완료


2. IAM을 활용해 Github Actions이 CLI로 AWS 접속할 사용자 생성한다.

IAM을 활용해 S3 권한을 가진 사용자 생성하기(CI/CD에서 CLI로 접속할 유저 id라고 생각하면 좋다.)

IAM -> 사용자 -> 사용자 추가
사용자 이름 입력(자유) / 엑세스 키 - 프로그래밍 방식 엑세스 체크 (CI/CD 서버의 CLI 환경에서 AWS 접속 할 유저 만드는 것)
AmazonS3FullAccess 체크 / (다음 태그 설정 안해도 됨)

 

사용자 만들기
사용자 추가 성공 후, 엑세스 키 ID와 비밀 엑세스 키를 저장하자. (.csv 다운로드로 가지고 있어도 된다) 비밀 엑세스키 노출 X

Github repo에 시크릿키 등록

사용자의 액세스 키, 시크릿 키를 등록하자.

이름은 상관없다. Github Actions에서 깃허브 시크릿키를 환경변수로 사용할 수 있다. (e.g. {{ secret.AWS_S3_ACCESS_KEY_ID}} 으로)


3. react 빌드한 /build 정적 리소스를 Github Actions를 활용해 자동으로 S3 버킷에 올린다.

build와 s3에 업로드하는 Github Actions

react 파일을 빌드한다. 빌드된 정적 리소스 파일을 위에서 만든 s3에 올리면 된다.

기본적으로 CI/CD 서버에는 aws CLI가 설치되어있어 aws 명령어를 사용할 수 있다.

1. aws-actions/configure-aws-credentials@v1을 사용해 aws에 접속할 사용자를 등록한다.

2. aws s3 명령어를 통해 react의 빌드 결과 build 폴더를 버킷에 올린다

# GitHub Actions CLI 환경에서 S3 사용자 권한의 aws 접속
- name: Configure AWS credentials 
  uses: aws-actions/configure-aws-credentials@v1 
  with: 
    aws-access-key-id: ${{ secrets.AWS_S3_ACCESS_KEY_ID }} 
    aws-secret-access-key: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY_ID }} 
    aws-region: ap-northeast-2


# react 빌드한 /build를 s3로 업로드
- name: Upload /build to S3
  env:
    BUCKET_NAME: ${{ secrets.AWS_S3_BUCKET_NAME}}
  run: |
    aws s3 cp --recursive --region ap-northeast-2 build s3://$BUCKET_NAME

build 폴더를 s3://$BUCKET_NAME에 업로드할 수 있다. (--recursive 옵션 : 하위 폴더 파일까지 모두)

s3에 업로드 된것을 확인할 수 있다.

 

버킷의 정적 웹사이트 호스팅 활성화하여 index.html 보내기

정적 웹 사이트 호스팅 설정

하단에 활성화되고, S3으로 정적 웹 사이트 호스팅 된 URL이 나온다.

URL 로 접속하면 S3에 react 정적 리소스 배포(?) 완료!


4. CloudFront 생성과 설정(사용자 권한 업데이트, Invalidation 갱신)

CloudFront 생성

 

CloudFront 배포 생성
원본 도메인 : 앞서 만든 S3 도메인 / OAI 사용, 기존 OAI가 없다면 새 OAI 생성

+a) OAI란?

CloudFront에서 S3 사용할 때 보통 ON을 한다. S3 버킷에 직접 접근하지 못하고 오직 CloudFront를 통해서만 파일 접근하도록 할 수 있다.  덧(22.06.15) : OAI는 웹 호스팅 때는 적용이 안된다고 한다. 

뷰어 프로토콜 정책은 선택사항이다. HTTPS만 사용하기 위해 HTTP 요청을 HTTPS로 리다이렉트하도록 설정했다.
기본값 루트 객체 : /index.html , 배포 생성

CloudFront 완료!

배포 - 일반 - 세부 정보 - 배포 도메인 이름에 나오는 CloudFront URL 접속 가능
S3 + CloudFront 배포 완료!

+a) CloudFront 추가 설정

위와 같이 설정했을 때 URL에 접속 안 되는 경우가 있다.

CloudFront가 배포가 아직 안되었는데 들어갔거나 에러 발생 시 응답 페이지 경로가 없는 경우일 수 있다.

사용자 정의 오류 응답 생성을 통해 HTTP 오류 코드 설정


5. CloudFront Invalidation 갱신과 Github Actions 정리

캐시 Invalidation(무효화) 갱신하기

CloudFront로 배포된 S3 정적 리소스들은 AWS 엣지 로케이션에 전송되어 클라이언트의 요청에 빠르게 응답할 수 있다. 엣지 로케이션의 캐시는 24시간으로 설정되어있다. 24시간이 지나면 오리진 서버(S3)의 정적 리소스를 다시 받아 캐시로 사용한다.

 

문제

react 프로젝트가 업데이트되어 새롭게 배포되었다. 오리진 S3에 올라간 정적 리소스는 최신이다. 하지만 전 세계 엣지 로케이션의 정적 리소스는 최신 리소스가 아닌 이전에 배포된 리소스이다.

 

해결

방법 1: 이를 위해 엣지로케이션의 캐시를 무효화 갱신 API를 사용하여 초기화

방법 2(참고) : CloudFront는 엣지로케이션 파일의 캐시가 없을 때 오리진에서 파일 새로 가져오는 방법을 사용해 파일 내용 바뀔 때 디렉토리 경로나 파일명 바꾸거나, Cache-Control의 캐시 유지 시간을 짧게 설정하여 운영할 수 있다. 단, 모든 파일 캐시 유지 시간을 짧게 하면 오리진에서 접속하여 새 파일 가져오는 주기가 짧아 전체 성능이 떨어질 수 있다

 

방법 1을 사용하자.

CloudFront의 캐시 Invalidation API는 아래 명령어를 사용하면 된다.

aws cloudfront create-invalidation --profile=[사용자 아이디] --distribution-id [CloudFront ID] --paths "/*"

+a 덧(22.06.24) : 전체 경로를 지정(와일드 카드 사용)에는 반드시 /* 이 아닌 따옴표 ("/*") 를 사용해야 한다. (참고) 

profile(AWS User(사용자) ID) : aws 실행할 환경에 profile 설정되어있다면 입력, (Github Actions에서 AWS 접속 명령 있다면 생략 가느으)

CloudFront ID : CloudFront → 배포 → ID 확인 가능

CloudFront ID

기존 S3에 접속하는 사용자에 CloudFront 권한 추가

CloudFrontFullAccess 추가

Invalidation API와 Github Actions에 추가

최종 Github Actions.yaml

aws cloudfront create-invalidation --distribution-id $CLOUD_FRONT_ID --paths "/*"

명령을 사용해 캐시를 무효화할 수 있다. 이때 즉각 엣지로케이션이 반응하는 것은 아니며 짧게는 10초, 프로젝트 단위에 따라 10분 넘게 걸릴 수 있다고 한다.

CloudFront -> 배포 -> CloudFront ID -> 무효화 에서 Invalidation 작동 확인할 수 있다.
쌍따옴표 없이 /* 으로 사용하면 수 많은 객체 경로가 나오지만 이는 적용되지 않는다.
"/*" 쌍따옴표로 적용해야 정상적으로 캐쉬가 무효화 된다

Github Actions 정리

기능만 하는 GIthub Actions .yaml 코드는 이곳 을 참고하면 된다. 

github repo secrets에서 아래와 같이 지정하면 된다.

AWS_CLOUDFRONT_ID = cloudfront id

AWS_S3_ACCESS_KEY_ID = S3 권한이 있는 유저 엑세스 키

AWS_S3_SECRET_ACCESS_KEY_ID = S3 권한이 있는 유저 엑세스 키

AWS_S3_BUCKET_NAME = 정적 리소스 배포할 S3 버킷 이름

+덧) (2022.06.16) 위 코드를 토대로 만든 개선 버전(node 버전 명시, npm install의 캐쉬 처리, .yaml의 성공 실패 여부 슬랙 메세지 알람 등)

 


6. 아키텍처

위 그림 : 사용자가 프론트 요청 / 아래 그림 : 빌드, 배포

 


7. 주의

위 배포 방법은 과금될 수 있다. 프리티어 횟수를 꼭 참고하자. 

S3 :  매달 5GB의 Amazon S3 스토리지(S3 Standard 스토리지 클래스), 20,000건의 GET 요청, 2,000건의 PUT, COPY, POST 또는 LIST 요청, 100GB의 데이터 송신 혜택

 

CloudFront

용량 :  Amazon CloudFront를 사용하면 매월 1TB의 데이터 전송, 10,000,000개의 HTTP 및 HTTPS 요청, 2,000,000개의 CloudFront 함수 호출

Invalidation(무료화) 요청 : 매달 추가 비용 없이 초기 1,000개의 경로에 대한 무효화 요청. 이후로 무효화 요청 경로당 0.005 USD가 청구

 


⛓ Reference

글에 대한 소스코드 - github/kukim

AWS CloudFront 공식문서

AWS 프리티어 S3 안내

AWS CloudFront 가격 안내

리액트 앱 AWS S3, CloudFront에 배포하기

AWS로 서버 없이 웹서비스 운영하기 - 리멤버 커리어

Github Actions + React + S3 시리즈(1탄) 아구몬

댓글