added basic support
This commit is contained in:
parent
d83bc3368a
commit
27d24e5b1d
@ -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"`
|
||||||
|
15
cmd/main.go
15
cmd/main.go
@ -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
2
config.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
domain_label: "ddns.acooldomain.co/hostname"
|
||||||
|
default_domain: "acooldomain.co"
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user