diff --git a/.d2/flux-seq.d2 b/.d2/flux-seq.d2
new file mode 100644
index 0000000..d0d63b8
--- /dev/null
+++ b/.d2/flux-seq.d2
@@ -0,0 +1,18 @@
+flux deployment: {
+ shape: sequence_diagram
+
+ laptop: My Laptop
+ gitlab: Git Repo
+ k8s: Kubernetes
+ flux: Flux Kustomize Controller
+ fluxhelm: Flux Helm Controller
+
+ laptop -> gitlab: git push
+ gitlab <- flux: git pull
+ k8s <- flux: apply HelmRelease
+
+ k8s."HelmRelease"
+ fluxhelm -> k8s: poll for HelmRelease resources
+ fluxhelm -> k8s: `helm upgrade`
+ k8s."NGINX Deployment, etc..."
+}
\ No newline at end of file
diff --git a/.d2/hugo-gitlab-pages.d2 b/.d2/hugo-gitlab-pages.d2
new file mode 100644
index 0000000..c35dfdb
--- /dev/null
+++ b/.d2/hugo-gitlab-pages.d2
@@ -0,0 +1,38 @@
+title: |md
+ # deploy w/ GitHub pages & actions
+| {
+ shape: text
+ near: top-center
+}
+
+my laptop {
+ icon: https://icons.terrastruct.com/tech%2Flaptop.svg
+ near: center-left
+}
+
+my laptop -> GitHub.repo: git push
+
+GitHub {
+ icon: https://icons.terrastruct.com/social%2F039-github.svg
+
+ repo <- runner.build job: pull
+
+ runner {
+ build job
+ deploy job
+ }
+
+ events api <- runner: long poll
+
+ runner.build job -> artifact storage.pages artifact: uses
+ runner.deploy job -> artifact storage.pages artifact: uses
+
+ runner.deploy job -> pages.Hugo site: deploys
+ artifact storage {
+ pages artifact
+ }
+
+ pages {
+ Hugo site
+ }
+}
diff --git a/content/posts/hugo-on-k8s-nginx.md b/content/posts/hugo-on-k8s-nginx.md
index 935392d..79e17a2 100644
--- a/content/posts/hugo-on-k8s-nginx.md
+++ b/content/posts/hugo-on-k8s-nginx.md
@@ -3,29 +3,34 @@ title = 'Hugo on Kubernetes & NGINX'
date = 2024-02-28T15:35:46-08:00
draft = true
series = ['wtf']
-categories = ['Tech']
-tags = ['meta', 'k8s']
+categories = ['Tutorial']
+tags = ['meta', 'k8s', 'flux', 'hugo']
+toc = true
+++
-i decided to make a website. a static one. this one. with [Hugo][hugo]. this
-is basically as a vanity project so i have some stuff to host in a
-[Kubernetes][k8s] cluster i'm running. the k8s cluster is also as a vanity
-project.
+i decided to make a website. a static one. this one. with [Hugo][hugo]. the
+main reason i have for this is as a vanity project so i have some stuff to
+host in a [Kubernetes][k8s] cluster i'm running. the k8s cluster is also as
+a vanity project.
-because i don't like software, i wanted a way to deploy my site that
-doesn't involve much of it. this post is about that.
+because i don't like software, i wanted a way to deploy my site that doesn't
+involve much of it. this post is about that.
## Getting Started
-i built my site by following the straight-forward _[Getting Started][hugo-started]_
-guide in the Hugo documentation.
+i built my site by following the straight-forward
+_[Getting Started][hugo-started]_ guide in the Hugo documentation.
-i did `hugo new site estradiol.cloud`. and then `cd estradiol.cloud; git init`. and
-then i picked a ridiculous theme ["inspired by terminal ricing aesthetics"][risotto],
-installing it like `git submodule add https://github.com/joeroe/risotto.git themes/risotto; echo "theme = 'risotto'" >> hugo.toml`. i appreciate the culinary naming choice.
+i did `hugo new site estradiol.cloud`. and then `cd estradiol.cloud; git init`.
+and then i picked a ridiculous theme
+["inspired by terminal ricing aesthetics"][risotto], installing it like `git
+submodule add https://github.com/joeroe/risotto.git themes/risotto; echo "theme
+= 'risotto'" >> hugo.toml`.[^1]
-at this point, my website is basically finished (i also changed the title in `hugo.toml`).
-i probably won't be putting anything on it, so there's no point fiddling with other
+[^1]: i appreciate the culinary theme.
+
+at this point, my website is basically finished (i also changed the title in
+`hugo.toml`). i probably won't be putting anything on it, so there's no point fiddling with other
details.
about deployment, the guide's _[Basic Usage][hugo-deploy]_ page has this to offer:
@@ -42,12 +47,14 @@ importantly, you can't make a post about deploying this way. _everyone_ deploys
this way. if _i_ deploy this way, this site will have no content.
it also involves some system somewhere that can run Hugo to build the site and push
-it to some remote system where my cluster can reach the `public/` output. i definitely
-already need Hugo installed on my workstation if i'm going to post anything here
-(unlikely), so now i'm running Hugo in two places. there's surely going to be
-other complex nonsense like webhooks involved.
+it to some remote system where my cluster can reach the compiled site. i definitely
+already need Hugo installed on my workstation if i'm going to post anything.[^2]
+so now i'm running Hugo in two places. there's surely going to be other complex
+nonsense like webhooks involved.
-
+[^2]: unlikely.
+
+![diagram: deploy w/ GitHub pages & actions](images/hugo-github-pages.svg)
----
@@ -61,37 +68,109 @@ _actual content_ into version control? couldn't be me.
## Getting Static
-what if instead i pushed my site to a git repository exactly as i intend to serve it?
-then i could shell into my web server, pull the site, and _nifty-galifty!_ isn't this
-the way it has [always been done][worm]?
+suppose i instead checked my content into git exactly as i intend to serve it?
+then i could shell into my server box, pull the site, and _nifty-galifty!_ isn't
+this the way it has [always been done][worm-love]?
-one problem is that i don't have a web server, i have a _container orchestration
-system_. there are several upsides to this (few of which are relevant for my project)
-but it also means that _somehow_ my content needs to end up in a container, and i
-don't want that container to need to retain state across restarts or replicas.
-i _could_ run a little pipeline that builds a container wrapping my static site,
+my problem is that i don't have a server box. i have a _container orchestration
+system_. there are several upsides to this[^3] but it means that _somehow_ my
+generated content needs to end up in a container. because [Pods][k8s-pods] are
+ephemeral and i'd like to run my site with horizontal scalability[^4], i don't
+want my container to need to retain runtime state across restarts or replicas.
+
+[^3]: few of which could be considered relevant for my project.
+[^4]: i absolutely will not need this
+
+i _could_ run a little pipeline that builds a container wrapping my content and
pushes it to a registry somewhere my deployments can pull it. all ready to go.
-but now i've got _software_ again: build stages and webhooks and to make matters
+but now i've got _software_ again: build stages and webhooks and, to make matters
worse, now i'm hosting and versioning container images.
-
+![diagram: deploy w/ container build](/images/hugo-container-build.svg)
-i don't want any of this.
+i don't want any of this. i just want to put some HTML and static assets behind a
+web server.
---
-instead, i'd like to deploy a popular stock container from a public registry and
-deliver my content to it continuously.
+instead, i'd like to deploy a popular container image from a public registry
+and deliver my content to it continuously.
-as a minimal version of this, i could do:
+a minimal setup to achieve this might look like:
-```yaml
+ - a `Pod` with:
+ - an `nginx` container to serve the content;
+ - a `git-pull` sidecar that loops, pulling the git content;
+ - an `initContainer` to do the initial checkout;
+ - an `emptyDir` volume to share between the containers.
+ - a `ConfigMap` to store the nginx config.
+
+
+![diagram: minimal pod/configmap setup](/images/hugo-minimal-pod-setup.svg)
+
+i use `git sparse-checkout` to avoid pulling repository contents i don't want
+to serve out:
+
+```bash
+# git-clone command
+git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/www;
+cd /tmp/www;
+git sparse-checkout init --cone;
+git sparse-checkout set public;
+git checkout;
+shopt -s dotglob
+mv /tmp/www/* /www
+```
+
+script up my `git pull` loop:
+
+```bash
+# git-pull command
+while true; do
+ cd /www && git -c safe.directory=/www pull origin trunk
+ sleep 60
+done
+```
+
+and configure `nginx` to use `public/` as root:
+
+```txt
+# ConfigMap; data: default.conf
+server {
+ listen 80;
+ location / {
+ root /www/public;
+ index index.html;
+ }
+}
+```
+
+the rest of this is pretty much boilerplate:
+
+{{< code-details summary="`kubectl apply -f estradiol-cloud.yaml`" lang="yaml" details=`
+# estradiol-cloud.yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ labels:
+ app.kubernetes.io/instance: estradiol-cloud
+ app.kubernetes.io/name: nginx
+ name: nginx-server-block
+data:
+ default.conf: |-
+ server {
+ listen 80;
+ location / {
+ root /www/public;
+ index index.html;
+ }
+ }
+---
apiVersion: v1
kind: Pod
metadata:
name: nginx
- namespace: ec
labels:
app.kubernetes.io/instance: estradiol-cloud
app.kubernetes.io/name: nginx
@@ -102,8 +181,10 @@ spec:
ports:
- containerPort: 80
volumeMounts:
- - mountPath: /app
- name: staticsite
+ - mountPath: /www
+ name: www
+ - mountPath: /etc/nginx/conf.d
+ name: nginx-server-block
- name: git-pull
image: bitnami/git
command:
@@ -111,142 +192,66 @@ spec:
- -ec
- |
while true; do
- cd /app && git -c safe.directory=/app pull origin trunk
+ cd /www && git -c safe.directory=/www pull origin trunk
sleep 60
done
volumeMounts:
- - mountPath: /app
- name: staticsite
+ - mountPath: /www
+ name: www
initContainers:
- name: git-clone
image: bitnami/git
command:
- /bin/bash
- - -ec
+ - -c
- |
- git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/app
- cd /tmp/app && git sparse-checkout init --cone
- git sparse-checkout set public && git checkout
- rm -rf /app && mv /tmp/app /app
+ shopt -s dotglob
+ git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/www;
+ cd /tmp/www;
+p git sparse-checkout init --cone;
+ git sparse-checkout set public;
+ git checkout;
+ mv /tmp/www/* /www
volumeMounts:
- - mountPath: /app
- name: staticsite
+ - mountPath: /www
+ name: www
volumes:
- - emptyDir: {}
- name: staticsite
+ - name: www
+ emptyDir: {}
+ - name: nginx-server-block
+ configMap:
+ name: nginx-server-block
+` >}}
---
-apiVersion: v1
-kind: ConfigMap
-metadata:
- labels:
- app.kubernetes.io/instance: estradiol-cloud
- app.kubernetes.io/name: nginx
- name: nginx-server-block
- namespace: ec
-data:
- server-block.conf: |-
- server {
- listen 8080;
- root /app/public;
- index index.html;
- }
-```
+my Hugo workflow now looks like:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ 1. make changes to source;
+ 1. run `hugo --gc --minify`;[^7]
+ 1. commit & push.
+
+the only active process from this point is my little control loop running `git pull`.
+
+[^7]: i added `disableHTML = true` to `[minify]` configuration in `hugo.toml`
+to keep HTML diffs readable.
+
+## Getting Web
+
+my Pod is running. everything is great. if i want to browse to my website i
+just need to setup a [port-forward][k8s-port]
+
+TK: YAML counts as software.
+
+conveniently, [Bitnami][bitnami] maintains a [Helm][helm] Chart that
+
+![diagram: helm setup](/images/hugo-helm-setup.svg)
## Getting Flux'd
-```yaml
-apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: HelmRepository
-metadata:
- name: bitnami
- namespace: default
-spec:
- url: https://charts.bitnami.com/bitnami
-```
+by this point i'm pretty `git push`-pilled and i'm thinking i don't much like
+having this `helm` client software installed on my laptop.
+
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
@@ -258,12 +263,92 @@ spec:
url: https://charts.bitnami.com/bitnami
```
+{{< code-details summary="`release.yaml`" lang="yaml" details=`
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+ name: web
+ namespace: estradiol-cloud
+spec:
+ interval: 5m
+ chart:
+ spec:
+ chart: nginx
+ version: '15.12.2'
+ sourceRef:
+ kind: HelmRepository
+ name: bitnami
+ namespace: default
+ interval: 1m
+ values:
+ cloneStaticSiteFromGit:
+ enabled: true
+ repository: "https://code.estradiol.cloud/tamsin/estradiol.cloud.git"
+ branch: trunk
+ gitClone:
+ command:
+ - /bin/bash
+ - -ec
+ - |
+ [[ -f "/opt/bitnami/scripts/git/entrypoint.sh" ]] && source "/opt/bitnami/scripts/git/entrypoint.sh"
+ git clone {{ .Values.cloneStaticSiteFromGit.repository }} --no-checkout --branch {{ .Values.cloneStaticSiteFromGit.branch }} /tmp/app
+ [[ "$?" -eq 0 ]] && cd /tmp/app && git sparse-checkout init --cone && git sparse-checkout set public && git checkout && shopt -s dotglob && rm -rf /app/* && mv /tmp/app/* /app/
+ ingress:
+ enabled: true
+ hostname: estradiol.cloud
+ ingressClassName: nginx
+ tls: true
+ annotations: {
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ }
+ serverBlock: |-
+ server {
+ listen 8080;
+ root /app/public;
+ index index.html;
+ }
+ service:
+ type: ClusterIP
+`>}}
+![Scenario 1: Across columns](images/flux-seq.svg)
+
+## A Note About Software
+
+at this point i'm forced to admit there's still a lot of software involved in this.
+setting aside the stuff that provisions and scales my cluster nodes, i have:
+
+ - `nginx` (running from a stock image);
+ - `git` & `bash` (running from a stock image);
+ - a remote git server (i'm running `gitea`[^8], but github dot com is fine here);
+ - Kubernetes (oops!);
+ - `fluxcd`;
+ - especially `kustomize-controller` and `helm-controller`;
+ - `nginx-ingress` controller;
+ - the `bitnami/nginx` Helm chart;
+
+[^8]: because i'm running `gitea` in my cluster and i want to avoid a circular
+dependency for my `flux` source repository, i also depend on GitLab dot com.
+
+i get to maintain my two `bash` scripts for `git-clone` and `git-pull`, my
+NGINX config, and a couple of blobs of YAML.
+
+at least there are no webhooks.
+
+---
+
+_fin_
+
+
+[bitnami]: https://bitnami.com/
+[helm]: https://helm.sh
[hugo]: https://gohugo.io
[hugo-deploy]: https://gohugo.io/getting-started/usage/#deploy-your-site
[hugo-started]: https://gohugo.io/getting-started
[k8s]: https://kubernetes.io
[k8s-init]: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+[k8s-pods]: https://kubernetes.io/docs/concepts/workloads/pods/
+[k8s-port]: https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/
[k8s-pv]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
[risotto]: https://github.com/joeroe/risotto
-[worm]: https://www.mikecurato.com/worm-loves-worm
+[worm-love]: https://www.mikecurato.com/worm-loves-worm
diff --git a/content/posts/hugo-on-k8s-nginx/images/flux-seq.svg b/content/posts/hugo-on-k8s-nginx/images/flux-seq.svg
new file mode 100644
index 0000000..f8ca08a
--- /dev/null
+++ b/content/posts/hugo-on-k8s-nginx/images/flux-seq.svg
@@ -0,0 +1,122 @@
+
diff --git a/content/posts/hugo-on-k8s-nginx/images/hugo-github-pages.svg b/content/posts/hugo-on-k8s-nginx/images/hugo-github-pages.svg
new file mode 100644
index 0000000..f68d957
--- /dev/null
+++ b/content/posts/hugo-on-k8s-nginx/images/hugo-github-pages.svg
@@ -0,0 +1,875 @@
+deploy w/ GitHub pages & actions
+{{ .Title | markdownify }}
+
{{ .Params.description }}
{{ end }} + {{ if or (.Params.author) (.Params.date) }} ++ {{ if .Params.author }}By {{ .Params.author }}{{ if .Date }}, {{ end }}{{ end }} + {{ if .Date }}{{ .Date.Format "2006-01-02" }}{{ end }} +
+ {{ end }} + + {{ if and (.Params.toc) (.TableOfContents) }} +Making vanity projects easy since 2024
+ diff --git a/public/categories/tech/index.html b/public/categories/tech/index.html deleted file mode 100644 index 15e4f16..0000000 --- a/public/categories/tech/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - -Making vanity projects easy since 2024
-Making vanity projects easy since 2024
+ diff --git a/public/posts/hugo-on-k8s-nginx/index.html b/public/posts/hugo-on-k8s-nginx/index.html deleted file mode 100644 index af7d4c4..0000000 --- a/public/posts/hugo-on-k8s-nginx/index.html +++ /dev/null @@ -1,325 +0,0 @@ - - - -i decided to make a website. a static one. this one. with Hugo. this -is basically as a vanity project so i have some stuff to host in a -Kubernetes cluster i’m running. the k8s cluster is also as a vanity -project.
-because i don’t like software, i wanted a way to deploy my site that -doesn’t involve much of it. this post is about that.
-i built my site by following the straight-forward Getting Started -guide in the Hugo documentation.
-i did hugo new site estradiol.cloud
. and then cd estradiol.cloud; git init
. and
-then i picked a ridiculous theme “inspired by terminal ricing aesthetics”,
-installing it like git submodule add https://github.com/joeroe/risotto.git themes/risotto; echo "theme = 'risotto'" >> hugo.toml
. i appreciate the culinary naming choice.
at this point, my website is basically finished (i also changed the title in hugo.toml
).
-i probably won’t be putting anything on it, so there’s no point fiddling with other
-details.
about deployment, the guide’s Basic Usage page has this to offer:
---Most of our users deploy their sites using a CI/CD workflow, where a push1 - -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.
--
-- The Git repository contains the entire project directory, typically excluding the -public directory because the site is built after the push.
-
importantly, you can’t make a post about deploying this way. everyone deploys -this way. if i deploy this way, this site will have no content.
-it also involves some system somewhere that can run Hugo to build the site and push
-it to some remote system where my cluster can reach the public/
output. i definitely
-already need Hugo installed on my workstation if i’m going to post anything here
-(unlikely), so now i’m running Hugo in two places. there’s surely going to be
-other complex nonsense like webhooks involved.
and hang on. let’s look at this again:
----
-- The Git repository contains the entire project directory, typically excluding the -public directory because the site is built after the push.
-
you’re telling me i’m going to build a nice static site and not check the -actual content into version control? couldn’t be me.
-what if instead i pushed my site to a git repository exactly as i intend to serve it? -then i could shell into my web server, pull the site, and nifty-galifty! isn’t this -the way it has always been done?
-one problem is that i don’t have a web server, i have a container orchestration -system. there are several upsides to this (few of which are relevant for my project) -but it also means that somehow my content needs to end up in a container, and i -don’t want that container to need to retain state across restarts or replicas.
-i could run a little pipeline that builds a container wrapping my static site, -pushes it to a registry somewhere my deployments can pull it. all ready to go. -but now i’ve got software again: build stages and webhooks and to make matters -worse, now i’m hosting and versioning container images.
- -i don’t want any of this.
-instead, i’d like to deploy a popular stock container from a public registry and -deliver my content to it continuously.
-as a minimal version of this, i could do:
-apiVersion: v1
-kind: Pod
-metadata:
- name: nginx
- namespace: ec
- labels:
- app.kubernetes.io/instance: estradiol-cloud
- app.kubernetes.io/name: nginx
-spec:
- containers:
- - name: nginx
- image: nginx:1.25.4
- ports:
- - containerPort: 80
- volumeMounts:
- - mountPath: /app
- name: staticsite
- - name: git-pull
- image: bitnami/git
- command:
- - /bin/bash
- - -ec
- - |
- while true; do
- cd /app && git -c safe.directory=/app pull origin trunk
- sleep 60
- done
- volumeMounts:
- - mountPath: /app
- name: staticsite
- initContainers:
- - name: git-clone
- image: bitnami/git
- command:
- - /bin/bash
- - -ec
- - |
- git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/app
- cd /tmp/app && git sparse-checkout init --cone
- git sparse-checkout set public && git checkout
- rm -rf /app && mv /tmp/app /app
- volumeMounts:
- - mountPath: /app
- name: staticsite
- volumes:
- - emptyDir: {}
- name: staticsite
-
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- labels:
- app.kubernetes.io/instance: estradiol-cloud
- app.kubernetes.io/name: nginx
- name: nginx-server-block
- namespace: ec
-data:
- server-block.conf: |-
- server {
- listen 8080;
- root /app/public;
- index index.html;
- }
-
apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: HelmRepository
-metadata:
- name: bitnami
- namespace: default
-spec:
- url: https://charts.bitnami.com/bitnami
-
apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: HelmRepository
-metadata:
- name: bitnami
- namespace: default
-spec:
- url: https://charts.bitnami.com/bitnami
-
Making vanity projects easy since 2024
-- - 2024-02-28 -
- - - - -Making vanity projects easy since 2024
+ diff --git a/public/tags/index.html b/public/tags/index.html index cb7a52a..f77d76b 100644 --- a/public/tags/index.html +++ b/public/tags/index.html @@ -2,7 +2,7 @@Making vanity projects easy since 2024
+ diff --git a/public/tags/k8s/index.html b/public/tags/k8s/index.html deleted file mode 100644 index ae7c14e..0000000 --- a/public/tags/k8s/index.html +++ /dev/null @@ -1,98 +0,0 @@ - - - -Making vanity projects easy since 2024
-Making vanity projects easy since 2024
-Making vanity projects easy since 2024
-