Kubernetes入門
第3回:Serviceによる「名前解決」の自動化
前回、私たちは衝撃的な事実を目の当たりにしました。Podは「揮発性」であり、再起動のたびにIPアドレスが変わってしまう——。VMの世界では考えられないこの仕様に、戸惑いを感じた方も多いでしょう。
「IPが変わるなら、どうやって通信先を特定するんだ?」
その疑問は正しい。そして今回、Kubernetesがその問いに対して用意した、エレガントな答えを体験していただきます。それがServiceです。
本記事を読み終える頃、あなたは「IPアドレス管理台帳」という名のExcelファイルを、そっと閉じたくなるかもしれません。
3.1 ネットワークの抽象化:IP管理からの解放
3.1.1 VM時代の負債「IPアドレス管理台帳(Excel)」を捨てられる理由
思い出してください。新しいVMを構築するたびに、あなたは何をしていましたか?
- IPアドレス管理台帳(Excel)を開く
- 空いているIPを探す
- 新VMに割り当て、台帳に記録
- DNSサーバーにAレコードを手動登録
- 関連するアプリケーションの設定ファイルを書き換え
- 変更管理票を起票し、承認を得る
この一連の作業、何度繰り返してきたでしょうか。そして、誰かが台帳の更新を忘れたせいで発生した「IPアドレス重複事故」や、退職した先輩だけが知っていた「謎の固定IP」に悩まされた経験は?
Kubernetesは、この負債を根本から解消します。
Serviceという仕組みを使えば、「このサービスに接続したい」という意図を名前で表現できます。背後にあるPodが何台あろうと、IPが何であろうと、呼び出す側は一切気にする必要がありません。
| 観点 | VM時代 | Kubernetes時代 |
|---|---|---|
| 通信先の指定 | IPアドレス(192.168.1.100) | サービス名(my-web-service) |
| IP管理 | Excel台帳で手動管理 | K8sが自動管理(人間は関与しない) |
| DNS登録 | 手動でAレコード追加 | 自動(CoreDNS が即時反映) |
| スケール時 | 全設定ファイルを書き換え | 何もしない(Serviceが吸収) |
3.1.2 Podが何台増えても、入り口は一つ:Service(ClusterIP)の仕組み
Serviceの基本形であるClusterIPを理解しましょう。
┌─────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────┐ │
│ │ Service │ │
│ │ Name: my-web │ │
│ │ ClusterIP: │ ┌─────┐ │
│ │ 10.96.100.50 │── ▶│ Pod A │ IP: 10.244.0.5 │
│ │ │ │ nginx │ │
│ │ Port: 80 │ └─────┘ │
│ │ │ │
│ │ │ ┌─────┐ │
│ │ │── ▶│ Pod B │ IP: 10.244.0.6 │
│ │ │ │ nginx │ │
│ └──────────┘ └─────┘ │
│ ▲ │
│ │ │
│ ┌────┴────┐ │
│ │ Client Pod │ │
│ │ curl my-web:80 │ ← IPを知らなくてもアクセス可能 │
│ └─────────┘ │
└─────────────────────────────────────────┘
Serviceは仮想的なロードバランサーです。クライアントはServiceの名前(またはClusterIP)にアクセスするだけで、背後にある複数のPodに自動的にトラフィックが分散されます。
VMの世界で言えば、「F5 BIG-IPやHAProxyが、DNSと連携して全自動で構成される」ようなものです。しかも無料で、数秒で。
Serviceが解決する3つの問題
- サービスディスカバリ: 「このサービスはどこにいる?」→ 名前で引ける
- 負荷分散: 複数Podへのトラフィック分散が自動
- 障害時の切り離し: 死んだPodへは自動的にルーティングされなくなる
3.1.3 2026年の標準:Gateway API(Ingressの後継)への入り口
本連載では内部通信(ClusterIP)と簡易的な外部公開(NodePort)を扱いますが、本番環境での外部公開にはGateway APIが2026年現在の標準です。
従来のIngressは、以下の課題を抱えていました:
- 実装ごとにアノテーションが異なり、移植性が低い
- 高度なトラフィック制御(ヘッダーベースルーティング等)が標準化されていない
- ロール(インフラ管理者 vs アプリ開発者)の分離が不明確
Gateway API v1.2+ は、これらを解決するために設計された次世代の標準です:
┌──────────────────────────────┐
│ Gateway API のリソース階層 │
│ │
│ GatewayClass(インフラ管理者が定義) │
│ │ │
│ ▼ │
│ Gateway(クラスタ管理者が定義) │
│ │ - リスナー(ポート、プロトコル) │
│ ▼ │
│ HTTPRoute(アプリ開発者が定義) │
│ - パスベースルーティング │
│ - ヘッダーマッチング │
│ - 重み付けトラフィック分割 │
└──────────────────────────────┘
Gateway APIの詳細は連載後半で扱いますが、今回学ぶServiceの概念は、Gateway APIを理解する上でも土台となります。まずはServiceをしっかり身につけましょう。
3.2 実践:サービス間通信の魔法
理論はここまで。手を動かして、Serviceの威力を体感しましょう。
3.2.1 複数のWebサーバを一つのServiceで束ねるマニフェスト作成
まず、前回作成したkindクラスターが稼働していることを確認します。
[Execution User: developer]
kind get clusters
my-cluster が表示されれば準備完了です。表示されない場合は、前回の手順でクラスターを再作成してください。
Deploymentの作成:3台のnginx Pod
Podを直接作成するのではなく、Deploymentを使って「nginx Podを3台維持する」という宣言をします。
[Execution User: developer]
cat << 'EOF' > 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-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
EOF
[Execution User: developer]
kubectl apply -f nginx-deployment.yaml
出力例:
deployment.apps/nginx-deployment created
Podが3台起動したことを確認します。
[Execution User: developer]
kubectl get pods -l app=nginx -o wide
出力例:
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-7c5b8f4d9-abc12 1/1 Running 0 30s 10.244.0.5 my-cluster-worker
nginx-deployment-7c5b8f4d9-def34 1/1 Running 0 30s 10.244.0.6 my-cluster-worker2
nginx-deployment-7c5b8f4d9-ghi56 1/1 Running 0 30s 10.244.0.7 my-cluster-worker
3台のPodが、それぞれ異なるIPアドレスを持っていますね。VM時代なら、この3つのIPを台帳に記録し、ロードバランサーに登録する作業が発生していました。
Serviceの作成:3台を1つの名前で束ねる
[Execution User: developer]
cat << 'EOF' > nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx # このラベルを持つPodが対象
ports:
- protocol: TCP
port: 80 # Serviceが受け付けるポート
targetPort: 80 # Podに転送するポート
type: ClusterIP # クラスタ内部からのみアクセス可能
EOF
[Execution User: developer]
kubectl apply -f nginx-service.yaml
出力例:
service/nginx-service created
Serviceが作成されたことを確認します。
[Execution User: developer]
kubectl get service nginx-service
出力例:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 10.96.142.87 <none> 80/TCP 10s
ここが革命的なポイントです。
CLUSTER-IP: 10.96.142.87という仮想IPが自動で割り当てられた- このIPは、背後の3台のPodへの「入り口」として機能する
- さらに、
nginx-serviceという名前でもアクセス可能(DNS自動登録)
3.2.2 内部DNSの確認:別Podから「サービス名」で疎通できる感動を体験
Serviceの真価は、名前でアクセスできることにあります。これを体験しましょう。
一時的なデバッグ用Podを起動し、そこからServiceにアクセスします。
[Execution User: developer]
kubectl run debug-pod --rm -it --restart=Never \
--image=curlimages/curl:8.11.1 \
-- /bin/sh
このコマンドは、curlが使えるPodを一時的に起動し、シェルに入ります。--rm オプションにより、終了時に自動削除されます。
シェルに入ったら、以下を実行してください。
[Execution User: debug-pod内]
# サービス名でアクセス(IPアドレス不要!)
curl -s nginx-service
出力例:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
nginx のウェルカムページが表示されました!
もう一度実行してみてください。
[Execution User: debug-pod内]
curl -s nginx-service
同じ結果が返ってきますが、実は背後では異なるPodに振り分けられている可能性があります。
DNSの名前解決を確認してみましょう。
[Execution User: debug-pod内]
nslookup nginx-service
出力例:
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: nginx-service.default.svc.cluster.local
Address: 10.96.142.87
10.96.0.10はKubernetesの内部DNSサーバー(CoreDNS)nginx-service.default.svc.cluster.localが正式なFQDN- 同一Namespace内では、単に
nginx-serviceで名前解決可能
デバッグPodから抜けるには exit を入力します。
[Execution User: debug-pod内]
exit
3.2.3 検証:背後のPodを全部落としても、サービス名での通信が自動復旧する様子を確認
ここからが本当の見せ場です。Podを全て落としても、サービスへのアクセスが自動的に復旧することを証明します。
ステップ1:現在のPod一覧を確認
[Execution User: developer]
kubectl get pods -l app=nginx -o wide
3台のPodとそのIPアドレスをメモしておいてください。
ステップ2:全Podを削除
[Execution User: developer]
kubectl delete pods -l app=nginx
出力例:
pod "nginx-deployment-7c5b8f4d9-abc12" deleted
pod "nginx-deployment-7c5b8f4d9-def34" deleted
pod "nginx-deployment-7c5b8f4d9-ghi56" deleted
ステップ3:Deploymentが自動復旧する様子を観察
[Execution User: developer]
kubectl get pods -l app=nginx -o wide --watch
出力例:
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-7c5b8f4d9-jkl78 0/1 ContainerCreating 0 2s <none> my-cluster-worker
nginx-deployment-7c5b8f4d9-mno90 0/1 ContainerCreating 0 2s <none> my-cluster-worker2
nginx-deployment-7c5b8f4d9-pqr12 0/1 ContainerCreating 0 2s <none> my-cluster-worker
nginx-deployment-7c5b8f4d9-jkl78 1/1 Running 0 5s 10.244.0.15 my-cluster-worker
nginx-deployment-7c5b8f4d9-mno90 1/1 Running 0 5s 10.244.0.16 my-cluster-worker2
nginx-deployment-7c5b8f4d9-pqr12 1/1 Running 0 5s 10.244.0.17 my-cluster-worker
Ctrl+C で監視を終了します。
注目してください。 新しいPodには、全く異なるIPアドレスが割り当てられています(10.244.0.15, 16, 17)。
ステップ4:サービス名でのアクセスを再確認
[Execution User: developer]
kubectl run debug-pod --rm -it --restart=Never \
--image=curlimages/curl:8.11.1 \
-- curl -s nginx-service
出力例:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
何事もなかったかのように、nginxが応答しています。
VM時代なら、この状況は大惨事でした:
- 3台のVMが同時にダウン
- 新しいVMを3台構築
- 新しいIPアドレスを台帳に登録
- ロードバランサーの設定を更新
- DNSレコードを更新
- アプリケーションの設定ファイルを修正
- 関係者への報告と変更管理
Kubernetesでは、これらすべてが自動で行われます。 あなたがやることは何もありません。深夜に電話が鳴ることもありません。
3.3 応用:外部公開の仕組み(NodePortとポートマッピング)
ClusterIPは素晴らしい仕組みですが、一つ制約があります。クラスタ内部からしかアクセスできないのです。
開発中のWebアプリを、自分のPCのブラウザで確認したい——そんなときに使うのがNodePortです。
3.3.1 ClusterIP(内部用)と NodePort(外部用)の使い分け
| タイプ | アクセス範囲 | 用途 |
|---|---|---|
| ClusterIP | クラスタ内部のみ | マイクロサービス間通信、DB接続など |
| NodePort | ノードのIP経由で外部から | 開発環境での動作確認、簡易的な公開 |
| LoadBalancer | クラウドLBと連携 | 本番環境での外部公開(AWSのELB等) |
本番環境での外部公開には、NodePortは使いません。 クラウド環境ではLoadBalancer、オンプレミスではMetalLBやGateway APIを使用します。NodePortは開発・検証用と割り切ってください。
3.3.2 VMのIPアドレスでWebサイトにアクセスするための「仕上げ」
kindでNodePortを使うには、クラスター作成時にポートマッピングを設定する必要があります。前回のクラスターを一度削除し、設定を追加して再作成しましょう。
[Execution User: developer]
kind delete cluster --name my-cluster
NodePort用のポートマッピングを含むクラスター設定を作成します。
[Execution User: developer]
cat << 'EOF' > kind-config-nodeport.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080 # NodePortのポート
hostPort: 8080 # ホストマシン(VM)のポート
protocol: TCP
- role: worker
- role: worker
EOF
[Execution User: developer]
kind create cluster --name my-cluster --config kind-config-nodeport.yaml
クラスターが起動したら、先ほどのDeploymentとServiceを再作成します。
[Execution User: developer]
kubectl apply -f nginx-deployment.yaml
今度はNodePortタイプのServiceを作成します。
[Execution User: developer]
cat << 'EOF' > nginx-nodeport-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080 # 固定のNodePort番号(30000-32767)
type: NodePort
EOF
[Execution User: developer]
kubectl apply -f nginx-nodeport-service.yaml
Serviceを確認します。
[Execution User: developer]
kubectl get service nginx-nodeport
出力例:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-nodeport NodePort 10.96.200.45 <none> 80:30080/TCP 5s
80:30080/TCP という表記は、「Serviceのポート80が、ノードのポート30080にマッピングされている」ことを意味します。
ブラウザからアクセス
ホストマシン(あなたのPC、またはkindを動かしているVM)のブラウザで以下にアクセスしてください。
http://localhost:8080
もしくはcurlで確認:
[Execution User: developer]
curl -s http://localhost:8080
nginxのウェルカムページが表示されれば成功です!
┌─────────────────────────────────────┐
│ 外部からのアクセス経路 │
│ │
│ ブラウザ │
│ │ │
│ │ http://localhost:8080 │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Host Machine (VM) │ │
│ │ Port 8080 │ │
│ │ │ │ │
│ │ │ Docker Port Mapping │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ kind Control Plane Node │ │ │
│ │ │ Port 30080 (NodePort) │ │ │
│ │ │ │ │ │ │
│ │ │ │ kube-proxy │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌─────────┐ │ │ │
│ │ │ │ Service │ │ │ │
│ │ │ │ nginx-nodeport │ │ │ │
│ │ │ │ Port 80 │ │ │ │
│ │ │ └────┬────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ┌──┴───┬───────┐ │ │ │
│ │ │ ▼ ▼ ▼ │ │ │
│ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ │
│ │ │ │Pod A │ │Pod B │ │Pod C │ │ │ │
│ │ │ │nginx │ │nginx │ │nginx │ │ │ │
│ │ │ └───┘ └───┘ └───┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
3.3.3 【次回予告】手順書を卒業する「宣言的定義(YAML)」の世界
今回、あなたは以下を体験しました:
- IPアドレスを一切意識せずにサービスにアクセスできる
- Podが全滅しても、サービス名での通信が自動復旧する
- 数行のYAMLで、ロードバランサーとDNSが自動構成される
これらはすべて、Kubernetesの「宣言的(Declarative)」な思想から生まれています。
次回の第4回では、この思想の核心に迫ります:
- 命令的(Imperative) vs 宣言的(Declarative):なぜ手順書が不要になるのか
- あるべき姿を書くだけで、K8sがその状態を維持し続ける仕組み
- Infrastructure as Codeの本当の意味
VMエンジニアにとって最も大きなパラダイムシフトが、次回待っています。
3.4 トラブルシューティングのTips
「Serviceにアクセスしても応答がない(Timeout)」ときの確認ポイント
Serviceを作成したのに、アクセスするとタイムアウトする——これは初心者が必ずハマる罠です。原因の9割はラベルの不一致です。
確認コマンド:kubectl get endpoints
[Execution User: developer]
kubectl get endpoints nginx-service
正常な場合:
NAME ENDPOINTS AGE
nginx-service 10.244.0.15:80,10.244.0.16:80,10.244.0.17:80 10m
異常な場合(ラベル不一致):
NAME ENDPOINTS AGE
nginx-service <none> 10m
ENDPOINTS が <none> の場合、ServiceはどのPodにも紐づいていません。
原因の特定
Serviceのselectorと、Podのlabelを突き合わせて確認します。
[Execution User: developer]
# Serviceのselectorを確認
kubectl get service nginx-service -o jsonpath='{.spec.selector}' | jq .
出力例:
{
"app": "nginx"
}
[Execution User: developer]
# Podのlabelを確認
kubectl get pods --show-labels
出力例:
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-7c5b8f4d9-jkl78 1/1 Running 0 5m app=nginx,pod-template-hash=7c5b8f4d9
Serviceの selector: app=nginx と、Podの app=nginx が一致していれば、正しく紐づきます。
よくあるミス
| 症状 | 原因 | 対処 |
|---|---|---|
ENDPOINTS が <none> | Serviceのselectorとpodのlabelが不一致 | labelを修正 |
| ENDPOINTS はあるが接続できない | targetPortの指定ミス | コンテナが実際にListenしているポートを確認 |
| 名前解決できない | Namespace が異なる | service-name.namespace でアクセス |
デバッグの定石
[Execution User: developer]
# 1. Endpointsを確認(Podとの紐づき)
kubectl get endpoints nginx-service
# 2. Serviceの詳細を確認
kubectl describe service nginx-service
# 3. Podの状態を確認
kubectl get pods -l app=nginx -o wide
# 4. Pod内から直接curlしてコンテナ自体の動作を確認
kubectl exec -it <pod-name> -- curl localhost:80
この順番で確認すれば、問題の箇所を素早く特定できます。
まとめ:今回学んだこと
| 学習項目 | VM時代 | Kubernetes時代 |
|---|---|---|
| 通信先の特定 | IPアドレス台帳で管理 | サービス名で自動解決 |
| ロードバランシング | F5/HAProxyを手動構築 | Serviceが自動提供 |
| DNS登録 | 手動でAレコード追加 | CoreDNSが即時反映 |
| スケール時の対応 | 設定ファイル書き換え | 何もしない |
| 障害復旧時 | IP変更→全設定修正 | 自動(透過的) |
今回の最重要コマンド:
[Execution User: developer]
# Service作成(クラスタ内部用)
kubectl apply -f nginx-service.yaml
# Service作成(外部公開用)
kubectl apply -f nginx-nodeport-service.yaml
# トラブルシューティングの第一歩
kubectl get endpoints <service-name>
次回予告:第4回「宣言的定義(YAML)の哲学」
「サーバーの状態を手順書で管理する」時代は終わりました。次回は、「あるべき姿を宣言し、システムに維持させる」という、Kubernetesの根幹思想に迫ります。
本記事の内容は、2026年1月時点のKubernetes v1.32、kind v0.26、AlmaLinux 10環境で検証しています。
