Configuração de Produção
Implante o Potato em produção com gunicorn, nginx ou Apache, terminação TLS, Docker, persistência de sessões e o controle de acesso de que um servidor compartilhado precisa.
Implante o Potato para tarefas de anotação do mundo real com vários usuários, persistência de dados e segurança adequada.
Configuração do Servidor
Configuração Básica de Produção
server:
host: 0.0.0.0
port: 8000
debug: falseO bloco server aceita host, port e debug, e nada mais. O número de workers e os timeouts pertencem ao servidor WSGI que executa o Potato, e não ao arquivo de configuração. Mantenha debug desligado em produção, porque o modo de depuração ignora a autenticação.
Usando HTTPS
A configuração do Potato não tem nenhuma opção de TLS, então os certificados não são configurados nela. Faça a terminação TLS no proxy reverso à frente do Potato, que é o que as seções de nginx e Apache abaixo configuram, ou entregue o certificado diretamente ao gunicorn, como mostrado em "Com SSL".
Executando com Gunicorn
Execute o Potato no gunicorn com exatamente um worker e aumente o número de threads quando precisar de mais concorrência:
gunicorn "potato.flask_server:create_app('config.yaml')" \
--workers 1 \
--threads 8 \
--bind 0.0.0.0:8000 \
--timeout 120O worker único é um requisito de correção, e não uma escolha de ajuste. O Potato mantém em memória, dentro do processo, o pool de itens, a fila de atribuição e o estado de cada anotador. Um segundo worker recebe sua própria cópia dos três, então distribui instâncias que o primeiro worker já atribuiu, e como o user_state.json é reescrito por inteiro a cada salvamento, o worker que salvar por último descarta as anotações do outro. Nada relata a perda, e as anotações simplesmente somem. As threads compartilham uma única cópia desse estado, então --threads aumenta a concorrência com segurança, o que --workers não faz.
Com 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.pemConfiguração de Proxy Reverso
Configuração do Nginx
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;
}
}Configuração do Apache
<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>Montagem em um Caminho de URL
Servir o Potato em https://host/app1/, em vez da raiz de um domínio, exige um passo a mais. A página de anotação solicita /static/... e /updateinstance relativos à raiz pública, então, sob um prefixo de caminho, os recursos retornam 404 e as anotações não são salvas, embora a estrutura da página ainda seja exibida.
Informe ao Potato seu prefixo público. Quando você controla o proxy e ele pode enviar cabeçalhos encaminhados:
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;
}Ative POTATO_PROXY_FIX somente quando o aplicativo for acessível exclusivamente por esse proxy. Ele confia nos cabeçalhos encaminhados, então um cliente que consiga acessar a porta interna diretamente poderia falsificar o prefixo e corromper todas as URLs geradas.
Quando você não pode adicionar cabeçalhos, mas sabe o caminho de montagem, defina-o diretamente. O proxy ainda precisa remover o prefixo antes de encaminhar:
export POTATO_URL_PREFIX=/app1
potato start config.yaml -p 8000Teste fazendo login em um navegador sem sessão, já que o formulário de login é a primeira página que um prefixo ausente quebra.
Os visualizadores de agente ao vivo e de programação ao vivo transmitem por Server-Sent Events, que um proxy com buffer retém até o fim do fluxo. Desative o buffer nessas rotas:
location /app1/api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_set_header Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
}Implantação com Docker
O Potato publica uma imagem pronta no GitHub Container Registry, ghcr.io/davidjurgens/potato. Ela contém o Potato e suas dependências, e mais nada. A pasta do seu projeto, com o config.yaml e seus dados, é montada em /app, então uma única imagem serve para qualquer tarefa:
docker run -p 8000:7860 \
-v "$PWD/myproject:/app" \
-e POTATO_SECRET_KEY="$(openssl rand -hex 32)" \
ghcr.io/davidjurgens/potato:latestA tag latest e as tags de versão, como 2.10.3, trazem apenas as dependências principais. As tags -all (latest-all, 2.10.3-all) acrescentam os SDKs de IA, os leitores de formatos e os extras de exportação e autenticação, e deixam de fora os modelos de visão.
O contêiner serve a tarefa com um worker do gunicorn e oito threads, pelo motivo dado na seção sobre gunicorn acima. Definir GUNICORN_WORKERS acima de 1 interrompe o contêiner na inicialização, a menos que POTATO_ALLOW_MULTIWORKER=1 também esteja definido, o que perde anotações em um estudo real. Aumente GUNICORN_THREADS para ter mais concorrência. POTATO_CONFIG indica um arquivo de configuração diferente de config.yaml, e PORT muda a porta dentro do contêiner, que é 7860.
O contêiner é executado como uid 1000 e grava a saída das anotações, o potato.log e seus bancos de dados SQLite em /app. No Linux, uma pasta de projeto montada via bind mount que pertença a outro usuário interrompe o servidor na inicialização com um erro que nomeia o problema. Passe a pasta para o uid 1000 com sudo chown -R 1000:1000 myproject. O Docker Desktop no Mac ignora a propriedade dos bind mounts, então o erro aparece apenas no Linux.
Argumentos depois do nome da imagem substituem o comando do servidor, o que é útil para verificações pontuais:
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-stoppedDentro da rede do Compose, o nginx acessa o Potato pelo nome do serviço, na porta do contêiner, então o bloco upstream da seção de proxy reverso acima passa a ser server potato:7860; em vez de server 127.0.0.1:8000;.
Como a pasta do projeto é montada, e não copiada para uma imagem, as anotações que o Potato grava nela permanecem no host quando o contêiner é substituído.
Controle de Acesso
Restringindo Quem Pode se Cadastrar
Por padrão, o Potato permite o autocadastro. Em um servidor público, liste quem pode se cadastrar:
user_config:
allow_all_users: false
users:
- annotator1@example.com
- annotator2@example.comAs duas chaves pertencem a user_config. Escritas no nível superior, elas são ignoradas, o que parece uma lista de acesso, mas admite todo mundo.
Acesso de Administrador
admin_api_key é uma chave compartilhada enviada no cabeçalho X-API-Key. Quando você não a define, o Potato gera uma e a grava em {task_dir}/admin_api_key.txt, então, em um host de produção, defina-a você mesmo e mantenha-a fora do repositório:
admin_api_key: "your-shared-key"
rbac:
enabled: true
user_role_assignments:
lead@example.com: adminCom rbac configurado, as pessoas nomeadas acessam /admin fazendo login normalmente, em vez de repassar a chave compartilhada. Consulte Gerenciamento de usuários para o conjunto completo de permissões.
Sessões
secret_key: "a-long-random-string"
session_timeout_minutes: 60
persist_sessions: true
session_lifetime_days: 7
max_session_seconds: 7200secret_key assina o cookie de sessão e pode vir de POTATO_SECRET_KEY. Sem ela, o Potato gera uma nova chave a cada inicialização, então cada reinicialização desconecta todo mundo no meio da tarefa. Em um servidor que reinicia a cada deploy, esta é a configuração que com mais frequência custa anotações.
Saída das Anotações
output_annotation_dir: /var/potato/annotations/
export_annotation_format: [csv, jsonl]
auto_export_interval: 300O Potato grava um diretório por anotador em output_annotation_dir, cada um contendo o user_state.json daquela pessoa. export_annotation_format indica os formatos que a exportação periódica grava, e um valor vazio significa nenhuma exportação automática. auto_export_interval é o intervalo entre exportações, em segundos, e o padrão é 60.
Backend de Banco de Dados
Por padrão, as anotações ficam em disco. Para implantações que precisam de um banco de dados, database.type aceita mysql ou file:
database:
type: mysql
host: localhost
port: 3306
database: potato_db
username: potato
password: ${DB_PASSWORD}As chaves são database e username, e não name e user. Uma string de conexão pode vir de POTATO_DB_CONNECTION, o que mantém a credencial fora do arquivo de configuração.
Instale o driver com pip install 'potato-annotation[mysql]'. Antes da 2.10.2, o backend MySQL nunca era ativado, e uma configuração que o indicava armazenava as anotações em disco sem nenhum aviso. A partir da 2.10.2, um driver ausente ou um servidor inacessível interrompe a inicialização, e a exportação, os backups e o potato deploy pull leem o banco de dados.
Verificações de Integridade e Registro
O Potato serve GET /health em toda implantação, e a rota não exige autenticação, então ela funciona como sonda de prontidão para um balanceador de carga ou um orquestrador:
curl -sf https://annotation.example.com/healthO nível de detalhe do registro é definido na configuração ou na linha de comando, e não em um bloco de logging:
verbose: true
debug_log: serverdebug_log aceita all, ui, server ou none. A variável de ambiente POTATO_LOGGERS restringe a saída a loggers específicos quando você está investigando um único subsistema.
Escalabilidade
O Potato é executado como um único processo que mantém todo o conjunto de itens em memória, então escale a máquina, e não o número de servidores. Executar várias réplicas atrás de um balanceador de carga não divide o trabalho. Cada réplica mantém sua própria cópia da fila de atribuição e do estado dos anotadores, o que reproduz exatamente a perda de dados descrita na seção sobre gunicorn acima.
A memória é o recurso que cresce com o conjunto de dados. Por volta de um milhão de itens, o conteúdo dos itens pode passar para um cache SQLite:
item_store:
backend: paged # default: memory
cache_size: 2048Meça antes de ativar isso. A paginação economiza cerca de 28% em itens de visão e 51% em texto, e não uma ordem de grandeza, e torna o carregamento do corpus cerca de 2,5 vezes mais lento. O arquivo é um cache, reconstruído a partir dos arquivos de dados a cada inicialização, então apagá-lo é seguro.
Em projetos grandes, as alavancas que funcionam são limitar o que cada anotador recebe com per_annotator_quota, dividir o corpus entre coortes com batch_assignment e adicionar arquivos de dados ao longo do tempo com data_directory, em vez de carregar tudo na inicialização.
Estratégia de Backup
Backups Automatizados
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/var/backups/potato
# Backup annotations
tar -czf $BACKUP_DIR/annotations_$DATE.tar.gz /var/potato/annotations/
# Backup config
cp /var/potato/config.yaml $BACKUP_DIR/config_$DATE.yaml
# Keep only last 7 days
find $BACKUP_DIR -mtime +7 -deleteAdicione ao crontab:
0 2 * * * /var/potato/backup.shLista de Verificação de Segurança
- Fazer a terminação TLS no proxy com um certificado válido
- Definir
secret_key, ouPOTATO_SECRET_KEY, para que as sessões sobrevivam a uma reinicialização - Definir
admin_api_keyem vez de usar o arquivo gerado - Restringir o cadastro com
user_config.users - Manter
debugdesligado, já que o modo de depuração ignora a autenticação - Executar o gunicorn com um único worker
- Manter credenciais em variáveis de ambiente, e não na configuração
- Ativar
POTATO_PROXY_FIXsomente atrás de um proxy confiável - Fazer backup do diretório de saída das anotações de forma programada
- Revisar quem tem um papel de administrador
Variáveis de Ambiente
# .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_dbGere uma chave de assinatura com:
python3 -c 'import secrets; print(secrets.token_hex(32))'Carregue no Docker Compose:
services:
potato:
env_file:
- .envSolução de Problemas
Verificar Logs
# Gunicorn logs
journalctl -u potato -f
# Docker logs
docker-compose logs -f potatoProblemas Comuns
502 Bad Gateway: verifique se o Potato está em execução e vinculado à porta para a qual o proxy encaminha.
Todos desconectados após um deploy: secret_key não está definida, então uma nova chave de assinatura foi gerada na inicialização.
Anotações faltando: verifique o número de workers. Mais de um worker do gunicorn descarta anotações sem relatar nada.
Recursos com 404 sob um prefixo de caminho: defina POTATO_PROXY_FIX ou POTATO_URL_PREFIX e confirme que o proxy remove o prefixo antes de encaminhar.
Lentidão com um corpus grande: adicione memória antes de adicionar servidores e considere item_store.backend: paged a partir de cerca de um milhão de itens.
Próximos Passos
- Gerenciamento de usuários - Listas de acesso, papéis e cotas
- Crowdsourcing - Integração com Prolific e MTurk
- Formatos de dados - Armazenamento e exportação
Para detalhes de implementação, consulte o guia de proxy reverso e o guia de escalabilidade.