도커실무 강의 3주차

김서영·2022년 11월 11일
0

도커 실무

목록 보기
3/3

1. postgresql 컨테이너 생성하기

- postgresql이란?

💡 오픈 소스 데이터베이스로, Oracle DB, MySQL 등 상용 라이센스를 가지고 있는 데이터베이스와는 다르게 무료로 사용 가능하다.

Oracle DB, Mysql, Microsoft SQL에 이어 네번째로 사용량이 많은 데이터베이스이다.

또한 장고에서는 기본 데이터베이스로 postgresql을 사용하는 것을 권장하고 있다.

- 원하는 docker 이미지를 찾는 방법

💡 docker에서 사용 가능한 이미지들은 https://hub.docker.com 에서 제공

사이트 접속 후 원하는 이미지를 검색하면 해당 이미지의 정보를 확인할 수 있다.

- 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: # postgresql 컨테이너에서 사용할 환경변수 지정해주기
          - POSTGRES_USER=user # 데이터베이스 사용자 지정
          - POSTGRES_PASSWORD=P@ssw0rd # 사용자 비밀번호 지정
          - POSTGRES_DB=django # 데이터베이스 이름 지정
        restart: always

- 컨테이너가 잘 생성됐는지 확인해보기

sudo docker compose up -d : 컨테이너 실행
sudo docker compose logs : 컨테이너 로그 찍어보기

2. gunicorn을 사용해 django 프로젝트 컨테이너 생성하기

- gunicorn이란?

💡 django 프로젝트를 실행할 때 사용되는 runserver와 같이, 사용자의 요청을 받아 django에 작성한 코드를 실행시켜 주도록 하는 역할
=> 배포용으로 사용되는 runserver라고 생각하자!

- runserver가 아닌 gunicorn을 사용해 배포하는 이유

💡 기본적으로 runserver는 배포용이 아닌 개발용으로 사용되는 명령어이며, 공식 문서에서도 runserver로 배포하는 것을 권장하지 않고 있다.

또한 runserver는 기본적으로 싱글 스레드에서 동작하지만, gunicorn은 멀티 스레드로 동작하도록 설정할 수 있기 때문에 많은 요청을 더 효율적으로 처리할 수 있다.

이외에도 runserver에 비해 속도, 안정성 등 다양한 장점을 가지고 있기 때문에 배포 환경에서는 gunicorn을 사용하는 것을 권장하고 있습니다.

- 소스코드 다운받기

git clone https://github.com/sparta-course/drf-project.git ./django

- django 프로젝트를 배포하기 전 설정할 것

- settings.py

   ALLOWED_HOSTS = ['*'] # 빈 리스트는 아무 호스트도 허용하지 않겠다는 뜻, *은 모든 호스트를 대상으로 허용하겠다.
   STATIC_ROOT = BASE_DIR / "static"

- timezone 설정하기

💡 Linux를 처음 설치하면 대부분 표준 시간대가 협정 세계시(UTC)로 설정되어 있다.
timezone을 한국 시간대(KST)로 변경해주자!!

리눅스에서 date 명령어를 사용하면 현재 적용된 timezone 정보를 확인할 수 있다.
timezone을 변경해주기 위해, 기존 timezone 설정을 Asia/Seoul로 덮어씌워야 한다.

sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

설정 후 다시 date 명령어를 확인해보면 KST로 바뀐 것을 확인할 수 있다.

- backend/Dockerfile

    # 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을 사용하기 위한 패키지를 설치
    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"
        ports:
          - 80:8000
        volumes:
          - ./backend/django/:/app/
          - /etc/localtime:/etc/localtime:ro # host의 timezone 설정을 컨테이너에 적용
          # ro 은 읽기 전용(read only) 속성으로 볼륨을 설정하는 것을 의미
        restart: always

- entrypoint 명령어 파헤쳐보기

💡 위에서 docker-compose.yml데 작성했던 entrypoint에서 사용한 명령어들을 살펴보자!

sh -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn project_name.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만큼 지정해준다.(ex - 2코어라면 22+1=5)
  • -b 0.0.0.0:8000 : 8000번 포트로 실행시킨다.

- 컨테이너가 잘 동작하는지 확인해보기

sudo docker compose up --build -d
sudo docker compose logs
웹브라우저에서 접속해보기

3. nginx를 사용해 웹서버 컨테이너 생성하기

- nginx란?


💡 nginx는 클라이언트의 request 요청을 처리해주는 웹 서버(web server)이다.

reverse proxy, 로드밸런싱, 캐싱 등의 기능을 지원하며, 클라이언트의 요청을 nginx가 받은 후 service(django) 데이터를 넘겨주는 역할을 해준다.

- nginx를 사용하는 이유

  • 로드밸런싱을 활용해 트래픽을 분산할 수 있다.
  • SSL 기능을 사용해 데이터를 안전하게 전달할 수 있다.
  • reverse proxy 기능을 통해 client에서 서버에 직접적으로 접근하는 것을 막아준다.
  • 콘텐츠를 캐싱하여 동일한 요청에 대해 더 빠른 속도로 처리할 수 있게 해준다.

- 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
웹브라우저에서 접속해보기

4. nginx / postgresql / django 연동하기

- nginx 설정파일 만들기

💡 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/ 경로에 있는 파일들을 보여줍니다.
      }
    }

- backend/Dockerfile

    # 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
    

- 디렉토리 구조

    path : /home/ubuntu/
    ├── backend
    │   ├── Dockerfile
    │   └── django # project directory
    ├── docker-compose.yml
    └── nginx
        └── default.conf

- 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

sudo docker compose up -d --build 하여 실행
sudo docker compose logs -f 하여 로그 확인

이미지는 기존에 다운로드 받은 이미지가 있기 때문에 다운받지 않아도 되고, 이전 명령어들이 cashed가 되어있는데 이건 Dockerfile에서 명령어를 작성하고 build할 때 속도의 최적화를 위해 기존에 한번 실행한 명령어는 캐싱을 해두어 빠른 속도로 실행할 수 있다.

- 배포 환경의 통신 구조


1. 먼저 사용자가 EC2의 nginx에 request 요청

  1. nginx에서 사용자의 요청을 받아 .conf 파일에서 설정한 서버로 요청을 전달

  2. 이후 gunicorn에서는 django로, django에서는 필요에 따라 데이터베이스에 쿼리를 날려 개발자가 작성한 코드를 실행

5. 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

environment 부분에서 =뒤에 부분을 모두 지워주면 된다!!
만약 .env 파일을 다른 이름으로 사용할 경우 env_file 옵션을 사용하여 불러올 수 있다!

env_file:
   - prod.env

6. 더 작은 이미지로 배포하기

- slim 이미지의 종류와 특징

아래 예제는 python 이미지 기준이며, 이미지마다 지원하는 버전은 차이가 있다.

동일한 docker 이미지라 하더라도 태그에 따라 이미지의 용량이 달라지는데, 가령 python 이미지의 경우 가장 큰 이미지와 가장 작은 이미지의 용량이 300mb 이상 차이가 난다.

<기본 python 이미지>

<가장 작은 용량의 python alpine 이미지>

다만, 용량이 작은 이미지를 사용할 때는 외부 패키지를 설치할 때 필요한 의존성 파일들이 존재하지 않아 에러가 발생할 수 있다.
내가 사용하려는 프로젝트에 맞는 이미지를 사용하는것이 중요!

내가 사용하는 이미지에 어떤 종류의 태그가 존재하는지 확인이 필요할 때는 docker hub 이미지 페이지의 tags에서 확인 가능하다.

- 이미지의 태그별 특징

  • buster, jessie, stretch
    • debian에서 만든 linux를 기반으로 만들어진 이미지
    • buster, jessie, stretch는 os의 codename이다. (링크)
    • python:3.8과 python:3.8-buster는 동일하다. (약 300~350mb)
  • slim
    • 실행에 필요한 환경만 만들어둔 이미지
    • 이미지가 기본이미지에 비해서는 작다. (약 40~50mb)
    • 보통 python 실행환경에서 가장 많이 쓰이는 이미지
  • alpine
    • 용량이 작고, 보안에 집중한 alpine-linux 기반으로 만들어진 이미지
    • 보통 이미지들 중에서 가장 작다는 특징을 가지고 있다. (약 15~20mb)
    • python기준으로 봤을 때, pip install을 할 때 불리한 점이 있다.

- backend/Dockerfile

    # python 3.10.8-slim버전 이미지를 사용해 빌드
    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

- docker-compose.yml

    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 : 이미지 용량 확인
sudo docker images prune -a : 사용하지 않는 이미지 삭제
sudo docker rmi 이미지 이름 : 이미지 지정해서 삭제(rmi는 remove image의 약자)
<전>

<후>

profile
개발과 지식의 성장을 즐기는 개발자

0개의 댓글