added basic support

This commit is contained in:
ACoolName 2025-04-02 04:39:27 +03:00
parent d83bc3368a
commit 27d24e5b1d
6 changed files with 166 additions and 49 deletions

View File

@ -50,14 +50,13 @@ type ServerSpec struct {
} }
type BrowserSpec struct { type BrowserSpec struct {
Image string `json:"image,omitempty"` On bool `json:"on,omitempty"`
On bool `json:"on,omitempty"`
Port Port `json:"port,omitempty"`
} }
// ServerManagerSpec defines the desired state of ServerManager // ServerManagerSpec defines the desired state of ServerManager
type ServerManagerSpec struct { type ServerManagerSpec struct {
Id string `json:"id,omitempty"` Id string `json:"id,omitempty"`
Storage string `json:"storage,omitempty"`
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
Server ServerSpec `json:"server,omitempty"` Server ServerSpec `json:"server,omitempty"`

View File

@ -23,6 +23,7 @@ import (
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them. // to ensure that exec-entrypoint and run can make use of them.
"gopkg.in/yaml.v3"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -37,6 +38,7 @@ import (
servermanagerv1alpha1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1" servermanagerv1alpha1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1"
"git.acooldomain.co/server-manager/kubernetes-operator/internal/controller" "git.acooldomain.co/server-manager/kubernetes-operator/internal/controller"
traefikv3 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
@ -49,6 +51,7 @@ func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(servermanagerv1alpha1.AddToScheme(scheme)) utilruntime.Must(servermanagerv1alpha1.AddToScheme(scheme))
utilruntime.Must(traefikv3.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme // +kubebuilder:scaffold:scheme
} }
@ -143,12 +146,20 @@ func main() {
setupLog.Error(err, "unable to start manager") setupLog.Error(err, "unable to start manager")
os.Exit(1) os.Exit(1)
} }
config := controller.ServerManagerReconcilerConfig{} config := &controller.ServerManagerReconcilerConfig{}
configData, err := os.ReadFile("config.yaml")
if err != nil {
setupLog.Error(err, "unable to read config file")
}
err = yaml.Unmarshal(configData, config)
if err != nil {
setupLog.Error(err, "failed to marshal data")
}
if err = (&controller.ServerManagerReconciler{ if err = (&controller.ServerManagerReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Config: config, Config: *config,
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServerManager") setupLog.Error(err, "unable to create controller", "controller", "ServerManager")
os.Exit(1) os.Exit(1)

2
config.yaml Normal file
View File

@ -0,0 +1,2 @@
domain_label: "ddns.acooldomain.co/hostname"
default_domain: "acooldomain.co"

View File

@ -90,6 +90,8 @@ spec:
working_dir: working_dir:
type: string type: string
type: object type: object
storage:
type: string
type: object type: object
status: status:
description: ServerManagerStatus defines the observed state of ServerManager description: ServerManagerStatus defines the observed state of ServerManager

View File

@ -6,4 +6,12 @@ metadata:
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: servermanager-sample name: servermanager-sample
spec: spec:
# TODO(user): Add fields here id: test-serverr
storage: 10Gi
server:
"on": true
image: git.acooldomain.co/server-manager/minecraft:paper-1.21.4
working_dir: /opt/server
ports:
- protocol: TCP
port: 25565

View File

@ -19,10 +19,13 @@ package controller
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time"
traefikv3 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" traefikv3 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
@ -34,9 +37,19 @@ import (
servermanagerv1alpha1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1" servermanagerv1alpha1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1"
) )
type MiddlewareRef struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
}
type BrowserConfig struct {
Middleware MiddlewareRef `yaml:"middleware"`
}
type ServerManagerReconcilerConfig struct { type ServerManagerReconcilerConfig struct {
DomainLabel string `yaml:"domain_label"` Browser BrowserConfig `yaml:"browser_middleware"`
DefaultDomain string `yaml:"default_domain"` DomainLabel string `yaml:"domain_label"`
DefaultDomain string `yaml:"default_domain"`
} }
// ServerManagerReconciler reconciles a ServerManager object // ServerManagerReconciler reconciles a ServerManager object
@ -84,63 +97,142 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, err return ctrl.Result{}, err
} }
} }
logging.Info("verified pvc")
serverPod := r.ServerPod(s, pvc) serverPod := r.ServerPod(s, pvc)
if serverPod != nil { found := &corev1.Pod{}
found := &corev1.Pod{} err = r.Get(ctx, client.ObjectKey{Namespace: pvc.Namespace, Name: pvc.Name}, found)
err = r.Get(ctx, client.ObjectKey{Namespace: pvc.Namespace, Name: pvc.Name}, found) if err == nil && !s.Spec.Server.On {
if err != nil { err = r.Delete(ctx, serverPod)
if errors.IsNotFound(err) { return ctrl.Result{Requeue: true}, err
}
if err != nil {
if errors.IsNotFound(err) {
if s.Spec.Server.On {
err = r.Create(ctx, serverPod) err = r.Create(ctx, serverPod)
return ctrl.Result{Requeue: true}, err return ctrl.Result{Requeue: true}, err
} else {
return ctrl.Result{}, err
} }
} else {
return ctrl.Result{}, err
} }
}
domain := r.Config.DefaultDomain logging.Info("verified pod")
if r.Config.DomainLabel != "" {
node := &corev1.Node{}
err = r.Get(ctx, client.ObjectKeyFromObject(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: found.Spec.NodeName}}), node)
if err != nil {
logging.Error(err, fmt.Sprintf("Failed to find node %s", found.Spec.NodeName))
return ctrl.Result{}, err
}
labelDomain, ok := node.GetLabels()[r.Config.DomainLabel] if found.Spec.NodeName == "" {
if ok { logging.Info("waiting for pod to start 2")
domain = labelDomain return ctrl.Result{RequeueAfter: time.Second * 10}, nil
} }
}
if domain != s.Status.Server.Domain { domain := r.Config.DefaultDomain
s.Status.Server.Domain = domain if r.Config.DomainLabel != "" {
err = r.Update(ctx, s) node := &corev1.Node{}
err = r.Get(ctx, client.ObjectKeyFromObject(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: found.Spec.NodeName}}), node)
if err != nil {
logging.Error(err, fmt.Sprintf("Failed to find node %s", found.Spec.NodeName))
return ctrl.Result{}, err return ctrl.Result{}, err
} }
labelDomain, ok := node.GetLabels()[r.Config.DomainLabel]
if ok {
domain = labelDomain
}
}
logging.Info("got domain", "domain", domain)
if domain != s.Status.Server.Domain {
s.Status.Server.Domain = domain
logging.Info("updating ServerManager object", "NewDomain", domain, "OldDomain", s.Status.Server.Domain)
err = r.Status().Update(ctx, s)
logging.Info(fmt.Sprintf("%#v", err))
return ctrl.Result{}, err
} }
service := r.ServerService(s) service := r.ServerService(s)
found_service := &corev1.Service{} foundService := &corev1.Service{}
err = r.Get(ctx, client.ObjectKeyFromObject(service), found_service) err = r.Get(ctx, client.ObjectKeyFromObject(service), foundService)
if err == nil && !s.Spec.Server.On {
err = r.Delete(ctx, service)
return ctrl.Result{Requeue: true}, err
}
if err != nil { if err != nil {
if !errors.IsNotFound(err) { if !errors.IsNotFound(err) {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
err = r.Create(ctx, service) if s.Spec.Server.On {
if err != nil { err = r.Create(ctx, service)
return ctrl.Result{}, err return ctrl.Result{Requeue: true}, err
}
}
logging.Info(fmt.Sprintf("verified service %#v", foundService))
return ctrl.Result{}, nil
}
func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim) *corev1.Pod {
ports := make([]corev1.ContainerPort, len(s.Spec.Server.Ports))
for i, port := range s.Spec.Server.Ports {
ports[i] = corev1.ContainerPort{
ContainerPort: port.Port,
Protocol: port.Protocol,
} }
} }
return ctrl.Result{}, nil pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-browser", s.Name),
Namespace: s.Namespace,
Labels: map[string]string{"server": s.Name},
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "volume",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvc.Name,
},
},
},
},
InitContainers: []corev1.Container{
{
Name: "proxy-setter",
Image: "filebrowser/filebrowser",
ImagePullPolicy: corev1.PullAlways,
WorkingDir: s.Spec.Server.WorkingDir,
Ports: ports,
Args: []string{},
},
},
Containers: []corev1.Container{
{
Name: "browser",
Image: "filebrowser/filebrowser",
ImagePullPolicy: corev1.PullAlways,
Ports: ports,
Args: []string{},
VolumeMounts: []corev1.VolumeMount{{
Name: "volume",
MountPath: s.Spec.Server.WorkingDir,
}},
Stdin: true,
TTY: true,
},
},
},
}
controllerutil.SetControllerReference(s, pod, r.Scheme)
return pod
} }
func (r *ServerManagerReconciler) ServerService(s *servermanagerv1alpha1.ServerManager) *corev1.Service { func (r *ServerManagerReconciler) ServerService(s *servermanagerv1alpha1.ServerManager) *corev1.Service {
ports := make([]corev1.ServicePort, len(s.Spec.Server.Ports)) ports := make([]corev1.ServicePort, len(s.Spec.Server.Ports))
for i, port := range s.Spec.Server.Ports { for i, port := range s.Spec.Server.Ports {
ports[i] = corev1.ServicePort{NodePort: 0, Port: port.Port, TargetPort: intstr.FromInt32(port.Port), Name: fmt.Sprintf("%s-%d", port.Protocol, port.Port)} ports[i] = corev1.ServicePort{NodePort: 0, Port: port.Port, TargetPort: intstr.FromInt32(port.Port), Name: fmt.Sprintf("%s-%d", strings.ToLower(string(port.Protocol)), port.Port)}
} }
service := &corev1.Service{ service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -148,9 +240,10 @@ func (r *ServerManagerReconciler) ServerService(s *servermanagerv1alpha1.ServerM
Namespace: s.Namespace, Namespace: s.Namespace,
}, },
Spec: corev1.ServiceSpec{ Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeNodePort,
Ports: ports, Ports: ports,
Selector: map[string]string{ Selector: map[string]string{
"server": s.Spec.Id, "server": s.Name,
}, },
}, },
} }
@ -167,6 +260,9 @@ func (r *ServerManagerReconciler) ServerPvc(s *servermanagerv1alpha1.ServerManag
Spec: corev1.PersistentVolumeClaimSpec{ Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}, AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany},
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceStorage: resource.MustParse(s.Spec.Storage)},
},
}, },
} }
@ -176,9 +272,6 @@ func (r *ServerManagerReconciler) ServerPvc(s *servermanagerv1alpha1.ServerManag
} }
func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim) *corev1.Pod { func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim) *corev1.Pod {
if !s.Spec.Server.On {
return nil
}
ports := make([]corev1.ContainerPort, len(s.Spec.Server.Ports)) ports := make([]corev1.ContainerPort, len(s.Spec.Server.Ports))
for i, port := range s.Spec.Server.Ports { for i, port := range s.Spec.Server.Ports {
@ -192,6 +285,7 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: s.Name, Name: s.Name,
Namespace: s.Namespace, Namespace: s.Namespace,
Labels: map[string]string{"server": s.Name},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Volumes: []corev1.Volume{ Volumes: []corev1.Volume{
@ -206,12 +300,13 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag
}, },
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {
Name: "server", Name: "server",
Image: s.Spec.Server.Image, Image: s.Spec.Server.Image,
Command: s.Spec.Server.Command, ImagePullPolicy: corev1.PullAlways,
Args: s.Spec.Server.Args, Command: s.Spec.Server.Command,
WorkingDir: s.Spec.Server.WorkingDir, Args: s.Spec.Server.Args,
Ports: ports, WorkingDir: s.Spec.Server.WorkingDir,
Ports: ports,
VolumeMounts: []corev1.VolumeMount{{ VolumeMounts: []corev1.VolumeMount{{
Name: "volume", Name: "volume",
MountPath: s.Spec.Server.WorkingDir, MountPath: s.Spec.Server.WorkingDir,