Enable Outlier Detection Metrics ​
This tutorial shows how to set up a complete scenario in which outlier detection is configured and its metrics are observed via proxyStatsMatcher. You deploy two namespaces (a target namespace with an HTTP workload and a source namespace with sidecar-injected curl Pods), configure a DestinationRule with outlier detection, trigger ejections, and observe the resulting metrics.
Prerequisites ​
- A running Kubernetes cluster with the Istio module added.
kubectlconfigured to access the cluster.
Context ​
Outlier detection is configured in a DestinationRule on the target host but is evaluated by the source proxy that makes requests. As a result, each source proxy tracks ejection state independently — one proxy may eject a host while another considers it healthy. This is why metrics must be observed on the source proxy, not the target.
When you configure proxyStatsMatcher globally via the Istio CR, all proxies in the mesh — including ingress and egress gateways — receive that matcher configuration. As a result, these proxies can emit matching statistics, while host-specific metrics appear when a given proxy has relevant traffic or state for that destination.
Procedure ​
Enable outlier detection metrics globally.
Configure the Istio CR to emit outlier detection statistics on all proxies in the mesh:
bashkubectl patch istio default -n kyma-system --type=merge -p '{"spec":{"config":{"proxyStatsMatcher":{"inclusionRegexps":[".*outlier_detection.*"]}}}}'Verify the configuration:
bashkubectl get istio default -n kyma-system -o jsonpath='{.spec.config.proxyStatsMatcher.inclusionRegexps}'All proxies in the mesh — sidecars and gateways — will now emit outlier detection metrics.
Prepare namespaces.
Create the target and source namespaces. Enable Istio sidecar injection only on the source:
bashkubectl create ns target kubectl create ns source kubectl label namespace source istio-injection=enabledDeploy the target workload.
Deploy an
httpbinworkload in thetargetnamespace:yamlapiVersion: v1 kind: ServiceAccount metadata: name: httpbin namespace: target --- apiVersion: v1 kind: Service metadata: name: httpbin namespace: target labels: app: httpbin spec: ports: - name: http port: 8000 targetPort: 80 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: target spec: replicas: 1 selector: matchLabels: app: httpbin template: metadata: labels: app: httpbin spec: serviceAccountName: httpbin containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80Configure outlier detection.
Apply a
DestinationRulethat ejects the host after five consecutive 5xx errors within a one-minute interval:yamlapiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: httpbin-policy namespace: target spec: host: httpbin.target.svc.cluster.local trafficPolicy: outlierDetection: consecutive5xxErrors: 5 interval: 1m baseEjectionTime: 5m maxEjectionPercent: 100Deploy source Pods.
Deploy two curl Pods in the
sourcenamespace. SinceproxyStatsMatcheris configured globally, metrics are automatically enabled on all proxies:yamlapiVersion: v1 kind: Pod metadata: name: curl-1 namespace: source labels: app: curl-1 spec: containers: - name: curl image: curlimages/curl command: ["/bin/sleep", "36000"] --- apiVersion: v1 kind: Pod metadata: name: curl-2 namespace: source labels: app: curl-2 spec: containers: - name: curl image: curlimages/curl command: ["/bin/sleep", "36000"]Wait for both Pods to be ready:
bashkubectl wait --for=condition=ready pod -n source curl-1 curl-2Trigger host ejection.
Send five consecutive 5xx responses from
curl-1to exceed the outlier detection threshold:bashfor status in 501 502 503 504 505; do kubectl exec -n source curl-1 -c curl -- curl -s -o /dev/null "http://httpbin.target.svc.cluster.local:8000/status/${status}" doneAfter the threshold is reached, the host is ejected from the load-balancing pool as seen from
curl-1.Observe the ejection.
Send one more request from
curl-1. It fails because the host is ejected:bashkubectl exec -n source curl-1 -c curl -- curl -v "http://httpbin.target.svc.cluster.local:8000/headers"Send the same request from
curl-2. It succeeds becausecurl-2has its own independent ejection state:bashkubectl exec -n source curl-2 -c curl -- curl -v "http://httpbin.target.svc.cluster.local:8000/headers"Inspect outlier detection metrics.
Check the metrics on
curl-1. The active ejection count should be1:bashkubectl exec -n source curl-1 -c istio-proxy -- pilot-agent request GET /stats/prometheus | grep outlier_detectionExpected output:
# TYPE envoy_cluster_outlier_detection_ejections_active gauge envoy_cluster_outlier_detection_ejections_active{cluster_name="outbound|8000||httpbin.target.svc.cluster.local"} 1Check the metrics on
curl-2. The active ejection count should be0because no ejection was triggered from this Pod:bashkubectl exec -n source curl-2 -c istio-proxy -- pilot-agent request GET /stats/prometheus | grep outlier_detectionExpected output:
# TYPE envoy_cluster_outlier_detection_ejections_active gauge envoy_cluster_outlier_detection_ejections_active{cluster_name="outbound|8000||httpbin.target.svc.cluster.local"} 0(Optional) Test outlier detection via the ingress gateway.
When
proxyStatsMatcheris configured globally, it propagates to the ingress gateway and sidecars. The gateway proxy evaluates outlier detection independently — ejection state is local to the proxy that originates the request, so the gateway tracks it separately from any sidecar.Two key properties follow from this:
- Ejection ownership: When requests travel through the ingress gateway to a backend, it is the gateway proxy that records the ejection, not the client that sent traffic to the gateway.
- Per-replica independence: When the gateway is scaled to multiple replicas, each Pod maintains its own ejection state. A replica ejects a backend after receiving enough consecutive errors, without affecting replicas that have not reached the error threshold.
Create a Gateway and VirtualService to route traffic through the ingress:
yamlapiVersion: networking.istio.io/v1 kind: Gateway metadata: name: httpbin-gateway namespace: istio-system spec: selector: istio: ingressgateway servers: - hosts: - 'httpbin.example.com' port: name: http number: 80 protocol: HTTP --- apiVersion: networking.istio.io/v1 kind: VirtualService metadata: name: httpbin namespace: istio-system spec: gateways: - istio-system/httpbin-gateway hosts: - 'httpbin.example.com' http: - route: - destination: host: httpbin.<target-namespace>.svc.cluster.local port: number: 8000Send enough consecutive 5xx requests through the gateway to exceed the ejection threshold:
bashfor i in $(seq 1 5); do kubectl exec -n <source-namespace> <curl-pod> -c curl -- curl -s -o /dev/null \ -H "Host: httpbin.example.com" \ "http://istio-ingressgateway.istio-system.svc.cluster.local/status/500" doneInspect metrics on the ingress gateway Pod. The active ejection count should be
1:bashingress_pod=$(kubectl get pod -n istio-system -l app=istio-ingressgateway -o jsonpath="{.items[0].metadata.name}") kubectl exec -n istio-system "${ingress_pod}" -c istio-proxy -- pilot-agent request GET /stats/prometheus | grep outlier_detectionVerify that the curl Pod's sidecar doesn't report an active ejection. It sent requests to the gateway, not directly to the backend, so it has no ejection state for that cluster:
bashkubectl exec -n <source-namespace> <curl-pod> -c istio-proxy -- pilot-agent request GET /stats/prometheus | grep outlier_detection
Result ​
You have configured proxyStatsMatcher to emit outlier detection metrics and verified that ejection state is tracked independently per source proxy. The curl-1 sidecar shows ejections_active 1, meaning it has ejected the httpbin backend from its upstream load-balancing pool after receiving consecutive errors. The curl-2 sidecar shows ejections_active 0 because it never triggered the ejection threshold - each proxy tracks this state independently.