2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kubernetes: Service の静的ノードポート用レンジを分割する (ServiceNodePortStaticSubrange)

Last updated at Posted at 2023-09-12

Service の静的ノードポート用のレンジを分割する ServiceNodePortStaticSubrange という機能が、Kubernetes v1.28 からデフォルトで有効になりました。これは、Service の動的なノードポートの割当と、静的なノードポートの衝突リスクを減らすための機能です。この記事では Kubernetes v1.28.1 時点で、この機能について調べた内容を記載しています。

まとめ

kube-apiserver の --service-node-port-range で指定される全体のポートレンジが 2 分割され、ポートの値が小さい方は静的割り当て用の優先レンジとなりました。静的なノードポートの指定する場合は、この優先レンジを使うと衝突のリスクを減らせます。

例えばポートレンジのデフォルト値 30000-32767 の場合は、30000-30085 が静的割当の優先レンジとなっています。静的なノードポートを使う場合は、ポートレンジの小さい値(この例だと 30000)から使っていくと良いでしょう。

背景

Service のノードポートは、動的・静的どちらの割り当ても可能です。動的・静的どちらの割り当ても、API サーバーが管理するノードポートのレンジから選択されます。しかし動的なノードポートの割当はこのレンジ内からランダムに選択されるため、静的なノードポートと衝突する可能性がありました。

例えば以下のように Service に type: NodePort を指定し .spec.ports[].nodePort を指定しない場合は、動的にノードポートが割り当てられます。

動的なノードポートの Service
apiVersion: v1
kind: Service
metadata:
  name: nodeport-dynamic
spec:
  type: NodePort
  selector:
    run: nginx
  ports:
  - port: 80

今回はノードポートに 30296 が割り当てられました。

$ kubectl get svc nodeport-dynamic
NAME               TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nodeport-dynamic   NodePort   10.97.31.191   <none>        80:30296/TCP   69s
                                                              ^^^^^

一方、.spec.ports[].nodePort を指定することで静的にノードポートを指定できます。ここで指定したポートが既に動的なポートで使われていた場合に衝突が起こります。

静的なノードポートの Service
apiVersion: v1
kind: Service
metadata:
  name: nodeport-static
spec:
  type: NodePort
  selector:
    run: nginx
  ports:
  - port: 80
    # 静的にポートを指定できる
    nodePort: 30296

この例のように、静的なノードポートの指定が衝突した場合、以下のようなエラーで作成に失敗します。今回の機能が実装されるまでは、どのポートを指定しても常にこのリスクがありました。

The Service "nodeport-static" is invalid: spec.ports[0].nodePort: Invalid value: 30296: provided port is already allocated

この機能の以前に、Service の IP アドレスの衝突を避ける ServiceIPStaticSubrange という機能が Kubernetes v1.26 からデフォルトで有効となっています。今回の ServiceNodePortStaticSubrange という機能はそれのノードポート版と言えます。

分割されるポートのレンジ

この衝突の問題を解決するのが、ServiceNodePortStaticSubrange という機能です。

image.png

ノードポートで利用できる動的・静的を含めた全体のレンジは kube-apiserver の --service-node-port-range で指定します。デフォルト値は 30000 〜 32767 となっています。この機能では、この全体のレンジを2つに分割し、ポートの値が小さい方を静的割り当て用の優先レンジ、値が大きい方を動的割り当て用の優先レンジとして分割して管理します。

例えばデフォルト値の 30000 〜 32767 の場合は以下のように分割されます。

ポート範囲 個数 用途
30000-30085 86 静的割り当て優先レンジ
30086-32767 2682 動的割り当て優先レンジ

このレンジはあくまで優先して使われるレンジであり、動的割り当て用レンジが枯渇した場合、静的割り当て用レンジからも動的に割り当てられる点にご注意ください。

静的割り当て用レンジのサイズ

静的割り当て用の優先レンジは、--service-node-port-range のポートの個数(式のnodeport-size)に応じて 0〜128 個の範囲で分割されます。この静的用レンジの大きさは、下記の計算式求められます。ただしポートの個数が 16 以下の場合は 0 となります。

min(max(16, nodeport-size / 32), 128)

以下はポートレンジごとの例です、カッコ内はポートの個数を示しています。

--service-node-port-range 静的割り当て優先レンジ 動的割り当て優先レンジ
30000-30015 (16) 無し 30000-30015
30000-30127 (128) 30000-30015 (16) 30016-30127 (112)
30000-32767 (2768) 30000-30085 (86) 30086-32767 (2682)
30000-34095 (4906) 30000-30127 (128) 30128-34095 (3968)

この計算式と例は Kubernetes 公式ブログの記事 Kubernetes 1.27: Avoid Collisions Assigning Ports to NodePort Services で紹介されています。詳細はそちらをご覧ください。

デモ

Kubernetes v1.28.1 の環境(minikube)で、動的用レンジの個数分 NodePort を使った Service を作り、ポートの割当を確認しました。以下の gif は割当の状況を早送りしたものです。静的用のレンジ (30000-30085) には割当が行われていないことが確認できます。. は空き、x は割り当て済みを表しています。

node-port-static-subrange.gif

以下はデモ全体の動画です。動的用のレンジを使い切ると静的用のレンジが使われます。この様子はデモの 2:09 から確認できます。

ServiceNodePortStaticSubrange Demo

なお、全体のレンジを使い切ると、下記のように allocate a nodePort: range is full というエラーが返るようになります。

Error from server (InternalError): error when creating "svc.yaml": Internal error occurred: failed to 
allocate a nodePort: range is full    

参考: デモのスクリプト

Service の作成につかったスクリプトは以下です。高速化のため 100 個単位でまとめて送信しています。

:pencil: metadata.generateName を使うと、オブジェクトの名前生成を Kubernetes に任せることができます。

#!/bin/bash
n=${1:-0}

svc=$(mktemp)
cat >"$svc" <<EOF
apiVersion: v1
kind: Service
metadata:
  generateName: nodeport-
spec:
  type: NodePort
  selector:
    run: nginx
  ports:
  - port: 80
---
EOF

for ((i = 0; i < n / 100; i++)); do
  for ((j = 0; j < 100; j++)); do
    cat "$svc"
  done | kubectl create -f -
done

for ((i = 0; i < (n % 100); i++)); do
  kubectl create -f "$svc"
done

ノードポートの割当表示は以下のように kubectl で取得したノードポート一覧を ruby のスクリプトで整形し、それを watch で 1 秒おきに表示しています。

watch --color -n 1 "kubectl get svc -o json | jq '.items[].spec.ports[] | select(.nodePort != null) | .nodePort'| ./plot.rb"
#!/usr/bin/env ruby
require 'set'

allocated = Set.new
while line = gets
  allocated.add(line.chomp)
end

# header
print '      | '
10.times do |n|
  printf '%-10d', n * 10
end
puts
print '------+ '
puts '-' * 100

(30_000..32_767).each do |n|
  print "#{n} | " if (n % 100).zero?
  if allocated.include?(n.to_s)
    print "\e[1m\e[31mx\e[0m"
  else
    print "\e[32m.\e[0m"
  end
  puts if ((n + 1) % 100).zero?
end
puts

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?