>  기사  >  백엔드 개발  >  Fly.io에서 SQLite를 사용하여 FastAPI 애플리케이션 배포

Fly.io에서 SQLite를 사용하여 FastAPI 애플리케이션 배포

Mary-Kate Olsen
Mary-Kate Olsen원래의
2024-10-22 11:15:291064검색

Deploy FastAPI application with SQLite on Fly.io

클라우드 솔루션은 중대형 프로젝트에 적합하지만 소규모 개인 프로젝트에는 너무 무겁습니다. 작은 것(몇 개의 API 엔드포인트 및 작은 저장소)을 시작하려는 경우 세 가지 옵션이 있습니다.

  • "대형" 프로젝트(AWS ECS/EKS, RDS)와 동일한 접근 방식을 사용하지만 중복되며 인프라 코드가 실제 프로젝트의 코드보다 클 수 있습니다. 게다가 가격도 비싸요(~$100).
  • 서버리스 솔루션(Lambda, Vercel)을 사용하세요. 대부분의 클라우드 제공업체에는 이러한 솔루션이 있지만 이러한 서비스는 간단한 데이터베이스로는 어려움을 겪습니다. 저렴한 공급업체 솔루션(AWS)을 제공하거나 관리형 데이터베이스가 필요합니다. 이는 역시 비용이 많이 듭니다(서버리스의 경우 거의 없음, DB의 경우 ~$20)
  • Docker와 함께 VPS를 사용하세요. 가격이 저렴하고(소형 머신의 경우 5달러) 인프라를 관리할 필요가 거의 없지만 배포가 형편없습니다(개인 또는 자체 호스팅 레지스트리, CI의 SSH 액세스 필요).

저는 보통 SQLite를 사용하여 작은 애플리케이션을 작성합니다. SQLite는 모든 프로그래밍 언어에서 작동하고 예를 들어 데이터를 분석하기 위해 로컬 시스템에 복사할 수 있는 편리한 작은 단일 파일 데이터베이스입니다. 그래서 서버리스 접근 방식, 배포 용이성, SQLite 사용 기능을 결합한 미들웨어 솔루션을 찾다가 Fly.io를 발견했습니다.

설정

Fly.io에 계정이 없다면 계정을 만들어야 합니다. 또한 프로젝트를 관리하려면 flyctl이라는 CLI 도구가 필요합니다. Fly.io는 로컬 및 CI에서 모두 배포할 수 있습니다.

flyctl은 Dockerfile의 프로젝트 루트 폴더에서 배포를 수행합니다. 이는 동일한 Dockerfile을 다른 시스템에서 사용할 수 있기 때문에 좋습니다. Fly.io를 플레이하기 위해 데이터베이스에 상태를 저장하는 간단한 FastAPI 프로젝트(클릭 계산 기능이 있는 일반 URL 단축기)를 준비했습니다.

도커파일:

FROM python:3.13-alpine
WORKDIR /app

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

COPY . /app

ENV HOST=0.0.0.0 PORT=8080
EXPOSE ${PORT}
CMD uvicorn main:app --host ${HOST} --port ${PORT}

main.py:

import asyncio
import random
import string
from urllib.parse import urlparse

import aiosqlite
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import RedirectResponse

DB_PATH = "/data/app.db"

app = FastAPI()

async def get_db() -> aiosqlite.Connection:
    if db := getattr(get_db, "_db", None):
        if db.is_alive:
            return db

    db = await aiosqlite.connect(DB_PATH, loop=asyncio.get_event_loop())
    db.row_factory = aiosqlite.Row

    qs = """
    CREATE TABLE IF NOT EXISTS links (
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        short_code TEXT PRIMARY KEY,
        full_url TEXT NOT NULL,
        clicks INTEGER DEFAULT 0
    )
    """

    await db.execute(qs)
    await db.commit()

    setattr(get_db, "_db", db)
    return db

def random_code(length=8) -> str:
    alphabet = string.ascii_letters + string.digits
    return "".join(random.choice(alphabet) for x in range(length))

def is_valid_url(url: str) -> bool:
    try:
        parts = urlparse(url)
        return all([parts.scheme, parts.netloc])
    except ValueError:
        return False

@app.post("/")
async def shorten(url: str, req: Request):
    if not is_valid_url(url):
        raise HTTPException(status_code=400, detail="Invalid URL")

    host = req.headers.get("host")
    if host is None:
        raise HTTPException(status_code=500, detail="Missing host header")

    short_code = random_code()
    db = await get_db()
    qs = "INSERT INTO links (short_code, full_url) VALUES (?, ?)"
    await db.execute(qs, (short_code, url))
    await db.commit()

    return f"https://{host}/{short_code}"

@app.get("/")
async def list_links():
    db = await get_db()
    qs = "SELECT short_code, full_url, clicks FROM links ORDER BY created_at DESC"
    async with db.execute(qs) as cursor:
        return await cursor.fetchall()

@app.get("/{short_code}")
async def redirect(short_code: str):
    db = await get_db()
    qs = """
    UPDATE links SET clicks = clicks + 1 WHERE short_code = ?
    RETURNING full_url
    """

    async with db.execute(qs, (short_code,)) as cursor:
        if row := await cursor.fetchone():
            return RedirectResponse(row["full_url"])

    raise HTTPException(status_code=404)

requirements.txt:

aiosqlite
fastapi
uvicorn

배포

코드를 배포하려면 먼저 Fly.io 프로젝트를 만들어야 합니다. 이는 웹 인터페이스나 flyctl을 사용하여 수행할 수 있습니다. 코드가 있는 루트 폴더에 CLU 도구를 사용하여 프로젝트를 생성하려면 flyctl launch를 실행해야 합니다. 이 명령은 원하는 하드웨어를 선택하고 fly.toml 파일을 생성합니다:

fly launch --build-only

나중에 이 파일의 매개변수를 변경하거나 웹 UI를 통해 프로젝트를 수정할 수 있습니다. 기본 fly.toml은 괜찮아 보이지만 SQLite에는 다음을 사용하여 생성할 수 있는 Storage가 필요합니다.

fly volumes create sqlite_data -s 1 -r ams

여기서 -s 1은 볼륨 크기를 1GB(기본값은 3GB)로 설정하고 -r은 볼륨이 생성될 영역입니다(Fly.io 프로젝트가 생성되는 동일한 영역 사용). 저장용량 크기는 나중에 언제든지 변경할 수 있습니다.

마지막으로 해야 할 일은 fly.toml에 마운트 섹션을 추가하는 것입니다. 그러면 애플리케이션에 볼륨이 연결됩니다.

FROM python:3.13-alpine
WORKDIR /app

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

COPY . /app

ENV HOST=0.0.0.0 PORT=8080
EXPOSE ${PORT}
CMD uvicorn main:app --host ${HOST} --port ${PORT}

sqlite_data는 스토리지 이름, /data는 볼륨이 연결될 경로입니다. 이는 docker run --mount source=sqlite_data,target=/data 또는 해당 Docker Compose 섹션과 기본적으로 동일합니다.

SQLite는 두 개 이상의 앱에서 쓸 수 없으며 Fly.io는 기본적으로 앱에 대해 2개의 인스턴스를 생성하므로 다음과 같은 경우에 복제본 수를 하나로 지정할 수 있습니다.

import asyncio
import random
import string
from urllib.parse import urlparse

import aiosqlite
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import RedirectResponse

DB_PATH = "/data/app.db"

app = FastAPI()

async def get_db() -> aiosqlite.Connection:
    if db := getattr(get_db, "_db", None):
        if db.is_alive:
            return db

    db = await aiosqlite.connect(DB_PATH, loop=asyncio.get_event_loop())
    db.row_factory = aiosqlite.Row

    qs = """
    CREATE TABLE IF NOT EXISTS links (
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        short_code TEXT PRIMARY KEY,
        full_url TEXT NOT NULL,
        clicks INTEGER DEFAULT 0
    )
    """

    await db.execute(qs)
    await db.commit()

    setattr(get_db, "_db", db)
    return db

def random_code(length=8) -> str:
    alphabet = string.ascii_letters + string.digits
    return "".join(random.choice(alphabet) for x in range(length))

def is_valid_url(url: str) -> bool:
    try:
        parts = urlparse(url)
        return all([parts.scheme, parts.netloc])
    except ValueError:
        return False

@app.post("/")
async def shorten(url: str, req: Request):
    if not is_valid_url(url):
        raise HTTPException(status_code=400, detail="Invalid URL")

    host = req.headers.get("host")
    if host is None:
        raise HTTPException(status_code=500, detail="Missing host header")

    short_code = random_code()
    db = await get_db()
    qs = "INSERT INTO links (short_code, full_url) VALUES (?, ?)"
    await db.execute(qs, (short_code, url))
    await db.commit()

    return f"https://{host}/{short_code}"

@app.get("/")
async def list_links():
    db = await get_db()
    qs = "SELECT short_code, full_url, clicks FROM links ORDER BY created_at DESC"
    async with db.execute(qs) as cursor:
        return await cursor.fetchall()

@app.get("/{short_code}")
async def redirect(short_code: str):
    db = await get_db()
    qs = """
    UPDATE links SET clicks = clicks + 1 WHERE short_code = ?
    RETURNING full_url
    """

    async with db.execute(qs, (short_code,)) as cursor:
        if row := await cursor.fetchone():
            return RedirectResponse(row["full_url"])

    raise HTTPException(status_code=404)

이제 모든 구성이 완료되었으며 다음 명령을 사용하여 앱을 배포할 수 있습니다.

aiosqlite
fastapi
uvicorn

앱이 성공적으로 부팅되고 공용 DNS 이름이 콘솔에 인쇄됩니다. 이제 URL을 단축기에 게시하여 확인할 수 있습니다.

fly launch --build-only

그런 다음 이 링크를 방문하면 https://example.com으로 리디렉션됩니다. 마지막으로 클릭수가 업데이트되었는지 확인할 수 있습니다.

fly volumes create sqlite_data -s 1 -r ams

배포 사이에 저장된 데이터베이스 상태를 확인하려면 fly 배포로 새 배포를 수행하고 링크 목록이 위와 동일하게 유지되는지 확인할 수 있습니다(링크 1개, 클릭 1개).

마이그레이션

마이그레이션을 위해 외부 솔루션을 사용하는 경우 앱이 시작될 때 코드에서 실행하는 대신 마이그레이션을 실행하는 유일한 방법은 RUN 명령의 일부로 Dockerfile에 넣는 것입니다.

지원

Fly SSH 콘솔을 사용하여 컴퓨터에 연결한 다음 /data 폴더에서 데이터베이스 파일과 상호 작용할 수 있습니다. 또한 다음을 사용하여 데이터베이스 파일을 로컬 컴퓨터에 복사할 수 있습니다.

[mounts]
source = "sqlite_data"
destination = "/data"

결론

Fly.io는 애플리케이션 배포를 위한 간단하고 편리한 서비스입니다. 배포는 Docker 컨테이너에서 작동하며 추가 서비스에는 PSQL, Redis, S3와 같은 스토리지(Vercel과 다름)가 포함됩니다. 가장 저렴한 서비스 비용은 3달러(공유 CPU 1개/256MB)입니다. 트래픽이 거의 없는 경우에는 더 적을 수도 있습니다. 컨테이너는 몇 분 동안 활동이 없으면 종료되고 트래픽이 나타나면 자동으로 켜집니다.

단점은 예약된 작업을 위한 내장 솔루션이 없다는 점입니다. 대신 공식적인 솔루션은 crontab을 사용하여 별도의 서버를 설정하고 여기에서 작업을 실행하는 것입니다. 다소 소름끼칩니다.

위 내용은 Fly.io에서 SQLite를 사용하여 FastAPI 애플리케이션 배포의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.