-
Notifications
You must be signed in to change notification settings - Fork 39.8k
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
implements StatefulSet update #46669
Conversation
api/swagger-spec/apps_v1beta1.json
Outdated
"updatedReplicas": { | ||
"type": "integer", | ||
"format": "int32", | ||
"description": "updatedReplicas is the number of replicas" |
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.
nit: at updatedRevision
api/swagger-spec/apps_v1beta1.json
Outdated
}, | ||
"updateRevision": { | ||
"type": "string", | ||
"description": "updateRevision is the revision the StatefulSet is attempting to roll out" |
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.
nit: comma after revision
as in updateRevision is the revision, the StatefulSet is ...
@@ -6452,6 +6535,47 @@ <h3 id="_v1_configmapenvsource">v1.ConfigMapEnvSource</h3> | |||
|
|||
</div> | |||
<div class="sect2"> | |||
<h3 id="_v1beta1_statefulsetupdatestrategy">v1beta1.StatefulSetUpdateStrategy</h3> | |||
<div class="paragraph"> | |||
<p>StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. It includes any additional parameters necessary to preform the update for the indicated strategy.</p> |
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.
nit: perform
pkg/apis/apps/types.go
Outdated
// PartitionStatefulSetStrategyType indicates that updates will only be | ||
// applied to a partition of the StatefulSet. This is useful for canaries | ||
// and phased roll outs. When a scale operation is performed with this | ||
// strategy, new Pods will be created from the updated specification. |
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.
updated specification means updateRevision here, right ?
pkg/apis/apps/types.go
Outdated
// tracking and ordered rolling restarts are disabled. Pods are recreated | ||
// from the StatefulSetSpec when they are manually deleted. When a scale | ||
// operation is performed with this strategy, new Pods will be created | ||
// from the current specification. |
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.
current specification means currentRevision here , right ?
pkg/apis/apps/types.go
Outdated
// from the StatefulSetSpec when they are manually deleted. When a scale | ||
// operation is performed with this strategy, new Pods will be created | ||
// from the current specification. | ||
OnDeleteStatefulSetStrategyType = "OnDelete" |
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.
is this the default strategy ?
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.
no. you should look at defaults.go.
type PartitionStatefulSetStrategy struct { | ||
// Ordinal indicates the ordinal at which the StatefulSet should be | ||
// partitioned. | ||
Ordinal int32 |
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.
Can this be omitted in the Partitioned case to assume a default of one ? In which case,this should be a pointer to distringuish between the default case .
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 think a default one one assumption will be helpful
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.
RollingUpdate is default
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 disagree with defaulting the partition. The user has to declare that they want to partition at a particular point.
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 default partition size of 1 is the canary case. Most people would have this use case. Without it, it means specifying one extra param for partitioning in the most common case. It seems like a reasonable default with no surprises .
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 disagree that canaries are the most common case. I don't think specifying the fraction of a staged roll out is an undo burden.
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 think we should require partition to be specified. The default doesn't add anything for the machine operated use case which is going to be much more common
pkg/apis/apps/types.go
Outdated
|
||
// updateStrategy indicates the StatefulSetUpdateStrategy that will be | ||
// employed to update Pods in the StatefulSet when a revision is made to | ||
// Template or VolumeClaimsTemplate. |
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.
Can we remove the VolumeClaimsTemplate from here, since we dont support the update to it.
pkg/apis/apps/types.go
Outdated
// currentReplicas is the number of replicas at the CurrentRevision | ||
CurrentReplicas int32 | ||
|
||
// updatedReplicas is the number of replicas |
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.
nit: at the updateRevision
} | ||
if in.RevisionHistoryLimit != nil { | ||
out.RevisionHistoryLimit = new(int32) | ||
*out.RevisionHistoryLimit = *in.RevisionHistoryLimit |
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.
and if its nil, set it to 2 ?
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.
defaults.go not conversions
@@ -140,8 +147,37 @@ func Convert_apps_StatefulSetSpec_To_v1beta1_StatefulSetSpec(in *apps.StatefulSe | |||
} else { | |||
out.VolumeClaimTemplates = nil | |||
} | |||
if in.RevisionHistoryLimit != nil { | |||
out.RevisionHistoryLimit = new(int32) | |||
*out.RevisionHistoryLimit = *in.RevisionHistoryLimit |
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.
in nil case, set it to 2
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.
no that is done in defaults not conversions
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: "foo"}, | ||
}, | ||
}, | ||
"empty parition": { |
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.
nit: partition
t.Errorf("Expected 0 events for successful status update %d", eventCount) | ||
} | ||
} | ||
|
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.
has all this content moved elsewhere ?
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 status tests are in stateful_set_status_updater.go
@@ -305,6 +308,32 @@ func (ssc *StatefulSetController) getPodsForStatefulSet(set *apps.StatefulSet, s | |||
return cm.ClaimPods(pods, filter) | |||
} | |||
|
|||
// adoptOrphanRevisions adopts any orphaned ControllerRevisions matched by set's Selector. | |||
func (ssc *StatefulSetController) adoptOrphanRevisions(set *apps.StatefulSet) error { | |||
revisions, err := ssc.control.ListRevisions(set) |
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.
is this listing of revisions based on the label on those revisions ?
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.
Yes the revisions are constructed with the StatefulSet's selector
} | ||
} | ||
if hasOrphans { | ||
fresh, err := ssc.kubeClient.AppsV1beta1().StatefulSets(set.Namespace).Get(set.Name, metav1.GetOptions{}) |
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.
curious why cant we use the set.UID instead of querying again ?
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.
We have to refresh the Object to ensure that it hasn't been deleted ensure that we don't patch a ControllerRevision to be owned by a cached deleted/(deleted and recreated) StatefulSet.
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.
hmm how can you be sure of that ?What if its gets deleted after your query ?
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 suppose in theory it could. In practice we have seen issues where a stale cache causes invalid adoption. This pattern is used in all controllers for orphan Pods.
if pod.Labels == nil { | ||
pod.Labels = make(map[string]string) | ||
} | ||
pod.Labels[apps.StatefulSetRevisionLabel] = revision |
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.
check if revision is empty ?
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.
It can't be empty based on the call path. I don't see a need for a check.
return patch, err | ||
} | ||
|
||
// newRevision creates a new ControllerRevision containing a patch that can convert a StatefulSet to an equivalent state to set. |
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.
nit: the last part of the sentence is not clear convert a StatefulSet to an equivalent state to set.
return history.NewControllerRevision(set, | ||
controllerKind, | ||
selector, | ||
runtime.RawExtension{Raw: patch}, |
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.
this is confusing and at the same time interesting to me as a concept :-) I have not seen this pattern before, but it seems like what you are doing is keeping a strategic merge patch in each revision history , so that any time we want to restore the StatefulSet to that revision, you just use the ready to apply patch from the Raw field to restore it. I think this is worth some more comment
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 added commentary to that effect above the getPatch and newRevision methods.
return nil, err | ||
} | ||
clone := obj.(*apps.StatefulSet) | ||
patched, err := strategicpatch.StrategicMergePatch([]byte(runtime.EncodeOrDie(patchCodec, clone)), revision.Data.Raw, clone) |
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.
check for err here ?
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 added a check under strategicpatch
If you mean check that the clone casts correctly, we generally do not do that when deep copying from using Scheme.
if err != nil { | ||
return nil, err | ||
} | ||
return clone, 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.
return clone, nil
func nextRevision(revisions []*apps.ControllerRevision) int64 { | ||
if count := len(revisions); count <= 0 { | ||
return 1 | ||
} else { |
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.
remove the else
t.Fatal(err) | ||
} | ||
set.Spec.Template.Spec.Containers[0].Name = "foo" | ||
restored, err := applyRevision(set, revision) |
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.
nit: restoredSet ?
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !history.EqualRevision(revision, restoredRevision) { |
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.
do you ignore the revision number when comparing the revisions ?
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.
Yes the revisions are only compared by hash and by contents. Two revisions with different ".Revisions" can be equal using this comparison.
pkg/kubectl/history.go
Outdated
if err != nil { | ||
return "", fmt.Errorf("failed to retrieve statefulset %s", err) | ||
} | ||
revisions, err := h.c.Apps().ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: sts.Spec.Selector.String()}) |
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.
will this sometimes also report orphaned revisions ? To avoid that, we should further validate if the ownerReferences are for this set
@@ -723,6 +723,13 @@ func appsFuncs(t apitesting.TestingCommon) []interface{} { | |||
if len(s.Spec.PodManagementPolicy) == 0 { | |||
s.Spec.PodManagementPolicy = apps.OrderedReadyPodManagement | |||
} | |||
if len(s.Spec.UpdateStrategy.Type) == 0 { | |||
s.Spec.UpdateStrategy.Type = apps.RollingUpdateStatefulSetStrategyType |
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.
Should you also update this one to OnDelete?
), | ||
recorder), | ||
NewRealStatefulSetStatusUpdater(kubeClient, setInformer.Lister()), | ||
history.NewHistory(kubeClient, revInformer.Lister()), | ||
), | ||
pvcListerSynced: pvcInformer.Informer().HasSynced, |
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.
Since you are using the controllerRevision cache to list CRs, you should wait for it to be synced before starting the sts controller. Means you need a crListerSynced field which you then pass in controller.WaitForCacheSync (L150).
/approve no-issue (there is issue from features repo, but bot still doesn't understand it). |
/approve no-issue |
|
1af3cf1
to
6b41b34
Compare
6b41b34
to
bec6c55
Compare
Implements history utilities for ControllerRevision in the controller/history package StatefulSetStatus now has additional fields for consistency with DaemonSet and Deployment StatefulSetStatus.Replicas now represents the current number of createdPods and StatefulSetStatus.ReadyReplicas is the current number of ready Pods
bec6c55
to
fabbcd6
Compare
@kow3ns: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
fabbcd6
to
1a784ef
Compare
return nil, err | ||
} | ||
var raw map[string]interface{} | ||
json.Unmarshal([]byte(str), &raw) |
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.
Would you want to check the returned error?
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously | ||
// recorded patches. | ||
func getPatch(set *apps.StatefulSet) ([]byte, error) { | ||
str, err := runtime.Encode(patchCodec, set) |
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.
Remove patchCodec
and use json.Marshal
here instead, csi team wants to avoid using api.Codecs as much as possible, see #47075 (comment)
LGTM, comments can be fixed in follow-up PRs |
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: janetkuo, kargakis, kow3ns, smarterclayton, wojtek-t Associated issue: 188 The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
Automatic merge from submit-queue (batch tested with PRs 46235, 44786, 46833, 46756, 46669) |
What this PR does / why we need it:
kubernetes/enhancements#188
Special notes for your reviewer:
Release note: