-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pod Disruption Budget implementation #900
base: master
Are you sure you want to change the base?
Conversation
a1c32e5
to
1eb8ab0
Compare
pdbSpec := hnp.GetPodDisruptionBudget() | ||
r.Log.Info("Entering PDB enforcement check", "nodePool", hnp.GetNodePoolName(), "pdbSpec", pdbSpec, "pdbSpec.Enabled", pdbSpec != nil && pdbSpec.Enabled) | ||
if pdbSpec != nil && pdbSpec.Enabled { | ||
pdbName := fmt.Sprintf("%s-%s-pdb", hc.Name, hnp.GetNodePoolName()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to explicitly add hc.Name
yourself, unless you want hc.Name
added twice in the name.
GetNodePoolName()
returns return strings.Join([]string{hnp.GetClusterName(), hnp.nodePoolName}, "-")
, and GetClusterName()
returns hnp.clusterName
which is set to hc.Name
. So essentially, we're already adding hc.Name
to the prefix returned by GetNodePoolName()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of constructing pdbName
here, perhaps you can instead just use GetPodDisruptionBudgetName()
which already construct the full PDB name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good observation. However, fwiw I haven't finished refactoring the code
|
||
r.Log.Info("Fetching PDB", | ||
"pdbName", pdbName, | ||
"namespace", hnp.GetNamespace()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to add the namespace to the logs yourself as we initialize the logger in the start of the Reconcile()
function with the namespace: https://github.com/humio/humio-operator/blob/master/controllers/humiocluster_controller.go#L91-L96
With this, it means all logs using r.Log
would include both the name of the HumioCluster
resource as well as the namespace of where it is located.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the info. Actually, I was unsure about the log message structure that and was going to ask about it. I will keep it simple as r.Log.Info("Fetching PDB", "pdbName", pdbName)
if k8serrors.IsNotFound(err) { | ||
r.Log.Info("PDB not found for node pool, proceeding without PDB check", | ||
"pdb", pdbName, | ||
"namespace", hnp.GetNamespace(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same thing is true here where you don't need to explicitly add namespace to the log events, since the logger already includes this to all events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
@@ -1922,6 +1928,64 @@ func (r *HumioClusterReconciler) ensureMismatchedPodsAreDeleted(ctx context.Cont | |||
|
|||
podsForDeletion := desiredLifecycleState.podsToBeReplaced | |||
|
|||
pdbSpec := hnp.GetPodDisruptionBudget() | |||
r.Log.Info("Entering PDB enforcement check", "nodePool", hnp.GetNodePoolName(), "pdbSpec", pdbSpec, "pdbSpec.Enabled", pdbSpec != nil && pdbSpec.Enabled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, we don't really need to add anything to ensureMismatchedPodsAreDeleted
, since ensureMismatchedPodsAreDeleted
is handling the case of pod replacements due to config changes and version changes/upgrades.
The case for adding PDB is more about ensuring components "outside the humio-operator" e.g. k8s worker node drain won't bring more pods/instances down than the configured amount. Config changes and version changes/upgrades is already controlled through the configurable update strategy (see https://github.com/humio/humio-operator/blob/master/api/v1alpha1/humiocluster_types.go#L302) and this update strategy logic already included in the current master version of ensureMismatchedPodsAreDeleted
.
Let me know if I missed something. Thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes total sense you explained. So what I understand what you said is that ensureMismatchedPodsAreDeleted
don't require explicit PDB handling because the function already coordinates pod replacements using:
- The cluster's update strategy (rolling updates)
- Max unavailable pods calculation
- Readiness checks
- Version/config mismatch detection
And PDB's are primarily for external kubernetes-level disruption, like node drains or maintenance, rather than operator-controlled replacements.
Said that, I have removed the PDB section from ensureMismatchedPodsAreDeleted
if !hc.DeletionTimestamp.IsZero() { | ||
if err := r.handlePDBFinalizers(ctx, hc); err != nil { | ||
return r.updateStatus(ctx, r.Client.Status(), hc, statusOptions(). | ||
withMessage(fmt.Sprintf("failed to handle PDB finalizers: %s", err))) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe handlePDBFinalizers
can be removed given that all it does is remove the finalizer.
As long as ownerReferences
on the PDB object is configured to point at the HumioCluster
object, then the PDB's are garbage collected once the HumioCluster
object is deleted, see https://kubernetes.io/docs/concepts/architecture/garbage-collection/#:~:text=Cascading%20deletion,a%20process%20called%20cascading%20deletion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for correcting me. Conditional statement removed.
|
||
if err := r.cleanupOrphanedPDBs(ctx, hc, &humioNodePools); err != nil { | ||
return r.updateStatus(ctx, r.Client.Status(), hc, statusOptions(). | ||
withMessage(err.Error())) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same comment goes here as https://github.com/humio/humio-operator/pull/900/files#r1930459818
Cleanup of PDB objects should already be garbage collected by k8s out of the box as long as owner references is properly set up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to be extra careful here. Deleted the conditional statement above.
api/v1alpha1/humiocluster_types.go
Outdated
type HumioPodDisruptionBudgetSpec struct { | ||
// +kubebuilder:validation:Type=string | ||
// +kubebuilder:validation:Format=int-or-string | ||
// +kubebuilder:validation:Immutable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the idea behind making this Immutable
? I don't see why we would not allow users to change the configs after initially configuring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have marked those fields immutable to prevent runtime changes that could break the cluster during updates. However, if the operator can safely handle PDB updates through proper requeuing and rolling update coordination as you previously said, then we could safely remove the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am removing the flag "kubebuilder:validation:Immutable".
api/v1alpha1/humiocluster_types.go
Outdated
|
||
// +kubebuilder:validation:Type=string | ||
// +kubebuilder:validation:Format=int-or-string | ||
// +kubebuilder:validation:Immutable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the idea behind making this Immutable
? I don't see why we would not allow users to change the configs after initially configuring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same answer as above
|
||
desiredPDB, err := r.constructPDB(hc, hnp, pdbSpec) | ||
if err != nil { | ||
r.Log.Error(err, "failed to construct PDB", "pdbName", hnp.GetPodDisruptionBudgetName(), "namespace", hnp.GetNamespace()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more place where you could skip adding the namespace yourself due to the logger already including this in all events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed "namespace" form the Log statement.
Labels: kubernetes.LabelsForHumio(hc.Name), // Add node pool name label if needed | ||
Finalizers: []string{HumioProtectionFinalizer}, | ||
OwnerReferences: []metav1.OwnerReference{ | ||
*metav1.NewControllerRef(hc, humiov1alpha1.GroupVersion.WithKind("HumioCluster")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I wasn't aware of this being a valid way to configure the owner reference.
The other places we use the controllerutil.SetControllerReference()
function to set the ownerReference. Here's an example of this:
if err := controllerutil.SetControllerReference(hc, hbt, r.Scheme()); err != nil { |
Do you know which one of these would be the better one to use? Perhaps we should be replacing the use of controllerutil.SetControllerReference()
with using metav1.NewControllerRef()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bringing this up. Had a bit of reflection on this comment. Both approaches are valid but serve different purposes:
controllerutil.SetControllerReference()
is preferred for most cases because:
- Automatically sets GVK (GroupVersionKind)
- Handles cross-namespace ownership prevention - Provides safety checks for CRD scope compatibility
- Maintains consistency with controller-runtime patterns
metav1.NewControllerRef()
is used here because:
- The PDB is explicitly namespaced with the cluster
- Direct control over owner reference creation was needed
- Avoids potential issues with automatic GVK resolution in some contexts.
In this case, using using metav1.NewControllerRef()
directly ensured the owner reference would always have:
apiVersion: core.humio.com/v1alpha1
kind: HumioCluster
Even if the type wasn't fully registered in the scheme. However, since we properly registered types in humiocluster_types.go
, we can safely use controllerutil.SetControllerReference() which provides better validation.
I would say that using controllerutil.SetControllerReference()
would be a better choice really. I will switch the PDB related code to use controllerutil.SetControllerReference()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thank you a lot for the explanation.
|
||
// SemanticPDBsEqual compares two PodDisruptionBudgets and returns true if they are equal | ||
func SemanticPDBsEqual(desired *policyv1.PodDisruptionBudget, current *policyv1.PodDisruptionBudget) bool { | ||
if !equality.Semantic.DeepEqual(desired.Spec.MinAvailable, current.Spec.MinAvailable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Wasn't aware of this equality
package.
I see we have a total of 10 references to reflect.DeepEqual()
various places in the code base on master branch, and maybe we should consider refactoring those to use equality.Semantic.DeepEqual()
instead.
We also have 91 places on master where we use cmp.diff()
. Not all of those actually need the diff so perhaps we could also replace and update those with equality.Semantic.DeepEqual()
.
I don't expect this to be incorporated in this PR though, just wanted to call it out as a possible cleanup we may consider later as I wasn't aware of this equality
package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
II also would like to tackle those changes on a different PR. Thanks for pointing that out
…ogging. A few code refactors
This is a new implementation for Pod Disruption Burget. This implements #104