diff --git a/api/v1alpha1/networkrouter_types.go b/api/v1alpha1/networkrouter_types.go
index 10640bc..151d43a 100644
--- a/api/v1alpha1/networkrouter_types.go
+++ b/api/v1alpha1/networkrouter_types.go
@@ -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.
diff --git a/docs/api-reference.md b/docs/api-reference.md
index c88e342..b7a361c 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -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: \{\}
|
+| `image` _string_ | Netbird client image. | | Optional: \{\}
|
+| `logLevel` _string_ | Log level for Netbird client. | | Optional: \{\}
|
| `workloadOverride` _[WorkloadOverride](#workloadoverride)_ | WorkloadOverride contains configuration that will override the default workload. | | Optional: \{\}
|
@@ -407,7 +409,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `labels` _object (keys:string, values:string)_ | Labels that will be added. | | Optional: \{\}
|
| `annotations` _object (keys:string, values:string)_ | Annotations that will be added. | | Optional: \{\}
|
-| `replicas` _integer_ | Replicas sets the amount of client replicas. | | Optional: \{\}
|
+| `replicas` _integer_ | Replicas sets the amount of client replicas. | 3 | Minimum: 1
Optional: \{\}
|
| `podTemplate` _[PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#podtemplatespec-v1-core)_ | PodTemplate overrides the pod template. | | Schemaless: \{\}
Optional: \{\}
|
diff --git a/helm/kubernetes-operator/crds/netbird.io_networkrouters.yaml b/helm/kubernetes-operator/crds/netbird.io_networkrouters.yaml
index 6644007..56d4c67 100644
--- a/helm/kubernetes-operator/crds/netbird.io_networkrouters.yaml
+++ b/helm/kubernetes-operator/crds/netbird.io_networkrouters.yaml
@@ -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:
diff --git a/internal/controller/networkrouter_controller.go b/internal/controller/networkrouter_controller.go
index b714b92..2928460 100644
--- a/internal/controller/networkrouter_controller.go
+++ b/internal/controller/networkrouter_controller.go
@@ -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,
diff --git a/pkg/applyconfigurations/api/v1alpha1/networkrouterspec.go b/pkg/applyconfigurations/api/v1alpha1/networkrouterspec.go
index 6704347..3b49eaf 100644
--- a/pkg/applyconfigurations/api/v1alpha1/networkrouterspec.go
+++ b/pkg/applyconfigurations/api/v1alpha1/networkrouterspec.go
@@ -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.