본문 바로가기
  • 달려가보자고~!
개발공부/Python

docker를 설치하고 활용해보기(2) / 서비스 배포하기(1)

by 도전왕 2022. 11. 14.

 

  • entrypoint 활용해보기
    - entrypoint : docker 컨테이너가 생성될 때 기본적으로 실행 할 명령어를 지정해 주는 옵션.
    - 예를 들어, 데이터베이스를 실행시키기 위해 만든 이미지는, 컨테이너가 생성될 때 데이터베이스 서비스를 실행시켜야 함.
       이 때 사용되는 옵션이 entrypoint임.
    - entrypoint는 Dockerfile과 docker-compose.yml 모두 작성가능.
    - 만약 Dockerfile, docker-compose.yml 모두 entrypoint가 작성되어 있다면 Dockerfile의 entrypoint는
       무시되고 docker-compose.yml의 명령어가 우선적으로 수행.
*Dockerfile*

FROM python:3.9.15

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

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

# /app/ 디렉토리를 생성합니다.
RUN mkdir /app/

# /app/ 경로를 작업 디렉토리로 설정합니다.
WORKDIR /app/

# main.py 파일을 /app/ 경로로 복사합니다.
COPY ./main.py /app/


       - docker-compose.yml

version: '3.8' # docker-compose.yml에 사용될 문법 버전을 정의합니다.

services:
  example: # 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example # 컨테이너 이름을 지정합니다.
    build: . # 현재 경로에 있는 Dockerfile을 사용해 이미지를 생성합니다.
    entrypoint: sh -c "python3 main.py" # 작업 디렉토리에 존재하는 main.py 파일을 실행시킵니다.
    restart: always # 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.

 

       - 임의의 파이썬 파일을 만들어 실행하기
         ㄴ vi 편집기를 사용해 실행시킬 python파일을 만들고 entrypoint를 사용해 컨테이너에서 실행시키기.
         ㄴ vi 편집기를 main.py 파일을 열고 수정하기

vi main.py

from time import sleep
for i in range(100):
    print(f"print number : {i}")
    sleep(1)


         ㄴ 코드 작성 후 esc -> :wq 를 입력하여 파일을 저장, 컨테이너를 생성.
         ㄴ sudo docker compose up --build -d


         ㄴ 이후 logs 명령어를 사용해 작성 한 print문이 정상적으로 출력되는지 확인.
         ㄴ sudo docker compose logs -f

 

 

  • 컨테이너를 두 개 이상 띄워보기
    - 주의사항
      ㄴ 컨테이너가 1개일 때와 2개 이상일 때 docker-compose.yml을 작성하는 방법은 기존과 크게 다르지 않음.
      ㄴ 다만, ports 혹은 서비스 이름 등 중복되면 안되는 몇몇 옵션이 존재하기 때문에 중복되는 값이 있는지
          확인해야 하며 만약 특정 값이 중복되어 들어간다면 컨테이너가 정상적으로 생성되지 않거나 생성하는 과정에서
          에러가 발생할 수 있음.
    - docker-compose.yml
version: '3.8'

services:
  example1:
    container_name: example1
    image: 'httpd:latest'
    ports:
      - 80:80
    restart: always
    
  example2: # 서비스 이름이 동일하면 컨테이너가 정상적으로 생성되지 않을 수 있습니다.
    container_name: example2 # 컨테이너 이름이 동일하면 컨테이너 생성 시 에러가 발생합니다.
    build: .
    entrypoint: sh -c "python3 main.py"
    restart: always



       - 컨테이너 생성해보기
         ㄴ docker-compose.yml 작성 이후 컨테이너를 실행시켜 보면 두 개의 컨테이너가 실행되는 것을 확인할 수 있음.
         ㄴ sudo docker compose up

         ㄴ depends_on을 사용해 컨테이너 실행 순서 바꿔보기
         ㄴ docker-compose.yml에 두 개 이상의 컨테이너를 생성하도록 작성한 경우, 기본적으로는 작성 한 순서대로 컨테이너가 실행.
         ㄴ 하지만 경우에 따라 특정 컨테이너가 먼저 실행되어야 하는 경우가 있음.
         ㄴ 예를 들어, django 컨테이너와 데이터베이스 컨테이너를 같이 띄우는 경우에는 데이터베이스 컨테이너가
             먼저 생성되어야 django 컨테이너에서 데이터베이스에 연결할 수 있음.
         ㄴ 이 때, depends_on 옵션을 사용해 컨테이너간 실행 순서를 컨트롤 할 수 있음.

docker-compose.yml

version: '3.8'

services:
  example1:
    container_name: example1
    image: 'httpd:latest'
    ports:
      - 80:80
    depends_on:
      - example2 # 해당 컨테이너보다 먼저 실행되어야 하는 컨테이너를 지정합니다.
    restart: always
    
  example2: # 서비스 이름이 동일하면 컨테이너가 정상적으로 생성되지 않을 수 있습니다.
    container_name: example2 # 컨테이너 이름이 동일하면 컨테이너 생성 시 에러가 발생합니다.
    build: .
    entrypoint: sh -c "python3 main.py"
    
    restart: always

 

         ㄴ 이후 컨테이너가 생성되는 로그를 보면 실행 순서가 바뀐 것을 확인할 수 있음.
         ㄴ depends_on을 설정하지 않은 경우

         ㄴ depends_on을 설정해준 경우

 

 


 

  • 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


         ㄴ 위와 같이 docker compose logs 명령어를 활용하면 컨테이너를 생성하고 서비스를 실행하는 과정에서
             문제가 없는지 확인할 수 있음.

  • 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 정보를 확인할 수 있으며,
            아무런 설정도 하지 않았을 경우 UTC로 설정되어 있는 것을 확인할 수 있음.


            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에 django 프로젝트를 실행시키기 위한 명령어를 사용.
         ㄴ 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코어라면 2*2+1=5)
         ㄴ -b 0.0.0.0:8000 : 8000번 포트로 실행시킴.

       - 컨테이너가 잘 동작하는지 확인하기.
         ㄴ sudo docker compose up --build -d


         ㄴ sudo docker compose logs


         ㄴ 웹브라우저에서 접속해서 확인하기.

 

  • 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


         ㄴ 웹브라우저에서 접속해보기

 

 

  • 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


       - 배포 환경의 통신 구조
         ㄴ 배포 환경의 구조를 그리면


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

 

'개발공부 > Python' 카테고리의 다른 글

이미지 처리 딥러닝(1)  (0) 2022.11.16
서비스 배포하기(2)  (0) 2022.11.15
내일배움단캠프 11주차 회고록(WIL)  (0) 2022.11.11
docker를 설치하고 활용해보기  (1) 2022.11.11
Linux의 특징과 이해(2)  (0) 2022.11.10

댓글