<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Kubernetes on </title>
    <link>https://albertogalvez.com/categories/kubernetes/</link>
    <description>Recent content in Kubernetes on </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Sat, 19 Apr 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://albertogalvez.com/categories/kubernetes/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Kubernetes CPU Throttled</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-cpu/</link>
      <pubDate>Sat, 19 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-cpu/</guid>
      <description>&lt;h1 id=&#34;kubernetes-cpu-requests-limits-and-throttling&#34;&gt;Kubernetes CPU Requests, Limits, and Throttling&lt;/h1&gt;
&lt;p&gt;Impacts on Application Performance, some Best Practices&lt;/p&gt;
&lt;p&gt;CPU Throttled&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://albertogalvez.com/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-09-36.png&#34; type=&#34;&#34; alt=&#34;throttled&#34;  /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CPU Throttling (Kubernetes Workloads): A process where Kubernetes and Container Runtimes restricts a container&amp;rsquo;s CPU usage to stay within its assigned limits, often causing tasks to wait for the next scheduler quota period when they exceed their allowed CPU time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU Throttling (Linux Kernel): A mechanism in the CFS Bandwidth Control where the kernel prevents a cgroup from exceeding its allocated CPU quota, forcing tasks to stop running until the next quota period.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="kubernetes-cpu-requests-limits-and-throttling">Kubernetes CPU Requests, Limits, and Throttling</h1>
<p>Impacts on Application Performance, some Best Practices</p>
<p>CPU Throttled</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-09-36.png" type="" alt="throttled"  /></p>
<ul>
<li>
<p>CPU Throttling (Kubernetes Workloads): A process where Kubernetes and Container Runtimes restricts a container&rsquo;s CPU usage to stay within its assigned limits, often causing tasks to wait for the next scheduler quota period when they exceed their allowed CPU time.</p>
</li>
<li>
<p>CPU Throttling (Linux Kernel): A mechanism in the CFS Bandwidth Control where the kernel prevents a cgroup from exceeding its allocated CPU quota, forcing tasks to stop running until the next quota period.</p>
</li>
<li>
<p>CPU Requests are implemented via assigning priorities to containers’ processes, i.e., assigning CPU weight and/or “nice” value to cgroups (files “cpu.weight” or “cpu.weight.nice”)
Doc: <a href="https://docs.kernel.org/admin-guide/cgroup-v2.html#cgroup-v2-cpu">https://docs.kernel.org/admin-guide/cgroup-v2.html#cgroup-v2-cpu</a>
Known in community terms also as “Soft Limit”.</p>
</li>
<li>
<p>CPU Limits are implemented via “CFS Bandwidth Control” (CFS BWC) and cgroups “cpu.max” interface file.
CFS BWC Doc: <a href="https://docs.kernel.org/scheduler/sched-bwc.html">https://docs.kernel.org/scheduler/sched-bwc.html</a>
Known in community terms also as “Hard Limit”.</p>
</li>
</ul>
<h2 id="cpu-usage-vs-cpu-throttling">CPU Usage vs CPU Throttling</h2>
<p>A random but real example: Grafana CPU Usage vs Throttling reports for a K8s container.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-12-00.png" type="" alt=""  /></p>
<p>How to explain low CPU Usage, even much below request, but high CPU Throttling?
… and this is commonly faced case.
What does those Throttling mean?
How is Throttling measured?</p>
<h2 id="cpu-management-in-kubernetes">CPU Management in Kubernetes:</h2>
<p>Limits, Requests, Throttling</p>
<ul>
<li>
<p>Requests: Guarantee resources for workloads.
In K8s, they affect how Pods are scheduled on nodes.
Play role as CPU tasks “weight” (priority) during CPU contention on host machine.</p>
</li>
<li>
<p>Limits: Cap CPU usage to avoid over-consumption.</p>
</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-13-55.png" type="" alt=""  /></p>
<p>K8s CPU and MEM Resources packed onto Node</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-14-38.png" type="" alt=""  /></p>
<ul>
<li>CPU Throttling:
The process of limiting CPU usage when tasks or containers exceed their allocated quota (limit), forcing them to pause and wait until the next scheduling period.</li>
</ul>
<h2 id="key-terms-and-concepts">Key Terms and Concepts</h2>
<ul>
<li>
<p>CPU: A unit of processing power, which can represent a physical CPU, a CPU core, or a Logical CPU (e.g., a physical core with Hyper-Threading enabled is considered as 2 logical CPUs). In Kubernetes, it&rsquo;s often referred to as a vCPU, equivalent to a share of processing power.</p>
</li>
<li>
<p>vCPU (virtual CPU) in cloud and/or virtualized environments – out of scope of this DOC but, in short: is a unit of CPU capacity assigned to a container or pod in Kubernetes. It abstracts the underlying physical hardware, allowing Kubernetes to allocate and manage processing resources in a platform-agnostic way.
In most environments, 1 vCPU corresponds to 1 physical CPU or 1 logical CPU (e.g., 1 thread on a hyper-threaded core).
On physical hardware, a single physical CPU core with Hyper-Threading enabled may back 2 vCPUs.
In virtualized environments (e.g., VMs or cloud instances), multiple vCPUs can be over-committed and mapped to the same physical CPU core. For instance:
N:1 mapping: 4 vCPUs could share 1 physical core, with each vCPU getting a fraction of the core&rsquo;s processing power (e.g., 25% if evenly divided).</p>
</li>
<li>
<p>CPU Time: The total cumulative time a task or group of tasks spends executing on one or more CPUs. This includes time spent running tasks in parallel (on multiple CPUs) or concurrently (on the same CPU) over a defined period.</p>
</li>
<li>
<p>CPU Usage: Represents the percentage of CPU capacity consumed by a task or group of tasks over a monitored interval of wall-clock time. It’s a rate (e.g., &ldquo;50% of one CPU&rdquo; or &ldquo;2 CPUs used at 80% i.e. 160%&rdquo;), typically measured in real-time or averaged over intervals.</p>
</li>
</ul>
<p>CPU Usage and CPU Time are related, they are not identical.</p>
<ul>
<li>
<p>&ldquo;wall-clock&rdquo; refers to real-world time as you would measure on a regular clock or stopwatch. It tracks the total elapsed time from start to finish, including everything (e.g., waiting, idle, and active time), not just CPU processing time.</p>
</li>
<li>
<p>Context switching: the process where the Kernel Scheduler saves the state of a running task (e.g., CPU registers, program counter) and restores the state of another task, allowing the CPU to switch between tasks efficiently. This enables multitasking by sharing CPU time among multiple processes or threads.</p>
</li>
</ul>
<h3 id="task-types-in-relation-to-cpu-access">Task Types in Relation to CPU Access</h3>
<ul>
<li>
<p>CPU-Bound Tasks: Spend most of their time on the CPU, continuously running until their processing is complete. These tasks use the CPU intensively and benefit from more CPU time. The scheduler gives them longer CPU slices, but they are preempted if other tasks have higher priority.</p>
</li>
<li>
<p>I/O-Bound Tasks: Spend less time on the CPU and frequently yield it to wait for I/O (disk, network). They use short bursts of CPU and quickly return to a waiting state, allowing the scheduler to switch to other tasks.</p>
</li>
<li>
<p>Memory-Bound Tasks: Performance depends on memory access; they alternate between short CPU bursts and waiting for data from RAM, using the CPU sporadically.</p>
</li>
<li>
<p>Hybrid Tasks: Some workloads exhibit a mix of characteristics (e.g., web servers handling both CPU-intensive request processing and I/O-heavy database access).</p>
</li>
</ul>
<h2 id="cpu-requests-vs-k8s-scheduler">CPU Requests vs K8s Scheduler</h2>
<p>Resources (like CPU, MEM) Requests hint K8s Scheduler where (on which node) to bind the pod.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-17-16.png" type="" alt=""  /></p>
<h2 id="cpu-requests-vs-node">CPU Requests vs Node</h2>
<p>Following image may be interpreted as CPU Requests booking on the node:
Container A booked 200m CPU or 20% of node Allocatable capacity,
Container B - 100m CPU or 10%,
For other containers remain 700m CPU or 70% of node Allocatable CPU resource.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-18-00.png" type="" alt=""  /></p>
<p>Let interpret pie slices like Real CPU Usage:
Container A and B use exactly amount of CPU they requested.
What if both wants all the node CPU at the same time?</p>
<p>Both containers wants all the node CPU at the same time:
Containers will compete for the same CPU but
K8s will distribute available CPU to them proportionally by their requests (weight).</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-18-46.png" type="" alt=""  /></p>
<p>Use case 1: A is bursting but B is needing now only 100m CPU, so A can use the whole remaining spare CPU, until…</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-19-15.png" type="" alt=""  /></p>
<p>Use case 2: B is also bursting, so K8s will distribute all available CPU sparely and proportionally to their requests between them…</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-19-34.png" type="" alt=""  /></p>
<p>Other use-case: “borrowing”
Container A requested 200m (20% of CPU capacity) but it is using now only 50m (5%).
Container B, requested 100m (10%), but it is bursting now and need all CPU, so it may and is using now what is available: 95%
It uses 10% of it’s request + 70% of unused and unreserved + 15% borrowed from A which is reserved/requested by A but not used now, so 95%.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-20-03.png" type="" alt=""  /></p>
<h2 id="cpu-requests-vs-limits">CPU Requests vs Limits</h2>
<p>Let introduce limits for A and B:
A CPU: Requests = 200m, Limits = 260m
B CPU: Requests = 100m, Limits = 150m
Containers may burst above their requests, but no more then defined limits, beyond which they will be CPU throttled, even if there is a plenty of idle CPU available.
Usage slices above requests up to limits are possible only if there are available unused CPU resources on the node.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-21-06.png" type="" alt=""  /></p>
<h3 id="cpu-limits">CPU Limits</h3>
<p>CPU limits translate into a time quota for CPU usage within a defined accounting period.
CPU Limits = 100m = 0.1 CPU may be translated as:
Use 10% of a single CPU capacity/time during a period, or
Use as much CPU as needed, but only for 10% of the time within a period.
Example:
Use the CPU only for 1 second every 10s, or, closer to reality:
Use the CPU for 10ms in every 100ms period</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-21-42.png" type="" alt=""  /></p>
<h3 id="cpu-limits-vs-throttling">CPU Limits vs Throttling</h3>
<ol>
<li>Application has only small (below quota) CPU bursts each period:</li>
</ol>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-22-16.png" type="" alt=""  /></p>
<ol start="2">
<li>Application has bigger CPU bursts each period, and CPU demand exceeds defined quota, so:</li>
</ol>
<p>CPU Time will be limited to quota
Remained time to execute is transferred to next period.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-22-52.png" type="" alt=""  /></p>
<ul>
<li>CPU Requests!
Always set CPU Requests to realistic and production-like performance testing proven values.
They should reflect normal (not minimal) CPU Usage of application, or even p99 of observed maximum, depending by case.</li>
</ul>
<p>They guarantee requested resources.
They allow spare and proportional to requests resources usage when:
Is needed higher amount of resources then requested,
Resources contention on node,</p>
<ul>
<li>
<p>CPU Limits?
This is tricky, controversial subject in community.
They may cause application’s big, unexpected and unjustified delays, but they also may&hellip; Cap greedy CPU Usage of some buggy crazy noisy neighbor.
So…?
Set them in function of use case.
Setting them requires lots of careful, iterative performance tests!
In production set them very high or remove at all for response time critical workloads, especially when there are lots of available CPUs.
Set them higher (30-50% to 200-300%) then CPU Request for other cases or when you are enforced to set them.</p>
</li>
<li>
<p>Set Requests = Limits for narrow use cases, like:
Performance Tests to determine workloads sizing and demands
When need “Guaranteed QoS” Pod classes
Especially Static CPU Binding use cases (narrow usage)</p>
</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-cpu/assets/index_2025-04-19_11-23-54.png" type="" alt=""  /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Backups With Velero</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-talos/backup/</link>
      <pubDate>Fri, 18 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-talos/backup/</guid>
      <description>&lt;h1 id=&#34;what-do-you-back-up-in-kubernetes&#34;&gt;What Do You Back Up In Kubernetes?&lt;/h1&gt;
&lt;p&gt;It&amp;rsquo;s easy to gloss over backups especially when you are setting up a new cluster, finally getting stuff running on it, and feeling excited that you can now deploy some service and basically automate certificates, ingress, and everything else that used to be a pain to do manually. But as soon as something fails, you will wish you spent a little more time not only thinking about backups, but also documenting step by step how to recover from a disaster.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="what-do-you-back-up-in-kubernetes">What Do You Back Up In Kubernetes?</h1>
<p>It&rsquo;s easy to gloss over backups especially when you are setting up a new cluster, finally getting stuff running on it, and feeling excited that you can now deploy some service and basically automate certificates, ingress, and everything else that used to be a pain to do manually. But as soon as something fails, you will wish you spent a little more time not only thinking about backups, but also documenting step by step how to recover from a disaster.</p>
<p>In Kubernetes, we have two main things to back up: persistent data, and the state of the cluster resources.</p>
<p>With Velero, backups in Kubernetes are EASY. Just do it now, before you put stuff on the cluster, and write down the steps to recover. Why not?</p>
<h1 id="etcd-backups-in-talos-linux">etcd Backups In Talos Linux</h1>
<p>Before we dive into Velero, I want to mention that theoretically, you could just use Velero for backups and never worry about etcd. However, it&rsquo;s a best practice to backup etcd and it&rsquo;s easy to do, there&rsquo;s no reason not to have another recovery point.</p>
<p>What is etcd (I think it&rsquo;s pronounced &ldquo;et-see-dee&rdquo;)? This is the database that the control plane nodes use to keep track of any and all resources in the cluster. It&rsquo;s the cluster database that keeps track of every resource, status, etc. in the cluster. This includes every node, pod, deployment, secret, etc. If you had a catastrophe and lost all your cluster resource state, or had something get corrupted, you should be able to restore the etcd database and get back to the exact state it was in when the backup was taken.</p>
<p>Side note: This tracks real time data so whatever storage the etcd cluster is running on (the actual disks the control plane node stores this data on) needs to be pretty fast. If it&rsquo;s slow, you can run into issues where <code>kubectl</code> commands are very slow, or stuff stops working as expected in the cluster. Maybe this note belongs in the Talos Linux setup section, but here we are.</p>
<p>Talos Linux, unlike a traditional kubeadm install, does not run etcd as a static pod. Instead, it&rsquo;s hidden behind the Talos API, meaning you have to use <code>talosctl</code> to manage it: <a href="https://www.talos.dev/v1.8/advanced/etcd-maintenance/">https://www.talos.dev/v1.8/advanced/etcd-maintenance/</a></p>
<h2 id="how-to-back-up-etcd-in-talos">How To Back Up etcd In Talos</h2>
<p><a href="https://www.talos.dev/v1.8/advanced/disaster-recovery/#backup">https://www.talos.dev/v1.8/advanced/disaster-recovery/#backup</a></p>
<ul>
<li><code>talosctl -n &lt;IP&gt; etcd snapshot /backup_path/etcd.snapshot.$(/bin/date +%Y%m%d)</code>
<ul>
<li>You need to pass any one of the control plane node IPs to the <code>-n</code> flag</li>
</ul>
</li>
</ul>
<p>You could automate this somewhere so you have regular backups. Highly recommended! If you do a bash script, you&rsquo;ll need to pass the <code>--talosconfig /path/to/talosconfig</code> option.</p>
<p>If you&rsquo;re currently in a Disaster Recovery scenario and the snapshot command is failing, copy the etcd database directly: <a href="https://www.talos.dev/v1.8/advanced/disaster-recovery/#disaster-database-snapshot">https://www.talos.dev/v1.8/advanced/disaster-recovery/#disaster-database-snapshot</a></p>
<ul>
<li><code>talosctl -n &lt;IP&gt; cp /var/lib/etcd/member/snap/db .</code></li>
</ul>
<h2 id="how-to-recover-etcd-in-talos">How To Recover etcd In Talos</h2>
<p><a href="https://www.talos.dev/v1.8/advanced/disaster-recovery/#recovery">https://www.talos.dev/v1.8/advanced/disaster-recovery/#recovery</a></p>
<p>I haven&rsquo;t done this process yet (and hopefully I don&rsquo;t need it). Follow the official guide.</p>
<h1 id="whats-velero">What&rsquo;s Velero?</h1>
<p><a href="https://velero.io/">https://velero.io/</a></p>
<p>Velero is an open source DR and backup/migration/recovery tool for Kubernetes. It backs up all cluster resources (or whatever you specify) and can be used for backup, restore, disaster recovery and migrating to new clusters. It&rsquo;s also capable of backing up PVs. Backups are stored on external endpoints (mostly S3 capable services including MinIO). Backup/restore is managed through CRDs. There are two components: cluster resources and a client side utility <code>velero</code>.</p>
<p>Don&rsquo;t trust their blog to be up to date because the current release according to the blog is 1.11, released April 26, 2023, although I&rsquo;m currently running 1.15 which was released November 5, 2024.</p>
<p>Get an accurate changelog right from the source: <a href="https://github.com/vmware-tanzu/velero/releases">https://github.com/vmware-tanzu/velero/releases</a></p>
<h1 id="velero-installationsetup">Velero Installation/Setup</h1>
<p>I&rsquo;ll walk through installing Velero with some options you might want to consider. Then I&rsquo;ll dive into running a backup and restore manually, scheduling backups, and backing up persistent volumes with Kopia.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Access to a Kubernetes cluster, v1.16 or later, with DNS and container networking enabled. For more information on supported Kubernetes versions, see the Velero <a href="https://github.com/vmware-tanzu/velero#velero-compatibility-matrix">compatibility matrix</a>.</li>
<li><code>kubectl</code> installed locally</li>
<li>Object storage. I&rsquo;m running MinIO in my lab. You could use MinIO, Ceph, AWS S3, Backblaze B2 (S3 API), or any other object storage you prefer.</li>
</ul>
<h2 id="installing-velero---client-side">Installing Velero - Client Side</h2>
<p><a href="https://velero.io/docs/v1.15/basic-install/">https://velero.io/docs/v1.15/basic-install/</a></p>
<ul>
<li>Install the <code>velero</code> client utility
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">VERSION</span><span class="o">=</span>v1.15.0
</span></span><span class="line"><span class="cl">curl -LJO https://github.com/vmware-tanzu/velero/releases/download/<span class="nv">$VERSION</span>/velero-<span class="nv">$VERSION</span>-linux-amd64.tar.gz
</span></span><span class="line"><span class="cl">tar zxvf velero-<span class="nv">$VERSION</span>-linux-amd64.tar.gz
</span></span><span class="line"><span class="cl">install -m <span class="m">755</span> velero-<span class="nv">$VERSION</span>-linux-amd64/velero /usr/local/bin/velero
</span></span><span class="line"><span class="cl">rm -rf velero-<span class="nv">$VERSION</span>-linux-amd64
</span></span><span class="line"><span class="cl">rm -rf velero-<span class="nv">$VERSION</span>-linux-amd64.tar.gz
</span></span><span class="line"><span class="cl">velero version
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<h2 id="installing-velero---server-side">Installing Velero - Server Side</h2>
<p>There are two installation methods, using the Helm chart or using the <code>velero</code> utility. Let&rsquo;s use <code>velero</code> CLI since the Helm method seems complicated based on what I can find in the docs.</p>
<h3 id="minio-setup">MinIO Setup</h3>
<p>We need to start with an access key for the S3 backup target. I&rsquo;m using MinIO: <a href="https://velero.io/docs/v1.15/contributions/minio/">https://velero.io/docs/v1.15/contributions/minio/</a></p>
<ul>
<li>Create a bucket <code>velero-talos</code></li>
<li>Create a group <code>velero-svc</code></li>
<li>Create a service account user <code>velero</code> and add to <code>velero-svc</code> group</li>
<li>Create a policy <code>velero-rw</code>. Raw Policy:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Version&#34;</span><span class="p">:</span> <span class="s2">&#34;2012-10-17&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Statement&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Effect&#34;</span><span class="p">:</span> <span class="s2">&#34;Allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Action&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;s3:ListBucket&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;s3:PutObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;s3:DeleteObject&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;s3:GetObject&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;Resource&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;arn:aws:s3:::velero-talos&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;arn:aws:s3:::velero-talos/*&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Assign that policy to the <code>velero-svc</code> group</li>
<li>Create an access key on the <code>velero</code> user</li>
<li>Create a file named <code>minio-access-key.txt</code>, replacing the values from your access key</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[default]</span>
</span></span><span class="line"><span class="cl"><span class="na">aws_access_key_id</span> <span class="o">=</span> <span class="s">minio</span>
</span></span><span class="line"><span class="cl"><span class="na">aws_secret_access_key</span> <span class="o">=</span> <span class="s">minio123</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Decide what you think is best for storing this file or the credentials somewhere  securely. I would suggest using SOPS, but I will leave the implementation up to you.</li>
</ul>
<h3 id="install-using-velero-utility">Install Using <code>velero</code> Utility</h3>
<ul>
<li>Arguments reference:
<ul>
<li><code>--provider</code> - Use the AWS provider which is also used for any generic S3 object storage, including MinIO</li>
<li><code>--secret-file</code> - Specifies the secrets to use for authentication against the MinIO storage</li>
<li><code>--plugins</code> - Required for Velero to connect to an S3 backend</li>
<li><code>--bucket</code> - specifies the name of the bucket on the S3 backend</li>
<li><code>--use-volume-snapshots=true</code> - Enables volume snapshots</li>
<li><code>--backup-location-config</code> - Configures the connection URL and options for the S3 backend</li>
<li><code>--features=EnableCSI</code> - Enables Velero to use a built in CSI snapshot driver, such as democratic-csi and take snapshots using a volumesnapshotclass. To be clear, Velero would create a volumesnapshot and it would be stored wherever democratic-csi stores snapshots (in our case that&rsquo;s in another dataset on the TrueNAS instance). This is the easy button if you already have CSI snapshots enabled, but the alternative is using Kopia and storing snapshots on the MinIO backend.</li>
</ul>
</li>
<li>Install:
<ul>
<li><code>velero install --provider aws --secret-file minio-access-key.txt --plugins velero/velero-plugin-for-aws:v1.11.0 --bucket velero --use-volume-snapshots=true --backup-location-config region=us-east-1,s3ForcePathStyle=&quot;true&quot;,s3Url=http://192.168.1.35:9000 --features=EnableCSI</code>
<ul>
<li>You may need to update the plugin version and s3URL.</li>
<li>Optionally, set <code>use-volume-snapshots=false</code> if you don&rsquo;t want to back up PVs.</li>
</ul>
</li>
</ul>
</li>
<li>Verify:
<ul>
<li><code>kubectl get all -n velero</code></li>
<li><code>kubectl -n velero get pod -l deploy=velero</code></li>
<li>Check logs, in particular to verify that Velero can reach the backup location
<ul>
<li><code>kubectl -n velero logs -l deploy=velero</code></li>
</ul>
<pre tabindex="0"><code>level=info msg=&#34;BackupStorageLocations is valid, marking as available&#34;
</code></pre></li>
</ul>
</li>
<li>Post install:
<ul>
<li>If you are using democratic-csi for snapshots, you also need to add a label on the VolumeSnapshotClass to let Velero know which one to use by default. This must only be set on 1 volumeSnapshotClass
<ul>
<li><code>kubectl edit volumesnapshotclass truenas-iscsi</code></li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">velero.io/csi-volumesnapshot-class</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>If you&rsquo;re ADDING this funtionality after you&rsquo;ve already installed Velero, you just need to modify the server deployment and also enable on the client: velero.io/csi-volumesnapshot-class: &ldquo;true&rdquo;
<ul>
<li>Server:
<ul>
<li><code>kubectl edit -n velero deploy/velero</code></li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">--<span class="l">features=EnableCSI</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Client:
<ul>
<li><code>velero client config set features=EnableCSI</code></li>
<li><code>velero client config get features</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="testing">Testing</h2>
<p>I&rsquo;ll go through a specific example that should work to follow along if you&rsquo;ve followed the series up to now. Otherwise, there are some good examples in the docs that you can reference: <a href="https://velero.io/docs/v1.15/examples/">https://velero.io/docs/v1.15/examples/</a></p>
<h3 id="manual-backuprestore">Manual Backup/Restore</h3>
<p>Let&rsquo;s back up the traefik namespace:</p>
<ul>
<li><code>velero backup create traefik-backup --include-namespaces traefik</code></li>
<li>Verify: <code>velero backup describe traefik-backup</code>
<ul>
<li>Looking for Phase: Complete</li>
</ul>
</li>
<li>Test a DR scenario:
<ul>
<li>Blow away the traefik namespace: <code>kubectl delete ns traefik</code></li>
<li>Restore traefik-backup with Velero: <code>velero restore create --from-backup traefik-backup</code></li>
<li>Verify: <code>kubectl get ns</code> or <code>velero restore describe traefik-backup-20241130181633</code> (get the restore name from the previous restore command)</li>
</ul>
</li>
</ul>
<p>Create a full backup: <code>velero backup create full-20241130</code> - If you don&rsquo;t specify namespaces, etc. then everything gets backed up.</p>
<h2 id="create-a-backup-schedule">Create A Backup Schedule</h2>
<p>I like to do daily full backups at 7am. TTL is the expiration time for each backup created, 720h is 30 days. So I get daily backups that fall off after 30 days.</p>
<ul>
<li>Create schedule: <code>velero schedule create daily-full --schedule &quot;0 7 * * *&quot; --ttl 720h</code></li>
<li>Verify: <code>velero get schedules</code></li>
</ul>
<p>Alternatively, you can use a manifest to define your backup schedule. You may not want to include certain namespaces in your daily backups since most likely you won&rsquo;t need/want to restore namespaces like <code>kube-system</code>, <code>kube-public</code>, and <code>kube-node-lease</code>. Let&rsquo;s look at defining a backup schedule using a manifest:</p>
<ul>
<li>Create the Schedule manifest, <code>schedule.yaml</code>:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">velero.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Schedule</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">daily-skipping-system-namespaces</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 12 * * *&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">skipImmediately</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">excludedNamespaces</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">kube-public</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">kube-system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">kube-node-lease</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Apply the schedule: <code>kubectl apply -f schedule.yaml</code></li>
<li>Verify: <code>velero get schedules</code></li>
</ul>
<h3 id="restore-from-scheduled-backup">Restore From Scheduled Backup</h3>
<ul>
<li><code>velero restore create --from-schedule SCHEDULE_NAME</code></li>
</ul>
<h2 id="restores-in-general">Restores In General</h2>
<p>See previous examples for specific restore commands. The basic syntax will be <code>velero restore create [RESTORE_NAME] [--from-backup BACKUP_NAME | --from-schedule SCHEDULE_NAME] [flags]</code></p>
<p>You can restore from a manually created backup or a scheduled backup. You can also restore specific items from a backup, so let&rsquo;s say you just want to restore your secret key to your sealed secrets from a daily-full scheduled backup, you can use flags to specify exactly what from that backup you want to restore. For example:</p>
<ul>
<li><code>velero restore create restore-sealed-secret-key --from-schedule daily-full --selector sealedsecrets.bitnami.com/sealed-secrets-key=active</code></li>
</ul>
<h2 id="back-up-pvcs-using-democratic-csi-snapshots">Back Up PVCs Using democratic-csi Snapshots</h2>
<p>If you enabled snapshots in democratic-csi and also enabled snapshos in Velero as described above, then anything you back up with Velero that includes a PVC will be snapshotted. I tested this by deploying a pod/deployment in the default namespace, attached to a test PVC, then ran a Velero backup against it.</p>
<ul>
<li><code>velero backup create full-backup</code></li>
</ul>
<h2 id="kopia">Kopia</h2>
<p>Velero uses Kopia (as a plugin) to copy existing snapshots to your S3 backend. This means that if you&rsquo;re not already creating &ldquo;regular&rdquo; snapshots already outside of Velero, you need to have EnableCSI enabled and working already so that a VolumeSnapshot is created, which can then be copied out to S3. In a homelab environment, this may be redundant if your MinIO storage is backed by the same storage you&rsquo;re using for snapshots, but for the sake of completeness I will run through how we can make this work and test it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes External Services</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-external-services/</link>
      <pubDate>Fri, 18 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-external-services/</guid>
      <description>&lt;h1 id=&#34;about-external-services&#34;&gt;About External Services&lt;/h1&gt;
&lt;p&gt;External services are anything you want to route traffic to that does not live in your Kubernetes cluster. For example, you might be running MinIO in a VM and accessing it via IP address, but you want to basically reverse proxy to it. You can use Kubernetes Ingress to act as a reverse proxy to pretty much anything, even if it doesn&amp;rsquo;t live within your cluster.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s pretty straightforward to do this, and you just need to create a few resources: &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;EndpointSlice&lt;/code&gt; and &lt;code&gt;Ingress&lt;/code&gt;. For the sake of organization, I like to use a separate namespace to separate any external service resources. If you&amp;rsquo;re doing this, just create a new namespace as your first step.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="about-external-services">About External Services</h1>
<p>External services are anything you want to route traffic to that does not live in your Kubernetes cluster. For example, you might be running MinIO in a VM and accessing it via IP address, but you want to basically reverse proxy to it. You can use Kubernetes Ingress to act as a reverse proxy to pretty much anything, even if it doesn&rsquo;t live within your cluster.</p>
<p>It&rsquo;s pretty straightforward to do this, and you just need to create a few resources: <code>Service</code>, <code>EndpointSlice</code> and <code>Ingress</code>. For the sake of organization, I like to use a separate namespace to separate any external service resources. If you&rsquo;re doing this, just create a new namespace as your first step.</p>
<h1 id="minio-example">MinIO Example</h1>
<p>I have MinIO running outside my Kubernetes cluster. For now, I just want basically a reverse proxy in front of it, but I want to use Kubernetes Ingress since I already have that running and configured to get TLS certificates from Let&rsquo;s Encrypt.</p>
<ul>
<li>Optionally, create a new <code>Namespace</code>. I&rsquo;m using &ldquo;externalservices&rdquo;
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Ceate an <code>EndpointSlice</code>. This object is the newer replacement for <code>Endpoints</code> which are now managed by <code>EndpointSlice</code>, so it&rsquo;s no longer recommended to manually create <code>Endpoints</code> directly. <code>EndpointSlice</code> requires a name and a label that matches the service name you will be using. The label&rsquo;s key is <code>kubernetes.io/service-name</code>. <code>namespace</code> is optional but recommended. Then you just specify address type (IPv4 or IPv6), ports, and the actual endpoint with the IP address. Here&rsquo;s an example of my EndpointSlice manifest where MinIO listening at 192.168.1.35 on port 80. I&rsquo;m using a namespace called &ldquo;externalservices&rdquo; and the service name is minio-service.
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">discovery.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">EndpointSlice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/service-name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">addressType</span><span class="p">:</span><span class="w"> </span><span class="l">IPv4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">addresses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;192.168.1.35&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ready</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Next, create a ClusterIP <code>Service</code> without a selector. Having no selectors allows the service to be attached directly to an Endpoint that you create instead of only being able to attach to a pod. The way it knows how to attach to the EndpointSlice is based on the label you used in the EndpointSlice manifest which has to exactly match the name you use in this service (in this case minio-service). This service just maps port 80 to targetPort 80, and we will handle HTTP to HTTPS redirection, plus TLS termination in the Ingress resource.
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Finally, create an <code>Ingress</code>. Here&rsquo;s a basic example using a subdomain, using a Let&rsquo;s Encrypt ClusterIssuer, and using traefik as the ingress class.
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">networking.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service-ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cert-manager.io/cluster-issuer</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;letsencrypt-staging&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">traefik.ingress.kubernetes.io/router.entrypoints</span><span class="p">:</span><span class="w"> </span><span class="l">websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ingressClassName</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">minio.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">tls-example-com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">minio.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">port</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">number</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">pathType</span><span class="p">:</span><span class="w"> </span><span class="l">Prefix</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p>TLDR; Put it all together into a single manifest:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">discovery.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">EndpointSlice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/service-name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">addressType</span><span class="p">:</span><span class="w"> </span><span class="l">IPv4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">addresses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;192.168.1.35&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ready</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">networking.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service-ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">externalservices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cert-manager.io/cluster-issuer</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;letsencrypt-staging&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">traefik.ingress.kubernetes.io/router.entrypoints</span><span class="p">:</span><span class="w"> </span><span class="l">websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ingressClassName</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">minio.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">tls-example-com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">minio.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minio-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">port</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">number</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">pathType</span><span class="p">:</span><span class="w"> </span><span class="l">Prefix</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Apply <code>kubectl apply -f minio-external-service.yaml</code></li>
<li>Shortly after deploying the resources, update DNS or your local hosts file and point to minio.example.com. It may take a bit to generate a signed certificate from the issuer, but you should still be able to route to your endpoint through the Ingress and verify that it&rsquo;s working.</li>
</ul>
<p>That&rsquo;s it. That&rsquo;s the end. You are now done.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes: Certificates With cert-manager and Let&#39;s Encrypt</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-cert-manager/</link>
      <pubDate>Fri, 18 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-cert-manager/</guid>
      <description>&lt;h1 id=&#34;what-is-cert-manager&#34;&gt;What is cert-manager?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://cert-manager.io/&#34;&gt;https://cert-manager.io/&lt;/a&gt;&lt;br&gt;
cert-manager is a certificate controller for Kubernetes which is capable of handling all your certificate needs. This of course includes acquiring and automatically renewing certificates from Let&amp;rsquo;s Encrypt, but it can also be used as a local CA (certificate authority) for private certificates between services, etc.&lt;/p&gt;
&lt;h1 id=&#34;installationsetup&#34;&gt;Installation/Setup&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://cert-manager.io/docs/&#34;&gt;https://cert-manager.io/docs/&lt;/a&gt;&lt;br&gt;
I run piHole internally on my network and also use DNS challenge for internal only hostnames. This means when I request a new certificate and cert-manager attempts to look up the DNS challenge record, it won&amp;rsquo;t be able to query it through my piHole.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="what-is-cert-manager">What is cert-manager?</h1>
<p><a href="https://cert-manager.io/">https://cert-manager.io/</a><br>
cert-manager is a certificate controller for Kubernetes which is capable of handling all your certificate needs. This of course includes acquiring and automatically renewing certificates from Let&rsquo;s Encrypt, but it can also be used as a local CA (certificate authority) for private certificates between services, etc.</p>
<h1 id="installationsetup">Installation/Setup</h1>
<p><a href="https://cert-manager.io/docs/">https://cert-manager.io/docs/</a><br>
I run piHole internally on my network and also use DNS challenge for internal only hostnames. This means when I request a new certificate and cert-manager attempts to look up the DNS challenge record, it won&rsquo;t be able to query it through my piHole.</p>
<p>The solution is to configure cert-manager to specifically use public DNS servers to do the lookup, and that is done by setting <code>dns01-recursive-nameservers</code> and <code>dns01-recursive-nameservers-only</code>.</p>
<p>For that reason, I don&rsquo;t use the &ldquo;Default static install&rdquo; method by just installing the manifest using kubectl. Instead, I use the Helm chart so that I can apply those overrides during the installation.</p>
<p>If you don&rsquo;t need to override the nameservers used for DNS challenge, I would recommend using their <code>Default static install method</code>: <a href="https://cert-manager.io/docs/installation/#default-static-install">https://cert-manager.io/docs/installation/#default-static-install</a></p>
<p>If you&rsquo;re still with me and want to apply using the Helm chart, follow along!</p>
<ul>
<li>Make sure you have Helm version 3 or later: <a href="https://helm.sh/docs/intro/install/">https://helm.sh/docs/intro/install/</a></li>
<li>Add the helm repo:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">helm repo add jetstack https://charts.jetstack.io --force-update
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Install the Helm chart. Note the <code>extraArgs</code> for DNS nameserver settings:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">helm install <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  cert-manager jetstack/cert-manager <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --namespace cert-manager <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --create-namespace <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --version v1.16.1 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --set crds.enabled<span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl">  --set <span class="s1">&#39;extraArgs={--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>If you&rsquo;re doing anything fancy on your network like me, double check that your Kubernetes nodes running cert-manager are able to reach the dns01-recursive-nameservers on port 53</li>
</ul>
</li>
<li>Verify deployment: <code>kubectl get all -n cert-manager</code></li>
<li>(optional) Verify dns01-recursive-nameserver settings were applied: <code>kubectl -n cert-manager describe deploy cert-manager | grep dns01</code></li>
</ul>
<h2 id="verify-installation">Verify Installation</h2>
<p>At this point cert-manager should be running and ready to issue self-signed certificates. We can test that.</p>
<ul>
<li>Test with this file named <code>cert-manager-verify.yaml</code>:
<ul>
<li>Apply: <code>kubectl apply -f cert-manager-verify.yaml</code></li>
<li>Verify: <code>kubectl describe cert -n cert-manager-test</code></li>
<li>Cleanup: <code>kubectl delete -f cert-manager-verify.yaml</code></li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager-test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Issuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test-selfsigned</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager-test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selfSigned</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">selfsigned-cert</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager-test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dnsNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">selfsigned-cert-tls</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">issuerRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">test-selfsigned</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<h1 id="getting-certificates-from-lets-encrypt-using-cloudflare-dns">Getting Certificates From Let&rsquo;s Encrypt (using Cloudflare DNS)</h1>
<p>Now we are ready to hook up Let&rsquo;s Encrypt so we can get certificates that are signed by a trusted authority. It&rsquo;s always a good idea to start with the staging issuer when testing Let&rsquo;s Encrypt so you don&rsquo;t run into rate limits with their production infrastructure.</p>
<p>I&rsquo;m using Cloudflare for my DNS nameservers so these steps will be based on that setup.</p>
<h2 id="getting-a-cloudflare-api-token">Getting A Cloudflare API Token</h2>
<ul>
<li>Create a Kubernetes secret resource with your Cloudflare API token. This allows cert-manager to add custom _acme-challenge records for domain validation. This will be the same API token you use for both Staging and Production certificates, so you only need to create one of these.
<ul>
<li>In Cloudflare, you can generate a new API token by navigating to your user account &gt; My Profile &gt; API Tokens &gt; Create Token. Make sure token permissions include <code>All zones - Zone Settings:Read, Zone:Read, DNS:Edit</code> or you can limit to a specific zone if you have multiple and want to use different API Tokens for different zones.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-cloudflare-api-token-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">api-token</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;api-token-goes-here&gt;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Apply the secret manifest: <code>kubectl apply -f letsencrypt-cloudflare-api-token-secret.yaml</code></li>
</ul>
<h2 id="deploying-a-cert-manager-issuer---staging">Deploying A cert-manager Issuer - Staging</h2>
<p>cert-manager has two types of issuers: Issuer and ClusterIssuer. Issuer is used for more fine-grained control, it can be placed within a specific namespace, etc. ClusterIssuer, as the name implies, can be accessed across the whole Kubernetes cluster. Since I&rsquo;m not getting too complicated, and also want to be able to easily utilize cert-manager from multiple namespaces, I&rsquo;m using ClusterIssuer. If you wanted to use Issuer, just pick a namespace and the steps are almost identical to this.</p>
<p>It&rsquo;s strongly recommended to test with Let&rsquo;s Encrypt&rsquo;s staging server to avoid any rate limiting. You are far less likely to run into rate limits during testing while using the staging issuer, and once you get this working it&rsquo;s simply a matter of updating the ACME server URL to the production issuer and changing the name on the Issuer/ClusterIssuer (or you can run boht at the same time).</p>
<p><a href="https://letsencrypt.org/docs/staging-environment/">https://letsencrypt.org/docs/staging-environment/</a></p>
<p>You can use any valid email address. When you request a new cert, the email address is registered with that particular request, and that&rsquo;s where they will send renewal/expiry notices.</p>
<ul>
<li>Create a manifest file for the staging issuer <code>letsencrypt-staging-clusterissuer.yaml</code>. Update your email address:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-staging</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">acme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># The ACME server URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://acme-staging-v02.api.letsencrypt.org/directory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Email address used for ACME registration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">email</span><span class="p">:</span><span class="w"> </span><span class="l">mail@example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Name of a secret used to store the ACME account private key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privateKeySecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-staging-issuer-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Enable the DNS-01 challenge provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">solvers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">dns01</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cloudflare</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">apiTokenSecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-cloudflare-api-token-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">api-token</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Apply the staging issuer: <code>kubectl apply -f letsencrypt-staging-clusterissuer.yaml</code></li>
<li>Verify your ClusterIssuer exists (or Issuer if that&rsquo;s what you&rsquo;re using): <code>kubectl get clusterissuer</code></li>
</ul>
<h2 id="request-a-real-staging-certificate-from-lets-encrypt">Request A Real (Staging) Certificate From Let&rsquo;s Encrypt</h2>
<p>Now we need to deploy a Certificate resource (again) to test our Let&rsquo;s Encrypt staging ClusterIssuer. This is similar to the verify manifest like before, but only needs to include the Certificate resource. However, we also need to update a couple things, notably adding the field Certificate.spec.issuerRef.kind and setting its value to ClusterIssuer (see <a href="https://cert-manager.io/docs/usage/certificate/">https://cert-manager.io/docs/usage/certificate/</a>)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-staging-certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dnsNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">justkidding.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-staging-cert-tls</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">issuerRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-staging</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Apply the Certificate manifest: <code>kubectl apply -f letsencrypt-staging-certificate.yaml</code></li>
<li>Check the Certificate is ready: <code>kubectl get certificate</code> - this will show Ready=False until it&rsquo;s fully issued</li>
<li>Be patient. This can sometimes be quick, but sometimes can take a while. My most recent attempt took 38 minutes (see Troubleshooting section below to dive into what&rsquo;s happening during the process).</li>
</ul>
<p>Once you get a certificate issued using the staging issuer, you are ready to move to production!</p>
<h2 id="deploying-a-cert-manager-issuer---production">Deploying A cert-manager Issuer - Production</h2>
<p>Follow the same steps as in Staging, but using the production ACME URL and probably not using the name &ldquo;staging&rdquo;.</p>
<ul>
<li>Create a manifest file for the production issuer <code>letsencrypt-production-clusterissuer.yaml</code>. Update your email address:
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">acme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># The ACME server URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://acme-v02.api.letsencrypt.org/directory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Email address used for ACME registration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">email</span><span class="p">:</span><span class="w"> </span><span class="l">mail@example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Name of a secret used to store the ACME account private key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privateKeySecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-production-issuer-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Enable the DNS-01 challenge provider</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">solvers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">dns01</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cloudflare</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">apiTokenSecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-cloudflare-api-token-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">api-token</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Apply the staging issuer: <code>kubectl apply -f letsencrypt-production-clusterissuer.yaml</code></li>
<li>Verify your ClusterIssuer exists (or Issuer if that&rsquo;s what you&rsquo;re using): <code>kubectl get clusterissuer</code></li>
</ul>
<h2 id="request-a-real-production-certificate-from-lets-encrypt">Request A Real (Production) Certificate From Let&rsquo;s Encrypt</h2>
<p>This is exactly the same as for staging, except we make the request from the production ClusterIssuer (the last line in the yaml file says which ClusterIssuer to use).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-production-certificate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dnsNames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">justkidding.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-production-cert-tls</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">issuerRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-production</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Apply the Certificate manifest: <code>kubectl apply -f letsencrypt-production-certificate.yaml</code></li>
<li>Check the Certificate is ready: <code>kubectl get certificate</code> - this will show Ready=False until it&rsquo;s fully issued</li>
<li>Be patient. This can sometimes be quick, but sometimes can take a while depending on a lot of factors. See Troubleshooting below for some tips.</li>
</ul>
<h1 id="troubleshooting-certificate-requests">Troubleshooting Certificate Requests</h1>
<ul>
<li>Definitive guide: <a href="https://cert-manager.io/docs/troubleshooting/">https://cert-manager.io/docs/troubleshooting/</a>
<ul>
<li>Also for Let&rsquo;s Encrypt: <a href="https://cert-manager.io/docs/troubleshooting/acme/">https://cert-manager.io/docs/troubleshooting/acme/</a></li>
</ul>
</li>
<li><code>kubectl get certificaterequests</code></li>
<li><code>kubectl describe certificaterequests</code></li>
<li><code>kubectl get order</code></li>
<li><code>kubectl describe order</code></li>
<li><code>kubectl get challenge</code></li>
<li><code>kubectl describe challenge</code>
<ul>
<li>This level will tell you if it&rsquo;s stuck waiting for DNS challenge (basically it&rsquo;s waiting for the _acme-challenge record to be added and validated from the cert-manager service. This is where sometimes you can get timeouts if your network doesn&rsquo;t allow cert-manager to query public DNS servers to check).</li>
<li>Troubleshooting steps I&rsquo;ve had here were basically verifying that the DNS record exists in Cloudflare while it&rsquo;s waiting. If it is, wait a while to see if it eventually solves the challenge. If not, you might need to dive deeper into dns01-recursive-nameserver settings (see above) or otherwise make sure your internal DNS resolver is able to query the new record.</li>
<li>In the output, look for Challenge.Spec.Key which is the value you should see in the _acme-challenge TXT record</li>
</ul>
</li>
<li>You can also check the logs on the cert-manager container
<ul>
<li>Find the pod name: <code>kubectl get po -n cert-manager</code></li>
<li><code>kubectl logs cert-manager-556766675f-pt123 -n cert-manager</code></li>
</ul>
</li>
<li>See cert-manager issue for more discussion: <a href="https://github.com/cert-manager/cert-manager/issues/5917">https://github.com/cert-manager/cert-manager/issues/5917</a></li>
</ul>
<h1 id="what-about-ingress">What About Ingress?</h1>
<p>I&rsquo;ll revisit this topic after we get to Ingress Controllers (using Traefik) and how to get certificates from Let&rsquo;s Encrypt. It&rsquo;s significantly easier than this, and since we already have this production ClusterIssuer in place, you basically just add a line in your Ingress resource to use this ClusterIssuer to attach a certificate and cert-manager does the rest!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>OpenEBS Replicated Storage Mayastor</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-talos/storage/</link>
      <pubDate>Fri, 18 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-talos/storage/</guid>
      <description>&lt;h1 id=&#34;intro-and-prerequisites&#34;&gt;Intro and Prerequisites&lt;/h1&gt;
&lt;p&gt;In a previous post, I mentioned that I struggled to get OpenEBS working in Talos and instead went with democratic-csi. In recent weeks, I decided to revisit this and figure out how to get OpenEBS replicated storage working in order to evaluate replicated storage in my cluster. I now have multiple disks that I can dedicate to my Kubernetes cluster and wanted to avoid the issue with the single point of failure using a TrueNAS VM for democratic-csi.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="intro-and-prerequisites">Intro and Prerequisites</h1>
<p>In a previous post, I mentioned that I struggled to get OpenEBS working in Talos and instead went with democratic-csi. In recent weeks, I decided to revisit this and figure out how to get OpenEBS replicated storage working in order to evaluate replicated storage in my cluster. I now have multiple disks that I can dedicate to my Kubernetes cluster and wanted to avoid the issue with the single point of failure using a TrueNAS VM for democratic-csi.</p>
<p>If you are following along, I will assume you are familiar with deploying Talos Linux itself and have talosctl installed with an existing cluster running. If you need more details on how to do that, check out <a href="https://blog.dalydays.com/post/kubernetes-homelab-series-part-1-talos-linux-proxmox/">https://blog.dalydays.com/post/kubernetes-homelab-series-part-1-talos-linux-proxmox/</a>.</p>
<h1 id="dedicated-storage-node">Dedicated Storage Node</h1>
<p>It&rsquo;s not absolutely necessary to use a dedicated storage node. I&rsquo;m doing this because I want to pass a disk directly to a VM for storage on each physical host and want to keep storage somewhat isolated from other worker nodes, and I can spare the few extra resources to dedicate to this purpose. If you want to use existing worker nodes, just follow this process for your existing nodes instead of creating new ones.</p>
<h2 id="create-new-talos-nodes">Create New Talos Node(s)</h2>
<ul>
<li>Create a VM in Proxmox with 4GB RAM and 4 vCPU cores (2GB RAM is not enough due to the fact that you will be enabling hugepages which takes up 2GB and you would see oom-kills otherwise. You also need a dedicated CPU core just for the io-engine, along with all the other stuff that runs. I tried with 2vCPU and it wouldn&rsquo;t schedule the io-engine pod due to insufficient resources). I named my first one talos-storage-1
<ul>
<li>Attach a Talos ISO to the CD ROM and boot from it</li>
<li>Get the IP address from the node</li>
</ul>
</li>
<li>Install Talos using the worker.yaml template used for other worker nodes (you may want to get a current or updated version of Talos from the image factory):
<ul>
<li><code>talosctl apply-config --insecure -n 10.0.50.135 --file _out/worker.yaml</code></li>
</ul>
</li>
<li>Apply a patch to set a static IP and node label, e.g.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ./patches/storage1.patch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">machine</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">talos-storage-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">interfaces</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">deviceSelector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">busPath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">addresses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="m">10.0.50.31</span><span class="l">/24</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">routes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">network</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="l">/0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">gateway</span><span class="p">:</span><span class="w"> </span><span class="m">10.0.50.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nameservers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">192.168.1.22</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>talosctl patch mc -n 10.0.50.135 --patch @patches/storage1.patch</code>
<ul>
<li>I&rsquo;m having trouble here with the node name changing, and I have to manually delete the random name from the cluster:</li>
<li>e.g. <code>kubectl delete node talos-lry-si8</code></li>
<li>Also you might need to delete the label <code>openebs.io/nodename=</code> if you already have openebs running and are adding/changing nodes
<ul>
<li><code>kubectl edit node talos-storage-1</code> and change the value to the current node name</li>
</ul>
</li>
</ul>
</li>
<li>Apply a patch to set some machine config stuff for OpenEBS which includes hugepages, a nodeLabel for where mayastor engine should run, and the <code>/var/local</code> bind mount:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ./patches/openebs.patch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">machine</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sysctls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">vm.nr_hugepages</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1024&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodeLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">openebs.io/engine</span><span class="p">:</span><span class="w"> </span><span class="l">mayastor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extraMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">/var/local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">bind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">/var/local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">options</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">rbind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">rshared</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">rw</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>talosctl patch mc -n 10.0.50.31 --patch @patches/openebs.patch</code></li>
<li>If you have an additional disk to use with OpenEBS, you&rsquo;ll need to pass it directly to the Talos node VM. I&rsquo;m using Proxmox
<ul>
<li>SSH into the Proxmox host and find the disk ID to be passed. I just run <code>ls -lh /dev/disk/by-id/ and get the root disk (not containing any &quot;_1&quot; or &quot;_part*&quot;, for example </code>/dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_2TB_S59CNM0W635077P`)</li>
<li>Pass the disk directly to the Talos VM, where 511 is the VM ID and assuming you only have 1 disk already on scsi0: <code> qm set 511 -scsi1 /dev/disk/by-id/nvme-Samsung_SSD_970_EVO_Plus_2TB_S59CNM0W635077P</code></li>
<li>Checking the hardware tab on VM 511 in Proxmox, you should see this new disk. Double click it and make sure to check &ldquo;Advanced&rdquo;, &ldquo;Discard&rdquo;, and &ldquo;SSD emulation&rdquo;
<ul>
<li>If you see orange on these settings, you will need to shut down the VM, then power it back on for the changes to apply. Rebooting won&rsquo;t do it.</li>
</ul>
</li>
<li>Now that the disk has been added, look for it with talosctl: <code>talosctl get disks -n 10.0.50.31</code>
<ul>
<li>In my case I see a disk named <code>sdb</code> which is 2.0TB with model &ldquo;QEMU HARDDISK&rdquo;</li>
</ul>
</li>
<li>Mount the disk to be passed to containers with appropriate privileges. This is required for openebs-io-engine to access the extra disk.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ./patches/mount-sdb.patch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">machine</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">disks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">device</span><span class="p">:</span><span class="w"> </span><span class="l">/dev/sdb</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Apply: <code>talosctl patch mc -n 10.0.50.31 --patch @patches/mount-sdb.patch</code> - At this point the Talos node will reboot and should come back up healthy in a minute.</li>
<li>View the console or check the dashboard with <code>talosctl dashbord -n 10.0.50.31</code></li>
<li>If you see an error about being unable to mount the disk or the partition being the wrong type, etc. you will need to wipe the disk and create a fresh GPT partition. As of Talos 1.9.0 this can be done with <code>talosctl wipe sdb -n 10.0.50.31</code>, otherwise you would need to do this outside of Talos.
<ul>
<li><code>talosctl wipe disk sdb -n 10.0.50.31</code> - where <code>sdb</code> is the device, you confirmed this right? Confirm using <code>talosctl get disks -n 10.0.50.31</code></li>
<li>Otherwise from Proxmox you could do this: Shut down the VM and do this in proxmox with <code>wipefs /dev/yourdev</code> and then use <code>fdisk /dev/yourdev</code> &gt; <code>g</code> &gt; <code>w</code> (<code>g</code> writes a new GPT table, <code>w</code> writes to disk). Now power Talos back on and it should do its thing.</li>
<li>Yet another option would be to boot into a different Linux ISO on the VM and use a tool like Gparted. Whatever you like best.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="lets-verify-our-disk-mount">Let&rsquo;s Verify Our Disk Mount</h3>
<p>When Talos successfully mounts the extra disk, we should see it listed with <code>lsblk</code> without any partitions. We want to pass the raw disk to OpenEBS. In order to check, run a debug pod on your storage node and check the bind mounts.</p>
<ul>
<li><code>kubectl debug node/talos-storage-1 -it --image=alpine -- /bin/sh</code></li>
<li><code>apk add lsblk</code></li>
<li><code>lsblk</code></li>
<li>Check for the mount, showing the full capacity of your disk.</li>
</ul>
<p>Now repeat this whole process for any other Talos nodes you need. I have 3, so I&rsquo;m doing <code>talos-storage-1</code>, <code>talos-storage-2</code> and <code>talos-storage-3</code>.</p>
<h2 id="worker-nodes-also-need-varlocal-mounted">Worker Nodes Also Need /var/local Mounted</h2>
<p>Certain OpenEBS components run on any node, and this requires all worker nodes to have <code>/var/local</code> mounted.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ./patches/mount-var-local.patch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">machine</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kubelet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">extraMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">destination</span><span class="p">:</span><span class="w"> </span><span class="l">/var/local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">bind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">/var/local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">options</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">rbind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">rshared</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">rw</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>In my case, I applied this to my 3 worker nodes. I don&rsquo;t think a reboot is required, but you could if you wanted to:</p>
<ul>
<li><code>talosctl patch mc -n 10.0.50.21 --patch @patches/mount-var-local.patch</code></li>
<li><code>talosctl patch mc -n 10.0.50.22 --patch @patches/mount-var-local.patch</code></li>
<li><code>talosctl patch mc -n 10.0.50.23 --patch @patches/mount-var-local.patch</code></li>
</ul>
<p>In order to check the other bind mount for <code>/var/local</code>, we have to wait until after deploying OpenEBS because the mount isn&rsquo;t utilized until a pod is deployed with a HostPath volume at or below this path. Specifically, the <code>openebs-io-engine-*</code> Daemonset maps to this path.</p>
<h1 id="installing-openebs">Installing OpenEBS</h1>
<p>This was a pain to figure out. Documentation from OpenEBS is lacking, and so is documentation from Talos on the same topic. Here&rsquo;s what I found to work. You need a privileged namespace, bind mounts on all worker nodes, then DiskPools before you can start testing PVCs.</p>
<h2 id="privileged-namespace">Privileged Namespace</h2>
<p>OpenEBS requires privileges, and the easiest way to handle that is by making the namespace privileged (rather than messing with machine configs).</p>
<ul>
<li>Add a new privileged namespace. The Helm chart wants you to use <code>openebs</code> so do this:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># namespace.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">openebs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pod-security.kubernetes.io/enforce</span><span class="p">:</span><span class="w"> </span><span class="l">privileged</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pod-security.kubernetes.io/warn</span><span class="p">:</span><span class="w"> </span><span class="l">privileged</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pod-security.kubernetes.io/audit</span><span class="p">:</span><span class="w"> </span><span class="l">privileged</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>kubectl apply -f namespace.yaml</code></li>
</ul>
<h2 id="helm-installation">Helm Installation</h2>
<ul>
<li><code>helm repo add openebs https://openebs.github.io/openebs</code></li>
<li><code>helm repo update</code></li>
<li>Grab the values from the Helm chart (<code>helm show values openebs/openebs &gt; values.yaml</code>), or use this. I have already modified the config to disable initContainers which is a known issue with Talos, and disabled local provisioners that I&rsquo;m not interested in.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># values.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">openebs-crds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">csi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumeSnapshots</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">keep</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># Refer to https://github.com/openebs/dynamic-localpv-provisioner/blob/v4.1.2/deploy/helm/charts/values.yaml for complete set of values.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">localpv-provisioner</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rbac</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># Refer to https://github.com/openebs/zfs-localpv/blob/v2.6.2/deploy/helm/charts/values.yaml for complete set of values.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">zfs-localpv</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">zfsLocalPv</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">csi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumeSnapshots</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># Refer to https://github.com/openebs/lvm-localpv/blob/lvm-localpv-1.6.2/deploy/helm/charts/values.yaml for complete set of values.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">lvm-localpv</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">lvmLocalPv</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">csi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumeSnapshots</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># Refer to https://github.com/openebs/mayastor-extensions/blob/v2.7.2/chart/values.yaml for complete set of values.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">mayastor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">csi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">node</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initContainers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">etcd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># -- Kubernetes Cluster Domain</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">clusterDomain</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">localpv-provisioner</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># -- Configuration options for pre-upgrade helm hook job.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">preUpgradeHook</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># -- The container image registry URL for the hook job</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">registry</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># -- The container repository for the hook job</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">bitnami/kubectl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># -- The container image tag for the hook job</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1.25.15&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># -- The imagePullPolicy for the container</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">IfNotPresent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">engines</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">local</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">lvm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">zfs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mayastor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>helm install openebs -n openebs openebs/openebs -f values.yaml</code></li>
<li>Verify: <code>kubectl get po -n openebs</code> and it should look something like this:</li>
</ul>
<pre tabindex="0"><code>NAME                                          READY   STATUS    RESTARTS      AGE
openebs-agent-core-74d4ddc7c5-hjnxl           2/2     Running   0             9m23s
openebs-agent-ha-node-f9bsb                   1/1     Running   0             9m23s
openebs-agent-ha-node-gjdbt                   1/1     Running   0             9m23s
openebs-agent-ha-node-mwjq9                   1/1     Running   0             9m23s
openebs-agent-ha-node-rfjrw                   1/1     Running   0             93s
openebs-api-rest-757d87d4bd-zd2ms             1/1     Running   0             9m23s
openebs-csi-controller-58c7dfcd5b-6jtcq       6/6     Running   0             9m23s
openebs-csi-node-fmmwg                        2/2     Running   0             9m23s
openebs-csi-node-j95f5                        2/2     Running   2 (60s ago)   93s
openebs-csi-node-jxkvq                        2/2     Running   0             9m23s
openebs-csi-node-xtsnt                        2/2     Running   0             9m23s
openebs-etcd-0                                1/1     Running   0             9m23s
openebs-etcd-1                                1/1     Running   0             9m23s
openebs-etcd-2                                1/1     Running   0             9m23s
openebs-io-engine-lb8zr                       2/2     Running   0             9m23s
openebs-localpv-provisioner-657c44878-wjmwr   1/1     Running   0             9m23s
openebs-loki-0                                1/1     Running   0             9m23s
openebs-nats-0                                3/3     Running   0             9m23s
openebs-nats-1                                3/3     Running   0             9m23s
openebs-nats-2                                3/3     Running   0             9m23s
openebs-obs-callhome-8665bb8f6f-4ntrd         2/2     Running   0             9m23s
openebs-operator-diskpool-6d44884f8f-52rrx    1/1     Running   0             9m23s
openebs-promtail-2g6kz                        1/1     Running   0             9m23s
openebs-promtail-d72cx                        1/1     Running   0             93s
openebs-promtail-hsxlc                        1/1     Running   0             9m23s
openebs-promtail-npw7f                        1/1     Running   0             9m23s
</code></pre><ul>
<li>If pods are stuck initializing after a few minutes, start checking logs with <code>openebs-etcd-0</code> since a lot of things depend on that being up before it will initialize.
<ul>
<li>Related to <code>/var/local</code> bind mounts not being added to worker nodes, <code>openebs-etcd-*</code> will have Warning events about &ldquo;FailedMount&rdquo;, stating that a PVC doesn&rsquo;t exist. If you look closely, the path starts with <code>/var/local/...</code> which means you&rsquo;re missing the <code>/var/local</code> bind mount on one or more Talos worker/storage nodes.</li>
<li>The fix in this situation would be to fix the bind mount on the worker nodes, and you could also try a reboot (you can identify the node based on the pod having issues). There&rsquo;s no need to change the OpenEBS deployment.</li>
<li>After fixing it, you might see stale pods with errors. You can terminate pods in an error state, and if they are needed the Deployment or Daemonset responsible for them will recreate them as needed.</li>
</ul>
</li>
</ul>
<h2 id="add-diskpools">Add DiskPool(s)</h2>
<p>I was excited at this point to test with a PVC, but then was confused about why it wouldn&rsquo;t provision anything. The Talos docs feel a bit sparse and seem to imply that now is the time to test with a PVC. But if you pay close attention they only mention testing the local provisioner, adding to my confusion. It turns out you need to add DiskPools, which makes sense in hindsight. If you&rsquo;ve ever used Longhorn there is a similar config needed after the initial install, so that you know what disk capacity you have to work with.</p>
<ul>
<li>Earlier, we mounted that 2TB disk in the talos-storage-1 node. Now we&rsquo;ll use that for our first DiskPool</li>
<li>Get the disk ID by exec-ing into the openebs-io-engine pod
<ul>
<li>Identify one of the io-engine pods: <code>kubectl get po -l app=io-engine -n openebs</code></li>
<li>Exec into the pod: <code>kubectl exec -it openebs-io-engine-jpnrh -c io-engine -n openebs -- bin/sh</code></li>
<li><code>ls -lh /dev/disk/by-id/</code> - grab the one pointing to <code>/dev/sdb</code> in our case, which for me is <code>scsi-0QEMU_QEMU_HARDDISK_drive-scsi1</code></li>
</ul>
</li>
<li>FYI I went with uring instead of aio since it&rsquo;s the new kid on the block</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># diskpool-1.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;openebs.io/v1beta2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">DiskPool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pool-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">openebs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">node</span><span class="p">:</span><span class="w"> </span><span class="l">talos-storage-1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">disks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;uring:///dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1&#34;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>kubectl apply -f diskpool-1.yaml</code></li>
<li>Verify: <code>kubectl get dsp -n openebs</code> - quickly it should change to Created state and Online POOL_STATUS</li>
</ul>
<p>Repeat this process for any/all storage nodes you have. Since I&rsquo;m virtualizing Talos, the disk path is exactly the same on all 3 nodes so I can reuse the config, just updating the pool name and the node name.</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<p>Sorry I can&rsquo;t be a ton of help here since I have only done really limited troubleshooting. If you run into issues with diskpools stuck in Creating status, you will need to describe the dsp or check logs.</p>
<p>What I can say is that I&rsquo;m running a homelab, which means I have 3 different disks, from 3 different manufacturers. Two of them worked in this configuration, but one did not. The disk is fine, it&rsquo;s fairly new, I&rsquo;ve tried wiping it multiple times, multiple ways, but it just would not work with the diskpool. I tried doing it as a bind mount and using /mnt/local/nvme2tb (this one requires adding the volume to the io-engine Daemonset), mounting /dev/sdb, mounting /dev/sdb1, everything I could think of, but it would not create. I rebuilt the Talos node but got the same results. I switched to a different disk and changed nothing else, and it works totally fine. For prosperity, these are the disks I have and which ones worked in this configuration.</p>
<ul>
<li>Samsung 970 EVO Plus 2TB - no problems</li>
<li>Samsung 990 EVO 2TB - no problems</li>
<li>WD BLACK SN770 2TB - no problems</li>
<li>Crucial P3 Plus 2TB (CT2000P3PSSD8) - COULD NOT GET THIS WORKING :(</li>
</ul>
<p>If you are reading this and you know or think you might know why this didn&rsquo;t work, please reach out! I&rsquo;m interested in understanding why this wouldn&rsquo;t work and how I could troubleshoot better.</p>
<h2 id="testing-a-replicated-pvc">Testing A Replicated PVC</h2>
<p>If you are here, you have at least one working diskpool and are ready to test that PVC provisioning works and can be attached to a running pod. Let&rsquo;s test that.</p>
<ul>
<li>Verify diskpools: <code>kubectl get dsp -n openebs</code> - for my 3 storage nodes with 2TB volumes, I&rsquo;m seeing this</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">NAME     NODE              STATE     POOL_STATUS   CAPACITY        USED   AVAILABLE
</span></span><span class="line"><span class="cl">pool-1   talos-storage-1   Created   Online        <span class="m">1998443249664</span>   <span class="m">0</span>      <span class="m">1998443249664</span>
</span></span><span class="line"><span class="cl">pool-2   talos-storage-2   Created   Online        <span class="m">1998443249664</span>   <span class="m">0</span>      <span class="m">1998443249664</span>
</span></span><span class="line"><span class="cl">pool-3   talos-storage-3   Created   Online        <span class="m">1998443249664</span>   <span class="m">0</span>      <span class="m">1998443249664</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Check what storage classes are available: <code>kubectl get sc</code></li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">NAME                     PROVISIONER               RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
</span></span><span class="line"><span class="cl">mayastor-etcd-localpv    openebs.io/local          Delete          WaitForFirstConsumer   <span class="nb">false</span>                  6h15m
</span></span><span class="line"><span class="cl">mayastor-loki-localpv    openebs.io/local          Delete          WaitForFirstConsumer   <span class="nb">false</span>                  6h15m
</span></span><span class="line"><span class="cl">openebs-hostpath         openebs.io/local          Delete          WaitForFirstConsumer   <span class="nb">false</span>                  6h15m
</span></span><span class="line"><span class="cl">openebs-single-replica   io.openebs.csi-mayastor   Delete          Immediate              <span class="nb">true</span>                   6h15m
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>For now, we&rsquo;re interested in testing that <code>openebs-single-replica</code> SC that uses Mayastor, so write this file. Note that this uses the default namespace:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># test-pvc-openebs-single-replica.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PersistentVolumeClaim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-testpvc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">storageClassName</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-single-replica</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accessModes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storage</span><span class="p">:</span><span class="w"> </span><span class="l">10Gi</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Apply it: <code>kubectl apply -f test-pvc-openebs-single-replica.yaml</code></li>
<li>Check PV and PVC:
<ul>
<li><code>kubectl get pv</code></li>
<li><code>kubectl get pvc</code> - you should see a PVC named <code>openebs-testpvc</code> with status Bound and storage class <code>openebs-single-replica</code></li>
</ul>
</li>
<li>Deploy a test pod to attach the PVC to - this pod is also in the default namespace:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># pod-using-testpvc.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">testlogger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">testlogger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">alpine:3.20</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/bin/ash&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;while true; do echo \&#34;$(date) - test log\&#34; &gt;&gt; /mnt/test.log &amp;&amp; sleep 1; done&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">testvol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/mnt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">testvol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">persistentVolumeClaim</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">claimName</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-testpvc</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>kubectl apply -f pod-using-testpvc.yaml</code></li>
<li>Verify it&rsquo;s running: <code>kubectl get po testlogger</code></li>
<li>Exec into the test pod: <code>kubectl exec -it testlogger -- /bin/sh</code></li>
<li>Look at your mounts with <code>df -h /mnt</code>. Since OpenEBS Mayastor uses NVMe-oF you should see that mount path <code>/mnt</code> attached to what looks like a NVMe block device.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">Filesystem                Size      Used Available Use% Mounted on
</span></span><span class="line"><span class="cl">/dev/nvme0n1              9.7G     28.0K      9.2G   0% /mnt
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Hmm. The size doesn&rsquo;t quite add up. 9.7G is close to 10Gi but then 28.0K used does not equal 9.2G available. Hmm&hellip;</li>
<li>Cleanup:
<ul>
<li><code>kubectl delete -f pod-using-testpvc.yaml</code></li>
<li><code>kubectl delete -f test-pvc-openebs-single-replica.yaml</code></li>
</ul>
</li>
</ul>
<h2 id="what-is-a-single-replica-anyway">What Is A &ldquo;Single&rdquo; Replica Anyway?</h2>
<blockquote>
<p>A replica is an exact reproduction of something&hellip;</p></blockquote>
<p>A replica by definition cannot exist without copying something that already exists, which inherently means there must be at least 2. In the context of OpenEBS, a single replica just means you only have ONE copy of the data. It gets randomly assigned to one of the diskpools available. If that disk fails, that data is gone. But we are using OpenEBS for the purpose of replication, so how do we get more replicas??? Follow me.</p>
<ul>
<li>Create a new storage class with 2 replicas (feel free to do 3 or any value at this point):</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># openebs-2-replicas-sc.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">storage.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">StorageClass</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-2-replicas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">nvmf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">repl</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">provisioner</span><span class="p">:</span><span class="w"> </span><span class="l">io.openebs.csi-mayastor</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>kubectl apply -f openebs-2-replicas-sc.yaml</code></li>
<li>Verify: <code>kubectl get sc</code></li>
<li>Test - deploy another test-pvc using the new SC:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># test-pvc-openebs-2-replicas.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PersistentVolumeClaim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-testpvc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">storageClassName</span><span class="p">:</span><span class="w"> </span><span class="l">openebs-2-replicas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accessModes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ReadWriteOnce</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storage</span><span class="p">:</span><span class="w"> </span><span class="l">10Gi</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>kubectl apply -f openebs-2-replicas.yaml</code></li>
<li>Test with a pod, using the same pod from earlier - <code>kubectl apply -f pod-using-testpvc.yaml</code></li>
<li>Exec into the test pod: <code>kubectl exec -it testlogger -- /bin/sh</code></li>
<li>Check your mounted disk: <code>df -h /mnt</code></li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>I&rsquo;m going to stop here. I didn&rsquo;t go into any details about how to check which diskpools hold the replica(s) for the PV, but I assume there is a way to do that. I also did not look at how to recover in case there is a diskpool or storage node failure. Assuming you had 3 replicas, and one went down, there would be no data loss.</p>
<p>I didn&rsquo;t cover performance, monitoring, recovery, or anything else that you probably care about long term. That could be a future post, but my next stop is actually evaluating Longhorn with the v2 engine. As of today, they have released 1.8.0-rc5, which enables support for their v2 engine with Talos (this just means they support NVMe-oF). If Longhorn can now work with NVMe-oF and Talos, to me that is a much more mature and feature rich product, with more community support than OpenEBS. I believe it also supports snapshots and other features that OpenEBS does not currently.</p>
<p>My next post will be all about blowing this setup away and doing it all over with Longhorn. Hopefully by then the stable 1.8.0 will have been released.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Keycloak as an OIDC provider for Kubernetes</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-oidc/</link>
      <pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-oidc/</guid>
      <description>&lt;h1 id=&#34;keycloak-as-an-oidc-provider-for-kubernetes&#34;&gt;Keycloak as an OIDC provider for Kubernetes&lt;/h1&gt;
&lt;p&gt;The workflow&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The client requests an ID Token with claims for it’s identity (name) and the groups he/she belongs to&lt;/li&gt;
&lt;li&gt;The client then requests access to Kubernetes providing the ID token from the IDP obtained previously&lt;/li&gt;
&lt;li&gt;This token (which contains the claims for name, group ) is used in each request to the API Server&lt;/li&gt;
&lt;li&gt;The API Server in turn checks the ID Token validity with the ID provider&lt;/li&gt;
&lt;li&gt;If the token is valid, then the API Server will check if the request is authorized based on the token’s claims and the configured RBAC (by matching it with the corresponding resources)&lt;/li&gt;
&lt;li&gt;Finally, the actions will be performed or denied&lt;/li&gt;
&lt;li&gt;A response is sent back to the client&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From the user perspective, once everything is setup, we will perform this actions to obtain access to the cluster:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="keycloak-as-an-oidc-provider-for-kubernetes">Keycloak as an OIDC provider for Kubernetes</h1>
<p>The workflow</p>
<ol>
<li>The client requests an ID Token with claims for it’s identity (name) and the groups he/she belongs to</li>
<li>The client then requests access to Kubernetes providing the ID token from the IDP obtained previously</li>
<li>This token (which contains the claims for name, group ) is used in each request to the API Server</li>
<li>The API Server in turn checks the ID Token validity with the ID provider</li>
<li>If the token is valid, then the API Server will check if the request is authorized based on the token’s claims and the configured RBAC (by matching it with the corresponding resources)</li>
<li>Finally, the actions will be performed or denied</li>
<li>A response is sent back to the client</li>
</ol>
<p>From the user perspective, once everything is setup, we will perform this actions to obtain access to the cluster:</p>
<ol>
<li>Get an ID Token (and Refresh token) from the ID provider (we will request the tokens from Keycloak).</li>
<li>Set a user’s credentials for kubectl .</li>
<li>Set a new kubectl config with this user and a configured cluster (for example, minikube )</li>
<li>Done: Use the config, issue commands</li>
</ol>
<p>From the Keycloak admin’s perspective, we will:</p>
<ol>
<li>Create a client (in our example, a public client, i.e.: no client secret)</li>
<li>Create some basic claims for identification and management of users and groups, specifically:
<ul>
<li>name</li>
<li>groups</li>
</ul>
</li>
<li>Place the target users within the corresponding group</li>
</ol>
<p>From the Kubernetes admin’s perspective, we will:</p>
<ol>
<li>Configure the required RBAC resources: for example, a ClusterRole with the permitted operations and a ClusterRoleBinding that matches the desired group.</li>
<li>Configure the API Server to use Keycloak as an OIDC provider</li>
</ol>
<h2 id="configure-a-keycloak-client-for-kubernetes-sso">Configure a Keycloak client for Kubernetes SSO</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;clientId&#34;</span><span class="p">:</span> <span class="s2">&#34;k8s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;k8s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Kubernetes SSO&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;rootUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;adminUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;surrogateAuthRequired&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;enabled&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alwaysDisplayInConsole&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;clientAuthenticatorType&#34;</span><span class="p">:</span> <span class="s2">&#34;client-secret&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;redirectUris&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/*&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;webOrigins&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/*&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;notBefore&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;bearerOnly&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;consentRequired&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;standardFlowEnabled&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;implicitFlowEnabled&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;directAccessGrantsEnabled&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;serviceAccountsEnabled&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;publicClient&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;frontchannelLogout&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;protocol&#34;</span><span class="p">:</span> <span class="s2">&#34;openid-connect&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;attributes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;oidc.ciba.grant.enabled&#34;</span><span class="p">:</span> <span class="s2">&#34;false&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;oauth2.device.authorization.grant.enabled&#34;</span><span class="p">:</span> <span class="s2">&#34;false&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;backchannel.logout.session.required&#34;</span><span class="p">:</span> <span class="s2">&#34;true&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;backchannel.logout.revoke.offline.tokens&#34;</span><span class="p">:</span> <span class="s2">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authenticationFlowBindingOverrides&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;fullScopeAllowed&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;nodeReRegistrationTimeout&#34;</span><span class="p">:</span> <span class="mi">-1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;defaultClientScopes&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;web-origins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;acr&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;profile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;roles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;groups&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;optionalClientScopes&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;address&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;phone&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;offline_access&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;microprofile-jwt&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;view&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;configure&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;manage&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-32-05.png" type="" alt=""  /></p>
<ul>
<li>I am allowing all redirects and all web origins, though this is less than desirable in production.</li>
<li>Please change this values to your redirect URLs to enhance the security.</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-31-35.png" type="" alt=""  /></p>
<ul>
<li>I have let only the Standard Flow and the Direct access (for username and password sing-in).</li>
</ul>
<h2 id="configuring-the-name-and-groups-claims">Configuring the name and groups claims</h2>
<p>We want to make those two claims available in the ID token. For that we will:</p>
<ul>
<li>Create new client scopes and</li>
<li>Specify how to map them to the token</li>
<li>Configure those new scopes in the Keycloak client that we created for Kubernetes authentication</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-33-06.png" type="" alt=""  /></p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-33-22.png" type="" alt=""  /></p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-33-40.png" type="" alt=""  /></p>
<p>The group scope:</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-34-05.png" type="" alt=""  /></p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-34-31.png" type="" alt=""  /></p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-34-43.png" type="" alt=""  /></p>
<p>Gotchas</p>
<ul>
<li>Forgetting to strip the path from the groups’s name</li>
<li>Forgetting to add this to the ID Token!</li>
<li>Forgetting to add this to the user info (if you plan to validate the token before submitting it in each request)</li>
</ul>
<p>The “Kubernetes” Client, Client Scopes configuration:</p>
<ul>
<li>Add the client scopes name and groups</li>
<li>Make them Default so that’s easier later on</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-35-40.png" type="" alt=""  /></p>
<h2 id="common-cli-environment-variables">Common CLI environment variables</h2>
<p>To make it easier to go through the rest of the steps we will use some environment variables:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">REALM</span><span class="o">=</span><span class="s1">&#39;the_realm_that_contains_the_k8s_client&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OIDC_SERVER</span><span class="o">=</span><span class="s1">&#39;https://your.keycloak.server.local:8443&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OIDC_ISSUER_URL</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">OIDC_SERVER</span><span class="si">}</span><span class="s2">/realms/</span><span class="si">${</span><span class="nv">REALM</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OIDC_CLIENT_ID</span><span class="o">=</span>k8s
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OIDC_TOKEN_ENDPOINT</span><span class="o">=</span><span class="k">$(</span>curl <span class="s2">&#34;</span><span class="si">${</span><span class="nv">OIDC_ISSUER_URL</span><span class="si">}</span><span class="s2">/.well-known/openid-configuration&#34;</span> <span class="p">|</span> jq -r <span class="s1">&#39;.token_endpoint&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">OIDC_USERINFO_ENDPOINT</span><span class="o">=</span><span class="k">$(</span>curl <span class="s2">&#34;</span><span class="si">${</span><span class="nv">OIDC_ISSUER_URL</span><span class="si">}</span><span class="s2">/.well-known/openid-configuration&#34;</span> <span class="p">|</span> jq -r <span class="s1">&#39;.userinfo_endpoint&#39;</span><span class="k">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="k8s-configure-kubernetes-api-server">K8S: Configure Kubernetes API Server</h2>
<p>We will setup minikube passing the configuration flags for the API Server directly from the command line.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># minikube setup</span>
</span></span><span class="line"><span class="cl">minikube start --driver docker --cpus <span class="m">8</span> --memory max --profile minikube <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--embed-certs <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.authorization-mode<span class="o">=</span>Node,RBAC <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-issuer-url<span class="o">=</span><span class="si">${</span><span class="nv">OIDC_ISSUER_URL</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-client-id<span class="o">=</span><span class="si">${</span><span class="nv">OIDC_CLIENT_ID</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-username-claim<span class="o">=</span>name <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-username-prefix<span class="o">=</span>- <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-groups-claim<span class="o">=</span>groups <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-config<span class="o">=</span>apiserver.oidc-groups-prefix<span class="o">=</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Command Line breakdown</p>
<p>— embed-certs : adds the certificates placed under $HOME/.minikube/certs/
— extra-config=apiserver.authorization-mode=Node,RBAC : this adds RBAC to the cluster while maintaining the local node access (to prevent lockout).
— extra-config=apiserver.oidc-issuer-url=${OIDC_ISSUER_URL} : the issuer URL (which is the URL with the realm, or in case of another OpenID provider, the URL where you can then find the .well-known/openid-configuration
— extra-config=apiserver.oidc-client-id=${OIDC_CLIENT_ID} : the client ID (in our case, the name of the client in the real)
— extra-config=apiserver.oidc-username-claim=name : the username claim from the token.
— extra-config=apiserver.oidc-username-prefix=- : intentionally configured with a single dash, which prevents any prefix to be appended to the name-
— extra-config=apiserver.oidc-groups-claim=groups : the configuration for the group name.
— extra-config=apiserver.oidc-groups-prefix= : this one is intentionally left blank to prevent the API Server to add any prefix (like - to the groups’ names)</p>
<p>Gotchas</p>
<pre><code>The config is not yet ready or you have not configured an alternative to RBAC
The certificate is not trusted (because it is self-signed or it is not available in the trust store)
</code></pre>
<h2 id="k8s-configure-rbac">K8S: Configure RBAC</h2>
<p>For now, we will create a ClusterRole and a ClusterRoleBinding to demonstrate how to create how to grant “ReadOnly” rights to Namespaces and Pods within the cluster:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># rbac.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;namespaces&#34;</span><span class="p">,</span><span class="s2">&#34;pods&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;watch&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterRoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiGroup</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Group</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;k8s&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiGroup</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>How does this work?</p>
<p>The ClusterRole defines which operations can be performed on the API Server and the ClusterRoleBinding matches the role with a specific subject: notice the subjects code block:</p>
<p>It references the name k8s of kind Group</p>
<p>By assigning this group (“k8s”) to our users they will get RO access to the clusters’ namespaces and pods.</p>
<p>Apply the config using the current local node credentials:</p>
<p>kubectl apply -f rbac.yaml
Configure the Client (kubectl)</p>
<p>As explained in the introductory section, we will first obtain an ID token and then configure kubectl with it.</p>
<p>For this purpose, I will create a user called k8suser in Keycloak and assign it the group k8s . You can skip this part if you have other users and or groups, just adjust to your needs:</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-oidc/assets/index_2025-04-17_21-38-08.png" type="" alt=""  /></p>
<p>Preparing the command line to request the ID Token</p>
<p>For this purpuse I will use curl and jq to get a response from Keycloak.</p>
<p>I will then extract the tokens and use them to configure kubectl .</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># Prepare some credentials</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">K8S_USER</span><span class="o">=</span><span class="s1">&#39;k8suser&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">K8S_USER_PASS</span><span class="o">=</span><span class="s1">&#39;THE_USER_PASS&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">RESPONSE</span><span class="o">=</span><span class="k">$(</span>curl -v -k -X POST <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-H <span class="s2">&#34;Content-Type: application/x-www-form-urlencoded&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">OIDC_TOKEN_ENDPOINT</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-d <span class="nv">grant_type</span><span class="o">=</span>password <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-d <span class="nv">client_id</span><span class="o">=</span><span class="si">${</span><span class="nv">OIDC_CLIENT_ID</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-d <span class="nv">username</span><span class="o">=</span><span class="si">${</span><span class="nv">K8S_USER</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-d <span class="nv">password</span><span class="o">=</span><span class="si">${</span><span class="nv">K8S_USER_PASS</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-d <span class="nv">scope</span><span class="o">=</span><span class="s2">&#34;openid profile email name groups&#34;</span> <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span><span class="k">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Extract the tokens:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">ID_TOKEN</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$RESPONSE</span><span class="p">|</span> jq -r <span class="s1">&#39;.id_token&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">REFRESH_TOKEN</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$RESPONSE</span><span class="p">|</span> jq -r <span class="s1">&#39;.refresh_token&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">ACCESS_TOKEN</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$RESPONSE</span><span class="p">|</span> jq -r <span class="s1">&#39;.access_token&#39;</span><span class="k">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now we can optionally check if we received all the information that we need by querying the user info endpoint with the access token.</p>
<p>Afterwards we do not need the access token anymore and we could discard it.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -s -H <span class="s2">&#34;Content-Type: application/x-www-form-urlencoded&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-H <span class="s2">&#34;Authorization: Bearer </span><span class="si">${</span><span class="nv">ACCESS_TOKEN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">OIDC_USERINFO_ENDPOINT</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">|</span> jq <span class="s1">&#39;.&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;sub&#34;</span>: <span class="s2">&#34;a42c4585-916d-4e9e-a068-ba1bbceb8887&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;email_verified&#34;</span>: true,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;k8suser&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;groups&#34;</span>: <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;k8s&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;preferred_username&#34;</span>: <span class="s2">&#34;k8suser&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;given_name&#34;</span>: <span class="s2">&#34;k8suser&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;family_name&#34;</span>: <span class="s2">&#34;KC&#34;</span>,
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;email&#34;</span>: <span class="s2">&#34;k8suser@rieragalm.es&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note: Check that name and groups appear and they are populated properly!</p>
<p>Gotchas</p>
<pre><code>You forgot to make the claims default or you are not requesting those scopes, so there are not available in the ID Token
</code></pre>
<p>Kubectl: create a set of credentials</p>
<p>Now that we have the tokens, let’s create a new set of kubectl credentials!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl config set-credentials <span class="si">${</span><span class="nv">K8S_USER</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>   --auth-provider<span class="o">=</span>oidc <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>   --auth-provider-arg<span class="o">=</span>idp-issuer-url<span class="o">=</span><span class="si">${</span><span class="nv">OIDC_ISSUER_URL</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>   --auth-provider-arg<span class="o">=</span>client-id<span class="o">=</span><span class="si">${</span><span class="nv">OIDC_CLIENT_ID</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>   --auth-provider-arg<span class="o">=</span>refresh-token<span class="o">=</span><span class="si">${</span><span class="nv">REFRESH_TOKEN</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>   --auth-provider-arg<span class="o">=</span>id-token<span class="o">=</span><span class="si">${</span><span class="nv">ID_TOKEN</span><span class="si">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now we have to create a new configuration context to use this newly created user in an existing cluster:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl config set-context <span class="si">${</span><span class="nv">K8S_USER</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--cluster<span class="o">=</span>minikube <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--user<span class="o">=</span><span class="si">${</span><span class="nv">K8S_USER</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--namespace<span class="o">=</span>default
</span></span></code></pre></td></tr></table>
</div>
</div><p>And finally we need to use it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl config use-context <span class="si">${</span><span class="nv">K8S_USER</span><span class="si">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Kubectl: check the access</p>
<p>We will now check our identity and we will try to perform a <code>get</code> operation to get the pods on the cluster:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl auth whoami
</span></span><span class="line"><span class="cl">ATTRIBUTE   VALUE
</span></span><span class="line"><span class="cl">Username    k8suser
</span></span><span class="line"><span class="cl">Groups      <span class="o">[</span>k8s system:authenticated<span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl auth can-i get pods
</span></span><span class="line"><span class="cl">yes
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl auth can-i get deployments.apps
</span></span><span class="line"><span class="cl">no
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">ubectl get pods -A
</span></span><span class="line"><span class="cl">NAMESPACE     NAME                                      READY   STATUS    RESTARTS        AGE
</span></span><span class="line"><span class="cl">kube-system   coredns-5dd5756b68-7xkmd                  1/1     Running   <span class="m">2</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span><span class="line"><span class="cl">kube-system   etcd-minikube-docker                      1/1     Running   <span class="m">3</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span><span class="line"><span class="cl">kube-system   kube-apiserver-minikube-docker            1/1     Running   <span class="m">1</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d22h
</span></span><span class="line"><span class="cl">kube-system   kube-controller-manager-minikube-docker   1/1     Running   <span class="m">3</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span><span class="line"><span class="cl">kube-system   kube-proxy-cbjf9                          1/1     Running   <span class="m">2</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span><span class="line"><span class="cl">kube-system   kube-scheduler-minikube-docker            1/1     Running   <span class="m">3</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span><span class="line"><span class="cl">kube-system   storage-provisioner                       1/1     Running   <span class="m">5</span> <span class="o">(</span>2d18h ago<span class="o">)</span>   2d23h
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get deployments.apps
</span></span><span class="line"><span class="cl">Error from server <span class="o">(</span>Forbidden<span class="o">)</span>: deployments.apps is forbidden: User <span class="s2">&#34;k8suser&#34;</span> cannot list resource <span class="s2">&#34;deployments&#34;</span> in API group <span class="s2">&#34;apps&#34;</span> in the namespace <span class="s2">&#34;default&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes Automated Cluster Scaling</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-hpa-vpa/</link>
      <pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-hpa-vpa/</guid>
      <description>&lt;h1 id=&#34;kubernetes-automated-cluster-scaling&#34;&gt;Kubernetes Automated Cluster Scaling&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details&#34;&gt;algorithm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;AutoScaling is one of the most powerful concepts in Kubernetes.&lt;/p&gt;
&lt;p&gt;This involves two main mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Horizontal Pod Autoscaling (HPA)&lt;/li&gt;
&lt;li&gt;Vertical Pod Autoscaling (VPA).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Automated cluster scaling refers to the process of dynamically adjusting the number of running pods (HPA) or their resource allocations (VPA) based on real-time metrics. This ensures that your applications can efficiently handle varying loads without manual intervention.&lt;/p&gt;
&lt;p&gt;With HPA, you can scale smarter, and with VPA, scale wiser. HPA handles traffic spikes like a champ.
VPA makes sure your pods get the resources they deserve.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="kubernetes-automated-cluster-scaling">Kubernetes Automated Cluster Scaling</h1>
<p><a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details">algorithm</a></p>
<p>AutoScaling is one of the most powerful concepts in Kubernetes.</p>
<p>This involves two main mechanisms:</p>
<ul>
<li>Horizontal Pod Autoscaling (HPA)</li>
<li>Vertical Pod Autoscaling (VPA).</li>
</ul>
<p>Automated cluster scaling refers to the process of dynamically adjusting the number of running pods (HPA) or their resource allocations (VPA) based on real-time metrics. This ensures that your applications can efficiently handle varying loads without manual intervention.</p>
<p>With HPA, you can scale smarter, and with VPA, scale wiser. HPA handles traffic spikes like a champ.
VPA makes sure your pods get the resources they deserve.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-hpa-vpa/assets/index_2025-04-17_16-18-28.png" type="" alt=""  /></p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-hpa-vpa/assets/index_2025-04-17_16-19-12.png" type="" alt=""  /></p>
<h2 id="horizontal-pod-autoscaling-hpa">Horizontal Pod Autoscaling (HPA)</h2>
<p>HPA automatically adjusts the number of pods in a deployment or replica set based on observed CPU utilization or other select metrics. For instance, if your application experiences a sudden increase in traffic, HPA will scale out by adding more pods to handle the load. Conversely, it will scale in by reducing the number of pods when the load decreases.</p>
<ul>
<li>Manual Scaling (Quick Recap). You manually scale pods using:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl scale deployment &lt;name&gt; --replicas<span class="o">=</span><span class="m">5</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p>Limitations: Manual, not reactive to load → not ideal for production.</p>
</li>
<li>
<p>Metrics Used: CPU/Memory utilization (via metrics-server).</p>
</li>
<li>
<p>Common Use Case: Web app getting heavy traffic → HPA increases pods → load balanced across more pods → better performance.</p>
</li>
<li>
<p>Key Fields in HPA YAML:</p>
</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scaleTargetRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">minReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">maxReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">resource</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cpu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">target</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Utilization</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">averageUtilization</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">autoscaling/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HorizontalPodAutoscaler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app-hpa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scaleTargetRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">minReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">maxReplicas</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">targetCPUUtilizationPercentage</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Deploying the Metrics Server</li>
</ul>
<p>Why: HPA needs CPU/memory stats → metrics-server collects and exposes these.</p>
<p>Install:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
</span></span><span class="line"><span class="cl">kubectl get deployment metrics-server -n kube-system
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Create Pod + Service
Example YAML:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">k8s.gcr.io/hpa-example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">200m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">500m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp-svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Deploy the HPA</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">kubectl autoscale deployment loadapp --cpu-percent=50 --min=1 --max=10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">kubectl get hpa</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Simulate Load</li>
</ul>
<p>To make CPU usage spike and trigger autoscaling:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl run -i --tty load-generator --image<span class="o">=</span>busybox /bin/sh
</span></span></code></pre></td></tr></table>
</div>
</div><p>Inside the pod:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="k">while</span> true<span class="p">;</span> <span class="k">do</span> wget -q -O- http://loadapp-svc.default.svc.cluster.local<span class="p">;</span> <span class="k">done</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This sends continuous traffic to the service, increasing CPU usage.</p>
<ul>
<li>Observe Scaling</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">watch kubectl get hpa
</span></span></code></pre></td></tr></table>
</div>
</div><p>You’ll see replicas increasing as CPU crosses threshold (e.g., &gt;50%).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get pods
</span></span></code></pre></td></tr></table>
</div>
</div><p>Eventually:</p>
<ul>
<li>More pods created</li>
<li>CPU usage spread across them</li>
<li>Load goes down</li>
</ul>
<h2 id="vertical-pod-autoscaling-vpa">Vertical Pod Autoscaling (VPA)</h2>
<p>VPA adjusts the resource requests and limits for your containers based on their usage. This means it can increase the CPU and memory allocated to a pod if it is consistently using more resources than initially requested, or it can reduce these allocations if the pod is over-provisioned.</p>
<ul>
<li>
<p>What it does: Changes the resources (CPU, memory) allocated to each pod.</p>
</li>
<li>
<p>Use Case: Workloads where replica count doesn’t need to change, but need more resources.</p>
</li>
<li>
<p>Limitations: VPA restarts pods to apply changes.</p>
</li>
<li>
<p>Example YAML:</p>
</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">autoscaling.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">VerticalPodAutoscaler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-vpa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">targetRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;apps/v1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">loadapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">updatePolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">updateMode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Auto&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Install the VPA components from their official GitHub if not available in your cluster.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-hpa-vpa/assets/index_2025-04-17_16-17-34.png" type="" alt=""  /></p>
<h1 id="tolerance">Tolerance</h1>
<p><a href="https://kubernetes.io/blog/2025/04/28/kubernetes-v1-33-hpa-configurable-tolerance/">doc</a></p>
<p>version: Kubernetes v1.33</p>
<p>Tolerances appear under the spec.behavior.scaleDown and spec.behavior.scaleUp fields and can thus be different for scale up and scale down. A typical usage would be to specify a small tolerance on scale up (to react quickly to spikes), but higher on scale down (to avoid adding and removing replicas too quickly in response to small metric fluctuations).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">autoscaling/v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HorizontalPodAutoscaler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">behavior</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scaleDown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tolerance</span><span class="p">:</span><span class="w"> </span><span class="m">0.05</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scaleUp</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tolerance</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes Controllers</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-controllers/</link>
      <pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-controllers/</guid>
      <description>&lt;h1 id=&#34;what-is-a-replication-controller&#34;&gt;What is a Replication Controller?&lt;/h1&gt;
&lt;p&gt;A Replication Controller (RC) ensures that a specified number of pod replicas are running at all times. It continuously monitors the cluster and if a pod fails, it will replace it to maintain the desired state.&lt;/p&gt;
&lt;p&gt;Features&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ensures availability of pods by replacing failed ones.
Basic scaling of pods by changing the replica count.
Legacy Component: Being replaced by ReplicaSets in modern Kubernetes due to limited functionality.
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;apiVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;v1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ReplicationController&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;metadata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;my-rc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;spec&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;replicas&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;selector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;myapp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;metadata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;labels&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;myapp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;spec&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;containers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx:latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;kubectl get rc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;what-is-a-replicaset&#34;&gt;What is a ReplicaSet?&lt;/h2&gt;
&lt;p&gt;A ReplicaSet (RS) is the newer, more advanced version of Replication Controllers. It provides additional functionality and is often managed by Deployments.
Features&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="what-is-a-replication-controller">What is a Replication Controller?</h1>
<p>A Replication Controller (RC) ensures that a specified number of pod replicas are running at all times. It continuously monitors the cluster and if a pod fails, it will replace it to maintain the desired state.</p>
<p>Features</p>
<pre><code>Ensures availability of pods by replacing failed ones.
Basic scaling of pods by changing the replica count.
Legacy Component: Being replaced by ReplicaSets in modern Kubernetes due to limited functionality.
</code></pre>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ReplicationController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-rc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:latest</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get rc
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="what-is-a-replicaset">What is a ReplicaSet?</h2>
<p>A ReplicaSet (RS) is the newer, more advanced version of Replication Controllers. It provides additional functionality and is often managed by Deployments.
Features</p>
<pre><code>Supports set-based label selectors, allowing complex filtering of pods.
Works seamlessly with Deployments for rolling updates and rollbacks.
Dynamic scaling support by modifying the number of replicas.
</code></pre>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ReplicaSet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-rs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:latest</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get rs
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="what-is-a-deployment">What is a Deployment?</h2>
<p>A Deployment is the most common way to manage ReplicaSets. It provides features like rolling updates, rollbacks, and scaling.
Features</p>
<pre><code>Manages ReplicaSets to maintain desired state of applications.
Supports Rolling Updates &amp; Rollbacks to ensure zero downtime during updates.
Automatically scales pods based on defined replicas.
Provides self-healing capabilities to replace unhealthy pods.
</code></pre>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">myapp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:latest</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Check Deployment Status:</p>
<p>kubectl rollout status deployment/my-deployment</p>
<p>View Deployment History:</p>
<p>kubectl rollout history deployment/my-deployment</p>
<p>Rollback to Previous Version:</p>
<p>kubectl rollout undo deployment/my-deployment</p>
<p>Scale the Deployment:</p>
<p>kubectl scale deployment my-deployment &ndash;replicas=5</p>
<p>Summary</p>
<pre><code>Replication Controller: Ensures availability of a specified number of pods.
ReplicaSet: Improved version of RC with better label selection and scaling support.
Deployment: Manages ReplicaSets, supports rolling updates, rollbacks, and scaling.
</code></pre>
]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes Init Containers</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-init-container/</link>
      <pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-init-container/</guid>
      <description>&lt;h1 id=&#34;kubernetes-init-containers&#34;&gt;Kubernetes Init Containers&lt;/h1&gt;
&lt;p&gt;Kubernetes init containers are specialized containers that run to completion before any of your application’s primary containers start. Unlike regular containers, they are not part of your ongoing workload but instead perform initialization tasks such as setting up prerequisites, configuring environments, or fetching secrets. This ensures that the main containers only start when the system is fully prepared.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Isolation of Setup Tasks: They allow you to separate initialization logic from the main application, keeping your application images lean and secure.&lt;/li&gt;
&lt;li&gt;Different Resource Allocation: Init containers may require different CPU/memory limits. The effective pod resource requests are determined by the highest values among the init containers and the app containers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using init containers offers several strategic advantages:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="kubernetes-init-containers">Kubernetes Init Containers</h1>
<p>Kubernetes init containers are specialized containers that run to completion before any of your application’s primary containers start. Unlike regular containers, they are not part of your ongoing workload but instead perform initialization tasks such as setting up prerequisites, configuring environments, or fetching secrets. This ensures that the main containers only start when the system is fully prepared.</p>
<ul>
<li>Isolation of Setup Tasks: They allow you to separate initialization logic from the main application, keeping your application images lean and secure.</li>
<li>Different Resource Allocation: Init containers may require different CPU/memory limits. The effective pod resource requests are determined by the highest values among the init containers and the app containers.</li>
</ul>
<p>Using init containers offers several strategic advantages:</p>
<ul>
<li>Enhanced Security: They can run privileged tasks (like fetching sensitive secrets from Vault or AWS Secrets Manager) without bloating the main application container image.</li>
<li>Environment Preparation: Init containers perform setup tasks like configuring databases, creating directories, or cloning repositories, ensuring that all dependencies are ready.</li>
<li>Simpler Application Images: By offloading initialization to separate containers, you can keep your main container images small, reducing the attack surface.</li>
<li>Better Resource Management: They allow you to allocate precise resources for initialization tasks that may have a short lifespan compared to the main application.</li>
</ul>
<h2 id="example-downloading-configuration-from-s3">Example: Downloading Configuration from S3</h2>
<p>When deploying a Django application on Kubernetes, it&rsquo;s essential to run database migrations before starting the main application. If migrations are not applied, the app might crash due to missing tables or outdated schemas.</p>
<p>Using an init container, we can apply migrations before starting the Django web server. This ensures that:</p>
<ul>
<li>The database schema is updated before the app runs.</li>
<li>The main container only starts after the migration process is successful.</li>
<li>There are no race conditions where multiple pods try to run migrations simultaneously.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">django-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">django</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">django</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initContainers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">run-migrations</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">my-django-app:latest </span><span class="w"> </span><span class="c"># Use the same image as your main app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;python&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;manage.py&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;migrate&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATABASE_URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;postgres://user:password@postgres-service:5432/mydb&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">django-config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/app/config </span><span class="w"> </span><span class="c"># If using external config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">django</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">my-django-app:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;gunicorn&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;myproject.wsgi:application&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--bind&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;0.0.0.0:8000&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">8000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">DATABASE_URL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;postgres://user:password@postgres-service:5432/mydb&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">django-config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/app/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">django-config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">emptyDir</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="how-it-works">How It Works</h2>
<ul>
<li>Init Container (run-migrations)
<ul>
<li>Uses the same Django application image as the main container.</li>
<li>Runs python manage.py migrate to apply database migrations.</li>
<li>Ensures that the database is ready before the main application starts.</li>
</ul>
</li>
<li>Main Container (django)
<ul>
<li>Runs Gunicorn as the Django web server.</li>
<li>Starts only after the init container completes successfully.</li>
</ul>
</li>
<li>Why Use an Init Container for Migrations?
<ul>
<li>Prevents multiple app instances from running migrations simultaneously.</li>
<li>Guarantees that migrations are applied before the app starts.</li>
<li>Ensures a stable startup process for Django in Kubernetes.</li>
</ul>
</li>
</ul>
<h2 id="advanced-insights-and-unknown-facts">Advanced Insights and Unknown Facts</h2>
<h3 id="resource-calculation-for-init-containers">Resource Calculation for Init Containers</h3>
<ul>
<li>Effective Resource Requests: The highest resource request (CPU/memory) specified among all init containers becomes the effective request for the pod. This might impact scheduling, so plan resource allocation carefully.</li>
</ul>
<h3 id="native-sidecar-functionality">Native Sidecar Functionality</h3>
<ul>
<li>Alpha Feature: Kubernetes v1.28 introduced native sidecar support via init containers by setting restartPolicy: Always. This allows an init container to function as a persistent sidecar that runs alongside your main application.</li>
<li>Use Case: Ideal for logging agents or monitoring tools that need to run continuously without blocking pod termination.</li>
</ul>
<h3 id="best-practices">Best Practices</h3>
<ul>
<li>Keep It Focused: Design init containers to perform a single, well-defined task.</li>
<li>Idempotency: Since init containers may be retried, ensure your initialization logic is idempotent.</li>
<li>Monitor Logs: Even after init containers finish, their logs are available for debugging purposes.</li>
<li>Security: Avoid embedding sensitive credentials in the main container image; fetch them securely in an init container instead.</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Helm: The package manager for Kubernetes</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-helm/</link>
      <pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-helm/</guid>
      <description>&lt;h1 id=&#34;helm&#34;&gt;Helm&lt;/h1&gt;
&lt;h2 id=&#34;helm-fetch&#34;&gt;helm fetch&lt;/h2&gt;
&lt;p&gt;You can use helm fetch to Download a chart to your local directory, so You can change the values in values.yaml file and then install it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for example:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;helm fetch stable/superset --untar
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <content:encoded><![CDATA[<h1 id="helm">Helm</h1>
<h2 id="helm-fetch">helm fetch</h2>
<p>You can use helm fetch to Download a chart to your local directory, so You can change the values in values.yaml file and then install it.</p>
<ul>
<li>for example:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">helm fetch stable/superset --untar
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes Gateway Api</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-gateway-api/</link>
      <pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-gateway-api/</guid>
      <description>&lt;h1 id=&#34;the-kubernetes-gateway-api&#34;&gt;The Kubernetes Gateway API&lt;/h1&gt;
&lt;p&gt;The Kubernetes Gateway API is a modern, extensible standard for managing ingress and routing traffic in Kubernetes environments. It builds upon the limitations of the legacy Ingress API to provide a vendor-agnostic, declarative framework for configuring L4 and L7 network traffic. The Gateway API is designed to unify and simplify traffic management while supporting advanced use cases such as multi-tenancy, path-based routing, and traffic splitting.&lt;/p&gt;
&lt;p&gt;Vendor-Agnostic Abstraction:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="the-kubernetes-gateway-api">The Kubernetes Gateway API</h1>
<p>The Kubernetes Gateway API is a modern, extensible standard for managing ingress and routing traffic in Kubernetes environments. It builds upon the limitations of the legacy Ingress API to provide a vendor-agnostic, declarative framework for configuring L4 and L7 network traffic. The Gateway API is designed to unify and simplify traffic management while supporting advanced use cases such as multi-tenancy, path-based routing, and traffic splitting.</p>
<p>Vendor-Agnostic Abstraction:</p>
<p>The Gateway API provides a universal standard for ingress and traffic routing, eliminating the dependency on vendor-specific annotations.</p>
<p>It allows seamless migration across cloud providers and ingress controllers without major changes to the configuration.</p>
<p>Separation Of Concerns:</p>
<p>Enables clear boundaries between application teams and infrastructure teams by splitting responsibilities -</p>
<p>Infrastructure Teams: Manage the Gateway resources (e.g., ingress controllers, load balancers).</p>
<p>Application Teams: Define Routes (e.g., HTTPRoute, TCPRoute) without worrying about the underlying network infrastructure.</p>
<p>Layer 7 Load Balancing:</p>
<p>Offers advanced routing capabilities, including path-based routing, header-based routing, and traffic splitting for canary deployments or blue-green deployments.</p>
<p>Supports precise L7 policies like rate limiting, request rewrites, and cross-origin resource sharing (CORS).</p>
<p>Extensibility:</p>
<p>Designed with extensibility in mind, it allows custom implementations via GatewayClass, enabling features specific to vendors like AWS, GCP, or Istio while adhering to the Gateway API standard.</p>
<p>Standardized CRDs reduce reliance on annotations.</p>
<p>How The Gateway API Solves The “Annotation Chaos”</p>
<p>The legacy Ingress API relied on annotations to configure features like SSL termination, load balancing algorithms, or backend timeouts. Each ingress controller implemented annotations differently, causing inconsistencies across environments.</p>
<p>The Gateway API eliminates this problem by introducing Custom Resource Definitions (CRDs) for defining</p>
<p>Gateways: Representing the physical or logical entry point (e.g., load balancers, proxies).</p>
<p>Routes: Defining application-specific routing rules (e.g., HTTPRoute, TCPRoute).</p>
<hr>
<p>Lack Of Flexibility:</p>
<p>The Ingress API was designed as a one-size-fits-all solution but fails to cater to advanced use cases. It offers limited configurability for Layer 7 (L7) routing beyond basic HTTP/HTTPS functionality.</p>
<p>Heavy Reliance On Annotations:</p>
<p>To extend Ingress capabilities, vendors introduced annotations, often in incompatible ways. For instance, annotations for AWS ALB differ significantly from those for NGINX or Traefik. This reliance creates vendor lock-in, making migrations between platforms challenging.</p>
<p>Multi-Layer Traffic Routing:</p>
<p>Applications often require both external ingress (e.g., handling traffic from the internet) and internal service-to-service communication. Routing traffic efficiently across these layers requires features like -</p>
<p>Path-based and header-based routing.</p>
<p>Weighted traffic splitting for canary deployments or A/B testing.</p>
<p>Mutual TLS (mTLS):</p>
<p>Securing communication between microservices is critical, especially for sensitive workloads. Traditional networking setups often struggle to enforce mTLS consistently across clusters.</p>
<p>Layer 4 (L4) &amp; Layer 7 (L7) Traffic Management:</p>
<p>Applications frequently require both low-level TCP/UDP routing (L4) and high-level HTTP/S routing (L7). Traditional setups struggle to provide unified solutions that handle both effectively.</p>
<p>Verify Gateway version:
kubectl get crd gateways.gateway.networking.k8s.io -o yaml</p>
<p>CRDs update:
kubectl apply -f <a href="https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.7.0/gateway.networking.k8s.io.crds.yaml">https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.7.0/gateway.networking.k8s.io.crds.yaml</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Upgrade Talos Linux and Kubernetes</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-talos/upgrade/</link>
      <pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-talos/upgrade/</guid>
      <description>&lt;h1 id=&#34;upgrade-kubernetes-version&#34;&gt;&lt;a href=&#34;https://www.talos.dev/v1.9/kubernetes-guides/upgrading-kubernetes/&#34;&gt;Upgrade&lt;/a&gt; Kubernetes Version&lt;/h1&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ talosctl --nodes 192.168.1.71 etcd snapshot etcd.backup
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Upgrading Kubernetes is non-disruptive to the cluster workloads.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;You can do this live, assuming you don&amp;rsquo;t have single-replica workloads that are node-specific.&lt;/p&gt;
&lt;p&gt;Today I will be upgrading to Kubernetes version &lt;code&gt;v1.31.5&lt;/code&gt;. I&amp;rsquo;m currently on &lt;code&gt;v1.30.0&lt;/code&gt; but I want to make sure I&amp;rsquo;m running the same version that is being tested on the CKA exam that I&amp;rsquo;m studying for which is currently 1.31.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="upgrade-kubernetes-version"><a href="https://www.talos.dev/v1.9/kubernetes-guides/upgrading-kubernetes/">Upgrade</a> Kubernetes Version</h1>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ talosctl --nodes 192.168.1.71 etcd snapshot etcd.backup
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>Upgrading Kubernetes is non-disruptive to the cluster workloads.</p></blockquote>
<p>You can do this live, assuming you don&rsquo;t have single-replica workloads that are node-specific.</p>
<p>Today I will be upgrading to Kubernetes version <code>v1.31.5</code>. I&rsquo;m currently on <code>v1.30.0</code> but I want to make sure I&rsquo;m running the same version that is being tested on the CKA exam that I&rsquo;m studying for which is currently 1.31.</p>
<p>Talos recommends using the <code>talosctl upgrade-k8s</code> command which automatically upgrades the entire cluster and has built in safety checks. They explain how to do it manually, but I chose Talos Linux partly based on the ease of ongoing maintenance and upgrades so I will be using the easy button here!</p>
<ul>
<li>Check current version: <code>kubectl get node</code></li>
<li>Upgrade: <code>talosctl -n 10.0.50.11 upgrade-k8s --to 1.31.5</code>
<ul>
<li>You only need to specify a single control plane node, but this will upgrade the whole cluster</li>
<li>You need to choose a real Kubernetes version - <a href="https://kubernetes.io/releases/">https://kubernetes.io/releases/</a></li>
<li>This will take a while, so try to be patient.</li>
</ul>
</li>
<li>Verify version: <code>kubectl get node</code></li>
</ul>
<p>I like to update my local talosconfig repo which was used to deploy the original Talos cluster and also includes secrets used to recover in case of any problems. This is a good time to update the Kubernetes version in controlplane.yaml and worker.yaml for any new nodes you deploy.</p>
<h1 id="upgrade-talos-os"><a href="https://www.talos.dev/v1.9/talos-guides/upgrading-talos/">Upgrade</a> Talos OS</h1>
<p>The Talos team recommends using the same version of <code>talosctl</code> that your nodes are running. You will then upgrade <code>talosctl</code> after the node upgrades are complete.</p>
<p>Be sure to upgrade one node at a time and check that it&rsquo;s healthy before moving on. You can blast through them using a for loop, or do them by hand. Just don&rsquo;t do them all at the same time :)</p>
<ul>
<li>
<p>Check versions:</p>
<ul>
<li>Client: <code>talosctl version --client</code></li>
<li>Server: <code>kubectl get node -o wide</code> (OS-IMAGE column)</li>
</ul>
</li>
<li>
<p>Get a new Talos OS image from the factory: <a href="https://factory.talos.dev">https://factory.talos.dev</a></p>
<ul>
<li>Make sure to add any existing extensions you&rsquo;re using such is <code>iscsi-tools</code></li>
<li>Copy the image string under the &ldquo;Upgrading Talos Image&rdquo; header. In my case this looks like <code>factory.talos.dev/installer/88d1f7a5c4f1d3aba7df787c448c1d3d008ed29cfb34af53fa0df4336a56040b:v1.9.2</code></li>
</ul>
</li>
<li>
<p>Upgrade one node: <code>talosctl upgrade -n 10.0.50.11 --image factory.talos.dev/installer/88d1f7a5c4f1d3aba7df787c448c1d3d008ed29cfb34af53fa0df4336a56040b:v1.9.2 --preserve</code></p>
<ul>
<li><code>-n</code>: Specify the node to upgrade</li>
<li><code>--image</code>: Specify the factory image to use</li>
<li><code>--preserve</code>: Don&rsquo;t wipe extraMounts if applicable. I default to using this unless I have a specific reason to wipe additional mounts.</li>
</ul>
</li>
<li>
<p>Repeat the upgrade command for each node, one at a time, until all nodes have been upgraded.</p>
</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ talosctl --talosconfig ~/.talos/talosconfig upgrade -n node3 --image factory.talos.dev/installer/88d1f7a5c4f1d3aba7df787c448c1d3d008ed29cfb34af53fa0df4336a56040b:v1.9.2 --preserve
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  talosctl -n 192.168.1.71 version
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Client:
</span></span><span class="line"><span class="cl">        Tag:         v1.7.6
</span></span><span class="line"><span class="cl">        SHA:         ae67123a
</span></span><span class="line"><span class="cl">        Built:
</span></span><span class="line"><span class="cl">        Go version:  go1.22.5
</span></span><span class="line"><span class="cl">        OS/Arch:     darwin/amd64
</span></span><span class="line"><span class="cl">Server:
</span></span><span class="line"><span class="cl">        NODE:        192.168.1.71
</span></span><span class="line"><span class="cl">        Tag:         v1.7.6
</span></span><span class="line"><span class="cl">        SHA:         ae67123a
</span></span><span class="line"><span class="cl">        Built:
</span></span><span class="line"><span class="cl">        Go version:  go1.22.5
</span></span><span class="line"><span class="cl">        OS/Arch:     linux/arm64
</span></span><span class="line"><span class="cl">        Enabled:     RBAC
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">❯  talosctl upgrade --nodes 192.168.1.71 --image ghcr.io/siderolabs/installer:v1.9.5
</span></span><span class="line"><span class="cl">◲ watching nodes: <span class="o">[</span>192.168.1.71<span class="o">]</span>
</span></span><span class="line"><span class="cl">    * 192.168.1.71: task: removeAllPods action: START
</span></span></code></pre></td></tr></table>
</div>
</div><p>In my homelab, I am comfortable blasting through upgrades with a for loop, so my upgrade command looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">for</span> node in <span class="m">11</span> <span class="m">12</span> <span class="m">13</span> <span class="m">21</span> <span class="m">22</span> <span class="m">23</span> <span class="m">31</span> <span class="m">32</span> 33<span class="p">;</span> <span class="k">do</span> talosctl upgrade -n 10.0.50.<span class="nv">$node</span> --image factory.talos.dev/installer/88d1f7a5c4f1d3aba7df787c448c1d3d008ed29cfb34af53fa0df4336a56040b:v1.9.2 --preserve<span class="p">;</span> <span class="k">done</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once Nodes have been upgraded, upgrade the <code>talosctl</code> client so the version matches the Talos node version.</p>
<ul>
<li><code>rm /usr/local/bin/talosctl</code></li>
<li><code>curl -sL https://talos.dev/install | sh</code> (this gets the latest version)
<ul>
<li>You can also download a specific release from <a href="https://github.com/siderolabs/talos/releases">https://github.com/siderolabs/talos/releases</a>, e.g. <code>curl -LJO https://github.com/siderolabs/talos/releases/download/v1.8.3/talosctl-linux-amd64</code></li>
</ul>
</li>
<li>Verify: <code>talosctl version --client</code></li>
</ul>
<p>I like to update my local talosconfig repo which was used to deploy the original Talos cluster and also includes secrets used to recover in case of any problems. This is a good time to update the factory image in controlplane.yaml and worker.yaml for any new nodes you deploy.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ talosctl -n node1 health
</span></span><span class="line"><span class="cl">discovered nodes: <span class="o">[</span><span class="s2">&#34;192.168.1.71&#34;</span> <span class="s2">&#34;192.168.1.59&#34;</span> <span class="s2">&#34;192.168.1.73&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd to be healthy: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd to be healthy: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be consistent across nodes: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be consistent across nodes: OK
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  talosctl --nodes 192.168.1.71 dmesg -f
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Kubernetes: CRD, Custom Resource Definition</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-crds/</link>
      <pubDate>Sat, 05 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-crds/</guid>
      <description>&lt;h1 id=&#34;create-your-own-crd&#34;&gt;Create your own CRD&lt;/h1&gt;
&lt;h2 id=&#34;crd-custom-resource-definition&#34;&gt;CRD (Custom Resource Definition)&lt;/h2&gt;
&lt;p&gt;This defines how we want our custom Kubernetes objects to look and behave.&lt;/p&gt;
&lt;p&gt;The name of the CRD must follow this format: &lt;plural-name&gt;.&lt;api-group&gt;&lt;/p&gt;
&lt;p&gt;Example: &lt;code&gt;albertocrds.crds.albertogalvez.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To list existing CRDs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;kubectl get crds
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the custom resource manifest, you must specify:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;apiVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;crds.albertogalvez.com/v1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Albertocrds&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;controller&#34;&gt;Controller&lt;/h2&gt;
&lt;p&gt;Now we create our controller, which is simply an application running continuously in Kubernetes, listening for changes to our custom resources.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="create-your-own-crd">Create your own CRD</h1>
<h2 id="crd-custom-resource-definition">CRD (Custom Resource Definition)</h2>
<p>This defines how we want our custom Kubernetes objects to look and behave.</p>
<p>The name of the CRD must follow this format: <plural-name>.<api-group></p>
<p>Example: <code>albertocrds.crds.albertogalvez.com</code></p>
<p>To list existing CRDs:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get crds
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the custom resource manifest, you must specify:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">crds.albertogalvez.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Albertocrds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">...</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="controller">Controller</h2>
<p>Now we create our controller, which is simply an application running continuously in Kubernetes, listening for changes to our custom resources.</p>
<p>There are libraries available to build controllers in various programming languages.</p>
<p>In Python, for example, you can use the <a href="https://github.com/nolar/kopf">kopf</a> library.</p>
<p>Example execution:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kopf run /path/to/controller.py
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Kubectl: Kubernetes Swiss Knife</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-kubectl/</link>
      <pubDate>Sat, 29 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-kubectl/</guid>
      <description>&lt;h1 id=&#34;kubectl&#34;&gt;Kubectl&lt;/h1&gt;
&lt;h2 id=&#34;kubectl-taint&#34;&gt;kubectl taint&lt;/h2&gt;
&lt;p&gt;Taints are used to prevent pods from being scheduled on certain nodes unless those pods have the appropriate tolerations.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;kubectl taint nodes node1 node2 node3 node-role.kubernetes.io/control-plane:NoSchedule-
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;node-role.kubernetes.io/control-plane:NoSchedule&lt;/code&gt; is a taint typically applied to control plane nodes to prevent regular pods from being scheduled on them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;-&lt;/code&gt; at the end removes the taint from the nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to add the taint instead of removing it, just remove the - at the end:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="kubectl">Kubectl</h1>
<h2 id="kubectl-taint">kubectl taint</h2>
<p>Taints are used to prevent pods from being scheduled on certain nodes unless those pods have the appropriate tolerations.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl taint nodes node1 node2 node3 node-role.kubernetes.io/control-plane:NoSchedule-
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p><code>node-role.kubernetes.io/control-plane:NoSchedule</code> is a taint typically applied to control plane nodes to prevent regular pods from being scheduled on them.</p>
</li>
<li>
<p>The <code>-</code> at the end removes the taint from the nodes.</p>
</li>
</ul>
<p>If you want to add the taint instead of removing it, just remove the - at the end:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl taint nodes node1 node2 node3 node-role.kubernetes.io/control-plane:NoSchedule
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="kubectl-wait">kubectl wait</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl <span class="nb">wait</span> -n istio-ingress --for<span class="o">=</span><span class="nv">condition</span><span class="o">=</span>programmed gateways.gateway.networking.k8s.io gateway
</span></span></code></pre></td></tr></table>
</div>
</div><p>This kubectl command waits for a specific condition (in this case, for the Gateway resource to be programmed) within a namespace called istio-ingress. Here&rsquo;s a breakdown of each part:</p>
<ul>
<li>
<p>kubectl wait: This command pauses execution until a resource or set of resources meets a specified condition.</p>
</li>
<li>
<p>-n istio-ingress: Specifies the namespace where the resource is located. In this case, the istio-ingress namespace.</p>
</li>
<li>
<p>&ndash;for=condition=programmed: Defines the condition that must be met. Here, it means the Gateway resource must reach the programmed state—indicating it has been properly configured and is ready for use.</p>
</li>
<li>
<p>gateways.gateway.networking.k8s.io: Specifies the type of resource being monitored. In this case, a Gateway resource from the gateway.networking.k8s.io API group.</p>
</li>
<li>
<p>gateway: The name of the specific Gateway resource you&rsquo;re monitoring.</p>
</li>
</ul>
<p>What does this mean in practice?</p>
<p>The command waits until the Gateway resource in the istio-ingress namespace is fully configured (i.e., programmed) and ready to handle network traffic. This is especially useful when you need to ensure that a network configuration or proxy (like an Istio Gateway) is ready before proceeding with a deployment or any other dependent operations.</p>
<p>If the Gateway does not reach the specified condition within the default timeout period (30 seconds by default, which can be adjusted using &ndash;timeout), the command will fail.</p>
<h2 id="plugins">Plugins</h2>
<ul>
<li>Basic Structure of a kubectl Plugin</li>
</ul>
<p>kubectl plugins are simply executables that start with the <code>kubectl-</code> prefix. When you execute a command like:</p>
<p><code>kubectl my-plugin</code></p>
<p>kubectl searches for an executable called kubectl-my-plugin in the directories listed in the PATH environment variable and executes it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>k9scli: Kubernetes cli to manage your clusters</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-k9scli/</link>
      <pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-k9scli/</guid>
      <description>&lt;h1 id=&#34;k9scli&#34;&gt;K9scli&lt;/h1&gt;
&lt;h2 id=&#34;shortcuts&#34;&gt;Shortcuts&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Ctrl-d: delete.
Ctrl-k: kill (no confirmation).
Ctrl-w: toggle wide columns. (Equivalent to kubectl … -o wide)
Ctrl-z: toggle error state
Ctrl-e: hide header.
Ctrl-s: save output (e.g. the YAML) to disk.
Ctrl-l: rollback.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;sort-by-column&#34;&gt;Sort by Column&lt;/h2&gt;
&lt;p&gt;If you want to sort any view (Pod/Services) based on some exact column - you can just press &lt;code&gt;Shift + Column Initial&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;e.g. If you want to sort items by column Age - Just press &lt;code&gt;Shift + A&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="k9scli">K9scli</h1>
<h2 id="shortcuts">Shortcuts</h2>
<pre tabindex="0"><code>Ctrl-d: delete.
Ctrl-k: kill (no confirmation).
Ctrl-w: toggle wide columns. (Equivalent to kubectl … -o wide)
Ctrl-z: toggle error state
Ctrl-e: hide header.
Ctrl-s: save output (e.g. the YAML) to disk.
Ctrl-l: rollback.
</code></pre><h2 id="sort-by-column">Sort by Column</h2>
<p>If you want to sort any view (Pod/Services) based on some exact column - you can just press <code>Shift + Column Initial</code></p>
<p>e.g. If you want to sort items by column Age - Just press <code>Shift + A</code></p>
<ul>
<li>Shift-c: sorts by CPU.</li>
<li>Shift-m: sorts by MEMORY.</li>
<li>Shift-s: sorts by STATUS.</li>
<li>Shift-n: sorts by name;</li>
<li>Shift-o: sorts by node;</li>
<li>Shift-i: sorts by IP address;</li>
<li>Shift-a: sorts by container age;</li>
<li>Shift-t: sorts by number of restarts;</li>
<li>Shift-r: sorts by pod readiness;</li>
</ul>
<h2 id="list-all-available-resources">List all available resources:</h2>
<p><code>:aliases</code> or <code>Ctrl-a</code>: list all available aliases and resources. <code>:crd:</code>  list all CRDs.</p>
<h2 id="filter">Filter</h2>
<ul>
<li><code>/&lt;filter&gt;</code> : regex filter.</li>
<li><code>/!&lt;filter&gt;</code> : inverse regex filter.</li>
<li><code>/-l &lt;label&gt;</code> : filter by labels.</li>
<li><code>/-f &lt;filter&gt;</code> : fuzzy match.</li>
</ul>
<h2 id="choose-context">Choose context</h2>
<p><code>:ctx</code>: list ctx, then select from the list.
<code>:ctx &lt;context&gt;</code>:  switch to the specified context.</p>
<h2 id="show-decrypted-secrets">Show Decrypted Secrets</h2>
<p>Type <code>:secrets</code> to list the secrets, then</p>
<ul>
<li>x to decrypt the secret.</li>
<li>Esc to leave the decrypted display.</li>
</ul>
<p><code>x</code>: decode a Secret.
<code>f</code>:  full screen. Tip: enter full screen mode before copying, to avoid in copied text.</p>
<h2 id="helm">Helm</h2>
<ul>
<li><code>:helm</code> : show helm releases.</li>
<li><code>:helm NAMESPACE</code> : show releases in a specific namespace.</li>
</ul>
<h2 id="xray-view">XRay View</h2>
<ul>
<li><code>:xray RESOURCE</code> , e.g. <code>:xray deploy</code>.</li>
</ul>
<h2 id="pulse-view">Pulse View</h2>
<ul>
<li><code>:pulse</code> : displays general information about the Kubernetes cluster.</li>
</ul>
<h2 id="show-disk-files">Show Disk Files</h2>
<ul>
<li><code>:dir /path</code></li>
</ul>
<p>E.g. <code>:dir /tmp</code> will show your <code>/tmp</code> folder on local disk. One common use case: <code>Ctrl-s</code> to save a yaml, then find it in <code>:dir /tmp/k9s-screens-root</code>, find the file, press e to edit and a to apply.</p>
<h2 id="benchmark">Benchmark</h2>
<p>k9s includes a basic HTTP load generator.</p>
<p>To enable it, you have to configure port forwarding in the pod. Select the pod and press <code>SHIFT + f</code>, go to the port-forward menu (using the pf alias).</p>
<p>After selecting the port and hitting <code>CTRL + b</code>, the benchmark would start. Its results are saved in <code>/tmp</code> for subsequent analysis.</p>
<p>To change the configuration of the benchmark, create the <code>$HOME/.k9s/bench-&lt;my_context&gt;.yml</code> file (unique for each cluster).</p>
<h2 id="check-resources-with-the-same-name-in-different-api-groups">Check Resources with the Same Name in Different API Groups</h2>
<p>e.g. Cluster may be found in different api groups, like <code>cluster.x-k8s.io</code> or <code>clusterregistry.k8s.io</code> or <code>baremetal.cluster.gke.io</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cluster.x-k8s.io/v1alpha3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">clusterregistry.k8s.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">baremetal.cluster.gke.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Use <code>apiVersion/kind</code> (i.e. <code>Group/Version/kind</code>) instead of just kind to check the API of a specific group.</p>
<pre tabindex="0"><code>:cluster.x-k8s.io/v1alpha3/clusters
:clusterregistry.k8s.io/v1alpha1/clusters
:baremetal.cluster.gke.io/v1/clusters
</code></pre><h2 id="change-log-setting">Change log setting</h2>
<p>Change <code>~/.config/k9s/config.yml</code>:</p>
<pre tabindex="0"><code>logger:
  tail: 500
  buffer: 5000
  sinceSeconds: -1
</code></pre><h2 id="cpumem-metrics"><a href="https://github.com/derailed/k9s/blob/master/change_logs/release_v0.13.4.md#cpumem-metrics">CPU/MEM Metrics</a></h2>
<p>A small change here based on <a href="https://github.com/binarycoded">Benjamin</a> excellent PR! We&rsquo;ve added 2 new columns for pod/container views to indicate percentages of resources request/limits if set on the containers. The columns have been renamed to represent the resources requests/limits as follows:</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Description</th>
          <th>Sort Keys</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>%CPU/R</td>
          <td>Percentage of requested cpu</td>
          <td>shift-x</td>
      </tr>
      <tr>
          <td>%MEM/R</td>
          <td>Percentage of requested memory</td>
          <td>shift-z</td>
      </tr>
      <tr>
          <td>%CPU/L</td>
          <td>Percentage of limited cpu</td>
          <td>ctrl-x</td>
      </tr>
      <tr>
          <td>%MEM/L</td>
          <td>Percentage of limited memory</td>
          <td>ctrl-z</td>
      </tr>
  </tbody>
</table>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">View: Pods<span class="o">(</span>&lt;namespace&gt;<span class="o">)[</span>number of pods listed<span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">NAME      pod name
</span></span><span class="line"><span class="cl">READY     number of pods in ready state / number of pods to be in ready state
</span></span><span class="line"><span class="cl">RESTARTS  number of <span class="nb">times</span> the pod has been restarted so far
</span></span><span class="line"><span class="cl">STATUS    state of the pod life cycle, such as Running <span class="p">|</span> ... <span class="p">|</span> Completed
</span></span><span class="line"><span class="cl">CPU       current CPU usage, unit is milli-vCPU
</span></span><span class="line"><span class="cl">MEM       current main memory usage, unit is MiB
</span></span><span class="line"><span class="cl">%CPU/R    current CPU usage as a percentage of what has been requested by the pod
</span></span><span class="line"><span class="cl">%MEM/R    current main memory usage as a percentage of what has been requested by the pod
</span></span><span class="line"><span class="cl">%CPU/L    current CPU usage as a percentage of the pod<span class="s1">&#39;s limit (it cannot go beyond its limit)
</span></span></span><span class="line"><span class="cl"><span class="s1">%MEM/L    current main memory usage as a percentage of the pod&#39;</span>s limit <span class="o">(</span>it cannot go beyond its limit<span class="o">)</span>
</span></span><span class="line"><span class="cl">IP        IP address of the pod
</span></span><span class="line"><span class="cl">NODE      name of the node the pod is running on
</span></span><span class="line"><span class="cl">AGE       age of the pod, units are indicated <span class="o">(</span><span class="nv">s</span> <span class="o">=</span> seconds, <span class="nv">m</span> <span class="o">=</span> minutes, <span class="nv">h</span> <span class="o">=</span> hours, <span class="nv">d</span> <span class="o">=</span> days<span class="o">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>What about CPU/A and MEM/A when you see the nodes?</p>
<ul>
<li>CPU/A is about the CPU allocatable (unit is milli-vCPU)</li>
<li>MEM/A is the memory allocatable (unit is MiB)</li>
</ul>
<p>if you&rsquo;re asking yourself like me, what a milli-vCPU is 1/1000 of
(Threads x Cores) x Physical CPU = Number vCPU</p>
<h2 id="views">Views</h2>
<ul>
<li>Example:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">views</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">v1/endpoints</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">columns</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">AGE|RW</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">NAME</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ENPOINTS|H</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">BLA:.subsets[*].ports[*].port</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">BOZO:.metadata.labels.app|W</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">BLEE:.metadata.creationTimestamp|T</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ZORG:.status.containersStatuses[*].restart</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p>keywords:</p>
<ul>
<li>W -&gt; show only in wide mode.</li>
<li>H -&gt; hide.</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Pod disruption budget (PDB)</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-pdb/</link>
      <pubDate>Sun, 23 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-pdb/</guid>
      <description>&lt;h1 id=&#34;pod-disruption-budget&#34;&gt;Pod disruption budget&lt;/h1&gt;
&lt;p&gt;PDBs define the minimum number of replicas that must remain running during disruptions. This prevents critical workloads from being evicted but can hinder node scaling.&lt;/p&gt;
&lt;p&gt;A PodDisruptionBudget (PDB) is a Kubernetes object that specifies the number of pods that can be unavailable in deployment, maintenance, or at any given time. This helps to ensure that your applications remain available even if some of their pods are terminated or evicted.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="pod-disruption-budget">Pod disruption budget</h1>
<p>PDBs define the minimum number of replicas that must remain running during disruptions. This prevents critical workloads from being evicted but can hinder node scaling.</p>
<p>A PodDisruptionBudget (PDB) is a Kubernetes object that specifies the number of pods that can be unavailable in deployment, maintenance, or at any given time. This helps to ensure that your applications remain available even if some of their pods are terminated or evicted.</p>
<p>Let’s take an example where my application has three pods (instances); I always want to have at least two running pods all the time; I can apply a PDB object which will guarantee that I will always have at least two running pods!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">policy/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">PodDisruptionBudget</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-pdb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">maxUnavailable</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">my-app</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>This configuration ensures one replica are always running but can block eviction when scaling down nodes, leaving some underutilized.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Kustomize: Kubernetes deployment</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-kustomize/</link>
      <pubDate>Sun, 16 Feb 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-kustomize/</guid>
      <description>kustomize lets you customize raw, template-free YAML files for multiple purposes, leaving the original YAML untouched and usable as is.</description>
      <content:encoded><![CDATA[<h1 id="kustomize">Kustomize</h1>
<p>Kustomize is an official sub-project of Kubernetes and is maintained by the Kubernetes SIG-CLI (Special Interest Group – Command Line Interface) community. It has gained popularity and is widely used in the Kubernetes ecosystem.</p>
<p>Kustomize is an open-source configuration management tool for Kubernetes.</p>
<p>It allows you to define and manage Kubernetes objects such as deployments, Daemonsets, services, configMaps, etc for multiple environments in a declarative manner without modifying the original YAML files. To put it simply, you have a single source of truth for YAMLs, and you patch required configurations on top of the base YAMLs as per the environment requirements.</p>
<p>Kustomize has two key concepts, Base and Overlays. With Kustomize we can reuse the base files (common YAMLs) across all environments and overlay (patches) specifications for each of those environments.</p>
<p>Overlaying is the process of creating a customized version of the manifest file (base manifest + overlay manifest = customized manifest file).</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_19-39-07.png" type="" alt=""  /></p>
<h2 id="kustomize-features-and-benefits">Kustomize Features and Benefits</h2>
<p>The following are the key features of Kustomize:</p>
<ol>
<li>Acts as a configuration tool with declarative configuration same as Kubernetes YAMLs.</li>
<li>It can modify resources without altering the original files.</li>
<li>It can add common labels and annotations to all the resources.</li>
<li>It can Modify container images based on the environment it is being deployed in.</li>
<li>Kustomize also ships with secretGenerator and configMapGenerator that use environment files or key-value pairs to create secrets and configMaps.</li>
</ol>
<p>Below are the benefits of Kustomize:</p>
<ul>
<li>Simplified Configuration Management: Kustomize is easy to use and allows you to manage and customize your Kubernetes configurations more easily by enabling you to define your configuration in a structured and modular way.</li>
<li>Reusability: With Kustomize we can reuse one of the base files across all environments and overlay specifications for each of those environments. This can save you time and effort by allowing you to reuse common configurations rather than having to create them from scratch for each new deployment.</li>
<li>Version Control: Kustomize allows you to version control your Kubernetes configurations, making it easier to track changes and roll back to previous configurations if necessary.</li>
<li>Template Free: Kustomize is template free. It expresses the full power of Kubernetes API, with no need to parameterize every single line compared to Helm.</li>
<li>Kustomize can be run natively from the Kubernetes command line interface.</li>
<li>Kustomize has built-in transformers to modify resources and It can be extended via a plug-in mechanism.</li>
<li>Kustomize does not have any templating language so we can use the usual YAML to state our configurations rapidly.</li>
<li>Kustomize is provided as a standalone Golang package and cli tool so it’s easy to integrate with users’ tools and workflows.</li>
<li>We can use Kustomize without installing it if we have kubectl 1.14+ version. kubectl allows us to make declarative changes to our configurations without touching a template.</li>
</ul>
<h2 id="kustomizationyamlfile">kustomization.yamlfile</h2>
<p>The kustomization.yaml file is the main file used by the Kustomize tool.</p>
<p>When you execute Kustomize, it looks for the file named kustomization.yaml. This file contains a list of all of the Kubernetes resources (YAML files) that should be managed by Kustomize. It also contains all the customizations that we want to apply to generate the customized manifest.</p>
<h2 id="base-and-overlays">Base and Overlays</h2>
<p>The Base folder represents the config that going to be identical across all the environments. We put all the Kubernetes manifests in the Base. It has a default value that we can overwrite.</p>
<p>On the other side, the Overlays folder allows us to customize the behavior on a per-environment basis. We can create an Overlay for each one of the environments. We specify all the properties and parameters that we want to overwrite &amp; change.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_19-52-30.png" type="" alt=""  /></p>
<p>Basically, Kustomize uses patch directive to introduce environment-specific changes on existing Base standard k8s config files without disturbing them.
Kustomize will check the base deployment file and compare it and patch the changes accordingly. That’s the beauty of Kustomize.</p>
<h2 id="transformers">Transformers</h2>
<p>As the name indicates, transformers are something that transforms one config into another. Using Transformers, we can transform our base Kubernetes YAML configs. Kustomize has several built-in transformers. Let’s see some common transformers:</p>
<ol>
<li>commonLabel – It adds a label to all Kubernetes resources</li>
<li>namePrefix – It adds a common prefix to all resource names</li>
<li>nameSuffix – It adds a common suffix to all resource names</li>
<li>Namespace – It adds a common namespace to all resources</li>
<li>commonAnnotations – It adds an annotation to all resources</li>
</ol>
<p>Let’s see an example. In the below image, we have used commonLabels in kustomization.yaml where label env: dev gets added to the customized deployment.yaml.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_19-56-09.png" type="" alt=""  /></p>
<h2 id="image-transformer">Image Transformer</h2>
<p>It allows us to modify an image that a specific deployment is going to use.</p>
<p>In the following example, the image transformer checks the nginx image name as mentioned deployment.yaml and changes it to the new name which is ubuntu in the kustomization.yaml file. We can change the tags as well.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_19-57-29.png" type="" alt=""  /></p>
<h2 id="patches-overlays">Patches (Overlays)</h2>
<p>Patches or overlays provide another method to modify Kubernetes configs. It provides more specific sections to change in the configuration. There are 3 parameters we need to provide:</p>
<ol>
<li>Operation Type: add or remove or replace</li>
<li>Target: Resource name which we want to modify</li>
<li>Value: Value name that will either be added or replaced. For the remove operation type, there would not be any value.</li>
</ol>
<p>There are two ways to define the patch:</p>
<ol>
<li>JSON 6902 and</li>
<li>Stragetic Merge Patching.</li>
</ol>
<h3 id="json-6902-patching">JSON 6902 Patching</h3>
<p>In this way, there are two details that we have to provide, the target and the patch details i.e. operation, path, and the new value.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">patches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">target</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">patch</span><span class="p">:</span><span class="w"> </span><span class="p">|-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      - op: replace
</span></span></span><span class="line"><span class="cl"><span class="sd">        path: /spec/replicas
</span></span></span><span class="line"><span class="cl"><span class="sd">        value: 5</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_20-03-50.png" type="" alt=""  /></p>
<h3 id="stragetic-merge-patching">Stragetic Merge Patching</h3>
<p>In this way, all the patch details are similar to a standard k8s config. It would be the original manifest file, we just add the fields that need to be modified.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">patches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">patch</span><span class="p">:</span><span class="w"> </span><span class="p">|-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      apiVersion: apps/v1
</span></span></span><span class="line"><span class="cl"><span class="sd">      kind: Deployment
</span></span></span><span class="line"><span class="cl"><span class="sd">      metadata:
</span></span></span><span class="line"><span class="cl"><span class="sd">        name: web-deployment
</span></span></span><span class="line"><span class="cl"><span class="sd">      spec:
</span></span></span><span class="line"><span class="cl"><span class="sd">        replicas: 5</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_20-05-15.png" type="" alt=""  /></p>
<h3 id="patch-from-file">Patch From File</h3>
<p>For both types of patching, instead of inline configs, we can use the separate file method. Specify all the patch details in a YAML file and refer it to the kustomization.yaml file under the patches directive.</p>
<p>For example, in kustomization.yaml you need to mention the patch file as follows. You need to specify the relative path of the YAML file.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">patches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">replicas.yaml</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>And we can put the changes in replicas.yaml as given below.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web-deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="review--apply-patches">Review &amp; Apply Patches</h2>
<p>Let’s review the patches. We can use the below command to review the patches and check whether everything is correct or not.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kustomize build overlays/dev
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="deploy">Deploy</h2>
<p>We can deploy the customized manifest using the following command.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kustomize build overlays/dev <span class="p">|</span> kubectl apply -f -
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can also use the following kubectl command.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl apply -k overlays/dev
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="kustomize-configmap-and-secret-generators">Kustomize Configmap and Secret Generators</h2>
<p>Kustomize has the functionality to generate Configmaps and Secrets.</p>
<p>In Kustomization YAML there are two supported fields</p>
<ul>
<li>configMapGenerator and</li>
<li>secretGenerator</li>
</ul>
<p>Let’s understand what problem it solves.</p>
<p>When you update a configmap attached to a pod as a volume, the configmap data gets propagated to the pod automatically. However, the pod does not get the latest data in the configmap in the following scenarios.</p>
<ul>
<li>If the pod gets environment variables from the configmap.</li>
<li>If the configmap is mounted as a volume using a subpath.</li>
</ul>
<p>In the above cases, the pod will continue using the old configmap data until we restart the pod. Because the pod is unaware of what got changed in configMap.</p>
<p>Essentially, the data from the ConfigMaps (such as properties, environment variables, etc.) is used by applications during their startup. So even if the updated configmap data is projected to the pod, if the application running inside the pod doesn’t have any hot reload mechanism, you will have to restart the pod for the changes to take place.</p>
<p>What options do we have to solve this issue?</p>
<ul>
<li>You can use Reloader Controller.</li>
<li>Using Kustomize ConfigMap Generator</li>
</ul>
<p>How the Kustoimize Configmap/Secret generator work.</p>
<ol>
<li>Kustomize generator creates a configMap and Secret with a unique name(hash) at the end. For example, if the name of the configmap is app-configmap, the generated one would have the name app-configmap-7b58b6ct6d. Here 7b58b6ct6d is the appended hash.</li>
<li>If you update the configmap/Secret, it will create a new configMap/Secret with the same name with a different hash(random sets of characters) at the end.</li>
<li>Kustomize will automatically update the Deployment with the new configmap name.</li>
<li>The moment Deployment is updated by Kustomize, a rollout will be triggered and the application runs on the pod and gets the updated configmap/secret data. In this way, we don’t need to redeploy or restart the deployment.</li>
</ol>
<p><img loading="lazy" src="/posts/kubernetes/k8s-kustomize/assets/index_2025-02-16_20-34-29.png" type="" alt=""  /></p>
<p>Following are the important points you should know about the Kustomize generators.</p>
<ul>
<li>Since Kustomize creates a new configmap every time there is an update, you need to garbage-collect your old orphaned Configmaps. If you have resource quota limits set for namespace, orphaned Configmaps could be an issue. Or you should use the –prune flag with labels in the kubectl apply command. Also, GitOps tools like ArgoCD offer Orphaned resource monitoring mechanisms.</li>
<li>You can use the disableNameSuffixHash: true flag to disable creating new Configmaps on every update, but it does not trigger a pod rollout. You need to manually trigger a rollout for pods to get the latest configmap data. Or the application running inside the pod should have a hot-reload mechanism.</li>
</ul>
<p>Here is the file structure of the repository. To understand the generators, we will use the generators overlay folder.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">├── base
</span></span><span class="line"><span class="cl">│   ├── deployment.yaml
</span></span><span class="line"><span class="cl">│   ├── kustomization.yaml
</span></span><span class="line"><span class="cl">│   └── service.yaml
</span></span><span class="line"><span class="cl">└── overlays
</span></span><span class="line"><span class="cl">    ├── dev
</span></span><span class="line"><span class="cl">    │   ├── deployment-dev.yaml
</span></span><span class="line"><span class="cl">    │   ├── kustomization.yaml
</span></span><span class="line"><span class="cl">    │   └── service-dev.yaml
</span></span><span class="line"><span class="cl">    ├── generators
</span></span><span class="line"><span class="cl">    │   ├── deployment.yaml
</span></span><span class="line"><span class="cl">    │   ├── files
</span></span><span class="line"><span class="cl">    │   │   └── index.html
</span></span><span class="line"><span class="cl">    │   ├── kustomization.yaml
</span></span><span class="line"><span class="cl">    │   └── service.yaml
</span></span><span class="line"><span class="cl">    └── prod
</span></span><span class="line"><span class="cl">        ├── deployment-prod.yaml
</span></span><span class="line"><span class="cl">        ├── kustomization.yaml
</span></span><span class="line"><span class="cl">        └── service-prod.yaml
</span></span></code></pre></td></tr></table>
</div>
</div><p>The configmap generation options should be added to the kustomization.yaml file under configMapGenerator field.</p>
<p>If you want to prune the orphaned Configmaps, use the –prune flag with the configmap label as shown below. The &ndash;prune flag instructs Kustomize to remove any resources from the final output that is no longer referenced or required.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kustomize build overlays/generators <span class="p">|</span> kubectl apply --prune -l <span class="nv">app</span><span class="o">=</span>web-service  -f -
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="disabling-hashed-configmap">Disabling Hashed ConfigMap</h3>
<p>If you don’t want to create hashed Configmaps using the ConfigMap generator, you can disable it by setting the disableNameSuffixHash flag to true under generatorOptions. It will disable the hash for all the Configmaps mentioned in the kustomization.yaml file.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">generatorOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">web-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">disableNameSuffixHash</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Note: If you disable Configmap hash, you need to manually restart the pods for the configmap data to be consumed by the application.</p>
<h2 id="generate-secrets-using-kustomize">Generate Secrets Using Kustomize</h2>
<p>You can generate secrets the same way you generate Configmaps.</p>
<p>For generating secrets, you need to use the secretGenerator field.</p>
<p>Here is an example of generating a secret object from a file.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">secretGenerator</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">files</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">files/secret.txt</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If you want to generate secrets from literals, use the following format.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">secretGenerator</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx-api-password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">literals</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">password=&#34;myS3cret&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>You can mount the secret as a volume or propagate it as an environment variable as per your requirements.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>While developing or before pushing to git, run kubectl kustomize cfg fmt file_name  command to format the file and set the indentation right.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Installing talos on a Turing Piv2 board with cm4 modules</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-talos/install/</link>
      <pubDate>Sun, 05 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-talos/install/</guid>
      <description>Installing talos on a Turing Piv2 board with cm4 modules.</description>
      <content:encoded><![CDATA[<h1 id="installing-talos-on-a-turing-piv2-board-with-cm4-modules">Installing talos on a Turing Piv2 board with cm4 modules.</h1>
<h2 id="flash-image-to-all-nodes">Flash image to all nodes</h2>
<ol>
<li>
<p>download metal-arm64.raw.xz from <a href="https://github.com/siderolabs/talos/releases">https://github.com/siderolabs/talos/releases</a></p>
</li>
<li>
<p>after putting cm4 in usb mode (use rpi cm4 emmc usb programming stick or use tpiv2, see: <a href="https://help.turingpi.com/hc/en-us/articles/8687165986205-Install-OS">https://help.turingpi.com/hc/en-us/articles/8687165986205-Install-OS</a>)</p>
</li>
<li>
<p>flash talos, this works for sd card and emmc storage. Check the correct device name.</p>
<pre tabindex="0"><code>time xz -d &lt; metal-arm64.raw.xz | sudo dd of=/dev/sda bs=1M status=progress conv=fsync
893665280 bytes (894 MB, 852 MiB) copied, 3 s, 298 MB/s1306525696 bytes (1,3 GB, 1,2 GiB) copied, 3,82763 s, 341 MB/s

0+152937 records in
0+152937 records out
1306525696 bytes (1,3 GB, 1,2 GiB) copied, 99,591 s, 13,1 MB/s

real	1m39,600s
user	0m3,607s
sys	0m0,226s
</code></pre></li>
<li>
<p>make sure tpiv4 node is no longer in device mode, it should be host mode for normal operation.</p>
</li>
<li>
<p>optionally log in to bmc and connect to serial console of the node with minicom.</p>
</li>
</ol>
<h2 id="minicom-example-for-node2">Minicom example for node2</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">microcom -s <span class="m">115200</span> /dev/ttyS1
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="hardwired-bmc-serial-port-connections-to-nodes">Hardwired bmc serial port connections to nodes</h2>
<table>
  <thead>
      <tr>
          <th>Node</th>
          <th>bmc device</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Node 1</td>
          <td>/dev/ttyS2</td>
      </tr>
      <tr>
          <td>Node 2</td>
          <td>/dev/ttyS1</td>
      </tr>
      <tr>
          <td>Node 3</td>
          <td>/dev/ttyS4</td>
      </tr>
      <tr>
          <td>Node 4</td>
          <td>/dev/ttyS5</td>
      </tr>
  </tbody>
</table>
<h2 id="boot-nodes">Boot nodes</h2>
<ol>
<li>
<p>boot all the nodes by powering down and up using the bmc</p>
</li>
<li>
<p>Check talos&rsquo; API port</p>
</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nc -zv 192.168.1.71 <span class="m">50000</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="create-cluster">Create Cluster</h2>
<h3 id="generate-cluster-files">Generate cluster files</h3>
<ol>
<li>Generate secrets:</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">talosctl gen secrets -o secrets.yaml
</span></span></code></pre></td></tr></table>
</div>
</div><ol>
<li>Generate control and worker config:</li>
</ol>
<p>Caveats (according talos <a href="https://www.talos.dev/v1.9/talos-guides/network/vip/#choose-your-shared-ip">documentation</a>):</p>
<p>Since VIP functionality relies on etcd for elections, the shared IP will not come alive until after you have bootstrapped Kubernetes.
Don’t use the VIP as the endpoint in the talosconfig, as the VIP is bound to etcd and kube-apiserver health, and you will not be able to recover from a failure of either of those components using Talos API.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">talosctl gen config --with-secrets secrets.yaml <span class="s2">&#34;clustername&#34;</span> https://CONTROL_PLANE_IP:6443
</span></span><span class="line"><span class="cl">talosctl gen config --with-secrets secrets.yaml <span class="s2">&#34;comanche&#34;</span> https://192.168.1.71:6443
</span></span></code></pre></td></tr></table>
</div>
</div><ol start="3">
<li>Edit controlplane.yaml:</li>
</ol>
<ul>
<li>set <code>controlPlane.scheduler.disabled: false</code>, I want control plane nodes to schedule work.</li>
<li>set <code>interface</code> with the value output from <code>talosctl -n node3 get links</code></li>
<li>add the VIP ipadress to network stanza:</li>
</ul>
<p>I have:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">      </span><span class="nt">network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">node3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">interfaces</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">dhcp</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">interface</span><span class="p">:</span><span class="w"> </span><span class="l">enxd83addbb1c3a</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">vip</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">ip</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.1.59</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">nameservers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">8.8.8.8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">8.8.4.4</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>set <code>install.disk:</code> to <code>/dev/mmcblk0</code></li>
<li>optionally set <code>install.wipe: true</code></li>
</ul>
<ol start="4">
<li>For each node (I have 3, an uneven number of control nodes is recommended in k8s):</li>
</ol>
<ul>
<li>change hostname: <code>network.hostname: nodeX</code> (set X to cm4 number)</li>
</ul>
<ol>
<li>apply config to cm4:</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">talosctl apply-config --insecure -n &lt;CM4 ipadres&gt; --file controlplane.yaml
</span></span><span class="line"><span class="cl">talosctl apply-config --insecure -n 192.168.1.71 --file controlplane-node1.yaml
</span></span><span class="line"><span class="cl">talosctl apply-config --insecure -n 192.168.1.72 --file controlplane-node2.yaml
</span></span><span class="line"><span class="cl">talosctl apply-config --insecure -n 192.168.1.73 --file controlplane-node3.yaml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>  118.061987<span class="o">]</span> <span class="o">[</span>talos<span class="o">]</span> etcd is waiting to join the cluster, <span class="k">if</span> this node is the first node in the cluster, please run <span class="sb">`</span>talosctl bootstrap<span class="sb">`</span> against one of the following IPs:
</span></span><span class="line"><span class="cl"><span class="o">[</span>  118.080674<span class="o">]</span> <span class="o">[</span>talos<span class="o">]</span> <span class="o">[</span>192.168.1.71<span class="o">]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="bootstrap-cluster-by-one-of-the-control-nodes">Bootstrap cluster by one of the control nodes.</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">   talosctl  -n &lt;CM4 ipadres&gt; -e &lt;CM4 ipadres&gt; --talosconfig ./talosconfig bootstrap
</span></span><span class="line"><span class="cl">talosctl --talosconfig ~/.talos/talosconfig -n 192.168.1.71 -e 192.168.1.71 bootstrap
</span></span><span class="line"><span class="cl"><span class="o">[</span>  259.296059<span class="o">]</span> <span class="o">[</span>talos<span class="o">]</span> bootstrap request received
</span></span></code></pre></td></tr></table>
</div>
</div><p>Grab ☕</p>
<h3 id="generate-kubeconfig">Generate kubeconfig.</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">talosctl kubeconfig -f -n &lt;VIP&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">unset</span> KUBECONFIG
</span></span><span class="line"><span class="cl">talosctl kubeconfig --talosconfig talosconfig -n 192.168.1.71 -e 192.168.1.71
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="watch-cluster">Watch cluster</h3>
<ol>
<li><code>watch -n 1.5 kubectl --kubeconfig=./kubeconfig --request-timeout=1s get pods,deployment,services,nodes -A -o wide</code></li>
</ol>
<p>talosctl config nodes 192.168.1.71,192.168.1.72,192.168.1.73 &ndash;talosconfig ~/.talos/talosconfig
talosctl config endpoint 192.168.1.71,192.168.1.72,192.168.1.73 &ndash;talosconfig ~/.talos/talosconfig</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ talosctl -n node1 health
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">discovered nodes: <span class="o">[</span><span class="s2">&#34;192.168.1.59&#34;</span> <span class="s2">&#34;192.168.1.72&#34;</span> <span class="s2">&#34;192.168.1.73&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd to be healthy: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd to be healthy: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be consistent across nodes: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be consistent across nodes: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be control plane nodes: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> etcd members to be control plane nodes: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> apid to be ready: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> apid to be ready: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes memory sizes: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes memory sizes: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes disk sizes: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes disk sizes: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> kubelet to be healthy: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> kubelet to be healthy: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes to finish boot sequence: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all nodes to finish boot sequence: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report ready: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report ready: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all control plane static pods to be running: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all control plane static pods to be running: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all control plane components to be ready: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all control plane components to be ready: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> kube-proxy to report ready: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> kube-proxy to report ready: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> coredns to report ready: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> coredns to report ready: OK
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report schedulable: ...
</span></span><span class="line"><span class="cl">waiting <span class="k">for</span> all k8s nodes to report schedulable: OK
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ talosctl --talosconfig ~/.talos/talosconfig config info
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Current context:     comanche
</span></span><span class="line"><span class="cl">Nodes:               192.168.1.71,192.168.1.72,192.168.1.73
</span></span><span class="line"><span class="cl">Endpoints:           192.168.1.71,192.168.1.72,192.168.1.73
</span></span><span class="line"><span class="cl">Roles:               os:admin
</span></span><span class="line"><span class="cl">Certificate expires: <span class="m">1</span> year from now <span class="o">(</span>2025-12-31<span class="o">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ talosctl -n node1 get admissioncontrolconfigs.kubernetes.talos.dev admission-control -o yaml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">node: node1
</span></span><span class="line"><span class="cl">metadata:
</span></span><span class="line"><span class="cl">    namespace: controlplane
</span></span><span class="line"><span class="cl">    type: AdmissionControlConfigs.kubernetes.talos.dev
</span></span><span class="line"><span class="cl">    id: admission-control
</span></span><span class="line"><span class="cl">    version: <span class="m">1</span>
</span></span><span class="line"><span class="cl">    owner: k8s.ControlPlaneAdmissionControlController
</span></span><span class="line"><span class="cl">    phase: running
</span></span><span class="line"><span class="cl">    created: 2025-01-01T19:14:12Z
</span></span><span class="line"><span class="cl">    updated: 1970-01-01T00:00:09Z
</span></span><span class="line"><span class="cl">spec:
</span></span><span class="line"><span class="cl">    config:
</span></span><span class="line"><span class="cl">        - name: PodSecurity
</span></span><span class="line"><span class="cl">          configuration:
</span></span><span class="line"><span class="cl">            apiVersion: pod-security.admission.config.k8s.io/v1alpha1
</span></span><span class="line"><span class="cl">            defaults:
</span></span><span class="line"><span class="cl">                audit: restricted
</span></span><span class="line"><span class="cl">                audit-version: latest
</span></span><span class="line"><span class="cl">                enforce: baseline
</span></span><span class="line"><span class="cl">                enforce-version: latest
</span></span><span class="line"><span class="cl">                warn: restricted
</span></span><span class="line"><span class="cl">                warn-version: latest
</span></span><span class="line"><span class="cl">            exemptions:
</span></span><span class="line"><span class="cl">                namespaces:
</span></span><span class="line"><span class="cl">                    - kube-system
</span></span><span class="line"><span class="cl">                runtimeClasses: <span class="o">[]</span>
</span></span><span class="line"><span class="cl">                usernames: <span class="o">[]</span>
</span></span><span class="line"><span class="cl">            kind: PodSecurityConfiguration
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ talosctl -n node1 logs kubelet
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ talosctl patch mc --talosconfig talosconfig --nodes 192.168.1.71 -e 192.168.1.71 --patch @patch.yaml
</span></span><span class="line"><span class="cl">WARNING: 192.168.1.71: server version 1.7.6 is older than client version 1.9.1
</span></span><span class="line"><span class="cl">patched MachineConfigs.config.talos.dev/v1alpha1 at the node 192.168.1.71
</span></span><span class="line"><span class="cl">Applied configuration without a reboot
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="client-installation">Client installation</h2>
<ul>
<li>MacOS:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -LO https://github.com/siderolabs/talos/releases/download/v1.7.6/talosctl-darwin-amd64
</span></span><span class="line"><span class="cl">chmod +x talosctl-darwin-amd64
</span></span><span class="line"><span class="cl">sudo mv talosctl-darwin-amd64 /usr/local/bin/talosctl
</span></span><span class="line"><span class="cl">talosctl version
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="operations">Operations</h2>
<ul>
<li>Show machine configuration:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">talosctl -n node3 get machineconfig -o yaml
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>K8S Monitoring</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-monitoring/</link>
      <pubDate>Sun, 18 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-monitoring/</guid>
      <description>Kubernetes monitoring</description>
      <content:encoded><![CDATA[<h2 id="readiness--liveness">Readiness &amp; Liveness</h2>
<ul>
<li>
<p>Readiness Probe: This probe determines if a pod is ready to start receiving traffic. Use it to ensure that the pod is fully initialized and ready to handle requests.</p>
</li>
<li>
<p>Liveness Probe: This probe checks the health of the pod on an ongoing basis. It ensures that the pod is still functioning correctly and can continue to receive traffic. If the liveness probe fails, the pod will be restarted.</p>
</li>
</ul>
<p>Your api should have <code>/health</code> endpoint.</p>
<p>Use readiness to check on things before routing traffic to it for instance.
Use liveness to check constantly if you should continue to receive traffic else reschedule.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>K8S Networking</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-networking/</link>
      <pubDate>Sun, 14 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-networking/</guid>
      <description>Kubernetes networking</description>
      <content:encoded><![CDATA[<h2 id="cni-plugins">CNI plugins</h2>
<p>CNI (<strong>Container Network Interface</strong>) is a standard API which allows different network implementations to plug into Kubernetes. Kubernetes calls the API any time a pod is being created or destroyed. There are two types of CNI plugins:</p>
<ul>
<li>CNI network plugins: responsible for adding or deleting pods to/from the Kubernetes pod network. This includes creating/deleting each pod’s network interface and connecting/disconnecting it to the rest of the network implementation.</li>
<li>CNI IPAM plugins: responsible for allocating and releasing IP addresses for pods as they are created or deleted. Depending on the plugin, this may include allocating one or more ranges of IP addresses (CIDRs) to each node, or obtaining IP addresses from an underlying public cloud’s network to allocate to pods.</li>
</ul>
<h3 id="cloud-provider-integrations">Cloud provider integrations</h3>
<p>Kubernetes cloud provider integrations are cloud-specific controllers that can configure the underlying cloud network to help provide Kubernetes networking. Depending on the cloud provider, this could include automatically programming routes into the underlying cloud network so it knows natively how to route pod traffic.</p>
<h3 id="kubenet">Kubenet</h3>
<p>Kubenet is an extremely basic network plugin built into Kubernetes. It does not implement cross-node networking or network policy. It is typically used together with a cloud provider integration that sets up routes in the cloud provider network for communication between nodes, or in single node environments. Kubenet is not compatible with Calico.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>K8S Troubleshooting</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-troubleshooting/</link>
      <pubDate>Sat, 13 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-troubleshooting/</guid>
      <description>Kubernetes troubleshooting</description>
      <content:encoded><![CDATA[<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl --v<span class="o">=</span><span class="m">9</span> get pods
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="authorization--authentication">Authorization / Authentication</h2>
<p>Review the API server logs to get more details about the authentication error. This may provide additional clues as to why the request is failing.</p>
<p>Make sure you can reach the Kubernetes API server from your machine:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -k https://&lt;api-server-address&gt;:&lt;port&gt;/healthz
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -k https://&lt;api-server-address&gt;:&lt;port&gt;/api/v1/namespaces --header <span class="s2">&#34;Authorization: Bearer &lt;token&gt;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl auth can-i --list
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">chmod <span class="m">600</span> /path/to/admin.crt /path/to/admin.key /path/to/ca.crt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl config view -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.users}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl --cert /path/to/admin.crt --key /path/to/admin.key --cacert /path/to/ca.crt https://&lt;api-server-address&gt;:&lt;port&gt;/api/v1/namespaces
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>K8S Configuration Overview</title>
      <link>https://albertogalvez.com/posts/kubernetes/k8s-configuration/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://albertogalvez.com/posts/kubernetes/k8s-configuration/</guid>
      <description>&lt;p&gt;Initially, Kubernetes was build to manage Docker Containers. Docker is the technology that made containers accessible to the mainstream. Containers were not an entirely new technology. LXC containers existed before and Linux namespaces are a thing since 2002, but Docker made it so easy to use containers. By handling software packaging, distribution and all low level network and device configuration, Docker allowed every developer to start any application in an isolated environment. Promising to finally overcoming the famous: It works on my machine meme.
Even the Kubernetes core team admits that Kubernetes would&amp;rsquo;ve been such a success without Docker.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Initially, Kubernetes was build to manage Docker Containers. Docker is the technology that made containers accessible to the mainstream. Containers were not an entirely new technology. LXC containers existed before and Linux namespaces are a thing since 2002, but Docker made it so easy to use containers. By handling software packaging, distribution and all low level network and device configuration, Docker allowed every developer to start any application in an isolated environment. Promising to finally overcoming the famous: It works on my machine meme.
Even the Kubernetes core team admits that Kubernetes would&rsquo;ve been such a success without Docker.</p>
<p>At some point, a lot of different people and companies tried to solve similar problems regarding containers. They agreed to build some common interfaces for their solutions so that parts of their implementations could be swapped while relying on certain standards. This led to the creation of the Open Container Initiative (OCI), Container Network Interface (CNI), Container Storage Interface (CSI) and Container Runtime Interface (CRI).</p>
<h2 id="labels">Labels</h2>
<p>A consistent labeling strategy helps you filter resources quickly and maintain a clear mental map of your cluster.</p>
<ul>
<li>Labels: Key-value pairs used for grouping and selecting Kubernetes objects. For instance, app=my-app, env=staging, team=payments.</li>
<li>Annotations: Key-value pairs for attaching non-identifying metadata (e.g., version info, contact email, or last-deployed timestamp).</li>
</ul>
<h3 id="safe-to-evict-annotations">Safe-to-Evict Annotations</h3>
<p>Pods marked with safe-to-evict: false cannot be removed or rescheduled during node scaling or bin-packing operations.</p>
<p>Annotation Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cluster-autoscaler.kubernetes.io/safe-to-evict</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Naked Pods</li>
</ul>
<p>Pods created directly without a controller (e.g., Deployments, ReplicaSets) lack the automation needed for rescheduling or scaling.</p>
<p>Naked Pod Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">standalone-pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app-container</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">app-image:v1</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>These pods prevent nodes from being scaled down, leading to idle resources.</p>
<ul>
<li>Removing Safe-to-Evict Annotations</li>
</ul>
<p>Safe-to-evict annotations prevent pods from being evicted by the autoscaler. To find pods with this annotation:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">kubectl get pods --all-namespaces -o json <span class="p">|</span> jq <span class="s1">&#39;.items[] | select(.metadata.annotations.&#34;cluster-autoscaler.kubernetes.io/safe-to-evict&#34; == &#34;false&#34;) | .metadata.name&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once identified, edit the pod configuration to remove the annotation.</p>
<h2 id="namespaces">Namespaces</h2>
<p>Why Use Namespaces?</p>
<p>Namespaces help:</p>
<ul>
<li>Isolate Resources: Separate applications and environments (e.g., development, staging, production) within the same cluster.</li>
<li>Manage Access: Apply role-based access control (RBAC) for specific namespaces.</li>
<li>Organize Resources: Group related resources logically for clarity.</li>
</ul>
<p>Namespace Like Your Life Depends on It</p>
<ul>
<li>Team-based namespaces: Dev, QA, Prod, or per microservice if that makes sense.</li>
<li>Access Control: Combine namespaces with RBAC (Role-Based Access Control) policies to ensure that only the right people (and services) can mess with your stuff.</li>
<li>Resource Quotas: You can set quotas (e.g., CPU, memory) per namespace, preventing one rogue microservice from hogging all resources.</li>
</ul>
<p>Take a step back and design your namespace strategy; future you will say thanks.</p>
<h2 id="cri">CRI</h2>
<p>With version 1.24, Kubernetes has overcome its dependence on Docker and now only relies on any CRI compliant runtime to start containers.
The containerd project was originally developed by Docker Inc and still is the core of Docker, but its ownership was since donated to the CNCF.
When a new Pod is scheduled to a node, the kubelet communicates with containerd over the container runtime interface to actually pull the image and start the container with the configuration specified in the pod definition. Containerd is now in charge to set up the Linux namespace, mount volumes or devices and apply seccomp profiles and much more for every new container. That&rsquo;s a lot of tasks, and not quite according to the UNIX philosophy to do one thing and do it well.</p>
<p>By default, containerd calls runc. The runc binary actually spawns the container process with all its applied properties. It does not pull images, manages the containers file system, sets up networking or monitors the container process. Overly simplified it is just a spawn syscall with a bunch of extra properties for some isolation, but it still runs on the same kernel since containers are NOT VMs.</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-overall.jpg" type="" alt=""  /></p>
<h2 id="master-components">Master Components</h2>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-master-components.jpg" type="" alt=""  /></p>
<h2 id="configuration">Configuration</h2>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-config.jpg" type="" alt=""  /></p>
<h2 id="networking">Networking</h2>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-net.jpg" type="" alt=""  /></p>
<h2 id="resources">Resources</h2>
<ul>
<li><strong>Resource Requests</strong>: This is basically your container’s baseline. If your container requests 200m CPU and 512Mi of memory, the Kubernetes scheduler will place your Pod on a node with at least that much capacity available.</li>
<li><strong>Resource Limits:</strong>:  This is the upper bound. If your container tries to exceed the limit, it might get throttled (CPU) or even evicted (memory).</li>
</ul>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-optimization-resources.jpg" type="" alt=""  /></p>
<p>Why do we need Requests &amp; Limits?</p>
<ul>
<li>Ensure fair resource sharing between containers.</li>
<li>Prevent resource hogging by any container.</li>
<li>Help scheduler make informed decisions about where to place pods.</li>
<li>Prevent node crashes from overcommitted memory.</li>
</ul>
<p>Sample YAML:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">resource-demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">busybox</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">busybox</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;sh&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;while true; do echo Hello; sleep 1; done&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;64Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;250m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Explanation:</p>
<ul>
<li>250m means 250 millicores of CPU (¼ core).</li>
<li>The container requests 64Mi memory but can&rsquo;t exceed 128Mi.</li>
</ul>
<p>How Scheduling Works with Requests:</p>
<pre tabindex="0"><code>Step 1: You apply the pod
Step 2: Scheduler looks for a node with &gt;= requested CPU/memory
Step 3: Pod is scheduled there
Step 4: During runtime, if pod tries to exceed limit:
            - For CPU: throttled
            - For Memory: killed (OOMKilled)
</code></pre><p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/index_2025-04-13_20-24-30.png" type="" alt=""  /></p>
<p>Let&rsquo;s check out QoS (Quality of Service) Classes as well, how Kubernetes uses requests and limits to categorize pods, and how that affects scheduling and eviction.</p>
<h3 id="kubernetes-qos-classes">Kubernetes QoS Classes</h3>
<p>Kubernetes assigns Quality of Service classes to pods based on how you set requests and limits for CPU and memory:</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/index_2025-04-13_20-25-42.png" type="" alt=""  /></p>
<p>Examples:</p>
<ol>
<li>Guaranteed Pod (QoS Class: Guaranteed)</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ol start="2">
<li>Burstable Pod (QoS Class: Burstable)</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;250m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;256Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ol start="3">
<li>BestEffort Pod (QoS Class: BestEffort)</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># No resources section</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/index_2025-04-13_20-27-41.png" type="" alt=""  /></p>
<p>When Node Memory Hits Limit (e.g., High Load)</p>
<p>Eviction Policy Triggered, Kubernetes starts evicting based on QoS priority.</p>
<p>1️. Pod C (BestEffort) → EVICTED first
2️. Pod B or D (Burstable) → May be evicted
3️. Pod A (Guaranteed) → Most protected</p>
<p>Example YAML: Pod with Resource Settings</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">guaranteed-pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;500m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>This Pod gets Guaranteed QoS class.</p>
<p>Flow:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">Client applies pod YAML</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">▼</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">Pod scheduled on node with enough resources</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">▼</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">Node allocates CPU &amp; Memory based on &#34;requests&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">▼</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">If pod exceeds &#34;limits&#34; → It gets throttled or OOMKilled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">▼</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">During pressure → Eviction happens in QoS order</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">BestEffort → Burstable → Guaranteed</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Eviction Priority (Important Concept)</p>
<p>When node runs out of memory, Kubernetes evicts pods based on QoS class:</p>
<pre tabindex="0"><code>BestEffort → Burstable → Guaranteed
 (first)                   (last)
</code></pre><p>So, Guaranteed pods are safest, and BestEffort gets evicted first.</p>
<p>Behavior During Resource Pressure:</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/index_2025-04-13_20-30-58.png" type="" alt=""  /></p>
<p>Multi-Container Pod Example</p>
<p>Let&rsquo;s look at a pod with two containers and different QoS setups:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">multi-demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;100Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;100Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">sidecar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">busybox</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;sh&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;while true; do echo sidecar; sleep 2; done&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;64Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;100m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Resulting QoS for the Pod: Burstable, since sidecar has request ≠ limit</p>
<p>Best Practices</p>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/index_2025-04-13_20-32-35.png" type="" alt=""  /></p>
<h2 id="taints">Taints</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">➜  kubectl describe node node1 <span class="p">|</span> grep Taints
</span></span><span class="line"><span class="cl">Taints:             node-role.kubernetes.io/control-plane:NoSchedule
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">➜  kubectl taint nodes node1 node-role.kubernetes.io/control-plane:NoSchedule-
</span></span><span class="line"><span class="cl">node/node1 untainted
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="security">Security</h2>
<p><img loading="lazy" src="/posts/kubernetes/k8s-configuration/assets/k8s-security.jpg" type="" alt=""  /></p>
<h3 id="secrets">Secrets</h3>
<p>Kubernetes expects the values in stringData to be strings (string)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stringData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;12345&#34;</span><span class="w">  </span><span class="c"># ✅ It is now a string</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If you use data, you must ensure that the values are in Base64:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Opaque</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">MTIzNDU= </span><span class="w"> </span><span class="c"># &#34;12345&#34; in Base64</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>echo -n &quot;12345&quot; | base64</code></p>
<h2 id="probes">Probes</h2>
<p>Probes are how Kubernetes checks if your containers are alive and ready to serve traffic.</p>
<p>They help Kubernetes:</p>
<ul>
<li>Know if a container needs to be restarted (if it&rsquo;s unhealthy).</li>
<li>Know if a container is ready to receive traffic.</li>
</ul>
<p>Three Types of Probes</p>
<ol>
<li>Liveness Probe</li>
</ol>
<p>Checks if the container is alive (i.e., not stuck, unresponsive).</p>
<ul>
<li>If the probe fails, Kubernetes restarts the container.</li>
<li>Useful for apps that might get into a deadlock or stuck state.</li>
</ul>
<ol start="2">
<li>Readiness Probe</li>
</ol>
<p>Checks if the container is ready to accept traffic.</p>
<ul>
<li>If it fails, traffic is not sent to the container.</li>
<li>Very useful when your app takes time to initialize or depend on something external.</li>
</ul>
<ol start="3">
<li>Startup Probe</li>
</ol>
<p>Used to delay liveness + readiness checks until the app has fully started.</p>
<ul>
<li>Prevents false failures when app takes longer to start.</li>
<li>Once successful, other probes kick in.</li>
</ul>
<p>Probe Mechanisms (How They Work)
a. Exec Probes</p>
<ul>
<li>Runs a command inside the container.</li>
<li>If exit code = 0, success; else failure.</li>
</ul>
<p>Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">exec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cat</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/tmp/healthy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>b. HTTP Probes</p>
<ul>
<li>Makes an HTTP GET request to a specific path/port.</li>
<li>If it returns 2xx or 3xx status code → success.</li>
</ul>
<p>Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>c. TCP Probes</p>
<ul>
<li>Tries to open a TCP socket to the container on a specific port.</li>
<li>If it connects, it&rsquo;s considered healthy.</li>
</ul>
<p>Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">startupProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tcpSocket</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">3306</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Anatomy of a Probe</p>
<p>Each probe has key fields:</p>
<ul>
<li>initialDelaySeconds: 10   # wait before first probe</li>
<li>periodSeconds: 5          # how often to probe</li>
<li>timeoutSeconds: 1         # how long to wait for a response</li>
<li>successThreshold: 1       # min consecutive successes</li>
<li>failureThreshold: 3       # after how many failures it&rsquo;s considered failed</li>
</ul>
<p>Key Details:</p>
<ul>
<li>Kubernetes Cluster: Manages the overall orchestration and communicates with the Kubelet on each node.</li>
<li>Kubelet: Responsible for monitoring containers on the nodes, executing the health probes, and reporting back the status to the API server.</li>
<li>Container (Pod): Runs the application, which responds to probe checks like /live, /ready, and /start.</li>
<li>Health Probes: Probes that check container states:</li>
<li>Liveness Probe: Ensures the container is running. If the probe fails, Kubernetes restarts the container.</li>
<li>Readiness Probe: Checks if the container is ready to serve traffic. If it fails, no traffic will be sent to that pod.</li>
<li>Startup Probe: Delays liveness and readiness checks until the application is fully started to avoid premature failure detection.</li>
</ul>
<p>Complete Example: All Three Probes Together</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Pod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">probe-demo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">demo-app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">myapp:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/live</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/ready</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">startupProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tcpSocket</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    
  </channel>
</rss>
