Heim > Artikel > Backend-Entwicklung > Stellen Sie die FastAPI-Anwendung mit SQLite auf Fly.io bereit
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:
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.
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
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).
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.
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"
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!