diff --git a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/BUILD b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/BUILD index 106adfe44b3..741403b314a 100644 --- a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/BUILD +++ b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/BUILD @@ -19,10 +19,11 @@ 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/homedir:go_default_library", + "//vendor/k8s.io/client-go/util/retry:go_default_library", ], ) diff --git a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/README.md b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/README.md index 6b7ed4eac98..e9c11a9355b 100644 --- a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/README.md +++ b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/README.md @@ -22,6 +22,8 @@ go build -o ./app Now, run this application on your workstation with your local kubeconfig file: ``` +./app +# or specify a kubeconfig file with flag ./app -kubeconfig=$HOME/.kube/config ``` @@ -30,13 +32,19 @@ Running this command will execute the following operations on your cluster: 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 add 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`. -3. **List Deployments:** This will retrieve Deployments in the `default` + 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, 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. -4. **Delete Deployment:** This will delete the Deployment object and its +5. **Delete Deployment:** This will delete the Deployment object and its dependent ReplicaSet resource. Verify with `kubectl get deployments`. Each step is separated by an interactive prompt. You must hit the @@ -55,6 +63,10 @@ Updating deployment... Updated deployment... -> Press Return key to continue. +Rolling back deployment... +Rolled back deployment... +-> Press Return key to continue. + Listing deployments in namespace "default": * demo-deployment (1 replicas) -> Press Return key to continue. diff --git a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go index 722a39458cd..f6d622745f9 100644 --- a/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go +++ b/staging/src/k8s.io/client-go/examples/create-update-delete-deployment/main.go @@ -22,23 +22,28 @@ import ( "flag" "fmt" "os" + "path/filepath" 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/homedir" + "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" ) func main() { - kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file") - flag.Parse() - if *kubeconfig == "" { - panic("-kubeconfig not specified") + var kubeconfig *string + if home := homedir.HomeDir(); home != "" { + kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + } else { + kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") } + flag.Parse() + config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) @@ -66,7 +71,7 @@ func main() { Containers: []apiv1.Container{ { Name: "web", - Image: "nginx:1.13", + Image: "nginx:1.12", Ports: []apiv1.ContainerPort{ { Name: "http", @@ -97,36 +102,53 @@ 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) + // + // More Info: + // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency - for { - result.Spec.Replicas = int32Ptr(1) // reduce replica count - result.Spec.Template.Annotations = map[string]string{ // add annotations - "foo": "bar", + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Retrieve the latest version of Deployment before attempting update + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + result, getErr := deploymentsClient.Get("demo-deployment", metav1.GetOptions{}) + if getErr != nil { + panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr)) } - if _, err := deploymentsClient.Update(result); errors.IsConflict(err) { - // Deployment is modified in the meanwhile, query the latest version - // and modify the retrieved object. - fmt.Println("encountered conflict, retrying") - result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{}) - if err != nil { - panic(fmt.Errorf("Get failed: %+v", err)) - } - } else if err != nil { - panic(err) - } else { - 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 + _, updateErr := deploymentsClient.Update(result) + return updateErr + }) + if retryErr != nil { + panic(fmt.Errorf("Update failed: %v", retryErr)) } fmt.Println("Updated deployment...") + // Rollback Deployment + prompt() + fmt.Println("Rolling back deployment...") + // Once again use RetryOnConflict to avoid update conflicts + retryErr = retry.RetryOnConflict(retry.DefaultRetry, func() error { + result, getErr := deploymentsClient.Get("demo-deployment", metav1.GetOptions{}) + if getErr != nil { + panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr)) + } + + result.Spec.RollbackTo = &appsv1beta1.RollbackConfig{ + Revision: 0, // can be specific revision number, or 0 for last revision + } + _, updateErr := deploymentsClient.Update(result) + return updateErr + }) + if retryErr != nil { + panic(fmt.Errorf("Rollback failed: %v", retryErr)) + } + fmt.Println("Rolled back deployment...") + // List Deployments prompt() fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)