Kubernetes で Pod を動かせるようになると、次に気になるのが Pod 同士の通信 です。
「ある Pod から別の Pod にリクエストを送りたいとき、どうすればいいの?」
kubectl get pods -o wide で IP を調べて、その IP を指定すればいい? でも、Pod が再作成されたら IP は変わります。ローリングアップデートしたら? スケールアウトしたら? そのたびに IP を調べ直す?
実は、Kubernetes にはこの問題を解決する仕組みがあります。それが Service です。
この記事では、Pod の IP を直接指定したときの問題を実際に体験し、ClusterIP Service でその問題を解決するまでをハンズオンで追います。
この記事を読み終わる頃には、
- Pod の IP が不安定であることを「体験として」知っている
- Service の4つの種類を概要レベルで把握している
- ClusterIP Service の YAML を自分で書ける
- Service 名(DNS)でアクセスできることを知っている
状態を目指します。
TL;DR
| やること | 使うコマンド / 概念 | ポイント |
|---|---|---|
| Pod IP 直指定の問題を体験する |
kubectl exec / curl
|
Pod が消えたら通信断 |
| Service の概念と4種類を知る | — | ClusterIP / NodePort / LoadBalancer / ExternalName |
| ClusterIP Service の YAML を書く | kind: Service / type: ClusterIP / selector | Pod のラベルで対象を選ぶ |
| Service を作成する | kubectl apply -f |
安定した IP と DNS 名が割り当てられる |
| ロードバランシングを確認する | curl を繰り返す | リクエストが複数の Pod に分散される |
| Pod を消しても通信が継続することを確認する | kubectl delete pod |
Service が健全な Pod に自動で振り分ける |
| Service 名(DNS)でアクセスする | Service 名を URL に指定 | IP より安定。再作成しても名前は変わらない |
対象読者
- Deployment の YAML を書いて Pod を複数動かせる方
- 「Pod 同士ってどうやって通信するの?」と疑問に思っている方
- Service という単語は聞いたことがあるが、何をしてくれるのか説明できない方
この記事は minikube が起動済み であることを前提としています。
minikube のインストールがまだの方は、公式ドキュメントを参考にセットアップを済ませてから読み進めてください。
Step 1: Pod の IP を直接指定する — 何が問題?
いきなり Service の話に入る前に、なぜ Service が必要なのかを体験してみましょう。
┌──────────────────────────────────────────────────────────────┐
│ │
│ 【これからやること】 │
│ │
│ ① nginx の Deployment を作成する(replicas: 3) │
│ ② Pod の IP を調べる │
│ ③ 一時的な curl Pod から、その IP にリクエストを送る → 成功 │
│ ④ その Pod を削除する │
│ ⑤ 同じ IP にリクエストを送る → 失敗! │
│ │
│ Pod の IP を信用して大丈夫か? を自分の手で確かめます │
│ │
└──────────────────────────────────────────────────────────────┘
環境をクリーンにする
まずは既存のリソースを片付けて、クリーンな状態から始めましょう。
kubectl delete deployment --all
kubectl delete service --all
--all で全リソースを削除しています。個人の学習環境以外では、削除対象を明示的に指定してください。
nginx の Deployment を作成する
以下の内容で nginx-deployment.yaml を作成してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27.0
ports:
- containerPort: 80
kubectl apply -f nginx-deployment.yaml
Pod の IP を調べる
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-5d66cc795f-7x9kl 1/1 Running 0 10s 10.244.0.5 minikube
nginx-deployment-5d66cc795f-mh4nw 1/1 Running 0 10s 10.244.0.6 minikube
nginx-deployment-5d66cc795f-p2bfj 1/1 Running 0 10s 10.244.0.7 minikube
IP 列に表示されている値が、各 Pod のプライベート IP アドレスです。ここでは 1つ目の Pod の IP(例: 10.244.0.5)を使います。
curl Pod から Pod の IP にリクエストを送る
一時的な curl Pod を作成して、Pod の IP に直接リクエストを送ります。
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- curl 10.244.0.5
--rm は Pod の終了後に自動削除、-it は対話モード、--restart=Never は再起動なしの設定です。-- の後ろが Pod 内で実行されるコマンドになります。IP アドレスの部分は、自分の環境で表示された値に置き換えてください。
nginx の Welcome ページの HTML が返ってきます。Pod の IP を直接指定してアクセスできました。
Pod を削除する
では、その Pod を削除してみましょう。
kubectl delete pod nginx-deployment-5d66cc795f-7x9kl
Deployment のセルフヒーリングにより、新しい Pod が自動で作成されます。
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-5d66cc795f-mh4nw 1/1 Running 0 2m 10.244.0.6 minikube
nginx-deployment-5d66cc795f-p2bfj 1/1 Running 0 2m 10.244.0.7 minikube
nginx-deployment-5d66cc795f-q8r2t 1/1 Running 0 5s 10.244.0.8 minikube
新しい Pod の IP は 10.244.0.8 です。元の 10.244.0.5 とは違う IP が割り当てられています。
古い IP にリクエストを送る → 失敗
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- curl --max-time 5 10.244.0.5
タイムアウトまたは接続拒否になります。古い IP にはもう誰もいません。
┌──────────────────────────────────────────────────────────────┐
│ │
│ Pod IP を直接指定した場合: │
│ │
│ curl Pod → 10.244.0.5 (Pod-A) ← Pod-A が消えたら… │
│ ✗ 通信断! │
│ │
│ 新しい Pod-D は 10.244.0.8 ← IP が変わっている │
│ → curl Pod は古い IP しか知らない │
│ → 手動で新しい IP を調べ直す必要がある │
│ │
└──────────────────────────────────────────────────────────────┘
Pod の IP を直接指定する問題
| 問題 | 具体的には? |
|---|---|
| IP が不安定 | Pod が再作成されると IP が変わる |
| スケールに対応できない | Pod が増えても、どの IP に送ればいいかわからない |
| ロードバランシングがない | 1つの Pod に全リクエストが集中する |
Pod の IP が不安定な理由は、Pod が「使い捨て」の存在だからです。削除・再作成・スケーリング・ノード障害など、あらゆる場面で IP は変わります。Pod IP を直接指定するのは、砂の上に住所を書くようなもの。消えることを前提に設計する必要があります。
Step 2: Service とは何か — 4つの種類を知る
Pod の IP を直接指定する問題が見えてきました。では、Kubernetes はどうやってこの問題を解決するのでしょうか?
その答えが Service です。
┌──────────────────────────────────────────────────────────────┐
│ │
│ 【Service がない世界】 │
│ │
│ curl Pod → Pod-A (10.244.0.5) ← IP 直指定。消えたら終わり │
│ │
│ 【Service がある世界】 │
│ │
│ curl Pod → Service (安定した IP / DNS 名) │
│ ├→ Pod-A ✓ │
│ ├→ Pod-B ✓ │
│ └→ Pod-C ✓ │
│ ↑ Service が健全な Pod に自動で振り分ける │
│ │
└──────────────────────────────────────────────────────────────┘
Service は「受付窓口」
Service の役割をひと言で表すなら、Pod の前に立つ 受付窓口 です。
- Pod の IP は変わるが、Service の IP と名前は変わらない
- Service は selector(ラベル) で「どの Pod に振り分けるか」を決める
- Pod が増減しても、Service が自動で追従する
リクエストを送る側は「Service の名前」さえ知っていればよく、裏にいる Pod の数や IP を一切気にする必要がありません。
4つの種類
Kubernetes には4種類の Service があります。
| 種類 | 用途 | アクセス範囲 |
|---|---|---|
| ClusterIP(デフォルト) | クラスタ内部の通信 | クラスタ内部のみ |
| NodePort | 開発・テスト用の外部公開 | ノードの IP + ポート経由で外部から |
| LoadBalancer | 本番環境の外部公開 | クラウドのロードバランサー経由 |
| ExternalName | 外部 DNS への参照 | クラスタ内部から外部 DNS へ |
この記事では ClusterIP にフォーカスします。
Step 3: ハンズオン — ClusterIP Service の YAML を書いて動かす
Service の概念がわかったところで、実際に ClusterIP Service を作ってみましょう。
┌──────────────────────────────────────────────────────────────┐
│ │
│ 【これからやること】 │
│ │
│ ① nginx-clusterip-svc.yaml を書く(Service の YAML) │
│ ② kubectl apply -f で Service を作成する │
│ ③ Service の情報を確認する(IP, Endpoints) │
│ ④ describe で詳細を確認する │
│ │
│ Step 1 で体験した「IP 直指定の問題」を Service で解決します │
│ │
└──────────────────────────────────────────────────────────────┘
nginx-clusterip-svc.yaml を作成する
以下の内容でファイルを作成してください。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
YAML の各フィールドを理解する
| フィールド | 意味 | ポイント |
|---|---|---|
apiVersion: v1 |
コアグループの API | Deployment は apps/v1 だったが、Service は v1
|
kind: Service |
作るのは Service | |
type: ClusterIP |
クラスタ内部のみアクセス可能 | 省略してもデフォルトで ClusterIP になるが、明示する方がわかりやすい |
selector: app: nginx |
app: nginx ラベルを持つ Pod に振り分ける |
Deployment の template.metadata.labels と一致させる |
port: 80 |
Service が受け付けるポート | |
targetPort: 80 |
Pod 側のポート(containerPort) |
selector の仕組みは ReplicaSet と同じ考え方です。ラベルが一致する Pod を対象にします。Service は「誰がその Pod を作ったか」は見ません。Deployment で作っても、ReplicaSet で作っても、単体の Pod でも、ラベルが一致すれば振り分けの対象になります。
kubectl apply -f で作成する
kubectl apply -f nginx-clusterip-svc.yaml
service/nginx-service created と表示されれば成功です。
Service の情報を確認する
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1d
nginx-service ClusterIP 10.96.45.123 <none> 80/TCP 10s
nginx-service に CLUSTER-IP が割り当てられています。この IP は Pod の IP と違い、Service が存在する限り変わりません。
describe で詳細を確認する
kubectl describe service nginx-service
出力の中で、以下のポイントを確認してください。
-
Type:
ClusterIP - IP: Service に割り当てられた安定した IP
-
Port:
80/TCP - Endpoints: selector に一致する Pod の IP が一覧で表示されている
Endpoints に表示される IP は、selector に一致する Pod のプライベート IP です。Pod が増減すると、Endpoints も自動で更新されます。Service は Endpoints を監視して、常に健全な Pod にリクエストを振り分けます。
Step 4: Service 経由でアクセスする — ロードバランシングを体験する
Service を作成しました。ここからが、この記事の感動ポイントです。
Step 1 では Pod の IP を直接指定して通信しました。今度は Service 経由でアクセスして、何が変わるかを体験しましょう。
┌──────────────────────────────────────────────────────────────┐
│ │
│ 【これからやること】 │
│ │
│ ① curl Pod から Service の名前にリクエストを送る │
│ ② 複数回送って、異なる Pod に振り分けられることを確認する │
│ ③ Pod を1つ削除しても通信が継続することを確認する │
│ │
│ Step 1: Pod IP 直指定 → Pod が消えたら通信断 │
│ Step 4: Service 経由 → Pod が消えても通信継続! │
│ │
└──────────────────────────────────────────────────────────────┘
Service の名前にリクエストを送る
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- curl nginx-service
nginx の Welcome ページが返ってきます。ここでのポイントは、IP アドレスではなく Service 名 nginx-service でアクセスしていることです。
ロードバランシングを確認する
リクエストが複数の Pod に分散されているかを確認しましょう。各 Pod のアクセスログを見ます。
# Pod の一覧を確認する
kubectl get pods
# 各 Pod のログを確認する(Pod 名は自分の環境に合わせて置き換え)
kubectl logs nginx-deployment-5d66cc795f-mh4nw
kubectl logs nginx-deployment-5d66cc795f-p2bfj
kubectl logs nginx-deployment-5d66cc795f-q8r2t
curl を何度か繰り返してから各 Pod のログを確認すると、リクエストが複数の Pod に分散されていることがわかります。
# 何度かリクエストを送る
kubectl run curl-test1 --image=curlimages/curl --rm -it --restart=Never -- curl -s nginx-service
kubectl run curl-test2 --image=curlimages/curl --rm -it --restart=Never -- curl -s nginx-service
kubectl run curl-test3 --image=curlimages/curl --rm -it --restart=Never -- curl -s nginx-service
Service は裏でリクエストを自動的に分散しています。どの Pod に送られるかは Service が決めるため、リクエストを送る側は Pod の数や場所を一切気にする必要がありません。
Pod を1つ削除しても通信が継続することを確認する
Step 1 では Pod を削除すると通信が断絶しました。Service 経由ではどうなるでしょうか?
# Pod を1つ削除する
kubectl delete pod <Pod名>
# すぐに Service 経由でアクセスする
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- curl nginx-service
通信は継続します! Service が残りの健全な Pod に自動で振り分けてくれるからです。
┌──────────────────────────────────────────────────────────────┐
│ │
│ 【Service 経由の通信】 │
│ │
│ curl Pod → nginx-service (ClusterIP) │
│ ├→ Pod-A ✓ ← リクエスト1 │
│ ├→ Pod-B ✓ ← リクエスト2 │
│ └→ Pod-C ✓ ← リクエスト3 │
│ ロードバランシング! │
│ │
│ Pod-A を削除しても… │
│ curl Pod → nginx-service (ClusterIP) │
│ ├→ Pod-B ✓ ← 引き続き到達可能 │
│ ├→ Pod-C ✓ │
│ └→ Pod-D ✓ ← セルフヒーリングで新しい Pod │
│ 通信は途切れない! │
│ │
└──────────────────────────────────────────────────────────────┘
Step 1 では Pod IP を直接指定して、Pod が消えたら通信断になりました。Service を介すことで、Pod が消えても通信が継続します。これが Service の最大の価値です。
Service と Deployment は独立したリソースです。Service は「どの Pod に振り分けるか」をラベルで動的に決めているだけで、Deployment のライフサイクルには関与しません。Deployment を削除・再作成しても、Service はラベルが一致する新しい Pod を自動で見つけて振り分けを再開します。
Step 5: Service 名(DNS)でアクセスする — IP よりも安定な方法
Step 4 では Service 名 nginx-service でアクセスしました。実はこれが最も安定したアクセス方法です。
3つのアクセス方法の比較
| 方法 | 例 | 安定性 |
|---|---|---|
| Pod の IP | curl 10.244.0.5 |
✗ Pod が再作成されると変わる |
| Service の ClusterIP | curl 10.96.45.123 |
△ Service を再作成すると変わる |
| Service の名前(DNS) | curl nginx-service |
✓ 最も安定。手動で名前を変えない限り不変 |
Service を削除・再作成して ClusterIP が変わることを確認する
# 現在の ClusterIP を確認する
kubectl get service
ClusterIP をメモしてください。
# Service を削除する
kubectl delete service nginx-service
# Service を再作成する
kubectl apply -f nginx-clusterip-svc.yaml
# ClusterIP を再確認する
kubectl get service
ClusterIP が変わっています。 Service を再作成すると、新しい IP が割り当てられるためです。
しかし、Service 名 nginx-service は同じです。名前でアクセスしていれば、IP が変わっても影響を受けません。
# Service 名でアクセスする → IP が変わっても問題なし
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- curl nginx-service
クラスタ内に CoreDNS が動いており、Service 名を DNS 名として解決してくれます。kubectl get pods -n kube-system を実行すると、CoreDNS の Pod が確認できます。Service の IP が変わっても、名前でアクセスしていれば影響を受けません。実務では Service 名でアクセスするのが基本です。
ClusterIP Service はクラスタ 内部 からのみアクセス可能です。ブラウザなどクラスタ外部からアクセスしたい場合は、別の種類の Service が必要になります。この壁を越えるのが NodePort Service です。
Step 6: お片付け
ハンズオンで作成したリソースを削除しましょう。
# Service を削除する
kubectl delete -f nginx-clusterip-svc.yaml
# Deployment を削除する
kubectl delete -f nginx-deployment.yaml
# クリーンな状態を確認する
kubectl get pods
kubectl get service
Service を削除しても Pod は削除されません。Service と Deployment は独立したリソースです。Service は「どの Pod に振り分けるか」をラベルで動的に決めているだけで、Pod のライフサイクルには関与しません。逆に、Deployment を削除しても Service は残ります。それぞれ個別に削除する必要があります。
まとめ
お疲れさまでした!今回のハンズオンの全体フローを振り返りましょう。
┌──────────────────────────────────────────────────────────────┐
│ │
│ Step 1: Pod IP 直指定の問題 │
│ │ Pod が消えたら通信断。スケールにも対応できない │
│ ▼ │
│ Step 2: Service の概念と4種類 │
│ │ 「受付窓口」として Pod の不安定さを吸収する │
│ ▼ │
│ Step 3: ClusterIP Service の YAML を書く │
│ │ selector で対象 Pod を選ぶ。apply で作成 │
│ ▼ │
│ Step 4: ロードバランシングを体験 │
│ │ リクエストが複数 Pod に分散! Pod を消しても通信継続! │
│ ▼ │
│ Step 5: Service 名(DNS)でアクセス │
│ │ IP よりも安定。実務ではこれが基本 │
│ ▼ │
│ Step 6: お片付け │
│ │
│ → ClusterIP はクラスタ内部の通信のプロ。 │
│ でも「外部からアクセスしたい」には対応できない。 │
│ この壁を越えるのが NodePort Service │
│ │
└──────────────────────────────────────────────────────────────┘
今回使ったコマンド一覧
| コマンド | 用途 |
|---|---|
kubectl apply -f <file> |
Deployment / Service を作成・更新 |
kubectl get pods -o wide |
Pod の一覧と IP アドレスを表示 |
kubectl get service |
Service の一覧を表示 |
kubectl describe service <name> |
Service の詳細情報(Endpoints 含む)を表示 |
kubectl run ... --rm -it -- curl <url> |
一時的な curl Pod でリクエストを送る |
kubectl logs <pod-name> |
Pod のログを表示 |
kubectl delete pod <name> |
指定した Pod を削除 |
kubectl delete -f <file> |
ファイルで指定したリソースを削除 |
kubectl get pods -n kube-system |
kube-system 名前空間の Pod を表示(CoreDNS の確認用) |
今回の学びのポイント
| ポイント | 内容 |
|---|---|
| Pod IP の問題 | Pod は使い捨て。IP は不安定。直接指定してはいけない |
| Service の役割 | Pod の前に立つ「受付窓口」。安定した IP と DNS 名を提供する |
| ClusterIP | クラスタ内部の通信に使う。デフォルトの Service タイプ |
| selector | ラベルで振り分け先の Pod を決める。Deployment の labels と一致させる |
| ロードバランシング | Service が自動でリクエストを分散する |
| DNS 名 | Service 名で直接アクセスできる。IP よりも安定 |
| Service と Deployment の独立性 | ラベルで動的に紐づく。ライフサイクルは別々 |
| ClusterIP の限界 | クラスタ外部からはアクセスできない → NodePort で解決(次の記事) |
この記事が誰かのためになれば幸いです!