{"id":17325,"date":"2025-07-17T17:47:42","date_gmt":"2025-07-18T00:47:42","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=17325"},"modified":"2025-07-18T11:56:18","modified_gmt":"2025-07-18T18:56:18","slug":"node-drains-webhook-pod-removal","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/","title":{"rendered":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal"},"content":{"rendered":"<p>Why and how we built an admission controller to make node drains safer when running stateful applications in Kubernetes.<\/p>\n<hr \/>\n<p data-renderer-start-pos=\"124\">Running stateful applications in Kubernetes is increasingly common and these are often managed using custom resources and operators. However, the dynamic nature of Kubernetes means pods, especially those in stateful workloads, aren\u2019t guaranteed to be long-lived. Events like maintenance or resource pressure can trigger pod evictions, which risk disrupting services if not handled carefully.<\/p>\n<p>The Eviction Reschedule Hook is an open source project that aims to address this issue by using Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods, while at the same time notifying the operator that a pod needs to be moved. Its goal is to help preserve service availability and reduce disruption during events like node maintenance.<\/p>\n<p>Read on as I cover:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li>The problem we\u2019re tackling<\/li>\n<li>Why the existing options aren\u2019t enough<\/li>\n<li>An introduction to reschedule hook project<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>While some familiarity with Kubernetes <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">operators<\/a>, node <a href=\"https:\/\/kubernetes.io\/docs\/tasks\/administer-cluster\/safely-drain-node\/\">draining<\/a>, admission <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/admission-controllers\/\">control<\/a> and dynamic <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/extensible-admission-controllers\/\">webhooks<\/a> would be helpful, I\u2019ll try and cover key concepts along the way.<span class=\"Apple-converted-space\"> The <\/span><a href=\"https:\/\/www.couchbase.com\/products\/operator\/\">Couchbase<\/a> Autonomous Operator will also be referenced throughout as the Operator for brevity and most of the examples and demos will involve Couchbase resources of some sort.<\/p>\n<p>Though originally developed with the Couchbase data platform in mind, the reschedule hook project is intended to be flexible and can be configured to protect other operator-managed stateful applications.<\/p>\n<hr \/>\n<h2 style=\"font-weight: 400;\">Part 1 &#8211; Why evictions are challenging for operator-managed stateful applications<\/h2>\n<h3 style=\"font-weight: 200;\">Understanding pod evictions<\/h3>\n<p>In Kubernetes, evicting a pod involves triggering the pod\u2019s <code>PreStop<\/code> hook then sending a <code>SIGTERM<\/code> after its completion. If the pod hasn\u2019t exited after a grace period, this is followed up by a <code>SIGKILL<\/code>. Evictions happen both voluntarily and involuntarily for a multitude of reasons, from node pressure to autoscaling, but this project focuses on voluntary evictions triggered when draining nodes. This is the process whereby pods are removed from nodes using the Kubernetes Eviction API, which is often required to clear the way for operations like maintenance or upgrades.<\/p>\n<p>Running <code>kubectl drain<\/code> is a common way to prepare a node for maintenance in Kubernetes. It marks the node as unschedulable and sends eviction requests concurrently for all the pods running on that node.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17326\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodedrains.png\" alt=\"\" width=\"616\" height=\"444\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodedrains.png 616w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodedrains-300x216.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodedrains-18x12.png 18w\" sizes=\"auto, (max-width: 616px) 100vw, 616px\" \/><\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Why stateful applications are challenging<\/h3>\n<p>Stateful workloads add complexity to eviction handling:<\/p>\n<p style=\"padding-left: 40px;\"><strong data-renderer-mark=\"true\">Unpredictable Shutdown Times:<\/strong> These workloads may involve long-running processes like in-flight queries, which need to finish before a pod can be safely terminated<\/p>\n<p style=\"padding-left: 40px;\"><strong data-renderer-mark=\"true\">Application Coordination:<\/strong> The application itself often needs to be notified before pod removal to ensure data consistency and proper rebalancing<\/p>\n<p style=\"padding-left: 40px;\"><strong data-renderer-mark=\"true\">Operator Management:<\/strong> Operators managing the application need time to coordinate pod removal with the operator\u2019s internal state<\/p>\n<p style=\"padding-left: 40px;\"><strong>Volume Movement:<\/strong> The migration of Kubernetes volumes from one node to another can take considerable time that exceeds the pod&#8217;s <code>terminationGracePeriodSeconds<\/code><\/p>\n<p>An Operator automates the lifecycle of complex applications by continuously reconciling their actual state with the desired state defined in custom resources.<\/p>\n<p>Take Couchbase as an example. A Couchbase cluster is composed of multiple pods, each running an instance of Couchbase Server, typically spread across different nodes and availability zones. The Operator ensures the cluster state matches the state defined in a <code>CouchbaseCluster<\/code> resource. When a pod needs to be removed, the Operator must notify the cluster to rebalance data and handle any running processes gracefully.<\/p>\n<h3 style=\"font-weight: 200;\">Organizational challenges<\/h3>\n<p>Another common challenge is the separation of responsibilities in larger organizations:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li>Kubernetes administrators manage infrastructure<\/li>\n<li>Application teams manage stateful applications running on that infrastructure<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Meaning Kubernetes administrators need ways to safely drain nodes to perform maintenance without requiring constant coordination with application owners or impacting the availability or health of the underlying application.<\/p>\n<hr role=\"presentation\" \/>\n<h3 style=\"font-weight: 200;\">Existing Kubernetes protections &#8211; why they\u2019re not enough<\/h3>\n<p>Kubernetes provides tools like <strong>PreStop hooks<\/strong> and <strong>Pod Disruption Budgets<\/strong> to help with graceful shutdowns and application availability during evictions.<\/p>\n<p><strong>PreStop Hooks<\/strong> run scripts inside the container before the <code>SIGTERM<\/code> signal, giving the pod a chance to gracefully shut down. For stateless applications, this is often enough. But for stateful apps, Operators often need to coordinate pod removal before termination to avoid the issues outlined above, which is tricky to do inside preStop hooks.<\/p>\n<p>One ingenious approach we\u2019ve seen involves adding a preStop script to the pod templates used by the Operator. This script has the pod add the reschedule annotation (discussed later) to itself in order to notify the Operator it should be moved, then loop until it has been safely ejected from the cluster.<\/p>\n<p><strong>Basic outline of script, not exact:<\/strong><\/p>\n<pre class=\"nums:false lang:default decode:true \">preStop:\r\n  exec:\r\n    command:\r\n    - \/bin\/sh\r\n      \/mnt\/kubectl annotate pod $SELF_POD_NAME cao.couchbase.com\/reschedule=true \\\\\r\n      while \/opt\/couchbase\/bin\/couchbase-cli server-list -c couchbases:\/\/localhost\\\\\r\n      do \\\\\r\n        sleep 1; \\\\\r\n      done\r\n<\/pre>\n<p>However, this requires mounting <code>kubectl<\/code> and <code>couchbase-cli<\/code> into each pod, increasing complexity and image size. Networking or other issues could also interrupt the script and result in the pod being prematurely terminated.<\/p>\n<p><strong>Pod Disruption Budgets (PDBs)<\/strong> ensure a minimum number of pods remain available during voluntary disruptions. But they only limit how many pods can be evicted simultaneously, meaning we\u2019re still dealing with the scenario where an operator isn\u2019t able to gracefully handle pod removal.<\/p>\n<hr role=\"presentation\" \/>\n<h3 style=\"font-weight: 200;\">What happens if pods are evicted today?<\/h3>\n<p>The Operator creates PDBs for Couchbase to limit how many pods can be evicted at once. While this ensures a minimal level of availability for the application, pods that do get evicted will still be unsafely removed from the Couchbase cluster by means of failover.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17327\" style=\"border: 1px solid Gainsboro;\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/currentDrain_terminal.gif\" alt=\"\" width=\"800\" height=\"172\" \/><\/p>\n<p>We can also see how Operator handles this by watching what happens in the Couchbase administrator UI.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17328\" style=\"border: 1px solid Gainsboro;\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/currentDrain_gui.gif\" alt=\"\" width=\"800\" height=\"319\" \/><\/p>\n<p>Even when evictions happen one pod at a time, once a pod restarts on a new node, Kubernetes may allow evictions on the next pod before the new pod has been safely added to the stateful application. At Couchbase, we prevent this with a readiness gate on the pods.<\/p>\n<p>To help with these issues, we introduced the <code>cao.couchbase.com\/reschedule<\/code> annotation in CAO v2.7.0. When added to a pod, it tells the Operator it should be recreated. However, manually cordoning nodes and adding this annotation is tedious, doesn\u2019t scale well, and only affects Couchbase pods &#8211; other pods still require normal draining.<\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Why not handle this entirely inside the Operator?<\/h3>\n<p>We considered embedding eviction validation logic inside the Operator itself, but:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li>It\u2019s an anti-pattern in Kubernetes operator design. Operators work via reconciliation loops, not admission controllers<\/li>\n<li>Adding admission control endpoints to operators complicates deployment and maintenance<\/li>\n<li>Validating webhooks are cluster scoped resources and therefore require cluster scoped permissions. The Operator is namespace scoped and would require a large expansion of its required permissions.<\/li>\n<li>Building this logic into it\u2019s own project allows open sourcing and invites community contributions for similar use cases<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr role=\"presentation\" \/>\n<h2 style=\"font-weight: 400;\">Part 2 &#8211; leveraging Kubernetes Webhooks for intelligent pod evictions<\/h2>\n<p>The Eviction Reschedule Hook is an open source Kubernetes admission controller designed to improve how evictions are handled during node drains to avoid the problems outlined in part 1. Working in tandem with an Operator, it automates pod rescheduling by intercepting eviction requests before they reach the pod. These requests are selectively rejected in a way that still allows the standard <code>kubectl drain<\/code> command to function as expected.<\/p>\n<p>Instead of immediately terminating pods or testing Pod Disruption Budget (PDB) limits, the admission controller notifies the Operator a pod needs to be moved using the <code>cao.couchbase.com\/reschedule<\/code> annotation.<\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Understanding admission control<\/h3>\n<p>Admission control is a key stage in the Kubernetes API request lifecycle that occurs after a request has been authenticated and authorized, but before it&#8217;s persisted to the cluster.<\/p>\n<p>Admission controllers can validate or mutate these requests based on custom logic.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17329\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_webhook.png\" alt=\"\" width=\"516\" height=\"296\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_webhook.png 516w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_webhook-300x172.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_webhook-18x10.png 18w\" sizes=\"auto, (max-width: 516px) 100vw, 516px\" \/><\/p>\n<p>When a node is drained in Kubernetes, each attempt at a pod eviction triggers a <code>CREATE<\/code> request for the <code>pods\/eviction<\/code> subresource. This is where our admission controller comes into play. It intercepts these eviction requests before they reach the pods and determines whether they should be allowed. In our case, the admission logic involves signalling to the Operator that a pod should be rescheduled safely by adding the reschedule annotation.<\/p>\n<p>We\u2019re using a validating webhook rather than a mutating webhook because our primary goal is to evaluate whether the eviction request is allowed. While we may annotate the pod as part of the decision process, the webhook itself is performing validation, not mutation, on the eviction request.<\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Handling node drains<\/h3>\n<p>As the title of this article makes clear, the primary goal of this project is to enable graceful handling of node drains. With the Eviction Reschedule Hook in place, the standard node drain flow is modified to support safer pod migration.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-17330\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced-1024x486.png\" alt=\"\" width=\"900\" height=\"427\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced-1024x486.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced-300x142.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced-768x364.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced-18x9.png 18w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/nodeDrains_advanced.png 1096w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<p>Eviction requests are intercepted before the <code>PreStop<\/code> hook is executed. Instead of the pod being terminated, the eviction request is rejected and the pod annotated. The Operator checks for the reschedule annotation during each reconciliation loop. If the annotation appears, it will handle safely recreating the pod.<\/p>\n<p>As draining a node taints it with <code>node.kubernetes.io\/unschedulable<\/code>, assuming there is another node in the Kubernetes cluster that matches the scheduling requirements of the pod, when the Operator creates the new pod it will exist on another node.<\/p>\n<p>Importantly, the admission controller is designed to selectively filter only relevant pods. All other workloads on the node continue to follow the default eviction process.<\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Working with Kubectl<\/h3>\n<p>To integrate smoothly with <code>kubectl<\/code>, it&#8217;s important to understand what happens under the hood when you run <code>kubectl drain &lt;node&gt;<\/code>. Looking at the <code>drain.go<\/code> <a href=\"https:\/\/github.com\/kubernetes\/kubectl\/blob\/master\/pkg\/cmd\/drain\/drain.go\">source<\/a>, after the node is cordoned, a separate goroutine is spawned for each pod on that node to handle its eviction.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-17331\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-1024x375.png\" alt=\"\" width=\"900\" height=\"330\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-1024x375.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-300x110.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-768x281.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-1536x562.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-18x7.png 18w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-1320x483.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain.png 1912w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<p>Whether <code>kubectl<\/code> continues attempting to evict a pod depends on the response from the admission controller. If the eviction request succeeds (i.e., no error is returned), the loop in which eviction requests are sent is exited. There is then a follow up check where <code>kubectl<\/code> waits for the pod\u2019s deletion. However, for Operator managed pods, we want <code>kubectl<\/code> to keep retrying until they have been safely rescheduled, at which point we should end the goroutine before any further checks.<\/p>\n<p>By design, <code>kubectl<\/code> treats a <code>429 TooManyRequests<\/code> response to the eviction request in the goroutine as a signal to pause for 5 seconds before retrying. We can leverage this behavior in our admission controller: after pod selection logic, we return a <code>429<\/code> status if the pod is either being annotated for rescheduling or is already annotated and waiting to be moved.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-17332\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-1024x233.png\" alt=\"\" width=\"900\" height=\"205\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-1024x233.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-300x68.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-768x175.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-1536x349.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-18x4.png 18w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2-1320x300.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-2.png 1540w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<p data-renderer-start-pos=\"11160\">After the pod has been successfully rescheduled, it will have a different name and therefore the admission controller will no longer be able to locate it by name and namespace. At that point, we can return a <code>404 NotFound<\/code> response to exit the goroutine.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-17333\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-1024x266.png\" alt=\"\" width=\"900\" height=\"234\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-1024x266.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-300x78.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-768x199.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-18x5.png 18w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3-1320x342.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/kubectl-drain-3.png 1342w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">In-place pod rescheduling<\/h3>\n<p>In some cases, the Operator may delete and recreate a pod using the same name.<\/p>\n<p>At Couchbase, the <code>upgradeProcess<\/code> field on a <code>CouchbaseCluster<\/code> can be changed to <a href=\"https:\/\/docs.couchbase.com\/operator\/current\/resource\/couchbasecluster.html#couchbaseclusters-spec-upgradeprocess\">control<\/a> how the Operator replaces pods. This defaults to <strong>SwapRebalance<\/strong>, which tells the Operator to create a new pod with a different name, rebalance it into the cluster and then delete the old pod. <strong data-renderer-mark=\"true\">InPlaceUpgrade<\/strong> is a faster but less flexible alternative, whereby the Operator will delete the pod and recreate it with the same name, reusing the existing volumes. The creates a challenge for the admission controller.<\/p>\n<p>The eviction requests sent by the goroutine in <code>kubectl<\/code> include only the pod\u2019s name and namespace. Because of this limited context, the admission controller can&#8217;t assume that a pod lacking a reschedule annotation needs to be moved &#8211; it may simply be a newly recreated instance of a pod that was already rescheduled.<\/p>\n<p>To handle this, the admission controller maintains a lightweight tracking mechanism which is used when pods will be rescheduled with the same name. When a pod is annotated for rescheduling, we will also annotates another resource, named the tracking resource, with the <code>reschedule.hook\/&lt;podNamespace&gt;.&lt;podName&gt; key<\/code>.<\/p>\n<p>If a subsequent eviction request is intercepted for a pod without the reschedule annotation, the presence of this annotation on the tracking resource indicates that the pod has already been rescheduled. When this occurs, the webhook will clean up the tracking annotation for that pod before return a <code>404 NotFound<\/code> response to exit the goroutine.<\/p>\n<p>While the tracking resource type will default to <code>CouchbaseCluster<\/code>, the webhook also supports using a pod\u2019s <code>Namespace<\/code>. If pods will never be rescheduled with the same name, this feature can be disabled.<\/p>\n<hr role=\"presentation\" \/>\n<h3 style=\"font-weight: 200;\">Running the Eviction Reschedule Hook<\/h3>\n<p>Deploying the Eviction Reschedule Hook and its supporting components is straightforward. The <a href=\"https:\/\/github.com\/couchbaselabs\/eviction-reschedule-hook\/blob\/master\/README.md\" data-renderer-mark=\"true\">README<\/a> provides detailed instructions for building the image and deploying the full stack.<\/p>\n<p>The main components that make up the reschedule hook are:<\/p>\n<p style=\"padding-left: 40px;\"><strong>ValidatingWebhookConfiguration: <\/strong>Tells the Kubernetes API server to forward <code>CREATE<\/code> requests for the <code>pods\/eviction<\/code> subresource to our webhook service<\/p>\n<p style=\"padding-left: 40px;\"><strong>Service: <\/strong>Routes incoming webhook requests to the admission controller pod<\/p>\n<p style=\"padding-left: 40px;\"><strong>Admission Controller: <\/strong>Runs the reschedule hook inside a container, typically deployed as a Kubernetes Deployment<\/p>\n<p style=\"padding-left: 40px;\"><strong>ServiceAccount, ClusterRole, and ClusterRoleBinding: <\/strong>These grant the admission controller the necessary permissions. At minimum, <code>get<\/code> and <code>patch<\/code> permissions for pods are needed. If using a tracking resource, <code>get<\/code> and <code>patch<\/code> permissions for the tracking resource should also be added<\/p>\n<p>Once deployed, draining a node on which Couchbase pods reside demonstrates how the Reschedule Hook intercepts and rejects eviction requests until the Operator safely recreates and balances pods back into the cluster:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17334\" style=\"border: 1px solid Gainsboro;\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/safeDrain_terminal.gif\" alt=\"\" width=\"800\" height=\"163\" \/><\/p>\n<p>The graceful handling of eviction requests can also be seen in the Couchbase administrator UI. Pods are no longer failed over and instead replaced with zero downtime:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17335\" style=\"border: 1px solid Gainsboro;\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/safeDrain_gui.gif\" alt=\"\" width=\"800\" height=\"225\" \/><\/p>\n<hr \/>\n<h3 style=\"font-weight: 200;\">Try it out, get involved<\/h3>\n<p>The Eviction Reschedule Hook makes node drains safer and more predictable for stateful applications running on Kubernetes. By coordinating eviction handling with an Operator, it enables graceful rescheduling without disrupting the underlying application.<\/p>\n<p>The project is open source. Check out the <a href=\"https:\/\/github.com\/couchbaselabs\/eviction-reschedule-hook\" data-renderer-mark=\"true\">repository<\/a> on GitHub to get started and take a look at the the <a href=\"https:\/\/github.com\/couchbaselabs\/eviction-reschedule-hook\/blob\/master\/CONTRIBUTE.md\">contributing<\/a> guide if you\u2019re interested in helping out. Feedback, issues and pull requests are all welcome!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Why and how we built an admission controller to make node drains safer when running stateful applications in Kubernetes. Running stateful applications in Kubernetes is increasingly common and these are often managed using custom resources and operators. However, the dynamic [&hellip;]<\/p>\n","protected":false},"author":85555,"featured_media":17336,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[9284,2322],"tags":[],"ppma_author":[10068,9590],"class_list":["post-17325","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-couchbase-autonomous-operator","category-kubernetes"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.8 (Yoast SEO v25.8) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal - The Couchbase Blog<\/title>\n<meta name=\"description\" content=\"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal\" \/>\n<meta property=\"og:description\" content=\"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-07-18T00:47:42+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-07-18T18:56:18+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2400\" \/>\n\t<meta property=\"og:image:height\" content=\"1256\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ben Mottershead, Software Engineer, Justin Ashworth - Senior Software Engineer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ben Mottershead, Software Engineer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\"},\"author\":{\"name\":\"Ben Mottershead, Software Engineer\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/2e3f0eb06846472e4fb9fa87e924eb1b\"},\"headline\":\"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal\",\"datePublished\":\"2025-07-18T00:47:42+00:00\",\"dateModified\":\"2025-07-18T18:56:18+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\"},\"wordCount\":2256,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png\",\"articleSection\":[\"Couchbase Autonomous Operator\",\"Kubernetes\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\",\"name\":\"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png\",\"datePublished\":\"2025-07-18T00:47:42+00:00\",\"dateModified\":\"2025-07-18T18:56:18+00:00\",\"description\":\"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png\",\"width\":2400,\"height\":1256},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/2e3f0eb06846472e4fb9fa87e924eb1b\",\"name\":\"Ben Mottershead, Software Engineer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/e61125d073286ff4deda9445d38fa03a\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg\",\"caption\":\"Ben Mottershead, Software Engineer\"},\"description\":\"Ben is a software engineer who works out of Couchbase's Manchester office. He's been a part of the autonomous operator team since August, which introduced him to the data on Kubernetes ecosystem. Ben has been developing applications for the cloud since he finished university in 2021 and has worked with a number of languages and technologies, more recently focusing on Go, K8s and Couchbase Server.\",\"url\":\"https:\/\/www.couchbase.com\/blog\/author\/benmottershead\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal - The Couchbase Blog","description":"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/","og_locale":"en_US","og_type":"article","og_title":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal","og_description":"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.","og_url":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/","og_site_name":"The Couchbase Blog","article_published_time":"2025-07-18T00:47:42+00:00","article_modified_time":"2025-07-18T18:56:18+00:00","og_image":[{"width":2400,"height":1256,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png","type":"image\/png"}],"author":"Ben Mottershead, Software Engineer, Justin Ashworth - Senior Software Engineer","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ben Mottershead, Software Engineer","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/"},"author":{"name":"Ben Mottershead, Software Engineer","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/2e3f0eb06846472e4fb9fa87e924eb1b"},"headline":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal","datePublished":"2025-07-18T00:47:42+00:00","dateModified":"2025-07-18T18:56:18+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/"},"wordCount":2256,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png","articleSection":["Couchbase Autonomous Operator","Kubernetes"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/","url":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/","name":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png","datePublished":"2025-07-18T00:47:42+00:00","dateModified":"2025-07-18T18:56:18+00:00","description":"The Eviction Reschedule Hook project uses Kubernetes Admission Controllers to intercept and reject eviction requests for operator-managed pods.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2025\/07\/blog-nodedrain.png","width":2400,"height":1256},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/node-drains-webhook-pod-removal\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Rethinking Node Drains: A Webhook Based Approach to Graceful Pod Removal"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/2e3f0eb06846472e4fb9fa87e924eb1b","name":"Ben Mottershead, Software Engineer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/e61125d073286ff4deda9445d38fa03a","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg","caption":"Ben Mottershead, Software Engineer"},"description":"Ben is a software engineer who works out of Couchbase's Manchester office. He's been a part of the autonomous operator team since August, which introduced him to the data on Kubernetes ecosystem. Ben has been developing applications for the cloud since he finished university in 2021 and has worked with a number of languages and technologies, more recently focusing on Go, K8s and Couchbase Server.","url":"https:\/\/www.couchbase.com\/blog\/author\/benmottershead\/"}]}},"authors":[{"term_id":10068,"user_id":85555,"is_guest":0,"slug":"benmottershead","display_name":"Ben Mottershead, Software Engineer","avatar_url":{"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg","url2x":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2024\/12\/1719955373757.jpeg"},"author_category":"","last_name":"Mottershead, Software Engineer","first_name":"Ben","job_title":"Software Engineer","user_url":"","description":"Ben is a software engineer who works out of Couchbase's Manchester office. He's been a part of the autonomous operator team since August, which introduced him to the data on Kubernetes ecosystem. Ben has been developing applications for the cloud since he finished university in 2021 and has worked with a number of languages and technologies,  more recently focusing on Go, K8s and Couchbase Server."},{"term_id":9590,"user_id":81274,"is_guest":0,"slug":"justin-ashworth","display_name":"Justin Ashworth - Senior Software Engineer","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/f8d9f3bd93c0cdd95cbe3e125a15ad07c4cc276956d641a724306dc519fe14fa?s=96&d=mm&r=g","author_category":"","last_name":"Ashworth","first_name":"Justin","job_title":"","user_url":"https:\/\/couchbase.com","description":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/17325","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/85555"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=17325"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/17325\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/17336"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=17325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=17325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=17325"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=17325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}