<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cilium on Jakub Hovorka</title><link>https://jakubhovorka.cloud/tags/cilium/</link><description>Recent content in Cilium on Jakub Hovorka</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>jhovorka@proton.me (Jakub Hovorka)</managingEditor><webMaster>jhovorka@proton.me (Jakub Hovorka)</webMaster><lastBuildDate>Sat, 29 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jakubhovorka.cloud/tags/cilium/index.xml" rel="self" type="application/rss+xml"/><item><title>Homelab Diary Part 5: Giving the cluster a network</title><link>https://jakubhovorka.cloud/posts/homelab-diary-part-5/</link><pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate><author>jhovorka@proton.me (Jakub Hovorka)</author><guid>https://jakubhovorka.cloud/posts/homelab-diary-part-5/</guid><description>&lt;p&gt;In the last part, I deployed a fresh Talos cluster. It boots, it has a working control plane, and I can pull a &lt;code&gt;kubeconfig&lt;/code&gt; out of the outputs. What it can&amp;rsquo;t do yet is run anything, because the nodes stay &lt;code&gt;NotReady&lt;/code&gt; until a CNI plugin is installed. So that&amp;rsquo;s what this part is about.&lt;/p&gt;
&lt;p&gt;The main job of a CNI (Container Network Interface) plugin is to give every pod an IP address and make sure pods can talk to each other, no matter which node they land on. There are many plugins to choose from, but my go-to choice is Cilium. It&amp;rsquo;s kind of a Swiss Army knife, because it offers so much more than the basic CNI functionality: it can replace kube-proxy, announce &lt;code&gt;LoadBalancer&lt;/code&gt; IPs on the local network, encrypt traffic between nodes, and show you what&amp;rsquo;s going on with all of it through Hubble.&lt;/p&gt;
&lt;h2 id="why-opentofu-and-not-argocd"&gt;Why OpenTofu, and not ArgoCD?&lt;/h2&gt;
&lt;p&gt;Just as in the previous part, I will be deploying everything through OpenTofu, and I create a module for each component. I usually don&amp;rsquo;t like to use OpenTofu for deploying and maintaining Kubernetes resources, but I make an exception for the CNI and the Prometheus CRDs, because trying to deploy these with ArgoCD creates a chicken/egg problem. ArgoCD itself is made of pods, and pods can&amp;rsquo;t start without a network. So the CNI has to be there before anything else, and once it is, ArgoCD can take over.&lt;/p&gt;
&lt;p&gt;To handle the actual deployment to Kubernetes, I always use Helm. I use Cilium&amp;rsquo;s official Helm chart, in combination with the &lt;a href="https://search.opentofu.org/provider/opentofu/helm/latest"&gt;opentofu/helm&lt;/a&gt; provider.&lt;/p&gt;
&lt;h2 id="disabling-kube-proxy"&gt;Disabling kube-proxy&lt;/h2&gt;
&lt;p&gt;Normally, kube-proxy takes care of routing traffic sent to a Service to the pods behind it. Cilium can replace it completely and handle this with eBPF directly in the kernel, which is faster and gives me one less component to run. For this to work, kube-proxy has to be disabled in the Talos config, so there is one small change to the cluster from the last part:&lt;/p&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/main.tf#L51-L62" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/main.tf on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/main.tf lines 51-62&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;talos_cluster&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/talos?ref=blog/homelab-diary-part5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talos_cluster_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talos_cluster_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;gateway_api_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway_api_version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;disable_kube_proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="c1"&gt; # Cilium (deployed below) replaces kube-proxy with its own eBPF implementation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talos_nodes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;module &amp;#34;talos_cluster&amp;#34; {
source = &amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/talos?ref=blog/homelab-diary-part5&amp;#34;
cluster = {
name = var.talos_cluster_name
region = var.talos_cluster_name
gateway_api_version = var.gateway_api_version
disable_kube_proxy = true # Cilium (deployed below) replaces kube-proxy with its own eBPF implementation
}
nodes = local.talos_nodes
}&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The only new line is &lt;code&gt;disable_kube_proxy = true&lt;/code&gt;. It&amp;rsquo;s an optional field of the &lt;code&gt;cluster&lt;/code&gt; variable and it defaults to &lt;code&gt;false&lt;/code&gt;, so the module behaves exactly as before for anyone who doesn&amp;rsquo;t set it.&lt;/p&gt;
&lt;h2 id="the-prometheus-crds"&gt;The Prometheus CRDs&lt;/h2&gt;
&lt;p&gt;One of my favorite Cilium features is Hubble, which gives you visibility into network traffic between pods. Hubble can also export flow metrics, and Cilium itself exposes its own metrics too. To get these into Prometheus, the Cilium Helm chart can create ServiceMonitor resources, which tell the Prometheus Operator what to scrape.&lt;/p&gt;
&lt;p&gt;The catch is that ServiceMonitor isn&amp;rsquo;t a built-in Kubernetes resource. It&amp;rsquo;s a CRD that comes with the Prometheus Operator, which I will deploy later with ArgoCD. If the CRD doesn&amp;rsquo;t exist when Cilium is installed, the Helm release fails, because Kubernetes doesn&amp;rsquo;t recognize the ServiceMonitor kind. That&amp;rsquo;s why I install the Prometheus CRDs separately with OpenTofu, before Cilium. There are no special parameters, it&amp;rsquo;s just a Helm release of the &lt;code&gt;prometheus-operator-crds&lt;/code&gt; chart:&lt;/p&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/modules/helm/prometheus-operator-crds/main.tf" target="_blank" rel="noopener" title="Open terraform/modules/helm/prometheus-operator-crds/main.tf on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/modules/helm/prometheus-operator-crds/main.tf&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;helm_release&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prometheus_crds&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prometheus-operator-crds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://prometheus-community.github.io/helm-charts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prometheus-operator-crds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;atomic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart_version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;resource &amp;#34;helm_release&amp;#34; &amp;#34;prometheus_crds&amp;#34; {
name = &amp;#34;prometheus-operator-crds&amp;#34;
repository = &amp;#34;https://prometheus-community.github.io/helm-charts&amp;#34;
chart = &amp;#34;prometheus-operator-crds&amp;#34;
atomic = true
version = var.chart_version
namespace = &amp;#34;default&amp;#34;
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And this is how I invoke it. The &lt;code&gt;depends_on&lt;/code&gt; makes sure it only runs after the cluster exists:&lt;/p&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/main.tf#L64-L72" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/main.tf on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/main.tf lines 64-72&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 4: Install the Prometheus Operator CRDs. Cilium&amp;#39;s chart (Step 5)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# creates ServiceMonitor resources when its Prometheus integration is
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# enabled, so those CRDs need to already exist in the cluster.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prometheus_operator_crds&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/helm/prometheus-operator-crds?ref=blog/homelab-diary-part5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;talos_cluster&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;chart_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;32.0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# Step 4: Install the Prometheus Operator CRDs. Cilium&amp;#39;s chart (Step 5)
# creates ServiceMonitor resources when its Prometheus integration is
# enabled, so those CRDs need to already exist in the cluster.
module &amp;#34;prometheus_operator_crds&amp;#34; {
source = &amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/helm/prometheus-operator-crds?ref=blog/homelab-diary-part5&amp;#34;
depends_on = [module.talos_cluster]
chart_version = &amp;#34;32.0.1&amp;#34;
}&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="deploying-cilium"&gt;Deploying Cilium&lt;/h2&gt;
&lt;p&gt;Now the main part. This is the module:&lt;/p&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/modules/helm/cilium/main.tf" target="_blank" rel="noopener" title="Open terraform/modules/helm/cilium/main.tf on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/modules/helm/cilium/main.tf&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;helm_release&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://helm.cilium.io/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;atomic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart_version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cilium_values_path&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;resource &amp;#34;helm_release&amp;#34; &amp;#34;cilium&amp;#34; {
name = &amp;#34;cilium&amp;#34;
namespace = var.namespace
repository = &amp;#34;https://helm.cilium.io/&amp;#34;
chart = &amp;#34;cilium&amp;#34;
atomic = true
timeout = var.timeout
version = var.chart_version
values = [
for v in var.cilium_values_path : file(v)
]
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s pretty standard stuff. I choose the chart version and the namespace, which defaults to &lt;code&gt;kube-system&lt;/code&gt;. Then I pass in one or more values files, which hold everything I want to override. &lt;code&gt;atomic&lt;/code&gt; is turned on, so if the release fails, Helm rolls it back instead of leaving a half-installed CNI behind, and the timeout defaults to ten minutes, because on a fresh cluster the image pulls can take a while.&lt;/p&gt;
&lt;p&gt;And this is how I invoke it. It waits for the Prometheus CRDs, for the reason above:&lt;/p&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/main.tf#L74-L86" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/main.tf on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/main.tf lines 74-86&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 5: Deploy Cilium as the cluster&amp;#39;s CNI, replacing kube-proxy (see
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# disable_kube_proxy in Step 3) with its own eBPF-based implementation.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cilium&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/helm/cilium?ref=blog/homelab-diary-part5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cilium_values_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;module&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cilium-values.yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prometheus_operator_crds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;chart_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.19.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# Step 5: Deploy Cilium as the cluster&amp;#39;s CNI, replacing kube-proxy (see
# disable_kube_proxy in Step 3) with its own eBPF-based implementation.
module &amp;#34;cilium&amp;#34; {
source = &amp;#34;git::https://github.com/hovorka-labs/iac-modules.git//terraform/modules/helm/cilium?ref=blog/homelab-diary-part5&amp;#34;
cilium_values_path = [
&amp;#34;${path.module}/cilium-values.yaml&amp;#34;
]
depends_on = [module.prometheus_operator_crds]
chart_version = &amp;#34;1.19.1&amp;#34;
}&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The module is the easy part. The important part is the values file, so I will go over it in more detail, section by section.&lt;/p&gt;
&lt;h3 id="pod-networking-and-routing"&gt;Pod networking and routing&lt;/h3&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L1-L14" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 1-14&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# --- Pod networking and routing ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# IPAM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ipam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kubernetes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requireIPv4PodCIDR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Native routing (no overlay)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;routingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;native&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;autoDirectNodeRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ipv4NativeRoutingCIDR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10.244.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;enableIPv4Masquerade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# --- Pod networking and routing ---
# IPAM
ipam:
mode: kubernetes
k8s:
requireIPv4PodCIDR: true
# Native routing (no overlay)
routingMode: native
autoDirectNodeRoutes: true
ipv4NativeRoutingCIDR: &amp;#34;10.244.0.0/16&amp;#34;
enableIPv4Masquerade: true&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;First, IPAM, which is how pods get their IP addresses. In &lt;code&gt;kubernetes&lt;/code&gt; mode, Cilium uses the PodCIDR that Kubernetes assigns to each node, carved out of Talos&amp;rsquo;s default pod subnet - I&amp;rsquo;m not overriding it, so it stays at &lt;code&gt;10.244.0.0/16&lt;/code&gt; (see Part 4). The alternative is Cilium&amp;rsquo;s own &lt;code&gt;cluster-pool&lt;/code&gt; mode, where Cilium manages its own IP pool instead of relying on Kubernetes&amp;rsquo; allocation. I don&amp;rsquo;t need that, since Talos already hands out PodCIDRs the standard way. Setting &lt;code&gt;requireIPv4PodCIDR&lt;/code&gt; makes Cilium wait until a node actually has its PodCIDR before it starts handing out addresses.&lt;/p&gt;
&lt;p&gt;By default, Cilium uses an overlay network, where traffic between pods on different nodes is wrapped in VXLAN tunnels. The overlay exists so pods can reach each other even when the underlying network has no idea what a pod CIDR is, which matters when nodes are spread across different networks. All of my nodes sit on the same local network though, so I don&amp;rsquo;t need that. I switched to native routing instead, so packets travel between nodes as they are, without the extra encapsulation. With &lt;code&gt;autoDirectNodeRoutes&lt;/code&gt;, every node gets routes to the pod CIDRs of all the other nodes. The catch is that this only works when all nodes are on the same L2 network, which is the case in my homelab, but might not be in yours.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ipv4NativeRoutingCIDR&lt;/code&gt; tells Cilium which traffic can be routed directly, and it has to match that same default pod subnet. Anything going outside of that range, like traffic to the internet, gets masqueraded behind the node&amp;rsquo;s IP address.&lt;/p&gt;
&lt;h3 id="talos-specific-settings"&gt;Talos-specific settings&lt;/h3&gt;
&lt;div class="code-panel code-panel--collapsible"&gt;
&lt;input type="checkbox" id="code-panel-6" class="code-panel__toggle" hidden /&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L16-L49" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 16-49&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# --- Talos-specific settings ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# kube-proxy replacement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kubeProxyReplacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Reach the API server through KubePrism&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;k8sServiceHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;k8sServicePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7445&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Security Context Configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ciliumAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;CHOWN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;KILL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;NET_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;NET_RAW&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;IPC_LOCK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYS_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYS_RESOURCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DAC_OVERRIDE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FOWNER&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SETGID&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SETUID&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cleanCiliumState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;NET_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYS_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYS_RESOURCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# CGroup Configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;cgroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;autoMount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/sys/fs/cgroup&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# --- Talos-specific settings ---
# kube-proxy replacement
kubeProxyReplacement: true
# Reach the API server through KubePrism
k8sServiceHost: localhost
k8sServicePort: 7445
# Security Context Configuration
securityContext:
capabilities:
ciliumAgent:
- CHOWN
- KILL
- NET_ADMIN
- NET_RAW
- IPC_LOCK
- SYS_ADMIN
- SYS_RESOURCE
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
cleanCiliumState:
- NET_ADMIN
- SYS_ADMIN
- SYS_RESOURCE
# CGroup Configuration
cgroup:
autoMount:
enabled: false
hostRoot: /sys/fs/cgroup&lt;/pre&gt;
&lt;label for="code-panel-6" class="code-panel__expand"&gt;
&lt;span class="code-panel__expand-more"&gt;Show full file (34 lines)&lt;/span&gt;
&lt;span class="code-panel__expand-less"&gt;Collapse&lt;/span&gt;
&lt;/label&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;kubeProxyReplacement&lt;/code&gt; is the other half of the change I made earlier. Kube-proxy is gone, so Cilium takes over its job.&lt;/p&gt;
&lt;p&gt;This creates another chicken/egg problem. Cilium needs to talk to the Kubernetes API server, and normally it would do that through the &lt;code&gt;kubernetes&lt;/code&gt; Service IP. But without kube-proxy, Service IPs only work once Cilium is running. Talos solves this with KubePrism, a small proxy to the API server that runs on every node at &lt;code&gt;localhost:7445&lt;/code&gt;. Pointing Cilium at it means it can reach the API server without depending on itself.&lt;/p&gt;
&lt;p&gt;The security context and cgroup settings come straight from the &lt;a href="https://docs.siderolabs.com/kubernetes-guides/cni/deploying-cilium#without-kube-proxy-6"&gt;Talos documentation&lt;/a&gt;. Talos doesn&amp;rsquo;t allow loading kernel modules, so the &lt;code&gt;SYS_MODULE&lt;/code&gt; capability is left out of the list. Talos also mounts cgroups itself, so Cilium is told to use the existing mount instead of creating its own.&lt;/p&gt;
&lt;h3 id="getting-traffic-into-the-cluster"&gt;Getting traffic into the cluster&lt;/h3&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L51-L55" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 51-55&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# --- Traffic into the cluster ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# L2 Announcements&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;l2announcements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# --- Traffic into the cluster ---
# L2 Announcements
l2announcements:
enabled: true&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;L2 announcements make &lt;code&gt;LoadBalancer&lt;/code&gt; services reachable from my home network. Cilium answers ARP requests for the service IPs, so other devices on the network know where to send the traffic. This replaces MetalLB, which I would otherwise need for the same job. Enabling it here doesn&amp;rsquo;t do anything on its own, though. Cilium also needs a pool of IPs to hand out and a policy that says which services to announce, and I will create those in a later part.&lt;/p&gt;
&lt;h3 id="security"&gt;Security&lt;/h3&gt;
&lt;div class="code-panel"&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L57-L63" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 57-63&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# --- Security ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Encryption settings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wireguard&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nodeEncryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# --- Security ---
# Encryption settings
encryption:
enabled: true
type: wireguard
nodeEncryption: true&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With WireGuard encryption enabled, all traffic between pods on different nodes is encrypted. &lt;code&gt;nodeEncryption&lt;/code&gt; extends that to traffic between the nodes themselves. The nice part is that it&amp;rsquo;s completely transparent, so workloads don&amp;rsquo;t need any changes.&lt;/p&gt;
&lt;p&gt;Cilium also offers mutual authentication via SPIRE, which issues an identity to every workload so Cilium can check who&amp;rsquo;s on both ends of a connection. I decided to skip it. It&amp;rsquo;s still in beta, and support for it is already being deprecated in favor of a different feature (Ztunnel Transparent Encryption), so I didn&amp;rsquo;t see the point of taking on a SPIRE server just to use something on its way out.&lt;/p&gt;
&lt;h3 id="observability"&gt;Observability&lt;/h3&gt;
&lt;div class="code-panel code-panel--collapsible"&gt;
&lt;input type="checkbox" id="code-panel-9" class="code-panel__toggle" hidden /&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L65-L97" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 65-97&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# --- Observability ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Prometheus metrics (core Cilium)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metricsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-prometheus-stack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Grafana dashboards (Cilium)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Operator metrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-prometheus-stack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Envoy (L7) metrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;envoy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-prometheus-stack&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# --- Observability ---
# Prometheus metrics (core Cilium)
prometheus:
enabled: true
metricsService: true
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
# Grafana dashboards (Cilium)
dashboards:
enabled: true
# Operator metrics
operator:
dashboards:
enabled: true
prometheus:
enabled: true
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
# Envoy (L7) metrics
envoy:
prometheus:
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack&lt;/pre&gt;
&lt;label for="code-panel-9" class="code-panel__expand"&gt;
&lt;span class="code-panel__expand-more"&gt;Show full file (33 lines)&lt;/span&gt;
&lt;span class="code-panel__expand-less"&gt;Collapse&lt;/span&gt;
&lt;/label&gt;
&lt;/div&gt;
&lt;p&gt;This is where the Prometheus CRDs from earlier come in. The Cilium agent, the operator, and Envoy each get their own ServiceMonitor. All of them have the &lt;code&gt;release: kube-prometheus-stack&lt;/code&gt; label, because by default, kube-prometheus-stack only picks up ServiceMonitors with that label. Without it, the monitors would exist, but nothing would scrape them.&lt;/p&gt;
&lt;p&gt;I also enabled the Grafana dashboards. Cilium creates them as ConfigMaps, and Grafana loads them automatically once it&amp;rsquo;s deployed.&lt;/p&gt;
&lt;div class="code-panel code-panel--collapsible"&gt;
&lt;input type="checkbox" id="code-panel-10" class="code-panel__toggle" hidden /&gt;
&lt;div class="code-panel__header"&gt;
&lt;span class="code-panel__led" aria-hidden="true"&gt;&lt;/span&gt;
&lt;a class="code-panel__link" href="https://github.com/hovorka-labs/iac-modules/blob/blog/homelab-diary-part5/terraform/examples/talos-on-proxmox/cilium-values.yaml#L99-L125" target="_blank" rel="noopener" title="Open terraform/examples/talos-on-proxmox/cilium-values.yaml on GitHub"&gt;
&lt;i class="fa-brands fa-github" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;hovorka-labs/iac-modules&lt;span class="code-panel__sep"&gt;/&lt;/span&gt;terraform/examples/talos-on-proxmox/cilium-values.yaml lines 99-125&lt;/span&gt;
&lt;/a&gt;
&lt;button type="button" class="code-panel__copy" aria-label="Copy code to clipboard"&gt;
&lt;i class="fa-solid fa-copy" aria-hidden="true"&gt;&lt;/i&gt;
&lt;span&gt;Copy&lt;/span&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;div class="code-panel__body"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Hubble configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;hubble&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;prometheus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-prometheus-stack&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;dns:query;ignoreAAAA&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;drop&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;tcp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;flow&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;icmp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;http&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enableOpenMetrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-prometheus-stack&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code-panel__raw" hidden aria-hidden="true"&gt;# Hubble configuration
hubble:
enabled: true
ui:
enabled: true
relay:
enabled: true
prometheus:
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
metrics:
enabled:
- dns:query;ignoreAAAA
- drop
- tcp
- flow
- icmp
- http
enableOpenMetrics: true
dashboards:
enabled: true
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack&lt;/pre&gt;
&lt;label for="code-panel-10" class="code-panel__expand"&gt;
&lt;span class="code-panel__expand-more"&gt;Show full file (27 lines)&lt;/span&gt;
&lt;span class="code-panel__expand-less"&gt;Collapse&lt;/span&gt;
&lt;/label&gt;
&lt;/div&gt;
&lt;p&gt;Finally, Hubble. Hubble Relay collects network flows from all nodes, and the Hubble UI shows them as a map of which services talk to each other. On top of that, I enabled a set of metrics: DNS queries (ignoring AAAA records to reduce noise), dropped packets, TCP, flows, ICMP, and HTTP. The &lt;code&gt;drop&lt;/code&gt; metric is the one I find most useful, because it shows right away when a network policy blocks something it shouldn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;With the CRDs and Cilium applied, the nodes can finally go &lt;code&gt;Ready&lt;/code&gt;, and the cluster can do the first thing it couldn&amp;rsquo;t do on its own: get pods talking to each other and to the network, with no kube-proxy and no MetalLB needed. That&amp;rsquo;s the whole scope I set out for this part. There&amp;rsquo;s still nothing in the cluster to manage what actually runs on top of this network, so that&amp;rsquo;s what I&amp;rsquo;m heading to next.&lt;/p&gt;</description></item></channel></rss>