Skip to content
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

Merged
merged 3 commits into from
Jun 7, 2017

Conversation

kow3ns
Copy link
Member

@kow3ns kow3ns commented May 31, 2017

What this PR does / why we need it:

  1. Implements rolling update for StatefulSets
  2. Implements controller history for StatefulSets.
  3. Makes StatefulSet status reporting consistent with DaemonSet and ReplicaSet.

kubernetes/enhancements#188

Special notes for your reviewer:

Release note:

Implements rolling update for StatefulSets. Updates can be performed using the RollingUpdate, Paritioned, or OnDelete strategies. OnDelete implements the manual behavior from 1.6. status now tracks 
replicas, readyReplicas, currentReplicas, and updatedReplicas. The semantics of replicas is now consistent with DaemonSet and ReplicaSet, and readyReplicas has the semantics that replicas did prior to this release.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label May 31, 2017
@k8s-github-robot k8s-github-robot added kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels May 31, 2017
"updatedReplicas": {
"type": "integer",
"format": "int32",
"description": "updatedReplicas is the number of replicas"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: at updatedRevision

},
"updateRevision": {
"type": "string",
"description": "updateRevision is the revision the StatefulSet is attempting to roll out"

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>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perform

// 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.

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 ?

// 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.

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 ?

// 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"

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 ?

Copy link
Member Author

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

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 .

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RollingUpdate is default

Copy link
Member Author

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.

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 .

Copy link
Member Author

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.

Copy link
Contributor

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


// updateStrategy indicates the StatefulSetUpdateStrategy that will be
// employed to update Pods in the StatefulSet when a revision is made to
// Template or VolumeClaimsTemplate.

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.

// currentReplicas is the number of replicas at the CurrentRevision
CurrentReplicas int32

// updatedReplicas is the number of replicas

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

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 ?

Copy link
Member Author

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

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

Copy link
Member Author

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": {

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)
}
}

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 ?

Copy link
Member Author

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)

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 ?

Copy link
Member Author

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{})

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 ?

Copy link
Member Author

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.

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 ?

Copy link
Member Author

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

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 ?

Copy link
Member Author

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.

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},

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

Copy link
Member Author

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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check for err here ?

Copy link
Member Author

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

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 {

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)

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) {

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 ?

Copy link
Member Author

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.

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()})

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
Copy link
Contributor

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,
Copy link
Contributor

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).

@wojtek-t
Copy link
Member

wojtek-t commented Jun 6, 2017

/approve no-issue (there is issue from features repo, but bot still doesn't understand it).

@wojtek-t
Copy link
Member

wojtek-t commented Jun 6, 2017

/approve no-issue

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 6, 2017
@0xmichalis
Copy link
Contributor

0xmichalis commented Jun 6, 2017

there is issue from features repo, but bot still doesn't understand it

FWIW kubernetes/enhancements#188

@k8s-github-robot k8s-github-robot removed lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Jun 6, 2017
@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 6, 2017
Kenneth Owens added 2 commits June 6, 2017 12:00
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
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 6, 2017
@k8s-ci-robot
Copy link
Contributor

k8s-ci-robot commented Jun 6, 2017

@kow3ns: The following test failed, say /retest to rerun them all:

Test name Commit Details Rerun command
pull-kubernetes-federation-e2e-gce c560debd7c521c50516689795c1e0ca554e66008 link @k8s-bot pull-kubernetes-federation-e2e-gce test this

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.

return nil, err
}
var raw map[string]interface{}
json.Unmarshal([]byte(str), &raw)
Copy link
Member

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)
Copy link
Member

@janetkuo janetkuo Jun 6, 2017

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)

@janetkuo
Copy link
Member

janetkuo commented Jun 6, 2017

LGTM, comments can be fixed in follow-up PRs

@janetkuo
Copy link
Member

janetkuo commented Jun 6, 2017

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 6, 2017
@k8s-github-robot
Copy link

[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 /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 46235, 44786, 46833, 46756, 46669)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.