Go, Contêineres e o Agendador do Linux
Serviços Go em contêineres muitas vezes julgam mal quanta CPU realmente têm, levando o runtime do Go a criar threads demais e sofrer forte estrangulamento sob o agendador CFS do Linux. Os comentaristas comparam quotas de CPU e shares, discutem se vale a pena depender de limites do Kubernetes/Docker ou de ajuste no aplicativo, e apontam ferramentas como `automaxprocs` e runtimes conscientes de cgroup (como em Java e .NET) como correções práticas. De forma mais ampla, a discussão destaca as trocas entre limites rígidos de CPU, overcommit para melhor utilização e latência previsível em grandes clusters multi-tenant.
Go, CFS e limites de CPU em contêineres
- Problema principal: o runtime do Go dimensiona o GOMAXPROCS a partir dos núcleos visíveis, ignorando as cotas de CPU do cgroup, então em contêineres ele frequentemente cria muito mais threads do sistema operacional do que o tempo de CPU realmente disponível.
- Isso leva a forte estrangulamento pelo CFS, piora de throughput e latência, e comportamento de “núcleos desperdiçados” observado לאורך de muitos anos.
- Alguns argumentam que o bug central está no runtime do Go (usando a métrica errada), e não no agendador do Linux.
Docker/Kubernetes: cotas vs. shares e flags
--cpusno Docker se mapeia para quota/período do CFS, não para cpuset, e pode causar estrangulamento rígido mesmo em hosts majoritariamente ociosos.cpu.sharesé um agendamento proporcional/relativo usado apenas sob contenção; quotas (cpu.cfs_quota_us) são limites rígidos de tempo.- No Kubernetes: pedido de CPU (request) → shares; limite de CPU (limit) → quota. Alguns dizem “não defina limits, apenas requests”; outros veem a quota como essencial para isolamento forte.
- Há desacordo sobre quando a quota do CFS realmente estrangula: alguns afirmam “apenas sob contenção”; outros citam documentação e experimentos mostrando que ela é um teto rígido independentemente disso.
Contornos: GOMAXPROCS e sondagem de cgroup
- Padrão popular: definir
GOMAXPROCSa partir de dados do cgroup (por exemplo,cpu.cfs_quota_us / cpu.cfs_period_us) ou usar bibliotecas comoautomaxprocs. - Há vários relatos de ganhos substanciais de latência e throughput depois disso, em comparação tanto com “sem limite” quanto com “com quota, mas sem ajuste”.
- Outros alertam que limitar
GOMAXPROCSà quota pode prejudicar o tratamento de picos e aumentar a latência de cauda; “o paralelismo máximo não deve ser igual ao orçamento de CPU de longo prazo.” - As sugestões incluem: webhooks mutantes para injetar GOMAXPROCS, usar
nproc/sched_getaffinity, ou lxcfs para falsificar/proce oferecer visões conscientes de contêiner.
Debate: deve-se usar limites de CPU?
- Um lado: “Pare de usar limites de CPU; use apenas reservas/requests.” Argumento: limites desperdiçam CPU, causam estrangulamento enquanto o host está ocioso e tornam o comportamento de capacidade imprevisível.
- Lado contrário: limites são necessários para:
- Impedir que vizinhos barulhentos afetem serviços críticos.
- Evitar depender de “CPU de burst grátis” que pode desaparecer conforme os nós enchem.
- Simular condições de pior caso para um planejamento de capacidade realista.
- Vários observam que overcommit e a mistura de workloads sensíveis à latência com workloads em lote são inerentemente complexos; abstrações do K8s podem enganar equipes de operações menos experientes.
Outros runtimes e consciência de contêiner
- Java, .NET e Rust adicionaram lógica consciente de contêiner, usando limites de cgroup para dimensionar pools de threads, threads de GC etc.
- O comportamento do Java evoluiu de usar shares para quotas; há flags para controlar se a quota do contêiner influencia a contagem de CPUs.
- Alguns sugerem que mecanismos semelhantes ou shims carregáveis antecipadamente poderiam ser aplicados ao Go.
Contêineres vs VMs/unikernels para Go
- Pergunta recorrente: como o Go produz binários estáticos, que valor os contêineres adicionam?
- Pontos pró-contêiner: empacotamento padronizado, isolamento de rede/sistema de arquivos/processos, controles de recursos, compatibilidade com orquestração (Kubernetes) e setups mais fáceis de CI/CD e multi-serviços.
- Visão cética: para serviços Go isolados, contêineres podem ser “bloat” redundante; unikernels junto com binários Go podem ser um encaixe melhor, embora as ferramentas e o deployment ainda sejam fracos.
- Consenso: ecossistema e padronização de fluxo de trabalho são razões importantes pelas quais apps Go ainda acabam em contêineres.
Diversos / pontos pouco claros
- Há alguma discussão sobre políticas do gerenciador de CPU do Kubernetes (
staticvsnone) e como elas alteram as máscaras de CPU visíveis; o comportamento difere entre configurações. - Menção a novos agendadores do Linux (EEVDF), mas sem experiência concreta em produção relatada no tópico.
- No geral, os participantes concordam que a interação entre runtimes de linguagem, CFS, cgroups e orquestradores continua sutil e fácil de configurar incorretamente.