0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KubernetesのServiceとは何か?

0
Posted at

はじめに

前回は、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へ、どのように安定して通信を届けているのか」が見えやすくなります。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?