KubernetesのClusterIP、NodePort、LoadBalancerの違いを理解する

皆さん、こんにちは。技術開発グループのn-ozawanです。
最近、知恵の輪にハマっているのですが、解けた知恵の輪を元に戻せません。

本題です。
Kubernetes、難しいですよね。前回はKubernetesのクラスタにPodを配置する方法、ReplicaSetやDeploymentの違いについて整理しました。前回まではPodを配置しただけで、まだクラスタ外部へ公開されていない状況です。今日はKubernetesのServiceAPIのClusterIP、NodePort、LoadBalancerの違いについて整理します。

ClusterIP、NodePort、LoadBalancerの違い

ゴールまでの道のり

今回のゴールは、ブラウザからクラスタ内に構築したNginxのページにアクセスできること、です。このゴールに辿り着くまでに3つの壁を乗り越えなければなりません。

壁1:どのノードにアクセスすればいいのか?
クラスタは複数のノードで構成されています。そしてNginxが稼働するPodはどのノードに配置されているのか外部からは分かりません。「Kubernetesのみぞ知る」です。

壁2:どのポートにアクセスすればいいのか?
ノードが特定できました。ではどのポートに接続すればよいのでしょうか?それは知っています。HTTPであれば80番ポートですね。なので80番号ポートを外部から接続できるように解放してあげる必要があります。

壁3:どのPodにアクセスすればいいのか?
ノードが特定できました。ポートにも接続できました。では、複数配置されたPodのどれにアクセスすればいいのでしょうか?PodにはKubernetesが自動的にIPアドレスを割り振るのですが、外部からはIPアドレスが分かりません。これも「Kubernetesのみぞ知る」です。

この3つ壁を乗り越えて、ようやく外部に公開できるようになります。

ClusterIP とは

では、まずは壁3「どのPodにアクセスすればいいのか?」から解消していきましょう。壁3の解消にはClusterIPサービスを利用します。ClusterIPは複数のPodを束ねて、1つのエンドポイントを提供するサービスです。ClusterIPにもIPアドレスが割り振られ、このIPアドレスにアクセスすることで、Pod宛ての通信をロードバランシング(負荷分散)してくれます。

Yamlは以下のように記述します。

apiVersion: v1
kind: Service
metadata:
  name: sample-service
spec:
  type: ClusterIP
  selector:
    app: sample-deployment
  ports:
  - protocol: "TCP"
    port: 8080
    targetPort: 80

apiVersionは「v1」固定です。kindは「Service」を指定します。

spec.typeには「ClusterIP」を指定します。spec.selectorには、束ねたいPodのラベルを指定します。spec.selectorで指定したラベルと、同じラベルが付与されたPodに対して、ロードバランシングするようになります。

spec.portsには接続するポートを指定します。portはClusterIPが受け付けるポート番号です。targetPortはClusterIPからPodへの転送先のポート番号になります。上記の場合、ClusterIPは8080番ポートで受け付けた通信を、Podの80番号ポートへ転送します。

metadata.nameにはClusterIPの名前を指定します。Kubernetesにはクラスタ内部でDNSのような機能を備えており、curl -s http://10.10.9.1:8080でもcurl -s http://sample-service:8080でもどちらでもアクセスすることが可能になります。

NodePortとは

ClusterIPはクラスタ内部に設けたエンドポイントでしかありません。つまり、クラスタ外部からClusterIPへアクセスできません。クラスタ外部からアクセスするには、ClusterIPに加えて、ノードのポートを解放してあげる必要があります。つまり、壁2「どのポートにアクセスすればいいのか?」の解消になります。

NodePortでやりたいことはシンプルです。外部と疎通したいポートを指定することです。ただし、注意点としてはNodePortを作成すると、同時にClusterIPも作成される、ということです。

Yamlは以下のように記述します。

apiVersion: v1
kind: Service
metadata:
  name: sample-service
spec:
  type: NodePort
  selector:
    app: sample-deployment
  ports:
  - protocol: "TCP"
    port: 8080
    targetPort: 80
    nodePort: 30080

ClusterIPと違うところは、spec.typeが「NodePort」になっていることと、spec.ports.nodePortが追加されているくらいです。上記のコードでは、すべてのノードに対して、30080番ポートを解放します。

spec.portsnodePortは、解放するノードのポート番号です。porttargetPortはClusterIPで説明した通りです。つまり上記のコードは、ノードの30080番ポートで受信した通信を、ClusterIPの8080番ポートへ転送し、さらにClusterIPはPodの80番ポートへ転送する、という意味になります。

なお、nodePortは30000~32767の間で指定する必要があります。

LoadBalancerとは

実はNodePortだけでも、クラスタに構築されたNginxは外部へ公開されている状態になります。試しにどれか適当なノードに対してブラウザでアクセスすると、Nginxのページが表示されるはずです。仮にNginxが配置されていないノードに対してアクセスしても、ClusterIPにて適切に処理され、Nginxのページが表示されます。

ただし1つ問題があります。仮にアクセスしているノードが故障などした場合、クラスタへのアクセスが出来なくなります。もちろん故障していないノードへアクセスすれば問題なくNginxのページは見れるのですが、実運用には耐えられません。このノードの障害耐性を高めるために、壁1「どのノードにアクセスすればいいのか?」を解消する必要があります。

この壁解消にはLoadBalancerを利用します。LoadBalancerを使用すると、クラウドプロバイダはクラスタに適したロードバランサーを作成してくれます。オンプレでLoadBalancerを使用する場合はMetalLBなどを利用すると良いでしょう。また、LoadBalancerを使用すると、NodePortとClusterIPを同時に作成してくれます。

Yamlは以下のように記述します。

apiVersion: v1
kind: Service
metadata:
  name: sample-service
spec:
  type: LoadBalancer
  selector:
    app: sample-deployment
  ports:
  - protocol: "TCP"
    port: 8080
    targetPort: 80
    nodePort: 30080

spec.typeが「LoadBalancer」になっている以外、NodePortとほぼ同じですね。注意点としては、spec.ports.portはClusterIPのポート番号であるのと同時に、LoadBalancerのポート番号にもなることです。

LoadBalancerとIngress

LoadBalancerサービスとは別に、KubernetesにはIngressという機能があります。LoadBalancerはKubernetesのServiceAPIカテゴリに属しますが、IngressはServiceAPIカテゴリに属しておらず、Ingressとして独立しています。

LoadBalancerとIngress、どちらもクラスタ外部にロードバランサーを構築するという点では同じです。しかし、LoadBalancerはL4の、IngressはL7のロードバランサーが構築される、という違いがあります。

以前、EKSでロードバランサーを構築するという記事では、IngressでALB(Application Load Balancer)を構築しました。もしIngressではなくLoadBalancerを使った場合、ALBではなく、NLB(Network Load Balancer)が構築されることでしょう。

おわりに

Kubernetesで必ず使われるServiceAPIのClusterIP、NodePort、LoadBalancerの違いについて整理しました。ClusterIPは外部公開以外にも、クラスタ内部でのPod間の通信で利用されます。次回はKubernetesの永続化についてお話しします。

ではまた。

Recommendおすすめブログ