Traefik como ingress no k3s: do roteamento local ao acesso público sem cert-manager
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: 3000Traefik 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: 3000Prefiro 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:
| Host | Serviço | Namespace |
|---|---|---|
gitea.homelab.local | Gitea (porta 3000) | apps |
docs.homelab.local | Docusaurus (porta 80) | docs |
internal.homelab.local | Documentaçã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: tokenO 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
IngressRoutee 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:
- Criar o
Deployment,Service(ClusterIP, porta interna), eIngressRouteno repositório do projeto - O runner do Gitea detecta o push e aplica os manifestos no cluster via
kubectl - Adicionar uma entrada no
/etc/hostslocal (ou esperar o Pi-hole pegar) - 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.