#!/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 # - Gitea 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} 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" } ask_hostname() { local label="$1" current="$2" default="$3" local value="${current:-$default}" if [[ -n "$value" ]]; then echo -e "${label}: ${GREEN}${value}${NC}" >&2 read -p "Enter para confirmar ou digite novo valor: " new_value [[ -n "$new_value" ]] && value="$new_value" else read -p "${label}: " value fi echo "$value" } 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 # Herdar defaults da aula-10 local INHERITED_DOMAIN="" if [[ -f "$AULA10_ENV" ]]; then source "$AULA10_ENV" INHERITED_DOMAIN="${DOMAIN}" log_info "Configuração herdada da aula-10" fi echo "" REGISTRY_HOST=$(ask_hostname "Registry" "$REGISTRY_HOST" "reg.${INHERITED_DOMAIN}") 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 REGISTRY_PROJECT="root" fi echo "" echo -e "Owner/repo no Gitea: ${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" 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 [[ -n "$LETSENCRYPT_EMAIL" ]]; then echo -e "Email Let's Encrypt: ${GREEN}${LETSENCRYPT_EMAIL}${NC}" read -p "Enter para confirmar ou digite novo valor: " new_email [[ -n "$new_email" ]] && LETSENCRYPT_EMAIL="$new_email" else 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 kubectl label namespace istio-system \ pod-security.kubernetes.io/enforce=privileged \ pod-security.kubernetes.io/warn=privileged \ --overwrite 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 --timeout=10m else helm install istiod istio/istiod -n istio-system --wait --timeout=10m 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..." local GW_ARGS="--set service.type=ClusterIP" if helm status istio-ingressgateway -n istio-system &> /dev/null; then helm upgrade istio-ingressgateway istio/gateway -n istio-system $GW_ARGS --wait --timeout=10m else helm install istio-ingressgateway istio/gateway -n istio-system $GW_ARGS --wait --timeout=10m 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 # Pausa para configuração de DNS local LB_IP LB_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "${CYAN} Configure o DNS${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" echo "No seu provedor DNS, crie registros A apontando para ${GREEN}${LB_IP}${NC}:" 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}" else echo -e "${YELLOW}⚠ Configure o DNS agora antes de continuar.${NC}" fi echo "" echo -n "Pressione ENTER quando o DNS estiver configurado..." read -r setup_ingress build_and_push_images create_registry_secret deploy_application setup_app_ingress create_test_pod show_summary } main "$@"