新卒インフラエンジニア向け「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=実際の転送先一覧」という関係です。

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
selector:app: 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.html・js/app.js・config.js・css/bootstrap.min.css など)は、第3回の Backend と同様に本シリーズの教材一式に同梱しています。本記事ではビルドに必要な Dockerfile と nginx.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.conf の proxy_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/members が HTTP 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 で行います。ここまでの通信構成を次の図にまとめます。

やってみよう
- Backend の ClusterIP Service(
backend-service.yaml)を作成し、kubectl get svc/get endpointslicesで ClusterIP と転送先 Pod を確認する。 - 一時
curlPod からhttp://fanclub-backend:8080/health/liveがUPを返すこと、nslookupで DNS 名が ClusterIP に解決されることを確認する。 - fanclub-frontend イメージ(v0.1.0)をビルドして k8s-registry に push する。
- Backend Service を作った後で Frontend Pod + Service を作成し、Nginx が起動することを確認する。
curlPod から Frontend の/(静的ページ)と/api/members(Backend へプロキシ → DB 無しで 503)を確認し、port-forwardでブラウザ表示する。
理解度チェック
次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。
- Pod の IP は再作成で変わるため、安定した通信先として Service を使う。
- Service のタイプを省略すると既定で
NodePortになる。 - クラスタ内 DNS 名は
<service>.<namespace>.svc.cluster.localの形式である。 - Service は selector に一致する Pod を EndpointSlice として管理し、そこへ転送する。
- Nginx の
proxy_passは起動時に upstream 名を解決するため、Backend Service は Frontend より先に作る必要がある。 /api/membersが 503 を返したのは、Backend に到達できていない(経路が未成立)ためである。- 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 と StatefulSet で PostgreSQL を追加し、3 層アプリを完成させます。永続ストレージ(PV / PVC / StorageClass)と StatefulSet の仕組みを学び、DB を永続化します。DB がつながると /api/members が UP になり、ブラウザから会員の登録・一覧・編集・削除が動くようになります。
シリーズ一覧
第1部:コンテナと Docker
- 第1回 コンテナ技術概念 + Docker 環境準備
- 第2回 Docker 基本操作
- 第3回 Dockerfile + マルチステージ + JDK 25 / Payara Micro イメージビルド
- 第4回 コンテナレジストリ + イメージタグ戦略 + Trivy スキャン
第2部:Kubernetes 基礎
第3部:アプリリソース
- 第7回 Pod + Multi-container パターン
- 第8回 Service とネットワーキング ← 今ここ
- 第9回 ストレージ(PVC + StatefulSet)+ PostgreSQL DB 追加
- 第10回 ConfigMap + Secret + ServiceAccount 基礎
- 第11回 Job + CronJob + DaemonSet
第4部:ワークロード戦略
- 第12回 Deployment + 3 Probe + Rolling Update + Probe デバッグ実践
- 第13回 Deployment 戦略補完(Blue/Green + Canary + Recreate)
- 第14回 ResourceQuota + LimitRange + Multi-tenant Namespace
