2026/03/15 Blog - 25

김기훈·2026년 3월 15일

TIL

목록 보기
165/194
post-thumbnail

코딩테스트(11723)


코드 수정

Ai 라이브러리

All support for the 'google.generativeai' package has ended. It will no longer be receiving updates or bug fixes. Please switch to the 'google.genai' package as soon as possible.
See README for more details:

이유

  • google.generativeai 라이브러리의 지원이 종료됨

    • 새로운 공식 SDK인 google.genai로 라이브러리를 변경하라는것

라이브러리 전환

  • pip uninstall google-generativeai
  • pip install google-genai

코드 수정

  • 새로운 SDK는 전역 공간에서 API 키를 설정하던 방식(genai.configure) 대신
    • Client 객체를 직접 생성하여 사용하는 구조로 변경되었음
    • 이는 여러 API 키를 관리하거나 멀티스레드 환경의 서버를 운영할 때 상태 충돌을 방지하는
      • 안전하고 최적화된 설계
  • 스트리밍 요청을 보낼 때 generate_content_stream 이라는 전용 메서드가 추가되었음
    • 모델명 역시 기존 gemini-flash-latest 대신 가장 최신의 권장 모델인
      • gemini-2.5-flash를 사용하는 것이 성능과 안정성 면에서 최선의 선택
  • import 수정

import google.generativeai as genai

——————————————————————————————————————[비교]—————————————————————————————————————————
from google import genai
# 새로운 SDK에서 모델 세부 설정(온도, 토큰 등)을 제어하기 위해 types 모듈을 가져오기
from google.genai import types
  • 전역설정 삭제 및 client 추가

# logger 아래 
genai.configure(api_key=settings.GEMINI_API_KEY)

——————————————————————————————————————[비교]—————————————————————————————————————————
# 함수 내부
client = genai.Client(api_key=settings.GEMINI_API_KEY)
        # 2. Gemini 모델 인스턴스를 생성합니다.
        model = genai.GenerativeModel(
            # 빠르고 가벼운 flash 모델을 사용
            model_name="gemini-flash-latest",
            # 앞에서 찾은 시스템 프롬프트를 모델의 기본 지시사항으로 주입
            system_instruction=system_prompt,
        )

        # 3. 스트리밍 모드로 텍스트 생성 요청
        response = model.generate_content(
            # 변환할 원본 텍스트를 첫 번째 인자로 전달
            text,
            # 스트리밍 옵션을 켜서, 생성 즉시 응답을 받도록 설정
            stream=True,
            # 결과물 생성을 위한 세부 설정값을 전달
            generation_config=genai.types.GenerationConfig(
                # 자연스럽고 적절한 변환을 위해 창의성 정도(온도)를 0.7로 설정
                temperature=0.7,
                # 블로그 글이 잘리지 않도록 넉넉하게 토큰 제한을 2500으로 제한
                max_output_tokens=2500,
            ),
        )
——————————————————————————————————————[비교]—————————————————————————————————————————
		# 2. 새로운 google.genai의 Client 인스턴스를 생성하면서 환경변수(settings)의 API 키를 주입
        client = genai.Client(api_key=settings.GEMINI_API_KEY)

        # 3. GenerativeModel 인스턴스를 따로 만들지 않고, Client를 통해 바로 스트리밍 생성을 요청
        response = client.models.generate_content_stream(
            # 속도와 성능이 모두 뛰어난 최신의 gemini-2.5-flash 모델을 명시적으로 지정
            model="gemini-2.5-flash",
            # 사용자가 블로그 글로 변환하고자 하는 원본 텍스트를 전달
            contents=text,
            # 4. 모델의 지시사항, 온도 등의 세부 옵션은 GenerateContentConfig 객체에 묶어서 전달
            config=types.GenerateContentConfig(
                # 앞에서 찾은 시스템 프롬프트를 모델의 기본 지시사항으로 주입
                system_instruction=system_prompt,
                # 자연스럽고 적절한 변환을 위해 창의성 정도(온도)를 0.7로 설정
                temperature=0.7,
                # 블로그 글이 잘리지 않도록 넉넉하게 토큰 제한을 2500으로 제한
                max_output_tokens=2500,
            )
        )

배포

고민

  • 기능 최적화 / 쿼리 최적화 등등을 배포후에 진행할까 전에 진행할까

선배포 후 수정

  • 이유

      1. 실제 병목 현상(Bottleneck) 파악
      • 개발 환경에서 예측한 성능 저하 포인트와 실제 사용자들이 접속했을 때 발생하는
        • 병목 구간은 전혀 다른 경우가 많음
      • 먼저 배포를 하고 AWS CloudWatch 등의 모니터링 도구를 연결하면
        • 어떤 API나 쿼리에서 실제로 지연이 발생하는지 정확한 데이터를 얻을 수 있음
        • 데이터에 기반한 최적화가 훨씬 효율적
      1. 성급한 최적화(Premature Optimization) 방지
      • "성급한 최적화는 모든 악의 근원이다"라는 개발 격언이 있음
      • 아직 발생하지 않은 트래픽이나 성능 문제를 미리 걱정하며 코드를 복잡하게 만들면
        • 오히려 기능 개발 속도가 느려지고 유지보수가 어려워질 수 있음
      1. 배포 파이프라인 조기 구축
      • 배포 자체도 많은 변수가 발생하는 까다로운 작업
        • 기능이 비교적 단순할 때 배포 환경(CI/CD)을 미리 구축해 두면
        • 이후 최적화를 진행하고 코드를 수정할 때마다 버튼 하나로 쉽게 서버에 반영할 수 있어
          • 개발 피드백 루프가 훨씬 빨라짐

인프라

  • 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 명령어 한 줄만 입력하면
      • 작성해 둔 설정에 따라 컨테이너들이 백그라운드에서 실행되며 배포가 완료

개선

  • 현재 static파일에서 프로필 기본이미지를 넘겨주고 있는데 이걸 S3로 전환하는게 좋을까?
    • 파일에 성격에 따라 S3로 옮기지 않고 지금처럼 Static 폴더에 두고 Nginx가 처리하도록 놔두기
  • 파일의 성격 (Static vs Media)

    • Media 파일 (S3 적합)
      • 사용자가 업로드하는 파일, 언제 생성될지, 몇 개나 생성될지 예측할 수 없음
    • Static 파일 (Nginx 적합)
      • 개발자가 코드와 함께 배포하는 파일
        • 로고, 아이콘, 그리고 '기본 프로필 이미지'가 여기에 속함
        • 앱의 디자인적인 요소이므로 Static 파일로 분류하는 것이 아키텍처상 올바름
  • 비용과 응답 속도
    • 사용자가 게시글 목록을 볼 때 댓글이나 작성자 프로필에 '기본 이미지'가 수십 개씩 뜰 수 있음
    • 이걸 S3에서 불러오면 S3에 계속 네트워크 요청을 보내야 하므로
      • 미세한 지연(Latency)이 발생하고 비용도 (아주 적지만) 청구됨
    • 반면 로컬 Nginx가 처리하면 네트워크 통신 없이 서버 내부에서 즉시 꺼내주므로 훨씬 빠르고 무료

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 인증서를 발급받고 적용
          • 자물쇠 마크 생성

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 = ["*"]

——————————————————————————————————————[비교]—————————————————————————————————————————
# 환경 변수에서 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

nginx

  • Nginx(엔진엑스)는 가볍고 높은 성능을 자랑하는 웹 서버(Web Server)이자
    • 리버스 프록시(Reverse Proxy) 서버 프로그램

Nginx의 핵심 이론

  • 어떻게 압도적인 성능을 내는가?

    • Nginx가 전 세계적으로 가장 많이 쓰이는 웹 서버가 된 이유는
      • '이벤트 기반(Event-Driven)'의 비동기(Asynchronous) 아키텍처를 사용하기 때문
    • 기존 방식 (Apache 등)

      • 클라이언트의 요청이 들어올 때마다 새로운 프로세스나 스레드를 생성하는
        • '스레드 기반(Thread-Driven)' 모델이었음
      • 동시 접속자가 많아지면 스레드가 무한정 생성되어 메모리가 고갈되고
        • 서버가 뻗어버리는 문제(C10K 문제)가 발생하기 쉬움
    • Nginx 방식

      • 소수의 고정된 워커 프로세스(Worker Process)만 띄워놓고
        • 들어오는 수많은 요청들을 '이벤트'로 취급하여 비동기 방식으로 처리
      • 하나의 요청이 디스크 I/O 등으로 대기 상태에 들어가면 프로세스가 쉬지 않고
        • 즉시 다른 이벤트를 처리함, 덕분에 적은 메모리로 수만 개의 동시 접속을 가볍게 처리할 수 있음

배포 중에 Nginx가 꼭 필요한 이유

  • 웹 애플리케이션(Spring Boot, Node.js, Django 등)을 만들고 나면
    • 그 자체로도 서버를 띄울 수는 있음
      • 하지만 실제 서비스 환경에 배포할 때는 반드시 그 앞에 Nginx를 두는 것이 표준 명세처럼 자리 잡았음
  • 그 이유는 다음과 같습니다

    • ① 리버스 프록시 (Reverse Proxy) 역할을 통한 보안 및 포트 숨김
      • Nginx를 인터넷 망과 내부 백엔드 서버(앱 서버) 사이에 둠
        • 보안 강화
          • 사용자는 내부 서버의 진짜 IP나 실행 중인 포트(ex. 3000, 8080)를 알 수 없음
          • Nginx가 80(HTTP) 또는 443(HTTPS) 포트로만 요청을 받아
            • 내부망에 있는 앱 서버로 몰래 전달(Proxy)해주기 때문에
            • 백엔드 서버가 해커에게 직접 노출되는 것을 막아줌
    • ② 정적 파일(Static Content)의 효율적인 분리 처리
      • HTML, CSS, JavaScript, 이미지 파일 같은
        • 정적 리소스는 굳이 무거운 백엔드 애플리케이션 서버가 처리할 필요가 없음
      • Nginx는 웹 서버로서 정적 파일을 제공하는 데 매우 특화되어 있음
        • Nginx가 앞단에서 정적 파일을 바로 클라이언트에게 던져주고
          • 백엔드 서버는 DB 조회나 복잡한 로직(동적 처리)에만 집중할 수 있게 하여
          • 전체 시스템의 퍼포먼스를 극대화
    • ③ 로드 밸런싱 (Load Balancing)
      • 블로그가 유명해져서 트래픽이 감당 안 돼 백엔드 서버를 3대(서버 A, B, C)로 늘렸다고 가정
        • Nginx는 들어오는 엄청난 양의 트래픽을 서버 A, B, C로 골고루 분산시켜 줌
        • 특정 서버에만 부하가 몰려 서버가 다운되는 것을 방지
    • ④ SSL/TLS 암호화 처리 (HTTPS 적용)
      • 현대 웹 환경에서 보안을 위한 HTTPS 적용은 필수임
        • 이를 위해서는 트래픽을 암호화하고 복호화하는 연산 과정이 필요함
      • 이 무거운 암복호화 작업을 백엔드 서버가 직접 하게 되면 서버의 부담이 매우 커짐
        • Nginx를 앞에 두면 Nginx가 클라이언트와의 HTTPS 통신(SSL Termination)을 전담하고
        • Nginx와 내부 백엔드 서버 사이에는 가벼운 HTTP 통신을 하게 만들어 앱 서버의 성능 저하를 막을 수 있음
    • ⑤ 비정상 트래픽 차단 및 속도 제한 (Rate Limiting)
      • 악의적인 디도스(DDoS) 공격이나 비정상적인 크롤링 봇의 접근이 발생했을 때
        • Nginx 단에서 특정 IP의 초당 접속 횟수를 제한하거나
        • 요청 버퍼 크기를 제한하여 뒤에 있는 진짜 서버를 보호하는 1차 방어선 역할을 함

profile
안녕하세요.

0개의 댓글