🚀 평생 무료 AI 중계 서버 만들기: 스트리밍 + 무한 키 로테이션 + HTTPS 완벽 가이드

2026. 1. 6.

 

AI 서비스를 개발하다 보면 세 가지 큰 벽에 부딪힙니다.

  1. 비용: API 호출 비용과 서버 유지비.
  2. 속도: AI가 답변을 다 쓸 때까지 30초 넘게 기다려야 하는 답답함.
  3. 제한(Rate Limit): 무료 API 키의 호출 횟수 제한.

오늘은 오라클 클라우드(Oracle Cloud)의 평생 무료 서버를 활용하여, Google Gemini API실시간 스트리밍(타자 치는 효과)으로 중계하고, 여러 개의 API 키를 자동으로 돌려쓰며 한도 제한을 우회하는 완벽한 서버를 구축해 보겠습니다.


🏗️ 아키텍처 (Architecture)

우리가 만들 시스템의 구조는 다음과 같습니다.

  • Client (JS): fetch API와 Reader를 사용해 데이터를 실시간으로 수신.
  • Web Server (Nginx): HTTPS 보안 처리 및 버퍼링 없는 스트림 전송 보장.
  • App Server (Python Flask + Gunicorn): API 키 로드밸런싱 및 Google API 스트림 중계.

📂 1단계: Python Flask 서버 구현 (app.py)

가장 핵심이 되는 파이썬 코드입니다.
기능:

  1. API 키 로테이션: 여러 개의 키 중 랜덤 선택, 429(제한) 에러 시 자동 재시도.
  2. 스트리밍(SSE): 구글에서 오는 데이터를 조각내어 클라이언트에 즉시 전달.
from flask import Flask, request, Response, stream_with_context
import requests
import json
import os
import random

app = Flask(__name__)

# 환경변수에서 API 키 리스트 로드 (콤마로 구분)
# 예: export GOOGLE_API_KEYS="key1,key2,key3..."
api_keys_str = os.environ.get("GOOGLE_API_KEYS", "")
ALL_KEYS = [k.strip() for k in api_keys_str.split(",") if k.strip()]

# 사용할 모델 설정 (Flash 모델이 속도가 빠름)
MODEL_NAME = "gemini-2.5-flash"

def get_stream_from_google(payload, tried_keys=None):
    """
    구글 API에 스트림 요청을 보내고, 
    429 에러 발생 시 다른 키로 재귀적으로 재시도하는 함수
    """
    if tried_keys is None:
        tried_keys = set()

    if len(tried_keys) >= len(ALL_KEYS):
        yield json.dumps({"error": "All API keys exhausted"}).encode('utf-8')
        return

    # 사용 가능한 키 중 랜덤 선택
    available_keys = list(set(ALL_KEYS) - tried_keys)
    if not available_keys:
        yield json.dumps({"error": "No keys available"}).encode('utf-8')
        return

    current_key = random.choice(available_keys)

    # URL에 alt=sse를 붙여야 Server-Sent Events 형식으로 받음
    target_url = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL_NAME}:streamGenerateContent?alt=sse&key={current_key}"
    headers = {"Content-Type": "application/json"}

    try:
        # stream=True 옵션 필수
        with requests.post(target_url, json=payload, headers=headers, stream=True) as resp:

            # 429(Too Many Requests) 또는 503 발생 시 재시도
            if resp.status_code in [429, 503]:
                print(f"Key {current_key[:5]}... limited! Retrying...")
                tried_keys.add(current_key)
                yield from get_stream_from_google(payload, tried_keys)
                return

            # 정상 응답이면 데이터 조각(Chunk)을 즉시 클라이언트로 토스
            for line in resp.iter_lines():
                if line:
                    decoded_line = line.decode('utf-8')
                    if decoded_line.startswith('data: '):
                        try:
                            # 구글 데이터 파싱
                            json_str = decoded_line[6:]
                            json_data = json.loads(json_str)
                            # 텍스트 추출 (구조는 모델 버전에 따라 다를 수 있음)
                            text_chunk = json_data['candidates'][0]['content']['parts'][0]['text']

                            # 클라이언트에게 바로 전송 (yield)
                            yield text_chunk.encode('utf-8')
                        except:
                            pass

    except Exception as e:
        yield str(e).encode('utf-8')

@app.route('/', methods=['POST'])
def proxy_stream():
    # Flask의 stream_with_context를 사용하여 제너레이터 반환
    return Response(stream_with_context(get_stream_from_google(request.json)), 
                    content_type='text/plain; charset=utf-8')

if __name__ == "__main__":
    # Gunicorn 실행 시 이 부분은 무시됨
    app.run(host="0.0.0.0", port=8080)

실행 명령어 (Gunicorn):
동시성 처리를 위해 --threads 옵션을 반드시 사용해야 합니다.

# 타임아웃 300초(5분) 설정, 쓰레드 8개
gunicorn --bind 0.0.0.0:8080 --workers 1 --threads 8 --timeout 300 app:app

⚙️ 2단계: Nginx 설정 (버퍼링 끄기)

Nginx를 리버스 프록시로 사용할 때 가장 중요한 설정입니다.
기본적으로 Nginx는 데이터를 모았다가(Buffering) 보내는 성질이 있어, 이걸 끄지 않으면 스트리밍이 동작하지 않습니다.

파일: /etc/nginx/sites-available/default

server {
    listen 80;
    server_name change.dns.org; # 본인의 도메인

    location / {
        proxy_pass http://127.0.0.1:8080; # 파이썬 서버 주소

        # [핵심] 버퍼링을 꺼야 실시간 스트리밍이 됨
        proxy_buffering off;
        proxy_cache off;

        # [핵심] 타임아웃을 길게 설정 (AI 답변이 길어질 경우 대비)
        proxy_read_timeout 300s;

        # 헤더 설정
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection '';
        chunked_transfer_encoding on;
    }
}

설정 후 sudo systemctl restart nginx로 재시작합니다.


💻 3단계: JavaScript 클라이언트 구현

이제 브라우저에서 이 스트림 데이터를 받아 처리하는 코드입니다. getReader()를 사용하는 것이 핵심입니다.

async function askAI() {
    const prompt = "AI에게 할 질문...";
    const outputDiv = document.getElementById("chat-output");
    outputDiv.innerText = ""; // 초기화

    try {
        const response = await fetch("https://change.dns.org/", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                contents: [{ parts: [{ text: prompt }] }]
            })
        });

        // 스트림 리더기 생성
        const reader = response.body.getReader();
        const decoder = new TextDecoder("utf-8");

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            // 조각 데이터를 텍스트로 변환하여 화면에 즉시 추가
            const chunk = decoder.decode(value, { stream: true });
            outputDiv.innerText += chunk;

            // 자동 스크롤
            window.scrollTo(0, document.body.scrollHeight);
        }

    } catch (error) {
        console.error("Error:", error);
        outputDiv.innerText += "\n[오류가 발생했습니다]";
    }
}

🔒 4단계: 무료 SSL (HTTPS) 적용

마지막으로 보안을 위해 Certbot을 사용해 HTTPS를 적용합니다.

# 1. Certbot 설치
sudo apt install certbot python3-certbot-nginx -y

# 2. 인증서 발급 및 Nginx 설정 자동 적용
sudo certbot --nginx -d change.dns.org
  • 이제 http://로 접속해도 https://로 자동 전환되며, 3개월마다 자동으로 갱신됩니다.

🚀 결론 및 성과

이렇게 구축하면 다음과 같은 효과를 얻을 수 있습니다.

  1. 속도 혁명: AI가 답변을 다 만들 때까지 30초~60초 기다릴 필요 없이, 1초 만에 답변이 시작됩니다.
  2. 비용 0원: 오라클 클라우드 프리티어와 Google Gemini 무료 API를 사용해 완전 무료입니다.
  3. 한도 무제한: API 키가 막히면 다음 키로 자동으로 넘어가므로, 중단 없는 서비스가 가능합니다.
  4. 보안: Nginx와 SSL을 통해 안전하게 암호화된 통신을 합니다.

개인 프로젝트나 포트폴리오용 AI 서비스를 만들 때 이 구조를 꼭 활용해 보세요! 😊


댓글