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: