mirror of
https://github.com/kubernetes/client-go.git
synced 2025-07-04 18:56:21 +00:00
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:
parent
4e80b27156
commit
ddf6c35ca5
@ -19,10 +19,10 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
|
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1: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/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/kubernetes: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/tools/clientcmd:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/util/retry:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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:
|
Running this command will execute the following operations on your cluster:
|
||||||
|
|
||||||
1. **Create Deployment:** This will create a 2 replica Deployment with
|
1. **Create Deployment:** This will create a 2 replica Deployment. Verify with
|
||||||
annotation `fizz=buzz`. Verify with `kubectl get pods`.
|
`kubectl get pods`.
|
||||||
2. **Update Deployment:** This will update the Deployment resource created in
|
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
|
previous step by setting the replica count to 1 and changing the container
|
||||||
encouraged to inspect the retry loop that handles conflicts. Verify the new
|
image to `nginx:1.13`. You are encouraged to inspect the retry loop that
|
||||||
replica count and `foo=bar` annotation with `kubectl describe deployment
|
handles conflicts. Verify the new replica count and container image with
|
||||||
demo`.
|
`kubectl describe deployment demo`.
|
||||||
3. **Rollback Deployment:** This will rollback the Deployment to the last
|
3. **Rollback Deployment:** This will rollback the Deployment to the last
|
||||||
revision, in this case the revision created in Step 1. Use `kubectl describe`
|
revision. In this case, it's the revision that was created in Step 1.
|
||||||
to verify the original annotation `fizz=buzz`. Also note the replica count
|
Use `kubectl describe` to verify the container image is now `nginx:1.12`.
|
||||||
is still 1; this is because a Deployment revision is created if and only
|
Also note that the Deployment's replica count is still 1; this is because a
|
||||||
if the Deployment's pod template (`.spec.template`) is changed.
|
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`
|
4. **List Deployments:** This will retrieve Deployments in the `default`
|
||||||
namespace and print their names and replica counts.
|
namespace and print their names and replica counts.
|
||||||
5. **Delete Deployment:** This will delete the Deployment object and its
|
5. **Delete Deployment:** This will delete the Deployment object and its
|
||||||
|
@ -26,10 +26,10 @@ import (
|
|||||||
|
|
||||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"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).
|
// 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"
|
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
)
|
)
|
||||||
@ -65,15 +65,12 @@ func main() {
|
|||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"app": "demo",
|
"app": "demo",
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
|
||||||
"fizz": "buzz",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Spec: apiv1.PodSpec{
|
Spec: apiv1.PodSpec{
|
||||||
Containers: []apiv1.Container{
|
Containers: []apiv1.Container{
|
||||||
{
|
{
|
||||||
Name: "web",
|
Name: "web",
|
||||||
Image: "nginx:1.13",
|
Image: "nginx:1.12",
|
||||||
Ports: []apiv1.ContainerPort{
|
Ports: []apiv1.ContainerPort{
|
||||||
{
|
{
|
||||||
Name: "http",
|
Name: "http",
|
||||||
@ -104,66 +101,52 @@ func main() {
|
|||||||
// 1. Modify the "deployment" variable and call: Update(deployment).
|
// 1. Modify the "deployment" variable and call: Update(deployment).
|
||||||
// This works like the "kubectl replace" command and it overwrites/loses changes
|
// This works like the "kubectl replace" command and it overwrites/loses changes
|
||||||
// made by other clients between you Create() and Update() the object.
|
// 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
|
// 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
|
// https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency
|
||||||
|
|
||||||
for {
|
err = retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
|
||||||
// Retrieve latest version of Deployment before modifying and updating
|
// 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{})
|
result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("Get failed: %+v", err))
|
panic(fmt.Errorf("Get failed: %+v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Spec.Replicas = int32Ptr(1) // reduce replica count
|
result.Spec.Replicas = int32Ptr(1) // reduce replica count
|
||||||
result.Spec.Template.Annotations = map[string]string{ // add annotations
|
result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13" // change nginx version
|
||||||
"foo": "bar",
|
result, err = deploymentsClient.Update(result)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Update failed: %+v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
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...")
|
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback Deployment
|
// Rollback Deployment
|
||||||
prompt()
|
prompt()
|
||||||
fmt.Println("Rolling back deployment...")
|
fmt.Println("Rolling back deployment...")
|
||||||
|
// Once again use RetryOnConflict to avoid update conflicts
|
||||||
// Use same method as above to avoid version conflicts
|
err = retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
|
||||||
for {
|
|
||||||
result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{})
|
result, err = deploymentsClient.Get("demo-deployment", metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("Get failed: %+v", err))
|
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 {
|
result.Spec.RollbackTo = &appsv1beta1.RollbackConfig{
|
||||||
if errors.IsConflict(err) {
|
Revision: 0, // can be specific revision number, or 0 for last revision
|
||||||
fmt.Println("Conflicting resource versions, retrying")
|
}
|
||||||
} else {
|
result, err = deploymentsClient.Update(result)
|
||||||
panic(err)
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Rollback failed: %+v", err))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fmt.Println("Rolled back deployment...")
|
fmt.Println("Rolled back deployment...")
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// List Deployments
|
// List Deployments
|
||||||
prompt()
|
prompt()
|
||||||
|
Loading…
Reference in New Issue
Block a user