Produktionseinrichtung
Potato in Produktion betreiben, mit gunicorn, nginx oder Apache, TLS-Terminierung, Docker, dauerhaften Sitzungen und der Zugriffskontrolle, die ein gemeinsam genutzter Server braucht.
Stellen Sie Potato für reale Annotationsaufgaben mit mehreren Benutzern, dauerhafter Datenspeicherung und angemessener Sicherheit bereit.
Serverkonfiguration
Grundlegende Produktionskonfiguration
server:
host: 0.0.0.0
port: 8000
debug: falseDer server-Block akzeptiert host, port und debug und sonst nichts. Worker-Anzahl und Timeouts gehören zu dem WSGI-Server, der Potato ausführt, und nicht in die Konfigurationsdatei. Lassen Sie debug in Produktion ausgeschaltet, weil der Debug-Modus die Admin-Seiten ohne Schlüssel für Anfragen vom Rechner des Servers selbst öffnet, und auf einem produktiv eingesetzten Host gehört dazu sein Reverse Proxy.
HTTPS verwenden
Die Konfiguration von Potato enthält keine TLS-Einstellungen, daher werden Zertifikate nicht dort eingerichtet. Terminieren Sie TLS am Reverse Proxy vor Potato, wie es die Abschnitte zu nginx und Apache weiter unten einrichten, oder übergeben Sie das Zertifikat direkt an gunicorn, wie unter „Mit SSL“ gezeigt.
Mit Gunicorn ausführen
Führen Sie Potato unter gunicorn mit genau einem Worker aus, und erhöhen Sie die Anzahl der Threads, wenn Sie mehr Nebenläufigkeit brauchen:
gunicorn "potato.flask_server:create_app('config.yaml')" \
--workers 1 \
--threads 8 \
--bind 0.0.0.0:8000 \
--timeout 120Der einzelne Worker ist eine Voraussetzung für korrekte Ergebnisse und keine Tuning-Entscheidung. Potato hält seinen Item-Pool, seine Zuweisungswarteschlange und den Zustand jedes Annotators im Speicher des Prozesses. Ein zweiter Worker bekommt von allen dreien eine eigene Kopie. Er verteilt deshalb Instanzen, die der erste Worker bereits zugewiesen hat, und weil user_state.json bei jedem Speichern vollständig neu geschrieben wird, verwirft der Worker, der zuletzt speichert, die Annotationen des anderen. Nichts meldet diesen Verlust, die Annotationen sind einfach weg. Threads teilen sich eine Kopie dieses Zustands, daher erhöht --threads die Nebenläufigkeit gefahrlos, --workers dagegen nicht.
Mit SSL
gunicorn "potato.flask_server:create_app('config.yaml')" \
--workers 1 \
--threads 8 \
--bind 0.0.0.0:443 \
--certfile /path/to/cert.pem \
--keyfile /path/to/key.pemReverse Proxy einrichten
Nginx-Konfiguration
upstream potato {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name annotation.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name annotation.example.com;
ssl_certificate /etc/letsencrypt/live/annotation.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/annotation.example.com/privkey.pem;
location / {
proxy_pass http://potato;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Long annotation sessions
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
}Apache-Konfiguration
<VirtualHost *:443>
ServerName annotation.example.com
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
</VirtualHost>Unter einem URL-Pfad einbinden
Wenn Potato unter https://host/app1/ statt an der Wurzel einer Domain laufen soll, ist ein zusätzlicher Schritt nötig. Die Annotationsseite fordert /static/... und /updateinstance relativ zur öffentlichen Wurzel an. Unter einem Pfadpräfix liefern die Assets daher 404, und Annotationen werden nicht gespeichert, während das Grundgerüst der Seite weiterhin angezeigt wird.
Teilen Sie Potato sein öffentliches Präfix mit. Wenn Sie den Proxy kontrollieren und er Forwarded-Header senden kann:
export POTATO_PROXY_FIX=1
potato start config.yaml -p 8000location /app1/ {
proxy_pass http://127.0.0.1:8000/; # trailing slash strips /app1/
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /app1;
}Aktivieren Sie POTATO_PROXY_FIX nur, wenn die Anwendung ausschließlich über diesen Proxy erreichbar ist. Die Einstellung vertraut Forwarded-Headern. Ein Client, der den internen Port direkt erreicht, könnte das Präfix fälschen und damit jede erzeugte URL verfälschen.
Wenn Sie keine Header hinzufügen können, den Einbindungspfad aber kennen, setzen Sie ihn direkt. Der Proxy muss das Präfix trotzdem vor dem Weiterleiten entfernen:
export POTATO_URL_PREFIX=/app1
potato start config.yaml -p 8000Testen Sie, indem Sie sich in einem Browser ohne bestehende Sitzung anmelden, denn das Anmeldeformular ist die Seite, die bei fehlendem Präfix zuerst nicht mehr funktioniert.
Die Viewer für Live-Agenten und Live-Coding streamen über Server-Sent Events, die ein puffernder Proxy bis zum Ende des Streams zurückhält. Schalten Sie das Puffern auf diesen Routen ab:
location /app1/api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_set_header Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
}Docker-Bereitstellung
Potato veröffentlicht ein fertiges Image in der GitHub Container Registry, ghcr.io/davidjurgens/potato. Es enthält Potato und seine Abhängigkeiten und sonst nichts. Ihr Projektordner mit config.yaml und den zugehörigen Daten wird unter /app eingebunden, sodass ein Image für jede Aufgabe genügt:
docker run -p 8000:7860 \
-v "$PWD/myproject:/app" \
-e POTATO_SECRET_KEY="$(openssl rand -hex 32)" \
ghcr.io/davidjurgens/potato:latestDer Tag latest und die Release-Tags wie 2.12.1 enthalten nur die Kernabhängigkeiten. Die -all-Tags (latest-all, 2.12.1-all) fügen die AI-SDKs, die Formatleser sowie die Extras für Export und Authentifizierung hinzu und lassen die Vision-Modelle weg.
Der Container bedient die Aufgabe mit einem gunicorn-Worker und acht Threads, aus dem Grund, der im Abschnitt zu gunicorn oben genannt ist. Wird GUNICORN_WORKERS auf mehr als 1 gesetzt, bricht der Container beim Start ab, sofern nicht zusätzlich POTATO_ALLOW_MULTIWORKER=1 gesetzt ist, was in einer echten Studie Annotationen verliert. Erhöhen Sie GUNICORN_THREADS für mehr Nebenläufigkeit. POTATO_CONFIG benennt eine andere Konfigurationsdatei als config.yaml, und PORT ändert den Port im Container, der sonst 7860 ist.
Der Container läuft als uid 1000 und schreibt die Annotationsausgabe, potato.log und seine SQLite-Datenbanken nach /app. Unter Linux hält ein per Bind-Mount eingebundener Projektordner, der einem anderen Benutzer gehört, den Server beim Start mit einer Fehlermeldung an, die das Problem benennt. Übertragen Sie den Ordner mit sudo chown -R 1000:1000 myproject an uid 1000. Docker Desktop auf dem Mac ignoriert die Eigentümerschaft von Bind-Mounts, daher tritt der Fehler nur unter Linux auf.
Argumente nach dem Image-Namen ersetzen den Serverbefehl, was für einmalige Prüfungen nützlich ist:
docker run -v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:latest potato validate config.yaml --strictDocker Compose
services:
potato:
image: ghcr.io/davidjurgens/potato:latest
ports:
- "8000:7860"
volumes:
- ./myproject:/app
environment:
- POTATO_SECRET_KEY=${POTATO_SECRET_KEY}
- POTATO_ADMIN_API_KEY=${POTATO_ADMIN_API_KEY}
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./certs:/etc/ssl/certs
depends_on:
- potato
restart: unless-stoppedInnerhalb des Compose-Netzwerks erreicht nginx Potato über den Servicenamen und den Port des Containers. Der upstream-Block aus dem Abschnitt zum Reverse Proxy oben lautet daher server potato:7860; statt server 127.0.0.1:8000;.
Da der Projektordner eingebunden und nicht in ein Image kopiert wird, bleiben die Annotationen, die Potato dort hineinschreibt, auf dem Host erhalten, wenn der Container ersetzt wird.
Zugriffskontrolle
Einschränken, wer sich registrieren darf
Potato erlaubt standardmäßig die Selbstregistrierung. Legen Sie auf einem öffentlichen Server fest, wer sich anmelden darf:
user_config:
allow_all_users: false
users:
- annotator1@example.com
- annotator2@example.comBeide Schlüssel gehören unter user_config. Auf oberster Ebene geschrieben, werden sie ignoriert, und die Konfiguration sieht dann aus wie eine Zugriffsliste, lässt aber jeden zu.
Admin-Zugriff
admin_api_key ist ein gemeinsamer Schlüssel, der im Header X-API-Key gesendet wird. Wenn Sie ihn nicht setzen, erzeugt Potato einen und schreibt ihn nach {task_dir}/admin_api_key.txt. Setzen Sie ihn auf einem Produktionsserver daher selbst und halten Sie ihn aus dem Repository heraus:
admin_api_key: "your-shared-key"
rbac:
enabled: true
user_role_assignments:
lead@example.com: adminWenn rbac konfiguriert ist, gelangen namentlich eingetragene Personen über eine normale Anmeldung zu /admin, statt den gemeinsamen Schlüssel weiterzugeben. Die vollständigen Berechtigungen finden Sie unter Benutzerverwaltung.
Sitzungen
secret_key: "a-long-random-string"
session_timeout_minutes: 60
persist_sessions: true
session_lifetime_days: 7
max_session_seconds: 7200secret_key signiert das Sitzungs-Cookie und kann stattdessen aus POTATO_SECRET_KEY kommen. Fehlt der Schlüssel, erzeugt Potato bei jedem Start einen neuen, sodass jeder Neustart alle Benutzer mitten in der Aufgabe abmeldet. Auf einem Server, der bei jedem Deployment neu startet, ist dies die Einstellung, die am häufigsten Annotationen kostet.
session_timeout_minutes löscht eine Sitzung, die so lange inaktiv war, und die Uhr beginnt bei jeder Anfrage von vorn. Potato 2.11 ist das erste Release, das die Einstellung durchsetzt, mit einem Standardwert von 480 Minuten. Bei 60, wie in diesem Beispiel, muss sich ein Annotator, der eine Stunde weggeht, bei seiner Rückkehr erneut anmelden.
Annotationsausgabe
output_annotation_dir: /var/potato/annotations/
export_annotation_format: [csv, jsonl]
auto_export_interval: 300Potato schreibt unter output_annotation_dir ein Verzeichnis pro Annotator, das jeweils die user_state.json dieser Person enthält. export_annotation_format nennt die Formate, die der periodische Export schreibt, und ein leerer Wert bedeutet, dass überhaupt nicht automatisch exportiert wird. auto_export_interval ist der Abstand zwischen zwei Exporten in Sekunden und hat den Standardwert 60.
Datenbank-Backend
Annotationen liegen standardmäßig auf der Festplatte. Für Deployments, die eine Datenbank brauchen, akzeptiert database.type die Werte mysql oder file:
database:
type: mysql
host: localhost
port: 3306
database: potato_db
username: potato
password: ${DB_PASSWORD}Die Schlüssel heißen database und username, nicht name und user. Ein Verbindungsstring kann stattdessen aus POTATO_DB_CONNECTION kommen, womit die Zugangsdaten nicht in der Konfigurationsdatei stehen.
Installieren Sie den Treiber mit pip install 'potato-annotation[mysql]'. Vor 2.10.2 wurde das MySQL-Backend nie aktiv, und eine Konfiguration, die es angab, speicherte Annotationen stillschweigend auf der Festplatte. Ab 2.10.2 bricht ein fehlender Treiber oder ein nicht erreichbarer Server den Start ab, und Export, Backups und potato deploy pull lesen die Datenbank.
Health-Checks und Protokollierung
Potato stellt GET /health in jedem Deployment bereit, und die Route erfordert keine Authentifizierung. Sie eignet sich daher als Readiness-Probe für einen Load Balancer oder einen Orchestrator:
curl -sf https://annotation.example.com/healthDie Ausführlichkeit der Protokollierung wird in der Konfiguration oder auf der Kommandozeile festgelegt, nicht in einem eigenen Logging-Block:
verbose: true
debug_log: serverdebug_log akzeptiert all, ui, server oder none. Die Umgebungsvariable POTATO_LOGGERS beschränkt die Ausgabe auf benannte Logger, wenn Sie ein einzelnes Subsystem untersuchen.
Skalierung
Potato läuft als einzelner Prozess, der den gesamten Item-Bestand im Speicher hält. Skalieren Sie daher die Maschine und nicht die Anzahl der Server. Mehrere Replikate hinter einem Load Balancer teilen die Arbeit nicht auf. Jedes Replikat hält eine eigene Kopie der Zuweisungswarteschlange und des Annotatorzustands, was genau den Datenverlust erzeugt, der oben im Abschnitt zu gunicorn beschrieben ist.
Der Speicher ist die Ressource, die mit dem Datensatz wächst. Ab etwa einer Million Items können die Nutzdaten in einen SQLite-Cache verlagert werden:
item_store:
backend: paged # default: memory
cache_size: 2048Messen Sie, bevor Sie dies aktivieren. Paging spart bei Vision-Items etwa 28 % und bei Text etwa 51 %, also keine Größenordnung, und es macht das Laden des Korpus etwa 2,5-mal langsamer. Die Datei ist ein Cache, der bei jedem Start aus den Datendateien neu aufgebaut wird, daher kann sie gefahrlos gelöscht werden.
Bei großen Projekten sind die wirksamen Hebel, mit per_annotator_quota zu begrenzen, wie viel einem einzelnen Annotator zugewiesen wird, den Korpus mit batch_assignment auf Kohorten aufzuteilen und mit data_directory Datendateien nach und nach hinzuzufügen, statt alles beim Start zu laden.
Backup-Strategie
Ein Backup muss mehr als das Ausgabeverzeichnis der Annotationen abdecken. Das Aufgabenverzeichnis enthält außerdem bis zu drei SQLite-Datenbanken, von denen sich keine aus den Datendateien neu aufbauen lässt: project.sqlite (Memos, das Codebuch, der Review-Workflow), datasets.sqlite und engine.sqlite, wenn die Labeling-Engine im Einsatz ist. Konten, die über das Registrierungsformular angelegt wurden, speichert Potato in user_config.json. Die Datenbanken laufen im WAL-Modus, während der Server in sie schreibt, daher kann ein tar oder cp einer Datenbankdatei eine Kopie erzeugen, die beschädigt ist oder der neuere Arbeit fehlt, und keiner der beiden Befehle warnt Sie davor.
Das eingebaute Backup
Ab Version 2.10 kopiert der backup-Block von Potato die Annotationsausgabe und .backup-Snapshots der Datenbanken vom Server weg und stellt sie wieder her, wenn der Server mit leerem Datenträger startet. Die Wiederherstellung bringt mit den Annotationen auch die Kontenliste zurück, und sie überschreibt nie Annotationen, die bereits auf dem Datenträger liegen. Der folgende Block schickt die Kopie alle fünf Minuten, dem Standardintervall, in einen S3-Bucket, sodass ein verlorener Datenträger die Annotatoren höchstens fünf Minuten Arbeit kostet:
backup:
schedule_minutes: 5
restore_on_boot: true
sinks:
- type: s3
bucket: lab-potato-backups
prefix: pilot-study
endpoint_url: https://s3.example.edu # leave out for AWS S3endpoint_url richtet die Senke auf jeden Dienst, der die S3-API spricht, etwa einen Objektspeicher der Hochschule, MinIO, Cloudflare R2 oder Backblaze B2. Die Zugangsdaten stammen aus POTATO_S3_ACCESS_KEY_ID und POTATO_S3_SECRET_ACCESS_KEY in der Umgebung des Servers oder aus der üblichen Kette von boto3. Ein Schlüssel, der auf diesen einen Bucket beschränkt ist, begrenzt, worauf der Server zugreifen kann. Eine Senke type: huggingface mit einer repo_id schickt die Kopie stattdessen in ein privates Hugging-Face-Dataset und verwendet dafür HF_TOKEN. Beide Senken brauchen das Extra hosting, pip install 'potato-annotation[hosting]'.
Eine Kopie, die auf Ihrem eigenen Speicher bleibt
Wenn eine Datennutzungsvereinbarung die Daten auf dem Server hält, kann ein nächtlicher Job sie stattdessen auf lokalen oder eingebundenen Speicher kopieren. Das folgende Skript archiviert das Ausgabeverzeichnis, erstellt von jeder Datenbank mit dem SQLite-Befehl .backup einen Snapshot, statt die Datei zu kopieren, und bewahrt Kopien von sieben Tagen auf. Es braucht das Kommandozeilenwerkzeug sqlite3:
#!/bin/bash
# backup.sh
set -e
TASK_DIR=/var/potato
DEST=/var/backups/potato/$(date +%Y%m%d_%H%M%S)
mkdir -p "$DEST"
tar -czf "$DEST/annotations.tar.gz" -C "$TASK_DIR" annotations
for db in project.sqlite datasets.sqlite engine.sqlite; do
if [ -f "$TASK_DIR/$db" ]; then
sqlite3 "$TASK_DIR/$db" ".backup '$DEST/$db'"
fi
done
cp "$TASK_DIR/config.yaml" "$DEST/"
if [ -f "$TASK_DIR/user_config.json" ]; then cp "$TASK_DIR/user_config.json" "$DEST/"; fi
find /var/backups/potato -mindepth 1 -maxdepth 1 -mtime +7 -exec rm -rf {} +Führen Sie es jede Nacht um 2 Uhr über cron aus:
0 2 * * * /var/potato/backup.shEine nächtliche Kopie kann bis zu einem Tag Arbeit verlieren, führen Sie sie während einer Studie mit vielen Annotatoren also häufiger aus. Stellen Sie vor Beginn der Studie eine Kopie auf einem Testserver wieder her und prüfen Sie, ob die letzte Annotation darin enthalten ist.
Sicherheits-Checkliste
- TLS am Proxy mit einem gültigen Zertifikat terminieren
-
secret_keyoderPOTATO_SECRET_KEYsetzen, damit Sitzungen einen Neustart überstehen -
admin_api_keysetzen, statt die erzeugte Datei auszuliefern - Registrierung mit
user_config.userseinschränken -
debugausgeschaltet lassen, da der Debug-Modus die Anfragen des Reverse Proxy ohne Schlüssel zu den Admin-Seiten durchlässt - gunicorn mit einem einzigen Worker ausführen
- Zugangsdaten in Umgebungsvariablen halten, nicht in der Konfiguration
-
POTATO_PROXY_FIXnur hinter einem vertrauenswürdigen Proxy aktivieren - Ausgabeverzeichnis, Kontendatei und Datenbanken sichern und eine Wiederherstellung testen
- Überprüfen, wer eine Admin-Rolle hat
Umgebungsvariablen
# .env file
POTATO_SECRET_KEY=your-super-secret-key
POTATO_ADMIN_API_KEY=your-admin-key
POTATO_DB_CONNECTION=mysql://potato:password@localhost:3306/potato_dbEinen Signaturschlüssel erzeugen Sie mit:
python3 -c 'import secrets; print(secrets.token_hex(32))'In Docker Compose laden:
services:
potato:
env_file:
- .envFehlerbehebung
Protokolle prüfen
# Gunicorn logs
journalctl -u potato -f
# Docker logs
docker-compose logs -f potatoHäufige Probleme
502 Bad Gateway: Prüfen Sie, ob Potato läuft und an den Port gebunden ist, an den der Proxy weiterleitet.
Nach einem Deployment sind alle abgemeldet: secret_key ist nicht gesetzt, daher wurde beim Start ein neuer Signaturschlüssel erzeugt.
Fehlende Annotationen: Prüfen Sie die Anzahl der Worker. Mehr als ein gunicorn-Worker verwirft Annotationen, ohne es zu melden.
Assets liefern unter einem Pfadpräfix 404: Setzen Sie POTATO_PROXY_FIX oder POTATO_URL_PREFIX, und prüfen Sie, ob der Proxy das Präfix vor dem Weiterleiten entfernt.
Langsam bei einem großen Korpus: Erweitern Sie den Speicher, bevor Sie Server hinzufügen, und ziehen Sie ab etwa einer Million Items item_store.backend: paged in Betracht.
Nächste Schritte
- Benutzerverwaltung - Zugriffslisten, Rollen und Kontingente
- Crowdsourcing - Prolific, SONA und andere Panels
- Datenformate - Speicherung und Export
Details zur Implementierung finden Sie in der Anleitung zum Reverse Proxy und in der Anleitung zur Skalierung.