istio icon indicating copy to clipboard operation
istio copied to clipboard

Istio Creates Unnecessary Envoy Clusters

Open wentwog opened this issue 2 years ago • 17 comments

Is this the right place to submit this?

  • [X] This is not a security vulnerability or a crashing bug
  • [X] This is not a question about how to use Istio

Bug Description

Overview

It looks that under certain circumstances Istio creates Envoy clusters that are never used. In our setup that leads to thousands of unused clusters in the workload's sidecar Envoy config and in egress and ingress gateways. By the workloads I mean business services as opposed to Mesh platform components.

We configure Istio to route business services' calls to Mesh-external services via Egress Gateway. Also, the business services in some cases use virtual DNS names of the external services, while EGW uses their real names (we actually do that for databases, caches, etc, to hide the real DNS names from the business services to be able to substitute the real ones when needed). We use Istio's Sidecar CRD to limit to what external services a business service has the access. Despite that, thousands of unused Envoy clusters are created in the business service's Istio sidecar config.

For EGW the Sidecar CRD is not applicable, as per its docs. Is there any other way to limit what it can call? Otherwise its Envoy config also contains thousands of excess clusters. I suspect the hosts a gateway can ever call can be determined from applicable routing configurations - Virtual Services. Instead of creating clusters for all k8s Services and Service Entries Istio finds in namespaces it monitors.

We suspect the excess clusters may lead to problems

  • sometimes when we change external or business services config, which results in creating / replacing relevant k8s resources, the business services experience connection terminations. E.g. if we add a new external service then many business services, that don't even use the added ext. service, experience their connections to external services they use being closed in the middle of a transaction.
  • excess memory / CPU consumption

About the scale of our setup - we have ~500-1000 external services, for all of them the same fleet of egress gateway pods is used. And we have ~700 business services.

Steps to Reproduce

E.g. let's

  • deploy service-a in busines-services namespace.
  • 3 external services into external-services namespace
    • www.cnn.com - service-a will call a virtual host cnn.virtual while EGW will call the real host. The traffic is routed through EGW port 80. The flow is service-a (http) -> envoy-sidecar (wraps with Istio mTLS) -> EGW (unwraps the mTLS, originates https, Simple) -> real host.
    • www.bloomberg.com and www.nytimes.com - to show different types of excess clusters let's not use virtual names here, and the EGW port will be 443, and the flow will be app (https) -> envoy-sidecar (as is) -> EGW (as is) -> real host
  • via Sidecar CRD give service-a an access to cnn.virtual and www.bloomberg.com.

Install Istio Base, IstioD, and EGW via for-github--install-istio.txt Install service-a via for-github-business-services.txt, and and external services via for-github-ext-services.txt.

You should get following clusters in service-a Envoy:

istioctl-1-18-0 proxy-config clusters "$(getNewestPod service-a)" -o json

SERVICE FQDN                                                     PORT     SUBSET                                           DIRECTION     TYPE             DESTINATION RULE
                                                                 80       -                                                inbound       ORIGINAL_DST     
                                                                 8081     -                                                inbound       ORIGINAL_DST     
BlackHoleCluster                                                 -        -                                                -             STATIC           
InboundPassthroughClusterIpv4                                    -        -                                                -             ORIGINAL_DST     
PassthroughCluster                                               -        -                                                -             ORIGINAL_DST     
agent                                                            -        -                                                -             STATIC           
cnn.virtual                                                      80       -                                                outbound      EDS              
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80       -                                                outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443      -                                                outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80       https-passthrough-subset-bloomberg-1-18-0-n1     outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443      https-passthrough-subset-bloomberg-1-18-0-n1     outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80       https-passthrough-subset-nytimes-1-18-0-n1       outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443      https-passthrough-subset-nytimes-1-18-0-n1       outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80       https-subset-mtls-cnn-1-18-0-n1                  outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443      https-subset-mtls-cnn-1-18-0-n1                  outbound      EDS              bloomberg-https-passthrough-1-18-0-n1.istio-system
prometheus_stats                                                 -        -                                                -             STATIC           
sds-grpc                                                         -        -                                                -             STATIC           
www.bloomberg.com                                                443      -                                                outbound      STRICT_DNS       
www.cnn.com                                                      443      -                                                outbound      STRICT_DNS       cnn-egress-https.external-services
xds-grpc                                                         -        -                                                -             STATIC           
zipkin                                                           -        -                                                -             STRICT_DNS       

Excess cluster types:

  1. Too many clusters to istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local while only the one with port 80, subset https-subset-mtls-cnn-1-18-0-n1 is used. Note how unrelated 443 port came to cnn, and 80 to bloomberg and nytimes.

  2. Clusters to real and virtual DNS names - cnn.virtual, www.cnn.com, and www.bloomberg.com - are excess because the business service never calls them directly, its Istio proxy redirects such calls to EGW.

In EGW you should get following clusters:

istioctl-1-18-0 proxy-config clusters "$(getNewestEgwPod)" -n istio-system

SERVICE FQDN                                                     PORT      SUBSET                                           DIRECTION     TYPE           DESTINATION RULE
BlackHoleCluster                                                 -         -                                                -             STATIC         
agent                                                            -         -                                                -             STATIC         
cnn.virtual                                                      80        -                                                outbound      EDS            
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80        -                                                outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443       -                                                outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     15021     -                                                outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80        https-passthrough-subset-bloomberg-1-18-0-n1     outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443       https-passthrough-subset-bloomberg-1-18-0-n1     outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     15021     https-passthrough-subset-bloomberg-1-18-0-n1     outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80        https-passthrough-subset-nytimes-1-18-0-n1       outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443       https-passthrough-subset-nytimes-1-18-0-n1       outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     15021     https-passthrough-subset-nytimes-1-18-0-n1       outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     80        https-subset-mtls-cnn-1-18-0-n1                  outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     443       https-subset-mtls-cnn-1-18-0-n1                  outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local     15021     https-subset-mtls-cnn-1-18-0-n1                  outbound      EDS            bloomberg-https-passthrough-1-18-0-n1.istio-system
istiod-1-18-0-n1.istio-system.svc.cluster.local                  443       -                                                outbound      EDS            
istiod-1-18-0-n1.istio-system.svc.cluster.local                  15010     -                                                outbound      EDS            
istiod-1-18-0-n1.istio-system.svc.cluster.local                  15012     -                                                outbound      EDS            
istiod-1-18-0-n1.istio-system.svc.cluster.local                  15014     -                                                outbound      EDS            
kube-dns.kube-system.svc.cluster.local                           53        -                                                outbound      EDS            
kube-dns.kube-system.svc.cluster.local                           9153      -                                                outbound      EDS            
kubernetes.default.svc.cluster.local                             443       -                                                outbound      EDS            
metrics-server.kube-system.svc.cluster.local                     443       -                                                outbound      EDS            
prometheus_stats                                                 -         -                                                -             STATIC         
sds-grpc                                                         -         -                                                -             STATIC         
service-a.business-services.svc.cluster.local                    80        -                                                outbound      EDS            
service-a.business-services.svc.cluster.local                    8081      -                                                outbound      EDS            
www.bloomberg.com                                                443       -                                                outbound      STRICT_DNS     
www.cnn.com                                                      443       -                                                outbound      STRICT_DNS     cnn-egress-https.external-services
www.nytimes.com                                                  443       -                                                outbound      STRICT_DNS     
xds-grpc                                                         -         -                                                -             STATIC         
zipkin                                                           -         -                                                -             STRICT_DNS     

Excess cluster types:

  1. to virtual DNS names (cnn.virtual). EGW calls only real ones.
  2. to EGW itself
  3. to the business services - e.g. service-a.business-services.svc.cluster.local - EGW never calls them, it's only for proxying business services' calls to Mesh-external services.

Version

istioctl-1-18-0 version
client version: 1.18.0
control plane version: 1.18.0
data plane version: 1.18.0 (2 proxies)

kubectl version --short
Client Version: v1.25.4
Kustomize Version: v4.5.7
Server Version: v1.24.15+k3s1

helm version --short
v3.6.3+gd506314

Additional Information

Note - below you will see errors like "Referenced selector not found" but they doesn't seem correct, you should see the labels if you do like

kubectl describe pod istio-egressgateway-1-18-0-n1-7d9999f94b-2kx8n -n istio-system | grep 'istio=egressgateway' --color -C 10

And if you change the Sidecars and Gateway resources you will see the changes are applied to the relevant Envoys. So the selectors work.

Target cluster context: default

Running with the following config: 

istio-namespace: istio-system
full-secrets: false
timeout (mins): 30
include: {  }
exclude: { Namespaces: kube-node-lease,kube-public,kube-system,local-path-storage }
end-time: 2023-07-11 23:07:41.751426024 +0200 CEST



Cluster endpoint: https://127.0.0.1:6443
CLI version:
version.BuildInfo{Version:"1.18.0", GitRevision:"697f4ff0fe6ee531bbd4f5f2a6b4b1f302c955a8", GolangVersion:"go1.20.4", BuildStatus:"Clean", GitTag:"1.18.0"}

The following Istio control plane revisions/versions were found in the cluster:
Revision 1-18-0-n1:
&version.MeshInfo{
    {
        Component: "istiod",
        Info:      version.BuildInfo{Version:"1.18.0", GitRevision:"697f4ff0fe6ee531bbd4f5f2a6b4b1f302c955a8", GolangVersion:"", BuildStatus:"Clean", GitTag:"1.18.0"},
    },
}

The following proxy revisions/versions were found in the cluster:
Revision 1-18-0-n1: Versions {1.18.0}


Fetching proxy logs for the following containers:

business-services/service-a/service-a-7d4dd5b76c-4hmz7/istio-proxy
business-services/service-a/service-a-7d4dd5b76c-4hmz7/main
istio-system/istio-egressgateway-1-18-0-n1/istio-egressgateway-1-18-0-n1-7d9999f94b-2kx8n/istio-proxy
istio-system/istiod-1-18-0-n1/istiod-1-18-0-n1-6689c65986-49s54/discovery

Fetching Istio control plane information from cluster.

Running istio analyze on all namespaces and report as below:
Analysis Report:
Error [IST0101] (Gateway external-services/egress-https-passthrough) Referenced selector not found: "istio=egressgateway"
Error [IST0101] (Gateway external-services/egress-https) Referenced selector not found: "istio=egressgateway"
Error [IST0101] (Sidecar business-services/service-a) Referenced selector not found: "app.kubernetes.io/name=service-a"
Warning [IST0146] (Deployment istio-system/istio-egressgateway-1-18-0-n1) Deployment istio-egressgateway-1-18-0-n1 contains `image: auto` but does not match any Istio injection webhook selectors.
Info [IST0102] (Namespace business-services) The namespace is not enabled for Istio injection. Run 'kubectl label namespace business-services istio-injection=enabled' to enable it, or 'kubectl label namespace business-services istio-injection=disabled' to explicitly mark it as not needing injection.
Info [IST0102] (Namespace default) The namespace is not enabled for Istio injection. Run 'kubectl label namespace default istio-injection=enabled' to enable it, or 'kubectl label namespace default istio-injection=disabled' to explicitly mark it as not needing injection.
Info [IST0102] (Namespace external-services) The namespace is not enabled for Istio injection. Run 'kubectl label namespace external-services istio-injection=enabled' to enable it, or 'kubectl label namespace external-services istio-injection=disabled' to explicitly mark it as not needing injection.
Info [IST0102] (Namespace upwork-services) The namespace is not enabled for Istio injection. Run 'kubectl label namespace upwork-services istio-injection=enabled' to enable it, or 'kubectl label namespace upwork-services istio-injection=disabled' to explicitly mark it as not needing injection.
Creating an archive at /home/[email protected]/code/_pleng/pleng-mesh-setup/misc/scripts/local_testing_kind/bug-report.tar.gz.
Cleaning up temporary files in /tmp/bug-report.
Done.

Affected product area

  • [ ] Ambient
  • [ ] Docs
  • [ ] Installation
  • [X] Networking
  • [X] Performance and Scalability
  • [ ] Extensions and Telemetry
  • [ ] Security
  • [ ] Test and Release
  • [ ] User Experience
  • [ ] Developer Infrastructure
  • [ ] Upgrade
  • [ ] Multi Cluster
  • [ ] Virtual Machine
  • [ ] Control Plane Revisions

wentwog avatar Jul 12 '23 15:07 wentwog

This is mostly implemented... but not turned on my default due to some bugs that can cause outages. See https://github.com/istio/istio/issues/29131 for more info

howardjohn avatar Jul 12 '23 16:07 howardjohn

@howardjohn I see that one mentions PILOT_FILTER_GATEWAY_CLUSTER_CONFIG - will it address only the gateways, or also the Envoys of the regular services?

wentwog avatar Jul 12 '23 21:07 wentwog

Only gateways, sorry I skimmed through this too quickly. The sidecar cases are valid, and can be implemented in a similar manner as PILOT_FILTER_GATEWAY_CLUSTER_CONFIG I think. However, they would have the same issue that current flag has so we would need to address that still

howardjohn avatar Jul 12 '23 21:07 howardjohn

Let me fix it once i have bandwidth

hzxuzhonghu avatar Jul 13 '23 04:07 hzxuzhonghu

Thanks.

Also - @howardjohn , @hzxuzhonghu - do you guys think the excess clusters could be the cause of the connection terminations mentioned?

sometimes when we change external or business services config, which results in creating / replacing relevant k8s resources, the business services experience connection terminations. E.g. if we add a new external service then many business services, that don't even use the added ext. service, experience their connections to external services they use being closed in the middle of a transaction.

wentwog avatar Jul 13 '23 16:07 wentwog

connection termination can be caused by xds change, we call listener drain. But excess clusters seems unrelated, you can check whether listener configuration updated

hzxuzhonghu avatar Jul 14 '23 08:07 hzxuzhonghu

Is it related to https://github.com/istio/istio/issues/42304 We have a working solution see https://github.com/istio/istio/pull/42401 @howardjohn sny ideas?

shivanshuraj1333 avatar Jul 26 '23 04:07 shivanshuraj1333

I don't think its related, this is about destination rule subsets which are not referenced. In sidecar, not gateway

howardjohn avatar Jul 26 '23 22:07 howardjohn

Just my 5 cents - this ticket shows excess clusters are present both in the sidecars and gateways. And not only due to destination rule subsets, e.g. take a look at an excess cluster from service-a to www.cnn.com while there is no subset in the destination rule of www.cnn.com

wentwog avatar Jul 27 '23 12:07 wentwog

The virtual service part could probably be improved fairly easily with a PR. But I bet if you split it manually like:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: cnn-https
  namespace: external-services
spec:
  hosts:
    - cnn.virtual
  gateways:
    - mesh
  http:
    - match:
        - port: 80
      route:
        - destination:
            host: istio-egressgateway-1-18-0-n1.istio-system.svc.cluster.local
            subset: https-subset-mtls-cnn-1-18-0-n1
            port:
              number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: cnn-https
  namespace: external-services
spec:
  hosts:
    - cnn.virtual
  gateways:
    - egress-https
  http:
    - match:
        - port: 80
      rewrite:
        authority: "www.cnn.com"
      route:
        - destination:
            host: "www.cnn.com"
            port:
              number: 443

it will work today.

IMO the "2 virtual services in one" pattern is bad. I know we document it, so totally understand why its used -- but I don't think its good personally

howardjohn avatar Jul 27 '23 16:07 howardjohn

no stale

hzxuzhonghu avatar Feb 17 '24 03:02 hzxuzhonghu

Anything new about this? This is a big problem for us and we cannot really use an Egress due to this.

pparth avatar Apr 19 '24 10:04 pparth

Anything new about this? This is a big problem for us and we cannot really use an Egress due to this.

+1

gunaganji avatar Apr 22 '24 06:04 gunaganji

the only workaround that I found for trimming down the egress configuration on workload's Envoys is as follows:

  • create a new empty DestinationRule for myapp's egress traffic, using workloadSelector to bind this to this particular app:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp-egress-{{ $istio.revision }}
  namespace: myapp-namespace
  labels:
    istio.io/rev: {{ $istio.revision }}
spec:
  host: istio-egressgateway-{{ $istio.revision }}.istio-system.svc.cluster.local
  workloadSelector:
    matchLabels:
      app: myapp
  subsets: [] # to be populated at runtime
  • copy the subsets from the virtualServices DestinationRules, for each external dependency that myapp needs.

Obviously this breaks orthogonality: if you talk to an external API from 100 microservices, you need 100 redeployments if you want to change anything on the egress configs.

ekarak-upwork avatar Apr 22 '24 08:04 ekarak-upwork

possible root cause for this issue is described in https://github.com/istio/istio/issues/44366

I did some investigation on the code base and here is the root cause: https://github.com/istio/istio/blob/master/pilot/pkg/model/push_context.go#L1766 in this function, we will merge subsets from multiple destination rules with same namespace and same host, while for exportTo, we always take the oldest DR from the merged DR list.

ekarak-upwork avatar Apr 22 '24 09:04 ekarak-upwork

This also has a massive hit on the Metrics Envoy ends up emitting, mostly worthless. Is there anyway that can be stemmed separate from Envoy's config?

sidewinder12s avatar May 03 '24 19:05 sidewinder12s

🧭 This issue or pull request has been automatically marked as stale because it has not had activity from an Istio team member since 2024-02-17. It will be closed on 2024-06-01 unless an Istio team member takes action. Please see this wiki page for more information. Thank you for your contributions.

Created by the issue and PR lifecycle manager.

istio-policy-bot avatar May 17 '24 05:05 istio-policy-bot