Canary Deployments no Kubernetes utilizando um CRD (desenvolvido por mim)

Realizar o deploy de novas versões de aplicações é uma parte crítica do ciclo de vida de desenvolvimento de software e, dependendo a criticidade de uma aplicação, isso se torna mais complicado. Por padrão, o Kubernetes, utiliza a estratégia de atualização chamada rolling update, que garante a disponibilidade de uma aplicação. Isso é ótimo, mas em alguns cenários, você pode querer ter mais controle sobre como ocorrem as atualizações de uma aplicação. Por exemplo, você quiser expor gradualmente uma nova versão para um pequeno grupo de usuários, monitorar seu desempenho por algum tempo e só então liberá-la completamente para todos usuários? Aqui que o Canary Deployment começa a fazer sentido. Embora ferramentas como o Istio possuem meios que permitem o desvio de tráfego e também gerenciar todo o ciclo de vida de um canary deployment, ajustar pesos do tráfego ou ainda promover uma versão canary para stable, realizar isso de forma manual não é tão simples e pode exigir até criação de scripts complexos, que podem não garantir o perfeito funcionamento das coisas. Unido ao desejo de me desafiar e querer muito desenvolver um CRD, coloquei a mão e aqui estamos. Criei o CRD em questão junto a um controlador gerenciá-lo. Ele foi construído utilizando Go Lang e feito especificamente para gerenciar uma ou mais aplicações, permitindo testar, em um cluster Kubernetes, uma versão canary sem afetar totalmente a versão stable. Apresentando o Custom Resource Definition (CRD) O projeto em questão está hospedado nesse repositório do GitHub. Trata-se de um CRD que nomeei CanaryDeployment. Esse CRD junto ao controlador, permite que, ao invés de gerenciarmos manualmente versões de uma aplicação, tudo ocorra de forma automática. Esse projeto foi baseado na proposta do Argo Rollouts e após algumas discussões técnicas com amigos, um pouco de estudo e também experiências obtidas no dia a dia. Importante frisar que ele funciona baseado no Virtual Services do Istio, logo, ter o Istio instalado no seu cluster Kubernetes, é um requisito. Para instalar o Istio, você pode se basear em um desses links: https://istio.io/latest/docs/setup/getting-started/ https://istio.io/latest/docs/setup/install/istioctl/ https://istio.io/latest/docs/setup/install/helm/ Certifique-se de ter instalado também o kubectl. Você pode instalar seguindo esse link 1 - Instalando o CRD e o controlador kubectl apply -f https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/dist/install.yaml 2 - Testando a instalação 1 - Digite no seu terminal kubectl api-resources | grep canarydeployments Você deverá ver algo como: 2 - Digite no seu terminal kubectl get deployments -n canary-deployment Você deverá ver algo como: 3 - Instalando o Nginx para utilizar como exemplo Para maior praticidade, utilizarei como exemplo o namespace default. 1 - Digite no seu terminal kubectl apply -f https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/config/samples/sample-deploy/deploy-v1.yaml -n default Esse manifesto instalará o nginx 1.26 no seu cluster junto a um service, um destination rule, um virtual service e um configmap. 2 - Digite no seu terminal kubectl get deploy nginx -n default Você deverá ver algo como: 4 - Simulando as requisições Vamos enviar algumas requisições para nossa aplicação e mais abaixo, veremos a "mágica" acontecendo. Para isso vamos subir um pod e através desse pod, iremos enviar várias requisições para testar o comportamento. Crie um arquivo chamado nginx-pod.yaml com o seguinte conteúdo: apiVersion: v1 kind: Pod metadata: name: nginx-curl spec: containers: - name: nginx-curl image: nginx:1.26-alpine imagePullPolicy: IfNotPresent ports: - containerPort: 80 1 - Digite no seu terminal kubectl apply -f nginx-pod.yaml -n default kubectl exec --stdin --tty nginx-curl -- /bin/sh 2 -Agora, dentro do container, digite: while true;do curl -s "nginx-svc:80/not-found" \ | grep "nginx/" \ | sed 's/^.*nginx\/\(.*\)

May 3, 2025 - 05:28
 0
Canary Deployments no Kubernetes utilizando um CRD (desenvolvido por mim)

Realizar o deploy de novas versões de aplicações é uma parte crítica do ciclo de vida de desenvolvimento de software e, dependendo a criticidade de uma aplicação, isso se torna mais complicado.

Por padrão, o Kubernetes, utiliza a estratégia de atualização chamada rolling update, que garante a disponibilidade de uma aplicação. Isso é ótimo, mas em alguns cenários, você pode querer ter mais controle sobre como ocorrem as atualizações de uma aplicação. Por exemplo, você quiser expor gradualmente uma nova versão para um pequeno grupo de usuários, monitorar seu desempenho por algum tempo e só então liberá-la completamente para todos usuários? Aqui que o Canary Deployment começa a fazer sentido.

Embora ferramentas como o Istio possuem meios que permitem o desvio de tráfego e também gerenciar todo o ciclo de vida de um canary deployment, ajustar pesos do tráfego ou ainda promover uma versão canary para stable, realizar isso de forma manual não é tão simples e pode exigir até criação de scripts complexos, que podem não garantir o perfeito funcionamento das coisas.

Unido ao desejo de me desafiar e querer muito desenvolver um CRD, coloquei a mão e aqui estamos. Criei o CRD em questão junto a um controlador gerenciá-lo. Ele foi construído utilizando Go Lang e feito especificamente para gerenciar uma ou mais aplicações, permitindo testar, em um cluster Kubernetes, uma versão canary sem afetar totalmente a versão stable.

Apresentando o Custom Resource Definition (CRD)

O projeto em questão está hospedado nesse repositório do GitHub. Trata-se de um CRD que nomeei CanaryDeployment. Esse CRD junto ao controlador, permite que, ao invés de gerenciarmos manualmente versões de uma aplicação, tudo ocorra de forma automática.

Esse projeto foi baseado na proposta do Argo Rollouts e após algumas discussões técnicas com amigos, um pouco de estudo e também experiências obtidas no dia a dia.

Importante frisar que ele funciona baseado no Virtual Services do Istio, logo, ter o Istio instalado no seu cluster Kubernetes, é um requisito. Para instalar o Istio, você pode se basear em um desses links:

Certifique-se de ter instalado também o kubectl. Você pode instalar seguindo esse link

1 - Instalando o CRD e o controlador

kubectl apply -f https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/dist/install.yaml

2 - Testando a instalação

1 - Digite no seu terminal

kubectl api-resources | grep canarydeployments

Você deverá ver algo como:
kubectl api-resources | grep canarydeployments

2 - Digite no seu terminal

kubectl get deployments  -n canary-deployment

Você deverá ver algo como:
kubectl get deployments  -n canary-deployment

3 - Instalando o Nginx para utilizar como exemplo

Para maior praticidade, utilizarei como exemplo o namespace default.
1 - Digite no seu terminal

kubectl apply -f https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/config/samples/sample-deploy/deploy-v1.yaml -n default

Esse manifesto instalará o nginx 1.26 no seu cluster junto a um service, um destination rule, um virtual service e um configmap.

2 - Digite no seu terminal

kubectl get deploy nginx -n default

Você deverá ver algo como:

kubectl get deploy nginx -n default

4 - Simulando as requisições

Vamos enviar algumas requisições para nossa aplicação e mais abaixo, veremos a "mágica" acontecendo. Para isso vamos subir um pod e através desse pod, iremos enviar várias requisições para testar o comportamento.

Crie um arquivo chamado nginx-pod.yaml com o seguinte conteúdo:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-curl
spec:
  containers:
  - name: nginx-curl
    image: nginx:1.26-alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80

1 - Digite no seu terminal

kubectl apply -f nginx-pod.yaml -n default

kubectl exec --stdin --tty nginx-curl -- /bin/sh

2 -Agora, dentro do container, digite:

while true;do curl -s "nginx-svc:80/not-found" \
| grep  "nginx/" \
| sed  's/^.*nginx\/\(.*\)<.*$/\1/' && sleep 3; done

Deixe esse terminal aberto enquanto seguimos com o post.

5 - Utilizado o CRD

1 - Digite no seu terminal

kubectl apply -f 
https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/config/samples/apps_v1alpha1_canarydeployment.yaml -n default

2 - Digite no seu terminal

kubectl get canarydeployment canarydeployment-1 -n default

Você deverá ver algo como:

kubectl get canarydeployment canarydeployment-1 -n default

Se tudo correu bem até aqui, você terá 2 deployments no seu cluster, um nginx na versão 1.26-alpine e um nginx-canary na versão 1.27-alpine. Confira digitando no seu terminal:

kubectl get deployments -n default

kubectl get deployments -n default

6 - Testando as requisições

Retorne ao terminal que você utilizou no tópico 4 e observe a saída. Você verá algumas saídas 1.27.N e várias 1.26.N e de acordo com o tempo as saídas 1.27.N irão aparecer mais vezes. Isso demonstra que o controlador funcionou e alterou o virtual service para ir modificando o tráfego entre a versão stable e a versão canary.

7 - Analisando o conteúdo do manifesto de canarydeployment

Imagem canarydeployment

  • Nas linhas 1 e 2, declaramos que iremos utilizar a apiVersion: mox.app.br/v1alpha1 e o kind: CanaryDeployment do CRD, tema desse post.

  • Na linha 6 declaramos que o nome da aplicação (deployment) que utilizaremos para aplicar a estratégia de canary deployment.
    As linhas 8 e 9, respectivamente, tratam da versão atual (stable) e a nova versão (canary).

  • Na linha 11 declaramos o nome do VirtualService. Isso será utilizado pelo controlador gerenciar os ”pesos” entre as versões stable e canary.

  • Na linha 12 declaramos configMapNames. O controlador irá utilizar para configurar o configmap a ser utilizado pela deployment na versão canary. Você pode declarar mais de um configmap.

  • Na linha 15 declaramos secretNames. O controlador irá utilizar para configurar a secret a ser utilizada pela versão canary. Você pode declarar mais de uma secret.

  • Na linha 17 a 27 declaramos, respectivamente, o peso e o tempo que desejamos que cada peso seja mantido (em segundos, no caso).

Abaixo um pequeno resumo de cada passo declarado no manifesto:

1 - 10% das requisições irão para a versão canary e 90%  das requisições irão para a versão stable, durante 60 segundos.

2 - 25% das requisições irão para a versão canary e 75%  das requisições irão para a versão stable, durante 2 minutos.

3 - 50% das requisições irão para a versão canary e 50%  das requisições irão para a versão stable, durante 5 minutos.

4 - 100% das requisições irão para a versão canary ou seja, a versão canary se torna a versão stable.

Observação.
É possível declarar as pausas em segundos, minutos e horas (seconds, minutes e hours)

Enquanto a versão canary existir, você pode usar os comandos abaixo para visualizar as alterações:

kubectl get vs nginx -n default -o yaml
kubectl get deploy --l="run-type: stable" -n default
kubectl get deploy --l="run-type: canary" -n default
kubectl get canarydeployment canarydeployment-1 -n default -o yaml

Abaixo, podemos visualizar a estrutura do manifesto de Canary Deployment enquanto o controlador estiver gerenciando as versões stable e canary:

manifesto de Canary Deployment

8 - Limpando a bagunça
Caso deseje remover o CRD, execute:

kubectl delete -f https://raw.githubusercontent.com/oliveiraxavier/canary-crd/refs/heads/master/dist/install.yaml

9 - That's all folks

Após a execução de todos os passos(steps) que definimos no nosso manifesto de exemplo, a versão canary se tornará a versão stable e o que era a versão stable, será removida do cluster.

Claro que, ao menos nesse momento, o projeto é algo bem simples. O que eu almejava era aplicar os conhecimentos que adquiri recentemente em Golang e deu certo e postar aqui, penso ser uma forma interessante de compartilhar conhecimento.

Agradecimentos aos gigantes Lucas Lobo, Edson Rocha, Adriano Ohana, Pedro Adalberto, Guilherme Lacerda e Thiago Pereira. Todos que me ajudaram de algum modo para chegar até a escrita desse post.