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

Switch Deployments to new hashing algo w/ collision avoidance mechanism #44774

Merged
merged 5 commits into from
May 25, 2017
Merged

Switch Deployments to new hashing algo w/ collision avoidance mechanism #44774

merged 5 commits into from
May 25, 2017

Conversation

0xmichalis
Copy link
Contributor

@0xmichalis 0xmichalis commented Apr 21, 2017

Implements kubernetes/community#477

@kubernetes/sig-apps-api-reviews @kubernetes/sig-apps-pr-reviews

Fixes #29735
Fixes #43948

Deployments are updated to use (1) a more stable hashing algorithm (fnv) than the previous one (adler) and (2) a hashing collision avoidance mechanism that will ensure new rollouts will not block on hashing collisions anymore.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Apr 21, 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-label-needed labels Apr 21, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@k8s-github-robot k8s-github-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed release-note-label-needed labels Apr 21, 2017

// Monotonically increasing counter that tracks hash collisions for
// the Deployment. Used as a collision avoidance mechanism by the
// Deployment controller.
Copy link
Member

Choose a reason for hiding this comment

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

I think we need +optional here
Ref #34860, cc @mehdy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we also need this tag in the internal types?

Copy link
Member

Choose a reason for hiding this comment

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

Yes we do

// Add uniquifier in the hash if it exists.
if uniquifier != nil {
uniquifierBytes := make([]byte, 8)
binary.PutVarint(uniquifierBytes, *uniquifier)
Copy link
Member

Choose a reason for hiding this comment

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

It'll panic with a larger uniquifier. Use binary.LittleEndian.PutUint64 instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can an int64 integer be larger than 8 bytes?

Copy link
Contributor Author

@0xmichalis 0xmichalis Apr 25, 2017

Choose a reason for hiding this comment

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

On a side note, I still need to unit test this function.

Copy link
Member

Choose a reason for hiding this comment

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

I did some experiment https://play.golang.org/p/3DKSA8wYup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. Updated and also added a simple unit test.

@janetkuo
Copy link
Member

We sync status by calling calculateStatus first, which basically wipes out whatever is set in Deployment status.uniquifier

@0xmichalis
Copy link
Contributor Author

We sync status by calling calculateStatus first, which basically wipes out whatever is set in Deployment status.uniquifier

Yeah, on my list to fix.

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 26, 2017
@k8s-github-robot k8s-github-robot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Apr 28, 2017
@0xmichalis
Copy link
Contributor Author

@janetkuo @kubernetes/sig-apps-pr-reviews this is ready for reviews. I will follow up with unit tests for getNewReplicaSet in a different PR because I want to break down that function into pieces before unit-testing it and I don't want to make reviews harder here.

@k8s-ci-robot k8s-ci-robot added the sig/apps Categorizes an issue or PR as relevant to SIG Apps. label Apr 29, 2017
@k8s-github-robot k8s-github-robot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Apr 29, 2017
@0xmichalis
Copy link
Contributor Author

@lukaszo can you also have a look?

// the Deployment. Used as a collision avoidance mechanism by the
// Deployment controller.
// +optional
Uniquifier *int64 `json:"uniquifier,omitempty" protobuf:"varint,8,opt,name=uniquifier"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we keep this in status? It seems little strange.
Also, who can update status? Only controller?

Copy link
Member

@janetkuo janetkuo May 9, 2017

Choose a reason for hiding this comment

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

Putting it in status was discussed in the proposal for hash collision. We don't want users to set it or even think about it.

API server can update status as well (if we implement it).

@@ -346,6 +346,9 @@ func ValidateDeploymentStatus(status *extensions.DeploymentStatus, fldPath *fiel
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
if status.Uniquifier != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a test for this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@janetkuo
Copy link
Member

janetkuo commented May 9, 2017

Sorry, didn't have time to review this further. Taking a look now.

framework.Logf("cannot get deployment %q: %v", deploymentName, err)
return false, nil
}
framework.Logf("deployment status: %v", d.Status)
Copy link
Member

@janetkuo janetkuo May 9, 2017

Choose a reason for hiding this comment

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

uniquifier is a pointer and this won't print its int64 value for debugging. It's easier to debug if field names are printed too.

Use framework.Logf(spew.Sprintf("deployment status: %#v", d.Status)) to address both. You'll need to import "github.com/davecgh/go-spew/spew"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -346,6 +346,9 @@ func ValidateDeploymentStatus(status *extensions.DeploymentStatus, fldPath *fiel
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
if status.Uniquifier != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(*status.Uniquifier, fldPath.Child("uniquifier"))...)
}
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we want something like validating that uniquifier won't decrease and will only be increased by 1 at a time in ValidateDeploymentStatusUpdate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't feel strong for tightening this to check that the field increases by 1 - either way if we stabilize to a value, we won't care. Added validation that it won't decrease.

Copy link
Member

Choose a reason for hiding this comment

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

My worry is that some stale Deployment status in cache may cause uniquifier to decrease or increase too much. We need the value to be stable for idempotent RS names. This can be a follow-up.

@@ -3646,7 +3648,7 @@ type updateRsFunc func(d *extensions.ReplicaSet)
func UpdateReplicaSetWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateRsFunc) (*extensions.ReplicaSet, error) {
var rs *extensions.ReplicaSet
var updateErr error
pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
pollErr := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

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

(optional) create const for poll interval and timeout so that we have consistent value in all func UpdateXxxWithRetries

Copy link
Contributor Author

@0xmichalis 0xmichalis May 10, 2017

Choose a reason for hiding this comment

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

I prefer to leave this change for a follow-up

@0xmichalis
Copy link
Contributor Author

I will add the appropriate labels at 12:00 CET tomorrow if there are no comments until then.

@0xmichalis
Copy link
Contributor Author

@k8s-bot pull-kubernetes-e2e-gce-etcd3 test this

@mikedanese
Copy link
Member

I've seen "uniquifier" referred to as "fingerprint" in other apis. Is there significance to the monotonically incrementing or is that an implementation detail? Is it guaranteed dense?

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 24, 2017
@0xmichalis
Copy link
Contributor Author

I've seen "uniquifier" referred to as "fingerprint" in other apis. Is there significance to the monotonically incrementing or is that an implementation detail? Is it guaranteed dense?

There is significance in that it's incrementing so in case of a collision we are essenitally changing the algo used to compute a hash and it should be a new one. It is used as a counter of the actual collisions.

@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 24, 2017
@wanghaoran1988
Copy link
Contributor

@k8s-bot pull-kubernetes-federation-e2e-gce test this

@lavalamp
Copy link
Member

I vote for collisionCounter and against uniquifier which is super unintuitive.

revision, generation, historyIndex all are better. fingerprint and etag both seem like the wrong concept.

@janetkuo
Copy link
Member

collisionCounter is fine. revision, generation, historyIndex aren't appropriate because this is not a field for version-like information but just a way to avoid name collisions when hash collision happens.

@bgrant0607
Copy link
Member

@Kargakis

PSA: I don't receive ANY github notifications now. If something needs my attention, please resort to other means, such as slack or email.

Clayton asked for my opinion via slack. I said I was ok with collisionCount(er).

The fact that it is sequential is not really material, nor strongly guaranteed, since there are scenarios where it could double count or reset, but "count" makes it easy to understand what it's doing. I oppose any name containing "index", "revision", or "generation", that might imply stronger semantics, be confused with other fields of the same names, or otherwise lead to an expectation that the value would appear in labels, annotations, or resource names.

@bgrant0607
Copy link
Member

Please remove the word "monotonically" from the API field descriptions.

@janetkuo janetkuo mentioned this pull request May 25, 2017
16 tasks
@bgrant0607
Copy link
Member

Also: @ncdc suggested hashCollisions, which isn't bad, though I'm not 100% committed to the idea of keeping a hash forever. Just collisions is a bit too obscure. resourceCollisions could potentially work, but collisionCount is good enough to call it done and move on.

@thockin
Copy link
Member

thockin commented May 25, 2017

I'm concerned that this is kept in Status, but doesn't really pass the litmus test of "could be rebuilt by observation",

@0xmichalis
Copy link
Contributor Author

I'm concerned that this is kept in Status, but doesn't really pass the litmus test of "could be rebuilt by observation",

I see your point and we could potentially annotate ReplicaSets with the collisionCounter they were created and reflect the highest adopted value back to the Deployment status but eventually this specific field doesn't need to be rebuilt, mostly due to our defaults (new Deployments in the apps group are defaulted to retaining 3 old ReplicaSets, coupled with fnv it's unlike that we will ever have a collision again).

@0xmichalis
Copy link
Contributor Author

Updated to use collisionCount and fixed the API comment.

@0xmichalis 0xmichalis added lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels May 25, 2017
@0xmichalis
Copy link
Contributor Author

Approving assuming consensus on the name. Adding lgtm based on prior lgtm.

@k8s-github-robot
Copy link

Automatic merge from submit-queue

@k8s-github-robot k8s-github-robot merged commit 26d7ee0 into kubernetes:master May 25, 2017
@0xmichalis 0xmichalis deleted the uniquifier branch May 25, 2017 13:41
k8s-github-robot pushed a commit that referenced this pull request Jun 3, 2017
Automatic merge from submit-queue

Implement Daemonset history

~Depends on #45867 (the 1st commit, ignore it when reviewing)~ (already merged)

Ref kubernetes/community#527 and kubernetes/community#594

@kubernetes/sig-apps-api-reviews @kubernetes/sig-apps-pr-reviews @erictune @kow3ns @lukaszo @Kargakis 

---

TODOs:
- [x] API changes
  - [x] (maybe) Remove rollback subresource if we decide to do client-side rollback 
- [x] deployment controller 
  - [x] controller revision
    - [x] owner ref (claim & adoption)
    - [x] history reconstruct (put revision number, hash collision avoidance)
    - [x] de-dup history and relabel pods
    - [x] compare ds template with history 
  - [x] hash labels (put it in controller revision, pods, and maybe deployment)
  - [x] clean up old history 
  - [x] Rename status.uniquifier when we reach consensus in #44774 
- [x] e2e tests 
- [x] unit tests 
  - [x] daemoncontroller_test.go 
  - [x] update_test.go 
  - [x] ~(maybe) storage_test.go // if we do server side rollback~

kubectl part is in #46144

--- 

**Release note**:

```release-note
```
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. sig/apps Categorizes an issue or PR as relevant to SIG Apps. 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.