<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Brian Grant on Medium]]></title>
        <description><![CDATA[Stories by Brian Grant on Medium]]></description>
        <link>https://medium.com/@bgrant0607?source=rss-ccdbe9a99319------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*IswKlFXcN7a_0bsXVi8l9A.jpeg</url>
            <title>Stories by Brian Grant on Medium</title>
            <link>https://medium.com/@bgrant0607?source=rss-ccdbe9a99319------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 09 Jun 2026 06:19:22 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bgrant0607/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Managed fields in Kubernetes]]></title>
            <link>https://itnext.io/managed-fields-in-kubernetes-0475978b3c98?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/0475978b3c98</guid>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 08 Jun 2026 16:21:15 GMT</pubDate>
            <atom:updated>2026-06-08T17:11:45.021Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hackL2AgFcN4qLqz4olhiw.png" /><figcaption>“Locked” fields. Image generated by Gemini. (Not perfect, but good enough for the illustration.)</figcaption></figure><p>It isn’t shown by default by kubectl get -o yaml or kubectl edit any longer, but you may have seen something like the following if you have been using Kubernetes for a while:</p><pre>  managedFields:<br>  - apiVersion: apps/v1<br>    fieldsType: FieldsV1<br>    fieldsV1:<br>      f:metadata:<br>        f:annotations:<br>          f:config.k8s.io/owning-inventory: {}<br>      f:spec:<br>        f:minReadySeconds: {}<br>        f:replicas: {}<br>        f:selector: {}<br>        f:strategy:<br>          f:rollingUpdate: {}<br>          f:type: {}<br>        f:template:<br>          f:metadata:<br>            f:labels:<br>              f:app: {}<br>          f:spec:<br>            f:containers:<br>              k:{&quot;name&quot;:&quot;worker&quot;}:<br>                .: {}<br>...<br>    manager: confighub-bridge-worker<br>    operation: Apply</pre><p><a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management">Managed fields</a> were added to Kubernetes as part of the <a href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/555-server-side-apply/README.md">server-side apply design</a>, so to understand it we should start with kubectl apply.</p><p>As I explained in <a href="https://medium.com/itnext/the-technical-history-of-kubernetes-2fe1988b522a">my first blog post</a> (search for “topic 8”), kubectl apply <a href="https://github.com/kubernetes/kubernetes/issues/1702">reconciles the desired state from files with resources in the cluster</a>. It does this without a separate <a href="https://itnext.io/state-in-infrastructure-as-code-tools-40515f7b4840">state file of the kind that Terraform maintains</a>. On a per-resource basis, the way this worked is that kubectl recorded the “last applied configuration” in an annotation like this:</p><pre>metadata:<br>  annotations:<br>    config.k8s.io/owning-inventory: 540bd711-7ae4-42c4-8f80-a36c9a6e52ba-confighub-worker<br>    kubectl.kubernetes.io/last-applied-configuration: |<br>      {&quot;apiVersion&quot;:&quot;apps/v1&quot;,&quot;kind&quot;:&quot;Deployment&quot;,&quot;metadata&quot;:{&quot;name&quot;:&quot;worker&quot;,&quot;namespace&quot;:&quot;confighub-worker&quot;},&quot;spec&quot;:{&quot;minReadySeconds&quot;:10,&quot;replicas&quot;:1,&quot;selector&quot;:{&quot;matchLabels&quot;:{&quot;app&quot;:&quot;worker&quot;}},&quot;strategy&quot;:{&quot;rollingUpdate&quot;:null,&quot;type&quot;:&quot;Recreate&quot;},&quot;template&quot;:{&quot;metadata&quot;:{&quot;labels&quot;:{&quot;app&quot;:&quot;worker&quot;}},&quot;spec&quot;:{&quot;containers&quot;:[{&quot;env&quot;:[{&quot;name&quot;:&quot;CONFIGHUB_URL&quot;,&quot;value&quot;:&quot;https://hub.confighub.com&quot;},{&quot;name&quot;:&quot;NAMESPACE&quot;,&quot;valueFrom&quot;:{&quot;fieldRef&quot;:{&quot;fieldPath&quot;:&quot;metadata.namespace&quot;}}}],&quot;envFrom&quot;:[{&quot;secretRef&quot;:{&quot;name&quot;:&quot;confighub-worker-secret&quot;}}],&quot;image&quot;:&quot;ghcr.io/confighubai/confighub-worker:d90ab40ddbab39b8d446a2f7c6b332cd52f644c9&quot;,&quot;imagePullPolicy&quot;:&quot;Always&quot;,&quot;name&quot;:&quot;worker&quot;,&quot;securityContext&quot;:{&quot;allowPrivilegeEscalation&quot;:false,&quot;capabilities&quot;:{&quot;drop&quot;:[&quot;ALL&quot;]},&quot;privileged&quot;:false,&quot;readOnlyRootFilesystem&quot;:true},&quot;volumeMounts&quot;:[{&quot;mountPath&quot;:&quot;/tmp&quot;,&quot;name&quot;:&quot;tmp&quot;}]}],&quot;securityContext&quot;:{&quot;fsGroup&quot;:2000,&quot;runAsGroup&quot;:3000,&quot;runAsNonRoot&quot;:true,&quot;runAsUser&quot;:1000,&quot;seccompProfile&quot;:{&quot;type&quot;:&quot;RuntimeDefault&quot;}},&quot;serviceAccountName&quot;:&quot;confighub-worker&quot;,&quot;terminationGracePeriodSeconds&quot;:60,&quot;volumes&quot;:[{&quot;emptyDir&quot;:{},&quot;name&quot;:&quot;tmp&quot;}]}}}}</pre><p>This enabled kubectl to diff the new desired state from its local input with the previous desired state from the annotation on the resource in the cluster and to apply that as a patch to the cluster — this is also called a “3-way diff” or “3-way merge”.</p><p>The original purpose of performing a diff <a href="https://itnext.io/kubernetes-strategic-merge-patch-4bdd19b48789">and patch</a> rather than just overwriting the resource in the cluster was to preserve desired-state values set by automated mechanisms, especially asynchronous controllers (topic 5 in the history blog post), to avoid flapping back and forth. The canonical example is horizontal (replicas) and vertical (cpu and memory) autoscalers. But I didn’t want to constrain the mechanism to just those two examples.</p><p>The diff-and-patch mechanism was implemented in kubectl, which, of course, was written in Go. This made it difficult to embed in other languages, like Python, Java, and Javascript, and exec’ing kubectl was not an option for web UIs. Furthermore, the implementation had enough dependencies that even other Go programs, such as <a href="https://registry.terraform.io/providers/alekc/kubectl/latest/docs/resources/kubectl_manifest">Terraform</a>, didn’t integrate the apply implementation. And <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">CRD</a> support required the OpenAPI spec, which was typically large, from the apiserver. There was also no dry-run support.</p><p>To address these issues, we decided to develop a server-side implementation. A comprehensive use-case analysis was done as part of <a href="https://docs.google.com/document/d/1q1UGAIfmOkLSxKhVg7mKknplq3OTDWAIQGWMJandHzg/edit?tab=t.0">the design</a>.</p><p>The original proposal included recording the field values, similar to the last-applied-configuration annotation, but that was problematic for some cases (e.g., secrets, large values), and we realized it was unnecessary due to the next development, which made the apply operation not a diff and patch, but a predicated patch.</p><p>To prevent overwriting or removing values set by automated systems, the “field manager” concept was developed, as a generalization of “apply” vs. “other operation”, where “other operation” includes:</p><ul><li>kubectl commands</li><li>asynchronous controllers, including those updating status fields</li><li>mutating <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">admission control</a></li><li>the Kubernetes default value pass</li></ul><p>The field manager effectively “owns” the field and can freely make changes. Other clients (with other field-manager values) attempting to change the field would fail due to a <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts">conflict</a>.</p><p>That introduced a new failure mode for Kubernetes clients, but only those using the new feature. Update requests could fail entirely or partially. For true conflicts, such as a <a href="https://itnext.io/is-gitops-actually-useful-a1c851ba99d8">GitOps controller</a> fighting with another controller, this was considered a feature. For one-off operational changes using kubectl, it was a potential source of problems, as kubectl has its own field-manager value.</p><p>As a consequence, some GitOps controllers like <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/sync-options/#server-side-apply">ArgoCD, grab ownership of fields they apply</a>, defeating the conflict mechanism. Flux <a href="https://github.com/fluxcd/kustomize-controller/blob/main/internal/controller/kustomization_controller.go#L864">selectively takes ownership</a>. Different apply implementations implement different policies, so it can be the source of surprising behavior. <a href="https://helm.sh/community/hips/hip-0023/#conflicts-and-forcing">Helm v4 apparently defaults to not taking ownership</a> (known as force conflicts).</p><p>While the field manager can be useful for debugging, similar to the User-Agent HTTP header field, its value is an arbitrary string, which makes it harder to use to decide what to do in the case of a pre-existing field manager. That’s why Flux hardcodes a few well known values, such as “kubectl”. A higher-level category, such as the categories I enumerated above, would be more useful.</p><p>I didn’t find a tool that did this, so I created one (<a href="https://github.com/confighub/sdk/tree/main/cmd/k8s-mf">github</a>), k8s-mf. It can read managed fields from a file (use kubectl get -o yaml --show-managed-fields) or from the cluster. It can also change managed fields.</p><pre>% k8s-mf --help<br>k8s-mf inspects and repairs Kubernetes server-side-apply (SSA) managed fields.<br><br>Managed fields record which field manager owns each field of a resource. Leftover<br>or competing managers are the usual cause of apply surprises — fields silently<br>retained, deletions blocked, or apply conflicts — especially after a kubectl<br>&quot;break glass&quot; edit or a transition between tools (kubectl apply, ArgoCD, Flux, Sveltos).<br><br>The kubeconfig is loaded with kubectl precedence: --kubeconfig flag, then the<br>KUBECONFIG environment variable, then $HOME/.kube/config.<br><br>Usage:<br>  k8s-mf [command]<br><br>Available Commands:<br>  categories    Show which fields each category of field manager owns<br>  cleanup       Show the result of refresh/import cleanup (ExtraCleanupObjects)<br>  completion    Generate the autocompletion script for the specified shell<br>  conflicts     Predict which fields an apply would conflict over, without changing anything<br>  dry-run-apply Server-side dry-run an apply as a given field manager<br>  help          Help about any command<br>  takeover      Remove other appliers&#39; field managers so one applier owns the resource<br>  values        Show the values of fields owned by appliers<br><br>Flags:<br>      --context string      Kubernetes context to use<br>  -h, --help                help for k8s-mf<br>      --kubeconfig string   Path to the kubeconfig file (overrides KUBECONFIG and the default)<br>  -n, --namespace string    Namespace of the resource (ignored for cluster-scoped resources) (default &quot;default&quot;)</pre><p>Using the earlier example, this is what the categories look like (some fields elided for length):</p><pre>% k8s-mf categories -f /tmp/worker-mf.yaml<br>Resource: Deployment/confighub-worker/worker<br><br>APPLIER — 2 manager(s): confighub-bridge-worker (ConfigHub), kubectl-rollout (kubectl)<br>  44 field(s):<br>    .metadata.annotations.config.k8s.io/owning-inventory<br>    .spec.minReadySeconds<br>    .spec.replicas<br>    .spec.selector<br>    .spec.strategy.rollingUpdate<br>    .spec.strategy.type<br>    .spec.template.metadata.annotations.kubectl.kubernetes.io/restartedAt<br>    .spec.template.metadata.labels.app<br>    .spec.template.spec.containers[name=&quot;worker&quot;].envFrom<br>    .spec.template.spec.containers[name=&quot;worker&quot;].env[name=&quot;CONFIGHUB_URL&quot;].name<br>...<br><br>ASYNCCONTROLLER — 1 manager(s): kube-controller-manager (Kubernetes)<br>  18 field(s):<br>    .metadata.annotations.deployment.kubernetes.io/revision<br>    .status.availableReplicas<br>    .status.conditions[type=&quot;Available&quot;].lastTransitionTime<br>...<br><br>DEFAULT FIELDS (present on the object but owned by no manager — API-server defaults):<br>  .metadata.annotations.kubectl.kubernetes.io/last-applied-configuration<br>  .spec.progressDeadlineSeconds<br>  .spec.revisionHistoryLimit<br>...</pre><p>The categories are determined by a fairly long (dozens of entries) hardcoded list:</p><pre>var exactManagers = map[string]managerInfo{<br> // --- Appliers (whole-resource owners) ---<br> &quot;argocd-controller&quot;:             {CategoryApplier, &quot;ArgoCD&quot;}, // ArgoCD default SSA manager (ArgoCDSSAManager)<br> &quot;argocd-application-controller&quot;: {CategoryApplier, &quot;ArgoCD&quot;}, // ArgoCD application controller (seen on some resources)<br> &quot;helm&quot;:                          {CategoryApplier, &quot;Helm&quot;},<br> &quot;helm-controller&quot;:               {CategoryApplier, &quot;Flux&quot;}, // Flux HelmRelease<br> &quot;kustomize-controller&quot;:          {CategoryApplier, &quot;Flux&quot;}, // Flux Kustomization<br> &quot;flux&quot;:                          {CategoryApplier, &quot;Flux&quot;},<br>...<br> &quot;endpoint-controller&quot;:         {CategoryAsyncController, &quot;Kubernetes&quot;},<br> &quot;endpointslice-controller&quot;:    {CategoryAsyncController, &quot;Kubernetes&quot;},<br> &quot;service-controller&quot;:          {CategoryAsyncController, &quot;Kubernetes&quot;},<br> &quot;deployment-controller&quot;:       {CategoryAsyncController, &quot;Kubernetes&quot;},<br>...<br> &quot;kube-controller-manager&quot;: {CategoryAsyncController, &quot;Kubernetes&quot;},<br>}</pre><p>You can also opt to see the values of a selected category, defaulting to Applier, which includes any kubectl operation — “Applier” is an approximation of general user intent:</p><pre>% k8s-mf values -f /tmp/worker-mf.yaml<br>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  annotations:<br>    config.k8s.io/owning-inventory: 540bd711-7ae4-42c4-8f80-a36c9a6e52ba-confighub-worker<br>  name: worker<br>  namespace: confighub-worker<br>spec:<br>  minReadySeconds: 10<br>  replicas: 1<br>...</pre><p>There is also a conflicts command to report field manager conflicts, a dry-run-apply command to invoke server-side apply with a specified field manager so you can see what it would do, and a takeover command to remove a specified field manager. The commands that communicate with a cluster have flags for that similar to kubectl ( — context, — kubeconfig, — namespace).</p><p>Give it a try and let me know what you think.</p><p>ConfigHub has a <a href="https://docs.confighub.com/guide/variants/#mutationsources-which-mutation-last-touched-each-path">mechanism for tracking the provenance of every field</a> that was inspired by managed fields, but records a lot more information and more semantically meaningful information, for use to decide how to merge configuration changes. An example of where this is used is to merge changes from the upgrade of <a href="https://itnext.io/a-new-kubernetes-package-installer-324db6f615ac">an installer package</a> without clobbering post-install changes made in ConfigHub. I’m tweaking how this mechanism works and this post is long already, so I’ll write another post about that.</p><p>Hopefully that gives you a sense of why managed fields were added, what their purpose is, and how they facilitate the merging of configuration from multiple sources. The ability to merge configuration intelligently is a configuration management power tool.</p><p>Have you seen managed fields in YAML returned by the apiserver and wondered what they were for? Have you been surprised by a Kubernetes server-side apply operation not changing all of the fields you intended to change? If so, how did you debug it? Have you had trouble figuring out how <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/#transferring-ownership">to transfer ownership</a> to a horizontal or vertical autoscaler? Did you wish that apply just overwrote all of the fields instead?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You are also welcome to <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0475978b3c98" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/managed-fields-in-kubernetes-0475978b3c98">Managed fields in Kubernetes</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kustomize, Kpt, Porch, and ConfigHub]]></title>
            <link>https://itnext.io/kustomize-kpt-porch-and-confighub-28b781336885?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/28b781336885</guid>
            <category><![CDATA[kustomize]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[kpt]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 01 Jun 2026 15:56:20 GMT</pubDate>
            <atom:updated>2026-06-01T16:00:02.966Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KWdm0TQ-ZEtbl1QRHd3-rg.png" /><figcaption>Logos for these tools, except for Porch. I just created an image for it.</figcaption></figure><p>In <a href="https://itnext.io/a-new-kubernetes-package-installer-324db6f615ac">my previous post</a>, I wrote about my new kustomize-based <a href="https://github.com/confighub/installer">installer</a>, which also integrates with kpt. <a href="https://kustomize.io/">Kustomize</a>, <a href="https://kpt.dev/">Kpt</a>, and <a href="https://docs.porch.nephio.org/docs/">Porch</a> are three <a href="https://itnext.io/what-is-configuration-as-data-210b0c4be324">configuration as data</a> tools for Kubernetes that I previously created before <a href="https://docs.confighub.com/background/why-confighub/">ConfigHub</a>. I already wrote <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">a post on the origins of Kustomize</a>, so I’m not going to rehash that, and hopefully you are already at least somewhat familiar with Kustomize, but I will discuss the challenges and gaps with Kustomize that led to the creation of the other tools and, ultimately, to ConfigHub.</p><pre>apiVersion: kustomize.config.k8s.io/v1beta1<br>kind: Kustomization<br>metadata:<br>  name: hello<br>resources:<br>- deployment.yaml<br>- service.yaml<br>- ingress.yaml<br>- configmap.yaml<br>commonLabels:<br>  app: hello<br>images:<br>- name: hello<br>  newTag: v42</pre><p>I wrote <a href="https://kpt.dev/guides/rationale/">the rationale for kpt</a>, which explains more about the concept of configuration as data than the rationale for kpt itself. However, it does touch on the motivation that kpt was intended to fill gaps with kustomize, much as <a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2377-Kustomize#long-standing-issues">kustomize intended to fill gaps in kubectl</a>.</p><p>One such gap was that we wanted a simple mechanism to <a href="https://itnext.io/packages-in-infrastructure-as-code-138fcf463880">package</a> sample configurations for Kubernetes (e.g., <a href="https://github.com/kubernetes/examples/tree/master/web/guestbook">guestbook</a>), GKE (e.g., <a href="https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/kustomize">microservices demo</a>), and Anthos (e.g., <a href="https://github.com/GoogleCloudPlatform/bank-of-anthos/tree/main/kubernetes-manifests">Bank of Anthos</a>) without taking on Helm as a dependency and without adding packaging to kustomize, due to <a href="https://github.com/kubernetes/design-proposals-archive/blob/main/architecture/scope.md#examples-of-projects-and-areas-not-in-scope">scope concerns</a>. Kustomize supports remote bases and <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md">rendering remote configuration</a>, but it doesn’t facilitate pulling a copy of the original configuration.</p><p>The core mechanic of <a href="https://kpt.dev/book/03-packages/">kpt’s package functionality</a> is based on the observation that many git-based package tools host multiple packages in a single git repository, but git itself doesn’t provide mechanisms for interacting with subdirectories in a repository. kpt is able to do that, making <a href="https://opensource.googleblog.com/2020/03/kpt-packaging-up-your-kubernetes.html">any existing folder of Kubernetes YAML implicitly gettable as a kpt package</a>.</p><pre>% kpt pkg get https://github.com/kubernetes/examples.git/web/guestbook/all-in-one@master</pre><p>In addition to packaging, the <a href="https://kpt.dev/book/06-deploying-packages/">kpt live command group</a> added more robust pruning than what kubectl provides, and the ability to wait for the applied resources to become ready, using <a href="https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md">kstatus</a>. Since then, <a href="https://helm.sh/community/hips/hip-0022/">kstatus was adopted by Helm 4</a>, and Flux integrated a <a href="https://github.com/fluxcd/cli-utils">fork of kstatus</a>. By this time, <a href="https://itnext.io/is-gitops-actually-useful-a1c851ba99d8">GitOps tools</a> have been widely adopted, but back in 2019 or so that was not the case, and the lack of a robust applier with pruning and status checking was considered a hindrance to adoption of kustomize and all other alternatives to Helm templates. Moreover, we hoped to <a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/3659-kubectl-apply-prune">eventually integrate the functionality into kubectl</a> to make kubectl itself more usable.</p><pre>% kpt live apply hello</pre><p>A third motivation was that the <a href="https://docs.cloud.google.com/kubernetes-engine/policy-controller/docs/overview">policy controller</a> team wanted to be able to run <a href="https://open-policy-agent.github.io/gatekeeper/website/">OPA Gatekeeper</a> over rendered configuration to validate it and enforce policies in CI. This was the initial driving motivation for the creation of <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md">KRM functions</a>. That effort merged with the kpt effort, bringing <a href="https://kpt.dev/book/04-using-functions/">functions to kpt</a>.</p><pre>apiVersion: kpt.dev/v1<br>kind: Kptfile<br>metadata:<br>  name: hello<br>pipeline:<br>  validators:<br>  - image: ghcr.io/kptdev/krm-functions-catalog/kubeconform:latest<br>  - image: ghcr.io/kptdev/krm-functions-catalog/gatekeeper:latest</pre><p>Originally I imagined kpt would complement kustomize, but there were several challenges to that. For example, support for remote bases in kustomize and common kustomization patterns made kustomization trees frequently not self-contained. Also, the addition of functions to kpt created overlap with kustomize transformer plugins. Additionally, since kustomize was integrated into kubectl, it was problematic to integrate the apply functionality into kustomize.</p><p>There was an effort to try to <a href="https://github.com/kptdev/kpt/issues/3121">converge kpt and kustomize</a>. A result was that <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/validatorPlugin.md">validators</a>, KRM functions, <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/proposals/22-04-localize-command.md">localize</a>, and <a href="https://github.com/kubernetes-sigs/kustomize/issues/3953">cfg configuration viewing commands</a> were added to kustomize.</p><p>What really made kpt distinct, though, was the departure on how it handled <a href="https://itnext.io/variant-generation-is-the-primary-capability-of-infrastructure-as-code-773e6b404b49">variants</a>, which also relates to one of the primary areas of criticism of kustomize: the <a href="https://www.reddit.com/r/kubernetes/comments/1nkx8kl/kustomize_whats_with_all_the_patching/">challenges with patches</a>.</p><p><a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/springboot/overlays/production/kustomization.yaml">Example</a>:</p><pre>resources:<br>- ../../base<br>patches:<br>- path: patch.yaml<br>- path: healthcheck_patch.yaml<br>- path: memorylimit_patch.yaml</pre><p>Kustomize generates variants of a base configuration by performing out-of-place transformations (i.e., rendering its output without modifying the base resource files), mostly notably patching. The patches, which can be <a href="https://itnext.io/kubernetes-strategic-merge-patch-4bdd19b48789">strategic merge patches</a> (aka overlays) or <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/jsonpatch.md">JSON patches</a>, are generally written and maintained by hand, which itself is one challenge. The patches are targeted by resource types and names, and specific field paths (using merge keys in the case of an overlay) or selectors. This can make patches brittle and refactoring difficult, similar to git merge conflicts. The final configuration can also be difficult to reason about when there are multiple layers of patches.</p><pre># ingress_patch.yaml<br>- op: add<br>  path: /spec/rules/0/http/paths/-<br>  value:<br>    path: &#39;/example&#39;<br>    backend:<br>      serviceName: hello<br>      servicePort: 5555<br>...<br># kustomization.yaml<br>- path: ingress_patch.yaml<br>  target:<br>    group: networking.k8s.io<br>    version: v1<br>    kind: Ingress<br>    name: hello</pre><p>The <a href="https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/replacements/">replacements transformer</a> is similar to a patch, but decouples the value source, which makes it more of a repeatable transformation engine than a one-off patch. It was introduced in order to <a href="https://github.com/kubernetes-sigs/kustomize/issues/4049">obviate the need for the parameter-like vars feature</a>. It shares most of the challenges with patches, however. It’s also too limited for some kinds of substitutions, which could be addressed by other transformers/functions, where necessary.</p><pre>replacements:<br>- source:<br>    kind: Service<br>    labelSelector: &quot;environment=production,tier=frontend&quot;<br>    fieldPath: metadata.name<br>  targets:<br>  - select:<br>      kind: Ingress<br>      name: hello<br>    fieldPaths:<br>    - spec.rules.0.http.paths.0.backend.service.name</pre><p>Both patches and templates are simple when there are only a few values that need to be customized in different variants, but they both become complicated when many values need to be changed. Patches can be simpler for the case where different variants need to change different values. Templates can be simpler when different variants need to change the same values. Kustomize’s built-in transformers were intended to address the most common such cases, such as changing images, but there aren’t specific transformers available for most Kubernetes resource fields. More powerful generators, such as <a href="https://itnext.io/can-configuration-languages-dsls-solve-configuration-complexity-eee8f124e13a">DSLs</a> and <a href="https://itnext.io/generating-configuration-using-general-purpose-programming-languages-19230a2c2573">general-purpose programming languages</a>, can express powerful logic, when necessary, but they don’t significantly improve the experience of exposing lots of input parameters read from a file, come with a learning curve (especially with DSLs), and don’t solve <a href="https://itnext.io/fundamental-challenges-with-infrastructure-as-code-imply-the-language-doesnt-matter-41030475c296">fundamental issues with configuration as code</a>, such as the <a href="https://itnext.io/the-12-anti-factors-of-infrastructure-as-code-acb52fba3ae0">need for flexibility driving complexity, lack of interoperability, configuration drift, blast radius, and sprawl</a>.</p><p>Kpt is a package tool that represents configuration as always fully rendered / WET YAML and facilitates forking of base packages by supporting merging of updates, automatically deriving patches. This solved a couple of the challenges: there’s no need to write and maintain patches, and the configuration is always clear. Kpt supports <a href="https://kpt.dev/reference/cli/pkg/update/">multiple merge strategies</a>, but conflicts are, of course, still possible, especially when whomever is maintaining a package is not also aware of the impact of changes on downstream variants of it.</p><p>Kpt emphasizes use of functions for transformations more than Kustomize emphasizes transformer plugins, for which there’s barely any documentation. Moreover, it enables functions to be invoked imperatively, while recording the result declaratively, since the configuration data is persisted in git. Kustomize’s <a href="https://itnext.io/configuration-editing-is-imperative-fa9db379fbe4">imperative editing experience</a> is limited to changing the kustomization.yaml file with the <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/image.md">edit command</a>. The kubectl patch --localcommand modifies resources in files locally, which can be handy. I also use kubectl create --dry-run=client -o yaml to create resources. I added a new command to the installer to do this more cleanly and extensibly.</p><p>A challenge with Kpt was that managing many WET variants required more orchestration to keep all of the variants up to date with upstream changes via kpt pkg updateand to invoke functions on multiple variants via kpt fn render and kpt fn eval. Additionally, the number of git and kpt pkgoperations required even to update a single variant made the experience fairly tedious.</p><pre># Create a package<br>% cd $upstream_dir<br>% kpt pkg init .<br>% git add . &amp;&amp; git commit -m &quot;init base package&quot; &amp;&amp; git push<br><br># Create a downstream variant<br>% cd $downstream_dir<br>% kpt pkg get &quot;$upstream_dir&quot;<br>% git add . &amp;&amp; git commit -m &quot;clone base into variant&quot; &amp;&amp; git push<br><br># Change upstream<br>% cd $upstream_dir<br>% $EDITOR deployment.yaml<br>% git add . &amp;&amp; git commit -m &quot;edited the base&quot; &amp;&amp; git push<br><br># Update downstream<br>% cd $downstream_dir<br>% kpt pkg update .<br>% git add . &amp;&amp; git commit -m &quot;update downstream&quot; &amp;&amp; git push</pre><p>This gave birth to <a href="https://docs.porch.nephio.org/docs/1_overview/">Porch</a>, the package orchestrator, which was spawned from a one-month prototyping effort (<a href="https://www.youtube.com/watch?v=d_iV22_6nAM">demo of the prototype</a>). Porch provided an <a href="https://docs.porch.nephio.org/docs/5_architecture_and_components/">API layer over Kpt and git</a>. That made it possible to read and write Kubernetes configuration and to invoke functions via Kubernetes APIs. The <a href="https://catalog.kpt.dev/">Go function catalog</a> was built into Porch, rather than invoked as individual containers, in order to achieve interactive performance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8E7JTeRHSwEjbUMa_jb5WQ.png" /></figure><p>The <a href="https://www.youtube.com/watch?v=L_x7z4CXHDw">Backstage GUI</a> (<a href="https://github.com/kptdev/kpt-backstage-plugins">repo</a>) used the KRM function interface to change the configuration and save it back to git automatically. This enabled ClickOps backed by GitOps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/0*f1w-tJPtswmUJWWP.png" /></figure><p>I <a href="https://youtu.be/UHr3fBNr8BI?list=PL1Z1ltLHS2uADkFYK2N3A8-yurwpjjusJ&amp;t=11212">thought Porch was promising</a>, but my responsibilities shifted and I had to focus elsewhere. Porch initially became part of the <a href="https://nephio.org/">Nephio project</a>, but it looks like it has been <a href="https://github.com/kptdev/porch">reunited with the Kpt project</a> recently.</p><p>That brings me to <a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a>. I started where I left off with Porch, but a clean slate allowed several different choices, such as:</p><ul><li>Kpt and Porch store the configuration as “code”, in git, and that’s core to how they work. ConfigHub stores configuration as data, in a database. This facilitates centralized, shared access to the <a href="https://itnext.io/configuration-needs-an-api-b36f08b92551">configuration via an API</a>, makes it <a href="https://docs.confighub.com/background/concepts/filters/">queryable</a>, mitigates <a href="https://itnext.io/the-insidious-problem-of-configuration-sprawl-66360fce6ca3">sprawl</a>, and has several other benefits. We’ll write another post on why we don’t store configuration in git.</li><li>ConfigHub supports richer relationships between <a href="https://docs.confighub.com/guide/variants/">variants</a> and <a href="https://docs.confighub.com/guide/dependencies/">dependencies</a>. The relationships between variants make it easier to reason about upstream and downstream relationships and to promote changes. The relationships between dependencies simplify propagation of values between resources. The <a href="https://docs.confighub.com/background/entities/link/">links</a> that represent dependencies can also include associated computation for transforming data from upstream configurations for insertion into downstream configurations.</li><li>ConfigHub stores <a href="https://docs.confighub.com/guide/variants/#mutationsources-which-mutation-last-touched-each-path">more metadata about changes</a>, which is used to control merge behavior at a fine-grained level, in addition to providing more value provenance information in more detail than what git records on its own.</li><li>ConfigHub decouples automated function invocations (<a href="https://docs.confighub.com/guide/validation-and-policies/">Triggers</a>) from configuration data, with different trigger events to distinguish variant creation and admission control. Kpt’s function pipelines were overloaded for multiple purposes without distinguishing them.</li><li>ConfigHub supports non-KRM configuration formats. For example, it enables reading and writing <a href="https://itnext.io/application-configuration-and-feature-flags-356f38712120">application configuration</a> (TOML, INI, JSON, etc.) in <a href="https://docs.confighub.com/guide/app-config/">its native representation</a>. This was <a href="https://github.com/kptdev/kpt/issues/3210">not solved in Kpt</a>, which has an <a href="https://kpt.dev/book/04-using-functions/#function-results">“everything is a Kubernetes resource” model</a>.</li><li>ConfigHub changed the function execution model, so that functions are hosted by an executor, including a built-in executor, rather than individual containers or executables. Even without additional optimization, this makes <a href="https://kpt.dev/book/05-developing-functions/#creating-custom-images">function “packaging”</a> less granular and function execution faster.</li><li>Neither ConfigHub nor the installer support problematic features of kpt, such as <a href="https://github.com/kptdev/kpt/issues/3131">setters</a> and <a href="https://github.com/kptdev/kpt/issues/3343">nested packages</a>.</li></ul><p>The ConfigHub <a href="https://github.com/confighub/sdk/tree/main/function-impl#readme">function executor and function catalog</a> are open source, if you’re curious how they work and/or what functions are available. The functions can be executed locally via the <a href="https://github.com/confighub/installer">installer</a> as a Kustomize transformer plugin and also imperatively usingcub function local.</p><p>Hopefully that gives you some insight into how these tools are tools are different from one another and why each was created. Which one is <em>better</em> depends on your use case, constraints, level of familiarity with the tools, and other factors. I’ll dive deeper into ConfigHub in future posts.</p><p>If you are dissatisfied with the Kubernetes configuration customization tool(s) you are currently using, I suggest <a href="https://auth.confighub.com/sign-up">giving ConfigHub a try</a>. It’s currently in preview. We’re looking for feedback, actively working to improve ConfigHub, and still have the flexibility to make changes.</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=28b781336885" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/kustomize-kpt-porch-and-confighub-28b781336885">Kustomize, Kpt, Porch, and ConfigHub</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A new Kubernetes package installer]]></title>
            <link>https://itnext.io/a-new-kubernetes-package-installer-324db6f615ac?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/324db6f615ac</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[helm]]></category>
            <category><![CDATA[kpt]]></category>
            <category><![CDATA[kustomize]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Tue, 26 May 2026 16:39:06 GMT</pubDate>
            <atom:updated>2026-05-26T18:09:52.783Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mzCu2Kiph-0OTgN1BkhwFg.png" /><figcaption>Image generated by Gemini</figcaption></figure><p><a href="https://helm.sh/">Helm</a> has long been most-used package manager for Kubernetes. It filled a gap for Kubernetes at a critical time back in 2016 <a href="https://helm.sh/community/history/">when Google got involved in the project</a>. We needed to make it easy to install off-the-shelf applications on Kubernetes to enable an ecosystem similar to what was developing around Docker Hub and what Mesosphere was developing <a href="https://github.com/dcos/dcos/tree/master/packages">around DCOS</a>.</p><p>Operating systems had packages, and so did configuration management tools: <a href="https://forge.puppet.com/">Puppet Forge</a>, <a href="https://galaxy.ansible.com/ui/">Ansible Galaxy</a>, and <a href="https://supermarket.chef.io/">Chef Supermarket</a>. So packages, and the <a href="https://github.com/helm/charts">community chart repo</a>, for Kubernetes made sense.</p><p>Speaking of Ansible, it used <a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_templating.html">Jinja YAML templates</a>. So did <a href="https://docs.cloud.google.com/deployment-manager/docs/configuration/templates/create-basic-template">Google Deployment Manager</a>. Lots of CI systems at the time also used YAML. YAML and templates were believed to be more familiar and accessible to operators than, say, Python code, which Google Deployment Manager also supported. Kubernetes (and Docker and Etcd) was written in Go, so Go templates made sense from that perspective, as there <a href="https://x.com/i/spaces/1nAKEpOOdwZxL">wasn’t a Jinja implementation in Go</a>.</p><p>But since then, <a href="https://itnext.io/kubernetes-configuration-in-2024-434abc7a5a1b">many alternative tools</a> have been developed, focused mainly on the configuration generation functionality of Helm, as an alternative to YAML templates. It’s not hard to find posts mentioning the complexity of Helm templates. I’ve <a href="https://itnext.io/complexity-and-toil-in-infrastructure-as-code-6ca9a6d2af37">written about it</a>. In particular, <a href="https://itnext.io/i-shouldnt-have-to-read-installer-code-every-day-4dc1e5f9ee1a">templates for off-the-shelf packages can be extremely large and complex</a> due to the desire to enable pretty much <a href="https://itnext.io/the-tension-between-flexibility-and-simplicity-in-infrastructure-as-code-6cec841e3d16">every field of every resource in each package to be customized</a>.</p><p>There have been fewer tools targeting Helm’s package management functionality, though there have been some. <a href="https://github.com/glasskube/glasskube">Glasskube</a> is a recent one, which does address some key package problem areas, notably <a href="https://glasskube.dev/products/package-manager/docs/design/dependency-management/">dependency management</a>. But it <a href="https://glasskube.dev/products/package-manager/docs/reference/package-manifest/">wraps Helm</a> and adds its own patching mechanism, so it doesn’t really address the configuration complexity problem.</p><p>I recently wrote about how I <a href="https://itnext.io/converting-helm-charts-to-agent-skills-2a92829d418c">converted a Helm chart to an AI agent skill</a>. I liked a number of aspects of that experience, but it was challenging to understand what the agent would do.</p><p>So I created my own package installer based on <a href="https://itnext.io/what-is-configuration-as-data-210b0c4be324">configuration as data</a>, with a goal of making the installation process simpler, more understandable, more automated, and more aligned with ConfigHub. It’s open source and can be used without ConfigHub.</p><ul><li>Repo: <a href="https://github.com/confighub/installer">https://github.com/confighub/installer</a></li><li><a href="https://github.com/confighub/installer/tree/main/examples/hello-app">Example package</a></li><li><a href="https://github.com/confighub/installer/blob/main/docs/consumer-guide.md">Package installation guide</a></li><li><a href="https://github.com/confighub/installer/blob/main/docs/author-guide.md">Package author guide</a></li><li><a href="https://github.com/confighub/installer/blob/main/docs/author-tutorial.md">Package author tutorial</a></li></ul><p>With help from AI, I was able to produce the bulk of the initial implementation in one week. It helped to be treading a relatively well paved path.</p><p>I already wrote a <a href="https://itnext.io/packages-in-infrastructure-as-code-138fcf463880">post about what a package is</a> in configuration as code tools, so I won’t rehash that here.</p><p>But what is a <strong>package installer</strong>? It’s a tool that installs application packages on a target platform. The reason I didn’t call it a <strong>package manager</strong> is because, while I do want to provide <a href="https://medium.com/@sdboyer/so-you-want-to-write-a-package-manager-4ae9c17d9527">dependency management similar to what package managers for other systems do</a>, I want to manage the lifecycle of the installed configuration differently (discussed more below).</p><p>I took inspiration from <a href="https://www.revenera.com/install/products/installshield/installshield-tips-tricks/what-is-an-installation-wizard">install wizards, like InstallShield</a>, and from the experience of <a href="https://www.youtube.com/watch?v=LO0JeIoEutA">installing apps on my laptop</a>. The experience is very simple, with at most a few high-level decisions about which components or features to install, and where to install the application, and with default values for all choices. Changes to detailed settings are made <strong>after installation </strong>rather than before or during the installation process<strong>. </strong>Post-installation changes can also be made to <a href="https://www.debian.org/doc/manuals/debian-faq/pkg-basics.en.html#conffile">Debian package conffiles</a>, specifically for the <a href="https://itnext.io/application-configuration-and-feature-flags-356f38712120">application configuration</a> part of the package.</p><p>Enabling post-installation changes to be made to Kubernetes configuration such that they won’t be clobbered upon upgrade requires <a href="https://github.com/kptdev/kpt/issues/2528">managing the rendered configuration differently</a>. Obviously, if the whole configuration is regenerated from scratch, naively, any changes to the previously generated configuration would be dropped. Managing configuration as data enables post-installation customization by storing the rendered, customized configuration, and by <a href="https://itnext.io/kubernetes-strategic-merge-patch-4bdd19b48789">merging</a> changes from rendered manifests.</p><p>The same approach enables decoupling <a href="https://docs.confighub.com/guide/variants/">variant management</a> and operational changes from installation. Packages don’t need to parameterize replicas, cpu and memory resources, environment variables, labels, annotations, and other standard Kubernetes fields because those can just be changed with later, with <a href="https://docs.confighub.com/background/entities/function/">functions</a>, AI agents, hand editing, or <a href="https://itnext.io/what-is-bidirectional-gitops-ce0ced75fa1c">merging from the live state</a>.</p><p>In the installer, choices of which “components” to install, where to install the application (i.e., the Kubernetes Namespace), and any other information that needs to be provided at install time are gathered by a “wizard”, which can be used interactively or non-interactively (including driven by an AI agent), and are recorded in <a href="https://itnext.io/on-using-the-kubernetes-resource-model-for-declarative-configuration-a5dd11b99546">KRM</a> files an out/spec directory.</p><pre>apiVersion: installer.confighub.com/v1alpha1<br>kind: Selection<br>metadata:<br>  name: argocd-selection<br>spec:<br>  package: argocd<br>  packageVersion: 3.4.2<br>  base: cluster-install<br>---<br>apiVersion: installer.confighub.com/v1alpha1<br>kind: Inputs<br>metadata:<br>  name: argocd-inputs<br>spec:<br>  package: argocd<br>  packageVersion: 3.4.2<br>  namespace: argocd<br>  values: {}</pre><p>I also took inspiration from <a href="https://www.gnu.org/software/autoconf/">Autoconf</a>, curl | bash installers, <a href="https://github.com/puppetlabs/facter">Puppet Facter</a>, <a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_vars_facts.html#ansible-facts">Ansible Facts</a>, and <a href="https://github.com/NVIDIA/aicr">Nvidia AICR</a>. These tools automatically gather information, such as the architecture of the target system, and use it to configure the installation process. That’s a kind of automation that has been lost in the pursuit of hermetic and idempotent configuration generation, which itself is a problem that was created by repeatedly regenerating configuration using configuration as code formats.</p><p>The installer currently supports a local “fact” collection step, which can collect details about the workload being deployed and/or facts about the target cluster the application will be deployed to. It’s local (for now, at least) so that it can use the context and credentials of the user. The facts are similarly recorded.</p><pre>apiVersion: installer.confighub.com/v1alpha1<br>kind: Facts<br>metadata:<br>  name: confighub-worker-facts<br>spec:<br>  package: confighub-worker<br>  packageVersion: 0.1.0<br>  values:<br>    bridgeWorkerID: 6590e916-caee-4a3e-8096-2faeda20d04e<br>    configHubURL: https://hub.confighub.com<br>    image: ghcr.io/confighubai/confighub-worker:v0.1.46</pre><p>Once choices, inputs, and facts are recorded, then the configuration is rendered using the recorded information, which makes it repeatable and idempotent unless inputs are changed deliberately.</p><p>I decided to use <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">Kustomize</a> to perform the rendering. The installer generates a kustomization.yaml file and uses the <a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/1802-kustomize-components">Components</a> feature of Kustomize to combine the selected bases and components, which was a good fit for that part of the process. That also makes it possible to use existing kustomizations in the installer.</p><p>Also, while Kustomize is best known for patching configuration, it also has the ability to transform configuration programmatically. The installer uses the <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/images/README.md">kustomize image transformer</a> to facilitate <a href="https://github.com/confighub/installer/blob/main/docs/consumer-guide.md#2---set-image-overrides-install-time-image-only">overriding images</a> specifically, because that’s frequently needed for image mirroring, rollbacks, rolling out CVE fixes, and so on.</p><p>However, kustomize has a modest number of other <a href="https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md">built-in transformers</a>, and I’m not aware of any popular transformer plugins or <a href="https://www.innoq.com/en/blog/2023/03/kustomize-enhancement-with-krm-functions/">KRM functions</a>. I wanted to enable more complex and robust configuration changes. So the installer transformer command serves as a Kustomize transformer plugin (as a native executable rather than as a container) that can invoke any <a href="https://docs.confighub.com/guide/functions/">built-in ConfigHub function</a>, including functions written in Starlark, CEL, and yq. It is invoked automatically by the rendering process for transformers and validators specified in the Package spec.</p><pre>  transformers:<br>    - toolchain: Kubernetes/YAML<br>      whereResource: &quot;&quot;<br>      description: Set the namespace on every namespaced resource and on RBAC subjects.<br>      invocations:<br>        - name: set-namespace<br>          args: [&quot;{{ .Namespace }}&quot;]</pre><p>I also wanted to be able to validate the rendered configuration. At the moment, that’s done in the installer after kustomize rendering completes.</p><pre>  validators:<br>    - toolchain: Kubernetes/YAML<br>      description: Default validators applied at the end of every render.<br>      invocations:<br>        - name: vet-schemas # runs kubeconform<br>        - name: vet-merge-keys # checks for duplicate merge keys, e.g. envs<br>        - name: vet-format # yaml linter</pre><p>All of these steps can be invoked via a single installer setup command. Installation with default bases, components, and inputs, if any, is concise:</p><pre>% installer setup --work-dir argo-install --namespace argocd \<br>    --pull ${installer_dir}/packages/argocd --non-interactive</pre><p>A record of what the installer did, functions invoked with expanded arguments and resource files generated, is also written. For now, this is mostly for debugging and auditing.</p><pre>apiVersion: installer.confighub.com/v1alpha1<br>kind: FunctionChain<br>metadata:<br>  name: argocd-function-chain<br>spec:<br>  package: argocd<br>  packageVersion: 3.4.2<br>  groups:<br>    - toolchain: Kubernetes/YAML<br>      invocations:<br>        - name: set-namespace<br>          args:<br>            - argocd<br>      description: Set the namespace on every namespaced resource and on RBAC subjects.<br>---<br>apiVersion: installer.confighub.com/v1alpha1<br>kind: ManifestIndex<br>metadata:<br>  name: argocd-manifest-index<br>spec:<br>  package: argocd<br>  packageVersion: 3.4.2<br>  files:<br>    - filename: clusterrole-argocd-application-controller.yaml<br>      slug: clusterrole-argocd-application-controller<br>      apiVersion: rbac.authorization.k8s.io/v1<br>      kind: ClusterRole<br>      name: argocd-application-controller<br>...<br>    - filename: statefulset-argocd-argocd-application-controller.yaml<br>      slug: statefulset-argocd-argocd-application-controller<br>      apiVersion: apps/v1<br>      kind: StatefulSet<br>      name: argocd-application-controller<br>      namespace: argocd</pre><p>Similar to Kustomize, installer has an edit command for modifying the Package spec, so that the YAML doesn’t have to be edited by hand in an editor, though it can be.</p><p>Secrets, if any, are output to a different directory out/secrets from the other manifests, which are output to out/manifests. There’s more work to do on secret support in the future. The rendered manifests could then be uploaded to ConfigHub using installer upload , or applied directly with kubectl or your favorite GitOps tool.</p><p>For those of you who use <a href="https://kpt.dev/">kpt</a> or want to try kpt, I added a <a href="https://github.com/confighub/installer/blob/main/docs/kpt-guide.md">kpt guide</a>. The role of kpt in the guide is to manage post-installation changes. Like ConfigHub, kpt can merge fully rendered, WET Kubernetes YAML, so it can be used to preserve changes after re-rendering with kustomize for upgrades and changed installation choices. That requires committing (and pushing and tagging) the rendered manifests and copying them and propagating updates with kpt pkg commands.</p><p>Why not just use kustomize or kpt? Neither tool was designed to be a full-blown installer. For example, they don’t have interactive wizards, component selection, fact collection, and so on.</p><p>Additionally, package management was explicitly <a href="https://github.com/kubernetes/design-proposals-archive/blob/main/architecture/scope.md">out of scope</a> for kustomize. kpt is git-centric (ConfigHub does not store configuration in git), doesn’t support <a href="https://itnext.io/advantages-of-storing-configuration-in-container-registries-rather-than-git-b4266dc0c79f">OCI-based packages</a> (<a href="https://github.com/kptdev/kpt/issues/2300">issue</a>), and doesn’t do dependency management. I also wanted to compose packages rather than statically nest them. I’ll write another post about installer’s package functionality, and another about kpt and its relation to kustomize.</p><p>So, the installer is DRY-ish (DAMP? — need a good backronym for that), because installers of off-the-shelf packages involve choices, computation, and, sometimes, non-hermetic processes. Luckily, with configuration as data (and ConfigHub), configuration <a href="https://docs.confighub.com/guide/authoring-config/">can be authored</a> using whatever mechanism is most effective, such as an install wizard or AI agent or GUI or importing it from a cluster, and can be modified directly in its fully rendered form after that, over time.</p><p>Decoupling the stages of customization (installation, post-installation, variant creation, operational changes, etc.), keeping the configuration fully rendered (WET) at every step, enabling tools to operate on the configuration, and maintaining relationships between variants can simplify configuration management compared to a monolithic template or generator that needs to incorporate all needs in one generation pass. Future posts will explore this approach in more depth.</p><p>I didn’t have space in this post to cover all of the installer’s features (e.g., application configuration), but hopefully this gave you a sense of why I created it and how it’s different.</p><p>Have you tried to use kustomize or kpt for package installation as an alternative to Helm? If you’ve tried Glasskube, what did you think? Have you tried another package manager? What would you like to see in a new Kubernetes package installer / manager? Give the installer a try and let me know what you think.</p><p>You are also welcome to <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=324db6f615ac" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/a-new-kubernetes-package-installer-324db6f615ac">A new Kubernetes package installer</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Converting Helm charts to Agent Skills]]></title>
            <link>https://itnext.io/converting-helm-charts-to-agent-skills-2a92829d418c?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a92829d418c</guid>
            <category><![CDATA[agent-skills]]></category>
            <category><![CDATA[vllm]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[helm]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 13 Apr 2026 16:22:08 GMT</pubDate>
            <atom:updated>2026-04-13T19:33:56.345Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ulaZxaOxedRwuxj0UC5YdQ.png" /></figure><p>I wanted to run <a href="https://github.com/vllm-project/vllm">vLLM</a> on Kubernetes. There were several options:</p><ul><li><a href="https://github.com/vllm-project/production-stack/tree/main/helm">A Helm chart</a></li><li><a href="https://github.com/vllm-project/production-stack/blob/main/operator/config/default/kustomization.yaml">An Operator, installed with kustomize</a></li><li><a href="https://docs.vllm.ai/en/latest/deployment/k8s/">Plain YAML with kubectl</a></li><li><a href="https://github.com/vllm-project/vllm-skills/tree/main/plugins/vllm-skills/skills/vllm-deploy-k8s">A Claude/agent skill</a></li></ul><p>While there was a little<a href="https://docs.vllm.ai/projects/production-stack/en/latest/deployment/crd.html"> documentation for the Operator</a>, the <a href="https://docs.vllm.ai/projects/production-stack/en/latest/deployment/helm.html">documentation focused on the Helm chart</a>, and most features (e.g., <a href="https://docs.vllm.ai/projects/production-stack/en/latest/use_cases/prefix-aware-routing.html">prefix-aware routing</a>) only had examples using the Helm chart. The <a href="https://code.claude.com/docs/en/skills">Claude/agent skill</a> and plain YAML were quite simple, and didn’t explicitly support most features, which were presumably covered elsewhere in the documentation. I liked the idea of using an agent skill for installation, though.</p><p>As <a href="https://medium.com/itnext/i-shouldnt-have-to-read-installer-code-every-day-4dc1e5f9ee1a">I’ve mentioned before</a>, Helm charts that act as installers for off-the-shelf applications can be quite complicated because they frequently enable significant customization of the configuration. Rather than trying to figure out how to use the Helm chart myself, I used Claude Code.</p><p>First I asked Claude what the Operator could do that the Helm chart couldn’t do, and the answer was nothing really. On the other hand, the Helm chart supported many more features: Ray, autoscaling, Prometheus, and more. So, I did need to start with the Helm chart.</p><p>The Helm chart had a values.schema.json file, but it did not seem complete. I asked Claude for an analysis:</p><pre>The helm chart&#39;s values.schema.json file is significantly incomplete. It covers only a fraction of the supported values.<br><br>What the schema covers<br><br>Only 3 top-level properties: servingEngineSpec, routerSpec, and cacheserverSpec — and even within those, coverage is partial. The schema handles the basics of modelSpec[] items reasonably well (name,<br>repository, tag, resources, GPU, PVC, probes, KEDA).<br><br>What&#39;s missing from the schema<br><br>6 entire top-level sections:<br><br>loraAdapters, loraController, extraObjects, grafanaDashboards, kube-prometheus-stack, prometheus-adapter<br>~10 servingEngineSpec-level properties, including:<br><br>enableEngine, configs, strategy, readinessProbe, sidecar, serviceMonitor, imagePullPolicy, env<br>~15+ modelSpec[] item properties, including:<br><br>chatTemplate, enableLoRA, enableTool, toolCallParser, shmSize, extraVolumes, extraVolumeMounts, affinity, raySpec, envFromSecret, storageClass, pvcAccessMode<br>~20+ lmcacheConfig sub-properties (all NIXL, PD, controller, and p2p fields)<br><br>~7+ vllmConfig sub-properties (tensorParallelSize, maxNumSeqs, maxLoras, gpuMemoryUtilization, v0, v1, prometheusMultiprocDir)<br><br>~15+ routerSpec properties (enableRouter, repository, tag, resources, serviceType, ingress, route, affinity, readinessProbe, serviceMonitor, etc.)<br><br>There are also a few values referenced in templates that aren&#39;t documented in values.yaml either (e.g., sharedPvcStorage, routerSpec.hf_token, pvcStorage as an object with emptyDir).</pre><p>As is fairly common, <a href="https://github.com/vllm-project/production-stack/blob/main/helm/values.yaml">the values.yaml file</a> used comments for documentation. For example:</p><pre># -- LoRA Adapter Configuration<br>loraAdapters: []<br><br>  # -- LoRA adapter instances to deploy<br>  # Each instance should contain:<br>  # - name: (string) The name of the LoRA adapter instance<br>  # - baseModel: (string) The name of the base model this adapter is for<br>  # - vllmApiKey: (optional) API key configuration for vLLM authentication<br>  #   - secretRef: Reference to a secret containing the API key<br>  #     - secretName: (string) Name of the secret<br>  #     - secretKey: (string) Key in the secret containing the API key<br>  #   - value: (string) Direct API key value<br>  # - adapterSource: (object) Configuration for the adapter source<br>  #   - type: (string) Type of adapter source (local, s3, http, huggingface)<br>  #   - adapterName: (string) Name of the adapter to apply<br>  #   - adapterPath: (optional, string) Path to the LoRA adapter weights<br>  #   - repository: (optional, string) Repository to get the LoRA adapter from<br>  #   - pattern: (optional, string) Pattern to use for the adapter name<br>  #   - maxAdapters: (optional, int) Maximum number of adapters to load<br>  #   - credentials: (optional, object) Reference to secret with storage credentials<br>  #     - secretName: (string) Name of the secret<br>  #     - secretKey: (string) Key in the secret containing the credentials<br>  # - loraAdapterDeploymentConfig: (object) Configuration for adapter deployment<br>  #   - algorithm: (string) Placement algorithm to use (default, ordered, equalized)<br>  #   - replicas: (optional, int) Number of replicas that should load this adapter<br>  # - labels: (optional, map) Additional labels for the LoRA adapter<br>  #<br>  # Example:<br>  # loraAdapters:<br>  #   - name: &quot;llama3-nemoguard-adapter&quot;<br>  #     baseModel: &quot;llama3-8b-instr&quot;<br>  #     vllmApiKey:<br>  #       secretRef:<br>  #         secretName: &quot;vllm-api-key&quot;<br>  #         secretKey: &quot;VLLM_API_KEY&quot;<br>  #     adapterSource:<br>  #       type: &quot;local&quot;<br>  #       adapterName: &quot;llama-3.1-nemoguard-8b-topic-control&quot;<br>  #       adapterPath: &quot;/data/lora-adapters/llama-3.1-nemoguard-8b-topic-control&quot;<br>  #     loraAdapterDeploymentConfig:<br>  #       algorithm: &quot;default&quot;<br>  #       replicas: 1<br>  #     labels:<br>  #       environment: &quot;production&quot;<br>  #       model: &quot;llama3-nemoguard&quot;</pre><p>The loraAdapters configuration above is used almost verbatim in a CRD:</p><pre>{{- if .Values.loraAdapters }}<br>{{- range .Values.loraAdapters }}<br>---<br>apiVersion: production-stack.vllm.ai/v1alpha1<br>kind: LoraAdapter<br>metadata:<br>  name: {{ .name }}<br>  namespace: {{ $.Release.Namespace }}<br>  labels:<br>    app.kubernetes.io/component: lora-adapter<br>    helm-release-name: {{ $.Release.Name }}<br>spec:<br>  baseModel: {{ .baseModel | quote }}<br>  {{- if and (not .vllmApiKey) ($.Values.servingEngineSpec.vllmApiKey) }}<br>  vllmApiKey:<br>    secretName: {{ $.Release.Name }}-secrets<br>    secretKey: vllmApiKey<br>  {{- else if .vllmApiKey }}<br>  vllmApiKey:<br>    secretName: {{ .vllmApiKey.secretName | quote }}<br>    secretKey: {{ .vllmApiKey.secretKey | quote }}<br>  {{- end }}<br>  adapterSource:<br>    type: {{ .adapterSource.type | quote }}<br>    adapterName: {{ .adapterSource.adapterName | quote }}<br>    {{- if .adapterSource.adapterPath }}<br>    adapterPath: {{ .adapterSource.adapterPath | quote }}<br>    {{- end }}<br>    {{- if .adapterSource.repository }}<br>    repository: {{ .adapterSource.repository | quote }}<br>    {{- end }}<br>    {{- if .adapterSource.pattern }}<br>    pattern: {{ .adapterSource.pattern | quote }}<br>    {{- end }}<br>    {{- if .adapterSource.maxAdapters }}<br>    maxAdapters: {{ .adapterSource.maxAdapters }}<br>    {{- end }}<br>    {{- if .adapterSource.credentials }}<br>    {{- if (kindIs &quot;string&quot; .adapterSource.credentials) }}<br>    credentialsSecretRef:<br>      name: {{ $.Release.Name }}-secrets<br>      key: lora_adapter_credentials_{{ .adapterSource.adapterName }}<br>    {{- else }}<br>    credentialsSecretRef:<br>      name: {{ .adapterSource.credentials.secretName | quote }}<br>      key: {{ .adapterSource.credentials.secretKey | quote }}<br>    {{- end }}<br>    {{- end }}<br>  loraAdapterDeploymentConfig:<br>    algorithm: {{ .loraAdapterDeploymentConfig.algorithm | default &quot;default&quot; | quote }}<br>    {{- if .loraAdapterDeploymentConfig.replicas }}<br>    replicas: {{ .loraAdapterDeploymentConfig.replicas }}<br>    {{- end }}<br>{{- end }}<br>{{- end }}</pre><p>Given the largely 1:1 value-to-field correspondence, and the use of examples both in the documentation and in the values.yaml file, I wondered what the point was of using Helm templates at all. I wanted to cut out that whole layer of unnecessary complexity. (This issue isn’t Helm-specific. It also applies to <a href="https://itnext.io/can-configuration-languages-dsls-solve-configuration-complexity-eee8f124e13a">domain-specific languages for configuration generation</a> and <a href="https://itnext.io/generating-configuration-using-general-purpose-programming-languages-19230a2c2573">general-purpose-language-based generators</a>.)</p><p>So, I asked Claude to create a Claude/agent skill to install vLLM:</p><blockquote>I want to create an agent skill that is as comprehensive as the helm chart, but that does not use helm, kustomize, or the Operator. I just want to use plain Kubernetes YAML files and select and modify them as necessary. The skill should be able to make use of the vLLM features supported by the helm chart.</blockquote><p>I didn’t want to directly install vLLM, however. I wanted to manage vLLM through <a href="https://confighub.com/">ConfigHub</a>:</p><blockquote>Instead of deploying the resources directly to a Kubernetes cluster, I want to create units in ConfigHub using its CLI, cub.</blockquote><p>That didn’t quite work in one shot. We need to develop a skill to teach AI agents how to use ConfigHub. But using <a href="https://docs.confighub.com/">our documentation</a> and --help mostly worked fine. We previously experimented with MCP, but didn’t find it to work better than our CLI.</p><pre>  SKILL.md (429 lines) — The skill definition with:                                                                                                                                                         <br>  - Trigger: activates when the user wants to deploy vLLM on Kubernetes via ConfigHub                                                                                                                       <br>  - 6-step deployment procedure: gather requirements, create space, prepare/create units, set namespaces, verify, report summary                                                                            <br>  - Comprehensive configuration reference for all vLLM features                                                                                                                                             <br>  - Instructions to use cub space create and cub unit create for each resource                                                                                                                              <br>                                                                              <br>  19 template YAML files covering all components from the helm chart:                                                                                                                                       <br>                                                                                                                                                                                                            <br>  ┌────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐        <br>  │      Category      │                                                                                 Templates                                                                                 │        <br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤<br>  │ Engine (per model) │ engine-deployment.yaml, engine-service.yaml, engine-pvc.yaml, engine-pdb.yaml, engine-configmap.yaml                                                                      │<br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤<br>  │ Router             │ router-deployment.yaml, router-service.yaml, router-serviceaccount.yaml, router-role.yaml, router-rolebinding.yaml, router-hpa.yaml, router-pdb.yaml, router-ingress.yaml │        <br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤        <br>  │ Cache Server       │ cache-server-deployment.yaml, cache-server-service.yaml                                                                                                                   │        <br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤        <br>  │ Autoscaling        │ engine-keda-scaledobject.yaml                                                                                                                                             │<br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤<br>  │ Monitoring         │ engine-servicemonitor.yaml, router-servicemonitor.yaml                                                                                                                    │<br>  ├────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤        <br>  │ Secrets            │ secrets.yaml                                                                                                                                                              │<br>  └────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘        <br>                                                            <br>  Key design decisions:<br>  - Each template is a single Kubernetes resource (1:1 mapping to ConfigHub units)<br>  - Uses confighubplaceholder for values requiring user input (model URL, etc.) and MODELNAME as a per-model substitution token                                                                             <br>  - Templates use reasonable defaults matching the helm chart (ports, probes, labels)                                          <br>  - The SKILL.md documents how to add every optional feature (LMCache, tensor parallelism, LoRA, NIXL, chat templates, init containers, etc.) as incremental YAML modifications rather than Helm            <br>  conditionals                                                                                                                                                                                              </pre><p>The skill required just a few iterations. I put it here:</p><p><a href="https://github.com/bgrant0607/vllm-skill">https://github.com/bgrant0607/vllm-skill</a></p><p>I didn’t spend too long on this, so I’m sure it could be improved. I also haven’t tested it much yet. It’s interesting that Claude replaces example strings in commands directly, but used low-level tools like yq to perform substitutions in configuration. I’ll need to experiment with that more.</p><p>However, there are already some clear benefits to using Claude skills (or other <a href="https://agentskills.io/home">agent skills</a>) over using Helm to install the application.</p><p>First, it’s interactive. Claude asks about whether I want to use specific optional features:</p><ul><li>Do you need a request router? (default: yes, if multiple models or production use)</li><li>Do you need persistent storage for model weights? (default: no)</li><li>Do you need a Hugging Face token for gated models?</li><li>Do you need autoscaling (KEDA for engines, HPA for router)?</li><li>Do you need monitoring (ServiceMonitor)?</li><li>Do you need an Ingress for external access?</li><li>Do you need an LMCache server?</li><li>Routing strategy: roundrobin (default), session, prefixaware, or kvaware?</li></ul><p>Second, it’s high-level. I don’t need to specify every Helm value individually.</p><p>Third, both the “template” configuration and the final configuration are both human readable and can be edited by tools, including <a href="https://docs.confighub.com/guide/functions/">ConfigHub functions</a>. For instance, this is the PVC configuration:</p><pre>apiVersion: v1<br>kind: PersistentVolumeClaim<br>metadata:<br>  name: vllm-MODELNAME-storage-claim<br>  namespace: confighubplaceholder<br>spec:<br>  accessModes:<br>    - ReadWriteOnce<br>  resources:<br>    requests:<br>      storage: 50Gi</pre><p>Fourth, the final configuration can be immediately validated by a number of <a href="https://itnext.io/kubernetes-configuration-linting-tools-699ddeedaeec">linting, validation</a>, and <a href="https://itnext.io/vetting-kubernetes-configuration-with-kyverno-prior-to-deployment-131ee33d315b">policy tools</a>. This can be done using <a href="https://docs.confighub.com/background/entities/trigger/">Triggers</a> in ConfigHub.</p><p>Fifth, I can make subsequent changes using any method I prefer, which might be using the skill, or could just be ConfigHub functions for common Kubernetes configuration changes, like changing resource requests (there’s a set-container-resources function). When I know what Kubernetes resource field I need to change, I don’t need to figure out what the corresponding Helm value is. Configuration can be created <a href="https://docs.confighub.com/guide/authoring-config/">using any method you like</a>, and then edits can be made using any method you like. They aren’t coupled. You can use the best tool for the task and situation.</p><p>More generally, AI agents are pretty good at generating and editing Kubernetes workload configuration at this point, especially plain YAML manifests and standard resource types like Deployment and Service. Rather than trying to factor out parts of the generated configuration that need to be customized for different uses or environments using Helm, kustomize, cdk8s, or other configuration generation tool, to <a href="https://itnext.io/how-software-engineering-instincts-clash-with-infrastructure-as-code-6b18a9cd9cef">make the configuration “DRY”</a>, which would make the configuration <a href="https://medium.com/itnext/complexity-and-toil-in-infrastructure-as-code-6ca9a6d2af37">more complicated</a>, the combination of AI and <a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a> make it viable to generate and manage <a href="https://itnext.io/how-are-variants-managed-in-confighub-061bc2f89cff">many copies (variants) of a configuration</a>.</p><p>When configuration is <a href="https://itnext.io/a-concrete-example-of-configuration-as-data-575115e2ba46">represented and managed as data</a> and can be <a href="https://itnext.io/configuration-needs-an-api-b36f08b92551">read and written via API</a>, configuration can be managed the way that other data in databases is managed, thereby avoiding the <a href="https://itnext.io/fundamental-challenges-with-infrastructure-as-code-imply-the-language-doesnt-matter-41030475c296">fundamental challenges with “as code” representations</a>.</p><p>Have you used agent skills to customize Kubernetes configuration? Has your experience been positive or negative? Was it reliable enough? How difficult were the skills to use? How difficult were they to create? Do you have tips for how to make such skills more effective? Do you think skills or something else AI-based will replace Helm for installing off-the-shelf Kubernetes applications?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><p>You are also welcome to <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a92829d418c" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/converting-helm-charts-to-agent-skills-2a92829d418c">Converting Helm charts to Agent Skills</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Challenge of ConfigMap rollouts in Kubernetes]]></title>
            <link>https://itnext.io/the-challenge-of-configmap-rollouts-in-kubernetes-1431398c0866?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/1431398c0866</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[kubernetes-configmap]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 30 Mar 2026 17:10:59 GMT</pubDate>
            <atom:updated>2026-03-30T17:40:05.927Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nP_ThLlhytWpPzUHe6pEYA.png" /><figcaption><a href="http://issues.k8s.io/22368">http://issues.k8s.io/22368</a></figcaption></figure><p>I was working on ConfigMap updates in <a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a>, and I bumped into a thorn in Kubernetes that I <a href="https://github.com/kubernetes/kubernetes/issues/22368">filed an issue for</a> and tried to solve just over 10 years ago and is still unresolved in the project: ConfigMap rolling updates. What’s the problem, how did it happen, and how can you rollout ConfigMap changes?</p><p>(Side note: Notice that that issue number was well above 20,000 in less than two years after we open-sourced Kubernetes. GitHub sent email notifications for every comment on every issue and PR, and there were easily at least 10x as many of those. It was crazy.)</p><h3>History</h3><p>Kubernetes <a href="https://kubernetes.io/docs/concepts/workloads/">workload controllers</a>, such as <a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployment</a>, <a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/">StatefulSet</a>, and <a href="https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/">DaemonSet</a>, can automatically <a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/">perform rolling updates</a> across the <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pods</a> (groups of containers) owned by the workload controller when the podSpec is modified. This was a key feature of Deployment relative to <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/">ReplicationController</a> (the precursor to <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSet</a>), for which rolling update was implemented in kubectl (and in the CLIs and scripts before that). (For more about the history of Kubernetes workload controllers, search for “Topic 4” in <a href="https://itnext.io/the-technical-history-of-kubernetes-2fe1988b522a">my 5-year anniversary history post</a>.)</p><p>As I mentioned in <a href="https://itnext.io/kubernetes-the-road-to-1-0-525a9420fdf0">my 10-year anniversary Road to 1.0 post</a>, Deployment did not make it into Kubernetes 1.0. Deployment was <a href="https://github.com/kubernetes/kubernetes/issues/503">inspired by an Openshift feature</a>. Work on it was well underway in 1.1, but the automated changes made by both Deployment and HorizontalPodAutoscaler, which was also under development, destabilized the ReplicationController, which up to that point had just been modified by hand or by simple loops in scripts and the CLI. I worked on the ReplicationController/ReplicaSet controller and on the Deployment controller in order to get it ready to ship as beta in <a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.2.md#changes-since-v111">Kubernetes 1.2</a> — back in February 2016.</p><p>Releases 1.1 had taken 4-5 months and 1.2 was already taking more than 3 months. We were formalizing and automating the release process at the same time. For instance, Kubernetes 1.2 was the first release to produce a <a href="https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG">release-specific ChangeLog</a>. There was not yet a formal concept of a <a href="https://github.com/kubernetes/sig-release/blob/master/release-team/README.md">release team</a>, but I played the role of the release lead, as well as working on ReplicaSet and Deployment and wearing many other hats on the project, <a href="https://github.com/cncf/toc/blob/main/EMERITUS.md">in CNCF</a>, and on the GKE team. It was a busy time.</p><p>ConfigMap <a href="https://github.com/kubernetes/kubernetes/issues/18455">had also just been added</a> as a non-secret <a href="https://itnext.io/the-challenge-presented-by-secrets-in-declarative-configuration-193a4d12b08e">Secret</a>, essentially, to inject <a href="https://itnext.io/application-configuration-and-feature-flags-356f38712120">application configuration</a> into containers. ConfigMaps could be referenced by Pods, and therefore by Deployments, to be injected <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/">as environment variables or volumes</a>. Updating the podSpec of a Deployment would roll out new pods referencing the ConfigMaps specified in the podSpec template, which would update any environment variables or files projecting ConfigMap values, but updating the ConfigMap would…do nothing. At least at the workload controller level. With both Deployment and ConfigMap shipping at the same time, there wasn’t enough time to implement ConfigMap-specific rollout features.</p><p>Kubelet could update the ConfigMap volume in place, which could be detected by processes inside containers via inotify if watching. Most applications do not do that and rely on other approaches, such as <a href="https://www.reddit.com/r/kubernetes/comments/1cr5ket/comment/l3xrgsx/">reload on HUP</a>, but if they did, they’d all update simultaneously unless there were some orchestration to control it. We had wanted to defer <a href="https://github.com/kubernetes/kubernetes/issues/9043">in-place rolling updates</a> until we could update more properties, including resource limits, through Docker. Kubelet support also would be needed to restart containers to update environment variables set by ConfigMap values.</p><p>I did not want to add a configMapTemplates field to Deployment for rolling out ConfigMaps. ConfigMaps were already just data blobs in Etcd, and didn’t need to be replicated, though they would <a href="https://github.com/kubernetes/kubernetes/issues/22368#issuecomment-203454883">need to be versioned</a>. Furthermore, adding them to Deployment would bloat that API and cause similar kinds of challenges for tools implementing ConfigMap support as embedding the <a href="https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec">PodSpec</a> in multiple different resource types did. That type of embedding is what drove adoption of <a href="https://knative.dev/v1.19-docs/eventing/concepts/duck-typing/">duck typing</a>. I held out hope for referencing a <a href="https://github.com/kubernetes/kubernetes/issues/170">separate PodTemplate API</a>, which did exist at the time, and wanted to treat ConfigMap similarly. But that would introduce a new pattern, and it was hard to build consensus around an approach.</p><p>Cascading deletion and garbage collection were also just being implemented around that time, and <a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.3.md">shipped in release 1.3</a>. The <a href="https://github.com/kubernetes/kubernetes/issues/22368#issuecomment-242850288">solution proposed</a> was to copy the ConfigMap(s) along with the ReplicaSet and add an ownerReference to the ConfigMap referring to the ReplicaSet. There was a <a href="https://github.com/kubernetes/kubernetes/issues/22368#issuecomment-3690555284">proposal this past December</a> that was similar to that proposal from 2016. That approach could work, and <a href="https://github.com/kubernetes/kubernetes/issues/22368#issuecomment-4087773047">I endorse it</a>. Certainly there would have to be ownerReferences on generated ConfigMaps to ensure they would be cleaned up when the Deployment was deleted.</p><p>There were subsequent proposals in <a href="https://github.com/kubernetes/kubernetes/pull/31701/changes">2016</a>, <a href="https://github.com/kubernetes/community/pull/1163">2017</a>, <a href="https://github.com/kubernetes/community/pull/2287">2018</a>, and <a href="https://github.com/kubernetes/enhancements/pull/948">2019</a>, and perhaps even after that, but none were accepted, unfortunately, partly due to various technical details, but largely due to non-technical factors, such as other priorities. For example, now the focus appears to be on AI workloads. There are also the challenges of stability and backward compatibility, especially now that the project is so widely used and the Deployment controller is such a core, critical component. Making and testing the changes would be a non-negligible amount of work, and maintainers would then be responsible for the code going forward.</p><p>The proposals had three main aspects:</p><ol><li>Triggering the pod rollout on a ConfigMap data change</li><li>Copying ConfigMaps to version them similarly to ReplicaSets</li><li>Garbage collecting or pruning old ConfigMap copies</li></ol><p>Some of the proposals focused on a subset of these issues, such as triggering or garbage collection. And of course there were numerous other details, such as automatic vs. explicit triggering, making the new behavior opt-in for backward compatibility, dealing with shared ConfigMaps, whether to use the general garbage collector or to do the pruning in the Deployment controller along with ReplicaSet management, impact on garbage-collector load in the case of the former, support in StatefulSet and DaemonSet as well as Deployment, whether to add fields to the podSpec (which was shared between the workload controllers and Pods), whether to add fields to ConfigMap for the benefit of workload controllers, and so on.</p><h3>Current solutions</h3><h4>Kustomize</h4><p>As a workaround, we added the ability to <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">Kustomize</a> to <a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/#configmapgenerator">generate ConfigMaps with unique names</a>. Because Kustomize already needed to know which resource fields contained references to other resources in order to add name prefixes or suffixes, Kustomize could also update references to generated ConfigMaps. For example:</p><pre>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: example-configmap-1-g4hk9g2ff8<br>...<br><br>apiVersion: apps/v1<br>kind: Deployment<br>  name: my-app<br>...<br>      volumes:<br>      - configMap:<br>          name: example-configmap-1-g4hk9g2ff8<br>        name: config</pre><p>Because the reference in the configMap.name field of the Deployment’s podSpec would be updated every time the data in the ConfigMap changed, that would trigger a rolling update by the Deployment controller. So kustomize did address (1) and (2), but not (3): keeping around old ConfigMaps that were still referenced, while deleting the ones that were not. So I don’t know how popular this approach is (about 61k search hits for configMapGenerator on GitHub), but only kustomize users would use it.</p><p>In general, pruning (i.e., deleting resources removed from the desired state) for kustomize is implemented by other tools, since kustomize doesn’t apply resources to the cluster itself. Pruning in kubectl apply identified applied resources using labels and was considered error prone, and an incorrect specification on the command line could cause resources to be deleted unintentionally.</p><p>Today, GitOps tools like ArgoCD and Flux handle pruning. For pruning to work properly with these tools, so that generated ConfigMaps would not be deleted while they were still in use during rolling updates or rollbacks, old revisions of the ConfigMaps would need to remain in the set of resources being applied until they were no longer needed by prior ReplicaSet revisions. I’m not aware of tools that do this kind of working-set management (except ConfigHub, which is discussed below).</p><h4>Helm</h4><p>Helm can <a href="https://stackoverflow.com/questions/57219325/how-to-create-a-config-map-from-a-file-in-helm">generate ConfigMap contents</a> of mutable ConfigMaps with stable names:</p><pre># Source - https://stackoverflow.com/a/65291822<br># Posted by PjoterS<br># Retrieved 2026-03-29, License - CC BY-SA 4.0<br><br>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: my-config<br>data:<br>  {{- (.Files.Glob &quot;configmap/*&quot;).AsConfig | nindent 2 }}</pre><p>It can’t generate immutable ConfigMaps easily because Helm’s pruning mechanism would prune old revisions immediately when the ConfigMap’s name changed.</p><p>Helm has an <a href="https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments">“official” workaround</a> for the rollout problem, which is to add a hash of a referenced ConfigMap to the podSpec template. This sets a pod annotation, but sometimes an environment variable value of a container is set instead:</p><pre>kind: Deployment<br>spec:<br>  template:<br>    metadata:<br>      annotations:<br>        checksum/config: {{ include (print $.Template.BasePath &quot;/configmap.yaml&quot;) . | sha256sum }}</pre><p>Like the kustomize resource name change, this also causes the podSpec template to be modified when the ConfigMap contents change. The key difference is that there’s only a single ConfigMap revision that is modified in place. This is mechanically simpler, but any pod that is created for any reason once the modified ConfigMap is applied will pick up the new ConfigMap data, and any processes using the inotify approach will pick up the new data immediately, which defeats the purpose of performing a rolling update.</p><h4>Reloader</h4><p>A number of tools have been developed by the community to address this gap, as well. A popular one, referenced in <a href="https://github.com/kubernetes/kubernetes/issues/22368">issue 22368</a>, is <a href="https://github.com/stakater/reloader">Reloader</a>. Reloader watches for changes and automatically triggers rollouts in workload controllers that are annotated:</p><pre>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: my-app<br>  annotations:<br>    reloader.stakater.com/auto: &quot;true&quot;<br>spec:<br>  template:<br>    metadata:<br>      labels:<br>        app: my-app<br>    spec:<br>      containers:<br>        - name: app<br>          image: your-image<br>          envFrom:<br>            - configMapRef:<br>                name: my-config<br>            - secretRef:<br>                name: my-secret</pre><p>Like the Helm method, it also triggers a rolling update by <a href="https://github.com/stakater/Reloader/blob/master/internal/pkg/callbacks/rolling_upgrade.go#L425">modifying annotations in the podSpec template</a>:</p><pre>  AnnotationTemplate: `{&quot;spec&quot;:{&quot;template&quot;:{&quot;metadata&quot;:{&quot;annotations&quot;:{&quot;%s&quot;:&quot;%s&quot;}}}}}`,</pre><p>While I wouldn’t say that approach is simple, it is simpler than copying ConfigMaps on write, updating references, and garbage collecting them when no longer needed. The ArgoCD project <a href="https://github.com/argoproj/argo-cd/issues/13642">recommends this approach also</a>.</p><h4>ConfigHub</h4><p>For ConfigHub, I implemented both approaches: the generation of unique, immutable ConfigMaps and simply updating the podSpec template with a hash of the ConfigMap.</p><p>In either case, ConfigHub generates the ConfigMap. <a href="https://docs.confighub.com/guide/app-config/">Application configuration is stored</a> in separate <a href="https://docs.confighub.com/background/entities/unit/">Units</a> in its native format. Currently supported formats include YAML, JSON, Properties, INI, TOML, Env, and Text.</p><p>For example, Properties configuration might look like this:</p><pre>database.port=5432<br>database.ssl.enabled=true</pre><p>Storing the configuration in its native format makes it easier to understand, easier to validate, and easier to keep consistent across environments (e.g., with local development), and enables many <a href="https://docs.confighub.com/guide/functions/">standard ConfigHub functions</a> to operate on it, since it is structured key/value data. It also works with ConfigHub’s <a href="https://docs.confighub.com/guide/variants/">variant-management mechanisms</a>, for example enabling changes to be automatically merged across variants.</p><p><strong>Unique, immutable ConfigMaps</strong></p><p>To indicate to ConfigHub what name to use as the prefix of the name of the data key in the generated ConfigMap, a special property needs to be added to the configuration data:</p><pre>configHub.configName=my-app-config</pre><p>This property is omitted from the data when generating the ConfigMap. Using an Env file as an example:</p><pre>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: my-config-03b362047a<br>...<br>immutable: true<br>data:<br>  my-app-config.env: |<br>    DATABASE_PORT=5433<br>    DATABASE_SSL_ENABLED=false</pre><p>The reference in the <a href="https://docs.confighub.com/background/entities/link/">linked</a> Deployment is updated via ConfigHub’s <a href="https://docs.confighub.com/background/concepts/needsprovides/">needs/provides value propagation mechanism</a>. This is an example of a volume, but ConfigHub also supports envFrom:</p><pre>      volumes:<br>      - name: config-volume<br>        configMap:<br>          name: my-config-03b362047a</pre><p>ConfigHub knows where such reference fields are located in built-in Kubernetes APIs by using <a href="https://github.com/kubernetes-sigs/kustomize/blob/4ef1cf823432786e937b587a7d194a182d62cedd/api/internal/konfig/builtinpluginconsts/namereference.go#L4">the list from kustomize</a>. For non-built-in CustomResourceDefinitions, it’s possible to configure the locations in a couple different ways, but that’s a topic for another post.</p><p>By default, ConfigHub will automatically maintain the 10 most recently generated ConfigMaps, because that’s the default revisionHistoryLimit in Kubernetes. That number is configurable via a RevisionHistoryLimit option (see <a href="https://docs.confighub.com/guide/app-config/">the documentation</a>), which we could derive automatically from the Deployment in the future.</p><p><strong>Mutable ConfigMaps</strong></p><p>ConfigHub also supports mutable ConfigMaps that have stable names.</p><pre>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: my-config</pre><p>The Deployment references are similarly set automatically.</p><p>Additionally, ConfigHub will generate a hash of the ConfigMap and propagate it to an annotation in the podSpec template:</p><pre>  template:<br>    metadata:<br>      labels:<br>        app: mydep<br>      annotations:<br>        confighub.com/Hash: a055c86133</pre><p>That will trigger re-creation of the Deployment’s pods similarly to the Helm and Reloader solutions.</p><h3>Wrap up</h3><p>That hopefully helped you understand the two main alternatives for rolling out ConfigMap changes today:</p><ol><li>Generate unique, immutable ConfigMaps and update references to them. Delete old revisions of the ConfigMaps when they are no longer needed. This ensures correct, reliable rolling update behavior, even in cases where pods are re-created for other reasons during the rollout and where processes watch the file using inotify.</li><li>Update the podSpec templates of workload controllers, such as Deployments, when the contents of referenced ConfigMaps (and/or Secrets) change, such as by generating and setting a hash value in an annotation. This is simpler and usually works well enough that the approach has been widely adopted.</li></ol><p>I wouldn’t hold my breath for this being addressed in upstream Kubernetes at this point, but the feature is feasible. The change would have to be thoroughly tested, but I think it could be isolated to the Deployment controller.</p><p>Have you ever stumbled on this quirk of Kubernetes when making a change to a ConfigMap and wondering why your application didn’t observe the change? Did you comment on or +1 a comment on issue 22368? Do you generate ConfigMaps from native application configuration files with kustomize or with helm rather than maintaining them as ConfigMaps manually? Do you use Reloader, or do you use another tool?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1431398c0866" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/the-challenge-of-configmap-rollouts-in-kubernetes-1431398c0866">The Challenge of ConfigMap rollouts in Kubernetes</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vetting Kubernetes configuration with Kyverno prior to deployment]]></title>
            <link>https://itnext.io/vetting-kubernetes-configuration-with-kyverno-prior-to-deployment-131ee33d315b?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/131ee33d315b</guid>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[policy-as-code]]></category>
            <category><![CDATA[kyverno]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Wed, 18 Mar 2026 15:42:57 GMT</pubDate>
            <atom:updated>2026-03-18T16:13:06.595Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OQRPxqEf16QtckCW" /><figcaption>Kyverno logo</figcaption></figure><p>In honor of <a href="https://github.com/cncf/toc/issues/1967#issuecomment-4074557252">Kyverno’s graduation in CNCF</a>, I thought I’d write a post about using Kyverno to vet changes to Kubernetes configuration prior to deployment.</p><p>First, what is <a href="https://kyverno.io/">Kyverno</a>? Kyverno is a popular Kubernetes “policy as code” tool. It is integrated with <a href="https://lenshq.io/blog/kubernetes-security-kyverno-policy-enforcement-guide">Lens</a>, <a href="https://devtron.ai/blog/how-to-secure-kubernetes-clusters-with-kyverno-policies/">Devtron</a>, and a number of other tools. I previously mentioned Kyverno in one of my earliest posts, on <a href="https://itnext.io/what-are-state-based-policy-constraints-good-for-019b70f0b698">state-based policy constraints</a>, and in one of my most popular posts, on <a href="https://itnext.io/kubernetes-configuration-linting-tools-699ddeedaeec">Kubernetes configuration linting tools</a>.</p><p>Kyverno policies can be expressed as Kubernetes custom resources and, therefore, as configuration documents. That means they can be changed and applied like any other Kubernetes resources, such as with <a href="https://itnext.io/is-gitops-actually-useful-a1c851ba99d8">GitOps tools</a> like <a href="https://argo-cd.readthedocs.io/en/stable/">ArgoCD</a> or <a href="https://fluxcd.io/">Flux</a>, and are easy to discover through the Kubernetes API. They don’t have to be pushed to or pulled from another storage location.</p><p>These policies can include fairly arbitrary expressions written in <a href="https://cel.dev/">CEL</a> to test values of Kubernetes resources, in effect imposing <strong>constraints</strong> on Kubernetes resources accepted as valid. This allows you to ban configurations that Kubernetes may allow, but that you don’t want to allow in your cluster(s) or at least in some namespaces. This differs from <a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">RBAC authorization</a> by constraining WHAT changes are made rather than by constraining WHO made the changes.</p><p>For example, here’s a policy that prohibits the use of the :latest tag in Deployments by checking the value of the appropriate field of the resource (slightly simplified compared to <a href="https://github.com/kyverno/policies/blob/main/best-practices-vpol/disallow-latest-tag/disallow-latest-tag.yaml">the sample</a>):</p><pre>apiVersion: policies.kyverno.io/v1<br>kind: ValidatingPolicy<br>metadata:<br>  name: disallow-latest-tag<br>spec:<br>  validationActions: [Deny]<br>  matchConstraints:<br>    resourceRules:<br>      - apiGroups: [&#39;apps&#39;]<br>        apiVersions: [v1]<br>        operations: [CREATE, UPDATE]<br>        resources: [deployments]<br>  validations:<br>    - expression: &gt;<br>        object.spec.template.spec.containers.all(c,<br>          !c.image.endsWith(&#39;:latest&#39;)<br>        )<br>      message: &quot;Using &#39;latest&#39; tag is not allowed.&quot;</pre><p>Kyverno has a <a href="https://github.com/kyverno/policies">sizable policy library</a>, and of course you can write your own, as well.</p><p>Normally, these policies are enforced by <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">Kubernetes dynamic admission control</a>. However, one then doesn’t find out until applying the configuration whether it passes the policy checks or not. If using ArgoCD or Flux, this failure may show up as a deployment that never successfully fully synchronizes with the cluster. It would be better to find out about such problems earlier and in a more obvious way.</p><p>The typical approach to checking the policies in advance would be to render the Helm charts, kustomizations, <a href="https://cdk8s.io/docs/latest/basics/constructs/">cdk8s constructs</a>, etc. to plain Kubernetes YAML, and then use the <a href="https://kyverno.io/docs/guides/applying-policies/#in-pipelines">kyverno CLI to apply the policies</a> to the output, generally in CI.</p><pre>% kyverno apply disallow-latest.yaml --resource=./rendered-resources/<br><br>Applying 1 policy rule(s) to 1 resource(s)...<br>policy disallow-latest-tag -&gt; resource default/Deployment/latest-deployment failed:<br>1 -  Using &#39;latest&#39; tag is not allowed.<br><br><br>pass: 0, fail: 1, warn: 0, error: 0, skip: 0</pre><p>Because the policy specifications are likely owned by a central team, such as a platform team or security team, rather than by the application team, and they are applied to multiple applications, they are typically in a different git repository. Fortunately, the kyverno CLI supports that (<a href="https://github.com/kyverno/kyverno/pull/13012">private repo support</a>):</p><pre>% kyverno apply https://github.com/kyverno/policies/tree/main/best-practices-vpol \<br>    --resource=./rendered-resources/</pre><p>This works, but there are multiple steps between making a configuration change and finding out that there was a problem with the change, results in CI are buried in log files unless they are pushed somewhere, and for teams that own their own CI pipelines/workflows, the policies are opt-in, and must be added to every repo’s CI pipelines/workflows and kept up to date.</p><p>I integrated Kyverno with <a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a> to demonstrate how Kyverno and similar policy tools can be leveraged to prevent misconfiguration by validating the configuration after every change.</p><p>First, a little background on ConfigHub. In ConfigHub, configuration is always fully rendered, or <a href="https://itnext.io/kubernetes-configuration-and-infrastructure-as-code-taxonomy-62ab4dd7e82f">WET</a> — <a href="https://itnext.io/what-is-configuration-as-data-210b0c4be324">configuration is represented as data</a>. In the case of the Kubernetes Deployment that failed the policy check above, it would look as follows, with no variables, conditionals, loops, or other computation:</p><pre>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: latest-deployment<br>  namespace: default<br>spec:<br>  replicas: 1<br>  selector:<br>    matchLabels:<br>      app: test<br>  template:<br>    metadata:<br>      labels:<br>        app: test<br>    spec:<br>      containers:<br>      - name: test-container<br>        image: nginx:latest</pre><p>Configuration is stored in <a href="https://docs.confighub.com/background/entities/unit/">units</a> in ConfigHub rather than as files in git. You could upload the configuration above into a unit with:</p><pre>% cub unit create latest-deployment latest-deployment.yaml</pre><p>(There’s a <a href="https://docs.confighub.com/get-started/tutorial/">basic tutorial to get started</a> with ConfigHub. If you’re using Helm or kustomize with ArgoCD or Flux, then we can import and render the configuration automatically, but that’s a topic for another post.)</p><p>ConfigHub has a capability analogous to Kubernetes dynamic admission webhooks called <a href="https://docs.confighub.com/background/entities/trigger/">Triggers</a>. Triggers invoke <a href="https://docs.confighub.com/background/entities/function/">functions</a> automatically on each configuration change. A function is code that executes on configuration data that ConfigHub can invoke. Some standard functions are built into ConfigHub. You can also <a href="https://github.com/confighub/sdk/blob/main/function-impl/README.md#authoring-functions">write your own functions</a> and build them into <a href="https://docs.confighub.com/background/entities/worker/">workers</a>.</p><p>A worker is analogous to a <a href="https://kubernetes.io/docs/concepts/architecture/controller/">controller</a> in Kubernetes or a runner in CI (though it has specific functionality built in rather than executing arbitrary containers). <a href="https://docs.confighub.com/guide/workers/">You run it</a>, typically in your Kubernetes cluster, and it connects back to ConfigHub and performs operations on your behalf.</p><p>As an example, I created a <a href="https://docs.confighub.com/guide/custom-workers/">custom worker</a> that calls Kyverno’s admission webhook to evaluate policies on configurations in ConfigHub.</p><p>To try it, clone our sdk:</p><pre>% git clone https://github.com/confighub/sdk</pre><p>The code is in <a href="https://github.com/confighub/sdk/tree/main/examples/kyverno-server">examples/kyverno-server</a> , which also contains a <a href="https://github.com/confighub/sdk/blob/main/examples/kyverno-server/demo.sh">demo script</a> that creates a <a href="https://kind.sigs.k8s.io/">kind</a> cluster, installs Kyverno and some policies, starts the custom worker, and invokes the function imperatively, as follows:</p><pre>% cub function do vet-kyverno-server --unit latest-deployment \<br>    --worker &quot;$KYVERNO_WORKER&quot;</pre><p>Validation functions return a pass/fail result and details about issues found in the case of failure.</p><pre>false 0 policy &quot;disallow-latest-tag&quot; rule &quot;validation&quot;: Using &#39;latest&#39; tag is not allowed.<br>Attributes:<br>&lt;nil&gt;   default/latest-deployment apps/v1/Deployment</pre><p>To create a trigger using this function, you can do:</p><pre>% cub trigger create kyverno Mutation Kubernetes/YAML vet-kyverno-server \<br>    --worker &quot;$KYVERNO_WORKER&quot;</pre><p>And then when changes are made to the configuration data, the function would automatically be invoked, and if it failed then an <em>apply gate</em> would be attached that would block applying the configuration to the cluster until the issue was addressed.</p><p>This approach enables shifting Kubernetes admission control left, to the point where the configuration is modified in storage, prior to deploying changes to the cluster. Any admission controller you have registered with your cluster to be invoked on Kubernetes API calls can also be invoked ahead of time, by your worker, through ConfigHub. Because the worker calls back to ConfigHub, ConfigHub does not need access to your cluster. However, if you’d prefer that the worker didn’t call your admission webhooks, check out <a href="https://github.com/confighub/sdk/tree/main/examples/kyverno">the other Kyverno worker example</a>, which executes the Kyverno CLI with specified policies and resources similar to the examples above.</p><p>These Kyverno worker functions are just a couple examples of the kind of validation and policy checks that can be enforced centrally using ConfigHub. There’s also a <a href="https://github.com/confighub/sdk/tree/main/examples/kube-score">kube-score example</a>, as an example of integrating a configuration scoring tool. For schema validation, kubeconform is integrated as the vet-schemas function. We plan integrations with <a href="https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/">Kubernetes validating admission policies</a>, <a href="https://github.com/open-policy-agent/opa">Open Policy Agent</a>, and other tools in the future. Additionally, you can use CEL expressions (vet-cel) or Starlark (vet-starlark) to write policy checks without building and running your own worker for that purpose.</p><p>Whether you are making a change to a configuration by hand, with yqor another tool, with a <a href="https://docs.confighub.com/guide/functions/">ConfigHub function</a>, or with an AI agent, with ConfigHub, not only is the change not obfuscated by layers of configuration generation code, but it can also be immediately automatically vetted, thereby reducing the opportunity for misconfiguration.</p><p>How have you tried to prevent common mistakes when people in your organization change Kubernetes configurations? Do you render your configurations and evaluate Kyverno or other policies in PRs before the changes are merged? Is there anything you would like to improve with that process? Do changes ever bypass the policy checks?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=131ee33d315b" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/vetting-kubernetes-configuration-with-kyverno-prior-to-deployment-131ee33d315b">Vetting Kubernetes configuration with Kyverno prior to deployment</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kubernetes Strategic Merge Patch]]></title>
            <link>https://itnext.io/kubernetes-strategic-merge-patch-4bdd19b48789?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/4bdd19b48789</guid>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 09 Mar 2026 20:37:34 GMT</pubDate>
            <atom:updated>2026-03-09T20:37:34.716Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3Q2edHiUq6nT1-MsvIgCkQ.png" /><figcaption>Image generated by Gemini</figcaption></figure><p>If you’ve used Kubernetes kubectl apply, <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">server-side apply</a>, or <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">kustomize</a>, then you may have encountered the “strategic merge patch” feature. “Strategic merge patch” is a mouthful. What does it mean? In what sense is it “strategic”? Why does it exist?</p><p>I explained this several years ago and copied it into my first blog post, on the <a href="https://itnext.io/the-technical-history-of-kubernetes-2fe1988b522a">Technical History of Kubernetes</a>. It’s also explained in the <a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#notes-on-the-strategic-merge-patch">Kubernetes documentation</a>, in the <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md">original design document</a>, on <a href="https://stackoverflow.com/questions/71165168/can-someone-explain-patchesstrategicmerge">Stackoverflow</a>, and by some <a href="https://oneuptime.com/blog/post/2026-02-09-strategic-merge-patch-vs-json-merge/view">others’ blog posts</a>. However, I was working on a new implementation for <a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a> that could be used for non-Kubernetes configuration as well, and it seemed like a good topic to cover again, because the reasons why strategic merge patch exists may not be obvious.</p><p>Strategic merge patch was developed originally by <a href="https://www.linkedin.com/in/ghodss/">Sam Ghods</a> for use <a href="https://github.com/kubernetes/kubernetes/issues/1702">in </a><a href="https://github.com/kubernetes/kubernetes/issues/1702">kubectl apply</a> , but it was necessary as a consequence of several other design decisions.</p><p>First, relatively early in the Kubernetes project, <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#lists-of-named-subobjects-preferred-over-maps">a decision had been made</a> that map keys in the API should not have structural significance in the API. The main rationale was the difficulty in <a href="https://github.com/kubernetes/kubernetes/issues/853#issuecomment-51851041">distinguishing map keys from fields in the API</a> without a schema. Additionally, even with a JSON or OpenAPI schema, documentation of map keys must be <a href="https://swagger.io/docs/specification/v3_0/data-models/dictionaries/">embedded in the description of the whole map</a>.</p><p>The original example was the specification of a port’s name using a field:</p><pre>ports:<br>  - name: www<br>    hostPort: 80<br>    containerPort: 80<br>    protocol: tcp</pre><p>Rather than this specification of its name using a map key:</p><pre>ports:<br>  www:<br>    hostPort:80<br>    containerPort:80<br>    protocol: tcp</pre><p>Kubernetes used the former approach for its API, where as configuration tools, such as the precursor to Docker Compose (Fig) chose the latter approach. The issue <a href="https://github.com/kubernetes/kubernetes/issues/2004">came up multiple times</a> because common configuration tasks were made more difficult by this decision. Later, cases were added where order matters, however, such as <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/">initContainers</a> , though that could have been solved using another field instead.</p><p>Second, I wanted the <a href="https://itnext.io/on-using-the-kubernetes-resource-model-for-declarative-configuration-a5dd11b99546">serialized Kubernetes API representation to be used as the basis for configuration formats</a>, rather than developing and maintaining a parallel but slightly different representation for configuration.</p><p>Third, I wanted the configuration primitives to be tolerant of fields being set dynamically by automation. <a href="https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/">Borg’s configuration tool</a> had ad hoc special-case logic to deal with such cases on a one-by-one basis, and the Borg API was modified to handle the most common cases, such as horizontal autoscaling. This was not an extensible approach and new automation components could be blocked until changes could be made to critical core libraries, since otherwise changes made by those components would be detected as <a href="https://itnext.io/why-configuration-drift-is-so-hard-to-avoid-in-practice-443248cafc9c">configuration drift</a> and would be undone by configuration tools.</p><p>That principle also enabled us to <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#defaulting">explicitly populate default values</a> in the API, so that client tools would not need to guess how the API interpreted unspecified fields or read them from a schema. Also, a hardcoded schema would not work for fields set by <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">admission control</a>, which we felt was important to support organization-specific policies.</p><p>Fourth, while admission control and defaulting are synchronous, <a href="https://kubernetes.io/docs/concepts/architecture/controller/">controllers</a>, such as the <a href="https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/">horizontal pod autoscaler</a>, operate asynchronously. That meant that if the state were completely overwritten without incorporating dynamically set fields, there would be a window of time where those API fields would be incorrect, and other controllers, such as the <a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployment</a> and <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSet</a> controllers, could act on those incorrect values.</p><p>The requirement to handle API fields being set asynchronously effectively necessitated <strong>merging</strong> of the desired state specified by configuration with the state in the API, rather than just replacing it completely. It turned out that merging was a key capability for configuration tools more generally, because <a href="https://itnext.io/reflections-on-declarative-configuration-c2fe1c1e50d5">declarative configuration</a> creates <strong>copies</strong> of documents that need to be kept in sync.</p><p>Most configuration tools, such as <a href="https://helm.sh/">Helm</a>, <strong>generate</strong> configuration using a <a href="https://itnext.io/the-unidirectionality-of-infrastructure-as-code-creates-asymmetry-40c9f5eed959">unidirectional process</a>. When either the generation code (or templates) or input values change, the whole configuration must be regenerated. Such a generated configuration contains specifications for whole resources, but only statically specified values and does not include dynamically set values.</p><p><a href="https://itnext.io/how-infrastructure-as-code-tool-implementations-differ-from-imperative-tools-31607c3ed37b">Other client tools</a>, such as CLIs, can generally update selected fields of resources rather than just replacing whole resources. They typically use <strong>patch</strong> APIs to do that.</p><p>There are two standard JSON patch specifications, <a href="https://datatracker.ietf.org/doc/html/rfc6902">JSON Patch</a> and <a href="https://datatracker.ietf.org/doc/html/rfc7386">JSON Merge Patch</a>. JSON Patch specifies a sequence of imperative field changes: add, replace, remove, move, copy, test. JSON Merge Patch accepts a document (in this case a resource) using the same structure as the original. For configuration use cases, JSON Merge Patch appeared more convenient than JSON Patch.</p><p>Also, on the surface, a merge patch looked like it could be produced without diffing. However, this was not the case. Sometimes fields need to be removed. One example in Kubernetes is a change from one volume source, like <a href="https://kubernetes.io/docs/concepts/storage/volumes/#emptydir">emptyDir</a>, to another, such as <a href="https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim">persistentVolumeClaim</a>. Since they support different options, they are structured in the API as separate fields — a union/oneOf essentially. Identifying such a case automatically requires a diff with the previously specified desired state and the field to be removed must be assigned null in JSON Merge Patch.</p><p>To simplify this procedure for clients, I <a href="https://github.com/kubernetes/kubernetes/issues/1178">had proposed</a> API support for automatically merging a resource specified by the client with fields stored in the server, by storing multiple sets of values. This proposal was not implemented, but API support for field tracking was eventually added years later in the form of server-side apply. Instead, to implement kubectl apply, we stored the previously specified desired state in an <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/">annotation</a>. In fact, that was one of <a href="https://github.com/kubernetes/kubernetes/issues/1201">the original motivations</a> for adding annotations to the API.</p><p>That solved the problem of diffing, but did not solve the problem of patching. The problem with JSON Merge Patch is its behavior with respect to arrays. From the RFC: “it is not possible to patch part of a target that is not an object, such as to replace just some of the values in an array.” That is a non-starter for the Kubernetes API, which extensively uses arrays, such as for the lists of containers in a <a href="https://kubernetes.io/docs/concepts/workloads/pods/">pod</a>, lists of ports as shown above, lists of environment variables, and so on.</p><p>So we needed to create our own patching mechanism in order to merge fields of array elements. But we also needed to handle the prohibition of the use of map keys. We can use the example of ports again. Let’s say a new port is inserted and another’s value is changed, and we want to merge with values in the API.</p><p>The original port:</p><pre>ports:<br>  - name: www<br>    containerPort: 80<br>    protocol: tcp</pre><p>And let’s say a hostPort field was added to bind the www port to port 80 via a separate API call:</p><pre>ports:<br>  - name: www<br>    containerPort: 80<br>    hostPort: 80<br>    protocol: tcp</pre><p>And then a user changed the configuration to:</p><pre>ports:<br>  - name: observability<br>    containerPort: 8090<br>    protocol: tcp<br>  - name: www<br>    containerPort: 8080<br>    protocol: tcp</pre><p>If just merging elements of the array positionally, index by index, then that would result in this in the API:</p><pre>ports:<br>  - name: observability<br>    containerPort: 8090<br>    hostPort: 80<br>    protocol: tcp<br>  - name: www<br>    containerPort: 8080<br>    protocol: tcp</pre><p>Rather than what was intended:</p><pre>ports:<br>  - name: observability<br>    containerPort: 8090<br>    protocol: tcp<br>  - name: www<br>    containerPort: 8080<br>    hostPort: 80<br>    protocol: tcp</pre><p>This example may seem contrived, but the API has many such arrays, and it seemed likely that something like this would happen.</p><p>Essentially such arrays should be treated similarly to maps even though they aren’t structured as maps. I call them <strong>associative arrays</strong>.</p><p>That brings us back to strategic merge patch. “Strategic” refers to the ability to specify a patchStrategyfor arrays, replace or merge. Additionally, in the case of merge, a patchMergeKey can be specified to identify the field of each element to use as the equivalent of a map key so that elements can be matched irrespective of their array positions.</p><p>To address the ports example above, the patchStrategy would be merge and the patchMergeKey would be name , to specify that the name field’s value should be used as the key for matching elements. That would enable the intended result to be achieved. However, in actuality, ports do not use the name field for merging, because the name field is optional. Instead, the containerPort field itself is used, along with the protocol, since TCP is not the only supported protocol. This can be seen in the <a href="https://github.com/kubernetes/api/blob/master/core/v1/types.go#L2945">Kubernetes API definition</a>:</p><pre> // +optional<br> // +patchMergeKey=containerPort<br> // +patchStrategy=merge<br> // +listType=map<br> // +listMapKey=containerPort<br> // +listMapKey=protocol<br> Ports []ContainerPort `json:&quot;ports,omitempty&quot; patchStrategy:&quot;merge&quot; patchMergeKey:&quot;containerPort&quot; protobuf:&quot;bytes,6,rep,name=ports&quot;`</pre><p>But the mechanism does achieve the intended result for other cases, such as containers.</p><p>Similar behavior can be specified in <a href="https://kubernetes.io/docs/reference/kubernetes-api/extend-resources/custom-resource-definition-v1/">CustomResourceDefinition</a> using x-kubernetes-list-type map and x-kubernetes-list-map-keys.</p><p>We later reused strategic merge patch <a href="https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesstrategicmerge/">in Kustomize</a>, to enable patching of what we called “overlays”, which are partial resource specifications. For example (from <a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/">the documentation</a>), to patch the spec.replicas field:</p><pre>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: my-nginx<br>spec:<br>  replicas: 3</pre><p>Kustomize gets information about array merge strategies and merge keys from <a href="https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/openapi/">OpenAPI documents</a>.</p><p>Kustomize’s ability to patch configuration made it useful not just for patching hand-written Kubernetes YAML files, to create <a href="https://itnext.io/variant-generation-is-the-primary-capability-of-infrastructure-as-code-773e6b404b49">variants</a> of the same configuration, but also to patch Kubernetes resources generated by other tools, such as Helm, enabling overrides.</p><p>ConfigHub supports <a href="https://itnext.io/how-are-variants-managed-in-confighub-061bc2f89cff">managing variants</a> by automatically merging changes from <em>upstream</em> configurations into <em>downstream</em> configurations, or, really, from any configuration into any other configuration, such as downstream to upstream or variant to variant.</p><p>As with Kustomize, ConfigHub can also use this mechanism to override values in generated configurations, also known as <a href="https://docs.confighub.com/guide/rendered-manifests/">rendered manifests</a>. Additionally, ConfigHub can <a href="https://itnext.io/what-is-bidirectional-gitops-ce0ced75fa1c">merge changes to the live state back into ConfigHub</a>, such as after a “break glass” operational change. This enables ConfigHub to maintain an accurate, up-to-date record of the state and eliminates artificial configuration drift.</p><p>Conceptually, the mechanism works similarly to Kustomize, but without the need to write patches by hand. It does that by computing a diff and encoding it in a form that can be used as a patch. To produce the patch correctly, the diff mechanism needs to be aware of array patch strategies and merge keys. Instead of requiring knowledge of the merge keys during both the diff and the patch processes, I chose to encode the merge keys in the patch.</p><p>More specifically, I encoded merge keys in resource field paths, as they would be in the case of maps rather than arrays. That was a convenient approach because a number of <a href="https://itnext.io/configuration-needs-an-api-b36f08b92551">ConfigHub functions</a> operate similarly in some ways to <a href="https://kubectl.docs.kubernetes.io/references/kustomize/builtins/">Kustomize transformers</a>, and operate on specific resource fields. It can be more convenient for users of such functions to locate fields using merge keys, such as environment variable names, rather than array indices. It’s effectively a simple kind of search within the configuration. I’ll write another post in the future that goes into more detail on how these “visitor” functions work.</p><p>Anyway, that was a fairly deep dive on the history of strategic merge patch. It was implemented to support the merging of configuration into the live state in Kubernetes, but merging, in general, is a key configuration capability that it facilitates, and one that we support in a general-purpose way in ConfigHub.</p><p>Have you wondered why “strategic merge patch” is called that? Have you wondered why it’s needed? Do you wish it weren’t needed? Or have you wondered why other tools don’t support it? Have you experienced surprising or unfortunate merge behavior? Do you wish that arrays of command-line flags could be merged? Do CRDs that you use specify merge keys appropriately?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4bdd19b48789" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/kubernetes-strategic-merge-patch-4bdd19b48789">Kubernetes Strategic Merge Patch</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Abstraction is the wrong way to simplify configuration]]></title>
            <link>https://itnext.io/abstraction-is-the-wrong-way-to-simplify-configuration-81ac4bad02ab?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/81ac4bad02ab</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[platform-engineering]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 18:05:33 GMT</pubDate>
            <atom:updated>2025-12-02T20:59:05.009Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zFjIrK-qqEQBKseZed4afw.png" /><figcaption>abstract art image “Fire truck” generated by Gemini</figcaption></figure><p>I’ve been asked how abstraction works in ConfigHub.</p><p>In the context of configuration, “<strong><em>abstraction</em></strong>” usually refers to encapsulating a set configuration generation artifacts, such as templates, and exposing a defined set of input parameters. Sometimes the artifacts are bundled into a <a href="https://itnext.io/packages-in-infrastructure-as-code-138fcf463880">package</a>, such as a <a href="https://helm.sh/">Helm chart</a> or <a href="https://developer.hashicorp.com/terraform/language/modules">Terraform module</a>. The write-only <a href="https://itnext.io/the-unidirectionality-of-infrastructure-as-code-creates-asymmetry-40c9f5eed959">generation process is undirectional</a> and produces the final configuration as output. Configuration changes can only be made through changes to input parameter values, through changes to the generation artifacts, or by using an another mechanism, such as <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">kustomize</a>, to modify the output.</p><p>Often the goal of abstraction is to simplify configurations for consumers of the abstraction. Over and over again over the past 15+ years I have seen that approach fail to simplify the configuration as much as was desired and/or expected. It works, but it is not as simple for the consumer as one would hope.</p><p>One reason is many users of the abstraction care about some details of the output configuration. Certainly <a href="https://itnext.io/what-is-bidirectional-gitops-ce0ced75fa1c">operators may care in emergency situations</a>. A consequence is that users need to reverse engineer how to make the configuration generation process result in the desired output, which may not be obvious from the abstraction. I find I almost always need to read Helm templates to understand how to set input values, for example, and then I need to inspect the output to verify it did what I intended. <a href="https://www.linkedin.com/in/adamjacob/">Adam Jacob</a> has called this the <a href="https://www.youtube.com/watch?v=5lPa2U239C4&amp;t=2014s">“200% knowledge problem”</a> — you need to understand what you want the output to be (e.g., the values of fields of <a href="https://itnext.io/on-using-the-kubernetes-resource-model-for-declarative-configuration-a5dd11b99546">Kubernetes resources</a>) and the mechanism that generates it.</p><p>Another pervasive problem is the tendency for the sizes of abstractions’ interfaces to grow to be comparable in size as or even larger than their output configurations. This is particularly prevalent in <a href="https://itnext.io/i-shouldnt-have-to-read-installer-code-every-day-4dc1e5f9ee1a">off-the-shelf configurations that need to handle many deployment scenarios</a>.</p><p>This is a fairly standard <a href="https://itnext.io/the-tension-between-flexibility-and-simplicity-in-infrastructure-as-code-6cec841e3d16">flexibility vs. simplicity tradeoff</a>: the more flexible a configuration needs to be, the more complicated its interface becomes. <a href="https://itnext.io/infrastructure-as-code-cargo-culting-patterns-b714f680db4b">The alternatives</a> to enable more field values to be set — forking the templates, adding more and more parameters over time, parameterizing all fields up front, creating larger and larger patches , or abandoning the abstraction entirely — all result in increased aggregate complexity.</p><p>Additionally, efforts to modularize configuration and make it <a href="https://itnext.io/kubernetes-configuration-and-infrastructure-as-code-taxonomy-62ab4dd7e82f">DRY</a> not only make the configuration harder to understand, but also increase <a href="https://itnext.io/modularity-and-blast-radius-in-infrastructure-as-code-9a1f6432196e">blast radius</a> and the complexity of making changes.</p><p>Abstraction through encapsulation and parameterization is a case of software engineers <a href="https://itnext.io/how-software-engineering-instincts-clash-with-infrastructure-as-code-6b18a9cd9cef">trying to use a software-engineering technique</a> to solve what is fundamentally a UX problem.</p><p>Users do not adopt Infrastructure/Configuration as Code (IaC) because they think it is simpler to use than other tools. The user experience of writing and changing configuration is <a href="https://itnext.io/complexity-and-toil-in-infrastructure-as-code-6ca9a6d2af37">more complicated</a> and <a href="https://itnext.io/infrastructure-as-code-is-artisanal-automation-2b6b7545c100">more manual</a>. <a href="https://itnext.io/what-drives-users-to-infrastructure-as-code-848e8640a506">Users adopt IaC for the capabilities it enables</a>.</p><p>As currently implemented, encapsulating abstractions make simplifying the user experience more difficult. One pragmatic reason is that each such abstraction is a bespoke interface and the cost of providing user assistance is harder to amortize. That’s true even for AI agents, which are not trained on your custom configuration templates and modules.</p><p>On the other hand, when we treat <a href="https://itnext.io/what-is-configuration-as-data-210b0c4be324">configuration as data</a> and <a href="https://itnext.io/configuration-needs-an-api-b36f08b92551">build tools targeting well defined schemas</a>, each tool provides views over and actions on the underlying configuration data. We can then apply standard UX techniques to standard APIs to simplify user experience. Through techniques like progressive disclosure, exceptional cases don’t need to pollute the common-case experience.</p><p>With this approach, using additional tools also doesn’t necessarily complicate everyone’s ability to understand the configuration. Configuration as data enables stronger separation of concerns. I don’t need to understand every tool that could modify a particular field in a configuration in order to use just one tool. And I don’t need to understand any of the tools in order to understand a field’s value. I can just look at the value, which could be extracted easily by a tool, as in <a href="https://itnext.io/a-concrete-example-of-configuration-as-data-575115e2ba46">my previous </a><a href="https://itnext.io/a-concrete-example-of-configuration-as-data-575115e2ba46">get-image example</a>.</p><p>The looser coupling between the configuration data and the implementations of tools that read and write the configuration benefits tool authors similarly, as they don’t need to understand every tool or any tool necessarily in order to write a new tool to interact with particular configuration details. The tools can be changed and even replaced over time without affecting existing configuration data.</p><p>If an abstraction was as much for restricting what the user could do as much as simplification, that purpose can be achieved with <a href="https://itnext.io/what-are-state-based-policy-constraints-good-for-019b70f0b698">constraints on the output</a>, which many use via <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">dynamic admission control</a> in Kubernetes already. That approach is also more auditable than implicit and even explicit checks buried inside configuration generation code. So this is not a compelling reason to stick to the monolithic encapsulation approach.</p><p>We’re at the early stages of exploring what is feasible to build on top of configuration as data, but it’s already clear that it opens up <a href="https://itnext.io/non-infrastructure-as-code-based-cloud-and-kubernetes-management-and-automation-tools-f5c6bde7fc22">a whole new world of possible tools we could build</a>. One of the strengths of Kubernetes is the <a href="https://medium.com/@bgrant0607/is-the-kubernetes-cncf-ecosystem-too-big-complex-and-fragmented-350d52f37e31">large ecosystem of tools around it</a>. I look forward to the day when the response to someone needing to make a complex configuration change for a particular purpose is “there’s a tool for that.”</p><p>Are your configuration abstractions extremely wide and complicated? Do you find that the abstractions erode over time? Do you find yourself reading templates in order to figure out how to use them properly? Do existing configuration tools get in the way of building internal developer platform functionality? If it were easier to build tools to modify and analyze configuration, what tools would you want to build?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/infrastructure-as-code-and-declarative-configuration-8c441ae74836">Infrastructure as Code and Declarative Configuration series</a> or in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81ac4bad02ab" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/abstraction-is-the-wrong-way-to-simplify-configuration-81ac4bad02ab">Abstraction is the wrong way to simplify configuration</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Configuration needs an API]]></title>
            <link>https://itnext.io/configuration-needs-an-api-b36f08b92551?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/b36f08b92551</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[gitops]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 16:49:27 GMT</pubDate>
            <atom:updated>2025-11-24T23:17:21.846Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e4QrfHWOKkJIMbJ--BEmBQ.png" /><figcaption>image generated by Gemini</figcaption></figure><p>If you’ve used <a href="https://itnext.io/kubernetes-configuration-in-2024-434abc7a5a1b">Kubernetes configuration tools</a> like Helm, cdk8s, or pretty much any other, you probably expect to need to write and edit the configuration by hand. Or probably now you try to use AI, which has improved significantly since <a href="https://itnext.io/the-status-of-llm-based-terraform-generation-at-the-end-of-2024-e31639068318">last year</a>, but still can’t perform repeated modifications predictably and reliably.</p><p>But what if you thought about your configuration as having an API and a schema?</p><p>You would build and use tools. You can do that with <a href="https://www.confighub.com/">ConfigHub</a>, which manages <a href="https://docs.confighub.com/background/config-as-data/"><strong>configuration as data</strong></a>.</p><p>ConfigHub supports a mechanism called <a href="https://docs.confighub.com/guide/functions/">functions</a>, which you can write <a href="https://github.com/confighub/sdk/blob/main/function/README.md">with our SDK</a>, that can be invoked on selected configurations.</p><p>Some simple examples:</p><ul><li>Need to update an image tag? Invoke set-image-reference</li><li>Scale horizontally? set-replicas</li><li>Scale vertically? set-container-resources</li><li>Change an environment variable? set-env-var</li><li>Add a label? set-label</li><li>Set the security context to best-practice values? <br>set-pod-defaults --security-context</li></ul><p>I use mutations as an example because they present a greater contrast with “as code” approaches, where <a href="https://itnext.io/configuration-editing-is-imperative-fa9db379fbe4">editing</a> of templates, patches, and configuration generation code is not amenable to such tooling.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F2yzn79XcgCU%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D2yzn79XcgCU&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F2yzn79XcgCU%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a8f556ffe352dd01a997ae61095c4337/href">https://medium.com/media/a8f556ffe352dd01a997ae61095c4337/href</a></iframe><p>Functions and other tools built around the API can be single purpose or multi-purpose, but they do not need to be monolithic in the way that configuration generators do in “as code” approaches. The configuration data is the source of record, so tools can be composed together to construct the final configuration to deploy. This interoperability means that you do not need to reinvent the wheel when you need to modify some additional properties. Just perform additional modifications.</p><p>Functions can also be as specific or as generic as you like. A function like set-image-reference targets specific, built-in fields of specific resource types, which simplifies configuration changes, similar to a CLI, and can reduce the opportunity for errors. Other functions, such as set-string-path and yq-i can modify arbitrary fields, but you need to provide the paths to target.</p><p>Of course, validation, queries, and value extraction are also enabled by treating configuration as structured data with schemas. Want to know what image is running in each environment? get-image will extract that information for you.</p><p><a href="https://itnext.io/api-characteristics-that-make-it-easier-or-harder-to-integrate-infrastructure-as-code-tools-7a1910e6ed94">Other APIs are more challenging to maintain in a serialized form</a>, but the <a href="https://itnext.io/on-using-the-kubernetes-resource-model-for-declarative-configuration-a5dd11b99546">Kubernetes API was designed for this</a>. Kubernetes resources, even <a href="https://github.com/datreeio/CRDs-catalog">many custom resources</a>, and cloud resources have schemas that are stable and backward compatible.</p><p><a href="https://itnext.io/application-configuration-and-feature-flags-356f38712120">Application configuration</a> files may not have formal schemas, but such schemas can be defined. For example, I pointed an AI agent at <a href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-authentication/ldap/">Grafana’s LDAP configuration documentation</a> and asked it to generate a schema, and it produced a 250-line schema in one shot. A schema specification is not strictly required, but is useful for validation.</p><p>The point is that configuration properties are well defined and can be targeted by tools, not just written out by hand over and over again.</p><p>As we can see from the <a href="https://medium.com/@bgrant0607/is-the-kubernetes-cncf-ecosystem-too-big-complex-and-fragmented-350d52f37e31">rich ecosystem of tools built around Kubernetes</a>, when we build around a common API, more and more useful tools are added to the toolbox over time — forward progress is made. On the other hand, if we keep building new parallel but incompatible ecosystems of <a href="https://itnext.io/fundamental-challenges-with-infrastructure-as-code-imply-the-language-doesnt-matter-41030475c296">configuration generation languages</a>, whether <a href="https://itnext.io/kubernetes-configuration-in-2024-434abc7a5a1b">templates</a>, <a href="https://itnext.io/can-configuration-languages-dsls-solve-configuration-complexity-eee8f124e13a">DSLs</a>, or <a href="https://itnext.io/generating-configuration-using-general-purpose-programming-languages-19230a2c2573">general-purpose languages</a>, we’re reinventing the wheel over and over again. Not only that, but we’re limiting the kinds of tools that can be built around them to just a few kinds of tools due to the <a href="https://itnext.io/infrastructure-as-code-is-artisanal-automation-2b6b7545c100">complicated syntax</a> and <a href="https://itnext.io/the-unidirectionality-of-infrastructure-as-code-creates-asymmetry-40c9f5eed959">unidirectional process</a>:</p><ol><li>Syntax-aware IDEs.</li><li>Scaffolding tools — templating templates! These are fire-and-forget configuration generation tools, generally.</li><li>Tools that treat the configuration generation code as a black box, such as deployment tools and version-control systems.</li><li>Tools that manipulate input variable values.</li><li>Tools that operate on the rendered output, such as <a href="https://itnext.io/kubernetes-configuration-linting-tools-699ddeedaeec">validation tools</a> and <a href="https://itnext.io/generating-transforming-and-patching-kubernetes-configuration-with-kustomize-fb7b02476a1b">patching tools</a>.</li><li>AI-based tools.</li></ol><p>I have a <a href="https://www.linkedin.com/in/bgrant0607/">background in compilers</a>, but requiring compiler techniques or AI to build most kinds of tools drastically limits what can be built, who can build them, and when it makes sense to build them.</p><p>These kinds of tools are also largely anchored in human-driven processes. For example, validation tools find problems but cannot fix them, whereas automatic remediation is feasible with API-based tools.</p><p>I look forward to the tools will be developed once we’re not wrapping configuration data with obstructive <a href="https://itnext.io/complexity-and-toil-in-infrastructure-as-code-6ca9a6d2af37">layers of complexity</a>.</p><p>Are there tools that work directly with the Kubernetes API that you wish you could apply to configuration files as well? Would you or members of your organization <a href="https://itnext.io/what-drives-users-to-infrastructure-as-code-848e8640a506">prefer to use a GUI</a> rather than editing configuration files in git? Do you feel the <a href="https://itnext.io/complexity-and-toil-in-infrastructure-as-code-6ca9a6d2af37">toil of pushing changes through git</a> to make small configuration changes? Have you ever needed to make <a href="https://itnext.io/making-mass-changes-to-infrastructure-as-code-b1edea2f7c48">similar changes to dozens of configuration files</a>? What tools would you build to modify, inspect, analyze, or validate configuration if it were easier to do?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/infrastructure-as-code-and-declarative-configuration-8c441ae74836">Infrastructure as Code and Declarative Configuration series</a> or in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b36f08b92551" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/configuration-needs-an-api-b36f08b92551">Configuration needs an API</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is Bidirectional GitOps?]]></title>
            <link>https://itnext.io/what-is-bidirectional-gitops-ce0ced75fa1c?source=rss-ccdbe9a99319------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce0ced75fa1c</guid>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Brian Grant]]></dc:creator>
            <pubDate>Mon, 17 Nov 2025 17:18:57 GMT</pubDate>
            <atom:updated>2025-11-17T21:40:53.590Z</atom:updated>
            <content:encoded><![CDATA[<p>And why (and when) would anyone want that?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m9nvJB7C7RdZpLIYfZLRNg.png" /><figcaption>image generated by Gemini</figcaption></figure><p>With <a href="https://medium.com/itnext/is-gitops-actually-useful-a1c851ba99d8">GitOps as currently defined and implemented</a>, a GitOps Operator will continuously apply the specified “desired” state from storage (git, <a href="https://medium.com/itnext/advantages-of-storing-configuration-in-container-registries-rather-than-git-b4266dc0c79f">OCI repositories</a>, etc.).</p><p>That’s useful in the case that a single apply would fail whereas an indeterminate number of applies would succeed. Such non-persistent failures could occur due to a number of reasons: unexpressed dependencies, lack of strong consistency, broken network connections, process restarts, etc. A continuous retry loop, essentially.</p><p>And when the configuration is updated in storage, the live state should be updated accordingly.</p><p>The main reason for the continuous reconciliation loop, however, is continuous drift detection and remediation. That is, if the live state is found to not match the configuration in storage, the live state will be updated to match the state from storage.</p><p>Configuration drift happens <a href="https://medium.com/itnext/why-configuration-drift-is-so-hard-to-avoid-in-practice-443248cafc9c">for a variety of reasons</a>. A common reason is that someone deliberately changed the live state. Why would someone do that when the configuration is specified in storage?</p><p>It could be that the person making the change wasn’t aware the configuration was managed in storage, though with Kubernetes configuration this is relatively unlikely, since <a href="https://medium.com/itnext/on-using-the-kubernetes-resource-model-for-declarative-configuration-a5dd11b99546">managing configuration</a> with version control has been a standard practice from the beginning.</p><p>It could be that someone was attempting to circumvent policy controls. Kubernetes RBAC and <a href="https://medium.com/itnext/what-are-state-based-policy-constraints-good-for-019b70f0b698">dynamic admission control</a> can be used to prevent that.</p><p>It could be an accident, such as a command invoked on the wrong cluster or namespace.</p><p>A more common occurrence is that someone made a change out of necessity.</p><p>For instance, let’s say something breaks due to an application update, an unexpected operational issue such as a recurring OOM occurs , or an application needs to be updated urgently to address a security concern.</p><p>A number of people have told me that they believe it’s not only simpler and faster but even safer to “break glass” and just change the cluster directly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*opzsvbNiQNNNUrpw0slkhg.png" /><figcaption>image generated by Gemini</figcaption></figure><p>If they use a GitOps tool, such as FluxCD or ArgoCD, they may need to suspend or disable it first. If they don’t, their change could be clobbered quickly, whatever problem they were trying to address.</p><p>I experienced something similar to that many years ago. I was on call. To address a production outage I needed to deploy a fix quickly. The normal process took a long time, too long. So I needed to do it another way. I deployed a new release and it fixed the problem. Seconds later, the previous release was redeployed by a reconciliation loop, taking the service down again. Disabling the reconciliation mechanism would take as long as using it to deploy, so I blocked its network access. Which would eventually be undone by another reconciliation loop.</p><p>Anyway, in the case that the operational change is successful and it needs to be made permanent, then it needs to be backported to the configuration source and the reconciliation mechanism needs to be re-enabled or unblocked. Typically, the configuration is in some kind of <a href="https://itnext.io/kubernetes-configuration-and-infrastructure-as-code-taxonomy-62ab4dd7e82f">DRY (Don’t Repeat Yourself)</a> format — templates, patches, etc. — so backporting the change is not trivial, at least not straightforward enough for a traditional deterministic tool to do. The values that need to be changed may be in a template, input variable values, value overrides, patches, configuration generation code, or the like. In the OOM case, we’d need to change container resources, as in this Helm chart excerpt:</p><pre>        resources:<br>          {{- toYaml .Values.paymentService.resources | nindent 10 }}</pre><p>In that case, the appropriate values file would need to be updated. If the values that needed to be changed weren’t parameterized, then the template would need to be changed, which would potentially affect all environments.</p><p>Both the configuration tool and the GitOps tool feel like they are getting in the way in this scenario. It should be straightforward to make these kinds of operational changes. You shouldn’t have to fight the automation.</p><p><a href="https://itnext.io/introducing-confighub-b127736641c5">ConfigHub</a> maintains a one-to-one correspondence between the configuration in ConfigHub and the live state. If you do need to update the live state directly, the changes can be pulled back into ConfigHub automatically.</p><pre>cub unit refresh --space apptique-prod paymentservice</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fd0OuQWEcpfA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dd0OuQWEcpfA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fd0OuQWEcpfA%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/fcab4299a19dbde0d3826cb937a69bf0/href">https://medium.com/media/fcab4299a19dbde0d3826cb937a69bf0/href</a></iframe><p>When it is not desirable to keep the changes, they can be undone, just like any other changes.</p><pre>cub unit update --patch --restore=-1 --space apptique-prod paymentservice<br>cub unit apply --space apptique-prod paymentservice</pre><p>Synchronizing the live state back into ConfigHub provides more visibility into what the current live state is and what changes have been made. I like to see what the actual state is before making changes to the configuration, not just when I am about to apply the changes.</p><p>Now imagine continuously synchronizing the live state back into storage. That would be essentially “reverse GitOps”. However, we know when an apply operation is being performed, so we can synchronize in that direction as needed as well — Bidirectional GitOps.</p><p>That kind of bidirectional synchronization provides more control over operational changes than the current <a href="https://itnext.io/the-unidirectionality-of-infrastructure-as-code-creates-asymmetry-40c9f5eed959">unidirectional approach</a>, making deliberate operational changes less challenging.</p><p>That said, with the 1:1 correspondence and configuration represented as data, this kind of change is also safer (due to minimal <a href="https://itnext.io/modularity-and-blast-radius-in-infrastructure-as-code-9a1f6432196e">blast radius</a>) and much more straightforward to make through ConfigHub, reducing the need to update the live state directly. For common cases, such as changing resources, there are already pre-built <a href="https://docs.confighub.com/guide/functions/">functions</a> for making such changes.</p><pre>cub function invoke --space apptique-prod --unit paymentservice \<br>    set-container-resources server floor 100m 200Mi 2<br>cub unit apply --space apptique-prod paymentservice</pre><p>But you have the option to choose the approach that’s best in the situation when some urgent operational change is necessary.</p><p>What do you think? Have you needed to make operational changes and GitOps made the process more complicated and/or take longer? Does bidirectional GitOps sound promising, or confusing? Do you think <a href="https://docs.systeminit.com/reference/architecture/digital-twin">digital twin</a> is a better term to describe this pattern?</p><p>Feel free to reply here, or send me a message on <a href="https://www.linkedin.com/in/bgrant0607/">LinkedIn</a>, <a href="https://x.com/bgrant0607">X/Twitter</a>, or <a href="https://bsky.app/profile/bgrant0607.bsky.social">Bluesky</a>, where I plan to crosspost this.</p><p>You could also <a href="https://auth.confighub.com/sign-up">try out ConfigHub</a>, which is now in preview.</p><p>If you found this interesting, you may be interested in other posts in my <a href="https://medium.com/@bgrant0607/list/infrastructure-as-code-and-declarative-configuration-8c441ae74836">Infrastructure as Code and Declarative Configuration series</a> or in my <a href="https://medium.com/@bgrant0607/list/kubernetes-8b0b8930195b">Kubernetes series</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce0ced75fa1c" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/what-is-bidirectional-gitops-ce0ced75fa1c">What is Bidirectional GitOps?</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>