Gyunpang

5. Nginx + React 무중단 배포하기

시롱시롱 2024. 3. 26. 20:55

프로젝트의 중심을 FE보단 BE에 맞추고 있다 보니, React의 배포를 최대한 간단하게 해보려한다.

구상한 계획은

  1. Nginx를 통해 메인 도메인으로 들어오는 경우 React 빌드 파일을 통해 정적 배포
  2. BE배포 처럼 배포 전략은 Blue/Green으로
    1. 두 빌드 파일을 서로 최신화하고 바꾸는 방식
  3. workflow 작성 및 deploy용 쉘 스크립트 작성

이렇게 된다.

Nginx로 React 프로젝트 배포하기

간단하게 메인 페이지와 /health 경로로 들어갔을 때 나오는 인사 화면 정도만 구성한 React 프로젝트를 빌드하고

scp -i ***.pem -r 빌드폴더경로/build 인스턴스사용자명@Ipv4:/인스턴스 내 저장할 경로

위 명령어를 통해 빌드 파일 통채로 인스턴스로 옮겨준다

이제 아래처럼 nginx에서 root위치를 build 폴더로 변경하고 index.html을 배포해주면 끝인데..

server {

       # root /var/www/html;
       # index index.html index.htm index.nginx-debian.html;
        server_name xx.com www.xx.com; # managed by Certbot
        root /home/ubuntu/fe/build;
        
        index index.html;

        location / {
                try_files $uri $uri/ /index.html;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/xx.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xx.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

403 오류가 발생했다.

/var/log/nginx/error.log 를 확인해보면 파일들을 읽을 권한이 없다고 뜬다.

즉, 경로 설정과 읽으려는 시도는 정상적으로 이루어지지만 해당 경로의 파일들로 nginx가 접근하지 못하는 것이다.

/etc/nginx/nginx.conf 를 확인해보면 그 이유를 알 수 있다.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}
...

user가 ubuntu가 아닌 www-data로 되어 있는 것을 확인할 수 있다.

즉, 빌드 폴더에 www-data가 접근할 수 있게 만들거나 nginx의 user를 ubuntu 로 변경해야 이를 해결할 수 있다.

나같은 경우 ubuntu나 Root로 변경하기엔 다른 파일들에 대한 접근이 모두 가능해지는 것이니 조금 꺼려져서 기존 /home/ubuntu 아래에 있던 빌드 폴더를 /www/html 밑으로 옮겨 접근을 가능하게 만들었다.

이제 배포한 도메인에 접속하면 정상적으로 리액트 페이지가 노출된다.

Nginx 설정 변경으로 빌드 파일 스왑하기

BE 배포 때 nginx 변수를 blue,green에 따라 변경한 후 reload하는 것 처럼 이번에는 root 경로를 변수로 설정하고 이를 blue, green 배포에 따라 변수를 덮어 씌운 후 reload해서 빌드 파일을 스왑해보려 한다.

/etc/nginx/conf.d/fe-route.inc 에

set $fe_route /var/www/fe_green/build;

변수를 설정해주고

fe-route-green엔 set $fe_route /var/www/fe_green/build;

fe-route-blue엔 set $fe_route /var/www/fe_blue/build;

이렇게 적어준 후

server {
        include /etc/nginx/conf.d/fe-route.inc;
        server_name xx.shop www.xx.shop; # managed by Certbot
        root $fe_route;
        
        index index.html;

        location / {
                try_files $uri $uri/ /index.html;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/xx.shop/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xx.shop/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

이렇게 fe_route 변수를 root를 하고 그 아래의 index.html을 배포하는 방식으로 Nginx 설정을 변경한다.

Github action으로 FE 무중단 배포 완성시키기

이제, blue,green에 따라 다른 빌드 파일을 배포할 수 있는 설정을 마쳤으니

github action workflow를 만들고 실행시킬 쉘 스크립트만 작성하면 완료된다.

workflow는 BE때 작성했던 것을 바탕으로 작성했고 아래와 같다

name: Build and Deploy

on:
  push:
    branches:
      - master

jobs:
  build:
    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: Install dependencies
      run: npm install

    - name: Build React app
      run: npm run build
      
    - name: Copy build files to EC2 instance
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        source: "build"
        target: "/home/ubuntu/fe/"

    - name: Send fe_deploy.sh
      uses: appleboy/scp-action@master
      with:
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        port: 22
        source: "./fe_deploy.sh"
        target: "/home/ubuntu/"

    ## deploy fe simply (without docker container)
    - name: change fe
      uses: appleboy/ssh-action@master
      with:
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        port: 22
        script: |
          chmod 777 ./fe_deploy.sh
          ./fe_deploy.sh

단순하지만 순서를 설명하자면

matser 브랜치를 감지 → push 발생 → workflow 실행 → node 환경 설정 → 패키지 설치 → React 빌드 → EC2 인스턴스에 build 폴더 전송 → 실행할 쉘 스크립트 전송 → ssh 연결로 쉘 스크립트 실행

의 순서로 실행된다.

쉘 스크립트는 아래와 같이 작성했다.

#!/bin/bash

IS_GREEN=$(cat /etc/nginx/conf.d/fe-route.inc | grep green)

if [ -z "$IS_GREEN" ]; then

  echo "### BLUE => GREEN ###"

  sudo rm -rf /var/www/fe_green

  sudo cp -r /home/ubuntu/fe /var/www/fe_green

  sudo cp /etc/nginx/conf.d/fe-route-green /etc/nginx/conf.d/fe-route.inc

  sudo nginx -s reload

else
  echo "### GREEN => BLUE ###"

    sudo rm -rf /var/www/fe_blue

    sudo cp -r /home/ubuntu/fe /var/www/fe_blue

    sudo cp /etc/nginx/conf.d/fe-route-blue /etc/nginx/conf.d/fe-route.inc

    sudo nginx -s reload
fi

마찬가지로 BE때 작성했던 것에서 많이 단순화 시킨 버전이다.

blue일 경우 기존 green 폴더를 지우고 전송된 신규 빌드 폴더를 green으로 옮긴 후, fe-route.inc 를 green 설정으로 변경한 후 nginx를 reload 시키는 방식이다.

BE 때 했던 방식과 많이 유사해서 구축하기 상당히 편했고, 또 의외로 폴더 권한과 명령어 권한 때문에 고생을 조금 했었다 .. !

끝 !


참고

https://projectlog.tistory.com/22

https://dejavuqa.tistory.com/358

http://miconblog.com/archives/1608

https://soranghouse.tistory.com/2

https://ganbarujoy.tistory.com/113