本記事には広告(アフィリエイトリンク)が含まれます。

Serviceとネットワーキング|CKAD第8回

広告

新卒インフラエンジニア向け「Kubernetes 実践教科書① CKAD アプリケーション開発編」の第8回です。第7回で fanclub-backend を Pod として動かしました。しかし Pod の IP は再作成のたびに変わるため、他から安定して呼び出すには Service が要ります。今回は Service の役割と 4 つのタイプ、クラスタ内 DNS、セレクタとエンドポイントを学び、Backend に ClusterIP Service を付けます。さらに fanclub-frontend(Nginx)Pod を新たに追加し、Frontend → Backend のクラスタ内通信を確立します(模擬アプリが 2 層になります)。

動作確認バージョン:kind v0.31.0(K8s v1.35.0)/ kubectl v1.35.6 / fanclub-backend:0.2.0 / fanclub-frontend:0.1.0(Nginx 1.27-alpine)/ curlimages/curl(2026-06-25 実機検証時点)

広告

今ここマップ(全 19 回中の現在地)

現在地は第 3 部「アプリリソース」の第 8 回です。Backend に続き Frontend を加え、Service でつなぎます。

  • 第 1 部 コンテナと Docker(第 1〜4 回)
  • 第 2 部 Kubernetes 基礎(第 5〜6 回)
  • 第 3 部 アプリリソース(第 7〜11 回)← 今ここ
  • 第 4 部 ワークロード戦略(第 12〜14 回)
  • 第 5 部 セキュリティ基礎(第 15〜16 回)
  • 第 6 部 パッケージ管理と HTTPS 公開(第 17〜19 回)

この回のゴール

  • Pod 間通信を Service で実現し、ClusterIP / NodePort / LoadBalancer / ExternalName を使い分けられる。
  • クラスタ内 DNS(<service>.<namespace>.svc.cluster.local)とセレクタ・エンドポイントの関係を説明できる。
  • fanclub-frontend Pod を追加し、Service 経由で Frontend → Backend 通信を確認できる。

なぜ Service が必要か

第7回の Backend Pod には 10.244.x.x の IP が割り当てられました。しかし Pod の IP は再作成(再起動・スケール・ノード移動)のたびに変わります。呼び出す側が IP を直接知っていると、相手が作り直された瞬間に通信が壊れます。

Service は、入れ替わる Pod 群の前に立つ安定した名前と仮想 IP(と負荷分散)を提供します。呼び出す側は Service の名前(DNS)を使うだけでよく、背後の Pod がどれだけ入れ替わっても通信先は変わりません。Service はラベルセレクタで「どの Pod 群に転送するか」を決めます。

Service の 4 タイプ

  • ClusterIP(既定):クラスタ内部だけで使える仮想 IP。Pod 間通信の基本。本回で主に使います。
  • NodePort:各ノードの特定ポートを開けて外部から到達可能にする。簡易な外部公開向け。
  • LoadBalancer:クラウドのロードバランサと連携して外部公開する。kind のような学習環境では既定では機能が限定的です。
  • ExternalName:外部ホスト名への CNAME を返すだけの Service(クラスタ外サービスへの別名)。

本シリーズでは内部通信に ClusterIP を使い、ブラウザからの外部公開(HTTPS)は第18回の Gateway API で行います。

クラスタ内 DNS 解決

Service には DNS 名が自動で割り当てられます。形式は <service>.<namespace>.svc.cluster.local で、これを解決するのが第5回で見た CoreDNS です。同じ Namespace 内なら fanclub-backend のように短い名前でも解決できます。Nginx や各 Pod はこの名前で Backend にアクセスします。

セレクタとエンドポイント

Service は selector(ラベル条件)に一致する Pod を探し、その実体(Pod の IP とポート)を EndpointSlice として管理します。Service 宛の通信は、この EndpointSlice に載った Pod へ転送されます(従来の Endpoints リソースは v1.33 以降 deprecated で、現在は EndpointSlice が標準です)。「Service=安定した名前」「EndpointSlice=実際の転送先一覧」という関係です。

呼び出し側が Service の安定した名前で呼ぶと、Service が selector で一致した Pod を EndpointSlice として管理し、実際の Backend Pod へ転送する。Pod の IP は可変だが Service の名前と ClusterIP は不変であることを示す図。
図1:Service の仕組み(selector で一致した Pod を EndpointSlice が管理し転送・名前と ClusterIP は不変)

Backend に ClusterIP Service を作る

Backend Pod(ラベル app: fanclub-backend)に ClusterIP Service を付けます。次のマニフェストを developer のホームに backend-service.yaml として作成します(一般ユーザー所有・root 不要)。

apiVersion: v1
kind: Service
metadata:
  name: fanclub-backend
spec:
  selector:
    app: fanclub-backend
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  • selectorapp: fanclub-backend のラベルを持つ Pod に転送します。
  • port:Service が受けるポート。targetPort:転送先 Pod のポート(Backend は 8080)。
  • type を省略すると既定の ClusterIP になります。

実行コマンド:

$ kubectl apply -f backend-service.yaml
$ kubectl get svc fanclub-backend
$ kubectl get endpointslices -l kubernetes.io/service-name=fanclub-backend

実行結果(例):Service に ClusterIP が付き、EndpointSlice に Backend Pod の IP が載ります。EndpointSlice は Service 作成の数秒後に生成されるため、直後に get endpointslices しても空のことがあります(その場合は数秒待って再実行します)。

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
fanclub-backend   ClusterIP   10.96.23.247   <none>        8080/TCP   5s

NAME                    ADDRESSTYPE   PORTS   ENDPOINTS      AGE
fanclub-backend-hcx8k   IPv4          8080    10.244.82.15   5s

Service が機能するか、一時的な curl Pod から名前で叩いて確認します。--rm で終了時に自動削除されます。

実行コマンド:

$ kubectl run curltest --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-backend:8080/health/live

実行結果(例):

{"status":"UP","checks":[{"name":"fanclub-api-live","status":"UP","data":{}}]}

DNS 名での解決も確認できます(一時的な busybox Pod)。

実行コマンド:

$ kubectl run dnstest --rm -i --restart=Never --image=busybox:1.37 -- \
    nslookup fanclub-backend.default.svc.cluster.local

実行結果(例・抜粋):Service の ClusterIP が返ります。

Name:	fanclub-backend.default.svc.cluster.local
Address: 10.96.23.247

fanclub-frontend Pod を追加して Backend につなぐ

会員管理 UI を提供する fanclub-frontend(Nginx + 静的 HTML/JS/Bootstrap)を追加します。Frontend の Nginx は /api/ へのリクエストを Backend Service へリバースプロキシします。これによりブラウザからは「同じオリジンの /api」を呼ぶだけでよく、第9回で DB を足せば port-forward 経由でブラウザから CRUD が動くようになります。

Frontend イメージをビルドして push する

Frontend のソース一式(Dockerfile / nginx.conf と、html/ 配下の index.htmljs/app.jsconfig.jscss/bootstrap.min.css など)は、第3回の Backend と同様に本シリーズの教材一式に同梱しています。本記事ではビルドに必要な Dockerfilenginx.conf の要点だけを示します。これらを developer のホームの fanclub-frontend/(一般ユーザー所有)に置き、第2〜4回の Docker スキルでビルド・push します。Dockerfile は軽量で、Nginx に静的ファイルと設定を載せるだけです。

FROM nginx:1.27-alpine
COPY html/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

nginx.conf の要点は /api/リバースプロキシです(Bootstrap はイメージに同梱し、CDN は使いません)。

location /api/ {
    proxy_pass http://fanclub-backend:8080;
}
location / {
    try_files $uri $uri/ =404;
}

実行コマンド:

$ cd ~/fanclub-frontend
$ docker build -t fanclub-frontend:0.1.0 .
$ docker tag fanclub-frontend:0.1.0 k8s-registry:5000/fanclub-frontend:0.1.0
$ docker push k8s-registry:5000/fanclub-frontend:0.1.0

順序に注意nginx.confproxy_pass http://fanclub-backend:8080 は、Nginx が起動時に fanclub-backend の名前を解決します。そのため Backend Service を先に作ってから Frontend Pod を起動してください。順序が逆だと Nginx が host not found in upstream "fanclub-backend" で起動に失敗します。

Frontend Pod と Service を作る

Frontend の Pod と ClusterIP Service を frontend.yaml として作成します(一般ユーザー所有・root 不要)。--- で 2 つのリソースを 1 ファイルにまとめています。

apiVersion: v1
kind: Pod
metadata:
  name: fanclub-frontend
  labels:
    app: fanclub-frontend
spec:
  containers:
  - name: frontend
    image: k8s-registry:5000/fanclub-frontend:0.1.0
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "32Mi"
        cpu: "50m"
      limits:
        memory: "128Mi"
        cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: fanclub-frontend
spec:
  selector:
    app: fanclub-frontend
  ports:
  - name: http
    port: 80
    targetPort: 80

実行コマンド:

$ kubectl apply -f frontend.yaml
$ kubectl get pod fanclub-frontend -o wide

実行結果(例):

NAME               READY   STATUS    RESTARTS   AGE   IP             NODE
fanclub-frontend   1/1     Running   0          10s   10.244.82.18   kind-control-plane

Nginx が upstream のエラーなく起動したことをログで確認します。

実行コマンド:

$ kubectl logs fanclub-frontend | tail -3

実行結果(例):

2026/06/25 13:36:35 [notice] 1#1: start worker processes
2026/06/25 13:36:35 [notice] 1#1: start worker process 36
2026/06/25 13:36:35 [notice] 1#1: start worker process 37

Frontend → Backend の通信を確認する

一時的な curl Pod から Frontend Service を叩きます。静的ページ(/)が返り、/api/members は Nginx が Backend へプロキシします。

実行コマンド:

$ kubectl run c1 --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-frontend/ | grep title
$ kubectl run c2 --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s -o /dev/null -w "HTTP %{http_code}\n" http://fanclub-frontend/api/members

実行結果(例):

<title>fanclub-api 会員管理</title>
HTTP 503

静的ページが配信され、/api/membersHTTP 503 を返しました。これは「Frontend の Nginx が /api を Backend へ正しくプロキシし、Backend には到達したが、DB がまだ無いため Backend が 503(準備未完)を返している」状態です。つまり Frontend → Backend の経路は成立しています。本物の会員データで CRUD が動くのは、第9回で PostgreSQL を追加してからです。

ブラウザで UI を見るには kubectl port-forward で Frontend Service をローカルに転送します。確認後は Ctrl-C で停止します。

実行コマンド:

$ kubectl port-forward svc/fanclub-frontend 8888:80

ブラウザで http://localhost:8888/ を開くと会員管理画面が表示されます(DB 未追加のため一覧は空・登録もまだ保存されません)。HTTPS での公開(https://fanclub.local)は第18回の Gateway API で行います。ここまでの通信構成を次の図にまとめます。

ブラウザ/curl が Frontend Service 経由で Nginx(fanclub-frontend)にアクセスし、静的ファイルは Nginx が配信、/api/ は Backend Service 経由で Backend Pod へリバースプロキシされる構成図。PostgreSQL は第9回で追加(点線)。現在 /api/members は DB 未追加のため HTTP 503。
図2:第8回の通信構成(Frontend Service → Nginx → /api → Backend Service → Backend Pod、DB は第9回)

やってみよう

  1. Backend の ClusterIP Service(backend-service.yaml)を作成し、kubectl get svc / get endpointslices で ClusterIP と転送先 Pod を確認する。
  2. 一時 curl Pod から http://fanclub-backend:8080/health/liveUP を返すこと、nslookup で DNS 名が ClusterIP に解決されることを確認する。
  3. fanclub-frontend イメージ(v0.1.0)をビルドして k8s-registry に push する。
  4. Backend Service を作った後で Frontend Pod + Service を作成し、Nginx が起動することを確認する。
  5. curl Pod から Frontend の /(静的ページ)と /api/members(Backend へプロキシ → DB 無しで 503)を確認し、port-forward でブラウザ表示する。

理解度チェック

次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。

  1. Pod の IP は再作成で変わるため、安定した通信先として Service を使う。
  2. Service のタイプを省略すると既定で NodePort になる。
  3. クラスタ内 DNS 名は <service>.<namespace>.svc.cluster.local の形式である。
  4. Service は selector に一致する Pod を EndpointSlice として管理し、そこへ転送する。
  5. Nginx の proxy_pass は起動時に upstream 名を解決するため、Backend Service は Frontend より先に作る必要がある。
  6. /api/members が 503 を返したのは、Backend に到達できていない(経路が未成立)ためである。
  7. ClusterIP Service はクラスタ内部からのみアクセスできる。

解答

  • 1. ○:Pod IP は不安定。Service が安定した名前と仮想 IP を提供する。
  • 2. ×:既定は ClusterIP
  • 3. ○:CoreDNS がこの形式の名前を解決する。
  • 4. ○:selector→EndpointSlice(実体の Pod IP)→転送、の関係。
  • 5. ○:リテラル名の proxy_pass は起動時解決。Backend Service を先に作る。
  • 6. ×:到達はしている。503 は Backend が DB 未準備で返したもの(第9回で DB 追加後に解消)。
  • 7. ○:ClusterIP は内部専用。外部公開は NodePort / LoadBalancer / Gateway API。

まとめ

本記事では、Pod IP の不安定さを起点に Service の役割と 4 タイプ、クラスタ内 DNS、セレクタと EndpointSlice を押さえ、Backend に ClusterIP Service を付けました。さらに fanclub-frontend(Nginx)Pod を追加し、Nginx の /api リバースプロキシで Frontend → Backend の通信を確立しました。/api/members が 503 を返したのは Backend に到達できている証拠で、残るは DB です。これで模擬アプリは Frontend と Backend の 2 層になりました。

次回予告

次回(第9回)は PVC と StatefulSetPostgreSQL を追加し、3 層アプリを完成させます。永続ストレージ(PV / PVC / StorageClass)と StatefulSet の仕組みを学び、DB を永続化します。DB がつながると /api/members が UP になり、ブラウザから会員の登録・一覧・編集・削除が動くようになります。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

広告
kubernetes
スポンサーリンク