123 lines
4.3 KiB
Go
123 lines
4.3 KiB
Go
|
|
package controller
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"k8s.io/apimachinery/pkg/runtime"
|
||
|
|
|
||
|
|
gomaprojv1beta1 "github.com/jkaninda/goma-operator/api/v1beta1"
|
||
|
|
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||
|
|
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/client"
|
||
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||
|
|
)
|
||
|
|
|
||
|
|
// createHpa creates HPA
|
||
|
|
func createHpa(r GatewayReconciler, ctx context.Context, req ctrl.Request, gateway *gomaprojv1beta1.Gateway) error {
|
||
|
|
logger := log.FromContext(ctx)
|
||
|
|
var metrics []autoscalingv2.MetricSpec
|
||
|
|
targetCPUUtilizationPercentage := gateway.Spec.AutoScaling.TargetCPUUtilizationPercentage
|
||
|
|
targetMemoryUtilizationPercentage := gateway.Spec.AutoScaling.TargetMemoryUtilizationPercentage
|
||
|
|
// Add CPU metric if targetCPUUtilizationPercentage is set
|
||
|
|
if targetCPUUtilizationPercentage != 0 {
|
||
|
|
metrics = append(metrics, autoscalingv2.MetricSpec{
|
||
|
|
Type: autoscalingv2.ResourceMetricSourceType,
|
||
|
|
Resource: &autoscalingv2.ResourceMetricSource{
|
||
|
|
Name: "cpu",
|
||
|
|
Target: autoscalingv2.MetricTarget{
|
||
|
|
Type: autoscalingv2.UtilizationMetricType,
|
||
|
|
AverageUtilization: int32Ptr(targetCPUUtilizationPercentage),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
// Add Memory metric if targetMemoryUtilizationPercentage is set
|
||
|
|
if targetMemoryUtilizationPercentage != 0 {
|
||
|
|
metrics = append(metrics, autoscalingv2.MetricSpec{
|
||
|
|
Type: autoscalingv2.ResourceMetricSourceType,
|
||
|
|
Resource: &autoscalingv2.ResourceMetricSource{
|
||
|
|
Name: "memory",
|
||
|
|
Target: autoscalingv2.MetricTarget{
|
||
|
|
Type: autoscalingv2.UtilizationMetricType,
|
||
|
|
AverageUtilization: int32Ptr(targetMemoryUtilizationPercentage),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
// Create HPA
|
||
|
|
hpa := &autoscalingv2.HorizontalPodAutoscaler{
|
||
|
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
|
Name: req.Name,
|
||
|
|
Namespace: req.Namespace,
|
||
|
|
},
|
||
|
|
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||
|
|
MinReplicas: int32Ptr(gateway.Spec.AutoScaling.MinReplicas),
|
||
|
|
MaxReplicas: gateway.Spec.AutoScaling.MaxReplicas,
|
||
|
|
Metrics: metrics,
|
||
|
|
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||
|
|
APIVersion: "apps/v1",
|
||
|
|
Kind: "Deployment",
|
||
|
|
Name: req.Name,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
// Check if the hpa already exists
|
||
|
|
var existHpa autoscalingv2.HorizontalPodAutoscaler
|
||
|
|
err := r.Get(ctx, types.NamespacedName{Name: req.Name, Namespace: req.Namespace}, &existHpa)
|
||
|
|
if err != nil && client.IgnoreNotFound(err) != nil {
|
||
|
|
logger.Error(err, "Failed to get HorizontalPodAutoscaler")
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err != nil && client.IgnoreNotFound(err) == nil {
|
||
|
|
// Create the HPA if it doesn't exist
|
||
|
|
if err = controllerutil.SetControllerReference(gateway, hpa, r.Scheme); err != nil {
|
||
|
|
logger.Error(err, "Failed to set controller reference")
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err = r.Create(ctx, hpa); err != nil {
|
||
|
|
logger.Error(err, "Failed to create HorizontalPodAutoscaler")
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
logger.Info("Created HorizontalPodAutoscaler", "HorizontalPodAutoscaler.Name", hpa.Name)
|
||
|
|
} else {
|
||
|
|
// Update the Deployment if the spec has changed
|
||
|
|
if !equalHpaSpec(existHpa, *hpa) {
|
||
|
|
existHpa.Spec = hpa.Spec
|
||
|
|
if err = r.Update(ctx, &existHpa); err != nil {
|
||
|
|
logger.Error(err, "Failed to update Deployment")
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
logger.Info("Updated HorizontalPodAutoscaler", "HorizontalPodAutoscaler.Name", hpa.Name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper function to compare Deployment specs
|
||
|
|
func equalHpaSpec(existing, desired autoscalingv2.HorizontalPodAutoscaler) bool {
|
||
|
|
// A deep equality check or field-by-field comparison would be more accurate
|
||
|
|
if existing.Spec.MinReplicas != desired.Spec.MinReplicas {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if existing.Spec.MaxReplicas != desired.Spec.MaxReplicas {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if existing.Spec.Metrics[0].Resource.Target.AverageUtilization != desired.Spec.Metrics[0].Resource.Target.AverageUtilization {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if existing.Spec.Metrics[1].Resource.Target.AverageUtilization != desired.Spec.Metrics[1].Resource.Target.AverageUtilization {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
func ConvertRawExtensionToStruct(raw runtime.RawExtension, out interface{}) error {
|
||
|
|
// Unmarshal the raw JSON into the provided struct
|
||
|
|
if err := json.Unmarshal(raw.Raw, out); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|