前回の記事(ingress-nginxでのCanary機能と根本的課題)では、ingress-nginxのメンテナーが『根本的な課題』と語るCanary機能の問題点について説明した。この記事では、どのような解決方針がありそうか検討したい。
根本原因は何か?
現在のingress-nginxのリクエストの処理フローとしては、
- nginx.confの定義に従って(Hostname+Pathに応じて)、Backendを決定。
- LuaモジュールのBalancer処理で、Backendを変更(Canary機能)
と、2段階でBackendの決定を行っているおり、ここに諸々の問題の原因が関連している。
根本原因①:ingress.yamlでの定義情報と内部的な紐付け情報の不整合
IngressのyamlではHostname
(+Path
)に対して設定されるCanaryの定義情報が、ingress-nginxの内部ではBackend
(≒Service
+Port
)に対して紐付いてしまっている。そのため、先程の2のBalancerでのBackend
の切り替え時にHostname
+Path
の情報を一切考慮しないため、特定のHostnameや特定のPathに対してCanaryを指定した場合に、意図しない動きをしてしまうことになる。
根本原因②:Backend
切替時の考慮漏れ
Canary機能によりBackend
が切り替わった際に、合わせて変更しなければいけない情報(ログに出力する値など)が考慮されていない。別の捉え方をすると、先程の2の処理でBackend
が切り替わった際にこれらを考慮しなければいけない作りになっている。
根本原因①の解決案
定義情報と内部実装がずれてしまっているのが問題なので、それを合わせてみる。
Canaryに関するデータとしては以下の2つがあるので、これらのデータを現在関連が張られているBackend
から、Location
に移してみる。
-
AlternativeBackends
- ルーティング先の
Backend
- ルーティング先の
-
TrafficShapingPolicy
- Canaryのルール
Luaモジュールへのこれらの情報の連携はどうなるだろうか。
現状の実装では、
-
Configuration.Backends[]
→ LuaのConfigurationモジュールを経由して、Luaモジュールに連携 -
Location.***
→nginx.conf
にset
で値をセットして、ngx.var.***
としてLuaモジュールから参照されている
ので、
Location.CanaryBackend(string)
やLocation.TrafficShapingPolicy
、は、ngx.var.***
として連携すれば問題なさそうだ。
これで、特定のHostnameや特定のPathに対していCanaryを設定した場合にも問題なくCanary機能を提供できそうだ。
根本原因②の解決案
Canary機能によるBackend
の切り替えの実装をLuaモジュールではなく、nginx.confのみで実現できれば、根本解決になりそうだが、実際問題かなり難しそうだ。。
そもそも、LuaモジュールでのBalancer処理の際に接続先Backend
を決定しているのは、ヘッダーやクッキーの値によって接続先を切り替えるといった高機能なCanary機能を提供するためである。
Issueの議論の中では、nginx自体の機能であるsplit_clientsを利用して、Luaモジュールでの処理ではなく、nginxそのものの処理の中(先程の1の処理の中)でCanary機能を実装できないかという提案も出されていたが、前述した高機能なCanary機能をsplit_clientsのみで提供するのは難しそうなのだ。。
なので、ここは愚直に、CanaryでBackendが切り替わった際に変更すべき値を愚直にLuaモジュール内で切り替えれるようにしていく方針がよさげ。
ということですでにIssueで上がっているログ情報の不整合について、修正案を考えてみたい。
CanaryでのBackend切り替えに伴うログ情報の修正
考慮すべきは、
ingress = ngx.var.ingress_name or "-",
service = ngx.var.service_name or "-",
の部分。
CanaryのBackendが切り替えられた場合は、
ingress = ngx.var.canary_ingress_name,
service = backend.service.metadata.name,
みたいな感じにする。
最終的にはこんな感じ。
location {{ $path }} {
{{ $ing := (getIngressInformation $location.Ingress $server.Hostname $location.Path) }}
set $ingress_name {{ $ing.Rule | quote }};
set $canary_ingress_name {{ $location.CanaryIngressName | quote }}; // <- Added
最後に
半ばメモみたいな感じで解決案を考えてみたが、今度はこの内容でKEP(Kubernetes Enhancement Proposals) を書いてみようと思う。