diff --git a/control-plane/connect-inject/annotations.go b/control-plane/connect-inject/annotations.go index 2189a9f31c..5d47e69eb6 100644 --- a/control-plane/connect-inject/annotations.go +++ b/control-plane/connect-inject/annotations.go @@ -20,6 +20,11 @@ const ( // This defaults to the name of the Kubernetes service associated with the pod. annotationService = "consul.hashicorp.com/connect-service" + // annotationKubernetesService is the name of the Kubernetes service to register. + // This allows a pod to specify what Kubernetes service should trigger a Consul + // service registration in the case of multiple services referencing a deployment. + annotationKubernetesService = "consul.hashicorp.com/kubernetes-service" + // annotationPort is the name or value of the port to proxy incoming // connections to. annotationPort = "consul.hashicorp.com/connect-service-port" diff --git a/control-plane/connect-inject/endpoints_controller.go b/control-plane/connect-inject/endpoints_controller.go index 8213fa2278..f4dbfdc893 100644 --- a/control-plane/connect-inject/endpoints_controller.go +++ b/control-plane/connect-inject/endpoints_controller.go @@ -175,6 +175,12 @@ func (r *EndpointsController) Reconcile(ctx context.Context, req ctrl.Request) ( continue } + serviceName, ok := pod.Annotations[annotationKubernetesService] + if ok && serviceEndpoints.Name != serviceName { + r.Log.Info("ignoring endpoint because it doesn't match explicit service annotation", "name", serviceEndpoints.Name, "ns", serviceEndpoints.Namespace) + continue + } + if hasBeenInjected(pod) { endpointPods.Add(address.TargetRef.Name) if err := r.registerServicesAndHealthCheck(pod, serviceEndpoints, healthStatus, endpointAddressMap); err != nil { diff --git a/control-plane/connect-inject/endpoints_controller_test.go b/control-plane/connect-inject/endpoints_controller_test.go index 9b25c149b0..4f941f49f5 100644 --- a/control-plane/connect-inject/endpoints_controller_test.go +++ b/control-plane/connect-inject/endpoints_controller_test.go @@ -3373,6 +3373,119 @@ func TestReconcile_endpointsIgnoredWhenNotInjected(t *testing.T) { require.False(t, resp.Requeue) } +// Test that when an endpoints pod specifies the name for the Kubernetes service it wants to use +// for registration, all other endpoints for that pod are skipped. +func TestReconcile_podSpecifiesExplicitService(t *testing.T) { + nodeName := "test-node" + namespace := "default" + + // Set up the fake Kubernetes client with a few endpoints, pod, consul client, and the default namespace. + badEndpoint := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "not-in-mesh", + Namespace: namespace, + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "1.2.3.4", + NodeName: &nodeName, + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: "pod1", + Namespace: namespace, + }, + }, + }, + }, + }, + } + endpoint := &corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "in-mesh", + Namespace: namespace, + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "1.2.3.4", + NodeName: &nodeName, + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Name: "pod1", + Namespace: namespace, + }, + }, + }, + }, + }, + } + pod1 := createPod("pod1", "1.2.3.4", true, true) + pod1.Annotations[annotationKubernetesService] = endpoint.Name + fakeClientPod := createPod("fake-consul-client", "127.0.0.1", false, true) + fakeClientPod.Labels = map[string]string{"component": "client", "app": "consul", "release": "consul"} + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + k8sObjects := []runtime.Object{badEndpoint, endpoint, pod1, fakeClientPod, &ns} + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() + + // Create test Consul server. + consul, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { c.NodeName = nodeName }) + require.NoError(t, err) + defer consul.Stop() + consul.WaitForServiceIntentions(t) + cfg := &api.Config{Address: consul.HTTPAddr} + consulClient, err := api.NewClient(cfg) + require.NoError(t, err) + addr := strings.Split(consul.HTTPAddr, ":") + consulPort := addr[1] + + // Create the endpoints controller. + ep := &EndpointsController{ + Client: fakeClient, + Log: logrtest.TestLogger{T: t}, + ConsulClient: consulClient, + ConsulPort: consulPort, + ConsulScheme: "http", + AllowK8sNamespacesSet: mapset.NewSetWith("*"), + DenyK8sNamespacesSet: mapset.NewSetWith(), + ReleaseName: "consul", + ReleaseNamespace: namespace, + ConsulClientCfg: cfg, + } + + // Run the reconcile process to check service registration. + serviceName := badEndpoint.Name + namespacedName := types.NamespacedName{Namespace: badEndpoint.Namespace, Name: serviceName} + resp, err := ep.Reconcile(context.Background(), ctrl.Request{NamespacedName: namespacedName}) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Check that no services are registered with Consul. + serviceInstances, _, err := consulClient.Catalog().Service(serviceName, "", nil) + require.NoError(t, err) + require.Len(t, serviceInstances, 0) + proxyServiceInstances, _, err := consulClient.Catalog().Service(serviceName+"-sidecar-proxy", "", nil) + require.NoError(t, err) + require.Len(t, proxyServiceInstances, 0) + + // Run the reconcile again with the service we want to register. + serviceName = endpoint.Name + namespacedName = types.NamespacedName{Namespace: badEndpoint.Namespace, Name: serviceName} + resp, err = ep.Reconcile(context.Background(), ctrl.Request{NamespacedName: namespacedName}) + require.NoError(t, err) + require.False(t, resp.Requeue) + + // Check that the correct services are registered with Consul. + serviceInstances, _, err = consulClient.Catalog().Service(serviceName, "", nil) + require.NoError(t, err) + require.Len(t, serviceInstances, 1) + proxyServiceInstances, _, err = consulClient.Catalog().Service(serviceName+"-sidecar-proxy", "", nil) + require.NoError(t, err) + require.Len(t, proxyServiceInstances, 1) +} + func TestFilterAgentPods(t *testing.T) { t.Parallel() cases := map[string]struct {