<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Kubernetes Archives | Abdul Wahab Junaid</title>
	<atom:link href="https://awjunaid.com/category/kubernetes/feed/" rel="self" type="application/rss+xml" />
	<link>https://awjunaid.com/category/kubernetes/</link>
	<description>Offensive Security Researcher &#38; Quantum Cryptography Analyst</description>
	<lastBuildDate>Thu, 06 Aug 2026 05:08:32 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://i0.wp.com/awjunaid.com/wp-content/uploads/2023/06/cropped-1668274976669.jpeg?fit=32%2C32&#038;ssl=1</url>
	<title>Kubernetes Archives | Abdul Wahab Junaid</title>
	<link>https://awjunaid.com/category/kubernetes/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">220030102</site>	<item>
		<title>Kubernetes Security Best Practices: A Practical Guide for 2026</title>
		<link>https://awjunaid.com/kubernetes/kubernetes-security-best-practices-a-practical-guide-for-2026/</link>
					<comments>https://awjunaid.com/kubernetes/kubernetes-security-best-practices-a-practical-guide-for-2026/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Mon, 03 Aug 2026 21:47:21 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=15673</guid>

					<description><![CDATA[<p>I still remember the first cluster I ever broke into during a security review. It wasn&#8217;t through some&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/kubernetes-security-best-practices-a-practical-guide-for-2026/">Kubernetes Security Best Practices: A Practical Guide for 2026</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I still remember the first cluster I ever broke into during a security review. It wasn&#8217;t through some exotic zero-day — it was a Kubernetes Dashboard exposed to the internet with no authentication, sitting quietly behind a load balancer someone forgot about. That&#8217;s the thing about Kubernetes security: most incidents don&#8217;t come from sophisticated attacks, they come from small misconfigurations that pile up over time.</p>



<p class="wp-block-paragraph">If you&#8217;re running workloads on Kubernetes, whether it&#8217;s a single cluster or a fleet of them across clouds, security can&#8217;t be an afterthought. This guide walks through the practical, battle-tested best practices I rely on when hardening clusters — from the control plane down to individual pods.</p>



<h2 class="wp-block-heading">Why Kubernetes Security Deserves Special Attention</h2>



<p class="wp-block-paragraph">Kubernetes is powerful because it abstracts away infrastructure, but that same abstraction is what makes security tricky. You&#8217;re not just securing servers anymore — you&#8217;re securing an API, a scheduler, a networking layer, secret storage, and dozens of moving components that all trust each other by default.</p>



<p class="wp-block-paragraph">A single overly permissive RBAC role, an exposed API server, or a container running as root can be the difference between a contained incident and a full cluster compromise.</p>



<h2 class="wp-block-heading">The Kubernetes Security Model at a Glance</h2>



<p class="wp-block-paragraph">Before diving into specific practices, it helps to picture where the risk actually lives in a cluster.</p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart TD
    A[Internet] -->|Ingress| B[API Server]
    B --> C[Authentication &amp; RBAC]
    C --> D[Scheduler]
    C --> E[Controller Manager]
    D --> F[Nodes / Kubelet]
    F --> G[Pods &amp; Containers]
    G --> H[Secrets &amp; ConfigMaps]
    G --> I[Network Policies]
    style B fill:#f96,stroke:#333
    style G fill:#69f,stroke:#333
</pre></div>



<p class="wp-block-paragraph">Every layer in this diagram is a place where a misconfiguration can quietly become an incident.</p>



<h2 class="wp-block-heading">1. Harden the Kubernetes API Server</h2>



<p class="wp-block-paragraph">The API server is the front door to your entire cluster. If it&#8217;s compromised, everything behind it is at risk.</p>



<ul class="wp-block-list">
<li>Disable anonymous authentication (<code>--anonymous-auth=false</code>).</li>



<li>Use strong authentication methods (OIDC, client certificates) instead of static tokens.</li>



<li>Enable audit logging so you can trace who did what and when.</li>



<li>Restrict access to the API server using network policies and firewall rules — it should never be reachable from the open internet without strict controls.</li>
</ul>



<p class="wp-block-paragraph">If you want a deeper walkthrough of this specific layer, I&#8217;ve written a dedicated <a href="https://awjunaid.com/kubernetes/">Kubernetes API Server Hardening Guide</a> that goes step by step through admission controllers and audit policies.</p>



<h2 class="wp-block-heading">2. Apply the Principle of Least Privilege with RBAC</h2>



<p class="wp-block-paragraph">Role-Based Access Control is one of the most misused features in Kubernetes — not because it&#8217;s complicated, but because teams default to <code>cluster-admin</code> for convenience.</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: &#91;""]
  resources: &#91;"pods"]
  verbs: &#91;"get", "list", "watch"]
</code></pre>



<p class="wp-block-paragraph">Bind roles narrowly, scope them to namespaces, and audit them regularly. I go into much more depth on this in my <a href="https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/">Kubernetes RBAC Security Guide</a>, including how to use ServiceAccounts safely, which I also cover separately when <a href="https://awjunaid.com/kubernetes/how-to-manage-serviceaccounts-in-kubernetes/">managing ServiceAccounts in Kubernetes</a>.</p>



<h2 class="wp-block-heading">3. Secure Secrets Properly</h2>



<p class="wp-block-paragraph">By default, Kubernetes Secrets are only base64-encoded, not encrypted. That&#8217;s not security — it&#8217;s obfuscation. Enable encryption at rest, use an external secrets manager (Vault, AWS Secrets Manager, or similar) where possible, and never bake secrets into container images.</p>



<p class="wp-block-paragraph">I cover this topic thoroughly in <a href="https://awjunaid.com/kubernetes/how-to-create-a-kubernetes-secret/">How to Secure Kubernetes Secrets</a>, including how Helm-based deployments should handle sensitive values, which is also discussed in my post on <a href="https://awjunaid.com/kubernetes/how-to-use-kubernetes-secrets-with-helm/">using Kubernetes Secrets with Helm</a>.</p>



<h2 class="wp-block-heading">4. Isolate Workloads with Network Policies</h2>



<p class="wp-block-paragraph">By default, every pod in a Kubernetes cluster can talk to every other pod. That&#8217;s a flat network, and it&#8217;s a gift to any attacker who lands a foothold. Network Policies let you define exactly which pods can talk to which.</p>



<pre class="wp-block-code"><code>apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
</code></pre>



<p class="wp-block-paragraph">Start with a default-deny policy, then open only the traffic paths your applications actually need. I break this down further in <a href="https://awjunaid.com/kubernetes/">Kubernetes Network Policies Explained</a>.</p>



<h2 class="wp-block-heading">5. Enforce Pod Security Standards</h2>



<p class="wp-block-paragraph">Pods running as root, with privileged mode enabled, or with host path mounts are common culprits in container breakouts. Kubernetes&#8217; built-in Pod Security Admission (replacing the older PodSecurityPolicy) lets you enforce baseline or restricted profiles cluster-wide.</p>



<ul class="wp-block-list">
<li>Run containers as non-root users.</li>



<li>Set <code>readOnlyRootFilesystem: true</code> where possible.</li>



<li>Drop unnecessary Linux capabilities.</li>



<li>Avoid <code>hostNetwork</code>, <code>hostPID</code>, and <code>hostIPC</code> unless absolutely required.</li>
</ul>



<p class="wp-block-paragraph">I still reference my own notes on <a href="https://awjunaid.com/kubernetes/how-to-set-up-pod-security-policies-in-kubernetes/">setting up Pod Security Policies in Kubernetes</a> whenever I need a refresher on the admission controller flags.</p>



<h2 class="wp-block-heading">6. Scan Images and Watch Runtime Behavior</h2>



<p class="wp-block-paragraph">Security doesn&#8217;t stop at deployment. You need to know what&#8217;s inside your images before they ship, and what&#8217;s happening inside your containers while they run. I&#8217;ve written separately about <a href="https://awjunaid.com/">Container Image Scanning</a> and the difference between that and <a href="https://awjunaid.com/">Runtime Container Security</a> — both are essential, and neither replaces the other.</p>



<h2 class="wp-block-heading">7. Monitor, Audit, and Alert</h2>



<p class="wp-block-paragraph">Visibility is what turns &#8220;we got breached and didn&#8217;t know for three months&#8221; into &#8220;we caught it in ten minutes.&#8221; Tools like Prometheus for metrics and alerting are worth setting up early — I walk through this in <a href="https://awjunaid.com/kubernetes/how-to-set-up-prometheus-alerting-in-kubernetes/">Setting Up Prometheus Alerting in Kubernetes</a>.</p>



<h2 class="wp-block-heading">Common Kubernetes Security Mistakes to Avoid</h2>



<ul class="wp-block-list">
<li><strong>Using the default namespace for production workloads.</strong> Namespaces are cheap; use them to isolate blast radius.</li>



<li><strong>Leaving the Kubernetes Dashboard exposed</strong> without authentication.</li>



<li><strong>Skipping resource limits</strong>, which can lead to noisy-neighbor denial-of-service conditions.</li>



<li><strong>Not rotating certificates and tokens</strong> on a schedule.</li>



<li><strong>Trusting default service account tokens</strong> mounted automatically into every pod — disable auto-mounting where it isn&#8217;t needed.</li>
</ul>



<h2 class="wp-block-heading">Best Practices Checklist</h2>



<ol class="wp-block-list">
<li>Enable RBAC and review bindings quarterly.</li>



<li>Rotate and encrypt secrets; avoid plaintext in manifests.</li>



<li>Apply default-deny network policies per namespace.</li>



<li>Enforce restricted Pod Security Standards.</li>



<li>Scan images in CI/CD before deployment.</li>



<li>Enable audit logging and forward logs to a SIEM.</li>



<li>Keep Kubernetes and node OS versions patched.</li>



<li>Use admission controllers (OPA/Gatekeeper, Kyverno) to enforce policy as code.</li>
</ol>



<h2 class="wp-block-heading">FAQs</h2>



<p class="wp-block-paragraph"><strong>Is Kubernetes secure by default?</strong> No. Kubernetes ships with sensible defaults for functionality, not maximum security. You have to actively configure RBAC, network policies, and pod security settings.</p>



<p class="wp-block-paragraph"><strong>What&#8217;s the single highest-impact change I can make today?</strong> Locking down RBAC and removing unnecessary <code>cluster-admin</code> bindings usually has the biggest immediate impact, followed closely by applying default-deny network policies.</p>



<p class="wp-block-paragraph"><strong>Do I need a service mesh for security?</strong> Not strictly, but a service mesh like Istio or Linkerd adds mutual TLS between services and finer-grained traffic policy, which is valuable at scale.</p>



<p class="wp-block-paragraph"><strong>How often should I audit my cluster&#8217;s security posture?</strong> At minimum quarterly, though continuous scanning with policy-as-code tools is far more effective than periodic manual reviews.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Kubernetes security isn&#8217;t a single setting you flip — it&#8217;s a layered discipline that spans the API server, RBAC, networking, secrets, workloads, and monitoring. Start with the fundamentals: least-privilege RBAC, default-deny networking, and non-root containers. From there, build out image scanning, runtime detection, and continuous auditing. None of this is glamorous work, but it&#8217;s the difference between a resilient cluster and a headline.</p>
<p>The post <a href="https://awjunaid.com/kubernetes/kubernetes-security-best-practices-a-practical-guide-for-2026/">Kubernetes Security Best Practices: A Practical Guide for 2026</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/kubernetes-security-best-practices-a-practical-guide-for-2026/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">15673</post-id>	</item>
		<item>
		<title>How to Set Up Custom Controllers in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-set-up-custom-controllers-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-set-up-custom-controllers-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:22:16 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7151</guid>

					<description><![CDATA[<p>Kubernetes ships with a powerful set of built-in controllers — the Deployment controller, the ReplicaSet controller, the Job&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-custom-controllers-in-kubernetes/">How to Set Up Custom Controllers in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Kubernetes ships with a powerful set of built-in controllers — the Deployment controller, the ReplicaSet controller, the Job controller, and dozens more running quietly inside <code>kube-controller-manager</code>. But sooner or later, if you work with Kubernetes long enough, you&#8217;ll hit a wall: you need logic that Kubernetes itself doesn&#8217;t know how to express. Maybe you want to automatically provision a database whenever someone creates a <code>Database</code> object, or you want to enforce a custom scaling policy based on a metric that isn&#8217;t CPU or memory. That&#8217;s where custom controllers come in.</p>



<p class="wp-block-paragraph">In this guide, I&#8217;ll walk through what a custom controller actually is, how it fits into Kubernetes&#8217; architecture, and how to build and deploy one from scratch — from the basic reconciliation loop all the way to production-grade concerns like leader election, RBAC, and observability.</p>



<h2 class="wp-block-heading">What Is a Controller, Really?</h2>



<p class="wp-block-paragraph">At its core, Kubernetes is a declarative system built around a single idea: <strong>desired state versus actual state</strong>. You tell the API server what you want (via a manifest), and controllers continuously work to make the actual state match that desired state. This is called a <strong>reconciliation loop</strong>, and it&#8217;s the heartbeat of everything Kubernetes does.</p>



<p class="wp-block-paragraph">A controller:</p>



<ol class="wp-block-list">
<li><strong>Watches</strong> the API server for changes to a resource (via <code>watch</code> on the API, backed by etcd).</li>



<li><strong>Compares</strong> the observed state to the desired state.</li>



<li><strong>Acts</strong> to reconcile the difference — creating, updating, or deleting resources as needed.</li>



<li><strong>Repeats</strong>, forever, reacting to new events and periodically re-syncing.</li>
</ol>



<p class="wp-block-paragraph">Built-in controllers do this for native resources like Pods and Deployments. A <strong>custom controller</strong> does the exact same thing, but for resources you define yourself — usually a <strong>Custom Resource Definition (CRD)</strong> — or even for built-in resources, if you want to add your own behavior on top of what Kubernetes already does.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Refresher</h2>



<p class="wp-block-paragraph">Before writing a controller, it helps to understand where it lives in the broader architecture:</p>



<ul class="wp-block-list">
<li><strong>API Server</strong>: The front door. All reads and writes go through it, and it persists objects into etcd.</li>



<li><strong>etcd</strong>: The cluster&#8217;s source of truth, a distributed key-value store.</li>



<li><strong>Scheduler</strong>: Assigns Pods to Nodes.</li>



<li><strong>kubelet</strong>: Runs on every node, actually starts/stops containers.</li>



<li><strong>Controller Manager</strong>: Runs the built-in controllers as control loops.</li>



<li><strong>Custom Controller</strong>: Just another client of the API server. It doesn&#8217;t need special privileges to &#8220;be&#8221; a controller — it authenticates like any other client, watches resources it cares about, and issues API calls to reconcile them.</li>
</ul>



<p class="wp-block-paragraph">This last point is important: a custom controller is not a special kind of process. It&#8217;s an ordinary program (often a Go binary, though not exclusively) that talks to the Kubernetes API using a client library, typically running as a Deployment inside the cluster it manages — or even outside the cluster, pointed at a kubeconfig.</p>



<h2 class="wp-block-heading">When You Need a Custom Controller vs. a CRD Alone</h2>



<p class="wp-block-paragraph">A CRD by itself just defines a new object type — it&#8217;s schema and storage, nothing more. If you create a CRD for <code>Website</code> and apply a <code>Website</code> object, absolutely nothing happens unless something is watching for it. That &#8220;something&#8221; is the controller. Together, a CRD plus a controller form what&#8217;s commonly called an <strong>Operator</strong> — a controller with domain-specific knowledge that automates an operational task a human would otherwise do by hand.</p>



<p class="wp-block-paragraph">Use a custom controller when:</p>



<ul class="wp-block-list">
<li>You&#8217;re building an Operator for a stateful application (databases, message queues, certificate authorities).</li>



<li>You want to enforce custom policies (e.g., automatically injecting sidecars, labeling resources, or denying non-compliant objects).</li>



<li>You need to bridge Kubernetes to an external system (cloud resources, DNS records, ticketing systems).</li>
</ul>



<h2 class="wp-block-heading">Building a Custom Controller: The Concepts</h2>



<p class="wp-block-paragraph">Most production controllers today are built with <strong>client-go</strong>, Kubernetes&#8217; official Go client library, often through higher-level tooling like <strong>controller-runtime</strong> (used by the Operator SDK and Kubebuilder). The core building blocks are:</p>



<ul class="wp-block-list">
<li><strong>Informer</strong>: Maintains a local, cache-synced copy of the resources you care about, so you&#8217;re not hammering the API server with reads.</li>



<li><strong>Lister</strong>: A read-only, cache-backed accessor built on top of the informer.</li>



<li><strong>Workqueue</strong>: A rate-limited queue that decouples &#8220;an event happened&#8221; from &#8220;process the event,&#8221; with automatic retries on failure.</li>



<li><strong>Reconciler</strong>: The function containing your actual business logic.</li>
</ul>



<p class="wp-block-paragraph">The typical flow looks like this:</p>



<pre class="wp-block-code"><code>API Server --watch--&gt; Informer --enqueue--&gt; Workqueue --dequeue--&gt; Reconcile()
</code></pre>



<h2 class="wp-block-heading">Step 1: Define a Custom Resource Definition</h2>



<p class="wp-block-paragraph">Let&#8217;s build a small controller that watches a custom <code>Website</code> resource and creates a Deployment and Service for it automatically.</p>



<pre class="wp-block-code"><code>apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: websites.example.com
spec:
  group: example.com
  names:
    kind: Website
    listKind: WebsiteList
    plural: websites
    singular: website
    shortNames: &#91;"ws"]
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                image:
                  type: string
                replicas:
                  type: integer
                  minimum: 1
              required: &#91;"image"]
            status:
              type: object
              properties:
                availableReplicas:
                  type: integer
      subresources:
        status: {}
</code></pre>



<p class="wp-block-paragraph">Apply it:</p>



<pre class="wp-block-code"><code>kubectl apply -f website-crd.yaml
kubectl get crd websites.example.com
</code></pre>



<h2 class="wp-block-heading">Step 2: Scaffold the Controller with Kubebuilder</h2>



<p class="wp-block-paragraph">Kubebuilder gives you a working project skeleton in minutes:</p>



<pre class="wp-block-code"><code>kubebuilder init --domain example.com --repo github.com/yourname/website-operator
kubebuilder create api --group web --version v1 --kind Website
</code></pre>



<p class="wp-block-paragraph">This generates a <code>WebsiteReconciler</code> struct with a <code>Reconcile</code> method — this is where your logic goes.</p>



<pre class="wp-block-code"><code>func (r *WebsiteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var website webv1.Website
    if err := r.Get(ctx, req.NamespacedName, &amp;website); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    deployment := &amp;appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      website.Name,
            Namespace: website.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &amp;website.Spec.Replicas,
            Selector: &amp;metav1.LabelSelector{
                MatchLabels: map&#91;string]string{"app": website.Name},
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map&#91;string]string{"app": website.Name},
                },
                Spec: corev1.PodSpec{
                    Containers: &#91;]corev1.Container{{
                        Name:  "web",
                        Image: website.Spec.Image,
                    }},
                },
            },
        },
    }

    if err := ctrl.SetControllerReference(&amp;website, deployment, r.Scheme); err != nil {
        return ctrl.Result{}, err
    }

    if err := r.Create(ctx, deployment); err != nil &amp;&amp; !apierrors.IsAlreadyExists(err) {
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}
</code></pre>



<p class="wp-block-paragraph">Notice the call to <code>SetControllerReference</code> — this sets an owner reference so that if the <code>Website</code> object is deleted, Kubernetes&#8217; built-in garbage collector cleans up the Deployment automatically.</p>



<h2 class="wp-block-heading">Step 3: RBAC for the Controller</h2>



<p class="wp-block-paragraph">A controller running inside the cluster needs a ServiceAccount and permissions scoped to exactly what it touches — nothing more.</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: website-controller-role
rules:
  - apiGroups: &#91;"web.example.com"]
    resources: &#91;"websites", "websites/status"]
    verbs: &#91;"get", "list", "watch", "update", "patch"]
  - apiGroups: &#91;"apps"]
    resources: &#91;"deployments"]
    verbs: &#91;"get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: website-controller-binding
subjects:
  - kind: ServiceAccount
    name: website-controller
    namespace: website-system
roleRef:
  kind: ClusterRole
  name: website-controller-role
  apiGroup: rbac.authorization.k8s.io
</code></pre>



<h2 class="wp-block-heading">Step 4: Deploy the Controller</h2>



<p class="wp-block-paragraph">Build and push the image, then deploy it as a standard Kubernetes Deployment:</p>



<pre class="wp-block-code"><code>make docker-build docker-push IMG=yourrepo/website-controller:v0.1.0
make deploy IMG=yourrepo/website-controller:v0.1.0
kubectl get pods -n website-system
</code></pre>



<p class="wp-block-paragraph">Test it:</p>



<pre class="wp-block-code"><code>kubectl apply -f - &lt;&lt;EOF
apiVersion: web.example.com/v1
kind: Website
metadata:
  name: demo-site
spec:
  image: nginx:1.27
  replicas: 2
EOF

kubectl get deployment demo-site
</code></pre>



<p class="wp-block-paragraph">You should see a Deployment named <code>demo-site</code> with 2 replicas appear automatically — created entirely by your controller.</p>



<h2 class="wp-block-heading">Leader Election for High Availability</h2>



<p class="wp-block-paragraph">Running a single replica of your controller is a single point of failure. Run multiple replicas with <strong>leader election</strong> enabled so only one is actively reconciling at a time, with automatic failover:</p>



<pre class="wp-block-code"><code>mgr, err := ctrl.NewManager(cfg, ctrl.Options{
    LeaderElection:   true,
    LeaderElectionID: "website-controller-leader",
})
</code></pre>



<p class="wp-block-paragraph">The non-leader replicas sit idle, watching a Lease object, ready to take over instantly if the leader&#8217;s pod dies.</p>



<h2 class="wp-block-heading">Observability and Troubleshooting</h2>



<ul class="wp-block-list">
<li><strong>Structured logging</strong>: Log reconcile events with the resource&#8217;s namespace/name for traceability.</li>



<li><strong>Metrics</strong>: controller-runtime exposes Prometheus metrics by default (<code>workqueue_depth</code>, <code>reconcile_errors_total</code>) on <code>:8080/metrics</code>.</li>



<li><strong>Events</strong>: Emit Kubernetes Events (<code>kubectl describe website demo-site</code>) so operators see what your controller is doing without digging through logs.</li>



<li><strong>Common mistakes</strong>: forgetting to handle <code>NotFound</code> errors after deletion, not setting owner references (leading to orphaned resources), and reconciling too aggressively without rate limiting, which can overwhelm the API server.</li>
</ul>



<h2 class="wp-block-heading">Production Best Practices</h2>



<ul class="wp-block-list">
<li>Keep reconcile functions <strong>idempotent</strong> — they may be called repeatedly for the same state.</li>



<li>Use <strong>finalizers</strong> if your controller needs to clean up external resources (cloud load balancers, DNS records) before an object is deleted.</li>



<li>Version your CRD schema carefully; use conversion webhooks if you need to evolve the API.</li>



<li>Set resource requests/limits on the controller Pod itself — a runaway controller can destabilize a cluster.</li>



<li>For disaster recovery, remember that CRs are stored in etcd like everything else — back up etcd regularly if your CRDs hold important state.</li>
</ul>



<h2 class="wp-block-heading">CI/CD Integration</h2>



<p class="wp-block-paragraph">In a real DevOps pipeline, the controller image is built and tested in CI (unit tests with <code>envtest</code>, which spins up a real API server without a full cluster), then the manifests are deployed via GitOps tools like Argo CD or Flux, so any change to the CRD or controller Deployment is applied automatically and auditable through Git history.</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Custom controllers are how Kubernetes becomes a true automation platform rather than just a container scheduler. By combining a CRD with a reconciliation loop, you can teach Kubernetes to manage anything — databases, certificates, cloud infrastructure — using the same declarative model it already uses for Pods and Services. Start small, keep your reconcile logic idempotent, lock down RBAC tightly, and add leader election and observability before you trust a controller with production traffic.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">Kubernetes Documentation: Custom Resources</a></li>



<li><a href="https://book.kubebuilder.io/">Kubebuilder Book</a></li>



<li><a href="https://github.com/kubernetes/client-go">client-go on GitHub</a></li>



<li><a href="https://www.cncf.io/">CNCF Operator White Paper</a></li>



<li><a href="https://github.com/kubernetes-sigs/controller-runtime">Kubernetes controller-runtime</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-custom-controllers-in-kubernetes/">How to Set Up Custom Controllers in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-set-up-custom-controllers-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7151</post-id>	</item>
		<item>
		<title>How to Use Role-Based Access Control (RBAC) in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:20:24 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7148</guid>

					<description><![CDATA[<p>If you&#8217;ve ever managed a Kubernetes cluster with more than one person on the team, you&#8217;ve probably run&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/">How to Use Role-Based Access Control (RBAC) in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If you&#8217;ve ever managed a Kubernetes cluster with more than one person on the team, you&#8217;ve probably run into the question: &#8220;Why does this developer have permission to delete production Pods?&#8221; That question is exactly what Role-Based Access Control (RBAC) exists to answer — and to prevent. In this guide, I&#8217;ll break down how RBAC works in Kubernetes from the ground up, and walk through practical examples you can apply immediately.</p>



<h2 class="wp-block-heading">Why RBAC Matters</h2>



<p class="wp-block-paragraph">Kubernetes clusters often host multiple teams, multiple environments, and multiple layers of automation (CI/CD pipelines, controllers, monitoring agents) all talking to the same API server. Without access control, any authenticated user or service account could do anything — read secrets, delete deployments, modify RBAC itself. RBAC solves this by letting you define <strong>who can do what, on which resources, in which namespaces</strong>.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Context</h2>



<p class="wp-block-paragraph">Every request to Kubernetes — whether from <code>kubectl</code>, a controller, or a CI pipeline — goes through three stages at the API server:</p>



<ol class="wp-block-list">
<li><strong>Authentication</strong>: Who are you? (certificates, tokens, OIDC, service accounts)</li>



<li><strong>Authorization</strong>: Are you allowed to do this? (this is where RBAC lives)</li>



<li><strong>Admission Control</strong>: Should this specific request be allowed/modified? (webhooks, policies)</li>
</ol>



<p class="wp-block-paragraph">RBAC is the authorization layer. It doesn&#8217;t care who you are beyond your identity (user or service account) and group memberships — it just checks whether a Role permits the verb and resource you&#8217;re requesting.</p>



<h2 class="wp-block-heading">The Four RBAC Objects</h2>



<p class="wp-block-paragraph">RBAC in Kubernetes is built from four API objects:</p>



<ul class="wp-block-list">
<li><strong>Role</strong>: A set of permissions (verbs on resources) scoped to a single namespace.</li>



<li><strong>ClusterRole</strong>: The same thing, but scoped cluster-wide (or reusable across namespaces).</li>



<li><strong>RoleBinding</strong>: Grants a Role to a user, group, or service account within a namespace.</li>



<li><strong>ClusterRoleBinding</strong>: Grants a ClusterRole cluster-wide.</li>
</ul>



<p class="wp-block-paragraph">The key mental model: <strong>Roles/ClusterRoles define permissions; Bindings assign those permissions to someone.</strong> A Role by itself does nothing until it&#8217;s bound.</p>



<h2 class="wp-block-heading">Step 1: Create a Namespace-Scoped Role</h2>



<p class="wp-block-paragraph">Let&#8217;s say a developer needs to view and manage Pods in the <code>staging</code> namespace, but nothing else.</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: staging
  name: pod-manager
rules:
  - apiGroups: &#91;""]
    resources: &#91;"pods", "pods/log"]
    verbs: &#91;"get", "list", "watch", "create", "update", "patch", "delete"]
</code></pre>



<p class="wp-block-paragraph">Apply it:</p>



<pre class="wp-block-code"><code>kubectl apply -f pod-manager-role.yaml
</code></pre>



<h2 class="wp-block-heading">Step 2: Bind the Role to a User</h2>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-manager-binding
  namespace: staging
subjects:
  - kind: User
    name: jane.doe
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-manager
  apiGroup: rbac.authorization.k8s.io
</code></pre>



<pre class="wp-block-code"><code>kubectl apply -f pod-manager-binding.yaml
</code></pre>



<p class="wp-block-paragraph">Now Jane can manage Pods only inside <code>staging</code> — nothing else, and nowhere else.</p>



<h2 class="wp-block-heading">Step 3: Cluster-Wide Permissions with ClusterRole</h2>



<p class="wp-block-paragraph">Suppose you have a monitoring service account that needs to read node metrics across the whole cluster.</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
  - apiGroups: &#91;""]
    resources: &#91;"nodes", "nodes/metrics"]
    verbs: &#91;"get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: node-reader-binding
subjects:
  - kind: ServiceAccount
    name: monitoring-agent
    namespace: monitoring
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io
</code></pre>



<h2 class="wp-block-heading">Understanding Verbs and Resources</h2>



<p class="wp-block-paragraph">Common verbs: <code>get</code>, <code>list</code>, <code>watch</code>, <code>create</code>, <code>update</code>, <code>patch</code>, <code>delete</code>, <code>deletecollection</code>. Resources can be scoped further using <code>resourceNames</code> to restrict access to specific named objects:</p>



<pre class="wp-block-code"><code>rules:
  - apiGroups: &#91;""]
    resources: &#91;"configmaps"]
    resourceNames: &#91;"app-config"]
    verbs: &#91;"get", "update"]
</code></pre>



<p class="wp-block-paragraph">This grants access only to the <code>app-config</code> ConfigMap — nothing else in that resource type.</p>



<h2 class="wp-block-heading">Aggregated ClusterRoles</h2>



<p class="wp-block-paragraph">For larger clusters, Kubernetes supports <strong>aggregated ClusterRoles</strong>, which combine multiple ClusterRoles using label selectors. This is how the built-in <code>admin</code>, <code>edit</code>, and <code>view</code> roles work internally, and it&#8217;s a clean pattern for building modular permission sets:</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-aggregate
  labels:
    rbac.example.com/aggregate-to-monitoring: "true"
rules: &#91;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-endpoints
  labels:
    rbac.example.com/aggregate-to-monitoring: "true"
rules:
  - apiGroups: &#91;""]
    resources: &#91;"endpoints", "services", "pods"]
    verbs: &#91;"get", "list", "watch"]
</code></pre>



<h2 class="wp-block-heading">Checking Permissions</h2>



<p class="wp-block-paragraph">Before you go debugging why something isn&#8217;t working, use <code>kubectl auth can-i</code>:</p>



<pre class="wp-block-code"><code>kubectl auth can-i delete pods --namespace staging --as jane.doe
# yes

kubectl auth can-i create deployments --namespace production --as jane.doe
# no
</code></pre>



<p class="wp-block-paragraph">This is invaluable for both testing and troubleshooting RBAC issues without waiting for a user to hit an error.</p>



<h2 class="wp-block-heading">RBAC for Service Accounts (CI/CD and Automation)</h2>



<p class="wp-block-paragraph">Every Pod runs with a ServiceAccount, and by default it&#8217;s the <code>default</code> service account in its namespace, which typically has minimal permissions. For a CI/CD pipeline deploying to a namespace:</p>



<pre class="wp-block-code"><code>apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer
  namespace: production
rules:
  - apiGroups: &#91;"apps"]
    resources: &#91;"deployments"]
    verbs: &#91;"get", "list", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: production
roleRef:
  kind: Role
  name: deployer
  apiGroup: rbac.authorization.k8s.io
</code></pre>



<p class="wp-block-paragraph">Your pipeline (GitHub Actions, GitLab CI, Argo CD) then authenticates using this ServiceAccount&#8217;s token, scoped to exactly the deployments it needs to touch.</p>



<h2 class="wp-block-heading">Security Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Principle of least privilege</strong>: Never grant <code>cluster-admin</code> unless absolutely necessary. Audit who has it with <code>kubectl get clusterrolebindings -o json | jq</code>.</li>



<li><strong>Avoid wildcard rules</strong> (<code>resources: ["*"]</code>, <code>verbs: ["*"]</code>) in production; they&#8217;re a common source of privilege escalation.</li>



<li><strong>Use groups, not individual users</strong>, for bindings when integrating with an identity provider (OIDC) — it scales much better as teams grow.</li>



<li><strong>Separate namespaces per environment/team</strong> and scope Roles accordingly, rather than relying on a handful of broad ClusterRoles.</li>



<li><strong>Rotate and audit service account tokens</strong>, especially long-lived ones; prefer projected, time-bound tokens (<code>BoundServiceAccountToken</code>) which are the default in modern Kubernetes.</li>



<li><strong>Watch for privilege escalation via RBAC itself</strong> — a user with <code>create</code> on <code>rolebindings</code> and <code>bind</code> verb capability could grant themselves broader access; Kubernetes has built-in escalation checks, but review carefully.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Binding a ClusterRole via a RoleBinding to try to limit it to a namespace — this actually works and is a valid pattern, but it&#8217;s easy to confuse with a ClusterRoleBinding, which grants access everywhere.</li>



<li>Forgetting <code>pods/log</code> and <code>pods/exec</code> are separate subresources from <code>pods</code> — granting <code>get</code> on <code>pods</code> doesn&#8217;t let someone <code>kubectl exec</code> into them.</li>



<li>Applying RBAC changes without testing with <code>kubectl auth can-i --as</code>, leading to either broken pipelines or unnoticed over-permissioning.</li>
</ul>



<h2 class="wp-block-heading">Troubleshooting RBAC Issues</h2>



<p class="wp-block-paragraph">When a request is denied, the API server returns a clear <code>Forbidden</code> error naming the exact verb, resource, and namespace it evaluated:</p>



<pre class="wp-block-code"><code>Error from server (Forbidden): pods is forbidden: User "jane.doe" cannot list resource "pods" in API group "" in the namespace "production"
</code></pre>



<p class="wp-block-paragraph">Read this message literally — it tells you exactly which Role or ClusterRoleBinding you&#8217;re missing.</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">RBAC is the backbone of multi-tenant, secure Kubernetes operations. By combining Roles and ClusterRoles (what&#8217;s allowed) with RoleBindings and ClusterRoleBindings (who gets it), you can build precise, auditable access control for humans, CI pipelines, and controllers alike. Start from least privilege, use <code>kubectl auth can-i</code> liberally, and treat every wildcard rule as a red flag worth double-checking.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">Kubernetes Documentation: Using RBAC Authorization</a></li>



<li><a href="https://kubernetes.io/docs/concepts/security/service-accounts/">Kubernetes Documentation: Service Accounts</a></li>



<li><a href="https://www.cncf.io/">CNCF Kubernetes Security Best Practices</a></li>



<li><a href="https://kubernetes.io/docs/reference/kubectl/">kubectl auth Reference</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/">How to Use Role-Based Access Control (RBAC) in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-use-role-based-access-control-rbac-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7148</post-id>	</item>
		<item>
		<title>How to Set Up Pod Disruption Budgets with Loki in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-loki-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-loki-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:18:49 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7145</guid>

					<description><![CDATA[<p>Loki, Grafana Labs&#8217; log aggregation system, is often deployed as a multi-component StatefulSet-based system inside Kubernetes — with&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-loki-in-kubernetes/">How to Set Up Pod Disruption Budgets with Loki in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Loki, Grafana Labs&#8217; log aggregation system, is often deployed as a multi-component StatefulSet-based system inside Kubernetes — with ingesters, distributors, queriers, and compactors all needing to stay available even while the cluster underneath them is being upgraded, drained, or autoscaled. If a node drain or cluster upgrade takes down too many Loki ingesters at once, you risk losing log data or breaking queries entirely. That&#8217;s exactly the problem <strong>Pod Disruption Budgets (PDBs)</strong> are built to solve. In this guide, I&#8217;ll explain what PDBs are and walk through configuring them specifically for a Loki deployment.</p>



<h2 class="wp-block-heading">Voluntary vs. Involuntary Disruptions</h2>



<p class="wp-block-paragraph">Kubernetes distinguishes between two kinds of Pod disruption:</p>



<ul class="wp-block-list">
<li><strong>Involuntary disruptions</strong>: Hardware failure, kernel panic, node running out of resources — things you can&#8217;t prevent, only recover from (via replication).</li>



<li><strong>Voluntary disruptions</strong>: Things initiated by an administrator or automation — draining a node for maintenance, <code>kubectl delete pod</code>, cluster autoscaler scale-down, or a rolling node upgrade.</li>
</ul>



<p class="wp-block-paragraph">A <strong>PodDisruptionBudget</strong> only protects against voluntary disruptions. It tells Kubernetes: &#8220;No matter what maintenance operation you&#8217;re trying to perform, never take down more than X Pods (or leave fewer than Y available) from this set at once.&#8221;</p>



<h2 class="wp-block-heading">Why This Matters for Loki Specifically</h2>



<p class="wp-block-paragraph">Loki&#8217;s architecture (especially in the microservices deployment mode) includes several stateful, quorum-sensitive components:</p>



<ul class="wp-block-list">
<li><strong>Ingesters</strong>: Buffer and flush log data to long-term storage. Losing too many at once can cause data loss for logs not yet flushed.</li>



<li><strong>Distributors</strong>: Stateless, but if too many go down simultaneously, ingestion throughput craters.</li>



<li><strong>Queriers / Query Frontends</strong>: Handle read traffic; losing too many degrades query latency for everyone using Grafana dashboards.</li>



<li><strong>Compactor</strong>: Usually a single replica; losing it isn&#8217;t catastrophic short-term, but you don&#8217;t want it flapping constantly during node churn.</li>
</ul>



<p class="wp-block-paragraph">Because ingesters use a hash-ring based replication factor (commonly 3), losing more replicas than your replication factor tolerates during a rolling node upgrade can cause write failures or gaps in log ingestion. A PDB prevents the cluster from ever getting into that state during voluntary operations.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Refresher: Where PDBs Fit</h2>



<p class="wp-block-paragraph">PDBs are enforced by the <strong>Eviction API</strong>, which is what <code>kubectl drain</code> and the cluster autoscaler use instead of a raw delete. When something calls the Eviction API against a Pod:</p>



<ol class="wp-block-list">
<li>The API server checks if any PDB covers that Pod (via label selector).</li>



<li>If evicting the Pod would violate the PDB&#8217;s <code>minAvailable</code> or <code>maxUnavailable</code>, the eviction is rejected with a 429 Too Many Requests.</li>



<li>The caller (drain, autoscaler) retries later, once the Pod count is back in a safe state.</li>
</ol>



<p class="wp-block-paragraph">Note: a PDB does <strong>not</strong> stop <code>kubectl delete pod</code> directly — it only governs the Eviction API. This distinction trips people up often.</p>



<h2 class="wp-block-heading">Step 1: Deploy Loki with Helm</h2>



<p class="wp-block-paragraph">Assuming you&#8217;re using the official <code>loki</code> Helm chart in microservices mode:</p>



<pre class="wp-block-code"><code>helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki -n logging --create-namespace \
  --set loki.commonConfig.replication_factor=3 \
  --set deploymentMode=Distributed
</code></pre>



<p class="wp-block-paragraph">Confirm the components are running:</p>



<pre class="wp-block-code"><code>kubectl get pods -n logging
</code></pre>



<p class="wp-block-paragraph">You should see distributor, ingester, querier, query-frontend, and compactor Pods.</p>



<h2 class="wp-block-heading">Step 2: Create a PDB for Loki Ingesters</h2>



<p class="wp-block-paragraph">Ingesters are the most disruption-sensitive component. With a replication factor of 3, you want to guarantee at least 2 are always available:</p>



<pre class="wp-block-code"><code>apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: loki-ingester-pdb
  namespace: logging
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: loki
      app.kubernetes.io/component: ingester
</code></pre>



<p class="wp-block-paragraph">Apply it:</p>



<pre class="wp-block-code"><code>kubectl apply -f loki-ingester-pdb.yaml
kubectl get pdb -n logging
</code></pre>



<p class="wp-block-paragraph">Expected output:</p>



<pre class="wp-block-code"><code>NAME               MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
loki-ingester-pdb  2               N/A                1                     10s
</code></pre>



<p class="wp-block-paragraph">The <code>ALLOWED DISRUPTIONS</code> column tells you exactly how many Pods can be evicted right now without violating the budget — this is the number you should watch during a maintenance window.</p>



<h2 class="wp-block-heading">Step 3: Create PDBs for Other Components</h2>



<p class="wp-block-paragraph">For distributors and queriers (stateless, but you still want availability during rolling maintenance), <code>maxUnavailable</code> is often more practical than <code>minAvailable</code> since it scales naturally with replica count:</p>



<pre class="wp-block-code"><code>apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: loki-distributor-pdb
  namespace: logging
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: loki
      app.kubernetes.io/component: distributor
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: loki-querier-pdb
  namespace: logging
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: loki
      app.kubernetes.io/component: querier
</code></pre>



<h2 class="wp-block-heading">Step 4: Configuring PDBs via the Helm Chart Values</h2>



<p class="wp-block-paragraph">Rather than managing PDBs as separate manifests, the Loki Helm chart supports PDB configuration natively — which is the cleaner, GitOps-friendly approach:</p>



<pre class="wp-block-code"><code># values.yaml
ingester:
  replicas: 3
  podDisruptionBudget:
    maxUnavailable: 1

querier:
  replicas: 3
  podDisruptionBudget:
    maxUnavailable: 1

distributor:
  replicas: 3
  podDisruptionBudget:
    maxUnavailable: 1
</code></pre>



<pre class="wp-block-code"><code>helm upgrade loki grafana/loki -n logging -f values.yaml
</code></pre>



<h2 class="wp-block-heading">Step 5: Test It — Simulate a Node Drain</h2>



<pre class="wp-block-code"><code>kubectl drain &lt;node-name&gt; --ignore-daemonsets --delete-emptydir-data
</code></pre>



<p class="wp-block-paragraph">If the drain would violate a PDB, you&#8217;ll see output like:</p>



<pre class="wp-block-code"><code>error when evicting pods/"loki-ingester-1" -n "logging": Cannot evict pod as it would violate the pod's disruption budget.
</code></pre>



<p class="wp-block-paragraph">Kubernetes will keep retrying automatically as Pods on other nodes become healthy again, until it&#8217;s safe to proceed.</p>



<h2 class="wp-block-heading">Monitoring Allowed Disruptions</h2>



<p class="wp-block-paragraph">For ongoing visibility, especially before a planned maintenance window, script a quick check:</p>



<pre class="wp-block-code"><code>kubectl get pdb -n logging -o custom-columns=\
NAME:.metadata.name,MIN_AVAIL:.spec.minAvailable,MAX_UNAVAIL:.spec.maxUnavailable,ALLOWED:.status.disruptionsAllowed
</code></pre>



<p class="wp-block-paragraph">If <code>ALLOWED</code> is <code>0</code> for any Loki component, that&#8217;s a signal something is already degraded — investigate before starting maintenance, not after.</p>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Setting <code>minAvailable</code> equal to total replica count — this makes the PDB impossible to satisfy during any voluntary disruption, effectively blocking node drains forever.</li>



<li>Using label selectors that are too broad, accidentally covering Pods from other apps and creating confusing eviction blocks.</li>



<li>Forgetting that PDBs don&#8217;t protect against involuntary disruptions (a node crashing outright bypasses the Eviction API entirely).</li>



<li>Not aligning <code>minAvailable</code>/<code>maxUnavailable</code> with the actual replication factor configured in Loki&#8217;s <code>commonConfig.replication_factor</code> — the PDB should reflect the same fault tolerance the application itself expects.</li>
</ul>



<h2 class="wp-block-heading">High Availability and Disaster Recovery Considerations</h2>



<ul class="wp-block-list">
<li>Spread Loki ingester replicas across multiple availability zones using <strong>pod anti-affinity</strong> or <strong>topology spread constraints</strong>, so a single zone failure doesn&#8217;t take out your entire replication factor at once — PDBs and anti-affinity work together, not as substitutes for each other.</li>



<li>Back up Loki&#8217;s storage backend (S3, GCS, or equivalent) independently; PDBs protect availability during maintenance, not data durability.</li>



<li>Combine PDBs with <strong>readiness probes</strong> — a Pod that&#8217;s technically &#8220;Running&#8221; but failing readiness checks doesn&#8217;t count as available, which affects how the PDB math is evaluated.</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Pod Disruption Budgets are a small but critical piece of running Loki reliably on Kubernetes. By setting sensible <code>minAvailable</code>/<code>maxUnavailable</code> values per component — aligned with Loki&#8217;s own replication factor — you ensure that routine cluster maintenance never accidentally causes log ingestion failures or query outages. Configure them through the Helm chart for consistency, verify them with <code>kubectl get pdb</code>, and always test a drain in a non-production cluster before you rely on this in an actual maintenance window.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/">Kubernetes Documentation: Pod Disruption Budgets</a></li>



<li><a href="https://grafana.com/docs/loki/latest/">Grafana Loki Documentation</a></li>



<li><a href="https://github.com/grafana/loki/tree/main/production/helm/loki">Grafana Loki Helm Chart</a></li>



<li><a href="https://www.cncf.io/">CNCF Loki Project Page</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-loki-in-kubernetes/">How to Set Up Pod Disruption Budgets with Loki in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-loki-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7145</post-id>	</item>
		<item>
		<title>How to Implement Pod Priority and Preemption with Helm in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-implement-pod-priority-and-preemption-with-helm-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-implement-pod-priority-and-preemption-with-helm-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:16:49 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7142</guid>

					<description><![CDATA[<p>When a cluster is under resource pressure, not all workloads are equal. A batch job that can retry&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-implement-pod-priority-and-preemption-with-helm-in-kubernetes/">How to Implement Pod Priority and Preemption with Helm in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">When a cluster is under resource pressure, not all workloads are equal. A batch job that can retry later matters a lot less than the payment-processing service that needs to stay up. Kubernetes handles this trade-off through <strong>Pod Priority and Preemption</strong> — and when you&#8217;re deploying real applications with Helm, you want this configured consistently as part of your chart, not bolted on manually after the fact. In this guide, I&#8217;ll cover how priority and preemption work, and how to wire them into a Helm-based deployment.</p>



<h2 class="wp-block-heading">What Priority and Preemption Actually Do</h2>



<p class="wp-block-paragraph">Every Pod can carry a <strong>priority</strong> — an integer value derived from a <code>PriorityClass</code> object. When the scheduler can&#8217;t find a node with enough resources for a pending high-priority Pod, it may <strong>preempt</strong> (evict) lower-priority Pods on some node to make room, rather than leaving the high-priority Pod stuck in <code>Pending</code> indefinitely.</p>



<p class="wp-block-paragraph">This is separate from and complementary to Pod Disruption Budgets — the scheduler respects PDBs where possible during preemption, but a PDB does not fully protect a Pod from being preempted if there&#8217;s no other way to schedule a higher-priority Pod.</p>



<h2 class="wp-block-heading">Kubernetes Architecture: Where This Happens</h2>



<p class="wp-block-paragraph">Priority and preemption live entirely in the <strong>scheduler</strong>:</p>



<ol class="wp-block-list">
<li>A Pod is submitted with a <code>priorityClassName</code>.</li>



<li>The <strong>admission controller</strong> resolves that name to a numeric <code>priority</code> value, stamped onto the Pod spec.</li>



<li>The <strong>scheduler</strong> tries to place it normally first.</li>



<li>If no node fits, and preemption is enabled (default), the scheduler looks for a node where evicting some lower-priority Pods would make room, chooses the node with the least &#8220;collateral damage,&#8221; and evicts just enough Pods.</li>



<li>The preempted Pods go back to <code>Pending</code> and get rescheduled elsewhere if capacity exists.</li>
</ol>



<h2 class="wp-block-heading">Step 1: Define PriorityClasses</h2>



<p class="wp-block-paragraph">PriorityClasses are cluster-scoped, so they&#8217;re usually created once, separately from your application charts (or as a shared &#8220;platform&#8221; chart installed first).</p>



<pre class="wp-block-code"><code>apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical-priority
value: 1000000
globalDefault: false
description: "Reserved for critical production services."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: standard-priority
value: 100000
globalDefault: true
description: "Default priority for most application workloads."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: batch-priority
value: 1000
globalDefault: false
preemptionPolicy: Never
description: "Low priority for batch and background jobs. Never preempts others."
</code></pre>



<pre class="wp-block-code"><code>kubectl apply -f priorityclasses.yaml
kubectl get priorityclass
</code></pre>



<p class="wp-block-paragraph">Notice <code>preemptionPolicy: Never</code> on the batch class — this means Pods in that class will never trigger preemption of others, even though they still have a defined (low) priority. That&#8217;s a useful pattern for jobs that should wait patiently rather than kick anything else off a node.</p>



<h2 class="wp-block-heading">Step 2: Reference PriorityClass in a Helm Chart</h2>



<p class="wp-block-paragraph">Rather than hardcoding <code>priorityClassName</code> in every Deployment template, expose it as a configurable value so different environments (or different releases of the same chart) can set it appropriately.</p>



<p class="wp-block-paragraph"><code>values.yaml</code>:</p>



<pre class="wp-block-code"><code>priorityClassName: standard-priority

replicaCount: 3

resources:
  requests:
    cpu: 250m
    memory: 256Mi
  limits:
    cpu: 500m
    memory: 512Mi
</code></pre>



<p class="wp-block-paragraph"><code>templates/deployment.yaml</code>:</p>



<pre class="wp-block-code"><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-app
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      {{- if .Values.priorityClassName }}
      priorityClassName: {{ .Values.priorityClassName }}
      {{- end }}
      containers:
        - name: app
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
</code></pre>



<h2 class="wp-block-heading">Step 3: Install with an Override per Environment</h2>



<p class="wp-block-paragraph">For a critical production release:</p>



<pre class="wp-block-code"><code>helm install payments-api ./chart -n production \
  --set priorityClassName=critical-priority
</code></pre>



<p class="wp-block-paragraph">For a batch workload chart:</p>



<pre class="wp-block-code"><code>helm install nightly-report ./chart -n batch \
  --set priorityClassName=batch-priority
</code></pre>



<p class="wp-block-paragraph">This gives you one chart, reused across workloads with wildly different scheduling importance, without duplicating templates.</p>



<h2 class="wp-block-heading">Step 4: Verify Priority Assignment</h2>



<pre class="wp-block-code"><code>kubectl get pod payments-api-xyz -o jsonpath='{.spec.priorityClassName}{"\n"}{.spec.priority}'
</code></pre>



<p class="wp-block-paragraph">Expected output:</p>



<pre class="wp-block-code"><code>critical-priority
1000000
</code></pre>



<h2 class="wp-block-heading">Observing Preemption in Action</h2>



<p class="wp-block-paragraph">When preemption occurs, check events on the newly scheduled Pod:</p>



<pre class="wp-block-code"><code>kubectl describe pod payments-api-xyz
</code></pre>



<p class="wp-block-paragraph">You&#8217;ll see an event like:</p>



<pre class="wp-block-code"><code>Events:
  Type     Reason      Message
  ----     ------      -------
  Normal   Preempted   Preempted by payments-api-xyz on node worker-3
</code></pre>



<p class="wp-block-paragraph">And on the evicted Pod&#8217;s side, it goes back to <code>Pending</code>, with a <code>Preempting</code> condition explaining why. The <code>kube-scheduler</code> logs (if you have access) also record the preemption decision in detail — useful for post-incident review of why a batch job got killed mid-run.</p>



<h2 class="wp-block-heading">Handling Graceful Termination During Preemption</h2>



<p class="wp-block-paragraph">Preempted Pods still respect <code>terminationGracePeriodSeconds</code>, so a preempted Pod isn&#8217;t just SIGKILLed instantly — it gets its normal shutdown hooks. For workloads doing meaningful in-flight work, tune this appropriately in your chart:</p>



<pre class="wp-block-code"><code>spec:
  template:
    spec:
      terminationGracePeriodSeconds: 60
</code></pre>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li><strong>Don&#8217;t overuse high priority.</strong> If every team sets their workloads to the highest PriorityClass &#8220;just in case,&#8221; you lose the entire mechanism&#8217;s value — it becomes a race to the top rather than a meaningful signal.</li>



<li><strong>Reserve the top priority tier</strong> (e.g., <code>system-cluster-critical</code>, which Kubernetes itself uses for core components like <code>kube-dns</code>) for things that are genuinely cluster-critical; don&#8217;t let application teams use system-reserved classes.</li>



<li><strong>Combine with resource requests/limits and PodDisruptionBudgets</strong> — priority determines <em>who</em> gets preempted; PDBs limit <em>how much</em> can be preempted from a given set at once, giving you defense in depth.</li>



<li><strong>Set <code>preemptionPolicy: Never</code></strong> on batch/best-effort classes so they never disrupt others, even though they can still be scheduled when capacity allows.</li>



<li><strong>Document your PriorityClass tiers</strong> in your platform&#8217;s onboarding docs so teams pick the correct one instead of guessing.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Forgetting <code>globalDefault: true</code> must be set on exactly one PriorityClass — if none is marked default, unlabeled Pods get priority <code>0</code>, which is lower than almost everything and can cause them to be preempted unexpectedly.</li>



<li>Setting overly aggressive high-priority classes on non-critical workloads, causing cascading preemptions across the cluster during load spikes.</li>



<li>Not testing preemption behavior in a staging cluster before rolling PriorityClasses into production — the first real preemption event shouldn&#8217;t be a surprise.</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Pod Priority and Preemption give Kubernetes a principled way to decide who keeps running when resources get tight, and wiring <code>priorityClassName</code> into your Helm charts as a configurable value lets you apply consistent, environment-aware scheduling policy without duplicating manifests. Define a small, well-documented set of PriorityClasses, expose the choice through Helm values, and pair it with resource requests and PDBs for a cluster that degrades gracefully under pressure instead of falling over.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/">Kubernetes Documentation: Pod Priority and Preemption</a></li>



<li><a href="https://helm.sh/docs/">Helm Documentation</a></li>



<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/">Kubernetes Scheduling Concepts</a></li>



<li><a href="https://www.cncf.io/">CNCF Kubernetes Project</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-implement-pod-priority-and-preemption-with-helm-in-kubernetes/">How to Implement Pod Priority and Preemption with Helm in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-implement-pod-priority-and-preemption-with-helm-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7142</post-id>	</item>
		<item>
		<title>How to Set Up Kubernetes Monitoring with Splunk</title>
		<link>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-splunk/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-splunk/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:14:37 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7139</guid>

					<description><![CDATA[<p>A lot of Kubernetes monitoring guides default to the Prometheus/Grafana stack, but plenty of organizations already run Splunk&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-splunk/">How to Set Up Kubernetes Monitoring with Splunk</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A lot of Kubernetes monitoring guides default to the Prometheus/Grafana stack, but plenty of organizations already run Splunk as their enterprise-wide observability platform and want their Kubernetes clusters feeding into it rather than standing up a parallel toolchain. In this guide, I&#8217;ll walk through setting up full Kubernetes monitoring with Splunk — logs, metrics, and traces — using the Splunk OpenTelemetry Collector, which has become the standard, supported path for this integration.</p>



<h2 class="wp-block-heading">Why Splunk for Kubernetes</h2>



<p class="wp-block-paragraph">Splunk excels at correlating data across an entire organization — infrastructure, applications, security events — in one searchable index. If your security team already lives in Splunk for SIEM purposes, routing Kubernetes observability data there too means one pane of glass, one query language (SPL), and one retention/compliance policy, instead of maintaining Splunk alongside a separate Prometheus stack.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Context: What You&#8217;re Actually Monitoring</h2>



<p class="wp-block-paragraph">Before wiring up any tool, it helps to be clear on what data sources exist in a cluster:</p>



<ul class="wp-block-list">
<li><strong>Container logs</strong>: stdout/stderr from every container, written to disk on each node by the container runtime.</li>



<li><strong>kubelet /metrics/cadvisor</strong>: Per-container CPU, memory, disk, and network usage.</li>



<li><strong>kube-state-metrics</strong>: Cluster-level object state (Deployment replica counts, Pod phase, node conditions) — this is <em>not</em> the same as resource usage; it&#8217;s object metadata as metrics.</li>



<li><strong>API server metrics</strong>: Request latency, error rates — critical for cluster health.</li>



<li><strong>Application metrics</strong>: Whatever your app exposes, often in Prometheus exposition format.</li>
</ul>



<p class="wp-block-paragraph">A complete monitoring setup pulls from all of these.</p>



<h2 class="wp-block-heading">Step 1: Install the Splunk OpenTelemetry Collector via Helm</h2>



<p class="wp-block-paragraph">Splunk&#8217;s officially supported path for Kubernetes is the <strong>Splunk Distribution of the OpenTelemetry Collector</strong>, deployed as a Helm chart that runs both a node-level agent (DaemonSet) and a cluster-level receiver (Deployment).</p>



<pre class="wp-block-code"><code>helm repo add splunk-otel-collector-chart https://signalfx.github.io/splunk-otel-collector-chart
helm repo update
</code></pre>



<p class="wp-block-paragraph">Create your values file:</p>



<pre class="wp-block-code"><code># splunk-otel-values.yaml
splunkObservability:
  realm: us1
  accessToken: "&lt;YOUR_SPLUNK_ACCESS_TOKEN&gt;"

clusterName: production-cluster

logsEngine: otel

splunkPlatform:
  endpoint: "https://splunk-hec.yourcompany.com:8088/services/collector"
  token: "&lt;YOUR_HEC_TOKEN&gt;"
  index: "kubernetes"
  metricsIndex: "k8s_metrics"
  insecureSkipVerify: false

metricsEnabled: true
tracesEnabled: true
logsEnabled: true
</code></pre>



<p class="wp-block-paragraph">Install it:</p>



<pre class="wp-block-code"><code>helm install splunk-otel-collector splunk-otel-collector-chart/splunk-otel-collector \
  -n monitoring --create-namespace \
  -f splunk-otel-values.yaml
</code></pre>



<p class="wp-block-paragraph">Verify:</p>



<pre class="wp-block-code"><code>kubectl get pods -n monitoring
</code></pre>



<p class="wp-block-paragraph">Expect to see an <code>agent</code> DaemonSet Pod on every node, plus a <code>k8s-cluster-receiver</code> Deployment Pod.</p>



<h2 class="wp-block-heading">Step 2: Understand the Two Collector Roles</h2>



<ul class="wp-block-list">
<li><strong>Agent (DaemonSet)</strong>: Runs on every node, tails container log files directly off disk, scrapes kubelet/cAdvisor metrics for that node&#8217;s Pods, and receives traces from local application SDKs.</li>



<li><strong>Cluster Receiver (single Deployment)</strong>: Watches the Kubernetes API for cluster-wide object state (this is where kube-state-metrics-equivalent data comes from) so you&#8217;re not duplicating that collection on every node.</li>
</ul>



<p class="wp-block-paragraph">This split matters for resource planning — the agent&#8217;s resource requests scale with node count, while the cluster receiver stays a single low-overhead Pod regardless of cluster size.</p>



<h2 class="wp-block-heading">Step 3: Verify Data Is Reaching Splunk</h2>



<p class="wp-block-paragraph">In Splunk Search:</p>



<pre class="wp-block-code"><code>index=kubernetes | stats count by k8s.namespace.name, k8s.pod.name
</code></pre>



<p class="wp-block-paragraph">For metrics (if using Splunk Infrastructure Monitoring / Observability Cloud):</p>



<pre class="wp-block-code"><code>index=k8s_metrics metric_name="k8s.pod.cpu.utilization"
| stats avg(value) by k8s.pod.name
</code></pre>



<p class="wp-block-paragraph">If nothing shows up after a few minutes, check the collector&#8217;s own logs first:</p>



<pre class="wp-block-code"><code>kubectl logs -n monitoring daemonset/splunk-otel-collector-agent
</code></pre>



<p class="wp-block-paragraph">Look for HEC connection errors — the most common early failure is a wrong token or an unreachable HEC endpoint due to network policy or firewall rules.</p>



<h2 class="wp-block-heading">Step 4: Configure HTTP Event Collector (HEC) on the Splunk Side</h2>



<p class="wp-block-paragraph">If you&#8217;re managing Splunk yourself (not Splunk Cloud), make sure HEC is enabled and a token exists with access to your target index:</p>



<pre class="wp-block-code"><code># On the Splunk instance
splunk enable listen 8088 -auth admin:changeme
</code></pre>



<p class="wp-block-paragraph">In Splunk Web: <strong>Settings → Data Inputs → HTTP Event Collector → New Token</strong>, scoped to the <code>kubernetes</code> and <code>k8s_metrics</code> indexes you created.</p>



<h2 class="wp-block-heading">Step 5: Add Kubernetes Metadata Enrichment</h2>



<p class="wp-block-paragraph">By default, the collector enriches every log line and metric with Kubernetes metadata (namespace, pod name, labels, node) via the <code>k8sattributes</code> processor, which is already configured in the chart. You can extend it to pull specific custom labels your teams use for ownership tagging:</p>



<pre class="wp-block-code"><code>agent:
  config:
    processors:
      k8sattributes:
        extract:
          labels:
            - tag_name: team
              key: team
              from: pod
            - tag_name: environment
              key: env
              from: pod
</code></pre>



<p class="wp-block-paragraph">This means every log and metric arriving in Splunk is automatically tagged with which team owns the Pod that generated it — critical for building dashboards and alerts scoped by team without manual tagging.</p>



<h2 class="wp-block-heading">Step 6: Building Dashboards and Alerts</h2>



<p class="wp-block-paragraph">Once data is flowing, build a basic health dashboard in Splunk with panels like:</p>



<pre class="wp-block-code"><code># Pod restart count in last hour
index=k8s_metrics metric_name="k8s.pod.restart_count"
| timechart span=5m max(value) by k8s.pod.name

# Error rate from application logs
index=kubernetes k8s.namespace.name="production" log_level="ERROR"
| timechart span=1m count
</code></pre>



<p class="wp-block-paragraph">Set up alerts on conditions like sustained high restart counts, node NotReady status, or PVC usage crossing a threshold — all queryable the same way you&#8217;d alert on any other Splunk data source.</p>



<h2 class="wp-block-heading">Security Considerations</h2>



<ul class="wp-block-list">
<li>Use a <strong>dedicated Kubernetes ServiceAccount</strong> with RBAC scoped only to read (<code>get</code>, <code>list</code>, <code>watch</code>) on the resources the collector needs — never grant it write access.</li>



<li>Store the Splunk access token and HEC token as <strong>Kubernetes Secrets</strong>, referenced via <code>envFrom</code>, never hardcoded in values files committed to Git.</li>



<li>Enable TLS between the collector and your Splunk HEC endpoint (<code>insecureSkipVerify: false</code> in production, always).</li>
</ul>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: splunk-otel-collector
rules:
  - apiGroups: &#91;""]
    resources: &#91;"pods", "nodes", "namespaces", "events"]
    verbs: &#91;"get", "list", "watch"]
  - apiGroups: &#91;"apps"]
    resources: &#91;"deployments", "replicasets", "statefulsets"]
    verbs: &#91;"get", "list", "watch"]
</code></pre>



<h2 class="wp-block-heading">Performance and Cost Optimization</h2>



<ul class="wp-block-list">
<li><strong>Filter noisy logs at the collector</strong>, not after ingestion — dropping health-check log spam before it hits Splunk saves significant licensing cost, since Splunk pricing is typically volume-based.</li>



<li><strong>Downsample high-cardinality metrics</strong> where per-second granularity isn&#8217;t needed.</li>



<li><strong>Use index-time field extraction sparingly</strong> — prefer search-time extraction where possible to reduce indexing overhead.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Forgetting to set <code>clusterName</code> uniquely per cluster — without it, data from multiple clusters gets conflated in Splunk, making dashboards useless in multi-cluster environments.</li>



<li>Not setting resource limits on the agent DaemonSet, letting it consume unbounded memory on log-heavy nodes.</li>



<li>Sending 100% of logs from noisy sidecars (like service mesh proxies) without filtering, drowning out application signal.</li>
</ul>



<h2 class="wp-block-heading">High Availability</h2>



<p class="wp-block-paragraph">Run the cluster receiver Deployment with at least 2 replicas behind leader election (the chart supports this natively) so a single Pod restart doesn&#8217;t create a gap in cluster-level metric collection. The DaemonSet agent is inherently HA per-node since each node has its own instance.</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Splunk can absolutely serve as your primary Kubernetes observability backend, and the Splunk OpenTelemetry Collector Helm chart is the well-supported way to get there — combining a DaemonSet agent for node-local logs/metrics/traces with a cluster receiver for object-level state. Get HEC configured correctly first, verify data with simple SPL queries, then layer in metadata enrichment, dashboards, and alerts once you trust the pipeline.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://docs.splunk.com/observability/en/gdi/opentelemetry/collector-kubernetes/collector-kubernetes.html">Splunk OpenTelemetry Collector for Kubernetes</a></li>



<li><a href="https://github.com/signalfx/splunk-otel-collector-chart">Splunk OpenTelemetry Collector Helm Chart</a></li>



<li><a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-usage-monitoring/">Kubernetes Documentation: Tools for Monitoring Resources</a></li>



<li><a href="https://www.cncf.io/">CNCF OpenTelemetry Project</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-splunk/">How to Set Up Kubernetes Monitoring with Splunk</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-splunk/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7139</post-id>	</item>
		<item>
		<title>How to Use Priority and Preemption in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-use-priority-and-preemption-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-use-priority-and-preemption-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:08:25 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7136</guid>

					<description><![CDATA[<p>Every cluster eventually hits a moment where demand outpaces capacity — a traffic spike, a batch of jobs&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-use-priority-and-preemption-in-kubernetes/">How to Use Priority and Preemption in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Every cluster eventually hits a moment where demand outpaces capacity — a traffic spike, a batch of jobs kicked off at the same time, or a node lost to maintenance. When that happens, Kubernetes needs a way to decide whose Pods get to run. That&#8217;s what Priority and Preemption is for. This guide covers the mechanism in depth, independent of any specific tool like Helm — how it works internally, how to configure it, and how to reason about its effects on scheduling.</p>



<h2 class="wp-block-heading">The Core Problem It Solves</h2>



<p class="wp-block-paragraph">By default, if the scheduler can&#8217;t find room for a Pod, that Pod just sits in <code>Pending</code> forever, waiting for capacity to free up on its own. For most workloads, that&#8217;s fine. But for something genuinely important — an API gateway, a database primary — waiting indefinitely isn&#8217;t acceptable. Priority and Preemption lets Kubernetes actively make room for important Pods by evicting less important ones.</p>



<h2 class="wp-block-heading">How Kubernetes Architecture Handles This</h2>



<p class="wp-block-paragraph">Two API objects and one scheduler behavior make this work:</p>



<ol class="wp-block-list">
<li><strong>PriorityClass</strong> (cluster-scoped): Maps a name to an integer priority value.</li>



<li><strong>Pod.spec.priorityClassName</strong>: References a PriorityClass; the admission controller resolves it into <code>Pod.spec.priority</code> at creation time.</li>



<li><strong>kube-scheduler</strong>: During scheduling, if a Pod can&#8217;t fit anywhere as-is, the scheduler runs its <strong>preemption logic</strong> — searching for a node where evicting some subset of lower-priority Pods would free enough resources, then evicts the minimum necessary set.</li>
</ol>



<p class="wp-block-paragraph">This all happens inside <code>kube-scheduler</code>; no other component is involved in the decision, though the API server and kubelet carry out the actual eviction and rescheduling.</p>



<h2 class="wp-block-heading">Step 1: Understand Default Behavior</h2>



<p class="wp-block-paragraph">If you never create a PriorityClass, every Pod has priority <code>0</code> and preemption essentially never triggers in a meaningful way, because everything is equally &#8220;important&#8221; (or unimportant). Priority only becomes useful once you deliberately tier your workloads.</p>



<p class="wp-block-paragraph">Check existing PriorityClasses, including built-in system ones:</p>



<pre class="wp-block-code"><code>kubectl get priorityclass
</code></pre>



<p class="wp-block-paragraph">Typical output on any cluster:</p>



<pre class="wp-block-code"><code>NAME                      VALUE        GLOBAL-DEFAULT   AGE
system-cluster-critical   2000000000   false            30d
system-node-critical      2000001000   false            30d
</code></pre>



<p class="wp-block-paragraph">These extremely high values are reserved for core cluster components like <code>kube-dns</code> and CNI plugins — application workloads should never use these classes.</p>



<h2 class="wp-block-heading">Step 2: Create Custom PriorityClasses</h2>



<pre class="wp-block-code"><code>apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 100000
globalDefault: false
description: "High priority for customer-facing production services."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: default-priority
value: 10000
globalDefault: true
description: "Default for general application workloads."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 100
globalDefault: false
preemptionPolicy: Never
description: "Best-effort batch and background workloads."
</code></pre>



<pre class="wp-block-code"><code>kubectl apply -f priorityclasses.yaml
</code></pre>



<h2 class="wp-block-heading">Step 3: Assign Priority to a Pod</h2>



<pre class="wp-block-code"><code>apiVersion: v1
kind: Pod
metadata:
  name: important-api
spec:
  priorityClassName: high-priority
  containers:
    - name: api
      image: myrepo/api:latest
      resources:
        requests:
          cpu: "1"
          memory: 1Gi
</code></pre>



<pre class="wp-block-code"><code>kubectl apply -f important-api.yaml
kubectl get pod important-api -o jsonpath='{.spec.priority}'
# 100000
</code></pre>



<h2 class="wp-block-heading">Step 4: Force a Preemption Scenario (for Learning)</h2>



<p class="wp-block-paragraph">To see preemption in action in a test cluster, fill a node close to capacity with <code>low-priority</code> Pods, then schedule a <code>high-priority</code> Pod requesting more than the currently free capacity:</p>



<pre class="wp-block-code"><code>kubectl apply -f low-priority-filler.yaml   # 5 replicas at low-priority
kubectl apply -f important-api.yaml         # high-priority, needs the space
</code></pre>



<p class="wp-block-paragraph">Watch events:</p>



<pre class="wp-block-code"><code>kubectl get events --sort-by='.lastTimestamp' | grep -i preempt
</code></pre>



<p class="wp-block-paragraph">You&#8217;ll see something like:</p>



<pre class="wp-block-code"><code>Normal  Preempted  pod/low-priority-filler-2  Preempted by important-api on node worker-1
</code></pre>



<p class="wp-block-paragraph">The evicted Pod returns to <code>Pending</code> and gets rescheduled elsewhere once capacity exists — assuming your cluster has room; if not, it stays pending, which is expected.</p>



<h2 class="wp-block-heading">PreemptionPolicy: Never</h2>



<p class="wp-block-paragraph">Setting <code>preemptionPolicy: Never</code> on a PriorityClass means Pods in that class will queue for available capacity like anything else, but will never cause other Pods to be evicted, even if their nominal priority is technically higher than some running Pod. This is the right choice for batch jobs that are &#8220;important but patient&#8221; — they shouldn&#8217;t disrupt live traffic just because someone gave them a high priority number for queue-ordering purposes.</p>



<pre class="wp-block-code"><code>apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: patient-batch
value: 50000
preemptionPolicy: Never
</code></pre>



<h2 class="wp-block-heading">How the Scheduler Chooses What to Evict</h2>



<p class="wp-block-paragraph">The scheduler doesn&#8217;t just grab the lowest-priority Pod on any node — it tries to minimize collateral damage:</p>



<ul class="wp-block-list">
<li>It only considers Pods with <strong>strictly lower</strong> priority than the pending Pod.</li>



<li>It prefers nodes where evicting the <strong>fewest</strong> Pods (or those causing least disruption) makes room.</li>



<li>It respects <strong>PodDisruptionBudgets</strong> where possible, though a PDB is not an absolute guarantee against preemption if there&#8217;s truly no other option — preemption can still violate a PDB as a last resort, unlike voluntary eviction via <code>kubectl drain</code>, which strictly respects PDBs.</li>



<li><strong>Graceful termination</strong> still applies — evicted Pods get their normal <code>terminationGracePeriodSeconds</code>.</li>
</ul>



<h2 class="wp-block-heading">Interaction with Other Scheduling Features</h2>



<ul class="wp-block-list">
<li><strong>Node affinity / taints and tolerations</strong>: Preemption only considers nodes the pending Pod could actually run on given its affinity rules and tolerations — it won&#8217;t preempt Pods on a node the pending Pod couldn&#8217;t schedule to anyway.</li>



<li><strong>Pod Disruption Budgets</strong>: Provide a soft guarantee, not a hard block, against preemption.</li>



<li><strong>Cluster Autoscaler</strong>: If preemption alone can&#8217;t free enough room and the cluster can scale, the autoscaler may add nodes instead — preemption and autoscaling work as complementary mechanisms, not substitutes.</li>
</ul>



<h2 class="wp-block-heading">Best Practices</h2>



<ul class="wp-block-list">
<li>Keep your PriorityClass tiers small and well-documented — 3 to 5 tiers is usually plenty (system, critical, standard, batch).</li>



<li>Reserve very high values for platform/system workloads only.</li>



<li>Use resource requests accurately — the scheduler&#8217;s preemption math is only as good as the requests Pods declare; underdeclared requests lead to unpredictable preemption behavior.</li>



<li>Combine with <strong>ResourceQuotas</strong> per namespace so a single team can&#8217;t flood the cluster with high-priority Pods and starve everyone else.</li>



<li>Test preemption behavior deliberately in staging before your first real production incident forces you to learn it live.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Assigning high priority to workloads &#8220;just in case,&#8221; which inflates priority creep across the org until the mechanism becomes meaningless.</li>



<li>Not setting a <code>globalDefault</code> PriorityClass, leaving unlabeled Pods at priority <code>0</code> — often lower than intended, making them the first candidates for preemption.</li>



<li>Assuming PDBs fully protect against preemption; they don&#8217;t guarantee it during the &#8220;last resort&#8221; scheduling path.</li>



<li>Ignoring <code>preemptionPolicy: Never</code> for batch workloads, causing unnecessary disruption of running work by lower-urgency jobs.</li>
</ul>



<h2 class="wp-block-heading">Troubleshooting</h2>



<p class="wp-block-paragraph">If a Pod stays <code>Pending</code> with <code>FailedScheduling</code> events even though lower-priority Pods exist:</p>



<pre class="wp-block-code"><code>kubectl describe pod important-api
</code></pre>



<p class="wp-block-paragraph">Check the event reason carefully — it may be that no single node has enough <em>evictable</em> lower-priority capacity, even though the cluster in aggregate does. Preemption only considers one node at a time; it doesn&#8217;t combine partial evictions across multiple nodes for a single Pod.</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Priority and Preemption gives Kubernetes a built-in way to enforce &#8220;important work wins&#8221; during resource contention, without requiring manual intervention. The mechanism is entirely scheduler-driven: PriorityClasses define the tiers, Pods reference them, and the scheduler evicts the minimum necessary lower-priority Pods to make room for higher-priority ones — always respecting graceful termination and, where possible, disruption budgets. Use it deliberately, with a small number of well-understood tiers, and it becomes one of the more powerful reliability tools in your cluster.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/">Kubernetes Documentation: Pod Priority and Preemption</a></li>



<li><a href="https://kubernetes.io/docs/reference/kubernetes-api/scheduling-resources/priority-class-v1/">Kubernetes Documentation: PriorityClass API Reference</a></li>



<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/">Kubernetes Scheduling Concepts Overview</a></li>



<li><a href="https://www.cncf.io/">CNCF Kubernetes Project</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-use-priority-and-preemption-in-kubernetes/">How to Use Priority and Preemption in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-use-priority-and-preemption-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7136</post-id>	</item>
		<item>
		<title>How to Set Up Pod Disruption Budgets with Thanos in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-thanos-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-thanos-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:06:04 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7133</guid>

					<description><![CDATA[<p>Thanos extends Prometheus with long-term storage, global querying, and high-availability capabilities across multiple Prometheus instances — and like&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-thanos-in-kubernetes/">How to Set Up Pod Disruption Budgets with Thanos in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Thanos extends Prometheus with long-term storage, global querying, and high-availability capabilities across multiple Prometheus instances — and like any distributed system running on Kubernetes, it needs protection from routine cluster maintenance taking down too many components at once. In this guide, I&#8217;ll walk through configuring Pod Disruption Budgets (PDBs) for a Thanos deployment, component by component, since Thanos isn&#8217;t a single monolith but a set of cooperating services each with different availability requirements.</p>



<h2 class="wp-block-heading">Why Thanos Needs Careful PDB Planning</h2>



<p class="wp-block-paragraph">Thanos is composed of several distinct components, each with different failure characteristics:</p>



<ul class="wp-block-list">
<li><strong>Sidecar</strong>: Runs alongside each Prometheus Pod, uploads blocks to object storage, and serves Prometheus&#8217;s local data to Queriers. Tightly coupled to its Prometheus instance.</li>



<li><strong>Querier</strong>: Stateless, fans out queries across Sidecars and Store Gateways, deduplicates results.</li>



<li><strong>Store Gateway</strong>: Serves historical data from object storage; often stateful with local caching.</li>



<li><strong>Compactor</strong>: Downsamples and compacts blocks in object storage — typically a <strong>single replica</strong>, since concurrent compaction against the same storage bucket can cause corruption.</li>



<li><strong>Receiver</strong>: Accepts remote-write traffic in HA setups; loses data if too many replicas go down before flushing.</li>
</ul>



<p class="wp-block-paragraph">Because the Compactor must run as a singleton, and the Receiver/Store Gateway have real availability requirements, a one-size-fits-all PDB strategy doesn&#8217;t work here — each component needs its own budget tuned to its actual replication and failure tolerance.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Refresher: How PDBs Are Enforced</h2>



<p class="wp-block-paragraph">A PDB doesn&#8217;t block direct <code>kubectl delete pod</code> calls. It only governs the <strong>Eviction API</strong>, which is what <code>kubectl drain</code>, the Cluster Autoscaler, and managed node upgrades use. When an eviction request comes in:</p>



<ol class="wp-block-list">
<li>The API server checks all PDBs whose selector matches the target Pod.</li>



<li>If evicting would drop available replicas below <code>minAvailable</code> (or exceed <code>maxUnavailable</code>), the request is denied with <code>429 Too Many Requests</code>.</li>



<li>The evicting controller retries later, once conditions allow.</li>
</ol>



<p class="wp-block-paragraph">This means PDBs protect you specifically during voluntary cluster operations — node drains, cordoning for upgrades, and autoscaler scale-downs — which is exactly when a multi-component system like Thanos is most at risk of losing more replicas at once than it can tolerate.</p>



<h2 class="wp-block-heading">Step 1: Deploy Thanos via Helm</h2>



<p class="wp-block-paragraph">Using the community <code>kube-prometheus-stack</code> or standalone <code>thanos</code> chart:</p>



<pre class="wp-block-code"><code>helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install thanos bitnami/thanos -n monitoring --create-namespace \
  --set query.replicaCount=2 \
  --set storegateway.replicaCount=2 \
  --set receive.replicaCount=3 \
  --set compactor.enabled=true
</code></pre>



<p class="wp-block-paragraph">Check what&#8217;s running:</p>



<pre class="wp-block-code"><code>kubectl get pods -n monitoring -l app.kubernetes.io/name=thanos
</code></pre>



<h2 class="wp-block-heading">Step 2: PDB for the Querier (Stateless, Safe to Disrupt Gradually)</h2>



<pre class="wp-block-code"><code>apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: thanos-query-pdb
  namespace: monitoring
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: thanos-query
</code></pre>



<p class="wp-block-paragraph">With 2 replicas and <code>maxUnavailable: 1</code>, at least one Querier always stays up to serve Grafana dashboards during node maintenance.</p>



<h2 class="wp-block-heading">Step 3: PDB for the Store Gateway</h2>



<p class="wp-block-paragraph">Store Gateways cache index data locally and can be expensive to &#8220;warm up&#8221; again after a restart, so it&#8217;s worth being conservative:</p>



<pre class="wp-block-code"><code>apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: thanos-storegateway-pdb
  namespace: monitoring
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: thanos-storegateway
</code></pre>



<p class="wp-block-paragraph">With <code>minAvailable: 1</code> on a 2-replica StatefulSet, only one can be evicted at a time — the other keeps serving historical query traffic while its sibling restarts and re-warms its cache.</p>



<h2 class="wp-block-heading">Step 4: PDB for the Receiver (Data-Loss Sensitive)</h2>



<p class="wp-block-paragraph">Receivers accept remote-write traffic; taking down too many at once during a rolling node upgrade can cause dropped samples if your remote-write clients don&#8217;t buffer well. With 3 replicas and a typical replication factor of 2 in the Receiver hashring:</p>



<pre class="wp-block-code"><code>apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: thanos-receive-pdb
  namespace: monitoring
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: thanos-receive
</code></pre>



<p class="wp-block-paragraph">This guarantees the hashring never drops below the minimum needed to satisfy its own replication factor during voluntary disruptions.</p>



<h2 class="wp-block-heading">Step 5: The Compactor — Deliberately No PDB (or a Trivial One)</h2>



<p class="wp-block-paragraph">Since the Compactor typically runs as a <strong>single replica by design</strong> (running more than one against the same bucket risks corrupting compacted blocks), a <code>minAvailable: 1</code> PDB would make it impossible to ever drain the node it&#8217;s on. For a singleton like this, it&#8217;s usually better to <strong>not</strong> create a PDB at all, and instead rely on it simply restarting elsewhere after eviction — accept the short gap in compaction rather than blocking cluster maintenance indefinitely.</p>



<pre class="wp-block-code"><code># Deliberately omitted: no PDB for thanos-compactor
# Rationale: single replica by design; a minAvailable:1 PDB would
# permanently block node drains for the node it's scheduled on.
</code></pre>



<p class="wp-block-paragraph">If you want <em>some</em> protection without blocking drains forever, use <code>maxUnavailable: 0</code> combined with a <strong>PriorityClass</strong> and tight pod anti-affinity instead — but understand this still allows involuntary disruption, it just discourages voluntary ones without hard-blocking them indefinitely (this requires care and isn&#8217;t a universal recommendation).</p>



<h2 class="wp-block-heading">Step 6: Apply and Verify</h2>



<pre class="wp-block-code"><code>kubectl apply -f thanos-pdbs.yaml
kubectl get pdb -n monitoring
</code></pre>



<pre class="wp-block-code"><code>NAME                       MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
thanos-query-pdb           N/A             1                 1                     2m
thanos-storegateway-pdb    1               N/A               1                     2m
thanos-receive-pdb         2               N/A               1                     2m
</code></pre>



<h2 class="wp-block-heading">Step 7: Simulate a Drain and Observe</h2>



<pre class="wp-block-code"><code>kubectl drain &lt;node-name&gt; --ignore-daemonsets --delete-emptydir-data
</code></pre>



<p class="wp-block-paragraph">Watch how eviction proceeds component by component — Queriers and Store Gateways drain one at a time per their budgets, while Receiver Pods stop being evicted once only 2 remain, with the drain command retrying automatically until the third becomes safe to move (once a replacement is healthy elsewhere).</p>



<h2 class="wp-block-heading">High Availability and Disaster Recovery</h2>



<ul class="wp-block-list">
<li>Spread Thanos components across <strong>multiple availability zones</strong> with topology spread constraints — PDBs limit disruption <em>during maintenance</em>, but zone-level redundancy protects against an entire zone outage, which PDBs cannot do anything about.</li>



<li>Ensure object storage (S3/GCS/Azure Blob) used by Thanos has its own durability guarantees and versioning — PDBs protect compute availability, not the underlying data.</li>



<li>Regularly test full restores from the object storage bucket to confirm your Store Gateways and Compactor can rebuild state if the cluster itself is lost.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Applying the same PDB template to every component without considering each one&#8217;s actual replication semantics — this is the single most common mistake with Thanos specifically, since it&#8217;s not architecturally uniform like a simple stateless web app.</li>



<li>Forgetting the Compactor is a singleton and accidentally blocking node drains with an overly strict PDB.</li>



<li>Setting <code>minAvailable</code> on the Receiver lower than its hashring replication factor, silently risking dropped writes during maintenance even though the PDB &#8220;passes.&#8221;</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Thanos isn&#8217;t one workload — it&#8217;s several, each with distinct availability semantics, and your PDB strategy needs to reflect that. Give the Querier and Store Gateway conservative budgets, protect the Receiver hashring&#8217;s replication factor explicitly, and think carefully before applying any PDB at all to the singleton Compactor. Done right, this lets you drain and upgrade nodes confidently without silently degrading your monitoring stack&#8217;s own reliability — which is the last thing you want to fail quietly.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/">Kubernetes Documentation: Pod Disruption Budgets</a></li>



<li><a href="https://thanos.io/tip/thanos/quick-tutorial.md/">Thanos Documentation</a></li>



<li><a href="https://github.com/thanos-io/thanos">Thanos GitHub Repository</a></li>



<li><a href="https://www.cncf.io/">CNCF Thanos Project Page</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-thanos-in-kubernetes/">How to Set Up Pod Disruption Budgets with Thanos in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-set-up-pod-disruption-budgets-with-thanos-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7133</post-id>	</item>
		<item>
		<title>How to Implement StatefulSets with Helm in Kubernetes</title>
		<link>https://awjunaid.com/kubernetes/how-to-implement-statefulsets-with-helm-in-kubernetes/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-implement-statefulsets-with-helm-in-kubernetes/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:04:13 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7130</guid>

					<description><![CDATA[<p>Deployments are great for stateless applications where any replica is interchangeable, but the moment you need stable network&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-implement-statefulsets-with-helm-in-kubernetes/">How to Implement StatefulSets with Helm in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Deployments are great for stateless applications where any replica is interchangeable, but the moment you need stable network identities, ordered startup, or persistent per-replica storage — databases, message brokers, distributed caches — you need a <strong>StatefulSet</strong>. Packaging one cleanly and reusably with Helm takes a bit more care than a typical stateless chart. This guide walks through why StatefulSets exist, how they behave differently from Deployments, and how to build a production-ready Helm chart around one.</p>



<h2 class="wp-block-heading">Why StatefulSets Exist</h2>



<p class="wp-block-paragraph">A Deployment&#8217;s Pods are fungible — Pod names are randomly suffixed, storage is typically shared or ephemeral, and Pods can be created/destroyed in any order. That model breaks for anything that needs:</p>



<ul class="wp-block-list">
<li><strong>Stable, predictable network identity</strong> (Pod-0 is always Pod-0, even after a restart).</li>



<li><strong>Stable storage per replica</strong> — Pod-0 always gets its own PersistentVolumeClaim back, not a random one.</li>



<li><strong>Ordered, graceful deployment and scaling</strong> — Pod-0 must be Running and Ready before Pod-1 starts, which matters for things like database replication bootstrapping.</li>
</ul>



<p class="wp-block-paragraph">A StatefulSet guarantees all three.</p>



<h2 class="wp-block-heading">Kubernetes Architecture: What Makes a StatefulSet Different</h2>



<ul class="wp-block-list">
<li><strong>Stable identity</strong>: Pods are named <code>&lt;statefulset-name>-0</code>, <code>&lt;statefulset-name>-1</code>, etc., not randomly suffixed.</li>



<li><strong>Headless Service required</strong>: StatefulSets need a Service with <code>clusterIP: None</code> to provide stable DNS entries per Pod (<code>pod-0.service-name.namespace.svc.cluster.local</code>).</li>



<li><strong>volumeClaimTemplates</strong>: Instead of one shared PVC, each replica gets its own PVC, created from a template, and that PVC follows the same-named Pod across rescheduling.</li>



<li><strong>Ordered rolling updates by default</strong>: Updates happen in reverse ordinal order (highest number first) unless you configure <code>podManagementPolicy: Parallel</code>.</li>
</ul>



<h2 class="wp-block-heading">Step 1: Scaffold a Helm Chart</h2>



<pre class="wp-block-code"><code>helm create my-statefulset-app
cd my-statefulset-app
rm templates/deployment.yaml templates/hpa.yaml templates/service.yaml
</code></pre>



<h2 class="wp-block-heading">Step 2: Define the Headless Service</h2>



<p class="wp-block-paragraph"><code>templates/headless-service.yaml</code>:</p>



<pre class="wp-block-code"><code>apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-statefulset-app.fullname" . }}-headless
  labels:
    {{- include "my-statefulset-app.labels" . | nindent 4 }}
spec:
  clusterIP: None
  selector:
    {{- include "my-statefulset-app.selectorLabels" . | nindent 4 }}
  ports:
    - name: app
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
</code></pre>



<h2 class="wp-block-heading">Step 3: Define the StatefulSet Template</h2>



<p class="wp-block-paragraph"><code>templates/statefulset.yaml</code>:</p>



<pre class="wp-block-code"><code>apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ include "my-statefulset-app.fullname" . }}
  labels:
    {{- include "my-statefulset-app.labels" . | nindent 4 }}
spec:
  serviceName: {{ include "my-statefulset-app.fullname" . }}-headless
  replicas: {{ .Values.replicaCount }}
  podManagementPolicy: {{ .Values.podManagementPolicy | default "OrderedReady" }}
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0
  selector:
    matchLabels:
      {{- include "my-statefulset-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-statefulset-app.selectorLabels" . | nindent 8 }}
    spec:
      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds | default 30 }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.targetPort }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            - name: data
              mountPath: {{ .Values.persistence.mountPath }}
          readinessProbe:
            tcpSocket:
              port: {{ .Values.service.targetPort }}
            initialDelaySeconds: 10
            periodSeconds: 5
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: &#91; {{ .Values.persistence.accessMode | quote }} ]
        storageClassName: {{ .Values.persistence.storageClass }}
        resources:
          requests:
            storage: {{ .Values.persistence.size }}
</code></pre>



<h2 class="wp-block-heading">Step 4: Values File</h2>



<p class="wp-block-paragraph"><code>values.yaml</code>:</p>



<pre class="wp-block-code"><code>replicaCount: 3

image:
  repository: myrepo/statefuldb
  tag: "1.0.0"

service:
  port: 5432
  targetPort: 5432

persistence:
  size: 10Gi
  accessMode: ReadWriteOnce
  storageClass: "fast-ssd"
  mountPath: /var/lib/data

podManagementPolicy: OrderedReady
terminationGracePeriodSeconds: 60

resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: "1"
    memory: 2Gi
</code></pre>



<h2 class="wp-block-heading">Step 5: Install and Verify</h2>



<pre class="wp-block-code"><code>helm install mydb ./my-statefulset-app -n data --create-namespace
kubectl get statefulset -n data
kubectl get pods -n data -w
</code></pre>



<p class="wp-block-paragraph">Expected Pod creation order:</p>



<pre class="wp-block-code"><code>mydb-0   0/1   Pending
mydb-0   1/1   Running
mydb-1   0/1   Pending
mydb-1   1/1   Running
mydb-2   0/1   Pending
mydb-2   1/1   Running
</code></pre>



<p class="wp-block-paragraph">Each Pod only starts once the previous one is <code>Running</code> and <code>Ready</code> — this is the ordered guarantee at work.</p>



<h2 class="wp-block-heading">Step 6: Verify Stable Storage and Identity</h2>



<pre class="wp-block-code"><code>kubectl get pvc -n data
</code></pre>



<pre class="wp-block-code"><code>NAME           STATUS   VOLUME    CAPACITY   ACCESS MODES
data-mydb-0    Bound    pvc-abc   10Gi       RWO
data-mydb-1    Bound    pvc-def   10Gi       RWO
data-mydb-2    Bound    pvc-ghi   10Gi       RWO
</code></pre>



<p class="wp-block-paragraph">Delete <code>mydb-1</code> and watch it come back with the <strong>same PVC</strong>, not a fresh empty volume:</p>



<pre class="wp-block-code"><code>kubectl delete pod mydb-1 -n data
kubectl get pod mydb-1 -n data -o jsonpath='{.spec.volumes&#91;0].persistentVolumeClaim.claimName}'
# data-mydb-1
</code></pre>



<p class="wp-block-paragraph">Check DNS resolution from another Pod in the cluster:</p>



<pre class="wp-block-code"><code>kubectl run -it --rm debug --image=busybox -n data -- nslookup mydb-1.mydb-headless.data.svc.cluster.local
</code></pre>



<h2 class="wp-block-heading">Rolling Updates with Partitions</h2>



<p class="wp-block-paragraph">For careful, staged rollouts (e.g., testing a new version on just the highest-ordinal replica first), use <code>rollingUpdate.partition</code>:</p>



<pre class="wp-block-code"><code>updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    partition: 2
</code></pre>



<p class="wp-block-paragraph">With <code>partition: 2</code> on a 3-replica set, only <code>mydb-2</code> gets updated on a chart upgrade; <code>mydb-0</code> and <code>mydb-1</code> stay untouched until you lower the partition value. This is a standard canary pattern for stateful workloads where you want to validate before wider rollout.</p>



<pre class="wp-block-code"><code>helm upgrade mydb ./my-statefulset-app --set image.tag=1.1.0 --set statefulset.partition=2
# validate mydb-2, then:
helm upgrade mydb ./my-statefulset-app --set image.tag=1.1.0 --set statefulset.partition=0
</code></pre>



<h2 class="wp-block-heading">Scaling Considerations</h2>



<p class="wp-block-paragraph">Scaling up adds new ordinals sequentially (<code>mydb-3</code> after <code>mydb-0..2</code> are healthy); scaling down removes the <strong>highest ordinal first</strong>, not an arbitrary one. This matters for anything using replica index for identity, like a database where node 0 is always primary by convention.</p>



<pre class="wp-block-code"><code>kubectl scale statefulset mydb --replicas=5 -n data
</code></pre>



<p class="wp-block-paragraph">Note that scaling down does <strong>not</strong> delete the PVCs by default — they&#8217;re retained so you don&#8217;t lose data by accident. Clean them up explicitly if you actually want the storage gone:</p>



<pre class="wp-block-code"><code>kubectl delete pvc data-mydb-4 data-mydb-3 -n data
</code></pre>



<h2 class="wp-block-heading">Security and Best Practices</h2>



<ul class="wp-block-list">
<li>Set <code>podManagementPolicy: Parallel</code> only for stateless-within-a-StatefulSet cases where ordering genuinely doesn&#8217;t matter (e.g., you just want stable identity/storage without startup ordering) — most real stateful apps need <code>OrderedReady</code>, the default.</li>



<li>Always define a <strong>readiness probe</strong> that reflects actual application health (e.g., can accept queries, has joined a cluster), not just &#8220;process is running&#8221; — this is what the ordering guarantee depends on.</li>



<li>Use <code>PodDisruptionBudgets</code> alongside StatefulSets to protect quorum-based systems (etcd, Kafka, Cassandra) from losing too many replicas during node maintenance.</li>



<li>Set <code>terminationGracePeriodSeconds</code> high enough for graceful shutdown (e.g., a database flushing to disk) — the default 30s is often too short for real workloads.</li>



<li>For disaster recovery, remember volumeClaimTemplates only protect against Pod rescheduling, not storage backend failure — pair with regular backups appropriate to your storage class/provider.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Forgetting the headless Service — without it, per-Pod DNS simply won&#8217;t resolve, breaking peer discovery for clustered apps.</li>



<li>Assuming scaling down deletes PVCs automatically — it doesn&#8217;t, which is a safety feature but surprises people expecting Deployment-like cleanup behavior.</li>



<li>Using <code>emptyDir</code> instead of <code>volumeClaimTemplates</code> for state that needs to survive Pod rescheduling.</li>



<li>Setting overly aggressive readiness probes that flap, causing the ordered rollout to stall waiting for a Pod that&#8217;s actually healthy but misreporting.</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">StatefulSets solve a real problem that Deployments can&#8217;t: stable identity, stable storage, and ordered lifecycle management for genuinely stateful workloads. Packaging one in Helm mainly means being deliberate about the headless Service, <code>volumeClaimTemplates</code>, and update/scaling behavior — none of which are optional details, since getting them wrong breaks the exact guarantees you chose a StatefulSet for in the first place.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/">Kubernetes Documentation: StatefulSets</a></li>



<li><a href="https://kubernetes.io/docs/concepts/services-networking/service/#headless-services">Kubernetes Documentation: Headless Services</a></li>



<li><a href="https://helm.sh/docs/chart_template_guide/">Helm Documentation: Chart Template Guide</a></li>



<li><a href="https://www.cncf.io/">CNCF Kubernetes Project</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-implement-statefulsets-with-helm-in-kubernetes/">How to Implement StatefulSets with Helm in Kubernetes</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-implement-statefulsets-with-helm-in-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7130</post-id>	</item>
		<item>
		<title>How to Set Up Kubernetes Monitoring with Zabbix</title>
		<link>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-zabbix/</link>
					<comments>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-zabbix/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[Abdul Wahab Junaid]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 09:01:56 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://awjunaid.com/?p=7127</guid>

					<description><![CDATA[<p>Zabbix has been a mainstay of infrastructure monitoring for over two decades, and while Kubernetes-native tooling like Prometheus&#8230;</p>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-zabbix/">How to Set Up Kubernetes Monitoring with Zabbix</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Zabbix has been a mainstay of infrastructure monitoring for over two decades, and while Kubernetes-native tooling like Prometheus tends to dominate the conversation, plenty of teams already run Zabbix across their entire infrastructure estate and want Kubernetes folded into that same system rather than run in parallel. In this guide, I&#8217;ll cover how to set up Kubernetes monitoring with Zabbix, using its official Helm charts and native Kubernetes monitoring template.</p>



<h2 class="wp-block-heading">Why Zabbix for Kubernetes</h2>



<p class="wp-block-paragraph">Zabbix gives you a single, mature monitoring platform across bare metal, VMs, network devices, and now Kubernetes — with built-in alerting, escalation policies, and a long history of enterprise reliability. If your infrastructure team already has Zabbix triggers, templates, and dashboards built out, extending that same system into Kubernetes avoids maintaining two separate alerting stacks with two separate on-call workflows.</p>



<h2 class="wp-block-heading">Kubernetes Architecture Context</h2>



<p class="wp-block-paragraph">Zabbix&#8217;s Kubernetes integration works differently from the typical Prometheus pull model. It uses:</p>



<ul class="wp-block-list">
<li><strong>Zabbix Agent 2</strong>, deployed as a DaemonSet, collecting node and container-level metrics.</li>



<li><strong>Zabbix Java Gateway</strong> or the newer native <strong>Kubernetes monitoring</strong> feature, which polls the Kubernetes API server directly for cluster-level object state (Pods, Deployments, Nodes) — conceptually similar to what kube-state-metrics does for Prometheus.</li>



<li><strong>Zabbix Server</strong>, the central component that stores collected data, evaluates triggers, and fires alerts.</li>



<li><strong>Zabbix Proxy</strong> (optional), useful in multi-cluster setups to reduce load on the central server and handle network segmentation.</li>
</ul>



<h2 class="wp-block-heading">Step 1: Deploy Zabbix Components via Helm</h2>



<pre class="wp-block-code"><code>helm repo add zabbix-community https://zabbix-community.github.io/helm-zabbix
helm repo update
</code></pre>



<p class="wp-block-paragraph">Install the Zabbix server, web frontend, and database:</p>



<pre class="wp-block-code"><code>helm install zabbix zabbix-community/zabbix -n zabbix --create-namespace \
  --set zabbixServer.enabled=true \
  --set zabbixWeb.enabled=true \
  --set mysql.enabled=true
</code></pre>



<p class="wp-block-paragraph">Check the deployment:</p>



<pre class="wp-block-code"><code>kubectl get pods -n zabbix
</code></pre>



<h2 class="wp-block-heading">Step 2: Deploy the Zabbix Agent as a DaemonSet</h2>



<pre class="wp-block-code"><code>apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: zabbix-agent
  namespace: zabbix
spec:
  selector:
    matchLabels:
      app: zabbix-agent
  template:
    metadata:
      labels:
        app: zabbix-agent
    spec:
      hostNetwork: true
      hostPID: true
      containers:
        - name: zabbix-agent2
          image: zabbix/zabbix-agent2:6.4-alpine-latest
          env:
            - name: ZBX_SERVER_HOST
              value: "zabbix-server.zabbix.svc.cluster.local"
            - name: ZBX_HOSTNAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          volumeMounts:
            - name: proc
              mountPath: /host/proc
              readOnly: true
            - name: sys
              mountPath: /host/sys
              readOnly: true
      volumes:
        - name: proc
          hostPath:
            path: /proc
        - name: sys
          hostPath:
            path: /sys
</code></pre>



<pre class="wp-block-code"><code>kubectl apply -f zabbix-agent-daemonset.yaml
</code></pre>



<h2 class="wp-block-heading">Step 3: Configure Native Kubernetes Monitoring</h2>



<p class="wp-block-paragraph">Modern Zabbix (6.4+) includes a <strong>native Kubernetes monitoring data collection</strong> feature that polls the API server directly, rather than requiring a separate exporter. Configure it through the Zabbix frontend:</p>



<ol class="wp-block-list">
<li><strong>Data collection → Kubernetes → Create Kubernetes cluster</strong></li>



<li>Provide the API server URL and a <strong>ServiceAccount token</strong> with read-only access (see RBAC below).</li>



<li>Zabbix auto-discovers Nodes, Pods, and Deployments and creates corresponding hosts and items automatically.</li>
</ol>



<p class="wp-block-paragraph">Create the ServiceAccount and token Zabbix will authenticate with:</p>



<pre class="wp-block-code"><code>apiVersion: v1
kind: ServiceAccount
metadata:
  name: zabbix-monitoring
  namespace: zabbix
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: zabbix-monitoring-role
rules:
  - apiGroups: &#91;""]
    resources: &#91;"pods", "nodes", "namespaces", "events", "services"]
    verbs: &#91;"get", "list", "watch"]
  - apiGroups: &#91;"apps"]
    resources: &#91;"deployments", "statefulsets", "daemonsets", "replicasets"]
    verbs: &#91;"get", "list", "watch"]
  - apiGroups: &#91;"metrics.k8s.io"]
    resources: &#91;"pods", "nodes"]
    verbs: &#91;"get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: zabbix-monitoring-binding
subjects:
  - kind: ServiceAccount
    name: zabbix-monitoring
    namespace: zabbix
roleRef:
  kind: ClusterRole
  name: zabbix-monitoring-role
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Secret
metadata:
  name: zabbix-monitoring-token
  namespace: zabbix
  annotations:
    kubernetes.io/service-account.name: zabbix-monitoring
type: kubernetes.io/service-account-token
</code></pre>



<p class="wp-block-paragraph">Retrieve the token to paste into the Zabbix frontend:</p>



<pre class="wp-block-code"><code>kubectl get secret zabbix-monitoring-token -n zabbix -o jsonpath='{.data.token}' | base64 -d
</code></pre>



<p class="wp-block-paragraph">Note this uses read-only verbs (<code>get</code>, <code>list</code>, <code>watch</code>) exclusively — Zabbix never needs write access to your cluster to monitor it.</p>



<h2 class="wp-block-heading">Step 4: Install the Kubernetes Metrics Server (Required for Resource Metrics)</h2>



<p class="wp-block-paragraph">Zabbix&#8217;s <code>metrics.k8s.io</code> polling depends on the standard Kubernetes <strong>metrics-server</strong> being present in the cluster:</p>



<pre class="wp-block-code"><code>kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl get deployment metrics-server -n kube-system
</code></pre>



<p class="wp-block-paragraph">Without this, Zabbix can still see object state (Pod phase, restart counts) but won&#8217;t get live CPU/memory utilization numbers.</p>



<h2 class="wp-block-heading">Step 5: Import the Kubernetes Nodes/Pods Template</h2>



<p class="wp-block-paragraph">Zabbix ships official templates (&#8220;Kubernetes nodes by HTTP&#8221;, &#8220;Kubernetes cluster state&#8221;) that map directly onto the discovered objects. Import via <strong>Data collection → Templates → Import</strong>, then link them to the auto-discovered host group for your cluster.</p>



<h2 class="wp-block-heading">Step 6: Build Triggers and Alerts</h2>



<p class="wp-block-paragraph">Common triggers worth configuring immediately:</p>



<ul class="wp-block-list">
<li>Node <code>NotReady</code> condition sustained for more than 2 minutes.</li>



<li>Pod <code>CrashLoopBackOff</code> state detected.</li>



<li>Deployment&#8217;s available replicas below desired replicas for more than 5 minutes.</li>



<li>PersistentVolumeClaim usage above 85%.</li>
</ul>



<p class="wp-block-paragraph">Example trigger expression (Zabbix trigger syntax) for a Pod restart spike:</p>



<pre class="wp-block-code"><code>last(/Kubernetes cluster state/kube.pod.restarts.rate&#91;production,api-server])&gt;5
</code></pre>



<p class="wp-block-paragraph">Set escalation actions in <strong>Alerts → Actions → Trigger actions</strong> to route these into whatever notification channel your team already uses in Zabbix (email, Slack integration, PagerDuty via webhook).</p>



<h2 class="wp-block-heading">Dashboards</h2>



<p class="wp-block-paragraph">Zabbix&#8217;s built-in dashboard widgets (Graph, Problems, Top hosts) can be composed into a Kubernetes overview dashboard without needing a separate tool like Grafana, though Zabbix also supports a Grafana data source plugin if your team prefers that visualization layer while keeping Zabbix as the collection/alerting backend.</p>



<h2 class="wp-block-heading">Security Considerations</h2>



<ul class="wp-block-list">
<li>Scope the monitoring ServiceAccount strictly to read-only verbs, as shown above — never grant <code>create</code>/<code>update</code>/<code>delete</code>.</li>



<li>Run the Zabbix Agent DaemonSet with only the host mounts it actually needs (<code>/proc</code>, <code>/sys</code> read-only); avoid unnecessary <code>hostPID</code>/<code>hostNetwork</code> if your monitoring needs don&#8217;t require them.</li>



<li>Store the Kubernetes API token as a Kubernetes Secret and restrict who can read it via RBAC on the <code>zabbix</code> namespace itself.</li>



<li>If exposing the Zabbix web frontend externally, put it behind proper authentication (LDAP/SAML integration, which Zabbix supports natively) and TLS.</li>
</ul>



<h2 class="wp-block-heading">Performance and Scaling</h2>



<ul class="wp-block-list">
<li>For large clusters (500+ nodes), consider a <strong>Zabbix Proxy</strong> per cluster or region to offload polling and reduce load on the central Zabbix Server, syncing data asynchronously.</li>



<li>Tune the discovery interval for Kubernetes objects — very frequent polling on large clusters can generate significant load on both the API server and Zabbix&#8217;s own database.</li>



<li>Archive/trend older historical data according to your retention needs; Zabbix&#8217;s housekeeping settings control this independently of Kubernetes.</li>
</ul>



<h2 class="wp-block-heading">Common Mistakes</h2>



<ul class="wp-block-list">
<li>Forgetting to deploy metrics-server, then wondering why CPU/memory graphs are empty even though Pod state monitoring works fine.</li>



<li>Granting the monitoring ServiceAccount write permissions &#8220;just in case&#8221; — unnecessary and a real security risk if the Zabbix server itself is ever compromised.</li>



<li>Not setting resource limits on the Zabbix Agent DaemonSet, letting it compete with actual workloads for node resources.</li>



<li>Over-polling large clusters without a Proxy tier, causing API server load spikes that show up as false &#8220;Node NotReady&#8221; alerts.</li>
</ul>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Zabbix can serve as a genuine, capable Kubernetes monitoring solution — not just a bolt-on — by combining its DaemonSet-based agent for node/container metrics with its native Kubernetes API polling for cluster object state. The setup takes a bit more manual RBAC and template work than a Prometheus stack, but it pays off if you&#8217;re consolidating Kubernetes into an observability platform your team already trusts and knows how to operate at scale.</p>



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li><a href="https://www.zabbix.com/documentation/current/en/manual/vm_monitoring/kubernetes">Zabbix Documentation: Kubernetes Monitoring</a></li>



<li><a href="https://github.com/zabbix-community/helm-zabbix">Zabbix Helm Charts</a></li>



<li><a href="https://github.com/kubernetes-sigs/metrics-server">Kubernetes Metrics Server</a></li>



<li><a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/">Kubernetes Documentation: Resource Metrics Pipeline</a></li>
</ul>
<p>The post <a href="https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-zabbix/">How to Set Up Kubernetes Monitoring with Zabbix</a> appeared first on <a href="https://awjunaid.com">Abdul Wahab Junaid</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://awjunaid.com/kubernetes/how-to-set-up-kubernetes-monitoring-with-zabbix/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7127</post-id>	</item>
	</channel>
</rss>
