DEV Community

Isadora de Menezes
Isadora de Menezes

Posted on

Usando Ngnix-Controller com MetalLB no meu Home Lab

Fala pessoal! Estou estudando Kubernetes (Fazendo o sensacional PICK da LinuxTips) e resolvi compartilhar o setup que preparei para o meu home lab.

Meu objetivo é ter o Nginx Controller balanceando as requisições para os meus serviços e diponível num FQDN (Full Qualified Domain Name) que eu separei pra ele.

Os manifestos usados na configuração podem ser achados aqui.

Esse post está divido em:

  • Setup Inicial
  • Conceitos Importantes
  • Tipos de Serviço
  • O que é ingress (com foco no Nginx Controller)
  • O que é Metal LB
  • Configurando tudo

Setup Inicial

Inicialmente meu cluster tem 1 Control Plane e 2 Workers, os três estão rodando na AWS, usando Ubuntu 22.04, em instâncias t3.mediume possuem IP's públicos efêmeros (eles mudam toda vez que eu reincio essas máquinas).

Eu segui uma instalação básica do K8S, e me guiei principalmente pelo Descomplicando o Kubernetes.

Conceitos Importantes

Disclaimer: Se, assim como eu você está começando a estudar o Kubernetes, ou se quer refrescar alguns conceitos, os próximos tópicos são pra você. Caso você já tenha uma boa ideia de toda a base, eu sugiro pular para o tópico "Configurando Tudo".

Tipos de Serviço

Antes de mais nada, um serviço no Kubernetes é uma forma de expor uma aplicação que esteja rodando num Pod na rede, de forma que se um Pod for recriado, ou se mais Pods de uma aplicação forem criados, todas as instâncias sejam acessíveis através do mesmo "canal".

Quando um serviço é criado (seja através do kubectl create svc ou do kubectl expose deploy), um objeto chamado "Endpoint" é criado junto. Esse objeto mantém a lista com os IPs de todos os Pods ativos rodando aquele determinado serviço. Quando um Pod é criado ou destruído a lista é atualizada.

Por exemplo, na imagem abaixo 2 Pods com App-A e App-B estão rodando. É importante relembrar que as aplicaçoes dentro desses Pods compartilham recursos como o endereço IP. Ao criar um serviço para expor a App-A, o Kubernetes identifica os endereços de todos os Pods rodando a aplicação (através das labels e selectors) e cria o serviço apontando para esses Pods. Para acessar a App-A de dentro do cluster nesse caso, é possível executar um curl http://10.100.100.100 e a requisição será encaminhada para qualquer dos Pods disponívels.

Diagrama com um retangulo para representar o Service em cima de dois retangulos para representar os Pods. O retangulo do Service tem as informações: APP-A-SVC, ClusterIP 10.100.100.100,Endpoints [10.5.6.7:80, 10.5.6.8:80]. O primeiro retangulo para representar um Pod tem Pod 1, IP: 10.5.6.7 e dois quadrados com "App-A, porta 80" e "App-B, porta 5555". O segundo retângulo para representar um Pod contem Pod 2, IP: 10.5.6.8 e dois quadrados com "App-A, porta 80" e "App-B, porta 5555"

Finalmente, os serviços podem ser de quatro tipos:

  1. Cluster IP: Esse é o tipo padrão de serviços, ele expõe a aplicação apenas dentro do cluster.
  2. Load Balancer: Expõe a aplicação através de um load balancer, com IP fixo fora do cluster.
  3. Node Port: Torna a aplicação acessível atravpes de uma porta específica em cada nó do cluster. Permite o acesso externo ao cluster.
  4. External Name: É uma exceção aos demais, invés de usar labels para definir os endpoints, usa DNS. É usado para apontar para serviços externos ao cluster.

O que é um Ingress?

O Ingress é um objeto no Kubernetes que ajuda no gerenciamento de acesso aos serviços. Através dele é possível definir regras de acesso, rotas, SSL certificados (com integrações) e outras coisas mais.

Para funcionar, o Ingress requer um Ingress Controller, que não é criado na instalação padrão no Kubernetes. É através do controller que toda a configuração do Ingress será feita, e há várias opções disponíveis. Aqui nessa página você pode checar a lista completa.

Um dos controllers mais utilizados (e um dos três suportados oficialmente projeto Kubernetes) é o Nginx-Controller. Ele é uma implementação do Nginx e pode fazer o balancemaento de requisições WebSocket, gRPC, TCP e UDP, além de suportar o redirecionamento de rotas e terminação TLS/SSL.

O que é MetalLB:

O MetalLB é uma implementação de LoadBalancer para Kubernetes que rodam em bare-metal (fora da cloud). Ele é capaz de alocar IP's para serviços do tipo LoadBalancer e essa é a principal característica que eu vou utilizar no meu HomeLab.

É importante ressaltar que o MetalLB não cria os IP's, ele apenas faz a alocação de IP's baseado em ranges definidos pelo usuário.

Configurando tudo

Eu sugiro começar a configuração pelo MetalLB

Minha configuração final é a seguinte:

O guia completo da Instalação e Configuração pode ser encontrado aqui.

O primeiro passo para a instalação é habilitar o modo 'strict ARP':

kubectl edit configmap -n kube-system kube-proxy
Enter fullscreen mode Exit fullscreen mode
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  strictARP: true
Enter fullscreen mode Exit fullscreen mode

Após sair salvando (:wq!), é hora de fazer a instalção do MetalLB:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
Enter fullscreen mode Exit fullscreen mode

Esse comando vai criar o namespace metallb-system com um controller no control-plane e um speaker em cada nó do cluster. Após todos os pods estarem rodando, alguma configuração é necessária:

ipaddrpool.yml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: k8s-lab
  namespace: metallb-system
spec:
  addresses:
  #No meu caso esses são os IPs que eu tenho disponíveis, caso queira aplicar no seu ambiente, atualize esses IPs
  - 54.152.233.228/32 
  - 54.167.93.192/32

Enter fullscreen mode Exit fullscreen mode

Como eu estou usando a configuração via ARP, também é necessário criar um L2Advertsiment:

L2Advertsiment.yml

apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: k8s-lab
  namespace: metallb-system
spec:
  ipAddressPools:
  - k8s-lab

Enter fullscreen mode Exit fullscreen mode

E então:

k apply -f ipaddrpool.yml
k apply -f L2Advertsiment.yml
Enter fullscreen mode Exit fullscreen mode

Checando se tudo está criado:

Image description

A configuração do MetalLB está feita, para validar, precisamos criar um serviço. Optei pelo bom e velho Nginx:

nginx-deployment.yml


apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 0.5
            memory: 256Mi
          requests:
            cpu: 0.3
            memory: 64Mi
Enter fullscreen mode Exit fullscreen mode
k apply -f nginx-deployment.yml
Enter fullscreen mode Exit fullscreen mode

Image description

A forma mais rápida de validar se o MetalLB está sendo capaz de fornecer IPs para os serviços, é expondo o deploy que acabamos de criar utilizando o serviço do tipo loadBalancer:

kubectl expose deploy nginx --port 80 --target-port 80 --type LoadBalancer
Enter fullscreen mode Exit fullscreen mode

Image description

E com um curl 54.167.93.192 recebemos a mensagem padrão do Nginx.

Okay, o MetalLB está funcionando e é possível acessar o serviço através do IP de dentro do cluster, mas eu quero que esse serviço esteja disponível no meu FQDN k8s-lab.ibmenezes.com. É hora de installar o nginx-controller.

O getting started dessa instalação pode ser encontrado aqui.

Como estamos em uma instalação 'bare-metal', a instalação será:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/baremetal/deploy.yaml
Enter fullscreen mode Exit fullscreen mode

Após alguns segundos (talvez minutos) temos o pod do ingress-controller rodando no Control Plane. Além disso, o serviço do ingress-controler é criado no namespace ingress-nginx, e o External IP é atribuído pelo MetalLB:

Image description

Nesse momento já é possível acessar o ingress controller no browser com https://54.152.233.228:31333/. Esse acesso retorna um 503 porque não existe nenhum service associado a ingress nesse momento.

Para corrigir isso, a criação do ingress:

ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: k8s-lab
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/issuer: "letsencrypt-staging"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - k8s-lab.ibmenezes.com
    secretName: "k8s-lab-tls"
  rules:
  - host: k8s-lab.ibmenezes.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
Enter fullscreen mode Exit fullscreen mode

Aqui algumas coisas são importantes:
A primeira annotation nginx.ingress.kubernetes.io/rewrite-target: / determina que todas as vezes que uma requisição for encaminhada para a /, ela será redirecionada ao path (que falaremos mais abaixo).

A segunda anotação, cert-manager.io/issuer: "letsencrypt-staging" é associada à criação de certificados SSL usando o letsencrypt. Nesse setup não iremos a fundo nisso, na próxima sessão explico os motivos. O bloco tls também está associado ao certificado.

Já nas especificações, o bloco de rules vai determinar:

- host: k8s-lab.ibmenezes.com -> O FQDN onde o ingress espera ouvir a requisição 
http.paths.path -> O caminho da applicação para onde o trafego será redirecionado quando o usuário digitar `/` (da annotation)
http.paths.pathType ->  O tipo de path, esse valor é requerido quando o tipo é Prefix ou Exact
http.paths.backend -> as configurações do serviço para o qual o ingress estará apontando
Enter fullscreen mode Exit fullscreen mode

Quase tudo pronto, alguns detalhes finais:

É importante remover o serviço que criamos anteriormente:

kubectl delete svc nginx
Enter fullscreen mode Exit fullscreen mode

Isso vai liberar o IP anteriormente atribuido ao serviço e prevenir que a gente valide o serviço errado. Antes de criar o ingress é importante criar o serviço definido em http.paths.backend. Como eu defini o meu se chamando 'nginx', me basta:

kubectl expose deploy nginx --port 80
Enter fullscreen mode Exit fullscreen mode

Image description

Para criar o ingress a partir do manifesto criado anteriormente basta executar:

kubectl apply -f ingress.yml
Enter fullscreen mode Exit fullscreen mode

Com um describe é possível verificar que o ingress recebe o mesmo IP do ingress-controller recebeu anteriormente.

Image description

A última configuração necessária é no provedor de DNS, no meu caso eu criei uma entrada tipo A apontando para o IP do ingress.

Para validar o balanceamento, editei o index.html de cada Pod:

k exec -it nginx-f888bbf65-j94fk /bin/bash
echo "Eu sou o pod nginx-f888bbf65-j94fk" > /usr/share/nginx/html/index.html
Enter fullscreen mode Exit fullscreen mode
k exec -it nginx-f888bbf65-tcmvz/bin/bash
echo "Eu sou o pod nginx-f888bbf65-tcmvz" > /usr/share/nginx/html/index.html
Enter fullscreen mode Exit fullscreen mode

E, acessando 'https://k8s-lab.ibmenezes.com:31333' pelo browser temos:

Image description

Resumindo como tudo funciona de forma superficial:

  1. Eu tenho uma entrada DNS 'k8s-lab.ibmenezes.com' do tipo A, aonde eu precisei atualizar o IP de destino;
  2. A lista de IP's disponíveis para a virtualização foi feita a partir dos IP's públicos das minhas máquinas na AWS e passada para o MetalLB;
  3. O MetalLB forneceu um dos IP's disponíveis para o nginx-controller
  4. Ta pronto o sorvetinho! Agora eu consigo acessar meus serviços externamente.

Image description

Top comments (2)

Collapse
 
guilhermeghm profile image
Guilherme Maciel

Muito show, valeu por compartilhar!

Collapse
 
badtuxx profile image
Jeferson Fernando

Ficou sensacional demais! <3 \o/