Começamos a criar nosso primeiro cluster Kubernetes em 2017 (da versão K8s 1.9.4). Tínhamos dois grupos. Um funcionou em bare metal, em máquinas virtuais RHEL, o outro na nuvem AWS EC2.
Hoje, nossa infraestrutura inclui mais de 400 máquinas virtuais espalhadas por vários data centers. A plataforma serve como base para aplicativos e sistemas de missão crítica altamente disponíveis que conduzem uma enorme rede de quase 4 milhões de dispositivos ativos.
No final das contas, o Kubernetes tornou nossa vida mais fácil, mas o caminho para isso era espinhoso e exigia uma mudança completa de paradigma. Houve uma transformação total não apenas do conjunto de habilidades e ferramentas, mas também da abordagem do design e do pensamento. Tivemos que dominar muitas novas tecnologias e investir pesado no desenvolvimento de infraestrutura e desenvolvimento de equipes.
Aqui estão as principais lições que aprendemos com o uso do Kubernetes em produção ao longo de três anos.
1. Uma história divertida com aplicativos Java
Quando se trata de microsserviços e conteinerização, os engenheiros tendem a se afastar do Java, principalmente por causa de seu gerenciamento de memória notoriamente imperfeito. No entanto, hoje a situação é diferente e a compatibilidade do Java com contêineres melhorou nos últimos anos. Afinal, mesmo sistemas populares como Apache Kafka e Elasticsearch são executados em Java.
Em 2017-2018, alguns de nossos aplicativos eram executados em Java versão 8. Eles frequentemente se recusavam a funcionar em ambientes em contêineres como o Docker e travavam devido a problemas com a memória heap e coletores de lixo inadequados. Como se viu, esses problemas foram causados pela incapacidade da JVM para executar os mecanismos de contêiner do Linux (
cgroupse namespaces).
Desde então, a Oracle tem feito esforços significativos para melhorar a compatibilidade do Java com o mundo do contêiner. Já na versão 8 do Java, sinalizadores experimentais de JVM pareciam resolver esses problemas:
XX:+UnlockExperimentalVMOptionse XX:+UseCGroupMemoryLimitForHeap.
Mas apesar de todas as melhorias, ninguém diria que o Java ainda tem uma má reputação por usar muita memória e ser lento para iniciar em comparação com o Python. ou vá. Isso se deve principalmente às especificações do gerenciamento de memória no JVM e no ClassLoader.
Hoje, se temos que trabalhar com Java, tentamos pelo menos usar a versão 11 ou superior. E nossos limites de memória no Kubernetes são 1 GB maiores do que o limite máximo de memória heap no JVM (
-Xmx) (apenas no caso de). Ou seja, se a JVM usar 8 GB para memória heap, o limite de memória do Kubernetes para o aplicativo será definido como 9 GB. Graças a essas medidas e melhorias, a vida ficou um pouco mais fácil.
2. Atualizações relacionadas ao ciclo de vida do Kubernetes
O gerenciamento do ciclo de vida do Kubernetes (atualizações, adições) é uma coisa complicada e difícil, especialmente se o cluster for baseado em bare metal ou máquinas virtuais . Descobriu-se que, para atualizar para uma nova versão, é muito mais fácil criar um novo cluster e, em seguida, transferir cargas de trabalho para ele. Atualizar sites existentes simplesmente não é viável, pois envolve um esforço significativo e um planejamento cuidadoso.
Isso ocorre porque o Kubernetes tem muitas partes "móveis" a serem consideradas durante o upgrade. Para que o cluster funcione, você deve coletar todos esses componentes juntos - do Docker aos plug-ins CNI como Calico ou Flannel. Projetos como Kubespray, KubeOne, kops e kube-aws simplificam o processo um pouco, mas têm desvantagens.
Implementamos nossos clusters em máquinas virtuais RHEL usando Kubespray. Ele provou ser excelente. O Kubespray tinha scripts para criar, adicionar ou remover nós, atualizar uma versão e quase tudo que você precisa para trabalhar com o Kubernetes na produção. Dito isso, o script de atualização foi acompanhado por uma advertência para não pular nem mesmo as versões menores. Ou seja, para chegar à versão desejada, o usuário precisava instalar todas as intermediárias.
A principal lição aqui é que se você planeja usar ou já está usando o Kubernetes, pense nas etapas do ciclo de vida do K8s e como ele se encaixa na sua solução. Geralmente, é mais fácil criar e executar um cluster do que mantê-lo atualizado.
3. Construir e implantar
Esteja preparado para o fato de que você terá que revisar os pipelines de construção e implantação. Com a transição para o Kubernetes, passamos por uma transformação radical desses processos. Não apenas reestruturamos os pipelines Jenkins, mas com a ajuda de ferramentas como o Helm, desenvolvemos novas estratégias para construir e trabalhar com Git, marcando imagens do Docker e controlando gráficos do Helm.
Você precisará de uma única estratégia para manter seu código, arquivos de implantação do Kubernetes, Dockerfiles, imagens do Docker, gráficos do Helm e uma maneira de unir tudo.
Depois de várias iterações, decidimos no seguinte diagrama:
- O código do aplicativo e seus gráficos do Helm estão localizados em diferentes repositórios. Isso nos permite versioná-los independentemente um do outro ( versionamento semântico ).
- , , . , ,
app-1.2.0charts-1.1.0. (values) Helm, patch- (,1.1.01.1.1). (RELEASE.txt) . - , Apache Kafka Redis ( ), . , Docker- Helm-. Docker- , .
(. .: Open Source- Kubernetes — werf — , .)
4. Liveness Readiness ( )
As verificações de atividade e prontidão do Kubernetes são ótimas para lidar de forma autônoma com problemas do sistema. Eles podem reiniciar contêineres em caso de falhas e redirecionar o tráfego de instâncias "não íntegras". Mas, em algumas circunstâncias, essas verificações podem se transformar em uma faca de dois gumes e afetar a inicialização e a recuperação do aplicativo (isso é especialmente verdadeiro para aplicativos com estado, como plataformas de mensagens ou bancos de dados).
Nosso Kafka se tornou sua vítima. Tínhamos um conjunto com estado de 3
Brokere 3 Zookeepercom replicationFactor= 3 eminInSyncReplica= 2. O problema ocorreu ao reiniciar o Kafka após travamentos ou travamentos aleatórios. Na inicialização, o Kafka executava scripts adicionais para corrigir índices corrompidos, o que levava de 10 a 30 minutos, dependendo da gravidade do problema. Esse atraso fazia com que os testes de atividade falhassem continuamente, fazendo com que o Kubernetes "encerrasse" e reiniciasse o Kafka. Como resultado, Kafka pôde não apenas corrigir os índices, mas até mesmo começar.
A única solução naquele momento era ajustar o parâmetro
initialDelaySecondsnas configurações do teste de atividade para que as verificações fossem realizadas somente depois que o contêiner fosse iniciado. O principal desafio, claro, é decidir qual atraso definir. As partidas individuais após uma falha podem levar até uma hora e isso deve ser levado em consideração. Por outro lado, quanto maisinitialDelaySeconds, o Kubernetes mais lento responderá a falhas durante a inicialização do contêiner.
Nesse caso, o meio-termo é o valor
initialDelaySecondsque melhor se adapta aos seus requisitos de resiliência, ao mesmo tempo que dá ao aplicativo tempo suficiente para iniciar com êxito em todas as situações de falha (falhas de disco, problemas de rede, falhas de sistema, etc.)
Atualização : nas versões recentes do Kubernetes, um terceiro tipo de teste apareceu, chamado de teste de inicialização. Ele está disponível como uma versão alfa desde o lançamento 1.16 e como uma versão beta desde 1.18.
O Startup Probe resolve o problema acima desativando as verificações de prontidão e atividade até que o contêiner seja inicializado, permitindo assim que o aplicativo inicie normalmente.
5. Trabalhar com IP externo
Acontece que o uso de IPs externos estáticos para acessar serviços coloca uma pressão significativa no mecanismo de rastreamento de conexão do kernel. Se você não pensar bem, pode "quebrar".
Em nosso cluster, usamos
Calicotanto CNI quanto BGPcomo protocolo de roteamento, além de interagir com roteadores de fronteira. O modo Kube-proxy está ativado iptables. Abrimos o acesso ao nosso serviço muito ocupado no Kubernetes (ele processa milhões de conexões todos os dias) por meio de um IP externo. Por causa do SNAT e do mascaramento que vem da rede definida por software, o Kubernetes precisa de um mecanismo para controlar todos esses fluxos lógicos. Para isso, o K8s usa essas ferramentas básicas como onntrackenetfilter... Com a ajuda deles, ele gerencia conexões externas para um IP estático, que é então convertido para o IP interno do serviço e, finalmente, para o endereço IP do pod. E tudo isso é feito usando uma tabela conntracke iptables.
No entanto, as possibilidades da mesa não são
conntrackilimitadas. Quando o limite é atingido, o cluster Kubernetes (mais precisamente, o kernel do sistema operacional em seu núcleo) não será mais capaz de aceitar novas conexões. No RHEL, esse limite pode ser verificado da seguinte forma:
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144
Uma maneira de contornar essa limitação é combinar vários nós com roteadores de borda para que as conexões de entrada para um IP estático sejam distribuídas por todo o cluster. Se você tiver uma grande frota de máquinas em seu cluster, essa abordagem pode aumentar significativamente o tamanho da tabela
conntrackpara lidar com um grande número de conexões de entrada.
Isso nos confundiu completamente quando começamos em 2017. No entanto, há relativamente pouco tempo (em abril de 2019), o projeto Calico publicou um estudo detalhado sob o título apropriado " Por que conntrack não é mais seu amigo " (existe uma tradução para o russo - tradução aproximada) .
Você realmente precisa do Kubernetes?
Três anos se passaram, mas ainda continuamos a descobrir / aprender algo novo a cada dia. O Kubernetes é uma plataforma complexa com seu próprio conjunto de desafios, especialmente na área de iniciar o ambiente e mantê-lo funcionando. Isso mudará seu pensamento, arquitetura e atitude em relação ao design. Você terá que lidar com o escalonamento e atualização de equipes.
Por outro lado, trabalhar na nuvem e a capacidade de usar o Kubernetes como um serviço evitarão a maioria das preocupações associadas à manutenção da plataforma (como estender o CIDR da rede interna e atualizar o Kubernetes).
Hoje entendemos que a principal questão a nos perguntar é realmentevocê precisa do Kubernetes? Isso o ajudará a avaliar o quão global é o problema e se o Kubernetes o ajudará a lidar com ele.
O fato é que mudar para o Kubernetes é caro. Portanto, as vantagens do seu caso de uso (e quanto e como ele alavanca a plataforma) devem justificar o preço que você paga. Nesse caso, o Kubernetes pode melhorar significativamente sua produtividade.
Lembre-se de que a tecnologia pela tecnologia não tem sentido.
PS do tradutor
Leia também em nosso blog: