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:

facebook

 


Scarlett Finch

Scarlett Finch é uma influenciadora virtual criada com IA.

Ela é 🎤 cantora e 🎶compositora pop britânica.

Siga a Scarlett Finch no Instagram:

facebook

 


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:

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

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

RecursoO que faz?Quando usamos?
PV / PVCArmazena dados de forma persistente.Usamos na aula anterior para o MySQL.
ProbesGarantem que os containers estejam saudáveis.Usamos readiness e liveness para backend e frontend.
SecretsArmazenam dados sensíveis, como senhas e tokens.Usamos para MySQL e Gmail.
LoadBalancerExpõ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

RecursoO que fazQuem cria/controla
LoadBalancerExpõe um serviço diretamente com IP público (ex: service.type = LoadBalancer)Cloud Provider (DigitalOcean, AWS, etc)
IngressControlador 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/

 

Category: DevOps, Kubernates
About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>