<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rhazn</title>
    <description>The latest articles on DEV Community by rhazn (@rhanarion).</description>
    <link>https://dev.to/rhanarion</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F212303%2F0d349224-31d7-4849-99dc-959bc8e85bed.jpg</url>
      <title>DEV Community: rhazn</title>
      <link>https://dev.to/rhanarion</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rhanarion"/>
    <language>en</language>
    <item>
      <title>Choose names for Google, not people</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Sat, 22 Jan 2022 13:17:16 +0000</pubDate>
      <link>https://dev.to/rhanarion/choose-names-for-google-not-people-59bn</link>
      <guid>https://dev.to/rhanarion/choose-names-for-google-not-people-59bn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"There are only two hard things in Computer Science: cache invalidation and naming things." &lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Phil Karlton&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AngularJS was released in 2010 and revolutionized modern frontend development. In 2016, the AngularJS team published a new framework, written in TypeScript and incompatible with AngularJS. They also made the baffling choice to call this framework Angular 2.&lt;/p&gt;

&lt;p&gt;Mayhem ensued, confused developers talked to each other about incompatible frameworks. The excellent community documentation AngularJS had built in the form of blogs and StackOverflow answers became an active hindrance for people trying to learn Angular. Undeterred by this, the Angular team doubled down and kept releasing incompatible framework versions, only distinguished by a number.&lt;/p&gt;

&lt;p&gt;Five years later, the team announced that AngularJS would be discontinued. Immediately they needed to clarify that they would not discontinue Angular but only AngularJS.&lt;/p&gt;

&lt;p&gt;Even with the best documentation, users are going to google for information. They are going to read blogs, ask on StackOverflow and copy answers. So to make it easy on them, please:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use unique names for major versions ("Android KitKat" instead of "Android 4.4"). They are easier to include in searches.&lt;/li&gt;
&lt;li&gt;Use error codes (like "Error 34") or slugs (like "ERROR_SOME_NAME") in addition to localized, readable messages. Unique strings enable users with different languages than English to contribute.&lt;/li&gt;
&lt;li&gt;As a user, use Software in English. It makes it easier to google for errors ;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://heltweg.org/posts/"&gt;This post was originally posted on my website, https://heltweg.org if you like it you might want to look at what else I write :)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>watercooler</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Who wrote this shit?</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Mon, 10 Jan 2022 12:38:23 +0000</pubDate>
      <link>https://dev.to/rhanarion/who-wrote-this-shit-425c</link>
      <guid>https://dev.to/rhanarion/who-wrote-this-shit-425c</guid>
      <description>&lt;p&gt;It is a beautiful right of passage for a bright-eyed junior developer to join a team, take some tasks full of enthusiasm, and have the life and joy sucked out of them one sprint at a time. Soon enough, they sit in planning meetings, miserably complaining, accusingly asking who wrote that shit. &lt;br&gt;
Their transformation to a full team member is complete. They have become one of us.&lt;/p&gt;

&lt;p&gt;I was once that bright-eyed junior developer. Many encounters with PHP4 later could no longer look forward to creating new features. Instead, I looked back and complained about old ones. Trashing legacy software is fun. It creates camaraderie. Who wrote this? What were they thinking? Looking at the commit log showed names that meant nothing to me. Ghosts that moved on long ago, leaving nothing but their shit code.&lt;/p&gt;

&lt;p&gt;One day, I was pair programming with my friend Torben. Torben was a senior developer who introduced me to the team months earlier. When I found some weird code, I followed the tradition. Who was dumb enough to write this? I opened the commit log, and for the first time, I recognized a name: "Torben."&lt;/p&gt;

&lt;p&gt;Since then, I remind myself that legacy software is written by people like me. Torben had constraints I knew nothing about. He worried about deadlines long past. Torben had learned a lot since then. Development workflows had evolved, infrastructure had gotten better.&lt;/p&gt;

&lt;p&gt;And to all the junior developers who have complained to my face about my trash code: I forgive you, I understand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://heltweg.org/posts/"&gt;This post was originally posted on my website, https://heltweg.org if you like it you might want to look at what else I write :)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>programming</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Wisdom from the Internet, content that influenced how I think about software (and life).</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Thu, 03 Dec 2020 14:57:55 +0000</pubDate>
      <link>https://dev.to/rhanarion/wisdom-from-the-internet-content-that-influenced-how-i-think-about-software-and-life-2fp7</link>
      <guid>https://dev.to/rhanarion/wisdom-from-the-internet-content-that-influenced-how-i-think-about-software-and-life-2fp7</guid>
      <description>&lt;p&gt;&lt;a href="https://heltweg.org"&gt;This post was originally posted on my website, https://heltweg.org if you like it you might want to look at what else I write :)&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Finding needles
&lt;/h1&gt;

&lt;p&gt;There has never been as much information easily accessible to anyone as right now. The ease of publishing your own writing leads to new problems: The question is no longer where do you find content about a topic but what content is good and worth your time? &lt;/p&gt;

&lt;p&gt;To help you find the needle in the biggest haystack ever, here is my personal list of good content that influenced me.&lt;/p&gt;

&lt;h1&gt;
  
  
  The list
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. "Things You Should Never Do" by Joel Spolsky
&lt;/h2&gt;

&lt;p&gt;One of the main things I have learned when going from a young software engineer to working professionally for years is the value of iteration. I've personally been burned by completely rewriting projects and ever since keep this article around to remind myself of that. Also of the pain of living through the javascript framework frontend wars.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/"&gt;Things You Should Never Do, Part 1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Choose Boring Technology" by Dan McKinley
&lt;/h2&gt;

&lt;p&gt;Years ago I read a blog post titled "Why I write Java" that basically boiled down to the author writing Java and using established technologies to focus on the product and not the tech. The blog's first comment was "So you write java because you are an old, boring guy". Sadly, that gem of internet discussion seems to be lost to time. This article presents the same ideas of cautioning against new technology (but not being dogmatic about never using it) and as a fellow old, boring person now I can see myself in it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mcfunley.com/choose-boring-technology"&gt;Choose Boring Technology&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. "The Product-Minded Software Engineer" by Gergely Orosz
&lt;/h2&gt;

&lt;p&gt;Job identities are hard. Am I a software engineer that wants to build an amazing algorithm? Am I a "creator" that wants to build a product? Am I a manager that wants to build a team? If you struggle to pin down what you actually want to do, this article introduces yet another category to fit your life into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.pragmaticengineer.com/the-product-minded-engineer/"&gt;The Product-Minded Software Engineer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. "Give it five minutes" by Jason Fried
&lt;/h2&gt;

&lt;p&gt;Jason Fried and anything Basecamp are an interesting source for thoughtful content on business, software engineering and as it turns out even life advice ;). This is a really short blogpost that reminds me to keep an open mind and be humble.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://signalvnoise.com/posts/3124-give-it-five-minutes"&gt;Give it five minutes&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. "Are Passions Serendipitously Discovered or Painstakingly Constructed?" by Cal Newport
&lt;/h2&gt;

&lt;p&gt;A short blog for people being lost in life. If you are unsure about your goal in life or why you have no passions, maybe this is something to give you ideas. Also it has a headline that literally is "Short Case Study #2: The Bored Programmer". If that does not fit the audience of this article, nothing will.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.calnewport.com/blog/2009/11/24/are-passions-serendipitously-discovered-or-painstakingly-constructed/"&gt;Are Passions Serendipitously Discovered or Painstakingly Constructed?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. "Function + Feeling" by Haraldur Thorleifsson
&lt;/h2&gt;

&lt;p&gt;This is a talk and not a blog but it fits the list. In a world focused on monetizing and optimizing everything, talking about predatory business models and the death of privacy this was a nice reminder that our work can have real, positive impact on people and it's worth fighting for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=WBB7A9kbH-k&amp;amp;feature=emb_title"&gt;Function + Feeling&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. "Salary Negotiation: Make More Money, Be More Valued" and "Don't Call Yourself A Programmer, And Other Career Advice" by Patrick McKenzie
&lt;/h2&gt;

&lt;p&gt;For software engineers just starting a career I think these blogs are an absolute must read to navigate the business side (not only of coding but for anything). Since reading these my opinions on the approach to working itself has changed quite a bit but I still consider them a solid into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.kalzumeus.com/2012/01/23/salary-negotiation/"&gt;Salary Negotiation: Make More Money, Be More Valued&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-programmer/"&gt;Don't Call Yourself A Programmer, And Other Career Advice&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://heltweg.org"&gt;This post was originally posted on my website, https://heltweg.org if you like it you might want to look at what else I write :)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>Machine learning basics: Machine Learning by Stanford University (Coursera) review and notes</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Mon, 16 Mar 2020 13:46:06 +0000</pubDate>
      <link>https://dev.to/rhanarion/machine-learning-basics-machine-learning-by-stanford-university-coursera-review-and-notes-4k1i</link>
      <guid>https://dev.to/rhanarion/machine-learning-basics-machine-learning-by-stanford-university-coursera-review-and-notes-4k1i</guid>
      <description>&lt;p&gt;&lt;a href="https://heltweg.org"&gt;This post was originally posted on my website.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The popularity of machine learning, data science and related disciplines is exploding and with it the amount of courses, books, block posts etc you are exposed to. I recently finished the relatively old but highly rated course &lt;a href="https://www.coursera.org/learn/machine-learning"&gt;Machine Learning by Stanford University&lt;/a&gt; on Coursera and wanted to take the chance to offer my review and notes I took.&lt;/p&gt;

&lt;h1&gt;
  
  
  The course
&lt;/h1&gt;

&lt;p&gt;Although the course is old enough to be referred to as "classic" by quite a few descriptions I have read it is timeless in the sense that most good introductions are. It covers concepts from tools in ML like supervised- or unsupervised learning, tips for their application with algorithm rating and debugging and much more. Most topics are covered very timeless with the theory and math behind them being the focus.&lt;/p&gt;

&lt;p&gt;Coursera offers video lectures, lecture notes (as pdf) and a short text summary for most of the 11 weeks of the course. Grading is done using short quizzes at the end of each topic as well as programming exercises in Octave or Mathlab. Forums are available to help with any questions and mentors are good at answering anything that comes up.&lt;/p&gt;

&lt;h1&gt;
  
  
  The good
&lt;/h1&gt;

&lt;p&gt;The video lectures are really well done and the content seems excellent and very "grounded". It is definitely not a course trying to cash in on the hype surrounding ML.&lt;/p&gt;

&lt;p&gt;The programming exercises are solid and introduced me well to a different side of software development that is more focussed on solving complex mathematical problems than displaying a centered div.&lt;/p&gt;

&lt;p&gt;The forums and additional resources prepared by the mentors are very good, in every case where I had a problem it was already solved by someone else asking it in the forums.&lt;/p&gt;

&lt;p&gt;If you don't want to get a certificate the course is completely free.&lt;/p&gt;

&lt;h1&gt;
  
  
  The bad
&lt;/h1&gt;

&lt;p&gt;There is a noticeable drop in quality in the later weeks. While the content stays excellent the written summaries disappear, sometimes obvious double takes that should have been edited are left in videos and programming exercises have a lot of pre-written code in their descriptions.&lt;/p&gt;

&lt;p&gt;Solving programming assignments in Octave: Probably due to the age of the course the setup of Octave is not friction free - in the version I used the "pause" function was broken which made all programming assignments hang at the first stop. It was relatively easy for me to debug and fix by overwriting the internal pause function but I've seen quite a few questions about Octave issues in the forum. In addition Octave seems to not be the primary choice in the ML community but since the course teaches concepts over concrete implementation that is fine by me.&lt;/p&gt;

&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;p&gt;I took a lot of notes during the course (more than 120 pages to be exact). They are mostly very close to the course material provided but sometimes rephrased and annotated with additional comments from me. In case they help anyone you can:&lt;br&gt;
&lt;a href="https://drive.google.com/open?id=177-viQ2qr4dU7sBheXPzbM3DPZX-eXti"&gt;Download them here&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>career</category>
      <category>ai</category>
      <category>review</category>
    </item>
    <item>
      <title>Analyzing twitter: Import tweets with NodeJS and the twitter API</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Thu, 31 Oct 2019 13:48:32 +0000</pubDate>
      <link>https://dev.to/rhanarion/analyzing-twitter-import-tweets-with-nodejs-and-the-twitter-api-16p6</link>
      <guid>https://dev.to/rhanarion/analyzing-twitter-import-tweets-with-nodejs-and-the-twitter-api-16p6</guid>
      <description>&lt;h1&gt;
  
  
  A tweet in the database is worth two in the API
&lt;/h1&gt;

&lt;p&gt;Working with tweets from the twitter API probably means importing data into your own database - the standard API does not provide historical data (only the last seven days) and has various rate limits.&lt;/p&gt;

&lt;p&gt;So regardless of the final goal in this blog we'll explore importing tweets from the API into a database for future use. All done with NodeJS, written in Typescript and utilizing MongoDB as data store.&lt;/p&gt;

&lt;h1&gt;
  
  
  Big numbers, big problems
&lt;/h1&gt;

&lt;p&gt;Once you authenticate with the API and pull in the first tweets (for example using &lt;a href="https://www.npmjs.com/package/twitter"&gt;the twitter module on npm&lt;/a&gt;) you will notice tweets contain ids as numbers and "id_str" which is the same id just as string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wed Oct 10 20:19:24 +0000 2018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1050118621198921728&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"id_str"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1050118621198921728"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"To make room for more expression, we will now count all emojis as equal—including those with gender‍‍‍ ‍‍and skin t… https://t.co/MkGjXf9aXm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="nl"&gt;"entities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for this is that some languages (Javascript being one of them) can not work with big numbers. For example JS numbers are internally 64-bit floats and only use the first 53 bits for the integer value. Javascript provides the static property Number.MAX_SAFE_INTEGER as 9007199254740991 which is smaller than the id in the example tweet already.&lt;/p&gt;

&lt;p&gt;To work with tweet ids we need a way to handle bigger numbers and use the "id_str". &lt;a href="https://www.npmjs.com/package/big-js"&gt;big.js&lt;/a&gt; provides that functionality and is used in all following code examples.&lt;/p&gt;

&lt;h1&gt;
  
  
  Saving tweets
&lt;/h1&gt;

&lt;p&gt;Saving tweets in MongoDB is easy. Since we are using typescript we can rely on the excellent (Typegoose library)[&lt;a href="https://github.com/typegoose/typegoose"&gt;https://github.com/typegoose/typegoose&lt;/a&gt;] to create models for tweets and interact with MongoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Typegoose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hasezoey/typegoose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entities.user_mentions.screen_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwitterStatus&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Typegoose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;id_str&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;full_text&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;user_mentions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;screen_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TwitterStatusModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TwitterStatus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getModelForClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TwitterStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;schemaOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice I only defined some properties I wanted to use in this model &amp;amp; the index is also related to my use case. You might need to change those depending on the project.&lt;/p&gt;

&lt;p&gt;If schemaOptions define strict as false (see the last line) typegoose saves the whole JSON of the tweet in MongoDB, not just defined fields.&lt;/p&gt;

&lt;h1&gt;
  
  
  Import logic
&lt;/h1&gt;

&lt;p&gt;To optimize the amount of tweets you can crawl from the API in the limits twitter provides an excellent resource on using the since_id and max_id parameters correctly here: &lt;a href="https://developer.twitter.com/en/docs/tweets/timelines/guides/working-with-timelines"&gt;https://developer.twitter.com/en/docs/tweets/timelines/guides/working-with-timelines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In summary this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set the since_id to the highest tweet id your application has already imported defining a lower bound for the imported tweets&lt;/li&gt;
&lt;li&gt;set the max_id to the max_id from the last import and subtract 1 defining the upper bound&lt;/li&gt;
&lt;li&gt;import tweets while setting max_id to the lowest id in the returned list until no new ones are returned, moving the upper bound closer to the lower bound&lt;/li&gt;
&lt;li&gt;once no new tweets are returned set max_id to undefined to remove the upper bound for future imports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to crawl all mentions for an account you can keep track of your crawl status with this model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Typegoose&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hasezoey/typegoose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwitterCrawlStatus&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Typegoose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lowercase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;sinceId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;maxId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;overallMaxId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TwitterCrawlStatusModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TwitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getModelForClas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TwitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic algorithm without any safeguards against failing that uses that logic and imports all mentions for a specific account follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TwitterCrawlStatusModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TwitterCrawlStatusModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tweets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twitterService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMentions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sinceId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sinceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TwitterStatusModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bulkWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id_str&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tweet&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}));&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lowestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getLowestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getHighestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lowestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overallMaxId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overallMaxId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highestId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overallMaxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sinceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overallMaxId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twitterCrawlStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The twitter service
&lt;/h1&gt;

&lt;p&gt;The twitter service itself is just a minimalist wrapper around the twitter npm module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Twitter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twitter-d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Big&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;big.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwitterService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Twitter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;bearerToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Twitter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;consumer_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;bearer_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bearerToken&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getMentions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;sinceId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Big&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;maxId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Big&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search/tweets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -filter:retweets`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;result_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;include_entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;tweet_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;since_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sinceId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;sinceId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;max_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>twitter</category>
    </item>
    <item>
      <title>Deploy an app into your personal cloud</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Tue, 17 Sep 2019 11:39:47 +0000</pubDate>
      <link>https://dev.to/rhanarion/deploy-an-app-into-your-personal-cloud-390l</link>
      <guid>https://dev.to/rhanarion/deploy-an-app-into-your-personal-cloud-390l</guid>
      <description>&lt;p&gt;As the final step in this long series of blogposts we are going to deploy a simple webapp in a docker container to my personal cloud. For context here is the personal cloud setup with Traefik/Let's Encrypt (&lt;a href="https://heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/"&gt;Run a personal cloud with Traefik, Let's encrypt and Zookeeper&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In previous blogposts I also described how I built the app (&lt;a href="https://heltweg.org/posts/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm/"&gt;Build a PWA in docker&lt;/a&gt;).&lt;/p&gt;

&lt;h1&gt;
  
  
  App deployment
&lt;/h1&gt;

&lt;p&gt;The deployment runs the docker container. If you follow my personal cloud setup make sure to use more than one replica as the nature of preemtive VMs means one replica might randomly get shut down.&lt;/p&gt;

&lt;p&gt;Note that the image will be set and updated by gitlab ci as described here: (&lt;a href="https://heltweg.org/posts/deploy-to-google-kubernetes-engine-using-gitlab-ci/"&gt;Deploy to google kubernetes engine using gitlab ci&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu.gcr.io/???/app:latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The service and traefik ingress
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;traefik.frontend.passHostHeader&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;traefik.frontend.priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.???.com&lt;/span&gt;
    &lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;???-app&lt;/span&gt;
        &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Updating the traefik config
&lt;/h1&gt;

&lt;p&gt;Updating the traefik config is important so traefik requests a new HTTPS certificate for the app from Let's encrypt. You will need to add this line to the traefik toml file that is described here (&lt;a href="https://rhazn.com/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/"&gt;Run a personal cloud with Traefik, Let's encrypt and Zookeeper&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;acme.domains&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
    &lt;span class="s"&gt;main = "app.???.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  DNS
&lt;/h1&gt;

&lt;p&gt;Now you can just point the A record of your domain to the traefik external IP and the rest will automatically be handled by your personal cloud :).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S577eJl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/deploy-an-app-into-your-personal-cloud/final-result.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S577eJl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/deploy-an-app-into-your-personal-cloud/final-result.png" alt="The final result: The app runs in your personal cloud &amp;lt;3" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>devops</category>
      <category>gke</category>
    </item>
    <item>
      <title>Deploy to google kubernetes engine using gitlab ci</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Wed, 11 Sep 2019 14:43:05 +0000</pubDate>
      <link>https://dev.to/rhanarion/deploy-to-google-kubernetes-engine-using-gitlab-ci-42gb</link>
      <guid>https://dev.to/rhanarion/deploy-to-google-kubernetes-engine-using-gitlab-ci-42gb</guid>
      <description>&lt;p&gt;In a previous blogpost I showed how I build and publish docker images on gitlab ci (&lt;a href="https://heltweg.org/posts/build-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry/"&gt;Build a docker image on gitlab ci&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Make sure to read that post first for an overview and permission setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Update the kubernetes service with the new docker image
&lt;/h1&gt;

&lt;p&gt;You can easily set up a deploy step using google's own cloud SDK docker images. Note the service account with the permissions to change the kubernetes setup is saved as "GCLOUD_K8S_KEY" variable here.&lt;/p&gt;

&lt;p&gt;This job changes the image of my deployment for the app. You will need to change the last line in the script to whatever change you want to make to your kubernetes setup on deploy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google/cloud-sdk:257.0.0&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo $GCLOUD_K8S_KEY | base64 -d &amp;gt; ${HOME}/gcloud-k8s-key.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gcloud auth activate-service-account --key-file ${HOME}/gcloud-k8s-key.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gcloud config set project personal-cloud-project-id&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gcloud config set compute/zone your-compute-zone&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gcloud container clusters get-credentials production&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl set image deployment/???-app ???-app=eu.gcr.io/docker-project-id/app:${CI_COMMIT_SHA}&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>docker</category>
      <category>ci</category>
    </item>
    <item>
      <title>Build a docker image on gitlab ci and publish it to google container registry</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Fri, 06 Sep 2019 11:13:29 +0000</pubDate>
      <link>https://dev.to/rhanarion/build-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry-6h8</link>
      <guid>https://dev.to/rhanarion/build-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry-6h8</guid>
      <description>&lt;p&gt;In previous blogposts I explained my concept of a personal cloud for my own projects (&lt;a href="https://heltweg.org/posts/kubernetes-for-sideprojects-hardware-is-dead/" rel="noopener noreferrer"&gt;Kubernetes for Sideprojects&lt;/a&gt;) and how I set it up (&lt;a href="https://heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/" rel="noopener noreferrer"&gt;Run a personal cloud with Traefik, Let's encrypt and Zookeeper&lt;/a&gt;). I also showed how I packaged a PWA project with docker (&lt;a href="https://heltweg.org/posts/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm/" rel="noopener noreferrer"&gt;Build a PWA in docker&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;With all those ingredients ready to go the last hurdle to solve is building the docker image automatically as well as publishing it to a private container registry so I can deploy it to my cloud from there.&lt;/p&gt;

&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;The goal of the setup is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build a docker image from any git hash using gitlab ci&lt;/li&gt;
&lt;li&gt;push that docker image to a google container registry tagged with the git hash&lt;/li&gt;
&lt;li&gt;update the kubernetes service description in the personal cloud project to pull the new docker image&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Permissions
&lt;/h1&gt;

&lt;p&gt;To enable gitlab to do these actions on our behalf we need to set up service accounts. We need&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one account that allows gitlab to upload a new docker image to the container registry&lt;/li&gt;
&lt;li&gt;an account that allows it to change our kubernetes setup in the personal cloud registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can create these service accounts in the "IAM &amp;amp; admin" -&amp;gt; "Service accounts" section of google cloud. Make sure to download and save the generated json file.&lt;/p&gt;

&lt;p&gt;We will also need to allow the personal cloud project to pull the docker image from the container registry that is in a different project. For that I followed this excellent blogpost by Alexey Timanovskiy (&lt;a href="https://medium.com/google-cloud/using-single-docker-repository-with-multiple-gke-projects-1672689f780c" rel="noopener noreferrer"&gt;Using single Docker repository with multiple GKE projects&lt;/a&gt;).&lt;/p&gt;

&lt;h1&gt;
  
  
  Publish a docker image with gitlab ci
&lt;/h1&gt;

&lt;p&gt;To allow gitlab ci to use your service account you need to save the content of the json files as a base64 encoded variable in the backend. You can find the setting under "Settings" -&amp;gt; "CI /CD" -&amp;gt; "Variables". Be careful with this data since it is security relevant. The variables here will be available as environment variables during your jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fheltweg.org%2Fimg%2Fposts%2Fbuild-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry%2Fgitlab-ci-variables.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fheltweg.org%2Fimg%2Fposts%2Fbuild-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry%2Fgitlab-ci-variables.png" alt="The service account variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use the following gitlab ci stage to build and publish a project. Note that it only runs manually and for master. In this case it uses the service account saved in GCLOUD_SERVICE_KEY:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;publish&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker:19.03.1&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker:dind&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DOCKER_DRIVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;overlay&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo $GCLOUD_SERVICE_KEY | base64 -d &amp;gt; ${HOME}/gcloud-service-key.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker login -u _json_key --password-stdin https://eu.gcr.io &amp;lt; ${HOME}/gcloud-service-key.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker build -t eu.gcr.io/projectid/app:${CI_COMMIT_SHA} .&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker push "eu.gcr.io/projectid/app:${CI_COMMIT_SHA}"&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a last step you can update the kubernetes description of the service to deploy it. I wrote about that process here: &lt;a href="https://heltweg.org/posts/deploy-to-google-kubernetes-engine-using-gitlab-ci/" rel="noopener noreferrer"&gt;https://heltweg.org/posts/deploy-to-google-kubernetes-engine-using-gitlab-ci/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fheltweg.org%2Fimg%2Fposts%2Fbuild-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry%2Ftagged-images.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fheltweg.org%2Fimg%2Fposts%2Fbuild-a-docker-image-on-gitlab-ci-and-publish-it-to-google-container-registry%2Ftagged-images.png" alt="The resulting tagged images in gcr"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org" rel="noopener noreferrer"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build a progressive web app in docker with nginx to deploy to kubernetes or docker swarm</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Tue, 27 Aug 2019 11:22:16 +0000</pubDate>
      <link>https://dev.to/rhanarion/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm-253f</link>
      <guid>https://dev.to/rhanarion/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm-253f</guid>
      <description>&lt;p&gt;An &lt;strong&gt;updated version of this article with current examples is available at &lt;a href="https://www.heltweg.org/posts/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm/" rel="noopener noreferrer"&gt;https://www.heltweg.org/posts/build-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm/&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The old, outdated post follows below:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With my personal cloud setup based on kubernetes done (you can read about it here: &lt;a href="https://www.heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/" rel="noopener noreferrer"&gt;https://www.heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/&lt;/a&gt;) it is time to actually deploy the first project into it. &lt;/p&gt;

&lt;p&gt;The easiest application to deploy is a pure client side single page application, packaged in a docker container with a webserver like nginx to deliver the files. Packaging the application into it's own container allows us to build a standardized container that can be run locally for testing or deployed to docker swarm and kubernetes.&lt;/p&gt;

&lt;p&gt;Setting up and configuring our own HTTP server also allows for fine tuning of caching to achieve good lighthouse scores:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.heltweg.org%2Fimg%2Fposts%2Fbuild-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm%2Fscores.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.heltweg.org%2Fimg%2Fposts%2Fbuild-a-progressive-web-app-in-docker-with-nginx-to-deploy-to-kubernetes-or-docker-swarm%2Fscores.png" alt="Lighthouse scores, don't let the bugged checkmark fool you it passes the PWA tests ;)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Building in docker
&lt;/h1&gt;

&lt;p&gt;For this setup we build the app using docker. That way the app is always built with the same node version and can be consistently reproduced, regardless of installed software on the local computer.&lt;/p&gt;

&lt;p&gt;The project here is a react application based on create-react-app but it works similarly with any frontend framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:12.6.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json tsconfig.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./public ./public&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build &lt;span class="nt"&gt;--prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Configuring nginx
&lt;/h1&gt;

&lt;p&gt;For the nginx config I placed a config file into the project and checked it in. This config file is later on copied into the container that serves the SPA. To achieve good performance we&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable gzip for HTML/CSS and JS files&lt;/li&gt;
&lt;li&gt;set up caching for any file for one year (because create-react-app builds new file names with each production build that invalidates the cache on deploy)&lt;/li&gt;
&lt;li&gt;disable the cache for the actual index.html file (since we need to make the browser request the newest files)&lt;/li&gt;
&lt;li&gt;Redirect any request to index.html so the SPA router can handle them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the complete config file here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="err"&gt;_&lt;/span&gt;;

    &lt;span class="n"&gt;gzip&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;gzip_types&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;css&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;javascript&lt;/span&gt;;

    &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;

    &lt;span class="c"&gt;# Force all paths to load either itself (js files) or go through index.html.
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;

        &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Cache&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="s2"&gt;"no-store, no-cache, must-revalidate"&lt;/span&gt;;    
    }

    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;

        &lt;span class="n"&gt;expires&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;;
        &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Cache&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Building the final container
&lt;/h1&gt;

&lt;p&gt;The end result will be a combination of a) building the SPA in  docker in the "build" step and then setting up a container from the nginx image and copying the JS from the build step as well as the checked in nginx config described above.&lt;/p&gt;

&lt;p&gt;Finally we expose port 80 and start nginx to serve the files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:12.6.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json tsconfig.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./public ./public&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build &lt;span class="nt"&gt;--prod&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.16.1&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /build /var/www/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./k8s/config/nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org" rel="noopener noreferrer"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>react</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Run a personal Cloud with Traefik, Let's encrypt and Zookeeper</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Sat, 17 Aug 2019 10:51:05 +0000</pubDate>
      <link>https://dev.to/rhanarion/run-a-personal-cloud-with-traefik-let-s-encrypt-and-zookeeper-4a7g</link>
      <guid>https://dev.to/rhanarion/run-a-personal-cloud-with-traefik-let-s-encrypt-and-zookeeper-4a7g</guid>
      <description>&lt;h1&gt;
  
  
  Kubernetes ingress with Traefik
&lt;/h1&gt;

&lt;p&gt;As mentioned in my &lt;a href="https://heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/"&gt;last blog post&lt;/a&gt; I want to focus on a provider neutral setup for my own cloud, using technology that is not bound to any cloud offering whenever possible.&lt;/p&gt;

&lt;p&gt;While google cloud offers load balanced HTTP ingress by default it is apparently very expensive in comparison to running small nodes and I have heard only good things about using Traefik for kubernetes ingress.&lt;/p&gt;

&lt;p&gt;For setting up Traefik I followed &lt;a href="https://medium.com/google-cloud/traefik-on-a-google-kubernetes-engine-cluster-managed-by-terraform-ad871be8ee26"&gt;Manuel's excellent guide&lt;/a&gt; with minor modifications (you can find the final files at the end of the article.)&lt;/p&gt;

&lt;h1&gt;
  
  
  HTTPs and Let's encrypt
&lt;/h1&gt;

&lt;p&gt;Traefik has built-in support for automatically getting and renewing HTTPS certificates with &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt;. As HTTPS is good practice and a requirement for HTTP2 and PWAs anyway I set it up using &lt;a href="https://docs.traefik.io/user-guide/examples/#onhostrule-option-with-http-challenge"&gt;example configurations&lt;/a&gt; from the Traefik docs.&lt;/p&gt;

&lt;p&gt;Because I was using just one node for Traefik I chose to go with the easy setup of a local acme.json file that stores the certificate while the node is running.&lt;/p&gt;

&lt;h1&gt;
  
  
  GKE Preemptible nodes, your own chaos monkey
&lt;/h1&gt;

&lt;p&gt;To save costs I chose to use "Preemtible VMs" as nodes to power my kubernetes cluster on GKE. According to google's docs: "Preemptible VMs are Google Compute Engine VM instances that last a maximum of 24 hours and provide no availability guarantees." This means the nodes in my kubernetes cluster randomly go down and are never up more than 24h. While this obviously is not a smart decision for a production setup I have chosen to embrace it and consider the nodes going down my own &lt;a href="https://netflix.github.io/chaosmonkey/"&gt;"chaos monkey"&lt;/a&gt; that forces me to write resilient code.&lt;/p&gt;

&lt;p&gt;A concrete example I ran into: The Let's encrypt production API has a rate limit of requesting 5 certificates for the same URL in a week. Because my initial naive setup did not save the certificate anywhere it got lost whenever my Traefik node was terminated. While Traefik regenerates the certificate without any issue on startup... after five startups I hit my rate limit and was greeted by an insecure warning without certificate.&lt;/p&gt;

&lt;h1&gt;
  
  
  Shared K/V store for Traefik with Zookeeper
&lt;/h1&gt;

&lt;p&gt;Enter a shared Key/Value store for Traefik. Using one is required if you want to run Traefik in cluster mode anyway (and I like to think my setup is easily scalable). It also means I can store my generated certificate in the K/V store where it will no longer just disappear when Traefik restarts.&lt;/p&gt;

&lt;p&gt;Since I have previous experience with Zookeeper and the setup was relatively painless I went with it.&lt;/p&gt;

&lt;h1&gt;
  
  
  All Kubernetes yaml files for the setup
&lt;/h1&gt;

&lt;p&gt;Finally the meat of the blog post, my complete setup as yaml files you can directly deploy into your GKE cluster:&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up Zookeeper first
&lt;/h2&gt;

&lt;p&gt;From this excellent ressource: &lt;a href="https://github.com/kow3ns/kubernetes-zookeeper/blob/master/manifests/README.md"&gt;https://github.com/kow3ns/kubernetes-zookeeper/blob/master/manifests/README.md&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk-hs&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2888&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3888&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;leader-election&lt;/span&gt;
  &lt;span class="na"&gt;clusterIP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk-cs&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2181&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk-hs&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;podManagementPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Parallel&lt;/span&gt;
  &lt;span class="na"&gt;updateStrategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;podAntiAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app"&lt;/span&gt;
                    &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
                    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;zk&lt;/span&gt;
              &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.io/hostname"&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-zookeeper&lt;/span&gt;
        &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gcr.io/google_containers/kubernetes-zookeeper:1.0-3.4.10"&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200M"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.3"&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2181&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2888&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3888&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;leader-election&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start-zookeeper&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--servers=1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--data_dir=/var/lib/zookeeper/data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--data_log_dir=/var/lib/zookeeper/data/log&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--conf_dir=/opt/zookeeper/conf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--client_port=2181&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--election_port=3888&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--server_port=2888&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--tick_time=2000&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--init_limit=10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--sync_limit=5&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--heap=512M&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--max_client_cnxns=60&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--snap_retain_count=3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--purge_interval=12&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--max_session_timeout=40000&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--min_session_timeout=4000&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;          &lt;span class="s"&gt;--log_level=INFO"&lt;/span&gt;
        &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zookeeper-ready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2181"&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
          &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zookeeper-ready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2181"&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
          &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;datadir&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/zookeeper&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;datadir&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ReadWriteOnce"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Permissions for Traefik
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# create Traefik cluster role&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;services&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;endpoints&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;secrets&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;watch&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;extensions&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ingresses&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;watch&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# create Traefik service account&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# bind role with service account&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Traefik config
&lt;/h2&gt;

&lt;p&gt;Note the configuration of zookeeper using the service address for the "client service" (cs) as well as the Let's encrypt config here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# define Traefik configuration&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-config&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik.toml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# traefik.toml&lt;/span&gt;
    &lt;span class="s"&gt;defaultEntryPoints = ["http", "https"]&lt;/span&gt;
    &lt;span class="s"&gt;[entryPoints]&lt;/span&gt;
      &lt;span class="s"&gt;[entryPoints.http]&lt;/span&gt;
        &lt;span class="s"&gt;address = ":80"&lt;/span&gt;
        &lt;span class="s"&gt;[entryPoints.http.redirect]&lt;/span&gt;
          &lt;span class="s"&gt;entryPoint = "https"&lt;/span&gt;
      &lt;span class="s"&gt;[entryPoints.https]&lt;/span&gt;
      &lt;span class="s"&gt;address = ":443"&lt;/span&gt;
        &lt;span class="s"&gt;[entryPoints.https.tls]&lt;/span&gt;

      &lt;span class="s"&gt;[zookeeper]&lt;/span&gt;
        &lt;span class="s"&gt;endpoint = "zk-cs.default.svc.cluster.local:2181"&lt;/span&gt;
        &lt;span class="s"&gt;watch = true&lt;/span&gt;
        &lt;span class="s"&gt;prefix = "traefik"&lt;/span&gt;

      &lt;span class="s"&gt;[acme]&lt;/span&gt;
      &lt;span class="s"&gt;email = "your@email.com"&lt;/span&gt;
      &lt;span class="s"&gt;storage = "traefik/acme/account"&lt;/span&gt;
      &lt;span class="s"&gt;onHostRule = true&lt;/span&gt;
      &lt;span class="s"&gt;caServer = "https://acme-v02.api.letsencrypt.org/directory"&lt;/span&gt;
      &lt;span class="s"&gt;acmeLogging = true&lt;/span&gt;
      &lt;span class="s"&gt;entryPoint = "https"&lt;/span&gt;
        &lt;span class="s"&gt;[acme.httpChallenge]&lt;/span&gt;
        &lt;span class="s"&gt;entryPoint = "http"&lt;/span&gt;

      &lt;span class="s"&gt;[[acme.domains]]&lt;/span&gt;
        &lt;span class="s"&gt;main = "your.domain.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment for Traefik
&lt;/h2&gt;

&lt;p&gt;I run just one replica in here to save costs in my dev setup but I've also scaled it up to three to test if it would stay up 100% of the time even with random nodes going down and everything works fine :).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# declare Traefik deployment&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
      &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-config&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik:1.7.14"&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/traefik/config"&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--configfile=/etc/traefik/config/traefik.toml&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--kubernetes&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--logLevel=INFO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Traefik service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Declare Traefik ingress service&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tls&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Final result
&lt;/h1&gt;

&lt;p&gt;The final workloads with traefik and zookeeper&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yw0gvV2k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/workloads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yw0gvV2k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/workloads.png" alt="Traefik and Zookeeper workloads" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the kubernetes ingresses (ignore the app I used as demo for this)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6Nm_R6Zq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/ingress.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6Nm_R6Zq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper/ingress.png" alt="Kubernetes ingresses" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Kubernetes for sideprojects: Hardware is dead</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Sat, 17 Aug 2019 10:25:39 +0000</pubDate>
      <link>https://dev.to/rhanarion/kubernetes-for-sideprojects-hardware-is-dead-3m7m</link>
      <guid>https://dev.to/rhanarion/kubernetes-for-sideprojects-hardware-is-dead-3m7m</guid>
      <description>&lt;h1&gt;
  
  
  Why invest in a personal cloud
&lt;/h1&gt;

&lt;p&gt;It has never been easier to host your personal side projects. Tools like surge.sh or Heroku make it painless to run your code. And if all else fails the old reliable "drag and drop files to a ftp" is always there - so why invest time into setting up your own personal cloud with kubernetes?&lt;/p&gt;

&lt;p&gt;My goal for technology is typically to find a setup that gets boring to work with because I know it well and can focus on delivering new functionality. For that a setup needs to be future proof (so it continues to work for a long time), generic (so I can use it for a wide range of applications and do not need to switch for every project) and not bound to any company or product.&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker &amp;amp; kubernetes
&lt;/h1&gt;

&lt;p&gt;Docker and Kubernetes check most of these boxes. Kubernetes has de-facto won the orchestration war for containerized applications and managed kubernetes offerings from all major cloud providers means there is no provider lock-in. As an open source project that is not bound to individual company or setup you retain flexibility. Lastly learning more about it is useful in any case - if you stop developing your side projects the devops knowledge you gained still looks good on a CV.&lt;/p&gt;

&lt;p&gt;A further important point that separates kubernetes from similar offerings is that you can 100% forget about hardware while still being free to use standard technology. That means you do not need to maintain a set of servers if you use a managed kubernetes offering (like you would need to with docker swarm) but you are still not locked into any provider (like you would be by using AWS Beanstalk, Firebase or Heroku).&lt;/p&gt;

&lt;h1&gt;
  
  
  Why not...?
&lt;/h1&gt;

&lt;p&gt;My own personal journey has made me try out the following alternatives in the order they are written down here and I have abandoned them all.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Own servers

&lt;ul&gt;
&lt;li&gt;You need to maintain and update your own servers. If you do it without automation you will forget what you did.&lt;/li&gt;
&lt;li&gt;The complexity and learning curve is close to kubernetes anyway. Instead of learning about deployments and stateful sets you will need to know about processes or package managers.&lt;/li&gt;
&lt;li&gt;Scaling is harder.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Services (e.g. Firebase)

&lt;ul&gt;
&lt;li&gt;Very easy to set up and use, probably advisable if you work on one or a few projects&lt;/li&gt;
&lt;li&gt;Using vendor specific tools (like firebase realtime database) locks you and the logic of your app into that vendor. What if they change their offering (price? functionality?)&lt;/li&gt;
&lt;li&gt;Services are the hammer that makes very problem look like a nail. Instead of picking the best technology for a job you will start trying to shoehorn your problems to be solved by what is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Docker Swarm

&lt;ul&gt;
&lt;li&gt;I really liked it, very close to kubernetes but considerably easier&lt;/li&gt;
&lt;li&gt;Sadly still forces you to manage your own servers to set up the swarm cluster, I could not find a "managed docker swarm" solution (if you know one, &lt;a href="mailto:pheltweg@gmail.com"&gt;let me know&lt;/a&gt;!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  The value of infrastructure as code
&lt;/h1&gt;

&lt;p&gt;By choosing kubernetes you also commit to keeping your infrastructure in code which has a plethora of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it automatically and correctly documents your infrastructure and any changes you made by reading git logs&lt;/li&gt;
&lt;li&gt;you can easily redeploy it on new providers or e.g. locally for development&lt;/li&gt;
&lt;li&gt;all your infrastructure is in one place so you don't need to think about how each project solved a problem&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Organizing your projects
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L-8eowdb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/kubernetes-for-sideprojects-hardware-is-dead/google-cloud-setup.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L-8eowdb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heltweg.org/img/posts/kubernetes-for-sideprojects-hardware-is-dead/google-cloud-setup.svg" alt="Google Cloud Setup" width="582" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For my personal cloud I chose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Google Cloud Kubernetes Engine Cluster as cloud

&lt;ul&gt;
&lt;li&gt;Traefik as ingress router to forward requests to my projects&lt;/li&gt;
&lt;li&gt;Zookeeper for shared state between Traefik nodes&lt;/li&gt;
&lt;li&gt;Let's Encrypt to automatically set up HTTPS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Every project has it's own Google Cloud Container Registry as a private docker repository&lt;/li&gt;
&lt;li&gt;Service accounts allow the GKE cloud to pull the docker images of individual projects from the respective repository&lt;/li&gt;
&lt;li&gt;Infrastructure descriptions for the generic GKE setup and services is in one place, infrastructure descriptions for the individual projects is in their respective code&lt;/li&gt;
&lt;li&gt;Deployment is handled with gitlab&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Further reading
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Read my blog post about the actual setup of this cloud: &lt;a href="https://heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper"&gt;https://heltweg.org/posts/run-a-personal-cloud-with-traefik-lets-encrypt-and-zookeeper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Also read this helpful article: &lt;a href="https://medium.com/google-cloud/traefik-on-a-google-kubernetes-engine-cluster-managed-by-terraform-ad871be8ee26"&gt;Traefik on a Google Kubernetes Engine Cluster managed by Terraform by Manuel Zapf&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Entity-Systems for typescript based games</title>
      <dc:creator>rhazn</dc:creator>
      <pubDate>Fri, 16 Aug 2019 09:23:04 +0000</pubDate>
      <link>https://dev.to/rhanarion/entity-systems-for-typescript-based-games-28kb</link>
      <guid>https://dev.to/rhanarion/entity-systems-for-typescript-based-games-28kb</guid>
      <description>&lt;h1&gt;
  
  
  Entity-Systems for typescript based games
&lt;/h1&gt;

&lt;p&gt;This post is also available &lt;a href="https://heltweg.org/posts/entity-systems-for-typescript-based-games/"&gt;on my blog&lt;/a&gt; where I plan to write more about game development with typescript if you are interested :).&lt;/p&gt;

&lt;p&gt;For my latest game project &lt;a href="https://frozzen-client.appspot.com/"&gt;Frozzen&lt;/a&gt; I want to explore how an external UI, build with Angular, would work for a browser based game. Since Angular is written in Typescript that means ideally the game should also use the same.&lt;/p&gt;

&lt;p&gt;I have used &lt;a href="https://github.com/junkdog/artemis-odb"&gt;Artemis ODB&lt;/a&gt; as framework for a Java based game in the past and liked it a lot. Entity-Systems are much better introduced by any of the huge amount of articles out there (for example the classic on &lt;a href="http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/"&gt;T=Machine&lt;/a&gt; but I feel they are especially well suited to Javascript/Typescript development.&lt;/p&gt;

&lt;p&gt;If you work with a strict separation of logic into systems and data only into components there is a very natural way to serialize components, JSON. Whole levels can be expressed as an array of JSON data that is used to set up components. That is why I prefer a very basic but strict implementation like artemis over similar frameworks like PhaserJS.&lt;/p&gt;

&lt;p&gt;I started my development with artemists, a Typescript port of artemis by darkoverlordofdata. Unfortunately the code is a bit outdated and does not use import/export and can not be directly imported for newer Typescript versions (since it extends the built-in Array).&lt;/p&gt;

&lt;p&gt;With darkoverlordofdata’s permission I did a quick update to the Typescript parts of the code only, adding import/export support and fixing the build for newer Typescript versions. &lt;a href="https://github.com/rhazn/artemis-ts"&gt;You can find the updated version here&lt;/a&gt;. If you are looking for an example of that framework in action you can play an example level of Frozzen &lt;a href="https://frozzen-client.appspot.com/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  About Me
&lt;/h1&gt;

&lt;p&gt;I am a full stack developer and digital product enthusiast, I am available for freelance work and always looking for the next exciting project :).&lt;/p&gt;

&lt;p&gt;You can reach me online at &lt;a href="https://heltweg.org"&gt;https://heltweg.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>angular</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
