aula-04: NGINX Ingress com Keep Request (Lua)
Quando todos os pods estão indisponíveis, o Ingress mantém a requisição por até 99s aguardando o backend voltar, alcançando zero falhas visíveis ao usuário.
This commit is contained in:
43
aula-04/configmap.yaml
Normal file
43
aula-04/configmap.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: app-config
|
||||||
|
data:
|
||||||
|
MAX_REQUESTS: "3"
|
||||||
|
|
||||||
|
app.js: |
|
||||||
|
const http = require("http");
|
||||||
|
|
||||||
|
const MAX_REQUESTS = Number(process.env.MAX_REQUESTS || 3);
|
||||||
|
|
||||||
|
let requestCount = 0;
|
||||||
|
|
||||||
|
console.log("MAX_REQUESTS =", MAX_REQUESTS);
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
if (req.url === "/health") {
|
||||||
|
if (requestCount > MAX_REQUESTS) {
|
||||||
|
console.log('❌ O health check falhou');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(`ok`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCount++;
|
||||||
|
console.log("request", requestCount);
|
||||||
|
|
||||||
|
if (requestCount > MAX_REQUESTS) {
|
||||||
|
console.log(`🔥 App travado após ${MAX_REQUESTS} requests`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end(`Req -> ${requestCount}/${MAX_REQUESTS}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(3000, () => {
|
||||||
|
console.log("App rodando na porta 3000");
|
||||||
|
});
|
||||||
47
aula-04/deployment.yaml
Normal file
47
aula-04/deployment.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: node-bugado
|
||||||
|
spec:
|
||||||
|
replicas: 5 # 5 pods - retry encontra pods saudáveis quando alguns falham
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: node-bugado
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: node-bugado
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 1
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: node:24-alpine
|
||||||
|
command: ["node", "/app/app.js"]
|
||||||
|
env:
|
||||||
|
- name: MAX_REQUESTS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: MAX_REQUESTS
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 3000
|
||||||
|
periodSeconds: 1 # Verifica a cada 1 segundo
|
||||||
|
failureThreshold: 1 # 1 falha = remove do Service
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 2 # Espera para começar a testar
|
||||||
|
periodSeconds: 1 # A cada 1 seg faz o teste novamente
|
||||||
|
failureThreshold: 2 # 2 falhas = reinicia o container
|
||||||
|
volumeMounts:
|
||||||
|
- name: app-volume
|
||||||
|
mountPath: /app
|
||||||
|
volumes:
|
||||||
|
- name: app-volume
|
||||||
|
configMap:
|
||||||
|
name: app-config
|
||||||
60
aula-04/ingress-nginx.yaml
Normal file
60
aula-04/ingress-nginx.yaml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# NGINX Ingress com "Keep Request" - Mantém request até backend disponível
|
||||||
|
# Quando todos os pods estão indisponíveis, aguarda até 99s antes de retornar 503
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: node-bugado-nginx
|
||||||
|
annotations:
|
||||||
|
# Location interno que aguarda o backend ficar disponível
|
||||||
|
nginx.ingress.kubernetes.io/server-snippet: |
|
||||||
|
error_page 502 503 504 = @wait_for_backend;
|
||||||
|
|
||||||
|
location @wait_for_backend {
|
||||||
|
internal;
|
||||||
|
content_by_lua_block {
|
||||||
|
local max_wait = 99
|
||||||
|
local interval = 1
|
||||||
|
local start = ngx.now()
|
||||||
|
local backend_host = "node-bugado.default.svc.cluster.local"
|
||||||
|
local backend_port = 3000
|
||||||
|
|
||||||
|
ngx.log(ngx.INFO, "[KeepRequest] Aguardando backend por ate ", max_wait, "s...")
|
||||||
|
|
||||||
|
while (ngx.now() - start) < max_wait do
|
||||||
|
local sock = ngx.socket.tcp()
|
||||||
|
sock:settimeout(1000)
|
||||||
|
|
||||||
|
local ok, err = sock:connect(backend_host, backend_port)
|
||||||
|
if ok then
|
||||||
|
sock:close()
|
||||||
|
local elapsed = math.floor(ngx.now() - start)
|
||||||
|
ngx.log(ngx.INFO, "[KeepRequest] Backend ready apos ", elapsed, "s")
|
||||||
|
-- Redirect interno (não retorna ao cliente)
|
||||||
|
return ngx.exec(ngx.var.request_uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
ngx.log(ngx.WARN, "[KeepRequest] Backend indisponivel: ", err)
|
||||||
|
ngx.sleep(interval)
|
||||||
|
end
|
||||||
|
|
||||||
|
ngx.status = 503
|
||||||
|
ngx.header["Content-Type"] = "text/plain"
|
||||||
|
ngx.say("Servico indisponivel apos ", max_wait, "s de espera")
|
||||||
|
ngx.exit(503)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Timeouts curtos para falhar rápido e ir para o handler Lua
|
||||||
|
nginx.ingress.kubernetes.io/proxy-connect-timeout: "2"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: node-bugado
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
26
aula-04/ingress.yaml-
Normal file
26
aula-04/ingress.yaml-
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Middleware Retry - tenta outros pods quando um falha
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: retry-middleware
|
||||||
|
spec:
|
||||||
|
retry:
|
||||||
|
attempts: 5 # 5 tentativas
|
||||||
|
initialInterval: 500ms # 500ms entre ciclos
|
||||||
|
---
|
||||||
|
# IngressRoute
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: node-bugado
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: PathPrefix(`/`)
|
||||||
|
kind: Rule
|
||||||
|
middlewares:
|
||||||
|
- name: retry-middleware
|
||||||
|
services:
|
||||||
|
- name: node-bugado
|
||||||
|
port: 3000
|
||||||
12
aula-04/service.yaml
Normal file
12
aula-04/service.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: node-bugado
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer # Acesso direto na porta 3000 + Traefik na porta 80
|
||||||
|
selector:
|
||||||
|
app: node-bugado
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
protocol: TCP
|
||||||
57
aula-04/teste-stress.sh
Executable file
57
aula-04/teste-stress.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Teste de Stress para verificar resiliência do Ingress
|
||||||
|
# Uso: ./teste-stress.sh [URL] [NUM_REQUESTS]
|
||||||
|
|
||||||
|
URL="${1:-http://localhost}"
|
||||||
|
TOTAL="${2:-50}"
|
||||||
|
TIMEOUT=120
|
||||||
|
|
||||||
|
echo "============================================"
|
||||||
|
echo " Teste de Stress - Alta Disponibilidade"
|
||||||
|
echo "============================================"
|
||||||
|
echo "URL: $URL"
|
||||||
|
echo "Requisições: $TOTAL"
|
||||||
|
echo "Timeout: ${TIMEOUT}s"
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SUCCESS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
for i in $(seq 1 $TOTAL); do
|
||||||
|
RESULT=$(curl -s -m $TIMEOUT "$URL" 2>&1)
|
||||||
|
|
||||||
|
if echo "$RESULT" | grep -q "Req ->"; then
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
echo -e "[$i/$TOTAL] \033[32mOK\033[0m - $RESULT"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
# Extrai apenas o título do erro se for HTML
|
||||||
|
if echo "$RESULT" | grep -q "<title>"; then
|
||||||
|
ERROR=$(echo "$RESULT" | grep -o "<title>[^<]*</title>" | sed 's/<[^>]*>//g')
|
||||||
|
else
|
||||||
|
ERROR="$RESULT"
|
||||||
|
fi
|
||||||
|
echo -e "[$i/$TOTAL] \033[31mFALHA\033[0m - $ERROR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo " Resultado Final"
|
||||||
|
echo "============================================"
|
||||||
|
PERCENT=$((SUCCESS * 100 / TOTAL))
|
||||||
|
echo -e "Sucesso: \033[32m$SUCCESS\033[0m / $TOTAL ($PERCENT%)"
|
||||||
|
echo -e "Falhas: \033[31m$FAIL\033[0m / $TOTAL"
|
||||||
|
echo "============================================"
|
||||||
|
|
||||||
|
if [ $FAIL -eq 0 ]; then
|
||||||
|
echo -e "\033[32m*** ZERO FALHAS! ***\033[0m"
|
||||||
|
elif [ $PERCENT -ge 95 ]; then
|
||||||
|
echo -e "\033[33mBom resultado (>= 95%)\033[0m"
|
||||||
|
else
|
||||||
|
echo -e "\033[31mResultado abaixo do esperado\033[0m"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user