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 の Service を apply すると、次の順で進みます。
- kube-apiserver に Service が保存される。この時点で ClusterIP と NodePort は割り当て済みで、
status.loadBalancerは空。 - cloud-controller-manager の Service controller が watch でこの Service を見つけ、Service に
EnsuringLoadBalancerというイベントを記録する。 - クラウドプロバイダーの実装 (AWS なら cloud-provider-aws) の
EnsureLoadBalancerが、クラウドの API を呼んで LB を作る。cloud-provider-aws のドキュメントによれば、既定では Classic Load Balancer で、annotationservice.beta.kubernetes.io/aws-load-balancer-type: nlbを付けると NLB になる。 - 全ノードの NodePort をターゲットに登録する。第 6 章で見た「LB はノードに送る」構成がここで作られる。
- できた 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 つのコントローラーが居ます。 公式ドキュメントの説明をまとめると次のとおりです。
- 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 は何もしません。
まず、Node controller の仕事の跡を見ます。
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-xxxxproviderID が kubevirt:// で始まり、taint の一覧に uninitialized が無いのは、Node controller が動いた証拠です。
次に type: LoadBalancer の Service を作ります。
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 を実装できます。
- 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 で疎通を担保する構成では、その仕事がありません。
参考
- Kubernetes ドキュメント「Cloud Controller Manager」(Node、Route、Service の 3 つのコントローラー) https://kubernetes.io/docs/concepts/architecture/cloud-controller/
- Kubernetes ドキュメント「Cloud Controller Manager Administration」(uninitialized taint の付与と除去、–cloud-provider=external) https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/
- Kubernetes ブログ「Completing the largest migration in Kubernetes history」(1.31 で in-tree プロバイダーを削除) https://kubernetes.io/blog/2024/05/20/completing-cloud-provider-migration/
- Kubernetes ドキュメント「Service」(type: LoadBalancer の非同期な作成と status、loadBalancerClass の意味) https://kubernetes.io/docs/concepts/services-networking/service/
- cloud-provider の Service controller のソース (EnsuringLoadBalancer / EnsuredLoadBalancer のイベント、loadBalancerClass の扱い) https://github.com/kubernetes/kubernetes/blob/release-1.37/staging/src/k8s.io/cloud-provider/controllers/service/controller.go
- cloud-provider-aws「Service controller」(既定は CLB、annotation で NLB) https://cloud-provider-aws.sigs.k8s.io/service_controller/
- AWS Load Balancer Controller「NLB」(loadBalancerClass: service.k8s.aws/nlb) https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/service/nlb/
- MetalLB ドキュメント「Concepts」 https://metallb.io/concepts/
- Cilium ドキュメント「LoadBalancer IP Address Management」(IP プール、loadBalancerClass の扱い) https://docs.cilium.io/en/stable/network/lb-ipam/