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\/\(.*\)

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
2 - Digite no seu terminal
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:
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:
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
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
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:
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.