scripts/gen_vpc_ip_limits.go (299 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // This program generates vpc_ip_resource_limit.go // It can be invoked by running `go run` package main import ( "context" "fmt" "os" "reflect" "sort" "strconv" "text/template" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" "github.com/aws/amazon-vpc-cni-k8s/pkg/vpc" "github.com/aws/aws-sdk-go-v2/service/ec2" ) const ipLimitFileName = "pkg/vpc/vpc_ip_resource_limit.go" const eniMaxPodsFileName = "misc/eni-max-pods.txt" var log = logger.DefaultLogger() // Helper to calculate the --max-pods to match the ENIs and IPs on the instance func printPodLimit(instanceType string, l vpc.InstanceTypeLimits) string { maxPods := l.ENILimit*(l.IPv4Limit-1) + 2 return fmt.Sprintf("%s %d", instanceType, maxPods) } func main() { ctx := context.Background() cfg, err := config.LoadDefaultConfig(ctx) if err != nil { log.Fatalf("Failed to load configuration: %v", err) } // Get instance types limits across all regions regions := describeRegions(ctx, cfg) eniLimitMap := make(map[string]vpc.InstanceTypeLimits) for _, region := range regions { describeInstanceTypes(ctx, cfg, region, eniLimitMap) } // Override faulty values and add missing instance types eniLimitMap = addManualLimits(eniLimitMap) // Sort the keys instanceTypes := make([]string, 0) for k := range eniLimitMap { instanceTypes = append(instanceTypes, k) } sort.Strings(instanceTypes) // Generate instance ENI limits f, err := os.Create(ipLimitFileName) if err != nil { log.Fatalf("Failed to create file: %v\n", err) } err = limitsTemplate.Execute(f, struct { ENILimits map[string]vpc.InstanceTypeLimits Regions []string }{ ENILimits: eniLimitMap, Regions: regions, }) if err != nil { log.Fatalf("Failed to generate template: %v\n", err) } log.Infof("Generated %s", ipLimitFileName) // Generate --max-pods file for awslabs/amazon-eks-ami eniPods := make([]string, 0) for _, it := range instanceTypes { eniPods = append(eniPods, printPodLimit(it, eniLimitMap[it])) } f, err = os.Create(eniMaxPodsFileName) if err != nil { log.Fatalf("Failed to create file: %s\n", err) } err = eksMaxPodsTemplate.Execute(f, struct { ENIPods []string Regions []string }{ ENIPods: eniPods, Regions: regions, }) if err != nil { log.Fatalf("Failed to generate template: %v\n", err) } log.Infof("Generated %s", eniMaxPodsFileName) } // Helper function to call the EC2 DescribeRegions API, returning sorted region names // Note that the credentials being used may not be opted-in to all regions func describeRegions(ctx context.Context, cfg aws.Config) []string { client := ec2.NewFromConfig(cfg) output, err := client.DescribeRegions(ctx, &ec2.DescribeRegionsInput{}) if err != nil { log.Fatalf("Failed to call EC2 DescribeRegions: %v", err) } var regionNames []string for _, region := range output.Regions { regionNames = append(regionNames, *region.RegionName) } sort.Strings(regionNames) return regionNames } // Helper function to call the EC2 DescribeInstanceTypes API for a region and merge the respective instance-type limits into eniLimitMap func describeInstanceTypes(ctx context.Context, cfg aws.Config, region string, eniLimitMap map[string]vpc.InstanceTypeLimits) { log.Infof("Describing instance types in region=%s", region) cfg.Region = region client := ec2.NewFromConfig(cfg) paginator := ec2.NewDescribeInstanceTypesPaginator(client, &ec2.DescribeInstanceTypesInput{}) // Iterate through all pages for paginator.HasMorePages() { output, err := paginator.NextPage(ctx) if err != nil { log.Fatalf("Failed to call EC2 DescribeInstanceTypes: %v", err) } // We just want the type name, ENI and IP limits for _, info := range output.InstanceTypes { // Ignore any missing values instanceType := string(info.InstanceType) // only one network card is supported, so use the MaximumNetworkInterfaces from the default card if more than one are present var eniLimit int if len(info.NetworkInfo.NetworkCards) > 1 { eniLimit = int(*info.NetworkInfo.NetworkCards[*info.NetworkInfo.DefaultNetworkCardIndex].MaximumNetworkInterfaces) } else { eniLimit = int(*info.NetworkInfo.MaximumNetworkInterfaces) } ipv4Limit := int(*info.NetworkInfo.Ipv4AddressesPerInterface) isBareMetalInstance := *info.BareMetal hypervisorType := string(info.Hypervisor) if hypervisorType == "" { hypervisorType = "unknown" } networkCards := make([]vpc.NetworkCard, *info.NetworkInfo.MaximumNetworkCards) defaultNetworkCardIndex := int(*info.NetworkInfo.DefaultNetworkCardIndex) for idx := 0; idx < len(networkCards); idx++ { networkCards[idx] = vpc.NetworkCard{ MaximumNetworkInterfaces: int64(*info.NetworkInfo.NetworkCards[idx].MaximumNetworkInterfaces), NetworkCardIndex: int64(*info.NetworkInfo.NetworkCards[idx].NetworkCardIndex), } } if instanceType != "" && eniLimit > 0 && ipv4Limit > 0 { limits := vpc.InstanceTypeLimits{ ENILimit: eniLimit, IPv4Limit: ipv4Limit, NetworkCards: networkCards, HypervisorType: strconv.Quote(hypervisorType), IsBareMetal: isBareMetalInstance, DefaultNetworkCardIndex: defaultNetworkCardIndex, } if existingLimits, contains := eniLimitMap[instanceType]; contains && !reflect.DeepEqual(existingLimits, limits) { // this should never happen log.Fatalf("A previous region has different limits for instanceType=%s than region=%s", instanceType, region) } eniLimitMap[instanceType] = limits } } } } // addManualLimits has the list of faulty or missing instance types // Instance types added here are missing the NetworkCard info due to not being publicly available. Only supporting // NetworkCard for instances currently accessible from the EC2 API to match customer accessibility. func addManualLimits(limitMap map[string]vpc.InstanceTypeLimits) map[string]vpc.InstanceTypeLimits { manuallyAddedLimits := map[string]vpc.InstanceTypeLimits{ "cr1.8xlarge": { ENILimit: 8, IPv4Limit: 30, HypervisorType: strconv.Quote("unknown"), IsBareMetal: false, }, "hs1.8xlarge": { ENILimit: 8, IPv4Limit: 30, HypervisorType: strconv.Quote("unknown"), IsBareMetal: false, }, "u-12tb1.metal": { ENILimit: 5, IPv4Limit: 30, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "u-18tb1.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "u-24tb1.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "u-6tb1.metal": { ENILimit: 5, IPv4Limit: 30, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "u-9tb1.metal": { ENILimit: 5, IPv4Limit: 30, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "c5a.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "c5ad.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, "p4de.24xlarge": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("nitro"), IsBareMetal: false, }, "c7g.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("nitro"), IsBareMetal: true, }, "bmn-sf1.metal": { ENILimit: 15, IPv4Limit: 50, HypervisorType: strconv.Quote("unknown"), IsBareMetal: true, }, } for instanceType, instanceLimits := range manuallyAddedLimits { val, ok := limitMap[instanceType] if ok { if reflect.DeepEqual(val, instanceLimits) { fmt.Printf("Delete %q: %v is already correct in the API\n", instanceType, val) } else { fmt.Printf("Replacing API value %v with override %v for %q\n", val, instanceLimits, instanceType) } } else { fmt.Printf("Adding %q: %v since it is missing from the API\n", instanceType, instanceLimits) } limitMap[instanceType] = instanceLimits } return limitMap } var limitsTemplate = template.Must(template.New("").Parse(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // Code generated by go generate; DO NOT EDIT. // // The regions queried were: {{- range $region := .Regions}} {{ printf "// - %s" $region }} {{- end }} package vpc // InstanceNetworkingLimits contains a mapping from instance type to networking limits for the type. Documentation found at // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI var instanceNetworkingLimits = map[string]InstanceTypeLimits{ {{- range $key, $value := .ENILimits}} "{{$key}}": { ENILimit: {{.ENILimit}}, IPv4Limit: {{.IPv4Limit}}, DefaultNetworkCardIndex: {{.DefaultNetworkCardIndex}}, NetworkCards: []NetworkCard{ {{- range .NetworkCards}} { MaximumNetworkInterfaces: {{.MaximumNetworkInterfaces}}, NetworkCardIndex: {{.NetworkCardIndex}}, }, {{end}} }, HypervisorType: {{.HypervisorType}}, IsBareMetal: {{.IsBareMetal}}, }, {{- end }} } `)) var eksMaxPodsTemplate = template.Must(template.New("").Parse(`# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You may # not use this file except in compliance with the License. A copy of the # License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is distributed # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either # express or implied. See the License for the specific language governing # permissions and limitations under the License. # # The regions queried were: {{- range $region := .Regions}} {{ printf "# - %s" $region }} {{- end }} # # Mapping is calculated from AWS EC2 API using the following formula: # * First IP on each ENI is not used for pods # * +2 for the pods that use host-networking (AWS CNI and kube-proxy) # # # of ENI * (# of IPv4 per ENI - 1) + 2 # # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI # # NOTE: For multi-card instance types (p5.48xlarge) the max limits is calculated only against the default network card at index (0). # {{- range $instanceLimit := .ENIPods}} {{ printf "%s" $instanceLimit }} {{- end }} `))