<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>Max Brenner</title>
 <link href="https://shipit.dev/atom.xml" rel="self"/>
 <link href="https://shipit.dev/"/>
 <updated>2025-12-16T23:11:25+00:00</updated>
 <id>https://shipit.dev</id>
 <author>
   <name>Max Brenner</name>
 </author>
 
 
   
   <entry>
     <title>My first steps in indie hacking</title>
     <link href="https://shipit.dev/posts/indie-hacking-first-steps.html"/>
     <updated>2024-03-04T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/indie-hacking-first-steps</id>
     <content type="html">&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/steps.jpg&quot; alt=&quot;Photo by Jake Hills&quot; height=&quot;300px&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Photo by Jake Hills
    
    / &lt;a href=&quot;https://unsplash.com/photos/person-wearing-green-pants-bt-Sc22W-BE&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;image source&lt;/a&gt;
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hey, thanks for stopping by. After over two years of silence on this blog I finally felt the urge to write about something that is worth sharing again. A lot has happened since my last post but that’s something for another day.&lt;/p&gt;

&lt;p&gt;Today we are talking indie hacking, an endeavor? hobby? lifestyle? 🤷 that I started moving into at the end of last year. In case you are unfamiliar with the term indie hacking, here’s my definition:&lt;/p&gt;

&lt;p&gt;person + computer = product (ideally with users and which turns profits)&lt;/p&gt;

&lt;p&gt;No external money, no employees, just a single person building and selling stuff online.&lt;/p&gt;

&lt;p&gt;The last two months to me felt a bit like back then in 2020 when I decided to go freelance full-time. A lot of uncertainty and a whole lot of new stuff to learn. Exciting times, but let’s start from the beginning.&lt;/p&gt;

&lt;h2 id=&quot;my-background&quot;&gt;My Background&lt;/h2&gt;

&lt;p&gt;So here’s my background in a few bullet points to set the baseline for my journey.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;got a degree in computer science&lt;/li&gt;
  &lt;li&gt;started working as a software developer (Java back then 🙊)&lt;/li&gt;
  &lt;li&gt;transitioned over to SysAdmin / DevOps, managing a local data center&lt;/li&gt;
  &lt;li&gt;switched jobs to gain experience in cloud infrastructure&lt;/li&gt;
  &lt;li&gt;stepped into DevOps freelancing full-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the while I was working on various open source projects, which allowed me get to &lt;del&gt;a decent&lt;/del&gt; some level in programming. Technologies that I was already familiar with included:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Python: main programming language for years&lt;/li&gt;
  &lt;li&gt;HTML, CSS, JS: I know the basics, no pro in any of them (can you be pro at HTML? 🤔)&lt;/li&gt;
  &lt;li&gt;Django: a few projects over the years, but nothing that made it into “production”&lt;/li&gt;
  &lt;li&gt;Angular, React, Vue: one project each, just to understand what they’re about&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-indie-hacking&quot;&gt;Why indie hacking?&lt;/h2&gt;

&lt;p&gt;Actually I really asked myself this question for the first time while writing this post and it wasn’t that easy to come up with an answer. I’m still not 100% sure but here are my thoughts.&lt;/p&gt;

&lt;h4 id=&quot;learning-new-stuff&quot;&gt;Learning new stuff&lt;/h4&gt;

&lt;p&gt;This one is probably the biggest factor for me.&lt;/p&gt;

&lt;p&gt;If you do something almost every day for a few years, you usually get pretty good at it. And that’s where I’m currently at when it comes to setting up, maintaining and improving cloud infrastructure. Don’t get me wrong, I’m definitely not saying that I am along the best DevOps guys out there. But for 90% of the topics the average client approaches me with, a solution is already in my head after a short time. Reason being that the challenges companies have, usually are all very similar.&lt;/p&gt;

&lt;p&gt;Long story short, I want to dive into different topics and indie hacking allows me to do just that. Wearing the marketing, sales, technical, … hat forces me to jump into areas I have never been in and that’s what I’m looking for.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/dalle-indie-hacker.webp&quot; alt=&quot;DALL-E&apos;s understanding of an indie hacker wearing multiple hats 😆&quot; height=&quot;400px&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    DALL-E&apos;s understanding of an indie hacker wearing multiple hats 😆
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;back-to-simplicity&quot;&gt;Back to simplicity&lt;/h4&gt;

&lt;p&gt;Holy f, cloud infrastructure got (was made) complicated. From single VMs that you SSH into, to hundreds of cloud resources that get created almost instantly using some Terraform module just to run an app on 0.5 vCPU cores and 1GB of for 100 dollars a month (hello AWS Fargate 😉).&lt;/p&gt;

&lt;p&gt;Obviously, there’s a place for that kind of stuff and I still love to work on it in my freelance projects. But for my “personal” projects I’d like to take a step back and embrace simplicity again. Simple technologies, simple processes, just get things done when they need to in the shortest time possible.&lt;/p&gt;

&lt;p&gt;This also touches the topic of perfectionism. In my professional work it’s often not about finding just any solution but the best. And that’s also what I expect from myself. Not just delivering anything, but the best possible thing.&lt;/p&gt;

&lt;p&gt;This may be fine, but if you want to build a product, alone, from scratch, without spending months before launching it and finding out nobody cares about it, you have to focus on what matters and be fine with an 80% solution (for the moment).&lt;/p&gt;

&lt;h4 id=&quot;building-for-users&quot;&gt;Building for users&lt;/h4&gt;

&lt;p&gt;In my freelance projects, especially when not being part of the actual product development, you get fairly disconnected from the people that you are actually building for. Which makes me feel a bit sad.&lt;/p&gt;

&lt;p&gt;If you’ve been shipping some kind of software you probably remember the first time you’ve got feedback from a random stranger on something you’ve built. Heck, even negative comments spark emotions as it’s always exciting to hear what other people think about your creation.&lt;/p&gt;

&lt;p&gt;I hope that with my indie projects I’ll encounter these kinds of situations more often and eventually find something that helps other people in whatever they are trying to achieve.&lt;/p&gt;

&lt;h4 id=&quot;decoupling-income-from-time&quot;&gt;Decoupling income from time&lt;/h4&gt;

&lt;p&gt;This one is pretty obvious and I’m probably romanticizing it too much. As a freelancer I know pretty well what it means to sell your time for money. I see these numbers every month on the invoices that I send out.&lt;/p&gt;

&lt;p&gt;So generating some income without actively spending time working, sounds like something desirable. Who knows what I’m going to do with the freed up time. 🤷&lt;/p&gt;

&lt;h2 id=&quot;getting-things-started&quot;&gt;Getting things started&lt;/h2&gt;

&lt;p&gt;Alright, that was a pretty long intro but I feel like it was necessary.&lt;/p&gt;

&lt;p&gt;So what was my first milestone? In the beginning of January I set myself the goal to get something shipped by the end of the month. It didn’t have to be pretty, it didn’t have to get much attention/users, it just had to be functional and providing some value (at least in my eyes). Here’s what I came up with.&lt;/p&gt;

&lt;h3 id=&quot;blocked-by-cors&quot;&gt;Blocked by CORS&lt;/h3&gt;

&lt;p&gt;If you’ve been building web applications, you’ve probably come across CORS issues. If you haven’t, just know, they are annoying, pretty hard to debug and a lot of developers encounter them each and every day (literally, just check Reddit 😄).&lt;/p&gt;

&lt;p&gt;Recognizing this, I set out to build a “universal solution” for all kinds of CORS issues, providing a knowledge base but more importantly, tools to help developers figure out what’s going on.&lt;/p&gt;

&lt;p&gt;As it was my first project I didn’t expect anything from it. It was just about getting something out there and that’s what I did. After just a little more than 7 days of work I started sharing &lt;a href=&quot;https://blockedbycors.dev/&quot; target=&quot;_blank&quot;&gt;blockedbycors.dev&lt;/a&gt; publicly.&lt;/p&gt;

&lt;p&gt;It only included one central feature, which is still its core as of today, a CORS debugging tool. Not getting into much detail here, but it tells you whether a request from you web app would pass a server’s CORS policy.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/blockedbycors-result.png&quot; alt=&quot;The result a user gets from the CORS debugger&quot; height=&quot;200px&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    The result a user gets from the CORS debugger
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To save time, I simply used tools I already knew pretty well. Django as a fullstack framework, no fancy JS library, just a bit of HTMX for interactivity and TailwindCSS + DaisyUI to make things don’t look too bad.&lt;/p&gt;

&lt;p&gt;A few days later I also added a configuration generator, as properly configuring CORS is not an easy task. In general, during the building process and while learning the ins and outs of CORS, so many ideas popped into my mind that one could build. But I had to resist. There’s another half (three-quarter? nine-tenth?), which is finding users for your app.&lt;/p&gt;

&lt;p&gt;So I split up my marketing approach (it sounds so wrong to call it like that 😆) into two parts:&lt;/p&gt;

&lt;h4 id=&quot;short-term-strategy&quot;&gt;Short term strategy&lt;/h4&gt;

&lt;p&gt;To get some short term validation and finding out whether it’s even worth pursuing the current path I just reached out to people having CORS issues and sharing my tool with them. Finding these people was pretty easy as they are numerous 😆. Enter “blocked by CORS” into Google, get the results from the last 24 hours, done.&lt;/p&gt;

&lt;p&gt;This way I approached two to five people every day for at least a week. The feedback was pretty good and I was able to help almost all of them to fix their issue. Very nice! Plus, the places I shared my tool (StackOverflow, various forums) still bring in some traffic up until today.&lt;/p&gt;

&lt;p&gt;This approach is perfect to get some (almost instant) feedback but obviously it doesn’t scale well. Nobody wants to spend their time doing this for weeks or even months.&lt;/p&gt;

&lt;p&gt;Additionally it’s the nature of problem solving tools to become useless once the problem is solved. Once the people get their CORS issue sorted, they move on and may only come back when they encounter the next one. But CORS issues are not like dirty dishes. It’s not something you have to deal with every week. At least I hope so.&lt;/p&gt;

&lt;p&gt;What I also did was to bundle all the accumulated knowledge about CORS into an easily digestible format. The outcome is an &lt;a href=&quot;https://blockedbycors.dev/cheatsheet/&quot; target=&quot;_blank&quot;&gt;interactive mindmap&lt;/a&gt; that was a nice piece to share and people really liked it. Traffic spikes from Reddit or Hacker News are motivating but they won’t bring too much results long term.&lt;/p&gt;

&lt;p&gt;So we need a way to bring in some regular traffic.&lt;/p&gt;

&lt;h4 id=&quot;long-term-strategy&quot;&gt;Long term strategy&lt;/h4&gt;

&lt;p&gt;The area of CORS issues felt like a typical case for organic traffic to me. People copy paste their CORS error into Google, they find an article on blockedbycors.dev, they use the tool to find the solution to their CORS issue, user is happy.&lt;/p&gt;

&lt;p&gt;So I started setting up a blog with Hugo (great tool!) and wrote some articles about various CORS topics. Now comes the big question, how do I get people to find my blog?&lt;/p&gt;

&lt;p&gt;The standard answer to this question are the magical three letters S E O (search engine optimization). You remember me saying that I wanted to dive into new topics? Here we go, I’ve never had anything to do with SEO. It’s basically the language to talk to search engines and whisper them in their ears: “Hey, Google/Bing/DuckDuckGo. When someones searches for X, send them to my page!”&lt;/p&gt;

&lt;p&gt;Here’s the simplified concept:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;create content that contains keywords you want to rank for, e.g. in my example: CORS issue, blocked by CORS&lt;/li&gt;
  &lt;li&gt;get other pages to link to your page with a dofollow link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This increases your domain rating, which in turn puts your page higher up in the search result.&lt;/p&gt;

&lt;p&gt;For me, this was probably the hardest thing to do during the last two months. It feels like a game that should not be played. Plus the whole process has a feedback loop time that a developer cannot cope with. Be happy if you can see any results within two months. I’ll keep you posted. 😅&lt;/p&gt;

&lt;h2 id=&quot;any-results&quot;&gt;Any Results?&lt;/h2&gt;

&lt;p&gt;OK, so what has been the outcome of all this work?&lt;/p&gt;

&lt;p&gt;Let’s start with some traffic numbers. For the static blog that contains the articles and references to the tools, Cloudflare Analytics reported 1200 page visits since the launch on January 17th. As usual, people that are using ad blockers or privacy protection are not included in these numbers.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/blockedbycors-blog-traffic.png&quot; alt=&quot;Top traffic sources on blockedbycors.dev of the last 30 days, numbers aren&apos;t accurate, Cloudflare Analytics just rounds everything to hundreds&quot; height=&quot;300px&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Top traffic sources on blockedbycors.dev of the last 30 days, numbers aren&apos;t accurate, Cloudflare Analytics just rounds everything to hundreds
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I also added some server-side request tracking to the Django application that is providing the tools and this reports 2400 visits since launch.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/blockedbycors-backend-traffic.png&quot; alt=&quot;Traffic on app.blockedbycors.dev of the last 30 days&quot; height=&quot;300px&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Traffic on app.blockedbycors.dev of the last 30 days
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So all together we are looking at roughly 3500 visits in total, which is a nice bit of traffic for a page that only exists for a few weeks.&lt;/p&gt;

&lt;p&gt;I’m even more happy to see that people are regularly using the CORS debugger to, hopefully, fix their CORS issues. Currently there are 5 to 10 different sites (and some of them are pretty well known 😆) analyzed everyday using the tool.&lt;/p&gt;

&lt;p&gt;To capture some direct user feedback I also added a small form, but I didn’t receive any submissions yet. Means that everything is working, right? 😅&lt;/p&gt;

&lt;p&gt;During the whole process I also started sharing regular updates about anything I do on Twitter and built a bit of a following by doing so. Currently I use it as a bit of a public journal which also really helped me when writing this blog post to kind of remind me of the whole process I went through.&lt;/p&gt;

&lt;h2 id=&quot;was-it-worth-it&quot;&gt;Was it worth it?&lt;/h2&gt;

&lt;p&gt;Well, it’s difficult to give a definitive answer here. I’m very happy that I was able to fulfil my goal of shipping an app from scratch within a very short time frame. I learned a whole lot during the process and could keep myself going even when tackling topics that I did not enjoy but were necessary.&lt;/p&gt;

&lt;p&gt;On the other side, all of this required me to spent substantial amounts of time. For roughly three weeks I probably spent 20-30 hours per week on top of my day job, tinkering on my indie project. This left little to no time on doing other things that I enjoy. It was very exciting to go “all-in” on it but I realized that if I want to do this long-term I had to take a different approach. So what’s up next?&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;

&lt;p&gt;With the accumulated knowledge I’m now feeling more confident than ever on continuing my indie hacker journey. To tell you the truth, I already did 😆. The initial idea of this blog post was to cover my first &lt;strong&gt;two&lt;/strong&gt; indie projects, but after writing about the first one, I realized that it’s just too much for one post. So yeah, I already worked on and &lt;strong&gt;even published&lt;/strong&gt; my second project.&lt;/p&gt;

&lt;p&gt;I’ll talk about it and what I did differently in the next blog post. In case you are curious, I already shared parts of the story in small pieces &lt;a href=&quot;https://twitter.com/__brennerm&quot; target=&quot;_blank&quot;&gt;on Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the way, if you are in a similar situation as me or have something to share or talk about, feel free to contact me. Difficult things get easier when you share them. Hope to talk to you soon. Cheers ✌️&lt;/p&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Sharing a Kubernetes cluster between multiple tenants</title>
     <link href="https://shipit.dev/posts/kubernetes-multi-tenancy.html"/>
     <updated>2022-01-11T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/kubernetes-multi-tenancy</id>
     <content type="html">&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/multi-tenancy.png&quot; alt=&quot;&quot; height=&quot;&quot; width=&quot;300&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Running and maintaining Kubernetes clusters is hard. They need to be upgraded, automatically scale based on demand, be monitored and most importantly, ensured to be highly available at all time.&lt;/p&gt;

&lt;p&gt;So the idea of sharing clusters across teams, divisions or even whole companies is definitely valid. There are various ways of achieving multi-tenancy which we will be exploring today.&lt;/p&gt;

&lt;h2 id=&quot;multiple-clusters&quot;&gt;Multiple clusters&lt;/h2&gt;

&lt;p&gt;This approach is probably not the one you were looking for when deciding to read this article but I don’t want it to go unnoticed. Of course it has some obvious downsides, like having to set up and maintain multiple Kubernetes control planes. On the other hand it limits the amount of damage an application can do to the scope of a single cluster keeping the remaining ones safe.&lt;/p&gt;

&lt;p&gt;But for sure, there are good ways to safely enable multi tenancy on a single Kubernetes cluster.&lt;/p&gt;

&lt;h2 id=&quot;namespaces&quot;&gt;Namespaces&lt;/h2&gt;

&lt;p&gt;Namespaces are a concept you are most likely already aware of. They are shipped with Kubernetes out of the box and a default installation already has few of them set up for you. Initially these namespaces are nothing more than a logical separation but in combination a few other components they introduce multi tenancy capabilities. Those are RBAC and resource quotas.&lt;/p&gt;

&lt;p&gt;RBAC stands for role-based access control and allows you to configure fine grained permissions to certain namespaces or global resources. It may be used to realize the following use case cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;create one admin user that is able to manage all namespaces&lt;/li&gt;
  &lt;li&gt;create several users with different levels of access to a single namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resource quotas on the other hand allow you to define limits for the memory and CPU usage of all pods within a single namespace. This can be achieved by creating a &lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/#create-a-resourcequota&quot; target=&quot;_blank&quot;&gt;ResourceQuota object&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ResourceQuota&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;example-resource-quota&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hard&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;requests.cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;requests.memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2Gi&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;limits.cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;4&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;limits.memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;4Gi&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Be aware that creating a ResourceQuota objects requires you to define CPU and memory requests and limits for each pod within this namespace. In case you want to know more about this topic check out my blog post about &lt;a href=&quot;/posts/wasting-money-with-kubernetes.html&quot; target=&quot;_blank&quot;&gt;how to waste money using Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The below namespace structure may be the result when deploying a frontend and backend application into three different environments.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get namespaces
NAME               STATUS   AGE
kube-system        Active   1d
default            Active   1d
frontend-dev       Active   1d
frontend-staging   Active   1d
frontend-prod      Active   1d
backend-dev        Active   1d
backend-staging    Active   1d
backend-prod       Active   1d
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using RBAC we can then define a cluster admin that is able to assign the appropriate resource quota to each namespace and create further roles that provide the right level of access to other users.&lt;/p&gt;

&lt;p&gt;What becomes clear is that this approach doesn’t scale very well. Each newly created namespace needs its own set of role bindings and resource quotas.&lt;/p&gt;

&lt;p&gt;Additionally, depending on your policies, creating these objects may require involvement of cluster admin users. So e.g. the backend team wouldn’t be able to independently create new namespaces with resources assigned to them when necessary.&lt;/p&gt;

&lt;p&gt;So how can we fix on these shortcomings?&lt;/p&gt;

&lt;h2 id=&quot;hierarchical-namespaces&quot;&gt;Hierarchical Namespaces&lt;/h2&gt;

&lt;p&gt;Hierarchical namespaces aren’t too popular yet but are pretty self-explanatory. Instead of having a single level of namespaces, it introduces the ability to nest them, allowing for permission and object propagation. This not only simplifies the RBAC configuration but also enables teams to independently manage their resource allocation.&lt;/p&gt;

&lt;p&gt;Assume your team’s parent namespace have been given a certain amount of CPU and memory resources, e.g. your team lead can decide how much of these to assign to the dev, staging and prod environment. This can happen without needing to bother the cluster admins.&lt;/p&gt;

&lt;p&gt;By introducing HNs our example namespace structure from above could be converted into this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl hns tree
kube-system
default
frontend
└── dev
└── staging
└── prod
backend
└── dev
└── staging
└── prod
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This allows to give teams complete power over their parent namespace including the assigned resources. This way each team is independently able to create and manage namespaces and distribute their available CPU and memory across them.&lt;/p&gt;

&lt;p&gt;To make use of this concept you first need to install the hierarchical namespace controller in your cluster. Have a look at &lt;a href=&quot;https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/&quot; target=&quot;_blank&quot;&gt;the docs&lt;/a&gt; to get more details on the process. Also be aware that there’s no HNC release recommended for production yet. But according to the &lt;a href=&quot;https://github.com/kubernetes-sigs/hierarchical-namespaces#roadmap-and-issues&quot; target=&quot;_blank&quot;&gt;roadmap&lt;/a&gt; this should happen very soon.&lt;/p&gt;

&lt;h2 id=&quot;virtual-cluster&quot;&gt;Virtual cluster&lt;/h2&gt;

&lt;p&gt;Maybe you remember the term DinD that came up a few years ago. It stands for Docker in Docker, meaning you can manage Docker containers from within a Docker container.&lt;/p&gt;

&lt;p&gt;A virtual cluster is pretty much the same in the Kubernetes world. It allows you to create a semi-independent cluster within your existing cluster. At the time of this writing there are two implementations that I know of and we will have a look at.&lt;/p&gt;

&lt;h3 id=&quot;kubernetes-virtualcluster&quot;&gt;Kubernetes VirtualCluster&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/cluster-api-provider-nested/tree/main/virtualcluster&quot; target=&quot;_blank&quot;&gt;Kubernetes VirtualCluster&lt;/a&gt; is the implementation of the official Kubernetes multi-tenancy SIG. It introduces a new CRD that can be used to create new clusters on demand. The solution consists of the following three components that share the necessary responsibilities:&lt;/p&gt;

&lt;h4 id=&quot;vc-manager&quot;&gt;vc-manager&lt;/h4&gt;

&lt;p&gt;The vc-manager manages the realization of said VirtualCluster CRD objects by creating the necessary control plane pods or even proxying an external Kubernetes cluster when providing the corresponding kubeconfig.&lt;/p&gt;

&lt;h4 id=&quot;syncer&quot;&gt;syncer&lt;/h4&gt;

&lt;p&gt;The syncer makes sure to reflect API object changes happening in the tenant cluster to the super cluster and to propagate events occurring in the super cluster back to the tenant cluster. During this process certain mappings may occur, e.g. to prevent naming conflicts. For example namespaces being created in the tenant cluster, will be stored with a prefix in the super cluster control plane.&lt;/p&gt;

&lt;h4 id=&quot;vn-agent&quot;&gt;vn-agent&lt;/h4&gt;

&lt;p&gt;The vn-agent is running on the worker node and proxying the API requests to the local kubelet process. It makes sure that each tenant can only access its own pods.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/virtual-cluster-architecture.png&quot; alt=&quot;VirtualCluster architecture&quot; height=&quot;&quot; width=&quot;700&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    VirtualCluster architecture
    
    / &lt;a href=&quot;https://github.com/kubernetes-sigs/cluster-api-provider-nested/blob/main/virtualcluster/doc/vc-icdcs.pdf&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;image source&lt;/a&gt;
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Even though VirtualCluster passes most of the Kubernetes conformance tests it still has a few limitations that you can read about &lt;a href=&quot;https://github.com/kubernetes-sigs/cluster-api-provider-nested/tree/main/virtualcluster#limitations&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;. Currently it does not support managing DaemonSets and persistent volumes from a tenant cluster just to name a few.&lt;/p&gt;

&lt;p&gt;Similarly to the HNC, there’s no productive release of VirtualCluster at the time of this writing but is expected to be coming soon.&lt;/p&gt;

&lt;h3 id=&quot;loft-labs-vcluster&quot;&gt;Loft Labs vcluster&lt;/h3&gt;

&lt;p&gt;Loft Labs implemented their own version of a &lt;a href=&quot;https://www.vcluster.com/&quot; target=&quot;_blank&quot;&gt;virtual Kubernetes cluster&lt;/a&gt; and open sourced it in April 2021. It provides a similar functionality but with a slightly different user experience and architecture.&lt;/p&gt;

&lt;p&gt;Instead of requiring you to install an operator and create an CRD object, vclusters are being deployed by applying standard Kubernetes manifests (StatefulSet, Service, Role, …) onto your cluster. This creates a pod with 2 containers.&lt;/p&gt;

&lt;h4 id=&quot;k3s&quot;&gt;k3s&lt;/h4&gt;

&lt;p&gt;One is running k3s, a stripped down Kubernetes distribution, to create a separate control plane including the usual API server, data store and controller manager.&lt;/p&gt;

&lt;h4 id=&quot;syncer-1&quot;&gt;syncer&lt;/h4&gt;

&lt;p&gt;The other component is called syncer yet again and acts as an replacement for the default Kubernetes scheduler. Instead of distributing pods onto worker nodes it makes sure that vcluster pod specs are being passed to the host cluster and actually being applied there.&lt;/p&gt;

&lt;p&gt;For every pod a proxy object is being instantiated in the vcluster that reflects the status of the actual pod running in the host cluster.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;https://www.vcluster.com/docs/media/diagrams/vcluster-architecture.svg&quot; alt=&quot;vCluster architecture&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    vCluster architecture
    
    / &lt;a href=&quot;https://www.vcluster.com/docs/architecture/basics&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;image source&lt;/a&gt;
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you are curious about a more detailed view of a Loft Labs employee about the differences between the two virtual cluster implementations have a look &lt;a href=&quot;https://github.com/loft-sh/vcluster/issues/5#issuecomment-825445342&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;And there you have it. Four ways of providing Kubernetes to multiple tenants. Multiple clusters definitely provide the highest level of separation but at a “high” cost. Simple namespaces come with the exact opposite.&lt;/p&gt;

&lt;p&gt;But upcoming technologies try to find a good balance between the two and broaden the arsenal of enabling multi-tenancy for Kubernetes clusters. This way, hopefully everyone is going to find the right tool of choice for their exact use case.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Moving from utterances to giscus</title>
     <link href="https://shipit.dev/posts/from-utterances-to-giscus.html"/>
     <updated>2021-12-27T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/from-utterances-to-giscus</id>
     <content type="html">&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/utterances-to-giscus.png&quot; alt=&quot;&quot; height=&quot;&quot; width=&quot;640&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;why-migrate&quot;&gt;Why migrate?&lt;/h2&gt;

&lt;p&gt;I added the ability to leave comments on posts of this blog around a year ago. Back then I was looking for a privacy friendly, open source, free and easy to setup solution. When I came across &lt;a href=&quot;https://utteranc.es/&quot; target=&quot;_blank&quot;&gt;utterances&lt;/a&gt; and knew I found the right tool.&lt;/p&gt;

&lt;p&gt;Using Github’s issue feature as a backend for comments is just a very elegant solution in my opinion. No database that you need to manage, using Github to authenticate users (although I’d like to allow for anonymous users) and an integration that only requires you to load one client-side script.&lt;/p&gt;

&lt;p&gt;utterances served me very well and I could have used for way longer. If just there wouldn’t be a better alternative. Around March 2021 a new Github project with the name &lt;a href=&quot;https://github.com/giscus/giscus&quot;&gt;&lt;em&gt;giscus&lt;/em&gt;&lt;/a&gt; has been created.&lt;/p&gt;

&lt;p&gt;While being very similar to utterances there’s one distinct difference. Instead of using Github Issues it uses the fairly new Discussions feature to store comments. This alone would not have made me do a move as there’s no major advantage of one over the other for this use case. But there a few points and one in particular that drove my decision to migrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github Dark theme&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Starting with a small annoyance, utterances is missing the Github Dark theme. Although there’s a theme with this name, the colors don’t match and I dislike the looks of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Post reactions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;utterances allows you to add reactions to comments but as an author I’m also interested in the general reception of the post itself. giscus provides this feature.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/post-reactions.png&quot; alt=&quot;giscus displaying page reactions&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    giscus displaying page reactions
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Conversation view&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While you are able to tag users, utterances will simply render comments as a list in the order they have been created. Following conversations this way is pretty hard. giscus groups replies to a comment instead.&lt;/p&gt;

&lt;p&gt;This is perhaps the only point where Github Discussions has a slight advantage over using Issues as the reply feature is directly built into it.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/comment-reply.png&quot; alt=&quot;giscus rendering replies&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    giscus rendering replies
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Missing maintenance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Coming to the most important fact that made me migrate. As with many open source projects, utterances is being taken care of by a single developer. Unfortunately it seems like he lost interest in supporting the project over the last year.&lt;/p&gt;

&lt;p&gt;There are a lot of open issues and even PRs that aim to solve almost all of my problems with utterances. But sadly the project seems to have been abandoned completely.&lt;/p&gt;

&lt;p&gt;Don’t get me wrong, I’m not blaming anyone here. I’m an open source maintainer myself and know how changing times and interests can highly impact the level of effort you want to put into certain projects. But of course this will (rightfully) make your users move away from your software.&lt;/p&gt;

&lt;p&gt;So the decision has been made. But how did I migrate my blog from utterances to giscus without loosing existing comments?&lt;/p&gt;

&lt;h2 id=&quot;preparing-your-site&quot;&gt;Preparing your site&lt;/h2&gt;

&lt;p&gt;The setup for giscus is very similar to the one for utterances. Simply go to &lt;a href=&quot;https://giscus.app/&quot; target=&quot;_blank&quot;&gt;giscus.app&lt;/a&gt;, click through the configuration guide, copy the resulting script tag and paste it into your page where the comments should appear. Here’s my script tag with some explanation:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://giscus.app/client.js&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;brennerm/brennerm.github.io-comments&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;MDEwOlJlcG9zaXRvcnkzMTg1MTk0ODQ=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Announcements&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Discussions&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;category&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;DIC_kwDOEvw4vM4CAcbV&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Discussions&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;category&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;discussions&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;flag&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;enable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;disable&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reactions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;theme&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;renderer&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;crossorigin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;anonymous&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;performing&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;CORS&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;credentials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;!--&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;flag&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;asynchronously&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If your repository is public, has the Discussions feature enabled and the giscus app installed, you should see the comment renderer displaying 0 items at this point. If you don’t have any previous comments that you’d like to keep, you are basically done at this point.&lt;/p&gt;

&lt;h2 id=&quot;migrating-comments-of-a-single-page&quot;&gt;Migrating comments of a single page&lt;/h2&gt;

&lt;p&gt;The most important part when migrating comments from utterances to giscus is to keep the mapping of pages to discussions working. This works by selecting to correct mapping value in your script tag.&lt;/p&gt;

&lt;p&gt;Usually it should be the same as in your utterances script tag as both offer the same mapping techniques and I expect them to be compatible. I kept using the &lt;em&gt;pathname&lt;/em&gt; mode and it worked flawlessly for me.&lt;/p&gt;

&lt;p&gt;To be sure, try to create a new discussion containing at least one comment with the same title as your utterances issues. If everything works it should appear on your page. Be sure to delete this discussion to prevent any conflicts later on.&lt;/p&gt;

&lt;p&gt;After verifying that the mapping works simply navigate to the relevant Github issue and click on &lt;em&gt;Convert to discussion&lt;/em&gt; on the bottom right. The following dialog should appear in which you select the correct category.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/convert-issue.png&quot; alt=&quot;Converting an issue into a discussion&quot; height=&quot;&quot; width=&quot;640&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Converting an issue into a discussion
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If that works you are done. In my case I ended up with this error.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/unable-to-convert.png&quot; alt=&quot;Error I encountered when converting an issue to a discussion&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Error I encountered when converting an issue to a discussion
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Luckily I found a workaround which should work for you as well. Navigate to the &lt;em&gt;Discussions&lt;/em&gt; tab, edit the categories and change the, in my case, &lt;em&gt;Announcements&lt;/em&gt; category to an &lt;em&gt;Open ended discussion&lt;/em&gt;. Convert the issue, which should succeed now and revert the category change.&lt;/p&gt;

&lt;p&gt;If everything worked you should see the comments pop up on your page.&lt;/p&gt;

&lt;h2 id=&quot;migrating-comments-of-multiple-pages&quot;&gt;Migrating comments of multiple pages&lt;/h2&gt;

&lt;p&gt;To migrate comments of multiple pages we simply need to repeat the above process for each issue. Fortunately Github provides some bulk operations to speed up the process. At first we need to label all issues that we want to migrate. Feel free to use an existing or create a new label.&lt;/p&gt;

&lt;p&gt;Then select the issues and apply the label of choice.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/issue-label.png&quot; alt=&quot;Labelling multiple issues at once&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Labelling multiple issues at once
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Afterwards navigate to the &lt;em&gt;Labels&lt;/em&gt; overview and select the &lt;em&gt;Convert issues&lt;/em&gt; action of the appropriate label.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/bulk-convert.png&quot; alt=&quot;Converting multiple issues into discussions&quot; height=&quot;&quot; width=&quot;640&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Converting multiple issues into discussions
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This process happens in the background and may take some time depending on the number of issues you migrate. After it’s done all your comments should be successfully shown by the giscus comment renderer on each of your pages.&lt;/p&gt;

&lt;p&gt;Feel free to leave a reaction and comment to make sure I did everything right. 😉 If you run into any trouble make sure to let me know or seek help in the &lt;a href=&quot;https://github.com/giscus/giscus/discussions&quot; target=&quot;_blank&quot;&gt;giscus community&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Why newly created AWS Route53 records may not resolve</title>
     <link href="https://shipit.dev/posts/failing-aws-route53-records.html"/>
     <updated>2021-12-23T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/failing-aws-route53-records</id>
     <content type="html">&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/route53-nxdomain.png&quot; alt=&quot;&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So there you are with your newly created Route53 hosted zone, ready to assign some DNS names to your hosts. A simple click on the “Create Record” button and you want to check if it works immediately. But trying to resolve the DNS entry fails. What’s going on?&lt;/p&gt;

&lt;h2 id=&quot;pending-vs-insync&quot;&gt;pending vs. insync&lt;/h2&gt;

&lt;p&gt;Creating a Route53 record is a two-step process consisting of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pushing the record to the Route 53 database&lt;/li&gt;
  &lt;li&gt;propagating it to the AWS DNS servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you use the AWS console, the CLI tool or the API, only the first step will happen synchronously and almost immediately. Afterwards the record or rather the submitted change request will be in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PENDING&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href=&quot;https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetChange.html&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetChange&lt;/code&gt; API endpoint&lt;/a&gt; or the &lt;a href=&quot;https://awscli.amazonaws.com/v2/documentation/api/latest/reference/route53/wait/resource-record-sets-changed.html&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait resource-record-sets-changed&lt;/code&gt; CLI command&lt;/a&gt; you can then make sure the state switches to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSYNC&lt;/code&gt;. This indicates that your changes have been propagated to all AWS DNS servers responsible for your domain. AFAIK the AWS console does not allow you to check this synchronization state. But AWS claims that &lt;a href=&quot;https://aws.amazon.com/premiumsupport/knowledge-center/route-53-propagate-dns-changes/&quot; target=&quot;_blank&quot;&gt;propagation is finished within 60 seconds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alright, your newly created record is now in sync and you can finally resolve it. Just to realize that it fails again. What’s the deal now?&lt;/p&gt;

&lt;h2 id=&quot;understanding-soa&quot;&gt;understanding SOA&lt;/h2&gt;

&lt;p&gt;If you have been managing a DNS domain you probably came across certain record types like A, CNAME or TXT. There’s another very important but fairly unknown one, called SOA.&lt;/p&gt;

&lt;p&gt;SOA stands for “start of authority record” and contains several information about a given DNS zone. For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shipit.dev&lt;/code&gt; it looks like so:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dig shipit.dev SOA  
...
shipit.dev. 1934 IN SOA dns1.registrar-servers.com. hostmaster.registrar-servers.com. 1638547874 43200 3600 604800 3601
...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s an explanation of each part. All time value are in seconds.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shipit.dev.&lt;/code&gt;: the zone name&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1934&lt;/code&gt;: the TTL of the SOA record&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IN&lt;/code&gt;: the class of this record
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SOA&lt;/code&gt;: the type of this record&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dns1.registrar-servers.com.&lt;/code&gt;: the primary DNS server&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hostmaster.registrar-servers.com.&lt;/code&gt;: the administrator’s mail address&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1638547874&lt;/code&gt;: the zone’s serial number&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;43200&lt;/code&gt;: the time secondary servers should wait until they refresh from the primary one&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3600&lt;/code&gt;: the retry interval for failed requests of secondary servers&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;604800&lt;/code&gt;: the expiration time for secondary servers if primary is unreachable&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3601&lt;/code&gt;: the minimum duration value for how long records may be cached&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t go into each fields’ detail as only two of them are relevant for our topic.&lt;/p&gt;

&lt;p&gt;You may know the term TTL (time to live) which determines how long a response for a certain DNS record is being cached. What’s important to us is that there’s even a TTL value for records that can’t be resolved.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc2308#section-5&quot; target=&quot;_blank&quot;&gt;RFC 2308&lt;/a&gt;, it’s being calculated by choosing the smaller value between the TTL of the SOA record, in our case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1934&lt;/code&gt;, and the minimum duration value of our zone, in our case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3601&lt;/code&gt;. The resulting value is also being referred to as the negative TTL.&lt;/p&gt;

&lt;p&gt;This means in our scenario, a negative answer to a DNS query may be cached up to one hour at worst. And this may lead to our problem.&lt;/p&gt;

&lt;h2 id=&quot;putting-it-together&quot;&gt;putting it together&lt;/h2&gt;

&lt;p&gt;In case you haven’t figured out what’s going on, let’s have a look at the timeline of events.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We create a new AWS Route53 DNS record.&lt;/li&gt;
  &lt;li&gt;We try to resolve it but the record has not yet been propagated to AWS’ DNS servers and the resolution fails.&lt;/li&gt;
  &lt;li&gt;The negative answer is being cached with a certain TTL.&lt;/li&gt;
  &lt;li&gt;We wait 60 seconds until our new DNS record has been propagated to all DNS servers.&lt;/li&gt;
  &lt;li&gt;All consecutive tries to resolve the record keep failing until the negative answer in our cache expires.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And this is why newly created Route53 records may not resolve for quite some time if you query them too quickly. This pitfall is especially important when creating records in an automated fashion and querying them immediately after.&lt;/p&gt;

&lt;p&gt;For example in my case I was encountering this issue when using external-dns. This component automatically sets up DNS entries for applications running in Kubernetes. Deploying my application and executing tests against its external endpoints using a CI pipeline resulted in numerous failing DNS queries.&lt;/p&gt;

&lt;p&gt;Preventing this issue from happening is fairly simple though. Waiting a minute before querying is probably the easiest solution. Actively polling the synchronization state will be a bit quicker but may be a lot harder depending on the situation.&lt;/p&gt;

&lt;p&gt;Be aware that this is not really an issue with AWS Route53 in particular. Negative TTLs exist for every DNS domain and are there to protect the DNS server from unnecessary queries.&lt;/p&gt;

&lt;p&gt;Hopefully you are going to remember this post when encountering similar issues. Until then, enjoy your time in the cloud.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Optimizing applications on Kubernetes using Machine Learning</title>
     <link href="https://shipit.dev/posts/optimzing-kubernetes-applications.html"/>
     <updated>2021-06-30T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/optimzing-kubernetes-applications</id>
     <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Hint: This post has been happily sponsored by &lt;a href=&quot;https://www.stormforge.io/&quot; target=&quot;_blank&quot;&gt;StormForge&lt;/a&gt;, the company building the product that is being used. Despite sponsorship, they allowed me to share my honest opinion. Huge thanks for their trust and support!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;intro&quot;&gt;intro&lt;/h2&gt;

&lt;p&gt;Manually optimizing an application on Kubernetes is hard, especially if your goal is to keep costs low. There are plenty of options, the dependency trees in modern microservice architectures are getting bigger and bigger and for some (most?) of us all of this takes place on hardware that we have no control over.&lt;/p&gt;

&lt;p&gt;Additionally without a lot of testing and monitoring effort it’s very difficult to determine the “correct” specs for your deployments. It requires extensive trial and error to push your application into the area where it still performs without being overprovisioned.&lt;/p&gt;

&lt;p&gt;Otherwise you’ll end up wasting resources (and thus &lt;a href=&quot;/posts/wasting-money-with-kubernetes.html&quot; target=&quot;_blank&quot;&gt;money&lt;/a&gt;) or with a system that falls apart as load increases. That’s why I decided to take a look at StormForge, a tool that aims to solve this problem without occupying endless amounts of engineering time called StormForge Optimize.&lt;/p&gt;

&lt;h2 id=&quot;optimizing-with-stormforge&quot;&gt;Optimizing with StormForge&lt;/h2&gt;

&lt;p&gt;StormForge is a SaaS product that combines trial and error experiments with machine learning to help you determine configurations that will optimize your application for the metrics you care about. It also includes Performance Testing to place load on your system in a test environment, but for this blog I’ll focus on StormForge’s optimization capabilities. This includes three key components:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the StormForge Kubernetes controller, handling the experiment execution and the communication with the machine learning (ML) model&lt;/li&gt;
  &lt;li&gt;the ML model, responsible for selecting the parameter sets for each trial based on previous results&lt;/li&gt;
  &lt;li&gt;the StormForge dashboard, allowing you to analyze and export the resulting configurations of your experiments during and after the execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below you can find a visualization that explains the general workflow of an StormForge experiment.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/sfo-flow.png&quot; alt=&quot;StormForge experiment flow&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    StormForge experiment flow
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol&gt;
  &lt;li&gt;You start by creating an experiment, including your parameters and metrics you want to optimize.&lt;/li&gt;
  &lt;li&gt;The baseline values of your parameters will act as a starting point.&lt;/li&gt;
  &lt;li&gt;Your Kubernetes application (defined by a Pod, Job, Deployment, StatefulSet or DaemonSet) will be patched with these parameter values.&lt;/li&gt;
  &lt;li&gt;StormForge will wait for a certain condition. Usually this will be your application getting ready to process incoming requests.&lt;/li&gt;
  &lt;li&gt;A trial job will be executed putting your application under load.&lt;/li&gt;
  &lt;li&gt;Metrics, that you can freely define, will be collected.&lt;/li&gt;
  &lt;li&gt;The gathered metrics + the parameters of this run will be fed into StormForge’s machine learning model.&lt;/li&gt;
  &lt;li&gt;Based on that, the ML model will determine new parameter values.&lt;/li&gt;
  &lt;li&gt;Steps 3 to 8 will be repeated for a configurable amount of times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Over the course of a single experiment, StormForge will be able to predict configurations that will exceed the performance of your baseline values, based on the metrics you defined.&lt;/p&gt;

&lt;p&gt;As Kubernetes is StormForge’s primary target platform, you interact with it using the manifests, a concept you are probably already familiar with. The main one being the &lt;em&gt;Experiment&lt;/em&gt; manifest, which consists of the following parts:&lt;/p&gt;

&lt;h3 id=&quot;optimization&quot;&gt;Optimization&lt;/h3&gt;

&lt;p&gt;The optimization section allows you to set the number of iterations that the experiment will go through. Adjust this value according to your use case. Integrating StormForge experiments into your CI pipeline to prevent performance regression? 20 trials may be sufficient.&lt;/p&gt;

&lt;p&gt;Wanting to create a baseline configuration for your newly written application? Let StormForge cycle through 200 iterations and leave it running overnight to get the most accurate result.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;optimization&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;              
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;experimentBudget&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;50&quot;&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;parameters&quot;&gt;Parameters&lt;/h3&gt;

&lt;p&gt;Parameters define the possible configuration values that StormForge can experiment with, like the amount of memory, number of replicas or different disk storage types.&lt;/p&gt;

&lt;p&gt;As a general rule of thumb &lt;strong&gt;increase the number of trials along with your number of parameters&lt;/strong&gt;. The more permutations your experiment is covering, the more data points the ML model will need to determine the optimal configurations.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;memory&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;500&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2000&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cpu&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;500&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2000&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;500&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;patches&quot;&gt;Patches&lt;/h3&gt;

&lt;p&gt;Patches instruct the StormForge controller how to apply the previously declared parameters to your Kubernetes manifests. This is where StormForge’s biggest strength is in my opinion as &lt;strong&gt;you can patch everything that is configurable through a Kubernetes manifest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It may start with simple things like CPU and memory resource limits. But with more and more extensions coming to Kubernetes lately the possibilities are endless. One example I can imagine is using &lt;a href=&quot;https://crossplane.io/&quot; target=&quot;_blank&quot;&gt;Crossplane&lt;/a&gt; to cycle through VM types of different cloud providers and see on which one your application performs best.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;template:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;spec:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;containers:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;- name: my-app&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;resources:&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;limits:&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;memory: &quot;{{ .Values.memory }}Mi&quot;&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;cpu: &quot;{{ .Values.cpu }}m&quot;&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;trial-template&quot;&gt;Trial template&lt;/h3&gt;

&lt;p&gt;The trial template specifies what your application-specific performance measurement looks like. Internally it launches a &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/controllers/job/&quot; target=&quot;_blank&quot;&gt;Kubernetes Job&lt;/a&gt; which gives you the freedom of using your tool of choice, like &lt;a href=&quot;https://www.youtube.com/watch?v=rDpeXWTAdS4&quot; target=&quot;_blank&quot;&gt;StormForge’s own load testing solution&lt;/a&gt;, or another tool like Locust or Gatling.&lt;/p&gt;

&lt;p&gt;Also you can take care of some preparation tasks like deploying a Helm chart or a dedicated Prometheus instance. Be aware that trial jobs will be executed on the same Kubernetes cluster as your application under test. So be sure to keep room for its potential additional CPU and RAM usage.&lt;/p&gt;

&lt;p&gt;Example for a PostgreSQL load test trial template:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;initialDelaySeconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;15&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;crunchydata/crunchy-pgbench:centos7-11.4-2.4.1&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pgbench&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;envFrom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;secretRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres-secret&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;metrics&quot;&gt;Metrics&lt;/h3&gt;

&lt;p&gt;Metrics allow you to define the criteria that you want to gather and/or optimize, e.g. minimizing the costs while maximizing the throughput of your application. StormForge comes with certain metrics out of the box, like trial duration, but can also query popular monitoring systems like Prometheus or Datadog if you are looking to optimize more specific metrics.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;duration&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{duration&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.StartTime&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.CompletionTime}}&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cost&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pods&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# calculating with $20/month/CPU core and $3/month/GB of RAM&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{resourceRequests&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.Pods&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cpu=0.020,memory=0.000000000003&quot;}}&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-app&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For more information check out &lt;a href=&quot;https://docs.stormforge.io/optimize/reference/experiment/v1beta2/&quot; target=&quot;_blank&quot;&gt;StormForge’s documentation&lt;/a&gt; which covers all these elements in more detail.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;getting started&lt;/h2&gt;

&lt;p&gt;With all the concepts clarified let’s prepare our application and cluster for the first experiment. To begin with we have to decide on an application that we want to optimize. I went with Apache Cassandra because I always see it using huge amounts of resources in Kubernetes clusters and wanted to check whether this can be improved.&lt;/p&gt;

&lt;p&gt;So I went ahead and created a standard &lt;em&gt;StatefulSet&lt;/em&gt; that takes care of deploying my Cassandra pods. Additionally we’ll need some way of putting load onto the database during the trial job. Luckily, Cassandra comes with a tool to do just that, called &lt;em&gt;cassandra-stress&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cassandra:4.0&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cassandra-stress&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# making sure that our trial job does not eat up endless amounts of resources &lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1024Mi&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2000m&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bash&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;-c&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;CASSANDRA_URL=&quot;cassandra.default.svc.cluster.local&quot;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;/opt/cassandra/tools/bin/cassandra-stress write n=100000 -rate threads=400 -node $CASSANDRA_URL&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;/opt/cassandra/tools/bin/cassandra-stress mixed n=100000 -rate threads=400 -node $CASSANDRA_URL&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;cqlsh --request-timeout=60 -e &quot;DROP KEYSPACE keyspace1;&quot; $CASSANDRA_URL || true&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see our trial job consists of the following three steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;measuring write performance while prepopulating the cluster at the same time&lt;/li&gt;
  &lt;li&gt;measuring mixed (~50:50 read and write) performance&lt;/li&gt;
  &lt;li&gt;dropping the keyspace to make sure each test run starts in a clean environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because we always execute the same number of read and write operations (2 x 10000), simply measuring the time of the trial job to complete will be a good way to judge how our database performs.&lt;/p&gt;

&lt;p&gt;With our application good to go, let’s quickly prepare our Kubernetes cluster. Assuming you have a connection to it set up, after &lt;a href=&quot;https://docs.stormforge.io/optimize/getting-started/install/#installing-the-stormforge-optimize-tool&quot; target=&quot;_blank&quot;&gt;installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redskyctl&lt;/code&gt; CLI tool&lt;/a&gt; the following commands will deploy the StormForge controller.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;redskyctl login &lt;span class=&quot;c&quot;&gt;# log into our StormForge account&lt;/span&gt;
Opening your default browser to visit:

        https://auth.carbonrelay.io/authorize?...

You are now logged &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;redskyctl init &lt;span class=&quot;c&quot;&gt;# deploy the StormForge controller&lt;/span&gt;
customresourcedefinition.apiextensions.k8s.io/experiments.redskyops.dev created
customresourcedefinition.apiextensions.k8s.io/trials.redskyops.dev created
clusterrole.rbac.authorization.k8s.io/redsky-manager-role created
clusterrolebinding.rbac.authorization.k8s.io/redsky-manager-rolebinding created
namespace/redsky-system created
deployment.apps/redsky-controller-manager created
clusterrole.rbac.authorization.k8s.io/redsky-patching-role created
clusterrolebinding.rbac.authorization.k8s.io/redsky-patching-rolebinding created
secret/redsky-manager created
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;redskyctl check controller &lt;span class=&quot;nt&quot;&gt;--wait&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# wait for the controller to become ready&lt;/span&gt;
Success.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;starting-simple&quot;&gt;starting simple&lt;/h2&gt;

&lt;p&gt;After going through the preparation phase we can start with our first experiment. To keep it simple, we will start with two parameters (CPU and memory limits) and a single metric (duration of our trial) that we want to optimize. Below you can see the relevant parts of the experiment spec:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;                                                                                                   
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;memory&lt;/span&gt;                             
    &lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4000&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2000&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cpu&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;500&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3500&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;baseline&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;patches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;targetRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;StatefulSet&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cassandra&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;spec:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;template:&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;spec:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;containers:&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;- name: cassandra&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;resources:&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;limits:&lt;/span&gt;
                  &lt;span class=&quot;s&quot;&gt;memory: &quot;{{ .Values.memory }}Mi&quot;&lt;/span&gt;
                  &lt;span class=&quot;s&quot;&gt;cpu: &quot;{{ .Values.cpu }}m&quot;&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;requests:&lt;/span&gt;
                  &lt;span class=&quot;s&quot;&gt;memory: &quot;{{ .Values.memory }}Mi&quot;&lt;/span&gt;
                  &lt;span class=&quot;s&quot;&gt;cpu: &quot;{{ .Values.cpu }}m&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;duration&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{duration&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.StartTime&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.CompletionTime}}&quot;&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After roughly 1.5 hours we end up with the following chart in the StormForge dashboard.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/sf-single-metric.png&quot; alt=&quot;Results of the single metric experiment&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Results of the single metric experiment
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you may have already guessed, the results are not really surprising. The more resources that fuel your application, the better it will perform. Who would’ve thought? Anyway, I think this experiment is valuable for the following two reasons.&lt;/p&gt;

&lt;p&gt;First, it’s a simple showcase to understand how StormForge works, and second it helped us &lt;strong&gt;validate our assumptions&lt;/strong&gt;. Imagine if Cassandra performed worse with more resources or the trial duration turned out to be completely random. In this case we would know that something in our test setup is messed up. Instead we now can jump onto our first proper experiment with confidence.&lt;/p&gt;

&lt;h2 id=&quot;adding-another-dimension&quot;&gt;adding another dimension&lt;/h2&gt;

&lt;p&gt;So let’s add another dimension of metrics. In most real world scenarios you try to achieve the best performance while keeping the resource usage as low as possible. And that’s exactly what we will be trying to do. As a general rule of thumb for all kinds of optimization, you should always try to &lt;strong&gt;optimize at least two metrics that contradict each other&lt;/strong&gt;, like increasing throughput while decreasing CPU usage or reducing latency while minimizing cache size.&lt;/p&gt;

&lt;p&gt;We will be combining the CPU and memory usage into a single cost estimation metric by using the &lt;em&gt;resourceRequests&lt;/em&gt; function which allows us to create a weighted sum of the two. The parameters will stay the same while the metrics change to the following:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;duration&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{duration&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.StartTime&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.CompletionTime}}&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;costs&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pods&lt;/span&gt;                                    
    &lt;span class=&quot;c1&quot;&gt;# weights are equal to AWS EC2 pricing of c5.xlarge instance ($30.60/month/CPU core,$15.30/month/GB RAM)&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{resourceRequests&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.Pods&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cpu=0.03060,memory=0.000000000001530&quot;}}&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;                                     
      &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;                         
        &lt;span class=&quot;na&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cassandra&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;

&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After applying our Experiment manifest we can have a look at the individual trials runs using &lt;em&gt;kubectl&lt;/em&gt; like so:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get trials &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; wide &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt;                                     
NAME                          STATUS      ASSIGNMENTS             VALUES
cassandra-two-metrics-1-000   Completed   &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2000, &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000   &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;69, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33.808642559999996
cassandra-two-metrics-1-001   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2845, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3003   &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;36, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;91.87477680384
cassandra-two-metrics-1-002   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1782, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2886   &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;47, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;59.15927121407999
cassandra-two-metrics-1-003   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1529, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3225   &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;51, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;51.961336128
cassandra-two-metrics-1-004   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;728, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3403    &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;87, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;27.73630531584
cassandra-two-metrics-1-005   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;537, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2269    &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;107, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;20.07240498432
cassandra-two-metrics-1-006   Completed   &lt;span class=&quot;nv&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1929, &lt;span class=&quot;nv&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4000   &lt;span class=&quot;nv&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;41, &lt;span class=&quot;nv&quot;&gt;costs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;65.44468512
...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/sf-two-metrics.png&quot; alt=&quot;Results of the two metrics experiment&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Results of the two metrics experiment
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now that we have a chart with more useful information here’s an explanation of the different data points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The blue square represents the result of our baseline configuration, in our case 1 CPU core and 2 GB of RAM which resulted in a trial job duration of 69 seconds.&lt;/li&gt;
  &lt;li&gt;Green circles stand for “normal” trial runs. Those can mostly be ignored as they were outperformed by others.&lt;/li&gt;
  &lt;li&gt;The beige squares show the trial runs that performed well and give you a variety of choices depending on how you prioritize lowering costs versus increasing performance.&lt;/li&gt;
  &lt;li&gt;The orange square represents the trial run that StormForge selected as the sweet spot, in our case decreasing the costs by 16% while achieving the same trial run duration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After selecting the trial run that fits our needs, we can export the new application configuration using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redskyctl&lt;/code&gt; tool. I’ll go with StormForge’s recommended trial that &lt;strong&gt;decreased the costs by 16% while achieving the same duration run&lt;/strong&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;s&quot;&gt;$ redskyctl export -t \&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# export the patched resource&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;-f cassandra.yaml \&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# path to the Cassandra STS manifest&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;-f experiment.yaml \&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# path to the Experiment manifest&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;cassandra-two-metrics-1-015&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the trial run with our desired configuration&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;875m&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1000Mi&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;875m&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1000Mi&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Comparing this configuration to our baseline values of 1000m CPU shares and 2000Mi memory we were able to save quite a bit of resources. While the CPU improvement is probably based on a bit of wriggle room of the trial run duration, the RAM savings definitely confirm that Cassandra is running perfectly fine with half of our original value.&lt;/p&gt;

&lt;p&gt;Please be aware that the outcome of StormForge experiments is only meaningful if &lt;strong&gt;your load test is very similar to what your application is being exposed to when running in production&lt;/strong&gt;. That’s why you should select a load generation tool that is capable of doing so.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;conclusion&lt;/h2&gt;

&lt;p&gt;So there you have it. A basic example of optimizing Cassandra on Kubernetes by letting software take care of tedious and repetitive tasks and thereby figuring out to which position certain knobs should be set. Of course this example could have been extended endlessly by adding more and more parameters, like JVM heap size, different disk types or various compression algorithms.&lt;/p&gt;

&lt;p&gt;But as all of those very much depend on your use case and the capabilities of your infrastructure, I’d be very happy if you are able to do that on your own using StormForge after reading this blog post. If there are any open questions, feel free to &lt;a href=&quot;https://www.stormforge.io/company/contact-us/&quot; target=&quot;_blank&quot;&gt;reach out to them&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Self-employment&#58; three months update</title>
     <link href="https://shipit.dev/posts/self-employment-month-three.html"/>
     <updated>2021-04-17T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/self-employment-month-three</id>
     <content type="html">&lt;h2 id=&quot;some-background&quot;&gt;some background&lt;/h2&gt;

&lt;p&gt;I jumped into the world of self-employment as a DevOps freelancer at the beginning of this year. After &lt;a href=&quot;/posts/first-month-of-self-employment.html&quot; target=&quot;_blank&quot;&gt;reviewing my first month&lt;/a&gt; was &lt;a href=&quot;https://www.reddit.com/r/devops/comments/ld9eg6/my_first_month_of_being_a_devops_freelancer/&quot; target=&quot;_blank&quot;&gt;well received&lt;/a&gt; by the community I decided to keep this going. Here’s my second update after three months of self-employment.&lt;/p&gt;

&lt;h2 id=&quot;lets-start-with-numbers&quot;&gt;let’s start with numbers&lt;/h2&gt;

&lt;p&gt;From my previous three clients &lt;strong&gt;two remain&lt;/strong&gt;. Will go into more detail on that topic in the next section.&lt;/p&gt;

&lt;p&gt;Since the beginning of this year I worked a total of &lt;strong&gt;469.5 hours&lt;/strong&gt; which makes &lt;strong&gt;156.5 a month&lt;/strong&gt; and &lt;strong&gt;7.57 per working day&lt;/strong&gt;. That’s still a pretty average workload in Germany. Let’s have a look at my work time per day of the week.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/working-hours-distribution-3.png&quot; alt=&quot;Working time distribution across the week&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Working time distribution across the week
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;No visible change here. Still a pretty standard distribution which I don’t think will change any time soon. How about my working hours per month?&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/hours-worked-per-month.png&quot; alt=&quot;Hours worked per month&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Hours worked per month
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Seeing this chart for the first time surprised me a little. From my feeling I worked less and less each month but the raw numbers tell that the exact opposite is the case. Apparently my brain is very good at tricking me into thinking what I want to think.&lt;/p&gt;

&lt;p&gt;A key takeaway here, &lt;strong&gt;track your time&lt;/strong&gt; no matter if you bill your clients by hour or by project and be honest to yourself. After all, there are different and more important topics in life than work. 😉&lt;/p&gt;

&lt;h2 id=&quot;clients-come-and-go&quot;&gt;clients come and go&lt;/h2&gt;

&lt;p&gt;As already said I, finalized working with one of my clients. At the same time other opportunities came up.&lt;/p&gt;

&lt;p&gt;After reading my first blog post about self-employment a kind fellow, currently founding a startup, approached me to show me the upcoming open source project he’s working on. While there was no option for me to support them at the moment, I’m really glad to have met him, get to know his product at such an early state and provide some feedback to hopefully make it just a little better.&lt;/p&gt;

&lt;p&gt;Similarly after reading my last blog post about &lt;a href=&quot;/posts/wasting-money-with-kubernetes.html&quot;&gt;wasting money with Kubernetes&lt;/a&gt; I was messaged by a really nice guy from an American company. We had two calls during the last weeks, getting to know each other a little and I got a quick glance at their product. Right now we are talking about different ways to collaborate. Let’s see where this will be going. 🙂&lt;/p&gt;

&lt;p&gt;Lastly, after attending my city’s monthly meetup of entrepreneurs and self-employed people like usual, I got in contact with a person from a local company. They were currently applying for a prototype fund backed by the German government. I was especially hooked after finding out that the resulting prototype has to be made publicly available. As I’m a huge fan of open source software I did my best to improve their application. Currently crossing fingers that we’ll get the fund and the project can start in autumn.&lt;/p&gt;

&lt;p&gt;Why do I tell you all of this? Well mainly cause I want to showcase that you can get to know products, companies and especially human beings from all kinds of sources. Get in contact with people, shout out your more or less professional opinion and join groups that share similar interests. This allows you to open up many opportunities which helps settling your mind in case you get unexpectedly dropped by one of your clients. Additionally having the choice where to go next is always better than depending on that one job you aren’t passionate about.&lt;/p&gt;

&lt;h2 id=&quot;taxes&quot;&gt;taxes&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Disclaimer: Be aware that I have no kind of qualification when it comes to taxes whatsoever. Everything I’m sharing about this topic is based on my experience and half-hearted research to at least somewhat understand how things work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This quarter I gained my first experience on how taxes are being handled in Germany if you are self-employed. Two kinds of taxes are important in this case, namely sales (or VAT) and income tax.&lt;/p&gt;

&lt;p&gt;The former only applies to sales within Germany and are paid on a monthly basis. Example: I’m selling my DevOps services with a net worth of 1000 € to a German company. They’ll pay me a total amount of 1190 € (19% is the general German sales tax) and I transfer the 119 € to my local finance authority till the beginning of the next month.&lt;/p&gt;

&lt;p&gt;For sales within Europe the so called &lt;a href=&quot;https://www.german-tax-consultants.com/vat-services/german-vat-reverse-charge.html&quot; target=&quot;_blank&quot;&gt;reverse charge mechanism&lt;/a&gt; applies. This transfers the responsibility for declaring sales tax to the invoice recipient according to their local tax laws. The same is true if I sell my services to companies outside of the EU.&lt;/p&gt;

&lt;p&gt;Income tax on the other hand is based, well as the name suggests on my income. As my total earnings will only be known by the end of the year and authorities don’t want to wait for their tax payments for that long, it’s common to go with an estimation here.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;https://imgs.xkcd.com/comics/tax_ai.png&quot; alt=&quot;Credits: &amp;lt;a href=&apos;https://xkcd.com/2265/&apos;&amp;gt;xdcd.com&amp;lt;/a&amp;gt;&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Credits: &lt;a href=&quot;https://xkcd.com/2265/&quot;&gt;xdcd.com&lt;/a&gt;
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That means communicating something like: “I plan on earning X € during this year.” at the beginning of each year. Based on that number you’ll make income tax payments (of the same amount) per month or in my case per quarter. At the end of the year your final earnings will be determined, the income tax will get calculated, all previous tax transaction will be accumulated and either you or the authorities have to balance out the difference.&lt;/p&gt;

&lt;p&gt;As usual, the rate of income tax increases with the amount you earn, combined from all sources (employment, self-employment, real estate income, …). It starts at 0% and goes up all the way to 45%. If you are interested in the detailed numbers check out &lt;a href=&quot;https://en.wikipedia.org/wiki/Taxation_in_Germany#Taxes_on_income&quot; target=&quot;_blank&quot;&gt;this Wikipedia article&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;payment-fees&quot;&gt;payment fees&lt;/h2&gt;

&lt;p&gt;If you read my first self-employment blog post you may remember me not being sure what I’ll end up paying to be able to receive money in foreign currencies. Turned out it was a lot (&amp;gt;5%).&lt;/p&gt;

&lt;p&gt;Paying a high fee just to be able to get money onto your bank account hurts. Especially if you make the calculation back to the amount of time you spent to earn it in the first place. Working 3 days a month just for a bank transfer is a very unsatisfying feeling.&lt;/p&gt;

&lt;p&gt;Lessons learned: Clarify how you’ll get paid before joining a client. With platforms like &lt;a href=&quot;https://wise.com/register/?utm_source=url&amp;amp;utm_medium=invite&amp;amp;utm_content=&amp;amp;utm_campaign=3for50&amp;amp;referralCode=maximilianb339&amp;amp;referralToken=maximilianb339&amp;amp;profileType=PERSONAL&quot; target=&quot;_blank&quot;&gt;Wise*&lt;/a&gt; (formerly: TransferWise) it can be very easy and cheap to pay and get paid from people all across the globe. But in certain countries their service isn’t available (yet). There are alternatives. But expect them to be (way) more expensive.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;* affiliate link&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion-after-three-months&quot;&gt;conclusion after three months&lt;/h2&gt;

&lt;p&gt;So how would I describe my three month journey of self-employment? To me it was one hell of a ride with emotions ranging from excitement and curiosity to fear and uncertainty. Leaving a stable, well paying job behind “just” to be able to work under your own guidance is a tough choice.&lt;/p&gt;

&lt;p&gt;But as usual, leaving your comfort zone always results in personal growth no matter what’s the outcome. Guess most of you remember the first time approaching that attractive girl/boy, stuttering something of coffee with oat milk foam and park. After doing that X times you get used to it. This is where I am right now after these three months, which is good and bad at the same time.&lt;/p&gt;

&lt;p&gt;Getting used to something allows you to free up your mind which enables you to take on new challenges. On the other hand it makes everything, like dating that girl you had a crush on since ages or doubling your paycheck, normal after some time. Let’s wrap it up here before I become to philosophical. Always strive to improve but never forget to appreciate what you already have. 🙂&lt;/p&gt;

&lt;p&gt;Self-employment isn’t easy and it’s definitely not for everyone. But I think there are more people that could benefit from it and the demand, at least on the DevOps market, is definitely there. That’s why I’d like to support people going for it. My first step is working out a getting started guide I would have loved to have a few months back.&lt;/p&gt;

&lt;p&gt;Feel free to hit me up if you have some questions that you’d like to get answered or topics that I should cover. Until then, stay safe. ✌️&lt;/p&gt;

&lt;hr /&gt;

</content>
   </entry>
   
 
   
   <entry>
     <title>How to waste money using Kubernetes</title>
     <link href="https://shipit.dev/posts/wasting-money-with-kubernetes.html"/>
     <updated>2021-03-14T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/wasting-money-with-kubernetes</id>
     <content type="html">&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-money-burn.jpg&quot; alt=&quot;&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Kubernetes enables you to do a lot. Orchestrating your application deployments, seamlessly integrating with public cloud services, distributing workload across thousand of nodes or simply generating a huge bill by the end of each month. While the latter can be beneficial if you are on the issuing side of the bill, this blog posts showcases a few pitfalls that I’ve seen (a lot) and explains how to avoid them to shave a few bucks off of it.&lt;/p&gt;

&lt;p&gt;I’m mainly focussing on saving up on pay-as-you-go cloud services or IaaS/PaaS offerings here. But if you are running Kubernetes on-premise you may be able to spend your freed up resources on something else instead.&lt;/p&gt;

&lt;h2 id=&quot;the-productive-vm&quot;&gt;the productive VM&lt;/h2&gt;

&lt;p&gt;Let’s start with the topic that is probably going to have the biggest impact on your bill, insufficient or missing autoscaling.&lt;/p&gt;

&lt;p&gt;Are you running the same amount of pods on the same number of nodes 24/7 to be able to handle the peak usage of your application that is occurring for only a few hours a day? Or do you keep your dev setups running all day long so that your engineers can continue where they left off at the next day?&lt;/p&gt;

&lt;p&gt;If so there’s something your are missing out on…Autoscaling&lt;/p&gt;

&lt;p&gt;Let’s highlight the three most common autoscaling mechanisms:&lt;/p&gt;

&lt;h3 id=&quot;vertical-pod-autoscaling&quot;&gt;vertical pod autoscaling&lt;/h3&gt;

&lt;p&gt;Pods can have hard limits for the amount of CPU and memory resources that they are allowed to consume. With vertical pod autoscaling those limits can be adjusted (within boundaries) on the fly and automatically.&lt;/p&gt;

&lt;h3 id=&quot;horizontal-pod-autoscaling&quot;&gt;horizontal pod autoscaling&lt;/h3&gt;

&lt;p&gt;Here we are talking about adjusting the number of pod replicas. Your pod reaches a certain amount of load? Let’s create another one and the let the load balancer do its job. The load decreases a lot? Let’s scale down again.&lt;/p&gt;

&lt;h3 id=&quot;cluster-autoscaling&quot;&gt;cluster autoscaling&lt;/h3&gt;

&lt;p&gt;This is by far the most important one as this is where the actual saving happens. Where’s the benefit if you scale down pods and your nodes end up sitting there idling and wasting your money anyway? This is where cluster autoscaling comes into play that takes care of destroying and spawning nodes based on their utilization.&lt;/p&gt;

&lt;p&gt;All of those three can be applied at the same time but you should know what you do in this case.&lt;/p&gt;

&lt;p&gt;When coming from a background (and I certainly do) where you did setup a server or VM and labeled it &lt;em&gt;prod-24_7-dont-touch-super-important-app-0&lt;/em&gt; there’s a mindset change necessary when moving to Kubernetes. Worker nodes are simple minions, slaves if you will with the only purpose of running your precious applications.&lt;/p&gt;

&lt;p&gt;They can be created within seconds but also disposed at the same rate. As soon as you accept that you can start having fun by decreasing the amount of computing resources your cluster runs on. Give it a try. Having a cluster with zero worker nodes is a thing today. 😉&lt;/p&gt;

&lt;p&gt;Sure, running a single &lt;em&gt;a1.xlarge&lt;/em&gt; AWS EC2 instance may only cost you 2.5 dollars a day but be aware that there a lot of factors that come into play here. Multiply this by having individual setups for your ten developers and running them 24/7 for a year and you are looking at a 9000 $ bill.&lt;/p&gt;

&lt;h2 id=&quot;fearing-the-spot&quot;&gt;fearing the spot&lt;/h2&gt;

&lt;p&gt;Cloud providers are always having a bit of extra computing capacity as requests are rising and there’s a lot of demand fluctuation during the day. That’s why most of them are offering so called spot instances. They are sold to you at a huge discount (70 - 90%) with the only premise that they can be shutdown and withdrawn with little to no announcement.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/spot.jpg&quot; alt=&quot;image credit: Marcelo Jaboo&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    image credit: Marcelo Jaboo
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As that sounds completely inappropriate for your traditional productive VM it would be great if there’d be some platform that could gracefully handle this interruption for me, right? Surprise surprise, with a little bit of (or even no) preparation Kubernetes can take care of that.&lt;/p&gt;

&lt;p&gt;If it receives the notification of a node being shut down it transfers the pods onto other nodes or requests a new one. Even if there’s no notification, the pods that are being terminated will get shifted and your application stays up and running.&lt;/p&gt;

&lt;p&gt;It takes some time to get confident with someone else randomly shutting down nodes that run your productive app but if you’ve seen it working a dozen times you get used to it. Honestly, depending on your bidding price for the spot instances it doesn’t really happen often anyway (at least on AWS).&lt;/p&gt;

&lt;p&gt;So instead of paying 100% of the on-demand price try offloading (some of) your workload onto those spot instances. If you want to be on the safe side you can also run your cluster on spot as well as on-demand instances at given ratio e.g. 50:50 or 20:80. That really comes down to your needs.&lt;/p&gt;

&lt;h2 id=&quot;lazy-and-hungry&quot;&gt;lazy and hungry&lt;/h2&gt;

&lt;p&gt;What’s worse than wasting money on an application that is just idling. Exactly, it’s wasting money on an application that is just idling and consuming a lot of resources at the same time.&lt;/p&gt;

&lt;p&gt;In my experience we are talking primarily about memory here. It has not been only once that I’ve seen a Java Spring micro service application consuming gigabytes of RAM and doing nothing. At the same time there are Go applications that you’d need hundred instances of to fill that amount of memory.&lt;/p&gt;

&lt;p&gt;Databases and message queues is also something to watch out for. I’ve joined a client 2 months ago and Cassandra is still not letting go of that 2 gigabytes.&lt;/p&gt;

&lt;p&gt;Of course there are a lot of other factors that come into play when choosing your tech stack but take the base capacity consumption into consideration. Another option is to extract resource hungry components and reuse them for multiple deployments of your application.&lt;/p&gt;

&lt;h2 id=&quot;buy-3-get-2&quot;&gt;buy 3, get 2&lt;/h2&gt;

&lt;p&gt;Having a look at the following image you can see that there’s something that can have an impact on the amount of resource capacity that is allocatable for your pods.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-resources.svg&quot; alt=&quot;Overview of Kubernetes&apos; resource capacity handling&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes&apos; resource capacity handling
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It’s called memory and CPU reservations which are being taken care of by kubelet, the application managing your Kubernetes workers. Those are very important to make sure your nodes stay healthy and have enough resources to execute their own processes besides running your pods.&lt;/p&gt;

&lt;p&gt;In most cases your worker nodes’ sole purpose should be to take over Kubernetes workloads but mandatory OS process, the container runtime or kubelet itself introduce a certain memory and CPU overhead that need to be considered.&lt;/p&gt;

&lt;p&gt;What’s important here is that reservations are sized correctly so that all essential functions can be executed but you don’t hold back resources that end up being unused. Most of the time you can influence these parameters by setting the &lt;em&gt;–kube-reserved&lt;/em&gt; and &lt;em&gt;–system-reserved&lt;/em&gt; kubelet parameters accordingly.&lt;/p&gt;

&lt;p&gt;For certain Kubernetes offers you are bound to certain predefined values. E.g. Azure Kubernetes service comes with &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/aks/concepts-clusters-workloads#resource-reservations&quot; target=&quot;_blank&quot;&gt;increased reservations for small instances&lt;/a&gt; (on a percentage basis) so you may decide to go with less but bigger nodes instead.&lt;/p&gt;

&lt;p&gt;Monitor your worker nodes and adjust the parameters or research your options depending on your Kubernetes operator and minimize the amount of money wasted on resource reservations.&lt;/p&gt;

&lt;h2 id=&quot;cpu-and-memory-requests&quot;&gt;CPU and memory requests&lt;/h2&gt;

&lt;p&gt;As you can see in the graph above CPU and memory request values, defined for each your pods, are being used to schedule them onto worker nodes. Setting these parameters too low and your application wont be able to perform as intended or in the worst case even get killed.&lt;/p&gt;

&lt;p&gt;Declaring them too high and you’ll waste resources that could be used for scheduling other pods. E.g. if your are setting a memory request of 1 GB of RAM and your application never consumes more than 400 Mi you are essentially wasting money for 600 Mi.&lt;/p&gt;

&lt;p&gt;This is why it’s important to define your requests as close as possible to the actual usage with going rather a little high than too low. Very similar to the previous resource reservations.&lt;/p&gt;

&lt;p&gt;Kubernetes’ resource requests and limits is a very complex topic and you should get used to the fact that you’ll never get it 100% right but rather aim for an “as good as possible” state. Collecting historical data of your pods’ consumption or incorporating vertical pod autoscaling will help you with that.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;summary&lt;/h2&gt;

&lt;p&gt;Of course there are still plenty of other things that can be improved in order to reduce the bill. Cleaning up unused resources, preventing to collect unnecessary logs and metrics, etc. But those points pretty much apply to every other infrastructure as well and this blog post already got longer than I initially anticipated. Seems like Kubernetes is pretty good at burning money. 😉&lt;/p&gt;

&lt;p&gt;So there you have it. Be aware that most of the topics discussed are only realizable if your applications meet certain requirements. Being able to get shutdown at any time and saving their state externally are only some. Talk with your devs, have a look at the &lt;a href=&quot;https://12factor.net/&quot; target=&quot;_blank&quot;&gt;Twelve-Factor App&lt;/a&gt; and make a plan.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the read and are able to save a few bucks in the future. 🙂&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>AWS EC2 launch configurations vs launch templates</title>
     <link href="https://shipit.dev/posts/aws-launch-configuration-vs-template.html"/>
     <updated>2021-02-11T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/aws-launch-configuration-vs-template</id>
     <content type="html">&lt;p&gt;At first sight AWS launch configurations and templates may seem very similar. Both allow you to define a blueprint for EC2 instances. Let’s have a look at their differences and see which one we should prefer.&lt;/p&gt;

&lt;h2 id=&quot;they-grow-up-so-fast&quot;&gt;They grow up so fast&lt;/h2&gt;

&lt;p&gt;Launch configuration are old. In terms of cloud technologies they are essentially ancient. During my research I found articles that date back to 2010. It’s hard to find exact details but it seems like they have been introduced together Auto Scaling Groups (ASGs) or shortly after. This also explains why there are only compatible with ASGs. Want to create a single EC2 instance based on an launch configuration? That is not going to happen.&lt;/p&gt;

&lt;p&gt;Settings that are supported include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the EC2 image (AMI)*&lt;/li&gt;
  &lt;li&gt;the instance type (e.g. m5.large)*&lt;/li&gt;
  &lt;li&gt;an SSH key pair to connect to the VM*&lt;/li&gt;
  &lt;li&gt;the purchase options (on-demand or spot)&lt;/li&gt;
  &lt;li&gt;an IAM profile&lt;/li&gt;
  &lt;li&gt;one or more security groups&lt;/li&gt;
  &lt;li&gt;a block device mapping to specify additional storage volumes&lt;/li&gt;
  &lt;li&gt;a few more minor things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;* marks required values&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Changing any of these parameters is not supported due to launch configurations’ nature of being immutable. This means instead of updating it in place you need to delete and recreate it.&lt;/p&gt;

&lt;p&gt;All in all launch configurations have a very specific use case and a set of configuration options limited to the basic parameters. Let’s see how launch templates compare.&lt;/p&gt;

&lt;h2 id=&quot;the-hot-stuff&quot;&gt;The hot stuff&lt;/h2&gt;

&lt;p&gt;The first big difference is the wider range of AWS services that are compatible with launch templates. Additionally to ASGs it can be used in managed EKS node groups and to create single EC2 instances.&lt;/p&gt;

&lt;p&gt;Regarding configuration options, they support a bit more than launch configurations like network settings and a few more advanced details (interruption behavior, termination protection, CloudWatch monitoring, …).&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/launch-template-advanced.png&quot; alt=&quot;A few of launch templates&apos; advanced settings&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    A few of launch templates&apos; advanced settings
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The main difference here is that every setting is optional. As you can see in the image above you can set the value “Don’t include in launch template” for every parameter. You can essentially create a launch template that specifies nothing. That’s kinda pointless but you get the idea.&lt;/p&gt;

&lt;p&gt;Combining this with the ability to source values from existing templates and you can start to imagine all the options that arise. Similar to something like Docker images you can start to create your base template(s) and inherit more specific templates from them.&lt;/p&gt;

&lt;p&gt;As nice as this may sound I just want to advice you to be cautious with doing this. Depending on organization and your upfront template “architecture” planning this may work really well. But it hasn’t been just once that I’ve seen this ending up in dependency hell. (including rhyme in blog post ✅) So think about if you don’t wanna stick with independent templates especially when factoring in the next feature.&lt;/p&gt;

&lt;p&gt;Launch templates support versioning. Meaning while a single version is immutable you are still able to make modifications which will result in a new one that you can refer to. In my opinion this workflow provides a much better user experience compared to the delete and recreate approach that you need to go through with launch configurations. But again it adds complexity of managing the references in your ASGs and child templates.&lt;/p&gt;

&lt;h2 id=&quot;which-is-the-better-choice&quot;&gt;Which is the better choice?&lt;/h2&gt;

&lt;p&gt;So, how did both do? Is there a clear winner or can I give you at least a recommendation which one you should prefer?&lt;/p&gt;

&lt;p&gt;I’m not sure how things really evolved so take the following with a grain of salt. To me it seems like launch configurations have been created out of necessity when introducing ASGs. Afterwards the folks from AWS noticed that having an EC2 blueprint could be useful for other services as well. Cause it was probably easier to create something new instead of making the existing solution more generic they &lt;a href=&quot;https://aws.amazon.com/about-aws/whats-new/2017/11/introducing-launch-templates-for-amazon-ec2-instances/&quot; target=&quot;_blank&quot;&gt;introduced launch templates in late 2017&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additionally as far as I know there’s nothing that you can achieve with launch configurations which is not doable using launch templates. Please let me know if there’s a use case I’m missing here. The only advantage of launch configurations is that they are just simpler. No versioning, no inheritance, immutability, just a minimal set of required and a few optional values and you are good to create your ASG.&lt;/p&gt;

&lt;p&gt;My impression from reading through the documentation is that AWS will soon start to deprecate launch configurations. They clearly &lt;a href=&quot;https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchConfiguration.html&quot; target=&quot;_blank&quot;&gt;discourage from using them&lt;/a&gt; and even provide &lt;a href=&quot;https://docs.aws.amazon.com/autoscaling/ec2/userguide/replace-launch-config.html&quot; target=&quot;_blank&quot;&gt;a guide&lt;/a&gt; to replace existing launch configurations with templates. That’s why I’d suggest you to use launch templates for anything new and start to migrate your existing launch configurations if you plan on continue using them long-term.&lt;/p&gt;

&lt;p&gt;That’s all with my little comparison. Hope you got some value out of it. Enjoy your day 👍&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>My first month of self-employment</title>
     <link href="https://shipit.dev/posts/first-month-of-self-employment.html"/>
     <updated>2021-02-05T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/first-month-of-self-employment</id>
     <content type="html">&lt;h2 id=&quot;my-way-towards-self-employment&quot;&gt;my way towards self-employment&lt;/h2&gt;

&lt;p&gt;In November 2020 I decided to take a huge personal step. I quit my beloved job and started my journey in becoming an independent freelancer. During the previous five years of working at two different companies the desire to start something for myself and being able to work with people from more than one company at a time became bigger and bigger. So the day came, 1st of January 2021, the first day of me being self-employed. Here’s what happened during the first month.&lt;/p&gt;

&lt;h2 id=&quot;some-numbers&quot;&gt;some numbers&lt;/h2&gt;

&lt;p&gt;Of course I was trying to prepare as best as I could beforehand and so I was able to start off working with two companies (both through Upwork). Another one approached me after reading one my blog posts which accumulates to &lt;strong&gt;three total clients&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Being self-employed also means being responsible for managing when and how much to work. As it’s what I’m used to and also works best for my clients I mostly kept working during the week which results in following distribution of my working time.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/working-hours-distribution.svg&quot; alt=&quot;Working time distribution across the week&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Working time distribution across the week
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While being surprised during the creation of this chart I guess this spread is pretty common. You start with a lot of energy at the beginning of the week and it decreases every day. So if you need something done quick, make sure to give me call at the beginning of the week. 😁&lt;/p&gt;

&lt;p&gt;In total I was working &lt;strong&gt;149 hours&lt;/strong&gt; across January. Dividing this by 20 working days results in &lt;strong&gt;7.45 hours a day&lt;/strong&gt; or &lt;strong&gt;37 hours a week&lt;/strong&gt;. That’s pretty close to the usual 40 hour contracts that are common in Germany and I had signed at my previous employers.&lt;/p&gt;

&lt;p&gt;I expect to stay at this level of workload at least as long as COVID is a thing. Afterwards I may spend more time on other activities I enjoy and have been missing since quite some time.&lt;/p&gt;

&lt;h2 id=&quot;upgrading&quot;&gt;upgrading&lt;/h2&gt;

&lt;p&gt;During my first month I also bought a few things to improve the experience of my clients, my productivity (at least that’s what I’m telling myself) and to help protect my personal health.&lt;/p&gt;

&lt;p&gt;As I’m mainly working from home and thus spending quite some time with people in video calls I got &lt;strong&gt;a proper microphone&lt;/strong&gt; cause who likes having a constant noise in their ear during a two hour call, right?&lt;/p&gt;

&lt;p&gt;Besides some small things, like a USB-C hub or wireless headphones, I finally got myself &lt;strong&gt;a standing desk&lt;/strong&gt;. Did get pretty used to these things at the office of my previous employer and missed having one a lot since working from home due to COVID. I’m a fan of mechanical ones cause I’m concerned about the electronic parts breaking and I don’t move it up and down multiple times a day anyway.&lt;/p&gt;

&lt;p&gt;Other than that I reused the simple setup I already had. A Dell XPS 13 and secondary monitor. That’s all I currently need and I don’t plan on extending my setup for the time being.&lt;/p&gt;

&lt;h2 id=&quot;all-hail-the-bureaucracy&quot;&gt;all hail the bureaucracy&lt;/h2&gt;

&lt;p&gt;I’m living in Germany and as you may know there are a lot of complex regulations here for every aspect of life. The fact that I had to create a different kind of invoice for each of my three clients should demonstrate that. Unfortunately the German law is behind its time when it comes to jobs that haven’t existed 10 years ago just like my profession as a Cloud and DevOps Engineer.&lt;/p&gt;

&lt;p&gt;This results in having to argue with the finance authority whether you’re an entrepreneur or a freelancer (yes those are two distinct things in German law). Don’t wanna go into too much detail here cause it’s probably not relevant to you anyway but just know that I had my fights.&lt;/p&gt;

&lt;p&gt;Tax declaration is another complex topic in Germany. You can probably take care of it on your own and spend hours on topics you don’t enjoy and prevent you from doing what you excel at.&lt;/p&gt;

&lt;p&gt;Or you can take a decent amount of money and let it getting handled by someone else. Ideally this should be someone you trust. That’s why I’m already at my second tax accountant after my first month of self-employment. Let’s hope I can stay with this one for a while.&lt;/p&gt;

&lt;p&gt;Last topic, bank account. Fortunately it’s not hard to get a cheap or even free bank account in Germany these days. There’s only one thing that almost no bank talks about for whatever reason and that’s receiving money from other countries or in foreign currencies.&lt;/p&gt;

&lt;p&gt;As one of the reasons getting self-employed was to work with companies outside of Germany this point is very relevant to me. I still don’t know what I need to pay until I receive my first payment and then it’s too late anyway. Let’s see how good or bad it’ll be.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;summary&lt;/h2&gt;

&lt;p&gt;So there you have it. My first month of self-employment in ~800 words. Hopefully you got some value out of my story. Feel free to reach out to me if you have some questions regarding the process of getting self-employed.&lt;/p&gt;

&lt;p&gt;All in all I’m very happy with my new way of working and hope that I have to take care of less and less annoyances each month and to be able to focus on what I enjoy. Maybe there’ll be another update after some time. Stay safe!&lt;/p&gt;

&lt;h2 id=&quot;update-17042021&quot;&gt;update 17.04.2021&lt;/h2&gt;

&lt;p&gt;I came up with an update after &lt;a href=&quot;/posts/self-employment-month-three.html&quot;&gt;three months of self-employment&lt;/a&gt;. Feel free to check it out.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Kubernetes operators with Python &#35;2&#58; Implementing Controller</title>
     <link href="https://shipit.dev/posts/k8s-operators-with-python-part-2.html"/>
     <updated>2021-01-18T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/k8s-operators-with-python-part-2</id>
     <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;This post is the second part of a little blog series in which we are going through the complete process of implementing our own Kubernetes operator with Python. &lt;a href=&quot;/posts/k8s-operators-with-python-part-1.html&quot;&gt;Previously&lt;/a&gt; we had a look at two ways on how to register a CRD for our ExchangeRate resource. Based on this we can now start writing our controller that will query the requested currency exchange rate and make them available to our Pods and Jobs in the form of a ConfigMap.&lt;/p&gt;

&lt;h2 id=&quot;implementing-the-controller&quot;&gt;Implementing the controller&lt;/h2&gt;

&lt;p&gt;If you decided to create your CRD(s) using the Kubernetes API you can include and package this code together with your controller. This makes sure the resources your controller acts on, are definitely available in your cluster. Otherwise you’d need to make sure that your Kubernetes manifest is being applied before starting your controller which can be cumbersome. That’s the reason I prefer the API approach and will be going forward using it.&lt;/p&gt;

&lt;p&gt;The controller application itself is essentially an endless loop that constantly watches Kubernetes resources of a specific kind, in our case ExchangeRate objects. Upon an update (creation, modification, deletion) its business logic will react accordingly. This can be anything from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/external-dns&quot; target=&quot;_blank&quot;&gt;creating DNS entries&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jetstack/cert-manager&quot; target=&quot;_blank&quot;&gt;issuing TLS certificates&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;or fetching exchange rates from an API and storing the result in a ConfigMap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although this could be implemented from scratch there’s a great framework that does most of the heavy lifting called &lt;a href=&quot;https://kopf.readthedocs.io/en/latest/&quot; target=&quot;_blank&quot;&gt;Kopf&lt;/a&gt;. It allows you to almost entirely focus on implementing the business logic of your controller.&lt;/p&gt;

&lt;p&gt;Below you can see all the code that is necessary to watch for and react on a newly created ExchangeRate object.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kopf&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kopf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;operators.brennerm.github.io&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;v1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;exchangerates&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;An ExchangeRate object has been created: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Having this we can concentrate on:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;extracting the requested currency out of the ExchangeRate object
    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;currency&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;querying the current exchange rate for this currency
    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;exchange_rates_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;https://api.exchangeratesapi.io/latest?symbols=&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exchange_rates_url&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;rates&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;creating a new ConfigMap containing the exchange rate
    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CoreV1Api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_namespaced_config_map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
 &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;s&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;s&quot;&gt;&apos;rate&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So there you have the essential parts of the controller. All that is left is handling an update (= update the exchange rate if the currency changes) and deletion (= destroy the ConfigMap if the ExchangeRate object is deleted) of an ExchangeRate object. Updating is pretty much the same as the above code but instead of creating a ConfigMap you’ll patch the existing one using:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CoreV1Api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;patch_namespaced_config_map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Regarding handling the deletion we could choose the obvious way of implementing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kopf.on.delete&lt;/code&gt; handler and deleting the ConfigMap manually. The more elegant way IMO is making use of Kubernetes’ &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/&quot; target=&quot;_blank&quot;&gt;owner references&lt;/a&gt;. These allow to specify parent-child relationships between objects which will result in an automatic garbage collection of all children upon deleting the parent.&lt;/p&gt;

&lt;p&gt;And as we are even too lazy to implement this ourselves we’ll let Kopf take care of it by passing our ConfigMap data to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kopf.adopt&lt;/code&gt;. Next to a few other things this will set the owner reference of the ConfigMap to our ExchangeRate object.&lt;/p&gt;

&lt;p&gt;And that is all to create a simple “CRUD” controller application using Kopf. Let’s package and finally deploy it to our Kubernetes cluster.&lt;/p&gt;

&lt;h2 id=&quot;packaging-and-running-the-controller&quot;&gt;Packaging and running the controller&lt;/h2&gt;

&lt;p&gt;As an operator will, similar to any other application, run within a Pod, we need to package it as one of the Kubernetes supported image formats. I decided to go with the most popular way of building my image using a Dockerfile. Below you can find its content and a few comments that explain each step.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;FROM python:3.8-alpine
apk --update add gcc build-base # required to build some of the following pip packages
RUN pip install --no-cache-dir kopf kubernetes requests # install our dependencies
ADD exchangerates-operator.py / # copy our operator into the image
CMD kopf run /exchangerates-operator.py # start our operator on container creation
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The resulting image needs to be pushed to some registry your Kubernetes cluster has access to. Afterwards you can deploy the operator e.g. by using a Deployment manifest that could look like this.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# make sure to not have more than one replicas&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Recreate&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# make sure the old pod is being killed before the new pod is being created&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.brennerm.io/exchangerates-operator:latest&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are using RBAC ensure that your operator has the sufficient permissions to register CRDs, read ExchangeRate objects, create events and ConfigMaps. The according role could look like so:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ClusterRole&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates-operator&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiGroups&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;apiextensions.k8s.io&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;customresourcedefinitions&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;verbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;create&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiGroups&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;operators.brennerm.github.io&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exchangerates&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;verbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiGroups&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;configmaps&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;verbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;create,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;patch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;apiGroups&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;events&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;verbs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;create&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that’s done we are ready to try out our new operator. To do that we’re going to use the following ExchangeRate object.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# exchangerate.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;operators.brennerm.github.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ExchangeRate&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchange-rate-usd&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;USD&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apply it and a ConfigMap with the following content should appear pretty much instantly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; exchangerate.yml
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get configmaps
NAME                      DATA   AGE
exchange-rate-usd-j98bc   1      2s

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl describe configmaps exchange-rate-usd-j98bc
Name:         exchange-rate-usd-j98bc
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  &amp;lt;none&amp;gt;

Data
&lt;span class=&quot;o&quot;&gt;====&lt;/span&gt;
rate:
&lt;span class=&quot;nt&quot;&gt;----&lt;/span&gt;
1.1901
Events:  &amp;lt;none&amp;gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And there we have our working operator that takes care of registering a CRD on startup and starting the controller application that watches objects of a this resource kind. Have a look at the picture below to review all the relevant parts and processes.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/exchangerates-operator.svg&quot; alt=&quot;Overview of how our Exchange Rates operator works&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of how our Exchange Rates operator works
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Of course that’s a fairly minimal example but it should give you all the tools and knowledge to create much more complex operators.&lt;/p&gt;

&lt;p&gt;I pushed all the code and manifests into a &lt;a href=&quot;https://github.com/brennerm/exchangerates-operator&quot; target=&quot;_blank&quot;&gt;Git repository&lt;/a&gt; for you to see the operator as a whole. If you still run into issues or have open questions feel free to drop me a message. Hope you enjoyed that little guide. 👍&lt;/p&gt;

&lt;h2 id=&quot;update-19012021&quot;&gt;Update 19.01.2021&lt;/h2&gt;
&lt;p&gt;Nolar, aka Kopf’s current maintainer, &lt;a href=&quot;https://twitter.com/nolar/status/1351289223979143174?s=20&quot; target=&quot;_blank&quot;&gt;pointed out&lt;/a&gt; that Timers would be a good fit for this use case as well. They allow you to regularly trigger your controller no matter if there were changes on your objects. For our use case this would for example allow us to automatically pull and update the exchange rate every hour like so:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kopf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;operators.brennerm.github.io&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;v1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;exchangerates&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3600.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_exchange_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# update ConfigMap
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check &lt;a href=&quot;https://kopf.readthedocs.io/en/stable/timers/&quot; target=&quot;_blank&quot;&gt;the documentation&lt;/a&gt; for some further details.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Kubernetes operators with Python &#35;1&#58; Creating CRDs</title>
     <link href="https://shipit.dev/posts/k8s-operators-with-python-part-1.html"/>
     <updated>2021-01-13T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/k8s-operators-with-python-part-1</id>
     <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;A lot of the core processes happening in a Kubernetes cluster are following the so called &lt;a href=&quot;https://kubernetes.io/docs/concepts/architecture/controller/#controller-pattern&quot; target=&quot;_blank&quot;&gt;controller pattern&lt;/a&gt;. This pattern describes an ongoing monitoring of resources and reacting appropriately to bring the current state closer their desired state.&lt;/p&gt;

&lt;p&gt;A simple example is the relationship between the Deployment and Pod resources. When increasing the replica count in the Deployment object the number of Pods will be adjusted by the responsible controller.&lt;/p&gt;

&lt;p&gt;Operators are a special kind of controllers and a popular way of extending Kubernetes clusters. They consist of a controller application and domain specific custom Kubernetes resources (&lt;a href=&quot;https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/&quot; target=&quot;_blank&quot;&gt;CRDs&lt;/a&gt;). The controller is watching for changes on objects he’s responsible for and executes tasks according to his business logic.&lt;/p&gt;

&lt;p&gt;As a huge part of the K8s ecosystem is written in Golang it’s also the de facto standard language when writing operators. Today I wanna show you how to write your own operator using a more beginner friendly programming language like Python.&lt;/p&gt;

&lt;p&gt;To showcase the whole process we’ll create an operator that provides currency exchange rates through a &lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/configmap/&quot; target=&quot;_blank&quot;&gt;ConfigMap&lt;/a&gt; object. These can then be referred to by our Pods and Jobs. The rates will be fetched from the &lt;a href=&quot;https://exchangeratesapi.io/&quot; target=&quot;_blank&quot;&gt;Exchange Rates API&lt;/a&gt; and to tell the operator which rates to pull, we’ll register our own CRD. The following diagram will provide an overview of all components and their relations.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/exchangerates-operator.svg&quot; alt=&quot;Overview of how our Exchange Rates operator works&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of how our Exchange Rates operator works
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We’ll take care of implementing the controller application in the next part of this little blog series. At first we are going to start with defining our custom ExchangeRate resource by creating a CRD.&lt;/p&gt;

&lt;h2 id=&quot;creating-the-crd&quot;&gt;Creating the CRD&lt;/h2&gt;

&lt;p&gt;Creating a CRD is nothing else than registering a new Kubernetes resource type with a fixed set of fields and their data types. To accomplish that we’ll have a look at two different ways.&lt;/p&gt;

&lt;h3 id=&quot;using-a-kubernetes-manifest&quot;&gt;Using a Kubernetes manifest&lt;/h3&gt;

&lt;p&gt;Every Kubernetes resource can be created using a manifest and as a CRD is a resource itself, it is no exception.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# crd.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apiextensions.k8s.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CustomResourceDefinition&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates.operators.brennerm.github.io&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;operators.brennerm.github.io&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# it&apos;s possible to provide multiple versions of a CRD&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;served&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# it&apos;s possible to disable a CRD&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# there can be multiple versions but only one can be used to store the objects&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;openAPIV3Schema&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
                  &lt;span class=&quot;na&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CAD&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CHF&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GBP&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;JPY&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;PLN&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;USD&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# we&apos;ll limit the valid currencies to these&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Namespaced&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# resources can be namespaced or available for the whole cluster&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;plural&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerates&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;singular&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchangerate&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ExchangeRate&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# this name is being used in manifests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;shortNames&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# these short names can be used in the CLI, e.g. kubectl get er&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;er&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The schema definition follows the &lt;a href=&quot;https://swagger.io/docs/specification/data-models/&quot; target=&quot;_blank&quot;&gt;OpenAPI v3 specification&lt;/a&gt; which can be used to define various data types and nested structures. After applying this file, e.g. with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply -f crd.yml&lt;/code&gt; we are ready to create our first &lt;em&gt;ExchangeRate&lt;/em&gt; object using the following manifest.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# exchangerate.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;operators.brennerm.github.io/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ExchangeRate&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exchange-rate-usd&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;USD&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using-the-kubernetes-api&quot;&gt;Using the Kubernetes API&lt;/h3&gt;

&lt;p&gt;Instead of using a manifest we can also register our CRD by using the Kubernetes API. Conveniently there’s an &lt;a href=&quot;https://github.com/kubernetes-client/python&quot; target=&quot;_blank&quot;&gt;official Python client&lt;/a&gt; that we’ll use for this purpose.&lt;/p&gt;

&lt;p&gt;Below you can find the definition of our CRD as a Python object. You’ll see a lot of similarities to the above manifest.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kubernetes.client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kubernetes.config&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_config&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;exchange_rate_crd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1CustomResourceDefinition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;apiextensions.k8s.io/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CustomResourceDefinition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1ObjectMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exchangerates.operators.brennerm.github.io&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1CustomResourceDefinitionSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;operators.brennerm.github.io&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1CustomResourceDefinitionVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;served&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1CustomResourceValidation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;open_apiv3_schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1JSONSchemaProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1JSONSchemaProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;currency&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1JSONSchemaProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CAD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CHF&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GBP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;JPY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PLN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;USD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Namespaced&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V1CustomResourceDefinitionNames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;plural&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exchangerates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;singular&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exchangerate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ExchangeRate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;short_names&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;er&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Afterwards we’ll need to load our &lt;em&gt;kubeconfig&lt;/em&gt; and call the API endpoint for finally creating the CRD.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;k8s_config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load_kube_config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;api_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiextensionsV1Api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;api_instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_custom_resource_definition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exchange_rate_crd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiException&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;409&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# if the CRD already exists the K8s API will respond with a 409 Conflict
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CRD already exists&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;

&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load_kube_config&lt;/code&gt; reads the access credentials to your K8s cluster from your local environment (most likely &lt;em&gt;~/.kube/config&lt;/em&gt;). If that’s not what you intend, the library also provides manually setting the configuration or loading it from within an &lt;a href=&quot;https://github.com/kubernetes-client/python-base/blob/b0021104307c99bac5b2a7e353df21d864f85809/config/incluster_config.py#L112&quot; target=&quot;_blank&quot;&gt;in-cluster environment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No matter how you end up creating your CRD, executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl api-resources&lt;/code&gt; should list your new resource type if you’ve done everything correctly.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl api-resources
NAME                              SHORTNAMES   APIGROUP                       NAMESPACED   KIND
...
exchangerates                     er           operators.brennerm.github.io   &lt;span class=&quot;nb&quot;&gt;true         &lt;/span&gt;ExchangeRate
...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Personally I prefer this approach compared to using a CRD manifest. I’ll talk about the reason in &lt;a href=&quot;/posts/k8s-operators-with-python-part-2.html&quot;&gt;the second part&lt;/a&gt; of this blog series in which we’ll implement the controller application.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Github Actions workflow for merged/closed PRs</title>
     <link href="https://shipit.dev/posts/trigger-github-actions-on-pr-close.html"/>
     <updated>2020-12-27T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/trigger-github-actions-on-pr-close</id>
     <content type="html">&lt;p&gt;Lately I made some investigations on different events that you can use to trigger Github Actions workflows. I was especially interested in the pull request events as executing some clean up task as soon as a PR is merged was one of my goals.&lt;/p&gt;

&lt;p&gt;Going through the &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request&quot; target=&quot;_blank&quot;&gt;list of available events&lt;/a&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;closed&lt;/code&gt; turned out to be what I was looking for.
Additionally I added the requirement to make a distinction between the following two cases when closing a pull request.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;merged - the PRs’ changes have been merged into the target branch&lt;/li&gt;
  &lt;li&gt;closed - the PR has been closed without merging its changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turns out the &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#pull_request&quot; target=&quot;_blank&quot;&gt;event received&lt;/a&gt; contains the pull request object which itself contains a lot of additional information such as whether the PR has been merged or not. Thus the following configuration is what I came up with.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Close Pull Request&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# only trigger on pull request closed events&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;closed&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;merge_job&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# this job will only run if the PR has been merged&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github.event.pull_request.merged == &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo PR #${{ github.event.number }} has been merged&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;close_job&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# this job will only run if the PR has been closed without being merged&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github.event.pull_request.merged == &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo PR #${{ github.event.number }} has been closed without being merged&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To see the above workflow in action check out this little &lt;a href=&quot;https://github.com/brennerm/github-actions-pr-close-showcase&quot; target=&quot;_blank&quot;&gt;showcase repo&lt;/a&gt; I created. Feel free to use it as a starting point.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Screwing up remote access to dozens of servers within seconds</title>
     <link href="https://shipit.dev/posts/screwing-up-remote-access-to-servers.html"/>
     <updated>2020-12-22T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/screwing-up-remote-access-to-servers</id>
     <content type="html">&lt;h2 id=&quot;the-power-of-infrastructure-as-code&quot;&gt;the power of infrastructure as code&lt;/h2&gt;

&lt;p&gt;Back in the day (must have been around 2016) my team and I were using Ansible to provision our infrastructure. It’s a tool to configure servers in an automated and reproducible manner. It helped us to setup web servers, databases and all kinds of services with very little preconditions.&lt;/p&gt;

&lt;p&gt;There’s no central server that you need to setup initially and keep running. The client machines don’t need any kind of custom agent. All that is required is a Python interpreter and remote access using SSH which both are baked into modern OS’ anyway.&lt;/p&gt;

&lt;p&gt;Additionally Ansible allows to create separate groups of servers and apply different actions on them as well as define tasks that will be executed for every server. E.g. increasing a version of nginx should only be applied to web servers while changing the address of the DNS or NTP server is something that is relevant for all hosts.&lt;/p&gt;

&lt;p&gt;Due to parallel execution, Ansible is able to rollout these changes to a huge amount of clients in a very short amount of time. And with this great power comes great responsibility…&lt;/p&gt;

&lt;h2 id=&quot;the-day-i-screwed-up&quot;&gt;the day I screwed up&lt;/h2&gt;

&lt;p&gt;As we used Ansible to make system level changes root permissions were almost always required. Hence we used the system’s root user which is debatable from my todays’ point of view. Anyway securing remote access to this user was very important.&lt;/p&gt;

&lt;p&gt;SSH, among others, supports authentication using a password or a SSL key pair. We used the latter as it is more secure (and convenient) but logging into the root account using a password was still an option which we wanted to actively disable.&lt;/p&gt;

&lt;p&gt;Sshd, the software realizing the remote access to the server, provides an configuration parameter called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PermitRootLogin&lt;/code&gt; with the following options:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yes&lt;/code&gt; - allows root login with all authentication methods&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no&lt;/code&gt; - disables root login using SSH completely&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prohibit-password&lt;/code&gt; - disables root login using password authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Number 3 it is, right? So I sat down and wrote the Ansible “code” (it’s actually YAML). As I am an experienced engineer, so I thought, testing my changes before deploying them was obvious. I spun up a new machine running Ubuntu 16.04, deployed my change, verified the config, tried logging in using a password which failed as expected… everything was looking great.&lt;/p&gt;

&lt;p&gt;There I was, confidently typing the Ansible command into my shell (it was actually executed on our CI server, but I’m trying to be dramatic here 😉), targeting all of our servers, hitting Enter and seeing the usual endless amounts of logs flying by.&lt;/p&gt;

&lt;p&gt;And then boom…around 50 servers responded with an error, oddly all of them running Ubuntu 14.04. Turns out that Ubuntu 14.04. came with an older version of sshd which, instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prohibit-password&lt;/code&gt;, expected the value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;without-password&lt;/code&gt; for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PermitRootLogin&lt;/code&gt; config.&lt;/p&gt;

&lt;p&gt;As already said, when using Ansible 50% of what you need is SSH access but SSH access is also what you need a 100%. So take a guess, what happens to an sshd service that encounters an unknown configuration parameter during its startup? Correct, &lt;strong&gt;there’s no startup&lt;/strong&gt;. All there is is an error message that never reached the display of my PC as I screwed up the SSH connection.&lt;/p&gt;

&lt;h2 id=&quot;the-learning&quot;&gt;the learning&lt;/h2&gt;

&lt;p&gt;Luckily for me all of these servers were LXC containers and no physical machines. So fixing the sshd config was just a matter of crafting a script with the correct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lxc-execute&lt;/code&gt; (same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker exec&lt;/code&gt; if that’s more familiar to you) command. Alright fire extinguished, coming to the even more important part. What could I have done to avoid this situation in the first place?&lt;/p&gt;

&lt;p&gt;In an ideal world replicating the whole setup in a staging environment and applying my changes there would be the way to go. You and me know that in reality this is mostly not achievable due to various reasons, e.g. limited computing capacities.&lt;/p&gt;

&lt;p&gt;Instead of copying the whole setup I could have tested my changes against all base images of our containers, e.g. Ubuntu 14.04 additionally to 16.04. This would have revealed this particular issue but may not have helped in a different case.&lt;/p&gt;

&lt;p&gt;Another out of the box feature of Ansible that helps preventing making erroneous configurations to all of your hosts at once is called &lt;a href=&quot;https://docs.ansible.com/ansible/latest/user_guide/guide_rolling_upgrade.html#the-rolling-upgrade&quot; target=&quot;_blank&quot;&gt;rolling updates&lt;/a&gt;. By passing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serial&lt;/code&gt; parameter, Ansible will apply your changes to batches of the given size. If it fails for one batch the execution will stop, leaving the remaining hosts intact.&lt;/p&gt;

&lt;p&gt;All in all I’m happy that I made this mistake for a service with no impact to the end user at all. A web or database service that is not starting would have been much worse.&lt;/p&gt;

&lt;p&gt;And that’s my how I screwed up remote access to dozens of servers in seconds. Hopefully you had a good laugh from my postmortem story and ideally learned how to prevent something like this from happening to you. 👍&lt;/p&gt;

&lt;h2 id=&quot;update-22122020&quot;&gt;Update 22.12.2020&lt;/h2&gt;

&lt;p&gt;Another safety measure several users on &lt;a href=&quot;https://www.reddit.com/r/ansible/comments/ki2u23/screwing_up_remote_access_to_dozens_of_servers/&quot; target=&quot;_blank&quot;&gt;Reddit&lt;/a&gt; recommended, is to validate the config after copying it onto the server. In case of sshd this would look like so:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upload sshd config file&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sshd_config&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/ssh/sshd_config&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/usr/sbin/sshd&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-T&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%s&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-T&lt;/code&gt; tells sshd to validate the config, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f&lt;/code&gt; is used to pass its path and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%s&lt;/code&gt; will be replaced by Ansible to point to the temporary config file.&lt;/p&gt;

&lt;p&gt;This is supported by multiple Ansible modules, like &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html&quot; target=&quot;_blank&quot;&gt;copy&lt;/a&gt; or &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/template_module.html&quot; target=&quot;_blank&quot;&gt;template&lt;/a&gt; and will prevent copying the faulty config.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Kubernetes Overview Diagrams</title>
     <link href="https://shipit.dev/posts/kubernetes-overview-diagrams.html"/>
     <updated>2020-12-01T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/kubernetes-overview-diagrams</id>
     <content type="html">&lt;p&gt;I started creating and sharing overviews of various Kubernetes objects on my &lt;a href=&quot;https://twitter.com/__brennerm&quot;&gt;Twitter&lt;/a&gt;. Some people requested to put them in a more convenient place for later use. This page is the result.&lt;/p&gt;

&lt;p&gt;Feel free to use them for yourself. Giving credit is very much appreciated. Have fun (y)&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-architecture.svg&quot; alt=&quot;Overview of Kubernetes&apos; basic architecture&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes&apos; basic architecture
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;workload&quot;&gt;Workload&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-workloads.svg&quot; alt=&quot;Overview of Kubernetes Workload objects&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes Workload objects
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;networking&quot;&gt;Networking&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-network.svg&quot; alt=&quot;Overview of Kubernetes Networking objects&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes Networking objects
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;storage&quot;&gt;Storage&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-storage.svg&quot; alt=&quot;Overview of Kubernetes Storage objects&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes Storage objects
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;rbac&quot;&gt;RBAC&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-rbac.svg&quot; alt=&quot;Overview of Kubernetes RBAC objects&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes RBAC objects
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;resource-requests-and-limits&quot;&gt;Resource requests and limits&lt;/h2&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/k8s-resources.svg&quot; alt=&quot;Overview of Kubernetes resources requests and limits&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of Kubernetes resources requests and limits
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Setting up a load balancer with failover support in Azure</title>
     <link href="https://shipit.dev/posts/azure-load-balancer-with-failover.html"/>
     <updated>2020-11-19T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/azure-load-balancer-with-failover</id>
     <content type="html">&lt;h2 id=&quot;the-cloud-solves-all-your-problems&quot;&gt;the cloud solves all your problems…&lt;/h2&gt;
&lt;p&gt;Lately I was in need of a load balancer solution supporting the classic active-passive scenario. In other words, forward (ideally TCP) traffic to server A. If server A is not reachable reroute to server B. Being a fairly simple and common problem it actually took me quite some time to find a solution I was happy with.&lt;/p&gt;

&lt;p&gt;Initially I was very confident that the Azure Load Balancer was exactly what I was looking for. Finding out that it does only support Azure internal endpoints made my anticipation decline. Next stop: Azure Application Gateway!&lt;/p&gt;

&lt;p&gt;Being already sceptical cause its kind of an overkill service for such a simple problem I sat down and created an instance anyway. Seeing external targets being an option when configuring the backend pools was good. HTTP and HTTPS being the only supported protocols wasn’t great. Finding out that App Gateway is not capable of my desired failover scenario took it off if my option list.&lt;/p&gt;

&lt;p&gt;There I was, sitting in front of cloud resources having terabytes of memory but not being able to realize a simple failover use case. Just before coming to the conclusion of setting up and managing a few HA proxy VMs by myself I stumbled upon a service I have never heard of… Azure Traffic Manager.&lt;/p&gt;

&lt;p&gt;After reading about it for a few minutes I was pretty sure that this is what a was looking for. Let’s see if I was right.&lt;/p&gt;

&lt;h2 id=&quot;but-does-it&quot;&gt;…but does it?&lt;/h2&gt;

&lt;p&gt;Azure Traffic Manager’s functionality is based on DNS. It acts as an DNS alias that is being rewritten depending on the load balancing algorithm, of which it supports the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Priority - provide multiple endpoints, the one that is available and has the highest priority receives all of the traffic&lt;/li&gt;
  &lt;li&gt;Weighted - allows to set a weight for each endpoint and distributing the traffic based on it&lt;/li&gt;
  &lt;li&gt;Performance - routes the request to the fastest endpoint based on where the request originates from&lt;/li&gt;
  &lt;li&gt;Geographic - routes the request to the closest endpoint based on where the request originates from&lt;/li&gt;
  &lt;li&gt;Multivalue - returns all available endpoints&lt;/li&gt;
  &lt;li&gt;Subnet - allows to map the requesting source IP subnet to a fixed endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For showcasing the general functionality we’ll setup a failover functionality for two DNS servers.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/dns-failover.svg&quot; alt=&quot;Overview of my DNS server failover showcase scenario&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of my DNS server failover showcase scenario
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Terraform code for setting up the corresponding Traffic Manager profile can be found below.&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;azurerm_traffic_manager_profile&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager-profile&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;                   &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager-profile&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;resource_group_name&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;traffic_routing_method&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Priority&quot;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;dns_config&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;relative_name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager-profile&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the host name for the FQDN of this traffic manager profile&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;ttl&lt;/span&gt;           &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the TTL for the DNS alias&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;monitor_config&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the config for monitoring the endpoints for availability and latency&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;protocol&lt;/span&gt;                     &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;tcp&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the protocol to monitor with&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;                         &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;53&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the port to monitor&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;interval_in_seconds&lt;/span&gt;          &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the time between probes&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;timeout_in_seconds&lt;/span&gt;           &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the time the probing agent waits for a response before considering a check as failed&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;tolerated_number_of_failures&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# the number of failing health probes that will be tolerated before marking the endpoint as unhealthy&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For our failover use case the &lt;em&gt;Priority&lt;/em&gt; method is the one we are going with. We’ll provide two endpoints, a primary and a secondary and the former will have a higher priority (=a lower value). In terms of Terraform code this configuration will look like this.&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;azurerm_traffic_manager_endpoint&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;primary&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;                &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudflare&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;resource_group_name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;profile_name&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager-profile&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;              &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1.1.1.1&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;                &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;externalEndpoints&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;priority&lt;/span&gt;            &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;azurerm_traffic_manager_endpoint&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;secondary&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;                &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;google&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;resource_group_name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;profile_name&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-traffic-manager-profile&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;              &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;8.8.8.8&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;                &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;externalEndpoints&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;priority&lt;/span&gt;            &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To test our configuration we simply need to constantly send DNS requests to our Traffic Manager instance and meanwhile shutdown the Cloudflare DNS server. ;) Afterwards our DNS requests should be rerouted the Google’s DNS server.&lt;/p&gt;

&lt;p&gt;To not interrupt DNS queries all over the world I decided to take the less interfering path of just disabling the &lt;em&gt;cloudflare&lt;/em&gt; endpoint like so:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;az network traffic-manager endpoint update &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; my-traffic-manager &lt;span class=&quot;nt&quot;&gt;--profile-name&lt;/span&gt; my-traffic-manager-profile &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; cloudflare &lt;span class=&quot;nt&quot;&gt;--endpoint-status&lt;/span&gt; Disabled
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This simulates an outage of our primary endpoint and after a short amount of time the following happens.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do &lt;/span&gt;host shipit.dev my-traffic-manager-profile.trafficmanager.net&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
...
Using domain server:
Name: my-traffic-manager-profile.trafficmanager.net
Address: 1.1.1.1#53
Aliases:

shipit.dev has address 185.199.109.153
shipit.dev has address 185.199.110.153
shipit.dev has address 185.199.108.153
shipit.dev has address 185.199.111.153

Using domain server:
Name: my-traffic-manager-profile.trafficmanager.net
Address: 8.8.8.8#53 &lt;span class=&quot;c&quot;&gt;# notice that the IP changed&lt;/span&gt;
Aliases:

shipit.dev has address 185.199.109.153
shipit.dev has address 185.199.111.153
shipit.dev has address 185.199.110.153
shipit.dev has address 185.199.108.153
...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see the failover to Google’s DNS server works just as expected. And if we re-enable the &lt;em&gt;cloudflare&lt;/em&gt; endpoint, requests will be routed back to it again automatically.&lt;/p&gt;

&lt;p&gt;This behavior can be accomplished for any kind of protocol running over TCP. Regarding HTTP and HTTPS, Traffic Manager even supports checking a specific path and matching given HTTP status codes to determine whether the endpoint is healthy.&lt;/p&gt;

&lt;p&gt;And there you have it. A very easy to configure load balancer with failover support that supports targets within and outside of the Azure cloud.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Setting up an EKS cluster with IAM/IRSA integration</title>
     <link href="https://shipit.dev/posts/setting-up-eks-with-irsa-using-terraform.html"/>
     <updated>2020-11-15T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/setting-up-eks-with-irsa-using-terraform</id>
     <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;AWS’ IAM service is a powerful system to provide fine-grained control over AWS resources. Additionally it is integrated into several AWS services and EKS is no exception. Next to the cluster role, &lt;a href=&quot;https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/&quot;&gt;AWS introduced 2019&lt;/a&gt; the concept of IRSA, which stands for IAM Roles for Service Accounts.&lt;/p&gt;

&lt;p&gt;Together with Kubernetes’ RBAC system it allows to assign IAM role capabilities to K8s computing resources like &lt;em&gt;Pods&lt;/em&gt; and &lt;em&gt;Jobs&lt;/em&gt;. A simple use case you can imagine is allowing a Pod to write to a S3 bucket. After reading through this blog post you will understand how to create an EKS cluster using Terraform with IRSA support and how to make use of it.&lt;/p&gt;

&lt;h2 id=&quot;preparing-the-vpc&quot;&gt;Preparing the VPC&lt;/h2&gt;
&lt;p&gt;Before creating an EKS cluster you need to set up a VPC network that your data and control plane traffic can go through. It will also define whether your Kubernetes API endpoint will be accessible publicly or only from within a private network.&lt;/p&gt;

&lt;p&gt;Depending on your requirements the VPC configuration can be more or less complex. If you don’t want to do anything too crazy I suggest to use &lt;a href=&quot;https://github.com/terraform-aws-modules/terraform-aws-vpc&quot;&gt;&lt;em&gt;aws-vpc&lt;/em&gt;&lt;/a&gt; module. It provides a nice abstraction layer for the AWS VPC Terraform resources that are being used under the hood. Below you can find an example VPC configuration that can act as a starting point for your EKS.&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vpc&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;terraform-aws-modules/vpc/aws&quot;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;                 &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-vpc&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cidr&lt;/span&gt;                 &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;10.0.0.0/16&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;azs&lt;/span&gt;                  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eu-central-1a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eu-central-1b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eu-central-1c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;public_subnets&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;10.0.1.0/24&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;10.0.2.0/24&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;10.0.3.0/24&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;enable_dns_support&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Be aware that an EKS cluster needs at least two subnets in different availability zones. Enabling the DNS related flags is necessary to allow the worker nodes to find and register themselves at the API server.&lt;/p&gt;

&lt;h2 id=&quot;creating-the-eks-cluster&quot;&gt;Creating the EKS cluster&lt;/h2&gt;
&lt;p&gt;Similar to the VPC, I recommend you to use the &lt;a href=&quot;https://github.com/terraform-aws-modules/terraform-aws-eks&quot;&gt;&lt;em&gt;aws-eks&lt;/em&gt;&lt;/a&gt; Terraform module if your EKS setup is not too far away from the ordinary.
Below you can find an example Terraform code snippet that uses the previously discussed VPC.&lt;/p&gt;
&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cluster&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;          &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;terraform-aws-modules/eks/aws&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cluster_name&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cluster&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cluster_version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1.18&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;vpc_id&lt;/span&gt;          &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vpc_id&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;subnets&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vpc_subnet_ids&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;enable_irsa&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;worker_groups&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;instance_type&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;t3.medium&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;asg_max_size&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;enable_irsa&lt;/em&gt; flag will lead to the OIDC (OpenID Connect) provider being created. Additionally we will define a Terraform output that contains its ARN (Amazon Resource Name) which will be used in the next step.&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;oidc_provider_arn&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cluster&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oidc_provider_arn&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And that’s all you need to continue with the next step.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-service-account-associated-with-an-iam-role&quot;&gt;Creating a service account associated with an IAM role&lt;/h2&gt;
&lt;p&gt;In this example we are going to create a service account that has full access to all of your S3 buckets. This can be easily changed by adjusting the policies that you attach to your IAM role.&lt;/p&gt;

&lt;p&gt;To start with we need to define a few variables.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;ROLE_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;s3-writer &lt;span class=&quot;c&quot;&gt;# the name of your IAM role&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SERVICE_ACCOUNT_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;s3-writer &lt;span class=&quot;c&quot;&gt;# the name of your service account name&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SERVICE_ACCOUNT_NAMESPACE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;default &lt;span class=&quot;c&quot;&gt;# the namespace for your service account&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PROVIDER_ARN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;terraform output &lt;span class=&quot;nt&quot;&gt;-json&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; .oidc_provider_arn.value&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# the ARN of your OIDC provider&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ISSUER_HOSTPATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;aws eks describe-cluster &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; cluster &lt;span class=&quot;nt&quot;&gt;--query&lt;/span&gt; cluster.identity.oidc.issuer &lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt; text | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 3- &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# the host path of your OIDC issuer&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The most important step is defining the correct assume policy for your IAM role.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; assume-policy.json &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Federated&quot;: &quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PROVIDER_ARN&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: {
          &quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ISSUER_HOSTPATH&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;:sub&quot;: &quot;system:serviceaccount:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SERVICE_ACCOUNT_NAMESPACE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SERVICE_ACCOUNT_NAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;
        }
      }
    }
  ]
}
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will allow the service account to switch to your role using the &lt;em&gt;AssumeRoleWithWebIdentity&lt;/em&gt; command.&lt;/p&gt;

&lt;p&gt;Afterwards you can create the new role using the above assume policy and attach your desired policies to it.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws iam create-role &lt;span class=&quot;nt&quot;&gt;--role-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ROLE_NAME&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--assume-role-policy-document&lt;/span&gt; file://assume-policy.json
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws iam update-assume-role-policy &lt;span class=&quot;nt&quot;&gt;--role-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ROLE_NAME&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--policy-document&lt;/span&gt; file://assume-policy.json
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws iam attach-role-policy &lt;span class=&quot;nt&quot;&gt;--role-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ROLE_NAME&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AmazonS3FullAccess
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last step is to create the Kubernetes Service Account and annotate it with the role ARN.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl create sa &lt;span class=&quot;nv&quot;&gt;$SERVICE_ACCOUNT_NAME&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ S3_ROLE_ARN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;aws iam get-role &lt;span class=&quot;nt&quot;&gt;--role-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ROLE_NAME&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--query&lt;/span&gt; Role.Arn &lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt; text&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl annotate sa &lt;span class=&quot;nv&quot;&gt;$SERVICE_ACCOUNT_NAME&lt;/span&gt; eks.amazonaws.com/role-arn&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$S3_ROLE_ARN&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-the-service-account-in-your-application&quot;&gt;Using the service account in your application&lt;/h2&gt;
&lt;p&gt;Assigning the newly created service account to your application only requires adding a single line to your &lt;em&gt;Deployment&lt;/em&gt; or &lt;em&gt;Job&lt;/em&gt; manifest.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;serviceAccountName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3-writer&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;myapp:latest&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;myapp&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will result in the &lt;a href=&quot;https://github.com/aws/amazon-eks-pod-identity-webhook/&quot;&gt;EKS Pod Identity Webhook&lt;/a&gt; injecting some environment variables and a volume mount into each of your pods that contain the access credentials for your IAM role. If you use a recent version of the &lt;a href=&quot;https://aws.amazon.com/tools/&quot;&gt;AWS SDK&lt;/a&gt; these will be picked up automatically when creating a new session and you are good to go.&lt;/p&gt;

&lt;p&gt;As a conclusion you can find an overview of all components and processes being part of the IRSA concept below.&lt;/p&gt;
&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;https://d2908q01vomqb2.cloudfront.net/ca3512f4dfa95a03169c5a670a4c91a19b3077b4/2019/08/12/irp-eks-setup-1024x1015.png&quot; alt=&quot;Overview of IRSA components and processes&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    Overview of IRSA components and processes
    
    / &lt;a href=&quot;https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;image source&lt;/a&gt;
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For further details check out &lt;a href=&quot;https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/&quot;&gt;the official AWS blog post about IRSA&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Integrating cdk8s with Argo CD</title>
     <link href="https://shipit.dev/posts/integrating-cdk8s-with-argocd.html"/>
     <updated>2020-05-04T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/integrating-cdk8s-with-argocd</id>
     <content type="html">&lt;p&gt;After having a look at &lt;a href=&quot;integrating-cdk8s-with-flux.html&quot;&gt;how to integrate cdk8s with Flux&lt;/a&gt; I was asked whether I could have a look at &lt;a href=&quot;https://argoproj.github.io/argo-cd/&quot;&gt;Argo CD&lt;/a&gt; as well. So without further ado, let’s integrate cdk8s with Argo CD.&lt;/p&gt;

&lt;h2 id=&quot;argo-cd&quot;&gt;Argo CD&lt;/h2&gt;

&lt;p&gt;But at first, what is Argo CD? Similar to Flux it realizes Kubernetes Manifest deployments by following the GitOps paradigm. This states that the desired condition of your system should be kept in one or more Git repositories. Tools like Argo CD take care of keeping your system (Kubernetes in this case) and this state in sync. In its simplest form think of a repository containing a single Kubernetes Manifest that defines a Deployment and a Service. Argo CD will make sure to deploy them to your cluster and continue to do so for any new change.&lt;/p&gt;

&lt;p&gt;To control Argo CD it ships with a CLI tool and a really nice web UI, that allows you to check for the status of your deployments in real time. Both allow you to create new applications or setup new cluster connections. The latter allow you to use the multi cluster functionality to orchestrate deployments over several clusters with a single instance of Argo CD. Setting one up would be next step from here.&lt;/p&gt;

&lt;h2 id=&quot;preparation&quot;&gt;Preparation&lt;/h2&gt;

&lt;p&gt;If you want to follow along you gonna need a running Argo CD instance. Feel free to use &lt;a href=&quot;https://gitlab.com/snippets/1967592&quot;&gt;my snippet&lt;/a&gt; to set one up within 5 minutes. Otherwise go through the official &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/getting_started/&quot;&gt;Getting Started&lt;/a&gt; guide.&lt;/p&gt;

&lt;p&gt;In case you are not familiar with cdk8s make sure to check out &lt;a href=&quot;integrating-cdk8s-with-flux.html#cdk8s&quot;&gt;this little rundown&lt;/a&gt; or &lt;a href=&quot;cdk8s-the-future-of-k8s-application-deployments.html&quot;&gt;my dedicated blog post&lt;/a&gt; that goes into more detail. Otherwise let’s dive right into the action.&lt;/p&gt;

&lt;p&gt;At first we need some cdk8s code that we can deploy later on. We are going to use the &lt;a href=&quot;https://github.com/argoproj/argocd-example-apps&quot;&gt;argocd-example-apps&lt;/a&gt; repository as a starting point. It contains the same configuration in different formats, like plain K8s manifests, a Helm Chart or kustomize files all deploying a simple guestbook application consisting of one Deployment and one Service. We are going to put our code into a new folder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk8s-guestbook&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As with every cdk8s application we start by executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk8s init python-app&lt;/code&gt;. I’m going to use Python but feel free to go with TypeScript. Afterwards we define the Deployment and Service objects in the &lt;em&gt;main.py&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;guestbook-ui&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;service&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServiceSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;LoadBalancer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServicePort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IntOrString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))],&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;deployment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                 &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeploymentSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LabelSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodTemplateSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ObjectMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                         &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;guestbook-ui&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                         &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;gcr.io/heptio-images/ks-guestbook-demo:0.2&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                         &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContainerPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])]))))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that’s done we push the code into a repo that is preferably publicly accessible. Otherwise we need to pass ArgoCD the credentials to the repo later on. Afterwards we can continue with the integration.&lt;/p&gt;

&lt;h2 id=&quot;integration&quot;&gt;Integration&lt;/h2&gt;

&lt;p&gt;Currently cdk8s is not supported by Argo CD out-of-the-box. To be able to use it we need to register cdk8s as a custom config management plugin. This works by simply creating/updating the &lt;em&gt;argocd-cm&lt;/em&gt; Kubernetes ConfigMap with something like the following content:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;configManagementPlugins&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;- name: cdk8s # the name of the plugin that we&apos;ll later use to reference it&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;init: # some optional preprocessing commands&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;command: [&quot;bash&quot;]&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;args: [&quot;-c&quot;, &quot;pipenv install &amp;amp;&amp;amp; cdk8s import -l python &amp;amp;&amp;amp; cdk8s synth&quot;] # making sure everything is installed and generating the K8s manifest(s)&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;generate: # the output of this command will be deployed onto the target cluster&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;command: [&quot;bash&quot;]&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;args: [&quot;-c&quot;, &quot;cat dist/*&quot;] # printing the generated Kubernetes manifests&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ConfigMap&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;argocd-cm&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;argocd&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;argocd-cm&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;argocd&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selfLink&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/api/v1/namespaces/argocd/configmaps/argocd-cm&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apply this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply -f config.yml&lt;/code&gt; and our plugin is ready to use.&lt;/p&gt;

&lt;p&gt;So let’s try it out. I’m going to use the Argo CD CLI tool but creating the application using the web UI will work as well. After issuing this command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;argocd app create guestbook &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# creating an application called guestbook&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--repo&lt;/span&gt; https://github.com/brennerm/argocd-example-apps.git &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the URL of our repo&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--path&lt;/span&gt; cdk8s-guestbook &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the path to the folder containing our config&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--dest-server&lt;/span&gt; https://kubernetes.default.svc &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the cluster we want to deploy to&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--dest-namespace&lt;/span&gt; default &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the namespace we want to deploy to&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--sync-policy&lt;/span&gt; automated &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# enabling automatic sync of changes in the repo&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;--config-management-plugin&lt;/span&gt; cdk8s &lt;span class=&quot;c&quot;&gt;# make sure to use our cdk8s plugin&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;we end up with the following error:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;FATA[0006] rpc error: code = InvalidArgument desc = application spec is invalid: InvalidSpecError: Unable to generate manifests in cdk8s-guestbook: rpc error: code = Unknown desc = ‘bash -c pipenv install &amp;amp;&amp;amp; cdk8s import -l python &amp;amp;&amp;amp; cdk8s synth’ failed exit status 127: bash: pipenv: command not found&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having some background knowledge of Argo CD’s internals make this issue somewhat predictable. Each config management plugin is being executed in a component called the &lt;em&gt;argocd-repo-server&lt;/em&gt;. To make our custom plugin work we also need to make sure that the tools we use are available in this environment. In our case these are &lt;em&gt;pipenv&lt;/em&gt; and &lt;em&gt;cdk8s&lt;/em&gt;. The proposed solutions are the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;using volume mounts containing the necessary binaries&lt;/li&gt;
  &lt;li&gt;providing a custom image for the &lt;em&gt;argocd-repo-server&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve read my last post you know that Flux has the exact same problem and that I’m pretty disappointed by these options. But that’s what we have to work with. I decided to go with the custom image as it appears easier to me. Below you can find my Dockerfile adding the missing binaries.&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; argoproj/argocd:latest&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; root&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;        curl &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;        python3-pip &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    apt-get clean &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    pip3 &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pipenv

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sS&lt;/span&gt; https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;deb https://dl.yarnpkg.com/debian/ stable main&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/yarn.list
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; yarn
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;yarn global add npm cdk8s-cli

&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; argocd&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After building this Dockerfile and pushing the resulting image to a Docker registry of your choice (pro tip: if you are working with &lt;a href=&quot;https://kind.sigs.k8s.io/&quot;&gt;kind&lt;/a&gt; use the really nice &lt;a href=&quot;https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster&quot;&gt;load feature&lt;/a&gt;) we need to update the &lt;em&gt;argocd-repo-server&lt;/em&gt; Kubernetes Deployment to use the new image, e.g. with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl edit -n argocd deployments.apps argocd-repo-server&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we’ve made sure that the &lt;em&gt;argocd-repo-server&lt;/em&gt; pod has been recreated with the new image we can give creating our application a second try. This time everything should work and we’ll end up with the &lt;em&gt;guestbook&lt;/em&gt; pod being started.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get pods
NAME                                                   READY   STATUS              RESTARTS   AGE
cdk8s-guestbook-deployment-967cec91-65b878495d-jcczj   0/1     ContainerCreating   0          16s
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make sure Argo CD is properly syncing changes let’s set the &lt;em&gt;replicaCount&lt;/em&gt; in the &lt;em&gt;main.py&lt;/em&gt; to 2, push the change and et voila:&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/static/images/argocd-replica-count-increase.gif&quot; alt=&quot;live update of the replica count change in Argo CD&apos;s Web UI&quot; height=&quot;&quot; width=&quot;&quot; loading=&quot;lazy&quot; class=&quot;img-zoomable&quot; /&gt;
  &lt;figcaption&gt;
    live update of the replica count change in Argo CD&apos;s Web UI
    
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;So there you have it. A fully functional integration of cdk8s with Argo CD. The main steps being:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;registering the cdk8s configuration management plugin&lt;/li&gt;
  &lt;li&gt;making the necessary tools available in the &lt;em&gt;argocd-repo-server&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;using the cdk8s plugin when creating the Argo CD application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m still not satisfied with having to customize an internal service to make everything work but AFAIK there’s currently no way around it. If you know of any better way or see some possible improvements please let me know.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Integrating cdk8s with Flux</title>
     <link href="https://shipit.dev/posts/integrating-cdk8s-with-flux.html"/>
     <updated>2020-04-08T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/integrating-cdk8s-with-flux</id>
     <content type="html">&lt;p&gt;After I got used to &lt;a href=&quot;/posts/cdk8s-the-future-of-k8s-application-deployments.html&quot;&gt;cdk8s&lt;/a&gt; I was curious how well it integrates with some current continuous delivery tools for Kubernetes. Therefore I sat down for a quick session for integrating it with &lt;a href=&quot;https://docs.fluxcd.io&quot;&gt;Flux&lt;/a&gt;. I will give you a short introduction for both tools to make sure you understand everything when we put them together. If you are already familiar with cdk8s and Flux you can probably skip the next two sections.&lt;/p&gt;

&lt;h2 id=&quot;flux&quot;&gt;Flux&lt;/h2&gt;

&lt;p&gt;Flux is a tool for deploying Kubernetes manifests to your cluster. It does so by following the GitOps paradigm which essentially says that one or more Git repositories are the single source of truth for the configuration of your system (in this case being Kubernetes). So in its simplest form imagine a Git repository with a single YAML file that contains a basic Kubernetes manifest (e.g. Deployment + Service). Flux will take care of applying this to your cluster. If you push more changes into this repository Flux will take care of keeping your cluster in the desired state.&lt;/p&gt;

&lt;p&gt;Now one could say: “Alright, so what’s the benefit over executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply -f *.yml&lt;/code&gt; on every code change through my CI/CD system?” The answer is very straightforward… Flux simply doesn’t need such a system. Instead of running on any infrastructure that you or someone else has to manage Flux lives in the Kubernetes cluster itself. If the cluster is down, Flux can and will be down as well. If the cluster is up, Flux should be running.&lt;/p&gt;

&lt;h2 id=&quot;cdk8s&quot;&gt;cdk8s&lt;/h2&gt;

&lt;p&gt;AWS Labs’ cdk8s is a framework that allows you to define Kubernetes deployments with object oriented programming languages like TypeScript or Python. It provides you with predefined classes, so called structs, for each Kubernetes resource. Below you can find a part of some example code defining a single Deployment.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;deployment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeploymentSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                 &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LabelSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                 &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodTemplateSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ObjectMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;hello-kubernetes&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;paulbouwer/hello-kubernetes:1.7&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContainerPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])]))))&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Out of this code cdk8s is able to generate valid manifest file(s).&lt;/p&gt;

&lt;p&gt;If you wanna get a more detailed introduction feel free to check out &lt;a href=&quot;/posts/cdk8s-the-future-of-k8s-application-deployments.html&quot;&gt;my last blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;putting-them-together&quot;&gt;Putting them together&lt;/h2&gt;

&lt;p&gt;After getting to know the purpose of our two tools one could ask how to put them together. How can I deploy my K8s manifests that I define and generate with cdk8s using Flux? As Flux only understands plain manifest files the main step that we have to automate is their generation. I came up with two possible solutions that you will find below. If you know of any better or different ways feel free to let me know.&lt;/p&gt;

&lt;h2 id=&quot;the-obvious-way&quot;&gt;the obvious way&lt;/h2&gt;

&lt;p&gt;What’s the first thing to do after celebrating that we don’t need a CI system when using Flux? Correct, introducing a CI system. ;) The idea is to put our cdk8s code into a repository and setting up a CI job that generates the Kubernetes manifests and pushes them to a separate branch. Afterwards we setup Flux to use this branch as its configuration source.&lt;/p&gt;

&lt;p&gt;The bash script for our CI job could look like this.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;cdk8s synth &lt;span class=&quot;c&quot;&gt;# `cdk8s synth` generates the K8s manifests and by default puts them in a folder called dist&lt;/span&gt;
git checkout &lt;span class=&quot;nt&quot;&gt;--orphan&lt;/span&gt; manifests &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; git checkout manifests
git add dist
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Update K8s manifests&quot;&lt;/span&gt;
git push origin manifests
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Be aware that the above script assumes that a valid cdk8s installation is already available. After putting this script into a CI pipeline that is being triggered on every code change the only thing that is left to do is correctly configuring Flux. We’ll use the following &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fluxctl&lt;/code&gt; command.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;fluxctl &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--git-user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--git-email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;@mycompany.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--git-url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;git@git.mycompany.com:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/k8s-deployments &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the URL of the Git repository containing the cdk8s code and the generated K8s manifests&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--git-branch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;manifests &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the branch that contains the K8s manifests&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--git-path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dist &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the folder that contains the K8s manifests&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;flux | kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; -
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fluxctl&lt;/code&gt; works very similar to other CLI tools in the Kubernetes ecosystem. It generates K8s manifests that you can directly pipe into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply -f -&lt;/code&gt; and that take care of setting up the Flux controller with the desired parameters.&lt;/p&gt;

&lt;p&gt;After having configured everything correctly the complete flow will look like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;some developer makes changes to the cdk8s code, e.g. in the &lt;em&gt;master&lt;/em&gt; branch&lt;/li&gt;
  &lt;li&gt;the CI pipeline is triggered by the new commit&lt;/li&gt;
  &lt;li&gt;the bash script is being executed within the context of the &lt;em&gt;master&lt;/em&gt; branch&lt;/li&gt;
  &lt;li&gt;the K8s manifests are being generated&lt;/li&gt;
  &lt;li&gt;the output is being pushed to the &lt;em&gt;manifests&lt;/em&gt; branch&lt;/li&gt;
  &lt;li&gt;Flux syncs its copy of the &lt;em&gt;manifests&lt;/em&gt; branch&lt;/li&gt;
  &lt;li&gt;Flux detects changes and applies them to the cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-not-yet-elegant-way&quot;&gt;the (not yet) elegant way&lt;/h2&gt;

&lt;p&gt;If you are someone that is familiar with Flux I already hear you shouting at me: “Max, just use Flux’s generators!” and that’s exactly what my second solution is based on. Initially I thought this approach is better than the first one due to the fact that we don’t need a CI system. Though while implementing it a big issue came up. But first things first, what is a Flux generator?&lt;/p&gt;

&lt;p&gt;Generators allow you to produce new or modify existing Kubernetes manifests on the fly before applying them on your cluster. So instead of your manifests you place a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.flux.yaml&lt;/code&gt; file along with your cdk8s code into your repository that could look like this.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;commandUpdated&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;generators&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cdk8s synth &amp;gt; /dev/null &amp;amp;&amp;amp; cat dist/*&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Flux will take of care of executing the command(s), capture the output, which should be valid K8s manifests and apply them on the cluster. Be aware that you have to mute every command that does generate output not containing K8s manifests. Otherwise you’re gonna run into problems.&lt;/p&gt;

&lt;p&gt;To enable the generators feature we have to pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--manifest-generation=true&lt;/code&gt; parameters when setting up Flux.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;fluxctl &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--git-user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--git-email&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;@mycompany.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--git-url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;git@git.mycompany.com:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GHUSER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/k8s-deployments &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# the URL of the Git repository containing the cdk8s code and the .flux.yaml file&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--manifest-generation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# enable generators&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;flux | kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; -
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So afterwards we simply put the above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.flux.yaml&lt;/code&gt; file next to our cdk8s code, push everything to a repo and we should be good to go, right? Of course not as here comes the big bummer. The execution of generators happen within the fluxd, the controller of Flux, container. This essentially means that we need to install the whole node/yarn- and Python/pip-stack into it to be able to execute cdk8s. For me, blowing up a container that needs to have full control over at least one K8s namespace to this extent is not acceptable. Anyway I checked if this approach is actually realizable so below you can find the working &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.flux.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;commandUpdated&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;generators&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;(/sbin/apk add yarn python3 npm &amp;amp;&amp;amp; pip3 install pipenv &amp;amp;&amp;amp; pipenv run pip install constructs cdk8s &amp;amp;&amp;amp; yarn global add cdk8s-cli &amp;amp;&amp;amp; cdk8s import &amp;amp;&amp;amp; cdk8s synth) &amp;gt; /dev/null &amp;amp;&amp;amp; cat dist/*&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;According to Flux’s documentation the developers are planning to allow executing generators in a separate container but for now this is not possible.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;conclusion&lt;/h2&gt;

&lt;p&gt;So there you have it. Two options of setting up a GitOps process using cdk8s and Flux. For a productive setup I’d accept the additional effort of setting up / managing a CI system (which one already has in most cases anyway) and prefer the first option for now. As soon as Flux supports the execution of generators in a separate container the second option becomes more viable in my opinion.&lt;/p&gt;

&lt;p&gt;All in all I was surprised how well both tools work together. As cdk8s becomes more mature I’ll definitely take the two into consideration for setting up my next K8s delivery pipeline.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>cdk8s, the future of Kubernetes application deployments?</title>
     <link href="https://shipit.dev/posts/cdk8s-the-future-of-k8s-application-deployments.html"/>
     <updated>2020-03-30T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/cdk8s-the-future-of-k8s-application-deployments</id>
     <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Hint: As cdk8s is fairly new at the time of this writing expect that things will change/have changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/awslabs/cdk8s&quot;&gt;cdk8s&lt;/a&gt; (probably cloud development kit for Kubernetes) is a new framework released by AWS Labs (AWS’s open source organization)that is written in TypeScript. It allows you to define Kubernetes manifests using modern object oriented programming languages. This enables you to develop a very flexible solution that is capable of complex operations for establishing your application deployment process to Kubernetes.&lt;/p&gt;

&lt;p&gt;To achieve this cdk8s provides so called &lt;strong&gt;constructs&lt;/strong&gt;, which are abstractions of Kubernetes resources (Deployment, Service, Ingress, …). A logical collection of these is called a &lt;strong&gt;chart&lt;/strong&gt; (similar to a Helm chart). Finally an &lt;strong&gt;app&lt;/strong&gt; is defined by one or more charts. Later on you will find an example that’ll showcase these units and their relation in more detail.&lt;/p&gt;

&lt;p&gt;Now you may wonder how you can use your programming language of choice if cdk8s itself is written in TypeScript. The answer is called jsii. AWS’ &lt;a href=&quot;https://github.com/aws/jsii&quot;&gt;jsii&lt;/a&gt; is a tool to generate bindings for several programming languages (currently Python, Java and C#) that allow you to interact with Type-/JavaScript classes. It is already in use by the AWS CDK (cdk8s but for &lt;a href=&quot;https://aws.amazon.com/cloudformation/&quot;&gt;CloudFormation&lt;/a&gt; files) and will probably get support for more languages over time.&lt;/p&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;p&gt;Installing cdk8s requires you to have the standard Node.js, yarn/npm stack available on your machine. To assist with bootstrapping and generating constructs for your particular Kubernetes version the kit comes with a CLI tool. Follow the &lt;a href=&quot;https://github.com/awslabs/cdk8s#getting-started&quot;&gt;Getting Started section&lt;/a&gt; to get a detailed introduction.&lt;/p&gt;

&lt;p&gt;After going through the introduction we are ready to write our code. Below you find a snippet that defines a single cdk8s app consisting of a single chart containing one service and one deployment.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;constructs&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Construct&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# importing base class for type hinting
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cdk8s&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Chart&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;imports&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# importing resource bindings for your particular Kubernetes version, previously generated by &quot;cdk8s import&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyChart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# our app instance
&lt;/span&gt;               &lt;span class=&quot;n&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# careful! this is not the K8s namespace but just a prefix for our resources
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# defining some common variables
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello-kubernetes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8080&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# defining a deployment with one container and two replicas
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;deployment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeploymentSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LabelSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodTemplateSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ObjectMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                       &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                         &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                           &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;hello-kubernetes&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                           &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;paulbouwer/hello-kubernetes:1.7&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                           &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContainerPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])]))))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# defining a service for pods created by the deployment above
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;service&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServiceSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                  &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;LoadBalancer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServicePort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k8s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IntOrString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;container_port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))],&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# creating an App instance
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MyChart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# installing our chart in the app under a certain namespace
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;synth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# this method call takes care of generating the K8s manifests
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Executing the above code or running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk8s synth&lt;/code&gt; results in the following manifest.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-deployment-c51e9e6b&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-kubernetes&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-kubernetes&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;paulbouwer/hello-kubernetes:1.7&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-kubernetes&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8080&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Service&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-service-9878228b&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;80&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;targetPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8080&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hello-kubernetes&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;LoadBalancer&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see in this simple example cdk8s takes care of creating a one to one translation of your objects into a Kubernetes manifest. Take into consideration that cdk8s’ structs are very explicit. They do not provide any kind of defaults (e.g. for the replica count) or detect any relations between objects (e.g. detecting the container port and using it for the service). This is where you come into play and develop charts that implement your desired logic. On the one hand this could be a generic chart that creates a Deployment and a Service manifest for an arbitrary container image. On the other hand it could be a chart creating a ConfigMap that dynamically pulls its values out of your key value store. You can achieve whatever your chosen programming language is capable of.&lt;/p&gt;

&lt;h2 id=&quot;comparison-against-kustomize-and-helm&quot;&gt;Comparison against kustomize and Helm&lt;/h2&gt;

&lt;p&gt;After reading to this point you may think why you shouldn’t just keep using tools like &lt;a href=&quot;https://github.com/kubernetes-sigs/kustomize&quot;&gt;kustomize&lt;/a&gt; or &lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt; and in most cases you are probably right in doing so.&lt;/p&gt;

&lt;p&gt;Depending on your background kustomize and Helm may be easier to learn. They don’t require you to write actual code but use a templating language. This allows you to do simple operations, like variable replacement, conditional blocks or for-loops. But as soon as you require more complex functionality or need to communicate with external services you’re gonna run into the limits of these tools.&lt;/p&gt;

&lt;p&gt;In my opinion cdk8s is one of the next obvious steps in the DevOps movement, bringing Ops people closer to the development and enabling developers to take over Ops tasks. As infrastructure gets more and more dynamic and complex the need for powerful application deployment orchestration rises.&lt;/p&gt;

&lt;h2 id=&quot;personal-opinion&quot;&gt;Personal Opinion&lt;/h2&gt;

&lt;p&gt;Would I use cdk8s in production today? No, there’s no stable version yet and the development is very active, making it unavoidable to break your setup from time to time. Instead I’m going to keep an eye on its progress and use it for some of my side projects. After cdk8s reaches a certain level of maturity (probably the first major version) I will definitely consider using it for new projects.&lt;/p&gt;

&lt;p&gt;Are you already using cdk8s and have some questions or experiences to share? Feel free to let me know. (y)&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Minikube vs. kind vs. k3s - What should I use?</title>
     <link href="https://shipit.dev/posts/minikube-vs-kind-vs-k3s.html"/>
     <updated>2019-12-05T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/minikube-vs-kind-vs-k3s</id>
     <content type="html">&lt;p&gt;These days there are a few tools that claim to (partially) replace a fully fledged Kubernetes cluster. Using them allows e.g. every developer to have their own local cluster instance running to play around with it, deploy their application or execute tests against applications running in K8s during CI/CD. In this post we’ll have a look at three of them, compare their pros and cons and identify use cases for each of them.&lt;/p&gt;

&lt;h2 id=&quot;minikube&quot;&gt;minikube&lt;/h2&gt;

&lt;p&gt;minikube is a Kubernetes SIGs project and has been started more than three years ago. It takes the approach of spawning a VM that is essentially a single node K8s cluster. Due to the support for a bunch of hypervisors it can be used on all of the major operating systems. This also allows you to create multiple instances in parallel.&lt;/p&gt;

&lt;p&gt;From a user perspective minikube is a very beginner friendly tool. You start the cluster using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minikube start&lt;/code&gt;, wait a few minutes and your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl&lt;/code&gt; is ready to go. To specify a Kubernetes version you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--kubernetes-version&lt;/code&gt; flag. A list of supported versions can be found &lt;a href=&quot;https://minikube.sigs.k8s.io/docs/reference/configuration/kubernetes/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are new to Kubernetes the first class support for its dashboard that minikube offers may help you. With a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minikube dashboard&lt;/code&gt; the application will open up giving you a nice overview of everything that is going on in your cluster. This is being achieved by &lt;a href=&quot;https://minikube.sigs.k8s.io/docs/tasks/addons/&quot;&gt;minikube’s addon system&lt;/a&gt; that helps you integrating things like, &lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt;, &lt;a href=&quot;https://developer.nvidia.com/kubernetes-gpu&quot;&gt;Nvidia GPUs&lt;/a&gt; and an &lt;a href=&quot;https://docs.docker.com/registry/&quot;&gt;image registry&lt;/a&gt; with your cluster.&lt;/p&gt;

&lt;h2 id=&quot;kind&quot;&gt;kind&lt;/h2&gt;

&lt;p&gt;Kind is another Kubernetes SIGs project but is quite different compared to minikube. As the name suggests it moves the cluster into Docker containers. This leads to a significantly faster startup speed compared to spawning VM.&lt;/p&gt;

&lt;p&gt;Creating a cluster is very similar to minikube’s approach. Executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kind create cluster&lt;/code&gt;, playing the waiting game and afterwards you are good to go. By using different names (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--name&lt;/code&gt;) kind allows you to create multiple instances in parallel.&lt;/p&gt;

&lt;p&gt;One feature that I personally enjoy is the ability to load my local images directly into the cluster. This saves me a few extra steps of setting up a registry and pushing my image each and every time I want to try out my changes. With a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kind load docker-image my-app:latest&lt;/code&gt; the image is available for use in my cluster. Very nice!&lt;/p&gt;

&lt;p&gt;If you are looking for a way to programmatically create a Kubernetes cluster, kind kindly (you have been for waiting for this, don’t you :P) publishes its Go packages that are used under the hood. If you want to get to know more have a look at the &lt;a href=&quot;https://godoc.org/sigs.k8s.io/kind/pkg/cluster&quot;&gt;GoDocs&lt;/a&gt; and check out how &lt;a href=&quot;https://github.com/kudobuilder/kudo/blob/f7b09025f5c2faf5492624facc1dc4c5c7a5ccad/pkg/test/harness.go#L105&quot;&gt;KUDO uses kind for their integration tests&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;k3s&quot;&gt;k3s&lt;/h2&gt;

&lt;p&gt;K3s is a minified version of Kubernetes developed by &lt;a href=&quot;https://rancher.com/&quot;&gt;Rancher Labs&lt;/a&gt;. By removing dispensable features (legacy, alpha, non-default, in-tree plugins) and using lightweight components (e.g. sqlite3 instead of etcd3) they achieved a significant downsizing. This results in a single binary with a size of around 60 MB.&lt;/p&gt;

&lt;p&gt;The application is split into the K3s server and the agent. The former acts as a manager while the latter is responsible for handling the actual workload. I discourage you from running them on your workstation as this leads to some clutter in your local filesystem. Instead put k3s in a container (e.g. by using &lt;a href=&quot;https://hub.docker.com/r/rancher/k3s&quot;&gt;rancher/k3s&lt;/a&gt;) which also allows you to easily run several independent instances.&lt;/p&gt;

&lt;p&gt;One feature that stands out is called &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/configuration/#auto-deploying-manifests&quot;&gt;auto deployment&lt;/a&gt;. It allows you to deploy your Kubernetes manifests and Helm charts by putting them in a specific directory. K3s watches for changes and takes care of applying them without any further interaction. This is especially useful for CI pipelines and IoT devices (both target use cases of K3s). Just create/update your configuration and K3s makes sure to keep your deployments up to date.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;I was a long time minikube user as there where simply no alternatives (at least I never heard of one) and to be honest…it does a pretty good job at being a local Kubernetes development environment. You create the cluster, wait a few minutes and you are good to go. However for my use cases (mostly playing around with tools that run on K8s) I could fully replace it with kind due to the quicker setup time. If you are working in an environment with a tight resource pool or need an even quicker startup time, K3s is definitely a tool you should consider.&lt;/p&gt;

&lt;p&gt;All in all these three tools are doing the job while using different approaches and focusing on different use cases. I hope you got a better understanding on how they work and which is the best candidate for solving your upcoming issue. Feel free to share your experience and let me know about use cases you are realizing with minikube, kind or k3s at &lt;a href=&quot;https://twitter.com/__brennerm&quot;&gt;@__brennerm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below you can find a table that lists a few key facts of each tool.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;minikube&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;kind&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;k3s&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;runtime&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;VM&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;container&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;native&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;supported architectures&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;AMD64&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;AMD64&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;AMD64, ARMv7, ARM64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;supported container runtimes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Docker,CRI-O,containerd,gvisor&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Docker&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Docker, containerd&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;startup time initial/following&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;5:19 / 3:15&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;2:48 / 1:06&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;0:15 / 0:15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;memory requirements&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;2GB&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;8GB (Windows, MacOS)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;512 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;requires root?&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;no&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;no&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;yes (rootless is experimental)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;multi-cluster support&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;no (can be achieved using containers)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;multi-node support&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;no&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;yes&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;project page&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://minikube.sigs.k8s.io/&quot;&gt;minikube&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://kind.sigs.k8s.io/&quot;&gt;kind&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;a href=&quot;https://k3s.io/&quot;&gt;k3s&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Installing Python in OpenWRT on a USB storage</title>
     <link href="https://shipit.dev/posts/installing-python-in-openwrt-on-usb-storage.html"/>
     <updated>2019-09-07T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/installing-python-in-openwrt-on-usb-storage</id>
     <content type="html">&lt;h2 id=&quot;1-the-problem&quot;&gt;1. The problem&lt;/h2&gt;

&lt;p&gt;Lately I was in need to periodically execute a Python script. To achieve this task my router, a &lt;a href=&quot;https://www.tp-link.com/us/products/details/cat-9_TL-WDR3600.html&quot;&gt;TP-Link N600&lt;/a&gt;, running &lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWRT&lt;/a&gt; Chaos Calmer came to my mind as it’s running the whole day anyway. So I checked the available packages in opkg, OpenWRT’s package manager, and found what I was looking for. Confidently executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opkg install python3&lt;/code&gt; (for Python 2 it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opkg install python&lt;/code&gt;) resulted in the following&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;Collected errors:
 * wfopen: /usr/lib/python2.7/encodings/mac_latin2.py: No space left on device.
 * wfopen: /usr/lib/python2.7/encodings/iso8859_3.py: No space left on device.
 * wfopen: /usr/lib/python2.7/encodings/iso8859_1.py: No space left on device.
 * wfopen: /usr/lib/python2.7/encodings/euc_kr.py: No space left on device.
 * wfopen: /usr/lib/python2.7/encodings/cp775.py: No space left on device.
 * pkg_write_filelist: Failed to open //usr/lib/opkg/info/python-codecs.list: No space left on device.
 * opkg_install_pkg: Failed to extract data files for python-codecs. Package debris may remain!
 * opkg_install_cmd: Cannot install package python.
 * opkg_conf_write_status_files: Can&apos;t open status file //usr/lib/opkg/status: No space left on device.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Well Shit! Being used to work with devices with loads of storage I didn’t expect that. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df&lt;/code&gt; told me that my router has an incredible amount of 4.5, in words &lt;strong&gt;four dot five&lt;/strong&gt;, megabytes of storage!&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;root@OpenWrt:~# df -h
Filesystem                Size      Used Available Use% Mounted on
rootfs                    4.5M      4.4M    108.0K  98% /
/dev/root                 2.3M      2.3M         0 100% /rom
tmpfs                    61.5M      1.2M     60.4M   2% /tmp
/dev/mtdblock3            4.5M      4.4M    108.0K  98% /overlay
overlayfs:/overlay        4.5M      4.4M    108.0K  98% /
tmpfs                   512.0K         0    512.0K   0% /dev
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And so the journey began.&lt;/p&gt;
&lt;h2 id=&quot;2-looking-for-solutions&quot;&gt;2. Looking for solutions&lt;/h2&gt;
&lt;p&gt;After some investigation I found out that opkg supports custom installation paths for packages. The router’s RAM, mounted at /tmp, is one of the predefined targets. It’s advantage is that it provides a lot more storage (in my case 61.5Mb), but you may already have one concern. Right, as we’re working on volatile memory all data will be lost upon reboot.&lt;/p&gt;

&lt;p&gt;As this was no option in my case I had to look further. Another solution would have been to use some kind of network storage and mount it via NFS. In my current environment I don’t have anything like that available to me so I went with the next obvious solution. Expanding my router’s storage using an USB device.&lt;/p&gt;
&lt;h2 id=&quot;3-accessing-a-usb-device&quot;&gt;3. Accessing a USB device&lt;/h2&gt;
&lt;p&gt;The support for USB devices is documented fairly good on the two pages about &lt;a href=&quot;https://wiki.openwrt.org/doc/howto/usb.essentials&quot;&gt;basic USB support&lt;/a&gt; and &lt;a href=&quot;https://wiki.openwrt.org/doc/howto/usb.storage&quot;&gt;USB storage&lt;/a&gt;. All I needed to do is to check the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; after plugging in my USB stick.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;[5952821.750000] usb 1-1.1: new high-speed USB device number 3 using ehci-platform
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In my case I had to install the driver for the ehci platform (USB 2.0).&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;opkg update
opkg install kmod-usb2 kmod-usb-storage
insmod ehci-hcd
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Afterwards the system detected the device and made it available at &lt;em&gt;/dev/sda&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Depending on the partitioning of your device you may need to install support for further filesystems like ext4.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;opkg install kmod-fs-ext4
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now I was able to mount my USB stick.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;mount -t ext4 /dev/sda1 /mnt/usb
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As the mounting should happen automatically after every reboot installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block-mount&lt;/code&gt; package, persisting the current mounts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block detect &amp;gt; /etc/config/fstab&lt;/code&gt; and enabling the autostart &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/init.d/fstab enable&lt;/code&gt; was necessary. All that was left was to enable the automount for the USB device by editing the file &lt;em&gt;/etc/config/fstab&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;4-installing-python-to-a-usb-device&quot;&gt;4. Installing Python to a USB device&lt;/h2&gt;
&lt;p&gt;With a working USB storage I needed to tell opkg to actually use it. This was done by adding the following the file &lt;em&gt;/etc/opkg.conf&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;dest usb /mnt/usb
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Installing packages to a non-default location may break. Some of the issues can be resolved by adjusting the path variables in &lt;em&gt;/etc/profile&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;export PATH=$PATH:/mnt/usb/bin:/mnt/usb/sbin:/mnt/usb/usr/bin:/mnt/usb/usr/sbin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/usb/lib:/mnt/usb/usr/lib
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Afterwards I was able to install Python using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opkg intall python3 -d usb&lt;/code&gt;.
The OpenWrt docs suggest to precompile all Python modules to achieve a faster execution. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python -m compileall&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Et voila, we have a running Python interpreter!&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GCC&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;4.8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;linux&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;help&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;copyright&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;credits&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;license&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;more&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;this&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;The&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Zen&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Python&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tim&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Peters&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Beautiful&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ugly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Explicit&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;implicit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Simple&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;complex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Complex&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;complicated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Flat&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Sparse&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dense&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Readability&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Special&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cases&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;aren&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;re&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dutch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Now&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;never&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Although&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;never&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;often&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;better&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;than&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;more&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;those&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;!&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>My top 5 positive points about Ansible in 2017</title>
     <link href="https://shipit.dev/posts/ansible-top-5.html"/>
     <updated>2017-09-19T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/ansible-top-5</id>
     <content type="html">&lt;p&gt;&lt;strong&gt;Intro:&lt;/strong&gt; In this post you are going to find a list of aspects where I think the provisioning engine Ansible excels. I’m a guy that lives in a Linux dominated infrastructure. Therefore my experiences may not align with yours. Make sure to let me know your opinion at &lt;a href=&quot;https://twitter.com/__brennerm&quot; target=&quot;_blank&quot;&gt;@__brennerm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt; The latest Ansible release as of this writing is 2.3.2.0.&lt;/p&gt;

&lt;h2 id=&quot;1-the-client-requirements&quot;&gt;1. The client requirements&lt;/h2&gt;
&lt;p&gt;Unlike other provisioning tools, like Puppet, Ansible is not in need of any additional agent software being installed on the clients. The only requirements are SSH access and Python, which are already fulfilled on most modern operating systems. The latter isn’t even required as you can use the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/raw_module.html&quot; target=&quot;_blank&quot;&gt;raw module&lt;/a&gt; to install Python e.g. by executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install -y python2.7&lt;/code&gt; before the execution of your “real” configuration. This module also gives you the ability to configure devices that do not run a fully fledged operating system, like switches or PDUs.&lt;/p&gt;

&lt;h2 id=&quot;2-the-nonexistence-of-the-master-server&quot;&gt;2. The nonexistence of the “master server”&lt;/h2&gt;

&lt;p&gt;As for the clients, the requirements to be able to start provisioning are minimal. All you need is the Ansible executable, your configuration you want to apply and a way to authenticate against the clients. This leads to the fact that theoretically any machine can become one of your provisioning servers in a matter of seconds. The clients are not fixed to one master server which results in a lot of flexibility. Additionally you don’t have to keep your provisioning server up all the time to be able to answer the client’s request for configuration updates. Also you know exactly when your clients are being updated and it’s easier to see what’s exactly happening during the deployment.&lt;/p&gt;

&lt;h2 id=&quot;3-python&quot;&gt;3. Python&lt;/h2&gt;

&lt;p&gt;This is the most subjective point of this list but what can I say. It’s just me liking Python and thus being able to extend Ansible in any way. I’m in contact with a lot of proprietary software where Ansible support isn’t available. For this kind of stuff it’s a real pleasure to use your beloved programming language to be able to create reusable building blocks in the form of &lt;a href=&quot;http://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html&quot; target=&quot;_blank&quot;&gt;Ansible modules&lt;/a&gt;. Another place where Python excels is the &lt;a href=&quot;http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html&quot; target=&quot;_blank&quot;&gt;dynamic inventory&lt;/a&gt;. As there is an API for almost every IaaS implementation available for Python it’s really easy to connect your Ansible provisioning to your already existing infrastructure.&lt;/p&gt;

&lt;h2 id=&quot;4-the-documentation&quot;&gt;4. The documentation&lt;/h2&gt;

&lt;p&gt;Giving some credit to the maintainers, the documentation is really good. For every part of Ansible you are going to find extensive and high quality documentation that is up to date. Additionally as it’s an already established piece of software a lot of questions have already been answered. The only small issue that comes to my mind is that &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html&quot; target=&quot;_blank&quot;&gt;some of the built-in modules&lt;/a&gt; are missing the documentation of their return values.&lt;/p&gt;

&lt;h2 id=&quot;5-the-ecosystem&quot;&gt;5. The ecosystem&lt;/h2&gt;

&lt;p&gt;Another advantage of Ansible being widely used is the existence of a lot of community projects around it. In this case I’m particularly talking about roles. There are roles for almost every piece of software that is publicly available. For popular technologies, like &lt;a href=&quot;https://github.com/ANXS/postgresql/blob/master/defaults/main.yml&quot; target=&quot;_blank&quot;&gt;PostgreSQL&lt;/a&gt; or &lt;a href=&quot;https://github.com/debops/ansible-gitlab/blob/master/defaults/main.yml&quot; target=&quot;_blank&quot;&gt;Gitlab&lt;/a&gt;, you’re going to find roles that let you adjust basically everything. Ansible Galaxy  does a good job of making all of these roles accessible over a unified interfaces. Additionally it prevents a lot of duplicate “code” as it allows you to easily handle your &lt;a href=&quot;http://docs.ansible.com/ansible/latest/galaxy.html#dependencies&quot; target=&quot;_blank&quot;&gt;role dependencies&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s my top 5 points about Ansible in 2017. If you think I missed something feel free to let me known at &lt;a href=&quot;https://twitter.com/__brennerm&quot; target=&quot;_blank&quot;&gt;@__brennerm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Make sure you check back for my top 5 negative points about Ansible.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Cap'n Proto with Python</title>
     <link href="https://shipit.dev/posts/capnproto-with-python.html"/>
     <updated>2015-05-19T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/capnproto-with-python</id>
     <content type="html">&lt;p&gt;If you are already familiar with Cap’n Proto and just want to see how to use it with Python click &lt;a href=&quot;#capnpwithpython&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;introduction-to-capn-proto&quot;&gt;Introduction to Cap’n Proto&lt;/h2&gt;
&lt;p&gt;Ever heard of Cap’n Proto? - No it’s not one of the worst named superheroes ever. Lets have a look at what the &lt;a href=&quot;https://capnproto.org&quot;&gt;official webpage&lt;/a&gt; is saying:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cap’n Proto is an insanely fast data interchange format and capability-based RPC system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This addresses two points - &lt;strong&gt;data interchange format&lt;/strong&gt; and &lt;strong&gt;RPC&lt;/strong&gt;. But before we can make use of whatever this means we will have a look at Cap’n Proto’s schema language.&lt;/p&gt;
&lt;h3 id=&quot;schema-language&quot;&gt;Schema language&lt;/h3&gt;
&lt;p&gt;This language is used to define structures for further use. It’s syntax shows similarities to the C programming language and supports common data types like Boolean, Integer, Struct, Enum, etc.
Let’s have a look at it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;const blogUrl :Text = &quot;shipit.dev/&quot;;

enum Language {
	en @0;
	de @1;
	ru @2;
}

struct Date{
	year @0 :Int16;
	month @1 :UInt8;
	day @2 :UInt8;
}

struct Post {
	availableLanguages @0 :List(Language);
	publishDate @1 :Date;
	content @2 :Text;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you see containers like &lt;em&gt;struct&lt;/em&gt; or &lt;em&gt;enum&lt;/em&gt; are supported and can be used to easily abstract real-world objects.&lt;/p&gt;

&lt;p&gt;You may have already noticed the numbers behind every single field. This enables Cap’n Proto to keep your evolving schema backwards compatible. As long as you follow &lt;a href=&quot;https://capnproto.org/language.html#evolving-your-protocol&quot;&gt;some rules&lt;/a&gt; there is no need to spend time on keeping your application compatible with older versions.&lt;/p&gt;

&lt;p&gt;There’s no point going over the whole vocabulary of the schema language. It’s just important to get a basic understanding, cause the following features use this schemas as a basis. If you want to learn more about the schema language have a look &lt;a href=&quot;https://capnproto.org/language.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;data-interchange-format&quot;&gt;data interchange format&lt;/h3&gt;
&lt;p&gt;The data interchange format is based on messages that can contain multiple instances of our self-defined objects. These messages are saved in a binary format which is controlled by the Cap’n Proto library.&lt;/p&gt;

&lt;p&gt;Each data type is handled in a &lt;a href=&quot;https://capnproto.org/encoding.html#value-encoding&quot;&gt;defined way&lt;/a&gt; which results in a well organized format.
For bandwidth limited use cases Cap’n Proto provides a built-in packing that can save up a lot of unnecessary zero bytes. In some cases the size can be shrunk further by applying a suitable compression algorithm.&lt;/p&gt;
&lt;h3 id=&quot;rpc&quot;&gt;RPC&lt;/h3&gt;
&lt;p&gt;RPC stands for &lt;strong&gt;remote procedure call&lt;/strong&gt; which is a type of inter-process communication. It allows you to call functions that are provided by another process on your machine or even over the network.&lt;/p&gt;

&lt;p&gt;It follows the client-server-architecture with the server providing and executing the functions and the clients calling them. The interface definition is handled through the schema language we discussed earlier:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;interface Calc {
	sum @0 (a :Int64, b :Int64) -&amp;gt; (result :Int64);
	sub @1 (a :Int64, b :Int64) -&amp;gt; (result :Int64);
	mul @2 (a :Int64, b :Int64) -&amp;gt; (result :Int64);
	div @3 (a :Int64, b :Int64) -&amp;gt; (result :Float64);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is an example of a really basic calculator, but contains all you need to know. It’s of course possible to use your own defined structures as parameters or return value.&lt;/p&gt;

&lt;p&gt;There are already several implementations of RPC like Java’s RMI, CORBA or DBUS, but Cap’n Proto provides a feature called &lt;strong&gt;promise pipelining&lt;/strong&gt;. This lets you use a return value of one function call as an argument for another without waiting for the first function to finish. This is especially useful when your application runs in an environment with a long round trip time, cause you save up unnecessary requests. More information can be found &lt;a href=&quot;https://capnproto.org/rpc.html#time-travel-promise-pipelining&quot;&gt;here&lt;/a&gt;.
&lt;a name=&quot;capnpwithpython&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;capn-proto-with-python&quot;&gt;Cap’n Proto with Python&lt;/h2&gt;
&lt;p&gt;With the knowledge of what Cap’n Proto is all about we’ll have a look into &lt;a href=&quot;https://github.com/jparyani/pycapnp&quot;&gt;pycapnp&lt;/a&gt;. It’s a wrapper for Cap’n Proto that makes most of the features available to use with Python. The following examples will be based on this schema:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;enum Unit {
	k @0;
	f @1;
	c @2;
}

struct Temperature {
	value @0 :Float64;
	unit @1 :Unit;
}

interface TempConv {
	convert @0 (temp :Temperature, target_unit :Unit) -&amp;gt; (result :Temperature);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;message-creation&quot;&gt;Message creation&lt;/h3&gt;
&lt;p&gt;Before we are able to create our messages we need to read in our schema. Pycapnp handles this pretty &lt;em&gt;pythonic&lt;/em&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;capnp&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will search the current directory and your &lt;em&gt;PYTHONPATH&lt;/em&gt; for a file called &lt;em&gt;tempconv.capnp&lt;/em&gt;. The schema is going to be processed and made accessible like a module within your code. You can then create and define your messages like that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;temperature:&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pretty easy, right? Pycapnp handles all validation and errors are raised when a value is inappropriate (type mismatch, not in enum, …).&lt;/p&gt;

&lt;p&gt;If you like dicts, this is a way you can go:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;restored_temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;temperature:&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;restored_temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;de-serialization&quot;&gt;De-/Serialization&lt;/h3&gt;
&lt;p&gt;The de- and serialization from and to the data interchange format is supported as well:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;capnp&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;tempconv_capnp&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;temp_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;restored_temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;restored_temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The resulting bytes can be send over the network or can be saved in a file.
To use Cap’n Proto’s built-in packing just append &lt;em&gt;_packed&lt;/em&gt; to &lt;em&gt;to_bytes&lt;/em&gt; and &lt;em&gt;from_bytes&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;rpc-1&quot;&gt;RPC&lt;/h3&gt;
&lt;p&gt;Let’s come to the most interesting part of the library. Our target is to build a server that offers and executes the &lt;em&gt;convert&lt;/em&gt; function. The functionality is implemented like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# name of the class doesn&apos;t matter, as long as you inherit from your server class
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		
		&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target_unit&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CONVERTER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;unit&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]][&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_unit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Be sure to name your function according to your interface definition and add &lt;em&gt;**kwargs&lt;/em&gt; to your parameters. This will ensure your server remains compatible with newer versions that may provide more arguments.&lt;/p&gt;

&lt;p&gt;Now it’s time to start our server:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# enables you to save capabilities and restore them later
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;as_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;tempConv&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TwoPartyServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;127.0.0.1:12345&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_forever&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the server up and running we can now connect our client:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TwoPartyClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;localhost:12345&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tempconv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ez_restore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;tempConv&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cast_as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the connection in hand we can finally call our &lt;em&gt;convert&lt;/em&gt; function:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;convert_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;k&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The resulting promise can be handled in two ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;asynchronous:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;synchronous:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And there is our result:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;373.15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s all you need to know for now to make use of Cap’n Proto for your Python application. When having a look at the &lt;a href=&quot;https://capnproto.org/roadmap.html&quot;&gt;roadmap&lt;/a&gt; there will be some interesting features in the future, like &lt;em&gt;shared-memory RPC&lt;/em&gt; or &lt;em&gt;dynamic schema transmission&lt;/em&gt; so stay tuned for updates.
&lt;br /&gt;All shown code examples can be found on &lt;a href=&quot;https://github.com/brennerm/brennerm.github.io/tree/master/_posts/capnproto_with_python&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;#Update 20.05.2015
Played around using my own sockets when communicating with pycapnp’s RPC lately and had some trouble in the beginning. So I just want to let you guys know how to get it working.
The first step is to connect your client and server socket:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# server
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;127.0.0.1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# client
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;127.0.0.1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now instead of handing over the address + port hand over your socket to the server and client constructor.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# server
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TwoPartyServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;on_disconnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# client
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TwoPartyClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tempconv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cast_as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tempconv_capnp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TempConv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tempconv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;convert_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;c&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;k&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Be sure to use &lt;em&gt;server.on_disconnect().wait()&lt;/em&gt; instead of &lt;em&gt;server.run_forever()&lt;/em&gt; and it will work as expected.&lt;/p&gt;
</content>
   </entry>
   
 
   
   <entry>
     <title>Python's str() vs. repr()</title>
     <link href="https://shipit.dev/posts/python-str-vs-repr.html"/>
     <updated>2015-04-10T00:00:00+00:00</updated>
     <id>https://shipit.dev/posts/python-str-vs-repr</id>
     <content type="html">&lt;p&gt;Ever wondered what happens when you call Python’s built-in str(X), with X being any object you want? The return value of this function depends on the two &lt;a href=&quot;https://rszalski.github.io/magicmethods/&quot; target=&quot;_blank&quot;&gt;magic methods&lt;/a&gt; &lt;a href=&quot;https://docs.python.org/3/reference/datamodel.html#object.__str__&quot; target=&quot;_blank&quot;&gt;__str__&lt;/a&gt; being the first choice and &lt;a href=&quot;https://docs.python.org/3/reference/datamodel.html#object.__repr__&quot; target=&quot;_blank&quot;&gt;__repr__&lt;/a&gt; as a fallback. But what’s the difference between them? When having a look at the docs&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;Create a new string object from the given object.&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;repr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;Return the canonical string representation of the object.&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;they seem to be fairly similar. Let’s see them in action:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;123&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;repr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;123&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Alright no difference for now.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Python&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;Python&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;repr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Python&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&quot;&apos;Python&apos;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A second pair of quotes around our string. Why?&lt;br /&gt;With the return value of &lt;em&gt;repr()&lt;/em&gt; it should be possible to recreate our object using eval(). This function takes a string and evaluates it’s content as Python code. In our case passing &lt;em&gt;“‘Python’“&lt;/em&gt; to it works, whereas &lt;em&gt;‘Python’&lt;/em&gt; leads to an error cause it’s interpreted as the variable &lt;em&gt;Python&lt;/em&gt; which is of course undefined. Let’s move on…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;datetime&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;2015-04-04 20:51:31.766862&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;repr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&apos;datetime.datetime(2015, 4, 4, 20, 51, 31, 766862)&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is some significant difference. While &lt;em&gt;str(now)&lt;/em&gt; computes a string containing the value of &lt;em&gt;now&lt;/em&gt;, &lt;em&gt;repr(now)&lt;/em&gt; again returns the Python code needed to rebuild our &lt;em&gt;now&lt;/em&gt; object.&amp;lt;/br&amp;gt;&lt;/p&gt;

&lt;p&gt;The following clues might help you to decide when to use which:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;str()&lt;/th&gt;
      &lt;th&gt;repr()&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;- make object readable&lt;/td&gt;
      &lt;td&gt;- need code that reproduces object&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;- generate output for end user&lt;/td&gt;
      &lt;td&gt;- generate output for developer&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
These points should also be considered when writing __str__ or __repr__ for your classes.&lt;/p&gt;

&lt;hr /&gt;
</content>
   </entry>
   
 
 
</feed>
