안녕하세요, '소프트웨어 공장'에 오신 것을 환영합니다!
지난 포스팅에서는 Python의 고급 기능들을 엮어 실시간 주가 데이터를 수집하고 시각화하는 파이프라인의 뼈대를 만들어보았습니다. 로컬 환경에서 코드가 잘 돌아가는 것을 확인했다면, 이제 다음 단계로 나아갈 차례입니다. 아무리 멋진 애플리케이션이라도 내 컴퓨터에서만 돌아간다면 진정한 '서비스'라고 부르기 어렵겠죠?
이번 포스팅에서는 지난 프로젝트를 한 단계 더 업그레이드해 보겠습니다. 실무에서 가장 요구하는 백그라운드 알림, JWT 기반 사용자 인증, 그리고 머신러닝 예측 기능을 얹고, 이를 Docker와 GitHub Actions를 통해 클라우드 서버에 자동으로 배포하는 전 과정을 다뤄보겠습니다. 토이 프로젝트를 실제 상용 서비스 수준으로 빌드업하는 재미를 느껴보세요!

📌 핵심 요약 3줄
- 기능적 확장: 백그라운드 모니터링(알림), JWT 보안(인증), 선형 회귀(예측) 알고리즘을 추가하여 앱의 완성도를 높입니다.
- 컨테이너화 배포: Docker를 활용해 개발 환경과 운영 환경의 격차를 줄이고 어디서나 동일하게 구동되는 인프라를 구축합니다.
- 배포 자동화: GitHub Actions를 이용해 코드가 메인 브랜치에 반영되는 순간 자동으로 빌드부터 서버 반영까지 이어지는 CI/CD를 구현합니다.
📈 프로젝트 확장 및 배포 로드맵
이번 고도화 작업은 크게 기능 확장(Application)과 인프라 구축(DevOps) 두 축으로 나누어 진행됩니다. 각 단계에서 구현할 핵심 기술 스택과 상세 역할은 아래 표와 같습니다.
| 분류 | 확장 단계 | 도입 기술 | 핵심 역할 및 이점 |
| 기능 확장 | 1. 알림 시스템 | FastAPI, asyncio | 백그라운드 태스크로 주기적인 주가 감시 및 조건 충족 시 알림 트리거 |
| 2. 사용자 인증 | JWT (JSON Web Token) | Stateless한 인증 방식으로 서버 부담을 줄이고 개인화된 데이터 접근 제어 | |
| 3. 데이터 예측 | scikit-learn | 과거 주가 데이터를 학습하여 선형 회귀 기반의 미래 가격 예측치 제공 | |
| 인프라/배포 | 1. 컨테이너화 | Docker | 애플리케이션 실행 환경을 이미지로 패키징하여 환경 의존성 문제 해결 |
| 2. 클라우드 배포 | AWS EC2 | 클라우드 가상 서버 환경에 도커 컨테이너를 구동하여 24시간 서비스 제공 | |
| 3. CI/CD 자동화 | GitHub Actions | 코드 변경 시 빌드, 테스트, 도커 허브 푸시, 서버 배포까지 원클릭 자동화 |
1. 사용자 알림 시스템 구현 (FastAPI & Asyncio)
사용자가 매번 화면을 새로고침하지 않아도, 설정한 목표가에 도달하면 시스템이 먼저 반응해야 합니다. FastAPI의 스타트업 이벤트와 asyncio의 무한 루프를 활용해 메인 API 스레드를 방해하지 않는 백그라운드 모니터링을 구현합니다.
import asyncio
from fastapi import FastAPI
app = FastAPI()
# 사용자가 등록한 감시 종목과 목표가 정보
watchlist = {"AAPL": 150.0, "GOOGL": 2800.0}
async def price_monitor():
"""메인 흐름과 별개로 백그라운드에서 주기적으로 주가를 감시하는 함수입니다."""
while True:
# 실제 운영 환경에서는 외부 주가 API를 비동기로 호출하는 로직이 들어갑니다.
current_prices = {"AAPL": 155.0, "GOOGL": 2790.0} # 예시 데이터
for symbol, target_price in watchlist.items():
if current_prices[symbol] >= target_price:
# 조건 충족 시 알림 트리거 (Slack, 이메일 연동 등으로 확장 가능)
print(
f"[알림] {symbol}의 현재 가격이 목표가({target_price})에 도달했습니다! 현재가: {current_prices[symbol]}"
)
# 60초 동안 대기했다가 다시 루프를 돕니다.
await asyncio.sleep(60)
@app.on_event("startup")
async def startup_event():
# 애플리케이션이 시작될 때 비동기 백그라운드 태스크로 모니터링을 등록합니다.
asyncio.create_task(price_monitor())
이렇게 asyncio.create_task를 사용하면 FastAPI가 클라이언트의 HTTP 요청을 처리하는 와중에도 백그라운드에서 1분마다 주가를 감시하는 루프가 독립적으로 돌아갑니다.
2. 사용자 인증 및 개인 대시보드 (JWT)
회원제 서비스로 가기 위한 첫 관문은 보안입니다. 세션을 쓰지 않고 토큰 자체에 정보를 담는 JWT(JSON Web Token) 방식을 도입하면, 마이크로서비스나 분산 환경에서도 가볍고 안전하게 인증을 처리할 수 있습니다.
from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
import jwt
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 가상의 유저 데이터베이스 (실무에서는 암호화하여 DB에 저장해야 합니다)
fake_users_db = {"john": {"username": "john", "password": "secret"}}
SECRET_KEY = "your_super_secret_key_dont_share"
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
"""사용자가 아이디/비밀번호를 전송하면 검증 후 JWT 토큰을 발급합니다."""
user = fake_users_db.get(form_data.username)
if not user or user["password"] != form_data.password:
raise HTTPException(status_code=400, detail="인증 정보가 올바르지 않습니다.")
# 토큰의 유효 기간이나 권한(Claim) 정보를 페이로드에 담아 서명합니다.
token = jwt.encode({"sub": user["username"]}, SECRET_KEY, algorithm="HS256")
return {"access_token": token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
"""발급받은 토큰을 헤더에 실어 보내면 유저 정보를 해석해 반환합니다."""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
username = payload.get("sub")
if username is None:
raise HTTPException(
status_code=401, detail="인증 토큰이 유효하지 않습니다."
)
return {"username": username, "status": "authenticated"}
except jwt.PyJWTError:
raise HTTPException(
status_code=401, detail="토큰을 디코딩하는 데 실패했습니다."
)
인증이 완료된 유저에게만 /users/me 엔드포인트를 열어줌으로써, 추후 개인 관심 종목 리스트나 커스텀 대시보드 레이아웃을 안전하게 매핑할 수 있는 기반이 마련되었습니다.
3. 데이터 분석 및 예측 기능 추가 (scikit-learn)
수집된 과거 데이터를 단순히 보여주는 것을 넘어, 아주 간단한 머신러닝 알고리즘을 얹어 미래 트렌드를 예측해 보겠습니다. 여기서는 단순 흐름 파악을 위해 scikit-learn의 선형 회귀(Linear Regression) 모델을 활용합니다.
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
# 과거 5일간의 가상 주가 데이터 테이블 생성
data = {"Day": [1, 2, 3, 4, 5], "Price": [150, 152, 153, 155, 157]}
df = pd.DataFrame(data)
# 독립변수(X)와 종속변수(y) 설정
X = df[["Day"]]
y = df["Price"]
# 선형 회귀 모델 초기화 및 학습
model = LinearRegression()
model.fit(X, y)
def predict_price(day):
"""지정한 일차(Day)의 주가를 예측하는 함수입니다."""
prediction = model.predict([[day]])
return prediction[0]
# 6일 차의 예상 주가 출력
print(f"👉 Day 6의 예측 가격은 {predict_price(6):.2f} 달러입니다.")
비록 실제 주가는 이보다 훨씬 복잡한 변수들에 의해 결정되지만, 파이프라인 관점에서는 이러한 예측 스크립트를 API나 Dash 대시보드 내부에 스케줄러로 녹여내어 매일 아침 예측치를 자동으로 갱신하도록 구성할 수 있습니다.
🐳 프로젝트 배포: 컨테이너화부터 자동화까지
개발이 끝난 애플리케이션을 외부 사용자가 접속할 수 있는 퍼블릭 환경에 띄우는 배포 작업을 진행해 보겠습니다. 인프라 구축의 핵심은 '자동화'와 '환경의 격리'입니다.
1단계: Docker를 이용한 배포 환경 컨테이너화
Dockerfile을 작성하여 내 컴퓨터의 라이브러리 버전이나 파이썬 패키지 설정 그대로 클라우드 인프라에 이식합니다.
# 가볍고 안정적인 파이썬 경량 이미지를 베이스로 선택합니다.
FROM python:3.9-slim
# 컨테이너 내 작업 디렉토리 설정
WORKDIR /app
# 종속성 파일을 먼저 복사하여 레이어 캐싱 이점을 활용합니다.
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# 나머지 소스 코드를 전부 복사합니다.
COPY . .
# FastAPI 서버가 외부 접근을 허용하도록 호스트와 포트를 바인딩하여 실행합니다.
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
2단계: 클라우드 서버(AWS EC2) 환경 배포 및 구동
작성한 Dockerfile을 기반으로 이미지를 빌드하고 백그라운드 데몬으로 컨테이너를 구동합니다. 터미널 명령어로 간단히 제어할 수 있습니다.
# 1. 도커 이미지 빌드 (태그 이름: stock-analysis-app)
$ docker build -t stock-analysis-app .
# 2. 컨테이너를 백그라운드(-d)에서 포트 8000번을 개방하여 실행
$ docker run -d -p 8000:8000 --name stock-service stock-analysis-app
3단계: GitHub Actions 기반의 CI/CD 자동화 파이프라인
코드가 main 브랜치에 푸시되면 자동으로 빌드 검증을 거쳐 실서버에 무중단으로 반영되도록 워크플로우를 설정합니다. .github/workflows/deploy.yml 경로에 아래 스크립트를 위치시킵니다.
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 소스코드 체크아웃
uses: actions/checkout@v3
- name: Docker Buildx 설정
uses: docker/setup-buildx-action@v2
- name: Docker Hub 로그인
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 도커 이미지 빌드 및 푸시
run: |
docker build -t stock-analysis-app .
docker tag stock-analysis-app:latest yourdockerhubusername/stock-analysis-app:latest
docker push yourdockerhubusername/stock-analysis-app:latest
- name: 원격 클라우드 서버(SSH) 배포 명령 전송
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SERVER_IP }}
username: ec2-user
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 기존 컨테이너 중지 및 제거 후 최신 이미지 풀링 및 재실행
docker stop stock-service || true
docker rm stock-service || true
docker pull yourdockerhubusername/stock-analysis-app:latest
docker run -d -p 8000:8000 --name stock-service yourdockerhubusername/stock-analysis-app:latest
🛠️ 개발을 위한 팁 (Tips for Developers)
- 환경 변수의 철저한 관리: JWT의 SECRET_KEY나 DB 접속 정보, AWS 비밀키 같은 민감한 데이터는 코드에 하드코딩하면 절대 안 됩니다. .env 파일을 만들어 관리하고, GitHub Actions에서는 Secrets 보관함을 사용하여 안전하게 주입해 주세요.
- Docker 빌드 속도 최적화: requirements.txt 복사와 패키지 설치(pip install) 단계를 소스 코드 복사(COPY . .)보다 윗줄에 두어야 합니다. 코드 한 줄 고칠 때마다 매번 패키지를 새로 다운로드하는 시간 낭비를 막을 수 있습니다.
- 가벼운 도커 이미지 선택: python:3.9 풀 버전 이미지 대신 python:3.9-slim이나 alpine 계열을 사용하면 이미지 용량을 수백 MB 이상 줄일 수 있어 배포 속도가 비약적으로 빨라집니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- 비동기 이벤트 루프 차단(Blocking): price_monitor 내부의 무한 루프에서 time.sleep(60)을 사용하면 절대 안 됩니다. 이는 전체 스레드를 멈추게 만들어 FastAPI 서버 전체가 먹통이 됩니다. 반드시 비동기 대기 함수인 await asyncio.sleep(60)을 사용해야 합니다.
- JWT 비밀키 분실 및 노출: 소스 코드를 퍼블릭 GitHub 레포지토리에 올리다가 SECRET_KEY가 유출되는 사고가 흔히 발생합니다. 봇 프로그램들이 노출된 키를 자동으로 스캔하므로, 유출되었다면 즉시 키를 재생성하고 배포 환경의 환경 변수를 교체해야 합니다.
- 머신러닝 예측 데이터 스케일링 누락: 데이터의 단위 차이가 큰 다변량 회귀 분석으로 확장할 때, 피처 스케일링(StandardScaler 등)을 누락하면 특정 변수가 모델을 왜곡할 수 있습니다. 전처리 파이프라인을 항상 모델과 묶어 관리하는 버릇을 들여야 합니다.
🏁 마치며
이번 포스팅에서는 단순한 파이프라인 구현을 넘어, 실제 상용 서비스로 나아가기 위한 백그라운드 관리, 보안 시스템 구축, 그리고 현대 개발 아키텍처의 핵심인 CI/CD 배포 자동화까지 모두 섭렵해 보았습니다.
개발자로서 내 아이디어가 코드가 되고, 그 코드가 클라우드 인프라를 타고 전 세계 사용자에게 닿는 배포 단계야말로 가장 짜릿한 순간이 아닐까 싶습니다. 오늘 다룬 배포 자동화 환경을 한 번 잘 구축해 두면, 앞으로 어떤 프로젝트를 진행하든 마우스 클릭 한 번 없이 메인 서버를 업데이트하는 마법을 경험하실 수 있을 겁니다.
작업 도중 도커 에러가 나거나 GitHub Actions 액션 스크립트에서 막히는 부분이 있다면 주저 말고 댓글로 에러 로그를 남겨주세요. 같이 고민해 드리겠습니다. 감사합니다!