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

Service詳細とMetalLBでLB実現【CKA第9回】

広告

新卒インフラエンジニア向け 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回 / 全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-systemMetalLB コンポーネント(第11回 Traefik でも使用)削除しない
svc-demo4 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.jsoninsecure-registries が IP 表記で登録されているため)。

判断③ MetalLB インストールは alma-proxy whitelist 追加が前提

MetalLB Helm chart ダウンロードのために metallb.universe.tfmetallb.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-opssvc-demo Namespace 削除済み・MetalLB 設定ファイル残存

MetalLB は第11回(Traefik)でも使用するため metallb-system Namespace は残存させます。環境の引き継ぎ状態が SP_vol2-pre-10 に保存されます。

Service 4 タイプの動作原理とユースケース

Service とは何か

Pod は生まれては消え、IP アドレスが変わります。Pod に直接アクセスすると、Pod の追加・削除・再起動のたびに接続先が変わってしまいます。Service はこの問題を解決するために、Pod 群の前段に「安定した ClusterIP と DNS 名」を提供するリソースです。

Service の役割 — 変動する Pod を安定したアクセス先で隠蔽
Service の役割 — 変動する Pod を安定したアクセス先で隠蔽

4 タイプ比較表

タイプ到達範囲spec.type自動生成 IPポート範囲
ClusterIPクラスタ内部のみClusterIP(デフォルト)10.96.0.0/12 内spec.ports[].port
NodePortクラスタ内 + 全ノード IP:NodePortNodePortClusterIP も割り当て30000-32767(デフォルト)
LoadBalancerクラスタ内 + NodePort + 外部 LB IPLoadBalancerClusterIP + 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 の包含関係

Service タイプの包含関係 — LoadBalancer ⊃ NodePort ⊃ ClusterIP → Pod
Service タイプの包含関係 — LoadBalancer ⊃ NodePort ⊃ ClusterIP → Pod

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

Endpoints と EndpointSlice — Pod との対応を確認する

EndpointSlice が Endpoints を置き換える経緯

EndpointSlice が Endpoints を置き換える経緯
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 の仕組み(iptables モード)
kube-proxy の仕組み(iptables モード)

kube-proxy は各 Node で DaemonSet として動作します。API Server から Service と EndpointSlice の変更を Watch し、変更があると各 Node の iptables ルールを更新します。ClusterIP はこの iptables による DNAT(宛先 NAT)として実現されているため、「Virtual IP」と呼ばれます。

3 モード比較表

モード仕組みK8s v1.35 での状態推奨度
iptablesiptables ルールで DNAT 変換デフォルト(変更なし)現在の標準
nftablesnftables API を使用(高速・効率的)Beta(K8s 1.31〜)将来の推奨標準
ipvsLinux 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 のコンポーネント

コンポーネント種類役割
controllerDeploymentIPAddressPool から LoadBalancer Service に IP を割り当て
speakerDaemonSet(全 Node)割り当てた IP を ARP/NDP でネットワークにアナウンス

L2 モードの動作(ステップバイステップ)

MetalLB L2 モードの払い出しフロー(IPAddressPool: 192.168.1.200-210)
MetalLB L2 モードの払い出しフロー(IPAddressPool: 192.168.1.200-210)
【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=falseprometheus.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 endpointskubectl 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 ipaddresspoolkubectl 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 が ActiveIdle のままなら第2回 polish の手順(kubectl set env daemonset/calico-node -n kube-system IP_AUTODETECTION_METHOD=interface=eth0 + firewalld 開放)を再適用する。緊急時は MetalLB webhook の failurePolicyIgnore に変更する回避策もあるが、本番では 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部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク