Ficket 프로젝트는 얼굴 인식을 기반으로 한 티켓팅 서비스를 제공합니다. 얼굴 인식 서비스 개발을 위해 빠른 프로토타이핑, 유연한 확장성, 그리고 Python 기반 환경에서의 효율적인 통합이 중요했습니다. 이를 충족하기 위해 경량 웹 프레임워크인 Flask를 서버 애플리케이션 프레임워크로 채택했습니다. Flask는 간단하고 직관적인 구조를 제공하여 얼굴 인식 기능을 효과적으로 구현할 수 있는 최적의 선택이었습니다.
또한, 서비스의 확장성과 유지보수성을 강화하기 위해 마이크로서비스 아키텍처(MSA)를 도입했습니다. 이를 기반으로 Eureka를 활용해 서비스 디스커버리를 구현하고, Spring Config 서버를 통해 중앙 집중식 설정 관리를 수행하며, RabbitMQ를 이용해 설정 변경 사항을 실시간으로 반영하는 시스템을 설계했습니다.
이 글에서는 Flask 애플리케이션을 Eureka 서버에 등록하고, Spring Config 서버에서 설정 정보를 가져오며, RabbitMQ를 통해 실시간으로 업데이트를 적용하는 과정을 단계별로 설명합니다.
VS Code에서 Ctrl + J를 누르면 터미널 창을 사용할 수 있습니다.
mkdir ficket-face
cd ficket-face
python -m venv venv
cd venv
cd Scripts
activate.bat
cd .. // flask run은 홈디렉토리에서 실행하는 것을 추천
cd ..
Flask를 설치합니다.
pip install flask
프로젝트 디렉토리에 app.py
파일을 생성하고 다음 코드를 작성합니다.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Hello, Flask!'
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000, debug=True)
Flask 애플리케이션을 실행합니다.
python app.py
브라우저에서 http://127.0.0.1:5000으로 접속하여 "Hello, Flask!" 메시지를 확인합니다.
Spring Config 서버에서 암호화된 설정 데이터를 가져와 복호화하는 과정을 구현합니다.
프로젝트 디렉토리에 decrypt_utils.py
파일을 생성하고 다음 코드를 작성합니다.
import base64
from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES, PKCS1_v1_5
from Crypto.PublicKey import RSA
def load_private_key(path):
with open(path, 'r') as f:
return RSA.importKey(f.read())
def unpad(s):
return s[:-ord(s[len(s) - 1:])]
def decrypt(data, private_key, salt):
length = int.from_bytes(data[0:2], byteorder="big")
random = data[2:length + 2]
cipher = PKCS1_v1_5.new(private_key)
iv = cipher.decrypt(random, sentinel=None)
password = iv.hex()
aes_secret_key = pbkdf2_hmac('sha1', password.encode("utf-8"), bytearray.fromhex(salt), 1024, 32)
cipher_aes = AES.new(aes_secret_key, AES.MODE_CBC, iv)
aes_encrypted = cipher_aes.decrypt(data[2 + len(random):])
return unpad(aes_encrypted).decode("utf-8")
def decrypt_value(value, private_key, salt='deadbeef'):
if not value.startswith("{cipher}"):
return value
value = value[len("{cipher}"):]
data = base64.b64decode(value.encode("utf-8"))
return decrypt(data, private_key, salt)
config.py
파일을 생성하고 Spring Config 서버에서 데이터를 로드하는 코드를 작성합니다.
import requests
from decrypt_utils import load_private_key, decrypt_value
def load_config_from_server():
CONFIG_SERVER_URL = "http://localhost:8888/face-service/local"
PRIVATE_KEY_PATH = "private.pem"
try:
private_key = load_private_key(PRIVATE_KEY_PATH)
if private_key is None:
raise ValueError("Private key could not be loaded")
response = requests.get(CONFIG_SERVER_URL)
response.raise_for_status()
config_data = response.json()
config = {"mysql": {}, "encryption": {}, "rabbitmq": {}, "aws": {}}
property_sources = config_data.get("propertySources", [])
for source in property_sources:
source_data = source["source"]
if "flask.mysql.url" in source_data:
config["mysql"]["url"] = source_data["flask.mysql.url"]
if "flask.mysql.password" in source_data:
config["mysql"]["password"] = decrypt_value(source_data["flask.mysql.password"], private_key)
if "encryption.secret_key" in source_data:
config["encryption"]["secret_key"] = decrypt_value(source_data["encryption.secret_key"], private_key)
if "spring.rabbitmq.host" in source_data:
config["rabbitmq"]["host"] = source_data["spring.rabbitmq.host"]
if "spring.rabbitmq.username" in source_data:
config["rabbitmq"]["username"] = source_data["spring.rabbitmq.username"]
if "spring.rabbitmq.password" in source_data:
config["rabbitmq"]["password"] = decrypt_value(source_data["spring.rabbitmq.password"], private_key)
return config
except requests.RequestException as e:
print(f"Failed to load configuration from Config Server: {e}")
return {}
rabbitmq_listener.py
파일을 생성하고 다음 코드를 작성합니다.
import pika
import threading
from config import load_config_from_server
def start_rabbitmq_listener(config, app):
RABBITMQ_HOST = config["rabbitmq"]["host"]
RABBITMQ_USERNAME = config["rabbitmq"]["username"]
RABBITMQ_PASSWORD = config["rabbitmq"]["password"]
QUEUE_NAME = "springCloudBus"
credentials = pika.PlainCredentials(RABBITMQ_USERNAME, RABBITMQ_PASSWORD)
connection = pika.BlockingConnection(pika.ConnectionParameters(host=RABBITMQ_HOST, credentials=credentials))
channel = connection.channel()
channel.queue_declare(queue="springCloudBusByFlask")
channel.queue_bind(exchange=QUEUE_NAME, queue="springCloudBusByFlask", routing_key="#")
def callback(ch, method, properties, body):
print("Configuration update received.")
new_config = load_config_from_server()
app.config.update(new_config)
channel.basic_consume(queue="springCloudBusByFlask", on_message_callback=callback, auto_ack=True)
print("Waiting for configuration updates...")
channel.start_consuming()
def start_rabbitmq_listener_thread(config, app):
thread = threading.Thread(target=start_rabbitmq_listener, args=(config, app))
thread.daemon = True
thread.start()
eureka_client_setup.py
파일을 생성하고 다음 코드를 작성합니다.
import py_eureka_client.eureka_client as eureka_client
def initialize_eureka_client():
eureka_client.init(
eureka_server="http://localhost:8761/eureka",
app_name="face-service",
instance_host="localhost",
instance_port=5000
)
app.py
파일을 수정해 Config 서버, RabbitMQ, Eureka를 통합합니다.
from flask import Flask
from config import load_config_from_server
from rabbitmq_listener import start_rabbitmq_listener_thread
from eureka_client_setup import initialize_eureka_client
app = Flask(__name__)
@app.route('/')
def home():
return 'Welcome to Ficket!'
if __name__ == '__main__':
config = load_config_from_server()
app.config.update(config)
start_rabbitmq_listener_thread(config, app)
initialize_eureka_client()
app.run(host='127.0.0.1', port=5000, debug=True)
Eureka 클라이언트로 등록: 서비스 디스커버리 지원.
Spring Config 서버와 통합: 중앙 집중식 설정 관리.
RabbitMQ를 통한 실시간 설정 업데이트: 동적 환경 변화 반영.