【ingress-nginx】リダイレクトの設定例

# ingressの役割

- クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトで、負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供する。
- 一つのingress上に、複数のドメインの設定を行うことが可能。
  - ingress-nginxを使った場合は、バーチャルサーバ(バーチャルホスト)の機能を使って実現している。
      - 具体的には、`nginx.conf`に以下のような設定が適用される（以下の例では、`foo.example.com`と`bar.next-example.jp`ドメインをホストしている）。
```nginx.conf
http {
    :
    :
	## start server foo.example.com
	server {
		server_name foo.example.com ;

		# Custom code snippet configured for host foo.example.com
		location ~* "^/bar/(.*)" {
            #
			# some config comes here
            #
		}
    }
	## end server foo.example.com

	## start server bar.next-example.jp
	server {
		server_name bar.next-example.jp ;

		location ~* "^/(.*)" {

		}

		location ~* "^/" {

		}
	}
	## end server bar.next-example.jp
}
```

# 前提

- Ingressリソースが動作するためには、クラスターでIngressコントローラーが実行されている必要がある。
  - Ingressコントローラーの一つに[Ingress NGINX Controller](https://kubernetes.github.io/ingress-nginx/)がある。
    - 今回対象とするのは、このコントローラー上で設定されるIngressリソース。 
  - クラスター内に複数のIngressコントローラーが存在する場合にどのIngressコントローラーを使用するかを示すために、複数のcontrollerをデプロイした時は、適切な`spec.ingressClassName`フィールドを指定する必要がある（例：`ingressClassName: nginx`）。
    - ドキュメントでは、`ingress.class`のアノテーションとなっている箇所がある。`ingressClassName`フィールドはこのアノテーションを置き換えるものだが、完全に等価ではない。
      - 実運用上は、 `ingress.class`のアノテーションは使わず、`ingressClassName`フィールドを使えば良い認識（[`ingress.class`は、kubernetes v1.22+で非推奨となる](https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/)）。
      - 単一のIngressコントローラーのみが存在する場合も、`ingressClassName`フィールドを指定しておけば間違いない。


> 参照ドキュメント
> - https://kubernetes.io/ja/docs/concepts/services-networking/ingress-controllers/
> - https://kubernetes.io/ja/docs/concepts/services-networking/ingress/
> - https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/

- Ingressリソースの設定は、`nginx.conf`に反映される。そのため、nginxの設定が理解できていると、イメージしやすい。


# 設定例

## PATHに応じてリダイレクトを行う

単一のドメインにリダイレクトを行う場合は、[ドキュメント](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#permanent-redirect)に書かれている通り、`nginx.ingress.kubernetes.io/permanent-redirect: https://www.google.com`というアノテーションが使えます。

PATHに応じてリダイレクトを行う場合は、以下のいずれかを使う必要があります。
- [nginx.ingress.kubernetes.io/server-snippet](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#server-snippet)
  - `nginx.conf`のserverディレクティブに設定が追加されます。
  - server-snippet は1つのホストにつき1回しか設定できない。
- [nginx.ingress.kubernetes.io/configuration-snippet](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#configuration-snippet)
  - `nginx.conf`のlocationディレクティブに設定が追加されます。
  - 既存のlocationディレクティブがあった場合、その全てに、ここで設定したものがネストして追加される（以下、例）
```sh
		location ~* "^/foo/(.*)" {　// 既存の location directive

			location ~* "^/foo/(.*)" { // configuration-snippet を使って追加された location directive。本来は、既存の location directive に追加したいが、ネストして追加される。

			}
		}

		location ~* "^/bar/(.*)" {　// 既存の location directive

			location ~* "^/foo/(.*)" { // configuration-snippet を使って追加された location directive。ここに追加する意味は本来ないが、追加される。

			}
		}

		location ~* "^/" {　// 既存の location directive

			location ~* "^/foo/(.*)" { // configuration-snippet を使って追加された location directive。ここに追加する意味は本来ないが、追加される。

			}

		}
```

以下、`configuration-snippet`を使った例。

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-ingress-nginx
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite ^/$ https://example.com permanent;
      rewrite ^/en\/?$ https://example.com/en/foo permanent;
      rewrite ^/ja\/?$ https://example.com/ja/foo permanent;
      rewrite ^/(.*) https://another-example.com/$1 permanent;
    cert-manager.io/issuer: "sample-letsencrypt-issuer"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - sample.example.com
    secretName: sample-letsencrypt-cert
  rules:
  - host: sample.example.com
    http:
      paths:
      - path: /(.*)
        pathType: Prefix
        backend:
          service:
            name: sample-service
            port:
              number: 80
```

以下、`server-snippet`で、`location` + `return 301` を使った例だが、この設定ではSSL証明書が発行されなかった。
SSL証明書発行時には、発行要求を受け付けるために temporary の ingress が立ち上がるが、そこでうまくリクエストを処理できないのではないかと思われるが、詳細は不明。

```yaml
  annotations:
    :
    nginx.ingress.kubernetes.io/server-snippet: |
      location ~ ^/((foo|bar)\/?)?$ {
        return 301 https://redirect1-sample.example.com/$1;
      }
      location ~ ^/(.*) {
        return 301 https://redirect2-sample.example.com/$1;
      }

```

## cert-manager.io/v1 設定例

```yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: sample-letsencrypt-issuer
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: foo@example.com
    privateKeySecretRef:
      name: sample-acme-client-letsencrypt
    solvers:
    - http01:
        ingress:
          class: nginx
```

# 備考

ingress-nginxの設定をする上で、nginxの設定を理解しておくことは重要です。
パフォーマンスに関しては、以下の記事が参考になります。
https://www.getpagespeed.com/server-setup/nginx-locations-performance-impact-and-optimizations

- 可能な限り、正規表現ではなく、完全一致を使う。
- `rewrite`ではなく、`nested locations`を使う。
  - しかし、上記の例では、`location` + `return` では SSL証明書の発行ができなかったため、`rewrite` を使っている。  


# nginx.conf の設定を確認する

```shell
NAMESPACE=ingress-nginx
kubectl get pods --namespace=$NAMESPACE
// で pod を確認し、以下のコマンドで指定する。

kubectl exec --stdin --tty --namespace=$NAMESPACE ingress-nginx-controller-844c488f4b-85h6j -- cat /etc/nginx/nginx.conf

// or

kubectl exec -it -n $NAMESPACE ingress-nginx-controller-844c488f4b-85h6j -- cat /etc/nginx/nginx.conf
```

# ingress のログを見る

```shell
kubectl logs ingress-nginx-controller-5849c9f946-dqczh -n $NAMESPACE -f

```

# ingress の pod にログインする

```shell
kubectl exec -it -n $NAMESPACE ingress-nginx-controller-844c488f4b-85h6j -- /bin/bash
```

# その他、参考URL

- [ingress-nginx Installation Guide](https://github.com/kubernetes/ingress-nginx/blob/main/docs/deploy/index.md)
- https://medium.com/ww-engineering/kubernetes-nginx-ingress-traffic-redirect-using-annotations-demystified-b7de846fb43d
- https://blog.grasys.io/post/tsunoda/nginx-rewrite/
- https://stackoverflow.com/questions/58993607/passing-parameters-down-to-permanent-redirect-in-ingress-nginx
- https://docs.nginx.com/nginx/admin-guide/web-server/web-server/
- https://blog.cybozu.io/entry/2015/11/20/080000
- http://nginx.org/en/docs/http/converting_rewrite_rules.html
- https://heartbeats.jp/hbblog/2012/04/nginx05.html