2026/03/16 Blog - 26

김기훈·2026년 3월 16일

TIL

목록 보기
166/194
post-thumbnail

코딩테스트(1620)


배포

배포 전 세팅 확인

불필요한 패키지 정리

  • 원래 redis celery를 사용하려 하였으나 진행중 기능 변경으로 인하여 필요없어짐

  • psycopg2-binary / psycopg

    • 둘 다 PostgreSQL과 Python을 연결해 주는 드라이버
    • 즉, DB 드라이버 중복 설치
  • poetry remove psycopg2-binary celery django-redis redis


정적 파일(Static) 및 미디어 파일(Media) 서빙 방식

  • 현재 nginx.conf에 있는 location /static/ 설정과
    • docker-compose.prod.yml의 static_volume 마운트 설정
  • 대용량 미디어나 유저 업로드 파일은 S3가 처리하고
    • 고정된 기본 프로필 이미지 1장만 Nginx가 캐싱하여 빠르게 서빙하는 방식 사용
      • 비용과 성능 면에서의 최적화
      • nginx.conf의 location /static/ 블록을 그대로 유지 필요

Nginx에 HTTPS(SSL) 적용하기

  • 현재
    • nginx.conf를 보면 HTTP에 해당하는 80번 포트만 열려있음
    • 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)
  • 서버 상태 모니터링 방법
  • 소셜로그인 안되는 문제
  • 로그인에서 이메일 인증기능 넣기

코드 수정 후 자동 배포(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 파일의 안의 긴 텍스트 내용을 안전하게 저장
          1. HOST (서버 주소)
          • AWS 탄력적 IP 주소(숫자)(예: 3.33.44.55 같은 숫자)
          1. USERNAME (서버 접속 아이디)
          • 이건 그냥 ubuntu
          • 아까 맥북 터미널에서 서버에 들어갈 때 ssh -i 키.pem ubuntu@IP주소 라고 쳤음
          • 그때 쓴 기본 관리자 아이디가 바로 ubuntu
          1. 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/ 로 자동 생성

서버 상태 모니터링 방법

로그인 이메일 인증

profile
안녕하세요.

0개의 댓글