Skip to content

Commit

Permalink
Implement minTtl feature (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkarlsen authored Apr 6, 2021
1 parent 6718ccd commit b9bf07a
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 21 deletions.
19 changes: 6 additions & 13 deletions api/v1alpha1/githubactionrunner_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package v1alpha1

import (
"errors"
"time"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -33,6 +31,11 @@ type GithubActionRunnerSpec struct {
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Maximum Pool Size",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:podCount"}
MaxRunners int `json:"maxRunners"`

// Minimum time to live for a runner. This can avoid trashing by keeping pods around longer than required by jobs, keeping caches hot.
// +kubebuilder:default="0m"
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Minimum time to live"
MinTTL metav1.Duration `json:"minTtl"`

// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Pod Template"
PodTemplateSpec v1.PodTemplateSpec `json:"podTemplateSpec"`
Expand All @@ -46,7 +49,7 @@ type GithubActionRunnerSpec struct {
// +kubebuilder:validation:Optional
// +kubebuilder:default="1m"
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Reconciliation Period"
ReconciliationPeriod string `json:"reconciliationPeriod"`
ReconciliationPeriod metav1.Duration `json:"reconciliationPeriod"`

// What order to delete idle pods in
// +kubebuilder:default="LeastRecent"
Expand Down Expand Up @@ -75,16 +78,6 @@ func (r GithubActionRunnerSpec) IsValid() (bool, error) {
return true, nil
}

// GetReconciliationPeriod returns period as a Duration
func (r GithubActionRunnerSpec) GetReconciliationPeriod() time.Duration {
duration, err := time.ParseDuration(r.ReconciliationPeriod)
if err != nil {
return time.Minute
}

return duration
}

// GithubActionRunnerStatus defines the observed state of GithubActionRunner
type GithubActionRunnerStatus struct {
// the current size of the build pool
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/garo.tietoevry.com_githubactionrunners.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ spec:
description: Minimum pool-size. Note that you need one runner in order for jobs to be schedulable, else they fail claiming no runners match the selector labels.
minimum: 1
type: integer
minTtl:
default: 0m
description: Minimum time to live for a runner. This can avoid trashing by keeping pods around longer than required by jobs, keeping caches hot.
type: string
organization:
description: Your GitHub organization
type: string
Expand Down Expand Up @@ -3822,6 +3826,7 @@ spec:
required:
- maxRunners
- minRunners
- minTtl
- organization
- podTemplateSpec
type: object
Expand Down
4 changes: 2 additions & 2 deletions controllers/githubactionrunner_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (r *GithubActionRunnerReconciler) handleScaling(ctx context.Context, instan

// scaleDown will scale down an idle runner based on policy in CR
func (r *GithubActionRunnerReconciler) scaleDown(ctx context.Context, podRunnerPairs podRunnerPairList, instance *garov1alpha1.GithubActionRunner) error {
idles := podRunnerPairs.getIdles(instance.Spec.DeletionOrder)
idles := podRunnerPairs.getIdles(instance.Spec.DeletionOrder, instance.Spec.MinTTL.Duration)
for _, pair := range idles {
err := r.unregisterRunner(ctx, instance, pair)
if err != nil { // should be improved, here we just assume it's because it's running a job and cannot be removed, skip to next candidate
Expand Down Expand Up @@ -178,7 +178,7 @@ func shouldScaleDown(podRunnerPairs podRunnerPairList, instance *garov1alpha1.Gi
}

func (r *GithubActionRunnerReconciler) manageOutcome(ctx context.Context, instance *garov1alpha1.GithubActionRunner, issue error) (reconcile.Result, error) {
return r.ManageOutcomeWithRequeue(ctx, instance, issue, instance.Spec.GetReconciliationPeriod())
return r.ManageOutcomeWithRequeue(ctx, instance, issue, instance.Spec.ReconciliationPeriod.Duration)
}

// SetupWithManager configures the controller by using the passed mgr
Expand Down
5 changes: 3 additions & 2 deletions controllers/podrunner_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/thoas/go-funk"
corev1 "k8s.io/api/core/v1"
"sort"
"time"
)

type podRunnerPair struct {
Expand Down Expand Up @@ -77,9 +78,9 @@ func (r podRunnerPairList) numIdle() int {
return r.numRunners() - r.numBusy()
}

func (r podRunnerPairList) getIdles(sortOrder v1alpha1.SortOrder) []podRunnerPair {
func (r podRunnerPairList) getIdles(sortOrder v1alpha1.SortOrder, minTTL time.Duration) []podRunnerPair {
idles := funk.Filter(r.pairs, func(pair podRunnerPair) bool {
return !(pair.runner.GetBusy() || util.IsBeingDeleted(&pair.pod))
return !(pair.runner.GetBusy() || util.IsBeingDeleted(&pair.pod)) && time.Now().After(pair.pod.CreationTimestamp.Add(minTTL))
}).([]podRunnerPair)

sort.SliceStable(idles, func(i, j int) bool {
Expand Down
8 changes: 4 additions & 4 deletions controllers/podrunner_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var podList = v1.PodList{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "name1",
CreationTimestamp: metav1.NewTime(time.Now()),
CreationTimestamp: metav1.NewTime(time.Now().Add(-time.Minute)),
},
Spec: v1.PodSpec{},
Status: v1.PodStatus{},
Expand All @@ -28,7 +28,7 @@ var podList = v1.PodList{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "name2",
CreationTimestamp: metav1.NewTime(time.Now().Add(time.Minute)),
CreationTimestamp: metav1.NewTime(time.Now()),
},
Spec: v1.PodSpec{},
Status: v1.PodStatus{},
Expand Down Expand Up @@ -87,7 +87,7 @@ func TestSort(t *testing.T) {
}

for _, tc := range testCases {
podList := tc.podRunnerPairList.getIdles(tc.sortOrder)
assert.Equal(t, podList, tc.podRunnerPair)
podList := tc.podRunnerPairList.getIdles(tc.sortOrder, time.Duration(0))
assert.Equal(t, tc.podRunnerPair, podList)
}
}

0 comments on commit b9bf07a

Please sign in to comment.