
Self-hosting de LLMs: controle sobre dados e infraestrutura
AI Summary
This article discusses the concept of self-hosting large language models (LLMs) and the benefits it can provide in terms of data control, infrastructure management, and technological autonomy. The key points are: 1. Self-hosting LLMs allows users to have complete control over their data, infrastructure, and model customization, without relying on external APIs or services. This ensures privacy, predictable costs, and unrestricted experimentation. 2. The article outlines the technical stack required for self-hosting, including Kubernetes, Terraform, Helm, and various applications like Ollama (LLM backend), LibreChat (web interface), MongoDB (data storage), and MeiliSearch (semantic search). 3. The article takes the reader through a step-by-step process, starting with setting up a local Kubernetes cluster using Minikube, and then deploying the Ollama LLM with GPU acceleration. This hands-on approach aims to provide a solid conceptual foundation and appreciation for the underlying infrastructure. 4. The key motivation behind self-hosting LLMs is not just about cost savings or privacy, but rather about achieving technological autonomy. By mastering the entire stack, from the LLM to the supporting infrastructure, users can become independent of external service providers and have full control over one of the most transformative technologies of our time. 5. The skills acquired in managing this LLM-focused infrastructure are directly applicable to managing any modern distributed system, making this a valuable learning experience for developers and system administrators.
Original Description
Você provavelmente já teve esse pensamento: "E se eu pudesse rodar minha própria IA, sem depender de APIs externas, sem limites de tokens, sem preocupações com privacidade?" Não é só você. Estamos vivendo um momento histórico onde rodar modelos de linguagem de ponta deixou de ser privilégio exclusivo de gigantes tech. Com uma GPU NVIDIA razoável e as ferramentas certas, você pode ter sua própria infraestrutura de IA, completa, privada, sob seu total controle. Privacidade Real, Não Apenas Expectativas Seus dados confidenciais, conversas estratégicas, código proprietário, tudo fica em sua infraestrutura. Não há logs em servidores de terceiros, não há treinamento de modelos com suas informações, não há termos de serviço que mudam da noite para o dia. Custo Previsível vs. Incerteza de APIs API pública: $20/mês base + $0.002/1k tokens = ??? (aquele projeto que explodiu custou quanto mesmo?) Self-hosted: GPU cloud: $0.50/hora = $360/mês fixo (ou hardware próprio: custo único) Experimentação Sem Limites Quer testar 47 variações de prompt? Fazer fine-tuning com datasets proprietários? Rodar benchmarks exaustivos? Com sua infraestrutura, você não está queimando créditos ou batendo em rate limits, está usando recursos que já são seus. Modelos Customizados e Especializados A verdadeira mágica acontece quando você vai além dos modelos genéricos. Fine-tuning para seu domínio específico, embeddings customizados, agentes especializados, tudo isso requer controle total sobre a stack. Você baixa o Ollama, roda docker run, puxa o modelo llama3:latest, e boom, está conversando com uma IA rodando no seu hardware. Mágico. Aí começam as perguntas: "Como eu coloco uma interface web bonita nisso?" "E se eu quiser múltiplos modelos com contextos diferentes?" "Como faço backup das conversas?" "E se eu quiser adicionar busca semântica?" "Como deixo isso acessível para meu time sem expor ao mundo?" "O que acontece se minha máquina reiniciar?" De repente, você não está mais brincando com Docker, está construindo infraestrutura distribuída. Esse é o momento onde muitos ficam presos em soluções frágeis: # A solução que funciona... até não funcionar mais: $ docker run -d ollama $ docker run -d mongodb $ docker run -d librechat # (Ctrl+C para sair) # (Máquina reinicia) # (Tudo morre) # (Você não lembra os comandos exatos) # (Configurações se perderam) Você precisa de: Orquestração de múltiplos serviços interdependentes Persistência de dados garantida Service discovery automático Roteamento inteligente de tráfego Gestão de secrets e configurações Alta disponibilidade e auto-recuperação Escalabilidade quando crescer Em outras palavras: você precisa de Kubernetes. Este guia nasceu exatamente dessa necessidade. Começou com o desejo simples de rodar Ollama + LibreChat localmente, e evoluiu para uma exploração completa de como construir infraestrutura moderna que: Sobrevive a reinicializações Pode ser recriada do zero em minutos Tem versionamento e auditoria de mudanças Escala de um laptop até dezenas de servidores Segue os mesmos padrões que grandes empresas usam O que você vai construir: Uma stack completa de IA self-hosted com: Ollama — Seus modelos de IA rodando com aceleração GPU LibreChat — Interface elegante estilo ChatGPT MongoDB — Persistência de conversas e contextos MeiliSearch — Busca semântica NGINX Ingress — Acesso seguro e profissional O que você vai aprender: Muito mais do que apenas "fazer funcionar". Você vai entender a evolução completa de infraestrutura moderna: Capítulo 1 — Manual: Configurar tudo na mão para entender conceitos Capítulo 2 — Terraform + K8s: Automatizar com Infrastructure as Code Capítulo 3 — Terraform + Helm: Abstrações corretas emergem Capítulo 4 — Terraform + ArgoCD + Helm: Padrões enterprise que escalam Self-hosting não é só sobre economizar dinheiro ou ter privacidade. É sobre autonomia tecnológica. Quando você domina a stack completa, desde o modelo de IA até a infraestrutura que o suporta, você não está mais dependente de: Empresas que podem fechar APIs repentinamente Mudanças unilaterais de preço Restrições arbitrárias de uso Latências de datacenters distantes Você tem controle real sobre uma das tecnologias mais transformadoras da nossa era. E a melhor parte? As habilidades que você vai desenvolver gerenciando infraestrutura para LLMs são exatamente as mesmas necessárias para gerenciar qualquer sistema distribuído moderno. Você está aprendendo Kubernetes através de um caso de uso real. Ao longo desta jornada, vamos implantar uma stack completa com: Ollama — Backend de modelos de IA com aceleração por GPU NVIDIA Uma aplicação stateful que precisa de recursos especializados (GPU) e configuração cuidadosa. LibreChat — Interface web moderna para interação com LLMs Aplicação frontend que se comunica com múltiplos backends. MongoDB — Banco de dados NoSQL Armazenamento persistente com requisitos de backup e recovery. MeiliSearch — Motor de busca Índices que precisam ser mantidos e sincronizados. NGINX Ingress — Roteamento inteligente de tráfego Porta de entrada para todo o tráfego HTTP/HTTPS. # Fundação Kubernetes: 1.28+ └─ Minikube: Cluster local para desenvolvimento └─ Docker: Runtime de containers └─ NVIDIA Container Toolkit: Ponte para GPUs # Gerenciamento kubectl: CLI oficial do Kubernetes Helm: Gerenciador de pacotes (pense npm/apt para K8s) Terraform: Infrastructure as Code (vem nos capítulos 2-4) ArgoCD: GitOps operator (capítulo 4) # Aplicações Ollama: ollama/ollama (com GPU) LibreChat: ghcr.io/danny-avila/librechat MongoDB: bitnami/mongodb MeiliSearch: getmeili/meilisearch Cada ferramenta aqui tem um propósito específico, e ao longo da jornada você vai entender exatamente quando e por que usar cada uma. Pode parecer contraproducente começar fazendo tudo na mão quando o objetivo final é automação total. Mas há método nessa aparente loucura: Fundação conceitual sólida — Você precisa entender Pods, Services, Ingress, DNS interno Debugging efetivo — Quando algo quebra em produção (e vai quebrar), você precisa saber o que procurar Apreciação das abstrações — Você só valoriza Helm/Terraform depois de escrever YAML manualmente Conhecimento das convenções — Cada ferramenta tem suas idiossincrasias Primeiro, vamos criar um playground seguro, um cluster Kubernetes rodando localmente na sua máquina. # Arch Linux (ajuste para sua distro) pacman -S minikube nvidia-container-toolkit libnvidia-container helm ollama terraform # Iniciar cluster com suporte a GPU minikube start \ --driver docker \ --container-runtime docker \ --gpus all \ --memory 8192 \ --cpus 4 O que está acontecendo aqui? Quando você executa minikube start, uma sequência de eventos é disparada: Criação do node: Minikube cria um container Docker que atua como o "servidor" do seu cluster Instalação do Kubernetes: Dentro desse container, componentes core do K8s são instalados: kube-apiserver — O cérebro, recebe todas as requisições etcd — Banco de dados distribuído que guarda o estado kube-scheduler — Decide em qual node cada Pod vai rodar kube-controller-manager — Garante que o estado atual = estado desejado kubelet — Agente que roda em cada node, gerencia containers Runtime de containers: Docker é configurado como o runtime (quem efetivamente roda os containers) Exposição de GPUs: --gpus all mapeia as GPUs do host para dentro do cluster O resultado? Um cluster Kubernetes real, funcionando completamente na sua máquina, isolado e seguro para experimentação. # Ativar addon do NGINX Ingress minikube addons enable ingress # Verificar que está rodando minikube kubectl -- get pods -n ingress-nginx Mas o que é um Ingress Controller, afinal? Imagine seu cluster Kubernetes como um prédio comercial com vários escritórios (Pods). Cada escritório tem um endereço interno, mas só o porteiro (Ingress Controller) sabe como rotear visitantes externos para o escritório correto baseado no nome que eles pedem. Sem Ingress: Usuário → NodePort :30080 → Pod1 Usuário → NodePort :30081 → Pod2 Usuário → NodePort :30082 → Pod3 Você precisa lembrar portas diferentes para cada aplicação. Horrível. Com Ingress: Usuário → http://app1.empresa.com → Ingress → Pod1 Usuário → http://app2.empresa.com → Ingress → Pod2 Usuário → http://app3.empresa.com → Ingress → Pod3 Você usa hostnames intuitivos. Civilizado. O NGINX Ingress Controller é uma das implementações mais populares, rodando um proxy NGINX otimizado que lê configurações de Ingress e configura rotas automaticamente. minikube kubectl -- create ns ollama Por que namespaces importam? Namespaces são isolamento lógicos. Pense neles como pastas em um sistema de arquivos: cluster/ ├── default/ # Namespace padrão, evite usar ├── kube-system/ # Componentes core do K8s ├── kube-public/ # Recursos públicos ├── ollama/ # Nossa aplicação └── librechat/ # Outra aplicação (vem depois) Benefícios práticos: Organização: Separa aplicações logicamente Permissões: RBAC pode ser aplicado por namespace Quotas: Limitar recursos por aplicação Isolamento de rede: Policies podem restringir comunicação Helm é frequentemente chamado de "gerenciador de pacotes do Kubernetes", mas é mais que isso, é um template engine com gerenciamento de lifecycle. # Adicionar repositório helm repo add ollama-helm https://otwld.github.io/ollama-helm/ helm repo update # Criar arquivo de configuração cat > ollama-values.yaml <<EOF ollama: gpu: enabled: true type: nvidia number: 1 models: - llama2 ingress: enabled: true hosts: - host: ollama.glukas.space paths: - path: / pathType: Prefix EOF # Instalar helm install ollama ollama-helm/ollama \ -f ollama-values.yaml \ --namespace ollama O que o Helm está fazendo por baixo dos panos? Quando você executa helm install, esta sequência acontece: Template rendering: values.yaml + chart templates → YAMLs do Kubernetes Helm pega seus values e injeta nos templates do chart, gerando Deployments, Services, ConfigMaps, etc. Dependency resolution: Pre-install hooks: Resource creation: kubectl apply Release tracking: Se você executar minikube kubectl get all -n ollama, verá algo como: NAME READY STATUS RESTARTS AGE pod/ollama-5d9c8f7b6-xk2j9 1/1 Running 0 2m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) service/ollama ClusterIP 10.96.234.123 <none> 11434/TCP NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/ollama 1/1 1 1 2m NAME DESIRED CURRENT READY AGE replicaset.apps/ollama-5d9c8f7b6 1 1 1 2m Cada linha representa um recurso Kubernetes criado pelo Helm. Para acessar via hostname em desenvolvimento: # Descobrir IP do cluster minikube ip # Exemplo de saída: 192.168.49.2 # Adicionar ao /etc/hosts echo "192.168.49.2 ollama.glukas.space" | sudo tee -a /etc/hosts Como isso funciona? Seu navegador tenta resolver ollama.glukas.space Sistema operacional consulta /etc/hosts primeiro Encontra 192.168.49.2 (IP do Minikube) Requisição vai para o cluster Ingress Controller vê o header Host: ollama.glukas.space Roteia para o Service do Ollama Service distribui para Pods Em produção, você usaria DNS real (Route53, CloudFlare, etc), mas o fluxo é idêntico. O LibreChat é uma aplicação que possui: 4 componentes interdependentes (LibreChat, MongoDB, MeiliSearch, Ollama) Secrets management (chaves, senhas, tokens) Service discovery (apps encontrando umas às outras) Configuração hierárquica (YAML dentro de YAML) # Gerar secrets criptograficamente seguros JWT_SECRET=$(openssl rand -hex 32) JWT_REFRESH_SECRET=$(openssl rand -hex 32) CREDS_KEY=$(openssl rand -hex 32) CREDS_IV=$(openssl rand -hex 16) # Criar Secret no Kubernetes kubectl create secret generic librechat-credentials-env \ --from-literal=JWT_SECRET="${JWT_SECRET}" \ --from-literal=JWT_REFRESH_SECRET="${JWT_REFRESH_SECRET}" \ --from-literal=CREDS_KEY="${CREDS_KEY}" \ --from-literal=CREDS_IV="${CREDS_IV}" \ --from-literal=MONGO_URI="mongodb://librechat-mongodb:27017/LibreChat" \ --from-literal=MEILI_HOST="http://librechat-meilisearch:7700" \ --from-literal=OLLAMA_BASE_URL="http://ollama.ollama.svc.cluster.local:11434" \ --namespace librechat Secrets vs ConfigMaps: Quando usar cada um? Aspecto ConfigMap Secret Propósito Configuração pública Dados sensíveis Encoding Plain text Base64 (não é encriptação!) Uso típico Feature flags, URLs públicas Senhas, tokens, chaves Auditoria Pode ser commitado no Git NUNCA commitar Exemplo API_ENDPOINT=https://api.com API_KEY=sk_live_xxxxx Importante: Base64 não é encriptação! É apenas encoding. Em produção, use soluções como: Sealed Secrets (Bitnami) External Secrets Operator + HashiCorp Vault SOPS (Mozilla) Cloud provider KMS (AWS Secrets Manager, GCP Secret Manager) O LibreChat chart espera configuração em um formato específico: cat > librechat-values.yaml <<'EOF' # Configuração de ambiente (nivel 1) config: APP_TITLE: "LibreChat + Ollama" HOST: "0.0.0.0" PORT: "3080" SEARCH: "true" MONGO_URI: "mongodb://librechat-mongodb:27017/LibreChat" MEILI_HOST: "http://librechat-meilisearch:7700" # Configuração da aplicação (nivel 2) librechat: configEnv: ALLOW_REGISTRATION: "true" # ATENÇÃO: YAML dentro de YAML (nivel 3) configYamlContent: | version: 1.1.5 cache: true endpoints: custom: - name: "Ollama" apiKey: "ollama" # DNS interno do Kubernetes: <service>.<namespace>.svc.cluster.local baseURL: "http://ollama.ollama.svc.cluster.local:11434/v1" models: default: - "llama2:latest" fetch: true titleConvo: true titleModel: "llama2:latest" summarize: false summaryModel: "llama2:latest" forcePrompt: false modelDisplayLabel: "Ollama" addParams: temperature: 0.7 max_tokens: 2000 # Referência ao Secret criado anteriormente extraEnvVarsSecret: "librechat-credentials-env" # Configuração de acesso externo ingress: enabled: true className: "nginx" hosts: - host: librechat.glukas.space paths: - path: / pathType: Prefix # Sub-charts (dependências) mongodb: enabled: true auth: enabled: false # NUNCA em produção! image: repository: bitnami/mongodb tag: latest persistence: enabled: true size: 8Gi meilisearch: enabled: true auth: enabled: false # NUNCA em produção! environment: MEILI_NO_ANALYTICS: "true" MEILI_ENV: "development" persistence: enabled: true size: 1Gi # Storage para a aplicação principal persistence: enabled: true size: 5Gi storageClass: "standard" EOF Dissecando a configuração: Camada 1 — Environment Variables Simples config: APP_TITLE: "LibreChat + Ollama" Variáveis de ambiente básicas injetadas no container. Camada 2 — Configuração da Aplicação librechat: configEnv: ALLOW_REGISTRATION: "true" Configurações específicas do LibreChat (estas viram env vars também). Camada 3 — YAML Aninhado configYamlContent: | version: 1.1.5 endpoints: custom: ... Isto é um YAML que será escrito como arquivo dentro do container. O pipe | preserva quebras de linha. Esta linha merece atenção especial: baseURL: "http://ollama.ollama.svc.cluster.local:11434/v1" Anatomia de um FQDN (Fully Qualified Domain Name) do Kubernetes: http://ollama.ollama.svc.cluster.local:11434/v1 └─┬─┘ └─┬─┘ └┬┘ └────┬─────┘ └─┬─┘└┬┘ Service NS Type Domain Port Path ollama — Nome do Service (primeira parte) ollama — Namespace onde o Service está svc — Tipo do recurso (service) cluster.local — Domínio base do cluster 11434 — Porta do serviço /v1 — Path da API Formas curtas que também funcionam (dentro do mesmo namespace): http://ollama:11434 # Mesmo namespace http://ollama.ollama:11434 # Namespace explícito http://ollama.ollama.svc:11434 # Com tipo Por que usar o FQDN completo? Porque LibreChat está no namespace librechat e precisa acessar Ollama no namespace ollama. Cross-namespace communication exige pelo menos <service>.<namespace>. # Instalar LibreChat e todas as dependências helm install librechat oci://ghcr.io/danny-avila/librechat-chart/librechat \ -f librechat-values.yaml \ --namespace librechat # Acompanhar o deploy minikube kubectl get pods -n librechat -w O que esperar: NAME READY STATUS RESTARTS AGE librechat-5d8f4b6c9d-xk7p2 0/1 ContainerCreating 0 10s librechat-mongodb-0 0/1 Pending 0 10s librechat-meilisearch-7f9d8c5b6-j4k8m 0/1 ContainerCreating 0 10s Aguarde até todos ficarem Running e READY ser 1/1. # Adicionar ao /etc/hosts echo "192.168.49.2 librechat.glukas.space" | sudo tee -a /etc/hosts # Acessar no navegador # http://librechat.glukas.space Se tudo funcionou, você verá a interface do LibreChat. Crie uma conta, configure o modelo Ollama, e converse com a IA rodando completamente local. 1. Reprodutibilidade: Se eu deletar o cluster agora, você consegue recriar tudo exatamente igual? Tecnicamente sim, mas você vai precisar: Lembrar de todos os comandos na ordem certa Recriar todos os secrets com os mesmos valores Esperar que os charts não tenham mudado Esperar que você não esqueceu de documentar alguma configuração 2. Versionamento: Como você faz rollback se algo quebrar? Você não faz. Helm tem releases, mas as configurações estão na sua máquina. Se você mudou um value há três semanas, boa sorte lembrando o que era antes. 3. Auditoria: Quem mudou o quê e quando? Ninguém sabe. Não há registro. 4. Consistência: Como garantir que dev, staging e produção são iguais? Copiar e colar valores entre ambientes. E rezar. 5. Drift Detection: O cluster está como você pensa que está? Talvez. Alguém pode ter dado kubectl edit diretamente. Você nunca vai saber. O Problema da Reprodutibilidade # O que você FEZ: $ kubectl create namespace ollama $ helm install ollama ... $ kubectl create secret ... $ helm install librechat ... # O que você TEM documentado: $ # (cricket sounds) Você fez funcionar, mas esse conhecimento está na sua cabeça e no histórico do bash. Se o cluster morrer amanhã, você vai precisar reconstruir tudo de memória ou garimpando logs. O Problema do Estado Distribuído Seu sistema agora tem estado em vários lugares: Helm releases — Metadados no cluster Secrets — Criados via kubectl Configurações — Arquivos YAML na sua máquina DNS — Entradas em /etc/hosts Comandos — Histórico do bash Não há uma única fonte de verdade. O Problema da Escala Isso funcionou para 2 aplicações. Agora imagine: 10 aplicações 3 ambientes (dev, staging, prod) 5 pessoas no time Você vai criar 30 configurações manuais? E quando alguém do time atualizar uma, como os outros sabem? O Problema do Drift # No dia 1: $ helm install app v1.0 # No dia 30, alguém faz: $ kubectl edit deployment app # Agora há divergência entre: # - O que Helm pensa que deployou # - O que está realmente rodando # - O que você acha que está rodando Drift é quando o estado atual diverge do estado desejado. Em infraestrutura manual, drift é invisível até quebrar. O Problema da Colaboração Desenvolvedor A: "Mudei a configuração do Ollama" Desenvolvedor B: "Qual? Onde?" Desenvolvedor A: "No meu values.yaml" Desenvolvedor B: "Você commitou?" Desenvolvedor A: "Era pra commitar?" Sem versionamento centralizado, colaboração é caótica. Apesar dos problemas mencionados, esta implementação manual foi essencial: Você agora entende Kubernetes por dentro Pods não são abstrações, são processos rodando Services não são mágica, são iptables rules Ingress não é complexo, é proxy HTTP com regras Você ganhou vocabulário técnico Pode conversar sobre namespaces, secrets, DNS interno Entende o que Helm faz vs o que Kubernetes faz Sabe a diferença entre StatefulSet e Deployment Você sentiu a dor que motiva automação Cada comando manual é uma oportunidade de erro Cada configuração não versionada é débito técnico Cada mudança manual é impossível de auditar Você construiu intuição sobre o ecossistema Sabe quando usar Helm chart vs YAML puro Entende trade-offs de design (simplicidade vs flexibilidade) Pode debugar problemas de networking e DNS Agora que você entende profundamente o quê está rodando e por quê, está pronto para aprender como automatizar. No Capítulo 2, vamos pegar tudo que fizemos aqui e transformar em código versionado, reproduzível e auditável. Vamos introduzir Terraform e explorar a primeira abordagem para gerenciar Kubernetes com Infrastructure as Code. Capítulo 2: Terraform + Kubernetes Provider Como usar Terraform para gerenciar recursos do Kubernetes diretamente. Vamos explorar por que isso parece uma boa ideia, onde funciona bem, e onde começa a quebrar. Aprenderemos sobre state management, planejamento de mudanças, e os limites da abordagem. Capítulo 3: Terraform + Helm A abstração correta emerge. Vamos usar Terraform para gerenciar releases do Helm, combinando o melhor dos dois mundos: versionamento e auditoria do Terraform com a expertise de empacotamento do Helm. Capítulo 4: Terraform + ArgoCD (GitOps) Arquitetura robusta que, separa completamente a gestão da infraestrutura (Terraform) da gestão das aplicações (ArgoCD), implementando continuous delivery real com GitOps, pull-based reconciliation, e multi-tenancy. Documentação Oficial: Kubernetes Documentation Helm Documentation Minikube Guide Ollama Documentation LibreChat Documentation Conceitos Importantes: The Twelve-Factor App — Metodologia para apps cloud-native Kubernetes Patterns — Design patterns para K8s DNS for Services and Pods — Entendendo DNS interno Comunidade: Kubernetes Slack r/kubernetes CNCF Landscape — Mapa completo do ecossistema cloud-native
Details
Discussion coming soon...