<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Oh The Huge Manatee!</title>
  <link href="https://ohthehugemanatee.org/atom.xml" rel="self"/>
  <link href="https://ohthehugemanatee.org/"/>
  <updated>2022-12-20T09:01:00+01:00</updated>
  <id>https://ohthehugemanatee.org/</id>
  <author>
    <name>Campbell Vertesi (ohthehugemanatee)</name>
    <uri>https://ohthehugemanatee.org/</uri>
  </author>
  <generator>Hugo -- gohugo.io</generator>
  <entry>
    <title type="html"><![CDATA[Software Estimation Deniers: the Flat-Earthers of Project Management]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/12/20/software-estimation-deniers-the-flat-earthers-of-project-management/"/>
    <id>https://ohthehugemanatee.org/blog/2022/12/20/software-estimation-deniers-the-flat-earthers-of-project-management/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-12-20T09:01:00+01:00</published>
    <updated>2022-12-20T09:01:00+01:00</updated>
    <content type="html"><![CDATA[<p>Another day, another <a href="https://news.ycombinator.com/item?id=34061206">Hacker News post</a> complaining about estimation in software build projects. These articles and comment sections should come with a trigger warning for armchair speculation. <strong>Project management and time estimation are fields of serious research. Drawing conclusions from your personal experience, with no idea what the research says, is as silly as writing your experience that the world is obviously flat.</strong></p>
<p>Estimation is actually well understood in academic project management. There is (Nobel Prize winning!) research about what, actually, are the problems inherent to estimation, and how to produce specific and accurate estimates despite them. This academic field is almost 50 years old, and no one who complains about estimation in blog posts or comments is aware of it.</p>
<p>Stop navel gazing and actually go READ about the subject. I know it&rsquo;s hard for us to take in anything longer than a StackExchange post, but please try, BEFORE you write about your shitty experience with estimates and generalize to the entire problem space.</p>
<p>Here are some things that the research has said for <em>decades</em>:</p>
<ul>
<li>
<p>Humans are <em>all</em> bad at time estimation. Even the ones who consider themselves good at it, estimating tasks with which they are very familiar, &ldquo;only&rdquo; underestimate by 30% <em>at best</em>.</p>
</li>
<li>
<p>Humans are pretty good at estimating non-time attributes of work, even those with a direct correlation to time. Like effort, complexity, or &ldquo;cups of coffee.&rdquo;</p>
</li>
<li>
<p>if you estimate something with a time corellation (e.g. complexity) in a consistent way and measure the average throughput over time, you can very precisely and accurately estimate time to completion. This is the Law of Large Numbers, which is how casinos can accurately predict profits when dealing with much more randomness than exists in software projects.</p>
</li>
<li>
<p>Note that long term estimates produced with the Law of Large Numbers include unexpected complexity, personal issues, illness, windows updates, etc. It&rsquo;s a statistical law.</p>
</li>
<li>
<p>the accuracy of average time estimates is proportional to the time left on the project. It runs opposite to the uncertainty of distant features. I.e. this method does not predict how much you can build in a week; you&rsquo;re better off with your relatively intimate knowledge of the feature at that point in time and a gut check. Rather it predicts how much you will build over 12 weeks, with extraordinary accuracy.</p>
</li>
<li>
<p>estimates are better understood when presented with a confidence interval, e.g. &ldquo;The work as we understand it today will take 8 weeks, with 95% certainty.&rdquo;</p>
</li>
<li>
<p>Estimates are damaging to teams when they are treated as proscriptive. They are constructive when used as predictions. i.e. rather than &ldquo;you must get this work done in 8 weeks,&rdquo; it&rsquo;s &ldquo;we understand this as 8 weeks of work.&rdquo; Proscriptive estimates compound the problems with human estimation (see first point).</p>
</li>
</ul>
<p>What I HAVEN&rsquo;T seen in the research, but which is undoubtedly true, is that most teams violate these fundamentals. A certain percentage of those team members then complain on the Internet that estimates are useless.</p>
<p>Asking your team to estimate in time units IS useless. Summing those time estimates to create a long term plan is doubly useless. Cracking the whip on your team when that estimate proves incorrect is triply useless. And complaining about it on the Internet because you&rsquo;ve never read any of the grown up work on the subject&hellip; well that&rsquo;s hacker culture.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Whatsapp Is Too Expensive for Me]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/09/19/whatsapp-is-too-expensive-for-me/"/>
    <id>https://ohthehugemanatee.org/blog/2022/09/19/whatsapp-is-too-expensive-for-me/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-09-19T19:30:16+02:00</published>
    <updated>2022-09-19T19:30:16+02:00</updated>
    <content type="html"><![CDATA[<p>Plenty of my friends and colleagues use WhatsApp and enjoy it. But I think few people are really considering the information pricetag they&rsquo;re paying for a chat client. It comes up frequently enough that I thought I&rsquo;d catalog the information you give Meta/Facebook/Zuckerberg by installing WhatsApp.</p>
<p>I imagine collecting metadata over a period of a few years to glean these insights. This is all extracted from &ldquo;metadata&rdquo;, by the way.  You don&rsquo;t need access to the contents of someone&rsquo;s messages to know all about them.</p>
<ul>
<li>Contacts
<ul>
<li>everyone you have ever known, when you last contacted them and when you last updated their info.</li>
<li>what high school, universities you attended</li>
<li>what you studied</li>
<li>where you have worked and when</li>
<li>who are your family (contacts named &ldquo;mom&rdquo; or with same last name)</li>
<li>who are your friends</li>
<li>approximately where you live</li>
<li>your hobbies</li>
<li>your political leanings and affiliations</li>
<li>what doctors you have visited regularly enough to store</li>
<li>what medical conditions you&rsquo;ve probably had</li>
</ul>
</li>
<li>Storage
<ul>
<li>every place you&rsquo;ve taken a picture</li>
<li>who you were with</li>
<li>what you were doing</li>
<li>what you wear (brands etc)</li>
<li>your interests/hobbies</li>
<li>when you travel, and where to</li>
<li>do you have kids, how many and how old</li>
</ul>
</li>
<li>Location
<ul>
<li>home address</li>
<li>work address (and therefore likely job)</li>
<li>friends and where they live</li>
<li>who you&rsquo;re sleeping with</li>
<li>your path to work, what transit you use, where you change stations etc</li>
<li>social events</li>
<li>hobbies</li>
<li>doctors / medical history</li>
<li>how often you use the toilet and for how long</li>
<li>kids&rsquo; schools and paths there</li>
<li>where you shop and what brands</li>
</ul>
</li>
<li>Generally from having an app
<ul>
<li>your sleep/wake schedule</li>
<li>how often you use your phone</li>
<li>your phone make/model/year</li>
<li>any phone peripherals you own (eg earbuds)</li>
<li>what other social applications you share from</li>
</ul>
</li>
</ul>
<p>Of course I haven&rsquo;t included any data that&rsquo;s publicly available to purchase and correlate with you to build a more complete profile, which Meta certainly does. Credit card information, brands you frequent, your subscriptions, which video services you use (netflix, hulu, etc), your SSN, income bracket, age, sex, and more. Nor have I included information we get by combining metadata, like psychological profile, alcohol/drug habits, risk of medical conditions like heart attack, etc.</p>
<p>Even talking about that knowledge in the abstract like this, the significance doesn&rsquo;t really land. Another way to look at it is the specific knowledge about your life that they gather from this information. Here are some examples from <a href="https://ssd.eff.org/en/module/why-metadata-matters">the EFF</a>:</p>
<blockquote>
<ul>
<li>They know you rang a phone sex line at 2:24 am and spoke for 18 minutes. But they don&rsquo;t know what you talked about.</li>
<li>They know you called the suicide prevention hotline from the Golden Gate Bridge. But the topic of the call remains a secret.</li>
<li>They know you got an email from an HIV testing service, then called your doctor, then visited an HIV support group website in the same hour. But they don&rsquo;t know what was in the email or what you talked about on the phone.</li>
<li>They know you received an email from a digital rights activist group with the subject line “Let’s Tell Congress: Stop SESTA/FOSTA” and then called your elected representative immediately after. But the content of those communications remains safe from government intrusion.</li>
<li>They know you called a gynecologist, spoke for a half hour, and then called the local abortion clinic’s number later that day.</li>
</ul>
</blockquote>
<p>This is not quite the same as knowing when you were masturbating and to what, that you&rsquo;re suicidally depressed, have HIV, that you&rsquo;re a digital rights activist, and are having an abortion&hellip; but it&rsquo;s just as bad.</p>
<p>When an app asks me for permissions, I try to mentally translate it into a request for specific information like this. So when I go to install WhatsApp, it says &ldquo;in order to use Whatsapp, you have to tell me everyone you have ever known, when you last talked to them, where you studied, your doctors&rsquo; names, your medical history, family and friends name, where you live and work, your route between them, when you&rsquo;re sleeping vs awake, your hobbies, etc&hellip;&rdquo;</p>
<p>There are plenty of apps where that tradeoff is worth it for me. But not a chat app. They&rsquo;re a dime a dozen! Especially not when there are alternatives like Signal available which ask for no information and offer the same features.</p>
<p>I think most people don&rsquo;t consider app installs this way. That&rsquo;s not an accident, it&rsquo;s a feature of how the system is built. People who are tricked - practically everyone - are not fools. They are victims. So when I see that you have WhatsApp installed, I don&rsquo;t judge you. I judge Meta.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Four Drupalists]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/09/08/four-drupalists/"/>
    <id>https://ohthehugemanatee.org/blog/2022/09/08/four-drupalists/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-09-08T21:07:31+02:00</published>
    <updated>2022-09-08T21:07:31+02:00</updated>
    <content type="html"><![CDATA[<p>Recently some of my work on prenotes came up, and reminded me that I&rsquo;ve wanted to post favorite moments here for some time. For those who don&rsquo;t know, a <em>prenote</em> is a session before the keynote of a conference or event. During that session, notables from across the community put on a humorous show of parody lyrics karaoke, terribly scripted dialogue, and general silliness. It&rsquo;s a great way to kick off an event, and really sets the tone if you&rsquo;re into flat-hierarchy and openness for newbies.</p>
<p>Without further ado, here is the first of my favorite prenote moments, performed for Drupalcon Amsterdam in 2019.</p>
<h2 id="the-four-drupalists">The four Drupalists</h2>
<p>(the four of us pass a joint around)</p>
<p><strong>Rob:</strong> Ahh.. Very passable, this, very passable.</p>
<p><strong>Cam:</strong> Nothing like a good hit of Lemon Kush Blue Drop Haze, ay Adam?</p>
<p><strong>Adam:</strong> Dude&hellip;</p>
<p><strong>Jam:</strong> Who&rsquo;d a thought ten years ago we&rsquo;d all be sittin&rsquo; here smoking a bong on the prenote stage?</p>
<p><strong>Rob:</strong> Yep. In those days, we&rsquo;d a&rsquo; been glad to even give a session.</p>
<p><strong>Cam:</strong> A session OFF the mainstage.</p>
<p><strong>Jam:</strong> Without mics or speaker notes.</p>
<p><strong>Adam:</strong> OR a session.</p>
<p><strong>Rob:</strong> In a filthy broom closet.</p>
<p><strong>Jam:</strong> I never used to have a broom closet. I used to have to present in the alley behind the convention center.</p>
<p><strong>Cam:</strong> The best WE could manage was talking to strangers at a bus stop.</p>
<p><strong>Adam:</strong> But you know, we were happy in those days, though we weren’t Drupal Famous.</p>
<p><strong>Rob:</strong> Aye. BECAUSE we weren’t Drupal Famous. Dries used to say to me: Rob, Twitter followers don&rsquo;t buy you happiness.'</p>
<p><strong>Jam:</strong> He was right. I was happier then and I had NO followers. We used to have this tiiiny old PHPBB bulletin board, with enormous fatal errors in the forums.</p>
<p><strong>Cam:</strong> Bulletin board! You were lucky to have a bulletin board! We used to share one ICQ account, all twenty-six of us, no wifi. Half the router was dead; we were all plugged into the other half for fear of PACKET LOSS!</p>
<p><strong>Adam:</strong> You were lucky to have a ROUTER! <em>We</em> used to have to use a token ring network!</p>
<p><strong>Rob:</strong> Ohhh we used to DREAM of a token ring network! Woulda’ been luxury to us. We used to share a dial-up connection on a copper phone line. We got woken up every morning by the sound of SCREEEEE-WOOOEOOOOOEOOO-BIBOMMMM-BIBOMM-BIBOMM. Internet connection! Hmpfh.</p>
<p><strong>Jam:</strong> Well when I say “Internet connection”, it was just a 14.4 baud modem connected to a tin can and some string, but it was an Internet connection to US.</p>
<p><strong>Cam:</strong> Our tin can and string were cut; we had to send packets by smoke signal!</p>
<p><strong>Adam:</strong> You were lucky to have smoke signals! There were a hundred and fifty of us sending our TCP packets on USB sticks by carrier pigeon.</p>
<p><strong>Rob:</strong> 1 Gigabyte sticks?</p>
<p><strong>Adam:</strong> Yep.</p>
<p><strong>Rob:</strong> You were lucky. For us, once a week old Geppeto would come to town pushing a hand cart with UDP packets. SYN and ACK, hmpf! We used to join stand-up at 6 in the morning, clean the backlog, drink a cup of cold coffee, go down to code in the basement for fourteen hours a day week-in-week-out. When we finished a project, the PM would schedule a 2 week retrospective.</p>
<p><strong>Cam:</strong> Luxury. We used to join standup at 3 o’clock in the morning, rewrite the whole ticketing system from scratch, eat a handful of coffee grinds, write our modules without an IDE for 2 cents a month, clock out, and the PM would beat us over the head with a laptop, if we were LUCKY!</p>
<p><strong>Adam:</strong> Well we had it tough. We used to have to wake up the carrier pigeons at twelve o’clock at night, and format the USB sticks by hand. We drank a cup of sulfuric acid, worked twenty-four hours a day writing modules on a punch card machine for for four cents every six years, and when we got home, our PM would strangle us with a mouse cable.</p>
<p><strong>Jam:</strong> Right. I had to join morning standup at ten o’clock at night, half an hour before I went to bed, drink a cup of molten lava, work twenty-nine hours a day making punch cards with my teeth, paying the CEO for permission to come to work, and when we got home, our PM would kill us and dance about on our graves singing the Drupal song.</p>
<p><strong>Rob:</strong> But you try and tell the young Drupalists today that&hellip; and they won&rsquo;t believe ya'.</p>
<p><strong>ALL:</strong> Nope, nope&hellip;</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[The purpose of Music Notation and Theory]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/04/25/the-purpose-of-music-notation-and-theory/"/>
    <id>https://ohthehugemanatee.org/blog/2022/04/25/the-purpose-of-music-notation-and-theory/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-04-25T11:00:15+02:00</published>
    <updated>2022-04-25T11:00:15+02:00</updated>
    <content type="html"><![CDATA[<p>I talk a fair bit about the connections between music and engineering, and a <a href="https://news.ycombinator.com/item?id=31145804#31146334">discussion on Hacker News</a> came up about one of my favorite points. I&rsquo;ve adapted my comments here.</p>
<p>Classical musicians are overwhelmingly taught that their art form is about reproducing the notes, dynamics, and composer&rsquo;s intention as accurately as possible. This is <a href="">mistaking the finger for the moon</a>. Music, of any genre, is never about reproducing the notes and dynamics accurately. MIDI does that, and the day we can create historically informed MIDI we will have it perfected.</p>
<p><em>Music is the spontaneous communication and individuality that occurs <strong>while</strong> reproducing those notes and dynamics and intents.</em> Genres differ in which dimensions of freedom the musician can use, but not in that fundamental objective. In classical music it&rsquo;s what makes it worthwhile to hear different interpreters of the same piece. It&rsquo;s why people join the <a href="https://www.ticketstosee.com/tickets/bayreuth-festival-tickets/">10 year waiting list to see Wagner in Bayreuth</a>, when arguably the greatest rendition of The Ring in 100 years (<a href="https://www.bbc.com/culture/article/20140430-the-10-best-classical-recordings">Solti, Vienna</a>, fight me) is available relatively cheaply everywhere.</p>
<p>If that&rsquo;s the case&hellip;</p>
<ol>
<li>
<p>Encourage your classical friends to break out of the mindset of &ldquo;cooking the recipe the master chef made exactly.&rdquo; Audiences complain that classical music is sterile and doesn&rsquo;t speak to them, precisely because this mindset is so common among all but the most elite performers. Also, notice that NO elite performers approach their musicianship that way. As Ansel Adams said, &ldquo;craft facility liberates expression.&rdquo; The point of learning all this technique and theory is to give you the tools to express <em>yourself</em> in music. Mozart&rsquo;s notes are only the vehicle.</p>
</li>
<li>
<p>Satisfying the written note, the theory, and the historical practice, is not making music. They&rsquo;re simply there as tools of communication between musicians, to describe recurring patterns we hear. Otherwise we&rsquo;d be forever saying things like &ldquo;Beethoven 3, it&rsquo;s the one that starts with that thing where it sounds like it&rsquo;s going to end but really doesn&rsquo;t.&rdquo; Calling it a &ldquo;deceptive cadence&rdquo; or even a &ldquo;surprise chromatic sixth&rdquo; is just way easier and lets us operate on a higher level of abstraction. It&rsquo;s the same way &ldquo;dependency injector class&rdquo; tells you about a given chunk of code, and lets you reason about its place in the larger structure. Further, a great engineer isn&rsquo;t defined by their ability to reproduce a textbook dependency injection class, but rather by their ability to adapt the concept of dependency injection in the right places and times.</p>
</li>
</ol>
<p>I think it&rsquo;s a common mistake, especially in classical music, to think about musical traditions as rooted in clear rules and numerical relationships. It&rsquo;s analogous to thinking that language is rooted in clear rules and structures.</p>
<p>The truth is just the reverse, the rules, numbers, and structures are rooted in music. <em>They exist to describe an organic, emergent cultural mechanism that is continuously changing.</em> Ask yourself: which came first, music or theory? Language or grammar? There are plenty of musical styles with no formalized structure or numeric relationships, just as there are plenty of languages with no formalized structure or even spelling. <strong>Grammar and music theory/notation are fingers pointing at the moon, and we are looking at the finger.</strong></p>
<p>When we try to engineer systems to help people point at the moon, we <em>should</em> be focused on the finger. Musical structure and numeric relationships are the way people communicate about music, and the tools should speak their language. Unfortunately they then have to grapple with the painful inconsistencies in that language and those structures. Computer music comprehension has problems analogous to computer language comprehension - emergent complexity so high it took neural nets to finally achieve real utility.</p>
<p>PS - it&rsquo;s true that Western music has, near the bottom, some relationships that were mathematically derived by none other than Pythagoras, among others. Relationships of fourths, fifths, octaves, and equal temperment all had numeric justification&hellip; But that justification was still only there to describe the practice which had already become common, an explanation of &ldquo;why this sounds harmonious&rdquo; as well as a proscription for &ldquo;how to sound harmonious&rdquo;. That some rules are internalized by some generations and broken by others illustrates exactly the problem with the system.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Time Management Advice for Effective Leaders]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/03/17/time-management-advice-for-effective-leaders/"/>
    <id>https://ohthehugemanatee.org/blog/2022/03/17/time-management-advice-for-effective-leaders/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-03-17T16:26:28+01:00</published>
    <updated>2022-03-17T16:26:28+01:00</updated>
    <content type="html"><![CDATA[<p>I coach a number of leaders of various stripes around tech, and one issue that comes up for <em>everyone</em> is time management. I think of it more as &ldquo;obligation control.&rdquo;</p>
<p>As any kind of leader, you are the the first resource people tend to call, not just for obstacles, but for FYIs and general information. Your job may involve knowing what&rsquo;s happening across a broad set of projects. Just think of the math: if you have a small team of 5 people to lead, you are managing 200 hours of work per week. Everyone on your team has things where they want - or should want - a bit of your brain space. Add to that the number of communicaton channels (25), timezones, and schedules, and you are already overloaded.</p>
<p>So how do effective leaders manage their time? How do you control the virtually unlimited number of obligations that seek your attention? I had a chat with a colleague today on the subject and thought I&rsquo;d share.</p>
<p>Personally, I&rsquo;m very strict about my schedule and up front about that with everyone. I have very hard start and stop times every day, and I try to be dogmatic about my &ldquo;no meeting day&rdquo; (more on that in another post). This has two important effects:</p>
<ol>
<li>
<p>It forces me to &ldquo;switch off&rdquo; my work brain at a certain time every day, driving a baseline level of work/life balance.</p>
</li>
<li>
<p>It imposes scarcity on my schedule, forcing me to be very picky about which meetings I attend.
The basic skill you have to nurture is to say &ldquo;no&rdquo; more than you think you can.</p>
</li>
</ol>
<p>It&rsquo;s hard. Some people find it helps to have a scapegoat&hellip; so think in advance of a couple of good excuses. &ldquo;I need to circle back with my team about that,&rdquo; and &ldquo;My child has an appointment at that time,&rdquo; are favorites from colleagues I&rsquo;ve mentored. Personally I just say &ldquo;I&rsquo;m not available at that time.&rdquo; No one questions it.</p>
<p>It&rsquo;s important to follow the &ldquo;no&rdquo; with a request proceed without you (if that&rsquo;s possible), or with a suggested future time. Very very very few things we deal with have life or death consequences within a 1 week time-frame. As an aside: if it <em>was</em> possible to proceed without you, that should trigger you to reconsider if you really need to be at that meeting cadence at all. A good leader tries to grow their people so that they are not personally needed for anything.</p>
<p>Personally, I&rsquo;m very strict about my schedule and try to communicate it clearly with everyone. I have very hard start and stop times every day, and I try to be dogmatic about my &ldquo;no meeting day&rdquo; (more on that in another post). This has two important effects:</p>
<ol>
<li>
<p>It forces me to &ldquo;switch off&rdquo; my work brain at a certain time every day, driving a baseline level of work/life balance.</p>
</li>
<li>
<p>It imposes scarcity on my schedule, forcing me to be very picky about which meetings I attend.</p>
</li>
</ol>
<p>It sounds mean. It even <em>feels</em> a little mean. But the habit is essential: with every meeting, first consider if your attendance is really critical, or if you can get the information another way. Here are some alternative ways to consider:</p>
<ul>
<li>Meeting recordings</li>
<li>Written meeting notes</li>
<li>Have a representative attend and report back to you</li>
<li>Simply trust that the team will continue operating as they have in the past.</li>
</ul>
<p>You can make use of all of these tactics at different times. A word of caution, however: you will miss some of the context, particularly the unspoken conditions on the team, this way. It&rsquo;s important that you book regular sessions with the people actually doing the work to compensate for this loss. If you build healthy relationships with the people where &ldquo;the rubber meets the road&rdquo;, they will tell you when there is unspoken disfunction or other trouble on the team.</p>
<p>One final trick:  the schedule I tell people is a little shorter than my ACTUAL availability. I say I&rsquo;m only available till 7.30pm local time, but the truth is it&rsquo;s fine if I work till 8. Most nights that extra half hour is for closing out the day&rsquo;s business, but I can make someone feel special and extra-important by &ldquo;staying on late&rdquo; for them.</p>
<p>You might note this all leaves you with very little agency to impact your own schedule. It&rsquo;s quite reactive! I can offer you two tricks for proactively deciding where to impact and for making efficiency gains:</p>
<ol>
<li>
<p>I keep a <a href="https://en.wikipedia.org/wiki/Responsibility_assignment_matrix">RACI chart</a> of all the different areas of work, and I only allow myself ONE item in the Responsible column. Everything else I monitor from the outside, often asynchronously. This chart has no validity for the rest of my organization; I&rsquo;m ultimately responsible fr all of the threads. It still helps as a mental tool though, to make me consciously decide where I will lean in to have impact&hellip; and to limit those places. Without it, it is too easy for me to see potential impact everywhere and dilute my time too much.</p>
</li>
<li>
<p>I do everything I can to make sure the work is organized and information is structured so it&rsquo;s easy to context switch. That&rsquo;s the best source for time savings I have. I use a project management tool, so I can view the information from different perspectives:</p>
</li>
</ol>
<ul>
<li>I&rsquo;m always taking notes in a relevant ticket.</li>
<li>I can &ldquo;zoom out&rdquo; to overview all of the workstreams</li>
<li>I can track progress across all the workstreams</li>
<li>I can quickly report on status of any given workstream and issue with a high degree of granularity.</li>
<li>If I&rsquo;m feeling flashy I can even make dashboards about it</li>
</ul>
<p>A good leader makes conscious decisions about where to invest their time, builds relationships and collects information from up and down their whole organization, leverages their whole team and every available option to maximize their reach, and exercises conscious restraint to avoid over-committing. Hopefully these tips help you get a few steps closer.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Starting a New Role: Global Partner Strategy ISV CTO for Red Hat]]></title>
    <link href="https://ohthehugemanatee.org/blog/2022/02/16/starting-a-new-role-global-partner-strategy-isv-cto-for-red-hat/"/>
    <id>https://ohthehugemanatee.org/blog/2022/02/16/starting-a-new-role-global-partner-strategy-isv-cto-for-red-hat/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2022-02-16T21:17:35+01:00</published>
    <updated>2022-02-16T21:17:35+01:00</updated>
    <content type="html"><![CDATA[<p>This month I start a new challenge at Microsoft, as <strong>Global Partner Strategy ISV CTO</strong> for Red Hat. Translated: I am taking on technical leadership of the <a href="https://blogs.microsoft.com/blog/2015/11/04/microsoft-and-red-hat-partner-to-deliver-more-flexibility-and-choice/">Microsoft/Red Hat partnership</a>.</p>
<p><img src="/images/microsoft-redhat-puzzle.jpg" alt="Microsoft and Red Hat logos as puzzle pieces fitting together" title="The Microsoft-Red Hat partnership"></p>
<p>I had a wonderful and fruitful 4 years as a technical and team lead in the Commercial Software Engineering department, continuing my project-based work with some of Microsoft&rsquo;s biggest commercial customers. I got to build interesting projects with brilliant engineers, working in close collaboration with Volkswagen, Daimler, E.ON, the United Nations, and others. I got to learn what it&rsquo;s like working in an enormous company like Microsoft, and how global enterprises engage with each other.</p>
<p>Working with Red Hat is an exciting next step for me. Red Hat 5.2 was my first exposure to Linux back in 1998, and though I never did get it working successfully, I tried again a couple of years later and built my first homemade router and file server on 6.1. It was my go-to distro for years: when I ran a research lab in university, the systems and servers were all Red Hat 8. If someone had told me back then, that 20 years later <em>this</em> would be my job, I wouldn&rsquo;t have believed it.</p>
<p>I feel like my life has completed one of those little circles, and I am over the moon about where I landed.</p>
<p>Microsoft and Red Hat have a lot in common: they are both enterprise focused, with strong technical fundamentals in infrastructure and cloud native in particular. Of all the hyperscalers Azure is the one that treats hybrid cloud as a first class citizen, and not just an onramp to public cloud. Red Hat is also hybrid focused, with a suite of powerful products  including Openshift, Ansible, and others. Though Microsoft has made great leaps in open source, we have a lot to learn by working closely with a company for whom open source principles are truly fundamental. And enterprise-focused though RedHat is, no one meets enterprise where they live as well as Microsoft.</p>
<p>With all this in common, and so much to learn from each other, the partnership is already a warm and welcoming place to land. In my first weeks here it is clear that this is very fertile ground. Look for exciting things to come!</p>
<p>Thank you to all my colleagues from Microsoft Commercial Software Engineering. I had a lot of fun with you, and learned a lot. On to the next adventure!</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Building blocks for autonomous driving simulation environments]]></title>
    <link href="https://ohthehugemanatee.org/blog/2021/11/12/building-blocks-for-autonomous-driving-simulation-environments/"/>
    <id>https://ohthehugemanatee.org/blog/2021/11/12/building-blocks-for-autonomous-driving-simulation-environments/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2021-11-12T19:15:33+01:00</published>
    <updated>2021-11-12T19:15:33+01:00</updated>
    <content type="html"><![CDATA[<p>My team&rsquo;s work with Volkswagen on an autonomous driving simulation environments just turned into a new <a href="https://docs.microsoft.com/en-us/azure/architecture/industries/automotive/building-blocks-autonomous-driving-simulation-environments">Azure Well-Architected</a> page, which makes this a good time to add some behind-the-scenes commentary from real world experience. The architecture recommended there came from a lot of discovery and experimentation work, and suits quite a broad case that I think is not well represented (yet) in cloud computing products.</p>
<h2 id="the-challenge">The challenge</h2>
<p>Testing and CI-style validation in autonomous driving development are a tricky challenge. See, an autonomous driving simulation environment is actually composed of several different components, all working together. There is always a system for time synchronization, typically coupled with some kind of data bus that all the other components can plug into. This is where the challenges start:</p>
<ul>
<li>Each component is a separate executable with separate requirements. In our case we had both Windows and Linux components, several of which needed exclusive GPU access.</li>
<li>Exactly which components you need for a test is totally dependent on what part of the autonomous driving system you&rsquo;re testing. For example, a lidar sensor dev team needs very high fidelity models of objects it might be detecting. Reflective and absorbtive properties and shape require a lot of detail. So do weather conditions as far as visual occlusion and light scattering effects go. They <em>don&rsquo;t</em> need much of a road surface simulation, or pedestrians, or other drivers. The dev team working on the highway control component however, needs a totally different set of components, practically mutually exclusive.</li>
<li>Even within one team the components may vary, as lots of this stuff is non-deterministic. It sometimes helps to validate against more than one simulation of the same environment.</li>
<li>Components have hard and complex interdependencies, despite separate execution environments. For example, Linux and Windows based components may work closely together to simulate other cars and their drivers&rsquo; behaviors. This impacts startup order, where some tools can&rsquo;t even start initializing until others - or until certain sets of others - are reporting ready. The whole simulation of course, can&rsquo;t start until everything is ready. These tools are designed to start and operate in a highly stateful manner.</li>
<li>Because we&rsquo;re dealing with human lives and liability here, each component needs to be independently validated and verifiable, starting from a known good state. Five years later, we need to be able to reproduce as closely as possible the starting point of the simulation. &ldquo;Formal Reproducibility&rdquo; would be amazing, but depends on component development too much to be within our control.</li>
<li>Finally, some components are designed for human UI interaction. For example the test runner, which has its own system of debug breakpoints and step debugging that can&rsquo;t be replicated in code (&ldquo;break when the car is &lt; 2m from the obstacle, or after 68000 virtual milliseconds&rdquo;). Developers rely on this kind of debugging to build.</li>
</ul>
<p>Anyone with DevOps engineering experience probably looks at that laundry list and thinks &ldquo;may as well wish for the f@#$king moon while you&rsquo;re at it.&rdquo; I urge a bit more empathy. Remember, these developers are working with non-deterministic code to handle non-deterministic environments. This is not trivial stuff.</p>
<p>A platform for automated driving simulation needs to address those problems. Put another way, the <em>system</em> needs total composability and the ability to model a directed acyclic graph for startup and execution of the simulation; the <em>components</em> need flexibility in OS and interactivity.</p>
<p>My team came up with this (admittedly complex) diagram to describe the problems in abstract.</p>
<p><a href="/images/building-blocks-autonomous-driving-simulation-environments.png"><img src="/images/building-blocks-autonomous-driving-simulation-environments.png" alt="Diagram of the abstract components of the simulation system" title="Simulation system abstracted components"></a></p>
<p>Go ahead and zoom in for a better view, it&rsquo;s a lot to take in. It&rsquo;s easiest to look at the big segments (&ldquo;layers&rdquo; in the parlance of the image): User input, Orchestration, Building Block Factory, Simulation Infrastructure, Storage. Most of these are self explanatory. The building block factory is whatever system creates and validates components, the &ldquo;building blocks&rdquo; of any simulation run.</p>
<p>The workflow in the above system is straightforward to describe:</p>
<ol>
<li>The developer submits a human-readable document to the UI, which validates and passes it to the orchestration layer. The orchestration layer.</li>
<li>The orchestration layer interprets the document and provisions the necessary resources - whatever they may be - in an appropriate networking, monitoring, and storage context. The resources themselves come from the Building Block Factory, so they&rsquo;re all validated and conform to a documented API (the &ldquo;building block contract&rdquo;). The provisioned, started components in their network etc frame form the Simulation Infrastructure Layer.</li>
<li>The simulation runs autonomously, as far as possible. At the end of the simulation, outputs and logs are in the Storage Layer, and the Simulation Infrastructure Layer is deprovisioned.</li>
</ol>
<p>Just three steps. But the number of boxes and lines in that diagram belie just how complicated it is, on the inside.</p>
<p>The key to the whole system is clearly in the Orchestration Layer. There are great options for the Building Block Factory in most any CI/CD and versioning toolkit. The infrastructure layer is provided by the cloud provider. But this bit of interpreting the user input into a complex graph of components, starting them, monitoring for readiness, then starting execution&hellip; that&rsquo;s hard to find in a single tool.</p>
<p>Some parts of this problem space are well handled by a variety of tools. Provisioning and tearing down cloud resources, for example, could be done through any number of tools like Terraform, or even straight up ARM (Azure Resource Manager) templates. But then what monitors component startup state, managing that directed acyclic graph through to readiness? We evaluated several options for core technologies around which we could architect, including Puppet/Chef, Ansible, Terraform, and a collection of Azure services held together by Azure Functions and duct tape. The clear winner by a substantial margin was Kubernetes.</p>
<p>This was a suspicous conclusion for us, a team of infra specialists. We always have to be careful about our own technical biases, especially towards the new-and-cool tools in our kit (Had Nomad existed at the time it probably would have been a good contender, and made us feel a bit better about having at least two alternatives). But however you cut the problem, Kubernetes out of the box solved almost all of the problems in the Orchestrator Layer space, and provided a great starting point for the remaining boxes.</p>
<p>The boxes that Kubernetes does <strong>not</strong> cover OOTB, are the flexibility to run VMs or containers interchangeably (with an abstraction layer to ensure identical APIs), and the monitoring of buildup and simulation start/end.</p>
<p>There are a number of projects to provide the former, including integrations into Azure, and our choice, <a href="https://kubevirt.io/">kube-virt</a>. Importantly for us at the time: kube-virt was an earlier stage CNCI project and therefore still rough around the edges, but it was already a supported part of Azure RedHat OpenShift, and the supported way to run Azure IoT Edge on Kubernetes.</p>
<p>The latter problem is the core business logic, so it is the Right Focus for Custom Code. But even there Kubernetes gives us a good start, as it is already an event-driven architecture with deep connections to container lifecycle and readiness/liveness probes. What&rsquo;s more, the model of Kubernetes objects and controllers already implements the concept of a larger object abstracting a group of lower-level ones, in Deployments.</p>
<p>In fact, we found that our custom controller would have to implement a CRD quite similar to a Kubernetes Deployment. We could even extend the Deployment object to do it. The user ultimately could provide a simple YAML like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">my simulation run</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">start</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">component</span>: <span style="color:#ae81ff">test-runner</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">command</span>: <span style="color:#ae81ff">C:\start.exe</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">components</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">sync-server</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">sim/sync-server:5.4-dev</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">test-runner</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">sim/test-runner:3.2.2</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">interactive</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">environment-sim</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">sim/vtd:2.1</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">requires</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">gpu</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">depends</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#ae81ff">test-runner</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#ae81ff">sync-server</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">results</span>:
</span></span><span style="display:flex;"><span>       <span style="color:#f92672">storageClass</span>: <span style="color:#ae81ff">AzureBlob</span>
</span></span></code></pre></div><p>Given some metadata on each component, this is probably sufficient information for a controller to create the required deployments and run the simulation.</p>
<ul>
<li><code>test-runner</code> is a Windows VM image, as indicated by the component&rsquo;s own metadata. The <code>interactive</code> flag indicates that the controller should return a proxy to the RDP port on the VM.</li>
<li>Other components are both containers.</li>
<li>One container shows a resource request for a gpu core, which is handled by assigning the container to the right node pool.</li>
<li>That container also declares its&rsquo; dependencies. Probes are defined in the component metadata; the kubernetes controller watches for both dependencies to pass their Readiness probes, and then starts <code>environment-sim</code>.</li>
<li>Once all three components pass their Readiness probes, <code>spec.start.command</code> is run inside <code>spec.start.component</code>. When the command terminates, the simulation is considered complete.</li>
<li>The <code>spec.results</code> key helps the Controller create a PVC for results. The actual PVC is later accessible through a well-known naming scheme, such as <code>[datetime]-[name]</code>.</li>
<li>Component definitions include default ports to expose, but by default nothing is exposed to the outside world.</li>
</ul>
<p>Of course the actual Deployments and/or Replicasets involved need a lot more information than this. But that information is all consistent enough between runs that it can be templated with default values. The structure of this CRD is similar enough to other Kubernetes objects, that we can allow the user freedom to add other keys from the container spec, for example, to override those defaults. The 99% case however, would profit from opinionated defaults.</p>
<h2 id="implications">Implications</h2>
<p>If you&rsquo;ve followed along this far, you&rsquo;ve noticed that this domain is not unique to automotive simulation. Lots of computational problems require a directed acyclic graph for building up interdependent components, with a defined execution and teardown afterwards. But execution automation tools like batch runners rarely offer the kind of composability that has become the norm in service-oriented infrastructure. The key insight that this approach offers, is that an event-driven architecture for managing infrastructure, like Kubernetes, has a lot to offer in discrete computational tasks as well. This is particularly the case in very domain-knowledge heavy areas like simulation, but probably also in data science, IoT, biochemistry, and others.</p>
<p>This diagram, architecture, and first steps with a commercial customer were only the beginning. I&rsquo;m no longer on that project, but I look forward to seeing how it develops.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[The Cluster in My Closet - Advice for running kubernetes at home]]></title>
    <link href="https://ohthehugemanatee.org/blog/2021/05/15/the-cluster-in-my-closet-advice-for-running-kubernetes-at-home/"/>
    <id>https://ohthehugemanatee.org/blog/2021/05/15/the-cluster-in-my-closet-advice-for-running-kubernetes-at-home/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2021-05-15T15:00:05+01:00</published>
    <updated>2021-05-15T15:00:05+01:00</updated>
    <content type="html"><![CDATA[<p>What do most people do with their old computers? I&rsquo;ve never been good at getting rid of mine. They were all repurposed into servers, running whatever key services for my household I could think of at the time. This year I decided to move out of the &ldquo;old sysadmin&rdquo; patterns of my roots, and try running my homelab in a more modern way. That&rsquo;s right: I set up a kubernetes cluster.</p>
<p>All my old devices now go to the great cluster in the sky, and live on serving us files and media, blocking ads, and running small automations. They&rsquo;re happy, now.</p>
<p>They may be happy, but I am not. I thought I was comfortable with Kubernetes, but boy was I wrong. Turns out, I was comfortable with kubernetes&hellip; in a stable, homoegeneous, cloud-based environment. Turns out, those cloud vendors really do cover a lot of complexity for you: using Azure Kubernetes Service to manage enterprise and microservices apps is <em>way easier</em> than running your own cluster for the kind of apps you use in the home.</p>
<p>My home cluster consists of four Raspberry pi 4s, two old laptops, one mini desktop, and a pine64 laptop. First pain point: that&rsquo;s a mix of CPU architectures and capabilities. I&rsquo;ve got it all covered: x64, arm7, and arm64&hellip; and many container images are only built for a subset of those. At least the laptops are x64, but they&rsquo;re different makes and models, with different quirks. One of them has an unreliable USB port, which wreaks intermittent havoc with its USB ethernet adapter. The other has a dead internal IDE chip, and has to boot off of an external drive duct-taped to the back of the screen. And of course the whole thing lives in my laundry room, where temperatures and vibration from the floor will vary with the drying cycle, and where dust is all-pervasive.</p>
<p>I&rsquo;m happy I&rsquo;ve stuck with it - this has made me <em>really</em> learn the ins and outs of detailed troubleshooting in kubernetes. I&rsquo;ve a much better understanding now of <em>why</em> different abstractions may exist, in a very practical way. Here are some of the fun features and workarounds I have in place. Each of these could be their own post, someday:</p>
<ul>
<li>Lots of home-oriented services run with SQLite as a backing database, which is a problem because sqlite&rsquo;s normal &lsquo;wal mode&rsquo; of accessing database files has a hard dependency on block-level file access. That means network filesystems, and all k8s&rsquo; built in abstractons -  are out.
<ul>
<li>I ran with iSCSI devices for awhile, but the unreliability of home networking hardware was sufficient to produce data corruption every few months.</li>
<li>I wrote my own sidecar service to periodically freeze a shared PV and sync all the files to NFS. But if the freeze comes at a bad time for sqlite, that will also cause data corruption.</li>
<li>Longhorn doesn&rsquo;t have images for armv7 yet.</li>
<li>Finally I settled on using the local provisioner for volumes, and using <a href="https://github.com/benbjohnson/litestream">litestream</a> to back the DBs up to NFS. This seems to be working well&hellip; even though the Pi SD cards are a slow place to write even interim data. Not to mention, when you use the local provisioner, your pod is always scheduled back to the same node. That breaks the flexibility which is half of the value of the system in the first place!</li>
</ul>
</li>
<li>Very few container images offer all three of my CPU architectures, so every one of my manifests needs to use <code>NodeSelector</code>.</li>
<li>I&rsquo;m running a single master k3s node on one of the Raspberry Pi 4s. That&rsquo;s plenty of capacity for a normal load, but when you add monitoring through <a href="https://www.netdata.cloud/">Netdata</a> and prometheus, log monitoring through Loki and Grafana, and a handful of multi-master applications, the load from cluster DNS can cause problems. I ended up implementing <a href="https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/">Nodelocal DNS cache</a> to lighten the load.</li>
<li>The tiny fans that come on most Raspberry Pi cases are not reliable in the difficult environment of my laundry room. I&rsquo;ve had to replace all the case fans several times and eventually bought a rack for the Pis.</li>
<li>One of my most critical services is Plex, which does much better with hardware decoding capabilities. I&rsquo;m using <a href="https://github.com/kubernetes-sigs/node-feature-discovery">node-feature-discovery</a> to get node capabilities into labels, and <a href="https://github.com/intel/intel-device-plugins-for-kubernetes/blob/main/cmd/gpu_plugin/README.md">intel-gpu-plugin</a> to make the hardware available in the pod. Oh, but node feature discovery doesn&rsquo;t have a build for armv7, so that only works on some nodes.</li>
<li>I use images from <a href="https://linuxserver.io/">linuxserver.io</a>, which are great&hellip; but all based on <a href="https://github.com/just-containers/s6-overlay">s6_overlay</a>. This means they&rsquo;re not compatible with common privilege restriction approaches on kubernetes like security contexts. Fortunately(?) they support including arbitrary scripts on container startup, so I can hack my way around most problems.</li>
<li>Internal services often prefer to use HTTPS. Fair enough, but they are internal services, so letsencrypt can&rsquo;t validate/issue certificates for them. That means either an internal CA, or app configurations that allow self signed certs.</li>
<li>I mentioned I have one node with an unreliable ethernet connection. I tuned kubernetes&rsquo; heartbeat and status check timings to minimize downtime when that node disappears, detecting it early and redistributing its&rsquo; pods. But it&rsquo;s a delicate balance: it&rsquo;s easy to get the math wrong, or just be overzealous, and your nodes start popping into <code>NotReady</code> state with no discernable reason. Of course that disrupts intra-service communication, which can cause cascading failures.</li>
<li>One of my cats knocked a couple of nodes down while I was away on vacation without physical access to the machines. I ended up building a private network connection to Azure and adding support for scaling with VMs there, to keep services running.</li>
</ul>
<p>It goes on and on. All of these are problems that you basically never encounter when running on a cloud provider. Your money really does go to a valuable purpose.</p>
<p>On the other hand, my graveyard of machines has taught me more about internals of enterprise orchestration than I ever would have learned by implementing in real-world enterprise contexts. So it&rsquo;s been worth it for me professionally, at least.</p>
<h2 id="whats-the-point">What&rsquo;s the point?</h2>
<p>The moral of the story is: running a home kubernetes cluster is a lot harder than you think; and if you have half a brain you already think it&rsquo;s pretty hard. It definitely does have benefits (beyond learning) though! My home services <em>are</em> actually self-healing, easy to diagnose, and very resiliant to failure. Capacity planning is a non-issue. I have a repo of human-readable files which describe my application environments in their entirety. The trade off has been worth it for me, but only when I look through a pretty broad lens. I could have spent less time on this had I just stuck with docker-compose, for example.</p>
<p>If you&rsquo;re considering using kubernetes for your home lab, here&rsquo;s my hard-won advice for you:</p>
<ul>
<li>Use uniform nodes. Identical CPUs and capabilities make your life so much better.</li>
<li>Use a lightweight kubernetes distro, like <a href="https://k3s.io/">k3s</a>. It&rsquo;s all API compatible anyway and the community will be full of users in similar situations to yours.</li>
<li>Don&rsquo;t worry about always doing things the Kubernetes Way. For many home applications, StatefulSets really do make for a more reliable result.</li>
<li>Set up centralized logging first. The <a href="https://github.com/grafana/helm-charts/tree/main/charts/loki-stack">Loki stack</a> helm chart is relatively turnkey.</li>
<li>Then set up centralized monitoring. <a href="https://netdata.cloud/">Netdata</a> beats the hell out of manually configuring everything in grafana.</li>
<li>Keep your manifests in a repo, for the love of all that is good in the world.</li>
<li>If any of your services are mission critical for your family members (Nexcloud and Plex in my house), leave them out of the cluster for as long as possible. Architect those applications as High-Availability. In my case, that means a multi-master mariadb cluster, multiple web heads, and layered failover&hellip; with all the health checks I can think of.</li>
<li>Automate node setup with rancher, ansible, or similar. It&rsquo;s hard to make home hardware act like &ldquo;cattle instead of pets&rdquo;; use every tool at your disposal.</li>
<li>Keep a timestamped log of every change you make that isn&rsquo;t in a YAML file, and every problem you encounter. Often that&rsquo;s the fastest route to find your foot guns.</li>
<li>Have fun! Remember you&rsquo;re (hopefully) not doing this to be pragmatic. Don&rsquo;t listen to the haters, as long as <em>you&rsquo;re</em> getting what <em>you</em> want out of the experience.</li>
</ul>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[5 Project Manager Comments That Out You as an Amateur]]></title>
    <link href="https://ohthehugemanatee.org/blog/2021/03/22/5-project-manager-comments-that-out-you-as-an-amateur/"/>
    <id>https://ohthehugemanatee.org/blog/2021/03/22/5-project-manager-comments-that-out-you-as-an-amateur/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2021-03-22T10:13:04+01:00</published>
    <updated>2021-03-22T10:13:04+01:00</updated>
    <content type="html"><![CDATA[<p>A lot of the project managers you&rsquo;ll meet in technical consulting are new. That&rsquo;s OK! The number of developers - and correspondingly, their project managers - is growing exponentially from year to year. Even if we could assign everyone a mentor with at least a year of experience, there wouldn&rsquo;t be enough mentors to go around. A lot of people are learning as they go, and land in a role because someone thinks they have an aptitude.</p>
<p>That said, the downside to this explosive growth is that there aren&rsquo;t always strong norms that show newcomers how highly successful professional technical project management actually <em>looks</em>, in practice. People end up substituting what makes them feel good, or what suits their personality, for what&rsquo;s actually successful in an evidence-based way.</p>
<p>So here&rsquo;s a bit of insight in list-icle format for you: 10 things that learner PMs say that make them look like amateurs.</p>
<h2 id="5-oh-wow-look-at-your-calendar-i-cant-work-that-way-planning-every-block-of-time">5) &ldquo;Oh wow, look at your calendar. I can&rsquo;t work that way, planning every block of time.&rdquo;</h2>
<p>Effective and precise management of a project extends necessarily into precise control of time. A good project manager has a lot of competing demands on their time. &ldquo;Ad hoc&rdquo; planning ultimately translates into &ldquo;whatever fire is most visible for me at the moment.&rdquo; This is not the same as the most important activities. These people will always find themselves with fires to put out, however, which can make them feel like real heroes!</p>
<h2 id="4-whatever-the-customer-says">4) &ldquo;Whatever the customer says&hellip;&rdquo;</h2>
<p>It often feels like the customer should always be right. It <em>is</em> their property you&rsquo;re building, after all! But a consultant&rsquo;s job is (usually) not just blindly implementing. The real value is in the consultant&rsquo;s unique insight: if the customer knew how best to solve the problem, they would have done it themselves! This means that good consulting necessarily involves a certain amount of conflict, or &ldquo;push back&rdquo; if you prefer. Blindly doing whatever the customer thinks is not the mark of a professional.</p>
<h2 id="3-we-have-to-get-this-done-on-time-i-dont-care-what-it-takes">3) &ldquo;We <em>have</em> to get this done on time, I don&rsquo;t care what it takes&rdquo;</h2>
<p>No, we don&rsquo;t. Development is a creative activity; tasks are very hard to estimate accurately. No matter what the customer&rsquo;s deadline is, even if you set the deadline yourselves, it does not have priority over the reality of how long it takes to build something well. A good PM identifies early when reality will diverge from estimated timelines, and <em>manages</em> the situation by adjusting project scope, team size, or deadlines.</p>
<h2 id="2-ive-been-working-60-hour-weeks">2) &ldquo;I&rsquo;ve been working 60 hour weeks&hellip;&rdquo;</h2>
<p>Especially in North America, it&rsquo;s easy for overwork to feel like a virtue. Leaving the cultural aspect aside, the project manager&rsquo;s <em>job</em> is to control scarce work resources over time. If they can&rsquo;t manage their own scarce work resources over time (only 40 hours each week!), they certainly can&rsquo;t manage for a team of other people!</p>
<h2 id="1-i-dont-bother-with-estimates--how-many-hours-will-this-take">1) &ldquo;I don&rsquo;t bother with estimates&hellip;&rdquo; / &ldquo;How many hours will this take?&rdquo;</h2>
<p>There is an entire (Nobel Prize winning) field of research on best approaches to estimation. To no one&rsquo;s surprise, estimation in time is one of the least accurate options. So inaccurate, that it appears in some situations to be worse than no estimates at all. Both of these comments indicate a PM who has never run estimates using any of the more effective approaches suggested by research, such as three-part estimation, third party estimation, or abstracted unit estimation. Any of those produce usefully accurate results. My own team&rsquo;s timelines land within 5% of reality over a 4 month project, and we can predict updates based on changes in scope or architecture weeks or months in advance. We&rsquo;re not doing anything magical or revolutionary, just some basic math with abstracted estimation units.</p>
<p>I hope this helps some new PMs out there. <strong>TL;DR</strong>:</p>
<ul>
<li>Get control of your schedule. Use some kind of a system to ensure you&rsquo;re spending your scarce time on the most <em>important</em> tasks, not the most urgent.</li>
<li>Don&rsquo;t be afraid to push back on stupid customer requests! The customer is there for <em>your</em> advice. If something won&rsquo;t work, or is counter-productive, or endangers the timeline, <em>tell them about it</em>. Document that you had the conversation. If the customer still decides to proceed, you can proceed with a clear conscience and likely a better idea of why.</li>
<li>No one wins from rushed, poor quality development at the end of a project. When it starts to look like you&rsquo;ll have a timeline crunch, <em>talk to your customer</em>. Help them decide how to adjust the scope, team size, or deadline so the crunch disappears.</li>
<li>Limit your work time to &ldquo;just&rdquo; 40 hours per week. Do not take your laptop home with you, do not open your email after hours. This forces you to be effective with the above.</li>
<li>Learn about different estimation systems. Go read some articles about how different Agile frameworks do it, and consider the core principles that they all share. Start estimating in <em>some</em> way that&rsquo;s more accurate than asking people for hours, and track the variance from reality over time. Estimate + variance = accurate, actionable estimates.</li>
</ul>
<p>The core job of a project manager is to <em>talk with your customer</em> when it looks like you need to adjust scope, deadline, or team size&hellip; and to do that as early as possible. If you make some effort towards accurate estimated timelines, constantly compare your timeline to what&rsquo;s happening in reality, and help your customer adjust scope/team/deadline when the numbers seem off&hellip; Congratulations, you are a good project manager.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[How to Format Video for Fast Playback on the Web]]></title>
    <link href="https://ohthehugemanatee.org/blog/2020/11/11/how-to-format-video-for-fast-playback-on-the-web/"/>
    <id>https://ohthehugemanatee.org/blog/2020/11/11/how-to-format-video-for-fast-playback-on-the-web/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2020-11-11T20:47:39+02:00</published>
    <updated>2021-04-14T20:47:39+02:00</updated>
    <content type="html"><![CDATA[<p>It&rsquo;s a pain in the ass to get your video optimized for web. Not only do you have to work out codecs and their support across browsers, but even within codecs there are tricks to help it stream more easily. Here&rsquo;s the short version.</p>
<p>In terms of format, there are some really great options if you only care about compatibility with the most popular browsers (ahem - chrome). webm/VP8 is the way to go here, or webm/VP9 if you really only care about chrome. You&rsquo;ll get a very small filesize and high quality, with hardware playback on the latest devices. But if you want to include Safari and others, or if you care about non-flagsip devices or ones more than a couple of years old, it has to be MP4/h.264 . Your filesize won&rsquo;t be as small, but it will play with hardware acceleration on any device since about 2011.</p>
<p>One common opimization is to use <code>@media</code> queries to dfault to webm/VP9 and fallback to other formats based on the browser capabilities. Do that if you want, but for my use cases I prefer simplicity over bandwidth savings.</p>
<p>I use <a href="https://ffmpeg.org/">ffmpeg</a>, because it does everything except my laundry (and I&rsquo;m pretty sure that&rsquo;s because I don&rsquo;t know th right flags for &ldquo;spin cycle&rdquo;). To convert one video into a format that is universally compatible:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ffmpeg  -i input.mp4 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -c:v libx264 <span style="color:#75715e"># h264 encoder \</span>
</span></span><span style="display:flex;"><span>  -profile:v main <span style="color:#75715e"># h264 options to make available. for &gt;5 year old phones, use &#34;baseline&#34;. \</span>
</span></span><span style="display:flex;"><span>  -preset veryslow <span style="color:#75715e"># slowest option, best compression \</span>
</span></span><span style="display:flex;"><span>  -s hd720 <span style="color:#75715e"># rarely need higher resolution than this \</span>
</span></span><span style="display:flex;"><span>  -b:v 1.5M <span style="color:#75715e"># sets the video bitrate \</span>
</span></span><span style="display:flex;"><span>  -an <span style="color:#75715e"># no audio track; this is for a background video \</span>
</span></span><span style="display:flex;"><span>  -movflags +faststart <span style="color:#75715e"># allows playing while the file is downloading \</span>
</span></span><span style="display:flex;"><span>  output.mp4
</span></span></code></pre></div><p>The non-obvious options:</p>
<ul>
<li><code>-profile:v main</code> sets which features of h264 to use. The standard evolved over time, and depending on just how new your playback devices are, there improvements available. Most of the time <code>main</code> is the right choice, but if you want to target older devices use <code>baseline</code>.</li>
<li><code>-b:v 1.5M</code> sets a 1.5 Megabit bitrate. You should experiment with this number to find the right tradeoff between quality and filesize. You can find out the bitrate of your original source with <code>ffinfo</code>, or with the output of this <code>ffmpeg</code> command.</li>
<li><code>-an</code> puts no audio in the finished video. I worked this out while making a background video for a site, so ths was appropriate. Side benefit that it simplifies this blog post. :) If you want audio, you could replace this with <code>-codec:a aac</code> is a very compatible option.</li>
<li><code>movflags +faststart</code> is critical if you want the video to play before being fully downloaded. Video files contain multiple streams of data (at least one stream of video and one of audio). Usually the metadata about each stream is at the <em>end</em> of the filee, meaning that a player has to load the whole file before it knows how to play the data. Faststart breaks the file up into chunks and puts the metadata in with each chunk, so your player can start playing the video as soon as the first bit is loaded.</li>
</ul>
<p>That&rsquo;s all you really need to make good looking video files, optimized for web, which play before they&rsquo;re fully downloaded, and work on every device. Have fun!</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Chinese censorship, values decisions, and free software]]></title>
    <link href="https://ohthehugemanatee.org/blog/2019/10/08/chinese-censorship-and-free-software/"/>
    <id>https://ohthehugemanatee.org/blog/2019/10/08/chinese-censorship-and-free-software/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2019-10-08T19:10:15+00:00</published>
    <updated>2019-10-08T19:10:15+00:00</updated>
    <content type="html"><![CDATA[<p>Chinese censors are in the news this weekend: <a href="https://variety.com/2019/gaming/news/blizzard-bans-blitzchung-hearthstone-hong-kong-china-statement-1203363050/">Blizzard banned a grandmaster Hearthstone player for supporting Hong Kong in an interview</a>, <a href="https://www.newsweek.com/south-park-band-china-why-banned-china-s23-e02-23x02-1463762">South Park was added to the &ldquo;banned&rdquo; list for their critique of Chinese censorship in Hollywood</a>, and <a href="https://www.cnn.com/2019/10/07/business/houston-rockets-nba-china-daryl-morey/index.html">an NBA franchise owner&rsquo;s job was on the line for a pro-HK tweet</a>. Increasingly, Western companies are finding themselves up against the wall to prioritize western liberal values against access to the enormous Chinese market. We can imagine the difficult, high-pressure decisions for executives in this situation.</p>
<p>As a consumer it feels like we don&rsquo;t face that kind of pressure, or that kind of decision. Those of us whose choices do not impact thousands of employees&rsquo; livelihoods, and millions of consumers&rsquo; information environments, seem to have little leverage. If <a href="https://www.theguardian.com/technology/2019/sep/25/revealed-how-tiktok-censors-videos-that-do-not-please-beijing">TikTok quietly hides any videos of Hong Kong unrest</a>, or <a href="https://sputniknews.com/asia/201801121060701327-china-delta-apology-taiwan-tibet/">Delta lists Taiwan as a part of China</a>, or <a href="https://mothership.sg/2018/01/marriott-zara-qantas-taiwan-tibet-sovereignty/">Marriott includes cities in Tibet, Taiwan, Hong Kong, and Macau as inside China</a>&hellip; we can&rsquo;t tell the difference. The whole point of censorship is that you remain blissfully unaware of what you&rsquo;re missing.</p>
<p>When we as consumers think about China&rsquo;s censorship power in our lives the important question is: <em>How can we tell when it&rsquo;s happening</em>? It&rsquo;s not unthinkable for Chrome to invisibly hide certain content, or Facebook, or your iPhone. In fact most of the information services you use likely do this already, in the name of curating content you will &ldquo;like.&rdquo; This isn&rsquo;t necessarily a problem in and of itself; it only gets problematic when the filters are <em>invisible</em>.</p>
<p>This is a part of the point of Free and Open Source software. Filters can&rsquo;t be applied in secret, and by definition you have the right to fork software to do things <em>your</em> way if you like. It&rsquo;s <em>your</em> right to have <em>your</em> device behave the way <em>you</em> want it to. And black boxes should inspire some suspicion that they may be doing things you don&rsquo;t like.</p>
<p>At this point it would be easy to descend into the typical open source rant: if only Facebook open sourced its filters! If only Instagram were open! I&rsquo;ll leave that for others. I want to point out something a little subtler:</p>
<p>The executive&rsquo;s values decision about western liberalism vs a larger addressable market, is remarkably close to the developer&rsquo;s values decision about a license that respects user control vs the easier monetization and control of a black box. That decision in turn is related to the consumer&rsquo;s values decision, about software that respects your rights vs the convenience of a popular black box.</p>
<p>It&rsquo;s easy to criticize Blizzard&rsquo;s decision to bow to Chinese sensibilities. But how do <em>you</em> decide, on your own much smaller scale? Do <em>you</em> prioritize values over convenience? Or do you accept the censored experience of a black box as a user? Do you enjoy the control of that black box as a developer?</p>
<p>Hearthstone players are by definition running a black box OS. Users of iOS, Windows, and Android demonstrate comfort with invisible censors in other parts of their devices. Is their presence in video streams and gameplay all that different? Do they have any right to complain? Having given away control to black boxes, perhaps complaining is the way they can impact the way their device runs.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[An Open Letter to my MEPs about Article 17 (formerly article 13)]]></title>
    <link href="https://ohthehugemanatee.org/blog/2019/03/21/an-open-letter-to-my-meps-about-article-17-formerly-article-13/"/>
    <id>https://ohthehugemanatee.org/blog/2019/03/21/an-open-letter-to-my-meps-about-article-17-formerly-article-13/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2019-03-21T23:44:43+00:00</published>
    <updated>2019-03-21T23:44:43+00:00</updated>
    <content type="html"><![CDATA[<p>The proposal for a directive on copyright in the digital single market is disastrous for the <strong>EU economy, culture, and democracy in the digital world</strong>. It is particularly bad for my country of Germany, as a leading light in Europe in all three areas. I am writing all of my MEPs listed in support of this impossibly bad proposal.</p>
<p>The German and European economies would be terribly damaged by this article, which <strong>effectively rules out small and medium sized competition in favor of the largest incumbents</strong>. I work for Microsoft on precisely the kind of machine-understanding tasks involved in the copyright filter requirement. I can tell you with authority: <strong>it is an impossible task which only the deepest pockets can approach</strong>. <strong>Article 17 makes Germany and Europe into hostile venues for Internet startups</strong>. The next generation of Youtubes, Soundclouds, and Netflixes are not possible under this Article - unless of course, it&rsquo;s an existing mega-corporation who decides to start it. <strong>This is a tremendous handicap in the fastest growing sector of the global economy.</strong></p>
<p>The disaster for culture exemplifies the impossibility of such a filter. My &ldquo;side business&rdquo; is an entertainment company, making opera music accessible for tens of thousands of Europeans every year. The entire classical music industry opposes digital filters because we all know the consequence: <strong>a machine or untrained human can&rsquo;t tell the difference between the 300 different versions of Bach&rsquo;s Wohl Temperierte Klavier</strong>. The piece is identified as probably copyrighted because copyrighted versions exist, and taken down as a precaution. <strong>Famously even the European anthem, <em>An die Freude</em>, suffers automated takedowns</strong> because there are copyrighted versions of the piece. <strong>Asking platforms to bulk-police content means that the more influential a piece of music is for our culture - that is to say, the more versions of it exist - the more prone it is to spurious blocking on copyright grounds.</strong></p>
<p>As much as the technical infeasibility and halting the spread of the most important pieces of our European culture bother me,** the effect on democracy in the digital age is the worst part of this Article. User-generated content is foundational to online discussion**. It is precisely this content which enriches online debate and engagement, which reaches younger generations and pulls them into a very participatory democratic environment. You&rsquo;ve no doubt heard that <strong>memes are culture</strong>, but <strong>they are also the medium of exchange in the biggest democratic commons humankind has ever created</strong>. Legislation which shuts down this medium of exchange, or which forces the commons into channels controlled by the largest (foreign) economic actors in history, is bad for the EU.</p>
<p><strong>Perhaps the world needs a digital copyright equivalent of Brexit</strong>, to scare everyone else away from the copyright lobby. Perhaps we all need a material example to see just how poorly the copyright lobby&rsquo;s 1960&rsquo;s-era ideology fits the 21st century economy.</p>
<p><strong>But I would prefer it not be my country, my continent that makes an example of itself.</strong></p>
<p>Please heed <a href="https://saveyourinternet.eu/statements/">the warnings from internet experts, the UN Special Rapporteur on Freedom of Expression, NGOs, programmers, and academics</a>. I urge you to reconsider your position on this digital Brexit.</p>
<p>Sincerely,</p>
<p>Campbell Vertesi</p>
<p>Berlin, Germany</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[BTRFS and free space - emergency response]]></title>
    <link href="https://ohthehugemanatee.org/blog/2019/02/11/btrfs-out-of-space-emergency-response/"/>
    <id>https://ohthehugemanatee.org/blog/2019/02/11/btrfs-out-of-space-emergency-response/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2019-02-11T13:58:39+00:00</published>
    <updated>2019-02-11T13:58:39+00:00</updated>
    <content type="html"><![CDATA[<p>I run BTRFS on my root filesystem (on Linux), mostly for the quick snapshot and restore functionality. Yesterday I ran into a common problem: <strong>my drive was suddenly full</strong>. I went from 4GB of free space on my system drive to 0 in an instant, causing all sorts of chaos on my system.</p>
<p>This problem happens to lots of people because BTRFS doesn&rsquo;t have a linear relationship to &ldquo;free space available&rdquo;. There are a few concepts that get in the way:</p>
<ul>
<li><strong>Compression</strong>: BTRFS supports compressing data as it writes. This obviously changes the amount of data that can be stored. - 50MB of text may take only 5MB &ldquo;room&rdquo; on the drive.</li>
<li><strong>Metadata</strong>: BTRFS stores your data separately from metadata. Both data and metadata occupy &ldquo;space&rdquo;.</li>
<li><strong>Chunk allocation</strong>: BTRFS allocates space for your data in chunks.</li>
<li><strong>Multiple devices</strong>: BTRFS supports multiple devices working together, RAID-style. That means there&rsquo;s extra information to store for every file. For example, RAID-1 stores two copies of every file, so a 50MB file takes 100MB of space.</li>
<li><strong>Snapshots</strong>: BTRFS can store snapshots of your device, which really store more like a diff from the current state. How much data is in the diff depends on your current state&hellip; so the snapshot itself doesn&rsquo;t have a consistent size.</li>
<li><strong>Nested volumes</strong>: BTRFS lets you divide the filesystem into &ldquo;subvolumes&rdquo; - each of which can (someday) have its own RAID configuration.</li>
</ul>
<p>It&rsquo;s easy to look at the drive and tell how many MiB of space has not been used yet. But it&rsquo;s very hard to accurately say how much of your data you can write in that space. For this reason the amount of &ldquo;free space&rdquo; reported on BRFS volumes by system utilities like <code>df</code> can jump a lot - like my disappearing 4GiB. Worse, the free space reported by general tools is misleading. BTRFS can run out of space while <code>df</code> still thinks you have lots available.</p>
<p>Let&rsquo;s walk through how BTRFS stores data, to understand the problem a bit better. Then we can solve it with some of BTRFS&rsquo; own tools.</p>
<h2 id="how-much-free-space-do-i-have">How much free space do I have?</h2>
<p>Rather than using general tools like <code>df</code> to answer this question, it&rsquo;s better to get more detail using the <code>btrfs</code> CLI tool.</p>
<p>BTRFS starts out with a big pool of raw storage, and allocates as it goes. You can get a listing of all the devices in a block device like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sudo btrfs <span style="color:#66d9ef">fi</span> show
</span></span><span style="display:flex;"><span>Label: <span style="color:#e6db74">&#39;OS&#39;</span>  uuid: c0d21ade-5570-41a3-b0cf-a5ce219e7a8e
</span></span><span style="display:flex;"><span>  Total devices <span style="color:#ae81ff">1</span> FS bytes used 31.74GiB
</span></span><span style="display:flex;"><span>  devid    <span style="color:#ae81ff">1</span> size 48.83GiB used 47.80GiB path /dev/nvme0n1p2
</span></span></code></pre></div><p>In this case, I only have one physical device involved. You can see that it gives me a total number of bytes allocated, compared to the total size. In another filesystem this might be the number reported to <code>df</code>. Not so with BTRFS! Let&rsquo;s dig deeper.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ btrfs <span style="color:#66d9ef">fi</span> df /
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Data, single: total<span style="color:#f92672">=</span>45.75GiB, used<span style="color:#f92672">=</span>30.56GiB
</span></span><span style="display:flex;"><span>System, single: total<span style="color:#f92672">=</span>32.00MiB, used<span style="color:#f92672">=</span>16.00KiB
</span></span><span style="display:flex;"><span>Metadata, single: total<span style="color:#f92672">=</span>2.02GiB, used<span style="color:#f92672">=</span>1.17GiB
</span></span><span style="display:flex;"><span>GlobalReserve, single: total<span style="color:#f92672">=</span>89.31MiB, used<span style="color:#f92672">=</span>0.00B
</span></span></code></pre></div><p>The &ldquo;total&rdquo; values here are the breakdown of what the first command counts as &ldquo;used&rdquo;. <code>btrfs fi df</code> shows us of the allocated space, how much is actually storing data, and how much is just empty allocation. In this case: on my 48GiB device, 47GiB is allocated. Of the allocation, 31GiB is actually storing data. Side note: if you&rsquo;re in a multi-drive situation this command will take into account RAID metadata.</p>
<p>Here&rsquo;s an easier view:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ sudo btrfs <span style="color:#66d9ef">fi</span> usage /
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Overall:
</span></span><span style="display:flex;"><span>    Device size:      48.83GiB
</span></span><span style="display:flex;"><span>    Device allocated:     47.80GiB
</span></span><span style="display:flex;"><span>    Device unallocated:      1.03GiB
</span></span><span style="display:flex;"><span>    Device missing:        0.00B
</span></span><span style="display:flex;"><span>    Used:       31.74GiB
</span></span><span style="display:flex;"><span>    Free <span style="color:#f92672">(</span>estimated<span style="color:#f92672">)</span>:     16.22GiB  <span style="color:#f92672">(</span>min: 16.22GiB<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    Data ratio:           1.00
</span></span><span style="display:flex;"><span>    Metadata ratio:         1.00
</span></span><span style="display:flex;"><span>    Global reserve:     89.31MiB  <span style="color:#f92672">(</span>used: 0.00B<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Data,single: Size:45.75GiB, Used:30.56GiB
</span></span><span style="display:flex;"><span>   /dev/nvme0n1p2   45.75GiB
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Metadata,single: Size:2.02GiB, Used:1.18GiB
</span></span><span style="display:flex;"><span>   /dev/nvme0n1p2    2.02GiB
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>System,single: Size:32.00MiB, Used:16.00KiB
</span></span><span style="display:flex;"><span>   /dev/nvme0n1p2   32.00MiB
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Unallocated:
</span></span><span style="display:flex;"><span>   /dev/nvme0n1p2    1.03GiB
</span></span></code></pre></div><p>This shows the breakdown of space allocated and used across all the devices in this block device. &ldquo;Overall&rdquo; is for the whole block device, and that &ldquo;Free (estimated)&rdquo; number is what gets reported to <code>df</code>.</p>
<p>This is a problem: <strong>most of my normal tools tell me I have 15GB free space. But if I write 1GiB more data, BTRFS will run out of space anyways.</strong> This issue is a pain in the ass and hard to diagnose. It&rsquo;s even harder to fix, since most of the solutions require having some extra space on the device.</p>
<h2 id="converting-unused-allocation-to-free-space">Converting unused allocation to free space</h2>
<p>So, why does BTRFS allocate so much space to store such a small amount of data? Here I am storing 31GiB of data in 47GiB of allocation, the used/total ratio is 0.66! This is very inefficient. It&rsquo;s an unfortunate consequence of being a copy-on-write filesystem - BTRFS starts every write in a freshly allocated chunk. But the chunksize is static, and files come in all sizes. So lots of the time, a chunk is incompletely filled. That&rsquo;s the &ldquo;allocated but not used&rdquo; space we&rsquo;re complaining about.</p>
<p>Fortunately there&rsquo;s a way to address this problem: BTRFS has a tool to &ldquo;rebalance&rdquo; your filesystem. It was originally designed for balancing the data stored across multiple drives (hence the name). It is also useful in single drive configurations though, to rebalance how data is stored within the allocation.</p>
<p>By default, <code>balance</code> will rewrite <em>all</em> the data on the disk. This is probably unnecessary. Chunks will be unevenly filled, but we saw above that the average should be about 66% used. So we&rsquo;ll filter based on data (<code>-d</code>) usage, and only rebalance chunks that are less than 66% used. That will leave any partially filled chunks which are more-filled than average.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Run it in the background, cause it takes a long time.</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs balance start -dusage<span style="color:#f92672">=</span><span style="color:#ae81ff">66</span> / &amp;
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Check status</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs balance status -v /       
</span></span><span style="display:flex;"><span>Balance on <span style="color:#e6db74">&#39;/&#39;</span> is running
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span> out of about <span style="color:#ae81ff">27</span> chunks balanced <span style="color:#f92672">(</span><span style="color:#ae81ff">5</span> considered<span style="color:#f92672">)</span>,  96% left
</span></span><span style="display:flex;"><span>Dumping filters: flags 0x1, state 0x1, 
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Or be lazy, and have bash report status every 60 seconds.</span>
</span></span><span style="display:flex;"><span>$ <span style="color:#66d9ef">while</span> :; <span style="color:#66d9ef">do</span> sudo btrfs balance status -v / ; sleep 60; <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>Balance on <span style="color:#e6db74">&#39;/&#39;</span> is running
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">3</span> out of about <span style="color:#ae81ff">27</span> chunks balanced <span style="color:#f92672">(</span><span style="color:#ae81ff">12</span> considered<span style="color:#f92672">)</span>,  89% left
</span></span><span style="display:flex;"><span>Dumping filters: flags 0x1, state 0x1, force is off
</span></span><span style="display:flex;"><span>  DATA <span style="color:#f92672">(</span>flags 0x2<span style="color:#f92672">)</span>: balancing, usage<span style="color:#f92672">=</span><span style="color:#ae81ff">66</span>
</span></span><span style="display:flex;"><span>Balance on <span style="color:#e6db74">&#39;/&#39;</span> is running
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">4</span> out of about <span style="color:#ae81ff">27</span> chunks balanced <span style="color:#f92672">(</span><span style="color:#ae81ff">13</span> considered<span style="color:#f92672">)</span>,  85% left
</span></span><span style="display:flex;"><span>Dumping filters: flags 0x1, state 0x1, force is off
</span></span><span style="display:flex;"><span>  DATA <span style="color:#f92672">(</span>flags 0x2<span style="color:#f92672">)</span>: balancing, usage<span style="color:#f92672">=</span><span style="color:#ae81ff">66</span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#75715e"># When the balance operation finishes:</span>
</span></span><span style="display:flex;"><span>Done, had to relocate <span style="color:#ae81ff">19</span> out of <span style="color:#ae81ff">59</span> chunks
</span></span></code></pre></div><p>There&rsquo;s a nice big differnce once it&rsquo;s finished:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ btrfs filesystem df /
</span></span><span style="display:flex;"><span>Data, single: total<span style="color:#f92672">=</span>32.53GiB, used<span style="color:#f92672">=</span>30.83GiB
</span></span><span style="display:flex;"><span>System, single: total<span style="color:#f92672">=</span>32.00MiB, used<span style="color:#f92672">=</span>16.00KiB
</span></span><span style="display:flex;"><span>Metadata, single: total<span style="color:#f92672">=</span>2.02GiB, used<span style="color:#f92672">=</span>1.17GiB
</span></span><span style="display:flex;"><span>GlobalReserve, single: total<span style="color:#f92672">=</span>84.67MiB, used<span style="color:#f92672">=</span>0.00B
</span></span></code></pre></div><p>That&rsquo;s 15GiB of space allocated for other use. My usage ratio is now 0.94. Huzzah! In some rare cases you may need to do this on the Metadata allocation (use <code>-musage</code> instead of <code>-dusage</code> above).</p>
<h2 id="if-youve-already-run-out-of-space">If you&rsquo;ve already run out of space</h2>
<p>If you have already run out of space, you can&rsquo;t run a <code>balance</code>! In that caseyou have to get sneaky. Here are your options:</p>
<h3 id="1-free-up-space">1) Free up space</h3>
<p>This is harder than it sounds. If you just delete data, it will probably leave those chunks partially filled and therefore allocated. What you really need is <em>unallocated</em> space. The easiest place to get this is by deleting snapshots. Start from the oldest one, since it will be the biggest.</p>
<p>Once you have a little bit of wiggle room, rebalance a small segment, like Metadata. Then proceed with rebalancing data as described above.</p>
<h3 id="2-add-some-space">2) Add some space</h3>
<p>Don&rsquo;t forget, a BTRFS volume can span multiple devices! I had to exercise this option recently. Add a device - a flash drive will do, but choose the fastest thing you can - and add it to the BTRFS volume.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Add your extra drive (/dev/sda).</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs device add -f /dev/sda / 
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Now run the smallest balance operation you can.</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs balance start -dusage<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> /
</span></span><span style="display:flex;"><span>Done, had to relocate <span style="color:#ae81ff">1</span> out of <span style="color:#ae81ff">59</span> chunks
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Remove the device, and run a proper balance.</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs device remove /dev/sda /
</span></span><span style="display:flex;"><span>$ sudo btrfs balance start -dusage<span style="color:#f92672">=</span><span style="color:#ae81ff">66</span> /
</span></span><span style="display:flex;"><span>Done, had to relocate <span style="color:#ae81ff">18</span> out of <span style="color:#ae81ff">59</span> chunks
</span></span></code></pre></div><p>Balance operations usually take a long time - more than an hour is not unusual. It will take even longer with slow flash media involved. For that reason, I use a very low balance filter (<code>-dusage=</code>) in this example. We only need to free up a teensy bit of space to run balance again without the flash disk in the mix.</p>
<p>And this last option is how I saved my computer last night. I hope this helps someone out of a similar predicament someday.</p>
<p><strong>Update to the update</strong>: Do not do this! A friendly commentor from the BTRFS community let me know that this is actually a <em>really bad idea</em>, since anything that interrupts your RAM will wreck your filesystem irreparably. Stick with the USB drive solution, above. Thank you <code>@Zygo</code> for the correction, and sorry for anyone who suffered for my learning.</p>
<p><strong>UPDATE</strong>: <del>Now that I&rsquo;ve had to do this a few times, it&rsquo;s <em>way</em> better to rebalance a full filesystem by adding a ramdisk to it. Not only is it faster than a flash device, it&rsquo;s also more reliable in most cases&hellip; and certainly for my kind of use case (a developer laptop) the important preconditions apply: lots of RAM, reliable power source. Here&rsquo;s the recipe:</del></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Create a ramdisk. Make sure /dev/ram0 isn&#39;t in use already before doing this!</span>
</span></span><span style="display:flex;"><span>$ sudo mknod -m <span style="color:#ae81ff">660</span> /dev/ram0 b <span style="color:#ae81ff">1</span> <span style="color:#ae81ff">0</span> 
</span></span><span style="display:flex;"><span>$ sudo chown root:disk /dev/ram0
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Mount the ramdisk with a concrete size. Otherwise it grows to whatever is needed.</span>
</span></span><span style="display:flex;"><span>$ sudo mkdir /mnt/ramdisk
</span></span><span style="display:flex;"><span>$ sudo mount -t ramfs -o size<span style="color:#f92672">=</span>4G,maxsize<span style="color:#f92672">=</span>4G /dev/ram0 /mnt/ramdisk
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create a file on the ramdisk to use as a loopback device.</span>
</span></span><span style="display:flex;"><span>$ sudo dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/zero of /mnt/ramdisk/extend.img bs<span style="color:#f92672">=</span>4M count<span style="color:#f92672">=</span><span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>$ sudo losetup -fP /mnt/ramdisk/extend.img
</span></span><span style="display:flex;"><span><span style="color:#75715e"># figure out which loopback device ID is yours</span>
</span></span><span style="display:flex;"><span>$ sudo losetup -a |grep extend.img
</span></span><span style="display:flex;"><span>/dev/loop10: <span style="color:#f92672">[</span>5243078<span style="color:#f92672">]</span>:8563965 <span style="color:#f92672">(</span>/mnt/ramdisk/extend.img<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Add the loopback device to the btrfs filesystem</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs device add /dev/loop10 /
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Decide on your balance ratio and balance as usual.</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs <span style="color:#66d9ef">fi</span> usage / |head -n <span style="color:#ae81ff">6</span>
</span></span><span style="display:flex;"><span>Overall:
</span></span><span style="display:flex;"><span>    Device size:		 400.91GiB
</span></span><span style="display:flex;"><span>    Device allocated:		 396.36GiB
</span></span><span style="display:flex;"><span>    Device unallocated:		   4.55GiB
</span></span><span style="display:flex;"><span>    Device missing:		     0.00B
</span></span><span style="display:flex;"><span>    Used:			 348.91GiB
</span></span><span style="display:flex;"><span>$ echo <span style="color:#e6db74">&#39;scale=2;348/396&#39;</span> |bc
</span></span><span style="display:flex;"><span>.87
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$ sudo btrfs balance start -dusage<span style="color:#f92672">=</span><span style="color:#ae81ff">87</span> /
</span></span><span style="display:flex;"><span>Done, had to relocate <span style="color:#ae81ff">46</span> out of <span style="color:#ae81ff">400</span> chunks
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Remove the device and destroy it.</span>
</span></span><span style="display:flex;"><span>$ sudo btrfs device delete /dev/loop0 /
</span></span><span style="display:flex;"><span>$ sudo losetup -d /dev/loop10
</span></span><span style="display:flex;"><span>$ sudo umount /mnt/ramdisk
</span></span><span style="display:flex;"><span>$ sudo rm -rf /dev/ram0 
</span></span></code></pre></div>]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Serverless is the MS Access of the Future]]></title>
    <link href="https://ohthehugemanatee.org/blog/2019/01/24/serverless-is-the-ms-access-of-the-future/"/>
    <id>https://ohthehugemanatee.org/blog/2019/01/24/serverless-is-the-ms-access-of-the-future/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2019-01-24T15:12:30+02:00</published>
    <updated>2019-01-24T15:12:30+02:00</updated>
    <content type="html"><![CDATA[<p>Controversial opinion time: the usefulness of what we presently call &ldquo;serverless&rdquo; will always be limited to simple use cases. It is a great choice for glue code or simple projects, but it will never be the best choice for even medium complexity development problems. Containers and similar technologies will eat its lunch the same way RDBMSes ate MSAccess'.</p>
<p>The benefits of serverless are real. Don&rsquo;t worry about infrastructure, don&rsquo;t worry about scaling or availability. Just paste your code here and we&rsquo;ll take care of the rest! Minimal running costs, infinite scalability, simpler units of code to maintain!</p>
<p>These benefits are only possible because the cloud provider made a lot of decisions for you, in a way that is sensible for a majority use case. These are all decisions that you could hypothetically configure for yourself, given the time to do so. And it&rsquo;s exactly here that the core value of serverless is found. If your use case happens to fit within the frame set out by your serverless provider, there&rsquo;s real value on the table in terms of setup and maintenance time/cost (and SLA).</p>
<p>These decisions come with limitations, like most technical choices. What languages and versions can you use? What modules, what dependency management systems? What external binaries are available? What memory or disk is available, with what kind of I/O throughput? What&rsquo;s the local development environment, and how well does it replicate the live one? What are the scaling characteristics? And so on.</p>
<p>In a simple use case, most of these probably don&rsquo;t matter, and the ones that <em>do</em> matter are often exposed by the cloud provider. You can choose your VM size, for example, and preemptive scaling rules, and attach disks and external services, and supply secondary binaries yourself, and&hellip;</p>
<p>Very quickly you&rsquo;ve taken on a similar complexity, setup, and maintenance cost to what you were trying to avoid in the first place. This might be OK if it were a net zero transaction, but it&rsquo;s not. You&rsquo;ve taken on those costs, in exchange for&hellip; the rest of the limitations and a platform over which you have no control.</p>
<p>This feels a lot like so many MSAccess applications I worked with in the early 2000&rsquo;s. When the application was simple, it was great to have a visual data engine. But with a larger data model, the UI increasingly became an obstacle. You would increasingly use text to express your queries, duplicate data in more convenient tables to avoid tricky joins, and &hellip; In the end, the workarounds piled up. Managing a complex application on MSAccess is just as hard as managing it on any other RDBMS, but without the flexibility and power, and with more kludgy workarounds. Access is a great product for simple or straightforward use cases. But the moment your application grows too big, you start paying a heavy tax.</p>
<p>It&rsquo;s not controversial to suggest that the core value of Serverless is providing OOTB great hosting for simpler, highly encapsulated code, even code segments. The controversial part is the future prediction, where the metaphor really kicks in.</p>
<p>What happened to MSAccess? Other RDBMS&rsquo; got much better, and friendlier. From easy-to-use ORMs for easy-to-use programming languages, to GUIs like MySQLAdmin, to cloud-based application builders backed by RDBMSes, the full-powered RDBMS ecosystem gradually took over the use case for MSAccess. The ease-of-use benefit which was so core to the Access value proposition gradually disappeared, and users ended up with the choice between a fully-flexible power system and a limited one, both relatively easy to use. Finally Access 2010 became a GUI on top of SQL, integrated with Sharepoint.</p>
<p>Serverless is headed in a similar direction. Other tools, largely from the container ecosystem, are already nibbling at their lunch. If you&rsquo;re at the point where you need to configure VM sizes and scaling rules on your Serverless provider, you&rsquo;re probably considering jumping to a managed Kubernetes provider instead. If you don&rsquo;t need to configure that stuff, you&rsquo;re looking at pure-container cloud solutions like Azure Container Instances. Same flexibility and cost structure, but with run-anywhere compatibility and a development environment which matches the CI testbed and prod. The only uncontested ground left is applications that are too small to bother containerizing.</p>
<p>Meanwhile the container ecosystem is taking off at rocket speed, making that &ldquo;flexibility tradeoff&rdquo; worse and worsse for serverless. Where is the multi-cloud ecosystem for serverless? Where is the network security modeling market? Compare the difficulty of local dev and hosted CI environments for serverless code, to the out of the box auto-detected container builds available in every major CI platform. I&rsquo;s clear: serverless isn&rsquo;t actually all that much easier anymore. And it&rsquo;s only going one way. Soon enough, Serverless will become a nice frontend for a container runtime.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Kubernetes for stateful applications: Scaling macroservices]]></title>
    <link href="https://ohthehugemanatee.org/blog/2019/01/07/kubernetes-tricks-for-stateful-applications/"/>
    <id>https://ohthehugemanatee.org/blog/2019/01/07/kubernetes-tricks-for-stateful-applications/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2019-01-07T11:10:21+00:00</published>
    <updated>2019-01-07T11:10:21+00:00</updated>
    <content type="html"><![CDATA[<p>I recently got to proctor an <a href="https://openhack.microsoft.com/">Openhack</a> event on modern containerization. It ended up an excuse to dig deep on one of the corner cases that we all encounter, but no one likes to talk about.</p>
<p><a href="https://kubernetes.io/">Kubernetes</a> is one of the greatest <strong>orchestration</strong> and <strong>scaling</strong> tools ever built, designed for modern <strong>decoupled</strong>, <strong>stateless</strong> architectures. Kubernetes tutorials abound to show you these strong use cases. <strong>But in the real world where you don&rsquo;t get to build &ldquo;green field&rdquo; every time, there are a lot of applications that don&rsquo;t fit that model</strong>.</p>
<p>Lots of people out there are still writing tightly-coupled monoliths, in many cases for good reason. In some use cases microservices style scalability isn&rsquo;t even useful - you actually <em>prefer</em> stateful applications with tight coupling. For example a game server, where you don&rsquo;t want to scale player capacity per-game, you want to add more games (server instances).</p>
<p>So today I&rsquo;m writing about <strong>stateful, non-scalable applications in kubernetes.</strong></p>
<p>There are a few different approaches to coupling appliciation components:</p>
<h2 id="multi-container-pods">Multi-container pods</h2>
<p>Level 0 is to simply specify multiple components (containers) in your deployment.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx-deployment</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">php</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">php:fpm</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">containerPort</span>: <span style="color:#ae81ff">9000</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nginx:latest</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">containerPort</span>: <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>This specifies 3 copies of the same application, with the same two containers in each replica. This is a coupled application, but it&rsquo;s still stateless. Let&rsquo;s add a volume - that&rsquo;s where we get into trouble.</p>
<p>The problem: If you add a Volume the normal way (<a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">persistentVolumeClaim</a>), each of your replicas will try and connect to the same volume. It&rsquo;ll act like a network shared drive. Maybe that&rsquo;s OK for your application, but not if it&rsquo;s our super-stateful example! And depending on your volume class, the volume may reject multiple connections like (<a href="https://docs.microsoft.com/en-us/azure/aks/azure-disks-dynamic-pv">Azure Disk</a> does, for example).</p>
<p>So how do we get around this limitation? I want a separate volume for each instance of the application.</p>
<p>Kubernetes supports a different object type for this use case, called a <a href="https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/">StatefulSet</a>. This is exactly what it sounds like: a set of objects that define a stateful application. It&rsquo;s a template for creating multiple copies of <em>all resources</em> defined therein.</p>
<p>A statefulset will create replicas similar to a deployment, but it will set up separate Volumes and VolumeClaims for each one. The replicas will be identical except for an index number at the end of the labels. The first one might be called <code>nginx-deployment-0</code>, the second: <code>nginx-deployment-1</code>, and so on.  The result is a set of tightly coupled components, which can be individually addressed, and scaled using normal Kubernetes tools.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">StatefulSet</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">serviceName</span>: <span style="color:#e6db74">&#34;nginx&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nginx:latest</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">containerPort</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">php</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">php:fpm</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">mountPath</span>: <span style="color:#e6db74">&#34;/var/www/html&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">volumeClaimTemplates</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">storageClassName</span>: <span style="color:#ae81ff">default</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">accessModes</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#ae81ff">ReadWriteOnce</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">requests</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">storage</span>: <span style="color:#ae81ff">5Gi</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">http</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">clusterIP</span>: <span style="color:#ae81ff">None</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">nginx</span>
</span></span></code></pre></div><p>There are a few details to notice here.</p>
<p>Yes, we&rsquo;ve replaced <em>Deployment</em> with <em>StatefulSet</em>. You get a shiny gold star if you noticed that one.</p>
<p>The interesting part is the <em>VolumeClaimTemplates</em> section, below the containers definition. This keyword only exists inside a StatefulSet, and it&rsquo;s just what it sounds like: a template for creating Persistent Volume Claims.</p>
<p>If you apply this config, you&rsquo;ll see three PVs created, with three PVCs, attached to three Pods. You can apply HPA rules to scale these up and down just like you would with deployments.</p>
<p>There&rsquo;s also that weird Service at the bottom. A naked service with no clusterIP? What&rsquo;s the point? The point is as a helper for Kubernetes&rsquo; internal DNS. All of those nice StatefulSet pods will come under a neat subdomain, eg nginx-0.nginx, nginx-1.nginx, etc. Additionally you can connect to active members of the StatefulSet by using that nginx domain component. A dns lookup on it will show all the IPs of the active members in the CNAME record.</p>
<p>&ldquo;But what about external access?&rdquo; I hear you cry. Yes, we&rsquo;ve built a great stateful application that can scale instances, but it&rsquo;s only internally addressable! Good luck hosting those games&hellip;</p>
<h2 id="external-access-and-metacontroller">External access and metacontroller</h2>
<p>Normally you would put a LoadBalancer service in front of your application. But a Kubernetes load balancer will grab all of these StatefulSet members - so you can&rsquo;t address them externally one-by-one. What you <em>really</em> want to do, is create an external IP address for each statefulset member.</p>
<p>One solution is to use a reverse proxy like nginx or HAProxy, configured to differentiate based on hostnames. But this is a blog post about Kubernetes, so we&rsquo;re going to do this the Kubernetes way!</p>
<p>Kubernetes is very extensible. If Pods, Services, etc don&rsquo;t make sense for your application or domain, you can define custom object types and behaviors, through <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">custom resources and controllers</a>. That&rsquo;s pretty edge case, but as we&rsquo;ve seen, some kubernetes edge cases are mainstream cases in the real world.</p>
<p>In our super-stateful application, we don&rsquo;t need a custom resource type. But we do want to attach <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers">custom behaviors</a> to our StatefulSet: every time we start up a pod we should create a LoadBalancer for it. We should be nice and tear them down when the pods are scaled down, of course.</p>
<p>We&rsquo;ll use the <a href="https://github.com/GoogleCloudPlatform/metacontroller">Metacontroller</a> add-on to make our lives easier. Metacontroller makes it &ldquo;easy&rdquo; to add custom behaviors. Just write a short script, stick it into a ConfigMap or FaaS, and let Metacontroller work its magic!</p>
<p>Metacontroller project comes with several well documented examples, including one that&rsquo;s very close to our requirement: <a href="https://github.com/GoogleCloudPlatform/metacontroller/tree/master/examples/service-per-pod">service-per-pod</a>.</p>
<p>Step 1 is to install Metacontroller, of course:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Create &#39;metacontroller&#39; namespace, service account, and role/binding.</span>
</span></span><span style="display:flex;"><span>kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/master/manifests/metacontroller-rbac.yaml
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create CRDs for Metacontroller APIs, and the Metacontroller StatefulSet.</span>
</span></span><span style="display:flex;"><span>kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/master/manifests/metacontroller.yaml
</span></span></code></pre></div><p>Then we&rsquo;ll add some new metadata to our existing StatefulSet. The metacontroller script will use these values to configure the load balancers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">StatefulSet</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">service-per-pod-label</span>: <span style="color:#e6db74">&#34;pod-name&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">service-per-pod-ports</span>: <span style="color:#e6db74">&#34;80:80&#34;</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>We also need to tell Kubernetes to decorate each StatefulSet with a pod-name label. We do this in the StatefulSet&rsquo;s pod template.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">pod-name-label</span>: <span style="color:#e6db74">&#34;pod-name&#34;</span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>Note: this only works in k8s 1.9+ - if you&rsquo;re stuck with a lower version, you can script this action with Metacontroller, too. :).</p>
<p>Now you&rsquo;re going to need two hooks. Put them in a directory together so they&rsquo;re easy to apply at once. These ones are written in jsonnet, but you could write this in whatever language you like.</p>
<p>The first hook actually creates the LoadBalancer for each Pod.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c#" data-lang="c#"><span style="display:flex;"><span>function(request) {
</span></span><span style="display:flex;"><span>  local statefulset = request.<span style="color:#66d9ef">object</span>,
</span></span><span style="display:flex;"><span>  local labelKey = statefulset.metadata.annotations[<span style="color:#e6db74">&#34;service-per-pod-label&#34;</span>],
</span></span><span style="display:flex;"><span>  local ports = statefulset.metadata.annotations[<span style="color:#e6db74">&#34;service-per-pod-ports&#34;</span>],
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Create a service for each Pod, with a selector on the given label key.</span>
</span></span><span style="display:flex;"><span>  attachments: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      apiVersion: <span style="color:#e6db74">&#34;v1&#34;</span>,
</span></span><span style="display:flex;"><span>      kind: <span style="color:#e6db74">&#34;Service&#34;</span>,
</span></span><span style="display:flex;"><span>      metadata: {
</span></span><span style="display:flex;"><span>        name: statefulset.metadata.name + <span style="color:#e6db74">&#34;-&#34;</span> + index,
</span></span><span style="display:flex;"><span>        labels: {app: <span style="color:#e6db74">&#34;service-per-pod&#34;</span>}
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      spec: {
</span></span><span style="display:flex;"><span>        type: <span style="color:#e6db74">&#34;LoadBalancer&#34;</span>,
</span></span><span style="display:flex;"><span>        selector: {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">          [labelKey]</span>: statefulset.metadata.name + <span style="color:#e6db74">&#34;-&#34;</span> + index
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        ports: [
</span></span><span style="display:flex;"><span>          {
</span></span><span style="display:flex;"><span>            local parts = std.split(portnums, <span style="color:#e6db74">&#34;:&#34;</span>),
</span></span><span style="display:flex;"><span>            port: std.parseInt(parts[<span style="color:#ae81ff">0</span>]),
</span></span><span style="display:flex;"><span>            targetPort: std.parseInt(parts[<span style="color:#ae81ff">1</span>]),
</span></span><span style="display:flex;"><span>          }
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">for</span> portnums <span style="color:#66d9ef">in</span> std.split(ports, <span style="color:#e6db74">&#34;,&#34;</span>)
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> index <span style="color:#66d9ef">in</span> std.range(<span style="color:#ae81ff">0</span>, statefulset.spec.replicas - <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The other hook is the &ldquo;finalizer&rdquo; - it responds to changes or deletions in pods by tearing down the corresponding LoadBalancers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c#" data-lang="c#"><span style="display:flex;"><span>function(request) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// If the StatefulSet is updated to no longer match our decorator selector,</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// or if the StatefulSet is deleted, clean up any attachments we made.</span>
</span></span><span style="display:flex;"><span>  attachments: [],
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Mark as finalized once we observe all Services are gone.</span>
</span></span><span style="display:flex;"><span>  finalized: std.length(request.attachments[<span style="color:#960050;background-color:#1e0010">&#39;</span>Service.v1<span style="color:#960050;background-color:#1e0010">&#39;</span>]) == <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Add those into a subdirectory, and put them into a configmap together. Metacontroller will run them from there.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl create configmap service-per-pod-hooks -n metacontroller --from-file<span style="color:#f92672">=</span>hooks
</span></span></code></pre></div><p>Now apply the actual decorator controller which will run those functions. Note that you have to identify your hook jsonnet files by (file) name! Get the name wrong, and the finalizer will hang forever, <a href="https://github.com/kubernetes/kubernetes/issues/72598">preventing you from deleting your statefulset</a>. In my case, the files were called <code>create-lb-per-pod.jsonnet</code> and <code>finalizer.json</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">metacontroller.k8s.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">DecoratorController</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1beta1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">resource</span>: <span style="color:#ae81ff">statefulsets</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">annotationSelector</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">matchExpressions</span>:
</span></span><span style="display:flex;"><span>      - {<span style="color:#f92672">key: service-per-pod-label, operator</span>: <span style="color:#ae81ff">Exists}</span>
</span></span><span style="display:flex;"><span>      - {<span style="color:#f92672">key: service-per-pod-ports, operator</span>: <span style="color:#ae81ff">Exists}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">attachments</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">resource</span>: <span style="color:#ae81ff">services</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">sync</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">webhook</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">http://service-per-pod.metacontroller/create-lb-per-pod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">finalize</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">webhook</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">url</span>: <span style="color:#ae81ff">http://service-per-pod.metacontroller/finalizer</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">metacontroller</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">app</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">app</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">hooks</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">metacontroller/jsonnetd:0.1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">imagePullPolicy</span>: <span style="color:#ae81ff">Always</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">workingDir</span>: <span style="color:#ae81ff">/hooks</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">hooks</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/hooks</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">hooks</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">name</span>: <span style="color:#ae81ff">service-per-pod-hooks</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">metacontroller</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">service-per-pod</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">targetPort</span>: <span style="color:#ae81ff">8080</span>
</span></span></code></pre></div><p>That&rsquo;s it! Now you can scale complete replicas of a very-stateful application with a simple <code>kubectl scale sts nginx --replicas=900</code>.</p>
<p>Enjoy bragging to your friends about your &ldquo;macroservices architecture&rdquo;, pushing the limits of Kubernetes to run and replicate a stateful monolith!</p>
<p><em>Everyone hates writing YAML. Check out the <a href="https://github.com/ohthehugemanatee/kubernetes-stateful-example">sample code for this post on Github</a></em></p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Optimizing data transfer speeds]]></title>
    <link href="https://ohthehugemanatee.org/blog/2018/12/27/optimizing-data-transfer-speeds/"/>
    <id>https://ohthehugemanatee.org/blog/2018/12/27/optimizing-data-transfer-speeds/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2018-12-27T10:30:23+00:00</published>
    <updated>2018-12-27T10:30:23+00:00</updated>
    <content type="html"><![CDATA[<p>One of my holiday projects was to set up my home &ldquo;data warehouse.&rdquo; Ever since <a href="https://www.dropboxforum.com/t5/Syncing-and-uploads/Linux-Dropbox-client-warn-me-that-it-ll-stop-syncing-in-Nov-why/m-p/290065/highlight/true#M42255">Dropbox killed modern Linux filesystem support</a> I&rsquo;ve been using (and loving) <a href="https://nextcloud.com/">Nextcloud</a> from my home. It backs up to an encrypted <a href="https://www.duplicati.com/">Duplicati</a> store on <a href="https://azure.microsoft.com/en-us/services/storage/blobs/">Azure blob store</a>, so that&rsquo;s offsite backups taken care of. But it was time to knit all my various drives together into a single RAID data warehouse. The only problem: how to transfer my 2 terabytes (rounded to make the math in the post easier) of data, without nasty downtime during the holidays?</p>
<p>A local network transfer is the fastest, with the least downtime. I have a switched gigabit network in my house, and all my servers are hard wired. That&rsquo;s about 125 megabytes per second; a theoretical 5 hours to transfer everything. Not bad! Start up an rsync and I&rsquo;m all done! So I kicked it off and went to bed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh nextcloud.vert
</span></span><span style="display:flex;"><span>$ rsync -axz /media/usbdrive/ warehouse:/mnt/storage/ --log-file<span style="color:#f92672">=</span>transfer-to-warehouse.log &amp;
</span></span></code></pre></div><p>I woke up in the morning with the excitement of a kid on Christmas. Everything should be done, right?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh warehouse df -h |grep md0
</span></span><span style="display:flex;"><span>/dev/md0        2.7T  501G  2.1T  20% /mnt/storage
</span></span><span style="display:flex;"><span>$
</span></span></code></pre></div><p>Wait, what? How had it only transferred 500 gigabytes overnight? Including time for <em>Doctor Who</em> and breakfast, that was only 1 Megabit per second! I knew it was time to play everyone&rsquo;s favorite game: <em>&ldquo;where&rsquo;s the bottleneck?</em></p>
<p>I guess it could be rsync scanning all those small files. If that&rsquo;s the case, we&rsquo;ll see high CPU usage, and even higher load numbers (as processes are I/O blocked):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh nextcloud
</span></span><span style="display:flex;"><span>$ top
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>top - 08:22:27 up 10:26,  <span style="color:#ae81ff">1</span> user,  load average: 1.20, 1.34, 1.33
</span></span><span style="display:flex;"><span>Tasks: <span style="color:#ae81ff">170</span> total,   <span style="color:#ae81ff">2</span> running, <span style="color:#ae81ff">106</span> sleeping,   <span style="color:#ae81ff">0</span> stopped,   <span style="color:#ae81ff">0</span> zombie
</span></span><span style="display:flex;"><span>%Cpu<span style="color:#f92672">(</span>s<span style="color:#f92672">)</span>: 28.0 us,  2.1 sy,  0.0 ni, 69.5 id,  0.1 wa,  0.0 hi,  0.3 si,  0.0 st
</span></span><span style="display:flex;"><span>KiB Mem : <span style="color:#ae81ff">16330372</span> total,   <span style="color:#ae81ff">180568</span> free,   <span style="color:#ae81ff">657104</span> used, <span style="color:#ae81ff">15492700</span> buff/cache
</span></span><span style="display:flex;"><span>KiB Swap:  <span style="color:#ae81ff">4194300</span> total,  <span style="color:#ae81ff">4162556</span> free,    <span style="color:#ae81ff">31744</span> used. <span style="color:#ae81ff">15300068</span> avail Mem 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                  
</span></span><span style="display:flex;"><span> <span style="color:#ae81ff">8755</span> ohthehu+  <span style="color:#ae81ff">20</span>   <span style="color:#ae81ff">0</span>  <span style="color:#ae81ff">130572</span>  <span style="color:#ae81ff">58456</span>   <span style="color:#ae81ff">2672</span> R  99.0  0.4 513:14.75 rsync                                                                                    
</span></span><span style="display:flex;"><span> <span style="color:#ae81ff">8756</span> ohthehu+  <span style="color:#ae81ff">20</span>   <span style="color:#ae81ff">0</span>   <span style="color:#ae81ff">49596</span>   <span style="color:#ae81ff">6648</span>   <span style="color:#ae81ff">5152</span> S  16.9  0.0  92:12.29 ssh 
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>OK, let&rsquo;s kill the transfer and start again using a single large, piped tarball. No more small file scans!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh nextcloud
</span></span><span style="display:flex;"><span>$ cd /media/bigdrive <span style="color:#f92672">&amp;&amp;</span> tar cf - . | ssh warehouse <span style="color:#e6db74">&#34;cd /mnt/storage &amp;&amp; tar xpvf -&#34;</span>
</span></span></code></pre></div><p>That helps, but we&rsquo;re still compressing lots of data unnecessarily (most of my data is already compressed), and encrypting it, too. We can improve it with a lightweight ssh cipher and disabled compression:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh nextcloud
</span></span><span style="display:flex;"><span>$ cd /media/bigdrive <span style="color:#f92672">&amp;&amp;</span> tar cf - . | ssh -o Compression<span style="color:#f92672">=</span>no -c chacha20-poly1305@openssh.com warehouse <span style="color:#e6db74">&#34;cd /mnt/storage &amp;&amp; tar xpf -&#34;</span>
</span></span></code></pre></div><p>That chacha20-poly1305 is a very fast cipher indeed - faster than the old arcfour cipher we used to use in this case. But SSH still puts extra work on the CPU. So let&rsquo;s remove it completely from the equation and just use netcat.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ssh nextcloud cd /media/bigdrive <span style="color:#f92672">&amp;&amp;</span> tar cf - . | pv | nc -l -q <span style="color:#ae81ff">5</span> -p <span style="color:#ae81ff">9999</span> 
</span></span><span style="display:flex;"><span><span style="color:#75715e"># in a separate terminal</span>
</span></span><span style="display:flex;"><span>$ ssh warehouse cd /mnt/storage <span style="color:#f92672">&amp;&amp;</span> nc nextcloud <span style="color:#ae81ff">9999</span> | pv | tar -xf -
</span></span></code></pre></div><p>Transfer speeds now average about 61 megabytes per second. That&rsquo;s fast enough to kick in the law of diminishing returns on my optimization effort: this will take about 8 hours to transfer if I keep it running. I had to pause work for an hour; now if I spend another hour on this, it has to shave more than 25% off my transfer time to finish any earlier tonight. I&rsquo;m not confident I can beat those numbers.</p>
<p>Still - What happened to my 125 theoretical megabytes per second? Here are the culprits I suspect - and can&rsquo;t really do anything about:</p>
<ul>
<li>
<p><strong>Slow disk</strong>: We are writing to a software RAID5 array of old drives. In my head I was using the channel width of SATA-II for my calculations. In reality, and especially on spinning metal, write speeds are much slower. I looked up my component disks on <a href="userbenchmark.com">userbenchmark.com</a>, and the slowest member has an average sustained sequential write speed of 69 MB/s. This is very likely my first bottleneck. At most I can only use half of my available bandwidth.</p>
</li>
<li>
<p><strong>TCP</strong>: After replacing all my drives with SSDs, TCP is the next culprit I would go after. The protocol technically only has about 6% of overhead, but it also dynamically seeks the maximum send rate through <a href="https://en.wikipedia.org/wiki/TCP_congestion_control">TCP Congestion Control</a>. It keeps trying to send &ldquo;just a little faster&rdquo;, until the number of unacknowledged packets exceeds a threshold. Then it backs off to 50%, and goes back to &ldquo;just a little faster&rdquo; mode. This loop means your practical speed with a TCP stream is about 75% of the pipe&rsquo;s theoretical maximum. Think of it like John Cleese offering just one more &ldquo;wafer thin&rdquo; packet. I considered using UDP to avoid this, but I actually <em>want</em> the error-checking in TCP. Probably the best solution is <a href="https://github.com/LabAdvComp/UDR">something esoteric like UDR</a>.</p>
</li>
<li>
<p><strong>Slow CPU</strong>: This is the last bottleneck here. <em>Warehouse</em> is an old Intel Core2 Duo I had lying around the house. Untar and netcat aren&rsquo;t exactly CPU hungry beasts, but at some point there IS a maximum. If you believe the FreeNAS folks, a fileserver needs an i5 and 8 gigs of RAM for basic functionality. I haven&rsquo;t found that to be the case, but then I&rsquo;m not using RAID-Z, either.</p>
</li>
</ul>
<p>I&rsquo;m happy with the outcome here. I have another drive to copy later, with another terabyte. I&rsquo;m considering removing that slowest drive from my RAID array, since the next-slowest one is almost 50% faster. Then I can copy to the array while it&rsquo;s in degraded mode, and re-add the slowpoke afterwards. We&rsquo;ll see.</p>
<p>Happy holidays!</p>
<h3 id="appendix-easy-performance-testing">Appendix: easy performance testing</h3>
<p>If you&rsquo;re working on a similar problem for yourself, you might find these performance testing commands helpful. The idea is to tease apart each component of the transfer. There are better, more detailed, dedicated tools for each of these, but in a game of &ldquo;find the bottleneck&rdquo; you really only need quick and dirty validation. Fun fact: the command <em>dd</em> is actually short for <strong>D</strong>own and <strong>D</strong>irty. Well it should be, at any rate.</p>
<p><strong>Read speed (on the source</strong> is easy: hand an arbitrary large file to dd, and write down the numbers it gives.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>large-file.tar.bz2 of<span style="color:#f92672">=</span>/dev/null bs<span style="color:#f92672">=</span>1M
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1021317200</span> bytes <span style="color:#f92672">(</span><span style="color:#ae81ff">1</span> GB<span style="color:#f92672">)</span> copied, 3.9888 s, <span style="color:#ae81ff">256</span> MB/s
</span></span></code></pre></div><p><strong>Network speed</strong> can be tested by netcatting a gigabyte of zeros from one machine to the other.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># On the receiving machine, open a port to /dev/null</span>
</span></span><span style="display:flex;"><span>$ nc -vvlnp <span style="color:#ae81ff">12345</span> &gt;/dev/null
</span></span><span style="display:flex;"><span><span style="color:#75715e"># On the sending machine, send a gig of zeroes to that port</span>
</span></span><span style="display:flex;"><span>$ dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/zero bs<span style="color:#f92672">=</span>1M count<span style="color:#f92672">=</span>1K | nc -vvn 192.168.1.50 <span style="color:#ae81ff">12345</span>
</span></span><span style="display:flex;"><span>Connection to 192.168.1.50 <span style="color:#ae81ff">12345</span> port <span style="color:#f92672">[</span>tcp/*<span style="color:#f92672">]</span> succeeded!
</span></span><span style="display:flex;"><span>1024+0 records in
</span></span><span style="display:flex;"><span>1024+0 records out
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1073741824</span> bytes <span style="color:#f92672">(</span>1.1 GB, 1.0 GiB<span style="color:#f92672">)</span> copied, 11.7811 s, 91.1 MB/s
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Remember, 8 bits to a byte!</span>
</span></span><span style="display:flex;"><span>$ echo <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>bc -l <span style="color:#f92672">&lt;&lt;&lt;</span> 91*8<span style="color:#66d9ef">)</span><span style="color:#e6db74"> Megabits&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">728</span> Megabits
</span></span></code></pre></div><p><strong>Write speed on the destination</strong> can be tested with dd, too:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/zero bs<span style="color:#f92672">=</span>1M count<span style="color:#f92672">=</span><span style="color:#ae81ff">1024</span> of<span style="color:#f92672">=</span>/mnt/storage/test.img
</span></span><span style="display:flex;"><span>1024+0 records in
</span></span><span style="display:flex;"><span>1024+0 records out
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1073741824</span> bytes <span style="color:#f92672">(</span>1.1 GB, 1.0 GiB<span style="color:#f92672">)</span> copied, 55.3836 s, 19.4 MB/s
</span></span></code></pre></div><p>(note: these tests were run while the copy was happening on warehouse. Your numbers should be better than this!)</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[Drupal Does Face Recognition: Introducing Image Auto Tag module]]></title>
    <link href="https://ohthehugemanatee.org/blog/2018/04/19/face-recognition-on-drupal/"/>
    <id>https://ohthehugemanatee.org/blog/2018/04/19/face-recognition-on-drupal/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2018-04-19T18:06:51+00:00</published>
    <updated>2018-04-19T18:06:51+00:00</updated>
    <content type="html"><![CDATA[<p>Last week I wrote a Drupal module that uses face recognition to automatically tag images with the people in them. You can find it on <a href="https://github.com/ohthehugemanatee/image_auto_tag">Github</a>, of course. With this module, you can add an image to a node, and automatically populate an entity_reference field with the names of the people in the image. This isn&rsquo;t such a big deal for individual nodes of course; it&rsquo;s really interesting for bulk use cases, like Digital Asset Management systems.</p>
<p><img src="/images/image-auto-tag.gif" alt="Automatic tags, now in a Gif."></p>
<p>I had a great time at Drupalcon Nashville, reconnecting with friends, mentors, and colleagues as always. But this time I had some fresh perspective. After 3 months working with Microsoft&rsquo;s (badass) CSE unit - building cutting edge proofs-of-concept for some of their biggest customers - the contrast was powerful. The Drupal core development team are famously obsessive about code quality and about optimizing the experience for developers and users. The velocity in the platform is truly amazing. But we&rsquo;re missing out on a lot of the recent stuff that large organizations are building in their more custom applications. You may have noticed the same: all the cool kids are posting about Machine Learning, sentiment analysis, and computer vision. We don&rsquo;t see any of that at Drupalcon.</p>
<p>There&rsquo;s no reason to miss out on this stuff, though. Services like Azure are making it extremely easy to do all of these things, layering simple HTTP-based APIs on top of the complexity. As far as I can tell, the biggest obstacle is that there aren&rsquo;t well defined standards for how to interact with these kinds of services, so it&rsquo;s hard to make a generic module for them. This isn&rsquo;t like the Lucene/Solr/ElasticSearch world, where one set of syntax - indeed, one model of how to think of content and communicate with a search-specialized service - has come to dominate. Great modules like search_api depend on these conceptual similarities between backends, and they just don&rsquo;t exist yet for cognitive services.</p>
<p>So I set out to try and explore those problems in a Drupal module.</p>
<p><strong>Image Auto Tag</strong> is my first experiment. It works, and I encourage you to play around with it, but please don&rsquo;t even think of using it in production yet. It&rsquo;s a starting point for how we might build an analog to the great <a href="https://drupal.org/project/search_api">search_api</a> framework, for cognitive services rather than search.</p>
<p>I built it on Azure&rsquo;s Cognitive Services Face API to start. Since the service is free for up to 5000 requests per month, this seemed like a place that most Drupalists would feel comfortable playing. Next up I&rsquo;ll abstract the Azure portion of it into a plugin system, and try to define a common interface that makes sense whether it&rsquo;s referring to Azure cognitive services, or a self-hosted, open source system like <a href="https://cmusatyalab.github.io/openface/">OpenFace</a>. That&rsquo;s the actual &ldquo;hard work&rdquo;.</p>
<p>In the meantime, I&rsquo;ll continue to make this more robust with more tests, an easier UI, asynchronous operations, and so on. At a minimum it&rsquo;ll become a solid &ldquo;Azure Face Detection&rdquo; module for Drupal, but I would love to make it more generally useful than that.</p>
<p>Comments, Issues, and helpful PRs are welcome.</p>
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[The #1 Question I Get Asked Working at MS: Why Do You Run Linux?]]></title>
    <link href="https://ohthehugemanatee.org/blog/2018/02/07/the-number-1-question-i-get-asked-working-at-msy-do-you-run-linux/"/>
    <id>https://ohthehugemanatee.org/blog/2018/02/07/the-number-1-question-i-get-asked-working-at-msy-do-you-run-linux/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2018-02-07T16:42:50+00:00</published>
    <updated>2018-02-07T16:42:50+00:00</updated>
    <content type="html"><![CDATA[<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[My War on Systemd-resolved]]></title>
    <link href="https://ohthehugemanatee.org/blog/2018/01/25/my-war-on-systemd-resolved/"/>
    <id>https://ohthehugemanatee.org/blog/2018/01/25/my-war-on-systemd-resolved/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2018-01-25T11:05:31+00:00</published>
    <updated>2018-01-25T11:05:31+00:00</updated>
    <content type="html"><![CDATA[<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
]]></content>
  </entry>
  <entry>
    <title type="html"><![CDATA[I&#39;m joining Microsoft, because they&#39;re doing Open Source Right]]></title>
    <link href="https://ohthehugemanatee.org/blog/2018/01/10/im-joining-microsoft-because-theyre-doing-open-source-right/"/>
    <id>https://ohthehugemanatee.org/blog/2018/01/10/im-joining-microsoft-because-theyre-doing-open-source-right/</id>
    <author>
      <name>Campbell Vertesi (ohthehugemanatee)</name>
    </author>
    <published>2018-01-10T20:32:50+00:00</published>
    <updated>2018-01-10T20:32:50+00:00</updated>
    <content type="html"><![CDATA[<p>I&rsquo;m excited to announce that I&rsquo;ve signed with <strong>Microsoft</strong> as a Principal Software Engineering Manager. <strong>I&rsquo;m joining Microsoft because they are doing enterprise Open Source the Right Way, and I want to be a part of it</strong>. This is a sentence that I never believed I would write or say, so I want to explain.</p>
<p>First I have to acknowledge the history. I co-founded my first tech company just as the <a href="https://en.wikipedia.org/wiki/Halloween_documents">Halloween documents</a> were leaked. That&rsquo;s where the world learned that Microsoft considered Open Source (and Linux in particular) a threat, and was intentionally spreading FUD as a strategic counter. It was also the origin of their famous <a href="https://en.wikipedia.org/wiki/Embrace%2C_extend%2C_and_extinguish">Embrace, Extend, and Extinguish</a> strategy. The Microsoft approach to Open Source only got more aggressive from there, funneling money to <a href="https://en.wikipedia.org/wiki/SCO/Linux_controversies">SCO&rsquo;s lawsuits</a> against Linux and its users, calling OSS licensing a &ldquo;cancer&rdquo;, and accusing Linux of violating MS intellectual property.</p>
<p>I don&rsquo;t need to get exhaustive about this to make my point: <strong>for the first decade of my career (or more), Microsoft was rightly perceived as a villain in the OSS world</strong>. They did real damage and disservice to the open source movement, and ultimately to their own customers. Five years ago I wouldn&rsquo;t have even entertained the thought of working for &ldquo;the evil empire.&rdquo;</p>
<p>Yes, Microsoft has made nice movements towards open source since the new CEO (Satya Nadella) took over in 2014. They open sourced .NET and Visual Studio, they released Typescript, they joined the <a href="https://www.linuxfoundation.org/">Linux Foundation</a> and went platinum with the <a href="https://opensource.org/">Open Source Initiative</a>, but come on. I&rsquo;m an open source warrior, an evangelist, and developer. I could see through the bullshit. Even when Microsoft announced the Linux subsystem on Windows, I was certain that this was just another round of Embrace, Extend, Extinguish.</p>
<p>Then I met <a href="http://www.joshholmes.com/">Josh Holmes</a> at the <a href="https://www.phpconference.nl/">Dutch PHP Conference</a>.</p>
<p>First of all, I was shocked to meet a Microsoft representative at an open source conference. He didn&rsquo;t even have bodyguards. I remember my first question for him was &ldquo;What are you <em>doing</em> here?&rdquo;.</p>
<p>Josh told me a story about visiting startup conferences in Silicon Valley on behalf of Microsoft in 2007, and reporting back to Ballmer&rsquo;s office:</p>
<blockquote>
<p>&ldquo;The good news is, no one is making jokes about Microsoft anymore. The bad news is, <strong>they aren&rsquo;t even making jokes about Microsoft anymore</strong>.&rdquo;</p>
</blockquote>
<p>For Josh, this was a big &ldquo;aha&rdquo; moment. The booming tech startup space was focused on Open Source, so if Microsoft wanted to survive there, they had to come to the table.</p>
<p>That revelation led to the creation of the Microsoft Partner Catalyst Team. Here&rsquo;s Josh&rsquo;s explanation of the team and its job, from an <a href="https://www.youtube.com/watch?v=qkTioWRH-Ws">interview</a> at the time I met him:</p>
<blockquote>
<p>&ldquo;We work with a lot of startups, at the very top edge of the enterprise mix. We look at their toughest problems, and we go solve those problems with open source. We&rsquo;ve got 70 engineers and architects, and we go work with the startups hand in hand. We&rsquo;ll sit down for a little pair programming with them, sometimes it will be a large enough problem that will take it off on our own and we&rsquo;ll work on it for a while, and we&rsquo;ll come back and give them the code. Everything that we do ends up in Github under typically an MIT or Apache license if it&rsquo;s original work that we&rsquo;re doing on our own, or a lot of times we&rsquo;re actually working within other open source projects.&rdquo;</p>
</blockquote>
<p>Meeting with Josh was a turning point for my understanding of Microsoft. This wasn&rsquo;t just something that I could begrudgingly call &ldquo;OK for open source&rdquo;. This wasn&rsquo;t just lip service. This was a whole department of people that were doing <em>exactly</em> what I believe in. Not only did I like the sound of this; I found that <strong>I actually wanted to work with this group</strong>.</p>
<p>Still, when I considered interviewing with Microsoft, <strong>I knew that my first question had to be about &ldquo;Embrace, Extend, and Extinguish&rdquo;</strong>. Josh is a nice guy, and very smart, but I wasn&rsquo;t going to let the wool be pulled over <em>my</em> eyes.</p>
<p>Over the next months, I would speak with five different people doing exactly this kind of work at Microsoft. I  I did my research, I plumbed all my back-channel resources for dirt. And everything I came back with said <strong>I was wrong</strong>.</p>
<p>Microsoft really <em>is</em> undergoing a fundamental shift towards Open Source.</p>
<p>CEO Sadya Nadella is frank that <strong>closed-source licensing as a profit model is a dead-end</strong>. Since 2014, Microsoft has been transitioning their core business from licensed software to platform services. After all, why sell a license once, when you can rent it out monthly? So they move all the licensed products they can online, and rent, instead of selling them. Then they rent out the infrastructure itself, too - hence Azure. Suddenly flexibility is at a premium. As one CTO put it, <strong>for Azure to be Windows-only would be a liability</strong>.</p>
<p>This shift is old news for most of the world. As much as the Hacker News crowd still bitches about it as FUD, this strategic direction has been in and out of the financial pages for years now. Microsoft has pivoted to platform services. Look at their profits by product over the last 8 years:</p>
<p><img src="/images/microsoft-profits-by-product.png" alt="Microsoft profits by product, over year."></p>
<p>The trend is obvious: <strong>server and platform services are the place to invest</strong>. Office only remains at the top of the heap because it transitioned to SaaS. Even Windows license profits are declining. This means focusing on interoperability. Make sure <em>everything</em> can run on your platform, because anything else is to handicap the source of your biggest short- and medium-term profit. In fact, <strong>remaining adversarial to Open Source would kill the golden goose</strong>. Microsoft <em>has</em> to change its values in order to make this shift.</p>
<p>So much for financial and strategic direction; but this is a hundred-thousand-person company. That ship doesn&rsquo;t turn on a dime, no matter what the press releases tell you. So <strong>my second interview question became &ldquo;How is the transition going?&rdquo;</strong> This sort of question makes people uncomfortable: the answer is either transparently unrealistic, or critical of your environment and colleagues. Over and over again, I heard the right answer: It&rsquo;s freakin&rsquo; hard.</p>
<p>MS has more than 40 years of proprietary development experience and institutional momentum. All of their culture and systems - from hiring, to code reviews, to legal authorizations - have been organized around that model. That&rsquo;s very hard to change! I heard horror stories about the beginning of the transition, having to pass every line of contribution past the Legal department. I heard about managers feeling lost, or losing a sense of authority over their own team. I heard about development teams struggling to understand that their place in an OSS project was on par with some Rando Calrissian contributor from Kansas. And I heard about how the company was helping people with the transition, changing systems and structures to make this cultural shift happen.</p>
<p>The stories I heard were important evidence, which contradicted the old narrative I had in my head. <strong>Embrace, extend, extinguish does not involve leadership challenges, or breaking down of hierarchies</strong>. It does not involve personal struggle and departmental reorganization. The stories I heard evidenced an organization trying a real paradigm shift, for tens of thousands of people around the world. It is not perfect, and it is not finished, but I believe that the transition is real.</p>
<p><strong>When you accept that Microsoft is trying to reorient its own culture to Open Source, suddenly all those &ldquo;transparent&rdquo; PR moves you dismissed get re-framed</strong>. They are accomplishments. It&rsquo;s incredibly difficult to change the culture of one of the biggest companies in the world&hellip; but today, almost half of Azure users run Linux. Microsoft&rsquo;s virtualization work made them the <a href="http://www.techradar.com/news/software/operating-systems/inside-the-linux-kernel-3-0-1035353/2">fifth largest contributor to the 3.x Linux kernel</a>. Microsoft maintains <a href="https://octoverse.github.com/">the biggest project on Github (by contributor count)</a>. They maintain a BSD distribution <em>and</em> a Linux distribution. And a huge part of LXD (the container-based virtualization system for Linux) comes from Microsoft&rsquo;s work with Canonical.</p>
<p>That&rsquo;s impressive for any company. But Microsoft? It boggles the mind. This level of contribution is not lip-service. You don&rsquo;t maintain a 15 thousand person community just for PR. <strong>Microsoft is contributing as much or more to open source than many other major players, who have had this in their culture from the start</strong> (Google, Facebook, Twitter, LinkedIn&hellip;). It&rsquo;s an accomplishment, and it&rsquo;s impressive!</p>
<p>In the group I&rsquo;m entering, a strong commitment to Open Source is built into the project structure, the team responsibilities, and the budgeting practice. Every project has time specifically set aside for contribution; developers&rsquo; connections to their communities are respected and encouraged. After a decade of working with companies who try to engage with open source responsibly, I can say that <strong>this is the strongest institutional commitment to &ldquo;giving back&rdquo; that I have ever seen</strong>. It&rsquo;s a stronger support for contribution than I&rsquo;ve ever been able to offer in any of my roles, from sole proprietor to CTO.</p>
<p>This does mean a lot more work outside of the Drupal world, though. I will still attend Drupalcons. I will still give technical talks, participate, and help make great open source communities for Drupal and other OSS projects. If anything, I will do those things <em>more</em>. And I will do them wearing a Microsoft shirt.</p>
<p>Microsoft is making a genuine, and enormous, push to being open source community members and leaders. From everything I&rsquo;ve seen, they are doing it extremely well. From the outside at least, <strong>this is what it looks like to do enterprise Open Source The Right Way</strong>.</p>
]]></content>
  </entry>
</feed>
