Fix scale-from-zero to retain the reg-only runner until other pods come up (#523)
Fixes #516
This commit is contained in:
parent
cb54864387
commit
e7020c7c0f
|
|
@ -68,65 +68,6 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
registrationOnlyRunnerNeeded := rs.Spec.Replicas != nil && *rs.Spec.Replicas == 0
|
|
||||||
registrationOnlyRunner := v1alpha1.Runner{}
|
|
||||||
registrationOnlyRunnerNsName := req.NamespacedName
|
|
||||||
registrationOnlyRunnerNsName.Name = registrationOnlyRunnerNameFor(rs.Name)
|
|
||||||
|
|
||||||
registrationOnlyRunnerExists := false
|
|
||||||
if err := r.Get(
|
|
||||||
ctx,
|
|
||||||
registrationOnlyRunnerNsName,
|
|
||||||
®istrationOnlyRunner,
|
|
||||||
); err != nil {
|
|
||||||
if !kerrors.IsNotFound(err) {
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
registrationOnlyRunnerExists = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if registrationOnlyRunnerNeeded {
|
|
||||||
if registrationOnlyRunnerExists {
|
|
||||||
if registrationOnlyRunner.Status.Phase == "" {
|
|
||||||
log.Info("Still waiting for the registration-only runner to be registered")
|
|
||||||
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// A registration-only runner does not exist and is needed, hence create it.
|
|
||||||
|
|
||||||
runnerForScaleFromToZero, err := r.newRunner(rs)
|
|
||||||
if err != nil {
|
|
||||||
return ctrl.Result{}, fmt.Errorf("failed to create runner for scale from/to zero: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runnerForScaleFromToZero.ObjectMeta.Name = registrationOnlyRunnerNsName.Name
|
|
||||||
runnerForScaleFromToZero.ObjectMeta.GenerateName = ""
|
|
||||||
runnerForScaleFromToZero.ObjectMeta.Labels = nil
|
|
||||||
metav1.SetMetaDataAnnotation(&runnerForScaleFromToZero.ObjectMeta, annotationKeyRegistrationOnly, "true")
|
|
||||||
|
|
||||||
if err := r.Client.Create(ctx, &runnerForScaleFromToZero); err != nil {
|
|
||||||
log.Error(err, "Failed to create runner for scale from/to zero")
|
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can continue to deleting runner pods only after the
|
|
||||||
// registration-only runner gets registered.
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// A registration-only runner exists and is not needed, hence delete it.
|
|
||||||
if registrationOnlyRunnerExists {
|
|
||||||
if err := r.Client.Delete(ctx, ®istrationOnlyRunner); err != nil {
|
|
||||||
log.Error(err, "Retrying soon because we failed to delete registration-only runner")
|
|
||||||
|
|
||||||
return ctrl.Result{Requeue: true}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
|
selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
@ -173,6 +114,71 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
||||||
desired = 1
|
desired = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registrationOnlyRunnerNsName := req.NamespacedName
|
||||||
|
registrationOnlyRunnerNsName.Name = registrationOnlyRunnerNameFor(rs.Name)
|
||||||
|
registrationOnlyRunner := v1alpha1.Runner{}
|
||||||
|
registrationOnlyRunnerExists := false
|
||||||
|
if err := r.Get(
|
||||||
|
ctx,
|
||||||
|
registrationOnlyRunnerNsName,
|
||||||
|
®istrationOnlyRunner,
|
||||||
|
); err != nil {
|
||||||
|
if !kerrors.IsNotFound(err) {
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registrationOnlyRunnerExists = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// On scale to zero, we must have fully registered registration-only runner before we start deleting other runners, hence `desired == 0`
|
||||||
|
// On scale from zero, we must retain the registratoin-only runner until one or more other runners get registered, hence `registrationOnlyRunnerExists && available == 0`.
|
||||||
|
// On RunnerReplicaSet creation, it have always 0 replics and no registration-only runner.
|
||||||
|
// In this case We don't need to bother creating a registration-only runner which gets deleted soon after we have 1 or more available repolicas,
|
||||||
|
// hence it's not `available == 0`, but `registrationOnlyRunnerExists && available == 0`.
|
||||||
|
// See https://github.com/actions-runner-controller/actions-runner-controller/issues/516
|
||||||
|
registrationOnlyRunnerNeeded := desired == 0 || (registrationOnlyRunnerExists && available == 0)
|
||||||
|
|
||||||
|
if registrationOnlyRunnerNeeded {
|
||||||
|
if registrationOnlyRunnerExists {
|
||||||
|
if registrationOnlyRunner.Status.Phase == "" {
|
||||||
|
log.Info("Still waiting for the registration-only runner to be registered")
|
||||||
|
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A registration-only runner does not exist and is needed, hence create it.
|
||||||
|
|
||||||
|
runnerForScaleFromToZero, err := r.newRunner(rs)
|
||||||
|
if err != nil {
|
||||||
|
return ctrl.Result{}, fmt.Errorf("failed to create runner for scale from/to zero: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerForScaleFromToZero.ObjectMeta.Name = registrationOnlyRunnerNsName.Name
|
||||||
|
runnerForScaleFromToZero.ObjectMeta.GenerateName = ""
|
||||||
|
runnerForScaleFromToZero.ObjectMeta.Labels = nil
|
||||||
|
metav1.SetMetaDataAnnotation(&runnerForScaleFromToZero.ObjectMeta, annotationKeyRegistrationOnly, "true")
|
||||||
|
|
||||||
|
if err := r.Client.Create(ctx, &runnerForScaleFromToZero); err != nil {
|
||||||
|
log.Error(err, "Failed to create runner for scale from/to zero")
|
||||||
|
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can continue to deleting runner pods only after the
|
||||||
|
// registration-only runner gets registered.
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A registration-only runner exists and is not needed, hence delete it.
|
||||||
|
if registrationOnlyRunnerExists {
|
||||||
|
if err := r.Client.Delete(ctx, ®istrationOnlyRunner); err != nil {
|
||||||
|
log.Error(err, "Retrying soon because we failed to delete registration-only runner")
|
||||||
|
|
||||||
|
return ctrl.Result{Requeue: true}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if available > desired {
|
if available > desired {
|
||||||
n := available - desired
|
n := available - desired
|
||||||
|
|
||||||
|
|
@ -244,6 +250,8 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
||||||
n = len(deletionCandidates)
|
n = len(deletionCandidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.V(0).Info(fmt.Sprintf("Deleting %d runner(s)", n), "desired", desired, "available", available, "ready", ready)
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
if err := r.Client.Delete(ctx, &deletionCandidates[i]); client.IgnoreNotFound(err) != nil {
|
if err := r.Client.Delete(ctx, &deletionCandidates[i]); client.IgnoreNotFound(err) != nil {
|
||||||
log.Error(err, "Failed to delete runner resource")
|
log.Error(err, "Failed to delete runner resource")
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
"k8s.io/utils/pointer"
|
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
|
||||||
|
|
@ -170,15 +168,7 @@ var _ = Context("Inside of a new namespace", func() {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, runner := range runners.Items {
|
runnersList.Sync(runners.Items)
|
||||||
runnersList.Add(&github.Runner{
|
|
||||||
ID: pointer.Int64Ptr(int64(i) + 1),
|
|
||||||
Name: pointer.StringPtr(runner.Name),
|
|
||||||
OS: pointer.StringPtr("linux"),
|
|
||||||
Status: pointer.StringPtr("online"),
|
|
||||||
Busy: pointer.BoolPtr(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(runners.Items)
|
return len(runners.Items)
|
||||||
},
|
},
|
||||||
|
|
@ -227,15 +217,7 @@ var _ = Context("Inside of a new namespace", func() {
|
||||||
logf.Log.Error(err, "list runners")
|
logf.Log.Error(err, "list runners")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, runner := range runners.Items {
|
runnersList.Sync(runners.Items)
|
||||||
runnersList.Add(&github.Runner{
|
|
||||||
ID: pointer.Int64Ptr(int64(i) + 1),
|
|
||||||
Name: pointer.StringPtr(runner.Name),
|
|
||||||
OS: pointer.StringPtr("linux"),
|
|
||||||
Status: pointer.StringPtr("online"),
|
|
||||||
Busy: pointer.BoolPtr(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(runners.Items)
|
return len(runners.Items)
|
||||||
},
|
},
|
||||||
|
|
@ -283,13 +265,7 @@ var _ = Context("Inside of a new namespace", func() {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
runnersList.Add(&github.Runner{
|
runnersList.AddOffline([]actionsv1alpha1.Runner{*updated})
|
||||||
ID: pointer.Int64Ptr(1001),
|
|
||||||
Name: pointer.StringPtr(regOnly.Name),
|
|
||||||
OS: pointer.StringPtr("linux"),
|
|
||||||
Status: pointer.StringPtr("offline"),
|
|
||||||
Busy: pointer.BoolPtr(false),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k8sClient.List(ctx, &runners, client.InNamespace(ns.Name), client.MatchingLabelsSelector{Selector: selector}); err != nil {
|
if err := k8sClient.List(ctx, &runners, client.InNamespace(ns.Name), client.MatchingLabelsSelector{Selector: selector}); err != nil {
|
||||||
|
|
@ -297,15 +273,7 @@ var _ = Context("Inside of a new namespace", func() {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, runner := range runners.Items {
|
runnersList.Sync(runners.Items)
|
||||||
runnersList.Add(&github.Runner{
|
|
||||||
ID: pointer.Int64Ptr(int64(i) + 1),
|
|
||||||
Name: pointer.StringPtr(runner.Name),
|
|
||||||
OS: pointer.StringPtr("linux"),
|
|
||||||
Status: pointer.StringPtr("online"),
|
|
||||||
Busy: pointer.BoolPtr(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(runners.Items)
|
return len(runners.Items)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package fake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
||||||
|
|
||||||
"github.com/google/go-github/v33/github"
|
"github.com/google/go-github/v33/github"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
@ -79,6 +80,18 @@ func (r *RunnersList) Sync(runners []v1alpha1.Runner) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RunnersList) AddOffline(runners []v1alpha1.Runner) {
|
||||||
|
for i, want := range runners {
|
||||||
|
r.Add(&github.Runner{
|
||||||
|
ID: github.Int64(int64(1000 + i)),
|
||||||
|
Name: github.String(want.Name),
|
||||||
|
OS: github.String("linux"),
|
||||||
|
Status: github.String("offline"),
|
||||||
|
Busy: github.Bool(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func exists(runners []*github.Runner, runner *github.Runner) bool {
|
func exists(runners []*github.Runner, runner *github.Runner) bool {
|
||||||
for _, r := range runners {
|
for _, r := range runners {
|
||||||
if *r.Name == *runner.Name {
|
if *r.Name == *runner.Name {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue