From c81a4cc27101bd3032142c9f77ebf3cd5de73fe5 Mon Sep 17 00:00:00 2001 From: Baolin Zhu Date: Thu, 19 Feb 2026 17:56:52 +0800 Subject: [PATCH 1/2] fix: proxy-webhook selector matches operator pods Both the `tekton-operator` and `tekton-operator-proxy-webhook` Deployments label their Pods with `name: tekton-operator`. The `tekton-operator-proxy-webhook` Service uses this same label as its only selector, so it inadvertently load-balances traffic across both Deployments. Because `tekton-operator` pods do not serve on port 8443, ~50% of admission webhook requests fail: failed calling webhook "proxy.operator.tekton.dev": Post ".../tekton-operator-proxy-webhook.../defaulting": dial tcp :443: connect: connection refused Because MutatingWebhookConfiguration has `failurePolicy: Fail`, each such failure immediately rejects TaskRun Pod creation. Rename the proxy-webhook Deployment's selector matchLabels and pod template label from `name: tekton-operator` to `name: tekton-operator-proxy-webhook`, and update the Service selector to match. The `app: tekton-operator` label is left unchanged. Applies to both Kubernetes and OpenShift manifests. Adding a set-based (NotIn) expression to the Service selector instead was not viable as Kubernetes Services only support equality-based (matchLabels) selectors. --- cmd/kubernetes/operator/kodata/webhook/webhook.yaml | 6 +++--- cmd/openshift/operator/kodata/webhook/webhook.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/kubernetes/operator/kodata/webhook/webhook.yaml b/cmd/kubernetes/operator/kodata/webhook/webhook.yaml index 808f34c544..5f2a3ad938 100644 --- a/cmd/kubernetes/operator/kodata/webhook/webhook.yaml +++ b/cmd/kubernetes/operator/kodata/webhook/webhook.yaml @@ -92,11 +92,11 @@ spec: replicas: 1 selector: matchLabels: - name: tekton-operator + name: tekton-operator-proxy-webhook template: metadata: labels: - name: tekton-operator + name: tekton-operator-proxy-webhook app: tekton-operator spec: serviceAccountName: tekton-operators-proxy-webhook @@ -152,7 +152,7 @@ spec: port: 443 targetPort: 8443 selector: - name: tekton-operator + name: tekton-operator-proxy-webhook --- diff --git a/cmd/openshift/operator/kodata/webhook/webhook.yaml b/cmd/openshift/operator/kodata/webhook/webhook.yaml index 58e8e8f720..292053b62c 100644 --- a/cmd/openshift/operator/kodata/webhook/webhook.yaml +++ b/cmd/openshift/operator/kodata/webhook/webhook.yaml @@ -99,11 +99,11 @@ spec: replicas: 1 selector: matchLabels: - name: tekton-operator + name: tekton-operator-proxy-webhook template: metadata: labels: - name: tekton-operator + name: tekton-operator-proxy-webhook app: tekton-operator spec: securityContext: @@ -160,7 +160,7 @@ spec: port: 443 targetPort: 8443 selector: - name: tekton-operator + name: tekton-operator-proxy-webhook --- From aa45bf2398fbac145794d5c1077ad6e08bf108f5 Mon Sep 17 00:00:00 2001 From: Baolin Zhu Date: Thu, 11 Jun 2026 21:34:24 +0000 Subject: [PATCH 2/2] fix(tektoninstallerset): recreate deployment on selector change Delete and recreate managed Deployments when their selector changes, because Kubernetes treats spec.selector as immutable. This keeps upgrades working for proxy-webhook selector migrations. Co-authored-by: OpenAI Codex --- .../kubernetes/tektoninstallerset/install.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/reconciler/kubernetes/tektoninstallerset/install.go b/pkg/reconciler/kubernetes/tektoninstallerset/install.go index ce9f213618..0f52689a97 100644 --- a/pkg/reconciler/kubernetes/tektoninstallerset/install.go +++ b/pkg/reconciler/kubernetes/tektoninstallerset/install.go @@ -31,6 +31,7 @@ import ( autoscalingv2 "k8s.io/api/autoscaling/v2" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -528,6 +529,18 @@ func (i *installer) ensureResource(ctx context.Context, expected *unstructured.U "expectedHash", expectedHashValue, ) + if selectorChanged, err := deploymentSelectorChanged(expected, existing); err != nil { + loggerWithContext.Errorw("failed to compare deployment selectors", "error", err) + return err + } else if selectorChanged { + loggerWithContext.Infow("deployment selector changed, deleting resource before recreate") + if err := i.mfClient.Delete(existing); err != nil { + loggerWithContext.Errorw("failed to delete deployment with changed selector", "error", err) + return v1alpha1.RECONCILE_AGAIN_ERR + } + return v1alpha1.RECONCILE_AGAIN_ERR + } + err = i.copyResourceFields(expected, existing, reconcileFields...) if err != nil { loggerWithContext.Errorw("failed to copy resource fields", "error", err) @@ -547,6 +560,24 @@ func (i *installer) ensureResource(ctx context.Context, expected *unstructured.U return nil } +func deploymentSelectorChanged(expected, existing *unstructured.Unstructured) (bool, error) { + if expected.GetKind() != "Deployment" { + return false, nil + } + + expectedDeployment := &appsv1.Deployment{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(expected.Object, expectedDeployment); err != nil { + return false, err + } + + existingDeployment := &appsv1.Deployment{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(existing.Object, existingDeployment); err != nil { + return false, err + } + + return !equality.Semantic.DeepEqual(expectedDeployment.Spec.Selector, existingDeployment.Spec.Selector), nil +} + func (i *installer) removeExtraKeyInMap(src, dst map[string]string) map[string]string { newMap := map[string]string{} if len(src) == 0 {