Service を使って Kubernetes の Pod にアクセスする
- 公開日
- カテゴリ:Kubernetes
- タグ:Kubernetes,学習メモ

Pod は1つずつ自分の IP を持つ。ただし Pod は作り直されることがあり、そのとき IP も変わる。Pod の IP を宛先にしていると、Pod が入れ替わったときに届かなくなる。
Service は、複数の Pod に対する変わらない宛先を定義するリソース。Pod が入れ替わっても、Service の名前と IP は変わらない。
この記事では、Service をローカルのクラスタに作り、クラスタの中からアクセスして、Pod が入れ替わったときの動きを確認する。
この記事でやること:
- Pod を作り直したときの IP の変化
- Service の YAML の確認
- Service と EndpointSlice の確認
- クラスタの中から Service へのアクセス
- アクセスが届いた Pod の確認
- Pod が入れ替わったときの動き
- Service の削除
contents
- 環境
- Pod の IP は変わる
- Service の YAML
- Service を作る
- クラスタの中からアクセスする
- アクセスはどの Pod に届いたか
- Pod が入れ替わったときの動き
- Service を消す
- Service の種類
- 用語メモ
- 参考 URL
環境
kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)
- 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1
Pod の IP は変わる
nginx の Pod を3つ動かす Deployment を用意する。(Deployment については「Kubernetes の Deployment で Pod を管理する」を参照)
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
$ kubectl apply -f nginx-deployment.yaml
# => deployment.apps/nginx-deployment created
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-76df5b8bb8-52kxv 1/1 Running 0 6s 10.244.1.2 k8s-study-worker <none> <none>
nginx-deployment-76df5b8bb8-mhjqh 1/1 Running 0 6s 10.244.1.3 k8s-study-worker <none> <none>
nginx-deployment-76df5b8bb8-pkrcd 1/1 Running 0 6s 10.244.2.2 k8s-study-worker2 <none> <none>
Pod は1つずつ別の IP を持っている。ここで Pod を1つ消す。
$ kubectl delete pod nginx-deployment-76df5b8bb8-52kxv
# => pod "nginx-deployment-76df5b8bb8-52kxv" deleted from default namespace
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-76df5b8bb8-mhjqh 1/1 Running 0 48s 10.244.1.3 k8s-study-worker <none> <none>
nginx-deployment-76df5b8bb8-pkrcd 1/1 Running 0 48s 10.244.2.2 k8s-study-worker2 <none> <none>
nginx-deployment-76df5b8bb8-t4jvl 1/1 Running 0 4s 10.244.2.3 k8s-study-worker2 <none> <none>
- 消した Pod(
...-52kxv、IP10.244.1.2)はなくなった - 代わりに作られた Pod(
...-t4jvl)は、名前も IP(10.244.2.3)も別 - 置かれた Node も、
k8s-study-workerからk8s-study-worker2に変わっている
10.244.1.2 を宛先にしていた場合、Pod が入れ替わった時点で届かなくなる。また、Pod は3つあるので、どの Pod にアクセスするかをアクセスする側が選ぶ必要もある。
Service の YAML
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
| 場所 | 意味 |
|---|---|
metadata.name | Service の名前。クラスタの中からは、この名前を宛先にしてアクセスできる |
spec.selector | どの label が付いた Pod を届け先にするかの指定 |
spec.ports.port | Service が受けるポート |
spec.ports.targetPort | 届け先の Pod のポート。省略すると port と同じ値 |
selector の app: nginx は、Deployment の template.metadata.labels で Pod に付けている label と同じもの。
nginx-deployment.yaml nginx-service.yaml
───────────────────── ──────────────────
template: spec:
metadata: selector:
labels: app: nginx
app: nginx
- Service の YAML には、Pod の名前も IP も書かない
- 書くのは「
app: nginxの label が付いた Pod」という条件だけ type(Service の種類)を書かなかった場合はClusterIPになる
Service を作る
$ kubectl apply -f nginx-service.yaml
# => service/nginx-service created
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d9h
nginx-service ClusterIP 10.96.28.227 <none> 80/TCP 17s
nginx-serviceが作った Service。kubernetesは最初からあるものCLUSTER-IP(10.96.28.227)が、Service に割り当てられた IP。Pod の IP(10.244.x.x)とは別の範囲になっているTYPEのClusterIPは、クラスタの中からだけアクセスできる種類
EndpointSlice
Service を作ると、EndpointSlice が自動で作られる。Service の selector に一致する Pod への参照(IP)を持つリソース。
$ kubectl get endpointslices
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
kubernetes IPv4 6443 172.21.0.4 3d9h
nginx-service-bf75h IPv4 80 10.244.2.3,10.244.1.3,10.244.2.2 2m12s
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-76df5b8bb8-mhjqh 1/1 Running 0 5m57s 10.244.1.3 k8s-study-worker <none> <none>
nginx-deployment-76df5b8bb8-pkrcd 1/1 Running 0 5m57s 10.244.2.2 k8s-study-worker2 <none> <none>
nginx-deployment-76df5b8bb8-t4jvl 1/1 Running 0 5m13s 10.244.2.3 k8s-study-worker2 <none> <none>
nginx-service-bf75h の ENDPOINTS に並ぶ3つの IP は、3つの Pod の IP と一致している。
Service・EndpointSlice・Pod の関係
Node と Deployment を加えると、次のようになる。
- 実線は Service 経由のアクセスの向き、点線は Deployment(ReplicaSet)が Pod を作って数を保つ関係
- Node の上にあるのは Pod だけ。Service、EndpointSlice、Deployment、ReplicaSet は、どの Node にも属していない
- Service は、届け先の Pod がどの Node にあるかを区別しない。2つの Node にある3つの Pod が、同じ Service の届け先になっている
- 同じ Pod を、Deployment は「数を保つ対象」として、Service は「届け先」として扱っている。どちらも
app: nginxの label で Pod を選んでいる
| リソース | 担当 | 誰が作るか |
|---|---|---|
| Service | 宛先(名前、IP、ポート)と、届け先を選ぶ条件(selector) | 自分で定義する。IP は自動で割り当てられる |
| EndpointSlice | 届け先の Pod の IP の一覧 | コントロールプレーンが自動で作り、更新する |
| Pod | コンテナを動かす | Deployment(ReplicaSet)が作る |
Service や Deployment はどこにあるか
Service や Deployment は、動いているプログラムではなく、YAML に書いた内容が記録されたデータ(オブジェクト)。Control Plane で動いている etcd に保存されている。
k8s-study-control-plane(Control Plane)
├─ etcd クラスタのすべてのデータの保存場所
│ ├─ Service nginx-service
│ ├─ EndpointSlice nginx-service-bf75h
│ ├─ Deployment nginx-deployment
│ ├─ ReplicaSet nginx-deployment-76df5b8bb8
│ └─ Pod nginx-deployment-76df5b8bb8-mhjqh など
├─ kube-apiserver
└─ kube-controller-manager
k8s-study-worker / k8s-study-worker2(Worker)
└─ Pod のコンテナ(nginx)
| リソース | etcd に保存されるデータ | Node の上で動くもの |
|---|---|---|
| Service | あり | なし |
| EndpointSlice | あり | なし |
| Deployment | あり | なし |
| ReplicaSet | あり | なし |
| Pod | あり | あり(コンテナ) |
- Pod もオブジェクトとして etcd に保存される。Pod だけが Node の上にあるのは、実際に動かすコンテナを持つため
- Service 用のコンテナが、どこかの Node で動いているわけではない
- 各 Node では kube-proxy が動いている。kube-proxy は Service と EndpointSlice の追加・削除を監視し、Service の IP とポート宛ての通信を届け先のどれかに転送するように、自分の Node を設定する
- Service の IP は、特定のホストが応答しているものではなく、仮想 IP として扱われる
(Control Plane のコンポーネントについては「Kubernetes のアーキテクチャを整理する:Control Plane と Node の構成要素」を参照)
クラスタの中からアクセスする
ClusterIP の Service には、クラスタの中からしかアクセスできない。クラスタの中に作業用の Pod を作り、その中からアクセスする。
$ kubectl run tmp --image=busybox:1.36 --restart=Never -- sleep 3600
# => pod/tmp created
$ kubectl get pod tmp
NAME READY STATUS RESTARTS AGE
tmp 1/1 Running 0 5s
| 部分 | 意味 |
|---|---|
kubectl run tmp | YAML を使わずに、tmp という名前の Pod を1つ作る |
--image=busybox:1.36 | 使う image。busybox は、基本的なコマンドをまとめた小さな image |
--restart=Never | コンテナが終了しても、起動し直さない |
-- sleep 3600 | コンテナで実行するコマンド。Pod を動かしたままにしておくためのもの |
STATUS が Running になったら、kubectl exec で Pod の中のシェルを起動する。
$ kubectl exec -it tmp -- sh
/ #
ここから先、/ # で始まる行は Pod の中で実行したコマンド。
(busybox:1.28 を使ったときは、シェルや wget が exit code 139 で異常終了することがあった。busybox:1.36 では起きていない)
Service の名前でアクセスする
busybox には curl が入っていないので、wget を使う。宛先は Service の名前。
/ # wget -qO- http://nginx-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
(以下省略)
nginx のページが返ってくる。宛先に指定したのは nginx-service という名前だけで、Pod の名前や IP は使っていない。
- クラスタの中で定義された Service には、DNS 名が割り当てられる
- Service の DNS 名は、Service の IP(
CLUSTER-IP)に名前解決される - 名前だけで届くのは、アクセスする Pod と Service が同じ namespace にある場合。別の namespace の Service は
Service の名前.namespace の名前で指定する
Service の IP でアクセスする
CLUSTER-IP を宛先にしても、同じ結果になる。
/ # wget -qO- http://10.96.28.227
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(以下省略)
アクセスはどの Pod に届いたか
続けて、Pod の中から6回アクセスする。
/ # for i in 1 2 3 4 5 6; do wget -qO /dev/null http://nginx-service; done
/ # exit
ここまでのアクセスは、名前で1回、IP で1回、for で6回の合計8回。どの Pod に届いたかは、nginx のアクセスログで確認できる。
$ kubectl logs -l app=nginx --prefix
| 部分 | 意味 |
|---|---|
-l app=nginx | app: nginx の label が付いた Pod すべてのログを出す。Pod ごとに最後の 10 行まで |
--prefix | 行の先頭に、どの Pod のどのコンテナのログかを付ける |
次の出力は、作業用の Pod(IP は 10.244.1.7)からのアクセスの行を抜き出したもの。
[pod/nginx-deployment-76df5b8bb8-t4jvl/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:21 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-t4jvl/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-t4jvl/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-t4jvl/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-t4jvl/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-mhjqh/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-pkrcd/nginx] 10.244.1.7 - - [29/Sep/2026:12:06:47 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
[pod/nginx-deployment-76df5b8bb8-pkrcd/nginx] 10.244.1.7 - - [29/Sep/2026:12:08:49 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
| Pod | 届いた回数 |
|---|---|
...-t4jvl | 5 |
...-mhjqh | 1 |
...-pkrcd | 2 |
- 同じ宛先へのアクセスが、3つの Pod に分かれて届いている
- 届いた回数は均等ではない
- kube-proxy の iptables モードでは、届け先の Pod は既定でランダムに選ばれる(このクラスタの kube-proxy のモードは確認していない)
Pod が入れ替わったときの動き
Service がある状態で、Pod を1つ消す。
$ kubectl delete pod nginx-deployment-76df5b8bb8-t4jvl
# => pod "nginx-deployment-76df5b8bb8-t4jvl" deleted from default namespace
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-76df5b8bb8-mhjqh 1/1 Running 0 18m 10.244.1.3 k8s-study-worker <none> <none>
nginx-deployment-76df5b8bb8-pkrcd 1/1 Running 0 18m 10.244.2.2 k8s-study-worker2 <none> <none>
nginx-deployment-76df5b8bb8-wf8x7 1/1 Running 0 8s 10.244.1.8 k8s-study-worker <none> <none>
tmp 1/1 Running 0 4m49s 10.244.1.7 k8s-study-worker <none> <none>
$ kubectl get endpointslices
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
kubernetes IPv4 6443 172.21.0.4 3d9h
nginx-service-bf75h IPv4 80 10.244.1.3,10.244.2.2,10.244.1.8 15m
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d9h
nginx-service ClusterIP 10.96.28.227 <none> 80/TCP 15m
| 消す前 | 消した後 | |
|---|---|---|
| Service の名前 | nginx-service | nginx-service |
| Service の IP | 10.96.28.227 | 10.96.28.227 |
| EndpointSlice の届け先 | 10.244.2.3、10.244.1.3、10.244.2.2 | 10.244.1.3、10.244.2.2、10.244.1.8 |
- 消した Pod の IP(
10.244.2.3)が EndpointSlice から外れ、新しい Pod の IP(10.244.1.8)が入っている - EndpointSlice は同じもの(名前が
nginx-service-bf75hのまま)で、中身だけが更新されている - Service の名前と IP は変わっていない
- 作業用の Pod
tmp(10.244.1.7)はapp: nginxの label を持たないので、届け先に入っていない
Kubernetes は、Service の selector に一致する Pod の集合が変わるたびに、その Service の EndpointSlice を更新する。アクセスする側は、Service の名前だけを宛先にしていればよい。
Service を消す
Service と Deployment は別々のリソースなので、それぞれ消す。作業用の Pod も消す。
$ kubectl delete -f nginx-service.yaml
# => service "nginx-service" deleted from default namespace
$ kubectl delete -f nginx-deployment.yaml
# => deployment.apps "nginx-deployment" deleted from default namespace
$ kubectl delete pod tmp
# => pod "tmp" deleted from default namespace
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d9h
$ kubectl get endpointslices
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
kubernetes IPv4 6443 172.21.0.4 3d9h
Service を消すと、EndpointSlice(nginx-service-bf75h)も一緒に消えている。
Service の種類
今回使った ClusterIP のほかにも、Service には種類がある。
| 種類 | 内容 |
|---|---|
ClusterIP | クラスタ内部の IP で Service を公開する。クラスタの中からだけアクセスできる。type を指定しない場合の既定 |
NodePort | 各 Node の IP の、決まったポートで Service を公開する。ポートは既定で 30000〜32767 の範囲から割り当てられる |
LoadBalancer | 外部のロードバランサーを使って、Service をクラスタの外に公開する |
用語メモ
| 用語 | 読み方 | 意味 |
|---|---|---|
| Service | サービス | 複数の Pod に対する宛先を定義するリソース。名前と IP を持つ |
| ClusterIP | クラスターアイピー | Service の種類の1つ。クラスタの中からだけアクセスできる。Service に割り当てられる IP もこう呼ばれる |
| EndpointSlice | エンドポイントスライス | Service の届け先(Pod の IP)の一覧を持つリソース |
| selector | セレクター | どの label が付いたリソースを対象にするかの指定 |
| label | ラベル | リソースに付ける キー: 値 の形の札 |
| kube-proxy | キューブプロキシ | 各 Node で動き、Service 宛ての通信を Pod に転送する設定を行うコンポーネント |
| etcd | エトセディー | クラスタのすべてのデータの保存場所として使われるキーバリューストア |
まとめ
- Pod の IP は、Pod が作り直されると変わる
- Service は、複数の Pod に対する宛先を定義するリソース。名前と IP を持ち、Pod が入れ替わっても変わらない
- Service や Deployment は、動いているプログラムではなく、Control Plane の etcd に保存されたデータ。Node の上で動くのは Pod のコンテナ
- 届け先の Pod は、
selectorに書いた label で決まる。Service の YAML に Pod の名前や IP は書かない - Service を作ると EndpointSlice が自動で作られる。Pod が入れ替わると、EndpointSlice の中身も自動で更新される
- クラスタの中からは、Service の名前を宛先にしてアクセスできる
- Service へのアクセスは、複数の Pod に分かれて届く
typeを指定しない Service はClusterIPで、クラスタの中からだけアクセスできる
参考 URL
- [official] Service | Kubernetes
- [official] EndpointSlice | Kubernetes
- [official] ServiceとPodに対するDNS | Kubernetes
- [official] Virtual IPs and Service Proxies | Kubernetes
- [official] Kubernetesオブジェクトを利用する | Kubernetes
- [official] クラスターのアーキテクチャ | Kubernetes

