client-go: use retry util in CRUD example

This updates the create-update-delete-deployment example with the following:
Make use of client-go retry util in Update() steps instead of simple for loops.
Using RetryOnConflict is generally better practice as it won't become stuck in a retry loop and uses exponential backoff to prevent exhausting the apiserver.
Instead of changing annotations to demonstrate Updates/Rollbacks, change the container image as it is less confusing for readers and a better real-world example.
Improve comments and README to reflect above changes.

Signed-off-by: John Kelly <jekohk@gmail.com>

Kubernetes-commit: 94f5bcf6f77d5b35074dfab47b5de37096d8ee00
This commit is contained in:
John Kelly 2017-10-05 19:07:57 -04:00 committed by Kubernetes Publisher
parent 4e80b27156
commit ddf6c35ca5
3 changed files with 39 additions and 55 deletions

View File

@ -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",
],
)

View File

@ -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

View File

@ -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()