본문 바로가기
FE & BE

10. AWS, gunicorn, nginx를 이용한 백엔드 배포 [MyHomepage: 개인 홈페이지 만들기]

by zeroiron0 2022. 11. 20.

안녕하세요! 개발자 최영철입니다.

 

목차

1. 도메인 네임서버란?

2. Route 53 호스팅 영역 생성 & 네임 서버 연결

3. EC2 인스턴스 생성

4. ACM을 통한 인증서 발급

5. 로드밸런서 & 타겟 그룹

6. RDS 인스턴스 생성

7. EC2 Django 프로젝트 설치

8. gunicorn

9. nginx

10. Django 설정

11. 마무리

 

이번 포스트가 개인 홈페이지 만들기 프로젝트의 마지막으로 구매한 도메인을 AWS와 연결하고 AWS 서비스를 이용해 HTTPS 백엔드 서버를 배포하는 이야기를 해드리겠습니다!

백엔드 작동 흐름

 

1. 도메인 네임서버란?

네임서버란 도메인을 등록된 IP로 바꿔주고 IP를 등록된 도메인으로 바꿔주는 서버입니다.  

 예를 들어 example.com 이라는 사이트가 있고 네임서버에 example.com이 127.0.0.1로 바뀌도록 설정되어있다면 네임서버를 통해 127.0.0.1을 접근할 수 있습니다. 반대 방향도 마찬가지 입니다.

 저는 가비아를 통해 도메인을 구매했기때문에 가비아를 예제로 설명하겠습니다.

 위의 그림과 같이 가비아에서 자신이 소유한 도메인 관리 페이지를 들어가면 네임 서버를 설정할 수 있습니다. 이를 기억하고 AWS Route 53를 살펴봅시다

 

2. Route 53 호스팅 영역 생성 & 네임 서버 연결

 AWs Route 53을 접속한 뒤 호스팅 영역을 클릭하면 다음과 같은 화면이 나타납니다. 이 곳에서 AWS 서비스에서 사용할 도메인을 등록할 수 있습니다. 도메인을 등록하기 위해 호스팅 영역 생성을 클릭하면

  위와 같은 화면이 나타납니다. 도메인 이름에 자신이 구매하고 사용한 도메인을 적으면 됩니다.

유형은 해당 프로젝트의 경우 Github Pages ↔ AWS 백엔드 서버를 통해 교신이 이뤄지므로 퍼블릭 호스팅 영역으로 생성했습니다. 이렇게 호스팅 영역을 생성하면 영역이 생기게 되고 그것을 클릭하면

  다음과 같이 자동으로 생성된 레코드가 있는데 "유형"의 NS와 SOA는 무슨 뜻일까요? 이는 DNS의 Record Type를 의미합니다. 아래 표

SOA (State of Authority) 해당 도메인의 관리자 정보, 타이머, 권한 등 전체적인 설정에 대한 레코드, 이 레코드가 없으면 도메인은 사용할 수 없습니다.
NS (Name Server) 해당 도메인이 사용하는 네임 서버에 대한 레코드, 이 레코드를 통해 도메인 주소와 IP 주소 사이에 상호 라우팅이 가능합니다.
CNAME (Canonical Name) 별칭 레코드로 직접 IP 주소와 연결할 수 없고 이미 존재하는 A에 대해 별칭으로 연결할 수 있게 합니다. 예를 들어 A 레코드로 설정된 example.com이 있으면 원하는 이름.example.com을 CNAME으로 설정하고 A와 연결함으로써 A 레코드에 적힌 주소로 접속할 수 있습니다.
A (Host) 도메인을 실제 IPv4로 라우팅되도록 만들어주는 레코드입니다.

이외에도 SRV, TXT, MX 등 다양한 Record Type이 있으니 https://0433.tistory.com/13 글을 참조하시면 좋을 것 같습니다.

가비아에서 네임 서버를 AWS에서 제공한 네임 서버로 바꿔줘야 해당 도메인을 AWS용으로 사용할 수 있습니다.

NS유형에 적혀있는 4개의 레코드 값을 가비아에서 설정합니다. 저장한 뒤 변경 실패가 뜨지 않는다면 성공적으로 적용된 것입니다.

3. EC2 인스턴스 생성

 백엔드 서버가 돌아갈 가상 리눅스 환경을 EC2를 이용해 만들 수 있습니다. EC2로 들어가서 인스턴스 시작을 클릭해줍니다.

 이름에는 자신이 원하는 인스턴스에 이름을 적으면 됩니다. 그리고 운영체제를 선택해야하는데 저는 익숙한 ubuntu를 선택했습니다. 다음에 나오는 인스턴스 유형은 원하시는 유형을 선택하시면 됩니다.\

 키 페어는 SSH를 통해 원격으로 서버와 연결할 때 사용하면 별다른 비밀번호 없이 연결할 수 있게 만들어줍니다. 새 키 페어 생성을 눌러 원하는 알고리즘으로 키 페어를 생성합시다. 그리고 생성한 키 페어를 선택해줍니다.

다음으로 네트워크 설정을 해야합니다. SSH 트래픽 허용의 경우 자신의 컴퓨터 IP를 통해서만 접속할 수 있도록 설정하면 됩니다. 해당 EC2를 백엔드 서버로 이용할 것이기 때문에 HTTPS 트래픽 허용을 체크해줍니다. 이제 인스턴스를 생성해줍시다.

제대로 인스턴스가 생성된 것을 볼 수 있습니다.

4. ACM을 통한 인증서 발급

보안을 위해 HTTPS를 적용하려면 인증서가 필요합니다. AWS에서는 자체적으로 인증서를 발급할 수 있는 서비스가 있는데 이를 이용합니다.

AWS에서 ACM으로 들어가서 "요청" 버튼을 클릭합니다.

위의 사진에서 퍼블릭 인증서와 프라이빗 인증서가 있는데, 퍼블릭 인증서의 경우 기본적으로 미리 브라우저에 유명하고 신뢰할 수 있는 CA (인증 기관)의 인증서가 담겨 있는데 이를 이용해서 인증서의 유효성을 검사하는 원리입니다.

퍼블릭 인증서 요청을 선택하고 다음을 클릭합니다.

인증서에 등록할 도메인 이름을 설정합니다. 여기에서 주의할 점은 별칭.example.com과 example.com은 다른 도메인이므로 둘 다 인증을 받기 위해 "*.도메인 주소"와 "도메인 주소" 두 개 모두 등록해줍니다. *를 사용할 경우 앞의 별칭이 무엇이든지 인증서 유효성 검사를 통과할 수 있습니다.

인증서를 발급 받기 전에 해당 도메인의 소유권을 확인하기 위해 검증 과정을 거칩니다. DNS 검증의 경우 도메인 소유자가 DNS에 AWS에서 요구한 레코드 타입, 레코드 이름, 레코드 값을 설정하면 AWS가 해당 DNS에서 그 레코드를 확인하고 소유자를 확인하는 방식입니다. 이메일 검증의 경우, DNS에 등록된 소유자의 이메일에 검증 메일을 발신해서 검증하는 방식입니다. 가비아는 DNS 레코드 타입을 설정할 수 있으므로 "DNS 검증" 방식을 이용합니다. 그리고 인증서를 요청해줍니다.

위의 사진은 이미 검증을 마치고 발급된 사진이기때문에 상태에 발급됨이 써있습니다. 처음 요청한다면 검증 보류 중이나 비슷한 말이 써있습니다. 도메인의 검증을 받기 위해 아래 "유형", "CNAME 이름", "CNAME 값"을 사용합니다.

가비아에서 왼쪽 탭의 DNS 정보를 누르고 위의 사진과 같이 나오면 DNS 관리를 클릭해 관리툴에 들어갑니다.

DNS 설정으로 들어갑니다. 그러면 오른쪽 사진처럼 타입을 선택하고, 호스트, 값을 적는 칸이 있습니다. 각각의 칸에 AWS에서 제공한 값을 넣습니다. 호스트에 CNAME 이름을 넣을 때 주의할 점은 AWS의 경우 도메인도 포함시켜서 제공하므로 호스트에 넣을 때는 ".도메인"을 삭제해주셔야 합니다. 성공적으로 저장하면 자동으로 AWS가 이를 인식해 인증서를 발급하고 상태가 "발급됨"으로 변경됩니다. 이제 생성한 발급서를 이용해 HTTP 요청을 HTTPS 요청으로 바꾸고 다룰 수 있도록 로드밸런서를 생성합니다.

5. 로드밸런서 & 타겟 그룹

다시 EC2로 들어와 왼쪽 카테고리에서 로드밸런스를 선택하고 로드밸런서 생성을 클릭합니다.

프론트엔드의 HTTP, HTTPS 요청만을 처리할 예정이므로 Application Load Balancer를 선택해줍니다.
Basic Configuration에서 자신이 원하는 로드밸런서 이름을 선택하고 Network mapping으로 내려갑니다.

아래 Mappings의 경우 적어도 2개를 선택해야하는데 하나는 EC2, S3, RDS 인스턴스가 존재하는 곳으로 선택해줍니다.

다음 Security Group의 경우 EC2를 생성할 때 사용한 보안 그룹을 선택해줍니다.

포워딩할 타겟 그룹이 없으므로 Create target group을 클릭합니다. 오른쪽 화면이 뜨면 Instances를 선택하고 원하는 타겟 이름을 설정한 뒤 Next를 누릅니다.

생성했던 EC2 인스턴스가 있을텐데 선택하고 Include as pending below를 누릅니다. 그 다음 Create new target을 누릅니다. 타겟 그룹을 생성했으면 다시 로드밸런서를 생성하던 화면으로 돌아와 생성한 타겟 그룹을 HTTP와 HTTPS에 연결해줍니다.

리스너로 HTTPS로 추가하는 순간 아래에 해당 사진처럼 Secure listener settings가 생길텐데 From ACM을 선택하고 ACM로 생성했던 인증서를 선택하면 됩니다.

마지막으로 Create load balancer를 누르면 위와 같이 로드 밸런서가 생성됩니다. 그런데 로드 밸런서를 생성할 때 HTTP 요청이 인스턴스로 연결되도록 하는 default action이므로 로드 밸런서를 선택하고 리스너에 들어가 HTTP: 80의 리스너를 HTTPS로 리다이렉션 하도록 만들어줍니다. 이렇게 HTTPS 요청만을 전달하는 로드 밸런서가 완성됐습니다.

이 기능을 사용하려면 프론트엔드의 요청이 로드 밸런서에게 전달되어야 하므로 Route 53에 다시 돌아가 생성했던 호스팅 영역에 레코드를 추가해야합니다. 레코드 이름에 원하는 이름 (ex. api, auth 등)을 넣고 레코드 유형을 A로 선택합니다. 그리고 별칭 버튼을 눌러 로드 밸런서를 선택할 수 있게 만듭니다. 이제 트래픽 라우팅 대상에서 Application/Classic Load Balancer / 로드 밸런서가 존재하는 리전 / 만든 로드 밸런서와 같이 선택한 후 래코드 생성을 하면 원하는 별칭.도메인으로 로드 밸런서에 요청을 보낼 수 있습니다.

 

 이렇게 HTTP 요청을 HTTPS 요청으로 바꾸고, HTTPS 요청을 인증서 문제 없이 전달할 수 있는 환경이 만들어졌습니다. 이제 방명록, 프로젝트, 유저를 저장할 DB를 RDS로 만들어보겠습니다.

6. RDS 인스턴스 생성

 RDS는 관계형 데이터베이스를 클라우드에서 쉽게 확장, 관리 및 운영할 수 있는 서비스입니다. 백엔드 서버의 DB로 사용될 MySQL DB 인스턴스를 RDS로 생성해보겠습니다.

MySQL을 선택한 뒤, 내려가서 원하는 템플릿을 선택하고 나면 설정 화면이 나옵니다. 식별자에는 원하는 이름을 적습니다. 자격 증명 설정의 경우 MySQL에서 사용할 이름과 암호를 뜻합니다. 

 연결 탭이 나올 때까지는 전부 기본으로 설정합니다. 연결에서는 생성한 EC2 인스턴스를 연결하기 위해 EC2 컴퓨팅 리소스 연결을 선택합니다. 이렇게 설정하면 EC2 인스턴스의 가상 환경에서만 해당 DB를 접속할 수 있으므로 보안성이 강화됩니다.

 데이터베이스 인증의 경우 Django에서 MySQL를 접속할 때 my_settings.py에 저장된 호스트와 암호를 사용하므로 암호 인증으로 설정합니다. 이제 데이터베이스 생성을 클릭하면 DB 인스턴스가 생성됩니다.

7. EC2 Django 프로젝트 설치

ssh -i {키 파일 위치} {인스턴스 이름@인스턴스 퍼블릭 IP나 DNS}

//예시
ssh -i /path/key.pem instance@127.0.0.1

EC2를 원격으로 접속할 때는 SSH를 이용합니다. EC2 인스턴스를 생성할 때 만든 키 페어를 이용해 위와 같이 사용하면 EC2에 원격 접속할 수 있습니다.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git
sudo apt-get install python3-pip
sudo apt-get install python3-venv

EC2에 접속하면 먼저 위의 명령어를 입력해 패키지를 업데이트 하고 git과 python의 pip, venv를 설치합니다.

python3 -m venv 가상환경 이름
git clone 배포용 레퍼지토리

원하는 위치로 이동해 venv로 가상환경을 만들어주고 git clone으로 배포할 프로젝트를 다운로드 해줍니다.

source 가상환경 위치/bin/activate

//Django 프로젝트 폴더로 이동한 뒤
pip install -r requirements.txt

source를 통해 가상환경을 활성화 시킨 후 다운받았던 배포용 프로젝트 폴더에 들어가서 pip install -r requirements.txt로 미리 만들어준 필요한 패키지를 다운받습니다. requirements.txt는 배포용 레퍼지토리에 업로드하기 전에 "pip freeze > requirements.txt" 명령어로 생성할 수 있습니다.

 Django의 모델들을 마이그레이션 하기 전에 settings.py에 저장한 이름의 데이터베이스를 생성하기 위해 RDS 인스턴스에 접근해야 합니다.

sudo apt-get install mysql-server

mysql -h RDS 엔드포인트 주소 -u RDS 사용자 이름 -p

//암호 입력 후 MySQL 접속한 뒤
create database 생성할 DB 이름;
quit;

mysql 명령어를 통해 RDS에 접근하기 위해 mysql-server를 설치합니다. 그 다음 위에 적은 mysql 명령어를 사용하고 암호를 입력하면 RDS에 접속할 수 있습니다. 사용할 데이터베이스를 settings.py에 적었던 이름으로 생성하고 quit으로 나갑니다.

python manage.py makemigrations

python manage.py migrate

이제 Django 명령어를 이용해 마이그레이션 파일을 생성하고 마이그레이션 해줍니다. 이때 당연히 settings.py나 my_settings.py에 담겨있는 데이터베이스 설정값은 RDS에서 얻은 값들로 변경해줘야합니다. 이렇게 기본적인 Django 프로젝트 설치가 끝났으므로 gunicorn과 nginx를 이용해 배포해봅시다.

8. gunicorn

만약 Django로 프로젝트를 개발했다면 python manage.py runserver로 서버를 열어서 테스트를 해봤을 것입니다. 하지만 이는 배포 단계에 들어가서는 하면 안되는데, 보안과 성능을 보장할 수 없기 때문입니다.

 그래서 파이썬 어플리케이션을 배포할 때는 WSGI를 사용하는 데 그 중 하나가 gunicorn입니다. WSGI란 웹 서버와 파이썬 어플리케이션 사이에서 요청을 어플리케이션에 보내주고, 어플리케이션의 응답을 웹 서버로 보내주는 역할을 합니다.

 nginx는 웹서버로 해당 서버에 전달된 여러 요청을 하나의 프로세스로 비동기 처리하기 때문에 리소스를 적게 먹고 가벼운 장점이 있습니다. 이 두개를 사용해서 백엔드 서버를 배포해보겠습니다.

pip install gunicorn

gunicorn --bind 0.0.0.0:8000 프로젝트 이름.wsgi:application

 gunicorn의 경우 파이썬의 패키지이기 때문에 가상 환경이 활성화 된 상태에서 첫번째 명령어를 생성합니다. gunicorn이 설치되면 두 번째 명령어로 gunicorn이 제대로 작동하는지 확인할 수 있습니다. 본 프로젝트에서는 EC2 인스턴스의 인바운드 규칙에 80포트를 설정하지 않았지만 80포트를 허용으로 설정한다면 브라우저로 "EC2 인스턴스 퍼블릭 IP:8000"에 접속하면 작동하는 모습을 확인할 수 있습니다. 가상 환경 자체에서는 curl 명령어를 사용하면 작동하는지 확인할 수 있습니다.

 작동을 확인하면 이제부터 gunicorn을 시스템 서비스에 등록해 백엔드에서 동작하고 인스턴스가 리부팅되어도 자동으로 실행되도록 하겠습니다.

sudo vi /etc/systemd/system/gunicorn.service

//작성할 내용
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu 계정 이름
Group=www-data
WorkingDirectory=프로젝트 위치 (ex: /home/ubuntu/Projects)
ExecStart=가상환경 위치/bin/gunicorn --workers 3 --bind unix:/tmp/gunicorn.sock 프로젝트 이름.wsgi:application

[Install]
WantedBy=multi-user.target

텍스트 에디터를 이용해 /etc/systemd/system/gunicorn.service 파일을 작성합니다. 작성할 내용은 아래 내용과 같습니다.

Group이 www-data인 이유는 권한을 제한해서 해킹을 당하더라도 모든 서비스를 이용하지 못하고 www-data와 관련된 서비스만 가능하도록 합니다. --workers 3의 경우 nginx로 부터 요청을 받으면 처리할 gunicorn의 프로세스를 3개 만든다는 의미입니다. 이번 bind의 경우 socket을 이용합니다. 만약 직접 ip를 bind하면 nginx에서 proxy를 아이피로 설정하고 데이터를 아이피로 보내고, gunicorn이 ip를 통해 데이터를 받습니다. 이와 반대로 socket을 이용하면 nginx에서 직접 socket을 통해 데이터를 보낼 수 있기 때문에 빠르고 효율적이게 됩니다. bind에 적은 소켓이 존재하지 않으면 gunicorn은 실행될 때 자동으로 해당 소켓을 생성하게 되므로 소켓을 만들지 않아도 괜찮습니다.

sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn

//상태와 로그를 보고 싶을 때
sudo systemctl status gunicorn

이렇게 서비스를 만든 후 위 3개의 명령어로 시스템을 리로드 해주고 gunicorn 서비스를 실행할 수 있습니다.

9. nginx

sudo apt install nginx

sudo vi /etc/nginx/sites-available/원하는 이름.conf

//작성 내용
server {
    listen 80;
    server_name EC2 퍼블릭 주소;
    location /static/ {
        root /home/프로젝트 폴더 위치;
    }
    location / {
        include proxy_params;
        proxy_pass http://unix:/tmp/gunicorn.sock;
    }
}

//nginx 문법 체크
sudo nginx -t

nginx를 사용하기 위해 첫 번째 명령어로 nginx를 설치합니다. 그 다음 두 번째 명령어를 통해 configuration을 작성합니다. 퍼블릭 주소:80으로 들어온 요청을 proxy_pass로 어디에 보낼 것인지 설정합니다.

 로드 밸런서로 모든 요청을 HTTPS로 바꿨는데 어떻게 80포트로 요청을 받을 수 있을까요? 로드 밸런서를 생성할 때 443포트에서 포워드하도록 만든 타겟 그룹이 EC2 인스턴스에 80포트로 라우팅되도록 설정되었기 때문입니다. 이렇게 저장한 뒤 sudo nginx -t로 해당 conf가 문법적으로 맞는 지 확인할 수 있습니다.

sudo ln -s /etc/nginx/sites-available/원하는 이름.conf /etc/nginx/sites-enabled/원하는 이름
sudo rm /etc/nginx/sites-enabled/default

sudo systemctl restart nginx

 다음으로 sites-available에 있는 파일을 sites-enabled 폴더에 symlink를 만들어서 nginx가 해당 설정을 사용하도록 해야합니다. symlink는 ln 명령어와 -s 옵션으로 생성할 수 있습니다. nginx는 처음 설치할 때 자동으로 시스템 서비스에 등록되며 실행됩니다. 이때 사용하는 것이 default 설정 파일인데 우리가 사용하는 포트와 아이피가 겹쳐 예기지 않은 행동을 할 수 있으므로 rm 명령어를 통해 삭제합니다. nginx가 사용할 sites-enabled 폴더의 내용이 바뀌었으므로 restart를 통해 다시 실행시키도록 합니다.

 

이렇게 AWS를 이용해 백엔드 서버 배포가 완료됐습니다!

  

10. Django 설정

이제 Django 프로젝트의 settings.py를 실제 배포용으로 사용할 수 있도록 설정합니다.

ALLOWED_HOSTS = ['백엔드 도메인 주소',
                 'EC2 인스턴스 프라이빗 IP',
                 'EC2 인스턴스 퍼블릭 IP']


CORS_ORIGIN_WHITELIST = ['프론트엔드 주소']

CORS_ALLOW_CREDENTIALS = True

DEBUG가 True면 rest framework 등을 통해 디버그 페이지가 표시되므로 이를 방지하기 위해 False 설정합니다. ALLOWED_HOSTS는 API 요청을 받는 데 사용하는 백엔드 도메인 주소와 인스턴스 IP를 추가해줍니다. CORS_ORIGIN_WHITELIST의 경우 등록되지 않는 사이트에서 API 요청이 오면 응답하지 않기 위해 프론트엔드 주소를 넣어줍니다.

 

이렇게 최종적으로 설정을 마치면 안전하게 백엔드 서버를 사용할 수 있게 됩니다!

이제 Route53에서 로드 밸런서를 연결한 도메인을 프론트엔드에서 백엔드 API 엔드포인트로 사용하면 됩니다.

 

11. 마무리

다양한 지식과 기술을 사용하면서 최종적으로 프론트엔드와 백엔드를 구현하고 배포할 수 있었습니다!

프론트엔드의 경우 GitHub Pages를 이용해 배포했지만 만약 직접 AWS를 통해 배포하고 싶다면 백엔드를 배포하는 과정과 비슷하므로 쉽게 배포할 수 있습니다.

 

이렇게 홈페이지 프로젝트 구현 이야기는 끝나지만 만약 오류를 발견해서 수정하거나 새로운 기능을 추가한다면 계속해서 포스트할 생각입니다. 지금까지 읽어주셔서 감사합니다!!!

댓글