<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Josef Polar</title>
    <description>The latest articles on DEV Community by Josef Polar (@josefpolar).</description>
    <link>https://dev.to/josefpolar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3904788%2Fc77f91e8-0242-4e0b-97cf-184d1d6eab58.png</url>
      <title>DEV Community: Josef Polar</title>
      <link>https://dev.to/josefpolar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/josefpolar"/>
    <language>en</language>
    <item>
      <title>We're all hybrid now</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Wed, 20 May 2026 12:51:27 +0000</pubDate>
      <link>https://dev.to/josefpolar/were-all-hybrid-now-3cco</link>
      <guid>https://dev.to/josefpolar/were-all-hybrid-now-3cco</guid>
      <description>&lt;p&gt;For about a decade, "cloud-native" was a kind of identity, a signal a choice had been made about how software would be built and where it would run. It meant containers and Kubernetes and managed services and twelve-factor everything, with greenfield as the default starting position. The architecture diagram had no on-prem box in the corner, and the absence of one was treated as a feature rather than an oversight.&lt;/p&gt;

&lt;p&gt;This identity is quietly dying, even if the conference talks haven't caught up yet. Most teams running at any real scale now operate a mix of containers, VMs, and physical servers spread across two or three clouds and at least one data center nobody wants to touch, which means the label "cloud-native" has become a description of a workload rather than a description of a company. Once teams accept this, a surprising amount of received wisdom stops holding up under any kind of weight.&lt;/p&gt;

&lt;p&gt;Service mesh is the most obvious example of an idea built for a world which never arrived. The sidecar-per-pod model assumed every workload would live in Kubernetes, and the considerable operational cost was worth paying because the architecture was uniform enough to justify it. Neither assumption survives contact with a real enterprise, where the mesh tax is real but the more serious problem is geometric: the mesh doesn't extend to the legacy payments system running on bare metal or the analytics cluster sitting on VMs. Teams end up running a mesh for the containerized half of the estate and something else entirely for the other half, and the boundary between the two becomes the actual hard problem to solve.&lt;/p&gt;

&lt;p&gt;North-south and east-west are the same problem in a hybrid estate, even though the mesh treated them as different because it could only ever see one of them clearly. The moment traffic crosses an environment edge, and in any real hybrid setup it crosses one constantly, teams are back to the boundary problem the mesh was supposed to abstract away in the first place.&lt;br&gt;
The industry built abstractions for a world which didn't arrive, and the tools assuming otherwise are now generating most of the operational friction platform teams feel day to day. We were promised greenfield Kubernetes everywhere and we got a patchwork instead, which has been the actual condition of enterprise infrastructure for longer than most vendors are willing to admit publicly.&lt;br&gt;
Something less ideologically clean is emerging in response, and a few vendors have started naming it directly. HAProxy has been talking about Universal Mesh, the idea being a single data plane spanning Kubernetes and VMs and bare metal alike, managed by one control plane sitting at the boundary of each environment rather than embedded inside every pod. The framing matters because it concedes the thing most vendors have spent years dancing around, namely the assumption a uniform substrate was ever realistic in the first place. The same policies apply whether traffic is hitting the public internet, moving between clouds, or routing between internal services, which isn't as architecturally elegant as "the mesh handles everything" but has the considerable advantage of matching the territory people are actually operating in.&lt;/p&gt;

&lt;p&gt;There's a generational shift underneath the technical shift too, and it's worth naming directly. Engineers who came up during the cloud-native peak treated Kubernetes as the substrate and everything else as a migration target to be eventually retired, whereas engineers running platforms today treat Kubernetes as one substrate among several and don't expect the others to disappear on any reasonable timeline. They're not nostalgic about VMs and they're not waiting for the legacy systems to be rewritten, because they've accepted the rewrite isn't coming, the on-prem data center is staying, and the job is to make a heterogeneous estate behave like a coherent system regardless of where any particular workload happens to live.&lt;br&gt;
That's a more mature stance than the one which preceded it, and it's the stance the next decade of platform work will be built on whether the vendors notice or not.&lt;/p&gt;

&lt;p&gt;The interesting tooling now isn't the kind insisting on a particular substrate as a precondition for operating, it's the kind substrate-agnostic by design, running the same way on a bare-metal box, a VM, and a Kubernetes node, giving operators one place to manage all of it without pretending the others don't exist.&lt;/p&gt;

&lt;p&gt;Cloud-native served a real purpose as a forcing function, pushing teams toward better operational habits and cleaner deployment models and real observability practices most of them wouldn't have adopted otherwise. As an identity it has outlived its usefulness, and the teams doing the best work right now aren't cloud-native or on-prem or hybrid in any meaningful self-description, they're just running infrastructure wherever it happens to live and treating the seams between environments as the actual product they're shipping.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloudnative</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Kubernetes policy engine — a buyer's guide</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Mon, 18 May 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/josefpolar/kubernetes-policy-engine-a-buyers-guide-40mk</link>
      <guid>https://dev.to/josefpolar/kubernetes-policy-engine-a-buyers-guide-40mk</guid>
      <description>&lt;p&gt;Four options for enforcing policy as code in a Kubernetes cluster, evaluated on learning curve, feature breadth, Kubernetes-native fit, community health, and how much ongoing maintenance you are taking on.&lt;/p&gt;




&lt;h2&gt;
  
  
  OPA / Gatekeeper
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature breadth&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes-native fit&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community health&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;OPA is a general-purpose policy engine originally built for service mesh, API gateways, and CI/CD; Gatekeeper is the Kubernetes admission controller wrapper on top of it&lt;/li&gt;
&lt;li&gt;Policies are written in Rego, a purpose-built declarative language that has a steep learning curve and is genuinely difficult to debug — most engineers need dedicated training to write non-trivial rules&lt;/li&gt;
&lt;li&gt;Gatekeeper handles validation only; mutation support was added later and remains limited compared to other options&lt;/li&gt;
&lt;li&gt;No native resource generation — you cannot use Gatekeeper to create dependent resources in response to policy events&lt;/li&gt;
&lt;li&gt;Two separate projects to track, update, and understand (OPA and Gatekeeper), which adds operational surface&lt;/li&gt;
&lt;li&gt;OPA itself is a CNCF graduated project with strong backing; Gatekeeper is actively maintained by a healthy community&lt;/li&gt;
&lt;li&gt;The Rego investment pays off if you need unified policy enforcement across Kubernetes and non-Kubernetes systems (APIs, microservices, Terraform) from a single language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay on OPA/Gatekeeper if you have existing Rego expertise or if cross-stack policy unification is a hard requirement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kubewarden
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature breadth&lt;/td&gt;
&lt;td&gt;7 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes-native fit&lt;/td&gt;
&lt;td&gt;7 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community health&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Policies compile to WebAssembly (WASM) modules, which means you can write them in Rust, Go, or any language with a WASM target — a genuine differentiator for teams with existing language preferences&lt;/li&gt;
&lt;li&gt;The WASM compilation and module distribution workflow adds a build step that most policy workflows do not need and most platform teams do not want to own&lt;/li&gt;
&lt;li&gt;Supports validate and mutate; resource generation support is limited&lt;/li&gt;
&lt;li&gt;PolicyReports are produced in the standard Kubernetes format, which integrates cleanly with dashboards&lt;/li&gt;
&lt;li&gt;Smaller community and slower release cadence than Kyverno or OPA/Gatekeeper; fewer production case studies&lt;/li&gt;
&lt;li&gt;Depends on a controller manager plus an audit scanner plus a policy server, which is more moving parts than the problem warrants for most clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worth considering for teams that already invest in WASM tooling or want to distribute policies as OCI artifacts without learning a policy-specific language.&lt;/p&gt;




&lt;h2&gt;
  
  
  jsPolicy
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature breadth&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes-native fit&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community health&lt;/td&gt;
&lt;td&gt;2 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Policies are written in JavaScript or TypeScript and executed in a V8 sandbox — execution is fast and the language is familiar to a wide audience&lt;/li&gt;
&lt;li&gt;The project is maintained by Loft Labs and has seen no meaningful releases or community activity since mid-2024; it should be treated as effectively unmaintained&lt;/li&gt;
&lt;li&gt;No path to CNCF graduation; not on any active roadmap; the GitHub repository has stalled issue backlogs and no active maintainer engagement&lt;/li&gt;
&lt;li&gt;Supports validation and mutation but not resource generation or image signature verification&lt;/li&gt;
&lt;li&gt;Running an unmaintained admission controller in a production cluster is a security liability — admission webhooks intercept every API request, and an unpatched webhook is a significant attack surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not adopt jsPolicy for new deployments. Teams running it should be planning a migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kyverno ✦ recommended
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;9 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature breadth&lt;/td&gt;
&lt;td&gt;10 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes-native fit&lt;/td&gt;
&lt;td&gt;10 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community health&lt;/td&gt;
&lt;td&gt;9 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Policies are Kubernetes resources — &lt;code&gt;ClusterPolicy&lt;/code&gt; and &lt;code&gt;Policy&lt;/code&gt; objects written in YAML, managed with &lt;code&gt;kubectl&lt;/code&gt;, versioned in Git, and applied with Kustomize or Helm like any other manifest&lt;/li&gt;
&lt;li&gt;No new language to learn; engineers already fluent in Kubernetes YAML can write functional policies on day one&lt;/li&gt;
&lt;li&gt;Supports the full policy lifecycle: validate, mutate, generate, clean up, and verify image signatures — all from a single engine with a consistent resource model&lt;/li&gt;
&lt;li&gt;Native image verification against Cosign and Notary signatures, with &lt;code&gt;ImageValidatingPolicy&lt;/code&gt; as a dedicated CRD (as of 1.14), covering software supply chain security without an additional tool&lt;/li&gt;
&lt;li&gt;Resource generation allows policies to create dependent resources automatically — default NetworkPolicies on namespace creation, for example — which no other tool in this group handles as cleanly&lt;/li&gt;
&lt;li&gt;Automatically generates rules for Pod controllers (Deployment, StatefulSet, DaemonSet) from a single Pod-level policy, removing a whole class of coverage gaps&lt;/li&gt;
&lt;li&gt;PolicyReports are generated automatically and integrate with Policy Reporter for dashboards and alerting&lt;/li&gt;
&lt;li&gt;CNCF incubating project with active development: 1.15 shipped in August 2025, 1.16 in November 2025; over 850 merged changes across 70+ contributors in the 1.15 cycle alone&lt;/li&gt;
&lt;li&gt;CLI supports offline policy testing in CI/CD pipelines against local manifests before anything reaches a cluster&lt;/li&gt;
&lt;li&gt;The new CEL-based policy types introduced in 1.14–1.16 align with native Kubernetes &lt;code&gt;ValidatingAdmissionPolicy&lt;/code&gt; and &lt;code&gt;MutatingAdmissionPolicy&lt;/code&gt;, giving a migration path toward in-server validation for performance-sensitive rules while keeping the Kyverno authoring experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only meaningful limitation is that Kyverno is purpose-built for Kubernetes and cannot enforce policy across non-Kubernetes systems — if cross-stack Rego reuse is genuinely required, OPA remains the answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Criteria comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;OPA / Gatekeeper&lt;/th&gt;
&lt;th&gt;Kubewarden&lt;/th&gt;
&lt;th&gt;jsPolicy&lt;/th&gt;
&lt;th&gt;Kyverno&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Policy language&lt;/td&gt;
&lt;td&gt;Rego&lt;/td&gt;
&lt;td&gt;Rust / Go / WASM&lt;/td&gt;
&lt;td&gt;JavaScript / TypeScript&lt;/td&gt;
&lt;td&gt;YAML (CEL in newer types)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validate&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutate&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generate resources&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image verification&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓ (Cosign + Notary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-gen for Pod controllers&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PolicyReports&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNCF status&lt;/td&gt;
&lt;td&gt;Graduated (OPA)&lt;/td&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;None — unmaintained&lt;/td&gt;
&lt;td&gt;Incubating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-K8s policy use&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;For teams starting fresh, Kyverno is the clear choice — it covers the full policy surface, requires no new language, and has the most active community in this group. Teams with cross-stack Rego investment should evaluate whether that investment justifies staying on OPA/Gatekeeper. Everyone running jsPolicy should migrate.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Buyer's Guide - S3 compatible storage in Kubernetes</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Wed, 13 May 2026 12:59:20 +0000</pubDate>
      <link>https://dev.to/josefpolar/buyers-guide-s3-compatible-storage-in-kubernetes-16a0</link>
      <guid>https://dev.to/josefpolar/buyers-guide-s3-compatible-storage-in-kubernetes-16a0</guid>
      <description>&lt;p&gt;Five options for teams running object storage inside a cluster, evaluated on Kubernetes integration, resource footprint, multi-site replication, license terms, and operational overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  MinIO
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site replication&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License clarity&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes operator exists and handles day-two operations like expansion and upgrades&lt;/li&gt;
&lt;li&gt;Documentation is thorough; large community with most operational questions already answered&lt;/li&gt;
&lt;li&gt;In 2024 MinIO relicensed from Apache 2 to BUSL-1.1 (Business Source License), which prohibits competing commercial use and converts to AGPL after four years — the project is effectively dead for most self-hosted use cases where the license previously made it attractive&lt;/li&gt;
&lt;li&gt;The last Apache 2 release (AGPL-era builds) is still in use at many sites but receives no upstream security fixes&lt;/li&gt;
&lt;li&gt;Multi-site replication was added after the fact and shows it — works, but feels bolted on&lt;/li&gt;
&lt;li&gt;Resource appetite makes it a poor fit when storage nodes are shared with application workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right call when throughput is the primary concern and you have dedicated storage infrastructure with real IOPS behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ceph / Rook
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;7 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site replication&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;2 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;2 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License clarity&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Rook operator wraps Ceph well enough that direct Ceph configuration is no longer required&lt;/li&gt;
&lt;li&gt;Unified block, filesystem, and object storage from a single cluster is a genuine advantage when you need all three&lt;/li&gt;
&lt;li&gt;Minimum viable three-node cluster consumes upward of 8 GB RAM before any workloads run&lt;/li&gt;
&lt;li&gt;Failure modes are numerous and tend to surface as subtle performance problems rather than obvious errors&lt;/li&gt;
&lt;li&gt;Deep Ceph expertise is effectively a prerequisite for running this in production without surprises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay on Rook if you already have a Ceph investment and need block and filesystem access alongside object storage; hard to justify for teams that only need S3.&lt;/p&gt;




&lt;h2&gt;
  
  
  SeaweedFS
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site replication&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;7 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License clarity&lt;/td&gt;
&lt;td&gt;7 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Handles high file counts and small objects particularly well&lt;/li&gt;
&lt;li&gt;Apache 2 license is clean and unambiguous for commercial use&lt;/li&gt;
&lt;li&gt;Kubernetes support relies on third-party Helm charts with inconsistent maintenance across versions&lt;/li&gt;
&lt;li&gt;Multi-datacenter replication model is more involved to configure than the problem warrants&lt;/li&gt;
&lt;li&gt;Less operational tooling and community support than MinIO or Ceph at equivalent scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worth evaluating for workloads dominated by small files, but the Kubernetes story needs more investment before it belongs in a production cluster alongside maintained operators.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zenko / Cloudserver
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site replication&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;4 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License clarity&lt;/td&gt;
&lt;td&gt;5 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Works well as an abstraction layer when you need a single S3 endpoint over multiple backends or cloud providers&lt;/li&gt;
&lt;li&gt;Multi-site story is strong for the gateway use case specifically&lt;/li&gt;
&lt;li&gt;Production deployment adds hard dependencies on Kafka and MongoDB — two more systems to operate and monitor&lt;/li&gt;
&lt;li&gt;Mixed licensing across components adds ambiguity that the Apache 2 core alone does not resolve&lt;/li&gt;
&lt;li&gt;Overhead is difficult to justify unless the multi-cloud gateway is the actual requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reasonable choice if the specific problem is abstracting over multiple existing backends; not the right fit if you are building a storage tier from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Garage ✦ recommended
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site replication&lt;/td&gt;
&lt;td&gt;9 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;9 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;8 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License clarity&lt;/td&gt;
&lt;td&gt;9 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Single static binary with no runtime dependencies — nothing to install beyond the process itself&lt;/li&gt;
&lt;li&gt;Zone-aware placement is built into the cluster layout model, not added as an afterthought&lt;/li&gt;
&lt;li&gt;Idle footprint on a three-node cluster sits around 128 MB RAM, making it viable on edge nodes and shared hardware&lt;/li&gt;
&lt;li&gt;AGPL license with no commercial carve-outs, BSL clauses, or enterprise-tier feature gates&lt;/li&gt;
&lt;li&gt;Helm chart is actively maintained and the configuration model stays legible under operational pressure&lt;/li&gt;
&lt;li&gt;Failure modes are narrow enough that a single engineer can own the storage layer without treating it as a full-time job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recommendation for most Kubernetes deployments, particularly those spanning more than one physical location or running on hardware where memory is shared with application workloads. It does exactly what it promises with very little ceremony.&lt;/p&gt;




&lt;h2&gt;
  
  
  Criteria comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;MinIO&lt;/th&gt;
&lt;th&gt;Ceph/Rook&lt;/th&gt;
&lt;th&gt;SeaweedFS&lt;/th&gt;
&lt;th&gt;Zenko&lt;/th&gt;
&lt;th&gt;Garage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K8s integration&lt;/td&gt;
&lt;td&gt;Operator exists&lt;/td&gt;
&lt;td&gt;Rook operator&lt;/td&gt;
&lt;td&gt;Helm, patchy&lt;/td&gt;
&lt;td&gt;Helm, complex&lt;/td&gt;
&lt;td&gt;Clean Helm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-site&lt;/td&gt;
&lt;td&gt;Added late&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Manual setup&lt;/td&gt;
&lt;td&gt;Gateway model&lt;/td&gt;
&lt;td&gt;Built into layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Min. footprint&lt;/td&gt;
&lt;td&gt;~2 GB RAM&lt;/td&gt;
&lt;td&gt;8+ GB RAM&lt;/td&gt;
&lt;td&gt;~512 MB RAM&lt;/td&gt;
&lt;td&gt;Needs Kafka/Mongo&lt;/td&gt;
&lt;td&gt;~128 MB RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;BUSL-1.1 (relicensed 2024)&lt;/td&gt;
&lt;td&gt;LGPL / Apache&lt;/td&gt;
&lt;td&gt;Apache 2&lt;/td&gt;
&lt;td&gt;Apache 2 core, mixed&lt;/td&gt;
&lt;td&gt;AGPL, consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops model&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High — deep Ceph knowledge&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Moderate plus deps&lt;/td&gt;
&lt;td&gt;Low — clear failure modes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;For teams with an existing Ceph investment or a hard throughput requirement and dedicated storage nodes, MinIO and Rook remain legitimate choices. For everyone else, Garage earns its recommendation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Kubernetes felt overwhelming until I understood the layers</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Thu, 07 May 2026 20:40:00 +0000</pubDate>
      <link>https://dev.to/josefpolar/kubernetes-felt-overwhelming-until-i-understood-the-layers-cno</link>
      <guid>https://dev.to/josefpolar/kubernetes-felt-overwhelming-until-i-understood-the-layers-cno</guid>
      <description>&lt;p&gt;When I first looked at Kubernetes, I saw a wall of YAML and acronyms. PVC, PV, StatefulSet, Endpoint — none of it connected. What helped was stepping back and mapping out what each piece actually does before trying to use any of it.&lt;/p&gt;

&lt;p&gt;Here's how I think about it now.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pod is the smallest unit
&lt;/h2&gt;

&lt;p&gt;A Pod is the smallest thing Kubernetes runs. It's one or more containers running together on the same node, sharing the same network and storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice I rarely create Pods directly. They're disposable. If a Pod dies, nothing brings it back on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deployment keeps pods running
&lt;/h2&gt;

&lt;p&gt;A Deployment manages Pods for me. I tell it what to run and how many replicas I want, and it keeps that many alive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a Pod crashes, the Deployment notices and spins up a replacement. It also handles rolling updates, swapping old pods for new ones without downtime.&lt;/p&gt;

&lt;p&gt;Deployments are for stateless apps. If the app doesn't need to remember anything between restarts, a Deployment is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  A StatefulSet is for apps with memory
&lt;/h2&gt;

&lt;p&gt;StatefulSets are for apps that need stable identity: databases, queues, anything that writes to disk and needs to know who it is across restarts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-db&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-db&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-db&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-db&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-db&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike a Deployment, each pod in a StatefulSet gets a stable name: &lt;code&gt;my-db-0&lt;/code&gt;, &lt;code&gt;my-db-1&lt;/code&gt;, &lt;code&gt;my-db-2&lt;/code&gt;. They start in order, and each one gets its own persistent storage that follows it around.&lt;/p&gt;

&lt;h2&gt;
  
  
  A service gives pods a stable address
&lt;/h2&gt;

&lt;p&gt;Pods come and go. Their IP addresses change every time they restart. A Service gives me a stable endpoint that always points at the right pods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;selector&lt;/code&gt; is what connects it to the pods. It matches the labels on my Deployment, and that's how the Service knows where to send traffic.&lt;/p&gt;

&lt;p&gt;There are a few Service types. &lt;code&gt;ClusterIP&lt;/code&gt; is only reachable inside the cluster and is the default. &lt;code&gt;NodePort&lt;/code&gt; exposes a port on every node. &lt;code&gt;LoadBalancer&lt;/code&gt; provisions an external load balancer, usually through a cloud provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints are what actually back a service
&lt;/h2&gt;

&lt;p&gt;This one confused me for a while. An Endpoint is the list of pod IPs that a Service routes to. Kubernetes manages them automatically based on which pods match the selector and are healthy.&lt;/p&gt;

&lt;p&gt;I don't create Endpoints manually, but I do inspect them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get endpoints my-app-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a Service wasn't routing traffic, this was the first thing I checked. It told me immediately whether Kubernetes had found any pods to send traffic to.&lt;/p&gt;

&lt;h2&gt;
  
  
  PersistentVolume is the actual storage
&lt;/h2&gt;

&lt;p&gt;A PersistentVolume is storage that exists independently of any pod. It could be a disk on the node, an NFS share, or a cloud disk. Kubernetes abstracts all of that behind a common interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolume&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-pv&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;capacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10Gi&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/my-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  PersistentVolumeClaim is how a pod requests it
&lt;/h2&gt;

&lt;p&gt;A PVC is how a pod asks for storage. Instead of pointing directly at a PV, it says "I need 5Gi with ReadWriteOnce access" and Kubernetes finds a PV that fits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-pvc&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I reference the PVC in my pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-storage&lt;/span&gt;
    &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-pvc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The split between PV and PVC is intentional. Whoever runs the cluster provisions the storage. Whoever deploys the app requests it. Neither needs to know the details of what the other did.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it stacks up
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ingress
  └── Service  (stable network endpoint)
        └── Deployment / StatefulSet  (manages pod lifecycle)
              └── Pod  (runs the container)
                    └── PVC → PV  (persistent storage, if needed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Endpoints sit between the Service and the Pods, managed automatically by Kubernetes. Once this hierarchy clicked, everything else started to feel like detail work rather than a new concept to learn.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>containers</category>
    </item>
    <item>
      <title>Adding Ingress with HAProxy</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Wed, 29 Apr 2026 21:29:42 +0000</pubDate>
      <link>https://dev.to/josefpolar/adding-ingress-with-haproxy-1l19</link>
      <guid>https://dev.to/josefpolar/adding-ingress-with-haproxy-1l19</guid>
      <description>&lt;p&gt;After getting the Service running, the next thing I needed was a way to route external HTTP traffic to it. That's where Ingress comes in — and I went with the HAProxy ingress controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the HAProxy ingress controller
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update
helm &lt;span class="nb"&gt;install &lt;/span&gt;haproxy-ingress haproxytech/kubernetes-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; ingress-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked it came up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Writing the Ingress resource
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-ingress&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;haproxy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app.example.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-service&lt;/span&gt;
                &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;host&lt;/code&gt; field is the domain I want to route. The &lt;code&gt;backend.service.name&lt;/code&gt; matches the Service I created earlier — that chain of connections (Deployment → Service → Ingress) is the pattern that keeps repeating in Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; ingress.yaml
kubectl get ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing it locally
&lt;/h2&gt;

&lt;p&gt;If I don't have a real domain yet, I can fake it by adding an entry to &lt;code&gt;/etc/hosts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hitting &lt;code&gt;http://my-app.example.com&lt;/code&gt; routes traffic through HAProxy into the service and down to the pods.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with Kubernetes: Creating a Basic Service</title>
      <dc:creator>Josef Polar</dc:creator>
      <pubDate>Wed, 29 Apr 2026 17:31:11 +0000</pubDate>
      <link>https://dev.to/josefpolar/getting-started-with-kubernetes-creating-a-basic-service-214p</link>
      <guid>https://dev.to/josefpolar/getting-started-with-kubernetes-creating-a-basic-service-214p</guid>
      <description>&lt;p&gt;Run an app in a container, then expose it so traffic could actually reach it. Two resources handle this: a &lt;strong&gt;Deployment&lt;/strong&gt; and a &lt;strong&gt;Service&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Writing the Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo-server:latest&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Kubernetes to run 2 copies of echo-server and label them &lt;code&gt;app: my-app&lt;/code&gt;. The label part matters — I'll need it in a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Writing the Service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;selector&lt;/code&gt; matches the label I set in the Deployment. That's how Kubernetes knows which pods to send traffic to. Once I understood that connection, the rest started clicking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Applying both files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked that everything came up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
kubectl get services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two pods in &lt;code&gt;Running&lt;/code&gt; state, and the service listed with a cluster IP. Good sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Testing it locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward service/my-app-service 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Opened &lt;code&gt;http://localhost:8080&lt;/code&gt; in the browser and got the echo-server return page. It worked.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
