Atualizar e reverter aplicações

KubernetesBeginner
Pratique Agora

Introdução

Agora você já sabe implantar, expor e dimensionar uma aplicação. A próxima questão operacional é: como substituir a versão da aplicação sem deixar todo o serviço indisponível?

Um Deployment do Kubernetes resolve isso com uma atualização contínua. Quando o modelo de Pod é alterado, o Deployment cria um novo ReplicaSet, inicia gradualmente novos Pods e remove os antigos apenas quando os substitutos ficam disponíveis. Durante todo o processo, o Service mantém o mesmo endereço estável.

Neste laboratório, você moverá uma aplicação NGINX de uma imagem fixada para outra, relacionará as revisões do Deployment aos ReplicaSets e Pods, provocará deliberadamente uma publicação com erro e fará a reversão para a última revisão saudável. Você também manterá o manifesto salvo consistente com o estado ativo recuperado — uma prática importante para evitar que um kubectl apply posterior reintroduza a falha.

Ler o ambiente inicial

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ê confirmará a conexão com o cluster e preparará um cliente HTTP dentro do cluster.

O ambiente deste curso já contém um cluster Kubernetes em execução, portanto não é necessário criar um. Primeiro, verifique para onde o kubectl enviará os comandos. config current-context identifica a conexão ativa, get node consulta a saúde do nó e version informa as versões do cliente local e do servidor de API acessível:

kubectl config current-context
kubectl get node
kubectl version

O contexto e o nó são chamados labex-v135, o nó está Ready e o servidor informa o Kubernetes v1.35.x. Um contexto é a seleção no kubeconfig que conecta o kubectl a um determinado cluster, usuário e namespace padrão.

Crie agora um pequeno Pod cliente. Você o usará mais tarde para acessar a aplicação por meio do Service. kubectl run cria um Pod; --image escolhe o BusyBox, --restart=Never mantém o recurso como um Pod independente e o separador -- introduz o comando de longa duração sleep 3600. Em seguida, kubectl wait aguarda por no máximo 30 segundos pela condição Ready:

kubectl run release-client --image=busybox:1.36 --image-pull-policy=IfNotPresent \
  --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/release-client --timeout=30s

Assim, o cliente fica separado dos Pods web, reproduzindo melhor a forma como uma carga de trabalho acessa outra dentro de um cluster.

Implantar a base estável

Nesta etapa, você estabelecerá uma revisão conhecida e saudável da aplicação antes de fazer qualquer alteração.

Antes de praticar uma atualização, estabeleça uma revisão confiável. Crie um manifesto contendo um Deployment com três réplicas e um Service ClusterIP estável.

cd entra no diretório de trabalho preparado. A sintaxe de documento incorporado cat <<'EOF' > release-web.yaml grava no arquivo tudo o que aparece até o EOF de fechamento. A linha --- separa dois objetos do Kubernetes no mesmo arquivo YAML:

cd /home/labex/project/update-lab
cat <<'EOF' > release-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-web
  annotations:
    kubernetes.io/change-cause: "Initial release: nginx 1.26"
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: release-web
  template:
    metadata:
      labels:
        app: release-web
    spec:
      containers:
        - name: nginx
          image: nginx:1.26-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: release-web
spec:
  selector:
    app: release-web
  ports:
    - name: http
      port: 80
      targetPort: http
EOF
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment,service,pods -l app=release-web

O Deployment gerencia os Pods, enquanto o Service os seleciona de forma independente por meio do rótulo. Atualizar o Deployment não alterará o endereço do Service.

Confirme a base a partir do cliente. Em kubectl exec POD -- COMMAND, -- separa os argumentos do kubectl do comando executado dentro do contêiner. wget -qO- faz a requisição silenciosamente e envia a resposta para a saída padrão; o pipe encaminha essa resposta para head, que exibe apenas o início:

kubectl exec release-client -- wget -qO- http://release-web | head

Publicar uma nova imagem de forma declarativa

Nesta etapa, você alterará o modelo de Pod salvo e permitirá que o Deployment execute uma atualização contínua.

Um Deployment inicia uma atualização quando seu modelo de Pod é alterado. Como a imagem está dentro desse modelo, modificá-la cria uma nova revisão e um novo ReplicaSet.

Atualize a imagem e também a descrição legível da alteração no manifesto salvo. Cada substituição sed -i 's/old/new/' file edita o arquivo diretamente. Em seguida, grep -nE exibe os números das linhas que contêm qualquer um dos termos da expressão regular estendida (change-cause ou image:), permitindo conferir as duas alterações antes de aplicá-las:

cd /home/labex/project/update-lab
sed -i 's/Initial release: nginx 1.26/Release nginx 1.27/' release-web.yaml
sed -i 's/nginx:1.26-alpine/nginx:1.27-alpine/' release-web.yaml
grep -nE 'change-cause|image:' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s

kubectl apply altera o estado desejado. Em seguida, o controlador do Deployment trabalha de forma assíncrona até que as três réplicas atualizadas estejam disponíveis. Para obter uma tabela concentrada dos Pods, -l seleciona o rótulo da aplicação e -o custom-columns associa cabeçalhos a caminhos de campos dos objetos:

kubectl get deployment release-web
kubectl get pods -l app=release-web \
  -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,READY:.status.containerStatuses[0].ready'

As três réplicas atuais usam a nova imagem. Após a conclusão da atualização, talvez você ainda veja brevemente um Pod com a imagem antiga no estado Terminating. Esse Pod já não é uma réplica desejada; o Kubernetes está apenas concluindo seu encerramento gracioso em segundo plano.

Relacionar revisões, ReplicaSets e Pods

Nesta etapa, você inspecionará os objetos por trás da atualização bem-sucedida e confirmará que o Service continuou disponível.

A atualização terminou, mas entender o que mudou é mais útil do que ver apenas “sucesso”. kubectl rollout history consulta as revisões armazenadas do Deployment. O comando get replicasets a seguir usa -l para manter apenas os objetos relacionados e custom-columns para comparar suas contagens de réplicas e imagens:

kubectl rollout history deployment/release-web
kubectl get replicasets -l app=release-web \
  -o custom-columns='NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas,READY:.status.readyReplicas,IMAGE:.spec.template.spec.containers[0].image'

Você deverá ver dois ReplicaSets. O novo possui três Pods; o antigo permanece com zero réplicas para que seu modelo de Pod fique disponível para uma reversão. O nome de um ReplicaSet inclui um hash derivado do modelo de Pod, por isso os nomes dos Pods mudaram quando a imagem foi alterada.

Verifique a imagem ativa e a quantidade de backends do Service. As duas expressões JSONPath usam range para repetir um modelo de saída sobre uma lista. Os espaços literais e {"\n"} produzem uma linha legível para cada Pod ou endpoint:

kubectl get pods -l app=release-web \
  -o jsonpath='{range .items[*]}{.metadata.name}{"  "}{.spec.containers[0].image}{"\n"}{end}'
kubectl get endpointslices -l kubernetes.io/service-name=release-web \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
kubectl exec release-client -- wget -qO- http://release-web | head

A revisão mudou, mas o Service continua com três backends prontos e o mesmo nome estável.

Diagnosticar uma publicação com erro

Nesta etapa, você introduzirá um erro controlado na imagem e usará os eventos do Pod para identificar por que a atualização não consegue ser concluída.

Agora simule um erro comum de publicação: uma tag de imagem de contêiner que não existe. Mantenha essa falha no mesmo Deployment para que o mecanismo de atualização demonstre uma importante propriedade de segurança. Os dois comandos sed -i editam diretamente o texto da anotação e da imagem. Normalmente, o tempo limite esperado da atualização retornaria um status de falha; || true permite deliberadamente que a lição continue:

cd /home/labex/project/update-lab
sed -i 's/Release nginx 1.27/Broken release: missing image/' release-web.yaml
sed -i 's/nginx:1.27-alpine/nginx:does-not-exist-course/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=20s || true
kubectl get deployment release-web
kubectl get pods -l app=release-web

A atualização não termina porque um novo Pod não consegue obter sua imagem. O || true permite que a lição continue após o tempo limite esperado.

Encontre o Pod com falha e inspecione seus eventos. $(...) armazena a saída do comando em BAD_POD. O primeiro pipe envia o JSON do Kubernetes para o jq; select(...) mantém o Pod que usa a imagem com erro e -r retorna seu nome como texto simples. Um segundo pipe para head -n1 mantém apenas um nome. Por fim, sed -n '/Events:/,$p' exibe a saída de describe desde Events: até o final:

BAD_POD=$(kubectl get pods -l app=release-web \
  -o json | jq -r '.items[] | select(.spec.containers[0].image == "nginx:does-not-exist-course") | .metadata.name' | head -n1)
echo "$BAD_POD"
kubectl describe pod "$BAD_POD" | sed -n '/Events:/,$p'
kubectl get replicasets -l app=release-web

Procure ErrImagePull ou ImagePullBackOff. Observe que o ReplicaSet saudável anterior ainda possui Pods disponíveis, portanto o Service pode continuar respondendo:

kubectl exec release-client -- wget -qO- http://release-web | head

Isso demonstra disponibilidade durante uma atualização malsucedida — não prova que a nova publicação funciona.

Reverter e reconciliar o manifesto

Nesta etapa, você restaurará a revisão saudável anterior e corrigirá o manifesto salvo.

O Deployment ativo mantém um histórico de revisões, permitindo que o Kubernetes restaure o modelo de Pod anterior. rollout undo solicita ao controlador do Deployment que reutilize a revisão anterior; rollout status aguarda essa recuperação, e custom-columns exibe a imagem recuperada e a quantidade de réplicas prontas:

kubectl rollout history deployment/release-web
kubectl rollout undo deployment/release-web
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment release-web \
  -o custom-columns='NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image,READY:.status.readyReplicas'

A imagem voltou a estar saudável e as três réplicas estão prontas. rollout undo cria uma nova revisão a partir de um modelo de Pod antigo; ele não retrocede o contador de revisões.

Ainda falta uma tarefa. O arquivo YAML continua contendo a imagem com erro, portanto um kubectl apply futuro quebraria o Deployment novamente. Reconcilie o estado desejado salvo com o estado ativo recuperado. As substituições corrigem o arquivo, apply o sincroniza e o comando final de histórico confirma a sequência resultante de revisões:

cd /home/labex/project/update-lab
sed -i 's/Broken release: missing image/Rollback to nginx 1.27/' release-web.yaml
sed -i 's/nginx:does-not-exist-course/nginx:1.27-alpine/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
grep -nE 'change-cause|image:' release-web.yaml
kubectl rollout history deployment/release-web

Agora o cluster e o arquivo descrevem a mesma publicação saudável.

Escolher um limite de atualização mais seguro

Nesta etapa, você tornará explícitos os limites de capacidade e disponibilidade da atualização do Deployment.

As configurações da estratégia do Deployment controlam a capacidade temporária permitida durante futuras atualizações:

  • maxUnavailable indica quantas réplicas desejadas podem ficar indisponíveis durante uma atualização.
  • maxSurge indica quantos Pods extras podem existir temporariamente acima da quantidade desejada de réplicas.

Para esta aplicação pequena, com três réplicas, exija que as três réplicas desejadas permaneçam disponíveis e permita um Pod extra.

O comando sed usa um endereço, /type: RollingUpdate/, para localizar a linha da estratégia. A ação a\ acrescenta o YAML das linhas seguintes, com a indentação necessária. Depois da aplicação, o JSONPath extrai os dois valores da estratégia para que você possa verificar a alteração sem examinar o objeto inteiro:

cd /home/labex/project/update-lab
sed -i '/type: RollingUpdate/a\    rollingUpdate:\n      maxUnavailable: 0\n      maxSurge: 1' release-web.yaml
kubectl apply -f release-web.yaml
kubectl get deployment release-web \
  -o jsonpath='maxUnavailable={.spec.strategy.rollingUpdate.maxUnavailable}{"\n"}maxSurge={.spec.strategy.rollingUpdate.maxSurge}{"\n"}'
kubectl get deployment release-web

Alterar apenas os campos da estratégia não substitui os Pods, pois o modelo de Pod não foi modificado. Em uma futura atualização da imagem, o Kubernetes poderá criar um Pod extra e não deverá reduzir intencionalmente o número de réplicas disponíveis para menos de três.

Essa configuração prioriza a disponibilidade, mas exige capacidade livre no cluster. Não existe um valor universalmente ideal: aplicações maiores costumam usar porcentagens, e as escolhas reais dependem da capacidade disponível, do tempo de inicialização e da interrupção aceitável.

Resumo

Você acompanhou uma publicação de aplicação durante todo o seu ciclo operacional:

  • estabeleceu uma base saudável com um Deployment e um Service estável;
  • alterou uma imagem fixada de forma declarativa e aguardou a atualização;
  • relacionou as revisões do Deployment aos ReplicaSets antigo e novo;
  • diagnosticou uma falha ao obter a imagem enquanto a publicação anterior continuava atendendo às requisições;
  • reverteu para o último modelo de Pod saudável;
  • reconciliou o manifesto para que uma aplicação posterior não restaure a imagem inválida; e
  • configurou limites explícitos de disponibilidade e capacidade adicional para futuras atualizações.

A ideia central é que um Deployment gerencia o estado desejado ao longo do tempo. Uma atualização não é simplesmente editar uma imagem: é uma transição controlada entre ReplicaSets, que deve ser observada, verificada e, se necessário, revertida.