Kubernetes: O Kubelet / Kubernetes deve funcionar com a troca ativada

Criado em 6 out. 2017  ·  94Comentários  ·  Fonte: kubernetes/kubernetes

É um RELATÓRIO DE BUGS ou PEDIDO DE RECURSO? :

Descomente apenas um, deixe em sua própria linha:

/ tipo bug
/ tipo recurso

O que aconteceu :

O Kubelet / Kubernetes 1.8 não funciona com a troca ativada em máquinas Linux.

Encontrei este problema original https://github.com/kubernetes/kubernetes/issues/31676
Este PR https://github.com/kubernetes/kubernetes/pull/31996
e a última alteração que o habilitou por padrão https://github.com/kubernetes/kubernetes/commit/71e8c8eba43a0fade6e4edfc739b331ba3cc658a

Se o Kubernetes não souber como lidar com o despejo de memória quando a troca estiver habilitada, ele deve encontrar uma maneira de fazer isso, mas sem pedir para se livrar da troca.

Por favor, siga kernel.org Capítulo 11 Gerenciamento de Swap , por exemplo

O leitor casual pode pensar que, com uma quantidade suficiente de memória, a troca é desnecessária, mas isso nos leva ao segundo motivo. Um número significativo de páginas referenciadas por um processo no início de sua vida só pode ser usado para inicialização e nunca mais usado novamente. É melhor trocar essas páginas e criar mais buffers de disco do que deixá-los residentes e sem uso.

No caso de rodar muitos aplicativos node / java, sempre vi muitas páginas trocadas, só porque não são mais usadas.

O que você esperava que acontecesse :

O Kubelet / Kubernetes deve funcionar com a troca ativada. Acredito que, em vez de desabilitar a troca e não dar aos usuários nenhuma escolha, o Kubernetes deve oferecer suporte a mais casos de uso e várias cargas de trabalho, alguns deles podem ser aplicativos que podem depender de caches.

Não tenho certeza de como o kubernetes decidiu o que matar com o despejo de memória, mas considerando que o Linux tem essa capacidade, talvez deva se alinhar com a forma como o Linux faz isso? https://www.kernel.org/doc/gorman/html/understand/understand016.html

Eu sugeriria reverter a alteração por falha quando a troca está habilitada e revisitar como a remoção de memória funciona atualmente no kubernetes. A troca pode ser importante para algumas cargas de trabalho.

Como reproduzi-lo (o mínimo e precisamente possível) :

Execute kubernetes / kublet com configurações padrão na caixa do Linux

Mais alguma coisa que precisamos saber? :

Meio Ambiente :

  • Versão do Kubernetes (use kubectl version ):
  • Provedor de nuvem ou configuração de hardware **:
  • SO (por exemplo, de / etc / os-release):
  • Kernel (por exemplo, uname -a ):
  • Ferramentas de instalação:
  • Outros:

nó / sig
cc @mtaufen @vishh @derekwaynecarr @dims

kinfeature sinode

Comentários muito úteis

Não é compatível com swap como padrão? Fiquei surpreso ao ouvir isso - pensei que o Kubernetes estava pronto para o horário nobre. A troca é um desses recursos.

Isso não é realmente opcional na maioria dos casos de uso abertos - é como o ecossistema Unix foi projetado para ser executado, com o VMM trocando as páginas inativas.

Se a escolha for sem troca ou sem limite de memória, optarei por manter a troca a qualquer dia e apenas ativar mais hosts quando começar a paginação, e ainda assim sairei economizando dinheiro.

Alguém pode esclarecer - o problema com o despejo de memória é apenas um problema se você estiver usando limites de memória na definição do pod, mas por outro lado, está tudo bem?

Seria bom trabalhar em um mundo onde tenho controle sobre a maneira como a memória de um aplicativo funciona, então não preciso me preocupar com o uso insuficiente de memória, mas a maioria dos aplicativos tem bastante espaço de memória inativa.

Sinceramente, acho que essa mudança recente para executar servidores sem troca é impulsionada pelos provedores de PaaS que tentam coagir as pessoas a instâncias de memória maiores - enquanto desconsidera ~ 40 anos de design de gerenciamento de memória. A realidade é que o kernel é realmente bom em saber quais páginas de memória estão ativas ou não - deixe-o fazer o seu trabalho.

Todos 94 comentários

O suporte para troca não é trivial. Os frutos garantidos nunca devem exigir troca. Os pods burstable devem ter suas solicitações atendidas sem a necessidade de troca. Os pods BestEffort não têm garantia. O kubelet agora não tem inteligência para fornecer a quantidade certa de comportamento previsível aqui entre os pods.

Discutimos esse tópico pessoalmente no gerenciamento de recursos no início deste ano. Não estamos muito interessados ​​em lidar com isso no curto prazo em relação aos ganhos que ela pode obter. Preferiríamos melhorar a confiabilidade em torno da detecção de pressão e otimizar os problemas de latência antes de tentar otimizar para a troca, mas se esta for uma prioridade mais alta para você, adoraríamos sua ajuda.

/ tipo recurso

@derekwaynecarr obrigado pela explicação! Era difícil obter qualquer informação / documentação por que a troca deveria ser desabilitada para kubernetes. Este foi o principal motivo pelo qual abri este tópico. Neste ponto, não tenho alta prioridade para esse assunto, apenas queria ter certeza de que temos um lugar onde ele possa ser discutido.

Há mais contexto na discussão aqui: https://github.com/kubernetes/kubernetes/issues/7294 - ter swap disponível tem interações muito estranhas e ruins com limites de memória. Por exemplo, um contêiner que atinge seu limite de memória _então_ começa a transbordar para a troca (isso parece ter sido corrigido desde f4edaf2b8c32463d6485e2c12b7fd776aef948bc - eles não terão permissão para usar qualquer troca, esteja lá ou não).

Este é um caso de uso crítico para nós também. Temos um cron job que ocasionalmente é executado em alto uso de memória (> 30 GB) e não queremos alocar permanentemente 40 + GB nodes. Além disso, como executamos em três zonas (GKE), isso alocará 3 dessas máquinas (1 em cada zona). E essa configuração deve ser repetida em mais de 3 instâncias de produção e mais de 10 instâncias de teste, tornando muito caro o uso de K8s. Somos forçados a ter mais de 25 nós de 48 GB, o que acarreta um custo enorme !.
Ative a troca !.

Uma solução alternativa para quem realmente deseja trocar. Se você

  • inicie o kubelet com --fail-swap-on=false
  • adicione swap aos seus nós
  • os contêineres que não especificam um requisito de memória serão, então, por padrão, capazes de usar toda a memória da máquina, incluindo swap.

Isso é o que estamos fazendo. Ou, pelo menos, tenho quase certeza que sim, na verdade não implementei pessoalmente, mas é o que deduzo.

Isso só pode ser realmente uma estratégia viável se nenhum de seus contêineres especificar um requisito de memória explícito ...

Executamos no GKE e não sei como definir essas opções.

Eu estaria aberto a considerar a adoção de zswap se alguém pudesse avaliar as implicações para despejos de memória no kubelet.

Estou executando o Kubernetes em meu laptop Ubuntu local e, a cada reinicialização, preciso desligar a troca. Além disso, tenho que me preocupar em não chegar perto do limite de memória, pois a troca está desativada.

Existe alguma maneira de eu não ter que desligar a troca a cada reinicialização, como alguma alteração no arquivo de configuração na instalação existente?

Eu não preciso trocar em nós rodando em cluster.

São apenas outros aplicativos no meu laptop, além do cluster de desenvolvimento local do Kubernetes, que precisam de troca para serem ativados.

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.2", GitCommit:"5fa2db2bd46ac79e5e00a4e6ed24191080aa463b", GitTreeState:"clean", BuildDate:"2018-01-18T10:09:24Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.2", GitCommit:"5fa2db2bd46ac79e5e00a4e6ed24191080aa463b", GitTreeState:"clean", BuildDate:"2018-01-18T09:42:01Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"linux/amd64"}

No momento, a bandeira não está funcionando.

# systemctl restart kubelet --fail-swap-on=false
systemctl: unrecognized option '--fail-swap-on=false'

Defina a seguinte sinalização Kubelet: --fail-swap-on=false

Na terça - feira, 30 de janeiro de 2018 às 13h59, icewheel

Estou executando o Kubernetes em meu laptop Ubuntu local e, a cada reinicialização,
tem que desligar a troca. Também tenho que me preocupar em não chegar perto da memória
limite se trocar se estiver desligado.

Existe alguma maneira com cada reinicialização eu não tenho que desligar o swap como alguns
alteração do arquivo de configuração na instalação existente?

Eu não preciso trocar em nós rodando em cluster.

São apenas outros aplicativos no meu laptop, além do Kubernetes Local Dev
cluster que precisa que a troca seja ativada.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/kubernetes/kubernetes/issues/53533#issuecomment-361748518 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AA3JwQdj2skL2dSqEVyV46iCllzT-sOVks5tP5DSgaJpZM4PwnD5
.

-
Michael Taufen
Google SWE

obrigado @mtaufen

Para sistemas que inicializam o cluster para você (como o terraform), pode ser necessário modificar o arquivo de serviço

Isso funcionou para mim

sudo sed -i '/kubelet-wrapper/a \ --fail-swap-on=false \\\' /etc/systemd/system/kubelet.service

Não é compatível com swap como padrão? Fiquei surpreso ao ouvir isso - pensei que o Kubernetes estava pronto para o horário nobre. A troca é um desses recursos.

Isso não é realmente opcional na maioria dos casos de uso abertos - é como o ecossistema Unix foi projetado para ser executado, com o VMM trocando as páginas inativas.

Se a escolha for sem troca ou sem limite de memória, optarei por manter a troca a qualquer dia e apenas ativar mais hosts quando começar a paginação, e ainda assim sairei economizando dinheiro.

Alguém pode esclarecer - o problema com o despejo de memória é apenas um problema se você estiver usando limites de memória na definição do pod, mas por outro lado, está tudo bem?

Seria bom trabalhar em um mundo onde tenho controle sobre a maneira como a memória de um aplicativo funciona, então não preciso me preocupar com o uso insuficiente de memória, mas a maioria dos aplicativos tem bastante espaço de memória inativa.

Sinceramente, acho que essa mudança recente para executar servidores sem troca é impulsionada pelos provedores de PaaS que tentam coagir as pessoas a instâncias de memória maiores - enquanto desconsidera ~ 40 anos de design de gerenciamento de memória. A realidade é que o kernel é realmente bom em saber quais páginas de memória estão ativas ou não - deixe-o fazer o seu trabalho.

Isso também tem um efeito que, se a memória se esgotar no nó, ele ficará potencialmente completamente bloqueado - exigindo a reinicialização do nó, em vez de apenas desacelerar e se recuperar um pouco mais tarde.

Os problemas ficam obsoletos após 90 dias de inatividade.
Marque o problema como novo com /remove-lifecycle stale .
Problemas obsoletos apodrecem após 30 dias adicionais de inatividade e, eventualmente, fecham.

Se for seguro encerrar este problema agora, faça-o com /close .

Envie feedback para sig-testing, kubernetes / test-infra e / ou fejta .
/ lifecycle stale

Eu tenho um alto número de leituras de disco em meus nós de cluster ( K8s Version - v1.11.2 ). Pode ser por causa da desativação da memória swap?

https://stackoverflow.com/questions/51988566/high-number-of-disk-reads-in-kubernetes-nodes

@srevenant No mundo do cluster, a RAM do outro nó é a nova troca. Dito isso, eu executo duas instâncias K8s de um nó em que a troca faz sentido. Mas este não é o caso de uso típico do K8s.

@srevenant Concordo totalmente com você, o SWAP é usado no Unix e no Linux por padrão desde que eles nasceram, acho que não vi um aplicativo durante 15 anos trabalhando no Linux que pedisse para desligar o SWAP.
O problema SWAP está sempre ativado por padrão quando instalamos qualquer distro Linux, então devo desativá-lo antes de instalar o K8s e isso foi uma surpresa.
O Linux Kernel sabe bem como gerenciar o SWAP para aumentar o desempenho dos servidores, especialmente temporariamente quando o servidor está prestes a atingir o limite de RAM.
Isso significa que devo desligar o SWAP para que o K8s funcione bem?

Tenho interesse em fazer este trabalho e tenho as habilidades e várias máquinas para testar. Se eu quisesse contribuir, qual seria o melhor lugar para começar?

@superdave , reúna um KEP em kubernetes / comunidade descrevendo como você gostaria que a troca fosse suportada e apresente-o ao sig-node. adoraríamos ter sua ajuda.

Eu defendo a ativação da troca nos pods do Kubernete corretamente. realmente não faz sentido não ter swap, já que quase todos os contêineres são instâncias personalizadas do Linux e, portanto, suportam swap por padrão.
É compreensível que o recurso seja complexo de implementar, mas desde quando isso nos impediu de seguir em frente?

Devo concordar que os problemas de troca devem ser resolvidos no Kubernetes, já que desabilitar a troca causa falha no nó ao ficar sem memória no nó. Por exemplo, se você tiver 3 nós de trabalho (20 GB de RAM cada) e um nó ficar inativo porque o limite de memória RAM foi atingido, 2 outros nós de trabalho também cairão depois de transferir todos os pods para eles nesse período.

Você pode evitar isso configurando as solicitações de memória de acordo com o real
necessidade do aplicativo.

Se um terço da memória do seu aplicativo estiver em 2 ordens de magnitude
armazenamento mais lento, ele será capaz de fazer algum trabalho útil?

Na quarta-feira, 26 de setembro de 2018 às 6h51, vasicvuk [email protected] escreveu:

Devo concordar que os problemas de troca devem ser resolvidos no Kubernetes desde
desativar a troca causa falha do nó ao ficar sem memória no nó. Para
exemplo, se você tiver 3 nós de trabalho (20 GB de RAM cada) e um nó for
para baixo porque o limite de RAM é atingido 2 outros nós de trabalho também irão
para baixo depois de transferir todos os pods para eles nesse tempo.

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/kubernetes/kubernetes/issues/53533#issuecomment-424604731 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAICBqZApBscFl5aNA4IcYvlxcvPA88Tks5ueyPlgaJpZM4PwnD5
.

@matthiasr Você pode fazer isso quando tiver 10-50 serviços. Mas quando você tem Cluster executando mais de 200 serviços e metade deles são implantados usando gráficos oficiais do Helm sem qualquer solicitação de memória neles, suas mãos estão zeradas.

Mas então, as solicitações de memória perdida não são o problema a ser resolvido?

@matthiasr em muitos casos de memória, uma vez mapeado para o processo usado apenas uma vez ou nunca realmente usado. Esses são casos válidos e não são vazamentos de memória. Quando você faz a troca, essas páginas são eventualmente trocadas e podem nunca ser trocadas novamente, mas você libera a memória RAM rápida para melhor uso.

Desativar a troca também não é uma boa maneira de garantir a capacidade de resposta. A menos que você fixe arquivos na memória (um recurso que os K8s devem ter para executáveis, pelo menos), o kernel ainda irá trocar todas e quaisquer páginas de arquivo em resposta à pressão da memória, ou mesmo simplesmente falta de uso.

Ter o swap habilitado não muda significativamente o comportamento do kernel. Tudo o que ele faz é fornecer um espaço para trocar páginas anônimas ou páginas modificadas carregadas de arquivos mapeados pelo COW.

Você não pode desligar a troca totalmente, então o K8s precisa sobreviver à sua existência, esteja ou não o caso especial de troca de memória anônima habilitado.

Isso torna isso um bug: você está falhando em oferecer suporte a um recurso do kernel que não pode realmente ser desativado.

@Baughn

o kernel ainda irá trocar toda e qualquer página de arquivo em resposta à pressão da memória, ou mesmo simplesmente à falta de uso. Ter o swap habilitado não muda significativamente o comportamento do kernel.

Você não pode desligar totalmente a troca,

Você pode fornecer alguma referência para que eu possa me educar?

A menos que você fixe arquivos na memória (um recurso que os K8s devem ter para executáveis, pelo menos),

Qual é a capacidade que você deseja que o k8s use? Se um binário for estático, apenas copiá-lo para um tmpfs no pod deve ajudar na latência de paginação.

@adityakali alguma

Você pode fornecer alguma referência para que eu possa me educar?

Como todos os sistemas operacionais modernos de memória virtual, o Linux exige executáveis ​​de páginas do disco para a memória. Sob pressão de memória, o kernel troca o código executável real do seu programa de / para o disco, assim como qualquer outra página de memória (a "troca" é simplesmente um descarte porque somente leitura, mas o mecanismo é o mesmo), e eles irão ser buscado novamente, se necessário. O mesmo vale para coisas como constantes de string, que são tipicamente mapeadas como somente leitura de outras seções do arquivo executável. Outros arquivos mmapped (comuns para cargas de trabalho do tipo banco de dados) também são trocados + out para seus arquivos de apoio relevantes (exigindo uma gravação real se tiverem sido modificados) em resposta à pressão da memória. A _apenas_ troca que você desabilita ao "desabilitar a troca" é a "memória anônima" - memória que _não_ está associada a um arquivo (os melhores exemplos são as estruturas de dados "pilha" e "pilha").

Há muitos detalhes que estou pulando na descrição acima, é claro. Em particular, os executáveis ​​podem "travar" porções de seu espaço de memória na memória RAM usando a família mlock de syscalls, fazer coisas inteligentes via madvise() , fica complicado quando as mesmas páginas são compartilhadas por vários processos (por exemplo, libc.so), etc. Infelizmente, não tenho um ponteiro mais útil para ler mais do que essas páginas de manual, ou coisas gerais como livros-texto ou fonte do kernel do Linux / docs / mailing-list.

Então, um efeito prático do acima é que conforme seu processo se aproxima do limite de memória, o kernel será forçado a remover _code_ porções e constantes da memória ram. Na próxima vez que esse bit de código ou valor constante for necessário, o programa fará uma pausa, esperando para recuperá-lo do disco (e expulsar outra coisa). Portanto, mesmo com "swap desabilitado", você ainda obtém a mesma degradação quando seu conjunto de trabalho excede a memória disponível.

Antes que as pessoas leiam o acima e comecem a chamar para mlock tudo na memória ou copiar tudo em um ramdrive como parte da caça às bruxas anti-swap, eu gostaria de repetir que o verdadeiro recurso de interesse aqui é o tamanho do conjunto de trabalho - não o tamanho total . Um programa que funciona linearmente por meio de gigabytes de dados na memória RAM pode funcionar apenas em uma janela estreita desses dados por vez. Este programa hipotético funcionaria bem com uma grande quantidade de swap e um pequeno limite de memória RAM - e seria terrivelmente ineficiente travar tudo na memória RAM real. Como você aprendeu com a explicação acima, isso é exatamente o mesmo que um programa que tem uma grande quantidade de _code_, mas executa apenas uma pequena quantidade em um determinado momento.

Meu último exemplo pessoal do mundo real de algo assim é vincular os executáveis ​​do kubernetes. Atualmente (ironicamente) não consigo compilar kubernetes em meu cluster de kubernetes porque o estágio do link go requer vários gigabytes de memória virtual (anônima), embora o conjunto de trabalho seja muito menor.

Para realmente criticar o ponto "trata-se do conjunto de trabalho, não da memória virtual", considere um programa que faz muita E / S de arquivo regular e nada a ver com mmap. Se você tiver memória RAM suficiente, o kernel armazenará em cache estruturas de diretório usadas repetidamente e dados de arquivo na memória ram e evitará ir para o disco, e permitirá que as gravações explodam na ram temporariamente para otimizar a gravação do disco. Mesmo um programa "ingênuo" como este irá degradar da velocidade da memória RAM para a velocidade do disco, dependendo do tamanho do conjunto de trabalho versus memória RAM disponível. Quando você fixa algo na ram desnecessariamente (por exemplo: usando mlock ou desabilitando swap), você evita que o kernel use aquela página de ram física para algo realmente útil e (se você não tiver ram suficiente para o conjunto de trabalho) você acabou de moveu a E / S do disco para um lugar ainda mais caro.

@superdave : Eu também estou interessado em melhorar o status quo aqui. Inclua-me se quiser que outro par de olhos analise um documento ou mãos no teclado.

Meu último exemplo pessoal do mundo real de algo assim é vincular os executáveis ​​do kubernetes. Atualmente (ironicamente) não consigo compilar kubernetes em meu cluster de kubernetes porque o estágio do link go requer vários gigabytes de memória virtual (anônima), embora o conjunto de trabalho seja muito menor.

@superdave : Eu também estou interessado em melhorar o status quo aqui. Inclua-me se quiser que outro par de olhos analise um documento ou mãos no teclado.

Bom resumo do problema em questão! O thrashing de swap é o problema chave aqui, de fato; é algo que espero abordar racionalmente. Parece-me, embora eu realmente não tenha tido tempo para pensar sobre isso o suficiente, que algum tipo de métrica de atividade de troca (entradas / saídas de página ao longo de um determinado período de tempo, talvez com uma regra de percentil para evitar ataques excessivamente ansiosos se um pode ser uma boa maneira de avaliar as coisas para despejo quando a troca está habilitada. Existem várias métricas, e suspeito que gostaríamos de oferecer muitos botões para mexer, bem como avaliar cuidadosamente os casos de uso prováveis. Também suspeito que ser capaz de instrumentar pods para interações de memória virtual deve ajudar as pessoas a sintonizar melhor; Não estou familiarizado o suficiente com o que já foi oferecido para dizer o que está lá agora, mas suspeito que vou descobrir.

Também não estou familiarizado o suficiente com os controles que temos para saber quão bem podemos controlar o comportamento de troca em pods / contêineres individuais; seria útil ser capaz de "renice" coisas para retenção ou troca, mas os desenvolvedores estão obviamente sempre livres para tentar mlock () quando eles absolutamente precisam garantir que as coisas serão residentes de qualquer maneira.

Em qualquer caso, sim, eu absolutamente quero avançar nisso. Tenho estado atolado no trabalho ultimamente (lidando com alguns problemas de OOM com nossos próprios microsserviços em k8s que teriam se beneficiado de serem capazes de trocar sob carga porque 99% das vezes eles não precisam de GB de RAM, a menos que alguém faça um disco inadvertidamente grande pedido), mas sinta-se à vontade para manter-se informado sobre isso. Eu nunca participei do processo KEP antes, então vou ser muito verde nisso, mas hoje em dia eu trabalho muito melhor em uma base de interrupção do que em uma votação. :-)

Gostaria de salientar que o zram funciona pegando carona nas trocas. Se não houver trocas no k8, não haverá compactação de memória, que é algo que a maioria dos sistemas operacionais não Linux ativou por padrão (Windows, MacOS).

Temos uma instância do Ubuntu no k8 que executa um grande trabalho em lote todas as noites, o que consome muita memória. Como a carga de trabalho não é predeterminada, somos forçados a (cara) alocar 16 GB para o nó, independentemente de seu consumo real de memória, para evitar OOM. Com a compactação de memória em nosso servidor de desenvolvimento local, o pico do trabalho é de apenas 3 GB. Caso contrário, durante o dia, leva apenas 1 GB de memória. Banir as trocas e, portanto, a compactação da memória é uma jogada bastante boba.

Acho que a principal preocupação aqui é provavelmente o isolamento. Uma máquina típica pode hospedar uma tonelada de pods e, se a memória ficar apertada, eles podem começar a trocar e destruir completamente o desempenho um do outro. Se não houver troca, o isolamento é muito mais fácil.

Acho que a principal preocupação aqui é provavelmente o isolamento. Uma máquina típica pode hospedar uma tonelada de pods e, se a memória ficar apertada, eles podem começar a trocar e destruir completamente o desempenho um do outro. Se não houver troca, o isolamento é muito mais fácil.

Mas, como explicado anteriormente, desabilitar a troca não nos compra nada aqui. Na verdade, uma vez que aumenta a pressão geral da memória, pode forçar o kernel a descartar partes do conjunto de trabalho quando, de outra forma, poderia ter trocado dados não utilizados - então, isso torna a situação pior .

Habilitar a troca, por si só, deve realmente melhorar o isolamento.

Mas ele compra muito para você, se você executa as coisas da maneira que deveria (e da maneira como o Google executa as coisas no Borg): todos os contêineres devem especificar o limite de memória superior. O Borg aproveita a infra-estrutura do Google e aprende os limites, se quiser (com base no uso de recursos anteriores e no comportamento OOM), mas existem limites.

Na verdade, estou meio perplexo que o pessoal do K8S permitiu que o limite de memória fosse opcional. Ao contrário da CPU, a pressão da memória tem um efeito muito não linear no desempenho do sistema, como qualquer pessoa que tenha visto um sistema travar completamente devido à troca pode atestar. Ele deve realmente exigi-lo por padrão e dar um aviso se você decidir desativá-lo.

Mas ele compra muito para você, se você executa as coisas da maneira que deveria (e da maneira como o Google executa as coisas no Borg): todos os contêineres devem especificar o limite de memória superior. O Borg aproveita a infra-estrutura do Google e aprende os limites, se quiser (com base no uso de recursos anteriores e no comportamento OOM), mas existem limites.

Na verdade, estou meio perplexo que o pessoal do K8S permitiu que o limite de memória fosse opcional. Ao contrário da CPU, a pressão da memória tem um efeito muito não linear no desempenho do sistema, como qualquer pessoa que tenha visto um sistema travar completamente devido à troca pode atestar. Ele deve realmente exigi-lo por padrão e dar um aviso se você decidir desativá-lo.

Acho que o problema que isso não consegue resolver é que o limite superior é variável e nem sempre conhecido para alguns processos. O problema com o qual estou lidando se concentra especificamente no uso de k8s para gerenciar nós de renderização de modelo 3D. Dependendo dos ativos para o modelo e a cena sendo renderizada, a quantidade de memória RAM necessária pode variar um pouco e, embora a maioria das renderizações seja pequena, o fato de que _algumas_ podem ser enormes significa que nossas solicitações e limites precisam reservar muito mais memória do que realmente precisamos de 90% do tempo para evitar OOM, em vez de o pod ocasionalmente exceder o limite configurado e ser capaz de transbordar para o espaço de troca.

Sim, e nesse caso você definiria seu limite superior para "Nenhum" ou algo parecido. Meu ponto é, não deve ser o padrão. Definir como nada anula completamente qualquer tipo de planejamento inteligente de carga de trabalho, já que o mestre simplesmente não sabe o tamanho do "item" (trabalho) que está prestes a ser colocado em uma "mochila" (kubelet).

O problema aqui não é que seu trabalho será transferido para a troca, mas todos os outros trabalhos em execução nesse nó também. E alguns (a maioria?) Deles não vão gostar, de jeito nenhum.

Os programas no Borg são escritos para serem preemptivos (elimináveis, para aqueles que não estão familiarizados com o jargão) a qualquer momento, sem efeito na integridade dos dados. Na verdade, isso é algo que não vejo muito fora do Google, e reconhecer a potencial mortalidade súbita do seu programa leva a escrever softwares muito mais confiáveis. Mas estou divagando.

Os sistemas construídos com esses programas prefeririam muito mais que esses programas morressem e reencarnassem em outro lugar na célula (cluster Borg), em vez de continuar a sofrer em um nó com excesso de assinaturas. A latência de cauda pode ser realmente problemática de outra forma, especialmente quando o número de tarefas em um trabalho é grande.

Não me entenda mal, não estou dizendo que essa seja a única maneira "correta" de fazer as coisas. Estou apenas tentando elucidar o possível fundamento lógico do design.

Exoneração de responsabilidade: sou um ex-Googler que usava o Borg para executar vários serviços muito grandes, então conheço muito bem, e esse conhecimento se traduz em grande parte no Kubernetes. Atualmente não estou no Google e tudo o que escrevo aqui são minhas próprias ideias.

@ 1e100 : Você está combinando o tamanho da "VM total" com o tamanho do "conjunto de trabalho". A carga de trabalho precisa ser agendada com base no tamanho do _conjunto_ de trabalho_, e o programa será degradado quando o tamanho do _conjunto de trabalho_ for excedido (assumindo que haja troca total suficiente disponível). O raciocínio que você declarou acima também se baseia na suposição incorreta de que a troca (e outras compensações de degradação de E / S de <-> ram) não acontecerá apenas porque a troca está desabilitada.

(Eu também sou um ex-Google-SRE e concordo que esses mitos comuns do Google são quase certamente o que levou à decisão de que estava tudo bem (ou até mesmo desejável) desabilitar a troca no k8s também. Eu assisti várias equipes do Google irem por meio do aprendizado de que desabilitar a troca não desabilita a troca, e o desperdício de memória agregado que resulta de apenas descrever um limite "rígido" (oom-kill) para a memória - essas são precisamente algumas das coisas que eu gostaria de melhorar com o k8s. há uma série de botões ajustáveis ​​cgroup / swap disponíveis agora que não tínhamos quando o modelo de recursos borg foi inicialmente projetado, e estou convencido de que podemos alcançar os resultados desejados sem essa abordagem de jogar o bebê para fora com a água do banho . Também observarei que a compensação do Google é _ muitas vezes_ ser menos eficiente em média, a fim de alcançar um tempo de pior caso melhor / conhecido (ou seja: comportamento em tempo real) e que esta freqüentemente _não_ é a escolha desejada fora do Google - menor / menos hosts, SLOs mais relaxados, orçamentos mais baixos, definições mais precárias trabalhos em lote ed, mais uso de linguagens não-compiladas e ineficientes, etc.)

A troca de memória anônima não acontecerá. Qualquer memória mapeada (incluindo código de programa e dados) pode e ainda irá trocar se houver pressão de memória, é por isso que sugeri que os limites de RAM deveriam ser exigidos por padrão: para tornar menos provável que haja pressão de memória em primeiro lugar. Para cargas de trabalho que precisam de garantias ainda mais rígidas, há também mlockall() e um swappiness baixo de

Como um antigo Google SRE, você não pode argumentar que não especificar o limite superior de RAM ou permitir que as tarefas troquem o que quiserem por capricho é uma coisa boa, a menos que você apenas queira ser contrário. A troca de arquivos mapeados de memória é ruim o suficiente, mas a introdução de ainda mais falhas de desempenho em potencial na mistura não é uma coisa boa.

Esses são ambientes compartilhados por design, e você deseja eliminar as maneiras de os programas tornarem o desempenho uns dos outros imprevisível, e não adicionar novos. Como dizem os SREs do Google, "esperança não é uma estratégia". Thrashing de swap é a maneira mais fácil que conheço de fazer uma máquina Linux travar completa e irrecuperavelmente, mesmo se você estiver trocando para SSD. Isso não pode ser bom, mesmo se você estiver executando apenas uma carga de trabalho na máquina, muito menos algumas dezenas. As falhas correlacionadas podem ser especialmente dolorosas em clusters menores com poucas tarefas / pods.

Pode-se ignorar o cheque swap até hoje, se quiser, mas com o entendimento explícito de que todas as apostas nesse caso estão canceladas.

Sim, concordo totalmente que precisamos ter um "tamanho" que usamos para agendamento e para evitar overcommit (não intencional). E também queremos evitar o thrash global da VM, porque o Linux não consegue se recuperar disso. O que nós _fazemos_ é que o kernel seja _capaz_ de trocar a memória anônima e reutilizar aquela ram para outra coisa, onde isso faz sentido, visto que isso é estritamente superior a um sistema que não pode fazer isso. Idealmente, queremos permitir que contêineres individuais sejam capazes de gerenciar compensações de ram / disco e enfrentar as consequências de suas próprias escolhas de recursos (super / subalocados) com efeito mínimo em outros contêineres.

Só para mostrar onde estou indo com isso, minha proposta de espantalho atual é:

  • A metáfora é que cada contêiner se comporta como se estivesse em uma máquina por si só com uma certa quantidade de memória RAM (fornecida por limits.memory ) e swap.
  • O mesmo que outros recursos: cronograma baseado em requests.memory , impor limite baseado em limits.memory . "Memória" neste caso significa "ram" - o uso de swap é gratuito.
  • Especificamente k8s requests.memory -> cgroup memory.low (reduzido por qualquer fator de overcommit); k8s limits.memory -> cgroup memory.high .
  • Se um contêiner exceder seu limite de memória configurado, ele começa a trocar - _independentemente_ da quantidade de memória RAM disponível disponível. Graças aos cgroups, _não_ é apenas o uso de VM, mas também inclui cache de bloco, buffers de soquete, etc. atribuíveis ao contêiner. Isso nos impede de colocar pressão de memória em outros contêineres (ou processos de host). Ao procurar uma página para despejar, o kernel procurará contêineres que estão excedendo seu tamanho de solicitação de memória.
  • Apresente um limite suave de kubelet de uso total de troca, em que o k8s parará de agendar novos pods no host (exatamente como outros "sistemas de arquivos compartilhados", como imagefs).
  • Se atingirmos o limite rígido de uso total de swap, comece a despejar pods com base em qos-class / priority e tamanho de VM acima de "solicitações" (exatamente como outros "sistemas de arquivos compartilhados", como imagefs).
  • Se um contêiner exceder muito seu conjunto de trabalho ( requests.memory ), ele pode sofrer um thrash (se também exceder limits.memory ou se não houver memória RAM suficiente disponível no host). Nós explicitamente _não_ fazemos nada sobre isso por meio do mecanismo de recursos. Se o contêiner estiver com thrashing de swap, então ele (presumivelmente) falhará nas verificações de sondagem de liveness / readiness e será eliminado por meio desse mecanismo (isto é: thrashing de swap está bem se não tivermos SLAs de responsividade configurados).

O resultado final é que o administrador é responsável por configurar a troca "suficiente" em cada sistema. Os aplicativos devem configurar limits.memory com a _max_ ram que desejam usar e requests.memory com o conjunto de trabalho pretendido (incluindo buffers de kernel, etc). Como com outros recursos, classes qos garantidas (limite == solicitação), burstable (limite indefinido ou! = Solicitação), melhor esforço (sem limite ou solicitação) ainda se aplicam. Em particular, isso incentiva os processos burstable para declarar perto de seu conjunto de trabalho pretendido (sem grande buffer de segurança), o que permite uma alocação eficiente (idealmente exatamente 100% da RAM alocada para o conjunto de trabalho) e dá uma degradação de desempenho suave quando os contêineres excedem isso - assim como outros recursos "indulgentes" como a CPU.

Eu acho que isso pode ser implementado dentro dos cgroups do Linux, aborda as questões de isolamento, continua os precedentes conceituais estabelecidos por outros recursos k8s e diminui para o comportamento existente quando o swap é desabilitado (facilitando a migração). A única questão em aberto que tenho é se isso é _já_ o que está implementado (menos o limite suave / rígido "swapfs" do kubelet) - eu preciso ler o código real dos cgroups do kubelet / CRI antes de escrever uma proposta concreta e itens de ação .

Comentários / discussão sobre o acima provavelmente não são apropriados neste problema do github (é um fórum de discussão pobre). Se houver algo terrivelmente errado com o acima, eu gostaria de receber feedback para guslees sobre o k8s slack, caso contrário, escreverei um documento adequado e irei passar pela discussão usual de design em algum momento.

Eu recomendo escrever um documento formal para que possamos ter um fórum de discussão melhor.

Concordou. Estou feliz em ajudar a escrever um KEP, porque tenho algumas ideias definidas para isso, mas nunca fiz uma antes e preferiria ter uma mão mais experiente no leme.

Mas, também, não tenho largura de banda para acompanhar um canal do Slack no meu tempo livre; se houver um método de coordenação mais assíncrono, me avise.

Apenas para manter as coisas vivas: ainda estou muito interessado em trabalhar em um KEP e / ou implementação para isso; assim que as coisas se acalmarem (tenho um workshop para preparar para o próximo fim de semana), tentarei entrar no canal do Slack.

Olá, há alguma discussão pública sobre esse problema acontecendo atualmente? (A folga do k8s não está aberta a todos no momento, e suponho que não estará por algum tempo).

@leonaves, não há discussão sobre a folga acontecendo atualmente AFAIK. o último comentário de @guslees é o último da discussão. Observe que deve haver um KEP com detalhes no repositório kubernetes / realces para iniciar as coisas e, provavelmente, os threads da lista de e-mails também.

Parece haver um fim do túnel para a reabertura da folga também em breve. cruzar meus dedos.

Acontece que ainda não tenho largura de banda mental para entrar em outro canal do Slack. Eu ainda gostaria de colaborar nisso por e-mail.

Uma solução alternativa para quem realmente deseja trocar. Se você

  • inicie o kubelet com --fail-swap-on=false
  • adicione swap aos seus nós
  • containers que _não_ especificam um requisito de memória, então, por padrão, serão capazes de usar toda a memória da máquina, incluindo swap.

Isso é o que estamos fazendo. Ou, pelo menos, tenho quase certeza que sim, na verdade não implementei pessoalmente, mas é o que deduzo.

Isso só pode ser realmente uma estratégia viável se nenhum de seus contêineres especificar um requisito de memória explícito ...

Este método não está funcionando mais !? Eu ligo a troca e implanto um pod sem configuração de memória, e tenho este contêiner

$ docker inspect <dockerID> | grep Memory
            "Memory": 0,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": -1

MemorySwap é "0", o que significa que este contêiner não pode acessar a troca :(

Os problemas ficam obsoletos após 90 dias de inatividade.
Marque o problema como novo com /remove-lifecycle stale .
Problemas obsoletos apodrecem após 30 dias adicionais de inatividade e, eventualmente, fecham.

Se for seguro encerrar este problema agora, faça-o com /close .

Envie feedback para sig-testing, kubernetes / test-infra e / ou fejta .
/ lifecycle stale

/ remove-lifecycle stale.

/ remove-lifecycle stale

Vou colocar isso aqui como outra referência para os leitores desta edição: https://chrisdown.name/2018/01/02/in-defence-of-swap.html

Os problemas ficam obsoletos após 90 dias de inatividade.
Marque o problema como novo com /remove-lifecycle stale .
Problemas obsoletos apodrecem após 30 dias adicionais de inatividade e, eventualmente, fecham.

Se for seguro encerrar este problema agora, faça-o com /close .

Envie feedback para sig-testing, kubernetes / test-infra e / ou fejta .
/ lifecycle stale

/ remove-lifecycle stale

esse recurso é realmente necessário em alguns casos de uso. atualmente, estamos usando k8s para aprendizado de máquina e às vezes precisamos carregar grandes modelos na memória (em nosso caso, às vezes 500 MB por solicitação de API!), e os limites da memória física estão causando problemas sérios. O dimensionamento funcionaria a partir do ponto de vista técnico, mas os custos iriam disparar. Se tivéssemos a memória virtual como uma opção, seria ótimo.

Alguma chance desse tíquete voltar ao roteiro novamente?

Parece um caso para mmap.

Também estou muito interessado neste recurso. Há alguma notícia sobre isso?

Ficaria feliz em começar a investigar isso quando tiver tempo, que está em falta agora, mas seria bom ter um caso canônico ou dois que agravam o problema para que possa ser mais completamente caracterizado (além "dele" começa a se debater e tudo vai para o inferno ") e a abordagem e correção final validadas.

Qualquer solução em perspectiva também deve considerar as implicações de segurança da troca. Isso, obviamente, é verdade para qualquer coisa em execução em um ambiente Unix, mas se as pessoas se acostumaram a rodar pods k8s com uma pseudo-garantia de nenhuma troca e ficaram preguiçosas sobre a disciplina de memória, isso poderia ser uma surpresa bastante rude se for habilitado por predefinição.

seria bom ter um ou dois casos canônicos que agravassem o problema para que pudesse ser caracterizado de forma mais completa

Isso soa como um KEP .

Qualquer solução em perspectiva também deve considerar as implicações de segurança da troca. Isso, obviamente, é verdade para qualquer coisa em execução em um ambiente Unix, mas se as pessoas se acostumaram a rodar pods k8s com uma pseudo-garantia de nenhuma troca e ficaram preguiçosas sobre a disciplina de memória, isso poderia ser uma surpresa bastante rude se for habilitado por predefinição.

Essa lógica se aplica a todos os processos em execução em uma combinação de contêineres, independentemente de o Kubernetes estar em uso ou não.

Concordou! Mas o Docker já suporta explicitamente a execução com swap. O Kubernetes explicitamente não (embora você possa forçá-lo). Meu argumento é que deve ser pelo menos chamado, porque não está no modelo de ameaça de todos, especialmente se eles não tiveram que pensar sobre isso antes.

Também sim, @sftim , é. :-) Acho que o que estou dizendo é que gostaria de escrever / contribuir para um KEP, mas gostaria de ver um caso de teste mínimo ou dois que exercem de forma confiável o problema em um determinado sistema de teste antes de me aventurar. que podemos ter certeza de que estamos resolvendo os problemas certos.

@superdave que tipo de caso de teste você tem em mente?

Aqui está um teste trivial:

  1. Configure um cluster com 1 nó, 16 GiB de RAM e 64GiB de arquivo de paginação.
  2. Tente agendar 20 pods, cada um com solicitação de memória de 1 GiB e limite de memória de 1 GiB.
  3. Observe que nem tudo está programado.

Aqui está outro:

  1. Configure 6 máquinas, cada uma com 16 GiB de RAM e 64GiB de arquivo de paginação.
  2. Tente usar kubeadm com opções padrão para configurar essas máquinas como um cluster Kubernetes.
  3. Observe que kubeadm não está satisfeito com a troca em uso.

Há uma grande mudança para SSD nas plataformas de nuvem mais respeitáveis ​​agora e considerando que o Linux tem otimizações dedicadas para troca em SSD https://lwn.net/Articles/704478/ com possibilidade adicional de compactação, esta situação cria uma oportunidade totalmente nova de utilizar a troca como recurso previsível e rápido para RAM adicional em caso de pressão de memória.
A troca desabilitada torna-se um recurso desperdiçado da mesma forma que a RAM não utilizada é desperdiçada se não for usada para buffers de E / S.

@superdave

Concordou! Mas o Docker já suporta explicitamente a execução com swap. O Kubernetes explicitamente não (embora você possa forçá-lo). Meu argumento é que deve ser pelo menos chamado, porque não está no modelo de ameaça de todos, especialmente se eles não tiveram que pensar sobre isso antes.

Nesse caso, seria justo supor que kubelet irá mlock() seu espaço de memória e definir OOM kill prioridade baixa para evitar ser trocado ou OOM morto, e executar todos os contêineres em cgroups com swapiness definido como 0 por padrão. Se alguém quiser se beneficiar da troca, ele pode optar por usar uma opção, ou seja, enableSwapiness: 50 para contêineres específicos no pod.
Sem surpresas, pilhas incluídas.

@sftim Isso demonstraria que a) o Kubelet não deseja agendar os contêineres eb) o Kubelet não será executado com a troca ativada por padrão. O que estou tentando exercitar são as situações no topo da discussão, por @derekwaynecarr :

O suporte para troca não é trivial. Os frutos garantidos nunca devem exigir troca. Os pods burstable devem ter suas solicitações atendidas sem a necessidade de troca. Os pods BestEffort não têm garantia. O kubelet agora não tem inteligência para fornecer a quantidade certa de comportamento previsível aqui entre os pods.

Discutimos esse tópico pessoalmente no gerenciamento de recursos no início deste ano. Não estamos muito interessados ​​em lidar com isso no curto prazo em relação aos ganhos que ela pode obter. Preferiríamos melhorar a confiabilidade em torno da detecção de pressão e otimizar os problemas de latência antes de tentar otimizar para a troca, mas se esta for uma prioridade mais alta para você, adoraríamos sua ajuda.

E também logo abaixo, de @matthiasr :

Há mais contexto na discussão aqui: # 7294 - ter swap disponível tem interações muito estranhas e ruins com os limites de memória. Por exemplo, um contêiner que atinge seu limite de memória _então_ começa a transbordar para a troca (isso parece ter sido corrigido desde f4edaf2 - eles não terão permissão para usar qualquer troca, esteja lá ou não).

Ambos fornecem uma boa visão dos problemas já vistos, mas seria bom ter uma noção de cenários conhecidos e reproduzíveis que poderiam agravar os problemas. Eu posso inventá-los sozinho, mas se outra pessoa já fez isso, é uma roda que eu não me importaria em não reinventar.

@superdave

Concordou! Mas o Docker já suporta explicitamente a execução com swap. O Kubernetes explicitamente não (embora você possa forçá-lo). Meu argumento é que deve ser pelo menos chamado, porque não está no modelo de ameaça de todos, especialmente se eles não tiveram que pensar sobre isso antes.

Nesse caso, seria justo supor que kubelet irá mlock() seu espaço de memória e definir OOM kill prioridade baixa para evitar ser trocado ou OOM morto, e executar todos os contêineres em cgroups com swapiness definido como 0 por padrão. Se alguém quiser se beneficiar da troca, ele pode optar por usar uma opção, ou seja, enableSwapiness: 50 para contêineres específicos no pod.
Sem surpresas, pilhas incluídas.

Acho que concordo com tudo aqui. Padrão para o comportamento atual para evitar surpresas desagradáveis.

Aqui está um exemplo de como poderia ser um aplicativo simples, onde por algum motivo uma grande parte da memória é alocada, mas nunca acessada novamente. Depois que toda a memória disponível é preenchida, o aplicativo trava ou cai em um loop infinito, basicamente bloqueando recursos ou forçando o eliminador de memória insuficiente:

#include <iostream>
#include <vector>
#include <unistd.h>
int main() {
  std::vector<int> data;
  try
    {
        while(true) { data.resize(data.size() + 200); };
    }
    catch (const std::bad_alloc& ex)
    {
        std::cerr << "Now we filled up memory, so assume we never access that stuff again and just moved on, or we're stuck in an endless loop of some sort...";
        while(true) { usleep(20000); };
    }
  return 0;
}

Uma solução alternativa para quem realmente deseja trocar. Se você

  • inicie o kubelet com --fail-swap-on=false
  • adicione swap aos seus nós
  • containers que _não_ especificam um requisito de memória, então, por padrão, serão capazes de usar toda a memória da máquina, incluindo swap.

Isso é o que estamos fazendo. Ou, pelo menos, tenho quase certeza que sim, na verdade não implementei pessoalmente, mas é o que deduzo.

Isso só pode ser realmente uma estratégia viável se nenhum de seus contêineres especificar um requisito de memória explícito ...

Olá @hjwp , obrigado por suas informações. Isso realmente ajuda muito!

Posso fazer uma pergunta depois disso?

Depois de configurar tudo como você disse, há uma maneira de limitar o uso de memória swap por contêineres?

Eu estava pensando em definir --memory-swap params do Docker
https://docs.docker.com/config/containers/resource_constraints/# --memory-swap-details
Atualmente, meu contêiner não tem limite de uso de troca ( "MemorySwap": -1 )

sudo docker inspect 482d70f73c7c | grep Memory
            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

Mas não consegui encontrar este param exposto em k8s.

A propósito, o limite de memória do pod também restringirá o uso de swap?

Minhas configurações relacionadas à vm

vm.overcommit_kbytes = 0
vm.overcommit_memory = 1
vm.overcommit_ratio = 50
vm.swappiness = 20
vm.vfs_cache_pressure = 1000

Obrigado!

@ pai911 Não acho que seja possível,

atualmente, CRI não suporta isso, veja isto , não há opção como --memory-swap no docker

esta é a limitação de CRI, porém, a especificação OCI suporta esta opção, mas não exportada para a camada CRI

uma solução possível (teoricamente) é criar DaemonSet privilegiado que, por sua vez, lê os dados de anotação dos pods e, em seguida, o DaemonSet edita o valor cgroup manualmente

cgroup

Olá @ win-t,

Obrigado pelo feedback!

Então, por enquanto, essa opção é apenas para uso interno?

Você sabe qual valor cgroup está mapeado para esta opção --memory-swap?

Então, por enquanto, essa opção é apenas para uso interno?

Sim, você não pode definir esta opção, pois eles não são expostos no k8s

A propósito, MemorySwap no docker inspect deve ser o mesmo com Memory acordo com isso , não sei como você pode obter -1 no docker inspect

Você sabe qual valor cgroup está mapeado para esta opção --memory-swap?

  • --memory no docker mapear para memory.limit_in_bytes no cgroup v1
  • --memory-swap no docker mapear para memory.memsw.limit_in_bytes no cgroup v1

@ win-t Muito obrigado!

Estou usando a seguinte versão

Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:16:51Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.10", GitCommit:"1bea6c00a7055edef03f1d4bb58b773fa8917f11", GitTreeState:"clean", BuildDate:"2020-02-11T20:05:26Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}

e eu olhei para a história. parece que a correção foi adicionada neste commit

Talvez não esteja incluído na versão que estou executando?

Então, por enquanto, essa opção é apenas para uso interno?

Sim, você não pode definir esta opção, pois eles não são expostos no k8s

A propósito, MemorySwap no docker inspect deve ser o mesmo com Memory acordo com isso , não sei como você pode obter -1 no docker inspect

Você sabe qual valor cgroup está mapeado para esta opção --memory-swap?

  • --memory no docker mapear para memory.limit_in_bytes no cgroup v1
  • --memory-swap no docker mapear para memory.memsw.limit_in_bytes no cgroup v1

Isso é estranho.

Eu estava usando kops + Debian, e a inspeção do Docker mostra que não há limite de memória Swap
(As informações de inspeção do Docker que postei anteriormente)

Mas então mudei para a imagem do Amazon Linux e foi isso que consegui

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Vou fazer mais algumas investigações e ver se isso é um bug

Então, por enquanto, essa opção é apenas para uso interno?

Sim, você não pode definir esta opção, pois eles não são expostos no k8s
A propósito, MemorySwap no docker inspect deve ser o mesmo com Memory acordo com isso , não sei como você pode obter -1 no docker inspect

Você sabe qual valor cgroup está mapeado para esta opção --memory-swap?

  • --memory no docker mapear para memory.limit_in_bytes no cgroup v1
  • --memory-swap no docker mapear para memory.memsw.limit_in_bytes no cgroup v1

Isso é estranho.

Eu estava usando kops + Debian, e a inspeção do Docker mostra que não há limite de memória Swap
(As informações de inspeção do Docker que postei anteriormente)

Mas então mudei para a imagem do Amazon Linux e foi isso que consegui

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Vou fazer mais algumas investigações e ver se isso é um bug

Agora posso reproduzir o problema existe na imagem oficial do Debian por kops

Parece que esta imagem oficial irá tornar a memória swap ilimitada
kope.io/k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17

Etapas de reprodução:

Meu grupo de instâncias kops é definido como o seguinte:

apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: "2020-03-12T06:33:09Z"
  generation: 5
  labels:
    kops.k8s.io/cluster: solrcluster.k8s.local
  name: node-2
spec:
  additionalUserData:
  - content: |
      #!/bin/sh
      sudo cp /etc/fstab /etc/fstab.bak
      sudo mkfs -t ext4 /dev/nvme1n1
      sudo mkdir /data
      sudo mount /dev/nvme1n1 /data
      echo '/dev/nvme1n1       /data   ext4    defaults,nofail        0       2' | sudo tee -a /etc/fstab
      sudo fallocate -l 2G /data/swapfile
      sudo chmod 600 /data/swapfile
      sudo mkswap /data/swapfile
      sudo swapon /data/swapfile
      echo '/data/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
      sudo sysctl vm.swappiness=10
      sudo sysctl vm.overcommit_memory=1
      echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
      echo 'vm.overcommit_memory=1' | sudo tee -a /etc/sysctl.conf
    name: myscript.sh
    type: text/x-shellscript
  image: kope.io/k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17
  instanceProtection: true
  kubelet:
    failSwapOn: false
  machineType: t3.micro

Passos:

  1. Depois que o cluster estiver instalado e funcionando.

  2. Implante o Solr Helm Chart com a seguinte configuração de recursos

resources:
  limits:
    cpu: "1"
    memory: 640Mi
  requests:
    cpu: 100m
    memory: 256Mi

** Qualquer outro pod também deve funcionar

  1. Listar contêineres para encontrar um ID de contêiner
    sudo docker container ls

  2. Inspecione os parâmetros de memória de um contêiner
    sudo docker inspect d67a72bba427 | grep Memory

            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

Devo enviar o problema em algum lugar? k8s ou kops?

Passos:

  1. Depois que o cluster estiver instalado e funcionando.
  2. Implante o Solr Helm Chart com a seguinte configuração de recursos
resources:
  limits:
    cpu: "1"
    memory: 640Mi
  requests:
    cpu: 100m
    memory: 256Mi

** Qualquer outro pod também deve funcionar

  1. Listar contêineres para encontrar um ID de contêiner
    sudo docker container ls
  2. Inspecione os parâmetros de memória de um contêiner
    sudo docker inspect d67a72bba427 | grep Memory
            "Memory": 671088640,
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": -1,
            "MemorySwappiness": null,

Devo enviar o problema em algum lugar? k8s ou kops?

Posso confirmar que só consigo ver o comportamento correto no Amazon Linux
ami-0cbc6aae997c6538a : amzn2-ami-hvm-2.0.20200304.0-x86_64-gp2

            "Memory": 671088640,
            "CpusetMems": "",
            "KernelMemory": 0,
            "MemoryReservation": 0,
            "MemorySwap": 671088640,
            "MemorySwappiness": null,

Isto é: "MemorySwap" == "Memory"

As outras duas imagens têm a mesma configuração: "MemorySwap": -1 , o que leva ao uso de troca ilimitado.

  • Debian

    • ami-075e61ad77b1269a7 : k8s-1.15-debian-stretch-amd64-hvm-ebs-2020-01-17

  • Ubuntu

    • ami-09a4a9ce71ff3f20b : ubuntu / images / hvm-ssd / ubuntu-bionic-18.04-amd64-server-20200112

Então eu acho que pode ser o problema do k8s?

Histórias de usuários:

(1) O programa fornecido pelo meu fornecedor usa um tempo de execução de linguagem que exige acesso à fonte do código do programa. Devido a isso, durante a inicialização, todo o código-fonte do programa é organizado em uma área de memória separada. Uma vez que o programa é inicializado e o container torna-se Pronto, esta memória não será acessada (você não pode provar isso, mas não vai). Além disso, o programa aloca algumas páginas para reservar para tratamento OOM personalizado. Esta memória pode ser trocada. Não quero que essa "memória morta" atrapalhe outros aplicativos de cluster. Posso calcular com precisão a quantidade de memória que ficará morta e listá-la como uma solicitação de troca na especificação do Pod.

(2) Estou executando um aprendizado de máquina ou carga de trabalho de análise de dados em que o uso de memória pode aumentar repentinamente e se retrair. Não há problema se esses aplicativos ficarem lentos, contanto que não sejam encerrados e, eventualmente, terminem. Quero provisionar o cluster com troca para mitigar despejos quando esses balões de memória acontecerem. Esses pods teriam solicitações baixas de memória e troca, um limite de memória moderado - talvez o suficiente para a linha de base + um conjunto de trabalho - e um grande limite de troca.

(3) Estou executando um servidor web em um interpretador (por exemplo, Ruby on Rails) que ocasionalmente precisa fazer um fork + exec. A contabilidade estrita da memória resulta em falhas de bifurcação, que são inaceitáveis. Quero provisionar a troca para que o kernel tenha espaço de memória garantido para cobrir o comportamento do processo entre as chamadas de fork e exec. O valor vm.swappiness pode ser definido para desencorajar extremamente a troca, e eu configurei alertas para notificar as operações se a troca for realmente usada durante a produção. A especificação do pod definiria a solicitação e o limite de troca com o mesmo valor.

Recentemente, tentamos migrar todos os nossos serviços baseados em docker para o Kubernetes, mas tivemos que abandonar o projeto porque a troca não era compatível.

Descobrimos que precisaríamos provisionar 3 vezes a quantidade de hardware para suportar o mesmo número de contêineres que estávamos executando atualmente com a troca habilitada.

O principal problema é que nossa carga de trabalho consiste em uma série de contêineres que podem usar até 1 Gb ou mais de memória (ou swap), mas normalmente usam cerca de 50 MB ou mais quando operando normalmente.

Não ser capaz de trocar significava que tínhamos que projetar tudo para a maior carga possível que ele pudesse precisar, ao invés de ter um bloco de troca disponível quando grandes 'trabalhos' precisassem ser tratados.

Acabamos abandonando nossa migração para o Kubernetes e, temporariamente, movemos tudo para o Swarm por enquanto, na esperança de que a troca seja compatível no futuro.

O principal problema é que nossa carga de trabalho consiste em uma série de contêineres que podem usar até 1 Gb ou mais de memória (ou swap), mas normalmente usam cerca de 50 MB ou mais quando operando normalmente.

Pode-se ousar dizer que os aplicativos em execução nesses contêineres são escritos de forma incrivelmente pobre.

Pode-se ousar dizer que os aplicativos em execução nesses contêineres são escritos de forma incrivelmente pobre.

Isso é irrelevante, e apontar o dedo raramente é construtivo. O ecossistema Kubernetes é construído para oferecer suporte a uma ampla variedade de perfis de aplicativos, e destacar um deles dessa forma não faz muito sentido.

O principal problema é que nossa carga de trabalho consiste em uma série de contêineres que podem usar até 1 Gb ou mais de memória (ou swap), mas normalmente usam cerca de 50 MB ou mais quando operando normalmente.

Pode-se ousar dizer que os aplicativos em execução nesses contêineres são escritos de forma incrivelmente pobre.

lol, este é um recurso do kernel, um aplicativo pode usar madvise(2) no arquivo shm, e não bloqueamos madvise syscall,
portanto, é elegível para o usuário aproveitar esse recurso em seu design, você não pode dizer "são escritos incrivelmente mal",

O principal problema é que nossa carga de trabalho consiste em uma série de contêineres que podem usar até 1 Gb ou mais de memória (ou swap), mas normalmente usam cerca de 50 MB ou mais quando operando normalmente.

Pode-se ousar dizer que os aplicativos em execução nesses contêineres são escritos de forma incrivelmente pobre.

Sua resposta indica que você não entende as cargas de trabalho com as quais muitos desenvolvedores estão trabalhando.

A carga de trabalho que mencionei lida com conjuntos de dados de tamanhos diferentes fornecidos pelos usuários, daí a grande variedade de possíveis requisitos de recursos.

Claro, nós _podemos_ usar arquivos mapeados de memória para manter o uso de memória consistente. Então, precisaríamos reescrever todas as bibliotecas para usar o mapeamento de memória, o que incluiria mais ou menos todas as bibliotecas existentes ... nunca.

Mas então teríamos criado o que é essencialmente um arquivo de paginação específico do aplicativo, que quase certamente teria um desempenho pior do que um gerenciado pelo kernel.

Algumas implantações do Kubernetes precisam de troca

Eu tenho um caso de uso válido - estou desenvolvendo um produto local, distro Linux, incluído com o kubeadm. sem escala horizontal por design. Para sobreviver a picos de memória oportunistas e ainda funcionar (mas lento), eu definitivamente preciso de troca .

Para instalar kubeadm com troca habilitada

  1. Crie um arquivo em /etc/systemd/system/kubelet.service.d/20-allow-swap.conf com o conteúdo:

    [Service]
    Environment="KUBELET_EXTRA_ARGS=--fail-swap-on=false"
    
  2. Corre

    sudo systemctl daemon-reload
    
  3. Inicialize kubeadm com sinalizador --ignore-preflight-errors=Swap

    kubeadm init --ignore-preflight-errors=Swap
    

https://stackoverflow.com/a/62158455/3191896

Como um desenvolvedor de software ingênuo, parece-me perfeitamente razoável que pods de sistema com cargas de trabalho sensíveis ao tempo solicitem um comportamento de não troca e outras cargas de trabalho (por padrão) colocadas em uma categoria de melhor esforço. Isso não resolveria todas as preocupações?

Para minhas próprias necessidades, muitos dos meus aplicativos se beneficiam de caches. Dito isso, se um aplicativo repentinamente precisar de um monte de memória, seria preferível enviar o cache mais antigo para o disco se esses aplicativos não responderem a uma solicitação para diminuir a pressão de memória do que deixar a nova carga de trabalho ficar sem memória, ou para apoiar a explosão com memória física + mais para implantações contínuas + mais para uma possível falha de nó.

@metatick disse:

Claro, poderíamos usar arquivos mapeados de memória para manter o uso consistente da memória. Então, precisaríamos reescrever todas as bibliotecas para usar o mapeamento de memória, o que incluiria mais ou menos todas as bibliotecas existentes ... nunca.

A biblioteca C padrão do Linux foi projetada para substituir o alocador de memória; os malloc , realloc e free são chamados por meio de ponteiros para esse propósito. Portanto, você poderia apenas LD_PRELOAD uma biblioteca que os substituiria para alocar de um arquivo mmapped.

Mas então teríamos criado o que é essencialmente um arquivo de paginação específico do aplicativo, que quase certamente teria um desempenho pior do que um gerenciado pelo kernel.

Na verdade, ele funcionaria exatamente como uma troca normal, porque seria gerenciado pelo mesmo código no kernel. A única diferença seria a falta do parâmetro de troca para ajustar sua prioridade.

A única questão em aberto que tenho é se isso já está implementado (menos o limite suave / rígido "swapfs" do kubelet) - preciso ler o código kubelet / CRI real dos cgroups antes de escrever uma proposta concreta e itens de ação .

@anguslees ,
Você já deu uma olhada para verificar o comportamento. Em caso afirmativo, você pode adicionar alguma resolução ou um link para uma, por favor?

Obrigado,
Jan

Você já deu uma olhada para verificar o comportamento. Em caso afirmativo, você pode adicionar alguma resolução ou um link para uma, por favor?

Eu não. (Eu pesquisei um pouco no código do docker, mas já esqueci tudo sobre ele e precisaria começar de novo)

Outros voluntários são bem-vindos! Espero não ter roubado o oxigênio de alguém dizendo que trabalharia nisso e depois falhei em :(

Para adicionar à história de @metatick :

Atualmente, uso Gigalixir como meu host, rodando em cima do Kubernetes. É um aplicativo da web. Às vezes, os clientes carregam um lote de fotos, então meu aplicativo acelera vários (ugh) processos do ImageMagick para redimensioná-los. O uso de memória atinge o pico, o assassino OOM é acionado e meu aplicativo cai (brevemente) e o upload é arruinado.

Acabo tendo que pagar muito mais para Gigalixir do que deveria apenas por causa do uso excessivo. Como outros mencionaram.

Você pode não gostar de trocar do ponto de vista do design, mas sua decisão está custando dinheiro aos empresários ... e é um desperdício.

Por favor conserte. :)

Este também é um grande problema para mim. No meu caso de uso, eu precisaria executar pods que usam ~ 100 MB na maioria das vezes, mas de vez em quando, quando um usuário aciona eventos específicos, ele pode estourar até 2 GB de RAM por alguns minutos antes de voltar (e não, não é porque está mal escrito, é a realidade da carga de trabalho).
Eu corro quase uma centena dessas cargas de trabalho por vez em máquinas de 16 GB com swap. Simplesmente não posso mover essa carga de trabalho para o Kubernetes porque isso não funcionaria de jeito nenhum. Então, agora, eu tenho meu próprio orquestrador que executa essas cargas de trabalho em sistemas não Kubernetes enquanto meu aplicativo principal é executado no Kubernetes, e isso anula o propósito da minha migração para o K8s. Sem a troca, ele morre ou preciso sempre desperdiçar muita memória RAM disponível durante os poucos minutos que os aplicativos podem (ou não) estourar.

Se você pode definir um limite de CPU, que estrangula a CPU de um pod, deve ser capaz de definir um limite de memória que estrangula a memória usada por um pod. Matar um pod quando ele atinge o limite de memória é tão ridículo quanto matar um se ele usar mais recursos da CPU do que o limite de CPU definido (desculpe, mas nem todo pod é uma réplica que pode ser desativada sem consequências).

O Kubernetes não pode funcionar com a troca definida no nó porque pode afetar todo o desempenho de todo o cluster, tudo bem (embora eu não ache que esse seja um argumento válido). Idealmente, o que precisaria acontecer é que o próprio pod teria um arquivo de troca no nível do pod em que apenas os processos dentro desses contêineres seriam trocados. Isso teoricamente restringiria o uso de RAM e o desempenho (devido à troca) dos pods que excedem seus limites de memória, assim como os limites de CPU os restringem.
Infelizmente, não parece que os cgroups podem especificar um arquivo de troca, apenas sua troca, e você não pode dizer ao kernel para "trocar se o uso de mem estiver acima deste limite", pois ao invés disso parece decidir quando trocar com base no último acesso e outras métricas.

Mas, enquanto isso, por que não permitir que a troca exista em um nó, defina a troca como 0 para os pods que não têm um limite definido e, quando um limite for definido (ou algum outro campo de especificação para dizer "swapInsteadOfKill") defina o troca para um valor diferente de zero?

Ao lado da discussão sobre "trocar ou não trocar" me deixa curioso que o comportamento descrito por @ pai911 não tenha sido mais abordado pela equipe k8s.

Posso confirmar que o kubelet parece se comportar de maneira diferente (e em alguns sistemas operacionais não de acordo com o código cortado mencionado acima) em relação às configurações de memória do docker deamon. Nossos clusters são executados em SUSE Linux e estamos experimentando o mesmo uso de swap ilimitado mencionado em https://github.com/kubernetes/kubernetes/issues/53533#issuecomment -598056151

Detalhes do SO: SUSE Linux Enterprise Server 12 SP4 - Linux 4.12.14-95.45-default

Sem o suporte adequado para Swap em k8s de qualquer forma, eu gostaria pelo menos que o kubelet tratasse as configurações de memória do Docker de forma consistente, independentemente do sistema operacional subjacente.

Eu coloquei a troca na agenda para ver se há apetite da comunidade ou voluntários para ajudar a levar isso adiante no 1.21. Não tenho nenhuma objeção em apoiar a troca, como observei em 2017, só precisa ter certeza de não confundir despejo de kubelet, prioridade de pod, qualidade de serviço de pod e, o que é mais importante, os pods devem ser capazes de dizer se toleram troca ou não. Todas essas coisas são importantes para garantir que os pods sejam portáteis.

Ultimamente, muita energia tem sido focada em fazer coisas como a memória alinhada com NUMA funcionarem, mas se há pessoas que são menos sensíveis ao desempenho e igualmente motivadas para ajudar a mover este espaço para frente, adoraríamos ajudar a obter uma vantagem inicial no design de detalhado KEP neste espaço.

Não tenho acompanhado o processo da comunidade muito bem ultimamente, pois as coisas têm estado muito ocupadas para mim ultimamente, no entanto, parece que eles devem se acalmar um pouco em breve. Existe uma maneira de interagir sem precisar entrar em um canal do Slack?

Esta página foi útil?
0 / 5 - 0 avaliações