feat: adicionar aula-16 (Flagger), remover Jaeger da aula-14 e integrar tracing Istio na aula-15
- aula-16: nova aula de canary deployment automatizado com Flagger - aula-14: removido Jaeger (tracing movido para aula-15), corrigido nome do serviço Victoria Metrics, adicionado rate limiting no Kiali - aula-15: adicionado receiver Zipkin no OTel Collector para receber traces do Istio, corrigida porta do Tempo (3100→3200), integração automática com Istio quando detectado - aula-03: corrigido MAX_REQUESTS de 10 para 3 (valor padrão) - CLAUDE.md: adicionada aula-16 na documentação
This commit is contained in:
@@ -26,6 +26,7 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he
|
||||
| 13 | Container Factory (eStargz) | Hetzner |
|
||||
| 14 | Istio Traffic Splitting | Hetzner |
|
||||
| 15 | APM: Grafana Tempo + OpenTelemetry | Hetzner |
|
||||
| 16 | Canary Automatizado com Flagger | Hetzner |
|
||||
|
||||
## Comandos Rápidos
|
||||
|
||||
@@ -42,6 +43,7 @@ cd aula-12 && ./setup.sh # Victoria Metrics + Grafana
|
||||
cd aula-13 && ./setup.sh # Container Factory
|
||||
cd aula-14 && ./setup.sh # Istio Traffic Splitting
|
||||
cd aula-15 && ./setup.sh # APM: Tempo + OpenTelemetry
|
||||
cd aula-16 && ./setup.sh # Canary Automatizado com Flagger
|
||||
```
|
||||
|
||||
## App node-bugado
|
||||
|
||||
@@ -3,7 +3,7 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
data:
|
||||
MAX_REQUESTS: "10"
|
||||
MAX_REQUESTS: "3"
|
||||
|
||||
app.js: |
|
||||
const http = require("http");
|
||||
|
||||
+17
-17
@@ -1,6 +1,6 @@
|
||||
# Aula 14 - Istio Traffic Splitting
|
||||
|
||||
Demonstração de **app-backend deployment** usando Istio para dividir tráfego entre duas versões da aplicação.
|
||||
Demonstração de **canary deployment manual** usando Istio para dividir tráfego entre duas versões da aplicação.
|
||||
|
||||
## Motivação
|
||||
|
||||
@@ -29,9 +29,9 @@ Em produção, lançar uma nova versão diretamente para 100% dos usuários é a
|
||||
│ │ bug │ │ ok │ │
|
||||
│ └──────┘ └──────┘ │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌────────┐ ┌─────────────────┐│
|
||||
│ │ Kiali │ │ Jaeger │ │ Victoria Metrics││
|
||||
│ └─────────┘ └────────┘ └─────────────────┘│
|
||||
│ ┌─────────┐ ┌─────────────────┐ │
|
||||
│ │ Kiali │ │ Victoria Metrics │ │
|
||||
│ └─────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -72,11 +72,10 @@ aula-14/
|
||||
│ ├── deployment-v2.yaml
|
||||
│ ├── service.yaml
|
||||
│ ├── destination-rule.yaml # Define subsets v1, v2
|
||||
│ └── virtual-service.yaml # Traffic splitting (90/10)
|
||||
│ ├── virtual-service.yaml # Traffic splitting (90/10)
|
||||
│ └── ingress-kiali.yaml
|
||||
└── istio/
|
||||
├── kiali-values.yaml
|
||||
├── jaeger-values.yaml
|
||||
└── gateway.yaml
|
||||
└── kiali-values.yaml
|
||||
```
|
||||
|
||||
## Instalação
|
||||
@@ -89,9 +88,9 @@ cd aula-14
|
||||
O script irá:
|
||||
1. Verificar pré-requisitos (incluindo Victoria Metrics da aula-12)
|
||||
2. Coletar configuração (domínio, registry, TLS)
|
||||
3. Instalar Istio (istio-base + istiod)
|
||||
4. Instalar Kiali e Jaeger (métricas via Victoria Metrics)
|
||||
5. Configurar Ingress para dashboards
|
||||
3. Instalar Istio (istio-base + istiod + ingressgateway)
|
||||
4. Instalar Kiali (dashboard do service mesh, métricas via Victoria Metrics)
|
||||
5. Configurar Ingress com Basic Auth e rate limiting
|
||||
6. Build e push das imagens v1 e v2
|
||||
7. Deploy da aplicação com traffic splitting 90/10
|
||||
|
||||
@@ -152,9 +151,9 @@ kubectl patch virtualservice app-backend -n istio --type='json' \
|
||||
{"op":"replace","path":"/spec/http/0/route/1/weight","value":50}]'
|
||||
```
|
||||
|
||||
### 4. Verificar traces no Jaeger
|
||||
### 4. Observar no Kiali
|
||||
|
||||
Abra o Jaeger e observe os traces das requisições passando pelo mesh.
|
||||
Volte ao Kiali e veja o tráfego redistribuído igualmente entre v1 e v2.
|
||||
|
||||
### 5. Rollout completo para v2
|
||||
|
||||
@@ -185,9 +184,6 @@ kubectl logs -n istio -l app=app-backend,version=v2 -f
|
||||
|
||||
# Port-forward para Kiali (alternativa ao Ingress)
|
||||
kubectl port-forward svc/kiali -n istio-system 20001:20001
|
||||
|
||||
# Port-forward para Jaeger
|
||||
kubectl port-forward svc/tracing -n istio-system 16686:80
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
@@ -196,7 +192,7 @@ kubectl port-forward svc/tracing -n istio-system 16686:80
|
||||
./cleanup.sh
|
||||
```
|
||||
|
||||
Remove Istio, addons e namespace da aplicação.
|
||||
Remove Istio, Kiali e namespace da aplicação.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -228,3 +224,7 @@ Verificar se DestinationRule existe:
|
||||
```bash
|
||||
kubectl get destinationrule app-backend -n istio -o yaml
|
||||
```
|
||||
|
||||
### E o tracing?
|
||||
|
||||
O tracing distribuído (ver o caminho de um request individual) é configurado na **aula-15** com Grafana Tempo + OpenTelemetry. O Istio envia traces automaticamente para o Tempo após a aula-15 ser instalada.
|
||||
|
||||
+6
-8
@@ -4,7 +4,7 @@
|
||||
# ============================================================================
|
||||
# Remove todos os componentes instalados pelo setup.sh:
|
||||
# - Namespace istio (aplicação)
|
||||
# - Kiali, Jaeger (addons)
|
||||
# - Kiali (addon)
|
||||
# - Istio (istiod + base)
|
||||
# - Ingress resources
|
||||
#
|
||||
@@ -37,9 +37,9 @@ echo -e "${CYAN}╚════════════════════
|
||||
echo ""
|
||||
echo -e "${YELLOW}Este script irá remover:${NC}"
|
||||
echo " - Namespace 'istio' com aplicação"
|
||||
echo " - Addons: Kiali, Jaeger"
|
||||
echo " - Addon: Kiali"
|
||||
echo " - Istio: istiod e istio-base"
|
||||
echo " - Ingress do Kiali e Jaeger"
|
||||
echo " - Ingress do Kiali"
|
||||
echo ""
|
||||
read -p "Continuar? (digite 'sim' para confirmar): " confirm
|
||||
if [[ "$confirm" != "sim" ]]; then
|
||||
@@ -64,16 +64,14 @@ fi
|
||||
|
||||
# Remover Ingress
|
||||
log_info "Removendo Ingress..."
|
||||
kubectl delete ingress kiali jaeger -n istio-system --ignore-not-found=true 2>/dev/null || true
|
||||
kubectl delete ingress kiali -n istio-system --ignore-not-found=true 2>/dev/null || true
|
||||
log_success "Ingress removidos"
|
||||
|
||||
# Remover addons (Kiali, Jaeger)
|
||||
# Remover addon (Kiali)
|
||||
log_info "Removendo Kiali..."
|
||||
kubectl delete -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml 2>/dev/null || true
|
||||
|
||||
log_info "Removendo Jaeger..."
|
||||
kubectl delete -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml 2>/dev/null || true
|
||||
log_success "Addons removidos"
|
||||
log_success "Addon removido"
|
||||
|
||||
# Remover istiod
|
||||
if helm status istiod -n istio-system &> /dev/null; then
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Jaeger - Distributed Tracing
|
||||
# Tracing para visualizar requisições através do mesh
|
||||
|
||||
provisionDataStore:
|
||||
cassandra: false
|
||||
|
||||
allInOne:
|
||||
enabled: true
|
||||
image: jaegertracing/all-in-one
|
||||
tag: "1.62"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
storage:
|
||||
type: memory
|
||||
|
||||
collector:
|
||||
enabled: false
|
||||
|
||||
query:
|
||||
enabled: false
|
||||
|
||||
agent:
|
||||
enabled: false
|
||||
@@ -15,10 +15,8 @@ deployment:
|
||||
|
||||
external_services:
|
||||
prometheus:
|
||||
url: http://vmsingle-victoria-metrics-k8s-stack.monitoring:8429
|
||||
url: http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429
|
||||
tracing:
|
||||
enabled: true
|
||||
in_cluster_url: http://tracing.istio-system:16685/jaeger
|
||||
use_grpc: true
|
||||
enabled: false
|
||||
grafana:
|
||||
enabled: false
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: jaeger
|
||||
namespace: istio-system
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/backend-protocol: HTTP
|
||||
nginx.ingress.kubernetes.io/auth-type: basic
|
||||
nginx.ingress.kubernetes.io/auth-secret: basic-auth
|
||||
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
||||
${TLS_ANNOTATION}
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
${TLS_CONFIG}
|
||||
rules:
|
||||
- host: ${JAEGER_HOST}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: tracing
|
||||
port:
|
||||
number: 80
|
||||
@@ -8,6 +8,9 @@ metadata:
|
||||
nginx.ingress.kubernetes.io/auth-type: basic
|
||||
nginx.ingress.kubernetes.io/auth-secret: basic-auth
|
||||
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
||||
nginx.ingress.kubernetes.io/limit-rps: "20"
|
||||
nginx.ingress.kubernetes.io/limit-connections: "10"
|
||||
nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
|
||||
${TLS_ANNOTATION}
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
|
||||
+13
-42
@@ -2,18 +2,16 @@
|
||||
# ============================================================================
|
||||
# Aula 14 - Istio Traffic Splitting
|
||||
# ============================================================================
|
||||
# Instala Istio com Kiali e Jaeger para demonstrar canary deployment
|
||||
# Instala Istio com Kiali para demonstrar canary deployment
|
||||
# usando traffic splitting entre duas versões da aplicação.
|
||||
#
|
||||
# Componentes:
|
||||
# - Istio (istio-base + istiod)
|
||||
# - Kiali (visualização do service mesh)
|
||||
# - Jaeger (tracing distribuído)
|
||||
# - Aplicação app-backend v1 e v2
|
||||
#
|
||||
# Observabilidade:
|
||||
# - Usa Victoria Metrics da aula-12 para métricas
|
||||
# - Jaeger para tracing distribuído
|
||||
#
|
||||
# Pré-requisitos:
|
||||
# - Cluster Kubernetes da aula-08
|
||||
@@ -61,7 +59,6 @@ REGISTRY_HOST=${REGISTRY_HOST}
|
||||
REGISTRY_PROJECT=${REGISTRY_PROJECT}
|
||||
APP_HOST=${APP_HOST}
|
||||
KIALI_HOST=${KIALI_HOST}
|
||||
JAEGER_HOST=${JAEGER_HOST}
|
||||
USE_LETSENCRYPT=${USE_LETSENCRYPT}
|
||||
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
|
||||
BASIC_AUTH_USER=${BASIC_AUTH_USER}
|
||||
@@ -96,7 +93,6 @@ collect_user_input() {
|
||||
echo -e " Registry: ${GREEN}${REGISTRY_HOST}${NC}"
|
||||
echo -e " App: ${GREEN}${APP_HOST}${NC}"
|
||||
echo -e " Kiali: ${GREEN}${KIALI_HOST}${NC}"
|
||||
echo -e " Jaeger: ${GREEN}${JAEGER_HOST}${NC}"
|
||||
echo ""
|
||||
echo -e "[1] Usar configuração existente"
|
||||
echo -e "[2] Inserir nova configuração"
|
||||
@@ -119,7 +115,6 @@ collect_user_input() {
|
||||
echo ""
|
||||
APP_HOST=$(ask_hostname "App" "$APP_HOST" "app.${INHERITED_DOMAIN}")
|
||||
KIALI_HOST=$(ask_hostname "Kiali" "$KIALI_HOST" "kiali.${INHERITED_DOMAIN}")
|
||||
JAEGER_HOST=$(ask_hostname "Jaeger" "$JAEGER_HOST" "jaeger.${INHERITED_DOMAIN}")
|
||||
|
||||
# Owner/repo no Gitea para o registry (ex: root, demo, factory)
|
||||
if [[ -z "$REGISTRY_PROJECT" ]]; then
|
||||
@@ -149,9 +144,9 @@ collect_user_input() {
|
||||
USE_LETSENCRYPT=false
|
||||
fi
|
||||
|
||||
# Basic Auth para Kiali e Jaeger
|
||||
# Basic Auth para Kiali
|
||||
echo ""
|
||||
echo -e "${CYAN}Autenticação para Kiali e Jaeger:${NC}"
|
||||
echo -e "${CYAN}Autenticação para Kiali:${NC}"
|
||||
if [[ -z "$BASIC_AUTH_USER" ]]; then
|
||||
BASIC_AUTH_USER="admin"
|
||||
fi
|
||||
@@ -204,9 +199,9 @@ check_prerequisites() {
|
||||
log_success "Cluster Kubernetes acessível"
|
||||
|
||||
# Verificar Victoria Metrics (aula-12)
|
||||
if kubectl get svc -n monitoring vmsingle-victoria-metrics-k8s-stack &> /dev/null; then
|
||||
if kubectl get svc -n monitoring vmsingle-monitoring-victoria-metrics-k8s-stack &> /dev/null; then
|
||||
log_success "Victoria Metrics encontrado (aula-12)"
|
||||
VICTORIA_METRICS_URL="http://vmsingle-victoria-metrics-k8s-stack.monitoring:8429"
|
||||
VICTORIA_METRICS_URL="http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429"
|
||||
else
|
||||
log_warn "Victoria Metrics não encontrado. Instale a aula-12 primeiro."
|
||||
log_warn "Kiali funcionará sem métricas até Victoria Metrics estar disponível."
|
||||
@@ -274,14 +269,9 @@ install_istio() {
|
||||
install_observability() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Instalando Kiali e Jaeger${NC}"
|
||||
echo -e "${CYAN} Instalando Kiali${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "Instalando Jaeger..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml 2>/dev/null || true
|
||||
log_success "Jaeger instalado"
|
||||
|
||||
# Instalar Kiali com configuração para Victoria Metrics
|
||||
log_info "Instalando Kiali..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml 2>/dev/null || true
|
||||
|
||||
@@ -290,18 +280,16 @@ install_observability() {
|
||||
log_info "Configurando Kiali para usar Victoria Metrics..."
|
||||
kubectl patch configmap kiali -n istio-system --type merge -p "{
|
||||
\"data\": {
|
||||
\"config.yaml\": \"external_services:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: true\\n in_cluster_url: http://tracing.istio-system:16685/jaeger\\n use_grpc: true\\n\"
|
||||
\"config.yaml\": \"auth:\\n strategy: anonymous\\nlogin_token:\\n signing_key: kiali-signing-key-1234\\nserver:\\n web_root: /kiali\\nexternalservices:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: false\\n grafana:\\n enabled: false\\n\"
|
||||
}
|
||||
}" 2>/dev/null || true
|
||||
# Reiniciar Kiali para aplicar configuração
|
||||
kubectl rollout restart deployment/kiali -n istio-system 2>/dev/null || true
|
||||
fi
|
||||
log_success "Kiali instalado"
|
||||
|
||||
log_info "Aguardando pods de observabilidade..."
|
||||
log_info "Aguardando Kiali..."
|
||||
kubectl wait --for=condition=available deployment/kiali -n istio-system --timeout=300s 2>/dev/null || true
|
||||
kubectl wait --for=condition=available deployment/jaeger -n istio-system --timeout=300s 2>/dev/null || true
|
||||
log_success "Observabilidade pronta"
|
||||
log_success "Kiali pronto"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@@ -311,7 +299,7 @@ install_observability() {
|
||||
setup_basic_auth() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Configurando Basic Auth para Kiali e Jaeger${NC}"
|
||||
echo -e "${CYAN} Configurando Basic Auth para Kiali${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "Criando secret basic-auth..."
|
||||
@@ -340,7 +328,7 @@ setup_basic_auth() {
|
||||
setup_ingress() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Configurando Ingress para Kiali e Jaeger${NC}"
|
||||
echo -e "${CYAN} Configurando Ingress para Kiali${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
local TLS_ANNOTATION=""
|
||||
@@ -360,19 +348,6 @@ setup_ingress() {
|
||||
export KIALI_HOST TLS_ANNOTATION TLS_CONFIG
|
||||
envsubst '${KIALI_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-kiali.yaml" | kubectl apply -f -
|
||||
log_success "Ingress do Kiali criado"
|
||||
|
||||
log_info "Criando Ingress para Jaeger..."
|
||||
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
|
||||
TLS_CONFIG="tls:
|
||||
- hosts:
|
||||
- ${JAEGER_HOST}
|
||||
secretName: jaeger-tls"
|
||||
else
|
||||
TLS_CONFIG=""
|
||||
fi
|
||||
export JAEGER_HOST TLS_ANNOTATION TLS_CONFIG
|
||||
envsubst '${JAEGER_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-jaeger.yaml" | kubectl apply -f -
|
||||
log_success "Ingress do Jaeger criado"
|
||||
}
|
||||
|
||||
setup_app_ingress() {
|
||||
@@ -521,7 +496,6 @@ show_summary() {
|
||||
echo -e "${GREEN}Componentes instalados:${NC}"
|
||||
echo " - Istio (istiod + base)"
|
||||
echo " - Kiali (dashboard do service mesh)"
|
||||
echo " - Jaeger (tracing distribuído)"
|
||||
echo " - app-backend v1 e v2"
|
||||
echo ""
|
||||
if [[ -n "$VICTORIA_METRICS_URL" ]]; then
|
||||
@@ -532,10 +506,9 @@ show_summary() {
|
||||
echo ""
|
||||
echo -e "${GREEN}URLs:${NC}"
|
||||
echo -e " App: ${CYAN}${protocol}://${APP_HOST}${NC}"
|
||||
echo -e " Kiali: ${CYAN}${protocol}://${KIALI_HOST}${NC}"
|
||||
echo -e " Jaeger: ${CYAN}${protocol}://${JAEGER_HOST}${NC}"
|
||||
echo -e " Kiali: ${CYAN}${protocol}://${KIALI_HOST}/kiali/${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}Credenciais (Basic Auth para Kiali/Jaeger):${NC}"
|
||||
echo -e "${GREEN}Credenciais (Basic Auth para Kiali):${NC}"
|
||||
echo -e " Usuário: ${CYAN}${BASIC_AUTH_USER}${NC}"
|
||||
echo -e " Senha: ${CYAN}${BASIC_AUTH_PASS}${NC}"
|
||||
echo ""
|
||||
@@ -562,7 +535,6 @@ show_summary() {
|
||||
echo -e "${YELLOW}Configure DNS para:${NC}"
|
||||
echo " ${APP_HOST} -> IP do Ingress"
|
||||
echo " ${KIALI_HOST} -> IP do Ingress"
|
||||
echo " ${JAEGER_HOST} -> IP do Ingress"
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -596,7 +568,6 @@ main() {
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${APP_HOST}${NC}"
|
||||
echo -e " ${YELLOW}${KIALI_HOST}${NC}"
|
||||
echo -e " ${YELLOW}${JAEGER_HOST}${NC}"
|
||||
echo ""
|
||||
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
|
||||
echo -e "${YELLOW}⚠ O Let's Encrypt precisa do DNS configurado para emitir o certificado.${NC}"
|
||||
|
||||
@@ -37,7 +37,10 @@ ports:
|
||||
jaeger-grpc:
|
||||
enabled: false
|
||||
zipkin:
|
||||
enabled: false
|
||||
enabled: true
|
||||
containerPort: 9411
|
||||
servicePort: 9411
|
||||
protocol: TCP
|
||||
|
||||
config:
|
||||
receivers:
|
||||
@@ -47,6 +50,8 @@ config:
|
||||
endpoint: "0.0.0.0:4317"
|
||||
http:
|
||||
endpoint: "0.0.0.0:4318"
|
||||
zipkin:
|
||||
endpoint: "0.0.0.0:9411"
|
||||
|
||||
connectors:
|
||||
spanmetrics:
|
||||
@@ -79,7 +84,7 @@ config:
|
||||
service:
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
receivers: [otlp, zipkin]
|
||||
processors: [memory_limiter, batch]
|
||||
exporters: [otlp/tempo, spanmetrics]
|
||||
metrics/spanmetrics:
|
||||
|
||||
+58
-1
@@ -209,7 +209,7 @@ data:
|
||||
datasources:
|
||||
- name: Tempo
|
||||
type: tempo
|
||||
url: http://tempo.monitoring:3100
|
||||
url: http://tempo.monitoring:3200
|
||||
access: proxy
|
||||
isDefault: false
|
||||
jsonData:
|
||||
@@ -413,6 +413,63 @@ log_success "Ingress demo-app criado"
|
||||
log_info "Aguardando demo-app ficar pronta..."
|
||||
kubectl wait --for=condition=available deployment/demo-app -n demo --timeout=180s 2>/dev/null || true
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURAR ISTIO TRACING (se Istio estiver instalado)
|
||||
# =============================================================================
|
||||
|
||||
if kubectl get deployment istiod -n istio-system &> /dev/null; then
|
||||
echo ""
|
||||
log_info "=== Istio detectado — configurando tracing via OTel Collector ==="
|
||||
|
||||
OTEL_ZIPKIN_URL="otel-collector-opentelemetry-collector.monitoring"
|
||||
|
||||
# Configurar istiod para enviar traces ao OTel Collector via Zipkin
|
||||
log_info "Configurando istiod para enviar traces ao OTel Collector..."
|
||||
helm upgrade istiod istio/istiod -n istio-system \
|
||||
--set "meshConfig.defaultConfig.tracing.zipkin.address=${OTEL_ZIPKIN_URL}:9411" \
|
||||
--set 'meshConfig.defaultConfig.tracing.sampling=100' \
|
||||
--set 'meshConfig.enablePrometheusMerge=true' \
|
||||
--set 'meshConfig.extensionProviders[0].name=zipkin' \
|
||||
--set "meshConfig.extensionProviders[0].zipkin.service=${OTEL_ZIPKIN_URL}.svc.cluster.local" \
|
||||
--set 'meshConfig.extensionProviders[0].zipkin.port=9411' \
|
||||
--reuse-values \
|
||||
--wait --timeout=5m 2>/dev/null || log_warn "Falha ao configurar istiod (helm upgrade)"
|
||||
log_success "istiod configurado"
|
||||
|
||||
# Criar Telemetry API resource
|
||||
log_info "Criando Telemetry resource..."
|
||||
kubectl apply -f - <<'TELEMETRY_EOF'
|
||||
apiVersion: telemetry.istio.io/v1
|
||||
kind: Telemetry
|
||||
metadata:
|
||||
name: mesh-default
|
||||
namespace: istio-system
|
||||
spec:
|
||||
tracing:
|
||||
- providers:
|
||||
- name: zipkin
|
||||
randomSamplingPercentage: 100
|
||||
TELEMETRY_EOF
|
||||
log_success "Telemetry resource criado"
|
||||
|
||||
# Atualizar Kiali para apontar tracing ao Grafana/Tempo
|
||||
log_info "Configurando Kiali para usar Tempo via Grafana..."
|
||||
VICTORIA_METRICS_URL="http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429"
|
||||
GRAFANA_HOST_INTERNAL="${GRAFANA_HOST:-grafana.${DOMAIN}}"
|
||||
kubectl patch configmap kiali -n istio-system --type merge -p "{
|
||||
\"data\": {
|
||||
\"config.yaml\": \"auth:\\n strategy: anonymous\\nlogin_token:\\n signing_key: kiali-signing-key-1234\\nserver:\\n web_root: /kiali\\nexternal_services:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: true\\n provider: tempo\\n internal_url: http://tempo.monitoring:3200\\n use_grpc: true\\n grafana:\\n enabled: false\\n\"
|
||||
}
|
||||
}" 2>/dev/null || true
|
||||
kubectl rollout restart deployment/kiali -n istio-system 2>/dev/null || true
|
||||
log_success "Kiali configurado com Tempo"
|
||||
|
||||
log_info "Reinicie os pods com sidecar para ativar tracing:"
|
||||
log_info " kubectl rollout restart deployment -n <namespace>"
|
||||
else
|
||||
log_info "Istio nao detectado — tracing apenas via OTel SDK (demo app)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# APLICAR ALERTAS
|
||||
# =============================================================================
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
# Aula 16 - Canary Automatizado com Flagger
|
||||
|
||||
## Motivação
|
||||
|
||||
Na aula 14, fizemos canary deployment na mão: alteramos os pesos do VirtualService com `kubectl patch`, observamos o comportamento, e decidimos promover ou reverter. Funcionou — mas imagine fazer isso toda sexta-feira às 18h com o time te pressionando pra deployar logo.
|
||||
|
||||
O **Flagger** resolve esse problema. Ele é um operador Kubernetes que automatiza o ciclo completo de canary deployment:
|
||||
|
||||
1. Detecta que uma nova versão foi deployada
|
||||
2. Cria pods canary com a nova versão
|
||||
3. Redireciona uma fração do tráfego (ex: 10%)
|
||||
4. Consulta métricas (taxa de sucesso, latência)
|
||||
5. Se tudo estiver saudável, aumenta o tráfego (20%, 30%, 50%...)
|
||||
6. Se algo estiver errado, reverte automaticamente para a versão anterior
|
||||
|
||||
Nesta aula, vamos aplicar o Flagger a um sistema real em produção: o **Streamify**.
|
||||
|
||||
## O que o Flagger faz por baixo dos panos
|
||||
|
||||
Quando você cria um recurso `Canary` apontando para um Deployment, o Flagger assume o controle:
|
||||
|
||||
```
|
||||
Você tem:
|
||||
─────────
|
||||
Deployment "web" (sua app)
|
||||
Service "web" (ClusterIP)
|
||||
|
||||
Flagger cria:
|
||||
─────────────
|
||||
Deployment "web-primary" (cópia estável, recebe 100% do tráfego)
|
||||
Service "web-primary" (aponta pros pods primary)
|
||||
Service "web-canary" (aponta pros pods canary)
|
||||
VirtualService (controla os pesos via Istio)
|
||||
```
|
||||
|
||||
O Deployment original (`web`) vira o "template" — quando você muda a image tag dele, o Flagger interpreta como "nova versão" e inicia o ciclo canary:
|
||||
|
||||
```
|
||||
CI muda image tag ──► ArgoCD sync ──► Flagger detecta mudança
|
||||
│
|
||||
▼
|
||||
Cria pods canary (nova versão)
|
||||
│
|
||||
▼
|
||||
VirtualService: 10% canary, 90% primary
|
||||
│
|
||||
▼
|
||||
Consulta Victoria Metrics:
|
||||
- Taxa de sucesso > 99%?
|
||||
- Latência p99 < 500ms?
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
OK? Falhou?
|
||||
│ │
|
||||
▼ ▼
|
||||
Aumenta peso Rollback automático
|
||||
20% → 30%... (0% canary, 100% primary)
|
||||
│
|
||||
▼
|
||||
50% atingido?
|
||||
│
|
||||
▼
|
||||
Promove: primary = nova versão
|
||||
VirtualService: 100% primary
|
||||
```
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Cluster Kubernetes │
|
||||
│ │
|
||||
│ ┌─── istio-system ──────────────────────────────────────────────┐ │
|
||||
│ │ istiod Flagger Controller │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ observa Canary CRD │ │
|
||||
│ │ │ gerencia VirtualService │ │
|
||||
│ │ │ consulta métricas │ │
|
||||
│ └───────────────────────┼───────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─── monitoring ────────┼───────────────────────────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ │ Victoria Metrics ◄───┘ query: istio_requests_total │ │
|
||||
│ │ ▲ istio_request_duration_* │ │
|
||||
│ │ │ scrape (:15020) │ │
|
||||
│ └───────┼───────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─── streamify-production ──────────────────────────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌────┴─────┐ ┌──────────────┐ ┌──────────────┐ │ │
|
||||
│ │ │ Envoy │ │ web-primary │ │ web-canary │ │ │
|
||||
│ │ │ sidecar │ │ (v1 estável) │ │ (v2 nova) │ │ │
|
||||
│ │ └──────────┘ └──────────────┘ └──────────────┘ │ │
|
||||
│ │ ▲ ▲ │ │
|
||||
│ │ 90% ─┘ └─ 10% │ │
|
||||
│ │ VirtualService (Istio) │ │
|
||||
│ │ ▲ │ │
|
||||
│ │ Internet ──► NGINX ──► svc/web-primary │ │
|
||||
│ │ Ingress │ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────┐ ┌────────────┐ ┌──────────┐ │ │
|
||||
│ │ │ queue │ │ schedule │ │ postgres │ (sem canary)│ │
|
||||
│ │ └────────────┘ └────────────┘ └──────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Conceitos
|
||||
|
||||
| Conceito | Descrição |
|
||||
|----------|-----------|
|
||||
| **Canary CRD** | Recurso customizado do Flagger que define a estratégia de deploy progressivo |
|
||||
| **Primary** | Deployment estável que recebe a maior parte do tráfego |
|
||||
| **Canary** | Pods temporários com a nova versão, recebendo tráfego incremental |
|
||||
| **Analysis** | Loop periódico onde Flagger consulta métricas e decide avançar ou reverter |
|
||||
| **Promotion** | Quando o canary atinge `maxWeight` com sucesso, a nova versão se torna primary |
|
||||
| **Rollback** | Quando métricas falham além do `threshold`, Flagger reverte para a versão anterior |
|
||||
| **stepWeight** | Incremento de tráfego a cada iteração (ex: 10% → 20% → 30%) |
|
||||
| **maxWeight** | Peso máximo do canary antes de promover (ex: 50%) |
|
||||
| **threshold** | Número de falhas toleradas antes de fazer rollback |
|
||||
| **Loadtester** | Componente que gera tráfego sintético para o canary ter métricas para análise |
|
||||
|
||||
## Por que Canary só no Web?
|
||||
|
||||
O Flagger analisa **métricas HTTP** (taxa de sucesso, latência). Os workers de queue e o scheduler não recebem tráfego HTTP externo — não há métricas HTTP para analisar. Por isso, apenas o Deployment `web` é gerenciado pelo Flagger. Queue e scheduler continuam com RollingUpdate normal.
|
||||
|
||||
## Regra de ouro: Migrations
|
||||
|
||||
Durante um canary, duas versões da aplicação rodam simultaneamente contra o **mesmo banco de dados**. Isso significa que migrations devem ser **sempre aditivas**:
|
||||
|
||||
| Seguro durante canary | Perigoso durante canary |
|
||||
|----------------------|------------------------|
|
||||
| `ADD COLUMN` | `DROP COLUMN` |
|
||||
| `ADD TABLE` | `RENAME COLUMN` |
|
||||
| `ADD INDEX` | `DROP TABLE` |
|
||||
| `ALTER COLUMN SET DEFAULT` | `ALTER COLUMN TYPE` |
|
||||
|
||||
Se precisar remover uma coluna, faça em duas releases:
|
||||
1. **Release A** (canary): deploy do código que **não usa mais** a coluna
|
||||
2. **Release B** (depois de promoted): migration que **remove** a coluna
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Cluster Kubernetes (aula-08)
|
||||
- Istio instalado (aula-14)
|
||||
- Victoria Metrics (aula-12)
|
||||
- Tempo + OTel Collector (aula-15)
|
||||
- ArgoCD (aula-11)
|
||||
- Streamify deployado em produção (`streamify-production`)
|
||||
- kubectl, helm
|
||||
|
||||
## Estrutura
|
||||
|
||||
```
|
||||
aula-16/
|
||||
├── README.md # Esta documentação
|
||||
├── setup.sh # Instalação automatizada
|
||||
├── cleanup.sh # Remoção limpa
|
||||
└── flagger-values.yaml # Configuração do Flagger
|
||||
```
|
||||
|
||||
## Instalação
|
||||
|
||||
```bash
|
||||
cd aula-16
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
O script vai:
|
||||
1. Verificar pré-requisitos
|
||||
2. Instalar o Flagger no cluster
|
||||
3. Configurar coleta de métricas do Istio
|
||||
4. Habilitar sidecar injection no namespace de produção
|
||||
5. Aplicar as mudanças no Helm chart do Streamify
|
||||
6. Aguardar o Flagger inicializar
|
||||
|
||||
## Verificação
|
||||
|
||||
Após a instalação, verifique se o Flagger inicializou corretamente:
|
||||
|
||||
```bash
|
||||
# Status do Canary
|
||||
kubectl get canary -n streamify-production
|
||||
|
||||
# Deve mostrar:
|
||||
# NAME STATUS WEIGHT
|
||||
# streamify-production-web Initialized 0
|
||||
|
||||
# Services criados pelo Flagger
|
||||
kubectl get svc -n streamify-production | grep web
|
||||
|
||||
# Deve mostrar 3 services:
|
||||
# streamify-production-web (original, gerenciado pelo Flagger)
|
||||
# streamify-production-web-primary (versão estável)
|
||||
# streamify-production-web-canary (versão nova durante canary)
|
||||
|
||||
# VirtualService criado pelo Flagger
|
||||
kubectl get virtualservice -n streamify-production
|
||||
```
|
||||
|
||||
## Exercício 1: Deploy com sucesso
|
||||
|
||||
Simule um deploy normal. O Flagger vai criar o canary, analisar as métricas, e promover automaticamente.
|
||||
|
||||
```bash
|
||||
# 1. Observe o canary em tempo real (deixe rodando num terminal)
|
||||
kubectl get canary -n streamify-production -w
|
||||
|
||||
# 2. Em outro terminal, faça um deploy (mude a image tag)
|
||||
# No repo streamify-deploy, edite values-production.yaml:
|
||||
# web.image.tag: <novo-sha>
|
||||
# Commit e push. ArgoCD vai sincronizar.
|
||||
|
||||
# 3. Acompanhe o progresso
|
||||
kubectl describe canary streamify-production-web -n streamify-production
|
||||
|
||||
# 4. Observe no Kiali o tráfego sendo dividido
|
||||
# https://kiali.kube.quest/kiali/
|
||||
```
|
||||
|
||||
Saída esperada:
|
||||
```
|
||||
NAME STATUS WEIGHT LASTTRANSITION
|
||||
streamify-production-web Progressing 0 ...
|
||||
streamify-production-web Progressing 10 ...
|
||||
streamify-production-web Progressing 20 ...
|
||||
streamify-production-web Progressing 30 ...
|
||||
streamify-production-web Progressing 40 ...
|
||||
streamify-production-web Progressing 50 ...
|
||||
streamify-production-web Promoting 0 ...
|
||||
streamify-production-web Succeeded 0 ...
|
||||
```
|
||||
|
||||
## Exercício 2: Deploy com falha (rollback automático)
|
||||
|
||||
Simule um deploy defeituoso. O Flagger vai detectar a falha nas métricas e reverter automaticamente.
|
||||
|
||||
```bash
|
||||
# 1. Observe o canary em tempo real
|
||||
kubectl get canary -n streamify-production -w
|
||||
|
||||
# 2. Faça um deploy com uma imagem que retorna erros
|
||||
# Use uma tag inválida ou uma versão com bug
|
||||
|
||||
# 3. O Flagger vai detectar:
|
||||
# - Taxa de sucesso < 99%
|
||||
# - Latência > 500ms
|
||||
# E vai reverter automaticamente
|
||||
|
||||
# 4. Verifique os eventos
|
||||
kubectl get events -n streamify-production --sort-by='.lastTimestamp' | grep -i canary
|
||||
```
|
||||
|
||||
Saída esperada:
|
||||
```
|
||||
NAME STATUS WEIGHT LASTTRANSITION
|
||||
streamify-production-web Failed 0 ...
|
||||
```
|
||||
|
||||
## Exercício 3: Observabilidade
|
||||
|
||||
Durante um canary ativo, observe:
|
||||
|
||||
1. **Kiali** (`https://kiali.kube.quest/kiali/`) — Graph mostra tráfego dividido entre primary e canary
|
||||
2. **Grafana/Tempo** — Explore > Tempo > traces mostram requests para ambas as versões
|
||||
3. **Victoria Metrics** — Query: `istio_requests_total{destination_workload=~"streamify.*"}`
|
||||
|
||||
## Comandos úteis
|
||||
|
||||
```bash
|
||||
# Status do canary
|
||||
kubectl get canary -n streamify-production
|
||||
|
||||
# Detalhes e eventos
|
||||
kubectl describe canary streamify-production-web -n streamify-production
|
||||
|
||||
# Logs do Flagger
|
||||
kubectl logs deployment/flagger -n istio-system -f
|
||||
|
||||
# Pesos atuais do VirtualService
|
||||
kubectl get virtualservice streamify-production-web -n streamify-production -o jsonpath='{.spec.http[0].route}' | python3 -m json.tool
|
||||
|
||||
# Forçar promoção manual (emergência)
|
||||
kubectl annotate canary streamify-production-web -n streamify-production \
|
||||
flagger.app/promote="true" --overwrite
|
||||
|
||||
# Forçar rollback manual (emergência)
|
||||
kubectl annotate canary streamify-production-web -n streamify-production \
|
||||
flagger.app/rollback="true" --overwrite
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
./cleanup.sh
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Canary fica em "Initializing" por muito tempo
|
||||
|
||||
Verifique se o Flagger está rodando:
|
||||
```bash
|
||||
kubectl get pods -n istio-system -l app.kubernetes.io/name=flagger
|
||||
kubectl logs deployment/flagger -n istio-system --tail=20
|
||||
```
|
||||
|
||||
### Métricas Istio não aparecem no Victoria Metrics
|
||||
|
||||
Verifique se os VMPodScrapes foram criados:
|
||||
```bash
|
||||
kubectl get vmpodscrape -n monitoring
|
||||
```
|
||||
|
||||
E se os sidecars estão injetados:
|
||||
```bash
|
||||
kubectl get pods -n streamify-production -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}'
|
||||
```
|
||||
|
||||
Cada pod deve ter 2 containers: `web` e `istio-proxy`.
|
||||
|
||||
### Canary falha imediatamente (threshold reached)
|
||||
|
||||
Geralmente significa que não há métricas suficientes. Verifique:
|
||||
1. O loadtester está gerando tráfego?
|
||||
2. Victoria Metrics tem dados? `istio_requests_total{destination_workload="streamify-production-web"}`
|
||||
|
||||
### Ingress retorna 503 após habilitar canary
|
||||
|
||||
O Ingress precisa apontar para o service `-primary`. Verifique:
|
||||
```bash
|
||||
kubectl get ingress -n streamify-production -o yaml | grep "name:"
|
||||
```
|
||||
|
||||
### ArgoCD mostra "OutOfSync" constantemente
|
||||
|
||||
O Flagger modifica o VirtualService dinamicamente. Configure `ignoreDifferences` no ArgoCD Application:
|
||||
```yaml
|
||||
ignoreDifferences:
|
||||
- group: networking.istio.io
|
||||
kind: VirtualService
|
||||
jsonPointers:
|
||||
- /spec/http/0/route
|
||||
```
|
||||
|
||||
## Dashboard: Streamify - Observabilidade Istio
|
||||
|
||||
O setup instala um dashboard no Grafana com 4 painéis que funcionam out-of-the-box com as métricas do Istio:
|
||||
|
||||
| Painel | O que mostra | Query |
|
||||
|--------|-------------|-------|
|
||||
| **Request Rate** | Requests/s por serviço | `sum by(destination_service_name) (rate(istio_requests_total{...}[5m]))` |
|
||||
| **Error Rate (5xx)** | Erros por serviço e código | `rate(istio_requests_total{response_code=~"5.*"}[5m])` |
|
||||
| **Latência p99** | Percentil 99 de latência | `histogram_quantile(0.99, rate(istio_request_duration_milliseconds_bucket{...}[5m]))` |
|
||||
| **Saturação de Memória** | % do memory limit usado | `container_memory_working_set_bytes / container_spec_memory_limit_bytes` |
|
||||
|
||||
Acesse: Grafana > Dashboards > Streamify - Observabilidade Istio
|
||||
|
||||
### O que esses painéis revelam durante um canary
|
||||
|
||||
- **Request Rate**: pico do loadtester visível (~15 req/s), tráfego dividido entre primary e canary
|
||||
- **Error Rate**: se a nova versão gera 500s, aparece aqui antes do Flagger reverter
|
||||
- **Latência p99**: degradação de performance da nova versão é visível por spike de latência
|
||||
- **Saturação**: se a nova versão tem memory leak, o painel mostra a curva subindo
|
||||
|
||||
## Além do Workshop: Observabilidade em Produção
|
||||
|
||||
Este workshop foca no pipeline **deploy → métricas → decisão automática**. Em produção real, o Grafana pode ir muito além. Abaixo está o que é viável em cada nível, com as limitações intencionais do workshop documentadas.
|
||||
|
||||
### Nível 1: Já funciona com o que temos (só falta dashboard)
|
||||
|
||||
Tudo abaixo usa métricas que o Istio + kubelet já exportam:
|
||||
|
||||
**CPU Throttling** — Mais útil que "% de CPU". Mostra quando o kernel está limitando o container:
|
||||
```promql
|
||||
rate(container_cpu_cfs_throttled_seconds_total{namespace="streamify-production"}[5m])
|
||||
```
|
||||
|
||||
**Deploy Impact** — Correlacionar timestamps de deploy com error rate. O Flagger gera eventos Kubernetes que podem virar annotations no Grafana.
|
||||
|
||||
**Request rate por response code** — Ver a distribuição 200/404/500 ao longo do tempo. Detecta regressões antes do usuário reclamar.
|
||||
|
||||
### Nível 2: Viável com instrumentação na app (~dias de trabalho)
|
||||
|
||||
Requer mudanças no código do Streamify (OTel SDK para Rails):
|
||||
|
||||
**Latência por camada (app vs banco vs fila)** — O Istio só dá o trace da camada de rede. Para ver "quanto tempo gastou no Postgres" ou "quanto tempo na fila do Sidekiq", precisa instrumentar o Rails com OpenTelemetry SDK. O OTel Collector e Tempo já estão prontos para receber esses traces.
|
||||
|
||||
**Métricas de negócio** — Leads processados/min, mensagens enviadas, conversões por hora. Requer que a app exporte métricas custom via `/metrics` (Prometheus client) ou OTel SDK. Combinar infra + negócio no mesmo dashboard responde: "a infra impactou receita?"
|
||||
|
||||
**Custo por namespace/cliente** — Para multi-tenant: custo por pod, por feature, por cliente. Requer dados de billing (Hetzner API) + alocação de recursos por label.
|
||||
|
||||
### Nível 3: Possível mas desproporcional para o contexto atual
|
||||
|
||||
Documentado para referência — faz sentido quando o sistema crescer:
|
||||
|
||||
**Detecção de anomalia sem threshold fixo** — Baseline histórico em vez de "alerta se CPU > 80%". Na prática, precisa de volume de dados significativo para o modelo estatístico funcionar. Com o tráfego atual do Streamify, thresholds fixos são mais confiáveis.
|
||||
|
||||
**Fingerprint de falhas** — Padrões como "CPU normal + latência alta + fila crescendo = problema de I/O externo". Faz sentido com 50+ microserviços onde os padrões se repetem. Com 1 app, o troubleshooting manual é mais rápido.
|
||||
|
||||
**Auto-healing contextual** — Escalar baseado em tipo de erro, trocar rotas, fallback automático. O Flagger + HPA já cobrem 90% disso. O restante é overengineering para o tamanho atual.
|
||||
|
||||
**ROI por recurso** — Cruzar custo de infra com valor gerado. Poderoso para SaaS em escala, mas o esforço de implementação não se paga com poucos serviços.
|
||||
|
||||
### Por que o workshop para aqui
|
||||
|
||||
O objetivo das aulas 14-16 é demonstrar o ciclo completo:
|
||||
|
||||
```
|
||||
código → CI → imagem → ArgoCD → Flagger → métricas → decisão → promote/rollback
|
||||
↕
|
||||
traces (Tempo)
|
||||
métricas (Victoria Metrics)
|
||||
visualização (Grafana + Kiali)
|
||||
```
|
||||
|
||||
Esse pipeline já resolve o problema real: **deploy seguro com rollback automático baseado em dados**. Os níveis 2 e 3 são extensões naturais conforme o sistema cresce — a infraestrutura de observabilidade (Tempo, OTel Collector, Victoria Metrics) já está pronta para recebê-los.
|
||||
|
||||
## Referências
|
||||
|
||||
- [Flagger — Progressive Delivery](https://flagger.app/)
|
||||
- [Flagger + Istio Tutorial](https://docs.flagger.app/tutorials/istio-progressive-delivery)
|
||||
- [Istio Traffic Management](https://istio.io/latest/docs/concepts/traffic-management/)
|
||||
- [OpenTelemetry Ruby SDK](https://opentelemetry.io/docs/languages/ruby/)
|
||||
- [Grafana Tempo — TraceQL](https://grafana.com/docs/tempo/latest/traceql/)
|
||||
- [USE Method (Utilization, Saturation, Errors)](https://www.brendangregg.com/usemethod.html)
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Aula 16 - Cleanup do Flagger
|
||||
# ============================================================================
|
||||
# Remove o Flagger e reverte as configurações de canary.
|
||||
# NÃO remove o Istio (aula-14) nem o Victoria Metrics (aula-12).
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERRO]${NC} $1"; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ Cleanup - Aula 16 (Flagger) ║${NC}"
|
||||
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Isso vai remover:${NC}"
|
||||
echo " - Flagger Loadtester (streamify-production)"
|
||||
echo " - Flagger Controller (istio-system)"
|
||||
echo " - Flagger CRDs"
|
||||
echo " - VMPodScrapes do Istio"
|
||||
echo " - PeerAuthentication"
|
||||
echo " - Label istio-injection do namespace"
|
||||
echo ""
|
||||
echo -e "${YELLOW}NÃO vai remover:${NC}"
|
||||
echo " - Istio (aula-14)"
|
||||
echo " - Victoria Metrics (aula-12)"
|
||||
echo " - Mudanças no Helm chart (reverter manualmente no repo streamify-deploy)"
|
||||
echo ""
|
||||
read -p "Confirmar remoção? (digite 'sim'): " confirm
|
||||
if [[ "$confirm" != "sim" ]]; then
|
||||
echo "Cancelado."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 1. Remover Loadtester
|
||||
log_info "Removendo Flagger Loadtester..."
|
||||
helm uninstall flagger-loadtester -n streamify-production --wait 2>/dev/null || true
|
||||
log_success "Loadtester removido"
|
||||
|
||||
# 2. Remover Canary CRD resource (para Flagger devolver controle do deployment)
|
||||
log_info "Removendo Canary resources..."
|
||||
kubectl delete canary --all -n streamify-production --ignore-not-found=true 2>/dev/null || true
|
||||
log_success "Canary resources removidos"
|
||||
|
||||
# 3. Aguardar Flagger restaurar o deployment original
|
||||
log_info "Aguardando restauração do deployment..."
|
||||
sleep 10
|
||||
|
||||
# 4. Remover Flagger
|
||||
log_info "Removendo Flagger..."
|
||||
helm uninstall flagger -n istio-system --wait 2>/dev/null || true
|
||||
log_success "Flagger removido"
|
||||
|
||||
# 5. Remover CRDs
|
||||
log_info "Removendo Flagger CRDs..."
|
||||
kubectl delete crd canaries.flagger.app metrictemplates.flagger.app alertproviders.flagger.app 2>/dev/null || true
|
||||
log_success "CRDs removidos"
|
||||
|
||||
# 6. Remover VMPodScrapes
|
||||
log_info "Removendo VMPodScrapes do Istio..."
|
||||
kubectl delete vmpodscrape envoy-stats istiod -n monitoring --ignore-not-found=true 2>/dev/null || true
|
||||
log_success "VMPodScrapes removidos"
|
||||
|
||||
# 7. Remover PeerAuthentication
|
||||
log_info "Removendo PeerAuthentication..."
|
||||
kubectl delete peerauthentication default -n streamify-production --ignore-not-found=true 2>/dev/null || true
|
||||
log_success "PeerAuthentication removida"
|
||||
|
||||
# 8. Remover label de sidecar injection
|
||||
log_info "Removendo label istio-injection..."
|
||||
kubectl label namespace streamify-production istio-injection- 2>/dev/null || true
|
||||
log_success "Label removida"
|
||||
|
||||
# 9. Reiniciar pods para remover sidecars
|
||||
log_info "Reiniciando deployments para remover sidecars..."
|
||||
kubectl rollout restart deployment -n streamify-production 2>/dev/null || true
|
||||
kubectl rollout status deployment/streamify-production-web -n streamify-production --timeout=300s 2>/dev/null || true
|
||||
log_success "Sidecars removidos"
|
||||
|
||||
# 10. Limpar VirtualService e DestinationRule órfãos
|
||||
log_info "Removendo VirtualServices e DestinationRules órfãos..."
|
||||
kubectl delete virtualservice --all -n streamify-production --ignore-not-found=true 2>/dev/null || true
|
||||
kubectl delete destinationrule --all -n streamify-production --ignore-not-found=true 2>/dev/null || true
|
||||
log_success "Recursos Istio limpos"
|
||||
|
||||
# 11. Remover .env
|
||||
read -p "Remover arquivo .env? [s/N]: " remove_env
|
||||
if [[ "$remove_env" == "s" || "$remove_env" == "S" ]]; then
|
||||
rm -f "${SCRIPT_DIR}/.env"
|
||||
log_success ".env removido"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Cleanup concluído!${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Lembrete:${NC} Para reverter as mudanças no Helm chart:"
|
||||
echo " 1. Remova canary.enabled do values-production.yaml"
|
||||
echo " 2. Remova o template canary.yaml"
|
||||
echo " 3. Reverta a linha do ingress.yaml (remova o sufixo -primary)"
|
||||
echo " 4. Commit e push no repo streamify-deploy"
|
||||
echo ""
|
||||
@@ -0,0 +1,5 @@
|
||||
# Flagger - Progressive Delivery Operator
|
||||
# Integrado com Istio + Victoria Metrics
|
||||
|
||||
meshProvider: istio
|
||||
metricsServer: http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429
|
||||
Executable
+739
@@ -0,0 +1,739 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Aula 16 - Canary Automatizado com Flagger
|
||||
# ============================================================================
|
||||
# Instala o Flagger para canary deployment automatizado do Streamify
|
||||
# usando Istio para traffic splitting e Victoria Metrics para métricas.
|
||||
#
|
||||
# Componentes:
|
||||
# - Flagger (operador de progressive delivery)
|
||||
# - Flagger Loadtester (gerador de tráfego sintético)
|
||||
# - VMPodScrapes (coleta de métricas Istio)
|
||||
# - Canary CRD (configuração no Helm chart do Streamify)
|
||||
#
|
||||
# Pré-requisitos:
|
||||
# - Cluster Kubernetes (aula-08)
|
||||
# - Istio instalado (aula-14)
|
||||
# - Victoria Metrics (aula-12)
|
||||
# - Tempo + OTel Collector (aula-15)
|
||||
# - ArgoCD (aula-11)
|
||||
# - Streamify em produção (streamify-production)
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERRO]${NC} $1"; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="${SCRIPT_DIR}/.env"
|
||||
|
||||
# ============================================================================
|
||||
# Gerenciamento de Configuração
|
||||
# ============================================================================
|
||||
|
||||
load_config() {
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
source "$ENV_FILE"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
save_config() {
|
||||
cat > "$ENV_FILE" << EOF
|
||||
# Configuração da Aula 16 - Flagger Canary
|
||||
# Gerado em: $(date)
|
||||
|
||||
GITEA_HOST=${GITEA_HOST}
|
||||
DEPLOY_REPO=${DEPLOY_REPO}
|
||||
EOF
|
||||
log_success "Configuração salva em .env"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Verificação de Pré-requisitos
|
||||
# ============================================================================
|
||||
|
||||
check_prerequisites() {
|
||||
echo ""
|
||||
log_info "Verificando pré-requisitos..."
|
||||
|
||||
local failed=false
|
||||
|
||||
for cmd in kubectl helm git; do
|
||||
if command -v "$cmd" &> /dev/null; then
|
||||
log_success "$cmd encontrado"
|
||||
else
|
||||
log_error "$cmd não encontrado"
|
||||
failed=true
|
||||
fi
|
||||
done
|
||||
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
log_error "Cluster Kubernetes não acessível"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Cluster Kubernetes acessível"
|
||||
|
||||
# Verificar Istio
|
||||
if kubectl get deployment istiod -n istio-system &> /dev/null; then
|
||||
log_success "Istio encontrado"
|
||||
else
|
||||
log_error "Istio não instalado. Execute a aula-14 primeiro."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verificar Victoria Metrics
|
||||
if kubectl get svc -n monitoring vmsingle-monitoring-victoria-metrics-k8s-stack &> /dev/null; then
|
||||
log_success "Victoria Metrics encontrado"
|
||||
else
|
||||
log_error "Victoria Metrics não encontrado. Execute a aula-12 primeiro."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verificar ArgoCD
|
||||
if kubectl get deployment argocd-server -n argocd &> /dev/null; then
|
||||
log_success "ArgoCD encontrado"
|
||||
else
|
||||
log_error "ArgoCD não encontrado. Execute a aula-11 primeiro."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verificar Streamify em produção
|
||||
if kubectl get deployment streamify-production-web -n streamify-production &> /dev/null; then
|
||||
log_success "Streamify production encontrado"
|
||||
else
|
||||
log_error "Streamify não está deployado em streamify-production."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$failed" == "true" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Coleta de Configuração
|
||||
# ============================================================================
|
||||
|
||||
collect_config() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Configuração do Flagger${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
|
||||
if load_config; then
|
||||
echo -e "Configuração existente encontrada:"
|
||||
echo -e " Gitea: ${GREEN}${GITEA_HOST}${NC}"
|
||||
echo -e " Repo: ${GREEN}${DEPLOY_REPO}${NC}"
|
||||
echo ""
|
||||
echo -e "[1] Usar configuração existente"
|
||||
echo -e "[2] Inserir nova configuração"
|
||||
read -p "Escolha [1/2]: " choice
|
||||
if [[ "$choice" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ -z "$GITEA_HOST" ]]; then
|
||||
GITEA_HOST="gitea.kube.quest"
|
||||
fi
|
||||
echo -e "Host do Gitea: ${GREEN}${GITEA_HOST}${NC}"
|
||||
read -p "Enter para confirmar ou digite novo valor: " new_host
|
||||
[[ -n "$new_host" ]] && GITEA_HOST="$new_host"
|
||||
|
||||
if [[ -z "$DEPLOY_REPO" ]]; then
|
||||
DEPLOY_REPO="depaula/streamify-deploy"
|
||||
fi
|
||||
echo -e "Repositório de deploy: ${GREEN}${DEPLOY_REPO}${NC}"
|
||||
read -p "Enter para confirmar ou digite novo valor: " new_repo
|
||||
[[ -n "$new_repo" ]] && DEPLOY_REPO="$new_repo"
|
||||
|
||||
save_config
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Instalação do Flagger
|
||||
# ============================================================================
|
||||
|
||||
install_flagger() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Instalando Flagger${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
helm repo add flagger https://flagger.app 2>/dev/null || true
|
||||
helm repo update flagger
|
||||
|
||||
log_info "Instalando Flagger CRDs..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml 2>/dev/null
|
||||
log_success "CRDs instalados"
|
||||
|
||||
log_info "Instalando Flagger no namespace istio-system..."
|
||||
if helm status flagger -n istio-system &> /dev/null; then
|
||||
helm upgrade flagger flagger/flagger \
|
||||
-n istio-system \
|
||||
-f "${SCRIPT_DIR}/flagger-values.yaml" \
|
||||
--wait
|
||||
else
|
||||
helm install flagger flagger/flagger \
|
||||
-n istio-system \
|
||||
-f "${SCRIPT_DIR}/flagger-values.yaml" \
|
||||
--wait
|
||||
fi
|
||||
log_success "Flagger instalado"
|
||||
|
||||
log_info "Aguardando Flagger..."
|
||||
kubectl wait --for=condition=available deployment/flagger -n istio-system --timeout=120s
|
||||
log_success "Flagger pronto"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Instalação do Loadtester
|
||||
# ============================================================================
|
||||
|
||||
install_loadtester() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Instalando Loadtester${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "O loadtester gera tráfego sintético para que o Flagger"
|
||||
log_info "tenha métricas suficientes para analisar o canary."
|
||||
echo ""
|
||||
|
||||
if helm status flagger-loadtester -n streamify-production &> /dev/null; then
|
||||
helm upgrade flagger-loadtester flagger/loadtester \
|
||||
-n streamify-production \
|
||||
--wait
|
||||
else
|
||||
helm install flagger-loadtester flagger/loadtester \
|
||||
-n streamify-production \
|
||||
--wait
|
||||
fi
|
||||
log_success "Loadtester instalado em streamify-production"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuração de Métricas Istio no Victoria Metrics
|
||||
# ============================================================================
|
||||
|
||||
setup_istio_metrics() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Configurando coleta de métricas Istio${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "Criando VMPodScrape para sidecars Envoy..."
|
||||
cat <<'EOF' | kubectl apply -f -
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMPodScrape
|
||||
metadata:
|
||||
name: envoy-stats
|
||||
namespace: monitoring
|
||||
labels:
|
||||
app.kubernetes.io/name: istio
|
||||
app.kubernetes.io/component: envoy
|
||||
spec:
|
||||
podMetricsEndpoints:
|
||||
- port: http-envoy-prom
|
||||
path: /stats/prometheus
|
||||
relabelConfigs:
|
||||
- action: keep
|
||||
sourceLabels: [__meta_kubernetes_pod_container_name]
|
||||
regex: "istio-proxy"
|
||||
namespaceSelector:
|
||||
any: true
|
||||
EOF
|
||||
log_success "VMPodScrape envoy-stats criado"
|
||||
|
||||
log_info "Criando VMPodScrape para istiod..."
|
||||
cat <<'EOF' | kubectl apply -f -
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMPodScrape
|
||||
metadata:
|
||||
name: istiod
|
||||
namespace: monitoring
|
||||
labels:
|
||||
app.kubernetes.io/name: istio
|
||||
app.kubernetes.io/component: istiod
|
||||
spec:
|
||||
podMetricsEndpoints:
|
||||
- port: http-monitoring
|
||||
path: /metrics
|
||||
selector:
|
||||
matchLabels:
|
||||
app: istiod
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- istio-system
|
||||
EOF
|
||||
log_success "VMPodScrape istiod criado"
|
||||
|
||||
log_info "As métricas do Istio levarão ~60s para aparecer no Victoria Metrics."
|
||||
|
||||
# Aumentar memória do Victoria Metrics (512Mi é insuficiente com métricas Istio)
|
||||
local VM_MEM
|
||||
VM_MEM=$(kubectl get vmsingle monitoring-victoria-metrics-k8s-stack -n monitoring \
|
||||
-o jsonpath='{.spec.resources.limits.memory}' 2>/dev/null || echo "512Mi")
|
||||
if [[ "$VM_MEM" == "512Mi" ]]; then
|
||||
log_warn "Victoria Metrics com apenas 512Mi — insuficiente para métricas Istio."
|
||||
log_info "Aumentando memória para 1536Mi..."
|
||||
kubectl patch vmsingle monitoring-victoria-metrics-k8s-stack -n monitoring \
|
||||
--type merge -p '{"spec":{"resources":{"limits":{"memory":"1536Mi"},"requests":{"memory":"512Mi"}}}}' 2>/dev/null || true
|
||||
log_success "Victoria Metrics memory atualizado para 1536Mi"
|
||||
else
|
||||
log_success "Victoria Metrics memory OK ($VM_MEM)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Habilitar Istio Sidecar Injection
|
||||
# ============================================================================
|
||||
|
||||
enable_sidecar_injection() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Habilitando Istio sidecar injection${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "Adicionando labels no namespace streamify-production..."
|
||||
kubectl label namespace streamify-production \
|
||||
istio-injection=enabled \
|
||||
pod-security.kubernetes.io/enforce=privileged \
|
||||
--overwrite
|
||||
|
||||
log_success "Labels adicionadas"
|
||||
|
||||
# Verificar se os pods já têm sidecar
|
||||
local PROXY_COUNT
|
||||
PROXY_COUNT=$(kubectl get pods -n streamify-production -o jsonpath='{range .items[*]}{.spec.containers[*].name}{"\n"}{end}' | grep -c "istio-proxy" || echo "0")
|
||||
|
||||
if [[ "$PROXY_COUNT" -eq 0 ]]; then
|
||||
log_info "Reiniciando deployments para injetar sidecars..."
|
||||
kubectl rollout restart deployment -n streamify-production
|
||||
log_info "Aguardando pods ficarem prontos..."
|
||||
kubectl rollout status deployment/streamify-production-web -n streamify-production --timeout=300s
|
||||
kubectl rollout status deployment/streamify-production-queue -n streamify-production --timeout=120s
|
||||
kubectl rollout status deployment/streamify-production-schedule -n streamify-production --timeout=120s
|
||||
log_success "Sidecars injetados em todos os pods"
|
||||
else
|
||||
log_success "Sidecars já estão injetados"
|
||||
fi
|
||||
|
||||
# Configurar PeerAuthentication para permitir tráfego do NGINX Ingress
|
||||
log_info "Configurando PeerAuthentication (PERMISSIVE) para NGINX Ingress..."
|
||||
cat <<'EOF' | kubectl apply -f -
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata:
|
||||
name: default
|
||||
namespace: streamify-production
|
||||
spec:
|
||||
mtls:
|
||||
mode: PERMISSIVE
|
||||
EOF
|
||||
log_success "PeerAuthentication configurada"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Modificar Helm Chart do Streamify
|
||||
# ============================================================================
|
||||
|
||||
update_helm_chart() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Atualizando Helm chart do Streamify${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
local DEPLOY_DIR
|
||||
DEPLOY_DIR=$(mktemp -d)
|
||||
|
||||
log_info "Clonando repositório de deploy..."
|
||||
git clone "https://${GITEA_HOST}/${DEPLOY_REPO}.git" "$DEPLOY_DIR" 2>&1 | tail -1
|
||||
log_success "Repositório clonado"
|
||||
|
||||
# Verificar se canary já está configurado
|
||||
if grep -q "canary:" "$DEPLOY_DIR/values.yaml" 2>/dev/null; then
|
||||
log_warn "Configuração canary já existe no values.yaml"
|
||||
log_info "Verificando se precisa atualizar..."
|
||||
fi
|
||||
|
||||
# 1. Adicionar bloco canary no values.yaml (defaults)
|
||||
if ! grep -q "canary:" "$DEPLOY_DIR/values.yaml"; then
|
||||
log_info "Adicionando configuração canary ao values.yaml..."
|
||||
cat >> "$DEPLOY_DIR/values.yaml" << 'EOF'
|
||||
|
||||
canary:
|
||||
enabled: false
|
||||
analysis:
|
||||
interval: "1m"
|
||||
threshold: 5
|
||||
maxWeight: 50
|
||||
stepWeight: 10
|
||||
metrics:
|
||||
requestSuccessRate: 99
|
||||
requestDuration: 500
|
||||
EOF
|
||||
log_success "values.yaml atualizado"
|
||||
fi
|
||||
|
||||
# 2. Adicionar canary.enabled: true no values-production.yaml
|
||||
if ! grep -q "canary:" "$DEPLOY_DIR/values-production.yaml"; then
|
||||
log_info "Habilitando canary no values-production.yaml..."
|
||||
cat >> "$DEPLOY_DIR/values-production.yaml" << 'EOF'
|
||||
|
||||
canary:
|
||||
enabled: true
|
||||
EOF
|
||||
log_success "values-production.yaml atualizado"
|
||||
fi
|
||||
|
||||
# 3. Criar template canary.yaml
|
||||
log_info "Criando template canary.yaml..."
|
||||
cat > "$DEPLOY_DIR/templates/canary.yaml" << 'TMPL'
|
||||
{{- if .Values.canary.enabled }}
|
||||
apiVersion: flagger.app/v1beta1
|
||||
kind: Canary
|
||||
metadata:
|
||||
name: {{ include "streamify.fullname" . }}-web
|
||||
labels:
|
||||
{{- include "streamify.web.labels" . | nindent 4 }}
|
||||
spec:
|
||||
targetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "streamify.fullname" . }}-web
|
||||
service:
|
||||
port: {{ .Values.web.service.port }}
|
||||
targetPort: http
|
||||
gateways:
|
||||
- mesh
|
||||
hosts:
|
||||
- {{ include "streamify.fullname" . }}-web
|
||||
analysis:
|
||||
interval: {{ .Values.canary.analysis.interval }}
|
||||
threshold: {{ .Values.canary.analysis.threshold }}
|
||||
maxWeight: {{ .Values.canary.analysis.maxWeight }}
|
||||
stepWeight: {{ .Values.canary.analysis.stepWeight }}
|
||||
metrics:
|
||||
- name: request-success-rate
|
||||
thresholdRange:
|
||||
min: {{ .Values.canary.analysis.metrics.requestSuccessRate }}
|
||||
interval: 30s
|
||||
- name: request-duration
|
||||
thresholdRange:
|
||||
max: {{ .Values.canary.analysis.metrics.requestDuration }}
|
||||
interval: 30s
|
||||
webhooks:
|
||||
- name: load-test
|
||||
url: http://flagger-loadtester.streamify-production/
|
||||
type: rollout
|
||||
metadata:
|
||||
cmd: "hey -z 1m -q 5 -c 2 http://{{ include "streamify.fullname" . }}-web-canary.streamify-production/up"
|
||||
{{- end }}
|
||||
TMPL
|
||||
log_success "canary.yaml criado"
|
||||
|
||||
# 4. Modificar ingress.yaml para apontar para -primary
|
||||
log_info "Ajustando ingress.yaml para service -primary..."
|
||||
if ! grep -q "canary.enabled" "$DEPLOY_DIR/templates/ingress.yaml"; then
|
||||
sed -i.bak 's/name: {{ include "streamify.fullname" \$ }}-web$/name: {{ include "streamify.fullname" $ }}-web{{- if $.Values.canary.enabled }}-primary{{- end }}/' \
|
||||
"$DEPLOY_DIR/templates/ingress.yaml"
|
||||
rm -f "$DEPLOY_DIR/templates/ingress.yaml.bak"
|
||||
log_success "ingress.yaml atualizado"
|
||||
else
|
||||
log_warn "ingress.yaml já contém configuração canary"
|
||||
fi
|
||||
|
||||
# 5. Adicionar annotation para sidecars aguardarem antes dos init containers
|
||||
log_info "Ajustando web.yaml para holdApplicationUntilProxyStarts..."
|
||||
if ! grep -q "holdApplicationUntilProxyStarts" "$DEPLOY_DIR/templates/deployments/web.yaml"; then
|
||||
sed -i.bak '/checksum\/secret/a\
|
||||
{{- if .Values.canary.enabled }}\
|
||||
proxy.istio.io/config: '"'"'{"holdApplicationUntilProxyStarts": true}'"'"'\
|
||||
{{- end }}' \
|
||||
"$DEPLOY_DIR/templates/deployments/web.yaml"
|
||||
rm -f "$DEPLOY_DIR/templates/deployments/web.yaml.bak"
|
||||
log_success "web.yaml atualizado com holdApplicationUntilProxyStarts"
|
||||
else
|
||||
log_warn "web.yaml já contém holdApplicationUntilProxyStarts"
|
||||
fi
|
||||
|
||||
# 6. Modificar hpa.yaml para desabilitar quando canary ativo
|
||||
log_info "Ajustando hpa.yaml..."
|
||||
if ! grep -q "canary.enabled" "$DEPLOY_DIR/templates/hpa.yaml"; then
|
||||
sed -i.bak 's/{{- if .Values.web.autoscaling.enabled }}/{{- if and .Values.web.autoscaling.enabled (not .Values.canary.enabled) }}/' \
|
||||
"$DEPLOY_DIR/templates/hpa.yaml"
|
||||
rm -f "$DEPLOY_DIR/templates/hpa.yaml.bak"
|
||||
log_success "hpa.yaml atualizado"
|
||||
else
|
||||
log_warn "hpa.yaml já contém configuração canary"
|
||||
fi
|
||||
|
||||
# 6. Commit e push
|
||||
log_info "Commitando mudanças..."
|
||||
cd "$DEPLOY_DIR"
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
log_warn "Nenhuma mudança para commitar"
|
||||
else
|
||||
git commit -m "feat: adicionar Flagger canary deployment para web
|
||||
|
||||
- Novo template canary.yaml (Canary CRD)
|
||||
- Ingress aponta para service -primary quando canary ativo
|
||||
- HPA desabilitado quando canary ativo
|
||||
- Canary habilitado apenas em production"
|
||||
log_info "Fazendo push para o Gitea..."
|
||||
git push origin main
|
||||
log_success "Mudanças enviadas para o repositório de deploy"
|
||||
fi
|
||||
cd "$SCRIPT_DIR"
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configurar ArgoCD
|
||||
# ============================================================================
|
||||
|
||||
configure_argocd() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Configurando ArgoCD${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "Adicionando ignoreDifferences para VirtualService no ArgoCD..."
|
||||
|
||||
# Patch do Application para ignorar mudanças no VirtualService feitas pelo Flagger
|
||||
kubectl patch app streamify-production -n argocd --type=json -p='[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/spec/ignoreDifferences",
|
||||
"value": [
|
||||
{
|
||||
"group": "networking.istio.io",
|
||||
"kind": "VirtualService",
|
||||
"jsonPointers": ["/spec/http/0/route"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]' 2>/dev/null || log_warn "Não foi possível configurar ignoreDifferences automaticamente"
|
||||
|
||||
log_success "ArgoCD configurado"
|
||||
|
||||
log_info "Sincronizando ArgoCD..."
|
||||
kubectl patch app streamify-production -n argocd --type=merge -p='{"operation":{"sync":{"prune":false}}}' 2>/dev/null || true
|
||||
|
||||
# Aguardar sync
|
||||
log_info "Aguardando ArgoCD sincronizar (pode levar até 3 minutos)..."
|
||||
for i in $(seq 1 36); do
|
||||
local STATUS
|
||||
STATUS=$(kubectl get app streamify-production -n argocd -o jsonpath='{.status.sync.status}' 2>/dev/null || echo "Unknown")
|
||||
if [[ "$STATUS" == "Synced" ]]; then
|
||||
log_success "ArgoCD sincronizado"
|
||||
return 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
log_warn "ArgoCD ainda não sincronizou. Verifique manualmente: kubectl get app streamify-production -n argocd"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Aguardar Inicialização do Flagger
|
||||
# ============================================================================
|
||||
|
||||
wait_for_canary() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Aguardando Flagger inicializar o Canary${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
log_info "O Flagger vai criar os services -primary e -canary..."
|
||||
log_info "Isso pode levar até 2 minutos."
|
||||
|
||||
for i in $(seq 1 24); do
|
||||
local STATUS
|
||||
STATUS=$(kubectl get canary streamify-production-web -n streamify-production -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
|
||||
if [[ "$STATUS" == "Initialized" || "$STATUS" == "Succeeded" ]]; then
|
||||
log_success "Canary inicializado com sucesso!"
|
||||
echo ""
|
||||
kubectl get canary -n streamify-production
|
||||
return 0
|
||||
elif [[ -n "$STATUS" ]]; then
|
||||
log_info "Status: $STATUS (aguardando Initialized...)"
|
||||
else
|
||||
log_info "Canary ainda não foi criado pelo ArgoCD..."
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
log_warn "Canary ainda não inicializou. Verifique:"
|
||||
echo " kubectl get canary -n streamify-production"
|
||||
echo " kubectl logs deployment/flagger -n istio-system --tail=20"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Dashboard Grafana
|
||||
# ============================================================================
|
||||
|
||||
create_dashboard() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Criando Dashboard no Grafana${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
local GRAFANA_SVC="monitoring-grafana.monitoring"
|
||||
local GRAFANA_PASS
|
||||
GRAFANA_PASS=$(kubectl get secret monitoring-grafana -n monitoring -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d)
|
||||
|
||||
if [[ -z "$GRAFANA_PASS" ]]; then
|
||||
log_warn "Não foi possível obter a senha do Grafana. Dashboard não criado."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Criando dashboard 'Streamify - Observabilidade Istio'..."
|
||||
|
||||
local RESULT
|
||||
RESULT=$(kubectl run grafana-dash --rm -i --restart=Never --image=curlimages/curl:latest --command -- \
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "admin:${GRAFANA_PASS}" \
|
||||
-d '{
|
||||
"dashboard": {
|
||||
"title": "Streamify - Observabilidade Istio",
|
||||
"tags": ["istio", "streamify", "canary"],
|
||||
"timezone": "browser",
|
||||
"refresh": "30s",
|
||||
"time": {"from": "now-1h", "to": "now"},
|
||||
"panels": [
|
||||
{
|
||||
"title": "Request Rate por Serviço",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||
"datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"},
|
||||
"fieldConfig": {"defaults": {"unit": "reqps", "custom": {"lineWidth": 2, "fillOpacity": 10}}},
|
||||
"targets": [{"expr": "sum by(destination_service_name) (rate(istio_requests_total{destination_workload_namespace=\"streamify-production\"}[5m]))", "legendFormat": "{{destination_service_name}}"}]
|
||||
},
|
||||
{
|
||||
"title": "Error Rate (5xx)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
|
||||
"datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"},
|
||||
"fieldConfig": {"defaults": {"unit": "reqps", "custom": {"lineWidth": 2, "fillOpacity": 10}, "color": {"mode": "fixed", "fixedColor": "red"}}},
|
||||
"targets": [{"expr": "sum by(destination_service_name, response_code) (rate(istio_requests_total{destination_workload_namespace=\"streamify-production\", response_code=~\"5.*\"}[5m]))", "legendFormat": "{{destination_service_name}} [{{response_code}}]"}]
|
||||
},
|
||||
{
|
||||
"title": "Latência p99 por Serviço",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
|
||||
"datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"},
|
||||
"fieldConfig": {"defaults": {"unit": "ms", "custom": {"lineWidth": 2, "fillOpacity": 10}}},
|
||||
"targets": [{"expr": "histogram_quantile(0.99, sum by(le, destination_service_name) (rate(istio_request_duration_milliseconds_bucket{destination_workload_namespace=\"streamify-production\"}[5m])))", "legendFormat": "p99 {{destination_service_name}}"}, {"expr": "histogram_quantile(0.50, sum by(le, destination_service_name) (rate(istio_request_duration_milliseconds_bucket{destination_workload_namespace=\"streamify-production\"}[5m])))", "legendFormat": "p50 {{destination_service_name}}"}]
|
||||
},
|
||||
{
|
||||
"title": "Saturação de Memória (% do limit)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
|
||||
"datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"},
|
||||
"fieldConfig": {"defaults": {"unit": "percentunit", "custom": {"lineWidth": 2, "fillOpacity": 10}, "thresholds": {"mode": "absolute", "steps": [{"color": "green", "value": null}, {"color": "yellow", "value": 0.7}, {"color": "red", "value": 0.85}]}}},
|
||||
"targets": [{"expr": "max by(container, pod) (container_memory_working_set_bytes{namespace=\"streamify-production\", container!=\"\", container!=\"istio-proxy\", container!=\"istio-init\"} / container_spec_memory_limit_bytes{namespace=\"streamify-production\", container!=\"\", container!=\"istio-proxy\", container!=\"istio-init\"} > 0)", "legendFormat": "{{pod}} / {{container}}"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overwrite": true
|
||||
}' \
|
||||
"http://${GRAFANA_SVC}/api/dashboards/db" 2>/dev/null)
|
||||
|
||||
if echo "$RESULT" | grep -q '"status":"success"'; then
|
||||
log_success "Dashboard criado no Grafana"
|
||||
else
|
||||
log_warn "Não foi possível criar o dashboard automaticamente"
|
||||
log_info "Crie manualmente: Grafana > Dashboards > Import"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Resumo
|
||||
# ============================================================================
|
||||
|
||||
show_summary() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Instalação Concluída${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}Componentes instalados:${NC}"
|
||||
echo " - Flagger (operador canary, namespace: istio-system)"
|
||||
echo " - Flagger Loadtester (tráfego sintético, namespace: streamify-production)"
|
||||
echo " - VMPodScrapes (coleta métricas Istio)"
|
||||
echo " - Canary CRD (no Helm chart do Streamify)"
|
||||
echo ""
|
||||
echo -e "${GREEN}Configuração do Canary:${NC}"
|
||||
echo " - Interval: 1m (análise a cada 1 minuto)"
|
||||
echo " - Step Weight: 10% (incremento de tráfego por step)"
|
||||
echo " - Max Weight: 50% (promove ao atingir 50%)"
|
||||
echo " - Threshold: 5 (rollback após 5 falhas)"
|
||||
echo " - Métricas: success rate > 99%, latência < 500ms"
|
||||
echo ""
|
||||
echo -e "${GREEN}Como funciona a partir de agora:${NC}"
|
||||
echo ""
|
||||
echo " 1. CI builda nova imagem e atualiza a tag no streamify-deploy"
|
||||
echo " 2. ArgoCD sincroniza, Flagger detecta a mudança"
|
||||
echo " 3. Flagger cria canary: 10% → 20% → 30% → 40% → 50% → promote"
|
||||
echo " 4. Se métricas falharem: rollback automático"
|
||||
echo ""
|
||||
echo -e "${GREEN}Observar:${NC}"
|
||||
echo ""
|
||||
echo " # Acompanhar canary em tempo real"
|
||||
echo " kubectl get canary -n streamify-production -w"
|
||||
echo ""
|
||||
echo " # Logs do Flagger"
|
||||
echo " kubectl logs deployment/flagger -n istio-system -f"
|
||||
echo ""
|
||||
echo " # Kiali (tráfego visual)"
|
||||
echo " https://kiali.kube.quest/kiali/"
|
||||
echo ""
|
||||
echo " # Dashboard Istio (métricas do canary)"
|
||||
echo " Grafana > Dashboards > Streamify - Observabilidade Istio"
|
||||
echo ""
|
||||
echo " # Traces (distributed tracing)"
|
||||
echo " Grafana > Explore > Tempo"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Execução
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
echo ""
|
||||
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ Aula 16 - Canary Automatizado com Flagger ║${NC}"
|
||||
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}"
|
||||
|
||||
check_prerequisites
|
||||
collect_config
|
||||
install_flagger
|
||||
install_loadtester
|
||||
setup_istio_metrics
|
||||
enable_sidecar_injection
|
||||
update_helm_chart
|
||||
configure_argocd
|
||||
wait_for_canary
|
||||
create_dashboard
|
||||
show_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user