<?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>Kubernetes on alikhil</title>
    <link>https://alikhil.dev/tags/kubernetes/</link>
    <description>Recent content in Kubernetes on alikhil</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>Alik Khilazhev</managingEditor>
    <lastBuildDate>Sun, 28 Dec 2025 21:14:12 +0300</lastBuildDate><atom:link href="https://alikhil.dev/tags/kubernetes/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Kubernetes In-Place Pod Resize</title>
      <link>https://alikhil.dev/posts/in-place-pod-resize/</link>
      <pubDate>Sun, 28 Dec 2025 21:14:12 +0300</pubDate>
      <author>Alik Khilazhev</author>
      <guid>https://alikhil.dev/posts/in-place-pod-resize/</guid>
      <description>&lt;p&gt;About six years ago, while operating a large Java-based platform in Kubernetes, I noticed a recurring problem: our services required significantly higher CPU and memory during application startup. Heavy use of Spring Beans and AutoConfiguration forced us to set inflated resource requests and limits just to survive bootstrap, even though those resources were mostly unused afterwards.&lt;/p&gt;
&lt;p&gt;This workaround never felt right. As an engineer, I wanted a solution that reflected the actual lifecycle of an application rather than its worst moment.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>About six years ago, while operating a large Java-based platform in Kubernetes, I noticed a recurring problem: our services required significantly higher CPU and memory during application startup. Heavy use of Spring Beans and AutoConfiguration forced us to set inflated resource requests and limits just to survive bootstrap, even though those resources were mostly unused afterwards.</p>
<p>This workaround never felt right. As an engineer, I wanted a solution that reflected the actual lifecycle of an application rather than its worst moment.</p>
<p>I opened an <a href="https://github.com/kubernetes/kubernetes/issues/83111">issue</a>  in the Kubernetes repository describing the problem and proposing an approach to adjust pod resources dynamically without restarts. The issue received little discussion but quietly accumulated interest over time (13 👍 emoji reaction). Every few months, an automation bot attempted to mark it as stale, and every time, I removed the label. This went on for nearly six years&hellip;</p>
<p>Until the release of Kubernetes 1.35 where In-Place Pod Resize feature was <a href="https://kubernetes.io/blog/2025/12/19/kubernetes-v1-35-in-place-pod-resize-ga/">marked as stable</a>.</p>
<h2 id="what-in-place-pod-resize-brings">What In-Place Pod Resize Brings</h2>
<p>In-Place Pod Resize allows Kubernetes to update CPU and memory requests and limits without restarting pods, whenever it is safe to do so. This significantly reduces unnecessary restarts caused by resource changes, leading to fewer disruptions and more reliable workloads.</p>
<p>For applications whose resource needs evolve over time, especially after startup, this feature provides a long-missing building block.</p>
<h2 id="impact-on-verticalpodautoscaler">Impact on VerticalPodAutoscaler</h2>
<p>The new <code>resizePolicy</code> field is configured at the pod spec level. While it is technically possible to change pod resources manually, doing so does not scale. In practice, this feature should be driven by a workload controller.</p>
<p>At the moment, the only controller that supports in-place pod resize is the Vertical Pod Autoscaler (VPA).</p>
<p>There are two enhancement proposals enable this behavior:</p>
<ul>
<li>
<p><a href="https://github.com/kubernetes/autoscaler/tree/455d29039bf6b1eb9f784f498f28769a8698bc21/vertical-pod-autoscaler/enhancements/4016-in-place-updates-support">AEP-4016: Support for in place updates in VPA</a> which introduces <code>InPlaceOrRecreate</code> update mode</p>
</li>
<li>
<p><a href="https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler/enhancements/7862-cpu-startup-boost">AEP-7862: CPU Startup Boost</a> which is about temporarily boosting pod by giving more cpu during pod startup. This is conceptually similar to the approach proposed in my original issue.</p>
</li>
</ul>
<p>Here is an example of Deployment and VPA using both AEP features:</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">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="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="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">app</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">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">example</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">app</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">my-heavy-java-app:stable</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">requests</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">1000m</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">1024Mi</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">2000m</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">2048Mi</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="nn">---</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="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;autoscaling.k8s.io/v1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">VerticalPodAutoscaler</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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-vpa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">targetRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;apps/v1&#34;</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">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">updatePolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">updateMode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;InPlaceOrRecreate&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resourcePolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">containerPolicies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">containerName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;app&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">minAllowed</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="s2">&#34;250m&#34;</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="s2">&#34;512Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">maxAllowed</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="s2">&#34;3000m&#34;</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="s2">&#34;8192Mi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># The CPU boosted resources can go beyond maxAllowed.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">startupBoost</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></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="s2">&#34;Factor&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">quantity</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2&#34;</span><span class="w">
</span></span></span></code></pre></div><p>With such configuration pod will have doubled cpu requests and limits during startup. During the boost period no resizing will happen.</p>
<p>Once the pod reaches the <code>Ready</code> state, the VPA controller scales CPU down to the currently recommended value.</p>
<p>After that, VPA continues operating normally, with the key difference that resource updates are applied in place whenever possible.</p>
<h2 id="limitations">Limitations</h2>
<p>Does this feature fully solve the problem described above? Only partially.</p>
<p>First, most application runtimes still impose fundamental constraints. Java and Python runtimes do not currently support resizing memory limits without a restart. This limitation exists outside of Kubernetes itself and is tracked in the OpenJDK project via <a href="https://bugs.openjdk.org/browse/JDK-8359211">an open ticket</a>.</p>
<figure class="align-center ">
    <picture>
        <source type="image/webp" srcset="/images/posts/in-place-pod-resize/runtimes-supporting-in-place-memory-resize_hu_b0b138508605bfb9.webp">
        <img loading="lazy" src="/images/posts/in-place-pod-resize/runtimes-supporting-in-place-memory-resize.png#center"
         alt="In-Place Pod Resize in Kubernetes: Dynamic Resource Management Without Restarts - Tim Allclair &amp; Mofi Rahman, Google"/>
    </picture><figcaption>
            In-Place Pod Resize in Kubernetes: Dynamic Resource Management Without Restarts - Tim Allclair &amp; Mofi Rahman, Google
        </figcaption>
</figure>
<p>Second, Kubernetes does not yet support decreasing memory limits, even with in-place Pod Resize enabled. This is a known limitation documented in the enhancement proposal for <a href="https://github.com/kubernetes/enhancements/tree/758ea034908515a934af09d03a927b24186af04c/keps/sig-node/1287-in-place-update-pod-resources#memory-limit-decreases">memory limit decreases</a>.</p>
<p>As a result, while in-place Pod Resize effectively addresses CPU-related startup spikes, memory resizing remains an open problem.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>In place Pod Resize gives a foundation for cool new features like StartupBoost and makes use of VPA more reliable. While important gaps remain, such as <a href="https://github.com/kubernetes/kubernetes/issues/135670">memory decrease support</a> and <a href="https://github.com/kubernetes/kubernetes/issues/126891">scheduling race condition</a>, this change represents a meaningful step forward.</p>
<p>For workloads with distinct startup and steady-state phases, Kubernetes is finally beginning to model reality more closely.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>kubectl-find - UNIX-find-like plugin to find resources and perform action on them</title>
      <link>https://alikhil.dev/posts/kubectl-find-plugin-50-stars-anniversary/</link>
      <pubDate>Fri, 17 Oct 2025 19:27:24 +0300</pubDate>
      <author>Alik Khilazhev</author>
      <guid>https://alikhil.dev/posts/kubectl-find-plugin-50-stars-anniversary/</guid>
      <description>&lt;p&gt;Recently, I have developed a plugin for &lt;code&gt;kubectl&lt;/code&gt; inspired by UNIX &lt;code&gt;find&lt;/code&gt; utility to find and perform action on resources. And few days ago number of stars in the repo reached 50! I think it&amp;rsquo;s a good moment to tell more about the project.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I have developed a plugin for <code>kubectl</code> inspired by UNIX <code>find</code> utility to find and perform action on resources. And few days ago number of stars in the repo reached 50! I think it&rsquo;s a good moment to tell more about the project.</p>
<h2 id="the-problem">The problem</h2>
<p>As engineer who works with kubernetes everyday I use kubectl a lot. Actually, more than 50% of my terminal history commands are related to kubernetes. Here is a top 10 commands:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">1	<span class="m">2079</span>  46.7611%    kubectl
</span></span><span class="line"><span class="cl">2	<span class="m">425</span>   9.55915%    git
</span></span><span class="line"><span class="cl">3	<span class="m">324</span>   7.28745%    helm
</span></span><span class="line"><span class="cl">4	<span class="m">156</span>   3.50877%    <span class="nb">cd</span>
</span></span><span class="line"><span class="cl">5	<span class="m">146</span>   3.28385%    ssh
</span></span><span class="line"><span class="cl">6	<span class="m">130</span>   2.92398%    kctx
</span></span><span class="line"><span class="cl">7	<span class="m">114</span>   2.5641%     kns
</span></span><span class="line"><span class="cl">8	<span class="m">80</span>    1.79937%    gcloud-auth
</span></span><span class="line"><span class="cl">9	<span class="m">78</span>    1.75439%    curl
</span></span><span class="line"><span class="cl">10	<span class="m">66</span>    1.48448%    docker
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><blockquote>
<p>Run <a href="https://superuser.com/a/250230">this command</a> if you are curious what about yours the most popular commands in terminal history.</p>
</blockquote>
<p>I use kubectl to check status of the pods, delete orphaned resources, trigger sync on <code>ExternalSecrets</code> and much more. When I realized half my terminal history was just kubectl commands, I thought — there must be a better way to find things in Kubernetes without chaining pipes with <code>grep</code> / <code>awk</code> / <code>xargs</code>. And I imagined how nice it would be to have a UNIX <code>find</code>-like tool — something that lets you search for exactly what you need in the cluster and then perform actions directly on the matching resources. I searched for a krew plugin like this but there was not any. For that reason, I decided to develop <a href="https://github.com/alikhil/kubectl-find">one</a>!</p>
<h2 id="development">Development</h2>
<p>I used <a href="https://github.com/kubernetes/sample-cli-plugin">sample-cli-plugin</a> as a starting point. Its clean repository structure and straightforward design make it a great reference for working with the Kubernetes API. Additionally, it allows easy reuse of the extensive Kubernetes client libraries.</p>
<p>Almost everything in the Kubernetes ecosystem is written in Go, and this plugin is no exception — which is great, as it allows building binaries for a wide range of CPU architectures and operating systems.</p>
<h2 id="features-overview">Features overview</h2>
<h3 id="filters">Filters</h3>
<h4 id="find-by-resource-name-using-regex">Find by resource name using regex</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl fd pods -r dev
</span></span></code></pre></div><h4 id="find-pods-with-restarted-containers">Find pods with restarted containers</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl fd pods --restarted
</span></span></code></pre></div><h4 id="find-pods-with-image-matching-regex">Find pods with image matching regex</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl fd pods --image bitnami
</span></span></code></pre></div><h4 id="find-pods-by-status">Find pods by status</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl fd pods --status Pending
</span></span></code></pre></div><h4 id="find-pods-running-on-nodes-matching-regex">Find pods running on nodes matching regex</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl fd pods --node <span class="s1">&#39;*spot&#39;</span>
</span></span></code></pre></div><h4 id="find-any-resource-by-custom-condition">Find any resource by custom condition</h4>
<p>Use <code>jq</code> filter to find any resource by any custom condition. <code>kubectl-find</code> uses <a href="https://github.com/itchyny/gojq">gojq</a> implementation of <code>jq</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># find Services with trafficDistribution set to PreferClose</span>
</span></span><span class="line"><span class="cl">kubectl fd svc -j <span class="s1">&#39;.spec.trafficDistribution == &#34;PreferClose&#34;&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># find pods with unset nodeSelector</span>
</span></span><span class="line"><span class="cl">kubectl fd pods -j <span class="s1">&#39;.spec.nodeSelector == null&#39;</span> -A
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># find pods with undefined resources</span>
</span></span><span class="line"><span class="cl">kubectl fd pods -j <span class="s1">&#39;any( .spec.containers[]; .resources == {} )&#39;</span> -A
</span></span></code></pre></div><h3 id="actions">Actions</h3>
<p>By default, <code>fd</code> will print found resources to Stdout. However, there flags that you can provide to perform action on found resources:</p>
<ul>
<li><code>--delete</code> - to delete them</li>
<li><code>--patch</code> - to patch with provided JSON</li>
<li><code>--exec</code> - to run command on pods</li>
</ul>
<h2 id="getting-started">Getting started</h2>
<p>Use <a href="https://krew.sigs.k8s.io/">krew</a> to install the plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">krew install fd
</span></span></code></pre></div><h2 id="whats-next">What’s next</h2>
<p>I’m currently working on adding:</p>
<ul>
<li>JSON/YAML output format</li>
<li>More filters</li>
<li>Saved queries</li>
</ul>
<p>If you’re tired of writing long <code>kubectl | grep | xargs</code> chains, give <code>kubectl fd</code> a try — it’s already saved me countless keystrokes.</p>
<p>Check out the repo ⭐ <a href="https://github.com/alikhil/kubectl-find">github.com/alikhil/kubectl-find</a>
and share your ideas or issues — I’d love to hear how you use it!</p>
<p><a href="https://github.com/alikhil/kubectl-find">
  <img loading="lazy" src="https://api.star-history.com/svg?repos=alikhil/kubectl-find&amp;type=date&amp;legend=bottom-right" alt="Star History Chart"  /></a></p>]]></content:encoded>
    </item>
    
    <item>
      <title>OAuth2-proxy: protect services in kubernetes</title>
      <link>https://alikhil.dev/posts/oauth2-proxy-protect-services-in-k8s/</link>
      <pubDate>Tue, 05 Aug 2025 21:14:12 +0300</pubDate>
      <author>Alik Khilazhev</author>
      <guid>https://alikhil.dev/posts/oauth2-proxy-protect-services-in-k8s/</guid>
      <description>&lt;p&gt;The original post wrote &lt;a href=&#34;https://alikhil.dev/posts/oauth2-proxy-for-kubernetes-services/&#34;&gt;about oauth2-proxy&lt;/a&gt; over seven years ago was quite popular at the time and attracted a lot of organic traffic to my blog, which still benefits my SEO today. Since the tutorial had become outdated, I decided to rewrite it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The original post wrote <a href="https://alikhil.dev/posts/oauth2-proxy-for-kubernetes-services/">about oauth2-proxy</a> over seven years ago was quite popular at the time and attracted a lot of organic traffic to my blog, which still benefits my SEO today. Since the tutorial had become outdated, I decided to rewrite it.</p>
<h2 id="what-we-have">What we have</h2>
<p>We have a Kubernetes cluster with several web services deployed for internal use.</p>
<h2 id="what-we-want-to-achieve">What we want to achieve</h2>
<p>We want to expose our internal web services to the Internet, but restrict access by requiring authorization. Access should be granted only to users authenticated through our Identity Provider (such as Google, GitHub, Keycloak, etc.).</p>
<h2 id="assumptions">Assumptions</h2>
<p>For simplicity, let&rsquo;s assume that both <a href="https://github.com/kubernetes/ingress-nginx">ingress-nginx</a> and <a href="https://cert-manager.io/">cert-manager</a> are already deployed in the cluster.</p>
<p>I will use <a href="https://github.com/pocket-id/pocket-id">Pocket ID</a> as Identity Provider in this tutorial. Configuration slightly differs for different providers. Check the <a href="https://oauth2-proxy.github.io/oauth2-proxy/configuration/providers/">official documentation</a> for your provider.</p>
<p>For the examples in this guide, I’ll use my <code>alikhil.dev</code> domain:</p>
<p>– <code>pocket-id.k8s.alikhil.dev</code> - will be used for Pocket ID</p>
<ul>
<li>
<p><code>k8s.alikhil.dev</code> - will be used for oauth2-proxy. I recommend to have higher domain for oauth2-proxy service for easier cookie setup.</p>
</li>
<li>
<p><code>*.k8s.alikhil.dev</code> - reserved for services deployed for internal usage</p>
</li>
</ul>
<h2 id="preparation">Preparation</h2>
<h3 id="dns">DNS</h3>
<p>I have added two DNS records:</p>
<ol>
<li><code>A</code> record for k8s.alikhil.dev pointing to ingress-nginx <code>LoadBalancer</code> IP address in the cluster (<code>kubectl get svc -n ingress-nginx | grep LoadBalancer</code>)</li>
<li><code>CNAME</code> record for <code>*.k8s.alikhil.dev</code> pointing to <code>k8s.alikhil.dev</code></li>
</ol>
<h3 id="install-pocket-id">Install Pocket ID</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">helm repo add anza-labs https://anza-labs.github.io/charts
</span></span><span class="line"><span class="cl">helm upgrade --install pocket-id anza-labs/pocket-id -f ./values/pocket-id.yaml
</span></span></code></pre></div><details>
    <summary>Values for pocket-id</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">persistence</span><span class="p">:</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">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># -- Specifies whether ingress should be enabled.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># -- Ingress class name.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">className</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 class="c"># -- Annotations to add to the ingress.</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">nginx.ingress.kubernetes.io/ssl-redirect</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.allow-http</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/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="c"># -- Ingress host configuration.</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">pocket-id.k8s.alikhil.dev</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">pathType</span><span class="p">:</span><span class="w"> </span><span class="l">ImplementationSpecific</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># -- List of TLS configurations for the ingress.</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">secretName</span><span class="p">:</span><span class="w"> </span><span class="l">pocket-id-tls</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">pocket-id.k8s.alikhil.dev</span><span class="w">
</span></span></span></code></pre></div></details>
<h3 id="configure-pocketid">Configure PocketID</h3>
<p>Go to <a href="https://pocket-id.k8s.alikhil.dev/signup/setup">https://pocket-id.k8s.alikhil.dev/signup/setup</a> and set initial configuration for Pocket ID.</p>
<p><img loading="lazy" src="/images/posts/oauth2-proxy/pocket-id-setup.png" type="" alt="Initial setup page"  /></p>
<p>Then add your passkey.</p>
<p><img loading="lazy" src="/images/posts/oauth2-proxy/pocket-id-pass.png" type="" alt="Add passkey"  /></p>
<p>Create <strong>developers</strong> group and add yourself to the list of members.
<img loading="lazy" src="/images/posts/oauth2-proxy/pocket-id-groups.png" type="" alt="Create group"  /></p>
<p>After that, go to <strong>OIDC clients</strong> page and create one for oauth2-proxy. Set proper callback url.</p>
<p><img loading="lazy" src="/images/posts/oauth2-proxy/pocket-id-client.png" type="" alt="PocketID client creation"  /></p>
<p>Save generated <strong>Client ID</strong> and <strong>Client Secret</strong> for later use.</p>
<h2 id="installing-oauth2-proxy">Installing oauth2-proxy</h2>
<p>I am using raw k8s secrets in this tutorial, but I highly recommend storing secrets in Vault or similar services and use <a href="https://external-secrets.io/latest/">External Secretes Operator</a> to deliver them to kubernetes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl create secret generic oauth2-proxy-secrets --from-literal<span class="o">=</span>client-id<span class="o">=</span><span class="nv">$CLIENT_ID</span> --from-literal<span class="o">=</span>client-secret<span class="o">=</span><span class="nv">$CLIENT_SECRET</span> --from-literal<span class="o">=</span>cookie-secret<span class="o">=</span><span class="k">$(</span>openssl rand -base64 <span class="m">32</span> <span class="p">|</span> head -c <span class="m">32</span> <span class="p">|</span> base64<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests
</span></span><span class="line"><span class="cl">helm install oauth2-proxy oauth2-proxy/oauth2-proxy -f values/oauth2-proxy.yaml
</span></span></code></pre></div><details>
    <summary>Adjust domains in values for oauth2-proxy</summary>
<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></span><span class="line"><span class="cl"><span class="c"># Oauth client configuration specifics</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">existingSecret</span><span class="p">:</span><span class="w"> </span><span class="l">oauth2-proxy-secrets</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">cookieName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;general-oauth2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Default configuration, to be overridden</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">configFile</span><span class="p">:</span><span class="w"> </span><span class="p">|-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    email_domains = [ &#34;*&#34; ]
</span></span></span><span class="line"><span class="cl"><span class="sd">    upstreams = [ &#34;file:///dev/null&#34; ]
</span></span></span><span class="line"><span class="cl"><span class="sd">    skip_provider_button = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    allowed_groups = [ &#34;developers&#34;, &#34;admins&#34; ]
</span></span></span><span class="line"><span class="cl"><span class="sd">    cookie_secure = false
</span></span></span><span class="line"><span class="cl"><span class="sd">    cookie_domains = [&#34;.k8s.alikhil.dev&#34;, &#34;k8s.alikhil.dev&#34;]
</span></span></span><span class="line"><span class="cl"><span class="sd">    whitelist_domains = [ &#34;*.k8s.alikhil.dev&#34;, &#34;k8s.alikhil.dev&#34; ]
</span></span></span><span class="line"><span class="cl"><span class="sd">    cookie_samesite = &#34;lax&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    cookie_csrf_per_request = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    cookie_csrf_expire = &#34;15m&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    pass_access_token = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    pass_authorization_header = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    provider = &#34;oidc&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    provider_display_name = &#34;PocketID&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    reverse_proxy = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    scope = &#34;openid profile email groups&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    session_store_type = &#34;redis&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    set_xauthrequest = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    set_authorization_header = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    silence_ping_logging = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    skip_auth_preflight = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    ssl_insecure_skip_verify = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    ssl_upstream_insecure_skip_verify = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    insecure_oidc_allow_unverified_email = true
</span></span></span><span class="line"><span class="cl"><span class="sd">    oidc_issuer_url = &#34;https://pocket-id.k8s.alikhil.dev&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    redirect_url = &#34;https://k8s.alikhil.dev/oauth2/callback&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    # to reduce log amount
</span></span></span><span class="line"><span class="cl"><span class="sd">    request_logging = false</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></span><span class="line"><span class="cl"><span class="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">className</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">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="c"># Only used if API capabilities (networking.k8s.io/v1) allow it</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pathType</span><span class="p">:</span><span class="w"> </span><span class="l">ImplementationSpecific</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Used to create an Ingress record.</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">k8s.alikhil.dev</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 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">nginx.ingress.kubernetes.io/cors-allow-origin</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;*&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nginx.ingress.kubernetes.io/enable-cors</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">kubernetes.io/ingress.allow-http</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nginx.ingress.kubernetes.io/ssl-redirect</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">tls</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Secrets must be manually created in the namespace.</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">oauth2-proxy-tls</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">k8s.alikhil.dev</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="c"># Configure the session storage type, between cookie and redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">sessionStorage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Can be one of the supported session storage cookie|redis</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">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">existingSecret</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">passwordKey</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;redis-password&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">clientType</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;standalone&#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="c"># Enables and configure the automatic deployment of the redis subchart</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># provision an instance of the redis sub-chart</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">architecture</span><span class="p">:</span><span class="w"> </span><span class="l">standalone</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">auth</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">master</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">persistence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</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">requests</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">100m</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">128Mi</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="m">1</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">1Gi</span><span class="w">
</span></span></span></code></pre></div></details>
<h2 id="testing">Testing</h2>
<h3 id="install-whoami">Install whoami</h3>
<p>To check oauth2-proxy we need a dummy service. I will use <a href="https://artifacthub.io/packages/helm/cowboysysop/whoami">whoami</a> helm chart for this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">helm repo add cowboysysop https://cowboysysop.github.io/charts/
</span></span><span class="line"><span class="cl">helm install whoami cowboysysop/whoami
</span></span></code></pre></div><details>
    <summary>Values for whoami helm chart</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ingressClassName</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">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">nginx.ingress.kubernetes.io/ssl-redirect</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.allow-http</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/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="c"># put oauth2-proxy domain here</span><span class="w">
</span></span></span><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="s2">&#34;https://k8s.alikhil.dev/oauth2/start?rd=https://$host$request_uri$is_args$args&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># service-name.namespace-name</span><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-example.svc.cluster.local:80/oauth2/auth</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="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">whoami.k8s.alikhil.dev</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="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">whoami.k8s.alikhil.dev</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">whoami-cert</span><span class="w">
</span></span></span></code></pre></div></details>
<h3 id="perform-test">Perform test</h3>
<p>Go to whoami url and check if oauth2-proxy redirects you to Pocket ID like in the demo:</p>
<p><img loading="lazy" src="/images/posts/oauth2-proxy/demo.gif" type="" alt="Demo"  /></p>
<h2 id="takeaways">Takeaways</h2>
<p>Later, when you need to protect any service in Kubernetes with oauth2-proxy, you simply need to add two annotations to your Ingress resource:</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">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">nginx.ingress.kubernetes.io/auth-signin</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://k8s.alikhil.dev/oauth2/start?rd=https://$host$request_uri$is_args$args&#34;</span><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-example.svc.cluster.local:80/oauth2/auth</span><span class="w">
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <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>
      <author>Alik Khilazhev</author>
      <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 ">
    <picture>
        <source type="image/webp" srcset="/images/posts/k8s-graceful-shutdown_hu_bc6cd8ddc1517085.webp">
        <img loading="lazy" src="/images/posts/k8s-graceful-shutdown.png#center"
         alt="ChatGPT: draw funny picture of Kubernetes pod gracefully shutting down"/>
    </picture><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="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="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="c1">// Block until we receive a shutdown signal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&lt;-</span><span class="nx">stop</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="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="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="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="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="c1">// Readiness probe - returns 503 when shutting down</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="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="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="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="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="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="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="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="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="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="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="c1">// Step 4: Finally, shut down the server gracefully</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="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="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="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="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="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="c"># Give pods enough time to shut down gracefully</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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>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>
      <author>Alik Khilazhev</author>
      <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></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="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="k">COPY</span> ./dist /app<span class="err">
</span></span></span><span class="line"><span class="cl"><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="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="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="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="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="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="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="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="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="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>
      <author>Alik Khilazhev</author>
      <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">    --clusterrole cluster-admin <span class="se">\
</span></span></span><span class="line"><span class="cl">    --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">    --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">    --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">    --set config.LEGO_SUPPORTED_INGRESS_CLASS<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl">    --set config.LEGO_SUPPORTED_INGRESS_PROVIDER<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl">    --set config.LEGO_DEFAULT_INGRESS_CLASS<span class="o">=</span>nginx <span class="se">\
</span></span></span><span class="line"><span class="cl">    --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">    --set rbac.create<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    --set image.tag<span class="o">=</span>0.1.5 <span class="se">\
</span></span></span><span class="line"><span class="cl">    --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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
    
  </channel>
</rss>
