...

Controlo de recursos do Systemd: limitar serviços Linux de forma seletiva

Com o systemd resource, controlo de forma específica a CPU, a RAM, as E/S e os PIDs dos serviços Linux, garantindo assim a previsibilidade dos serviços produtivos. Os passos seguintes mostram, de forma prática, como defino limites em «units» e «slices», recorro aos cgroups v2 e resolvo conflitos de recursos com regras claras; assim, cada Instância previsível.

Pontos centrais

A seguinte síntese reúne os pontos mais importantes que explico em pormenor no artigo; serve como uma rápida Guia.

  • cgroups v2 como uma hierarquia unificada com o systemd como gestor central
  • Tipos de unidades Combinar de forma específica o serviço, o âmbito e a fatia
  • CPUQuota e Peso da CPU para uma distribuição justa da CPU
  • MemóriaMax e MemóriaAlta contra o OOM e a limitação de largura de banda
  • Fatias relativamente aos limites dos grupos e às prioridades no funcionamento do servidor

Por que é que o systemd e os cgroups v2 funcionam em conjunto

Organizo todos os processos em cgroups v2 e utilizo o systemd como Central de controlo. A hierarquia unificada em /sys/fs/cgroup agrupa de forma clara controladores como cpu, memory, io e pids. Cada unidade recebe o seu próprio cgroup, o que me permite aplicar limites de forma consistente a toda uma família de serviços. Esta estrutura impede que PIDs individuais contornem os limites, uma vez que o grupo é considerado como um todo. Desde a versão 232 do systemd que este gere a hierarquia de forma exclusiva e define os limites nas interfaces do kernel; só autorizo a delegação de forma deliberada, para que nada escape aos controlos. É assim que mantenho o meu Recursos controlável a qualquer momento.

Compreender os tipos de unidades: Service, Scope, Slice

Encapsulo Daemons clássicos em Serviço-Units e agrupo processos iniciados externamente em Scopes. Para a hierarquia, crio Slices que, como nós internos, definem recursos para grupos inteiros. Os serviços e os âmbitos constituem as folhas que herdam os limites da respetiva «slice». Desta forma, distribuo os orçamentos de CPU, memória e E/S ao longo da árvore, em vez de considerar cada serviço isoladamente. Para principiantes, recomendo dar uma vista de olhos em Gerir serviços de alojamento de forma eficiente, para compreender o papel das unidades no funcionamento do servidor e criar as suas próprias Fatias para planear.

Verificar os pré-requisitos: hierarquia uniforme e controlador

Certifico-me de que o sistema está a funcionar no modo unificado e de que todos os controladores necessários estão ativos. Verifico isso através de /sys/fs/cgroup (um ponto de montagem) e pelo facto de o systemd gerir a estrutura em árvore. Se faltar algum controlador (por exemplo, io), verifico a configuração do kernel e, se necessário, os parâmetros de arranque. Especialmente em ambientes mais antigos, migro deliberadamente da v1 para a v2, para que as diretivas descritas, como IOWeight, MemoryHigh ou AllowedCPUs, tenham efeito. Só quando o accounting e os controladores estiverem a funcionar é que vale a pena o trabalho de ajuste fino nas ponderações e quotas.

Controlo da CPU: utilizar corretamente o CPUWeight e o CPUQuota

Eu controlo a utilização da CPU através de CPUQuota e prioridades relativas através do CPUWeight. Uma quota de 50% limita o serviço a metade do tempo de núcleo, enquanto um peso de 200 dá preferência a este serviço em relação a outros com pesos menores. Desta forma, regulo tarefas com carga contínua sem atrasar os serviços interativos. Na prática, começo com quotas moderadas, observo as latências e aumente o peso dos serviços mais importantes. É assim que distribuo o tempo de computação por ordem de importância, em vez de por ordem aleatória.

Afinidade da CPU, AllowedCPUs e períodos de quota

Quando pretendo atribuir núcleos de forma fixa, utilizo o CPUAffinity ou o controlo mais preciso do cpuset através do AllowedCPUs. Desta forma, por exemplo, separo as cargas de trabalho em lote dos serviços sensíveis à latência em núcleos distintos. Para picos de carga, ajusto o parâmetro `CPUQuotaPeriodSec`: um período mais longo permite picos maiores e de curta duração dentro da mesma quota média, o que melhora a latência P99 em serviços com picos de tráfego.

[Serviço]
# Seleção de núcleos (sched_affinity) vs. cpuset (cgroup v2)
CPUAffinity=0 1 2 3
AllowedCPUs=0-3

# 150% Tempo total com período de 200 ms (maior margem para picos de carga)
CPUQuota=150%
CPUQuotaPeriodSec=200 ms

# Ponderação relativa na mesma fatia
CPUWeight=200

Limites de memória com MemoryMax, MemoryHigh, MemoryLow

Estabeleço um limite rígido com MemóriaMax, para evitar situações de OOM causadas por picos de utilização. Com o MemoryHigh, limito o acesso à memória antes mesmo de atingir o limite máximo, o que aumenta a estabilidade geral. O MemoryLow e o MemoryMin proporcionam zonas de proteção aos serviços, para que o kernel recupere primeiro outros grupos. Este escalonamento evita efeitos em cascata quando vários serviços crescem simultaneamente. Quem estiver à procura de informações sobre o controlador, pode encontrar em O controlador de memória explicado uma introdução clara aos conceitos relacionados Mecanismos.

Definir deliberadamente a estratégia de swap e o comportamento OOM

Defino claramente se e em que medida uma unidade pode utilizar o swap. Com o MemorySwapMax, estabeleço um limite máximo para a utilização combinada da RAM e do swap. Para serviços em que a latência é crítica, limito frequentemente o swap de forma significativa ou desativo-o, para evitar page-outs. Além disso, com o `OOMScoreAdjust`, influencio a probabilidade com que o kernel encerra processos individuais – e, com o `OOMPolicy`, determino como o `systemd` reage a uma situação de OOM na unidade (por exemplo, parar toda a unidade ou deixá-la continuar a funcionar).

[Serviço]
# Máximo de 2G, incluindo swap; o limite rígido de RAM continua a ser o MemoryMax
MemoryMax=1,5G
MemorySwapMax=2G

# Priorização da decisão OOM (valor mais baixo = maior proteção)
OOMScoreAdjust=-500

# Reação quando o OOM Killer entra em ação dentro da unidade
OOMPolicy=stop

Com esta combinação, evito swapes descontrolados, garanto cenários de failover definidos e mantenho as bases de dados ou as caches em memória de forma fiável sob um ambiente controlável e previsível.

Limites de E/S e de processo: IOWeight, larguras de banda e TasksMax

Limito as velocidades de leitura e gravação através de IOReadBandwidthMax e IOWriteBandwidthMax, quando os discos são partilhados. Para a priorização relativa, utilizo o IOWeight, para que as cargas de trabalho centrais tenham prioridade sobre os fluxos em lote. Com o TasksMax, estabeleço um limite máximo claro para processos e threads, o que impede eficazmente as «fork bombs». Estes controlos estabilizam ambientes com vários servidores, nos quais, de outra forma, tarefas individuais dominariam toda a E/S. Especialmente em servidores de compilação, garanto assim resultados reprodutíveis Produções de.

Controlar de forma seletiva as E/S por dispositivo

Em configurações heterogéneas com NVMe e HDDs, defino os limites individualmente para cada dispositivo. Isto evita que os SSDs rápidos sejam prejudicados por um vizinho ruidoso no HDD. A combinação de pesos relativos e limites absolutos por dispositivo abrange a maioria dos casos práticos.

[Serviço]
# Peso relativo para todos os dispositivos
IOWeight=300

# Peso por dispositivo (por exemplo, dar prioridade a NVMe)
IODeviceWeight=/dev/nvme0n1 500
IODeviceWeight=/dev/sda 100

# Limite absoluto por dispositivo (taxa de leitura/gravação)
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 30M

Importante: o IOWeight funciona apenas de forma relativa entre cgroups ativos; as diretivas „Max“ estabelecem limites rígidos. Costumo começar com pesos e só adiciono limites rígidos nos casos em que preciso de controlar com segurança os «vizinhos ruidosos».

Configurar: ficheiros de unidade, drop-ins e set-property

Introduzo os limites diretamente no Unidade-File ou utilizo «drop-ins», que mantêm os ficheiros originais inalterados. Com o comando «systemctl edit NOME.service», crio um fragmento que adiciona as diretivas CPUQuota, CPUWeight, MemoryMax e outras. Para testes rápidos, utilizo o comando «systemctl set-property»; o systemd grava a alteração de forma organizada num «drop-in». Após as adaptações, reinicio os daemons e verifico o estado para confirmar o efeito. Este método de trabalho garante que as atualizações não causem conflitos e fornece a cada Alteração com um historial claro.

Prioridades de «drop-in», predefinições e valores por defeito

Tenho cuidado com a ordem dos ficheiros «drop-in»: o systemd carrega-os por ordem numérica; um ficheiro 90-override.conf, por exemplo, substitui os ficheiros 10-*.conf anteriores. Não alterei as predefinições do fornecedor; substituí-as em /etc para que as atualizações de pacotes não causem problemas. Definições predefinidas a nível do sistema, como DefaultTasksMax, DefaultCPUAccounting ou DefaultMemoryAccounting, defino deliberadamente no ficheiro systemd.conf, para garantir métricas uniformes e limites de segurança, mesmo para novas unidades.

# Verificar os valores ativos
systemctl show NOME.service -p CPUQuota -p CPUWeight -p MemoryMax
systemd-analyze dump | grep -E "Default(TasksMax|CPUAccounting|MemoryAccounting)"

# Abrir/criar ficheiro de substituição persistente
systemctl edit NAME.service

Slices na prática: definir limites adequados para os grupos

Agrupo serviços relacionados em suas próprias Fatias, como web.slice, db.slice e batch.slice. No batch.slice, por exemplo, atribuo 200% de CPU e 4G de RAM, para que as tarefas em segundo plano tenham recursos suficientes sem sobrecarregar os front-ends. Organizo os serviços na sua «slice» de destino através de «Slice=»; os limites aplicam-se então a todos os membros em conjunto. Este agrupamento simplifica enormemente as diretrizes: um novo projeto de equipa adota automaticamente as políticas da sua «slice». Para grupos isolados de clientes ou de aplicações, é também útil consultar Isolamento de cgroups, para que a separação seja feita de forma clara Plano.

Fatias padrão: system.slice, user.slice, machine.slice

Deixo os serviços do sistema no system.slice e defino lá limites globais com cuidado, para que os serviços essenciais não fiquem sem recursos. Os processos dos utilizadores vão para o user.slice, onde limito as sessões interativas sem bloquear totalmente os shells. Reúno as virtualizações e os contentores no «machine.slice» e atribuo orçamentos claros por VM ou contentor. Esta estrutura padrão cria ordem e oferece pontos de referência úteis para as minhas próprias «slices». Quem herda de forma organizada poupa-se a muitas regras individuais e mantém a Transparência elevado.

Delegação para contentores e cargas de trabalho dinâmicas

Quando transfiro subárvores para ambientes de execução em contentores ou ferramentas orientadas pelo utilizador, defino deliberadamente `Delegate=yes`, mas apenas nos pontos em que é necessário exercer controlo. Desta forma, a autoridade permanece com o systemd, enquanto o destinatário da delegação pode criar os seus próprios cgroups dentro da sua subárvore. Em combinação com os escopos, consigo recolher, limitar e libertar processos de curta duração (por exemplo, tarefas de CI) de forma organizada, sem diluir as fatias.

[Serviço]
# Permite o controlo subjacente da subárvore do cgroup (por exemplo, através do runtime do contentor)
Delegate=yes
Slice=machine.slice
MemoryMax=4G
CPUWeight=300

Monitorização e resolução de problemas: Status, cgtop, cgls

Primeiro, verifico com systemctl status NAME.service, quais os limites ativos e como o serviço está a funcionar. Com o systemd-cgtop, vejo o consumo de CPU e de memória por cgroup em tempo real. O systemd-cgls mostra-me a estrutura em árvore e torna visível a herança. Em caso de anomalias, consulto os ficheiros em /sys/fs/cgroup para verificar os valores definidos pelos controladores. Em seguida, ajusto as quotas gradualmente, observo as métricas e documento cada Alteração.

Aprofundar o acompanhamento: Contabilidade, PSI e testes rápidos

Para obter métricas significativas, ativo o CPUAccounting, o MemoryAccounting e o IOAccounting nas unidades ou por predefinição. Além disso, observo os picos de carga através das informações de pressão (PSI) no kernel, para detetar se as restrições (memory.high) estão a aumentar ou se a E/S está permanentemente escassa. Para testes reproduzíveis, inicio cargas de trabalho com o `systemd-run` como âmbito e atribuo limites temporários antes de as transferir para um drop-in persistente.

# Âmbito temporário com ponderação de E/S e CPU
systemd-run --scope -p IOWeight=400 -p CPUWeight=300 --unit test-batch -- dd if=/dev/zero of=/tmp/out bs=1M count=1024

Ativar a contabilidade # numa unidade existente
systemctl set-property NAME.service CPUAccounting=yes MemoryAccounting=yes IOAccounting=yes

Resolução de problemas e obstáculos típicos

  • Harsh Caps vs. Burst: Uma quota de CPU demasiado restrita, sem um período ajustado, provoca falhas de desempenho. Aumento o valor de CPUQuotaPeriodSec ou reduzo a quota apenas moderadamente e recorro mais ao CPUWeight.
  • O limitador de memória intervém demasiado cedo: será que o valor de MemoryHigh foi definido demasiado baixo? Vou aumentá-lo ou definir o MemoryLow, para que os caminhos críticos não sejam recuperados de forma demasiado agressiva.
  • Dispositivos de E/S endereçados incorretamente: as diretivas IO* esperam dispositivos de bloco. Verifico o caminho do dispositivo com o comando lsblk e defino regras por dispositivo, e não por ponto de montagem.
  • Os threads estão a atingir o limite: o valor do TasksMax demasiado baixo está a abrandar os conjuntos de workers. Faço o dimensionamento com base no número máximo de threads, acrescido de uma margem de segurança, e monitorizo a coluna «Tasks» com o systemd-cgtop.
  • Atualizações sem efeito: Após as alterações, executo o comando «systemctl daemon-reload» e verifico com o comando «systemctl show» se as propriedades estão efetivamente definidas.

Boas práticas em matéria de prioridades e limites

Agrupo os serviços por função, atribuo valores de CPUWeight e IOWeight de acordo com a importância e defino limites rígidos de memória através de MemóriaMax. As bases de dados críticas recebem maior prioridade e quotas menos restritivas, enquanto os relatórios e as tarefas em lote são sujeitos a restrições mais rigorosas. Defino o TasksMax quando as aplicações utilizam muitos trabalhadores ou quando existe o risco de explosão de threads. Cada ajuste é guardado com uma versão no repositório, para que eu possa acompanhá-lo e revertê-lo, se necessário. No ambiente de teste, calibro os valores de acordo com os perfis de carga e, em seguida, aplico-os de forma conservadora no Produção.

Quadro resumido das diretivas mais importantes

Esta tabela compacta resume as configurações típicas e ajuda-me a encontrar as opções adequadas Valores para escolher.

Objetivo diretiva Exemplo de valor Efeito
Quota da CPU Peso da CPU 200 Dá prioridade em relação às unidades com menor ponderação; distribui CPU justo.
Quota da CPU CPUQuota 50% Limita o tempo de trabalho efetivo; ideal para tarefas de longa duração Empregos.
Armazenamento em disco rígido MemóriaMax 1G Limite absoluto; evita o OOM devido a valores atípicos no mesmo Slice.
Memória flexível MemóriaAlta 800 m Limita a velocidade antes do Max; reduz a pressão sobre o Sistema.
Prioridade de E/S IOWeight 500 Dá preferência a serviços centralizados em recursos partilhados Discos.
PIDs/Threads TarefasMax 512 Limita processos/threads; protege contra Forks-Avalanches.

Casos de utilização na área do alojamento e da gestão de servidores

Criei, para os meus clientes, os meus próprios Fatias Defino e atribuo orçamentos de CPU e RAM por cliente. Em configurações de microsserviços, os serviços de API e autenticação recebem pesos mais elevados, enquanto o reporting funciona de forma assíncrona. Para os executores de CI/CD, crio uma fatia de lote (batch-slice), para que as compilações nunca prejudiquem os front-ends. Em ambientes de contentores e máquinas virtuais, encapsulo as cargas de trabalho no «machine.slice» e mantenho os orçamentos por cliente bem definidos. Esta divisão reduz os efeitos de «vizinho ruidoso» e garante resultados reproduzíveis Latências em períodos de pico.

Resumo

Eu controlo os serviços do Linux com o systemd e os cgroups v2 Unidade em vez de me concentrar em processos individuais. CPUQuota, CPUWeight, MemoryMax, MemoryHigh, IOWeight e TasksMax constituem o meu conjunto básico para uma distribuição justa e limites máximos claros. As «slices» criam ordem, agrupam diretrizes e facilitam a operação, bem como a integração de novos serviços. A monitorização com o `systemctl status`, o `cgtop` e o `cgls` indica atempadamente onde preciso de fazer ajustes. Desta forma, o desempenho e a disponibilidade continuam a ser previsíveis e consigo manter os conflitos de recursos sob Controlo.

Artigos actuais

Servidor Linux com indicadores visualizados de «Pressure Stall Information» no centro de dados
Administração

Linux PSI para uma análise e monitorização precisas do desempenho

O Linux PSI (Pressure Stall Information) mostra em que medida a CPU, a memória e as E/S estão a limitar o desempenho do teu sistema. Descobre como ativar o PSI e como utilizá-lo para uma monitorização precisa do desempenho.