배포

김기훈·2026년 3월 26일

AWS

목록 보기
5/5

nginx < - 이론 보기


인프라 📌

  • AWS 기본 아키텍처(EC2, RDS 등) 설계

  • Github Actions 등을 활용한 CI/CD 구축

  • AWS 프리티어 요금 폭탄 방지 설정

  • 코드 내 환경 변수(Secret) 분리 작업


진행방안

  • 1단계: 어떤 AWS 서비스를 사용할지 결정하기 (아키텍처 스케치)

    • 서버 (컴퓨터)

      • EC2 (Elastic Compute Cloud)를 주로 사용합니다. 가상의 컴퓨터를 한 대 빌리는 개념
    • 데이터베이스

      • RDS (Relational Database Service)를 사용하면 관리가 편함 (MySQL, PostgreSQL 등)
  • 2단계: 네트워크와 보안 문지기 설정 (보안 그룹)

    • 클라우드에 컴퓨터를 빌렸으니, 아무나 들어오지 못하게 문단속을 해야 함

      • AWS에서는 이를 보안 그룹(Security Group)이라고 부름
    • 인바운드 규칙 (들어오는 트래픽)

      • 웹사이트 접속을 위해 80(HTTP), 443(HTTPS) 포트를 열어줌
      • 내가 원격으로 서버에 접속하기 위해 22(SSH) 포트도 내 IP에 한해서만 열어둠
  • 3단계: EC2와 RDS 생성 및 세팅 (인프라 구축)

    • EC2 만들기

      • 보통 프리티어로 무료 사용이 가능한 t2.micro 사양의 Ubuntu 리눅스 서버를 생성
    • RDS 만들기

      • 블로그의 글과 유저 정보가 저장될 데이터베이스를 만듬 (이때 DB 비밀번호를 꼭 기록)
    • 환경 변수 세팅

      • DB 주소, 비밀번호 등 민감한 정보를 EC2 서버의 환경 변수로 설정
  • 4단계: 수동으로 첫 배포해 보기

    • 처음부터 자동화(CI/CD)를 구축하면 에러가 났을 때 원인을 찾기 힘듬
      • 처음에는 일단 손으로 해보는게 좋음
    • 터미널을 통해 EC2 서버에 원격으로 접속(SSH)합니다.
    • Github에서 블로그 코드를 git clone으로 서버에 다운로드합니다.
    • 필요한 패키지를 설치하고 빌드합니다 (npm install, npm run build 등).
    • 서버를 무중단으로 실행합니다. (Node.js라면 PM2, Java라면 nohup 등을 사용)
  • 5단계: 도메인 연결 및 HTTPS(보안 연결) 적용

    • IP 주소(예: 13.123.45.67)로 접속할 수는 없으니, 도메인을 붙여주기
    • 도메인 구매
      • 가비아, Route 53 등에서 도메인을 구매합니다.
    • HTTPS 적용
      • AWS Certificate Manager(ACM)에서 무료 인증서를 발급받고
      • ALB (로드밸런서)나 CloudFront를 통해 HTTPS 통신(자물쇠 마크)을 적용

적용

  • Docker를 사용하는 Django 프로젝트 진행중

    • EC2 인스턴스에 Docker Compose를 활용하는 방법으로 진행
  • 1. 데이터베이스(RDS) 분리

    • Docker 컨테이너 안에 데이터베이스를 함께 띄울 수도 있지만
      • 컨테이너가 삭제되면 블로그 글도 모두 날아갈 위험이 존재
      • 따라서 안전한 데이터 보관을 위해 AWS RDS(PostgreSQL 또는 MySQL)를 별도로 생성하고
      • Django의 settings.py에서 이 RDS 주소를 바라보게 설정해야 함
  • 2. Nginx와 Gunicorn 구성 준비

    • Django의 기본 runserver 명령어는 개발용이므로 실제 배포 환경에서는 버티지 못함
    • 클라이언트의 요청을 안정적으로 받아주는 웹 서버(Nginx)와
      • Python 코드를 실행해 주는WAS(Gunicorn)를 설정해야 함
    • Nginx, Gunicorn, Django를 각각의 컨테이너로 만들고 docker-compose.yml로 묶어서 준비
  • 3. 정적 파일(Static/Media) 처리 설정

    • 블로그의 CSS, JavaScript, 유저가 업로드한 이미지 등은 Django가 직접 처리하면 속도가 느려짐
    • 이 파일들은 Nginx가 대신 서빙하도록 볼륨(Volume)을 공유하거나,
      • AWS S3라는 전용 저장소로 분리하도록 코드를 약간 수정
  • 4. EC2 서버 준비 및 Docker 설치

    • AWS에서 EC2(가상 컴퓨터)를 하나 임대하여 접속한 뒤
    • Docker와 Docker Compose 모듈을 설치합니다. 코드를 실행할 빈 도화지를 준비하는 과정
  • 5. 서버에서 이미지 빌드 및 실행

    • EC2 서버 안에서 Github에 올려둔 코드를 다운로드(git pull)
    • 그 후 docker-compose up -d --build 명령어 한 줄만 입력하면
      • 작성해 둔 설정에 따라 컨테이너들이 백그라운드에서 실행되며 배포가 완료

AWS 배포 전체 진행 로드맵

  • Step 1. 로컬 배포 환경 준비 (Docker & Django 세팅)

    • 서버에 코드를 올리기 전, 내 컴퓨터(로컬)에서 배포용 세팅을 마무리하는 단계
      • Django 설정 수정

        • settings.py에서 DEBUG = False로 변경하고, ALLOWED_HOSTS를 설정함
        • 정적 파일을 모으기 위한 STATIC_ROOT 설정도 추가
      • Docker 설정 작성

        • docker-compose.ymlnginx.conf 파일
        • 그리고 Django용 Dockerfile을 작성하여 프로젝트 폴더에 위치시킴
      • 환경 변수 분리

        • DB 비밀번호, S3 시크릿 키 등을 .env 파일로 분리하여 Github에 올라가지 않도록 처리
  • Step 2. AWS RDS (데이터베이스) 생성 및 연결

    • EC2 서버를 만들기 전에, 데이터를 안전하게 보관할 전용 데이터베이스를 먼저 만듬
      • RDS 인스턴스 생성

        • MySQL 또는 PostgreSQL (프리티어) 데이터베이스를 만듬
      • 보안 그룹 설정

        • 외부에서 아무나 DB에 접근하지 못하도록 방화벽(Security Group)을 설정
      • 로컬 테스트

        • 내 컴퓨터에서 AWS RDS로 접속이 잘 되는지 테스트
  • Step 3. AWS EC2 (서버) 생성 및 기본 세팅

    • 실제 24시간 돌아갈 가상의 컴퓨터를 임대하는 단계
      • EC2 인스턴스 생성

        • Ubuntu 리눅스 OS가 설치된 서버(프리티어)를 만듬
      • 고정 IP 할당

        • 서버가 재부팅되어도 주소가 바뀌지 않도록 '탄력적 IP(Elastic IP)'를 부여
      • 보안 그룹 설정

        • 웹 접속을 위한 80(HTTP), 443(HTTPS) 포트와 원격 접속을 위한 22(SSH) 포트를 열어줌
  • Step 4. 서버 원격 접속 및 첫 배포 실행

    • 준비된 EC2 서버에 들어가서 내 코드를 실행하는 단계
      • 서버 접속 및 패키지 설치

        • 터미널로 EC2에 접속(SSH)한 뒤, Docker와 Docker Compose를 설치
      • 코드 다운로드 및 .env 세팅

        • Github에서 코드를 clone 받아오고, 서버 환경에 맞춰 .env 파일을 생성
      • Docker 실행

        • docker-compose up -d --build 명령어로 서버를 띄움
      • 마무리 작업

        • migrate(DB 테이블 생성)와 collectstatic(Nginx용 정적 파일 모으기) 명령어를 실행
  • Step 5. 도메인 연결 및 HTTPS (보안 인증서) 적용

    • IP 주소 대신 예쁜 도메인 이름으로 접속할 수 있게 마무리하는 단계
      • 도메인 연결

        • 가비아 등에서 구매한 도메인을 EC2의 고정 IP와 연결
      • HTTPS 적용

        • Let's Encrypt(Certbot)를 활용하여 Nginx에 무료 SSL 인증서를 발급받고 적용
          • 자물쇠 마크 생성

파일 생성 📌

파일 (80번 포트) 📌

Dockerfile

# 파이썬 3.13의 가벼운(slim) 버전을 기반 이미지로 사용하여 컨테이너 용량을 줄입니다.
FROM python:3.13-slim

# 파이썬 출력을 버퍼링하지 않고 콘솔에 즉시 출력하도록 설정하여 로그 확인을 쉽게 합니다.
ENV PYTHONUNBUFFERED=1

# 파이썬이 불필요한 .pyc(바이트코드) 파일을 생성하지 않도록 설정하여 용량을 아낍니다.
ENV PYTHONDONTWRITEBYTECODE=1

# 컨테이너 내부에서 작업이 이루어질 기본 디렉토리를 /app으로 지정합니다.
WORKDIR /app

# 운영체제 패키지 목록을 업데이트하고 필요한 필수 시스템 패키지들을 설치합니다.
RUN apt-get update && apt-get install -y \
    curl \
    libpq-dev \
    gcc \
    # [최적화] 설치가 끝난 후 불필요해진 apt 캐시를 삭제하여 도커 이미지 크기를 대폭 줄입니다.
    && rm -rf /var/lib/apt/lists/*

# Poetry 패키지 관리자를 pip를 통해 설치합니다.
RUN pip install poetry

# Poetry가 가상환경(virtualenv)을 생성하지 않고 시스템(컨테이너) 전역에 패키지를 설치하도록 설정합니다. (도커 자체가 이미 격리된 환경이므로 불필요)
RUN poetry config virtualenvs.create false

# 의존성 설치에 필요한 pyproject.toml 파일과 poetry.lock 파일을 먼저 복사합니다. (캐시 활용을 위해 소스 코드보다 먼저 복사)
COPY pyproject.toml poetry.lock* /app/

# Poetry를 사용하여 의존성 패키지들을 설치합니다. (--no-root: 현재 프로젝트 자체는 설치 제외, --no-interaction: 사용자 입력 무시, --no-ansi: 색상 출력 무시)
RUN poetry install --no-root --no-interaction --no-ansi

# 나머지 모든 프로젝트 소스 코드를 도커 컨테이너 내부의 /app 디렉토리로 복사합니다.
COPY . /app/

배포용 docker-compose.prod.yml 생성

  • 기존 파일은 runserver를 사용했음, 이는 개발용이므로,
    • 배포 환경에서는 GunicornNginx를 사용하도록 구조를 완전히 전환
    • 프로젝트 루트에 docker-compose.prod.yml 이라는 파일을 새로 만들어주자
# Docker Compose 파일의 포맷 버전을 명시합니다.
version: '3.8'

# 실행할 개별 컨테이너(서비스)들을 정의합니다.
services:
  # 1. 백엔드(Django + Gunicorn) 서비스
  backend:
    # 현재 디렉토리의 Dockerfile을 기반으로 이미지를 빌드합니다.
    build: .
    # 컨테이너의 이름을 알기 쉽게 지정합니다.
    container_name: blog_backend_prod
    # 서버가 다운되었을 때 자동으로 재시작되도록 설정합니다.
    restart: always
    # Django 앱을 실행하는 명령어로, runserver 대신 성능이 좋은 Gunicorn을 사용합니다.
    command: >
      sh -c "python manage.py collectstatic --noinput &&
             python manage.py migrate &&
             gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3"
             # [최적화] collectstatic으로 정적 파일을 모으고, Gunicorn worker를 3개 띄워 요청을 병렬 처리합니다.
    # Nginx와 정적 파일을 공유하기 위해 static_volume을 /app/staticfiles 경로에 마운트합니다.
    volumes:
      - static_volume:/app/staticfiles
    # 환경 변수가 저장된 .env 파일을 컨테이너에 전달합니다.
    env_file:
      - .env
    # 백엔드는 데이터베이스(db)가 완전히 실행된 이후에 켜지도록 의존성을 설정합니다.
    depends_on:
      - db
    # 컨테이너들이 통신할 내부 네트워크를 지정합니다.
    networks:
      - app_network

  # 2. 데이터베이스(PostgreSQL) 서비스
  db:
    # PostgreSQL 15 버전의 공식 이미지를 사용합니다.
    image: postgres:15
    # 컨테이너의 이름을 지정합니다.
    container_name: blog_db_prod
    # 서버 재시작 시 DB도 자동으로 다시 켜지도록 설정합니다.
    restart: always
    # 환경 변수 파일을 통해 DB 생성에 필요한 유저명, 비밀번호 등을 전달합니다.
    env_file:
      - .env
    # DB 데이터가 날아가지 않도록 postgres_data 볼륨을 물리적 디스크에 저장합니다.
    volumes:
      - postgres_data:/var/lib/postgresql/data
    # 통신할 내부 네트워크를 지정합니다.
    networks:
      - app_network

  # 3. 웹 서버(Nginx) 서비스 - Static 파일 서빙 및 프록시 역할
  nginx:
    # Nginx 최신 버전 이미지를 사용합니다.
    image: nginx:latest
    # 컨테이너 이름을 지정합니다.
    container_name: blog_nginx_prod
    # 서버가 켜질 때 자동으로 실행되도록 설정합니다.
    restart: always
    # 호스트(EC2)의 80번(HTTP) 포트로 들어오는 요청을 Nginx 컨테이너의 80번 포트로 연결합니다.
    ports:
      - "80:80"
    # 백엔드 컨테이너와 볼륨을 공유하기 위해 설정합니다.
    volumes:
      # 로컬에 작성할 Nginx 설정 파일을 컨테이너의 설정 파일로 덮어씌웁니다.
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      # 백엔드가 모아둔 정적 파일을 Nginx가 읽을 수 있도록 동일한 볼륨을 마운트합니다.
      - static_volume:/app/staticfiles
    # 백엔드 컨테이너가 실행된 후 Nginx가 실행되도록 설정합니다.
    depends_on:
      - backend
    # 통신할 내부 네트워크를 지정합니다.
    networks:
      - app_network

# 서비스들이 데이터를 저장하거나 공유할 볼륨(가상 폴더)을 선언합니다.
volumes:
  # 데이터베이스 영구 저장을 위한 볼륨
  postgres_data:
  # Nginx와 Django가 정적 파일을 공유하기 위한 볼륨
  static_volume:

# 컨테이너들끼리 안전하게 통신할 수 있는 가상 네트워크를 선언합니다.
networks:
  app_network:
    driver: bridge

Nginx 설정 파일 생성

  • 프로젝트 폴더 안에 nginx 라는 폴더를 만들고, 그 안에 nginx.conf 파일을 생성
# Nginx의 서버 블록을 정의합니다.
server {
    # 80번 포트(HTTP)로 들어오는 요청을 대기합니다.
    listen 80;
    
    # 처리할 도메인 주소나 IP를 지정합니다. (일단 모든 요청을 받도록 언더스코어(_) 사용)
    server_name _;

    # 클라이언트가 /static/ 경로로 요청을 보낼 때 처리하는 블록입니다.
    location /static/ {
        # Django를 거치지 않고, Nginx가 공유받은 볼륨 경로에서 직접 파일을 찾아 반환합니다. (응답 속도 최적화)
        alias /app/staticfiles/;
    }

    # 정적 파일 외의 모든 일반적인 웹 요청(/)을 처리하는 블록입니다.
    location / {
        # 요청을 백엔드(Django Gunicorn) 컨테이너의 8000번 포트로 그대로 넘겨줍니다. (프록시)
        proxy_pass http://backend:8000;
        
        # 원래 클라이언트가 요청한 도메인(Host) 정보를 백엔드에 전달합니다.
        proxy_set_header Host $host;
        
        # 클라이언트의 실제 IP 주소를 백엔드에 전달하여, 백엔드가 방문자 IP를 알 수 있게 합니다.
        proxy_set_header X-Real-IP $remote_addr;
        
        # 여러 프록시를 거쳤을 경우의 IP 체인 정보를 백엔드에 전달합니다.
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

settings.py

# 환경 변수에서 ALLOWED_HOSTS 리스트를 가져오되, 값이 없다면 기본값으로 모든 접속('*')을 허용
# 배포 환경의 .env 파일에는 ALLOWED_HOSTS=13.123.45.67,myblog.com 처럼 쉼표로 구분해서 작성하게 됨

ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["*"])
# ---------------------------------------------------------------------------
# 배포 환경(Nginx + HTTPS)을 위한 보안 설정
# ---------------------------------------------------------------------------

# Nginx 같은 프록시 서버가 클라이언트의 HTTPS 접속을 받아서 HTTP로 넘겨줄 때, 
# Django가 원래 요청이 HTTPS였음을 파악할 수 있게 헤더를 지정합니다.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

# 배포 환경(DEBUG=False)일 때만 엄격한 쿠키 보안 정책을 적용합니다.
if not DEBUG:
    # 세션 쿠키를 HTTPS 연결에서만 전송하도록 강제합니다. (해커가 쿠키를 가로채는 것을 방지)
    SESSION_COOKIE_SECURE = True
    
    # CSRF 보안 쿠키도 HTTPS 연결에서만 전송하도록 강제합니다.
    CSRF_COOKIE_SECURE = True
    
    # 브라우저가 XSS 공격을 통해 쿠키(세션 등)에 접근하는 것을 자바스크립트 레벨에서 차단합니다.
    SESSION_COOKIE_HTTPONLY = True

파일 (433 포트) 📌

Nginx에 HTTPS(SSL) 적용하기

  • AWS 기반의 실제 서비스 환경(Production)에서는 보안과 SEO(검색엔진 최적화)를 위해
    • HTTPS(443번 포트) 적용이 필수
  • 배포 테스트에는 80번 포트로도 가능하지만 실 서비스 직전에는 AWS의 ALB(Application Load Balancer)
    • Let's Encrypt(Certbot)를 활용해 SSL 인증서를 적용하고
    • 80번 포트 요청을 443번 포트로 리다이렉트(Redirect) 하도록 Nginx 설정을 변경해야 함

nginx.conf 수정

  • HTTP로 접속하는 사용자를 HTTPS로 안전하게 리다이렉트 시키고, 443 포트로 암호화 통신을 하도록 수정
# Nginx의 서버 블록을 정의합니다.
server {
    # 80번 포트(HTTP)로 들어오는 요청을 대기합니다.
    listen 80;

    # 처리할 도메인 주소나 IP를 지정합니다. (일단 모든 요청을 받도록 언더스코어(_) 사용)
    server_name _;

    # 클라이언트가 /static/ 경로로 요청을 보낼 때 처리하는 블록입니다.
    location /static/ {
        # Django를 거치지 않고, Nginx가 공유받은 볼륨 경로에서 직접 파일을 찾아 반환합니다. (응답 속도 최적화)
        alias /app/staticfiles/;
    }

    # 정적 파일 외의 모든 일반적인 웹 요청(/)을 처리하는 블록입니다.
    location / {
        # 요청을 백엔드(Django Gunicorn) 컨테이너의 8000번 포트로 그대로 넘겨줍니다. (프록시)
        proxy_pass http://backend:8000;

        # 원래 클라이언트가 요청한 도메인(Host) 정보를 백엔드에 전달합니다.
        proxy_set_header Host $host;

        # 클라이언트의 실제 IP 주소를 백엔드에 전달하여, 백엔드가 방문자 IP를 알 수 있게 합니다.
        proxy_set_header X-Real-IP $remote_addr;

        # 여러 프록시를 거쳤을 경우의 IP 체인 정보를 백엔드에 전달합니다.
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

——————————————————————————————————————[비교]—————————————————————————————————————————
# -------------------------------------------------------------------
# [1] HTTP 요청을 HTTPS로 리다이렉트하는 서버 블록입니다.
# -------------------------------------------------------------------
server {
    # 80번 포트(HTTP)로 들어오는 요청을 대기합니다.
    listen 80;
    
    # 처리할 도메인 주소나 IP를 지정합니다. (실제 도메인으로 변경해야 합니다. 예: myblog.com)
    server_name _; 
    
    # 들어온 모든 요청을 HTTPS 주소로 영구 이동(301 상태 코드) 시킵니다.
    return 301 https://$host$request_uri;
}

# -------------------------------------------------------------------
# [2] HTTPS(443) 보안 요청을 처리하는 메인 서버 블록입니다.
# -------------------------------------------------------------------
server {
    # 443번 포트(HTTPS)로 들어오는 요청을 대기하며, SSL을 사용하도록 설정합니다.
    listen 443 ssl;
    
    # 처리할 도메인 주소를 지정합니다. (위와 동일하게 실제 도메인으로 변경 필요)
    server_name _;

    # [중요] 발급받은 SSL 인증서(공용 키) 파일의 경로를 지정합니다. (Let's Encrypt 기준)
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    
    # [중요] 발급받은 SSL 인증서의 개인 키 파일 경로를 지정합니다.
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # 클라이언트가 /static/ 경로로 요청을 보낼 때 처리하는 블록입니다. (기본 프로필 이미지 서빙용)
    location /static/ {
        # Django를 거치지 않고, Nginx가 공유받은 볼륨 경로에서 직접 파일을 찾아 반환합니다.
        alias /app/staticfiles/;
    }

    # 정적 파일 외의 모든 일반적인 웹 요청(/)을 처리하는 블록입니다.
    location / {
        # 요청을 백엔드(Django Gunicorn) 컨테이너의 8000번 포트로 그대로 넘겨줍니다.
        proxy_pass http://backend:8000;
        
        # 원래 클라이언트가 요청한 도메인(Host) 정보를 백엔드에 전달합니다.
        proxy_set_header Host $host;
        
        # 클라이언트의 실제 방문자 IP 주소를 백엔드에 전달합니다.
        proxy_set_header X-Real-IP $remote_addr;
        
        # 여러 프록시를 거쳤을 경우의 IP 체인 정보를 백엔드에 전달합니다.
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # 백엔드(Django)가 현재 요청이 HTTP가 아닌 HTTPS로 안전하게 들어왔음을 알 수 있게 전달합니다.
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

docker-compose.prod.yml

  • Nginx 부분 수정

    • HTTPS 통신을 위해 443 포트를 열고
    • 호스트(EC2)에 저장된 SSL 인증서를 Nginx 컨테이너가 읽을 수 있도록 볼륨을 추가해야 함
    # 호스트(EC2)의 80번(HTTP) 포트로 들어오는 요청을 Nginx 컨테이너의 80번 포트로 연결합니다.
    ports:
      - "80:80"
    # 백엔드 컨테이너와 볼륨을 공유하기 위해 설정합니다.
    volumes:
      # 로컬에 작성할 Nginx 설정 파일을 컨테이너의 설정 파일로 덮어씌웁니다.
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      # 백엔드가 모아둔 정적 파일을 Nginx가 읽을 수 있도록 동일한 볼륨을 마운트합니다.
      - static_volume:/app/staticfiles
      
——————————————————————————————————————[비교]—————————————————————————————————————————
	ports:
      # 호스트의 80포트(HTTP)를 컨테이너 80포트로 연결합니다. (리다이렉트 용도)
      - "80:80"
      # 호스트의 443포트(HTTPS)를 컨테이너 443포트로 연결합니다. (실제 서비스 용도)
      - "443:443"
    # 백엔드 컨테이너 및 호스트와 파일/폴더를 공유하기 위해 설정합니다.
    volumes:
      # 로컬에 작성한 Nginx 설정 파일을 컨테이너의 기본 설정 파일로 덮어씌웁니다.
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      # 백엔드가 모아둔 정적 파일(기본 프로필 이미지)을 읽을 수 있도록 볼륨을 마운트합니다.
      - static_volume:/app/staticfiles
      # [추가됨] 호스트(EC2)에 설치된 Let's Encrypt 인증서 폴더를 Nginx가 읽기 전용(ro)으로 참조하도록 마운트합니다.
      - /etc/letsencrypt:/etc/letsencrypt:ro

진행 📌

AWS

전체적인 흐름

  • 1. EC2 인스턴스 생성 및 보안 그룹 설정 (가상 컴퓨터 대여)

    • AWS의 가상 서버인 EC2 인스턴스를 하나 대여
      • 운영체제(OS)

        • 보통 관리가 편한 Ubuntu 22.04 또는 24.04를 추천
      • 인스턴스 유형

        • 테스트 목적이라면 t2.micro (프리티어)로 충분하긴함
        • 하지만, Gunicorn 워커 3개와 DB, Nginx 를 동시에 돌리기엔
          • 램(1GB)이 부족해 멈출 수 있음
          • t3.small (램 2GB) 이상을 권장
      • 보안 그룹(방화벽)

        • 외부에서 서버로 들어올 수 있는 '문'을 열어줘야 함
          • 인바운드 규칙에 다음 3가지를 반드시 추가
            • 포트 80 (HTTP)
              • 위치 무관 (0.0.0.0/0)
            • 포트 443 (HTTPS)
              • 위치 무관 (0.0.0.0/0)
            • 포트 22 (SSH)
              • 내 IP
              • 보안을 위해 본인 컴퓨터에서만 원격 접속이 가능하도록 설정하는 것이 가장 좋음
  • 2. 탄력적 IP (Elastic IP) 할당 및 도메인 연결

    • EC2를 껐다 켜면 IP 주소가 바뀜

      • 이를 방지하기 위해 평생 바뀌지 않는 고정 IP를 발급받아야 함
    • 할당

      • AWS 메뉴에서 '탄력적 IP'를 발급받아 방금 만든 EC2에 연결(연결하지 않으면 과금됨)
    • 도메인 연결

      • 가비아, 호스팅케이알 등에서 구매
      • 구매한 도메인의 DNS 설정(A 레코드)에 이 탄력적 IP를 입력하여 도메인과 서버를 이어줌
  • 3. 서버 원격 접속 및 도커(Docker) 설치

    • 내 컴퓨터의 터미널(또는 PuTTY 등)을 이용해 다운로드한 키페어(.pem)로 EC2에 원격 접속
      • 우분투 서버에 접속한 뒤, 가장 먼저 DockerDocker Compose를 설치 필요
      • (AWS EC2에는 기본적으로 도커가 깔려있지 않음)
  • 4. 프로젝트 코드 가져오기 및 환경변수(.env) 설정

    • 이제 작성한 블로그 코드를 서버로 가져올 차례
      • Git Clone
        • Github 등에 올려둔 프로젝트를 git clone 명령어로 EC2 안에 다운로드
      • .env 생성
        • 깃허브에 올리지 않았던 .env 파일을 서버에 직접 만들어야 함
        • vi .env 명령어로 파일을 열고
          • Django Secret Key , DB 비밀번호, S3 키 등을 꼼꼼히 채워 넣음
  • 5. HTTPS(SSL) 인증서 발급 (★매우 중요)

    • 이전 단계에서 작성한 docker-compose.prod.yml
    • Nginx는 호스트(EC2)의 /etc/letsencrypt 경로에 인증서가 있다는 전제하에 동작
      • Certbot 실행
        • 도커를 켜기 전에, EC2 터미널에서 Certbot을 설치하고
          • 도메인에 대한 Let's Encrypt 인증서를 발급받아야 함
          • 이 과정이 완료되어야 폴더가 생성되고 Nginx 컨테이너가 정상적으로 켜짐
  • 6. 대망의 Docker Compose 실행 (배포)

    • 모든 준비가 끝
    • 프로젝트 폴더로 이동하여 아래 명령어를 실행하면 컨테이너들이 백그라운드에서 빌드되고 실행됨
    • sudo docker compose -f docker-compose.prod.yml up --build -d

진행(이미지) 📌

튜토리얼

  • 이름 및 태그 / 애플리케이션 및 OS 이미지

  • 인스턴스 유형 / 키 페어(로그인)

  • 네트워크 설정

  • 방화벽(보안 그룹)

  • 스토리지 구성


EC2 인스턴스 생성

  • AWS 콘솔 로그인

    • AWS 사이트에 로그인한 뒤, 상단 검색창에 EC2를 검색해서 클릭
  • 인스턴스 시작

    • 화면 중간에 있는 주황색 [인스턴스 시작] 버튼을 누름
  • 이름 및 운영체제(OS) 설정

    • 이름
      • blog-server-prod 등 알아보기 쉬운 이름으로 적어주기
    • OS (Amazon Machine Image)
      • Ubuntu(우분투)를 클릭하고
      • 버전은 Ubuntu 24.04 LTS 또는 22.04 LTS를 선택 (가장 대중적이고 자료가 많음)
  • 인스턴스 유형 (컴퓨터 사양)

    • 처음엔 무료인 t3.micro를 선택
    • 나중에 램이 부족해서 서버가 멈추면 그때 t3.small 등으로 올려도 됨
  • 키 페어 (로그인 열쇠) - ★매우 중요

    • [새 키 페어 생성]을 누름
    • 이름은 blog-key 등으로 적고, 형식은 .pem으로 설정한 뒤 [키 페어 생성]을 누름
    • 주의
      • .pem 파일은 컴퓨터에 다운로드되는데 서버에 접속할 수 있는 '마스터 키'
      • 절대 잃어버리거나 깃허브에 올리지 마시고 안전한 폴더에 보관 필요
  • 네트워크 설정 (방화벽 열어주기)

    • 경우 1. 간단한 체크박스가 보이는 경우 (기본 화면)
      • SSH 트래픽 허용
        • 체크되어 있는지 확인하고, 우측 드롭다운 메뉴를 '내 IP'로 변경
      • 인터넷에서 HTTP 트래픽 허용: 체크
      • 인터넷에서 HTTPS 트래픽 허용: 체크
  • 경우 2. 우측 상단의 [편집]을 눌러 상세 화면으로 들어간 경우

    • 말씀하신 [보안 그룹 규칙 추가] 버튼을 눌러서 직접 아래의 3가지 규칙을 만들어 줌
      • 유형: SSH / 소스 유형: 내 IP
      • 유형: HTTP / 소스 유형: 위치 무관(Anywhere) (자동으로 0.0.0.0/0이 입력됨)
      • 유형: HTTPS / 소스 유형: 위치 무관(Anywhere) (자동으로 0.0.0.0/0이 입력됨)
  • 스토리지 (하드디스크 용량)

    • 기본 8GB, 프리티어(무료)는 최대 30GB까지 가능
    • 넉넉하게 30GB로 숫자를 바꿔주기
  • [인스턴스 시작] 클릭

    • 오른쪽 아래 주황색 버튼을 누르면 나만의 서버가 생성완료
  • 완성


탄력적 IP

  • 평생 안 바뀌는 주소 부여하기

  • EC2는 컴퓨터를 껐다 켜면 IP 주소가 바뀜, 유저들이 찾아올 수 있도록 고정된 IP 주소를 달아줘야 함
    • EC2 화면 왼쪽 메뉴 탭에서 주욱 내려서 [네트워크 및 보안] -> [탄력적 IP]를 클릭
    • 오른쪽 위 [탄력적 IP 주소 할당]을 누르고
      • 바로 다음 화면에서 우측 하단 [할당]을 누름 (새로운 고정 IP가 생김)
    • 방금 생성된 IP를 체크하고, 오른쪽 위 [작업] -> [탄력적 IP 주소 연결]을 누름
    • 인스턴스 칸을 클릭하면 방금 1단계에서 만든 내 서버(blog-server-prod)가 뜸, 그걸 선택
    • [연결]을 누르면 끝
      • 이제 이 IP 주소가 블로그의 영구적인 접속 주소가 됨
  • 주의
    • 탄력적 IP는 만들어놓고 EC2에 '연결'하지 않으면 소액의 요금이 발생하니 꼭 바로 연결 필요

도메인 구매

  • 가비아

  • 사이트 접속 및 가입

    • 원하시는 도메인 사이트(가비아 등)에 접속하여 회원가입을 진행
  • 원하는 주소 검색

    • 메인 화면의 커다란 검색창에 만들고 싶은 블로그 주소(예: kihoon-blog)를 영어로 검색
  • 도메인 확장자 선택

    • 검색 결과에 .com, .net, .kr 등 다양한 확장자와 1년 단위의 가격이 나옵니다.
  • 💡 팁

    • 가장 대중적이고 좋은 것은 .com이나 .co.kr이지만 (보통 1년에 1.5만 원 ~ 2만 원 선)
    • 배포 및 테스트가 주 목적이라면 .xyz, .site, .shop 처럼
    • 첫해 이벤트로 500원 ~ 2,000원에 살 수 있는 아주 저렴한 도메인을 추천
  • 장바구니 및 결제

    • 마음에 드는 도메인과 기간(보통 1년)을 선택하고 결제를 진행
  • 결제창

    • 네임서버
      • 가비아 홈페이지 안에서 무료로 DNS(A 레코드 등)를 설정하겠다는 뜻
      • 화면이 모두 한글이고 직관적이라 초보자가 관리하기 훨씬 쉽고, 추가 비용이 전혀 들지 않음
    • 타사 네임서버 사용
      • 이건 AWS의 'Route 53'이나 '클라우드플레어(Cloudflare)' 같은
        • 전문 네임서버 관리 서비스로 권한을 넘길 때 씀
        • (AWS Route 53을 쓰면 한 달에 약 700원 정도의 유지비가 추가로 발생)

결제 후

  • 가비아 메인 화면 우측 상단의 [My가비아]를 클릭
  • 서비스 관리 화면에서 방금 구매한 도메인 옆의 [관리] 버튼을 누름
  • 스크롤을 살짝 내려서 [DNS 관리] 또는 [DNS 설정] 메뉴로 들어감
  • 방금 산 도메인을 체크하고 [DNS 설정] 버튼을 누른 뒤,
    • [레코드 추가]를 클릭하여 아래처럼 입력
      • 타입: A
      • 호스트: @
      • 값/위치: AWS에서 복사해 둔 탄력적 IP 주소 숫자 입력
      • TTL: 3600 (기본값 그대로)
  • [확인][저장]을 누르면 끝
  • 선택사항
    • 호스트에 www라고 적고 똑같이 하나 더 추가해 두면
      • 사람들이 www.coding-garden.site 라고 쳐도 잘 들어오게 됨

서버 터미널

AWS 서버(EC2)에 원격 접속하기

  • Mac 기준

    • 터미널(Terminal) 앱을 킴
    • 키 페어(.pem파일)가 있는 폴더로 이동 (다운로드 폴더에 있다면 아래 명령어 입력 후 엔터)
      • cd ~/Downloads
    • 키 파일의 보안 권한을 낮춰주는 명령어(필수)를 입력
      • 파일 이름(blog-key.pem)은 본인 파일명에 맞게 변경
      • chmod 400 blog-key.pem
    • 마지막으로 서버에 접속하는 명령어를 입력 (본인의 탄력적 IP 주소로 바꿈)
      • ssh -i "blog-key.pem" ubuntu@탄력적IP주소
  • 명령어를 치고 나면 영어로
    • Are you sure you want to continue connecting (yes/no)? 라고 물어보는 문구가 뜸
    • yes 라고 타이핑한 후 엔터
  • 입력창 왼쪽에 내 컴퓨터 이름 대신 초록색 글씨로
    • ubuntu@ip-172-31-... 같은 글자가 떴다면 완벽하게 접속에 성공

  • 현재 접속해 있는 t3.micro 서버는 깨끗한 '새 컴퓨터' 상태

    • 이제 여기에 프로젝트를 실행해 줄 도커(Docker)를 설치하고
    • 램(RAM) 부족으로 서버가 뻗는 것을 방지하기 위한 스왑 메모리(가상 램) 최적화 작업을 진행

도커(Docker) 및 도커 컴포즈 설치

  • 우분투 서버에 가장 안전하고 최신 버전의 도커를 공식 저장소에서 받아 설치하는 과정
# 1. 우분투의 패키지(앱) 목록을 최신 상태로 업데이트
sudo apt-get update

# 2. 도커를 설치할 때 통신에 필요한 필수 시스템 패키지들을 설치 (-y는 묻지 않고 설치하겠다는 뜻)
sudo apt-get install -y ca-certificates curl gnupg lsb-release

# 3. 안전한 다운로드를 위해 도커의 공식 GPG 키(인증서)를 서버의 안전한 폴더에 저장
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 4. 도커 공식 다운로드 주소(저장소)를 우분투 서버가 알 수 있도록 리스트에 추가
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. 새로운 저장소가 추가되었으니 패키지 목록을 다시 한번 업데이트
sudo apt-get update

# 6. 대망의 도커 엔진과 도커 컴포즈 최신 버전을 서버에 설치 (시간이 조금 걸림)
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
  • 매번 sudo(관리자 권한)를 치지 않고도 편하게 도커를 쓸 수 있도록 권한 주기
# 현재 접속 중인 'ubuntu' 유저를 'docker' 그룹에 포함시킵니다.
sudo usermod -aG docker $USER

# [중요] 방금 부여한 권한을 적용하려면 서버에서 한 번 나갔다가 다시 접속해야 합니다!
# exit을 치면 터미널 창이 내 컴퓨터로 빠져나옵니다.
exit
  • ssh -i "blog-key.pem" ubuntu@탄력적IP 이용하여 재접속

서버 다운 방지! 스왑 메모리(가상 램) 설정

  • 최적화 포인트

    • t3.micro는 램이 1GB밖에 없어서
      • 나중에 무거운 DB와 Django를 한 번에 켤 때 램 부족으로 컴퓨터가 멈춰버릴 수 있음
      • 하드디스크(SSD)의 2GB를 램처럼 쓰게 속이는 최고의 가성비 최적화 작업
# 1. 하드디스크에서 2기가바이트(2G) 만큼의 공간을 떼어내어 '/swapfile'이라는 빈 파일을 만듬
sudo fallocate -l 2G /swapfile

# 2. 보안을 위해 이 가상 램 파일은 관리자(root)만 읽고 쓸 수 있도록 권한을 제한
sudo chmod 600 /swapfile

# 3. 방금 만든 빈 파일을 시스템이 인식할 수 있는 '스왑 메모리 공간'으로 포맷(초기화)
sudo mkswap /swapfile

# 4. 포맷된 스왑 메모리를 시스템에 즉시 활성화하여 적용
sudo swapon /swapfile

# 5. 서버를 껐다 켜도 스왑 메모리가 유지되도록 시스템 설정 파일(fstab)의 맨 아랫줄에 내용을 추가
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
  • 모든 명령어 입력이 끝났다면
    • 터미널에 docker compose version 이라고 쳐보자
    • 화면에 Docker Compose version v2... 라고 뜨면 성공

코드 가져오기 및 SSL(HTTPS) 인증서 발급

  • 깃허브(Github)에서 내 프로젝트 코드 가져오기

# 1. 깃허브에서 내 프로젝트 코드를 서버로 다운로드(복제) 합니다. 
# (주의: 아래 주소를 본인의 실제 깃허브 레포지토리 주소로 꼭 바꿔서 입력해 주세요!)
git clone https://github.com/본인아이디/프로젝트이름.git

# 2. 다운로드가 완료되면, 방금 생성된 프로젝트 폴더 안으로 이동합니다.
# (본인의 프로젝트 폴더 이름으로 바꿔주세요!)
cd 프로젝트이름
  • 환경변수(.env) 파일 서버에 직접 만들기

# 1. 현재 폴더에 '.env'라는 이름의 텍스트 파일을 만들고, 편집기를 엽니다.
nano .env
  • 나노(nano) 에디터 사용법

    • 까만 화면이 열리면, 로컬 컴퓨터(내 PC)에 있던
      • .env 파일의 내용(Django Secret Key, DB 비밀번호, S3 정보 등)을 모두 복사해서
      • 터미널 창에 그대로 붙여넣기(우클릭) 함
    • 저장을 위해 키보드에서 Ctrl + O (알파벳 O)를 누름
    • 파일 이름 확인 창이 뜨면 Enter 를 침
    • 에디터를 빠져나오기 위해 Ctrl + X 를 누름
  • 자물쇠(HTTPS) 달기 (Certbot SSL 인증서 사전 발급)

    • 어디서 하든 상관은 없음

      • 우분투 서버라는 큰 '컴퓨터 전체(전역)'에서 쓸 수 있도록 설치되기 때문에
    • 아까 Nginx 설정 코드에서 /etc/letsencrypt/... 경로에 있는 인증서를 읽어오도록 코드 작성
    • 따라서 도커를 켜기 전에 반드시 인증서를 먼저 발급받아 두어야만 에러가 나지 않음
# 1. Certbot을 설치하기 위해 우분투의 앱스토어격인 snapd를 최신 상태로 업데이트합니다.
sudo snap install core; sudo snap refresh core

# 2. SSL 인증서를 발급해 줄 Certbot 프로그램을 설치합니다.
sudo snap install --classic certbot

# 3. Certbot 명령어를 서버 어디서든 쉽게 쓸 수 있도록 바로가기(심볼릭 링크)를 만듭니다.
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# 4. [★매우 중요★] 아직 Nginx 웹 서버가 켜지기 전이므로, 내장된 임시 웹서버(standalone)를 이용해
# 아까 구매하신 도메인(coding-garden.site)에 대한 인증서를 발급받습니다.
sudo certbot certonly --standalone -d coding-garden.site
  • 4번 명령어를 치면 나타나는 화면 대처법

    • 이메일 주소 입력
      • 본인의 진짜 이메일 주소를 적고 Enter를 칩니다. (인증서 만료 전 알림이 옵니다.)
    • 약관 동의 (A/C)
      • A (Agree)를 누르고 Enter를 칩니다.
    • 이메일 마케팅 동의 (Y/N)
      - N (No)를 누르고 Enter를 칩니다.

진짜 배포(도커 실행)

  • 최종 배포 명령어

# -f : 실행할 도커 설정 파일을 지정합니다.
# up : 컨테이너들을 켭니다.
# --build : 켜기 전에 도커 이미지를 최신 코드로 새로 굽습니다(빌드).
# -d : 백그라운드(데몬) 모드로 실행하여 터미널 창을 꺼도 서버가 계속 돌아가게 합니다.
sudo docker compose -f docker-compose.prod.yml up --build -d
  • 실시간 로그(블랙박스)

# 켜진 컨테이너들의 실시간 로그를 확인하는 명령어입니다. (빠져나올 때는 Ctrl + C)
sudo docker compose -f docker-compose.prod.yml logs -f

문제

psycopg.OperationalError: connection failed: 
connection to server at "127.0.0.1", port 5432 failed: Connection refused
  • 현상
    • Django가 데이터베이스를 찾을 때 127.0.0.1 (자기 자신)에서 찾으려고 시도하고 있음
  • 이유
    • 호스트가 localhost 였음
    • 도커 환경에서는 각 컨테이너가 독립된 가상 컴퓨터와 같음
    • 따라서 Django 입장에서 127.0.0.1은 DB 컨테이너가 아니라 자기 자신을 의미
  • 해결책
    • Django의 settings.py (또는 .env) 설정에서
      • 데이터베이스의 HOST(호스트) 주소를 127.0.0.1이나 localhost가 아닌
      • 도커 컴포즈에 정의된 서비스 이름인 db로 바꿔야 합니다.
cannot load certificate "/etc/letsencrypt/live/yourdomain.com/fullchain.pem": No such file or directory
  • 현상
    • Nginx 설정 파일에 경로가 yourdomain.com으로 되어 있어
    • 실제 발급받은 coding-garden.site 인증서를 찾지 못하고 있습니다.
  • 해결
    • Nginx 설정 파일 수정
# Nginx 설정 파일을 엽니다. (경로가 다를 경우 실제 경로로 이동하세요)
nano nginx/nginx.conf
  • 도메인 주소 수정

    • 파일 내용 중에서 yourdomain.com이라고 되어 있는 부분을 모두 coding-garden.site로 수정
  • 배포 끝


도커

# 실행 중인 컨테이너와 네트워크를 모두 중지하고 삭제합니다.
sudo docker compose -f docker-compose.prod.yml down

# 수정된 설정값을 가지고 다시 빌드하여 실행합니다.
sudo docker compose -f docker-compose.prod.yml up --build -d

하면 좋은 일

코드 수정 후 자동 배포(CI/CD)

  • "내가 브랜치에 push 하면, 네가 내 대신 AWS 서버에 접속해서 도커 업데이트 명령어 좀 쳐줘!"
    • 1단계: 깃허브에 AWS 접속 열쇠(Secret) 맡기기

      • Settings(설정) -> Secrets(비밀 금고)
        • 깃허브 프로젝트(레포지토리) 페이지로 들어갑니다.
        • 상단 탭에서 [Settings] 를 누릅니다.
        • 왼쪽 사이드 메뉴에서 [Secrets and variables] -> [Actions]를 클릭합니다.
        • 초록색 [New repository secret] 버튼을 눌러서 아래 3개를 각각 만들어주세요.
          • Name: HOST / Secret: 복사한 IP
          • Name: USERNAME / Secret: ubuntu
          • Name: KEY / Secret: 복사한 아주 긴 키 텍스트
            • 키 파일이 있는 폴더로 이동하고 cat 본인키이름.pem 사용
      • 내 서버의 IP, 유저 이름(ubuntu), 그리고 .pem 파일의 안의 긴 텍스트 내용을 안전하게 저장
        • HOST (서버 주소)
          • AWS 탄력적 IP 주소(숫자)(예: 3.33.44.55 같은 숫자)
        • USERNAME (서버 접속 아이디)
          • 이건 그냥 ubuntu
          • 아까 맥북 터미널에서 서버에 들어갈 때 ssh -i 키.pem ubuntu@IP주소 라고 쳤음
          • 그때 쓴 기본 관리자 아이디가 바로 ubuntu
        • KEY (프라이빗 키 내용)
          • 이게 핵심
          • 파일 업로드가 아니라, .pem 파일 안에 적힌 암호화된 긴 텍스트 전체를 복사해서 붙여넣기
    • 2단계: 자동화 스크립트(.yml) 작성하기

      • prune은 서버 용량이 꽉 차지 않도록 안 쓰는 도커 찌꺼기를 자동 청소용 명령어
name: Deploy to EC2

# 어떤 상황일 때 이 스크립트를 실행할지 정합니다.
on:
  push:
    branches: [ "main" ] # main 브랜치에 코드가 push(또는 merge) 될 때만 작동합니다.

jobs:
  deploy:
    runs-on: ubuntu-latest # 깃허브가 빌려주는 임시 컴퓨터(우분투)에서 실행됩니다.
    
    steps:
      - name: SSH로 EC2에 접속해서 배포 명령어 실행하기
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          # 아래 script 부분이 깃허브 로봇이 서버에 접속해서 대신 쳐줄 명령어들입니다.
          script: |
            cd ~/indi_Blog_Project
            git pull origin main
            sudo docker compose -f docker-compose.prod.yml down
            sudo docker compose -f docker-compose.prod.yml up --build -d
            sudo docker system prune -f
  • AWS의 '보안 그룹(방화벽)' 설정

    • AWS 홈페이지에 로그인하고 EC2 대시보드로 들어갑니다.
    • 왼쪽 메뉴에서 [네트워크 및 보안] -> [보안 그룹]을 클릭합니다.
    • 현재 블로그 인스턴스에 연결되어 있는 보안 그룹을 클릭합니다.
      • (보통 이름이 launch-wizard-1 등이거나 직접 만드신 이름일 겁니다.)
    • 화면 아래쪽 탭에서 [인바운드 규칙] -> [인바운드 규칙 편집]을 누릅니다.
    • 유형이 SSH (포트 22)인 줄을 찾습니다.
    • 소스(Source) 설정 부분을 [위치 무관 (Anywhere-IPv4)] (또는 0.0.0.0/0)으로 변경
    • [규칙 저장]을 누릅니다.

소셜로그인 해결

http://127.0.0.1:8000/api/v1/user/login-page/?code=07dfc64f122e47b76db1
  • 배포환경에서 소셜로그인 누르면 위의 주소로 넘어가면서 소셜로그인이 진행이 안됨
    • 코드 내에 콜백 주소(redirect_uri)가 로컬호스트 주소로 하드코딩(고정)되어 있기 때문
class GithubLoginAPIView(APIView):
    permission_classes = [AllowAny]

    def get(self, request):
        client_id = settings.GITHUB_CLIENT_ID
		redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/"
				...
——————————————————————————————————————[비교]—————————————————————————————————————————
from django.urls import reverse

class GithubLoginAPIView(APIView):
    permission_classes = [AllowAny]

    def get(self, request):
        client_id = settings.GITHUB_CLIENT_ID

      # 2. reverse로 'login_page'의 경로를 찾고, build_absolute_uri로 현재 도메인이 포함된 절대 경로를 동적으로 생성합니다.
      # 이렇게 하면 로컬/배포 환경에 상관없이 현재 서버의 도메인이 올바르게 반영됩니다.
      redirect_uri = request.build_absolute_uri(reverse('login_page'))
    def get(self, request):
        client_id = settings.DISCORD_CLIENT_ID

        # 프론트엔드 로그인 페이지로 돌아가도록 설정
        redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/?provider=discord"

        # URL에 들어갈 수 있도록 특수문자(:, /, ? 등)를 안전하게 인코딩합니다.
        encoded_redirect_uri = urllib.parse.quote(redirect_uri)

——————————————————————————————————————[비교]—————————————————————————————————————————
class DiscordLoginAPIView(APIView):
    # 누구나 접근 가능한 뷰입니다.
    permission_classes = [AllowAny]

    def get(self, request):
        # 디스코드 클라이언트 ID를 가져옵니다.
        client_id = settings.DISCORD_CLIENT_ID

        # 1. 기본 로그인 페이지 URL을 현재 호스트에 맞춰 동적으로 생성합니다.
        base_redirect_uri = request.build_absolute_uri(reverse('login_page'))
        
        # 2. 디스코드 콜백임을 식별하기 위해 쿼리 파라미터를 덧붙입니다.
        redirect_uri = f"{base_redirect_uri}?provider=discord"

        # 3. URL에 들어갈 수 있도록 특수문자(:, /, ? 등)를 안전하게 인코딩합니다.
        encoded_redirect_uri = urllib.parse.quote(redirect_uri)
    def post(self, request):
        # GET.get이 아닌 data.get으로 body에 담긴 JSON 데이터를 꺼냅니다.
        code = request.data.get("code")

        # 토큰을 요청할 때도 위(APIView)에서 적었던 주소와 완벽히 똑같아야 합니다!
        redirect_uri = "http://127.0.0.1:8000/api/v1/user/login-page/?provider=discord"

——————————————————————————————————————[비교]—————————————————————————————————————————
def post(self, request):
        # 인가 코드를 가져옵니다.
        code = request.data.get("code")

        # 1. 토큰 요청 시에도 권한 요청 시 보냈던 완벽히 동일한 redirect_uri를 전달해야 합니다.
        # 따라서 현재 환경에 맞는 동적 URL을 다시 한번 만들어 줍니다.
        base_redirect_uri = request.build_absolute_uri(reverse('login_page'))
        redirect_uri = f"{base_redirect_uri}?provider=discord"
  • 개발자 콘솔 도메인 수정

    GitHub: Developer Settings -> OAuth Apps -> Authorization callback URL 수정
    Discord: Developer Portal -> Applications -> OAuth2 -> Redirects 수정

  • request.build_absolute_uri()

    • 장고(Django)가 제공하는 매우 유용한 기능
    • "현재 유저가 어떤 도메인 주소를 치고 우리 서버에 들어왔는지"를 스스로 파악하여 URL을 완성해줌
      • 개발할 때 로컬 환경에서 테스트하면
        • http://127.0.0.1:8000/api/v1/user/login-page/ 로 자동 생성
      • 배포 후 https://my-blog.com으로 유저가 접속하면
        • https://my-blog.com/api/v1/user/login-page/ 로 자동 생성

서버 닫기

순서

  • 데이터 백업
  • 인스턴스 종료
  • 탄력적 IP 릴리스
# 1. AMI(Amazon Machine Image) 생성
# 이유: 현재 블로그 설정과 데이터를 이미지로 구워두면, 
# 새 계정에서 이 이미지를 공유받아 그대로 복구할 수 있음

# 2. 인스턴스 종료 (Terminate)
# 이유: '중지'가 아닌 '종료'를 해야 연결된 EBS 볼륨(디스크)이 함께 삭제되어 
# 저장 공간에 대한 추가 과금을 막을 수 있음

# 3. 탄력적 IP 주소 릴리스 (Release Elastic IP addresses)
# 이유: 인스턴스를 종료해도 IP는 '내 계정 소유'로 남습니다. 
# 주인이 없는 IP는 AWS가 시간당 비용을 페널티처럼 부과하므로 반드시 '릴리스'해야 함

# 4. 탄력적 IP 릴리스 확인 (Final Check)
# 이유: 리전(Region)별로 혹시 남겨둔 IP가 없는지 'EC2 대시보드'에서 
# 'Elastic IPs' 항목이 0인지 반드시 확인해야 함

실행

[사전 준비] 데이터 백업 (AMI 생성)

  • EC2 콘솔에서 해당 인스턴스 선택
  • 작업(Actions) -> 이미지 및 템플릿 -> 이미지 생성 클릭
  • 이미지 이름을 지정하고 이미지 생성 클릭 (약 5~10분 소요)

EC2 인스턴스 완전히 종료하기

  • AWS 관리 콘솔에 로그인
  • 상단 검색창에 EC2를 입력하고 서비스로 이동
  • 왼쪽 메뉴에서 인스턴스(Instances)를 클릭
  • 종료할 인스턴스 체크박스를 선택
  • 오른쪽 상단 인스턴스 상태 버튼을 누르고 인스턴스 종료(Terminate instance)를 클릭
  • 확인 창이 뜨면 다시 한번 종료를 누릅니다.
    • Shutting-down을 거쳐 Terminated로 바뀌면 성공

탄력적 IP 반납하기

  • EC2 페이지 왼쪽 메뉴를 아래로 내려 네트워크 및 보안 섹션의 탄력적 IP(Elastic IPs)를 클릭
  • 사용 중인 IP 주소 체크박스를 선택
  • 오른쪽 상단 작업(Actions) 버튼을 누름
  • 탄력적 IP 주소 릴리스(Release Elastic IP addresses)를 클릭
  • 만약 '연결 해제'가 먼저 필요하다고 나오면
    • 탄력적 IP 주소 연결 해제를 먼저 진행한 후 릴리스를 하면 됨
  • 확인 창에서 릴리스를 누름 (목록이 비어있어야 안전)

재연결

해야하는 일

  • 도메인 연결 변경
    • 새 계정에서 새 탄력적 IP를 발급받으면
    • 가비아나 Route53 같은 곳에서 도메인의 A 레코드 값을 새 IP로 업데이트해야 함
  • 설정 파일 수정
    • 코드나 DB 설정 파일 안에 혹시 기존 IP 주소가 하드코딩 되어 있다면
    • 이를 도메인 주소나 새 IP로 바꿔줘야 함
profile
안녕하세요.

0개의 댓글