Kubernetesでアプリを動かせても、「外からどうやってPodにアクセスするのか」でつまずくことは多いです。ServiceとIngressはどちらも通信に関わりますが、守備範囲が違い、混ざると通信経路を説明できなくなります。
この記事は、『Kubernetesのデプロイ概要まとめ』で見たとおり「Podは作り直されるたびにIPが変わる」という前提から出発し、なぜPodに直接アクセスしないのか、Serviceが何を安定させ、Ingressが何を担うのかを、通信経路として一本につなぎます。
- Podに直接アクセスしない理由
- ServiceとIngressの役割の違い
- 外部リクエストがPodに届くまでの通信経路
- GKEでIngressがGCPのLoad Balancerに対応する話
前提知識:Kubernetesの通信リソースとは
『Kubernetesの概要・全体像まとめ』では、KubernetesをCluster・Control Plane・Node・Pod・Containerの関係で整理しました。そこで出てきたPodは、コンテナを動かす最小単位です。
ただし、Podはアプリケーションを動かす場所であって、安定した通信先そのものではありません。『Kubernetesのデプロイ概要まとめ』で見たように、Podは落ちたり更新されたりすると新しいPodとして作り直されます。
そこでKubernetesでは、Podへ通信を届けるために、Podとは別のリソースを使います。この記事で扱うServiceとIngressは、その通信経路を作るためのリソースです。
flowchart TB
Service["Service(Podの安定した宛先)"]
Ingress["Ingress(外部からの入口)"]
subgraph Cluster["Cluster"]
subgraph Node["Node"]
Pod["Pod(コンテナが動く場所)"]
end
end
Ingress --> Service --> Pod
ServiceはPodの前に立つ安定した宛先で、Ingressはクラスタ外からのリクエストを受ける入口です。つまり、ServiceはKubernetesの「どこかの機械」を指す名前ではなく、変わりやすいPodに通信を届けるためのKubernetesリソースです。
Service: Podへの安定した宛先
Serviceは、変わりやすいPodに直接アクセスしなくてよいように、Podの前に置く安定した宛先です。まず、なぜPodを直接宛先にしないのかを押さえます。
Podに直接アクセスしない理由
Podは使い捨ての単位です。落ちれば新しいPodとして作り直され、そのときIPアドレスが変わります。ローリングアップデートでも、Podは新しいものに入れ替わります。
つまりPodのIPは、いつ変わってもおかしくない一時的なものです。もし呼び出す側がPodのIPを直接指定していたら、Podが作り直されるたびに宛先が無効になり、通信が切れます。さらにPodが複数あるとき、どのPodに振り分けるかも自分で管理しなければなりません。
この「宛先が安定しない」「複数Podへの振り分けを自分でやる必要がある」という問題を解くのがServiceです。
ServiceがPodをまとめて振り分ける
Serviceは、複数のPodをまとめて1つの安定した宛先として見せるリソースです。個々のPodのIPは変わっても、Serviceには変わらない名前とアドレスが割り当てられます。
flowchart TB Client["呼び出す側"] SVC["Service(安定した宛先)"] P1["Pod"] P2["Pod"] P3["Pod"] Client -->|"Service名でアクセス"| SVC SVC -->|"振り分け"| P1 SVC -->|"振り分け"| P2 SVC -->|"振り分け"| P3
呼び出す側はService名にアクセスするだけでよく、Serviceが背後の生きているPodへ振り分けます。Podが増減しても、落ちてIPが変わっても、Serviceを見ている側は影響を受けません。
Serviceは主にクラスタ内の通信で使われます。「注文サービスからユーザーサービスを呼ぶ」とき、相手のPodではなくユーザーサービスのServiceを宛先にする、という使い方です。振り分けはIPやポート単位(L4)で行われます。
一方で、Serviceだけでは「外部から特定のパスのリクエストを、このサービスに送る」といったWeb向けのルーティングは扱えません。ここでIngressが登場します。
Ingress: 外部からの入口
Ingressは、クラスタの外から来るHTTP/HTTPSリクエストの入口をまとめ、URLのパスやホスト名に応じて適切なServiceへ振り分けるリソースです。
たとえば次のようなルーティングを1か所で定義できます。
/api/usersへのリクエストはユーザーサービスのServiceへ/api/ordersへのリクエストは注文サービスのServiceへ- HTTPSの証明書もIngressでまとめて扱う
Serviceがクラスタ内の安定した宛先とL4の振り分けを担うのに対し、Ingressは外部公開とURLベース(L7)のルーティングを担います。両者は競合せず、Ingressの振り分け先がServiceになる、という積み重ねの関係です。
外部リクエストがPodに届くまでの通信経路
ここまでの登場人物を通信の順につなぐと、外部リクエストがPodに届くまでは次の経路になります。
flowchart LR User["User"] Ing["Ingress"] UserSvc["users Service"] OrderSvc["orders Service"] UserPod1["users Pod"] UserPod2["users Pod"] OrderPod1["orders Pod"] OrderPod2["orders Pod"] User --> Ing Ing -->|"/api/users"| UserSvc Ing -->|"/api/orders"| OrderSvc UserSvc --> UserPod1 UserSvc --> UserPod2 OrderSvc --> OrderPod1 OrderSvc --> OrderPod2
- User
- 外部の利用者からのリクエスト
- Ingress
- 入口としてリクエストを受け、URLに応じてServiceを選ぶ。HTTPのパスやホスト名を見て振り分けるので、L7ルーティングと呼ばれる
- Service
- 選ばれたサービスの安定した宛先として、同じ役割を持つ複数のPodへ振り分ける。HTTPのパスではなく、宛先IPやポートをもとにPodへ渡すので、L4の振り分けと呼ばれる
- Pod
- 実際に処理するコンテナ
この経路が分かると、「Podが入れ替わってもUserは同じURLでアクセスし続けられる」理由が説明できます。変わりやすいPodを、ServiceとIngressという安定した層で包んでいるためです。
参考: GKEではIngressがGCPのLoad Balancerに対応する
Ingressは「こうルーティングしたい」という宣言で、それを実現する実体は環境によって変わります。GKE(GCPのマネージドKubernetes)では、Ingressを作ると実体としてGCPのLoad Balancerが自動的に構成されます。
- Ingressを作ると、GCP側にApplication Load Balancerが用意される
- ServiceはLoad Balancerそのものではなく、Ingressの振り分け先として使われる
これにより、Kubernetesの世界で「Ingressで公開する」と書いた内容が、GCPのネットワーク層で実際のトラフィック分散として動きます。GCPのLoad Balancerそのものの種類や構成は『GCPのLoad Balancer・Backend Service・MIG・VMの関係を整理する』で扱っています。
なお、Ingressの後継として、より柔軟なルーティングを宣言できるGateway APIも使われるようになっていますが、「外部からの入口をServiceへ振り分ける」という役割の位置づけは同じです。
まとめ
ServiceとIngressは、変わりやすいPodを安定した層で包み、外部からの通信を届けるための仕組みです。要点は以下です。
- Podは作り直されるたびにIPが変わるため、直接名指しでアクセスしない
- Serviceは複数のPodをまとめた安定した宛先で、クラスタ内通信とL4の振り分けを担う
- Ingressは外部からの入口で、URLに応じてServiceへ振り分けるL7ルーティングを担う
- 通信経路は User → Ingress → Service → Pod で、変わりやすいPodを安定層が包む構造
- GKEではIngress/GatewayがGCPのLoad Balancerに対応し、宣言がGCPのトラフィック分散として実現される