Repository: apecloud/kubeblocks

Issue:
[Improvement] instanceset can update pending pod
## Use case
If a user has performed a hscale/vscale ops that is stuck running because of insufficient resources, he might want to rollback the change. But since the newly created pod is in Pending status (which is typically because of failed scheduling), instanceset will keep waiting forever.

## Expected behavior
Instanceset can treat Pending status as a special case. Pod in Pending status can be updated/deleted freely.

Hints from issue comments:

Solution patch:
diff --git a/apis/parameters/v1alpha1/paramconfigrenderer_types.go b/apis/parameters/v1alpha1/paramconfigrenderer_types.go
index fe8177f78..a14fd45e1 100644
--- a/apis/parameters/v1alpha1/paramconfigrenderer_types.go
+++ b/apis/parameters/v1alpha1/paramconfigrenderer_types.go
@@ -107,8 +107,8 @@ type ComponentConfigDescription struct {
 	//    sectionName: mysqld
 	// ```
 	//
-	// +kubebuilder:validation:Required
-	FileFormatConfig *FileFormatConfig `json:"fileFormatConfig"`
+	// +optional
+	FileFormatConfig *FileFormatConfig `json:"fileFormatConfig,omitempty"`
 
 	// Specifies whether the configuration needs to be re-rendered after v-scale or h-scale operations to reflect changes.
 	//
diff --git a/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml b/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml
index 946926e69..a73993244 100644
--- a/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml
+++ b/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml
@@ -151,7 +151,6 @@ spec:
                       description: Specifies the name of the referenced componentTemplateSpec.
                       type: string
                   required:
-                  - fileFormatConfig
                   - name
                   type: object
                 type: array
diff --git a/controllers/apps/cluster/transformer_cluster_component.go b/controllers/apps/cluster/transformer_cluster_component.go
index 832ca942e..d53cc0aeb 100644
--- a/controllers/apps/cluster/transformer_cluster_component.go
+++ b/controllers/apps/cluster/transformer_cluster_component.go
@@ -52,7 +52,7 @@ var _ graph.Transformer = &clusterComponentTransformer{}
 
 func (t *clusterComponentTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.OrigCluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/cluster/transformer_cluster_component_status.go b/controllers/apps/cluster/transformer_cluster_component_status.go
index 581e155bf..e279548a0 100644
--- a/controllers/apps/cluster/transformer_cluster_component_status.go
+++ b/controllers/apps/cluster/transformer_cluster_component_status.go
@@ -31,7 +31,6 @@ import (
 	"github.com/apecloud/kubeblocks/pkg/constant"
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 )
 
 const (
@@ -46,7 +45,7 @@ var _ graph.Transformer = &clusterComponentStatusTransformer{}
 
 func (t *clusterComponentStatusTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.OrigCluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/cluster/transformer_cluster_normalization.go b/controllers/apps/cluster/transformer_cluster_normalization.go
index 136d7881b..2a0ec800a 100644
--- a/controllers/apps/cluster/transformer_cluster_normalization.go
+++ b/controllers/apps/cluster/transformer_cluster_normalization.go
@@ -37,7 +37,6 @@ import (
 	"github.com/apecloud/kubeblocks/pkg/constant"
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 	"github.com/apecloud/kubeblocks/pkg/controllerutil"
 )
 
@@ -49,7 +48,7 @@ var _ graph.Transformer = &clusterNormalizationTransformer{}
 func (t *clusterNormalizationTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
 	cluster := transCtx.Cluster
-	if model.IsObjectDeleting(transCtx.OrigCluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/cluster/transformer_cluster_placement.go b/controllers/apps/cluster/transformer_cluster_placement.go
index 7c164beae..7ad905c8e 100644
--- a/controllers/apps/cluster/transformer_cluster_placement.go
+++ b/controllers/apps/cluster/transformer_cluster_placement.go
@@ -28,7 +28,6 @@ import (
 	appsutil "github.com/apecloud/kubeblocks/controllers/apps/util"
 	"github.com/apecloud/kubeblocks/pkg/constant"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 	"github.com/apecloud/kubeblocks/pkg/controller/multicluster"
 )
 
@@ -41,7 +40,7 @@ var _ graph.Transformer = &clusterPlacementTransformer{}
 
 func (t *clusterPlacementTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.OrigCluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/cluster/transformer_cluster_service.go b/controllers/apps/cluster/transformer_cluster_service.go
index b2bba726f..c90a1962a 100644
--- a/controllers/apps/cluster/transformer_cluster_service.go
+++ b/controllers/apps/cluster/transformer_cluster_service.go
@@ -44,7 +44,7 @@ var _ graph.Transformer = &clusterServiceTransformer{}
 
 func (t *clusterServiceTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.OrigCluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.OrigCluster.Annotations) {
diff --git a/controllers/apps/cluster/transformer_cluster_sharding_account.go b/controllers/apps/cluster/transformer_cluster_sharding_account.go
index 1260875fb..2bb1b047d 100644
--- a/controllers/apps/cluster/transformer_cluster_sharding_account.go
+++ b/controllers/apps/cluster/transformer_cluster_sharding_account.go
@@ -44,7 +44,7 @@ var _ graph.Transformer = &clusterShardingAccountTransformer{}
 
 func (t *clusterShardingAccountTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.Cluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/cluster/transformer_cluster_sharding_tls.go b/controllers/apps/cluster/transformer_cluster_sharding_tls.go
index 64e0c8298..d10afae00 100644
--- a/controllers/apps/cluster/transformer_cluster_sharding_tls.go
+++ b/controllers/apps/cluster/transformer_cluster_sharding_tls.go
@@ -44,7 +44,7 @@ var _ graph.Transformer = &clusterShardingTLSTransformer{}
 
 func (t *clusterShardingTLSTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*clusterTransformContext)
-	if model.IsObjectDeleting(transCtx.Cluster) {
+	if transCtx.OrigCluster.IsDeleting() {
 		return nil
 	}
 
diff --git a/controllers/apps/component/transformer_component_account.go b/controllers/apps/component/transformer_component_account.go
index 3445db8f5..acf42da7e 100644
--- a/controllers/apps/component/transformer_component_account.go
+++ b/controllers/apps/component/transformer_component_account.go
@@ -60,7 +60,7 @@ var _ graph.Transformer = &componentAccountTransformer{}
 
 func (t *componentAccountTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.ComponentOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_account_provision.go b/controllers/apps/component/transformer_component_account_provision.go
index d4ea07e90..c42979dcd 100644
--- a/controllers/apps/component/transformer_component_account_provision.go
+++ b/controllers/apps/component/transformer_component_account_provision.go
@@ -37,7 +37,6 @@ import (
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
 	"github.com/apecloud/kubeblocks/pkg/controller/lifecycle"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 )
 
 const (
@@ -53,7 +52,7 @@ var _ graph.Transformer = &componentAccountProvisionTransformer{}
 
 func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.ComponentOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_hostnetwork.go b/controllers/apps/component/transformer_component_hostnetwork.go
index b08f62e6a..447392cb9 100644
--- a/controllers/apps/component/transformer_component_hostnetwork.go
+++ b/controllers/apps/component/transformer_component_hostnetwork.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 	intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
 )
 
@@ -34,7 +33,7 @@ var _ graph.Transformer = &componentHostNetworkTransformer{}
 
 func (t *componentHostNetworkTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 
diff --git a/controllers/apps/component/transformer_component_monitor.go b/controllers/apps/component/transformer_component_monitor.go
index 406c94a20..a63905f52 100644
--- a/controllers/apps/component/transformer_component_monitor.go
+++ b/controllers/apps/component/transformer_component_monitor.go
@@ -25,7 +25,6 @@ import (
 	"github.com/apecloud/kubeblocks/pkg/common"
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 )
 
 type componentMonitorContainerTransformer struct{}
@@ -38,7 +37,7 @@ func (c componentMonitorContainerTransformer) Transform(ctx graph.TransformConte
 	compDef := transCtx.CompDef
 	synthesizeComp := transCtx.SynthesizeComponent
 
-	if model.IsObjectDeleting(compOrig) {
+	if isCompDeleting(compOrig) {
 		return
 	}
 	if common.IsCompactMode(compOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_post_provision.go b/controllers/apps/component/transformer_component_post_provision.go
index 3bc319f42..58683b371 100644
--- a/controllers/apps/component/transformer_component_post_provision.go
+++ b/controllers/apps/component/transformer_component_post_provision.go
@@ -40,7 +40,7 @@ var _ graph.Transformer = &componentPostProvisionTransformer{}
 
 func (t *componentPostProvisionTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 
diff --git a/controllers/apps/component/transformer_component_rbac.go b/controllers/apps/component/transformer_component_rbac.go
index 47771d9dc..58b6cf9ab 100644
--- a/controllers/apps/component/transformer_component_rbac.go
+++ b/controllers/apps/component/transformer_component_rbac.go
@@ -54,7 +54,7 @@ const EventReasonRBACManager = "RBACManager"
 func (t *componentRBACTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
 	synthesizedComp := transCtx.SynthesizeComponent
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.ComponentOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_reload_sidecar.go b/controllers/apps/component/transformer_component_reload_sidecar.go
index ec96a85e2..680ea0f73 100644
--- a/controllers/apps/component/transformer_component_reload_sidecar.go
+++ b/controllers/apps/component/transformer_component_reload_sidecar.go
@@ -30,7 +30,6 @@ import (
 	"github.com/apecloud/kubeblocks/pkg/controller/component"
 	configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration"
 	"github.com/apecloud/kubeblocks/pkg/controller/graph"
-	"github.com/apecloud/kubeblocks/pkg/controller/model"
 	"github.com/apecloud/kubeblocks/pkg/controller/render"
 )
 
@@ -47,7 +46,7 @@ func (t *componentReloadSidecarTransformer) Transform(ctx graph.TransformContext
 	compOrig := transCtx.ComponentOrig
 	builtinComp := transCtx.SynthesizeComponent
 
-	if model.IsObjectDeleting(compOrig) {
+	if isCompDeleting(compOrig) {
 		return nil
 	}
 	if common.IsCompactMode(compOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_service.go b/controllers/apps/component/transformer_component_service.go
index ab48b388e..86a72eb24 100644
--- a/controllers/apps/component/transformer_component_service.go
+++ b/controllers/apps/component/transformer_component_service.go
@@ -58,7 +58,7 @@ var _ graph.Transformer = &componentServiceTransformer{}
 
 func (t *componentServiceTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.ComponentOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_status.go b/controllers/apps/component/transformer_component_status.go
index c20eda066..5641a1e03 100644
--- a/controllers/apps/component/transformer_component_status.go
+++ b/controllers/apps/component/transformer_component_status.go
@@ -69,7 +69,7 @@ var _ graph.Transformer = &componentStatusTransformer{}
 
 func (t *componentStatusTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 
diff --git a/controllers/apps/component/transformer_component_template.go b/controllers/apps/component/transformer_component_template.go
index 8dbb33ea3..0f5649906 100644
--- a/controllers/apps/component/transformer_component_template.go
+++ b/controllers/apps/component/transformer_component_template.go
@@ -52,7 +52,7 @@ var _ graph.Transformer = &componentFileTemplateTransformer{}
 
 func (t *componentFileTemplateTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 
diff --git a/controllers/apps/component/transformer_component_vars.go b/controllers/apps/component/transformer_component_vars.go
index c9e0fbf36..1549e0935 100644
--- a/controllers/apps/component/transformer_component_vars.go
+++ b/controllers/apps/component/transformer_component_vars.go
@@ -49,7 +49,7 @@ var _ graph.Transformer = &componentVarsTransformer{}
 func (t *componentVarsTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
 
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 	if common.IsCompactMode(transCtx.ComponentOrig.Annotations) {
diff --git a/controllers/apps/component/transformer_component_workload.go b/controllers/apps/component/transformer_component_workload.go
index ba4d1e7a0..472e84c05 100644
--- a/controllers/apps/component/transformer_component_workload.go
+++ b/controllers/apps/component/transformer_component_workload.go
@@ -57,7 +57,7 @@ var _ graph.Transformer = &componentWorkloadTransformer{}
 
 func (t *componentWorkloadTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
 	transCtx, _ := ctx.(*componentTransformContext)
-	if model.IsObjectDeleting(transCtx.ComponentOrig) {
+	if isCompDeleting(transCtx.ComponentOrig) {
 		return nil
 	}
 
diff --git a/controllers/apps/component/utils.go b/controllers/apps/component/utils.go
index 446d7c410..7d85e972f 100644
--- a/controllers/apps/component/utils.go
+++ b/controllers/apps/component/utils.go
@@ -88,3 +88,10 @@ func mapDiff[T interface{}](m1, m2 map[string]T) (sets.Set[string], sets.Set[str
 	s1, s2 := sets.KeySet(m1), sets.KeySet(m2)
 	return setDiff(s1, s2)
 }
+
+func isCompDeleting(comp *appsv1.Component) bool {
+	if comp.GetDeletionTimestamp().IsZero() {
+		return false
+	}
+	return comp.Spec.TerminationPolicy != appsv1.DoNotTerminate
+}
diff --git a/controllers/parameters/parametertemplateextension_controller.go b/controllers/parameters/parametertemplateextension_controller.go
index db341674c..70e91b592 100644
--- a/controllers/parameters/parametertemplateextension_controller.go
+++ b/controllers/parameters/parametertemplateextension_controller.go
@@ -127,7 +127,7 @@ func updateConfigsForParameterTemplate(reqCtx intctrlutil.RequestCtx, reader cli
 		}
 		cm := corev1.ConfigMap{}
 		if err := reader.Get(reqCtx.Ctx, cmKey, &cm); err != nil {
-			return err
+			return client.IgnoreNotFound(err)
 		}
 		config.ConfigMap = &corev1.ConfigMapVolumeSource{
 			LocalObjectReference: corev1.LocalObjectReference{Name: cm.Name},
@@ -140,7 +140,7 @@ func updateConfigsForParameterTemplate(reqCtx intctrlutil.RequestCtx, reader cli
 		}
 		parameterCR, err := resolveParameterCR(compName)
 		if err != nil {
-			if apierrors.IsNotFound(err) {
+			if !apierrors.IsNotFound(err) {
 				return err
 			}
 			return nil
diff --git a/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml b/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml
index 946926e69..a73993244 100644
--- a/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml
+++ b/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml
@@ -151,7 +151,6 @@ spec:
                       description: Specifies the name of the referenced componentTemplateSpec.
                       type: string
                   required:
-                  - fileFormatConfig
                   - name
                   type: object
                 type: array
diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go
index 4cd49cab7..356fa964c 100644
--- a/pkg/controller/component/synthesize_component.go
+++ b/pkg/controller/component/synthesize_component.go
@@ -474,6 +474,7 @@ func synthesizeFileTemplate(comp *appsv1.Component, tpl appsv1.ComponentFileTemp
 				return merge(stpl, utpl)
 			}
 		}
+		return merge(stpl, appsv1.ClusterComponentConfig{})
 	}
 	return stpl
 }
diff --git a/pkg/controller/instanceset/instance_util.go b/pkg/controller/instanceset/instance_util.go
index 2326db25e..03ed3fc2b 100644
--- a/pkg/controller/instanceset/instance_util.go
+++ b/pkg/controller/instanceset/instance_util.go
@@ -169,6 +169,8 @@ func isTerminating(pod *corev1.Pod) bool {
 	return pod.DeletionTimestamp != nil
 }
 
+func isPodPending(pod *corev1.Pod) bool { return pod.Status.Phase == corev1.PodPending }
+
 // isImageMatched returns true if all container statuses have same image as defined in pod spec
 func isImageMatched(pod *corev1.Pod) bool {
 	for _, container := range pod.Spec.Containers {
diff --git a/pkg/controller/instanceset/reconciler_update.go b/pkg/controller/instanceset/reconciler_update.go
index c4d042094..412995dbf 100644
--- a/pkg/controller/instanceset/reconciler_update.go
+++ b/pkg/controller/instanceset/reconciler_update.go
@@ -110,7 +110,7 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder
 	}
 	currentUnavailable := 0
 	for _, pod := range oldPodList {
-		if !intctrlutil.IsPodAvailable(pod, its.Spec.MinReadySeconds) {
+		if !intctrlutil.IsPodAvailable(pod, its.Spec.MinReadySeconds) && !isPodPending(pod) {
 			currentUnavailable++
 		}
 	}
@@ -133,30 +133,43 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder
 	isBlocked := false
 	needRetry := false
 	sortObjects(oldPodList, priorities, false)
-	for _, pod := range oldPodList {
-		if updatingPods >= updateCount || updatingPods >= unavailable {
-			break
-		}
-		if updatedPods >= replicas {
-			break
-		}
 
+	canBeUpdated := func(pod *corev1.Pod) bool {
+		// pending pod can be updated because there's no consequence of deleting it
+		if isPodPending(pod) {
+			return true
+		}
 		if !isImageMatched(pod) {
 			tree.Logger.Info(fmt.Sprintf("InstanceSet %s/%s blocks on update as the pod %s does not have the same image(s) in the status and in the spec", its.Namespace, its.Name, pod.Name))
-			break
+			return false
 		}
 		if !intctrlutil.IsPodReady(pod) {
 			tree.Logger.Info(fmt.Sprintf("InstanceSet %s/%s blocks on update as the pod %s is not ready", its.Namespace, its.Name, pod.Name))
-			break
+			return false
 		}
 		if !intctrlutil.IsPodAvailable(pod, its.Spec.MinReadySeconds) {
 			tree.Logger.Info(fmt.Sprintf("InstanceSet %s/%s blocks on update as the pod %s is not available", its.Namespace, its.Name, pod.Name))
 			// no pod event will trigger the next reconciliation, so retry it
 			needRetry = true
-			break
+			return false
 		}
 		if !isRoleReady(pod, its.Spec.Roles) {
 			tree.Logger.Info(fmt.Sprintf("InstanceSet %s/%s blocks on update as the role of pod %s is not ready", its.Namespace, its.Name, pod.Name))
+			return false
+		}
+
+		return true
+	}
+
+	for _, pod := range oldPodList {
+		if updatingPods >= updateCount || updatingPods >= unavailable {
+			break
+		}
+		if updatedPods >= replicas {
+			break
+		}
+
+		if !canBeUpdated(pod) {
 			break
 		}
 



Test patch:
diff --git a/pkg/controller/instanceset/reconciler_update_test.go b/pkg/controller/instanceset/reconciler_update_test.go
index 0c93d08f4..72328ddf1 100644
--- a/pkg/controller/instanceset/reconciler_update_test.go
+++ b/pkg/controller/instanceset/reconciler_update_test.go
@@ -39,10 +39,13 @@ import (
 )
 
 var _ = Describe("update reconciler test", func() {
+	var replicas int32
+
 	BeforeEach(func() {
+		replicas = 3
 		its = builder.NewInstanceSetBuilder(namespace, name).
 			SetUID(uid).
-			SetReplicas(3).
+			SetReplicas(replicas).
 			SetSelectorMatchLabel(selectors).
 			SetTemplate(template).
 			SetVolumeClaimTemplates(volumeClaimTemplates...).
@@ -50,6 +53,42 @@ var _ = Describe("update reconciler test", func() {
 			GetObject()
 	})
 
+	prepareForUpdate := func(tree *kubebuilderx.ObjectTree) {
+		By("fix meta")
+		reconciler = NewFixMetaReconciler()
+		res, err := reconciler.Reconcile(tree)
+		Expect(err).Should(BeNil())
+		Expect(res).Should(Equal(kubebuilderx.Commit))
+
+		By("update revisions")
+		reconciler = NewRevisionUpdateReconciler()
+		res, err = reconciler.Reconcile(tree)
+		Expect(err).Should(BeNil())
+		Expect(res).Should(Equal(kubebuilderx.Continue))
+
+		By("assistant object")
+		reconciler = NewAssistantObjectReconciler()
+		res, err = reconciler.Reconcile(tree)
+		Expect(err).Should(BeNil())
+		Expect(res).Should(Equal(kubebuilderx.Continue))
+
+		By("replicas alignment")
+		reconciler = NewReplicasAlignmentReconciler()
+		res, err = reconciler.Reconcile(tree)
+		Expect(err).Should(BeNil())
+		Expect(res).Should(Equal(kubebuilderx.Continue))
+	}
+
+	expectUpdatedPods := func(tree *kubebuilderx.ObjectTree, names []string) {
+		pods := tree.List(&corev1.Pod{})
+		Expect(pods).Should(HaveLen(int(replicas) - len(names)))
+		for _, name := range names {
+			Expect(slices.IndexFunc(pods, func(object client.Object) bool {
+				return object.GetName() == name
+			})).Should(BeNumerically("<", 0))
+		}
+	}
+
 	Context("PreCondition & Reconcile", func() {
 		It("should work well", func() {
 			By("PreCondition")
@@ -61,7 +100,7 @@ var _ = Describe("update reconciler test", func() {
 
 			By("prepare current tree")
 			// desired: bar-hello-0, bar-foo-1, bar-foo-0, bar-3, bar-2, bar-1, bar-0
-			replicas := int32(7)
+			replicas = int32(7)
 			its.Spec.Replicas = &replicas
 			its.Spec.PodManagementPolicy = appsv1.ParallelPodManagement
 			nameHello := "hello"
@@ -77,30 +116,7 @@ var _ = Describe("update reconciler test", func() {
 			}
 			its.Spec.Instances = append(its.Spec.Instances, instanceFoo)
 
-			// prepare for update
-			By("fix meta")
-			reconciler = NewFixMetaReconciler()
-			res, err := reconciler.Reconcile(tree)
-			Expect(err).Should(BeNil())
-			Expect(res).Should(Equal(kubebuilderx.Commit))
-
-			By("update revisions")
-			reconciler = NewRevisionUpdateReconciler()
-			res, err = reconciler.Reconcile(tree)
-			Expect(err).Should(BeNil())
-			Expect(res).Should(Equal(kubebuilderx.Continue))
-
-			By("assistant object")
-			reconciler = NewAssistantObjectReconciler()
-			res, err = reconciler.Reconcile(tree)
-			Expect(err).Should(BeNil())
-			Expect(res).Should(Equal(kubebuilderx.Continue))
-
-			By("replicas alignment")
-			reconciler = NewReplicasAlignmentReconciler()
-			res, err = reconciler.Reconcile(tree)
-			Expect(err).Should(BeNil())
-			Expect(res).Should(Equal(kubebuilderx.Continue))
+			prepareForUpdate(tree)
 
 			By("update all pods to ready with outdated revision")
 			pods := tree.List(&corev1.Pod{})
@@ -125,15 +141,6 @@ var _ = Describe("update reconciler test", func() {
 				makePodAvailableWithOldRevision(pod)
 			}
 
-			expectUpdatedPods := func(tree *kubebuilderx.ObjectTree, names []string) {
-				pods = tree.List(&corev1.Pod{})
-				Expect(pods).Should(HaveLen(int(replicas) - len(names)))
-				for _, name := range names {
-					Expect(slices.IndexFunc(pods, func(object client.Object) bool {
-						return object.GetName() == name
-					})).Should(BeNumerically("<", 0))
-				}
-			}
 			makePodLatestRevision := func(pod *corev1.Pod) {
 				labels := pod.Labels
 				if labels == nil {
@@ -150,7 +157,7 @@ var _ = Describe("update reconciler test", func() {
 			// expected: bar-hello-0 being deleted
 			defaultTree, err := tree.DeepCopy()
 			Expect(err).Should(BeNil())
-			res, err = reconciler.Reconcile(defaultTree)
+			res, err := reconciler.Reconcile(defaultTree)
 			Expect(err).Should(BeNil())
 			Expect(res).Should(Equal(kubebuilderx.Continue))
 			expectUpdatedPods(defaultTree, []string{"bar-hello-0"})
@@ -250,5 +257,40 @@ var _ = Describe("update reconciler test", func() {
 			Expect(res).Should(Equal(kubebuilderx.Continue))
 			expectUpdatedPods(strictInPlaceTree, []string{})
 		})
+
+		It("updates pending pod", func() {
+			tree := kubebuilderx.NewObjectTree()
+			its.Spec.PodManagementPolicy = appsv1.ParallelPodManagement
+			tree.SetRoot(its)
+
+			prepareForUpdate(tree)
+
+			pods := tree.List(&corev1.Pod{})
+			Expect(pods).Should(HaveLen(3))
+			readyCondition := corev1.PodCondition{
+				Type:               corev1.PodReady,
+				Status:             corev1.ConditionTrue,
+				LastTransitionTime: metav1.NewTime(time.Now().Add(-1 * minReadySeconds * time.Second)),
+			}
+			for i, object := range pods {
+				pod, ok := object.(*corev1.Pod)
+				Expect(ok).Should(BeTrue())
+				// mark the last pod pending with old revision
+				if i == len(pods)-1 {
+					pod.Labels[appsv1.ControllerRevisionHashLabelKey] = "old-revision"
+					pod.Status.Phase = corev1.PodPending
+					break
+				}
+				// mark first two pods available
+				pod.Status.Phase = corev1.PodRunning
+				pod.Status.Conditions = append(pod.Status.Conditions, readyCondition)
+			}
+
+			reconciler = NewUpdateReconciler()
+			res, err := reconciler.Reconcile(tree)
+			Expect(err).Should(BeNil())
+			Expect(res).Should(Equal(kubebuilderx.Continue))
+			expectUpdatedPods(tree, []string{"bar-2"})
+		})
 	})
 })