Skip to content

Commit 0225dbf

Browse files
authored
Merge pull request #3139 from fabriziopandini/add-conditions-to-kcp
🌱 Add conditions to the KubeadmControlPlane object
2 parents 80653d1 + 74f2b4f commit 0225dbf

14 files changed

+229
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha3
18+
19+
import clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
20+
21+
// Conditions and condition Reasons for the KubeadmControlPlane object
22+
23+
const (
24+
// MachinesReady reports an aggregate of current status of the machines controlled by the KubeadmControlPlane.
25+
MachinesReadyCondition clusterv1.ConditionType = "MachinesReady"
26+
)
27+
28+
const (
29+
// CertificatesAvailableCondition documents that cluster certificates were generated as part of the
30+
// processing of a a KubeadmControlPlane object.
31+
CertificatesAvailableCondition clusterv1.ConditionType = "CertificatesAvailable"
32+
33+
// CertificatesGenerationFailedReason (Severity=Warning) documents a KubeadmControlPlane controller detecting
34+
// an error while generating certificates; those kind of errors are usually temporary and the controller
35+
// automatically recover from them.
36+
CertificatesGenerationFailedReason = "CertificatesGenerationFailed"
37+
)
38+
39+
const (
40+
// AvailableCondition documents that the first control plane instance has completed the kubeadm init operation
41+
// and so the control plane is available and an API server instance is ready for processing requests.
42+
AvailableCondition clusterv1.ConditionType = "Available"
43+
44+
// WaitingForKubeadmInitReason (Severity=Info) documents a KubeadmControlPlane object waiting for the first
45+
// control plane instance to complete the kubeadm init operation.
46+
WaitingForKubeadmInitReason = "WaitingForKubeadmInit"
47+
)
48+
49+
const (
50+
// MachinesSpecUpToDateCondition documents that the spec of the machines controlled by the KubeadmControlPlane
51+
// is up to date. Whe this condition is false, the KubeadmControlPlane is executing a rolling upgrade.
52+
MachinesSpecUpToDateCondition clusterv1.ConditionType = "MachinesSpecUpToDate"
53+
54+
// RollingUpdateInProgressReason (Severity=Warning) documents a KubeadmControlPlane object executing a
55+
// rolling upgrade for aligning the machines spec to the desired state.
56+
RollingUpdateInProgressReason = "RollingUpdateInProgress"
57+
)
58+
59+
const (
60+
// ResizedCondition documents a KubeadmControlPlane that is resizing the set of controlled machines.
61+
ResizedCondition clusterv1.ConditionType = "Resized"
62+
63+
// ScalingUpReason (Severity=Info) documents a KubeadmControlPlane that is increasing the number of replicas.
64+
ScalingUpReason = "ScalingUp"
65+
66+
// ScalingDownReason (Severity=Info) documents a KubeadmControlPlane that is decreasing the number of replicas.
67+
ScalingDownReason = "ScalingDown"
68+
)

controlplane/kubeadm/api/v1alpha3/kubeadm_control_plane_types.go

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v1alpha3
1919
import (
2020
corev1 "k8s.io/api/core/v1"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
2223

2324
cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
2425
"sigs.k8s.io/cluster-api/errors"
@@ -114,6 +115,10 @@ type KubeadmControlPlaneStatus struct {
114115
// ObservedGeneration is the latest generation observed by the controller.
115116
// +optional
116117
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
118+
119+
// Conditions defines current service state of the KubeadmControlPlane.
120+
// +optional
121+
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
117122
}
118123

119124
// +kubebuilder:object:root=true
@@ -137,6 +142,14 @@ type KubeadmControlPlane struct {
137142
Status KubeadmControlPlaneStatus `json:"status,omitempty"`
138143
}
139144

145+
func (in *KubeadmControlPlane) GetConditions() clusterv1.Conditions {
146+
return in.Status.Conditions
147+
}
148+
149+
func (in *KubeadmControlPlane) SetConditions(conditions clusterv1.Conditions) {
150+
in.Status.Conditions = conditions
151+
}
152+
140153
// +kubebuilder:object:root=true
141154

142155
// KubeadmControlPlaneList contains a list of KubeadmControlPlane.

controlplane/kubeadm/api/v1alpha3/zz_generated.deepcopy.go

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml

+44
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,50 @@ spec:
10451045
status:
10461046
description: KubeadmControlPlaneStatus defines the observed state of KubeadmControlPlane.
10471047
properties:
1048+
conditions:
1049+
description: Conditions defines current service state of the KubeadmControlPlane.
1050+
items:
1051+
description: Condition defines an observation of a Cluster API resource
1052+
operational state.
1053+
properties:
1054+
lastTransitionTime:
1055+
description: Last time the condition transitioned from one status
1056+
to another. This should be when the underlying condition changed.
1057+
If that is not known, then using the time when the API field
1058+
changed is acceptable.
1059+
format: date-time
1060+
type: string
1061+
message:
1062+
description: A human readable message indicating details about
1063+
the transition. This field may be empty.
1064+
type: string
1065+
reason:
1066+
description: The reason for the condition's last transition
1067+
in CamelCase. The specific API may choose whether or not this
1068+
field is considered a guaranteed API. This field may not be
1069+
empty.
1070+
type: string
1071+
severity:
1072+
description: Severity provides an explicit classification of
1073+
Reason code, so the users or machines can immediately understand
1074+
the current situation and act accordingly. The Severity field
1075+
MUST be set only when Status=False.
1076+
type: string
1077+
status:
1078+
description: Status of the condition, one of True, False, Unknown.
1079+
type: string
1080+
type:
1081+
description: Type of condition in CamelCase or in foo.example.com/CamelCase.
1082+
Many .condition.type values are consistent across resources
1083+
like Available, but because arbitrary conditions can be useful
1084+
(see .node.status.conditions), the ability to deconflict is
1085+
important.
1086+
type: string
1087+
required:
1088+
- status
1089+
- type
1090+
type: object
1091+
type: array
10481092
failureMessage:
10491093
description: ErrorMessage indicates that there is a terminal problem
10501094
reconciling the state, and will be set to a descriptive error message.

controlplane/kubeadm/controllers/controller.go

+35-4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
capierrors "sigs.k8s.io/cluster-api/errors"
5050
"sigs.k8s.io/cluster-api/util"
5151
"sigs.k8s.io/cluster-api/util/annotations"
52+
"sigs.k8s.io/cluster-api/util/conditions"
5253
"sigs.k8s.io/cluster-api/util/patch"
5354
"sigs.k8s.io/cluster-api/util/predicates"
5455
"sigs.k8s.io/cluster-api/util/secret"
@@ -184,6 +185,17 @@ func (r *KubeadmControlPlaneReconciler) Reconcile(req ctrl.Request) (res ctrl.Re
184185
}
185186
}
186187

188+
// Always update the readyCondition.
189+
conditions.SetSummary(kcp,
190+
conditions.WithConditions(
191+
controlplanev1.MachinesSpecUpToDateCondition,
192+
controlplanev1.ResizedCondition,
193+
controlplanev1.MachinesReadyCondition,
194+
controlplanev1.AvailableCondition,
195+
controlplanev1.CertificatesAvailableCondition,
196+
),
197+
)
198+
187199
// Always attempt to Patch the KubeadmControlPlane object and status after each reconciliation.
188200
if err := patchHelper.Patch(ctx, kcp); err != nil {
189201
logger.Error(err, "Failed to patch KubeadmControlPlane")
@@ -229,8 +241,10 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
229241
controllerRef := metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))
230242
if err := certificates.LookupOrGenerate(ctx, r.Client, util.ObjectKey(cluster), *controllerRef); err != nil {
231243
logger.Error(err, "unable to lookup or create cluster certificates")
244+
conditions.MarkFalse(kcp, controlplanev1.CertificatesAvailableCondition, controlplanev1.CertificatesGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
232245
return ctrl.Result{}, err
233246
}
247+
conditions.MarkTrue(kcp, controlplanev1.CertificatesAvailableCondition)
234248

235249
// If ControlPlaneEndpoint is not set, return early
236250
if cluster.Spec.ControlPlaneEndpoint.IsZero() {
@@ -264,11 +278,27 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
264278
}
265279

266280
controlPlane := internal.NewControlPlane(cluster, kcp, ownedMachines)
267-
requireUpgrade := controlPlane.MachinesNeedingUpgrade()
268-
// Upgrade takes precedence over other operations
269-
if len(requireUpgrade) > 0 {
270-
logger.Info("Upgrading Control Plane")
281+
282+
// Aggregate the operational state of all the machines; while aggregating we are adding the
283+
// source ref (reason@machine/name) so the problem can be easily tracked down to its source machine.
284+
conditions.SetAggregate(controlPlane.KCP, controlplanev1.MachinesReadyCondition, ownedMachines.ConditionGetters(), conditions.AddSourceRef())
285+
286+
// Control plane machines rollout due to configuration changes (e.g. upgrades) takes precedence over other operations.
287+
needRollout := controlPlane.MachinesNeedingRollout()
288+
switch {
289+
case len(needRollout) > 0:
290+
logger.Info("Rolling out Control Plane machines")
291+
// NOTE: we are using Status.UpdatedReplicas from the previous reconciliation only to provide a meaningful message
292+
// and this does not influence any reconciliation logic.
293+
conditions.MarkFalse(controlPlane.KCP, controlplanev1.MachinesSpecUpToDateCondition, controlplanev1.RollingUpdateInProgressReason, clusterv1.ConditionSeverityWarning, "Rolling %d replicas with outdated spec (%d replicas up to date)", len(needRollout), kcp.Status.UpdatedReplicas)
271294
return r.upgradeControlPlane(ctx, cluster, kcp, controlPlane)
295+
default:
296+
// make sure last upgrade operation is marked as completed.
297+
// NOTE: we are checking the condition already exists in order to avoid to set this condition at the first
298+
// reconciliation/before a rolling upgrade actually starts.
299+
if conditions.Has(controlPlane.KCP, controlplanev1.MachinesSpecUpToDateCondition) {
300+
conditions.MarkTrue(controlPlane.KCP, controlplanev1.MachinesSpecUpToDateCondition)
301+
}
272302
}
273303

274304
// If we've made it this far, we can assume that all ownedMachines are up to date
@@ -280,6 +310,7 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
280310
case numMachines < desiredReplicas && numMachines == 0:
281311
// Create new Machine w/ init
282312
logger.Info("Initializing control plane", "Desired", desiredReplicas, "Existing", numMachines)
313+
conditions.MarkFalse(controlPlane.KCP, controlplanev1.AvailableCondition, controlplanev1.WaitingForKubeadmInitReason, clusterv1.ConditionSeverityInfo, "")
283314
return r.initializeControlPlane(ctx, cluster, kcp, controlPlane)
284315
// We are scaling up
285316
case numMachines < desiredReplicas && numMachines > 0:

controlplane/kubeadm/controllers/controller_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ import (
4343
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
4444
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/hash"
4545
capierrors "sigs.k8s.io/cluster-api/errors"
46+
"sigs.k8s.io/cluster-api/test/helpers"
4647
"sigs.k8s.io/cluster-api/util"
48+
"sigs.k8s.io/cluster-api/util/conditions"
4749
"sigs.k8s.io/cluster-api/util/kubeconfig"
4850
"sigs.k8s.io/cluster-api/util/secret"
4951
ctrl "sigs.k8s.io/controller-runtime"
5052
"sigs.k8s.io/controller-runtime/pkg/client"
51-
"sigs.k8s.io/controller-runtime/pkg/client/fake"
5253
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
5354
"sigs.k8s.io/controller-runtime/pkg/handler"
5455
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -769,6 +770,7 @@ kubernetesVersion: metav1.16.1`,
769770

770771
g.Expect(kcp.Status.Selector).NotTo(BeEmpty())
771772
g.Expect(kcp.Status.Replicas).To(BeEquivalentTo(1))
773+
g.Expect(conditions.IsFalse(kcp, controlplanev1.AvailableCondition)).To(BeTrue())
772774

773775
s, err := secret.GetFromNamespacedName(context.Background(), fakeClient, client.ObjectKey{Namespace: "test", Name: "foo"}, secret.ClusterCA)
774776
g.Expect(err).NotTo(HaveOccurred())
@@ -1193,7 +1195,7 @@ func newFakeClient(g *WithT, initObjs ...runtime.Object) client.Client {
11931195
g.Expect(controlplanev1.AddToScheme(scheme.Scheme)).To(Succeed())
11941196
return &fakeClient{
11951197
startTime: time.Now(),
1196-
Client: fake.NewFakeClientWithScheme(scheme.Scheme, initObjs...),
1198+
Client: helpers.NewFakeClientWithScheme(scheme.Scheme, initObjs...),
11971199
}
11981200
}
11991201

controlplane/kubeadm/controllers/scale.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(
146146

147147
func selectMachineForScaleDown(controlPlane *internal.ControlPlane) (*clusterv1.Machine, error) {
148148
machines := controlPlane.Machines
149-
if needingUpgrade := controlPlane.MachinesNeedingUpgrade(); needingUpgrade.Len() > 0 {
149+
if needingUpgrade := controlPlane.MachinesNeedingRollout(); needingUpgrade.Len() > 0 {
150150
machines = needingUpgrade
151151
}
152152
return controlPlane.MachineInFailureDomainWithMostMachines(machines)

controlplane/kubeadm/controllers/scale_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
184184
endMachines := internal.NewFilterableMachineCollectionFromMachineList(controlPlaneMachines)
185185
for _, m := range endMachines {
186186
bm, ok := beforeMachines[m.Name]
187+
bm.SetResourceVersion("1")
187188
g.Expect(ok).To(BeTrue())
188189
g.Expect(m).To(Equal(bm))
189190
}

controlplane/kubeadm/controllers/status.go

+20
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/hash"
2626
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/machinefilters"
2727
"sigs.k8s.io/cluster-api/util"
28+
"sigs.k8s.io/cluster-api/util/conditions"
2829
)
2930

3031
// updateStatus is called after every reconcilitation loop in a defer statement to always make sure we have the
@@ -44,12 +45,30 @@ func (r *KubeadmControlPlaneReconciler) updateStatus(ctx context.Context, kcp *c
4445
kcp.Status.UpdatedReplicas = int32(len(currentMachines))
4546

4647
replicas := int32(len(ownedMachines))
48+
desiredReplicas := *kcp.Spec.Replicas
4749

4850
// set basic data that does not require interacting with the workload cluster
4951
kcp.Status.Replicas = replicas
5052
kcp.Status.ReadyReplicas = 0
5153
kcp.Status.UnavailableReplicas = replicas
5254

55+
switch {
56+
// We are scaling up
57+
case replicas < desiredReplicas:
58+
conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingUpReason, clusterv1.ConditionSeverityWarning, "Scaling up to %d replicas (actual %d)", desiredReplicas, replicas)
59+
// We are scaling down
60+
case replicas > desiredReplicas:
61+
conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingDownReason, clusterv1.ConditionSeverityWarning, "Scaling down to %d replicas (actual %d)", desiredReplicas, replicas)
62+
default:
63+
// make sure last resize operation is marked as completed.
64+
// NOTE: we are checking the number of machines ready so we report resize completed only when the machines
65+
// are actually provisioned (vs reporting completed immediately after the last machine object is created).
66+
readyMachines := ownedMachines.Filter(machinefilters.IsReady())
67+
if int32(len(readyMachines)) == replicas {
68+
conditions.MarkTrue(kcp, controlplanev1.ResizedCondition)
69+
}
70+
}
71+
5372
// Return early if the deletion timestamp is set, we don't want to try to connect to the workload cluster.
5473
if !kcp.DeletionTimestamp.IsZero() {
5574
return nil
@@ -69,6 +88,7 @@ func (r *KubeadmControlPlaneReconciler) updateStatus(ctx context.Context, kcp *c
6988
// This only gets initialized once and does not change if the kubeadm config map goes away.
7089
if status.HasKubeadmConfig {
7190
kcp.Status.Initialized = true
91+
conditions.MarkTrue(kcp, controlplanev1.AvailableCondition)
7292
}
7393

7494
if kcp.Status.ReadyReplicas > 0 {

controlplane/kubeadm/controllers/status_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
3333
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
3434
"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
35+
"sigs.k8s.io/cluster-api/util/conditions"
3536
"sigs.k8s.io/controller-runtime/pkg/log"
3637
)
3738

@@ -197,6 +198,7 @@ func TestKubeadmControlPlaneReconciler_updateStatusAllMachinesReady(t *testing.T
197198
g.Expect(kcp.Status.FailureMessage).To(BeNil())
198199
g.Expect(kcp.Status.FailureReason).To(BeEquivalentTo(""))
199200
g.Expect(kcp.Status.Initialized).To(BeTrue())
201+
g.Expect(conditions.IsTrue(kcp, controlplanev1.AvailableCondition)).To(BeTrue())
200202
g.Expect(kcp.Status.Ready).To(BeTrue())
201203
}
202204

controlplane/kubeadm/internal/control_plane.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,11 @@ func (c *ControlPlane) EtcdImageData() (string, string) {
9393
return "", ""
9494
}
9595

96-
// MachinesNeedingUpgrade return a list of machines that need to be upgraded.
97-
func (c *ControlPlane) MachinesNeedingUpgrade() FilterableMachineCollection {
96+
// MachinesNeedingRollout return a list of machines that need to be rolled out due to configuration changes.
97+
//
98+
// NOTE: Expiration of the spec.UpgradeAfter value forces inclusion of all the machines in this set even if
99+
// no changes have been made to the KubeadmControlPlane.
100+
func (c *ControlPlane) MachinesNeedingRollout() FilterableMachineCollection {
98101
now := metav1.Now()
99102
if c.KCP.Spec.UpgradeAfter != nil && c.KCP.Spec.UpgradeAfter.Before(&now) {
100103
return c.Machines.AnyFilter(

0 commit comments

Comments
 (0)