Déployer l'application FastAPI avec SQLite sur Fly.io

Mary-Kate Olsen
Libérer: 2024-10-22 11:15:29
original
1057 Les gens l'ont consulté

Deploy FastAPI application with SQLite on Fly.io

Les solutions cloud sont bonnes pour les projets moyens et grands, mais trop lourdes pour les petits projets personnels. Si vous souhaitez lancer quelque chose de petit (quelques points de terminaison d'API et un petit référentiel), il existe trois options :

  • Utilisez les mêmes approches que pour les « grands » projets (AWS ECS/EKS, RDS), mais elles sont redondantes et le code de l'infrastructure peut être plus volumineux que le code du projet réel. En plus, c'est cher (~ 100 $).
  • Utiliser des solutions sans serveur (Lambda, Vercel). La plupart des fournisseurs de cloud proposent de telles solutions, mais ces services ont des difficultés avec les bases de données simples : ils fournissent des solutions de fournisseurs bon marché (AWS) ou nécessitent une base de données gérée, ce qui est encore une fois coûteux (la plupart du temps rien pour le sans serveur, ~ 20 $ pour la base de données)
  • Utilisez VPS avec Docker. C'est bon marché (~ 5 $ pour une petite machine) et presque pas besoin de gérer l'infrastructure, mais les déploiements sont nuls (nécessite un registre privé ou auto-hébergé, un accès SSH depuis CI).

J'écris habituellement mes petites applications en utilisant SQLite, c'est une petite base de données de fichier unique pratique qui fonctionne dans n'importe quel langage de programmation et peut être copiée sur une machine locale pour analyser des données par exemple. Je cherchais donc une solution middleware combinant l'approche sans serveur, la facilité de déploiement et la possibilité d'utiliser SQLite et j'ai trouvé Fly.io.

Installation

Si vous n'avez pas de compte sur Fly.io, vous devez le créer. Un outil CLI appelé flyctl est également requis pour gérer les projets. Fly.io peut être déployé à la fois localement et depuis CI.

flyctl effectue le déploiement à partir du dossier racine du projet à partir de Dockerfile, ce qui est cool, car le même Dockerfile peut être utilisé dans d'autres systèmes. Pour jouer avec Fly.io, j'ai préparé un projet FastAPI simple qui stocke l'état dans la base de données – un raccourcisseur d'URL générique avec comptage de clics.

Fichier Docker :

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}
Copier après la connexion
Copier après la connexion

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)
Copier après la connexion
Copier après la connexion

requirements.txt :

aiosqlite
fastapi
uvicorn
Copier après la connexion
Copier après la connexion

Déployer

Pour déployer notre code, nous devons d'abord créer un projet Fly.io. Cela peut être fait soit dans l'interface Web, soit avec flyctl. Pour créer un projet avec l'outil CLU dans le dossier racine (où se trouve le code), le lancement flyctl doit être exécuté. Cette commande proposera de sélectionner le matériel souhaité et créera le fichier fly.toml :

fly launch --build-only
Copier après la connexion
Copier après la connexion

Vous pouvez modifier le projet à l'avenir en modifiant les paramètres dans ce fichier ou via l'interface utilisateur Web. Le fly.toml de base semble correct, mais SQLite nécessite un stockage, qui peut être créé avec :

fly volumes create sqlite_data -s 1 -r ams
Copier après la connexion
Copier après la connexion

où -s 1 définit la taille du volume sur 1 Go (la valeur par défaut est 3 Go) et -r est la région dans laquelle le volume sera créé (utilisez la même région dans laquelle le projet Fly.io est créé). Vous pourrez toujours modifier la taille de stockage plus tard.

La dernière chose à faire est d'ajouter une section mounts à fly.toml, qui attache le volume à l'application :

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}
Copier après la connexion
Copier après la connexion

sqlite_data est le nom du stockage, /data est le chemin où le volume sera connecté. C'est essentiellement la même chose que docker run --mount source=sqlite_data,target=/data ou la section Docker Compose correspondante.

SQLite ne peut pas être écrit à partir de plus d'une application, et Fly.io crée par défaut 2 instances pour une application, nous pouvons donc spécifier le nombre de répliques comme une seule au cas où :

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)
Copier après la connexion
Copier après la connexion

Toutes les configurations sont terminées maintenant et nous pouvons déployer notre application avec la commande :

aiosqlite
fastapi
uvicorn
Copier après la connexion
Copier après la connexion

L'application devrait démarrer avec succès et le nom DNS public sera imprimé sur la console. Nous pouvons maintenant le vérifier en publiant une URL sur le raccourcisseur :

fly launch --build-only
Copier après la connexion
Copier après la connexion

Ensuite, nous pouvons visiter ce lien, il devrait rediriger vers https://example.com. Enfin, nous pouvons vérifier que les clics sont mis à jour :

fly volumes create sqlite_data -s 1 -r ams
Copier après la connexion
Copier après la connexion

Pour vérifier l'état de la base de données enregistré entre les déploiements, nous pouvons effectuer un nouveau déploiement avec fly déployer et vérifier que la liste des liens est restée la même que ci-dessus (1 lien, 1 clic).

Migrations

Si vous utilisez une solution externe pour les migrations, plutôt que de les exécuter à partir du code au démarrage de l'application, le seul moyen d'exécuter la migration est de la placer dans Dockerfile dans le cadre de la commande RUN.

Sauvegarde

Nous pouvons nous connecter à la machine avec la console fly ssh, puis dans le dossier /data interagir avec le fichier de base de données. Nous pouvons également copier le fichier de base de données sur la machine locale avec :

[mounts]
source = "sqlite_data"
destination = "/data"
Copier après la connexion

Conclusion

Fly.io est un service simple et pratique pour déployer des applications. Déployez les travaux à partir de Docker Containers, les services supplémentaires incluent PSQL, Redis, le stockage de type S3 (contrairement à Vercel). C'est bon marché, le service le moins cher coûte 3 dollars (1 CPU partagé / 256 Mo) – peut-être même moins si vous avez peu de trafic – le conteneur s'éteint après quelques minutes d'inactivité et se rallume automatiquement lorsque du trafic apparaît.

Par contre, il n'y a pas de solution intégrée pour les tâches planifiées – à la place, la solution officielle consiste à configurer un serveur séparé avec crontab et à exécuter des tâches à partir de celui-ci – c'est un peu effrayant.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal