챗봇 Flask 배포기 (feat.GCP)

미키오·2025년 3월 22일
6
post-thumbnail

0. 들어가며..

지난주, 머기업 인사팀에 다니고 있는 친구가 SOS를 보냈다.

교육계획안 자동 생성 프로젝트 Gapple에서 GPT API를 다뤄본 경험이 있어서, 이론 상으로는 하루만에도 만들 수 있을 것 같았다.
그렇게 프론트엔드는 만난지 10분만에 React로 간단한 채팅 화면을 뚝딱 만들어 냈다. GPT 만세

이어서 GPT API 가져오는 일도 금방 끝났다. 코드잇 만세

근데 친구가 이걸로 PT를 하려면 배포해야하지 않나??
그렇다, 배포를 간과하고 있었다. 늘 나의 프로젝트는 대신 배포해주는 훌륭한 백엔드들이 많았다..

Python으로 간단한 로컬 프로젝트, 회사 내부 자동화는 종종 해봤지만, 제대로 된 서버 환경에서 동작시키고, 클라우드에 올리는 일은 정말 처음이었다. 이러한 기회에 도전하게 된 Flask + GCP 배포!

1. 🧪 Flask

Flask는 파이썬 언어로 작성된 심플하고 가벼운 웹 프레임워크이다. Django처럼 배터리 포함(batteries-included)이 아닌, 필요한 기능을 골라 붙일 수 있는 최소주의(Microframework) 방식이라는 게 가장 큰 특징이다. 덕분에 배우기 쉽고, 특히나 챗봇 화면만 필요한 이번 프로젝트에 적합했다.

Why Flask?

  • 가벼운 규모의 웹 서비스

“기본 챗봇 기능에, 몇몇 REST API만 제공” 하는 수준인 이 웹에 적합했다. 이 웹(채용봇)은 Form 입력 → 챗봇 API 호출 → 결과 출력 정도의 로직을 수행한다. 대규모 ORM, Admin 기능 등이 필요하지 않았고, 가볍게 API만 노출하면 충분했다.

  • 파이썬에 대한 익숙함 + 초보에게도 직관적

이미 파이썬 문법에는 친숙했지만, 백엔드 쪽 프레임워크를 복잡하게 배우고 싶진 않았다.. (물리적으로 그럴 시간도 없다) 어차피 프로토타입이다.

또한 Flask는 한두 개의 파일만으로도 API 서버를 돌릴 수 있을 만큼 구조가 단순하고, 실제로 써보니 실험적·프로토타입 프로젝트(채용봇)에 딱 맞는 민첩함이 돋보였다.

  • 확장성과 유연성

필요하면 Flask-Cors, Gunicorn, ORM 라이브러리들을 골라 붙일 수 있다. “이번 웹은 리소스가 많지 않고, 나중에 조금씩 기능을 붙여도 충분하겠군!” 생각했기에, Flask를 고르고 확장성을 열어두었다.

Flask 환경 구성

Python 가상환경 & 의존성 설치

파이썬 프로젝트이다. 피로그래밍에서 배운 것처럼 Python 가상환경(venv) 을 만들고 진입해보자.

python3 -m venv venv
source venv/bin/activate
  • 가상환경을 만드는 것은 프로젝트마다 버전 충돌을 피하기 위해서이다!
    그리고 requirements.txt에 필요한 라이브러리를 적어둔다.
flask
flask-cors
gunicorn
openai
python-dotenv

이제 pip install -r requirements.txt 명령어로 한 번에 설치!

폴더구조라고 하기에도 민망한 간단함

my-flask-project/
 ┣ app.py
 ┣ requirements.txt
 ┗ .env

app.py: Flask 메인 로직

.env: OpenAI API 키 등 민감정보

requirements.txt: Python 의존성 목록

Flask 기본 구조

import os
from flask import Flask, request, jsonify
from flask_cors import CORS
from openai import OpenAI  
from dotenv import load_dotenv

import:

  • os: 환경 변수나 시스템 경로를 다룰 때 사용

  • Flask & request, jsonify: Flask 웹 프레임워크와 HTTP 요청/응답 처리

  • CORS: 크로스 도메인 요청 허용을 위한 라이브러리

  • OpenAI: OpenAI API (chat.completions)를 사용하기 위한 라이브러리

  • dotenv: .env 파일 로드 (API 키 같은 민감정보를 안전하게 관리)

# 환경 변수 로드
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

.env 파일에서 환경 변수 불러오기:

load_dotenv(): 현재 디렉터리에 있는 .env 파일을 자동으로 읽어와 환경변수로 설정

OPENAI_API_KEY: .env 파일에 정의된 OPENAI_API_KEY 값을 파이썬 변수에 저장

만약 .env 파일에 OPENAI_API_KEY=sk-... 라고 적혀있다면, 이 코드로 Flask 앱이 실행될 때 자동으로 그 키를 가져온다.

app = Flask(__name__)
CORS(app, resources={r"/chat": {"origins": "*"}})

Flask 앱 & CORS 설정:

app = Flask(name): Flask 인스턴스를 생성

CORS(app, resources={r"/chat": {"origins": "*"}}): CORS 설정으로, /chat 경로에 대해서 모든 도메인(origins="*")에서 오는 요청을 허용

React 클라이언트(예: localhost:3000)가 이 서버에 AJAX 요청을 보내도 CORS 에러 없이 통신 가능 (배포용은 실제 도메인 적용)

# ✅ 최신 OpenAI 클라이언트 초기화
client = OpenAI(api_key=OPENAI_API_KEY)

OpenAI API 클라이언트 초기화:

OpenAI 라이브러리로 Chat Completion을 호출할 수 있는 인스턴스를 생성하고 위에서 가져온 OPENAI_API_KEY를 여기서 사용한다.

이후에 client.chat.completions.create(...) 식으로 GPT 모델에 메시지를 보낼 때 사용한다.

@app.route("/chat", methods=["POST"])
def chat():
    """React에서 받은 메시지를 OpenAI API에 전달하고 응답 반환"""
    try:
        data = request.json
        user_input = data.get("message", "").strip()

라우트 설정 (/chat):

@app.route("/chat", methods=["POST"]): URL /chat로 들어오는 POST 요청을 처리할 함수 chat()를 등록

data = request.json: 클라이언트(React 등)에서 보낸 JSON 바디를 파싱

user_input = data.get("message", "").strip(): 메시지 필드가 없으면 기본값(""), 있으면 문자열 앞뒤 공백 제거

        if not user_input:
            return jsonify({"error": "메시지가 비어 있습니다."}), 400

빈 메시지 예외 처리:

사용자 입력이 비어 있으면 HTTP 400(Bad Request) 응답과 함께 에러 메시지 반환

        messages = [
            {"role": "system", "content": "당신은 00회사의 채용 전문가 AI 챗봇입니다. 00회사나 채용과 관련한 질문이 아니면 다시 00회사나 채용과 관련한 질문을 할 수 있도록 유도해주세요."},
            {"role": "user", "content": user_input}
        ]

OpenAI Chat에 보낼 메시지 구성:

  • Chat Completion API는 역할(system, user, assistant)과 content를 기반으로 대화를 진행

  • 여기서는 system 역할에 “00회사의 채용 전문가 AI”라는 컨텍스트를 부여하고,

  • user 메시지에 user_input을 담아 보냄. 이 부분은 추가로 프롬프트 엔지니어링중이다!

        if not OPENAI_API_KEY:
            return jsonify({"error": "OpenAI API 키가 설정되지 않았습니다."}), 500

API 키 미설정 시 예외 처리:

.env에 키가 없거나 설정을 깜빡했다면 500 에러 반환

        # ✅ 최신 OpenAI API 호출 방식으로 수정
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            temperature=0.5
        )

        assistant_reply = response.choices[0].message.content
        return jsonify({"reply": assistant_reply})

GPT 모델 호출 & 결과 응답:

model="gpt-4o-mini": 임의로 지정한 모델이름(예: GPT-3.5, GPT-4 등). 싼 모델 중에서 제일 좋다.

messages=messages: 앞서 만든 대화 맥락을 전달

temperature=0.5: 모델의 창의성(무작위성) 정도

assistant_reply: GPT가 생성한 답변 텍스트

jsonify({"reply": ...}): JSON 형태로 응답을 반환 → React가 이걸 받아 챗봇 UI에 표시

    except Exception as e:
        print(f"🔴 서버 오류 발생: {str(e)}")  # 오류 로그 출력
        return jsonify({"error": str(e)}), 500

예외 처리:

API 호출 중 에러(네트워크, OpenAI 문제, 키 유효성 등) 발생 시

콘솔에 빨간 경고를 찍고, {"error": ...}, HTTP 500 으로 응답

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8080))  # Cloud Run이 설정한 포트 사용
    app.run(host="0.0.0.0", port=port)

Flask 앱 실행 (개발모드):

if name == "main": 이 스크립트가 직접 실행될 때만 아래 코드 실행

os.environ.get("PORT", 8080): Cloud Run이나 Heroku 같은 PaaS 환경에서 동적으로 배정된 포트를 받아 사용

app.run(host="0.0.0.0", port=port): 모든 인터페이스(0.0.0.0)로 바인딩해서 외부 요청도 수신

2. Flask에서 GCP로 가는 길

위처럼 Flask 코드를 완성했다면, 이제 실제 웹에서 접근 가능한 서버로 만들어야 한다. 바로 “배포” 과정이다. 필자는 GCP(Google Cloud Platform)를 통해 서버리스 컨테이너 배포인 Cloud Run을 사용했는데, 그 과정을 대략 정리해보면 다음과 같다.

Gunicorn 도입 (프로덕션 서버용)

Flask 내장 서버는 개발 테스트용으로 적합하지만, 실제 프로덕션 환경에선 Gunicorn 같은 WSGI 서버가 더 안정적이고 효율적이다.

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .  # app.py, .env, etc.

# Gunicorn으로 Flask 실행 (4개 워커)
CMD exec gunicorn -w 4 -b 0.0.0.0:$PORT app:app

참고: PORT는 Cloud Run 같은 PaaS 환경에서 자동으로 할당받는 포트 번호이다.

Docker 컨테이너 빌드 & 푸시

로컬에서 Docker 이미지 빌드

내 컴은 M2 Mac(ARM 환경)이라 처음에는 오류가 났다. M1/M2 Mac처럼 ARM 환경의 경우에는 Cloud Run이 요구하는 linux/amd64 아키텍처와 달라서 오류가 발생할 수 있으니, docker buildx를 사용해 멀티플랫폼 빌드를 해야 한다.

# 로컬(PC)에서:
docker buildx create --use
docker buildx build --platform linux/amd64 \
  -t asia.gcr.io/YOUR_PROJECT_ID/flask-hirebot:latest \
  --push .
  • asia.gcr.io/YOUR_PROJECT_ID/flask-hirebot:latestGCP Container Registry(GCR)에 업로드할 경로

  • --platform linux/amd64Cloud Run이 지원하는 아키텍처

GCP Artifact Registry vs Container Registry

  • Container Registry(GCR) : asia.gcr.io/... 형태

  • Artifact Registry(GAR) : asia-northeast3-docker.pkg.dev/... 형태

둘 다 Docker 이미지를 저장하는 GCP 서비스지만, 초심자는 GCR이 좀 더 간단한 편이라 GCR로 진행했다.

GCP Cloud Run 배포

이제 Cloud Shell에서 다음 명령어를 실행하면 배포를 완료할 수 있다.

gcloud run deploy flask-hirebot \
  --image asia.gcr.io/YOUR_PROJECT_ID/flask-hirebot:latest \
  --platform managed \
  --region asia-northeast3 \
  --allow-unauthenticated
  • flask-hirebot : Cloud Run 서비스 이름

  • --allow-unauthenticated : 누구나 접근 가능한 공개 주소

  • --region asia-northeast3 : 한국 리전(서울)을 예시로 사용

배포에 성공하면 다음과 같은 메시지가 뜬다 :

여기서 주는 url을 이제 프런트엔드에 적용하면 된다!

마무리

드디어 처음부터 끝까지 혼자 힘으로 백엔드를 구축하고, 클라우드 환경에 배포까지 완료했다. 웹 동화 제작부터 시작된 개발 여정이 벌써 3년차. 그동안 주로 프론트엔드 개발자로서 React만 열심히 했는데, 최근 다양한 개발을 경험하면서 어느새 백엔드까지 혼자 배포할 수 있게 된 건 나름의 큰 성과였다. 🎉

사실 처음엔 "배포"라는 말만 들어도 두려웠는데, GCP의 Cloud Run 덕분에 컨테이너만 제대로 준비하면 CLI 몇 줄로 세상에 내 프로젝트를 공개할 수 있다는 걸 알게 되었다. 특히 Python과 Flask 덕분에 프로토타입부터 실제 배포까지 빠르게 해낼 수 있었다.

채용 챗봇은 더 완벽한 프롬프트 엔지니어링을 통해 정확하고 친절한 채용 전문 챗봇으로 계속 업그레이드할 예정이다.

개발을 배워서 주변의 누군가에게 실질적인 도움을 줄 수 있다는 게 정말 즐겁고 뿌듯했다. 개발자라는 직업은 떠났지만, 역시 취미용 개발은 재미있다! 😆

profile
교육 전공 개발자 💻

0개의 댓글