K8s NodePort 割当失敗時の挙動について

2 min read読了の目安(約2600字

問題点

NodePort service を作成する際、 NodePort の割当に失敗する場合があります。具体的には、下記コマンドで割当失敗をシミュレートすることができます。
※悪意のあるコマンドなので実行は自己責任でお願いします。

$ kubectl create service nodeport temp-svc --tcp=`python3 <<EOF
print("1", end="")
for i in range(2, 1026):
  print("," + str(i), end="")
EOF
`

このコマンドは 1026 個の NodePort を持つ service を作成します。 NodePort を open するためにファイルディスクリプタを一つ消費しますが、ファイルディスクリプタの上限はデフォルトで1024個であるため、いくつかの NodePort の割当に失敗します。

しかし、ユーザーは NodePort の割当失敗に気づきません。なぜならコマンドを実行するとservice/temp-svc createdが返って来ますし、kubectl getを使用するとポートが正常に割り当てられたように表示されるからです。

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)
temp-svc     NodePort    10.0.0.139   <none>        1:31335/TCP,2:32367/TCP,3:30263/TCP,(omitted),1023:31821/TCP,1024:32475/TCP,1025:30311/TCP   12s

さらに、kubectl describeを見てもエラーが表示されません。

解決策

実はkube-proxyのログを見ると下記エラーが表示されているため、問題に気がつくことはできます。ただ、kube-proxyは Node ごとに存在するので、全ての Node を見て回るのは時間的に難しい場合が多いでしょう。

E0327 08:50:10.216571  660960 proxier.go:1286] "can't open port, skipping this nodePort" err="listen tcp4 :30641: socket: too many open files" port="\"nodePort for default/temp-svc:744\" (:30641/tcp4)"
E0327 08:50:10.216611  660960 proxier.go:1286] "can't open port, skipping this nodePort" err="listen tcp4 :30827: socket: too many open files" port="\"nodePort for default/temp-svc:857\" (:30827/tcp4)"
...
E0327 08:50:10.217119  660960 proxier.go:1286] "can't open port, skipping this nodePort" err="listen tcp4 :32484: socket: too many open files" port="\"nodePort for default/temp-svc:805\" (:32484/tcp4)"
E0327 08:50:10.217293  660960 proxier.go:1612] "Failed to execute iptables-restore" err="pipe2: too many open files ()"
I0327 08:50:10.217341  660960 proxier.go:1615] "Closing local ports after iptables-restore failure"

そこで、 kubernetes event にエラーを表示するよう修正しました。今後はこのように表示されます。

$ kubectl get event
LAST SEEN   TYPE      REASON                                            OBJECT           MESSAGE
4s          Warning   listen tcp4 :30486: socket: too many open files   node/127.0.0.1   can't open port "nodePort for default/temp-svc:503" (:30486/tcp4), skipping it

下記パッチで修正したので、1.22から使用できる予定です。

https://github.com/kubernetes/kubernetes/commit/3266136c1d964f992fea00c5c7362b88d86e401c

補足

NodePort service を作るだけなのに、なぜポートを open する必要があるのだろうと疑問を持った方も居るかも知れません。

実際、 service を作成した際は iptables/ipvs を操作するだけです。ただ、そのポートが他に使われないようにブロックするために open する必要があるのです。

これについては、ブロックが目的ならば、 open せずに eBPF を使えば良いのではという議論もあります。これが実現すれば、ここで取り上げた問題も将来的に解決するかも知れません。興味のある方はこちらの issue を参照して下さい。

https://github.com/kubernetes/kubernetes/issues/100643