iTranslated by AI
Serverless Vim with Kubernetes and Knative
Introduction
About half a month ago, I finally migrated everything running in my VPS environment to a Kubernetes cluster. I feel very satisfied and glad I did it.
Web servers, mail servers, Nostr relay servers, and various bots running on Nostr/Bluesky/Twitter are all on Kubernetes.
Yesterday, I introduced knative, so I made it possible to easily run cloud-native apps in various languages like Go, Rust, Ruby, and Python.
knative is useful
Unfortunately, knative is not provided as a helm package. Something that looks like it is published on ArtifactHub, but it is almost the same as manual work.
You can install it roughly with the following steps. In knative, the following three networking layers are available:
- net-kourier
- net-contour
- net-istio
I was told that if you are not sure, it is good to use kourier, so I went with kourier.
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.14.0/serving-default-domain.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.14.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.14.0/serving-core.yaml
kubectl patch configmap/config-network \
--namespace knative-serving \
--type merge \
--patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.14.0/kourier.yaml
Once you have executed up to this point, the execution environment will mostly be up and running.
DomainMapping
Configure the following so that you can access the app from a subdomain once it is started.
apiVersion: networking.internal.knative.dev/v1alpha1
kind: ClusterDomainClaim
metadata:
name: example.com # Domain name (without subdomain)
spec:
namespace: default # Namespace to put the app in
apiVersion: v1
kind: ConfigMap
metadata:
name: config-network
namespace: knative-serving
data:
ingress.class: kourier.ingress.networking.knative.dev
autocreate-cluster-domain-claims: "true"
#domain-template: '{{.Name}}.{{.Domain}}'
http-protocol: "Enabled"
external-domain-tls: "Disabled"
cluster-local-tls: "Disabled"
auto-tls: "Enabled"
apiVersion: v1
kind: ConfigMap
metadata:
name: config-domain
namespace: knative-serving
data:
example.com: ""
Let's try starting an app with Go right away
Prepare some code.
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
log.Print("helloworld: received a request")
target := os.Getenv("TARGET")
if target == "" {
target = "World"
}
fmt.Fprintf(w, "Hello %s!\n", target)
}
func main() {
log.Print("helloworld: starting server...")
http.HandleFunc("/", handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("helloworld: listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
For Go, using ko is incredibly convenient, so it's a good idea to install it.
After setting the environment variable KO_DOCKER_REPO, run the following:
kn service create --force helloworld-go --image=$(ko build .) --env TARGET="Go Sample v1"
If you want to pull from a private GHCR registry, register your credentials (GitHub PAT) in a service account and run it as follows:
kn service create --force helloworld-go --image=$(ko build .) --env TARGET="Go Sample v1" --service-account ghcr
You will now be able to access it via the subdomain successfully.
Naturally, since it's serverless, it will automatically go to sleep if you leave it for a while.
How cool!
Serverless Vim
Now that we've come this far, you'll probably want to do serverless with Vim. I get it. Unfortunately, Vim doesn't have the functionality to act as a web server on its own. So, I'll enlist the help of Apache.
#!/bin/bash
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT INT TERM HUP
/usr/bin/vim -nNes -c "let &verbose=1" -c "let &viminfo=''" -c "redir!>$TMPFILE" -c "source $1" -c "redir END" -c "qall!"
tail -n +1 $TMPFILE
I've made it possible to run Vim script as a CGI by using this as a runtime.
#!/usr/bin/vim-cgi
echon "Content-Type: text/plain\r\n\r\n"
func s:main() abort
for i in range(1,100)
echo [[i,"Buzz"],["Fizz","FizzBuzz"]][i%3<1][i%5<1]
endfor
endfunc
call s:main()
Write a Dockerfile, build it, and push it to a suitable location.
FROM httpd:2.4.46
RUN apt update && apt install -y vim && apt clean
COPY vim-cgi /usr/bin/vim-cgi
RUN chmod +x /usr/bin/vim-cgi
COPY hello.vim /usr/local/apache2/cgi-bin/hello.vim
RUN chmod +x /usr/local/apache2/cgi-bin/hello.vim
CMD httpd-foreground -c "LoadModule cgid_module modules/mod_cgid.so"
After that, just create the serverless app by specifying the Apache port.
kn service create --port 80 --force helloworld-vim --image=$IMAGE --env TARGET="Vim Sample v1"

Success!
Conclusion
Vim was a serverless app after all.
ΩΩΩ< WHAAAT!?
Discussion