From 8b6d6a1c5fc497129a28ba95bbeb2e616f094d50 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Tue, 24 Mar 2026 08:41:32 -0700 Subject: [PATCH 01/14] Use uber calico/calico image for OSS component deployments Switch the operator to use the consolidated calico/calico uber image for all OSS (non-enterprise, non-FIPS) component deployments: typha, kube-controllers, apiserver, CSI driver, CNI plugin, and flexvol. Each component gets an explicit Command override to dispatch to the correct subcommand (e.g., ["calico", "typha"]). Enterprise and FIPS deployments continue using per-component images unchanged. --- config/calico_versions.yml | 2 ++ hack/gen-versions/calico.go.tpl | 10 +++++++ hack/gen-versions/components.go | 2 +- pkg/components/calico.go | 9 +++++++ pkg/render/apiserver.go | 10 ++++++- pkg/render/apiserver_test.go | 3 ++- pkg/render/csi.go | 10 +++++-- pkg/render/csi_test.go | 2 +- .../kubecontrollers/kube-controllers.go | 26 ++++++++++++------- .../kubecontrollers/kube-controllers_test.go | 7 ++++- pkg/render/node.go | 20 +++++++++++--- pkg/render/node_test.go | 13 ++++++++-- pkg/render/typha.go | 10 +++++-- 13 files changed, 100 insertions(+), 24 deletions(-) diff --git a/config/calico_versions.yml b/config/calico_versions.yml index 67b2c93b33..a6b09691e0 100644 --- a/config/calico_versions.yml +++ b/config/calico_versions.yml @@ -49,3 +49,5 @@ components: version: master webhooks: version: master + calico: + version: master diff --git a/hack/gen-versions/calico.go.tpl b/hack/gen-versions/calico.go.tpl index 90630e5476..cf6006fc7a 100644 --- a/hack/gen-versions/calico.go.tpl +++ b/hack/gen-versions/calico.go.tpl @@ -282,6 +282,15 @@ var ( variant: calicoVariant, } {{- end }} +{{ with index .Components.calico }} + ComponentCalico = Component{ + Version: "{{ .Version }}", + Image: "{{ .Image }}", + Registry: "{{ .Registry }}", + imagePath: "{{ .ImagePath }}", + variant: calicoVariant, + } +{{- end }} CalicoImages = []Component{ ComponentCalicoCNI, @@ -314,5 +323,6 @@ var ( ComponentCalicoIstioZTunnel, ComponentCalicoIstioProxyv2, ComponentCalicoWebhooks, + ComponentCalico, } ) diff --git a/hack/gen-versions/components.go b/hack/gen-versions/components.go index 2c3139460a..302a47d467 100644 --- a/hack/gen-versions/components.go +++ b/hack/gen-versions/components.go @@ -64,10 +64,10 @@ var ( "istio-ztunnel": "istio-ztunnel", "istio-proxyv2": "istio-proxyv2", "webhooks": "webhooks", + "calico": "calico", } ignoredImages = map[string]struct{}{ - "calico": {}, "networking-calico": {}, "calico-private": {}, "manager-proxy": {}, diff --git a/pkg/components/calico.go b/pkg/components/calico.go index 7dad787921..0c7c3b2b36 100644 --- a/pkg/components/calico.go +++ b/pkg/components/calico.go @@ -260,6 +260,14 @@ var ( variant: calicoVariant, } + ComponentCalico = Component{ + Version: "master", + Image: "calico", + Registry: "", + imagePath: "", + variant: calicoVariant, + } + CalicoImages = []Component{ ComponentCalicoCNI, ComponentCalicoCNIFIPS, @@ -291,5 +299,6 @@ var ( ComponentCalicoIstioZTunnel, ComponentCalicoIstioProxyv2, ComponentCalicoWebhooks, + ComponentCalico, } ) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index d22d50050e..bd768d4ec0 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -157,6 +157,7 @@ type apiServerComponent struct { l7AdmissionControllerImage string l7AdmissionControllerEnvoyImage string dikastesImage string + uberImage bool } func (c *apiServerComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -196,10 +197,11 @@ func (c *apiServerComponent) ResolveImages(is *operatorv1.ImageSet) error { errMsgs = append(errMsgs, err.Error()) } } else { - c.apiServerImage, err = components.GetReference(components.ComponentCalicoAPIServer, reg, path, prefix, is) + c.apiServerImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) if err != nil { errMsgs = append(errMsgs, err.Error()) } + c.uberImage = true } } @@ -1175,9 +1177,15 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { apiServerTargetPort := getContainerPort(c.cfg, APIServerContainerName).ContainerPort + var apiServerCommand []string + if c.uberImage { + apiServerCommand = []string{"calico", "apiserver"} + } + apiServer := corev1.Container{ Name: string(APIServerContainerName), Image: c.apiServerImage, + Command: apiServerCommand, ImagePullPolicy: ImagePullPolicy(), Args: c.startUpArgs(), Env: env, diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index bc61d958ac..3fb81a6109 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -1867,8 +1867,9 @@ var _ = Describe("API server rendering tests (Calico)", func() { Expect(len(d.Spec.Template.Spec.Containers)).To(Equal(1)) Expect(d.Spec.Template.Spec.Containers[0].Name).To(Equal("calico-apiserver")) Expect(d.Spec.Template.Spec.Containers[0].Image).To(Equal( - fmt.Sprintf("testregistry.com/%s%s:%s", components.CalicoImagePath, components.ComponentCalicoAPIServer.Image, components.ComponentCalicoAPIServer.Version), + fmt.Sprintf("testregistry.com/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version), )) + Expect(d.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "apiserver"})) expectedArgs := []string{ "--secure-port=5443", diff --git a/pkg/render/csi.go b/pkg/render/csi.go index 1d01086437..0a35ac40a5 100644 --- a/pkg/render/csi.go +++ b/pkg/render/csi.go @@ -53,6 +53,7 @@ type csiComponent struct { csiImage string csiRegistrarImage string + uberImage bool } func CSI(cfg *CSIConfiguration) Component { @@ -138,9 +139,14 @@ func (c *csiComponent) csiAffinities() *corev1.Affinity { func (c *csiComponent) csiContainers() []corev1.Container { mountPropagation := corev1.MountPropagationBidirectional + var csiCommand []string + if c.uberImage { + csiCommand = []string{"calico", "csi"} + } csiContainer := corev1.Container{ Name: CSIContainerName, Image: c.csiImage, + Command: csiCommand, ImagePullPolicy: ImagePullPolicy(), Args: []string{ "--nodeid=$(KUBE_NODE_NAME)", @@ -393,11 +399,11 @@ func (c *csiComponent) ResolveImages(is *operatorv1.ImageSet) error { } c.csiRegistrarImage, err = components.GetReference(components.ComponentCalicoCSIRegistrarFIPS, reg, path, prefix, is) } else { - c.csiImage, err = components.GetReference(components.ComponentCalicoCSI, reg, path, prefix, is) + c.csiImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) if err != nil { return err } - + c.uberImage = true c.csiRegistrarImage, err = components.GetReference(components.ComponentCalicoCSIRegistrar, reg, path, prefix, is) } } diff --git a/pkg/render/csi_test.go b/pkg/render/csi_test.go index 7a44140d71..a46bc97268 100644 --- a/pkg/render/csi_test.go +++ b/pkg/render/csi_test.go @@ -309,7 +309,7 @@ var _ = Describe("CSI rendering tests", func() { Expect(comp.ResolveImages(nil)).To(BeNil()) createObjs, _ := comp.Objects() dsResource := rtest.GetResource(createObjs, "csi-node-driver", common.CalicoNamespace, "apps", "v1", "DaemonSet") - Expect(dsResource.(*appsv1.DaemonSet).Spec.Template.Spec.Containers[0].Image).To(Equal(fmt.Sprintf("%s%s%s:%s", components.CalicoRegistry, components.CalicoImagePath, components.ComponentCalicoCSI.Image, components.ComponentCalicoCSI.Version))) + Expect(dsResource.(*appsv1.DaemonSet).Spec.Template.Spec.Containers[0].Image).To(Equal(fmt.Sprintf("%s%s%s:%s", components.CalicoRegistry, components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version))) Expect(dsResource.(*appsv1.DaemonSet).Spec.Template.Spec.Containers[1].Image).To(Equal(fmt.Sprintf("%s%s%s:%s", components.CalicoRegistry, components.CalicoImagePath, components.ComponentCalicoCSIRegistrar.Image, components.ComponentCalicoCSIRegistrar.Version))) }) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 2bd28b4edf..af88b221e5 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -216,7 +216,8 @@ type kubeControllersComponent struct { cfg *KubeControllersConfiguration // Internal state generated by the given configuration. - image string + image string + uberImage bool kubeControllerServiceAccountName string kubeControllerRoleName string @@ -242,7 +243,8 @@ func (c *kubeControllersComponent) ResolveImages(is *operatorv1.ImageSet) error if operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { c.image, err = components.GetReference(components.ComponentCalicoKubeControllersFIPS, reg, path, prefix, is) } else { - c.image, err = components.GetReference(components.ComponentCalicoKubeControllers, reg, path, prefix, is) + c.image, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) + c.uberImage = true } } return err @@ -589,19 +591,26 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { sc.RunAsUser = ptr.Int64ToPtr(999) sc.RunAsGroup = ptr.Int64ToPtr(0) + readinessCmd := []string{"/usr/bin/check-status", "-r"} + livenessCmd := []string{"/usr/bin/check-status", "-l"} + var containerCommand []string + if c.uberImage { + containerCommand = []string{"calico", "kube-controllers"} + readinessCmd = []string{"calico", "check-status", "-r"} + livenessCmd = []string{"calico", "check-status", "-l"} + } + container := corev1.Container{ Name: c.kubeControllerName, Image: c.image, + Command: containerCommand, ImagePullPolicy: render.ImagePullPolicy(), Env: env, Resources: c.kubeControllersResources(), ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{ - "/usr/bin/check-status", - "-r", - }, + Command: readinessCmd, }, }, TimeoutSeconds: 10, @@ -609,10 +618,7 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{ - "/usr/bin/check-status", - "-l", - }, + Command: livenessCmd, }, }, FailureThreshold: 6, diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 72aba80380..69fe866fbe 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -191,9 +191,14 @@ var _ = Describe("kube-controllers rendering tests", func() { // Image override results in correct image. Expect(ds.Spec.Template.Spec.Containers[0].Image).To(Equal( - fmt.Sprintf("test-reg/%s%s:%s", components.CalicoImagePath, components.ComponentCalicoKubeControllers.Image, components.ComponentCalicoKubeControllers.Version), + fmt.Sprintf("test-reg/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version), )) + // Verify command and probes use the uber image entrypoint. + Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers"})) + Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "check-status", "-r"})) + Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "check-status", "-l"})) + // Verify env expectedEnv := []corev1.EnvVar{ {Name: "DATASTORE_TYPE", Value: "kubernetes"}, diff --git a/pkg/render/node.go b/pkg/render/node.go index 366d223b06..cffad13550 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -170,6 +170,7 @@ type nodeComponent struct { cniImage string flexvolImage string nodeImage string + uberImage bool } func (c *nodeComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -189,13 +190,15 @@ func (c *nodeComponent) ResolveImages(is *operatorv1.ImageSet) error { c.nodeImage = appendIfErr(components.GetReference(components.ComponentTigeraNode, reg, path, prefix, is)) c.flexvolImage = appendIfErr(components.GetReference(components.ComponentTigeraFlexVolume, reg, path, prefix, is)) } else { - c.flexvolImage = appendIfErr(components.GetReference(components.ComponentCalicoFlexVolume, reg, path, prefix, is)) if operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { + c.flexvolImage = appendIfErr(components.GetReference(components.ComponentCalicoFlexVolume, reg, path, prefix, is)) c.cniImage = appendIfErr(components.GetReference(components.ComponentCalicoCNIFIPS, reg, path, prefix, is)) c.nodeImage = appendIfErr(components.GetReference(components.ComponentCalicoNodeFIPS, reg, path, prefix, is)) } else { - c.cniImage = appendIfErr(components.GetReference(components.ComponentCalicoCNI, reg, path, prefix, is)) + c.cniImage = appendIfErr(components.GetReference(components.ComponentCalico, reg, path, prefix, is)) + c.flexvolImage = appendIfErr(components.GetReference(components.ComponentCalico, reg, path, prefix, is)) c.nodeImage = appendIfErr(components.GetReference(components.ComponentCalicoNode, reg, path, prefix, is)) + c.uberImage = true } } @@ -1187,11 +1190,16 @@ func (c *nodeComponent) cniContainer() corev1.Container { {MountPath: "/host/etc/cni/net.d", Name: "cni-net-dir"}, } + cniCommand := []string{"/opt/cni/bin/install"} + if c.uberImage { + cniCommand = []string{"calico", "cni", "install"} + } + return corev1.Container{ Name: "install-cni", Image: c.cniImage, ImagePullPolicy: ImagePullPolicy(), - Command: []string{"/opt/cni/bin/install"}, + Command: cniCommand, Env: cniEnv, SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: cniVolumeMounts, @@ -1205,9 +1213,15 @@ func (c *nodeComponent) flexVolumeContainer() corev1.Container { {MountPath: "/host/driver", Name: "flexvol-driver-host"}, } + var flexvolCommand []string + if c.uberImage { + flexvolCommand = []string{"calico", "flexvol"} + } + return corev1.Container{ Name: "flexvol-driver", Image: c.flexvolImage, + Command: flexvolCommand, ImagePullPolicy: ImagePullPolicy(), SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: flexVolumeMounts, diff --git a/pkg/render/node_test.go b/pkg/render/node_test.go index 0413d94490..c8172b0ccd 100644 --- a/pkg/render/node_test.go +++ b/pkg/render/node_test.go @@ -3335,15 +3335,21 @@ func verifyInitContainers(ds *appsv1.DaemonSet, instance *operatorv1.Installatio Expect(cniContainer).NotTo(BeNil()) rtest.ExpectEnv(cniContainer.Env, "CNI_CONF_NAME", "10-calico.conflist") rtest.ExpectEnv(cniContainer.Env, "SLEEP", "false") - cniImage := fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalicoCNI.Image, components.ComponentCalicoCNI.Version) + cniImage := fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version) + expectedCNICommand := []string{"calico", "cni", "install"} if instance.FIPSMode != nil && *instance.FIPSMode == operatorv1.FIPSModeEnabled { // Calico CNI image should have -fips suffix when FIPS mode is enabled. cniImage = fmt.Sprintf("quay.io/%s%s:%s-fips", components.CalicoImagePath, components.ComponentCalicoCNI.Image, components.ComponentCalicoCNI.Version) + expectedCNICommand = nil } if instance.Variant == operatorv1.TigeraSecureEnterprise { cniImage = components.TigeraRegistry + "tigera/cni:" + components.ComponentTigeraCNI.Version + expectedCNICommand = nil } Expect(cniContainer.Image).To(Equal(cniImage)) + if expectedCNICommand != nil { + Expect(cniContainer.Command).To(Equal(expectedCNICommand)) + } Expect(*cniContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) Expect(*cniContainer.SecurityContext.Privileged).To(BeTrue()) Expect(*cniContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) @@ -3429,8 +3435,11 @@ func verifyInitContainers(ds *appsv1.DaemonSet, instance *operatorv1.Installatio Expect(flexvolContainer).NotTo(BeNil()) if instance.Variant == operatorv1.TigeraSecureEnterprise { Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("%s%s%s:%s", components.TigeraRegistry, components.TigeraImagePath, components.ComponentTigeraFlexVolume.Image, components.ComponentTigeraFlexVolume.Version))) - } else { + } else if operatorv1.IsFIPSModeEnabled(instance.FIPSMode) { Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalicoFlexVolume.Image, components.ComponentCalicoFlexVolume.Version))) + } else { + Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version))) + Expect(flexvolContainer.Command).To(Equal([]string{"calico", "flexvol"})) } Expect(*flexvolContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) diff --git a/pkg/render/typha.go b/pkg/render/typha.go index 00c736e75c..e8a7d9e8ea 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -92,6 +92,7 @@ type typhaComponent struct { // Generated internal config, built from the given configuration. typhaImage string + uberImage bool } func (c *typhaComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -105,7 +106,8 @@ func (c *typhaComponent) ResolveImages(is *operatorv1.ImageSet) error { if operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { c.typhaImage, err = components.GetReference(components.ComponentCalicoTyphaFIPS, reg, path, prefix, is) } else { - c.typhaImage, err = components.GetReference(components.ComponentCalicoTypha, reg, path, prefix, is) + c.typhaImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) + c.uberImage = true } } if err != nil { @@ -568,7 +570,7 @@ func (c *typhaComponent) typhaPorts() []corev1.ContainerPort { // typhaContainer creates the main typha container. func (c *typhaComponent) typhaContainer() corev1.Container { lp, rp := c.livenessReadinessProbes("localhost") - return corev1.Container{ + container := corev1.Container{ Name: TyphaContainerName, Image: c.typhaImage, ImagePullPolicy: ImagePullPolicy(), @@ -580,6 +582,10 @@ func (c *typhaComponent) typhaContainer() corev1.Container { ReadinessProbe: rp, SecurityContext: securitycontext.NewNonRootContext(), } + if c.uberImage { + container.Command = []string{"calico", "typha"} + } + return container } func (c *typhaComponent) typhaContainerNonClusterHost() corev1.Container { From d01a1dc7cc0319988d3f6ecacd509d040123b6d0 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Tue, 24 Mar 2026 10:20:44 -0700 Subject: [PATCH 02/14] Use uber image for goldmane, webhooks, and whisker-backend Extend the uber calico/calico image usage to goldmane, webhooks, and whisker-backend components for non-enterprise, non-FIPS deployments. Each component gets a command override to dispatch to the correct subcommand in the uber binary. --- pkg/render/goldmane/component.go | 14 ++++++++++++-- pkg/render/goldmane/component_test.go | 4 +++- pkg/render/webhooks/render.go | 12 +++++++++++- pkg/render/webhooks/render_test.go | 27 +++++++++++++++++++++++++++ pkg/render/whisker/component.go | 14 ++++++++++++-- pkg/render/whisker/component_test.go | 6 ++++-- 6 files changed, 69 insertions(+), 8 deletions(-) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 72ee39983d..2520055b7e 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -83,6 +83,7 @@ type Component struct { cfg *Configuration goldmaneImage string + uberImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -92,7 +93,12 @@ func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { var err error - c.goldmaneImage, err = components.GetReference(components.ComponentCalicoGoldmane, reg, path, prefix, is) + if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise || operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { + c.goldmaneImage, err = components.GetReference(components.ComponentCalicoGoldmane, reg, path, prefix, is) + } else { + c.goldmaneImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) + c.uberImage = true + } if err != nil { return err } @@ -236,7 +242,7 @@ func (c *Component) goldmaneContainer() corev1.Container { MountPath: GoldmaneConfigFilePath, }) - return corev1.Container{ + container := corev1.Container{ Name: GoldmaneContainerName, Image: c.goldmaneImage, ImagePullPolicy: render.ImagePullPolicy(), @@ -256,6 +262,10 @@ func (c *Component) goldmaneContainer() corev1.Container { }, VolumeMounts: volumeMounts, } + if c.uberImage { + container.Command = []string{"calico", "goldmane"} + } + return container } func (c *Component) goldmaneService() *corev1.Service { diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index fecd02d01b..05b3e9cc1d 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -92,6 +92,7 @@ var _ = Describe("ComponentRendering", func() { DescribeTable("Goldmane Deployment", func(cfg *goldmane.Configuration, expected *appsv1.Deployment) { component := goldmane.Goldmane(cfg) + Expect(component.ResolveImages(nil)).NotTo(HaveOccurred()) objsToCreate, _ := component.Objects() deployment, err := rtest.GetResourceOfType[*appsv1.Deployment](objsToCreate, goldmane.GoldmaneName, goldmane.GoldmaneNamespace) @@ -138,7 +139,8 @@ var _ = Describe("ComponentRendering", func() { Containers: []corev1.Container{ { Name: goldmane.GoldmaneContainerName, - Image: "", + Image: "quay.io/calico/calico:master", + Command: []string{"calico", "goldmane"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, diff --git a/pkg/render/webhooks/render.go b/pkg/render/webhooks/render.go index 4ce74f7269..eb48ad228a 100644 --- a/pkg/render/webhooks/render.go +++ b/pkg/render/webhooks/render.go @@ -71,6 +71,7 @@ type component struct { // Images. webhooksImage string + uberImage bool } func (c *component) ResolveImages(is *operatorv1.ImageSet) error { @@ -82,7 +83,12 @@ func (c *component) ResolveImages(is *operatorv1.ImageSet) error { if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { c.webhooksImage, err = components.GetReference(components.ComponentTigeraWebhooks, reg, path, prefix, is) } else { - c.webhooksImage, err = components.GetReference(components.ComponentCalicoWebhooks, reg, path, prefix, is) + if operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { + c.webhooksImage, err = components.GetReference(components.ComponentCalicoWebhooks, reg, path, prefix, is) + } else { + c.webhooksImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) + c.uberImage = true + } } return err } @@ -178,6 +184,10 @@ func (c *component) Objects() ([]client.Object, []client.Object) { }, } + if c.uberImage { + dep.Spec.Template.Spec.Containers[0].Command = []string{"calico", "webhooks"} + } + if c.cfg.Installation.ControlPlaneReplicas != nil && *c.cfg.Installation.ControlPlaneReplicas > 1 { dep.Spec.Template.Spec.Affinity = podaffinity.NewPodAntiAffinity(WebhooksName, []string{common.CalicoNamespace}) } diff --git a/pkg/render/webhooks/render_test.go b/pkg/render/webhooks/render_test.go index ae65da4c64..f09321ff6e 100644 --- a/pkg/render/webhooks/render_test.go +++ b/pkg/render/webhooks/render_test.go @@ -93,6 +93,16 @@ var _ = Describe("Webhooks rendering tests", func() { rtest.ExpectResources(resources, expectedResources) + // Verify the Calico (non-enterprise, non-FIPS) variant uses the uber calico/calico image with Command set. + dep := rtest.GetResource(resources, webhooks.WebhooksName, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(dep.Spec.Template.Spec.Containers).To(HaveLen(1)) + Expect(dep.Spec.Template.Spec.Containers[0].Image).To(Equal( + fmt.Sprintf("test-registry.com/%s%s:%s", + components.CalicoImagePath, + components.ComponentCalico.Image, + components.ComponentCalico.Version))) + Expect(dep.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "webhooks"})) + // Verify the ClusterRole includes expected rules. cr := rtest.GetResource(resources, webhooks.WebhooksName, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) Expect(cr.Rules).To(ContainElements( @@ -114,6 +124,23 @@ var _ = Describe("Webhooks rendering tests", func() { })) }) + It("should use the per-component image and no Command for Calico FIPS", func() { + fipsEnabled := operatorv1.FIPSModeEnabled + installation.FIPSMode = &fipsEnabled + component := webhooks.Component(cfg) + Expect(component.ResolveImages(nil)).NotTo(HaveOccurred()) + resources, _ := component.Objects() + + dep := rtest.GetResource(resources, webhooks.WebhooksName, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(dep.Spec.Template.Spec.Containers).To(HaveLen(1)) + Expect(dep.Spec.Template.Spec.Containers[0].Image).To(Equal( + fmt.Sprintf("test-registry.com/%s%s:%s", + components.CalicoImagePath, + components.ComponentCalicoWebhooks.Image, + components.ComponentCalicoWebhooks.Version))) + Expect(dep.Spec.Template.Spec.Containers[0].Command).To(BeNil()) + }) + It("should render all resources for Enterprise with the correct image", func() { installation.Variant = operatorv1.TigeraSecureEnterprise component := webhooks.Component(cfg) diff --git a/pkg/render/whisker/component.go b/pkg/render/whisker/component.go index 82c99ecbef..da436e9995 100644 --- a/pkg/render/whisker/component.go +++ b/pkg/render/whisker/component.go @@ -95,6 +95,7 @@ type Component struct { whiskerImage string whiskerBackendImage string + uberImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -109,7 +110,12 @@ func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { return err } - c.whiskerBackendImage, err = components.GetReference(components.ComponentCalicoWhiskerBackend, reg, path, prefix, is) + if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise || operatorv1.IsFIPSModeEnabled(c.cfg.Installation.FIPSMode) { + c.whiskerBackendImage, err = components.GetReference(components.ComponentCalicoWhiskerBackend, reg, path, prefix, is) + } else { + c.whiskerBackendImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) + c.uberImage = true + } if err != nil { return err } @@ -199,7 +205,7 @@ func (c *Component) whiskerService() *corev1.Service { } func (c *Component) whiskerBackendContainer() corev1.Container { - return corev1.Container{ + container := corev1.Container{ Name: WhiskerBackendContainerName, Image: c.whiskerBackendImage, ImagePullPolicy: render.ImagePullPolicy(), @@ -215,6 +221,10 @@ func (c *Component) whiskerBackendContainer() corev1.Container { c.cfg.TrustedCertBundle.VolumeMounts(c.SupportedOSType()), c.cfg.WhiskerBackendKeyPair.VolumeMount(c.SupportedOSType())), } + if c.uberImage { + container.Command = []string{"calico", "whisker-backend"} + } + return container } func (c *Component) deployment() *appsv1.Deployment { diff --git a/pkg/render/whisker/component_test.go b/pkg/render/whisker/component_test.go index 28aaed763c..25c6328c50 100644 --- a/pkg/render/whisker/component_test.go +++ b/pkg/render/whisker/component_test.go @@ -77,6 +77,7 @@ var _ = Describe("ComponentRendering", func() { DescribeTable("Whisker Deployment", func(cfg *whisker.Configuration, expected *appsv1.Deployment) { component := whisker.Whisker(cfg) + Expect(component.ResolveImages(nil)).NotTo(HaveOccurred()) objsToCreate, _ := component.Objects() deployment, err := rtest.GetResourceOfType[*appsv1.Deployment](objsToCreate, whisker.WhiskerName, whisker.WhiskerNamespace) @@ -118,7 +119,7 @@ var _ = Describe("ComponentRendering", func() { Containers: []corev1.Container{ { Name: whisker.WhiskerContainerName, - Image: "", + Image: "quay.io/calico/whisker:master", ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, @@ -138,7 +139,8 @@ var _ = Describe("ComponentRendering", func() { }, { Name: whisker.WhiskerBackendContainerName, - Image: "", + Image: "quay.io/calico/calico:master", + Command: []string{"calico", "whisker-backend"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, From 2996f7238b3ed3851e596e1026419f5ad4a75cbb Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Tue, 24 Mar 2026 17:45:37 -0700 Subject: [PATCH 03/14] Switch goldmane to HTTP health probes for uber image The uber image doesn't include the separate /health binary. Use httpGet probes against goldmane's health server instead of exec probes when using the uber image. --- pkg/render/goldmane/component.go | 54 ++++++++++++++++++--------- pkg/render/goldmane/component_test.go | 17 +++++---- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 2520055b7e..ab9c616fad 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -59,6 +59,7 @@ const ( GoldmaneConfigFilePath = "/config" GoldmaneConfigFileName = "config.json" GoldmaneMetricsServiceName = "goldmane-metrics" + GoldmaneHealthPort = 8080 ) func Goldmane(cfg *Configuration) render.Component { @@ -242,30 +243,47 @@ func (c *Component) goldmaneContainer() corev1.Container { MountPath: GoldmaneConfigFilePath, }) - container := corev1.Container{ + readinessProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"/health", "-ready"}, + }}, + } + livenessProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"/health", "-live"}, + }}, + } + + var containerCommand []string + if c.uberImage { + containerCommand = []string{"calico", "goldmane"} + readinessProbe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Host: "localhost", + Path: "/readiness", + Port: intstr.FromInt(GoldmaneHealthPort), + }}, + } + livenessProbe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Host: "localhost", + Path: "/liveness", + Port: intstr.FromInt(GoldmaneHealthPort), + }}, + } + } + + return corev1.Container{ Name: GoldmaneContainerName, Image: c.goldmaneImage, ImagePullPolicy: render.ImagePullPolicy(), + Command: containerCommand, Env: env, SecurityContext: securitycontext.NewNonRootContext(), - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"/health", "-ready"}, - }}, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"/health", "-live"}, - }, - }, - }, - VolumeMounts: volumeMounts, - } - if c.uberImage { - container.Command = []string{"calico", "goldmane"} + ReadinessProbe: readinessProbe, + LivenessProbe: livenessProbe, + VolumeMounts: volumeMounts, } - return container } func (c *Component) goldmaneService() *corev1.Service { diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index 05b3e9cc1d..830e3460df 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" @@ -154,16 +155,18 @@ var _ = Describe("ComponentRendering", func() { }, SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"/health", "-ready"}, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Host: "localhost", + Path: "/readiness", + Port: intstr.FromInt(goldmane.GoldmaneHealthPort), }}, }, LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"/health", "-live"}, - }, - }, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Host: "localhost", + Path: "/liveness", + Port: intstr.FromInt(goldmane.GoldmaneHealthPort), + }}, }, VolumeMounts: append( []corev1.VolumeMount{defaultTLSKeyPair.VolumeMount(rmeta.OSTypeLinux)}, From 80a17f5135660b5529302e8743cd1a454c8a119e Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Fri, 27 Mar 2026 17:03:02 -0700 Subject: [PATCH 04/14] Switch goldmane uber probes back to exec The HTTP probes fail on dual-stack clusters because the kubelet resolves localhost to [::1] (IPv6) while goldmane's health server binds only to 127.0.0.1. Use exec probes with the new "calico goldmane-check" subcommand to match how the standalone image worked. --- pkg/render/goldmane/component.go | 12 ++++-------- pkg/render/goldmane/component_test.go | 13 ++++--------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index ab9c616fad..63bbd8ea44 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -258,17 +258,13 @@ func (c *Component) goldmaneContainer() corev1.Container { if c.uberImage { containerCommand = []string{"calico", "goldmane"} readinessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Host: "localhost", - Path: "/readiness", - Port: intstr.FromInt(GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "goldmane-check", "--ready"}, }}, } livenessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Host: "localhost", - Path: "/liveness", - Port: intstr.FromInt(GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "goldmane-check", "--live"}, }}, } } diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index 830e3460df..efcd84d96b 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -24,7 +24,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" @@ -155,17 +154,13 @@ var _ = Describe("ComponentRendering", func() { }, SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Host: "localhost", - Path: "/readiness", - Port: intstr.FromInt(goldmane.GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "goldmane-check", "--ready"}, }}, }, LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Host: "localhost", - Path: "/liveness", - Port: intstr.FromInt(goldmane.GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "goldmane-check", "--live"}, }}, }, VolumeMounts: append( From 4ce3dcbb88b2f7fa3c08d807a62da772615d91ee Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Fri, 27 Mar 2026 20:19:03 -0700 Subject: [PATCH 05/14] Switch goldmane uber probes to httpGet The goldmane HealthAggregator now binds all interfaces instead of localhost, so kubelet HTTP probes work on dual-stack clusters. Switch from exec-based probes back to native httpGet probes. --- pkg/render/goldmane/component.go | 10 ++++++---- pkg/render/goldmane/component_test.go | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 63bbd8ea44..3f43ee5ad7 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -258,13 +258,15 @@ func (c *Component) goldmaneContainer() corev1.Container { if c.uberImage { containerCommand = []string{"calico", "goldmane"} readinessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"calico", "goldmane-check", "--ready"}, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Path: "/readiness", + Port: intstr.FromInt(GoldmaneHealthPort), }}, } livenessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"calico", "goldmane-check", "--live"}, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Path: "/liveness", + Port: intstr.FromInt(GoldmaneHealthPort), }}, } } diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index efcd84d96b..04daafa383 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" @@ -154,13 +155,15 @@ var _ = Describe("ComponentRendering", func() { }, SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"calico", "goldmane-check", "--ready"}, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Path: "/readiness", + Port: intstr.FromInt(goldmane.GoldmaneHealthPort), }}, }, LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"calico", "goldmane-check", "--live"}, + ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Path: "/liveness", + Port: intstr.FromInt(goldmane.GoldmaneHealthPort), }}, }, VolumeMounts: append( From 338c5381752ee46ff81c26c8ce1293a298353f37 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Fri, 27 Mar 2026 20:34:00 -0700 Subject: [PATCH 06/14] Use generic calico health exec probes for uber image components Switch goldmane and kube-controllers uber probes to use the generic "calico health" command with exec probes. This avoids host-to-pod HTTP traffic while using the standardized health check infrastructure. Kube-controllers now starts an HTTP health server on port 9094 via --health-port flag, alongside the legacy file-based status. --- pkg/render/goldmane/component.go | 18 +++--- pkg/render/goldmane/component_test.go | 13 ++--- .../kubecontrollers/kube-controllers.go | 56 ++++++++++++------- .../kubecontrollers/kube-controllers_test.go | 8 +-- 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 3f43ee5ad7..e42d56acab 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -258,16 +258,18 @@ func (c *Component) goldmaneContainer() corev1.Container { if c.uberImage { containerCommand = []string{"calico", "goldmane"} readinessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Path: "/readiness", - Port: intstr.FromInt(GoldmaneHealthPort), - }}, + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=readiness"}, + }, + }, } livenessProbe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Path: "/liveness", - Port: intstr.FromInt(GoldmaneHealthPort), - }}, + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=liveness"}, + }, + }, } } diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index 04daafa383..7c26da4cf3 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -15,6 +15,8 @@ package goldmane_test import ( + "fmt" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -24,7 +26,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" @@ -155,15 +156,13 @@ var _ = Describe("ComponentRendering", func() { }, SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Path: "/readiness", - Port: intstr.FromInt(goldmane.GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", goldmane.GoldmaneHealthPort), "--type=readiness"}, }}, }, LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Path: "/liveness", - Port: intstr.FromInt(goldmane.GoldmaneHealthPort), + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", goldmane.GoldmaneHealthPort), "--type=liveness"}, }}, }, VolumeMounts: append( diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index af88b221e5..8e5f6672f4 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -591,40 +591,56 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { sc.RunAsUser = ptr.Int64ToPtr(999) sc.RunAsGroup = ptr.Int64ToPtr(0) - readinessCmd := []string{"/usr/bin/check-status", "-r"} - livenessCmd := []string{"/usr/bin/check-status", "-l"} + readinessProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/usr/bin/check-status", "-r"}, + }, + }, + TimeoutSeconds: 10, + } + livenessProbe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/usr/bin/check-status", "-l"}, + }, + }, + FailureThreshold: 6, + InitialDelaySeconds: 10, + TimeoutSeconds: 10, + } var containerCommand []string if c.uberImage { - containerCommand = []string{"calico", "kube-controllers"} - readinessCmd = []string{"calico", "check-status", "-r"} - livenessCmd = []string{"calico", "check-status", "-l"} - } - - container := corev1.Container{ - Name: c.kubeControllerName, - Image: c.image, - Command: containerCommand, - ImagePullPolicy: render.ImagePullPolicy(), - Env: env, - Resources: c.kubeControllersResources(), - ReadinessProbe: &corev1.Probe{ + containerCommand = []string{"calico", "kube-controllers", "--health-port=9094"} + readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: readinessCmd, + Command: []string{"calico", "health", "--port=9094", "--type=readiness"}, }, }, TimeoutSeconds: 10, - }, - LivenessProbe: &corev1.Probe{ + } + livenessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: livenessCmd, + Command: []string{"calico", "health", "--port=9094", "--type=liveness"}, }, }, FailureThreshold: 6, InitialDelaySeconds: 10, TimeoutSeconds: 10, - }, + } + } + + container := corev1.Container{ + Name: c.kubeControllerName, + Image: c.image, + Command: containerCommand, + ImagePullPolicy: render.ImagePullPolicy(), + Env: env, + Resources: c.kubeControllersResources(), + ReadinessProbe: readinessProbe, + LivenessProbe: livenessProbe, SecurityContext: sc, VolumeMounts: c.kubeControllersVolumeMounts(), } diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 69fe866fbe..059c7739d8 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -194,10 +194,10 @@ var _ = Describe("kube-controllers rendering tests", func() { fmt.Sprintf("test-reg/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version), )) - // Verify command and probes use the uber image entrypoint. - Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers"})) - Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "check-status", "-r"})) - Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "check-status", "-l"})) + // Verify command and probes use the uber image entrypoint with generic health check. + Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers", "--health-port=9094"})) + Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9094", "--type=readiness"})) + Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9094", "--type=liveness"})) // Verify env expectedEnv := []corev1.EnvVar{ From 453155ac51e4f3ce2af185345d92054cdbe9d92e Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Fri, 27 Mar 2026 20:42:34 -0700 Subject: [PATCH 07/14] Fix kube-controllers health port to 9440 to avoid prometheus conflict --- pkg/render/kubecontrollers/kube-controllers.go | 6 +++--- pkg/render/kubecontrollers/kube-controllers_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 8e5f6672f4..29b2f33cf1 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -611,11 +611,11 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { } var containerCommand []string if c.uberImage { - containerCommand = []string{"calico", "kube-controllers", "--health-port=9094"} + containerCommand = []string{"calico", "kube-controllers", "--health-port=9440"} readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{"calico", "health", "--port=9094", "--type=readiness"}, + Command: []string{"calico", "health", "--port=9440", "--type=readiness"}, }, }, TimeoutSeconds: 10, @@ -623,7 +623,7 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { livenessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{"calico", "health", "--port=9094", "--type=liveness"}, + Command: []string{"calico", "health", "--port=9440", "--type=liveness"}, }, }, FailureThreshold: 6, diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 059c7739d8..6bf291829f 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -195,9 +195,9 @@ var _ = Describe("kube-controllers rendering tests", func() { )) // Verify command and probes use the uber image entrypoint with generic health check. - Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers", "--health-port=9094"})) - Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9094", "--type=readiness"})) - Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9094", "--type=liveness"})) + Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers", "--health-port=9440"})) + Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=readiness"})) + Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=liveness"})) // Verify env expectedEnv := []corev1.EnvVar{ From 1083fdc4d1388ccdd2a864e5f886561fab80368f Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 08:59:20 -0700 Subject: [PATCH 08/14] Rename uberImage to combinedImage throughout --- pkg/render/apiserver.go | 6 +++--- pkg/render/csi.go | 6 +++--- pkg/render/goldmane/component.go | 6 +++--- pkg/render/kubecontrollers/kube-controllers.go | 6 +++--- pkg/render/kubecontrollers/kube-controllers_test.go | 2 +- pkg/render/node.go | 8 ++++---- pkg/render/typha.go | 6 +++--- pkg/render/webhooks/render.go | 6 +++--- pkg/render/webhooks/render_test.go | 2 +- pkg/render/whisker/component.go | 6 +++--- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index bd768d4ec0..14113fb0c4 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -157,7 +157,7 @@ type apiServerComponent struct { l7AdmissionControllerImage string l7AdmissionControllerEnvoyImage string dikastesImage string - uberImage bool + combinedImage bool } func (c *apiServerComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -201,7 +201,7 @@ func (c *apiServerComponent) ResolveImages(is *operatorv1.ImageSet) error { if err != nil { errMsgs = append(errMsgs, err.Error()) } - c.uberImage = true + c.combinedImage = true } } @@ -1178,7 +1178,7 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { apiServerTargetPort := getContainerPort(c.cfg, APIServerContainerName).ContainerPort var apiServerCommand []string - if c.uberImage { + if c.combinedImage { apiServerCommand = []string{"calico", "apiserver"} } diff --git a/pkg/render/csi.go b/pkg/render/csi.go index 0a35ac40a5..573c2459e1 100644 --- a/pkg/render/csi.go +++ b/pkg/render/csi.go @@ -53,7 +53,7 @@ type csiComponent struct { csiImage string csiRegistrarImage string - uberImage bool + combinedImage bool } func CSI(cfg *CSIConfiguration) Component { @@ -140,7 +140,7 @@ func (c *csiComponent) csiAffinities() *corev1.Affinity { func (c *csiComponent) csiContainers() []corev1.Container { mountPropagation := corev1.MountPropagationBidirectional var csiCommand []string - if c.uberImage { + if c.combinedImage { csiCommand = []string{"calico", "csi"} } csiContainer := corev1.Container{ @@ -403,7 +403,7 @@ func (c *csiComponent) ResolveImages(is *operatorv1.ImageSet) error { if err != nil { return err } - c.uberImage = true + c.combinedImage = true c.csiRegistrarImage, err = components.GetReference(components.ComponentCalicoCSIRegistrar, reg, path, prefix, is) } } diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index e42d56acab..1240ce5f01 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -84,7 +84,7 @@ type Component struct { cfg *Configuration goldmaneImage string - uberImage bool + combinedImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -98,7 +98,7 @@ func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { c.goldmaneImage, err = components.GetReference(components.ComponentCalicoGoldmane, reg, path, prefix, is) } else { c.goldmaneImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) - c.uberImage = true + c.combinedImage = true } if err != nil { return err @@ -255,7 +255,7 @@ func (c *Component) goldmaneContainer() corev1.Container { } var containerCommand []string - if c.uberImage { + if c.combinedImage { containerCommand = []string{"calico", "goldmane"} readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 29b2f33cf1..8c246a354f 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -217,7 +217,7 @@ type kubeControllersComponent struct { // Internal state generated by the given configuration. image string - uberImage bool + combinedImage bool kubeControllerServiceAccountName string kubeControllerRoleName string @@ -244,7 +244,7 @@ func (c *kubeControllersComponent) ResolveImages(is *operatorv1.ImageSet) error c.image, err = components.GetReference(components.ComponentCalicoKubeControllersFIPS, reg, path, prefix, is) } else { c.image, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) - c.uberImage = true + c.combinedImage = true } } return err @@ -610,7 +610,7 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { TimeoutSeconds: 10, } var containerCommand []string - if c.uberImage { + if c.combinedImage { containerCommand = []string{"calico", "kube-controllers", "--health-port=9440"} readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 6bf291829f..4dd01b4f53 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -194,7 +194,7 @@ var _ = Describe("kube-controllers rendering tests", func() { fmt.Sprintf("test-reg/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version), )) - // Verify command and probes use the uber image entrypoint with generic health check. + // Verify command and probes use the combined image entrypoint with generic health check. Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers", "--health-port=9440"})) Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=readiness"})) Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=liveness"})) diff --git a/pkg/render/node.go b/pkg/render/node.go index cffad13550..f06c4591e1 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -170,7 +170,7 @@ type nodeComponent struct { cniImage string flexvolImage string nodeImage string - uberImage bool + combinedImage bool } func (c *nodeComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -198,7 +198,7 @@ func (c *nodeComponent) ResolveImages(is *operatorv1.ImageSet) error { c.cniImage = appendIfErr(components.GetReference(components.ComponentCalico, reg, path, prefix, is)) c.flexvolImage = appendIfErr(components.GetReference(components.ComponentCalico, reg, path, prefix, is)) c.nodeImage = appendIfErr(components.GetReference(components.ComponentCalicoNode, reg, path, prefix, is)) - c.uberImage = true + c.combinedImage = true } } @@ -1191,7 +1191,7 @@ func (c *nodeComponent) cniContainer() corev1.Container { } cniCommand := []string{"/opt/cni/bin/install"} - if c.uberImage { + if c.combinedImage { cniCommand = []string{"calico", "cni", "install"} } @@ -1214,7 +1214,7 @@ func (c *nodeComponent) flexVolumeContainer() corev1.Container { } var flexvolCommand []string - if c.uberImage { + if c.combinedImage { flexvolCommand = []string{"calico", "flexvol"} } diff --git a/pkg/render/typha.go b/pkg/render/typha.go index e8a7d9e8ea..cb94610160 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -92,7 +92,7 @@ type typhaComponent struct { // Generated internal config, built from the given configuration. typhaImage string - uberImage bool + combinedImage bool } func (c *typhaComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -107,7 +107,7 @@ func (c *typhaComponent) ResolveImages(is *operatorv1.ImageSet) error { c.typhaImage, err = components.GetReference(components.ComponentCalicoTyphaFIPS, reg, path, prefix, is) } else { c.typhaImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) - c.uberImage = true + c.combinedImage = true } } if err != nil { @@ -582,7 +582,7 @@ func (c *typhaComponent) typhaContainer() corev1.Container { ReadinessProbe: rp, SecurityContext: securitycontext.NewNonRootContext(), } - if c.uberImage { + if c.combinedImage { container.Command = []string{"calico", "typha"} } return container diff --git a/pkg/render/webhooks/render.go b/pkg/render/webhooks/render.go index eb48ad228a..e5c528b02c 100644 --- a/pkg/render/webhooks/render.go +++ b/pkg/render/webhooks/render.go @@ -71,7 +71,7 @@ type component struct { // Images. webhooksImage string - uberImage bool + combinedImage bool } func (c *component) ResolveImages(is *operatorv1.ImageSet) error { @@ -87,7 +87,7 @@ func (c *component) ResolveImages(is *operatorv1.ImageSet) error { c.webhooksImage, err = components.GetReference(components.ComponentCalicoWebhooks, reg, path, prefix, is) } else { c.webhooksImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) - c.uberImage = true + c.combinedImage = true } } return err @@ -184,7 +184,7 @@ func (c *component) Objects() ([]client.Object, []client.Object) { }, } - if c.uberImage { + if c.combinedImage { dep.Spec.Template.Spec.Containers[0].Command = []string{"calico", "webhooks"} } diff --git a/pkg/render/webhooks/render_test.go b/pkg/render/webhooks/render_test.go index f09321ff6e..4ee74941bb 100644 --- a/pkg/render/webhooks/render_test.go +++ b/pkg/render/webhooks/render_test.go @@ -93,7 +93,7 @@ var _ = Describe("Webhooks rendering tests", func() { rtest.ExpectResources(resources, expectedResources) - // Verify the Calico (non-enterprise, non-FIPS) variant uses the uber calico/calico image with Command set. + // Verify the Calico (non-enterprise, non-FIPS) variant uses the combined calico/calico image with Command set. dep := rtest.GetResource(resources, webhooks.WebhooksName, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) Expect(dep.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(dep.Spec.Template.Spec.Containers[0].Image).To(Equal( diff --git a/pkg/render/whisker/component.go b/pkg/render/whisker/component.go index da436e9995..f4925e3064 100644 --- a/pkg/render/whisker/component.go +++ b/pkg/render/whisker/component.go @@ -95,7 +95,7 @@ type Component struct { whiskerImage string whiskerBackendImage string - uberImage bool + combinedImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -114,7 +114,7 @@ func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { c.whiskerBackendImage, err = components.GetReference(components.ComponentCalicoWhiskerBackend, reg, path, prefix, is) } else { c.whiskerBackendImage, err = components.GetReference(components.ComponentCalico, reg, path, prefix, is) - c.uberImage = true + c.combinedImage = true } if err != nil { return err @@ -221,7 +221,7 @@ func (c *Component) whiskerBackendContainer() corev1.Container { c.cfg.TrustedCertBundle.VolumeMounts(c.SupportedOSType()), c.cfg.WhiskerBackendKeyPair.VolumeMount(c.SupportedOSType())), } - if c.uberImage { + if c.combinedImage { container.Command = []string{"calico", "whisker-backend"} } return container From 8946012d1d685e08493e0e9357c1a0da317ec83a Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 09:38:20 -0700 Subject: [PATCH 09/14] Use combined image for node init container and probes When the combined calico/calico image is available, use it for: - ebpf-bootstrap init container: calico node init [--best-effort] - preStop hook: calico node shutdown - readiness probe: calico node health --felix-ready [--bird-ready] The node container itself still uses calico/node since it needs the full base image with BIRD, runit, etc. --- pkg/render/node.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/pkg/render/node.go b/pkg/render/node.go index f06c4591e1..f17653cf71 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -1256,17 +1256,26 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { }, } + image := c.nodeImage command := []string{CalicoNodeObjectName, "-init"} + if c.combinedImage { + image = c.cniImage // cniImage is set to the combined calico/calico image + command = []string{"calico", "node", "init"} + } // If BPF is not enabled, then we run the init container in best-effort mode. // This means that it will not fail if the BPF filesystem is not mounted, but // it will still attempt to mount it if it is available. This is useful when we // are running calico in test environments like KinD or K3s. if !c.cfg.Installation.BPFEnabled() { - command = append(command, "-best-effort") + if c.combinedImage { + command = append(command, "--best-effort") + } else { + command = append(command, "-best-effort") + } } return corev1.Container{ Name: "ebpf-bootstrap", - Image: c.nodeImage, + Image: image, ImagePullPolicy: ImagePullPolicy(), Env: c.bpffsEnvvars(), Command: command, @@ -1730,6 +1739,9 @@ func (c *nodeComponent) nodeEnvVars() []corev1.EnvVar { // nodeLifecycle creates the node's postStart and preStop hooks. func (c *nodeComponent) nodeLifecycle() *corev1.Lifecycle { preStopCmd := []string{"/bin/calico-node", "-shutdown"} + if c.combinedImage { + preStopCmd = []string{"calico", "node", "shutdown"} + } lc := &corev1.Lifecycle{ PreStop: &corev1.LifecycleHandler{Exec: &corev1.ExecAction{Command: preStopCmd}}, } @@ -1740,16 +1752,24 @@ func (c *nodeComponent) nodeLifecycle() *corev1.Lifecycle { func (c *nodeComponent) nodeLivenessReadinessProbes() (*corev1.Probe, *corev1.Probe) { // Determine liveness and readiness configuration for node. livenessPort := intstr.FromInt(c.cfg.FelixHealthPort) - readinessCmd := []string{"/bin/calico-node", "-bird-ready", "-felix-ready"} - - // Want to check for BGP metrics server if this is enterprise - if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { - readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready", "-bgp-metrics-ready"} - } + var readinessCmd []string - // If not using BGP or using VPP, don't check bird status (or bgp metrics server for enterprise). - if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { - readinessCmd = []string{"/bin/calico-node", "-felix-ready"} + if c.combinedImage { + readinessCmd = []string{"calico", "node", "health", "--bird-ready", "--felix-ready"} + if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { + readinessCmd = append(readinessCmd, "--bgp-metrics-ready") + } + if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { + readinessCmd = []string{"calico", "node", "health", "--felix-ready"} + } + } else { + readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready"} + if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { + readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready", "-bgp-metrics-ready"} + } + if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { + readinessCmd = []string{"/bin/calico-node", "-felix-ready"} + } } lp := &corev1.Probe{ From 29d86af535e05f62dea82fb51a14ae129733514a Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 10:17:50 -0700 Subject: [PATCH 10/14] Fix node probes/lifecycle to use calico-node cobra syntax The calico-node binary now uses cobra subcommands. Update the operator to use the new syntax when the combined image layout is active: - Init container: calico-node init [--best-effort] (stays on node image) - PreStop: calico-node shutdown - Readiness: calico-node health --felix-ready [--bird-ready] The node container still uses calico/node since it needs the full base image with BIRD, runit, iptables, and bpftool. --- pkg/render/node.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/render/node.go b/pkg/render/node.go index f17653cf71..0665e2204a 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -1256,16 +1256,12 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { }, } - image := c.nodeImage + // The init container uses the node image (needs privileged base for BPF mount). + // calico-node uses cobra subcommands when the combined image layout is active. command := []string{CalicoNodeObjectName, "-init"} if c.combinedImage { - image = c.cniImage // cniImage is set to the combined calico/calico image - command = []string{"calico", "node", "init"} + command = []string{"/bin/calico-node", "init"} } - // If BPF is not enabled, then we run the init container in best-effort mode. - // This means that it will not fail if the BPF filesystem is not mounted, but - // it will still attempt to mount it if it is available. This is useful when we - // are running calico in test environments like KinD or K3s. if !c.cfg.Installation.BPFEnabled() { if c.combinedImage { command = append(command, "--best-effort") @@ -1738,9 +1734,10 @@ func (c *nodeComponent) nodeEnvVars() []corev1.EnvVar { // nodeLifecycle creates the node's postStart and preStop hooks. func (c *nodeComponent) nodeLifecycle() *corev1.Lifecycle { + // calico-node uses cobra subcommands when the combined image layout is active. preStopCmd := []string{"/bin/calico-node", "-shutdown"} if c.combinedImage { - preStopCmd = []string{"calico", "node", "shutdown"} + preStopCmd = []string{"/bin/calico-node", "shutdown"} } lc := &corev1.Lifecycle{ PreStop: &corev1.LifecycleHandler{Exec: &corev1.ExecAction{Command: preStopCmd}}, @@ -1754,13 +1751,16 @@ func (c *nodeComponent) nodeLivenessReadinessProbes() (*corev1.Probe, *corev1.Pr livenessPort := intstr.FromInt(c.cfg.FelixHealthPort) var readinessCmd []string + // The readiness probe runs inside the node container (calico/node image), which + // has calico-node. When using the combined image layout, calico-node uses cobra + // subcommands; otherwise it uses the legacy flag syntax. if c.combinedImage { - readinessCmd = []string{"calico", "node", "health", "--bird-ready", "--felix-ready"} + readinessCmd = []string{"/bin/calico-node", "health", "--bird-ready", "--felix-ready"} if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { readinessCmd = append(readinessCmd, "--bgp-metrics-ready") } if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { - readinessCmd = []string{"calico", "node", "health", "--felix-ready"} + readinessCmd = []string{"/bin/calico-node", "health", "--felix-ready"} } } else { readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready"} From 0c7a343d97df41aa071dead5eac5cf70b4514e33 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 17:48:57 -0700 Subject: [PATCH 11/14] Use combined calico binary from node image for probes/lifecycle The node image now includes the combined calico binary at /usr/bin/calico, avoiding the need to compile it separately. Update init container, probes, and lifecycle hooks to use "calico node" subcommands via /usr/bin/calico when the combined image layout is active. --- pkg/render/node.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/render/node.go b/pkg/render/node.go index 0665e2204a..fbc5448c63 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -1256,11 +1256,10 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { }, } - // The init container uses the node image (needs privileged base for BPF mount). - // calico-node uses cobra subcommands when the combined image layout is active. + // The node image includes the combined calico binary at /usr/bin/calico. command := []string{CalicoNodeObjectName, "-init"} if c.combinedImage { - command = []string{"/bin/calico-node", "init"} + command = []string{"/usr/bin/calico", "node", "init"} } if !c.cfg.Installation.BPFEnabled() { if c.combinedImage { @@ -1734,10 +1733,10 @@ func (c *nodeComponent) nodeEnvVars() []corev1.EnvVar { // nodeLifecycle creates the node's postStart and preStop hooks. func (c *nodeComponent) nodeLifecycle() *corev1.Lifecycle { - // calico-node uses cobra subcommands when the combined image layout is active. + // The node image includes both calico-node (legacy) and calico (combined binary). preStopCmd := []string{"/bin/calico-node", "-shutdown"} if c.combinedImage { - preStopCmd = []string{"/bin/calico-node", "shutdown"} + preStopCmd = []string{"/usr/bin/calico", "node", "shutdown"} } lc := &corev1.Lifecycle{ PreStop: &corev1.LifecycleHandler{Exec: &corev1.ExecAction{Command: preStopCmd}}, @@ -1751,16 +1750,14 @@ func (c *nodeComponent) nodeLivenessReadinessProbes() (*corev1.Probe, *corev1.Pr livenessPort := intstr.FromInt(c.cfg.FelixHealthPort) var readinessCmd []string - // The readiness probe runs inside the node container (calico/node image), which - // has calico-node. When using the combined image layout, calico-node uses cobra - // subcommands; otherwise it uses the legacy flag syntax. + // The node image includes the combined calico binary at /usr/bin/calico. if c.combinedImage { - readinessCmd = []string{"/bin/calico-node", "health", "--bird-ready", "--felix-ready"} + readinessCmd = []string{"/usr/bin/calico", "node", "health", "--bird-ready", "--felix-ready"} if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { readinessCmd = append(readinessCmd, "--bgp-metrics-ready") } if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { - readinessCmd = []string{"/bin/calico-node", "health", "--felix-ready"} + readinessCmd = []string{"/usr/bin/calico", "node", "health", "--felix-ready"} } } else { readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready"} From 16129a9b874a38fc0027759d51ad25ca53516015 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 21:13:35 -0700 Subject: [PATCH 12/14] Fix compile: use c.nodeImage for init container (not removed 'image' var) --- pkg/render/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/render/node.go b/pkg/render/node.go index fbc5448c63..9e3ca26058 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -1270,7 +1270,7 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { } return corev1.Container{ Name: "ebpf-bootstrap", - Image: image, + Image: c.nodeImage, ImagePullPolicy: ImagePullPolicy(), Env: c.bpffsEnvvars(), Command: command, From 4b5bd15b7eeefe27b11ea1b59c57dbc8603faa32 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 28 Mar 2026 22:35:43 -0700 Subject: [PATCH 13/14] Set goldmane probe period to 10s for faster readiness --- pkg/render/goldmane/component.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 1240ce5f01..3121c56f0d 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -263,6 +263,7 @@ func (c *Component) goldmaneContainer() corev1.Container { Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=readiness"}, }, }, + PeriodSeconds: 10, } livenessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -270,6 +271,7 @@ func (c *Component) goldmaneContainer() corev1.Container { Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=liveness"}, }, }, + PeriodSeconds: 10, } } From 4925fc991c8c90c1729157d5861cc704c1f9705a Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Mon, 30 Mar 2026 14:10:40 -0700 Subject: [PATCH 14/14] Update commands to use 'calico component' namespacing The uber calico binary now namespaces all component entry points under 'calico component'. Update all command arrays in render code and tests to insert "component" after "calico" for component commands. User-facing commands (health, ctl, version) remain at the top level. --- pkg/render/apiserver.go | 4 ++-- pkg/render/apiserver_test.go | 2 +- pkg/render/csi.go | 6 +++--- pkg/render/goldmane/component.go | 4 ++-- pkg/render/goldmane/component_test.go | 2 +- .../kubecontrollers/kube-controllers.go | 4 ++-- .../kubecontrollers/kube-controllers_test.go | 2 +- pkg/render/node.go | 20 +++++++++---------- pkg/render/node_test.go | 4 ++-- pkg/render/typha.go | 6 +++--- pkg/render/webhooks/render.go | 4 ++-- pkg/render/webhooks/render_test.go | 2 +- pkg/render/whisker/component.go | 4 ++-- pkg/render/whisker/component_test.go | 2 +- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 14113fb0c4..b1827b4407 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -157,7 +157,7 @@ type apiServerComponent struct { l7AdmissionControllerImage string l7AdmissionControllerEnvoyImage string dikastesImage string - combinedImage bool + combinedImage bool } func (c *apiServerComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -1179,7 +1179,7 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { var apiServerCommand []string if c.combinedImage { - apiServerCommand = []string{"calico", "apiserver"} + apiServerCommand = []string{"calico", "component", "apiserver"} } apiServer := corev1.Container{ diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index 3fb81a6109..f742fbe6ad 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -1869,7 +1869,7 @@ var _ = Describe("API server rendering tests (Calico)", func() { Expect(d.Spec.Template.Spec.Containers[0].Image).To(Equal( fmt.Sprintf("testregistry.com/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version), )) - Expect(d.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "apiserver"})) + Expect(d.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "component", "apiserver"})) expectedArgs := []string{ "--secure-port=5443", diff --git a/pkg/render/csi.go b/pkg/render/csi.go index 573c2459e1..5152af6c27 100644 --- a/pkg/render/csi.go +++ b/pkg/render/csi.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ type csiComponent struct { csiImage string csiRegistrarImage string - combinedImage bool + combinedImage bool } func CSI(cfg *CSIConfiguration) Component { @@ -141,7 +141,7 @@ func (c *csiComponent) csiContainers() []corev1.Container { mountPropagation := corev1.MountPropagationBidirectional var csiCommand []string if c.combinedImage { - csiCommand = []string{"calico", "csi"} + csiCommand = []string{"calico", "component", "csi"} } csiContainer := corev1.Container{ Name: CSIContainerName, diff --git a/pkg/render/goldmane/component.go b/pkg/render/goldmane/component.go index 3121c56f0d..510d402fdd 100644 --- a/pkg/render/goldmane/component.go +++ b/pkg/render/goldmane/component.go @@ -84,7 +84,7 @@ type Component struct { cfg *Configuration goldmaneImage string - combinedImage bool + combinedImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -256,7 +256,7 @@ func (c *Component) goldmaneContainer() corev1.Container { var containerCommand []string if c.combinedImage { - containerCommand = []string{"calico", "goldmane"} + containerCommand = []string{"calico", "component", "goldmane"} readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index 7c26da4cf3..8e519bceae 100644 --- a/pkg/render/goldmane/component_test.go +++ b/pkg/render/goldmane/component_test.go @@ -142,7 +142,7 @@ var _ = Describe("ComponentRendering", func() { { Name: goldmane.GoldmaneContainerName, Image: "quay.io/calico/calico:master", - Command: []string{"calico", "goldmane"}, + Command: []string{"calico", "component", "goldmane"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 8c246a354f..ea07da9bef 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -216,7 +216,7 @@ type kubeControllersComponent struct { cfg *KubeControllersConfiguration // Internal state generated by the given configuration. - image string + image string combinedImage bool kubeControllerServiceAccountName string @@ -611,7 +611,7 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { } var containerCommand []string if c.combinedImage { - containerCommand = []string{"calico", "kube-controllers", "--health-port=9440"} + containerCommand = []string{"calico", "component", "kube-controllers", "--health-port=9440"} readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index 4dd01b4f53..cea01d16d2 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -195,7 +195,7 @@ var _ = Describe("kube-controllers rendering tests", func() { )) // Verify command and probes use the combined image entrypoint with generic health check. - Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "kube-controllers", "--health-port=9440"})) + Expect(ds.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "component", "kube-controllers", "--health-port=9440"})) Expect(ds.Spec.Template.Spec.Containers[0].ReadinessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=readiness"})) Expect(ds.Spec.Template.Spec.Containers[0].LivenessProbe.Exec.Command).To(Equal([]string{"calico", "health", "--port=9440", "--type=liveness"})) diff --git a/pkg/render/node.go b/pkg/render/node.go index 9e3ca26058..93e6f53c33 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -167,10 +167,10 @@ type nodeComponent struct { cfg *NodeConfiguration // Calculated internal fields based on the given information. - cniImage string - flexvolImage string - nodeImage string - combinedImage bool + cniImage string + flexvolImage string + nodeImage string + combinedImage bool } func (c *nodeComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -1192,7 +1192,7 @@ func (c *nodeComponent) cniContainer() corev1.Container { cniCommand := []string{"/opt/cni/bin/install"} if c.combinedImage { - cniCommand = []string{"calico", "cni", "install"} + cniCommand = []string{"calico", "component", "cni", "install"} } return corev1.Container{ @@ -1215,7 +1215,7 @@ func (c *nodeComponent) flexVolumeContainer() corev1.Container { var flexvolCommand []string if c.combinedImage { - flexvolCommand = []string{"calico", "flexvol"} + flexvolCommand = []string{"calico", "component", "flexvol"} } return corev1.Container{ @@ -1259,7 +1259,7 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { // The node image includes the combined calico binary at /usr/bin/calico. command := []string{CalicoNodeObjectName, "-init"} if c.combinedImage { - command = []string{"/usr/bin/calico", "node", "init"} + command = []string{"/usr/bin/calico", "component", "node", "init"} } if !c.cfg.Installation.BPFEnabled() { if c.combinedImage { @@ -1736,7 +1736,7 @@ func (c *nodeComponent) nodeLifecycle() *corev1.Lifecycle { // The node image includes both calico-node (legacy) and calico (combined binary). preStopCmd := []string{"/bin/calico-node", "-shutdown"} if c.combinedImage { - preStopCmd = []string{"/usr/bin/calico", "node", "shutdown"} + preStopCmd = []string{"/usr/bin/calico", "component", "node", "shutdown"} } lc := &corev1.Lifecycle{ PreStop: &corev1.LifecycleHandler{Exec: &corev1.ExecAction{Command: preStopCmd}}, @@ -1752,12 +1752,12 @@ func (c *nodeComponent) nodeLivenessReadinessProbes() (*corev1.Probe, *corev1.Pr // The node image includes the combined calico binary at /usr/bin/calico. if c.combinedImage { - readinessCmd = []string{"/usr/bin/calico", "node", "health", "--bird-ready", "--felix-ready"} + readinessCmd = []string{"/usr/bin/calico", "component", "node", "health", "--bird-ready", "--felix-ready"} if c.cfg.Installation.Variant == operatorv1.TigeraSecureEnterprise { readinessCmd = append(readinessCmd, "--bgp-metrics-ready") } if !bgpEnabled(c.cfg.Installation) || c.vppDataplaneEnabled() { - readinessCmd = []string{"/usr/bin/calico", "node", "health", "--felix-ready"} + readinessCmd = []string{"/usr/bin/calico", "component", "node", "health", "--felix-ready"} } } else { readinessCmd = []string{"/bin/calico-node", "-bird-ready", "-felix-ready"} diff --git a/pkg/render/node_test.go b/pkg/render/node_test.go index c8172b0ccd..e283dcbebf 100644 --- a/pkg/render/node_test.go +++ b/pkg/render/node_test.go @@ -3336,7 +3336,7 @@ func verifyInitContainers(ds *appsv1.DaemonSet, instance *operatorv1.Installatio rtest.ExpectEnv(cniContainer.Env, "CNI_CONF_NAME", "10-calico.conflist") rtest.ExpectEnv(cniContainer.Env, "SLEEP", "false") cniImage := fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version) - expectedCNICommand := []string{"calico", "cni", "install"} + expectedCNICommand := []string{"calico", "component", "cni", "install"} if instance.FIPSMode != nil && *instance.FIPSMode == operatorv1.FIPSModeEnabled { // Calico CNI image should have -fips suffix when FIPS mode is enabled. cniImage = fmt.Sprintf("quay.io/%s%s:%s-fips", components.CalicoImagePath, components.ComponentCalicoCNI.Image, components.ComponentCalicoCNI.Version) @@ -3439,7 +3439,7 @@ func verifyInitContainers(ds *appsv1.DaemonSet, instance *operatorv1.Installatio Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalicoFlexVolume.Image, components.ComponentCalicoFlexVolume.Version))) } else { Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("quay.io/%s%s:%s", components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version))) - Expect(flexvolContainer.Command).To(Equal([]string{"calico", "flexvol"})) + Expect(flexvolContainer.Command).To(Equal([]string{"calico", "component", "flexvol"})) } Expect(*flexvolContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) diff --git a/pkg/render/typha.go b/pkg/render/typha.go index cb94610160..3c5f65bf9b 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -91,8 +91,8 @@ type typhaComponent struct { cfg *TyphaConfiguration // Generated internal config, built from the given configuration. - typhaImage string - combinedImage bool + typhaImage string + combinedImage bool } func (c *typhaComponent) ResolveImages(is *operatorv1.ImageSet) error { @@ -583,7 +583,7 @@ func (c *typhaComponent) typhaContainer() corev1.Container { SecurityContext: securitycontext.NewNonRootContext(), } if c.combinedImage { - container.Command = []string{"calico", "typha"} + container.Command = []string{"calico", "component", "typha"} } return container } diff --git a/pkg/render/webhooks/render.go b/pkg/render/webhooks/render.go index e5c528b02c..26dccf3019 100644 --- a/pkg/render/webhooks/render.go +++ b/pkg/render/webhooks/render.go @@ -71,7 +71,7 @@ type component struct { // Images. webhooksImage string - combinedImage bool + combinedImage bool } func (c *component) ResolveImages(is *operatorv1.ImageSet) error { @@ -185,7 +185,7 @@ func (c *component) Objects() ([]client.Object, []client.Object) { } if c.combinedImage { - dep.Spec.Template.Spec.Containers[0].Command = []string{"calico", "webhooks"} + dep.Spec.Template.Spec.Containers[0].Command = []string{"calico", "component", "webhooks"} } if c.cfg.Installation.ControlPlaneReplicas != nil && *c.cfg.Installation.ControlPlaneReplicas > 1 { diff --git a/pkg/render/webhooks/render_test.go b/pkg/render/webhooks/render_test.go index 4ee74941bb..62aad409fa 100644 --- a/pkg/render/webhooks/render_test.go +++ b/pkg/render/webhooks/render_test.go @@ -101,7 +101,7 @@ var _ = Describe("Webhooks rendering tests", func() { components.CalicoImagePath, components.ComponentCalico.Image, components.ComponentCalico.Version))) - Expect(dep.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "webhooks"})) + Expect(dep.Spec.Template.Spec.Containers[0].Command).To(Equal([]string{"calico", "component", "webhooks"})) // Verify the ClusterRole includes expected rules. cr := rtest.GetResource(resources, webhooks.WebhooksName, "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) diff --git a/pkg/render/whisker/component.go b/pkg/render/whisker/component.go index f4925e3064..08a054ff97 100644 --- a/pkg/render/whisker/component.go +++ b/pkg/render/whisker/component.go @@ -95,7 +95,7 @@ type Component struct { whiskerImage string whiskerBackendImage string - combinedImage bool + combinedImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -222,7 +222,7 @@ func (c *Component) whiskerBackendContainer() corev1.Container { c.cfg.WhiskerBackendKeyPair.VolumeMount(c.SupportedOSType())), } if c.combinedImage { - container.Command = []string{"calico", "whisker-backend"} + container.Command = []string{"calico", "component", "whisker-backend"} } return container } diff --git a/pkg/render/whisker/component_test.go b/pkg/render/whisker/component_test.go index 25c6328c50..684b8d6da2 100644 --- a/pkg/render/whisker/component_test.go +++ b/pkg/render/whisker/component_test.go @@ -140,7 +140,7 @@ var _ = Describe("ComponentRendering", func() { { Name: whisker.WhiskerBackendContainerName, Image: "quay.io/calico/calico:master", - Command: []string{"calico", "whisker-backend"}, + Command: []string{"calico", "component", "whisker-backend"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"},