From 700a8dbdf69e5f1852f9710d321af471f5d1e319 Mon Sep 17 00:00:00 2001 From: tamsin woo Date: Mon, 4 Mar 2024 10:58:01 -0800 Subject: [PATCH] drop duplicate post --- content/posts/hugo-on-k8s-nginx.md | 354 ----------------------------- 1 file changed, 354 deletions(-) delete mode 100644 content/posts/hugo-on-k8s-nginx.md diff --git a/content/posts/hugo-on-k8s-nginx.md b/content/posts/hugo-on-k8s-nginx.md deleted file mode 100644 index 79e17a2..0000000 --- a/content/posts/hugo-on-k8s-nginx.md +++ /dev/null @@ -1,354 +0,0 @@ -+++ -title = 'Hugo on Kubernetes & NGINX' -date = 2024-02-28T15:35:46-08:00 -draft = true -series = ['wtf'] -categories = ['Tutorial'] -tags = ['meta', 'k8s', 'flux', 'hugo'] -toc = true -+++ - -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. - -## Getting Started - -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`.[^1] - -[^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: - -> Most of our users deploy their sites using a CI/CD workflow, where a push{{< sup "1" >}} -> 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. -> -> 1. 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 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) - ----- - -and hang on. let's look at this again: - -> 1. 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. - -## Getting Static - -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]? - - -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 -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 just want to put some HTML and static assets behind a -web server. - ---- - -instead, i'd like to deploy a popular container image from a public registry -and deliver my content to it continuously. - -a minimal setup to achieve this might look like: - - - 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 - 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: /www - name: www - - mountPath: /etc/nginx/conf.d - name: nginx-server-block - - name: git-pull - image: bitnami/git - command: - - /bin/bash - - -ec - - | - while true; do - cd /www && git -c safe.directory=/www pull origin trunk - sleep 60 - done - volumeMounts: - - mountPath: /www - name: www - initContainers: - - name: git-clone - image: bitnami/git - command: - - /bin/bash - - -c - - | - 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: /www - name: www - volumes: - - name: www - emptyDir: {} - - name: nginx-server-block - configMap: - name: nginx-server-block -` >}} - ---- - -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 - -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 -kind: HelmRepository -metadata: - name: bitnami - namespace: default -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-love]: https://www.mikecurato.com/worm-loves-worm