自分のクラスタのノードは、どの基盤の上で動いているのでしょうか。 Node の情報や、Service に記録された出来事から、その手がかりを探せます。
この章では Web ターミナルを使い、クラウド連携に関わる設定と状態を読みます。 外部アドレスが決まらない Service も作り、担当するプログラムがいないと何が起きるかを確かめます。
GOAL この章のゴール
- Node に記録された、基盤に関する情報を読める
- Service の状態とイベントから、外部公開の進み具合を確認できる
- クラスタ自体を Kubernetes のリソースとして管理する仕組みが分かる
手元のクラスタの構造
実習用のノードは、運営側のクラスタ上で動く VM です。 自分のクラスタと、それを動かす管理側のクラスタの関係を図で見ます。
この VM を動かしているのは KubeVirtキューブバートKubernetes の上で VM を動かす仕組み。用語集で見る です。 EKS に置き換えると、管理クラスタが「AWS 管理のアカウント」、KubeVirt の VM が「EC2」、kubevirt-cloud-controller-manager が「cloud-provider-aws」、kubevirt-csi-driver が「aws-ebs-csi-driver」に当たります。 EKS との違いはコントロールプレーンの置き場所で、ここではコントロールプレーンも VM の中 (kubeadm で構築、1 ノードで control-plane 兼 worker) にあります。 2 章目の ManagedBoundary で言う「自前 (EC2 + kubeadm)」に、CCM と CSI だけ管理側から供給されている構成です。
kubectl version
kubectl get nodes -o wide出力例
Server Version: v1.37.1
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE CONTAINER-RUNTIME
tenant-xxxx-control-plane-abcde Ready control-plane 3h v1.37.1 10.0.1.11 Ubuntu 26.04.1 LTS containerd://2.xNode に残った CCM の指紋
3 章目で、Node controller が providerID と addresses を書き、uninitialized taint を外すと説明しました。
Kubernetes のドキュメントの言葉では、Node controller は「クラウドの API から得たサーバーの一意な ID で Node を更新し、リージョンやリソースのようなクラウド固有の情報を label と annotation に付け、ホスト名とネットワークアドレスを得る」部品です。
その跡を場所を決めて見に行きます。
NODE=$(kubectl get node -o jsonpath='{.items[0].metadata.name}')
kubectl get node "$NODE" -o json | jq '{
providerID: .spec.providerID,
addresses: .status.addresses,
taints: .spec.taints,
labels: (.metadata.labels | with_entries(select(.key | test("topology|instance-type|kubernetes.io/(hostname|os|arch)"))))
}'出力例
{
"providerID": "kubevirt://tenant-xxxx-control-plane-abcde",
"addresses": [
{ "address": "10.0.1.11", "type": "InternalIP" },
{ "address": "tenant-xxxx-control-plane-abcde", "type": "Hostname" }
],
"taints": null,
"labels": {
"kubernetes.io/arch": "arm64",
"kubernetes.io/hostname": "tenant-xxxx-control-plane-abcde",
"kubernetes.io/os": "linux"
}
}出力を上から読みます。
providerIDはkubevirt://<VM 名>で埋まっています。この値を書いたのは CCM の Node controller で、管理クラスタの VirtualMachine を見て書いています。EKS ならaws:///ap-northeast-1a/i-0123456789abcdef0という形で、i-…の部分がそのまま EC2 のインスタンス ID です。taintsにnode.cloudprovider.kubernetes.io/uninitializedがありません。CCM が初期化を終えて外した証拠です。topology.kubernetes.io/zoneやnode.kubernetes.io/instance-typeの label は、provider が埋める場合と埋めない場合があります。EKS では zone にap-northeast-1a、instance-type にm6g.largeのような値が入り、scheduler の topology spread や StorageClass のallowedTopologiesがそれを使います。この環境で空なら、kubevirt CCM がそこまで埋めていないということです。
CSI 側の登録も同じ場所で確かめられます。
kubectl get node "$NODE" -o jsonpath='{.metadata.annotations}' | jq 'with_entries(select(.key | test("cloud|kubevirt|csi")))'
kubectl get csinodes "$NODE" -o jsonpath='{.spec.drivers[*].name}' ; echo出力例
{
"csi.volume.kubernetes.io/nodeid": "{\"csi.kubevirt.io\":\"tenant-xxxx-control-plane-abcde\"}"
}
csi.kubevirt.ioCSI の Node plugin は csi.kubevirt.io として登録されています。
EKS なら ebs.csi.aws.com と efs.csi.aws.com が並ぶ場所です。
pending のままの LoadBalancer
3 章目で、Service controller の不在を <pending> で確かめました。
今度は時間を追って、「何が起きないか」を記録します。
kubectl create deployment hello --image=nginx:1.29 --port=80
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: LoadBalancer
selector:
app: hello
ports:
- port: 80
targetPort: 80
EOF
kubectl get svc hello -w &
sleep 20; kill %1出力例
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.96.45.67 <pending> 80:30987/TCP 0s出力は、20 秒待っても 1 行だけです。
-w は status が更新されたときに新しい行を出すので、1 行だけということは status が一度も更新されていないということです。
EKS では同じ -w で、数十秒後に EXTERNAL-IP が DNS 名に変わる行がもう 1 行出ます。
kubectl get events --field-selector involvedObject.name=hello,involvedObject.kind=Service
kubectl get svc hello -o jsonpath='{.status}' ; echo
kubectl get endpointslices -l kubernetes.io/service-name=hello
kubectl run -it --rm probe --image=curlimages/curl:8.11.0 --restart=Never -- curl -s -o /dev/null -w '%{http_code}\n' http://hello出力例
No resources found in default namespace.
{"loadBalancer":{}}
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
hello-abc12 IPv4 80 10.244.0.23 1m
200この出力から 4 つの事実が読めます。
- Events が無い。Service controller が居れば
EnsuringLoadBalancer→EnsuredLoadBalancer(またはSyncLoadBalancerFailed) が記録されます。何も無いのは、誰もこの Service を reconcile していないからです。 status.loadBalancerが空。だから<pending>です。- EndpointSlice はある。これは kube-controller-manager の endpointslice controller の仕事で、CCM とは無関係です。
- ClusterIP で届く。ClusterIP 宛ての通信を Pod へ送り届ける仕組み (この環境では Cilium の eBPF) も CCM とは無関係です。
Service の転送はすべて動いていて、欠けているのは外からの入口を用意する Service controller だけです。
Kubernetes のドキュメントが「外部 LB の無い環境では type: LoadBalancer は NodePort と同じように働く」と書いている通りの状態です。
kubectl delete svc hello
kubectl delete deployment helloクラスタ自体を宣言する Cluster API
最後に、手元のクラスタがどうやって建ったかを見ます。 管理クラスタ側の話なので、ここは図で追います。
Cluster APIクラスターエーピーアイクラスタそのものを Kubernetes のリソース (Cluster、Machine など) として宣言し、controller が VM や EC2 を作る仕組み。用語集で見る のドキュメントは、関係する部品を次のように定義しています。
Cluster API とその provider が動き、他のクラスタの一生を管理するクラスタが management cluster (管理クラスタ)。
管理される側のクラスタが workload cluster で、Cluster という CR で表されます。
Machine は「Kubernetes の Node を載せるインフラ (たとえば VM) の宣言的な spec」で、MachineDeployment は Kubernetes の Deployment と同じ要領で Machine の集合を更新します。
VM やネットワークを実際に作るのが infrastructure provider、サーバーを Kubernetes のノードに仕立てるのが bootstrap provider (kubeadm)、コントロールプレーンの Machine を束ねるのが KubeadmControlPlane です。
kube-classroom は、利用者の handson-<user> namespace に Cluster を作ります。
Cluster API の controller がそれを見て KubeadmControlPlane と Machine を作り、infrastructure provider (この環境では cluster-api-provider-kubevirt) が KubevirtMachine から KubeVirt の VirtualMachine を作り、VM が起動すると cloud-init で kubeadm が走ってノードになります。
kubeconfig は <cluster>-kubeconfig という Secret に置かれ、Web ターミナル はそれを読んで kubectl を通しています。
Part 1 の reconciliation loop が、ここでは「クラスタそのもの」を対象に回っています。
VM を消せば Machine controller が気づいて作り直し、MachineDeployment の replicas を増やせばノードが増えます。
EKS を Cluster API で建てる場合は AWSManagedControlPlane という CR を使い、コントロールプレーンの部分を EKS に委ねます。
eksctl が CloudFormation で、Terraform が state ファイルでやっていることを、Kubernetes の CR と controller でやる、というのが Cluster API です。
Part 5 のまとめ
- クラウドのサービスは「どの層を誰が管理するか」の物差しで並ぶ。マネージド Kubernetes の最低ラインはコントロールプレーンと etcd で、ノード / CSI / LB 連携の境界はサービスごとに違う。
type: LoadBalancerから LB が生えるのは Service controller の reconcile。EXTERNAL-IP は controller が status に書いた値で、書く人が居なければ pending のまま。- EKS は Kubernetes の拡張点に AWS の OSS を差し込んだもので、部品はリポジトリ名で呼べる。Auto Mode はその多くを AWS 側に移した。
- Fargate は task / Pod ごとの microVM。その下の Firecracker と、Google の gVisor は、同じ「他人のコードを同居させる」問題への別の答えで、境界を置く層が違う。
- 手元のクラスタも同じ構造を持っている。providerID、taint、Events、status を読めば、どのクラウドの上で、誰が何をしているかが分かる。
ふりかえり
Qこの教材のクラスタで providerID が kubevirt:// で埋まっているのは誰の仕事?
kubelet は –cloud-provider=external で起動し、uninitialized taint 付きで Node を登録するだけです。providerID と addresses を埋めて taint を外すのは CCM の Node controller で、この環境では管理クラスタ側で動いています。
Qtype: LoadBalancer の Service が pending のまま、しかし ClusterIP では届く。この状態から言えることは?
EndpointSlice は endpointslice controller、ClusterIP の転送は kube-proxy や Cilium の仕事で、どちらも CCM とは無関係に動きます。欠けているのは status.loadBalancer を書く Service controller だけです。
QEKS で kubectl get clusters が何も返さないのはなぜ?
クラスタ自体の定義は Kubernetes の外 (EKS の API、CloudFormation、または管理クラスタの Cluster API) にあります。自分のクラスタの kubeconfig からは見えません。Cluster API で EKS を建てることもできますが、その CR も管理クラスタ側にあります。
参考
- Cloud Controller Manager (Kubernetes Documentation, Concepts)
- Cloud Controller Manager Administration (Kubernetes Documentation, Tasks)
- Service (Kubernetes Documentation, Concepts)
- Concepts (The Cluster API Book)
- Route TCP and UDP traffic with Network Load Balancers (Amazon EKS User Guide)
読み切ると称号を獲得
雲の裏側を知る者
この Part を読み切りました。
称号一覧