Heim  >  Artikel  >  Backend-Entwicklung  >  Stellen Sie die FastAPI-Anwendung mit SQLite auf Fly.io bereit

Stellen Sie die FastAPI-Anwendung mit SQLite auf Fly.io bereit

Mary-Kate Olsen
Mary-Kate OlsenOriginal
2024-10-22 11:15:29975Durchsuche

Deploy FastAPI application with SQLite on Fly.io

Cloud-Lösungen sind gut für mittlere und große Projekte, aber zu schwer für kleine persönliche Projekte. Wenn Sie etwas Kleines starten möchten (ein paar API-Endpunkte und ein kleines Repository), gibt es drei Möglichkeiten:

  • Verwenden Sie die gleichen Ansätze wie für „große“ Projekte (AWS ECS/EKS, RDS), diese sind jedoch redundant und der Infrastrukturcode kann größer sein als der Code des eigentlichen Projekts. Außerdem ist es teuer (~100 $).
  • Verwenden Sie serverlose Lösungen (Lambda, Vercel). Die meisten Cloud-Anbieter verfügen über solche Lösungen, aber diese Dienste haben Schwierigkeiten mit einfachen Datenbanken – sie bieten günstige Anbieterlösungen (AWS) oder erfordern eine verwaltete Datenbank, die wiederum teuer ist (meistens nichts für Serverless, ~20 $ für DB)
  • Verwenden Sie VPS mit Docker. Es ist günstig (ca. 5 US-Dollar für eine kleine Maschine) und erfordert fast keine Verwaltung der Infrastruktur, aber Bereitstellungen sind scheiße (erfordert private oder selbst gehostete Registrierung, SSH-Zugriff von CI).

Normalerweise schreibe ich meine kleinen Anwendungen mit SQLite, einer praktischen kleinen Einzeldateidatenbank, die in jeder Programmiersprache funktioniert und auf einen lokalen Computer kopiert werden kann, um beispielsweise Daten zu analysieren. Also suchte ich nach einer Middleware-Lösung, die den serverlosen Ansatz, die einfache Bereitstellung und die Fähigkeit zur Verwendung von SQLite kombiniert, und bin auf Fly.io gestoßen.

Aufstellen

Wenn Sie kein Konto bei Fly.io haben, müssen Sie eines erstellen. Zum Verwalten von Projekten ist außerdem ein CLI-Tool namens „flyctl“ erforderlich. Fly.io kann sowohl lokal als auch über CI bereitgestellt werden.

flyctl ermöglicht die Bereitstellung aus dem Stammordner des Projekts von Dockerfile, was cool ist, da dasselbe Dockerfile in anderen Systemen verwendet werden kann. Zum Spielen mit Fly.io habe ich ein einfaches FastAPI-Projekt vorbereitet, das den Status in einer Datenbank speichert – generischer URL-Shortener mit Klickzählung.

Docker-Datei:

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

Einsetzen

Um unseren Code bereitzustellen, müssen wir zunächst ein Fly.io-Projekt erstellen. Dies kann entweder im Webinterface oder mit flyctl erfolgen. Um ein Projekt mit dem CLU-Tool im Stammordner (wo sich der Code befindet) zu erstellen, sollte „flyctl launch“ ausgeführt werden. Dieser Befehl bietet die Möglichkeit, die gewünschte Hardware auszuwählen und die Datei „fly.toml“ zu erstellen:

fly launch --build-only

Sie können das Projekt in Zukunft ändern, indem Sie die Parameter in dieser Datei oder über die Web-Benutzeroberfläche ändern. Das grundlegende fly.toml sieht gut aus, aber SQLite erfordert Speicher, der erstellt werden kann mit:

fly volumes create sqlite_data -s 1 -r ams

wobei -s 1 die Volume-Größe auf 1 GB festlegt (Standard ist 3 GB) und -r die Region ist, in der das Volume erstellt wird (verwenden Sie dieselbe Region, in der das Fly.io-Projekt erstellt wird). Sie können die Speichergröße später jederzeit ändern.

Als letztes müssen Sie einen Mount-Abschnitt zu fly.toml hinzufügen, der das Volume an die Anwendung anhängt:

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 ist der Name des Speichers, /data ist der Pfad, mit dem das Volume verbunden wird. Dies ist im Wesentlichen dasselbe wie docker run --mount source=sqlite_data,target=/data oder der entsprechende Docker Compose-Abschnitt.

SQLite kann nicht von mehr als einer App aus beschrieben werden, und Fly.io erstellt standardmäßig zwei Instanzen für eine App, sodass wir für alle Fälle die Anzahl der Replikate als eine angeben können:

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)

Alle Konfigurationen sind jetzt abgeschlossen und wir können unsere App mit folgendem Befehl bereitstellen:

aiosqlite
fastapi
uvicorn

Die App sollte erfolgreich gestartet werden und der öffentliche DNS-Name wird auf der Konsole ausgegeben. Jetzt können wir es uns ansehen, indem wir eine URL zum Shortener posten:

fly launch --build-only

Dann können wir diesen Link besuchen, er sollte zu https://example.com weiterleiten. Schließlich können wir überprüfen, ob die Klicks aktualisiert werden:

fly volumes create sqlite_data -s 1 -r ams

Um zu überprüfen, ob der Datenbankstatus zwischen Bereitstellungen gespeichert wurde, können wir eine neue Bereitstellung mit Fly Deploy durchführen und prüfen, ob die Linkliste dieselbe wie oben geblieben ist (1 Link, 1 Klick).

Migrationen

Wenn Sie eine externe Lösung für Migrationen verwenden, anstatt sie beim Starten der App über den Code auszuführen, besteht die einzige Möglichkeit, die Migration auszuführen, darin, sie als Teil des RUN-Befehls in Dockerfile einzufügen.

Sicherung

Wir können mit der Fly-SSH-Konsole eine Verbindung zur Maschine herstellen und dann im Ordner /data mit der Datenbankdatei interagieren. Wir können die Datenbankdatei auch auf den lokalen Computer kopieren mit:

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

Abschluss

Fly.io ist ein einfacher und bequemer Dienst zum Bereitstellen von Anwendungen. Die Bereitstellung funktioniert über Docker-Container. Zu den zusätzlichen Diensten gehören PSQL, Redis und S3-ähnlicher Speicher (im Gegensatz zu Vercel). Es ist günstig, der günstigste Dienst kostet 3 Dollar (1 gemeinsam genutzte CPU / 256 MB) – vielleicht sogar weniger, wenn Sie wenig Verkehr haben – der Container schaltet sich nach ein paar Minuten ohne Aktivität ab und schaltet sich automatisch ein, wenn Verkehr auftritt.

Der Nachteil ist, dass es keine integrierte Lösung für geplante Aufgaben gibt – stattdessen besteht die offizielle Lösung darin, einen separaten Server mit crontab einzurichten und Aufgaben von dort aus auszuführen – das ist irgendwie gruselig.

Das obige ist der detaillierte Inhalt vonStellen Sie die FastAPI-Anwendung mit SQLite auf Fly.io bereit. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn