Repository: Kong/kubernetes-ingress-controller
Pull request #2639 (2022-07-01 09:19:14)

chore(deps): bump k8s.io/code-generator from 0.24.0 to 0.24.2 in /third_party
Bumps [k8s.io/code-generator](https://github.com/kubernetes/code-generator) from 0.24.0 to 0.24.2.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/kubernetes/code-generator/compare/v0.24.0...v0.24.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k8s.io/code-generator&package-manager=go_modules&previous-version=0.24.0&new-version=0.24.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbd32b0e5..14449a8b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,8 @@
 - Added support for Gateway Listener TLS configuration, to enable full use of
   TLSRoute and HTTPS HTTPRoutes.
   [#2580](https://github.com/Kong/kubernetes-ingress-controller/pull/2580)
+- Added information about service mesh deployment and distribution in telemetry data reported to Kong.
+  [#2642](https://github.com/Kong/kubernetes-ingress-controller/pull/2642)
 
 #### Fixed
 
diff --git a/internal/manager/run.go b/internal/manager/run.go
index 4ad394305..ed27dc2d5 100644
--- a/internal/manager/run.go
+++ b/internal/manager/run.go
@@ -192,7 +192,7 @@ func Run(ctx context.Context, c *Config, diagnostic util.ConfigDumpDiagnostic) e
 
 	if c.AnonymousReports {
 		setupLog.Info("Starting anonymous reports")
-		if err := mgrutils.RunReport(ctx, kubeconfig, kongConfig, metadata.Release, featureGates); err != nil {
+		if err := mgrutils.RunReport(ctx, kubeconfig, kongConfig, c.PublishService, metadata.Release, featureGates); err != nil {
 			setupLog.Error(err, "anonymous reporting failed")
 		}
 	} else {
diff --git a/internal/manager/utils/reports.go b/internal/manager/utils/reports.go
index ac824470a..abb5c50ee 100644
--- a/internal/manager/utils/reports.go
+++ b/internal/manager/utils/reports.go
@@ -5,17 +5,19 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/bombsimon/logrusr/v2"
 	"github.com/google/uuid"
 	"github.com/sirupsen/logrus"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/meshdetect"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 )
 
 // RunReport runs the anonymous data report and reports any errors that have occurred.
-func RunReport(ctx context.Context, kubeCfg *rest.Config, kongCfg sendconfig.Kong, kicVersion string, featureGates map[string]bool) error {
+func RunReport(ctx context.Context, kubeCfg *rest.Config, kongCfg sendconfig.Kong, publishServiceName string, kicVersion string, featureGates map[string]bool) error {
 	// if anonymous reports are enabled this helps provide Kong with insights about usage of the ingress controller
 	// which is non-sensitive and predominantly informs us of the controller and cluster versions in use.
 	// This data helps inform us what versions, features, e.t.c. end-users are actively using which helps to inform
@@ -41,6 +43,11 @@ func RunReport(ctx context.Context, kubeCfg *rest.Config, kongCfg sendconfig.Kon
 		return fmt.Errorf("failed to fetch k8s api-server version: %w", err)
 	}
 
+	podInfo, err := util.GetPodDetails(ctx, kc)
+	if err != nil {
+		return fmt.Errorf("failed to get pod details: %w", err)
+	}
+
 	// gather versioning information from the kong client
 	root, err := kongCfg.Client.Root(ctx)
 	if err != nil {
@@ -75,6 +82,16 @@ func RunReport(ctx context.Context, kubeCfg *rest.Config, kongCfg sendconfig.Kon
 		Info:   info,
 		Logger: logrus.New(),
 	}
+
+	// add mesh detector to reporter
+	detector, err := meshdetect.NewDetectorByConfig(kubeCfg, podInfo.Namespace, podInfo.Name, publishServiceName,
+		logrusr.New(reporter.Logger),
+	)
+	if err == nil {
+		reporter.MeshDetectionEnabled = true
+		reporter.MeshDetector = detector
+	}
+
 	go reporter.Run(ctx.Done())
 
 	return nil
diff --git a/internal/meshdetect/detector.go b/internal/meshdetect/detector.go
new file mode 100644
index 000000000..2e063fe8e
--- /dev/null
+++ b/internal/meshdetect/detector.go
@@ -0,0 +1,283 @@
+package meshdetect
+
+import (
+	"context"
+	"strings"
+
+	"github.com/go-logr/logr"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/rest"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Detector provides methods to detect the following:
+//
+//   - whether a service mesh is deployed to the cluster
+//   - whether KIC is injected with service mesh
+//   - count of services injected within mesh networks
+type Detector struct {
+	// Client is the kubernetes client to read kubernetes services.
+	Client client.Client
+
+	// PodNamespace is the namespace of the pod in which the mesh detector is running.
+	PodNamespace string
+
+	// PodName is the name of pod  in which the mesh detector is running.
+	PodName string
+
+	// PublishServiceName is the name of the Kubernetes service used for
+	// ingress traffic to the Kong Gateway.
+	PublishServiceName string
+
+	Logger logr.Logger
+}
+
+// NewDetectorByConfig creates a new Detector provided a Kubernetes
+// config for the relevant cluster and the name of the Kubernetes service
+// for ingress traffic to the Kong Gateway.
+func NewDetectorByConfig(kubeCfg *rest.Config,
+	podNamespace, podName, publishServiceName string,
+	logger logr.Logger) (*Detector, error) {
+	c, err := client.New(kubeCfg, client.Options{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &Detector{
+		Client:             c,
+		PodNamespace:       podNamespace,
+		PodName:            podName,
+		PublishServiceName: publishServiceName,
+		Logger:             logger,
+	}, nil
+}
+
+// DetectMeshDeployment detects which kinds of mesh networks are deployed.
+func (d *Detector) DetectMeshDeployment(ctx context.Context) map[MeshKind]*DeploymentResults {
+	deploymentResults := map[MeshKind]*DeploymentResults{}
+
+	for _, meshKind := range MeshesToDetect {
+		deploymentResult := &DeploymentResults{}
+		if d.detectMeshDeploymentByService(ctx, meshKind) {
+			deploymentResult.ServiceExists = true
+		}
+		deploymentResults[meshKind] = deploymentResult
+	}
+
+	return deploymentResults
+}
+
+// detectMeshDeploymentByService finds the service for each mesh in the cluster.
+func (d *Detector) detectMeshDeploymentByService(ctx context.Context, meshKind MeshKind) bool {
+	serviceName := meshServiceName[meshKind]
+
+	svcList := &corev1.ServiceList{}
+	err := d.Client.List(ctx, svcList, &client.ListOptions{
+		FieldSelector: fields.SelectorFromSet(fields.Set{"metadata.name": serviceName}),
+	})
+	if err != nil {
+		return false
+	}
+
+	for _, svc := range svcList.Items {
+		if svc.Name == serviceName {
+			return true
+		}
+	}
+
+	return false
+}
+
+// DetectRunUnder detects whether KIC pod is running under each kind of service mesh.
+// in this function, we want to detect whether the pod running this detector, which is
+// also the KIC pod, is running under a certain kind of service mesh.
+// for example, if the pod is injected with istio sidecar container and init container,
+// we report that the detector is running under istio mesh.
+func (d *Detector) DetectRunUnder(ctx context.Context) map[MeshKind]*RunUnderResults {
+	runUnderResults := map[MeshKind]*RunUnderResults{}
+	// get the pod itself.
+	pod := &corev1.Pod{}
+	err := d.Client.Get(ctx, client.ObjectKey{Namespace: d.PodNamespace, Name: d.PodName}, pod)
+	if err != nil {
+		return runUnderResults
+	}
+
+	publishService := &corev1.Service{}
+	if d.PublishServiceName != "" {
+		parts := strings.Split(d.PublishServiceName, "/")
+		objKey := client.ObjectKey{}
+		// format <namespace>/<name>
+		if len(parts) == 2 {
+			objKey.Namespace = parts[0]
+			objKey.Name = parts[1]
+		} else {
+			d.Logger.Info("publish service " + d.PublishServiceName + " is not in <namespace>/<name> format")
+		}
+		// only try to get service if the namespace and name are correctly filled
+		if objKey.Name != "" && objKey.Namespace != "" {
+			err := d.Client.Get(ctx, objKey, publishService)
+			if err != nil {
+				d.Logger.Info(
+					"failed to get service to publish gateway configuration:"+err.Error(),
+					"namespace", objKey.Namespace, "name", objKey.Name)
+			}
+		}
+	}
+
+	for _, meshKind := range MeshesToDetect {
+		runUnderResults[meshKind] = &RunUnderResults{}
+
+		// detect if service for kong-gateway has annotations(only for traefik)
+		if publishService != nil && isServiceInjected(meshKind, publishService) {
+			runUnderResults[meshKind].PodOrServiceAnnotation = true
+		}
+
+		// detect if pod has annotations.
+		podAnnotations := meshPodAnnotations[meshKind]
+		if podAnnotations != nil && podAnnotations.Matches(labels.Set(pod.Annotations)) {
+			runUnderResults[meshKind].PodOrServiceAnnotation = true
+		}
+
+		// detect if pod has a sidecar.
+		runUnderResults[meshKind].SidecarContainerInjected = isPodSidecarInjected(meshKind, pod)
+
+		// detect if pod has a init container.
+		runUnderResults[meshKind].InitContainerInjected = isPodInitContainerInjected(meshKind, pod)
+
+	}
+
+	return runUnderResults
+}
+
+func isServiceInjected(meshKind MeshKind, svc *corev1.Service) bool {
+	if svc == nil {
+		return false
+	}
+	if svc.Annotations == nil {
+		return false
+	}
+
+	svcAnnotations := meshServiceAnnotations[meshKind]
+	if svcAnnotations == nil {
+		return false
+	}
+	if svcAnnotations.Matches(labels.Set(svc.Annotations)) {
+		return true
+	}
+
+	return false
+}
+
+func isPodSidecarInjected(meshKind MeshKind, pod *corev1.Pod) bool {
+	sidecarName := meshSidecarContainerName[meshKind]
+	if sidecarName == "" {
+		return false
+	}
+	for _, container := range pod.Spec.Containers {
+		if container.Name == sidecarName {
+
+			switch meshKind { // nolint:exhaustive
+			case MeshKindAWSAppMesh:
+				// special judgement for AWS appmesh here:
+				// AWS appmesh uses `envoy` as sidecar name, which is a very common name.
+				// We do a further check on the container and treat as really injected
+				// if the container uses image `aws-appmesh-envoy:*`.
+				if strings.Contains(container.Image, awsAppMeshEnvoyImageName) {
+					return true
+				}
+			default:
+				// for meshes other than AWS app mesh, directly return true (pod injected)
+				// when the container with the sidecar name is found.
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func isPodInitContainerInjected(meshKind MeshKind, pod *corev1.Pod) bool {
+	initContainerName := meshInitContainerName[meshKind]
+	if initContainerName == "" {
+		return false
+	}
+	for _, initContainer := range pod.Spec.InitContainers {
+		if initContainer.Name == initContainerName {
+			return true
+		}
+	}
+
+	return false
+}
+
+// DetectServiceDistribution detects how many services are running under each mesh.
+func (d *Detector) DetectServiceDistribution(ctx context.Context) (*ServiceDistributionResults, error) {
+	ret := &ServiceDistributionResults{
+		MeshDistribution: map[MeshKind]int{},
+	}
+
+	// list all services
+	serviceList := &corev1.ServiceList{}
+	err := d.Client.List(ctx, serviceList)
+	if err != nil {
+		d.Logger.Error(err, "failed to list all services in cluster")
+		return nil, err
+	}
+
+	ret.TotalServices = len(serviceList.Items)
+
+	for i := range serviceList.Items {
+		svc := &serviceList.Items[i]
+		endpoints := &corev1.Endpoints{}
+		err := d.Client.Get(ctx, client.ObjectKey{Namespace: svc.Namespace, Name: svc.Name}, endpoints)
+		if err != nil {
+			continue
+		}
+
+		// injected is set to true if the service(pod) is injected by mesh.
+		injected := map[MeshKind]bool{}
+
+		// detect if service has annotations to indicate that the service is injected
+		// (only for traefik)
+
+		for meshKind := range meshServiceAnnotations {
+			injected[meshKind] = isServiceInjected(meshKind, svc)
+		}
+
+		for _, subset := range endpoints.Subsets {
+			for _, address := range subset.Addresses {
+				// skip if the target endpoint address is not a pod.
+				if address.TargetRef == nil {
+					continue
+				}
+				if address.TargetRef.Kind != "Pod" {
+					continue
+				}
+
+				// if one of the pod is injected, we consider this service as running under the mesh.
+				pod := &corev1.Pod{}
+				err := d.Client.Get(ctx,
+					client.ObjectKey{Namespace: address.TargetRef.Namespace, Name: address.TargetRef.Name},
+					pod)
+				if err != nil {
+					continue
+				}
+
+				for _, meshKind := range MeshesToDetect {
+					// set injected to true if one of pods in service is injected with sidecar and init container.
+					injected[meshKind] = injected[meshKind] ||
+						(isPodSidecarInjected(meshKind, pod) || isPodInitContainerInjected(meshKind, pod))
+				}
+			}
+		}
+
+		for meshKind := range injected {
+			if injected[meshKind] {
+				ret.MeshDistribution[meshKind]++
+			}
+		}
+	}
+
+	return ret, nil
+}
diff --git a/internal/meshdetect/mesh_kind.go b/internal/meshdetect/mesh_kind.go
new file mode 100644
index 000000000..891b4a5f2
--- /dev/null
+++ b/internal/meshdetect/mesh_kind.go
@@ -0,0 +1,92 @@
+package meshdetect
+
+import (
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/selection"
+)
+
+// MeshKind defines names of different service meshes.
+type MeshKind string
+
+const (
+	MeshKindIstio      MeshKind = "istio"
+	MeshKindLinkerd    MeshKind = "linkerd"
+	MeshKindKuma       MeshKind = "kuma"
+	MeshKindKongMesh   MeshKind = "kong-mesh"
+	MeshKindConsul     MeshKind = "consul"
+	MeshKindTraefik    MeshKind = "traefik"
+	MeshKindAWSAppMesh MeshKind = "aws-app-mesh"
+)
+
+// MeshesToDetect is the list of meshes to detect.
+var MeshesToDetect = []MeshKind{
+	MeshKindIstio,
+	MeshKindLinkerd,
+	MeshKindKuma,
+	MeshKindKongMesh,
+	MeshKindConsul,
+	MeshKindTraefik,
+	MeshKindAWSAppMesh,
+}
+
+// meshServiceName is one of the services of system components in each mesh.
+var meshServiceName = map[MeshKind]string{
+	MeshKindIstio:      "istiod",
+	MeshKindLinkerd:    "linkerd-proxy-injector",
+	MeshKindKuma:       "kuma-control-plane",
+	MeshKindKongMesh:   "kong-mesh-control-plane",
+	MeshKindConsul:     "consul-server",
+	MeshKindTraefik:    "traefik-mesh-controller",
+	MeshKindAWSAppMesh: "appmesh-controller-webhook-service",
+}
+
+// mustMakeRequirement is a tool function to make labels.Requirement in map initializations.
+// it runs labels.NewRequirement without opts and panics on error.
+func mustMakeRequirement(key string, op selection.Operator, vals []string) *labels.Requirement {
+	req, err := labels.NewRequirement(key, op, vals)
+	if err != nil {
+		panic("failed to make requirement:" + err.Error())
+	}
+	return req
+}
+
+// meshPodAnnotations is the annotation of pod indicating that the pod should be injected with sidecars.
+var meshPodAnnotations = map[MeshKind]*labels.Requirement{
+	MeshKindIstio:    mustMakeRequirement("sidecar.istio.io/status", selection.Exists, []string{}),
+	MeshKindLinkerd:  mustMakeRequirement("linkerd.io/proxy-version", selection.Exists, []string{}),
+	MeshKindKuma:     mustMakeRequirement("kuma.io/sidecar-injected", selection.Equals, []string{"true"}),
+	MeshKindKongMesh: mustMakeRequirement("kuma.io/sidecar-injected", selection.Equals, []string{"true"}),
+	MeshKindConsul:   mustMakeRequirement("consul.hashicorp.com/connect-inject-status", selection.Equals, []string{"injected"}),
+}
+
+// meshServiceAnnotations is the annotation of service indicating that
+// the service in managed by the service mesh. (currently only for traefik)
+var meshServiceAnnotations = map[MeshKind]*labels.Requirement{
+	MeshKindTraefik: mustMakeRequirement("mesh.traefik.io/traffic-type", selection.In, []string{"HTTP", "TCP"}),
+}
+
+// meshSidecarContainerName is the name of sidecar container injected by each service mesh.
+var meshSidecarContainerName = map[MeshKind]string{
+	MeshKindIstio:      "istio-proxy",
+	MeshKindLinkerd:    "linkerd-proxy",
+	MeshKindKuma:       "kuma-sidecar",
+	MeshKindKongMesh:   "kuma-sidecar",
+	MeshKindConsul:     "envoy-sidecar",
+	MeshKindAWSAppMesh: "envoy",
+}
+
+const (
+	// awsAppMeshEnvoyImageName is the image used for aws appmesh sidecars.
+	// reference from AWS: https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html
+	awsAppMeshEnvoyImageName = "aws-appmesh-envoy"
+)
+
+// meshInitContainerName is the name of init container injected by each service mesh.
+var meshInitContainerName = map[MeshKind]string{
+	MeshKindIstio:      "istio-init",
+	MeshKindLinkerd:    "linkerd-init",
+	MeshKindKuma:       "kuma-init",
+	MeshKindKongMesh:   "kuma-init",
+	MeshKindConsul:     "consul-connect-inject-init",
+	MeshKindAWSAppMesh: "proxyinit",
+}
diff --git a/internal/meshdetect/types.go b/internal/meshdetect/types.go
new file mode 100644
index 000000000..21d81740a
--- /dev/null
+++ b/internal/meshdetect/types.go
@@ -0,0 +1,26 @@
+package meshdetect
+
+// DeploymentResults is the result of detecting signals of whether a certain
+// mesh is deployed in kubernetes cluster.
+type DeploymentResults struct {
+	ServiceExists bool `json:"serviceExists"`
+}
+
+// RunUnderResults is the result of detecting signals of whether KIC is
+// running under a certain service mesh.
+type RunUnderResults struct {
+	PodOrServiceAnnotation   bool `json:"podOrServiceAnnotation"`
+	SidecarContainerInjected bool `json:"sidecarContainerInjected"`
+	InitContainerInjected    bool `json:"initContainerInjected"`
+}
+
+// ServiceDistributionResults contains number of total services and number of
+// services running under each mesh.
+type ServiceDistributionResults struct {
+	TotalServices int `json:"totalServices"`
+	// MeshDistribution is the number of services running under each kind of mesh.
+	// We decided to directly use number here instead of ratio in total services in a
+	// floating number because using floating number needs extra work on calculating
+	// and serializing.
+	MeshDistribution map[MeshKind]int `json:"meshDistribution"`
+}
diff --git a/internal/util/reports.go b/internal/util/reports.go
index da9078f21..660e04fb4 100644
--- a/internal/util/reports.go
+++ b/internal/util/reports.go
@@ -1,6 +1,7 @@
 package util
 
 import (
+	"context"
 	"crypto/tls"
 	"fmt"
 	"net"
@@ -9,6 +10,8 @@ import (
 	"time"
 
 	"github.com/sirupsen/logrus"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/meshdetect"
 )
 
 var (
@@ -42,6 +45,9 @@ type Reporter struct {
 	serializedInfo string
 
 	Logger logrus.FieldLogger
+
+	MeshDetectionEnabled bool
+	MeshDetector         *meshdetect.Detector
 }
 
 func (r *Reporter) once() {
@@ -91,6 +97,25 @@ func (r *Reporter) sendPing(uptime int) {
 func (r *Reporter) send(signal string, uptime int) {
 	message := "<14>signal=" + signal + ";uptime=" +
 		strconv.Itoa(uptime) + ";" + r.serializedInfo
+	// run mesh detection if enabled.
+	if r.MeshDetectionEnabled {
+		meshMessage, err := r.getMeshMessages(context.Background())
+		if err != nil {
+			// log the error if mesh detection fails,
+			// but still send the messages without mesh detection results.
+			r.Logger.Debugf("failed to run mesh detection: %w", err)
+
+		} else {
+			// append results from mesh detection to reported message if returned any.
+			if meshMessage != "" {
+				if !strings.HasSuffix(message, ";") {
+					message = message + ";"
+				}
+				message = message + meshMessage
+			}
+		}
+	}
+
 	conn, err := tls.DialWithDialer(&dialer, "tcp", net.JoinHostPort(reportsHost,
 		strconv.FormatUint(uint64(reportsPort), 10)), &tlsConf)
 	if err != nil {
@@ -108,3 +133,24 @@ func (r *Reporter) send(signal string, uptime int) {
 		r.Logger.Debugf("failed to send report: %s", err)
 	}
 }
+
+// getMeshMessages returns serialized messages of results of mesh detection.
+func (r *Reporter) getMeshMessages(ctx context.Context) (string, error) {
+	if r.MeshDetector == nil {
+		return "", fmt.Errorf("no mesh detector")
+	}
+
+	meshMessages := []string{}
+	deploymentResults := r.MeshDetector.DetectMeshDeployment(ctx)
+	meshMessages = append(meshMessages, serializeMeshDeploymentResults(deploymentResults))
+
+	runUnderResults := r.MeshDetector.DetectRunUnder(ctx)
+	meshMessages = append(meshMessages, serializeMeshRunUnderResults(runUnderResults))
+
+	serviceDistributionResults, detectErr := r.MeshDetector.DetectServiceDistribution(ctx)
+	if detectErr != nil {
+		return "", fmt.Errorf("failed to detect service distribution under meshes: %w", detectErr)
+	}
+	meshMessages = append(meshMessages, serializeMeshServiceDistribution(serviceDistributionResults))
+	return strings.Join(meshMessages, ";"), nil
+}
diff --git a/internal/util/reports_mesh.go b/internal/util/reports_mesh.go
new file mode 100644
index 000000000..409f2e930
--- /dev/null
+++ b/internal/util/reports_mesh.go
@@ -0,0 +1,110 @@
+package util
+
+import (
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/meshdetect"
+)
+
+var meshKindShortNames = map[meshdetect.MeshKind]string{
+	meshdetect.MeshKindIstio:      "i",
+	meshdetect.MeshKindLinkerd:    "l",
+	meshdetect.MeshKindKuma:       "k",
+	meshdetect.MeshKindKongMesh:   "km",
+	meshdetect.MeshKindConsul:     "c",
+	meshdetect.MeshKindTraefik:    "t",
+	meshdetect.MeshKindAWSAppMesh: "a",
+}
+
+func serializeMeshDeploymentResults(
+	results map[meshdetect.MeshKind]*meshdetect.DeploymentResults) string {
+	if results == nil {
+		return ""
+	}
+
+	signals := []string{}
+	for _, meshKind := range meshdetect.MeshesToDetect {
+		result := results[meshKind]
+		if result == nil {
+			continue
+		}
+		// signal3: service exists
+		if result.ServiceExists {
+			signals = append(signals, meshKindShortNames[meshKind]+"3")
+		}
+	}
+
+	if len(signals) > 0 {
+		// sort the signals (in alphabetical order),
+		// then join them together to produce a consistent output for same results.
+		sort.Strings(signals)
+		return "mdep=\"" + strings.Join(signals, ",") + "\""
+	}
+
+	return ""
+}
+
+func serializeMeshRunUnderResults(
+	results map[meshdetect.MeshKind]*meshdetect.RunUnderResults) string {
+	if results == nil {
+		return ""
+	}
+
+	signals := []string{}
+	for _, meshKind := range meshdetect.MeshesToDetect {
+		result := results[meshKind]
+		if result == nil {
+			continue
+		}
+
+		// signal2: pod/service has annotation
+		if result.PodOrServiceAnnotation {
+			signals = append(signals, meshKindShortNames[meshKind]+"2")
+		}
+		// signal3: sidecar injected
+		if result.SidecarContainerInjected {
+			signals = append(signals, meshKindShortNames[meshKind]+"3")
+		}
+		// signal4: init container injected
+		if result.InitContainerInjected {
+			signals = append(signals, meshKindShortNames[meshKind]+"4")
+		}
+	}
+
+	if len(signals) > 0 {
+		// sort the signals to produce a constistent output.
+		sort.Strings(signals)
+		return "kinm=\"" + strings.Join(signals, ",") + "\""
+	}
+	return ""
+}
+
+func serializeMeshServiceDistribution(
+	result *meshdetect.ServiceDistributionResults) string {
+	if result == nil {
+		return ""
+	}
+
+	// format: mdist="all100,a10,i20,k50,km50"
+	serializedStr := "mdist=\""
+	serializedStr = serializedStr + "all" + strconv.Itoa(result.TotalServices)
+	if result.MeshDistribution != nil {
+		// append number of services running in the mesh, if there are any.
+		var signals []string
+		for _, meshKind := range meshdetect.MeshesToDetect {
+			num := result.MeshDistribution[meshKind]
+			if num > 0 {
+				signals = append(signals, meshKindShortNames[meshKind]+strconv.Itoa(num))
+			}
+		}
+
+		if len(signals) > 0 {
+			// sort the signals to produce a constistent output.
+			sort.Strings(signals)
+			serializedStr = serializedStr + "," + strings.Join(signals, ",")
+		}
+	}
+	return serializedStr + "\""
+}
diff --git a/third_party/go.mod b/third_party/go.mod
index 2bf1f6a55..15028094c 100644
--- a/third_party/go.mod
+++ b/third_party/go.mod
@@ -4,7 +4,7 @@ go 1.18
 
 require (
 	github.com/golangci/golangci-lint v1.46.2
-	k8s.io/code-generator v0.24.0
+	k8s.io/code-generator v0.24.2
 	sigs.k8s.io/controller-tools v0.7.0
 	sigs.k8s.io/kustomize/kustomize/v4 v4.5.5
 )
diff --git a/third_party/go.sum b/third_party/go.sum
index 8a65a4850..0b888256c 100644
--- a/third_party/go.sum
+++ b/third_party/go.sum
@@ -1644,8 +1644,9 @@ k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA=
 k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
 k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw=
 k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
-k8s.io/code-generator v0.24.0 h1:7v52LjqCntfGxV9x8c57gkhDqkMHd0Z2jfRqGr6it6g=
 k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
+k8s.io/code-generator v0.24.2 h1:EGeRWzJrpwi6T6CvoNl0spM6fnAnOdCr0rz7H4NU1rk=
+k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
 k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
 k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=


Test patch:
diff --git a/internal/meshdetect/detector_test.go b/internal/meshdetect/detector_test.go
new file mode 100644
index 000000000..55feb33f4
--- /dev/null
+++ b/internal/meshdetect/detector_test.go
@@ -0,0 +1,505 @@
+package meshdetect
+
+import (
+	"context"
+	"testing"
+
+	"github.com/go-logr/logr"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func TestDetectMeshDeployment(t *testing.T) {
+	testScheme := runtime.NewScheme()
+	err := corev1.AddToScheme(testScheme)
+	require.NoErrorf(t, err, "should add corev1 to scheme successfully")
+
+	b := fake.NewClientBuilder().WithScheme(testScheme)
+	b.WithObjects(
+		// add services.
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "istio-system",
+				Name:      "istiod",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kuma-sys",
+				Name:      "kong-mesh-control-plane",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "default",
+				Name:      "traefik-mesh-controller",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "consul-mesh",
+				Name:      "consul-server",
+			},
+		},
+	)
+
+	c := b.Build()
+	d := &Detector{
+		Client:       c,
+		PodNamespace: "kong",
+		PodName:      "kic-1",
+		Logger:       logr.Discard(),
+	}
+
+	res := d.DetectMeshDeployment(context.Background())
+	expected := map[MeshKind]*DeploymentResults{
+		MeshKindIstio: {
+			ServiceExists: true,
+		},
+		MeshKindLinkerd: {
+			ServiceExists: false,
+		},
+		MeshKindKuma: {
+			ServiceExists: false,
+		},
+		MeshKindKongMesh: {
+			ServiceExists: true,
+		},
+		MeshKindConsul: {
+			ServiceExists: true,
+		},
+		MeshKindTraefik: {
+			ServiceExists: true,
+		},
+		MeshKindAWSAppMesh: {
+			ServiceExists: false,
+		},
+	}
+
+	for _, meshKind := range MeshesToDetect {
+		require.Equalf(t, expected[meshKind], res[meshKind], "detection result should be the same for mesh %s", meshKind)
+	}
+}
+
+func TestDetectRunUnder(t *testing.T) {
+	b := fake.NewClientBuilder()
+	b.WithObjects(
+		// add KIC pod.
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kong",
+				Name:      "kong-ingress-controller",
+				Annotations: map[string]string{
+					"sidecar.istio.io/status":  "injected",
+					"linkerd.io/proxy-version": "1.0.0",
+					"kuma.io/sidecar-injected": "true",
+				},
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					// KIC container
+					{
+						Name:  "ingress-controller",
+						Image: "kong/kubernetes-ingress-controller:2.4",
+					},
+					// sidecars
+					{Name: "istio-proxy"},
+					{Name: "kuma-sidecar"},
+					{
+						Name:  "envoy",
+						Image: "public.ecr.aws/appmesh/aws-appmesh-envoy:v1.22.2.0-prod",
+					},
+				},
+				// init containers.
+				InitContainers: []corev1.Container{
+					{Name: "istio-init"},
+				},
+			},
+		},
+		// add kong-proxy service.
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kong",
+				Name:      "kong-proxy",
+				Annotations: map[string]string{
+					"mesh.traefik.io/traffic-type": "HTTP",
+				},
+			},
+		},
+		// add another namespace.
+		&corev1.Namespace{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "kong-2",
+				Annotations: map[string]string{
+					"linkerd.io/inject": "enabled",
+				},
+				Labels: map[string]string{
+					"appmesh.k8s.aws/sidecarInjectorWebhook": "enabled",
+				},
+			},
+		},
+		// add another KIC pod and kong-proxy service.
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kong-2",
+				Name:      "kong-ingress-controller",
+				Annotations: map[string]string{
+					"linkerd.io/proxy-version":                   "1.0.0",
+					"consul.hashicorp.com/connect-inject-status": "injected",
+				},
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					// KIC container
+					{
+						Name:  "ingress-controller",
+						Image: "kong/kubernetes-ingress-controller:2.4",
+					},
+					// sidecars
+					{Name: "linkerd-proxy"},
+					{Name: "envoy-sidecar"},
+				},
+				// init containers.
+				InitContainers: []corev1.Container{
+					{Name: "linkerd-init"},
+					{Name: "consul-connect-inject-init"},
+				},
+			},
+		},
+		// add a KIC pod without a publishing service, and no injection.
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kong-3",
+				Name:      "kong-ingress-controller",
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					// KIC container
+					{
+						Name:  "ingress-controller",
+						Image: "kong/kubernetes-ingress-controller:2.4",
+					},
+				},
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "kong-2",
+				Name:      "kong-proxy",
+				Annotations: map[string]string{
+					"mesh.traefik.io/traffic-type": "UDP",
+				},
+			},
+		},
+	)
+
+	testCases := []struct {
+		caseName        string
+		podNamespace    string
+		podName         string
+		expectedResults map[MeshKind]*RunUnderResults
+	}{
+		{
+			caseName:     "injected-istio,kuma,traefik,aws;annotation-linkerd",
+			podNamespace: "kong",
+			podName:      "kong-ingress-controller",
+			expectedResults: map[MeshKind]*RunUnderResults{
+				MeshKindIstio: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    true,
+				},
+				MeshKindLinkerd: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: false,
+					InitContainerInjected:    false,
+				},
+				MeshKindKuma: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    false,
+				},
+				MeshKindKongMesh: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    false,
+				},
+				MeshKindConsul: {
+					// all false
+				},
+				MeshKindTraefik: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: false,
+					InitContainerInjected:    false,
+				},
+				MeshKindAWSAppMesh: {
+					PodOrServiceAnnotation:   false,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    false,
+				},
+			},
+		},
+		{
+			caseName:     "injected-linkerd,consul",
+			podNamespace: "kong-2",
+			podName:      "kong-ingress-controller",
+			expectedResults: map[MeshKind]*RunUnderResults{
+				MeshKindIstio: {
+					// all false
+				},
+				MeshKindLinkerd: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    true,
+				},
+				MeshKindKuma: {
+					// all false
+				},
+				MeshKindKongMesh: {
+					// all false
+				},
+				MeshKindConsul: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    true,
+				},
+				MeshKindTraefik: {
+					// all false
+				},
+				MeshKindAWSAppMesh: {
+					PodOrServiceAnnotation:   false,
+					SidecarContainerInjected: false,
+					InitContainerInjected:    false,
+				},
+			},
+		},
+		{
+			caseName:     "nothing injected",
+			podNamespace: "kong-3",
+			podName:      "kong-ingress-controller",
+			expectedResults: map[MeshKind]*RunUnderResults{
+				// all mesh kinds -> all false
+				MeshKindIstio:      {},
+				MeshKindLinkerd:    {},
+				MeshKindKuma:       {},
+				MeshKindKongMesh:   {},
+				MeshKindConsul:     {},
+				MeshKindTraefik:    {},
+				MeshKindAWSAppMesh: {},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.caseName, func(t *testing.T) {
+			c := b.Build()
+			d := &Detector{
+				Client:       c,
+				PodNamespace: tc.podNamespace,
+				PodName:      tc.podName,
+				Logger:       logr.Discard(),
+			}
+			d.PublishServiceName = tc.podNamespace + "/kong-proxy"
+			res := d.DetectRunUnder(context.Background())
+			for _, meshKind := range MeshesToDetect {
+				require.Equalf(t, tc.expectedResults[meshKind], res[meshKind],
+					"test case %s: detection result should be same for mesh %s", tc.caseName, meshKind)
+			}
+		})
+
+	}
+}
+
+func TestDetectServiceDistribution(t *testing.T) {
+	b := fake.NewClientBuilder()
+	// add services/endpoints/pods.
+	b.WithObjects(
+		// services.
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service1",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service2",
+			},
+		},
+		&corev1.Service{
+			// service with no available endpoints.
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service3",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "service1",
+			},
+		},
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "service2",
+				Annotations: map[string]string{
+					"mesh.traefik.io/traffic-type": "TCP",
+				},
+			},
+		},
+		// service with no endpoints.
+		&corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "service3",
+			},
+		},
+		// endpoints.
+		&corev1.Endpoints{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service1",
+			},
+			Subsets: []corev1.EndpointSubset{
+				{
+					Addresses: []corev1.EndpointAddress{
+						{
+							TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "ns1", Name: "pod1"},
+						},
+					},
+				},
+			},
+		},
+		&corev1.Endpoints{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service2",
+			},
+			Subsets: []corev1.EndpointSubset{
+				{
+					Addresses: []corev1.EndpointAddress{
+						{
+							TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "ns1", Name: "pod2"},
+						},
+					},
+				},
+			},
+		},
+		&corev1.Endpoints{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "service3",
+			},
+			// endpoints with no subsets.
+		},
+		&corev1.Endpoints{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "service1",
+			},
+			Subsets: []corev1.EndpointSubset{
+				{
+					Addresses: []corev1.EndpointAddress{
+						{
+							TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "ns2", Name: "pod1"},
+						},
+					},
+				},
+			},
+		},
+		&corev1.Endpoints{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "service2",
+			},
+			Subsets: []corev1.EndpointSubset{
+				{
+					Addresses: []corev1.EndpointAddress{
+						{
+							TargetRef: &corev1.ObjectReference{Kind: "Pod", Namespace: "ns2", Name: "pod2"},
+						},
+						{},
+					},
+				},
+			},
+		},
+		// pods.
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "pod1",
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					{Name: "worker"},
+					{Name: "istio-proxy"},
+				},
+			},
+		},
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns1",
+				Name:      "pod2",
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					{Name: "worker"},
+					{Name: "kuma-sidecar"},
+				},
+			},
+		},
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "pod1",
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					{Name: "worker"},
+					{Name: "istio-proxy"},
+					{Name: "linkerd-proxy"},
+				},
+			},
+		},
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "ns2",
+				Name:      "pod2",
+			},
+			Spec: corev1.PodSpec{
+				Containers: []corev1.Container{
+					{Name: "worker"},
+				},
+			},
+		},
+	)
+
+	c := b.Build()
+	d := &Detector{
+		Client: c,
+		Logger: logr.Discard(),
+	}
+
+	expectedTotal := 6
+	expected := map[MeshKind]int{
+		MeshKindIstio:    2,
+		MeshKindLinkerd:  1,
+		MeshKindKuma:     1,
+		MeshKindKongMesh: 1,
+		MeshKindTraefik:  1,
+	}
+
+	res, err := d.DetectServiceDistribution(context.Background())
+	require.NoErrorf(t, err, "should not return error in detecting service distribution")
+	require.Equalf(t, expectedTotal, res.TotalServices, "total number of services should be the same")
+	for _, meshKind := range MeshesToDetect {
+		require.Equalf(t, expected[meshKind], res.MeshDistribution[meshKind],
+			"service within mesh %s should be same as expected", meshKind)
+	}
+}
diff --git a/internal/util/reports_mesh_test.go b/internal/util/reports_mesh_test.go
new file mode 100644
index 000000000..8330296ce
--- /dev/null
+++ b/internal/util/reports_mesh_test.go
@@ -0,0 +1,168 @@
+package util
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/meshdetect"
+)
+
+func TestSerializeMeshDeploymentResults(t *testing.T) {
+	testCases := []struct {
+		caseName      string
+		results       map[meshdetect.MeshKind]*meshdetect.DeploymentResults
+		serializedStr string
+	}{
+		{
+			caseName: "deployment:kong-mesh",
+			results: map[meshdetect.MeshKind]*meshdetect.DeploymentResults{
+				meshdetect.MeshKindKongMesh: {
+
+					ServiceExists: true,
+				},
+			},
+			serializedStr: "mdep=\"km3\"",
+		},
+		{
+			caseName: "deployment:traefik",
+			results: map[meshdetect.MeshKind]*meshdetect.DeploymentResults{
+				meshdetect.MeshKindTraefik: {
+					ServiceExists: true,
+				},
+			},
+			serializedStr: "mdep=\"t3\"",
+		},
+		{
+			caseName: "deployment:consul,aws-app-mesh",
+			results: map[meshdetect.MeshKind]*meshdetect.DeploymentResults{
+				meshdetect.MeshKindConsul: {
+					ServiceExists: true,
+				},
+				meshdetect.MeshKindAWSAppMesh: {
+					ServiceExists: true,
+				},
+			},
+			serializedStr: "mdep=\"a3,c3\"",
+		},
+		{
+			caseName:      "deployment:nil results should produce empty string",
+			results:       nil,
+			serializedStr: "",
+		},
+	}
+
+	for _, tc := range testCases {
+		// t.Run runs function in separate goroutine, so we need to assign tc to a local variable.
+		tc := tc
+		t.Run(tc.caseName, func(t *testing.T) {
+			serialized := serializeMeshDeploymentResults(tc.results)
+			require.Equalf(t, tc.serializedStr, serialized,
+				"case %s: serialized message should be the same as expected", tc.caseName)
+		})
+
+	}
+}
+
+func TestSerializeMeshRunUnderResult(t *testing.T) {
+	testCases := []struct {
+		caseName      string
+		results       map[meshdetect.MeshKind]*meshdetect.RunUnderResults
+		serializedStr string
+	}{
+		{
+			caseName: "run_under:istio,linkerd",
+			results: map[meshdetect.MeshKind]*meshdetect.RunUnderResults{
+				meshdetect.MeshKindIstio: {
+					PodOrServiceAnnotation: true,
+				},
+				meshdetect.MeshKindLinkerd: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    true,
+				},
+			},
+			serializedStr: "kinm=\"i2,l2,l3,l4\"",
+		},
+		{
+			caseName: "run_under:kuma,kong-mesh",
+			results: map[meshdetect.MeshKind]*meshdetect.RunUnderResults{
+				meshdetect.MeshKindKuma: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+				},
+				meshdetect.MeshKindKongMesh: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+				},
+			},
+			serializedStr: "kinm=\"k2,k3,km2,km3\"",
+		},
+		{
+			caseName: "run_under:traefik,aws-app-mesh",
+			results: map[meshdetect.MeshKind]*meshdetect.RunUnderResults{
+				meshdetect.MeshKindTraefik: {
+
+					PodOrServiceAnnotation: true,
+				},
+				meshdetect.MeshKindAWSAppMesh: {
+					PodOrServiceAnnotation:   true,
+					SidecarContainerInjected: true,
+					InitContainerInjected:    true,
+				},
+			},
+			serializedStr: "kinm=\"a2,a3,a4,t2\"",
+		},
+		{
+			caseName:      "run_under:should return empty string for nil results",
+			results:       nil,
+			serializedStr: "",
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.caseName, func(t *testing.T) {
+			serialized := serializeMeshRunUnderResults(tc.results)
+			require.Equalf(t, tc.serializedStr, serialized,
+				"case %s: serialized message should be the same as expected", tc.caseName)
+		})
+	}
+}
+
+func TestSerializeMeshServiceDistribution(t *testing.T) {
+	testCases := []struct {
+		caseName      string
+		results       *meshdetect.ServiceDistributionResults
+		serializedStr string
+	}{
+		{
+			caseName: "service_distribution:istio=32,kuma=50,kong-mesh=50,traefik=20",
+			results: &meshdetect.ServiceDistributionResults{
+				TotalServices: 234,
+				MeshDistribution: map[meshdetect.MeshKind]int{
+					meshdetect.MeshKindIstio:    32,
+					meshdetect.MeshKindKuma:     50,
+					meshdetect.MeshKindKongMesh: 50,
+					meshdetect.MeshKindTraefik:  20,
+				},
+			},
+			serializedStr: "mdist=\"all234,i32,k50,km50,t20\"",
+		},
+		{
+			caseName:      "service_distribution:should return empty string for nil results",
+			results:       nil,
+			serializedStr: "",
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.caseName, func(t *testing.T) {
+			serialized := serializeMeshServiceDistribution(tc.results)
+			require.Equalf(t, tc.serializedStr, serialized,
+				"case %s: serialized message should be the same as expected", tc.caseName)
+		})
+
+	}
+}