<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yue Li</title>
    <description>The latest articles on DEV Community by Yue Li (@yuelirex).</description>
    <link>https://dev.to/yuelirex</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1489048%2F7de5c01a-956a-498a-ad50-a966399a0409.png</url>
      <title>DEV Community: Yue Li</title>
      <link>https://dev.to/yuelirex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuelirex"/>
    <language>en</language>
    <item>
      <title>The Challenge: Cross-Namespace Configuration Sharing in Kubernetes</title>
      <dc:creator>Yue Li</dc:creator>
      <pubDate>Mon, 11 Aug 2025 12:22:27 +0000</pubDate>
      <link>https://dev.to/yuelirex/practical-example-of-using-a-mutating-admission-webhook-5ff8</link>
      <guid>https://dev.to/yuelirex/practical-example-of-using-a-mutating-admission-webhook-5ff8</guid>
      <description>&lt;p&gt;When managing multiple applications on Kubernetes, the standard practice is to group them by use-case or functionality into Namespaces. This works perfectly for sharing ConfigMaps or Secrets within that specific boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F904153z8wndzremyowmm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F904153z8wndzremyowmm.png" alt=" " width="694" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Configuration Sprawl
&lt;/h2&gt;

&lt;p&gt;As an organization grows, you might manage hundreds of projects across dozens of namespaces. Many of these projects require identical configurations (e.g., global proxy settings, logging sidecars, or common environment variables).&lt;/p&gt;

&lt;p&gt;If you stick to standard Kubernetes objects, you end up with repeated configurations spread across the entire cluster. This leads to a maintenance nightmare: modifying a single global value requires manual "copy-paste" updates in every namespace, increasing the risk of human error and configuration drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution with Helm lib-chart&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Helm now is a standard way to build, deploy and share the software for Kubernetes. With help of lib-chart, you can define common configuration values once and declare them as dependencies in your application charts.&lt;/p&gt;

&lt;p&gt;Example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v2&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mylibchart&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A Helm chart for Kubernetes&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
&lt;span class="na"&gt;appVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.16.0"&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mylibchart&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file://../mylibchart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Benefit:&lt;/strong&gt; It solves the DRY (Don't Repeat Yourself) problem at the template level. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Downside:&lt;/strong&gt; It’s still a "push" model. Whenever the global config changes, you must re-release and re-deploy every single application package to see the update.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Dynamic Alternative: The Mutating Webhook
&lt;/h2&gt;

&lt;p&gt;What if we could inject these configurations automatically at runtime? This is where the Mutating Admission Webhook comes in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Kubernetes Webhook?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A webhook is an extension point for the Kubernetes API server. When a user creates or updates a resource (like a Deployment), Kubernetes sends that resource to your webhook service via HTTPS. The webhook can then:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ValidatingWebhook:&lt;/strong&gt; Check the resource and reject it if it violates rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MutatingWebhook:&lt;/strong&gt; Modify the resource (injecting labels, sidecars, or env vars) before it is saved to etcd.&lt;/p&gt;

&lt;p&gt;By using a Mutating Webhook, we can "patch" configurations into Deployments regardless of which namespace they land in—no manual PRs or Helm upgrades required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbq2r6ny9bjy0al46dzug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbq2r6ny9bjy0al46dzug.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Blueprint
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Implement the webhook service — a REST API that receives and return AdmissionReview object.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tell Kubernetes which resources to send to your service. We use a MutatingWebhookConfiguration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JSON Patch is used for actual modification.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The webhook service doesn't return the whole YAML; it returns a JSON Patch (RFC 6902). This tells Kubernetes exactly what and how to change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admissionregistration.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MutatingWebhookConfiguration&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default-value-webhook&lt;/span&gt;
&lt;span class="na"&gt;webhooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default-value-webhook.default.svc.cluster.local&lt;/span&gt;
    &lt;span class="na"&gt;admissionReviewVersions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;sideEffects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
    &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
    &lt;span class="na"&gt;objectSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;resource-webhook-enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;clientConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default-value-webhook&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhook/default-value"&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
      &lt;span class="na"&gt;caBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;Base64-encoded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;certificate&amp;gt;"&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;operations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;apiVersions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/spec/template/spec/containers/0/env/-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GLOBAL_REGION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;}}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Critical Considerations
&lt;/h2&gt;

&lt;p&gt;While powerful, building a custom webhook server has some "gotchas" you must prepare for:&lt;/p&gt;

&lt;p&gt;HTTPS Only: Kubernetes requires the webhook to communicate over TLS. Managing these certificates (and rotating them) can be complex.&lt;/p&gt;

&lt;p&gt;JSON Patch limitations: Unlike Helm templates, raw JSON Patches don't support complex logic like if/else. And You won't get raw yaml content from request.&lt;/p&gt;

&lt;p&gt;Library Support: The AdmissionReview object structure is specific. Ensure your chosen language (Go, Python, etc.) has a robust Kubernetes SDK to handle the serialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using a Mutating Webhook shifts your configuration management from a Static/Template model to a Dynamic/Runtime model. It’s a sophisticated approach that’s perfect for platform teams looking to enforce global standards without bothering application developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo projects:
&lt;/h2&gt;

&lt;p&gt;Cluster setup: &lt;a href="https://github.com/YueLiRex/k8s-fluxcd-webhook-cluster" rel="noopener noreferrer"&gt;https://github.com/YueLiRex/k8s-fluxcd-webhook-cluster&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Webhook server implementation: &lt;a href="https://github.com/YueLiRex/k8s-fluxcd-webhook-webhook" rel="noopener noreferrer"&gt;https://github.com/YueLiRex/k8s-fluxcd-webhook-webhook&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webhook</category>
      <category>k8s</category>
      <category>helm</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
