Kubernetes のサービスとは (2) リソース、API Server、コントローラー

# この記事について

[Kubernetes のサービスとは (1) 大雑把な理解](https://qiita.com/nozmiz/items/a97406d89ad440655eb8)の続き。
Service/Endpoints/EndpointSlice リソースと、それを処理する API Server、コントローラーについてまとめる。

内容は大体こんな感じ：
- Service リソースを作ると、ClusterIP や NodePort の割り当てが決まる
- Service リソースを作ると、コントローラーが Endpoints/EndpointSlice リソースを作る
- 実際のネットワーク設定は kube-proxy や coredns が行う

# リソースと API Server
初めに、以下のような Service のマニフェストを用いて `kubectl apply` を実行したとする。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: http
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
```

API Server はこのリクエストを受けると Service リソースを作成して etcd データベースにその情報を永続化する。これはどのリソース作成でも同じだが、Service リソースの場合は単にマニフェストに定義された情報を保存するだけではなく、次のようなパラメータの値が自動で割り当てられる。
- ClusterIP
- NodePort

以下は、マニフェストを実際に適用した後の状態。
```bash
$ kubectl get service nginx -o yaml
apiVersion: v1
kind: Service
...
spec:
  clusterIP: 10.103.213.221  ★ClusterIP が自動割り当てされる
  clusterIPs:
  - 10.103.213.221  ★ClusterIP が自動割り当てされる
  ...
  ports:
  - name: http
    nodePort: 30109  ★NodePort が自動割り当てされる
    port: 8080
    protocol: TCP
    targetPort: 80
  type: NodePort
  ...
```

**なお、あくまで割り当てるべき ClusterIP や NodePort の値が決まっただけで、これだけではネットワーク設定は変更されてないことに注意。**

これら自動割り当て範囲は API Server の以下の起動時の引数で指定できるが、通常はマネージドのクラスター構築時か、自前で kubeadm でクラスター構築する際に指定するはず。
```bash
# Service Network の IP アドレス範囲。例えば 10.96.0.0/12 といった範囲が指定される。
--service-cluster-ip-range
# NodePort の割り当て範囲の指定(デフォルトは 30000-32767)
--service-node-port-range
```

まずはこれで Service リソースが作成できた。一応全体を俯瞰するために、ここまでの図を掲載。
※ NodePort や Service の ClusterIP って何、という話は別の記事に記載予定。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539081/22023d61-6612-80ca-9bb2-c9ce5f9be76d.png)

# リソースとコントローラー

Service は単に Kubernetes の REST API のリソースであり、端的に言えば etcd というデータベースに保存されるデータに過ぎない。データだけでは役立たないので、リソースが作成・更新・削除されたときにアクションを起こして何かを作ったり構成したりしてくれる Kubernetes のコンポーネントが API Server 以外にいて、それは Controller と呼ばれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539081/b9905d96-1206-1cbc-4157-cb1f43b4fa37.png)

Controller は概ね、Deployment Controller や Job Controller のようにリソースの種類ごとに存在し、自身が担当するリソースの変更を API Server の Watch API で監視し、変更があったらアクションを起こす。

これらの Controller は実際には単一バイナリとしてビルドされて Kubernetes 上で動いている(Controller Manager というコンポで上記の図では `c-m` と書かれてる)。その実体は master ノードで動作してる以下の Pod(※ API Server もそうだが、Static Pod という特殊な形態の Pod であり、kubelet から直接起動されている)。

```bash
$ kubectl get pod -n kube-system
NAME                            READY   STATUS    RESTARTS       AGE
coredns-565d847f94-cqhfz        1/1     Running   9 (31h ago)    21d
coredns-565d847f94-dsg54        1/1     Running   9 (31h ago)    21d
etcd-master                     1/1     Running   9 (31h ago)    21d 
kube-apiserver-master           1/1     Running   9 (31h ago)    21d  ★ こっちは API Server
kube-controller-manager-master  1/1     Running   9 (31h ago)    21d  ★ これが Controler Manager
kube-proxy-hrpwp                1/1     Running   9 (31h ago)    21d
kube-proxy-vqsk7                1/1     Running   8 (31h ago)    21d
kube-scheduler-master           1/1     Running   9 (31h ago)    21d
...
```


この辺りは一般的な話なのでいいとして、では Service リソースの場合は Service Controller がいるかと思いきや、実は Endpoints Controller または EndpointSlice Controller が担当している。

# Endpoints と EndpointSlice
Endpoints は Service を作成するとそのペアとして自動で作成される(Endpoints Controller が作成する)リソースで、Load Balancer の背後にある Pod の一覧情報を管理する。EndpointSlice は役割は同じだが、非常に多くの Pod があるような場合に Endpoints ではスケーラビリティの問題が出るため、それを解決するために登場した後発のリソース。

以下は nginx という名前の Deployment(レプリカ数=2)と Service を作った場合の例。Endpoints と EndpointSlice が自動で作成されているのと、それらが Pod の IP/Port の一覧を持っていることが分かる。

```bash
# Service
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    21d
nginx        ClusterIP   10.108.58.191   <none>        8080/TCP   158m   ★ nginx Service

# Endpoints
$ kubectl get endpoints
NAME         ENDPOINTS                   AGE
kubernetes   10.0.0.11:6443              21d
nginx        10.44.0.2:80,10.44.0.3:80   159m   ★nginx Service のペアになる Endpoints (Pod の IP/Port 一覧を保持）

# EndpointSlice
$ kubectl get endpointslice
NAME          ADDRESSTYPE   PORTS   ENDPOINTS             AGE
kubernetes    IPv4          6443    10.0.0.11             21d
nginx-fjqqr   IPv4          80      10.44.0.2,10.44.0.3   159m  ★ nginx Service のペアになる EndpointSlice (Pod の IP/Port 一覧を保持）
```

では、どちらのリソースが使われるかというと、現在(Kubernete v1.25)では EndpointSlice のようだ。
これらのリソースを参照するのは kube-proxy だが、下記ソースの該当箇所を見ると kube-proxy がレガシーな user-space モードで動作してない限りは EndpointSlice を使うことになる。

- [kubernetes/cmd/kube-proxy/app/server_others.go:](https://github.com/kubernetes/kubernetes/blob/v1.25.0/cmd/kube-proxy/app/server_others.go#L363)
- [kubernetes/cmd/kube-proxy/app/server.go:](https://github.com/kubernetes/kubernetes/blob/v1.25.0/cmd/kube-proxy/app/server.go#L760)

とはいえ、Endpoints Controller と EndpointSlice Controller はやっていることは大体同じなので、以降ではよりシンプルな Endpoints Controller について記載する。

# Endpoints Controller がやっていること
Endpoints Controller が何をするかと言うと、Service や Pod が変更された場合に、対象 Service のペアとなる Endpoints を作成・更新・削除するのが仕事である。例えば、Service リソースが作成されると、自動でそれに紐づく Endpoints リソースを作成する。

まず、Endpoints Controller は以下のリソースとその変更を監視している。
- Service の作成、更新、削除
- Pod の作成、更新、削除
- Endpoints の削除

そして、上記の変更を検知した場合は以下のようなアクションを起こす。

- 新しい Service が作成された場合
  - Service のセレクターにマッチする Pod の一覧を取得して、それらの IP アドレスを含む Endpoints リソースを作成する
  - その Endpoints の Namespace とリソース名は Service と同じものになる
- Pod に変化があった場合
  - (例) 新しい Pod が作られた、Pod の状態が Running から変わった等々
  - 対象 Pod にマッチする Selector を持つ Service を取得して、それに紐づく Endpoints を更新する。このケースは Pod 数や IP、状態が変わってるので、Endpoints に含まれる Pod の IP アドレス一覧の更新等を行う
    - つまり、Pod の増減やIPの変更、死活を追跡していることになる
- Endpoints を削除した場合
  - Endpoints を再作成する

※ 細かく言うと、Headleass Service という形態があったり、Running じゃない Pod も Endpoints に含めちゃう設定があるなどいろいろがあるが、それらはここでは割愛。

つまり、Endpoints Controller 自身は Endpoints リソースをいじるだけで、ネットワーク設定は何も行わない。

# 結局 Service/Endpoints/EndpointSlice の役割は？

ここまでの話は結局、Service/Endpoints/EndpointSlice という API リソースが作成・更新・削除されるだけであり、通信に関わるクラスター設定に変化はない。つまり [Kubernetes のサービスとは  (1) 大雑把な理解](https://qiita.com/nozmiz/items/a97406d89ad440655eb8)で記載した赤枠部分の構成は行われてない。
※ POD の IP を追跡する情報は作られるけど。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539081/1dd3c3d8-a1c4-6077-cb74-918af947bb43.png)


ではこれらのリソースやその Controller の役割は何かというと、実際にネットワーク構成を行う kube-proxy や coredns といったコンポーネントが参照する情報を提供・メンテすることになる。kube-proxy や coredns はこれらのリソースがあって初めて動くことができる。情報を提供する Producer と、消費(参照)する Consumer のコンポが明確に分かれている Kubernetes らしい分散設計な気がする。

kube-proxy や coredns については続きで記載する。

# 続き
- [Kubernetes のサービスとは (3) kube-proxy とネットワーク設定](https://qiita.com/nozmiz/items/9a74433258a79be26c36)

# 補足・おまけ

- Service の ClusterIP 割り当ては [ソースのここ](https://github.com/kubernetes/kubernetes/blob/v1.25.0/pkg/registry/core/service/storage/alloc.go#L338) で行っている
- ClusterIP と NodePort の割り当て管理データは etcd の `/registry/ranges/serviceips` と `/registry/ranges/servicenodeports` というキーで保存されている。ClusterIP の場合のデータは以下のようなもの。
```json
{
  "kind": "RangeAllocation",
  "apiVersion": "v1",
  "metadata": {
    "creationTimestamp": null
  },
  "range": "10.96.0.0/12",
  "data": "EAAAAAAAAAAAAAA....."
}
```