Configurando um ambiente de staging com Kubernetes e feature flags
Aqui no Jusbrasil temos uma cultura forte de desenvolvimento: todo código que é escrito passa por um processo de code review, e passa por muitos testes antes de ser liberado para nossos usuários. Mas mesmo após estas etapas, é importante fazer uma última validação antes de subir o código para produção (o ambiente que os usuários tem acesso). Por isso é importante termos um ambiente de staging.
O ambiente de staging é uma réplica do ambiente de produção. A ideia é que ele seja o mais parecido possível com este, pois no ambiente local, onde desenvolvemos e fazemos os primeiros testes, nem sempre conseguimos reproduzir o mesmo ambiente — alguns serviços são difíceis de executar localmente, outros possuem muitas dependências, e mesmo os que conseguimos executar localmente podem não funcionar da mesma forma em diferentes arquiteturas ou sistemas operacionais.
Configurando a infra-estrutura
Para criar um ambiente de staging, precisamos inicialmente preparar a infra-estrutura com configurações semelhantes às de produção. A arquitetura da nossa infra-estrutura, de forma simplificada, é como mostra a figura abaixo:
Quando um usuário acessa jusbrasil.com.br
, a requisição passa por um servidor nginx (que roda dentro de um cluster Kubernetes), e daí é encaminhada para um Service. Este componente seleciona os Pods que respondem pela app, e encaminha a requisição para um deles, como um balanceador de carga.
A arquitetura que desejamos montar se assemelha à da figura a seguir:
Podemos perceber na imagem que agora temos um segundo Service e um segundo conjunto de Pods. Estes novos recursos responderão pelo ambiente de staging. Segue um exemplo fictício de como criá-los:
Deployment e Service da myapp-web em produção:
apiVersion: v1
kind: Deployment
metadata:
name: myapp-web
labels:
app: myapp
env: production
spec:
replicas: 10
# ...
---
apiVersion: v1
kind: Service
metadata:
name: myapp-web
labels:
app: myapp
env: production
spec:
selector:
app: myapp
env: production
ports:
- port: 5000
# ...
Deployment e Service da myapp-web em staging:
apiVersion: v1
kind: Deployment
metadata:
name: myapp-web-staging
labels:
app: myapp
env: staging
spec:
replicas: 2
# ...
---
apiVersion: v1
kind: Service
metadata:
name: myapp-web-staging
labels:
app: myapp
env: staging
spec:
selector:
app: myapp
env: staging
ports:
- port: 5000
# ...
As diferenças entre os Deployments de produção e staging estão destacadas em negrito nos YAMLs acima. Além dos nomes diferentes, adicionamos a label env
com os valores production
e staging
em cada objeto. O uso dessa label no seletor dos Services permite distinguir as instâncias por ambiente.
Mudamos também o número de réplicas: como o ambiente de produção é acessado por todos os nossos usuários, precisa de um número maior de réplicas do que o de staging, que só é usado por nossos usuários internos, portanto recebe um tráfego muito menor.
Feitas as configurações acima, já conseguimos distinguir os dois ambientes: o Service myapp-web
encaminha requisições para os Pods de produção, e o myapp-web-staging
, para os Pods de staging. Agora precisamos expor o acesso a esses Services para fora do cluster Kubernetes.
Usamos o nginx para expor o acesso aos Services no Kubernetes. Uma opção seria criar uma URL diferente para o ambiente de staging, como um sub-domínio (algo como staging.meudominio.com.br
, por exemplo). Porém, isso exigiria configurações mais complexas nos nossos serviços internos, como login, por exemplo. Por isso, optamos por seguir uma abordagem mais simples: mapear diferentes ambientes no mesmo domínio com o uso de cookies. Para que isso funcione, precisamos adicionar uma configuração como esta no nosso nginx:
upstream myapp-web {
server myapp-web:5000 max_fails=0;
}upstream myapp-web-staging {
server myapp-web-staging:5000 max_fails=0;
}map $cookie_staging_env $myapp_upstream {
default http://myapp-web;
"true" http://myapp-web-staging;
}server {
listen 443;
server_name meudominio.com.br; location /myapp {
proxy_pass $myapp_upstream;
}
}
Na configuração acima, inicialmente definimos um upstream para cada Service — como o próprio nginx roda no mesmo cluster Kubernetes, ele consegue acessar os Services pelos seus nomes (myapp-web
e myapp-web-staging
). Em seguida, criamos uma variável $myapp_upstream
, cujo valor depende do cookie staging_env
: caso ele esteja definido e com o valor true
, o conteúdo da variável será http://myapp-web-staging
; caso contrário, http://myapp-web
. Finalmente, passamos o valor dessa variável para um proxy pass, dentro da location /myapp
.
Feita essa configuração, se acessarmos o path /myapp
do nginx, a requisição será encaminhada por padrão para o ambiente de produção. Porém, se a requisição incluir o cookie staging_env=true
, ela será encaminhada para staging. Podemos fazer esse teste usando a ferramenta curl
:
# requisição para produção
curl https://meudominio.com.br/myapp# requisição para staging
curl -H "Cookie: staging_env=true" https://meudominio.com.br/myapp
Para acessar staging via browser, precisamos abrir o inspector (disponível em todos os browsers modernos) e digitar essa linha no console:
document.cookie = "staging_env=true; domain=.meudominio.com.br; path=/";
A partir desse momento, as requisições para https://meudominio.com.br/myapp
serão direcionadas para o ambiente de staging. Ao remover esse cookie, as requisições posteriores serão direcionadas para produção. Mesma URL, dois ambientes diferentes!
Acessando via browser
Como acabamos de ver, agora basta usar um cookie para acessar o ambiente de staging. Porém, manipular cookies via console do browser não é prático, principalmente se o usuário não estiver habituado com esta ferramenta.
Para facilitar o acesso a staging por nossos usuários internos, criamos um item no menu do usuário para ligar ou desligar o acesso a esse ambiente via feature flag. Quando um usuário do Jusbrasil está logado como administrador da plataforma, ele tem acesso a um item Ambiente de staging
no menu:
Ativando esta opção, o cookie é automaticamente injetado na página, e o usuário passa a acessar o ambiente de staging. Desativando esta opção, ele retorna ao ambiente de produção.
Um problema que acontecia eventualmente era alguns de nossos usuários internos ativarem o ambiente de staging e esquecerem se tinham desativado ou não — ou seja, não sabiam se estavam acessando produção ou staging. Para tornar visível qual é o ambiente atual, adicionamos um elemento na topbar, que só fica visível em staging:
Fazendo deploys para staging
Finalmente, para facilitar o deploy de código para o ambiente de staging, configuramos um pipeline específico no nosso servidor de integração contínua — o pipeline padrão faz deploy de todo código da branch master
para produção. No novo pipeline, todo código enviado para a branch staging
faz o deploy para este ambiente. Dessa forma, se quisermos testar um código de uma branch local em staging, precisamos fazer git push origin minha_branch:staging
.
Com isso, conseguimos ter um ambiente para testar features antes de liberá-las em produção, de forma segura e simples.