feat(recommendation): add K8s infra recommendation API (unified from control-plane/node-group)#374
feat(recommendation): add K8s infra recommendation API (unified from control-plane/node-group)#374hanizang77 wants to merge 2 commits into
Conversation
…control-plane/node-group)
yunkon-kim
left a comment
There was a problem hiding this comment.
@hanizang77 Kubernetes 인프라 마이그레이션 초안을 작업 하시느라 고생하셨습니다.
한 차례 리뷰 후, 질문/코멘트 드립니다.
확인해 보시고 편하게 의견 남겨주시기 바랍니다.
|
|
||
| // RecommendedK8sCluster is the recommendation output for a K8s cluster migration | ||
| // using the K8sClusterDynamic API path (TB auto-manages vNet/SG/SSHKey). | ||
| type RecommendedK8sCluster struct { |
There was a problem hiding this comment.
몇 가지 질문/코멘트 드립니다.
- 기존 RecommendedInfra.TargetInfra를 검토해 보셨는지요?
- 이를 활용하지 않고 RecommendedK8sCluster를 별도로 정의하신 이유가 있으신지요?
- RecommenedOoo은 타 서브시스템(예, Damselfly)에서 참조하는 것을 전제로 model.go에 정의하고 있습니다. RecommendedK8sCluster도 Damselfly에 통합되야하나요?
- Cluster와 AddtionalNodeGroups로 구분하신 이유가 궁금합니다.
There was a problem hiding this comment.
Question 1 & 2 에 대한 답변:
- RecommendedInfra.TargetInfra를 활용하려면 K8s 타입을 통째로 삽입하거나 K8s 전용 필드를 직접 추가해야 합니다.
- K8s 타입이나 K8s 필드를 추가하면 TB 원본 타입과의 동기화 계약이 깨집니다.
- 통합 작업은 TB의 프로비저닝 레이어가 K8s/VM을 통합한 이후에 자연스럽게 이루어질 것으로 사려됩니다.
Question 3에 대한 답변:
- Damselfly 가 추천 결과를 저장하려면 RecommendedK8sCluster 을 통합해야 할것으로 보입니다.
- 다만 Q1 & Q2에서 언급한 모델 통합 이슈(TB 프로비저닝 레이어의 K8s/VM 통합)가 완전히 해소된 이후에 다루는 편이 좋을 것 같습니다.
Question 4에 대한 답변:
- Azure/GCP는 cluster 생성 시 첫 NodeGroup이 함께 생성되지만, AWS는 cluster 생성 후 NodeGroup을 별도로 추가해야 하는 구조입니다.
- 이 구분 덕분에 CSP별 NodeGroup 생성 시점 차이(cluster와 동시 생성 vs. cluster 생성 후 별도 추가)를 하나의 구조체로 일관되게 표현할 수 있습니다.
- 다만 필드명은 AddtionalNodeGroups 대신 NodeGroups 처럼 일반화하도록 하겠습니다.
There was a problem hiding this comment.
추가 질문드립니다.
(Question 1 & 2 에 대한 답변 관련)
최근, Tumblebug에서 VM Infra와 Kubernetes Infra를 유사한 메커니즘으로 제어하기 위해 아래 용어 변경이 진행된 것으로 알고 있습니다. (see Why the Terminology Change)
이러한 상황임에도 K8s 전용 필드를 직접 추가해야 하는 것인지요?
이것은 혹시 언급하신 "TB의 프로비저닝 레이어가 K8s/VM을 통합"이 진행되지 않았기 때문에 임시로 적용하는 상황이고, 추후 업데이트가 필요한 상황으로 이해하면 될까요?
(Question 3에 대한 답변 관련)
Beetle에서 K8s 인프라 마이그레이션을 위한 모델을 정의하고, 사용자가 알 수 있도록 명확하게 제공해야 합니다.
관련해서 외부에서 활용하도록 제공하는 모델은 imdl/cloud-model/model.go에 정의하기로 협의된 상황이니 이제 맞게 위치를 수정해 주십시오.
Damselfly 부분도 교차 확인해보시고요 (see code)
(참고 - 모델을 명시했으면 좋겠다는 요구사항이 있어 RecommendedInfraModel을 정의 및 제공하고 있었으나, Damselfly의 현황은 RecommendedInfra를 활용하고 있네요.)
(Question 4에 대한 답변 관련)
위에서 문의드린 "TB의 프로비저닝 레이어가 K8s/VM을 통합"이 되면 Beetle API 차원에서는 일반화하고 내부 로직상에서 Tumblebug API를 몇 차례 호출하면 될 것 같은데, 맞을까요?
| // Recommendation APIs for K8s infrastructure | ||
| gRecommendation.POST("/k8sInfraWithDefaults", controller.RecommendK8sInfraWithDefaults) | ||
|
|
||
| // Deprecated: replaced by /k8sInfraWithDefaults |
There was a problem hiding this comment.
@hanizang77
Deprecated 로 표기해 주셨고, 타 서브시스템에 통합되지 않았던 것으로 기억합니다. 삭제해도 될까요?
| emptyRet := cloudmodel.RecommendedK8sCluster{} | ||
|
|
||
| // Step 1: validate CSP support | ||
| nodeGroupCreatedWithCluster, err := isNodeGroupsOnCreation(csp) |
There was a problem hiding this comment.
Tumblebug에서 Managed Kubernetes Cluster와 NodeGroup을 한번에 생성하는 시나리오와 Managed Kubernetes Cluster 생성 후 NodeGroup을 생성하는 시나리오를 구분하고 있나요?
There was a problem hiding this comment.
"Question 4 에 대한 답변"에서 설명한 것과 같이 Cluster와 AdditionalNodeGroups를 분리한 것이 바로 이 두 시나리오를 처리하기 위한 설계입니다. isNodeGroupsOnCreation 플래그로 두 시나리오를 분기하며, AWS EKS 실 환경에서 검증을 진행했습니다.
|
|
||
| // RecommendK8sOsImage searches for a K8s-compatible OS image matching the given node's OS. | ||
| // Returns "default" if no matching image is found (CSP provides a default K8s node image). | ||
| func RecommendK8sOsImage(csp, region string, node onpremmodel.NodeProperty) (string, error) { |
There was a problem hiding this comment.
pkg/core/recommendation/resource-vm-image.go의 RecommendVmOsImages 함수와 로직이 유사하고, 호출하는 Tumblebug API도 tbclient.NewSession().SearchVmOsImage(nsId, searchImageReq)로 동일합니다.
RecommendVmOsImages가 K8s까지 포함하도록 수정한다고 가정했을 때, 이슈가 될만한 것으로 예상되는 부분이 있을까요?
| // K8s minimum requirements (vCPU >= 2, memoryGiB >= 4) are applied before querying. | ||
| // Uses the same POST /tumblebug/recommendSpec endpoint as VM spec recommendation. | ||
| // NCP KVM hypervisor filter from RecommendVmSpecs is intentionally excluded for Managed K8s. | ||
| func RecommendK8sSpecs(csp, region string, node onpremmodel.NodeProperty, limit int) ([]cloudmodel.SpecInfo, int, error) { |
There was a problem hiding this comment.
pkg/core/recommendation/resource-vm-spec.go의 RecommendVmSpecs함수와 로직이 유사하고, 호출하는 Tumblebug API도 tbclient.NewSession().InfraRecommendSpec(planToSearchProperVm)로 동일합니다.
Image 부분과 마찬가지로 RecommendVmSpecs 함수가 K8s까지 포함하도록 수정한다고 가정했을 때, 이슈가 될만한 것으로 예상되는 부분이 있을까요?
| return specId, imageId, nil | ||
| } | ||
|
|
||
| // Deprecated: RecommendK8sControlPlane is replaced by RecommendK8sInfraWithDefaults. |
| } | ||
|
|
||
| return recommendedNodeGroup, nil | ||
| // Deprecated: RecommendK8sNodeGroup is replaced by RecommendK8sInfraWithDefaults. |
Co-authored-by: Yunkon Kim <hermitkim1@gmail.com>
feat(recommendation): add K8s infra recommendation API (unified from control-plane/node-group)
Summary
POST /beetle/recommendation/k8sInfraWithDefaultsto recommend a K8s cluster configuration from on-premise infra info, as scoped in #371desiredNodeSizematching source node count (minimum 2 enforced per group for K8s HA)k8sNodeGroupsOnCreationmap to distinguish CSPs that create NodeGroups with the cluster (Azure, GCP) from those that require separate creation (AWS, Alibaba, Tencent)RecommendK8sControlPlane/RecommendK8sNodeGroupas separate functions; these are replaced by the unifiedRecommendK8sInfraWithDefaults(old functions are deprecated stubs)Test Results (AWS ap-northeast-2)
Phase 1 (Recommendation), Phase 2 (Cluster + NodeGroup creation), and Phase 2-C (Cleanup) all passed without errors.
Test Input (OnpremInfra)
{ "desiredCspAndRegionPair": { "csp": "aws", "region": "ap-northeast-2" }, "onpremiseInfraModel": { "network": { "ipv4Networks": { "cidrBlocks": [ "192.168.0.0/24" ], "defaultGateways": [ { "ip": "192.168.0.1", "interfaceName": "eth0", "machineId": "aabbccdd-0001-0001-0001-000000000001" }, { "ip": "192.168.0.1", "interfaceName": "eth0", "machineId": "aabbccdd-0002-0002-0002-000000000002" }, { "ip": "192.168.0.1", "interfaceName": "eth0", "machineId": "aabbccdd-0003-0003-0003-000000000003" } ] }, "ipv6Networks": {} }, "nodes": [ { "hostname": "k8s-master-01", "machineId": "aabbccdd-0001-0001-0001-000000000001", "role": "control-plane", "cpu": { "architecture": "x86_64", "cpus": 1, "cores": 2, "threads": 4, "maxSpeed": 2.5, "vendor": "GenuineIntel", "model": "Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz" }, "memory": { "type": "DDR4", "totalSize": 8, "available": 4, "used": 4 }, "rootDisk": { "label": "/", "type": "SSD", "totalSize": 50, "available": 30, "used": 20 }, "interfaces": [ { "name": "eth0", "macAddress": "52:54:00:aa:bb:01", "ipv4CidrBlocks": ["192.168.0.10/24"], "mtu": 1500, "state": "UP" } ], "routingTable": [ { "destination": "0.0.0.0/0", "gateway": "192.168.0.1", "interface": "eth0", "metric": 100, "protocol": "static", "scope": "global", "linkState": "UP" }, { "destination": "192.168.0.0/24", "gateway": "", "interface": "eth0", "metric": 0, "protocol": "kernel", "scope": "link", "linkState": "UP" } ], "firewallTable": [ { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "6443", "protocol": "TCP", "direction": "inbound", "action": "allow" }, { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "10250", "protocol": "TCP", "direction": "inbound", "action": "allow" } ], "os": { "prettyName": "Ubuntu 22.04.2 LTS", "name": "Ubuntu", "versionId": "22.04", "versionCodename": "jammy", "id": "ubuntu", "idLike": "debian" } }, { "hostname": "k8s-worker-01", "machineId": "aabbccdd-0002-0002-0002-000000000002", "role": "worker", "cpu": { "architecture": "x86_64", "cpus": 1, "cores": 2, "threads": 4, "maxSpeed": 2.5, "vendor": "GenuineIntel", "model": "Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz" }, "memory": { "type": "DDR4", "totalSize": 8, "available": 5, "used": 3 }, "rootDisk": { "label": "/", "type": "SSD", "totalSize": 50, "available": 35, "used": 15 }, "interfaces": [ { "name": "eth0", "macAddress": "52:54:00:aa:bb:02", "ipv4CidrBlocks": ["192.168.0.11/24"], "mtu": 1500, "state": "UP" } ], "routingTable": [ { "destination": "0.0.0.0/0", "gateway": "192.168.0.1", "interface": "eth0", "metric": 100, "protocol": "static", "scope": "global", "linkState": "UP" } ], "firewallTable": [ { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "10250", "protocol": "TCP", "direction": "inbound", "action": "allow" }, { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "30000-32767", "protocol": "TCP", "direction": "inbound", "action": "allow" } ], "os": { "prettyName": "Ubuntu 22.04.2 LTS", "name": "Ubuntu", "versionId": "22.04", "versionCodename": "jammy", "id": "ubuntu", "idLike": "debian" } }, { "hostname": "k8s-worker-02", "machineId": "aabbccdd-0003-0003-0003-000000000003", "role": "worker", "cpu": { "architecture": "x86_64", "cpus": 1, "cores": 2, "threads": 4, "maxSpeed": 2.5, "vendor": "GenuineIntel", "model": "Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz" }, "memory": { "type": "DDR4", "totalSize": 8, "available": 5, "used": 3 }, "rootDisk": { "label": "/", "type": "SSD", "totalSize": 50, "available": 35, "used": 15 }, "interfaces": [ { "name": "eth0", "macAddress": "52:54:00:aa:bb:03", "ipv4CidrBlocks": ["192.168.0.12/24"], "mtu": 1500, "state": "UP" } ], "routingTable": [ { "destination": "0.0.0.0/0", "gateway": "192.168.0.1", "interface": "eth0", "metric": 100, "protocol": "static", "scope": "global", "linkState": "UP" } ], "firewallTable": [ { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "10250", "protocol": "TCP", "direction": "inbound", "action": "allow" }, { "srcCIDR": "0.0.0.0/0", "srcPorts": "*", "dstCIDR": "0.0.0.0/0", "dstPorts": "30000-32767", "protocol": "TCP", "direction": "inbound", "action": "allow" } ], "os": { "prettyName": "Ubuntu 22.04.2 LTS", "name": "Ubuntu", "versionId": "22.04", "versionCodename": "jammy", "id": "ubuntu", "idLike": "debian" } } ], "k8sCluster": { "name": "on-prem-k8s-cluster", "version": "v1.32.3", "podCIDR": "10.244.0.0/16", "serviceCIDR": "10.96.0.0/12", "cniPlugin": "flannel", "nodePortRange": "30000-32767" } } }Recommendation Result (API Response)
POST /beetle/recommendation/k8sInfraWithDefaults?desiredCsp=aws&desiredRegion=ap-northeast-2{ "success": true, "data": { "cluster": { "name": "k8s-aws-ap-northeast-2", "version": "1.33", "nodeGroupName": "worker-group-1", "connectionName": "aws-ap-northeast-2", "specId": "aws+ap-northeast-2+t3a.medium", "imageId": "default", "rootDiskType": "default", "rootDiskSize": 50, "onAutoScaling": "false", "desiredNodeSize": 2, "minNodeSize": 1, "maxNodeSize": 2 }, "additionalNodeGroups": [ { "name": "worker-group-1", "specId": "aws+ap-northeast-2+t3a.medium", "imageId": "default", "rootDiskType": "default", "rootDiskSize": 50, "onAutoScaling": "false", "desiredNodeSize": 2, "minNodeSize": 1, "maxNodeSize": 2 } ] } }Migration Result
k8s-aws-ap-northeast-2)worker-group-1, 2 nodes)AWS EKS cluster and NodeGroup creation/deletion completed successfully.
References