mirror of
https://github.com/YuzuZensai/netbird-kubernetes-operator.git
synced 2026-09-13 10:49:15 +00:00
Add network router and resource (#189)
This change adds two new resources, NetworkRouter and NetworkResource, which enable clusters to expose Kubernetes services to Netbird. The NetworkRouter is responsible for creating the network, group, setup key and routing peer all of which are unique to the isntance. Along with the deployment of the client in the cluster. The NetworkResource exposes a service by linking to the specific router it wants to expose to. This makes coupling between the resource and network easy to understand. Routers also set a DNS zone which is used to give names to resources based on the name and namespace of the service being exposed. Part of #172 Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/conditions"
|
||||
"github.com/fluxcd/pkg/runtime/patch"
|
||||
netbird "github.com/netbirdio/netbird/shared/management/client/rest"
|
||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1"
|
||||
"github.com/netbirdio/kubernetes-operator/internal/netbirdutil"
|
||||
)
|
||||
|
||||
type NetworkResourceReconciler struct {
|
||||
client.Client
|
||||
|
||||
Netbird *netbird.Client
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=netbird.io,resources=networkresources,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=netbird.io,resources=networkresources/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=netbird.io,resources=networkresources/finalizers,verbs=update
|
||||
|
||||
// nolint:gocyclo
|
||||
func (r *NetworkResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
netResource := &nbv1alpha1.NetworkResource{}
|
||||
err := r.Get(ctx, req.NamespacedName, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
sp := patch.NewSerialPatcher(netResource, r.Client)
|
||||
|
||||
if !netResource.DeletionTimestamp.IsZero() {
|
||||
return r.reconcileDelete(ctx, sp, netResource)
|
||||
}
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: netResource.Spec.ServiceRef.Name,
|
||||
Namespace: netResource.Namespace,
|
||||
},
|
||||
}
|
||||
err = r.Get(ctx, client.ObjectKeyFromObject(svc), svc)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
conditions.MarkFalse(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced Service cannot be found.")
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if svc.Spec.Type != corev1.ServiceTypeClusterIP {
|
||||
conditions.MarkFalse(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced Service is not of type ClusterIP.")
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
if svc.Spec.ClusterIP == "" || svc.Spec.ClusterIP == corev1.ClusterIPNone {
|
||||
conditions.MarkFalse(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced Service does not have a ClusterIP set.")
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
netRouter := &nbv1alpha1.NetworkRouter{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: netResource.Spec.NetworkRouterRef.Name,
|
||||
Namespace: netResource.Spec.NetworkRouterRef.Namespace,
|
||||
},
|
||||
}
|
||||
err = r.Get(ctx, client.ObjectKeyFromObject(netRouter), netRouter)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
conditions.MarkFalse(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced NetworkRouter cannot be found.")
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if netRouter.Status.NetworkID == "" || netRouter.Status.RoutingPeerID == "" {
|
||||
conditions.MarkFalse(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.DependencyReason, "Referenced NetworkRouter is not ready.")
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
groupIDs, err := netbirdutil.GetGroupIDs(ctx, r.Client, r.Netbird, netResource.Spec.Groups, netResource.Namespace)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
controllerutil.AddFinalizer(netResource, nbv1alpha1.NetbirdFinalizer)
|
||||
|
||||
resourceID, err := func() (string, error) {
|
||||
netReq := api.NetworkResourceRequest{
|
||||
Name: svc.Name + "/" + svc.Namespace,
|
||||
Address: svc.Spec.ClusterIP,
|
||||
Enabled: true,
|
||||
Groups: groupIDs,
|
||||
}
|
||||
if netResource.Status.ResourceID != "" {
|
||||
netResp, err := r.Netbird.Networks.Resources(netRouter.Status.NetworkID).Update(ctx, netResource.Status.ResourceID, netReq)
|
||||
if err != nil && !netbird.IsNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
if err == nil {
|
||||
return netResp.Id, nil
|
||||
}
|
||||
}
|
||||
netResp, err := r.Netbird.Networks.Resources(netRouter.Status.NetworkID).Create(ctx, netReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return netResp.Id, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
netResource.Status.NetworkID = netRouter.Status.NetworkID
|
||||
netResource.Status.ResourceID = resourceID
|
||||
err = sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Create DNS records for resource.
|
||||
zone, err := netbirdutil.GetDNSZoneByName(ctx, r.Netbird, netRouter.Spec.DNSZoneRef.Name)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// If zone has changed we need to delete the old records.
|
||||
if netResource.Status.DNSZoneID != "" && netResource.Status.DNSZoneID != zone.Id {
|
||||
err = r.Netbird.DNSZones.DeleteRecord(ctx, netResource.Status.DNSZoneID, netResource.Status.DNSRecordID)
|
||||
if err != nil && !netbird.IsNotFound(err) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
netResource.Status.DNSZoneID = ""
|
||||
netResource.Status.DNSRecordID = ""
|
||||
}
|
||||
|
||||
recordID, err := func() (string, error) {
|
||||
dnsReq := api.DNSRecordRequest{
|
||||
Content: svc.Spec.ClusterIP,
|
||||
Name: strings.Join([]string{svc.Name, svc.Namespace, zone.Name}, "."),
|
||||
Ttl: int(5 * time.Minute / time.Second),
|
||||
Type: api.DNSRecordTypeA,
|
||||
}
|
||||
if netResource.Status.DNSZoneID != "" && netResource.Status.DNSRecordID != "" {
|
||||
recordResp, err := r.Netbird.DNSZones.UpdateRecord(ctx, netResource.Status.DNSZoneID, netResource.Status.DNSRecordID, dnsReq)
|
||||
if err != nil && !netbird.IsNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
if err == nil {
|
||||
return recordResp.Id, nil
|
||||
}
|
||||
}
|
||||
recordResp, err := r.Netbird.DNSZones.CreateRecord(ctx, zone.Id, dnsReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return recordResp.Id, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
netResource.Status.DNSZoneID = zone.Id
|
||||
netResource.Status.DNSRecordID = recordID
|
||||
|
||||
conditions.MarkTrue(netResource, nbv1alpha1.ReadyCondition, nbv1alpha1.ReconciledReason, "")
|
||||
err = sp.Patch(ctx, netResource, patch.WithStatusObservedGeneration{})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *NetworkResourceReconciler) reconcileDelete(ctx context.Context, sp *patch.SerialPatcher, netResource *nbv1alpha1.NetworkResource) (ctrl.Result, error) {
|
||||
if netResource.Status.NetworkID != "" && netResource.Status.ResourceID != "" {
|
||||
err := r.Netbird.Networks.Resources(netResource.Status.NetworkID).Delete(ctx, netResource.Status.ResourceID)
|
||||
if err != nil && !netbird.IsNotFound(err) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
if netResource.Status.DNSZoneID != "" && netResource.Status.DNSRecordID != "" {
|
||||
err := r.Netbird.DNSZones.DeleteRecord(ctx, netResource.Status.DNSZoneID, netResource.Status.DNSRecordID)
|
||||
if err != nil && !netbird.IsNotFound(err) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
controllerutil.RemoveFinalizer(netResource, nbv1alpha1.NetbirdFinalizer)
|
||||
err := sp.Patch(ctx, netResource)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *NetworkResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
err := mgr.GetFieldIndexer().IndexField(context.Background(), &nbv1alpha1.NetworkResource{}, ".spec.networkRouterRef", func(obj client.Object) []string {
|
||||
netResource := obj.(*nbv1alpha1.NetworkResource)
|
||||
ref := netResource.Spec.NetworkRouterRef
|
||||
if ref.Name == "" {
|
||||
return nil
|
||||
}
|
||||
if ref.Namespace == "" {
|
||||
ref.Namespace = netResource.Namespace
|
||||
}
|
||||
return []string{fmt.Sprintf("%s/%s", ref.Name, ref.Namespace)}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mgr.GetFieldIndexer().IndexField(context.Background(), &nbv1alpha1.NetworkResource{}, ".spec.serviceRef", func(obj client.Object) []string {
|
||||
netResource := obj.(*nbv1alpha1.NetworkResource)
|
||||
ref := netResource.Spec.ServiceRef
|
||||
if ref.Name == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{netResource.Spec.ServiceRef.Name}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&nbv1alpha1.NetworkResource{}).
|
||||
Watches(
|
||||
&nbv1alpha1.NetworkRouter{},
|
||||
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
netResourceList := &nbv1alpha1.NetworkResourceList{}
|
||||
err := r.List(ctx, netResourceList, client.MatchingFields{".spec.networkRouterRef": fmt.Sprintf("%s/%s", obj.GetName(), obj.GetNamespace())})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
requests := make([]reconcile.Request, len(netResourceList.Items))
|
||||
for i, item := range netResourceList.Items {
|
||||
requests[i] = reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: item.Name,
|
||||
Namespace: item.Namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}),
|
||||
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
|
||||
).
|
||||
Watches(
|
||||
&corev1.Service{},
|
||||
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
netResourceList := &nbv1alpha1.NetworkResourceList{}
|
||||
err := r.List(ctx, netResourceList, client.InNamespace(obj.GetNamespace()), client.MatchingFields{".spec.serviceRef": obj.GetName()})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
requests := make([]reconcile.Request, len(netResourceList.Items))
|
||||
for i, item := range netResourceList.Items {
|
||||
requests[i] = reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: item.Name,
|
||||
Namespace: item.Namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}),
|
||||
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
|
||||
).
|
||||
Complete(r)
|
||||
}
|
||||
Reference in New Issue
Block a user