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レベルでの負荷分散が可能となる。
ExternalIP
ClusterIPの一種。特定のノードのIP(external IP)に対する通信をClusterIPに転送する。
前提としてクライアントからexternal IPに対して疎通できる必要があるためユースケースはかなり限られている。
必要になる背景
// あとでよむ
■ NodePort
任意のノードのIPとポートを指定してクラスタ外部から内部へのアクセスを実現するリソース。
なお、NodePortを作成するとClusterIPも内部的に構成される。
ExternalIPと異なりIPを指定する必要がなく、全てのノードへの通信は内部で転送される。
実際にはそのまま使うことはほぼない。
必要になる背景
ClusterIPによりコンテナでホストするアプリケーションへのアクセスを可能にした。
しかし、ClusterIPが有効なのはクラスタ内の通信のみである(=結局外部からアクセス可能なIPを持ってないため)。
Podのコンテナに外部(例えばホストPC)からアクセスしようとしてもホストのIPレンジとクラスタ内のIPレンジが異なるためうまくいかない。
この問題を解決するのがNodePort Serviceである。
NodePort Serviceは、各Podが稼働するノードのグローバルIPと全ノードで共通の一意なポート番号でClusterIPへのアクセスを提供する。
■ LoadBalancer
NodePortへのリクエストの負荷分散を行うL4ロードバランサ。
なお、LoadBalancerを作成するClusterIP、NodePortも内部的に構成される。
※ ただし、単体では利用できず、外部のロードバランサと連携させるプラグイン(Controller)が必要となる。
kubernetesでサービスを外部に公開するときにtype: LoadBalancerを使います。しかしこれはAWSやGCP、Azureといったパブリッククラウドのみで現在は扱えるものでオンプレミスの環境では利用できません。
MetalLBはオンプレミスでもtype: LoadBalancerを扱えるようにする、kubernetes上で動作するOSSのアプリケーションです。
必要になる背景
NodePortで外部からのアクセスは可能にはなったものの、ノード自体もスケールするのでIPは固定ではない。それゆえNodePortもアクセスが安定することはない。
これはCulsterIPと同様の考え方で各ノードのNodePortをバランシングするプロキシ(Load Balancer Service)を立てることでこの問題を解決する。
これにより、クライアントはNodePortのIPを直接知る必要がなく、静的なLoad BalancerのIPでアクセスすることができる。
なお、クラウドプロバイダのマネージドな環境でk8sを利用する場合、Load Balancer作成時にDNSが割り振られるのでそのURLでアクセスすることも可能。
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)のロードバランサとして動作します.
Kubernetes Ingress
Kubernetes Ingress を作成すると、LBC は AWS Application Load Balancer (ALB) を作成します。Ingress リソースに適用できる注釈を確認してください。
LoadBalancer タイプの Kubernetes サービス
LoadBalancer タイプの Kubernetes サービスを作成すると、LBC は AWS Network Load Balancer (NLB) を作成します。サービスリソースに適用できる注釈を確認してください。
NLBのターゲット(AWS)
NLBのターゲットにはインスタンスかIP(もしくはALB)を指定できる。
- インスタンスをターゲットとした場合、NLBはトラフィックをNodePortにルーティングする。
- 一方でIPをターゲットにするとClusterIPやNodePortなくPodに直接ルーティングする。
また以下は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にトラフィックを転送することが可能。
// あとでよむ
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へバランシングされる。
Service内部のアーキテクチャ
Serviceの実体は書くノードで動くkube-proxy
とiptables
である。
kube-proxy
はコントロールプレーンのkube-apiserver
を監視し、Serviceの定義に従いノードのiptables
を書き換える。ポッドのスケーリングによるバランシングの変更もiptables
を書き換えることで成り立っている。
Service(ClusterIP
、NodePort
)に関わる部分のみ切り出すと構造は以下のようになってる。(iptablesモード)
Linux Bridge
でPodの仮想NICとホスト上のネットワーク名前空間の間でパケットを転送する。
なお、上記の絵において、ClusterIP
とNodePort
に差はない。クラスタ内の別のPodからのパケットであろうと、クラスタ外からのパケットであろうと、いずれもノードからすれば物理NICに届いたパケットを適切なPodへルーティングすることになる。
kube-proxy
DaemonSet としてデプロイされ(1ノードに1ポッド立ち上がる)、kube-apiserver
を監視し、Service/Endpoints/EndpointSliceの変更を取り込みiptables
に反映する。
これは特権コンテナであり、ノードのiptables
を書き換え可能となっている。
参考
- 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/