<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Tutorial on alikhil</title>
    <link>https://alikhil.dev/tags/tutorial/</link>
    <description>Recent content in Tutorial on alikhil</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 23 Jun 2025 15:00:00 +0300</lastBuildDate><atom:link href="https://alikhil.dev/tags/tutorial/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Why Graceful Shutdown Matters in Kubernetes</title>
      <link>https://alikhil.dev/posts/graceful-shutdown/</link>
      <pubDate>Sun, 01 Jun 2025 20:31:06 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/graceful-shutdown/</guid>
      <description>&lt;p&gt;Have you ever deployed a new version of your app in Kubernetes and noticed errors briefly spiking during rollout? Many teams do not even realize this is happening, especially if they are not closely monitoring their error rates during deployments.&lt;/p&gt;
&lt;p&gt;There is a common misconception in the Kubernetes world that bothers me. The official Kubernetes &lt;a href=&#34;https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/&#34;&gt;documentation&lt;/a&gt; and most guides claim that &amp;ldquo;if you want zero downtime upgrades, just use rolling update mode on deployments&amp;rdquo;. I have learned the hard way that this simply it is not true - rolling updates alone are &lt;strong&gt;NOT enough&lt;/strong&gt; for true zero-downtime deployments.&lt;/p&gt;
&lt;p&gt;And it is not just about deployments. Your pods can be terminated for many other reasons: scaling events, node maintenance, preemption, resource constraints, and more. Without proper graceful shutdown handling, any of these events can lead to dropped requests and frustrated users.&lt;/p&gt;
&lt;p&gt;In this post, I will share what I have learned about implementing proper graceful shutdown in Kubernetes. I will show you exactly what happens behind the scenes, provide working code examples, and back everything with real test results that clearly demonstrate the difference.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Have you ever deployed a new version of your app in Kubernetes and noticed errors briefly spiking during rollout? Many teams do not even realize this is happening, especially if they are not closely monitoring their error rates during deployments.</p>
<p>There is a common misconception in the Kubernetes world that bothers me. The official Kubernetes <a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/">documentation</a> and most guides claim that &ldquo;if you want zero downtime upgrades, just use rolling update mode on deployments&rdquo;. I have learned the hard way that this simply it is not true - rolling updates alone are <strong>NOT enough</strong> for true zero-downtime deployments.</p>
<p>And it is not just about deployments. Your pods can be terminated for many other reasons: scaling events, node maintenance, preemption, resource constraints, and more. Without proper graceful shutdown handling, any of these events can lead to dropped requests and frustrated users.</p>
<p>In this post, I will share what I have learned about implementing proper graceful shutdown in Kubernetes. I will show you exactly what happens behind the scenes, provide working code examples, and back everything with real test results that clearly demonstrate the difference.</p>
<h2 id="the-problem-hidden-errors-during-pod-termination">The Problem: Hidden Errors During Pod Termination</h2>
<figure class="align-center ">
    <img loading="lazy" src="/images/posts/k8s-graceful-shutdown.png#center"/><figcaption>
            ChatGPT: draw funny picture of Kubernetes pod gracefully shutting down
        </figcaption>
</figure>
<p>If you are running services on Kubernetes, you have probably noticed that even with rolling updates (where Kubernetes gradually replaces pods), you might still see errors during deployment. This is especially annoying when you are trying to maintain &ldquo;zero-downtime&rdquo; systems.</p>
<p>When Kubernetes needs to terminate a pod (for any reason), it follows this sequence:</p>
<ol>
<li>Sends a SIGTERM signal to your container</li>
<li>Waits for a grace period (30 seconds by default)</li>
<li>If the container does not exit after the grace period, it gets brutal and sends a SIGKILL signal</li>
</ol>
<p>The problem? Most applications do not properly handle that SIGTERM signal. They just die immediately, dropping any in-flight requests. In the real world, while most API requests complete in 100-300ms, there are often those long-running operations that take 5-15 seconds or more. Think about processing uploads, generating reports, or running complex database queries. When these longer operations get cut off, that&rsquo;s when users really feel the pain.</p>
<h3 id="when-does-kubernetes-terminate-pods">When Does Kubernetes Terminate Pods?</h3>
<p>Rolling updates are just one scenario where your pods might be terminated. Here are other common situations that can lead to pod terminations:</p>
<ul>
<li>
<p><strong>Horizontal Pod Autoscaler Events</strong>: When HPA scales down during low-traffic periods, some pods get terminated.</p>
</li>
<li>
<p><strong>Resource Pressure</strong>: If your nodes are under resource pressure, the Kubernetes scheduler might decide to evict certain pods.</p>
</li>
<li>
<p><strong>Node Maintenance</strong>: During cluster upgrades, node draining causes many pods to be evicted.</p>
</li>
<li>
<p><strong>Spot/Preemptible Instances</strong>: If you are using cost-saving node types like spot instances, these can be reclaimed with minimal notice.</p>
</li>
</ul>
<p>All these scenarios follow the same termination process, so implementing proper graceful shutdown handling protects you from errors in all of these cases - not just during upgrades.</p>
<h2 id="lets-test-it-basic-vs-graceful-service">Let&rsquo;s Test It: Basic vs. Graceful Service</h2>
<p>Instead of just talking about theory, I built a small lab to demonstrate the difference between proper and improper shutdown handling. I created two nearly identical Go services:</p>
<ul>
<li><strong>Basic Service</strong>: A standard HTTP server with no special shutdown handling</li>
<li><strong>Graceful Service</strong>: The same service but with proper SIGTERM handling</li>
</ul>
<p>Both services:</p>
<ul>
<li>Process requests that take about 4 seconds to complete (intentionally configured for easier demonstration)</li>
<li>Run in the same Kubernetes cluster with identical configurations</li>
<li>Serve the same endpoints</li>
</ul>
<p>I specifically chose a 4-second processing time to make the problem obvious. While this might seem long compared to typical 100-300ms API calls, it perfectly simulates those problematic long-running operations that occur in real-world applications. The only difference between the services is how they respond to termination signals.</p>
<p>To test them, I wrote a simple k6 script that hammers both services with requests while triggering rolling restart of service&rsquo;s deployment. Here is what happened:</p>
<h3 id="basic-service-the-error-prone-one">Basic Service: The Error-Prone One</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">checks_total.......................: 695    11.450339/s
</span></span><span class="line"><span class="cl">checks_succeeded...................: 97.98% 681 out of 695
</span></span><span class="line"><span class="cl">checks_failed......................: 2.01%  14 out of 695
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">✗ status is 200
</span></span><span class="line"><span class="cl">  ↳  97% — ✓ 681 / ✗ 14
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">http_req_failed....................: 2.01%  14 out of 696
</span></span></code></pre></div><h3 id="graceful-service-the-reliable-one">Graceful Service: The Reliable One</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">checks_total.......................: 750     11.724824/s
</span></span><span class="line"><span class="cl">checks_succeeded...................: 100.00% 750 out of 750
</span></span><span class="line"><span class="cl">checks_failed......................: 0.00%   0 out of 750
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">✓ status is 200
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">http_req_failed........... ........: 0.00%  0 out of 751
</span></span></code></pre></div><p>The results speak for themselves. The basic service dropped 14 requests during the update (that is 2% of all traffic), while the graceful service handled everything perfectly without a single error.</p>
<p>You might think &ldquo;2% it is not that bad&rdquo; — but if you are doing several deployments per day and have thousands of users, that adds up to a lot of errors. Plus, in my experience, these errors tend to happen at the worst possible times.</p>
<h2 id="so-how-do-we-fix-it-the-graceful-shutdown-recipe">So How Do We Fix It? The Graceful Shutdown Recipe</h2>
<p>After digging into this problem and testing different solutions, I have put together a simple recipe for proper graceful shutdown. While my examples are in Go, the fundamental principles apply to any language or framework you are using.</p>
<p>Here are the key ingredients:</p>
<h3 id="1-listen-for-sigterm-signals">1. Listen for SIGTERM Signals</h3>
<p>First, your app needs to catch that SIGTERM signal instead of ignoring it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Set up channel for shutdown signals</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">stop</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Signal</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">signal</span><span class="p">.</span><span class="nf">Notify</span><span class="p">(</span><span class="nx">stop</span><span class="p">,</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Interrupt</span><span class="p">,</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGTERM</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Block until we receive a shutdown signal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">&lt;-</span><span class="nx">stop</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Shutdown signal received&#34;</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></div><p>This part is easy - you are just telling your app to wake up when Kubernetes asks it to shut down.</p>
<h3 id="2-track-your-in-flight-requests">2. Track Your In-Flight Requests</h3>
<p>You need to know when it is safe to shut down, so keep track of ongoing requests:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Create a request counter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nx">inFlightRequests</span><span class="w"> </span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/process&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Increment counter when request starts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">inFlightRequests</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// do not forget to decrement when done!</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">inFlightRequests</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Your normal request handling...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">4</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">  </span><span class="c1">// Simulating long-running work</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</span></span></span></code></pre></div><p>This counter lets you check if there are still requests being processed before shutting down. it is especially important for those long-running operations that users have already waited several seconds for - the last thing they want is to see an error right before completion!</p>
<h3 id="3-separate-your-health-checks">3. Separate Your Health Checks</h3>
<p>Here is a commonly overlooked trick - you need different health check endpoints for liveness and readiness:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Track shutdown state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nx">isShuttingDown</span><span class="w"> </span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Readiness probe - returns 503 when shutting down</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/ready&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">isShuttingDown</span><span class="p">.</span><span class="nf">Load</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusServiceUnavailable</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Shutting down, not ready&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Ready for traffic&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Liveness probe - always returns 200 (we are still alive!)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/alive&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;I&#39;m alive&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">})</span><span class="w">
</span></span></span></code></pre></div><p>This separation is crucial. The readiness probe tells Kubernetes to stop sending new traffic, while the liveness probe says &ldquo;do not kill me yet, I&rsquo;m still working!&rdquo;</p>
<h3 id="4-the-shutdown-dance">4. The Shutdown Dance</h3>
<p>Now for the most important part - the shutdown sequence:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Step 1: Mark service as shutting down</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">isShuttingDown</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Step 2: Let Kubernetes notice the readiness probe failing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">5</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Step 3: Wait for in-flight requests to finish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">for</span><span class="w"> </span><span class="nx">inFlightRequests</span><span class="p">.</span><span class="nf">Load</span><span class="p">()</span><span class="w"> </span><span class="p">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// Step 4: Finally, shut down the server gracefully</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">server</span><span class="p">.</span><span class="nf">Shutdown</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Forced shutdown: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>I&rsquo;ve found this sequence to be optimal. First, we mark ourselves as &ldquo;not ready&rdquo; but keep running. We pause to give Kubernetes time to notice and update its routing. Then we patiently wait until all in-flight requests finish before actually shutting down the server.</p>
<h3 id="5-configure-kubernetes-correctly">5. Configure Kubernetes Correctly</h3>
<p>Do not forget to adjust your Kubernetes configuration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Use different probes for liveness and readiness</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/alive </span><span class="w"> </span><span class="c"># Always returns OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/ready </span><span class="w"> </span><span class="c"># Returns 503 during shutdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># Give pods enough time to shut down gracefully</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">terminationGracePeriodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w"> </span><span class="c"># Stops routing traffic after 2 failed checks (6 seconds)</span><span class="w">
</span></span></span></code></pre></div><p>This tells Kubernetes to wait up to 30 seconds for your app to finish processing requests before forcefully terminating it.</p>
<h2 id="tl-dr-quick-tips">TL; DR; Quick Tips</h2>
<p>If you are in a hurry, here are the key takeaways:</p>
<ol>
<li>
<p><strong>Catch SIGTERM Signals</strong>: Do not let your app be surprised when Kubernetes wants it to shut down.</p>
</li>
<li>
<p><strong>Track In-Flight Requests</strong>: Know when it is safe to exit by counting active requests.</p>
</li>
<li>
<p><strong>Split Your Health Checks</strong>: Use separate endpoints for liveness (am I running?) and readiness (can I take traffic?).</p>
</li>
<li>
<p><strong>Fail Readiness First</strong>: As soon as shutdown begins, start returning &ldquo;not ready&rdquo; on your readiness endpoint.</p>
</li>
<li>
<p><strong>Wait for Requests</strong>: Do not just shut down - wait for all active requests to complete first.</p>
</li>
<li>
<p><strong>Use Built-In Shutdown</strong>: Most modern web frameworks have graceful shutdown options; use them!</p>
</li>
<li>
<p><strong>Configure Terminaton Grace Period</strong>: Give your pods enough time to complete the shutdown sequence.</p>
</li>
<li>
<p><strong>Test Under Load</strong>: You will not catch these issues in simple tests - you need realistic traffic patterns.</p>
</li>
</ol>
<h2 id="wrap-up-is-it-worth-the-extra-code">Wrap Up: Is It Worth the Extra Code?</h2>
<p>You might be wondering if adding all this extra code is really worth it. After all, we&rsquo;re only talking about a 2% error rate during pod termination events.</p>
<p>From my experience working with high-traffic services, I would say absolutely yes - for three reasons:</p>
<ol>
<li>
<p><strong>User Experience</strong>: Even small error rates look bad to users. Nobody wants to see &ldquo;Something went wrong&rdquo; messages, especially after waiting 10+ seconds for a long-running operation to complete.</p>
</li>
<li>
<p><strong>Cascading Failures</strong>: Those errors can cascade through your system, especially if services depend on each other. Long-running requests often touch multiple critical systems.</p>
</li>
<li>
<p><strong>Deployment Confidence</strong>: With proper graceful shutdown, you can deploy more frequently without worrying about causing problems.</p>
</li>
</ol>
<p>The good news is that once you have implemented this pattern once, it is easy to reuse across your services. You can even create a small library or template for your organization.</p>
<p>In production environments where I have implemented these patterns, we have gone from seeing a spike of errors with every deployment to deploying multiple times per day with zero impact on users. that is a win in my book!</p>
<h2 id="further-reading">Further Reading</h2>
<p>If you want to dive deeper into this topic, I recommend checking out the article <a href="https://learnk8s.io/graceful-shutdown">Graceful shutdown and zero downtime deployments in Kubernetes</a> from learnk8s.io. It provides additional technical details about graceful shutdown in Kubernetes, though it does not emphasize the critical role of readiness probes in properly implementing the pattern as we have discussed here.</p>
<hr>
<p>For those interested in seeing the actual code I used in my testing lab, I&rsquo;ve published it on <a href="https://github.com/alikhil/gracefull-shutdown-lab">GitHub</a> with instructions for running the demo yourself.</p>
<p>Have you implemented graceful shutdown in your services? Did you encounter any other edge cases I didn&rsquo;t cover? Let me know in the comments how this pattern has worked for you!</p>]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use 3 Computers with One Monitor, Keyboard and Mouse – Without a KVM Switch</title>
      <link>https://alikhil.dev/posts/how-to-use-3-computers-without-kvm/</link>
      <pubDate>Mon, 23 Jun 2025 15:00:00 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/how-to-use-3-computers-without-kvm/</guid>
      <description>&lt;p&gt;Hi there! Today, I want to share how I organize my three-computer setup (MacBook Air, Mac mini, and Raspberry Pi) without a KVM switch, using a single keyboard, mouse, and monitor.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hi there! Today, I want to share how I organize my three-computer setup (MacBook Air, Mac mini, and Raspberry Pi) without a KVM switch, using a single keyboard, mouse, and monitor.</p>
<h2 id="my-devices">My devices</h2>
<h3 id="computers">Computers</h3>
<ul>
<li>Macbook Air M2 14&quot; – work Laptop.</li>
<li>Mac Mini M1 – my personal computer.</li>
<li><a href="https://www.clockworkpi.com/uconsole">ClockworkPi uConsole</a>, based on <a href="https://www.raspberrypi.com/products/compute-module-4/">RPI CM4</a> is a portable device for Linux tinkering.</li>
</ul>
<h3 id="peripheral-devices">Peripheral devices</h3>
<ul>
<li>The <a href="https://www.amazon.com/UGREEN-Bluetooth-Ergonomic-Prevention-Compatible/dp/B0CM6FSPY3">UGREEN Vertical Wireless</a> - can connect up to three devices: Two via Bluetooth and one via a 2.4GHz USB dongle.</li>
<li>The <a href="https://www.keychron.com/products/keychron-k15-max-alice-layout-qmk-wireless-custom-mechanical-keyboard?srsltid=AfmBOoo83yQiVI4ctH1noZzohk5v1fqAPMx_RbL86q7PxBWGvmSdUtw1">Keychrone K15 Max</a> allows me to connect up to three devices via Bluetooth.</li>
<li>The <a href="https://www.dell.com/es-es/shop/monitor-dell-27-4k-uhd-con-usb-c-s2722qc/apd/210-bbrq/monitores-y-accesorios">Monitor Dell 27 4K UHD con USB-C (S2722QC)</a> with two HDMI inputs and one USB-C port which allows me to connect it up to three devices.</li>
</ul>
<p>As you can see, all peripheral devices could connect to all my computers. At least in theory.</p>
<h2 id="connecting-devices">Connecting devices</h2>
<p>Historically my devices were numbered as follows:</p>
<ol>
<li>uConsole</li>
<li>Macbook Air</li>
<li>Mac Mini</li>
</ol>
<h3 id="keyboard">Keyboard</h3>
<p>The keyboard connection scheme is straightforward: uConsole connects via Bluetooth to the first device, the MacBook Air, as the second device, and the Mac Mini as the third device.</p>
<p>Later, I can use the <code>Fn + 1/2/3</code> hotkey to switch the keyboard between devices.</p>
<p><img loading="lazy" src="/images/posts/kvm/kvm-keyboard.png" type="" alt="Switching devices for keyboard"  /></p>
<h3 id="mouse">Mouse</h3>
<p>The first connection of the Ugreen mouse is reserved for the 2.4 GHz USB dongle. I plugged it into my uConsole, and then my MacBook Air and Mac mini connected as the second and third devices, respectively.</p>
<p>To switch devices with the mouse, press the red button on the bottom.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Dk_LNbkvECs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h3 id="monitor">Monitor</h3>
<p>The MacBook Air is connected to the USB-C port, which charges the computer.</p>
<p>The Mac Mini connects via HDMI-1, and the uConsole connects via HDMI-2.</p>
<h4 id="easiest-way-to-switch-devices">Easiest way to switch devices</h4>
<p>The easiest way to switch devices on the monitor is to press the small, round button in the bottom right corner to open the settings menu.</p>
<p><img loading="lazy" src="/images/posts/kvm/kvm-monitor-2.jpg" type="" alt="Bottom corner buttons"  /></p>
<p>Then, choose the desired port.</p>
<p><img loading="lazy" src="/images/posts/kvm/kvm-monitor.jpg" type="" alt="Monitor menu"  /></p>
<p>However, <strong>&ldquo;easiest&rdquo;</strong> does not mean <strong>&ldquo;most comfortable&rdquo;</strong>. Changing the monitor&rsquo;s input source this way requires pressing these small buttons several times. You also need to keep track of which devices are connected to HDMI-1 and HDMI-2.</p>
<h4 id="the-comfortable-way-to-switch-devices">The comfortable way to switch devices</h4>
<p>Instead of pressing buttons on the monitor and navigating its menu, I could press a hotkey on my keyboard to trigger the monitor to change input sources via its API (Display Data Channel/Command Interface Standard (DDC/CI).).</p>
<p>I have explained in detail how one could do it on a Mac in my another post - <a href="https://alikhil.dev/posts/monitor-input-source-control-mac/">Monitor input source control on Mac</a>.</p>
<p>I won&rsquo;t go into detail here. I&rsquo;ll just say that on my Mac Mini and MacBook Air, I have configured the following:</p>
<p><strong>Mac Mini (#3)</strong></p>
<ul>
<li>On <code>CMD + F1</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/raycast/switch-monitor-to-uconsole.sh">uConsole</a></li>
<li>On <code>CMD + F2</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/raycast/switch-monitor.sh">Macbook Air</a></li>
</ul>
<p><strong>Macbook Air (#2)</strong></p>
<ul>
<li>On <code>CMD + F1</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/raycast/switch-monitor-to-uconsole.sh">uConsole</a></li>
<li>On <code>CMD + F3</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/raycast/switch-monitor.sh">Mac mini</a></li>
</ul>
<p>For <strong>uConsole Linux machine (#1)</strong> I used <a href="https://github.com/rockowitz/ddcutil">ddcutil</a> and configured the following hotkeys:</p>
<ul>
<li><code>Ctrl + F2</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/linux/switch-to-macbook.sh">Macbook Air</a></li>
<li><code>Ctrl + F3</code> triggers switch to <a href="https://github.com/alikhil/configs/blob/master/linux/switch-to-mac-mini.sh">Mac Mini</a></li>
</ul>
<h2 id="switch-sequence">Switch sequence</h2>
<p>Once all the devices are connected and the hotkeys are configured, the following sequence of actions is needed to switch to device <code>#N</code> (where N is 1, 2, or 3):</p>
<ol>
<li>Switch the monitor input source by pressing <code>CMD + F#N</code>.</li>
<li>Switch the keyboard device by pressing <code>Fn + #N</code>.</li>
<li>Switch the mouse by pressing red button on the bottom of it until the indicator is under <code>#N</code> is blinking.</li>
</ol>
<p>For example, let&rsquo;s assume I am currently using my third device – Mac Mini, to switch to Macbook Air (#2) I do:</p>
<ol>
<li>Press <code>CMD + F2</code></li>
<li>Press <code>Fn + 2</code></li>
<li>Press red button on the bottom of the mouse until indicator under 2 is on.</li>
</ol>
<p>Here is a demo video:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/NgVESzIzpw4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="pros-and-cons">Pros and Cons</h2>
<h3 id="pros">Pros</h3>
<ul>
<li>I don&rsquo;t need a special KVM switch to switch between devices.</li>
<li>I use my keyboard to switch (for two-thirds of the KVM).</li>
</ul>
<h3 id="cons">Cons</h3>
<ul>
<li>Switching the mouse takes longer since there is only one way iteration over the connected devices.</li>
<li>Three actions are needed to switch all peripheral devices, as opposed to one action with a KVM.</li>
<li>I still need to manually reconnect other peripheral devices, e.g. web camera.</li>
</ul>
<h2 id="final-thoughts">Final thoughts</h2>
<p>It has some limitations, but if you have a keyboard and mouse that support multiple connected devices, as well as a monitor, you won&rsquo;t need a special KVM switch to switch between computers.</p>
<p>Thank you for reading this!</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Remote LAN access with WireGuard and Mikrotik</title>
      <link>https://alikhil.dev/posts/remote-lan-access-with-wireguard-and-mikrotik/</link>
      <pubDate>Sun, 13 Apr 2025 14:11:43 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/remote-lan-access-with-wireguard-and-mikrotik/</guid>
      <description>&lt;p&gt;Recently I have configured out how to access my home and cloud network remotely using WireGuard and Mikrotik Hex S router. With this step-by-step tutorial I will show you (and perhaps my future self) how to do it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I have configured out how to access my home and cloud network remotely using WireGuard and Mikrotik Hex S router. With this step-by-step tutorial I will show you (and perhaps my future self) how to do it.</p>
<h2 id="requirements">Requirements</h2>
<h3 id="what-i-have">What I have</h3>
<ul>
<li>
<p>A Mikrotik Hex S router with a dynamic public IP address.</p>
</li>
<li>
<p>Services in a cloud VM (Ubuntu 22) with a static public IP address.</p>
</li>
<li>
<p>Services in a VM on my home network.</p>
</li>
<li>
<p>Clients - laptops and phones - that need to access the services in my home and cloud network.</p>
</li>
</ul>
<h3 id="what-i-want">What I want</h3>
<ul>
<li>
<p>Clients outside of my home network should be able to access services both on my home and cloud network.</p>
</li>
<li>
<p>Only traffic to my home and cloud network should be routed through the VPN.</p>
</li>
<li>
<p>Clients inside my home network should be able to access services on my cloud network without additional configuration.</p>
</li>
<li>
<p>No external centralized service should be used.</p>
</li>
<li>
<p>No open ports on my home router.</p>
</li>
</ul>
<p><img loading="lazy" src="/images/posts/mikrotik-wg/final.png" type="" alt="Final configuration"  /></p>
<h2 id="implementation">Implementation</h2>
<blockquote>
<p>Nowadays, there are plenty of VPN solutions like zero-tier and tailscale. However, I think they are too bloated for my humble needs and WireGuard is more than enough for that.</p></blockquote>
<p>Because of last requirement, it&rsquo;s obvious that traffic to home network should be routed though my cloud server.<br>
So, I will use WireGuard to create a tunnel between mikrotik router and cloud server.<br>
This way, I can access my home network from anywhere without exposing any ports on my home router.</p>
<h3 id="i-initial-configuration-of-wireguard-on-cloud-vm">I. Initial configuration of WireGuard on cloud VM</h3>
<blockquote>
<p><em>For the sake of reproducibility and simplicity I will use <a href="https://www.wireguard.com/">vanilla wireguard</a> and configure it on OS level, not in docker container.</em></p></blockquote>
<h4 id="1-install-wireguard-on-ubuntu-2204">1. Install wireguard on Ubuntu 22.04</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install wireguard -y
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Edit the configuration file</span>
</span></span><span class="line"><span class="cl">sudo nano /etc/sysctl.conf
</span></span><span class="line"><span class="cl"><span class="c1"># Find and uncomment the line:</span>
</span></span><span class="line"><span class="cl">net.ipv4.ip_forward<span class="o">=</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Save and exit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then apply the changes:</span>
</span></span><span class="line"><span class="cl">sudo sysctl -p
</span></span></code></pre></div><h4 id="2-create-public-and-private-key">2. Create public and private key</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo su -
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /etc/wireguard
</span></span><span class="line"><span class="cl"><span class="c1"># Generate private and public keys</span>
</span></span><span class="line"><span class="cl">wg genkey <span class="p">|</span> tee privatekey <span class="p">|</span> wg pubkey &gt; publickey
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check the keys</span>
</span></span><span class="line"><span class="cl">cat privatekey
</span></span><span class="line"><span class="cl">cat publickey
</span></span></code></pre></div><h4 id="3-create-the-configuration-file">3. Create the configuration file</h4>
<p>I will use <code>172.16.10.0/24</code> subnet for Wireguard network.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/wireguard/wg0.conf
</span></span><span class="line"><span class="cl"><span class="c1"># Add the following lines:</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>Interface<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">Address</span> <span class="o">=</span> 172.16.10.1/24
</span></span><span class="line"><span class="cl"><span class="nv">ListenPort</span> <span class="o">=</span> <span class="m">27277</span> <span class="c1"># You can choose any port you want</span>
</span></span><span class="line"><span class="cl"><span class="nv">Privatekey</span> <span class="o">=</span> &lt;your_private_key&gt; <span class="c1"># from previous step</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># These are placeholder lines for next steps, keep them commented for now</span>
</span></span><span class="line"><span class="cl"><span class="c1"># # Mikrotik peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [Peer]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AllowedIPs = 172.16.10.0/24,192.168.0.0/16</span>
</span></span><span class="line"><span class="cl"><span class="c1"># PublicKey = mikrotik public key</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># # client peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [Peer]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AllowedIPs = 172.16.10.2/32</span>
</span></span><span class="line"><span class="cl"><span class="c1"># PublicKey = first client device public key</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># CRTL+X: save and exit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Change the permissions of the configuration file</span>
</span></span><span class="line"><span class="cl">chmod <span class="m">600</span> wg0.conf
</span></span></code></pre></div><h4 id="4-start-the-wireguard-interface">4. Start the WireGuard interface</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Enable service to start on boot</span>
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> wg-quick@wg0.service
</span></span><span class="line"><span class="cl">systemctl daemon-reload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start the service</span>
</span></span><span class="line"><span class="cl">systemctl start wg-quick@wg0
</span></span><span class="line"><span class="cl"><span class="c1"># Check wg status</span>
</span></span><span class="line"><span class="cl">wg
</span></span></code></pre></div><p>It should show the interface is up like this</p>
<p><img loading="lazy" src="/images/posts/mikrotik-wg/wg.png" type="" alt="WireGuard status"  /></p>
<p>More commands to check the status of the service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Check the status of the service</span>
</span></span><span class="line"><span class="cl">systemctl status wg-quick@wg0
</span></span><span class="line"><span class="cl"><span class="c1"># Restart the service if needed</span>
</span></span><span class="line"><span class="cl">systemctl restart wg-quick@wg0
</span></span></code></pre></div><h3 id="ii-mikrotik-configuration">II. Mikrotik configuration</h3>
<p>I will use Mikrotik command line interface (CLI) to configure the router. You can use Winbox or WebFig if you prefer.</p>
<h4 id="1-create-wireguard-interface">1. Create WireGuard interface</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Create WireGuard interface and it will automatically generate private and public keys</span>
</span></span><span class="line"><span class="cl">/interface wireguard add listen-port<span class="o">=</span><span class="m">13231</span> <span class="nv">mtu</span><span class="o">=</span><span class="m">1420</span> <span class="nv">name</span><span class="o">=</span>cloud-wg
</span></span><span class="line"><span class="cl"><span class="c1"># Add the IP address to the interface</span>
</span></span><span class="line"><span class="cl">/ip add <span class="nv">address</span><span class="o">=</span>172.16.10.3/24 <span class="nv">interface</span><span class="o">=</span>cloud-wg <span class="nv">network</span><span class="o">=</span>172.16.10.0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Print the keys, you will need only the public key later</span>
</span></span><span class="line"><span class="cl">/interface/wireguard/print
</span></span></code></pre></div><h4 id="2-create-the-peer">2. Create the peer</h4>
<p>Here we add cloud vm as a peer to the Mikrotik router&rsquo;s wireguard. The public key of the cloud server is needed here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># In allowed-address we add IP ranges which will be routed through this peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 172.21.0.0/16 - subnet of docker containers on cloud server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 172.16.10.0/16 - subnet of wireguard network</span>
</span></span><span class="line"><span class="cl">/interface wireguard peers add allowed-address<span class="o">=</span>172.16.10.0/24,172.21.0.0/16 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    endpoint-address<span class="o">=</span>cloud.vm.ip.address <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    endpoint-port<span class="o">=</span><span class="m">27277</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="nv">interface</span><span class="o">=</span>cloud-wg <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    public-key<span class="o">=</span><span class="s2">&#34;PUT CLOUD SERVER PUBLIC KEY HERE&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="nv">name</span><span class="o">=</span>peer1 persistent-keepalive<span class="o">=</span>25s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Here is a tricky part</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Routing rule for subnet of wireguard network is added automatically on adding IP address to the interface</span>
</span></span><span class="line"><span class="cl"><span class="c1"># But we need to add explicitly the route for docker containers subnet</span>
</span></span><span class="line"><span class="cl"><span class="c1"># You may need to adjust the distance and routing-table values</span>
</span></span><span class="line"><span class="cl">/ip route <span class="nv">disabled</span><span class="o">=</span>no <span class="nv">distance</span><span class="o">=</span><span class="m">1</span> dst-address<span class="o">=</span>172.21.0.0/16 <span class="nv">gateway</span><span class="o">=</span>cloud-wg routing-table<span class="o">=</span>main <span class="nv">scope</span><span class="o">=</span><span class="m">30</span> suppress-hw-offload<span class="o">=</span>no target-scope<span class="o">=</span><span class="m">10</span>
</span></span></code></pre></div><h3 id="3-add-wireguard-peer-vm">3. Add WireGuard peer (VM)</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/wireguard/wg0.conf
</span></span><span class="line"><span class="cl"><span class="c1"># Uncomment the lines for Mikrotik peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Mikrotik peer</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>Peer<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">AllowedIPs</span> <span class="o">=</span> 172.16.10.0/24,192.168.0.0/16
</span></span><span class="line"><span class="cl"><span class="nv">PublicKey</span> <span class="o">=</span> mikrotik public key
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Save and exit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Restart the WireGuard service</span>
</span></span><span class="line"><span class="cl">sudo systemctl restart wg-quick@wg0
</span></span><span class="line"><span class="cl"><span class="c1"># Check if changes are applied</span>
</span></span><span class="line"><span class="cl">wg show
</span></span></code></pre></div><p>You should see the Mikrotik peer in the list of peers.
<img loading="lazy" src="/images/posts/mikrotik-wg/wg-show-2.png" type="" alt="WireGuard peers"  /></p>
<details>
<summary>More commands to tests connection between cloud vm and mikrotik wg</summary>
<h5 id="run-in-mikrotik-cli">Run in mikrotik CLI</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ping wireguard IP of cloud server</span>
</span></span><span class="line"><span class="cl">ping 172.16.10.1
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/mikrotik-ping.png" type="" alt="WireGuard ping"  /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># resolve google.com via adguard container on cloud vm</span>
</span></span><span class="line"><span class="cl">put <span class="o">[</span>resolve google.com server 172.21.0.114<span class="o">]</span>
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/mikrotik-resolve.png" type="" alt="Wireguard resolve"  /></p>
<h5 id="run-in-cloud-server">Run in cloud server</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ping wireguard IP of Mikrotik router</span>
</span></span><span class="line"><span class="cl">ping 172.168.10.3
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/vm-ping.png" type="" alt="VM ping"  /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># resolve google.com via adguard container on home adguard</span>
</span></span><span class="line"><span class="cl">dig google.com @192.168.11.2
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/vm-resolve.png" type="" alt="VM resolve"  /></p>
<h5 id="run-on-my-mac-connected-to-home-network">Run on my Mac connected to home network</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ping wireguard IP of cloud server</span>
</span></span><span class="line"><span class="cl">ping 172.16.10.1
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/mac-ping.png" type="" alt="mac ping"  /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># resolve google.com via adguard container on cloud adguard</span>
</span></span><span class="line"><span class="cl">dig google.com @172.121.0.114
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mikrotik-wg/mac-resolve.png" type="" alt="mac resolve"  /></p>
</details>
<h2 id="iv-add-first-client-device-as-a-peer">IV. Add first client device as a peer</h2>
<p>I recommend you to use your smartphone as first client device, because it can work from both home WiFi and mobile data. This way you can test the connection from both networks.</p>
<p>Also, install on your smartphone:</p>
<ul>
<li>WireGuard app (<a href="https://apps.apple.com/us/app/wireguard/id1441195209">iOS</a>, <a href="https://play.google.com/store/apps/details?id=com.wireguard.android&amp;hl=en&amp;pli=1">Android</a>)</li>
<li>Network debugging app (<a href="https://apps.apple.com/bo/app/inettools-ping-dns-port-scan/id561659975">iOS</a>, <a href="https://play.google.com/store/apps/details?id=com.ulfdittmer.android.ping&amp;hl=en">Android</a>)</li>
</ul>
<h3 id="1-create-keys-for-the-client">1. Create keys for the client</h3>
<p>Install Wireguard app for your client OS.</p>
<p>Then, generate public and private keys on the device, for that create config from scratch in the app and then click on <strong>Generate keypair</strong> button.</p>
<p>Or you can generate keys on the cloud server and then copy them to the client device.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">wg genkey <span class="p">|</span> tee peer-privatekey <span class="p">|</span> wg pubkey &gt; peer-publickey
</span></span></code></pre></div><h3 id="2-add-the-client-as-a-peer-to-wireguard-config-on-cloud-server">2. Add the client as a peer to WireGuard config on cloud server</h3>
<p>On the cloud server, edit the WireGuard config file and add the client as a peer.</p>
<p>Each time increment previous peer address by 1.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/wireguard/wg0.conf
</span></span><span class="line"><span class="cl"><span class="c1"># Uncomment the lines for client peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># client peer</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>Peer<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">AllowedIPs</span> <span class="o">=</span> 172.16.10.2/32
</span></span><span class="line"><span class="cl"><span class="nv">PublicKey</span> <span class="o">=</span> put public key
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># CRTL+X: save and exit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Restart the WireGuard service</span>
</span></span><span class="line"><span class="cl">sudo systemctl restart wg-quick@wg0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check if changes are applied</span>
</span></span><span class="line"><span class="cl">wg show
</span></span></code></pre></div><h3 id="3-continue-configuring-the-client-device">3. Continue configuring the client device</h3>
<p>You already have public and private keys for the client device, other configuration parameters are:</p>
<h4 id="interface">Interface</h4>
<ul>
<li><strong>Address</strong> - It&rsquo;s address of peer in wireguard subnet. Put the same address you set in <code>AllowedIPs</code> field in previous step.</li>
<li><strong>DNS servers</strong> - if you have adguard/pihole running on the cloud server, you can use it as a DNS server. Put it&rsquo;s IP address here.</li>
<li><strong>MTU</strong> and <strong>ListenPort</strong> - you can leave them empty, they will be set automatically.</li>
</ul>
<h4 id="peer">Peer</h4>
<ul>
<li><strong>Endpoint</strong> - cloud server IP address and port (27277)</li>
<li><strong>Public key</strong> - public key of the cloud server</li>
<li><strong>AllowedIPs</strong> - here we put all subnets that we want to access from this current client device</li>
</ul>
<p>Here is an example of the configuration file for the client device:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Interface]</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateKey</span> <span class="o">=</span> <span class="s">private-key-of-client-device</span>
</span></span><span class="line"><span class="cl"><span class="na">Address</span> <span class="o">=</span> <span class="s">172.16.10.2/32</span>
</span></span><span class="line"><span class="cl"><span class="na">DNS</span> <span class="o">=</span> <span class="s">172.21.0.114 # IP of adguard container on cloud server</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Peer]</span>
</span></span><span class="line"><span class="cl"><span class="na">PublicKey</span> <span class="o">=</span> <span class="s">public-key-of-cloud-server</span>
</span></span><span class="line"><span class="cl"><span class="na">AllowedIPs</span> <span class="o">=</span> <span class="s">192.168.0.0/16, 172.16.10.0/24, 172.21.0.0/16 # subnets of home, cloud and wireguard network</span>
</span></span><span class="line"><span class="cl"><span class="na">Endpoint</span> <span class="o">=</span> <span class="s">ip.of.cloud.servner:27277</span>
</span></span></code></pre></div><p>That&rsquo;s it! Disconnect your device from the home Wi-Fi, switch to mobile data and connect to the VPN.</p>
<p>Then try to:</p>
<ul>
<li>ping the cloud server and Mikrotik router IP addresses in wireguard subnet.</li>
<li>check ports of services in docker containers on cloud and home server VM.</li>
</ul>
<style>
td, th {
   border: none!important;
}
</style>
<table>
  <thead>
      <tr>
          <th>ping cloud</th>
          <th>ping home</th>
          <th>check adguard port</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><img loading="lazy" src="/images/posts/mikrotik-wg/screen-ping-vm.jpeg" type="" alt="ping cloud"  /></td>
          <td><img loading="lazy" src="/images/posts/mikrotik-wg/screen-ping-home.jpeg" type="" alt="ping home"  /></td>
          <td><img loading="lazy" src="/images/posts/mikrotik-wg/screen-port-scan.jpeg" type="" alt="port scan"  /></td>
      </tr>
  </tbody>
</table>
<h2 id="v-bonus-dns-configuration">V. Bonus. DNS configuration</h2>
<p>Since I have 2 adguard instances and I use them as DNS servers everywhere, I will add DNS records for accessing my services:</p>
<ul>
<li>*<strong>.cloud.domain.com</strong> - pointing to traefik docker container on cloud server</li>
<li>*<strong>.home.domain.com</strong> - pointing to traefik docker container on home server</li>
</ul>
<p>Thus, I can access my services using domain names instead of IP addresses.</p>
<img src="/images/posts/mikrotik-wg/dns.png" width="30%">
<h2 id="vi-final-thoughts">VI. Final thoughts</h2>
<p>I hope this tutorial was helpful for you. I will keep it updated if I find any issues or improvements.</p>
<p>If you have any questions or suggestions, feel free to leave a comment below.</p>
<p>Credits to <a href="https://www.laroberto.com/">@laroberto</a> for their <a href="https://www.laroberto.com/remote-lan-access-with-wireguard/">guide on LAN access with WireGuard</a>. I followed it to set up the initial configuration and then adapted it to my needs.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Storing and using secrets in Mikrotik RouterOS</title>
      <link>https://alikhil.dev/posts/saving-and-using-secrets-in-mikrotitk-routeros/</link>
      <pubDate>Sun, 30 Mar 2025 16:01:25 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/saving-and-using-secrets-in-mikrotitk-routeros/</guid>
      <description>&lt;p&gt;Recently I have replaced my stock ISP router with &lt;a href=&#34;https://mikrotik.com/product/hex_s&#34;&gt;Mikrotik Hex S&lt;/a&gt;. I have been using it for a while and I am very happy with it. It is a very powerful device which can be programmed and automated with built-in scripting language.&lt;/p&gt;
&lt;p&gt;When I started writing my first scripts I faced a problem: how to store and use secrets in my scripts. I have found a solution and I want to share it with you.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I have replaced my stock ISP router with <a href="https://mikrotik.com/product/hex_s">Mikrotik Hex S</a>. I have been using it for a while and I am very happy with it. It is a very powerful device which can be programmed and automated with built-in scripting language.</p>
<p>When I started writing my first scripts I faced a problem: how to store and use secrets in my scripts. I have found a solution and I want to share it with you.</p>
<h2 id="the-problem">The problem</h2>
<p>Let&rsquo;s say I want to write a script that will send me telegram notifications. To do so I need to store my telegram bot token and chat id. Since I keep my RouterOS configuration in a git repository, I don&rsquo;t want to hardcode my secrets in the script.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="p">:</span><span class="n">global</span> <span class="n">sendTelegramMessage</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">botToken</span> <span class="s2">&#34;1234567890:ABCDEFGHIJKLMN&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">chatId</span> <span class="s2">&#34;10000000&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">message</span> <span class="s2">&#34;$1&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># telegram notification</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="k">tool</span> <span class="n">fetch</span> <span class="n">url</span><span class="o">=</span><span class="s2">&#34;https://api.telegram.org/bot$botToken/sendMessage\?chat_id=$chatId&amp;text=$message&#34;</span> <span class="n">keep</span><span class="o">-</span><span class="n">result</span><span class="o">=</span><span class="n">no</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="the-solution">The solution</h2>
<p>RouterOS has low level feature <code>/ppp secret</code> which can be used to store secrets. However, it could be inconvenient and a bit messy to use it directly in scripts. Instead, I would like to have more high level API to store and use secrets.</p>
<p>And, I have one. There is post in <a href="https://forum.mikrotik.com/viewtopic.php?p=916159#p916159">mikrotik forum</a> by user with nickname <strong>Amm0</strong>. Ammo has shared a script of global function which can be used to store and retrieve secrets like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">$SECRET set mySecret password=&#34;mySecretPassword&#34;
</span></span><span class="line"><span class="cl">:put [$SECRET get mySecret]
</span></span></code></pre></div><p><img loading="lazy" src="/images/posts/mirotik-secrets/terminal.png" type="" alt="Terminal Screenshot"  /></p>
<p>Now, I modify my script to use this function to keep it clean and secure:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="p">:</span><span class="n">global</span> <span class="n">sendTelegramMessage</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">botToken</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">set</span> <span class="n">botToken</span> <span class="s2">&#34;$[$SECRET get TELEGRAM_TOKEN]&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">chatId</span> <span class="s2">&#34;$[$SECRET get CHAT_ID]&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">:</span><span class="n">local</span> <span class="n">message</span> <span class="s2">&#34;$1&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># telegram notification</span>
</span></span><span class="line"><span class="cl">    <span class="o">/</span><span class="k">tool</span> <span class="n">fetch</span> <span class="n">url</span><span class="o">=</span><span class="s2">&#34;https://api.telegram.org/bot$botToken/sendMessage\?chat_id=$chatId&amp;text=$message&#34;</span> <span class="n">keep</span><span class="o">-</span><span class="n">result</span><span class="o">=</span><span class="n">no</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
  <summary>global SECRET function source code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">### $SECRET
</span></span><span class="line"><span class="cl">#   get &lt;name&gt;
</span></span><span class="line"><span class="cl">#   set &lt;name&gt; password=&lt;password&gt;
</span></span><span class="line"><span class="cl">#   remove &lt;name
</span></span><span class="line"><span class="cl">#   print
</span></span><span class="line"><span class="cl">:global SECRET
</span></span><span class="line"><span class="cl">:set $SECRET do={
</span></span><span class="line"><span class="cl">    :global SECRET
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # helpers
</span></span><span class="line"><span class="cl">    :local fixprofile do={
</span></span><span class="line"><span class="cl">        :if ([/ppp profile find name=&#34;null&#34;]) do={:put &#34;nothing&#34;} else={
</span></span><span class="line"><span class="cl">            /ppp profile add bridge-learning=no change-tcp-mss=no local-address=0.0.0.0 name=&#34;null&#34; only-one=yes remote-address=0.0.0.0 session-timeout=1s use-compression=no use-encryption=no use-mpls=no use-upnp=no
</span></span><span class="line"><span class="cl">        }
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">    :local lppp [:len [/ppp secret find where name=$2]]
</span></span><span class="line"><span class="cl">    :local checkexist do={
</span></span><span class="line"><span class="cl">        :if (lppp=0) do={
</span></span><span class="line"><span class="cl">            :error &#34;\$SECRET: cannot find $2 in secret store&#34;
</span></span><span class="line"><span class="cl">        }
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # $SECRET
</span></span><span class="line"><span class="cl">    :if ([:typeof $1]!=&#34;str&#34;) do={
</span></span><span class="line"><span class="cl">        :put &#34;\$SECRET&#34;
</span></span><span class="line"><span class="cl">        :put &#34;   uses /ppp/secrets to store stuff like REST apikeys, or other sensative data&#34;
</span></span><span class="line"><span class="cl">        :put &#34;\t\$SECRET print - prints stored secret passwords&#34;
</span></span><span class="line"><span class="cl">        :put &#34;\t\$SECRET get &lt;name&gt; - gets a stored secret&#34;
</span></span><span class="line"><span class="cl">        :put &#34;\t\$SECRET set &lt;name&gt; password=\&#34;YOUR_SECRET\&#34; - sets a secret password&#34;
</span></span><span class="line"><span class="cl">        :put &#34;\t\$SECRET remove &lt;name&gt; - removes a secret&#34;
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # $SECRET print
</span></span><span class="line"><span class="cl">    :if ($1~&#34;^pr&#34;) do={
</span></span><span class="line"><span class="cl">        /ppp secret print where comment~&#34;\\\$SECRET&#34;
</span></span><span class="line"><span class="cl">        :return [:nothing]
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # $SECRET get
</span></span><span class="line"><span class="cl">    :if ($1~&#34;get&#34;) do={
</span></span><span class="line"><span class="cl">        $checkexist
</span></span><span class="line"><span class="cl">       :return [/ppp secret get $2 password]
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # $SECRET set
</span></span><span class="line"><span class="cl">    :if ($1~&#34;set|add&#34;) do={
</span></span><span class="line"><span class="cl">        :if ([:typeof $password]=&#34;str&#34;) do={} else={:error &#34;\$SECRET: password= required&#34;}
</span></span><span class="line"><span class="cl">        :if (lppp=0) do={
</span></span><span class="line"><span class="cl">            /ppp secret add name=$2 password=$password
</span></span><span class="line"><span class="cl">        } else={
</span></span><span class="line"><span class="cl">            /ppp secret set $2 password=$password
</span></span><span class="line"><span class="cl">        }
</span></span><span class="line"><span class="cl">        $fixprofile
</span></span><span class="line"><span class="cl">        /ppp secret set $2 comment=&#34;used by \$SECRET&#34;
</span></span><span class="line"><span class="cl">        /ppp secret set $2 profile=&#34;null&#34;
</span></span><span class="line"><span class="cl">        /ppp secret set $2 service=&#34;async&#34;
</span></span><span class="line"><span class="cl">        :return [$SECRET get $2]
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    # $SECRET remove
</span></span><span class="line"><span class="cl">    :if ($1~&#34;rm|rem|del&#34;) do={
</span></span><span class="line"><span class="cl">        $checkexist
</span></span><span class="line"><span class="cl">        :return [/ppp secret remove $2]
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">    :error &#34;\$SECRET: bad command&#34;
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div></details>
<h2 id="conclusion">Conclusion</h2>
<p>The good thing about this approach is that secrets storing and retrieving mechanism encapsulated and can be easily changed in the future without changing the scripts. Also, it is easy to use and understand.</p>
<p>Keep your secrets safe and happy scripting!</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Monitor input source control on Mac</title>
      <link>https://alikhil.dev/posts/monitor-input-source-control-mac/</link>
      <pubDate>Wed, 16 Oct 2024 20:31:06 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/monitor-input-source-control-mac/</guid>
      <description>&lt;p&gt;If you as me have single monitor and 2 Mac devices (for example, I have corporate Macbook and personal Mac Mini) you may want to use the same monitor for both devices. And you may want to switch between them without unplugging and plugging cables or selecting input source using monitor buttons.&lt;/p&gt;
&lt;p&gt;In this post I will show you how to configure hotkeys for that.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you as me have single monitor and 2 Mac devices (for example, I have corporate Macbook and personal Mac Mini) you may want to use the same monitor for both devices. And you may want to switch between them without unplugging and plugging cables or selecting input source using monitor buttons.</p>
<p>In this post I will show you how to configure hotkeys for that.</p>
<h2 id="hardware">Hardware</h2>
<p>You will need a monitor with <strong>multiple input sources</strong>.
For example, I have Dell S2722QC tt has 2 HDMI ports and 1 USB-C port where:</p>
<ul>
<li>Macbook Air connected to port HDMI-2</li>
<li>Mac Mini connected to port USB-C-1</li>
</ul>
<h2 id="software">Software</h2>
<p>There is app called <a href="https://github.com/waydabber/BetterDisplay">BetterDisplay</a> that has a lot of powerful features. But for our case we need only one feature - <strong>change display inputs using DDC</strong>.</p>
<p>Install it on both Macs. You will have 14 days trial period with all PRO features.</p>
<p><strong>Enable Accessibility for BetterDisplay in System Settings -&gt; Privacy &amp; Security -&gt; Accessibility.</strong></p>
<p>Then try to switch input source by clicking on BetterDisplay icon in the menu bar -&gt; <em>DDC Input Source -&gt; Select next port.</em></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><img loading="lazy" src="/images/posts/menu-shot.png" type="" alt="Menu Shot"  /></td>
          <td><img loading="lazy" src="/images/posts/input-choose.png" type="" alt="Input Choose"  /></td>
      </tr>
  </tbody>
</table>
<p>If it works, you can continue to the next step.
Otherwise check if your monitor supports DDC protocol and ensure Accessibility is enabled for BetterDisplay.</p>
<h3 id="paid-option">Paid option</h3>
<p>If you are ready to pay 19$/19€ x2 for both Macs you can <a href="https://betterdisplay.pro/buy/">buy BetterDisplay</a>. And then configure hotkeys in the app settings <em>Settings -&gt; Keyboards -&gt; Custom keyboard shortcuts -&gt; DDC Input Source</em>.</p>
<p>Click &ldquo;Record Shortcut&rdquo; and press the key combination you want to use, for example <code>CMD + F1</code> and <code>CMD + F2</code>.</p>
<p><img loading="lazy" src="/images/posts/shortcuts.png" type="" alt="alt text"  /></p>
<h3 id="free-option">Free option</h3>
<p>If you like me don&rsquo;t want to pay for 40$ for single feature there is a hacky way to do it.</p>
<p>We need an app that can handle hotkeys and run shell commands. I use <a href="https://www.raycast.com/">Raycast</a>, so called &ldquo;Spotlight on steroids&rdquo; and it can handle custom hotkeys. Or you can use any other app you like.</p>
<h4 id="configuring-shell-command">Configuring shell command</h4>
<p>Before configuring <strong>Raycast</strong> we need to know <code>ddc</code> value for each input source. To do so, go to <em>Settings -&gt; Displays -&gt; &ldquo;Your monitor name&rdquo; -&gt; DDC Input Sources</em>, and save IDs from <strong>Value</strong> column for each input source:</p>
<p><img loading="lazy" src="/images/posts/ddc-input-sources.png" type="" alt="alt text"  /></p>
<p>In my case it&rsquo;s <code>18</code> for HDMI-2 and <code>25</code> for USB-C-1.</p>
<p>Then create a directory <code>~/raycast-scripts</code> and put there a script <code>change-input-source.sh</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1"># See full documentation here: https://github.com/raycast/script-commands</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Required parameters:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># @raycast.schemaVersion 1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># @raycast.title Switch Monitor Input Source</span>
</span></span><span class="line"><span class="cl"><span class="c1"># @raycast.mode silent</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Optional parameters:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># @raycast.icon 🖥️</span>
</span></span><span class="line"><span class="cl"><span class="c1"># @raycast.packageName Raycast Scripts</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DEST_ID</span><span class="o">=</span><span class="m">18</span> &lt;PUT YOUR DDC ID HERE&gt;
</span></span><span class="line"><span class="cl"><span class="nv">DEST_NAME</span><span class="o">=</span><span class="s2">&#34;Home Mac Mini&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># PUT YOUR ACTUAL HOSTNAME</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="sb">`</span>hostname<span class="sb">`</span> <span class="o">==</span> <span class="s2">&#34;Aliks-Mac-mini.local&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">DEST_ID</span><span class="o">=</span><span class="m">25</span> &lt;PUT YOUR SECOND DDC ID HERE&gt;
</span></span><span class="line"><span class="cl">  <span class="nv">DEST_NAME</span><span class="o">=</span><span class="s2">&#34;Work Macbook Air&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Switching monitor input source to </span><span class="nv">$DEST_NAME</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">/Applications/BetterDisplay.app/Contents/MacOS/BetterDisplay <span class="nb">set</span> -ddc<span class="o">=</span><span class="nv">$DEST_ID</span> -vcp<span class="o">=</span>inputSelect
</span></span></code></pre></div><p>After that try to run it in terminal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod +x ~/raycast-scripts/change-input-source.sh
</span></span><span class="line"><span class="cl">~/raycast-scripts/change-input-source.sh
</span></span></code></pre></div><p>If it works, you can continue to the next step.</p>
<h4 id="configuring-raycast">Configuring Raycast</h4>
<ol>
<li>Open Raycast and go to <em>Settings -&gt; Extensions -&gt; Search for Scripts</em></li>
<li>Click on <em>Add Script Directory</em> and select <code>~/raycast-scripts</code></li>
<li>Click on <em>Record Shortcut</em> for newly added script and press the key combination you want to use, for example <code>CMD + F1</code> on first Mac and <code>CMD + F2</code> on second.</li>
</ol>
<p><img loading="lazy" src="/images/posts/raycast-settings.png" type="" alt="alt text"  /></p>
<p>And that&rsquo;s it! Now you can switch input source using hotkeys.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Build own drone.io docker image</title>
      <link>https://alikhil.dev/posts/build-own-drone-image/</link>
      <pubDate>Fri, 20 Sep 2019 10:30:00 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/build-own-drone-image/</guid>
      <description>&lt;p&gt;¡Hola, amigos!&lt;/p&gt;
&lt;p&gt;In this post, I will quickly descibe how you can build your own &lt;a href=&#34;https://drone.io&#34;&gt;drone.io&lt;/a&gt; docker image.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>¡Hola, amigos!</p>
<p>In this post, I will quickly descibe how you can build your own <a href="https://drone.io">drone.io</a> docker image.</p>
<p>Drone is very popular container native CI/CD platform. Not long time ago, there was release of new <a href="https://blog.drone.io/drone-1/">1.0 version</a> of drone. Which brang a lot of cool features and new <a href="https://discourse.drone.io/t/licensing-and-subscription-faq/3839">license</a>. The license tells that we can use Enterprise version of drone for free without any limits by building our own docker image if we are individuals or startup (read the licence for more detail).</p>
<p>So, how to build it?</p>
<h1 id="instructions">Instructions</h1>
<p>First, clone the drone repo to your local machine.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@github.com:drone/drone.git
</span></span></code></pre></div><p>Second, checkout to version of drone you want to build. For example, I want to build <a href="https://github.com/drone/drone/tag/v1.3.1">v1.3.1</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git checkout v1.3.1
</span></span></code></pre></div><p>We will use single dockerfile to build the image. To do so, we need to add extra step to existing dockerfile which is in <code>docker</code> directory. Let&rsquo;s say we want to build docker image for <code>linux</code> OS and <code>amd64</code> architecture, then we will edit <code>docker/Dockerfile.server.linux.amd64</code>.</p>
<!-- Original drone docker images built in drone itself. To check how they built you can check `drone.yml`. -->
<p>If you check the dockerfile you will see, that binaries are just copied into docker image during the build and they are built outside of the docker build. So, the step we will add to dockerfile is <code>go build</code> step.</p>
<p>To build the binary, we need to know what version of go is used for building binary in original docker image. We can find it in <code>drone.yml</code> <strong>build</strong> step. For version 1.3.1 of drone <code>golang:1.12.9</code> docker image is used for building binaries.</p>
<p>Then, we use same image to build binary in our dockerfile:</p>
<p><strong>docker/Dockerfile.server.linux.amd64</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Dockerfile" data-lang="Dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">golang:1.12.9</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">builder</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/go/src/github.com/drone/drone</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> GOOS linux<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> GOARCH amd64<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> CGO_ENABLED <span class="m">1</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> REPO github.com/drone/drone<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> GO111MODULE on<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> go build -tags nolimit -ldflags <span class="s2">&#34;-extldflags \&#34;-static\&#34;&#34;</span> -o release/linux/<span class="si">${</span><span class="nv">GOARCH</span><span class="si">}</span>/drone-server <span class="si">${</span><span class="nv">REPO</span><span class="si">}</span>/cmd/drone-server<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">FROM</span><span class="w"> </span><span class="s">alpine:3.9</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">alpine</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> apk add -U --no-cache ca-certificates<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">FROM</span><span class="w"> </span><span class="s">alpine:3.9</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span> <span class="m">443</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">VOLUME</span><span class="w"> </span><span class="s">/data</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> GODEBUG <span class="nv">netdns</span><span class="o">=</span>go<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> XDG_CACHE_HOME /data<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> DRONE_DATABASE_DRIVER sqlite3<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> DRONE_DATABASE_DATASOURCE /data/database.sqlite<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> <span class="nv">DRONE_RUNNER_OS</span><span class="o">=</span>linux
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">DRONE_RUNNER_ARCH</span><span class="o">=</span>amd64
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">DRONE_SERVER_PORT</span><span class="o">=</span>:80<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> <span class="nv">DRONE_SERVER_HOST</span><span class="o">=</span>localhost
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">DRONE_DATADOG_ENABLED</span><span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">DRONE_DATADOG_ENDPOINT</span><span class="o">=</span>https://stats.drone.ci/api/v1/series<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> --from<span class="o">=</span>alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> --from<span class="o">=</span>builder /go/src/github.com/drone/drone/release/linux/amd64/drone-server /bin/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENTRYPOINT</span> <span class="p">[</span><span class="s2">&#34;/bin/drone-server&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Also we need to delete .dockerignore file from root of the repo.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm .dockerignore
</span></span></code></pre></div><p>Then we build docker image like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t alikhil/drone:1.3.1 -f docker/Dockerfile.server.linux.amd64 .
</span></span></code></pre></div><p>That&rsquo;s all! Now you can use own newly built docker image instead of official one if your use case meet license conditions.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Deploy SPA application to Kubernetes</title>
      <link>https://alikhil.dev/posts/deploy-spa-to-k8s/</link>
      <pubDate>Sat, 08 Jun 2019 22:30:00 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/deploy-spa-to-k8s/</guid>
      <description>&lt;p&gt;Hello, folks!&lt;/p&gt;
&lt;p&gt;Today I want you to share with you tutorial on how to deploy your SPA application to Kubernetes. Tutorial is oriented for those don&amp;rsquo;t very familiar with docker and k8s but want their single page application run in k8s.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hello, folks!</p>
<p>Today I want you to share with you tutorial on how to deploy your SPA application to Kubernetes. Tutorial is oriented for those don&rsquo;t very familiar with docker and k8s but want their single page application run in k8s.</p>
<h3 id="dockerize-the-application">Dockerize the application</h3>
<p>I expect that you have docker installed in your machine. If it isn&rsquo;t you can install it by following <a href="https://docs.docker.com/install/">official installation guide</a>.</p>
<p>As SPA project I will use <a href="https://github.com/gothinkster/vue-realworld-example-app">vue-realworld-example-app</a> as SPA project. You can your own SPA project if you have one.</p>
<p>So, I have cloned it, installed dependencies and built:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/gothinkster/vue-realworld-example-app
</span></span><span class="line"><span class="cl">yarn
</span></span><span class="line"><span class="cl">yarn build
</span></span></code></pre></div><p>Next step is to decide how our application will be served. There are bunch of possible solutions but I decided to use <a href="https://nginx.org/en/">nginx</a> since it recommends itself as one of the best http servers.</p>
<p>To serve SPA we need to return all requested files if they exist or otherwise fallback to index.html. To do so I wrote the following nginx config:</p>
<p><strong>nginx.conf</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="c1"># ...
</span></span></span><span class="line"><span class="cl"><span class="c1"># other configs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kn">root</span> <span class="s">/app</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">alias</span> <span class="s">/app/</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">try_files</span> <span class="nv">$uri</span> <span class="s">/index.html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Full config file can be found in <a href="https://github.com/alikhil/vue-realworld-example-app/blob/master/nginx.conf">my fork of the repo</a></p>
<p>Then, we need to write <strong>Dockerfile</strong> for building image with our application. Here it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Dockerfile" data-lang="Dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">nginx</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/root</span>/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> ./dist /app<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> ./nginx.conf /etc/nginx/conf.d/default.conf<span class="err">
</span></span></span></code></pre></div><p>We assume that artifacts of build placed in the <code>dist</code> directory and so that during the docker build the content of <code>dist</code> directory copied into containers <code>/app</code> directory.</p>
<p>Now, we are ready to build it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t alikhil/my-spa:0.1 .
</span></span></code></pre></div><p>And run it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run -p 8080:80 alikhil/my-spa:0.1
</span></span></code></pre></div><p>Then if we open http://localhost:8080 we will see something similar to:</p>
<img width="1279" alt="Screen Shot 2019-06-09 at 14 21 09" src="https://user-images.githubusercontent.com/7482065/59158361-e792f580-8ac1-11e9-9cd0-de309c697c12.png">
<p>Cool! It works!</p>
<p>We will need to use our newly builded docker image to deploy to k8s. So, we need to make it available from the k8s cluster by pulling to some docker registry. I will push image to <a href="http://hub.docker.com/">DockerHub</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker push alikhil/my-spa:0.1
</span></span></code></pre></div><h3 id="deploy-to-k8s">Deploy to k8s</h3>
<p>To run the application in k8s we will use <code>Deployment</code> resource type. Here it is:</p>
<p><strong>deployment.yaml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">alikhil/my-spa:0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">150m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">250Mi</span><span class="w">
</span></span></span></code></pre></div><p>Then we create deployment by running <code>kubectl apply -f deployment.yaml</code> and newly created pods can found:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ kubectl get pods
</span></span><span class="line"><span class="cl">NAME                      READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">my-spa-84b6dcd48d-mhv9f   1/1     Running   <span class="m">0</span>          18s
</span></span></code></pre></div><p>Then we need to expose our app to the world. It can be done by using service of type NodePort or via Ingress. We will do it with Ingress. For that we will need service:</p>
<p><strong>service.yaml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span></code></pre></div><p>And ingress itself:</p>
<p><strong>ingress.yaml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa-ing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.class</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">my-spa.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa-cert-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">serviceName</span><span class="p">:</span><span class="w"> </span><span class="l">my-spa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">servicePort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f ingress.yaml -f service.yaml
</span></span></code></pre></div><p>And here it is! Our SPA runs in the k8s!</p>
<img width="1253" alt="Screen Shot 2019-06-10 at 22 39 00" src="https://user-images.githubusercontent.com/7482065/59221731-a1788780-8bd0-11e9-806b-734048d3a291.png">]]></content:encoded>
    </item>
    
    <item>
      <title>Oauth2 Proxy for Kubernetes Services</title>
      <link>https://alikhil.dev/posts/oauth2-proxy-for-kubernetes-services/</link>
      <pubDate>Sun, 20 May 2018 21:30:36 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/oauth2-proxy-for-kubernetes-services/</guid>
      <description>&lt;p&gt;Hello, folks!&lt;/p&gt;
&lt;p&gt;In this post, I will go through configuring &lt;a href=&#34;https://github.com/bitly/oauth2_proxy&#34;&gt;Bitly OAuth2 proxy&lt;/a&gt; in a kubernetes cluster.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hello, folks!</p>
<p>In this post, I will go through configuring <a href="https://github.com/bitly/oauth2_proxy">Bitly OAuth2 proxy</a> in a kubernetes cluster.</p>
<h3 id="upd-5082025">UPD 5/08/2025</h3>
<p>There is a fresh tutorial about <a href="https://alikhil.dev/posts/oauth2-proxy-protect-services-in-k8s/">oauth2-proxy</a></p>
<hr>
<p>A few days ago I was configuring <a href="https://en.wikipedia.org/wiki/Single_sign-on">SSO</a> for our internal dev-services in <a href="https://github.com/KazanExpress">KE Technologies</a>.</p>
<p>And I spent the whole to make it work properly, and at the end I decided that I will share my experience by writing this post, hoping that it will help others(and possibly me in the future) to go through this process.</p>
<!-- toc -->
<h1 id="what-do-we-want">What do we want?</h1>
<p>We have internal services in our k8s cluster that we want to be accessible for developers.
It can be <em>kubernetes-dashboard</em> or <em>kibana</em> or anything else.</p>
<p>Before that we used Basic Auth, it&rsquo;s <a href="https://banzaicloud.com/blog/ingress-auth/">easy to setup</a> in ingresses. But this approach has several disadvantages:</p>
<ol>
<li>We need to share a single pair of <em>login</em> and <em>password</em> for all services among all developers</li>
<li>Developers will be asked to enter credentials each time when they access service first time</li>
</ol>
<p>What we want is that developer will log in once and will have access to all other services without additional authentication.</p>
<p>So, a possible scenario could be:</p>
<ol>
<li>Developers open <a href="https://kibana.example.com">https://kibana.example.com</a> which is internal service</li>
<li>Browser redirects them to <a href="https://auth.example.com">https://auth.example.com</a> where they sign in</li>
<li>After successful authentication browser redirects them to <a href="https://kibana.example.com">https://kibana.example.com</a></li>
</ol>
<h1 id="preparation">Preparation</h1>
<h2 id="update">Update</h2>
<h3 id="upd-10073018">UPD 1.0(07/30/18)</h3>
<p>Using kube-lego for configuring Let&rsquo;s Encrypt certificates is depricated now. Consider using <a href="https://github.com/jetstack/cert-manager">cert-manager</a> instead.</p>
<h3 id="upd-20082418">UPD 2.0(08/24/18)</h3>
<p>Initialy, when I was writing this post I was using old version of nginx 0.9.0, because it did not work correctly on newer version. Now, I found the problem and it have been fixed in 0.18.0 release. But ingress exposing private services should be updated(<a href="https://github.com/helm/charts/issues/5958#issuecomment-408457931">more details</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nginx.ingress.kubernetes.io/auth-signin</span><span class="p">:</span><span class="w"> </span><span class="l">https://auth.example.com/oauth2/start?rd=https://$host$request_uri$is_args$args</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nginx.ingress.kubernetes.io/auth-url</span><span class="p">:</span><span class="w"> </span><span class="l">http://oauth2-proxy.oauth-proxy.svc.cluster.local:4180/oauth2/auth</span><span class="w">
</span></span></span></code></pre></div><h2 id="kubernetes">Kubernetes</h2>
<p>First of all, we need a Kubernetes cluster. I will use the newly created cluster in <strong>Google Cloud Platform</strong> with version <strong>1.8.10-gke.0</strong>. If you have a cluster with configured ingress and https you can skip this step.</p>
<p>Then we need to install <a href="https://github.com/kubernetes/ingress-nginx"><strong>nginx ingress</strong></a> and <a href="https://github.com/jetstack/kube-lego"><strong>kube lego</strong></a>. Let&rsquo;s do it using helm:</p>
<h3 id="init-helm">Init helm</h3>
<p>With RBAC:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># giving default accout admin role:</span>
</span></span><span class="line"><span class="cl"><span class="nv">ACCOUNT</span><span class="o">=</span><span class="k">$(</span>gcloud info --format<span class="o">=</span><span class="s1">&#39;value(config.account)&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl create clusterrolebinding owner-cluster-admin-binding <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --clusterrole cluster-admin <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --user <span class="nv">$ACCOUNT</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl -n kube-system create sa tiller
</span></span><span class="line"><span class="cl">kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount<span class="o">=</span>kube-system:tiller
</span></span><span class="line"><span class="cl">helm init --service-account tiller
</span></span></code></pre></div><p>without RBAC:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">helm init
</span></span></code></pre></div><h3 id="install-nginx-ingress">Install nginx-ingress</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">helm install stable/nginx-ingress --name nginx-ing --namespace nginx-ing <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set controller.image.repository<span class="o">=</span>gcr.io/google_containers/nginx-ingress-controller <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set controller.image.tag<span class="o">=</span><span class="s2">&#34;0.9.0-beta.15&#34;</span>
</span></span><span class="line"><span class="cl">    --set rbac.create<span class="o">=</span><span class="nb">true</span> <span class="c1"># if RBAC is enabled in the cluster</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># see all options here: https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/values.yaml</span>
</span></span></code></pre></div><p>After it&rsquo;s installed we can retrieve controller IP address:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl --namespace nginx-ing get services -o wide -w nginx-ing-nginx-ingress-controller
</span></span></code></pre></div><p>and create DNS record to point our domain and subdomains to this IP address.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">A       example.com     xxx.xxx.xx.xxx
</span></span><span class="line"><span class="cl">CNAME   *.example.com   example.com
</span></span></code></pre></div><h3 id="install-kube-lego">Install kube-lego</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">helm install --name kube-lego stable/kube-lego --namespace kube-lego <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set config.LEGO_SUPPORTED_INGRESS_CLASS<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set config.LEGO_SUPPORTED_INGRESS_PROVIDER<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set config.LEGO_DEFAULT_INGRESS_CLASS<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set config.LEGO_URL<span class="o">=</span>https://acme-v01.api.letsencrypt.org/directory <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set rbac.create<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set image.tag<span class="o">=</span>0.1.5 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --set config.LEGO_LOG_LEVEL<span class="o">=</span>debug
</span></span></code></pre></div><h3 id="test-it">Test it!</h3>
<p>Let&rsquo;s run simple HTTP server as service and expose it using nginx ingress:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl run simple-http --image<span class="o">=</span>strm/helloworld-http --port<span class="o">=</span><span class="m">80</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl expose deployment simple-http --name example-service --port<span class="o">=</span><span class="m">80</span> --target-port<span class="o">=</span><span class="m">80</span> --type<span class="o">=</span>NodePort
</span></span></code></pre></div><p><strong>example-ing.yaml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.class</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/tls-acme</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;true&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">service.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">serviceName</span><span class="p">:</span><span class="w"> </span><span class="l">example-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">servicePort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;service.example.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">ing-tls</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f example-ing.yaml
</span></span></code></pre></div><p>Wait for a few seconds and open <a href="https://service.example.com">https://service.example.com</a> and you should see something similar to this:</p>



<div class="figure " >
  
    <img class="fig-img" src="https://user-images.githubusercontent.com/7482065/40230488-575867a4-5aa0-11e8-907c-8878b0b951e6.png"  alt="Example">
  
   
    <span class="caption">Example</span>
  
</div>

<h2 id="github-application">GitHub application</h2>
<p>In this post, we will use GitHub accounts for authentication.</p>
<p>So, go to <a href="https://github.com/settings/applications/new">https://github.com/settings/applications/new</a> and create new OAuth application</p>
<p>Fill <strong>Authorization callback URL</strong> field with <a href="https://auth.example.com/oauth2/callback">https://auth.example.com/oauth2/callback</a> where <em>example.com</em> is your domain name.</p>



<div class="figure [classes]" >
  
    <img class="fig-img" src="https://user-images.githubusercontent.com/7482065/40223994-7f7c7f94-5a8d-11e8-97db-9ca6e0809e0a.png"  alt="GitHub Application">
  
   
    <span class="caption">GitHub Application</span>
  
</div>

<p>After creating an application you will have <strong>Client ID</strong> and <strong>Client Secret</strong> which we will need in next step.</p>
<h1 id="deploy-oauth-proxy">Deploy OAuth Proxy</h1>
<p>There are a lot of docker images for OAuth proxy, but we can not use them because they do not support domain white-listing. The problem is that such functionality has not implemented yet.</p>
<p>Actualy there are several PRs that solve that problem but seems to be they frozen for an unknown amount of time.</p>
<p>So, the only thing I could do is to merge one of the PRs to current master and build own image.</p>
<p>You also can use my image, but if you worry about security just clone <a href="https://github.com/alikhil/oauth2_proxy">my fork</a> and build image yourself.</p>
<p>Let&rsquo;s create a namespace and set it as current:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create ns oauth-proxy
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kns oauth-proxy <span class="c1"># I am using kubectx tool -&gt; https://github.com/ahmetb/kubectx</span>
</span></span></code></pre></div><h2 id="deploy-secret">Deploy secret</h2>
<p><strong>secret.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">github-client-id</span><span class="p">:</span><span class="w"> </span><span class="l">base64(YOUR_CLIENT_ID)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">github-client-secret</span><span class="p">:</span><span class="w"> </span><span class="l">base64(YOUR_CLIENT_SECRET)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cookie-secret</span><span class="p">:</span><span class="w"> </span><span class="l">base64(random_string)</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create -f secret.yml
</span></span></code></pre></div><h2 id="deploy-deployment">Deploy deployment</h2>
<p><strong>oauth-proxy.deployment.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">alikhil/oauth2_proxy:2.2.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">imagePullPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">provider=github</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">email-domain=*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">upstream=file:///dev/null</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">http-address=0.0.0.0:4180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">whitelist-domain=.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- --<span class="l">cookie-domain=.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># - --cookie-expire duration: expire timeframe for cookie (default 168h0m0s)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># - --cookie-name string: the name of the cookie that the oauth_proxy creates (default &#34;_oauth2_proxy&#34;)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># - --cookie-refresh duration: refresh the cookie after this duration; 0 to disable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># - --cookie-secret string: the seed string for secure cookies (optionally base64 encoded)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">OAUTH2_PROXY_CLIENT_ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">github-client-id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">OAUTH2_PROXY_CLIENT_SECRET</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">github-client-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">OAUTH2_PROXY_COOKIE_SECRET</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">secretKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">cookie-secret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">4180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create -f oauth-proxy.deployment.yml
</span></span></code></pre></div><h2 id="deploy-service">Deploy service</h2>
<p><strong>oauth-service.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">4180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">4180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">k8s-app</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create -f oauth-service.yml
</span></span></code></pre></div><h2 id="deploy-ingress">Deploy ingress</h2>
<p><strong>oauth-ing.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/tls-acme</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.class</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;nginx&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">auth.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">serviceName</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">servicePort</span><span class="p">:</span><span class="w"> </span><span class="m">4180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/oauth2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">auth.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">oauth-proxy-tls</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl create -f oauth-ing.yml
</span></span></code></pre></div><h2 id="test-it-1">Test it!</h2>
<p>You can update ingress that we used while configuring nginx-ingress or create a new one:</p>
<p><strong>example-ing.yml</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">extensions/v1beta1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Ingress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">annotations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/ingress.class</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kubernetes.io/tls-acme</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;true&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ingress.kubernetes.io/auth-url</span><span class="p">:</span><span class="w"> </span><span class="l">https://auth.example.com/oauth2/auth</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ingress.kubernetes.io/auth-signin</span><span class="p">:</span><span class="w"> </span><span class="l">https://auth.example.com/oauth2/start?rd=https://$host$request_uri$is_args$args</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">service.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">serviceName</span><span class="p">:</span><span class="w"> </span><span class="l">example-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">servicePort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">hosts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">service.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">ing-tls</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f example-ing.yml
</span></span></code></pre></div><p>Then visit service.example.com and you will be redirected to GitHub authorization page:</p>



<div class="figure " >
  
    <img class="fig-img" src="https://user-images.githubusercontent.com/7482065/40262777-feb43e2a-5b12-11e8-92dd-d7d066d61c50.png"  alt="GitHub Authorization page">
  
   
    <span class="caption">GitHub Authorization page</span>
  
</div>

<p>And once you authenticate, you will have access to all your services under ingress that point to auth.example.com until cookie expires.</p>
<p>And that&rsquo;s it! Now you can put any of your internal services behind ingress with OAuth.</p>
<h2 id="resources">Resources</h2>
<p>Here is a list of resources that helped me to go through this proccess first time:</p>
<ul>
<li><a href="https://eng.fromatob.com/post/2017/02/lets-encrypt-oauth-2-and-kubernetes-ingress/">https://eng.fromatob.com/post/2017/02/lets-encrypt-oauth-2-and-kubernetes-ingress/</a></li>
<li><a href="https://www.midnightfreddie.com/oauth2-proxy.html">https://www.midnightfreddie.com/oauth2-proxy.html</a></li>
<li><a href="https://thenewstack.io/single-sign-on-for-kubernetes-dashboard-experience/">https://thenewstack.io/single-sign-on-for-kubernetes-dashboard-experience/</a></li>
</ul>]]></content:encoded>
    </item>
    
    <item>
      <title>Go Quickstart</title>
      <link>https://alikhil.dev/posts/go-quickstart/</link>
      <pubDate>Fri, 06 Apr 2018 16:22:35 +0300</pubDate>
      
      <guid>https://alikhil.dev/posts/go-quickstart/</guid>
      <description>&lt;p&gt;Hi folks!
It&amp;rsquo;s been a long time since I have published the last post, but now I came back with short quickstart guide in &lt;strong&gt;Go&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this tutorial, we will configure Go environment in VS Code and write our first program in Go.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hi folks!
It&rsquo;s been a long time since I have published the last post, but now I came back with short quickstart guide in <strong>Go</strong>.</p>
<p>In this tutorial, we will configure Go environment in VS Code and write our first program in Go.</p>
<h3 id="install-go">Install Go</h3>
<p>The first thing that you need to do it&rsquo;s to install Go on your computer. To do so, download installer for your operating system from  <a href="https://golang.org/dl/">here</a> and then run the installer.</p>
<h3 id="configure-gopath">Configure GOPATH</h3>
<p>By language convention, Go developers store all their code in a single place called <em>workspace</em>. Go also puts dependency packages in the workspace. So, in order to Go perform correctly, we need to set <code>GOPATH</code> variable with the path to the workspace.</p>
<h4 id="macos-and-linux">MacOS and Linux</h4>
<p>Set the <code>GOPATH</code> envar with workspace</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GOPATH</span><span class="o">=</span><span class="nv">$HOME</span>/go
</span></span></code></pre></div><p>Also, we need to add <code>GOPATH/bin</code> to <code>PATH</code> in order to run compiler Go programs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$GOPATH</span>/bin
</span></span></code></pre></div><h3 id="configure-vs-code">Configure VS Code</h3>
<p>Install <a href="https://github.com/Microsoft/vscode-go">official Go extension</a>.</p>
<p>Install delve debugger:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go get -u github.com/derekparker/delve/cmd/dlv
</span></span></code></pre></div><p>I recommend you to add the following lines to your VS Code user settings:</p>
<p><strong>settings.json</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;go.autocompleteUnimportedPackages&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;go.formatTool&#34;</span><span class="p">:</span> <span class="s2">&#34;gofmt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="windows">Windows</h4>
<p>Create <code>GOPATH</code> envar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">GOPATH</span><span class="o">=</span>c:<span class="se">\U</span>sers<span class="se">\%</span>USERNAME%<span class="se">\g</span>o
</span></span></code></pre></div><p>Also, we need to add <code>GOPATH\bin</code> to <code>PATH</code> in order to run compiler Go programs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">PATH</span><span class="o">=</span>%PATH%<span class="p">;</span>%GOPATH%<span class="se">\b</span>in
</span></span></code></pre></div><h3 id="create-project">Create project</h3>
<p>Move to your <code>GOPATH/src</code> directory. Create a directory for your project:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> <span class="nv">$GOPATH</span>/src
</span></span><span class="line"><span class="cl">mkdir -p github.com/alikhil/hello-world-with-go
</span></span></code></pre></div><p>Open it using vscode:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">code github.com/alikhil/hello-world-with-go
</span></span></code></pre></div><h3 id="hello-world">Hello World</h3>
<p>Let&rsquo;s create a file named <code>program.go</code> and put the following code there:</p>
<p><strong>program.go</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;¡Hola, mundo!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h3 id="run-the-program">Run the program</h3>
<p>Finally, to run the program by pressing the <code>F5</code> button in VS Code and you should see the message printed to <em>Debug Console</em>.</p>
<p>That&rsquo;s all! My congratulations, you have just written your first program in Go!</p>
<h3 id="troubleshooting">Troubleshooting</h3>
<p>If you fail to run your program and there is some message like <strong>&ldquo;Cannot find a path to <code>go</code>&rdquo;</strong>.
Try to add to your <code>PATH</code> envar with path directory where <code>go</code> binary is stored.</p>
<p>For example in MacOS I have added following line to my <code>~/.bash_profile</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/usr/local/go/bin:<span class="nv">$PATH</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
  </channel>
</rss>
