diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml index 43dfe8172134..e7779bc988f2 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml @@ -114,6 +114,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml index a2705fa4c09d..7b0b3eb831e6 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml @@ -5086,6 +5086,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string @@ -8394,6 +8399,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml index eca3466303ba..decc651d84d9 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml @@ -5086,6 +5086,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string @@ -8394,6 +8399,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml index 4aae865cb696..3d04dca742b6 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.global.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml index 064b42136f8e..f5e91c6dfd1d 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.override-env-vars.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml index 428a17d157c3..bf50070ce020 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.overrides.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml index 846019cf9d5a..1176dc5268fc 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.registry.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml index 8a79a6ba3fdb..0a874ed66df9 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.tproxy-ebpf-experimental-enabled.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml index 377916de4c1c..da2a6ccd6f52 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-egress.golden.yaml @@ -69,6 +69,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml index e0ff2c0fc9da..1f2ae10ea1e2 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml @@ -5106,6 +5106,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string @@ -8414,6 +8419,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml index 8a56cbdbd966..290cd18aa5c1 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-values.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml index a53eab548a46..1a872b0a95a0 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-ingress.golden.yaml @@ -69,6 +69,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml index 8772ac115698..1fc670c3a592 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.zone.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml index 46bb40590d03..e97faf37fc75 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/doNotChangeTlsChecksum.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml index ee1358e44647..3c9309046779 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/empty.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml index 94f7d6856bb8..8e4a063eda3e 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4485.golden.yaml @@ -76,6 +76,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml index 25e6144457ba..0b78ef2e9de9 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4496.golden.yaml @@ -73,6 +73,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml index e7c617b0f66a..cc3ed99e51ba 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix4935.golden.yaml @@ -134,6 +134,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml index d5d490a8d16e..982e4995f352 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix5978.golden.yaml @@ -79,6 +79,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml index 90276104b4b0..02b1736634cd 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7724.golden.yaml @@ -62,6 +62,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml index dad9023cf8fd..94b77b464313 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/fix7824.golden.yaml @@ -94,6 +94,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml index c49d97d8464e..f8393084f568 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/minReadySeconds.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml b/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml index 2f79ce7b8f84..eebb34379294 100644 --- a/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-cp-helm/securityContext.golden.yaml @@ -59,6 +59,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml index 78760f2d8be0..c3da687945b9 100644 --- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml @@ -6518,6 +6518,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string diff --git a/deployments/charts/kuma/crds/kuma.io_meshservices.yaml b/deployments/charts/kuma/crds/kuma.io_meshservices.yaml index 535af7054889..3e1f5f86c68e 100644 --- a/deployments/charts/kuma/crds/kuma.io_meshservices.yaml +++ b/deployments/charts/kuma/crds/kuma.io_meshservices.yaml @@ -65,6 +65,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string diff --git a/deployments/charts/kuma/templates/cp-rbac.yaml b/deployments/charts/kuma/templates/cp-rbac.yaml index 60da7d01bae5..2c0145f0c164 100644 --- a/deployments/charts/kuma/templates/cp-rbac.yaml +++ b/deployments/charts/kuma/templates/cp-rbac.yaml @@ -43,6 +43,14 @@ rules: verbs: - list - watch + - apiGroups: + - "discovery.k8s.io" + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - "apps" resources: diff --git a/docs/generated/raw/crds/kuma.io_meshservices.yaml b/docs/generated/raw/crds/kuma.io_meshservices.yaml index 535af7054889..3e1f5f86c68e 100644 --- a/docs/generated/raw/crds/kuma.io_meshservices.yaml +++ b/docs/generated/raw/crds/kuma.io_meshservices.yaml @@ -65,6 +65,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string diff --git a/pkg/core/resources/apis/meshservice/api/v1alpha1/meshservice.go b/pkg/core/resources/apis/meshservice/api/v1alpha1/meshservice.go index 5973062a593e..4054ce275aa8 100644 --- a/pkg/core/resources/apis/meshservice/api/v1alpha1/meshservice.go +++ b/pkg/core/resources/apis/meshservice/api/v1alpha1/meshservice.go @@ -11,6 +11,11 @@ type DataplaneTags map[string]string type Selector struct { DataplaneTags DataplaneTags `json:"dataplaneTags,omitempty"` + DataplaneRef *DataplaneRef `json:"dataplaneRef,omitempty"` +} + +type DataplaneRef struct { + Name string `json:"name,omitempty"` } type Port struct { diff --git a/pkg/core/resources/apis/meshservice/api/v1alpha1/schema.yaml b/pkg/core/resources/apis/meshservice/api/v1alpha1/schema.yaml index 315decb779a0..4df6fe4e83d3 100644 --- a/pkg/core/resources/apis/meshservice/api/v1alpha1/schema.yaml +++ b/pkg/core/resources/apis/meshservice/api/v1alpha1/schema.yaml @@ -40,6 +40,11 @@ properties: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string diff --git a/pkg/core/resources/apis/meshservice/api/v1alpha1/zz_generated.deepcopy.go b/pkg/core/resources/apis/meshservice/api/v1alpha1/zz_generated.deepcopy.go index 5d3ba1654cb3..30a373975e4a 100644 --- a/pkg/core/resources/apis/meshservice/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/core/resources/apis/meshservice/api/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,21 @@ func (in *Address) DeepCopy() *Address { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataplaneRef) DeepCopyInto(out *DataplaneRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataplaneRef. +func (in *DataplaneRef) DeepCopy() *DataplaneRef { + if in == nil { + return nil + } + out := new(DataplaneRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in DataplaneTags) DeepCopyInto(out *DataplaneTags) { { @@ -115,6 +130,11 @@ func (in *Selector) DeepCopyInto(out *Selector) { (*out)[key] = val } } + if in.DataplaneRef != nil { + in, out := &in.DataplaneRef, &out.DataplaneRef + *out = new(DataplaneRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. diff --git a/pkg/core/resources/apis/meshservice/k8s/crd/kuma.io_meshservices.yaml b/pkg/core/resources/apis/meshservice/k8s/crd/kuma.io_meshservices.yaml index 535af7054889..3e1f5f86c68e 100644 --- a/pkg/core/resources/apis/meshservice/k8s/crd/kuma.io_meshservices.yaml +++ b/pkg/core/resources/apis/meshservice/k8s/crd/kuma.io_meshservices.yaml @@ -65,6 +65,11 @@ spec: x-kubernetes-list-type: map selector: properties: + dataplaneRef: + properties: + name: + type: string + type: object dataplaneTags: additionalProperties: type: string diff --git a/pkg/plugins/policies/core/xds/meshroute/listeners.go b/pkg/plugins/policies/core/xds/meshroute/listeners.go index ef90803b5323..1dffbfc7a918 100644 --- a/pkg/plugins/policies/core/xds/meshroute/listeners.go +++ b/pkg/plugins/policies/core/xds/meshroute/listeners.go @@ -71,6 +71,9 @@ func CollectServices( for _, outbound := range proxy.Dataplane.Spec.GetNetworking().GetOutbounds() { oface := proxy.Dataplane.Spec.Networking.ToOutboundInterface(outbound) if outbound.BackendRef != nil { + if outbound.GetAddress() == proxy.Dataplane.Spec.GetNetworking().GetAddress() { + continue + } ms, ok := meshCtx.MeshServiceByName[outbound.BackendRef.Name] if !ok { // we want to ignore service which is not found. Logging might be excessive here. diff --git a/pkg/plugins/runtime/k8s/controllers/meshservice_controller.go b/pkg/plugins/runtime/k8s/controllers/meshservice_controller.go index c85da58a7538..fcba6f57f47d 100644 --- a/pkg/plugins/runtime/k8s/controllers/meshservice_controller.go +++ b/pkg/plugins/runtime/k8s/controllers/meshservice_controller.go @@ -2,11 +2,13 @@ package controllers import ( "context" + "fmt" "maps" "github.com/go-logr/logr" "github.com/pkg/errors" kube_core "k8s.io/api/core/v1" + kube_discovery "k8s.io/api/discovery/v1" kube_apierrs "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" kube_runtime "k8s.io/apimachinery/pkg/runtime" @@ -28,6 +30,7 @@ import ( "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1" "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/metadata" "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/util" + "github.com/kumahq/kuma/pkg/util/k8s" "github.com/kumahq/kuma/pkg/util/pointer" ) @@ -109,36 +112,215 @@ func (r *MeshServiceReconciler) Reconcile(ctx context.Context, req kube_ctrl.Req return kube_ctrl.Result{}, err } - ms := &meshservice_k8s.MeshService{ - ObjectMeta: v1.ObjectMeta{ - Name: svc.GetName(), - Namespace: svc.GetNamespace(), + if svc.Spec.ClusterIP != kube_core.ClusterIPNone { + name := kube_client.ObjectKeyFromObject(svc) + op, err := r.manageMeshService( + ctx, + svc, + mesh, + r.setFromClusterIPSvc, + name, + ) + if err != nil { + return kube_ctrl.Result{}, err + } + switch op { + case kube_controllerutil.OperationResultCreated: + r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, CreatedMeshServiceReason, "Created Kuma MeshService: %s", name.Name) + case kube_controllerutil.OperationResultUpdated: + r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, UpdatedMeshServiceReason, "Updated Kuma MeshService: %s", name.Name) + } + + return kube_ctrl.Result{}, nil + } + + trackedPodEndpoints := map[kube_types.NamespacedName]struct{}{} + meshServices := &meshservice_k8s.MeshServiceList{} + if err := r.List( + ctx, + meshServices, + kube_client.MatchingLabels(map[string]string{ + metadata.KumaServiceName: svc.Name, + }), + ); err != nil { + return kube_ctrl.Result{}, errors.Wrap(err, "unable to list MeshServices for headless Service") + } + for _, svc := range meshServices.Items { + if len(svc.GetOwnerReferences()) == 0 { + continue + } + owner := svc.GetOwnerReferences()[0] + if owner.Kind != "Pod" || owner.APIVersion != kube_core.SchemeGroupVersion.String() { + continue + } + trackedPodEndpoints[kube_types.NamespacedName{Namespace: svc.Namespace, Name: owner.Name}] = struct{}{} + } + + endpointSlices := &kube_discovery.EndpointSliceList{} + if err := r.List( + ctx, + endpointSlices, + kube_client.InNamespace(svc.Namespace), + kube_client.MatchingLabels(map[string]string{ + kube_discovery.LabelServiceName: svc.Name, + }), + ); err != nil { + return kube_ctrl.Result{}, errors.Wrap(err, "unable to list EndpointSlices for headless Service") + } + + servicePodEndpoints := map[kube_types.NamespacedName]kube_discovery.Endpoint{} + // We need to look at our EndpointSlice to see which Pods this headless + // service points to + var created, updated int + for _, slice := range endpointSlices.Items { + for _, endpoint := range slice.Endpoints { + if endpoint.TargetRef == nil || + endpoint.TargetRef.Kind != "Pod" || + (endpoint.TargetRef.APIVersion != kube_core.SchemeGroupVersion.String() && + endpoint.TargetRef.APIVersion != "") { + continue + } + servicePodEndpoints[kube_types.NamespacedName{Name: endpoint.TargetRef.Name, Namespace: endpoint.TargetRef.Namespace}] = endpoint + } + } + + // Delete trackedPodEndpoints - servicePodEndpoints + for tracked := range trackedPodEndpoints { + if _, ok := servicePodEndpoints[tracked]; ok { + continue + } + delete(trackedPodEndpoints, tracked) + ms := meshservice_k8s.MeshService{ + ObjectMeta: v1.ObjectMeta{ + Namespace: tracked.Namespace, + Name: tracked.Name, + }, + } + if err := r.Delete(ctx, &ms); err != nil && !kube_apierrs.IsNotFound(err) { + return kube_ctrl.Result{}, errors.Wrap(err, "unable to delete MeshService tracking headless Service endpoint") + } + } + + for current, endpoint := range servicePodEndpoints { + // Our name is unique depending on the service identity and pod name + // Note that a Pod name can be max 63 characters so we won't hit the 253 + // limit with our MeshService name + canonicalNameHasher := k8s.NewHasher() + canonicalNameHasher.Write([]byte(svc.Name)) + canonicalNameHasher.Write([]byte(svc.Namespace)) + canonicalName := fmt.Sprintf("%s-%s", current.Name, k8s.HashToString(canonicalNameHasher)) + op, err := r.manageMeshService( + ctx, + svc, + mesh, + r.setFromPodAndHeadlessSvc(endpoint), + kube_types.NamespacedName{Namespace: current.Namespace, Name: canonicalName}, + ) + if err != nil { + return kube_ctrl.Result{}, errors.Wrap(err, "unable to create/update MeshService for headless Service") + } + switch op { + case kube_controllerutil.OperationResultCreated: + created++ + case kube_controllerutil.OperationResultUpdated: + updated++ + } + } + if created > 0 { + r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, CreatedMeshServiceReason, "Created %d MeshServices", created) + } + if updated > 0 { + r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, UpdatedMeshServiceReason, "Updated %d MeshServices", updated) + } + + return kube_ctrl.Result{}, nil +} + +func (r *MeshServiceReconciler) setFromClusterIPSvc(ms *meshservice_k8s.MeshService, svc *kube_core.Service) error { + if ms.ObjectMeta.GetGeneration() != 0 { + if owners := ms.GetOwnerReferences(); len(owners) == 0 || owners[0].UID != svc.GetUID() { + r.EventRecorder.Eventf( + svc, kube_core.EventTypeWarning, FailedToGenerateMeshServiceReason, "MeshService already exists and isn't owned by Service", + ) + return errors.Errorf("MeshService already exists and isn't owned by Service") + } + } + ms.Spec.Selector = meshservice_api.Selector{ + DataplaneTags: svc.Spec.Selector, + } + + ms.Status.VIPs = []meshservice_api.VIP{ + { + IP: svc.Spec.ClusterIP, }, } - operationResult, err := kube_controllerutil.CreateOrUpdate(ctx, r.Client, ms, func() error { + if err := kube_controllerutil.SetOwnerReference(svc, ms, r.Scheme); err != nil { + return errors.Wrap(err, "could not set owner reference") + } + return nil +} + +func (r *MeshServiceReconciler) setFromPodAndHeadlessSvc(endpoint kube_discovery.Endpoint) func(*meshservice_k8s.MeshService, *kube_core.Service) error { + return func(ms *meshservice_k8s.MeshService, svc *kube_core.Service) error { if ms.ObjectMeta.GetGeneration() != 0 { - if owners := ms.GetOwnerReferences(); len(owners) == 0 || owners[0].UID != svc.GetUID() { + if owners := ms.GetOwnerReferences(); len(owners) == 0 || owners[0].UID != endpoint.TargetRef.UID { r.EventRecorder.Eventf( - svc, kube_core.EventTypeWarning, FailedToGenerateMeshServiceReason, "MeshService already exists and isn't owned by Service", + svc, kube_core.EventTypeWarning, FailedToGenerateMeshServiceReason, "MeshService already exists and isn't owned by Pod", ) - return errors.Errorf("MeshService already exists and isn't owned by Service") + return errors.Errorf("MeshService already exists and isn't owned by Pod") } } + ms.Spec.Selector = meshservice_api.Selector{ + DataplaneRef: &meshservice_api.DataplaneRef{ + Name: endpoint.TargetRef.Name, + }, + } + for _, address := range endpoint.Addresses { + ms.Status.VIPs = append(ms.Status.VIPs, + meshservice_api.VIP{ + IP: address, + }) + } + owner := kube_core.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: endpoint.TargetRef.Name, + Namespace: endpoint.TargetRef.Namespace, + UID: endpoint.TargetRef.UID, + }, + } + if err := kube_controllerutil.SetOwnerReference(&owner, ms, r.Scheme); err != nil { + return errors.Wrap(err, "could not set owner reference") + } + return nil + } +} + +func (r *MeshServiceReconciler) manageMeshService( + ctx context.Context, + svc *kube_core.Service, + mesh string, + setSpec func(*meshservice_k8s.MeshService, *kube_core.Service) error, + meshServiceName kube_types.NamespacedName, +) (kube_controllerutil.OperationResult, error) { + ms := &meshservice_k8s.MeshService{ + ObjectMeta: v1.ObjectMeta{ + Name: meshServiceName.Name, + Namespace: meshServiceName.Namespace, + }, + } + + return kube_controllerutil.CreateOrUpdate(ctx, r.Client, ms, func() error { ms.ObjectMeta.Labels = maps.Clone(svc.GetLabels()) if ms.ObjectMeta.Labels == nil { ms.ObjectMeta.Labels = map[string]string{} } ms.ObjectMeta.Labels[mesh_proto.MeshTag] = mesh - ms.ObjectMeta.Labels[metadata.KumaSerivceName] = svc.GetName() + ms.ObjectMeta.Labels[metadata.KumaServiceName] = svc.GetName() if ms.Spec == nil { ms.Spec = &meshservice_api.MeshService{} } - ms.Spec.Selector = meshservice_api.Selector{ - DataplaneTags: svc.Spec.Selector, - } - ms.Spec.Ports = []meshservice_api.Port{} for _, port := range svc.Spec.Ports { if port.Protocol != kube_core.ProtocolTCP { @@ -154,27 +336,8 @@ func (r *MeshServiceReconciler) Reconcile(ctx context.Context, req kube_ctrl.Req if ms.Status == nil { ms.Status = &meshservice_api.MeshServiceStatus{} } - ms.Status.VIPs = []meshservice_api.VIP{ - { - IP: svc.Spec.ClusterIP, - }, - } - if err := kube_controllerutil.SetOwnerReference(svc, ms, r.Scheme); err != nil { - return errors.Wrap(err, "could not set owner reference") - } - return nil + return setSpec(ms, svc) }) - if err != nil { - return kube_ctrl.Result{}, err - } - switch operationResult { - case kube_controllerutil.OperationResultCreated: - r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, CreatedMeshServiceReason, "Created Kuma MeshService: %s", ms.Name) - case kube_controllerutil.OperationResultUpdated: - r.EventRecorder.Eventf(svc, kube_core.EventTypeNormal, UpdatedMeshServiceReason, "Updated Kuma MeshService: %s", ms.Name) - } - log.V(1).Info("mesh service reconciled", "result", operationResult) - return kube_ctrl.Result{}, nil } func (r *MeshServiceReconciler) deleteIfExist(ctx context.Context, key kube_types.NamespacedName) error { @@ -185,7 +348,7 @@ func (r *MeshServiceReconciler) deleteIfExist(ctx context.Context, key kube_type }, } if err := r.Client.Delete(ctx, ms); err != nil && !kube_apierrs.IsNotFound(err) { - return errors.Wrap(err, "could not delete mesh service") + return errors.Wrap(err, "could not delete MeshService") } return nil } @@ -193,13 +356,27 @@ func (r *MeshServiceReconciler) deleteIfExist(ctx context.Context, key kube_type func (r *MeshServiceReconciler) SetupWithManager(mgr kube_ctrl.Manager) error { return kube_ctrl.NewControllerManagedBy(mgr). For(&kube_core.Service{}). - // on Namespace update we reconcile Services in this namespace Watches(&kube_core.Namespace{}, kube_handler.EnqueueRequestsFromMapFunc(NamespaceToServiceMapper(r.Log, mgr.GetClient())), builder.WithPredicates(predicate.LabelChangedPredicate{})). - // on Mesh create or delete reconcile all Services - Watches(&v1alpha1.Mesh{}, kube_handler.EnqueueRequestsFromMapFunc(MeshToMeshService(r.Log, mgr.GetClient())), builder.WithPredicates(CreateOrDeletePredicate{})). + Watches(&v1alpha1.Mesh{}, kube_handler.EnqueueRequestsFromMapFunc(MeshToAllMeshServices(r.Log, mgr.GetClient())), builder.WithPredicates(CreateOrDeletePredicate{})). + Watches(&kube_discovery.EndpointSlice{}, kube_handler.EnqueueRequestsFromMapFunc(EndpointSliceToServicesMapper(r.Log, mgr.GetClient()))). Complete(r) } +func EndpointSliceToServicesMapper(l logr.Logger, client kube_client.Client) kube_handler.MapFunc { + return func(ctx context.Context, obj kube_client.Object) []kube_reconcile.Request { + slice := obj.(*kube_discovery.EndpointSlice) + + svcName, ok := slice.Labels[kube_discovery.LabelServiceName] + if !ok { + return nil + } + req := []kube_reconcile.Request{ + {NamespacedName: kube_types.NamespacedName{Namespace: slice.Namespace, Name: svcName}}, + } + return req + } +} + func NamespaceToServiceMapper(l logr.Logger, client kube_client.Client) kube_handler.MapFunc { l = l.WithName("namespace-to-service-mapper") return func(ctx context.Context, obj kube_client.Object) []kube_reconcile.Request { @@ -218,7 +395,7 @@ func NamespaceToServiceMapper(l logr.Logger, client kube_client.Client) kube_han } } -func MeshToMeshService(l logr.Logger, client kube_client.Client) kube_handler.MapFunc { +func MeshToAllMeshServices(l logr.Logger, client kube_client.Client) kube_handler.MapFunc { l = l.WithName("mesh-to-service-mapper") return func(ctx context.Context, obj kube_client.Object) []kube_reconcile.Request { services := &kube_core.ServiceList{} diff --git a/pkg/plugins/runtime/k8s/controllers/meshservice_controller_test.go b/pkg/plugins/runtime/k8s/controllers/meshservice_controller_test.go index d368a7a7cbc2..12aa04461ad8 100644 --- a/pkg/plugins/runtime/k8s/controllers/meshservice_controller_test.go +++ b/pkg/plugins/runtime/k8s/controllers/meshservice_controller_test.go @@ -10,6 +10,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" kube_core "k8s.io/api/core/v1" + kube_discovery "k8s.io/api/discovery/v1" kube_types "k8s.io/apimachinery/pkg/types" kube_record "k8s.io/client-go/tools/record" kube_ctrl "sigs.k8s.io/controller-runtime" @@ -47,6 +48,8 @@ var _ = Describe("MeshServiceController", func() { obj = &meshservice_k8s.MeshService{} case strings.Contains(yamlObj, "kind: Service"): obj = &kube_core.Service{} + case strings.Contains(yamlObj, "kind: EndpointSlice"): + obj = &kube_discovery.EndpointSlice{} case strings.Contains(yamlObj, "kind: Namespace"): obj = &kube_core.Namespace{} case strings.Contains(yamlObj, "kind: Mesh"): @@ -103,5 +106,9 @@ var _ = Describe("MeshServiceController", func() { inputFile: "05.resources.yaml", outputFile: "05.meshservice.yaml", }), + Entry("service for headless Service", testCase{ + inputFile: "headless.resources.yaml", + outputFile: "headless.meshservice.yaml", + }), ) }) diff --git a/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.meshservice.yaml b/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.meshservice.yaml new file mode 100644 index 000000000000..69f4b5678d20 --- /dev/null +++ b/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.meshservice.yaml @@ -0,0 +1,86 @@ +items: +- metadata: + creationTimestamp: null + labels: + k8s.kuma.io/service-name: example + kuma.io/mesh: default + name: example-1-87ff74c98 + namespace: demo + ownerReferences: + - apiVersion: v1 + kind: Pod + name: example-1 + uid: example-1-1 + resourceVersion: "1" + spec: + ports: + - port: 80 + protocol: http + targetPort: 8080 + - port: 443 + protocol: tcp + targetPort: 8443 + selector: + dataplaneRef: + name: example-1 + status: + tls: {} + vips: + - ip: 192.168.0.5 +- metadata: + creationTimestamp: null + labels: + k8s.kuma.io/service-name: example + kuma.io/mesh: default + name: example-2-87ff74c98 + namespace: demo + ownerReferences: + - apiVersion: v1 + kind: Pod + name: example-2 + uid: example-2-1 + resourceVersion: "1" + spec: + ports: + - port: 80 + protocol: http + targetPort: 8080 + - port: 443 + protocol: tcp + targetPort: 8443 + selector: + dataplaneRef: + name: example-2 + status: + tls: {} + vips: + - ip: 192.168.0.6 +- metadata: + creationTimestamp: null + labels: + k8s.kuma.io/service-name: example + kuma.io/mesh: default + name: very-long-very-long-very-long-very-long-very-long-v-87ff74c98 + namespace: demo + ownerReferences: + - apiVersion: v1 + kind: Pod + name: very-long-very-long-very-long-very-long-very-long-v + uid: very-long-1 + resourceVersion: "1" + spec: + ports: + - port: 80 + protocol: http + targetPort: 8080 + - port: 443 + protocol: tcp + targetPort: 8443 + selector: + dataplaneRef: + name: very-long-very-long-very-long-very-long-very-long-v + status: + tls: {} + vips: + - ip: 192.168.0.7 +metadata: {} diff --git a/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.resources.yaml b/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.resources.yaml new file mode 100644 index 000000000000..ec09c7edf854 --- /dev/null +++ b/pkg/plugins/runtime/k8s/controllers/testdata/meshservice/headless.resources.yaml @@ -0,0 +1,59 @@ +--- +apiVersion: discovery.k8s.io/v1 +kind: EndpointSlice +metadata: + name: example-ip4 + namespace: demo + labels: + kubernetes.io/service-name: example +addressType: IPv4 +endpoints: + - addresses: + - 192.168.0.5 + targetRef: + kind: Pod + name: example-1 + namespace: demo + uid: example-1-1 + - addresses: + - 192.168.0.6 + targetRef: + kind: Pod + name: example-2 + namespace: demo + uid: example-2-1 + - addresses: + - 192.168.0.7 + targetRef: + kind: Pod + name: very-long-very-long-very-long-very-long-very-long-v + namespace: demo + uid: very-long-1 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: demo + name: example +spec: + clusterIP: None + ports: + - appProtocol: http + port: 80 + targetPort: 8080 + protocol: TCP + - port: 443 + targetPort: 8443 + protocol: TCP +--- +apiVersion: v1 +kind: Namespace +metadata: + name: demo + labels: + kuma.io/sidecar-injection: enabled +--- +apiVersion: kuma.io/v1alpha1 +kind: Mesh +metadata: + name: default diff --git a/pkg/plugins/runtime/k8s/metadata/annotations.go b/pkg/plugins/runtime/k8s/metadata/annotations.go index fb457269ec2c..ee3bf2f03846 100644 --- a/pkg/plugins/runtime/k8s/metadata/annotations.go +++ b/pkg/plugins/runtime/k8s/metadata/annotations.go @@ -117,7 +117,7 @@ const ( KumaWaitForDataplaneReady = "kuma.io/wait-for-dataplane-ready" // KumaServiceName points to the Service that a MeshService is derived from - KumaSerivceName = "k8s.kuma.io/service-name" + KumaServiceName = "k8s.kuma.io/service-name" ) var PodAnnotationDeprecations = []Deprecation{ diff --git a/pkg/util/k8s/name_converter.go b/pkg/util/k8s/name_converter.go index 787f2247a8b8..3a9faa631656 100644 --- a/pkg/util/k8s/name_converter.go +++ b/pkg/util/k8s/name_converter.go @@ -2,9 +2,12 @@ package k8s import ( "fmt" + "hash" + "hash/fnv" "strings" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/rand" ) func CoreNameToK8sName(coreName string) (string, string, error) { @@ -23,3 +26,15 @@ func CoreNameToK8sName(coreName string) (string, string, error) { func K8sNamespacedNameToCoreName(name, namespace string) string { return fmt.Sprintf("%s.%s", name, namespace) } + +func NewHasher() hash.Hash32 { + return fnv.New32a() +} + +// HashToString calculates a hash the same way Pod template hashes are computed +func HashToString(h hash.Hash32) string { + return rand.SafeEncodeString(fmt.Sprint(h.Sum32())) +} + +// MaxHashStringLength is the max length of a string returned by HashToString +const MaxHashStringLength = 10 diff --git a/pkg/xds/topology/outbound.go b/pkg/xds/topology/outbound.go index e7c701287263..bac05197507c 100644 --- a/pkg/xds/topology/outbound.go +++ b/pkg/xds/topology/outbound.go @@ -157,6 +157,9 @@ func fillLocalMeshServices( for _, meshSvc := range meshServices { tagSelector := mesh_proto.TagSelector(meshSvc.Spec.Selector.DataplaneTags) + if meshSvc.Spec.Selector.DataplaneRef != nil { + continue + } for _, inbound := range dpNetworking.GetHealthyInbounds() { if !tagSelector.Matches(inbound.GetTags()) {