django app 공개 운영을 위해 [Django] 장고 앱 공개 운영 포스트 Fig 1의 nginx, mysql, django app을 하나의 ec2 인스턴스에 각각 도커 컨테이너로 실행 하려고 계획을 하고 서버를 실행했지만 이 방식에 큰 단점이 있어 아키텍쳐를 변경하기로 했다.
이 포스팅은 aws RDS 인스턴스를 생성해 ec2 인스턴스와 연동하는 방법에 대해 정리한다.
목표
Fig 1. (a)는 처음 계획한 운영 아키텍쳐로 ec2 instance 하나에 nginx, django app, mysql 3개를 각각 도커 컨테이너로 실행하는 방식이었고 Fig 1.(b)는 변경하고자 하는 아키텍쳐로 ec2 instance에는 nginx, django app만 운영하고 mysql은 aws RDS 인스턴스로 분리 운영 하는 방식이다.
기존 아키텍쳐의 문제
Fig 1.(a) 처럼 아키텍쳐를 설계하는 것에는 다음과 같은 문제 점이 있었다.
1. cpu 성능: 현재 테스트 운영에 사용하고자 하는 aws 프리티어 계정으로 만들수 있는 ec2 인스턴스는 vcpu 1개 , ram 1GB 가 한계 스펙이다. 이 성능으로 (a)와 같이 아키텍쳐를 구성한 결과 심심치 않게 cpu 사용률이 70% 이상으로 치솟는 경우가 발생했다.
2. database backup: 나는 database 전문가가 아니라 EBS를 비휘발성으로 설정해 database backup 설정을 하고 실패 없이 관리한 자신이 없었다.
위와 같은 문제를 해결하기 위해 mysql을 ec2에 설치해 운영 하지 않고 Fig 1. (b) 처럼 별도 RDS인스턴스로 분리하기로 결정했다.
Fig 1. (a) 처럼 운영하는 것도 장점은 있다. RDS는 비용이 생각 보다 비싼데 database를 잘알고 백업 보안 schema를 잘 설정 할 수 있다면 ec2 를 이용하는 게 비용적인 측명에서 더 좋다고한다.
RDS 인스턴스 생성
RDS 인스턴스 생성을 위해서는 aws console 검색 창에 RDS 를 검색 한후 'rds console -> 데이터베이스 -> 데이터베이스 생성' 을 클릭하면 된다.
나의 경우 mysql rds 인스턴스를 생성했다. mysql의 경우 라이선스가 GNU v2.0 으로 별도 라이선스 비용이 들어가지 않는다. 아래 그림 처럼 표준 생성에서 '엔진 옵션->Mysql' 을 선택하고 버전을 기존 사용 하던 8.0.31로 설정했다.
다음으로 아래 그림의 제일 위는 템플릿을 선택하는 과정인데 나는 당연히 월 750시간 사용이 무료인 계정이니 '프리 티어'를 선택했다. '프리 티어' 템플릿을 선택하면 '가용성 및 내구성'은 자동으로 '단일 DB 인스턴스'로 설정된다.
그 아래에서는 'DB 인스턴스 식별자'를 입력하는데 여기서 입력하는 이름이 'rds console -> 데이터베이스'에 들어가면 나오는 db 인스턴스 식별자이다. 자신의 용도에 맞게 직관적인 이름을 넣으면 된다.
자격증명은 mysql 서버에 접속 할때 사용하는 user id 와 password 이다.
인스턴스 클래스는 정보는 link에 매우 상세하게 나와있으니 정확한 정보는 링크를 통해 확인하고 여기선 간단히만 정리하자.
스탠다드 클래스 : 컴퓨팅, 메모리 및 네트워크 리소스의 균형을 제공하며 많은 데이터베이스 워크로드에 좋은 선택. 스탠다드 클래스는 대부분의 일반적인 데이터베이스 요구사항을 충족 할수 있는 사양이다.
메모리 최적화 클래스: 메모리에서 대용량 데이터 집합을 처리하는 워크로드의 성능을 가속화합니다. 높은 처리 성능이 요구되는 경우에 적합하다. 메모리를 더 많이 확보하면 더 많은 데이터를 메모리에 저장하고 이는 쿼리 시간 단축으로 이어질 확률이 높으므로..
버스터블 클래스: 기준 수준의 CPU 성능과 함께 기준 수준 이상으로 버스트할 수 있는 기능을 제공합니다. 여기서 버스트란 순간 확장으로 순간적으로 더많은 리소스를 필요로 하는 경우 이를 가능하게 한다는 의미 이다.
퍼블릭 액세스가 불가능한 경우 접속하고자 하는 데이터베이스와 같은 vpc(vitual private cloud)에 속한 ec2 인스턴스에 연결해
'퍼블릭 액세스 가능' 한경우와 똑같이 '앤드포인트' 정보를 이용해 접속할 수 있다.
mysql -h product.cie6ndifnblb.ap-northeast-2.rds.amazonaws.com -P 3306 -u admin -p
추가 작업
나의 경우 docker-compose 로 서버를 실행 하므로 위의 rds database instance 앤드포인트 정보로 docker-compose.yml 정보를 업데이트 해줘야한다. 환경변수를 선언하는 environment의 DB_HOST_ADDRESS 값에 database 앤드포인트를 할당한다.
(django app 실행 전에 새로 만든 mysql 서버에 django app에서 사용 하는 database를 생성하고 django app에서 database 를 migrate 하는 절차를 반드시 실행해야 한다.)
EC2 인스턴스에서 실행 중이던 앱에 접속이 안되어 인스턴스 상태 확인을 해보니 상태검사 1/2개 통과라는 메시지를 발견해 이를 해결하기 위한 과정을 정리해본다.
-> 시스템 로그의 정확한 분석에 실패해 나의 경우 해결은 아직 못했다.(2023.01.21)
상태 검사
Amazon ec2는 아래 두 가지 상태 확인을 통해 각 ec2 인스턴스 상태를 모니터링한다.
1. 시스템 상태 확인
시스템 상태 확인은 인스턴스가 실행되는 기본 호스트에서의 문제를 탐지합니다. 네트워크, 하드웨어 또는 소프트웨어 문제로 인해 기본 호스트가 응답하지 않거나 이에 연결할 수 없는 경우 이 상태 확인에 실패합니다.
2. 인스턴스 상태 확인
인스턴스 상태 확인 실패는 인스턴스의 연결 가능성에 문제가 있음을 나타냅니다. 이 문제는 다음과 같은 운영 체제 수준 오류로 인해 발생합니다. 1. 운영 체제 부팅 실패 2. 올바른 볼륨 탑재 실패 3. CPU 및 메모리 소진 4. 커널 패닉 5. 네트워크가 작동하지 않음
위 두 가지중 어떤것이 문제인지 파악하기 위해서는 'ec2 콘솔 -> 인스턴스 -> 상태 검사' 탭을 참조하면 알 수 있다.
아래는 문제가 생긴 내 인스턴스의 상태 검사 탭인데 나의 경우 시스템 상태 검사 : 통과, 인스턴스 상태 검사: 인스턴스 연결성 검사 실패 였다.
원인 분석
인스턴스 상태 검사를 통과 하지 못한 경우 원인 분석을 위해 시스템 로그를 확인하고 시스템 로그에 있는 데이터에 따라 그에 맞는 해결 방법을 적용해야 한다.
ec2 인스턴스에 nginx 서버를 띄우고 'wget my.ip.address' 로 통신 확인을 하니 응답이 오지 않고 timeout 이 발생해서 해결방법을 알아 보다 aws ec2 보안 그룹이 80번 포트를 오픈 하지 않아서 라는 이유를 발견하고 이에 대한 해결책으로 본 포스팅을 작성한다.
인바운드 규칙 변경:
aws ec2 는 보안그룹 설정이 있다. 'ec2 -> 인스턴스->보안 텝'에 가면 아래와 같은 정보를 볼수 있다. 이때 인바운드 규칙을 보면 22, 443 번 포트만 open되어 있는 것을 확인 할 수 있다.
아래 처럼 입력 하면 기본 http 서버 즉 80 번 포트로 접속을 시도 하기 때문에 허용되지 않은 포트라 페이지를 가져 오지 못한것이다. (내가 참고한 블로그에서는 aws 가 기본적으로 80번 포트를 오픈 해놨다고 했는데 잘못된 정보 였나 보다. )
wget 192.168.0.5
테스트를 위해 보안 그룹 인바운드 규칙에 80번 포트 허용을 추가 하자.
인바운드 규칙 추가를 위해 Fig 1. 에서 '보안 그룹' 밑에 있는 파란 글자 'sg-01c149~~~~'를 클릭하면 아래와 같은 화면이 나온다. 이 화면에서 우측하단에 보면 '인바운드 규칙 편집' 이라는 버튼이 있다.
클릭 하자.
그럼 아래와 같은 화면이 나온다.
좌측 하단에 규칙 추가를 클릭해서 '유형' Http를 검색해 추가 한 후 '소스' 에 0.0.0.0/0 을 추가 하면 아래와 같이 된다.
규칙 저장을 클릭 하면 변경 사항이 적용 된다.
저장 하고 보안 그룹의 인바운드 규칙을 보면 아래 그림과 같이 80번 포트를 허용이 추가된 것을 볼 수 있다.
wget 을 이용해 다시 테스트 해보니 이번엔 아래와 같이 응답을 받았다. 다만 gateway 에러가 났다.
이렇게 하려고 하다 보니 생기는 문제가 static 파일들을 어떻게 nginx 와 django app이 실행되는 container 에 모두 연결 할수 있는가 였다. 사실 어려운 문제는 아니지만 웹 운영에 익숙치 않는 나의 얕은 지식에서 비롯된 문제 였다.
내가 처음 헷갈렸던 문제는 다음과 같다.
이슈 1. container 1(nginx)와 container 2(django app)이 접근 또는 사용 하는 static 파일들은 물리적으로 같은게 좋은건가? 각각 동일한 파일들을 복사해서 따로 사용 하는게 좋은 걸까?
Answer: 실제 운영 환경에서 어떤지는 잘 모르겠다. 다만 static 파일이 다르면 django app에서 물리 파일이 추가/변경 될때 마다 nginx가 참조 하는 static 파일들을 django app의 최신 파일들과 싱크를 맞추는 과정이 필요하다.
이슈 2. container 1(nginx) 와 container 2(django app) 내에서 static 파일들의 절대 경로가 같아야 하는가?
Answer: 이럴 필요 전혀 없었다.
nginx 를 실행 하는 container 상의 staticfile 위치와 django app 실행 하는 container 내의 staticfile 위치는 달라도 된다.
Container 분리를 위한 과정 및 설정
1. django app settings.py(내 경우 config/settings/prod.py 파일) 에 STATIC_ROOT=./staticfiles 를 파일 맨 밑에 추가.
django 에서 static 파일과 관련된 django setting.py 내의 변수들은 대략 5가지 인데 3가지만 정리 우선 정리 하면 1. STATIC_URL: 웹페이지에서 사용할 정적 파일들의 최상위 URL 경로. 실제 파일들의 물리적인 위치가 아니며 개념적인 경로로이고 반드시 '/'로 끝나야 한다. 2. STATICFILES_DIRS: 개발 단계에서 사용하는 정적(static)파일이 존재 하는 위치하는 경로를 설정하는 항목. 운영시에도 django app은 여기 정의된 위치에서 static 파일을 참조 한다고 한다. 3. STATIC_ROOT:django app에서 사용하는 모든 정적파일들이 모여 있는 디렉토리로 아래 collectstatic 명령 사용시 정적파일들이 복사 되는 위치이기도 하다.
2. 아래 명령을 실행해 static 파일들을 한 directory 에 모은다.
python manage.py collectstatic
3. 이 directory 를 nginx container로 mount 하고 nginx server 설정 파일(django_app_server)에서 내 서버로 들어오는 요청이 static 파일 참조가 필요할 경우 mount한 위치에서 static 파일을 찾도록 아래와 같이 내용 변경.
upstream django_app{
server 192.168.0.102:8000;
}
server {
listen 80;
server_name 192.168.0.102; ##client 가 접속하는 도메인네임, 장고 app 의 서버 주소
location = /favicon.ico { access_log off; log_not_found off; }
location /static {
alias /home/nginx/www/static; ##docker compose 에서 volume 마운트 한 static 파일의 위치를 여기에 설정
}
location / {
include proxy_params;
proxy_pass http://django_app; # static 이 아닌 요청은 jnbdrive_app upstream 으로 요청 포워딩
}
}
아래는 docker-compose.yml 파일이다. django_app_server 설정 파일과 staticfiles 파일의 마운트 위치를 잘 살펴 보자.
staticfiles은 nginx container의 /home/nginx/www/static 위치에 마운트 했고 이 이 위치를 django_app_server 설정에서 참조하도록 했다.