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.


