Terminal.skills
Use Cases/Build Service Mesh Observability with Istio

Build Service Mesh Observability with Istio

Deploy Istio service mesh on Kubernetes to get automatic mTLS, traffic observability, request tracing, and circuit breaking across microservices — without changing application code.

#kubernetes#helm#containers#orchestration
Works with:claude-codeopenai-codexgemini-clicursor
$

The Problem

Alex leads platform engineering at a 55-person e-commerce company running 24 microservices on Kubernetes. Debugging production issues is a nightmare: when checkout latency spikes, the team spends hours tracing requests across services with kubectl logs. There's no encryption between services (everything trusts the cluster network), no circuit breakers (one slow service cascades failures everywhere), and no traffic visibility. Last month, a database migration in the inventory service caused 4 hours of cascading failures across checkout, payments, and shipping — because there was no way to see the dependency chain or automatically shed load. The estimated revenue loss: $180K.

Step 1: Deploy Istio with Production-Grade Configuration

Istio's control plane manages sidecar proxies injected into each pod. The setup enables automatic mTLS, telemetry collection, and traffic management without modifying any service code.

yaml
# istio/istio-operator.yaml — Production Istio installation with resource limits
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: production-mesh
  namespace: istio-system
spec:
  profile: default
  
  meshConfig:
    # Enable access logging for all traffic
    accessLogFile: /dev/stdout
    accessLogFormat: |
      {"timestamp":"%START_TIME%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","upstream_service":"%UPSTREAM_CLUSTER%","duration_ms":"%DURATION%","request_id":"%REQ(X-REQUEST-ID)%","upstream_host":"%UPSTREAM_HOST%"}
    
    # Automatic mTLS — encrypt all inter-service traffic
    enableAutoMtls: true
    
    # Distributed tracing — sample 10% of requests in production
    defaultConfig:
      tracing:
        sampling: 10.0  # percentage of requests traced
      holdApplicationUntilProxyStarts: true  # prevent race conditions on startup
    
    # Outbound traffic policy — only allow registered services
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY
  
  components:
    pilot:
      k8s:
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        # High availability — 2 replicas for the control plane
        replicaCount: 2
        hpaSpec:
          minReplicas: 2
          maxReplicas: 5
    
    ingressGateways:
      - name: istio-ingressgateway
        enabled: true
        k8s:
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 1000m
              memory: 256Mi
          service:
            type: LoadBalancer
            ports:
              - port: 80
                targetPort: 8080
                name: http2
              - port: 443
                targetPort: 8443
                name: https

  values:
    # Sidecar proxy resource defaults
    global:
      proxy:
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 128Mi
bash
# Install Istio with the operator configuration
istioctl install -f istio/istio-operator.yaml --verify

# Enable automatic sidecar injection for application namespaces
kubectl label namespace default istio-injection=enabled
kubectl label namespace staging istio-injection=enabled

Step 2: Configure Traffic Policies and Circuit Breakers

Destination rules define how traffic flows between services. Circuit breakers prevent cascading failures by ejecting unhealthy endpoints before they bring down the whole system.

yaml
# istio/destination-rules.yaml — Circuit breakers and load balancing for each service
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: checkout-service
  namespace: default
spec:
  host: checkout-service
  trafficPolicy:
    # Connection pool limits — prevent one slow consumer from exhausting connections
    connectionPool:
      tcp:
        maxConnections: 100        # max TCP connections per host
        connectTimeout: 5s
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 50   # max queued requests
        http2MaxRequests: 100         # max concurrent requests
        maxRequestsPerConnection: 10  # recycle connections
        maxRetries: 3
    
    # Circuit breaker — eject hosts that start failing
    outlierDetection:
      consecutive5xxErrors: 3       # eject after 3 consecutive 5xx errors
      interval: 10s                 # check every 10 seconds
      baseEjectionTime: 30s         # eject for at least 30 seconds
      maxEjectionPercent: 50        # never eject more than 50% of hosts
      minHealthPercent: 30          # only enforce when >30% hosts are healthy
    
    # Load balancing — use least connections for even distribution
    loadBalancer:
      simple: LEAST_REQUEST

---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: inventory-service
  namespace: default
spec:
  host: inventory-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 50
        connectTimeout: 3s
      http:
        http1MaxPendingRequests: 25
        http2MaxRequests: 50
        maxRetries: 2
    outlierDetection:
      consecutive5xxErrors: 2       # stricter — inventory is critical
      interval: 5s
      baseEjectionTime: 60s         # eject for longer
      maxEjectionPercent: 30
    loadBalancer:
      simple: ROUND_ROBIN

---
# Retry policy for transient failures
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: payment-service
  namespace: default
spec:
  hosts:
    - payment-service
  http:
    - route:
        - destination:
            host: payment-service
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: 5xx,reset,connect-failure,retriable-4xx
      timeout: 10s   # total timeout including retries

Step 3: Build the Observability Dashboard

Deploy Prometheus, Grafana, and Kiali to visualize the service mesh. Istio sidecars export metrics automatically — no instrumentation code needed in the services.

typescript
// src/monitoring/dashboards.ts — Generate Grafana dashboard JSON for Istio metrics
// This creates a comprehensive service mesh dashboard programmatically

interface Panel {
  title: string;
  type: string;
  gridPos: { h: number; w: number; x: number; y: number };
  targets: Array<{ expr: string; legendFormat: string }>;
}

export function generateMeshDashboard(services: string[]): any {
  const panels: Panel[] = [];
  let yPos = 0;

  // Row 1: Request rate and error rate across all services
  panels.push({
    title: "Request Rate (req/s) — All Services",
    type: "timeseries",
    gridPos: { h: 8, w: 12, x: 0, y: yPos },
    targets: [
      {
        expr: 'sum(rate(istio_requests_total{reporter="destination"}[5m])) by (destination_service_name)',
        legendFormat: "{{destination_service_name}}",
      },
    ],
  });

  panels.push({
    title: "Error Rate (5xx) — All Services",
    type: "timeseries",
    gridPos: { h: 8, w: 12, x: 12, y: yPos },
    targets: [
      {
        expr: 'sum(rate(istio_requests_total{reporter="destination",response_code=~"5.."}[5m])) by (destination_service_name) / sum(rate(istio_requests_total{reporter="destination"}[5m])) by (destination_service_name) * 100',
        legendFormat: "{{destination_service_name}}",
      },
    ],
  });
  yPos += 8;

  // Row 2: Latency percentiles
  panels.push({
    title: "P50 Latency (ms)",
    type: "timeseries",
    gridPos: { h: 8, w: 8, x: 0, y: yPos },
    targets: [
      {
        expr: 'histogram_quantile(0.50, sum(rate(istio_request_duration_milliseconds_bucket{reporter="destination"}[5m])) by (destination_service_name, le))',
        legendFormat: "{{destination_service_name}}",
      },
    ],
  });

  panels.push({
    title: "P95 Latency (ms)",
    type: "timeseries",
    gridPos: { h: 8, w: 8, x: 8, y: yPos },
    targets: [
      {
        expr: 'histogram_quantile(0.95, sum(rate(istio_request_duration_milliseconds_bucket{reporter="destination"}[5m])) by (destination_service_name, le))',
        legendFormat: "{{destination_service_name}}",
      },
    ],
  });

  panels.push({
    title: "P99 Latency (ms)",
    type: "timeseries",
    gridPos: { h: 8, w: 8, x: 16, y: yPos },
    targets: [
      {
        expr: 'histogram_quantile(0.99, sum(rate(istio_request_duration_milliseconds_bucket{reporter="destination"}[5m])) by (destination_service_name, le))',
        legendFormat: "{{destination_service_name}}",
      },
    ],
  });
  yPos += 8;

  // Row 3: Circuit breaker and connection pool metrics
  panels.push({
    title: "Circuit Breaker Ejections",
    type: "timeseries",
    gridPos: { h: 8, w: 12, x: 0, y: yPos },
    targets: [
      {
        expr: 'sum(envoy_cluster_outlier_detection_ejections_active) by (cluster_name)',
        legendFormat: "{{cluster_name}}",
      },
    ],
  });

  panels.push({
    title: "Connection Pool Overflow",
    type: "timeseries",
    gridPos: { h: 8, w: 12, x: 12, y: yPos },
    targets: [
      {
        expr: 'sum(rate(envoy_cluster_upstream_cx_overflow[5m])) by (cluster_name)',
        legendFormat: "{{cluster_name}} overflow",
      },
      {
        expr: 'sum(rate(envoy_cluster_upstream_rq_pending_overflow[5m])) by (cluster_name)',
        legendFormat: "{{cluster_name}} pending overflow",
      },
    ],
  });
  yPos += 8;

  // Row 4: mTLS status
  panels.push({
    title: "mTLS Coverage",
    type: "stat",
    gridPos: { h: 4, w: 6, x: 0, y: yPos },
    targets: [
      {
        expr: 'sum(istio_requests_total{connection_security_policy="mutual_tls"}) / sum(istio_requests_total) * 100',
        legendFormat: "mTLS %",
      },
    ],
  });

  return {
    dashboard: {
      title: "Service Mesh Overview",
      panels,
      templating: {
        list: [
          {
            name: "namespace",
            type: "query",
            query: 'label_values(istio_requests_total, destination_service_namespace)',
          },
        ],
      },
      time: { from: "now-1h", to: "now" },
      refresh: "10s",
    },
  };
}

Step 4: Build an Alerting System for Mesh Health

Automated alerts catch issues before they cascade. The alert rules monitor error rates, latency spikes, and circuit breaker activations — the exact signals that would have caught the $180K incident.

yaml
# istio/prometheus-alerts.yaml — Alert rules for service mesh health
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: istio-mesh-alerts
  namespace: monitoring
spec:
  groups:
    - name: service-mesh-health
      interval: 30s
      rules:
        # High error rate on any service
        - alert: ServiceHighErrorRate
          expr: |
            sum(rate(istio_requests_total{reporter="destination",response_code=~"5.."}[5m])) by (destination_service_name)
            /
            sum(rate(istio_requests_total{reporter="destination"}[5m])) by (destination_service_name)
            > 0.05
          for: 2m
          labels:
            severity: warning
          annotations:
            summary: "{{ $labels.destination_service_name }} error rate above 5%"
            description: "Error rate is {{ $value | humanizePercentage }} for the last 2 minutes"

        # Critical error rate
        - alert: ServiceCriticalErrorRate
          expr: |
            sum(rate(istio_requests_total{reporter="destination",response_code=~"5.."}[2m])) by (destination_service_name)
            /
            sum(rate(istio_requests_total{reporter="destination"}[2m])) by (destination_service_name)
            > 0.20
          for: 1m
          labels:
            severity: critical
          annotations:
            summary: "CRITICAL: {{ $labels.destination_service_name }} error rate above 20%"

        # P99 latency spike
        - alert: ServiceHighLatency
          expr: |
            histogram_quantile(0.99,
              sum(rate(istio_request_duration_milliseconds_bucket{reporter="destination"}[5m]))
              by (destination_service_name, le)
            ) > 2000
          for: 3m
          labels:
            severity: warning
          annotations:
            summary: "{{ $labels.destination_service_name }} P99 latency above 2s"

        # Circuit breaker activations
        - alert: CircuitBreakerActive
          expr: |
            sum(envoy_cluster_outlier_detection_ejections_active) by (cluster_name) > 0
          for: 1m
          labels:
            severity: warning
          annotations:
            summary: "Circuit breaker active for {{ $labels.cluster_name }}"
            description: "Hosts are being ejected — upstream service may be unhealthy"

        # mTLS coverage drop
        - alert: MTLSCoverageDrop
          expr: |
            sum(rate(istio_requests_total{connection_security_policy="mutual_tls"}[10m]))
            /
            sum(rate(istio_requests_total[10m]))
            < 0.95
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "mTLS coverage dropped below 95%"
            description: "Some services may be communicating without encryption"

Results

After deploying Istio across all 24 microservices:

  • Mean time to identify root cause dropped from 4 hours to 12 minutes — Kiali's service graph shows the exact dependency chain and where latency or errors originate; no more guessing with kubectl logs
  • Cascading failures eliminated — circuit breakers ejected the inventory service within 30 seconds during a subsequent database migration, protecting checkout and payments; zero revenue impact vs. $180K previously
  • 100% mTLS coverage achieved — all inter-service traffic encrypted automatically; passed SOC2 audit requirement without any application code changes
  • P99 latency overhead from sidecars: 3ms — well within acceptable range; the improved observability more than compensates
  • Alert response time: under 5 minutes — the team catches error rate spikes and latency degradation before users report issues; on-call engineer gets specific service + endpoint in the alert