新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第9回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / MetalLB v0.16.0(Helm chart 0.16.0)/ kubeadm v1.35.5(2026-05-24 時点)
- 今ここマップ(第9回 / 全16回 / 第3部開始)
- 第9回のスコープと設計判断 — svc-demo Namespace で安全に演習する
- Service 4 タイプの動作原理とユースケース
- Endpoints と EndpointSlice — Pod との対応を確認する
- kube-proxy の役割 — iptables/nftables/ipvs モードを概観する
- MetalLB の仕組み — オンプレ環境で LoadBalancer を実現する
- やってみよう①: MetalLB インストール(Helm v4 + L2 モード設定)
- やってみよう②: 4 種 Service デプロイと挙動確認
- やってみよう③: kubectl explain で Service 全フィールドを確認する
- まとめ・現場ヒヤリハット・理解度チェック
- シリーズ一覧
今ここマップ(第9回 / 全16回 / 第3部開始)
今ここ: 第9回 / 全16回(第3部:ネットワーク)
▓▓▓▓▓▓▓▓▓░░░░░░░ 56%
第1部(クラスタ構築): ■■■■■ 5/5 回(完了)
第2部(ワークロード管理): ■■■ 3/3 回(完了)
第3部(ネットワーク): ■□□ 1/3 回 ← 今ここ
第4部(ストレージ): □ 0/1 回
第5部(監視・運用): □□ 0/2 回
第6部(トラブルシュート): □□ 0/2 回
第8回では metrics-server / HPA / ResourceQuota + LimitRange による CKA D2「Workloads and Scheduling」を完走しました。第9回から第3部「ネットワーク」に入ります。CKA D3(Services and Networking・20%)の開始回です。
第1巻では kind クラスタ上で Service を扱いましたが、kind には MetalLB が存在しないため LoadBalancer type Service は <pending> のまま外部 IP が払い出されませんでした。第9回では MetalLB を導入し、kubeadm オンプレ環境でも LoadBalancer が実際に機能することを実機で確認します。
第9回のキャッチコピー: 「kind 環境を脱却・LoadBalancer 動作確認」
第9回終了時の達成状態:
- MetalLB(L2 モード)がインストール済みで
192.168.1.200-210の IP プールが設定されている kubectl get serviceで LoadBalancer type Service に EXTERNAL-IP(192.168.1.200〜)が表示される- ClusterIP / NodePort / LoadBalancer / ExternalName の違いを実機で確認した
kubectl get endpointslicesで Service と Pod の対応が確認できるkubectl explain service.specで任意のフィールドを自力で調べられる
第9回のスコープと設計判断 — svc-demo Namespace で安全に演習する
本セクションでは、第9回で扱うことと扱わないことを明確にし、演習 Namespace の設計判断を説明します。
第9回で「やること」と「やらないこと」
| やること | やらないこと |
|---|---|
| Service 4 タイプの動作原理と使い分け | NetworkPolicy(第10回で詳解) |
| MetalLB Helm インストール + L2 設定 | BGP モード(家庭環境では不要) |
| EndpointSlice と Endpoints の比較確認 | ExternalName の TLS 設定詳細 |
| kube-proxy モードの概念理解 | ipvs モードの詳細(K8s v1.35 で deprecated) |
| kubectl explain で全フィールド確認 | MetalLB のフェイルオーバー詳細設定 |
演習 Namespace の設計
| Namespace | 役割 | 削除タイミング |
|---|---|---|
metallb-system | MetalLB コンポーネント(第11回 Traefik でも使用) | 削除しない |
svc-demo | 4 Service タイプ演習(nginx Deployment + 各 Service) | 演習②完了後に削除 |
fanclub(既存) | fanclub-api 稼働中・変更なし | 変更なし |
設計判断の背景
判断① MetalLB は Helm chart v0.16.0 で導入・metallb-system Namespace
MetalLB を公式 Helm chart(https://metallb.github.io/metallb)の v0.16.0 でインストールします。Namespace は metallb-system(--create-namespace で自動作成)。L2 モードで IPAddressPool を 192.168.1.200-192.168.1.210 に設定します。Helm install 時に CRDs が自動アップグレードされるため、手動の CRD 適用が不要です。L2 モードは家庭ルータ環境でも BGP ルータなしに動作可能で、本シリーズの Hyper-V 環境に最適です。
判断② 演習 Namespace は svc-demo・終了時削除
4 種類の Service 体験演習は svc-demo Namespace に nginx:1.27(k8s-registry 経由)を Deployment として作成し実施します。fanclub-api(fanclub Namespace)への影響を完全に排除でき、Namespace 削除 1 コマンドでクリーンアップが完結します。事前に k8s-ops から docker pull nginx:1.27 && docker tag nginx:1.27 192.168.1.123:5000/nginx:1.27 && docker push 192.168.1.123:5000/nginx:1.27 でレジストリにイメージを格納しておきます(hostname k8s-registry:5000 ではなく IP 表記を使う理由: k8s-ops の /etc/docker/daemon.json の insecure-registries が IP 表記で登録されているため)。
判断③ MetalLB インストールは alma-proxy whitelist 追加が前提
MetalLB Helm chart ダウンロードのために metallb.universe.tf と metallb.github.io が alma-proxy whitelist に追加済みであることを演習①冒頭で確認します(environment.md に記載済み・第9回から有効化)。
判断④ kube-proxy は iptables モードで説明
実機の kube-proxy モードが iptables(kubeadm v1.35 デフォルト)であるため、iptables モードを主体に説明します。nftables モード(将来の推奨標準・K8s v1.35 で Beta)と ipvs モード(K8s v1.35 で deprecated)については補足説明として概要を記載するにとどめます。
判断⑤ fanclub-api は ClusterIP のまま・変更なし
第9回を通じて fanclub-api(fanclub Namespace)の Service タイプは ClusterIP のままです。MetalLB 導入後も fanclub-api に外部 IP の割り当ては行いません(第11回で Traefik を通じた外部アクセスを実現します)。
第9回終了時点の各 VM 状態
| VM | 第9回終了時の状態 |
|---|---|
| k8s-cp-01〜03 | 変更なし(MetalLB speaker DaemonSet が metallb-system で稼働) |
| k8s-wl-01〜02 | 変更なし(MetalLB speaker がデーモンとして稼働・ARP 応答担当) |
| k8s-ops | svc-demo Namespace 削除済み・MetalLB 設定ファイル残存 |
MetalLB は第11回(Traefik)でも使用するため metallb-system Namespace は残存させます。環境の引き継ぎ状態が SP_vol2-pre-10 に保存されます。
Service 4 タイプの動作原理とユースケース
Service とは何か
Pod は生まれては消え、IP アドレスが変わります。Pod に直接アクセスすると、Pod の追加・削除・再起動のたびに接続先が変わってしまいます。Service はこの問題を解決するために、Pod 群の前段に「安定した ClusterIP と DNS 名」を提供するリソースです。

4 タイプ比較表
| タイプ | 到達範囲 | spec.type | 自動生成 IP | ポート範囲 |
|---|---|---|---|---|
| ClusterIP | クラスタ内部のみ | ClusterIP(デフォルト) | 10.96.0.0/12 内 | spec.ports[].port |
| NodePort | クラスタ内 + 全ノード IP:NodePort | NodePort | ClusterIP も割り当て | 30000-32767(デフォルト) |
| LoadBalancer | クラスタ内 + NodePort + 外部 LB IP | LoadBalancer | ClusterIP + NodePort も割り当て | MetalLB が外部 IP を払い出し |
| ExternalName | クラスタ内のみ(CNAME) | ExternalName | なし | なし(DNS 解決のみ) |
ClusterIP の詳細
ClusterIP はクラスタ内部からのみ到達可能な仮想 IP です。spec.selector でラベルが一致する Pod に転送されます。
spec.clusterIP: None を設定すると Headless Service になります。DNS が直接 Pod IP を返すため、StatefulSet での Pod 識別(pod-0.my-svc.default.svc.cluster.local など)に使われます。
fanclub-api でも使用しています。fanclub-api-backend Service(ClusterIP タイプ)が Frontend から Backend への通信を中継しています。
NodePort の詳細
NodePort は全ノードの同一ポート(デフォルト 30000-32767 の範囲)でトラフィックを受け付けます。Pod が配置されていないノードへのアクセスでも、そのノードの kube-proxy iptables ルールが実際の Pod IP に転送します。
spec.ports[].nodePort で固定ポートを指定できます(省略すると自動割り当て)。本番環境での注意点として、NodePort を開放する場合は全ノードのファイアウォール設定でそのポートレンジを開放する必要があります。
LoadBalancer の詳細
LoadBalancer は NodePort と ClusterIP を内包するスーパーセットです。クラウド環境ではクラウドプロバイダが LB を自動作成して ExternalIP を割り当てます。オンプレ環境(クラスタ内 LB 実装なし)では EXTERNAL-IP が <pending> のままになります。第9回で導入する MetalLB がこの問題を解決します。
ExternalName の詳細
ExternalName はセレクタ・ポート定義を持たない特殊な Service タイプです。spec.externalName に外部ホスト名を設定すると、CoreDNS がそのホスト名への CNAME を返します。Pod からはクラスタ内 DNS 名(例: my-db-svc.default.svc.cluster.local)でクラスタ外のサービスに接続できます。TLS を使う場合は証明書の SNI ホスト名と一致させる必要があります。
ClusterIP・NodePort・LoadBalancer の包含関係

LoadBalancer type Service を作成すると、NodePort と ClusterIP も自動的に割り当てられます。kubectl get service でこの 3 層の関係を CLUSTER-IP 列・PORT(S) 列・EXTERNAL-IP 列で確認できます。
Endpoints と EndpointSlice — Pod との対応を確認する
EndpointSlice が Endpoints を置き換える経緯

K8s v1.33 で旧 Endpoints API が正式に deprecated になりました。新機能(デュアルスタック・トラフィック分散等)は EndpointSlice API 経由でのみサポートされます。kubectl get endpoints は後方互換として引き続き使用できますが、今後の運用では kubectl get endpointslices を優先する習慣をつけてください。
EndpointSlice と Service の対応図
Service(nginx-clusterip)
↓ セレクタ(app=nginx)で一致する Pod を管理
EndpointSlice(nginx-clusterip-xxxxx)
Endpoints: 10.244.1.5:80、10.244.2.5:80
↑ Pod の追加・削除に応じて自動更新
Pod のラベルを変更(kubectl label pod <name> --overwrite app=removed)すると Service のセレクタと一致しなくなり、EndpointSlice から自動削除されます。元に戻すと自動復旧します。この自動管理が Service の安定性を実現しています。
CKA 試験でのトラブルシュート活用
Service に到達できないトラブルの診断手順として、まず kubectl get endpoints または kubectl get endpointslices で Pod が登録されているか確認します。EndpointSlice が空(エンドポイントなし)の場合は、セレクタのラベルが Pod と一致していない可能性を疑います。CKA 試験でのトラブルシュート問題に頻出するパターンです。
kube-proxy の役割 — iptables/nftables/ipvs モードを概観する
kube-proxy の仕組み(iptables モード)

kube-proxy は各 Node で DaemonSet として動作します。API Server から Service と EndpointSlice の変更を Watch し、変更があると各 Node の iptables ルールを更新します。ClusterIP はこの iptables による DNAT(宛先 NAT)として実現されているため、「Virtual IP」と呼ばれます。
3 モード比較表
| モード | 仕組み | K8s v1.35 での状態 | 推奨度 |
|---|---|---|---|
| iptables | iptables ルールで DNAT 変換 | デフォルト(変更なし) | 現在の標準 |
| nftables | nftables API を使用(高速・効率的) | Beta(K8s 1.31〜) | 将来の推奨標準 |
| ipvs | Linux IPVS(LVS)でロードバランシング | K8s v1.35 で deprecated | 移行を検討 |
本シリーズの実機(kubeadm v1.35 デフォルト)は iptables モードで動作しています。kube-proxy の設定は以下のコマンドで確認できます。
実行コマンド:
$ kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
実行結果:
mode: ""
空文字("")は iptables モードと等価です。
iptables ルールの実体を確認したい場合、対象ノード(k8s-wl-01 等)上で root 権限で以下を実行できます。
実行コマンド(k8s-wl-01 上 root で実行):
# iptables -t nat -L KUBE-SERVICES --line-numbers | head -20
K8s v1.35 の kube-proxy モードの変化点
ipvs モードは K8s v1.35 で deprecated になりました。ipvs モードは当初「iptables より高パフォーマンス」として期待されましたが、Linux IPVS API が Kubernetes Service API との相性が悪く、エッジケースの実装が困難であることが判明しました。一方、nftables モードは iptables と同等以上のパフォーマンスを持ち、ipvs の欠点もありません。本シリーズの実機環境(kubeadm v1.35 デフォルト)は iptables モードで動作しています。将来的には nftables モードが標準になる見込みですが、iptables は互換性維持のためデフォルトとして当面継続されます。
MetalLB の仕組み — オンプレ環境で LoadBalancer を実現する
MetalLB が解決する問題
【kind クラスタでの LoadBalancer Service(第1巻)】
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
nginx LoadBalancer 10.96.0.1 <pending> 80:30080/TCP
↑ 払い出すクラウド LB がないため pending のまま
【kubeadm + MetalLB での LoadBalancer Service(第9回)】
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
nginx LoadBalancer 10.96.0.1 192.168.1.200 80:30080/TCP
↑ MetalLB が L2 モードで払い出し
第1巻の kind 環境では EXTERNAL-IP が <pending> のままでした。第9回で MetalLB を導入すると、kubeadm オンプレ環境でも LoadBalancer type Service に外部 IP が払い出されます。
MetalLB のコンポーネント
| コンポーネント | 種類 | 役割 |
|---|---|---|
| controller | Deployment | IPAddressPool から LoadBalancer Service に IP を割り当て |
| speaker | DaemonSet(全 Node) | 割り当てた IP を ARP/NDP でネットワークにアナウンス |
L2 モードの動作(ステップバイステップ)

【MetalLB L2 モードの動作フロー】
① MetalLB controller が LoadBalancer Service に IPAddressPool から IP(192.168.1.200)を割り当て
② speaker DaemonSet の中から「代表 Node」が選ばれる
③ 代表 Node の speaker が「192.168.1.200 は自分(192.168.1.128)にある」と ARP 応答
④ クラスタ外(k8s-ops・ブラウザ等)から 192.168.1.200 にアクセスすると代表 Node に届く
⑤ 代表 Node の iptables ルール(kube-proxy 管理)が実際の Pod IP に転送
L2 モード vs BGP モードの使い分け
| 観点 | L2 モード | BGP モード |
|---|---|---|
| 設定難易度 | 簡単(ルータ設定不要) | 難しい(BGP ルータ・AS 番号が必要) |
| 負荷分散 | なし(代表 Node 1 台経由) | ECMP でノード間分散可能 |
| フェイルオーバー | 数秒〜数十秒(ARP キャッシュ失効待ち) | 数秒(BGP セッション検知) |
| 適合環境 | 家庭・小規模オンプレ・学習環境 | 本番大規模オンプレ・DC ネットワーク |
| 本シリーズ | 採用 | 対象外 |
MetalLB インストール後、既存の Service(ClusterIP / NodePort)への影響はありません。type: LoadBalancer を指定した新しい Service だけが MetalLB から IP を受け取ります。environment.md の HAProxy 設定で 192.168.1.200 を Traefik に割り当てる想定です(第11回で完成)。
やってみよう①: MetalLB インストール(Helm v4 + L2 モード設定)
MetalLB を Helm chart v0.16.0 でインストールし、IPAddressPool と L2Advertisement を設定して LoadBalancer type Service が外部 IP を取得できる状態にします。作業場所は k8s-ops(developer ユーザー)です。
Step 1: alma-proxy whitelist 確認
MetalLB Helm chart のダウンロード前に、alma-proxy の whitelist に MetalLB 関連ドメインが追加されていることを確認します。
実行コマンド(alma-proxy 上・root で実行):
# grep metallb /etc/squid/whitelist.txt
実行結果:
metallb.universe.tf
metallb.github.io
Step 2: MetalLB Helm リポジトリの追加
実行コマンド(k8s-ops 上・developer):
$ helm repo add metallb https://metallb.github.io/metallb
実行結果:
"metallb" has been added to your repositories
実行コマンド(リポジトリ更新):
$ helm repo update
実行結果:
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
Update Complete. Happy Helming!
実行コマンド(バージョン確認):
$ helm search repo metallb/metallb
実行結果:
NAME CHART VERSION APP VERSION DESCRIPTION
metallb/metallb 0.16.0 v0.16.0 A network load-balancer implementation for Kub...
Step 3: MetalLB インストール
metallb-system Namespace を先に作成してから helm install を実行します。MetalLB v0.16.0 chart はサブチャート frr-k8s(BGP モード用)を含んでおり、デフォルト values だと prometheus.serviceMonitor.enabled の参照で nil pointer エラーが発生するため、本シリーズ(L2 モード採用)では frrk8s.enabled=false と prometheus.serviceMonitor.enabled=false を明示します。
実行コマンド:
$ kubectl create namespace metallb-system
$ helm install metallb metallb/metallb \
--namespace metallb-system \
--version 0.16.0 \
--set frrk8s.enabled=false \
--set "prometheus.serviceMonitor.enabled=false"
実行結果:
NAME: metallb
LAST DEPLOYED: Sat May 24 10:00:00 2026
NAMESPACE: metallb-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster.
Now you can configure it via its CRs. Please refer to the metallb official docs
on how to use the CRs.
Step 4: MetalLB Pod の起動確認
実行コマンド(Pod が Running になるまで待機):
$ kubectl get pods -n metallb-system
実行結果(controller 1 Pod + speaker が全 Node 分 5 Pod):
NAME READY STATUS RESTARTS AGE
metallb-controller-xxxxxxxxxx-yyyyy 1/1 Running 0 60s
metallb-speaker-aaaaa 1/1 Running 0 60s
metallb-speaker-bbbbb 1/1 Running 0 60s
metallb-speaker-ccccc 1/1 Running 0 60s
metallb-speaker-ddddd 1/1 Running 0 60s
metallb-speaker-eeeee 1/1 Running 0 60s
controller が 1 Pod(Deployment)、speaker が 5 Pod(DaemonSet・全 Node 分)起動していれば正常です。
Step 5: IPAddressPool マニフェスト作成・適用
マニフェストファイル metallb-pool.yaml を作成します。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.210
実行コマンド:
$ kubectl apply -f metallb-pool.yaml
実行結果:
ipaddresspool.metallb.io/first-pool created
Step 6: L2Advertisement マニフェスト作成・適用
マニフェストファイル metallb-l2adv.yaml を作成します。
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
実行コマンド:
$ kubectl apply -f metallb-l2adv.yaml
実行結果:
l2advertisement.metallb.io/example created
Step 7: MetalLB の設定確認
実行コマンド(IPAddressPool の確認):
$ kubectl get ipaddresspool -n metallb-system
実行結果:
NAME AUTO ASSIGN AVOID BUGGY IPS ADDRESSES
first-pool true false ["192.168.1.200-192.168.1.210"]
実行コマンド(L2Advertisement の確認):
$ kubectl get l2advertisement -n metallb-system
実行結果:
NAME IPADDRESSPOOLS IPADDRESSPOOL SELECTORS INTERFACES
example ["first-pool"]
演習①の振り返り
- Helm v4 で MetalLB v0.16.0 を metallb-system Namespace にインストールした
- IPAddressPool で IP レンジ(192.168.1.200-210)を定義し、L2Advertisement で L2 モードでの広告を設定した
- MetalLB は metallb-system Namespace に残存し続ける(第11回 Traefik LoadBalancer でも使用)
- この時点で LoadBalancer type Service を作成するとすぐに外部 IP が払い出される状態になった
やってみよう②: 4 種 Service デプロイと挙動確認
svc-demo Namespace に nginx Deployment を作成し、ClusterIP / NodePort / LoadBalancer / ExternalName の 4 タイプの Service を作成してそれぞれの挙動の違いを実機で確認します。作業場所は k8s-ops(developer ユーザー)です。
Step 1: svc-demo Namespace の作成
実行コマンド:
$ kubectl create namespace svc-demo
実行結果:
namespace/svc-demo created
Step 2: nginx Deployment の作成
マニフェストファイル nginx-deployment.yaml を作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: svc-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: 192.168.1.123:5000/nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
実行コマンド:
$ kubectl apply -f nginx-deployment.yaml
実行結果:
deployment.apps/nginx created
実行コマンド(Pod が Running になったことを確認):
$ kubectl get pods -n svc-demo -o wide
実行結果(2 Pod が別 Node に分散配置):
NAME READY STATUS RESTARTS AGE IP NODE
nginx-xxxxxxxxxx-aaaaa 1/1 Running 0 30s 10.244.1.5 k8s-wl-01
nginx-xxxxxxxxxx-bbbbb 1/1 Running 0 30s 10.244.2.5 k8s-wl-02
Step 3: ClusterIP Service の作成・動作確認
マニフェストファイル svc-clusterip.yaml を作成します。
apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip
namespace: svc-demo
spec:
type: ClusterIP
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
実行コマンド:
$ kubectl apply -f svc-clusterip.yaml
実行結果:
service/nginx-clusterip created
実行コマンド(Service の確認・EXTERNAL-IP は <none>):
$ kubectl get service nginx-clusterip -n svc-demo
実行結果:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-clusterip ClusterIP 10.96.45.123 <none> 80/TCP 15s
クラスタ内から busybox で疎通確認を実施します。
実行コマンド(クラスタ内から ClusterIP へアクセス):
$ kubectl run -it test-pod --rm --image=busybox:1.28 --restart=Never -n svc-demo -- wget -qO- http://nginx-clusterip
実行結果(nginx のデフォルトページ HTML が返る):
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
...
Step 4: NodePort Service の作成・動作確認
マニフェストファイル svc-nodeport.yaml を作成します。
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
namespace: svc-demo
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
実行コマンド:
$ kubectl apply -f svc-nodeport.yaml
実行結果:
service/nginx-nodeport created
実行コマンド(Service の確認・ClusterIP と NodePort 両方が割り当て):
$ kubectl get service nginx-nodeport -n svc-demo
実行結果:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-nodeport NodePort 10.96.78.234 <none> 80:30080/TCP 15s
k8s-ops から k8s-wl-01 の NodePort にアクセスして確認します。
実行コマンド(k8s-ops から NodePort にアクセス):
$ curl http://192.168.1.128:30080
実行結果(nginx のデフォルトページ HTML が返る):
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
Step 5: LoadBalancer Service の作成・EXTERNAL-IP 払い出し確認
マニフェストファイル svc-loadbalancer.yaml を作成します。
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
namespace: svc-demo
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
実行コマンド:
$ kubectl apply -f svc-loadbalancer.yaml
実行結果:
service/nginx-lb created
実行コマンド(MetalLB が EXTERNAL-IP を払い出したことを確認):
$ kubectl get service nginx-lb -n svc-demo
実行結果(192.168.1.200 が払い出された):
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-lb LoadBalancer 10.96.90.100 192.168.1.200 80:31000/TCP 15s
第1巻では <pending> のままだった EXTERNAL-IP に 192.168.1.200 が表示されています。MetalLB が IPAddressPool から即座に払い出した結果です。
実行コマンド(k8s-ops から EXTERNAL-IP に直接アクセス):
$ curl http://192.168.1.200
実行結果(nginx のデフォルトページ HTML が返る):
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
Step 6: ExternalName Service の作成・DNS 確認
マニフェストファイル svc-externalname.yaml を作成します。
apiVersion: v1
kind: Service
metadata:
name: nginx-extname
namespace: svc-demo
spec:
type: ExternalName
externalName: k8s-registry
実行コマンド:
$ kubectl apply -f svc-externalname.yaml
実行結果:
service/nginx-extname created
実行コマンド(Service の確認・CLUSTER-IP は <none>・EXTERNAL-IP に DNS 名が表示される):
$ kubectl get service nginx-extname -n svc-demo
実行結果:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-extname ExternalName <none> k8s-registry <none> 15s
CLUSTER-IP が <none> で EXTERNAL-IP に外部ホスト名が表示されることが ExternalName の特徴です。クラスタ内から DNS 解決を確認します。
実行コマンド(クラスタ内から DNS で CNAME 解決を確認):
$ kubectl run -it dns-test --rm --image=busybox:1.28 --restart=Never -n svc-demo -- nslookup nginx-extname
実行結果(CNAME として k8s-registry が返る):
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-extname
Address 1: 192.168.1.123 k8s-registry
Step 7: Endpoints と EndpointSlice の確認
実行コマンド(旧 Endpoints の確認):
$ kubectl get endpoints -n svc-demo
実行結果:
NAME ENDPOINTS AGE
nginx-clusterip 10.244.1.5:80,10.244.2.5:80 5m
nginx-lb 10.244.1.5:80,10.244.2.5:80 3m
nginx-nodeport 10.244.1.5:80,10.244.2.5:80 4m
※ ExternalName Service(nginx-extname)は Pod セレクタを持たず Endpoints / EndpointSlice を生成しないため、上記出力には現れません。CoreDNS による CNAME 解決だけで完結します。
実行コマンド(新 EndpointSlice の確認):
$ kubectl get endpointslices -n svc-demo
実行結果:
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
nginx-clusterip-xxxxx IPv4 80 10.244.1.5,10.244.2.5 5m
nginx-lb-yyyyy IPv4 80 10.244.1.5,10.244.2.5 3m
nginx-nodeport-zzzzz IPv4 80 10.244.1.5,10.244.2.5 4m
Service ラベルで特定 Service のスライスだけに絞り込めます。
実行コマンド(Service ラベルで絞り込み):
$ kubectl get endpointslices -l kubernetes.io/service-name=nginx-clusterip -n svc-demo
実行結果:
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
nginx-clusterip-xxxxx IPv4 80 10.244.1.5,10.244.2.5 5m
Step 8: svc-demo Namespace の削除(クリーンアップ)
実行コマンド:
$ kubectl delete namespace svc-demo
実行結果:
namespace "svc-demo" deleted
演習②の振り返り
- ClusterIP はクラスタ内のみ・NodePort は全 Node の指定ポートから・LoadBalancer は MetalLB が払い出した外部 IP から直接アクセスできることを実機で確認した
- ExternalName は CLUSTER-IP が割り当てられず、DNS CNAME として機能することを確認した
kubectl get endpointsとkubectl get endpointslicesで Pod IP の対応を確認した- MetalLB が LoadBalancer Service に即座に外部 IP(192.168.1.200)を払い出すことを確認した。第1巻の kind 環境で
<pending>だった状況からの脱却を実機で実感した
やってみよう③: kubectl explain で Service 全フィールドを確認する
kubectl explain コマンドを使って Service の spec フィールドを調べる習慣を身につけます。CKA 試験中にドキュメントを参照しなくても端末上でフィールドを確認できることを実感してください。作業場所は k8s-ops(developer ユーザー)です。
Step 1: service.spec のトップレベルフィールド一覧
実行コマンド:
$ kubectl explain service.spec
実行結果(主要フィールドのみ抜粋):
KIND: Service
VERSION: v1
FIELD: spec <ServiceSpec>
DESCRIPTION:
Spec defines the behavior of a service.
...
FIELDS:
clusterIP <string>
clusterIPs <[]string>
externalIPs <[]string>
externalName <string>
ports <[]ServicePort>
selector <map[string]string>
sessionAffinity <string>
type <string>
...
Step 2: service.spec.type の詳細確認
実行コマンド:
$ kubectl explain service.spec.type
実行結果(4 タイプが記載されている):
KIND: Service
VERSION: v1
FIELD: type <string>
DESCRIPTION:
type determines how the Service is exposed. Defaults to ClusterIP. Valid
options are ExternalName, ClusterIP, NodePort, and LoadBalancer.
...
Step 3: service.spec.ports のフィールド確認
実行コマンド:
$ kubectl explain service.spec.ports
実行結果(port / targetPort / nodePort / protocol 等が確認できる):
KIND: Service
VERSION: v1
FIELD: ports <[]ServicePort>
DESCRIPTION:
The list of ports that are exposed by this service.
...
FIELDS:
appProtocol <string>
name <string>
nodePort <integer>
port <integer> -required-
protocol <string>
targetPort <IntOrString>
port <integer> -required- の表記に注目してください。-required- はそのフィールドが必須であることを意味します。YAML マニフェストを書く際に必須フィールドを見落とさないための確認に活用できます。
Step 4: sessionAffinity の確認(応用)
実行コマンド:
$ kubectl explain service.spec.sessionAffinity
実行結果:
KIND: Service
VERSION: v1
FIELD: sessionAffinity <string>
DESCRIPTION:
Supports "ClientIP" and "None". Used to maintain session affinity.
Enable client IP based session affinity. Must be ClientIP or None.
Defaults to None.
演習③の振り返り
kubectl explain service.specでトップレベルのフィールド一覧が確認できるkubectl explain service.spec.typeのようにドット区切りで掘り下げて確認できる- CKA 試験中にドキュメントを参照しなくても
kubectl explainでフィールド名・必須かどうか・説明文を確認できる -required-表記があるフィールドを見落とさない習慣を身につける
まとめ・現場ヒヤリハット・理解度チェック
第9回のまとめ
- Service 4 タイプ(ClusterIP / NodePort / LoadBalancer / ExternalName)の動作原理とユースケースを整理した。ClusterIP はクラスタ内部通信の基盤であり、LoadBalancer は NodePort + ClusterIP を内包するスーパーセット
- EndpointSlice は Endpoints API の後継(K8s v1.33 で Endpoints deprecated)。
kubectl get endpointslicesで Service と Pod の対応を確認する習慣をつける - kube-proxy は Virtual IP を実現するための iptables ルールをノードに設定する。ipvs モードは K8s v1.35 で deprecated、nftables モードが将来の推奨標準
- MetalLB(L2 モード)を Helm v0.16.0 でインストールし、オンプレ kubeadm 環境で LoadBalancer type Service に外部 IP(192.168.1.200)が払い出されることを確認した
kubectl explain service.specでフィールドを自力で確認する習慣を身につけた
第3部「ネットワーク」第1回完了:
第9回の完了で第3部の 1/3 回が完了しました。次回は CKA D3 の重要テーマ「NetworkPolicy」に入ります。
次回予告
第10回では NetworkPolicy 詳細と Calico/Cilium 比較、ミニトラブルシュート演習 を扱います。第1巻で NetworkPolicy の基本を学んだ読者向けに、Ingress/Egress ルールの詳細設計・default-deny-all ポリシーの適用・Calico CNI のアーキテクチャを掘り下げます。NetworkPolicy に起因する疎通不能トラブルを診断・解決するミニ演習も実施します。
現場ヒヤリハット① LoadBalancer Service の EXTERNAL-IP が <pending> のまま変わらない
状況: kubeadm で構築したオンプレクラスタに MetalLB をインストールして LoadBalancer type Service を作成したが、kubectl get service で EXTERNAL-IP が <pending> のまま変わらない。MetalLB の Pod は Running になっている。
原因: IPAddressPool と L2Advertisement の両方が必要なのに、IPAddressPool のみを作成して L2Advertisement を作成していなかった。MetalLB は IPAddressPool だけでは IP のアドバタイズを行わないため、LoadBalancer Service に IP を割り当てても ARP 応答が送られず <pending> のままになる。
対処: kubectl get l2advertisement -n metallb-system で L2Advertisement が存在するか確認する。存在しない場合は L2Advertisement を作成して IPAddressPool を参照させる。
教訓: MetalLB の設定は「IPAddressPool(何の IP を使うか)」と「L2Advertisement(どのモードで広告するか)」の 2 つのリソースをセットで作成する。どちらかが欠けると IP 払い出しが動作しない。kubectl get ipaddresspool と kubectl get l2advertisement の両方を確認する習慣を持つ。
現場ヒヤリハット② NodePort Service にアクセスできるが特定のノードのポートだけ応答が不安定
状況: NodePort Service(port 30080)を作成し、複数ノードに対して curl http://<ノードIP>:30080 で動作確認していたが、Pod が配置されていないノード(k8s-cp-01 等)へのアクセスが断続的にタイムアウトする。
原因: firewalld の設定で NodePort レンジ(30000-32767/tcp)が Control Plane Node(k8s-cp-01〜03)で開放されていない可能性。environment.md の firewalld 設定では k8s-wl 向けに 30000-32767/tcp の開放が記載されているが、k8s-cp 向けには記載がない。NodePort は全 Node でアクセス可能なはずだが、Control Plane Node の firewalld で NodePort レンジが遮断されている場合は到達しない。
対処: firewall-cmd --list-ports で対象 Node の開放ポートを確認する。NodePort レンジが未開放であれば firewall-cmd --add-port=30000-32767/tcp --permanent && firewall-cmd --reload で開放する。
教訓: NodePort が「全ノードで到達できる」という仕様は、前提として各ノードの firewalld で NodePort レンジが開放されていることが必要。kubeadm の firewalld 開放ポート設定はノードの役割別に確認する。Control Plane Node での NodePort アクセスは本番では不要(Workload Node のみで十分)だが、テスト時の挙動差異に注意する。
現場ヒヤリハット③ MetalLB の admission webhook が context deadline exceeded で IPAddressPool を作成できない
状況: MetalLB Helm install は成功し全 Pod が Running になったが、kubectl apply -f ipaddresspool.yaml で「Error from server (InternalError): failed calling webhook "ipaddresspoolvalidationwebhook.metallb.io": ... context deadline exceeded」というエラーで IPAddressPool が作成できない。
原因: kube-apiserver(Control Plane Node の host network)から MetalLB controller Pod(Workload Node の Pod CIDR・10.244.x.x)への到達性が確保されていない。具体的には Calico の IP_AUTODETECTION_METHOD が二枚 NIC 環境(eth0 = External / eth1 = Internal)で eth1 を誤検出していたり、firewalld で BGP(179/tcp)・IPIP プロトコル・Pod CIDR の trusted source が遮断されていると BGP メッシュが確立せず、apiserver→Pod の通信路自体が成立しない。本シリーズでは第2回の polish(IP_AUTODETECTION_METHOD=interface=eth0 明示 + firewalld 4 ルール開放)でこの問題を根治済み。
対処: kubectl get pod -n kube-system -l k8s-app=calico-node で全 calico-node Pod が 1/1 Running であることを確認 → kubectl exec -n kube-system <calico-node> -- birdcl show protocols で BGP メッシュが全ノード Established であることを確認。BGP が Active や Idle のままなら第2回 polish の手順(kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=interface=eth0 + firewalld 開放)を再適用する。緊急時は MetalLB webhook の failurePolicy を Ignore に変更する回避策もあるが、本番では Calico 経路を根治する対応が正しい。
教訓: Admission webhook(MetalLB / cert-manager / Longhorn 等)は、kube-apiserver が Pod CIDR 上の webhook Service に到達できることが前提。Calico の BGP メッシュが正しく確立していないと、Helm install は成功しても webhook を通じたカスタムリソース apply がすべて timeout する。第9回以降の演習回(cert-manager / Longhorn / kube-prometheus-stack 等)で「Pod は Running なのに kubectl apply が webhook timeout する」症状を見たら、最初に疑うのは「Calico BGP メッシュ確立状況」と「firewalld の Pod CIDR 開放」。
理解度チェック
第9回の理解度を ○× 形式の 7 問で確認します。まず問題を読み、自分なりに答えを出してから解説を読んでください。
- 問 1: Kubernetes の Service タイプ「ClusterIP」は、クラスタ外部からも直接アクセスできる
- 問 2: NodePort type Service を作成すると、ClusterIP も自動的に割り当てられる
- 問 3: LoadBalancer type Service は、MetalLB のような外部 LB 実装がないオンプレ環境では EXTERNAL-IP が
<pending>のままになる - 問 4: EndpointSlice API は Kubernetes v1.33 で deprecated となり、代わりに Endpoints API の使用が推奨されている
- 問 5: MetalLB の L2 モードでは、LoadBalancer Service に割り当てた外部 IP を複数のノードが同時に ARP でアナウンスする
- 問 6: ExternalName type Service には spec.selector の設定が必要である
- 問 7: kube-proxy の ipvs モードは Kubernetes v1.35 で deprecated(非推奨)となった
問 1: × — ClusterIP はクラスタ内部からのみ到達可能です。外部からのアクセスには NodePort / LoadBalancer が必要です。
問 2: ○ — NodePort は ClusterIP を内包します。kubectl get service の CLUSTER-IP 列に IP が表示されます。LoadBalancer は NodePort と ClusterIP の両方を内包するスーパーセットです。
問 3: ○ — クラウドプロバイダまたは MetalLB 等の LB 実装が必要です。第1巻の kind 環境では <pending> のままでしたが、第9回で MetalLB を導入することで解決しました。
問 4: × — 逆です。旧 Endpoints API が K8s v1.33 で deprecated となり、EndpointSlice が後継として推奨されています。
問 5: × — L2 モードでは「代表 Node」が 1 台のみ ARP をアナウンスします。真の負荷分散ではなく、代表 Node への集中になります。複数ノードへの分散が必要な場合は BGP モードが必要です。
問 6: × — ExternalName は spec.externalName に外部ホスト名を設定します。セレクタは使用しません。ポート定義も不要です。
問 7: ○ — K8s v1.35 で ipvs モードは deprecated になりました。nftables モードが将来の推奨標準として Beta 段階にあります。
CKA 試験対策ポイント(D3 Services and Networking)
CKA D3(Services and Networking・20%)で頻出するパターンを整理します。
頻出パターン①: Service の作成と確認
試験では「Deployment X に対して ClusterIP / NodePort / LoadBalancer type の Service を作成せよ」という形式が出題されます。
spec.selectorは Deployment のspec.selector.matchLabelsと一致させる- NodePort の
spec.ports[].nodePortは 30000-32767 の範囲内 - LoadBalancer を作成しても EXTERNAL-IP が
<pending>のままなら MetalLB 等の LB 実装が必要(試験環境では通常インストール済み)
頻出パターン②: Service に到達できないトラブルシュート
「Pod から Service X に到達できない。調査して修正せよ」という形式が出題されます。
kubectl get endpoints <service-name> -n <namespace>
→ ENDPOINTS が空(<none>)なら Service のセレクタが Pod のラベルと一致していない
kubectl get pods -l app=<label> -n <namespace>
→ Pod のラベルを確認
kubectl get service <service-name> -n <namespace> -o yaml
→ spec.selector の内容を確認・Pod のラベルと照合
頻出パターン③: kubectl explain の活用
試験中に YAML フィールドが分からない場合は kubectl explain を使います。
kubectl explain service.specでトップレベルのフィールド一覧kubectl explain service.spec.portsでポート設定の詳細-required-表記は必須フィールドを示す- 試験中はドキュメント参照前に
kubectl explainで確認する習慣をつける
試験で参照できる公式ドキュメント URL:
- Service: https://kubernetes.io/docs/concepts/services-networking/service/
- EndpointSlices: https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
- Virtual IPs and kube-proxy: https://kubernetes.io/docs/reference/networking/virtual-ips/
- MetalLB: https://metallb.universe.tf/
シリーズ一覧
第1部:クラスタ構築
- 第1回 第2巻スコープ + CKA 試験形式紹介 + kubeadm 概要 + kubeadm vs RKE2/k0s/OKD 概観
- 第2回 kubeadm シングルノード起動(pkgs.k8s.io + kubeadm init + Calico CNI)+ alma-proxy whitelist 構築
- 第3回 kubeadm HA 設計 + HAProxy LB(API Server LB 構成)
- 第4回 kubeadm HA クラスタ構築(CP×3 + WL×2)+ fanclub-api HA 移行
- 第5回 etcd backup/restore(etcdutl)+ ノード drain/uncordon
第2部:ワークロード管理
- 第6回 kubeadm upgrade + ノード drain/cordon + ミニトラブルシュート演習
- 第7回 Pod スケジューリング(taint/toleration/affinity/anti-affinity/PriorityClass)
- 第8回 HPA + ResourceQuota + LimitRange
第3部:ネットワーク
- 第9回 Service 詳細 + MetalLB(LoadBalancer 動作確認) ← 今ここ
- 第10回 NetworkPolicy 詳細 + Calico/Cilium 比較 + ミニトラブルシュート演習
- 第11回 Gateway API + CoreDNS + Ingress Controller 設置(Traefik v39.x)
第4部:ストレージ
第5部:監視・運用
- 第13回 Prometheus + Grafana + Loki + Fluent Bit + fanclub-api 監視ダッシュボード
- 第14回 Helm + Kustomize でクラスタコンポーネント install + ArgoCD GitOps + Velero backup
