10 armadilhas que encontrei ao subir Kubernetes no Oracle Cloud (OKE)
Publicado em 10 de março de 2026

Contexto
Recebi o projeto: migrar toda a infraestrutura de um portal de e-commerce de grande volume do AWS para a Oracle Cloud Infrastructure (OCI). A motivação era puramente econômica — uma redução projetada de cerca de 78% nos custos mensais de cloud.
No AWS, a stack rodava com ECS Fargate para a camada Node.js/Next.js, EC2 ARM para WordPress, banco de dados gerenciado e Application Load Balancer. No OCI, a proposta era substituir o Fargate por OKE (Oracle Kubernetes Engine) com nodes ARM64 no shape VM.Standard.A1.Flex — que entra na Always Free Tier da Oracle.
Sou CKA certificado, então o Kubernetes em si não era o problema. O problema foi a OCI.
O que se segue são as 10 armadilhas que encontrei, todas documentadas em tempo real durante a execução do projeto.
1. A porta 12250 — que ninguém documenta direito
Criei o cluster, criei o node pool, esperei. Os workers ficaram em NotReady por 20 minutos sem nenhuma mensagem de erro clara no console.
O problema: a porta 12250 precisa estar liberada no NSG (Network Security Group) para o endpoint do OKE. Sem ela, o agente do node não consegue se registrar no control plane.
A documentação da Oracle menciona isso, mas de forma dispersa e sem destaque. Não há um erro explícito — você simplesmente fica esperando e nada acontece.
Fix: adicionar regra de ingress na porta 12250 no NSG associado ao endpoint do OKE. Os nodes se registraram em menos de 2 minutos após a correção.
2. O Cloud Controller Manager cria o próprio Load Balancer
Tentei reaproveitar um Load Balancer já existente usando a annotation oci-load-balancer-id. Não funcionou. O OCI CCM v1.34 simplesmente ignora essa annotation — ele sempre cria um novo LB próprio.
A tentativa de usar oci-load-balancer-reserved-ip-id para atribuir um IP reservado também não teve efeito.
A solução foi aceitar o comportamento do CCM: ele cria e gerencia o LB automaticamente. A vantagem é que ele também atualiza os backends quando os nodes mudam — o que seria trabalhoso fazer manualmente.
Importante: o IP do LB criado pelo CCM é classificado como ephemeral, mas é estável na prática enquanto o Service Kubernetes existir. Não muda sem você destruir o Service.
3. OCI WAF Access Control NÃO bypassa Protection Rules
Essa me custou horas.
Criei uma regra de Access Control com ação ALLOW para usuários autenticados no WordPress, identificados pelo prefixo do cookie de sessão. Esperava que isso deixasse essas requisições passarem sem verificação das regras OWASP.
Não é assim que o OCI WAF funciona. Access Control e Protection Rules são pipelines completamente independentes. Uma regra ALLOW no Access Control não afeta em nada a execução das Protection Rules.
O bypass correto é feito adicionando uma condition diretamente na Protection Rule que deve ser desativada para usuários autenticados:
!contains(to_string(keys(http.request.cookies)), 'wordpress_logged_in_')
Isso instrui a regra a só executar se o cookie de autenticação não estiver presente — efetivamente desativando-a para sessões logadas.
4. http.request.cookies vs http.request.headers.cookie
Ainda no WAF: tentei inicialmente usar http.request.headers.cookie — a string raw do header — para identificar cookies nas conditions. Não funcionava de forma confiável em runtime.
A versão correta é http.request.cookies — o objeto já parseado pela engine do WAF. É com esse objeto que as operações de leitura de cookies funcionam como esperado nas expressions das regras de proteção.
5. OCI Cache exige TLS — e o OpenResty não suporta nativamente
O OCI Cache (Redis-compatível, baseado em Valkey) exige conexão TLS. Ponto final. Não há opção para desabilitar.
O problema: o módulo srcache do OpenResty se conecta ao Redis usando a biblioteca interna — que não suporta TLS.
Solução: instalar o stunnel na VM como proxy local. O nginx se conecta em 127.0.0.1:6379 sem TLS, o stunnel encapsula em TLS e entrega ao OCI Cache no endpoint gerenciado.
OpenResty → 127.0.0.1:6379 → stunnel → OCI Cache FQDN:6379 (TLS)
Simples depois que você entende o problema, mas não óbvio se você espera que um Redis gerenciado funcione out-of-the-box com stacks nginx comuns.
6. try_files em location de API REST causa loop 301
Ao configurar uma location para /wp-json/ com try_files no nginx, as requisições da API REST do WordPress entravam em loop de redirect 301.
O try_files tenta servir arquivos físicos ou redirecionar para o index — comportamento correto para páginas HTML, mas catastrófico para endpoints REST que precisam ir direto ao PHP-FPM.
Fix: substituir try_files por fastcgi_pass direto na location, sem verificação de arquivo físico. Parece óbvio depois, mas o bug não se manifesta de forma clara no primeiro momento.
7. File.exists? foi removido no Ruby 3.2
Para centralizar os logs dos pods no OCI Logging, utilizei o fluent-plugin-oci-logging. O problema: a gem usa File.exists?, método removido no Ruby 3.2.
O resultado é um crash silencioso do Fluentd ao inicializar — sem mensagem de erro clara, o pod simplesmente não sobe.
Fix no Dockerfile:
RUN sed -i 's/File\.exists?/File.exist?/g' \
$(gem contents fluent-plugin-oci-logging | grep '\.rb$')
Um patch de uma linha, mas que levou tempo para identificar a causa raiz porque o erro não aparecia de forma legível nos logs do container.
8. Dynamic groups para nodes OKE precisam usar compartment OCID
Para dar ao agente de logging rodando nos nodes permissão para enviar dados ao OCI Logging, precisei criar um Dynamic Group que identificasse as instâncias do cluster.
Tentativa inicial: usar o OCID do node pool. Não funcionou — os nodes não eram reconhecidos como membros do grupo.
A regra correta usa o OCID do compartimento:
All {instance.compartment.id = 'ocid1.compartment.oc1..xxxxx'}
Isso inclui todas as instâncias do compartimento — mais amplo do que o ideal, mas funcional. Para escopo mais granular, é possível usar tags OCI personalizadas aplicadas nos nodes do node pool.
9. CRI-O no Oracle Linux 8 exige nomes completos de imagem
O runtime de containers nos nodes OKE com Oracle Linux 8 é o CRI-O configurado em modo short-name restritivo. Isso significa que prom/prometheus:latest falha — ele não infere o registry docker.io automaticamente.
Todos os campos image: nos manifests Kubernetes precisam usar o nome de registry completo:
image: docker.io/prom/prometheus:v2.53.0
image: registry.k8s.io/metrics-server/metrics-server:v0.7.2
Parece detalhe, mas dependendo do ambiente de origem — onde o Docker Desktop resolve nomes curtos automaticamente — os manifests podem não ter o registry explícito e quebrar silenciosamente no OKE sem mensagem clara.
10. required-server-files.json guarda todas as variáveis baked no build
Durante a migração, precisei recuperar as variáveis de ambiente NEXT_PUBLIC_* embutidas na imagem de produção do Next.js — sem acesso ao repositório ou ao CI/CD original.
A solução: extrair o arquivo required-server-files.json de dentro da imagem Docker:
docker run --rm --entrypoint cat \
<imagem>:<tag> \
/app/.next/required-server-files.json
Esse arquivo, gerado pelo next build, contém todas as variáveis de ambiente resolvidas no momento do build — incluindo todos os NEXT_PUBLIC_*. Útil em situações de contingência onde o ambiente de CI não está acessível.
Conclusão
O OKE é uma opção sólida para Kubernetes gerenciado, especialmente quando os custos dos nodes ARM64 (A1.Flex) se enquadram na Always Free Tier da Oracle. Mas a curva de aprendizado das peculiaridades da OCI é real e não deve ser subestimada.
Cada um dos pontos acima custou tempo de diagnóstico — alguns minutos, outros horas. A documentação da Oracle existe, mas é fragmentada. A maioria dos problemas não tem uma mensagem de erro que aponte diretamente para a causa.
Se você está avaliando OKE para um projeto de produção, espero que essa lista poupe algumas horas do seu próximo projeto.


