Service を動かして理解する
v1.37 時点(2026年10月26日 確認)
目次
この記事では Pod の集まりをネットワークに公開する Service の仕組みを、図で説明します。Service がラベルセレクタで Pod を選び、変わらない IP アドレス(cluster IP)で通信を受けることがわかります。転送先の Pod の一覧を EndpointSlice というリソースが持ち、その一覧をもとに kube-proxy がノードで通信を Pod へ届けることもわかります。後半では kind のクラスタで Pod を増やしたり減らしたりして、Service の転送先が自動で変わる様子を確かめる手順を示します。
Pod の IP アドレスが Pod と一緒に変わることを見た第20回の内容を前提にしています。まだの方は先にこちらをどうぞ。
Pod 同士が通信する仕組みPod 同士が通信する仕組みを、Kubernetes のネットワークモデルから図で説明します。Pod ごとに付く IP アドレス、NAT を使わない Pod どうしの通信、ネットワークプラグイン(CNI プラグイン)の役割、Pod の IP アドレスの範囲を押さえ、kind のマルチノードのクラスタで、別のノードの Pod に IP アドレスで直接届くことを確かめる手順を紹介します。以下の図は、後半の「手元で試す」で作る my-nginx という Service と、その転送先の Pod、EndpointSlice の関係を表しています。
client の Pod は相手の Pod の IP アドレスではなく Service の cluster IP に接続します。Service はラベルセレクタ run=my-nginx に合う Pod を転送先にします。転送先の Pod の IP アドレスとポートは、Service が持つ EndpointSlice に書かれます。公式のチュートリアルによると、Pod が無くなると EndpointSlice から自動で外され、セレクタに合う新しい Pod は自動で加えられます。cluster IP は Service がある間は変わらないので、Pod が入れ替わっても client は同じ IP アドレスに接続し続けられます。
参考:Connecting Applications with Services | Kubernetes、EndpointSlices | Kubernetes
図の 2 本の実線は意味が違います。client から Service への実線は通信を表します。Service から Pod の枠への実線(selector: run=my-nginx)は、ラベルセレクタで Pod を選ぶ関係を表します。この実線と破線の「持つ」は、第6回と第11回の図と同じ意味です。
ただし、Service のオブジェクトそのものが通信を受け取って中継するわけではありません。Service は kube-apiserver を通して保存されるオブジェクト(設定)です。公式ドキュメントによると、Service の IP アドレスに特定の 1 台のホストが応答するわけではありません。kube-proxy を使うクラスタ(kind もそうです)では、cluster IP 宛ての通信をつかまえて Pod へ振り向けるのは、各ノードで動く kube-proxy がそのノードに入れた規則です。図では kube-proxy を省いています(「kube-proxy:ノードで通信を Pod へ届ける」で説明します)。
参考:Service | Kubernetes、Virtual IPs and Service Proxies | Kubernetes、Virtual IPs and Service Proxies | Kubernetes
Service とは
Service は クラスタの中で 1 つ以上の Pod として動くネットワークのアプリケーションを、公開する方法 です。公式ドキュメントは Service の API を、Pod の集まりをネットワークに公開するための抽象化(abstraction)と説明しています。Service のオブジェクトは論理的なエンドポイントの集まりと、それにどうアクセスできるようにするかの方針(policy)を決めます。エンドポイントは Service の通信を受け取る相手のことで、ふつうは Pod です。
Service は Pod や ConfigMap と同じくオブジェクトで、ふつうは kubectl で作ったり、見たり、変えたりします。v1.37 時点では、Service の名前は英小文字、数字、- だけでできた 63 文字までの名前で、英数字で始まり英数字で終わる形にします。数字で始めてもかまいません。Kubernetes の決まりでは、この形の名前(RFC 1123 のラベル名)は英字で始める必要があります(RFC 1123 そのものは数字で始めることを認めています)。Service の名前を数字で始められるのは、Service だけは機能ゲート RelaxedServiceNameValidation で例外にでき、この機能ゲートが v1.37 で安定版になり、既定で有効になったためです。
参考:Service | Kubernetes、Object Names and IDs | Kubernetes、Feature Gates | Kubernetes
公式ドキュメントによると、Service が通信を届ける Pod の集まりは、ふつうは利用者が書くセレクタで決まります。第11回で見たラベルセレクタです。Service のセレクタはキーと値を並べるだけの形で、等価ベースの条件にしか対応していません。Deployment のように matchLabels の下には書かず、selector の下にキーと値を直接書きます。
Service と組みで使うのが EndpointSlice です。EndpointSlice は Service の転送先(エンドポイント)の IP アドレスを記録するリソース です。公式ドキュメントによると、コントロールプレーンはセレクタを書いた Service に、EndpointSlice を自動で作ります。その EndpointSlice には、Service のセレクタに合うすべての Pod への参照が入ります。EndpointSlice の API は v1.21 から安定版(Stable)です。
参考:EndpointSlices | Kubernetes、EndpointSlice | Kubernetes API Reference
この回には似た名前の言葉がいくつか出てきます。公式ドキュメントでの意味に沿って、次の表にまとめます。
| 言葉 | 指すもの |
|---|---|
| Service | Pod の集まりをネットワークに公開するリソース |
| cluster IP | Service に割り当てられる、クラスタの中で使う IP アドレス(spec.clusterIP) |
| エンドポイント(endpoint) | Service の通信を受け取る、ネットワーク上の相手。ふつうは Pod |
| EndpointSlice | Service のエンドポイントの一覧の一部(slice)を表すリソース。apiVersion は discovery.k8s.io/v1 |
| Endpoints | EndpointSlice より前からある、エンドポイントの一覧のリソース。v1.33 から非推奨 |
| endpointslice-controller | kube-controller-manager の中で、EndpointSlice を作って管理するコントローラー |
| kube-proxy | 各ノードで動き、Service の cluster IP への通信をエンドポイントへ振り向けるよう、ノードを設定するコンポーネント(第4回) |
参考:Service | Kubernetes、EndpointSlices | Kubernetes、kube-controller-manager | Kubernetes、Virtual IPs and Service Proxies | Kubernetes
なぜ Service が必要なのか
第20回の最後で、Pod を作り直すと IP アドレスが変わることがあると見ました。Service のページも同じ問題から説明を始めています。Deployment でアプリケーションを動かすと、Deployment は Pod を動的に作ったり消したりします。そのため、ある瞬間にいくつの Pod が正常に動いているのかも、その Pod の名前すらも、わからないことがあります。Pod にはそれぞれの IP アドレスが付きますが、Pod の顔ぶれはある瞬間と次の瞬間とで違うことがあります。
公式ドキュメントはここから生まれる問題を次のように書いています。ある Pod の集まり(バックエンド)が、クラスタの中のほかの Pod(フロントエンド)に機能を提供しているとします。このとき、フロントエンドはどの IP アドレスに接続すればよいのかを、どうやって知り、追い続ければよいのでしょうか。
公式ドキュメントの例は画像を処理するバックエンドを 3 つのレプリカ(同じ Pod の複製)で動かす場合です。どのレプリカも同じ働きをするので、フロントエンドはどのバックエンドを使うかを気にしません。バックエンドを作る実際の Pod が変わっても、フロントエンドはそれを知る必要がなく、バックエンドの集まりを自分で追う必要もないはずです。この切り離しを可能にするのが Service だ、と公式ドキュメントは説明しています。
公式のチュートリアルによると、Service には作られたときに一意な IP アドレス(clusterIP)が割り当てられます。このアドレスは Service の寿命に結び付いていて、Service がある間は変わりません。Pod は Service に接続するように設定でき、Service への通信は Service の一員の Pod のどれかに自動で振り分けられます(負荷分散)。Service のページは Service の大きな狙いの 1 つとして、なじみのない「サービスの見つけ方」を使うために、既存のアプリケーションを書き換えなくてよいことを挙げています。
参考:Connecting Applications with Services | Kubernetes、Service | Kubernetes
公式のチュートリアルによると、クライアントが Service を見つける主な方法は、環境変数と DNS の 2 つです。環境変数の方法では、kubelet が Pod を動かすときに、動いている Service ごとに {SVCNAME}_SERVICE_HOST(cluster IP)や {SVCNAME}_SERVICE_PORT(ポート)などの環境変数を Pod に入れます。{SVCNAME} は Service の名前を大文字にし、- を _ に変えたものです。DNS は第23回で扱います。Kubernetes の API を使えるアプリケーションなら、API サーバーに Service の EndpointSlice を問い合わせる方法もあります。Kubernetes は Service の Pod の集まりが変わるたびに、EndpointSlice を書き換えます。
参考:Connecting Applications with Services | Kubernetes、Service | Kubernetes、Service | Kubernetes、Service | Kubernetes
Service の仕組み
マニフェストに書くフィールド
以下は公式ドキュメントにある Service のマニフェストです(そのまま引用しています)。app.kubernetes.io/name=MyApp のラベルを持ち、TCP の 9376 番のポートで待ち受ける Pod の集まりがある、という想定の例です。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376公式ドキュメントによると、このマニフェストを渡すと、既定の種類の ClusterIP で my-service という Service が作られます。この Service は app.kubernetes.io/name: MyApp のラベルを持つ Pod の、TCP の 9376 番のポートに通信を送ります。Kubernetes はこの Service に IP アドレス(cluster IP)を割り当てます。その Service を受け持つコントローラー(endpointslice-controller)はセレクタに合う Pod を探し続け、Service の EndpointSlice に必要な書き換えをします。
マニフェストに書いた主なフィールドを、v1.37 時点の API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 | 既定値 |
|---|---|---|
spec.selector | このセレクタのラベルのキーと値に合う Pod へ、Service の通信を送る。空か書かない場合、エンドポイントは外部のプロセスが管理するものとみなされ、Kubernetes は変えない | なし |
spec.ports[].port | Service が公開するポート。必須 | なし |
spec.ports[].targetPort | Service の通信を送る、Pod のポートの番号か名前 | port と同じ値 |
spec.ports[].protocol | ポートのプロトコル(通信の決まり)。TCP、UDP、SCTP のどれか | TCP |
spec.ports[].name | Service の中でのポートの名前。ポートが 1 つだけなら省略できる | なし |
spec.type | Service をどう公開するか | ClusterIP |
ポートの番号は 3 か所に出てきます。公式のチュートリアルの説明に沿って、それぞれの役目を次の表にまとめます。
| フィールド | 書く場所 | 役目 |
|---|---|---|
port | Service | Service のポート。ほかの Pod が Service に接続するときに使う |
targetPort | Service | 通信を送る先の、コンテナが通信を受け付けるポート |
containerPort | Pod のコンテナ | コンテナが待ち受けるポート(第8回) |
上の例では、クライアントは Service の 80 番のポートに接続し、通信は Pod の 9376 番のポートに届きます。公式ドキュメントによると、Service は受け付ける port がどの番号でも、それを targetPort に対応付けられます(2 つの番号は違っていてよい)。targetPort を書かなければ port と同じ値になります。targetPort には、Pod のポートに付けた名前(spec.containers[].ports[].name)も書けます。公式ドキュメントは、名前で指しておけば、次のバージョンのバックエンドで Pod のポートの番号を変えても、クライアントが使えなくならずに済むと説明しています。ポートを 2 つ以上持つ Service では、すべてのポートに名前を付ける必要があります。
cluster IP と Service の種類
type を書かない Service は ClusterIP の種類になります。公式ドキュメントによると、ClusterIP の Service には、クラスタがそのために取っておいた IP アドレスの範囲から IP アドレスが割り当てられます。範囲は kube-apiserver に設定します(第20回)。設定の名前は --service-cluster-ip-range です。kind(v0.33.0 時点)は既定では、IPv4 の Service の範囲に 10.96.0.0/16 を使います。ClusterIP の Service にはクラスタの中からだけ届きます。
参考:Service | Kubernetes、Service | Kubernetes、kube-apiserver | Kubernetes、Service | Kubernetes、Configuration | kind
API リファレンスによると、cluster IP はふつう無作為に割り当てられます。spec.clusterIP に自分で IP アドレスを書くこともできます。その場合は範囲の中で、まだ使われていないアドレスである必要があります。cluster IP は、type を ExternalName に変えるときなどを除いて、あとから変えられません。
cluster IP は Pod の IP アドレスとは性質が違います。公式ドキュメントによると、Pod の IP アドレスへの通信は決まった相手(その Pod)に実際に届きます。一方、Service の IP アドレスは、特定の 1 台のホスト(マシンや Pod)が自分のアドレスとして受け持って応答するものではありません。kube-proxy がパケットを処理する仕組み(Linux の iptables など)を使って仮想の(virtual)IP アドレスを作り、その宛ての通信を、クライアントが意識しない形でエンドポイントへ振り向けます。公式のチュートリアルも、Service の IP アドレスは完全に仮想のものだと説明しています(原文は “it never hits the wire”)。宛先が cluster IP のままのパケットがネットワークの線の上を流れることはない、という意味に読めます。
参考:Virtual IPs and Service Proxies | Kubernetes、Connecting Applications with Services | Kubernetes
Service の種類(spec.type)は 4 つあります。公式ドキュメントの説明と、このシリーズでの扱いを次の表にまとめます。
type | 公式ドキュメントの説明 | このシリーズでの扱い |
|---|---|---|
ClusterIP | クラスタの中の IP アドレスで Service を公開する。クラスタの中からだけ届く。type を書かないときの既定 | この回 |
NodePort | 各ノードの IP アドレスの、決まったポートで Service を公開する | 第22回 |
LoadBalancer | 外部のロードバランサーを使って、Service をクラスタの外に公開する | 第22回 |
ExternalName | Service を externalName フィールドの内容に対応付ける。転送(プロキシ)は一切設定されない | この回では扱わない |
公式ドキュメントによると、type は前の段に機能を足していく入れ子の作りで、ほかの種類のいくつかは ClusterIP を土台にしています。API リファレンスも、NodePort は ClusterIP の上に、LoadBalancer は NodePort の上に作られる、と説明しています。そのため、この回で見る cluster IP と EndpointSlice の仕組みは、第22回の種類でも土台になると考えられます。
Service には、仕組みが違う形が 2 つあります。headless Service はこの回の「cluster IP に接続すると Pod に振り分けられる」の説明に、セレクタの無い Service は「EndpointSlice が自動で作られる」の説明に当てはまりません。
| 形 | 違い |
|---|---|
headless Service(spec.clusterIP: None) | cluster IP が割り当てられず、kube-proxy も扱わない。セレクタがあれば EndpointSlice は作られ、クラスタの DNS が Pod の IP アドレスを返す(第14回) |
| セレクタの無い Service | EndpointSlice は自動では作られない。利用者が EndpointSlice を作り、kubernetes.io/service-name のラベルで Service と結び付ける。クラスタの外のバックエンドなどを指すのに使える |
EndpointSlice:Service の転送先の一覧
以下は公式ドキュメントにある EndpointSlice の例です(そのまま引用しています)。example という Service が持つ EndpointSlice です。
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: example-abc
labels:
kubernetes.io/service-name: example
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.1.2.3"
conditions:
ready: true
hostname: pod-1
nodeName: node-1
zone: us-west2-a例に出てくる主なフィールドを、v1.37 時点の API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 |
|---|---|
addressType | この EndpointSlice のアドレスの種類(IPv4 など)。作ったあとは変えられない |
ports | この EndpointSlice のどのエンドポイントにも当てはまるポートの一覧 |
endpoints[].addresses | エンドポイントの IP アドレス。endpointslice-controller が作ったものでは、いつも 1 つ |
endpoints[].conditions | エンドポイントの今の状態(ready、serving、terminating) |
endpoints[].nodeName | エンドポイントがあるノードの名前 |
endpoints[].targetRef | エンドポイントにあたる Kubernetes のオブジェクトへの参照 |
公式ドキュメントによると、EndpointSlice はアドレスの種類(IPv4 か IPv6)、プロトコル、ポートの番号、Service の名前の組み合わせごとにエンドポイントをまとめます。1 つの EndpointSlice に入るエンドポイントは、既定では 100 個までです(kube-controller-manager の設定で増やせます)。そのため、1 つの Service が複数の EndpointSlice を持つこともあります。API リファレンスによると、Service の EndpointSlice をすべて見つけるには、Service と同じ Namespace の EndpointSlice のうち、kubernetes.io/service-name のラベルに Service の名前が入ったものを一覧します。
参考:EndpointSlices | Kubernetes、EndpointSlice | Kubernetes API Reference
EndpointSlice を作って管理するのは、ふつうはコントロールプレーンの endpointslice-controller(公式ドキュメントの言い方では endpoint slice controller)です。v1.37 時点の kube-controller-manager では、endpointslice-controller という名前のコントローラーです。公式ドキュメントによると、このコントローラーは自分が管理する EndpointSlice に、endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io のラベルを付けます。多くの場合、EndpointSlice の持ち主は、エンドポイントを追っている Service です。それぞれの EndpointSlice の持ち主の参照(ownerReferences。第6回)と、kubernetes.io/service-name のラベルが、それを表します。
参考:EndpointSlices | Kubernetes、EndpointSlices | Kubernetes、kube-controller-manager | Kubernetes
エンドポイントの状態(conditions)は 3 つあります。公式ドキュメントと API リファレンスの説明を、次の表にまとめます。
| 状態 | 意味 |
|---|---|
serving | エンドポイントが今、応答を返していて、Service の通信の転送先として使うべき(should)状態。Pod のエンドポイントでは、Pod の Ready の状態(conditions)に対応する。v1.26 から安定版 |
terminating | エンドポイントが終了の途中にある。Pod のエンドポイントでは、Pod が消され始めたとき(削除の時刻が書き込まれたとき)に付く。v1.26 から安定版 |
ready | ほぼ「serving で、terminating ではない」ことを表す(例外になる設定もある) |
参考:EndpointSlices | Kubernetes、EndpointSlice | Kubernetes API Reference
Pod の Ready は第5回と第9回で見た Pod の状態の 1 つです。公式ドキュメントは Ready を、Pod がリクエストに応えられ、一致するすべての Service の負荷分散の対象に加えるべき状態、と説明しています。終了の途中の Pod のエンドポイントは、EndpointSlice からすぐには消されません。代わりに terminating の状態が付き、ready はいつも false になります。そのため、通信をエンドポイントへ振り分ける仕組み(原文は load balancers)は、そのエンドポイントをふだんの通信には使いません。EndpointSlice のページも、Service のプロキシ(kube-proxy のように、Service の通信をエンドポイントへ振り向けるもの)は、終了の途中のエンドポイントをふつうは無視すると説明しています。
参考:Pod Lifecycle | Kubernetes、Pod Lifecycle | Kubernetes、EndpointSlices | Kubernetes
Pod の増減と EndpointSlice
以下の図は後半の手順と同じく、Deployment my-nginx の Pod の数を 2 から 3 に増やし、そのあと 1 に減らしたときに、EndpointSlice の転送先がどう変わるかをコマ送りで表しています。図の「転送先(ready)」は EndpointSlice のエンドポイントのうち、ready が true のものの数です。Deployment が持つ ReplicaSet(第12回)は省いています。後半の手順にある「Pod を消して作り直させる」段も、図では省いています。
数を増やすと、ReplicaSet が新しい Pod を作ります(第11回)。新しい Pod は Ready になるまで、転送先(ready)に数えられません。公式ドキュメントによると、Probe(コンテナの状態を確かめる仕組み。第34回)を書いていないコンテナでは、kubelet はその Probe の結果をいつも成功(Success)とみなします。この回の nginx の Pod には Probe を書かないので、コンテナが動き出せば Ready になると考えられます。
参考:Liveness, Readiness, and Startup Probes | Kubernetes、Pod Lifecycle | Kubernetes
数を減らすと、ReplicaSet は第11回で見た決まりで消す Pod を選び、終了させます。終了の途中の Pod のエンドポイントは、ready が false になって転送先から外れます。Pod が消えたあとは、そのエンドポイントも EndpointSlice から外されます。どちらの向きでも、EndpointSlice を書き換えるのは endpointslice-controller で、利用者が EndpointSlice を書き換える必要はありません。
参考:Pod Lifecycle | Kubernetes、Connecting Applications with Services | Kubernetes、EndpointSlices | Kubernetes
kube-proxy:ノードで通信を Pod へ届ける
第4回で見たとおり、kube-proxy は各ノードで動くネットワークのプロキシで、Service をノードの上で実現します。kind のクラスタでは、kube-proxy は kube-system の Namespace の DaemonSet として動き、ノードごとに Pod が 1 つずつあります(第15回)。公式ドキュメントによると、kube-proxy の代わりになる別のコンポーネントを入れていない限り、クラスタのすべてのノードで kube-proxy が動きます。kube-proxy は ExternalName 以外の種類の Service のために、仮想の IP アドレスの仕組みを受け持ちます。ただし Service のページによると、headless Service は kube-proxy が扱いません。
参考:Virtual IPs and Service Proxies | Kubernetes、kube-proxy | Kubernetes、Implementation details | Kubernetes、Service | Kubernetes
それぞれの kube-proxy は、Service と EndpointSlice が足されたり消されたりするのを見張ります。Service ごとに、Service の clusterIP と port への通信をつかまえて、Service のエンドポイントの 1 つへ振り向けるよう、ノードを設定します。この設定は制御のループによって、API サーバーにある Service と EndpointSlice の状態と食い違わないように保たれます。公式ドキュメントは、kube-proxy がクラスタの中の通信をどう届けるかを決めるときに、EndpointSlice がよりどころ(source of truth)になると説明しています。
参考:Virtual IPs and Service Proxies | Kubernetes、EndpointSlices | Kubernetes
以下の図は、endpointslice-controller と kube-proxy が、それぞれ何を見張り、何を書き換えるかを表しています。
endpointslice-controller は Service と Pod を見張って EndpointSlice を書き換え、kube-proxy は Service と EndpointSlice を見張ってノードの転送の規則を設定します。図のとおり、2 つのコンポーネントは EndpointSlice を介してつながっています。図では省きましたが、第4回で見たとおり、どちらも kube-apiserver を通して見張ったり書き込んだりしています。点線は監視、破線は作る・書き換える・設定する(管理する)関係を表します。
参考:Virtual IPs and Service Proxies | Kubernetes、EndpointSlices | Kubernetes、Cluster Architecture | Kubernetes
kube-proxy がノードをどう設定するかは、動き方(モード)で変わります。公式ドキュメントによると、kube-proxy を起動するときにモードを明示しなければ、勧められている既定のモードが使われます。v1.37 時点の既定は iptables で、今後のバージョンで nftables に変わる予定です。公式ドキュメントは、バージョンを上げたときに思いがけずモードが変わらないよう、どのクラスタでもモードを明示した設定にしておくべき(should)だとしています。
v1.37 時点の公式ドキュメントに沿って、Linux のノードで選べるモードを次の表にまとめます。表に出てくるカーネルは一般に、OS の中心にあってネットワークの処理なども受け持つ部分のことです。
| モード | 公式ドキュメントの説明 |
|---|---|
iptables | v1.37 時点の既定。カーネルの iptables の API で、パケットを転送する規則を設定する。既定では転送先の Pod を無作為に選ぶ |
nftables | iptables の API の後継の、カーネルの nftables の API で規則を設定する。今後のバージョンで既定になる予定 |
ipvs | カーネルの IPVS と iptables の API で、転送の規則を作る。v1.35 から非推奨 |
参考:Virtual IPs and Service Proxies | Kubernetes、Virtual IPs and Service Proxies | Kubernetes、Virtual IPs and Service Proxies | Kubernetes
kind の公式ドキュメントによると、kind のクラスタの kube-proxy は既定では iptables のモードで動きます。クラスタの設定ファイルの networking の kubeProxyMode で、nftables や ipvs に変えられます。none にすると kube-proxy は入りません。この回のクラスタは設定ファイルに networking を書かないので、iptables のモードのはずです。
iptables のモードでの流れを、公式ドキュメントの例に沿ってまとめます。Service が作られると、コントロールプレーンが仮想の IP アドレスを割り当て、クラスタのすべての kube-proxy が新しい Service に気づきます。kube-proxy は仮想の IP アドレスへの通信を Service ごとの規則へ回し、さらにエンドポイントごとの規則へつなぐ、一連の iptables の規則を入れます。エンドポイントごとの規則は宛先のアドレスを書き換える NAT(Destination NAT。第20回)で、通信をバックエンドへ転送します。クライアントが仮想の IP アドレスに接続すると、規則が働いて、バックエンドが 1 つ選ばれます。選び方は、セッションアフィニティ(後で説明します)を設定していればそれに沿い、設定していなければ無作為です。パケットはクライアントの IP アドレスを書き換えずにバックエンドへ転送されます。
cluster IP は仮想の IP アドレスで、その宛ての通信を振り向ける規則は、kube-proxy が自分のノードに入れます。kube-proxy を使うクラスタでは、kube-proxy はすべてのノードで動くので、クライアントの Pod がどのノードにあっても、そのノードにはこの規則があります。公式ドキュメントは kube-proxy が「ノード」を設定すると書いているので、規則はクライアントの Pod があるノードの上で働くと考えられます。そうだとすると、宛先を Pod の IP アドレスに書き換えたあとは、第20回で見た Pod どうしの通信と同じく、ネットワークプラグインが届けるはずです。転送先の Pod が別のノードにあっても届くのは、そのためだと考えられます。第4回で見たとおり、ネットワークプラグインの中には kube-proxy の働きを自前で実装するものもあり、それを使うノードでは kube-proxy は要りません。
参考:Virtual IPs and Service Proxies | Kubernetes、Services, Load Balancing, and Networking | Kubernetes
同じクライアントからの接続を毎回同じ Pod に届けたいときは、spec.sessionAffinity を ClientIP にします(セッションアフィニティ)。クライアントの IP アドレスをもとに、届ける Pod を決める設定です。既定値は None です。ClientIP にしたときに同じ Pod へ届け続ける最長の時間は spec.sessionAffinityConfig.clientIP.timeoutSeconds で決まり、既定値は 10800 秒(3 時間)です。
参考:Virtual IPs and Service Proxies | Kubernetes、Service | Kubernetes API Reference
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。Pod を 2 つ動かす Deployment と、それを転送先にする Service を作り、EndpointSlice に Pod の IP アドレスが並ぶことを確かめます。通信元の Pod から Service の cluster IP で nginx に届くことを見たあと、Pod を増やす、Pod を消して作り直させる、Pod を減らす、の順に操作し、そのたびに EndpointSlice の転送先が変わる様子を見ます。
この節で引用する出力の例は、公式ドキュメントにある、ほかのクラスタのものです。表示の形を見るためのもので、名前や IP アドレスなどの値は手元とは違います。各段の最後には成功かどうかを見分ける点を短く挙げます。どれも公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。
コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | マルチノード(クラスタ名 multi-node):コントロールプレーン 1、ワーカー 2 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。この回は第20回と同じマルチノードのクラスタを使います。nginx の Pod と通信元の Pod が別々のノードに置かれることがあり、Pod を 1 つに減らしたあとの段で、別のノードの Pod にも cluster IP で届くかを確かめられるからです。Pod が 2 つ以上あるあいだに、1 回ごとにどの Pod に届いたかを見分けることは、この手順ではしません。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。マルチノードのクラスタを用意する
第2回と同じ次の設定ファイルを使います。公式ドキュメントの例をそのまま引用しています。kind-multi-node.yaml という名前で、コマンドを実行するディレクトリに保存します。第20回までに保存したファイルが残っていれば、それを使えます。
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerまず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
multi-node が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。
$ kind create cluster --name multi-node --config kind-multi-node.yaml
$ kind get clusters
kind get clusters に multi-node が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-multi-node を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-multi-node
ノードが 3 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
前の回のクラスタを使い続けている場合は、my-nginx という Deployment と Service、run: my-nginx のラベルを持つ Pod が無いことを確かめます。この回で使う公式のチュートリアルの Deployment(次の「マニフェストを用意する」で示します)は、自分自身のラベル(metadata.labels)を持ちません。そのため、ラベルで絞り込む指定の -l run=my-nginx(第6回)では選ばれません。run: my-nginx は Pod のひな形(spec.template)に書いた、Pod に付くラベルです。そこで、Deployment と Service は名前を、Pod はラベルを手がかりに調べます。
$ kubectl get deployments,services --context kind-multi-node
$ kubectl get pods -l run=my-nginx --context kind-multi-node
1 つ目のコマンドの一覧に my-nginx が無く、2 つ目のコマンドで見つからないことを知らせる 1 行が出れば、そのまま進めます。1 つ目のコマンドでは、kubernetes という Service が表示されると考えられます(「片付け」で説明します)。my-nginx が表示されたら、次のコマンドで消してから進みます。--ignore-not-found は、消す相手が無くてもエラーにしない指定です。
$ kubectl delete deployment my-nginx --ignore-not-found --context kind-multi-node
$ kubectl delete service my-nginx --ignore-not-found --context kind-multi-node
Deployment を消すと、その Pod も消えます(第6回)。それでも run: my-nginx の Pod が残っていたら、kubectl delete pod -l run=my-nginx --context kind-multi-node で消します。run: my-nginx の Pod が残ったままだと、この回の Service がその Pod も選んでしまうからです。
参考:Connecting Applications with Services | Kubernetes、Labels and Selectors | Kubernetes、kubectl get | Kubernetes、kubectl delete | Kubernetes
マニフェストを用意する
使うマニフェストは 3 つです。1 つ目は公式のチュートリアル(Connecting Applications with Services)にある Deployment です(そのまま引用しています)。run-my-nginx.yaml という名前で、コマンドを実行するディレクトリに保存します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80Pod のひな形に run: my-nginx のラベルを付けた、nginx の Pod を 2 つ動かす Deployment です(第12回)。これまでの回と違い、イメージにタグ(:1.14.2 のようなバージョンの印)を付けていません。公式のチュートリアルのマニフェストを、手を加えずにそのまま使うためです。公式ドキュメントによると、タグを書かないと latest のタグとみなされます。さらにイメージを取得する方針(imagePullPolicy)を書いていなければ、方針は Always になります。Always でも、イメージの中身がすべてノードにそろっていれば、kubelet はダウンロードし直さずにそれを使います。
2 つ目は同じチュートリアルにある Service です(そのまま引用しています)。nginx-svc.yaml という名前で保存します。
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx公式のチュートリアルによると、この Service は run: my-nginx のラベルを持つ Pod の、TCP の 80 番のポートに通信を送ります。targetPort を書いていないので、targetPort は port と同じ 80 になり、nginx のコンテナの containerPort の 80 と合います。metadata.labels は Service 自身に付けるラベルです。転送先の Pod を決めるのは spec.selector です。type を書いていないので、ClusterIP の Service になります。チュートリアルは、kubectl expose deployment/my-nginx で Service を作るのも、このマニフェストを kubectl apply -f で渡すのと同じだと説明しています。
参考:Connecting Applications with Services | Kubernetes、Service | Kubernetes API Reference
3 つ目は第20回と同じ通信元の Pod です。busybox:1.28 のイメージで、何もせずに待ち続けるコンテナを 1 つ動かします。client.yaml という名前で保存します。第20回で保存したファイルが残っていれば、それを使えます。
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
containers:
- name: busybox
image: busybox:1.28
command: ['sh', '-c', 'sleep 3600']参考:Pod | Kubernetes API Reference、Using Source IP | Kubernetes
client の Pod には run: my-nginx のラベルが無いので、Service の転送先には入りません。
EndpointSlice の監視を始める
ここからは端末を 2 つ使います。第11回・第13回〜第20回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でマニフェストを渡したり、Pod の中でコマンドを動かしたりします。この回で 1 つ目の端末が追うのは、my-nginx の Service の EndpointSlice です。2 つ目の端末を開いたら、cd でマニフェストを保存したディレクトリに移動しておきます。
1 つ目の端末で、kubernetes.io/service-name=my-nginx のラベルを持つ EndpointSlice を監視します。このラベルで、my-nginx の Service の EndpointSlice だけに絞り込めます。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。この監視は片付けの前まで止めずに続けます。
$ kubectl get endpointslices -l kubernetes.io/service-name=my-nginx --watch --context kind-multi-node
参考:kubectl get | Kubernetes、Connecting Applications with Services | Kubernetes
まだ Service が無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
Deployment と Service を作る
2 つ目の端末で、Deployment、Service、通信元の Pod のマニフェストを順に渡します。
$ kubectl apply -f run-my-nginx.yaml --context kind-multi-node
$ kubectl apply -f nginx-svc.yaml --context kind-multi-node
$ kubectl apply -f client.yaml --context kind-multi-node
deployment.apps/my-nginx、service/my-nginx、pod/client が作られたことを知らせる行が出るはずです。1 つ目の端末には、my-nginx- で始まる名前の EndpointSlice の行が現れるはずです。公式ドキュメントのとおり、コントロールプレーンはセレクタを書いた Service に EndpointSlice を自動で作るからです。Pod が動き出すにつれて EndpointSlice が書き換えられ、行が書き足されていくと考えられます。クラスタを作ったばかりなら、イメージの取得に時間がかかるので、Pod の IP アドレスが並ぶまでしばらくかかることがあります。
公式のチュートリアルの例では、この EndpointSlice の一覧は次のとおりです。
例では、ENDPOINTS 列に 2 つの Pod の IP アドレスが並び、PORTS 列は targetPort の 80 になっています。API リファレンスによると、Service から作られた EndpointSlice のポートの番号は、Service の targetPort にする決まりです。名前の後ろの 7vzhx のような文字列は、手元では違うものになるはずです。ENDPOINTS 列の IP アドレスも手元のクラスタの Pod のものになります。チュートリアルの例はほかのクラスタのものですが、kind(v0.33.0 時点)の既定の Pod の範囲も 10.244.0.0/16 なので、手元の IP アドレスも 10.244. で始まると考えられます(第20回)。
参考:Configuration | kind、EndpointSlice | Kubernetes API Reference
ENDPOINTS 列は転送先の増減をひと目で追うための目安として使います。この列にどの状態のエンドポイントが並ぶのかは、公式ドキュメントで確かめられませんでした。エンドポイントが転送先に入っているかどうかは、EndpointSlice の endpoints[].conditions.ready で確かめます。その手順は次の「Pod と Service を確かめる」で示します。
この段では、次の 3 点が合っていれば成功です。
- 3 つの
kubectl applyがどれも作られたことを知らせる行を出す - 1 つ目の端末に、
my-nginx-で始まる名前の EndpointSlice の行が現れる - その行の
ENDPOINTS列に、やがて IP アドレスが 2 つ並ぶ
Pod と Service を確かめる
2 つ目の端末で、run: my-nginx のラベルを持つ Pod を、IP アドレスとノードの列つきで一覧します。
$ kubectl get pods -l run=my-nginx -o wide --context kind-multi-node
公式のチュートリアルの例では、出力は次のとおりです。
例の NODE 列の値は公式の例のクラスタのノードの名前のままです。手元では NAME、IP、NODE 列の値が手元のクラスタのものになり、例よりも列が多いことがあると考えられます(第8回)。2 つの Pod の STATUS 列が Running になったら、IP 列の 2 つの IP アドレスを、1 つ目の端末の ENDPOINTS 列と見比べます。同じ 2 つの IP アドレスが並んでいるはずです。NODE 列を見ると、2 つの Pod が別々のノードにあるかどうかもわかります。
続けて、Service を表示します。
$ kubectl get svc my-nginx --context kind-multi-node
svc は services の短い書き方です。
公式のチュートリアルの例では、出力は次のとおりです。
TYPE 列は type を書かなかったので ClusterIP、PORT(S) 列は Service の port とプロトコルの 80/TCP のはずです。CLUSTER-IP 列の値は例とは違い、kind(v0.33.0 時点)の既定の Service の範囲 10.96.0.0/16 に入るはずです。この値を <my-nginx の CLUSTER-IP> として控えておきます。あとで、Pod を入れ替えたあとの値と見比べます。
Service のくわしい情報も表示します。
$ kubectl describe svc my-nginx --context kind-multi-node
公式のチュートリアルの例では、出力は次のとおりです。
Selector: の行がラベルセレクタ、TargetPort: の行が書かなかったために port と同じになった targetPort です。Port: の行の <unset> はポートに名前(name)を付けていないことを表すと考えられます。Endpoints: の行には、転送先の Pod の IP アドレスとポートが並びます。チュートリアルも、この行の IP アドレスが最初に作った Pod の IP アドレスと同じことを確かめるよう案内しています。Session Affinity: が None なのは、sessionAffinity の既定値が None だからです。手元では、IP: などの値が手元のクラスタのものになり、例に無い行が出ることもあると考えられます。
参考:Connecting Applications with Services | Kubernetes、Service | Kubernetes API Reference
最後に、EndpointSlice の中身を YAML の形で表示します。
$ kubectl get endpointslices -l kubernetes.io/service-name=my-nginx -o yaml --context kind-multi-node
-l で絞り込んで表示しているので、EndpointSlice は一覧(items)の中に入って表示されると考えられます。「EndpointSlice:Service の転送先の一覧」で見たとおりなら、表示の中に次の点が見つかるはずです。
metadata.labelsにkubernetes.io/service-name: my-nginxとendpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.ioがあるmetadata.ownerReferencesに、持ち主としてmy-nginxの Service があるaddressTypeがIPv4で、portsに 80 番のポートがあるendpointsの項目が 2 つあり、それぞれのaddressesが Pod の IP アドレスと同じ- それぞれの項目の
conditionsで、readyとservingがtrueになっている
terminating は false と表示されるか、項目そのものが無いと考えられます。API リファレンスによると、terminating の値が無いときは false とみなします。
endpoints の各項目には、nodeName に Pod があるノードの名前、targetRef に Pod の名前も入っていると考えられます。kubectl get pods -o wide の NODE 列と見比べてみてください。
参考:EndpointSlices | Kubernetes、EndpointSlice | Kubernetes API Reference
この先の段では、エンドポイントの IP アドレスと ready の値だけを手早く見るために、次のコマンドを使います。ここでも一度実行しておきます。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。range と end のあいだに書いた指定を、すべての EndpointSlice のすべてのエンドポイントについて繰り返し、1 つのエンドポイントを 1 行で表示します。{"\t"} はタブ、{"\n"} は改行です。
$ kubectl get endpointslices -l kubernetes.io/service-name=my-nginx -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\t"}{.conditions.ready}{"\n"}{end}' --context kind-multi-node
参考:JSONPath Support | Kubernetes、kubectl Quick Reference | Kubernetes、EndpointSlice | Kubernetes API Reference
Pod の IP アドレスごとに 1 行ずつ、IP アドレスと ready の値が並ぶはずです。ready が true の行のエンドポイントが転送先に入っています。ready の値が空なら、値が書かれていないということで、API リファレンスはこれを true とみなすべき(should)としています。
この段では、次の 3 点が合っていれば成功です。
- Pod の
IP列の 2 つの IP アドレスが、1 つ目の端末のENDPOINTS列と同じ - Service の
TYPE列がClusterIPで、CLUSTER-IP列が10.96.で始まる - 上のコマンドで、2 つの IP アドレスの行の
readyがtrueになっている
Service の cluster IP で nginx に届ける
2 つ目の端末で、client の Pod が Running になっていることを確かめます。
$ kubectl get pod client --context kind-multi-node
STATUS 列が Running になったら、client の Pod の中で wget を動かし、Service の cluster IP に問い合わせます。<my-nginx の CLUSTER-IP> には控えた値を入れます。-q と -O- は第10回・第20回と同じで、一般に、進み具合を表示せず、受け取った内容をそのまま画面に出す指定です。:80 は Service の port です。
$ kubectl exec client --context kind-multi-node -- wget -q -O- http://<my-nginx の CLUSTER-IP>:80/
第20回と同じく、<h1>Welcome to nginx!</h1> の行を含む、nginx の初期のページの HTML が表示されれば成功です。今度は Pod の IP アドレスではなく Service の cluster IP を宛先にしています。client のノードの kube-proxy が設定した規則が、この通信を転送先の Pod の 1 つへ振り向けたと考えられます。どちらの Pod に届いたかは、この表示ではわかりません。2 つの nginx の Pod は同じページを返すからです。この段で確かめられるのは、cluster IP を宛先にした通信が、nginx の Pod のどれかに届くことまでです。
参考:Virtual IPs and Service Proxies | Kubernetes、Connecting Applications with Services | Kubernetes
数秒たっても何も表示されずに止まったままなら、Ctrl+C で止めます。そのうえで、IP アドレスの打ち間違いが無いかと、1 つ目の端末の ENDPOINTS 列に Pod の IP アドレスが並んでいるかを見直します。
Pod を増やす
2 つ目の端末で、Deployment の Pod の数を 3 にします。公式のチュートリアルでも、kubectl scale で Deployment の数を変えています。
$ kubectl scale deployment my-nginx --replicas=3 --context kind-multi-node
参考:kubectl scale | Kubernetes、Connecting Applications with Services | Kubernetes
数を変えたことを知らせる 1 行が出るはずです。1 つ目の端末には、my-nginx- で始まる同じ名前の EndpointSlice の行が書き足され、ENDPOINTS 列の IP アドレスが 3 つになると考えられます。Service にも EndpointSlice にも手を触れていないのに、転送先が増えたことになります。エンドポイントが 100 個を超えない限り、新しい EndpointSlice は作られず、同じ EndpointSlice が書き換えられるはずです。
参考:Service | Kubernetes、Connecting Applications with Services | Kubernetes
前に書いたとおり、ENDPOINTS 列にどの状態のエンドポイントが並ぶのかは、公式ドキュメントで確かめられませんでした。そのため、新しい Pod の IP アドレスが Ready になる前から並ぶこともあると考えられます。新しい Pod が転送先(ready)に入ったかどうかは、「Pod と Service を確かめる」の最後に示した、ready を表示するコマンドで確かめます。新しい Pod の READY 列が 1/1 になってから少し待てば、新しい IP アドレスの行の ready は true になっているはずです。
参考:EndpointSlices | Kubernetes、Liveness, Readiness, and Startup Probes | Kubernetes
Pod も一覧して、3 つ目の Pod の IP アドレスが ENDPOINTS 列に加わったことを見比べます。
$ kubectl get pods -l run=my-nginx -o wide --context kind-multi-node
この段では、次の 3 点が合っていれば成功です。
ENDPOINTS列の IP アドレスが 3 つになる- EndpointSlice の名前は変わらず、同じ EndpointSlice の行が書き足される
readyを表示するコマンドで、3 つの行のreadyがtrueになる
Pod を消して作り直させる
第20回の最後では、Pod を作り直すと IP アドレスが変わることがあると見ました。今度は Service を通して、その影響を見ます。2 つ目の端末で、my-nginx の Pod を 1 つ消します。<my-nginx の Pod の名前> には、直前の一覧の NAME 列から 1 つ選んで入れます。消す Pod の IP アドレスも控えておきます。
$ kubectl delete pod <my-nginx の Pod の名前> --context kind-multi-node
ReplicaSet は Pod の数を 3 に保つので、新しい Pod を作ります(第11回)。1 つ目の端末では、行が何回か書き足され、消した Pod の IP アドレスが ENDPOINTS 列から消えて、新しい Pod の IP アドレスが加わると考えられます。公式のチュートリアルによると、無くなった Pod は EndpointSlice から自動で外され、セレクタに合う新しい Pod は自動で加えられるからです。消した Pod が終了の途中にあるあいだは、そのエンドポイントが terminating の状態で EndpointSlice に残ることがあるので、IP アドレスが消えるまで少しかかることがあります。
参考:Connecting Applications with Services | Kubernetes、Pod Lifecycle | Kubernetes
新しい Pod が Running になったら、2 つ目の端末で Pod と Service をもう一度表示します。
$ kubectl get pods -l run=my-nginx -o wide --context kind-multi-node
$ kubectl get svc my-nginx --context kind-multi-node
新しい Pod の IP 列を、消した Pod の IP アドレスと見比べてみてください。第20回で見たとおり、別の IP アドレスになっていることがあります。一方、my-nginx の Service の CLUSTER-IP 列は、控えた値と同じはずです。cluster IP は Service がある間は変わらないからです。控えた cluster IP で、もう一度問い合わせます。
$ kubectl exec client --context kind-multi-node -- wget -q -O- http://<my-nginx の CLUSTER-IP>:80/
前と同じく nginx の初期のページが表示されるはずです。client は Pod の IP アドレスを調べ直さずに、同じ cluster IP のまま nginx に届いています。第20回で「相手が作り直されるたびに IP アドレスを調べ直すのは大変」とした手間を、Service と EndpointSlice が引き受けていることになります。
この段では、次の 4 点が合っていれば成功です。
- 消した Pod の IP アドレスが
ENDPOINTS列から消え、新しい Pod の IP アドレスが加わる - 新しい Pod が Ready になったあと、
readyを表示するコマンドで 3 つの行のreadyがtrueになる - Service の
CLUSTER-IP列が控えた値と同じ - 控えた cluster IP で、nginx の初期のページが表示される
Pod を減らす
2 つ目の端末で、Deployment の Pod の数を 1 にします。
$ kubectl scale deployment my-nginx --replicas=1 --context kind-multi-node
ReplicaSet が 2 つの Pod を選んで終了させます(第11回)。1 つ目の端末では、行が何回か書き足され、最後は ENDPOINTS 列の IP アドレスが 1 つになると考えられます。終了の途中の Pod のエンドポイントはすぐには EndpointSlice から消されず、ready が false になります。そのため、途中の行では、終了の途中の Pod の IP アドレスがまだ並んで見えることもあると考えられます。そのあいだに ready を表示するコマンドを実行すると、終了の途中の Pod の行は ready が false になっているはずです。
参考:Pod Lifecycle | Kubernetes、ReplicaSet | Kubernetes、EndpointSlices | Kubernetes
ready を表示するコマンドの行が 1 つだけになったら、Pod を一覧して、残った Pod の IP アドレスが ENDPOINTS 列の 1 つと同じことを確かめます。もう一度 wget で問い合わせると、残った 1 つの Pod から nginx の初期のページが返るはずです。
$ kubectl get pods -l run=my-nginx -o wide --context kind-multi-node
$ kubectl exec client --context kind-multi-node -- wget -q -O- http://<my-nginx の CLUSTER-IP>:80/
このとき転送先は 1 つなので、ページが返ったのは残った Pod に届いたからだと考えられます。続けて、client の Pod がどのノードにあるかを、NODE 列で確かめます。
$ kubectl get pod client -o wide --context kind-multi-node
client の NODE 列が、残った nginx の Pod の NODE 列と違えば、通信元とは別のノードにある Pod にも cluster IP で届いたと考えられます。同じノードだった場合は、この手順では別のノードへの通信は確かめられません。
この段では、次の 3 点が合っていれば成功です。
- 最後は
ENDPOINTS列の IP アドレスが 1 つになり、残った Pod の IP アドレスと同じ readyを表示するコマンドの行が最後は 1 つになり、readyがtrue- 控えた cluster IP で、nginx の初期のページが表示される
片付け
1 つ目の端末で Ctrl+C を押して、監視を止めます。
2 つ目の端末で、作った Pod、Service、Deployment を、マニフェストを指定して削除します。
$ kubectl delete -f client.yaml --context kind-multi-node
$ kubectl delete -f nginx-svc.yaml --context kind-multi-node
$ kubectl delete -f run-my-nginx.yaml --context kind-multi-node
Deployment を消すと、その ReplicaSet と Pod も消えます(第6回)。EndpointSlice は持ち主の参照で Service を指しているので、Service を消すと、同じカスケード削除の仕組みで EndpointSlice も消されると考えられます。client を消すコマンドは、Pod の終了までの猶予の時間(既定では 30 秒)のぶん、終わるまで少しかかることがあります(第20回)。
参考:EndpointSlices | Kubernetes、Garbage Collection | Kubernetes、Pod Lifecycle | Kubernetes
少し待ってから、残っていないかを確かめます。
$ kubectl get deployments,pods --context kind-multi-node
$ kubectl get endpointslices -l kubernetes.io/service-name=my-nginx --context kind-multi-node
$ kubectl get services --context kind-multi-node
Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。1 つ目と 2 つ目のコマンドで、見つからないことを知らせる 1 行が出れば、削除できています。3 つ目のコマンドでは、my-nginx は表示されないはずです。前の回の Service が残っていなければ、kubernetes という Service だけが表示されると考えられます。公式ドキュメントによると、kube-apiserver のクラスタの中でのアドレスは、default の Namespace の kubernetes という Service にも公開されています。Pod はこれを使って kube-apiserver を指せます。この回で作ったものではないので、消さずに残します。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster --name multi-node
$ kind get clusters
kind delete cluster は、存在しないクラスタを指定してもエラーを返しません。kind get clusters に multi-node が表示されなくなったことを確かめます。kubeconfig に kind-multi-node のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-multi-node で消します。
注意点
Service でつまずきやすい点と、あとから変えられない設定、バージョンで変わる点を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| ClusterIP の Service にはクラスタの中からだけ届く | クラスタの外から届けるには、Service の種類の NodePort や LoadBalancer(第22回)、Ingress や Gateway API(第24回)を使う |
| セレクタは、ラベルが合う Pod を選ぶ | 公式ドキュメントの例の Service は、セレクタのラベルを持つ Pod なら、どの Pod でも(any Pod)転送先にする。ほかのアプリケーションの Pod が同じラベルを持っていると、その Pod にも届くと考えられる |
| Ready でない Pod には、ふだんの通信は届かない | 転送先になるのは ready のエンドポイント。Pod が Ready かどうかを決める readiness Probe は第34回 |
| 同じクライアントでも、届く Pod は毎回同じとは限らない | iptables のモードでは、既定では転送先を無作為に選ぶ。同じ Pod に届け続けたいときは sessionAffinity: ClientIP にする(既定は None) |
| cluster IP はあとから変えられない | type を ExternalName に変えるときなどを除き、spec.clusterIP は更新で変えられない。自分で決める場合は Service の範囲の中で、使われていないアドレスにする |
| 環境変数は Service より先に作った Pod には入らない | kubelet は Pod を動かすとき、動いている Service ごとに {SVCNAME}_SERVICE_HOST などの環境変数を入れる。環境変数で Service を見つける場合は、Service をクライアントの Pod より先に作る必要がある。DNS の名前で接続する方法は第23回 |
| Endpoints の API は非推奨 | v1.33 から非推奨。公式ドキュメントは、すべてのクライアントに EndpointSlice の API を使うよう勧めている |
| kube-proxy の既定のモードは変わる予定 | v1.37 時点の既定は iptables で、今後のバージョンで nftables に変わる予定。ipvs は v1.35 から非推奨。公式ドキュメントは、モードを明示した設定にしておくべき(should)としている |
| headless Service とセレクタの無い Service は仕組みが違う | headless Service は cluster IP を持たず、kube-proxy も扱わない。セレクタの無い Service には EndpointSlice が自動では作られない |
参考:Service | Kubernetes、Service | Kubernetes、Service | Kubernetes、Virtual IPs and Service Proxies | Kubernetes、Virtual IPs and Service Proxies | Kubernetes、Service | Kubernetes API Reference、Service | Kubernetes、Service | Kubernetes、Service | Kubernetes、Service | Kubernetes
関連記事
ClusterIP の上に作られる NodePort と LoadBalancer の違いと、クラスタの外から Service に届ける方法については、以下の記事で解説しています。
NodePort と LoadBalancer の違いService をクラスタの外に公開する NodePort と LoadBalancer の違いを図で説明します。すべてのノードの同じポートで受ける NodePort と、その前に外部のロードバランサーを置く LoadBalancer の関係を押さえ、kind のクラスタで、ノードが Docker のコンテナであることによる制約と、手元のマシンから届ける方法を確かめる手順を紹介します。この回では cluster IP を控えて接続しましたが、Service の名前で接続できる、クラスタの中の DNS については、以下の記事で解説しています。
なぜ Service 名で通信できるのかService の名前を IP アドレスに変えるクラスタ DNS の仕組みを図で説明します。CoreDNS と kube-dns の Service、kubelet が Pod に書き込む /etc/resolv.conf の search と ndots、Namespace をまたぐ名前の書き方を押さえ、kind のクラスタで名前を引いて確かめる手順を紹介します。Pod が Ready かどうかを決める readiness Probe と、Service の転送先との関係については、以下の記事で解説しています。
liveness・readiness・startup Probe の違いkubelet がコンテナを定期的に確かめる Probe の 3 種類、liveness・readiness・startup の違いを図で説明します。失敗したときに kubelet がすること、確かめ方と設定のフィールド、readiness Probe と Service の転送先との関係を押さえ、kind のクラスタで readiness が失敗した Pod が転送先から外れることを確かめる手順を紹介します。