<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>parithosh.jayanthi</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://parithosh.com/</id>
  <link href="https://parithosh.com/" rel="alternate"/>
  <link href="https://parithosh.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, parithosh.jayanthi</rights>
  <subtitle>Notes on testing Ethereum protocol upgrades, homelab infrastructure and whatever else I break along the way.</subtitle>
  <title>Parithosh Jayanthi</title>
  <updated>2026-01-03T00:00:00.000Z</updated>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>I tend to cherish the time between Christmas and the New Year: Work is quieter, There’s little happening socially and I can spend time trying out new things on my Homelab! I’d been ignoring a home assistant error for a month at this point, so I decided to finally take a look - only to be immediately met with a wall of certificate related errors. A <a href="https://parithosh.com/2023-08-12-setup-k3s-cluster-on-vm/">few years ago</a>, I decided to setup Kubernetes as the orchestration engine for my homelab in an attempt to learn the internals better - it seems like ignoring to update kubernetes leads to certificates expiring, causing the wall of errors I saw earlier. Apparently updating k3s also takes care of the certificate renewals, something I had to manually trigger to get the cluster and my applications healthy again. </p><p>The experience just showed me how much I dislike kubernetes again - its way too over-engineered, atleast for a homelab. While I did learn a lot from the experience, I prefer knowing the moving bits and pieces! However the experience also showed me what I was missing with the plain docker compose approach I had before! I appreciated having the ability to auto-create certificates, having git as my source of truth and not thinking about routing or monitoring!</p><p>Thinking about the requirements led to the following:</p><ul><li>Github as the source of truth, with a services and a core folder</li><li>The core folder outside the scope of gitops, its the ingress and deployment manager (to avoid the bootstrap problem)</li><li>The services contain all the configs that need deploying</li><li>Github bot&#x2F;action to track releases and auto-create PRs that can be automatically deployed on approval</li></ul><p>With Claude code all the hype this year, I decided to see how nice it was for design exploration! I came up with a list of requirements and let claude interactively suggest options for me, once we narrowed down a list of options - I asked it to generate all the template configs so that I could visually see what was possible and not. Additionally I could setup small test examples and prod at them, refining my requirement list each time. This sort of iterative exploration would have taken days in the past, I didn’t have to deal with outdated <code>README</code>s or bespoke bugs! </p><p>The stack I ended up deploying (and liking!):</p><ul><li>Traefik to handle ingress and certificates. I avoided traefik in the past because the middleware concept wasn’t intuitive for me, but using examples from claude - I could finally grok it! My old default was HAProxy and at work we use nginx + proxy mangager, both now seem more cumbersome compared to traefik. </li><li>Komodo for deployments. Komodo handles gitOps for docker compose files, it can track a folder in a github repository and deploy the changes. However, unlike Kubernetes, I can still SSH into the machine and make changes manually if I wish (the next run would overwrite it unless I upstream the changes). This lets me easily interact with my homelab and extend it with host binaries (during dev), without having to write a helm chart to even start. Komodo also has the concept of a <code>core</code> and a <code>periphery</code>, so I can theoretically have it running on multiple hosts if I want to expand my homelab. The config for Komodo is a simple TOML file, so the deployment orchestrator settings is also tracked in git.</li><li>Renovate bot for PRs, deployed as a github action with scoped access and permissions. This allows me to automate away updates as komodo will pick them up after merging. </li><li>PockedID for Passkey based OIDC. This is something I wanted to try out this year, Traefik integrates well with OIDC and so do most of the services I plan on using. I wanted to centralize my user management and use passkeys by default everywhere. I’m not sold on the idea of passkeys, but I must admit that they work better for me since moving to iPhone from a Samsung.</li></ul><img src="/2026-01-03-what-comes-after-kubernetes/homelab-architecture.svg" class="" title="Homelab Architecture"><p>Exploring, iterating and debugging on the entire workflow wouldn’t have been possible without Claude! The last years, this exercise used to take 2-3d of toil, that’s down to an afternoon now. Claude isn’t bulletproof yet, it makes mistakes that are often hard to catch or would only show up later in time - but we’ve really come a long way with AI tooling!</p>]]>
    </content>
    <id>https://parithosh.com/2026-01-03-what-comes-after-kubernetes/</id>
    <link href="https://parithosh.com/2026-01-03-what-comes-after-kubernetes/"/>
    <published>2026-01-03T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>I tend to cherish the time between Christmas and the New Year: Work is quieter, There’s little happening socially and I can spend time tr]]>
    </summary>
    <title>What comes after Kubernetes?</title>
    <updated>2026-01-03T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Working with computers all day is a recipe for eye strain, often leading to tired eyes by the night. The 20-20-20 rule is a guideline meant to reduce eye strain. According to the rule, one should look at something 20f away for 20s every 20 minutes. I’ve tried to follow it by memory, but mostly failed. So lets automate it! I thought of finding some app to remind me, but its more fun to do it with built-in OS features :)</p><p>Apply allows us to use <code>osascript</code> to create a notification. This can be a silent notification (<code>display notification</code>) or an “in your face” alert (<code>display alert</code>). I chose the in your face alert because I don’t want to miss it, lets see if i regret it during my first presentation when the alert goes off. </p><p>The one liner for the alert,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">osascript -e &#x27;display alert &quot;20-20-20 Rule&quot; message &quot;Take a 20s break!&quot;&#x27;</span><br></pre></td></tr></table></figure><p>However, I want to also receive an audio cue. You can use <code>afplay</code> on mac to call a system sound. Putting the two together, gives you this script:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/zsh</span><br><span class="line"># script called eye-strain-reminder.sh</span><br><span class="line">osascript -e &#x27;display alert &quot;20-20-20 Rule&quot; message &quot;Take a 20s break!&quot;&#x27;; afplay /System/Library/Sounds/Funk.aiff</span><br><span class="line">sleep 20</span><br><span class="line">osascript -e &#x27;display alert &quot;20-20-20 Rule&quot; message &quot;20s done!&quot;&#x27;; afplay /System/Library/Sounds/Funk.aiff</span><br></pre></td></tr></table></figure><p>Now we have to wrap it inside a launch agent, this has to be saved in <code>~/Library/LaunchAgents/eye-strain-reminder.plist</code> with the following content:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;</span><br><span class="line">&lt;plist version=&quot;1.0&quot;&gt;</span><br><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;key&gt;Label&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;eye-strain-reminder&lt;/string&gt;</span><br><span class="line"></span><br><span class="line">    &lt;key&gt;ProgramArguments&lt;/key&gt;</span><br><span class="line">    &lt;array&gt;</span><br><span class="line">        &lt;string&gt;sh&lt;/string&gt;</span><br><span class="line">        &lt;string&gt;&lt;ABSOLUTE_PATH_TO&gt;/eye-strain-reminder.sh&lt;/string&gt;</span><br><span class="line">    &lt;/array&gt;</span><br><span class="line"></span><br><span class="line">    &lt;key&gt;StartInterval&lt;/key&gt;</span><br><span class="line">    &lt;integer&gt;1200&lt;/integer&gt;</span><br><span class="line">&lt;/dict&gt;</span><br><span class="line">&lt;/plist&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Don’t forget to update the path to the absolute path of your script!</p><p>Now you need to load and start the agent:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">launchctl load ~/Library/LaunchAgents/eye-strain-reminder.plist</span><br><span class="line">launchctl start eye-strain-reminder.plist</span><br></pre></td></tr></table></figure><p>You can always <code>stop</code> and <code>unload</code> the above <code>plist</code> file if you want to get rid of the job.</p>]]>
    </content>
    <id>https://parithosh.com/2024-02-13-eye-strain-reminder/</id>
    <link href="https://parithosh.com/2024-02-13-eye-strain-reminder/"/>
    <published>2024-02-13T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Working with computers all day is a recipe for eye strain, often leading to tired eyes by the night. The 20-20-20 rule is a guideline mea]]>
    </summary>
    <title>Automatic notifications for taking a break</title>
    <updated>2024-02-13T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>An ingress controller is a special loadbalancer on kubernetes. It accepts traffic from outside kubernetes (k8s) and routes it to resources inside k8s.</p><p>By default kubernetes installations don’t come with an ingress controller. K3s however, does come with Traefik by default. </p><p>Traefik is an open sourced edge router, it automatically discovers the configuration (based on labels and ingress definitions) and routes as requested. However, after trying out the default Traefik installation, I ran into a series of noob problems that soured my experince: </p><ul><li>I struggled to reach the default <code>dashboard</code> as I was using the URL <code>xyz.com/dashboard</code> instead of <code>xyz.com/dashboard/</code>. The trailing <code>/</code> was needed to trigger the router. I’m sure there’s some way to disable or change this, but as someone new to Traefik, it was annoying.</li><li>I wanted to expose a self-hosted homepage called <a href="https://github.com/pawelmalak/flame">Flame</a>. The route was discovered as expected, the setup looks great but I kept hitting a blank page. After some <code>curl</code> fu, it seems like there were some header shenanigans going on that prevented JS from loading. I was able to find a few issues online hitting the same issue, however most solutions seem to use Nginx behind Traefik or using some Middleware service to fix the headers before routing. Again, extremely overwhelming for a noob to Traefik.</li></ul><p>Having used Nginx and HAProxy extensively in the past, I didn’t see a real reason to stick with Traefik and its potential unknowns any longer. So I got rid of my existing cluster with the default Traefik (an easy action thanks to <code>pulumi</code>) and setup a new cluster with the flag <code>--disable=traefik</code> during the <code>k3s server</code> setup process. This would setup k3s without a default ingress controller. </p><h2 id="Install-Nginx-as-ingress-controller"><a href="#Install-Nginx-as-ingress-controller" class="headerlink" title="Install Nginx as ingress controller"></a>Install Nginx as ingress controller</h2><p>Once the new k3s cluster is setup, check again that there indeed is no ingress controller. Once checked, we’d want to go to the Nginx bare metal setup docs found <a href="https://kubernetes.github.io/ingress-nginx/deploy/?ref=blog.thenets.org#bare-metal-clusters">here</a>. </p><p>There’s a simple in the above URL, fetch it and save it on a local file called <code>ingress-controller.yaml</code>. <code>kubectl apply -f </code> said file to specify all the required components. To make it a bit more transparent, I fetched the file and saved it in my local git repo to be a bit more explicit as to what is deployed on the cluster. </p><p>We still need a entrypoint, so lets depoy one:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">ingress-nginx-controller-loadbalancer</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">ingress-nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/component:</span> <span class="string">controller</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/instance:</span> <span class="string">ingress-nginx</span></span><br><span class="line">    <span class="attr">app.kubernetes.io/name:</span> <span class="string">ingress-nginx</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">https</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">443</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">LoadBalancer</span></span><br></pre></td></tr></table></figure><h2 id="Testing-nginx-ingress-controller"><a href="#Testing-nginx-ingress-controller" class="headerlink" title="Testing nginx ingress controller"></a>Testing nginx ingress controller</h2><p>Lets create a new namespace called <code>nginx-test</code> with <code>kubectl create namespace nginx-test</code>. We’ll deploy all the required resources for the test in this namespace. </p><p>Now we will attempt to deploy a <code>Deployment</code> called <code>test-nginx-dep</code>, this will deploy a <code>Pod</code> called <code>nginx-backend</code> and a <code>Service</code> called <code>test-nginx-svc</code>. We’ll also need to deploy a <code>Ingress</code>resource called <code>test-nginx-ingress</code>, this will specify the <code>host</code> to listen on and the <code>path</code> as well as the backend to route the request to. The <code>IngressController</code> will then route the request specified by the <code>Ingress</code> resource. </p><p>Save the below <code>YAML</code> as a file called <code>example.yaml</code>:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-nginx-dep</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">nginx-test</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">test-nginx-backend</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">test-nginx-backend</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-backend</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">docker.io/nginx:alpine</span></span><br><span class="line">          <span class="attr">imagePullPolicy:</span> <span class="string">Always</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-nginx-svc</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">nginx-test</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">81</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">test-nginx-backend</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">networking.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Ingress</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">test-nginx-ingress</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">nginx-test</span></span><br><span class="line">  <span class="attr">annotations:</span></span><br><span class="line">    <span class="attr">nginx.ingress.kubernetes.io/ssl-redirect:</span> <span class="string">&quot;false&quot;</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ingressClassName:</span> <span class="string">&quot;nginx&quot;</span></span><br><span class="line">  <span class="attr">rules:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">host:</span> <span class="string">xyz.com</span></span><br><span class="line">    <span class="attr">http:</span></span><br><span class="line">      <span class="attr">paths:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">/</span></span><br><span class="line">          <span class="attr">pathType:</span> <span class="string">Prefix</span></span><br><span class="line">          <span class="attr">backend:</span></span><br><span class="line">            <span class="attr">service:</span></span><br><span class="line">              <span class="attr">name:</span> <span class="string">test-nginx-svc</span></span><br><span class="line">              <span class="attr">port:</span></span><br><span class="line">                <span class="attr">number:</span> <span class="number">81</span></span><br></pre></td></tr></table></figure><p>Note: Ensure that the URL specified in <code>host</code> actually routes to your k3s cluster. I achieved this by setting a DNS rule on my router to resolve <code>xyz.com</code> to my cluster. The same can be achieved by modifying <code>/etc/hosts</code> on a linux machine.</p><p>Apply the file with: <code>kubectl apply -n nginx-test -f example.yaml</code>. This should create all the resouces required. You can now navigate to <code>xyz.com</code> on your machine to see the Nginx default landing page - indicating that the nginx <code>IngressController</code> picked up the <code>Ingress</code> route and passes traffic as expected. </p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>We switched the k3s cluster from the default Traefik to a Nginx ingress controller and we will now use Nginx for exposing future services as well as configuring SSL certificates. </p>]]>
    </content>
    <id>https://parithosh.com/2023-12-27-ingress-controllers-on-k3s/</id>
    <link href="https://parithosh.com/2023-12-27-ingress-controllers-on-k3s/"/>
    <published>2023-12-27T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>An ingress controller is a special loadbalancer on kubernetes. It accepts traffic from outside kubernetes (k8s) and routes it to resource]]>
    </summary>
    <title>Ingresses Controllers on k3s</title>
    <updated>2023-12-27T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Pulumi is an open sourced IaC SDK that allows you to define and manage infrastructure through the use of various programming languages. This helps reduce the need to learn another HCL-ish language and gives you the extensibility and toolkit of whatever (popular) programming language you are comfortable with.</p><p>For my usecase, I have a Promxox node (<code>Minisforum 773 Lite, 64GB RAM, 1TB SSD</code>) and would want to use it to host my VMs. Initially I want to run 3 VMs to host a kubernetes cluster that I can use to better learn the technology.</p><p>I’m assuming that Pulumi is already installed and configured. Let’s now create a new stack with <code>pulumi new python -y</code>. This will create a few new files for us: <code>Pulumi.yaml</code>(Project metadata), <code>__main__.py</code>(Program entrypoint), <code>requirements.txt</code>(dependencies), <code>venv</code> (virtual env for the project).</p><p>Proxmox has an API that allows us to provision resources, however we would need to know the calls to make. To simplify the process, we can use a pulumi proxmox provider, which can be installed as a basic pip package with <code>venv/bin/pip install pulumi-proxmoxve</code> (or install via the <code>requirements.yaml</code>). This will allow us to create a proxmox provider and to define the VM in an easier manner and the package takes care of the required API calls to provision the VM.</p><p>In order to make configuration of VMs a bit easier, lets also define a folder called <code>vms</code> that contains <code>YAML</code> files containing all the values that we’d want to configure. We can then read the files in said folder, parse the values and then configure the pulumi proxmox provider to create the VMs for us. </p><p>To start with, lets configure <code>.gitignore</code> to ignore the <code>.env</code> file that will be containing our secrets. Then create a <code>.env</code> file with the following contents:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PROXMOX_ENDPOINT=&quot;https://&lt;PROXMOX-IP&gt;:8006&quot;</span><br><span class="line">PROXMOX_INSECURE=&quot;true&quot;</span><br><span class="line">PROXMOX_USERNAME=&quot;username@pam&quot;</span><br><span class="line">PROXMOX_PASSWORD=&quot;PASSWORD&quot;</span><br><span class="line">PROXMOX_USER_ACCOUNT_PASSWORD=&quot;DEBIAN_BASE_PASSWORD&quot;</span><br></pre></td></tr></table></figure><p>The pulumi python logic for `_<em>main</em>_.py` is the following:  </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pulumi</span><br><span class="line"><span class="keyword">import</span> pulumi_proxmoxve <span class="keyword">as</span> proxmox</span><br><span class="line"><span class="keyword">import</span> os,yaml</span><br><span class="line"><span class="keyword">from</span> dotenv <span class="keyword">import</span> load_dotenv</span><br><span class="line"><span class="keyword">import</span> ipaddress</span><br><span class="line">load_dotenv()</span><br><span class="line">provider = proxmox.Provider(<span class="string">&#x27;proxmoxve&#x27;</span>,</span><br><span class="line">                            endpoint=os.getenv(<span class="string">&quot;PROXMOX_ENDPOINT&quot;</span>),</span><br><span class="line">                            insecure=os.getenv(<span class="string">&quot;PROXMOX_INSECURE&quot;</span>),</span><br><span class="line">                            username=os.getenv(<span class="string">&quot;PROXMOX_USERNAME&quot;</span>),</span><br><span class="line">                            password=os.getenv(<span class="string">&quot;PROXMOX_PASSWORD&quot;</span>),</span><br><span class="line">                            )</span><br><span class="line"></span><br><span class="line">folder_path = <span class="string">&quot;vms/&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">load_yaml_files_from_folder</span>(<span class="params">folder_path</span>):</span><br><span class="line">    yaml_files = [file <span class="keyword">for</span> file <span class="keyword">in</span> os.listdir(folder_path) <span class="keyword">if</span> file.endswith(<span class="string">&quot;.yaml&quot;</span>)]</span><br><span class="line">    loaded_data = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> yaml_file <span class="keyword">in</span> yaml_files:</span><br><span class="line">        file_path = os.path.join(folder_path, yaml_file)</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(file_path, <span class="string">&#x27;r&#x27;</span>) <span class="keyword">as</span> file:</span><br><span class="line">            yaml_data = yaml.safe_load(file)</span><br><span class="line">            loaded_data.append(yaml_data)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> loaded_data</span><br><span class="line"></span><br><span class="line">parsed_data = load_yaml_files_from_folder(folder_path)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> vm <span class="keyword">in</span> parsed_data:</span><br><span class="line">    disks = []</span><br><span class="line">    nets = []</span><br><span class="line">    ip_configs = []</span><br><span class="line">    ssh_keys = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> v <span class="keyword">in</span> vm:</span><br><span class="line">        <span class="keyword">for</span> vmcount <span class="keyword">in</span> <span class="built_in">range</span>(v[<span class="string">&#x27;count&#x27;</span>]):</span><br><span class="line">            base_resource_name=v[<span class="string">&#x27;resource_name&#x27;</span>]</span><br><span class="line">            name_counter = vmcount + <span class="number">1</span></span><br><span class="line">            base_vm_id=v[<span class="string">&#x27;vm_id&#x27;</span>],</span><br><span class="line">            <span class="keyword">for</span> disk_entry <span class="keyword">in</span> v[<span class="string">&#x27;disks&#x27;</span>]:</span><br><span class="line">                <span class="keyword">for</span> d <span class="keyword">in</span> disk_entry:</span><br><span class="line">                    disks.append(</span><br><span class="line">                        proxmox.vm.VirtualMachineDiskArgs(</span><br><span class="line">                            interface=disk_entry[d][<span class="string">&#x27;interface&#x27;</span>],</span><br><span class="line">                            datastore_id=disk_entry[d][<span class="string">&#x27;datastore_id&#x27;</span>],</span><br><span class="line">                            size=disk_entry[d][<span class="string">&#x27;size&#x27;</span>],</span><br><span class="line">                            file_format=disk_entry[d][<span class="string">&#x27;file_format&#x27;</span>],</span><br><span class="line">                            cache=disk_entry[d][<span class="string">&#x27;cache&#x27;</span>]</span><br><span class="line">                        )</span><br><span class="line">                    )</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> ip_config_entry <span class="keyword">in</span> v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;ip_configs&#x27;</span>]:</span><br><span class="line">                ipv4 = ip_config_entry.get(<span class="string">&#x27;ipv4&#x27;</span>)</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> ipv4:</span><br><span class="line">                    new_address = <span class="string">&#x27;&#x27;</span></span><br><span class="line">                    ip, subnet = ipv4.get(<span class="string">&#x27;address&#x27;</span>, <span class="string">&#x27;&#x27;</span>).split(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">                    new_ip = <span class="built_in">str</span>(ipaddress.ip_address(ip) + vmcount)</span><br><span class="line">                    new_address = <span class="string">f&quot;<span class="subst">&#123;new_ip&#125;</span>/<span class="subst">&#123;subnet&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">                    ip_configs = []</span><br><span class="line">                    ip_configs.append(</span><br><span class="line">                        proxmox.vm.VirtualMachineInitializationIpConfigArgs(</span><br><span class="line">                            ipv4=proxmox.vm.VirtualMachineInitializationIpConfigIpv4Args(</span><br><span class="line">                                address=new_address,</span><br><span class="line">                                gateway=ipv4.get(<span class="string">&#x27;gateway&#x27;</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                            )</span><br><span class="line">                        )</span><br><span class="line">                    )</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> ssk_keys_entry <span class="keyword">in</span> v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;user_account&#x27;</span>][<span class="string">&#x27;keys&#x27;</span>]:</span><br><span class="line">                ssh_keys.append(ssk_keys_entry)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> net_entry <span class="keyword">in</span> v[<span class="string">&#x27;network_devices&#x27;</span>]:</span><br><span class="line">                <span class="keyword">for</span> n <span class="keyword">in</span> net_entry:</span><br><span class="line">                    nets.append(</span><br><span class="line">                        proxmox.vm.VirtualMachineNetworkDeviceArgs(</span><br><span class="line">                            bridge=net_entry[n][<span class="string">&#x27;bridge&#x27;</span>],</span><br><span class="line">                            model=net_entry[n][<span class="string">&#x27;model&#x27;</span>]</span><br><span class="line">                        )</span><br><span class="line">                    )</span><br><span class="line"></span><br><span class="line">            virtual_machine = proxmox.vm.VirtualMachine(</span><br><span class="line">                vm_id=base_vm_id[<span class="number">0</span>] + vmcount,</span><br><span class="line">                resource_name=<span class="string">f&quot;<span class="subst">&#123;base_resource_name&#125;</span>-<span class="subst">&#123;name_counter&#125;</span>&quot;</span>,</span><br><span class="line">                node_name=v[<span class="string">&#x27;node_name&#x27;</span>],</span><br><span class="line">                agent=proxmox.vm.VirtualMachineAgentArgs(</span><br><span class="line">                    enabled=v[<span class="string">&#x27;agent&#x27;</span>][<span class="string">&#x27;enabled&#x27;</span>],</span><br><span class="line">                    <span class="comment"># trim=v[&#x27;agent&#x27;][&#x27;trim&#x27;],</span></span><br><span class="line">                    <span class="built_in">type</span>=v[<span class="string">&#x27;agent&#x27;</span>][<span class="string">&#x27;type&#x27;</span>]</span><br><span class="line">                ),</span><br><span class="line">                bios=v[<span class="string">&#x27;bios&#x27;</span>],</span><br><span class="line">                cpu=proxmox.vm.VirtualMachineCpuArgs(</span><br><span class="line">                    cores=v[<span class="string">&#x27;cpu&#x27;</span>][<span class="string">&#x27;cores&#x27;</span>],</span><br><span class="line">                    sockets=v[<span class="string">&#x27;cpu&#x27;</span>][<span class="string">&#x27;sockets&#x27;</span>]</span><br><span class="line">                ),</span><br><span class="line">                clone=proxmox.vm.VirtualMachineCloneArgs(</span><br><span class="line">                    node_name=v[<span class="string">&#x27;clone&#x27;</span>][<span class="string">&#x27;node_name&#x27;</span>],</span><br><span class="line">                    vm_id=v[<span class="string">&#x27;clone&#x27;</span>][<span class="string">&#x27;vm_id&#x27;</span>],</span><br><span class="line">                    full=v[<span class="string">&#x27;clone&#x27;</span>][<span class="string">&#x27;full&#x27;</span>],</span><br><span class="line">                ),</span><br><span class="line">                disks=disks,</span><br><span class="line">                memory=proxmox.vm.VirtualMachineMemoryArgs(</span><br><span class="line">                    dedicated=v[<span class="string">&#x27;memory&#x27;</span>][<span class="string">&#x27;dedicated&#x27;</span>]</span><br><span class="line">                ),</span><br><span class="line">                name=<span class="string">f&quot;<span class="subst">&#123;base_resource_name&#125;</span>-<span class="subst">&#123;name_counter&#125;</span>&quot;</span>,</span><br><span class="line">                network_devices=nets,</span><br><span class="line">                initialization=proxmox.vm.VirtualMachineInitializationArgs(</span><br><span class="line">                    <span class="built_in">type</span>=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;type&#x27;</span>],</span><br><span class="line">                    datastore_id=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;datastore_id&#x27;</span>],</span><br><span class="line">                    interface=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;interface&#x27;</span>],</span><br><span class="line">                    dns=proxmox.vm.VirtualMachineInitializationDnsArgs(</span><br><span class="line">                        domain=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;dns&#x27;</span>][<span class="string">&#x27;domain&#x27;</span>],</span><br><span class="line">                        server=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;dns&#x27;</span>][<span class="string">&#x27;server&#x27;</span>]</span><br><span class="line">                    ),</span><br><span class="line">                    ip_configs=ip_configs,</span><br><span class="line">                    user_account=proxmox.vm.VirtualMachineInitializationUserAccountArgs(</span><br><span class="line">                        username=v[<span class="string">&#x27;cloud_init&#x27;</span>][<span class="string">&#x27;user_account&#x27;</span>][<span class="string">&#x27;username&#x27;</span>],</span><br><span class="line">                        password=os.getenv(<span class="string">&quot;PROXMOX_USER_ACCOUNT_PASSWORD&quot;</span>),</span><br><span class="line">                        keys=ssh_keys</span><br><span class="line">                    ),</span><br><span class="line">                ),</span><br><span class="line">                on_boot=v[<span class="string">&#x27;on_boot&#x27;</span>],</span><br><span class="line">                reboot=v[<span class="string">&#x27;on_boot&#x27;</span>],</span><br><span class="line">                opts=pulumi.ResourceOptions(provider=provider,ignore_changes=v[<span class="string">&#x27;ignore_changes&#x27;</span>]),</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">            pulumi.export(v[<span class="string">&#x27;name&#x27;</span>], virtual_machine.<span class="built_in">id</span>)</span><br></pre></td></tr></table></figure><p>The YAML for the proxmox VMs looks like this:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&quot;k3s-nodes&quot;</span></span><br><span class="line">  <span class="attr">count:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">node_name:</span> <span class="string">&quot;proxmox-vmfarm&quot;</span></span><br><span class="line">  <span class="attr">resource_name:</span> <span class="string">&quot;k3s-node&quot;</span></span><br><span class="line">  <span class="attr">vm_id:</span> <span class="number">1000</span></span><br><span class="line">  <span class="attr">agent:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">&quot;virtio&quot;</span></span><br><span class="line">  <span class="attr">bios:</span> <span class="string">&quot;seabios&quot;</span></span><br><span class="line">  <span class="attr">ignore_changes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;disks&quot;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&quot;cdrom&quot;</span></span><br><span class="line">  <span class="attr">cpu:</span></span><br><span class="line">    <span class="attr">cores:</span> <span class="number">4</span></span><br><span class="line">    <span class="attr">sockets:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">cloud_init:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">&quot;nocloud&quot;</span></span><br><span class="line">    <span class="attr">interface:</span> <span class="string">&quot;ide0&quot;</span></span><br><span class="line">    <span class="attr">datastore_id:</span> <span class="string">&quot;local-lvm&quot;</span></span><br><span class="line">    <span class="attr">dns:</span></span><br><span class="line">      <span class="attr">domain:</span> <span class="string">&quot;&quot;</span></span><br><span class="line">      <span class="attr">server:</span> <span class="string">&quot;1.1.1.1 8.8.8.8&quot;</span></span><br><span class="line">    <span class="attr">ip_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ipv4:</span></span><br><span class="line">          <span class="attr">address:</span> <span class="string">&quot;192.168.90.50/32&quot;</span></span><br><span class="line">          <span class="attr">gateway:</span> <span class="string">&quot;192.168.90.1&quot;</span></span><br><span class="line">    <span class="attr">user_account:</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">&quot;root&quot;</span></span><br><span class="line">      <span class="attr">keys:</span></span><br><span class="line">       <span class="bullet">-</span> <span class="string">ssh-ed25519</span> <span class="string">AAAAC3NzaC1lZDI1NTE5AAAAIAiL82k0HKfSFp3JgdpShVvx9mRDk5fyYQyto00tBdeC</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">clone:</span></span><br><span class="line">    <span class="attr">node_name:</span> <span class="string">&quot;proxmox-vmfarm&quot;</span></span><br><span class="line">    <span class="attr">vm_id:</span> <span class="number">100</span></span><br><span class="line">    <span class="attr">full:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">disks:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">disk1:</span></span><br><span class="line">        <span class="attr">interface:</span> <span class="string">&quot;scsi0&quot;</span></span><br><span class="line">        <span class="attr">datastore_id:</span> <span class="string">&quot;local-lvm&quot;</span></span><br><span class="line">        <span class="attr">size:</span> <span class="number">180</span></span><br><span class="line">        <span class="attr">file_format:</span> <span class="string">&quot;raw&quot;</span></span><br><span class="line">        <span class="attr">cache:</span> <span class="string">&quot;none&quot;</span></span><br><span class="line">  <span class="attr">memory:</span></span><br><span class="line">    <span class="attr">dedicated:</span> <span class="number">16384</span></span><br><span class="line">  <span class="attr">network_devices:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">net1:</span></span><br><span class="line">        <span class="attr">bridge:</span> <span class="string">&quot;vmbr0&quot;</span></span><br><span class="line">        <span class="attr">model:</span> <span class="string">&quot;virtio&quot;</span></span><br><span class="line">  <span class="attr">on_boot:</span> <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The values being configured are quite self-explanatory, if not there is a detailed documentation in the proxmox provider page. </p><p>Once done, all you have to do is run <code>pulumi up</code> in the folder and the <code>pulumi</code> interactive shell will guide you through the rest!</p><p>Once the <code>pulumi</code> job is done, you should be able to see brand new VMs created in your proxmox host. If you want to change a value, you can simply modify the <code>YAML</code> and run <code>pulumi up</code> again. To destroy the VMs, you can run <code>pulumi down</code> or set the count to <code>0</code> to not affect the rest of the <code>pulumi</code> stack. </p>]]>
    </content>
    <id>https://parithosh.com/2023-12-22-proxmox-pulumi-vms/</id>
    <link href="https://parithosh.com/2023-12-22-proxmox-pulumi-vms/"/>
    <published>2023-12-22T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Pulumi is an open sourced IaC SDK that allows you to define and manage infrastructure through the use of various programming languages. T]]>
    </summary>
    <title>Setting up VMs on Proxmox with Pulumi</title>
    <updated>2023-12-22T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>The ubiquity of kubernetes in the DevOps community can’t be overlooked, its become the go to orchestration solution for a lot of companies. I typically view my Homelab as a way to tinker and keep up to date with tech, so I wanted to give kubernetes a chance at home. Since I wasn’t fully sold on how I could utilize it at home, I decided to go the VM approach instead of buying new bare-metal hosts for the purpose.</p><p>I looked into <a href="https://github.com/kelseyhightower/kubernetes-the-hard-way">kubernetes the hard way</a>, it was a great deep dive into the components - but I was looking for something a bit more lightweight. K3s seemed perfect for my usecase, it was: Lightweight, Easy to install (via ansible or k3sup) and had low ram requirements. Time to setup the VMs and get to the races!</p><h3 id="Setting-up-VMs-on-my-NAS"><a href="#Setting-up-VMs-on-my-NAS" class="headerlink" title="Setting up VMs on my NAS"></a>Setting up VMs on my NAS</h3><p>My home NAS is an 4 core CPU (<code>i3-10100 CPU</code> ) with 16GB of RAM (<code>Crucial 2666Mhz</code>) and 16TB of space running on a B460M motherboard with Unraid as my software of choice. My considerations at the time of purchase was that it should be low power as well as support Intel Quick Sync for Plex - So the setup isn’t optimized for heavy workloads. That being said, its already running 5-6 docker containers and 1 VMs without any issues - All at a relatively low power draw.</p><p>I uploaded the latest Debian 12 ISO on to the NAS <code>images</code> and went to the VM page to create a new VM. Unfortunately Unraid allows you to create just 1VM at a time (Some posts online indicate you can copy over the disk image to clone it) - So I just did the creation process 3 times manually. In the future, I should probably have a pre-provisioned image with user account and basic tools installed and use that as the base.</p><p>The 3 VMs worked great, I was able to SSH into all of them and ensured that they have a static IP. Now I had to decide how to install k3s on these VMs.</p><h3 id="Setting-up-k3s"><a href="#Setting-up-k3s" class="headerlink" title="Setting up k3s"></a>Setting up k3s</h3><p>I tested out two approaches(k3sup and ansible), eventually settling on the Ansible based approach since I’m most comfortable with it. I’ll describe the k3sup approach and then the ansible approach later.</p><h4 id="k3sup"><a href="#k3sup" class="headerlink" title="k3sup"></a>k3sup</h4><p><a href="https://github.com/alexellis/k3sup">k3sup</a> is an extremely easy to use and one-command approach to setting up k3s. You can target a remote host by just specifying the IP, k3sup will execute the commands via SSH. Install k3sup via their github repo instructions before proceeding.</p><p>Kubernetes has a <code>server</code>-<code>agent</code> model, so its prudent to setup the <code>server</code> first and then join the <code>agents</code> to it. In order to setup the <code>server</code>, you can just run the command <code>k3sup install --ip $IP --user $USER</code>. This will already result in a <code>kubeconfig</code> file that you need for accessing the cluster in the current location from which you ran the <code>k3sup</code> command.</p><p>In order to setup the <code>agents</code>, simply run <code>k3sup join --ip $AGENT_IP --server-ip $SERVER_IP --user $USER</code> and <code>k3sup</code> will automagically install the required software and setup the cluster.</p><p>You can run <code>kubectl --kubeconfig=$(pwd)/kubeconfig get nodes</code> to check if the nodes are all present as expected.</p><p><code>k3sup</code> was extremely easy to use, but the setup process felt a bit ephemeral to me. I’m sure you can also track which version of k3s the nodes run, but I still felt a bit uneasy that it wasn’t defined in a static manner. Most of the problems can also be fixed by a simple script or by allowing <code>ansible</code> to call <code>k3sup</code> instead - meaning easier integration with the rest of my homelab setup.</p><h4 id="Ansible"><a href="#Ansible" class="headerlink" title="Ansible"></a>Ansible</h4><p>Most of my homelab is already controlled by <code>ansible</code>, so it felt natural to try and rely on <code>ansible</code> atleast for the provisioning aspects of the homelab. The team I work with at the Ethereum Foundation maintains <a href="https://github.com/ethpandaops/ansible-collection-general">this</a> massive list of <code>ansible roles</code> for use in most of our day to day activities. We do have some baremetal k3s clusters that have been provisioned with the <code>bootstrap</code> and <code>k3s</code> roles. I know that the setup has worked well for almost a year and the config and versions are easy to track&#x2F;read - So I wanted to reuse them for my homelab as well. </p><p>I modified my <code>inventory.ini</code> to include the new VM hosts that I provisioned:  </p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># VMs on home NAS, used for k3s  </span></span><br><span class="line"><span class="string">k8s-node-1</span> <span class="string">ansible_host=...</span>  </span><br><span class="line"><span class="string">k8s-node-2</span> <span class="string">ansible_host=...</span>  </span><br><span class="line"><span class="string">k8s-node-3</span> <span class="string">ansible_host=...</span></span><br><span class="line"></span><br><span class="line">[<span class="string">k3s_cluster</span>]  </span><br><span class="line"><span class="string">k8s-node-1</span>  </span><br><span class="line"><span class="string">k8s-node-2</span>  </span><br><span class="line"><span class="string">k8s-node-3</span>  </span><br><span class="line">  </span><br><span class="line">[<span class="string">k3s_server</span>]  </span><br><span class="line"><span class="string">k8s-node-1</span>  </span><br><span class="line">  </span><br><span class="line">[<span class="string">k3s_agent</span>]  </span><br><span class="line"><span class="string">k8s-node-2</span>  </span><br><span class="line"><span class="string">k8s-node-3</span></span><br></pre></td></tr></table></figure><p>The above <code>inventory.ini</code> now defines the 3 VMs I created, the <code>ansible_host</code> variable defines the IP address they are reachable with, The <code>k3s_cluster</code> defines the entire cluster, the others define the <code>server</code> and the <code>agents</code>. </p><p>I then created a file in <code>group_vars/k3s_cluster.yaml</code> that defines the k3s version to use as well as the server IP:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">k3s_version:</span> <span class="string">v1.27.4+k3s1</span></span><br><span class="line"><span class="attr">k3s_server_ip:</span> <span class="string">...</span></span><br></pre></td></tr></table></figure><p>The <code>server</code> and <code>agent</code> have a very similar <code>group_vars/k3s_server.yaml</code> or <code>group_vars/k3s_agent.yaml</code> file:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">k3s_node_type:</span> <span class="string">server</span> <span class="comment"># or agent</span></span><br><span class="line"><span class="attr">k3s_server_extra_args:</span> <span class="string">&gt;-</span> <span class="comment"># or `k3s_agent_extra_args`</span></span><br><span class="line">  <span class="string">--node-external-ip=&#123;&#123;</span> <span class="string">ansible_host</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><p>It is also possible with the <code>ansible</code> role to setup a <code>wireguard</code> mesh in between the hosts (Bit overkill for a homelab, but useful if you have many hosts).</p><p>Then you’d have to install the <code>ethpandaops</code> ansible collection found <a href="https://github.com/ethpandaops/ansible-collection-general">here</a>.</p><p>The playbook to run would look as below:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">hosts:</span> <span class="string">k3s_cluster</span></span><br><span class="line">  <span class="attr">become:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">serial:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; batch_count | default(&#x27;100%&#x27;) &#125;&#125;</span>&quot;</span></span><br><span class="line">  <span class="attr">roles:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">role:</span> <span class="string">ethpandaops.general.k3s</span></span><br><span class="line">      <span class="attr">tags:</span> <span class="string">k3s</span></span><br></pre></td></tr></table></figure><p>Now it all comes together with <code>ansible-playbook -i inventories/servers/inventory.ini playbooks/setup_k3s.yml</code>. This will setup the cluster in the configuration defined and allow you to update it with ease if you ever need to. You can SSH into one of the nodes and grab the <code>kubeconfig</code>, place it in your local <code>.kube/</code> folder and from then on <code>kubectl get nodes</code> should work without any hitches(make sure you have the right context set)! </p><p>Cleanups can be managed with a simple variable being set, <code>-e k3s_cleanup=true</code>!</p><h3 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h3><p>We looked into the why as well as a few approach to setup <code>k3s</code>, we now should have a running cluster that’s ready for use! We definitely could have done certain things in a more automated manner, but I was able to get the cluster up in under an hour - so its probably enough for now. Stay tuned for more about how to actually deploy things to the cluster and to learn if I decide to give up on it altogether :D</p>]]>
    </content>
    <id>https://parithosh.com/2023-08-12-setup-k3s-cluster-on-vm/</id>
    <link href="https://parithosh.com/2023-08-12-setup-k3s-cluster-on-vm/"/>
    <published>2023-08-12T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>The ubiquity of kubernetes in the DevOps community can’t be overlooked, its become the go to orchestration solution for a lot of companie]]>
    </summary>
    <title>How to setup a k3s cluster on a NAS</title>
    <updated>2023-08-12T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Sometimes, we need to fetch the information at a slot in order to debug a<br>testnet. The consensus clients make this easy by following the standard beacon<br>APIs [<a href="https://ethereum.github.io/beacon-APIs]">https://ethereum.github.io/beacon-APIs]</a> (most of them atleast), we just<br>need to query them in order.</p><p>Information that is usually at hand for debugging: Slot number. So we can just<br>use the endpoint &#x2F;eth&#x2F;v2&#x2F;beacon&#x2F;blocks&#x2F;{block_id}, where block_id can be either<br>“finalized”,”genesis”,”slot” or a “hex encoded root”.</p><p>Since we would want to do this often, let’s abstract it into a cli function. I<br>will assume from here on that your system uses zsh and has jq installed, it<br>should however work similarly for bash.</p><ol><li>Open your ~&#x2F;.zshrc file</li><li>Go to the bottom and add a comment # fetch the entire block given a CL slot<br>number or something similar</li><li>Below the comment, enter: fetch_block_at_slot(){curl -sS<br>“$1”&#x2F;eth&#x2F;v1&#x2F;beacon&#x2F;blocks&#x2F;$2 | jq }</li><li>Reload the config with source ~&#x2F;.zshrc</li></ol><p>This will add the function fetch_block_at_slot and it accepts 2 arguments: The<br>first argument being the endpoint to which the data is feched from. The second<br>is the slot number.</p><p>An example of the usage is:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fetch_block_at_slot localhost:4000 1100</span><br><span class="line">&gt; &#123;</span><br><span class="line">  &quot;data&quot;: &#123;</span><br><span class="line">    &quot;message&quot;: &#123;</span><br><span class="line">      &quot;slot&quot;: &quot;1100&quot;,</span><br><span class="line">      &quot;proposer_index&quot;: &quot;12798&quot;,</span><br><span class="line">      &quot;parent_root&quot;: &quot;0xf130e3162dc304d05d397c8d79f81b38e7d7a1b92a7ca87b65f5c2f81f03a10a&quot;,</span><br><span class="line">      &quot;state_root&quot;: &quot;0xea580fc86377c3db5301acd224204603394f607e98c9a63813d6e2ef68cd605c&quot;,</span><br><span class="line">      &quot;body&quot;: &#123;</span><br><span class="line">....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Supposing we need to fetch the execution payload very often, well, we can just<br>add a new function to handle that as well! Similar to before, add a function<br>called: <code>fetch_execution_payload(){curl -sS &quot;$1&quot;/eth/v1/beacon/blocks/$2 | jq -r &#39;.data.message.body.execution_payload&#39; }</code>.</p><p>An example of this function is:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">fetch_execution_payload localhost:4000 1100</span><br><span class="line">&gt; &#123;</span><br><span class="line">  &quot;parent_hash&quot;: &quot;0x0000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;coinbase&quot;: &quot;0x0000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;state_root&quot;: &quot;0x0000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;receipt_root&quot;: &quot;0x0000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;logs_bloom&quot;: &quot;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;random&quot;: &quot;0x0000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;block_number&quot;: &quot;0&quot;,</span><br><span class="line">  &quot;gas_limit&quot;: &quot;0&quot;,</span><br><span class="line">  &quot;gas_used&quot;: &quot;0&quot;,</span><br><span class="line">  &quot;timestamp&quot;: &quot;0&quot;,</span><br><span class="line">  &quot;extra_data&quot;: &quot;0x&quot;,</span><br><span class="line">  &quot;base_fee_per_gas&quot;: &quot;0&quot;,</span><br><span class="line">  &quot;block_hash&quot;: &quot;0x0000000000000000000000000000000000000000000000000000000000000000&quot;,</span><br><span class="line">  &quot;transactions&quot;: []</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>TTD stands for total terminal difficulty, it defines when the Ethereum PoW is<br>ready for the merge. Difficulty goes up with each block till TTD is hit, if<br>things go correctly, we then switch to PoS mode. Total Difficulty can be<br>obtained from the block information, similar to the earlier examples.</p><ol><li>Open your ~&#x2F;.zshrc file</li><li>Go to the bottom and add a comment # calculate percentage of TTD doneor<br>something similar</li><li>Below the comment, enter: fetch_ttd_status(){echo “Total Difficulty<br>completed till TTD: <code>$(($(curl -s --request POST &quot;$1&quot; --header &#39;Content-Type: application/json&#39; --data-raw &#39;{ &quot;jsonrpc&quot;:&quot;2.0&quot;, &quot;method&quot;:&quot;eth_getBlockByNumber&quot;, &quot;params&quot;:[ &quot;latest&quot;, false ], &quot;id&quot;:1 }&#39; | jq -r &#39;.result.totalDifficulty&#39;)))/&quot;$2&quot; * 100&quot; | bc) % }</code></li><li>Reload the config with source ~&#x2F;.zshrc</li><li>You need to pass the RPC URL as well as the Total Terminal Difficulty while<br>calling this function</li></ol><p>An example of this function is:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">fetch_ttd_status https://rpc.devnet3.themerge.dev 5000000000</span><br><span class="line">&gt; Total Difficulty completed till TTD:  8.1700 % </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://parithosh.com/2021-12-07-fetch-ttd-from-slot/</id>
    <link href="https://parithosh.com/2021-12-07-fetch-ttd-from-slot/"/>
    <published>2021-12-07T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Sometimes, we need to fetch the information at a slot in order to debug a<br>testnet. The consensus clients make this easy by following t]]>
    </summary>
    <title>Fetching slot and Difficulty information from the terminal</title>
    <updated>2021-12-07T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Let’s say for some reason, you ran git add . and said “oops!”, there’s a file<br>there that shouldn’t be pushed. Easy right? You just run git rm filename and<br>call it a day. However, it errors out and in a fit of rage you run git rm -f<br>filename! That worked, it doesn’t show up in the commit anymore.</p><p>Some time passes and you realize you need something from that file, but for<br>whatever reason the file doesn’t exist anymore. Yikes. Well, no need to worry.<br>Git will have stored the file as a blob to its object store, you just need to<br>figure out which one it is.</p><p>Let’s recreate the scenario really quick:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ git init</span><br><span class="line">&gt; Initialized empty Git repository in /tmp/.git/</span><br><span class="line"></span><br><span class="line">$ echo &quot;text&quot; &gt; test</span><br><span class="line"></span><br><span class="line">$ git add test.txt</span><br><span class="line"></span><br><span class="line">$ git rm -f test</span><br><span class="line">&gt; rm &#x27;test&#x27;</span><br></pre></td></tr></table></figure><p>Great, now we want to try and get back the test file. There are two ways to do<br>this:</p><ol><li>You can list the dangling objects by using git fsck –lost-found giving us<br>the result dangling blob <code>8e27be7d6154a1f68ea9160ef0e18691d20560dc</code>. We can<br>check the file content by using git show<br><code>8e27be7d6154a1f68ea9160ef0e18691d20560d</code> and restore the file.</li><li>You can manually check the folders and list the objects to check which one<br>is your file, finally restoring it.</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ ls .git/objects/</span><br><span class="line">&gt; 8e  info  pack</span><br><span class="line"></span><br><span class="line">$ ls .git/objects/8e</span><br><span class="line">27be7d6154a1f68ea9160ef0e18691d20560dc</span><br><span class="line"></span><br><span class="line">$ git show 8e27be7d6154a1f68ea9160ef0e18691d20560d</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://parithosh.com/2021-11-1-restoring-uncommited-and-deleted-git-files/</id>
    <link href="https://parithosh.com/2021-11-1-restoring-uncommited-and-deleted-git-files/"/>
    <published>2021-11-18T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Let’s say for some reason, you ran git add . and said “oops!”, there’s a file<br>there that shouldn’t be pushed. Easy right? You just run]]>
    </summary>
    <title>Restoring uncommited and deleted git files</title>
    <updated>2021-11-18T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Ethereum consensus layer or more popularly known as eth2 is an upgrade to the Ethereum aiming at a switch from PoW to PoS<br>consensus. The eth2 phase 0 launch has been successful so far and the Beacon chain has been functional for almost 9 months.<br>The eth2 community is now actively working on the <code>altair</code> hard fork, the first protocol update for the eth2 network. In<br>preparation for this hard fork there have been multiple <code>devnets</code> to test for bugs.</p><p><code>altair-devnet-3</code> is the fourth devnet spun up with the express intent to test client compatibility and catch any<br>issues before a larger public testnet. The specs for the devnet can be found <a href="https://github.com/eth2-clients/eth2-networks/tree/master/shared/altair-devnet-3">here</a>.<br>The devnet had a rocky start due to improper configs by some participant, the issue was found quite quickly and the devnet<br>started to finalize. The altair hardfork was successfully completed and the devnet continued as expected. The attestation<br>rates were relatively good and sync aggregate participation rates were hovering around the 95% mark. The devnet had worked<br>as expected and things seemed good. The rest of the blogpost purely refers to steps used in debugging and not the actual<br>fixe itself.</p><p>Metrics and logs seemed to indicate something was wrong though. First step was to check the logs - the lighthouse nodes<br>seem to have lost all their peers and were at roughly 3 peers. The next step was to check the metrics to see what the timeline<br>looked like. I logged into Grafana and created a quick dashboard that logged peers per client. The metrics clearly showed<br> that the lighthouse client was leaking peers slowly. That tells us that there was no issue with the hardfork leading to a<br>sharp drop in peers. It additionally tells us that the peer score was slowly being reduced until the lighthouse node banned<br>the peer.</p><img src="/2021-08-12-debug-an-eth2-devnet/altair-prysm-lighthouse-peers.png" class="" title="Lighthouse and Prysm peers"><p>Armed with this information, I restarted the nodes with <code>debug</code> logging enabled to get more information about peer scoring.<br>Since debug logs can be extremely overwhelming, some <code>grep-fu</code> was required. Since we don’t have an exact log line we are looking<br>for, general grepping of terms such as <code>voted</code>, <code>invalid</code> and <code>downvoted</code> would make sense.</p><p>I ran <code>docker logs beacon 2&gt;&amp;1 | grep &quot;invalid&quot;</code> and most results were related to <code>PastSlots</code> indicating a high latency to<br>other peers and receiving slots too late. But there was one log message in particular that was interesting:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DEBG Invalid sync committee message from network, type: &quot;sync_contribution&quot;, peer_id: 16Uiu2HAmP4jDeYxvVkVizndoYksJYZvpCkgSrWxeHEL5qj4tsTtk, reason: InvalidSignature</span><br></pre></td></tr></table></figure><p>Now we had an invalid signature (bad thing) and a peerID to hunt down. The lighthouse API endpoint allows us to get all the<br>peers of that node along with metadata about the client and the IP address. The standard endpoint is <code>/eth/v1/beacon/peers</code>,<br>but lighthouse also provides us with <code>/lighthouse/peers</code> that contains more metadata. Since the response would be a wall of json,<br>we use <code>jq</code> to make it look pretty and <code>grep</code> to fetch the corresponding peer. Grep allows us to get the lines above and below<br>the matched line, allowing us to get the entire json body.</p><p>Running <code>curl localhost:4000/lighthouse/peers | jq | grep -B 1 -A 52 &quot;16Uiu2HAmP4jDeYxvVkVizndoYksJYZvpCkgSrWxeHEL5qj4tsTtk&quot;</code><br>gives us the information about the peer. The response looks like this:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;peer_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;16Uiu2HAmP4jDeYxvVkVizndoYksJYZvpCkgSrWxeHEL5qj4tsTtk&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;peer_info&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;_status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Healthy&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Real&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;lighthouse_score&quot;</span><span class="punctuation">:</span> <span class="number">-58.15072332402626</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;gossipsub_score&quot;</span><span class="punctuation">:</span> <span class="number">8.653623324336476</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;ignore_negative_gossipsub_score&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span> <span class="number">-58.14044714632861</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;client&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;kind&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Nimbus&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;unknown&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;os_version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;unknown&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;protocol_version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ipfs/0.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;agent_string&quot;</span><span class="punctuation">:</span> <span class="string">&quot;nimbus&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;connection_status&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;banned&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;connections_in&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;connections_out&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;last_seen&quot;</span><span class="punctuation">:</span> <span class="number">23148</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span>...</span><br></pre></td></tr></table></figure><p>That tells us the peer in question that produced the invalid signature is a nimbus client and it was banned by lighthouse due<br>to its poor score.</p><p>Since the devnet is a multiclient testnet, we should be able to see what the other clients think of this peer. So I SSH-ed into<br>an instance containing a prysm instance and checked the logs for this peer. I switched prysm to <code>debug</code> logging as well and<br>ran <code>docker logs beacon 2&gt;&amp;1 | grep &quot;16Uiu2HAmP4jDeYxvVkVizndoYksJYZvpCkgSrWxeHEL5qj4tsTtk&quot;</code>. The logs however indicate no<br>issues. Prysm simply listed one log message when it connected to the peer and there were no other entries about the peer.</p><p>The information was passed on to the respective client teams along with access to the logs. They are now investigating the issue further.<br>Since this is not a critical issue and hasn’t stopped the chain from finalizing, it would be treated as something that needs a patch. I<br>will try my best to update this blogpost in the future with the final outcome.</p>]]>
    </content>
    <id>https://parithosh.com/2021-08-12-debug-an-eth2-devnet/</id>
    <link href="https://parithosh.com/2021-08-12-debug-an-eth2-devnet/"/>
    <published>2021-08-12T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Ethereum consensus layer or more popularly known as eth2 is an upgrade to the Ethereum aiming at a switch from PoW to PoS<br>consensus. T]]>
    </summary>
    <title>Debugging an eth2 devnet</title>
    <updated>2021-08-12T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Backups are an extremely important part of any system. You never need them until you really do. The golden rule<br>for backups is called the <code>3-2-1 rule</code>, essentially 3 copies of the data on 2 different media and 1 offsite location. Ideally<br>the backups are automated and run at some fixed interval.</p><p>Data de-duplication refers to a method to eliminate storing duplicate copies of repeating data. This is extremely advantageous<br>if we want to save space on our backup machine for our regular backups. Since the offsite location might not be fully under<br>our control we would also want to encrypt our backups as well as compress it to save space.</p><p>So essentially we are looking for a backup tool that:<br>    - is space efficient<br>    - supports encryption<br>    - supports compression<br>    - run from the cli&#x2F;scriptable<br>    - performs de-duplication<br>    - available on multiple platforms</p><p>Enter BorgBackup(short Borg), a client-server based backup tool that fits all of our criteria! Let’s set it up and see how it works.</p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><p>For the purpose of this post, I will assume the host and target machine is a linux based system (Ubuntu&#x2F;Debian ideally).</p><p>Borg is part of the official ubuntu&#x2F;debian repos, so you can install it with <code>sudo apt install borgbackup</code></p><h2 id="Configuration"><a href="#Configuration" class="headerlink" title="Configuration"></a>Configuration</h2><p>Borgbackup can be run to backup locally or to a mounted volume or to backup remotely. This config will focus on remote backups.</p><ul><li>Initialize the backup repository on your <code>backup server</code> with <code>borg init --encryption=repokey &lt;/path/to/repo&gt;</code>. Borg will setup an<br>empty backup repository that is ready for future backups. Please note the path, as that is important information. Enter<br>an encryption password when prompted, this is your encryption key and is needed for each future backup or restore.</li><li>Now shift to your host machine and get your SSH public key, typically <code>cat ~/.ssh/id_rsa.pub</code> and add this key to the<br>backup machine’s file named <code>~/.ssh/authorized_keys</code>. This would allow your host machine to SSH into your target machine<br>without an SSH password.</li><li>Test the SSH connection between the two with <code>ssh username@backup-ip-address</code>. Additionally, you can create a dedicated<br>backup user on the backup machine for a tighter control.</li><li>We can create a test backup to check if everything works as expected with this command: <code>borg create -v --stats username@backup-ip-address:&lt;/path/to/repo&gt;::BACKUP_NAME &lt;/path/to/something/to/backup&gt;</code>.</li><li>Now run <code>borg list username@backup-ip-address:&lt;/path/to/repo&gt;</code>, it should list the backup you just created.</li><li>We can start scripting this process now, create a file called <code>borgbackup.sh</code>:<br>Note: I like to have my backups with unique names, so I append the backup name with the current time.</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Backup a folder to a remote address using borg.</span></span><br><span class="line">set -eu</span><br><span class="line"></span><br><span class="line">echo &quot;Successfully set the time&quot;</span><br><span class="line">TIME=$(date +%s)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">export</span> the borg passphrase as an environment variable to avoid having to enter it each <span class="keyword">time</span></span></span><br><span class="line">export BORG_PASSPHRASE=&#x27;blablabla&#x27;</span><br><span class="line">echo &quot;Backing up local to remote server&quot;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Backing up xyz content</span></span><br><span class="line">borg create --progress --stats username@backup-ip-address:&lt;/path/to/repo&gt;::BACKUP_NAME-$TIME &lt;/path/to/something/to/backup&gt;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Prune old backups</span></span><br><span class="line">borg prune username@backup-ip-address:&lt;/path/to/repo&gt; --keep-weekly=1 --keep-monthly=2</span><br></pre></td></tr></table></figure><ul><li>Make the script executable with <code>chmod +x borgbackup.sh</code> and run the script to test it. Run <code>borg list username@backup-ip-address:&lt;/path/to/repo&gt;</code><br>to confirm.</li><li>Let’s make the script run periodically now, we will do this using <code>crontab</code>. Run <code>crontab -e</code> and configure the<br>frequency and script, e.g: <code>0 3 * * * /path/to/borgbackup.sh</code> to run the script at 3AM every morning. Check a crontab guide<br>to figure out how to modify the time.</li><li>Run <code>crontab -l</code> to confirm the crontab has been setup.</li></ul><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Have fun with your nightly backups! I will try to write up a guide for setting up a wireguard system, so that you can roll<br>your own private VPN and move your backup server far away, yet private.</p>]]>
    </content>
    <id>https://parithosh.com/2021-07-27-backups-with-borgbackup/</id>
    <link href="https://parithosh.com/2021-07-27-backups-with-borgbackup/"/>
    <published>2021-07-27T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Backups are an extremely important part of any system. You never need them until you really do. The golden rule<br>for backups is called]]>
    </summary>
    <title>Backups with BorgBackup</title>
    <updated>2021-07-27T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Loki is a log aggregation system inspired by Prometheus. It is extremely easy to integrate it with Grafana as well as<br>the alertmanager.</p><p>In this blogpost, we will look at deploying Loki, deploying Promtail and collecting docker logs with the log driver.</p><h2 id="Loki"><a href="#Loki" class="headerlink" title="Loki"></a>Loki</h2><p>We will be deploying Loki as a docker container.</p><ul><li>Create a directory to store the config</li><li>Copy the loki configuration inside this folder called <code>loki-config.yml</code> with the below contents:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"># Enables authentication through the X-Scope-OrgID header, which must be present</span><br><span class="line"># if true. If false, the OrgID will always be set to &quot;fake&quot;.</span><br><span class="line">auth_enabled: false</span><br><span class="line"></span><br><span class="line"># Configures the server of the launched module(s).</span><br><span class="line">server:</span><br><span class="line">  http_listen_port: 3100</span><br><span class="line">  grpc_listen_port: 9096</span><br><span class="line"></span><br><span class="line"># Configures the ingester and how the ingester will register itself to a</span><br><span class="line"># key value store.</span><br><span class="line">ingester:</span><br><span class="line">  wal:</span><br><span class="line">    enabled: true</span><br><span class="line">    dir: /tmp/wal</span><br><span class="line">  lifecycler:</span><br><span class="line">    address: 127.0.0.1</span><br><span class="line">    ring:</span><br><span class="line">      kvstore:</span><br><span class="line">        store: inmemory</span><br><span class="line">      replication_factor: 1</span><br><span class="line">    final_sleep: 0s</span><br><span class="line">  chunk_idle_period: 1h       # Any chunk not receiving new logs in this time will be flushed</span><br><span class="line">  max_chunk_age: 1h           # All chunks will be flushed when they hit this age, default is 1h</span><br><span class="line">  chunk_target_size: 1048576  # Loki will attempt to build chunks up to 1.5MB, flushing first if chunk_idle_period or max_chunk_age is reached first</span><br><span class="line">  chunk_retain_period: 30s    # Must be greater than index read cache TTL if using an index cache (Default index read cache TTL is 5m)</span><br><span class="line">  max_transfer_retries: 0     # Chunk transfers disabled</span><br><span class="line"></span><br><span class="line"># Configures the chunk index schema</span><br><span class="line">schema_config:</span><br><span class="line">  configs:</span><br><span class="line">    - from: 2021-06-23</span><br><span class="line">      store: boltdb-shipper</span><br><span class="line">      object_store: filesystem</span><br><span class="line">      schema: v11</span><br><span class="line">      index:</span><br><span class="line">        prefix: index_</span><br><span class="line">        period: 24h</span><br><span class="line"></span><br><span class="line"># Configures where Loki will store data.</span><br><span class="line">storage_config:</span><br><span class="line">  boltdb_shipper:</span><br><span class="line">    active_index_directory: /tmp/loki/boltdb-shipper-active</span><br><span class="line">    cache_location: /tmp/loki/boltdb-shipper-cache</span><br><span class="line">    cache_ttl: 24h         # Can be increased for faster performance over longer query periods, uses more disk space</span><br><span class="line">    shared_store: filesystem</span><br><span class="line">  filesystem:</span><br><span class="line">    directory: &quot;/mnt/config/chunks&quot;</span><br><span class="line"></span><br><span class="line"># Configures the compactor component which compacts index shards for performance.</span><br><span class="line">compactor:</span><br><span class="line">  working_directory: /tmp/loki/boltdb-shipper-compactor</span><br><span class="line">  shared_store: filesystem</span><br><span class="line"></span><br><span class="line"># Configures limits per-tenant or globally</span><br><span class="line"># ingestation when promtail is run for the first time can be a lot of data, hence the high burst limit</span><br><span class="line">limits_config:</span><br><span class="line">  reject_old_samples: true</span><br><span class="line">  reject_old_samples_max_age: 168h</span><br><span class="line">  ingestion_rate_mb: 50</span><br><span class="line">  ingestion_burst_size_mb: 150</span><br><span class="line"></span><br><span class="line"># Configures how Loki will store data in the specific store.</span><br><span class="line">chunk_store_config:</span><br><span class="line">  max_look_back_period: 0s</span><br><span class="line"></span><br><span class="line"># Configures the table manager for retention</span><br><span class="line">table_manager:</span><br><span class="line">  retention_deletes_enabled: false</span><br><span class="line">  retention_period: 0s</span><br><span class="line"></span><br><span class="line"># The ruler_config configures the Loki ruler.</span><br><span class="line">ruler:</span><br><span class="line">  storage:</span><br><span class="line">    type: local</span><br><span class="line">    local:</span><br><span class="line">      directory: /tmp/loki/rules</span><br><span class="line">  rule_path: /tmp/loki/rules-temp</span><br><span class="line">  alertmanager_url: http://localhost:9093</span><br><span class="line">  ring:</span><br><span class="line">    kvstore:</span><br><span class="line">      store: inmemory</span><br><span class="line">  enable_api: true</span><br></pre></td></tr></table></figure><ul><li>Run the docker container with the below command:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name loki --restart unless-stopped -p 3100:3100 -v &#123;&#123; loki_dir &#125;&#125;:/mnt/config grafana/loki:latest --config.file=/mnt/config/loki-config.yaml</span><br></pre></td></tr></table></figure><ul><li>Run <code>curl localhost:3100/ready</code> to check if the container is up as expected</li></ul><p>Now we have a running version of Loki, we just need to get logs to it.</p><h2 id="Promtail"><a href="#Promtail" class="headerlink" title="Promtail"></a>Promtail</h2><p>Promtail is a collector agent that scrapes the logs on a<br>local machine, parses them and sends the logs to a Loki<br>instance for longer term storage.</p><p>We will also run promtail as a docker container. We use a volume mapping in read-only mode to allow Promtail to read the<br>system logs of the machine. These logs are then scraped as specified in the <code>scrape_configs</code>, similar to Prometheus.<br>Any extra labels can be added at this stage.</p><ul><li>Create a directory to store the config</li><li>Copy the promtail configuration inside this folder called <code>promtail-config.yml</code> with the below contents:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"># speficies the promtail server ports</span><br><span class="line">server:</span><br><span class="line">  http_listen_port:9080</span><br><span class="line">  grpc_listen_port:0</span><br><span class="line"></span><br><span class="line"># specifies the temp positions directory</span><br><span class="line">positions:</span><br><span class="line">  filename: /tmp/positions.yaml</span><br><span class="line"></span><br><span class="line"># specifies the loki server URL to which logs are sent</span><br><span class="line">clients:</span><br><span class="line">  - url: http://localhost:3100/loki/api/v1/push</span><br><span class="line"></span><br><span class="line"># specifies the scrape locations for the logs as well as any postprocessing</span><br><span class="line">scrape_configs:</span><br><span class="line">  - job_name: system</span><br><span class="line">    static_configs:</span><br><span class="line">      - targets:</span><br><span class="line">          - localhost</span><br><span class="line">        labels:</span><br><span class="line">          job: varlogs</span><br><span class="line">          __path__: /var/log/*log</span><br></pre></td></tr></table></figure><ul><li>Run the docker container with the below command:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name promtail --restart unless-stopped  -v &#123;&#123; promtail_dir &#125;&#125;:/mnt/config -v /var/log:/var/log:ro grafana/promtail:2.2.1 --config.file=/mnt/config/promtail-config.yaml</span><br></pre></td></tr></table></figure><h2 id="Docker-logs"><a href="#Docker-logs" class="headerlink" title="Docker logs"></a>Docker logs</h2><p>While it is easy for promtail to forward docker logs and tag them, it is relatively hard for it to label the logs with the<br>associated <code>container_name</code> or other labels. This could be done before running the docker container itself, but for sake<br>of simplicity as well as not needing to always add labels, we can set it as the default docker log method.</p><p>We achieve this by installing the loki docker plugin and then by setting the default docker log method to it. That way<br>all newly created containers will use the new log plugin.</p><ul><li>Install the docker loki plugin with:<br><code>docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions</code></li><li>Either modify or create a file at <code>/etc/docker/daemon.json</code> with the following content. Add any extra <code>log-opts</code> if needed:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;debug&quot; : true,</span><br><span class="line">  &quot;log-driver&quot;: &quot;loki&quot;,</span><br><span class="line">  &quot;log-opts&quot;: &#123;</span><br><span class="line">    &quot;loki-url&quot;: &quot;http://localhost:&#123;&#123; loki_http_listen_port &#125;&#125;/loki/api/v1/push&quot;,</span><br><span class="line">    &quot;loki-batch-size&quot;: &quot;400&quot;,</span><br><span class="line">    &quot;loki-external-labels&quot;: &quot;job=dockerlogs,container_name=&#123;&#123;.Name&#125;&#125;&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>Restart the docker engine to apply the changed with: <code>sudo systemctl restart docker</code></li><li>Now we can try running a new container and send logs directly to loki with <code>docker run -it --name test alpine echo hi</code></li></ul><p>As long as Loki has been added as a data source on Grafana, we should be able to see the logs on the <code>Explore tab</code> on Grafana.</p>]]>
    </content>
    <id>https://parithosh.com/2021-06-29-logging-with-loki/</id>
    <link href="https://parithosh.com/2021-06-29-logging-with-loki/"/>
    <published>2021-06-29T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Loki is a log aggregation system inspired by Prometheus. It is extremely easy to integrate it with Grafana as well as<br>the alertmanager]]>
    </summary>
    <title>Logging with Loki</title>
    <updated>2021-06-29T00:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>parithosh.jayanthi</name>
    </author>
    <content>
      <![CDATA[<p>Warning: Please do not try this with real ether, this blogpost is purely meant<br>for testnet use</p><p>Let’s assume for a second you have a lot of testnet ether lying around and you<br>want to test out your staking setup. Sounds great!…until you have to make the<br>dozens of deposits. Thankfully we can script this and perform it in stages!</p><p>Pre-requisites:<br>We will use the following tools in this blogpost:</p><ul><li>eth2-val-tools: <a href="https://github.com/protolambda/eth2-val-tools">https://github.com/protolambda/eth2-val-tools</a></li><li>ethereal: <a href="https://github.com/wealdtech/ethereal">https://github.com/wealdtech/ethereal</a></li></ul><p>Deposit steps:</p><ol><li><p>We start by obtaining the Goerli ether, either from faucets or from someone<br>who has a lot. For the sake of this blog, let’s assume the funds are held on<br>metamask.</p></li><li><p>Create a new folder and a file inside that folder called secrets.env. The<br>secrets.env file will contain our secrets to avoid having to store them in our<br>script directly. You should add this file to your .gitignore to avoid committing<br>it by mistake.</p></li><li><p>Fill secrets.env with the following variable names:</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">ACC_START_INDEX=</span><br><span class="line">ACC_END_INDEX=</span><br><span class="line">DEPOSIT_AMOUNT=</span><br><span class="line">FORK_VERSION=</span><br><span class="line">VALIDATORS_MNEMONIC=</span><br><span class="line">WITHDRAWALS_MNEMONIC=</span><br><span class="line">DEPOSIT_DATAS_FILE_LOCATION=</span><br><span class="line"></span><br><span class="line">DEPOSIT_CONTRACT_ADDRESS=</span><br><span class="line">ETH1_FROM_ADDR=</span><br><span class="line">ETH1_FROM_PRIV=</span><br><span class="line">FORCE_DEPOSIT=false</span><br><span class="line">ETH1_NETWORK=goerli</span><br></pre></td></tr></table></figure><p>Find the deposit contract address, deposit amount and fork version<br>for your required scenario. For example, we have these values for the Prater<br>testnet:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DEPOSIT_AMOUNT=32000000000</span><br><span class="line">FORK_VERSION=&quot;0x00001020&quot;</span><br><span class="line"></span><br><span class="line">DEPOSIT_CONTRACT_ADDRESS=&quot;0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b&quot;</span><br></pre></td></tr></table></figure><ol start="5"><li>Our Mnemonic is the source of the private keys of the validators. Loosing<br>this mnemonic means the loss of all funds, so please store the mnemonic safely.<br>We can generate the validator and withdrawal mnemonic using the tool<br>eth2-val-tools. In your terminal, type out:</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eth2-val-tools mnemonic</span><br></pre></td></tr></table></figure><p>The output should contain a list of 24 words. Copy those words into the<br>secrets.env file under the variable <code>VALIDATORS_MNEMONIC</code>. Don’t forget to wrap<br>the 24 words inside “”!</p><p>Repeat the process for <code>WITHDRAWALS_MNEMONIC</code>.</p><blockquote><p>Please backup these mnemonics safely! Ideally inside a password manager as a<br>secure note.</p></blockquote><ol start="6"><li><p>Decide on how many deposits you would like to perform. Generally this would<br>be (amount of testnet ether you possess)&#x2F;32. Set the <code>ACC_START_INDEX</code> and<br><code>ACC_END_INDEX</code> in secrets.env accordingly.</p></li><li><p>Now we can finally add our private key and address from which the deposits<br>will be made. This will be the in metamask, go to the account with the goerli<br>ether &gt; click on the three dots &gt; click on Account details &gt; click on export<br>private key. We will need the private key in the script to access the funds and<br>perform the deposits, so save this somewhere safely. Fill out <code>ETH1_FROM_ADDR</code> and<br><code>ETH1_FROM_PRIV</code> in <code>secrets.env</code> accordingly. Don’t forget to wrap both inside “”<br>and they should start with 0x.</p></li><li><p>We are finally ready to start generating our deposit data! Instead of<br>directly performing the deposits, we will split our scripts into two halves- One<br>to generate the deposit data and one to execute the deposits.</p></li></ol><p>We will store the deposit data temporarily to allow us to manually inspect it,<br>enter the full path of where the file should be stored as the variable<br><code>DEPOSIT_DATAS_FILE_LOCATION</code> in <code>secrets.env</code>.</p><p>Create a file called build_deposits.sh with the following contents:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line"></span><br><span class="line">echo &quot;USE AT YOUR OWN RISK&quot;</span><br><span class="line">read -p &quot;Are you sure? &quot; -n 1 -r</span><br><span class="line">echo</span><br><span class="line">if [[ ! $REPLY =~ ^[Yy]$ ]]</span><br><span class="line">then</span><br><span class="line">    [[ &quot;$0&quot; = &quot;$BASH_SOURCE&quot; ]] &amp;&amp; exit 1 || return 1</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">source secrets.env</span><br><span class="line"></span><br><span class="line">eth2-val-tools deposit-data \</span><br><span class="line">  --source-min=$ACC_START_INDEX \</span><br><span class="line">  --source-max=$ACC_END_INDEX \</span><br><span class="line">  --amount=$DEPOSIT_AMOUNT \</span><br><span class="line">  --fork-version=$FORK_VERSION \</span><br><span class="line">  --withdrawals-mnemonic=&quot;$WITHDRAWALS_MNEMONIC&quot; \</span><br><span class="line">  --validators-mnemonic=&quot;$VALIDATORS_MNEMONIC&quot; &gt; $DEPOSIT_DATAS_FILE_LOCATION.txt</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The script will read the variables inside secrets.env and build the deposit data<br>for you in a text file stored at <code>$DEPOSIT_DATAS_FILE_LOCATION</code>.</p><p>Run the script using .&#x2F;build_deposits.sh.</p><ol start="9"><li>The deposit data will be a bunch of json with the following format:</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;account&quot;: &quot;m/12381/3600/1/0/0&quot;,</span><br><span class="line">  &quot;deposit_data_root&quot;: &quot;&quot;,</span><br><span class="line">  &quot;pubkey&quot;: &quot;&quot;,</span><br><span class="line">  &quot;signature&quot;: &quot;&quot;,</span><br><span class="line">  &quot;value&quot;: 32000000000,</span><br><span class="line">  &quot;version&quot;: 1,</span><br><span class="line">  &quot;withdrawal_credentials&quot;: &quot;&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Manually check if it looks okay to begin with. The account format<br>would be <code>m/12381/3600/key_number/0/0</code>. The key_number should co-relate to the<br><code>ACC_START_INDEX</code> and <code>ACC_END_INDEX</code> entered earlier.</p><ol start="10"><li>Manually generate the public keys using eth2-val-tools to cross check if the<br>public keys generated are correct.</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eth2-val-tools pubkeys --validators-mnemonic=$VALIDATORS_MNEMONIC --source-min=$ACC_START_INDEX --source-max=$ACC_END_INDEX</span><br></pre></td></tr></table></figure><p>You will get a list of public keys in order of the min and max entered. I<br>normally check the first and last public key as a sanity check.</p><ol start="11"><li>Perform the deposits! Please be really sure before executing this step!</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line">echo &quot;USE AT YOUR OWN RISK&quot;</span><br><span class="line">read -p &quot;Are you sure? &quot; -n 1 -r</span><br><span class="line">echo</span><br><span class="line">if [[ ! $REPLY =~ ^[Yy]$ ]]</span><br><span class="line">then</span><br><span class="line">    [[ &quot;$0&quot; = &quot;$BASH_SOURCE&quot; ]] &amp;&amp; exit 1 || return 1</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">source secrets.env</span><br><span class="line"></span><br><span class="line">if [[ -z &quot;$&#123;DEPOSIT_CONTRACT_ADDRESS&#125;&quot; ]]; then</span><br><span class="line">  echo &quot;need DEPOSIT_CONTRACT_ADDRESS environment var&quot;</span><br><span class="line">  exit 1 || return 1</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">if [[ -z &quot;$&#123;ETH1_FROM_ADDR&#125;&quot; ]]; then</span><br><span class="line">  echo &quot;need ETH1_FROM_ADDR environment var&quot;</span><br><span class="line">  exit 1 || return 1</span><br><span class="line">fi</span><br><span class="line">if [[ -z &quot;$&#123;ETH1_FROM_PRIV&#125;&quot; ]]; then</span><br><span class="line">  echo &quot;need ETH1_FROM_PRIV environment var&quot;</span><br><span class="line">  exit 1 || return 1</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line"># Iterate through lines, each is a json of the deposit data and some metadata</span><br><span class="line">while read x; do</span><br><span class="line">   # TODO: check validity of deposit before sending it</span><br><span class="line">   account_name=&quot;$(echo &quot;$x&quot; | jq &#x27;.account&#x27;)&quot;</span><br><span class="line">   pubkey=&quot;$(echo &quot;$x&quot; | jq &#x27;.pubkey&#x27;)&quot;</span><br><span class="line">   echo &quot;Sending deposit for validator $account_name $pubkey&quot;</span><br><span class="line">   ethereal beacon deposit \</span><br><span class="line">      --allow-unknown-contract=$FORCE_DEPOSIT \</span><br><span class="line">      --address=&quot;$DEPOSIT_CONTRACT_ADDRESS&quot; \</span><br><span class="line">      --network=$ETH1_NETWORK \</span><br><span class="line">      --data=&quot;$x&quot; \</span><br><span class="line">      --value=&quot;$DEPOSIT_ACTUAL_VALUE&quot; \</span><br><span class="line">      --from=&quot;$ETH1_FROM_ADDR&quot; \</span><br><span class="line">      --privatekey=&quot;$ETH1_FROM_PRIV&quot;</span><br><span class="line">   echo &quot;Sent deposit for validator $account_name $pubkey&quot;</span><br><span class="line">   sleep 2</span><br><span class="line">done &lt; &quot;$DEPOSIT_DATAS_FILE_LOCATION.txt&quot;</span><br></pre></td></tr></table></figure><p>This script will read the values in secrets.env and then go through the<br>temporary deposits file specified in <code>$DEPOSIT_DATAS_FILE_LOCATION</code>. It will<br>process the json line by line and perform the deposit using the private key.</p><p>Since we want to perform a large amount of deposits, we ideally want to avoid<br>dos-ing the network all at once. To avoid that, there is a sleep command between<br>each deposit to allow some time between deposits.</p><p>Note: It is possible that one or more deposits might fail if done too quickly or<br>due to the network state. To avoid this, its best to split up deposits into<br>smaller batches of 500&#x2F;1000 or so - this would also make debugging nonce issues<br>easier. I have tested doing 2000 deposits in one go on Goerli without an issue<br>with the above mentioned parameters.</p><p>Finally,Run the script using exec_deposits.sh .</p><ol start="12"><li><p>Give the transactions some time to be included in the block and indexed.<br>Then head over to a explorer such as Otterscan<br>[<a href="https://github.com/wmitsuda/otterscan]">https://github.com/wmitsuda/otterscan]</a> or Etherscan<br>[<a href="https://goerli.etherscan.io/]">https://goerli.etherscan.io/]</a> and enter either your own address or the deposit<br>contract address. You should now be able to see a list of all your deposits,<br>also the same as the output from exec_deposits.sh.</p></li><li><p>Collect one or more public keys, either from the sanity check stage of 10.<br>or from the deposit data and head over to Beaconchain<br>[<a href="https://prater.beaconcha.in/]">https://prater.beaconcha.in/]</a>, enter your Validator public key here to check<br>its status. You would usually have to wait for the deposit to be processed and<br>for the validator to be activated. Beaconchain will also show you if your<br>deposit was valid or if there was some error.</p></li><li><p>Setup your validator ahead of time! Do not wait for the validator to be<br>activated to begin the setup process, there is no penalty for starting a<br>validator before it is activated. To help with client diversity, please setup a<br>non-majority [<a href="https://twitter.com/superphiz/status/1434857668783575040]">https://twitter.com/superphiz/status/1434857668783575040]</a> client!</p></li><li><p>Profit ???</p></li></ol><p>Huge shoutout to Protolambda [<a href="https://protolambda.com/]">https://protolambda.com/]</a> and the tool developers<br>for creating both the scripts and the tools! </p>]]>
    </content>
    <id>https://parithosh.com/2021-09-06-eth2-deposits/</id>
    <link href="https://parithosh.com/2021-09-06-eth2-deposits/"/>
    <published>2021-06-09T00:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Warning: Please do not try this with real ether, this blogpost is purely meant<br>for testnet use</p>
<p>Let’s assume for a second you ha]]>
    </summary>
    <title>How to make a lot of eth2 deposits?</title>
    <updated>2021-06-09T00:00:00.000Z</updated>
  </entry>
</feed>
