LimitNPROC versus TasksMax: o que uma unidade OpenVPN conta
O LimitNPROC conta os processos de um usuário em todo o sistema; o TasksMax limita o cgroup de uma unidade. Leia os valores efetivos antes de elevar qualquer um deles.
Uma inicialização falhada do OpenVPN costuma chegar como uma queixa sobre recursos junto com uma sugestão bem-intencionada de elevar algum limite, e a sugestão só funciona em cerca de metade dos casos. Por trás dessas falhas estão dois contadores sem relação. Um é um limite do kernel sobre o número de tarefas pertencentes a um identificador de usuário real, herdado quando o processo é executado. O outro é um orçamento de cgroup que o gestor de serviços aplica a uma unidade e a tudo o que ela bifurca. Ler o valor efetivo de cada um, para a unidade que realmente executa, custa menos que um palpite.
Dois contadores, duas perguntas
LimitNPROC é RLIMIT_NPROC sob um nome do systemd, e sua unidade de contabilização é o identificador de usuário real. Todo processo pertencente a esse usuário conta no mesmo total, incluindo trabalho iniciado por outras unidades, por uma sessão de login ou por uma tarefa do agendador. O limite, portanto, não é um teto por serviço mesmo quando é definido dentro de uma unidade de serviço, e um serviço que bifurca um auxiliar para cada cliente compete com todo o resto que roda com essa conta. Uma tarefa cron ocupada sob o mesmo usuário pode empurrar uma unidade VPN além de uma linha que ela nunca cruzou sozinha.
TasksMax tem outro dono: o controlador pids no cgroup da unidade, que conta tarefas em vez de processos. Threads são tarefas, então um daemon que parece de uma só thread mas tem um conjunto de trabalhadores pode consumir um orçamento que, à primeira vista, parece muito maior que o número de processos. EffectiveTasksMax informa o que a hierarquia de cgroup realmente permite depois que as fatias pai disseram a sua.
Por que a exceção do root muda a resposta
O kernel não aplica RLIMIT_NPROC a um processo que carrega CAP_SYS_RESOURCE ou CAP_SYS_ADMIN, e é por isso que uma unidade executada como root sem abrir mão de privilégios fica efetivamente sem limite por esse ajuste. Essa única exceção explica os conselhos contraditórios que as pessoas trocam: elevar LimitNPROC conserta uma implantação que abriu mão de privilégios e não faz absolutamente nada por uma que permaneceu privilegiada. Verifique User= e se o daemon troca de identidade após a inicialização antes de atribuir mérito ou culpa a esse ajuste. Uma unidade privilegiada que depois passa para uma conta sem privilégios volta a ficar sob o limite no momento da troca, o que também torna a linha do tempo da falha digna de leitura.
A etapa da falha estreita ainda mais. Uma recusa durante a preparação da execução, um erro de bifurcação relatado pelo próprio daemon e uma criação de tarefa rejeitada dentro de um cgroup produzem linhas de journal diferentes, e apenas uma delas é respondida com um orçamento maior.
Leia os valores efetivos, não os do shell
ulimit -u de um shell interativo descreve esse shell. Não descreve o que o gestor de serviços passou ao daemon e não diz nada sobre a contabilização de cgroup. Consulte a unidade em vez disso, usando o nome de instância que existe no host e não um exemplo de um guia.
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 de um contêiner a resposta exige mais duas leituras, porque um valor interno de infinity descreve apenas a visão do convidado. Peça ao dono do host o teto de pids próprio do contêiner e confirme se o gestor de serviços interno consegue delegar esse controlador. Uma unidade aninhada não pode manter um orçamento que seu cgroup pai nunca concedeu.
Dimensione a correção e prove-a sob carga
Escolha um número a partir da carga de trabalho e não do texto do erro. Conte os processos que o daemon inicia por cliente conectado ou por invocação de auxiliar, multiplique pela concorrência que a implantação deve suportar, acrescente uma margem para sessões administrativas e então compare esse número com os dois valores efetivos. Uma substituição local do tipo drop-in mantém a mudança revisável e sobrevive às atualizações de pacotes, enquanto editar um arquivo de unidade entregue esconde a modificação até a próxima atualização substituí-lo. Escreva a aritmética ao lado da substituição, porque a próxima pessoa que a elevar ou baixar precisa das suposições, não do número final.
As mudanças de orçamento são aplicadas na execução, então uma recarga sozinha não redimensiona processos que já estão rodando. Reinicie a unidade pelo seu ciclo de vida normal, aceitando a interrupção da sessão, e leia os contadores novamente na nova instância.
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
O que prova a correção é uma unidade reiniciada cuja contagem de tarefas fica abaixo dos dois tetos enquanto carrega a carga pretendida, além de um journal que não relata mais esgotamento de tarefas ou de bifurcações na mesma etapa. Guarde os números de antes e depois junto com a mudança para que a próxima investigação comece por uma medição e não pelo mesmo conselho. Se a contagem ainda atinge o teto, a aritmética estava errada e não o limite pequeno demais por acaso, e essa diferença vale a pena conhecer antes do próximo reinício.