Potato auf einem Server bereitstellen
Potato in Produktion hinter nginx mit TLS betreiben, in Docker oder auf einer VM, und warum ein Potato-Server als einzelner Prozess statt mit mehreren Workern läuft.
Betreiben Sie einen Potato-Prozess pro Projekt, stellen Sie nginx und ein TLS-Zertifikat davor und erhöhen Sie die Zahl der Threads statt der Worker-Prozesse, wenn Sie mehr Nebenläufigkeit brauchen. Ein zweiter Worker-Prozess verliert Annotationen, ohne es zu melden.
Potato hält seinen Item-Pool, seine Zuweisungswarteschlange und den Zustand jedes Annotators im Arbeitsspeicher des Serverprozesses. Diese eine Tatsache bestimmt den größten Teil eines Deployments. Wegen dieses prozessinternen Zustands skaliert ein Potato-Server über Threads statt über Worker, ist das Ausgabeverzeichnis der Annotationen das, was Sie sichern müssen, und verfälscht Lastverteilung über mehrere Potato-Prozesse eine Studie, statt sie zu beschleunigen. Dieser Leitfaden behandelt die drei Wege, einen Server zu betreiben, den Reverse Proxy und TLS davor und die Konfigurationsschlüssel, auf die es ankommt, sobald echte Annotatoren darauf arbeiten.
Warum ein Potato-Server als ein Prozess läuft
Ein zweiter gunicorn-Worker bekommt seine eigene Kopie des Item-Pools, der Zuweisungswarteschlange und des Benutzerzustands. Er verteilt Instanzen, die der erste Worker schon 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 in den Logs meldet das. Annotationen verschwinden, und das erste Anzeichen ist meist ein Annotator, der sagt, er habe ein Item gelabelt, das jetzt unbearbeitet aussieht.
Das veröffentlichte Docker-Image von Potato erzwingt diese Regel. Es startet einen einzigen gunicorn-Worker und verweigert den Start mit mehr, und POTATO_ALLOW_MULTIWORKER=1 hebt diese Sperre für alles auf, was keine echte Studie ist. Nebenläufigkeit kommt stattdessen von Threads, die sich eine einzige Kopie des Zustands teilen. Das Image nutzt standardmäßig acht davon über GUNICORN_THREADS, was für die Dutzenden gleichzeitigen Annotatoren einer typischen Studie reicht.
Diese Einschränkung setzt den Rahmen für den Rest dieses Leitfadens. Betreiben Sie einen Prozess pro Projekt, geben Sie ihm genug Speicher für den Datensatz und nutzen Sie den Reverse Proxy für TLS und statische Dateien, nicht zur Lastverteilung.
Drei Wege, einen Server zu betreiben
| Methode | Was Sie bekommen | Wann sie passt |
|---|---|---|
potato deploy | Stellt Host, TLS und Image aus Ihrer Konfiguration bereit | Eine Studie, die heute laufen soll |
| Docker-Image | Jedes Mal dieselben Bytes, kein Bauen von Abhängigkeiten | Eine Maschine, die Sie bereits verwalten |
| VM mit systemd | Volle Kontrolle über Host und Python-Umgebung | Ein Institutsserver mit eigenen Regeln |
potato deploy für den schnellen Weg
potato deploy stellt einen Host bereit, installiert das Image und richtet TLS anhand der Konfigurationsdatei ein, die Sie schon haben:
potato deploy up myproject/config.yaml --provider digitaloceanEinige Minuten später haben Sie eine HTTPS-URL, die Sie an die Annotatoren schicken können, und potato deploy pull holt die Annotationen zurück. Seit 2.10.0 sind die Ziele local, potato share und dreizehn Cloud-Hosts, darunter AWS, Jetstream2, Hetzner, Fly und Railway, und sie unterscheiden sich darin, ob die Daten einen Neustart überstehen. Die sieben VM-Ziele bieten persistenten Speicher, Log-Streaming und SSH zusammen, und ein Host, der seinen Datenträger löscht, braucht --backup, das die Annotationen in ein HuggingFace-Dataset oder einen S3-Bucket kopiert. Potato lokal oder in der Cloud ausführen vergleicht sie. Beginnen Sie mit local. Es führt dasselbe Bundle und dasselbe Image ohne Konto und ohne Rechnung aus und zeigt Ihnen, was bereitgestellt würde, bevor irgendetwas Kosten verursacht.
Das veröffentlichte Docker-Image
Potato veröffentlicht ein vorgefertigtes Image in der GitHub Container Registry. Es enthält den Server und seine Abhängigkeiten, und Ihr Projekt wird zur Laufzeit unter /app eingebunden, sodass ein Image für jede Aufgabe dient:
docker run -p 8000:7860 \
-v "$PWD/myproject:/app" \
-e POTATO_SECRET_KEY="$(openssl rand -hex 32)" \
ghcr.io/davidjurgens/potato:latestDas Image gibt es, weil zu den Kernabhängigkeiten von Potato numpy, pandas, scipy und scikit-learn gehören. Sie auf einer kleinen VM zu installieren ist bestenfalls langsam. Auf einer Maschine mit 1 GB wird der Build wegen Speichermangels abgebrochen, und auf 2 GB dauert er bei jedem erneuten Deployment fünf bis zehn Minuten. Das Image herunterzuladen dauert weniger als eine Minute.
Zwei Tags sind relevant. latest enthält die Kernabhängigkeiten und ist etwa 840 MB groß, und latest-all fügt die KI-SDKs, Dokumentformate, Export und OAuth-Anmeldungen hinzu und kommt auf etwa 1,5 GB. Beide werden für linux/amd64 und linux/arm64 gebaut. Legen Sie für eine Studie, die Monate läuft, eine feste Version statt latest fest, denn latest ändert sich unter Ihnen.
Setzen Sie POTATO_SECRET_KEY bei allem, was länger als einen einzelnen Lauf existiert. Ohne ihn ist der Signaturschlüssel für Flask-Sitzungen pro Prozess zufällig, sodass jeder Neustart alle Annotatoren abmeldet. POTATO_CONFIG wählt einen Konfigurationspfad im Container, PORT setzt den Port, auf dem der Server lauscht (standardmäßig 7860), und GUNICORN_TIMEOUT setzt das Worker-Timeout in Sekunden.
Das Image läuft als uid 1000, und ein Bind-Mount behält die Eigentumsrechte des Hosts bei, sodass der Container unter Linux oft nicht in ein Verzeichnis schreiben kann, das Sie angelegt haben. Der Server prüft das beim Start und verweigert den Betrieb, statt mitten im Hochfahren abzustürzen. Übergeben Sie das Verzeichnis entweder mit sudo chown -R 1000:1000 myproject an den Container-Benutzer, oder führen Sie den Container mit docker run --user "$(id -u):$(id -g)" als Sie selbst aus, womit auch die Annotationen Ihnen gehören. Docker Desktop unter macOS und Windows ignoriert Eigentumsrechte bei Bind-Mounts, sodass ein Setup, das auf einem Mac funktioniert, auf einem Linux-Server trotzdem scheitern kann.
Installation auf einer VM
Auf einer Maschine, die Sie direkt verwalten, installieren Sie Potato in eine virtuelle Umgebung und überlassen systemd den Prozess.
sudo apt update
sudo apt install python3 python3-venv python3-pip nginx certbot python3-certbot-nginx
sudo mkdir -p /opt/potato/projects
cd /opt/potato
python3 -m venv venv
source venv/bin/activate
pip install potato-annotation gunicorn
potato --versionFühren Sie die App unter gunicorn mit einem Worker und mehreren Threads aus. Die Application Factory liegt unter potato.flask_server:create_app und nimmt den Konfigurationspfad entgegen:
gunicorn -w 1 --threads 8 -b 127.0.0.1:8000 \
--chdir /opt/potato/projects/my_project \
"potato.flask_server:create_app('config.yaml')"Legen Sie /etc/systemd/system/potato.service an, damit der Server nach einem Neustart oder Absturz wieder hochkommt:
[Unit]
Description=Potato Annotation Server
After=network.target
[Service]
Type=simple
User=potato
Group=potato
WorkingDirectory=/opt/potato/projects/my_project
Environment="PATH=/opt/potato/venv/bin"
EnvironmentFile=/opt/potato/.env
ExecStart=/opt/potato/venv/bin/gunicorn -w 1 --threads 8 -b 127.0.0.1:8000 \
"potato.flask_server:create_app('config.yaml')"
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetAktivieren und starten Sie ihn:
sudo systemctl enable potato
sudo systemctl start potato
sudo systemctl status potatoDie EnvironmentFile enthält die Geheimnisse, die nicht in die Konfiguration gehören:
# /opt/potato/.env
POTATO_SECRET_KEY=a-long-random-value
OPENAI_API_KEY=sk-...Erzeugen Sie den geheimen Schlüssel einmal mit openssl rand -hex 32 und behalten Sie ihn für die gesamte Studie. Ihn zu ändern macht alle Sitzungen ungültig und meldet alle Annotatoren ab.
nginx und TLS
Potato bindet an localhost, und nginx terminiert TLS davor. Das Upload-Limit ist bei Bild- und Audioprojekten wichtig, wo ein Standardlimit von 1 MB für den Request-Body Medien ablehnt, ohne dass der Annotator den Fehler sieht.
server {
listen 443 ssl;
http2 on;
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;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:8000;
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;
}
}Aktivieren Sie die Site und holen Sie ein Zertifikat:
sudo ln -s /etc/nginx/sites-available/potato /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d annotation.example.com
sudo systemctl enable certbot.timerSchließen Sie alles andere an der Firewall, denn der Potato-Port darf nicht mehr direkt erreichbar sein, sobald ein Proxy davorsteht:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enableMehrere Projekte unter einem Hostnamen bereitstellen
Institutionen stellen oft mehrere Potato-Server über einen einzigen öffentlichen HTTPS-Endpunkt bereit, indem sie Pfade auf lokale Ports abbilden. Die Annotationsoberfläche lädt Ressourcen und speichert über wurzelrelative URLs, deshalb muss ein unter /app1 eingebundener Server sein Präfix kennen, sonst liefert das CSS 404 und automatische Speicherungen scheitern mit "annotations not saved". Potato liest das Präfix aus dem WSGI-SCRIPT_NAME, und es gibt zwei Wege, es zu setzen.
Verwenden Sie POTATO_PROXY_FIX=1, wenn Sie den Proxy kontrollieren und er X-Forwarded-Prefix senden kann. Verwenden Sie POTATO_URL_PREFIX=/app1, wenn Sie die Proxy-Konfiguration nicht ändern können. Der Proxy muss das Präfix trotzdem vor dem Weiterleiten entfernen, damit Flask weiterhin Pfade ohne Präfix erhält:
location /app1/ {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /app1;
}ProxyFix vertraut weitergeleiteten Headern, aktivieren Sie POTATO_PROXY_FIX also nur, wenn die App ausschließlich über den vertrauenswürdigen Proxy erreichbar ist. Ist der interne Port auch direkt erreichbar, kann ein Client das Präfix fälschen und die URLs vergiften, die Potato erzeugt.
Die Konfiguration für ein Produktionsprojekt
Vier Einstellungen unterscheiden eine Produktionskonfiguration von einer lokalen: ein stabiler Signaturschlüssel, eine Passwortpflicht, eine explizite Sitzungsdauer und ein Ausgabeverzeichnis, von dem Sie eine Sicherung haben.
annotation_task_name: "Production Annotation Task"
task_description: "Classify each passage."
host: 127.0.0.1
port: 8000
# Sessions survive a restart only when the signing key is stable.
# Set it here, or leave it out and supply POTATO_SECRET_KEY in the environment.
require_password: true
persist_sessions: true
session_timeout_minutes: 480
data_files:
- data/items.jsonl
item_properties:
id_key: id
text_key: text
output_annotation_dir: annotation_output/production/
export_annotation_format: jsonl
user_config:
allow_all_users: false
users:
- annotator1
- annotator2
annotation_schemes:
- annotation_type: radio
name: sentiment
description: "What is the sentiment expressed in this text?"
labels:
- name: positive
- name: neutral
- name: negative
label_requirement:
required: truesession_timeout_minutes begrenzt die Leerlaufzeit, nicht die Länge einer Arbeitsschicht, denn die Uhr beginnt bei jeder Anfrage neu. Der Standardwert 480 passt zu Annotatoren, die einen ganzen Tag arbeiten; senken Sie ihn, wenn die Maschine geteilt wird. Setzen Sie allow_all_users: false und listen Sie Ihre Annotatoren auf, sobald der Server aus dem öffentlichen Internet erreichbar ist, damit sich ein Fremder, der die URL findet, nicht registrieren und in Ihren Datensatz schreiben kann.
Health-Checks und Logs
/health ist eine nicht authentifizierte Liveness-Probe für Container und Load Balancer und meldet absichtlich fast nichts, weil alles, was sie sagt, öffentlich ist:
curl http://localhost:8000/health
# {"status": "ok"} once the state managers exist
# {"status": "starting"} 503 while the server is still coming upEin Container-Healthcheck und die Abfrage eines Deployment-Anbieters nach dem Anlegen richten sich beide nach diesem Übergang. Aufgabennamen, Item-Zahlen und Annotatorenzahlen liefert stattdessen /admin/health, das den Admin-API-Schlüssel verlangt.
Die Protokollierung läuft über den Prozess, sodass systemd sie erfasst:
sudo journalctl -u potato -fErhöhen Sie die Stufe in der Konfiguration mit verbose: true, oder schalten Sie mit debug_log die Teile der Debug-Protokollierung ein, die Sie brauchen. Es nimmt all, ui, server oder none. Lassen Sie beides während einer echten Studie aus, denn die Debug-Protokollierung ist so ausführlich, dass sie über einen langen Lauf eine kleine Festplatte füllen kann.
Was gesichert werden muss
Drei Dinge im Projektverzeichnis lassen sich nicht neu erzeugen. Die Annotationsausgabe unter output_annotation_dir ist die Studie. project.sqlite enthält Memos, das Codebuch, Fälle, den Suchindex und den Review-Workflow. datasets.sqlite enthält die Datensatzeinträge. Alles andere lässt sich aus Ihren Datendateien und Ihrer Konfiguration neu aufbauen.
#!/bin/bash
# /opt/potato/backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/opt/potato/backups
tar -czf $BACKUP_DIR/annotations_$DATE.tar.gz \
/opt/potato/projects/*/annotation_output/ \
/opt/potato/projects/*/project.sqlite \
/opt/potato/projects/*/datasets.sqlite \
/opt/potato/projects/*/config.yaml
find $BACKUP_DIR -mtime +7 -deleteFühren Sie das Skript jede Nacht über crontab aus und kopieren Sie das Archiv auf eine andere Maschine:
0 2 * * * /opt/potato/backup.shDie Maschine dimensionieren
Arbeitsspeicher ist die Ressource, die mit dem Datensatz wächst, weil der Item-Pool im Prozess gehalten wird. Alles andere wird inzwischen erst bei Bedarf geladen, sodass der Embedding- und sentence-transformers-Stack beim Start nur importiert wird, wenn eine Funktion ihn braucht. Auf dem Referenzdatensatz aus den Release Notes von v2.6.0 sank der residente Speicher von etwa 750 MB auf etwa 365 MB und der Start mit 50.000 Items von etwa 10 Sekunden auf etwa 5,7 Sekunden.
Die Kosten pro Item sind klein und messbar. Ein Vision-Item mit einer Bild-URL und zwei Feldern belegt etwa 932 Bytes, ein Text-Item mit rund 540 eindeutigen Zeichen etwa 1430 Bytes. Eine Million Text-Items entspricht also etwa 1,43 GB residentem Speicher, und das ist die Zahl, mit der Sie planen sollten.
Für Korpora, bei denen das ins Gewicht fällt, können die Item-Inhalte in einer SQLite-Datei mit einem kleinen Cache im Speicher liegen:
item_store:
backend: paged
cache_size: 2048Fügen Sie diesen Block der obigen Konfiguration hinzu. Lesen Sie die Messung, bevor Sie ihn aktivieren. Paging spart 28 % bei Vision-Items und 51 % bei Text, keine Größenordnung, und dafür dauert das Laden des Korpus etwa 2,5-mal so lange. Unter einer Million Items bringt es einen langsameren Server und keinen Nutzen.
Nehmen Sie lieber eine Maschine mit mehr RAM, als ein Projekt auf mehrere Server aufzuteilen, denn das Aufteilen ist genau der Mehrprozessfehler, der am Anfang dieses Leitfadens beschrieben ist. Wenn eine Maschine den Korpus wirklich nicht fassen kann, teilen Sie stattdessen die Arbeit auf. Batch-Zuweisung gibt jedem Annotator einen Ausschnitt, per_annotator_quota begrenzt, wie viel einer einzelnen Person zugewiesen wird, und ein data_directory mit Verzeichnisüberwachung erlaubt es, Dateien hinzuzufügen, während der Server läuft.
Fehlerbehebung
Der Port ist bereits belegt. Suchen Sie den Prozess und beenden Sie ihn, statt einen zweiten Potato auf einem anderen Port mit demselben Projektverzeichnis zu starten:
sudo lsof -i :8000Zugriff auf das Projektverzeichnis verweigert. Der Dienstbenutzer muss Eigentümer sein, und in Docker ist dieser Benutzer uid 1000:
sudo chown -R potato:potato /opt/potatoAnnotatoren werden nach jedem Neustart abgemeldet. POTATO_SECRET_KEY oder der Konfigurationswert secret_key fehlt, sodass der Signaturschlüssel bei jedem Start neu erzeugt wird.
Ressourcen liefern 404 und automatische Speicherungen scheitern hinter einem Proxy. Der Server kennt sein URL-Präfix nicht. Setzen Sie POTATO_PROXY_FIX=1 oder POTATO_URL_PREFIX und prüfen Sie, ob der Proxy das Präfix vor dem Weiterleiten entfernt.
Weiterführende Literatur
- Produktions-Setup und Reverse Proxy auf dieser Website
- Running Potato in Docker als Referenz zum Image
- Scaling and Large Datasets mit den Messungen hinter den Empfehlungen zur Dimensionierung
- Annotationsstudien auf Prolific durchführen, sobald der Server erreichbar ist