3. Docker Compose 서비스 튜토리얼
1. Mission
- Client에서 POST 방식으로 JSON 형식의 데이터를 전송한다.
- 이때 JSON의 내용은
{"a": 1, "b": 2, "operator": "+"}
와 같이 사칙연산에 필요한 데이터이며, 사칙연산 후 결괏값을 반환받아야 한다. - Docker Compose로 Proxy Server(Nginx), Calculate Server(Gunicorn 및 Flask), Cache Server(Redis), DB Server(MongoDB)를 구성한다.
- Nginx는 로드 밸런싱 등의 기능을 사용하기 위함이다.
- Gunicorn은 WSGI 미들웨어로 멀티 프로세싱이 가능하도록 하며, 이때 Gunicorn과 연결되는 웹 애플리케이션은 동기 방식의 Flask로 구성한다.
- Redis는 요청된 데이터를 미리 확인하여 만약 한 번이라도 연산되었던 피연산자와 연산자에 대한 데이터인 경우 연산 과정 없이 결괏값을 바로 반환하기 위함이다.
- MongoDB는 모든 피연산자와 연산자, 그리고 결괏값에 대한 데이터를 저장하기 위함이다.
2. Docker Compose 설치 및 확인
1) docker compose
사용 가능 여부 확인
- 최신 버전의 Docker에서는
sudo docker compose
를 사용할 수 있는데, 먼저 확인해 보자.
- 위와 같이 확인되는 것은 예전 버전이다.
- 이런 경우
sudo docker-compose
를 사용할 수 있도록 별도로 설치해야 한다.
2) docker-compose
설치
- Docker 공식 웹 사이트에서 설치 방법을 확인할 수 있다.
- 리눅스 환경에서 설치하는 방법은 먼저 현재 안정적인 릴리스를 다운로드한다.
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- 그리고 바이너리에 실행 권한을 적용한다.
- 설치 후 만약
docker-compose
명령이 실패하면 경로를 확인한다. /usr/bin
또는 다른 디렉토리에 대한 심볼릭 링크를 생성할 수도 있다.
- 설치가 완료되었는지 확인한다.
- 만약 설치된
docker-compose
를 삭제하려면 다음과 같이 실행한다.
- 참고로
docker-compose
의 명령어 목록은 다음과 같다.
Commands:
build Build or rebuild services
config Validate and view the Compose file
create Create services
down Stop and remove resources
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show version information and quit
3. Docker Compose 구성하기
1) 이미지 가져오기
- Docker Compose를 구성하기 위한 이미지를 가져온다.
sudo docker pull python && \
sudo docker pull nginx && \
sudo docker pull redis && \
sudo docker pull mongo
2) Docker Compose 구성 파일 목록
- 먼저
docker-compose
라는 디렉터리를 생성하는데, 이때 디렉터리의 위치는 중요하지 않다.
- 준비해야 할 Docker Compose 구성 파일 목록은 다음과 같다.
3) app
서비스
- 먼저
app
서비스부터 준비할 것인데 Python 스크립트는 다른 서비스가 준비된 후에 작성하는 것이 좋다.
from flask import Flask, request
from pymongo import MongoClient
from bson.objectid import ObjectId
import redis
import hashlib
import requests
import json
import os
app = Flask(__name__)
redis_client = redis.StrictRedis(host="cache", port=6379, db=0)
mongo_client = MongoClient(host="db", port=27017)
mongo_db = mongo_client["test_db"]
mongo_collection = mongo_db["test_collection"]
@app.route("/", methods=["POST"])
def main():
body = request.get_json(force=True)
str_a = body["a"]
str_b = body["b"]
operator = body["operator"]
int_a = int(str_a)
int_b = int(str_b)
if operator == "+":
result = int_a + int_b
elif operator == "-":
result = int_a - int_b
elif operator == "*":
result = int_a * int_b
elif operator == "/":
result = int_a / int_b
result = str(result)
h = hashlib.new("sha256")
h.update(str_a.encode("utf-8"))
h.update(str_b.encode("utf-8"))
h.update(operator.encode("utf-8"))
h.update(result.encode("utf-8"))
hashed_key = h.hexdigest()
data_dict = {"a": str_a,
"b": str_b,
"operator": operator,
"result": result}
dict_value = json.dumps(data_dict, ensure_ascii=False).encode("utf-8")
redis_client.set(hashed_key, dict_value)
mongo_collection.insert_one(data_dict)
print(f"{str_a} {operator} {str_b} = {result}")
return result
if __name__ == "__main__":
app.run(host="0.0.0.0", port="80", debug=True)
- Docker의
python
이미지를 빌드하여 사용할 것이기 때문에Dockerfile
을 작성한다.
FROM python:latest
LABEL APP Server
MAINTAINER VoicePrint
RUN pip install redis pymongo
RUN pip install requests gunicorn flask gevent
- WSGI로는 Gunicorn을 사용할 것이기 때문에
gunicorn.conf.py
파일을 작성한다.
gunicorn.conf.py
import multiprocessing
# Python Module:Variable
wsgi_app = "app:app"
# Host:Port
bind = "0.0.0.0:9004"
# Worker Class
worker_class = "gevent"
# Num of Workers
# workers = int(multiprocessing.cpu_count() * 0.1)
workers = 1
# Num of Worker Connections
worker_connections = 1024
# Timeout == 0(Deactivate)
timeout = 0
4) proxy
서비스
- Nginx 이미지를 사용하여
proxy
서비스를 구성할 것이다. - 이때 필요한 설정에 맞게
nginx.conf
파일을 작성한다.
nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream application {
server app:9004;
}
server {
listen 9003;
server_name localhost;
location / {
proxy_pass http://application;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
5) docker-compose.yml
작성
- 이제
docker-compose.yml
을 작성할 것인데, 이때networks
를 추가로 생성하여 정적 IP를 유지하도록 할 것이다. cache
와db
에 대한 설정 파일은 작성하지 않지만, 필요한 경우 작성해서volumes
로 연결하면 된다.
docker-compose.yml
version: "3.8"
services:
app:
container_name: "app"
build:
context: .
dockerfile: ./Dockerfile
restart: always
ports:
- "9004:9004"
depends_on:
- cache
- db
working_dir: /Project
entrypoint: ["gunicorn"]
volumes:
- ./gunicorn.conf.py:/Project/gunicorn.conf.py
- ./app.py:/Project/app.py
networks:
app_net:
ipv4_address: 172.16.238.3
proxy:
container_name: "proxy"
image: nginx:latest
restart: always
ports:
- "9003:9003"
depends_on:
- app
working_dir: /
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
app_net:
ipv4_address: 172.16.238.2
cache:
container_name: "cache"
image: redis:latest
restart: always
ports:
- "6379:6379"
working_dir: /
entrypoint: ["redis-server"]
volumes:
- /data:/db
networks:
app_net:
ipv4_address: 172.16.238.4
db:
container_name: "db"
image: mongo:latest
restart: always
ports:
- "27017:27017"
working_dir: /
volumes:
- /data:/data/db
networks:
app_net:
ipv4_address: 172.16.238.5
networks:
app_net:
ipam:
driver: default
config:
- subnet: "172.16.238.0/24"
4. Docker Compose 실행하기
- 이제
docker-compose
명령어를 사용하여 위에서 작성한docker-compose.yml
을 실행시킨다.
- 실행 순서는
cache
및db
→app
→proxy
순이다.
5. Docker Compose 서비스 테스트
- 별도의 터미널을 띄워 다음과 같이 테스트를 해본다.
json='{"a": "1", "b": "1", "operator": "+"}' && \
curl -X POST \
http://14.138.218.153:9003 \
-H 'Content-type: application/json' \
-d "$json"
- 결과는 다음과 같다.
- 실제로
cache
와db
에 데이터가 저장되었는지 확인한다.
test.py
라는 이름의 스크립트를 작성할 것인데, 그전에 에디터를 설치해야 한다.
test.py
스크립트를 작성한다.
test.py
from pymongo import MongoClient
from bson.objectid import ObjectId
import redis
redis_client = redis.StrictRedis(host="cache", port=6379, db=0)
mongo_client = MongoClient(host="db", port=27017)
mongo_db = mongo_client["test_db"]
mongo_collection = mongo_db["test_collection"]
keys = redis_client.keys()
keys = [key.decode("utf-8") for key in keys]
vals = redis_client.mget(keys)
vals = [val.decode("utf-8") for val in vals]
kv = dict(zip(keys, vals))
print(kv)
for document in mongo_collection.find():
print(document)
test.py
스크립트를 실행해 보면cache
와db
에 잘 저장되어 있는 것을 확인할 수 있다.
{'3ad3a37b3c9a4d6e65b58c4a937e65277eba441a807a1f3df49e20011a753539': '{"a": "1", "b": "1", "operator": "+", "result": "2"}'}
{'_id': ObjectId('618393880dfb22aee9700a1e'), 'a': '1', 'b': '1', 'operator': '+', 'result': '2'}