Kubernetes の nginx Ingress でパスを書き換えて転送したい

Kubernetes の Ingress が受け取ったパスの一部を加工して、後ろにいる Service (Pod) へ転送したくなった。どういうことかというと、

といった URL で Ingress がリクエストを受け取った時、後ろに用意した my-app-1 Service に対しては

といったように、間の /my-app-1/ 部分を除去してやりたいのだ。

しかし、通常 Ingress の path 指定をそのまま書いてしまうと、Pod に対しても

というように、/my-app-1/ 部分が付いたままリクエストが転送されてしまい、正しいパスにならずに 404 エラーになってしまったりする。

そこで今回は、rewrite-target 機能と use-regex 機能を使って、このパス置換 (パス・リライト) をやってやろうと思う。

いきなりコード

いきなりだが、こんな感じでコードを書けば良い。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  labels:
    name: my-ingress
  annotations:
    # 正規表現を使う
    nginx.ingress.kubernetes.io/use-regex: "true"
    # 「/」直後から、正規表現のキャプチャしたグループでパスを指定する
    nginx.ingress.kubernetes.io/rewrite-target: /$1$2
spec:
  tls:
    - hosts:
      - example.com
  rules:
    - host: example.com
      http:
        paths:
          # 前述のような `/my-app-1/index.html` などのリクエストを処理する
          - path: /my-app-1/(.*)
            backend:
              serviceName: my-app-1-service
              servicePort: 3000
          
          # 別の Service 向けの転送処理 : コチラはパスを書き換えず `http://my-app-2:8000/my-app-2/index.html` とそのまま転送する
          - path: /(my-app-2)(\/.*)
            backend:
              serviceName: my-app-2-service
              servicePort: 8000
          
          # 最後にフォールバック的にルートパスへのリダイレクト
          - path: /
            backend:
              serviceName: root-app
              servicePort: 8080
          - path: /(.*)
            backend:
              serviceName: root-app
              servicePort: 8080

重要なのは annotations に書いた2つの機能と、paths.path の中の正規表現キャプチャだ。

アノテーションで use-regexrewrite-target を指定していて、rewrite-target 部分で、/ 以降の文字列をどのように変換するかというルールを書いている。正規表現のキャプチャした文字列を $1$2 と指定して、それを渡すようにしている。

my-app-1path 指定を見ると、/my-app-1/(.*) と書いている。この場合、キャプチャしているのは $1 のみで、$2 に相当する文字列はないので空文字になる。つまり my-app-1-service に転送されるパスは /$1 の文字列になり、間に書かれていた /my-app-1/ が除去できるワケだ。

一方、my-app-2path 指定は、こうしたパスの除去を行わない例。path: /(my-app-2)(\/.*) と書いていて、$1my-app-2$2/.* となっているので、それをそのままくっつけて my-app-2-service に転送されるというワケ。

今回は $1$2 を指定したが、ココはお好みで。以下のように $2$3 で処理するような例もある。

metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2$3
spec:
  rules:
    - path: /()(path1)(.*)
      backend:
        serviceName: webapp1
    - path: /(path2)(.*)
      backend:
        serviceName: webapp2

正規表現を扱わないといけなくなってトリッキーだが、とりあえずこんな感じで対応できそう。