<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Vishnu Deva on Medium]]></title>
        <description><![CDATA[Stories by Vishnu Deva on Medium]]></description>
        <link>https://medium.com/@vishnudeva?source=rss-cdb1ccfa62ba------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*xkNdxCYK_BtsEWN7.</url>
            <title>Stories by Vishnu Deva on Medium</title>
            <link>https://medium.com/@vishnudeva?source=rss-cdb1ccfa62ba------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:16:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@vishnudeva/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Legacy]]></title>
            <link>https://vishnudeva.medium.com/legacy-d588485c195a?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/d588485c195a</guid>
            <category><![CDATA[legacy]]></category>
            <category><![CDATA[italy]]></category>
            <category><![CDATA[rome]]></category>
            <category><![CDATA[roman-empire]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Sun, 01 Dec 2024 21:42:09 GMT</pubDate>
            <atom:updated>2025-02-23T09:59:24.181Z</atom:updated>
            <content:encoded><![CDATA[<p>I was on a flight to Rome after successfully tussling with the Lufthansa website for a window seat, only to be reminded as I sat down that airplanes have wings. Consoling myself that I could still see ahead if I positioned my neck awkwardly enough, I shielded against the glare of the afternoon sun to get a peek of the Alps as we flew over it from Munich.</p><p>I was suddenly struck by the thought that everything I knew was powered by a ball of gas billions of years old, flying over mountains that were millions of years old, visiting a city that was thousands of years old, on a contraption devised a hundred years ago.</p><p>I wondered if the ancient Romans dreamt of flying vehicles.</p><p>Rome was a revelation. The Colosseum, crumbling and pock marked, is a grand spectacle, a shocking sight even in the age of skyscrapers and megastructures— I can only imagine how it looked to people two millennia ago in all its glory. I learned that 400,000 people died here over the course of the 400 years it was used for gladiatorial combat. <em>400 years! </em>We have archaeologists now working on sites from the 1700s and 1800s. How would it have felt to know that the building you’re working in or visiting, seating tens of thousands, has been used actively for hundreds of years for the same macabre purpose?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o2VsQjW6GvqYwemwLuH-JA.jpeg" /><figcaption>The tunnels beneath the colosseum arena.</figcaption></figure><p>The Pantheon takes it to another level. It’s one thing to see a ruin of the Roman Empire’s might, but entirely another to see an intact building from 2000 years in the past. I spent hours at the Pantheon, admiring it from the outside and from within. The level of detail in the interior and the coffered dome, still the largest unreinforced concrete dome in the world, is truly a sight to see. I was left confounded as I tried to understand how the intricate artwork at such massive scale could’ve been achieved without modern precision tools.</p><p>The biggest question I had, though, as I explored Rome and saw stunning, ancient sites around seemingly every corner was: Why? Why go to this level of effort? What motivated them to do this and push the boundaries of what was possible at the time? What drove them?</p><p>In Florence, the City of Art, I found one of my favourite views in the world at the very top of the bell tower of the Santa Maria del Fiore Cathedral. It threw off my itinerary for the day because I spent two hours up there just enjoying the view of a stunningly beautiful city and that of the cathedral itself.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KTKTCTY63QJ_eght9kBZ1w.jpeg" /><figcaption>Cattedrale di Santa Maria del Fiore</figcaption></figure><p>It was also in Florence that I saw the Santa Croce Basilica, home to the tombs of Machiavelli, Galileo, and Michelangelo. They were laid to rest here with intricate, beautiful artwork celebrating their lives. But as with all tombs, I wondered, who was this meant for? A promise kept to the departed, or was it more a way for future generations to remember them? Or maybe a mix of the two; a message to everyone that sees the tombs: “Become this great, and you too could be celebrated centuries after your time.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dR19RfRCaAV5x1zUlBOpew.jpeg" /><figcaption>The tomb of Galileo.</figcaption></figure><p>In Napoli, founded as Neapolis by the ever enterprising Greeks more than 2500 years ago, I fell in love with the city’s chaos, Pizza™️, and the Neapolitan sense of style. But what I enjoyed the most was the city’s hidden underground. The Greeks painstakingly built an extensive underground aqueduct system that supplied the entire city with water till as recently as the 1800s. The tunnels are deep underground and stretch out over tens of kilometres through solid rock. The engineering brilliance, ingenuity, and persistence of those who came before us always leave me speechless.</p><p>In Pompeii, I saw the symbolic plaster casts of people that died when Mount Vesuvius erupted 2000 years ago. Contrary to what I’d vaguely thought before, there was nothing organic there. It was just plaster in a roughly humanoid shape; nothing impressive.</p><p>Except… I felt more connected to these unfortunate souls than I had to all of the “famous” people who’d been “immortalised” in bronze, marble, and paint. The people of Pompeii were going about their daily lives, unaware that their city was built on and prospered due to the volcanic soil from even earlier eruptions of Vesuvius. And as they must’ve uncomprehendingly witnessed the wrath of the Gods obliterating a mountain, they sought comfort in their last moments on the planet, curling up alone or huddling with others. Their names will never<em> </em>be found, and their identities, thoughts, memories, and desires are irrecoverably lost to time. But they were us, and we are them.</p><p>As I saw these ancient places and more surviving neglect, looting, bombing, natural calamities, or simply the ravages of time, and as I saw the descendants of those ancients still living their lives there today, I felt a certainty take root in me. This is probably a textbook example of hubris from Greek tragedies, but I’m sure that humanity will <em>never</em> perish — some form of it will always survive. Maybe in the stars after an unified, expansionist push by all countries — something that seems increasingly unlikely with every passing day. Or maybe in isolated communities deep underground after aeons of devolution and an apocalyptic nuclear war.</p><p>I thought about all the people who’d spent their lifetimes obsessed with legacy: Alexander the Great, Julius Caesar, Napoleon Bonaparte, Genghis Khan, and countless others who’ve achieved varying levels of success in having their names immortalised. But these are just names to us, most likely not even spelled or pronounced the right way. Their thoughts and actions have been filtered and modified through generations of propagandists and people wearing rose or red tinted glasses. A million people speak Alexander’s name today and he’s not alive to hear it. Tens of millions of people are said to be descendants of Genghis Khan and he’s not here to witness it. So why go through all of that effort just to have yourself turned into a figurehead or a caricature?</p><p>People living today struggle to accurately represent themselves to the ones that love them the most. Most people simply hide their deepest thoughts, fears, and worries. So why is it so important to have a legacy if <em>at best</em>, even when you’re a world-altering conqueror, only a pale imitation of you will be remembered?</p><p>I have a strong desire to leave the planet a slightly better place than when I came into it, but that’s just something that gives me direction. Why would I let my short lifespan be consumed with thoughts of what future generations might think of me? Why do I need to be remembered?</p><p>In front of the Castel Sant’Angelo in Rome, I listened to a violinist play the theme from the movie <em>Interstellar</em>. It was one of the most moving experiences of my life, but it was just another evening of busking for him. Whatever money I put down and my quiet, mispronounced “grazie” before his next song began was a paltry effort to convey how I felt. But I will carry that experience with me forever — even if I don’t know his name and to be quite honest, have forgotten what moved me so much in the first place. Although… maybe that’s the secret?</p><blockquote>Life isn’t about the details. It isn’t about past or the future.</blockquote><blockquote>It’s a series of “Right Now”s.</blockquote><p>I remember my mom had a cactus when I was younger. And as we eagerly waited, on the same day every year, a beautiful flower would bloom overnight and it would be gone the next day. And once or twice to our sadness, it was there and gone even when we weren’t around to witness its beauty.</p><blockquote><a href="https://youtu.be/SrSNQCa-C7A?feature=shared">A thing isn’t beautiful because it lasts.</a></blockquote><p>Maybe Legacy is a tool like Religion. A shared belief across time and cultures. A way to channel humanity’s best and worst instincts. To make us work tirelessly for the perceived good of future generations and the pre-determined answer to the question when we inevitably ask: “But what am <em>I </em>getting out of it?”</p><p>I don’t have the answers, nor I believe will anyone, <em>ever</em>. Because I think there is no question to be had.</p><p>Life doesn’t need a purpose. And Humans don’t need a legacy to live their lives to the fullest.</p><p>Be passionate about everything you do and this should make you happy in all of the “right now”s that lead to the day you die. If things work out, then maybe people will barely remember you two thousand years from now as they gaze in awe at your name on the side of an imposing marble monument. If they don’t, well… that’s no longer your problem, is it?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xuJsPltHpQ2FNTp5_SPMfw.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d588485c195a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[KubeRay vs Ray Clusters on Cloud VMs | Ray Chronicles]]></title>
            <link>https://vishnudeva.medium.com/kuberay-vs-ray-clusters-on-cloud-vms-ray-chronicles-9d58882cbe1b?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/9d58882cbe1b</guid>
            <category><![CDATA[distributed-computing]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[anyscale]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Wed, 23 Aug 2023 00:53:49 GMT</pubDate>
            <atom:updated>2023-11-10T02:26:49.375Z</atom:updated>
            <content:encoded><![CDATA[<p>This is an article comparing KubeRay and Ray on AWS/GCP Virtual Machines. Some knowledge of Ray and KubeRay is assumed.</p><p><strong>Please note</strong>: I work at a startup and maintain our automated ML training systems myself, so a lot of my decision making is informed by this. Between complex systems that have low buy-in but require a lot of upkeep, and complex systems that require a lot of engineering effort to set-up but require little upkeep, I always choose the latter. I doubt that approach is valid when larger teams and more engineering resources are involved, so many of the points we discuss here might not be relevant or an issue for you.</p><h3>TL;DR</h3><ul><li>I think Ray on AWS/GCP VMs is the way to go today if you want critical workloads to be run on a dependable system that can be maintained with minimal resources.</li><li>KubeRay is the way to go in the long-term, especially when paired with a powerful Kubernetes (K8s) cluster autoscaler like <a href="https://vishnudeva.medium.com/scaling-kubernetes-with-karpenter-1dc785e79010">Karpenter</a>. So explore KubeRay on the side and have an integration ready to go, because I think it’s a question of <em>when</em> to use it in production, not <em>if</em>.</li></ul><p>Let’s now look at different categories for KubeRay and for Ray clusters on cloud VMs. I’ve only used Ray clusters on EC2 instances, but as I understand it, GCP is also officially supported and the functionality is very similar. For brevity, whenever I mention VMs, I mean an EC2 instance or a GCE Virtual Machine.</p><h3>Cluster Configuration</h3><p>The cluster configuration is a YAML file that lets you configure the composition of your cluster and its autoscaling characteristics.</p><h4>KubeRay</h4><ul><li>You have the ability to specify the pod spec for the head and each of the worker pods. This gives you near-complete control of the pods, except for the command that specifies the primary Ray process that runs in the main container.</li><li>However, your Ray cluster goes only as far as your K8s cluster does. If you have a K8s cluster that doesn’t allow pod-level VM configuration, you’re going to have to resort to node groups along with node-selectors and tolerations in order to control the machines on which your Ray pods end up.<br> — Considering the best practice is to schedule a single Ray pod per machine, you’re looking at quite a few node groups if you have a complex Ray cluster configuration with many differing types of worker groups.<br> — This equation completely changes if you have a powerful K8s cluster autoscaler like <a href="https://vishnudeva.medium.com/scaling-kubernetes-with-karpenter-1dc785e79010">Karpenter</a> because it lets you specify the instance type and more VM configuration options right from the pod specification.</li></ul><h4>Ray on VMs</h4><ul><li>You can specify the entire cloud-specific VM configuration for the head and worker groups.</li><li>You can control every aspect of the infrastructure through this — security groups, subnets, instance market type, etc.</li></ul><h4>Conclusion</h4><p>You need Karpenter or an equally powerful K8s cluster autoscaler to truly unlock the power of KubeRay and make it flexible.</p><p>But even with that, you can never match the sheer simplicity and flexibility of using Ray on cloud VMs. Not to mention, as a rule, even if you optimize by <a href="https://vishnudeva.medium.com/you-dont-have-to-optimize-your-docker-images-just-bake-em-3bdd1c3ca433">baking Docker images into your AMIs</a>, cold starts for KubeRay will <em>always </em>be slower than for Ray on VMs.</p><h3>The Core Ray Process</h3><h4>KubeRay</h4><ul><li>[You] –kubectl exec→ [Pod [Ray Python Process]].</li><li>The Ray initialization command (ray start …) is used to start the pod, so you have no direct access to the command even if you exec into the pod’s container, because interrupting the process will kill the container.</li><li>You have access to rayStartParams in the YAML manifest of the RayCluster Custom Resource (CR) through which you can configure ray start&#39;s parameters.</li><li>According to <a href="https://ray-project.github.io/kuberay/guidance/pod-command/#timing-1-before-ray-start">this document</a>, you can also specify a command and args in the YAML manifest for each one of the head or worker group specifications. This is then prepended to the ray start command before the pod is started.<br> — Example: command args &amp;&amp; ulimit -n 65536; ray start …</li></ul><h4>Ray on VMs</h4><ul><li>[You] –SSH→ [Machine [Ray Python Process]]</li><li>Each of the following fields takes a list of strings (commands) and are executed in the order specified.</li><li>initialization_commands are the first to be run on each machine when it’s started.</li><li>setup_commands, head_setup_commands, and worker_setup_commands come next.<br> — The head_setup_commands are merged into setup_commands and run on the head node.<br> — The worker_setup_commands are merged into setup_commands and run on the worker nodes.</li><li>head_start_ray_commands and worker_start_ray_commands are the final step and are responsible for initializing Ray on the head node and worker nodes respectively.</li></ul><h4>Conclusion</h4><p>With KubeRay, there’s no interfering with the Ray process once it has been started, either the cluster works or it doesn’t, you can’t fix things in real-time if any issue develops in a cluster.</p><p>With Ray on VMs, you have unlimited access to the machine. In case something goes wrong with the Ray process, you can manually kill and start it yourself with the commands cleanly listed out in the cluster manifest. Not that you’d do it often — or if you’re lucky, at all — but the fact remains that you <em>can</em> “heal” a cluster or very easily debug issues since you have direct access to the ray code on the machine. Anecdotally, I’ve used this a few times to implement hot-fixes in obscure parts of the Ray code — and creating a new machine image with the change baked in — with the actual fixes sometimes months away in an official release.</p><h3>Autoscaling</h3><p>The Ray autoscaler is responsible for monitoring resource demands from the Global Control Service (GCS) and making the appropriate changes to the underlying node provider — KubeRay, AWS, GCP, etc.</p><p>While KubeRay is a GitHub project separate from the core Ray library, the autoscaler used for KubeRay resides in the core library, alongside the one used for AWS and GCP.</p><h4>KubeRay</h4><ul><li>Autoscaling has to be explicitly enabled with enableInTreeAutoscaling: true in the RayCluster CR. The config for the autoscaler container is then built <a href="https://github.com/ray-project/kuberay/blob/8be0a21dd1f0f4ce7298b54f4191e98a7ca9a558/ray-operator/controllers/ray/common/pod.go#L415-L469">like so</a> and injected into the head node’s pod <a href="https://github.com/ray-project/kuberay/blob/8be0a21dd1f0f4ce7298b54f4191e98a7ca9a558/ray-operator/controllers/ray/common/pod.go#L123-L124">like so</a>.</li><li>The command that’s used for the autoscaler pod runs <a href="https://github.com/ray-project/ray/blob/e78b0ef566a728f6c91caf84f1dd9192d30908f8/python/ray/autoscaler/_private/kuberay/run_autoscaler.py#L19-L20">this function</a>.</li><li>The autoscaler here doesn’t make direct autoscaling changes. Instead, it updates the RayCluster Custom Resource (CR), and those changes are picked up by KubeRay’s operator which then constructs the pods, deletes them, etc.</li><li>Any updates made to the RayCluster CR while the cluster is running are picked up in real-time by KubeRay’s operator. This lets you endlessly tweak and manage your cluster</li><li>As with the Ray process when working with KubeRay, there’s no option of interrupting the autoscaling process if needed.</li></ul><h4>Ray on VMs</h4><ul><li>Autoscaling is integrated into the head node’s Ray initialization. If you take a look at the head_ray_start_commands, you’ll notice a --autoscaling-config section that triggers the launch of the autoscaler.</li><li>Unlike with KubeRay, the autoscaler here does make direct changes to the underlying node provider. For example, with AWS, it launches and terminates EC2 instances and other infrastructure components as needed.</li><li>Each Ray cluster’s head node contains a ~/ray_bootstrap_config.yaml file that describes the cluster. Any change made to this YAML file is immediately picked up the autoscaler which makes the necessary changes to the cluster. Like with KubeRay, this lets you maintain fine-grained control over your cluster throughout its lifetime.</li><li>Since the autoscaler is launched as part of the Ray initialization, stopping ray also stops the autoscaler. This lets you directly modify the underlying code to debug issues if any pop up. You can also choose to start Ray without the autoscaler integrated and launch the autoscaler as a separate process.</li></ul><h4>Conclusion</h4><p>Autoscaling for Ray on VMs is straight-forward, has complete configurability on all levels, has a minimal number of moving parts, and can be thoroughly trusted in production with many years of a proven track record.</p><p>KubeRay is a long way from that today.</p><h3>Potential for Catastrophes and Complications</h3><h4>KubeRay</h4><ul><li>Multiple Ray clusters reside within a single Kubernetes (K8s) cluster.</li><li>Depending on your needs, you need a team dedicated to maintaining KubeRay, a powerful cluster autoscaler, RBAC policies and enforcement, network policies and enforcement, and much more to ensure that you get proper isolation for your Ray clusters. Both from the usage perspective and the underlying infrastructure perspective.</li><li>Even with all of that effort, a K8s cluster-wide issue will instantly knock out all your Ray clusters.</li></ul><h4>Ray on VMs</h4><ul><li>Each Ray cluster is completely isolated. Once the head node has been launched, there are no more centralized components to worry about.</li><li>The only thing that can take out multiple Ray clusters would be a region-wide or cloud account-wide issue, at which point you’ll have bigger issues than Ray to contend with.</li><li>Blast Radius for a cluster going down is exactly zero, since it’s trivial to enforce networking constraints at the VM level with security groups.</li></ul><h4>Conclusion</h4><p>You <em>can</em> get to a very high level of stability and isolation with KubeRay. Kubernetes is the farthest thing from an obscure technology and it will only become more mature and resilient as the years pass. But, today with KubeRay, the amount of effort it will take you to get to that safe, stable state of your system is astronomical in comparison to what you almost instantly get with Ray on VMs.</p><h3>Project Stability</h3><h4>KubeRay</h4><ul><li>The KubeRay autoscaler MVP was introduced in March 2022.</li><li>KubeRay entered beta in August 2022.</li><li>Autoscaler support is still listed as in beta as of August 2023.</li><li>KubeRay is at 0.6.0 as of August 2023.</li><li>A lot of development focus seems to be on KubeRay at the moment. And while to my knowledge it hasn’t been explicitly stated anywhere, looking at the activity around it, I get the strong sense that it will eventually become the officially recommended way to deploy Ray in any production scenario.</li></ul><h4>Ray on VMs</h4><ul><li>Has been stable and in use for at least the last 3–5 years.</li><li>Very strong anecdotal evidence of its stability.</li></ul><h3>Final Conclusion</h3><p>If you’re undecided, you’ll first need to answer these questions carefully. Keep a rough note of the number of “Yes”es.</p><ul><li>Do you need to define the characteristics of the underlying virtual machine at the Ray cluster’s head/worker node level?</li><li>Do you need infrastructure level isolation for your Ray Clusters? This means that each Ray cluster is in an isolated infrastructure pool with one cluster being unable to interfere with another’s operation.</li><li>Do you have strict security requirements?</li><li>Do you have strict RBAC requirements?</li><li>Do you have strict uptime requirements?</li><li>Do you lack access to cloud K8s clusters that you can easily experiment with?</li><li>Do you lack engineering resources that are highly skilled in K8s administration?<br> — More importantly, if you do have those resources, do they lack bandwidth to dive deep into any issues that crop up in the many K8s components that will be involved in productionizing KubeRay?</li><li>Do you have a short timeline to get to production?</li><li>Are your workloads critical?</li></ul><p><strong>More “Yes”es than “No”s</strong></p><ul><li>It will take a significant investment of resources to get production-ready on KubeRay. It will also have a high ongoing maintenance cost.</li><li>With Ray on VMs on the other hand:<br> — It should be much easier to productionize it.<br> — It’s easier and cheaper to find engineering resources for it because experienced AWS/GCP devs are more readily available than experienced K8s ones.<br> — You’ll have a much lower maintenance cost since there aren’t as many moving parts.<br> — And <em>trust</em> me on this, there will be no catastrophes thanks to the near-perfect isolation of each Ray cluster. Only rare “oopsies” that you can easily debug and fix.</li></ul><p><strong>More “No”s than “Yes”es</strong></p><ul><li>It should be very easy to get up and running with KubeRay. Definitely worth investing a bit of time to see if it can work for your needs.</li></ul><blockquote><em>Though I’ve chosen Ray on EC2 for the near future, I’m operating with the understanding that a shift to KubeRay is inevitable. It’s just a matter of </em><strong>when</strong><em>, not </em>if<em>.</em></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PbNxVSLi_bbrlrSuyOEDIA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d58882cbe1b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What are my words worth?]]></title>
            <link>https://vishnudeva.medium.com/what-are-my-words-worth-d2753d709f27?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2753d709f27</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[writing]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[generative-ai]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Sat, 18 Mar 2023 11:49:59 GMT</pubDate>
            <atom:updated>2023-03-18T11:51:18.941Z</atom:updated>
            <content:encoded><![CDATA[<p>When I first used ChatGPT, one of my first unprompted thoughts, quite selfishly, was to be thankful that I had a number of articles and documents written before any AI even remotely gained the ability to do it. I had a portfolio of writing before the advent of Large Language Models (LLMs) so I wouldn’t have to keep defending the provenance of my words forever.</p><p>Even before I could formulate it in words, I’ve had this vague, sinking feeling every time I interacted with an LLM over the last few months. This isn’t right, my brain told me, you’re not talking to a human. You don’t have to say please and thank you and be polite, it’s a program. But I couldn’t do it, it’s more engaging, patient, smarter, and kinder than many humans I’ve interacted with in forums online. And that’s when I realized something. Until some point in time last year, if we saw text anywhere, we implicitly knew a human must’ve written them, there was just no alternative. But from now and until the day the human race ends, there will always be the doubt: who’s doing the talking? Are these your thoughts? Or am I speaking to an AI?</p><p>It’s not yet a societal issue, since not all people currently see its potential or face its effects. If they do use it, they’re critical of the flaws in the system, proud that they can spot fakes. But I have to say, before November ’22 when ChatGPT was released, AI was just an amusement to most, the bumbling antics of Google Assistant, Siri, or Alexa compounding this. They were like the shiny robot puppies of the 2000s, wonderfully silly but nothing more. But now, everything has changed, AI is everywhere, offering to do the writing for you, clamouring to reduce your brain’s labours.</p><p>Even as I type this on Notion, I’m a single forward slash (/) away from accessing an AI that can write this entire article for me and make it more intelligent than I could ever hope to make it. It understands linguistics and esoteric philosophy from 2000 years ago, how can I compete?</p><p>And yes, I use the word “understands” consciously even though I know that’s not what it’s doing. Why do we suddenly care about the circumstances of the writer? We’ve been communicating through blocks of text with disembodied agents since the dawn of the internet and even that of writing itself, and we’ve certainly never cared about the humanity of the writer. The words were what have always indicated the intelligence, empathy, and a thousand other human characteristics of the writer and they continue to do so now. The fact that the words come from an AI is irrelevant if it can comfort someone going through a tough time or give financial advice to someone on the brink of losing their money. For the first time in history, we have an intelligent, all-knowledgeable agent that has no angle — no love, passion, hatred, greed, kindness, empathy, or any emotion influencing it. It’s just intelligent for the sake of being intelligent.</p><p>So… what are my words worth? I can feed an outline to an AI and get it to write this entire article to specifically cater to the reading ability of my target demographic. It could correct mistakes, fix my awkward phrasing at times, and so much more. And I can’t even make the argument that it would rob my writing of its tone. All I have to do is feed it my old works and it would precisely mimic my tone to a level beyond human perfection, with none being the wiser.</p><p>So I feel as if my words are no longer a strong indicator of my humanity or intelligence when I could fake it in seconds with AI.</p><p>The ideas I have, thankfully, are still valuable. But only for now. I already see that LLMs have the uncanny ability to put 2 and 2 together to come up with a million. They will, at some point, surpass a human’s ability to create planned chaos from order, and isn’t that what creativity is? The only thing we can do better is to test our hypotheses and draw on past experience to validate ideas. But when you give these models some mechanism to remember past experience and the agency to act in real-world systems… who can compete?</p><p>So… what are my words — these words — worth? What do they mean? What do they indicate? I have no clue. None.</p><p>But I do feel the need to write more, now more than ever, and ensure we don’t give up our voices and stay quiet now that there’s a smarter kid on the block.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2753d709f27" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Topic Modelling and Search with Top2Vec]]></title>
            <link>https://vishnudeva.medium.com/topic-modelling-and-search-with-top2vec-c19f8954ff36?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/c19f8954ff36</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[dataset]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Tue, 08 Nov 2022 07:24:14 GMT</pubDate>
            <atom:updated>2022-11-09T04:00:19.739Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/536/1*gALw5k1i84gTHWnG5jgjCw.png" /></figure><blockquote>An entry in a series of blogs written during the Vector Search Hackathon organized by the MLOps Community, <a href="https://vishnudeva.medium.com/redis-as-a-vector-database-rediscloud-2a444c478f3d">Redis</a>, and <a href="https://vishnudeva.medium.com/building-a-proto-mlops-platform-on-saturn-cloud-bd3eaa6df5b8">Saturn Cloud</a>.</blockquote><p><a href="https://ar5iv.labs.arxiv.org/html/2008.09470">The Top2Vec paper</a> explains the concepts behind the Top2Vec library in a more accessible way than I ever could. So instead of diving into the concepts, we’ll look at the practicalities of using Top2Vec and the data generated from it.</p><p>We have three major types of objects in Top2Vec: words, documents, and topics. All the unique, cleaned-up words in all the documents fed into the model make up the vocabulary. Topics refer to centroid vectors representing dense clusters of documents, the clusters having been determined using UMAP and HDBSCAN.</p><p>The following is my simplified understanding of the code in the top2vec library. This is what happens once Top2Vec(documents) gets called and the training begins.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5c4ef8d01ab7d2af6e3a90caf3021ef9/href">https://medium.com/media/5c4ef8d01ab7d2af6e3a90caf3021ef9/href</a></iframe><p>Looking at this code, we find that these objects have the most relevance:</p><ul><li>vocab: list of words</li><li>word_vectors: array of word vectors</li><li>word_indexes: dictionary of words to word indexes. Useful to get the word index given the word without iterating over the list of words.</li><li>documents: numpy array of all documents</li><li>document_vectors: array of document vectors</li><li>topic_vectors: array of topic vectors</li><li>topic_words: 2-D array of 50 words to describe every topic</li><li>topic_word_scores: 2-D array of 50 scores to show the dist of the 50 words for every topic in topic_words</li><li>doc_top: topic for each document</li><li>doc_dist: scores per topic for each document</li><li>topic_sizes: frequency of documents per topic</li></ul><p>We then come up with a data model to load this data into Redis:</p><h4>class Paper</h4><p>Dealt with by <a href="https://github.com/redis/redis-om-python">RedisOM</a>.</p><pre>class Paper(HashModel):<br>    paper_id: str = Field(index=True)<br>    title: str = Field(index=True, full_text_search=True)<br>    year: int = Field(index=True)<br>    authors: str<br>    categories: str<br>    abstract: str = Field(index=True, full_text_search=True)<br>    input: str</pre><p>In addition, each paper will have a topic index and a topic score assigned to it to indicate the topic the document is closest to.</p><p>The input field is a cleaned version of title + abstract.</p><h4>paper_vector:</h4><ul><li>paper_pk</li><li>paper_id</li><li>doc_idx</li><li>categories</li><li>year</li><li>vector</li></ul><h4>word_vector:</h4><ul><li>word_id</li><li>word</li><li>vector</li></ul><h4>topic_vector:</h4><ul><li>topic_id</li><li>size</li><li>vector</li><li>word_0 … word_15</li><li>word_score_0 … word_15</li></ul><p>A total of 33 fields per topic_vector.</p><h3>Indexes</h3><p>We create three different HNSW indexes for the vector key with Redisearch. The separation between the indexes is assured by the prefix we provide it; it will only look for hashes that begin with the prefix.</p><h4>papers</h4><p>prefix: paper_vector:</p><h4>words</h4><p>prefix: word_vector:</p><h4>topics</h4><p>prefix: topic_vector:</p><h3>Conclusion</h3><p>We’ve barely scratched the surface here. There is a ton of information contained within the Redis DB now, both in terms of metadata and vectors. Correlating them and exploring relationships should yield very interesting results. And the promise of this not being in a single computer is tantalizing. We’re no longer dealing with a Top2Vec model we have to port everywhere.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c19f8954ff36" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis as a Vector Database | RedisCloud]]></title>
            <link>https://vishnudeva.medium.com/redis-as-a-vector-database-rediscloud-2a444c478f3d?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a444c478f3d</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Tue, 08 Nov 2022 07:17:17 GMT</pubDate>
            <atom:updated>2022-11-08T07:17:17.804Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*w_NEtF7HD7DE_Qky" /></figure><blockquote>An entry in a series of blogs written during the Vector Search Hackathon organized by the MLOps Community, Redis, and Saturn Cloud.</blockquote><p>I’ve used Redis extensively before, but like most, I’ve only used it as a cache. So I was quite excited coming into the Vector Search Hackathon; it’s always fun when something you’re familiar with still holds secrets.</p><p>In this blog, we’ll look over different aspects of Redis and how we can use it as a vector database that’s capable of vector similarity search.</p><h3>Managed Redis</h3><p>As I noted <a href="https://vishnudeva.medium.com/building-a-proto-mlops-platform-on-saturn-cloud-bd3eaa6df5b8">here</a>, I’m a fan of self-hosted software and that includes deploying Redis wherever we’d needed it previously. But working with RedisCloud now, it’s hard to understate how much simpler using high-availability Redis has become. I don’t have to think about sharding, availability, uptime, throughput, etc. This is perfect for teams that are unable to devote resources to maintaining a large, self-hosted Redis deployment.</p><h3>Redis Modules</h3><p>Redis is a key-value store. But there are certain tasks you can do on top of that store, and instead of having the user implement those externally, Redis Modules bring a lot of common use-cases like search, time-series data, and graphs into the Redis DB directly. For the hackathon, we primarily used Redisearch, which offers basic and full-text search and vector-similarity search. The API is very mature and the Python SDKs are great to work with.</p><h3>Data Loading</h3><p>I shouldn’t be surprised, but I was blown away by how quick Redis is at loading a ton of vectors into the Database. It’s very reasonable to do this near real-time. So if you have a use-case where an application needs vectors.</p><h3>Redis Object Mapping Python</h3><p>This is probably one of my favourite tools that I got from this already great experience. Using Pydantic models, we’re able to create detailed data models that can be directly stored/queried in/from Redis without ever having to look at a single backend command like hget.</p><pre>class Paper(HashModel):<br>    paper_id: str = Field(index=True)<br>    title: str = Field(index=True, full_text_search=True)<br>    year: int = Field(index=True)<br>    authors: str<br>    categories: str<br>    abstract: str = Field(index=True, full_text_search=True)<br>    input: str</pre><p>Since we’re dealing with Pydantic, there are no holds barred on validation — perfect!</p><p>Here, you’ll notice that the paper_id, title, year, and abstract variables are of type Field. The title and abstract alone though have full_text_search set to True. Now whenever equality or range checks are done on paper_id or year, it’ll use an index to speed things up. And when you search for specific words in the title or abstract, Redisearch will use full-text search to deliver the papers containing those words to you.</p><pre>paper_instance = Paper(<br>    paper_id=&quot;0704.3780&quot;,<br>    title=&quot;Stochastic Optimization Algorithms&quot;,<br>    year=&quot;2006&quot;,<br>    categories=&quot;cs.NE&quot;,<br>    abstract=&quot;---&quot;,<br>    input=&quot;---&quot;,<br>)</pre><p>This will now create an instance of the Paper class on your machine, but it still hasn’t been sent over to Redis. When you now do the following, it will create a hash in Redis and save this data.</p><pre>paper_instance.save()</pre><h4>Querying</h4><p>When querying, you don’t have to worry about the hashes, you can just reuse the same model you used to create the hash: Paper.</p><p>A few examples:</p><pre># Return all papers after 2004<br>Paper.find(Paper.year &gt; 2004).all()</pre><pre># Return the first paper that contains the word &quot;generative&quot;<br># in their abstract.<br>Paper.find(Paper.abstract % &quot;generative&quot;).first()</pre><p>This makes code so much more readable. This is a great fit for situations where you want the rigidity of a strongly typed DB with advanced validation features, but at the same time don’t want the overhead that comes with using a proper RDB.</p><h3>Vectors</h3><p>Vectors in the DB have to be bytes. So if you’re dealing with Numpy Arrays, you’ll have to convert them to bytes form first before storing them.</p><p>An interesting problem is that the datatype choice is up to you, so if you choose np.float32 to store a vector to the DB and retrieve it using np.fromBuffer with the data type as np.float64, you’re going to end up with a vector that’s half the size being returned. So use the same data type on both ends, when the vector enters and exits the DB.</p><pre>np.array(vec, dtype=np.float32).tobytes()</pre><h3>Vector Indexes</h3><p>There are hashes and there are keys beneath those hashes. We store vectors as values under a specific key. So when indexing the vectors, we need a way to group vectors of a kind together. A practical example is what we did with Top2Vec. We have three different kinds of vectors: word, document, and topic. And needed three indexes, one for each kind of vector. So we kept the key names for all three types the same, it was still “vector”. But when creating the index, we can pass a prefix option that will let us filter out a specific set of hashes whose “vector” fields will be indexed. So in our case, we had three functional indexes: “papers”, “words”, and “topics” and the hashes for each type began thus: paper_vector:, word_vector:, topic_vector.</p><pre>redis_conn.ft(index_name).create_index(<br>    fields=fields,<br>    definition=IndexDefinition(<br>        prefix=[prefix],<br>        index_type=IndexType.HASH<br>    ),<br>)</pre><p>For a detailed explanation of vector similarity in redis, visit this <a href="https://redis.io/docs/stack/search/reference/vectors/">document</a>.</p><h3>Vector Search</h3><p>The vector search queries are rather straightforward:</p><p>You define:</p><ul><li>The number of results you want to be returned.</li><li>The field which contains the vector.</li><li>The bytes representation of the vector for which you want to find the k-nearest neighbours.</li></ul><p>With the Python client for Redis, this is made even simpler, especially when using code from Tyler Hutcherson’s <a href="https://github.com/RedisVentures/redis-arXiv-search/blob/main/backend/vecsim_app/search_index.py">library</a>.</p><pre>query = search_index.vector_query(number_of_results=50)</pre><pre>redis_conn.ft(&quot;papers&quot;).search(<br>    query,<br>    query_params={<br>        &quot;vec_param&quot;: np.array(vec, dtype=np.float32).tobytes()<br>    },<br>)</pre><p>Something to note is that the redis-py library doesn’t seem to be directly communicating with the API. Rather, it’s just constructing CLI commands through a Pythonic interface.</p><p>I see a lot of doors opening up for my team with vector capabilities from Redis. Just having it as a live vector store is more than valuable enough. We’re really looking forward to working further with Redisearch and try out more efficient ways of storing vectors and retrieving them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a444c478f3d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a proto-MLOps Platform on Saturn Cloud]]></title>
            <link>https://vishnudeva.medium.com/building-a-proto-mlops-platform-on-saturn-cloud-bd3eaa6df5b8?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/bd3eaa6df5b8</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[mlops]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Tue, 08 Nov 2022 07:16:17 GMT</pubDate>
            <atom:updated>2022-11-09T04:09:14.480Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/810/0*Wlc2sAyz6rnsVTQj.jpg" /></figure><blockquote>An entry in a series of blogs written during the Vector Search Hackathon organized by the MLOps Community, <a href="https://medium.com/@vishnudeva/redis-as-a-vector-database-rediscloud-2a444c478f3d">Redis</a>, and Saturn Cloud.</blockquote><p>I’m a <em>die-hard</em> (note the italics) fan of deploying and self-hosting open-source software for production. But that said, I’ve found myself enjoying the freedom that Saturn Cloud provided. Starting our project only three days ago, when my teammate, <a href="https://medium.com/u/73c1f6a968d5">Chirag Bajaj</a>, and I had to double down and focus on the ML and Redis exploration, I was ecstatic to not have to think about infrastructure. Things just worked out of the box; a unique experience for someone like me, since I usually have to assemble the parts before getting started with anything. I can see why teams comprising solely of ML engineers would gravitate here. Zero thought gets put into infrastructure, but their work gets done on time.</p><p>Our approach in this hackathon was two-pronged, one on the MLOps side and one on the ML side.</p><h3>MLOps</h3><p>Machine Learning or Data Science tend to plateau after a point without solid tools backing them. There’s only so far you can go with manually naming your models, storing your scripts on disk, and using log files to understand how each training run progressed. So we made the conscious decision to devote our attention primarily to building a rudimentary MLOps platform. Both in the hackathon and in the real world, this causes ML work to crawl along at a snail’s pace when the platform is being set up; the uncertainty is excruciating and it feels like nothing gets done. But once everything is available and running, the pace of research accelerates to levels that would never be possible without the platform.</p><h3>ML</h3><p>Our goal here was to extract relationships between papers in the arXiv dataset and explore the natural clusters that formed in the data using topic modelling. To this end, we used Top2Vec, an excellent Python library which implements the ideas explored in this <a href="https://arxiv.org/abs/2008.09470">paper</a> by Dimo Angelov.</p><p>Let’s take a quick look at the mechanisms that Saturn Cloud offers to enable what we’re trying to do.</p><h3>Saturn Cloud</h3><p>There are three concepts relevant to us:</p><h4>Jupyter Servers</h4><p>Used as easy development environments backed by powerful machines. Accessible through the Jupyter UI on your browser or through SSH. The SSH connection also lets you connect to this server from the familiar comfort of your IDE, which is always a wonderful experience.</p><h4>Deployments</h4><p>These are like servers but use images, startup scripts and commands to automate the process of deploying an application. So once the data scientist finishes developing something with a Jupyter Server, it should be trivial for them to clone their environment and launch it as a deployment that will be permanently accessible.</p><h4>Jobs</h4><p>Similar to deployments, but these are meant to be ephemeral, dying once their task is complete. These are perfect for orchestrating jobs that have to be triggered on a periodic schedule or on a specific signal.</p><h4>Notes</h4><ul><li>Saturn Cloud, the community edition, exposes only one port: 8000. This is the case on your deployments, and even on your Jupyter servers should you wish to use it. This port is automatically mapped to a website that looks like this:</li></ul><pre><a href="https://castle-grafana.community.saturnenterprise.io/explore?orgId=1&amp;left=%7B%22datasource%22:%22AKalLOvVz%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22AKalLOvVz%22%7D%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D">https://&lt;subdomain you choose&gt;.community.saturnenterprise.io</a></pre><ul><li>To protect the platform from the execution of malicious code, external images are disabled on the community version of Saturn Cloud Hosted. But since all sensible OSS have a simplified installation process, this doesn’t really affect us as you’ll see below. We can just install whatever we need during runtime or build a custom image with their image builder tool.</li></ul><p>We go into some detail on the components of our platform below.</p><h3>Model Training</h3><p>We needed a fair bit of compute to train our Top2Vec model. It had to chew through a couple of million papers, and the compute requirements were made worse by the fact that we weren’t going to be generating embeddings from a pre-trained model, but rather training a Doc2Vec model from scratch. And to add to this, Top2Vec doesn’t support GPU acceleration.</p><p>So we used the 4XLarge machine, with its 16 CPU cores and 128 GB of memory. Thankfully the library was able to fire at a 100% on all 16 cores and made it through the entire dataset in ~7 hours.</p><p>The auto-shutoff feature helps you save costs if you leave a server running without having it open. But if you do have a lengthy training running on it, make sure to connect to it via SSH from a stable server so the SSH connection prevents the auto-shutoff from being triggered.</p><p>As noted earlier, the interesting part is that even though we developed an ad-hoc script and trained with a notebook on a Jupyter server, Saturn Cloud allows you to clone your environment and recreate that as a job or a deployment. Combine this with the feature that lets you automatically pull code from a GitHub repository and a simple Docker image builder to pre-package your dependencies, you now have a platform in Saturn Cloud that can take any bit of code that a data scientist tests and plays around with and translates that into a periodic job with little effort. This is an inspired workflow.</p><h3>Object Storage</h3><p>Since we have a system that’s spread out across multiple components, having our files on disk doesn’t quite cut it for us. We deployed MinIO to act as an S3-compatible object store. I don’t think I’ll ever get over how well the S3 API has unified the landscape for object storage. It was a breeze to use MinIO with boto3.</p><h4>Installation and startup</h4><pre>wget <a href="https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20221029062133.0.0_amd64.deb">https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20221029062133.0.0_amd64.deb</a> -O minio.deb<br>sudo dpkg -i minio.deb</pre><pre>mkdir -p ~/minio</pre><pre># Startup command for the deployment.<br>minio server ~/minio --address :8000</pre><h4>Interfacing with MinIO from boto3</h4><p>The only difference from using this with S3 is that we specify an endpoint URL as follows.</p><pre>s3_resource = boto3.resource(<br>    &quot;s3&quot;,<br>    endpoint_url=&quot;https://xyz-minio.community.saturnenterprise.io&quot;,<br>    config=boto3.session.Config(signature_version=&quot;s3v4&quot;),<br>)</pre><h3>Model Tracking</h3><blockquote><em>Behind every great model is a model registry rolling its eyes.</em></blockquote><p>Mistakes are a core part of the data science journey, but mistakes only generate value if they are tracked. A model registry lets you do this and more.</p><p>We deployed MLFlow on Saturn Cloud to track our training runs. Though Top2Vec doesn’t quite generate metrics during training that we can track, we plan to create a metric from Topic Information Gain as described in equation 5 of the Top2Vec <a href="https://arxiv.org/pdf/2008.09470.pdf">paper</a>. It’s also useful to get an idea of the number of documents, words, and topics after each model is trained; we log these as params to MLFlow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f2KsaWUFyY2fxrGjPYmK_Q.png" /></figure><h4>Installation and startup</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J5qPkdsRAr2Hgh3eFbGlSQ.png" /></figure><pre># Startup command for the deployment.<br>mlflow ui --host 0.0.0.0 --port 8000 --backend-store-uri sqlite:///mlflow.db --default-artifact-root ./artifacts</pre><h4>Interfacing with MLFlow from Python</h4><p>It’s very straightforward.</p><ul><li>Set the Tracking URI.</li></ul><p>mlflow.set_tracking_uri(os.environ[“MLFLOW_TRACKING_URI”])</p><ul><li>When your training/task begins, start your run.</li></ul><p>mlflow.start_run(run_name=run_name)</p><ul><li>To log parameters that feed into the task or describe the task at a high level, use log_param or log_params. In the screenshot above, num_topics, num_words, etc are parameters.</li></ul><p>mlflow.log_param(key=”num_workers”, value=workers)</p><ul><li>When your training/task ends, end your run.</li></ul><p>mlflow.end_run()</p><h3>Logging</h3><p>Distributed anything is a dream for scalability, but it’s also a nightmare for logs. Ideally, you’d want a centralized logging interface so that you can make sense of everything. Log files sound good, and do the job, but are practically hard to work with when you want to get a sense of your system as a whole.</p><p>I swear by Loki for anything to do with logging, because its ability to process large volumes of logs and extract useful information and metrics from them is nothing short of magical.</p><h4>Installation and startup</h4><pre>curl -O -L &quot;<a href="https://github.com/grafana/loki/releases/download/v2.6.1/loki-linux-amd64.zip">https://github.com/grafana/loki/releases/download/v2.6.1/loki-linux-amd64.zip</a>&quot;<br>sudo apt update; sudo apt install unzip -y<br>unzip &quot;loki-linux-amd64.zip&quot;<br>chmod a+x &quot;loki-linux-amd64&quot;</pre><pre>wget <a href="https://castle-minio.community.saturnenterprise.io/topvecsim/platform/loki/loki_config.yaml">https://xyz-minio.community.saturnenterprise.io/topvecsim/platform/loki/loki_config.yaml</a></pre><pre># Startup Command for the deployment.<br>./loki-linux-amd64 --config.file loki_config.yaml</pre><p>In the script above, you’ll note a link to xyz-minio. We store our Loki configuration file in our MinIO object store and download it whenever the Loki deployment starts up. Loki listens by default on port 3100, so we use the config file to redirect it to port 8000; specifically, we set http_listen_port: 8000.</p><h4>Interfacing with Loki from Python</h4><p>Loki has a simple API that’s easy to use, but we can use a library called python-logging-loki which makes the process easier by providing a logging handler that we can consume for our logger.</p><p>Here’s a sample:</p><pre>loki_handler = logging_loki.LokiHandler(<br>    url=<a href="https://castle-loki.community.saturnenterprise.io/loki/api/v1/push">https://xyz-loki.community.saturnenterprise.io/loki/api/v1/push</a>,<br>    version=&quot;1&quot;,<br>)</pre><pre>logger = logging.getLogger(&quot;topvecsim&quot;)<br>logger.addHandler(loki_handler)<br>logger.setLevel(logging.INFO)</pre><pre>logger.info(&quot;Starting model training.&quot;)</pre><p>The library also provides a Queue Handler which is well-suited for high log-volume situations.</p><h3>Monitoring</h3><p>Grafana needs no introduction as an observability interface. It pairs perfectly with Loki and lets you manipulate logs into any number of intuitive visualizations.</p><h4>Installation and startup</h4><pre>sudo apt-get update<br>sudo apt-get install -y adduser libfontconfig1<br>wget <a href="https://dl.grafana.com/oss/release/grafana_9.2.3_amd64.deb">https://dl.grafana.com/oss/release/grafana_9.2.3_amd64.deb</a><br>sudo dpkg -i grafana_9.2.3_amd64.deb</pre><pre># Change port 3000 to 8000.<br>sudo sed -i &#39;s/3000/8000/g&#39; /etc/grafana/grafana.ini<br>sudo sed -i &#39;s/3000/8000/g&#39; /usr/share/grafana/conf/defaults.ini</pre><pre># Startup Command for the deployment.<br>sudo /usr/sbin/grafana-server  --homepath=&#39;/usr/share/grafana&#39; --config=&#39;/usr/share/grafana/conf/defaults.ini&#39;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ijA5keezUbFEiGVLap6ocw.png" /></figure><h3><strong>What was missed out/in-progress for the Hackathon?</strong></h3><h4><strong>Jaeger</strong></h4><p>Distributed tracing to help you track the performance of your APIs and even your Python code that’s deployed in different places.</p><h4>Loki Metrics on Grafana Dashboards</h4><p>Sending logs to Loki as a JSON encoded string lets it deconstruct it and extract values from it. Doing this will let us plot any metrics from our application on Grafana through LogQL queries.</p><p>There are certain low-effort tasks you can take up that will make your life infinitely better. This is especially true when it comes to something as inherently chaotic and disorganized as ML/DS research and exploration. Invest in simple tools like the above for exponential workflow improvement.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd3eaa6df5b8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[You Don’t Have to Optimize Your Docker Images; Just Bake ‘em]]></title>
            <link>https://vishnudeva.medium.com/you-dont-have-to-optimize-your-docker-images-just-bake-em-3bdd1c3ca433?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/3bdd1c3ca433</guid>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[hashicorp]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Sun, 16 Oct 2022 06:52:53 GMT</pubDate>
            <atom:updated>2022-10-16T06:52:53.915Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/649/1*OJk6gbze3iFIOKQcuMW-wQ.jpeg" /></figure><p>Container Images have become the de facto mechanism for packaging dependencies and code. A few years ago, when the task of building these Images was under the purview of operations specialists, there existed the luxury of optimization — with beautiful multi-stage builds, clean-up after every step, etc. But today, any developer working on a modern stack gets to build their own images and pass them along to a container orchestrator like Kubernetes or Docker Swarm to deploy their application.</p><p>This decoupling between operations and application development has been a boon. A tiny team of Ops engineers — whatever you call them: Ops, DevOps, Platform, Infra — can serve a wide range of products at virtually any scale. At no point does an operations specialist need to get involved in this process of Image building once a simple build pipeline has been set up.</p><p>On the flip side, this decoupling now means that the Images built by app developers are barely optimized, contain a lot of unused packages and dead weight, and little thought is given to breaking an application into multiple pieces. Which is… completely fair. What we lose with the efficiency of these Images, we gain exponentially with the increase in developer velocity. There’s nothing worse than having Ops be a bottleneck to development. So, I’ll gladly take a few extra Gigabytes any day.</p><p>But, that said, here’s one of the biggest problems I face as a platform engineer: <strong>Large Startup Times</strong>. It’s pretty much the villain in my book, the silent killer of efficiency, and has implications up and down the stack.</p><p>In the case of Kubernetes with AWS EKS, here’s what happens when a new pod is launched but no node currently exists to serve it.</p><ol><li>A new EC2 instance is launched.</li><li>The instance joins the cluster and becomes a K8s ‘node’.</li><li>The Docker image necessary for the pod to run is pulled to the node.</li><li>The container is launched and the application is started on it.</li></ol><p>Each step takes time, but there’s nuance to this:</p><ul><li>Steps 1 and 2 are inevitable, and only EKS and K8s devs can contribute to any improvements here.</li><li>Step 4 is dependent on the app developer. If they use a potato language like Python (I say this with love) with a lot of module imports, the app launch is going to be quite drawn out.</li></ul><p>So, steps 1, 2, and 4 for a particular application take a fixed amount of time that’s out of the hands of an Ops engineer. But step 3, the Image pull time, is interesting, because it introduces the biggest opportunity for guaranteed improvement in this process.</p><p>Let’s say a particular task takes <strong>four minutes</strong> to run, and the 4 GB Image needed for the task takes <strong>two minutes </strong>to be pulled. This means that <strong>33%</strong> of the computing time is taken up by the Image pull step, which is significant. And this only gets worse when the Image gets even larger and autoscaling gets aggressive. You could end up spending thousands of minutes of computing time a day on nothing but Image pulls.</p><p>How does this affect the larger picture? Say the app team wants a task to start in 20 seconds flat from a cold start, this is pretty much impossible unless you always have a pool of warmed up nodes that have the Container Images pulled and waiting for new tasks to be scheduled. This “headroom” is incredibly wasteful, and since many workloads are rather hard to predict, you’re paying round the clock for compute capacity that is <em>guaranteed</em> never to be used. (Disclaimer: I refuse to take responsibility if your finance team comes across this.)</p><h3>The Solution</h3><p>Every major cloud has the concept of a machine image from which a virtual machine can be launched. If the Container Image is ‘baked’ into a Machine Image, then when the machine is booted, the Container Image is already present and ready to go, thus completely eliminating the Container Image pull time.</p><p>The following steps assume the use of AWS EKS since it’s the most popular way to deploy K8s today, but the underlying concepts are incredibly straightforward and can be used for any cloud/platform.</p><h3>Overview</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rahKALEqYXBz-gHr73luUg.png" /></figure><h4>Steps</h4><ol><li>Trigger Container Image build.</li><li>Store Image in a remote Container Image Registry.</li><li>Trigger Machine Image build with Packer.</li><li>Packer starts an EC2 instance from an EKS AMI.</li><li>It then runs a script on the instance that pulls the newly built Container Image from ECR.</li><li>Once done, it stops the EC2 instance and triggers the creation of an AMI from the stopped instance.</li><li>Once the AMI is created, Packer cleans up everything else.</li><li>We now make use of this AMI in our Kubernetes cluster manager — Karpenter, Cluster Autoscaler, Spotinst Ocean, etc.</li></ol><h3>Packer</h3><p>Packer is an automation tool that lets you build, among a lot of other things, EC2 AMIs, which are AWS’ version of Machine Images. We’ll be using it to bake our Container Images into EKS AMIs so that we can use these new AMIs to launch the EC2 instances that will back the nodes of our K8s cluster.</p><p>Packer uses its own Domain Specific Language, but it’s well thought out and thus easy to grasp. Glance through the following script, and we’ll break it down in the section that follows it. I do have to note at this point that this is a bare-bones introduction to Packer to serve our current needs. For a comprehensive overview, I suggest following the <a href="https://learn.hashicorp.com/collections/packer/aws-get-started">official tutorials</a>, they’re structured in an easy-to-consume manner.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/18ef5681dcb42fb4c5a2d497c118f34d/href">https://medium.com/media/18ef5681dcb42fb4c5a2d497c118f34d/href</a></iframe><p>This is executed using the following command:</p><pre>packer build \<br>--var IMAGE=xyz.dkr.ecr.us-east-1.amazonaws.com/app:potato-0.2 \<br>--var AMI_NAME=baked-potato-ami-0.2 \<br>--var SOURCE_AMI=amazon-eks-gpu-node-1.23-* \<br>prepull.pkr.hcl</pre><h4>Breakdown</h4><p>Our goal is to:</p><ol><li>Launch an instance from a base EKS AMI. This is the default AMI that is used when new nodes are launched.</li><li>Execute the nerdctl-script.sh file on the launched instance.</li><li>Create a new AMI.</li></ol><p>Let’s forget about the nerdctl-script.sh file for now, because its contents have no bearing on the packer build process, we only care that it modifies the instance in some beneficial way.</p><p>In the packer build command above, you’ll notice that we specify three var arguments. These are variables that configure the specific build. Each var is a key=value pair, and the corresponding keys will be defined as variable “key” in the pkr.hcl script.</p><p>We’ve used this mechanism to configure:</p><ul><li>IMAGE: This is the Container Image that will be baked into the AMI.</li><li>AMI_NAME: The name of the AMI that will be created.</li><li>SOURCE_AMI: The base AMI on top of which the customization will take place.</li></ul><p>The source “amazon-ebs” “eks-ami” section captures information about the EC2 instance that will be launched, including the instance type, the volume size, IAM role attached to it, and the source AMI that’s used. You’ll spot the use of SOURCE_AMI through ${var.SOURCE_AMI} in the source_ami_filter section here. More details <a href="https://www.packer.io/plugins/builders/amazon">here</a> and <a href="https://www.packer.io/plugins/builders/amazon/ebs">here</a>.</p><p>The build section makes use of the source section and adds the shell provisioner on top of it. As the name suggests, the shell provisioner does nothing but execute a shell command or script on the source. In this case, the source is an EC2 instance and nerdctl-script.sh is the script that gets executed on it.</p><h4>The Script</h4><p>Kubernetes is an incredible container orchestrator, but for all its magic, it still requires a container runtime to run the Images it’s given. Up to K8s version 1.22, at least on EKS, Docker was the default runtime, but starting with version 1.23, containerd is used instead. For our purposes, this just means that for versions 1.22 and lower, we use Docker to pull the images from the remote Image registry. For version 1.23+, we use nerdctl, which is a Docker compatible CLI for containerd.</p><p>The “Docker compatible” part is pretty important for us because it lets us trivially authenticate nerdctl with AWS ECR to retrieve our Images in the same way we’d do it with Docker.</p><p>The following script, meant for K8s nodes with containerd instead of Docker, <a href="https://github.com/containerd/nerdctl/releases/">installs</a> and uses nerdctl to pull Images to the machine. The comments in the script provide an explanation for each step.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/755c9e131d0107076f0fdfde98a4f59c/href">https://medium.com/media/755c9e131d0107076f0fdfde98a4f59c/href</a></iframe><p>The next script is meant for K8s nodes using Docker. Quite straightforward as opposed to the above.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/827953e3804b64074f41202d186d74ea/href">https://medium.com/media/827953e3804b64074f41202d186d74ea/href</a></iframe><p>As discussed earlier, the shell provisioner in the packer script just mentions a script that should be executed on the machine. So your folder structure is now the following, and you’d run the packer build command above from the packer-build folder.</p><pre>packer-build/<br>  prepull.pkr.hcl<br>  nerdctl-script.sh</pre><h3>Karpenter</h3><p>Once you have your new AMI from the previous steps, you just need to update your cluster manager. In this case, it’s Karpenter, which is an incredible K8s native cluster manager that currently only supports EKS. If you haven’t come across it, I urge you to read more about it <a href="https://vishnudeva.medium.com/scaling-kubernetes-with-karpenter-1dc785e79010">here</a>; it’s a true game-changer in my opinion.</p><p>Karpenter has the concept of a provisioner which is used to configure the instances it launches. There can be multiple provisioners, each with different characteristics for different purposes. For example, I have a provisioner each for CPU, GPU, and High Availability workloads. We can also link an AWSNodeTemplate to each provisioner to precisely configure the EC2 instances that are launched. Here’s a detailed example of a provisioner and the AWSNodeTemplate it uses, just for reference.</p><p>TL;DR: There is an aws-ids field in the AWSNodeTemplate that can be used to select the AMI. There are also <a href="https://bit.ly/3s1pXCE">other ways</a> of specifying the AMI.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aaee559d2a36b25414649eb81a72e0dd/href">https://medium.com/media/aaee559d2a36b25414649eb81a72e0dd/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/94a21d995ba3f0ecd1482c1a50a28643/href">https://medium.com/media/94a21d995ba3f0ecd1482c1a50a28643/href</a></iframe><p>With Karpenter + AMI baking, I’ve seen cold starts of 40–50 seconds even with 4–5 GB Docker Images. (This feels like a poor man’s version of discussing quarter mile times in drag races.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6jh3e-1oQAcrNBZm3HpXJg.jpeg" /></figure><h3>Conclusion</h3><p>Optimizing large Container Images is a long-term goal that should be addressed through thorough training sessions, code reviews, and more. In the meantime, Ops teams can’t ignore the nightmare of torturous Image pull times and the effect it has on their platforms. So as much as I hate the bodging that this article promotes, we really do need it until a solution comes along to fix this issue for good (maybe it’s <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/image-streaming">image streaming</a>?). I can only hope it’s sooner rather than later.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3bdd1c3ca433" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kubernetes Command Line Tooling | Shorts]]></title>
            <link>https://vishnudeva.medium.com/kubernetes-command-line-tooling-dd8a2b600af3?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/dd8a2b600af3</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[cli]]></category>
            <category><![CDATA[tools]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[kubectl]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Wed, 31 Aug 2022 10:43:55 GMT</pubDate>
            <atom:updated>2022-08-31T10:45:35.338Z</atom:updated>
            <content:encoded><![CDATA[<p>The Kubernetes API has a wide range of options, and it gets tiresome to work with through kubectl on the command line for extended periods of time. And regardless of how much effort you put into aliases and making your custom tooling look good, it’ll never be as good as tools that were expressly developed for this specific purpose.</p><p>So this article goes over some necessary tools for your Terminal to make life easier when working with Kubernetes.</p><h3>Kube PS1</h3><p><a href="https://github.com/jonmosco/kube-ps1">kube-ps1</a></p><p>Displays your current Kubernetes cluster and namespace information in the terminal prompt. It’s a script that can be installed with the <a href="https://brew.sh/">Brew</a> package manager if you already have it:</p><pre>$ brew update<br>$ brew install kube-ps1</pre><p>If you don’t have brew, don’t bother with it, just <a href="https://github.com/jonmosco/kube-ps1#from-source">install kube-ps1 from source</a>, it’s nothing but a script that needs to be sourced.</p><p>When you use managed clusters, your cluster names tend to be unwieldy, something like arn:aws:eks:us-east-1:&lt;aws-account-id:cluster/&lt;cluster-name&gt;. So you can shorten it with a function before sourcing the kube-ps1 script. For example, here’s what my .bashrc contains:</p><pre># kube-ps1<br>function get_cluster_short() {<br>  echo &quot;$1&quot; | cut -d : -f6 | cut -d / -f2<br>}</pre><pre>KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short<br>. $HOME/.kube-ps1/kube-ps1.sh</pre><pre>PS1=&#39;$(kube_ps1) ${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\[\033[00m\]\[\033[01;34m\]\W\[\033[00m\]\$ &#39;</pre><p>This gives me a concise cluster name, namespace, and local folder name. For brevity, my PS1 displays only the current directory’s name.</p><pre>(⎈|&lt;cluster-name&gt;:kube-system) directory$</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ke4fRJlHEABXKYV7.gif" /><figcaption>From <a href="https://github.com/jonmosco/kube-ps1">https://github.com/jonmosco/kube-ps1</a></figcaption></figure><h4>Note</h4><p>PS1 is the primary prompt variable on Linux. It’s what defines the contents of the prompt you see whenever you open the terminal.</p><h3>Aliases for kubectl</h3><p><a href="https://github.com/ahmetb/kubectl-aliases">kubectl-aliases</a></p><p>This project contains aliases for more than 1200 useful kubectl commands in a consistent manner. The consistency guarantee is key because it makes it easy to guess commands without referring to the aliases or memorizing them. For example, kubectl get pods is kgpo</p><p>and kubectl get ingress -o yaml is kgingoyaml</p><p>Thus, kubectl get pods -o yaml is kgpooyaml</p><p>I was expecting that it’d take me a month to get used to it, but I became quite comfortable with it in just a day.</p><h4>Installation</h4><p>A <a href="https://github.com/ahmetb/kubectl-aliases#bashzsh">straightforward process</a> since it just needs the ~/.kubectl_aliases file to be sourced during shell start.</p><h3>Kubectl Tree</h3><p><a href="https://github.com/ahmetb/kubectl-tree">kubectl-tree</a></p><p>There are a lot of interrelated resources in Kubernetes and kubectl doesn’t do the relationships justice when you look at your resources category by category. Using ownerReferences, kubectl-tree shows you the relevant resources in one view. Here’s an example, when we take a look at the ‘karpenter’ deployment with: kubectl tree deploy karpenter -n karpenter</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/879/1*3Hg14dpd2-i4QbrESUMdFQ.png" /></figure><p>A minor convenience, but a welcome one.</p><h4>Installation</h4><p>Uses the <a href="https://krew.sigs.k8s.io/">krew</a> kubectl plugin manager. Once you have that, installation is just: kubectl krew install tree</p><h3>Kubectx and Kubens</h3><p><a href="https://github.com/ahmetb/kubectx">Link</a></p><p>Context management is a pain. Namespace management is a pain. These tools take care of that in a visually appealing and efficient manner.</p><p>Instead of going through kubectl config get-contexts and then use kubectl config use-context &lt;context&gt;. We just do kubectx &lt;context&gt;.</p><p>Similarly, just using kubens &lt;namespace&gt; will also work.</p><p>But the main value-add here is not the alias, but rather the autocompletion and fuzzy finder for both kubectx and kubens. This makes dealing with multiple clusters and endless namespaces <em>so</em> much easier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oTS8UO31zCsAwITO.gif" /><figcaption>GIF from <a href="https://github.com/ahmetb/kubectx#interactive-mode">https://github.com/ahmetb/kubectx#interactive-mode</a></figcaption></figure><h4>Installation</h4><p>Both can be installed with the ‘<a href="https://krew.sigs.k8s.io/">krew</a>’ kubectl plugin manager.</p><pre>kubectl krew install ctx<br>kubectl krew install ns</pre><p>If you want the interactive mode shown in the gif above, you only need to <a href="https://github.com/junegunn/fzf#installation">install fzf</a> in addition to the two plugins above.</p><h3>K9s Terminal UI</h3><p><a href="https://k9scli.io/">K9s</a></p><p>I was previously a heavy user of the desktop UI <a href="https://k8slens.dev/">Lens</a>, but more recently, I’ve found that I much prefer K9s for its snappiness and keyboard shortcuts. You do miss the IDE feel that Lens gives you, but overall, I think K9s is the best Kubernetes graphical interface out there. And the minimal resource usage is the cherry on top.</p><p>It excels at giving you a bird’s eye view of your cluster from the command line. It also lets you edit resources, view logs, attach to a pod, etc. I’m surprised by how intuitive it was; where I expected a learning curve, I found none.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xDQTY0EYwAwtwcHP.png" /><figcaption>From <a href="https://k9scli.io/">https://k9scli.io/</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*DgLtpl3MZM7B-hQ5.png" /><figcaption>If nothing else, you need to use K9s just to show appreciation for this logo</figcaption></figure><h3>Managing multiple Kubectl Versions</h3><p>I’ve covered this in this <a href="https://vishnudeva.medium.com/managing-multiple-kubectl-versions-shorts-4973e0e2d24">article</a>. I highly recommend this if you routinely deal with multiple Kubernetes clusters running different K8s versions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd8a2b600af3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[10x Your Kubernetes Autoscaling Experience with Karpenter]]></title>
            <link>https://vishnudeva.medium.com/scaling-kubernetes-with-karpenter-1dc785e79010?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/1dc785e79010</guid>
            <category><![CDATA[autoscaling]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[aws-eks]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Mon, 22 Aug 2022 18:33:01 GMT</pubDate>
            <atom:updated>2022-08-22T19:34:55.566Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve been working with Kubernetes for a few years and it’s always jarring to experience the absolute disconnect between the cluster and its underlying infrastructure. I’ve used two tools so far for cluster management, the <a href="https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler">Cluster Autoscaler</a> and <a href="https://spot.io/products/ocean/">Spotinst Ocean</a>, and found both to be severely lacking.</p><p>Even a task as simple as providing a dedicated set of nodes for a group of users requires creating new node groups with custom labels and taints. It greatly limited the potential of Kubernetes and caused nightmares for replication because we’d have to capture application requirements partially in K8s YAML files and partly in the node group configurations. Not to mention, <a href="https://aws.github.io/aws-eks-best-practices/cluster-autoscaling/#reducing-the-number-of-node-groups">having too many node groups is an anti-pattern</a>, so we couldn’t even commit to that completely.</p><p>I’d reached a state of acceptance that this was as far as we’d ever get with cluster management and that I’d just have to learn to live with it. But out of seemingly nowhere…</p><blockquote><strong>Enter Karpenter. The casual overachiever.</strong></blockquote><p>Can I request infrastructure resources directly from the pod spec? <strong>Uh-huh.</strong></p><p>Can I use Kubernetes <em>YAML files </em>to define my infrastructure? Files that I could then deploy and manage with ArgoCD? <strong>Sure why not?</strong></p><p>*Thinking of something wildly unrealistic* What about isolated, dynamic node groups that need nothing more than a node selector or node affinity specified in the workload definition? <strong>Pfft, trivial.</strong></p><p>Honestly, I have whiplash from how Karpenter just upended everything I thought I knew about cluster autoscaling; there’s just no going back from such a K8s native infrastructure interface now.</p><p>The rest of the article goes over some high-level features of Karpenter followed by a short discussion of the concepts underlying Karpenter and some questions.</p><h3>Features</h3><h4>Rapid node launch times</h4><p>I’ve consistently seen unschedulable pods start execution in as little as 40 seconds. This speed is attributed to Karpenter determining instance types and provisioning them from the EC2 fleet API within a couple of seconds of the pod entering the unschedulable state.</p><p>Because of this, I’m already rethinking how I view autoscaling. What previously used to be a task of warming up node groups to handle workload spikes is now just a passing thought, “Eh, I’ve got Karpenter, it’ll be up in half a minute.”</p><h4>Complete Control of Instance Provisioning</h4><p>Using the provisioner spec or the pod spec, you can control everything about your EC2 instances from your K8s cluster. No more fiddling with infrastructure interfaces like the AWS console/CLI or Spotinst Ocean to provision node groups.</p><h4>Cost Management</h4><p>You can use Spot instances with the guarantee of fallback to On-Demand. Karpenter uses the EC2 Fleet API to ensure long-lived spot instances.</p><h4>Kubernetes Native</h4><p>Karpenter does a great job of being K8s native, and the more I think about it, the more I see that this was a really good design choice. Because they’ve taken three core components of pod specifications: resource requests, node-selectors, and node-affinity. And they’ve transformed it into an interface for your infrastructure by requiring you to do nothing but match specific labels. For example, specifying karpenter.k8s.aws/instance-family and c5 lets you limit your workloads to EC2 instances from the c5 instance family. Something that would&#39;ve previously taken a node group and a cluster administrator to achieve.</p><h4>Zero Third-Party Cost</h4><p>Directly managing node groups on AWS is of course free. But as soon as you start using external tools like Spotinst Ocean, you run into charges. For example, Ocean reportedly charges you ~20% of the costs you save by using Spot instances [<a href="https://renovacloud.com/spotinst-adds-automation-to-simplify-and-optimize-kubernetes-clusters/?lang=en#:~:text=The%20Professional%20plan%20charges%20on%20a%20pay%2Das%2Dyou%2Dgo%20basis%20by%20collecting%2020%25%20of%20the%20costs%20saved%20versus%20running%20the%20customer%20environment%20at%20on%2Ddemand%20prices">1</a>, <a href="https://www.saasworthy.com/product/spotinst">2</a>, <a href="https://spot.io/blog/abeja-using-ecs-elastigroup/#:~:text=20%25%20of%20the%20amount%20saved%20compared%20to%20On%2DDemand%20costs">3</a>]. This is a not-so-insignificant amount when you’re talking about running hundreds of Spot instances; and what exactly are you paying for? Not the instances themselves, you still have to pay AWS for that. Are you paying all that money just for them provisioning your Spot instances on your AWS account? Is it really worth that?</p><p>With Karpenter, you only use AWS native services like the EC2 Fleet API, so you pay for nada but your EC2 usage.</p><h4>Deprovisioning and Consolidation</h4><p>Karpenter can save costs by terminating empty and underutilized nodes.</p><h3>Concepts</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7S_5hDXEDQ7ioujZrtXryw.jpeg" /></figure><h4>Karpenter Controller</h4><p>This component looks for unschedulable pods and launches (provisions) EC2 instances (nodes) to schedule these pods.</p><p>When a node is unused for a pre-configured period of time, the controller also deletes (deprovisions) the corresponding nodes.</p><p>The controller also takes care of consolidation; when nodes are underutilized, it will move workloads around and deprovision nodes to more efficiently bin-pack your cluster.</p><h4>Layered Constraints</h4><p>AWS has hundreds of instance types and dozens of instance families. Instead of forcing either the developer or the cluster administrator to take a call on the instance types and other attributes of the instances before the workload is even deployed, Karpenter allows both parties to specify constraints in different places.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/664/1*mcJsBVpbcbS13ee_HPK9jw.jpeg" /></figure><p><strong>An example:</strong></p><p>The cluster administrator wants to keep the costs low and prevent instances with more than 50 GB of RAM from ever being provisioned for the cluster. So they specify this constraint in the ‘provisioner’.</p><p>The application developer finds this constraint too permissive for their application. Too many instance types have less than 50 GB of RAM, and the dev wants their pods to run only on a node that has between 15 and 25 GB of RAM, is network-optimized, and has a directly mounted SSD volume. Previously, they would have to put out a request to the cluster administrator, who would then provision a node group with a particular node label and taint, following which the app developer would be able to use it. Any follow-ups or modifications would have to be through the cluster admin.</p><p>With Karpenter, however, they can define this constraint directly in the pod specification of their workload using resource requests and node-selectors/node-affinity. Because the cluster admin has already enforced their constraints on the provisioner, they’re not involved in any way with this process. I’ve said this before, but this is game-changing! The infrastructure configuration resides inside your application YAML definition. And most importantly, you don’t have to bother your overworked infrastructure team for every little change, which means your development enters hyperspeed!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/0*3XOizr3GA4JqWkJv.gif" /></figure><h4>Constraints</h4><p>Constraints are the key/value pairs specified in the provisioner or the pod spec that can control:</p><ul><li>Instance type — memory, CPU, architecture, and accelerators.</li><li>Availability zone in which the instance is launched. (Topology)</li><li>Where pods are deployed in relation to other pods — pod affinity/anti-affinity.</li></ul><h4>Provisioner (K8s Custom Resource)</h4><p>Used at the cluster level, usually by the cluster administrator, to impose high-level constraints. The provisioner has a requirements field that allows the same constraints that can be specified in the pod spec, so instance types, topology, etc can be managed here. When pods then go into the unschedulable state, Karpenter tries to find a matching provisioner that can launch instances which will satisfy the constraints of the pod.</p><p>We can also specify the behaviour of the cloud provider, AWS for example. So things like the IAM InstanceProfile, AMI, User data, EC2 tags, etc can be configured here.</p><h4>Provider</h4><p>Even though Karpenter only supports AWS at present, it’s built to be cloud provider agnostic. So anything specific to a cloud is relegated to the provider definition. For AWS, this is the ‘AWSNodeTemplate’ Custom Resource, which can be referred to from the provisioner.</p><h4>How does Karpenter schedule pods?</h4><p>When there’s an unschedulable pod, Karpenter looks at the pod spec and determines the instance to be launched based on the resource requests and node-selectors/node-affinity.</p><p>It looks for a provisioner that is able to satisfy those constraints. When there are multiple provisioners eligible, Karpenter chooses one at random, so it’s advisable to make your provisioners mutually exclusive.</p><p>Twenty instance types are selected following this <a href="https://karpenter.sh/v0.15.0/faq/#how-does-karpenter-dynamically-select-instance-types">process</a>, and a request is made to the EC2 fleet API to provision the necessary number of instances.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*XGZdZejGY6C7M-4AerspKA.jpeg" /></figure><h4>How are cost savings possible?</h4><p>Karpenter allows you to use Spot instances with the option to fall back to On- Demand. So you get both low costs and guaranteed scheduling. If your provisioner spec or pod spec doesn’t limit the pool of possible instance types/zone by too much, you will get a spot instance 100% of the time. There are hundreds of EC2 instance types and multiple zones in every AWS region, which makes it practically impossible for the Spot market to ever completely “run out”.</p><h4>How does Karpenter interact with the Spot Market?</h4><p>It doesn’t! It interacts with the EC2 fleet API specifying the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet-allocation-strategy.html#ec2-fleet-allocation-use-cases:~:text=Spot%20capacity%20pools.-,capacity%2Doptimized,-The%20Spot%20Instances">capacity-optimized-prioritized</a> allocation strategy. This tells EC2 to provide spot instances that have the least likelihood of being interrupted so that the Spot instances on your cluster are not constantly being reclaimed by AWS. By letting the Fleet API do what it’s good at, Karpenter is saving itself from an impossible task — one of monitoring and keeping up with the Spot market in real-time.</p><h4>How stable is Karpenter?</h4><p>It’s more than stable enough for me to use it without worry in a volatile environment with regular and rapid autoscaling. But setting that aside, since I haven’t used it for very long, you can take the following points to the bank:</p><ol><li>Community engagement is high.</li><li>It has well-recognized adopters using it in production.</li><li>Development is highly active and any necessary features are either already available or in design, development or testing.</li><li>AWS seems to be all in on it! In all honesty, this is pretty much the only assurance I needed. This project is here to stay.</li></ol><h3>Conclusion</h3><p>Considering the issues that plague Kubernetes Cluster Scaling today, I see Karpenter’s revolutionary features gaining it immense traction over the course of the next year. As more people begin to use it, we will also start seeing its adoption by other cloud providers. If that doesn’t happen for some reason, they will at the very least adopt a similar model of bringing in infrastructure configuration into the cluster which can only be great news for anyone that uses managed Kubernetes (which <a href="https://www.datadoghq.com/container-report/#1">seems to be</a> the majority).</p><p>So a few years down the line, we’ll be looking at cluster scaling that will be baked into and native to every managed Kubernetes cluster.</p><p>Or so goes my dream.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dc785e79010" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing Multiple Kubectl Versions | Shorts]]></title>
            <link>https://vishnudeva.medium.com/managing-multiple-kubectl-versions-shorts-4973e0e2d24?source=rss-cdb1ccfa62ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/4973e0e2d24</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cloud-native]]></category>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[kubectl]]></category>
            <dc:creator><![CDATA[Vishnu Deva]]></dc:creator>
            <pubDate>Sat, 06 Aug 2022 16:34:02 GMT</pubDate>
            <atom:updated>2022-08-06T16:35:46.962Z</atom:updated>
            <content:encoded><![CDATA[<p>Kubernetes versions move fast, and we might find ourselves managing clusters that are 4 or 5 minor versions apart. And since upgrading K8s is not a trivial task, we’ll always be playing catch-up with some clusters.</p><p>Right now, for example, I have to use a 1.18 cluster and a 1.22 cluster simultaneously, which is painful because the version of kubectl you use to access a cluster can only be a <a href="https://kubernetes.io/releases/version-skew-policy/#kubectl">maximum of one minor version away</a>: skew ≤ 1. But in my case, the skew = 4, which means that I need a different kubectl binary for each cluster.</p><p>Managing these binaries directly is out of the question, it’s just not a clean solution. Instead, we use the ‘asdf’ tool version manager.</p><ul><li>Install asdf.</li><li>Install the asdf kubectl plugin.</li><li>Before accessing a specific cluster, we request asdf for the correct kubectl version to be enabled.</li><li>That’s it!</li></ul><h3>Steps</h3><h4>Core Installation</h4><p>Taken from <a href="https://asdf-vm.com/guide/getting-started.html#_2-download-asdf">here</a>.</p><pre># Clone the asdf Github Repository to ~/.asdf<br>git clone <a href="https://github.com/asdf-vm/asdf.git">https://github.com/asdf-vm/asdf.git</a> ~/.asdf --branch v0.10.2</pre><pre># Add the following lines to ~/.bashrc if on Linux.<br># Or to ~/.bash_profile if on MacOS.</pre><pre>. $HOME/.asdf/asdf.sh<br>. $HOME/.asdf/completions/asdf.bash</pre><h4>Kubectl Plugin Installation</h4><p>Taken from <a href="https://github.com/asdf-community/asdf-kubectl">here</a>.</p><pre># Install the kubectl plugin.<br>asdf plugin-add kubectl</pre><pre># Install specific versions of kubectl as needed.<br>asdf install kubectl 1.18.20<br>asdf install kubectl 1.22.10</pre><h4>Use asdf</h4><pre># To enable 1.18 access, run:<br>asdf global kubectl 1.18.20</pre><pre># To enable 1.22 access, run:<br>asdf global kubectl 1.22.10</pre><p>In cases where a single machine is shared by multiple people, use local instead of global to use multiple versions of kubectl simultaneously.</p><pre>asdf local kubectl 1.18.20<br>asdf local kubectl 1.22.10</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4973e0e2d24" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>