Quando comecei a montar meu homelab num Raspberry Pi 4B, uma das primeiras perguntas foi: como faço para que múltiplos serviços rodando no cluster respondam cada um no seu próprio domínio, sem abrir uma porta diferente para cada um?

Eu já sabia a resposta — lembrava de ter trabalhado com EKS na AWS no passado, onde o ingress era um componente separado que precisava ser instalado e configurado. No k3s, ele já vem incluso, nome e tudo: Traefik.


O que o k3s já traz por padrão

O k3s não é Kubernetes “vanilla”. Ele vem com algumas escolhas já feitas:

  • Traefik como ingress controller
  • CoreDNS para resolução de nomes
  • Flannel como CNI
  • local-path-provisioner para volumes
  • SQLite em vez de etcd — muito mais leve

Essas decisões de design fazem do k3s uma escolha natural para hardware ARM com recursos limitados, como o Raspberry Pi. E se você vem do mundo do nginx-ingress-controller ou esperava configurar seu próprio ingress, é uma surpresa agradável descobrir que tudo já está lá — sem setup necessário antes de começar a usar.

O Traefik que vem com o k3s expõe dois entrypoints por padrão:

  • web → porta 80 (HTTP)
  • websecure → porta 443 (HTTPS)

E é aqui que começa a diferença importante entre usar o Traefik “do jeito padrão” e usar do jeito que ele foi desenhado para ser usado.


IngressRoute vs Ingress: qual a diferença?

O Kubernetes tem um recurso nativo chamado Ingress que qualquer ingress controller pode implementar. É a API padrão, funciona com qualquer coisa, e você provavelmente vai encontrar exemplos dele em toda documentação.

O Traefik suporta Ingress padrão. Mas também expõe seus próprios CRDs (Custom Resource Definitions) — e o principal é o IngressRoute.

Por que usar IngressRoute em vez de Ingress?

Porque o Ingress padrão foi desenhado para ser genérico. Ele usa annotations que cada controller interpreta de forma diferente, criando uma camada de indireção. Com o IngressRoute, você escreve a intenção diretamente no YAML — sem annotations mágicas, sem precisar memorizar o que cada controller aceita.

Na prática, a diferença fica assim:

Ingress padrão:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gitea
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  rules:
  - host: gitea.homelab.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gitea
            port:
              number: 3000

Traefik IngressRoute:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: gitea
  namespace: apps
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`gitea.homelab.local`)
      kind: Rule
      services:
        - name: gitea
          port: 3000

Prefiro o segundo. É mais explícito — você vê exatamente qual entrypoint, qual regra de match, e qual serviço de destino. Nenhuma camada implícita.


A configuração do homelab: roteamento por hostname

No meu cluster tenho serviços espalhados por diferentes namespaces, cada um respondendo no seu próprio hostname:

HostServiçoNamespace
gitea.homelab.localGitea (porta 3000)apps
docs.homelab.localDocusaurus (porta 80)docs
internal.homelab.localDocumentação interna (porta 80)docs

Cada um tem seu próprio IngressRoute no namespace correspondente. O Traefik descobre todos automaticamente porque observa CRDs em todos os namespaces do cluster.

O DNS para *.homelab.local eu resolvo localmente — via /etc/hosts nas máquinas de onde acesso, ou via Pi-hole quando quero que a rede inteira veja. Não é elegante, mas é suficiente para uso pessoal.


TLS: por que eu não tenho cert-manager

A resposta direta: recursos. O Raspberry Pi 4B tem 4 GB de RAM, e o cert-manager traz três pods permanentes (controller, webhook, cainjector) que consomem entre 100–200 MB em idle. No meu orçamento de memória, isso é significativo.

Mas a questão maior é conceitual: por que ter TLS local se o acesso externo já é HTTPS por outro caminho?

Minha solução foi o Cloudflare Tunnel. Em vez de expor portas no roteador e gerenciar certificados Let’s Encrypt, rodo um pod cloudflared no cluster que abre uma conexão outbound com a rede da Cloudflare. O tráfego público entra pela Cloudflare (HTTPS com o certificado deles), atravessa o túnel, e chega aos serviços internos como HTTP puro.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
  namespace: infra
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: cloudflared
        image: cloudflare/cloudflared:latest
        args:
        - tunnel
        - --no-autoupdate
        - run
        - --token
        - $(TUNNEL_TOKEN)
        env:
        - name: TUNNEL_TOKEN
          valueFrom:
            secretKeyRef:
              name: cloudflared-secret
              key: token

O token vem de um Secret criado manualmente uma vez no cluster. O mapeamento de domínio público para serviço interno vive no painel do Cloudflare Zero Trust — sem YAML, sem redeployar nada.

Resultado: TLS na borda pública (a Cloudflare cuida disso), HTTP puro internamente (o Traefik roteia), sem cert-manager, sem renovação de certificado para gerenciar.


O que isso resolve e o que não resolve

Essa configuração funciona bem para um homelab porque:

  • Nenhuma porta aberta no roteador
  • Nenhuma exposição de IP público
  • TLS automático via Cloudflare, sem Let’s Encrypt
  • Recursos mínimos no Pi
  • Adicionar novos serviços é trivial: um IngressRoute e uma entrada no painel da Cloudflare

O que não resolve: se você quiser TLS interno — requisições dentro do cluster via HTTPS — ainda vai precisar de cert-manager ou uma CA interna. No meu caso, o tráfego interno é HTTP e eu aceito esse trade-off.


Adicionando um novo serviço

O fluxo prático quando subo algo novo no cluster:

  1. Criar o Deployment, Service (ClusterIP, porta interna), e IngressRoute no repositório do projeto
  2. O runner do Gitea detecta o push e aplica os manifestos no cluster via kubectl
  3. Adicionar uma entrada no /etc/hosts local (ou esperar o Pi-hole pegar)
  4. Se eu quiser acesso externo: adicionar o hostname público no Cloudflare Zero Trust apontando para http://service.namespace.svc.cluster.local:port

Hoje cada projeto tem seu próprio pipeline no Gitea que cuida do deploy — sem apply manual.

Não existe um passo de “recarregar configuração do ingress”. O Traefik observa CRDs em tempo real e começa a rotear imediatamente.

É essa combinação — k3s com Traefik embutido, IngressRoute declarativo, e Cloudflare Tunnel para exposição pública — que tornou o lado de rede do homelab simples o suficiente para eu focar no que realmente importa: melhoria contínua do ambiente e dos seus serviços.