|
| 1 | +--- |
| 2 | +layout: blog |
| 3 | +title: "Kubernetes v1.33: EndpointsからEndpointSliceへの継続的な移行を進める" |
| 4 | +slug: endpoints-deprecation |
| 5 | +date: 2025-04-24T10:30:00-08:00 |
| 6 | +author: > |
| 7 | + Dan Winship (Red Hat) |
| 8 | +translator: > |
| 9 | + [Takuto Nagami](https://github.com/logica0419) (千葉工業大学) |
| 10 | +--- |
| 11 | + |
| 12 | +[EndpointSlice] ([KEP-752])がv1.15でアルファとして導入され、v1.21でGAとなって以来、Endpoints APIはKubernetesの中でほぼ使われず、埃を被っています。 |
| 13 | +[デュアルスタックネットワーク]や[トラフィック分散]など、Serviceの新機能はEndpointSlice APIでのみサポートされているため、全てのサービスプロキシ、Gateway API実装、及び同様のコントローラーはEndpointsからEndpointSliceへの移行を余儀なくされました。 |
| 14 | +現時点のEndpoints APIは、未だにEndpointsを使っているエンドユーザーのワークロードやスクリプトの互換性を維持するための存在に過ぎません。 |
| 15 | + |
| 16 | +Kubernetes 1.33以降、Endpoints APIは正式に非推奨となり、Endpointsリソースを読み書きするユーザーに対して、EndpointSliceを使用するようAPIサーバーから警告が返されるようになりました。 |
| 17 | + |
| 18 | +最終的には、「ServiceとPodに基づいてEndpointsオブジェクトを生成する _Endpointsコントローラー_ がクラスター内で実行されている」という基準を[Kubernetes Conformance]から除外することが[KEP-4974]にて計画されています。 |
| 19 | +これの実現によって、現代的なほとんどのクラスターにおいて不要な作業を回避することができます。 |
| 20 | + |
| 21 | +[Kubernetes非推奨ポリシー]に従うと、Endpointsタイプ自体が完全に廃止されることはおそらく無いですが、Endpoints APIを使うワークロードやスクリプトを保有しているユーザーはEndpointSliceへの移行が推奨されます。 |
| 22 | + |
| 23 | +[EndpointSlice]: /blog/2020/09/02/scaling-kubernetes-networking-with-endpointslices/ |
| 24 | +[KEP-752]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/0752-endpointslices/README.md |
| 25 | +[デュアルスタックネットワーク]: /ja/docs/concepts/services-networking/dual-stack/ |
| 26 | +[トラフィック分散]: /docs/reference/networking/virtual-ips/#traffic-distribution |
| 27 | +[Kubernetes非推奨ポリシー]: /ja/docs/reference/using-api/deprecation-policy/ |
| 28 | +[KEP-4974]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/4974-deprecate-endpoints/README.md |
| 29 | +[Kubernetes Conformance]: https://www.cncf.io/training/certification/software-conformance/ |
| 30 | + |
| 31 | +## EndpointsからEndpointSliceへの移行に関する注意点 |
| 32 | + |
| 33 | +### EndpointSliceを利用する |
| 34 | + |
| 35 | +エンドユーザーにとって、Endpoints APIとEndpointSlice APIの最大の違いは、`selector`を持つ全てのServiceが自身と同じ名前のEndpointsオブジェクトを必ず1つずつ持つのに対し、1つのServiceに紐づけられるEndpointSliceは複数存在する可能性がある、という点です。 |
| 36 | + |
| 37 | +```console |
| 38 | +$ kubectl get endpoints myservice |
| 39 | +Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice |
| 40 | +NAME ENDPOINTS AGE |
| 41 | +myservice 10.180.3.17:443 1h |
| 42 | + |
| 43 | +$ kubectl get endpointslice -l kubernetes.io/service-name=myservice |
| 44 | +NAME ADDRESSTYPE PORTS ENDPOINTS AGE |
| 45 | +myservice-7vzhx IPv4 443 10.180.3.17 21s |
| 46 | +myservice-jcv8s IPv6 443 2001:db8:0123::5 21s |
| 47 | +``` |
| 48 | + |
| 49 | +この場合、Serviceがデュアルスタックであるため、EndpointSliceがIPv4アドレス用とIPv6アドレス用の2つ存在します。 |
| 50 | +(Endpoints APIはデュアルスタックをサポートしていないため、Endpointsオブジェクトにはクラスターのプライマリアドレスファミリーのアドレスのみが表示されています。) |
| 51 | + |
| 52 | +複数のEndpointSliceを持つ _可能性_ は、複数のエンドポイントが存在するあらゆるServiceにありますが、代表的なケースが3つ存在します。 |
| 53 | + |
| 54 | + - EndpointSliceは単一のIPファミリーのエンドポイントしか表現できないため、デュアルスタックServiceの場合、IPv4用とIPv6用のEndpointSliceがそれぞれ作成されます。 |
| 55 | + |
| 56 | + - 単一のEndpointSlice内のエンドポイントは、全て同じポートを対象とする必要があります。例えば、エンドポイントとなるPodをロールアウトして、リッスンするポート番号を80から8080に更新する場合、ロールアウト中はServiceに2つのEndpointSliceが必要になります。1つはポート80をリッスンしているエンドポイント用、もう1つはポート8080をリッスンしているエンドポイント用です。 |
| 57 | + |
| 58 | + - Serviceに100以上のエンドポイントが存在する場合、Endpointsコントローラーは1つの巨大なオブジェクトにエンドポイントを集約していましたが、EndpointSliceコントローラーはこれらを複数のEndpointSliceに分割します。 |
| 59 | + |
| 60 | +ServiceとEndpointSliceの間に予測可能な1対1の対応関係はないため、あるServiceに紐づけられるEndpointSliceリソースの実際の名前を事前に知ることはできません。 |
| 61 | +そのため、Serviceに紐づけられるEndpointSliceリソースを取得する際は、名前で取得するのではなく、`"kubernetes.io/service-name"`[ラベル](/ja/docs/concepts/overview/working-with-objects/labels/)が目的のServiceを指しているEndpointSliceを全て取得する必要があります。 |
| 62 | + |
| 63 | +```console |
| 64 | +kubectl get endpointslice -l kubernetes.io/service-name=myservice |
| 65 | +``` |
| 66 | + |
| 67 | +Goのコードでも同様の変更が必要です。 |
| 68 | +Endpointsを使用して次のように記述していたところは、 |
| 69 | + |
| 70 | +```go |
| 71 | +// `namespace`内の`name`という名前のEndpointsを取得する |
| 72 | +endpoint, err := client.CoreV1().Endpoints(namespace).Get(ctx, name, metav1.GetOptions{}) |
| 73 | +if err != nil { |
| 74 | + if apierrors.IsNotFound(err) { |
| 75 | + // サービスに対応するEndpointsが(まだ)存在しない |
| 76 | + ... |
| 77 | + } |
| 78 | + // 他のエラーを処理 |
| 79 | + ... |
| 80 | +} |
| 81 | + |
| 82 | +// `endpoint`を使った処理を続ける |
| 83 | +... |
| 84 | +``` |
| 85 | + |
| 86 | +EndpointSliceを使うと次のようになります。 |
| 87 | + |
| 88 | +```go |
| 89 | +// `namespace`内の`name`というServiceに紐づいた全てのEndpointSliceを取得する |
| 90 | +slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx, |
| 91 | + metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + name}) |
| 92 | +if err != nil { |
| 93 | + // エラーを処理 |
| 94 | + ... |
| 95 | +} else if len(slices.Items) == 0 { |
| 96 | + // Serviceに対応するEndpointSliceが(まだ)存在しない |
| 97 | + ... |
| 98 | +} |
| 99 | + |
| 100 | +// `slices.Items`を使った処理を続ける |
| 101 | +... |
| 102 | +``` |
| 103 | + |
| 104 | +### EndpointSliceを生成する |
| 105 | + |
| 106 | +手作業でEndpointsを生成している箇所やコントローラーについては、複数のEndpointSliceを考慮しなくてもよい場合が多いため、比較的簡単にEndpointSliceへの移行ができます。 |
| 107 | +Endpointsから少し情報の整理の仕方は変わっていますが、単にEndpointSliceという新しい型を使用するようにYAMLやGoのコードを更新するだけで済みます。 |
| 108 | + |
| 109 | +例えばこのようなEndpointsオブジェクトの場合、 |
| 110 | + |
| 111 | +```yaml |
| 112 | +apiVersion: v1 |
| 113 | +kind: Endpoints |
| 114 | +metadata: |
| 115 | + name: myservice |
| 116 | +subsets: |
| 117 | + - addresses: |
| 118 | + - ip: 10.180.3.17 |
| 119 | + nodeName: node-4 |
| 120 | + - ip: 10.180.5.22 |
| 121 | + nodeName: node-9 |
| 122 | + - ip: 10.180.18.2 |
| 123 | + nodeName: node-7 |
| 124 | + notReadyAddresses: |
| 125 | + - ip: 10.180.6.6 |
| 126 | + nodeName: node-8 |
| 127 | + ports: |
| 128 | + - name: https |
| 129 | + protocol: TCP |
| 130 | + port: 443 |
| 131 | +``` |
| 132 | +
|
| 133 | +次のようなEndpointSliceオブジェクトになります。 |
| 134 | +
|
| 135 | +```yaml |
| 136 | +apiVersion: discovery.k8s.io/v1 |
| 137 | +kind: EndpointSlice |
| 138 | +metadata: |
| 139 | + name: myservice |
| 140 | + labels: |
| 141 | + kubernetes.io/service-name: myservice |
| 142 | +addressType: IPv4 |
| 143 | +endpoints: |
| 144 | + - addresses: |
| 145 | + - 10.180.3.17 |
| 146 | + nodeName: node-4 |
| 147 | + - addresses: |
| 148 | + - 10.180.5.22 |
| 149 | + nodeName: node-9 |
| 150 | + - addresses: |
| 151 | + - 10.180.18.12 |
| 152 | + nodeName: node-7 |
| 153 | + - addresses: |
| 154 | + - 10.180.6.6 |
| 155 | + nodeName: node-8 |
| 156 | + conditions: |
| 157 | + ready: false |
| 158 | +ports: |
| 159 | + - name: https |
| 160 | + protocol: TCP |
| 161 | + port: 443 |
| 162 | +``` |
| 163 | +
|
| 164 | +いくつか留意点があります。 |
| 165 | +
|
| 166 | +1. この例では明示的に`name`を指定していますが、`generateName`を使用することでAPIサーバーにユニークなサフィックスを付加させることもできます。重要なのは名前自体ではなく、Serviceを指す`"kubernetes.io/service-name"`ラベルです。 |
| 167 | + |
| 168 | +2. 明示的に`addressType: IPv4`(または`IPv6`)を指定する必要があります。 |
| 169 | + |
| 170 | +3. EndpointSliceは、Endpointsの`"subsets"`フィールドの一要素と類似しています。複数のsubsetsを持つEndpointsオブジェクトを表現する場合、基本的には異なる`"ports"`を持つ複数のEndpointSliceにする必要があります。 |
| 171 | + |
| 172 | +4. `endpoints`フィールドと`addresses`フィールドはどちらも配列ですが、慣習的に`addresses`フィールドは1つの要素しか含みません。Serviceに複数のエンドポイントがある場合は、`endpoints`フィールドに複数の要素を持たせ、それぞれの`addresses`フィールドには1つの要素のみを含める必要があります。 |
| 173 | + |
| 174 | +5. Endpoints APIでは「ready」と「not-ready」のエンドポイントが別々に列挙されますが、EndpointSlice APIでは各エンドポイントに対してconditions(`ready: false`など)を設定することができます。 |
| 175 | + |
| 176 | +もちろん、ひとたびEndpointSliceに移行すれば、topology hintsやterminating endpointsなどEndpointSlice特有の機能を活用できます。 |
| 177 | +詳細は[EndpointSlice APIのドキュメント](/docs/reference/kubernetes-api/service-resources/endpoint-slice-v1)をご参照下さい。 |
0 commit comments