Aula 14 – K8S – Configuração do Ingress Controller
Aula 14 – K8S – Configuração do Ingress Controller
Voltar para página principal do blog
Todas as aulas desse curso
Aula 13 Aula 15
Redes Sociais do Código Fluente:
Scarlett Finch
Scarlett Finch é uma influenciadora virtual criada com IA.
Ela é 🎤 cantora e 🎶compositora pop britânica.
Siga a Scarlett Finch no Instagram:
Conecte-se comigo!
LinkedIn: Fique à vontade para me adicionar no LinkedIn.
Ao conectar-se comigo, você terá acesso a atualizações regulares sobre desenvolvimento web, insights profissionais e oportunidades de networking no setor de tecnologia.
GitHub: Siga-me no GitHub para ficar por dentro dos meus projetos mais recentes, colaborar em código aberto ou simplesmente explorar os repositórios que eu contribuo, o que pode ajudar você a aprender mais sobre programação e desenvolvimento de software.
Recursos e Afiliados
Explorando os recursos abaixo, você ajuda a apoiar nosso site.
Somos parceiros afiliados das seguintes plataformas:
- https://heygen.com/ – Eleve a produção de seus vídeos com HeyGen! Com esta plataforma inovadora, você pode criar vídeos envolventes utilizando avatares personalizados, ideal para quem busca impactar e conectar com audiências em todo o mundo. HeyGen transforma a maneira como você cria conteúdo, oferecendo ferramentas fáceis de usar para produzir vídeos educativos, demonstrações de produtos e muito mais. Descubra o poder de comunicar através de avatares interativos e traga uma nova dimensão para seus projetos. Experimente HeyGen agora e revolucione sua forma de criar vídeos!
- letsrecast.ai – Redefina a maneira como você consome artigos com Recast. Esta plataforma transforma artigos longos em diálogos de áudio que são informativos, divertidos e fáceis de entender. Ideal para quem está sempre em movimento ou busca uma forma mais conveniente de se manter informado. Experimente Recast agora.
- dupdub.com – Explore o universo do marketing digital com DupDub. Esta plataforma oferece ferramentas inovadoras e soluções personalizadas para elevar a sua estratégia de marketing online. Ideal para empresas que buscam aumentar sua visibilidade e eficiência em campanhas digitais. Descubra mais sobre DupDub.
- DeepBrain AI Studios – Revolucione a criação de conteúdo com a tecnologia de inteligência artificial da DeepBrain AI Studios. Esta plataforma avançada permite que você crie vídeos interativos e apresentações utilizando avatares digitais gerados por IA, que podem simular conversas reais e interações humanas. Perfeito para educadores, criadores de conteúdo e empresas que querem inovar em suas comunicações digitais. Explore DeepBrain AI Studios.
- Audyo.ai – Transforme a maneira como você interage com conteúdo auditivo com Audyo.ai. Esta plataforma inovadora utiliza inteligência artificial para criar experiências de áudio personalizadas, melhorando a acessibilidade e a compreensão de informações através de podcasts, transcrições automáticas e síntese de voz avançada. Ideal para profissionais de mídia, educadores e qualquer pessoa que deseje acessar informações auditivas de maneira mais eficiente e envolvente. Descubra Audyo.ai e suas possibilidades.
- Acoust.io – Transforme sua produção de áudio com Acoust.io. Esta plataforma inovadora fornece uma suite completa de ferramentas para criação, edição e distribuição de áudio, ideal para artistas, produtores e empresas de mídia em busca de excelência e inovação sonora. Acoust.io simplifica o processo de levar suas ideias à realidade, oferecendo soluções de alta qualidade que elevam seus projetos de áudio. Experimente Acoust.io agora e descubra um novo patamar de possibilidades para seu conteúdo sonoro.
- Hostinger – Hospedagem web acessível e confiável. Ideal para quem busca soluções de hospedagem de sites com excelente custo-benefício e suporte ao cliente robusto. Saiba mais sobre a Hostinger.
- Digital Ocean – Infraestrutura de nuvem para desenvolvedores. Oferece uma plataforma de nuvem confiável e escalável projetada especificamente para desenvolvedores que precisam de servidores virtuais, armazenamento e networking. Explore a Digital Ocean.
- One.com – Soluções simples e poderosas para o seu site. Uma escolha ideal para quem busca registrar domínios, hospedar sites ou criar presença online com facilidade e eficiência. Visite One.com.
Educação e Networking
Amplie suas habilidades e sua rede participando de cursos gratuitos e comunidades de desenvolvedores:
- Digital Innovation One – Cursos gratuitos com certificado.
- Workover – Aprenda Python3 gratuitamente.
- Comunidades de desenvolvedores para networking:
Canais do Youtube
Explore nossos canais no YouTube para uma variedade de conteúdos educativos e de entretenimento, cada um com um foco único para enriquecer sua experiência de aprendizado e lazer.
Toti
Toti: Meu canal pessoal, onde posto clips artesanais de músicas que curto tocar, dicas de teoria musical, entre outras coisas.
Lofi Music Zone Beats
Lofi Music Zone Beats: O melhor da música Lofi para estudo, trabalho e relaxamento, criando o ambiente perfeito para sua concentração.
Backing Track / Play-Along
Backing Track / Play-Along: Acompanhe faixas instrumentais para prática musical, ideal para músicos que desejam aprimorar suas habilidades.
Código Fluente
Código Fluente: Aulas gratuitas de programação, devops, IA, entre outras coisas.
Putz!
Putz!: Canal da banda Putz!, uma banda virtual, criada durante a pandemia com mais 3 amigos, Fábio, Tatá e Lula.
Scarlett Finch
Scarlett Finch: Cantora e influenciadora criada com IA.
PIX para doações

PIX Nubank
Repositório da Aula
Aula 14 – K8S – Configuração do Ingress Controller
⚠️ Pré-requisitos:
Esta aula assume que você já concluiu a Aula 13, com os serviços backend e frontend funcionando via LoadBalancer.
📚 Introdução
Ao longo das aulas anteriores, configuramos nosso ambiente no Kubernetes, garantindo a persistência de dados com PV/PVC, a resiliência da aplicação com probes e a gestão segura de credenciais com Secrets. No entanto, até agora, o acesso ao nosso backend e frontend tem sido feito via Service.type = LoadBalancer
, o que pode ser custoso e difícil de escalar.
📌 O que você aprenderá nesta aula?
- O que é um Ingress Controller e qual sua importância.
- Diferença entre LoadBalancer e Ingress no Kubernetes.
- Instalação do NGINX Ingress Controller com Terraform + Helm.
- Criação do recurso Ingress para rotear tráfego para frontend e backend.
- Adaptação do cluster para usar Ingress em vez de múltiplos LoadBalancers.
- Testes e validação da comunicação via Ingress.
💡 Retrospectiva rápida
Recurso | O que faz? | Quando usamos? |
---|---|---|
PV / PVC | Armazena dados de forma persistente. | Usamos na aula anterior para o MySQL. |
Probes | Garantem que os containers estejam saudáveis. | Usamos readiness e liveness para backend e frontend. |
Secrets | Armazenam dados sensíveis, como senhas e tokens. | Usamos para MySQL e Gmail. |
LoadBalancer | Expõe um serviço diretamente ao tráfego externo. | Usamos para o frontend nas aulas anteriores. |
🔀 O que é o Ingress?
O Ingress atua como um ponto único de entrada HTTP/HTTPS para dentro do cluster, onde você pode criar regras de roteamento para distribuir o tráfego entre múltiplos serviços com base na URL.
🧠 Analogia didática
- LoadBalancer é como o porteiro: ele só abre a porta e encaminha para um único serviço.
- Ingress é como a recepcionista: ela escuta qual o setor que você quer e direciona para o serviço certo (frontend, backend, admin…).
🧭 Diferença principal entre LoadBalancer e Ingress
Recurso | O que faz | Quem cria/controla |
---|---|---|
LoadBalancer | Expõe um serviço diretamente com IP público (ex: service.type = LoadBalancer ) | Cloud Provider (DigitalOcean, AWS, etc) |
Ingress | Controlador de rotas HTTP. Permite vários serviços por um único IP. | Ingress Controller (NGINX, Traefik…) |
🔧 Ferramenta extra: Helm
Helm é um gerenciador de pacotes para Kubernetes (como o apt ou npm). Ele nos permite instalar aplicativos complexos com um único comando. No nosso caso, usaremos o Helm para instalar o NGINX Ingress Controller.
🎯 Objetivo da Aula
Manter o mesmo projeto da Aula 13, com frontend React e backend Fiber, mas agora com o tráfego externo sendo roteado através de um único Ingress Controller. As imagens Docker já estão publicadas no Docker Hub, e vamos modificar apenas o necessário.
🧐 Mas e a aula passada? A gente usou só 1 LoadBalancer, não foi?
Sim, na aula anterior (Aula 13), o único serviço exposto externamente era o frontend
, usando um Service
do tipo LoadBalancer
. O backend era um ClusterIP
, acessado apenas internamente pelo frontend.
Isso funcionava, mas tem limitações:
- Só servia para este caso simples (frontend + backend)
- Se precisássemos expor mais serviços (como admin, API externa, etc), cada um precisaria de um LoadBalancer separado, gerando mais custos e mais IPs públicos
Com o Ingress Controller, resolvemos isso de forma mais elegante e escalável.
🧠 Mesmo com 1 LoadBalancer, o Ingress ainda traz vantagens:
- Permite
/api
,/admin
,/outro-app
em um único IP público - Melhor integração com HTTPS e certificados TLS (via cert-manager)
- Centraliza o roteamento e facilita o controle de entrada de tráfego
É por isso que, mesmo tendo usado só um LoadBalancer antes, faz sentido migrar para o uso do Ingress nesta aula.
🗂️ Estrutura atualizada do projeto
codigo-fluente-fiber-tutorial/
├── backend/
│ └── fiber-project/
├── frontend/
│ └── react-auth/
│ └── nginx.conf
├── devops/
│ ├── Dockerfile-backend
│ ├── Dockerfile-frontend
│ ├── deployment.tf <-- Modificado nesta aula
│ ├── helm_ingress.tf <-- Criado nesta aula
│ ├── ingress.tf <-- Criado nesta aula
│ ├── main.tf <-- Modificado nesta aula
│ ├── outputs.tf <-- Modificado nesta aula
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
│ ├── terraform.tfvars
│ ├── variables.tf <-- Modificado nesta aula
🚧 Alterações feitas nesta aula
- Criação do
devops/terraform/ingress.tf
– ele define as regras de roteamento HTTP. - Criação do
devops/terraform/helm_ingress.tf
– instala o NGINX Ingress Controller com Helm. - Removido o LoadBalancer do frontend – agora usaremos apenas o Ingress.
📁 devops/helm_ingress.tf
🔹 Explicando o recurso helm_release "ingress_nginx"
✅ 1. O que esse recurso faz?
Esse bloco instala o NGINX Ingress Controller no cluster Kubernetes usando o Helm, que é um gerenciador de pacotes para K8s. Ele automatiza toda a configuração do Ingress sem precisar criar arquivos YAML manualmente.
✅ 2. Explicação dos parâmetros utilizados
resource "helm_release" "ingress_nginx" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
namespace = "default"
timeout = 600
Explicação: Esse bloco define:
– name
: nome dado à instalação Helm.
– repository
: onde está localizado o chart oficial do NGINX.
– chart
: nome do pacote Helm a ser instalado.
– namespace
: espaço lógico onde o Ingress será instalado.
– timeout
: tempo limite de instalação (em segundos).
✅ 3. Configurações adicionais com set
set {
name = "controller.service.type"
value = "LoadBalancer"
}
set {
name = "controller.publishService.enabled"
value = "true"
}
Explicação:
– O primeiro set
configura o tipo do serviço do Ingress como LoadBalancer
, o que é necessário na DigitalOcean (ou outros provedores) para que um IP público seja atribuído automaticamente.
– O segundo set
garante que o serviço seja publicado e que seu IP fique disponível para uso posterior, como na variável `APP_URL` do backend.
🧩 Esse recurso é essencial para que o Ingress Controller funcione corretamente e para que o Kubernetes possa receber e redirecionar requisições HTTP externas ao cluster de forma centralizada, segura e escalável.
devops/helm_ingress.tf
resource "helm_release" "ingress_nginx" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
namespace = "default"
timeout = 600
# Precisamos do Service tipo LoadBalancer
# para a DigitalOcean provisionar IP externo
set {
name = "controller.service.type"
value = "LoadBalancer"
}
# Publicar service do controller para relatarmos IP
set {
name = "controller.publishService.enabled"
value = "true"
}
}
📁 devops/ingress.tf (criação das rotas públicas via Ingress)
🔹 Explicando o recurso kubernetes_ingress_v1
✅ 1. Para que serve esse arquivo?
Esse recurso cria um Ingress, que funciona como um “porteiro” central para direcionar as requisições externas (HTTP) para os serviços corretos no cluster Kubernetes.
Ele substitui a necessidade de usar múltiplos LoadBalancers, o que reduz custos e organiza melhor o tráfego.
✅ 2. Annotations utilizadas
"kubernetes.io/ingress.class" = "nginx"
"nginx.ingress.kubernetes.io/ssl-redirect" = "false"
"nginx.ingress.kubernetes.io/proxy-body-size" = "8m"
Explicação:
– A primeira define que o Ingress será gerenciado pelo NGINX.
– A segunda evita redirecionamento automático para HTTPS (útil em dev).
– A terceira aumenta o limite de tamanho para uploads no body de requisições (útil para formulários grandes).
✅ 3. Rotas da API
As seguintes rotas são direcionadas ao serviço auth-api
(porta 3000):
– /api
– /forgot
– /reset
– /register
– /login
– /logout
– /user
Exemplo:
Se um usuário acessar https://seusite.com/api/user
, o tráfego será roteado internamente para o serviço da API no cluster.
✅ 4. Rotas do frontend
As seguintes rotas são enviadas para o serviço do frontend (porta 80):
– /reset/
(com barra – para a visualização do formulário por token)
– /static
(arquivos estáticos como CSS e JS)
– /
(todas as outras rotas, como /home
, /dashboard
etc.)
Por que separar /reset
e /reset/
?
Para permitir que o POST /reset
vá para a API e o GET /reset/token
vá para o frontend.
✅ 5. Dependência do Helm
depends_on = [helm_release.ingress_nginx]
Explicação:
Esse bloco garante que o Ingress só será criado depois que o Ingress Controller (instalado via Helm) estiver pronto no cluster.
Esse arquivo é essencial para habilitar o acesso externo com Ingress, e foi cuidadosamente roteado para separar backend e frontend da forma correta.
devops/ingress.tf
resource "kubernetes_ingress_v1" "app_ingress" {
metadata {
name = "app-ingress"
annotations = {
"kubernetes.io/ingress.class" = "nginx"
"nginx.ingress.kubernetes.io/ssl-redirect" = "false"
"nginx.ingress.kubernetes.io/proxy-body-size" = "8m"
}
}
spec {
ingress_class_name = "nginx"
rule {
http {
# Todas as rotas da API
path {
path = "/api"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
path {
path = "/forgot"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
# Rota exata para POST /reset (processamento do formulário)
path {
path = "/reset"
path_type = "Exact"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
# Rota para /reset/[token] (visualização do formulário)
path {
path = "/reset/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_ui.metadata[0].name
port {
number = 80
}
}
}
}
path {
path = "/register"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
path {
path = "/login"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
path {
path = "/logout"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
path {
path = "/user"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_api.metadata[0].name
port {
number = 3000
}
}
}
}
# Serve arquivos estáticos
path {
path = "/static"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_ui.metadata[0].name
port {
number = 80
}
}
}
}
# Todas as outras rotas (como /reset/:token) vão para o frontend
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.auth_ui.metadata[0].name
port {
number = 80
}
}
}
}
}
}
}
depends_on = [helm_release.ingress_nginx]
}
📁 devops/deployment.tf (alterações no backend e frontend)
🔹 Explicando as alterações destacadas em azul
✅ 1. Substituição do IP do LoadBalancer do frontend pelo IP do Ingress Controller
ANTES:
value = "http://${data.kubernetes_service.auth_ui.status[0].load_balancer[0].ingress[0].ip}"
DEPOIS:
value = "http://${data.kubernetes_service.ingress_nginx.status[0].load_balancer[0].ingress[0].ip}"
Explicação:
Com o Ingress gerenciando o tráfego externo, agora o backend usa o IP do Ingress Controller para formar a variável APP_URL
. Isso garante que a aplicação saiba qual é sua URL pública correta.
✅ 2. Adição do data "kubernetes_service"
para o Ingress Controller
data "kubernetes_service" "ingress_nginx" {
metadata {
name = "ingress-nginx-controller"
namespace = "default"
}
depends_on = [helm_release.ingress_nginx]
}
Explicação:
Esse bloco coleta o IP externo atribuído automaticamente ao LoadBalancer do Ingress. Esse IP será usado por outras partes da aplicação (como o backend) para compor URLs públicas corretamente.
✅ 3. Atualização da imagem do frontend para versão v1.1
ANTES:
image = "toticavalcanti/auth-ui:v1.0"
DEPOIS:
image = "toticavalcanti/auth-ui:v1.1"
Explicação:
A nova imagem contém melhorias e o suporte à leitura dinâmica da URL da API a partir do arquivo config.js
gerado no container.
✅ 4. Adição do comando que gera dinamicamente o config.js
com HEREDOC
command = ["/bin/sh", "-c"]
# Usamos a sintaxe de HEREDOC no "args" para evitar problemas de escape
args = [
<<-EOT echo 'window._env_ = { REACT_APP_API_URL: \"${var.react_app_api_url}\" };' > /usr/share/nginx/html/config.js
nginx -g 'daemon off;'
EOT
]
Explicação:
Esse comando roda no container do frontend ao iniciar. Ele cria o arquivo config.js
que define dinamicamente a variável global REACT_APP_API_URL
para o navegador.
✅ 5. Alteração do tipo do serviço auth_ui
(frontend) para ClusterIP
ANTES:
type = "LoadBalancer"
DEPOIS:
type = "ClusterIP"
Explicação:
O frontend não é mais exposto diretamente por um LoadBalancer. Agora ele é acessado internamente via Ingress Controller, o que é mais seguro e econômico.
devops/deployment.tf
data "kubernetes_service" "ingress_nginx" {
metadata {
name = "ingress-nginx-controller"
namespace = "default"
}
depends_on = [helm_release.ingress_nginx]
}
##################################
# SECRETS
##################################
resource "kubernetes_secret" "gmail_credentials" {
metadata {
name = "gmail-credentials"
}
data = {
username = var.gmail_username
password = var.gmail_password
}
}
resource "kubernetes_secret" "mysql_secret" {
metadata {
name = "mysql-secret"
}
data = {
mysql-root-password = base64encode(var.mysql_root_password)
}
}
##################################
# VOLUMES
##################################
resource "kubernetes_persistent_volume" "mysql_pv" {
metadata {
name = "mysql-pv"
labels = {
type = "local"
app = "mysql"
}
}
spec {
capacity = {
storage = "5Gi"
}
access_modes = ["ReadWriteOnce"]
storage_class_name = "manual"
persistent_volume_reclaim_policy = "Retain"
persistent_volume_source {
host_path {
path = "/mnt/data"
type = "DirectoryOrCreate"
}
}
}
}
resource "kubernetes_persistent_volume_claim" "mysql_pvc" {
metadata {
name = "mysql-pvc"
labels = {
app = "mysql"
}
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = "5Gi"
}
}
storage_class_name = "manual"
volume_name = kubernetes_persistent_volume.mysql_pv.metadata[0].name
}
depends_on = [
kubernetes_persistent_volume.mysql_pv
]
}
##################################
# MYSQL: DEPLOYMENT E SERVICE
##################################
resource "kubernetes_deployment" "mysql" {
metadata {
name = "mysql"
labels = {
app = "mysql"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "mysql"
}
}
template {
metadata {
labels = {
app = "mysql"
}
}
spec {
container {
name = "mysql"
image = "mysql:5.7"
env {
name = "MYSQL_ROOT_PASSWORD"
value = var.mysql_root_password
}
env {
name = "MYSQL_DATABASE"
value = "mysql"
}
resources {
limits = {
memory = "512Mi"
cpu = "500m"
}
requests = {
memory = "256Mi"
cpu = "250m"
}
}
port {
container_port = 3306
name = "mysql"
}
volume_mount {
name = "mysql-persistent-storage"
mount_path = "/var/lib/mysql"
}
readiness_probe {
tcp_socket {
port = 3306
}
initial_delay_seconds = 15
period_seconds = 10
}
liveness_probe {
tcp_socket {
port = 3306
}
initial_delay_seconds = 20
period_seconds = 10
}
}
volume {
name = "mysql-persistent-storage"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.mysql_pvc.metadata[0].name
}
}
}
}
}
depends_on = [kubernetes_persistent_volume_claim.mysql_pvc]
}
resource "kubernetes_service" "mysql_service" {
metadata {
name = "mysql-service"
labels = {
app = "mysql"
}
}
spec {
selector = {
app = "mysql"
}
type = "ClusterIP"
port {
port = 3306
target_port = 3306
name = "mysql"
}
}
}
##################################
# BACKEND: DEPLOYMENT E SERVICE
##################################
resource "kubernetes_deployment" "auth_api" {
metadata {
name = "auth-api"
labels = {
app = "auth-api"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "auth-api"
}
}
template {
metadata {
labels = {
app = "auth-api"
}
}
spec {
init_container {
name = "wait-for-mysql"
image = "busybox:1.28"
command = [
"sh", "-c",
"until nc -z mysql-service 3306; do echo waiting for mysql; sleep 2; done;"
]
}
container {
name = "auth-api"
image = "toticavalcanti/fiber-auth-api:v1.0"
env {
name = "MYSQL_ROOT_PASSWORD"
value = var.mysql_root_password
}
env {
name = "DB_DSN"
value = "root:${var.mysql_root_password}@tcp(mysql-service:3306)/mysql?parseTime=true"
}
env {
name = "GMAIL_USERNAME"
value_from {
secret_key_ref {
name = kubernetes_secret.gmail_credentials.metadata[0].name
key = "username"
}
}
}
env {
name = "GMAIL_PASSWORD"
value_from {
secret_key_ref {
name = kubernetes_secret.gmail_credentials.metadata[0].name
key = "password"
}
}
}
env {
name = "APP_URL"
value = "http://${data.kubernetes_service.ingress_nginx.status[0].load_balancer[0].ingress[0].ip}"
}
port {
container_port = 3000
name = "http"
}
resources {
limits = {
cpu = "250m"
memory = "256Mi"
}
requests = {
cpu = "100m"
memory = "128Mi"
}
}
readiness_probe {
http_get {
path = "/api/health"
port = 3000
}
initial_delay_seconds = 15
period_seconds = 10
}
liveness_probe {
http_get {
path = "/api/health"
port = 3000
}
initial_delay_seconds = 20
period_seconds = 10
}
}
}
}
}
depends_on = [
kubernetes_deployment.mysql,
kubernetes_service.mysql_service
]
}
resource "kubernetes_service" "auth_api" {
metadata {
name = "auth-api-service"
labels = {
app = "auth-api"
}
}
spec {
selector = {
app = "auth-api"
}
type = "ClusterIP"
port {
port = 3000
target_port = 3000
name = "http"
}
}
}
##################################
# FRONTEND: DEPLOYMENT E SERVICE
##################################
resource "kubernetes_deployment" "auth_ui" {
metadata {
name = "auth-ui"
labels = {
app = "auth-ui"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "auth-ui"
}
}
template {
metadata {
labels = {
app = "auth-ui"
}
}
spec {
container {
name = "auth-ui"
image = "toticavalcanti/auth-ui:v1.1"
env {
name = "REACT_APP_API_URL"
value = var.react_app_api_url
}
command = ["/bin/sh", "-c"]
# Usamos a sintaxe de HEREDOC no "args" para evitar problemas de escape
args = [
<<-EOT echo 'window._env_ = { REACT_APP_API_URL: \"${var.react_app_api_url}\" };' > /usr/share/nginx/html/config.js
nginx -g 'daemon off;'
EOT
]
port {
container_port = 80
name = "http"
}
readiness_probe {
http_get {
path = "/index.html"
port = 80
}
initial_delay_seconds = 10
period_seconds = 5
failure_threshold = 3
success_threshold = 1
timeout_seconds = 1
}
liveness_probe {
http_get {
path = "/index.html"
port = 80
}
initial_delay_seconds = 15
period_seconds = 10
failure_threshold = 3
success_threshold = 1
timeout_seconds = 1
}
resources {
limits = {
cpu = "200m"
memory = "256Mi"
}
requests = {
cpu = "100m"
memory = "128Mi"
}
}
}
}
}
}
depends_on = [
kubernetes_deployment.auth_api
]
}
resource "kubernetes_service" "auth_ui" {
metadata {
name = "auth-ui-service"
labels = {
app = "auth-ui"
}
}
spec {
selector = {
app = "auth-ui"
}
type = "ClusterIP"
port {
port = 80
target_port = 80
name = "http"
}
}
}
📁 devops/main.tf (configuração principal do cluster)
🔹 Explicando as alterações destacadas em azul
✅ 1. Adição do provider Helm
helm = {
source = "hashicorp/helm"
version = "2.17.0"
}
Explicação:
Foi adicionado o provedor helm
para permitir a instalação de pacotes via Helm Charts, como o Ingress Controller, diretamente pelo Terraform.
✅ 2. Atualização da versão do Kubernetes
ANTES:
version = "1.31.1-do.3"
DEPOIS:
# Ajuste para a versão que desejar
version = "1.32.2-do.0"
Explicação:
A versão do cluster foi atualizada para a mais recente compatível com os recursos desejados. O comentário acima ajuda a lembrar que é possível customizar conforme necessário.
✅ 3. Atualização do tamanho do nó (droplet)
ANTES:
size = "s-1vcpu-2gb"
DEPOIS:
size = "s-2vcpu-4gb"
Explicação:
Foi aumentado o poder de processamento e memória dos nós do cluster, melhorando o desempenho da aplicação e suportando serviços como o Ingress NGINX com mais estabilidade.
✅ 4. Ajustes no script local-exec
para exibir mensagens mais claras
ANTES:
echo "Kubeconfig salvo com sucesso!"
echo "Aguardando cluster ficar pronto..."
DEPOIS:
echo "Kubeconfig salvo. Aguardando cluster ficar pronto..."
Explicação:
A mensagem foi simplificada para tornar o processo de criação do cluster mais direto no terminal. A funcionalidade permanece a mesma.
✅ 5. Adição do bloco provider "helm"
provider "helm" {
kubernetes {
host = digitalocean_kubernetes_cluster.meu_cluster.endpoint
token = digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].token
cluster_ca_certificate = base64decode(
digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].cluster_ca_certificate
)
}
}
Explicação:
Esse bloco configura o provedor Helm para se conectar ao mesmo cluster Kubernetes criado via DigitalOcean. Ele permite que o Terraform aplique charts Helm (como o Ingress NGINX) de forma automatizada, sem depender de comandos manuais.
devops/main.tf
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "2.33.0"
}
helm = {
source = "hashicorp/helm"
version = "2.17.0"
}
}
}
provider "digitalocean" {
token = var.do_token
}
# Criação do Cluster Kubernetes
resource "digitalocean_kubernetes_cluster" "meu_cluster" {
name = "meu-cluster"
region = "nyc1"
# Ajuste para a versão que desejar
version = "1.32.2-do.0"
node_pool {
name = "default-pool"
size = "s-2vcpu-4gb"
node_count = 2
}
provisioner "local-exec" {
command = <<-EOT
echo "Iniciando kubeconfig..."
doctl kubernetes cluster kubeconfig save meu-cluster
echo "Kubeconfig salvo. Aguardando cluster ficar pronto..."
sleep 45
kubectl wait --for=condition=Ready nodes --all --timeout=300s
echo "Cluster está pronto!"
EOT
}
}
provider "kubernetes" {
host = digitalocean_kubernetes_cluster.meu_cluster.endpoint
token = digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].token
cluster_ca_certificate = base64decode(
digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].cluster_ca_certificate
)
}
provider "helm" {
kubernetes {
host = digitalocean_kubernetes_cluster.meu_cluster.endpoint
token = digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].token
cluster_ca_certificate = base64decode(
digitalocean_kubernetes_cluster.meu_cluster.kube_config[0].cluster_ca_certificate
)
}
}
📁 devops/outputs.tf (ajustes para uso com Ingress)
🔹 Explicando as alterações destacadas em azul
✅ 1. Remoção do output do LoadBalancer do frontend
ANTES:
output "auth_ui_load_balancer_ip" {
value = try(kubernetes_service.auth_ui.status[0].load_balancer[0].ingress[0].ip, "Pending")
description = "The external IP of the load balancer for the auth UI service (if available)"
}
DEPOIS: Vazio
<!-- Removido completamente -->
Explicação:
Esse output foi removido porque o serviço do frontend deixou de usar LoadBalancer e passou a usar ClusterIP
. Agora o acesso externo é feito via Ingress.
✅ 2. Adição do output do IP ou hostname do Ingress
output "ingress_controller_ip_or_hostname" {
value = try(
kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].ip,
try(
kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].hostname,
"Pending - External IP/Hostname not yet assigned"
)
)
description = "O IP ou hostname atribuído ao Ingress pela DigitalOcean"
}
Explicação:
Esse output mostra qual IP ou hostname foi atribuído ao Ingress pela DigitalOcean. Ele é útil para saber como acessar o cluster externamente via navegador ou domínio.
✅ 3. Atualização do output application_url
ANTES:
value = try(
"http://${kubernetes_service.auth_ui.status[0].load_balancer[0].ingress[0].ip}",
"Pending - External IP not yet assigned"
)
DEPOIS:
value = try(
"http://${kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].ip}",
try(
"http://${kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].hostname}",
"Pending - External IP/Hostname not yet assigned"
)
)
Explicação:
Antes, o endereço externo da aplicação vinha direto do LoadBalancer do frontend. Agora ele vem do Ingress, que centraliza todo o tráfego da aplicação. Isso é necessário para ambientes que usam Ingress Controller.
devops/outputs.tf
# Frontend UI Service
output "auth_ui_service_name" {
value = kubernetes_service.auth_ui.metadata[0].name
description = "The name of the Kubernetes service for the auth UI"
}
output "auth_ui_service_type" {
value = kubernetes_service.auth_ui.spec[0].type
description = "The type of the Kubernetes service for the auth UI"
}
# Backend API Service
output "auth_api_service_name" {
value = kubernetes_service.auth_api.metadata[0].name
description = "The name of the Kubernetes service for the auth API"
}
output "auth_api_service_type" {
value = kubernetes_service.auth_api.spec[0].type
description = "The type of the Kubernetes service for the auth API"
}
output "auth_api_cluster_ip" {
value = kubernetes_service.auth_api.spec[0].cluster_ip
description = "The Cluster IP of the auth API service"
}
# MySQL Service
output "mysql_service_name" {
value = kubernetes_service.mysql_service.metadata[0].name
description = "The name of the Kubernetes service for MySQL"
}
output "mysql_connection_string" {
value = "mysql://root:${var.mysql_root_password}@tcp(${kubernetes_service.mysql_service.metadata[0].name}:3306)/mysql"
sensitive = true
description = "MySQL connection string (sensitive)"
}
# Exibir IP/Host do Ingress
output "ingress_controller_ip_or_hostname" {
value = try(
kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].ip,
try(
kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].hostname,
"Pending - External IP/Hostname not yet assigned"
)
)
description = "O IP ou hostname atribuído ao Ingress pela DigitalOcean"
}
# URL final da aplicação via Ingress
output "application_url" {
value = try(
"http://${kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].ip}",
try(
"http://${kubernetes_ingress_v1.app_ingress.status[0].load_balancer[0].ingress[0].hostname}",
"Pending - External IP/Hostname not yet assigned"
)
)
description = "URL para acessar a aplicação (frontend) via Ingress"
}
# URL interna para a API
output "api_internal_url" {
value = "http://${kubernetes_service.auth_api.metadata[0].name}:3000/api"
description = "URL interna para a API"
}
📁 devops/variables.tf (ajuste fino na URL da API)
🔹 Explicando a alteração destacada em azul
✅ 1. Ajuste no valor padrão da variável react_app_api_url
ANTES:
default = "/api"
DEPOIS:
default = "/api/"
Explicação:
Esse pequeno ajuste evita erros de concatenação de URLs no frontend (por exemplo: /api/forgot
funciona, mas /apiforgot
não).
O uso da barra final garante que o caminho base sempre esteja correto ao montar rotas como ${REACT_APP_API_URL}forgot
, register
, etc.
devops/variables.tf
variable "do_token" {
type = string
sensitive = true
description = "Token de acesso para a API da DigitalOcean"
}
variable "gmail_username" {
type = string
description = "Endereço do Gmail para enviar emails"
}
variable "gmail_password" {
type = string
sensitive = true
description = "Senha do Gmail ou Senha de App"
}
variable "mysql_root_password" {
description = "Senha do root para o MySQL"
type = string
}
variable "react_app_api_url" {
description = "URL para a API do auth-api"
type = string
default = "/api/"
}
📌 Observação importante sobre as imagens Docker utilizadas
Durante este tutorial, utilizei a imagem atualizada do frontend: toticavalcanti/auth-ui:v1.1
.
Essa versão inclui uma correção importante onde a requisição await axios.post('forgot', ...)
foi atualizada para await axios.post('/forgot', ...)
, garantindo que o caminho funcione corretamente com o Ingress e evite falhas no envio do formulário de recuperação de senha.
Caso você deseje pular a etapa de construir as imagens por conta própria, pode utilizar diretamente as minhas imagens públicas que subi no Docker Hub:
toticavalcanti/fiber-auth-api:v1.0
– Backend (Go + Fiber)toticavalcanti/auth-ui:v1.1
– Frontend (React com ajuste no path do forgot)
No entanto, se preferir criar suas próprias imagens Docker personalizadas, basta clonar os repositórios do projeto (ou baixá-los como ZIP), fazer as modificações desejadas, e então construir suas próprias imagens com os comandos docker build
e docker push
para o seu repositório do Docker Hub, conforme explicado em detalhes na aula passada, a aula 13.
Fica a seu critério: usar as minhas imagens já prontas para agilizar o processo, ou customizar com liberdade total.
🧪 Teste final
Primeira coisa a fazer é logar na cloud que você vai usar, nesse caso, a Digital Ocean:
doctl auth init --access-token seu_token_da_digitalocean
Entre na pasta devops e rode os comandos do terraform:
cd devops // entrar na pasta devops
doctl kubernetes options versions
terraform init //or terraform init -upgrade
terraform apply -auto-approve
Conecte-se ao seu Cluster, pegando suas credenciais:
doctl kubernetes cluster kubeconfig save meu-cluster
Este comando salva automaticamente a configuração do cluster no seu arquivo kubeconfig padrão (geralmente localizado em ~/.kube/config
).
Execute os comandos:
kubectl get svc ingress-nginx-controller
Acesse no navegador: http://IP-DO-INGRESS
/
→ frontend
/api
→ backend (ex: /api/user
)
✅ Conclusão
Agora nossa aplicação está com uma arquitetura muito mais robusta, com:
- Tráfego centralizado via Ingress Controller
- Redução de custos com IPs públicos
- Pronto para suportar HTTPS com cert-manager (próxima aula!)
📦 Os arquivos desta aula estão disponíveis no repositório.
Por essa aula é só, até a próxima. \o/