ドキュメントからわかったk8sのService


# はじめに

こんにちは。もう 1 月が終わってしまいますね。。。
もう 2025 年も終わってしまうのではないかと思ってしまいます。。。

最近私は k8s のドキュメントを頭から少しずつ読んでいるということをしています。
k8s 自体はお家 k8s をやるぐらいには触っているのですが、それでもドキュメントを読むと新しい発見がめちゃめちゃあります。

そして今回はドキュメントを読んでわかった Service の話をしたいと思います。

## Service の役割

Service の主要な役割は、Service に紐づいている Pod などに対して安定したネットワークアクセスを提供することです。[^1]
ここで、「安定」と表現した理由は、Service を介さない Pod への直接アクセスは安定していないためです。

Service を利用しない場合、Pod の IP アドレスを直接指定する必要があると思いますが、Pod は予期せぬタイミングで削除されたり、作成されたりする可能性があり、そのたびに IP アドレスが変わります。そのため Pod の IP アドレスを直接指定して安定的にアクセスすることは基本的には難しいです。

Service を利用すると、紐づいている Pod が増減したとしても安定してアクセスすることができます。
また、Service は紐づいている複数の Pod に対してロードバランシングも行ってくれます。

## ではどのように実現されているのか?

上では抽象レベルの高い Service の役割を説明しました。
実際は Service の役割と設定項目さえ知っていれば、利用する分にはあまり困らないかもしれませんが、ここからはドキュメントを読んでわかった、Service の大まかな実態について説明します。

今回はデフォルト実装の iptables を利用した Service の実装かつ、Cluster 内部の通信(ClusterIP への通信)について説明します。
登場人物は、Service、EndpointSlice、kube-proxy、iptables がいます。

まず、iptables とは Linux においてパケットのフィルタリングや NAT などを行うための仕組みです。これは k8s の仕組みではなく、Linux の仕組みで、Pod に対してではなく、Node 自体に対して設定されます。
そして、k8s を利用すると、Service の IP アドレスに対して、その Service に紐づいている Pod の IP アドレスに転送するためのルールが iptables に追加されます。

以下は、Service の IP アドレスが `xxx.xxx.xxx.xxx`で、その Service に`aaa.aaa.aaa.aaa`と`bbb.bbb.bbb.bbb`の Pod が紐づいている場合の iptables のルールの一部です。

- Chain PREROUTING (policy ACCEPT 105K packets, 15M bytes)

  | pkts | bytes | target        | prot | opt | in  | out | source    | destination |
  | ---- | ----- | ------------- | ---- | --- | --- | --- | --------- | ----------- |
  | 28M  | 3597M | KUBE-SERVICES | all  | --  | \*  | \*  | 0.0.0.0/0 | 0.0.0.0/0   |

- Chain KUBE-SERVICES (2 references)

  | pkts | bytes | target                    | prot | opt | in  | out | source    | destination     |
  | ---- | ----- | ------------------------- | ---- | --- | --- | --- | --------- | --------------- |
  | 0    | 0     | KUBE-SVC-XXXXXXXXXXXXXXXX | tcp  | --  | \*  | \*  | 0.0.0.0/0 | xxx.xxx.xxx.xxx |

- Chain KUBE-SVC-XXXXXXXXXXXXXXXX (1 references)

  | pkts | bytes | target                    | prot | opt | in  | out | source    | destination |
  | ---- | ----- | ------------------------- | ---- | --- | --- | --- | --------- | ----------- |
  | 0    | 0     | KUBE-SEP-AAAAAAAAAAAAAAAA | all  | --  | \*  | \*  | 0.0.0.0/0 | 0.0.0.0/0   |
  | 0    | 0     | KUBE-SEP-BBBBBBBBBBBBBBBB | all  | --  | \*  | \*  | 0.0.0.0/0 | 0.0.0.0/0   |

- Chain KUBE-SEP-5YYM5B6SNLNZHU7T (1 references)

  | pkts | bytes | target | prot | opt | in  | out | source    | destination |
  | ---- | ----- | ------ | ---- | --- | --- | --- | --------- | ----------- |
  | 0    | 0     | DNAT   | tcp  | --  | \*  | \*  | 0.0.0.0/0 | 0.0.0.0/0   |

- iptables-save で保存されている DNAT のルール
  ```
  -A KUBE-SEP-AAAAAAAAAAAAAAAA -p tcp -m tcp -j DNAT --to-destination aaa.aaa.aaa.aaa:80
  ```

流れとしては、PREOUTING チェインで KUBE-SERVICES チェインにパケットが渡され、KUBE-SERVICES チェインで Service の IP アドレスに対してのルールが適用され、KUBE-SVC-XXXXXXXXXXXXXXXX チェインにパケットが渡され、KUBE-SVC-XXXXXXXXXXXXXXXX チェインで Service に紐づいている Pod の IP アドレスに転送するためのルールが適用されるという流れです。
また、今回のように Service に紐づいている Pod が複数ある場合、KUBE-SVC-XXXXXXXXXXXXXXXX チェインで複数の Pod に対してルールが適用され、ロードバランシングが行われます。

そしてこの iptables の操作を行うのが kube-proxy です。

kube-proxy は、kube-api-server から Service, EndpointSlice を監視しており、Service、EndpointSlice に変更があった場合に iptables のルールを追加、削除します。
また、kube-proxy は DaemonSet として各 Node に配置されているため、各 Node に対して漏れなく iptables のルールが追加できます。

EndpointSlice は Service に紐づいている Pod の IP アドレスを保持するリソースです。
上の例だと以下のような設定になっており、Service に紐づいている Pod の IP アドレスが保持されています。

```yaml
addressType: IPv4
apiVersion: discovery.k8s.io/v1
endpoints:
  - addresses:
      - aaa.aaa.aaa.aaa
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: aaa-node
    targetRef:
      kind: Pod
      name: xxx-xxx-xxx-xxx-aaa
      namespace: xxx-ns
      uid: xxx-xxx-xxx-xxx-aaa
  - addresses:
      - bbb.bbb.bbb.bbb
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: bbb-node
    targetRef:
      kind: Pod
      name: xxx-xxx-xxx-xxx-bbb
      namespace: xxx-ns
      uid: xxx-xxx-xxx-xxx-bbb
kind: EndpointSlice
metadata:
  creationTimestamp: "2025-01-21T12:44:28Z"
  generateName: xxx-xxx-xxx-xxx-
  generation: 1
  labels:
    endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
    kubernetes.io/service-name: xxx-service
  name: xxx-service-2g87z
  namespace: xxx-ns
  ownerReferences:
    - apiVersion: v1
      blockOwnerDeletion: true
      controller: true
      kind: Service
      name: xxx-service
      uid: xxx-xxx-xxx-
  resourceVersion: "7323594"
  uid: yyyy-yyyy-yyyy-yyyy
ports:
  - name: http-xxx-xxx
    port: 80
    protocol: TCP
```

つまり、まとめると

Service や EndpointSlice が作成、変更されると、全ての Node の kube-proxy が自身の Node の iptables を更新し、Service の IP アドレスに対して、その Service に紐づいている Pod の IP アドレスに転送するためのルールを追加することで、Pod へのアクセスを可能にしています。

ちなみに、DNS を使って Pod の名前解決をせず、kube-proxy のような proxy の仕組みを利用する理由として、公式ドキュメントに以下のような記載がありました。[^2]

> There is a long history of DNS implementations not respecting record TTLs, and caching the results of name lookups after they should have expired.
> Some apps do DNS lookups only once and cache the results indefinitely.
> Even if apps and libraries did proper re-resolution, the low or zero TTLs on the DNS records could impose a high load on DNS that then becomes difficult to manage.

おそらく、Pod のような短命なリソースに対して DNS のキャッシュが残ってしまうと、Pod の IP アドレスが変わった場合にアクセスできなくなるため、kube-proxy のような proxy の仕組みを利用しているのだと思います。

ただ、Service 自体は安定した IP アドレスを持つので、Service の IP アドレス自体は k8s 上の DNS を使って名前解決をします。[^3]よく利用されるのは CoreDNS というものです。
この CoreDNS にもキャッシュの設定がありますが、Service 自体の IP アドレスは安定しているため問題ないのでしょう。

## 終わりに

今回はざっくりと Service の実装について説明しました。
この記事を書いている間にも iptables の仕組みの詳細などを調査し理解を深められたので、よかったです。
ただ、今回の説明だけでは、Pod の ClusterIP を手に入れることはできるものの、その先の通信の仕組みについてはまだまだ理解が足りないと感じました。
このあたりは CNI のプラグインによって実装されているため、実装によって異なりますが、CNI そのものの規格などを調べるのも良いなと思っています。

引き続き、k8s に詳しくなっていければなと思います。

ここまで読んでいただき、ありがとうございました。

[^1]: 参照 https://kubernetes.io/ja/docs/concepts/services-networking/service/
[^2]: 参照 https://kubernetes.io/docs/reference/networking/virtual-ips/
[^3]: 実は Pod も DNS 名があるみたいです。ただ、ip アドレスがプレフィックスにあるものなので、どういう時に使うのか全くわかっていません。https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/