Files
workshop/aula-14/setup.sh
ArgoCD Setup 3860809e5c feat(aula-14): adicionar Istio Traffic Splitting com canary deployment
- Instala Istio (base + istiod + ingressgateway)
- Configura Kiali e Jaeger para observabilidade
- Deploy de app-backend v1 e v2 com traffic splitting 90/10
- Integra com Victoria Metrics da aula-12
- Inclui teste-stress.sh para validar distribuição de tráfego
- Tráfego externo passa pelo Istio Gateway via NGINX Ingress
2026-01-24 07:40:51 -03:00

593 lines
24 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Aula 14 - Istio Traffic Splitting
# ============================================================================
# Instala Istio com Kiali e Jaeger 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
# - Victoria Metrics da aula-12
# - GitLab com Registry da aula-10
# - kubectl, helm e docker instalados
# ============================================================================
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"
AULA10_ENV="${SCRIPT_DIR}/../aula-10/.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 14 - Istio Traffic Splitting
# Gerado em: $(date)
REGISTRY_HOST=${REGISTRY_HOST}
REGISTRY_PROJECT=${REGISTRY_PROJECT}
DOMAIN=${DOMAIN}
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}
BASIC_AUTH_PASS=${BASIC_AUTH_PASS}
EOF
log_success "Configuração salva em .env"
}
collect_user_input() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Configuração do Istio Traffic Splitting${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo ""
if load_config; then
echo -e "Configuração existente encontrada:"
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"
read -p "Escolha [1/2]: " choice
if [[ "$choice" == "1" ]]; then
return 0
fi
fi
if [[ -f "$AULA10_ENV" ]]; then
source "$AULA10_ENV"
log_info "Configuração herdada da aula-10"
fi
if [[ -z "$DOMAIN" ]]; then
read -p "Domínio base (ex: kube.quest): " DOMAIN
else
echo -e "Domínio: ${GREEN}${DOMAIN}${NC}"
read -p "Enter para confirmar ou digite novo valor: " new_domain
[[ -n "$new_domain" ]] && DOMAIN="$new_domain"
fi
if [[ -z "$REGISTRY_HOST" ]]; then
REGISTRY_HOST="reg.${DOMAIN}"
fi
echo -e "Registry: ${GREEN}${REGISTRY_HOST}${NC}"
read -p "Enter para confirmar ou digite novo valor: " new_reg
[[ -n "$new_reg" ]] && REGISTRY_HOST="$new_reg"
# Grupo/projeto no GitLab para o registry (ex: root, demo, factory)
if [[ -z "$REGISTRY_PROJECT" ]]; then
REGISTRY_PROJECT="root"
fi
echo ""
echo -e "Grupo/projeto no GitLab: ${GREEN}${REGISTRY_PROJECT}${NC}"
echo -e " Imagens: ${CYAN}${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v1${NC}"
echo -e " ${CYAN}${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v2${NC}"
read -p "Grupo/projeto [${REGISTRY_PROJECT}]: " new_project
[[ -n "$new_project" ]] && REGISTRY_PROJECT="$new_project"
APP_HOST="${APP_HOST:-app.${DOMAIN}}"
KIALI_HOST="${KIALI_HOST:-kiali.${DOMAIN}}"
JAEGER_HOST="${JAEGER_HOST:-jaeger.${DOMAIN}}"
echo ""
echo -e "Hosts para serviços:"
echo -e " App: ${GREEN}${APP_HOST}${NC}"
echo -e " Kiali: ${GREEN}${KIALI_HOST}${NC}"
echo -e " Jaeger: ${GREEN}${JAEGER_HOST}${NC}"
read -p "Enter para confirmar ou 'n' para personalizar: " confirm
if [[ "$confirm" == "n" ]]; then
read -p "Host do App: " APP_HOST
read -p "Host do Kiali: " KIALI_HOST
read -p "Host do Jaeger: " JAEGER_HOST
fi
echo ""
echo -e "[1] Usar Let's Encrypt (HTTPS)"
echo -e "[2] Sem TLS (HTTP)"
read -p "Escolha [1/2]: " tls_choice
if [[ "$tls_choice" == "1" ]]; then
USE_LETSENCRYPT=true
if [[ -z "$LETSENCRYPT_EMAIL" ]]; then
read -p "Email para Let's Encrypt: " LETSENCRYPT_EMAIL
fi
else
USE_LETSENCRYPT=false
fi
# Basic Auth para Kiali e Jaeger
echo ""
echo -e "${CYAN}Autenticação para Kiali e Jaeger:${NC}"
if [[ -z "$BASIC_AUTH_USER" ]]; then
BASIC_AUTH_USER="admin"
fi
echo -e "Usuário: ${GREEN}${BASIC_AUTH_USER}${NC}"
read -p "Enter para confirmar ou digite novo valor: " new_user
[[ -n "$new_user" ]] && BASIC_AUTH_USER="$new_user"
if [[ -z "$BASIC_AUTH_PASS" ]]; then
BASIC_AUTH_PASS=$(openssl rand -base64 12 | tr -d '/+=' | head -c 16)
echo -e "Senha gerada: ${GREEN}${BASIC_AUTH_PASS}${NC}"
else
echo -e "Senha: ${GREEN}${BASIC_AUTH_PASS}${NC}"
fi
read -p "Enter para confirmar ou digite nova senha: " new_pass
[[ -n "$new_pass" ]] && BASIC_AUTH_PASS="$new_pass"
save_config
}
# ============================================================================
# Verificação de Pré-requisitos
# ============================================================================
check_prerequisites() {
echo ""
log_info "Verificando pré-requisitos..."
if ! command -v kubectl &> /dev/null; then
log_error "kubectl não encontrado"
exit 1
fi
log_success "kubectl encontrado"
if ! command -v helm &> /dev/null; then
log_error "helm não encontrado"
exit 1
fi
log_success "helm encontrado"
if ! command -v docker &> /dev/null; then
log_error "docker não encontrado"
exit 1
fi
log_success "docker encontrado"
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 Victoria Metrics (aula-12)
if kubectl get svc -n monitoring vmsingle-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"
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."
VICTORIA_METRICS_URL=""
fi
}
# ============================================================================
# Instalação do Istio
# ============================================================================
install_istio() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Instalando Istio${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
helm repo add istio https://istio-release.storage.googleapis.com/charts 2>/dev/null || true
helm repo update istio
kubectl create namespace istio-system 2>/dev/null || true
log_info "Instalando istio-base..."
if helm status istio-base -n istio-system &> /dev/null; then
helm upgrade istio-base istio/base -n istio-system --wait
else
helm install istio-base istio/base -n istio-system --wait
fi
log_success "istio-base instalado"
log_info "Instalando istiod..."
if helm status istiod -n istio-system &> /dev/null; then
helm upgrade istiod istio/istiod -n istio-system --wait
else
helm install istiod istio/istiod -n istio-system --wait
fi
log_success "istiod instalado"
log_info "Aguardando istiod..."
kubectl wait --for=condition=available deployment/istiod -n istio-system --timeout=300s
log_success "istiod pronto"
log_info "Instalando istio-ingressgateway..."
if helm status istio-ingressgateway -n istio-system &> /dev/null; then
helm upgrade istio-ingressgateway istio/gateway -n istio-system --wait
else
helm install istio-ingressgateway istio/gateway -n istio-system --wait
fi
log_success "istio-ingressgateway instalado"
log_info "Aguardando ingressgateway..."
kubectl wait --for=condition=available deployment/istio-ingressgateway -n istio-system --timeout=300s
log_success "ingressgateway pronto"
}
# ============================================================================
# Instalação de Observabilidade
# ============================================================================
install_observability() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Instalando Kiali e Jaeger${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
# Configurar Kiali para usar Victoria Metrics
if [[ -n "$VICTORIA_METRICS_URL" ]]; then
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\"
}
}" 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..."
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"
}
# ============================================================================
# Configuração de Basic Auth
# ============================================================================
setup_basic_auth() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Configurando Basic Auth para Kiali e Jaeger${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
log_info "Criando secret basic-auth..."
# Verificar se htpasswd está disponível
if ! command -v htpasswd &> /dev/null; then
log_info "htpasswd não encontrado, usando openssl..."
# Gerar hash com openssl (compatível com Apache htpasswd)
local HASH=$(openssl passwd -apr1 "${BASIC_AUTH_PASS}")
echo "${BASIC_AUTH_USER}:${HASH}" > /tmp/auth
else
htpasswd -cb /tmp/auth "${BASIC_AUTH_USER}" "${BASIC_AUTH_PASS}"
fi
kubectl delete secret basic-auth -n istio-system 2>/dev/null || true
kubectl create secret generic basic-auth -n istio-system --from-file=auth=/tmp/auth
rm -f /tmp/auth
log_success "Secret basic-auth criado"
}
# ============================================================================
# Configuração de Ingress
# ============================================================================
setup_ingress() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Configurando Ingress para Kiali e Jaeger${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
local TLS_ANNOTATION=""
local TLS_CONFIG=""
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
TLS_ANNOTATION="cert-manager.io/cluster-issuer: letsencrypt-prod"
fi
log_info "Criando Ingress para Kiali..."
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
TLS_CONFIG="tls:
- hosts:
- ${KIALI_HOST}
secretName: kiali-tls"
fi
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() {
echo ""
log_info "Criando Ingress para App..."
local TLS_ANNOTATION=""
local TLS_CONFIG=""
if [[ "$USE_LETSENCRYPT" == "true" ]]; then
TLS_ANNOTATION="cert-manager.io/cluster-issuer: letsencrypt-prod"
TLS_CONFIG="tls:
- hosts:
- ${APP_HOST}
secretName: app-tls"
fi
export APP_HOST TLS_ANNOTATION TLS_CONFIG
envsubst '${APP_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-app.yaml" | kubectl apply -f -
log_success "Ingress do App criado"
}
# ============================================================================
# Build e Push das Imagens
# ============================================================================
build_and_push_images() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Build e Push das Imagens (ARM64)${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
local IMAGE_BASE="${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend"
log_info "Build app-backend:v1 (linux/arm64)..."
docker buildx build --platform linux/arm64 -t "${IMAGE_BASE}:v1" --push "${SCRIPT_DIR}/app-backend/v1"
log_success "app-backend:v1 construída e enviada"
log_info "Build app-backend:v2 (linux/arm64)..."
docker buildx build --platform linux/arm64 -t "${IMAGE_BASE}:v2" --push "${SCRIPT_DIR}/app-backend/v2"
log_success "app-backend:v2 construída e enviada"
}
# ============================================================================
# Deploy da Aplicação
# ============================================================================
create_registry_secret() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Criando Secret do Registry${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
log_info "Criando namespace istio..."
kubectl apply -f "${SCRIPT_DIR}/k8s/namespace.yaml"
log_success "Namespace criado"
log_info "Obtendo credenciais do registry..."
local CREDS
if command -v docker-credential-osxkeychain &> /dev/null; then
CREDS=$(docker-credential-osxkeychain get <<< "${REGISTRY_HOST}" 2>/dev/null || true)
elif command -v docker-credential-secretservice &> /dev/null; then
CREDS=$(docker-credential-secretservice get <<< "${REGISTRY_HOST}" 2>/dev/null || true)
fi
if [[ -n "$CREDS" ]]; then
local USERNAME=$(echo "$CREDS" | jq -r '.Username')
local PASSWORD=$(echo "$CREDS" | jq -r '.Secret')
kubectl delete secret regcred -n istio 2>/dev/null || true
kubectl create secret docker-registry regcred -n istio \
--docker-server="${REGISTRY_HOST}" \
--docker-username="${USERNAME}" \
--docker-password="${PASSWORD}"
log_success "Secret regcred criado"
else
log_warn "Credenciais não encontradas no credential helper"
log_info "Tentando criar secret a partir do Docker config..."
kubectl delete secret regcred -n istio 2>/dev/null || true
kubectl create secret generic regcred -n istio \
--from-file=.dockerconfigjson="${HOME}/.docker/config.json" \
--type=kubernetes.io/dockerconfigjson 2>/dev/null || \
log_warn "Falha ao criar secret. Faça login no registry: docker login ${REGISTRY_HOST}"
fi
}
deploy_application() {
echo ""
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} Deploy da Aplicação${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
log_info "Deploy app-backend v1..."
export REGISTRY_HOST REGISTRY_PROJECT
envsubst < "${SCRIPT_DIR}/k8s/deployment-v1.yaml" | kubectl apply -f -
log_success "v1 implantada"
log_info "Deploy app-backend v2..."
envsubst < "${SCRIPT_DIR}/k8s/deployment-v2.yaml" | kubectl apply -f -
log_success "v2 implantada"
log_info "Criando Service..."
kubectl apply -f "${SCRIPT_DIR}/k8s/service.yaml"
log_success "Service criado"
log_info "Aplicando DestinationRule..."
kubectl apply -f "${SCRIPT_DIR}/k8s/destination-rule.yaml"
log_success "DestinationRule aplicada"
log_info "Aplicando Gateway..."
export APP_HOST
envsubst < "${SCRIPT_DIR}/k8s/gateway.yaml" | kubectl apply -f -
log_success "Gateway aplicado"
log_info "Aplicando VirtualService (90% v1, 10% v2)..."
envsubst < "${SCRIPT_DIR}/k8s/virtual-service.yaml" | kubectl apply -f -
log_success "VirtualService aplicado"
log_info "Aguardando pods..."
kubectl wait --for=condition=available deployment/app-backend-v1 -n istio --timeout=120s
kubectl wait --for=condition=available deployment/app-backend-v2 -n istio --timeout=120s
log_success "Pods prontos"
}
create_test_pod() {
log_info "Criando pod de teste..."
kubectl run curl-test -n istio --image=curlimages/curl:latest \
--restart=Never --command -- sleep infinity 2>/dev/null || true
kubectl wait --for=condition=ready pod/curl-test -n istio --timeout=60s 2>/dev/null || true
log_success "Pod de teste disponível"
}
# ============================================================================
# Resumo
# ============================================================================
show_summary() {
local protocol="http"
[[ "$USE_LETSENCRYPT" == "true" ]] && protocol="https"
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 " - 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
echo -e "${GREEN}Métricas:${NC} Victoria Metrics (aula-12)"
else
echo -e "${YELLOW}Métricas:${NC} Victoria Metrics não disponível. Instale aula-12."
fi
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 ""
echo -e "${GREEN}Credenciais (Basic Auth para Kiali/Jaeger):${NC}"
echo -e " Usuário: ${CYAN}${BASIC_AUTH_USER}${NC}"
echo -e " Senha: ${CYAN}${BASIC_AUTH_PASS}${NC}"
echo ""
echo -e "${GREEN}Fluxo de Uso:${NC}"
echo ""
echo " # 1. Setup"
echo " ./setup.sh # Deploy com 90% v1, 10% v2"
echo ""
echo " # 2. Testar distribuição"
echo " ./teste-stress.sh # Veja ~90 v1, ~10 v2"
echo ""
echo " # 3. Mudar para 50/50 e re-testar"
echo " kubectl patch virtualservice app-backend -n istio --type='json' \\"
echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":50},"
echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":50}]'"
echo " ./teste-stress.sh"
echo ""
echo " # 4. Rollout completo para v2"
echo " kubectl patch virtualservice app-backend -n istio --type='json' \\"
echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":0},"
echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":100}]'"
echo " ./teste-stress.sh"
echo ""
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 ""
}
# ============================================================================
# Execução
# ============================================================================
main() {
echo ""
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Aula 14 - Istio Traffic Splitting ║${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}"
check_prerequisites
collect_user_input
install_istio
install_observability
setup_basic_auth
setup_ingress
build_and_push_images
create_registry_secret
deploy_application
setup_app_ingress
create_test_pod
show_summary
}
main "$@"