K8s NodePort 割当失敗時の挙動について
問題点
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から使用できる予定です。
補足
NodePort service を作るだけなのに、なぜポートを open する必要があるのだろうと疑問を持った方も居るかも知れません。
実際、 service を作成した際は iptables/ipvs を操作するだけです。ただ、そのポートが他に使われないようにブロックするために open する必要があるのです。
これについては、ブロックが目的ならば、 open せずに eBPF を使えば良いのではという議論もあります。これが実現すれば、ここで取り上げた問題も将来的に解決するかも知れません。興味のある方はこちらの issue を参照して下さい。
Discussion