Un avvio fallito di OpenVPN arriva spesso come una lamentela sulle risorse insieme a un suggerimento ben intenzionato di alzare un limite, e il suggerimento funziona solo circa una volta su due. Dietro questi fallimenti ci sono due contatori senza rapporto. Uno è un limite del kernel sul numero di attività possedute da un identificatore utente reale, e viene ereditato quando il processo viene eseguito. L’altro è un budget cgroup che il gestore dei servizi applica a una unità e a tutto ciò che essa biforca. Leggere il valore effettivo di ciascuno, per l’unità che gira davvero, costa meno di una supposizione.

Due contatori, due domande

LimitNPROC è RLIMIT_NPROC sotto un nome systemd, e la sua unità di contabilità è l’identificatore utente reale. Ogni processo posseduto da quell’utente conta sullo stesso totale, compreso il lavoro avviato da altre unità, da una sessione di accesso o da un’attività dello scheduler. Il limite quindi non è un tetto per servizio anche quando è impostato dentro una unità di servizio, e un servizio che biforca un aiutante per ogni client compete con tutto il resto che gira con quell’account. Un’attività cron occupata sotto lo stesso utente può spingere una unità VPN oltre una linea che da sola non ha mai superato.

TasksMax ha un altro proprietario: il controller pids sul cgroup dell’unità, che conta attività invece di processi. I thread sono attività, quindi un demone che sembra a thread singolo ma ha un pool di lavoratori può consumare un budget che a prima vista sembra molto più grande del suo numero di processi. EffectiveTasksMax riporta ciò che la gerarchia cgroup permette realmente dopo che le slice padre hanno detto la loro.

Perché l’eccezione root cambia la risposta

Il kernel non applica RLIMIT_NPROC a un processo che porta CAP_SYS_RESOURCE o CAP_SYS_ADMIN, ed è per questo che una unità eseguita come root senza rinunciare ai privilegi è di fatto illimitata da quell’impostazione. Questa unica eccezione spiega i consigli contraddittori che le persone si scambiano: alzare LimitNPROC ripara un dispiegamento che ha rinunciato ai privilegi e non fa assolutamente nulla per uno rimasto privilegiato. Verifica User= e se il demone cambia identità dopo l’avvio prima di attribuire meriti o colpe a quell’impostazione. Una unità privilegiata che poi passa a un account non privilegiato ricade sotto il limite nel momento del passaggio, il che rende anche la cronologia del guasto degna di lettura.

La fase del guasto lo restringe ulteriormente. Un rifiuto durante la preparazione dell’esecuzione, un errore di biforcazione segnalato dal demone stesso e una creazione di attività respinta dentro un cgroup producono righe di journal diverse, e solo una di esse viene risolta con un budget maggiore.

Leggi i valori effettivi, non quelli della shell

ulimit -u di una shell interattiva descrive quella shell. Non descrive ciò che il gestore dei servizi ha passato al demone e non dice nulla sulla contabilità cgroup. Interroga invece l’unità, usando il nome di istanza che esiste sull’host e non un esempio preso da una guida.

systemctl show [email protected] -p User -p LimitNPROC -p TasksCurrent -p TasksMax -p EffectiveTasksMax
cat /proc/$(systemctl show -p MainPID --value [email protected])/limits | grep -i processes

Dentro un container la risposta richiede due letture in più, perché un valore interno di infinity descrive solo la vista dell’ospite. Chiedi al proprietario dell’host il tetto pids proprio del container e verifica se il gestore dei servizi interno possa delegare quel controller. Una unità annidata non può tenere un budget che il suo cgroup padre non ha mai concesso.

Dimensiona la correzione e dimostrala sotto carico

Scegli un numero dal carico di lavoro e non dal testo dell’errore. Conta i processi che il demone avvia per ogni client connesso o per ogni invocazione di aiutante, moltiplica per la concorrenza che il dispiegamento deve sopportare, aggiungi un margine per le sessioni amministrative, poi confronta quella cifra con i due valori effettivi. Una sovrascrittura locale di tipo drop-in mantiene la modifica rivedibile e sopravvive agli aggiornamenti dei pacchetti, mentre modificare un file di unità fornito nasconde la modifica finché il prossimo aggiornamento non lo sostituisce. Scrivi l’aritmetica accanto alla sovrascrittura, perché la prossima persona che la alzerà o abbasserà ha bisogno delle ipotesi, non del numero finale.

Le modifiche al budget si applicano all’esecuzione, quindi un semplice ricaricamento non ridimensiona i processi già in corso. Riavvia l’unità attraverso il suo normale ciclo di vita, accettando l’interruzione della sessione, e rileggi i contatori sulla nuova istanza.

sudo systemctl daemon-reload
sudo systemctl restart [email protected]
systemctl show [email protected] -p TasksCurrent -p EffectiveTasksMax
sudo journalctl -u [email protected] -b --no-pager | tail -n 20

Ciò che dimostra la riparazione è una unità riavviata il cui conteggio delle attività resta sotto entrambi i tetti mentre porta il carico previsto, più un journal che non segnala più esaurimento di attività o biforcazioni nella stessa fase. Conserva i numeri prima e dopo insieme alla modifica, così la prossima indagine parte da una misurazione e non dallo stesso consiglio. Se il conteggio raggiunge ancora il tetto, l’aritmetica era sbagliata e non il limite troppo piccolo per caso, e quella differenza vale la pena conoscerla prima del prossimo riavvio.

Articoli correlati