<?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>ansible on Luis Logs</title>
    <link>https://luislogs.com/tags/ansible/</link>
    <description>Recent content in ansible on Luis Logs</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 23 Sep 2023 12:40:30 +0900</lastBuildDate><atom:link href="https://luislogs.com/tags/ansible/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Re-engineering the Homelab with IaC and Kubernetes: An overview</title>
      <link>https://luislogs.com/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/</link>
      <pubDate>Sat, 23 Sep 2023 12:40:30 +0900</pubDate>
      
      <guid>https://luislogs.com/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/</guid>
      <description>In relation to my previous post where I mentioned that I will be starting a new journey learning IaC or Infrastructure-as-Code, today I am very happy to record this milestone of finally achieving a stable kubernetes cluster created with the help of Ansible and Terraform.
At this time of writing, so far only two services have been migrated from the docker environment into the new K8S cluster. That is my DNS which is also replaced now by AdguardHome (sorry Pihole!</description>
      <content:encoded><![CDATA[<p>In relation to my previous post where I mentioned that I will be starting a new journey learning IaC or Infrastructure-as-Code, today I am very happy to record this milestone of finally achieving a stable kubernetes cluster created with the help of Ansible and Terraform.</p>
<p>At this time of writing, so far only two services have been migrated from the docker environment into the new K8S cluster. That is my DNS which is also replaced now by AdguardHome (sorry Pihole!), and Traefik, which terminates all external HTTP communication incoming to my Homelab. And for the rest of the other services, those will be migrated in the coming weeks. For now what is important is I have HTTPS running perfectly fine with valid CA certificates even with dockerized services in the backend.</p>
<p>I also want to note here that during the learning process, whatever services that were running in the docker setup, all of those were nearly untouched since I was working inside a staging environment, keeping rigorous activities of countless create-delete of resources in isolation from my home production network.</p>
<p>That said, I received zero complaints from the wife except for the time I lost connectivity to the cluster when I thought everything was already stable. Lol more on this later.</p>
<p>In summary below opensource tools have been used for this project.</p>
<ul>
<li><a href="https://www.proxmox.com/">Proxmox</a> - Hypervisor (installed in 3 devices)</li>
<li><a href="https://www.terraform.io/">Terraform</a> - VM instantiation</li>
<li><a href="https://www.ansible.com/">Ansible</a> - VM configuration, Kubernetes installation</li>
<li><a href="https://k3s.io/">K3s</a> - Kubernetes platform</li>
<li><a href="https://kube-vip.io/">Kube-vip</a> - LB for Kube API</li>
<li><a href="https://longhorn.io/">Longhorn</a> - Block storage</li>
<li><a href="https://cilium.io/">Cilium</a> - CNI</li>
<li><a href="https://traefik.io/traefik/">Traefik</a> - Reverse Proxy</li>
<li><a href="https://cert-manager.io/">Cert-manager</a> - SSL certificate manager</li>
</ul>
<p><strong>Terraform</strong> and <strong>Ansible</strong> play the major role of orchestrators allowing for quick creation and deletion of virtual resources putting Infrascture-as-Code into practice. The rest are supplementary to learning IaC.</p>
<p><em>Side node: Unfortunately Terraform will be transitioning away from a completely opensource license and soon will be sitting behind a BSL. It might be a good idea to switch to <a href="https://opentofu.org/">OpenTF</a> (or OpenTofu) which is a fork of Terraform and now officially a CNCF project as well.</em></p>
<h2 id="the-setup">The setup</h2>
<p>All development activities were done in a staging environment. This is where the unaccounted events of VM creation-deletion have transpired from testing out the Terraform scripts until the very end when installing Longhorn and Cilium with Ansible, and even when playing around with Traefik in K8s.</p>
<p>The staging environment was deployed on my Unraid box and as for the production build, I am using Tiny PCs that house a total of 32GB RAM each allowing more room for other VMs that I might need in the long run. To give a glimpse of the production HW:</p>
<table>
<thead>
<tr>
<th>PVE host</th>
<th>CPU</th>
<th>Memory</th>
<th>Disk</th>
</tr>
</thead>
<tbody>
<tr>
<td>PVE1</td>
<td>6</td>
<td>32GB</td>
<td>512GB</td>
</tr>
<tr>
<td>PVE2</td>
<td>4</td>
<td>32GB</td>
<td>480GB</td>
</tr>
<tr>
<td>PVE3</td>
<td>4</td>
<td>32GB</td>
<td>480GB</td>
</tr>
</tbody>
</table>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/hardware2.jpeg" alt="Hardware diagram">
      <figcaption>Actual hardware</figcaption>
    </figure>
  </p>
<p>I was also able to source data center-grade Intel S3510 SSD drives from the second-hand market. These should help in the reliability department of these nodes that will be running data replication because of Longhorn. Currently, these are housed in 2 of 3 nodes. Still looking to get another one and pop it in PVE1.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/hp_1200px.jpg" alt="Intel DC S3510 SSDs">
      <figcaption>Intel DC S3510 SSDs in PVE2 and PVE3</figcaption>
    </figure>
  </p>
<h2 id="one-container-to-rule-them-all">One container to rule them all</h2>
<p>Both the Terraform and Ansible controllers are running from a LXC container deployed on PVE1. This is where I do all of the development work. I am running a docker instance of VS code on the same container which enables me to create and modify code easily. The same container is used to communicate with the production environment.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/portainer5.png" alt="Portainer as Ansible and Terraform control node">
      <figcaption>Portainer LXC as Ansible and Terraform control node</figcaption>
    </figure>
  </p>
<p>One of the best advantages of using an LXC container is that it&rsquo;s so lightweight you can easily backup the environment anytime. I confirm Terraform and Ansible works well with the Ubuntu 22.04 template that comes with Proxmox.</p>
<h2 id="terraform-and-ansible">Terraform and Ansible</h2>
<p>Learning Terraform wasn&rsquo;t so bad. The Proxmox provider documentation from Telmate is enough to get you started and with the declarative style that is used to write Terraform language, it just makes the learning process a lot more easier to deal with. This is also the part where I had the least modifications and time spent the least.</p>
<p>Once I got the VMs up and working, next thing I worked on is Ansible. Now this is the part where I spent the biggest chunk of my time. I literally am unable to count the times I had to execute <code>terraform destroy</code> and <code>terraform apply</code> to re-create the VMs and test out my Asnbile playbooks. To be fair starting with Ansible wasn&rsquo;t really hard. It was the amount of automation I wanted to go with that later on will prove useful. Though I have to admit the playbooks are rather simple and which others might find lacking in terms of best practices. But hey, I have to start from somewhere!</p>
<p>The five commands to have a complete working k3s cluster:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">terraform apply
</span></span><span class="line"><span class="cl">ansible-playbook -i inventory.yaml preflight.yaml
</span></span><span class="line"><span class="cl">ansible-playbook -i inventory.yaml logical-volume-create.yaml
</span></span><span class="line"><span class="cl">ansible-playbook -i inventory.yaml k3s-kubevip-helm-ciliumInstallHelmCli.yaml
</span></span><span class="line"><span class="cl">ansible-playbook -i inventory.yaml longhorn-install.yaml
</span></span></code></pre></div><p>The whole process takes about more or less 15 minutes. Terraform creates 3 VMs, one on each proxmox node. Each VM is set 4 vCPU, 12GB RAM, 50GB boot disk + 200GB longhorn disk. <code>preflight.yaml</code> defines the ssh keys for passwordless authentication and installs the necessarry packages to run k3s-related software. <code>logical-volume-create.yaml</code> as its name suggests, creates the logical volume to be used for longhorn. <code>k3s-kubevip-helm-ciliumInstallHelmCli.yaml</code> installs k3s, kube-vip, helm, and Cilium altogether sequentially. And last but not the least, <code>longhorn-install.yaml</code> installs longhorn via helm.</p>
<h2 id="cilium">Cilium</h2>
<p>While Calico would be the go-to CNI for most, I opted to go for Cilium. The main reason for this is to start learning eBPF and have a grasp how things move within the kernel space. The installation was straightforward but to get it working in properly was a challenge.</p>
<p>I was able to make BGP control plane work during the initial phase and when I thought everything was already stable, I then started seeing BGP peers getting dropped and at some point even lost connectvivity to my DNS hosted in the cluster. After adding into the parameters one by one, I was able to make it work without disconnects.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">helm install cilium cilium/cilium --version 1.14.2 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--namespace kube-system <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set bgpControlPlane.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">tunnel</span><span class="o">=</span>disabled <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set ipam.operator.clusterPoolIPv4PodCIDRList<span class="o">=</span>10.42.0.0/16 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">kubeProxyReplacement</span><span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">k8sServiceHost</span><span class="o">={{</span> _k8sServiceHost <span class="o">}}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">k8sServicePort</span><span class="o">=</span><span class="m">6443</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">routingMode</span><span class="o">=</span>native <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">autoDirectNodeRoutes</span><span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set <span class="nv">ipv4NativeRoutingCIDR</span><span class="o">=</span>10.42.0.0/16 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set loadBalancer.mode<span class="o">=</span>dsr <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set ipv4.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set prometheus.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set operator.prometheus.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set hubble.enabled<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--set hubble.metrics.enabled<span class="o">=</span><span class="s2">&#34;{dns,drop,tcp,flow,port-distribution,icmp,http}&#34;</span>
</span></span></code></pre></div><p><code>kubeProxyReplacement</code> is enabled to make use of eBPF instead of the traditional way of iptables. <code>loadBalancerMode</code> is also set to DSR making it possible for nodes to respond directly to the sender, avoiding the need to go via the return path. <code>routingMode</code> set to native and <code>autoDirectNodeRoutes</code> is set to true since all nodes are connected via the same L2 network. You can read more on Cilium routing <a href="https://docs.cilium.io/en/stable/network/concepts/routing/#routing">here</a>. Prometheus and Hubble are also enabled so I can touch on them later once I get more time.</p>
<p><strong>cilium-bgp-peering-policy.yaml</strong>:</p>
<div class="highlight"><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="s2">&#34;cilium.io/v2alpha1&#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">CiliumBGPPeeringPolicy</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="m">00</span>-<span class="l">bgp-peering-policy</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 class="c"># CiliumBGPPeeringPolicySpec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodeSelector</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">kubernetes.io/hostname</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-master-0-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">virtualRouters</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPVirtualRouter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">localASN</span><span class="p">:</span><span class="w"> </span><span class="m">65090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exportPodCIDR</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">serviceSelector</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">exposedExternal</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;yes&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">neighbors</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPNeighbor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">peerAddress</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;10.20.0.1/32&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">peerASN</span><span class="p">:</span><span class="w"> </span><span class="m">65000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">eBGPMultihopTTL</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">connectRetryTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">holdTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">keepAliveTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">gracefulRestart</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">restartTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</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="s2">&#34;cilium.io/v2alpha1&#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">CiliumBGPPeeringPolicy</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="m">01</span>-<span class="l">bgp-peering-policy</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 class="c"># CiliumBGPPeeringPolicySpec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodeSelector</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">kubernetes.io/hostname</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-master-1-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">virtualRouters</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPVirtualRouter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">localASN</span><span class="p">:</span><span class="w"> </span><span class="m">65091</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exportPodCIDR</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">serviceSelector</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">exposedExternal</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;yes&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">neighbors</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPNeighbor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">peerAddress</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;10.20.0.1/32&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">peerASN</span><span class="p">:</span><span class="w"> </span><span class="m">65000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">eBGPMultihopTTL</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">connectRetryTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">holdTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">keepAliveTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">gracefulRestart</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">restartTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</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="s2">&#34;cilium.io/v2alpha1&#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">CiliumBGPPeeringPolicy</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="m">02</span>-<span class="l">bgp-peering-policy</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 class="c"># CiliumBGPPeeringPolicySpec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodeSelector</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">kubernetes.io/hostname</span><span class="p">:</span><span class="w"> </span><span class="l">k8s-master-2-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">virtualRouters</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPVirtualRouter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">localASN</span><span class="p">:</span><span class="w"> </span><span class="m">65092</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exportPodCIDR</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">serviceSelector</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">exposedExternal</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;yes&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">neighbors</span><span class="p">:</span><span class="w"> </span><span class="c"># []CiliumBGPNeighbor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">peerAddress</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;10.20.0.1/32&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">peerASN</span><span class="p">:</span><span class="w"> </span><span class="m">65000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">eBGPMultihopTTL</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">connectRetryTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">holdTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">keepAliveTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">gracefulRestart</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">restartTimeSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span></code></pre></div><p>As for configuring BGP on OPNsense, all I had to do was download the <code>os-frr</code> plugin and apply the configuration according to the Cilium BGP resources.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/opnsense.png" alt="OPNsense BGP neighbor configuration">
      <figcaption>OPNsense BGP neighbor configuration</figcaption>
    </figure>
  </p>
<p>Once BGP is configured, I then configured <code>CiliumLoadBalancerIPPool</code> and configured the <code>serviceSelector</code> there so any service with a matching label will be assigned an external IP.</p>
<div class="highlight"><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">cilium.io/v2alpha1</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">CiliumLoadBalancerIPPool</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">externalpool</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">cidrs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">cidr</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.100.0</span><span class="l">/27</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">disabled</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">serviceSelector</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">exposedExternal</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;yes&#34;</span><span class="w">
</span></span></span></code></pre></div><p>To assign services with external IPs, all you have to do is ensure two things (third one is optional):</p>
<ol>
<li>The service should have a label matching what you defined in your <code>CiliumLoadBalancerIPPool</code>.</li>
<li>The service should be of type <code>LoadBalancer</code>.</li>
<li>For static external IP assignment, the service should have an annotation of <code>io.cilium/lb-ipam-ips</code> followed by the IP address.</li>
</ol>
<div class="highlight"><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">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">io.cilium/lb-ipam-ips</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.100.7</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">exposedExternal</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;yes&#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">adguard-ui</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">adguard</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="nn">...</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">LoadBalancer</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="nn">...</span><span class="w">
</span></span></span></code></pre></div><p>When all of the above are applied, you should now see an external IP assigned to your service.
e.g.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">❯ k -n adguard get svc
</span></span><span class="line"><span class="cl">NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
</span></span><span class="line"><span class="cl">adguard      LoadBalancer   10.43.254.122   192.168.100.8   53:30176/UDP   27d
</span></span><span class="line"><span class="cl">adguard-ui   LoadBalancer   10.43.38.101    192.168.100.7   80:32108/TCP   27d
</span></span></code></pre></div><p>You should also be able to check the BGP peering status as well as the learned routes in OPNsense.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/opnsense_2.png" alt="BGP peering status">
      <figcaption>OPNsense BGP status</figcaption>
    </figure>
  </p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/opnsense_3.png" alt="BGP routes">
      <figcaption>OPNsense BGP routes</figcaption>
    </figure>
  </p>
<h2 id="longhorn">Longhorn</h2>
<p>Before deciding to go for Longhorn, I was was trying to make <a href="https://piraeus.io/">Piraeus</a> datastore work (FOSS version of Linstor storage for Kubernetes). I got it to work with ReadWriteOnce but the moment I tried to test ReadWriteMany(RWX), it just wouldn&rsquo;t. On top of this it also felt that there was a steep learning curve to understand how Piraeus work on a deeper level in case I had to do extra troubleshooting in the future.</p>
<p>Longhorn on the other hand worked well out of the box. Testing out RWX by re-creating a pod on a different node worked well too and since Longhorn seems to use NFS to support this feature, accessing the volume from something external to the cluster e.g. from a VM works out of the box. The only thing is that there seems to be an <a href="https://github.com/cilium/cilium/issues/21541">open issue with Cilium</a> when exposing the volume externally. When I try to mount the share to a VM, I do experience slowdowns when opening a file with vim or even when just browsing through the directories.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/longhorn.png" alt="OPNsense BGP neighbor configuration">
      <figcaption>Longhorn Dashboard</figcaption>
    </figure>
  </p>
<p>Longhorn makes use of a 200Gi second volume that was declared in the Terraform script. The above snapshot shows the amount of available volume.</p>
<p>For reference, below is the playbook task to install longhorn:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">install longhorn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kubernetes.core.helm</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">longhorn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chart_ref</span><span class="p">:</span><span class="w"> </span><span class="l">longhorn/longhorn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">release_namespace</span><span class="p">:</span><span class="w"> </span><span class="l">longhorn-system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">create_namespace</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">update_repo_cache</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">set_values</span><span class="p">:</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="l">service.ui.type=LoadBalancer</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="l">defaultSettings.defaultDataPath=/longhorn_vol</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="l">defaultSettings.defaultReplicaCount=3</span><span class="w">
</span></span></span></code></pre></div><h2 id="exposing-services">Exposing services</h2>
<p>I took more time than expected when I was trying to expose the services with Traefik now running in Kubernetes. In the docker setup, the configuration was pretty straightforward with minimal reading required of the documentation. Whereas when running treafik in k8s, it came to the point that it already felt like I was digging my own grave with all the research and testing.</p>
<p>Eventually I got it to work by going with the base installation and slowly inching my way through the custom values in the yaml file. Once I got the middleware (for additional security headers) and TLS working via cert-manager, all I had to do was create individual ingress resources for each of the services I wanted to expose. The certificates are automatically created and managed by Cert-manager.</p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/traefik_2.png" alt="Network diagram">
      <figcaption>HTTP flow with Traefik and Cert-Manager</figcaption>
    </figure>
  </p>
<p>One thing to note, in Docker, the certificates can be managed by Traefik. But when using Traefik in a K8s environment, to make use of Let&rsquo;s Encrypt, the only option is to use Cert-manager which can only be paired up with the default Kubernetes Ingress resource. Traefik&rsquo;s Ingress CRD doesn&rsquo;t support this at the moment.</p>
<p>Below is a sample Ingress resource to reach the Adguard GUI from external to the cluster:</p>
<div class="highlight"><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">adguard-ui</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">adguard</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-cluster-issuer&#34;</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">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">adguard.su-root.net</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-adguard-ui-ingress-dns</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">adguard.su-root.net</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">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><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">adguard-ui</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></code></pre></div><p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/adguard.png" alt="Traefik UI">
      <figcaption>Adguard UI with valid CA certificate from LE</figcaption>
    </figure>
  </p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/traefik_ui.png" alt="Traefik UI">
      <figcaption>Traefik Dashboard</figcaption>
    </figure>
  </p>
<p>
    <figure>
      <img loading="lazy" src="/posts/re-engineering-the-homelab-with-iac-and-kubernetes-an-overview/traefik_ui2.png" alt="Traefik UI">
      <figcaption>Routers-Services Mapping in Traefik Dashboard</figcaption>
    </figure>
  </p>
<h2 id="the-journey-continues">The journey continues</h2>
<p>The learning doesn&rsquo;t stop here. What I have achieved so far is a basic understanding of how IaC integrates and can be made to work in different environments depending on the requirement. Another good thing is that this adds up to my confidence knowingly I can spin up my cluster in a matter of minutes even in the event that I have to physically migrate to another environment.</p>
<p>Going further I will continue to enhance the Ansible playbooks and try to make use of industry best practices even if this is only intended for Homelab use. I&rsquo;m also looking into integrating this with some kind of CICD tool like Jenkins or ArgoCD in the near future.</p>
<p>If you are interested to see more of this project, feel free to check out the repository over at my <a href="https://github.com/luifrancisco/k3s-ha">Github page</a>. A disclaimer though, the README is not updated yet! I will be updating this sooner or later and together with that will try to explain in detail the idea behind each step of the installation process.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
