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..b1827b4407 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -157,6 +157,7 @@ type apiServerComponent struct { l7AdmissionControllerImage string l7AdmissionControllerEnvoyImage string dikastesImage string + combinedImage 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.combinedImage = true } } @@ -1175,9 +1177,15 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { apiServerTargetPort := getContainerPort(c.cfg, APIServerContainerName).ContainerPort + var apiServerCommand []string + if c.combinedImage { + apiServerCommand = []string{"calico", "component", "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..f742fbe6ad 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", "component", "apiserver"})) expectedArgs := []string{ "--secure-port=5443", diff --git a/pkg/render/csi.go b/pkg/render/csi.go index 1d01086437..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,6 +53,7 @@ type csiComponent struct { csiImage string csiRegistrarImage string + combinedImage 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.combinedImage { + csiCommand = []string{"calico", "component", "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.combinedImage = 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/goldmane/component.go b/pkg/render/goldmane/component.go index 72ee39983d..510d402fdd 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 { @@ -83,6 +84,7 @@ type Component struct { cfg *Configuration goldmaneImage string + combinedImage bool } func (c *Component) ResolveImages(is *operatorv1.ImageSet) error { @@ -92,7 +94,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.combinedImage = true + } if err != nil { return err } @@ -236,25 +243,48 @@ func (c *Component) goldmaneContainer() corev1.Container { MountPath: GoldmaneConfigFilePath, }) + 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.combinedImage { + containerCommand = []string{"calico", "component", "goldmane"} + readinessProbe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=readiness"}, + }, + }, + PeriodSeconds: 10, + } + livenessProbe = &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", GoldmaneHealthPort), "--type=liveness"}, + }, + }, + PeriodSeconds: 10, + } + } + 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, + ReadinessProbe: readinessProbe, + LivenessProbe: livenessProbe, + VolumeMounts: volumeMounts, } } diff --git a/pkg/render/goldmane/component_test.go b/pkg/render/goldmane/component_test.go index fecd02d01b..8e519bceae 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" @@ -92,6 +94,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 +141,8 @@ var _ = Describe("ComponentRendering", func() { Containers: []corev1.Container{ { Name: goldmane.GoldmaneContainerName, - Image: "", + Image: "quay.io/calico/calico:master", + Command: []string{"calico", "component", "goldmane"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"}, @@ -153,15 +157,13 @@ var _ = Describe("ComponentRendering", func() { SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"/health", "-ready"}, + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", goldmane.GoldmaneHealthPort), "--type=readiness"}, }}, }, LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"/health", "-live"}, - }, - }, + ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"calico", "health", fmt.Sprintf("--port=%d", goldmane.GoldmaneHealthPort), "--type=liveness"}, + }}, }, VolumeMounts: append( []corev1.VolumeMount{defaultTLSKeyPair.VolumeMount(rmeta.OSTypeLinux)}, diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 2bd28b4edf..ea07da9bef 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 + combinedImage 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.combinedImage = true } } return err @@ -589,36 +591,56 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { sc.RunAsUser = ptr.Int64ToPtr(999) sc.RunAsGroup = ptr.Int64ToPtr(0) - container := corev1.Container{ - Name: c.kubeControllerName, - Image: c.image, - ImagePullPolicy: render.ImagePullPolicy(), - Env: env, - Resources: c.kubeControllersResources(), - ReadinessProbe: &corev1.Probe{ + 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.combinedImage { + containerCommand = []string{"calico", "component", "kube-controllers", "--health-port=9440"} + readinessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{ - "/usr/bin/check-status", - "-r", - }, + Command: []string{"calico", "health", "--port=9440", "--type=readiness"}, }, }, TimeoutSeconds: 10, - }, - LivenessProbe: &corev1.Probe{ + } + livenessProbe = &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ - Command: []string{ - "/usr/bin/check-status", - "-l", - }, + Command: []string{"calico", "health", "--port=9440", "--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 72aba80380..cea01d16d2 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 combined image entrypoint with generic health check. + 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"})) + // Verify env expectedEnv := []corev1.EnvVar{ {Name: "DATASTORE_TYPE", Value: "kubernetes"}, diff --git a/pkg/render/node.go b/pkg/render/node.go index 366d223b06..93e6f53c33 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -167,9 +167,10 @@ type nodeComponent struct { cfg *NodeConfiguration // Calculated internal fields based on the given information. - cniImage string - flexvolImage string - nodeImage string + cniImage string + flexvolImage string + nodeImage string + combinedImage 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.combinedImage = 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.combinedImage { + cniCommand = []string{"calico", "component", "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.combinedImage { + flexvolCommand = []string{"calico", "component", "flexvol"} + } + return corev1.Container{ Name: "flexvol-driver", Image: c.flexvolImage, + Command: flexvolCommand, ImagePullPolicy: ImagePullPolicy(), SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: flexVolumeMounts, @@ -1242,13 +1256,17 @@ func (c *nodeComponent) bpfBootstrapInitContainer() corev1.Container { }, } + // The node image includes the combined calico binary at /usr/bin/calico. command := []string{CalicoNodeObjectName, "-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.combinedImage { + command = []string{"/usr/bin/calico", "component", "node", "init"} + } 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", @@ -1715,7 +1733,11 @@ func (c *nodeComponent) nodeEnvVars() []corev1.EnvVar { // nodeLifecycle creates the node's postStart and preStop hooks. 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", "component", "node", "shutdown"} + } lc := &corev1.Lifecycle{ PreStop: &corev1.LifecycleHandler{Exec: &corev1.ExecAction{Command: preStopCmd}}, } @@ -1726,16 +1748,25 @@ 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"} + var readinessCmd []string - // 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"} - } - - // 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"} + // The node image includes the combined calico binary at /usr/bin/calico. + if c.combinedImage { + 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", "component", "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{ diff --git a/pkg/render/node_test.go b/pkg/render/node_test.go index 0413d94490..e283dcbebf 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", "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) + 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", "component", "flexvol"})) } Expect(*flexvolContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) diff --git a/pkg/render/typha.go b/pkg/render/typha.go index 00c736e75c..3c5f65bf9b 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -91,7 +91,8 @@ type typhaComponent struct { cfg *TyphaConfiguration // Generated internal config, built from the given configuration. - typhaImage string + typhaImage string + combinedImage 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.combinedImage = 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.combinedImage { + container.Command = []string{"calico", "component", "typha"} + } + return container } func (c *typhaComponent) typhaContainerNonClusterHost() corev1.Container { diff --git a/pkg/render/webhooks/render.go b/pkg/render/webhooks/render.go index 4ce74f7269..26dccf3019 100644 --- a/pkg/render/webhooks/render.go +++ b/pkg/render/webhooks/render.go @@ -71,6 +71,7 @@ type component struct { // Images. webhooksImage string + combinedImage 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.combinedImage = true + } } return err } @@ -178,6 +184,10 @@ func (c *component) Objects() ([]client.Object, []client.Object) { }, } + if c.combinedImage { + dep.Spec.Template.Spec.Containers[0].Command = []string{"calico", "component", "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..62aad409fa 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 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( + 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", "component", "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..08a054ff97 100644 --- a/pkg/render/whisker/component.go +++ b/pkg/render/whisker/component.go @@ -95,6 +95,7 @@ type Component struct { whiskerImage string whiskerBackendImage string + combinedImage 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.combinedImage = 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.combinedImage { + container.Command = []string{"calico", "component", "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..684b8d6da2 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", "component", "whisker-backend"}, ImagePullPolicy: render.ImagePullPolicy(), Env: []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "INFO"},