Migrar uma plataforma robusta como o Magento (MageOS) para Kubernetes é apenas o começo. O verdadeiro desafio é fazer essa arquitetura ser eficiente, escalável e barata.
Neste post, vou compartilhar como migramos nossa operação de e-commerce da Adobe Cloud para o Amazon EKS e, principalmente, como resolvemos o maior gargalo de performance do Magento — o processamento de filas — utilizando KEDA (Kubernetes Event-driven Autoscaling).
O Desafio
O Magento é tradicionalmente uma aplicação monolítica voraz. Nossos principais problemas eram:
Processamento de Filas Lento: O auto-scaling nativo do Kubernetes (HPA) baseado em CPU/Memória é lento demais para reagir a picos de mensagens no RabbitMQ.
Desperdício de Recursos: Manter servidores de consumers ligados 24/7 com 16GB de RAM para processar filas que ficam vazias na madrugada.
Deploy Lento e Frágil: Dependências de banco de dados durante o build da imagem.
Aqui está como resolvemos isso, camada por camada.
1. A Fundação: Imagem Base e Build
Optamos por uma estratégia de imagem "All-in-One", onde a mesma imagem Docker contém o Nginx, o PHP-FPM e o código da aplicação. O comportamento do pod é definido apenas no momento da inicialização (runtime).
A Imagem Base
Criamos uma imagem base otimizada contendo apenas as dependências do sistema. Aqui ajustamos o php.ini e o nginx.conf para alta performance:
PHP Tuning: Ajustamos opcache.validate_timestamps=0 (para performance máxima em prod) e aumentamos limites críticos como upload_max_filesize e post_max_size para 10GB (necessário para nosso gerenciador de vídeos).
Nginx: Configuramos timeouts agressivos e aumentamos o client_max_body_size.
O Build da Aplicação (CI)
No nosso pipeline (GitLab CI), o Dockerfile herda da imagem base e foca apenas na construção dos artefatos estáticos.
Dockerfile simplificado
FROM nossa-imagem-base:latest as builder
pasta 'vendor'
USER root
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --optimize-autoloader
COPY . .
RUN chown -R www-data:www-data .
FROM nossa-imagem-base:latest
WORKDIR /var/www/html
USER root
COPY --chown=www-data:www-data --from=builder /app .
# Copia o env.php, precisa conter no diretorio
COPY --chown=www-data:www-data env.php app/etc/env.php
USER www-data
# Agrupamos todos os comandos de deploy em um único RUN para otimização
RUN set -ex && \
# Sincroniza o banco de dados com o código
echo "Atualizando schema do banco de dados..." && \
php -d memory_limit=-1 bin/magento setup:upgrade --keep-generated && \
# Gera os arquivos estáticos (CSS/JS)
echo "Gerando conteúdo estático..." && \
php -d memory_limit=-1 bin/magento setup:static-content:deploy pt_BR en_US -f --jobs 4 && \
# Compila o código de Injeção de Dependência
echo "Compilando DI..." && \
php -d memory_limit=-1 bin/magento setup:di:compile && \
# Limpa o cache
echo "Limpando cache..." && \
php -d memory_limit=-1 bin/magento cache:flush
USER root
# Usa o entrypoint da imagem base, que inicia o supervisor (Nginx + PHP-FPM)
2. Segregação de Workloads no Kubernetes
Em vez de tratar todos os pods como iguais, dividimos o Magento em "perfis" usando Argo Rollouts. Usamos a mesma imagem Docker, mas mudamos o command e args de inicialização.
Perfil 1: Web e Admin (Frontend)
Esses pods atendem o tráfego HTTP. O tráfego é roteado via AWS ALB (Ingress).
Inicialização: Usamos o supervisord para rodar Nginx e PHP-FPM juntos no mesmo container.
Comando: exec /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
Perfil 2: Cron Daemon (Scheduler)
O Magento precisa do cron rodando a cada 6 minutos.
CronJob do Kubernetes: Que sobe e inicializa o script:
- |
set -e
echo "Preparando diretórios de log..."
mkdir -p var/log
touch var/log/cron.log
echo "--------------------------------"
echo "Executando Comando Magento CRON..."
php -d memory_limit=-1 bin/magento cron:run >> var/log/magento.cron.log 2>&1
sleep 240
echo "--------------------------------"
echo "--- LOGS CRON ---"
cat var/log/cron.log
echo "--- FIM DOS LOGS CRON ---"
Truque: Configuramos 'cron_run' => false no env.php para impedir que este pod tente processar filas, evitando conflitos.
Perfil 3: Consumers (Workers)
Aqui está a "cereja do bolo". Esses pods não recebem tráfego HTTP; eles apenas processam mensagens do RabbitMQ.
3. Auto-Scaling Inteligente com KEDA
O HPA padrão do Kubernetes não sabe quantas mensagens tem na fila. O KEDA sabe (duplicamos filas que contém no env.php para manifest do Keda).
Implementamos o KEDA para monitorar nossas filas no Amazon MQ (RabbitMQ) e escalar os pods de consumers, por exemplo de 0 a 20 réplicas automaticamente.
A Configuração do ScaledObject
O segredo para a estabilidade foi o ajuste fino do comportamento de escala (behavior):
YAML
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: NOME_SCALE_OBJECT
lables:
app: NOME_POD_CONSUMER_QUE_SERA_ESCALADO
spec:
scaleTargetRef:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
name: NOME_POD_CONSUMER_QUE_SERA_ESCALADO
# Configuração de Zero-Scaling (Dormir/Acordar)
minReplicaCount: 0 # Escala a ZERO quando não há trabalho (Economia!)
maxReplicaCount: 20
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Espera 5 min vazio antes de desligar
policies:
- periodSeconds: 15
type: Percent
value: 100
scaleUp:
stabilizationWindowSeconds: 0 # Sobe imediatamente
policies:
- periodSeconds: 15
type: Percent
value: 100
- periodSeconds: 15
type: Pods
value: 4 # Adiciona até 4 pods de uma vez no pico
triggers:
# FILA 1
- type: rabbitmq
authenticationRef:
name: keda-trigger-auth-rabbitmq
metadata:
mode: QueueLength
queueName: NOME_FILA_RABBITMQ
value: "10000"
activationThreshold: "1" # Acorda com 1 mensagem
host: "amqps://rabbit.mq.us-east-1.on.aws:5671//vhost"
tls: "enable"
# FILA 2...
O Desafio da Autenticação na AWS (O "Pulo do Gato")
O RabbitMQ na AWS usa strings de conexão complexas. Tivemos problemas com o KEDA não encontrando o Virtual Host. A Solução: Usar uma "barra dupla" na string de conexão do Secret (amqps://host:port//vhost) e remover a definição explícita de vhost do trigger. Foi a única forma de fazer a API de métricas e a conexão AMQP funcionarem juntas sem erros 403 ou 404.
4. Otimização e "Auto-Cura" dos Consumers
Consumers PHP de longa duração sofrem com memory leaks. Em nossa arquitetura antiga, os pods cresciam até estourar a memória (OOMKilled).
Resolvemos isso combinando a infraestrutura com a configuração nativa do Magento:
Limites no Pod: Definimos requests/limits iguais (QoS Guaranteed) de 6GB.
Limites no Processo: Limitamos cada processo PHP a 512MB (php -d memory_limit=512M).
Auto-Reciclagem: No comando de inicialização, usamos a flag --max-messages=10000.
O consumer processa 10.000 mensagens e morre "de propósito".
O Kubernetes percebe a morte e sobe um pod novo, "limpo" e rápido.
Script de Inicialização dos Consumers:
Bash
- |
set -e
# Garante que o diretório e o arquivo de log existam
mkdir -p var/log
touch var/log/consumers.log
# --- LÓGICA DE ENCERRAMENTO CONTROLADO ---
# Função que será chamada quando o Kubernetes enviar o sinal de 'stop' (SIGTERM)
shutdown() {
echo "Desligando... Enviando SIGTERM para todos os processos de consumer..."
# Envia o sinal de 'stop' para todos os processos 'php' filhos
pkill -P $$ -TERM 'php'
echo "Aguardando o término dos processos..."
# Espera que todos os processos em background terminem
wait
echo "Todos os processos de consumer foram encerrados. Saindo..."
exit 0
}
# O 'trap' "apanha" o sinal de encerramento do Kubernetes e chama a nossa função 'shutdown'
trap 'shutdown' TERM INT
echo "Iniciando todos os consumers do Magento em background..."
PROCESS_COUNT=$(php -r '
$config = include "app/etc/env.php";
$consumers = $config["cron_consumers_runner"]["multiple_processes"] ?? [];
# Conta o número de chaves (filas), não a soma dos valores
echo count($consumers);
')
echo "========================================================"
echo "CONTADOR DE PROCESSOS: Forçando 1 processo por fila. Total de filas: $PROCESS_COUNT"
echo "========================================================"
# 2. Gera os comandos (ignora o $processCount do env.php)
COMMANDS=$(php -r '
$config = include "app/etc/env.php";
$consumers = $config["cron_consumers_runner"]["multiple_processes"] ?? [];
# Itera sobre cada fila (ignora a contagem de processos)
foreach ($consumers as $consumerName => $processCount) {
# IGNORA o $processCount e inicia APENAS 1 processo
echo "echo \"Iniciando 1 processo (forçado) para o consumer: $consumerName\"; ";
# 1. memory_limit=768M (seguro)
# 2. SEM --max-messages (para rodar para sempre)
echo "php -d memory_limit=768M bin/magento queue:consumers:start $consumerName >> var/log/consumers.log 2>&1 & ";
}
')
# Executa todos os comandos gerados
eval $COMMANDS
echo "Todos os consumers foram iniciados. Exibindo logs em tempo real..."
# Mantém o contêiner rodando e transmite o log
tail -f var/log/consumers.log &
wait
Com essa arquitetura, saímos de um modelo estático e caro para uma infraestrutura elástica.
Economia: Durante a madrugada, temos zero pods de consumer rodando.
Performance: Em picos de atualização de catálogo, o cluster escala para 20 pods em segundos, drenando filas de milhões de mensagens rapidamente.
Estabilidade: A segregação de Cron e Consumers acabou com os deadlocks de banco de dados.
O Magento no Kubernetes não precisa ser uma dor de cabeça. Com as ferramentas certas (KEDA, Argo CD) e os ajustes corretos no env.php e Dockerfile, ele se torna uma plataforma robusta e moderna.
Observação: Pode ter melhorias em PHP e seus processos, não sou especialista nessa linguagem.
Link: Completo
Link: Biografia
Top comments (0)