AWS EC2에 React+Node.js 프로젝트 배포하기

js43o·2022년 8월 25일
6

React와 Node.js로 만든 내 프로젝트를 AWS에 배포하는 과정에서 발생한 문제 상황해결 방법을 기록한 글이다.

1. 인스턴스 생성

먼저, AWS에 가입 후 원하는 지역에 인스턴스를 생성해 준다. 내 경우 아마존 프리 티어로 이용 가능한 t2.micro 유형에 Ubuntu 22.04 AMI를 선택했다. 인스턴스 생성 시 맞춘 키 페어를 잘 저장해 두자.

인스턴스를 재시작할 때마다 IP가 바뀌는 것을 막기 위해 탄력적 IP를 적용하였다.
생성이 완료되었으면 퍼블릭 IPv4 DNS를 호스트로 하여 XShell 등의 프로그램을 통해 서버에 접속한다.


2. 패키지 설치

이제 프로젝트에 필요한 여러 패키지들을 설치한다.

sudo apt update # 사용 가능한 패키지 목록 업데이트
sudo apt install nodejs npm
node -v # node.js 버전 확인

sudo npm install -g yarn # global로 설치
yarn -v # yarn 버전 확인

sudo 명령어를 일일이 붙이기 싫다면?
=> su 키워드를 통해 root 계정으로 전환하면 된다. 이때, 처음 인스턴스를 생성한 시점이라면 그 전에 sudo passwd root를 통해 root 계정의 패스워드를 먼저 설정해 주도록 하자.

⚠️ Ubuntu 22.04 MongoDB 미지원

내가 만든 프로젝트는 DB로 MongoDB를 사용하는데, Ubuntu 22.04 버전이 아직 MongoDB를 완전히 지원하지 않아서 설치가 되지 않는 문제가 있었다.

정확히는 MongoDB 설치에 필요한 libssl 1.1.1 패키지를 설치할 수 없었다.

구글링 결과 다음 글을 통해 해결할 수 있었다.

wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1-1ubuntu2.1\~18.04.20_amd64.deb	# 파일 다운로드
sudo dpkg -i libssl1.1_1.1.1-1ubuntu2.1~18.04.20_amd64.deb # 설치

libssl 1.1.1 설치가 잘 되었다면 MongoDB를 설치하자.

# public key 불러오기
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
# sources list에 MongoDB repository 추가
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list 
sudo apt update
sudo apt install -y mongodb-org

sudo systemctl enable mongod
sudo service mongod start  # 데몬 등록 및 실행

23.08.07: 아래 링크의 방법을 이용하자.
https://www.fosstechnix.com/how-to-install-mongodb-on-ubuntu-22-04-lts/


3. Nginx 설정

Nginx는 클라이언트의 요청에 따라 정적 파일을 제공하는 웹 서버이다. 또한 클라이언트와 백엔드 서버 사이에서 요청과 응답을 받아 전해주는 역할을 하기도 한다.
먼저 Nginx를 설치하고 실행한다.

sudo apt install nginx
sudo service nginx start

실행 후 브라우저에서 인스턴스의 퍼블릭 IP로 접속했을 때 이런 페이지가 보이면 성공이다.
이제 github에 업로드된 내 프로젝트를 받아온다.

sudo apt install git
git clone https://github.com/js43o/myapp.git

⚠️ 리액트 프로젝트 빌드 불가

프론트엔드 코드를 빌드하여 나온 결과물을 nginx의 지정된 폴더에 넣어주면 되는데, yarn build 명령어를 입력하면 빌드가 완료되지 않고 그대로 멈춰버리는 문제가 발생했다.

원인은 EC2 프리 티어 인스턴스의 메모리 용량이 1GB로 매우 작아서 빌드를 하기에 충분하지 않아서였다.
이 문제는 디스크 영역 일부를 가상 메모리로 전환함으로써 해결하였다.

# dd: 블록 단위로 파일을 복사하거나 변환하는 명령어 
# /dev/zero의 내용(수많은 '0')을 /swapfile로 64MB 블록 32개만큼 복사
sudo dd if=/dev/zero of=/swapfile bs=64M count=32

sudo chmod 600 /swapfile
sudo mkswap /swapfile	# /swapfile을 스왑 영역으로 지정
sudo swapon /swapfile	# 스왑 영역 활성화

# /etc/fstab: 부팅 단계에서 마운트 될 볼륨 정보들이 저장된 파일
sudo vim /etc/fstab
# ...
# 마지막 라인에 다음 문장 추가
/swapfile swap swap defaults 0 0

free 명령어를 통해 Swap 메모리 영역이 생성된 것을 확인 가능하다. 빌드도 무리없이 진행되었다.
이제 빌드 결과 디렉토리를 nginx에게 알려주면 된다. nginx 설정 파일을 만들어 보자.

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default	# 기본 설정파일 삭제

cd /etc/nginx/sites-available/
sudo vim myapp.conf
# ...
# 다음 문장 입력
server {
  listen 80;
  location / {
    root   /home/user/myapp/build;	# 빌드 결과 디렉토리
    index  index.html index.htm;	# 인덱스 파일
    try_files $uri $uri/ /index.html;
  }
}
# 작성이 끝났으면 이 파일의 심볼릭 링크를 sites-enabled 디렉토리에 생성
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/myapp.conf
sudo systemctl restart nginx # nginx 재시작

설정 파일을 수정했으면 반드시 systemctl restart nginx 명령으로 nginx를 재시작하도록 하자.

이제 퍼블릭 IP로 접속하면 기본 페이지 대신 내가 만든 프로젝트의 프론트엔드 페이지가 나타나야 하는데...

⚠️ Nginx: 500 Internal Server Error

이런 에러가 나타났다. 하라는 대로 했는데 왜 안 되지?
구글링 결과 디렉토리의 권한 문제로 nginx가 해당 파일에 접근할 수 없어서 발생하는 에러라고 한다.
리눅스의 권한 계층은 owner, group, other 3개로 구분되고, 빌드 결과물의 경로는 /home/ubuntu/myapp/frontend/build에 있는데, 해당 디렉토리의 권한이 Nginx(= other)가 실행할 수 없게 되어있는 것이 문제였다.

chmod o+x 명령을 통해 위 경로에 포함된 디렉토리마다 other의 실행(execute, 'x') 권한을 부여하였더니 원하는 대로 빌드 페이지가 나타나게 되었다.

그러나...

⚠️ 디렉토리 권한 변경 후 접속 불가

다음 날 다시 EC2 인스턴스에 접속을 하려고 하는데 키 페어가 등록되지 않았다는 오류가 뜨면서 계속 접속되지 않는 문제가 발생했다.

알고 보니 리눅스에서 home 디렉토리의 기본 유저 디렉토리(/home/ubuntu)의 권한을 변경하면 접속이 불가능해지는 현상이 일어난다는 것이었다.

AMI를 따와 새 인스턴스에 새로운 키 페어를 생성해도 접속이 되지 않았다. 결국 처음부터 다시 시작하는 수밖에 없었다.

그렇다면 nginx가 빌드 디렉토리에 접근할 수 있도록 하려면 어떻게 해야 할까?

처음엔 nginx의 설정 파일(/etc/nginx/nginx.conf)에서 첫 줄의 user를 www-data 대신 ubuntu로 변경함으로써 해결하였다.

하지만 이후 nginx에게 관리자 권한을 주는 것이 보안상 좋지 않음을 알게 되었고, 대신 프로젝트 빌드 경로를 /home/ubuntu가 아닌 /home 하위 폴더로 옮겨서 other 권한으로도 접근 가능하도록 권한을 부여하였다. (이 경우엔 EC2 접속에 영향을 끼치지 않는다)

⚠️ API 요청 거부됨

백그라운드에서 백엔드 코드를 실행하고, nginx 페이지에서 API 요청을 보내면 요청이 거부되었다는 메시지가 뜬다.
nginx 페이지의 포트는 80(http)이고, 현재 열려있는 백엔드 코드는 포트 4000에서 실행 중이기 때문에 CORS 에러가 발생하는 것이다.
이 경우 nginx 내부에서 해당 요청을 변환해 대신 보내주는 reverse-proxy 처리가 필요하다.

# /etc/nginx/sites-available/myapp.conf
...
    location /api {
        proxy_pass http://localhost:4000;
        proxy_http_version 1.1;
    }

URL이 /api로 시작하는 모든 요청을 localhost:4000으로 호스트를 바꾸어 보내주도록 설정하였다.

⚠️ 프로필 이미지가 표시되지 않음

백엔드로부터 사용자의 프로필 이미지 URL이 담겨오면 프론트엔드 페이지에서 그것을 img 태그에 담아 보여주도록 되어있다.
하지만 이미지 URL이 /img로 시작하는 것에 반해, 실제 이미지가 저장된 곳은/uploads 디렉토리이다.
이 경우 Nginx 설정 파일에서 따로 alias를 설정해주면 된다.

# /etc/nginx/sites-available/myapp.conf
...
location /img {
	alias /home/ubuntu/myapp/backend/uploads;
}

이제 Nginx는 /img/uploads로 인식하므로, 정상적으로 이미지 요청에 응답할 수 있다.


4. 프론트엔드 코드 수정

이제 로컬 환경이 아닌 배포 환경에서 코드를 실행해야 하므로, 로컬에서 쓰이던 정보를 담고 있는 파일을 배포 전용으로 수정해야 한다.

또는 process.env.NODE_ENV 값을 통해 현재 개발 환경이 'production'인지 'development'인지 확인할 수 있다. 코드 레벨에서 분기문을 통해 처음부터 현재 개발 환경에 따른 변수를 적절히 처리해줄 수 있다.

특히 프론트엔드 빌드 후 nginx 접속 시 빈 화면만 표시되는 현상이 있었는데, 이는 react-router의 컴포넌트인 BrowserRouter의 인자로 basename={process.env.PUBLIC_URL}이 포함되어 있던 게 원인이었다.
이전에 프로젝트를 github pages에 배포할 때 거쳤던 작업이었고, 지금은 필요가 없으므로 해당 인자를 제거 후 빌드하니 별 이상 없이 정상적으로 작동하였다.

카카오 로그인의 리다이렉트 페이지 경로 등 외부에서 로컬 변수를 쓰고 있는 경우도 잊지 말고 변경해주자.

⚠️ Nginx의 Nested Routing 인식 불가

Nginx를 통해 react-router SPA 페이지를 이용하려면, 사용자가 링크를 통해 페이지를 이동할 때마다 실제 해당 html 파일을 찾는 것이 아니라 index.html 내에서 URL만 바꿔줘야 한다. (그렇지 않으면 존재하지 않는 html 파일을 찾게 되므로 404 에러가 발생함)
그것이 위에서 설정한 nginx.conf 파일의 try_files 부분이다.

하지만 나의 경우, 루트 디렉토리나 /login, /record 등 1계층 경로까지는 정상적으로 동작이 되었지만, /auth/kakao/redirect처럼 2계층 이상으로 이루어진 경로에 접근하면 404 Error가 발생했다.

이 경우 명확한 해결법을 찾을 수 없어 결국 해당 경로의 이름을 /kakao로 변경하는 것으로 처리했다.


5. 완성

나의 첫 개인 프로젝트가 드디어 배포 단계까지 마무리되었다. 꽤 긴 시간이 걸린 것에 비해 조금 아쉬운 부분도 많지만, 그만큼 얻어가는 것도 많은 프로젝트였다.
특히 우여곡절이 많았던 AWS 배포 과정을 이번 글로 기록해두었으니 다음 프로젝트를 할 때에 유용하게 참고할 수 있겠다.

Reference

https://velog.io/@jjhstoday/AWS-EC2에-React-Node.js-앱-배포하기-1-AWS-EC2-instance-생성
https://www.cloudbooklet.com/how-to-install-mongodb-on-ubuntu-22-04/
https://github.com/dotnet/sdk/issues/24759
https://www.zinnunkebi.com/aws-t2-micro-swap-allocate/
https://blog.naver.com/PostView.nhn?blogId=pjt3591oo&logNo=222058472880

profile
공부용 블로그

0개의 댓글