<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[MrGeek]]></title><description><![CDATA[I write about my daily software engineering routine mainly, and some other interesting stuff]]></description><link>https://blog.mrg.sh</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1601887664806/VXpqT6akF.jpeg</url><title>MrGeek</title><link>https://blog.mrg.sh</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 07 Oct 2026 03:38:10 GMT</lastBuildDate><atom:link href="https://blog.mrg.sh/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[[Side Project] Post automated Youtube videos from Reddit]]></title><description><![CDATA[Long introduction
I've always been curious about some videos that I come across on Facebook and YouTube that look a little strange. Like they were just generated by a robot. Think of a video playing with a little box of a guy reacting to it while the...]]></description><link>https://blog.mrg.sh/side-project-post-automated-youtube-videos-from-reddit</link><guid isPermaLink="true">https://blog.mrg.sh/side-project-post-automated-youtube-videos-from-reddit</guid><category><![CDATA[youtube]]></category><category><![CDATA[golang]]></category><category><![CDATA[automation]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Wed, 05 Oct 2022 18:44:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664995370323/xCw2EP9D1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-long-introduction">Long introduction</h2>
<p>I've always been curious about some videos that I come across on Facebook and YouTube that look a little strange. Like they were just generated by a robot. Think of a video playing with a little box of a guy reacting to it while their reaction is incredibly strange and appears utterly unrelated. They always feature stuff that either doesn't fit the caption or the content itself is unusual.</p>
<p>I finally understand why. These videos are all automated and performed by scripts. It also appears that "Youtube/Facebook automation" is popular right now.</p>
<p>I first became curious about "Youtube automation" after running across the word. I didn't know that some people were building scripts to scrape stuff from websites (like Facebook and Reddit, but largely Reddit, to be honest), process it, and then produce a video from it to put online.</p>
<p>Imagine this thing running, and having a cron-job to run and post a new video every day. Consider your potential earnings and the consistency of this. You simply need to worry about funding your server and maybe improving how the material is processed while being scraped.</p>
<p>There is a lot to say about this, but I'm here to explain how I used Golang to get several Tiktok videos from a subreddit, combine them, and post a video of the "Top 10 funny/cringiest Tiktok videos" on YouTube without knowing what the video's content was.</p>
<blockquote>
<h3 id="heading-tldr">TLDR</h3>
<p>Social media is really weird. Authentic content is becoming harder to find, and BTW! I managed to code a Golang script to scrape Tiktok videos from a Subreddit, combine them into a single video, and upload it to Youtube all automatically.</p>
</blockquote>
<h2 id="heading-so-how-though">So how though?</h2>
<p>So for starters, the initial idea is to split the big thing into smaller things and blah blah you know the boring engineering stuff. But I seriously wanted to make it to scale. So it made sense to have 3 Golang packages/modules:</p>
<ol>
<li>Reddit Aggregator:<ul>
<li>This would be responsible for scraping a subreddit and extracting Tiktok videos out of it.</li>
<li>Will save the results into a MongoDB collection - because schema is overrated -</li>
</ul>
</li>
<li>Video Processor:<ul>
<li>This would grab unpublished results from MongoDB collection:<ul>
<li>Downloads all videos on the server</li>
<li>Process each single video</li>
<li>Combine all videos into a single video</li>
</ul>
</li>
<li>Now obviously this seems a bit too much for a single service, but ... meh</li>
</ul>
</li>
<li>Uploader:<ul>
<li>This will upload the final video to Youtube</li>
</ul>
</li>
</ol>
<blockquote>
<h3 id="heading-tldr">TLDR</h3>
<p>This is a side project, and I wanted it to be complex enough that Youtube would actually use it. However, spoiler alert: they won't. That is why I divided the code into three Golang packages to run them as microservices serverless functions. Spoiler alert again: they are not.</p>
</blockquote>
<h2 id="heading-reddit-aggregator">Reddit Aggregator</h2>
<p>The aggregator is straightforward; all it does is use the Reddit API to obtain posts from a subreddit and store them in a MongoDB collection.</p>
<pre><code class="lang-bash">https://www.reddit.com/r/&lt;SUBREDDIT&gt;/&lt;POSTS_TYPE&gt;.json?<span class="hljs-built_in">limit</span>=&lt;LIMIT&gt;
https://www.reddit.com/r/linux/hot.json?<span class="hljs-built_in">limit</span>=10
</code></pre>
<p>Now not to keep it too simple, every single thing between <code>&lt; &gt;</code> is a variable, that can be provided before the run to make it more robust.</p>
<p>The aggregator will save each post on a DB collection that will look like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"_id"</span> : ObjectId(<span class="hljs-string">"633c37a1740ff26f2433f99c"</span>),
    <span class="hljs-attr">"hash"</span> : <span class="hljs-string">"78a0428f87a9adf7d692e5435466bc5b"</span>,
    <span class="hljs-attr">"title"</span> : <span class="hljs-string">"&lt;POST_TITLE&gt;"</span>,
    <span class="hljs-attr">"video"</span> : <span class="hljs-string">"&lt;VIDEO_LINK&gt;"</span>,
    <span class="hljs-attr">"created_at"</span> : ISODate(<span class="hljs-string">"2022-10-04T15:39:45.808+02:00"</span>),
    <span class="hljs-attr">"updated_at"</span> : ISODate(<span class="hljs-string">"2022-10-04T15:39:45.808+02:00"</span>),
    <span class="hljs-attr">"published"</span> : <span class="hljs-literal">false</span>
}
</code></pre>
<p>What are the <code>hash</code> and <code>published</code> fields for you wonder?</p>
<p>Well, <code>hash</code> is a simple MD5 hash of the video link, and on DB this field has a unique index so that we're sure that the aggregator never inserts duplicates. While <code>published</code> is for determining to not return a post/video that was already published to our Youtube channel, this way it will not slip into the process.</p>
<h2 id="heading-video-processor">Video processor</h2>
<p>So, this package will begin by getting a list of unpublished posts from the database, downloading them all to a specific directory, and then starting the processor to process them all.</p>
<h3 id="heading-downloader">Downloader</h3>
<p>Downloader is using <code>youtube-dl</code> behind the scenes to download every single video, now why not just a simple HTTP GET request and write the file?</p>
<ul>
<li><code>youtube-dl</code> provide great support for downloading videos, from various sources.</li>
<li>The goal is to be scalable and to be able to even have multiple aggregators ( facebook aggregator, youtube aggregator, Vimeo... etc )</li>
<li>I already had a wrapper to run a CLI command safely that I did (<a target="_blank" href="https://github.com/omarahm3/turtle/blob/master/pkg/sniffer/sniff.go#L118">sniff.go#L118</a>)</li>
</ul>
<p>Goroutines were required since downloading 10 videos one at a time seemed incredibly slow. I had to run 10 goroutines and wait for them to complete before going on to the next step.</p>
<p>With Goroutines in place, there comes a challenge, more on that will be on <a class="post-section-overview" href="#concurrency-challenge">here</a></p>
<p>So to download a single video is just that:</p>
<pre><code class="lang-go"><span class="hljs-keyword">const</span> ydl_command = <span class="hljs-string">"youtube-dl %s -q -o %s.%%(ext)s"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">download</span><span class="hljs-params">(url, base, filename <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>, error)</span></span> {
    dest := path.Join(base, <span class="hljs-string">"downloads"</span>, filename)
    cmdString := fmt.Sprintf(ydl_command, url, dest)
    args := strings.Split(cmdString, <span class="hljs-string">" "</span>)

    cmd := exec.Command(args[<span class="hljs-number">0</span>], args[<span class="hljs-number">1</span>:]...)
    out, err := helpers.RunCmd(cmd)

    <span class="hljs-keyword">return</span> args[<span class="hljs-built_in">len</span>(args)<span class="hljs-number">-1</span>], out, err
}
</code></pre>
<h3 id="heading-processor">Processor</h3>
<p><strong>ffmpeg is pure evil</strong></p>
<p>The processor is identical to the downloader; it will run through each video and, given that all Tiktok videos are vertical and have a black background on the left and right sides, its sole function will be to create blurry background for the video.</p>
<p>So to achieve that goal, you will have to use <code>ffmpeg</code> which seems more evil than Regex.</p>
<p>Anyway for processing a single video this is the function to do it:</p>
<pre><code class="lang-go">
<span class="hljs-keyword">const</span> (
    ffmpeg_quality = <span class="hljs-string">"1080k"</span>
    ffmpeg_command = <span class="hljs-string">`ffmpeg -i %s -lavfi %s -vb %s -c:v libx264 -crf 20 %s.mp4 -n`</span>
    ffmpeg_filters = <span class="hljs-string">`[0:v]scale=ih*16/9:-1,boxblur=luma_radius=min(h\,w)/20:luma_power=1:chroma_radius=min(cw\,ch)/20:chroma_power=1[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2,crop=h=iw*9/16`</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">process</span><span class="hljs-params">(video downloader.Video, base <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>, error)</span></span> {
    dest := path.Join(base, <span class="hljs-string">"blurry"</span>, video.Post.Hash)
    cmdString := fmt.Sprintf(ffmpeg_command, video.Path, ffmpeg_filters, ffmpeg_quality, dest)
    args := strings.Split(cmdString, <span class="hljs-string">" "</span>)

    cmd := exec.Command(args[<span class="hljs-number">0</span>], args[<span class="hljs-number">1</span>:]...)
    out, err := helpers.RunCmd(cmd)
    <span class="hljs-keyword">if</span> strings.Contains(out, <span class="hljs-string">"already exists. Exiting"</span>) {
        err = <span class="hljs-literal">nil</span>
    }

    <span class="hljs-keyword">return</span> args[<span class="hljs-built_in">len</span>(args)<span class="hljs-number">-2</span>], out, err
}
</code></pre>
<p>For the record, it took me ~1hr to fix an issue running <code>ffmpeg</code> command to learn that I had to change this:</p>
<pre><code class="lang-go"><span class="hljs-string">`ffmpeg -i %s -lavfi '%s' -vb %s -c:v libx264 -crf 20 %s.mp4 -n`</span>
</code></pre>
<p>to this:</p>
<pre><code class="lang-go"><span class="hljs-string">`ffmpeg -i %s -lavfi %s -vb %s -c:v libx264 -crf 20 %s.mp4 -n`</span>
</code></pre>
<p>Now of course since the script will be processing 10 videos, this needs to be done using Goroutines to benefit the concurrency. As with the downloader, this will be later explained in the <a class="post-section-overview" href="#concurrency-challenge">concurrency challenge</a> section</p>
<h3 id="heading-merger">Merger</h3>
<p>The merger is the processor's last step, where after adding the blurry background to all of the videos, it will merge them all into a single video. But it does that in steps:</p>
<ol>
<li>Create a text file that has all of the videos paths in this format</li>
</ol>
<pre><code class="lang-txt">file /some/path/blurry-file1.mp4
file /some/path/blurry-file2.mp4
</code></pre>
<ol>
<li>Then pass that file to <code>ffmpeg</code> so that it combines all videos</li>
</ol>
<pre><code class="lang-bash">ffmpeg -f concat -safe 0 -i all_videos.txt final.mp4
</code></pre>
<ol>
<li>Then remove the temporary <code>all_videos.txt</code> file</li>
</ol>
<pre><code class="lang-golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">MergeAll</span><span class="hljs-params">(videos []ProcessedVideo, base, output <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    temp, err := createVideosFile(videos, base)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    out, err := runMerge(output, temp)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Printf(<span class="hljs-string">"error running command, output: %q"</span>, out)
        <span class="hljs-keyword">return</span> err
    }

    <span class="hljs-keyword">return</span> os.Remove(temp)
}
</code></pre>
<h2 id="heading-uploader">Uploader</h2>
<p>This is the last piece of the puzzle where the final video gets uploaded to youtube, but to avoid having the same title always, there is an option to have a random title deduced from the posts we already have on the database.</p>
<p>Since the goal is to be able to publish a video every single day, you can just get all posts of today and get a single random post, and then make it the video's title.</p>
<pre><code class="lang-golang"><span class="hljs-keyword">type</span> Config <span class="hljs-keyword">struct</span> {
    VideoInfoType    <span class="hljs-keyword">string</span>
    VideoTitle       <span class="hljs-keyword">string</span>
    VideoDescription <span class="hljs-keyword">string</span>
    DatabaseURI      <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Config)</span> <span class="hljs-title">GetTitle</span><span class="hljs-params">()</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, error)</span></span> {
    <span class="hljs-keyword">switch</span> c.VideoInfoType {
    <span class="hljs-keyword">case</span> VideoTypeManual:
        <span class="hljs-keyword">return</span> c.VideoTitle, <span class="hljs-literal">nil</span>
    <span class="hljs-keyword">case</span> VideoTypeRandom:
        db, cancel, err := getDbClient(c)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, err
        }
        <span class="hljs-keyword">defer</span> cancel()

        <span class="hljs-keyword">var</span> posts []types.Post
        err = db.FindRandomOfToday(<span class="hljs-number">1</span>, &amp;posts)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, err
        }
        p := posts[<span class="hljs-number">0</span>]
        <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"Top 10 best/cringiest Tiktoks today: %s"</span>, p.Title), <span class="hljs-literal">nil</span>
    <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, fmt.Errorf(<span class="hljs-string">"unknown video type %v"</span>, c.VideoInfoType)
    }
}
</code></pre>
<p>Besides that script of course will need <code>client_credentials</code> and API token to be able to use youtube API to upload the video.</p>
<pre><code class="lang-golang">upload := &amp;youtube.Video{
  Snippet: &amp;youtube.VideoSnippet{
    Title:       title,
    Description: description,
  },
  Status: &amp;youtube.VideoStatus{
    PrivacyStatus: c.PrivacyStatus,
  },
}

call := service.Videos.Insert([]<span class="hljs-keyword">string</span>{<span class="hljs-string">"snippet"</span>, <span class="hljs-string">"status"</span>}, upload)

file, err := os.Open(c.OutputFile)
check(err)
<span class="hljs-keyword">defer</span> file.Close()

r, err := call.Media(file).Do()
check(err)
</code></pre>
<h2 id="heading-concurrency-challenge">Concurrency challenge</h2>
<p>The main challenge for me was to be able to just concurrently run tasks I want to run, of which i have 2:</p>
<ul>
<li>Downloading videos</li>
<li>Processing videos</li>
</ul>
<p>These are the most expensive tasks I have in terms of time because if they run one by one it will take so much time obviously.</p>
<p>So I wanted to have a way of running a tasks pool, where I pass the number of needed threads, and the pool will just split the tasks across these threads. So I ended up implementing this:</p>
<pre><code class="lang-golang"><span class="hljs-keyword">type</span> ThreadElement <span class="hljs-keyword">struct</span> {
    Element <span class="hljs-keyword">interface</span>{}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Threadify</span><span class="hljs-params">(numOfThreads <span class="hljs-keyword">int</span>, elements []ThreadElement, f <span class="hljs-keyword">func</span>(args ...<span class="hljs-keyword">interface</span>{})</span>)</span> {
    length := <span class="hljs-built_in">len</span>(elements)
    each := length / numOfThreads
    acc := length - (numOfThreads * each)

    <span class="hljs-keyword">var</span> wg sync.WaitGroup

    wg.Add(numOfThreads)

    start := <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; numOfThreads; i++ {
        running := each

        <span class="hljs-keyword">if</span> acc &gt; <span class="hljs-number">0</span> {
            running++
            acc--
        }

        <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(start, i <span class="hljs-keyword">int</span>)</span></span> {
            <span class="hljs-keyword">for</span> j := <span class="hljs-number">0</span>; j &lt; running; j++ {
                e := elements[start]
                f(e.Element)
                start++
            }

            wg.Done()
        }(start, i)

        start += running
    }

    wg.Wait()
}
</code></pre>
<p>Pretty basic right? and for sure there is a better way to do it, but hey it actually worked, and now I can download videos by calling it like this:</p>
<pre><code class="lang-golang"><span class="hljs-keyword">var</span> (
  videos   []Video
  elements []helpers.ThreadElement
)

<span class="hljs-keyword">for</span> _, p := <span class="hljs-keyword">range</span> posts {
  elements = <span class="hljs-built_in">append</span>(elements, ThreadElement{
    Element: p,
  })
}

Threadify(<span class="hljs-number">3</span>, elements, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(args ...<span class="hljs-keyword">interface</span>{})</span></span> {
  p := args[<span class="hljs-number">0</span>].(types.Post)
  log.Printf(<span class="hljs-string">"calling download %q"</span>, p.Hash)
  downloadedPath, out, err := download(p.Video, base, p.Hash)
  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    log.Fatalf(<span class="hljs-string">"error downloading video: %q of this post: %q::: %q\ncommand output: %s"</span>, p.Video, p.Hash, err.Error(), out)
  }

  log.Printf(<span class="hljs-string">"Downloaded video: %q with hash %q on %q\n"</span>, p.Title, p.Hash, downloadedPath)
  videos = <span class="hljs-built_in">append</span>(videos, Video{
    Post: p,
    Path: strings.ReplaceAll(downloadedPath, <span class="hljs-string">"%(ext)s"</span>, <span class="hljs-string">"mp4"</span>),
  })
})

fmt.Println(<span class="hljs-string">"Videos downloaded"</span>, videos)
</code></pre>
<p>I loved how generic this function is, and that is I just can use it for anything I want to concurrently run, and the way you can pass elements to it, by just kind of mapping it into <code>ThreadElement</code> so that it understands what it will later pass to the callback function.</p>
<p>But still, that looked hard to maintain, and I asked myself there has to be a better way to do this, just out of curiosity i googled and came across <a target="_blank" href="https://github.com/panjf2000/ants">ants</a> which seemed exactly right for this kind of functionality I wanted, so I converted the same function to use ants, and it became this:</p>
<pre><code class="lang-golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Threadify</span><span class="hljs-params">(numOfThreads <span class="hljs-keyword">int</span>, elements []ThreadElement, f <span class="hljs-keyword">func</span>(args ...<span class="hljs-keyword">interface</span>{})</span>) <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> wg sync.WaitGroup

    p, err := ants.NewPoolWithFunc(numOfThreads, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(e <span class="hljs-keyword">interface</span>{})</span></span> {
        f(e)
        wg.Done()
    })
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    <span class="hljs-keyword">defer</span> p.Release()

    <span class="hljs-keyword">for</span> _, e := <span class="hljs-keyword">range</span> elements {
        wg.Add(<span class="hljs-number">1</span>)
        err = p.Invoke(e.Element)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> err
        }
    }

    wg.Wait()
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>Now it even looks cleaner, and I just don't care anymore about how it works as long as it works, and the most beautiful thing is that the function signature is still the same, which means I could still use it in the same way I did and have it generic just as I wanted initially.</p>
<h2 id="heading-lessons-learned">Lessons learned</h2>
<p>Finally now after the project is complete, here are the top lessons I learned from:</p>
<ul>
<li>I shouldn't be surprised to know about "Youtube automation", literally everything is possible nowadays.</li>
<li>Golang is just super easy, Goroutines are easy when you just think of them logically, but also they're easy to produce bugs.</li>
<li>Don't use any <code>dotenv</code> package in Golang, IDK why I did!! it just makes no sense at least in this kind of project.</li>
</ul>
<h2 id="heading-finally">Finally</h2>
<p>I got the idea from this <a target="_blank" href="https://www.reddit.com/r/linux/comments/l5awi1/automating_an_entire_youtube_channel_with/">Reddit post</a> huge kudos to the OP, he made it simply in bash, but I just had to be a nerd to do it in Golang :/</p>
<p>Here is the repository link: https://mrg.sh/github/automato</p>
<p>There is huge room for improvement indeed, but I'm happy with the results, mission is achieved now to the new side project.</p>
]]></content:encoded></item><item><title><![CDATA[MongoDB aggregations are easy]]></title><description><![CDATA[Building aggregation pipelines sometimes just seems so hard, especially when you're working with NoSQL database such as MongoDB, since there is no defined schema, and there are just so many nested fields
Well in this article i'm not going to go deep ...]]></description><link>https://blog.mrg.sh/mongodb-aggregations-are-easy</link><guid isPermaLink="true">https://blog.mrg.sh/mongodb-aggregations-are-easy</guid><category><![CDATA[MongoDB]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Databases]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Sun, 01 Aug 2021 05:54:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627797148355/nC7q1RHeZ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building aggregation pipelines sometimes just seems so hard, especially when you're working with NoSQL database such as MongoDB, since there is no defined schema, and there are just so many nested fields</p>
<p>Well in this article i'm not going to go deep into what aggregation query is, since i think  <a target="_blank" href="https://docs.mongodb.com/manual/core/aggregation-pipeline/">MongoDB Docs</a>  explained this perfectly, but instead i will go through a kind of seeming-complex aggregation query, which is you probably going to face if you're currently with MongoDB and i will use this example here to just show you how aggregation pipelines are just logically easy, and it's always depends on how you think while building it</p>
<h2 id="the-example">The example</h2>
<p>Well for the sake of our example, let's consider that you have this silly <code>products</code> collection which has these 2 documents:</p>
<pre><code>{
  <span class="hljs-string">"_id"</span>: {
    <span class="hljs-string">"<span class="hljs-variable">$oid</span>"</span>: <span class="hljs-string">"55c30ff62cfa09af198b465a"</span>
  },
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"Awesome Tshirt"</span>,
  <span class="hljs-string">"currency"</span>: <span class="hljs-string">"usd"</span>,
  <span class="hljs-string">"one_size"</span>: <span class="hljs-string">"xl"</span>,
  <span class="hljs-string">"variants"</span>: [
    {
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"color"</span>,
      <span class="hljs-string">"base_sku"</span>: <span class="hljs-number">132145</span>,
      <span class="hljs-string">"items"</span>: [
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"Grey Melange"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">80</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">1243252369</span>
        },
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"Bottle Green"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">90</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">1243252368</span>
        },
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"Deep Charcoal Grey"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">80</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">1243252376</span>
        },
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"White"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">80</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">1243252363</span>
        },
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"Black"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">80</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">1243252362</span>
        }
      ]
    }
  ]
},
{
  <span class="hljs-string">"_id"</span>: {
    <span class="hljs-string">"<span class="hljs-variable">$oid</span>"</span>: <span class="hljs-string">"55c30ff62cfa09af198b465c"</span>
  },
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"Hacker Tshirt"</span>,
  <span class="hljs-string">"currency"</span>: <span class="hljs-string">"usd"</span>,
  <span class="hljs-string">"one_size"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-string">"variants"</span>: [
    {
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"color"</span>,
      <span class="hljs-string">"base_sku"</span>: <span class="hljs-number">132155</span>,
      <span class="hljs-string">"items"</span>: [
        {
          <span class="hljs-string">"color"</span>: <span class="hljs-string">"Black"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">100</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">87987963</span>
        }
      ]
    },
    {
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"size"</span>,
      <span class="hljs-string">"base_sku"</span>: <span class="hljs-number">342434</span>,
      <span class="hljs-string">"items"</span>: [
        {
          <span class="hljs-string">"size"</span>: <span class="hljs-string">"sm"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">100</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">97896796</span>
        },
        {
          <span class="hljs-string">"size"</span>: <span class="hljs-string">"xl"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">100</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">43534534</span>
        },
        {
          <span class="hljs-string">"size"</span>: <span class="hljs-string">"xxl"</span>,
          <span class="hljs-string">"price"</span>: <span class="hljs-number">100</span>,
          <span class="hljs-string">"sku"</span>: <span class="hljs-number">76576532</span>
        }
      ]
    }
  ]
}
</code></pre><p>and now our goal is to get the price of all grey T-shirts, so since as you can see that each product has it's price vary based on the variant itself, so we have to get both from variants items <code>item</code> object. Yes in this collection its so silly that somehow you have to choose of either having black T-shirt or XXl tshirt, but not both :D</p>
<h2 id="stage-1">Stage #1</h2>
<p>The very first step when you create a pipeline is to always <code>match</code> your query, this to narrow down querying the whole collection to just a limited number of documents that match your criteria</p>
<blockquote>
<p>You always want to narrow down the number of documents you're searching within at the very beginning of the aggregation pipeline, this will lead to faster queries</p>
</blockquote>
<p>So let's do this, first we want to use only the documents that has <code>color</code> variant, and it has also grey color inside it's variant items. So this is how we're translating this:</p>
<pre><code>    {
        <span class="hljs-string">'<span class="hljs-subst">$match</span>'</span>: {
            <span class="hljs-string">'variants'</span>: {
                <span class="hljs-string">'<span class="hljs-subst">$elemMatch</span>'</span>: {
                    <span class="hljs-string">'type'</span>: <span class="hljs-string">'color'</span>, 
                    <span class="hljs-string">'items'</span>: {
                        <span class="hljs-string">'<span class="hljs-subst">$elemMatch</span>'</span>: {
                            <span class="hljs-string">'color'</span>: /grey/i
                        }
                    }
                }
            }
        }
    }
</code></pre><blockquote>
<p> <a target="_blank" href="https://docs.mongodb.com/manual/reference/operator/query/elemMatch/">$elemMatch</a> operator matches documents that contain an array field with at least one element that matches all the specified query criteria.</p>
</blockquote>
<p>We're using $elemMatch here to find a variant of <code>type</code> <strong>color</strong> first, and then we're using it again to find an element of color that contains <strong>grey</strong> and its case insensitive - notice the regex <code>/grey/i</code> - </p>
<p>So this was our first step, notice that only 1 item will be returned in this case that has ID <code>55c30ff62cfa09af198b465a</code>, since it is the only one with variants of type color that has grey color</p>
<p>Document <code>55c30ff62cfa09af198b465c</code> it has variants of type color, but it has only black color</p>
<h2 id="stage-2">Stage #2</h2>
<p>Now we still need to query a nested object that is inside variants <em>(array)</em> and also inside items <em>(array)</em>, so its more like this <code>variants -&gt; items -&gt; {color, price}</code></p>
<p>This seems complicated, and since we're dealing with array of objects here, what could make it easier? .. to just deal with this array as an object instead, so we can just leverage the dot notation syntax in Mongo</p>
<p>Well we can do that by just unwinding the array! simple as that, just flatten this array into objects by using <code>$unwind</code></p>
<blockquote>
<p> <a target="_blank" href="https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/">$unwind</a> Deconstructs an array field from the input documents to output a document for each element</p>
</blockquote>
<p>$unwind is so simple that the only required parameter to pass is <code>path</code> which is the path of the array you want to flatten</p>
<pre><code>  {
    <span class="hljs-string">'<span class="hljs-subst">$unwind</span>'</span>: {
      <span class="hljs-string">'path'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>'</span>
    }
  }
</code></pre><p>Notice the dollar sign before <code>variants</code>, we have to prefix the field name with it so Mongo can interpret it, it just tells Mongo to inject the actual value of <code>variants</code></p>
<p>Now by then we will have also 1 document still, since <code>variants</code> array has only 1 element</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627792430807/rnhcOYVkj.png" alt="carbon.png" /></p>
<p>Notice that <code>variants</code> is now object instead of array</p>
<h2 id="stage-3">Stage #3</h2>
<p>Now what? we still need to query <code>variants.items</code> elements which is the same case as variants before we unwind it. So i guess we will have to flatten <code>variants.items</code> too, so next stage will be</p>
<pre><code>  {
    <span class="hljs-string">'<span class="hljs-subst">$unwind</span>'</span>: {
      <span class="hljs-string">'path'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items'</span>
    }
  }
</code></pre><p>Notice now that we can easily access <code>items</code> with dot notation, since <code>variants</code> is an object and not an array anymore, now these are the the new documents returned after this stage</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627793694932/UovDaFYs_.png" alt="Untitled design (1).png" /></p>
<p><code>items</code> is an object now with 1 document per <code>items</code> element which is exactly what we need, but did you notice something strange?</p>
<h2 id="stage-4">Stage #4</h2>
<p>Now we have documents with <code>items.color</code> value that does not contains <code>grey</code> we have Black, White, and Bottle Green as well, why is that?</p>
<p>Well that would be because our first <code>$match</code> stage was  only getting the documents that have items with grey color, having this does not necessary means that it will magically just filter other colors from <code>items</code>, this our job to do now</p>
<p>So now we will need to get only the documents that has <code>variants.items.color</code> with greyish color, looks like another <code>$match</code> query, right?</p>
<pre><code>  {
    <span class="hljs-string">'$match'</span>: {
    <span class="hljs-string">'variants.type'</span>: <span class="hljs-string">'color'</span>, 
    <span class="hljs-string">'variants.items.color'</span>: <span class="hljs-regexp">/grey/i</span>
  }
</code></pre><p>It is so simple now, here we're just saying, out of the result of stage 3 we just want the documents which has variants of type <code>color</code> and any item that has <code>grey</code> with case insensitive, that will return us these documents:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627795781626/7hVYrERPH.png" alt="Untitled design (4).png" /></p>
<h2 id="stage-5">Stage #5</h2>
<p>Now we have the documents, but we don't really care about all of these properties right? We only care about the color and the price</p>
<p>So since we're having a couple levels of nesting here <code>variants -&gt; items</code> and we only care about <code>price</code> and <code>color</code> properties, then we need to focus/project these properties only and ignore any other stuff we don't need</p>
<blockquote>
<p> <a target="_blank" href="https://docs.mongodb.com/manual/reference/operator/aggregation/project/">$project</a> Passes along the documents with the requested fields to the next stage in the pipeline.</p>
</blockquote>
<p>$project is so simple also, you'll probably just need to know that:</p>
<ul>
<li>It takes field name as a key and 1 or 0 as value (1 to show, 0 to hide)</li>
<li><code>_id</code> is shown by default, unless you specify to hide it</li>
<li>You have to specify at least 1 output field</li>
</ul>
<p>So our stage implementation will be simple as</p>
<pre><code>  {
    <span class="hljs-string">'_id'</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">'color'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items.color'</span>,
    <span class="hljs-string">'price'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items.price'</span>
  }
</code></pre><p>Notice that we passed <code>'_id': 0</code> because we don't really care about the document ID - at least not in this example, normally you'll need it though - so we just hid it</p>
<p>So now this will be the final result</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627795846252/SRNzGxSFv.png" alt="Untitled design (5).png" /></p>
<h2 id="the-aggregation-query">The aggregation query</h2>
<pre><code>db.getCollection(<span class="hljs-string">'products'</span>).aggregate([
  {
    <span class="hljs-string">'<span class="hljs-subst">$match</span>'</span>: {
      <span class="hljs-string">'variants'</span>: {
        <span class="hljs-string">'<span class="hljs-subst">$elemMatch</span>'</span>: {
          <span class="hljs-string">'type'</span>: <span class="hljs-string">'color'</span>, 
          <span class="hljs-string">'items'</span>: {
            <span class="hljs-string">'<span class="hljs-subst">$elemMatch</span>'</span>: {
              <span class="hljs-string">'color'</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">'grey'</span>, <span class="hljs-string">'i'</span>)
            }
          }
        }
      }
    }
  },
  {
    <span class="hljs-string">'<span class="hljs-subst">$unwind</span>'</span>: {
      <span class="hljs-string">'path'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>'</span>
    }
  },
  {
    <span class="hljs-string">'<span class="hljs-subst">$unwind</span>'</span>: {
      <span class="hljs-string">'path'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items'</span>
    }
  },
  {
    <span class="hljs-string">'<span class="hljs-subst">$match</span>'</span>: {
      <span class="hljs-string">'variants.type'</span>: <span class="hljs-string">'color'</span>, 
      <span class="hljs-string">'variants.items.color'</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">'grey'</span>, <span class="hljs-string">'i'</span>)
    }
  },
  {
    $project:  {
      <span class="hljs-string">'_id'</span>: <span class="hljs-number">0</span>,
      <span class="hljs-string">'color'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items.color'</span>,
      <span class="hljs-string">'price'</span>: <span class="hljs-string">'<span class="hljs-subst">$variants</span>.items.price'</span>
    }
  }
])
</code></pre><h2 id="final-words">Final words</h2>
<p>As you can see it's pretty straight forward, and it's quite easy and seems quite logical too, building aggregation pipelines stages is just like actually talking to rubber duck while coding, instead this time you're just talking to Mongo shell </p>
]]></content:encoded></item><item><title><![CDATA[I don’t know!]]></title><description><![CDATA[Just finished my working day and wow today was just filled up that it made me wonder, how long I’m gonna last working like this, and most importantly, do I even want this to end? Or do I like it? I don’t know.

Meetings are indeed time-consuming but ...]]></description><link>https://blog.mrg.sh/i-dont-know</link><guid isPermaLink="true">https://blog.mrg.sh/i-dont-know</guid><category><![CDATA[Blogging]]></category><category><![CDATA[software development]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Wed, 28 Jul 2021 21:47:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627508764874/IGi7TUMUP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Just finished my working day and wow today was just filled up that it made me wonder, how long I’m gonna last working like this, and most importantly, do I even want this to end? Or do I like it? I don’t know.</p>
<blockquote>
<p>Meetings are indeed time-consuming but only if they were with the wrong people, or if they were just useless, thank god that is not my case.</p>
</blockquote>
<p>The day started with an ~1hr long meeting, discussing some challenges that are facing us related to our upcoming features, and oh these challenges are indeed complex and a brand new type for me probably I can write about them whenever I get the chance.</p>
<blockquote>
<p>Having challenges are quite good training and experience booster, but do you like having constant challenges? Do I like that?</p>
</blockquote>
<p>Then my day went on to exploring some emails, checking snd reviewing some PRs, till I was interrupted with some issue, that I kinda didn’t have the time to check it and it was quite important. I checked the card, did some quick investigation (~1hr) and hey, it was not replicating! Smooth eh? Typical bug, the support team will say it exists but you can’t replicate it, no surprise there.</p>
<p>I checked again, went deep this time by checking some corner cases, read the code again tried to be at least sure before pulling out my big gun, but it was in vain. So I had to do it... I called one of our support guys, the one reported it, we hoped in a meeting, that lasted for ~1hr and we come up with this besides exchanging some laughs</p>
<ul>
<li>Issue is not an issue, and it was something wrong with the client configuration.</li>
<li>There is another different type of issue happening (nice trade eh?)</li>
</ul>
<p>so that was it, now I had to eat something and get some fresh air, but oh wait... it’s hot outside, so just eat something while watching some YouTube videos.</p>
<p>I finished and went back to a task that I planned on doing, we have an issue and we need to check for a memory leak. So ideas were thrown at my first meeting to try flame graphs, and heap dumps, so here I go trying.</p>
<p>I tried to use <code>perf</code> command to monitor a Nodejs running server, got nothing, just weird ASCII characters on the output, tried parsing them, but it just didn’t look right, so now I just have to try heap… oh message from DevOps what they want? They’re asking about something related to the database, I have to give them a query now, and it’s a kinda complex one. Okay, let’s first start the Vagrant machine, hop into Robo3t to access my local database, and start building a 5 stages aggregation pipeline to return a field value from a nested array of objects.</p>
<p>Now where we’re we? Oh, heap dump, yeah let’s try to use <code>heapdump</code> package to generate a heap dump for my running Nodejs server, as usual pasting some code from the documentation, tweaking it a bit, and now .. a message from scrum master? I need to review these 2 PRs ASAP that belong to another team but they’re touching our team functionality so we can do the release. Ding it, I didn’t notice these PRs so I gotta check them.</p>
<p>Now, what is this variable for? Let me open the repository, look for it, I get it now.. wait.. no I  let’s understand the code, oh what’s that? Let me call the developer he knows better, I message him we hop into a meeting and he explains the issue perfectly and we find that this code has a problem and it wouldn’t properly work with a specific corner case. Okay great now I just jest changes on the PR, type a comment summarizing our meeting discussion, and back to where I was.</p>
<p>Now what? Where? Oh, heap dump I left the server running, so I just have to hit a specific endpoint and the heap snapshot will be written to the disk for me to analyze it, let’s do that. Endpoint called, no errors, but the server is restarting … hmmm is this expected? Does this package somehow affect the runtime? IDK but hey doesn’t matter now, let me just see the heap dump.</p>
<pre><code><span class="hljs-attribute">cat</span> ./heaptest.heapsnapshot
</code></pre><p>It’s empty! Hmm weird, let me do it again. After 4 times repeating the file is still empty now I’m pissed off it should work, right? Okay, let just google that, I found tutorials, articles, and StackOverflow questions but they’re all the same, maybe I’m missing something.</p>
<p>In the meanwhile, I’m replying to messages and answering questions, but what got me mentally blocked is another PR, but this time a comment I made a while ago and the developer is asking about it. The comment was about some clean code stuff and how it’s better not to pollute a function with custom conditions and instead think of another way of doing it. We discussed and he suggested a perfect approach, so now it was time to continue.</p>
<p>But I lost it already, I’m not as focused as before, so I just did a couple of other tries, and called it day at 6 pm already.</p>
<h2 id="now-what">Now what?</h2>
<p>This was indeed a busy day, but I’m kinda used to it by now, but Is this okay? I do not know.</p>
<p>This is definitely not bad to make you practice multitasking and make you available at all times, but it comes with a cost on your concentration. I’m not here to propose a solution though, I’m just here to share and for the hope that I will find the solution by just writing this kind of a daily routine.</p>
<p>Do I like being busy? Yeah, it makes my mind spin and thinks, and mostly shifting from thinking on a certain problem to another one, helps me find a solution for the first problem. I got a weird mind IKR?</p>
]]></content:encoded></item><item><title><![CDATA[Why you should start your wargames journey?]]></title><description><![CDATA[Lately i been exploring some hacking and offensive security content, so basically it involved me just turning off my room lights, me wearing a hacker hoodie, and watching 
 John Hammond videos  and nodding my head, gasping and a little bit actually t...]]></description><link>https://blog.mrg.sh/why-you-should-start-your-wargames-journey</link><guid isPermaLink="true">https://blog.mrg.sh/why-you-should-start-your-wargames-journey</guid><category><![CDATA[hacking]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Security]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Tue, 27 Jul 2021 17:02:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627405112141/bpvqJ1q2W.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lately i been exploring some hacking and offensive security content, so basically it involved me just turning off my room lights, me wearing a hacker hoodie, and watching 
 <a target="_blank" href="https://www.youtube.com/c/JohnHammond010">John Hammond videos</a>  and nodding my head, gasping and a little bit actually trying to mimic what he does on my machine</p>
<p>So wargames bandit was something that John Hammond actually recommended to get familiar with Linux for anyone that wanted to start hacking and become a hacker. I thought oh what is this wargames and started googling</p>
<p>I found it,  <a target="_blank" href="https://overthewire.org/wargames/bandit/">wargames bandit</a> and i read it and thought, well this is going to be easy, i know Linux i been working on it for about 6 years now, managing and deploying servers and use it everyday since then for my daily life, i even play games on it for god sake</p>
<h2 id="what-is-wargames-bandit">What is wargames bandit?</h2>
<p>Well this game is simple, its consists of levels, you start at level 0 and make your way through by finishing the level, basically finishing a level means that you get the password for the next level, and so on</p>
<p>Bandit is made for absolute wargames beginners and it aims to get players familiar with Linux and the wargames style</p>
<h2 id="why-wargames">Why wargames?</h2>
<p>Well first because it's fun, and i mean it, it is actually fun, and no matter how you're using Linux, mostly you'll learn a lot by playing it</p>
<p>You will know more about some commands that you might be using everyday like <code>grep</code>, <code>ssh</code>, <code>cat</code>, or <code>find</code>. And even new commands (at least for me) like <code>strings</code>, <code>uniq</code>, <code>bzip2</code>, <code>s_client</code>, and <code>xxd</code></p>
<p>You will write a simple bash scripts, which is super fun especially if you're like me, someone that loves to automate some tedious work by writing scripts</p>
<p>You will understand some Unix concepts and even interact with text editors like <code>vi</code> for example, and even know how to deal with different shells</p>
<p>Also you will interact with git and running some git commands so you can look for some secret credentials in git branches, commits, or tags</p>
<p>And of course you will know some hacking concepts, and how to protect your server by avoiding some silly mistakes, that we all may be doing</p>
<p>As a developer you'll find this also important, especially if you're interacting with servers on any environment, you'll learn essential security concepts and it will open your eyes to a whole new world</p>
<h2 id="the-gist">The gist</h2>
<p>You should  <a target="_blank" href="https://overthewire.org/wargames/bandit/">give a try</a> , i started it on a weekend and i found myself finishing the 33 levels in 1 day that's how entertaining this is, and i will probably check other  <a target="_blank" href="https://overthewire.org/wargames/">wargame</a>  as well maybe natas? who knows!</p>
]]></content:encoded></item><item><title><![CDATA[Build a websocket server using express & ws package]]></title><description><![CDATA[This is yet another kind of NodeJs websocket tutorial that i was hoping to talk about my experience in building a websocket server and implementing a pub/sub pattern to send and receive events the same way socket.io is providing but we here we're usi...]]></description><link>https://blog.mrg.sh/build-a-websocket-server-using-express-and-ws-package</link><guid isPermaLink="true">https://blog.mrg.sh/build-a-websocket-server-using-express-and-ws-package</guid><category><![CDATA[Node.js]]></category><category><![CDATA[SocketIO]]></category><category><![CDATA[Express]]></category><category><![CDATA[websockets]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Mon, 26 Jul 2021 23:42:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627342920074/4wUHizwEW.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is yet another kind of NodeJs websocket tutorial that i was hoping to talk about my experience in building a websocket server and implementing a pub/sub pattern to send and receive events the same way <code>socket.io</code> is providing but we here we're using the simple to use and blazing fast <code>ws</code> package.</p>
<h2 id="why-not-socketio">Why not socket.io?</h2>
<p>Well first it has all you might need, and i mean all like all of it, which is kinda a lot since you end up using a lot of stuff you don't actually need, while on the other hand <code>ws</code> provides you with the base functionality and the ability to customize it as per needs</p>
<p> Besides i remember times when <code>socket.io</code> repository was abandoned, and they had a lot of issues, and they it was kinda deprecated in favor of <code>engine.io</code> and made a great role also in choosing <code>ws</code> over it.</p>
<h2 id="http-server-setup">HTTP server setup</h2>
<p>Now let's setup our express HTTP server a typical implementation with nothing fancy</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> app = express()
<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">3000</span> || process.env.PORT

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">_, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Welcome to main page'</span>)
})

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is working on http://localhost:<span class="hljs-subst">${PORT}</span>`</span>)
})
</code></pre>
<p>Nothing special here, we defined our index endpoint which is going to return <code>Welcome to main page</code> if we went to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a></p>
<p>So to setup our websocket server we need to import <code>ws</code> package and then tweak this a little bit</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)
<span class="hljs-keyword">const</span> WsServer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'ws'</span>)
<span class="hljs-keyword">const</span> { createServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>)

<span class="hljs-keyword">const</span> app = express()
<span class="hljs-keyword">const</span> server = createServer(app)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initWs</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> options = {
    <span class="hljs-attr">noServer</span>: <span class="hljs-literal">true</span>
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> WsServer.Server(options)
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initHttpServer</span>(<span class="hljs-params">port</span>) </span>{
  app.set(<span class="hljs-string">'view engine'</span>, <span class="hljs-string">'ejs'</span>)

  app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">_, res</span>) =&gt;</span> {
    res.render(<span class="hljs-string">'index'</span>)
  })

  server.listen(port, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is working on http://localhost:<span class="hljs-subst">${port}</span>`</span>)
  })

  <span class="hljs-keyword">return</span> app
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initWebSocketServer</span>(<span class="hljs-params">port = <span class="hljs-number">3000</span></span>) </span>{
  initHttpServer(port)
  <span class="hljs-keyword">const</span> wss = initWs()

  server.on(<span class="hljs-string">'upgrade'</span>, <span class="hljs-keyword">async</span>(req, socket, head) =&gt; {
    <span class="hljs-keyword">try</span> {
      wss.handleUpgrade(req, socket, head, <span class="hljs-function">(<span class="hljs-params">ws</span>) =&gt;</span> {
        <span class="hljs-comment">// Do something before firing the connected event</span>

        wss.emit(<span class="hljs-string">'connection'</span>, ws, req)
      })
    } <span class="hljs-keyword">catch</span>(err) {
      <span class="hljs-comment">// Socket uprade failed</span>
      <span class="hljs-comment">// Close socket and clean</span>
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Socket upgrade failed'</span>, err)
      socket.destroy()
    }
  })

  <span class="hljs-keyword">return</span> wss
}

<span class="hljs-keyword">const</span> wss = initWebSocketServer()

wss.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">ws</span>) =&gt;</span> {
  ws.on(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    ws.send(data)
  })
})
</code></pre>
<p>Now we splitted the code into 3 functions, 1 to handle preparing our websocket headless server by just passing <code>noServer: true</code> to ws server constructor, beside a function to setup our http server and notice that we added new route </p>
<pre><code class="lang-jsx">  app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">_, res</span>) =&gt;</span> {
    res.render(<span class="hljs-string">'index'</span>)
  })
</code></pre>
<p>This to handle our client that is going to be connected to our server and thats where our client code will live on <code>index</code> view, where it exists under <code>views</code> directory here</p>
<pre><code class="lang-jsx">websocket-server/
├─ views/
│  ├─ index.ejs
├─ server.js
</code></pre>
<h2 id="client-side-setup">Client side setup</h2>
<p>Now on the client side the code is pretty simple</p>
<pre><code class="lang-jsx">&lt;!DOCTYPE html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>WS Test<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">let</span> keepAliveId = <span class="hljs-literal">null</span>
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connect</span>(<span class="hljs-params">{ url, keepAlive, keepAlivePeriod }</span>) </span>{
        <span class="hljs-keyword">const</span> ws = <span class="hljs-keyword">new</span> WebSocket(url)

        <span class="hljs-keyword">if</span> (keepAlive) {
          <span class="hljs-built_in">clearInterval</span>(keepAliveId)
          keepAliveId = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
            ws.send(<span class="hljs-string">'ping'</span>)
          }, keepAlivePeriod || <span class="hljs-number">5000</span>)
        }

        ws.onopen = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Websocket is open'</span>)
        }

        ws.onclose = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Websocket closed'</span>)
        }

        ws.onerror = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Websocket error'</span>, err)
        }

        ws.onmessage = <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Websocket data'</span>, message.data)
        }
      }

      connect({
        <span class="hljs-attr">url</span>: <span class="hljs-string">'ws://localhost:3000'</span>,
        <span class="hljs-attr">keepAlive</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">keepAlivePeriod</span>: <span class="hljs-number">5000</span>
      })
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>We have our connect function here that expects the following options <code>{ url, keepAlive, keepAlivePeriod }</code></p>
<p><code>keepAlive</code> property is to keep the websocket connection alive by sending message after a period of milliseconds which what in the <code>keepAlivePeriod</code> for</p>
<p>The way we're keeping the websocket alive is in this snippet</p>
<pre><code class="lang-jsx">        <span class="hljs-keyword">if</span> (keepAlive) {
          <span class="hljs-built_in">clearInterval</span>(keepAliveId)
          keepAliveId = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
            ws.send(<span class="hljs-string">'ping'</span>)
          }, keepAlivePeriod || <span class="hljs-number">5000</span>)
        }
</code></pre>
<p>So every period of time we're sending this ping message to server and thus server is going to echo back what we're sending and this is how simply the socket connection is alive </p>
<p>That’s basically how we created a simple echo server using just <code>ws</code> package with even the ability to keep the socket connection alive. Later on I will explain how to trigger events from client side and even subscribe to these events so whenever server is emitting an event it will be received by the client</p>
]]></content:encoded></item><item><title><![CDATA[Moving from Vagrant to Docker & Docker Compose: Docker compose]]></title><description><![CDATA[On  the last part , i talked about my try using Kubernetes to orchestrate our local development environment and how it was no good at least at this stage since there was a lot of problems related to mounted volumes, and how pods are communicating so ...]]></description><link>https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-docker-compose</link><guid isPermaLink="true">https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-docker-compose</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Sun, 25 Jul 2021 17:37:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627224611730/_JbTmd67L.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On  <a target="_blank" href="/moving-from-vagrant-to-docker-and-docker-compose-kubernetes">the last part</a> , i talked about my try using Kubernetes to orchestrate our local development environment and how it was no good at least at this stage since there was a lot of problems related to mounted volumes, and how pods are communicating so it made more sense to switch to a simpler orchestrator which is Docker Compose.</p>
<h2 id="the-compose-file">The compose file</h2>
<pre><code><span class="hljs-attribute">version</span>: <span class="hljs-string">"3.2"</span>
<span class="hljs-attribute">services</span>:
  <span class="hljs-attribute">mongo</span>:
    <span class="hljs-attribute">container_name</span>: mg_mongo
    <span class="hljs-attribute">build</span>:
      <span class="hljs-attribute">context</span>: ./config/mongo/
    <span class="hljs-attribute">restart</span>: always
    <span class="hljs-attribute">networks</span>:
      <span class="hljs-attribute">default</span>:
        <span class="hljs-attribute">aliases</span>:
          - mrgeek_mongo
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"27017:27017"</span>
    <span class="hljs-attribute">volumes</span>:
      # Mongo config
      - ./config/mongo/mongod.<span class="hljs-attribute">conf</span>:/etc/mongod.conf
      # Mongo data to be persistent between reboots
      - <span class="hljs-attribute">mongo-data</span>:/data/db


  <span class="hljs-attribute">rabbitmq</span>:
    <span class="hljs-attribute">container_name</span>: mg_rabbitmq
    <span class="hljs-attribute">build</span>:
      <span class="hljs-attribute">context</span>: ./config/rabbitmq/
    <span class="hljs-attribute">restart</span>: always
    <span class="hljs-attribute">networks</span>:
      <span class="hljs-attribute">default</span>:
        <span class="hljs-attribute">aliases</span>:
          - mrgeek_rabbitmq
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"5672:5672"</span>
      - <span class="hljs-string">"15672:15672"</span>
    <span class="hljs-attribute">volumes</span>:
      # RabbitMQ configuration
      - ./config/rabbitmq/:/etc/rabbitmq/

  <span class="hljs-attribute">elasticsearch</span>:
    <span class="hljs-attribute">container_name</span>: mg_elasticsearch
    <span class="hljs-attribute">build</span>:
      <span class="hljs-attribute">context</span>: ./config/elasticsearch/
    <span class="hljs-attribute">restart</span>: unless-stopped
    <span class="hljs-attribute">networks</span>:
      <span class="hljs-attribute">default</span>:
        <span class="hljs-attribute">aliases</span>:
          - mrgeek_elasticsearch
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"9200:9200"</span>
    <span class="hljs-attribute">volumes</span>:
      # Elasticsearch data
      - <span class="hljs-attribute">elastic-data</span>:/var/lib/elasticsearch
      # Elasticsearch configuration files
      - ./config/elasticsearch/elasticsearch.<span class="hljs-attribute">yml</span>:/usr/share/elasticsearch/config/elasticsearch.yml
      - ./config/elasticsearch/jvm.<span class="hljs-attribute">options</span>:/usr/share/elasticsearch/config/jvm.options

  <span class="hljs-attribute">kibana</span>:
    <span class="hljs-attribute">container_name</span>: mg_kibana
    <span class="hljs-attribute">build</span>:
      <span class="hljs-attribute">context</span>: ./config/kibana/
    <span class="hljs-attribute">restart</span>: unless-stopped
    <span class="hljs-attribute">depends_on</span>:
      - elasticsearch
    <span class="hljs-attribute">environment</span>:
      <span class="hljs-attribute">ELASTICSEARCH_URL</span>: <span class="hljs-attribute">http</span>:<span class="hljs-comment">//mrgeek_elasticsearch:9200</span>
      <span class="hljs-attribute">ELASTICSEARCH_HOSTS</span>: <span class="hljs-attribute">http</span>:<span class="hljs-comment">//mrgeek_elasticsearch:9200</span>
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"5601:5601"</span>
    <span class="hljs-attribute">volumes</span>:
      # Kibana configuration files
      - ./config/kibana/kibana.<span class="hljs-attribute">yml</span>:/usr/share/kibana/config/kibana.yml
      - ./config/kibana/node.<span class="hljs-attribute">options</span>:/usr/share/kibana/config/node.options
      - ./config/nginx/ssl/:/usr/share/kibana/config/ssl/

  <span class="hljs-attribute">main</span>:
    <span class="hljs-attribute">container_name</span>: mg_main
    <span class="hljs-attribute">build</span>:
      # define parent directory as context so we can get things from other
      # repositories
      <span class="hljs-attribute">context</span>: ./../
      <span class="hljs-attribute">dockerfile</span>: ./mg_docker/Dockerfile
    <span class="hljs-attribute">depends_on</span>:
      - mongo
      - rabbitmq
      - elasticsearch
    <span class="hljs-attribute">ports</span>:
      - <span class="hljs-string">"443:443"</span>
      - <span class="hljs-string">"80:80"</span> 
      - <span class="hljs-string">"5222:5222"</span> 
      - <span class="hljs-string">"22:22"</span>
    <span class="hljs-attribute">extra_hosts</span>:
      # extra hosts
      - <span class="hljs-string">"mrgeek_api:127.0.0.1"</span>
      - <span class="hljs-string">"mrgeek_xmpp:127.0.0.1"</span>
      - <span class="hljs-string">"mrgeek_file_upload:127.0.0.1"</span>
      - <span class="hljs-string">"mrgeek_reporting:127.0.0.1"</span>
      - <span class="hljs-string">"host.docker.internal:host-gateway"</span>
    <span class="hljs-attribute">volumes</span>:
      # Bashrc
      - ./config/.<span class="hljs-attribute">bashrc</span>:/root/.bashrc

      # dnsmasq.conf
      - ./config/dnsmasq/dnsmasq.<span class="hljs-attribute">conf</span>:/etc/dnsmasq.conf

      # Nginx configuration from our nginx-edge repo
      - ./config/nginx/ssl/:/etc/nginx/ssl/
      - ./../mg-nginx/src/conf.d/:/etc/nginx/conf.d/

      # Kibana configuration
      - ./config/kibana/:/etc/kibana/

      # Ejabberd
      - ./config/ejabberd/conf/:/opt/ejabberd/conf/
      - <span class="hljs-attribute">ejabberd-data</span>:/opt/ejabberd/database/
      - ./config/ejabberd/ssl/:/opt/ejabberd/ssl/

      # PHP fpm conf
      - ./config/api/php-fpm.<span class="hljs-attribute">conf</span>:/etc/php/fpm/php-fpm.conf
      - ./config/api/www.<span class="hljs-attribute">conf</span>:/etc/php/fpm/pool.d/www.conf
      - ./config/api/opcache.<span class="hljs-attribute">ini</span>:/etc/php/fpm/conf.d/<span class="hljs-number">05</span>-opcache.ini
      - ./config/api/php.<span class="hljs-attribute">ini</span>:/etc/php/fpm/php.ini
      - ./config/api/php.<span class="hljs-attribute">ini</span>:/etc/php/cli/php.ini

      #
      # Main services
      #

      # api
      - ./../mg_api/:/var/www
      - ./../mg_api/composer.<span class="hljs-attribute">lock</span>:/var/www/composer.lock

      # mg_xmpp_node
      - ./../mg_xmpp/:/var/app/mg_xmpp/

      # mg_reporting
      - ./../mg_reporting/:/var/app/mg_reporting/

      # mg_file_upload
      - ./../mg_file_upload/:/var/app/mg_file_upload/

<span class="hljs-attribute">volumes</span>:
  <span class="hljs-attribute">mongo-data</span>:
  <span class="hljs-attribute">elastic-data</span>:
  <span class="hljs-attribute">ejabberd-data</span>:
</code></pre><p>The first 4 services  have common attributes as you can see all of their config we get from a <code>./config</code> directory were it has sub-directories for each service it has its own configuration files plus its own <code>Dockerfile</code> as you can see here:</p>
<pre><code>mg_docker/
├─ config/
│  ├─ api/
│  │  ├─ php-fpm.conf
│  │  ├─ www.conf
│  │  ├─ opcache.ini
│  │  ├─ php.ini
│  ├─ dnsmasq/
│  │  ├─ dnsmasq.conf
│  ├─ elasticsearch/
│  │  ├─ elasticsearch.yml
│  │  ├─ jvm.<span class="hljs-keyword">options</span>
│  │  ├─ Dockerfile
│  ├─ kibana/
│  │  ├─ Dockerfile
│  │  ├─ kibana.yml
│  │  ├─ node.<span class="hljs-keyword">options</span>
│  ├─ ejabberd/
│  │  ├─ conf/
│  │  │  ├─ ejabberd.yml
│  │  │  ├─ ejabberdctl.cfg
│  ├─ rabbitmq/
│  │  ├─ Dockerfile
│  │  ├─ enabled_plugins
│  ├─ mongo/
│  │  ├─ Dockerfile
│  │  ├─ mongod.conf
├─ Dockerfile
├─ docker-compose.yml
├─ docker-entrypoint.sh
mg_api/
mg_xmpp/
mg_reporting/
mg_file_upload/
</code></pre><p>Important thing to note here is the networks aliases for each service because that's how the services are going to communicate between each other, a good example for this is <code>elasticsearch</code> container aliases:</p>
<pre><code>    <span class="hljs-attribute">networks</span>:
      <span class="hljs-attribute">default</span>:
        <span class="hljs-attribute">aliases</span>:
          - mrgeek_elasticsearch
</code></pre><p>and we're using this alias in <code>kibana</code> container passing it to it's configuration as environment variables</p>
<pre><code>    <span class="hljs-attribute">environment</span>:
      <span class="hljs-attribute">ELASTICSEARCH_URL</span>: <span class="hljs-attribute">http</span>:<span class="hljs-comment">//mrgeek_elasticsearch:9200</span>
      <span class="hljs-attribute">ELASTICSEARCH_HOSTS</span>: <span class="hljs-attribute">http</span>:<span class="hljs-comment">//mrgeek_elasticsearch:9200</span>
</code></pre><p>Now for the main service it has more configuration as you can see, but good thing to notice is that the context is set with <code>context: ./../</code> and since the context is changed we're referring to the docker file with this <code>dockerfile: ./mg_docker/Dockerfile</code></p>
<blockquote>
<p>Context is changed so we can refer to other services which exist on the parent directory of this one <code>../</code></p>
</blockquote>
<p>And we're also setting the extra hosts so that services can communicate to each other inside the same container</p>
<pre><code>    <span class="hljs-selector-tag">extra_hosts</span>:
      # <span class="hljs-selector-tag">extra</span> <span class="hljs-selector-tag">hosts</span>
      <span class="hljs-selector-tag">-</span> "<span class="hljs-selector-tag">mrgeek_api</span><span class="hljs-selector-pseudo">:127.0.0.1"</span>
      <span class="hljs-selector-tag">-</span> "<span class="hljs-selector-tag">mrgeek_xmpp</span><span class="hljs-selector-pseudo">:127.0.0.1"</span>
      <span class="hljs-selector-tag">-</span> "<span class="hljs-selector-tag">mrgeek_file_upload</span><span class="hljs-selector-pseudo">:127.0.0.1"</span>
      <span class="hljs-selector-tag">-</span> "<span class="hljs-selector-tag">mrgeek_reporting</span><span class="hljs-selector-pseudo">:127.0.0.1"</span>
      <span class="hljs-selector-tag">-</span> "<span class="hljs-selector-tag">host</span><span class="hljs-selector-class">.docker</span><span class="hljs-selector-class">.internal</span><span class="hljs-selector-pseudo">:host-gateway"</span>
</code></pre><p>Also we're setting the volumes as well where you can see we're mounting other services</p>
<blockquote>
<p><code>dnsmasq</code> is used to act as a DHCP server inside the container so we can reference these extra hosts inside the container</p>
</blockquote>
<h2 id="the-docker-files">The docker files</h2>
<p>Docker files for our services are really simple and they actually might be redundant and we can even use docker compose directly for this, but for better and clear control we went with docker files</p>
<p>The one hell of a <code>Dockerfile</code> that we rely on is the main docker file for our main service which has:</p>
<ul>
<li>Nginx</li>
<li>PHP &amp; PHP-FPM</li>
<li>Ejabberd</li>
<li>NodeJs</li>
</ul>
<p>It is a big one i know, and later on this one will be splitted into multiple services of course, so here is it:</p>
<pre><code>FROM debian:<span class="hljs-number">10</span>

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install basic software</span>
<span class="hljs-comment">#</span>
RUN set -x \
    &amp;&amp; apt-get update \
    &amp;&amp; apt-get install -y --<span class="hljs-literal">no</span>-install-recommends \
        systemd \
        sudo \
        build-essential \
        nano \
        openssh-server \
        apt-transport-https \
        ca-certificates \
        gnupg \
        curl \
        software-properties-common \
        locales \
        python-pip \
        python-setuptools \
        python \
        unzip

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install nginx</span>
<span class="hljs-comment">#</span>
RUN curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -

RUN add-apt-repository <span class="hljs-string">'deb https://nginx.org/packages/mainline/debian/ buster nginx'</span>

RUN set -x \
    &amp;&amp; apt-get update \
    &amp;&amp; apt-get install -y nginx \
    &amp;&amp; rm -rf <span class="hljs-regexp">/var/lib/apt/lists/</span>*

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install ejabberd</span>
<span class="hljs-comment">#</span>
ENV EJABBERD_HOME=/opt/ejabberd
ENV EJABBERD_CTL=/opt/ejabberd/bin/ejabberdctl
ENV NGINX_INTERNAL_SERVICE_HOST=localhost

COPY ./mg_ejabberd/ejabberd.deb /root/ejabberd.deb
RUN dpkg -i /root/ejabberd.deb

RUN mkdir -p /var/log/ejabberd
RUN touch /var/log/ejabberd/extauth.log
RUN chown -R ejabberd:ejabberd /var/log/ejabberd
RUN chown -R ejabberd:ejabberd ${EJABBERD_HOME}/

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install dnsmasq for nginx resolver</span>
<span class="hljs-comment">#</span>
RUN apt-get update
RUN apt-get install -y dnsmasq dnsutils net-tools


<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install php, php-fpm and composer</span>
<span class="hljs-comment">#</span>
RUN apt-get update
RUN apt-get install -y \
      php-cli \
      php-fpm \
      php-xml \
      php-bcmath \
      php-mbstring \
      php-gd \
      php-curl \
      php-mongodb \
      php-pear \
      php-dev \
      php-xdebug \
      php-apcu

<span class="hljs-comment"># Setup xdebug</span>
COPY ./mg_docker/config/api/xdebug.ini /etc/php/mods-available/xdebug.ini

<span class="hljs-comment"># Install mongo extenstion</span>
RUN pecl install mongodb

RUN curl -sS https://getcomposer.org/installer -o /root/composer-setup.php
RUN php /root/composer-setup.php --install-dir=/usr/local/bin --filename=composer

RUN echo <span class="hljs-string">"extension=mongodb.so"</span> &gt; /etc/php/cli/conf.d/<span class="hljs-number">20</span>-mongodb.ini
RUN echo <span class="hljs-string">"extension=mongodb.so"</span> &gt;/etc/php/fpm/conf.d/<span class="hljs-number">20</span>-mongodb.ini

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Some cleanup to reduce space</span>
<span class="hljs-comment">#</span>
RUN apt-get clean &amp;&amp; apt-get autoremove
RUN rm -rf /root/ejabberd.deb
RUN rm -rf /root/composer-setup.php

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Nginx main configuration</span>
<span class="hljs-comment">#</span>
COPY ./mg-nginx/src/nginx.conf /etc/nginx/nginx.conf

ARG USER_ID=<span class="hljs-number">1000</span>
ARG GROUP_ID=<span class="hljs-number">1000</span>

<span class="hljs-comment"># Remove www-data user</span>
RUN userdel -f www-data &amp;&amp;\
    <span class="hljs-keyword">if</span> getent group www-data ; <span class="hljs-keyword">then</span> groupdel www-data; fi

<span class="hljs-comment"># Generate a new www-data user with IDs 1000</span>
<span class="hljs-comment"># Will be used by API to create cache &amp; logs</span>
RUN groupadd -g ${GROUP_ID} www-data
RUN useradd -l -u ${USER_ID} -g www-data www-data
RUN usermod -G sudo www-data
RUN install -d -m <span class="hljs-number">0755</span> -o www-data -g www-data /home/www-data /var/run/php /var/run/nginx
RUN chown --changes --<span class="hljs-literal">no</span>-dereference --recursive \
        ${USER_ID}:${GROUP_ID} \
        /home/www-data \
        /var/lib/php/sessions \
        /etc/nginx /var/run/nginx /var/run/php \
        /var/cache/nginx \
        /var/log/nginx \
        /var/run/php \
        /var/run/nginx \
        <span class="hljs-regexp">/etc/php/</span>
RUN sed  -i <span class="hljs-string">'s/^\%sudo.*/%sudo   ALL=(ALL) NOPASSWD:ALL/g'</span> /etc/sudoers

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Entrypoint</span>
<span class="hljs-comment">#</span>
COPY ./mg_docker/docker-entrypoint.sh /tmp/docker-entrypoint.sh
RUN chmod +x /tmp/docker-entrypoint.sh
USER www-data

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install nvm and node v14.15.4 and npm v6.14.10</span>
<span class="hljs-comment">#</span>
ENV NODE_VERSION=<span class="hljs-number">14.15</span><span class="hljs-number">.4</span>
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0<span class="hljs-number">.37</span><span class="hljs-number">.2</span>/install.sh | bash
ENV NVM_DIR=<span class="hljs-regexp">/home/www-data/</span>.nvm
RUN . <span class="hljs-string">"$NVM_DIR/nvm.sh"</span> &amp;&amp; nvm install ${NODE_VERSION}
RUN . <span class="hljs-string">"$NVM_DIR/nvm.sh"</span> &amp;&amp; nvm use v${NODE_VERSION}
RUN . <span class="hljs-string">"$NVM_DIR/nvm.sh"</span> &amp;&amp; nvm alias <span class="hljs-keyword">default</span> v${NODE_VERSION}
ENV PATH=<span class="hljs-string">"/home/www-data/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"</span>
RUN node --version
RUN <span class="hljs-built_in">npm</span> --version


<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Install pm2</span>
<span class="hljs-comment">#</span>
RUN <span class="hljs-built_in">npm</span> install -g pm2

ENTRYPOINT [<span class="hljs-string">"./tmp/docker-entrypoint.sh"</span>]

<span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">######</span><span class="hljs-comment">#</span>
<span class="hljs-comment"># Expose needed ports</span>
<span class="hljs-comment">#</span>
EXPOSE <span class="hljs-number">433</span> <span class="hljs-number">80</span> <span class="hljs-number">5222</span> <span class="hljs-number">22</span>

STOPSIGNAL SIGQUIT

CMD [<span class="hljs-string">"nginx"</span>]
</code></pre><p>It's pretty straight forward docker file the only thing to note is this part</p>
<pre><code>ARG USER_ID=<span class="hljs-number">1000</span>
ARG GROUP_ID=<span class="hljs-number">1000</span>

# Remove www-<span class="hljs-keyword">data</span> user
RUN userdel -f www-<span class="hljs-keyword">data</span> &amp;&amp;\
    <span class="hljs-keyword">if</span> getent group www-<span class="hljs-keyword">data</span> ; then groupdel www-<span class="hljs-keyword">data</span>; fi

# Generate a new www-<span class="hljs-keyword">data</span> user with IDs <span class="hljs-number">1000</span>
# Will be used <span class="hljs-keyword">by</span> API to create cache &amp; logs
RUN groupadd -g ${GROUP_ID} www-<span class="hljs-keyword">data</span>
RUN useradd -l -u ${USER_ID} -g www-<span class="hljs-keyword">data</span> www-<span class="hljs-keyword">data</span>
RUN usermod -G sudo www-<span class="hljs-keyword">data</span>
RUN install -d -m <span class="hljs-number">0755</span> -o www-<span class="hljs-keyword">data</span> -g www-<span class="hljs-keyword">data</span> /home/www-<span class="hljs-keyword">data</span> /<span class="hljs-keyword">var</span>/run/php /<span class="hljs-keyword">var</span>/run/nginx
RUN chown --changes --no-dereference --recursive \
        ${USER_ID}:${GROUP_ID} \
        /home/www-<span class="hljs-keyword">data</span> \
        /<span class="hljs-keyword">var</span>/lib/php/sessions \
        /etc/nginx /<span class="hljs-keyword">var</span>/run/nginx /<span class="hljs-keyword">var</span>/run/php \
        /<span class="hljs-keyword">var</span>/cache/nginx \
        /<span class="hljs-keyword">var</span>/log/nginx \
        /<span class="hljs-keyword">var</span>/run/php \
        /<span class="hljs-keyword">var</span>/run/nginx \
        /etc/php/
RUN sed  -i <span class="hljs-string">'s/^\%sudo.*/%sudo   ALL=(ALL) NOPASSWD:ALL/g'</span> /etc/sudoers
</code></pre><p>Here we're creating new <code>www-data</code> user with the same UID and GID as the host user which are both <code>1000</code> then we're changing the owner of most of services' directories to the new user, so whenever for example PHP is generating logs, which are mounted on the machine, host user can still control them without using <code>sudo</code></p>
<p>This is also used to run our docker entrypoint as you can see </p>
<pre><code><span class="hljs-keyword">COPY</span> ./mg_docker/docker-entrypoint.sh /tmp/docker-entrypoint.sh
RUN chmod +x /tmp/docker-entrypoint.sh
ENTRYPOINT ["./tmp/docker-entrypoint.sh"]
`
</code></pre><p>In which we're running all the services of this container.</p>
<p>This way it was easier to mount files, and even the mounted volumes will have the same exact user as on the host machine. So we will never have to use <code>sudo</code> to remove a directory for example or change a file.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>It was quite a journey, which is still ongoing BTW but to reach this point where at least the software is up &amp; running and is development friendly is quite amazing</p>
<p>The directory structure we have plays an important role to have this working, where each service is a repository on its own, and exists on the parent directory of this docker project, that's why it was easier to reuse them in docker containers</p>
<p>DevOps indeed helped and fixed some hard issues, and i learned a lot by just touching this bare topic, i was exposed to so many technologies so deep more than i would ever dreamed.</p>
]]></content:encoded></item><item><title><![CDATA[Moving from Vagrant to Docker & Docker Compose: Kubernetes?]]></title><description><![CDATA[So on  first part  we talked about what the problem was, and at the end i talked about us wanted to explore our chances with k8s since we already wanted developers to get more familiar with it, and what is more needed to get familiar with something t...]]></description><link>https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-kubernetes</link><guid isPermaLink="true">https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Docker]]></category><category><![CDATA[nginx]]></category><category><![CDATA[rabbitmq]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Sat, 24 Jul 2021 17:41:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627148343133/2ycr94cWl.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So on  <a target="_blank" href="/moving-from-vagrant-to-docker-and-docker-compose-the-problem">first part</a>  we talked about what the problem was, and at the end i talked about us wanted to explore our chances with k8s since we already wanted developers to get more familiar with it, and what is more needed to get familiar with something than using it everyday!</p>
<p>So the real work started, i had to read more about k8s since now i'm not gonna simply use it like i did before by just running some ambiguous commands that i barely know, but i will now need to setup a whole environment using k8s and booooi that was  shocking!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627141179374/63bbq12VA.gif" alt="AnotherImperfectAmazontreeboa-max-1mb.gif" /></p>
<h2 id="the-research">The research</h2>
<p>I knew something about k8s but know i know even more, kubernetes has yaml files configurations where you just setup your environment into services, applications, secrets and more. This was quite interesting for me because now i need to do the exact same for our tiny local development.</p>
<p>Minikube was the first thing i did install, i already had virtualbox to run vagrant so this dependency is checked already. So i started playing a bit first of course i looked at some examples, explore some already written code on github and started to connect some dots, beside of course actually looking at our kubernetes code that we already using on production, our DevOps have written some good documentation for that and this was actually my starting point.</p>
<p>It is worth to mention that one of the main resources i had was  <a target="_blank" href="https://kubernetes.io/docs/home/">kubernetes documentation</a> , they have written an excellent docs that helped me getting up and running and even understand some concepts, especially for  <a target="_blank" href="https://kubernetes.io/docs/tutorials/kubernetes-basics/">learning the basics</a>.</p>
<p>At this stage i was kinda ready to actually slowly build our environment, but first you need to know about the tech stack itself.</p>
<h2 id="the-tech-stack">The tech stack</h2>
<p>Well we're obviously have a microservices architecture and that's why we're using kubernetes on production to orchestrate these services for us, but what is the actual tech stack we're using?</p>
<p>It's a SaaS web application so our tech stack consists of:</p>
<ul>
<li>Nginx</li>
<li>NodeJs</li>
<li>AngularJs</li>
<li>PHP</li>
<li>Ejabberd</li>
<li>RabbitMQ</li>
<li>Elasticsearch</li>
<li>Kibana</li>
<li>MongoDB</li>
<li>Python</li>
</ul>
<p>Now i need to write some yaml configuration files for kubernetes to have our local development environment ready, but wait .. we already have all the yaml files written for production so i can re-use these but tweak a bit for development environment.</p>
<h2 id="tweaking-the-configurations">Tweaking the configurations</h2>
<p>It was not easy as it seems though, turns out kubernetes is designed to orchestrate a production ready pods not some development applications. But what's the difference though?</p>
<p>Well in development environment what developers really care about is the ability to immediately see their code changes by a simple page refresh if its a web app, or a just by running the same command again if it is a CLI app. But for production app you'll have to rebuild the whole pod, and then maybe clear some cache and then you'll be able to see these changes, and that is of course time consuming.</p>
<p>So hot-reloading is required, and sure to do this in docker you must have some volumes mounted to the host machine, so that whenever you change a file on your host machine, the file is also going to be changed on the container itself, and hence on the pod.</p>
<p>So now you understand the main issue, that given this main requirement k8s is going to be a pain to configure since it's by design not ready for this.</p>
<h2 id="lets-try-anyway">Let's try anyway</h2>
<p>So who cares, we knew that it won't be easy, so let's try anyway! and let me walk you through the whole process.</p>
<p>I started writing some yaml files of course that will define how i wanted our apps, services and secrets to communicate, here is an example of one of our NodeJs services deployment file that i wrote.</p>
<pre><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">super-doper-nodejs-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">super-doper-nodejs-service</span>
      <span class="hljs-attr">group:</span> <span class="hljs-string">super-doper</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">super-doper-nodejs-service</span>
        <span class="hljs-attr">group:</span> <span class="hljs-string">super-doper</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">hostPath:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/host/path/super-doper/</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">super-doper-persistent-storage</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">command:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">/var/app/run-super-doper.sh</span>
        <span class="hljs-attr">volumeMounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/app/super-doper</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">super-doper-persistent-storage</span>
        <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">LOG_LEVEL</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">debug</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">PROCESSORS</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">'2'</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DOMAIN_NAME</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">super-doper.default.svc.cluster.local</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">MONGO_MAIN_DB</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">awesome-db</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">QUEUE_SERVER</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">rabbitmq-svc.default.svc.cluster.local</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">QUEUE_USER</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">test</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">QUEUE_PWD</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">test</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ENCRYPT_KEY</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">encrypt_key</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">common-secret</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">mrgeek/super-doper:latest</span>
        <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">IfNotPresent</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/health-check</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">3002</span>
            <span class="hljs-attr">scheme:</span> <span class="hljs-string">HTTP</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">300</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">60</span>
          <span class="hljs-attr">timeoutSeconds:</span> <span class="hljs-number">58</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">super-doper</span>
</code></pre><p>No crazy stuff of course, looking at this you deduce the approach i'm taking to actually start this service. First by filling some metadata like the deployment name <code>super-doper-nodejs-service</code> and having this under a group <code>super-doper</code> and also passing some ENV variables to the pod that will be later used by our NodeJs application like for example our RabbitMQ server will be taken out of this ENV variable <code>QUEUE_SERVER</code> which has the value of <code>rabbitmq-svc.default.svc.cluster.local</code> which is our RabbitMQ service FQDN inside our kubernetes cluster.</p>
<p>Notice also <code>volumes</code> and <code>volumeMounts</code> as this is how we're telling kubernetes to mount the host path to the pod, so whenever we change some files, they will be reflected into the pod and hence a simple refresh is needed.</p>
<p>And this is the service file for this deployment, since we need to define also how are we going to communicate with this NodeJs app</p>
<pre><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">super-doper</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">super-doper-svc</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3002</span>
      <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">super-doper</span>
</code></pre><p>So now the app will run and we will start experiencing the issues with hot reloading not actually working as expected along with other issues due to this matter, mostly in services not able to communicate between each other, or changing some files will cause pod to crash or even to change some internal configurations entirely.</p>
<p>Its good to mention i tried this long time ago, that's why i'm not so sure about the issues but that is the glance of it. But i'm sure that i'm going to try it again especially after i fixed most of the issues using docker compose as we're going to see in the next part, and maybe kubernetes is not the problem after all and we can actually use it as a development cluster.</p>
]]></content:encoded></item><item><title><![CDATA[Moving from Vagrant to Docker & Docker Compose: The Problem]]></title><description><![CDATA[I have been working on Vagrant for 2 years now, mostly I'm just working on the VM and my daily routine is just starting/stopping/restarting some services, tracing some logs, fight with some configurations for some services, and most of the time just ...]]></description><link>https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-the-problem</link><guid isPermaLink="true">https://blog.mrg.sh/moving-from-vagrant-to-docker-and-docker-compose-the-problem</guid><category><![CDATA[vagrant]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Omar Ahmed]]></dc:creator><pubDate>Fri, 23 Jul 2021 10:22:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627035565233/yCRHuIIOD.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been working on Vagrant for 2 years now, mostly I'm just working on the VM and my daily routine is just starting/stopping/restarting some services, tracing some logs, fight with some configurations for some services, and most of the time just syncing my changes on my host machine to VM (which is done automatically by configuring the IDE to just run <code>rsync</code> whenever I hit ctrl + s).</p>
<p>This is fine and mostly even be the average daily routine for all software engineers, but to get to the gist we'll have to understand what is Vagrant first.</p>
<h2 id="how-are-we-abusing-vagrant">How are we ab/using Vagrant?</h2>
<p>Well, I mentioned VM above, and that's basically what Vagrant is. Simply by writing a small text file <code>Vagrantfile</code> in which you'll configure what VM you'll use, and what open ports you need along with much other stuff, Vagrant will then transform these text configurations into a VM box which you can just ssh to and manually install services just like you do on any Linux VM.</p>
<p>Now, what makes it good is that after you're happy with your box, and you got it prepared to contain all necessary services/dependencies you can then ship this box and distribute it among all of you developers so they can all have the same environment and by just simple commands each one can just get the software up &amp; running.</p>
<h2 id="so-why-bother-moving-then">So why bother moving then!?</h2>
<p>Well, it has a major downside, which is the box itself! because now each developer will have to clone not only the <code>Vagrantfile</code> they will need to also download the box itself which is huge (our vagrant box was ~25Gb) and then developers will need to use git-lfs to download and this is an extra dependency besides git.</p>
<p>But the main downside is that our Vagrant box becomes hard to maintain and then sure outdated because it's just hard to upload then download ~25Gb whenever you change a line or something in the box.</p>
<p>This of course led to errors and issues that each developer was facing, and then he will have to inform all the team about the solution in case someone is facing the same problem again.</p>
<p>So obviously this is the exact kind of problem that Docker is solving, and giving that we're using Docker on production and Vagrant only for local environments it even made more sense to do it.</p>
<h2 id="but-wait-why-docker-why-not-k8s">But wait! why docker? why not K8S?</h2>
<p>We're using Docker on the production of course, but we're using Kubernetes to orchestrate everything, so hmmm... why not have the same infrastructure but tweak it as a local development environment for a variety of reasons and the most important one is to make developers familiar with K8s and to make them get used to it since each developer have to touch some Kubernetes files or even manage his own cloud development environment that is deployed specifically for each developer.</p>
<p>This was the actual beginning of the journey and of a developer who knows little about Vagrant trying to migrate it to K8s which he knows very very very very little about.</p>
<p>In the next post, I will try to explain and remember what I did and what issues I faced.
Till then, keep yourself ALWAYS posted ;)</p>
]]></content:encoded></item></channel></rss>