Melhores práticas para contêineres Kubernetes: verificações de integridade

Melhores práticas para contêineres Kubernetes: verificações de integridade

TL, DR

  • Para alcançar alta observabilidade de contêineres e microsserviços, logs e métricas primárias não são suficientes.
  • Para uma recuperação mais rápida e maior resiliência, as aplicações devem aplicar o Princípio de Alta Observabilidade (HOP).
  • No nível do aplicativo, o NOP requer: registro adequado, monitoramento rigoroso, verificações de integridade e rastreamento de desempenho/transição.
  • Use cheques como um elemento do NOR sonda de prontidão и Sonda de vivacidade Kubernetes.

O que é um modelo de verificação de integridade?

Ao projetar um aplicativo de missão crítica e altamente disponível, é muito importante pensar em um aspecto como a tolerância a falhas. Um aplicativo é considerado tolerante a falhas se se recuperar rapidamente de uma falha. Um aplicativo de nuvem típico usa uma arquitetura de microsserviços – onde cada componente é colocado em um contêiner separado. E para garantir que o aplicativo no k8s esteja altamente disponível ao projetar um cluster, você precisa seguir certos padrões. Entre eles está o modelo de verificação de saúde. Ele define como o aplicativo comunica ao k8s que está íntegro. Estas não são apenas informações sobre se o pod está em execução, mas também sobre como ele recebe e responde às solicitações. Quanto mais o Kubernetes souber sobre a integridade do pod, mais decisões inteligentes ele tomará sobre roteamento de tráfego e balanceamento de carga. Assim, o Princípio da Alta Observabilidade permite que a aplicação responda às solicitações em tempo hábil.

Princípio de Alta Observabilidade (HOP)

O princípio da alta observabilidade é um dos princípios para projetar aplicativos em contêineres. Numa arquitetura de microsserviços, os serviços não se importam com a forma como o seu pedido é processado (e com razão), mas o que importa é como recebem as respostas dos serviços destinatários. Por exemplo, para autenticar um usuário, um contêiner envia uma solicitação HTTP para outro, esperando uma resposta em um determinado formato - isso é tudo. PythonJS também pode processar a solicitação e Python Flask pode responder. Os contêineres são como caixas pretas com conteúdos ocultos entre si. No entanto, o princípio NOP exige que cada serviço exponha vários endpoints de API que indiquem quão íntegro ele é, bem como seu status de prontidão e tolerância a falhas. O Kubernetes solicita esses indicadores para pensar nas próximas etapas de roteamento e balanceamento de carga.

Um aplicativo em nuvem bem projetado registra seus principais eventos usando os fluxos de E/S padrão STDERR e STDOUT. Em seguida vem um serviço auxiliar, por exemplo filebeat, logstash ou fluentd, entregando logs para um sistema de monitoramento centralizado (por exemplo Prometheus) e um sistema de coleta de logs (conjunto de software ELK). O diagrama abaixo mostra como um aplicativo em nuvem funciona de acordo com o Padrão de Teste de Saúde e o Princípio de Alta Observabilidade.

Melhores práticas para contêineres Kubernetes: verificações de integridade

Como aplicar o padrão de verificação de saúde no Kubernetes?

Pronto para uso, o k8s monitora o status dos pods usando um dos controladores (implantações, ReplicaSets, Conjuntos de daemon, Conjuntos de estado etc etc.). Ao descobrir que o pod caiu por algum motivo, o controlador tenta reiniciá-lo ou movê-lo para outro nó. No entanto, um pod pode informar que está funcionando, mas não está funcionando. Vamos dar um exemplo: sua aplicação usa Apache como servidor web, você instalou o componente em vários pods do cluster. Como a biblioteca foi configurada incorretamente, todas as solicitações à aplicação respondem com o código 500 (erro interno do servidor). Ao verificar a entrega, verificar o status dos pacotes dá um resultado positivo, mas os clientes pensam de forma diferente. Descreveremos esta situação indesejável da seguinte forma:

Melhores práticas para contêineres Kubernetes: verificações de integridade

Em nosso exemplo, k8s faz verificação de funcionalidade. Nesse tipo de verificação, o kubelet verifica continuamente o estado do processo no contêiner. Assim que ele entender que o processo foi interrompido, ele o reiniciará. Se o erro puder ser resolvido simplesmente reiniciando o aplicativo, e o programa for projetado para encerrar qualquer erro, então uma verificação de integridade do processo é tudo que você precisa para seguir o NOP e o padrão de teste de integridade. A única pena é que nem todos os erros são eliminados ao reiniciar. Nesse caso, o k8s oferece duas maneiras mais profundas de identificar problemas com o pod: Sonda de vivacidade и sonda de prontidão.

LivenessProbe

Durante a Sonda de vivacidade O kubelet realiza 3 tipos de verificações: não apenas determina se o pod está em execução, mas também se está pronto para receber e responder adequadamente às solicitações:

  • Configure uma solicitação HTTP para o pod. A resposta deve conter um código de resposta HTTP no intervalo de 200 a 399. Assim, os códigos 5xx e 4xx sinalizam que o pod está com problemas, mesmo que o processo esteja em execução.
  • Para testar pods com serviços não HTTP (por exemplo, o servidor de e-mail Postfix), é necessário estabelecer uma conexão TCP.
  • Execute um comando arbitrário para um pod (internamente). A verificação é considerada bem-sucedida se o código de conclusão do comando for 0.

Um exemplo de como isso funciona. A próxima definição de pod contém um aplicativo NodeJS que gera um erro 500 em solicitações HTTP. Para garantir que o contêiner seja reiniciado ao receber tal erro, usamos o parâmetro livenessProbe:

apiVersion: v1
kind: Pod
metadata:
 name: node500
spec:
 containers:
   - image: magalix/node500
     name: node500
     ports:
       - containerPort: 3000
         protocol: TCP
     livenessProbe:
       httpGet:
         path: /
         port: 3000
       initialDelaySeconds: 5

Isso não é diferente de qualquer outra definição de pod, mas estamos adicionando um objeto .spec.containers.livenessProbe. Parâmetro httpGet aceita o caminho para o qual a solicitação HTTP GET é enviada (em nosso exemplo é /, mas em cenários de combate pode haver algo como /api/v1/status). Outro livenessProbe aceita um parâmetro initialDelaySeconds, que instrui a operação de verificação a aguardar um número especificado de segundos. O atraso é necessário porque o contêiner precisa de tempo para iniciar e, ao ser reiniciado, ficará indisponível por algum tempo.

Para aplicar essa configuração a um cluster, use:

kubectl apply -f pod.yaml

Após alguns segundos, você pode verificar o conteúdo do pod usando o seguinte comando:

kubectl describe pods node500

No final da saída, encontre é isso que.

Como você pode ver, livenessProbe iniciou uma solicitação HTTP GET, o contêiner gerou um erro 500 (que foi programado para fazer) e o kubelet o reiniciou.

Se você está se perguntando como o aplicativo NideJS foi programado, aqui está o app.js e o Dockerfile que foram usados:

aplicativo js

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(500, { "Content-type": "text/plain" });
    res.end("We have run into an errorn");
});

server.listen(3000, function() {
    console.log('Server is running at 3000')
})

dockerfile

FROM node
COPY app.js /
EXPOSE 3000
ENTRYPOINT [ "node","/app.js" ]

É importante observar o seguinte: livenessProbe só reiniciará o contêiner se ele falhar. Se uma reinicialização não corrigir o erro que está impedindo a execução do contêiner, o kubelet não poderá tomar medidas para corrigir o problema.

sonda de prontidão

readinessProbe funciona de forma semelhante a livenessProbes (solicitações GET, comunicações TCP e execução de comandos), exceto para ações de solução de problemas. O contêiner no qual a falha é detectada não é reiniciado, mas isolado do tráfego de entrada. Imagine que um dos contêineres esteja realizando muitos cálculos ou esteja sob carga pesada, fazendo com que os tempos de resposta aumentem. No caso do livenessProbe, a verificação de disponibilidade da resposta é acionada (por meio do parâmetro de verificação timeoutSeconds), após o qual o kubelet reinicia o contêiner. Quando iniciado, o contêiner começa a executar tarefas que consomem muitos recursos e é reiniciado novamente. Isto pode ser crítico para aplicações que necessitam de velocidade de resposta. Por exemplo, um carro enquanto está na estrada aguarda uma resposta do servidor, a resposta atrasa - e o carro sofre um acidente.

Vamos escrever uma definição redinessProbe que definirá o tempo de resposta da solicitação GET para não mais que dois segundos, e o aplicativo responderá à solicitação GET após 5 segundos. O arquivo pod.yaml deve ficar assim:

apiVersion: v1
kind: Pod
metadata:
 name: nodedelayed
spec:
 containers:
   - image: afakharany/node_delayed
     name: nodedelayed
     ports:
       - containerPort: 3000
         protocol: TCP
     readinessProbe:
       httpGet:
         path: /
         port: 3000
       timeoutSeconds: 2

Vamos implantar um pod com kubectl:

kubectl apply -f pod.yaml

Vamos esperar alguns segundos e ver como o readinessProbe funcionou:

kubectl describe pods nodedelayed

No final da saída você pode ver que alguns dos eventos são semelhantes este aqui.

Como você pode ver, o kubectl não reiniciou o pod quando o tempo de verificação excedeu 2 segundos. Em vez disso, ele cancelou o pedido. As comunicações recebidas são redirecionadas para outros pods em funcionamento.

Observe que agora que o pod foi descarregado, o kubectl roteia as solicitações para ele novamente: as respostas às solicitações GET não são mais atrasadas.

Para comparação, abaixo está o arquivo app.js modificado:

var http = require('http');

var server = http.createServer(function(req, res) {
   const sleep = (milliseconds) => {
       return new Promise(resolve => setTimeout(resolve, milliseconds))
   }
   sleep(5000).then(() => {
       res.writeHead(200, { "Content-type": "text/plain" });
       res.end("Hellon");
   })
});

server.listen(3000, function() {
   console.log('Server is running at 3000')
})

TL, DR
Antes do advento dos aplicativos em nuvem, os logs eram o principal meio de monitorar e verificar a integridade dos aplicativos. No entanto, não havia meios de tomar qualquer ação corretiva. Os registros ainda são úteis hoje; eles precisam ser coletados e enviados para um sistema de coleta de registros para analisar situações de emergência e tomar decisões. [Tudo isso poderia ser feito sem aplicativos em nuvem usando o monit, por exemplo, mas com o k8s ficou muito mais fácil :) – nota do editor. ]

Hoje, as correções precisam ser feitas quase em tempo real, de modo que os aplicativos não precisam mais ser caixas pretas. Não, eles devem mostrar endpoints que permitam aos sistemas de monitoramento consultar e coletar dados valiosos sobre o estado dos processos para que possam responder instantaneamente, se necessário. Isso é chamado de Padrão de Design de Teste de Desempenho, que segue o Princípio de Alta Observabilidade (HOP).

O Kubernetes oferece 2 tipos de verificações de integridade por padrão: readinessProbe e livenessProbe. Ambos usam os mesmos tipos de verificações (solicitações HTTP GET, comunicações TCP e execução de comandos). Eles diferem nas decisões que tomam em resposta aos problemas nos grupos. livenessProbe reinicia o contêiner na esperança de que o erro não aconteça novamente e readinessProbe isola o pod do tráfego de entrada até que a causa do problema seja resolvida.

O design adequado do aplicativo deve incluir ambos os tipos de verificação e garantir que eles coletem dados suficientes, especialmente quando uma exceção é lançada. Ele também deve mostrar os endpoints de API necessários que fornecem ao sistema de monitoramento (Prometheus) métricas de saúde importantes.

Fonte: habr.com

Adicionar um comentário