Updating Ingress Resources

Creating an Ingress resource is just the first step. Applications evolve, routing needs change, and TLS certificates get renewed. Therefore, knowing how to update existing Ingress resources programmatically is just as important as creating them.

Common reasons to update an Ingress resource include:

  • Adding a new path rule to route traffic for a new feature or microservice.

  • Changing the backend Service for an existing path (e.g., during a blue-green deployment).

  • Adding or modifying host rules.

  • Updating the secretName in the tls section when a certificate is renewed.

  • Modifying annotations to change the behavior of the Ingress Controller.

  • Removing obsolete rules or hosts.

The Get-Modify-Update Pattern

You cannot directly modify just a part of an existing Kubernetes resource via a simple API call (except through Patch, which is more complex). The standard pattern for updating resources using client-go's typed clients (like the Ingress client) is:

  1. Get: Retrieve the current version of the Ingress resource from the API server using ingressClient.Get(...).

  2. Modify: Make the desired changes to the Go struct object you received in memory. For example, append a new HTTPIngressPath to the Paths slice within a specific rule, or add a new IngressRule to the Rules slice.

  3. Update: Call ingressClient.Update(...), passing the entire modified Go struct.

Crucial Concept: Optimistic Concurrency and resourceVersion

Kubernetes uses a mechanism called optimistic concurrency control to prevent conflicting updates. Every Kubernetes object has a metadata.resourceVersion field. This field is updated by the API server every time the object is changed.

When you call Update, the API server compares the resourceVersion of the object you're submitting with the resourceVersion of the object currently stored in etcd (the cluster's database).

  • If they match: The update is allowed, the object is changed, and a new resourceVersion is assigned.

  • If they don't match: This means someone else (or another process) modified the object between the time you performed your Get and when you submitted your Update. The API server rejects your update with an HTTP 409 Conflict error. client-go's k8serrors.IsConflict(err) function will return true in this case.

Handling Conflicts:

The standard way to handle a conflict error during an update is to:

  1. Catch the conflict error (k8serrors.IsConflict).

  2. Re-Get: Fetch the latest version of the resource from the API server again.

  3. Re-Apply Changes: Apply your intended modifications to this newly fetched object. (This might require some care if the conflicting change affected the part you wanted to modify).

  4. Retry Update: Call Update again with the re-modified object.

This process might need to be repeated a few times if conflicts are frequent. Libraries like k8s.io/client-go/util/retry provide helpers (like retry.RetryOnConflict) to automate this retry loop.

Example: Adding a Path Rule to an Existing Ingress

Let's extend our previous example. Assume the Ingress example-ingress already exists (created in the previous step). We now want to add a new path rule under the app.example.com host to route /admin traffic to an admin-service on port 8888.

Explanation:

  1. retry.RetryOnConflict: We wrap the Get-Modify-Update logic inside this helper function. It automatically retries the inner function if ingressClient.Update returns a conflict error (errors.IsConflict). It uses default backoff settings between retries.

  2. Get Latest: Inside the function, the first step is always to Get the most current version of the Ingress.

  3. Modify In Memory: We find the specific rule for the targetHost and append our new HTTPIngressPath struct to its HTTP.Paths slice. We added a check to avoid adding duplicates. (More complex logic would be needed to add a new host rule if it didn't exist).

  4. Call Update: We attempt the Update using the modified currentIngress object.

  5. Return Error: The function passed to RetryOnConflict must return the error from the Update call. RetryOnConflict checks if this error is a conflict; if so, it waits and retries the function. If it's another error, or if the update succeeds (nil error), the retry loop stops.

Patch vs Update:

While Update replaces the entire object, Kubernetes also supports Patch operations (JSON Patch, Strategic Merge Patch, Server-Side Apply) for partial updates. Patching can be more efficient network-wise and less prone to certain types of conflicts, but constructing the patch requests can be more complex than modifying the Go struct for Update. For many common modifications like adding items to lists within the spec, the Get-Modify-Update pattern with RetryOnConflict is a robust and widely used approach.

Mastering the Get-Modify-Update pattern (especially with conflict handling) is essential for building reliable Go applications that manage the lifecycle of Kubernetes resources like Ingress.

Last updated

Was this helpful?