Kubernetes v1.33: EndpointsからEndpointSliceへの継続的な移行を進める
EndpointSlice (KEP-752)がv1.15でアルファとして導入され、v1.21でGAとなって以来、Endpoints APIはKubernetesの中でほぼ使われず、埃を被っています。 デュアルスタックネットワークやトラフィック分散など、Serviceの新機能はEndpointSlice APIでのみサポートされているため、全てのサービスプロキシ、Gateway API実装、及び同様のコントローラーはEndpointsからEndpointSliceへの移行を余儀なくされました。 現時点のEndpoints APIは、未だにEndpointsを使っているエンドユーザーのワークロードやスクリプトの互換性を維持するための存在に過ぎません。
Kubernetes 1.33以降、Endpoints APIは正式に非推奨となり、Endpointsリソースを読み書きするユーザーに対して、EndpointSliceを使用するようAPIサーバーから警告が返されるようになりました。
最終的には、「ServiceとPodに基づいてEndpointsオブジェクトを生成する Endpointsコントローラー がクラスター内で実行されている」という基準をKubernetes Conformanceから除外することがKEP-4974にて計画されています。 これの実現によって、現代的なほとんどのクラスターにおいて不要な作業を回避することができます。
Kubernetes非推奨ポリシーに従うと、Endpointsタイプ自体が完全に廃止されることはおそらく無いですが、Endpoints APIを使うワークロードやスクリプトを保有しているユーザーはEndpointSliceへの移行が推奨されます。
EndpointsからEndpointSliceへの移行に関する注意点
EndpointSliceを利用する
エンドユーザーにとって、Endpoints APIとEndpointSlice APIの最大の違いは、selector
を持つ全てのServiceが自身と同じ名前のEndpointsオブジェクトを必ず1つずつ持つのに対し、1つのServiceに紐づけられるEndpointSliceは複数存在する可能性がある、という点です。
$ kubectl get endpoints myservice
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME ENDPOINTS AGE
myservice 10.180.3.17:443 1h
$ kubectl get endpointslice -l kubernetes.io/service-name=myservice
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
myservice-7vzhx IPv4 443 10.180.3.17 21s
myservice-jcv8s IPv6 443 2001:db8:0123::5 21s
この場合、Serviceがデュアルスタックであるため、EndpointSliceがIPv4アドレス用とIPv6アドレス用の2つ存在します。 (Endpoints APIはデュアルスタックをサポートしていないため、Endpointsオブジェクトにはクラスターのプライマリアドレスファミリーのアドレスのみが表示されています。)
複数のEndpointSliceを持つ 可能性 は、複数のエンドポイントが存在するあらゆるServiceにありますが、代表的なケースが3つ存在します。
EndpointSliceは単一のIPファミリーのエンドポイントしか表現できないため、デュアルスタックServiceの場合、IPv4用とIPv6用のEndpointSliceがそれぞれ作成されます。
単一のEndpointSlice内のエンドポイントは、全て同じポートを対象とする必要があります。例えば、エンドポイントとなるPodをロールアウトして、リッスンするポート番号を80から8080に更新する場合、ロールアウト中はServiceに2つのEndpointSliceが必要になります。1つはポート80をリッスンしているエンドポイント用、もう1つはポート8080をリッスンしているエンドポイント用です。
Serviceに100以上のエンドポイントが存在する場合、Endpointsコントローラーは1つの巨大なオブジェクトにエンドポイントを集約していましたが、EndpointSliceコントローラーはこれらを複数のEndpointSliceに分割します。
ServiceとEndpointSliceの間に予測可能な1対1の対応関係はないため、あるServiceに紐づけられるEndpointSliceリソースの実際の名前を事前に知ることはできません。
そのため、Serviceに紐づけられるEndpointSliceリソースを取得する際は、名前で取得するのではなく、"kubernetes.io/service-name"
ラベルが目的のServiceを指しているEndpointSliceを全て取得する必要があります。
kubectl get endpointslice -l kubernetes.io/service-name=myservice
Goのコードでも同様の変更が必要です。 Endpointsを使用して次のように記述していたところは、
// `namespace`内の`name`という名前のEndpointsを取得する
endpoint, err := client.CoreV1().Endpoints(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
// サービスに対応するEndpointsが(まだ)存在しない
...
}
// 他のエラーを処理
...
}
// `endpoint`を使った処理を続ける
...
EndpointSliceを使うと次のようになります。
// `namespace`内の`name`というServiceに紐づいた全てのEndpointSliceを取得する
slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx,
metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + name})
if err != nil {
// エラーを処理
...
} else if len(slices.Items) == 0 {
// Serviceに対応するEndpointSliceが(まだ)存在しない
...
}
// `slices.Items`を使った処理を続ける
...
EndpointSliceを生成する
手作業でEndpointsを生成している箇所やコントローラーについては、複数のEndpointSliceを考慮しなくてもよい場合が多いため、比較的簡単にEndpointSliceへの移行ができます。 Endpointsから少し情報の整理の仕方は変わっていますが、単にEndpointSliceという新しい型を使用するようにYAMLやGoのコードを更新するだけで済みます。
例えばこのようなEndpointsオブジェクトの場合、
apiVersion: v1
kind: Endpoints
metadata:
name: myservice
subsets:
- addresses:
- ip: 10.180.3.17
nodeName: node-4
- ip: 10.180.5.22
nodeName: node-9
- ip: 10.180.18.2
nodeName: node-7
notReadyAddresses:
- ip: 10.180.6.6
nodeName: node-8
ports:
- name: https
protocol: TCP
port: 443
次のようなEndpointSliceオブジェクトになります。
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: myservice
labels:
kubernetes.io/service-name: myservice
addressType: IPv4
endpoints:
- addresses:
- 10.180.3.17
nodeName: node-4
- addresses:
- 10.180.5.22
nodeName: node-9
- addresses:
- 10.180.18.12
nodeName: node-7
- addresses:
- 10.180.6.6
nodeName: node-8
conditions:
ready: false
ports:
- name: https
protocol: TCP
port: 443
いくつか留意点があります。
この例では明示的に
name
を指定していますが、generateName
を使用することでAPIサーバーにユニークなサフィックスを付加させることもできます。重要なのは名前自体ではなく、Serviceを指す"kubernetes.io/service-name"
ラベルです。明示的に
addressType: IPv4
(またはIPv6
)を指定する必要があります。EndpointSliceは、Endpointsの
"subsets"
フィールドの一要素と類似しています。複数のsubsetsを持つEndpointsオブジェクトを表現する場合、基本的には異なる"ports"
を持つ複数のEndpointSliceにする必要があります。endpoints
フィールドとaddresses
フィールドはどちらも配列ですが、慣習的にaddresses
フィールドは1つの要素しか含みません。Serviceに複数のエンドポイントがある場合は、endpoints
フィールドに複数の要素を持たせ、それぞれのaddresses
フィールドには1つの要素のみを含める必要があります。Endpoints APIでは「ready」と「not-ready」のエンドポイントが別々に列挙されますが、EndpointSlice APIでは各エンドポイントに対してconditions(
ready: false
など)を設定することができます。
もちろん、ひとたびEndpointSliceに移行すれば、topology hintsやterminating endpointsなどEndpointSlice特有の機能を活用できます。 詳細はEndpointSlice APIのドキュメントをご参照下さい。