{ "meta": { "title": "Applicazioni multicontainer | ProxMenux", "description": "Come OCI manager Apps trasforma un'applicazione con il suo database e la cache in LXCs nativi coordinati: piano, rete privata, gancio di dipendenza, convalida e aggiornamenti transazionali." }, "header": { "title": "Applicazioni con più container", "description": "Un'applicazione con il suo database, la cache e altri servizi diventa diversi LXC nativi coordinati, installati e aggiornati come uno.", "section": "OCI manager Apps" }, "sections": [ { "id": "intro", "blocks": [ { "calloutInfo": { "title": "Un'applicazione per l'utente, diversi LXC per Proxmox VE", "body": "Una definizione multi-container è una voce del catalogo. L'installatore crea un LXC nativo per servizio e mantiene esplicite le proprie dipendenze. Immich, Nextcloud, Paperless-ngx e Tandoor sono installati in questo modo." } }, { "mermaid": { "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", "labels": { "plan": "Piano di stack", "app": "Applicazione LXC", "appNet": "LAN + privato", "db": "PostgreSQL LXC", "cache": "Valkey LXC", "private": "privato" } } } ] }, { "id": "plan", "title": "1. Il piano, prima che esista un contenitore", "blocks": [ { "p": "Nessun LXC viene creato mentre la definizione Compose viene interpretata. Un piano completo arriva prima, con ogni membro, immagine, VMID, rete, percorso, segreto, ordine e controllo sanitario. Se il piano non è coerente, l'installazione non inizia." }, { "table": { "headers": [ "Elemento di piano", "Contenuto", "Controllato prima di creare qualcosa" ], "rows": [ [ "Membri", "Applicazione principale, database, cache, machine learning e altre dipendenze", "VMID unici, ruoli noti e esattamente un membro principale" ], [ "Immagini", "Riferimento rolling, architettura e digest risolto di ogni servizio", "Tutte esistono, supportano l’architettura e superano il controllo di integrità OCI" ], [ "Rete", "Ponte privato, subnet, indirizzo fisso per servizio e accesso LAN per il membro principale", "Nessuna collisione con ponti o sottorete esistenti e nessun indirizzo ripetuto" ], [ "Persistenza", "Dischi dei container, directory dell’host, proprietari e backup", "Nessun percorso sovrapposto, deposito disponibile e permessi dichiarati" ], [ "Segreti", "password del database, chiavi dell'applicazione e credenziali iniziali", "Generati una volta e forniti solo ai membri che li usano" ], [ "Ciclo di vita", "Ordine di inizio, ordine di arresto e un controllo sanitario per membro", "L'applicazione principale inizia l'ultima e si ferma prima" ] ] } } ] }, { "id": "create", "title": "2. Creazione, membro per membro", "blocks": [ { "steps": { "items": [ { "title": "Prenota ogni VMID", "body": "L'inventario Proxmox VE e il registro delle istanze sono controllati. Un CT esistente non è adottato e un contratto che appartiene ancora ad un'altra istanza non viene riutilizzato." }, { "title": "Preparare ogni immagine", "body": "Tutte le immagini vengono risolte, scaricate e verificate prima che venga creato il primo contenitore." }, { "title": "Creare ogni rootfs", "body": "I metadati OCI ufficiali sono importati, e l'identità di istanza ProxMenux e il ruolo membro sono aggiunti." }, { "title": "Rete privata", "body": "Viene creato un ponte e una subnet e ogni membro riceve il suo indirizzo fisso; solo il membro principale riceve anche l'interfaccia LAN." }, { "title": "Persistenza", "body": "Ogni database e configurazione ottiene il proprio disco contenitore; solo i dati destinati ad essere condivisi utilizzano directory host." }, { "title": "Ambiente", "body": "Vengono scritti endpoint interni, segreti condivisi e variabili di servizio. L'applicazione raggiunge le sue dipendenze nei loro indirizzi privati riservati." }, { "title": "Registrazione", "body": "Ogni membro registra la sua configurazione nativa, le modifiche rootfs che gli aggiornamenti devono riprodurre e la sua relazione con lo stack." } ] } } ] }, { "id": "checks", "title": "3. Avviare e controllare ogni contenitore", "intro": "Un LXC creato non significa un servizio pronto. Le dipendenze iniziano in ordine e ognuno passa un controllo specifico al suo servizio.", "blocks": [ { "table": { "headers": [ "Servizio", "Controllo", "Cosa mostra" ], "rows": [ [ "PostgreSQL", "pg_isready nel CT con l'host previsto, l'utente e il database", "Il server accetta le connessioni per il database configurato" ], [ "Redis / Valkey", "redis-cli o valkey-cli PING contro il suo indirizzo privato", "Il broker ascolta e risponde" ], [ "Machine learning Immich", "HTTP GET /ping, oltre a un controllo del runtime GPU selezionato", "Il servizio risponde e l’accelerazione richiesta non è passata alla CPU" ], [ "Nextcloud", "GET /status.php con installed=true, maintenance=false e needsDbUpgrade=false", "Inizializzazione terminata senza migrazione in sospeso" ], [ "Paperless-ngx / Tandoor", "HTTP sull'indirizzo LAN e vera porta del servizio", "Il frontend e le sue dipendenze servono l'applicazione" ], [ "Applicazione principale", "Il punto finale del suo modello, ad esempio /api/server/ping in Immich", "L'intero stack funziona attraverso l'applicazione che utilizza le dipendenze" ] ] } }, { "calloutWarning": { "title": "In esecuzione non significa funzionante", "body": "Lo stato in esecuzione dice solo che esiste il processo LXC. Se il servizio offre un controllo migliore, viene utilizzato un controllo exec o HTTP con un timeout. Se un membro si ferma o non supera la verifica, lo stack non viene dichiarato installato." } } ] }, { "id": "hook", "title": "4. Il gancio di dipendenza", "blocks": [ { "p": "Il gancio è impostato solo sul contenitore principale di uno stack dipendente. Proxmox VE mantiene lo script come snippet e lo gestisce come hookscript di quel CT. La ricetta dello stack non è scritta nello script: vive in un contratto privato separato." }, { "codeGrid": { "items": [ { "title": "Configurazione principale CT", "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" }, { "title": "Contratto privato (esempio)", "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" } ] } }, { "snippet": { "summary": "Fonte completa di proxmenux-stack-dependencies.sh", "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", "snippetCode": "stackDependencyHook" } }, { "p": "Lo script è lo stesso per ogni stack. VMID, nomi, tipi di controllo e timeout provengono dal contratto privato /etc/pve/priv/proxmenux-stack-VMID.json di ogni stack." }, { "steps": { "items": [ { "title": "Proxmox VE chiama pre-start", "body": "Prima dell'avvio principale di CT, il gancio viene eseguito con il VMID e la fase del ciclo di vita." }, { "title": "Un blocco per stack", "body": "flock su /run/lock/proxmenux-stack-VMID.lock previene due sequenze di avvio contemporaneamente." }, { "title": "Il contratto viene letto e convalidato", "body": "Uno schema noto, dipendenze numeriche, etichette, un exec, http o controllo in esecuzione e un timeout positivo sono necessari." }, { "title": "Dipendenze in ordine", "body": "Ogni CT deve esistere; se è fermo viene avviato, se è già in esecuzione non viene riavviato." }, { "title": "Attendi che il servizio sia pronto", "body": "Il controllo viene eseguito ogni due secondi, e il CT è confermato anche di essere ancora in esecuzione." }, { "title": "Il principale CT inizia", "body": "Quando ogni dipendenza è pronta, pre-start termina e Proxmox VE avvia l'applicazione." } ] } }, { "calloutInfo": { "title": "Il gancio non ferma le dipendenze", "body": "Le fasi post-start, pre-stop e post-stop non fanno nulla. Arrestare il CT principale lascia in esecuzione PostgreSQL, Redis, Valkey o il machine learning. L’hook ordina l’avvio; non trasforma diversi LXC in un unico processo." } }, { "table": { "headers": [ "Tipo", "Esempio", "Avvii successivi" ], "rows": [ [ "stack dipendente", "Immich, Nextcloud, applicazione con PostgreSQL", "L’hook del CT principale avvia le dipendenze e attende che siano pronte." ], [ "Suite di applicazione", "Suite Arr", "Nessun membro principale: ogni LXC segue il proprio onboot" ], [ "Applicazione singola", "Jellyfin", "Proxmox VE avvia direttamente LXC" ] ] } } ] }, { "id": "stack-checks", "title": "5. Controlla sullo stack nel suo complesso", "blocks": [ { "list": { "items": [ "Ogni VMID esiste, è unico e mantiene l'identità di istanza prevista.", "Ogni contratto appartiene allo stesso stack, mantiene il suo ruolo e la sua configurazione nativa registrata.", "Il membro principale è l’ultimo in start_order e il primo in stop_order.", "Nessun membro, volume o adattamento rootfs manca.", "Il gancio punta al snippet ufficiale, il suo contenuto è invariato e il suo contratto corrisponde alla ricetta stack.", "Le immagini e i digest osservati corrispondono agli archivi OCI preparati.", "Dispositivi, profili GPU, montaggi, segreti ed endpoint corrispondono ancora ai contratti.", "Quando tutte le dipendenze superano i controlli, l’endpoint dell’applicazione principale verifica l’integrazione fra i membri." ] } } ] }, { "id": "manage", "title": "Gestione di uno stack installato", "intro": "In Gestisci applicazioni OCI installate, qualsiasi membro porta all’intero stack. Il menu offre Aggiorna tutti i container dell’applicazione, Modifica percorsi e dispositivi aggiuntivi, Ricrea tutti i container con la configurazione salvata e Rimuovi: elimina l’applicazione e i suoi container. Ricrea ricostruisce ogni container dall’immagine originale tramite il digest salvato, senza cercarne una più recente, seguendo gli stessi passaggi di un aggiornamento. È disponibile per gli stack aggiornabili.", "blocks": [ { "steps": { "items": [ { "title": "Qualsiasi membro", "body": "Il contratto dei membri nomina il VMID principale e l'elenco completo dei membri." }, { "title": "Lo stack è riproducibile", "body": "Identità, contratti, gancio, adattamenti e una ripetizione coordinata per quella ricetta sono convalidati." }, { "title": "Immagini prima", "body": "L'applicazione non viene fermata fino a quando ogni digest è stato risolto, scaricato e verificato." }, { "title": "Arresta i componenti e crea un backup dell'insieme", "body": "I backup nativi verificati sono presi con lo stack fermato, quindi l'applicazione e i database appartengono allo stesso momento." }, { "title": "Aggiornare e controllare ogni membro", "body": "Adattamento, rete, montaggi, segreti e dispositivi vengono applicati nuovamente prima del controllo di integrità di ciascun membro." }, { "title": "Pubblica o recupera tutto", "body": "I nuovi contratti sono pubblicati solo quando ogni membro passa. Se uno fallisce, ogni membro viene ripristinato." } ] } }, { "table": { "headers": [ "Modifica", "Quali cambiamenti", "Cosa viene conservato" ], "rows": [ [ "Percorsi e dispositivi aggiuntivi", "Vengono aggiunti o rimossi dal contenitore di applicazione, che si riavvia. Nulla è ricostruito", "I dati dell'applicazione, il suo database e gli altri contenitori" ], [ "Che cosa esegue il riconoscimento (Immich)", "Il contenitore di apprendimento automatico prende l'immagine e i dispositivi del CPU o di un GPU dell'host. L'intera applicazione viene interrotta e aggiornata, come in un aggiornamento", "La cache del modello, la libreria e il database. Se qualcosa fallisce, i contenitori precedenti e la scelta precedente vengono ripristinati" ] ] } }, { "calloutWarning": { "title": "Una pila senza un replay coordinato non è aggiornata", "body": "Se la preparazione di uno stack non può essere riprodotta, l'aggiornamento viene rifiutato prima che lo stack venga interrotto. L'applicazione continua a funzionare come è." } } ] }, { "id": "failure", "title": "6. Quando qualcosa fallisce", "blocks": [ { "p": "Durante un'installazione, un errore si ferma e rimuove i contenitori incompleti che l'operazione ha creato, e il ponte privato se l'operazione l'ha creata. Una pila non viene pubblicata come valida fino al termine dell'intera sequenza." }, { "p": "Un aggiornamento è transazionale: tutte le immagini sono preparate prima, poi lo stack si ferma, viene eseguito e verificato il backup di ogni membro, e solo allora è sostituito ogni rootfs. I membri iniziano uno per uno con il loro controllo sanitario; se uno fallisce, ogni membro viene ripristinato dalla stessa serie di backup, quindi il database e l'applicazione non appartengono mai a momenti diversi." }, { "mermaid": { "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", "labels": { "prepare": "Preparare ogni immagine", "stop": "Stop, primo membro principale", "backup": "Backup verificato di ogni CT", "replace": "Sostituisci il rootfs", "healthy": "Tutti funzionanti?", "yes": "Sì", "no": "No", "commit": "Pubblica i contratti", "rollback": "Ripristinare ogni membro" } } } ] }, { "id": "not-assumed", "title": "Che cosa uno stack non include", "blocks": [ { "list": { "items": [ "Una suite installata in un unico flusso, come la suite Arr, non è uno stack dipendente: i suoi contenitori non hanno ordine di partenza tra di loro.", "La rete privata non sostituisce l'autenticazione, TLS o la configurazione di ogni applicazione.", "Il backup di un LXC non contiene il ponte, il gancio o i contratti del resto dello stack.", "Prowlarr, Sonarr o Radarr non ricevono indici, profili o fornitori da ProxMenux." ] } } ] } ] }