Repository: istio/istio (go)

Issue:
mergeHostVIPs data race
https://prow.istio.io/view/gs/istio-prow/pr-logs/pull/istio_istio/34189/unit-tests_istio/1435761503490805760

Regression in https://github.com/istio/istio/commit/6577dc91bea0535ff2cfbdcc5e3a33e8c729b9da

Pull request description:
Fixing data race in aggregate controller
The aggregate controller performs a merge of service VIPs when it iterates across the service registries. This logic is protected by a mutex. However, we recently added the copying of `hostname` to this logic as well, which is not protected. This shouldn't be needed, however, since the first service should already have the correct hostname.

Fixes #35079

**Please provide a description of this PR:**

Patch:
diff --git a/galley/pkg/config/source/kube/inmemory/kubesource.go b/galley/pkg/config/source/kube/inmemory/kubesource.go
index b4036ae090..5bb13d110a 100644
--- a/galley/pkg/config/source/kube/inmemory/kubesource.go
+++ b/galley/pkg/config/source/kube/inmemory/kubesource.go
@@ -270,6 +270,7 @@ func (s *KubeSource) parseChunk(r *collection.Schemas, name string, lineNum int,
 	}
 
 	schema, found := r.FindByGroupVersionKind(schemaresource.FromKubernetesGVK(groupVersionKind))
+
 	if !found {
 		return kubeResource{}, &unknownSchemaError{
 			group:   groupVersionKind.Group,
@@ -278,6 +279,20 @@ func (s *KubeSource) parseChunk(r *collection.Schemas, name string, lineNum int,
 		}
 	}
 
+	// Cannot create new instance. This occurs because while newer types do not implement proto.Message,
+	// this legacy code only supports proto.Messages.
+	// Note: while NewInstance can be slightly modified to not return error here, the rest of the code
+	// still requires a proto.Message so it won't work without completely refactoring galley/
+	_, e := schema.Resource().NewInstance()
+	cannotHandleProto := e != nil
+	if cannotHandleProto {
+		return kubeResource{}, &unknownSchemaError{
+			group:   groupVersionKind.Group,
+			version: groupVersionKind.Version,
+			kind:    groupVersionKind.Kind,
+		}
+	}
+
 	t := rt.DefaultProvider().GetAdapter(schema.Resource())
 	obj, err := t.ParseJSON(jsonChunk)
 	if err != nil {
diff --git a/manifests/charts/gateway/Chart.yaml b/manifests/charts/gateway/Chart.yaml
new file mode 100644
index 0000000000..f3db496f6e
--- /dev/null
+++ b/manifests/charts/gateway/Chart.yaml
@@ -0,0 +1,16 @@
+apiVersion: v2
+name: gateway
+description: Helm chart for deploying Istio gateways
+type: application
+
+# This version is never actually shipped. istio/release-builder will replace it at build-time
+# with the appropriate version
+version: 1.0.0
+appVersion: 1.0.0
+
+sources:
+- http://github.com/istio/istio
+icon: https://istio.io/latest/favicons/android-192x192.png
+keywords:
+- istio
+- gateways
\ No newline at end of file
diff --git a/manifests/charts/gateway/README.md b/manifests/charts/gateway/README.md
new file mode 100644
index 0000000000..2aab11e1cf
--- /dev/null
+++ b/manifests/charts/gateway/README.md
@@ -0,0 +1,122 @@
+# Istio Gateway Helm Chart
+
+This chart installs an Istio gateway deployment.
+
+## Setup Repo Info
+
+```console
+helm repo add istio https://istio-release.storage.googleapis.com/charts
+helm repo update
+```
+
+_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._
+
+## Installing the Chart
+
+To install the chart with the release name `istio-ingressgateway`:
+
+```console
+helm install istio-ingressgateway istio/gateway
+```
+
+## Uninstalling the Chart
+
+To uninstall/delete the `istio-ingressgateway` deployment:
+
+```console
+helm delete istio-ingressgateway
+```
+
+## Configuration
+
+To view support configuration options and documentation, run:
+
+```console
+helm show values istio/gateway
+```
+
+### Examples
+
+#### Egress Gateway
+
+Deploying a Gateway to be used as an [Egress Gateway](https://istio.io/latest/docs/tasks/traffic-management/egress/egress-gateway/):
+
+```yaml
+service:
+  # Egress gateways do not need an external LoadBalancer IP
+  type: ClusterIP
+```
+
+#### Multi-network/VM Gateway
+
+Deploying a Gateway to be used as a [Multi-network Gateway](https://istio.io/latest/docs/setup/install/multicluster/) for network `network-1`:
+
+```yaml
+networkGateway: network-1
+```
+
+### Migrating from other installation methods
+
+Installations from other installation methods (such as istioctl, Istio Operator, other helm charts, etc) can be migrated to use the new Helm charts
+following the guidance below.
+If you are able to, a clean installation is simpler. However, this often requires an external IP migration which can be challenging.
+
+WARNING: when installing over an existing deployment, the two deployments will be merged together by Helm, which may lead to unexpected results.
+
+#### General concerns
+
+For a smooth migration, the resource names and `Deployment.spec.selector` labels must match.
+
+If you install with `helm install istio-gateway istio/gateway`, resources will be named `istio-gateway` and the `selector` labels set to:
+
+```yaml
+app: istio-gateway
+istio: gateway # the release name with leading istio- prefix stripped
+```
+
+If your existing installation doesn't follow these names, you can override them. For example, if you have resources named `my-custom-gateway` with `selector` labels
+`foo=bar,istio=ingressgateway`:
+
+```yaml
+name: my-custom-gateway # Override the name to match existing resources
+labels:
+  app: "" # Unset default app selector label
+  istio: ingressgateway # override default istio selector label
+  foo: bar # Add the existing custom selector label
+```
+
+#### Migrating an existing Helm release
+
+An existing helm release can be `helm upgrade`d to this chart by using the same release name. For example, if a previous
+installation was done like:
+
+```console
+helm install istio-ingress manifests/charts/gateways/istio-ingress -n istio-system
+```
+
+It could be upgraded with
+
+```console
+helm upgrade istio-ingress manifests/charts/gateway -n istio-system --set name=istio-ingressgateway --set labels.app=istio-ingressgateway --set labels.istio=ingressgateway
+```
+
+Note the name and labels are overridden to match the names of the existing installation
+
+#### Other migrations
+
+If you see errors like `rendered manifests contain a resource that already exists` during installation, you may need to forcibly take ownership.
+
+The script below can handle this for you. Replace `RELEASE` and `NAMESPACE` with the name and namespace of the release:
+
+```console
+KINDS=(service deployment)
+RELEASE=istio-ingressgateway
+NAMESPACE=istio-system
+for KIND in "${KINDS[@]}"; do
+    kubectl --namespace $NAMESPACE --overwrite=true annotate $KIND $RELEASE meta.helm.sh/release-name=$RELEASE
+    kubectl --namespace $NAMESPACE --overwrite=true annotate $KIND $RELEASE meta.helm.sh/release-namespace=$NAMESPACE
+    kubectl --namespace $NAMESPACE --overwrite=true label $KIND $RELEASE app.kubernetes.io/managed-by=Helm
+done
+```
+
+You may ignore errors about resources not being found.
diff --git a/manifests/charts/gateway/templates/NOTES.txt b/manifests/charts/gateway/templates/NOTES.txt
new file mode 100644
index 0000000000..78451d33eb
--- /dev/null
+++ b/manifests/charts/gateway/templates/NOTES.txt
@@ -0,0 +1,9 @@
+"{{ include "gateway.name" . }}" successfully installed!
+
+To learn more about the release, try:
+  $ helm status {{ .Release.Name }}
+  $ helm get all {{ .Release.Name }}
+
+Next steps:
+  * Deploy an HTTP Gateway: https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/
+  * Deploy an HTTPS Gateway: https://istio.io/latest/docs/tasks/traffic-management/ingress/secure-ingress/
diff --git a/manifests/charts/gateway/templates/_helpers.tpl b/manifests/charts/gateway/templates/_helpers.tpl
new file mode 100644
index 0000000000..e75d27345e
--- /dev/null
+++ b/manifests/charts/gateway/templates/_helpers.tpl
@@ -0,0 +1,52 @@
+{{- define "gateway.name" -}}
+{{- if eq .Release.Name "RELEASE-NAME" -}}
+  {{- .Values.name | default "istio-ingressgateway" -}}
+{{- else -}}
+  {{- .Values.name | default .Release.Name | default "istio-ingressgateway" -}}
+{{- end -}}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "gateway.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{- define "gateway.labels" -}}
+helm.sh/chart: {{ include "gateway.chart" . }}
+{{ include "gateway.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+app.kubernetes.io/name: {{ include "gateway.name" . }}
+{{- range $key, $val := .Values.labels }}
+{{- if not (or (eq $key "app") (eq $key "istio")) }}
+{{ $key | quote }}: {{ $val | quote }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{- define "gateway.selectorLabels" -}}
+{{- if hasKey .Values.labels "app" }}
+{{- with .Values.labels.app }}app: {{.|quote}}
+{{- end}}
+{{- else }}app: {{ include "gateway.name" . }}
+{{- end }}
+{{- if hasKey .Values.labels "istio" }}
+{{- with .Values.labels.istio }}
+istio: {{.|quote}}
+{{- end}}
+{{- else }}
+istio: {{ include "gateway.name" . | trimPrefix "istio-" }}
+{{- end }}
+{{- end }}
+
+{{- define "gateway.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- .Values.serviceAccount.name | default (include "gateway.name" .)    }}
+{{- else }}
+{{- .Values.serviceAccount.name | default "default" }}
+{{- end }}
+{{- end }}
diff --git a/manifests/charts/gateway/templates/deployment.yaml b/manifests/charts/gateway/templates/deployment.yaml
new file mode 100644
index 0000000000..5a6f42175e
--- /dev/null
+++ b/manifests/charts/gateway/templates/deployment.yaml
@@ -0,0 +1,84 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "gateway.name" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "gateway.labels" . | nindent 4}}
+  annotations:
+    {{- .Values.annotations | toYaml | nindent 4 }}
+spec:
+  {{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "gateway.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        sidecar.istio.io/inject: "true"
+        {{- with .Values.revision }}
+        istio.io/rev: {{ . }}
+        {{- end }}
+        {{- include "gateway.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "gateway.serviceAccountName" . }}
+      {{- if not .Values.runAsRoot }}
+      securityContext:
+        runAsUser: 1337
+        runAsGroup: 1337
+        runAsNonRoot: true
+        fsGroup: 1337
+      {{- end }}
+      containers:
+        - name: istio-proxy
+          image: auto
+          {{- if not .Values.runAsRoot }}
+          securityContext:
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop:
+              - ALL
+            privileged: false
+            readOnlyRootFilesystem: true
+          {{- end }}
+          env:
+          {{- if not .Values.runAsRoot }}
+          - name: ISTIO_META_UNPRIVILEGED_POD
+            value: "true"
+          {{- end }}
+          {{- with .Values.networkGateway }}
+          - name: ISTIO_META_REQUESTED_NETWORK_VIEW
+            value: "{{.}}"
+          {{- end }}
+          {{- range $key, $val := .Values.env }}
+          - name: {{ $key }}
+            value: {{ $val }}
+          {{- end }}
+          ports:
+          - containerPort: 15090
+            protocol: TCP
+            name: http-envoy-prom
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/manifests/charts/gateway/templates/hpa.yaml b/manifests/charts/gateway/templates/hpa.yaml
new file mode 100644
index 0000000000..956a5ee505
--- /dev/null
+++ b/manifests/charts/gateway/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta2
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "gateway.name" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "gateway.labels" . | nindent 4 }}
+  annotations:
+    {{- .Values.annotations | toYaml | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "gateway.name" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        target:
+          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+          type: Utilization
+    {{- end }}
+
+{{- end }}
diff --git a/manifests/charts/gateway/templates/role.yaml b/manifests/charts/gateway/templates/role.yaml
new file mode 100644
index 0000000000..3febf79bc4
--- /dev/null
+++ b/manifests/charts/gateway/templates/role.yaml
@@ -0,0 +1,25 @@
+{{/*Set up roles for Istio Gateway. Not required for gateway-api*/}}
+{{- if .Values.rbac.enabled }}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: {{ include "gateway.serviceAccountName" . }}
+  namespace: {{ .Release.Namespace }}
+rules:
+- apiGroups: [""]
+  resources: ["secrets"]
+  verbs: ["get", "watch", "list"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: {{ include "gateway.serviceAccountName" . }}
+  namespace: {{ .Release.Namespace }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: {{ include "gateway.serviceAccountName" . }}
+subjects:
+- kind: ServiceAccount
+  name: {{ include "gateway.serviceAccountName" . }}
+{{- end }}
diff --git a/manifests/charts/gateway/templates/service.yaml b/manifests/charts/gateway/templates/service.yaml
new file mode 100644
index 0000000000..65234604d0
--- /dev/null
+++ b/manifests/charts/gateway/templates/service.yaml
@@ -0,0 +1,45 @@
+{{- if not (eq .Values.service.type "None") }}
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "gateway.name" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "gateway.labels" . | nindent 4 }}
+    {{- with .Values.networkGateway }}
+    topology.istio.io/network: "{{.}}"
+    {{- end }}
+  annotations:
+    {{- .Values.annotations | toYaml | nindent 4 }}
+spec:
+{{- with .Values.service.loadBalancerIP }}
+  loadBalancerIP: "{{ . }}"
+{{- end }}
+{{- with .Values.service.loadBalancerSourceRanges }}
+  loadBalancerSourceRanges:
+{{ toYaml . | indent 4 }}
+{{- end }}
+{{- if .Values.service.externalTrafficPolicy }}
+  externalTrafficPolicy: "{{ . }}"
+{{- end }}
+  type: {{ .Values.service.type }}
+  ports:
+{{- if .Values.networkGateway }}
+  - name: status-port
+    port: 15021
+    targetPort: 15021
+  - name: tls
+    port: 15443
+    targetPort: 15443
+  - name: tls-istiod
+    port: 15012
+    targetPort: 15012
+  - name: tls-webhook
+    port: 15017
+    targetPort: 15017
+{{- else }}
+{{ .Values.service.ports | toYaml | indent 4 }}
+{{- end }}
+  selector:
+    {{- include "gateway.selectorLabels" . | nindent 4 }}
+{{- end }}
\ No newline at end of file
diff --git a/manifests/charts/gateway/templates/serviceaccount.yaml b/manifests/charts/gateway/templates/serviceaccount.yaml
new file mode 100644
index 0000000000..e5b2304d62
--- /dev/null
+++ b/manifests/charts/gateway/templates/serviceaccount.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.serviceAccount.create }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "gateway.serviceAccountName" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "gateway.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/manifests/charts/gateway/values.schema.json b/manifests/charts/gateway/values.schema.json
new file mode 100644
index 0000000000..f7dbe20715
--- /dev/null
+++ b/manifests/charts/gateway/values.schema.json
@@ -0,0 +1,169 @@
+{
+  "$schema": "http://json-schema.org/schema#",
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "affinity": {
+      "type": "object"
+    },
+    "annotations": {
+      "additionalProperties": {
+        "type": [
+          "string",
+          "integer"
+        ]
+      },
+      "type": "object"
+    },
+    "autoscaling": {
+      "type": "object",
+      "properties": {
+        "enabled": {
+          "type": "boolean"
+        },
+        "maxReplicas": {
+          "type": "integer"
+        },
+        "minReplicas": {
+          "type": "integer"
+        },
+        "targetCPUUtilizationPercentage": {
+          "type": "integer"
+        }
+      }
+    },
+    "env": {
+      "type": "array"
+    },
+    "labels": {
+      "type": "object"
+    },
+    "name": {
+      "type": "string"
+    },
+    "nodeSelector": {
+      "type": "object"
+    },
+    "podAnnotations": {
+      "type": "object",
+      "properties": {
+        "inject.istio.io/templates": {
+          "type": "string"
+        },
+        "prometheus.io/path": {
+          "type": "string"
+        },
+        "prometheus.io/port": {
+          "type": "string"
+        },
+        "prometheus.io/scrape": {
+          "type": "string"
+        }
+      }
+    },
+    "replicaCount": {
+      "type": "integer"
+    },
+    "resources": {
+      "type": "object",
+      "properties": {
+        "limits": {
+          "type": "object",
+          "properties": {
+            "cpu": {
+              "type": "string"
+            },
+            "memory": {
+              "type": "string"
+            }
+          }
+        },
+        "requests": {
+          "type": "object",
+          "properties": {
+            "cpu": {
+              "type": "string"
+            },
+            "memory": {
+              "type": "string"
+            }
+          }
+        }
+      }
+    },
+    "revision": {
+      "type": "string"
+    },
+    "runAsRoot": {
+      "type": "boolean"
+    },
+    "service": {
+      "type": "object",
+      "properties": {
+        "annotations": {
+          "type": "object"
+        },
+        "externalTrafficPolicy": {
+          "type": "string"
+        },
+        "loadBalancerIP": {
+          "type": "string"
+        },
+        "loadBalancerSourceRanges": {
+          "type": "array"
+        },
+        "ports": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "port": {
+                "type": "integer"
+              },
+              "protocol": {
+                "type": "string"
+              },
+              "targetPort": {
+                "type": "integer"
+              }
+            }
+          }
+        },
+        "type": {
+          "type": "string"
+        }
+      }
+    },
+    "serviceAccount": {
+      "type": "object",
+      "properties": {
+        "annotations": {
+          "type": "object"
+        },
+        "name": {
+          "type": "string"
+        },
+        "create": {
+          "type": "boolean"
+        }
+      }
+    },
+    "rbac": {
+      "type": "object",
+      "properties": {
+        "enabled": {
+          "type": "boolean"
+        }
+      }
+    },
+    "tolerations": {
+      "type": "array"
+    },
+    "networkGateway": {
+      "type": "string"
+    }
+  }
+}
diff --git a/manifests/charts/gateway/values.yaml b/manifests/charts/gateway/values.yaml
new file mode 100644
index 0000000000..d6bff8a746
--- /dev/null
+++ b/manifests/charts/gateway/values.yaml
@@ -0,0 +1,83 @@
+# Name allows overriding the release name. Generally this should not be set
+name: ""
+# revision declares which revision this gateway is a part of
+revision: ""
+
+replicaCount: 1
+
+rbac:
+  # If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed
+  # when using http://gateway-api.org/.
+  enabled: true
+
+serviceAccount:
+  # If set, a service account will be created. Otherwise, the default is used
+  create: true
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set, the release name is used
+  name: ""
+
+podAnnotations:
+  prometheus.io/port: "15020"
+  prometheus.io/scrape: "true"
+  prometheus.io/path: "/stats/prometheus"
+  inject.istio.io/templates: "gateway"
+  sidecar.istio.io/inject: "true"
+
+# Whether to run the gateway in a privileged container
+runAsRoot: false
+
+service:
+  # Type of service. Set to "None" to disable the service entirely
+  type: LoadBalancer
+  ports:
+  - name: status-port
+    port: 15021
+    protocol: TCP
+    targetPort: 15021
+  - name: http2
+    port: 80
+    protocol: TCP
+    targetPort: 8080
+  - name: https
+    port: 443
+    protocol: TCP
+    targetPort: 8443
+  annotations: {}
+  loadBalancerIP: ""
+  loadBalancerSourceRanges: []
+  externalTrafficPolicy: ""
+
+resources:
+  requests:
+    cpu: 100m
+    memory: 128Mi
+  limits:
+    cpu: 2000m
+    memory: 1024Mi
+
+autoscaling:
+  enabled: true
+  minReplicas: 1
+  maxReplicas: 5
+  targetCPUUtilizationPercentage: 80
+
+# Pod environment variables
+env: []
+
+# Labels to apply to all resources
+labels: {}
+
+# Annotations to apply to all resources
+annotations: {}
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+# If specified, the gateway will act as a network gateway for the given network.
+networkGateway: ""
diff --git a/pilot/pkg/serviceregistry/aggregate/controller.go b/pilot/pkg/serviceregistry/aggregate/controller.go
index c8971ef65e..61ac83eecd 100644
--- a/pilot/pkg/serviceregistry/aggregate/controller.go
+++ b/pilot/pkg/serviceregistry/aggregate/controller.go
@@ -180,9 +180,6 @@ func mergeService(dst, src *model.Service, srcRegistry serviceregistry.Instance)
 
 func mergeHostVIPs(dst, src *model.HostVIPs, srcRegistry serviceregistry.Instance) {
 	// Prefer the k8s HostVIPs where possible
-	if srcRegistry.Provider() == provider.Kubernetes || len(dst.Hostname) == 0 {
-		dst.Hostname = src.Hostname
-	}
 	clusterID := srcRegistry.Cluster()
 	if srcRegistry.Provider() == provider.Kubernetes || len(dst.ClusterVIPs.GetAddressesFor(clusterID)) == 0 {
 		newAddresses := src.ClusterVIPs.GetAddressesFor(clusterID)
diff --git a/pilot/pkg/serviceregistry/kube/controller/endpointslice.go b/pilot/pkg/serviceregistry/kube/controller/endpointslice.go
index 9a04527cfe..8e98a9057e 100644
--- a/pilot/pkg/serviceregistry/kube/controller/endpointslice.go
+++ b/pilot/pkg/serviceregistry/kube/controller/endpointslice.go
@@ -15,15 +15,18 @@
 package controller
 
 import (
+	"fmt"
 	"sync"
 
 	v1 "k8s.io/api/core/v1"
 	discovery "k8s.io/api/discovery/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	klabels "k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/apimachinery/pkg/types"
 	discoverylister "k8s.io/client-go/listers/discovery/v1beta1"
 	"k8s.io/client-go/tools/cache"
+	mcs "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
 
 	"istio.io/api/label"
 	"istio.io/istio/pilot/pkg/model"
@@ -40,6 +43,11 @@ type endpointSliceController struct {
 
 var _ kubeEndpointsController = &endpointSliceController{}
 
+var (
+	endpointSliceRequirements = newEndpointSliceRequirements()
+	endpointSliceSelector     = klabels.NewSelector().Add(endpointSliceRequirements...)
+)
+
 func newEndpointSliceController(c *Controller, informer filter.FilteredSharedIndexInformer) *endpointSliceController {
 	// TODO Endpoints has a special cache, to filter out irrelevant updates to kube-system
 	// Investigate if we need this, or if EndpointSlice is makes this not relevant
@@ -73,14 +81,17 @@ func (esc *endpointSliceController) onEvent(curr interface{}, event model.Event)
 		}
 	}
 
-	return processEndpointEvent(esc.c, esc, ep.Labels[discovery.LabelServiceName], ep.Namespace, event, ep)
+	if endpointSliceSelector.Matches(klabels.Set(ep.Labels)) {
+		return processEndpointEvent(esc.c, esc, ep.Labels[discovery.LabelServiceName], ep.Namespace, event, ep)
+	}
+	return nil
 }
 
 // GetProxyServiceInstances returns service instances co-located with a given proxy
 // TODO: this code does not return k8s service instances when the proxy's IP is a workload entry
 // To tackle this, we need a ip2instance map like what we have in service entry.
 func (esc *endpointSliceController) GetProxyServiceInstances(c *Controller, proxy *model.Proxy) []*model.ServiceInstance {
-	eps, err := discoverylister.NewEndpointSliceLister(esc.informer.GetIndexer()).EndpointSlices(proxy.Metadata.Namespace).List(klabels.Everything())
+	eps, err := discoverylister.NewEndpointSliceLister(esc.informer.GetIndexer()).EndpointSlices(proxy.Metadata.Namespace).List(endpointSliceSelector)
 	if err != nil {
 		log.Errorf("Get endpointslice by index failed: %v", err)
 		return nil
@@ -151,10 +162,10 @@ func (esc *endpointSliceController) forgetEndpoint(endpoint interface{}) []*mode
 			esc.c.pods.endpointDeleted(key, a)
 		}
 	}
-	host, _, _ := esc.getServiceInfo(slice)
+	hostName, _, _ := esc.getServiceInfo(slice)
 	// endpointSlice cache update
-	esc.endpointCache.Delete(host, slice.Name)
-	return esc.endpointCache.Get(host)
+	esc.endpointCache.Delete(hostName, slice.Name)
+	return esc.endpointCache.Get(hostName)
 }
 
 func (esc *endpointSliceController) buildIstioEndpoints(es interface{}, host host.Name) []*model.IstioEndpoint {
@@ -198,7 +209,7 @@ func (esc *endpointSliceController) buildIstioEndpoints(es interface{}, host hos
 }
 
 func (esc *endpointSliceController) buildIstioEndpointsWithService(name, namespace string, host host.Name) []*model.IstioEndpoint {
-	esLabelSelector := klabels.Set(map[string]string{discovery.LabelServiceName: name}).AsSelectorPreValidated()
+	esLabelSelector := endpointSliceSelectorForService(name)
 	slices, err := discoverylister.NewEndpointSliceLister(esc.informer.GetIndexer()).EndpointSlices(namespace).List(esLabelSelector)
 	if err != nil || len(slices) == 0 {
 		log.Debugf("endpoint slices of (%s, %s) not found => error %v", name, namespace, err)
@@ -215,7 +226,7 @@ func (esc *endpointSliceController) getServiceInfo(es interface{}) (host.Name, s
 }
 
 func (esc *endpointSliceController) InstancesByPort(c *Controller, svc *model.Service, reqSvcPort int, labelsList labels.Collection) []*model.ServiceInstance {
-	esLabelSelector := klabels.Set(map[string]string{discovery.LabelServiceName: svc.Attributes.Name}).AsSelectorPreValidated()
+	esLabelSelector := endpointSliceSelectorForService(svc.Attributes.Name)
 	slices, err := discoverylister.NewEndpointSliceLister(esc.informer.GetIndexer()).EndpointSlices(svc.Attributes.Namespace).List(esLabelSelector)
 	if err != nil {
 		log.Infof("get endpoints(%s, %s) => error %v", svc.Attributes.Name, svc.Attributes.Namespace, err)
@@ -374,3 +385,17 @@ func (e *endpointSliceCache) Get(hostname host.Name) []*model.IstioEndpoint {
 	}
 	return endpoints
 }
+
+func newEndpointSliceRequirements() klabels.Requirements {
+	// If the EndpointSlice was generated by a Kubernetes Multi-Cluster Services (MCS) controller, ignore it.
+	// We already read endpoints from each cluster, so it's redundant.
+	out, err := klabels.NewRequirement(mcs.LabelServiceName, selection.DoesNotExist, nil)
+	if err != nil {
+		panic(fmt.Sprintf("failed creating requirements for EndpointSlice: %v", err))
+	}
+	return klabels.Requirements{*out}
+}
+
+func endpointSliceSelectorForService(name string) klabels.Selector {
+	return klabels.Set(map[string]string{discovery.LabelServiceName: name}).AsSelectorPreValidated().Add(endpointSliceRequirements...)
+}
diff --git a/pilot/pkg/serviceregistry/kube/controller/fake.go b/pilot/pkg/serviceregistry/kube/controller/fake.go
index 61243a95a9..f382a48d93 100644
--- a/pilot/pkg/serviceregistry/kube/controller/fake.go
+++ b/pilot/pkg/serviceregistry/kube/controller/fake.go
@@ -108,6 +108,10 @@ func (fx *FakeXdsUpdater) SvcUpdate(_ model.ShardKey, hostname string, _ string,
 }
 
 func (fx *FakeXdsUpdater) Wait(et string) *FakeXdsEvent {
+	return fx.WaitForDuration(et, 5*time.Second)
+}
+
+func (fx *FakeXdsUpdater) WaitForDuration(et string, d time.Duration) *FakeXdsEvent {
 	for {
 		select {
 		case e := <-fx.Events:
@@ -115,7 +119,7 @@ func (fx *FakeXdsUpdater) Wait(et string) *FakeXdsEvent {
 				return &e
 			}
 			continue
-		case <-time.After(5 * time.Second):
+		case <-time.After(d):
 			return nil
 		}
 	}


Test patch:
diff --git a/pilot/pkg/serviceregistry/kube/controller/controller_test.go b/pilot/pkg/serviceregistry/kube/controller/controller_test.go
index d953778753..b72c7a676e 100644
--- a/pilot/pkg/serviceregistry/kube/controller/controller_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/controller_test.go
@@ -124,7 +124,7 @@ func TestServices(t *testing.T) {
 			})
 
 			// 2 ports 1001, 2 IPs
-			createEndpoints(ctl, testService, ns, []string{"http-example", "foo"}, []string{"10.10.1.1", "10.11.1.2"}, nil, t)
+			createEndpoints(t, ctl, testService, ns, []string{"http-example", "foo"}, []string{"10.10.1.1", "10.11.1.2"}, nil, nil)
 
 			svc, err := sds.GetService(hostname)
 			if err != nil {
@@ -349,7 +349,7 @@ func TestGetProxyServiceInstances(t *testing.T) {
 			svc1Ips := []string{"128.0.0.1"}
 			portNames := []string{"tcp-port"}
 			// Create 1 endpoint that refers to a pod in the same namespace.
-			createEndpoints(controller, "svc1", "nsA", portNames, svc1Ips, nil, t)
+			createEndpoints(t, controller, "svc1", "nsA", portNames, svc1Ips, nil, nil)
 
 			// Creates 100 endpoints that refers to a pod in a different namespace.
 			fakeSvcCounts := 100
@@ -363,12 +363,12 @@ func TestGetProxyServiceInstances(t *testing.T) {
 					[]int32{8080}, map[string]string{"app": "prod-app"}, t)
 				fx.Wait("service")
 
-				createEndpoints(controller, svcName, "nsfake", portNames, svc1Ips, nil, t)
+				createEndpoints(t, controller, svcName, "nsfake", portNames, svc1Ips, nil, nil)
 				fx.Wait("eds")
 			}
 
 			// Create 1 endpoint that refers to a pod in the same namespace.
-			createEndpoints(controller, "svc1", "nsa", portNames, svc1Ips, nil, t)
+			createEndpoints(t, controller, "svc1", "nsa", portNames, svc1Ips, nil, nil)
 			fx.Wait("eds")
 
 			// this can test get pod by proxy ID
@@ -810,8 +810,8 @@ func TestController_GetIstioServiceAccounts(t *testing.T) {
 			svc1Ips := []string{"128.0.0.2"}
 			svc2Ips := make([]string, 0)
 			portNames := []string{"tcp-port"}
-			createEndpoints(controller, "svc1", "nsA", portNames, svc1Ips, nil, t)
-			createEndpoints(controller, "svc2", "nsA", portNames, svc2Ips, nil, t)
+			createEndpoints(t, controller, "svc1", "nsA", portNames, svc1Ips, nil, nil)
+			createEndpoints(t, controller, "svc2", "nsA", portNames, svc2Ips, nil, nil)
 
 			// We expect only one EDS update with Endpoints.
 			<-fx.Events
@@ -1472,7 +1472,14 @@ func TestController_ExternalNameService(t *testing.T) {
 	}
 }
 
-func createEndpoints(controller *FakeController, name, namespace string, portNames, ips []string, refs []*coreV1.ObjectReference, t *testing.T) {
+func createEndpoints(t *testing.T, controller *FakeController, name, namespace string,
+	portNames, ips []string, refs []*coreV1.ObjectReference, labels map[string]string) {
+	if labels == nil {
+		labels = make(map[string]string)
+	}
+	// Add the reference to the service. Used by EndpointSlice logic only.
+	labels[discovery.LabelServiceName] = name
+
 	if refs == nil {
 		refs = make([]*coreV1.ObjectReference, len(ips))
 	}
@@ -1491,6 +1498,7 @@ func createEndpoints(controller *FakeController, name, namespace string, portNam
 		ObjectMeta: metaV1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
+			Labels:    labels,
 		},
 		Subsets: []coreV1.EndpointSubset{{
 			Addresses: eas,
@@ -1524,9 +1532,7 @@ func createEndpoints(controller *FakeController, name, namespace string, portNam
 		ObjectMeta: metaV1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
-			Labels: map[string]string{
-				discovery.LabelServiceName: name,
-			},
+			Labels:    labels,
 		},
 		Endpoints: sliceEndpoint,
 		Ports:     esps,
@@ -1849,7 +1855,7 @@ func TestEndpointUpdate(t *testing.T) {
 			svc1Ips := []string{"128.0.0.1"}
 			portNames := []string{"tcp-port"}
 			// Create 1 endpoint that refers to a pod in the same namespace.
-			createEndpoints(controller, "svc1", "nsa", portNames, svc1Ips, nil, t)
+			createEndpoints(t, controller, "svc1", "nsa", portNames, svc1Ips, nil, nil)
 			if ev := fx.Wait("eds"); ev == nil {
 				t.Fatalf("Timeout incremental eds")
 			}
@@ -1935,7 +1941,7 @@ func TestEndpointUpdateBeforePodUpdate(t *testing.T) {
 						})
 					}
 				}
-				createEndpoints(controller, svcName, "nsA", []string{"tcp-port"}, ips, refs, t)
+				createEndpoints(t, controller, svcName, "nsA", []string{"tcp-port"}, ips, refs, nil)
 			}
 			assertEndpointsEvent := func(ips []string, pods []string) {
 				t.Helper()
@@ -2062,7 +2068,7 @@ func TestWorkloadInstanceHandlerMultipleEndpoints(t *testing.T) {
 	}
 	pod1Ips := []string{"172.0.1.1"}
 	portNames := []string{"tcp-port"}
-	createEndpoints(controller, "svc1", "nsA", portNames, pod1Ips, nil, t)
+	createEndpoints(t, controller, "svc1", "nsA", portNames, pod1Ips, nil, nil)
 	if ev := fx.Wait("eds"); ev == nil {
 		t.Fatal("Timeout incremental eds")
 	}
diff --git a/pilot/pkg/serviceregistry/kube/controller/endpointslice_test.go b/pilot/pkg/serviceregistry/kube/controller/endpointslice_test.go
index 6bd87e2561..9c179d477f 100644
--- a/pilot/pkg/serviceregistry/kube/controller/endpointslice_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/endpointslice_test.go
@@ -17,8 +17,14 @@ package controller
 import (
 	"reflect"
 	"testing"
+	"time"
+
+	coreV1 "k8s.io/api/core/v1"
+	mcs "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
 
 	"istio.io/api/label"
+	"istio.io/istio/pilot/pkg/serviceregistry/kube"
+	"istio.io/istio/pkg/config/labels"
 )
 
 func TestGetLocalityFromTopology(t *testing.T) {
@@ -61,3 +67,55 @@ func TestGetLocalityFromTopology(t *testing.T) {
 		})
 	}
 }
+
+func TestEndpointSliceFromMCSShouldBeIgnored(t *testing.T) {
+	const (
+		ns      = "nsa"
+		svcName = "svc1"
+		appName = "prod-app"
+	)
+
+	controller, fx := NewFakeControllerWithOptions(FakeControllerOptions{Mode: EndpointSliceOnly})
+	defer controller.Stop()
+
+	node := generateNode("node1", map[string]string{
+		NodeZoneLabel:              "zone1",
+		NodeRegionLabel:            "region1",
+		label.TopologySubzone.Name: "subzone1",
+	})
+	addNodes(t, controller, node)
+
+	pod := generatePod("128.0.0.1", "pod1", ns, "svcaccount", "node1",
+		map[string]string{"app": appName}, map[string]string{})
+	pods := []*coreV1.Pod{pod}
+	addPods(t, controller, fx, pods...)
+
+	createService(controller, svcName, ns, nil,
+		[]int32{8080}, map[string]string{"app": appName}, t)
+	if ev := fx.Wait("service"); ev == nil {
+		t.Fatal("Timeout creating service")
+	}
+
+	// Ensure that the service is available.
+	hostname := kube.ServiceHostname(svcName, ns, controller.opts.DomainSuffix)
+	svc, err := controller.GetService(hostname)
+	if svc == nil {
+		t.Fatalf("failed to get service: %v", err)
+	}
+
+	// Create an endpoint that indicates it's an MCS endpoint for the service.
+	svc1Ips := []string{"128.0.0.1"}
+	portNames := []string{"tcp-port"}
+	createEndpoints(t, controller, svcName, ns, portNames, svc1Ips, nil, map[string]string{
+		mcs.LabelServiceName: svcName,
+	})
+	if ev := fx.WaitForDuration("eds", 2*time.Second); ev != nil {
+		t.Fatalf("Received unexpected EDS event")
+	}
+
+	// Ensure that getting by port returns no ServiceInstances.
+	instances := controller.InstancesByPort(svc, svc.Ports[0].Port, labels.Collection{})
+	if len(instances) != 0 {
+		t.Fatalf("should be 0 instances: len(instances) = %v", len(instances))
+	}
+}
diff --git a/pilot/pkg/serviceregistry/kube/controller/serviceexportcache_test.go b/pilot/pkg/serviceregistry/kube/controller/serviceexportcache_test.go
index 56366f01e4..5d006be205 100644
--- a/pilot/pkg/serviceregistry/kube/controller/serviceexportcache_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/serviceexportcache_test.go
@@ -109,7 +109,7 @@ func newTestServiceExportCache(t *testing.T, stopCh chan struct{}) *serviceExpor
 	// Create the test service and endpoints.
 	createService(c, serviceExportName, serviceExportNamespace, map[string]string{},
 		[]int32{8080}, map[string]string{"app": "prod-app"}, t)
-	createEndpoints(c, serviceExportName, serviceExportNamespace, []string{"tcp-port"}, []string{serviceExportPodIP}, nil, t)
+	createEndpoints(t, c, serviceExportName, serviceExportNamespace, []string{"tcp-port"}, []string{serviceExportPodIP}, nil, nil)
 
 	ec := c.exports.(*serviceExportCacheImpl)
 
diff --git a/pilot/pkg/serviceregistry/kube/controller/serviceimportcache_test.go b/pilot/pkg/serviceregistry/kube/controller/serviceimportcache_test.go
index 46dfa89948..23f5a4ec41 100644
--- a/pilot/pkg/serviceregistry/kube/controller/serviceimportcache_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/serviceimportcache_test.go
@@ -100,7 +100,7 @@ func newTestServiceImportCache(t *testing.T, stopCh chan struct{}) *serviceImpor
 	// Create the test service and endpoints.
 	createService(c, serviceImportName, serviceImportNamespace, map[string]string{},
 		[]int32{8080}, map[string]string{"app": "prod-app"}, t)
-	createEndpoints(c, serviceImportName, serviceImportNamespace, []string{"tcp-port"}, []string{serviceImportPodIP}, nil, t)
+	createEndpoints(t, c, serviceImportName, serviceImportNamespace, []string{"tcp-port"}, []string{serviceImportPodIP}, nil, nil)
 
 	ic := c.imports.(*serviceImportCacheImpl)
 
diff --git a/tests/integration/pilot/ingress_test.go b/tests/integration/pilot/ingress_test.go
index 98dabdb314..468cd31dee 100644
--- a/tests/integration/pilot/ingress_test.go
+++ b/tests/integration/pilot/ingress_test.go
@@ -779,7 +779,7 @@ gateways:
 				retry.UntilSuccessOrFail(t, func() error {
 					_, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, gatewayNs.Name(), "istio=custom-gateway-helm"))
 					return err
-				}, retry.Timeout(time.Minute*2))
+				}, retry.Timeout(time.Minute*2), retry.Delay(time.Millisecond*500))
 				_ = t.Config().ApplyYAMLNoCleanup(gatewayNs.Name(), fmt.Sprintf(`apiVersion: networking.istio.io/v1alpha3
 kind: Gateway
 metadata:
@@ -818,5 +818,72 @@ spec:
 					Validator: echo.ExpectOK(),
 				})
 			})
+			t.NewSubTest("helm-simple").Run(func(t framework.TestContext) {
+				gatewayNs := namespace.NewOrFail(t, t, namespace.Config{Prefix: "custom-gateway-helm"})
+				d := filepath.Join(t.TempDir(), "gateway-values.yaml")
+				rev := ""
+				if t.Settings().Revisions.Default() != "" {
+					rev = t.Settings().Revisions.Default()
+				}
+				os.WriteFile(d, []byte(fmt.Sprintf(`
+revision: %v
+service:
+  type: ClusterIP # LoadBalancer is slow and not necessary for this tests
+autoscaling:
+  enabled: false
+resources:
+  requests:
+    cpu: 10m
+    memory: 40Mi
+`, rev)), 0o644)
+				cs := t.Clusters().Default().(*kubecluster.Cluster)
+				h := helm.New(cs.Filename())
+				// Install ingress gateway chart
+				if err := h.InstallChart("helm-simple", filepath.Join(env.IstioSrc, "manifests/charts/gateway"), gatewayNs.Name(),
+					d, helmtest.Timeout); err != nil {
+					t.Fatal(err)
+				}
+				retry.UntilSuccessOrFail(t, func() error {
+					_, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, gatewayNs.Name(), "istio=helm-simple"))
+					return err
+				}, retry.Timeout(time.Minute*2), retry.Delay(time.Millisecond*500))
+				_ = t.Config().ApplyYAMLNoCleanup(gatewayNs.Name(), fmt.Sprintf(`apiVersion: networking.istio.io/v1alpha3
+kind: Gateway
+metadata:
+  name: app
+spec:
+  selector:
+    istio: helm-simple
+  servers:
+  - port:
+      number: 80
+      name: http
+      protocol: HTTP
+    hosts:
+    - "*"
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: app
+spec:
+  hosts:
+  - "*"
+  gateways:
+  - app
+  http:
+  - route:
+    - destination:
+        host: %s
+        port:
+          number: 80
+`, apps.PodA[0].Config().FQDN()))
+				apps.PodB[0].CallWithRetryOrFail(t, echo.CallOptions{
+					Port:      &echo.Port{ServicePort: 80},
+					Scheme:    scheme.HTTP,
+					Address:   fmt.Sprintf("helm-simple.%s.svc.cluster.local", gatewayNs.Name()),
+					Validator: echo.ExpectOK(),
+				})
+			})
 		})
 }