AWS EC2에서 Nginx로 React 앱 직접 배포하기

Davin
18 min readMay 5, 2020

--

React로 만든 앱을 AWS(Amazon Web Services)의 EC2를 이용해 Nginx로 배포하는 과정을 흐름에 따라 정리해보겠습니다. 물론 React 프로젝트 배포를 위해서는 정적 웹 호스팅을 지원하는 S3 등을 이용할 수도 있고, 이는 웹 서버 컴퓨터를 별로 신경 쓰지 않아도 되는 등 장점이 있을 것입니다. 하지만 개인적으로 과거에 직접 EC2를 이용하여 필요한 것들을 설치하고 Nginx를 이용해 설정을 하는 과정에서 느낀 것들도 많았기 때문에, 이 흐름을 정리해보고자 합니다. 기본적인 것들도 설명할 부분은 한 번씩 가볍게 짚으며 넘어가도록 하겠습니다. 이 글은 이후 배포 자동화를 하는 등의 더 똑똑한 과정은 포함하고 있지 않습니다.

EC2는 Elastic Compute Cloud의 약자로, 서비스를 운영하기 위해 직접 물리적인 컴퓨터 하드웨어들에 신경(급히 메모리를 사와서 추가로 꽂는다거나 등) 쓸 필요 없이 간단하게 필요에 따라 컴퓨터 사양을 조절하고 모니터링을 할 수 있게 해주는 AWS의 대표적인 서비스입니다. 그러나 EC2 instance를 만들어서 깨끗한 상태의 Linux에서 설정을 하는 과정은 처음 해보는 사람에게는 생소하고 조금 어려운 작업일 수도 있습니다. 때문에 추가적인 처리가 거의 필요 없고 웹을 만들기 위해 많이 사용하는 React를 예로 들었을 뿐이지, 마지막 부분 외의 내용은 어떤 framework를 이용하든 어떤 서비스를 배포하기 위해서든, 날 것의 EC2에 정말 간단한 수준의 배포를 하기 위한 것이라면 공통적인 과정일 것입니다.

1. EC2 instance 만들기

EC2 Launch instance — Choose AMI

EC2에서 Launch instance를 선택해 들어오면, AMI(Amazon Machine Image)를 고르는 화면이 나타납니다. 미리 특정 OS들에 몇 가지 패키지들만 설치하여 깨끗한 상태로 Amazon이 준비해둔 것이 AMI라고 할 수 있습니다. ‘Free tier eligible’이라고 되어있는 것은 Free Tier를 사용 중인 계정(가입하고 1년)이라면, 해당 AMI는 무료로 사용할 수 있다는 것입니다. 그러나 완전히 무료라고 할 수는 없고, 특정 사양(t2.micro)의 instance만 무료로 사용 가능하며, 한 달에 750시간을 무료로 사용할 수 있습니다. 물론 한 달은 750시간보다 작지만, 여러 instance를 사용하면 그 총합은 750시간을 넘을 수 있으므로, 결국 계정당 한 달에 t2.micro 한 대를 무료로 쓸 수 있다는 셈입니다.

여기서는 대표적인 Linux 운영체제인 CentOS 기반 ‘Amazon Linux 2 AMI (HVM)’를 선택하도록 하겠습니다. 따라서 사용하는 명령어(대표적으로 yum)가 Ubuntu와는 다를 수 있습니다. CentOS는 여러 기업과 대학에서도 실제로 많이 사용하는 운영 체제입니다. Instance Type에서는 사양을 보고 직접 택하면 되는데, 지금은 Free Tier로 사용할 수 있는 t2.micro를 선택할 것입니다.

EC2 Launch instance — Add Storage

이후 바로 ‘Review and Launch’를 선택해 instance 생성 직전 단계로 넘어갈 수 있지만, ‘Configure Instance Details’를 택해 다른 상세한 설정들도 보도록 하겠습니다. ‘Configure Instance’ 단계의 내용들은 그대로 두고 넘어가서, ‘Add Storage’ 단계에서 Size를 조절할 것입니다. Free Tier는 30GB의 SSD를 무료로 사용할 수 있게 지원하므로 30GB를 사용하도록 하겠습니다. 이것도 역시 여러 instance를 사용한다면 그 실제 사용 공간들의 총합을 고려해야 할 것입니다. 다음 단계인 ‘Add Tags’에서는, 여러 instance들을 관리할 때 쉽게 각각을 구분할 수 있도록 여러 정보를 Key-Value 형식으로 저장하는 것입니다. 자유롭게 설정하면 되고, 예를 들어 ‘Name’이라는 Key를 사용해 값을 저장하면 EC2 intance 관리 화면에서 해당 Value가 바로 노출됩니다.

EC2 Launch instance — Configure Security Group

다음으로 ‘Configure Security Group’은 특히 중요한데, 내 instance에 어떤 방법으로 어디서 접근할 수 있는지를 결정하기 때문입니다. 하나의 Security Group으로 EC2 외에도 AWS 내 여러 서비스의 instance들을 포함시켜 관리할 수 있습니다. 새로운 Security Group을 생성하고, HTTP와 HTTPS로는 모든 곳(Anywhere)에서 접근할 수 있게 하겠습니다. 기본적으로 지금 배포하려는 웹이 모든 사람에 의해 모든 곳에서 사용될 수 있기를 바라기 때문입니다. 각 소통의 유형들은 기본적으로 사용하는 port가 정해져있는데, HTTP는 80, HTTPS는 443입니다. 0.0.0.0은 IPv4의 모든 IP 주소를 뜻하며, ::0은 IPv6의 모든 IP 주소를 뜻합니다.

그런데 SSH에 대해서는 ‘My IP’를 택하는 것이 좋습니다. ‘SSH’는 Secure Shell의 약자로, 네트워크를 통해 다른 컴퓨터에 접속하고 다룰 수 있게 해주는 프로토콜입니다. 여기서 Anywhere을 택한다고 하더라도 함부로 아무나 이 instance에 쉽게 들어올 수 있는 것은 아니고 이후 단계에서 발급할 private key를 가지고 있어야 하지만, 어쨌든 좋은 방식은 아닙니다. 물론 지금 ‘My IP’를 택한다면 나중에 카페 등에서 다른 WiFi로 접속했을 때에는 AWS에 와서 Security Group의 Inbound rules를 바꿔주는 등의 과정을 거쳐야할 것입니다. 마지막 단계인 ‘Review’에서는 지금까지 설정한 내용을 다시 한 번 살펴보고, ‘Launch’ 버튼을 통해 instance를 실제로 생성합니다.

그러면 key pair에 대한 창이 나타납니다. 앞서 언급한 ssh 접속 등을 위해서 필요한 것으로, 기존에 있던 key pair를 택하거나 새로운 것을 만들 수 있습니다. 새로 만드는 instance에 적용할 key pair를 현재 갖고 있지 않다면 새로 생성하고 이를 다운로드하면 됩니다. ‘pair’라고 표현한 것은 public key와 private key가 한 쌍으로 존재한다는 것입니다. public key는 지금 생성할 instance 내부에 저장되고, 다운로드 받는 것은 private key입니다. private key를 가진 사람이 ssh로 접속할 때, 양 쪽의 key를 사용하여 통신이 이뤄집니다. 때문에 private key는 함부로 공유해서는 안되며, 잃어버리지도 않게 조심해야 합니다.

2. ssh로 instance 접속하기

생성 후 Instance State가 ‘running’이 될 때까지 약간 기다리면 됩니다. 접속을 위해서 몇 가지 간단한 작업을 하면 이후 편리합니다. 우선 방금 다운로드 받은 private key를 ~/.ssh 내부로 옮깁니다. # 표시를 한 것은 설정한 Key pair의 이름입니다.

MacBookPro:~ $ mv ~/Downloads/#######.pem ~/.ssh

~/.ssh 내부에 config라는 이름의 파일을 생성하거나 열어서, 다음과 같은 내용을 입력합니다. 이 과정은 필수적인 것은 아니고, ssh XXX.XXX.XXX.XXX -i ~/.ssh/#######.pem 과 같은 방식으로 그때그때 어떤 위치의 어떤 identity_file을 사용할지 명시해서 접속할 수 있지만, 좀 더 편리하게 접속할 수 있게 하고자 설정하는 것입니다. 또한 XXX로 표시한, instance의 IP를 매번 입력하는 번거로움도 해소할 수 있습니다.

Host #######
HostName XXX.XXX.XXX.XXX
User ec2-user
IdentityFile ~/.ssh/#######.pem

config 파일에서 Host 다음 #에 해당하는 곳에, ssh로 접속할 때 이용하고자 하는 이름을 넣으면 됩니다. HostName에는 instance의 IP 주소를 입력합니다. User가 ec2-user인 이유는, Amazon Linux에서 기본으로 사용되는 user가 ec2-user라는 이름이기 때문입니다. 만약 AMI에서 Ubuntu 계열을 택했다면 user 이름이 ubuntu일 것입니다. IdentityFile에는 다운로드 받아 방금 이동시킨 private key 위치를 입력하면 됩니다. 이제 Security Group에 SSH가 등록된 IP로 연결되어 있을 때는, 내 컴퓨터 어떤 위치에서든 ssh ####### (#는 지정한 Host 이름)만 입력하면 생성한 instance로 접속할 수 있는 것입니다.

처음 접속시에는 The authenticity of host ‘XXX.XXX.XXX.XXX (XXX.XXX.XXX.XXX)’ can’t be established. 라고 하면서 연결을 계속할 것이냐는 질문이 나타날텐데, ‘yes’를 입력하면 됩니다. 그 다음에는 아마 UNPROTECTED PRIVATE KEY FILE 이라는 경고가 발생할 것입니다. 현재 사용하는 private key의 권한이 너무 개방적이라는 것으로, 좀 더 폐쇄적으로 설정해주면 됩니다. 자신(소유자)만 읽고 쓸 수 있음을 뜻하는 400으로 권한을 변경해줍니다. 그리고 다시 시도하면 정말 접속이 됩니다.

MacBookPro:~ $ chmod 400 ~/.ssh/#######.pem
MacBookPro:~ $ ssh #######
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 4 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$

3. 필요한 package 설치하고 React build하기

package들의 update를 한 번 해준 후, 필요한 것들을 설치해보겠습니다. 여기서는 Git을 통해 원격 repository에서 프로젝트 코드를 받아온다고 가정하겠습니다. 또한 Nginx를 통해 웹 연결을 설정할 것이므로 다음 명령어들을 통해 이들을 설치합니다.

$ sudo yum update
$ sudo yum install git
$ sudo yum install nginx

Nginx에 대해 Amazon Linux 1 같은 경우에는 그냥 이렇게 하면 설치가 진행되지만, Amazon Linux 2의 환경에서는 nginx is available in Amazon Linux Extra topics “nginx1.12” and “nginx1” 과 같은 말이 표시될 것입니다. 사람들이 자주 사용하는 라이브러리들을 카탈로그 느낌으로 따로 준비해놨으니 이걸 이용해서 설치하라는 뜻이니까 해당 방식으로 Nginx 1.12 버전을 설치하도록 하겠습니다. amazon-linux-extras를 입력하면 카탈로그 목록을 볼 수도 있습니다.

$ sudo amazon-linux-extras install nginx1.12

git clone을 통해 프로젝트를 받아온 후, build를 진행합니다. React로 개발하는 과정에는 일반적으로 yarn start 등을 통해 웹 브라우저에서 내용을 확인하지만, 배포를 위해서 이런 방식을 사용하는 것은 매우 좋지 않고 build를 해서 앱을 제공해야 합니다. 우선 build를 하기 앞서 yarn 자체를 설치하기 위해 다음과 같은 과정을 거칩니다. 이 과정에서 Node.js 또한 설치합니다. yarn 설치를 위해서는 yum 을 이용해서 설치할 수 있도록 하는 등 다른 다양한 방식들도 존재합니다.

$ curl -o- -L https://yarnpkg.com/install.sh | bash
$ source ~/.bashrc
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm install node

이제 yarn을 사용할 수 있으므로, 프로젝트 내부의 React 관련 상위 디렉토리(public, src 등의 디렉토리가 존재하는 곳)로 진입합니다. 프로젝트에서 사용하는 dependency들을 설치하기 위해 yarn install 후 build를 생성합니다. 이 두 과정은 이후 개발이 진행할 때마다 dependency와 코드가 변경될 것이므로, 새로운 버전을 배포할 때마다 이뤄지도록 해야합니다.

$ yarn install
$ yarn run build --prod

build가 성공적으로 진행된다면 build라는 디렉토리가 생성되어 pubilc, src 등의 디렉토리들과 함께 위치해있음을 확인할 수 있습니다. build 내부에는 index.html이라는 파일이 있는데, 이후 Nginx 설정에서 언급됩니다. React로 만들어진 웹에서의 시작점이라고 할 수 있습니다.

만약 build를 진행하는 과정이 지나치게 오래 걸리거나 실패할 수 있는데, 혹시 그렇다면 슬프게도 t2.micro의 메모리로 감당할 수 없는 규모의 앱일 가능성이 높습니다(생각보다 조금만 규모가 커져도 감당하기 어려워합니다). 정말 바람직하지 않지만(!) 지금 당장의 배포에 의의를 두기 위해서는 instance 외부의 로컬에서 build를 진행하고, build 디렉토리를 받아오는 방법도 있습니다.

4. Nginx 설정하기

https://www.nginx.com/

build가 무사히 되었다면 이제 Nginx 설정을 진행합니다. Nginx는 지금 다루고 있는 instance처럼 특정 컴퓨터 내의 데이터를 외부에 빠르게 제공하고, 외부로부터의 요청을 받아 처리할 수 있도록 하는 웹 서버입니다. Nginx와 관련된 설정 파일들은 /etc 하위에 존재하므로, sudo 권한을 이용해서 파일 생성과 수정 등을 해야 합니다. 우선 아래 명령어를 통해 해당 파일을 엽니다. 기본적으로 여러 내용이 이미 설정되어 있는 것을 확인할 수 있습니다.

$ sudo vi /etc/nginx/nginx.conf

nginx.conf 내에서 React 프로젝트의 build로 바로 이어지게 설정할 수도 있지만, 설정들을 깔끔하게 관리하기 위해서 일반적으로 선호되는 방식은, /etc/nginx 내에 sites-enabled 디렉토리를 생성하여 여기에 각 서비스의 설정을 넣고 nginx.conf가 이들을 확인하도록 하는 것입니다. 하나의 instance 내부인데도 ‘각 서비스’라고 표현한 것은, 가상 호스트(서버) 개념을 사용할 수 있기 때문입니다. 이를 통해 하나의 컴퓨터, 즉 하나의 IP 내에서 여러 웹을 배포할 수 있습니다. 사용자는 각 웹 서비스에 접속하기 위해 서로 다른 도메인(예를 들어 example.com과 another.com)을 입력해서 들어오는데, 사실 이들은 같은 IP로 통하고 Nginx가 각각의 도메인에 따라 한 컴퓨터 내의 서로 다른 곳으로 연결되도록 해주는 것입니다. nginx.conf 파일의 http { } 블럭 내부에 server { } 블럭을 볼 수 있는데, listen 행의 default_server가 그러한 각 도메인 이름이 될 수 있습니다.

지금은 앞서 말한대로 sites-enabled 디렉토리에 따로 설정을 만들어줄 것이므로, 이 server 블럭 행들은 모두 주석처리(행 앞에 #) 하도록 합니다. 그리고 server 블럭 바로 위에 아래와 같이 include /etc/nginx/sites-enabled/*.conf; 를 추가함으로써 sites-enabled 하위의 설정 파일들을 포함하도록 합니다.

...include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
#server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
# location / {
# }
# error_page 404 /404.html;
# location = /40x.html {
# }
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
#}
...

처음 Nginx를 설치했다면 sites-enabled 디렉토리가 없으므로, 이를 직접 생성해줍니다. 그런데 sites-enabled에 직접 설정 파일을 작성할 것은 아니고, 일반적인 방식은 sites-available 디렉토리에 필요한 파일들을 작성한 후 이들과 연결되는 symbolic link(symlink)를 sites-enabled에 추가하는 것입니다. 때문에 /etc/nginx 내부에 두 디렉토리를 모두 생성해줍니다. 그리고 sites-available 내에 원하는 이름으로 설정 파일을 생성해 열도록 합니다.

$ sudo mkdir /etc/nginx/sites-available
$ sudo mkdir /etc/nginx/sites-enabled
$ sudo vi /etc/nginx/sites-available/#######.conf

지금은 도메인 등록도 되어있지 않고, HTTPS를 위한 준비도 되어있지 않으므로, HTTP에 해당하는 port 80에 대해서 아주 기본 설정만 포함합니다.

server {
listen 80;
location / {
root /home/ec2-user/#######/#######/build;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}

server 블록의 location 블록은 Nginx에서 특히 많이 사용하게 되는 부분입니다. location 뒤의 /는 directive라고 하는 부분인데, IP 주소나 도메인의 뒷 부분인 URI에 대응됩니다. 예를 들어 example.com/blahblah 라는 URL이 있다면, ‘/blahblah’가 URI에 해당합니다. directive에 대한 여러 Nginx 문법이 존재하는데, 그냥 /만 쓰면 ‘/’로 시작하는 모든 URI에 해당한다는 것으로 현재는 이 instance의 IP 주소로 port 80을 통해 들어오는 모든 URL을 연결시켜주는 셈입니다. root 행에는 아까 Git을 통해 가져온 프로젝트 내부의 build 디렉토리 경로를 입력합니다.

$ sudo ln -s /etc/nginx/sites-available/#######.conf /etc/nginx/sites-enabled/#######.conf
$ sudo nginx -t

이제 위의 명령어를 통해 sites-enabled에 symlink를 생성하고, Nginx 설정을 테스트합니다.

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

위와 같은 내용이 출력되면 설정의 문법이 정상적이라는 것입니다.

$ sudo systemctl start nginx

이제 Nginx를 동작시키고, 웹 브라우저에서 instance의 IP 주소를 입력하면 build한 React 앱으로 연결되는 것을 확인할 수 있습니다.

그러나…

그런데, 위 이미지처럼 500 Internal Server Error가 발생할 수 있습니다. 사실 간단한 문제로, build까지의 경로로 접근할 때 거치는 디렉토리들에 대해 외부에서의 실행 권한이 없어서 발생하는 문제일 가능성이 높습니다. 홈 디렉토리인 /home/ec2-user의 권한을 others의 실행 권한을 포함한 711로 설정합니다.

$ chmod 711 /home/ec2-user

이후 다시 웹 브라우저로 접속해보면 제대로 동작할 것입니다.

It worked!

도메인을 직접 구입해 IP 주소에 연결하는 방법에 대해서는 이 스토리를 참고해주세요!

--

--