<?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>Kubernetes on IT Comparison</title><link>https://comparison.metacog.co.kr/tags/kubernetes/</link><description>Recent content in Kubernetes on IT Comparison</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 03 Aug 2026 05:32:13 +0900</lastBuildDate><atom:link href="https://comparison.metacog.co.kr/tags/kubernetes/index.xml" rel="self" type="application/rss+xml"/><item><title>Sidecar Pattern vs Ambassador Pattern: General-Purpose Helper vs Network Proxy</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-sidecar-pattern-vs-ambassador-pattern-general-purpose-helper/</link><pubDate>Mon, 03 Aug 2026 05:32:13 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-sidecar-pattern-vs-ambassador-pattern-general-purpose-helper/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;The &lt;strong class="kw"&gt;Sidecar Pattern&lt;/strong&gt; is the general technique of running a helper container alongside your app in the same pod to add any cross-cutting capability — logging, metrics, config sync, or a mesh proxy. The &lt;strong class="kw"&gt;Ambassador Pattern&lt;/strong&gt; is a specific flavor of that sidecar dedicated to one job: sitting between the app and the network, so the app talks to localhost while the ambassador handles the real, often messy, connection to an external service.&lt;/p&gt;</description></item><item><title>Docker Swarm vs Kubernetes: Container Orchestration Compared</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-docker-swarm-vs-kubernetes-container-orchestration-compared/</link><pubDate>Mon, 03 Aug 2026 05:29:45 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-docker-swarm-vs-kubernetes-container-orchestration-compared/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Docker Swarm and Kubernetes are both container orchestration platforms that manage deployment, scaling, and networking of containerized applications, but they differ sharply in operational complexity and feature depth. Swarm prioritizes &lt;strong class="kw"&gt;simplicity&lt;/strong&gt;, integrating directly into the Docker CLI for fast setup, while Kubernetes offers a far more &lt;strong class="kw"&gt;extensible&lt;/strong&gt;, battle-tested platform built for large-scale, production-grade workloads.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="34" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="bold"&gt;Docker Swarm&lt;/text&gt;&lt;text x="480" y="34" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="bold"&gt;Kubernetes&lt;/text&gt;&lt;rect x="60" y="50" width="200" height="55" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="73" text-anchor="middle" style="fill:var(--content)" font-size="13" font-weight="bold"&gt;Swarm Manager&lt;/text&gt;&lt;text x="160" y="92" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;Raft consensus store&lt;/text&gt;&lt;line x1="105" y1="105" x2="105" y2="150" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;line x1="215" y1="105" x2="230" y2="150" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="35" y="150" width="140" height="90" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="105" y="168" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;rect x="50" y="180" width="50" height="24" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;text x="75" y="196" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Task&lt;/text&gt;&lt;rect x="110" y="180" width="50" height="24" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;text x="135" y="196" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Task&lt;/text&gt;&lt;rect x="50" y="210" width="110" height="24" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;text x="105" y="226" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Task&lt;/text&gt;&lt;rect x="185" y="150" width="90" height="90" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="230" y="168" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;rect x="200" y="185" width="60" height="24" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;text x="230" y="201" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Task&lt;/text&gt;&lt;rect x="200" y="212" width="60" height="24" rx="3" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1"/&gt;&lt;text x="230" y="228" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Task&lt;/text&gt;&lt;text x="160" y="320" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;2 node roles: manager + worker&lt;/text&gt;&lt;rect x="370" y="50" width="240" height="95" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="490" y="66" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Control Plane&lt;/text&gt;&lt;rect x="380" y="74" width="105" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="432" y="91" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;API Server&lt;/text&gt;&lt;rect x="495" y="74" width="105" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="547" y="91" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;etcd&lt;/text&gt;&lt;rect x="380" y="105" width="105" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="432" y="122" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Scheduler&lt;/text&gt;&lt;rect x="495" y="105" width="105" height="26" rx="3" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="547" y="122" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Controller Mgr&lt;/text&gt;&lt;line x1="420" y1="145" x2="420" y2="170" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;line x1="560" y1="145" x2="560" y2="170" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="365" y="170" width="110" height="100" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="420" y="188" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;text x="420" y="204" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;kubelet&lt;/text&gt;&lt;rect x="380" y="212" width="80" height="45" rx="4" style="fill:none;stroke:var(--compare-b)" stroke-width="1" stroke-dasharray="3 2"/&gt;&lt;text x="420" y="224" text-anchor="middle" style="fill:var(--content)" font-size="9"&gt;Pod&lt;/text&gt;&lt;rect x="386" y="230" width="30" height="18" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;rect x="422" y="230" width="30" height="18" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;rect x="505" y="170" width="110" height="100" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="560" y="188" text-anchor="middle" style="fill:var(--content)" font-size="12" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;text x="560" y="204" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;kubelet&lt;/text&gt;&lt;rect x="520" y="212" width="80" height="45" rx="4" style="fill:none;stroke:var(--compare-b)" stroke-width="1" stroke-dasharray="3 2"/&gt;&lt;text x="560" y="224" text-anchor="middle" style="fill:var(--content)" font-size="9"&gt;Pod&lt;/text&gt;&lt;rect x="526" y="230" width="30" height="18" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;rect x="562" y="230" width="30" height="18" rx="2" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1"/&gt;&lt;text x="490" y="300" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;Layered control plane + kubelet-managed pods&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Docker Swarm&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup &amp;amp; installation&lt;/td&gt;
&lt;td&gt;Single command: docker swarm init/join&lt;/td&gt;
&lt;td&gt;Multi-step: kubeadm, managed service, or install tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster architecture&lt;/td&gt;
&lt;td&gt;Manager nodes (Raft consensus) + worker nodes&lt;/td&gt;
&lt;td&gt;Control plane (API server, etcd, scheduler, controller manager) + worker nodes with kubelet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment unit&lt;/td&gt;
&lt;td&gt;Service made of identical Tasks, one container each&lt;/td&gt;
&lt;td&gt;Pod: one or more co-located, co-scheduled containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Networking&lt;/td&gt;
&lt;td&gt;Built-in overlay network, configured automatically&lt;/td&gt;
&lt;td&gt;Pluggable via CNI plugins (Calico, Cilium, Flannel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service discovery &amp;amp; load balancing&lt;/td&gt;
&lt;td&gt;Built-in DNS plus routing mesh VIP&lt;/td&gt;
&lt;td&gt;kube-proxy with Service objects and Ingress controllers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling &amp;amp; scheduling&lt;/td&gt;
&lt;td&gt;Basic spread or binpack placement strategies&lt;/td&gt;
&lt;td&gt;Fine-grained scheduling with affinity rules, taints, and resource requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-healing &amp;amp; updates&lt;/td&gt;
&lt;td&gt;Restarts failed tasks, basic rolling updates&lt;/td&gt;
&lt;td&gt;Reconciliation loops, rolling updates, rollbacks, HPA/VPA autoscaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem &amp;amp; extensibility&lt;/td&gt;
&lt;td&gt;Minimal, small plugin ecosystem&lt;/td&gt;
&lt;td&gt;Vast ecosystem: CRDs, Operators, Helm, service meshes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker Swarm favors &lt;strong class="kw"&gt;simplicity&lt;/strong&gt;, built directly into the Docker CLI, while Kubernetes requires setting up a separate &lt;strong class="kw"&gt;control plane&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Swarm deploys single-container &lt;strong class="kw"&gt;Tasks&lt;/strong&gt;, whereas Kubernetes groups containers into &lt;strong class="kw"&gt;Pods&lt;/strong&gt; that share network and storage.&lt;/li&gt;
&lt;li&gt;Kubernetes offers far more granular &lt;strong class="kw"&gt;scheduling&lt;/strong&gt; controls than Swarm&amp;rsquo;s basic spread strategy.&lt;/li&gt;
&lt;li&gt;Kubernetes&amp;rsquo; &lt;strong class="kw"&gt;ecosystem&lt;/strong&gt; of CRDs, Operators, and Helm dwarfs Swarm&amp;rsquo;s, at the cost of a steeper learning curve.&lt;/li&gt;
&lt;li&gt;Swarm networking is &lt;strong class="kw"&gt;automatic&lt;/strong&gt; overlay by default, while Kubernetes relies on pluggable CNI plugins requiring explicit choice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Docker Swarm&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Horizontal Pod Autoscaler vs Vertical Pod Autoscaler: Scaling Out vs Scaling Up</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-horizontal-pod-autoscaler-vs-vertical-pod-autoscaler-scaling/</link><pubDate>Mon, 03 Aug 2026 05:24:29 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-horizontal-pod-autoscaler-vs-vertical-pod-autoscaler-scaling/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Both controllers watch metrics and adjust Kubernetes workloads automatically, but they scale in different dimensions. The &lt;strong class="kw"&gt;Horizontal Pod Autoscaler&lt;/strong&gt; adds or removes pod replicas to handle load, while the &lt;strong class="kw"&gt;Vertical Pod Autoscaler&lt;/strong&gt; resizes the CPU and memory requests/limits of existing pods.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="50" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Horizontal Pod Autoscaler&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Vertical Pod Autoscaler&lt;/text&gt;&lt;text x="160" y="58" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;before&lt;/text&gt;&lt;rect x="130" y="68" width="60" height="48" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="96" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;pod&lt;/text&gt;&lt;line x1="160" y1="122" x2="160" y2="148" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;text x="160" y="166" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;after (load increases)&lt;/text&gt;&lt;rect x="70" y="178" width="50" height="44" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="135" y="178" width="50" height="44" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="200" y="178" width="50" height="44" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="95" y="204" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;pod&lt;/text&gt;&lt;text x="160" y="204" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;pod&lt;/text&gt;&lt;text x="225" y="204" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;pod&lt;/text&gt;&lt;text x="160" y="246" text-anchor="middle" style="fill:var(--primary)" font-size="13" font-weight="bold"&gt;Scale OUT&lt;/text&gt;&lt;text x="160" y="264" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;more replicas, same pod size&lt;/text&gt;&lt;text x="480" y="58" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;before&lt;/text&gt;&lt;rect x="455" y="70" width="50" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="92" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;pod&lt;/text&gt;&lt;line x1="480" y1="122" x2="480" y2="150" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;text x="480" y="168" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;after (load increases)&lt;/text&gt;&lt;rect x="430" y="180" width="100" height="96" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="224" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;pod&lt;/text&gt;&lt;text x="480" y="242" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;CPU/mem ↑&lt;/text&gt;&lt;text x="480" y="300" text-anchor="middle" style="fill:var(--primary)" font-size="13" font-weight="bold"&gt;Scale UP&lt;/text&gt;&lt;text x="480" y="318" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;bigger pod, same replica count&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Horizontal Pod Autoscaler&lt;/th&gt;
&lt;th&gt;Vertical Pod Autoscaler&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it adjusts&lt;/td&gt;
&lt;td&gt;Number of pod replicas in a Deployment/ReplicaSet/StatefulSet&lt;/td&gt;
&lt;td&gt;CPU and memory requests/limits on the pod&amp;rsquo;s containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics source&lt;/td&gt;
&lt;td&gt;Metrics Server or custom/external metrics (CPU, memory, custom queries) via metrics.k8s.io API&lt;/td&gt;
&lt;td&gt;Historical and current usage sampled by the VPA recommender component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger condition&lt;/td&gt;
&lt;td&gt;Observed metric crosses a target threshold averaged across pods&lt;/td&gt;
&lt;td&gt;Recommender detects requests are consistently over- or under-provisioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action taken&lt;/td&gt;
&lt;td&gt;Creates or deletes pod replicas to match target replica count&lt;/td&gt;
&lt;td&gt;Evicts and recreates pods with new resource requests (or just recommends, depending on updateMode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disruption to running pods&lt;/td&gt;
&lt;td&gt;None — existing pods are untouched, new ones are added or removed&lt;/td&gt;
&lt;td&gt;Pod restart required to apply new resource values, causing brief downtime unless using in-place resize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit for workload type&lt;/td&gt;
&lt;td&gt;Stateless, horizontally scalable services behind a Service/load balancer&lt;/td&gt;
&lt;td&gt;Single-instance or hard-to-replicate workloads, or right-sizing before enabling HPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflict risk&lt;/td&gt;
&lt;td&gt;Can fight with VPA if both manage CPU on the same workload&lt;/td&gt;
&lt;td&gt;Should not manage CPU/memory targeted by HPA on the same workload simultaneously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration object&lt;/td&gt;
&lt;td&gt;HorizontalPodAutoscaler resource with min/max replicas and target metrics&lt;/td&gt;
&lt;td&gt;VerticalPodAutoscaler resource with updateMode (Off, Initial, Recreate, Auto)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HPA changes &lt;strong class="kw"&gt;replica count&lt;/strong&gt;, VPA changes &lt;strong class="kw"&gt;resource requests&lt;/strong&gt; on existing pods.&lt;/li&gt;
&lt;li&gt;VPA updates typically require a &lt;strong class="kw"&gt;pod restart&lt;/strong&gt; to take effect, while HPA scaling adds/removes pods without disrupting the rest.&lt;/li&gt;
&lt;li&gt;Running both on the &lt;strong class="kw"&gt;same metric&lt;/strong&gt; (like CPU) causes conflicting decisions unless carefully scoped.&lt;/li&gt;
&lt;li&gt;VPA is often used in &lt;strong class="kw"&gt;recommendation-only mode&lt;/strong&gt; to right-size requests before HPA takes over scaling.&lt;/li&gt;
&lt;li&gt;HPA assumes the workload is &lt;strong class="kw"&gt;stateless and replicable&lt;/strong&gt;; VPA fits singleton or stateful workloads that can&amp;rsquo;t simply be duplicated.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Horizontal Pod Autoscaler&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>StatefulSet vs Deployment: Stable Identity vs Stateless Replicas</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-statefulset-vs-deployment-stable-identity-vs-stateless-repli/</link><pubDate>Mon, 03 Aug 2026 05:23:54 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-statefulset-vs-deployment-stable-identity-vs-stateless-repli/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Both are Kubernetes controllers that manage sets of pods from a template, but they solve different problems: a &lt;strong class="kw"&gt;Deployment&lt;/strong&gt; treats pods as interchangeable, disposable replicas, while a &lt;strong class="kw"&gt;StatefulSet&lt;/strong&gt; gives each pod a stable name, network identity, and persistent storage that survives rescheduling. The choice matters because workloads like databases or clustered systems break if pod identity or storage isn&amp;rsquo;t preserved across restarts.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;text x="160" y="30" text-anchor="middle" font-size="18" font-weight="bold" style="fill:var(--primary)"&gt;Deployment&lt;/text&gt;&lt;text x="480" y="30" text-anchor="middle" font-size="18" font-weight="bold" style="fill:var(--primary)"&gt;StatefulSet&lt;/text&gt;&lt;line x1="320" y1="45" x2="320" y2="330" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;rect x="55" y="70" width="190" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="150" y="100" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-7f9d4c&lt;/text&gt;&lt;rect x="55" y="150" width="190" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="150" y="180" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-x9y8z2&lt;/text&gt;&lt;rect x="55" y="230" width="190" height="50" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="150" y="260" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-m4n5p6&lt;/text&gt;&lt;path d="M245,95 C280,110 280,160 245,175" fill="none" style="stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="3 3"/&gt;&lt;path d="M245,175 C280,190 280,240 245,255" fill="none" style="stroke:var(--compare-a)" stroke-width="1.5" stroke-dasharray="3 3"/&gt;&lt;text x="150" y="315" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;interchangeable, no stable identity&lt;/text&gt;&lt;rect x="395" y="70" width="150" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="470" y="100" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-0&lt;/text&gt;&lt;rect x="395" y="150" width="150" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="470" y="180" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-1&lt;/text&gt;&lt;rect x="395" y="230" width="150" height="50" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="470" y="260" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;web-2&lt;/text&gt;&lt;line x1="470" y1="120" x2="470" y2="146" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="470,150 465,140 475,140" style="fill:var(--compare-b)"/&gt;&lt;line x1="470" y1="200" x2="470" y2="226" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="470,230 465,220 475,220" style="fill:var(--compare-b)"/&gt;&lt;line x1="545" y1="95" x2="565" y2="95" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="80" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="568" y="80" width="44" height="30" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="110" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="590" y="99" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;pvc-0&lt;/text&gt;&lt;line x1="545" y1="175" x2="565" y2="175" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="160" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="568" y="160" width="44" height="30" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="190" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="590" y="179" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;pvc-1&lt;/text&gt;&lt;line x1="545" y1="255" x2="565" y2="255" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="240" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="568" y="240" width="44" height="30" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;ellipse cx="590" cy="270" rx="22" ry="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="590" y="259" text-anchor="middle" font-size="9" style="fill:var(--content)"&gt;pvc-2&lt;/text&gt;&lt;text x="470" y="315" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;stable name, network ID &amp;amp; storage per pod&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;StatefulSet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod naming&lt;/td&gt;
&lt;td&gt;Random hash suffix per replica (web-7f9d4c-x2z9p), changes on every recreation&lt;/td&gt;
&lt;td&gt;Stable ordinal index (web-0, web-1, web-2) fixed for the pod&amp;rsquo;s lifetime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network identity&lt;/td&gt;
&lt;td&gt;Pods share a single Service VIP/DNS; individual pods have no predictable DNS name&lt;/td&gt;
&lt;td&gt;Requires a headless Service; each pod gets a stable DNS entry (web-0.svc.namespace)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;PVCs, if used, aren&amp;rsquo;t guaranteed to reattach to the same pod on recreation&lt;/td&gt;
&lt;td&gt;volumeClaimTemplates provision a dedicated PVC per pod that persists and reattaches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Creates or removes pods in parallel, in any order&lt;/td&gt;
&lt;td&gt;Scales one pod at a time in strict ordinal order (0, 1, 2, &amp;hellip;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rolling updates&lt;/td&gt;
&lt;td&gt;Replaces pods per maxSurge/maxUnavailable, order not guaranteed&lt;/td&gt;
&lt;td&gt;Updates pods one at a time in reverse ordinal order (N-1 down to 0) by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure recovery&lt;/td&gt;
&lt;td&gt;Replacement pod gets a new name and no guaranteed storage continuity&lt;/td&gt;
&lt;td&gt;Replacement pod keeps the same name/identity and reattaches its original PVC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical workload&lt;/td&gt;
&lt;td&gt;Stateless web servers, APIs, workers that scale horizontally&lt;/td&gt;
&lt;td&gt;Databases, message queues, and clustered systems needing stable peers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deployment pods get &lt;strong class="kw"&gt;random names&lt;/strong&gt; on every recreation, while StatefulSet pods keep a fixed &lt;strong class="kw"&gt;ordinal name&lt;/strong&gt; for life&lt;/li&gt;
&lt;li&gt;Only StatefulSet supports &lt;strong class="kw"&gt;volumeClaimTemplates&lt;/strong&gt;, giving each pod its own persistent volume that survives rescheduling&lt;/li&gt;
&lt;li&gt;StatefulSet requires a &lt;strong class="kw"&gt;headless Service&lt;/strong&gt; to give each pod a resolvable, stable DNS entry&lt;/li&gt;
&lt;li&gt;StatefulSet scales and updates pods in strict &lt;strong class="kw"&gt;ordinal order&lt;/strong&gt;; Deployment does both in parallel&lt;/li&gt;
&lt;li&gt;On node failure, Deployment pods lose their identity entirely, while StatefulSet pods are recreated with the &lt;strong class="kw"&gt;same identity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Liveness Probe vs Readiness Probe: Kubernetes Health Checks Compared</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-liveness-probe-vs-readiness-probe-kubernetes-health-checks-c/</link><pubDate>Mon, 03 Aug 2026 05:22:16 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-liveness-probe-vs-readiness-probe-kubernetes-health-checks-c/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Kubernetes uses liveness and readiness probes to answer two different questions about a running container: is it alive, and is it ready to serve requests. A failed liveness probe triggers a container &lt;strong class="kw"&gt;restart&lt;/strong&gt;, while a failed readiness probe only affects &lt;strong class="kw"&gt;traffic routing&lt;/strong&gt; by pulling the pod out of Service endpoints without killing it.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="10" x2="320" y2="350" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="26" text-anchor="middle" style="fill:var(--primary)" font-size="15" font-weight="bold"&gt;Liveness Probe&lt;/text&gt;&lt;text x="480" y="26" text-anchor="middle" style="fill:var(--primary)" font-size="15" font-weight="bold"&gt;Readiness Probe&lt;/text&gt;&lt;rect x="90" y="42" width="140" height="34" rx="4" style="fill:none;stroke:var(--content)"/&gt;&lt;text x="160" y="63" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Container&lt;/text&gt;&lt;line x1="160" y1="76" x2="160" y2="96" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;rect x="60" y="96" width="200" height="38" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="119" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;kubelet: is it alive?&lt;/text&gt;&lt;line x1="160" y1="134" x2="160" y2="152" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;polygon points="160,152 198,180 160,208 122,180" style="fill:none;stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="184" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Fails?&lt;/text&gt;&lt;line x1="160" y1="208" x2="160" y2="240" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="172" y="226" style="fill:var(--secondary)" font-size="10"&gt;yes&lt;/text&gt;&lt;rect x="60" y="240" width="200" height="38" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="263" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Kill &amp;amp; Restart Container&lt;/text&gt;&lt;path d="M60,259 C15,259 15,59 88,59" style="fill:none;stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;polygon points="88,59 78,54 78,64" style="fill:var(--compare-a)"/&gt;&lt;line x1="122" y1="180" x2="90" y2="180" style="stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="105" y="192" text-anchor="end" style="fill:var(--secondary)" font-size="10"&gt;no&lt;/text&gt;&lt;text x="30" y="184" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;stays&lt;/text&gt;&lt;text x="30" y="196" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;running&lt;/text&gt;&lt;text x="160" y="305" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;No effect on Service traffic&lt;/text&gt;&lt;rect x="410" y="42" width="140" height="34" rx="4" style="fill:none;stroke:var(--content)"/&gt;&lt;text x="480" y="63" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Container&lt;/text&gt;&lt;line x1="480" y1="76" x2="480" y2="96" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="380" y="96" width="200" height="38" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="119" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;kubelet: is it ready?&lt;/text&gt;&lt;line x1="480" y1="134" x2="480" y2="152" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;polygon points="480,152 518,180 480,208 442,180" style="fill:none;stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="184" text-anchor="middle" style="fill:var(--content)" font-size="10"&gt;Ready?&lt;/text&gt;&lt;line x1="480" y1="208" x2="480" y2="240" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="492" y="226" style="fill:var(--secondary)" font-size="10"&gt;yes&lt;/text&gt;&lt;rect x="380" y="240" width="200" height="38" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="263" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Added to Service Endpoints&lt;/text&gt;&lt;line x1="518" y1="180" x2="530" y2="180" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="535" y="175" style="fill:var(--secondary)" font-size="10"&gt;no&lt;/text&gt;&lt;rect x="530" y="186" width="80" height="46" rx="4" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3 3"/&gt;&lt;text x="570" y="204" text-anchor="middle" style="fill:var(--content)" font-size="9"&gt;Removed&lt;/text&gt;&lt;text x="570" y="216" text-anchor="middle" style="fill:var(--content)" font-size="9"&gt;(not killed)&lt;/text&gt;&lt;line x1="480" y1="278" x2="480" y2="300" style="stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="390" y="300" width="180" height="34" rx="4" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="480" y="321" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;Service / Load Balancer&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Liveness Probe&lt;/th&gt;
&lt;th&gt;Readiness Probe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core question&lt;/td&gt;
&lt;td&gt;Is the process still functioning?&lt;/td&gt;
&lt;td&gt;Is the process ready to accept traffic?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action on failure&lt;/td&gt;
&lt;td&gt;kubelet kills and restarts the container&lt;/td&gt;
&lt;td&gt;Container is left running, no restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effect on Service endpoints&lt;/td&gt;
&lt;td&gt;None directly; pod may keep receiving traffic until restart completes&lt;/td&gt;
&lt;td&gt;Pod is removed from Service endpoints, stops receiving traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effect on rolling deployments&lt;/td&gt;
&lt;td&gt;Not consulted for rollout progress&lt;/td&gt;
&lt;td&gt;Must pass before the pod counts as available and rollout proceeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart count impact&lt;/td&gt;
&lt;td&gt;Increments the container restart count on each failure&lt;/td&gt;
&lt;td&gt;Never causes a restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical checks used&lt;/td&gt;
&lt;td&gt;Lightweight self-check for hangs or deadlocks&lt;/td&gt;
&lt;td&gt;Checks dependency health: DB connections, cache warm-up, config load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Misconfiguration risk&lt;/td&gt;
&lt;td&gt;Too-aggressive thresholds cause restart loops (CrashLoopBackOff)&lt;/td&gt;
&lt;td&gt;Too-aggressive thresholds pull healthy pods out of rotation, cutting capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Liveness failure causes a container &lt;strong class="kw"&gt;restart&lt;/strong&gt;; readiness failure only removes the pod from &lt;strong class="kw"&gt;Service endpoints&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Liveness answers &amp;ldquo;is it alive,&amp;rdquo; readiness answers &amp;ldquo;is it &lt;strong class="kw"&gt;ready for traffic&lt;/strong&gt;.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Readiness gates &lt;strong class="kw"&gt;rolling deployments&lt;/strong&gt;; liveness has no say in rollout progress.&lt;/li&gt;
&lt;li&gt;Using a dependency check as a liveness probe risks a &lt;strong class="kw"&gt;restart loop&lt;/strong&gt; when the real problem is an external service, not the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Liveness Probe&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Helm vs Kustomize: Templating vs Overlay-Based Kubernetes Config</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-helm-vs-kustomize-templating-vs-overlay-based-kubernetes-con/</link><pubDate>Mon, 03 Aug 2026 05:20:13 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-helm-vs-kustomize-templating-vs-overlay-based-kubernetes-con/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Helm packages Kubernetes manifests as parameterized &lt;strong class="kw"&gt;charts&lt;/strong&gt; rendered through a Go templating engine, then tracks each install as a versioned release. Kustomize takes plain manifests and applies declarative &lt;strong class="kw"&gt;overlays&lt;/strong&gt; that patch a base configuration per environment, with no templating language or release state at all.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;text x="160" y="30" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Helm&lt;/text&gt;&lt;text x="480" y="30" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Kustomize&lt;/text&gt;&lt;rect x="40" y="55" width="240" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="80" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;Chart&lt;/text&gt;&lt;text x="160" y="100" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;templates/*.yaml + values.yaml&lt;/text&gt;&lt;line x1="160" y1="115" x2="160" y2="150" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;rect x="40" y="150" width="240" height="45" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="178" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;helm template / install&lt;/text&gt;&lt;line x1="160" y1="195" x2="160" y2="230" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;rect x="40" y="230" width="240" height="45" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="160" y="253" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Rendered manifests&lt;/text&gt;&lt;text x="160" y="268" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;tracked as a Release&lt;/text&gt;&lt;line x1="160" y1="275" x2="160" y2="305" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;&lt;rect x="40" y="305" width="240" height="40" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4 3"/&gt;&lt;text x="160" y="330" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Cluster&lt;/text&gt;&lt;rect x="400" y="55" width="110" height="55" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="455" y="78" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;base/&lt;/text&gt;&lt;text x="455" y="95" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;plain manifests&lt;/text&gt;&lt;rect x="520" y="55" width="110" height="55" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="575" y="78" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;overlays/prod&lt;/text&gt;&lt;text x="575" y="95" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;patches&lt;/text&gt;&lt;line x1="455" y1="115" x2="500" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;line x1="575" y1="115" x2="510" y2="150" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="400" y="150" width="230" height="45" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="515" y="178" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;kustomize build&lt;/text&gt;&lt;line x1="515" y1="195" x2="515" y2="230" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="400" y="230" width="230" height="45" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="515" y="253" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Merged manifests&lt;/text&gt;&lt;text x="515" y="268" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;no state tracked&lt;/text&gt;&lt;line x1="515" y1="275" x2="515" y2="305" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;&lt;rect x="400" y="305" width="230" height="40" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4 3"/&gt;&lt;text x="515" y="330" text-anchor="middle" font-size="12" style="fill:var(--content)"&gt;Cluster&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto"&gt;&lt;path d="M0,0 L8,4 L0,8 Z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Helm&lt;/th&gt;
&lt;th&gt;Kustomize&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configuration model&lt;/td&gt;
&lt;td&gt;Go template engine that generates YAML text before it&amp;rsquo;s parsed&lt;/td&gt;
&lt;td&gt;Native Kubernetes objects patched via strategic merge or JSON patch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input format&lt;/td&gt;
&lt;td&gt;Chart with templates/, values.yaml, and Chart.yaml metadata&lt;/td&gt;
&lt;td&gt;Plain, valid YAML manifests plus a kustomization.yaml&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameterization&lt;/td&gt;
&lt;td&gt;Placeholder values injected as text, so output can become invalid YAML if misused&lt;/td&gt;
&lt;td&gt;Structured patches applied to already-valid objects, so output stays schema-correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment customization&lt;/td&gt;
&lt;td&gt;Layered values files (values-prod.yaml) merged into one chart&lt;/td&gt;
&lt;td&gt;Overlay directories per environment referencing a shared base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packaging &amp;amp; distribution&lt;/td&gt;
&lt;td&gt;Versioned, shareable chart archives published to chart repositories or OCI registries&lt;/td&gt;
&lt;td&gt;No packaging format; kustomization directories are just checked into git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency management&lt;/td&gt;
&lt;td&gt;Subcharts declared in Chart.yaml and pulled via helm dependency update&lt;/td&gt;
&lt;td&gt;Bases and components composed by referencing other directories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment execution&lt;/td&gt;
&lt;td&gt;helm install/upgrade tracks a named release and its revision history&lt;/td&gt;
&lt;td&gt;kustomize build pipes to kubectl apply with no release object created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback &amp;amp; drift&lt;/td&gt;
&lt;td&gt;helm rollback reverts to a stored prior release revision&lt;/td&gt;
&lt;td&gt;No built-in rollback; relies on git revert or kubectl&amp;rsquo;s own history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Helm renders manifests through &lt;strong class="kw"&gt;text templating&lt;/strong&gt;, while Kustomize edits already-parsed objects via &lt;strong class="kw"&gt;structural patches&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Helm tracks installs as stateful &lt;strong class="kw"&gt;releases&lt;/strong&gt; with revision history; Kustomize has &lt;strong class="kw"&gt;no release state&lt;/strong&gt; at all&lt;/li&gt;
&lt;li&gt;Helm charts are &lt;strong class="kw"&gt;packaged and versioned&lt;/strong&gt; for reuse; Kustomize configs are just plain manifests in git&lt;/li&gt;
&lt;li&gt;Kustomize is built into &lt;strong class="kw"&gt;kubectl&lt;/strong&gt; directly, while Helm requires installing a separate CLI/tool&lt;/li&gt;
&lt;li&gt;Many teams combine both: a Helm chart as the base, customized per environment with Kustomize&amp;rsquo;s &lt;strong class="kw"&gt;overlay patches&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Helm&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>GitOps vs Traditional CI/CD: Push vs Pull Deployment</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-gitops-vs-traditional-ci-cd-push-vs-pull-deployment/</link><pubDate>Mon, 03 Aug 2026 05:17:05 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-gitops-vs-traditional-ci-cd-push-vs-pull-deployment/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Both aim to automate software delivery, but they differ in who initiates the deployment and where the source of truth lives. Traditional &lt;strong class="kw"&gt;CI/CD&lt;/strong&gt; pushes changes into infrastructure from an external pipeline, while &lt;strong class="kw"&gt;GitOps&lt;/strong&gt; has an in-cluster agent continuously pull and reconcile state against a Git repository. The distinction matters most for security posture, drift handling, and auditability in Kubernetes-native environments.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;defs&gt;
&lt;marker id="arrowA" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;
&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-a)"/&gt;
&lt;/marker&gt;
&lt;marker id="arrowB" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;
&lt;path d="M0,0 L10,5 L0,10 z" style="fill:var(--compare-b)"/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;line x1="320" y1="10" x2="320" y2="350" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4,4"/&gt;
&lt;text x="170" y="26" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;Traditional CI/CD&lt;/text&gt;
&lt;text x="170" y="44" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;push-based&lt;/text&gt;
&lt;text x="490" y="26" text-anchor="middle" style="fill:var(--primary)" font-size="16" font-weight="bold"&gt;GitOps&lt;/text&gt;
&lt;text x="490" y="44" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;pull-based&lt;/text&gt;
&lt;rect x="60" y="60" width="160" height="45" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="140" y="87" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Git Repo&lt;/text&gt;
&lt;line x1="140" y1="105" x2="140" y2="158" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;
&lt;text x="150" y="135" style="fill:var(--secondary)" font-size="10"&gt;merge trigger&lt;/text&gt;
&lt;rect x="60" y="160" width="160" height="45" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="140" y="187" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;CI/CD Pipeline&lt;/text&gt;
&lt;line x1="140" y1="205" x2="140" y2="258" style="stroke:var(--compare-a)" stroke-width="1.5" marker-end="url(#arrowA)"/&gt;
&lt;text x="150" y="235" style="fill:var(--secondary)" font-size="10"&gt;kubectl apply&lt;/text&gt;
&lt;text x="150" y="248" style="fill:var(--secondary)" font-size="10"&gt;(holds cluster creds)&lt;/text&gt;
&lt;rect x="60" y="260" width="160" height="55" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;
&lt;text x="140" y="291" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Production&lt;/text&gt;
&lt;text x="140" y="306" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Cluster&lt;/text&gt;
&lt;text x="140" y="335" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;external system pushes with cluster creds&lt;/text&gt;
&lt;rect x="420" y="60" width="160" height="45" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="500" y="87" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Git Repo&lt;/text&gt;
&lt;rect x="420" y="260" width="160" height="55" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;
&lt;text x="500" y="284" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;Production Cluster&lt;/text&gt;
&lt;text x="500" y="300" text-anchor="middle" style="fill:var(--content)" font-size="11"&gt;(GitOps agent)&lt;/text&gt;
&lt;path d="M 460,260 C 600,225 600,140 465,107" fill="none" style="stroke:var(--compare-b)" stroke-width="1.5" marker-end="url(#arrowB)"/&gt;
&lt;text x="605" y="185" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;pulls &amp;amp;&lt;/text&gt;
&lt;text x="605" y="198" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;diffs state&lt;/text&gt;
&lt;text x="500" y="335" text-anchor="middle" style="fill:var(--secondary)" font-size="10"&gt;agent auto-reconciles drift, no external creds&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Traditional CI/CD&lt;/th&gt;
&lt;th&gt;GitOps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment trigger&lt;/td&gt;
&lt;td&gt;Pipeline job runs on merge/tag and executes a deploy step&lt;/td&gt;
&lt;td&gt;In-cluster agent continuously polls or watches the Git repo for changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source of truth&lt;/td&gt;
&lt;td&gt;Pipeline scripts and job history define what was deployed&lt;/td&gt;
&lt;td&gt;Git repository is the sole declarative source of desired state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster access model&lt;/td&gt;
&lt;td&gt;CI server holds cluster credentials and pushes from outside the network&lt;/td&gt;
&lt;td&gt;Agent runs inside the cluster; no external system needs cluster credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift detection&lt;/td&gt;
&lt;td&gt;None built-in; manual kubectl edits go unnoticed until the next run&lt;/td&gt;
&lt;td&gt;Agent continuously compares live state to Git and flags or corrects drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;Re-run the pipeline against a previous artifact or commit&lt;/td&gt;
&lt;td&gt;git revert triggers an automatic re-sync to the prior state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;Split across CI logs, deploy scripts, and any manual changes&lt;/td&gt;
&lt;td&gt;Single, complete history captured in Git commit log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-cluster scaling&lt;/td&gt;
&lt;td&gt;Pipeline needs explicit logic and credentials per target environment&lt;/td&gt;
&lt;td&gt;Each cluster runs its own agent watching the same or a branched repo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CI/CD is &lt;strong class="kw"&gt;push-based&lt;/strong&gt; from an external system; GitOps is &lt;strong class="kw"&gt;pull-based&lt;/strong&gt; from inside the cluster&lt;/li&gt;
&lt;li&gt;GitOps treats the Git repo as the exclusive &lt;strong class="kw"&gt;source of truth&lt;/strong&gt;; CI/CD&amp;rsquo;s truth lives in pipeline state&lt;/li&gt;
&lt;li&gt;CI/CD requires the pipeline to hold &lt;strong class="kw"&gt;cluster credentials&lt;/strong&gt;; GitOps keeps them inside the cluster boundary&lt;/li&gt;
&lt;li&gt;GitOps performs automatic &lt;strong class="kw"&gt;drift correction&lt;/strong&gt;; CI/CD has no ongoing reconciliation&lt;/li&gt;
&lt;li&gt;Rollback in GitOps is a simple &lt;strong class="kw"&gt;git revert&lt;/strong&gt; instead of re-running a pipeline job&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Traditional CI/CD&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Docker vs Kubernetes: Containers vs Orchestration</title><link>https://comparison.metacog.co.kr/posts/2026-08-03-docker-vs-kubernetes-containers-vs-orchestration/</link><pubDate>Mon, 03 Aug 2026 05:15:40 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-03-docker-vs-kubernetes-containers-vs-orchestration/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;&lt;strong class="kw"&gt;Docker&lt;/strong&gt; packages an application and its dependencies into a portable container image and runs it on a single host, while &lt;strong class="kw"&gt;Kubernetes&lt;/strong&gt; schedules, scales, and heals many containers across a cluster of machines. They aren&amp;rsquo;t direct substitutes — Kubernetes typically runs containers built by Docker (or another OCI-compatible tool), sitting one layer above it.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;text x="160" y="32" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Docker&lt;/text&gt;&lt;text x="480" y="32" text-anchor="middle" font-size="18" style="fill:var(--primary)"&gt;Kubernetes&lt;/text&gt;&lt;rect x="40" y="55" width="240" height="270" rx="8" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="160" y="78" text-anchor="middle" font-size="12" style="fill:var(--secondary)"&gt;Single Host&lt;/text&gt;&lt;rect x="65" y="95" width="70" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="100" y="130" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;app A&lt;/text&gt;&lt;rect x="150" y="95" width="70" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="185" y="130" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;app B&lt;/text&gt;&lt;rect x="65" y="170" width="70" height="60" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="100" y="205" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;app C&lt;/text&gt;&lt;rect x="150" y="170" width="70" height="60" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="4"/&gt;&lt;text x="185" y="205" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;idle&lt;/text&gt;&lt;text x="160" y="265" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;docker run&lt;/text&gt;&lt;text x="160" y="282" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;manual, per-host&lt;/text&gt;&lt;text x="160" y="310" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;if host dies, all lost&lt;/text&gt;&lt;rect x="400" y="55" width="160" height="36" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="480" y="78" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;Control Plane&lt;/text&gt;&lt;line x1="440" y1="91" x2="400" y2="120" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3"/&gt;&lt;line x1="480" y1="91" x2="480" y2="120" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3"/&gt;&lt;line x1="520" y1="91" x2="560" y2="120" style="stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3"/&gt;&lt;rect x="360" y="120" width="80" height="90" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="400" y="135" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;Node 1&lt;/text&gt;&lt;rect x="370" y="145" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="404" y="145" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="370" y="178" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="440" y="120" width="80" height="90" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="480" y="135" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;Node 2&lt;/text&gt;&lt;rect x="450" y="145" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="484" y="145" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="520" y="120" width="80" height="90" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="560" y="135" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;Node 3&lt;/text&gt;&lt;rect x="530" y="145" width="26" height="26" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;rect x="564" y="145" width="26" height="26" rx="4" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="3"/&gt;&lt;text x="480" y="235" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;scheduler places pods&lt;/text&gt;&lt;text x="480" y="252" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;auto-reschedules on failure&lt;/text&gt;&lt;text x="480" y="280" text-anchor="middle" font-size="11" style="fill:var(--secondary)"&gt;declarative, cluster-wide&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Docker&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core purpose&lt;/td&gt;
&lt;td&gt;Build, package, and run containers from a single image spec&lt;/td&gt;
&lt;td&gt;Orchestrate and manage many containers across a fleet of machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit of work&lt;/td&gt;
&lt;td&gt;Container, defined by a Dockerfile and run via docker run&lt;/td&gt;
&lt;td&gt;Pod, a group of one or more containers scheduled together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment scope&lt;/td&gt;
&lt;td&gt;Single host (or manually scripted across hosts)&lt;/td&gt;
&lt;td&gt;Multi-node cluster with a control plane scheduling workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration model&lt;/td&gt;
&lt;td&gt;Imperative CLI commands or docker-compose.yml&lt;/td&gt;
&lt;td&gt;Declarative YAML manifests reconciled continuously toward desired state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Networking &amp;amp; discovery&lt;/td&gt;
&lt;td&gt;User-defined bridge networks and container name resolution&lt;/td&gt;
&lt;td&gt;Cluster-wide Services, DNS, and Ingress across nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Manual — start more containers or use docker-compose scale&lt;/td&gt;
&lt;td&gt;Automated via ReplicaSets and Horizontal Pod Autoscaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure recovery&lt;/td&gt;
&lt;td&gt;No built-in restart across host failure; relies on restart policies per host&lt;/td&gt;
&lt;td&gt;Self-healing — reschedules pods automatically if a node or container fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollouts &amp;amp; updates&lt;/td&gt;
&lt;td&gt;Rebuild image and manually restart containers&lt;/td&gt;
&lt;td&gt;Rolling updates and rollbacks managed declaratively per Deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker operates at the level of a single &lt;strong class="kw"&gt;container&lt;/strong&gt;; Kubernetes operates at the level of a &lt;strong class="kw"&gt;cluster&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Kubernetes doesn&amp;rsquo;t replace Docker — it typically schedules containers that Docker (or another &lt;strong class="kw"&gt;container runtime&lt;/strong&gt;) built and runs.&lt;/li&gt;
&lt;li&gt;Docker&amp;rsquo;s model is largely &lt;strong class="kw"&gt;imperative&lt;/strong&gt;, while Kubernetes is fundamentally &lt;strong class="kw"&gt;declarative&lt;/strong&gt;, continuously reconciling actual state to desired state.&lt;/li&gt;
&lt;li&gt;Kubernetes adds &lt;strong class="kw"&gt;self-healing&lt;/strong&gt; and autoscaling that plain Docker has no native concept of.&lt;/li&gt;
&lt;li&gt;For a single app on one machine, Kubernetes&amp;rsquo; &lt;strong class="kw"&gt;control plane&lt;/strong&gt; overhead is often unjustified complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Role vs ClusterRole: Kubernetes RBAC Scope Compared</title><link>https://comparison.metacog.co.kr/posts/2026-08-02-role-vs-clusterrole-kubernetes-rbac-scope-compared/</link><pubDate>Sun, 02 Aug 2026 11:24:24 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-08-02-role-vs-clusterrole-kubernetes-rbac-scope-compared/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Role and ClusterRole are both Kubernetes RBAC objects that define sets of permission rules (verbs on resources), but they differ in scope: a Role only applies within a single namespace, while a ClusterRole is defined once for the whole cluster and can be bound either cluster-wide or scoped down to one namespace. Understanding this distinction is essential for applying least-privilege access control in multi-tenant clusters.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;defs&gt;&lt;marker id="arrowA" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;&lt;path d="M0,0L10,5L0,10z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;text x="155" y="35" text-anchor="middle" font-size="20" font-weight="bold" style="fill:var(--primary)"&gt;Role&lt;/text&gt;&lt;text x="477" y="35" text-anchor="middle" font-size="20" font-weight="bold" style="fill:var(--primary)"&gt;ClusterRole&lt;/text&gt;&lt;rect x="30" y="55" width="250" height="270" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5" stroke-dasharray="5 5"/&gt;&lt;text x="45" y="75" font-size="12" style="fill:var(--secondary)"&gt;Namespace: dev&lt;/text&gt;&lt;rect x="90" y="95" width="130" height="44" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="155" y="122" text-anchor="middle" font-size="14" style="fill:var(--content)"&gt;Role&lt;/text&gt;&lt;line x1="155" y1="139" x2="155" y2="174" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;rect x="90" y="177" width="130" height="40" rx="6" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="155" y="202" text-anchor="middle" font-size="13" style="fill:var(--content)"&gt;RoleBinding&lt;/text&gt;&lt;line x1="155" y1="217" x2="155" y2="254" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;circle cx="155" cy="278" r="20" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="155" y="282" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;User&lt;/text&gt;&lt;text x="155" y="316" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;limited to this namespace&lt;/text&gt;&lt;rect x="345" y="55" width="265" height="270" rx="6" style="fill:none;stroke:var(--border)" stroke-width="1.5"/&gt;&lt;text x="360" y="75" font-size="12" style="fill:var(--secondary)"&gt;Cluster scope&lt;/text&gt;&lt;rect x="412" y="95" width="140" height="44" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="482" y="122" text-anchor="middle" font-size="14" style="fill:var(--content)"&gt;ClusterRole&lt;/text&gt;&lt;line x1="450" y1="139" x2="417" y2="174" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;line x1="514" y1="139" x2="558" y2="174" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;rect x="360" y="177" width="110" height="36" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="415" y="199" text-anchor="middle" font-size="11" style="fill:var(--content)"&gt;RoleBinding&lt;/text&gt;&lt;rect x="495" y="177" width="130" height="36" rx="6" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="560" y="199" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;ClusterRoleBinding&lt;/text&gt;&lt;line x1="415" y1="213" x2="415" y2="248" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;line x1="560" y1="213" x2="560" y2="248" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;circle cx="415" cy="268" r="18" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="415" y="272" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;User&lt;/text&gt;&lt;text x="415" y="300" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;this ns only&lt;/text&gt;&lt;circle cx="560" cy="268" r="18" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5"/&gt;&lt;text x="560" y="272" text-anchor="middle" font-size="10" style="fill:var(--content)"&gt;User&lt;/text&gt;&lt;text x="560" y="300" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;all namespaces&lt;/text&gt;&lt;text x="477" y="318" text-anchor="middle" font-size="10" style="fill:var(--secondary)"&gt;one definition, reused via either binding&lt;/text&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;ClusterRole&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API object scope&lt;/td&gt;
&lt;td&gt;Namespaced object; exists only within one Namespace&lt;/td&gt;
&lt;td&gt;Cluster-scoped object; exists once for the entire cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resources it can grant access to&lt;/td&gt;
&lt;td&gt;Only namespaced resources (pods, configmaps, secrets, etc.) within its own namespace&lt;/td&gt;
&lt;td&gt;Namespaced resources cluster-wide plus cluster-scoped resources such as nodes, persistentvolumes, and namespaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-resource URLs (e.g. /healthz, /metrics)&lt;/td&gt;
&lt;td&gt;Cannot reference non-resource URLs&lt;/td&gt;
&lt;td&gt;Can include rules for non-resource URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binding object required&lt;/td&gt;
&lt;td&gt;RoleBinding only, created in the same namespace&lt;/td&gt;
&lt;td&gt;RoleBinding for a namespace-scoped grant, or ClusterRoleBinding for a cluster-wide grant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective grant when bound&lt;/td&gt;
&lt;td&gt;Permissions always limited to the Role&amp;rsquo;s own namespace&lt;/td&gt;
&lt;td&gt;Spans every namespace when bound via ClusterRoleBinding, or just one namespace when bound via RoleBinding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse across namespaces&lt;/td&gt;
&lt;td&gt;Must be duplicated in each namespace that needs the same rules&lt;/td&gt;
&lt;td&gt;Defined once, reused across many namespaces or cluster-wide via separate bindings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregation support&lt;/td&gt;
&lt;td&gt;None; rules are static within the object&lt;/td&gt;
&lt;td&gt;Supports aggregationRule to auto-combine rules from other ClusterRoles by label selector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical built-in examples&lt;/td&gt;
&lt;td&gt;None shipped by default; teams author their own per namespace&lt;/td&gt;
&lt;td&gt;cluster-admin, admin, edit, view, and system: component roles ship as default ClusterRoles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong class="kw"&gt;Role&lt;/strong&gt; is namespace-scoped while &lt;strong class="kw"&gt;ClusterRole&lt;/strong&gt; is cluster-scoped by definition, regardless of how it&amp;rsquo;s later bound.&lt;/li&gt;
&lt;li&gt;Only ClusterRole can grant access to cluster-scoped resources like nodes or to &lt;strong class="kw"&gt;non-resource URLs&lt;/strong&gt; such as /metrics.&lt;/li&gt;
&lt;li&gt;A ClusterRole can still be restricted to one namespace by binding it with a &lt;strong class="kw"&gt;RoleBinding&lt;/strong&gt; instead of a ClusterRoleBinding.&lt;/li&gt;
&lt;li&gt;ClusterRole supports &lt;strong class="kw"&gt;aggregation&lt;/strong&gt; to compose permissions from labeled ClusterRoles; Role has no equivalent mechanism.&lt;/li&gt;
&lt;li&gt;Kubernetes ships default admin/edit/view permission sets as &lt;strong class="kw"&gt;built-in ClusterRoles&lt;/strong&gt;, never as Roles.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>