Escalar e balancear a carga de aplicações

KubernetesBeginner
Pratique Agora

Introdução

Os Services fornecem aos clientes um endereço estável para um conjunto de Pods. Isso é especialmente útil quando a demanda varia: um Deployment pode adicionar ou remover réplicas enquanto a identidade do Service permanece a mesma.

Neste laboratório, cada backend retorna o hostname do próprio Pod. Você aumentará a escala de duas para quatro réplicas, enviará requisições independentes por meio de um único Service e verá respostas de vários Pods. Depois, reduzirá novamente para duas réplicas e observará o Deployment e o EndpointSlice convergirem para o novo estado desejado.

Este é um escalonamento horizontal manual. O escalonamento automático com HPA depende de solicitações de recursos, métricas e uma política de controle; por isso, deve ser estudado depois que o comportamento manual das réplicas estiver totalmente compreendido.

Criar uma aplicação replicada observável

Inicialização do ambiente: Este laboratório inicia um cluster Kubernetes completo para você. A configuração do plano de controle, do nó e dos componentes de rede normalmente leva 2–3 minutos. Aguarde pacientemente até que o ambiente termine de carregar antes de começar.

Nesta etapa, você criará dois backends que revelam qual Pod processou cada requisição. Assim, a distribuição do tráfego pelo Service fica visível, em vez de permanecer apenas como um conceito abstrato.

O primeiro comando usa cd para entrar no workspace preparado. O comando seguinte usa um here-document: cat <<'EOF' > hostname-web.yaml grava todas as linhas seguintes, até o EOF de fechamento, no arquivo YAML, substituindo qualquer conteúdo anterior.

cd /home/labex/project/scale-lab
cat <<'EOF' > hostname-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostname-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hostname-web
  template:
    metadata:
      labels:
        app: hostname-web
    spec:
      containers:
        - name: web
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command: ["sh", "-c"]
          args:
            - mkdir -p /www; hostname > /www/index.html; exec httpd -f -p 8080 -h /www
          ports:
            - name: http
              containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: hostname-web
spec:
  selector:
    app: hostname-web
  ports:
    - name: http
      port: 80
      targetPort: http
EOF
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web

Depois do EOF de fechamento, kubectl apply -f envia todos os objetos do arquivo ao servidor da API. rollout status aguarda até 60 segundos pelos dois Pods desejados, enquanto -l app=hostname-web lista apenas os Pods que possuem esse rótulo.

Dentro do comando do contêiner, os pontos e vírgulas executam as ações em sequência: criam /www, redirecionam o hostname para index.html e usam exec para tornar o servidor web o processo principal do contêiner. Os nomes dos dois Pods são diferentes, portanto cada backend retorna um valor de página distinto.

Estabelecer os backends de referência

Nesta etapa, você relacionará a quantidade de réplicas do Deployment ao número de backends prontos do Service.

Consulte três visões relacionadas. get deployment mostra as réplicas desejadas e prontas; -l seleciona os Pods da aplicação e -o wide acrescenta as colunas de IP e nó; o seletor de rótulo final encontra o EndpointSlice criado para este Service:

kubectl get deployment hostname-web
kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web

O Deployment exibirá 2/2, e o EndpointSlice mostrará dois endereços. Faça várias requisições ao Service usando um único Pod cliente de longa duração.

kubectl run cria um Pod independente porque --restart=Never foi definido. O separador -- encerra as opções do kubectl; sleep 3600 é o comando do contêiner que o mantém em execução. O loop for usa seq 1 6 para gerar seis iterações, e kubectl exec POD -- COMMAND executa wget dentro do cliente a cada vez:

kubectl run load-client --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/load-client --timeout=30s
for i in $(seq 1 6); do kubectl exec load-client -- wget -qO- http://hostname-web; done

Cada resposta será o nome de um Pod. Em uma amostra curta, você poderá ver um ou ambos os nomes; a distribuição do Service não garante uma ordem estritamente alternada.

Aumentar a escala de forma declarativa

Nesta etapa, você alterará o estado desejado salvo de duas para quatro réplicas. Editar o manifesto mantém o arquivo e o objeto em execução consistentes.

sed -i 's/old/new/' file substitui diretamente em um arquivo o texto correspondente. Em seguida, grep -n procura por replicas: e exibe o número da linha, oferecendo uma verificação rápida antes de aplicar a alteração:

cd /home/labex/project/scale-lab
sed -i 's/replicas: 2/replicas: 4/' hostname-web.yaml
grep -n 'replicas:' hostname-web.yaml
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get deployment hostname-web

O Deployment deverá mostrar 4/4. O controlador criou mais dois Pods porque o estado atual estava abaixo do novo estado desejado.

Observar a expansão do conjunto de backends do Service

Nesta etapa, você verificará que o Service, que não foi alterado, descobre automaticamente os novos Pods.

O comando do EndpointSlice usa JSONPath porque a tabela padrão pode abreviar alguns detalhes. range repete o modelo para cada endpoint; cada repetição exibe o primeiro endereço, a palavra literal ready, o valor de prontidão e uma nova linha. A barra invertida continua um único comando do shell em várias linhas de exibição:

kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'

Agora existem quatro endereços prontos. Você não editou o Service: o seletor dele continuou correspondendo a todos os Pods prontos com app=hostname-web.

Compare o IP estável do Service com o conjunto ampliado de backends:

kubectl get service hostname-web -o wide

O ClusterIP permanece estável, enquanto os membros do EndpointSlice são alterados.

Observar requisições chegando a vários Pods

Nesta etapa, você enviará requisições HTTP independentes por meio de um único Service e resumirá quais hostnames de Pods respondem.

Primeiro, rm -f remove um arquivo de resultados antigo, caso ele exista; -f também faz com que a ausência do arquivo não seja um problema. O loop envia 20 requisições. >> acrescenta cada hostname ao arquivo, sem substituir os resultados anteriores. Por fim, o pipe envia as linhas ordenadas para uniq -c, que agrupa duplicatas adjacentes e prefixa cada hostname com sua contagem:

rm -f /tmp/hostname-responses.txt
for i in $(seq 1 20); do
  kubectl exec load-client -- wget -qO- http://hostname-web >> /tmp/hostname-responses.txt
done
sort /tmp/hostname-responses.txt | uniq -c

Você deverá ver mais de um hostname. As contagens podem ser desiguais, e uma execução curta não precisa necessariamente alcançar os quatro backends. O roteamento do Service no Kubernetes distribui conexões, mas não garante uma sequência perfeitamente uniforme ou ordenada.

Confirme quantos backends exclusivos foram alcançados pela sua amostra. sort -u mantém uma cópia de cada hostname; o pipe envia essas linhas para wc -l, que conta as linhas:

sort -u /tmp/hostname-responses.txt | wc -l

Um valor maior que um é uma evidência direta de que o endereço estável do Service roteou requisições para vários Pods.

Reduzir a escala de forma imperativa

Nesta etapa, você usará kubectl scale para fazer um ajuste rápido no Deployment em execução, reduzindo-o de quatro réplicas para duas.

kubectl scale altera imediatamente a quantidade desejada de réplicas do Deployment em execução. --replicas=2 informa a nova quantidade; esse comando não edita o arquivo YAML:

kubectl scale deployment/hostname-web --replicas=2
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web

O Kubernetes encerrará dois Pods e manterá dois. Aguarde até que o conjunto de backends do Service também convirja.

Este loop de verificação limitado tenta no máximo 30 vezes. A cada passagem, a contagem de endpoints prontos é armazenada em count; jq filtra o JSON do EndpointSlice para obter os endpoints prontos e retorna o tamanho do array. [ "$count" -eq 2 ] é um teste numérico do shell, && break encerra o loop quando o teste é bem-sucedido e sleep 1 faz uma pausa antes da nova tentativa:

for i in $(seq 1 30); do
  count=$(kubectl get endpointslices -l kubernetes.io/service-name=hostname-web -o json | jq '[.items[].endpoints[] | select(.conditions.ready == true)] | length')
  [ "$count" -eq 2 ] && break
  sleep 1
done
echo "Ready backends: $count"

O Deployment em execução agora solicita duas réplicas, mas o arquivo ainda informa quatro. Essa diferença é intencional para a próxima etapa.

Reconciliar o manifesto e consultar o histórico do controlador

Nesta etapa, você fará o manifesto salvo corresponder ao estado atual de duas réplicas e relacionará as ações de escalonamento às evidências registradas pelo controlador.

Primeiro, compare os dois estados desejados. grep -n exibe a linha salva; JSONPath extrai apenas o campo .spec.replicas atual e acrescenta uma nova linha:

grep -n 'replicas:' /home/labex/project/scale-lab/hostname-web.yaml
kubectl get deployment hostname-web -o jsonpath='Live replicas: {.spec.replicas}{"\n"}'

Altere o arquivo de quatro para duas réplicas e aplique-o:

sed -i 's/replicas: 4/replicas: 2/' /home/labex/project/scale-lab/hostname-web.yaml
kubectl apply -f /home/labex/project/scale-lab/hostname-web.yaml

Como o estado atual já possui duas réplicas, esta aplicação não deverá criar mais Pods. Consulte os eventos do Deployment. O pipe passa toda a saída de describe para sed -n; /Events:/,$p significa “imprimir desde a linha que contém Events: até o final”:

kubectl describe deployment hostname-web | sed -n '/Events:/,$p'

Procure mensagens ScalingReplicaSet que indiquem decisões de aumento e redução da escala. Por fim, remova o cliente temporário. --ignore-not-found faz com que a limpeza seja bem-sucedida mesmo que o Pod já tenha desaparecido:

kubectl delete pod load-client --ignore-not-found

O escalonamento manual altera a quantidade desejada de réplicas; o controlador do Deployment cria ou encerra Pods, e o Service acompanha automaticamente os membros prontos. Manter o manifesto sincronizado evita que um kubectl apply posterior restaure inesperadamente uma quantidade antiga.

Resumo

Você aumentou manualmente um Deployment de duas para quatro réplicas e depois voltou a duas. Observou o controlador reconciliar os Pods, verificou que os membros do EndpointSlice acompanhavam os backends prontos sem nenhuma alteração no Service e reuniu evidências diretas de que um único endereço do Service roteou requisições independentes para vários Pods.

O modelo mental essencial é: a quantidade de réplicas representa o estado desejado, o controlador do Deployment reconcilia os Pods reais e o Service acompanha os backends prontos que correspondem aos rótulos. Arquivos declarativos devem ser reconciliados com alterações deliberadas no ambiente em execução, para que futuras aplicações permaneçam previsíveis.