🤖

kubernetes knative でサーバレス Vim

2024/05/21に公開

はじめに

半月ほど前に、ようやく自分の VPS 環境で動いているものすべてを kubernetes クラスタに移行しました。とても満足感が高くやって良かったと思っています。

ウェブサーバ、メールサーバ、Nostr のリレーサーバや Nostr/Bluesky/Twitter で動かしている各種 bot もすべて kubernetes です。

昨日は knative を導入したので、Go や Rust や Ruby や Python や、いろんな言語のクラウドネイティブアプリを簡単に実行できる様にしました。

knative 便利

残念ながら knative は helm パッケージとして提供されていません。ArtifactHub でそれっぽい物が公開されていますが、ほぼ手作業と変わりません。
おおよそ以下の手順でインストールできます。knative ではネットワークレイヤとして以下の3つが用意されています。

  • net-kourier
  • net-contour
  • net-istio

よく分かってない場合は kourier を入れておくと良いと言われたので 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

ここまで実行するとおおよそ実行環境が起動します。

DomainMapping

以下を設定してアプリが起動したらサブドメインからアクセスできる様に。

apiVersion: networking.internal.knative.dev/v1alpha1
kind: ClusterDomainClaim
metadata:
  name: example.com # ドメイン名(サブドメイン抜き)
spec:
  namespace: default # アプリを入れるネームスペース
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: ""

さっそく Go でアプリを起動してみる

適当なコードを用意します。

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))
}

Go の場合は ko を使うとめちゃめちゃ便利なので入れておくといいです。

環境変数 KO_DOCKER_REPO を設定したあと以下を実行。

kn service create --force helloworld-go --image=$(ko build .) --env TARGET="Go Sample v1"

ghcr のプライベートレジストリから pull したい場合はクレデンシャル(GitHub PAT)をサービスアカウントに登録して以下の様に実行。

kn service create --force helloworld-go --image=$(ko build .) --env TARGET="Go Sample v1" --service-account ghcr

無事、サブドメインからアクセスできる様になります。

https://x.com/mattn_jp/status/1792580782810186079

当然ですがサーバレスなので、しばらく放置しておくと勝手に眠ってくれます。
かっこいい!

サーバレス Vim

こうなれば Vim でサーバレスしたいですよね。わかります。ただ残念ながら Vim はウェブサーバになる為の機能を持っていません。そこで 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

これをランタイムとして Vim script を CGI として起動できる様にしました。

#!/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()

Dockerfile を書いてビルドし、適当な所に push します。

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"

後は apache のポートを指定してサーバレスアプリを作れば OK

kn service create --port 80 --force helloworld-vim --image=$IMAGE --env TARGET="Vim Sample v1"

やった!

おわりに

Vim はサーバレスアプリだったんだよ。
ΩΩΩ<な、なんだってー!?

Discussion