From c1deeaf8bb0d34c5161992906aba178b0e2f6de6 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Mon, 7 Dec 2020 09:40:49 +0900 Subject: [PATCH] feat: EKS IAM Roles for Service Accounts for Runner Pods One of the pod recreation conditions has been modified to use hash of runner spec, so that the controller does not keep restarting pods mutated by admission webhooks. This naturally allows us, for example, to use IRSA for EKS that requires its admission webhook to mutate the runner pod to have additional, IRSA-related volumes, volume mounts and env. Resolves #200 --- controllers/runner_controller.go | 24 +++++++++++++++++++++--- hash/fnv.go | 17 +++++++++++++++++ hash/hash.go | 25 +++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 hash/fnv.go create mode 100644 hash/hash.go diff --git a/controllers/runner_controller.go b/controllers/runner_controller.go index 3753819493..03dbe50ec8 100644 --- a/controllers/runner_controller.go +++ b/controllers/runner_controller.go @@ -19,7 +19,7 @@ package controllers import ( "context" "fmt" - "reflect" + "github.com/summerwind/actions-runner-controller/hash" "strings" "github.com/go-logr/logr" @@ -39,6 +39,8 @@ import ( const ( containerName = "runner" finalizerName = "runner.actions.summerwind.dev" + + runnerHashAnnotationKey = "runner-hash" ) // RunnerReconciler reconciles a Runner object @@ -198,7 +200,10 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } - if !runnerBusy && (!reflect.DeepEqual(pod.Spec.Containers[0].Env, newPod.Spec.Containers[0].Env) || pod.Spec.Containers[0].Image != newPod.Spec.Containers[0].Image) { + curHash := pod.Annotations[runnerHashAnnotationKey] + newHash := newPod.Annotations[runnerHashAnnotationKey] + + if !runnerBusy && curHash != newHash { restart = true } @@ -357,12 +362,25 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { } env = append(env, runner.Spec.Env...) + + annotations := map[string]string{} + + for k, v := range runner.Annotations { + annotations[k] = v + } + + // This implies that we recreate the runner pod whenever the runner has changes in: + // - metadata.labels + // - metadata.annotations + // - metadata.spec (including image, env, organization, repository, group, token, and so on) + annotations[runnerHashAnnotationKey] = hash.FNVHashStringObjects(runner.Labels, runner.Annotations, runner.Spec) + pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: runner.Name, Namespace: runner.Namespace, Labels: runner.Labels, - Annotations: runner.Annotations, + Annotations: annotations, }, Spec: corev1.PodSpec{ RestartPolicy: "OnFailure", diff --git a/hash/fnv.go b/hash/fnv.go new file mode 100644 index 0000000000..a8382544a7 --- /dev/null +++ b/hash/fnv.go @@ -0,0 +1,17 @@ +package hash + +import ( + "fmt" + "hash/fnv" + "k8s.io/apimachinery/pkg/util/rand" +) + +func FNVHashStringObjects(objs ...interface{}) string { + hash := fnv.New32a() + + for _, obj := range objs { + DeepHashObject(hash, obj) + } + + return rand.SafeEncodeString(fmt.Sprint(hash.Sum32())) +} diff --git a/hash/hash.go b/hash/hash.go new file mode 100644 index 0000000000..a6c3e1c62f --- /dev/null +++ b/hash/hash.go @@ -0,0 +1,25 @@ +// Copyright 2015 The Kubernetes Authors. +// hash.go is copied from kubernetes's pkg/util/hash.go +// See https://github.com/kubernetes/kubernetes/blob/e1c617a88ec286f5f6cb2589d6ac562d095e1068/pkg/util/hash/hash.go#L25-L37 + +package hash + +import ( + "hash" + + "github.com/davecgh/go-spew/spew" +) + +// DeepHashObject writes specified object to hash using the spew library +// which follows pointers and prints actual values of the nested objects +// ensuring the hash does not change when a pointer changes. +func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) { + hasher.Reset() + printer := spew.ConfigState{ + Indent: " ", + SortKeys: true, + DisableMethods: true, + SpewKeys: true, + } + printer.Fprintf(hasher, "%#v", objectToWrite) +}