60e69c9ff6
- 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
740 lines
30 KiB
Bash
Executable File
740 lines
30 KiB
Bash
Executable File
#!/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 "$@"
|