Repository: apache/camel-k
Pull request #2649 (2021-09-20 12:39:30)

fix: Unify post-build integration phases reconciliation (1.6.x backport)
Back port #2645 to 1.6.x branch.

**Release Note**
```release-note
fix: Unify post-build integration phases reconciliation
```



Patch:
diff --git a/config/manifests/bases/camel-k.clusterserviceversion.yaml b/config/manifests/bases/camel-k.clusterserviceversion.yaml
index 9d58e13e6..96520a041 100644
--- a/config/manifests/bases/camel-k.clusterserviceversion.yaml
+++ b/config/manifests/bases/camel-k.clusterserviceversion.yaml
@@ -30,7 +30,7 @@ metadata:
     operators.operatorframework.io/project_layout: go.kubebuilder.io/v2
     repository: https://github.com/apache/camel-k
     support: Camel
-  name: camel-k.v1.6.0
+  name: camel-k.v1.6.1
   namespace: placeholder
 spec:
   apiservicedefinitions: {}
@@ -154,4 +154,4 @@ spec:
   selector:
     matchLabels:
       name: camel-k-operator
-  version: 1.6.0-snapshot
+  version: 1.6.1-snapshot
diff --git a/pkg/controller/integration/build_kit.go b/pkg/controller/integration/build_kit.go
index 005ed45f9..8c2b6a04a 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -93,12 +93,9 @@ func (action *buildKitAction) Handle(ctx context.Context, integration *v1.Integr
 
 		if kit.Status.Phase == v1.IntegrationKitPhaseReady {
 			integration.Status.Image = kit.Status.Image
+			integration.Status.Phase = v1.IntegrationPhaseDeploying
 			integration.SetIntegrationKit(kit)
 
-			if _, err := trait.Apply(ctx, action.client, integration, kit); err != nil {
-				return nil, err
-			}
-
 			return integration, nil
 		}
 
diff --git a/pkg/controller/integration/deploy.go b/pkg/controller/integration/deploy.go
deleted file mode 100644
index fee22ae54..000000000
--- a/pkg/controller/integration/deploy.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package integration
-
-import (
-	"context"
-
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/trait"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
-	"github.com/pkg/errors"
-)
-
-// NewDeployAction create an action that handles integration deploy
-func NewDeployAction() Action {
-	return &deployAction{}
-}
-
-type deployAction struct {
-	baseAction
-}
-
-func (action *deployAction) Name() string {
-	return "deploy"
-}
-
-func (action *deployAction) CanHandle(integration *v1.Integration) bool {
-	return integration.Status.Phase == v1.IntegrationPhaseDeploying
-}
-
-func (action *deployAction) Handle(ctx context.Context, integration *v1.Integration) (*v1.Integration, error) {
-	if integration.Status.IntegrationKit == nil {
-		return nil, errors.Errorf("no kit set on integration %s", integration.Name)
-	}
-
-	kit, err := kubernetes.GetIntegrationKit(ctx, action.client, integration.Status.IntegrationKit.Name, integration.Status.IntegrationKit.Namespace)
-	if err != nil {
-		return nil, errors.Wrapf(err, "unable to find integration kit %s/%s, %s", integration.Status.IntegrationKit.Namespace, integration.Status.IntegrationKit.Name, err)
-	}
-
-	_, err = trait.Apply(ctx, action.client, integration, kit)
-	if err != nil {
-		return nil, err
-	}
-
-	integration.Status.Phase = v1.IntegrationPhaseRunning
-
-	return integration, nil
-}
diff --git a/pkg/controller/integration/error.go b/pkg/controller/integration/error.go
deleted file mode 100644
index a7f490c93..000000000
--- a/pkg/controller/integration/error.go
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package integration
-
-import (
-	"context"
-
-	appsv1 "k8s.io/api/apps/v1"
-	corev1 "k8s.io/api/core/v1"
-
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/util/digest"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
-)
-
-// NewErrorAction creates a new error action for an integration
-func NewErrorAction() Action {
-	return &errorAction{}
-}
-
-type errorAction struct {
-	baseAction
-}
-
-func (action *errorAction) Name() string {
-	return "error"
-}
-
-func (action *errorAction) CanHandle(integration *v1.Integration) bool {
-	return integration.Status.Phase == v1.IntegrationPhaseError
-}
-
-func (action *errorAction) Handle(ctx context.Context, integration *v1.Integration) (*v1.Integration, error) {
-	hash, err := digest.ComputeForIntegration(integration)
-	if err != nil {
-		return nil, err
-	}
-
-	if hash != integration.Status.Digest {
-		action.L.Info("Integration needs a rebuild")
-
-		integration.Initialize()
-		integration.Status.Digest = hash
-
-		return integration, nil
-	}
-
-	if kubernetes.IsConditionTrue(integration, v1.IntegrationConditionDeploymentAvailable) {
-		deployment, err := kubernetes.GetDeployment(ctx, action.client, integration.Name, integration.Namespace)
-		if err != nil && k8serrors.IsNotFound(err) {
-			return nil, err
-		}
-
-		// if the integration is in error phase, check if the corresponding pod is running ok, the user may have updated the integration.
-		deployAvailable := false
-		progressingOk := false
-		for _, c := range deployment.Status.Conditions {
-			// first, check if the container is in available state
-			if c.Type == appsv1.DeploymentAvailable {
-				deployAvailable = c.Status == corev1.ConditionTrue
-			}
-			// second, check the progressing and the reasons
-			if c.Type == appsv1.DeploymentProgressing {
-				progressingOk = c.Status == corev1.ConditionTrue && (c.Reason == "NewReplicaSetAvailable" || c.Reason == "ReplicaSetUpdated")
-			}
-		}
-		if deployAvailable && progressingOk {
-			availableCondition := v1.IntegrationCondition{
-				Type:   v1.IntegrationConditionReady, 
-				Status: corev1.ConditionTrue,
-				Reason: v1.IntegrationConditionReplicaSetReadyReason,
-			}
-			integration.Status.SetConditions(availableCondition)
-			integration.Status.Phase = v1.IntegrationPhaseRunning
-			return integration, nil
-		}
-	}
-
-	// TODO check also if deployment matches (e.g. replicas)
-	return nil, nil
-}
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index 0a4df39a6..719318aaa 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -309,9 +309,7 @@ func (r *reconcileIntegration) Reconcile(ctx context.Context, request reconcile.
 		NewPlatformSetupAction(),
 		NewInitializeAction(),
 		newBuildKitAction(),
-		NewDeployAction(),
 		NewMonitorAction(),
-		NewErrorAction(),
 	}
 
 	for _, a := range actions {
diff --git a/pkg/controller/integration/monitor.go b/pkg/controller/integration/monitor.go
index 515d2d40c..96d1f5add 100644
--- a/pkg/controller/integration/monitor.go
+++ b/pkg/controller/integration/monitor.go
@@ -20,6 +20,8 @@ package integration
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 
@@ -45,10 +47,18 @@ func (action *monitorAction) Name() string {
 }
 
 func (action *monitorAction) CanHandle(integration *v1.Integration) bool {
-	return integration.Status.Phase == v1.IntegrationPhaseRunning
+	return integration.Status.Phase == v1.IntegrationPhaseDeploying ||
+		integration.Status.Phase == v1.IntegrationPhaseRunning ||
+		integration.Status.Phase == v1.IntegrationPhaseError
 }
 
 func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integration) (*v1.Integration, error) {
+	// At that staged the Integration must have a Kit
+	if integration.Status.IntegrationKit == nil {
+		return nil, errors.Errorf("no kit set on integration %s", integration.Name)
+	}
+
+	// Check if the Integration requires a rebuild
 	hash, err := digest.ComputeForIntegration(integration)
 	if err != nil {
 		return nil, err
@@ -94,14 +104,18 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra
 		}
 	}
 
-	// Mirror ready condition from the owned resource (e.g.ReplicaSet, Deployment, CronJob, ...)
+	// Reconcile Integration phase
+	if integration.Status.Phase == v1.IntegrationPhaseDeploying {
+		integration.Status.Phase = v1.IntegrationPhaseRunning
+	}
+
+	// Mirror ready condition from the owned resource (e.g., Deployment, CronJob, KnativeService ...)
 	// into the owning integration
 	previous := integration.Status.GetCondition(v1.IntegrationConditionReady)
 	kubernetes.MirrorReadyCondition(ctx, action.client, integration)
 
-	if next := integration.Status.GetCondition(v1.IntegrationConditionReady);
-		(previous == nil || previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero()) &&
-			next != nil && next.Status == corev1.ConditionTrue && !(next.FirstTruthyTime == nil || next.FirstTruthyTime.IsZero()) {
+	if next := integration.Status.GetCondition(v1.IntegrationConditionReady); (previous == nil || previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero()) &&
+		next != nil && next.Status == corev1.ConditionTrue && !(next.FirstTruthyTime == nil || next.FirstTruthyTime.IsZero()) {
 		// Observe the time to first readiness metric
 		duration := next.FirstTruthyTime.Time.Sub(integration.Status.InitializationTimestamp.Time)
 		action.L.Infof("First readiness after %s", duration)
@@ -116,29 +130,57 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra
 			return nil, err
 		}
 
-		deployUnavailable := false
-		progressingFailing := false
-		for _, c := range deployment.Status.Conditions {
-			// first, check if the container status is not available
-			if c.Type == appsv1.DeploymentAvailable {
-				deployUnavailable = c.Status == corev1.ConditionFalse
+		switch integration.Status.Phase {
+		case v1.IntegrationPhaseRunning:
+			deployUnavailable := false
+			progressingFailing := false
+			for _, c := range deployment.Status.Conditions {
+				// first, check if the container status is not available
+				if c.Type == appsv1.DeploymentAvailable {
+					deployUnavailable = c.Status == corev1.ConditionFalse
+				}
+				// second, check when it is progressing and reason is the replicas are available but the number of replicas are zero
+				// in this case, the container integration is failing
+				if c.Type == appsv1.DeploymentProgressing {
+					progressingFailing = c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" && deployment.Status.AvailableReplicas < 1
+				}
 			}
-			// second, check when it is progressing and reason is the replicas are available but the number of replicas are zero
-			// in this case, the container integration is failing
-			if c.Type == appsv1.DeploymentProgressing {
-				progressingFailing = c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" && deployment.Status.AvailableReplicas < 1
+			if deployUnavailable && progressingFailing {
+				notAvailableCondition := v1.IntegrationCondition{
+					Type:    v1.IntegrationConditionReady,
+					Status:  corev1.ConditionFalse,
+					Reason:  v1.IntegrationConditionErrorReason,
+					Message: "The corresponding pod(s) may be in error state, look at the pod status or log for errors",
+				}
+				integration.Status.SetConditions(notAvailableCondition)
+				integration.Status.Phase = v1.IntegrationPhaseError
+				return integration, nil
 			}
-		}
-		if deployUnavailable && progressingFailing {
-			notAvailableCondition := v1.IntegrationCondition{
-				Type:   v1.IntegrationConditionReady, 
-				Status: corev1.ConditionFalse,
-				Reason: v1.IntegrationConditionErrorReason,
-				Message: "The corresponding pod(s) may be in error state, look at the pod status or log for errors",
+
+		case v1.IntegrationPhaseError:
+			// if the integration is in error phase, check if the corresponding pod is running ok, the user may have updated the integration.
+			deployAvailable := false
+			progressingOk := false
+			for _, c := range deployment.Status.Conditions {
+				// first, check if the container is in available state
+				if c.Type == appsv1.DeploymentAvailable {
+					deployAvailable = c.Status == corev1.ConditionTrue
+				}
+				// second, check the progressing and the reasons
+				if c.Type == appsv1.DeploymentProgressing {
+					progressingOk = c.Status == corev1.ConditionTrue && (c.Reason == "NewReplicaSetAvailable" || c.Reason == "ReplicaSetUpdated")
+				}
+			}
+			if deployAvailable && progressingOk {
+				availableCondition := v1.IntegrationCondition{
+					Type:   v1.IntegrationConditionReady,
+					Status: corev1.ConditionTrue,
+					Reason: v1.IntegrationConditionReplicaSetReadyReason,
+				}
+				integration.Status.SetConditions(availableCondition)
+				integration.Status.Phase = v1.IntegrationPhaseRunning
+				return integration, nil
 			}
-			integration.Status.SetConditions(notAvailableCondition)
-			integration.Status.Phase = v1.IntegrationPhaseError
-			return integration, nil
 		}
 	}
 
diff --git a/pkg/trait/affinity.go b/pkg/trait/affinity.go
index 330bd5879..bd278467f 100644
--- a/pkg/trait/affinity.go
+++ b/pkg/trait/affinity.go
@@ -68,7 +68,7 @@ func (t *affinityTrait) Configure(e *Environment) (bool, error) {
 		return false, fmt.Errorf("both pod affinity and pod anti-affinity can't be set simultaneously")
 	}
 
-	return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return e.IntegrationInRunningPhases(), nil
 }
 
 func (t *affinityTrait) Apply(e *Environment) (err error) {
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index a55341054..4358ea2e1 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -22,7 +22,6 @@ import (
 	"path"
 	"sort"
 
-	"github.com/apache/camel-k/pkg/util/kubernetes"
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
@@ -34,6 +33,7 @@ import (
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/envvar"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
 const (
@@ -130,7 +130,7 @@ func (t *containerTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -157,16 +157,12 @@ func (t *containerTrait) Apply(e *Environment) error {
 		if err := t.configureDependencies(e); err != nil {
 			return err
 		}
+	} else {
+		return t.configureContainer(e)
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
-		if err := t.configureImageIntegrationKit(e); err != nil {
-			return err
-		}
-	}
-
-	if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
-		return t.configureContainer(e)
+	if err := t.configureImageIntegrationKit(e); err != nil {
+		return err
 	}
 
 	return nil
diff --git a/pkg/trait/cron.go b/pkg/trait/cron.go
index 45a325e47..b851a798e 100644
--- a/pkg/trait/cron.go
+++ b/pkg/trait/cron.go
@@ -125,7 +125,7 @@ func (t *cronTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying) {
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -234,7 +234,7 @@ func (t *cronTrait) Apply(e *Environment) error {
 		}
 	}
 
-	if IsNilOrFalse(t.Fallback) && e.IntegrationInPhase(v1.IntegrationPhaseDeploying) {
+	if IsNilOrFalse(t.Fallback) && e.IntegrationInRunningPhases() {
 		if e.ApplicationProperties == nil {
 			e.ApplicationProperties = make(map[string]string)
 		}
@@ -260,13 +260,8 @@ func (t *cronTrait) Apply(e *Environment) error {
 }
 
 func (t *cronTrait) getCronJobFor(e *Environment) *v1beta1.CronJob {
-	labels := map[string]string{
-		v1.IntegrationLabel: e.Integration.Name,
-	}
-
-	annotations := make(map[string]string)
-
 	// Copy annotations from the integration resource
+	annotations := make(map[string]string)
 	if e.Integration.Annotations != nil {
 		for k, v := range filterTransferableAnnotations(e.Integration.Annotations) {
 			annotations[k] = v
@@ -279,9 +274,11 @@ func (t *cronTrait) getCronJobFor(e *Environment) *v1beta1.CronJob {
 			APIVersion: v1beta1.SchemeGroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name:        e.Integration.Name,
-			Namespace:   e.Integration.Namespace,
-			Labels:      labels,
+			Name:      e.Integration.Name,
+			Namespace: e.Integration.Namespace,
+			Labels: map[string]string{
+				v1.IntegrationLabel: e.Integration.Name,
+			},
 			Annotations: e.Integration.Annotations,
 		},
 		Spec: v1beta1.CronJobSpec{
@@ -292,7 +289,9 @@ func (t *cronTrait) getCronJobFor(e *Environment) *v1beta1.CronJob {
 				Spec: batchv1.JobSpec{
 					Template: corev1.PodTemplateSpec{
 						ObjectMeta: metav1.ObjectMeta{
-							Labels:      labels,
+							Labels: map[string]string{
+								v1.IntegrationLabel: e.Integration.Name,
+							},
 							Annotations: annotations,
 						},
 						Spec: corev1.PodSpec{
diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
index 301d7d066..ac9d2d873 100644
--- a/pkg/trait/deployer.go
+++ b/pkg/trait/deployer.go
@@ -29,7 +29,6 @@ import (
 
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/patch"
 )
 
@@ -54,61 +53,37 @@ func newDeployerTrait() Trait {
 }
 
 func (t *deployerTrait) Configure(e *Environment) (bool, error) {
-	return e.IntegrationInPhase(
-		v1.IntegrationPhaseNone,
-		v1.IntegrationPhaseWaitingForBindings,
-		v1.IntegrationPhaseWaitingForPlatform,
-		v1.IntegrationPhaseInitialization,
-		v1.IntegrationPhaseBuildingKit,
-		v1.IntegrationPhaseResolvingKit,
-		v1.IntegrationPhaseDeploying,
-		v1.IntegrationPhaseRunning,
-	), nil
+	return e.Integration != nil && IsNilOrTrue(t.Enabled), nil
 }
 
 func (t *deployerTrait) Apply(e *Environment) error {
-	switch e.Integration.Status.Phase {
-
-	case v1.IntegrationPhaseBuildingKit, v1.IntegrationPhaseResolvingKit:
-		if e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) {
-			e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
-				// trigger integration deploy
-				e.Integration.Status.Phase = v1.IntegrationPhaseDeploying
-				return nil
-			})
-		}
-
-	case v1.IntegrationPhaseNone, v1.IntegrationPhaseInitialization,
-		v1.IntegrationPhaseWaitingForPlatform, v1.IntegrationPhaseWaitingForBindings,
-		v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning:
-		// Register a post action that patches the resources generated by the traits
-		e.PostActions = append(e.PostActions, func(env *Environment) error {
-			for _, resource := range env.Resources.Items() {
-				// We assume that server-side apply is enabled by default.
-				// It is currently convoluted to check pro-actively whether server-side apply
-				// is enabled. This is possible to fetch the OpenAPI endpoint, which returns
-				// the entire server API document, then lookup the resource PATCH endpoint, and
-				// check its list of accepted MIME types.
-				// As a simpler solution, we fallback to client-side apply at the first
-				// 415 error, and assume server-side apply is not available globally.
-				if hasServerSideApply {
-					if err := t.serverSideApply(env, resource); err == nil {
-						continue
-					} else if isIncompatibleServerError(err) {
-						t.L.Info("Fallback to client-side apply to patch resources")
-						hasServerSideApply = false
-					} else {
-						// Keep server-side apply unless server is incompatible with it
-						return err
-					}
-				}
-				if err := t.clientSideApply(env, resource); err != nil {
+	// Register a post action that patches the resources generated by the traits
+	e.PostActions = append(e.PostActions, func(env *Environment) error {
+		for _, resource := range env.Resources.Items() {
+			// We assume that server-side apply is enabled by default.
+			// It is currently convoluted to check pro-actively whether server-side apply
+			// is enabled. This is possible to fetch the OpenAPI endpoint, which returns
+			// the entire server API document, then lookup the resource PATCH endpoint, and
+			// check its list of accepted MIME types.
+			// As a simpler solution, we fallback to client-side apply at the first
+			// 415 error, and assume server-side apply is not available globally.
+			if hasServerSideApply {
+				if err := t.serverSideApply(env, resource); err == nil {
+					continue
+				} else if isIncompatibleServerError(err) {
+					t.L.Info("Fallback to client-side apply to patch resources")
+					hasServerSideApply = false
+				} else {
+					// Keep server-side apply unless server is incompatible with it
 					return err
 				}
 			}
-			return nil
-		})
-	}
+			if err := t.clientSideApply(env, resource); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
 
 	return nil
 }
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index cf788e790..34369fb65 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -55,7 +55,7 @@ func (t *deploymentTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
+	if e.IntegrationInPhase(v1.IntegrationPhaseRunning, v1.IntegrationPhaseError) {
 		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionDeploymentAvailable)
 		return condition != nil && condition.Status == corev1.ConditionTrue, nil
 	}
@@ -98,34 +98,18 @@ func (t *deploymentTrait) ControllerStrategySelectorOrder() int {
 }
 
 func (t *deploymentTrait) Apply(e *Environment) error {
-	if e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) ||
-		e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseRunning) {
-		maps := e.computeConfigMaps()
-		deployment := t.getDeploymentFor(e)
+	maps := e.computeConfigMaps()
+	deployment := t.getDeploymentFor(e)
 
-		e.Resources.AddAll(maps)
-		e.Resources.Add(deployment)
+	e.Resources.AddAll(maps)
+	e.Resources.Add(deployment)
 
-		e.Integration.Status.SetCondition(
-			v1.IntegrationConditionDeploymentAvailable,
-			corev1.ConditionTrue,
-			v1.IntegrationConditionDeploymentAvailableReason,
-			fmt.Sprintf("deployment name is %s", deployment.Name),
-		)
-
-		if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
-			// Reconcile the deployment replicas
-			replicas := e.Integration.Spec.Replicas
-			// Deployment replicas defaults to 1, so we avoid forcing
-			// an update to nil that will result to another update cycle
-			// back to that default value by the Deployment controller.
-			if replicas == nil {
-				one := int32(1)
-				replicas = &one
-			}
-			deployment.Spec.Replicas = replicas
-		}
-	}
+	e.Integration.Status.SetCondition(
+		v1.IntegrationConditionDeploymentAvailable,
+		corev1.ConditionTrue,
+		v1.IntegrationConditionDeploymentAvailableReason,
+		fmt.Sprintf("deployment name is %s", deployment.Name),
+	)
 
 	return nil
 }
@@ -178,5 +162,16 @@ func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
 		},
 	}
 
+	// Reconcile the deployment replicas
+	replicas := e.Integration.Spec.Replicas
+	// Deployment replicas defaults to 1, so we avoid forcing
+	// an update to nil that will result to another update cycle
+	// back to that default value by the Deployment controller.
+	if replicas == nil {
+		one := int32(1)
+		replicas = &one
+	}
+	deployment.Spec.Replicas = replicas
+
 	return &deployment
 }
diff --git a/pkg/trait/environment.go b/pkg/trait/environment.go
index 6ea9e9bf7..ed5ee41e8 100644
--- a/pkg/trait/environment.go
+++ b/pkg/trait/environment.go
@@ -18,7 +18,6 @@ limitations under the License.
 package trait
 
 import (
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/envvar"
 )
@@ -59,7 +58,7 @@ func newEnvironmentTrait() Trait {
 
 func (t *environmentTrait) Configure(e *Environment) (bool, error) {
 	if IsNilOrTrue(t.Enabled) {
-		return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+		return e.IntegrationInRunningPhases(), nil
 	}
 
 	return false, nil
diff --git a/pkg/trait/error_handler.go b/pkg/trait/error_handler.go
index dfb4799de..571e4ecc9 100644
--- a/pkg/trait/error_handler.go
+++ b/pkg/trait/error_handler.go
@@ -21,9 +21,10 @@ import (
 	"fmt"
 	"strings"
 
+	"gopkg.in/yaml.v2"
+
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"gopkg.in/yaml.v2"
 )
 
 // The error-handler is a platform trait used to inject Error Handler source into the integration runtime.
@@ -52,7 +53,7 @@ func (t *errorHandlerTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -65,7 +66,7 @@ func (t *errorHandlerTrait) Configure(e *Environment) (bool, error) {
 
 func (t *errorHandlerTrait) Apply(e *Environment) error {
 	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
-		// If the user configure directly the URI, we need to autodiscover the underlying component
+		// If the user configure directly the URI, we need to auto-discover the underlying component
 		// and add the related dependency
 		defaultErrorHandlerURI := e.Integration.Spec.GetConfigurationProperty(
 			fmt.Sprintf("%s.deadLetterUri", v1alpha1.ErrorHandlerAppPropertiesPrefix))
diff --git a/pkg/trait/gc.go b/pkg/trait/gc.go
index 59210ea16..92b61dd9f 100644
--- a/pkg/trait/gc.go
+++ b/pkg/trait/gc.go
@@ -82,17 +82,14 @@ func (t *garbageCollectorTrait) Configure(e *Environment) (bool, error) {
 		t.DiscoveryCache = &s
 	}
 
-	return e.IntegrationInPhase(
-			v1.IntegrationPhaseInitialization,
-			v1.IntegrationPhaseDeploying,
-			v1.IntegrationPhaseRunning),
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(),
 		nil
 }
 
 func (t *garbageCollectorTrait) Apply(e *Environment) error {
 	switch e.Integration.Status.Phase {
 
-	case v1.IntegrationPhaseRunning:
+	case v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning, v1.IntegrationPhaseError:
 		// Register a post action that deletes the existing resources that are labelled
 		// with the previous integration generations.
 		// TODO: this should be refined so that it's run when all the replicas for the newer generation
diff --git a/pkg/trait/ingress.go b/pkg/trait/ingress.go
index dd0d56548..24115fabf 100644
--- a/pkg/trait/ingress.go
+++ b/pkg/trait/ingress.go
@@ -65,7 +65,7 @@ func (t *ingressTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
diff --git a/pkg/trait/init.go b/pkg/trait/init.go
index 466c24e49..3cf84729b 100644
--- a/pkg/trait/init.go
+++ b/pkg/trait/init.go
@@ -29,7 +29,6 @@ import (
 
 const flowsInternalSourceName = "camel-k-embedded-flow.yaml"
 
-// Internal trait
 type initTrait struct {
 	BaseTrait `property:",squash"`
 }
@@ -45,54 +44,47 @@ func (t *initTrait) Configure(e *Environment) (bool, error) {
 		return false, errors.New("trait init cannot be disabled")
 	}
 
-	return true, nil
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization), nil
 }
 
 func (t *initTrait) Apply(e *Environment) error {
-	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
-
-		// Flows need to be turned into a generated source
-		if len(e.Integration.Spec.Flows) > 0 {
-			content, err := dsl.ToYamlDSL(e.Integration.Spec.Flows)
-			if err != nil {
-				return err
-			}
-			e.Integration.Status.AddOrReplaceGeneratedSources(v1.SourceSpec{
-				DataSpec: v1.DataSpec{
-					Name:    flowsInternalSourceName,
-					Content: string(content),
-				},
-			})
+	// Flows need to be turned into a generated source
+	if len(e.Integration.Spec.Flows) > 0 {
+		content, err := dsl.ToYamlDSL(e.Integration.Spec.Flows)
+		if err != nil {
+			return err
 		}
+		e.Integration.Status.AddOrReplaceGeneratedSources(v1.SourceSpec{
+			DataSpec: v1.DataSpec{
+				Name:    flowsInternalSourceName,
+				Content: string(content),
+			},
+		})
+	}
 
-		//
-		// Dependencies need to be recomputed in case of a trait declares a capability but as
-		// the dependencies trait runs earlier than some task such as the cron one, we need to
-		// register a post step processor that recompute the dependencies based on the declared
-		// capabilities
-		//
-		e.PostStepProcessors = append(e.PostStepProcessors, func(environment *Environment) error {
-			//
-			// The camel catalog is set-up by the camel trait so it may not be available for
-			// traits executed before that one
-			//
-			if e.CamelCatalog != nil {
-				// add runtime specific dependencies
-				for _, capability := range e.Integration.Status.Capabilities {
-					for _, dependency := range e.CamelCatalog.Runtime.CapabilityDependencies(capability) {
-						util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, dependency.GetDependencyID())
-					}
+	// Dependencies need to be recomputed in case of a trait declares a capability but as
+	// the dependencies trait runs earlier than some task such as the cron one, we need to
+	// register a post step processor that recompute the dependencies based on the declared
+	// capabilities.
+	e.PostStepProcessors = append(e.PostStepProcessors, func(environment *Environment) error {
+		// The camel catalog is set up by the camel trait, so it may not be available for
+		// traits executed before that one.
+		if e.CamelCatalog != nil {
+			// add runtime specific dependencies
+			for _, capability := range e.Integration.Status.Capabilities {
+				for _, dependency := range e.CamelCatalog.Runtime.CapabilityDependencies(capability) {
+					util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, dependency.GetDependencyID())
 				}
 			}
+		}
 
-			if e.Integration.Status.Dependencies != nil {
-				// sort the dependencies to get always the same list if they don't change
-				sort.Strings(e.Integration.Status.Dependencies)
-			}
+		if e.Integration.Status.Dependencies != nil {
+			// sort the dependencies to get always the same list if they don't change
+			sort.Strings(e.Integration.Status.Dependencies)
+		}
 
-			return nil
-		})
-	}
+		return nil
+	})
 
 	return nil
 }
diff --git a/pkg/trait/istio.go b/pkg/trait/istio.go
index f6fc9510b..fd485593a 100644
--- a/pkg/trait/istio.go
+++ b/pkg/trait/istio.go
@@ -23,11 +23,9 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 
 	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
-
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
-// The Istio trait allows to configure properties related to the Istio service mesh,
+// The Istio trait allows configuring properties related to the Istio service mesh,
 // such as sidecar injection and outbound IP ranges.
 //
 // +camel-k:trait=istio
@@ -53,7 +51,7 @@ func newIstioTrait() Trait {
 
 func (t *istioTrait) Configure(e *Environment) (bool, error) {
 	if IsTrue(t.Enabled) {
-		return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+		return e.IntegrationInRunningPhases(), nil
 	}
 
 	return false, nil
diff --git a/pkg/trait/jolokia.go b/pkg/trait/jolokia.go
index 9579d7c47..ebfbb46b8 100644
--- a/pkg/trait/jolokia.go
+++ b/pkg/trait/jolokia.go
@@ -75,11 +75,11 @@ func newJolokiaTrait() Trait {
 }
 
 func (t *jolokiaTrait) Configure(e *Environment) (bool, error) {
-	return IsTrue(t.Enabled) && e.IntegrationInPhase(
-		v1.IntegrationPhaseInitialization,
-		v1.IntegrationPhaseDeploying,
-		v1.IntegrationPhaseRunning,
-	), nil
+	if IsNilOrFalse(t.Enabled) {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(), nil
 }
 
 func (t *jolokiaTrait) Apply(e *Environment) (err error) {
diff --git a/pkg/trait/jvm.go b/pkg/trait/jvm.go
index b31bca69d..a857e7d07 100644
--- a/pkg/trait/jvm.go
+++ b/pkg/trait/jvm.go
@@ -71,8 +71,11 @@ func (t *jvmTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) ||
-		e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseRunning), nil
+	if !e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) || !e.IntegrationInRunningPhases() {
+		return false, nil
+	}
+
+	return true, nil
 }
 
 func (t *jvmTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go
index 6b0b2a824..db68bf216 100644
--- a/pkg/trait/kamelets.go
+++ b/pkg/trait/kamelets.go
@@ -25,7 +25,8 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/apache/camel-k/pkg/util/source"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -37,8 +38,7 @@ import (
 	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/apache/camel-k/pkg/util/dsl"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"github.com/apache/camel-k/pkg/util/source"
 )
 
 // The kamelets trait is a platform trait used to inject Kamelets into the integration runtime.
@@ -92,7 +92,7 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -126,15 +126,14 @@ func (t *kameletsTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *kameletsTrait) Apply(e *Environment) error {
-	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseRunning) {
+	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases() {
 		if err := t.addKamelets(e); err != nil {
 			return err
 		}
 	}
-
 	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
 		return t.addConfigurationSecrets(e)
-	} else if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	} else if e.IntegrationInRunningPhases() {
 		return t.configureApplicationProperties(e)
 	}
 
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index a76ae5700..30d214f8e 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -108,7 +108,7 @@ func (t *knativeTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization) && !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -216,7 +216,7 @@ func (t *knativeTrait) Apply(e *Environment) error {
 		util.StringSliceUniqueAdd(&e.Integration.Status.Capabilities, v1.CapabilityPlatformHTTP)
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if e.IntegrationInRunningPhases() {
 		env := knativeapi.NewCamelEnvironment()
 		if t.Configuration != "" {
 			if err := env.Deserialize(t.Configuration); err != nil {
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index a86e7bd08..c7a318660 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -102,7 +102,7 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) && !e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -161,7 +161,7 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		}
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
+	if e.IntegrationInPhase(v1.IntegrationPhaseRunning, v1.IntegrationPhaseError) {
 		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable)
 		return condition != nil && condition.Status == corev1.ConditionTrue, nil
 	}
@@ -170,7 +170,10 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *knativeServiceTrait) Apply(e *Environment) error {
-	ksvc := t.getServiceFor(e)
+	ksvc, err := t.getServiceFor(e)
+	if err != nil {
+		return err
+	}
 	maps := e.computeConfigMaps()
 
 	e.Resources.AddAll(maps)
@@ -183,56 +186,6 @@ func (t *knativeServiceTrait) Apply(e *Environment) error {
 		fmt.Sprintf("Knative service name is %s", ksvc.Name),
 	)
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
-		replicas := e.Integration.Spec.Replicas
-
-		isUpdateRequired := false
-		minScale, ok := ksvc.Spec.Template.Annotations[knativeServingMinScaleAnnotation]
-		if ok {
-			min, err := strconv.Atoi(minScale)
-			if err != nil {
-				return err
-			}
-			if replicas == nil || min != int(*replicas) {
-				isUpdateRequired = true
-			}
-		} else if replicas != nil {
-			isUpdateRequired = true
-		}
-
-		maxScale, ok := ksvc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation]
-		if ok {
-			max, err := strconv.Atoi(maxScale)
-			if err != nil {
-				return err
-			}
-			if replicas == nil || max != int(*replicas) {
-				isUpdateRequired = true
-			}
-		} else if replicas != nil {
-			isUpdateRequired = true
-		}
-
-		if isUpdateRequired {
-			if replicas == nil {
-				if t.MinScale != nil && *t.MinScale > 0 {
-					ksvc.Spec.Template.Annotations[knativeServingMinScaleAnnotation] = strconv.Itoa(*t.MinScale)
-				} else {
-					delete(ksvc.Spec.Template.Annotations, knativeServingMinScaleAnnotation)
-				}
-				if t.MaxScale != nil && *t.MaxScale > 0 {
-					ksvc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation] = strconv.Itoa(*t.MaxScale)
-				} else {
-					delete(ksvc.Spec.Template.Annotations, knativeServingMaxScaleAnnotation)
-				}
-			} else {
-				scale := strconv.Itoa(int(*replicas))
-				ksvc.Spec.Template.Annotations[knativeServingMinScaleAnnotation] = scale
-				ksvc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation] = scale
-			}
-		}
-	}
-
 	return nil
 }
 
@@ -262,23 +215,16 @@ func (t *knativeServiceTrait) ControllerStrategySelectorOrder() int {
 	return 100
 }
 
-func (t *knativeServiceTrait) getServiceFor(e *Environment) *serving.Service {
-	labels := map[string]string{
-		v1.IntegrationLabel: e.Integration.Name,
-	}
-
-	annotations := make(map[string]string)
-
+func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, error) {
 	// Copy annotations from the integration resource
+	annotations := make(map[string]string)
 	if e.Integration.Annotations != nil {
 		for k, v := range filterTransferableAnnotations(e.Integration.Annotations) {
 			annotations[k] = v
 		}
 	}
 
-	//
-	// Set Knative Scaling behavior
-	//
+	// Set Knative auto-scaling behavior
 	if t.Class != "" {
 		annotations[knativeServingClassAnnotation] = t.Class
 	}
@@ -301,16 +247,20 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) *serving.Service {
 			APIVersion: serving.SchemeGroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name:        e.Integration.Name,
-			Namespace:   e.Integration.Namespace,
-			Labels:      labels,
+			Name:      e.Integration.Name,
+			Namespace: e.Integration.Namespace,
+			Labels: map[string]string{
+				v1.IntegrationLabel: e.Integration.Name,
+			},
 			Annotations: e.Integration.Annotations,
 		},
 		Spec: serving.ServiceSpec{
 			ConfigurationSpec: serving.ConfigurationSpec{
 				Template: serving.RevisionTemplateSpec{
 					ObjectMeta: metav1.ObjectMeta{
-						Labels:      labels,
+						Labels: map[string]string{
+							v1.IntegrationLabel: e.Integration.Name,
+						},
 						Annotations: annotations,
 					},
 					Spec: serving.RevisionSpec{
@@ -323,5 +273,53 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) *serving.Service {
 		},
 	}
 
-	return &svc
+	replicas := e.Integration.Spec.Replicas
+
+	isUpdateRequired := false
+	minScale, ok := svc.Spec.Template.Annotations[knativeServingMinScaleAnnotation]
+	if ok {
+		min, err := strconv.Atoi(minScale)
+		if err != nil {
+			return nil, err
+		}
+		if replicas == nil || min != int(*replicas) {
+			isUpdateRequired = true
+		}
+	} else if replicas != nil {
+		isUpdateRequired = true
+	}
+
+	maxScale, ok := svc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation]
+	if ok {
+		max, err := strconv.Atoi(maxScale)
+		if err != nil {
+			return nil, err
+		}
+		if replicas == nil || max != int(*replicas) {
+			isUpdateRequired = true
+		}
+	} else if replicas != nil {
+		isUpdateRequired = true
+	}
+
+	if isUpdateRequired {
+		if replicas == nil {
+			if t.MinScale != nil && *t.MinScale > 0 {
+				svc.Spec.Template.Annotations[knativeServingMinScaleAnnotation] = strconv.Itoa(*t.MinScale)
+			} else {
+				delete(svc.Spec.Template.Annotations, knativeServingMinScaleAnnotation)
+			}
+			if t.MaxScale != nil && *t.MaxScale > 0 {
+				svc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation] = strconv.Itoa(*t.MaxScale)
+			} else {
+				delete(svc.Spec.Template.Annotations, knativeServingMaxScaleAnnotation)
+			}
+		} else {
+			scale := strconv.Itoa(int(*replicas))
+			svc.Spec.Template.Annotations[knativeServingMinScaleAnnotation] = scale
+			svc.Spec.Template.Annotations[knativeServingMaxScaleAnnotation] = scale
+		}
+	}
+
+	return &svc, nil
 }
diff --git a/pkg/trait/logging.go b/pkg/trait/logging.go
index 0c97dccc7..1f14e2ce7 100644
--- a/pkg/trait/logging.go
+++ b/pkg/trait/logging.go
@@ -18,7 +18,6 @@ limitations under the License.
 package trait
 
 import (
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/envvar"
 )
 
@@ -61,7 +60,7 @@ func (l loggingTrait) Configure(environment *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return environment.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return environment.IntegrationInRunningPhases(), nil
 }
 
 func (l loggingTrait) Apply(environment *Environment) error {
diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go
index baf3b4517..3a95b778f 100644
--- a/pkg/trait/owner.go
+++ b/pkg/trait/owner.go
@@ -20,6 +20,7 @@ package trait
 import (
 	appsv1 "k8s.io/api/apps/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	serving "knative.dev/serving/pkg/apis/serving/v1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -52,7 +53,7 @@ func (t *ownerTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(), nil
 }
 
 func (t *ownerTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/pdb.go b/pkg/trait/pdb.go
index f862dae36..4a334c587 100644
--- a/pkg/trait/pdb.go
+++ b/pkg/trait/pdb.go
@@ -66,10 +66,7 @@ func (t *pdbTrait) Configure(e *Environment) (bool, error) {
 		return false, fmt.Errorf("both minAvailable and maxUnavailable can't be set simultaneously")
 	}
 
-	return e.IntegrationInPhase(
-		v1.IntegrationPhaseDeploying,
-		v1.IntegrationPhaseRunning,
-	), nil
+	return e.IntegrationInRunningPhases(), nil
 }
 
 func (t *pdbTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/platform.go b/pkg/trait/platform.go
index 525d9cdfc..560de1ce2 100644
--- a/pkg/trait/platform.go
+++ b/pkg/trait/platform.go
@@ -18,10 +18,11 @@ limitations under the License.
 package trait
 
 import (
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/util/openshift"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 )
 
 // The platform trait is a base trait that is used to assign an integration platform to an integration.
diff --git a/pkg/trait/pod.go b/pkg/trait/pod.go
index e45edc00b..8ce9aed09 100644
--- a/pkg/trait/pod.go
+++ b/pkg/trait/pod.go
@@ -58,7 +58,7 @@ func (t *podTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return e.IntegrationInRunningPhases(), nil
 }
 
 func (t *podTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/prometheus.go b/pkg/trait/prometheus.go
index bf192f65d..6db5c5149 100644
--- a/pkg/trait/prometheus.go
+++ b/pkg/trait/prometheus.go
@@ -57,11 +57,11 @@ func newPrometheusTrait() Trait {
 }
 
 func (t *prometheusTrait) Configure(e *Environment) (bool, error) {
-	return IsTrue(t.Enabled) && e.IntegrationInPhase(
-		v1.IntegrationPhaseInitialization,
-		v1.IntegrationPhaseDeploying,
-		v1.IntegrationPhaseRunning,
-	), nil
+	if IsNilOrFalse(t.Enabled) {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(), nil
 }
 
 func (t *prometheusTrait) Apply(e *Environment) (err error) {
diff --git a/pkg/trait/pull_secret.go b/pkg/trait/pull_secret.go
index 005cdae4e..d7e9ee479 100644
--- a/pkg/trait/pull_secret.go
+++ b/pkg/trait/pull_secret.go
@@ -20,15 +20,17 @@ package trait
 import (
 	"fmt"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/platform"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
-	"github.com/apache/camel-k/pkg/util/openshift"
 	"github.com/pkg/errors"
+
 	corev1 "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/apache/camel-k/pkg/platform"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/openshift"
 )
 
 // The Pull Secret trait sets a pull secret on the pod,
@@ -64,7 +66,7 @@ func (t *pullSecretTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -72,7 +74,7 @@ func (t *pullSecretTrait) Configure(e *Environment) (bool, error) {
 		if t.SecretName == "" {
 			secret := e.Platform.Status.Build.Registry.Secret
 			if secret != "" {
-				key := client.ObjectKey{Namespace: e.Platform.Namespace, Name: secret}
+				key := ctrl.ObjectKey{Namespace: e.Platform.Namespace, Name: secret}
 				obj := corev1.Secret{}
 				if err := t.Client.Get(t.Ctx, key, &obj); err != nil {
 					return false, err
@@ -119,7 +121,7 @@ func (t *pullSecretTrait) Apply(e *Environment) error {
 }
 
 func (t *pullSecretTrait) delegateImagePuller(e *Environment) error {
-	// Applying the rolebinding directly because it's a resource in the operator namespace
+	// Applying the RoleBinding directly because it's a resource in the operator namespace
 	// (different from the integration namespace when delegation is enabled).
 	rb := t.newImagePullerRoleBinding(e)
 	if err := kubernetes.ReplaceResource(e.C, e.Client, rb); err != nil {
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index 20f0c4378..3583ee956 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -120,7 +120,7 @@ func (t *routeTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
@@ -278,9 +278,9 @@ func (t *routeTrait) readContentIfExists(secretName string) (string, error) {
 	if len(key) > 0 {
 		content := string(secret.Data[key])
 		if len(content) == 0 {
-			return "", fmt.Errorf("Could not find key %s in secret %s in namespace %s", key, secretName, t.service.Namespace)
+			return "", fmt.Errorf("could not find key %s in secret %s in namespace %s", key, secretName, t.service.Namespace)
 		}
 		return content, nil
 	}
 	return "", nil
-}
\ No newline at end of file
+}
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index dc710e7a2..640445fd0 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -66,7 +66,7 @@ func (t *serviceTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if !e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	if !e.IntegrationInRunningPhases() {
 		return false, nil
 	}
 
diff --git a/pkg/trait/service_binding.go b/pkg/trait/service_binding.go
index 501205088..421633a2d 100644
--- a/pkg/trait/service_binding.go
+++ b/pkg/trait/service_binding.go
@@ -20,16 +20,16 @@ package trait
 import (
 	"fmt"
 
-	"github.com/apache/camel-k/pkg/util/reference"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
 	sb "github.com/redhat-developer/service-binding-operator/api/v1alpha1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util/reference"
 )
 
 // The Service Binding trait allows users to connect to Provisioned Services and ServiceBindings in Kubernetes:
@@ -57,12 +57,8 @@ func (t *serviceBindingTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return e.IntegrationInPhase(
-		v1.IntegrationPhaseInitialization,
-		v1.IntegrationPhaseWaitingForBindings,
-		v1.IntegrationPhaseDeploying,
-		v1.IntegrationPhaseRunning,
-	), nil
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseWaitingForBindings) ||
+		e.IntegrationInRunningPhases(), nil
 }
 
 func (t *serviceBindingTrait) Apply(e *Environment) error {
@@ -121,7 +117,7 @@ func (t *serviceBindingTrait) Apply(e *Environment) error {
 				e.Resources.Add(&request)
 			}
 		}
-	} else if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+	} else if e.IntegrationInRunningPhases() {
 		e.ServiceBindings = make(map[string]string)
 		for _, name := range serviceBindings {
 			sb, err := t.getServiceBinding(e, name)
@@ -168,7 +164,7 @@ func isCollectionReady(sb sb.ServiceBinding) bool {
 
 func (t *serviceBindingTrait) getServiceBinding(e *Environment, name string) (sb.ServiceBinding, error) {
 	serviceBinding := sb.ServiceBinding{}
-	key := k8sclient.ObjectKey{
+	key := ctrl.ObjectKey{
 		Namespace: e.Integration.Namespace,
 		Name:      name,
 	}
diff --git a/pkg/trait/toleration.go b/pkg/trait/toleration.go
index add142826..dc5c1cbd2 100644
--- a/pkg/trait/toleration.go
+++ b/pkg/trait/toleration.go
@@ -22,7 +22,6 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
@@ -61,7 +60,7 @@ func (t *tolerationTrait) Configure(e *Environment) (bool, error) {
 		return false, fmt.Errorf("no taint was provided")
 	}
 
-	return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return e.IntegrationInRunningPhases(), nil
 }
 
 func (t *tolerationTrait) Apply(e *Environment) (err error) {
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index fc6d0f06e..89ec86b25 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -246,7 +246,10 @@ func (e *Environment) IntegrationInPhase(phases ...v1.IntegrationPhase) bool {
 	return false
 }
 
-// IntegrationKitInPhase --
+func (e *Environment) IntegrationInRunningPhases() bool {
+	return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning, v1.IntegrationPhaseError)
+}
+
 func (e *Environment) IntegrationKitInPhase(phases ...v1.IntegrationKitPhase) bool {
 	if e.IntegrationKit == nil {
 		return false


Test patch:
diff --git a/e2e/common/integration_fail_test.go b/e2e/common/integration_fail_test.go
index f2b3940eb..720e1328e 100644
--- a/e2e/common/integration_fail_test.go
+++ b/e2e/common/integration_fail_test.go
@@ -25,21 +25,37 @@ import (
 	"testing"
 
 	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/gstruct"
+
+	v1 "k8s.io/api/core/v1"
 
 	. "github.com/apache/camel-k/e2e/support"
 	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	v1 "k8s.io/api/core/v1"
 )
 
 func TestBadRouteIntegration(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+
 		t.Run("run bad java route", func(t *testing.T) {
-			Expect(Kamel("run", "-n", ns, "files/BadRoute.java").Execute()).To(Succeed())
-			Eventually(IntegrationPodPhase(ns, "bad-route"), TestTimeoutMedium).Should(Equal(v1.PodRunning))
-			Eventually(IntegrationPhase(ns, "bad-route"), TestTimeoutShort).Should(Equal(camelv1.IntegrationPhaseError))
-			Eventually(IntegrationCondition(ns, "bad-route", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionFalse))
-			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+			name := "bad-route"
+			Expect(Kamel("run", "-n", ns, "files/BadRoute.java", "--name", name).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, name), TestTimeoutMedium).Should(Equal(v1.PodRunning))
+			Eventually(IntegrationPhase(ns, name), TestTimeoutShort).Should(Equal(camelv1.IntegrationPhaseError))
+			Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionFalse))
+
+			// Make sure the Integration can be scaled
+			Expect(ScaleIntegration(ns, name, 2)).To(Succeed())
+			// Check the scale cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(2))
+			// Check it also cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 2)))
+			// Check the Integration stays in error phase
+			Eventually(IntegrationPhase(ns, name), TestTimeoutShort).Should(Equal(camelv1.IntegrationPhaseError))
 		})
+
+		// Clean up
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
 	})
 }
diff --git a/e2e/common/scale_test.go b/e2e/common/scale_binding_test.go
similarity index 60%
rename from e2e/common/scale_test.go
rename to e2e/common/scale_binding_test.go
index a19f2a201..27fc90079 100644
--- a/e2e/common/scale_test.go
+++ b/e2e/common/scale_binding_test.go
@@ -24,7 +24,6 @@ package common
 import (
 	"testing"
 
-	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gstruct"
 
@@ -38,93 +37,10 @@ import (
 
 	. "github.com/apache/camel-k/e2e/support"
 	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/client/camel/clientset/versioned"
 )
 
-func TestIntegrationScale(t *testing.T) {
-	WithNewTestNamespace(t, func(ns string) {
-		name := "java"
-		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
-		Expect(Kamel("run", "-n", ns, "files/Java.java", "--name", name).Execute()).To(Succeed())
-		Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(v1.PodRunning))
-		Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
-		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
-
-		t.Run("Update integration scale spec", func(t *testing.T) {
-			RegisterTestingT(t)
-			Expect(ScaleIntegration(ns, name, 3)).To(Succeed())
-			// Check the readiness condition becomes falsy
-			Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionFalse))
-			// Check the scale cascades into the Deployment scale
-			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(3))
-			// Check it also cascades into the Integration scale subresource Status field
-			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
-				Should(gstruct.PointTo(BeNumerically("==", 3)))
-			// Finally check the readiness condition becomes truthy back
-			Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(v1.ConditionTrue))
-		})
-
-		t.Run("Scale integration with polymorphic client", func(t *testing.T) {
-			RegisterTestingT(t)
-			// Polymorphic scale client
-			groupResources, err := restmapper.GetAPIGroupResources(TestClient().Discovery())
-			Expect(err).To(BeNil())
-			mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
-			resolver := scale.NewDiscoveryScaleKindResolver(TestClient().Discovery())
-			scaleClient, err := scale.NewForConfig(TestClient().GetConfig(), mapper, dynamic.LegacyAPIPathResolverFunc, resolver)
-			Expect(err).To(BeNil())
-
-			// Patch the integration scale subresource
-			patch := "{\"spec\":{\"replicas\":2}}"
-			_, err = scaleClient.Scales(ns).Patch(TestContext, camelv1.SchemeGroupVersion.WithResource("integrations"), name, types.MergePatchType, []byte(patch), metav1.PatchOptions{})
-			Expect(err).To(BeNil())
-
-			// Check the readiness condition is still truthy as down-scaling
-			Expect(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady)()).To(Equal(v1.ConditionTrue))
-			// Check the Integration scale subresource Spec field
-			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
-				Should(gstruct.PointTo(BeNumerically("==", 2)))
-			// Then check it cascades into the Deployment scale
-			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(2))
-			// Finally check it cascades into the Integration scale subresource Status field
-			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
-				Should(gstruct.PointTo(BeNumerically("==", 2)))
-		})
-
-		t.Run("Scale integration with Camel K client", func(t *testing.T) {
-			RegisterTestingT(t)
-			camel, err := versioned.NewForConfig(TestClient().GetConfig())
-			Expect(err).To(BeNil())
-
-			// Getter
-			integrationScale, err := camel.CamelV1().Integrations(ns).GetScale(TestContext, name, metav1.GetOptions{})
-			Expect(err).To(BeNil())
-			Expect(integrationScale.Spec.Replicas).To(BeNumerically("==", 2))
-			Expect(integrationScale.Status.Replicas).To(BeNumerically("==", 2))
-
-			// Setter
-			integrationScale.Spec.Replicas = 1
-			integrationScale, err = camel.CamelV1().Integrations(ns).UpdateScale(TestContext, name, integrationScale, metav1.UpdateOptions{})
-			Expect(err).To(BeNil())
-
-			// Check the readiness condition is still truthy as down-scaling
-			Expect(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady)()).To(Equal(v1.ConditionTrue))
-			// Check the Integration scale subresource Spec field
-			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
-				Should(gstruct.PointTo(BeNumerically("==", 1)))
-			// Then check it cascades into the Deployment scale
-			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(1))
-			// Finally check it cascades into the Integration scale subresource Status field
-			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
-				Should(gstruct.PointTo(BeNumerically("==", 1)))
-		})
-
-		// Clean up
-		RegisterTestingT(t)
-		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
-	})
-}
-
 func TestKameletBindingScale(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		name := "binding"
diff --git a/e2e/common/scale_integration_test.go b/e2e/common/scale_integration_test.go
new file mode 100644
index 000000000..d88c8ca27
--- /dev/null
+++ b/e2e/common/scale_integration_test.go
@@ -0,0 +1,125 @@
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "knative"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"testing"
+
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/gstruct"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/restmapper"
+	"k8s.io/client-go/scale"
+
+	. "github.com/apache/camel-k/e2e/support"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/client/camel/clientset/versioned"
+)
+
+func TestIntegrationScale(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		name := "java"
+		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+		Expect(Kamel("run", "-n", ns, "files/Java.java", "--name", name).Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(v1.PodRunning))
+		Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+		t.Run("Update integration scale spec", func(t *testing.T) {
+			RegisterTestingT(t)
+			Expect(ScaleIntegration(ns, name, 3)).To(Succeed())
+			// Check the readiness condition becomes falsy
+			Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionFalse))
+			// Check the scale cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(3))
+			// Check it also cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 3)))
+			// Finally check the readiness condition becomes truthy back
+			Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutMedium).Should(Equal(v1.ConditionTrue))
+		})
+
+		t.Run("Scale integration with polymorphic client", func(t *testing.T) {
+			RegisterTestingT(t)
+			// Polymorphic scale client
+			groupResources, err := restmapper.GetAPIGroupResources(TestClient().Discovery())
+			Expect(err).To(BeNil())
+			mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
+			resolver := scale.NewDiscoveryScaleKindResolver(TestClient().Discovery())
+			scaleClient, err := scale.NewForConfig(TestClient().GetConfig(), mapper, dynamic.LegacyAPIPathResolverFunc, resolver)
+			Expect(err).To(BeNil())
+
+			// Patch the integration scale subresource
+			patch := "{\"spec\":{\"replicas\":2}}"
+			_, err = scaleClient.Scales(ns).Patch(TestContext, camelv1.SchemeGroupVersion.WithResource("integrations"), name, types.MergePatchType, []byte(patch), metav1.PatchOptions{})
+			Expect(err).To(BeNil())
+
+			// Check the readiness condition is still truthy as down-scaling
+			Expect(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady)()).To(Equal(v1.ConditionTrue))
+			// Check the Integration scale subresource Spec field
+			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 2)))
+			// Then check it cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(2))
+			// Finally check it cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 2)))
+		})
+
+		t.Run("Scale integration with Camel K client", func(t *testing.T) {
+			RegisterTestingT(t)
+			camel, err := versioned.NewForConfig(TestClient().GetConfig())
+			Expect(err).To(BeNil())
+
+			// Getter
+			integrationScale, err := camel.CamelV1().Integrations(ns).GetScale(TestContext, name, metav1.GetOptions{})
+			Expect(err).To(BeNil())
+			Expect(integrationScale.Spec.Replicas).To(BeNumerically("==", 2))
+			Expect(integrationScale.Status.Replicas).To(BeNumerically("==", 2))
+
+			// Setter
+			integrationScale.Spec.Replicas = 1
+			integrationScale, err = camel.CamelV1().Integrations(ns).UpdateScale(TestContext, name, integrationScale, metav1.UpdateOptions{})
+			Expect(err).To(BeNil())
+
+			// Check the readiness condition is still truthy as down-scaling
+			Expect(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady)()).To(Equal(v1.ConditionTrue))
+			// Check the Integration scale subresource Spec field
+			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			// Then check it cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutShort).Should(HaveLen(1))
+			// Finally check it cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+		})
+
+		// Clean up
+		RegisterTestingT(t)
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+	})
+}
diff --git a/pkg/trait/deployer_test.go b/pkg/trait/deployer_test.go
index d5ffb9735..58c8b7343 100644
--- a/pkg/trait/deployer_test.go
+++ b/pkg/trait/deployer_test.go
@@ -42,7 +42,7 @@ func TestConfigureDeployerTraitInWrongPhaseDoesNotSucceed(t *testing.T) {
 
 	configured, err := deployerTrait.Configure(environment)
 
-	assert.False(t, configured)
+	assert.True(t, configured)
 	assert.Nil(t, err)
 }
 
@@ -65,16 +65,6 @@ func TestApplyDeployerTraitInInitializationPhaseDoesSucceed(t *testing.T) {
 	assert.Len(t, environment.PostActions, 1)
 }
 
-func TestApplyDeployerTraitInResolvingKitPhaseSkipPostActions(t *testing.T) {
-	deployerTrait, environment := createNominalDeployerTest()
-	environment.Integration.Status.Phase = v1.IntegrationPhaseResolvingKit
-
-	err := deployerTrait.Apply(environment)
-
-	assert.Nil(t, err)
-	assert.Len(t, environment.PostActions, 0)
-}
-
 func createNominalDeployerTest() (*deployerTrait, *Environment) {
 	trait := newDeployerTrait().(*deployerTrait)
 
diff --git a/pkg/trait/ingress_test.go b/pkg/trait/ingress_test.go
index a0fb217a5..923a63943 100644
--- a/pkg/trait/ingress_test.go
+++ b/pkg/trait/ingress_test.go
@@ -60,7 +60,7 @@ func TestConfigureIngressTraitInWrongPhaseDoesNotSucceed(t *testing.T) {
 
 	configured, err := ingressTrait.Configure(environment)
 
-	assert.False(t, configured)
+	assert.True(t, configured)
 	assert.Nil(t, err)
 	assert.Len(t, environment.Integration.Status.Conditions, 0)
 }
diff --git a/pkg/trait/jvm_test.go b/pkg/trait/jvm_test.go
index 343b25dd8..0eeb0f3ea 100644
--- a/pkg/trait/jvm_test.go
+++ b/pkg/trait/jvm_test.go
@@ -54,7 +54,7 @@ func TestConfigureJvmTraitInWrongIntegrationPhaseDoesNotSucceed(t *testing.T) {
 
 	configured, err := trait.Configure(environment)
 	assert.Nil(t, err)
-	assert.False(t, configured)
+	assert.True(t, configured)
 }
 
 func TestConfigureJvmTraitInWrongIntegrationKitPhaseDoesNotSucceed(t *testing.T) {