[Cloud 12] 내 서비스에 이름 붙이기: Route53 도메인 및 탄력적 IP 연결
서버를 만들고 DB를 연결했지만, 아직 우리 서비스에 접속하려면 13.124.xx.xx 같은 IP 주소를 입력해야 합니다. 사용자가 기억하기 쉽도록 **탄력적 IP(EIP)**로 주소를 고정하고, Route53을 통해 도메인을 연결해 봅시다.
1. 변하지 않는 주소: 탄력적 IP (Elastic IP)
EC2 인스턴스는 기본적으로 중지 후 재시작하면 IP 주소가 변경됩니다. 도메인을 연결하기 전에 반드시 IP를 고정해야 합니다.
⚙️ 설정 방법
- AWS 콘솔: EC2 서비스의 [네트워크 및 보안] -> [탄력적 IP]로 이동합니다.
- 할당: [탄력적 IP 주소 할당]을 눌러 새로운 공인 IP를 받습니다.
- 연결: 할당된 IP를 선택하고 [동작] -> [탄력적 IP 주소 연결]을 클릭하여, 현재 실행 중인 EC2 인스턴스에 연결합니다.
⚠️ 주의사항: 탄력적 IP는 할당받고 나서 실행 중인 인스턴스에 연결하지 않으면 시간당 비용이 발생합니다. 사용하지 않을 때는 반드시 '릴리스(해제)'해야 합니다.
2. 세상에 하나뿐인 이름: Route53과 도메인
AWS의 Route53은 도메인 이름을 IP 주소로 연결해 주는 가용성이 뛰어난 DNS(Domain Name System) 서비스입니다.
① 호스팅 영역 생성
- Route53 콘솔에서 [호스팅 영역 생성]을 누릅니다.
- 내가 구매한 도메인 이름(예: dev-portfolio.com)을 입력하고 생성합니다.
② 네임서버(NS) 설정
- 호스팅 영역을 만들면 4개의 네임서버 주소가 생성됩니다.
- 가비아, 후이즈 등 도메인을 구매한 대행 업체 사이트에 접속하여, 해당 도메인의 네임서버 정보를 AWS가 준 4개의 주소로 교체해야 합니다. (반영까지 최대 24~48시간 소요될 수 있습니다.)
3. 레코드 등록: 도메인과 IP 연결하기
이제 도메인과 우리 서버의 탄력적 IP를 실제로 매핑합니다.
- A 레코드 생성:
- 레코드 이름: 비워둠 (루트 도메인 example.com) 또는 www 입력.
- 레코드 유형: A - IPv4 주소로 트래픽 라우팅.
- 값: 위에서 설정한 탄력적 IP 주소를 입력합니다.
[Image showing Route53 record creation screen with A record pointing to an Elastic IP address]
4. Django 설정 업데이트 (ALLOWED_HOSTS)
도메인을 연결한 후에는 Django가 이 도메인을 통한 접속을 허용하도록 설정해야 합니다.
settings.py 수정:
# .env 파일 등을 활용하는 것이 좋습니다.
ALLOWED_HOSTS = [
'13.124.xx.xx', # 탄력적 IP
'dev-portfolio.com', # 내 도메인
'www.dev-portfolio.com', # www 서브도메인
]
5. [선생님의 심화 보충] HTTPS(SSL) 예고
도메인을 연결하고 나면 브라우저 주소창에 "주의 요함"이라는 문구가 뜰 것입니다. 이는 HTTP 통신을 하고 있기 때문입니다.
- 보안을 위해 HTTPS 적용이 필수입니다.
- 다음 단계인 Nginx 설정에서 **Certbot(Let's Encrypt)**을 사용하여 무료로 SSL 인증서를 발급받고 적용하는 과정이 이어집니다. 도메인이 연결되어 있어야만 이 인증서를 발급받을 수 있으므로 지금 단계가 매우 중요합니다.
6. 전체 구조 요약
- 사용자가 도메인(example.com) 입력.
- Route53이 도메인을 확인하고 탄력적 IP 주소를 응답.
- 사용자 브라우저가 탄력적 IP가 할당된 EC2 서버로 접속.
- 서버 안의 **Docker(Nginx/Gunicorn)**가 요청을 받아 Django 앱 실행.
✍️ 블로그 작성을 위한 마지막 조언
독자들에게 **"드디어 내 서비스가 고유한 주소를 갖게 되었습니다"**라고 축하해 주세요. 이제는 지인들에게 IP 번호가 아닌 멋진 URL을 공유할 수 있다는 점이 가장 큰 즐거움이죠.
앞에서 작성한 view.py를 폴더로 분리해서 관리하기
__init__.py 파일이란?
- 파이썬에서 **패키지(package)**를 만들 때 꼭 들어가는 특별한 파일이에요.
- “이 폴더는 그냥 일반 폴더가 아니라, 파이썬에서 불러올 수 있는 패키지야!” 라고 표시하는 역할을 해요.
즉, __init__.py가 있으면 그 폴더를 모듈처럼 import 해서 쓸 수 있어요.
예시로 이해하기
📂 프로젝트 구조가 이렇게 있다고 해볼게요:
1) __init__.py가 없을 때
- math_tools 폴더는 그냥 일반 폴더일 뿐이에요.
- import math_tools 같은 코드를 쓰면 에러가 나요.
2) __init__.py가 있을 때
- 이제 math_tools는 파이썬에서 패키지로 인식돼요.
- main.py 안에서 이렇게 쓸 수 있어요:
__init__.py 안에는 뭐가 들어가나?
- 비워둬도 돼요
그냥 폴더가 패키지라는 표시만 해도 충분해요. -
# __init__.py (빈 파일)
- 초기 설정을 넣을 수도 있어요
패키지를 불러올 때 자동으로 실행할 코드나, 미리 불러올 모듈을 지정할 수 있어요.그러면 main.py에서이렇게 더 간단히 쓸 수 있어요. -
from math_tools import add_numbers
-
# __init__.py from .add import add_numbers from .subtract import subtract_numbers
정리
- __init__.py = “이 폴더는 패키지야” 라고 표시하는 파일.
- 안에 아무것도 없어도 되고, 초기 실행 코드나 모듈 연결 코드도 넣을 수 있음.
- 덕분에 파이썬에서 import 할 수 있게 됨.
한 줄 요약
- 웹서버: 정적 파일(HTML/CSS/JS/이미지)을 빠르게 내보내고, 동적 요청은 뒤의 앱으로 프록시(중계). (예: Nginx, Apache HTTPD)
- WAS(애플리케이션 서버): 비즈니스 로직을 실행해 동적 페이지를 만들어 응답. (예: Tomcat, WildFly, Spring Boot(내장 톰캣), Node/Express, Django+Gunicorn/Uvicorn, FastAPI+Uvicorn)
역할 비교 (핵심만)
웹서버
- 정적 파일 서빙(캐싱·압축·HTTP/2/HTTP/3 최적화)
- SSL/TLS 종료(HTTPS 처리), 리버스 프록시/로드밸런싱
- 속도와 동시성에 최적화, 가볍고 빠름
- 예: Nginx, Apache HTTP Server
WAS
- 라우팅, 컨트롤러, 서비스, DB 연동, 세션/인증 등 애플리케이션 로직
- 스레드풀/커넥션풀 관리, 트랜잭션, 미들웨어
- 언어별 런타임/프레임워크 위에서 동작
- 예: Tomcat/Jetty/WildFly(WebSphere/WebLogic), Spring Boot(내장 톰캣), Node.js(Express/Nest), Django(FastAPI) + Gunicorn/uWSGI/Uvicorn
요청 흐름(전형적 구조)
- 보통 80/443(공인 포트)은 웹서버가 받고,
- 내부 포트(예: 8000/8080/3000)는 WAS가 듣습니다.
- 웹서버가 정적은 바로 응답, 동적은 WAS로 프록시 패스.
스택별로 보면
- Django / FastAPI (Python)
- 웹서버: Nginx
- WAS: Gunicorn/uWSGI + Uvicorn(ASGI) 위에 Django/FastAPI 앱
- 정적 파일은 Nginx가, API/동적은 Uvicorn으로 프록시
- Java
- 웹서버: Nginx/Apache
- WAS: Tomcat/Jetty/WildFly, 또는 Spring Boot(내장 톰캣) 단독 실행
- 대규모 엔터프라이즈는 WAS 용어가 특히 익숙
- PHP
- 웹서버: Nginx/Apache
- WAS 역할: php-fpm(FastCGI) 프로세스 풀
- Nginx ↔ php-fpm로 동적 처리
- Node.js
- Node 프로세스(Express/Nest)가 사실상 앱 서버(WAS) 역할
- 보통 Nginx 리버스 프록시 앞단에 둬서 HTTPS/정적/로드밸런싱 담당
왜 굳이 나누나?
- 성능: 정적은 웹서버가 초고속 처리, 동적만 WAS에 전달 → 전체 처리량↑
- 보안: TLS 종료·WAF·속도 제한·리라이트 규칙을 웹서버에서 통제
- 확장성: 웹서버는 가볍게 여러 대로, WAS는 로직 중심으로 스케일 아웃
- 운영 편의: 무중단 배포, 헬스체크, 트래픽 분산, Canary 등 운영 패턴에 유리
자주 쓰는 포트 & 프로토콜
- 웹서버 외부: 80(HTTP), 443(HTTPS)
- 웹서버 → WAS 내부: 8000/8080/3000 등 HTTP, 또는 Unix Socket
- DB: 5432(PostgreSQL), 3306(MySQL/MariaDB)
- 연결 프로토콜: HTTP, FastCGI(PHP), AJP(과거 Tomcat), gRPC(근래 일부)
헷갈리기 쉬운 포인트
- 요즘은 경계가 흐림:
- Spring Boot는 WAS(톰캣)를 내장해 단독으로 8080에서 떠요. 하지만 앞단에 Nginx 두면 운영·보안·정적 처리에 더 좋아요.
- Node/Express도 자체 서버가 있지만, 실서비스에선 Nginx 앞단이 일반적.
- 개발/테스트에선 WAS만 띄워도 됨(간단) ↔ 운영에선 웹서버+WAS 분리가 표준.
최소 예시 (Django/FastAPI 배치 감 잡기)
1) Uvicorn/Gunicorn으로 앱(WAS) 실행
2) Nginx가 앞에서 프록시(웹서버)
최종 정리
- 웹서버는 문지기 + 캐시 + 정적 파일 전문가이자 프록시/로드밸런서 역할.
- WAS는 로직을 실행해 동적 컨텐츠를 만드는 애플리케이션 엔진.
- 개발은 단독 WAS로 시작해도 OK, 운영은 웹서버 앞단 + WAS 뒷단이 정석.
# ✅ Django 운영 서버 준비 (Gunicorn + Nginx)
# 1) Gunicorn 설치 (Python WSGI 서버)
pip install gunicorn
# -> Django/Flask 같은 Python 웹앱을 운영 환경에서 실행할 때 사용
# 2) Nginx 설치 (웹 서버)
sudo dnf install nginx -y
# -> 정적 파일 서빙, 리버스 프록시 역할 (Gunicorn 앞단에서 트래픽 관리)
# 3) Nginx 서비스 상태 확인
sudo systemctl status nginx
# -> 설치 후 Nginx가 실행 중인지, 에러 없는지 확인
# 4) Nginx 실행
sudo systemctl start nginx
# -> 웹 서버 시작 (기본 포트: 80, https는 443)
settings.py
DEBUG = False
STATIC_URL = 'static/' # 정적 파일을 요청할 때 사용할 URL prefix
STATICFILES_DIRS = [BASE_DIR / 'static'] # 개발 시 프로젝트 내 static 폴더 지정
- 개발 모드에서는 DEBUG = True
- 에러 화면에서 상세한 디버깅 정보 표시
- 정적 파일(CSS/JS/이미지)을 Django가 직접 서빙 (편의용)
- 운영(배포) 모드에서는 DEBUG = False
- 에러 화면은 사용자 친화적인 500 에러 페이지로 보임
- Django는 정적 파일을 직접 제공하지 않음 → 대신 Nginx 같은 웹 서버가 정적 파일을 제공해야 함
👉 정리: 운영할 땐 디버깅 끄고 보안·성능에 맞는 구조로 바꿔야 함
python manage.py collectstatic
- 프로젝트 안의 여러 앱(app/static/…)과 루트 static/에 흩어진 파일들을
- 한 곳(STATIC_ROOT)에 모아줌

sudo vim /etc/nginx/nginx.conf
user play;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
# 동적 모듈 로드 (필요하면)
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
# 기본 MIME, 로그 포맷 등 -------------------------------------------------
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# ----------------------------------------------------------------------
# 1) upstream 정의 (http 블록 안!)
# ----------------------------------------------------------------------
upstream encore {
server 127.0.0.1:8000;
}
# ----------------------------------------------------------------------
# 2) 가상 서버 정의
# ----------------------------------------------------------------------
server {
listen 80 default_server;
listen [::]:80 default_server;
# ---------- 정적 파일 ----------
location /static/ {
alias /home/play/workspace/django_test/mysite/static/;
}
# ---------- Django (Gunicorn) ----------
location / {
proxy_pass http://encore; # upstream 이름 사용
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
- Gunicorn 단독 실행
- 보통 python manage.py runserver 또는
gunicorn --bind 0.0.0.0:8000 mysite.wsgi 형태로 실행 - 이건 그냥 일반 프로세스가 특정 포트(8000 등)에 바인딩
- SELinux는 "gunicorn이 8000포트 쓰는 건 괜찮다"라고 허용
- 보통 python manage.py runserver 또는
- Nginx 실행
- 보통 80(HTTP), 443(HTTPS) 포트를 사용
- SELinux 정책에서 “웹서버(Nginx)가 외부 네트워크 연결하거나 특정 디렉토리 접근”은 기본적으로 제한됨
- 그래서 Nginx가 포트 열었는데도 접속 불가가 생길 수 있음
getenforce #리눅스에서 SELinux의 현재 동작 모드를 확인
📌 1. SELinux란?
- Security-Enhanced Linux
- 리눅스 커널에 붙은 보안 모듈
- 파일 접근, 네트워크, 프로세스 실행 같은 걸 더 세밀하게 제어해줌
- 보통 Red Hat, CentOS, Rocky Linux 계열에서 기본 활성화됨
👉 단순히 권한(rwx)으로만 제어하는 게 아니라, “이 프로세스가 이 파일에 접근할 수 있는가?” 같은 추가 정책을 적용
출력값은 3가지 중 하나:
- Enforcing → SELinux 정책이 강제로 적용 중 (제일 빡셈)
- Permissive → 위반을 기록만 하고 막지는 않음 (로그 확인용)
- Disabled → 꺼져 있음 (아예 동작 안 함)
모드 전환(일시적):
sudo setenforce 0 # Permissive로 전환
sudo setenforce 1 # Enforcing으로 전환
설정 파일 변경(영구):
sudo vim /etc/selinux/config
#SELINUX=permissive 이걸로 변경
gunicorn --bind 0:8000 mysite.wsgi:application
📌 1. Gunicorn이 뭐야?
- Gunicorn (Green Unicorn) = Python WSGI 서버
- Django, Flask 같은 웹 프레임워크는 자체 개발 서버가 있는데, 이건 개발용이라 실제 서비스엔 적합하지 않음.
- Gunicorn은 운영 환경에서 쓰는, 안정적이고 빠른 웹 서버.
- Nginx 같은 리버스 프록시랑 자주 짝을 이룸.
📌 2. 옵션 설명
--bind 0:8000
- --bind = 어떤 IP와 포트에 서버를 열지 지정
- 0:8000 → 0.0.0.0:8000의 축약형
- 0.0.0.0 = 모든 네트워크 인터페이스에서 접속 허용
- :8000 = 8000번 포트에서 대기
👉 즉, 어디서든 내 서버IP:8000으로 접속 가능
mysite.wsgi:application
- mysite = Django 프로젝트 디렉토리 이름
- wsgi = Django가 만든 WSGI 엔트리 파일 (mysite/wsgi.py)
- application = 그 안에 정의된 WSGI 애플리케이션 객체
이제 포트번호를 적지않고 serv1로 만 접속해도 웹서버로 접속돼소 웹서버에서 was를 불러 화면을 보여준다
📌 1. 데몬(daemon)이란?
- 리눅스/유닉스에서 백그라운드에서 계속 돌아가는 프로세스
# ✅ Gunicorn을 systemd 서비스로 등록해서 재부팅해도 Django 서버 자동 실행되도록 설정
# 1) Gunicorn 실행 파일 위치 확인 (가상환경 안에 설치된 경로 확인)
which gunicorn # 예: /home/play/workspace/django_test/venv/bin/gunicorn
# 2) 서비스 파일 생성 (/etc/systemd/system/mysite.service)
sudo vim /etc/systemd/system/mysite.service
# ---- mysite.service 내용 ----
[Unit]
Description=gunicorn daemon # 서비스 설명
After=network.target # 네트워크 활성화 후 실행
[Service]
User=play # 실행할 사용자
Group=play # 실행 그룹
WorkingDirectory=/home/play/workspace/django_test/mysite/ # Django 프로젝트 경로
ExecStart=/home/play/workspace/django_test/venv/bin/gunicorn \
--workers 2 \ # 동시에 처리할 프로세스 수(멀티프로세스)
--bind 0:8000 mysite.wsgi:application # 모든 IP에서 8000포트로 실행
[Install]
WantedBy=multi-user.target # 다중 사용자 모드에서 실행
# --------------------------------
# 3) systemd에 서비스 반영
sudo systemctl daemon-reload # 서비스 파일 다시 읽기
sudo systemctl start mysite.service # 서비스 즉시 실행
sudo systemctl enable mysite.service # 재부팅 시 자동 실행 등록
# 4) Nginx도 부팅 시 자동 실행되도록 등록
sudo systemctl enable nginx
로그 보는 습관 길들이기
'웹 > 배포' 카테고리의 다른 글
| [Final 17] 서비스 모니터링 기법과 17일간의 로드맵 회고,"로그 관리 (0) | 2025.09.12 |
|---|---|
| [Final 16] 코드만 push하세요, 배포는 GitHub Actions가 알아서 합니다 (0) | 2025.09.11 |
| [Final 15] 아직도 HTTP? Certbot으로 5분 만에 HTTPS 적용하기 (0) | 2025.09.10 |
| [Final 14] 서버 용량 걱정 끝! S3로 Static & Media 파일 관리하기 (0) | 2025.09.10 |
| [Cloud 13] Nginx 리버스 프록시 설정으로 서버 트래픽 안정화하기 (1) | 2025.09.08 |