Por Lendro Damascena
Expondo serviços HTTP, TCP e UDP no Network Load Balancer (com TLS Termination) e NGINX Ingress Controller no AWS EKS.
Antes de começarmos a conversa técnica, é preciso entender quais são os requisitos técnicos dessa demanda e o motivo pelo qual foi adotado o AWS Network Load Balancer com NGINX Ingress Controller.
Os requisitos:
1. Payload com dezenas/centenas de MB;
2. IP estático para controle de firewall da rede corporativa do cliente. Por isso a escolha do Network Load Balancer;
3. Alto throughput. Mais 1 ponto para o Network Load Balancer;
4. Uso do NGINX Ingress Controller como ingress controller devido a familiaridade da equipe do cliente;
5. Poder trafegar HTTP/S,TCP,UDP utilizando apenas 1 Load Balancer e ter a possibilidade de TLS Termination no próprio Load Balancer;
6. SSL Offloading entre o Load Balancer e os pods;
7. Múltiplos subdomínios;
8. Redirecionamento de tráfego HTTP baseado em path.
Os recursos utilizados:
1. Nginx Ingress controller do projeto Kubernetes;
2. TLS termination no Network Load Balancer.Recurso suportado desde 2019;
3. Múltiplos ingress para múltiplos subdomínios (ACM wildcard);
4. Múltiplos ingress com routes para diferentes services;
5. TCP/UDP Proxy para serviços nativamente TCP/UDP.
A implementação:
Como o tema deste artigo é de nível avançado e requer um bom conhecimento da parte de networking do Kubernetes, então eu vou assumir que neste momento você já criou seu cluster (público ou privado), criou os node groups, criou seu OIDC provider e as service account necessárias ou fez o attach das policies necessárias na IAM Role dos nós do seu cluster. Caso tenha dúvidas, a AWS possui um artigo em seu blog que pode ajudar em sua compreensão.
1. Certificado
Crie um certificado ACM para o domínio que desejar. Procure adicionar o certificado como wildcard para que você possa utilizar diversos subdomínios. No meu caso, eu tenho um domínio chamado awscdk.cloud e vou usar ele como exemplo. Caso tenha dúvidas, utilize este artigo como referência:
O certificado tem que estar com status “issued” para poder ser utilizado.
2. Instalando o NGINX Ingress Controller
Faça o download do arquivo de deploy direto do repositório do GitHub
Uma vez que o arquivo foi baixado, precisamos fazer algumas alterações para que seja possível ajustar o certificado, os IPs estáticos (vamos alocar Elastic IPs) e o protocolo que será TLS (para termos TLS Termination no NLB).
Vá até mais ou menos a linha 263 e você verá os annotations do Kubernetes para o “aws-load-balancer”
Primeira alteração:
Adicione as seguintes linhas:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm: xxx (ARN DO SEU CERTIFICADO ACM)service.beta.kubernetes.io/aws-load-balancer-ssl-ports: “https”service.beta.kubernetes.io/aws-load-balancer-eip-allocations: “eipalloc-5bc1bb50,eipalloc-xxx,eipalloc-xxx” (adicione quantos ips elásticos você quiser, lembrando que tem que ser o máximo de número de AZs)
Seu arquivo ficará próximo disso:
Segunda alteração:
Para que o Network Load Balancer consiga fazer o TLS Termination nele, e não nos pods, é preciso alterar o protocolo de destino que o ingress vai enviar. Vá até a linha 287 e localize o item “- name: https”. Altere o targetPort e o appProtocol para http ao invés de https, dessa forma a conexão SSL terminará no Network Load Balancer e não nos pods.
Abaixo segue uma imagem que explica como funciona o SSL Offloading em um Load Balancer.
Crie os resources no Kubernetes com o comando “kubectl apply -f deploy.yaml” e aguarde a criação de todos os resources.
ATENÇÃO: Se a criação do service “ingress-nginx-controller” ficar em pending e você não enxergar o Network Load Balancer na AWS, há algum problema de permissão da sua role ou do serviceaccount. Utilize o Cloudtrail para ajudar a identificar se o problema é permissão.
Assumindo que tudo deu certo e o Network Load Balancer foi criado com sucesso, você deve enxergar na AWS o recurso da seguinte forma:
Agora podemos realizar o deploy dos nossos primeiros serviços e testar o acesso HTTP e HTTPS.
3. Deploy de serviços HTTP
Como exemplo para este artigo, vamos utilizar um deploy de um pod com apache e criar 1 ingress para ele. Esse é apenas um exemplo, você pode fazer deploy da aplicação HTTP que desejar.
Criando o deployment e o ingress
Digite o seguinte comando para criar deployment da sua primeira app:
kubectl apply -f https://raw.githubusercontent.com/leandrodamascena/medium-nlb/main/apache-deploy.yaml
Agora que temos o deployment criado, é hora de criar um ingress para apontar para este deployment.
Digite o seguinte comando:
wget https://raw.githubusercontent.com/leandrodamascena/medium-nlb/main/ingress-1.yaml
Abra o arquivo e altere a linha “- host: medium.awscdk.cloud” para o domínio/subdomínio que você criou o ACM. Lembre-se de adicionar uma entrada na sua zona de DNS (do tipo CNAME) apontando para o endereço do Network Load Balancer, no meu caso ficou da seguinte maneira:
Faça o deploy deste arquivo utilizando o comando: “kubectl apply -f ingress-1.yaml”.
Caso queira realizar o deploy de mais ingress HTTP apontando para outros subdomínios e outros services, então copie o arquivo “ingress-1.yaml” para outro arquivo e troque as entradas de “host”, “serviceName” e “servicePort”. Desta forma, você terá múltiplos ingress respondendo por múltiplos domínios.
ATENÇÃO: A partir da versão 1.22 o Kubernetes vai mudar a API do Ingress de “extensions/v1beta1” para “networking.k8s.io/v1”.
Mais informações em: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#ingress-v122
Acessando os domínios
Abra o browser e digite o domínio configurado, no meu caso “https://medium.awscdk.cloud/”
Caso encontre qualquer problema ou erro 404, verifique os logs do pod “ingress-nginx-controller-*” no namespace “ingress-nginx”.
4. Deploy de serviços TCP
Como exemplo para este deploy, vamos utilizar o serviço do mongodb queestá disponível/ na página do próprio Kubernetes.
Deploy do mongo e do service
Digite os seguintes comandos:
kubectl apply -f https://k8s.io/examples/application/mongodb/mongo-deployment.yamlkubectl apply -f https://k8s.io/examples/application/mongodb/mongo-service.yaml
Após executar os comandos acima, você verá no namespace default o pod e o service do mongo.
Configurando o ingress controller
Abra novamente o arquivo “deploy.yaml” e agora faremos uma série de alterações:
• No service do Ingress Controller ( mais ou menos na linha 260) devemos adicionar as seguintes linhas abaixo da porta https:
– name: mongo
port: 27017
protocol: TCP
targetPort: 27017
appProtocol: TCP
• Procure pelo deployment do controller (mais ou menos linha 330) e adicione os seguintes comandos na chave “args”:
– –tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
– –udp-services-configmap=$(POD_NAMESPACE)/udp-services
• Desça um pouco mais o arquivo e onde tem as portas do container, adicione também a porta 27017:
– name: mongo
containerPort: 27017
protocol: TCP
• Agora execute o comando para carregar as novas configurações para nosso ingress controller. Se tudo correr bem, o seu Load Balancer deve ficar conforme foto abaixo:
kubectl apply -f deploy.yaml
• Ainda precisamos criar os configmap e abaixo é possível entender o motivo pelo qual precisamos disso. Execute os seguintes comandos:
kubectl apply -f https://raw.githubusercontent.com/leandrodamascena/medium-nlb/main/config-map-tcp.yamlkubectl apply -f https://raw.githubusercontent.com/leandrodamascena/medium-nlb/main/config-map-udp.yaml
Note que no arquivo TCP eu mapeio a porta para o namespace, service e porta do serviço. Você pode adicionar quantas portas quiser.
Para adicionar para o serviço UDP, edite o arquivo e faça o mapping das portas como fiz no serviço TCP.
ATENÇÃO:
O motivo pelo qual precisamos criar configmaps para serviços TCP e UDP é que o Ingress Controller não aceita TCP/UDP nativamente, então ele deve apontar para um configmap para indicar o namespace, o service e a porta que vai usar. Abaixo segue a explicação do site oficial: (https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services/)
“Ingress does not support TCP or UDP services. For this reason this Ingress controller uses the flags –tcp-services-configmap and –udp-services-configmap to point to an existing config map where the key is the external port to use and the value indicates the service to expose using the format:
It is also possible to use a number or the name of the port. The two last fields are optional. Adding PROXY in either or both of the two last fields we can use Proxy Protocol decoding (listen) and/or encoding (proxy_pass) in a TCP service. The first PROXY controls the decode of the proxy protocol and the second PROXY controls the encoding using proxy protocol. This allows an incoming connection to be decoded or an outgoing connection to be encoded. It is also possible to arbitrate between two different proxies by turning on the decode and encode on a TCP service.”
Agora basta conectar no endereço medium.awscdk.cloud, na porta 27017 e o ingress fechará a conexão com seu serviço TCP:
[root@ip-172-31-78-41 keys]# telnet medium.awscdk.cloud 27017
Trying 44.195.204.52…
Connected to medium.awscdk.cloud.
Escape character is ‘^]’.
Logs do Ingress Controller:
[3.232.229.57] [01/Sep/2021:01:14:46 +0000] TCP 200 0 19 6.752
[3.232.229.57] [01/Sep/2021:01:15:18 +0000] TCP 200 0 17 8.460
I0901 01:15:29.333757 6 leaderelection.go:253] successfully acquired lease ingress-nginx/ingress-controller-leader
I0901 01:15:29.333775 6 status.go:84] “New leader elected” identity=”ingress-nginx-controller-8468b46b64-8xvhp”
I0901 01:15:29.344527 6 status.go:284] “updating Ingress status” namespace=”default” ingress=”micro-ingress” currentValue=[] newValue=[{IP: Hostname:a16e087863c2d457fbe56a20746cbd60-e176fb991afdfd87.elb.us-east-1.amazonaws.com Ports:[]}]
I0901 01:15:29.353062 6 event.go:282] Event(v1.ObjectReference{Kind:”Ingress”, Namespace:”default”, Name:”micro-ingress”, UID:”c8ee028a-c3b5-4e02-94d8-80499b961375″, APIVersion:”networking.k8s.io/v1″, ResourceVersion:”125703″, FieldPath:””}): type: ‘Normal’ reason: ‘Sync’ Scheduled for sync
183.89.148.191 – – [01/Sep/2021:01:21:30 +0000] “GET / HTTP/1.1” 400 150 “-” “-” 18 0.000 [] [] – – – – c92a1c3e761d28b8ceee528fa71c37ed
[3.232.229.57] [01/Sep/2021:01:41:38 +0000] TCP 200 0 16 67.223
Logs do pod do Mongo:
2021-09-01T00:58:50.933+0000 I NETWORK [listener] connection accepted from 127.0.0.1:48504 #1 (1 connection now open)
2021-09-01T00:58:56.312+0000 I NETWORK [conn1] Error receiving request from client: ProtocolError: Client sent an HTTP request over a native MongoDB connection. Ending connection from 127.0.0.1:48504 (connection id: 1)
2021-09-01T00:58:56.312+0000 I NETWORK [conn1] end connection 127.0.0.1:48504 (0 connections now open)
2021-09-01T01:14:00.155+0000 I NETWORK [listener] connection accepted from 172.31.4.141:40024 #2 (1 connection now open)
2021-09-01T01:14:38.425+0000 I NETWORK [conn2] Error receiving request from client: SSLHandshakeFailed: SSL handshake received but server is started without SSL support. Ending connection from 172.31.4.141:40024 (connection id: 2)
2021-09-01T01:14:38.425+0000 I NETWORK [conn2] end connection 172.31.4.141:40024 (0 connections now open)
2021-09-01T01:14:39.655+0000 I NETWORK [listener] connection accepted from 172.31.15.47:35708 #3 (1 connection now open)
2021-09-01T01:14:46.407+0000 I NETWORK [conn3] Error receiving request from client: SSLHandshakeFailed: SSL handshake received but server is started without SSL support. Ending connection from 172.31.15.47:35708 (connection id: 3)
2021-09-01T01:14:46.407+0000 I NETWORK [conn3] end connection 172.31.15.47:35708 (0 connections now open)
2021-09-01T01:15:10.100+0000 I NETWORK [listener] connection accepted from 172.31.15.47:36014 #4 (1 connection now open)
2021-09-01T01:15:18.560+0000 I NETWORK [conn4] Error receiving request from client: SSLHandshakeFailed: SSL handshake received but server is started without SSL support. Ending connection from 172.31.15.47:36014 (connection id: 4)
2021-09-01T01:15:18.560+0000 I NETWORK [conn4] end connection 172.31.15.47:36014 (0 connections now open)
2021-09-01T01:40:31.110+0000 I NETWORK [listener] connection accepted from 172.31.15.47:49656 #5 (1 connection now open)
2021-09-01T01:41:38.332+0000 I NETWORK [conn5] Error receiving request from client: SSLHandshakeFailed: SSL handshake received but server is started without SSL support. Ending connection from 172.31.15.47:49656 (connection id: 5)
2021-09-01T01:41:38.332+0000 I NETWORK [conn5] end connection 172.31.15.47:49656 (0 connections now open)
2021-09-01T01:41:40.091+0000 I NETWORK [listener] connection accepted from 172.31.15.47:50262 #6 (1 connection now open)
Para limpar o ambiente basta apenas digitar o seguinte comando:
kubectl delete -f deploy.yaml
Espero que tenham gostado do artigo e deixem seus comentários em caso de dúvidas!