はじめに
前回は、Podの個数や更新状態を管理するDeploymentについて学びました。
Deploymentを使うと、複数のPodを維持し、停止したPodを新しく作り直せます。
Deployment
└── ReplicaSet
├── Rails Pod 1
├── Rails Pod 2
└── Rails Pod 3
しかし、Podには次の特徴があります。
- PodごとにIPアドレスが割り当てられる
- Podが再作成されるとIPアドレスが変わる可能性がある
- Podの数はDeploymentによって増減する
それでは、別のPodや利用者は、変わり続けるPodへどのように通信すればよいのでしょうか。
そこで登場するのが Service(サービス) です。
この記事では、次の内容を整理します。
- Serviceとは何か
- なぜPodのIPアドレスへ直接アクセスしないのか
- Serviceが対象のPodを見つける仕組み
- Service・EndpointSlice・Podの関係
-
port・targetPort・containerPortの違い - ClusterIP・NodePort・LoadBalancer・ExternalNameの違い
- Deployment・Service・Ingressの役割の違い
- EKSにおけるServiceの位置づけ
結論
Serviceとは、複数のPodに対して、安定した接続先を提供するKubernetesのリソースです。
利用する側は、入れ替わる個々のPodではなく、Serviceの名前やIPアドレスへアクセスします。
通信する側
↓
Service
├── Rails Pod 1
├── Rails Pod 2
└── Rails Pod 3
Serviceはラベルを使って対象のPodを見つけ、通信をいずれかのPodへ届けます。
Podが削除・再作成されてIPアドレスが変わっても、Serviceの接続先は基本的に変わりません。
再作成前
Service
└── Pod A:10.0.1.10
Pod Aを再作成
↓
再作成後
Service
└── Pod B:10.0.2.25
一言で表すと、次のようになります。
Deployment:Podを作成・維持する
Service :Podへの安定した接続先を提供する
PodのIPアドレスへ直接アクセスする問題
Podには、それぞれIPアドレスがあります。
Rails Pod 1:10.0.1.10
Rails Pod 2:10.0.1.11
Rails Pod 3:10.0.2.20
理論上は、別のPodから10.0.1.10へ直接アクセスできます。
しかし、Podは一時的な存在です。
Deploymentによる更新、障害からの復旧、Nodeのメンテナンスなどで、Podは削除・再作成されます。
古いPod
10.0.1.10
↓ 削除
新しいPod
10.0.2.25
利用する側が古いIPアドレスを記憶していると、新しいPodへ接続できません。
また、Podが複数ある場合、利用する側がすべてのPodのIPアドレスを管理し、接続先を選ぶのも大変です。
利用する側
├── 10.0.1.10を知る必要がある
├── 10.0.1.11を知る必要がある
└── 10.0.2.20を知る必要がある
そこで、Pod群の前にServiceという安定した入口を配置します。
利用する側
└── rails-serviceだけを知ればよい
↓
Rails Pod群
Serviceとは
Serviceは、特定の役割を持つPodの集まりを、1つの接続先として扱うためのリソースです。
たとえば、Rails Podが3個ある場合、Serviceはそれらを1つのバックエンドとして扱います。
rails-service
├── Rails Pod 1
├── Rails Pod 2
└── Rails Pod 3
Serviceは、主に次の役割を持ちます。
- Pod群に対する安定したIPアドレスを提供する
- Service名によるDNSアクセスを提供する
- 対象のPodをラベルから見つける
- 複数のPodへ通信を振り分ける
- Podの追加・削除に合わせて転送先を更新する
Service自体がRailsやNginxを実行するわけではありません。
実際にリクエストを処理するのは、Serviceの先にあるPod内のコンテナです。
ServiceはラベルでPodを見つける
Serviceは、通常selectorを使って対象のPodを選びます。
Podに次のラベルが付いているとします。
labels:
app: rails
Service側では、同じラベルをselectorに指定します。
selector:
app: rails
すると、Serviceはapp: railsを持つPodを対象にします。
Service
selector: app=rails
↓
├── Pod A:app=rails ← 対象
├── Pod B:app=rails ← 対象
└── Pod C:app=sidekiq ← 対象外
Serviceは、Deploymentそのものを選んでいるわけではありません。
Deploymentが作ったPodにラベルが付き、そのラベルをServiceが選びます。
Deployment
└── app=railsのPodを作る
Service
└── app=railsのPodを選ぶ
この2つはPodのラベルを通じて間接的につながっています。
DeploymentとServiceの関係
DeploymentとServiceは、同じPod群に関係しますが、役割が異なります。
Deployment
└── ReplicaSet
├── Rails Pod 1 ←┐
├── Rails Pod 2 ←┼─ Serviceから通信
└── Rails Pod 3 ←┘
| リソース | 役割 |
|---|---|
| Deployment | Podの定義・個数・更新状態を管理する |
| ReplicaSet | 指定された個数のPodを維持する |
| Service | 対象のPodに安定した接続先を提供する |
| Pod | コンテナを実際に動かす |
DeploymentがPodを作り、ServiceがPodへの通信を担当します。
DeploymentがServiceを作成するわけでも、ServiceがDeploymentを管理するわけでもありません。
それぞれ独立したKubernetesリソースです。
ServiceとEndpointSliceの関係
Serviceのselectorに一致したPodのIPアドレスは、EndpointSliceというリソースに記録されます。
Service
selector: app=rails
↓
EndpointSlice
├── 10.0.1.10:3000
├── 10.0.1.11:3000
└── 10.0.2.20:3000
↓
Rails Pod群
EndpointSliceは、Serviceの実際の転送先となるエンドポイントを表します。
Podが追加・削除されると、対象のEndpointSliceも更新されます。
Podを1個追加
↓
EndpointSliceへ新しいPodのIPを追加
Podを1個削除
↓
EndpointSliceから古いPodのIPを削除
利用する側は、この更新を自分で追いかける必要がありません。
Serviceを接続先にしておけば、Kubernetesが現在利用できるPodとの対応を管理します。
Serviceの通信はどのようにPodへ届くのか
ClusterIP型のServiceを作成すると、クラスター内で使う仮想的なIPアドレスが割り当てられます。
ServiceのClusterIP:10.96.10.50
実際のPod
├── 10.0.1.10
├── 10.0.1.11
└── 10.0.2.20
通信する側は、ServiceのClusterIPまたはDNS名へリクエストを送ります。
別のPod
↓
Service:10.96.10.50
↓
現在利用できるPodの1つ
一般的なKubernetes環境では、各Node上のkube-proxyなどがService用のネットワークルールを管理し、通信をPodへ転送します。
Service宛ての通信
↓
Node上のネットワークルール
↓
EndpointSliceに含まれるPod
Serviceは、常に独立したプロキシサーバーやコンテナが1台起動して通信を中継する仕組みではありません。
利用するネットワーク実装によって、Service通信を実現する具体的な方法は異なります。
Serviceを定義するYAML
以下は、Rails Podへ通信を届ける基本的なServiceです。
apiVersion: v1
kind: Service
metadata:
name: rails-service
spec:
type: ClusterIP
selector:
app: rails
ports:
- name: http
protocol: TCP
port: 80
targetPort: 3000
このServiceは、次の動きをします。
rails-serviceの80番ポート
↓
app=railsのPod
↓
Podの3000番ポート
YAMLの各項目
apiVersionとkind
apiVersion: v1
kind: Service
| 項目 | 意味 |
|---|---|
apiVersion: v1 |
Serviceを扱うKubernetes APIのバージョン |
kind: Service |
作成するリソースがServiceであることを示す |
metadata.name
metadata:
name: rails-service
Service自体の名前です。
同じNamespace内のPodは、基本的にrails-serviceというDNS名でアクセスできます。
http://rails-service
type
type: ClusterIP
Serviceをどこから利用できるようにするかを決めます。
ClusterIPはクラスター内部から利用する標準的なServiceタイプです。
selector
selector:
app: rails
どのPodを通信先にするかをラベルで指定します。
DeploymentのPodテンプレートには、対応するラベルが必要です。
template:
metadata:
labels:
app: rails
ports
ports:
- name: http
protocol: TCP
port: 80
targetPort: 3000
Serviceが受け付けるポートと、Pod側へ届けるポートを指定します。
| 項目 | 意味 |
|---|---|
name |
Service内でポートを識別する名前 |
protocol |
TCP・UDPなどの通信プロトコル |
port |
Serviceが受け付けるポート |
targetPort |
通信を転送するPod側のポート |
port・targetPort・containerPortの違い
ポートの違いは、Serviceを理解するうえで重要です。
通信する側
↓
Service
port: 80
↓
Pod
targetPort: 3000
↓
Rails・Puma
3000番ポートで待ち受け
Deployment側のコンテナ定義には、次のように書けます。
containers:
- name: rails
image: example/rails-app:v1
ports:
- containerPort: 3000
それぞれの役割は次のとおりです。
| 項目 | 場所 | 意味 |
|---|---|---|
port |
Service | Serviceが通信を受け付けるポート |
targetPort |
Service | ServiceからPodへ転送するときの宛先ポート |
containerPort |
Podのコンテナ定義 | コンテナが使用するポートを示す情報 |
containerPortを書くだけで、アプリケーションがそのポートで待ち受けたり、外部へ自動公開されたりするわけではありません。
実際には、RailsやPumaなどのアプリケーション側も3000番ポートで待ち受ける必要があります。
Serviceを作成する
YAMLをservice.yamlとして保存した場合、次のコマンドで作成できます。
kubectl apply -f service.yaml
Serviceの一覧を確認します。
kubectl get services
短縮形も使用できます。
kubectl get svc
Serviceの詳細やイベントを確認します。
kubectl describe service rails-service
Serviceに対応するEndpointSliceを確認します。
kubectl get endpointslices
特定のServiceに関連するEndpointSliceを絞り込む場合は、次のように確認できます。
kubectl get endpointslices \
-l kubernetes.io/service-name=rails-service
Podのラベルを確認する場合は、次のコマンドを使用します。
kubectl get pods --show-labels
ServiceのDNS名
Kubernetesクラスター内では、ServiceにDNS名が作られます。
同じNamespaceからは、Service名だけでアクセスできます。
rails-service
別のNamespaceからアクセスする場合は、Namespace名を含められます。
rails-service.production
完全修飾ドメイン名は、通常次のような形です。
rails-service.production.svc.cluster.local
たとえば、同じNamespaceにあるフロントエンドPodからRails Serviceへアクセスする場合は、次のように指定できます。
http://rails-service
これにより、設定ファイルへ個々のRails PodのIPアドレスを書く必要がなくなります。
ReadyではないPodには通信を送らない
Serviceのselectorに一致しているだけで、常にすべてのPodが通常の通信先になるとは限りません。
Readiness Probeに失敗し、リクエストを受け付ける準備ができていないPodは、通常のService通信の対象から外れます。
Service
├── Pod A:Ready ← 通信先
├── Pod B:Ready ← 通信先
└── Pod C:NotReady ← 通常は通信先から外れる
たとえば、新しいRails Podが起動していても、初期化が終わっていなければリクエストを処理できない場合があります。
Readiness Probeを設定すると、準備完了後にServiceの通信先へ加えられます。
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
Deploymentのローリングアップデートを安全に行うためにも、Readiness Probeは重要です。
Serviceの種類
Serviceには、主に次のタイプがあります。
| タイプ | 主な用途 |
|---|---|
| ClusterIP | クラスター内部からアクセスする |
| NodePort | 各NodeのIPアドレスと特定ポートで公開する |
| LoadBalancer | クラウドのロードバランサーなどを通じて公開する |
| ExternalName | Service名を外部のDNS名へ対応させる |
ClusterIP
ClusterIPは、Serviceのデフォルトタイプです。
spec:
type: ClusterIP
クラスター内部から利用できる仮想IPアドレスを提供します。
クラスター内部のPod
↓
ClusterIP Service
↓
対象のPod
たとえば、Webアプリケーションから内部APIへアクセスする場合などに使用します。
インターネットからClusterIPへ直接アクセスすることは、通常できません。
NodePort
NodePortは、各NodeのIPアドレス上に特定のポートを公開します。
spec:
type: NodePort
NodeのIP:NodePort
↓
Service
↓
対象のPod
たとえば、NodePortとして30080が割り当てられた場合、次のような接続先になります。
NodeのIPアドレス:30080
NodePortは、標準設定では30000から32767の範囲から割り当てられます。
NodePortだからといって、必ず安全にインターネットへ公開されるわけではありません。
実際に到達できるかは、Nodeのネットワーク、Security Group、Firewallなどの設定にも依存します。
LoadBalancer
LoadBalancerは、対応するクラウド環境で外部ロードバランサーを作成し、Serviceへ通信を届けるためのタイプです。
spec:
type: LoadBalancer
インターネット
↓
クラウドのLoad Balancer
↓
Service
↓
Pod
AWSなどでは、Kubernetesとクラウド側の機能が連携してロードバランサーを用意します。
ただし、どの種類のロードバランサーが作られるか、NodeまたはPodのどちらを転送先にするかなどは、クラウド環境や利用するコントローラーの設定によって異なります。
ExternalName
ExternalNameは、Service名をクラスター外部のDNS名へ対応させます。
apiVersion: v1
kind: Service
metadata:
name: external-api
spec:
type: ExternalName
externalName: api.example.com
クラスター内のPodはexternal-apiという名前を使い、外部のapi.example.comを参照できます。
Pod
↓ external-api
Kubernetes DNS
↓
api.example.com
ExternalNameはselectorでPodを選び、通信をプロキシするServiceではありません。
DNSのCNAMEレコードを返す仕組みです。
Headless Service
ServiceにClusterIPを割り当てず、対象PodのIPアドレスをDNSから直接取得したい場合は、Headless Serviceを使用できます。
spec:
clusterIP: None
通常のService
DNS名 → ServiceのClusterIP
Headless Service
DNS名 → 対象PodのIPアドレス群
Headless Serviceは、StatefulSetなどで各Podを個別に発見したい場合に利用されます。
通常のWebアプリケーションへ安定した1つの入口を提供する用途では、まずClusterIP Serviceを考えるのが基本です。
ServiceはどのPodへ通信を送るのか
Serviceに複数のPodが紐づいている場合、通信は利用可能なエンドポイントのいずれかへ転送されます。
Service
├── Pod 1
├── Pod 2
└── Pod 3
ただし、「必ずPod 1、Pod 2、Pod 3の順に均等なラウンドロビンになる」とは限りません。
実際の選択方法は、kube-proxyのモードやネットワーク実装、通信の状態などに依存します。
Serviceは、アプリケーション固有の複雑な振り分けルールを持つロードバランサーではありません。
ホスト名やURLパスによるHTTPルーティングは、Ingressなどが担当します。
ServiceとIngressの違い
ServiceとIngressは、どちらも通信経路に登場しますが、役割が異なります。
インターネット
↓
Ingress・Load Balancer
↓
Service
↓
Pod
| リソース | 主な役割 |
|---|---|
| Service | Pod群に対する安定した接続先を提供する |
| Ingress | Host名やURLパスを見て転送先Serviceを決める |
たとえば、次のようなルーティングはIngress側の役割です。
example.com/api
↓
api-service
example.com/admin
↓
admin-service
Serviceは、その先で対象のPod群を表します。
api-service
├── API Pod 1
└── API Pod 2
Ingressリソースを書いただけでは通信処理は実行されず、Ingress Controllerも必要です。
この詳細は、次のIngress編で整理します。
ServiceとALBの違い
ServiceはKubernetesのリソースで、ALBはAWSのロードバランサーです。
Service
= Kubernetes内でPod群を表す安定した接続先
ALB
= AWS上でHTTP・HTTPS通信を受け、ルーティングするロードバランサー
EKSでは、IngressやServiceの設定とAWS Load Balancer Controllerなどを組み合わせ、ALBやNLBを作成する構成があります。
ブラウザ
↓
ALB
↓
Kubernetes側のバックエンド
↓
Pod
具体的な経路は、ALBのTarget TypeやIngress Controllerの設定によって変わります。
そのため、必ずすべての構成で物理的にALB → ClusterIP → Podと流れるとは限りません。
図を読むときは、Serviceが「Pod群をどのラベルで表しているか」と、ALBのTarget Groupが「NodeとPodのどちらを対象にしているか」を分けて確認します。
RailsとSidekiqではServiceが必要か
RailsはHTTPリクエストを受け付けるため、通常はServiceを作成します。
rails-service
└── Rails Pod群
一方、Sidekiqは通常、RedisやValkeyなどからジョブを取得して処理します。
他のアプリケーションからSidekiq Podへ直接リクエストを送らない構成であれば、Sidekiq用Serviceは必須ではありません。
Rails Pod
↓ ジョブを登録
Redis・Valkey
↓ ジョブを取得
Sidekiq Pod
Serviceが必要かどうかは、そのPod群が他のコンポーネントから通信を受け付ける必要があるかで考えます。
Serviceで問題が起きたときの確認順序
Serviceへ接続できない場合は、次の順番で確認すると関係を追いやすくなります。
1. Serviceが存在するか
↓
2. Serviceのselectorは正しいか
↓
3. Podに対応するラベルが付いているか
↓
4. EndpointSliceにPodのIPが登録されているか
↓
5. PodがReadyになっているか
↓
6. portとtargetPortは正しいか
↓
7. アプリケーションがtargetPortで待ち受けているか
確認に使用する主なコマンドは次のとおりです。
kubectl get svc
kubectl describe svc rails-service
kubectl get pods --show-labels
kubectl get endpointslices \
-l kubernetes.io/service-name=rails-service
kubectl describe pod <pod-name>
EndpointSliceにエンドポイントが1つもなければ、selectorとPodラベルの不一致や、PodのReady状態などを確認します。
よくある勘違い
ServiceはPodを作成する?
違います。
Podを作成・維持するのはDeploymentやReplicaSetです。
Serviceは、すでに存在するPodをラベルで選び、通信先として扱います。
ServiceはDeploymentを選んでいる?
違います。
Serviceのselectorが選ぶのはPodのラベルです。
DeploymentとServiceは、同じPodラベルを介してつながります。
Serviceは1台のサーバーやPod?
違います。
一般的なClusterIP Serviceは、専用のPodが1個起動して通信を中継する仕組みではありません。
仮想IP、DNS、EndpointSlice、Node上のネットワークルールなどによって実現されます。
Serviceを作ればインターネットからアクセスできる?
Serviceの種類によります。
ClusterIPは、通常クラスター内部からのみ利用できます。
外部公開には、LoadBalancer Service、NodePort、Ingressなどの構成を検討します。
ServiceのIPアドレスはPodのIPアドレス?
違います。
ClusterIPはService用の仮想IPアドレスであり、PodのIPアドレスとは別です。
Serviceを作ればPodが複数Nodeへ分散される?
分散されません。
Podの配置先を決めるのはkube-schedulerです。
Serviceは、配置済みのPodへ通信を届けます。
containerPortを書けばServiceから接続できる?
それだけでは接続できません。
Serviceのselectorとポート設定が正しく、アプリケーション自身も対象ポートで待ち受ける必要があります。
ServiceはHTTPのURLパスを見て振り分ける?
通常のServiceは、/apiや/adminなどのURLパスを見て転送先を決める役割ではありません。
そのようなHTTPルーティングは、Ingressなどが担当します。
まとめ
Serviceについて、重要な点をまとめます。
- Serviceは、Pod群に安定した接続先を提供する
- Podは再作成によってIPアドレスが変わる可能性がある
- 利用する側は、個々のPodではなくServiceのDNS名やIPアドレスへアクセスする
- Serviceはselectorを使ってPodのラベルを選ぶ
- ServiceがDeploymentを直接選ぶわけではない
- 対象PodのIPアドレスはEndpointSliceに記録される
- Podの追加・削除に合わせてエンドポイントも更新される
-
portはService側、targetPortはPod側の宛先ポート -
containerPortだけでアプリケーションやポートが自動公開されるわけではない - ClusterIPはクラスター内部向けの標準的なService
- NodePortは各Nodeの特定ポートで公開する
- LoadBalancerはクラウドのロードバランサーなどと連携して公開する
- ExternalNameはService名を外部DNS名へ対応させる
- Readiness Probeに失敗したPodは、通常の通信先から外れる
- DeploymentはPodを管理し、ServiceはPodへの通信を担当する
- IngressはHost名やURLパスから転送先Serviceを決める
- ServiceとAWSのALBは別のもの
最も簡単に覚えるなら、次の関係です。
Deployment
└── Podを作成・維持する
Service
└── Podへの安定した接続先を提供する
全体の流れは、次のようになります。
利用する側
↓
Service
↓
EndpointSliceに登録されたPod
↓
Container
Deploymentを理解したあとにServiceを学ぶと、Kubernetesが「増減・再作成されるPodへ、どのように安定して通信を届けているのか」が見えやすくなります。