Skip to content

Commit

Permalink
Report status of k0smotroncontrolplane replicas
Browse files Browse the repository at this point in the history
Signed-off-by: Adrian Pedriza <[email protected]>
  • Loading branch information
AdrianPedriza committed Dec 24, 2024
1 parent cc1e009 commit 9237196
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 79 deletions.
28 changes: 23 additions & 5 deletions api/controlplane/v1beta1/k0smotron_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,27 @@ type K0smotronControlPlaneList struct {

type K0smotronControlPlaneStatus struct {
// Ready denotes that the control plane is ready
Ready bool `json:"ready"`
ControlPlaneReady bool `json:"controlPlaneReady"`
Inititalized bool `json:"initialized"`
ExternalManagedControlPlane bool `json:"externalManagedControlPlane"`
Version string `json:"version"`
Ready bool `json:"ready"`
ControlPlaneReady bool `json:"controlPlaneReady"`
Inititalized bool `json:"initialized"`
ExternalManagedControlPlane bool `json:"externalManagedControlPlane"`
// version represents the minimum Kubernetes version for the control plane pods
// in the cluster.
// +optional
Version string `json:"version"`
// replicas is the total number of pods targeted by this control plane
// +optional
Replicas int32 `json:"replicas"`
// updatedReplicas is the total number of pods targeted by this control plane
// that have the desired version.
// +optional
UpdatedReplicas int32 `json:"updatedReplicas"`
// readyReplicas is the total number of fully running and ready control plane pods.
// +optional
ReadyReplicas int32 `json:"readyReplicas"`
// unavailableReplicas is the total number of unavailable pods targeted by this control plane.
// This is the total number of pods with Condition Ready = false.
// They may either be pods that are running but not yet ready.
// +optional
UnavailableReplicas int32 `json:"unavailableReplicas"`
}
4 changes: 2 additions & 2 deletions api/k0smotron.io/v1beta1/k0smotroncluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ type Mount struct {
const (
defaultK0SImage = "k0sproject/k0s"
defaultK0SVersion = "v1.27.9-k0s.0"
defaultK0SSuffix = "k0s.0"
DefaultK0SSuffix = "k0s.0"
)

func (c *ClusterSpec) GetImage() string {
Expand All @@ -126,7 +126,7 @@ func (c *ClusterSpec) GetImage() string {
}

if !strings.Contains(k0sVersion, "-k0s.") {
k0sVersion = fmt.Sprintf("%s-%s", k0sVersion, defaultK0SSuffix)
k0sVersion = fmt.Sprintf("%s-%s", k0sVersion, DefaultK0SSuffix)
}

if c.Image == "" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4314,14 +4314,39 @@ spec:
ready:
description: Ready denotes that the control plane is ready
type: boolean
readyReplicas:
description: readyReplicas is the total number of fully running and
ready control plane pods.
format: int32
type: integer
replicas:
description: replicas is the total number of pods targeted by this
control plane
format: int32
type: integer
unavailableReplicas:
description: |-
unavailableReplicas is the total number of unavailable pods targeted by this control plane.
This is the total number of pods with Condition Ready = false.
They may either be pods that are running but not yet ready.
format: int32
type: integer
updatedReplicas:
description: |-
updatedReplicas is the total number of pods targeted by this control plane
that have the desired version.
format: int32
type: integer
version:
description: |-
version represents the minimum Kubernetes version for the control plane pods
in the cluster.
type: string
required:
- controlPlaneReady
- externalManagedControlPlane
- initialized
- ready
- version
type: object
type: object
served: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4314,14 +4314,39 @@ spec:
ready:
description: Ready denotes that the control plane is ready
type: boolean
readyReplicas:
description: readyReplicas is the total number of fully running and
ready control plane pods.
format: int32
type: integer
replicas:
description: replicas is the total number of pods targeted by this
control plane
format: int32
type: integer
unavailableReplicas:
description: |-
unavailableReplicas is the total number of unavailable pods targeted by this control plane.
This is the total number of pods with Condition Ready = false.
They may either be pods that are running but not yet ready.
format: int32
type: integer
updatedReplicas:
description: |-
updatedReplicas is the total number of pods targeted by this control plane
that have the desired version.
format: int32
type: integer
version:
description: |-
version represents the minimum Kubernetes version for the control plane pods
in the cluster.
type: string
required:
- controlPlaneReady
- externalManagedControlPlane
- initialized
- ready
- version
type: object
type: object
served: true
Expand Down
44 changes: 42 additions & 2 deletions docs/resource-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11554,13 +11554,53 @@ will pick it automatically.<br/>
Ready denotes that the control plane is ready<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>readyReplicas</b></td>
<td>integer</td>
<td>
readyReplicas is the total number of fully running and ready control plane pods.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>replicas</b></td>
<td>integer</td>
<td>
replicas is the total number of pods targeted by this control plane<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>unavailableReplicas</b></td>
<td>integer</td>
<td>
unavailableReplicas is the total number of unavailable pods targeted by this control plane.
This is the total number of pods with Condition Ready = false.
They may either be pods that are running but not yet ready.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>updatedReplicas</b></td>
<td>integer</td>
<td>
updatedReplicas is the total number of pods targeted by this control plane
that have the desired version.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>version</b></td>
<td>string</td>
<td>
<br/>
version represents the minimum Kubernetes version for the control plane pods
in the cluster.<br/>
</td>
<td>true</td>
<td>false</td>
</tr></tbody>
</table>

Expand Down
119 changes: 118 additions & 1 deletion internal/controller/controlplane/k0smotron_controlplane_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package controlplane

import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"time"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
Expand All @@ -45,6 +48,8 @@ import (

cpv1beta1 "github.com/k0sproject/k0smotron/api/controlplane/v1beta1"
kapi "github.com/k0sproject/k0smotron/api/k0smotron.io/v1beta1"
"github.com/k0sproject/k0smotron/internal/exec"
"github.com/k0sproject/version"
)

type K0smotronController struct {
Expand Down Expand Up @@ -107,6 +112,26 @@ func (c *K0smotronController) Reconcile(ctx context.Context, req ctrl.Request) (
// return ctrl.Result{}, nil
// }

defer func() {
derr := c.updateStatus(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, kcp)
if derr != nil {
if errors.Is(derr, ErrNotReady) {
res = ctrl.Result{RequeueAfter: 10 * time.Second, Requeue: true}
} else {
log.Error(derr, "Failed to update K0smotronContorlPlane status")
err = derr
return
}
}

derr = c.Status().Patch(ctx, kcp, client.Merge)
if derr != nil {
log.Error(derr, "Failed to patch K0smotronContorlPlane status")
err = derr
return
}
}()

res, ready, err := c.reconcile(ctx, cluster, kcp)
if err != nil {
log.Error(err, "Reconciliation failed")
Expand Down Expand Up @@ -147,7 +172,6 @@ func (c *K0smotronController) Reconcile(ctx context.Context, req ctrl.Request) (
kcp.Status.ExternalManagedControlPlane = true
kcp.Status.Inititalized = true
kcp.Status.ControlPlaneReady = true
kcp.Status.Version = kcp.Spec.Version
err = c.Status().Update(ctx, kcp)

return res, err
Expand Down Expand Up @@ -300,6 +324,99 @@ func (c *K0smotronController) ensureCertificates(ctx context.Context, cluster *c
return certificates.LookupOrGenerate(ctx, c.Client, capiutil.ObjectKey(cluster), *metav1.NewControllerRef(kcp, cpv1beta1.GroupVersion.WithKind("K0smotronControlPlane")))
}

func (c *K0smotronController) updateStatus(ctx context.Context, cluster types.NamespacedName, kcp *cpv1beta1.K0smotronControlPlane) error {
var kCluster kapi.Cluster
err := c.Client.Get(ctx, cluster, &kCluster)
if err != nil {
if apierrors.IsNotFound(err) {
// The cluster is not yet created.
return nil
}

return err
}

contolPlanePods := &corev1.PodList{}
opts := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{
"cluster.x-k8s.io/cluster-name": cluster.Name,
"cluster.x-k8s.io/control-plane": "true",
}),
}
err = c.List(ctx, contolPlanePods, opts)
if err != nil {
return err
}

kcp.Status.Replicas = int32(len(contolPlanePods.Items))

var (
updatedReplicas, readyReplicas, unavailableReplicas int
)

desiredVersionStr := kcp.Spec.Version
if !strings.Contains(desiredVersionStr, "+") {
desiredVersionStr = fmt.Sprintf("%s+%s", desiredVersionStr, kapi.DefaultK0SSuffix)
}
desiredVersion, err := version.NewVersion(desiredVersionStr)
if err != nil {
return err
}
minimumVersion := *desiredVersion

for _, pod := range contolPlanePods.Items {
isPodReady := false
for _, c := range pod.Status.Conditions {
// readiness probe in pod will propagate pod status Ready = True if k0s service is running successfully.
if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
isPodReady = true
break
}
}
if isPodReady {
readyReplicas++
} else {
unavailableReplicas++
// if pod is unavailable subsequent checks do not apply
continue
}

currentVersionOutput, err := exec.PodExecCmdOutput(ctx, c.ClientSet, c.RESTConfig, pod.GetName(), pod.GetNamespace(), "k0s version")
if err != nil {
return err
}
currentVersionStr, _ := strings.CutSuffix(currentVersionOutput, "\n")
currentVersion, err := version.NewVersion(currentVersionStr)
if err != nil {
return err
}

if desiredVersion.Equal(currentVersion) {
updatedReplicas++
}

if currentVersion.LessThan(&minimumVersion) {
minimumVersion = *currentVersion
}
}

kcp.Status.UpdatedReplicas = int32(updatedReplicas)
kcp.Status.ReadyReplicas = int32(readyReplicas)
kcp.Status.UnavailableReplicas = int32(unavailableReplicas)

if kcp.Status.ReadyReplicas > 0 {
kcp.Status.Version = minimumVersion.String()
}

// if no replicas are yet available or the desired version is not in the current state of the
// control plane, the reconciliation is requeued waiting for the desired replicas to become available.
if kcp.Status.UnavailableReplicas > 0 || desiredVersion.String() != kcp.Status.Version {
return ErrNotReady
}

return nil
}

// SetupWithManager sets up the controller with the Manager.
func (c *K0smotronController) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"sigs.k8s.io/yaml"

km "github.com/k0sproject/k0smotron/api/k0smotron.io/v1beta1"
kcontrollerutil "github.com/k0sproject/k0smotron/internal/controller/util"
"github.com/k0sproject/k0smotron/internal/util"
)

Expand Down Expand Up @@ -99,8 +100,8 @@ func (r *ClusterReconciler) generateConfig(kmc *km.Cluster, sans []string) (v1.C
ObjectMeta: metav1.ObjectMeta{
Name: kmc.GetConfigMapName(),
Namespace: kmc.Namespace,
Labels: labelsForCluster(kmc),
Annotations: annotationsForCluster(kmc),
Labels: kcontrollerutil.LabelsForK0smotronCluster(kmc),
Annotations: kcontrollerutil.AnnotationsForK0smotronCluster(kmc),
},
Data: map[string]string{
"K0SMOTRON_K0S_YAML": string(b),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"text/template"

km "github.com/k0sproject/k0smotron/api/k0smotron.io/v1beta1"
kcontrollerutil "github.com/k0sproject/k0smotron/internal/controller/util"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -55,8 +56,8 @@ func (r *ClusterReconciler) generateEntrypointCM(kmc *km.Cluster) (v1.ConfigMap,
ObjectMeta: metav1.ObjectMeta{
Name: kmc.GetEntrypointConfigMapName(),
Namespace: kmc.Namespace,
Labels: labelsForCluster(kmc),
Annotations: annotationsForCluster(kmc),
Labels: kcontrollerutil.LabelsForK0smotronCluster(kmc),
Annotations: kcontrollerutil.AnnotationsForK0smotronCluster(kmc),
},
Data: map[string]string{
"k0smotron-entrypoint.sh": entrypointBuf.String(),
Expand Down
Loading

0 comments on commit 9237196

Please sign in to comment.