Skip to content

Commit

Permalink
time based resource scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
pghildiyal committed Aug 16, 2024
1 parent 661d9bc commit 2f86126
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 21 deletions.
35 changes: 19 additions & 16 deletions api/v1alpha1/hibernator_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1

import (
v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -27,17 +28,18 @@ import (
type HibernatorSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
When TimeRangesWithZone `json:"timeRangesWithZone,omitempty"`
Selectors []Rule `json:"selectors"`
Hibernate bool `json:"hibernate,omitempty"`
UnHibernate bool `json:"unHibernate,omitempty"`
ReSyncInterval int `json:"reSyncInterval,omitempty"`
Pause bool `json:"pause,omitempty"`
PauseUntil DateTimeWithZone `json:"pauseUntil,omitempty"`
RevisionHistoryLimit *int `json:"revisionHistoryLimit,omitempty"`
Action Action `json:"action"`
DeleteStore bool `json:"deleteStore,omitempty"`
TargetReplicas *[]int `json:"targetReplicas,omitempty"`
When TimeRangesWithZone `json:"timeRangesWithZone,omitempty"`
Selectors []Rule `json:"selectors"`
Hibernate bool `json:"hibernate,omitempty"`
UnHibernate bool `json:"unHibernate,omitempty"`
ReSyncInterval int `json:"reSyncInterval,omitempty"`
Pause bool `json:"pause,omitempty"`
PauseUntil DateTimeWithZone `json:"pauseUntil,omitempty"`
RevisionHistoryLimit *int `json:"revisionHistoryLimit,omitempty"`
Action Action `json:"action"`
DeleteStore bool `json:"deleteStore,omitempty"`
TargetReplicas *[]int `json:"targetReplicas,omitempty"`
TargetResources *[]map[string]v1.ResourceRequirements `json:"targetResources,omitempty"`
}

type Rule struct {
Expand Down Expand Up @@ -149,11 +151,12 @@ type HibernatorList struct {
type Action string

const (
Delete Action = "delete"
Hibernate Action = "hibernate"
UnHibernate Action = "unhibernate"
Scale Action = "scale"
Sleep Action = "sleep" // for legacy reason; sleep is same as hibernate
Delete Action = "delete"
Hibernate Action = "hibernate"
UnHibernate Action = "unhibernate"
Scale Action = "scale"
Sleep Action = "sleep" // for legacy reason; sleep is same as hibernate
ScaleResource Action = "scaleResource"
)

type Weekday string
Expand Down
31 changes: 31 additions & 0 deletions config/crd/bases/pincher.devtron.ai_hibernators.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,37 @@ spec:
items:
type: integer
type: array
targetResources:
items:
additionalProperties:
description: ResourceRequirements describes the compute resource
requirements.
properties:
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Limits describes the maximum amount of compute
resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Requests describes the minimum amount of compute
resources required. If Requests is omitted for a container,
it defaults to Limits if that is explicitly specified, otherwise
to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
type: object
type: array
timeRangesWithZone:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
Expand Down
12 changes: 8 additions & 4 deletions controllers/HibernatorAction.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,17 @@ func (r *HibernatorActionImpl) scale(hibernator *pincherv1alpha1.Hibernator, tim

reSync := hibernator.Spec.Action == hibernator.Status.Action

hibernator.Status.Action = pincherv1alpha1.Scale

impactedObjects, excludedObjects := make([]pincherv1alpha1.ImpactedObject, 0), make([]pincherv1alpha1.ExcludedObject, 0)
if timeGap.WithinRange {
impactedObjects, excludedObjects = r.executeRules(hibernator, r.resourceAction.ScaleActionFactory(hibernator, timeGap), reSync)
if hibernator.Spec.Action == pincherv1alpha1.Scale {
impactedObjects, excludedObjects = r.executeRules(hibernator, r.resourceAction.ScaleActionFactory(hibernator, timeGap), reSync)
} else if hibernator.Spec.Action == pincherv1alpha1.ScaleResource {
impactedObjects, excludedObjects = r.executeRules(hibernator, r.resourceAction.ScaleResourceActionFactory(hibernator, timeGap), reSync)
}
} else {
impactedObjects, excludedObjects = r.executeRules(hibernator, r.resourceAction.ResetScaleActionFactory(hibernator), reSync)
if hibernator.Spec.Action == pincherv1alpha1.Scale {
impactedObjects, excludedObjects = r.executeRules(hibernator, r.resourceAction.ResetScaleActionFactory(hibernator), reSync)
}
}

if len(impactedObjects) > 0 {
Expand Down
91 changes: 91 additions & 0 deletions controllers/ResourceAction.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ package controllers

import (
"context"
"encoding/json"
"fmt"
pincherv1alpha1 "github.com/devtron-labs/winter-soldier/api/v1alpha1"
"github.com/devtron-labs/winter-soldier/pkg"
"github.com/tidwall/gjson"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
"strconv"
"strings"
"time"
)

Expand All @@ -34,6 +38,7 @@ type Execute func(included []unstructured.Unstructured) ([]pincherv1alpha1.Impac
type ResourceAction interface {
DeleteAction(included []unstructured.Unstructured) ([]pincherv1alpha1.ImpactedObject, []pincherv1alpha1.ExcludedObject)
ScaleActionFactory(hibernator *pincherv1alpha1.Hibernator, timeGap pincherv1alpha1.NearestTimeGap) Execute
ScaleResourceActionFactory(hibernator *pincherv1alpha1.Hibernator, timeGap pincherv1alpha1.NearestTimeGap) Execute
ResetScaleActionFactory(hibernator *pincherv1alpha1.Hibernator) Execute
}

Expand Down Expand Up @@ -259,3 +264,89 @@ func (r *ResourceActionImpl) hasReplicaAnnotation(res unstructured.Unstructured)
originalCount := annotations.Map()[replicaAnnotation].Str
return len(originalCount) != 0
}

func (r *ResourceActionImpl) ScaleResourceActionFactory(hibernator *pincherv1alpha1.Hibernator, timeGap pincherv1alpha1.NearestTimeGap) Execute {
fmt.Printf("entering ScaleResourceActionFactory %s \n", time.Now().Format(time.RFC1123Z))
var resourceRequirements map[string]v1.ResourceRequirements
if hibernator.Spec.TargetResources != nil && len(*hibernator.Spec.TargetResources) > timeGap.MatchedIndex {
resourceRequirements = (*hibernator.Spec.TargetResources)[timeGap.MatchedIndex]
} else if len(*hibernator.Spec.TargetResources) != 0 && len(*hibernator.Spec.TargetReplicas) <= timeGap.MatchedIndex {
resourceRequirements = (*hibernator.Spec.TargetResources)[len(*hibernator.Spec.TargetResources)-1]
}

return func(included []unstructured.Unstructured) ([]pincherv1alpha1.ImpactedObject, []pincherv1alpha1.ExcludedObject) {

impactedObjects := make([]pincherv1alpha1.ImpactedObject, 0)
excludedObjects := make([]pincherv1alpha1.ExcludedObject, 0)

if resourceRequirements == nil {
return impactedObjects, excludedObjects
}
resourceRequirementsAsSpec := make([]map[string]interface{}, 0)
for k, v := range resourceRequirements {
resourceRequirementsAsSpec = append(resourceRequirementsAsSpec, map[string]interface{}{"name": k, "resources": v})
}
var err error

for _, inc := range included {
if !strings.EqualFold(inc.GetKind(), "Pod") {
continue
}
var pod v1.Pod
err = runtime.DefaultUnstructuredConverter.FromUnstructured(inc.UnstructuredContent(), &pod)
if err != nil {
continue
}
needUpdate := false
for _, container := range pod.Spec.Containers {
rr, ok := resourceRequirements[container.Name]
if !ok {
continue
}
cpuRequestEqual := container.Resources.Requests.Cpu().Cmp(*rr.Requests.Cpu()) == 0
memoryRequestEqual := container.Resources.Requests.Memory().Cmp(*rr.Requests.Memory()) == 0
cpuLimitEqual := container.Resources.Limits.Cpu().Cmp(*rr.Limits.Cpu()) == 0
memoryLimitEqual := container.Resources.Limits.Memory().Cmp(*rr.Limits.Memory()) == 0
allEqual := cpuRequestEqual && memoryRequestEqual && cpuLimitEqual && memoryLimitEqual
if allEqual {
continue
}
needUpdate = true
}
if !needUpdate {
continue
}
resourcePatch := map[string]map[string][]map[string]interface{}{"spec": {"containers": resourceRequirementsAsSpec}}

patch := ""
if requirements, err := json.Marshal(resourcePatch); err == nil {
patch = string(requirements)
}
if len(patch) == 0 {
continue
}

impactedObject := pincherv1alpha1.ImpactedObject{
ResourceKey: getResourceKey(inc),
Status: "success",
}

request := &pkg.PatchRequest{
Name: inc.GetName(),
Namespace: inc.GetNamespace(),
GroupVersionKind: inc.GroupVersionKind(),
Patch: patch,
PatchType: string(types.StrategicMergePatchType),
}
_, err = r.Kubectl.PatchResource(context.Background(), request)

if err != nil {
impactedObject.Status = "error"
impactedObject.Message = err.Error()
}

impactedObjects = append(impactedObjects, impactedObject)
}
return impactedObjects, excludedObjects
}
}
2 changes: 1 addition & 1 deletion controllers/hibernator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (r *HibernatorReconciler) process(hibernator pincherv1alpha1.Hibernator) (c
finalHibernator, updated = r.HibernatorAction.delete(&hibernator)
} else if hibernator.Spec.Action == pincherv1alpha1.Hibernate || hibernator.Spec.Action == pincherv1alpha1.Sleep {
finalHibernator, updated = r.HibernatorAction.hibernate(&hibernator, nearestTimeGap)
} else if hibernator.Spec.Action == pincherv1alpha1.Scale {
} else if hibernator.Spec.Action == pincherv1alpha1.Scale || hibernator.Spec.Action == pincherv1alpha1.ScaleResource {
finalHibernator, updated = r.HibernatorAction.scale(&hibernator, nearestTimeGap)
} else {
log.Info("didnt hibernate or unHibernate -", "action", nearestTimeGap.WithinRange, "timegap", nearestTimeGap.TimeGapInSeconds, "isHibernating", hibernator.Status.IsHibernating)
Expand Down

0 comments on commit 2f86126

Please sign in to comment.