はじめに
こんにちは!ACS事業部の谷合です。
去年12月にパブリックプレビューで、Node autoprovision(以降NAP)がAKSでサポートされたのは皆さんご存じでしょうか?
NAPはAWS社製のOSSであるKarpenterをベースにしたAKSの拡張機能で、ノードのリソースひっ迫などで、スケジューリングできないPodがある場合、自動で事前定義済みのノードをプロビジョニングして、水平スケールさせます。
また、以下の利点があります。
- AKSのオートスケール機能を経由しないので、ノードの払い出しが早い
- ノードのスケールインが早い(オートスケール機能のスケールイン評価には10分かかります)
- NodePoolリソースや、AKSNodeClassリソースなどで、払い出しノードが事前定義できる
- NodePoolリソースを複数作成することで、Kubernetesの機能であるnodeSelector や Node Affinity によって払い出しノードを選択できる。
- 払い出しノードを定義するNodePoolリソースに重みづけをし、優先度を設定できる
パブリックプレビューのアナウンスから1か月以上、触れていなかったので、検証してみます。
なお本ブログは以下のドキュメントを元にしています。
まずはクラスタ作成の準備
ます、aks-preview
CLI拡張機能を有効化します。
$ az extension add --name aks-preview Extension 'aks-preview' 0.5.163 is already installed.
次に、NodeAutoProvisioningPreview
機能フラグを登録します。
$ az feature register --namespace "Microsoft.ContainerService" --name "NodeAutoProvisioningPreview"
NodeAutoProvisioningPreview
機能フラグが登録されたか確認するには、以下のコマンドを実行します。
"state": "Registed"と表示されるまで数分かかる場合があります。
$ az feature show --namespace "Microsoft.ContainerService" --name "NodeAutoProvisioningPreview"
"state": "Registed"と表示されたら、以下コマンドでMicrosoft.ContainerService リソース プロバイダーの登録を最新の情報に更新します。
az provider register --namespace Microsoft.ContainerService
AKSで有効化しよう
現在は以下の制限があります。
- Windows および Azure Linux ノード プールはまだサポートされていません(Ubuntuノードのみのサポートです)
- ノード プール構成による Kubelet 構成はサポートされていません
- NAP は現在、新規クラスター作成でのみ有効にできます
- オーバーレイ ネットワークと cilium ネットワーク ポリシーを使用する必要があります。
それでは、az aks create コマンドを使用して、NAPを有効化したAKSを作成してみましょう。
作成時には、--node-provisioning-mode を "Auto" に設定します。
なお、今回は --nodepool-taints CriticalAddonsOnly=true:NoSchedule
オプションを有効にして、ノードプールに CriticalAddonsOnly=true:NoSchedule
taintを付与してシステムノードプールのみのクラスタとします。
$ az aks create \ --name aks-karp-test \ --resource-group <your resource group> \ --node-provisioning-mode Auto \ --network-plugin azure \ --network-plugin-mode overlay \ --network-dataplane cilium \ --nodepool-taints CriticalAddonsOnly=true:NoSchedule
作成されたAKSで以下コマンドを実行してみてください。Karpenter関連のAPIが入っているはずです。
今回は、nodepoolsリソースを検証対象しています。
$ kubectl api-resources | awk 'NR==1' ; k api-resources | grep karpenter NAME SHORTNAMES APIVERSION NAMESPACED KIND aksnodeclasses aksnc,aksncs karpenter.azure.com/v1alpha2 false AKSNodeClass machines karpenter.sh/v1alpha5 false Machine nodeclaims karpenter.sh/v1beta1 false NodeClaim nodepools karpenter.sh/v1beta1 false NodePool provisioners karpenter.sh/v1alpha5 false Provisioner
以上で、検証の準備完了です。
スケールさせてみようぜ
NAPは、通常ノードのリソース不足の際に、スケジューリングされていないPending状態のPodを検出して、自動でノードを水平スケールさせる機能です。
今回は、AKSクラスタを作成した際に、CriticalAddonsOnly=true:NoSchedule
taintを付与してシステムノードプールのみのクラスタとしたことで、「ノードのリソース不足でPodがスケジューリングできない状態」を疑似的に発生させてみます。
それでは行ってみましょー
まず、適当なPodをデプロイします。
すると、Podはスケジューリングできずに Pending
となるはずです。
この時、ノードは3台のシステムノードのみです。
$ kubectl run test --image=nginx $ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test 0/1 Pending 0 13s <none> <none> <none> <none> $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME aks-nodepool1-36650083-vmss000000 Ready agent 5m52s v1.27.7 10.224.0.4 <none> Ubuntu 22.04.3 LTS 5.15.0-1053-azure containerd://1.7.5-1 aks-nodepool1-36650083-vmss000001 Ready agent 5m49s v1.27.7 10.224.0.6 <none> Ubuntu 22.04.3 LTS 5.15.0-1053-azure containerd://1.7.5-1 aks-nodepool1-36650083-vmss000002 Ready agent 5m43s v1.27.7 10.224.0.5 <none> Ubuntu 22.04.3 LTS 5.15.0-1053-azure containerd://1.7.5-1
同時に以下のコマンドも流しておくことで、新規ノードの払い出しの様子も見ることができるようになります。
$ kubectl get events -A --field-selector source=karpenter -w NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE default 2s Normal Unconsolidatable node/aks-default-65f84 Can't replace with a cheaper node default 2s Normal Unconsolidatable nodeclaim/default-65f84 Can't replace with a cheaper node default 93s Normal Nominated pod/test Pod should schedule on: nodeclaim/default-65f84
すると、Podが新規払い出しされたノード(aks-default-65f84)にスケジューリングされます。
$ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test 1/1 Running 0 2m17s 10.244.3.245 aks-default-65f84 <none> <none>
また、新規ノードにはtaintがないことも分かります。
$ kubectl describe node aks-default-65f84 | grep Taint Taints: <none> $ kubectl describe node aks-nodepool1-36650083-vmss000000 | grep Taint Taints: CriticalAddonsOnly=true:NoSchedule
さて、この新規ノードはどういう定義のノードプールとなっているのでしょう?
正解は、以下のdefault NodePoolリソースと、AKSNodeClassリソースで定義されています。
NodePoolにはノード プロビジョナの要件や、Weightなど定義可能となっています。
また、AKSNodeClassにはノードのイメージやストレージサイズなどを定義します。
NodePoolを複数デプロイしておくことで、スケジューリングされていないPodに合うノードがデプロイされるわけです。
$ kubectl get nodepool default -oyaml apiVersion: karpenter.sh/v1beta1 kind: NodePool metadata: annotations: karpenter.sh/nodepool-hash: "12393960163388511505" kubernetes.io/description: General purpose NodePool for generic workloads meta.helm.sh/release-name: aks-managed-karpenter-overlay meta.helm.sh/release-namespace: kube-system creationTimestamp: "2024-01-18T10:28:20Z" generation: 1 labels: app.kubernetes.io/managed-by: Helm helm.toolkit.fluxcd.io/name: karpenter-overlay-main-adapter-helmrelease helm.toolkit.fluxcd.io/namespace: 65a8fc4f884470000177418e name: default resourceVersion: "410732" uid: 6e9dcd87-dd1d-4167-aa5e-9171926d616c spec: disruption: consolidationPolicy: WhenUnderutilized expireAfter: Never template: spec: nodeClassRef: name: default requirements: - key: kubernetes.io/arch operator: In values: - amd64 - key: kubernetes.io/os operator: In values: - linux - key: karpenter.sh/capacity-type operator: In values: - on-demand - key: karpenter.azure.com/sku-family operator: In values: - D $ kubectl get aksnodeclasses default -oyaml apiVersion: karpenter.azure.com/v1alpha2 kind: AKSNodeClass metadata: annotations: kubernetes.io/description: General purpose AKSNodeClass for running Ubuntu2204 nodes meta.helm.sh/release-name: aks-managed-karpenter-overlay meta.helm.sh/release-namespace: kube-system creationTimestamp: "2024-01-18T10:28:20Z" generation: 1 labels: app.kubernetes.io/managed-by: Helm helm.toolkit.fluxcd.io/name: karpenter-overlay-main-adapter-helmrelease helm.toolkit.fluxcd.io/namespace: 65a8fc4f884470000177418e name: default resourceVersion: "1731" uid: e8baaa03-40b9-43b9-8b35-c90cf6452b53 spec: imageFamily: Ubuntu2204 osDiskSizeGB: 128
上記のノード定義は、ノードのラベルで確認可能となっています。
$ kubectl get node aks-default-65f84 -ojson | jq '.metadata.labels' { beta.kubernetes.io/arch=amd64 beta.kubernetes.io/instance-type=Standard_D2_v4 beta.kubernetes.io/os=linux failure-domain.beta.kubernetes.io/region=japaneast failure-domain.beta.kubernetes.io/zone=japaneast-2 karpenter.azure.com/sku-cpu=2 karpenter.azure.com/sku-family=D karpenter.azure.com/sku-gpu-count=0 karpenter.azure.com/sku-memory=8192 karpenter.azure.com/sku-name=Standard_D2_v4 karpenter.azure.com/sku-networking-accelerated=true karpenter.azure.com/sku-version=4 karpenter.azure.com/zone=japaneast-2 karpenter.sh/capacity-type=on-demand karpenter.sh/initialized=true karpenter.sh/nodepool=default karpenter.sh/registered=true kubernetes.azure.com/cluster=MC_rg-taniai_aks-karp-test_japaneast kubernetes.azure.com/ebpf-dataplane=cilium kubernetes.azure.com/mode=user kubernetes.azure.com/network-name=aks-vnet-25367643 kubernetes.azure.com/network-subnet=aks-subnet kubernetes.azure.com/network-subscription=d79e0410-8e3c-4207-8d0a-1f7885d35859 kubernetes.azure.com/nodenetwork-vnetguid=72f5c0f4-5f2b-413a-8a9c-d2aa0d3d27ce kubernetes.azure.com/podnetwork-type=overlay kubernetes.azure.com/role=agent kubernetes.io/arch=amd64 kubernetes.io/hostname=aks-default-65f84 kubernetes.io/os=linux kubernetes.io/role=agent }
AKSでサポートされているNodePoolリソースのサポートされているノード プロビジョナの要件は以下ドキュメントを参照してください。 learn.microsoft.com また、NodePoolの他の設定は、以下のKarpenterの公式ドキュメントで確認可能です。 karpenter.sh
では、次はPodを削除した際の動きを確認しましょう。
Podを削除して、不要になったノードは、NAPによって自動で削除されます。
$ kubectl delete po test $ kubectl get events -A --field-selector source=karpenter -w NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE : default 0s Normal DisruptionTerminating node/aks-default-65f84 Disrupting Node: Consolidation/Delete default 0s Normal DisruptionTerminating nodeclaim/default-65f84 Disrupting NodeClaim: Consolidation/Delete default 0s Normal DisruptionWaitingDeletion nodeclaim/default-65f84 Waiting on deletion to continue disruption
以下のコマンドでも削除されている様子が確認できるはずです。
$ kubectl get node NAME STATUS ROLES AGE VERSION aks-default-65f84 NotReady agent 7m46s v1.27.7 aks-nodepool1-36650083-vmss000000 Ready agent 15m v1.27.7 aks-nodepool1-36650083-vmss000001 Ready agent 15m v1.27.7 aks-nodepool1-36650083-vmss000002 Ready agent 15m v1.27.7 $ kubectl get node NAME STATUS ROLES AGE VERSION aks-nodepool1-36650083-vmss000000 Ready agent 15m v1.27.7 aks-nodepool1-36650083-vmss000001 Ready agent 15m v1.27.7 aks-nodepool1-36650083-vmss000002 Ready agent 15m v1.27.7
NoodPoolリソースを追加してみよう
NodePoolリソースを追加した際の挙動も見てみましょう。
重みづけで優先度設定をする
まずは以下のNodePoolリソースを追加します。
この時、karpenter.azure.com/sku-cpuに8を指定します。
また、Weightを指定することで優先度をつけることもできます。
今回はをWeightを10にすることで、default NodePoolよりも優先させます。
$ cat << EOT | k apply -f - apiVersion: karpenter.sh/v1beta1 kind: NodePool metadata: name: my-node spec: disruption: consolidationPolicy: WhenUnderutilized expireAfter: Never template: spec: nodeClassRef: name: default requirements: - key: karpenter.azure.com/sku-cpu operator: In values: - "8" weight: 10 EOT
まず、Podをデプロイします。
すると、my-node NodePoolリソースを使用して、ノードが払い出されたことが確認できます。
$ kubectl run test --image=nginx $ kubectl get events -A --field-selector source=karpenter -w NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE : default 0s Normal Nominated pod/test Pod should schedule on: nodeclaim/my-node-lzf4f default 0s Normal Unconsolidatable node/aks-my-node-lzf4f Can't replace with a cheaper node default 0s Normal Unconsolidatable nodeclaim/my-node-lzf4f Can't replace with a cheaper node $ kubectl get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test 1/1 Running 0 9m58s 10.244.3.107 aks-my-node-lzf4f <none> <none>
ノードのスペックを確認してみます。
"karpenter.azure.com/sku-cpu": "8"
ラベルや、'.status.capacity'
からmy-node NodePoolの設定が効いていることが確認できますね!
$ kubectl get node aks-my-node-lzf4f -ojson | jq '.metadata.labels' { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "Standard_D8_v4", "beta.kubernetes.io/os": "linux", "failure-domain.beta.kubernetes.io/region": "japaneast", "failure-domain.beta.kubernetes.io/zone": "japaneast-2", "karpenter.azure.com/sku-cpu": "8", "karpenter.azure.com/sku-family": "D", "karpenter.azure.com/sku-gpu-count": "0", "karpenter.azure.com/sku-memory": "32768", "karpenter.azure.com/sku-name": "Standard_D8_v4", "karpenter.azure.com/sku-networking-accelerated": "true", "karpenter.azure.com/sku-version": "4", "karpenter.azure.com/zone": "japaneast-2", "karpenter.sh/capacity-type": "on-demand", "karpenter.sh/initialized": "true", "karpenter.sh/nodepool": "my-node", "karpenter.sh/registered": "true", "kubernetes.azure.com/cluster": "MC_rg-taniai_aks-karp-test_japaneast", "kubernetes.azure.com/ebpf-dataplane": "cilium", "kubernetes.azure.com/mode": "user", "kubernetes.azure.com/network-name": "aks-vnet-25367643", "kubernetes.azure.com/network-subnet": "aks-subnet", "kubernetes.azure.com/network-subscription": "d79e0410-8e3c-4207-8d0a-1f7885d35859", "kubernetes.azure.com/nodenetwork-vnetguid": "72f5c0f4-5f2b-413a-8a9c-d2aa0d3d27ce", "kubernetes.azure.com/podnetwork-type": "overlay", "kubernetes.azure.com/role": "agent", "kubernetes.io/arch": "amd64", "kubernetes.io/hostname": "aks-my-node-lzf4f", "kubernetes.io/os": "linux", "kubernetes.io/role": "agent", "node-role.kubernetes.io/agent": "", "node.kubernetes.io/instance-type": "Standard_D8_v4", "topology.disk.csi.azure.com/zone": "japaneast-2", "topology.kubernetes.io/region": "japaneast", "topology.kubernetes.io/zone": "japaneast-2" } $ kubectl get node aks-my-node-lzf4f -ojson | jq '.status.capacity' { "cpu": "8", "ephemeral-storage": "129886128Ki", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "memory": "32864128Ki", "pods": "110" }
nodeSelectorでNoodPoolリソースを選択する
次に、以下のNoodPoolリソースを作成します。
この時karpenter.azure.com/sku-memory
を"131072"にします。
$ cat << EOT | k apply -f - apiVersion: karpenter.sh/v1beta1 kind: NodePool metadata: name: my-node-comp spec: disruption: consolidationPolicy: WhenUnderutilized expireAfter: Never template: spec: nodeClassRef: name: default requirements: - key: karpenter.azure.com/sku-memory operator: In values: - "131072" EOT $ kubectl get nodepools.karpenter.sh -owide NAME NODECLASS WEIGHT default default my-node default 10 my-node-comp default system-surge system-surge
次にnodeSelectorで上記NodePoolリソースを選択してみましょう。
nodeSelectorか、Node Affinityが使用できますが、今回はnodeSelectorを使用します。
nodeSelectorに、上記NodePoolリソースの karpenter.azure.com/sku-memory: "131072"
を指定します。
すると、指定したラベルがあるNodePoolリソースで、ノードが払い出されるはずです。
$ cat << EOT | k apply -f - apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: test-comp name: test-comp spec: containers: - image: nginx name: test-comp resources: nodeSelector: karpenter.azure.com/sku-memory: "131072" EOT pod/test-comp created $ kubectl get events -A --field-selector source=karpenter -w : default 0s Normal Nominated pod/test-comp Pod should schedule on: nodeclaim/my-node-comp-v57hv $ kubectl get node NAME STATUS ROLES AGE VERSION aks-my-node-comp-v57hv Ready agent 42s v1.27.7 aks-nodepool1-36650083-vmss000000 Ready agent 4d1h v1.27.7 aks-nodepool1-36650083-vmss000001 Ready agent 4d1h v1.27.7 aks-nodepool1-36650083-vmss000002 Ready agent 4d1h v1.27.7
重みづけの時と同じく、"karpenter.azure.com/sku-memory": "131072"
ラベルや、'.status.capacity'
からmy-node-comp NodePoolの設定が効いていることが確認できます。
$ kubectl get node aks-my-node-comp-v57hv -ojson | jq '.metadata.labels' { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "Standard_E16as_v5", "beta.kubernetes.io/os": "linux", "failure-domain.beta.kubernetes.io/region": "japaneast", "failure-domain.beta.kubernetes.io/zone": "japaneast-2", "karpenter.azure.com/sku-cpu": "16", "karpenter.azure.com/sku-encryptionathost-capable": "true", "karpenter.azure.com/sku-family": "E", "karpenter.azure.com/sku-gpu-count": "0", "karpenter.azure.com/sku-memory": "131072", "karpenter.azure.com/sku-name": "Standard_E16as_v5", "karpenter.azure.com/sku-networking-accelerated": "true", "karpenter.azure.com/sku-storage-premium-capable": "true", "karpenter.azure.com/sku-version": "5", "karpenter.azure.com/zone": "japaneast-2", "karpenter.sh/capacity-type": "spot", "karpenter.sh/initialized": "true", "karpenter.sh/nodepool": "my-node-comp", "karpenter.sh/registered": "true", "kubernetes.azure.com/cluster": "MC_rg-taniai_aks-karp-test_japaneast", "kubernetes.azure.com/ebpf-dataplane": "cilium", "kubernetes.azure.com/mode": "user", "kubernetes.azure.com/network-name": "aks-vnet-25367643", "kubernetes.azure.com/network-subnet": "aks-subnet", "kubernetes.azure.com/network-subscription": "d79e0410-8e3c-4207-8d0a-1f7885d35859", "kubernetes.azure.com/nodenetwork-vnetguid": "72f5c0f4-5f2b-413a-8a9c-d2aa0d3d27ce", "kubernetes.azure.com/podnetwork-type": "overlay", "kubernetes.azure.com/role": "agent", "kubernetes.io/arch": "amd64", "kubernetes.io/hostname": "aks-my-node-comp-v57hv", "kubernetes.io/os": "linux", "kubernetes.io/role": "agent", "node-role.kubernetes.io/agent": "", "node.kubernetes.io/instance-type": "Standard_E16as_v5", "topology.disk.csi.azure.com/zone": "japaneast-2", "topology.kubernetes.io/region": "japaneast", "topology.kubernetes.io/zone": "japaneast-2" } $ kubectl get node aks-my-node-comp-v57hv -ojson | jq '.status.capacity' { "cpu": "16", "ephemeral-storage": "129886128Ki", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "memory": "131903372Ki", "pods": "110" }
重みづけでの優先度設定と、ラベルでのNodePool選択方法をご紹介しました。
NodePool選択時に、指定したラベルを持つNodePoolが複数ある場合は、重みづけをするなどユースケースが考えられると思います。
さいごに
今回はKarpeneterをベースにしたNAPをご紹介しました。
まだパブリックプレビューですが、GAが待ち遠しい機能ですよね。
さらなるAKSのノードスケール機能の進化を心して待ちましょう!
ACS事業部のご紹介
私達ACS事業部はAzure・AKSなどのクラウドネイティブ技術を活用した内製化やGitHub Enterpriseの導入のご支援をしております。
www.ap-com.co.jp
www.ap-com.co.jp
また、一緒に働いていただける仲間も募集中です!
我々の事業部のCultureDeckはコチラ。
www.ap-com.co.jp 今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。 www.ap-com.co.jp