Kubernetes 解体新書Part 2 ネットワーク

ELB を作るのは誰か

Service の設定を読み、ロードバランサーを作る担当を追います。外部アドレスが決まらない場合も観察します。

  • L1 Web ターミナル
  • 更新 2026年10月6日

Service に type: LoadBalancer と書いても、それだけでロードバランサーを作れるとは限りません。 実際に外部の機器やクラウドを操作する担当が必要です。 担当がいなければ、外部アドレスの欄は <pending> のままです。

この章では、Service の依頼を読み、ロードバランサーを作って、そのアドレスを書き戻す流れを追います。 通信が通る経路から、その経路を用意する側へ視点を移します。

GOAL この章のゴール

  • Service の設定からロードバランサーが作られる流れを説明できる
  • 外部アドレスが決まらないとき、担当の有無を確認できる
  • クラウド以外にも Service の外部公開を実現する方法があると分かる

Service controller が ELB を作るまで

Part 1 の第 4 章で見たとおり、Kubernetes のコントローラーは kube-apiserver を watch して、担当のリソースが変わったら動きます。 cloud-controller-managerクラウドコントローラーマネージャークラウド固有の制御ロジックを組み込んだコントロールプレーンのコンポーネント。Node、Route、Service の 3 つのコントローラーを持ち、クラウドごとの実装 (cloud-provider-aws など) を差し込む。用語集で見る もその一つで、公式ドキュメントは「クラウド固有の制御ロジックを組み込んだコントロールプレーンのコンポーネント」と説明しています。

type: LoadBalancer から ELB ができるまで: Service controller が apiserver を watch し、クラウド API を呼び、status に IP を書き戻すkubectl が Service を作ると、cloud-controller-manager の Service controller がそれを見つけ、cloud-provider-aws 経由で ELB を作り、ノードをターゲットに登録し、Service の status.loadBalancer に ELB のホスト名を書き戻す。kubectlapplykube-apiserverService (type: LoadBalancer)1. CREATEcloud-controller-managerService controllercloud-provider-aws2. watchAWS APIelasticloadbalancing3. EnsureLoadBalancer (AWS API)ELBa1b2c3.elb.amazonaws.comNode10.0.1.10Node10.0.1.114. ターゲットに全ノード (NodePort) を登録5. status.loadBalancer.ingress に書き戻す
図 1kubectl が Service を作ると、Service controller がそれを見つけ、cloud-provider-aws 経由で ELB を作り、ノードを登録し、status に ELB のホスト名を書き戻す。

type: LoadBalancer の Service を apply すると、次の順で進みます。

  1. kube-apiserver に Service が保存される。この時点で ClusterIP と NodePort は割り当て済みで、status.loadBalancer は空。
  2. cloud-controller-manager の Service controller が watch でこの Service を見つけ、Service に EnsuringLoadBalancer というイベントを記録する。
  3. クラウドプロバイダーの実装 (AWS なら cloud-provider-aws) の EnsureLoadBalancer が、クラウドの API を呼んで LB を作る。cloud-provider-aws のドキュメントによれば、既定では Classic Load Balancer で、annotation service.beta.kubernetes.io/aws-load-balancer-type: nlb を付けると NLB になる。
  4. 全ノードの NodePort をターゲットに登録する。第 6 章で見た「LB はノードに送る」構成がここで作られる。
  5. できた LB のホスト名や IP を Service の status.loadBalancer.ingress に書き戻し、EnsuredLoadBalancer のイベントを記録する。公式ドキュメントの言い方では「LB の作成は非同期に行われ、作られた LB の情報は Service の .status.loadBalancer に公開される」。kubectl get svc の EXTERNAL-IP はこの値を表示している。

Service を消せば同じ controller が LB を消し、ノードが増減すればターゲットの登録も追従させます。

3 つのコントローラー

cloud-controller-manager の中には、Service controller のほかに 2 つのコントローラーが居ます。 公式ドキュメントの説明をまとめると次のとおりです。

cloud-controller-manager の 3 つのコントローラー: Node、Route、ServiceNode controller は Node オブジェクトに providerID やアドレスを埋め、Route controller は Pod CIDR の経路を VPC のルートテーブルに書き、Service controller は type: LoadBalancer の LB を作る。全部 kube-apiserver を watch してクラウド API を呼ぶ。cloud-controller-manager (cloud-provider-aws / -gcp / -azure / kubevirt …)Node controllerNode に providerID、addresses、zone ラベルを埋めるuninitialized taint を外す消えた VM の Node を削除→ 演習クラスタに居るRoute controllerNode の podCIDR をVPC のルートテーブルに書く(kubenet などオーバーレイ無しのCNI と組む)→ Cilium VXLAN では不要Service controllertype: LoadBalancer を見てクラウドの LB を作る / 消すノードをターゲットに登録status に IP を書き戻す→ 演習クラスタでは無効クラウド API
図 2Node controller は Node に providerID やアドレスを埋め、Route controller は Pod CIDR の経路を VPC に書き、Service controller は LB を作る。
  • Node controller:クラウドの API で VM の情報を引き、Node オブジェクトにクラウド側の一意な識別子 (providerID)、ホスト名とアドレス、リージョンやリソースのラベルを埋める。VM がクラウド側で消えていれば Node も消す。--cloud-provider=external で起動した kubelet は Node に node.cloudprovider.kubernetes.io/uninitialized (NoSchedule) の taint を付け、cloud-controller-manager が初期化を終えたときにこの taint を外す。外れるまで Pod は配置されない。
  • Route controller:「別のノードのコンテナ同士が通信できるよう、クラウドに経路を設定する」。第 4 章で見た「クラウドの VPC ルート」方式の Pod ネットワークと組み合わせて使う。VXLAN のオーバーレイや VPC CNI を使うクラスタでは出番が無い。
  • Service controller:上で見たとおり、type: LoadBalancer の Service に対応するクラウドの LB を作る。

どの controller も、Kubernetes 上で求められているクラウドの資源と、クラウド上に実在する資源を突き合わせて差を埋める点で、Part 1 で見た Deployment controller と同じ形をしています。

演習クラスタで pending を観察する

演習クラスタのノードは KubeVirt の VM で、管理クラスタ側で kubevirt-cloud-controller-manager が動いています。 この CCM は Node controller として働き、Node の providerID と addresses を埋めて uninitialized taint を外します。 一方で LoadBalancer の機能は無効にしてあるので、Service controller は何もしません。

演習クラスタの CCM: Node controller は居るが Service controller は無効。type: LoadBalancer は pending のまま管理クラスタ側で動く kubevirt-cloud-controller-manager が Node の providerID を埋める。LoadBalancer 機能は無効化されているので、Service の status.loadBalancer は空のままで、EXTERNAL-IP が pending と表示される。管理クラスタ (KubeVirt)kubevirt-cloud-controller-managerNode controller有効Service controller無効VirtualMachineInstance= 演習クラスタのノードそのもの演習クラスタ (あなたの tenant)Node10.0.1.11providerID: kubevirt://…taint uninitialized は外れている埋めるService web (type: LoadBalancer)EXTERNAL-IP <pending>status.loadBalancer: {} 誰も書かない何もしない
図 3kubevirt-cloud-controller-manager は Node controller として Node を初期化するが、Service controller は無効。type: LoadBalancer の status は誰も書かない。

まず、Node controller の仕事の跡を見ます。

Node に CCM が書いた値を見る
kubectl get node -o jsonpath='{.items[0].spec.providerID}{"\n"}'
kubectl get node -o jsonpath='{range .items[0].status.addresses[*]}{.type}={.address}{"\n"}{end}'
kubectl get node -o jsonpath='{.items[0].spec.taints}{"\n"}'

出力例

kubevirt://tenant-abc-control-plane-xxxx
InternalIP=10.0.1.11
Hostname=tenant-abc-control-plane-xxxx

providerID が kubevirt:// で始まり、taint の一覧に uninitialized が無いのは、Node controller が動いた証拠です。 次に type: LoadBalancer の Service を作ります。

type: LoadBalancer を作って待つ
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: web-lb
spec:
  type: LoadBalancer
  selector: { app: web }
  ports:
    - name: http
      port: 80
      targetPort: 80
EOF
sleep 20
kubectl get svc web-lb
kubectl get svc web-lb -o jsonpath='{.status.loadBalancer}{"\n"}'
kubectl describe svc web-lb | grep -A5 Events

出力例

NAME     TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
web-lb   LoadBalancer   10.96.77.12    <pending>     80:31988/TCP   20s
{}
Events:  <none>

ClusterIP と NodePort は kube-apiserver が割り当てているので埋まっています。 EXTERNAL-IP だけが <pending> で、status.loadBalancer は空の {} のままです。 Events が無いのは、この Service を処理する controller が居ないからです。 EKS なら EnsuringLoadBalancer と EnsuredLoadBalancer のイベントが並び、しばらくして ELB のホスト名が入ります。

<pending> が続くときは、CCM が動いているか、Service controller が有効か、クラウド API を呼ぶ権限 (IAM) があるか、loadBalancerClass で別の controller を指名していないかを確認します。 演習クラスタは 2 番目で止まっています。

片付け
kubectl delete svc web-lb

同じ Service を実装する他の顔ぶれ

「Service を watch して LB を作り、status に IP を書き戻す」controller であれば、クラウドの CCM でなくても type: LoadBalancer を実装できます。

type: LoadBalancer を実装する側の顔ぶれ: クラウドの CCM、オンプレの MetalLB / Cilium LB IPAM、loadBalancerClass での使い分け同じ Service の type: LoadBalancer を、クラウドでは cloud-provider-* の Service controller が、オンプレでは MetalLB や Cilium の LB IPAM が実装する。loadBalancerClass で担当を指定できる。Servicetype: LoadBalancercloud-provider-aws / gcp / azureCCM の Service controller が CLB / NLB を作るMetalLB / Cilium LB IPAMIP プールから払い出し、ARP / BGP で広告AWS Load Balancer ControllerloadBalancerClass: service.k8s.aws/nlbクラウドオンプレloadBalancerClass で指名どれも「Service を watch して LB を作り、status に IP を書き戻す」controller
図 4クラウドでは CCM の Service controller、オンプレでは MetalLB や Cilium の LB IPAM、特定の controller を指名するなら loadBalancerClass。
  • MetalLB / Cilium LB IPAM:オンプレ向け。IP プールから 1 つ払い出して status に書き、その IP をノードから ARP (L2) か BGP で広告する。LB の役割はノード上の書き換え表 (第 5 章) が担い、外部装置は介在しない。
  • AWS Load Balancer Controller:第 8 章で見たコントローラーは Service も扱える。spec.loadBalancerClass: service.k8s.aws/nlb を書くと、こちらが NLB を作る。
  • loadBalancerClass:公式ドキュメントによれば、spec.loadBalancerClass を指定すると「そのクラスに一致する LB 実装が Service を watch していると見なされ、既定の LB 実装 (クラウドプロバイダーのものなど) はこのフィールドが設定された Service を無視する」。Service controller のソースも、LoadBalancerClass が設定された Service を処理対象から外している。複数の実装が同居するクラスタで担当を分けるための仕組みで、空なら CCM の Service controller が担当する。

cloud-controller-manager は Part 5 (パブリッククラウドとの関係) でも扱います。 cloud-provider-aws の中身と、EKS が CCM をどう動かしているかを見ます。

ふりかえり

Qkubectl get svc の EXTERNAL-IP に値を書いているのは?

ClusterIP と NodePort は kube-apiserver が割り当てますが、EXTERNAL-IP は status.loadBalancer.ingress の表示で、LB を作った controller (CCM の Service controller など) が書き戻します。

Q演習クラスタで type: LoadBalancer が pending のままになる理由は?

Node controller は動いていて providerID を埋めていますが、Service controller は無効です。status.loadBalancer を書く controller が居ないので、空のまま pending と表示されます。

QRoute controller が不要になるのはどんな構成?

Route controller は Node の Pod CIDR への経路をクラウドのルートテーブルに書きます。Pod ネットワークの実装がオーバーレイや ENI で疎通を担保する構成では、その仕事がありません。

参考