A ship in harbor is safe, but that is not what ships are built for.

개발일지/AI 캠프

내일배움캠프 AI - 51일차 TIL, 2022.11.11

플리피나리 2022. 11. 14. 14:24
반응형

스파르타 코딩클럽 내일배움캠프 AI 웹개발자양성과정 3회차

2022.11.11. 51일차 - TIL

 

 

1. Docker 원격강의

  • 목표
    1) Django 컨테이너를 gunicorn을 사용해 실행시킬 수 있다.
    2) nginx / postgresql / django 컨테이너를 생성하고 연동할 수 있다.
    3) 도메인을 구매하고, 네임서버를 설정할 수 있다.
    4) 구매한 도메인에 접속했을 때 배포한 서버에 접속되도록 설정할 수 있다.
    5) 로드밸런서를 사용해 https 프로토콜을 적용할 수 있다.
  • postgresql : 오픈 소스 데이터베이스(공짜다 공짜...), django에서 기본 DB로 권장
  • 원하는 docker 이미지는 여기에서 제공받을 수 있다.
# postgresql 컨테이너를 위한 docker-compose.yml
version: '3.8'

volumes:
	postgres: {}  # postgresql에서 사용할 볼륨 지정
    
services:
	postgres:  # 서비스 이름
    	container_name: postgres  # 컨테이너 이름
        image: postgres:14.5  # 컨테이너 생성 시 사용할 이미지
        volumes:
        	- postgres:/var/lib/postgresql/data
        environment:
        	- POSTGRES_USER=user  # 데이터베이스 사용자 지정
            - POSTGRES_PASSWORD=P@ssw0rd  # 사용자 비밀번호 지정
            - POSTGRES_DB=django  # 데이터베이스 이름 지정
        restart: always
        
####################################################################
# 컨테이너가 잘 생성됐는지 확인
sudo docker compose up -d  # 컨테이너 실행(docker-compose.yml을 읽어 정의된 서비스 실행$데몬으로 실행)
sudo docker compose logs  # 컨테이너 로그 확인
  • gunicorn : django 프로젝트 실행 시 사용되는 runserver 대신 사용자의 요청을 받아 django에 작성한 코드를 실행시켜주도록 하는 역할(배포용 runserver)
  • 기본적으로 runserver은 개발용, 또한 runserver는 기본적으로 싱글 스레드에서 동작하지만, gunicorn은 멀티 스레드로 동작하도록 설정할 수 있어 많은 요청을 효율적으로 처리한다. 또 속도, 안정성 등의 장점이 있다.
  • django 프로젝트 배포 전 설정 변경
# settings.py
ALLOWED_HOSTS = ['*']
STATIC_ROOT = BASE_DIR / "static"
# 현재 적용된 timezone 확인
date

# timezone을 Asia/Seoul로 변경
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul etc/localtime
# Dockerfile

# python 3.10.8 버전 이미지를 사용해 빌드
FROM python:3.10.8

# .pyc 파일을 생성하지 않도록 설정
ENV PYTHONDONTWRITEBYTECODE 1

# 파이썬 로그가 버퍼링 없이 즉각적으로 출력하도록 설정
ENV PYTHONUNBUFFERED 1

# /app/ 디렉토리를 생성
RUN mkdir /app/

# /app/ 경로르 작업 디렉토리로 설정
WORKDIR /app/

# requirements.txt를 작업 디렉토리(/app/) 경로로 복사
COPY ./django/requirements.txt .

# 프로젝트 실행에 필요한 패키지들을 설치
RUN pip install --no-cache-dir -r requirements.txt

# gunicorn을 사용하기 위한 패키지 설치
RUN pip install gunicorn
# docker-compose.yml

version: '3.8'

services:
  backend:  # 서비스 이름
    container_name: backend  # 컨테이너 이름
    build: ./backend/
    # drf_project.wsgi는 프로젝트 경로에 맞게 지정해야 한다
    entrypoint: sh -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn drf_project.wsgi --workers=5 -b 0.0.0.0:8000"
    # sh -c : 컨테이너에서 뒤에 작성한 명령어를 실행
    # && : 특정 명령어를 실행한 후 이후 명령어 실행
    # python manage.py collectstatic --no--input : 배포를 위해 static 파일을 모음(settings.py에 STATIC_ROOT정의 필수)
    # python manage.py migrate : django에 연결된 db를 migrate
    # gunicorn project_name.wsgi --workers=5 -b 0.0.0.0:8000 : gunicorn을 사용해 django 프로젝트 실행
    # project_name : django 프로젝트 이름
    # --workers=5 : django에서 실행시킬 process 갯수를 입력(cpu코어 개수*2+1)
    # -ㅠ 0.0.0.0:8000 : 8000번 포트로 실행
    ports:
      - 80:8000  # 80번 포트를 8000번 포트로 포트포워딩 설정
    volumes:
      - ./backend/django/:/app/
      - /etc/localtime:/etc/localtime:ro # host의 timezone 설정을 컨테이너에 적용
      # ro 은 읽기 전용(read only) 속성으로 볼륨을 설정하는 것
    restart: always
# 컨테이너가 잘 동작하는지 확인
sudo docker compose up --build -d
sudo docker compose logs

# 웹 브라우저 접속
  • nginx : 클라이언트의 request 요청을 처리해주는 웹서버, reverse proxy, 로드밸런싱, 캐싱 등의 기능 지원
    -> 클라이언트의 요청을 nginx가 받은 후 service(django) 데이터를 넘겨주는 역할
    -> 로드밸런싱을 활용해 트래픽 분산 + SSL 기능을 사용해 데이터 안전 전달 + reverse proxy 기능을 사용해 클라이언트에서 서버에 직접적 접근 차단 + 콘텐츠를 캐싱해 동일 요청에 대한 더 빠른 처리
# docker-compose.yml

version: '3.8'
services:
  nginx:
    container_name : nginx
    image: nginx:1.23.2
    ports:
      - "80:80" # http 포트포워딩
      - "443:443" # https 포트포워딩
    restart: always
# 컨테이너 잘 동작하는지 확인

sudo docker compose up -d
sudo docker compose logs

# 웹브라우저 접속
  • nginx / postgresql / django 컨테이너 연동하기
# nginx 디렉토리 내 default.conf에 아래 파일 생성
server {
  listen 80;
  server_name _; # 모든 도메인 혹은 ip로 들어오는 요청에 대해 처리

  location / { # nginx로 요청이 들어왔을 때
    proxy_pass http://backend:8000/; # backend 컨테이의 8000번 포트로 전달
  }

  location /static/ { # 브라우저에서 /static/ 경로로 요청이 들어왔을 때
    alias /static/; # /static/ 경로에 있는 파일들을 보여줌
  }

  location /media/ { # 브라우저에서 /media/ 경로로 요청이 들어왔을 때
    alias /media/; # /media/ 경로에 있는 파일들을 보여줌
  }
}
# python 3.10.8버전 이미지를 사용해 빌드
FROM python:3.10.8

# .pyc 파일을 생성하지 않도록 설정
ENV PYTHONDONTWRITEBYTECODE 1

# 파이썬 로그가 버퍼링 없이 즉각적으로 출력하도록 설정
ENV PYTHONUNBUFFERED 1

# /app/ 디렉토리를 생성
RUN mkdir /app/

# /app/ 경로를 작업 디렉토리로 설정
WORKDIR /app/

# requirments.txt를 작업 디렉토리(/app/) 경로로 복사
COPY ./django/requirements.txt .

# 프로젝트 실행에 필요한 패키지들을 설치
RUN pip install --no-cache-dir -r requirements.txt

# gunicorn과 postgresql을 사용하기 위한 패키지를 설치
RUN pip install gunicorn psycopg2
# django settings.py 설정

import os

# 환경변수에 따라 DEBUG모드 여부를 결정
DEBUG = os.environ.get('DEBUG', '0') == '1'

# 접속을 허용할 host를 설정
ALLOWED_HOSTS = ['backend', ]

# postgres 환경변수가 존재 할 경우에 postgres db에 연결을 시도
POSTGRES_DB = os.environ.get('POSTGRES_DB', '')
if POSTGRES_DB:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': POSTGRES_DB,
            'USER': os.environ.get('POSTGRES_USER', ''),
            'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''),
            'HOST': os.environ.get('POSTGRES_HOST', ''),
            'PORT': os.environ.get('POSTGRES_PORT', ''),
        }
    }

# 환경변수가 존재하지 않을 경우 sqlite3을 사용
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }


# CORS 허용 목록에 ec2 ip를 추가
CORS_ORIGIN_WHITELIST = ['http://$ec2_public_ip']
# ex) CORS_ORIGIN_WHITELIST = ['http://43.201.72.190']

# CSRF 허용 목록을 CORS와 동일하게 설정
CSRF_TRUSTED_ORIGINS = CORS_ORIGIN_WHITELIST
# docker-compose.yml

version: '3.8'

volumes:
  postgres: {}
  django_media: {}
  django_static: {}

services:
  postgres:
    container_name: postgres
    image: postgres:14.5
    volumes:
      - postgres:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=P@ssw0rd
      - POSTGRES_DB=django
    restart: always

  backend:
    container_name: backend
    build: ./backend/
    entrypoint: sh -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn drf_project.wsgi --workers=5 -b 0.0.0.0:8000"
    volumes:
      - ./backend/django/:/app/
      - /etc/localtime:/etc/localtime:ro
      - django_media:/app/media/ # nginx에서 media를 사용할 수 있도록 volume을 지정
      - django_static:/app/static/ # nginx에서 static을 사용할 수 있도록 volume을 지정
    environment: # django에서 사용할 설정들을 지정
      - DEBUG=1
      - POSTGRES_DB=django
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=P@ssw0rd
      - POSTGRES_HOST=postgres
      - POSTGRES_PORT=5432
    depends_on:
      - postgres
    restart: always

  nginx:
    container_name : nginx
    image: nginx:1.23.2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - django_media:/media/ # django의 media를 사용할 수 있도록 volume을 지정
      - django_static:/static/ # django의 static 사용할 수 있도록 volume을 지정
    depends_on:
      - backend
    restart: always
# 디렉토리 구조
path : /home/ubuntu/
├── backend
│   ├── Dockerfile
│   └── django # project directory
├── docker-compose.yml
└── nginx
    └── default.conf

> 정리하면, 가장 먼저 사용자는 EC2의 nginx에 request 요청을 하고, nginx에서는 사용자의 요청을 받아 .conf 파일에서 설정한 서버로 요청을 전달한다. 이후 gunicorn에서는 django로, django에서는 필요에 따라 DB에 쿼리를 날려 개발자가 작성한 코드를 실행한다.

  • env 파일 작성하기
# .env 파일
DEBUG=1
POSTGRES_DB=django
POSTGRES_USER=user
POSTGRES_PASSWORD=P@ssw0rd
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# docker-compose.yml

version: '3.8'

volumes:
  postgres: {}
  django_media: {}
  django_static: {}

services:
  postgres:
    container_name: postgres
    image: postgres:14.5
    # 만약 .env 파일을 다른 이름으로 사용할 경우 env_file 옵션을 사용해 불러옴
    # env_file:
    #   - prod.env
    volumes:
      - postgres:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    restart: always

  backend:
    container_name: backend
    build: ./backend/
    entrypoint: sh -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn drf_project.wsgi --workers=5 -b 0.0.0.0:8000"
    volumes:
      - ./backend/django/:/app/
      - /etc/localtime:/etc/localtime:ro
      - django_media:/app/media/
      - django_static:/app/static/
    environment: # 
      - DEBUG
      - POSTGRES_DB
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_HOST
      - POSTGRES_PORT
    depends_on:
      - postgres
    restart: always

  nginx:
    container_name : nginx
    image: nginx:1.23.2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - django_media:/media/ # django의 media를 사용할 수 있도록 volume을 지정
      - django_static:/static/ # django의 static 사용할 수 있도록 volume을 지정
    depends_on:
      - backend
    restart: always
  • slim 이미지 : 용량 최적화를 위해 컨테이너 실행에 필요한 최소한의 파일만 포함됨, but. 외부 패키지 설치 시 의존성 파일이 존재하지 않아 에러 발생 가능성 있음
    - buster, jessie, stretch : debian에서 만든 linux 기반 이미지로, os의 codename이다.
    - slim : 실행에 필요한 환경만 만들어둔 이미지
    - alpine : 용량이 작고, 보안에 집중한 alpiine-linux 기반으로 만든 이미지
# python 3.10.8-slim버전 이미지를 사용해 빌드
# Dockerfile
FROM python:3.10.8-slim

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN mkdir /app/
WORKDIR /app/

# slim 이미지에서 postgresql 패키지를 설치하기 위해 필요 명령어 추가
RUN apt update && apt install libpq-dev gcc -y

COPY ./django/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn psycopg2
version: '3.8'

volumes:
  postgres: {}
  django_media: {}
  django_static: {}

services:
  postgres:
    container_name: postgres
    image: postgres:14.5-alpine # alpine 이미지를 지정
    volumes:
      - postgres:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    restart: always

  backend:
    container_name: backend
    build: ./backend/
    entrypoint: sh -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn drf_project.wsgi --workers=5 -b 0.0.0.0:8000"
    volumes:
      - ./backend/django/:/app/
      - /etc/localtime:/etc/localtime:ro
      - django_media:/app/media/
      - django_static:/app/static/
    environment:
      - DEBUG=1
      - POSTGRES_DB
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_HOST
      - POSTGRES_PORT
    depends_on:
      - postgres
    restart: always

  nginx:
    container_name : nginx
    image: nginx:1.23.2-alpine # alpine 이미지를 지정
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - django_media:/media/
      - django_static:/static/
    depends_on:
      - backend
    restart: always
# 이미지 용량 비교
sudo docker images
  • 도메인 구매 : freenom에서 무료 도메인 구매 -> freenom 홈페이지는 매우 느림
  • 도메인을 사용해 배포 서버에 접근하도록 설정
    freenom nameserver 설정하기 : aws 접속 후 route53 서비스 > 호스팅 영역 생성 버튼 > 구매한 도메인 입력 > 호스팅 영역 생성 > 호스팅 영역에서 지정된 도메인 확인 > 값/트래픽 라우팅 대상 값들 확인 > freenom 페이지에서 manage domain > management tools의 nameserver > use custom nameservers 클릭 후 aws 호스팅 영역에서 확인한 ns 유형의 값/트래픽 라우팅 값들 입력 > change nameservers
    route53 설정하기 : route53 > 호스팅 영역에서 도메인 클릭 > 레코드 생성 버튼 > 값에 ec2의 퍼블릭 ip 입력 후 생성 > 동일한 방법으로 레코드 이름에 www를 입력하고 레코드 유형을 CNAME으로 변경 후 값에 구매한 도메인 입력해 레코드 생성 > 레코드 목록 확인
# nginx의 default.conf 변경

server {
  listen 80;
  server_name www.spartacodingclub.tk; # www.spartacodingclub.tk 도메인으로 들어오는 요청을 처리

  location / {
    proxy_pass http://backend:8000/;
  }

  location /static/ {
    alias /static/;
  }

  location /media/ {
    alias /media/;
  }
}

server {
  listen 80;
  server_name spartacodingclub.tk; # www가 없는 url로 요청 했을 때
  return 301 http://www.spartacodingclub.tk$request_uri; # www를 붙인 url로 redirection
}
  • https : 웹서버에서 데이터 전송을 위한 http 프로토콜에 보안을 붙여 http 통신을 더 안전하게 할 수 있도록 하는 프로토콜 -> SSL 인증서를 발급받고 웹서버에서 인증서를 지정해주는 과정 필요
  • SSL 인증서 발급받기
    aws에서 ssl 검색 > Certificate Manager 접속 > 인증서 요청 클릭 > 퍼블릭 인증서 요청 체크 > 구매한 도메인 이름 입력 > 이 인증서에 다른 이름 추가 > *.구매한 도메인 이름 입력 > DNS 검증 > 요청 > 인증서 목록에서 요청한 인증서 ID 클릭 > 도메인 소유자 증명위해 Route 53에서 레코드 생성 버튼 클릭 > 도메인 확인 후 레코드 생성 버튼 > 발급 확인
  • 대상 그룹 설정하기
    aws > ec2 서비스 접속 > 로드 밸런싱 > 대상 그룹 > Create target group > 인스턴스 선택 > Target group name 지정 > Health check path 입력 후 Next > 대상이 될 서버 지정 > Include as pending below 버튼 클릭 > 추가 대상 확인 후 Create target group > Continue > 등록한 대상 확인
    ^ Health check : 로드 밸런서에서 서버가 정상 동작하는지 확인하는 용도
  • 로드밸런서 설정하기
    aws > ec2 서비스 접속 > 로드 밸런싱 > 로드 밸런서 > 로드 밸런서 생성 > Application Load Balancer Create > Basic Configuration에서 로드밸런서 이름 작성 후 Internet-facing, IPv4 선택 > Network mapping에서 ap-northeast-2a, 2b 선택 > Security gruops에서 default 그룹 선택 > Listeners and routing에서 이전에 생성한 대상 그룹을 지정, Add listener > Protocol에서 HTTPS 선택 후 동일하게 대상 그룹 선택 > Secure listener settings에서 생성한 SSL 인증서 선택 > Create load balancer > 로드밸런서 목록 확인
  • route53 설정하기 : 도메인 접속 시 인스턴스가 아닌 로드밸런서를 통해 접속하도록 설정
    aws > route53 서비스 접속 > 호스팅 영역 > 등록한 도메인 클릭 > 유형 A로 등록된 도메인 선택 후 레코드 편집 > 별칭 옵션 활성화 > Application/Classic Load Balancer > 아시아 태평양(서울) > 생성한 로드밸런서 순서대로 선택 후 저장
  • http요청을 https로 redirect 하도록 설정하기
    aws > ec2 서비스 접속 > 로드밸런서 > 생성한 로드밸런서 클릭 > 리스너 > HTTP:80에 해당하는 리스너의 규칙보기/편집 > 추가 버튼, 규칙 삽입 > IF의 조건 추가 > 호스트 헤더 선택 > *.구매한도메인이름 > 체크 > THEN의 작업 추가 > 리다이렉션 대상 선택 > HTTPS 선택 및 443 포트 입력 > 기본 호스트, 301 선택 > 체크 > 설정 확인 후 저장
  • django settings.py 설정하기
# CORS 허용 목록에 도메인 서버를 추가
CORS_ORIGIN_WHITELIST = ['https://www.$domain', ]
# CORS_ORIGIN_WHITELIST = ['https://www.$domain', ]

# CSRF 허용 목록을 CORS와 동일하게 설정
CSRF_TRUSTED_ORIGINS = CORS_ORIGIN_WHITELIST
반응형