mirror of
https://github.com/YuzuZensai/netbird-kubernetes-operator.git
synced 2026-09-13 18:59:09 +00:00
Add ingress feature to controller (#5)
Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
This commit is contained in:
co-authored by
Maycon Santos
parent
cea60745d2
commit
166091b8e0
@@ -0,0 +1,259 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
|
||||
"github.com/netbirdio/kubernetes-operator/internal/util"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// ServiceReconciler reconciles a Service object
|
||||
type ServiceReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
ClusterName string
|
||||
ClusterDNS string
|
||||
NamespacedNetworks bool
|
||||
ControllerNamespace string
|
||||
}
|
||||
|
||||
const (
|
||||
// ServiceExposeAnnotation Service annotation for exposing
|
||||
ServiceExposeAnnotation = "netbird.io/expose"
|
||||
serviceGroupsAnnotation = "netbird.io/groups"
|
||||
serviceResourceAnnotation = "netbird.io/resource-name"
|
||||
servicePolicyAnnotation = "netbird.io/policy"
|
||||
servicePortsAnnotation = "netbird.io/policy-ports"
|
||||
serviceProtocolAnnotation = "netbird.io/policy-protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
networkDescription = "Created by kubernetes-operator"
|
||||
)
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := ctrl.Log.WithName("Service").WithValues("namespace", req.Namespace, "name", req.Name)
|
||||
logger.Info("Reconciling Service")
|
||||
|
||||
svc := corev1.Service{}
|
||||
err := r.Get(ctx, req.NamespacedName, &svc)
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
logger.Error(errKubernetesAPI, "error getting Service", "err", err)
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
_, shouldExpose := svc.Annotations[ServiceExposeAnnotation]
|
||||
|
||||
// If Service is being deleted, un-expose
|
||||
shouldExpose = shouldExpose && svc.DeletionTimestamp == nil
|
||||
|
||||
if shouldExpose {
|
||||
return r.exposeService(ctx, req, svc, logger)
|
||||
}
|
||||
|
||||
return r.hideService(ctx, req, svc, logger)
|
||||
}
|
||||
|
||||
// hideService deletes NBResource for Service
|
||||
func (r *ServiceReconciler) hideService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) {
|
||||
var nbResource netbirdiov1.NBResource
|
||||
err := r.Client.Get(ctx, req.NamespacedName, &nbResource)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
logger.Error(errKubernetesAPI, "error getting NBResource", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if !errors.IsNotFound(err) {
|
||||
err = r.Client.Delete(ctx, &nbResource)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error deleting NBResource", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if util.Contains(svc.Finalizers, "netbird.io/cleanup") {
|
||||
svc.Finalizers = util.Without(svc.Finalizers, "netbird.io/cleanup")
|
||||
err := r.Client.Update(ctx, &svc)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error updating Service", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// exposeService creates/updates NBResource for Service
|
||||
func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, svc corev1.Service, logger logr.Logger) (ctrl.Result, error) {
|
||||
routerNamespace := r.ControllerNamespace
|
||||
if r.NamespacedNetworks {
|
||||
routerNamespace = req.Namespace
|
||||
}
|
||||
|
||||
if !util.Contains(svc.Finalizers, "netbird.io/cleanup") {
|
||||
svc.Finalizers = append(svc.Finalizers, "netbird.io/cleanup")
|
||||
err := r.Client.Update(ctx, &svc)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error updating Service", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var routingPeer netbirdiov1.NBRoutingPeer
|
||||
// Check if NBRoutingPeer exists
|
||||
err := r.Client.Get(ctx, types.NamespacedName{Namespace: routerNamespace, Name: "router"}, &routingPeer)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
logger.Error(errKubernetesAPI, "error getting NBRoutingPeer", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Create NBRoutingPeer with default values if not exists
|
||||
if errors.IsNotFound(err) {
|
||||
routingPeer = netbirdiov1.NBRoutingPeer{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "router",
|
||||
Namespace: routerNamespace,
|
||||
Finalizers: []string{"netbird.io/cleanup"},
|
||||
},
|
||||
Spec: netbirdiov1.NBRoutingPeerSpec{},
|
||||
}
|
||||
|
||||
err = r.Client.Create(ctx, &routingPeer)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error creating NBRoutingPeer", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logger.Info("Network not available")
|
||||
// Requeue to make sure network is created
|
||||
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
|
||||
}
|
||||
|
||||
if routingPeer.Status.NetworkID == nil {
|
||||
logger.Info("Network not available")
|
||||
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
|
||||
}
|
||||
|
||||
var nbResource netbirdiov1.NBResource
|
||||
err = r.Client.Get(ctx, req.NamespacedName, &nbResource)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
logger.Error(errKubernetesAPI, "error getting NBResource", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
nbrsErr := r.reconcileNBResource(&nbResource, req, svc, routingPeer)
|
||||
if nbrsErr != nil {
|
||||
return ctrl.Result{}, nbrsErr
|
||||
}
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
err = r.Client.Create(ctx, &nbResource)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error creating NBResource", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else {
|
||||
err = r.Client.Update(ctx, &nbResource)
|
||||
if err != nil {
|
||||
logger.Error(errKubernetesAPI, "error updating NBResource", "err", err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// reconcileNBResource ensures NBResource settings are in-line with Service definition and annotations
|
||||
func (r *ServiceReconciler) reconcileNBResource(nbResource *netbirdiov1.NBResource, req ctrl.Request, svc corev1.Service, routingPeer netbirdiov1.NBRoutingPeer) error {
|
||||
groups := []string{fmt.Sprintf("%s-%s-%s", r.ClusterName, req.Namespace, req.Name)}
|
||||
if v, ok := svc.Annotations[serviceGroupsAnnotation]; ok {
|
||||
groups = nil
|
||||
for _, g := range strings.Split(v, ",") {
|
||||
groups = append(groups, strings.TrimSpace(g))
|
||||
}
|
||||
}
|
||||
|
||||
resourceName := fmt.Sprintf("%s-%s", req.Namespace, req.Name)
|
||||
if v, ok := svc.Annotations[serviceResourceAnnotation]; ok {
|
||||
resourceName = v
|
||||
}
|
||||
|
||||
nbResource.ObjectMeta.Name = req.Name
|
||||
nbResource.ObjectMeta.Namespace = req.Namespace
|
||||
nbResource.Finalizers = []string{"netbird.io/cleanup"}
|
||||
nbResource.Spec.Name = resourceName
|
||||
nbResource.Spec.NetworkID = *routingPeer.Status.NetworkID
|
||||
nbResource.Spec.Address = fmt.Sprintf("%s.%s.%s", svc.Name, svc.Namespace, r.ClusterDNS)
|
||||
nbResource.Spec.Groups = groups
|
||||
|
||||
if v, ok := svc.Annotations[servicePolicyAnnotation]; ok {
|
||||
nbResource.Spec.PolicyName = v
|
||||
var filterProtocols []string
|
||||
if v, ok := svc.Annotations[serviceProtocolAnnotation]; ok {
|
||||
filterProtocols = []string{v}
|
||||
}
|
||||
var filterPorts []int32
|
||||
if v, ok := svc.Annotations[servicePortsAnnotation]; ok {
|
||||
for _, v := range strings.Split(v, ",") {
|
||||
port, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filterPorts = append(filterPorts, int32(port))
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range svc.Spec.Ports {
|
||||
if len(filterProtocols) > 0 && !util.Contains(filterProtocols, string(p.Protocol)) {
|
||||
continue
|
||||
}
|
||||
if len(filterPorts) > 0 && !util.Contains(filterPorts, p.Port) {
|
||||
continue
|
||||
}
|
||||
switch p.Protocol {
|
||||
case corev1.ProtocolSCTP:
|
||||
if !util.Contains(nbResource.Spec.TCPPorts, p.Port) {
|
||||
nbResource.Spec.TCPPorts = append(nbResource.Spec.TCPPorts, p.Port)
|
||||
}
|
||||
case corev1.ProtocolTCP:
|
||||
if !util.Contains(nbResource.Spec.TCPPorts, p.Port) {
|
||||
nbResource.Spec.TCPPorts = append(nbResource.Spec.TCPPorts, p.Port)
|
||||
}
|
||||
case corev1.ProtocolUDP:
|
||||
if !util.Contains(nbResource.Spec.UDPPorts, p.Port) {
|
||||
nbResource.Spec.UDPPorts = append(nbResource.Spec.UDPPorts, p.Port)
|
||||
}
|
||||
default:
|
||||
return errUnknownProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Handle removed policy name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Service{}).
|
||||
Named("service").
|
||||
Complete(r)
|
||||
}
|
||||
Reference in New Issue
Block a user