Istio’s sidecar proxies, while essential for its powerful features, can become a significant bottleneck, consuming valuable CPU and memory and adding latency to every request.

Let’s see what that looks like in practice. Imagine a simple service productpage that calls details.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: details
        port:
          number: 9080

When a request hits productpage, it first goes to the productpage sidecar (Envoy proxy), then to the productpage application, then back to the productpage sidecar, then to the details sidecar, then to the details application, and finally back through the same proxy chain. Each hop adds overhead.

The core problem Istio solves is decoupling network concerns from application logic. Instead of each microservice needing to implement retry logic, circuit breaking, mTLS, tracing, and traffic management, these capabilities are offloaded to the Envoy sidecar. This allows developers to focus on business logic while operations teams manage the network behavior.

Internally, Envoy operates as a high-performance, asynchronous network proxy. When a request arrives at a pod, it’s intercepted by iptables rules injected by the Istio sidecar. These rules redirect ingress and egress traffic to the Envoy proxy running in the same pod. Envoy then applies the configured policies (e.g., routing, security, telemetry) before forwarding the request to the actual application container or out to another service’s sidecar.

The primary levers you have for tuning are within the Istio Operator configuration and the Envoy configuration itself, which can be influenced by Istio resources.

Istio Operator Configuration:

  • Sidecar Resource Limits: This is your first line of defense. By default, Istio often doesn’t set explicit resource requests and limits for the sidecar, allowing Kubernetes to assign arbitrary resources or relying on the node’s capacity. This can lead to resource contention.

    To fix this, you can define specific resources for the istio-proxy container in your IstioOperator custom resource:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      components:
        pilot:
          k8s:
            env:
            - name: PILOT_SIDECACHE_ENABLED
              value: "true" # Enable sidecar proxy caching for config
        sidecarInjector:
          k8s:
            resources:
              requests:
                cpu: "50m"
                memory: "100Mi"
              limits:
                cpu: "200m"
                memory: "256Mi"
    

    This tells Kubernetes to allocate at least 50m CPU and 100Mi memory to the sidecar, and not let it exceed 200m CPU and 256Mi memory. This prevents the sidecar from starving the application and ensures predictable resource usage.

  • PILOT_SIDECACHE_ENABLED: Set this to true in the Pilot component’s environment variables in your IstioOperator. By default, Pilot may not aggressively cache generated Envoy configurations. Enabling this allows Pilot to cache these configurations, reducing the CPU load on Pilot itself and speeding up the delivery of configuration updates to sidecars. This means sidecars spend less time waiting for configuration and more time processing traffic.

Envoy Configuration (Influenced by Istio Resources):

  • Stat Filter Configuration: Envoy generates a lot of metrics by default. While useful, this can add CPU overhead. You can selectively disable or reduce the collection of certain stats.

    This is controlled via the proxy.istio.io/config annotation on your Pods or through ProxyConfig resources. For instance, to disable stats collection for ingress listeners:

    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app-pod
      annotations:
        proxy.istio.io/config: '{"proxyMetadata":{"statsFilter": "ingress-listener-stats-off"}}'
    spec:
      containers:
      - name: my-app
        image: my-app-image
    

    This reduces the number of internal Envoy operations related to stat collection, freeing up CPU cycles for request processing.

  • Connection Pool Settings: For services with high concurrency, tuning connection pool settings can be critical. By default, Envoy might be too aggressive or too conservative with its connection pooling.

    You can override these defaults using DestinationRule resources. For example, to increase the maximum connections per host and per outbound cluster:

    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: details-dr
    spec:
      host: details
      trafficPolicy:
        connectionPool:
          tcp:
            maxConnections: 1000 # Increased from default of 100
          http:
            http1MaxPendingRequests: 100 # Increased from default of 10
            maxRequestsPerConnection: 100 # Increased from default of 1
    

    Increasing maxConnections and maxRequestsPerConnection allows Envoy to handle more concurrent requests to a single upstream service instance without establishing new connections for every request, reducing connection setup overhead and improving throughput.

  • ISTIO_META_RBAC_ENABLED Environment Variable: In your istio-proxy container’s environment variables within the IstioOperator or pod template, you can set ISTIO_META_RBAC_ENABLED to false if you are not using Istio’s RBAC (AuthorizationPolicy) features.

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          proxy:
            env:
            - name: ISTIO_META_RBAC_ENABLED
              value: "false"
    

    Disabling RBAC means Envoy doesn’t perform authorization checks for every request, reducing latency and CPU usage for requests that would otherwise pass authorization.

  • ISTIO_META_TLS_MODE Environment Variable: If you are not using mTLS for all services (e.g., using permissive mode or disabling mTLS where it’s not strictly necessary), ensure ISTIO_META_TLS_MODE is set appropriately. For permissive mode, it’s PERMISSIVE. If you’ve selectively disabled mTLS, you might need to tune specific DestinationRule tls settings. However, if you’ve disabled it entirely for a specific service (e.g., tls.mode: ISTIO_MUTUAL is removed from DestinationRule), the sidecar might still perform some TLS handshake overhead checks. Ensuring it’s truly off where not needed is key.

    You can control this at the pod level via annotations, or more globally via IstioOperator:

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      values:
        global:
          meshConfig:
            defaultConfig:
              proxyMetadata:
                ISTIO_META_TLS_MODE: "PERMISSIVE" # Or NONE if you've disabled it everywhere
    

    Setting ISTIO_META_TLS_MODE to PERMISSIVE (or NONE if you have explicitly disabled mTLS in DestinationRules or via meshConfig) prevents Envoy from attempting or enforcing mTLS handshakes when they are not required, directly reducing latency and CPU load associated with TLS operations.

The most subtle performance impact comes from the default behavior of Envoy’s connection pooling for HTTP/1.1. By default, Envoy often creates a new connection for each request to an upstream host (maxRequestsPerConnection: 1). This is a conservative setting to prevent a single lingering connection from blocking a thread, but for high-throughput scenarios, it means a massive amount of connection setup and teardown overhead. You’ll see this reflected in high http1_cx_total and http1_cx_destroy metrics in Envoy’s stats.

If you fix all of the above, you’ll likely start seeing issues with OutOfMemory errors on your application pods as the sidecar’s resource limits become more constrictive.

Want structured learning?

Take the full Istio course →