diff --git a/Labs/10-Istio/README.md b/Labs/10-Istio/README.md index cb74bdf..47f0ce6 100644 --- a/Labs/10-Istio/README.md +++ b/Labs/10-Istio/README.md @@ -1,5 +1,3 @@ ---- - # Istio Service Mesh & Kiali - `Istio` is an open-source service mesh that provides a uniform way to manage microservices communication. @@ -11,7 +9,7 @@ ## What will we learn? - Install and configure Istio service mesh using Helm -- Deploy Kiali, Prometheus, Grafana, and Jaeger as observability addons +- Deploy Kiali, Prometheus, Grafana, Jaeger, and Loki as observability addons - Deploy a microservices demo application with sidecar injection - Generate live traffic and observe it in Kiali's topology graph - Configure traffic management: routing, canary deployments, fault injection @@ -39,17 +37,18 @@ | **Prometheus** | Metrics collection and storage for Istio telemetry | `9090` | | **Grafana** | Dashboards for mesh, service, and workload metrics | `3000` | | **Jaeger** | Distributed tracing backend and UI | `16686` | +| **Loki** | Log aggregation (used with Grafana for logs) | `3100` | ### Istio Key CRDs | CRD | Purpose | | --------------------- | --------------------------------------------------------------------------------- | | `VirtualService` | Define routing rules: traffic shifting, fault injection, timeouts | -| `DestinationRule` | Define policies after routing: load balancing, circuit breaker, mTLS | +| `DestinationRule` | Define policies after routing: load balancing, circuit breaker, mTLS | | `Gateway` | Configure load balancer at mesh edge for HTTP/TCP traffic | -| `PeerAuthentication` | Configure mTLS mode: `STRICT`, `PERMISSIVE`, `DISABLE` | +| `PeerAuthentication` | Configure mTLS mode: `STRICT`, `PERMISSIVE`, `DISABLE` | | `AuthorizationPolicy` | Access control policies for workloads | -| `ServiceEntry` | Add external services (outside the mesh) to Istio's service registry | +| `ServiceEntry` | Add external services (outside the mesh) to Istio's service registry | --- @@ -67,6 +66,7 @@ graph TB grafana["Grafana"] jaeger["Jaeger"] kiali["Kiali"] + loki["Loki"] end subgraph bookinfo["bookinfo namespace (istio-injection=enabled)"] @@ -104,6 +104,7 @@ graph TB jaeger --> kiali istiod --> kiali prometheus --> grafana + loki -. logs .-> grafana end ``` @@ -111,7 +112,7 @@ graph TB ## Directory Structure -``` +```bash 10-Istio/ ├── README.md # This file ├── demo.sh # Main deployment script (deploy/cleanup) @@ -120,7 +121,7 @@ graph TB ├── scripts/ │ ├── common.sh # Shared functions & colors │ ├── 01-install-istio.sh # Install Istio via Helm -│ ├── 02-install-addons.sh # Install Kiali, Prometheus, Grafana, Jaeger +│ ├── 02-install-addons.sh # Install observability addons + gateway routes │ ├── 03-deploy-bookinfo.sh # Deploy Bookinfo sample application │ ├── 04-traffic-generator.sh # Deploy live traffic generator │ └── 05-verify.sh # Verify all components @@ -130,12 +131,14 @@ graph TB │ ├── bookinfo.yaml # Bookinfo application manifests │ ├── bookinfo-gateway.yaml # Istio Gateway + VirtualService for ingress │ ├── destination-rules.yaml # DestinationRules for all service versions +│ ├── observability-routes.yaml # VirtualServices (addons + Bookinfo via gateway) │ ├── traffic-generator.yaml # CronJob for continuous traffic generation │ └── addons/ # Observability addon manifests │ ├── prometheus.yaml │ ├── grafana.yaml │ ├── jaeger.yaml -│ └── kiali.yaml +│ ├── kiali.yaml +│ └── loki.yaml │ └── istio-features/ ├── 01-traffic-shifting.yaml # Canary: route % of traffic to v2/v3 @@ -155,9 +158,11 @@ graph TB - Kubernetes cluster (v1.24+) with at least 8 GB RAM available - `kubectl` configured to access your cluster - `Helm 3.x` installed -- Nginx Ingress Controller (required for Ingress-based access to dashboards and Bookinfo) - (Optional) `istioctl` for debugging +Access to dashboards and Bookinfo is via the Istio Ingress Gateway +(port-forward or hosts file); no Nginx Ingress Controller is required. + ```bash # Install kubectl (macOS) brew install kubectl @@ -175,7 +180,7 @@ helm version --- -# Lab +## Lab ## Part 01 - Deploy Istio Service Mesh @@ -193,7 +198,8 @@ The script will: - Check prerequisites: `kubectl`, `helm`, cluster connectivity - Install Istio CRDs and control plane via Helm -- Install Kiali, Prometheus, Grafana, and Jaeger +- Install observability addons (Kiali, Prometheus, Grafana, Jaeger, Loki) and + Istio Gateway routes for them - Create the `bookinfo` namespace with sidecar injection enabled - Deploy the Bookinfo sample application (4 microservices, multiple versions) - Configure the Istio Ingress Gateway and DestinationRules @@ -543,11 +549,14 @@ kubectl logs -n istio-system -l app=kiali --tail=50 This will remove: - `traffic-gen` namespace and all traffic generator resources -- `bookinfo` namespace and all application resources -- Kiali, Prometheus, Grafana, and Jaeger Helm releases -- Istio control plane Helm release -- All Istio CRDs -- All remaining namespaces created by this lab +- `bookinfo` namespace and all application resources (including Istio gateway + routes) +- Observability addons (Kiali, Prometheus, Grafana, Jaeger, Loki) and their + Istio VirtualService routes; Loki PVCs (e.g. `storage-loki-0`) are deleted + so persistent volumes are not left behind +- Istio Ingress Gateway, Istiod, and base Helm releases +- `istio-system` namespace +- All Istio CRDs and cluster-wide addon RBAC (e.g. Prometheus, Kiali, Loki) ### Partial Cleanup @@ -561,6 +570,8 @@ kubectl delete namespace traffic-gen # Remove only observability addons (keep Istio + app running) kubectl delete -f manifests/addons/ -n istio-system +# If you removed addons manually, delete Loki PVCs to free storage (e.g. storage-loki-0): +kubectl delete pvc -n istio-system storage-loki-0 --ignore-not-found ``` --- diff --git a/Labs/10-Istio/demo.sh b/Labs/10-Istio/demo.sh index d26a2cd..5937f4e 100755 --- a/Labs/10-Istio/demo.sh +++ b/Labs/10-Istio/demo.sh @@ -8,37 +8,39 @@ set -euo pipefail # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/scripts/common.sh" +LAB_DIR="${SCRIPT_DIR}" +source "${LAB_DIR}/scripts/common.sh" -# Deploy all components +# Run full lab deployment: Istio, addons, Bookinfo, traffic generator, and verify. +# Prints access info at the end. deploy() { print_header "Istio + Kiali Lab Deployment" check_prerequisites echo "" - # Step 1: Install Istio - source "${SCRIPT_DIR}/scripts/01-install-istio.sh" + # Step 1: Install Istio (sourced script may overwrite SCRIPT_DIR, so use LAB_DIR) + source "${LAB_DIR}/scripts/01-install-istio.sh" install_istio echo "" # Step 2: Install observability addons - source "${SCRIPT_DIR}/scripts/02-install-addons.sh" + source "${LAB_DIR}/scripts/02-install-addons.sh" install_addons echo "" # Step 3: Deploy Bookinfo application - source "${SCRIPT_DIR}/scripts/03-deploy-bookinfo.sh" + source "${LAB_DIR}/scripts/03-deploy-bookinfo.sh" deploy_bookinfo echo "" # Step 4: Deploy traffic generator - source "${SCRIPT_DIR}/scripts/04-traffic-generator.sh" + source "${LAB_DIR}/scripts/04-traffic-generator.sh" deploy_traffic_generator echo "" # Step 5: Verify - source "${SCRIPT_DIR}/scripts/05-verify.sh" + source "${LAB_DIR}/scripts/05-verify.sh" verify_deployment echo "" @@ -46,7 +48,7 @@ deploy() { display_access_info } -# Display access information +# Print gateway URLs, /etc/hosts hints, port-forward commands, and demo usage. display_access_info() { echo "" echo "==========================================" @@ -54,21 +56,25 @@ display_access_info() { echo "==========================================" echo "" - # Get ingress IP - INGRESS_IP=$(kubectl get ingress -n istio-system kiali -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) + # Get Istio Ingress Gateway external IP (or hostname for LoadBalancer) + GATEWAY_IP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) || true + if [ -z "$GATEWAY_IP" ]; then + GATEWAY_IP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null) || true + fi - print_info "Access via Ingress (recommended):" + print_info "Access via Istio Ingress Gateway:" echo "" echo -e " Kiali: ${GREEN}http://kiali.local${NC}" echo -e " Grafana: ${GREEN}http://grafana.local${NC}" echo -e " Jaeger: ${GREEN}http://jaeger.local${NC}" echo -e " Prometheus: ${GREEN}http://prometheus.local${NC}" + echo -e " Loki: ${GREEN}http://loki.local${NC}" echo -e " Bookinfo: ${GREEN}http://bookinfo.local/productpage${NC}" echo "" # Check /etc/hosts HOSTS_NEEDED="" - for host in kiali.local grafana.local jaeger.local prometheus.local bookinfo.local; do + for host in kiali.local grafana.local jaeger.local prometheus.local loki.local bookinfo.local; do if ! grep -q "$host" /etc/hosts 2>/dev/null; then HOSTS_NEEDED="$HOSTS_NEEDED $host" fi @@ -76,7 +82,12 @@ display_access_info() { if [ -n "$HOSTS_NEEDED" ]; then print_warning "Add these hosts to /etc/hosts:" - echo " echo \"${INGRESS_IP:-192.168.139.2}${HOSTS_NEEDED}\" | sudo tee -a /etc/hosts" + if [ -n "$GATEWAY_IP" ]; then + echo " echo \"${GATEWAY_IP}${HOSTS_NEEDED}\" | sudo tee -a /etc/hosts" + else + echo " # Get gateway IP: kubectl get svc istio-ingressgateway -n istio-system" + echo " echo \"${HOSTS_NEEDED}\" | sudo tee -a /etc/hosts" + fi echo "" else print_success "/etc/hosts already configured" @@ -87,6 +98,7 @@ display_access_info() { echo "" echo " kubectl port-forward svc/kiali -n istio-system 20001:20001 &" echo " kubectl port-forward svc/grafana -n istio-system 3000:3000 &" + echo " kubectl port-forward svc/loki -n istio-system 3100:3100 &" echo " kubectl port-forward svc/tracing -n istio-system 16686:80 &" echo " kubectl port-forward svc/prometheus -n istio-system 9090:9090 &" echo " kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80 &" @@ -116,59 +128,66 @@ display_access_info() { echo "" } -# Cleanup all components +# Remove all lab resources: traffic-gen, Bookinfo, addons, Istio Helm releases, +# istio-system namespace, CRDs, and related cluster roles/bindings. cleanup() { print_header "Cleaning Up Istio + Kiali Lab" # Remove traffic generator print_step "Removing traffic generator..." - kubectl delete namespace traffic-gen 2>/dev/null + kubectl delete namespace traffic-gen 2>/dev/null || true print_success "Traffic generator removed" # Remove Bookinfo print_step "Removing Bookinfo application..." - kubectl delete -f "${SCRIPT_DIR}/manifests/bookinfo-gateway.yaml" -n bookinfo 2>/dev/null - kubectl delete -f "${SCRIPT_DIR}/manifests/destination-rules.yaml" -n bookinfo 2>/dev/null - kubectl delete -f "${SCRIPT_DIR}/manifests/bookinfo.yaml" -n bookinfo 2>/dev/null - kubectl delete namespace bookinfo 2>/dev/null + kubectl delete -f "${LAB_DIR}/manifests/bookinfo-gateway.yaml" -n bookinfo 2>/dev/null || true + kubectl delete -f "${LAB_DIR}/manifests/destination-rules.yaml" -n bookinfo 2>/dev/null || true + kubectl delete -f "${LAB_DIR}/manifests/bookinfo.yaml" -n bookinfo 2>/dev/null || true + kubectl delete namespace bookinfo 2>/dev/null || true print_success "Bookinfo removed" # Remove Istio feature demos (if any applied) print_step "Cleaning up Istio feature demos..." - kubectl delete peerauthentication --all -n bookinfo 2>/dev/null - kubectl delete peerauthentication --all -n istio-system 2>/dev/null - - # Remove addons and ingress - print_step "Removing observability addons and ingress..." - kubectl delete -f "${SCRIPT_DIR}/manifests/ingress.yaml" 2>/dev/null - kubectl delete -f "${SCRIPT_DIR}/manifests/addons/" -n istio-system 2>/dev/null - print_success "Addons and ingress removed" + kubectl delete peerauthentication --all -n bookinfo 2>/dev/null || true + kubectl delete peerauthentication --all -n istio-system 2>/dev/null || true + + # Remove addons and Istio gateway routes + print_step "Removing observability addons and gateway routes..." + kubectl delete -f "${LAB_DIR}/manifests/observability-routes.yaml" 2>/dev/null || true + kubectl delete -f "${LAB_DIR}/manifests/addons/" 2>/dev/null || true + # Delete Loki PVCs from StatefulSet volumeClaimTemplates (e.g. storage-loki-0) + for pvc in $(kubectl get pvc -n istio-system -o name 2>/dev/null | sed 's|persistentvolumeclaim/||' | grep -E '^storage-loki-' || true); do + kubectl delete pvc -n istio-system "$pvc" --ignore-not-found 2>/dev/null || true + done + print_success "Addons and gateway routes removed" # Remove Istio print_step "Removing Istio..." - helm uninstall istio-ingressgateway -n istio-system 2>/dev/null - helm uninstall istiod -n istio-system 2>/dev/null - helm uninstall istio-base -n istio-system 2>/dev/null + helm uninstall istio-ingressgateway -n istio-system 2>/dev/null || true + helm uninstall istiod -n istio-system 2>/dev/null || true + helm uninstall istio-base -n istio-system 2>/dev/null || true print_success "Istio Helm releases removed" # Clean up namespace print_step "Removing istio-system namespace..." - kubectl delete namespace istio-system 2>/dev/null + kubectl delete namespace istio-system 2>/dev/null || true # Clean up Istio CRDs print_step "Removing Istio CRDs..." - kubectl get crd -o name | grep 'istio.io' | xargs -r kubectl delete 2>/dev/null + for crd in $(kubectl get crd -o name 2>/dev/null | grep 'istio.io' || true); do + kubectl delete "$crd" --ignore-not-found 2>/dev/null || true + done - # Clean up cluster-wide resources - kubectl delete clusterrole istio-prometheus kiali 2>/dev/null - kubectl delete clusterrolebinding istio-prometheus kiali 2>/dev/null + # Clean up cluster-wide resources (addon ClusterRoles/ClusterRoleBindings) + kubectl delete clusterrole istio-prometheus kiali loki-clusterrole 2>/dev/null || true + kubectl delete clusterrolebinding istio-prometheus kiali loki-clusterrolebinding 2>/dev/null || true echo "" print_success "Cleanup complete! All Istio + Kiali resources removed." } # Parse command line arguments -case "${1}" in +case "${1:-}" in deploy) deploy ;; diff --git a/Labs/10-Istio/demo/01-basic-setup/demo.sh b/Labs/10-Istio/demo/01-basic-setup/demo.sh index 0b123ab..9b23ed8 100755 --- a/Labs/10-Istio/demo/01-basic-setup/demo.sh +++ b/Labs/10-Istio/demo/01-basic-setup/demo.sh @@ -16,6 +16,7 @@ TRAFFIC_LOG_FILE="$RUNTIME_DIR/traffic.log" ISTIO_DOWNLOAD_DIR="" +# Remove temporary Istio download directory on exit (used by trap). cleanup_temp_dirs() { if [ -n "${ISTIO_DOWNLOAD_DIR}" ] && [ -d "${ISTIO_DOWNLOAD_DIR}" ]; then rm -rf "${ISTIO_DOWNLOAD_DIR}" >/dev/null 2>&1 || true @@ -24,11 +25,14 @@ cleanup_temp_dirs() { trap cleanup_temp_dirs EXIT +# Return whether a Kubernetes CRD exists (exit 0 if yes). +# Args: $1 - CRD name (e.g. virtualservices.networking.istio.io). crd_exists() { local crd_name="$1" kubectl get crd "${crd_name}" >/dev/null 2>&1 } +# Remove prior demo resources: port-forwards, Bookinfo in default, Istio config, demo namespace workloads. cleanup_previous_demo() { echo "Step 00: Cleaning previous demo resources (if any)..." @@ -57,19 +61,39 @@ cleanup_previous_demo() { kubectl delete -n demo deployment/nginx deployment/httpd svc/nginx svc/httpd --ignore-not-found >/dev/null 2>&1 || true fi + # Remove istio-injection label from namespaces we labeled so unrelated namespaces are unaffected. + kubectl label namespace default istio-injection- 2>/dev/null || true + kubectl label namespace demo istio-injection- 2>/dev/null || true + echo "Cleanup complete." echo } +# Return whether a TCP port is in LISTEN state (tries lsof, ss, netstat). +# Portable: macOS and Linux (e.g. Ubuntu). Uses lsof (both), ss (Linux), netstat (both). +# Args: $1 - Port number. Returns: 0 if listening, 1 otherwise. is_port_listening() { local port="$1" if command -v lsof >/dev/null 2>&1; then lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1 - else + return + fi + if command -v ss >/dev/null 2>&1; then + if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then + return 0 + fi return 1 fi + if command -v netstat >/dev/null 2>&1; then + if netstat -an 2>/dev/null | grep -E '^(tcp|tcp4|tcp6)' | grep LISTEN | grep -qE "[:.]${port}[[:space:]]"; then + return 0 + fi + return 1 + fi + return 1 } +# Stop port-forwards and traffic generator started by this script (using saved PIDs). stop_previous_port_forwards() { mkdir -p "$RUNTIME_DIR" >/dev/null 2>&1 || true @@ -86,17 +110,23 @@ stop_previous_port_forwards() { kill "${pid}" 2>/dev/null || true fi done <"$PF_PID_FILE" - rm -f "$PF_PID_FILE" + rm -f "$PF_PID_FILE" >/dev/null 2>&1 || true fi rm -f "$RUNTIME_DIR"/port-forward-*.log >/dev/null 2>&1 || true } +# Start kubectl port-forward in background; skip if local port already in use. Append PID to PF_PID_FILE. +# Args: $1 - namespace; $2 - service name; $3 - local port; $4 - remote port. start_port_forward() { local namespace="$1" local svc="$2" local local_port="$3" local remote_port="$4" + if ! mkdir -p "$RUNTIME_DIR" 2>/dev/null; then + echo "Error: Cannot create runtime directory $RUNTIME_DIR" >&2 + return 1 + fi local log_file="$RUNTIME_DIR/port-forward-${namespace}-${svc}-${local_port}.log" if is_port_listening "${local_port}"; then @@ -112,6 +142,8 @@ start_port_forward() { echo $! >>"$PF_PID_FILE" } +# Open URL in default browser (macOS open or Linux xdg-open). No-op if neither available. +# Args: $1 - URL to open. open_url() { local url="$1" if command -v open >/dev/null 2>&1; then @@ -121,6 +153,8 @@ open_url() { fi } +# Start a background loop that curls productpage and reviews; store PID in TRAFFIC_PID_FILE. +# No-op if curl missing or generator already running. start_traffic_generator() { if ! command -v curl >/dev/null 2>&1; then return 0 @@ -128,7 +162,7 @@ start_traffic_generator() { if [ -f "$TRAFFIC_PID_FILE" ] && kill -0 "$(cat "$TRAFFIC_PID_FILE" 2>/dev/null)" 2>/dev/null; then return 0 fi - nohup bash -lc 'while true; do curl -fsS http://127.0.0.1:8080/productpage >/dev/null; curl -fsS http://127.0.0.1:8080/api/v1/products/0/reviews >/dev/null; sleep 1; done' >"$TRAFFIC_LOG_FILE" 2>&1 & + nohup bash -c 'while true; do curl -fsS http://127.0.0.1:8080/productpage >/dev/null; curl -fsS http://127.0.0.1:8080/api/v1/products/0/reviews >/dev/null; sleep 1; done' >"$TRAFFIC_LOG_FILE" 2>&1 & echo $! >"$TRAFFIC_PID_FILE" } @@ -209,14 +243,10 @@ echo "Step 04: Verifying Kiali installation..." kubectl get pods -n istio-system echo -# Step 05: Enable Istio Injection -echo "Step 05: Enabling Istio injection in all non-system namespaces..." - -for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do - case "$ns" in kube-system | kube-public | kube-node-lease | istio-system) continue ;; esac - kubectl label namespace "$ns" istio-injection=enabled --overwrite -done - +# Step 05: Enable Istio Injection (only namespaces used by this demo) +echo "Step 05: Enabling Istio injection in default and demo namespaces..." +kubectl label namespace default istio-injection=enabled --overwrite +kubectl label namespace demo istio-injection=enabled --overwrite 2>/dev/null || true echo "Injection enabled." echo @@ -249,32 +279,33 @@ kubectl apply -f "$ISTIO_HOME/samples/bookinfo/networking/destination-rule-all.y echo "Destination rules applied." echo -# Step 11: Create VirtualService Demo -echo "Step 11: Creating demo VirtualService for reviews service (route to v2)..." +# Step 10: Create VirtualService Demo +echo "Step 10: Creating demo VirtualService for reviews service (route to v2)..." kubectl apply -f "$WORK_DIR/reviews-vs.yaml" echo "VirtualService created." echo -# Step 14: Create Demo Namespace -echo "Step 14: Creating demo namespace..." +# Step 11: Create Demo Namespace +echo "Step 11: Creating demo namespace..." kubectl apply -f "$WORK_DIR/demo-namespace.yaml" +kubectl label namespace demo istio-injection=enabled --overwrite echo "Demo namespace created." echo -# Step 15: Deploy Nginx -echo "Step 15: Deploying Nginx with curl loop..." +# Step 12: Deploy Nginx +echo "Step 12: Deploying Nginx with curl loop..." kubectl apply -f "$WORK_DIR/nginx-demo.yaml" echo "Nginx deployed." echo -# Step 16: Deploy HTTPD -echo "Step 16: Deploying HTTPD with curl loop..." +# Step 13: Deploy HTTPD +echo "Step 13: Deploying HTTPD with curl loop..." kubectl apply -f "$WORK_DIR/httpd-demo.yaml" echo "HTTPD deployed." echo -# Step 17: Verify Demo -echo "Step 17: Verifying demo deployment..." +# Step 14: Verify Demo +echo "Step 14: Verifying demo deployment..." kubectl get pods -n demo kubectl wait --for=condition=available --timeout=180s deployment/nginx -n demo kubectl wait --for=condition=available --timeout=180s deployment/httpd -n demo @@ -307,7 +338,10 @@ echo "5. To verify the VirtualService routing:" echo " Refresh the productpage multiple times and check the reviews section" echo " (reviews v1: no stars, v2: black stars, v3: red stars)" echo -echo "Cleanup: Run 'istioctl uninstall --purge -y' and 'kubectl delete ns istio-system demo' when done." +echo "Cleanup: Remove istio-injection labels (so pods stop getting sidecars after uninstall):" +echo " kubectl label namespace default istio-injection-" +echo " kubectl label namespace demo istio-injection-" +echo " Then: istioctl uninstall --purge -y and kubectl delete ns istio-system demo" echo echo "========================================" @@ -327,8 +361,8 @@ start_traffic_generator sleep 2 if command -v curl >/dev/null 2>&1; then - curl -fsSI http://127.0.0.1:20001/kiali/ >/dev/null 2>&1 && echo "Kiali: reachable on http://localhost:20001/kiali/" || echo "Kiali: not reachable yet (check .port-forward-istio-system-kiali-20001.log)" - curl -fsS http://127.0.0.1:8080/productpage >/dev/null 2>&1 && echo "Bookinfo: reachable on http://localhost:8080/productpage" || echo "Bookinfo: not reachable yet (check .port-forward-istio-system-istio-ingressgateway-8080.log)" + curl -fsSI http://127.0.0.1:20001/kiali/ >/dev/null 2>&1 && echo "Kiali: reachable on http://localhost:20001/kiali/" || echo "Kiali: not reachable yet (check ${RUNTIME_DIR}/port-forward-istio-system-kiali-20001.log)" + curl -fsS http://127.0.0.1:8080/productpage >/dev/null 2>&1 && echo "Bookinfo: reachable on http://localhost:8080/productpage" || echo "Bookinfo: not reachable yet (check ${RUNTIME_DIR}/port-forward-istio-system-istio-ingressgateway-8080.log)" fi open_url "http://localhost:20001/kiali/" diff --git a/Labs/10-Istio/demo/02-traffic-splitting-3pods/demo.sh b/Labs/10-Istio/demo/02-traffic-splitting-3pods/demo.sh index ceb8c23..3bd5898 100755 --- a/Labs/10-Istio/demo/02-traffic-splitting-3pods/demo.sh +++ b/Labs/10-Istio/demo/02-traffic-splitting-3pods/demo.sh @@ -11,15 +11,49 @@ WORK_DIR="$SCRIPT_DIR" RUNTIME_DIR="${TMPDIR:-/tmp}/kuberneteslabs-istio-traffic-splitting" PF_PID_FILE="$RUNTIME_DIR/port-forward.pids" +# Kill port-forwards started by this script and remove PF_PID_FILE. No-op if file missing or empty. +cleanup_port_forwards() { + [ -f "$PF_PID_FILE" ] || return 0 + [ -s "$PF_PID_FILE" ] || return 0 + while IFS= read -r pid; do + [ -n "$pid" ] || continue + kill "$pid" 2>/dev/null || true + done <"$PF_PID_FILE" + rm -f "$PF_PID_FILE" >/dev/null 2>&1 || true +} +trap cleanup_port_forwards EXIT INT TERM + +# Return whether a TCP port is in LISTEN state (tries lsof, ss, netstat). +# Portable: macOS and Linux (e.g. Ubuntu). Uses lsof (both), ss (Linux), netstat (both). +# Args: $1 - Port number. Returns: 0 if listening, 1 otherwise. +# If no tool is available, logs a warning and returns 1 (guard disabled). is_port_listening() { local port="$1" + # lsof: macOS and Ubuntu (same flags) if command -v lsof >/dev/null 2>&1; then lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1 - else + return + fi + # ss: Ubuntu (iproute2); not on macOS + if command -v ss >/dev/null 2>&1; then + if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then + return 0 + fi return 1 fi + # netstat: macOS (*.port) and Ubuntu (:port); -an is portable + if command -v netstat >/dev/null 2>&1; then + if netstat -an 2>/dev/null | grep -E '^(tcp|tcp4|tcp6)' | grep LISTEN | grep -qE "[:.]${port}[[:space:]]"; then + return 0 + fi + return 1 + fi + echo "Warning: Port-check guard disabled (lsof/ss/netstat not found). Duplicate port-forwards may occur." >&2 + return 1 } +# Start kubectl port-forward in background; skip if local port already in use. Append PID to PF_PID_FILE. +# Args: $1 - namespace; $2 - service name; $3 - local port; $4 - remote port. start_port_forward() { local namespace="$1" local svc="$2" @@ -40,13 +74,18 @@ start_port_forward() { echo $! >>"$PF_PID_FILE" } +# Open URL in default browser (macOS open or Linux xdg-open). No-op if neither available. +# Args: $1 - URL to open. open_url() { local url="$1" if command -v open >/dev/null 2>&1; then open "${url}" >/dev/null 2>&1 || true + return 0 elif command -v xdg-open >/dev/null 2>&1; then xdg-open "${url}" >/dev/null 2>&1 || true + return 0 fi + return 0 } echo "========================================" @@ -102,7 +141,7 @@ echo "========================================" echo "Starting Port-Forward + Local Curl Loop" echo "========================================" -touch "$PF_PID_FILE" +: > "$PF_PID_FILE" start_port_forward istio-system kiali 20001 20001 start_port_forward istio-system istio-ingressgateway 8080 80 @@ -129,7 +168,7 @@ echo "To see traffic splitting in Kiali:" echo "- Go to Graph" echo "- Select namespace: istio02" echo "- Turn on 'Requests distribution' (traffic animation/labels)" -echo "Cleanup resources: kubectl delete -f manifests/02-istio-routing.yaml; kubectl delete -f manifests/01-app.yaml" +echo "Cleanup resources: kubectl delete -f ${WORK_DIR}/manifests/02-istio-routing.yaml; kubectl delete -f ${WORK_DIR}/manifests/01-app.yaml" echo echo "========================================" diff --git a/Labs/10-Istio/manifests/addons/grafana.yaml b/Labs/10-Istio/manifests/addons/grafana.yaml index 05f46a4..49f6536 100644 --- a/Labs/10-Istio/manifests/addons/grafana.yaml +++ b/Labs/10-Istio/manifests/addons/grafana.yaml @@ -69,11 +69,23 @@ spec: metadata: labels: app: grafana - annotations: - sidecar.istio.io/inject: "false" + sidecar.istio.io/inject: "true" spec: + securityContext: + fsGroup: 472 + runAsNonRoot: true containers: - name: grafana + securityContext: + runAsUser: 472 + runAsGroup: 472 + runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault image: grafana/grafana:10.4.1 ports: - containerPort: 3000 diff --git a/Labs/10-Istio/manifests/addons/jaeger.yaml b/Labs/10-Istio/manifests/addons/jaeger.yaml index 07d55e2..d008fb6 100644 --- a/Labs/10-Istio/manifests/addons/jaeger.yaml +++ b/Labs/10-Istio/manifests/addons/jaeger.yaml @@ -76,8 +76,9 @@ spec: metadata: labels: app: jaeger + sidecar.istio.io/inject: "true" annotations: - sidecar.istio.io/inject: "false" + sidecar.istio.io/inject: "true" prometheus.io/scrape: "true" prometheus.io/port: "14269" spec: diff --git a/Labs/10-Istio/manifests/addons/kiali.yaml b/Labs/10-Istio/manifests/addons/kiali.yaml index b679190..f68461a 100644 --- a/Labs/10-Istio/manifests/addons/kiali.yaml +++ b/Labs/10-Istio/manifests/addons/kiali.yaml @@ -141,17 +141,31 @@ spec: metadata: labels: app: kiali + sidecar.istio.io/inject: "true" annotations: - sidecar.istio.io/inject: "false" + sidecar.istio.io/inject: "true" prometheus.io/scrape: "true" prometheus.io/port: "9090" spec: serviceAccountName: kiali + securityContext: + fsGroup: 1000 + runAsNonRoot: true containers: - name: kiali image: quay.io/kiali/kiali:v1.86 command: ["/opt/kiali/kiali"] args: ["-config", "/kiali-configuration/config.yaml"] + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault env: - name: ACTIVE_NAMESPACE valueFrom: diff --git a/Labs/10-Istio/manifests/addons/loki.yaml b/Labs/10-Istio/manifests/addons/loki.yaml new file mode 100644 index 0000000..98685f7 --- /dev/null +++ b/Labs/10-Istio/manifests/addons/loki.yaml @@ -0,0 +1,407 @@ +--- +# Source: loki/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: loki + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" +automountServiceAccountToken: true +--- +# Source: loki/templates/config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: loki + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" +data: + config.yaml: | + + auth_enabled: false + bloom_build: + builder: + planner_address: "" + enabled: false + bloom_gateway: + client: + addresses: "" + enabled: false + common: + compactor_grpc_address: 'loki.istio-system.svc.cluster.local:9095' + path_prefix: /var/loki + replication_factor: 1 + storage: + filesystem: + chunks_directory: /var/loki/chunks + rules_directory: /var/loki/rules + frontend: + scheduler_address: "" + tail_proxy_url: "" + frontend_worker: + scheduler_address: "" + index_gateway: + mode: simple + limits_config: + max_cache_freshness_per_query: 10m + query_timeout: 300s + reject_old_samples: true + reject_old_samples_max_age: 168h + split_queries_by_interval: 15m + volume_enabled: true + memberlist: + join_members: + - loki-memberlist.istio-system.svc.cluster.local + pattern_ingester: + enabled: false + query_range: + align_queries_with_step: true + ruler: + storage: + type: local + wal: + dir: /var/loki/ruler-wal + runtime_config: + file: /etc/loki/runtime-config/runtime-config.yaml + schema_config: + configs: + - from: "2024-04-01" + index: + period: 24h + prefix: index_ + object_store: 'filesystem' + schema: v13 + store: tsdb + server: + grpc_listen_port: 9095 + http_listen_port: 3100 + http_server_read_timeout: 600s + http_server_write_timeout: 600s + storage_config: + bloom_shipper: + working_directory: /var/loki/data/bloomshipper + boltdb_shipper: + index_gateway_client: + server_address: "" + hedging: + at: 250ms + max_per_second: 20 + up_to: 3 + tsdb_shipper: + index_gateway_client: + server_address: "" + use_thanos_objstore: false + tracing: + enabled: false +--- +# Source: loki/templates/runtime-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: loki-runtime + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" +data: + runtime-config.yaml: | + {} +--- +# Source: loki/templates/backend/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" + name: loki-clusterrole +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["configmaps"] + verbs: ["get", "watch", "list"] +--- +# Source: loki/templates/backend/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: loki-clusterrolebinding + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" +subjects: + - kind: ServiceAccount + name: loki + namespace: istio-system +roleRef: + kind: ClusterRole + name: loki-clusterrole + apiGroup: rbac.authorization.k8s.io +--- +# Source: loki/templates/service-memberlist.yaml +apiVersion: v1 +kind: Service +metadata: + name: loki-memberlist + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" + annotations: +spec: + type: ClusterIP + clusterIP: None + ports: + - name: tcp + port: 7946 + targetPort: http-memberlist + protocol: TCP + selector: + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/part-of: memberlist +--- +# Source: loki/templates/single-binary/service-headless.yaml +apiVersion: v1 +kind: Service +metadata: + name: loki-headless + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" + variant: headless + prometheus.io/service-monitor: "false" + annotations: +spec: + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + selector: + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki +--- +# Source: loki/templates/single-binary/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: loki + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" + annotations: +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + selector: + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/component: single-binary +--- +# Source: loki/templates/single-binary/statefulset.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: loki + namespace: istio-system + labels: + helm.sh/chart: loki-6.49.0 + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/version: "3.6.3" + app.kubernetes.io/component: single-binary + app.kubernetes.io/part-of: memberlist +spec: + replicas: 1 + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: loki-headless + revisionHistoryLimit: 10 + + persistentVolumeClaimRetentionPolicy: + whenDeleted: Delete + whenScaled: Delete + selector: + matchLabels: + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/component: single-binary + template: + metadata: + annotations: + storage/size: "10Gi" + kubectl.kubernetes.io/default-container: "loki" + sidecar.istio.io/inject: "true" + labels: + app.kubernetes.io/name: loki + app.kubernetes.io/instance: loki + app.kubernetes.io/component: single-binary + app.kubernetes.io/part-of: memberlist + sidecar.istio.io/inject: "true" + spec: + serviceAccountName: loki + automountServiceAccountToken: true + enableServiceLinks: true + + securityContext: + fsGroup: 10001 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 10001 + runAsNonRoot: true + runAsUser: 10001 + terminationGracePeriodSeconds: 30 + containers: + - name: loki + image: docker.io/grafana/loki:3.6.3 + imagePullPolicy: IfNotPresent + args: + - -config.file=/etc/loki/config/config.yaml + - -target=all + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + readinessProbe: + failureThreshold: 3 + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 15 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: storage + mountPath: /var/loki + - name: sc-rules-volume + mountPath: "/rules" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + - name: loki-sc-rules + image: docker.io/kiwigrid/k8s-sidecar:1.30.9 + imagePullPolicy: IfNotPresent + env: + - name: METHOD + value: WATCH + - name: LABEL + value: "loki_rule" + - name: FOLDER + value: "/rules" + - name: RESOURCE + value: "both" + - name: WATCH_SERVER_TIMEOUT + value: "60" + - name: WATCH_CLIENT_TIMEOUT + value: "60" + - name: LOG_LEVEL + value: "INFO" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: tmp + mountPath: /tmp + - name: sc-rules-volume + mountPath: "/rules" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/component: single-binary + app.kubernetes.io/instance: 'loki' + app.kubernetes.io/name: 'loki' + topologyKey: kubernetes.io/hostname + volumes: + - name: tmp + emptyDir: {} + - name: config + configMap: + name: loki + items: + - key: "config.yaml" + path: "config.yaml" + - name: runtime-config + configMap: + name: loki-runtime + - name: sc-rules-volume + emptyDir: {} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: storage + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "10Gi" diff --git a/Labs/10-Istio/manifests/addons/prometheus.yaml b/Labs/10-Istio/manifests/addons/prometheus.yaml index 1097450..1e893d8 100644 --- a/Labs/10-Istio/manifests/addons/prometheus.yaml +++ b/Labs/10-Istio/manifests/addons/prometheus.yaml @@ -134,8 +134,7 @@ spec: metadata: labels: app: prometheus - annotations: - sidecar.istio.io/inject: "false" + sidecar.istio.io/inject: "true" spec: serviceAccountName: prometheus containers: diff --git a/Labs/10-Istio/manifests/bookinfo-gateway.yaml b/Labs/10-Istio/manifests/bookinfo-gateway.yaml index cd643aa..6e822df 100644 --- a/Labs/10-Istio/manifests/bookinfo-gateway.yaml +++ b/Labs/10-Istio/manifests/bookinfo-gateway.yaml @@ -25,7 +25,8 @@ metadata: namespace: bookinfo spec: hosts: - - "*" + - "bookinfo.local" + - "istio-ingressgateway.istio-system.svc.cluster.local" gateways: - bookinfo-gateway http: diff --git a/Labs/10-Istio/manifests/ingress.yaml b/Labs/10-Istio/manifests/ingress.yaml deleted file mode 100644 index 1a056ef..0000000 --- a/Labs/10-Istio/manifests/ingress.yaml +++ /dev/null @@ -1,115 +0,0 @@ -################################################################################################## -# Ingress resources for all Istio observability services -# Uses nginx ingress controller (class: nginx) -# Hosts: kiali.local, grafana.local, jaeger.local, prometheus.local, bookinfo.local -################################################################################################## - -# Kiali Ingress -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: kiali - namespace: istio-system - labels: - app: kiali -spec: - ingressClassName: nginx - rules: - - host: kiali.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: kiali - port: - number: 20001 ---- -# Grafana Ingress -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: grafana - namespace: istio-system - labels: - app: grafana -spec: - ingressClassName: nginx - rules: - - host: grafana.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: grafana - port: - number: 3000 ---- -# Jaeger Ingress -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: jaeger - namespace: istio-system - labels: - app: jaeger -spec: - ingressClassName: nginx - rules: - - host: jaeger.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: tracing - port: - number: 80 ---- -# Prometheus Ingress -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: prometheus - namespace: istio-system - labels: - app: prometheus -spec: - ingressClassName: nginx - rules: - - host: prometheus.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: prometheus - port: - number: 9090 ---- -# Bookinfo Productpage Ingress (via Istio Ingress Gateway) -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: bookinfo - namespace: istio-system - labels: - app: bookinfo -spec: - ingressClassName: nginx - rules: - - host: bookinfo.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: istio-ingressgateway - port: - number: 80 diff --git a/Labs/10-Istio/manifests/observability-routes.yaml b/Labs/10-Istio/manifests/observability-routes.yaml new file mode 100644 index 0000000..f7f6eca --- /dev/null +++ b/Labs/10-Istio/manifests/observability-routes.yaml @@ -0,0 +1,90 @@ +################################################################################################## +# Istio VirtualServices for observability addons (Kiali, Grafana, Jaeger, Prometheus, Loki) +# Uses the same Istio Ingress Gateway as Bookinfo (no nginx ingress). +# Add kiali.local, grafana.local, etc. to /etc/hosts pointing at the gateway's external IP. +################################################################################################## + +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: kiali + namespace: istio-system +spec: + hosts: + - "kiali.local" + gateways: + - bookinfo/bookinfo-gateway + http: + - route: + - destination: + host: kiali + port: + number: 20001 +--- +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: grafana + namespace: istio-system +spec: + hosts: + - "grafana.local" + gateways: + - bookinfo/bookinfo-gateway + http: + - route: + - destination: + host: grafana + port: + number: 3000 +--- +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: jaeger + namespace: istio-system +spec: + hosts: + - "jaeger.local" + gateways: + - bookinfo/bookinfo-gateway + http: + - route: + - destination: + host: tracing + port: + number: 80 +--- +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: prometheus + namespace: istio-system +spec: + hosts: + - "prometheus.local" + gateways: + - bookinfo/bookinfo-gateway + http: + - route: + - destination: + host: prometheus + port: + number: 9090 +--- +apiVersion: networking.istio.io/v1 +kind: VirtualService +metadata: + name: loki + namespace: istio-system +spec: + hosts: + - "loki.local" + gateways: + - bookinfo/bookinfo-gateway + http: + - route: + - destination: + host: loki + port: + number: 3100 diff --git a/Labs/10-Istio/manifests/traffic-generator.yaml b/Labs/10-Istio/manifests/traffic-generator.yaml index b030027..420e606 100644 --- a/Labs/10-Istio/manifests/traffic-generator.yaml +++ b/Labs/10-Istio/manifests/traffic-generator.yaml @@ -6,6 +6,8 @@ apiVersion: v1 kind: Namespace metadata: name: traffic-gen + labels: + istio-injection: enabled --- # Immediate seed job - runs right away to populate dashboards with data apiVersion: batch/v1 diff --git a/Labs/10-Istio/monitor.sh b/Labs/10-Istio/monitor.sh index 839de22..7364dd1 100755 --- a/Labs/10-Istio/monitor.sh +++ b/Labs/10-Istio/monitor.sh @@ -20,6 +20,8 @@ CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' +# Print a cyan section header with a border. +# Args: $1 - Header text. print_header() { echo "" echo -e "${CYAN}========================================${NC}" @@ -27,17 +29,27 @@ print_header() { echo -e "${CYAN}========================================${NC}" } +# Print a blue subsection label. +# Args: $1 - Section title. print_section() { echo "" echo -e "${BLUE}--- $1 ---${NC}" } +# Print an info line with a green checkmark. print_info() { echo -e "${GREEN}✓${NC} $1"; } + +# Print a warning line with a yellow symbol. print_warning() { echo -e "${YELLOW}⚠${NC} $1"; } + +# Print an error line with a red symbol. print_error() { echo -e "${RED}✗${NC} $1"; } + +# Print a key-value line with magenta key. +# Args: $1 - Label; $2 - Value. print_value() { echo -e " ${MAGENTA}$1:${NC} $2"; } -# Check prerequisites +# Ensure kubectl is installed and cluster is reachable. Exits on failure. check_prerequisites() { if ! command -v kubectl >/dev/null 2>&1; then print_error "kubectl is not installed" @@ -50,7 +62,7 @@ check_prerequisites() { print_info "Connected to Kubernetes cluster" } -# Check Istio control plane +# Report Istiod, ingress gateway, sidecar injection, and Helm releases in istio-system. check_istio() { print_header "ISTIO CONTROL PLANE" @@ -86,7 +98,7 @@ check_istio() { helm list -n istio-system 2>/dev/null | grep -E '(NAME|istio)' || echo " No Istio Helm releases found" } -# Check all pods +# List pods in istio-system, bookinfo, and traffic-gen (including cronjobs/jobs). check_pods() { print_header "POD STATUS" @@ -100,11 +112,11 @@ check_pods() { kubectl get pods,cronjobs,jobs -n traffic-gen 2>/dev/null } -# Check observability addons +# Report status of Prometheus, Grafana, Jaeger, Kiali and list addon services. check_addons() { print_header "OBSERVABILITY ADDONS" - for addon in prometheus grafana jaeger kiali; do + for addon in prometheus grafana jaeger kiali loki; do STATUS=$(kubectl get pods -n istio-system -l app=$addon -o jsonpath='{.items[0].status.phase}' 2>/dev/null) if [ "$STATUS" = "Running" ]; then print_info "${addon}: Running" @@ -114,11 +126,11 @@ check_addons() { done print_section "Addon Services" - kubectl get svc -n istio-system -l 'app in (prometheus,grafana,kiali,jaeger)' 2>/dev/null || - kubectl get svc -n istio-system 2>/dev/null | grep -E '(NAME|prometheus|grafana|kiali|tracing|jaeger|zipkin)' + kubectl get svc -n istio-system -l 'app in (prometheus,grafana,kiali,jaeger,loki)' 2>/dev/null || + kubectl get svc -n istio-system 2>/dev/null | grep -E '(NAME|prometheus|grafana|kiali|tracing|jaeger|zipkin|loki)' } -# Check Bookinfo application +# Report Bookinfo services, deployments, sidecar status, and Istio config (VS/DR/GW). check_bookinfo() { print_header "BOOKINFO APPLICATION" @@ -133,8 +145,8 @@ check_bookinfo() { POD_NAME=$(echo "$line" | awk '{print $1}') VERSION=$(echo "$line" | awk '{print $2}') PHASE=$(echo "$line" | awk '{print $3}') - HAS_SIDECAR=$(echo "$line" | grep -c "istio-proxy") - if [ "$HAS_SIDECAR" -gt 0 ]; then + HAS_SIDECAR=$(echo "$line" | grep -c "istio-proxy" || true) + if [ "${HAS_SIDECAR:-0}" -gt 0 ]; then print_info "$app ($VERSION): $PHASE [sidecar ✓]" else print_warning "$app ($VERSION): $PHASE [NO sidecar]" @@ -157,7 +169,7 @@ check_bookinfo() { kubectl get gateways -n bookinfo 2>/dev/null | sed 's/^/ /' } -# Check mesh traffic metrics +# Query Prometheus for Bookinfo request rate, 5xx rate, and traffic generator status. check_traffic() { print_header "MESH TRAFFIC METRICS" @@ -196,7 +208,7 @@ check_traffic() { fi } -# Check mTLS status +# Show PeerAuthentication policies and per-namespace mTLS mode (bookinfo, istio-system). check_mtls() { print_header "mTLS STATUS" @@ -214,7 +226,8 @@ check_mtls() { done } -# Test all components +# Run connectivity checks: Istiod, gateway, addons, Bookinfo pods, productpage HTTP, traffic gen. +# Prints passed/failed counts. test_pipeline() { print_header "COMPONENT CONNECTIVITY TEST" @@ -222,7 +235,7 @@ test_pipeline() { local failed=0 print_section "Step 1: Istio Control Plane" - ISTIOD=$(kubectl get pods -n istio-system -l app=istiod --no-headers 2>/dev/null | grep -c Running) + ISTIOD=$(kubectl get pods -n istio-system -l app=istiod --no-headers 2>/dev/null | grep -c Running || true) if [ "$ISTIOD" -gt 0 ]; then print_info "Istiod is running" passed=$((passed + 1)) @@ -232,9 +245,9 @@ test_pipeline() { fi print_section "Step 2: Ingress Gateway" - GW=$(kubectl get pods -n istio-system -l istio=ingressgateway --no-headers 2>/dev/null | grep -c Running) + GW=$(kubectl get pods -n istio-system -l istio=ingressgateway --no-headers 2>/dev/null | grep -c Running || true) if [ "$GW" -eq 0 ]; then - GW=$(kubectl get pods -n istio-system -l app=istio-ingressgateway --no-headers 2>/dev/null | grep -c Running) + GW=$(kubectl get pods -n istio-system -l app=istio-ingressgateway --no-headers 2>/dev/null | grep -c Running || true) fi if [ "$GW" -gt 0 ]; then print_info "Ingress Gateway is running" @@ -245,8 +258,8 @@ test_pipeline() { fi print_section "Step 3: Observability Addons" - for addon in prometheus grafana kiali jaeger; do - RUNNING=$(kubectl get pods -n istio-system -l app=$addon --no-headers 2>/dev/null | grep -c Running) + for addon in prometheus grafana kiali jaeger loki; do + RUNNING=$(kubectl get pods -n istio-system -l app=$addon --no-headers 2>/dev/null | grep -c Running || true) if [ "$RUNNING" -gt 0 ]; then print_info "$addon is running" passed=$((passed + 1)) @@ -258,7 +271,7 @@ test_pipeline() { print_section "Step 4: Bookinfo Application" for app in productpage details reviews ratings; do - RUNNING=$(kubectl get pods -n bookinfo -l app=$app --no-headers 2>/dev/null | grep -c Running) + RUNNING=$(kubectl get pods -n bookinfo -l app=$app --no-headers 2>/dev/null | grep -c Running || true) if [ "$RUNNING" -gt 0 ]; then print_info "$app is running ($RUNNING pod(s))" passed=$((passed + 1)) @@ -298,7 +311,7 @@ test_pipeline() { print_header "TEST RESULTS: $passed passed, $failed failed" } -# Quick summary +# Print a short status table and port-forward / feature-demo commands. show_summary() { print_header "QUICK SUMMARY" @@ -308,15 +321,15 @@ show_summary() { ISTIOD=$(kubectl get pods -n istio-system -l app=istiod -o jsonpath='{.items[0].status.phase}' 2>/dev/null) print_value "Istiod" "${ISTIOD:-Not Found}" - for addon in kiali prometheus grafana jaeger; do + for addon in kiali prometheus grafana jaeger loki; do STATUS=$(kubectl get pods -n istio-system -l app=$addon -o jsonpath='{.items[0].status.phase}' 2>/dev/null) print_value "$addon" "${STATUS:-Not Found}" done - BOOKINFO_PODS=$(kubectl get pods -n bookinfo --no-headers 2>/dev/null | grep -c Running) + BOOKINFO_PODS=$(kubectl get pods -n bookinfo --no-headers 2>/dev/null | grep -c Running || true) print_value "Bookinfo pods running" "${BOOKINFO_PODS:-0}" - SIDECAR_PODS=$(kubectl get pods -n bookinfo -o jsonpath='{range .items[*]}{.spec.containers[*].name}{"\n"}{end}' 2>/dev/null | grep -c istio-proxy) + SIDECAR_PODS=$(kubectl get pods -n bookinfo -o jsonpath='{range .items[*]}{.spec.containers[*].name}{"\n"}{end}' 2>/dev/null | grep -c istio-proxy || true) print_value "Pods with sidecar" "${SIDECAR_PODS:-0}" echo "" @@ -330,7 +343,7 @@ show_summary() { echo " ./istio-features/apply-feature.sh list" } -# Interactive menu +# Print the interactive monitoring menu (numbered options and exit). show_menu() { echo "" echo -e "${CYAN}╔═══════════════════════════════════════════════╗${NC}" @@ -350,13 +363,15 @@ show_menu() { echo "" } -# Main +# Entry point: run full/test/summary report or start interactive menu. +# Args: $@ - Optional mode: full, test, summary, or none for menu. main() { clear print_header "ISTIO + KIALI LAB MONITORING" check_prerequisites - if [ "$1" = "full" ] || [ "$1" = "-f" ] || [ "$1" = "--full" ]; then + local mode="${1:-}" + if [ "$mode" = "full" ] || [ "$mode" = "-f" ] || [ "$mode" = "--full" ]; then show_summary check_istio check_pods @@ -368,12 +383,12 @@ main() { exit 0 fi - if [ "$1" = "test" ] || [ "$1" = "-t" ] || [ "$1" = "--test" ]; then + if [ "$mode" = "test" ] || [ "$mode" = "-t" ] || [ "$mode" = "--test" ]; then test_pipeline exit 0 fi - if [ "$1" = "summary" ] || [ "$1" = "-s" ] || [ "$1" = "--summary" ]; then + if [ "$mode" = "summary" ] || [ "$mode" = "-s" ] || [ "$mode" = "--summary" ]; then show_summary exit 0 fi diff --git a/Labs/10-Istio/scripts/01-install-istio.sh b/Labs/10-Istio/scripts/01-install-istio.sh index 36e8ba0..ff8e5e8 100755 --- a/Labs/10-Istio/scripts/01-install-istio.sh +++ b/Labs/10-Istio/scripts/01-install-istio.sh @@ -7,6 +7,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common.sh" +# Install Istio service mesh via Helm: base CRDs, istiod, and ingress gateway. +# Cleans conflicting resources from prior istioctl/Helm installs. Exits on failure. install_istio() { print_header "Installing Istio Service Mesh via Helm" @@ -15,6 +17,38 @@ install_istio() { helm repo add istio https://istio-release.storage.googleapis.com/charts helm repo update + # Remove existing Istio resources not managed by Helm (istio-base) to avoid + # "invalid ownership metadata" when a prior install used istioctl or left + # resources without Helm annotations + print_step "Checking for conflicting Istio resources..." + + for crd in $(kubectl get crd -o name 2>/dev/null | grep istio.io || true); do + release=$(kubectl get "$crd" -o jsonpath='{.metadata.annotations["meta.helm.sh/release-name"]}' 2>/dev/null || true) + [ -n "$release" ] || continue + if [ "$release" != "istio-base" ]; then + print_info "Removing $crd (not managed by Helm istio-base)" + kubectl delete "$crd" --ignore-not-found 2>/dev/null || true + fi + done + + for webhook in $(kubectl get validatingwebhookconfiguration -o name 2>/dev/null | grep -i istio || true); do + release=$(kubectl get "$webhook" -o jsonpath='{.metadata.annotations["meta.helm.sh/release-name"]}' 2>/dev/null || true) + [ -n "$release" ] || continue + if [ "$release" != "istio-base" ]; then + print_info "Removing $webhook (not managed by Helm istio-base)" + kubectl delete "$webhook" --ignore-not-found 2>/dev/null || true + fi + done + + for webhook in $(kubectl get mutatingwebhookconfiguration -o name 2>/dev/null | grep -i istio || true); do + release=$(kubectl get "$webhook" -o jsonpath='{.metadata.annotations["meta.helm.sh/release-name"]}' 2>/dev/null || true) + [ -n "$release" ] || continue + if [ "$release" != "istio-base" ]; then + print_info "Removing $webhook (not managed by Helm istio-base)" + kubectl delete "$webhook" --ignore-not-found 2>/dev/null || true + fi + done + # Install Istio base (CRDs) print_step "Installing Istio base (CRDs)..." helm upgrade --install istio-base istio/base \ @@ -30,6 +64,27 @@ install_istio() { fi print_success "Istio CRDs installed" + # Remove cluster-scoped resources that istiod would manage but exist without + # Helm ownership (e.g. from a previous istioctl install) + print_step "Checking for conflicting Istiod cluster resources..." + for role in $(kubectl get clusterrole -o name 2>/dev/null | grep -i istio || true); do + release=$(kubectl get "$role" -o jsonpath='{.metadata.annotations["meta.helm.sh/release-name"]}' 2>/dev/null || true) + [ -n "$release" ] || continue + if [ "$release" != "istiod" ] && [ "$release" != "istio-base" ]; then + print_info "Removing $role (not managed by Helm istiod)" + kubectl delete "$role" --ignore-not-found 2>/dev/null || true + fi + done + for binding in $(kubectl get clusterrolebinding -o name 2>/dev/null | grep -i istio || true); do + release=$(kubectl get "$binding" -o jsonpath='{.metadata.annotations["meta.helm.sh/release-name"]}' 2>/dev/null || true) + [ -n "$release" ] || continue + if [ "$release" != "istiod" ] && [ "$release" != "istio-base" ]; then + print_info "Removing $binding (not managed by Helm istiod)" + kubectl delete "$binding" --ignore-not-found 2>/dev/null || true + fi + done + sleep 2 + # Install Istiod (control plane) print_step "Installing Istiod (control plane)..." helm upgrade --install istiod istio/istiod \ diff --git a/Labs/10-Istio/scripts/02-install-addons.sh b/Labs/10-Istio/scripts/02-install-addons.sh index 2027f3b..cabde6d 100755 --- a/Labs/10-Istio/scripts/02-install-addons.sh +++ b/Labs/10-Istio/scripts/02-install-addons.sh @@ -1,13 +1,15 @@ #!/bin/bash set -euo pipefail # ============================================================================= -# Install Istio Observability Addons (Kiali, Prometheus, Grafana, Jaeger) +# Install Istio Observability Addons (Kiali, Prometheus, Grafana, Jaeger, Loki) # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LAB_DIR="$(dirname "$SCRIPT_DIR")" source "${SCRIPT_DIR}/common.sh" +# Install Istio observability addons (Prometheus, Grafana, Jaeger, Kiali, Loki) +# and apply gateway routes. Waits for addon pods to be ready. install_addons() { print_header "Installing Istio Observability Addons" @@ -31,28 +33,35 @@ install_addons() { kubectl apply -f "${LAB_DIR}/manifests/addons/kiali.yaml" print_success "Kiali manifest applied" + # Install Loki + print_step "Installing Loki..." + kubectl apply -f "${LAB_DIR}/manifests/addons/loki.yaml" + print_success "Loki manifest applied" + # Wait for addons to be ready print_step "Waiting for addons to be ready..." wait_for_pods "app=prometheus" "istio-system" 180 wait_for_pods "app=grafana" "istio-system" 180 wait_for_pods "app=jaeger" "istio-system" 180 wait_for_pods "app=kiali" "istio-system" 180 + wait_for_pods "app.kubernetes.io/name=loki" "istio-system" 180 - # Install Ingress for all services - print_step "Installing Ingress resources for all services..." - kubectl apply -f "${LAB_DIR}/manifests/ingress.yaml" - print_success "Ingress resources applied" + # Route addons and Bookinfo through Istio Ingress Gateway (no nginx) + print_step "Configuring Istio Gateway routes for all services..." + kubectl apply -f "${LAB_DIR}/manifests/observability-routes.yaml" + print_success "Istio Gateway routes applied" echo "" print_success "All observability addons installed!" echo "" kubectl get pods -n istio-system echo "" - print_info "Ingress URLs:" + print_info "Access via Istio Ingress Gateway (add these hosts to /etc/hosts with gateway IP):" echo " - http://kiali.local" echo " - http://grafana.local" echo " - http://jaeger.local" echo " - http://prometheus.local" + echo " - http://loki.local" echo " - http://bookinfo.local/productpage" } diff --git a/Labs/10-Istio/scripts/03-deploy-bookinfo.sh b/Labs/10-Istio/scripts/03-deploy-bookinfo.sh index 8b7c72d..68cf69b 100755 --- a/Labs/10-Istio/scripts/03-deploy-bookinfo.sh +++ b/Labs/10-Istio/scripts/03-deploy-bookinfo.sh @@ -8,6 +8,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LAB_DIR="$(dirname "$SCRIPT_DIR")" source "${SCRIPT_DIR}/common.sh" +# Deploy the Bookinfo sample app: namespace, microservices, gateway, and +# destination rules. Waits for pods and optionally verifies productpage connectivity. deploy_bookinfo() { print_header "Deploying Bookinfo Sample Application" @@ -43,16 +45,19 @@ deploy_bookinfo() { echo "" kubectl get pods -n bookinfo - # Verify the application works + # Verify the application works (non-fatal — don't abort deploy if check fails) echo "" print_step "Verifying application connectivity..." sleep 5 - PRODUCTPAGE_POD=$(kubectl get pod -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}') - RESULT=$(kubectl exec -n bookinfo "$PRODUCTPAGE_POD" -c productpage -- curl -s -o /dev/null -w "%{http_code}" http://productpage:9080/productpage 2>/dev/null) - if [ "$RESULT" = "200" ]; then - print_success "Bookinfo productpage is responding (HTTP 200)" + if PRODUCTPAGE_POD=$(kubectl get pod -n bookinfo -l app=productpage -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) && [ -n "$PRODUCTPAGE_POD" ]; then + RESULT=$(kubectl exec -n bookinfo "$PRODUCTPAGE_POD" -c productpage -- wget -q -S -O /dev/null http://productpage:9080/productpage 2>&1 | awk '/HTTP\// {print $2}' | tail -1) || true + if [ "$RESULT" = "200" ]; then + print_success "Bookinfo productpage is responding (HTTP 200)" + else + print_warning "Productpage returned HTTP ${RESULT:-unknown} - it may need more time to initialize" + fi else - print_warning "Productpage returned HTTP $RESULT - it may need more time to initialize" + print_warning "Could not find productpage pod - skipping connectivity check" fi } diff --git a/Labs/10-Istio/scripts/04-traffic-generator.sh b/Labs/10-Istio/scripts/04-traffic-generator.sh index f9646aa..de0e622 100755 --- a/Labs/10-Istio/scripts/04-traffic-generator.sh +++ b/Labs/10-Istio/scripts/04-traffic-generator.sh @@ -8,14 +8,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LAB_DIR="$(dirname "$SCRIPT_DIR")" source "${SCRIPT_DIR}/common.sh" +# Deploy the traffic generator CronJob to send periodic requests to Bookinfo +# for observability demos (Kiali, Grafana, Jaeger). deploy_traffic_generator() { print_header "Deploying Traffic Generator" - # Create traffic-gen namespace - print_step "Creating traffic-gen namespace..." - kubectl create namespace traffic-gen 2>/dev/null || true - - # Deploy traffic generator CronJob + # Deploy traffic generator (namespace is defined in the manifest with istio-injection label) print_step "Deploying traffic generator CronJob..." kubectl apply -f "${LAB_DIR}/manifests/traffic-generator.yaml" print_success "Traffic generator deployed" diff --git a/Labs/10-Istio/scripts/05-verify.sh b/Labs/10-Istio/scripts/05-verify.sh index 0e40ed0..2744174 100755 --- a/Labs/10-Istio/scripts/05-verify.sh +++ b/Labs/10-Istio/scripts/05-verify.sh @@ -7,6 +7,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common.sh" +# Verify Istio control plane, ingress gateway, addons, Bookinfo, traffic +# generator, and Prometheus metrics. Prints pass/fail summary. verify_deployment() { print_header "Verifying Istio + Kiali Deployment" @@ -14,7 +16,7 @@ verify_deployment() { # Check Istio control plane print_step "Checking Istio control plane..." - ISTIOD_STATUS=$(kubectl get pods -n istio-system -l app=istiod -o jsonpath='{.items[0].status.phase}' 2>/dev/null) + ISTIOD_STATUS=$(kubectl get pods -n istio-system -l app=istiod -o jsonpath='{.items[0].status.phase}' 2>/dev/null) || true if [ "$ISTIOD_STATUS" = "Running" ]; then print_success "Istiod: Running" else @@ -23,12 +25,12 @@ verify_deployment() { fi # Check Ingress Gateway - GW_STATUS=$(kubectl get pods -n istio-system -l app=istio-ingressgateway -o jsonpath='{.items[0].status.phase}' 2>/dev/null) + GW_STATUS=$(kubectl get pods -n istio-system -l app=istio-ingressgateway -o jsonpath='{.items[0].status.phase}' 2>/dev/null) || true if [ "$GW_STATUS" = "Running" ]; then print_success "Ingress Gateway: Running" else # Try the Helm chart label - GW_STATUS=$(kubectl get pods -n istio-system -l istio=ingressgateway -o jsonpath='{.items[0].status.phase}' 2>/dev/null) + GW_STATUS=$(kubectl get pods -n istio-system -l istio=ingressgateway -o jsonpath='{.items[0].status.phase}' 2>/dev/null) || true if [ "$GW_STATUS" = "Running" ]; then print_success "Ingress Gateway: Running" else @@ -40,7 +42,7 @@ verify_deployment() { # Check addons print_step "Checking observability addons..." for addon in prometheus grafana kiali jaeger; do - STATUS=$(kubectl get pods -n istio-system -l app=$addon -o jsonpath='{.items[0].status.phase}' 2>/dev/null) + STATUS=$(kubectl get pods -n istio-system -l app=$addon -o jsonpath='{.items[0].status.phase}' 2>/dev/null) || true if [ "$STATUS" = "Running" ]; then print_success "$addon: Running" else @@ -52,10 +54,10 @@ verify_deployment() { # Check Bookinfo print_step "Checking Bookinfo application..." for app in productpage details reviews ratings; do - STATUS=$(kubectl get pods -n bookinfo -l app=$app -o jsonpath='{.items[0].status.phase}' 2>/dev/null) + STATUS=$(kubectl get pods -n bookinfo -l app=$app -o jsonpath='{.items[0].status.phase}' 2>/dev/null) || true if [ "$STATUS" = "Running" ]; then # Check sidecar injection - CONTAINERS=$(kubectl get pods -n bookinfo -l app=$app -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) + CONTAINERS=$(kubectl get pods -n bookinfo -l app=$app -o jsonpath='{.items[0].spec.containers[*].name}' 2>/dev/null) || true if echo "$CONTAINERS" | grep -q "istio-proxy"; then print_success "$app: Running (sidecar injected)" else @@ -69,7 +71,7 @@ verify_deployment() { # Check traffic generator print_step "Checking traffic generator..." - CRONJOB=$(kubectl get cronjob -n traffic-gen traffic-generator -o jsonpath='{.metadata.name}' 2>/dev/null) + CRONJOB=$(kubectl get cronjob -n traffic-gen traffic-generator -o jsonpath='{.metadata.name}' 2>/dev/null) || true if [ -n "$CRONJOB" ]; then print_success "Traffic generator CronJob: Active" else @@ -78,10 +80,10 @@ verify_deployment() { # Check Istio metrics in Prometheus print_step "Checking Istio metrics..." - PROM_POD=$(kubectl get pods -n istio-system -l app=prometheus -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + PROM_POD=$(kubectl get pods -n istio-system -l app=prometheus -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) || true if [ -n "$PROM_POD" ]; then - METRIC_COUNT=$(kubectl exec -n istio-system "$PROM_POD" -- wget -qO- 'http://localhost:9090/api/v1/query?query=count(istio_requests_total)' 2>/dev/null | grep -o '"value"' | wc -l | tr -d ' ') - if [ "$METRIC_COUNT" -gt 0 ]; then + METRIC_COUNT=$(kubectl exec -n istio-system "$PROM_POD" -- wget -qO- 'http://localhost:9090/api/v1/query?query=count(istio_requests_total)' 2>/dev/null | grep -o '"value"' | wc -l | tr -d ' ') || true + if [ "${METRIC_COUNT:-0}" -gt 0 ]; then print_success "Istio metrics available in Prometheus" else print_warning "No Istio metrics yet (traffic may need more time)" diff --git a/Labs/10-Istio/scripts/common.sh b/Labs/10-Istio/scripts/common.sh index e8b9954..cea6e32 100755 --- a/Labs/10-Istio/scripts/common.sh +++ b/Labs/10-Istio/scripts/common.sh @@ -12,12 +12,23 @@ CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' +# Print an informational message to stdout. print_info() { echo -e "${BLUE}[INFO]${NC} $1"; } + +# Print a success message to stdout. print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } + +# Print a warning message to stdout. print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +# Print an error message to stderr. print_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Print a step label to stdout. print_step() { echo -e "${CYAN}[STEP]${NC} $1"; } +# Print a section header with a decorative border. +# Args: $1 - Header text to display. print_header() { echo "" echo "============================================" @@ -26,12 +37,16 @@ print_header() { echo "" } -# Check if a command exists +# Return whether a command is available in PATH. +# Args: $1 - Command name to check. +# Returns: 0 if command exists, non-zero otherwise. command_exists() { command -v "$1" >/dev/null 2>&1 } -# Check required tools +# Verify that a required CLI tool is installed; exit with error if not. +# Args: $1 - Command name (e.g. kubectl, helm). +# Returns: 1 if tool is missing, 0 otherwise. check_tool() { if ! command_exists "$1"; then print_error "$1 is not installed. Please install it first." @@ -39,6 +54,8 @@ check_tool() { fi } +# Ensure required tools (kubectl, helm) are installed and cluster is reachable. +# Exits with status 1 if any check fails. check_prerequisites() { print_info "Checking prerequisites..." @@ -51,7 +68,7 @@ check_prerequisites() { exit 1 fi - # Check if kubectl can connect to cluster + # Check if kubectl can connect to cluster. if ! kubectl cluster-info &>/dev/null; then print_error "Cannot connect to Kubernetes cluster. Please configure kubectl." exit 1 @@ -60,7 +77,8 @@ check_prerequisites() { print_success "All prerequisites are met!" } -# Wait for pods with a label in a namespace +# Block until pods matching a label in a namespace are ready or timeout. +# Args: $1 - Label selector; $2 - Namespace; $3 - Timeout in seconds (default 300). wait_for_pods() { local label="$1" local namespace="$2" @@ -69,7 +87,8 @@ wait_for_pods() { kubectl wait --for=condition=ready pod -l "$label" -n "$namespace" --timeout="${timeout}s" 2>/dev/null } -# Get the directory of this script +# Resolve and print the lab directory (parent of scripts/). +# Uses the script that sourced this file when available. get_lab_dir() { cd "$(dirname "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")" && pwd }