estradiol.cloud/public/posts/hugo-on-k8s-nginx/index.html
2024-03-12 21:06:40 -07:00

615 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head><title>Hugo on Kubernetes &amp; NGINX &ndash; estradiol.cloud</title>
<meta name="description" content="&gt; [the regrown limb can be monstrous, duplicated, potent. We have all been injured, profoundly.](https://doi.org/10.5749/minnesota/9780816650477.003.0001)
----
Ь 887 | 2
">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/academicons/1.9.4/css/academicons.min.css" integrity="sha512-IW0nhlW5MgNydsXJO40En2EoCkTTjZhI3yuODrZIc8cQ4h1XcF53PsqDHa09NqnkXuIe0Oiyyj171BqZFwISBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://estradiol.cloud/css/palettes/material.css">
<link rel="stylesheet" href="https://estradiol.cloud/css/risotto.css">
<link rel="stylesheet" href="https://estradiol.cloud/css/custom.css">
<link rel="alternate" type="application/rss+xml" title="estradiol.cloud Feed" href="index.xml" />
</head>
<body>
<div class="page">
<header class="page__header"><nav class="page__nav main-nav">
<ul>
<li class="nomarker"><h1 class="page__logo"><a href="https://estradiol.cloud/" class="page__logo-inner">estradiol.cloud</a></h1></li>
<li class="main-nav__item"><a class="nav-main-item active" href="https://estradiol.cloud/posts/" title="">Posts</a></li>
</ul>
</nav>
</header>
<section class="page__body">
<header class="content__header">
<h1>Hugo on Kubernetes &amp; NGINX</h1>
</header>
<div class="content__body">
<p>i decided to make a website. a static one. this one. with <a href="https://gohugo.io">Hugo</a>. the
main reason i have for needing a website is as a vanity project, so i have some
stuff to host in a <a href="https://kubernetes.io">Kubernetes</a> cluster i&rsquo;m running. the k8s cluster is
also a vanity project.</p>
<p>because i don&rsquo;t like software, i wanted a way to deploy my site that doesn&rsquo;t
involve much of it. this post is about that.</p>
<h2 id="getting-started">Getting Started</h2>
<p>i built my site by following the straight-forward
<em><a href="https://gohugo.io/getting-started">Getting Started</a></em> guide in the Hugo documentation.</p>
<p>i did <code>hugo new site estradiol.cloud</code>. and then <code>cd estradiol.cloud; git init</code>.
and then i picked a ridiculous theme
<a href="https://github.com/joeroe/risotto">&ldquo;inspired by terminal ricing aesthetics&rdquo;</a>, installing it like <code>git submodule add https://github.com/joeroe/risotto.git themes/risotto; echo &quot;theme = 'risotto'&quot; &gt;&gt; hugo.toml</code>.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>at this point, my website is basically finished (i also changed the title in
<code>hugo.toml</code>). i probably won&rsquo;t be putting anything on it, so there&rsquo;s no point
fiddling with other details.</p>
<p>about deployment, the Hugo guide&rsquo;s <em><a href="https://gohugo.io/getting-started/usage/#deploy-your-site">Basic Usage</a></em> page has this
to offer:</p>
<blockquote>
<p>Most of our users deploy their sites using a CI/CD workflow, where a push<sup>1</sup>
to their GitHub or GitLab repository triggers a build and deployment. Popular
providers include AWS Amplify, CloudCannon, Cloudflare Pages, GitHub Pages,
GitLab Pages, and Netlify.</p>
<ol>
<li>The Git repository contains the entire project directory, typically excluding the
public directory because the site is built <em>after</em> the push.</li>
</ol>
</blockquote>
<p>importantly, you can&rsquo;t make a post about deploying this way. <em>everyone</em> deploys
this way. if <em>i</em> deploy this way, this site will have no content.</p>
<p>this approach also involves a build system somewhere that can run Hugo to
compile the code and assets and push them onto my host. i definitely
already need Hugo installed on my laptop if i&rsquo;m going to post anything.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
so now i&rsquo;m running Hugo in two places. there&rsquo;s surely going to be other
complex nonsense like webhooks involved.</p>
<p><img src="images/hugo-github-pages.svg" alt="diagram: deploy w/ GitHub Pages &amp; Actions"></p>
<hr>
<p>and hang on. let&rsquo;s look at this again:</p>
<blockquote>
<ol>
<li>The Git repository contains the entire project directory, typically excluding the
public directory because the site is built <em>after</em> the push.</li>
</ol>
</blockquote>
<p>you&rsquo;re telling me i&rsquo;m going to build a nice static site and not check the
<em>actual content</em> into version control? couldn&rsquo;t be me.</p>
<h2 id="getting-static">Getting Static</h2>
<p>suppose i instead check my content in exactly as i intend to serve it?
then i could shell into my server box, pull the site, and <em>nifty-galifty!</em> isn&rsquo;t
this the way it has <a href="https://www.mikecurato.com/worm-loves-worm">always been done</a>?</p>
<p>my problem is that i don&rsquo;t have a server box. i have a <em>container orchestration
system</em>. there are several upsides to this<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> but it means that <em>somehow</em> my
generated content needs to end up in a container. because <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pods</a> are
ephemeral and i&rsquo;d like to run my site with horizontal scalability<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, i don&rsquo;t
want my container to retain runtime state across restarts or replicas.</p>
<p>i <em>could</em> run a little pipeline that builds a container image wrapping my
content and pushes it to a registry. when i deploy, the cluster pulls the
image, content and all. all ready to go. but now i&rsquo;ve got <em>software</em> again:
build stages and webhooks and, to make matters worse, now i&rsquo;m hosting
and versioning container images.</p>
<p><img src="images/hugo-container-build.svg" alt="diagram: deploy w/ container build"></p>
<p>i don&rsquo;t want any of this. i just want to put some HTML and static assets behind a
web server.</p>
<hr>
<p>instead, i&rsquo;d like to deploy a popular container image from a public registry
and deliver my content to it continuously.</p>
<p>a minimal setup to achieve this might look like:</p>
<ul>
<li>a <code>Pod</code> with:
<ul>
<li>an <code>nginx</code> container to serve the content;</li>
<li>a <code>git-pull</code> sidecar that loops, pulling the content;</li>
<li>an <code>initContainer</code> to do the initial checkout;</li>
<li>an <code>emptyDir</code> volume to share between the containers.</li>
</ul>
</li>
<li>a <code>ConfigMap</code> to store the nginx config.</li>
</ul>
<p><img src="images/hugo-minimal-pod-setup.svg" alt="diagram: minimal pod/configmap setup"></p>
<p>when a new <code>Pod</code> comes up, the <code>initContainer</code> mounts the
<a href="https://kubernetes.io/docs/concepts/storage/volumes/#emptydir"><code>emptyDir</code></a> at <code>/www</code> and clones the repository into it. i use
<code>git sparse-checkout</code> to avoid pulling repository contents i don&rsquo;t want to serve
out:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># git-clone command</span>
</span></span><span style="display:flex;"><span>git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/www;
</span></span><span style="display:flex;"><span>cd /tmp/www;
</span></span><span style="display:flex;"><span>git sparse-checkout init --cone;
</span></span><span style="display:flex;"><span>git sparse-checkout set public;
</span></span><span style="display:flex;"><span>git checkout;
</span></span><span style="display:flex;"><span>shopt -s dotglob
</span></span><span style="display:flex;"><span>mv /tmp/www/* /www
</span></span></code></pre></div><p>for the sidecar, i script up a <code>git pull</code> loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># git-pull command</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> true; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span> cd /www <span style="color:#f92672">&amp;&amp;</span> git -c safe.directory<span style="color:#f92672">=</span>/www pull origin trunk
</span></span><span style="display:flex;"><span> sleep <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>and i create a <a href="https://kubernetes.io/docs/concepts/configuration/configmap/">ConfigMap</a> with a server block to configure
<code>nginx</code> to use Hugo&rsquo;s <code>public/</code> as root:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span># ConfigMap; data: default.conf
</span></span><span style="display:flex;"><span>server {
</span></span><span style="display:flex;"><span> listen 80;
</span></span><span style="display:flex;"><span> location / {
</span></span><span style="display:flex;"><span> root /www/public;
</span></span><span style="display:flex;"><span> index index.html;
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>the rest of this is pretty much boilerplate:</p>
<p>
<details>
<summary><code>kubectl apply -f https://estradiol.cloud/posts/hugo-on-k8s/site.yaml</code></summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># estradiol-cloud.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/instance</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx-server-block</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">default.conf</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> server {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> listen 80;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> location / {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> root /www/public;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> index index.html;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> }</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Pod</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/instance</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">nginx:1.25.4</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">containerPort</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">www</span>
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/etc/nginx/conf.d</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx-server-block</span>
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">git-pull</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">bitnami/git</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">command</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">/bin/bash</span>
</span></span><span style="display:flex;"><span> - -<span style="color:#ae81ff">ec</span>
</span></span><span style="display:flex;"><span> - |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> while true; do
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> cd /www &amp;&amp; git -c safe.directory=/www pull origin trunk
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> sleep 60
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> done</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">initContainers</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">git-clone</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">bitnami/git</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">command</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">/bin/bash</span>
</span></span><span style="display:flex;"><span> - -<span style="color:#ae81ff">c</span>
</span></span><span style="display:flex;"><span> - |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> shopt -s dotglob
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/www;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> cd /tmp/www;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git sparse-checkout init --cone;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git sparse-checkout set public;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git checkout;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> mv /tmp/www/* /www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">www</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">emptyDir</span>: {}
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx-server-block</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx-server-block</span>
</span></span></code></pre></div>
</details>
</p>
<hr>
<p>my Hugo workflow now looks like:</p>
<ol>
<li>make changes to source;</li>
<li>run <code>hugo --gc --minify</code>;<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li><code>git</code> commit &amp; push.</li>
</ol>
<p>my <code>git pull</code> control loop takes things over from here and i&rsquo;m on easy street.</p>
<h2 id="getting-web">Getting Web</h2>
<p>this is going great! my <code>Pod</code> is running. it&rsquo;s serving out my code. i get
Continuous Deployment™ for the low price of 11 lines <code>bash</code>. i mean&hellip;
no one can actually browse to my website<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> but that will be an easy fix,
right? yes. networking is always the easy part.</p>
<p>first, i need a <a href="https://kubernetes.io/docs/concepts/services-networking/service/"><code>Service</code></a>. this gives me a proxy to my several
replicas<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> and in-cluster service discovery.</p>
<p>
<details>
<summary><code>kubectl apply -f https://estradiol.cloud/posts/hugo-on-k8s-nginx/service.yaml</code></summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># service.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/instance</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">type</span>: <span style="color:#ae81ff">ClusterIP</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/instance</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">http</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">targetPort</span>: <span style="color:#ae81ff">http</span>
</span></span></code></pre></div>
</details>
</p>
<p>next, i need an <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/"><code>Ingress</code></a> to handle traffic inbound to the cluster
and direct it to the <code>Service</code>:</p>
<p>
<details>
<summary><code>kubectl apply -f https://estradiol.cloud/posts/hugo-on-k8s-nginx/ingress.yaml</code></summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># ingress.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">networking.k8s.io/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Ingress</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/instance</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">rules</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">host</span>: <span style="color:#ae81ff">estradiol.cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">http</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">paths</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">backend</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">service</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">port</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">http</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">path</span>: <span style="color:#ae81ff">/</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">pathType</span>: <span style="color:#ae81ff">Prefix</span>
</span></span></code></pre></div>
</details>
</p>
<p>this part expresses a routing rule: traffic reaching the cluster via
<code>estradiol.cloud</code> should go to my <code>Service</code>, and then to one of its backend <code>Pod</code>s.
to actually apply this rule, i need an ingress controller. mine is
<a href="https://kubernetes.github.io/ingress-nginx/">ingress-nginx</a>.</p>
<p>when i deployed controller in my cluster, it created <em>some more</em> <code>nginx</code> <code>Pod</code>s.
these update their configuration dynamically based on the rules
in my <code>Ingress</code> resource(s). the controller also also creates a <code>Service</code> of
type <code>LoadBalancer</code>, which <a href="https://docs.digitalocean.com/products/kubernetes/how-to/add-load-balancers/">magically</a> creates a load balancer appliance
in my cloud provider. off-screen, i can point DNS to <em>that</em> appliance to finish
the setup.</p>
<p><a href="https://kubernetes.io/docs/concepts/services-networking/ingress/#what-is-ingress"><img src="https://kubernetes.io/docs/images/ingress.svg" alt="diagram: kubernetes ingress"></a></p>
<p>you can tell it&rsquo;s working by looking at your browser bar.</p>
<hr>
<p>as this has come together, i&rsquo;ve gotten increasingly anxious about how much
YAML i&rsquo;ve had to write. this is a problem because YAML is software and, as
established, i&rsquo;m hoping not to have much of that. it&rsquo;s also annoying that most
of this YAML really is just boilerplate.</p>
<p>conveniently, <a href="https://bitnami.com/">Bitnami</a> maintains a <a href="https://helm.sh">Helm</a> Chart that templates
out all the boilerplate and does exactly what i&rsquo;ve just been doing.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> i can
replace all my YAML with a call out to this chart and a few lines of
configuration, assuming i have <a href="https://helm.sh/docs/intro/install/">helm client installed</a>:</p>
<p>
<details>
<summary><code>helm upgrade --install --create-namespace --namespace estradiol-cloud -f https://estradiol.cloud/posts/hugo-on-k8s-nginx/values.yaml oci://registry-1.docker.io/bitnamicharts/nginx</code></summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># values.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">cloneStaticSiteFromGit</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">repository</span>: <span style="color:#e6db74">&#34;https://code.estradiol.cloud/tamsin/estradiol.cloud.git&#34;</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">branch</span>: <span style="color:#ae81ff">trunk</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">gitClone</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">command</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">/bin/bash</span>
</span></span><span style="display:flex;"><span> - -<span style="color:#ae81ff">ec</span>
</span></span><span style="display:flex;"><span> - |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> [[ -f &#34;/opt/bitnami/scripts/git/entrypoint.sh&#34; ]] &amp;&amp; source &#34;/opt/bitnami/scripts/git/entrypoint.sh&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git clone {{ .Values.cloneStaticSiteFromGit.repository }} --no-checkout --branch {{ .Values.cloneStaticSiteFromGit.branch }} /tmp/app
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> [[ &#34;$?&#34; -eq 0 ]] &amp;&amp; cd /tmp/app &amp;&amp; git sparse-checkout init --cone &amp;&amp; git sparse-checkout set public &amp;&amp; git checkout &amp;&amp; shopt -s dotglob &amp;&amp; rm -rf /app/* &amp;&amp; mv /tmp/app/* /app/</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ingress</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">estradiol.cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ingressClassName</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">tls</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">annotations</span>: {
</span></span><span style="display:flex;"><span> <span style="color:#f92672">cert-manager.io/cluster-issuer</span>: <span style="color:#ae81ff">letsencrypt-prod</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span><span style="color:#f92672">serverBlock</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> server {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> listen 8080;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> root /app/public;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> index index.html;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> }</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">service</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">type</span>: <span style="color:#ae81ff">ClusterIP</span>
</span></span></code></pre></div>
</details>
</p>
<p><img src="images/hugo-helm-setup.svg" alt="diagram: helm setup"></p>
<p>configuration for the <code>git-clone</code> script and our custom server block are added
via <code>values.yaml</code>. the <code>git-pull</code> loop configured by the chart works as-is.
by using the chart, we get a few other nicities. for instance,
my <code>Pod</code>s are now managed by a <a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"><code>Deployment</code></a>.<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> this will make my
grand scale-out plans a breeze.</p>
<h2 id="getting-fluxd">Getting Flux&rsquo;d</h2>
<p>by now, i&rsquo;m riding high. my whole setup is my static site code and &lt;30 lines of
YAML.</p>
<p>i <em>do</em> have a bunch of stuff deployed into my cluster, and none of this is very
reproducible without all of that. my workflow has also expanded to:</p>
<ol>
<li>for routine site deploys:
<ol>
<li>make changes to source;</li>
<li>run <code>hugo --gc --minify</code>;<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
<li><code>git</code> commit &amp; push.</li>
</ol>
</li>
<li>to update <code>nginx</code>, the chart version, or change config:
<ol>
<li>make changes to <code>values.yaml</code></li>
<li><code>helm upgrade</code></li>
</ol>
</li>
</ol>
<p>i could do without the extra <code>helm</code> client dependency on my laptop. i&rsquo;m also
pretty <code>git push</code>-pilled, and i really want the solution to all my problems
to take the now familiar shape: put a control loop in my cluster and push
to a <code>git</code> repository.</p>
<p>enter <a href="https://fluxcd.io/"><code>flux</code></a>.</p>
<p>with <code>flux</code>, i decide on a repository (and maybe a path within it) to act as
a source for my Kubernetes YAML. i go through a short <a href="https://fluxcd.io/flux/installation/bootstrap/">bootstrap</a>
process which installs the <code>flux</code> controllers and add them to repository. to
make a change to a resource in my cluster, i edit the YAML and push to the
repository. <code>flux</code> listens and applies the changes.</p>
<p><code>flux</code> supports Helm deploys, so i can get that <code>helm</code> client off my laptop.
i can also use it to manage my ingress controller, <code>cert-manager</code>, <code>flux</code>
itself and whatever other infrastructural junk i may end up needing.</p>
<p>to move my web stack into <code>flux</code>, i create a <code>HelmRepository</code> resource for
the <code>bitnami</code> Helm charts:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># bitnami-helm.yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">source.toolkit.fluxcd.io/v1beta2</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">HelmRepository</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">bitnami</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">default</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">url</span>: <span style="color:#ae81ff">https://charts.bitnami.com/bitnami</span>
</span></span></code></pre></div><p>and add a <code>HelmRelease</code> pointing to the repository/chart version and containing
my <code>values.yaml</code>:</p>
<p>
<details>
<summary><code>release.yaml</code></summary>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">helm.toolkit.fluxcd.io/v2beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">HelmRelease</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">web</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">estradiol-cloud</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">interval</span>: <span style="color:#ae81ff">5m</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">chart</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">chart</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">version</span>: <span style="color:#e6db74">&#39;15.12.2&#39;</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">sourceRef</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">HelmRepository</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">name</span>: <span style="color:#ae81ff">bitnami</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">default</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">interval</span>: <span style="color:#ae81ff">1m</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">values</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">cloneStaticSiteFromGit</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">repository</span>: <span style="color:#e6db74">&#34;https://code.estradiol.cloud/tamsin/estradiol.cloud.git&#34;</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">branch</span>: <span style="color:#ae81ff">trunk</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">gitClone</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">command</span>:
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">/bin/bash</span>
</span></span><span style="display:flex;"><span> - -<span style="color:#ae81ff">ec</span>
</span></span><span style="display:flex;"><span> - |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> [[ -f &#34;/opt/bitnami/scripts/git/entrypoint.sh&#34; ]] &amp;&amp; source &#34;/opt/bitnami/scripts/git/entrypoint.sh&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> git clone {{ .Values.cloneStaticSiteFromGit.repository }} --no-checkout --branch {{ .Values.cloneStaticSiteFromGit.branch }} /tmp/app
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> [[ &#34;$?&#34; -eq 0 ]] &amp;&amp; cd /tmp/app &amp;&amp; git sparse-checkout init --cone &amp;&amp; git sparse-checkout set public &amp;&amp; git checkout &amp;&amp; shopt -s dotglob &amp;&amp; rm -rf /app/* &amp;&amp; mv /tmp/app/* /app/</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ingress</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">hostname</span>: <span style="color:#ae81ff">estradiol.cloud</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ingressClassName</span>: <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">tls</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">annotations</span>: {
</span></span><span style="display:flex;"><span> <span style="color:#f92672">cert-manager.io/cluster-issuer</span>: <span style="color:#ae81ff">letsencrypt-prod</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#f92672">serverBlock</span>: |-<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> server {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> listen 8080;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> root /app/public;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> index index.html;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> }</span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">service</span>:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">type</span>: <span style="color:#ae81ff">ClusterIP</span>
</span></span></code></pre></div>
</details>
</p>
<p>when i push these to my <code>flux</code> <a href="https://gitlab.com/no_reply/sublingual/-/tree/trunk/estradiol.cloud">source repository</a>, the Helm
release rolls out.</p>
<p><img src="images/flux-seq.svg" alt="diagram: flux git push/deploy sequence"></p>
<h2 id="a-note-about-software">A Note About Software</h2>
<p>in the end, i&rsquo;m forced to admit there&rsquo;s still a lot of software involved in all
of this. setting aside the stuff that provisions and scales my cluster nodes,
and the <em>magic</em> <code>LoadBalancer</code>, i have:</p>
<ul>
<li><code>nginx</code> (running from a stock image);</li>
<li><code>git</code> &amp; <code>bash</code> (running from a stock image);</li>
<li>a remote git server (i&rsquo;m running <code>gitea</code><sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>, but github dot com is fine here);</li>
<li>Kubernetes (oops!);
<ul>
<li><code>flux</code>, especially <code>kustomize-controller</code> and <code>helm-controller</code>;</li>
<li><code>ingress-nginx</code> controller;</li>
<li><code>cert-manager</code> and Let&rsquo;s Encrypt;</li>
</ul>
</li>
<li>the <code>bitnami/nginx</code> Helm chart;</li>
</ul>
<p>the bulk of this i&rsquo;ll be able to reuse for the other things i deploy on the
cluster<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>. and it replaces SASS black-boxes like &ldquo;AWS Amplify, CloudCannon,
Cloudflare Pages, GitHub Pages, GitLab Pages, and Netlify&rdquo; in the recommended
Hugo deployment.</p>
<p>to actually deploy my site, i get to maintain a <code>bash</code> scripts for <code>git-clone</code>, my
NGINX config, and a couple of blobs of YAML.</p>
<p>at least there are no webhooks.</p>
<hr>
<p><em>fin</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>i appreciate the culinary branding.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>unlikely.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>few of which could be considered relevant for my project.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>i absolutely will not need this&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>i added <code>disableHTML = true</code> and <code>disableXML = true</code> to <code>[minify]</code>
configuration in <code>hugo.toml</code> to keep HTML and RSS diffs readable.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>i can check that its working, at least, with a <a href="https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/">port-forward</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>lmao&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>what incredible luck! (obviously, until now i&rsquo;ve been working backward from this chart)&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>i also snuck a TLS certificate configuration via Let&rsquo;s Encrypt with
<a href="https://cert-manager.io/docs/tutorials/acme/nginx-ingress/"><code>cert-manager</code></a> into this iteration. if you&rsquo;re following along at home and don&rsquo;t have <code>cert-manager</code> installed, this should still work fine (but with no HTTPS).&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>because i&rsquo;m running <code>gitea</code> in my cluster and i want to avoid a circular
dependency for my <code>flux</code> source repository, i also depend on GitLab dot com.&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>i won&rsquo;t.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
</div>
<footer class="content__footer"></footer>
</section>
<section class="page__aside">
<div class="aside__about">
<div class="aside__about">
<h1 class="about__title">it&#39;s estradiol.cloud!</h1>
<p class="about__description"><blockquote>
<p><a href="https://doi.org/10.5749/minnesota/9780816650477.003.0001">the regrown limb can be monstrous, duplicated, potent. We have all been injured, profoundly.</a></p>
</blockquote>
<hr>
<p>Ь 887 | 2</p>
</p>
</div>
<ul class="aside__social-links">
<li>
<i class="fa-brands fa-mastodon"></i> <a href="https://hachyderm.io/@no_reply" rel="me" title="Hachyderm">hachyderm.io/@no_reply</a>
</li>
<li>
<i class="fa-brands fa-mastodon"></i> <a href="https://chaosfem.tw/@t4tamsin" rel="me" title="Chaosfem"></i>chaosfem.tw/@t4tamsin</a>
</li>
<li>
<i class="fa-brands fa-git-alt"></i> <a href="https://code.estradiol.cloud" rel="me" title="Code">code</a>
</li>
<li>
<i class="fa-brands fa-gitlab"></i> <a href="https://gitlab.com/no-reply" rel="me" title="Code">work code</a>
</li>
<li>
<i class="fa-brands fa-github"></i> <a href="https://github.com/no_eply" rel="me" title="Code">more code</a>
</li>
</ul>
</div>
<hr>
<div class="aside__content">
<p>kubernetes is for girls</p>
<p>
2024-03-12
</p>
<hr>
Hugo on Kubernetes &amp; NGINX:
<nav id="TableOfContents">
<ol>
<li><a href="#getting-started">Getting Started</a></li>
<li><a href="#getting-static">Getting Static</a></li>
<li><a href="#getting-web">Getting Web</a></li>
<li><a href="#getting-fluxd">Getting Flux&rsquo;d</a></li>
<li><a href="#a-note-about-software">A Note About Software</a></li>
</ol>
</nav>
</div>
</section>
<footer class="page__footer"></footer>
</div>
</body>
</html>