Kubernetes入門 第3回:Serviceによる「名前解決」の自動化

Kubernetes入門
第3回:Serviceによる「名前解決」の自動化


前回、私たちは衝撃的な事実を目の当たりにしました。Podは「揮発性」であり、再起動のたびにIPアドレスが変わってしまう——。VMの世界では考えられないこの仕様に、戸惑いを感じた方も多いでしょう。

「IPが変わるなら、どうやって通信先を特定するんだ?」

その疑問は正しい。そして今回、Kubernetesがその問いに対して用意した、エレガントな答えを体験していただきます。それがServiceです。

本記事を読み終える頃、あなたは「IPアドレス管理台帳」という名のExcelファイルを、そっと閉じたくなるかもしれません。


3.1 ネットワークの抽象化:IP管理からの解放

3.1.1 VM時代の負債「IPアドレス管理台帳(Excel)」を捨てられる理由

思い出してください。新しいVMを構築するたびに、あなたは何をしていましたか?

  1. IPアドレス管理台帳(Excel)を開く
  2. 空いているIPを探す
  3. 新VMに割り当て、台帳に記録
  4. DNSサーバーにAレコードを手動登録
  5. 関連するアプリケーションの設定ファイルを書き換え
  6. 変更管理票を起票し、承認を得る

この一連の作業、何度繰り返してきたでしょうか。そして、誰かが台帳の更新を忘れたせいで発生した「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つの問題

  1. サービスディスカバリ: 「このサービスはどこにいる?」→ 名前で引ける
  2. 負荷分散: 複数Podへのトラフィック分散が自動
  3. 障害時の切り離し: 死んだ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時代なら、この状況は大惨事でした:

  1. 3台のVMが同時にダウン
  2. 新しいVMを3台構築
  3. 新しいIPアドレスを台帳に登録
  4. ロードバランサーの設定を更新
  5. DNSレコードを更新
  6. アプリケーションの設定ファイルを修正
  7. 関係者への報告と変更管理

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環境で検証しています。