From e3655bb142020d40567d63eb4376ffcf1a27672e Mon Sep 17 00:00:00 2001 From: Kiichiro Okano Date: Mon, 20 Mar 2017 17:04:52 +0000 Subject: [PATCH] Trigger deployment as success when new replicaSet has reached minimum you need which is number of replicas minus maxUnavailable --- cmd/helm/install.go | 2 +- cmd/helm/rollback.go | 2 +- cmd/helm/upgrade.go | 2 +- docs/using_helm.md | 6 +++++- pkg/kube/client.go | 45 +++++++++++++++++++++++++++++++++++++------- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/cmd/helm/install.go b/cmd/helm/install.go index d838f0b7f..779539964 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -172,7 +172,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") - f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, and Services are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") return cmd } diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 267bfa0dd..bcc780c1d 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -80,7 +80,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") - f.BoolVar(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, and Services are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") return cmd } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 5261efb06..cf8b3f33f 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -114,7 +114,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") - f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, and Services are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.MarkDeprecated("disable-hooks", "use --no-hooks instead") diff --git a/docs/using_helm.md b/docs/using_helm.md index 76bc35a96..00793e8e5 100755 --- a/docs/using_helm.md +++ b/docs/using_helm.md @@ -353,11 +353,15 @@ is not a full list of cli flags. To see a description of all flags, just run - `--timeout`: A value in seconds to wait for Kubernetes commands to complete This defaults to 300 (5 minutes) -- `--wait`: Waits until all Pods are in a ready state, PVCs are bound, and +- `--wait`: Waits until all Pods are in a ready state, PVCs are bound, Deployments + have minimum (`Desired` minus `maxUnavailable`) Pods in ready state and Services have and IP address (and Ingress if a `LoadBalancer`) before marking the release as successful. It will wait for as long as the `--timeout` value. If timeout is reached, the release will be marked as `FAILED`. + + Note: In scenario where Deployment has `replicas` set to 1 and `maxUnavailable` is not set to 0 as part of rolling + update strategy, `--wait` will return as ready as it has satisfied the minimum Pod in ready condition. - `--no-hooks`: This skips running hooks for the command - `--recreate-pods` (only available for `upgrade` and `rollback`): This flag will cause all pods to be recreated (with the exception of pods belonging to diff --git a/pkg/kube/client.go b/pkg/kube/client.go index a43627416..7bcf8754e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -34,10 +34,12 @@ import ( apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" batchinternal "k8s.io/kubernetes/pkg/apis/batch" batch "k8s.io/kubernetes/pkg/apis/batch/v1" + ext "k8s.io/kubernetes/pkg/apis/extensions" extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -59,6 +61,12 @@ type Client struct { SchemaCacheDir string } +// deployment holds associated replicaSets for a deployment +type deployment struct { + replicaSets *ext.ReplicaSet + deployment *ext.Deployment +} + // New create a new Client func New(config clientcmd.ClientConfig) *Client { return &Client{ @@ -541,6 +549,15 @@ func volumesReady(vols []api.PersistentVolumeClaim) bool { return true } +func deploymentsReady(deployments []deployment) bool { + for _, v := range deployments { + if !(v.replicaSets.Status.ReadyReplicas >= v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) { + return false + } + } + return true +} + func getPods(client *internalclientset.Clientset, namespace string, selector map[string]string) ([]api.Pod, error) { list, err := client.Pods(namespace).List(api.ListOptions{ FieldSelector: fields.Everything(), @@ -569,6 +586,8 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { pods := []api.Pod{} services := []api.Service{} pvc := []api.PersistentVolumeClaim{} + replicaSets := []*ext.ReplicaSet{} + deployments := []deployment{} for _, v := range created { obj, err := c.AsVersionedObject(v.Object) if err != nil && !runtime.IsNotRegisteredError(err) { @@ -596,13 +615,25 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { if err != nil { return false, err } - for _, r := range rs.Items { - list, err := getPods(client, value.Namespace, r.Spec.Selector.MatchLabels) - if err != nil { - return false, err - } - pods = append(pods, list...) + + for i := range rs.Items { + replicaSets = append(replicaSets, &rs.Items[i]) + } + + currentDeployment, err := client.Deployments(value.Namespace).Get(value.Name) + if err != nil { + return false, err + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.FindNewReplicaSet(currentDeployment, replicaSets) + if err != nil { + return false, err + } + newDeployment := deployment{ + newReplicaSet, + currentDeployment, } + deployments = append(deployments, newDeployment) case (*extensions.DaemonSet): list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) if err != nil { @@ -635,7 +666,7 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { services = append(services, *svc) } } - return podsReady(pods) && servicesReady(services) && volumesReady(pvc), nil + return podsReady(pods) && servicesReady(services) && volumesReady(pvc) && deploymentsReady(deployments), nil }) }