<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Multi-cluster on vishctl</title><link>https://vishctl.dev/tags/multi-cluster/</link><description>Recent content in Multi-cluster on vishctl</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 19 Sep 2026 13:00:00 +0800</lastBuildDate><atom:link href="https://vishctl.dev/tags/multi-cluster/index.xml" rel="self" type="application/rss+xml"/><item><title>What Is Karmada? Kubernetes Across Clusters, Explained with YAML</title><link>https://vishctl.dev/posts/karmada-kubernetes-multi-cluster/</link><pubDate>Sat, 19 Sep 2026 13:00:00 +0800</pubDate><guid>https://vishctl.dev/posts/karmada-kubernetes-multi-cluster/</guid><category>kubernetes</category><category>karmada</category><category>multi-cluster</category><category>devops</category><description>Karmada&amp;#39;s CNCF graduation, its multi-cluster architecture, and practical YAML for placement, replica splitting, overrides, and failover.</description><content:encoded><![CDATA[<p>You have a Kubernetes cluster in your data center and another in the cloud. Both can run your application. You want four replicas in one, two in the other, and a sensible response when a cluster becomes unavailable.</p>
<p>Kubernetes understands the desired state inside each cluster. Who owns the desired state across both?</p>
<p>That is the problem <strong>Karmada</strong> tackles. Its recent CNCF graduation is a good reason to look past the announcement and follow what actually happens to an application.</p>
<h2 id="first-what-happened-with-cncf">First, what happened with CNCF?</h2>
<p>CNCF lists Karmada&rsquo;s graduation date as <strong>September 3, 2026</strong>, following Sandbox acceptance on September 14, 2021 and incubation on December 12, 2023. The <a href="https://www.cncf.io/projects/karmada/">CNCF project record</a> is the reference for those dates.</p>
<p>The <a href="https://www.cncf.io/announcements/2026/09/07/cloud-native-computing-foundation-announces-karmada-graduation/">public graduation announcement</a> has a September 7 page date and a September 8 dateline for the event in Shanghai. It highlights a third-party security audit, formal governance, and production adoption, including Bloomberg, Wellhub, and Trip.com.</p>
<p>Graduation is a project maturity milestone. Individual features still have their own stability levels and defaults. That matters especially for failover, which we will come back to.</p>
<p>This walkthrough uses the <strong>Karmada v1.19 documentation</strong>, checked on September 19, 2026. The examples are a documented lab exercise with expected results, not a claim that I have benchmarked or operated this setup in production.</p>
<h2 id="what-is-karmada">What is Karmada?</h2>
<p>Karmada, short for <em>Kubernetes Armada</em>, is a control plane for orchestrating workloads across Kubernetes clusters.</p>
<p>You submit familiar objects such as Deployments and Services, then attach policies describing where they should go. The member clusters remain independent Kubernetes clusters. Each keeps its own API server, controllers, scheduler, nodes, and networking.</p>
<p>The <a href="https://karmada.io/docs/core-concepts/concepts/">core concepts</a> separate three questions:</p>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Input</th>
            <th scope="col">Question it answers</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Resource template, such as a Deployment</td>
            <td>What application should exist?</td>
        </tr>
        <tr>
            <td><code>PropagationPolicy</code></td>
            <td>Which clusters should receive it, and how should replicas be allocated?</td>
        </tr>
        <tr>
            <td><code>OverridePolicy</code></td>
            <td>What should differ for a particular destination?</td>
        </tr>
    </tbody>
  </table>
</div>
<p>For example, one web Deployment can go to two clusters while a policy allocates different replica counts. You keep one application definition and express the placement separately.</p>
<h2 id="what-problem-is-it-solving">What problem is it solving?</h2>
<p>Copying YAML to a second cluster is easy. Keeping a fleet consistent while its capacity and health change is the harder part.</p>
<p>Imagine an internal portal running in Singapore and a secondary location. A release needs to reach both. One environment needs a different setting. During maintenance, you want another cluster to take over. Each of those decisions can become a script, a pipeline branch, or a manual runbook step.</p>
<p>Karmada brings placement and propagation into a reconciled API. Instead of treating every destination as an unrelated deployment, you describe the relationship between the application and the fleet.</p>
<p>The practical questions become explicit: which clusters are eligible, how much should each receive, what differs there, and what does the controller believe is actually running?</p>
<p>That is useful when fleet behavior is part of the platform&rsquo;s requirements. For a single cluster, it adds another control plane without solving much of a problem.</p>
<h2 id="architecture-two-levels-of-scheduling">Architecture: two levels of scheduling</h2>
<p><strong>Karmada schedules resources to clusters. Kubernetes schedules Pods to nodes within each cluster.</strong></p>
<p>The Karmada API server uses the Kubernetes API server implementation. Submitted workload objects are stored centrally; the central Deployment template does not itself create application Pods in the host cluster. Those Pods appear after propagation to members. The <a href="https://karmada.io/docs/core-concepts/components/">component documentation</a> describes this boundary.</p>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    U["kubectl or GitOps"] --> API["Karmada API server"]
    subgraph HOST["Karmada control plane"]
        API <--> DB["etcd"]
        API <--> CM["Karmada controllers"]
        API <--> SCH["Karmada scheduler<br/>Select clusters and replicas"]
    end
    CM -->|Push mode shown| A["Member 1 API server"]
    CM -->|Push mode shown| B["Member 2 API server"]
    A --> KA["Local controllers and scheduler"]
    B --> KB["Local controllers and scheduler"]
    KA --> PA["Pods on member 1 nodes"]
    KB --> PB["Pods on member 2 nodes"]
    classDef central fill:#13264a,stroke:#38bdf8,color:#e6f6ff;
    classDef member fill:#281d48,stroke:#a78bfa,color:#f1edff;
    classDef pods fill:#173f59,stroke:#22d3ee,color:#ecfeff;
    class API,DB,CM,SCH central;
    class A,B,KA,KB member;
    class PA,PB pods;</div>
  <figcaption>Logical control flow. The host cluster runs Karmada; member clusters run the application. Arrows represent management operations, not user traffic.</figcaption>
</figure>

<p>The core pieces have familiar jobs: etcd persists state, the API server exposes it, controllers reconcile it, and the scheduler decides destinations. A full installation also includes admission webhooks, an aggregated API server for additional APIs and proxy access, and selected Kubernetes controllers. The diagram leaves those out to keep the workload path readable.</p>
<p>Do not confuse the <strong>host cluster&rsquo;s API endpoint</strong> with the <strong>Karmada API endpoint</strong>. Applying an application to the host&rsquo;s ordinary Kubernetes API can run it on the host instead of distributing it.</p>
<h3 id="follow-one-deployment-through-the-system">Follow one Deployment through the system</h3>
<p>The <a href="https://karmada.io/docs/core-concepts/architecture/">architecture guide</a> describes a controller pipeline:</p>
<ol>
<li>You create a Deployment and a matching propagation policy in the Karmada API.</li>
<li>The policy controller creates a <code>ResourceBinding</code> for the selected resource.</li>
<li>The scheduler records the chosen clusters and replica assignments in the binding.</li>
<li>The binding controller prepares destination-specific <code>Work</code> objects, incorporating applicable overrides.</li>
<li>Execution applies the manifests to member APIs. Local Kubernetes controllers create ReplicaSets and Pods.</li>
</ol>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    T["Deployment template"] --> R["ResourceBinding"]
    P["PropagationPolicy"] --> R
    S["Scheduler decisions"] --> R
    R --> W["Per-cluster Work objects"]
    O["OverridePolicy"] --> W
    W --> D["Member Deployments"]
    D --> POD["Local ReplicaSets and Pods"]
    D -.->|Reported status| STATUS["Karmada status view"]</div>
  <figcaption>Bindings record placement. Work objects carry manifests for execution. These are useful debugging checkpoints, not extra files you normally author.</figcaption>
</figure>

<h3 id="push-versus-pull">Push versus pull</h3>
<p>In <strong>push mode</strong>, the control plane connects to member API servers and applies resources. In <strong>pull mode</strong>, a <code>karmada-agent</code> serving each member watches its work and applies it locally, reporting status back. Pull is useful when members can reach the central endpoint but inbound access to their APIs is restricted.</p>
<p>Pull mode still needs working connectivity, authentication, and certificates. Some proxy and aggregated-access features need additional connectivity beyond basic agent synchronization. The <a href="https://karmada.io/docs/userguide/clustermanager/cluster-registration/">registration guide</a> explains the tradeoffs.</p>
<h2 id="a-small-lab-one-application-two-clusters">A small lab: one application, two clusters</h2>
<p>Use a disposable environment with Karmada v1.19 and two healthy members named <code>member1</code> and <code>member2</code>. For installation, follow the <a href="https://karmada.io/docs/installation/">installation overview</a>. The project&rsquo;s <a href="https://karmada.io/docs/get-started/nginx-example/">local walkthrough</a> uses <code>hack/local-up-karmada.sh</code> to create a host and member clusters with kind; it also builds components, so check its prerequisites before running it.</p>
<p>The commands below use Bash, including WSL2. They assume the local walkthrough&rsquo;s kubeconfig paths and context names. If your installation differs, change the paths and contexts first. All application resources use the <code>default</code> namespace, which should already exist in the control plane and members.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">KARMADA_CONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.kube/karmada.config&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MEMBERS_CONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.kube/members.config&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">k<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  kubectl --kubeconfig <span class="s2">&#34;</span><span class="nv">$KARMADA_CONFIG</span><span class="s2">&#34;</span> --context karmada-apiserver <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">m<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">member</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">shift</span>
</span></span><span class="line"><span class="cl">  kubectl --kubeconfig <span class="s2">&#34;</span><span class="nv">$MEMBERS_CONFIG</span><span class="s2">&#34;</span> --context <span class="s2">&#34;</span><span class="nv">$member</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">k get clusters
</span></span><span class="line"><span class="cl">m member1 get nodes
</span></span><span class="line"><span class="cl">m member2 get nodes
</span></span></code></pre></div><p>Continue once both members report ready and have capacity for this small workload. An extra member created by the local installer is fine; our policies select only two.</p>
<h3 id="1-define-an-ordinary-deployment-and-service">1. Define an ordinary Deployment and Service</h3>
<p>Save this as <code>app.yaml</code>. The resource requests make the intended Pod size explicit. The environment variable gives us a visible value to override later.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:1.28.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">CLUSTER_SITE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">shared</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">100m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">64Mi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">500m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">128Mi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k apply -f app.yaml
</span></span></code></pre></div><p>This image tag is fixed for the example, not a recommendation to freeze a production service on that release. Production images need your normal update and vulnerability review process.</p>
<h3 id="2-propagate-to-both-members">2. Propagate to both members</h3>
<p>Save this as <code>placement.yaml</code>. <code>PropagationPolicy</code> is namespaced; the selected resources are in the same namespace. Use <code>ClusterPropagationPolicy</code> when you need a policy with cluster-wide scope.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">policy.karmada.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PropagationPolicy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resourceSelectors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">placement</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">clusterAffinity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">clusterNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">member1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">member2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spreadConstraints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">spreadByField</span><span class="p">:</span><span class="w"> </span><span class="l">cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">minGroups</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">maxGroups</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">replicaScheduling</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">replicaSchedulingType</span><span class="p">:</span><span class="w"> </span><span class="l">Duplicated</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k apply -f placement.yaml
</span></span><span class="line"><span class="cl">m member1 rollout status deployment/fleet-web -n default --timeout<span class="o">=</span>180s
</span></span><span class="line"><span class="cl">m member2 rollout status deployment/fleet-web -n default --timeout<span class="o">=</span>180s
</span></span><span class="line"><span class="cl">m member1 get deployment,service -n default
</span></span><span class="line"><span class="cl">m member2 get deployment,service -n default
</span></span></code></pre></div><p><strong>Expected: six desired replicas in each cluster, twelve across the fleet.</strong> <code>Duplicated</code> repeats the template&rsquo;s count. A Service has no replica count; each selected member receives its own Service.</p>
<p>The <a href="https://karmada.io/docs/userguide/scheduling/propagation-policy/">propagation policy guide</a> distinguishes eligibility (<code>clusterAffinity</code>) from spread constraints and replica allocation. Here the eligible set is two clusters and the spread requirement is two clusters.</p>
<h3 id="3-split-six-replicas-into-four-and-two">3. Split six replicas into four and two</h3>
<p>Replace <code>placement.yaml</code> with the following and apply it again. This updates the same policy rather than creating a second policy that competes for the resource.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">policy.karmada.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PropagationPolicy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resourceSelectors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">placement</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">clusterAffinity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">clusterNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">member1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">member2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">replicaScheduling</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">replicaSchedulingType</span><span class="p">:</span><span class="w"> </span><span class="l">Divided</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">replicaDivisionPreference</span><span class="p">:</span><span class="w"> </span><span class="l">Weighted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">weightPreference</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">staticWeightList</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">targetCluster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">clusterNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span>- <span class="l">member1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">weight</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">targetCluster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">clusterNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span>- <span class="l">member2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">weight</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k apply -f placement.yaml
</span></span><span class="line"><span class="cl">m member1 get deployment fleet-web -n default
</span></span><span class="line"><span class="cl">m member2 get deployment fleet-web -n default
</span></span></code></pre></div><p>After reconciliation, the desired split is <strong>4 + 2 = 6</strong>. The source Deployment still says six. The policy changes what that number means across destinations.</p>
<div class="table-scroll">
  <table>
    <thead>
        <tr>
            <th scope="col">Policy for a six-replica template</th>
            <th scope="col" style="text-align: right">member1</th>
            <th scope="col" style="text-align: right">member2</th>
            <th scope="col" style="text-align: right">Fleet total</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Duplicated across both</td>
            <td style="text-align: right">6</td>
            <td style="text-align: right">6</td>
            <td style="text-align: right">12</td>
        </tr>
        <tr>
            <td>Divided with weights 2:1</td>
            <td style="text-align: right">4</td>
            <td style="text-align: right">2</td>
            <td style="text-align: right">6</td>
        </tr>
    </tbody>
  </table>
</div>
<p>Static weights express a chosen ratio. They do not prove that either cluster can run its allocation. Also, static-weight division does not use spread constraints, so this replacement deliberately omits the earlier spread block.</p>
<p>For capacity-sensitive placement, Karmada supports dynamic weighting using <code>AvailableReplicas</code>. Its <a href="https://karmada.io/docs/userguide/scheduling/cluster-resources/">resource modeling guide</a> explains why free CPU summed across a cluster is not enough: a Pod needs to fit on an actual node. Optional scheduler estimators improve fit estimates. Resource requests, node constraints, and available hardware still matter.</p>
<h3 id="4-change-one-setting-for-member2">4. Change one setting for member2</h3>
<p>Save this as <code>override.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">policy.karmada.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">OverridePolicy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web-site</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resourceSelectors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">fleet-web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">overrideRules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">targetCluster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">clusterNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">member2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">overriders</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">plaintext</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/spec/template/spec/containers/0/env/0/value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">operator</span><span class="p">:</span><span class="w"> </span><span class="l">replace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">secondary</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k apply -f override.yaml
</span></span><span class="line"><span class="cl">m member2 rollout status deployment/fleet-web -n default --timeout<span class="o">=</span>180s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">k get deployment fleet-web -n default <span class="se">\
</span></span></span><span class="line"><span class="cl">  -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.spec.template.spec.containers[0].env[0].value}{&#34;\n&#34;}&#39;</span>
</span></span><span class="line"><span class="cl">m member2 get deployment fleet-web -n default <span class="se">\
</span></span></span><span class="line"><span class="cl">  -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.spec.template.spec.containers[0].env[0].value}{&#34;\n&#34;}&#39;</span>
</span></span></code></pre></div><p>Expected values: <code>shared</code> centrally and <code>secondary</code> in member2. The override changes the destination&rsquo;s Pod template and triggers a local rollout. NGINX does not use this variable to change its web page; it is simply an observable configuration example.</p>
<p>The JSON path is tied to the first container and first environment entry in this exact manifest. Update the path if you reorder them. The <a href="https://karmada.io/docs/userguide/scheduling/override-policy/">override guide</a> also documents specialized image, command, label, and annotation overriders for other cases.</p>
<h2 id="how-do-i-debug-the-path">How do I debug the path?</h2>
<p>Start centrally, then inspect the destination. These commands expose the intermediate objects shown in the architecture diagram:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k get propagationpolicies -n default
</span></span><span class="line"><span class="cl">k get resourcebindings -n default -o yaml
</span></span><span class="line"><span class="cl">k get works -n karmada-es-member1
</span></span><span class="line"><span class="cl">k get works -n karmada-es-member2
</span></span><span class="line"><span class="cl">k get events -n default --sort-by<span class="o">=</span>.metadata.creationTimestamp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">m member1 get pods -n default -l <span class="nv">app</span><span class="o">=</span>fleet-web
</span></span><span class="line"><span class="cl">m member1 describe deployment fleet-web -n default
</span></span><span class="line"><span class="cl">m member1 get events -n default --sort-by<span class="o">=</span>.metadata.creationTimestamp
</span></span></code></pre></div><p>My debugging order would be: did the policy select the resource, did the binding get destinations, did the Work reach the member, and did local Kubernetes accept and run it?</p>
<p>A successful placement decision cannot pull an unavailable image or repair a failing readiness probe. If the Deployment exists in the member but its Pods are pending, inspect the member&rsquo;s scheduling events and resource constraints.</p>
<p>For a direct HTTP check, run <code>m member1 port-forward -n default service/fleet-web 8080:80</code>, then open <code>http://localhost:8080</code> while that command is running. Repeat for member2 after stopping the first port-forward.</p>
<h2 id="what-happens-when-a-cluster-fails">What happens when a cluster fails?</h2>
<p>First, <strong>v1.19 documents the <code>Failover</code> feature gate as Beta and disabled by default</strong>. Enable <code>Failover=true</code> in the Karmada controller manager through your installation configuration before expecting cluster eviction and rescheduling. Merge it with any existing feature-gate settings. The <a href="https://karmada.io/docs/userguide/failover/cluster-failover/">cluster failover guide</a> is explicit about this.</p>
<p>There is a second detail: unhealthy status and eviction are different steps. The <a href="https://karmada.io/docs/userguide/failover/failover-analysis/">failover process analysis</a> says unhealthy clusters receive <code>NoSchedule</code> taints, while a non-tolerated <code>NoExecute</code> taint triggers removal from placement and rescheduling. Configure cluster taint management for your desired automatic behavior; do not assume the feature gate alone defines a complete outage policy.</p>
<p>For the manual exercise below, also set <code>--enable-no-execute-taint-eviction=true</code> on the Karmada controller manager. If you later automate taints with a <code>ClusterTaintPolicy</code> that uses <code>NoExecute</code>, the webhook additionally needs <code>--allow-no-execute-taint-policy=true</code>. These are separate opt-ins documented in <a href="https://karmada.io/docs/userguide/failover/cluster-taint-management/">cluster taint management</a>.</p>
<p>In a disposable lab with both controller-manager settings enabled, use the divided policy from step 3 and deliberately taint member2. Install the matching <code>karmadactl</code> CLI first; its <a href="https://karmada.io/docs/reference/karmadactl/karmadactl-commands/karmadactl_taint/">taint command</a> operates on Karmada clusters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Lab only: this can affect every Karmada workload using member2.</span>
</span></span><span class="line"><span class="cl">karmadactl taint clusters member2 lab-drain<span class="o">=</span>true:NoExecute <span class="se">\
</span></span></span><span class="line"><span class="cl">  --kubeconfig <span class="s2">&#34;</span><span class="nv">$KARMADA_CONFIG</span><span class="s2">&#34;</span> --karmada-context karmada-apiserver
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">k get resourcebindings -n default -o yaml
</span></span><span class="line"><span class="cl">m member1 get deployment fleet-web -n default --watch
</span></span></code></pre></div><p>With sufficient capacity and successful reconciliation, the intended replacement is six replicas on member1. This is a controlled eviction exercise, not a simulation of every network partition failure mode.</p>
<p>Remove the lab taint afterward:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">karmadactl taint clusters member2 lab-drain:NoExecute- <span class="se">\
</span></span></span><span class="line"><span class="cl">  --kubeconfig <span class="s2">&#34;</span><span class="nv">$KARMADA_CONFIG</span><span class="s2">&#34;</span> --karmada-context karmada-apiserver
</span></span></code></pre></div><p>Removing the taint restores eligibility. Do not assume it immediately restores the original 4:2 distribution; verify placement and use an intentional rebalance workflow if needed.</p>
<h3 id="failover-has-application-level-consequences">Failover has application-level consequences</h3>
<p>Restoring replicas is only one part of recovery. Users also need traffic to reach those replicas, and the application needs usable data.</p>
<p>In a network partition, an unreachable cluster may still be serving requests. Creating replacements does not prove the old instances have stopped. For a writer or queue consumer, think about fencing, leases, idempotency, and duplicate processing before enabling automatic movement.</p>
<p>Karmada supports state-preservation mechanisms for particular recovery workflows, but moving a StatefulSet definition cannot copy the bytes on its disks. Database replication, accessible checkpoints, storage compatibility, and recovery testing need their own design.</p>
<h2 id="what-about-networking-services-and-storage">What about networking, Services, and storage?</h2>
<p>Our two Services are cluster-local. Copying a Service manifest does not create one global virtual IP, connect Pod networks, or route public traffic between regions.</p>
<p>Karmada documents <a href="https://karmada.io/docs/userguide/network/working-with-submariner/">Submariner integration</a> for cross-cluster connectivity. Whether you need that connectivity, a service mesh, or independent ingress endpoints depends on the application.</p>
<p>For this web example, I would design the user path separately:</p>
<figure class="mermaid-figure">
  <div class="mermaid">flowchart TD
    USER["Users"] --> ROUTE["Global DNS or load balancer<br/>Endpoint health checks"]
    ROUTE --> I1["Member 1 ingress or gateway"]
    ROUTE --> I2["Member 2 ingress or gateway"]
    I1 --> S1["Local Service and ready Pods"]
    I2 --> S2["Local Service and ready Pods"]</div>
  <figcaption>A possible traffic architecture. The global routing layer and health checks must be configured separately from workload placement.</figcaption>
</figure>

<p>A 4:2 replica allocation does not automatically produce a 2:1 traffic split. The routing layer has its own policy and observations.</p>
<p>Dependencies deserve the same attention. Karmada can <a href="https://karmada.io/docs/userguide/scheduling/propagate-dependencies/">propagate referenced ConfigMaps and Secrets</a> with <code>propagateDeps</code>, but that does not provision external databases or grant cloud IAM permissions. Our lab selects the Service explicitly because a Deployment does not reference its Service as a dependency.</p>
<p>For real applications, check namespace creation, registry access, storage classes, secrets, CRDs, and required operators in every eligible destination. Propagating a custom resource to a cluster without its operator will not make the application work.</p>
<h2 id="where-does-gitops-fit">Where does GitOps fit?</h2>
<p>The <a href="https://karmada.io/docs/userguide/cicd/working-with-argocd/">Argo CD integration</a> treats the Karmada API as a deployment destination. Git can hold the application templates and Karmada policies; Argo CD reconciles them centrally, and Karmada handles member placement.</p>
<p>I would make ownership explicit: one reconciler owns the central application definition, and Karmada owns the propagated copies. Configuring another GitOps application to overwrite those same member objects invites conflicting changes, particularly around replica counts and overrides.</p>
<p>If all you need is a fixed application copy in a fixed set of clusters, existing GitOps workflows may already satisfy the requirement. Karmada becomes more compelling when placement and allocation need to respond to fleet conditions.</p>
<h2 id="what-i-would-check-before-production">What I would check before production</h2>
<p>The additional control plane is now part of your recovery story. Give its API and etcd a high-availability and backup plan, protect member credentials, and test what happens when the management plane is unavailable. Independent member control planes can continue local reconciliation, but central changes and cross-cluster decisions depend on Karmada recovering.</p>
<p>I would start with a stateless service and measure the full failure sequence: detection, eviction decision, replacement readiness, and restored user traffic. Test a control-plane outage separately from a member outage. Then test a network partition where the old application is still alive.</p>
<p>Capacity is another operational choice. A fallback cluster that is already full is not a useful recovery destination. Similarly, a GPU cluster label does not guarantee the correct accelerator, driver, model files, or per-node GPU count for an inference Pod. This sits one layer above the prerequisites in my <a href="https://vishctl.dev/posts/vllm-on-wsl2-minikube/">vLLM on Kubernetes walkthrough</a>.</p>
<p>The question I would use to decide whether Karmada belongs in a platform is: <strong>do we need a controller to continuously manage where this application lives across clusters?</strong> If the answer is yes, its templates, bindings, and placement policies give that decision a concrete API and an inspectable execution path.</p>
<h3 id="clean-up-the-lab-application">Clean up the lab application</h3>
<p>After removing the lab taint, delete the central application and override, then the propagation policy. Keep Karmada running so it can reconcile the deletions into members.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">k delete -f app.yaml -f override.yaml
</span></span><span class="line"><span class="cl">k delete -f placement.yaml
</span></span><span class="line"><span class="cl">m member1 get deployment,service -n default
</span></span><span class="line"><span class="cl">m member2 get deployment,service -n default
</span></span></code></pre></div><p>Confirm <code>fleet-web</code> disappears from both members before tearing down the lab control plane.</p>
<h2 id="cheat-sheet">Cheat sheet</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Karmada              = a control plane that places Kubernetes workloads across clusters
</span></span><span class="line"><span class="cl">Member cluster       = an ordinary, independent cluster that Karmada sends workloads to
</span></span><span class="line"><span class="cl">Resource template    = the normal Deployment or Service you submit: what should exist
</span></span><span class="line"><span class="cl">PropagationPolicy    = which clusters receive it and how replicas are divided
</span></span><span class="line"><span class="cl">OverridePolicy       = what should differ in one particular cluster
</span></span><span class="line"><span class="cl">ResourceBinding      = the record of chosen clusters and replica counts for one resource
</span></span><span class="line"><span class="cl">Work                 = the per-cluster copy of the manifest, with overrides applied
</span></span><span class="line"><span class="cl">Two-level scheduling = Karmada picks clusters; each cluster&#39;s own scheduler picks nodes
</span></span><span class="line"><span class="cl">Push mode            = the control plane connects to member API servers and applies resources
</span></span><span class="line"><span class="cl">Pull mode            = a karmada-agent in the member fetches its work and applies it locally
</span></span><span class="line"><span class="cl">Karmada API endpoint = where you apply resources; not the host cluster&#39;s own API
</span></span><span class="line"><span class="cl">Graduation           = a project maturity milestone; features keep their own stability levels
</span></span></code></pre></div>]]></content:encoded></item></channel></rss>