[Kubernetes] ServiceとIngressの機能とその実体

# Servcie
> 全て同じ機能を提供する、クラスター内のどこかで実行するPodの論理的な集合を定義した抽象物です。

`どこか`っていう表現が結局よくわからなかったけどクラスタ内の各ノードで稼働する`kube-proxy`ポッド(と、それによって適宜更新される`iptables`)の集合ということと解釈した。
物理NICにパケットが届いた際に`iptables`を元に評価されて宛先のポッドへ振り分けられる。

## 機能概要
K8sにおいてノードやPodは常にあるものではなくスケールしたり不具合があれば取り替えられるものであるため常に同じIPアドレスが割り当てられるとは限らない。ServiceはノードやPodへのアクセスをプロキシし静的なエンドポイントを提供するもの。

## Serviceの3つのタイプ
- ClusterIP
- NodePort
- LoadBalancer

他にもいくつかあるがここでは深掘りしない。

## ■ ClusterIP
クラスタ内でポッドへのアクセスを分散するL4ロードバランサ。

### 必要になる背景
例えば、あるPodでホストするアプリケーションにクラスタ内の別のPodからアクセスしたい場合(Eg. frontendコンテナを並べたPodからbackendコンテナを並べたPodにアクセスしたいとした場合)、これらはクラスタ内の同一のネットワークにいるので互いにPodに割り当てられたIPアドレスでアクセスすることが可能。

しかし、これで運用していくのは辛い。
というのも、Podは永続的なものではなくスケールしたり再起動する際に同じIPアドレスが割り当てられるとは限らないため。
つまりIPは変わって突然アクセスできなくなる可能性がある。

そこで、Serviceというリソースが有用。
IPアドレスを直接知らなくてもアクセスできるように静的なIPを持ったプロキシ(ClusterIP Service)を置いてあげれば、クライアントは背後のPodのIPが何かは気にする必要がなく、常に静的なIPでアクセスできる。
ClusterIP Serviceは後ろのPodのIPの変化を理解して適切にPodにリクエストを転送してくれる。またこの構成によりL4レベルでの負荷分散が可能となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/48559f5a-5423-a555-6e65-a312f75159ec.png)

### ExternalIP
ClusterIPの一種。特定のノードのIP(external IP)に対する通信をClusterIPに転送する。
前提としてクライアントからexternal IPに対して疎通できる必要があるためユースケースはかなり限られている。 

https://blog.framinal.life/entry/2021/03/17/185546

### 必要になる背景 
// あとでよむ

https://blog.framinal.life/entry/2020/04/16/022042

## ■ NodePort
任意のノードのIPとポートを指定してクラスタ外部から内部へのアクセスを実現するリソース。
なお、NodePortを作成するとClusterIPも内部的に構成される。
ExternalIPと異なりIPを指定する必要がなく、全てのノードへの通信は内部で転送される。
実際にはそのまま使うことはほぼない。

### 必要になる背景
ClusterIPによりコンテナでホストするアプリケーションへのアクセスを可能にした。
しかし、ClusterIPが有効なのはクラスタ内の通信のみである(=結局外部からアクセス可能なIPを持ってないため)。
Podのコンテナに外部(例えばホストPC)からアクセスしようとしてもホストのIPレンジとクラスタ内のIPレンジが異なるためうまくいかない。

この問題を解決するのがNodePort Serviceである。
NodePort Serviceは、各Podが稼働するノードのグローバルIPと全ノードで共通の一意なポート番号でClusterIPへのアクセスを提供する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/3f6bae3f-de47-f0ab-1e5d-5d5fded236fe.png)

## ■ LoadBalancer 
NodePortへのリクエストの負荷分散を行うL4ロードバランサ。
なお、LoadBalancerを作成するClusterIP、NodePortも内部的に構成される。
※ ただし、単体では利用できず、外部のロードバランサと連携させるプラグイン(Controller)が必要となる。

> kubernetesでサービスを外部に公開するときにtype: LoadBalancerを使います。しかしこれはAWSやGCP、Azureといったパブリッククラウドのみで現在は扱えるものでオンプレミスの環境では利用できません。
> MetalLBはオンプレミスでもtype: LoadBalancerを扱えるようにする、kubernetes上で動作するOSSのアプリケーションです。

https://blog.framinal.life/entry/2020/04/16/022042


### 必要になる背景
NodePortで外部からのアクセスは可能にはなったものの、ノード自体もスケールするのでIPは固定ではない。それゆえNodePortもアクセスが安定することはない。
これはCulsterIPと同様の考え方で各ノードのNodePortをバランシングするプロキシ(Load Balancer Service)を立てることでこの問題を解決する。
これにより、クライアントはNodePortのIPを直接知る必要がなく、静的なLoad BalancerのIPでアクセスすることができる。
なお、クラウドプロバイダのマネージドな環境でk8sを利用する場合、Load Balancer作成時にDNSが割り振られるのでそのURLでアクセスすることも可能。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/920a0bb6-0e7f-8b0d-5f46-6f137d154998.png)

## Controllerについて

極めてわかりやすい。
https://qiita.com/yuanying/items/704a173033410d882eea

LoadBalancerタイプのServiceを作成するとこんな感じで`External IP`が作成されない。これはコントローラと呼ばれるコンポーネント(実体としてはPod)が存在しないためexternal IPまたはDNSを割り当てて外部からアクセス可能にしてくれる存在がいないため。

```
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx     LoadBalancer   10.100.21.216   <pending>    8080:30616/TCP   20s
```

AWS EKSの環境下ではAWSが`AWS Load Balancer Controller`をアドオンとして提供しており、ServiceやIngressリソースを指すAWSロードバランサをプロビジョニングしてくれるためこれらが割り当てられる。GKEの場合はCloudLoadBalancerが自動的に利用される(おそらく同様に何かしらのコントローラが動いてる)。

> GKEやAWSなどのクラウドプロバイダではLoadBalancerタイプをデフォルトでサポートしており, GKEの場合は Cloud Load Balancing が自動的に利用され, L4 (TCP/UDP)のロードバランサとして動作します.

https://sff8.hatenablog.com/entry/2018/10/27/234757

> Kubernetes Ingress
Kubernetes Ingress を作成すると、LBC は AWS Application Load Balancer (ALB) を作成します。Ingress リソースに適用できる注釈を確認してください。

> LoadBalancer タイプの Kubernetes サービス
LoadBalancer タイプの Kubernetes サービスを作成すると、LBC は AWS Network Load Balancer (NLB) を作成します。サービスリソースに適用できる注釈を確認してください。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/aws-load-balancer-controller.html

### NLBのターゲット(AWS)

NLBのターゲットにはインスタンスかIP(もしくはALB)を指定できる。
- インスタンスをターゲットとした場合、NLBはトラフィックをNodePortにルーティングする。
- 一方でIPをターゲットにするとClusterIPやNodePortなくPodに直接ルーティングする。

https://blog.framinal.life/entry/2021/03/17/185546#%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%81%AE%E5%A0%B4%E5%90%88

また以下はIngressの説明だがほぼ同じことと予想
https://blog.linkode.co.jp/entry/2020/06/26/095917

### ルートベースクラスタとVPCネイティブクラスタ (Google Cloud)

同様に、
- GKE ルートベースクラスタではVPCとクラスタ内のネットワークが独立する。
- GKE VPCネイティブクラスタではVPCとネットワークを共有する。この場合、 Net work Endpoint Group (NEG) を利用して直接Podにトラフィックを転送することが可能。

// あとでよむ

https://medium.com/google-cloud-jp/gke-network-basic-8a22be15517d


## Ingress
L7ロードバランサでありServiceではない独立したコンポーネント。
ServiceのLBと同様にそれ単体では動かないのコントローラが必要。

### 必要になる背景
LoadBalancerタイプのServiceにより外部からコンテナアクセスが可能になったものの、この構成ではサービス1つにつき1つのLBが必要になる。
クラスタ内で複数のマイクロサービスを展開する場合その数だけLBが必要になるのは非効率であると言える。
こういう時、一般にURLのパスに基づいて振り分けができればいいが、ServiceのLBはL4ロードバランサであるが故にHTTPリクエストの中身を解釈出来ない。
この問題を解決したのがIngress。
これはServiceのリソースではなくServiceと組み合わせることで効果を発揮する。Ingress + NodePortの構成を取ることで実現可能。

### オンプレミスとクラウドのアーキテクチャ比較

オンプレミスでIngressによるバランシングを実現する場合、クラウドとは異なる。
クラウドリソースとしてのLBを内部に構築することになるのでLBで受けたトラフィックはNodePort → ClusterIP → Podと渡る。つまり、ServiceのLBとほとんど同じように振る舞うことができる。

#### EKSでのアーキテクチャ

前項の"Controllerについて"でも触れたようにIngressではL7のロードバランサがプロビジョニングされる。

> > Kubernetes Ingress
Kubernetes Ingress を作成すると、LBC は AWS Application Load Balancer (ALB) を作成します。Ingress リソースに適用できる注釈を確認してください。
>
>https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/aws-load-balancer-controller.html

#### GKEでのアーキテクチャ

`Ingress Controller`がK8sのクラスタ外にCloud Load Balancerを構築する。

#### オンプレのアーキテクチャ

一方でオンプレミスの場合は自身のクラスタ内で`Nginx Ingress Controller`のようなコントローラを稼働させ、Ingress用のPod(`Nginx Pod`)をクラスタ内に展開する。
このPodも当然IPは可変なため従来通りClusterIPやNodePortを割り振り、外部からアクセスできるようにしてあげる必要がある。
すなわち、アプリケーションのPodに対してトラフィックがわたるまでに、一度`Nginx Pod`宛のNodePort、ClusterIPを経由し、`Nginx Pod`のIngressでその先の各アプリケーションPodのClusterIPへバランシングされる。

https://blog.framinal.life/entry/2021/03/17/185546#External-IP

https://hyoublog.com/2020/06/30/kubernetes-ingress/


----

## Service内部のアーキテクチャ
Serviceの実体は書くノードで動く`kube-proxy`と`iptables`である。
`kube-proxy`はコントロールプレーンの`kube-apiserver`を監視し、Serviceの定義に従いノードの`iptables`を書き換える。ポッドのスケーリングによるバランシングの変更も`iptables`を書き換えることで成り立っている。

Service(`ClusterIP`、`NodePort`)に関わる部分のみ切り出すと構造は以下のようになってる。(iptablesモード)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/e1120bd2-6f8a-4d73-45e2-7d0f8880c137.png)

`Linux Bridge`でPodの仮想NICとホスト上のネットワーク名前空間の間でパケットを転送する。

https://speakerdeck.com/hhiroshell/kubernetes-network-fundamentals-69d5c596-4b7d-43c0-aac8-8b0e5a633fc2?slide=15

https://markunet.github.io/blog/Intro_Linux_interfaces_JP/

なお、上記の絵において、`ClusterIP`と`NodePort`に差はない。クラスタ内の別のPodからのパケットであろうと、クラスタ外からのパケットであろうと、いずれもノードからすれば物理NICに届いたパケットを適切なPodへルーティングすることになる。

### kube-proxy
DaemonSet としてデプロイされ(1ノードに1ポッド立ち上がる)、`kube-apiserver`を監視し、Service/Endpoints/EndpointSliceの変更を取り込み`iptables`に反映する。
これは特権コンテナであり、ノードの`iptables`を書き換え可能となっている。

https://qiita.com/nozmiz/items/9a74433258a79be26c36

https://qiita.com/kouares/items/94a073baed9dffe86ea0

---

参考

- https://blog.framinal.life/entry/2021/03/17/185546
- https://hyoublog.com/2020/05/20/kubernetes-nodeport-service/
- https://zenn.dev/keigo_hirohara/articles/26885f9d9251af#nodeport%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84
- https://www.ios-net.co.jp/blog/20230621-1179/
- https://kubernetes.io/ja/docs/tutorials/services/connect-applications-service/