현재 진행중인 프로젝트 웹 페이지이다.
페이지를 구성하기 위해서는 현재 상장된 약 2400개 기업의 재무제표를 모두 크롤링 해와야 한다.
단순히 빠르게 크롤링 하려면 파이썬의 병렬 스레드나 멀티 프로세싱 기술을 사용해서 크롤링 하면 수십만개의 재무제표를 크롤링 하는데 그리 오랜 시간이 걸리지 않을 것이다. 하지만 크롤링을 하기위해서 접근하는 opendart 페이지와 opendart의 api를 사용하기 위해서는 1분당 접근이 1000회를 넘어가서는 안되며 하루에 10000번으로 접근횟수가 제한되어있다. 따라서 재무제표 크롤링은 시간을 들여야하는 조심스러운 작업이라고 생각했고, 크롤링을 위한 컨테이너를 따로 만들어줘서 안정적인 크롤링 환경을 구성해야겠다고 생각했다.
Dockerfile 설정
FROM python:3.9.0
WORKDIR /home/
COPY ./ /home/ubuntu/quant_server
WORKDIR /home/ubuntu/quant_server/
RUN apt-get upgrade && pip3 install --upgrade pip
RUN pip install -r requirements.txt
EXPOSE 8000
EXPOSE 8001
EXPOSE 8080
위 코드는 내가 로컬에서 간단하게 테스트를 하기위한 이미지를 만드는 dockerfile이다.
주목할 점은 EXPOSE로 8000, 8001, 8080 포트를 내부적으로 열어준다는 점이다.
EXPOSE의 의미는 컨테이너 내부 포트를 열어준다는 뜻이다. EXPOSE만 해준다고 해당 포트를 통해서 외부와 소통할수는 없다.
그리고 도커파일의 CMD 부분은 삭제를 해줘야 한다.
image build 설정
위 도커파일을 통해서 베이스가 되는 이미지 파일을 만들어 준다.
docker build -t 이미지이름:태그 -f 도커파일이름.Dockerfile .
docker-compose 설정
version: "3.3"
services:
nginx:
image: nginx:1.21.1
networks:
- lq
volumes:
- ./localnginx.conf:/etc/nginx/nginx.conf
- static-volume-quant:/data/static
ports:
- 80:80
depends_on:
- quant_home
- quant_crawling
quant_home1:
image: quant:0.1
networks:
- lq
volumes:
- .:/home/ubuntu/quant_server
- static-volume-quant:/home/ubuntu/quant_server/staticroot
environment:
- PYTHONUNBUFFERED=1
entrypoint: ["bash", "-c"]
command:
- |
python manage.py makemigrations --settings=config.settings.local
python manage.py migrate --settings=config.settings.local
python manage.py runserver 0.0.0.0:8000 --settings=config.settings.local
quant_home2:
image: quant:0.1
networks:
- lq
volumes:
- .:/home/ubuntu/quant_server
- static-volume-quant:/home/ubuntu/quant_server/staticroot
environment:
- PYTHONUNBUFFERED=1
entrypoint: ["bash", "-c"]
command:
- |
python manage.py makemigrations --settings=config.settings.local
python manage.py migrate --settings=config.settings.local
python manage.py runserver 0.0.0.0:8001 --settings=config.settings.local
quant_crawling:
image: quant:0.1
networks:
- lq
volumes:
- .:/home/ubuntu/quant_server
- static-volume-quant:/home/ubuntu/quant_server/staticroot
environment:
- PYTHONUNBUFFERED=1
entrypoint: ["bash", "-c"]
command:
- |
python manage.py makemigrations --settings=config.settings.local
python manage.py migrate --settings=config.settings.local
python manage.py runserver 0.0.0.0:8080 --settings=config.settings.local
networks:
lq:
volumes:
static-volume-quant:
로컬 환경에서 서버를 구동하기 위한 docker-compose.yml 파일이다.
quant_home1, quant_home2
일반 페이지를 담당하는 컨테이너이다.
entrypoint: ["bash", "-c"]
command:
- |
python manage.py makemigrations --settings=config.settings.local
python manage.py migrate --settings=config.settings.local
python manage.py runserver 0.0.0.0:8000 --settings=config.settings.local
위 코드를 주의해서 봐야 한다.
entrypoint 와 command는 해당 컨테이너가 수행하게 될 실행 명령을 정의하는 선언이다.
즉, 컨테이너가 무슨 일을 하는지 결정하는 최종 단계를 정의하는 명령이라고 생각하면 된다.
그렇기 때문에 Dockerfile 의 가장 마지막 부분 쯤에 entrypoint 또는 command 를 선언하게 된다.
entrypoint 와 command 의 가장 큰 차이점은 바로 컨테이너 시작시 실행 명령에 대한 Default 지정 여부이다.
만약 entrypoint 를 사용하여 컨테이너 수행 명령을 정의한 경우,
해당 컨테이너가 수행될 때 반드시 entrypoint 에서 지정한 명령을 수행되도록 지정 된다.
하지만, command를 사용하여 수행 명령을 정의한 경우에는,
컨테이너를 실행할때 인자값을 주게 되면 Dockerfile 에 지정된 CMD 값을 대신 하여 지정한 인자값으로 변경하여 실행되게 된다.
출처: https://bluese05.tistory.com/77 [ㅍㅍㅋㄷ]
entrypoint: ["bash", "-c"]
컨테이너 시작시 default로 수행할 명령을 정의한 것이다.
리눅스의 bash 셸에서 -c 옵션을 사용하겠다는 뜻이다.
-c 옵션 : 뒤에 받을 명령을 string 타입의 인자로 받겠다는 옵션이다.
command:
- |
python manage.py makemigrations --settings=config.settings.local
python manage.py migrate --settings=config.settings.local
python manage.py runserver 0.0.0.0:8000 --settings=config.settings.local
컨테이너 시작시 수행할 명령어를 정의했다.
총 3개의 명령을 수행할 것이기 때문에 '|' 기호를 사용해서 다중 명령어를 인식하도록 했다.
중요한 점은 총 3개의 django 컨테이너를 만드는데 컨테이너마다 외부에 열어줄 포트를 다르게 해준다는 점이다.
quant_crawling 컨테이너에는 8080 포트를 열어줬음을 확인할 수 있다.
도움을 받은 블로그
위 블로그에서 다중 명령어를 수행하는 여러가지 방법을 알 수 있다.
stack deploy 설정
docker stack deploy -c docker-compose 스택이름
위 명령어를 사용해서 총 4개의 컨테이너(nginx 1개, django 3개)를 서비스로 구동시켜 보자
nginx설정
worker_processes auto;
events{
worker_connections 1024;
use epoll;
}
http {
upstream quant {
# default = round_robbin;
# least_conn;
# ip_hash;
server quant_home1:8000;
server quant_home2:8001 backup;
}
upstream crawling {
server quant_crawling:8080;
}
server {
listen 80;
include mime.types;
location /static/ {
alias /data/static/;
}
location /api/v1/stock/crawling {
proxy_pass http://crawling;
}
location /api {
proxy_pass http://quant;
}
location /admin {
proxy_pass http://quant;
}
}
}
아주 간단한 nginx.conf 파일이다.
docker-compose를 통해서 만든 컨테이너의 연결을 관리해 주고 로드밸런싱을 담당한다.
upstream quant {
# default = round_robbin;
# least_conn;
# ip_hash;
server quant_home1:8000;
server quant_home2:8001 backup;
}
upstream crawling {
server quant_crawling:8080;
}
이 부분이 nginx의 upstream을 정의해 주는 부분이다.
upstream 이란?
강의 상류라는 뜻으로 애플리케이션 계층에서 데이터를 내려주는 server를 의미한다.
따라서 downstream은 client라고 할 수 있다.
upstream 으로 quant와 crawling을 만든 것을 볼 수 있다. quant upstream에서는 8000, 8001 포트가 열려있는 컨테이너를 사용하고, crawling upstream에서는 8080포트가 열려있는 컨테이너를 사용한다.
기본적으로 round_robbin 방식의 로드밸런싱을 사용하고, 설정에 따라서 least_conn(가장 적게 연결된 서버 우선 연결 방식), ip_hash(client의 ip를 hash한 결과로 연결할 서버 결정) 방식 중 선택할 수 있다.
server quant_home2:8001 backup;
backup 서버라는 뜻으로 다른 모든 서버들이 오류로 인해서 작동을 멈췄을 때 사용되는 서버이다.
주의할 점(ip_hash는 backup서버 설정 불가)
크롤링 서버는 백업을 두면 안되는 이유(하나만 있어야 하는 이유)
크롤링을 해서 재무제표를 저장하는 과정에는 상당히 많은 오류가 발생할 수 있고, 수많은 예외처리가 필요하다. 그리고 양이 매우 많기 때문에 중복된 데이터가 들어오는 경우는 치명적이다.
크롤링 백업서버가 있다면 어떠한 이유 때문에 고장난 서버를 대신에 백업 서버가 크롤링은 이어서 할 것이다. 이때 데이터의 중복이 발생할 수 있다. 그리고 백업서버가 없더라도 만약 round-robbin이나 least_conn 방식으로 로드밸런싱을 한다면 여러개의 컨테이너에서 동시에 크롤링을 진행하게 될 수도 있기 때문에 데이터의 중복이 발생할 수 있다. - 경험에 의거한 결론!
location /api/v1/stock/crawling {
proxy_pass http://crawling;
}
location /api {
proxy_pass http://quant;
}
엔드포인트가 /api/v1/stock/crawling으로 시작하면 crawling upstream으로 요청을 보낸다.
엔드포인트가 /api로 시작하면 quant upstream으로 요청을 보낸다.
위 location 선언을 통해서 요청을 올바른 컨테이너로 보낼 수 있게되었다.
위 전체 과정을 통해서 홈페이지상의 오류에 관계없이 크롤링을 안정적으로 구동할 수 있게 되었다.
'DevOps > Docker' 카테고리의 다른 글
[Docker] m1 mac에서 image build 후 Linux에서 실행하는 방법 (0) | 2022.11.07 |
---|---|
[Docker] 이미지와 레이어(layer) 구조 (0) | 2022.01.25 |
[Docker] certbot 컨테이너를 사용해 SSL 인증서 발급받기 (0) | 2021.11.14 |
[Docker] Docker Swarm 서비스하기 (0) | 2021.08.24 |
[Docker] Docker Swarm에 대해서 (0) | 2021.08.18 |