diff --git a/examples/create-update-delete-deployment/BUILD b/examples/create-update-delete-deployment/BUILD index 106adfe4..10839cfb 100644 --- a/examples/create-update-delete-deployment/BUILD +++ b/examples/create-update-delete-deployment/BUILD @@ -19,10 +19,10 @@ go_library( deps = [ "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", + "//vendor/k8s.io/client-go/util/retry:go_default_library", ], ) diff --git a/examples/create-update-delete-deployment/README.md b/examples/create-update-delete-deployment/README.md index 7bf30576..e9c11a93 100644 --- a/examples/create-update-delete-deployment/README.md +++ b/examples/create-update-delete-deployment/README.md @@ -29,18 +29,19 @@ Now, run this application on your workstation with your local kubeconfig file: Running this command will execute the following operations on your cluster: -1. **Create Deployment:** This will create a 2 replica Deployment with - annotation `fizz=buzz`. Verify with `kubectl get pods`. +1. **Create Deployment:** This will create a 2 replica Deployment. Verify with + `kubectl get pods`. 2. **Update Deployment:** This will update the Deployment resource created in - previous step to set the replica count to 1 and update annotations. You are - encouraged to inspect the retry loop that handles conflicts. Verify the new - replica count and `foo=bar` annotation with `kubectl describe deployment - demo`. + previous step by setting the replica count to 1 and changing the container + image to `nginx:1.13`. You are encouraged to inspect the retry loop that + handles conflicts. Verify the new replica count and container image with + `kubectl describe deployment demo`. 3. **Rollback Deployment:** This will rollback the Deployment to the last - revision, in this case the revision created in Step 1. Use `kubectl describe` - to verify the original annotation `fizz=buzz`. Also note the replica count - is still 1; this is because a Deployment revision is created if and only - if the Deployment's pod template (`.spec.template`) is changed. + revision. In this case, it's the revision that was created in Step 1. + Use `kubectl describe` to verify the container image is now `nginx:1.12`. + Also note that the Deployment's replica count is still 1; this is because a + Deployment revision is created if and only if the Deployment's pod template + (`.spec.template`) is changed. 4. **List Deployments:** This will retrieve Deployments in the `default` namespace and print their names and replica counts. 5. **Delete Deployment:** This will delete the Deployment object and its diff --git a/examples/create-update-delete-deployment/main.go b/examples/create-update-delete-deployment/main.go index e026c7e9..ab866d7c 100644 --- a/examples/create-update-delete-deployment/main.go +++ b/examples/create-update-delete-deployment/main.go @@ -26,10 +26,10 @@ import ( appsv1beta1 "k8s.io/api/apps/v1beta1" apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/retry" // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters). // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) @@ -65,15 +65,12 @@ func main() { Labels: map[string]string{ "app": "demo", }, - Annotations: map[string]string{ - "fizz": "buzz", - }, }, Spec: apiv1.PodSpec{ Containers: []apiv1.Container{ { Name: "web", - Image: "nginx:1.13", + Image: "nginx:1.12", Ports: []apiv1.ContainerPort{ { Name: "http", @@ -104,66 +101,52 @@ func main() { // 1. Modify the "deployment" variable and call: Update(deployment). // This works like the "kubectl replace" command and it overwrites/loses changes // made by other clients between you Create() and Update() the object. - // 2. Modify the "result" returned by Create()/Get() and retry Update(result) until + // 2. Modify the "result" returned by Get() and retry Update(result) until // you no longer get a conflict error. This way, you can preserve changes made - // by other clients between Create() and Update(). This is implemented below: + // by other clients between Create() and Update(). This is implemented below + // using the retry utility package included with client-go. (RECOMMENDED) // - // See the API Conventions: + // More Info: // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency - for { - // Retrieve latest version of Deployment before modifying and updating + err = retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { + // Retrieve the latest version of Deployment before attempting update + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{}) if err != nil { panic(fmt.Errorf("Get failed: %+v", err)) } - result.Spec.Replicas = int32Ptr(1) // reduce replica count - result.Spec.Template.Annotations = map[string]string{ // add annotations - "foo": "bar", - } - if _, err = deploymentsClient.Update(result); err != nil { - if errors.IsConflict(err) { - // Deployment was modified since last retrieved, need to retry - fmt.Println("Conflicting resource versions, retrying") - } else { - panic(err) - } - } else { - fmt.Println("Updated deployment...") - break - } - - // TODO: You should sleep here with an exponential backoff to avoid - // exhausting the apiserver, and add a limit/timeout on the retries to - // avoid getting stuck in this loop indefintiely. + result.Spec.Replicas = int32Ptr(1) // reduce replica count + result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13" // change nginx version + result, err = deploymentsClient.Update(result) + return + }) + if err != nil { + panic(fmt.Errorf("Update failed: %+v", err)) } + fmt.Println("Updated deployment...") // Rollback Deployment prompt() fmt.Println("Rolling back deployment...") - - // Use same method as above to avoid version conflicts - for { + // Once again use RetryOnConflict to avoid update conflicts + err = retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{}) if err != nil { panic(fmt.Errorf("Get failed: %+v", err)) } - result.Spec.RollbackTo = &appsv1beta1.RollbackConfig{ - Revision: 0, // Can be specific revision number or 0 for last revision - } - if _, err = deploymentsClient.Update(result); err != nil { - if errors.IsConflict(err) { - fmt.Println("Conflicting resource versions, retrying") - } else { - panic(err) - } - } else { - fmt.Println("Rolled back deployment...") - break + result.Spec.RollbackTo = &appsv1beta1.RollbackConfig{ + Revision: 0, // can be specific revision number, or 0 for last revision } + result, err = deploymentsClient.Update(result) + return + }) + if err != nil { + panic(fmt.Errorf("Rollback failed: %+v", err)) } + fmt.Println("Rolled back deployment...") // List Deployments prompt()