You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by as...@apache.org on 2021/04/15 11:54:10 UTC

[camel-k] 02/05: feat(monitoring): Migrate to PodMonitor in Prometheus trait

This is an automated email from the ASF dual-hosted git repository.

astefanutti pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit c61f1400dc78617b9cee46270d62e5ade1572134
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Tue Apr 13 18:47:31 2021 +0200

    feat(monitoring): Migrate to PodMonitor in Prometheus trait
---
 e2e/common/operator_metrics_test.go  |  47 ++++++------
 e2e/common/traits/prometheus_test.go |  36 ++++-----
 pkg/cmd/run_test.go                  |   4 +-
 pkg/trait/container.go               |  43 ++++++-----
 pkg/trait/prometheus.go              | 139 ++++++++++++++---------------------
 pkg/trait/prometheus_test.go         |  82 ++++-----------------
 pkg/trait/route.go                   |   2 +-
 pkg/trait/route_test.go              |   2 +-
 pkg/trait/service.go                 |   5 +-
 pkg/trait/trait_types.go             |  24 ++++++
 pkg/util/kubernetes/collection.go    |  14 ++--
 11 files changed, 167 insertions(+), 231 deletions(-)

diff --git a/e2e/common/operator_metrics_test.go b/e2e/common/operator_metrics_test.go
index 7814eef..fa10779 100644
--- a/e2e/common/operator_metrics_test.go
+++ b/e2e/common/operator_metrics_test.go
@@ -31,14 +31,15 @@ import (
 	. "github.com/onsi/gomega"
 	. "github.com/onsi/gomega/gstruct"
 	"github.com/onsi/gomega/types"
-	v1 "k8s.io/api/core/v1"
+
+	corev1 "k8s.io/api/core/v1"
 
 	prometheus "github.com/prometheus/client_model/go"
 	"github.com/prometheus/common/expfmt"
 
 	. "github.com/apache/camel-k/e2e/support"
 	. "github.com/apache/camel-k/e2e/support/util"
-	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
 func TestMetrics(t *testing.T) {
@@ -47,15 +48,15 @@ func TestMetrics(t *testing.T) {
 		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
 		Expect(Kamel("run", "-n", ns, "files/Java.java",
 			"-t", "prometheus.enabled=true",
-			"-t", "prometheus.service-monitor=false").Execute()).To(Succeed())
-		Eventually(IntegrationPodPhase(ns, name), TestTimeoutMedium).Should(Equal(v1.PodRunning))
-		Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
+			"-t", "prometheus.pod-monitor=false").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, name), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
+		Eventually(IntegrationCondition(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
 		Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
 
 		pod := OperatorPod(ns)()
 		Expect(pod).NotTo(BeNil())
 
-		logs := StructuredLogs(ns, pod.Name, v1.PodLogOptions{})
+		logs := StructuredLogs(ns, pod.Name, corev1.PodLogOptions{})
 		Expect(logs).NotTo(BeEmpty())
 
 		response, err := TestClient().CoreV1().RESTClient().Get().
@@ -80,7 +81,7 @@ func TestMetrics(t *testing.T) {
 				AddStep(MatchFields(IgnoreExtras, Fields{
 					"LoggerName":  Equal("camel-k.controller.build"),
 					"Message":     Equal("Build state transition"),
-					"Phase":       Equal(string(camelv1.BuildPhasePending)),
+					"Phase":       Equal(string(v1.BuildPhasePending)),
 					"RequestName": Equal(build.Name),
 				}), LogEntryNoop).
 				AddStep(MatchFields(IgnoreExtras, Fields{
@@ -188,8 +189,8 @@ func TestMetrics(t *testing.T) {
 			platformReconciled := getMetric(metrics["camel_k_reconciliation_duration_seconds"],
 				MatchFieldsP(IgnoreExtras, Fields{
 					"Label": ConsistOf(
-						label("group", camelv1.SchemeGroupVersion.Group),
-						label("version", camelv1.SchemeGroupVersion.Version),
+						label("group", v1.SchemeGroupVersion.Group),
+						label("version", v1.SchemeGroupVersion.Version),
 						label("kind", "IntegrationPlatform"),
 						label("namespace", ns),
 						label("result", "Reconciled"),
@@ -203,8 +204,8 @@ func TestMetrics(t *testing.T) {
 			platformRequeued := getMetric(metrics["camel_k_reconciliation_duration_seconds"],
 				MatchFieldsP(IgnoreExtras, Fields{
 					"Label": ConsistOf(
-						label("group", camelv1.SchemeGroupVersion.Group),
-						label("version", camelv1.SchemeGroupVersion.Version),
+						label("group", v1.SchemeGroupVersion.Group),
+						label("version", v1.SchemeGroupVersion.Version),
 						label("kind", "IntegrationPlatform"),
 						label("namespace", ns),
 						label("result", "Requeued"),
@@ -236,8 +237,8 @@ func TestMetrics(t *testing.T) {
 					"Type": EqualP(prometheus.MetricType_HISTOGRAM),
 					"Metric": ContainElement(MatchFieldsP(IgnoreExtras, Fields{
 						"Label": ConsistOf(
-							label("group", camelv1.SchemeGroupVersion.Group),
-							label("version", camelv1.SchemeGroupVersion.Version),
+							label("group", v1.SchemeGroupVersion.Group),
+							label("version", v1.SchemeGroupVersion.Version),
 							label("kind", "Integration"),
 							label("namespace", it.Namespace),
 							label("result", "Reconciled"),
@@ -268,8 +269,8 @@ func TestMetrics(t *testing.T) {
 					"Type": EqualP(prometheus.MetricType_HISTOGRAM),
 					"Metric": ContainElement(MatchFieldsP(IgnoreExtras, Fields{
 						"Label": ConsistOf(
-							label("group", camelv1.SchemeGroupVersion.Group),
-							label("version", camelv1.SchemeGroupVersion.Version),
+							label("group", v1.SchemeGroupVersion.Group),
+							label("version", v1.SchemeGroupVersion.Version),
 							label("kind", "IntegrationKit"),
 							label("namespace", it.Status.IntegrationKit.Namespace),
 							label("result", "Reconciled"),
@@ -295,8 +296,8 @@ func TestMetrics(t *testing.T) {
 			buildReconciled := getMetric(metrics["camel_k_reconciliation_duration_seconds"],
 				MatchFieldsP(IgnoreExtras, Fields{
 					"Label": ConsistOf(
-						label("group", camelv1.SchemeGroupVersion.Group),
-						label("version", camelv1.SchemeGroupVersion.Version),
+						label("group", v1.SchemeGroupVersion.Group),
+						label("version", v1.SchemeGroupVersion.Version),
 						label("kind", "Build"),
 						label("namespace", build.Namespace),
 						label("result", "Reconciled"),
@@ -310,8 +311,8 @@ func TestMetrics(t *testing.T) {
 			buildRequeued := getMetric(metrics["camel_k_reconciliation_duration_seconds"],
 				MatchFieldsP(IgnoreExtras, Fields{
 					"Label": ConsistOf(
-						label("group", camelv1.SchemeGroupVersion.Group),
-						label("version", camelv1.SchemeGroupVersion.Version),
+						label("group", v1.SchemeGroupVersion.Group),
+						label("version", v1.SchemeGroupVersion.Version),
 						label("kind", "Build"),
 						label("namespace", build.Namespace),
 						label("result", "Requeued"),
@@ -336,7 +337,7 @@ func TestMetrics(t *testing.T) {
 				AddStep(MatchFields(IgnoreExtras, Fields{
 					"LoggerName":  Equal("camel-k.controller.build"),
 					"Message":     Equal("Build state transition"),
-					"Phase":       Equal(string(camelv1.BuildPhasePending)),
+					"Phase":       Equal(string(v1.BuildPhasePending)),
 					"RequestName": Equal(build.Name),
 				}), func(l *LogEntry) { ts2 = l.Timestamp.Time }).
 				Walk()
@@ -385,7 +386,7 @@ func TestMetrics(t *testing.T) {
 			ts1 = it.Status.InitializationTimestamp.Time
 			Expect(ts1).NotTo(BeZero())
 			// The end time is reported into the ready condition first truthy time
-			ts2 = it.Status.GetCondition(camelv1.IntegrationConditionReady).FirstTruthyTime.Time
+			ts2 = it.Status.GetCondition(v1.IntegrationConditionReady).FirstTruthyTime.Time
 			Expect(ts2).NotTo(BeZero())
 
 			duration := ts2.Sub(ts1)
@@ -396,8 +397,8 @@ func TestMetrics(t *testing.T) {
 					"LoggerName":  Equal("camel-k.controller.integration"),
 					"Message":     Equal("Reconciling Integration"),
 					"RequestName": Equal(it.Name),
-					"PhaseFrom":   Equal(string(camelv1.IntegrationPhaseInitialization)),
-					"PhaseTo":     Equal(string(camelv1.IntegrationPhaseBuildingKit)),
+					"PhaseFrom":   Equal(string(v1.IntegrationPhaseInitialization)),
+					"PhaseTo":     Equal(string(v1.IntegrationPhaseBuildingKit)),
 				}), func(l *LogEntry) { ts1 = l.Timestamp.Time }).
 				AddStep(MatchFields(IgnoreExtras, Fields{
 					"LoggerName":  Equal("camel-k.controller.integration"),
diff --git a/e2e/common/traits/prometheus_test.go b/e2e/common/traits/prometheus_test.go
index e3204ca..91ce909 100644
--- a/e2e/common/traits/prometheus_test.go
+++ b/e2e/common/traits/prometheus_test.go
@@ -29,9 +29,9 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	v1 "k8s.io/api/core/v1"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/errors"
 
-	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
 	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
 
@@ -45,14 +45,14 @@ func TestPrometheusTrait(t *testing.T) {
 		ocp, err := openshift.IsOpenShift(TestClient())
 		assert.Nil(t, err)
 
-		// suppress Service Monitor for the time being as CI test runs on OCP 3.11
-		createServiceMonitor := false
+		// Do not create PodMonitor for the time being as CI test runs on OCP 3.11
+		createPodMonitor := false
 
 		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
 
 		Expect(Kamel("run", "-n", ns, "files/Java.java",
 			"-t", "prometheus.enabled=true",
-			"-t", fmt.Sprintf("prometheus.service-monitor=%v", createServiceMonitor)).Execute()).To(Succeed())
+			"-t", fmt.Sprintf("prometheus.pod-monitor=%v", createPodMonitor)).Execute()).To(Succeed())
 		Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutLong).Should(Equal(v1.PodRunning))
 		Eventually(IntegrationCondition(ns, "java", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
 		Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
@@ -67,15 +67,9 @@ func TestPrometheusTrait(t *testing.T) {
 			assert.Contains(t, string(response), "camel.route.exchanges.total")
 		})
 
-		t.Run("Service is created", func(t *testing.T) {
-			// service name is "<integration name>-prometheus"
-			service := Service(ns, "java-prometheus")
-			Eventually(service, TestTimeoutShort).ShouldNot(BeNil())
-		})
-
-		if ocp && createServiceMonitor {
-			t.Run("Service Monitor is created on OpenShift", func(t *testing.T) {
-				sm := serviceMonitor(ns, "java")
+		if ocp && createPodMonitor {
+			t.Run("PodMonitor is created", func(t *testing.T) {
+				sm := podMonitor(ns, "java")
 				Eventually(sm, TestTimeoutShort).ShouldNot(BeNil())
 			})
 		}
@@ -84,19 +78,19 @@ func TestPrometheusTrait(t *testing.T) {
 	})
 }
 
-func serviceMonitor(ns string, name string) func() *monitoringv1.ServiceMonitor {
-	return func() *monitoringv1.ServiceMonitor {
-		sm := monitoringv1.ServiceMonitor{}
-		key := k8sclient.ObjectKey{
+func podMonitor(ns string, name string) func() *monitoringv1.PodMonitor {
+	return func() *monitoringv1.PodMonitor {
+		pm := monitoringv1.PodMonitor{}
+		key := ctrl.ObjectKey{
 			Namespace: ns,
 			Name:      name,
 		}
-		err := TestClient().Get(TestContext, key, &sm)
-		if err != nil && k8serrors.IsNotFound(err) {
+		err := TestClient().Get(TestContext, key, &pm)
+		if err != nil && errors.IsNotFound(err) {
 			return nil
 		} else if err != nil {
 			panic(err)
 		}
-		return &sm
+		return &pm
 	}
 }
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index eddb32f..212765b 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -353,7 +353,7 @@ func TestConfigureTraits(t *testing.T) {
 		"--trait", "container.probes-enabled=false",
 		"--trait", "environment.container-meta=false",
 		"--trait", "jvm.print-command=false",
-		"--trait", "prometheus.service-monitor=false",
+		"--trait", "prometheus.pod-monitor=false",
 		"example.js")
 	if err != nil {
 		t.Error(err)
@@ -372,7 +372,7 @@ func TestConfigureTraits(t *testing.T) {
 	assertTraitConfiguration(t, traits, "container", `{"probesEnabled":false}`)
 	assertTraitConfiguration(t, traits, "environment", `{"containerMeta":false}`)
 	assertTraitConfiguration(t, traits, "jvm", `{"printCommand":false}`)
-	assertTraitConfiguration(t, traits, "prometheus", `{"serviceMonitor":false}`)
+	assertTraitConfiguration(t, traits, "prometheus", `{"podMonitor":false}`)
 }
 
 func assertTraitConfiguration(t *testing.T, traits map[string]v1.TraitSpec, trait string, expected string) {
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index 238a827..11dcb61 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -36,11 +36,12 @@ import (
 )
 
 const (
-	defaultContainerName = "integration"
-	defaultContainerPort = 8080
-	defaultServicePort   = 80
-	defaultProbePath     = "/health"
-	containerTraitID     = "container"
+	defaultContainerName     = "integration"
+	defaultContainerPort     = 8080
+	defaultContainerPortName = "http"
+	defaultServicePort       = 80
+	defaultProbePath         = "/health"
+	containerTraitID         = "container"
 )
 
 // The Container trait can be used to configure properties of the container where the integration will run.
@@ -111,9 +112,8 @@ func newContainerTrait() Trait {
 	return &containerTrait{
 		BaseTrait:       NewBaseTrait(containerTraitID, 1600),
 		Port:            defaultContainerPort,
-		PortName:        httpPortName,
 		ServicePort:     defaultServicePort,
-		ServicePortName: httpPortName,
+		ServicePortName: defaultContainerPortName,
 		Name:            defaultContainerName,
 		ProbesEnabled:   util.BoolP(false),
 		ProbePath:       defaultProbePath,
@@ -205,11 +205,13 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 		return err
 	}
 
-	//
+	portName := t.PortName
+	if portName == "" {
+		portName = defaultContainerPortName
+	}
 	// Deployment
-	//
 	if err := e.Resources.VisitDeploymentE(func(deployment *appsv1.Deployment) error {
-		if util.IsTrue(t.ProbesEnabled) && t.PortName == httpPortName {
+		if util.IsTrue(t.ProbesEnabled) && portName == defaultContainerPortName {
 			if err := t.configureProbes(e, &container, t.Port, t.ProbePath); err != nil {
 				return err
 			}
@@ -234,11 +236,9 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 		return err
 	}
 
-	//
 	// Knative Service
-	//
 	if err := e.Resources.VisitKnativeServiceE(func(service *serving.Service) error {
-		if util.IsTrue(t.ProbesEnabled) && t.PortName == httpPortName {
+		if util.IsTrue(t.ProbesEnabled) && portName == defaultContainerPortName {
 			// don't set the port on Knative service as it is not allowed.
 			if err := t.configureProbes(e, &container, 0, t.ProbePath); err != nil {
 				return err
@@ -275,11 +275,9 @@ func (t *containerTrait) configureContainer(e *Environment) error {
 		return err
 	}
 
-	//
 	// CronJob
-	//
 	if err := e.Resources.VisitCronJobE(func(cron *v1beta1.CronJob) error {
-		if util.IsTrue(t.ProbesEnabled) && t.PortName == httpPortName {
+		if util.IsTrue(t.ProbesEnabled) && portName == defaultContainerPortName {
 			if err := t.configureProbes(e, &container, t.Port, t.ProbePath); err != nil {
 				return err
 			}
@@ -313,8 +311,13 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont
 		return
 	}
 
+	name := t.PortName
+	if name == "" {
+		name = defaultContainerPortName
+	}
+
 	containerPort := corev1.ContainerPort{
-		Name:          t.PortName,
+		Name:          name,
 		ContainerPort: int32(t.Port),
 		Protocol:      corev1.ProtocolTCP,
 	}
@@ -323,7 +326,7 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont
 		Name:       t.ServicePortName,
 		Port:       int32(t.ServicePort),
 		Protocol:   corev1.ProtocolTCP,
-		TargetPort: intstr.FromString(t.PortName),
+		TargetPort: intstr.FromString(name),
 	}
 
 	e.Integration.Status.SetCondition(
@@ -345,9 +348,7 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont
 }
 
 func (t *containerTrait) configureResources(_ *Environment, container *corev1.Container) {
-	//
 	// Requests
-	//
 	if container.Resources.Requests == nil {
 		container.Resources.Requests = make(corev1.ResourceList)
 	}
@@ -369,9 +370,7 @@ func (t *containerTrait) configureResources(_ *Environment, container *corev1.Co
 		}
 	}
 
-	//
 	// Limits
-	//
 	if container.Resources.Limits == nil {
 		container.Resources.Limits = make(corev1.ResourceList)
 	}
diff --git a/pkg/trait/prometheus.go b/pkg/trait/prometheus.go
index 7a5a87d..8ef81b5 100644
--- a/pkg/trait/prometheus.go
+++ b/pkg/trait/prometheus.go
@@ -22,7 +22,6 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
 
 	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
 
@@ -30,35 +29,30 @@ import (
 	"github.com/apache/camel-k/pkg/util"
 )
 
-// The Prometheus trait configures a Prometheus-compatible endpoint. This trait also exposes the integration with
-// `Service` and `ServiceMonitor` resources, so that the endpoint can be scraped automatically, when using the
-// Prometheus Operator.
+// The Prometheus trait configures a Prometheus-compatible endpoint. It also creates a `PodMonitor` resource,
+// so that the endpoint can be scraped automatically, when using the Prometheus operator.
 //
 // The metrics are exposed using MicroProfile Metrics.
 //
-// WARNING: The creation of the `ServiceMonitor` resource requires the https://github.com/coreos/prometheus-operator[Prometheus Operator]
+// WARNING: The creation of the `PodMonitor` resource requires the https://github.com/coreos/prometheus-operator[Prometheus Operator]
 // custom resource definition to be installed.
-// You can set `service-monitor` to `false` for the Prometheus trait to work without the Prometheus Operator.
+// You can set `pod-monitor` to `false` for the Prometheus trait to work without the Prometheus Operator.
 //
 // The Prometheus trait is disabled by default.
 //
 // +camel-k:trait=prometheus
 type prometheusTrait struct {
 	BaseTrait `property:",squash"`
-	// The Prometheus endpoint port (default `9779`, or `8080` with Quarkus).
-	Port *int `property:"port" json:"port,omitempty"`
-	// Whether a `ServiceMonitor` resource is created (default `true`).
-	ServiceMonitor *bool `property:"service-monitor" json:"serviceMonitor,omitempty"`
-	// The `ServiceMonitor` resource labels, applicable when `service-monitor` is `true`.
-	ServiceMonitorLabels []string `property:"service-monitor-labels" json:"serviceMonitorLabels,omitempty"`
+	// Whether a `PodMonitor` resource is created (default `true`).
+	PodMonitor *bool `property:"pod-monitor" json:"podMonitor,omitempty"`
+	// The `PodMonitor` resource labels, applicable when `pod-monitor` is `true`.
+	PodMonitorLabels []string `property:"pod-monitor-labels" json:"podMonitorLabels,omitempty"`
 }
 
-const prometheusPortName = "prometheus"
-
 func newPrometheusTrait() Trait {
 	return &prometheusTrait{
-		BaseTrait:      NewBaseTrait("prometheus", 1900),
-		ServiceMonitor: util.BoolP(true),
+		BaseTrait:  NewBaseTrait("prometheus", 1900),
+		PodMonitor: util.BoolP(true),
 	}
 }
 
@@ -70,7 +64,7 @@ func (t *prometheusTrait) Configure(e *Environment) (bool, error) {
 	), nil
 }
 
-func (t *prometheusTrait) Apply(e *Environment) error {
+func (t *prometheusTrait) Apply(e *Environment) (err error) {
 	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
 		// Add the Camel Quarkus MP Metrics extension
 		util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, "mvn:org.apache.camel.quarkus:camel-quarkus-microprofile-metrics")
@@ -94,60 +88,38 @@ func (t *prometheusTrait) Apply(e *Environment) error {
 		Reason: v1.IntegrationConditionPrometheusAvailableReason,
 	}
 
-	port := 8080
-	if t.Port == nil {
-		t.Port = &port
-	}
-
-	// Configure the Prometheus container port
-	containerPort := t.getContainerPort()
 	controller, err := e.DetermineControllerStrategy()
 	if err != nil {
 		return err
 	}
-	// Skip declaring the Prometheus port when Knative is enabled, as only one container port is supported
-	if controller != ControllerStrategyKnativeService {
+
+	containerPort := e.getIntegrationContainerPort()
+	if containerPort == nil {
+		containerPort = t.getContainerPort(e, controller)
 		container.Ports = append(container.Ports, *containerPort)
 	}
+
 	condition.Message = fmt.Sprintf("%s(%d)", container.Name, containerPort.ContainerPort)
 
-	// Retrieve the service or create a new one if the service trait is enabled
-	serviceEnabled := false
-	service := e.Resources.GetServiceForIntegration(e.Integration)
-	if service == nil {
-		trait := e.Catalog.GetTrait(serviceTraitID)
-		if serviceTrait, ok := trait.(*serviceTrait); ok {
-			serviceEnabled = serviceTrait.isEnabled()
-		}
-		if serviceEnabled {
-			// Add a new service if not already created
-			service = getServiceFor(e)
-			// Override the service name if none exists.
-			// This is required for Knative Serving, that checks no standard eponymous service exist
-			service.Name += "-prometheus"
-			e.Resources.Add(service)
+	// Add the PodMonitor resource
+	if util.IsTrue(t.PodMonitor) {
+		portName := containerPort.Name
+		// Knative defaults to naming the userland container port "user-port".
+		// Let's rely on that default, granted it is not officially part of the Knative
+		// runtime contract.
+		// See https://github.com/knative/specs/blob/main/specs/serving/runtime-contract.md
+		if portName == "" && controller == ControllerStrategyKnativeService {
+			portName = "user-port"
 		}
-	} else {
-		serviceEnabled = true
-	}
 
-	// Add the service port and service monitor resource
-	if serviceEnabled {
-		servicePort := t.getServicePort()
-		service.Spec.Ports = append(service.Spec.Ports, *servicePort)
-		condition.Message = fmt.Sprintf("%s(%s/%d) -> ", service.Name, servicePort.Name, servicePort.Port) + condition.Message
-
-		// Add the ServiceMonitor resource
-		if util.IsNilOrTrue(t.ServiceMonitor) {
-			smt, err := t.getServiceMonitorFor(e)
-			if err != nil {
-				return err
-			}
-			e.Resources.Add(smt)
+		podMonitor, err := t.getPodMonitorFor(e, portName)
+		if err != nil {
+			return err
 		}
+		e.Resources.Add(podMonitor)
+		condition.Message = fmt.Sprintf("PodMonitor (%s) -> ", podMonitor.Name) + condition.Message
 	} else {
-		condition.Status = corev1.ConditionFalse
-		condition.Reason = v1.IntegrationConditionServiceNotAvailableReason
+		condition.Message = "ContainerPort " + condition.Message
 	}
 
 	e.Integration.Status.SetConditions(condition)
@@ -155,55 +127,58 @@ func (t *prometheusTrait) Apply(e *Environment) error {
 	return nil
 }
 
-func (t *prometheusTrait) getContainerPort() *corev1.ContainerPort {
-	containerPort := corev1.ContainerPort{
-		ContainerPort: int32(*t.Port),
-		Protocol:      corev1.ProtocolTCP,
+func (t *prometheusTrait) getContainerPort(e *Environment, controller ControllerStrategy) *corev1.ContainerPort {
+	var name string
+	var port int
+
+	if t := e.Catalog.GetTrait(containerTraitID); t != nil {
+		name = t.(*containerTrait).PortName
+		port = t.(*containerTrait).Port
 	}
-	return &containerPort
-}
 
-func (t *prometheusTrait) getServicePort() *corev1.ServicePort {
-	servicePort := corev1.ServicePort{
-		Name:     prometheusPortName,
-		Port:     int32(*t.Port),
-		Protocol: corev1.ProtocolTCP,
-		// Avoid relying on named port, as Knative enforces specific values used for content negotiation
-		TargetPort: intstr.FromInt(*t.Port),
+	// Let's rely on Knative default HTTP negotiation
+	if name == "" && controller != ControllerStrategyKnativeService {
+		name = defaultContainerPortName
+	}
+
+	return &corev1.ContainerPort{
+		Name:          name,
+		ContainerPort: int32(port),
+		Protocol:      corev1.ProtocolTCP,
 	}
-	return &servicePort
 }
 
-func (t *prometheusTrait) getServiceMonitorFor(e *Environment) (*monitoringv1.ServiceMonitor, error) {
-	labels, err := keyValuePairArrayAsStringMap(t.ServiceMonitorLabels)
+func (t *prometheusTrait) getPodMonitorFor(e *Environment, portName string) (*monitoringv1.PodMonitor, error) {
+	labels, err := keyValuePairArrayAsStringMap(t.PodMonitorLabels)
 	if err != nil {
 		return nil, err
 	}
 	labels[v1.IntegrationLabel] = e.Integration.Name
 
-	smt := monitoringv1.ServiceMonitor{
+	podMonitor := monitoringv1.PodMonitor{
 		TypeMeta: metav1.TypeMeta{
-			Kind:       "ServiceMonitor",
-			APIVersion: "monitoring.coreos.com/v1",
+			Kind:       "PodMonitor",
+			APIVersion: monitoringv1.SchemeGroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      e.Integration.Name,
 			Namespace: e.Integration.Namespace,
 			Labels:    labels,
 		},
-		Spec: monitoringv1.ServiceMonitorSpec{
+		Spec: monitoringv1.PodMonitorSpec{
 			Selector: metav1.LabelSelector{
 				MatchLabels: map[string]string{
 					v1.IntegrationLabel: e.Integration.Name,
 				},
 			},
-			Endpoints: []monitoringv1.Endpoint{
+			PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{
 				{
+					Port: portName,
 					Path: "/q/metrics",
-					Port: prometheusPortName,
 				},
 			},
 		},
 	}
-	return &smt, nil
+
+	return &podMonitor, nil
 }
diff --git a/pkg/trait/prometheus_test.go b/pkg/trait/prometheus_test.go
index 1bf8d65..670a911 100644
--- a/pkg/trait/prometheus_test.go
+++ b/pkg/trait/prometheus_test.go
@@ -70,16 +70,10 @@ func TestApplyNominalPrometheusTraitDoesSucceed(t *testing.T) {
 	assert.Equal(t, int32(8080), ports[0].ContainerPort)
 	assert.Equal(t, corev1.ProtocolTCP, ports[0].Protocol)
 
-	service := environment.Resources.GetService(func(service *corev1.Service) bool {
-		return service.Name == "integration-name-prometheus"
+	podMonitor := environment.Resources.GetPodMonitor(func(pm *monitoringv1.PodMonitor) bool {
+		return pm.Name == "integration-name"
 	})
-	assert.NotNil(t, service)
-	assert.Len(t, service.Spec.Ports, 1)
-
-	serviceMonitor := environment.Resources.GetServiceMonitor(func(service *monitoringv1.ServiceMonitor) bool {
-		return service.Name == "integration-name"
-	})
-	assert.NotNil(t, serviceMonitor)
+	assert.NotNil(t, podMonitor)
 
 	assert.Len(t, environment.Integration.Status.Conditions, 1)
 	condition := environment.Integration.Status.Conditions[0]
@@ -101,70 +95,22 @@ func TestApplyPrometheusTraitWithoutContainerDoesNotSucceed(t *testing.T) {
 	assert.Equal(t, corev1.ConditionFalse, condition.Status)
 }
 
-func TestApplyPrometheusTraitWithServiceDoesSucceed(t *testing.T) {
-	trait, environment := createNominalPrometheusTest()
-	environment.Resources = kubernetes.NewCollection(
-		&appsv1.Deployment{
-			Spec: appsv1.DeploymentSpec{
-				Template: corev1.PodTemplateSpec{
-					Spec: corev1.PodSpec{
-						Containers: []corev1.Container{
-							{
-								Name: defaultContainerName,
-							},
-						},
-					},
-				},
-			},
-		},
-		&corev1.Service{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "Service",
-				APIVersion: "v1",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "service-name",
-				Namespace: "namespace",
-				Labels: map[string]string{
-					v1.IntegrationLabel:             "integration-name",
-					"camel.apache.org/service.type": v1.ServiceTypeUser,
-				},
-			},
-			Spec: corev1.ServiceSpec{
-				Ports: []corev1.ServicePort{},
-				Selector: map[string]string{
-					v1.IntegrationLabel: "integration-name",
-				},
-			},
-		})
-
-	err := trait.Apply(environment)
-
-	assert.Nil(t, err)
-
-	assert.Len(t, environment.Integration.Status.Conditions, 1)
-	condition := environment.Integration.Status.Conditions[0]
-	assert.Equal(t, v1.IntegrationConditionPrometheusAvailableReason, condition.Reason)
-	assert.Equal(t, corev1.ConditionTrue, condition.Status)
-}
-
-func TestPrometheusTraitGetServiceMonitor(t *testing.T) {
+func TestPrometheusTraitGetPodMonitor(t *testing.T) {
 	trait, environment := createNominalPrometheusTest()
 
-	serviceMonitor, err := trait.getServiceMonitorFor(environment)
+	podMonitor, err := trait.getPodMonitorFor(environment, defaultContainerPortName)
 
 	assert.Nil(t, err)
 
-	assert.NotNil(t, serviceMonitor)
-	assert.Equal(t, "ServiceMonitor", serviceMonitor.Kind)
-	assert.Equal(t, "monitoring.coreos.com/v1", serviceMonitor.APIVersion)
-	assert.Equal(t, "integration-name", serviceMonitor.Name)
-	assert.Equal(t, "integration-namespace", serviceMonitor.Namespace)
-	assert.Equal(t, "integration-name", serviceMonitor.Labels[v1.IntegrationLabel])
-	assert.Equal(t, "integration-name", serviceMonitor.Spec.Selector.MatchLabels[v1.IntegrationLabel])
-	assert.Len(t, serviceMonitor.Spec.Endpoints, 1)
-	assert.Equal(t, "prometheus", serviceMonitor.Spec.Endpoints[0].Port)
-	assert.Equal(t, "/q/metrics", serviceMonitor.Spec.Endpoints[0].Path)
+	assert.NotNil(t, podMonitor)
+	assert.Equal(t, "PodMonitor", podMonitor.Kind)
+	assert.Equal(t, "monitoring.coreos.com/v1", podMonitor.APIVersion)
+	assert.Equal(t, "integration-name", podMonitor.Name)
+	assert.Equal(t, "integration-namespace", podMonitor.Namespace)
+	assert.Equal(t, "integration-name", podMonitor.Labels["camel.apache.org/integration"])
+	assert.Equal(t, "integration-name", podMonitor.Spec.Selector.MatchLabels["camel.apache.org/integration"])
+	assert.Len(t, podMonitor.Spec.PodMetricsEndpoints, 1)
+	assert.Equal(t, defaultContainerPortName, podMonitor.Spec.PodMetricsEndpoints[0].Port)
 }
 
 func createNominalPrometheusTest() (*prometheusTrait, *Environment) {
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index cd830d6..4cc05f6 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -112,7 +112,7 @@ func (t *routeTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *routeTrait) Apply(e *Environment) error {
-	servicePortName := httpPortName
+	servicePortName := defaultContainerPortName
 	dt := e.Catalog.GetTrait(containerTraitID)
 	if dt != nil {
 		servicePortName = dt.(*containerTrait).ServicePortName
diff --git a/pkg/trait/route_test.go b/pkg/trait/route_test.go
index a2c85a4..11b7eea 100644
--- a/pkg/trait/route_test.go
+++ b/pkg/trait/route_test.go
@@ -114,7 +114,7 @@ func TestRoute_Default(t *testing.T) {
 	assert.NotNil(t, route)
 	assert.Nil(t, route.Spec.TLS)
 	assert.NotNil(t, route.Spec.Port)
-	assert.Equal(t, httpPortName, route.Spec.Port.TargetPort.StrVal)
+	assert.Equal(t, defaultContainerPortName, route.Spec.Port.TargetPort.StrVal)
 }
 
 func TestRoute_Disabled(t *testing.T) {
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 2ece2fd..61cf4a9 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -40,10 +40,7 @@ type serviceTrait struct {
 	NodePort *bool `property:"node-port" json:"nodePort,omitempty"`
 }
 
-const (
-	serviceTraitID = "service"
-	httpPortName   = "http"
-)
+const serviceTraitID = "service"
 
 func newServiceTrait() Trait {
 	return &serviceTrait{
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index b3cd991..2ca1095 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -801,6 +801,30 @@ func (e *Environment) getIntegrationContainer() *corev1.Container {
 	return e.Resources.GetContainerByName(containerName)
 }
 
+func (e *Environment) getIntegrationContainerPort() *corev1.ContainerPort {
+	container := e.getIntegrationContainer()
+	if container == nil {
+		return nil
+	}
+
+	portName := ""
+	t := e.Catalog.GetTrait(containerTraitID)
+	if t != nil {
+		portName = t.(*containerTrait).PortName
+	}
+	if portName == "" {
+		portName = defaultContainerPortName
+	}
+
+	for i, port := range container.Ports {
+		if port.Name == portName {
+			return &container.Ports[i]
+		}
+	}
+
+	return nil
+}
+
 func (e *Environment) getAllInterceptors() []string {
 	res := make([]string, 0)
 	util.StringSliceUniqueConcat(&res, e.Interceptors)
diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go
index 008bb66..c5d75dc 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -476,19 +476,19 @@ func (c *Collection) Remove(selector func(runtime.Object) bool) runtime.Object {
 	return nil
 }
 
-func (c *Collection) VisitServiceMonitor(visitor func(*monitoringv1.ServiceMonitor)) {
+func (c *Collection) VisitPodMonitor(visitor func(*monitoringv1.PodMonitor)) {
 	c.Visit(func(res runtime.Object) {
-		if conv, ok := res.(*monitoringv1.ServiceMonitor); ok {
+		if conv, ok := res.(*monitoringv1.PodMonitor); ok {
 			visitor(conv)
 		}
 	})
 }
 
-func (c *Collection) GetServiceMonitor(filter func(*monitoringv1.ServiceMonitor) bool) *monitoringv1.ServiceMonitor {
-	var retValue *monitoringv1.ServiceMonitor
-	c.VisitServiceMonitor(func(serviceMonitor *monitoringv1.ServiceMonitor) {
-		if filter(serviceMonitor) {
-			retValue = serviceMonitor
+func (c *Collection) GetPodMonitor(filter func(*monitoringv1.PodMonitor) bool) *monitoringv1.PodMonitor {
+	var retValue *monitoringv1.PodMonitor
+	c.VisitPodMonitor(func(podMonitor *monitoringv1.PodMonitor) {
+		if filter(podMonitor) {
+			retValue = podMonitor
 		}
 	})
 	return retValue