Lately, I have been migrating the public services I have running on a DigitalOcean Droplet to a cluster of Raspberry Pis running K3s at home. To simplify the development workflow for my projects I have converted many of them to OpenFaaS functions that run on the cluster. As many of these projects are exposed as subdomains, I have deployed OpenFaaS with its IngressOperator which helps me manage the mapping from nodeinfo.example.com to example.com/function/nodeinfo through the use of the FunctionIngress CRD.

Using the latest stable version of K3s (v1.20.6+k3s1) I ran into an issue where paths on a subdomain would be incorrectly mapped to functions. For example, nodeinfo.example.com/test would be mapped to example.com/function/nodeinfotest, effectively stripping a slash. This is a known issue with the rewrite-target annotation that the OpenFaaS IngressOperator uses to map function paths.

Unfortunately, K3s v1.20.6+k3s1 comes bundled with Traefik v1.7.19, and the fix for this issue was not released until v1.7.21. Normally I would just upgrade the bundled Helm chart to the required version, but Rancher had just released K3s v1.21+k3s1 which came with the much-anticipated upgrade to Traefik v2.4.8, so I opted to upgrade my cluster.

There are significant changes in Traefik v1 to v2, the most relevant of which is the replacement of ingress annotations in Kubernetes with CRDs. Currently, the OpenFaaS IngressOperator is not compatible with Traefik v2, so to recreate the functionality of the rewrite-target annotation, we would need to define a new ReplacePathRegex Middleware. Using the nodeinfo example from earlier, we will create the following resource.

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: nodeinfo-replacepathregex
  namespace: openfaas
spec:
  replacePathRegex:
    regex: ^/(.*)
    replacement: /function/nodeinfo/$1

To use this Middleware, we will add a custom annotation to our normal FunctionIngress definition.

apiVersion: openfaas.com/v1alpha2
kind: FunctionIngress
metadata:
  name: nodeinfo
  namespace: openfaas
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: openfaas-nodeinfo-replacepathregex@kubernetescrd
spec:
  domain: "nodeinfo.example.com"
  function: "nodeinfo"
  ingressType: "traefik"

Now, assuming you have your DNS configured correctly, paths on your subdomains will be properly mapped to the function defined by your FunctionIngress. Going forward, I hope that the OpenFaaS IngressOperator implements Traefik v2 support or, though unlikely, Traefik reimplements inline middleware definitions for Kubernetes similar to what they have for other platforms with labels.