estradiol.cloud/public/posts/hugo-on-k8s-nginx/index.html

615 lines
47 KiB
HTML
Raw Normal View History

2024-03-12 23:53:26 +00:00
<!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)
----
2024-03-13 03:55:20 +00:00
Ь 887 | 2
2024-03-12 23:53:26 +00:00
">
<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
2024-03-12 23:53:26 +00:00
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>
2024-03-13 04:01:27 +00:00
<p>i could do without the extra <code>helm</code> client dependency on my laptop. i&rsquo;m also
2024-03-12 23:53:26 +00:00
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>
2024-03-13 03:55:20 +00:00
<p>Ь 887 | 2</p>
2024-03-12 23:53:26 +00:00
</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>