Skip to content

Commit

Permalink
feat: take the embedded version cluster into account (#102)
Browse files Browse the repository at this point in the history
when deciding to start or not an upgrade we should take into account
what is the current embedded cluster we are running on. without this
fix we end up starting a cluster upgrade when it is not necessary.

we were using the kubernetes version (as in `kubectl version`) to
determine if an upgrade was necessary or not. it turns out that when
installed with k0s version v1.29.1+k0s.1 the kubernetes version reported
is v1.29.1+k0s (notice the missing .1 at the end). because of this the
running verion was never equal to the desired version (v1.29.1+k0s.1 !=
v1.29.1+k0s).

now we take into account the actual embedded cluster version. we do so
through an environment variable. this environment variable will change
only when the new operator deployment takes place (add-on upgrade) and
at that stage the kubernetes has already been upgraded.
  • Loading branch information
ricardomaraschini authored Feb 16, 2024
1 parent 7f1fae7 commit 26026e1
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ spec:
command:
- /manager
image: {{ printf "%s:%s" .Values.image.repository .Values.image.tag | quote }}
env:
- name: EMBEDDEDCLUSTER_VERSION
valueFrom:
configMapKeyRef:
name: embedded-cluster-config
key: embedded-cluster-version
name: manager
{{- if .Values.livenessProbe }}
livenessProbe:
Expand Down
56 changes: 51 additions & 5 deletions controllers/installation_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controllers
import (
"context"
"fmt"
"os"
"sort"
"strings"
"time"
Expand Down Expand Up @@ -193,59 +194,104 @@ func (r *InstallationReconciler) ReportInstallationChanges(ctx context.Context,
// upgrade plan already exists we make sure the installation status is updated with the
// latest plan status.
func (r *InstallationReconciler) ReconcileK0sVersion(ctx context.Context, in *v1beta1.Installation) error {
// if the installation has no desired version then there isn't much we can do other
// than flagging as installed. this will allow the add-ons to be applied.
if in.Spec.Config == nil || in.Spec.Config.Version == "" {
in.Status.SetState(v1beta1.InstallationStateKubernetesInstalled, "")
return nil
}

// if we are running the desired version sets the kubernetes as installed. the upgrade
// process is: 1st) upgrade the k0s cluster and 2nd) update the addons. if we are online
// and our version matches the desired version then it means that the k0s upgrade went
// through.
curstr := strings.TrimPrefix(os.Getenv("EMBEDDEDCLUSTER_VERSION"), "v")
desstr := strings.TrimPrefix(in.Spec.Config.Version, "v")
if curstr == desstr {
in.Status.SetState(v1beta1.InstallationStateKubernetesInstalled, "")
return nil
}

// fetch the metadata for the desired embedded cluster version.
meta, err := release.MetadataFor(ctx, in.Spec.Config.Version, in.Spec.MetricsBaseURL)
if err != nil {
in.Status.SetState(v1beta1.InstallationStateFailed, err.Error())
return nil
}

// find out the kubernetes version we are currently running so we can compare with
// the desired kubernetes version. we don't want anyone trying to do a downgrade.
vinfo, err := r.Discovery.ServerVersion()
if err != nil {
return fmt.Errorf("failed to get server version: %w", err)
}
runningVersion := vinfo.GitVersion
if runningVersion == meta.Versions.Kubernetes {
in.Status.SetState(v1beta1.InstallationStateKubernetesInstalled, "")
return nil
}
running, err := version.NewVersion(runningVersion)
if err != nil {
reason := fmt.Sprintf("Invalid running version %s", runningVersion)
in.Status.SetState(v1beta1.InstallationStateFailed, reason)
return nil
}
desired, err := version.NewVersion(meta.Versions.Kubernetes)

// if we have installed the cluster with a k0s version like v1.29.1+k0s.1 then
// the kubernetes server version reported back is v1.29.1+k0s. i.e. the .1 is
// not part of the kubernetes version, it is the k0s version. we trim it down
// so we can compare kube with kube version.
desiredVersion := meta.Versions.Kubernetes
index := strings.Index(desiredVersion, "k0s")
if index == -1 {
reason := fmt.Sprintf("Invalid desired version %s", desiredVersion)
in.Status.SetState(v1beta1.InstallationStateFailed, reason)
return nil
}
desiredVersion = desiredVersion[:index+len("k0s")]
desired, err := version.NewVersion(desiredVersion)
if err != nil {
reason := fmt.Sprintf("Invalid desired version %s", in.Spec.Config.Version)
in.Status.SetState(v1beta1.InstallationStateFailed, reason)
return nil
}

// stop here if someone is trying a downgrade. we do not support this, flag the
// installation accordingly and returns.
if running.GreaterThan(desired) {
in.Status.SetState(v1beta1.InstallationStateFailed, "Downgrades not supported")
return nil
}

var plan apv1b2.Plan
okey := client.ObjectKey{Name: "autopilot"}
if err := r.Get(ctx, okey, &plan); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to get upgrade plan: %w", err)
} else if errors.IsNotFound(err) {
// there is no autopilot plan in the cluster so we are free to
// start our own plan. here we link the plan to the installation
// by its name.
if err := r.StartUpgrade(ctx, in); err != nil {
return fmt.Errorf("failed to start upgrade: %w", err)
}
return nil
}

// if we have created this plan we just found for the installation we are
// reconciling we set the installation state according to the plan state.
if plan.Spec.ID == in.Name {
r.SetStateBasedOnPlan(in, plan)
return nil
}

// this is most likely a plan that has been created by a previous installation
// object, we can't move on until this one finishes. this can happen if someone
// issues multiple upgrade requests at the same time.
if !autopilot.HasThePlanEnded(plan) {
reason := fmt.Sprintf("Another upgrade is in progress (%s)", plan.Spec.ID)
in.Status.SetState(v1beta1.InstallationStateWaiting, reason)
return nil
}

// it seems like the plan previously created by other installation object
// has been finished, we can delete it. this will trigger a new reconcile
// this time without the plan (i.e. we will be able to create our own plan).
if err := r.Delete(ctx, &plan); err != nil {
return fmt.Errorf("failed to delete previous upgrade plan: %w", err)
}
Expand Down

0 comments on commit 26026e1

Please sign in to comment.