<?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[Yankee Maharjan]]></title><description><![CDATA[Software Engineer, Open Source Enthusiast, Cloud-Native fanatic]]></description><link>https://yankee.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 23:46:42 GMT</lastBuildDate><atom:link href="https://yankee.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Hands-On vLLM Thinking Token Budget]]></title><description><![CDATA[vLLM is a workhorse to run inference for any LLM under the sun. One of the recent developments in the project is the ability to define thinking_token_budget, basically a request level argument that ca]]></description><link>https://yankee.dev/hands-on-vllm-thinking-token-budget</link><guid isPermaLink="true">https://yankee.dev/hands-on-vllm-thinking-token-budget</guid><category><![CDATA[vLLM]]></category><category><![CDATA[llm]]></category><category><![CDATA[ModelServing]]></category><category><![CDATA[inference]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sun, 24 May 2026 18:39:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/400a3c85-5dc3-447a-9f4c-1bbd2da22028.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>vLLM is a workhorse to run inference for any LLM under the sun. One of the recent developments in the project is the ability to define <code>thinking_token_budget</code>, basically a request level argument that can determine how much tokens the model will spend on thinking.</p>
<p>First place to go to check how to use this and get a list of supported models is definitely on the <a href="https://docs.vllm.ai/en/latest/features/reasoning_outputs/#thinking-budget-control">docs</a>. It lists only three models as of today and a single example that does not tell you much about what are all the models that support thinking budget and what are the reasoning start and end string that we can use while serving the model. With that I started digging into the source code of vLLM and got a gist of which models in vLLM supports <code>thinking_token_budget</code> and how to find them.</p>
<h2>Finding all the reasoning parsers</h2>
<p>All of the reasoning parsers supported by vLLM can be found conveniently inside of the <a href="https://github.com/vllm-project/vllm/blob/releases/v0.21.0/vllm/reasoning/__init__.py"><code>vllm/reasoning/__init__.py</code></a> file. It shows, what's the name of the parser (required while running vLLM), filename (inside of the same directory) and classname (inside the file) to load it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/0306fd13-f0de-4fa9-8a5b-3d8324b9c5d0.png" alt="" style="display:block;margin:0 auto" />

<p>---</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/91e70220-bcfb-4ca8-b325-d9ccdbeb7d01.png" alt="List of Reasoning parser in vLLM" style="display:block;margin:0 auto" />

<h2>Classes to look for</h2>
<p>If we look into the source code, then we can see two Base Classes used by all the parsers. One is <code>ReasoningParser</code> and another one is <code>BaseThinkingReasoningParser</code> which inherits from <code>ReasoningParser</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/0d42a2a4-1500-406c-94d4-5463b2ea1e7c.png" alt="" style="display:block;margin:0 auto" />

<p>Looking at the code, any Reasoning class that directly inherits from <code>ReasoningParser</code> is an older parser for models that do not support <code>thinking_token_budget</code> or do not contain simple tokens for determining start and stop (<a href="https://github.com/vllm-project/vllm/blob/main/vllm/reasoning/granite_reasoning_parser.py#L42-L46">take granite as an example</a>). Also a big giveaway is their <code>reasoning_start_str</code> and <code>reasoning_end_str</code> property are null.</p>
<p>So if we are looking for a model that supports thinking budget, then we have to look for reasoning parser classes that inherit from <code>BaseThinkingReasoningParser</code>. And to be able to use it, we have to find <code>reasoning_start_str</code> and <code>reasoning_end_str</code> within that class. Some of the classes use <code>start_token</code> and <code>end_token</code> instead of the aforementioned ones, but those are the values you are looking for to pass to your <code>--reasoning-config</code> flag.</p>
<h2>Putting it together</h2>
<p>As a <a href="https://github.com/vllm-project/vllm/blob/releases/v0.21.0/vllm/v1/engine/input_processor.py#L101-L108">hard requirement vLLM</a> specifically requires us to pass <code>--reasoning-parser</code> along with <code>--reasoning-config</code> where we need to pass <code>reasoning_start_str</code> and <code>reasoning_end_str</code> token to use thinking budget control. You can find all the reasoning tokens tokens from the parsers using this command:</p>
<pre><code class="language-shell">rg -l 'BaseThinkingReasoningParser' vllm/reasoning/ | xargs rg -t py -A 2 'def (start_token|end_token|reasoning_start_str|reasoning_end_str)\b' 
</code></pre>
<p><strong>First command</strong>: finds all Python files under <code>vllm/reasoning/</code> that reference <code>BaseThinkingReasoningParser</code>.</p>
<p><strong>Second command</strong>: prints each definition of <code>start_token</code>, <code>end_token</code>, <code>reasoning_start_str</code>, or <code>reasoning_end_str</code> (plus the 2 lines following it) from those files.</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/24ddce96-9320-4f64-8e6f-2e1404d19be5.png" alt="" style="display:block;margin:0 auto" />

<h2>Finally</h2>
<p>Now if we want to use <code>thinking_token_budget</code> for <code>gemma4</code> suppose; I need to get:</p>
<ul>
<li><p>name of the parser (from <code>vllm/reasoning/__init__.py</code>)</p>
</li>
<li><p>reasoning config (<code>reasoning_start_str</code> / <code>start_token</code> and <code>reasoning_end_str</code> / <code>stop_token</code>) from <code>vllm/reasoning/gemma4_reasoning_parser.py</code></p>
</li>
</ul>
<pre><code class="language-shell">vllm serve google/gemma-4-31B \
    --reasoning-parser gemma4 \
    --reasoning-config '{"reasoning_start_str": "&lt;|channel&gt;", "reasoning_end_str": "&lt;channel|&gt;"}'
</code></pre>
<p>When the server is up and running, I can send my request with <code>thinking_budget_token</code>:</p>
<pre><code class="language-plaintext">curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B",
    "messages": [
      { "role": "user", "content": "How many r's in strawberry?" }
    ],
    "thinking_token_budget": 10
  }'
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How I manage my Python Virtual Environments]]></title><description><![CDATA[In a common workflow we create a virtual environment using different tools inside of the project directory. You might have venv, .venv, env and so on. I find it a bit cluttered. My approach is a bit d]]></description><link>https://yankee.dev/how-i-manage-my-python-virtual-environments</link><guid isPermaLink="true">https://yankee.dev/how-i-manage-my-python-virtual-environments</guid><category><![CDATA[Python]]></category><category><![CDATA[zsh]]></category><category><![CDATA[Git]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Git worktree]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Wed, 29 Apr 2026 04:15:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/4c607c14-979b-45f9-80f1-37017d68c09e.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a common workflow we create a virtual environment using different tools inside of the project directory. You might have <code>venv</code>, <code>.venv</code>, <code>env</code> and so on. I find it a bit cluttered. My approach is a bit different, and you might already be using something similar with a third party tool.</p>
<p>Here's how it goes, all the virtual environments go inside of the central <code>~/.venvs</code> directory. If I am inside of a project called <code>project-xyz</code>, I create a virtual environment inside of the central directory like <code>~/.venvs/project-xyz</code>. I have one-to-one mapping of my project directory name with my virtual environment name and it comes with some benefits and shortcuts.</p>
<h2>Creating Virtual Environments</h2>
<p>I have a zsh function that helps me build a virtual environment using the python version of my choice. And yes I use <a href="https://docs.astral.sh/uv/getting-started/installation/">uv</a>.</p>
<p>This is what I have in my <code>~/.zshrc</code></p>
<pre><code class="language-shell">vm() {
    base_name=\((basename "\)PWD")
    uv venv --python 3.\({1:-11} ~/.venvs/"\)base_name"
}
</code></pre>
<ul>
<li><p><code>vm</code>: name of the function, will also automatically become a SHELL alias</p>
<p>For me everything relating to virtual environment starts with <code>v</code> so it's "virtual environment make", aka <code>vm</code></p>
</li>
<li><p><code>base_name=\((basename "\)PWD")</code>: we are taking the base name of the "present working directory (pwd)" which will be the name of the project directory.</p>
<p><strong>example:</strong></p>
<p>if <code>pwd</code> is <em>"/home/yankee/projects/tldr-generator"</em></p>
<p>then <code>basename</code> is <code>tldr-generator</code></p>
</li>
<li><p><code>uv venv --python 3.${1:-11}</code>: create a virtual environment using <code>uv</code>. Set the python version that defaults to 11 and if I want to use any other version it will take the first argument passed to the alias.</p>
<p><strong>example</strong>:</p>
<p>if I run <code>vm 13</code>, it will create a virtual environment with python version 3.13. At times I feel lazy to write the whole <code>3.13</code> so I just pass the minor version I want to use.</p>
</li>
<li><p><code>~/.venvs/"$base_name</code>": The later part is just the location where I want to create my virtual environment.</p>
</li>
</ul>
<p>Now with this command, I can to go to any project directory, then run:</p>
<pre><code class="language-shell">vm

# OR
# for python 3.13
vm 13

# for python 3.12
vm 12

# and so on...
</code></pre>
<h2>Activating Virtual Environments</h2>
<p>Adding on to the idea of having the <code>base_name</code> as the virtual environment on the centralized location. Any time I am in the project directory I can easily activate the virtual environment from <code>~/.venvs/&lt;base_name&gt;/bin/activate</code>. For that I have a function called <code>va</code> (virtual environment activate).</p>
<pre><code class="language-shell">va() {
    base_name=\((basename "\)PWD")
    source ~/.venvs/"$base_name"/bin/activate
}
</code></pre>
<p>Now if I am in any of my project directory, I can simply run <code>va</code> and it will activate the correct virtual environment for the project. And if it does not exist, I can do <code>vm</code> followed by <code>va</code>.</p>
<h2>Deactivating</h2>
<p>To deactivate a virtual environment, one can simply write <code>deactivate</code> but it ‘s a long word to type and mistakes have happened. So following the pattern, I have an alias for this one called <code>vd</code> (more about aliases <a href="https://www.youtube.com/watch?v=Ww3IfxEut4M">in this video</a>)</p>
<pre><code class="language-shell">alias vd="deactivate"
</code></pre>
<h2>Deletion</h2>
<p>One common action I have to do sometimes is delete a virtual environment and build a new one again. For deletion I have an alias called <code>vrm</code> that deletes the existing virtual environment.</p>
<pre><code class="language-bash">vrm(){
    base_name=\((basename "\)PWD")
    venv_path="\(HOME/.venvs/\)base_name"
    rm -rf "$venv_path"
    echo "Deleted $venv_path"
}
</code></pre>
<p>Same idea with <code>basename</code> to find the virtual environment and delete it. Now with creation and activating the new venv, the whole flow looks like this:</p>
<pre><code class="language-bash">vrm 
vm 
va
uv sync # or whatever package installer action
</code></pre>
<h2>Putting it all together</h2>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/b4b98707-5c58-4ba2-a2ec-65399f235a3b.gif" alt="" style="display:block;margin:0 auto" />

<h2>Why do this?</h2>
<ul>
<li><p><strong>Birds-eye view of all virtual environments and deletion</strong>: most of the time there are projects with venv that takes huge amounts of storage. They are just there sitting idle and it’s hard to find them. With everything under <code>~/.venvs</code> I have an birds-eye view of which project is consuming what amount of storage. And if it’s taking too much storage or I no longer need it, I can delete it from there. We can do this with a simple built-in <code>du</code> command or you can point your storage manager app here.</p>
<pre><code class="language-shell">du -sh */ | sort -hr
</code></pre>
<ul>
<li><p><code>-sh</code>: shows size of each directory in human-readable form</p>
</li>
<li><p><code>sort -h</code> → sorts by size (handles KB, MB, GB correctly)</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/5999ca05-6630-4573-9b2e-759ac47256af.png" alt="" style="display:block;margin:0 auto" /></li>
</ul>
</li>
<li><p><strong>Good with git worktrees, kinda</strong>*: When I am working with multiple features (mostly two at a time) for a project, I usually work on the main worktree and create a new worktree to hack on a different feature. So the way I create worktrees is similar to virtual environments, where things are kept central. The pattern I follow is:</p>
<pre><code class="language-shell">~/.worktrees/&lt;feature-name&gt;/&lt;project-name&gt;
</code></pre>
<p>Here the <code>project-name</code> is same as the main project directory name (basename). For <code>va</code> to work, we explicitly need to match the name of the project directory, hence I create a nested directory structure where feature branch is a parent directory that contains the main project directory.</p>
<p>This way I can switch to my worktree, run <code>va</code> again, and by nature of finding virtual environment and activating based on the basename, it will automatically activate the existing virtual environment that I created from my main worktree.</p>
<img src="https://cdn.hashnode.com/uploads/covers/609a020321068818a5c65045/f575111c-68cf-4d9e-80bc-f74309768424.gif" alt="Activating the same virtual environment with different worktrees" style="display:block;margin:0 auto" />

<p>I have a dedicated tool created to switch between worktrees with breeze called <a href="https://github.com/yankeexe/git-worktree-switcher">git-worktree-switcher</a>. And to see how you can work with git worktrees and make use of this tool to be more effective you can check my dedicated video:</p>
</li>
</ul>
<p><a class="embed-card" href="https://www.youtube.com/watch?v=fRPpM5kvlls">https://www.youtube.com/watch?v=fRPpM5kvlls</a></p>

<pre><code>So the `kinda*` is, sometimes the feature that I am building requires a version upgrade, addition or removal of a package. In such cases using the same virtual environment across two different feature branch creates chaos. When this happens I don’t bother creating a project directory nested inside the feature directory. I simply create a worktree that does not match the main worktree project directory name and create a new virtual environment for it.

```shell
git worktree add -b new-feature-x ~/.worktrees/new-feature-x
```
</code></pre>
<ul>
<li><strong>Faaaast and error free</strong>: with all this zsh/bash function and aliases I can go about my day to day dealings with virtual environment and worktrees super fast. I have been using this workflow for couple of years now and these aliases has helped me immensely to type less and make less mistakes. You are very less likely to mistake a two letter alias compared to writing a whole command or a tab autocompletion suggestion which might have had a typo.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Build LLM App with Web Search: Step-by-Step Guide]]></title><description><![CDATA[Learn how to build your own powerful Large Language Model (LLM) application that can search the web, extract information, and answer your questions – all LOCALLY!
In this tutorial, I'll walk you through the process of creating a web-searching LLM fro...]]></description><link>https://yankee.dev/build-llm-app-with-web-search-step-by-step-guide</link><guid isPermaLink="true">https://yankee.dev/build-llm-app-with-web-search-step-by-step-guide</guid><category><![CDATA[AI]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[llm]]></category><category><![CDATA[ollama]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Mon, 07 Jul 2025 20:25:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751919821066/59d6acaa-7d56-478d-9d4a-1efb55a8b7b3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Learn how to build your own powerful Large Language Model (LLM) application that can search the web, extract information, and answer your questions – all LOCALLY!</p>
<p>In this tutorial, I'll walk you through the process of creating a web-searching LLM from scratch, covering web crawling, vector databases, and semantic search. No API keys or cloud services needed!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/kNgx0AifVo0">https://youtu.be/kNgx0AifVo0</a></div>
]]></content:encoded></item><item><title><![CDATA[Build your own Artifact Previewer using Local LLM]]></title><description><![CDATA[Large Language Models are great at generating code, but we need to go an extra mile to run them; well not any more.
In this video, we will be building our application that can use both local and hosted large language models (Gemini, ChatGPT, and so o...]]></description><link>https://yankee.dev/build-your-own-artifact-previewer-using-local-llm</link><guid isPermaLink="true">https://yankee.dev/build-your-own-artifact-previewer-using-local-llm</guid><category><![CDATA[llm]]></category><category><![CDATA[AI]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[chatgpt]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Mon, 07 Jul 2025 20:15:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751919190643/1f5c3a79-baab-4ca2-8e73-05af2e2a9f46.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Large Language Models are great at generating code, but we need to go an extra mile to run them; well not any more.</p>
<p>In this video, we will be building our application that can use both local and hosted large language models (Gemini, ChatGPT, and so on) for generating code, and then we'll build our own sandbox to run that code and preview the output. This is similar to what you can experience with ChatGPT Canvas and Claude Artifact; but in our case, we have complete end-to-end control over the environment, dependencies, and the entire workflow. Go beyond generation – execute and iterate with confidence! 🚀</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/raB3KG8OFRY">https://youtu.be/raB3KG8OFRY</a></div>
]]></content:encoded></item><item><title><![CDATA[AWS Code Artifact for Private PyPI repository]]></title><description><![CDATA[Having a private PyPI repository can be beneficial in many ways. We can host our internal private packages; from security perspective, it can be a controlled environment for public packages from where all the dependencies are installed in our project...]]></description><link>https://yankee.dev/aws-code-artifact-for-private-pypi-repository</link><guid isPermaLink="true">https://yankee.dev/aws-code-artifact-for-private-pypi-repository</guid><category><![CDATA[AWS]]></category><category><![CDATA[Python]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sun, 04 Feb 2024 16:52:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707065439621/ea3f41b5-6c0d-46f3-9a1f-0f070ba5939e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Having a private PyPI repository can be beneficial in many ways. We can host our internal private packages; from security perspective, it can be a controlled environment for public packages from where all the dependencies are installed in our project/product.</p>
<p>If your company is invested in AWS then it makes perfect sense to use AWS Code Artifact to host private packages, be it for Python, Node, Java or others.</p>
<p>Benefits of Code Artifact includes:</p>
<ul>
<li><p>a private PyPI instance, where <code>pip</code> or other tools work the same as with the public instance. Just mention the vanilla package name and that's it. No more <code>pip installgit+ssh</code> , <code>git+https</code> or <code>./path-to-package</code>.</p>
</li>
<li><p>secure infrastructure that you can fully control and define who can access what.</p>
</li>
<li><p>packages encrypted by default with AWS KMS key.</p>
</li>
</ul>
<h2 id="heading-overview-of-elements-in-aws-code-artifact">🔎 Overview of elements in AWS Code Artifact</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704219590435/d346a5a5-5206-47f4-b1c2-970bf192806a.png" alt class="image--center mx-auto" /></p>
<p><strong>TLDR;</strong> you get a top-level construct called domain, under a domain you can have different repositories for same or different programming languages, each repository can store packages and their versions.</p>
<h2 id="heading-create-your-domain">🌐 Create your domain</h2>
<p>In AWS Code Artifact, domains are the namespace where you can host package repositories (PyPI, npm, maven). This is similar to creating domain name for website. Whatever name you put for your domain will be part of the URL that <code>pip</code> or other related tools will call for managing the packages.</p>
<ol>
<li><p>Go to Code Artifact and under Artifacts select Domains.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706807366692/ccad4d19-91c6-46b9-ab0b-4ac90b85a6ff.png" alt class="image--center mx-auto" /></p>
<p> Then add a domain name and select Create Domain.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812594886/4e4faa78-4d1f-476f-a5c0-4df3bfce5940.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Apply Domain policy<br /> Now that we have the domain, let's set the domain policy which allows us to get temporary authorization token for accessing repositories in the domain.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706807734712/6ae64101-03e8-4035-bbc4-e5df792dd69f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Paste the following resource-based policy and save it.<br /> Here we are allowing a particular IAM principal, in this case a user called 'test-user' to be able to perform <code>GetAuthorizationToken</code> action against the resource.<br /> Any valid IAM Principal is allowed here.</p>
<pre><code class="lang-yaml"> {
     <span class="hljs-attr">"Version":</span> <span class="hljs-string">"2012-10-17"</span>,
     <span class="hljs-attr">"Statement":</span> [
         {
             <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
             <span class="hljs-attr">"Principal":</span> {
                 <span class="hljs-attr">"AWS":</span> [
                     <span class="hljs-string">"arn:aws:iam::xyz:user/test-user"</span>
                 ]
             },
             <span class="hljs-attr">"Action":</span> <span class="hljs-string">"codeartifact:GetAuthorizationToken"</span>,
             <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"*"</span>
         }
     ]
 }
</code></pre>
</li>
</ol>
<blockquote>
<p>Note: AWS Code Artifact requires both resource-based policy and identity-based policy to work.</p>
</blockquote>
<h2 id="heading-create-your-repository">📦 Create your repository</h2>
<p>You can host multiple repositories under a single domain. We'll just create one for PyPI. Each repository is an independent entity and comes with its own configurations, and resource-based policies.</p>
<ol>
<li><p>Click on create repository</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706808592524/3891edf5-52fc-4e12-bc7f-469003d14a91.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Name your repository, add description to identify its usage properly.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812463348/d9be9c67-ee2c-4626-b5fb-950068e357b2.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Setting Public upstream repositories<br /> When you authorize <code>pip</code> or related tools to fetch packages from private repository, if the requested package is not in the private repo it will fetch from the selected public repository. The requested package will then be stored on the private repository as well.</p>
<p> We can select PyPI repository as upstream or leave it as blank if we intend to keep just the private packages. For me, I set the upstream as public PyPI as most of the private packages depend on other third-party packages which needs to be pulled while installing them.</p>
</li>
<li><p>Select Create Repository and we are done.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812523553/a5e8f06b-1983-4f8d-b535-90f15c7d57c1.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Similar to domain policy we need to set repository-level policy as well.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812702305/f38a31d3-0700-4186-95b9-843651b867e0.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Apply the following Policy.<br /> Policy has two statements, "PublishPackages" for publishing packages, here the principal will likely be an IAM Role, the pipeline assumes this role and uploads the built package to the private repository. For this demo, we are providing access to an IAM user.</p>
<p> The "InstallPackages" statement is a read-only policy which allows the IAM Principal to read and install packages from the private repository.</p>
<p> IAM principals that create the package will most likely be different from the ones that consume it, so it makes sense to have them separate. This is also a good approach in-terms of security.</p>
<pre><code class="lang-yaml"> {
     <span class="hljs-attr">"Version":</span> <span class="hljs-string">"2012-10-17"</span>,
     <span class="hljs-attr">"Statement":</span> [
         {
             <span class="hljs-attr">"Sid":</span> <span class="hljs-string">"PublishPackages"</span>,
             <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
             <span class="hljs-attr">"Principal":</span> {
                 <span class="hljs-attr">"AWS":</span> <span class="hljs-string">"arn:aws:iam::xyz:user/test-user"</span>
             },
             <span class="hljs-attr">"Action":</span> <span class="hljs-string">"codeartifact:PublishPackageVersion"</span>,
             <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"*"</span>
         },
         {
             <span class="hljs-attr">"Sid":</span> <span class="hljs-string">"InstallPackages"</span>,
             <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
             <span class="hljs-attr">"Principal":</span> {
                 <span class="hljs-attr">"AWS":</span> [
                     <span class="hljs-string">"arn:aws:iam::xyz:user/test-user"</span>
                 ]
             },
             <span class="hljs-attr">"Action":</span> [
                 <span class="hljs-string">"codeartifact:DescribePackageVersion"</span>,
                 <span class="hljs-string">"codeartifact:DescribeRepository"</span>,
                 <span class="hljs-string">"codeartifact:GetPackageVersionReadme"</span>,
                 <span class="hljs-string">"codeartifact:GetRepositoryEndpoint"</span>,
                 <span class="hljs-string">"codeartifact:ListPackageVersionAssets"</span>,
                 <span class="hljs-string">"codeartifact:ListPackageVersionDependencies"</span>,
                 <span class="hljs-string">"codeartifact:ListPackageVersions"</span>,
                 <span class="hljs-string">"codeartifact:ListPackages"</span>,
                 <span class="hljs-string">"codeartifact:ReadFromRepository"</span>,
                 <span class="hljs-string">"codeartifact:GetRepositoryEndpoint"</span>
             ],
             <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"*"</span>
         }
     ]
 }
</code></pre>
</li>
</ol>
<h2 id="heading-push-your-package">🚀 Push your package</h2>
<p>I will be pushing my CLI-tool called <a target="_blank" href="https://github.com/yankeexe/timezones-cli">timezones-cli</a> for this demo. You can use any Python package of your choice.</p>
<blockquote>
<p>🌟Note: Make sure you have <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI v2</a> installed on your machine.</p>
</blockquote>
<p>Before we can push anything, we need to configure the policy for our user to be able to authorize to the Code Artifact domain and get the packages.</p>
<p>Set the following policy for the IAM user:</p>
<pre><code class="lang-yaml">{
    <span class="hljs-attr">"Version":</span> <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement":</span> [
        {
            <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action":</span> <span class="hljs-string">"codeartifact:GetAuthorizationToken"</span>,
            <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"arn:aws:codeartifact:ap-south-1:xyz:domain/blog-pypi"</span>
        },
        {
            <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action":</span> <span class="hljs-string">"sts:GetServiceBearerToken"</span>,
            <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Condition":</span> {
                <span class="hljs-attr">"StringEquals":</span> {
                    <span class="hljs-attr">"sts:AWSServiceName":</span> <span class="hljs-string">"codeartifact.amazonaws.com"</span>
                }
            }
        },
        {
            <span class="hljs-attr">"Effect":</span> <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action":</span> [
                <span class="hljs-string">"codeartifact:DescribePackageVersion"</span>,
                <span class="hljs-string">"codeartifact:DescribeRepository"</span>,
                <span class="hljs-string">"codeartifact:GetPackageVersionReadme"</span>,
                <span class="hljs-string">"codeartifact:GetRepositoryEndpoint"</span>,
                <span class="hljs-string">"codeartifact:ListPackageVersionAssets"</span>,
                <span class="hljs-string">"codeartifact:ListPackageVersionDependencies"</span>,
                <span class="hljs-string">"codeartifact:ListPackageVersions"</span>,
                <span class="hljs-string">"codeartifact:ListPackages"</span>,
                <span class="hljs-string">"codeartifact:ReadFromRepository"</span>
            ],
            <span class="hljs-attr">"Resource":</span> <span class="hljs-string">"arn:aws:codeartifact:ap-south-1:xyz:repository/blog-pypi/private-pypi"</span>
        }
    ]
}
</code></pre>
<p>That's it for the policy, we are all set now! Let's get back to publishing the package.</p>
<p>AWS Code Artifact provides you a list of tools you can authorize using AWS CLI.</p>
<h3 id="heading-lets-configure-twine-to-upload-packages"><strong>Let's configure twine to upload packages</strong></h3>
<ol>
<li><p>Inside of the repository, select on View connection instructions</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706810789452/c81e6028-d094-4536-874a-5eed92c5b31a.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Select twine and copy the <code>aws codeartifact</code> CLI command.<br /> You should have <a target="_blank" href="https://pypi.org/project/twine/">twine</a> installed on your virtual environment or global scope.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706809914380/427d1ab6-08c9-417b-befa-1e8ca7a69f50.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Let's create a source distribution (.tar.gz) and a wheel distribution (.whl) for our package.<br /> From the root of the project run the following command.</p>
<blockquote>
<p>🌟 Note: If you are creating wheel distribution then make sure <code>wheel</code> package is installed on your machine.</p>
</blockquote>
<pre><code class="lang-bash"> python setup.py bdist_wheel sdist
</code></pre>
<p> This command will create a <code>dist</code> directory which will contain our package ready to be pushed.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706810315228/85a2b8f1-2a85-4c75-8d0f-2bcd6d2e56e0.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>On your terminal, paste the command copied for Code Artifact, if you need to use any particular AWS CLI profile then use the <code>--profile</code> flag to pass the profile name like in the example.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706810922898/c4a160d9-6845-4dfb-ae6f-74088defcd6d.png" alt class="image--center mx-auto" /></p>
<p> After successful login, twine configurations will be in <code>~/.pypirc</code>, if you want to logout then delete this file.</p>
</li>
<li><p>Let's upload our package 🎉<br /> We need to specify the repository we are pushing to, which is done using the <code>-r</code> flag.</p>
<pre><code class="lang-bash"> twine upload -r codeartifact dist/*
</code></pre>
<p> Package is uploaded successfully! 🙌</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706811604629/b9a3d06c-00a6-425c-be41-4417837125f4.png" alt class="image--center mx-auto" /></p>
<p> We can confirm this checking our repository.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706811666668/adde2dc8-7388-4eb9-8d9f-c2d1b387e0fc.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h2 id="heading-pull-your-package">⚡️ Pull your package</h2>
<p>Now that we've pushed our package, it's time to download them.</p>
<p>Go to the repository page and select "View connection instructions" again. This time select the <code>pip</code> tool and copy the login command.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706811795598/7c5ea57c-3d65-46f7-884b-ec06aef04b6e.png" alt class="image--center mx-auto" /></p>
<p>We should be out of our package's virtual environment, in a completely new virtual environment, or installing the package in the global context. I will be installing it in the global context.</p>
<p>Paste the command for pip login, once logged in, pip generates configuration on <code>~/.config/pip/pip.conf</code>. To logout, we can delete this file or run <code>pip config unset global.index-url</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812031409/1a332ae6-05f1-4e0f-9fd4-971cf3030bf3.png" alt class="image--center mx-auto" /></p>
<p>Now every <code>pip install</code> command we put out will try to communicate with our private repository. We can install the package with the regular pip install:</p>
<pre><code class="lang-yaml"><span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">timezones-cli</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706812811827/2692da36-8993-4ca3-8b92-95f23f2201c2.png" alt class="image--center mx-auto" /></p>
<p>We have successfully uploaded and downloaded package from our private PyPI repository hosted on AWS Code Artifact. ⚡️</p>
<h2 id="heading-conclusion">🌟 Conclusion</h2>
<p>Using AWS Code Artifact is one of many ways of installing private python packages. If you or your company is already invested in AWS then it makes perfect sense to use AWS Code Artifact. If not then you can evaluate the pros and cons for using this solution. But all AWS Code Artifact provides a complete and secure solution to get you up and running with your private package infrastructure in no time.</p>
]]></content:encoded></item><item><title><![CDATA[Effortless CI/CD with GitHub Composite Actions]]></title><description><![CDATA[GitHub Actions is a powerful and versatile CI/CD solution that works for different teams and projects. It's a built-in feature of GitHub, the platform that many developers use daily, so it's easy to set up and use.
One of the benefits of GitHub Actio...]]></description><link>https://yankee.dev/cicd-with-github-composite-actions</link><guid isPermaLink="true">https://yankee.dev/cicd-with-github-composite-actions</guid><category><![CDATA[Devops]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[continuous deployment]]></category><category><![CDATA[Git]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Tue, 19 Dec 2023 07:26:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702970446393/69755f1b-9710-4aa5-a1c8-7f56a2004a58.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GitHub Actions is a powerful and versatile CI/CD solution that works for different teams and projects. It's a built-in feature of GitHub, the platform that many developers use daily, so it's easy to set up and use.</p>
<p>One of the benefits of GitHub Actions is that it allows you to write DRY (Don't Repeat Yourself) code for your pipeline, using reusable steps. Composite actions let you combine multiple run steps into a single reusable action that you can share across repos, organizations or publicly.</p>
<h2 id="heading-mental-model-for-composite-actions">🧠 Mental Model for Composite Actions</h2>
<h3 id="heading-as-extensions">🧩 As extensions</h3>
<p>When you think of composite action you should think of it <strong>as an extension of your existing workflow.</strong> If certain steps are repeated in multiple workflows, then it makes sense to put them as a composite action.</p>
<p>Note that: composite action is one of the three ways you can create actions that can be shared in public.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701273324196/cfb79b55-8ecd-4757-8bcd-49654efed224.png" alt class="image--center mx-auto" /></p>
<p><strong>For example,</strong> there is a process of building, tagging and uploading a Docker image to the registry for many of your projects, then you can create a composite action that can be referenced in all of your required projects.</p>
<hr />
<h3 id="heading-as-functions">🧬 As Functions</h3>
<p>Now as developers, we are pretty familiar with what functions are and how they work. Just for a refresher, you wrap a piece of code as a function when it is supposed to be used in multiple places. You can pass arguments to it which it processes and produces outputs.</p>
<p>Similarly, we can think of Composite Actions as functions to which we can pass arguments, which process and produce certain output.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701272115552/0aae7efa-b501-4c41-ba0b-bb06838f2a86.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-how-to-structure-your-actions">📚 How to structure your actions?</h2>
<p>There are two ways of structuring Composite Actions:</p>
<ol>
<li><p>Single repo single action (root action)</p>
</li>
<li><p>Single repo multi-action (path-based)</p>
</li>
</ol>
<h3 id="heading-single-repo-action">Single repo action</h3>
<p>This is the most common pattern that you see in publicly shared GitHub Actions. You have a repository that houses <code>action.yaml</code> file on the root which defines a set of steps that can be reused.</p>
<p>Example of a simple hello-world composite action:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Greeter</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Composite</span> <span class="hljs-string">Action</span> <span class="hljs-string">to</span> <span class="hljs-string">greet</span> <span class="hljs-string">someone</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-attr">who-to-greet:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">default:</span> <span class="hljs-string">World</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">who</span> <span class="hljs-string">to</span> <span class="hljs-string">greet</span>

<span class="hljs-attr">runs:</span>
  <span class="hljs-attr">using:</span> <span class="hljs-string">composite</span>
  <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">greet</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Hello $<span class="hljs-template-variable">{{ inputs.who-to-greet }}</span>"</span>
      <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
</code></pre>
<p>You can browse the above sample action here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/yankeexe/actions-hello-world">https://github.com/yankeexe/actions-hello-world</a></div>
<p> </p>
<h3 id="heading-single-repo-multiple-actions">Single repo multiple actions</h3>
<p>In this pattern, you have a single git repository that houses multiple actions that you can reuse across your organizations. This pattern is mostly popular in organizations where you house multiple composite actions in the same place which is referenced across different projects, teams or domains.</p>
<blockquote>
<p>When using this pattern you have to specify exactly where your action is located so the path of the action is a key here.</p>
</blockquote>
<p>An example of this pattern is as follows:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/yankeexe/actions-combined">https://github.com/yankeexe/actions-combined</a></div>
<p> </p>
<p>You can notice that each composite action has its directory and the workflow file is named <code>action.yaml</code> .</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702962841104/a43f3592-d01a-46f1-bb5a-366851ed9278.png" alt /></p>
<p>When we name the files <code>action.yaml</code>, we don't have to mention the file name when calling the action. Meaning we can do this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">uses:</span> <span class="hljs-string">yankeexe/actions-combined/actions/ping@main</span>
</code></pre>
<p>If your filename is something different then you have to mention the filename in the calling workflow as well. For example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">uses:</span> <span class="hljs-string">yankeexe/actions-combined/actions/ping/ping-action.yaml@main</span>
</code></pre>
<h2 id="heading-actions-gotchas">👻 Actions Gotchas</h2>
<h3 id="heading-is-your-action-accessible">🔎 Is your Action Accessible?</h3>
<p>For both single repo actions and single repo multi-actions, we have to note repository visibility. If the composite action is in a public repository then it's accessible for everyone. If the action is in a private repository then it can be accessed within the organization or users repositories by going to: <code>settings &gt; Actions &gt; General</code> and enabling the following option.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702960030619/4ca92937-2ce4-4361-a4a1-7eedccd31e5a.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-referencing-your-action">🫵 Referencing your action</h3>
<p>You can reference composite actions in multiple ways:</p>
<ol>
<li><p>🪵 Branch name</p>
<p> Takes the steps from the workflow file present in a branch.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">uses:</span> <span class="hljs-string">yankeexe/actions-hello-world@main</span>
</code></pre>
</li>
<li><p>#️⃣ Commit hash</p>
<p> Takes the steps from the workflow file present in a particular commit.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">uses:</span> <span class="hljs-string">yankeexe/actions-hello-world@a37bf8bbc2b75c505b41a0741ce589bf403ed9f9</span>
</code></pre>
</li>
<li><p>🏷️ Tag</p>
<p> Takes the steps from the workflow file present in a particular tag.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">uses:</span> <span class="hljs-string">yankeexe/actions-hello-world@0.0.1</span>
</code></pre>
</li>
</ol>
<h3 id="heading-action-context">🌌 Action Context</h3>
<p>The context of the composite action is always set to the repo of the calling workflow if they have checkout action used in them. Otherwise, we can explicitly use the checkout action on the composite action as well.</p>
<p>To use checkout action in the calling workflow it needs the permissions for reading the content of the repository.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># calling workflow</span>
<span class="hljs-attr">permissions:</span> 
    <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">demo_job:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">call_composite_action</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">demo_user/demo_actions/.github/actions/do_something@main</span>
        <span class="hljs-string">...</span>
</code></pre>
<p>To use the checkout action in the composite action itself, we need the <code>GITHUB_TOKEN</code> passed from the calling workflow to the composite action.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Calling workflow</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">call_composite_action</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">demo_user/demo_actions/.github/actions/do_something@main</span>
  <span class="hljs-attr">with:</span>
      <span class="hljs-attr">GITHUB_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<pre><code class="lang-yaml"><span class="hljs-comment"># composite action </span>
<span class="hljs-attr">inputs:</span> 
   <span class="hljs-attr">GITHUB_TOKEN:</span>
       <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
       <span class="hljs-attr">description:</span> <span class="hljs-string">Github</span> <span class="hljs-string">token</span> <span class="hljs-string">passed</span> <span class="hljs-string">from</span> <span class="hljs-string">calling</span> <span class="hljs-string">workflow</span>
       <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">runs:</span>
  <span class="hljs-attr">using:</span> <span class="hljs-string">composite</span>
  <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
      <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
      <span class="hljs-attr">with:</span>
          <span class="hljs-attr">token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">repository:</span> <span class="hljs-string">demo_user/demo_project</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">main</span>
</code></pre>
<h3 id="heading-each-step-requires-a-shell">🐚 Each step requires a shell</h3>
<p>Whenever we are writing our composite action we'll likely forget to add the <code>shell</code> key for each of the steps. Shell defines where we want to execute the commands defined in the <code>run</code> field. Possible values for this field are:</p>
<ul>
<li><p>sh</p>
</li>
<li><p>bash</p>
</li>
<li><p>pwsh</p>
</li>
<li><p>python</p>
</li>
<li><p>nodejs</p>
</li>
<li><p>cmd</p>
</li>
<li><p>powershell</p>
</li>
</ul>
<p>You can read more about the <code>shell</code> <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell">field here.</a></p>
<h2 id="heading-oidc-in-reusable-workflow">🔐 OIDC in reusable workflow</h2>
<p>One of the benefits of composite actions over other reusable workflows is its ability to <a target="_blank" href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers">use OIDC</a> to authenticate with cloud providers without having to store any credentials for it. You can use OIDC with:</p>
<ul>
<li><p>Amazon Web Services</p>
</li>
<li><p>Azure</p>
</li>
<li><p>Google Cloud Platform</p>
</li>
<li><p>HashiCorp Vault</p>
</li>
</ul>
<p>Example: If your reusable action has some step that involves using AWS resources like S3 to upload an object, then using OIDC you can authenticate with AWS, assume a role you want to use and access the services. This is not possible with <code>workflow_run</code> or <code>workflow_call</code>.</p>
<p>But a gotcha when using OIDC with composite action is that the calling workflow or the main workflow that calls/consumes composite action should have permission set to:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">permissions:</span> 
    <span class="hljs-attr">id_token:</span> <span class="hljs-string">write</span>
    <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-comment"># Calling workflow</span>
<span class="hljs-attr">demo_job:</span>
  <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">call_composite_action</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">demo_user/demo_actions/.github/actions/do_something@main</span>
      <span class="hljs-attr">with:</span>
          <span class="hljs-attr">role_to_assume:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ROLE_TO_ASSUME</span> <span class="hljs-string">}}</span>
</code></pre>
<p>Then your composite action can have steps as:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">inputs:</span>
  <span class="hljs-attr">aws_role_to_assume:</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Role</span> <span class="hljs-string">ARN</span> <span class="hljs-string">to</span> <span class="hljs-string">use</span>

<span class="hljs-attr">runs:</span>
  <span class="hljs-attr">using:</span> <span class="hljs-string">composite</span>
  <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Credentials</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v2</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">role-to-assume:</span> <span class="hljs-string">${{</span> <span class="hljs-string">inputs.aws_role_to_assume</span> <span class="hljs-string">}}</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">List</span> <span class="hljs-string">S3</span> <span class="hljs-string">buckets</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">aws</span> <span class="hljs-string">s3</span> <span class="hljs-string">ls</span>
      <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
</code></pre>
<h2 id="heading-conclusion">👋 Conclusion</h2>
<p>As we navigate through the ever-evolving landscape of technology, tools like Composite Actions become indispensable and can be a game-changer propelling you toward more streamlined, effective, and enjoyable deployment experiences.</p>
<p>Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Securing Your Supply Chain: A Guide to Signing and Verifying Blobs]]></title><description><![CDATA[Signing artifacts should be a crucial part of our workflow. As a part of Supply Chain Security, we must ensure that the artifact built on our CI platform is the artifact we are deploying on our production environments.
If by any means, we cannot veri...]]></description><link>https://yankee.dev/supply-chain-security-sigstore-sign-and-verify-blobs</link><guid isPermaLink="true">https://yankee.dev/supply-chain-security-sigstore-sign-and-verify-blobs</guid><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Application Security]]></category><category><![CDATA[supplychainsecurity]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sat, 08 Jul 2023 05:40:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688794506428/a04e0622-20f4-476f-936e-639aeec4febe.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Signing artifacts should be a crucial part of our workflow. As a part of Supply Chain Security, we must ensure that the artifact built on our CI platform is the artifact we are deploying on our production environments.</p>
<p>If by any means, we cannot verify that the artifact built is not the artifact we are going to deploy then it’s a RED flag and we might be likely compromised. In this walkthrough, we are going to look at how we can use components of Project Sigstore: Cosign, Flucio, and Rekor - to sign and verify our artifacts.</p>
<blockquote>
<p>This guide is mostly focused on Binary Large Objects (Blob) and does not cover workflows related to Containers.</p>
</blockquote>
<h2 id="heading-primer">Primer 🐳</h2>
<p>We produce different types of artifacts on our build system, which can range from container images, zip files, log records, SBOMs and many more depending on the use case. These artifacts can be business essential and when tampered with will disrupt our businesses.</p>
<p>The process of verifying the artifacts that happen in 3 main high-level steps:</p>
<ul>
<li><p>Signing the artifact ✍️</p>
</li>
<li><p>Storing the artifact and signature 📦</p>
</li>
<li><p>Verifying the artifact ✅</p>
</li>
</ul>
<h3 id="heading-signing-artifacts">Signing Artifacts ✍️</h3>
<p>Sigstore offers us two ways of signing any artifact:</p>
<ol>
<li><p>using a public-private key (asymmetric encryption)</p>
</li>
<li><p>using keyless mode (recommended) ✨</p>
</li>
</ol>
<h2 id="heading-using-public-private-keys">Using Public-Private Keys</h2>
<p>This mechanism is something we have been using for ages. We sign our data using Private Key and verify the signature using the Public Key.</p>
<p>With cosign we can generate public-private key pair secured with a password.</p>
<p><a target="_blank" href="https://docs.sigstore.dev/cosign/installation/">Download cosign</a> ✨</p>
<pre><code class="lang-bash">cosign generate-key-pair
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688790875059/21b761e9-e08b-4a17-bed1-842991381dd2.png" alt class="image--center mx-auto" /></p>
<p>This will generate a public key: <code>cosign.pub</code> and a private key <code>cosign.key</code>.</p>
<p>Now using this private key, let’s sign our artifact. For this demo, I have created a zip file called <code>artifact.zip</code> where I have kept an arbitrary README file. You can do the same with a zip file or any file of your preference.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🧠</div>
<div data-node-type="callout-text">When we say signing the artifact, we are actually signing the hash (SHA-256) of the artifact. This is much more efficient when working with large artifacts.</div>
</div>

<pre><code class="lang-bash">cosign sign-blob --key cosign.key

<span class="hljs-comment"># OR</span>
<span class="hljs-comment"># we can also store our public key and signature as a bundle using: </span>
cosign sign-blob --key cosign.key --bundle cosign.bundle
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791101013/813ba440-e850-4f6c-95e4-734740109088.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-just-happened-here"><strong>What just happened here?</strong></h3>
<p>We just finished signing our artifact but a few things have happened in the background.</p>
<ul>
<li><p>Cosign signed generated a SHA256 hash of our artifact and created a signature by signing it</p>
</li>
<li><p>then it uploaded the public key and the signature to the transparency log Rekor and returned its index</p>
</li>
<li><p>Rekor is a public infrastructure that logs the public key and signature of all the data signed using cosign.</p>
</li>
</ul>
<hr />
<h3 id="heading-understanding-the-log-record-from-rekor"><strong>Understanding the log record from Rekor 🧐</strong></h3>
<p>We get the index of the log entry on the Rekor after signing the artifact which is provided as: <code>tlog entry created with index</code>. We can view the Rekor log for our artifact using the <a target="_blank" href="https://docs.sigstore.dev/rekor/overview/#usage-and-installation">rekor-cli</a>.</p>
<pre><code class="lang-bash">rekor-cli get --log-index &lt;rekor-log-index&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791456277/86e68c35-0392-492c-a91e-365c7f07bf0e.png" alt class="image--center mx-auto" /></p>
<p>Let's look at this result in depth (numbers co-relate to the image above ):</p>
<ol>
<li><p>The SHA256 sum of our artifact.zip file, we can verify this using:</p>
<pre><code class="lang-bash"> shasum -a 256 artifact.zip
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791490968/980ee99a-ddae-472a-a524-1b01d54ef3fb.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Inside <code>signature &gt; content</code> we have the same signature generated after signing the SHA of our artifact.</p>
<pre><code class="lang-bash"> rekor-cli get --log-index &lt;log-index&gt; --format json | jq -r .Body.HashedRekordObj.data.hash.value
</code></pre>
</li>
<li><p>Our public key that was generated when we ran <code>cosign generate-key-pair</code>. It is base64 encoded right now, if we decode it, we’ll get the contents of our public key aka <code>cosign.pub</code></p>
<pre><code class="lang-bash"> rekor-cli get --log-index &lt;rekor-log-id&gt; --format json | jq -r .Body.HashedRekordObj.signature.publicKey.content | base64 -D
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791653137/f6a34176-ae16-461b-bc54-08819465fd2c.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h3 id="heading-verifying-the-artifact"><strong>Verifying the artifact ✅</strong></h3>
<p>We’ll need the signature to verify our artifact. We can get it from the Rekor log and store it on a file called <code>cosign.sig</code>.</p>
<pre><code class="lang-bash">rekor-cli get --log-index &lt;rekor-log-id&gt; --format json | jq -r .Body.HashedRekordObj.signature.content &gt; cosign.sig
</code></pre>
<p>Now let’s verify our artifact! ⚡</p>
<p>We will be using our public key and signature for this.</p>
<pre><code class="lang-bash">cosign verify-blob --key cosign.pub artifact.zip --signature cosign.sig  artifact.zip

<span class="hljs-comment"># If you generated cosign.bundle when signing the artifact, you can use that bundle to verify the artifact</span>

cosign-verify-blob --bundle cosign.bundle artifact.zip
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791711532/f15d58ad-4d33-468e-ab8f-fb3926bdfcac.png" alt class="image--center mx-auto" /></p>
<p>We’ll get the message of <strong>“Verified OK”.</strong> If our public key didn’t match or the signature didn’t match, then we’ll get an error.</p>
<p>Congratulations, we have successfully signed an artifact and verified it 🎉.</p>
<p>In real world scenario, we’ll be signing the artifact and storing it somewhere, and at the time of dispatching/deployment/usage, we’ll make sure it is verified and only then make the intended use of it. This ensures that our artifact has not been tampered and it’s safe for our workflow/workload.</p>
<h2 id="heading-keyless-mode">Keyless Mode ✨</h2>
<p>Keyless mode is a convenient feature that hugely reduces the friction in artifact signing. When we use Public-Private keys, there’s this hassle of managing the keys, and keeping them safe. With keyless mode, we don’t have the overhead of managing any Private keys at all. It is all managed for us!</p>
<p>But the catch of using keyless mode is, it requires OIDC (Open ID Connect) to associate our identity to the X.509 certificate it generates; this is to attest the signer of the artifact which we can later verify. And the certificate is pushed to a public append-only log Rekor, where our identity will be available. Identity can be an email or pipeline ID which we and our team can verify is authentic and trust. For OIDC, it currently supports:</p>
<ul>
<li><p>Google</p>
</li>
<li><p>Microsoft</p>
</li>
<li><p>GitHub</p>
</li>
</ul>
<h3 id="heading-preparing-artifact">Preparing Artifact 🔨</h3>
<p>Let’s create a new zip from the existing zip and call it <code>artifact-keyless.zip</code> (or choose any file to your liking). For this demo, SHA is different for both of the zips created.</p>
<pre><code class="lang-bash">zip -r artifact-keyless.zip artifact.zip

shasum -a 256 artifact.zip | awk <span class="hljs-string">'{print $1}'</span>

shasum -a 256 artifact-keyless.zip | awk <span class="hljs-string">'{print $1}'</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791857986/03d28b95-ff19-43c7-b6f2-775ed7f88c51.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-sign-the-artifact"><strong>Sign the artifact</strong></h3>
<p>To sign in the keyless mode we just have to run the following command:</p>
<pre><code class="lang-bash">cosign sign-blob artifact-keyless.zip

<span class="hljs-comment"># OR </span>
<span class="hljs-comment"># sign-blob and generate a bundle containing X.509 ceritifcate and signature. </span>
cosign sign-blob artifact-keyless.zip --bundle cosign.bundle
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791920515/c04501f3-3cda-4b1c-ae2d-6558c76896c6.png" alt class="image--center mx-auto" /></p>
<p>Press <code>y</code> on the prompt. Then a browser tab will open for the OIDC verification. This is the identity verification step for the Certificate Authority (CA) before creating our digital certificate.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791949333/2f79b392-d870-4f9a-bb2d-4eb55b154ba1.png" alt class="image--center mx-auto" /></p>
<p>We need to select one of the OIDC providers, and once the authentication is successful, we’ll see a similar message as shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688791979114/a691917a-69aa-41bc-8455-3086daaed406.png" alt class="image--center mx-auto" /></p>
<p>You can notice that it has generated an ephemeral certificate which is only valid for 10 mins.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688792009844/1d7e1726-dcd1-48cc-b1b8-d53ca792ffe3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-just-happened-here-1"><strong>What just happened here?</strong></h3>
<ul>
<li><p>Cosign generated a private key in memory and then a public key out of that. The private key never touches the physical storage.</p>
</li>
<li><p>It generated the SHA of our artifact and signed it generating a signature.</p>
</li>
<li><p>It then communicated with the Certificate Authority (CA) Flucio to bind its identity with the public key on an X.509 certificate.</p>
</li>
<li><p>For identity proofing, we were prompted for OIDC.</p>
</li>
<li><p>Once we verify our identity, it receives the OIDC token.</p>
</li>
<li><p>It took the public key, signature and OIDC token to the Fulcio Certificate Authority.</p>
</li>
<li><p>the CA created an X.509 certificate which binds our public key with our identity, along with the signature of the signed artifact.</p>
</li>
<li><p>CA signed that certificate with its Private Key and pushed the certificate details to the Rekor Transparency log.</p>
</li>
</ul>
<p>See a lot of things happened in the background, but when we were using it; it was frictionless! It is one of the benefits of keyless signing with cosign. 🤩</p>
<p>Like before, on the output we have received the index of our Rekor log. Let’s take that log index and see the details of our artifact.</p>
<pre><code class="lang-bash">rekor-cli get --log-index &lt;your-log-index&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688792104021/8afbd167-18f1-427b-94d1-95183f125c7c.png" alt class="image--center mx-auto" /></p>
<p>The output is of the same format as before. But we should notice that the size of <code>publicKey &gt; content</code> is bigger than before. In the private-public key mode, we were using our public key and its size was small, but now it holds the X.509 certificate generated by Flucio.</p>
<p>Let’s see what our certificate looks like; the content is Base64 encoded, we should first decode it and save it to a file called <code>artifact-keyless.crt</code>:</p>
<pre><code class="lang-bash">rekor-cli get --log-index &lt;rekor-log-index&gt; --format json | jq -r .Body.HashedRekordObj.signature.publicKey.content | base64 -D &gt; artifact-keyless.crt
</code></pre>
<p>Now, let’s use a tool called <code>step</code> to view our certificate. You can <a target="_blank" href="https://smallstep.com/docs/step-cli/installation/">download it here</a>.</p>
<pre><code class="lang-bash">step certificate inspect artifact-keyless.crt --format json
</code></pre>
<p>It will generate a huge JSON, if you search for your email address you can find it in the <code>subject_alt_name</code> section.</p>
<pre><code class="lang-bash">step certificate inspect artifact-keyless.crt --format json  | jq .extensions.subject_alt_name
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688792168718/e97c51bd-c366-4ab2-98ea-389aa4254fe1.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-chain-of-trust"><strong>Chain of Trust ⛓️</strong></h3>
<p>Now we have the X.509 certificate, how do we make sure that the Fulcio Certificate Authority has issued it?</p>
<p>Well, our X.509 certificate has been signed with the private key from Fulcio and we can use its public key to verify them. The Root Certificate and Intermediate Certificate for Fulcio is <a target="_blank" href="https://fulcio.sigstore.dev/api/v2/trustBundle">available here</a>. You can download them and save them to a file called <code>chain.crt</code> or any name you prefer.</p>
<p>You can also find a copy of it here, but it's best if you download it from the official sources 😉</p>
<ul>
<li><pre><code class="lang-bash">    -----BEGIN CERTIFICATE-----
    MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw
    KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
    MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl
    LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C
    AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7
    7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS
    0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB
    BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp
    KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI
    zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR
    nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP
    mygUY7Ii2zbdCdliiow=
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
    KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
    MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
    LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
    XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
    X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
    YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
    wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
    KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
    WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
    TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
    -----END CERTIFICATE-----
</code></pre>
</li>
</ul>
<p>Let’s verify our X.509 certificate.</p>
<p>If we are validating the certificate within 10 mins of signing then we can use the following command:</p>
<pre><code class="lang-bash">openssl verify -CAfile chain.crt artifact-keyless.crt
</code></pre>
<p>If we are trying to validate the certificate, beyond 10 mins then we should use the following command, to skip checking time in order to validate the overall certificate:</p>
<pre><code class="lang-bash">openssl verify  -no_check_time -CAfile chain.crt artifact-keyless.crt
</code></pre>
<h3 id="heading-verify-the-artifact"><strong>Verify the artifact</strong> ✅</h3>
<p>Now that we have verified that the certificate is signed by the root CA. Let’s move into verifying our artifact. For this, we will also require the signature of our artifact. We can grab it from the <code>signature &gt; content</code> section from the output of the rekor cli. And we can store it in a file called <code>artifact-keyless.sig</code></p>
<pre><code class="lang-bash">rekor-cli get --log-index &lt;index-number&gt; --format json | jq -r .Body.HashedRekordObj.signature.content &gt; artifact-keyless.sig
</code></pre>
<p>With that in place, let’s use the following command where we pass the certificate, signature, certificate identity and certificate OIDC issuer to verify our artifact.</p>
<pre><code class="lang-bash">cosign verify-blob artifact-keyless.zip \
 --certificate artifact-keyless.crt \
 --signature artifact-keyless.sig \
 --certificate-identity=&lt;your-email-address&gt; \
 --certificate-oidc-issuer=https://accounts.google.com

<span class="hljs-comment"># OR </span>
<span class="hljs-comment"># If you have generated the cosign.bundle when signing the artifact, you can you that bundle to replace certificate and signature, as the bundle contains both of these.</span>

cosign verify-blob artifact-keyless.zip \
 --bundle cosign.bundle \
 --certificate-identity=&lt;your-email-address&gt; \
 --certificate-oidc-issuer=https://accounts.google.com
</code></pre>
<p>If we have got all the things right then we should get the “Verified OK” message, else we’ll get an error.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688792366281/5823ee35-61fa-4469-91be-93d6d1f45cd5.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Artifact signing and verification have become one of crucial steps in securing our infrastructure against supply chain attacks. Project Sigstore has provided us with the tools and infrastructure to do this with minimal effort. If you are not signing your artifacts then it’s high time you start doing so!</p>
]]></content:encoded></item><item><title><![CDATA[Streamline Github Actions Releases with Github CLI]]></title><description><![CDATA[GitHub Actions is the default choice for CI/CD in many Open Source and Enterprise projects. It gets regular feature updates and is flexible enough to get most of the things done.
One of the powerful feature it provides is the ability to re-use Action...]]></description><link>https://yankee.dev/streamline-github-actions-releases-with-github-cli</link><guid isPermaLink="true">https://yankee.dev/streamline-github-actions-releases-with-github-cli</guid><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Git]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sat, 11 Feb 2023 05:53:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676347802175/18ca0bef-3db1-4299-aefd-6218366e338a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GitHub Actions is the default choice for CI/CD in many Open Source and Enterprise projects. It gets regular feature updates and is flexible enough to get most of the things done.</p>
<p>One of the powerful feature it provides is the ability to re-use Actions, which is available on <a target="_blank" href="https://github.com/marketplace?type=actions">Marketplace</a> and we can use it from any GH repos. Most common Actions that I see being used almost everywhere are related to:</p>
<ul>
<li><p>creating a Release</p>
</li>
<li><p>generating Release Notes for the release</p>
</li>
<li><p>and uploading any Artifacts for the release</p>
</li>
</ul>
<p>Well, I was doing the same until recently. Playing around with the <a target="_blank" href="https://github.com/cli/cli">GitHub CLI</a> I figured everything I mentioned above could be done with it effortlessly! ✨ Not to mention it is flexible and provides options to move things our way. ++ It's already installed on the GitHub Actions runner.</p>
<p>Enough talk, now let's see it in Action 🥁</p>
<h2 id="heading-creating-a-release">Creating a Release</h2>
<p>GitHub CLI provides a built-in sub-command to create a release. 🌟</p>
<p>For a basic example: we want to create a release whenever we push a tag 🏷️</p>
<pre><code class="lang-yaml"><span class="hljs-attr">steps:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">gh</span> <span class="hljs-string">release</span> <span class="hljs-string">create</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">-t</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span>
</code></pre>
<ul>
<li><p><code>gh release create &lt;tag&gt;</code> : create a new release with the latest tag pushed</p>
</li>
<li><p><code>-t &lt;tag&gt;</code>: Set title of the release; same as the pushed tag</p>
</li>
</ul>
<p>Apart from general releases, we can also create <strong>draft releases</strong> and <strong>prerelease</strong> with the following flags.</p>
<ul>
<li><p><code>-d</code> : draft release</p>
</li>
<li><p><code>-p</code>: prerelease</p>
</li>
</ul>
<h2 id="heading-generate-release-notes">Generate Release Notes</h2>
<p>Now this one is pretty easy. We just have to pass the <code>--generate_notes</code> flag to the above command and it will be generated for us.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">steps:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">gh</span> <span class="hljs-string">release</span> <span class="hljs-string">create</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">-t</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">--generate-notes</span>
</code></pre>
<p>If we want more flexibility then it also provides options to generate release notes from a file or from STDIN with the <code>-F</code> flag.</p>
<h2 id="heading-upload-artifacts-with-the-release">Upload artifacts with the Release</h2>
<p>Now this gets more interesting. You won't believe me if I say it.</p>
<p>To upload artifacts to our release all we have to do is pass the file or directory names to the same command!</p>
<p>I am taking an <a target="_blank" href="https://github.com/yankeexe/12ft-browser-extension/blob/main/.github/workflows/release.yaml">example of my repository</a> where I build browser extension artifacts for Chrome and Firefox:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">permissions:</span> <span class="hljs-string">write-all</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">GH</span> <span class="hljs-string">Release</span>
<span class="hljs-attr">steps:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Artifacts</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">./release.sh</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">gh</span> <span class="hljs-string">release</span> <span class="hljs-string">create</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">-t</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">12_ft_chrome.zip</span> <span class="hljs-string">12_ft_firefox.zip</span> <span class="hljs-string">--generate-notes</span>
</code></pre>
<ul>
<li><p><code>permissions: write-all</code>: For uploading artifacts, we will need to assign additional permissions to our <code>GITHUB_TOKEN</code>.</p>
</li>
<li><p><code>./release.sh</code>: Builds Release Artifacts</p>
</li>
<li><p><code>12_ft_&lt;browser&gt;.zip</code>: are the artifacts generated by the above script</p>
</li>
</ul>
<p>If we want a separate step to upload our artifacts, we can do so with the following change:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">permissions:</span> <span class="hljs-string">write-all</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">"Create GH Release"</span>
<span class="hljs-attr">steps:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"Build Artifacts"</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">./release.sh</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">gh</span> <span class="hljs-string">release</span> <span class="hljs-string">create</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">-t</span> <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">--generate-notes</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Artifact</span> <span class="hljs-string">to</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">run:</span> <span class="hljs-string">gh</span> <span class="hljs-string">release</span> <span class="hljs-string">upload</span>  <span class="hljs-string">${GITHUB_REF#refs/*/}</span> <span class="hljs-string">12_ft_chrome.zip</span> <span class="hljs-string">12_ft_firefox.zip</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We don't have to dabble around with multiple third-party actions, all of it can be done from a single command using GitHub CLI. This is more manageable, and effortless.</p>
<p>Hope this has been helpful! ✨</p>
<blockquote>
<p><a target="_blank" href="https://github.com/yankeexe/12ft-browser-extension/blob/main/.github/workflows/release.yaml"><strong>Full reference to the above Action YAML</strong></a> <strong>🚀</strong></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Streamlining EC2 Access with AWS Session Manager and Port Forwarding: An In-Depth Guide]]></title><description><![CDATA[EC2 is one of the fundamental services provided by AWS and used by many who come across the platform. This compute service has applications that extend far and wide depending on the use cases.
One of the basic tasks, we do with EC2 is SSH into it, to...]]></description><link>https://yankee.dev/ec2-session-manager-access-port-forwarding</link><guid isPermaLink="true">https://yankee.dev/ec2-session-manager-access-port-forwarding</guid><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Security]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sun, 15 Jan 2023 04:03:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673755163102/eca799cf-851d-431e-ae9c-b330f6967ae8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>EC2 is one of the fundamental services provided by AWS and used by many who come across the platform. This compute service has applications that extend far and wide depending on the use cases.</p>
<p>One of the basic tasks, we do with EC2 is SSH into it, to “do stuff”. This article revolves around the very topic of accessing the EC2 instance via SSH.</p>
<h1 id="heading-conventional-way">Conventional Way</h1>
<p><strong>A private Key</strong> is required whenever you want to establish a connection between your machine and the EC2 instance. It’s an accepted standard practice that has been used for many years to connect to a remote machine. But using a private key comes with its baggage.</p>
<p><strong>What if:</strong></p>
<ul>
<li><p>you lose your keys</p>
</li>
<li><p>delete your keys</p>
</li>
<li><p>someone steals your keys</p>
</li>
<li><p>you don’t know who has access to your keys (this bothers me)</p>
</li>
</ul>
<p>Every one of us is susceptible to the above scenarios. And it brings chaos, confusion, and vulnerability when you just want to perform your task.</p>
<p>Access to your keys means access to your machine. If you are distributing the keys to multiple team members or using multiple keys so that they can connect and perform some action, then it immediately becomes a problem. You are entrusting the access and security of your workload that spreads across different machines, regions and god knows where. Also, logging becomes a hassle to get visibility into what users are doing on the system.</p>
<p><strong>Also, SSH-ing in itself is a tedious task:</strong></p>
<ul>
<li><p>you have to assign an elastic IP address so you are always connecting to the same public IP address.</p>
</li>
<li><p>add a port for SSH to the Security Group and expose it to the world or a part of it.</p>
</li>
<li><p>enter a long command or configure ssh config</p>
</li>
<li><p>make sure private keys are in a secure location on your machine</p>
</li>
</ul>
<p>Example of a command for ssh-ing into an EC2 instance.</p>
<pre><code class="lang-bash">ssh -i <span class="hljs-string">"private_key.pem"</span> username@&lt;ip-address&gt;.&lt;region&gt;.compute.amazonaws.com
</code></pre>
<h1 id="heading-aws-session-manager-the-modern-secure-way">AWS Session Manager: The <s>modern</s> secure way</h1>
<p>Well, I am not the only one talking about all of the issues above. It’s faced by the development, DevOps, and security teams across different organizations. When it comes to security, AWS made sure it is providing the right set of tools, mostly an intuitive one to make all of the aforementioned issues go away.</p>
<p>AWS Session Manager is a one-stop service that should always be used with your EC2 instance. It immediately shows its benefits upfront:</p>
<ul>
<li><p>no need for private keys 🙌</p>
</li>
<li><p>provide access directly to IAM users with fine-grained policies 🔐</p>
</li>
<li><p>no need to expose SSH ports on Security Groups ✨</p>
</li>
<li><p>no need for an elastic IP address 🎉</p>
</li>
<li><p>centralized logging with direct access to CloudWatch, S3 🪵</p>
</li>
<li><p>and most importantly, an intuitive command to connect to an instance 🌟</p>
</li>
</ul>
<p>Example of a command for connecting to an EC2 instance.</p>
<pre><code class="lang-bash">aws ssm start-session --target &lt;instance-id&gt;
</code></pre>
<h2 id="heading-setting-up-session-manager-with-ec2">Setting up Session Manager with EC2</h2>
<p>Setting up Session Manager with EC2 is an effortless process. There is one extra step before we launch any new instances.</p>
<p><strong>Create a new Role that allows access from Session Manager to the EC2 instance.</strong></p>
<ol>
<li><p>Go to IAM → Roles</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942512316/a1ab53f4-6ca5-4b7a-ba36-f1db3281e326.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Select the <a target="_blank" href="https://aws.permissions.cloud/managedpolicies/AmazonSSMManagedInstanceCore"><code>AmazonSSMManagedInstanceCore</code></a> policy</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942552084/cca2246e-2557-4002-aac2-8c1a6a51379e.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Name it anything that makes sense, I will name it as <strong>SSMInstanceCore</strong>.</p>
</li>
</ol>
<p><strong>Now that we have a Role, let’s create a new EC2 instance and attach it to it.</strong></p>
<ol>
<li><p>Click on Launch Instances</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942587236/ed2af033-7f9f-4376-a4cb-69a2374df0e6.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Give your instance a relatable name.</p>
</li>
<li><p>I will choose the default Amazon Linux 2 AMI and Instance type (it selects the free tier by default).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942618888/a9e3ab49-0a1e-4138-ac9b-1846c67d4c61.png" alt /></p>
</li>
<li><p>Finally, in the Key Pair section, choose <code>Proceed without a key pair</code> ⚡️</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942660101/8998c9dd-35e5-45ab-9d78-f53850ba6efd.png" alt /></p>
</li>
<li><p>On <code>Network Settings</code> you can name your security group and keep it empty.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942667476/c4876bb6-bec5-44f5-9a7f-5360f7e6dd0b.png" alt /></p>
</li>
<li><p>Make sure <code>Auto-assign public IP</code> is set to enabled inside of Network settings, for Session Manager to connect to it.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942682913/58f12eaa-6b56-4230-ae9f-bbf4ee7383fb.png" alt /></p>
</li>
<li><p>I will leave the storage as default.</p>
</li>
<li><p>On <code>Advanced Settings</code> → IAM Instance Profile: Select the Role we created in the earlier section. I named it <strong>SSMInstanceCore.</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942699160/a600f84c-5a1e-458c-997f-cdf15b4939d2.png" alt /></p>
</li>
<li><p>Select <code>Launch Instance</code> 🚀</p>
</li>
</ol>
<h2 id="heading-setting-up-permissions-to-access-via-session-manager">Setting up permissions to access via Session Manager</h2>
<p>The minimum policy configuration we’ll for an IAM user to be able to connect to an EC2 instance is as follows:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"ConnectToSSMInstance"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"ssm:TerminateSession"</span>,
                <span class="hljs-string">"ssm:StartSession"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: [
                <span class="hljs-string">"arn:aws:ec2:*:*:instance/i-010a4c6d9cb6223e3"</span>,
                <span class="hljs-string">"arn:aws:ssm:ap-southeast-1:*:session/*"</span>
            ]
        }
    ]
}
</code></pre>
<p>You can make it more secure by tightening up this policy. But we'll leave it like above.</p>
<p>Once you attach this policy to a user, they’ll be able to connect to the defined EC2 instance using Session Manager.</p>
<hr />
<blockquote>
<p><strong>NOTE</strong>: Installing SSM Agent on your Instance if it's not pre-installed</p>
</blockquote>
<p>Before we move further, we need to make sure our EC2 instance has the SSM agent installed. <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ami-preinstalled-agent.html">This list provides a list of AMIs</a> that has the agent pre-installed. If you are using an instance that does not have an SSM agent, <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html">follow the docs</a> and follow the instructions on manually installing the agent for <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install.html">Linux</a>, <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-win.html">Windows</a> or <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install-macos2.html">Mac</a>.</p>
<h2 id="heading-accessing-ec2-instance-using-session-manager">Accessing EC2 Instance using Session Manager</h2>
<p>You can access EC2 Instance using Session Manager, in 3 different ways:</p>
<ol>
<li><p><strong>From the EC2 instance connect option</strong></p>
<p> This will redirect the user to the Session Manager console and start the session from there, but we’ll see how to connect from the Session Manager console after this.</p>
<ol>
<li><p>Select your EC2 instance and click on connect.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942719297/2d30d75f-b877-4258-a22b-f8aee077b275.png" alt /></p>
</li>
<li><p>Select Session Manager and Click on Connect</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942735162/50797ac5-6516-4c8e-8ecb-38c21d65ab1d.png" alt /></p>
</li>
<li><p>You can start using your instance</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942753271/1e134a81-c20b-4e08-af56-88b27755e4de.png" alt /></p>
</li>
</ol>
</li>
<li><p><strong>Connect from Session Manager</strong></p>
<ol>
<li><p>Go to Systems Manager and Select <strong>Session Manager</strong> on the left-hand side menu.</p>
</li>
<li><p>Click on Start Session</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942767801/3abf4094-9349-4972-92bd-6d1ed559ac87.png" alt /></p>
</li>
<li><p>Select the name of the instance you want to connect to and select <strong>Start Session</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942785223/7648b8a7-4c51-4c84-8f45-bbef80b0f8e0.png" alt /></p>
</li>
<li><p>We’ll get the same terminal window, we got from the first option.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673942800262/af1631ec-c8f5-49b5-a433-95ae27378fe3.png" alt /></p>
</li>
</ol>
</li>
</ol>
<p><strong>Connect from Terminal</strong></p>
<p>Probably my favorite way of connecting to the EC2 instance with the Session Manager is via terminal. For this, you need to have <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI installed on our machine</a> and <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html">configured</a>. After that, you need to install the <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html">Session Manager plugin</a> for the CLI.</p>
<p>After that, anytime we want to connect to our instance, we can run the following command:</p>
<pre><code class="lang-bash">aws ssm start-session --target &lt;instance-id&gt;

aws ssm start-session --target i-010a4c6d9cb6223e3
</code></pre>
<p>You’ll get a similar screen as below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673528399403/e4ac3d9f-e7c0-4164-9f9b-6cfce6601b32.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-setting-up-logging">Setting Up Logging</h2>
<p>One of the benefits of Session Manager is centralized <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-logging.html">logging</a>. You can have multiple users doing different things on the instance, and you can track all of that in a single log group in CloudWatch or you can upload it to an S3 bucket. In this blog, we'll be <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-logging.html#session-manager-logging-cwl-streaming">sending it to CloudWatch</a>.</p>
<p>Before we dive into logging, we need to update our Role <strong>SSMInstanceCore</strong> and attach the <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/iam-identity-based-access-control-cw.html#managed-policies-cloudwatch-CloudWatchAgentServerPolicy"><strong>CloudWatchAgentServerPolicy</strong></a>. This will allow the EC2 instance to send logs to the CloudWatch log group.</p>
<h3 id="heading-setup-permission-to-send-logs-to-cloudwatch">Setup Permission to send logs to Cloudwatch</h3>
<ol>
<li><p>Go to IAM -&gt; Roles, Search for the role we created earlier(if you are following the blog: it is <strong>SSMInstanceCore</strong>) and select <strong>Attach policies</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673534230770/674392f8-2d96-465b-af98-1a81f317f84a.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>On the search bar, paste <strong>CloudWatchAgentServerPolicy</strong> and select it and click on <strong>Attach Policies</strong>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673534474554/2b903d75-c710-491b-b1b6-fcdbd80b09f5.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-create-a-new-log-group">Create a new Log Group</h3>
<p> Before we can send any logs, we need to create a new log group to hold our logs. Run the command below to create a new log group:</p>
<pre><code class="lang-bash"> aws logs create-log-group --log-group-name SSMInstanceLogs
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673703266798/774442fb-044e-4fcb-a21e-2b66461036d9.png" alt="Command to create a new AWS CloudWatch log group" class="image--center mx-auto" /></p>
<p> Note: that all the session logs for any EC2 instance will be forwarded to this log group. You can't assign a log group per instance. The best thing is the logs will be JSON formatted so they'll be easy to read.</p>
<h3 id="heading-send-session-logs-to-cloudwatch">Send Session Logs to CloudWatch</h3>
<p> Now that our EC2 instance has permission to write to CloudWatch, we can go to Session Manager under Systems Manager.</p>
<ol>
<li><p>Select the "Preferences" tab and Click on "Edit"</p>
</li>
<li><p>Enable CloudWatch logging and select "Stream session logs"</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673703765657/12f9fcef-4731-48b3-885b-e3bfb1e7dce4.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Then add the name of the CloudWatch Log group we created earlier.</p>
</li>
<li><p>Our log group is not encrypted with Customer Managed Keys (CMK) so we do not enable the "Enforce encryption" option.<br /> <em>But a side note, CloudWatch logs are always encrypted using AWS-managed keys.</em></p>
</li>
<li><p>Click on Save</p>
<h3 id="heading-viewing-logs">Viewing Logs</h3>
<p> Now that we have logs enabled for Session Manager, let's jump into our EC2 instance and run some commands.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673704220550/f8584f98-002e-470e-a054-87535e588e18.png" alt class="image--center mx-auto" /></p>
<p> Now let's had to our Log Group and check our log stream:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673704443746/b93517a6-91b0-4c78-ad4b-3a8718ac50fe.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
</ol>
<h2 id="heading-port-forwarding">Port Forwarding</h2>
<h3 id="heading-managing-access-from-ec2-to-private-rds">Managing Access from EC2 to private RDS</h3>
<p>Another common use case of an EC2 instance is to use it as a bastion host (jump server) to connect to resources on a private subnet securely.</p>
<p>Let's say there's an RDS Postgres instance on a private subnet running on port 5432 that we'd like to connect to. RDS has no access to the internet and is accessible only by the services within that VPC.</p>
<p>To connect to this database service we need to:</p>
<ol>
<li><p>create an EC2 instance in that VPC</p>
</li>
<li><p>go to the Security Group attached to the RDS instance and, select "Edit inbound rules" and add a new inbound rule with:</p>
<ul>
<li><p>type as PostgreSQL</p>
</li>
<li><p>Source as the Security Group name of our EC2 instance.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673711705796/6a629a38-7964-488c-a296-e0f6f1ca3aa2.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p>Then Click on Save.</p>
</li>
</ol>
<p>That's it! Now our EC2 instance has access to RDS. Now we can use our EC2 instance as a bridge to connect our local machine to the RDS instance.</p>
<h3 id="heading-connecting-to-private-rds">Connecting to private RDS</h3>
<p>Session manager makes it easy to forward port from our EC2 instance with the use of <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html"><strong>Documents</strong></a><strong>.</strong> Documents are blueprints of action that the Session manager performs on our EC2 instances. AWS manages hundreds of these documents for common actions and they have one for Port Forwarding too.</p>
<p>To view what documents are available. Go to System Managers, and on the left-hand menu you can find "Documents" under "Shared Resources".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673707441495/3cc49234-719b-4011-a1c7-50e7cbac170a.png" alt /></p>
<p>The document we are going to use is titled <code>AWS-StartPortForwardingSessionToRemoteHost</code>. You can search for it in the Documents section to get its details. Click on the result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673707600311/62e8325a-2744-4175-8b78-b5fbac2cb9e5.png" alt class="image--center mx-auto" /></p>
<p>Go to the <code>Details</code> tab and under <code>Parameters</code> section you can find what parameters we can pass to this document. Parameters are passed as a key-value pair.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673707747251/28aea461-b6aa-4afb-bb91-976a5c660683.png" alt class="image--center mx-auto" /></p>
<p>To use this document, we need to update the policy we created above for users:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"ConnectToSSMInstance"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"ssm:TerminateSession"</span>,
                <span class="hljs-string">"ssm:StartSession"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: [
                <span class="hljs-string">"arn:aws:ec2:*:*:instance/i-010a4c6d9cb6223e3"</span>,
                <span class="hljs-string">"arn:aws:ssm:ap-southeast-1:*:session/*"</span>,
                <span class="hljs-string">"arn:aws:ssm:*:*:document/AWS-StartPortForwardingSessionToRemoteHost"</span>,
            ]
        }
    ]
}
</code></pre>
<hr />
<p>Now the only thing we need to grab is the database host URL.</p>
<p>You can go to the RDS instance and on the "Connectivity &amp; Security" tab find "Endpoint &amp; port". Now we have the parameters gathered for port forwarding let's see it in action.</p>
<pre><code class="lang-bash">aws ssm start-session --target i-010a4c6d9cb6223e3\
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters <span class="hljs-string">'{"portNumber":["5432"],"localPortNumber":["5432"],"host":["testdatabase.csrc3be2awyi.ap-southeast-1.rds.amazonaws.com"]}'</span>
</code></pre>
<p>I am port forwarding from RDS on a private subnet to my local machine on port 5432. Since I am using Postgres I can connect to it using the following command:</p>
<pre><code class="lang-bash">psql -U postgres -h localhost -p 5432
</code></pre>
<p><img src="https://i.imgur.com/dvy6xNx.gif" alt class="image--center mx-auto" /></p>
<p>That's it! Now you have a connection to a private RDS instance from your EC2 instance using session manager. ✨</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Session Manager is a powerful tool from a security and usability perspective. There are many actions you can do with it on an EC2 instance and <a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html">having the power of Documents</a> makes it more resourceful.</p>
<hr />
<p>If you have any suggestions or queries about this article, you can reach out to me on <a target="_blank" href="https://twitter.com/yankexe">Twitter</a> or add a comment below. 🎉</p>
]]></content:encoded></item><item><title><![CDATA[Build CLI blazingly fast with python-fire 🔥]]></title><description><![CDATA[Command line applications are developers best friend. Want to get something done quickly? Just a few keystrokes and you already have what you are looking for.
Python is the first language many developers pick if they need to hack together something q...]]></description><link>https://yankee.dev/build-cli-blazingly-fast-with-python-fire</link><guid isPermaLink="true">https://yankee.dev/build-cli-blazingly-fast-with-python-fire</guid><category><![CDATA[Python]]></category><category><![CDATA[cli]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Python 3]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Wed, 26 Oct 2022 14:55:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666795860793/65qLaDMZg.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Command line applications are developers best friend. Want to get something done quickly? Just a few keystrokes and you already have what you are looking for.</p>
<p>Python is the first language many developers pick if they need to hack together something quickly. But what we scrap together is not a CLI in its entirety most of the time, you need to manage flags, parse the arguments, chain sub-commands, and many more which is a hassle, thus results in multiple small  and unmanaged scripts.</p>
<p>In todays article we are going to put an end to this and see how we can build reasonably feature rich CLI in mere minutes without any fancy decorators or anything.</p>
<h3 id="heading-create-and-activate-a-virtual-environment">Create and activate a  virtual environment</h3>
<pre><code class="lang-bash">python -m venv venv

<span class="hljs-built_in">source</span> venv/bin/activate

<span class="hljs-comment"># Install python-fire 🔥</span>
pip install fire
</code></pre>
<h3 id="heading-your-first-sub-command">Your first sub-command</h3>
<p>Our CLI application is going to be an aggregation of  bunch of tools, so we will just call it tools CLI.</p>
<p>With <a target="_blank" href="https://github.com/google/python-fire">python-fire</a> you can use either function or class to create your subcommands. But I find working with classes more intuitive and manageable. Our first command is going to be a sub-command that shows us the UTC time.</p>
<p>We will create a new method <code>utc()</code> which will be our sub-command and we have an argument called <code>pretty</code> which will be the <strong>flag</strong> for our sub-command which prints UTC date time in more human readable format. <strong>This argument already has a default value so this is not a required flag.</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># tools.py</span>

<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">import</span> fire

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tools</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">utc</span>(<span class="hljs-params">self, pretty: bool = False</span>):</span>
        <span class="hljs-string">"""
        Get UTC date time
        """</span>
        utc_time = datetime.utcnow()

        <span class="hljs-keyword">if</span> pretty:
            <span class="hljs-comment">## strftime format codes:</span>
            <span class="hljs-comment"># https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes</span>
            print(utc_time.strftime(<span class="hljs-string">"%B %d :: %H:%M %p"</span>))
        <span class="hljs-keyword">else</span>:
            print(utc_time)
</code></pre>
<p>Next we need to run this file as a script so at the end of our file we need to add the following:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    fire.Fire(Tools)
</code></pre>
<p>Now we have our CLI ready! Let’s run it!!</p>
<pre><code class="lang-bash">python tools.py utc
python tools.py utc --pretty

<span class="hljs-comment"># For help message</span>
python tools.py

<span class="hljs-comment"># For sub-command help message</span>
python tools.py utc --<span class="hljs-built_in">help</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666793993291/U3uIeL7gb.gif" alt="Basic.gif" /></p>
<p>It’s a bit overwhelming to type through all of these overheads <code>python tools.py &lt;command&gt;</code>; well it’s not even like a CLI I was hoping for 🤷‍♂️.
You might have questions like:</p>
<ul>
<li>How can I invoke it from any location I want?</li>
<li>I want to name it to something I find intuitive, how do I do that?</li>
</ul>
<p>Well for that you would want to create a distribution for it.</p>
<h3 id="heading-package-for-command-line">Package for Command Line 📦</h3>
<p>First we need to revamp our program a little to accommodate packaging:</p>
<pre><code class="lang-python"><span class="hljs-comment"># REMOVE THIS CHUNK</span>
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    fire.Fire(Tools)

<span class="hljs-comment"># ADD THIS CHUNK</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>():</span>
    fire.Fire(Tools)
</code></pre>
<p>Now let’s create a <code>setup.py</code> file to manage our packaging/distribution. You can use this file as a reference to create your own CLI:</p>
<pre><code class="lang-python"><span class="hljs-comment"># setup.py</span>
<span class="hljs-string">"""Package setup"""</span>
<span class="hljs-keyword">import</span> setuptools

<span class="hljs-comment"># Development Requirements</span>
requirements_dev = [<span class="hljs-string">"pytest"</span>, <span class="hljs-string">"black"</span>, <span class="hljs-string">"mypy"</span>, <span class="hljs-string">"flake8"</span>, <span class="hljs-string">"isort"</span>]

setuptools.setup(
    name=<span class="hljs-string">"tools_cli"</span>,
    version=<span class="hljs-string">"0.0.1"</span>,
    author=<span class="hljs-string">"Yankee Maharjan"</span>,
    url=<span class="hljs-string">"https://yankee.dev/build-cli-blazingly-fast-with-python-fire"</span>,
    description=<span class="hljs-string">"Collection of handy tools using CLI"</span>,
    license=<span class="hljs-string">"MIT"</span>,
    packages=setuptools.find_packages(exclude=[<span class="hljs-string">"dist"</span>, <span class="hljs-string">"build"</span>, <span class="hljs-string">"*.egg-info"</span>, <span class="hljs-string">"tests"</span>]),
    install_requires=[<span class="hljs-string">"fire"</span>],
    extras_require={<span class="hljs-string">"dev"</span>: requirements_dev},
    entry_points={<span class="hljs-string">"console_scripts"</span>: [<span class="hljs-string">"to = tools:run"</span>]},
)
</code></pre>
<p>The line you need to focus here is the <code>entry_points</code>, which describes the entry point to our program as a console script.</p>
<pre><code class="lang-python">entry_points={<span class="hljs-string">"console_scripts"</span>: [<span class="hljs-string">"to = tools:run"</span>]},
</code></pre>
<p>Here <code>to</code> is the name of our CLI, you can name it to anything you like. If you want to name it to <code>brr</code> it will go like this:</p>
<pre><code class="lang-python">entry_points={<span class="hljs-string">"console_scripts"</span>: [<span class="hljs-string">"brr = tools:run"</span>]},
</code></pre>
<p><code>tools:run</code> represent the name of our module followed by the function it needs to run. Console Scripts always require a function to run hence the modification we did earlier.</p>
<h3 id="heading-feels-like-a-cli">Feels like a CLI 💆‍♂️</h3>
<p>Now let’s install our CLI in an editable mode within our virtual environment. This is like hot reloading for your CLI, whatever changes you make is reflected instantly.</p>
<p>Inside your project directory run the following command.</p>
<pre><code class="lang-bash">pip install -e .
</code></pre>
<p>Now you can use your CLI using the command <code>to</code> or whatever you put on the <strong>console_scripts</strong></p>
<pre><code class="lang-bash">to utc
to utc --pretty
to utc --<span class="hljs-built_in">help</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666794561287/__mkCxFHB.gif" alt="desired_command.gif" /></p>
<p>This is pretty neat!</p>
<p>Now how do I make sure I can run it from any location I want?</p>
<ul>
<li><p>Deactivate your virtual environment:</p>
<pre><code class="lang-bash">  deactivate
</code></pre>
</li>
<li><p>Install the project in editable mode again on your global site-packages:</p>
<pre><code class="lang-bash">  pip install -e .
</code></pre>
</li>
</ul>
<p>Now this is done, you will have your CLI accessible throughout the system. But note that if you make any changes to the main CLI logic, it will be reflected instantly.</p>
<h3 id="heading-bonus-nested-commands">Bonus: Nested Commands ➿</h3>
<p>If you have made it this far, then you are set to make your own CLI and get done with most of the use cases. But if you want to see some more then stick around for a bit.</p>
<p>Let’s add other commands to our tool, first a sub-command called <code>leap()</code> that validates if the given year is leap or not, lastly a sub-command called <code>pw()</code> to generate a strong password.</p>
<pre><code class="lang-python">...
<span class="hljs-keyword">import</span> calendar
<span class="hljs-keyword">import</span> string
<span class="hljs-keyword">import</span> secrets

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tools</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">utc</span>(<span class="hljs-params">self, ...</span>):</span>
        ...

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">leap</span>(<span class="hljs-params">self, year:int</span>):</span> <span class="hljs-comment"># required: since no default value here</span>
        <span class="hljs-string">"""
        Check if given year is leap or not
        """</span>
        print(calendar.isleap(year))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pw</span>(<span class="hljs-params">self, len: int = <span class="hljs-number">16</span></span>):</span>
        <span class="hljs-string">"""
        Generate strong password
        """</span>
        alphabet = string.ascii_letters + string.digits + string.punctuation
        pwd_length = len

        pwd = <span class="hljs-string">""</span>
        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(pwd_length):
            pwd += <span class="hljs-string">""</span>.join(secrets.choice(alphabet))

        print(pwd)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>():</span>
    fire.Fire(Tools)
</code></pre>
<p>Now run the commands</p>
<pre><code class="lang-python">to leap <span class="hljs-number">2022</span>
to pw
to pw --len <span class="hljs-number">22</span>
to pw <span class="hljs-number">25</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666794956744/cu51uuqji.gif" alt="leap__pw.gif" /></p>
<p>Sometimes there are related commands that you want to group together, like in our case we can group <code>utc</code> and <code>leap</code> under something like <code>datetime</code> or <code>dt</code> for short. Basically what we want to do here is nested commands.</p>
<p>Let’s group our commands. We will move our <code>leap()</code> and <code>utc()</code> method inside of a new class called <code>DateTime</code>.</p>
<pre><code class="lang-python">...

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DateTime</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">utc</span>(<span class="hljs-params">self, pretty: bool = False</span>):</span>
        <span class="hljs-string">"""
        Get UTC time
        """</span>

        <span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime

        utc_time = datetime.utcnow()

        <span class="hljs-keyword">if</span> pretty:
            print(utc_time.strftime(<span class="hljs-string">"%B %d :: %H:%M %p"</span>))
        <span class="hljs-keyword">else</span>:
            print(utc_time)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">leap</span>(<span class="hljs-params">self, year:int</span>):</span> <span class="hljs-comment"># required: since no default value here</span>
        <span class="hljs-string">"""
        Check if given year is leap or not
        """</span>
        print(calendar.isleap(year))

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tools</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.dt = DateTime()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pw</span>(<span class="hljs-params">self, ...</span>):</span>
        ...
</code></pre>
<p>Sub-command is determined by whatever we put as variable name when instantiating the new <code>DateTime</code> class. Here we have named it as <code>dt</code>, but you can name it as <code>datetime</code>, <code>dtt</code> or whatever you want. </p>
<p>Now, we have more organized sub-commands for our CLI. If you want to run commands that are related to date time you can do so using <code>to dt &lt;command-name&gt;</code> ; for example:</p>
<pre><code class="lang-python">to dt utc
to dt leap <span class="hljs-number">2025</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666795231838/nPbOSfz_g.gif" alt="group_commands.gif" /></p>
<p>Password command will be normal:</p>
<pre><code class="lang-python">to pw
to pw --len <span class="hljs-number">30</span>
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Using python-fire makes the process of creating CLIs really easy and intuitive because you are using nothing but Python functions and classes. I hope this mini rundown of the tool and how to package it for your daily use has been helpful.</p>
]]></content:encoded></item><item><title><![CDATA[Docker tips for better Developer Experience]]></title><description><![CDATA[Docker is an indispensable tool for development. Knowing your way around it makes you more productive -- getting things done faster. This blog incorporates some of the Docker know-hows (updated frequently) that will help you to get the best experienc...]]></description><link>https://yankee.dev/docker-tips-for-better-developer-experience</link><guid isPermaLink="true">https://yankee.dev/docker-tips-for-better-developer-experience</guid><category><![CDATA[Docker]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Wed, 10 Aug 2022 02:58:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660099091459/n7wM8U1yo.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Docker is an indispensable tool for development. Knowing your way around it makes you more productive -- getting things done faster. This blog incorporates some of the Docker know-hows (<em>updated frequently</em>) that will help you to get the best experience from Docker development.</p>
<blockquote>
<p>Last Updated: Oct 20, 2023</p>
</blockquote>
<h2 id="heading-overwriting-entrypoint">Overwriting Entrypoint</h2>
<p>Whenever you use the <code>docker run</code> command the first process that runs inside of your container is determined by the <code>ENTRYPOINT</code> command.</p>
<p>The most common thing you want to do with a container is to attach a pseudo-terminal to it. We do it using:</p>
<pre><code class="lang-bash">docker run -it &lt;image-name&gt;  &lt;shell-name&gt; 
docker run -it ubuntu:latest bash
</code></pre>
<p>Now if you are using some images like the official AWS python image, you can’t actually do it by default.</p>
<pre><code class="lang-bash">docker run -it amazon/aws-lambda-python sh
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660014080503/QtA5AuH41.png" alt="image.png" /></p>
<p>You get some generic info related to lambdas like your current working directory and handler but no sign of a terminal 🤔. If you <a target="_blank" href="https://hub.docker.com/layers/aws-lambda-python/amazon/aws-lambda-python/latest/images/sha256-1f11684c63f4cf4c221ee5a336e55912f9fbe695469354f2afb4211aef448bff?context=explore">dig into the image layers</a> we can see that at the end of the Dockerfile there’s an <code>ENTRYPOINT</code> command that runs a shell script.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660014416448/LqXg12Npr.png" alt="image.png" /></p>
<p>Docker is pretty flexible in how we want to make it work. So we can overwrite the default <code>ENTRYPOINT</code> which can be any valid Linux command and attach a pseudo-terminal.</p>
<pre><code class="lang-bash">docker run --rm -it --entrypoint sh amazon/aws-lambda-python
</code></pre>
<h2 id="heading-create-images-from-running-containers">Create Images from running containers</h2>
<p>Docker offers the ability to export a new Image from a running container. This can be handy when you quickly need whatever you're running in a container as an image that can be distributed for debugging or playing around. Note that, this will not export data from any volume attached to the container.</p>
<h3 id="heading-example">Example :</h3>
<p>Run a base Ubuntu container:</p>
<pre><code class="lang-bash">docker run --rm -it --name ubuntu-base ubuntu:latest bash
</code></pre>
<p>Install a few tools that you always require, perhaps Vim, cURL, iputils-ping, to name a few.</p>
<p>Inside the container:</p>
<pre><code class="lang-bash">apt update &amp;&amp; apt install -y vim curl iputils-ping
</code></pre>
<p>Now on your host terminal, you can commit the contents of the running container and create a new image:</p>
<pre><code class="lang-bash">docker commit ubuntu-base ubuntu:tools
</code></pre>
<p>Now if we run the image <code>ubuntu:tools</code>, we'll have the tools like vim, cURL already installed to work with.</p>
<pre><code class="lang-bash">docker run --rm -it ubuntu:tools bash
</code></pre>
<hr />
<p>With the <code>--change</code> or <code>-c</code> flag we can also overwrite Dockerfile instructions.</p>
<p>Let's say we want to overwrite <code>ENTRYPOINT</code> and <code>CMD</code> of ubuntu:tools and create a new image that pings a certain URL.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a container </span>
 docker create --name ubuntu-tools ubuntu:tools

<span class="hljs-comment"># Commit a new image overwriting ENTRYPOINT and CMD</span>
 docker commit -c <span class="hljs-string">'ENTRYPOINT ["ping"]'</span> -c <span class="hljs-string">'CMD ["google.com"]'</span> ubuntu-tools ubuntu:ping
</code></pre>
<p>Now if we run our new image, it'll be running <code>ping</code> command by default.</p>
<p>The best part about it is, that you won't require a Dockerfile to do any of this. But keep note that, this is intended to be used for quick debugging and not for production usage.</p>
<h2 id="heading-one-off-containers">One-off containers</h2>
<p>Sometimes we just want to run the container for a single purpose, and don’t need it in our system. These can be cases where we want to attach a terminal inside an image and check for contents or debug something, run a container for testing purposes, and all the other things that might float your boat.</p>
<p>For that kind of need, docker ships with the <code>--rm</code> command, when you run your container with it, it immediately removes the container instead of putting it in a <strong>stop</strong> state.</p>
<p>By default, when you stop a container, it is in a dormant state but occupies space on your hard drive. You can use <code>-a</code> flag to view all your stopped containers.</p>
<pre><code class="lang-bash">docker ps -a
</code></pre>
<p>To remove them from your system you’d have to use:</p>
<pre><code class="lang-bash">docker rm &lt;container-id&gt;/&lt;container-name&gt;
</code></pre>
<p><strong>For example</strong>: I use these one-off containers to test out the new features from the latest release of Python by opening a Python Shell from the container.</p>
<pre><code class="lang-bash">docker run --rm -it python:3.11.0b4-alpine3.16 python
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096223525/WiwnI5Bkr.gif" alt="One Off Container.gif" /></p>
<h2 id="heading-command-substitution">Command Substitution</h2>
<p>Docker has some filters and flags that you can use to perform an iterative task.</p>
<p>Common tasks like creating new versions of the image with the same name and tag can result in dangling images. The command below shows us a list of all the dangling images in the system eating our disk space.</p>
<pre><code class="lang-bash">docker images --filter dangling=<span class="hljs-literal">true</span>
</code></pre>
<p>Now we want to remove all of them to clear up some storage! It’s not productive to remove them one by one using</p>
<pre><code class="lang-bash">docker rmi &lt;image-name or id&gt;
</code></pre>
<p>With <code>-q</code> flag we can list only the ids of the images and pass its output to the <code>remove</code> command using <strong>command substitution</strong> (supported by most SHELL)</p>
<pre><code class="lang-bash">docker rmi $(docker images -q --filter dangling=<span class="hljs-literal">true</span>)
</code></pre>
<p>NOTE: If your SHELL supports it, you can replace <code>$(...)</code> with back-ticks:</p>
<pre><code class="lang-bash">docker rmi `docker images -q --filter dangling=<span class="hljs-literal">true</span>`
</code></pre>
<h2 id="heading-software-bill-of-materials-sbom">Software Bill of Materials (SBOM)</h2>
<p>If you are shipping images that are used by multiple teams or are public for use then it is essential that you publish the Software Bill of Materials (SBOM) along with your images. Software Bill of Materials provides a transparent overview of the packages that make up your final image. These can be your OS system packages, or language-specific packages, along with their version. It makes it easy for teams and end-users to identify packages on our image that are vulnerable with simple search or vulnerability scanners like <a target="_blank" href="https://github.com/anchore/grype">grype</a>.</p>
<p>There are two ways you can go about generating SBOMs for your Docker image:</p>
<ol>
<li><p>Docker introduced native support for generating SBOM in Docker Desktop 4.7.0. You can also install it as a plugin.</p>
<pre><code class="lang-bash"> curl -sSfL https://raw.githubusercontent.com/docker/sbom-cli-plugin/main/install.sh | sh -s --
</code></pre>
<p> Note: that the plugin is in the <strong>Experimental</strong> stage and things are subject to change rapidly.</p>
</li>
<li><p>You can use the <a target="_blank" href="https://github.com/anchore/syft#installation">syft command-line tool</a> that Docker uses internally.</p>
</li>
</ol>
<h4 id="heading-generating-sbom">Generating SBOM</h4>
<p>You can generate SBOM in two widely used standard formats <a target="_blank" href="https://spdx.dev/">SPDX</a> and <a target="_blank" href="https://cyclonedx.org/">CycloneDX</a> or take the plain JSON route if you aren’t sure of the mentioned formats. Plain JSON has a dump of all the information <code>syft</code> can find in your image.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Docker CLI</span>
docker sbom &lt;image-name&gt; -o &lt;image-name.sbom.json&gt;
docker sbom ubuntu:latest -o ubuntu.sbom.json

<span class="hljs-comment"># Syft CLI</span>
syft &lt;image-name&gt; -o json=&lt;image-name.sbom.json&gt;
syft ubuntu:latest -o json=ubuntu.sbom.json
</code></pre>
<h4 id="heading-vulnerability-scanning">Vulnerability Scanning</h4>
<p>Once you have the SBOM, the next step is to scan for vulnerabilities. One of the profound tools for this is <a target="_blank" href="https://github.com/anchore/grype">grype</a>. You can pass the SBOM JSON generated into grype to list the vulnerabilities. It will include the installed version, fixed version, and severity of the packages scanned.</p>
<pre><code class="lang-bash">grype ubuntu.sbom.json
</code></pre>
<p>If you want to go further you can add an <a target="_blank" href="https://github.com/anchore/syft#adding-an-sbom-to-an-image-as-an-attestation-using-syft">SBOM to an image as an attestation using Syft and Cosign.</a></p>
<p>There are multiple tools to scan for vulnerabilities in Docker images with/without the need for SBOM, you can find these tools listed <a target="_blank" href="https://geekflare.com/container-security-scanners/">in this article by geekflare</a>.</p>
<h3 id="heading-always-be-scanning">Always be scanning</h3>
<p>With the introduction of Docker extensions on Docker Desktop scanning for vulnerability in your images has never been easier.</p>
<p>Open your Docker Desktop and go to extensions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096379707/Z8o9cG28M.png" alt="image.png" /></p>
<p>From there, you can download <a target="_blank" href="https://www.notion.so/Blogs-db72e383211b4dc499714e6000e36221">Snyk</a> and <a target="_blank" href="https://www.notion.so/Blogs-db72e383211b4dc499714e6000e36221">Trivy</a>. You can download their CLI's if you are not using Docker Desktop.</p>
<p>Go to one of its dashboards, select the image to scan and you’ll be provided with a list of vulnerabilities in your image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096396471/m3a3YNjWG.png" alt="image.png" /></p>
<h2 id="heading-cross-platform-images">Cross-platform Images</h2>
<p>When you want to distribute images among your team; you have to make sure that it runs on all of the machines. That’s why we are using Docker in the first place, right?</p>
<p>Although Docker makes it quite easy to package your application and move around in any environment; we do have to make sure that the underlying CPU architecture supports the image we have packaged. Images are tied to the CPU architecture where it is built and it specified on the <a target="_blank" href="https://github.com/opencontainers/image-spec/blob/main/image-index.md#image-index-property-descriptions">OCI Image Index properties</a>. To view this, you can inspect your any image on your machine:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096645624/Fb-crqPb3.gif" alt="arch.gif" /></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Search for Architecture</span>
docker image inspect busybox:latest | less
</code></pre>
<p>To view all the available CPU architecture for a particular image, you can run the following command:</p>
<pre><code class="lang-bash">docker buildx imagetools inspect busybox:latest
</code></pre>
<p>Best practice is to build these multi-platform images on your CI pipelines; for this tutorial, we will be doing it on our local machine. After we build the multi-arch images, we need to push them to the Docker Hub since the Docker engine registry only supports single CPU architecture. For this you need to authenticate your Docker CLI for Docker Hub before you proceed to the next section.</p>
<p>**Create a new builder: **</p>
<pre><code class="lang-bash">docker buildx create --name multi --use
</code></pre>
<p><strong>Create a new test Dockerfile</strong></p>
<pre><code class="lang-bash">FROM alpine:latest
CMD <span class="hljs-built_in">echo</span> “Running on $(uname -a)”
</code></pre>
<p><strong>Build and push to Docker Hub</strong></p>
<p>To create multi-arch images we use the <code>--platforms</code> flag and specify which architecture we want to build against.</p>
<pre><code class="lang-bash">docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t yankexe/host-info --push .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096751437/qJleCLLv_.png" alt="image.png" /></p>
<p><strong>View multi-arch images in Docker Hub</strong></p>
<p>In DockerHub, we have images that can be used against multiple platforms. Now, if anyone pulls our image from the Hub, Docker CLI will fetch the one specific to their CPU architecture.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660096769512/Z-XJT3_1W.png" alt="image.png" /></p>
<h2 id="heading-connecting-to-the-host-machine">Connecting to the host machine</h2>
<p>Publishing port using <code>-p</code> or <code>—port</code> flags to make services available outside of Docker (host machine) is common in everyone's development workflow. But what if you want to connect to a service running on your host machine from inside of the container?</p>
<p>For this, Docker has a built-in DNS name: <code>host.docker.internal</code> which resolves to the host machine IP address. Note that, this is only available on Docker for Mac and Windows.</p>
<p>If you have a database instance running on your host machine at port <code>:5432</code> then you can access it from the application inside your container using <code>host.docker.internal:5432</code></p>
<h3 id="heading-demo">Demo</h3>
<p>Create a simple flask server: <a target="_blank" href="http://app.py">app.py</a> on your host machine.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)

<span class="hljs-meta">@app.route("/")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello world"</span>
</code></pre>
<p>Run the server:</p>
<pre><code class="lang-bash">flask --app app --debug run
</code></pre>
<p>Get inside an one-off container:</p>
<pre><code class="lang-bash">docker run --rm -it ubuntu bash
</code></pre>
<p>Make a request from inside of the container to the host machine.</p>
<pre><code class="lang-bash">apt update; apt install curl -y
curl host.docker.internal:5000
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660097348170/RHI_5-mmn.png" alt="image.png" /></p>
<blockquote>
<p>Thank you for reading. If you have any suggestions or feedback feel free to reach out to me on <a target="_blank" href="https://twitter.com/yankexe">Twitter</a>.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[6 Tools to Run Kubernetes Locally]]></title><description><![CDATA[Kubernetes is a big and complicated technology and it clearly requires some time and dedication to wrap your head around. There is no vendor lock-in meaning it runs the same no matter which managed cloud platform you use it on. This means using it lo...]]></description><link>https://yankee.dev/6-tools-to-run-kubernetes-locally</link><guid isPermaLink="true">https://yankee.dev/6-tools-to-run-kubernetes-locally</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[containers]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Thu, 05 Aug 2021 07:43:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628148249210/-JvsBHAz6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kubernetes is a big and complicated technology and it clearly requires some time and dedication to wrap your head around. There is no vendor lock-in meaning it runs the same no matter which managed cloud platform you use it on. This means using it locally will be no different from using it on the cloud.</p>
<p>There are multiple tools for running Kubernetes on your local machine, but it basically boils down to two approaches on how it is done: </p>
<ol>
<li>running it from a single binary package </li>
<li>running it as a container using Docker in Docker (DinD)</li>
</ol>
<h3 id="kubernetes-marketplace">Kubernetes Marketplace</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/alexellis/arkade">https://github.com/alexellis/arkade</a></div>
<p>Before we move on to talk about all the tools, it will be beneficial if you installed <code>arkade</code> on your machine. It will help you get these tools with a single command. </p>
<pre><code class="lang-bash">curl -sLS https://get.arkade.dev | sudo sh
</code></pre>
<h2 id="features">Features?</h2>
<p>All of the tools listed here more or less offer the same feature, including but not limited to: </p>
<ol>
<li>Multi-Node cluster</li>
<li>Persistent volumes </li>
<li>Networking</li>
<li>Certificates</li>
<li>Bare-metal support</li>
<li>Dashboard </li>
<li>Kubernetes Versions</li>
<li>Add-ons</li>
<li>Cross-platform</li>
<li>Tracks upstream Kubernetes</li>
</ol>
<h2 id="single-package-binary">Single package binary</h2>
<ul>
<li><p><strong> <a target="_blank" href="https://github.com/k3s-io/k3s">k3s</a> </strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146388504/EuqE3teij.png" alt="K3s logo.png" /></p>
<p>k3s is a lightweight Kubernetes distribution from Rancher Labs. It is specifically targeted for running on IoT and Edge devices, meaning it is a perfect candidate for your Raspberry Pi or a virtual machine. </p>
<p>It comes with a single binary of mere &lt;40 MB and takes as low as 500 MB of RAM.  </p>
<p>You can bootstrap k3s quickly using  <a target="_blank" href="https://github.com/alexellis/k3sup">k3sup</a> !</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://yankee.dev/multi-node-kubernetes-k3s-locally">https://yankee.dev/multi-node-kubernetes-k3s-locally</a></div>
<pre><code class="lang-bash">arkade get k3sup
</code></pre>
</li>
<li><p><strong> <a target="_blank" href="https://github.com/k0sproject/k0s">k0s</a> </strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146395939/RQI9JmCfU.png" alt="k0s.png" />
k0s is the latest entry on the block. By name, you might think it's a more stripped version of k3s, but it's an entirely different distribution from an entirely different company, called Mirantis. Contrary to the name, it comes in a larger binary of 150 MB+.</p>
<p>It can be run as a binary or in DinD mode. k0s takes security seriously and out of the box, it meets the <a target="_blank" href="https://www.sdxcentral.com/security/definitions/what-does-mean-fips-compliant/">FIPS compliance</a>.  Although, a new distribution, k0s has reached the production-ready status, so there wouldn't be an issue for development usage. </p>
<pre><code class="lang-bash">arkade get k0s
</code></pre>
</li>
<li><p><strong> <a target="_blank" href="https://github.com/ubuntu/microk8s">Microk8s</a> </strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146540551/J5ApDUbjf.jpeg" alt="Micro k8s.jpg" />
MicroK8s is a Kubernetes distribution by Canonical, the company behind Ubuntu. You already saw this coming; it can only be installed using <code>snap</code>.  It comes with loads of add-ons baked in like Fluentd, Grafana and Prometheus. </p>
<p>If you are on Ubuntu or its derivatives that uses <code>snap</code> you'll feel right at home using MicroK8s.</p>
<pre><code class="lang-bash">sudo snap install microk8s --classic
</code></pre>
</li>
</ul>
<h2 id="docker-in-docker-dind">Docker in Docker (DinD)</h2>
<p>Running Docker inside of Docker (Inception anyone?) is a popular way of bootstrapping Kubernetes. The isolating nature of Docker makes running a multi-node cluster a breeze on a single machine, and also ensures the running instance does not affect the machine itself.</p>
<blockquote>
<p>As the title suggests, you need to have Docker installed on your machine to go this route.</p>
</blockquote>
<ul>
<li><p><strong> <a target="_blank" href="https://github.com/kubernetes/minikube">minikube</a> </strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146684436/clc9TZbZ4.png" alt="Minikube.png" />
Despite running on top of Docker and similar container technologies, minikube is really flexible in how it operates and supports multiple virtualization drivers making it adaptable to different computing environments.  These include KVM2, Virtualbox, Podman, Hyperkit, Hyper-V and many more.</p>
<pre><code class="lang-bash">arkade get minikube
</code></pre>
</li>
</ul>
<ul>
<li><p><strong> <a target="_blank" href="https://github.com/kubernetes-sigs/kind">KinD</a></strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146781587/SgSgJa0dZ.png" alt="Kind.png" />
Kubernetes in Docker (KinD) is similar to minikube but it does not spawn VM's to run clusters and works only with Docker.  KinD for the most part has the least bells and whistles and offers an intuitive developer experience in getting started with Kubernetes in no time.</p>
<pre><code class="lang-bash">arkade get kind
</code></pre>
</li>
</ul>
<ul>
<li><p><strong> <a target="_blank" href="https://github.com/rancher/k3d/">k3d</a> </strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628146849609/XZXhr_4Sk.png" alt="k3d.png" />
k3d is basically running k3s inside of Docker. It provides an instant benefit over using k3s on a local machine, that is, multi-node clusters. Running inside Docker, we can easily spawn multiple instances of our k3s Nodes.</p>
<pre><code class="lang-bash">arkade get k3d
</code></pre>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Either you choose a single binary package or DinD approach, Kubernetes has made itself pretty accessible. For new learners, the barrier to entry is low, and the feedback loop is instantaneous. </p>
<p>I hope this article has been helpful in deciding which tool to use for running your local Kubernetes instance.</p>
]]></content:encoded></item><item><title><![CDATA[Incognito Mode for Shell Environments]]></title><description><![CDATA[Whatever we write on the Shell, its history is saved on the HISTFILE. If you go and type history on your terminal, you're going to see a list of all the commands you recently typed. 
https://youtu.be/WYD6Xkk1U8E
What is Shell's history?
Think of it l...]]></description><link>https://yankee.dev/incognito-mode-for-shell-environments</link><guid isPermaLink="true">https://yankee.dev/incognito-mode-for-shell-environments</guid><category><![CDATA[command line]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sun, 16 May 2021 03:10:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1621134503901/Wah94mrwX.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Whatever we write on the Shell, its history is saved on the <code>HISTFILE.</code> If you go and type <code>history</code> on your terminal, you're going to see a list of all the commands you recently typed. </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/WYD6Xkk1U8E">https://youtu.be/WYD6Xkk1U8E</a></div>
<h2 id="what-is-shells-history">What is Shell's history?</h2>
<p>Think of it like your browser history, where all the sites you have visited are stored, but when you don't want to keep that history, you switch to <strong>Incognito Mode</strong>. Like the browser, we can switch to Incognito Mode when working with secrets and other sensitive data that we don't want to be leaked on the history.  </p>
<p>How many times have you done this to test out something, and the next thing you know, this is logged in your <code>history</code>.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> AWS_PASSWORD=<span class="hljs-string">"xyz#31"</span>
</code></pre>
<p>All of our histories are saved on a file; depending on your shell environment, <code>HISTFILE</code> can be in a certain location with a certain name. </p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-variable">$HISTFILE</span>

<span class="hljs-comment"># View the contents of HISTFILE</span>
cat <span class="hljs-variable">${HISTFILE}</span>
</code></pre>
<p>And how much history is saved is determined by the <code>HISTSIZE</code> environment variable.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-variable">$HISTSIZE</span>
</code></pre>
<p>You can change the <code>HISTSIZE</code> to let's say store only the last 100 commands you have written. </p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> HISTSIZE=100
</code></pre>
<h2 id="incognito-mode-on-bash-shell">Incognito Mode on Bash shell</h2>
<p>To use the Bash shell on Incognito mode all we have to do is unset the <code>HISTFILE</code>. </p>
<pre><code class="lang-bash"><span class="hljs-built_in">unset</span> HISTFILE
</code></pre>
<p>This will unset the history file for the current terminal session, meaning, if you restart the terminal session or open a new instance of the Bash shell, you won't find any history.</p>
<p>To see that in action, write some random unique commands you can identify on the terminal, and restart the terminal. </p>
<pre><code class="lang-bash"><span class="hljs-comment"># Do random things </span>
<span class="hljs-comment"># ... </span>

<span class="hljs-comment"># Refresh bash session</span>
<span class="hljs-built_in">exec</span> bash

<span class="hljs-comment"># Check history</span>
<span class="hljs-built_in">history</span>
</code></pre>
<h2 id="incognito-mode-on-zsh">Incognito mode on ZSH</h2>
<p>ZSH has one of the easiest ways of disabling history, all you have to do is <strong>add a space in front of the command</strong> and that won't be listed on the <code>HISTFILE</code>.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># space in front of the command </span>
 <span class="hljs-built_in">echo</span> <span class="hljs-string">"Goodbye History"</span>
</code></pre>
<p>And just like that, you won't have any <code>history</code>. If this is not working out for you, go to the zsh configuration file, usually <code>.zshrc</code>, and follow the instructions below.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># open the config file and write</span>
<span class="hljs-built_in">setopt</span> HIST_IGNORE_SPACE
<span class="hljs-comment"># close and save the config file</span>

<span class="hljs-comment"># execute the contents of config file </span>
<span class="hljs-built_in">source</span> &lt;path-to-zsh_config_file&gt;
</code></pre>
<h2 id="incognito-mode-on-fish-shell">Incognito mode on Fish Shell</h2>
<p>Fish shell has a built-in private mode that can be enabled with ease using: </p>
<pre><code class="lang-bash">fish --private
</code></pre>
<p>Once you are done with dealing with sensitive data, you can just exit from the private mode and there will be no single trace of <code>history</code>.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># exit from private mode</span>
<span class="hljs-built_in">exit</span>

<span class="hljs-comment"># check history</span>
<span class="hljs-built_in">history</span>
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>Hope this has been a helpful guide on working with sensitive data on your terminal. If you want to read more articles like this make sure to subscribe to my newsletter and <a target="_blank" href="https://www.youtube.com/channel/UCsHbJuyHI7eKje1AH_joWyQ">YouTube channel</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Practical Guide to Git Worktree]]></title><description><![CDATA[Git has a solution to all of our problems, you just need to know where to look. As developers, context switching is a part of the job that you need to account for in more than a few occasions.
Problem Statement
Imagine this; you are working on a feat...]]></description><link>https://yankee.dev/practical-guide-to-git-worktree</link><guid isPermaLink="true">https://yankee.dev/practical-guide-to-git-worktree</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[workflow]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Mon, 12 Apr 2021 13:40:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1620740452125/tLWtrGawh.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Git has a solution to all of our problems, you just need to know where to look. As developers, context switching is a part of the job that you need to account for in more than a few occasions.</p>
<h3 id="problem-statement">Problem Statement</h3>
<p>Imagine this; you are working on a feature where you have made bunch of changes to files that are not yet commited, and suddenly you need to work on a hot fix or a more priority feature. There are two ways you can tackle this:</p>
<ol>
<li><a target="_blank" href="https://dev.to/yankee/mastering-git-stash-workflow-223">Using Git Stash workflow</a></li>
<li>Using Git Worktree (You are here! 📍)</li>
</ol>
<h3 id="git-worktree-to-the-rescue">Git worktree to the rescue 🌳</h3>
<p>Git worktree helps you manage multiple working trees attached to the same repository.</p>
<blockquote>
<p>In short, you can check out multiple branches at the same time by maintaining multiple clones of the same repository.</p>
</blockquote>
<p>OK back to our problem! Update changes? New Feature? Hot Fix? Whatever it is, you need to change to a different branch and work on it without any changes to your current work directory.</p>
<p>Let’s say it’s a new feature, your workflow would look like this:</p>
<ol>
<li>create an replica of your project and switch to a new branch</li>
<li>create a new feature</li>
<li>push it</li>
<li>back to previous working directory</li>
</ol>
<h3 id="create-worktree">Create worktree</h3>
<p>Let’s say the name of your feature is <code>feature-x</code> and you want the branch with the same name. You can create additional worktree on the same directory or move it to a desired path, I prefer the later.</p>
<p><code>git worktree add</code> command creates a worktree along with a branch that is named after the final word in your path.</p>
<pre><code class="lang-bash">git worktree add &lt;PATH&gt;

<span class="hljs-comment"># Create feature-x directory and branch with the same name.</span>
git worktree add ../feature-x
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll5h29lxleual7qez3o2.gif" alt="Creating new git worktree" /></p>
<h3 id="named-branch">Named Branch</h3>
<p>If you want to give you branch a unique name then you can use the <code>-b</code> flag with the <code>add</code> command.</p>
<pre><code class="lang-bash">git worktree add -b feature-xyz ../feature-xyz
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v38rqcy4vkpafms1fr4u.gif" alt="Git worktree creating new named branch" /></p>
<h3 id="track-remote-branch">Track remote branch</h3>
<p>Let’s say you want to switch to a new branch that is tracking the branch at remote, where you want to push changes to.</p>
<pre><code class="lang-bash">git worktree add -b &lt;branch-name&gt; &lt;PATH&gt; &lt;remote&gt;/&lt;branch-name&gt;

git worktree add -b feature-zzz ../feature-x origin/feature-zzz
</code></pre>
<p>View the list of worktrees with <code>git worktree list</code></p>
<h3 id="switching-worktrees">Switching Worktrees</h3>
<p>As much easy it is to create a worktree, it is equally difficult to navigate back and forth between them if they are spread across. You have to <code>git worktree list</code> and then copy the path navigate to the worktree of your choice. To minimize this friction, I have built a small tool that let’s you switch between worktrees just with their partial or complete directory name.</p>
<blockquote>
<p><a target="_blank" href="https://github.com/yankeexe/git-worktree-switcher"><strong>Download wt CLI tool</strong></a> for faster switching between worktrees.</p>
</blockquote>
<p>With this I can simply switch between my worktrees.
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5bpg3fracpfoyb68lbt.gif" alt="wt cli tool to switch between git worktrees" />
You can <code>wt list</code> which is equivalent to <code>git worktree list</code> to see the list of your worktrees. Now to move to <code>feature-x</code> worktree directory, I can just use <code>wt feature-x</code> to cd into that directory to continue with the work. To go back to my main worktree directory I can just <code>wt -</code>.</p>
<h3 id="remove-worktrees">Remove Worktrees</h3>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzezl1l6yl7cwiu9fltw.gif" alt="Remove git worktrees" />
Now that you have created a new worktree, switched to it and made your changes and pushed it. To remove the worktree, we can run: </p>
<pre><code class="lang-bash">git worktree remove &lt;name-of-worktree&gt;

git worktree remove feature-x
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>Git worktree is a handy feature that let's you context switch in your project to try out things on a completely different environment, without modifying your main work directory. This might come handy occasionally but it's pretty neat being able to do so without breaking a sweat. </p>
<p>I hope this guide has been helpful, if you have any queries or corrections, feel free to reach out to me.</p>
]]></content:encoded></item><item><title><![CDATA[Streamline your projects using Makefile]]></title><description><![CDATA[make is one of the tools that we use heavily for streamlining tasks on our projects. It has proven to be helpful specifically for streamlining the development process, repeating mundane tasks with custom CLI like subcommands and mainly onboarding new...]]></description><link>https://yankee.dev/streamline-projects-using-makefile</link><guid isPermaLink="true">https://yankee.dev/streamline-projects-using-makefile</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Python]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sat, 26 Dec 2020 04:15:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1620706451697/tVkNbOBPV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><code>make</code> is one of the tools that we use heavily for streamlining tasks on our projects. It has proven to be helpful specifically for streamlining the development process, repeating mundane tasks with custom CLI like subcommands and mainly <strong>onboarding new team members</strong>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/vybdPTfNLo4">https://youtu.be/vybdPTfNLo4</a></div>
<p>With a set of rules in <strong>Makefile</strong>, you can get up and running in no time, keeping the process sane and saving time and effort for everyone in the team. We'll be going through the basics to some interesting stuffs we can do with Makefile.</p>
<p>There are two pieces to this equation, one is the <code>make</code> CLI tool and the next is the Makefile . The basics is <code>make</code> reads the <strong>rules</strong> from the Makefile and executes them. What I will be showing today is just a small part of what <code>make</code> is capable of.</p>
<h2 id="heading-writing-makefile">Writing Makefile</h2>
<p>If you have worked with <code>YAML</code> files before then you will feel right at home writing Makefiles.</p>
<h3 id="heading-anatomy-of-rules">Anatomy of Rules</h3>
<p>Every <code>Makefile</code> consists of rules with the anatomy of: </p>
<pre><code class="lang-makefile"><span class="hljs-section">target: dependencies</span>
    recipe
</code></pre>
<ul>
<li><strong>target</strong>: 
target can be an executable, object or just a name for an action that we want to carry out. We will be using targets purely with the placeholder name for the rule. Be mindful about the name, as it should resonate with the action we want to perform with no confusion whatsoever.</li>
<li><strong>dependencies</strong>: 
Dependencies are the rules that needs to be executed, in order for the current rule to work.</li>
<li><strong>recipe</strong>: 
recipe is the meat of the <code>Makefile</code>, it is the action that we want to perform with our <code>target</code> name. Make sure to put a <code>tab</code> character at the start of every recipe line (just like YAML). You can also replace the <code>tab</code> character with anything you want using the <code>.RECIPEPREFIX</code> variable.</li>
</ul>
<p>Next we will be looking into some examples on how to make use of <code>Makefile</code>. These examples will be based on setting up development environments.</p>
<h3 id="heading-basic-rules">Basic Rules</h3>
<p>A basic rule where you just want to put some alias is straight forward. </p>
<p>Let’s say you have a <strong>python</strong> project and you want to hand it over to a new team member. How do you streamline the setup process. Maybe it can look something like this. </p>
<blockquote>
<p><strong>Note</strong>: 
<code>#</code> is for comments. </p>
<p><code>@</code> symbol is to disable printing the recipe to stdout. 
Test without the <code>@</code> symbol at the beginning of the recipe.</p>
<p><code>:=</code> is the expansion operator which prevents using subsequent value with the same variable name.</p>
<p><code>SHELL</code> variable determines the default shell to execute the recipe.</p>
</blockquote>
<pre><code class="lang-makefile">SHELL :=/bin/bash

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: format check</span>

<span class="hljs-section">venv: # setup a virtual environment</span>
    @python3 -m venv venv

<span class="hljs-section">setup: # install dev dependencies</span>
    @pip install -e .[dev]
    @echo -e <span class="hljs-string">"\nInstalling pre-commit hook..."</span>
    @pre-commit install

<span class="hljs-section">format: # format code using black</span>
    @black .

<span class="hljs-section">check: # check for formatting using black</span>
    @black --check --diff -v .

<span class="hljs-section">test: # run pytest</span>
    @pytest -vvv
</code></pre>
<p>You can do something similar with your existing project.</p>
<p>Now to get up and running, all you have to do is: </p>
<pre><code class="lang-bash">$ make venv 

$ . venv/bin/activate 

$ make setup

$ make format 

<span class="hljs-comment"># and so on</span>
</code></pre>
<h3 id="heading-rules-with-dependencies">Rules with Dependencies</h3>
<p>Taking the reference from the example above, suppose we want to print out the output of <code>check</code> target every time we run the <code>format</code> target. So how do we create that dependency? It’s plain simple, we just have to update the <code>format</code> target to look something like this: </p>
<pre><code class="lang-makefile"><span class="hljs-section">format: check # run the formatter on files.</span>
 @black .
</code></pre>
<p>We have added the dependency of <code>check</code> to the right of the target, just like showcased on the anatomy <strong>Anatomy of Rules</strong> section.</p>
<h2 id="heading-variables">Variables</h2>
<p>We can also define variables if we have some piece of command for repeated use. For this example we will be taking the reference of the <code>Django</code> management command. </p>
<p>Variables are normally written with all caps and uses <code>:=</code> to assign variable name to a value. Variables can be accessed using either <code>$()</code> or <code>${}</code> syntax.</p>
<pre><code class="lang-makefile">DJANGO_MANAGE := python manage.py
<span class="hljs-section">run: </span>
 @${DJANGO_MANAGE} runserver

<span class="hljs-section">show: </span>
 @${DJANGO_MANAGE} showmigrations

<span class="hljs-section">migrate: </span>
 @${DJANGO_MANAGE} migrate
</code></pre>
<p>Also your <strong>SHELL environment variables</strong> are converted in to Makefile environment variables, so you can directly make use of them while creating your rules. </p>
<p><strong>Example:</strong> </p>
<p>In our shell we can export an environment variable called <code>INFO</code>. </p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">export</span> INFO=<span class="hljs-string">"Run make help to show all the available rules."</span>
</code></pre>
<p>And now in the Makefile we can refer to it as any variable. </p>
<pre><code class="lang-makefile"><span class="hljs-section">info: # show project info</span>
    @echo ${INFO}
</code></pre>
<h2 id="heading-default-target">Default target</h2>
<p>If you just run <code>make</code> on your command line and it will run the first target on your Makefile. But we can change that by using the <code>.DEFAULTGOAL</code> special variable and assigning the target we want to run by default.</p>
<pre><code class="lang-makefile">.DEFAULT_GOAL := run
</code></pre>
<p>Now, next time you run <code>make</code> it is going to run the <code>Django</code> server by default.</p>
<h2 id="heading-self-documenting">Self documenting</h2>
<p>Now we have bunch of targets on our <code>Makefile</code> and we also called this combo as a custom mini CLI app. Wouldn’t it be great, if we could have a help command similar to a real CLI app? Say no more, thanks to the <a target="_blank" href="https://dev.to/victoria/how-to-create-a-self-documenting-makefile-2b0e">blog</a> from <a target="_blank" href="https://dev.to/victoria">Victoria Drake</a> we have the script to do so. </p>
<p>Just create a <code>help</code> target and assign it as a <code>.DEFAULT_GOAL</code>. With this, all the comments we have been writing on our target gets converted into a nice help message.</p>
<pre><code class="lang-makefile">.DEFAULT_GOAL := help 
<span class="hljs-section">help: # Show this help</span>
 @egrep -h '\s<span class="hljs-comment">#\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?# "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'</span>
</code></pre>
<h2 id="heading-include-other-makefiles">Include other Makefiles</h2>
<p>We can separate out Makefiles based on the tasks they perform and <code>include</code> them into the main <code>Makefile</code>. We usually have separate <code>Makefile</code> managed for <strong>environment variables</strong>,  <strong>Docker</strong> and <strong>Kubernetes</strong>. This offloads all the tasks from project set up to Deployment to the Makefile.</p>
<p>I will show a brief example of each of the file just to give an example: </p>
<blockquote>
<p>Note: Since make runs each recipe on a new instance of the shell, we can lazy evaluate the variables using <code>?=</code> meaning, they are initialized only when referenced for a single shell instance.</p>
</blockquote>
<p><strong>Makefile</strong>
Root makefile composed of other Makefiles.</p>
<pre><code class="lang-makefile">SHELL :=/bin/bash
APP_ROOT := <span class="hljs-variable">$(PWD)</span>
TMP_PATH := <span class="hljs-variable">$(APP_ROOT)</span>/.tmp
VENV_PATH := <span class="hljs-variable">$(APP_ROOT)</span>/.venv

<span class="hljs-keyword">export</span> ENVIRONMENT_OVERRIDE_PATH ?= <span class="hljs-variable">$(APP_ROOT)</span>/env/Makefile.<span class="hljs-keyword">override</span>

<span class="hljs-keyword">-include</span> <span class="hljs-variable">$(ENVIRONMENT_OVERRIDE_PATH)</span>
<span class="hljs-keyword">include</span> <span class="hljs-variable">$(APP_ROOT)</span>/targets/Makefile.docker
<span class="hljs-keyword">include</span> <span class="hljs-variable">$(APP_ROOT)</span>/targets/Makefile.k8s
</code></pre>
<p><strong>Environment Variables</strong>
<em>Makefile.override</em>
Makefile containing just the essential environment variables.</p>
<pre><code class="lang-makefile">STAGE ?= &lt;stage&gt;
SERVICE_NAME ?= &lt;service-name&gt;
AKS_RESOURCE_GROUP ?= &lt;resource-group&gt;
AKS_CLUSTER_NAME ?= &lt;cluster-name&gt;
REGISTRY_URL ?= &lt;registry-url&gt;
AZ_ACR_REPO_NAME ?= &lt;repo-name&gt;
</code></pre>
<p><strong>Docker</strong>
<em>Makefile.docker</em>
Makefile containing docker rules.</p>
<pre><code class="lang-makefile"><span class="hljs-keyword">export</span> GIT_COMMIT ?= <span class="hljs-variable">$(<span class="hljs-built_in">shell</span> cut -c-8 &lt;&lt;&lt; `git rev-parse HEAD`)</span>
<span class="hljs-keyword">export</span> BRANCH ?= <span class="hljs-variable">$(<span class="hljs-built_in">shell</span> git rev-parse --abbrev-ref HEAD)</span>

<span class="hljs-keyword">export</span> DOCKER_BUILD_FLAGS ?= --no-cache
<span class="hljs-keyword">export</span> DOCKER_BUILD_PATH ?= <span class="hljs-variable">$(APP_ROOT)</span>
<span class="hljs-keyword">export</span> DOCKER_FILE ?= <span class="hljs-variable">$(APP_ROOT)</span>/Dockerfile

<span class="hljs-keyword">export</span> TARGET_IMAGE ?= <span class="hljs-variable">$(REGISTRY_URL)</span>/<span class="hljs-variable">$(AZ_ACR_REPO_NAME)</span>/<span class="hljs-variable">$(SERVICE_NAME)</span>
<span class="hljs-keyword">export</span> TARGET_IMAGE_LATEST ?= <span class="hljs-variable">$(TARGET_IMAGE)</span>:<span class="hljs-variable">$(BRANCH)</span>-<span class="hljs-variable">$(GIT_COMMIT)</span>

<span class="hljs-section">acr-docker-login:</span>
    az acr login --name <span class="hljs-variable">$(AZ_ACR_REPO_NAME)</span>

<span class="hljs-section">docker-build:</span>
    docker build <span class="hljs-variable">$(DOCKER_BUILD_FLAGS)</span> -t <span class="hljs-variable">$(SERVICE_NAME)</span> -f <span class="hljs-variable">$(DOCKER_FILE)</span> <span class="hljs-variable">$(DOCKER_BUILD_PATH)</span>

<span class="hljs-section">docker-tag:</span>
    docker tag <span class="hljs-variable">$(SERVICE_NAME)</span> <span class="hljs-variable">$(TARGET_IMAGE_LATEST)</span>

<span class="hljs-section">docker-push: acr-docker-login</span>
    docker push <span class="hljs-variable">$(TARGET_IMAGE_LATEST)</span>
</code></pre>
<p><strong>Kubernetes</strong></p>
<p><em>Makefile.k8s</em>
Makefile containing rules for Kubernetes.</p>
<pre><code class="lang-makefile"><span class="hljs-keyword">export</span> OVERLAY_PATH ?= <span class="hljs-variable">$(APP_ROOT)</span>/k8s/overlays/<span class="hljs-variable">$(STAGE)</span>/

<span class="hljs-keyword">define</span> kustomize-image-edit
    cd <span class="hljs-variable">$(OVERLAY_PATH)</span> &amp;&amp; kustomize edit set image api=$(1) &amp;&amp; \
    cd <span class="hljs-variable">$(APP_ROOT)</span>
<span class="hljs-keyword">endef</span>

<span class="hljs-section">kubectl-apply:</span>
    kustomize build <span class="hljs-variable">$(OVERLAY_PATH)</span>
    kustomize build <span class="hljs-variable">$(OVERLAY_PATH)</span> | kubectl apply -f -

<span class="hljs-section">update-kubeconfig:</span>
    az aks get-credentials --resource-group <span class="hljs-variable">$(AKS_RESOURCE_GROUP)</span> --name <span class="hljs-variable">$(AKS_CLUSTER_NAME)</span>

<span class="hljs-section">aks-deploy: update-kubeconfig</span>
    <span class="hljs-variable">$(<span class="hljs-built_in">call</span> kustomize-image-edit,<span class="hljs-variable">$(TARGET_IMAGE_LATEST)</span>)</span>
    make kubectl-apply

<span class="hljs-section">aks-delete: update-kubeconfig</span>
    kubectl delete namespace <span class="hljs-variable">$(STAGE)</span>-api

<span class="hljs-section">kustomize-edit:</span>
    <span class="hljs-variable">$(<span class="hljs-built_in">call</span> kustomize-image-edit,<span class="hljs-variable">$(TARGET_IMAGE_LATEST)</span>)</span>
</code></pre>
<p>Now we have orchestrated all these Makefiles, it is easier to keep track of all the rules and makes working with Makefiles sane, if you are doing a lot with it.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So with the use of <code>Makefile</code> we can streamline a lot of redundant tasks in our projects without having to remember overwhelmingly long and varying commands. </p>
<p>It increases the productivity of the whole team; with easier project setup and redundant tasks outsourced to the <code>Makefile</code> with intuitive target names, leaving the devs to focus on more serious tasks at hand.</p>
]]></content:encoded></item><item><title><![CDATA[How Rolling and Rollback Deployments work in Kubernetes]]></title><description><![CDATA[Kubernetes has been used heavily on production for the past few years. It offers a plethora of solutions for orchestrating your containers using its declarative API. One of the prominent feature of Kubernetes is its resilience with the ability to per...]]></description><link>https://yankee.dev/rolling-rollback-deployments-kubernetes</link><guid isPermaLink="true">https://yankee.dev/rolling-rollback-deployments-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[deployment]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sun, 25 Oct 2020 08:42:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741544628/KPAUqdCV3M.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kubernetes has been used heavily on production for the past few years. It offers a plethora of solutions for orchestrating your containers using its declarative API. One of the prominent feature of Kubernetes is its resilience with the ability to perform Rolling and Rollback Deployments.</p>
<h2 id="primer"><strong>Primer</strong></h2>
<h3 id="deployments"><strong>Deployments</strong></h3>
<p>Deployment is one of the mechanisms for handling workloads (applications) in Kubernetes. It is managed by <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/">Kubernetes Deployment Controller</a>.</p>
<blockquote>
<p>In Kubernetes, controllers are control loops that watch the state of your cluster, then make or request changes where needed. Each controller tries to move the current cluster state closer to the desired state.</p>
</blockquote>
<p>In case of deployment here, the desired state we want to achieve is for the pods. Everything is declarative in K8s, so the desired state is written as a spec in Deployment manifest file.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># deployment.yaml</span>
<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">nginx-deployment</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</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">nginx</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:1.14.2</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>If any of our pod instances should fail or update(a status change), the Kubernetes system responds to the difference between manifest spec and status by making a correction, i.e. matching the state of <code>Deployment</code> as defined on the spec.</p>
<h3 id="deployment-under-the-hood"><strong>Deployment under the hood</strong></h3>
<p>Deployment is an abstraction over <code>ReplicaSet</code>. Under the hood, Deployment creates a ReplicaSet which in turn creates pods on our cluster. As per the name, <code>ReplicaSet</code> is used for managing the replicas of our pods.</p>
<blockquote>
<p> In summary, Controller reads the Deployment spec, forwards the pod configuration to ReplicaSet and then it creates the pods with proper replicas.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741524403/rso4JVj6x.png" alt="**Deployment &gt; ReplicaSet &gt; Pods**" /><strong><em>Deployment &gt; ReplicaSet &gt; Pods</em></strong></p>
<h3 id="rolling-deployment"><strong>Rolling Deployment</strong></h3>
<p>Kubernetes promises zero down time and one of the reasons behind it is Rolling Deployments. With Rolling Deployments, Kubernetes makes sure that the traffic to the pods are not interrupted when updated pods are being deployed.</p>
<h3 id="rollback-deployment"><strong>Rollback Deployment</strong></h3>
<p>Rollback Deployment means, going back to the previous instance of the deployment if there is some issue with the current deployment.</p>
<h2 id="hands-on"><strong>Hands on</strong> 🙌</h2>
<p>Let’s get our hands-on with Kubernetes and see how these deployment strategies are carried out.</p>
<h3 id="setup-kind"><strong>Setup KinD</strong></h3>
<p>We will setup a single node Kubernetes cluster on our local machine using KinD (Kubernetes in Docker). Make sure you have Docker running.</p>
<blockquote>
<p><a target="_blank" href="https://kind.sigs.k8s.io/docs/user/quick-start/">Install KinD</a></p>
</blockquote>
<p>We will create a K8s cluster using:</p>
<pre><code class="lang-bash">$ kind create cluster
</code></pre>
<p>With our cluster ready we are ready for Deployments.</p>
<p>For the purpose of this tutorial, we won’t be touching any YAML files and will be fully utilizing the power of <code>kubectl</code> CLI tool.</p>
<h3 id="create-deployment"><strong>Create Deployment</strong></h3>
<p>Let’s create our first Deployment using the <code>nginx</code> image.</p>
<pre><code class="lang-bash">$ kubectl create deployment test-nginx --image=nginx:1.18-alpine
</code></pre>
<p>Like mentioned earlier, a Deployment creates a <code>ReplicaSet</code> followed by <code>Pods</code>. You can check these newly created resources using:</p>
<pre><code class="lang-bash">$ kubectl get deploy,rs,po -l app=test-nginx
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741526376/jh2i6oMV-.png" alt="List newly created Deployment, ReplicaSet and Pod" /><em>List newly created Deployment, ReplicaSet and Pod</em></p>
<p>You can check to make sure the <code>Replicaset</code> was created by our <code>Deployment</code> using:</p>
<pre><code class="lang-bash">$ kubectl describe rs &lt;replica-set-name&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741528383/4xrxP2o59.png" alt="Check ReplicaSet created by Deployment" /><em>Check ReplicaSet created by Deployment</em></p>
<p>You can do the same with Pods:</p>
<pre><code class="lang-bash">$ kubectl describe po &lt;pod-name&gt;
</code></pre>
<h3 id="scale-deployment"><strong>Scale Deployment</strong></h3>
<p>Let’s scale the deployment to have 3 instances of <code>nginx</code> pods.</p>
<pre><code class="lang-bash">$ kubectl scale deploy test-nginx --replicas=3
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741530289/CO74APoP9.png" alt="Creating 3 replicas of nginx pods" /><em>Creating 3 replicas of nginx pods</em></p>
<p>Now that we have significant number of pods on our cluster. Let’s try out the Deployment strategies.</p>
<h3 id="hands-on-rolling-update-deployment"><strong>Hands-on: Rolling Update Deployment</strong> 🍥⏩</h3>
<p>Let’s say you were having some issues with <code>v18</code> of nginx and the <code>v19</code> fixes it for you. You need to rollout a new update of nginx image to your pod.</p>
<p>By updating the image of the current pods (state change), Kubernetes will rollout a new Deployment.</p>
<pre><code class="lang-bash">$ kubectl <span class="hljs-built_in">set</span> image deploy test-nginx nginx=nginx:1.19-alpine
</code></pre>
<p>After we set the new image, we can see the old pods getting terminated and new pods getting created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741532371/nNJgLT-V_.png" alt="Rolling update, new pod replacing old ones" /><em>Rolling update, new pod replacing old ones</em></p>
<p>We can see Kubernetes at work, making sure the pods are maintained properly. The last of the old pod doesn’t get terminated until the complete replicas for the new pods are created. The old pods also have a <strong><em>grace period</em></strong> which makes sure the traffic it is serving isn’t disconnected for certain time until the requests can be safely routed to the newly created pods.</p>
<p>We successfully updated all our pods to use <code>nginx v19</code>.</p>
<pre><code class="lang-bash">$ kubectl describe deploy test-nginx
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741534788/QbbxjRMXk.png" alt="Pods updated to nginx v19" /><em>Pods updated to nginx v19</em></p>
<h3 id="hands-on-rollback-deployment"><strong>Hands-on: Rollback Deployment</strong> 🍥⏪</h3>
<p>Let’s assume the new nginx update has even more problems than the last one and now you realized how blissful life was with the old version. Time for a rollback to the previous version of nginx.</p>
<p><strong><em>But how do we do that?</em></strong> You might have noticed that there are now two <code>ReplicaSets</code>. It’s due to the same Deployment pattern we discussed earlier, we update our Deployment, it creates a new ReplicaSet which creates new Pods. Kubernetes holds history of up to 10 ReplicaSet by default, we can update that figure by using <code>revisionHistoryLimit</code> on our Deployment spec.</p>
<p>These history are tracked as rollouts. Only the latest rollout is active.</p>
<p>By now, we have made two changes to our Deployment <code>test-nginx</code> so the rollout history should be two.</p>
<pre><code class="lang-bash">$ kubectl get rs 

$ kubectl rollout <span class="hljs-built_in">history</span> deploy test-nginx

$ kubectl rollout <span class="hljs-built_in">history</span> deploy test-nginx --revision=1
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741536781/Fs933kXXD.png" alt="Rollout history for test-nginx Deployment" />
<em>Rollout history for test-nginx Deployment</em></p>
<p>Alright let’s get to rolling back our update to the previous rollout. We want to rollback to the stage where we were using nginx v18 which is rollout <code>Revision 1</code>.</p>
<pre><code class="lang-bash">$ kubectl rollout undo deploy test-nginx --to-revision=1
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741538804/38IW6gKec.png" alt="Rollout undo pod states" />
<em>Rollout undo pod states</em></p>
<p>Like the Rolling Update Deployment, the Rollback Deployment terminates the current pods and replaces them with the pods containing the spec from Revision 1.</p>
<p>If you check the rollout history once again, you can see that the Revision 1 has been used to create the latest pods tagging it with Revision 3. There is no point in maintaining the same spec repeated for multiple revisions, so Kubernetes removes the Revision 1 since we have the latest Revision 3 of the same spec.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741540777/TnCMrQ8Wy.png" alt="Revision history after rollout undo" />
<em>Revision history after rollout undo</em></p>
<p>Now we are back on the nginx v18. with the Rollback Deployment. You can check that out by:</p>
<pre><code class="lang-bash">$ kubectl describe deploy test-nginx
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620741542943/AGiWht0ZH.png" alt="Check rollback nginx version" /><em>Check rollback nginx version</em></p>
<h2 id="conclusion"><strong>Conclusion</strong> 🚀</h2>
<p>Kubernetes makes it easy to control the Deployment of our applications with these strategies. This was just a rundown of how Rolling and Rollback Update Deployments work from a ground level. In real-life, we rarely do all these steps manually, since we hand it down to our CI/CD pipeline like <a target="_blank" href="https://argoproj.github.io/argo-cd/">ArgoCD</a>.</p>
<p>Thank you for reading. Hope this guide has been helpful for you to understand how Rolling and Rollback Deployments work in Kubernetes. If you have any corrections, suggestions, or feedback feel free to DM me on <a target="_blank" href="https://twitter.com/yankexe">Twitter</a> or comment down below.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up multi-node Kubernetes cluster locally with K3s and Multipass]]></title><description><![CDATA[There are a lot of tools that allow you to setup a local Kubernetes cluster in no time. But with a full-blown K8s running on your local machine, you will soon hit a wall if you want to play with multi-node cluster.
For tackling this very issue, we wi...]]></description><link>https://yankee.dev/multi-node-kubernetes-k3s-locally</link><guid isPermaLink="true">https://yankee.dev/multi-node-kubernetes-k3s-locally</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sat, 24 Oct 2020 15:12:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742066504/DcJZKhAl3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are a lot of tools that allow you to setup a local Kubernetes cluster in no time. But with a full-blown K8s running on your local machine, you will soon hit a wall if you want to play with multi-node cluster.</p>
<p>For tackling this very issue, we will be looking into how we can setup a lightweight Kubernetes cluster using <a target="_blank" href="https://k3s.io/">K3s</a> and <a target="_blank" href="https://multipass.run/">Multipass</a> for our VMs.</p>
<blockquote>
<p>If you are interested in what makes K3s so light, you can watch the talk on <a target="_blank" href="https://www.youtube.com/watch?v=-HchRyqNtkU">K3s under the hood</a>.</p>
</blockquote>
<h2 id="prerequisites">Prerequisites</h2>
<h3 id="installing-multipass">Installing Multipass</h3>
<p>Multipass is a command line tool to orchestrate virtual Ubuntu instances on your local machine. With the CLI, you can spawn VMs within minutes allocating optimal CPU, memory and disk space.</p>
<blockquote>
<p><a target="_blank" href="https://github.com/canonical/multipass#install-multipass">Download Multipass</a></p>
<h3 id="installing-k3sup-ketchup">Installing K3sup (ketchup)</h3>
</blockquote>
<p>We talked about K3s but it can be daunting to set up if you just want a quick hands on with Kubernetes. To abstract all that we will be installing a handy CLI called <a target="_blank" href="https://github.com/alexellis/k3sup">k3sup</a>. It will bootstrap Kubernetes cluster on our VMs using K3s within a minute! 🚀</p>
<p>k3sup uses SSH under the hood, so make sure you have SSH service on your machine.</p>
<blockquote>
<p><a target="_blank" href="https://github.com/alexellis/k3sup#download-k3sup-tldr">Install k3sup</a></p>
</blockquote>
<h2 id="creating-virtual-machines">Creating Virtual machines</h2>
<p>Nodes are just virtual machines on Kubernetes working in sync. For our cluster we will be creating 3 virtual machines, each with 1CPU, 1GB RAM, 2GB Storage.</p>
<h3 id="generating-keys">Generating keys</h3>
<p>If you have SSH installed and already have <code>~/.ssh/id_rsa</code> and <code>~/.ssh/id_rsa.pub</code> , you can skip this part and move to <strong>creating config file </strong>section.</p>
<p><strong>Create Private/Public key:</strong></p>
<pre><code class="lang-bash">$ ssh-keygen
</code></pre>
<p>This will create the aforementioned files on your system. Copy the contents of <code>~/.ssh/id_rsa.pub</code></p>
<pre><code class="lang-bash">$ cat ~/.ssh/id_rsa.pub
</code></pre>
<h3 id="creating-config-file"><strong>Creating config file</strong></h3>
<p>Create a file called <code>multipass.yaml</code> and place your public key in <code>ssh-rsa</code>.</p>
<pre><code class="lang-YAML"><span class="hljs-comment"># multipass.yaml</span>
<span class="hljs-attr">ssh_authorized_keys:</span>

  <span class="hljs-bullet">-</span> <span class="hljs-string">ssh-rsa</span> <span class="hljs-string">&lt;add-your-public-key&gt;</span>
</code></pre>
<p>This config file makes sure the public key is stored on the virtual machine once it’s created. Let’s create our VMs with proper names based on their roles we will be assigning (master/worker).</p>
<pre><code class="lang-bash">$ multipass launch --cpus 1 --mem 1G --disk 2G --name master-node --cloud-init multipass.yaml

$ multipass launch --cpus 1 --mem 1G --disk 2G --name agent-master --cloud-init multipass.yaml

$ multipass launch --cpus 1 --mem 1G --disk 2G --name agent-worker --cloud-init multipass.yaml

$ multipass ls
</code></pre>
<p>In K3s terms, a master node is called the server and the rest of the nodes are called agents. Agents are simply the nodes that gets added to the master node; they can be another master node or a worker node.</p>
<h2 id="adding-k8s-sauce-with-k3sup">Adding K8s sauce with k3sup</h2>
<p>Now that we have our VMs ready, let’s install Kubernetes on them. First we will be creating a master node to setup a control plane.</p>
<h3 id="adding-a-master-node">Adding a master node</h3>
<p>We will need the <code>IP</code> and <code>username</code> of our virtual machine to SSH into and install Kubernetes. Run <code>multipass ls</code> and take note of IP of <code>master-node</code> . All the usernames for VMs are <code>ubuntu</code> by default.</p>
<pre><code class="lang-bash">$ k3sup install --ip &lt;IP&gt; --user ubuntu --k3s-extra-args <span class="hljs-string">"--cluster-init"</span>
</code></pre>
<p>We are passing the <code>--k3s-extra-args "--cluster-init"</code> to make sure this node is prepared to connect with another master node, else it might cause errors.</p>
<p>Once installed it downloads the <code>kubeconfig</code> file on the directory where you invoked your command. You can set environment variable <code>KUBECONFIG</code> with path to recently downloaded <code>kubeconfig</code> file.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># mac / linux</span>
$ <span class="hljs-built_in">export</span> KUBECONFIG=&lt;path-to-kubeconfig&gt;

<span class="hljs-comment"># windows</span>
$ setx KUBECONFIG &lt;path-to-kubeconfig&gt;
</code></pre>
<p>Now you can <code>kubectl get nodes</code> to view the nodes on your cluster.</p>
<h3 id="adding-a-second-master-node-ha">Adding a second master node (HA)</h3>
<p>Multi master setup on local machine is an overkill for most use cases but for the purpose of this tutorial we are going to add it anyway. To join a new master node in the cluster we use the <code>join</code> command.</p>
<p>Here, all we have to do is introduce the server IP and username (previously created) that this node should connect to . And additionally pass the <code>--server</code> flag specifying this will be a server node (master).</p>
<pre><code class="lang-bash">$ k3sup install --ip &lt;IP-of-agent-master&gt; --user ubuntu --server-ip &lt;IP-of-master-node&gt; --server-user ubuntu --server
</code></pre>
<p>And that’s about it. Now you can <code>kubectl get nodes</code> again, and you’ll see two master nodes that is Highly Available (HA).</p>
<h2 id="adding-a-worker-node">Adding a worker node</h2>
<p>Finally, we have to setup our worker node where we will be deploying our applications and services.</p>
<p>For this grab the IP of <code>agent-worker</code> and pass on the same command as before minus the <code>--server</code> flag.</p>
<pre><code class="lang-bash">$ k3sup join --ip &lt;IP-of-agent-worker&gt; --user ubuntu --server-ip &lt;IP-of-master-node&gt; --server-user ubuntu
</code></pre>
<p>Now if you <code>kubectl get nodes</code> , you will find two master nodes and a single worker node composing your multi node cluster setup. You can create new VMs and add more master or worker nodes depending on how you plan to utilize your cluster.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Thank you for reading. Hope this guide has been helpful for you to setup and play with multi-node Kubernetes cluster. If you have any corrections, suggestions, or feedback feel free to DM me on <a target="_blank" href="https://twitter.com/yankexe">Twitter</a> or comment below.</p>
<p>Happy hacking! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Deploy your Serverless Python function locally with OpenFaas in Kubernetes]]></title><description><![CDATA[We have come a long way in building distributed applications. From monoliths to Microservices we have been able to decouple and scale our applications with optimal use of the underlying hardware resources. But now we are moving towards more lightweig...]]></description><link>https://yankee.dev/serverless-function-openfaas-kubernetes-locally</link><guid isPermaLink="true">https://yankee.dev/serverless-function-openfaas-kubernetes-locally</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Yankee Maharjan]]></dc:creator><pubDate>Sat, 15 Aug 2020 10:27:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742493246/bct3CHBXW.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We have come a long way in building distributed applications. From monoliths to Microservices we have been able to decouple and scale our applications with optimal use of the underlying hardware resources. But now we are moving towards more lightweight approach to deploy our applications; <strong>Serverless</strong>! With the inception of Function as a Service (FaaS), application modularization has been taking an ascent in the world of cloud computing.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/3JpnyF0agxY">https://youtu.be/3JpnyF0agxY</a></div>
<p>In this blog, we will be exploring the development to deployment of our Serverless function locally.</p>
<blockquote>
<p>The beauty of OpenFaas and KinD is that you don’t need to deploy your functions to cloud to test them, you can run them locally!</p>
</blockquote>
<h3 id="prerequisite"><strong>Prerequisite</strong>:</h3>
<p>You just need to have <strong>Docker</strong> installed on your machine. The rest we will setup as we go along. We will be installing a few tools so bear with me. 🐻</p>
<h3 id="openfaashttpswwwopenfaascom"><strong><a target="_blank" href="https://www.openfaas.com/">OpenFaas</a>?</strong> 🐬</h3>
<p>OpenFaas is an awesome tool/framework developed by <a target="_blank" href="https://github.com/sponsors/alexellis">Alex Ellis</a> with the Open Source community that helps to deploy burstable functions and microservices to Kubernetes without repetitive, boiler-plate coding.</p>
<p>It was built with developer experience in mind creating a low barrier of entry to the Kubernetes ecosystem. It comes with it’s own cli-tool called the <code>faas-cli</code> which makes a clever use of templates and makes pushing docker images and deployment of containers a breeze. And once deployed it takes care of auto-scaling and auto-provisioning based on the network traffic.</p>
<p>With all the overhead being handled by OpenFaas, all you have to do is focus on writing your function. But in our case we have some setting up to do 👨‍🏭</p>
<h3 id="properties-of-functions"><strong>Properties of functions</strong>:</h3>
<p>Here are few things to keep in mind when you are creating a function.</p>
<ul>
<li><p>A rule of thumb is to make your function stateless.</p>
</li>
<li><p>Function should be atomic (single responsibility).</p>
</li>
<li><p>It should be idempotent.</p>
</li>
<li><p>Trigger to the function could be TCP or event, however effect must be an event.</p>
</li>
</ul>
<h2 id="lets-get-started"><strong>Let’s get started!</strong> 🚀</h2>
<h3 id="install-arkade"><strong>Install arkade</strong></h3>
<p><a target="_blank" href="https://github.com/alexellis/arkade">arkade</a> will be our one stop tool to install and deploy apps and services to Kubernetes. Think of this tool as a package manager like <code>apt</code> or <code>pacman</code>. We will be needing a bunch of CLI utilities to get up and running.</p>
<p>Make sure you install this if you have never set up a Kubernetes tooling in your system.</p>
<pre><code class="lang-bash">$ curl -sLS https://dl.get-arkade.dev | sudo sh
</code></pre>
<p><strong>Just to make things clear:</strong></p>
<ul>
<li><p>use arkade get to download cli tools and applications related to Kubernetes.</p>
</li>
<li><p>use arkade install to install Kubernetes applications using <a target="_blank" href="https://helm.sh/docs/topics/charts/">helm charts</a> or vanilla <code>yaml</code> files.</p>
</li>
</ul>
<h3 id="install-kubectl"><strong>Install kubectl</strong></h3>
<p><a target="_blank" href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a> is a command line tool that talks to the Kubernetes Master Controller API for performing actions on our cluster.</p>
<pre><code class="lang-bash">$ arkade get kubectl

<span class="hljs-comment"># follow the instructions on the screen.</span>
</code></pre>
<h2 id="creating-a-local-kubernetes-cluster"><strong>Creating a local Kubernetes cluster</strong> 🧱</h2>
<p>We will be deploying our function on a local Kubernetes cluster so let’s set that up. We will be using a tool called <strong>KinD</strong>(Kubernetes in Docker) for this.</p>
<p>Unlike <a target="_blank" href="https://github.com/kubernetes/minikube">Minikube</a> which requires VM; KinD utilizes Docker container to deploy your cluster. It is comparatively faster.</p>
<h3 id="install-kind"><strong>Install KinD</strong></h3>
<p>Our goal is to keep everything local so we will be creating a local Docker registry (<a target="_blank" href="https://hub.docker.com/">Dockerhub</a> for your local machine). KinD provides a <a target="_blank" href="https://kind.sigs.k8s.io/docs/user/local-registry/">shell script</a> to create a Kubernetes cluster along with local Docker registry enabled.</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>
<span class="hljs-built_in">set</span> -o errexit

<span class="hljs-comment"># create registry container unless it already exists</span>
reg_name=<span class="hljs-string">'kind-registry'</span>
reg_port=<span class="hljs-string">'5000'</span>
running=<span class="hljs-string">"<span class="hljs-subst">$(docker inspect -f '{{.State.Running}}' <span class="hljs-string">"<span class="hljs-variable">${reg_name}</span>"</span> 2&gt;/dev/null || true)</span>"</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">${running}</span>"</span> != <span class="hljs-string">'true'</span> ]; <span class="hljs-keyword">then</span>
  docker run \
    -d --restart=always -p <span class="hljs-string">"<span class="hljs-variable">${reg_port}</span>:5000"</span> --name <span class="hljs-string">"<span class="hljs-variable">${reg_name}</span>"</span> \
    registry:2
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># create a cluster with the local registry enabled in containerd</span>
cat &lt;&lt;EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins.<span class="hljs-string">"io.containerd.grpc.v1.cri"</span>.registry.mirrors.<span class="hljs-string">"localhost:<span class="hljs-variable">${reg_port}</span>"</span>]
    endpoint = [<span class="hljs-string">"http://<span class="hljs-variable">${reg_name}</span>:<span class="hljs-variable">${reg_port}</span>"</span>]
EOF

<span class="hljs-comment"># connect the registry to the cluster network</span>
docker network connect <span class="hljs-string">"kind"</span> <span class="hljs-string">"<span class="hljs-variable">${reg_name}</span>"</span>

<span class="hljs-comment"># tell https://tilt.dev to use the registry</span>
<span class="hljs-comment"># https://docs.tilt.dev/choosing_clusters.html#discovering-the-registry</span>
<span class="hljs-keyword">for</span> node <span class="hljs-keyword">in</span> $(kind get nodes); <span class="hljs-keyword">do</span>
  kubectl annotate node <span class="hljs-string">"<span class="hljs-variable">${node}</span>"</span> <span class="hljs-string">"kind.x-k8s.io/registry=localhost:<span class="hljs-variable">${reg_port}</span>"</span>;
<span class="hljs-keyword">done</span>
</code></pre>
<blockquote>
<p>kind-with-registry.sh from <a target="_blank" href="https://kind.sigs.k8s.io/docs/user/local-registry/">KinD website</a></p>
</blockquote>
<p>Once you have the file on your local machine. Run the following command to make it executable.</p>
<pre><code class="lang-bash">$ chmod +x kind-with-registry.sh
</code></pre>
<p>Now you can run the script to create your local Kubernetes cluster with local Docker registry.</p>
<pre><code class="lang-bash">$ ./kind-with-registry.sh
</code></pre>
<p>To make sure the kubectl context is set to the newly created cluster; run:</p>
<pre><code class="lang-bash">$ kubectl config current-context
</code></pre>
<p>If the result is not <code>kind-kind</code>; then run:</p>
<pre><code class="lang-bash">$ kubectl config use kind-kind
</code></pre>
<p>Make sure your cluster is running:</p>
<pre><code class="lang-bash">$ kubectl cluster-info
</code></pre>
<p>Make sure Docker registry is running.</p>
<pre><code class="lang-bash">$ docker logs -f kind-registry
OR 
$ docker ps -l
</code></pre>
<h3 id="deploying-openfaas-to-kind-cluster"><strong>Deploying OpenFaas to KinD cluster</strong></h3>
<p>Now that we have our local Kubernetes cluster up and running lets deploy our OpenFaas services to support our functions.</p>
<pre><code class="lang-bash">$ arkade install openfaas
</code></pre>
<p>Make sure OpenFaas is deployed. It might take a minute or two.</p>
<pre><code class="lang-bash">$ kubectl get pods -n openfaas
</code></pre>
<h3 id="templateshttpsdocsopenfaascomclitemplatestemplates"><strong><a target="_blank" href="https://docs.openfaas.com/cli/templates/#templates">Templates</a></strong>📂</h3>
<p>Before we move to creating our function, it is essential to understand the concept of templates in OpenFaas.</p>
<p><strong>What are templates?</strong></p>
<p>Templates are basically a wrapper for your functions. Same template can be used for multiple functions.</p>
<p>OpenFaas already provides a variety of templates for different programming languages to start with.</p>
<p>There are two type of templates based on the webserver :</p>
<ol>
<li><p><strong>classic watchdog</strong> which uses template format from <a target="_blank" href="https://github.com/openfaas/templates">here</a>.</p>
</li>
<li><p><strong>of-watchdog</strong> which uses template format from <a target="_blank" href="https://github.com/openfaas-incubator?q=template&amp;type=&amp;language=">here</a>.</p>
</li>
</ol>
<p>Watchdogs are basically webservers to proxy request to our functions.</p>
<p>We will be using the templates that supports the latest <code>of-watchdog</code>. You can read more about <a target="_blank" href="https://docs.openfaas.com/architecture/watchdog/">OpenFaas watchdog</a>.</p>
<p>The most important piece in the templates directory is the Dockerfile, it drives everything from your watchdog to how and where your function gets placed in the template. You can also create your own template based on the need of your function.</p>
<p>Pull the template from store:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># list all the templates </span>
$ faas-cli template store list

<span class="hljs-comment"># pull python3-flask template from store</span>
$ faas-cli template store pull python3-flask
</code></pre>
<p>This will pull all the templates for python from OpenFaas store to your <strong>template</strong> directory of your project folder. We will be using the <a target="_blank" href="https://github.com/openfaas-incubator/python-flask-template/tree/master/template/python3-flask-debian">python3-flask-debian</a> template. You can ignore rest of the templates or delete them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742217127/MrsvXKZMe.png" alt="Openfaas template structure." /><em>Openfaas template structure.</em></p>
<p>Shown above is the basic template structure for all languages.</p>
<p><strong>Template structure explanation:</strong></p>
<ol>
<li><p><code>index.py</code> is an entrypoint to our Flask application.</p>
</li>
<li><p><code>requirements.txt</code> on the root project directory contains dependencies for our Flask application. It contains <a target="_blank" href="https://flask.palletsprojects.com/en/1.1.x/">Flask</a> and <a target="_blank" href="https://docs.pylonsproject.org/projects/waitress/en/latest/">waitress</a> (WSGI).</p>
</li>
<li><p><code>template.yml</code> contains the deployment specific configurations.</p>
</li>
<li><p><code>requirements.txt</code> inside of the function directory is for the function specific dependencies.</p>
</li>
<li><p><code>Dockerfile</code> contains the instructions to build image for our function.</p>
</li>
</ol>
<h2 id="building-our-function"><strong>Building our function</strong> 🔨</h2>
<p>Now it’s time to create our Serverless function.</p>
<p>We’ll keep the scope really small and build a simple dictionary function that returns the meaning of words you query. For this we will be using the <a target="_blank" href="https://pypi.org/project/PyDictionary/">PyDictionary</a> module.</p>
<p>Since we have our template in place, we can utilize it to scaffold our function. You may have noticed the <strong>function</strong> directory and <strong>template.yml</strong> file inside our <code>python3-flask-debian</code> template folder earlier. We can leverage that to create our new function using the <code>faas-cli</code>.</p>
<pre><code class="lang-bash">$ faas-cli new &lt;function-name&gt; --lang &lt;template-name&gt;

$ faas-cli new pydict --lang python3-flask-debian
</code></pre>
<p>This will create a folder with the function name(pydict) along with the contents we saw earlier inside the templates function folder, plus a <code>yaml</code> file with the function name(pydict).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742218989/DbrBlvYJr.png" alt /></p>
<p>Let’s add our dependency to the requirements.txt file of function folder.</p>
<pre><code class="lang-txt">PyDictionary==2.0.1
</code></pre>
<p>Update the <code>handler.py</code> with the following code.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> PyDictionary <span class="hljs-keyword">import</span> PyDictionary

dictionary = PyDictionary()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span>(<span class="hljs-params">word</span>):</span>     
    <span class="hljs-keyword">return</span> dictionary.meaning(word)
</code></pre>
<p>Our function is complete. ✔</p>
<h3 id="openfaas-yaml-configuration"><strong>Openfaas YAML configuration</strong> 📄</h3>
<p>A Kubernetes guide won’t be complete without talking about YAML files. They are necessary to describe the state of our deployments. <code>faas-cli</code> has already created a yaml file for our function, namely, <code>pydict.yml</code></p>
<p>This file will tell the <code>faas-cli</code> about the functions and images we want to deploy, language templates we want to use, scaling factor, and other configurations.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742220793/DrXNLm_vG.png" alt="contents of pydict.yml" /><em>contents of pydict.yml</em></p>
<h4 id="yaml-file-breakdown"><strong>YAML file breakdown</strong></h4>
<ol>
<li><p><code>gateway</code>: determines the IP address to connect to the OpenFaas service. Since we are testing it locally the gateway provided by default is fine. 
<strong>Note:</strong> if we were deploying on managed cloud: we will take the IP address of <code>gateway-external</code></p>
</li>
<li><p>Under <code>functions</code>: we can register different Serverless functions we want to deploy along with their configuration.</p>
</li>
<li><p><code>lang</code>: name of the language template the function uses.</p>
</li>
<li><p><code>handler</code>: location of our Serverless function.</p>
</li>
<li><p><code>image</code>: fully qualified docker registry URL along with image name and tag.</p>
</li>
</ol>
<p>Since we are using Local registry we have to change the image tag to incorporate it. Change value of image to the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742222732/DPXllRi5k.png" alt="Change image tag to use local container registry" /><em>Change image tag to use local container registry</em></p>
<h3 id="port-forward-to-localhost"><strong>Port-forward to Localhost</strong> ⏩</h3>
<p>Now, we need to port-forward the OpenFaas gateway service to our localhost port. Remember the gateway on our yaml file?</p>
<p>To check the gateway of OpenFaas service; run:</p>
<pre><code class="lang-bash">$ kubectl get service -n openfaas
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742224744/XZBHnN1Ml.png" alt /></p>
<pre><code class="lang-bash">$ kubectl port-forward -n openfaas svc/gateway 8080:8080
</code></pre>
<p>Although the the cluster is deployed locally, internally Kubernetes manages it’s own IP addresses. We are port forwarding to access the service inside the Kubernetes cluster from our local machine. Once that is done, open a new terminal window, and prepare for take off!</p>
<h2 id="build-push-deploy"><strong>Build ➡Push ➡Deploy</strong> 🚀</h2>
<p>We have most of the setup ready; now we can build our image, push it to the registry, and deploy it to Kubernetes.</p>
<p><strong>Build our image</strong></p>
<pre><code class="lang-bash">$ faas-cli build -f pydict.yml
</code></pre>
<p><strong>Push our image to the local registry</strong></p>
<pre><code class="lang-bash">$ faas-cli push -f pydict.yml
</code></pre>
<p>Now to deploy the function to the cluster; we need to login to <code>faas-cli</code> Let’s generate credentials for authentication.</p>
<p><strong>Generate password:</strong></p>
<pre><code class="lang-bash">$ PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath=<span class="hljs-string">"{.data.basic-auth-password}"</span> | base64 --decode; <span class="hljs-built_in">echo</span>)
</code></pre>
<p><strong>Check if password exists:</strong></p>
<pre><code class="lang-bash">$ env | grep PASSWORD
</code></pre>
<p>Now, login to <code>faas-cli</code> :</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">echo</span> -n <span class="hljs-variable">$PASSWORD</span> | faas-cli login --username admin --password-stdin
</code></pre>
<p><strong>Time for deployment:</strong></p>
<pre><code class="lang-bash">$ faas-cli deploy -f pydict.yml -g http://127.0.0.1:8080
</code></pre>
<p>Check if our deployment has been successful.</p>
<pre><code class="lang-bash">$ kubectl get pods -n openfaas-fn
</code></pre>
<h2 id="testing-our-function"><strong>Testing our function</strong> 👨‍🔬</h2>
<h3 id="test-using-cli"><strong>Test using CLI</strong></h3>
<p>We can invoke our function from CL I to get the result.</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">echo</span> <span class="hljs-string">"brevity"</span> | faas-cli invoke pydict
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742226757/p7fQEYmMn.png" alt /></p>
<h3 id="test-using-openfaas-portal"><strong>Test using Openfaas Portal</strong></h3>
<p>OpenFaas also comes with a neat UI portal to invoke our function.</p>
<p>Navigate to <code>localhost:8080</code> in your browser. A prompt will ask you for username and password. Use <strong>admin</strong> for <em>username</em> and for <em>password</em> check the password we generated earlier. In case you forgot, you can always repeat the earlier command, i.e.</p>
<pre><code class="lang-bash">PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath=<span class="hljs-string">"{.data.basic-auth-password}"</span> | base64 --decode; <span class="hljs-built_in">echo</span>) &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-variable">$PASSWORD</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620742228689/G36lm1WeI.png" alt="OpenFaas UI" /><em>OpenFaas UI</em></p>
<h3 id="build-more-functions"><strong>Build more functions</strong> 🔨</h3>
<p>Now that you have your cluster deployed with OpenFaas, you can create function with ease and deploy them. If you want to create a new function, all you have to do is:</p>
<ol>
<li><p>scaffold a new function using <code>faas-cli new</code></p>
</li>
<li><p>write your function</p>
</li>
<li><p><code>faas-cli up -f &lt;config-yaml-file&gt;</code></p>
</li>
</ol>
<p><code>faas-cli up</code> is a shortcut for <code>build, push and deploy</code> command.</p>
<h2 id="conclusion"><strong>Conclusion</strong></h2>
<p>Hope this has been a helpful guide in learning how to use OpenFaas for Serverless deployment. If you have any queries or suggestions, let’s talk in the comment section or you can <a target="_blank" href="https://twitter.com/yankexe">DM me on Twitter</a>.</p>
<blockquote>
<p><strong>Check out example <a target="_blank" href="https://github.com/yankeexe/openfaas-functions">functions on GitHub</a></strong></p>
</blockquote>
]]></content:encoded></item></channel></rss>