📁 개발

[부스트캠프/NDD] 곰터뷰는 왜 Cloudflare를 사용해서 배포하게 되었을까?


결과적으로 우리 NDD 팀은 Cloudflare를 사용해서 React 페이지를 배포하게 되었습니다. 이 글은 왜 수많은 선택지중에 Cloudflare를 사용해서 배포하게 되었는지에 대한 과정이 담겨있습니다.

어떤 방법으로 배포할까?

여러가지 배포 방법을 살펴보기 전에 먼저 React 배포 프로세스에 대해 간단하게 짚고 넘어가겠습니다.

  1. 빌드: React 애플리케이션이 번들러(Webpack, Vite 등)을 통해 빌드되어 정적 파일로 변환됩니다.
  2. 파일 업로드: 빌드된 정적 파일이 서버에 업로드됩니다. 서버에 업로드된 파일은 인터넷을 통해 접근 가능하고 클라이언트가 해당 주소로 접속 시 번들링된 정적 파일을 제공해줍니다.
  3. 사용자 동작: 클라이언트(브라우저)는 서버로부터 받아온 정적 파일을 다운로드받아 웹 페이지를 랜더링합니다.

위 과정을 거치며 정적 페이지가 배포되는데요. 여기서 주의해야 할 점은 React는 SPA구조로 되어있어 사용자가 애플리케이션의 root(/) 경로에서 /user등의 경로로 이동해도 실제로는 index.html 한 파일 내에서 처리되어야 한다는 것입니다. 때문에 이에 대한 서버 설정을 별도로 하지 않으면 /user 경로로 이동시 404페이지가 반환됩니다. 따라서 Nginx등의 웹 서버와 함께 배포해서 모든 경로 요청을 index.html로 리다이랙션 하는 로직을 구현해야 합니다. SPA 사이트를 배포할 때 리다이랙션을 처리해야 하는 이유

Docker를 사용해서 배포하기

사실 React는 정적 사이트라서 Docker를 사용해서 배포했을 때 얻을 수 있는 장점이 거의 없습니다. 그럼에도 불구하고 제가 이 선택지를 고려했던 이유는 다음과 같습니다.

위와 같은 이유들이 있었지만 우리 팀은 Docker를 선택하지 않았습니다. 그 이유는 우리가 NCP를 사용하지 않기로 결정해서 호스팅 서버를 변경할 일도 없었고, 모노레포라고 해서 꼭 한 서버에 업로드할 필요는 없었기 때문입니다. 우리 팀은 NCP 대신 AWS EC2를 사용하기로 결정했고, 클라이언트와 서버를 한 곳에 업로드하는 것은 오히려 EC2의 비용만 증가시키는 일이기 때문에 이 선택지는 기각되었습니다.

이전 프로젝트에서 Docker를 사용해서 배포를 했던 이유

아까 제가 Docker 도입을 염두했던 큰 이유중 하나는 이전에 Docker를 통해 배포했던 경험이 있었기 때문입니다. 그 때는 배포 방식에 대해 큰 고민을 하지 않고 바로 Docker를 사용했었는데요. 그 이유는 다음과 같았습니다.

전산원 리눅스 서버의 구조

배포시에 사용했던 스크립트

name: Docker CD
 
on:
  push:
    branches:
      - 'production'
    tags:
      - 'v*'
  pull_request:
    branches:
      - 'production'
 
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
 
      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18.x'
 
      - name: Make env
        run: |
          touch ./.env
          echo "${{ secrets.ENV }}" > ./.env
          echo .env
        shell: sh
 
      - name: Install dependencies
        run: yarn
 
      - name: Build Project
        run: yarn build
 
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: |
            ${{ secrets.DOCKER_USERNAME }}/home-appcenter
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
 
      - name: Login to DockerHub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
 
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
 
      - name: Deploy
        uses: appleboy/ssh-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          password: ${{ secrets.PASSWORD }}
          script: |
            docker stop inu-hompage
            docker rm inu-hompage
            docker pull ${{ secrets.DOCKER_USERNAME }}/home-appcenter:production
            docker run -d --name inu-hompage -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/home-appcenter:production
            docker image prune -f

S3와 CloundFront를 사용해서 배포하기

AWS를 사용하기로 결정한 후 S3라는 선택지가 생겼습니다! S3는 AWS에서 제공해주는 객체 스토리지 서비스입니다. React는 정적 사이트이므로 스토리지에 번들링된 파일을 올려두는 방식으로도 배포할 수 있습니다. S3를 사용해서 배포한다면 다음과 같은 장점들을 얻을 수 있습니다.

S3에 배포하는 방법

전체 시나리오

IAM 사용자 생성

  1. IAM 서비스로 이동해서 사용자 생성 버튼을 클릭합니다.

  2. IAM 사용자에게 다음 권한을 부여합니다.

    • AmazonS3FullAccess
    • CloudFrontFullAccess
  3. 사용자를 생성합니다.

  4. 액세스키를 생성하고 저장해둡니다. (다시 볼 수 없으니 꼭!! 저장해두셔야 합니다.)

S3 버킷 생성하기

  1. 버킷에 적당한 이름과 리전을 선택합니다.

  2. 퍼블릭 액세스 차단은 체크박스 해제를 해줍시다.

    • 우리는 S3를 정적 사이트 배포용으로 사용할 것이기 때문에 퍼블릭 엑세스가 허용되어 있어야 합니다.
  3. 나머지 옵션은 그대로 둔채로 버킷을 생성합니다.

  4. 생성된 버킷의 속성 탭으로 이동해서 맨 아래로 스크롤을 내려서 정적 웹 사이트 호스팅 설정에 들어갑니다.

  5. 정적 웹 사이트 호스팅 옵션을 활성화로 바꿔준 후 인덱스 문서와 오류 문서의 파일명을 index.html로 변경해줍니다.

  6. 버킷의 권한 탭으로 이동해서 버킷 정책을 편집합니다.

  7. 버킷 ARN을 복사한 후 정책 생성기로 이동합니다.

  8. 아래와 같이 정책을 설정합니다.

    • Select Type of Policy: S3정책이므로 S3 Bucket Policy를 선택합니다.
    • Principal의 * 은 모든 유저를 뜻합니다.
    • Actions는 GetObject를 선택합니다.
    • ARN에는 아까 복사했던 버킷 ARN 뒤에 /*를 붙여줍니다.
  9. Generate Policy를 눌러서 생성된 정책을 복사합니다.

  10. 생성된 정책을 추가합니다.

S3로 올리는 Github Action 파일 작성하기

name: Deploy React App to Amazon S3
 
on:
  push:
    branches:
      - dev
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
 
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18'
 
      - name: Change to FE directory
        run: cd ./FE
 
      - name: Install Dependencies
        run: yarn install
        working-directory: ./FE
 
      - name: Build
        run: yarn build
        working-directory: ./FE
 
      - 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 ./FE/dist/ s3://${{ secrets.AWS_S3_BUCKET }} --region ap-northeast-2 --delete

우리팀의 레포지토리는 아래와 같은 구조로 올라가있었기 때문에 FE 디렉터리로 이동하는 과정을 스크립트에 추가로 작성했습니다.

├── BE
├── FE
└── README.md

이 액션 파일에 필요한 환경변수는 아래와 같습니다.

CloundFront에서 커스텀 에러 응답 설정하기

위와 같은 과정을 거치고 Github Action이 수행되고 나면 아래와 같이 S3 버킷에 빌드된 파일이 올라갑니다.

하지만 index.html에 접근해보면 우리의 페이지는 보이지 않고 404에러 메시지만 나타나게 됩니다.

다른 하위 경로로 접속해보면 403 에러가 발생하게 됩니다. 왜냐하면 S3 버킷에서 퍼블릭 접근은 index.html만 허용되어 있기 때문이죠.

이러한 문제점은 커스텀 에러 응답을 설정해서 해결할 수 있습니다. 글 초반부에서 설명했듯이 SPA에서 라우터가 정상적으로 동작하기 위해서는 웹서버 등에서 리다이랙션하는 과정이 필요한데요. CloundFront에서 에러 페이지를 index.html로 커스텀하면 이 동작을 구현할 수 있습니다.

  1. CloudFront 배포 생성하기 AWS CloudFront 서비스에 들어가서 S3로 배포된 웹사이트의 도메인을 입력합니다.

  2. 웹 애플리케이션 방화벽은 비활성화 한 후 배포를 생성합니다. (방화벽 활성화하면 돈들어요!)

  3. 생성된 배포에서 사용자 정의 오류 응답 생성 버튼을 클릭합니다.

  4. 아래와 같이 404 에러에 대해 index.html로 리다이랙션 시켜주고 200 응답을 내려주는 커스텀 응답을 생성합니다.

  5. 403 에러에 대해서도 동일한 처리를 해줍니다.

  6. CloudFront의 배포 도메인으로 접속하면 페이지가 정상적으로 표시되는 것을 확인할 수 있습니다.

S3와 CloudFront를 선택하지 않은 이유

위의 과정처럼 S3와 CloudFront에 대해 학습하고 배포하는 과정도 직접 수행했지만 이 방법을 선택하지 않았습니다. 왜냐하면 버전 관리 기능이 Cloudflare에 비해 빈약했기 때문인데요. Cloudflare에서는 메인 브랜치로 PR이 머지될 때 마다 해당 버전에 대한 preview URL을 생성해주는 기능을 지원해줍니다. 이 밖에도 S3와 CloudFront에서 할 수 있는 일들의 상위 호환되는 작업을들 Cloudflare에서 수행할 수 있었기 때문에 S3와 CloudFront 대신 Cloudflare를 선택했습니다.

Cloudflare로 배포하기

우리팀은 최종적으로 Cloudflare를 사용한 배포 방식을 채택했습니다. 왜냐하면 아래와 같은 장점들이 있었기 때문입니다.

Cloudflare 배포 방법

  1. Cloudflare에서 Workers & Pages 탭에 들어갑니다.

  2. Pages 탭에서 Connect to Git 버튼을 눌러 레포지토리 저장소와 연결합니다.

  3. Connect Github를 통해 깃허브와 연동합니다.

  4. 연결하고싶은 레포지토리를 선택한 후 설정을 마칩니다.

  5. 프로젝트의 빌드 설정에 맞게 아래 값들을 설정해줍니다.

  6. Save and Deploy를 눌러주면 빌드 설정이 완료됩니다~!

마무리

Docker, S3와 CloudFront, Cloudflare를 통해 React 웹사이트를 배포해본 결과 Cloudflare를 사용해서 배포하는 방식을 채택했습니다. 지금까지 배포 업무를 진행할 때는 큰 학습 없이 "올리기만 하면 된다!" 라는 생각을 갖고 있었는데요. 이번 기회에 다양한 배포 방식에 대해 학습 해볼 수 있게 되어 유익했습니다.😊 학습한 내용에 비해 Cloudflare에서 너무 쉽게 배포를 진행할 수 있어서 허무하기도 했습니다. (로봇에게 일자리를 뺏긴 노동자가 된 기분) 앞으로 프로젝트를 진행하면서 Cloudflare의 다양한 기능에 대해 알게된다면 추가로 포스팅하겠습니다ㅎㅎ

참고 자료

AWS S3로 React 배포하기