Set best practice defaults for network router deployment (#214)

This change sets some Kubernetes best practices as defaults. Such as
topology spread and pod disruption budget.

It also exposes log level and image settings in the root struct to make
it easier to override commonly configured settings.

Fixes #77 
Fixes #162

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2026-04-28 15:18:15 +02:00
committed by GitHub
parent a94071224b
commit 37d48b5ca8
5 changed files with 115 additions and 10 deletions
+10
View File
@@ -11,6 +11,14 @@ type NetworkRouterSpec struct {
// +required
DNSZoneRef DNSZoneReference `json:"dnsZoneRef"`
// Netbird client image.
// +optional
Image string `json:"image,omitempty"`
// Log level for Netbird client.
// +optional
LogLevel string `json:"logLevel,omitempty"`
// WorkloadOverride contains configuration that will override the default workload.
// +optional
WorkloadOverride *WorkloadOverride `json:"workloadOverride,omitempty"`
@@ -34,6 +42,8 @@ type WorkloadOverride struct {
// Replicas sets the amount of client replicas.
// +optional
// +kubebuilder:default=3
// +kubebuilder:validation:Minimum=1
Replicas *int32 `json:"replicas"`
// PodTemplate overrides the pod template.
+3 -1
View File
@@ -255,6 +255,8 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `dnsZoneRef` _[DNSZoneReference](#dnszonereference)_ | DNSZoneRef is a reference to the DNS zone used to create records for resources. | | Required: \{\} <br /> |
| `image` _string_ | Netbird client image. | | Optional: \{\} <br /> |
| `logLevel` _string_ | Log level for Netbird client. | | Optional: \{\} <br /> |
| `workloadOverride` _[WorkloadOverride](#workloadoverride)_ | WorkloadOverride contains configuration that will override the default workload. | | Optional: \{\} <br /> |
@@ -407,7 +409,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `labels` _object (keys:string, values:string)_ | Labels that will be added. | | Optional: \{\} <br /> |
| `annotations` _object (keys:string, values:string)_ | Annotations that will be added. | | Optional: \{\} <br /> |
| `replicas` _integer_ | Replicas sets the amount of client replicas. | | Optional: \{\} <br /> |
| `replicas` _integer_ | Replicas sets the amount of client replicas. | 3 | Minimum: 1 <br />Optional: \{\} <br /> |
| `podTemplate` _[PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#podtemplatespec-v1-core)_ | PodTemplate overrides the pod template. | | Schemaless: \{\} <br />Optional: \{\} <br /> |
@@ -57,6 +57,12 @@ spec:
required:
- name
type: object
image:
description: Netbird client image.
type: string
logLevel:
description: Log level for Netbird client.
type: string
workloadOverride:
description: WorkloadOverride contains configuration that will override
the default workload.
@@ -75,8 +81,10 @@ spec:
description: PodTemplate overrides the pod template.
x-kubernetes-preserve-unknown-fields: true
replicas:
default: 3
description: Replicas sets the amount of client replicas.
format: int32
minimum: 1
type: integer
type: object
required:
@@ -16,11 +16,16 @@ import (
"github.com/netbirdio/netbird/shared/management/http/api"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/strategicpatch"
appsv1ac "k8s.io/client-go/applyconfigurations/apps/v1"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"
policyv1ac "k8s.io/client-go/applyconfigurations/policy/v1"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -190,12 +195,31 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
"app.kubernetes.io/instance": req.Name,
}
logLevel := "info"
if netRouter.Spec.LogLevel != "" {
logLevel = netRouter.Spec.LogLevel
}
clientImage := r.ClientImage
if netRouter.Spec.Image != "" {
clientImage = netRouter.Spec.Image
}
podTemplateSpecAC := corev1ac.PodTemplateSpec().
WithLabels(selectorLabels).
WithSpec(corev1ac.PodSpec().
WithTopologySpreadConstraints(
corev1ac.TopologySpreadConstraint().
WithMaxSkew(1).
WithTopologyKey(corev1.LabelHostname).
WithWhenUnsatisfiable(corev1.ScheduleAnyway).
WithLabelSelector(metav1ac.LabelSelector().
WithMatchLabels(selectorLabels),
),
).
WithContainers(corev1ac.Container().
WithName("netbird").
WithImage(r.ClientImage).
WithImage(clientImage).
WithEnv(
corev1ac.EnvVar().
WithName("NB_SETUP_KEY").
@@ -210,7 +234,13 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
WithValue(r.ManagementURL),
corev1ac.EnvVar().
WithName("NB_LOG_LEVEL").
WithValue("info"),
WithValue(logLevel),
corev1ac.EnvVar().
WithName("NB_LOG_FILE").
WithValue("console"),
corev1ac.EnvVar().
WithName("NB_ENTRYPOINT_SERVICE_TIMEOUT").
WithValue("0"),
).
WithStartupProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "startup"))).
WithReadinessProbe(corev1ac.Probe().WithExec(corev1ac.ExecAction().WithCommand("netbird", "status", "--check", "ready"))).
@@ -221,19 +251,25 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
WithAdd("SYS_ADMIN"),
).
WithPrivileged(true),
).
WithResources(corev1ac.ResourceRequirements().
WithRequests(corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("128Mi"),
}),
),
),
)
depLabels := map[string]string{}
depAnnotations := map[string]string{}
workloadLabels := map[string]string{}
workloadAnnotations := map[string]string{}
replicas := int32(3)
if netRouter.Spec.WorkloadOverride != nil {
if netRouter.Spec.WorkloadOverride.Labels != nil {
depLabels = netRouter.Spec.WorkloadOverride.Labels
workloadLabels = netRouter.Spec.WorkloadOverride.Labels
}
if netRouter.Spec.WorkloadOverride.Annotations != nil {
depAnnotations = netRouter.Spec.WorkloadOverride.Annotations
workloadAnnotations = netRouter.Spec.WorkloadOverride.Annotations
}
if netRouter.Spec.WorkloadOverride.Replicas != nil {
replicas = *netRouter.Spec.WorkloadOverride.Replicas
@@ -257,17 +293,46 @@ func (r *NetworkRouterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
}
}
maps.Copy(depLabels, selectorLabels)
maps.Copy(workloadLabels, selectorLabels)
depAC := appsv1ac.Deployment(fmt.Sprintf("networkrouter-%s", req.Name), req.Namespace).
WithOwnerReferences(ownerRef).
WithLabels(depLabels).
WithAnnotations(depAnnotations).
WithLabels(workloadLabels).
WithAnnotations(workloadAnnotations).
WithSpec(appsv1ac.DeploymentSpec().WithReplicas(replicas).WithSelector(metav1ac.LabelSelector().WithMatchLabels(selectorLabels)).WithTemplate(podTemplateSpecAC))
err = r.Client.Apply(ctx, depAC)
if err != nil {
return ctrl.Result{}, err
}
if replicas > 1 {
pdbAC := policyv1ac.PodDisruptionBudget(fmt.Sprintf("networkrouter-%s", req.Name), req.Namespace).
WithOwnerReferences(ownerRef).
WithLabels(workloadLabels).
WithAnnotations(workloadAnnotations).
WithSpec(policyv1ac.PodDisruptionBudgetSpec().
WithMaxUnavailable(intstr.FromInt(1)).
WithSelector(metav1ac.LabelSelector().
WithMatchLabels(selectorLabels),
),
)
err = r.Client.Apply(ctx, pdbAC)
if err != nil {
return ctrl.Result{}, err
}
} else {
pdb := policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("networkrouter-%s", req.Name),
Namespace: req.Namespace,
},
}
err = r.Client.Delete(ctx, &pdb)
if err != nil && !kerrors.IsNotFound(err) {
return ctrl.Result{}, err
}
}
dep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: *depAC.Name,
@@ -9,6 +9,10 @@ package v1alpha1
type NetworkRouterSpecApplyConfiguration struct {
// DNSZoneRef is a reference to the DNS zone used to create records for resources.
DNSZoneRef *DNSZoneReferenceApplyConfiguration `json:"dnsZoneRef,omitempty"`
// Netbird client image.
Image *string `json:"image,omitempty"`
// Log level for Netbird client.
LogLevel *string `json:"logLevel,omitempty"`
// WorkloadOverride contains configuration that will override the default workload.
WorkloadOverride *WorkloadOverrideApplyConfiguration `json:"workloadOverride,omitempty"`
}
@@ -27,6 +31,22 @@ func (b *NetworkRouterSpecApplyConfiguration) WithDNSZoneRef(value *DNSZoneRefer
return b
}
// WithImage sets the Image field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Image field is set to the value of the last call.
func (b *NetworkRouterSpecApplyConfiguration) WithImage(value string) *NetworkRouterSpecApplyConfiguration {
b.Image = &value
return b
}
// WithLogLevel sets the LogLevel field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the LogLevel field is set to the value of the last call.
func (b *NetworkRouterSpecApplyConfiguration) WithLogLevel(value string) *NetworkRouterSpecApplyConfiguration {
b.LogLevel = &value
return b
}
// WithWorkloadOverride sets the WorkloadOverride field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the WorkloadOverride field is set to the value of the last call.