Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ const (
KubeControllersDeploymentName = "calico-kube-controllers"
WindowsDaemonSetName = "calico-node-windows"

// ServalName is the name of Serval's Deployment and of the Service in front of
// it. Typha needs the Service name too, so it lives here rather than in the
// serval render package, which imports render.
ServalName = "serval"
// ServalServicePortName is the name of the HTTPS port on that Service.
ServalServicePortName = "https"

// Monitor + Prometheus related const
TigeraPrometheusNamespace = "tigera-prometheus"

Expand Down
5 changes: 3 additions & 2 deletions pkg/controller/csr/csr_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ func (r *reconcileCSR) Reconcile(ctx context.Context, request reconcile.Request)
}
needsCSRRole = monitorCR.Spec.ExternalPrometheus != nil

// Check whether the non-cluster host feature is enabled.
// Non-cluster hosts generate CSRs to establish mTLS connections with the cluster.
// Check whether the non-cluster host feature is enabled. Non-cluster hosts
// generate CSRs to establish mTLS connections with the cluster, in both the
// serval gateway and legacy modes.
if !needsCSRRole {
nonclusterhost, err := utils.GetNonClusterHost(ctx, r.client)
if err != nil {
Expand Down
95 changes: 40 additions & 55 deletions pkg/controller/installation/core_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

v3 "github.com/tigera/api/pkg/apis/projectcalico/v3"
calicoclient "github.com/tigera/api/pkg/client/clientset_generated/clientset"
operatorv1 "github.com/tigera/operator/api/v1"
"github.com/tigera/operator/pkg/active"
"github.com/tigera/operator/pkg/common"
Expand All @@ -72,6 +71,7 @@ import (
"github.com/tigera/operator/pkg/controller/migration/datastoremigration"
"github.com/tigera/operator/pkg/controller/options"
"github.com/tigera/operator/pkg/controller/status"
"github.com/tigera/operator/pkg/controller/typhaautoscaler"
"github.com/tigera/operator/pkg/controller/utils"
"github.com/tigera/operator/pkg/controller/utils/imageset"
"github.com/tigera/operator/pkg/ctrlruntime"
Expand Down Expand Up @@ -340,8 +340,7 @@ func newReconciler(mgr manager.Manager, opts options.ControllerOptions) (*Reconc
go nodeIndexInformer.Run(opts.ShutdownContext.Done())

// Create a Typha autoscaler.
typhaListWatch := cache.NewListWatchFromClient(opts.K8sClientset.AppsV1().RESTClient(), "deployments", "calico-system", fields.OneTermEqualSelector("metadata.name", "calico-typha"))
typhaScaler := newTyphaAutoscaler(opts.K8sClientset, nodeIndexInformer, typhaListWatch, statusManager)
typhaScaler := typhaautoscaler.New(opts.K8sClientset, nodeIndexInformer, statusManager, []string{common.TyphaDeploymentName})

r := &ReconcileInstallation{
config: mgr.GetConfig(),
Expand All @@ -365,7 +364,7 @@ func newReconciler(mgr manager.Manager, opts options.ControllerOptions) (*Reconc
apiDiscovery: opts.APIDiscovery,
}
r.status.Run(opts.ShutdownContext)
r.typhaAutoscaler.start(opts.ShutdownContext)
r.typhaAutoscaler.Start(opts.ShutdownContext)

return r, nil
}
Expand Down Expand Up @@ -402,26 +401,25 @@ var _ reconcile.Reconciler = &ReconcileInstallation{}
type ReconcileInstallation struct {
// This client, initialized using mgr.Client() above, is a split client
// that reads objects from the cache and writes to the apiserver
config *rest.Config
client client.Client
clientset *kubernetes.Clientset
scheme *runtime.Scheme
shutdownContext context.Context
watches map[runtime.Object]struct{}
autoDetectedProvider operatorv1.Provider
status status.StatusManager
typhaAutoscaler *typhaAutoscaler
typhaAutoscalerNonClusterHost *typhaAutoscaler
namespaceMigration migration.NamespaceMigration
enterpriseCRDsExist bool
migrationChecked bool
clusterDomain string
manageCRDs bool
tierWatchReady *utils.ReadyFlag
migrationWatchReady *utils.ReadyFlag
v3CRDs bool
kubernetesVersion *common.VersionInfo
apiDiscovery *discovery.APIDiscovery
config *rest.Config
client client.Client
clientset *kubernetes.Clientset
scheme *runtime.Scheme
shutdownContext context.Context
watches map[runtime.Object]struct{}
autoDetectedProvider operatorv1.Provider
status status.StatusManager
typhaAutoscaler *typhaautoscaler.Autoscaler
namespaceMigration migration.NamespaceMigration
enterpriseCRDsExist bool
migrationChecked bool
clusterDomain string
manageCRDs bool
tierWatchReady *utils.ReadyFlag
migrationWatchReady *utils.ReadyFlag
v3CRDs bool
kubernetesVersion *common.VersionInfo
apiDiscovery *discovery.APIDiscovery

// newComponentHandler returns a new component handler. Useful stub for unit testing.
newComponentHandler func(log logr.Logger, client client.Client, scheme *runtime.Scheme, cr metav1.Object) utils.ComponentHandler
Expand Down Expand Up @@ -1012,19 +1010,12 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
if !installationMarkedForDeletion {
// If the autoscalar is degraded then trigger a run and recheck the degraded status. If it is still degraded after the
// the run the reset the degraded status and requeue the request.
if r.typhaAutoscaler.isDegraded() {
if err := r.typhaAutoscaler.triggerRun(); err != nil {
if r.typhaAutoscaler.IsDegraded() {
if err := r.typhaAutoscaler.TriggerRun(); err != nil {
r.status.SetDegraded(operatorv1.ResourceScalingError, "Failed to scale typha", err, reqLogger)
return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil
}
}

if r.typhaAutoscalerNonClusterHost != nil && r.typhaAutoscalerNonClusterHost.isDegraded() {
if err := r.typhaAutoscalerNonClusterHost.triggerRun(); err != nil {
r.status.SetDegraded(operatorv1.ResourceScalingError, "Failed to scale typha for noncluster hosts", err, reqLogger)
return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil
}
}
}

// The operator supports running in a "Calico only" mode so that it doesn't need to run enterprise-specific controllers.
Expand Down Expand Up @@ -1450,14 +1441,18 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
TrustedBundle: typhaNodeTLS.TrustedBundle,
}))

// Check if non-cluster host feature is enabled.
var nonclusterhost *operatorv1.NonClusterHost
// Check if the non-cluster host feature is enabled. The legacy directly-exposed Typha
// deployment (calico-typha-noncluster-host) renders only when a NonClusterHost sets
// spec.typhaEndpoint. An unset endpoint selects the serval gateway (rendered by the
// nonclusterhost controller), whose in-process Typha replaces this deployment.
var legacyNonClusterHost *operatorv1.NonClusterHost
if instance.Spec.Variant.IsEnterprise() {
nonclusterhost, err = utils.GetNonClusterHost(ctx, r.client)
nonclusterhost, err := utils.GetNonClusterHost(ctx, r.client)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Failed to query NonClusterHost resource", err, reqLogger)
return reconcile.Result{}, err
} else if nonclusterhost != nil {
} else if nonclusterhost != nil && nonclusterhost.Spec.TyphaEndpoint != "" {
legacyNonClusterHost = nonclusterhost
// This is the default common name in CSR from non-cluster hosts.
typhaNodeTLS.NodeNonClusterHostCommonName = render.FelixCommonName + render.TyphaNonClusterHostSuffix
// Attempt to retrieve the BYO node certificates for non-cluster hosts if they are present.
Expand All @@ -1473,22 +1468,6 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
typhaNodeTLS.NodeNonClusterHostCommonName = cn
typhaNodeTLS.NodeNonClusterHostURISAN = urisan
}

if r.typhaAutoscalerNonClusterHost == nil {
calicoClient, err := calicoclient.NewForConfig(r.config)
if err != nil {
r.status.SetDegraded(operatorv1.InvalidConfigurationError, "Failed to initialize Calico client", err, reqLogger)
return reconcile.Result{}, err
}

hepListWatch := cache.NewListWatchFromClient(calicoClient.ProjectcalicoV3().RESTClient(), "hostendpoints", corev1.NamespaceAll, fields.Everything())
hepIndexInformer := cache.NewSharedIndexInformer(hepListWatch, &v3.HostEndpoint{}, 0, cache.Indexers{})
go hepIndexInformer.Run(r.shutdownContext.Done())

typhaNonClusterHostWatch := cache.NewListWatchFromClient(r.clientset.AppsV1().RESTClient(), "deployments", "calico-system", fields.OneTermEqualSelector("metadata.name", "calico-typha"+render.TyphaNonClusterHostSuffix))
r.typhaAutoscalerNonClusterHost = newTyphaAutoscaler(r.clientset, hepIndexInformer, typhaNonClusterHostWatch, r.status, typhaAutoscalerOptionNonclusterHost(true))
r.typhaAutoscalerNonClusterHost.start(r.shutdownContext)
}
}
}

Expand All @@ -1500,7 +1479,7 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
TLS: typhaNodeTLS,
MigrateNamespaces: needsNamespaceMigration,
ClusterDomain: r.clusterDomain,
NonClusterHost: nonclusterhost,
NonClusterHost: legacyNonClusterHost,
FelixHealthPort: *felixConfiguration.Spec.HealthPort,
}
components = append(components, render.Typha(&typhaCfg))
Expand Down Expand Up @@ -1738,8 +1717,14 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile
// deployment becomes unhealthy and reconciliation of non-NetworkPolicy resources in the core controller
// would resolve it, we render the network policies of components last to prevent a chicken-and-egg scenario.
if includeV3NetworkPolicy {
if nonclusterhost != nil {
if legacyNonClusterHost != nil {
components = append(components, render.NewTyphaNonClusterHostPolicy(&typhaCfg))
} else {
// Garbage-collect the policy when the legacy Typha is not deployed. It
// belongs here rather than alongside that deployment and service,
// because a v3 resource is only reconcilable while the API server is
// healthy.
components = append(components, render.NewDeletionPassthrough(render.TyphaNonClusterHostPolicyForDeletion()))
}
components = append(components,
kubecontrollers.NewCalicoKubeControllersPolicy(&kubeControllersCfg, calicoSystemDefaultDenyForCalicoSystem()),
Expand Down
81 changes: 66 additions & 15 deletions pkg/controller/installation/core_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
schedv1 "k8s.io/api/scheduling/v1"
storagev1 "k8s.io/api/storage/v1"
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -57,6 +58,7 @@ import (
"github.com/tigera/operator/pkg/components"
"github.com/tigera/operator/pkg/controller/certificatemanager"
"github.com/tigera/operator/pkg/controller/status"
"github.com/tigera/operator/pkg/controller/typhaautoscaler"
"github.com/tigera/operator/pkg/controller/utils"
ctrlrfake "github.com/tigera/operator/pkg/ctrlruntime/client/fake"
"github.com/tigera/operator/pkg/dns"
Expand Down Expand Up @@ -195,7 +197,7 @@ var _ = Describe("Testing core-controller installation", func() {
scheme: scheme,
autoDetectedProvider: operator.ProviderNone,
status: mockStatus,
typhaAutoscaler: newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus),
typhaAutoscaler: typhaautoscaler.New(cs, nodeIndexInformer, mockStatus, []string{common.TyphaDeploymentName}),
namespaceMigration: &fakeNamespaceMigration{},
enterpriseCRDsExist: true,
migrationChecked: true,
Expand All @@ -204,7 +206,7 @@ var _ = Describe("Testing core-controller installation", func() {
newComponentHandler: utils.NewComponentHandler,
}

r.typhaAutoscaler.start(ctx)
r.typhaAutoscaler.Start(ctx)
certificateManager, err := certificatemanager.Create(c, nil, "", common.OperatorNamespace(), certificatemanager.AllowCACreation())
Expect(err).NotTo(HaveOccurred())

Expand Down Expand Up @@ -254,22 +256,26 @@ var _ = Describe("Testing core-controller installation", func() {
cancel()
})

// These cover the legacy direct mode, which renders
// calico-typha-noncluster-host. spec.typhaEndpoint is what selects it;
// leaving the field unset selects the tunnel, where hosts reach Typha
// through serval and this deployment is not rendered.
Context("non-cluster host tests", func() {
nonClusterHostObjectMeta := metav1.ObjectMeta{Name: "tigera-secure"}

BeforeEach(func() {
By("Creating a NonClusterHost CR")
By("Creating a NonClusterHost CR in the legacy direct mode")
Expect(c.Create(ctx, &operator.NonClusterHost{
TypeMeta: metav1.TypeMeta{Kind: "NonClusterHost", APIVersion: "operator.tigera.io/v1"},
ObjectMeta: nonClusterHostObjectMeta,
Spec: operator.NonClusterHostSpec{
Endpoint: "https://1.2.3.4:443",
TyphaEndpoint: "5.6.7.8:5473",
},
})).NotTo(HaveOccurred())

r.typhaAutoscalerNonClusterHost = newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus)
r.typhaAutoscalerNonClusterHost.start(ctx)
})

AfterEach(func() {
r.typhaAutoscalerNonClusterHost = nil
Expect(c.Delete(ctx, &operator.NonClusterHost{ObjectMeta: nonClusterHostObjectMeta})).NotTo(HaveOccurred())
})

Expand Down Expand Up @@ -345,6 +351,51 @@ var _ = Describe("Testing core-controller installation", func() {
})
})

// The counterpart to the legacy direct mode above: with spec.typhaEndpoint
// unset the hosts reach Typha through serval, so the core controller must
// not deploy a Typha of its own for them.
Context("non-cluster host tests, tunnel mode", func() {
nonClusterHostObjectMeta := metav1.ObjectMeta{Name: "tigera-secure"}

BeforeEach(func() {
By("Creating a NonClusterHost CR with no typhaEndpoint")
Expect(c.Create(ctx, &operator.NonClusterHost{
TypeMeta: metav1.TypeMeta{Kind: "NonClusterHost", APIVersion: "operator.tigera.io/v1"},
ObjectMeta: nonClusterHostObjectMeta,
Spec: operator.NonClusterHostSpec{Endpoint: "https://1.2.3.4:443"},
})).NotTo(HaveOccurred())
})

AfterEach(func() {
Expect(c.Delete(ctx, &operator.NonClusterHost{ObjectMeta: nonClusterHostObjectMeta})).NotTo(HaveOccurred())
})

It("should not create a separate Typha deployment for non-cluster hosts", func() {
_, err := r.Reconcile(ctx, reconcile.Request{})
Expect(err).NotTo(HaveOccurred())

err = c.Get(ctx, types.NamespacedName{Name: "calico-typha-noncluster-host", Namespace: common.CalicoNamespace}, &appsv1.Deployment{})
Expect(kerror.IsNotFound(err)).To(BeTrue(), "calico-typha-noncluster-host should not be deployed in tunnel mode")
})

It("should still deploy the in-cluster Typha", func() {
_, err := r.Reconcile(ctx, reconcile.Request{})
Expect(err).NotTo(HaveOccurred())

// The hosts are served through serval, which is an ordinary client of
// this deployment, so it must be unaffected by the mode.
typha := appsv1.Deployment{}
Expect(c.Get(ctx, types.NamespacedName{Name: common.TyphaDeploymentName, Namespace: common.CalicoNamespace}, &typha)).NotTo(HaveOccurred())

// Serval is a client per replica rather than per node, so Typha counts
// its endpoints when it sizes its connection limit.
Expect(typha.Spec.Template.Spec.Containers[0].Env).To(ContainElements(
corev1.EnvVar{Name: "TYPHA_K8SEXTRACLIENTSERVICENAME", Value: "serval"},
corev1.EnvVar{Name: "TYPHA_K8SEXTRACLIENTPORTNAME", Value: "https"},
))
})
})

Context("with Goldmane installed", func() {
BeforeEach(func() {
// Create a Goldmane CR.
Expand Down Expand Up @@ -824,7 +875,7 @@ var _ = Describe("Testing core-controller installation", func() {
scheme: scheme,
autoDetectedProvider: operator.ProviderNone,
status: mockStatus,
typhaAutoscaler: newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus),
typhaAutoscaler: typhaautoscaler.New(cs, nodeIndexInformer, mockStatus, []string{common.TyphaDeploymentName}),
namespaceMigration: &fakeNamespaceMigration{},
enterpriseCRDsExist: true,
migrationChecked: true,
Expand All @@ -833,7 +884,7 @@ var _ = Describe("Testing core-controller installation", func() {
migrationWatchReady: &utils.ReadyFlag{},
newComponentHandler: utils.NewComponentHandler,
}
r.typhaAutoscaler.start(ctx)
r.typhaAutoscaler.Start(ctx)

cr = &operator.Installation{
ObjectMeta: metav1.ObjectMeta{Name: "default"},
Expand Down Expand Up @@ -1046,7 +1097,7 @@ var _ = Describe("Testing core-controller installation", func() {
scheme: scheme,
autoDetectedProvider: operator.ProviderNone,
status: mockStatus,
typhaAutoscaler: newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus),
typhaAutoscaler: typhaautoscaler.New(cs, nodeIndexInformer, mockStatus, []string{common.TyphaDeploymentName}),
namespaceMigration: &fakeNamespaceMigration{},
enterpriseCRDsExist: true,
migrationChecked: true,
Expand All @@ -1055,7 +1106,7 @@ var _ = Describe("Testing core-controller installation", func() {
newComponentHandler: utils.NewComponentHandler,
}

r.typhaAutoscaler.start(ctx)
r.typhaAutoscaler.Start(ctx)
ca, err := tls.MakeCA("test")
Expect(err).NotTo(HaveOccurred())
cert, _, _ := ca.Config.GetPEMBytes() // create a valid pem block
Expand Down Expand Up @@ -2334,7 +2385,7 @@ var _ = Describe("Testing core-controller installation", func() {
scheme: scheme,
autoDetectedProvider: operator.ProviderNone,
status: mockStatus,
typhaAutoscaler: newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus),
typhaAutoscaler: typhaautoscaler.New(cs, nodeIndexInformer, mockStatus, []string{common.TyphaDeploymentName}),
namespaceMigration: &fakeNamespaceMigration{},
enterpriseCRDsExist: true,
migrationChecked: true,
Expand All @@ -2343,7 +2394,7 @@ var _ = Describe("Testing core-controller installation", func() {
migrationWatchReady: &utils.ReadyFlag{},
newComponentHandler: utils.NewComponentHandler,
}
r.typhaAutoscaler.start(ctx)
r.typhaAutoscaler.Start(ctx)

cr = &operator.Installation{
ObjectMeta: metav1.ObjectMeta{Name: "default"},
Expand Down Expand Up @@ -2471,7 +2522,7 @@ var _ = Describe("Testing core-controller installation", func() {
scheme: scheme,
autoDetectedProvider: operator.ProviderNone,
status: mockStatus,
typhaAutoscaler: newTyphaAutoscaler(cs, nodeIndexInformer, test.NewTyphaListWatch(cs), mockStatus),
typhaAutoscaler: typhaautoscaler.New(cs, nodeIndexInformer, mockStatus, []string{common.TyphaDeploymentName}),
namespaceMigration: &fakeNamespaceMigration{},
enterpriseCRDsExist: true,
migrationChecked: true,
Expand All @@ -2482,7 +2533,7 @@ var _ = Describe("Testing core-controller installation", func() {
},
}

r.typhaAutoscaler.start(ctx)
r.typhaAutoscaler.Start(ctx)
certificateManager, err := certificatemanager.Create(c, nil, "", common.OperatorNamespace(), certificatemanager.AllowCACreation())
Expect(err).NotTo(HaveOccurred())

Expand Down
Loading
Loading