Skip to content

Commit edd83f2

Browse files
authored
feat: add EKS Hybrid Nodes support (#4336)
* fix hybrid-pods eni resolver * fix caching and add tests * use const for hybridNetworkInterfaceID
1 parent 6a05030 commit edd83f2

File tree

6 files changed

+715
-2
lines changed

6 files changed

+715
-2
lines changed

pkg/k8s/node_utils.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
)
99

1010
var awsInstanceIDRegex = regexp.MustCompile("^i-[^/]*$")
11+
var eksHybridIDRegex = regexp.MustCompile("^mi-[^/]*$")
1112

1213
// GetNodeCondition will get pointer to Node's existing condition.
1314
// returns nil if no matching condition found.
@@ -26,6 +27,18 @@ func ExtractNodeInstanceID(node *corev1.Node) (string, error) {
2627
return "", errors.Errorf("providerID is not specified for node: %s", node.Name)
2728
}
2829

30+
// Check if this is a hybrid node
31+
if strings.HasPrefix(providerID, "eks-hybrid://") {
32+
providerIDParts := strings.Split(providerID, "/")
33+
hybridID := providerIDParts[len(providerIDParts)-1]
34+
if !eksHybridIDRegex.MatchString(hybridID) {
35+
return "", errors.Errorf("providerID %s is invalid for EKS hybrid instances, node: %s", providerID, node.Name)
36+
}
37+
// Return a special prefix to identify hybrid nodes
38+
return "hybrid-" + hybridID, nil
39+
}
40+
41+
// Handle EC2 instances (existing logic)
2942
providerIDParts := strings.Split(providerID, "/")
3043
instanceID := providerIDParts[len(providerIDParts)-1]
3144
if !awsInstanceIDRegex.MatchString(instanceID) {

pkg/k8s/node_utils_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ func TestExtractNodeInstanceID(t *testing.T) {
102102
},
103103
want: "i-abcdefg0",
104104
},
105+
{
106+
name: "node by EKS Hybrid",
107+
args: args{
108+
node: &corev1.Node{
109+
ObjectMeta: metav1.ObjectMeta{
110+
Name: "hybrid-node-name",
111+
},
112+
Spec: corev1.NodeSpec{
113+
ProviderID: "eks-hybrid:///ap-northeast-1/eks-test/mi-01c49bb1234567890",
114+
},
115+
},
116+
},
117+
want: "hybrid-mi-01c49bb1234567890",
118+
},
105119
{
106120
name: "node by EKS Fargate",
107121
args: args{

pkg/networking/networking_manager.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ func (m *defaultNetworkingManager) computeIngressPermissionsPerSGWithPodEndpoint
161161

162162
podsBySG := make(map[string][]k8s.PodInfo)
163163
for podKey, eniInfo := range eniInfoByPodKey {
164+
// Handle hybrid pods specially - they don't have security groups
165+
if eniInfo.NetworkInterfaceID == hybridNetworkInterfaceID {
166+
continue
167+
}
164168
sgID, err := m.resolveEndpointSGForENI(ctx, eniInfo)
165169
if err != nil {
166170
return nil, err

pkg/networking/node_info_provider.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,22 @@ func (p *defaultNodeInfoProvider) FetchNodeInstances(ctx context.Context, nodes
4646
}
4747
nodeKeysByInstanceID := make(map[string][]types.NamespacedName, len(nodes))
4848
for _, node := range nodes {
49+
if node.Labels["eks.amazonaws.com/compute-type"] == "hybrid" {
50+
continue
51+
}
4952
instanceID, err := k8s.ExtractNodeInstanceID(node)
5053
if err != nil {
5154
return nil, err
5255
}
5356
nodeKey := k8s.NamespacedName(node)
5457
nodeKeysByInstanceID[instanceID] = append(nodeKeysByInstanceID[instanceID], nodeKey)
5558
}
59+
60+
// If no EC2 instances to fetch, return empty result
61+
if len(nodeKeysByInstanceID) == 0 {
62+
return make(map[types.NamespacedName]*ec2types.Instance), nil
63+
}
64+
5665
instanceIDs := sets.StringKeySet(nodeKeysByInstanceID).List()
5766
req := &ec2sdk.DescribeInstancesInput{
5867
InstanceIds: instanceIDs,

pkg/networking/pod_eni_info_resolver.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828

2929
labelEKSComputeType = "eks.amazonaws.com/compute-type"
3030
labelSageMakerComputeType = "sagemaker.amazonaws.com/compute-type"
31+
hybridNetworkInterfaceID = "hybrid-no-eni"
3132
)
3233

3334
// PodENIInfoResolver is responsible for resolve the AWS VPC ENI that supports pod network.
@@ -176,6 +177,18 @@ func (r *defaultPodENIInfoResolver) resolvePodsViaCascadedLookup(ctx context.Con
176177
}
177178
}
178179
}
180+
// Hybrid pods don't have ENIs - they're connected via Direct Connect
181+
// We'll handle them specially in the networking manager
182+
if len(podsByComputeType.hybridPods) > 0 {
183+
// Return empty ENI info for hybrid pods - they'll be handled specially
184+
for _, pod := range podsByComputeType.hybridPods {
185+
eniInfoByPodKey[pod.Key] = ENIInfo{
186+
// Use a special identifier to mark this as a hybrid pod
187+
NetworkInterfaceID: hybridNetworkInterfaceID,
188+
SecurityGroups: []string{},
189+
}
190+
}
191+
}
179192
return eniInfoByPodKey, nil
180193
}
181194

@@ -401,14 +414,15 @@ func (r *defaultPodENIInfoResolver) isPodSupportedByNodeENI(pod k8s.PodInfo, nod
401414
return false
402415
}
403416

404-
// PodsByComputeType groups pods based on their compute type (EC2, Fargate, SageMaker HyperPod)
417+
// PodsByComputeType groups pods based on their compute type (EC2, Fargate, SageMaker HyperPod, Hybrid)
405418
type PodsByComputeType struct {
406419
ec2Pods []k8s.PodInfo
407420
fargatePods []k8s.PodInfo
408421
sageMakerHyperPodPods []k8s.PodInfo
422+
hybridPods []k8s.PodInfo
409423
}
410424

411-
// classifyPodsByComputeType classifies in to ec2, fargate and sagemaker-hyperpod groups
425+
// classifyPodsByComputeType classifies in to ec2, fargate, sagemaker-hyperpod and hybrid groups
412426
func (r *defaultPodENIInfoResolver) classifyPodsByComputeType(ctx context.Context, pods []k8s.PodInfo) (PodsByComputeType, error) {
413427
var podsByComputeType PodsByComputeType
414428
nodeNameByComputeType := make(map[string]string)
@@ -418,9 +432,12 @@ func (r *defaultPodENIInfoResolver) classifyPodsByComputeType(ctx context.Contex
418432
podsByComputeType.fargatePods = append(podsByComputeType.fargatePods, pod)
419433
} else if nodeNameByComputeType[pod.NodeName] == "sagemaker-hyperpod" {
420434
podsByComputeType.sageMakerHyperPodPods = append(podsByComputeType.sageMakerHyperPodPods, pod)
435+
} else if nodeNameByComputeType[pod.NodeName] == "hybrid" {
436+
podsByComputeType.hybridPods = append(podsByComputeType.hybridPods, pod)
421437
} else {
422438
podsByComputeType.ec2Pods = append(podsByComputeType.ec2Pods, pod)
423439
}
440+
continue // Skip the rest of the loop iteration since we already processed this pod
424441
}
425442

426443
nodeKey := types.NamespacedName{Name: pod.NodeName}
@@ -434,6 +451,9 @@ func (r *defaultPodENIInfoResolver) classifyPodsByComputeType(ctx context.Contex
434451
} else if node.Labels[labelSageMakerComputeType] == "hyperpod" {
435452
podsByComputeType.sageMakerHyperPodPods = append(podsByComputeType.sageMakerHyperPodPods, pod)
436453
nodeNameByComputeType[pod.NodeName] = "sagemaker-hyperpod"
454+
} else if node.Labels[labelEKSComputeType] == "hybrid" {
455+
podsByComputeType.hybridPods = append(podsByComputeType.hybridPods, pod)
456+
nodeNameByComputeType[pod.NodeName] = "hybrid"
437457
} else {
438458
podsByComputeType.ec2Pods = append(podsByComputeType.ec2Pods, pod)
439459
nodeNameByComputeType[pod.NodeName] = "ec2"

0 commit comments

Comments
 (0)