quarta-feira, 15 de janeiro de 2020

Como fazer rollback de uma aplicação que roda no kubernetes

Como fazer rollback de deployments no kubernetes

Sempre falo bastante sobre estrategias de CI/CD, uso do Auto DevOps do gitlab e como o kubernetes é legal. Mas, o que muito ocorre e pouco se fala, é: O que fazer quando da ruim e preciso fazer rollback rapidão?
Eu achei um artigo bem detalhado e que explica tudo muito bem, mas para aqueles que pulam para a flag verde no stackoverflow, vou resumir colocando os passos para operar um rollback de um deployment no kubernetes.

Bora lá

Vou partir da premissa que você já usa o kubectx e o kubens (por favor!) e também que nosso deployment possui o nome "app".

Seguem os passos:

# "acessar" o cluster
kubectx [cluster]

# "acessar" o namespace
kubens [namespace]

# verificar os deployments do namespace 
kubectl get deployments

# verifica a revisão (versão) do deployment que quero operar um rollback
kubectl describe deployment/app | grep deployment.kubernetes.io/revision:

# verificar o histórico de revisões (versões) do deployment que desejo fazer rollback
kubectl rollout history deployment/app

# faço rollback pra revisão que desejo
kubectl rollout undo deployment/app --to-revision=25

# acompanho e aguardo os pods e o deployment ficarem disponíveis
kubectl get deployments,pods

# verifico se a revisão foi alterada (vai incrementar)
kubectl describe deployment/app | grep deployment.kubernetes.io/revision:


terça-feira, 7 de janeiro de 2020

Configurando auto escalonamento usando gitlab com Auto DevOps

Como usar o helm requests e limits com Auto DevOps do Gitlab

Já falei bastante aqui no blog sobre o Auto DevOps. O que poderíamos fazer para permitir que nossas aplicações (que já fazem uso de todas as praticas dos 12 fatores, rodam na nuvem em clusters de orquestração de container, possuem capacidade de tolerância a falha) possam chegar ao próximo passo e escalar de forma inteligente ainda fazendo uso do Auto DevOps?
- Agora é o momento de fazer com que nossa aplicação use apenas o recurso que ela realmente necessita e deixe o resto para quem quiser usar ou para eventuais sobrecargas que exijam "auto escalonamento" :)



Para aqueles que usam o auto DevOps do gitlab e já perceberam que nem tudo é configurável, apresento-lhes a variável HELM_UPGRADE_EXTRA_ARGS do .gitlab-ci.yaml. Essa varável nos permite apontar um arquivo para modificar outros valores dos nossos arquivos yaml gerados pelo Auto DevOps. E um desses valores (que vou falar aqui), é o resources.

Para usa-la, basta inclui-la em variables no .gitlab-ci.yaml:
variables:
  - HELM_UPGRADE_EXTRA_ARGS: "--values helm-values.yaml"
*percebam que passei o nome de um arquivo depois de "--values"

Certo, seguem os passos:
- Junto ao arquivo .gitlab-ci.yaml, vamos criar um arquivo chamado: helm-values.yaml
- Dentro do arquivo helm-values.yaml, vamos incluir as informações de resources (recurso que minha aplicação tem disponível para trabalhar e o limite acima disso).

Dentro do helm-values.yaml:
resources:
  requests:
    memory: "150Mi"
    cpu: "50m"
  limits:
    memory: "195Mi"
    cpu: "65m"
*para encher linguiça, vou apenas deixar aqui a explicação e sugiro que você leia antes de brincar com requests e limits: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container

Outra forma de configurar o upgrade helm

É possível usar um nome e path padrão. Se usado, o gitlab entende e não precisamos colocar a variável no .gitlab-ci.yaml. para isso, basta criar o arquivo helm: .gitlab/auto-deploy-values.yaml com o mesmo conteúdo do a helm-values.yaml mencionado acima.

Variável criada e arquivo helm preparado

Faremos o deploy via Auto DevOps e dessa forma, possibilitamos nosso cluster escalar nossa aplicação caso ela atinga determinadas métricas (podendo crescer ou diminuir) de acordo com a demanda.

Para configurar o auto scale no seu objeto deployment, faça o seguinte:
- kubectx [nome do cluster]
- kubens [nome do namespace]
- kubectl get deployment
*aqui irá listar seus deployments
- kubectl autoscale deployment [nome do deployment] --min=1 --max=3 --cpu-percent=85
*min, max e cpu são apenas exemplos. Você pode configurar de acordo com sua necessidade.

É isso o/ flwss

Ref:. https://gitlab.com/gitlab-org/gitlab-foss/issues/65497

segunda-feira, 30 de dezembro de 2019

Solução para replicar dados entre fontes de dados

Replicar dados usando CDAP

Cask Data Application Platform, ou simplesmente CDAP, é uma plataforma que prove uma grande quantidade de recursos e ferramenta para Analise de Dados, Transformação ou no nosso caso, migração de dados entre diferentes Fontes de dados.

Primeiro acho importante mencionar como cheguei até o CDAP

Em uma bela manhã de sol...Não, pera! não estava sol.. ou estava? bem, não me recordo.
No inicio do ano (e hoje é dia 30/12, então posso escrever dessa forma), no meio de alguns projetos, me deparei com alguns problemas entre VPN para acessar os dados em um projeto, depois a necessidade de decompor em serviços preservando contextos de dados e os serviços possuírem seus próprios bancos de dados (para fins de uso adequado de recurso e scaling individual), até a necessidade desses dados serem atualizados frequentemente, de forma independente e em diferentes periodicidades.
No decorrer das pesquisas, encontramos uma solução rápida para atender nossa necessidade, sem muito trabalho ou necessidade de treinamento, foi o FDW (ou Foreign Data Wrapper) do PostgreSQL. E colhemos ótimos frutos, pois entregamos o que precisávamos e nosso maior problema, que era não usar banco via VPN, foi solucionado.

Fim. Ou não!

Não existe bala de prata e arquitetura nunca tem fim

Com o passar do tempo, o esforço com manutenção de uma aplicação fazendo sincronismo de dados, a constante necessidade de alteração das queries que buscam dados na origem, a eventual indisponibilidade ou falhas da aplicação que estava rodando em um cluster kubernetes, a limitação do FDW por diferentes fontes de dados, e até mesmo, a necessidade de interagir e evoluir essa replica de dados junto com outros times da empresa, nos fez questionar se não seria o momento de buscar algo mais adequado para replicar esses dados. E foi então, que o Gabriel Faraday, em suas leituras, encontrou o Data Fusion do Google, que parecia uma solução elegante para o problema e me falou sobre o que havia visto. Então tínhamos que buscar a certeza de que seria a solução adequada para nossos problemas.  
Enfim, fizemos uma reunião com um pessoal do Google que nos deram duas opções, uma como serviço (Data Fusion), outra através da implantação de uma VM (CDAP). Ambos são CDAP, mas o Data Fusion é a solução do Google, como serviço e como estávamos buscando testar e validar se para nós era realmente adequado, avaliando custo e o fato de ambos serem a mesma plataforma por trás, seguimos para o CDAP.

Por onde começar?

Bom, primeira coisa que fizemos, foi instalar o CDAP através de uma VM gerenciada pelo Google (na Google Cloud), pelo gerenciador de implantação. E caso você queira testar, use o gerenciador de implantação da Google Cloud que te da a maquina pronta para usar (você terá de habilitar o Cloud Deployment Manager V2 API para usar o gerenciador de implantação). Faça a busca por CDAP e implante uma instancia (pode ser a maquina default para iniciar).
Feito isso, usei um plugin de postgresql para alimentar um arquivo, coloquei no preview e "vualaaa", tudo acontece magicamente. Coloquei uma simples query para pegar os dados de uma ponta e jogar no arquivo, apenas arrastando duas caixinhas (uma de postgres e outra de file).

E depois?

Depois de implantar, você receberá no painel um endereço ip, usuário e senha. Acesse com esses dados e você será apresentado a um painel com alguns recursos. São eles: Namespace, Pipeline (draft e published), Hub e isso é tudo que você precisa entender para iniciar.
Namespace: É um agrupamento de pipeline. Pode ser nome de um departamento (caso vários usem, ou até ambiente). Ex: Produção
Pipeline: São os processos. Muito semelhante ao pipeline de CI/CD que usamos em nossas aplicações. Desde a preparação, coleta de dados em uma fonte de dados de origem, até transformação e inclusão dos dados transformados no destino. Um detalhe curioso é que nós trabalhamos com o rascunho (draft) e quando publicamos um pipeline, ele deve ser clonado caso você queira altera-lo, e a nova publicação (published) desse pipeline, gera uma nova versão - os antigos você pode simplesmente deletar.
Hub: No hub você encontra os plugins e drivers que você deve instalar e configurar para conseguir trabalhar com fontes de dados que não estão previamente configuradas - como Oracle ou PostgreSQL (geralmente tem o link do que você precisa). 

O que mais é possível fazer?

Pelo que explorei até agora, é possível criar pipelines programados (scheduled), instantâneos (real-time), é possível pegar de diferentes fontes, agrupar, transformar, executar qualquer query de banco, integrar com s3, gs, criar csv, conectar diferentes bancos e mais uma imensidão de coisas que vi, mas ainda não utilizei - é adequado afirmar que é uma solução muito elegante para qualquer time de BI.

Fico por aqui e deixo o convite para que você explore a ferramenta. Se sua necessidade gira em torno de migração, replica, transformação ou analise de dados, certamente CDAP vai te ajudar!

Abraços :) 

sexta-feira, 6 de dezembro de 2019

Definindo uma arquitetura focada em disponibilidade de dados

Uma arquitetura distribuída, altamente disponível e com foco na disponibilidade dos dados.

Vou utilizar a GCP (Google Cloud Platform) para essa proposta de arquitetura, mas poderia ser outro provider (desde que, obviamente, os componentes preservem as características dos que elenquei nessa proposta.

É fácil afirmar que, nenhuma arquitetura é 100% disponível, mesmo quando falamos em multi-cloud. Afinal, fatores externos podem gerar indisponibilidade e não temos como controlá-los, mas temos como minimizar as chances para termos problemas e gerar uma maior garantia de prevenção a falhas.

O modelo que vou apresentar, não é um modelo de missão crítica, nem deve ser considerado. Afinal, nem mesmo os providers como GCP, Azure ou AWS, garantem isso. 

Certo! vamos às armas oO'

Vou listar algumas ferramentas que vou utilizar nessa proposta, mas poderia ser outra, desde que tenha a proposta semelhante.

- Aplicações (cloud run, cloud function, kubernetes apps).
- Pub/Sub messaging - com disponibilidade GLOBAL - super importante.
- Bancos de dados (postgresql, mongodb, ...).
- Banco de cache (redis, memcached, ...).

O ponto mais importante aqui, é o pub/sub, por ser uma alternativa com disponibilidade GLOBAL. Ou seja, é aqui que vou garantir que receberei o dado gerado pelo usuário final independente da indisponibilidade da aplicação.

- A estratégia aqui, pode ser implementada de várias formas, vou apresentar apenas um modelo para exemplificar.

Tornando as aplicações mais disponíveis:

As aplicações, estejam onde estiverem, vamos buscar torná-las disponíveis entre diferentes zonas e regiões, usando essa solução que já expliquei como implementar e deploy em 2 regiões diferentes (sempre multi-zona), das aplicações Cloud run e Cloud function (por cobrarem por uso, não é um problema executar em regiões diferentes ou estarem duplicadas, você só vai pagar pelo processamento efetuado).

Tornando o banco de dados disponível:

Os bancos de dados, mongodb, postgresql, sqlserver o qualquer outro, podem estar onde você quiser, até mesmo em uma VM gerando snapshots para evitar dores de cabeça. O que vai tornar o dado disponível, é o banco de cache (redis ou memcached - destaco esses pela facilidade de subir instancias desses dentro do kubernetes).

A "mágica"

O segredo aqui, é a forma como as aplicações lidam com os dados. 
Basicamente nossas aplicações, independente de onde executam, vão gravar os dados em tópicos do pub/sub (pela garantia de disponibilidade) e vão ler os dados dos bancos de cache que estarão dentro do Kubernetes. 
Quando o usuário fizer uma operação para gravar um dado, a aplicação vai mandar para o pud/sub e utilizar um recurso assíncrono, para processamento dessa fila (pode ser hangfire, por exemplo) para enviar o dado da fila, para o banco de dados da aplicação (postgresql, sql server, mongodb ou outro) e esse mesmo poderia atualizar o banco de cache, mas isso pode ser feito no momento da leitura.
Vamos imaginar que agora o usuário faz uma requisição aos serviços e esses teriam de buscar o dados no banco de dados. No lugar de buscar no banco de dados, vai buscar no banco de cache e disparar uma thread para atualizar o banco de cache.
Dessa forma, estamos assegurando que a leitura está sempre na mesma região, independente se o banco de dados está distribuído em diferentes regiões ou está em uma maquina rodando apenas em uma zona.

Dica: Se quiser garantir mais disponibilidade entre regiões com seu banco de dados, opte por usar Cloud SQL da GCP, com banco MySql (ele possui recurso para replicas em diferentes regiões).

Algumas questões:

- Se o banco de dados estiver indisponível: Não tem problema, o dado estará disponível no pub/sub até que o banco esteja disponível, para receber o dado.
- Diferentes regiões, terão bancos de cache com dados diferentes. Mas isso não é um grande problema se você implementou o MCI como expliquei aqui, pois o Load Balancer vai cuidar de direcionar o usuário para o cluster mais próximo a ele e só vai ocorrer de cair em outro região, se o Load Balancer encontrar motivos para te mandar para lá. E entre não oferecer nada para o usuário e oferecer um dado que não está totalmente atualizado, prefiro dado não atualizado.

Fechamos \o/
Esse foi um exemplo BEM SIMPLES e BEM RESUMIDO. Obviamente, de acordo com a necessidade do seu negócio, isso pode ou não ser mais elaborado. É sempre valido avaliar os benefícios de uma arquitetura distribuída em qualquer problema, pode ser simples e barato resolver problemas que parecem impossíveis de serem resolvidos, apenas usando as ferramentas adequadas.

Load Balancer entre diferentes clusters Kubernetes

Como criar um Load Balancer Global, entre diferentes clusters Kubernetes na Google Cloud


Quando me foi apresentado a ideia de "multi cluster ingress" (ou MCI), deixei em standby pelo fato de aplicação trabalhar com o recurso auto devops do gitlab. E para viabilizar o MCI, eu teria de escrever os deployments e services e sair da praticidade do auto devops (perdendo recursos como incremental rollout que já está disponível no auto devops) e segui avaliando as alterações que gerariam impacto na aplicação por meio de outro recurso chamado NEG. Por fim, descobri em algumas pesquisas, recursos que o Gitlab auto devops provê, que poderiam viabilizar o MCI e retomei o foco para ele (sabiamente). 

Mas, qual o objetivo?

A proposta do MCI é basicamente unir 2 clusters através de um Load Balancer. E sabendo que cada cluster pode ter pools de maquinas em diferentes zonas, os números de disponibilidade se tornam interessantíssimos e começamos a falar em algo em torno de 99.95%.

Qual botão eu clico?

Calma lá, lembre-se que: Com grandes poderes, vêm grandes responsabilidades Com grandes problemas, vêm grandes noites sem dormir. 
- Se você acompanha meus posts, sabe que gosto muito do gitlab e nesse caso, então a coisa sai um pouco da simplicidade e se faz necessário partir para automatizações e customizações para atingir o objetivo de forma elegante. 

Blz :/ Então o que eu faço?

A aplicação que fará a comunicação com o Load Balancer, deve obrigatoriamente atender alguns critérios. 
Primeiro algumas configurações precisam ser iguais em todos os clusters (external e internal ports, são especificas para NEG, mas vamos deixar diferente para saber diferenciar facilmente uma aplicação da outra dentro da listagem de services do kubernetes) e para equalizar essa configurações, vamos utilizar algo que o gitlab nos fornece. O HELM_UPGRADE_EXTRA_ARGS

Crie um arquivo values.yaml na aplicação com o conteúdo:
service:
externalPort: 8080
internalPort: 8080
type: NodePort
- Aqui apenas coloquei as portas do container (interna e externa), para 8080 no lugar de 5000 e o tipo de ClusterIP (default), mudei para NodePort. 

No arquivo de configuração do pipeline (.gitlab-ci.yaml) inclua as variáveis abaixo:
HELM_UPGRADE_EXTRA_ARGS: "--values values.yaml"
HELM_RELEASE_NAME: mci-app
KUBE_NAMESPACE: default
NODE_PORT: 30999
- Essas variáveis, respectivamente: Atribui o arquivo values.yaml para o runner do gitlab processar no pipeline, coloca um nome padrão para a aplicação (precisa ser igual em diferentes clusters) e coloco a aplicação no namespace default do kubernetes (preciosismo apenas).

Outro requisito, é que a porta do service NodePort, seja a mesma nos clusters. E ai já é um problema a resolver, pois o gitlab não tem isso para passar pelo HELM. Então, sugiro criar um step a mais no seu pipeline, que basicamente pega o service e modifica o valor do nodePort dentro do arquivo .gitlab-ci.yaml:
node_port_change:
<<: *node_port_change
allow_failure: true
stage: setup mci
image: google/cloud-sdk:latest
script:
- gcloud auth activate-service-account --key-file ${SEU_ARQUIVO_GCLOUD}
- gcloud config set project ${SEU_PROJETO_GCP}
- gcloud container clusters get-credentials \ ${NOME_DO_SEU_CLUSTER} --zone ${ZONA} --project ${SEU_PROJETO_GCP}
- kubectl get services --namespace ${KUBE_NAMESPACE} \ -l app=${HELM_RELEASE_NAME} -o yaml | sed -E \ "s/nodePort:\ [0-9]+$/nodePort:\ ${NODE_PORT}/g" >> service.yaml
- kubectl apply -f service.yaml
only:
refs:
- master

mci prod1: &node_port_change
environment:
name: prod1
variables:
NOME_DO_SEU_CLUSTER: prod1
ZONA: ${SUA_ZONA_PROD1}

mci prod2: &node_port_change
environment:
name: prod2
variables:
NOME_DO_SEU_CLUSTER: prod2
ZONA: ${SUA_ZONA_PROD2}
- Quando essa etapa executar, basicamente vai pegar o service de cada cluster no kubernetes, baixar, fazer um replace da porta, colocar a mesma porta para ambos (perceba a variável NODE_PORT: 30999) e problema resolvido.

Show :) Terminamos?

Pff, terminamos a aplicação, agora vem o MCI. Seguindo os passos abaixo, você terá o MCI configurado (usando bash).

1) Crie uma pasta /mci
- mkdir mci

2) Entre na pasta
- cd mci

3) Exporte os arquivos dos clusters
- KUBECONFIG=clusters.yaml gcloud container clusters  get-credentials prod1 --zone=${ZONA-PROD1}
- KUBECONFIG=clusters.yaml gcloud container clusters  get-credentials prod2 --zone=${ZONA-PROD2}
- Isso vai gerar um arquivo com nome clusters.yaml.

4) Crie um IP global
- gcloud compute addresses create --global mci-ip

5) Se você quiser colocar certificado HTTPS no seu Load Balance, esse é o momento
- gcloud compute ssl-certificates create ssl-cert --certificate ${CERT_FILE} --private-key ${PRIVATE_KEY}

6) Faça download do kubemci

7) Atribua permissão adequada ao kubemci
- chmod +x ./kubemci

8) Crie o ingress-mci.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: mci-app-auto-deploy
annotations:
kubernetes.io/ingress.class: gce-multi-cluster
spec:
backend:
serviceName: mci-app-auto-deploy
servicePort: 8080
- Perceba que usamos a porta dos containers, nome do app gerado pelo auto devops, certificado que criamos anteriormente e nome do ip que também criamos acima.

9) Execute o ./kubemci
./kubemci create my-mci \
    --ingress=ingress-mci.yaml \
    --gcp-project=${SEU_PROJETO_GCP} \
    --kubeconfig=clusters.yaml

Atenção: Caso não queira o HTTPS, retire do ingress-mci.yaml a annotation "...allow-http: false" e não execute a etapa 5.

É isso :) Espero que não sofra o tanto que sofri pra chegar nesse resultado rsrs, pois não tem nada por ai explicando como fazer isso com auto devops do gitlab. Mas agora tem xD divirta-se e indique a solução para seu amiguinho que está com o mesmo problema!

Abraços!

Referências: