ingress-nginxはどうやって動いているのか(1)、ingress-nginxはどうやって動いているのか(2)と続けて、ingress-nginxがどのような仕組みで動いているのかを見てきた。
この記事では、ingress-nginxが提供するCanary機能の概要と、メンテナーの方が現時点(v0.26.1)で『Fundamental Issue(根本的な課題)』と言っているある課題について見ていく。
Canary機能
公式ドキュメントでは、
In some cases, you may want to "canary" a new set of changes by sending a small number of requests to a different service than the production service. The canary annotation enables the Ingress spec to act as an alternative service for requests to route to depending on the rules applied.
とあるように、Canary機能用の独自AnnotationをIngress用のyamlファイルに記述しておくことで、クライアントから来たリクエストの数%を別のServiceに流したりすることができる。
現時点でサポートしているのは、以下の4つのパターン。
Name | Description |
---|---|
canary-weigh | %を指定し、特定の割合でルーティング先を変えることができる。 |
canary-by-cookie | 特定のCookieの名前を設定しておくことで、そのCookieの値がAlways の時にルーティング先を変えることができる。 |
canary-by-header | 特定のHeaderの名前を設定しておくことで、そのHeaderの値がAlways の時にルーティング先を変えることができる。 |
canary-by-header-value |
canary-by-header で指定したHeaderの値が、ここで指定した値の場合にルーティング先を変えることができる。 |
Canaryの例
kind: Ingress
spec:
rules:
- host: foobar.com
http:
paths:
- path: /
backend:
serviceName: foo-service
servicePort: 1234
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: foobar.com
http:
paths:
- path: /
backend:
serviceName: bar-service
servicePort: 1234
この2つのyamlを適用すると、foobar.com
へのアクセスは90%がfoo-service
にルーティングされて、10%がbar-service
にルーティングされる。
実際にはIngressリソース以外に、ingress-nginxをIngress ControllerとしてデプロイするためのDeploymentやServiceのyamlが必要だが、そちらについては以下のサイトなどを参照してほしい。
- https://github.com/ContainerSolutions/k8s-deployment-strategies/tree/master/canary/nginx-ingress
- https://www.elvinefendi.com/2018/11/25/canary-deployment-with-ingress-nginx.html
内部的な処理フローとコードリーディング
処理③:Canaryを参照。
Canary機能の根本的な課題
ingress-nginxのメンテナーの1人であるElvinEfendiが、Canary機能の実装上の根本的な課題として、以下のようなコメントをしている。
I think fundamental issue is that canary is configured based on host and path combination but when it is actually applied to a request, we use $proxy_upstream_name, which is based on namespace, service and port. This is the problem.
どういうことかというのを、具体的な例で見ていく。
kind: Ingress
spec:
rules:
- host: foobar.com
http:
paths:
- path: /
backend:
serviceName: foo-service
servicePort: 1234
- path: /foobar
backend:
serviceName: foo-service
servicePort: 1234
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: foobar.com
http:
paths:
- path: /foobar
backend:
serviceName: bar-service
servicePort: 1234
このような2つのyamlで、foobar.com/foobar
に対して、Canaryを設定する。
これがこの部分のことである。
canary is configured based on host and path combination
しかし、内部的な実装としては、ingress-nginxはどうやって動いているのか(2)#オブジェクトの関連図 にある通り、Backend
(namespace, service, portで一意)にAlternativeBackends
として、Canaryとして追加されるルーティング先(Backend
)が格納される。
これが、
we use $proxy_upstream_name, which is based on namespace, service and port.
の部分のことを示している。
この、設定の仕方と内部実装の違いが、いくつかの問題を生じさせている。
具体的な課題①:複数Path指定のIngressでCanaryがうまく動かない
Issue#4028: Canary ingress with multiple paths behaves improperly
実は先程例であげた2つのyamlを適用した場合、想定どおりにCanary機能が動作しない。
パス | 期待する挙動 | 実際の挙動 |
---|---|---|
/ | 100%: foo-service | 90%: foo-service, 10%: bar-service |
/foobar | 90%: foo-service, 10%: bar-service | 90%: foo-service, 10%: bar-service |
foo-service
のBackend
のAlternativeBackends
に、bar-service
のBackend
が設定されており、/foobar
というPath情報はBackend
では考慮されないため、どのパスへのリクエストももれなくCanaryの対象となってしまっている。
これが、ElvinEfendiが述べている"根本的な課題"のことである。
処理の流れ的にはこんな感じ(なはず)
-
/
へのリクエストをnginxが受け取る -
nginx.conf
のServer>Locationの設定に応じて、メインのBackend
が選択される(今回の場合はfoo-service
) - 実際のルーティング処理を行うLuaのBalancerモジュールでは、
foo-service
のBackend
に設定されているAlternativeBackends
を見て、10%の確率でリクエストをbar-service
へルーティングする
具体的な課題②:Canary指定時のログの情報が正しくない
Issue#4368: Incorrect logs and ngx variables when using canary Ingresses
先程例であげた2つのyamlを適用した場合、Canaryでbar-service
にリクエストがルーティングされた場合にも、ログにはfoo-service
の情報が記録されてしまうというもの。
これは、ログに記録する情報はnginx.confで設定される値を利用していることに起因している。Canary機能を利用しない場合はそれで問題ないが、Canary機能を利用する場合、LuaのBalancerモジュールでルーティング処理時に、ルーティング先のBackend
が変更される可能性がある。現在の実装ではその場合も、ログに書かれる情報の更新を行っていないため、正しくない情報がログに記録されることになる。
具体的な課題③:同一Serviceで複数Hostnameの場合のCanaryの挙動が正しくない
Issue#4667: Ingresses with different host point to same upstream cause confusion
別々のHostnameが同一のサービスへルーティングされる場合に片方のHostnameでCanaryを適用すると、もう片方のHostnameへのアクセスもCanaryの対象となってしまうというもの。
kind: Ingress
spec:
rules:
- host: foobar1.com
http:
paths:
- path: /
backend:
serviceName: foo-service
servicePort: 1234
- host: foobar2.com
http:
paths:
- path: /
backend:
serviceName: foo-service
servicePort: 1234
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: foobar1.com
http:
paths:
- path: /
backend:
serviceName: bar-service
servicePort: 1234
上記のyamlを適用した場合、foobar1.com
に対して設定したつもりのCanaryの設定が、foo-service
(のBackend
)へ適用されてしまうため、foobar2.com
へのアクセスも同様のCanaryの設定で動いてしまう。
これも具体的な課題①:複数Path指定のIngressでCanaryがうまく動かないと同様に、設定の仕方と内部実装の矛盾が根本原因となっている。
解決案
次回はこの根本的課題の解決案について検討してみたい。
→ ingress-nginxのCanary機能の根本的課題の解決案