Keep the deployment when scale to zero.#1320
Keep the deployment when scale to zero.#1320akyyy wants to merge 8 commits intoknative:masterfrom akyyy:keepDeploymentWhenScaleToZero
Conversation
|
This pr contains a subset of changes (keeping the deployment and service) in my other pr #1251. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: akyyy Assign the PR to them by writing The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
|
||
| // teardownK8SResources deletes autoscaler resources, but keeps the revision service and deployment. | ||
| // It is used when the revision serving state is Reserve. | ||
| func (c *Controller) teardownK8SResources(ctx context.Context, rev *v1alpha1.Revision) error { |
There was a problem hiding this comment.
A clearer name would be better here. scaleRevisionResourcesToZero or something. Otherwise it is unclear what this does vs delete does unless you read the func comments.
|
|
||
| func (c *Controller) deleteAutoscalerResources(ctx context.Context, rev *v1alpha1.Revision) error { | ||
| logger := logging.FromContext(ctx) | ||
| if err := c.deleteAutoscalerDeployment(ctx, rev); err != nil { |
There was a problem hiding this comment.
Why not set the replica size to 0 for this one as well? (Keep in mind that if we do that, updating controller code will no longer update any existing revision's autoscaler).
There was a problem hiding this comment.
For revision deployment, the reason to keep it is to unblock new pod creation during existing pod terminating. For autoscaler deployment, I don't see a reason to keep it?
There was a problem hiding this comment.
Keep in mind that if we do that, updating controller code will no longer update any existing revision's autoscaler
@mdemirhan Curious to know why you think changes couldn't be reconciled to update existing autoscaler deployments?
There was a problem hiding this comment.
+1 to setting the autoscaler deployment to 0 as well. I hope that will allow the autoscaler to come back up more quickly and therefore respond more quickly to large traffic spikes when scaled to zero. It would also help avoid a similar 30-second deadzone in scaling from 1-N (e.g. the deployment is being deleted but passes the controller reconciliation loop by existing).
@dprotaso, I think that he means we won't update the single-tenant autoscaler binary if we just scale to zero instead of creating a deployment anew from the latest autoscaler container passed into the controller through commandline args. That's true. But we are in the process of migrating to an always-on multiple-revision autoscaler. So that issue won't be around for long.
There was a problem hiding this comment.
@mdemirhan, @josephburnett You're right. I should keep autoscaler deployment as well. I'll work on that. Thanks!
There was a problem hiding this comment.
@dprotaso I was looking at the current implementation for reconcileAutoscalerDeployment - if there is an existing deployment, it will skip reconcilliation. So, if we don't delete the deployment but just update the replica count of the existing deployment, the autoscaler will never be updated. Currently though, if something scales to zero, autoscaler will get latest bits once activation happens next time.
That being said, I like deterministic behavior and I think current behavior is worse because in case there is an issue with an updated autoscaler, you will see the negative effects of it sometime in the future when N->0->1 happens.
We need a better way to upgrade Knative - ko apply -f config/ is too unpredictable for upgrades. I will file an Issue on this issue.
There was a problem hiding this comment.
@mdemirhan IMO if we update our sidecars (because of a controller update), we should update deployments during the normal reconciliation. The same if Operators push other config changes (in fact, the sidecars should probably move to ConfigMaps with updates picked up via configmap.Watcher).
My attempt at updating deployments during reconciliation was thwarted by its defaulter, but I'd like to reach a point where we reconcile updates around our controllers for all resources we manage.
There was a problem hiding this comment.
I'd also like any Deployment reconciliation we do to follow the pattern of checkAndUpdateDeployment that's commented as much as possible (though it's implementation may remain heavily commented).
| // TODO(mattmoor): Compare the deployments and update if it has changed | ||
| // out from under us. | ||
| logger.Infof("Found existing deployment %q, updating", deploymentName) | ||
| _, err = dc.Update(deployment) |
There was a problem hiding this comment.
Old code used to skip updating the deployment if it already exist. However; this one will keep updating the deployment objects every 30 seconds. Seems like a regression.
|
The following is the coverage report on pkg/. Say
*TestCoverage feature is being tested, do not rely on any info here yet |
| return err | ||
| // TODO(mattmoor): Compare the deployments and update if it has changed | ||
| // out from under us. So far the deployment could only be updated for replicas field. | ||
| if *existingDeployment.Spec.Replicas == *desiredDeployment.Spec.Replicas { |
There was a problem hiding this comment.
Something happened to my comments. I think I accidentally deleted them. Here it goes again:
This code will reset the replica count that is set by autoscaler every 30 seconds and that seems wrong. If a deployment exist, we should just return.
There was a problem hiding this comment.
Yeah, this seems strange to me too. I think the controller should be overwriting everything except the replicas count. We leave that to the Activator and the Autoscaler. And the update is punted for now by the TODO(mattmoor) above.
There was a problem hiding this comment.
I am not sure, but I think the reason for not updating deployments every 30 seconds today is because updating deployments even without changes might cause restart of the pods (I don't think that is the case unless the pod spec changes, but I am not 100% sure). @mattmoor is that the reason that deployments are never reconciled today?
| // And the deployment is no longer ready, so update that | ||
| rev.Status.MarkInactive() | ||
| logger.Infof("Updating status with the following conditions %+v", rev.Status.Conditions) | ||
| if _, err := c.updateStatus(rev); err != nil { |
| // And the deployment is no longer ready, so update that | ||
| rev.Status.MarkInactive() | ||
| logger.Infof("Updating status with the following conditions %+v", rev.Status.Conditions) | ||
| if _, err := c.updateStatus(rev); err != nil { |
|
|
||
| func (c *Controller) deleteAutoscalerResources(ctx context.Context, rev *v1alpha1.Revision) error { | ||
| logger := logging.FromContext(ctx) | ||
| if err := c.deleteAutoscalerDeployment(ctx, rev); err != nil { |
There was a problem hiding this comment.
Keep in mind that if we do that, updating controller code will no longer update any existing revision's autoscaler
@mdemirhan Curious to know why you think changes couldn't be reconciled to update existing autoscaler deployments?
| if err := c.resolver.Resolve(desiredDeployment); err != nil { | ||
| logger.Error("Error resolving deployment", zap.Error(err)) | ||
| rev.Status.MarkContainerMissing(err.Error()) | ||
| if _, updateErr := c.updateStatus(rev); updateErr != nil { |
| var ( | ||
| elaPodReplicaCount = int32(1) | ||
| elaPodMaxUnavailable = intstr.IntOrString{Type: intstr.Int, IntVal: 1} | ||
| elaPodMaxUnavailable = intstr.IntOrString{Type: intstr.Int, IntVal: 0} |
There was a problem hiding this comment.
Just curious to know what your motivation was to change this to 0?
| return err | ||
| } | ||
| if *deployment.Spec.Replicas == 0 { | ||
| logger.Infof("Deployment %s is scaled to 0 already.", deploymentName) |
| return err | ||
| } | ||
|
|
||
| logger.Infof("Successfully scaled deployment %s to 0.", deploymentName) |
| Labels: map[string]string{ | ||
| "testLabel1": "foo", | ||
| "testLabel2": "bar", | ||
| serving.RouteLabelKey: "test-route", |
There was a problem hiding this comment.
Is the constant serving.RouteLabelKey used anymore?
|
@akyyy: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions 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. |
|
I believe this is subsumed by @mdemirhan's new PR. |
Fixes #1250
Proposed Changes