Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart availability zones #556

Merged
merged 14 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Update Chart.lock with current version of dependencies.

### Added

- Smart defaulting for AWS availability zones using actual AZs in the region of choice rather than hardcoded values.

## [0.66.0] - 2024-03-21

### Added
Expand Down
25 changes: 25 additions & 0 deletions azs-getter/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module github.com/giantswarm/azs-getter

go 1.22.1

require (
github.com/aws/aws-sdk-go-v2/config v1.27.8
github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0
)

require (
github.com/aws/aws-sdk-go-v2 v1.26.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect
github.com/aws/smithy-go v1.20.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
38 changes: 38 additions & 0 deletions azs-getter/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA=
github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
github.com/aws/aws-sdk-go-v2/config v1.27.8 h1:0r8epOsiJ7YJz65MGcb8i91ehFp4kvvFe2qkq5oYeRI=
github.com/aws/aws-sdk-go-v2/config v1.27.8/go.mod h1:XsmYKxYNuIhLsFddpNds+j9H5XKzjWDdg/SZngiwFio=
github.com/aws/aws-sdk-go-v2/credentials v1.17.8 h1:WUdNLXbyNbU07V/WFrSOBXqZTDgmmMNMgUFzpYOKJhw=
github.com/aws/aws-sdk-go-v2/credentials v1.17.8/go.mod h1:iPZzLpaBIfhyvVS/XGD3JvR1GP3YdHTqpySKDlqkfs8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4 h1:S+L2QSKhUuShih3aq9P/mkzDBiOO5tTyVg+vXREfsfg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0 h1:ltCQObuImVYmIrMX65ikB9W83MEun3Ry2Sk11ecZ8Xw=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.152.0/go.mod h1:TeZ9dVQzGaLG+SBIgdLIDbJ6WmfFvksLeG3EHGnNfZM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0=
github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
64 changes: 64 additions & 0 deletions azs-getter/internal/awshelper/awshelper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package awshelper

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
)

type AwsHelper struct {
sdkConfig aws.Config
}

func New(awsConfig aws.Config) (*AwsHelper, error) {
return &AwsHelper{
sdkConfig: awsConfig,
}, nil
}

func (a *AwsHelper) ListRegions(ctx context.Context) ([]string, error) {
ec2Client := ec2.NewFromConfig(a.sdkConfig)
regionsOutput, err := ec2Client.DescribeRegions(ctx, &ec2.DescribeRegionsInput{
AllRegions: aws.Bool(false),
})

if err != nil {
fmt.Println("Couldn't get regions")
return nil, err
}

ret := make([]string, 0)
for _, region := range regionsOutput.Regions {
ret = append(ret, *region.RegionName)
}

return ret, nil
}

func (a *AwsHelper) GetAzsForRegion(ctx context.Context, region string) ([]string, error) {
config := a.sdkConfig
config.Region = region
ec2Client := ec2.NewFromConfig(config)

azsOutput, err := ec2Client.DescribeAvailabilityZones(ctx, &ec2.DescribeAvailabilityZonesInput{
AllAvailabilityZones: aws.Bool(true),
Filters: []types.Filter{{Name: aws.String("zone-type"), Values: []string{"availability-zone"}}},
})

if err != nil {
return nil, err
}

// We just want the zone letter (such as "a") rather than the full zone name (such as "us-west-2a")
zoneNames := make([]string, 0)
for _, az := range azsOutput.AvailabilityZones {
clean, _ := strings.CutPrefix(*az.ZoneName, region)
zoneNames = append(zoneNames, clean)
}

return zoneNames, nil
}
98 changes: 98 additions & 0 deletions azs-getter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

import (
"context"
"fmt"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
awscreds "github.com/aws/aws-sdk-go-v2/credentials"
"gopkg.in/yaml.v3"

"github.com/giantswarm/azs-getter/internal/awshelper"
)

type credentials struct {
accessKeyId string
secretAccessKey string
region string
}

func main() {
ctx := context.TODO()

creds := []credentials{
{
// EUROPE
accessKeyId: os.Getenv("AWS_ACCESS_KEY_ID_EUROPE"),
secretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY_EUROPE"),
region: "eu-west-1",
},
{
// CHINA
accessKeyId: os.Getenv("AWS_ACCESS_KEY_ID_CHINA"),
secretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY_CHINA"),
region: "cn-north-1",
},
}

data := map[string][]string{}

for _, c := range creds {
sdkConfig, err := config.LoadDefaultConfig(ctx, config.WithCredentialsProvider(awscreds.NewStaticCredentialsProvider(c.accessKeyId, c.secretAccessKey, "")), config.WithRegion(c.region))
if err != nil {
fmt.Println("Couldn't load default configuration. Have you set up your AWS account?")
fmt.Println(err)
return
}

azsPerRegion, err := getAzsFromCredentials(ctx, sdkConfig)
if err != nil {
fmt.Println("Error getting azs")
fmt.Println(err)
return
}

for r, azs := range azsPerRegion {
data[r] = azs
}
}

b, err := yaml.Marshal(data)
if err != nil {
fmt.Println("error marshaling azs to yaml")
fmt.Println(err)
return
}

fmt.Print(string(b))
}

func getAzsFromCredentials(ctx context.Context, sdkConfig aws.Config) (map[string][]string, error) {
helper, err := awshelper.New(sdkConfig)
if err != nil {
fmt.Println("Error initializing aws helper")
return nil, err
}

regions, err := helper.ListRegions(ctx)
if err != nil {
fmt.Println("Error listing regions")
return nil, err
}

ret := make(map[string][]string)

for _, region := range regions {
azs, err := helper.GetAzsForRegion(ctx, region)
if err != nil {
fmt.Printf("Couldn't get azs for region %s", region)
return nil, err
}

ret[region] = azs
}

return ret, nil
}
2 changes: 1 addition & 1 deletion helm/cluster-aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Properties within the `.global.connectivity` object
| `global.connectivity.proxy.httpProxy` | **HTTP proxy** - To be passed to the HTTP_PROXY environment variable in all hosts.|**Type:** `string`<br/>|
| `global.connectivity.proxy.httpsProxy` | **HTTPS proxy** - To be passed to the HTTPS_PROXY environment variable in all hosts.|**Type:** `string`<br/>|
| `global.connectivity.proxy.noProxy` | **No proxy** - To be passed to the NO_PROXY environment variable in all hosts.|**Type:** `string`<br/>|
| `global.connectivity.subnets` | **Subnets** - Subnets are created and tagged based on this definition.|**Type:** `array`<br/>**Default:** `[{"cidrBlocks":[{"availabilityZone":"a","cidr":"10.0.0.0/20"},{"availabilityZone":"b","cidr":"10.0.16.0/20"},{"availabilityZone":"c","cidr":"10.0.32.0/20"}],"isPublic":true},{"cidrBlocks":[{"availabilityZone":"a","cidr":"10.0.64.0/18"},{"availabilityZone":"b","cidr":"10.0.128.0/18"},{"availabilityZone":"c","cidr":"10.0.192.0/18"}],"isPublic":false}]`|
| `global.connectivity.subnets` | **Subnets** - Subnets are created and tagged based on this definition.|**Type:** `array`<br/>**Default:** `[{"cidrBlocks":[{"cidr":"10.0.0.0/20"},{"cidr":"10.0.16.0/20"},{"cidr":"10.0.32.0/20"}],"isPublic":true},{"cidrBlocks":[{"cidr":"10.0.64.0/18"},{"cidr":"10.0.128.0/18"},{"cidr":"10.0.192.0/18"}],"isPublic":false}]`|
| `global.connectivity.subnets[*]` | **Subnet**|**Type:** `object`<br/>|
| `global.connectivity.subnets[*].cidrBlocks` | **Network**|**Type:** `array`<br/>|
| `global.connectivity.subnets[*].cidrBlocks[*]` |**None**|**Type:** `object`<br/>|
Expand Down
1 change: 1 addition & 0 deletions helm/cluster-aws/ci/ci-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ global:
- r6i.xlarge
- m5.xlarge
providerSpecific:
region: "eu-west-1"
awsAccountId: "1234567890"
components:
containerd:
Expand Down
2 changes: 2 additions & 0 deletions helm/cluster-aws/ci/test-mc-proxy-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ global:
enabled: true
httpProxy: http://proxy.mcproxy.example.com:4000
httpsProxy: http://proxy.mcproxy.example.com:4000
providerSpecific:
region: "eu-west-1"
2 changes: 2 additions & 0 deletions helm/cluster-aws/ci/test-network-topology-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ global:
vpcMode: private
controlPlane:
apiMode: private
providerSpecific:
region: "eu-west-1"
2 changes: 2 additions & 0 deletions helm/cluster-aws/ci/test-spot-instances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ global:
spotInstances:
enabled: true
maxPrice: 1.2
providerSpecific:
region: "eu-west-1"
2 changes: 2 additions & 0 deletions helm/cluster-aws/ci/test-wc-minimal-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ global:
servicePriority: lowest
connectivity:
baseDomain: example.com
providerSpecific:
region: "eu-west-1"
80 changes: 80 additions & 0 deletions helm/cluster-aws/files/azs-in-region.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
ap-northeast-1:
- a
- c
- d
ap-northeast-2:
- a
- b
- c
- d
ap-northeast-3:
- a
- b
- c
ap-south-1:
- a
- b
- c
ap-southeast-1:
- a
- b
- c
ap-southeast-2:
- a
- b
- c
ca-central-1:
- a
- b
- d
cn-north-1:
- a
- b
- d
cn-northwest-1:
- a
- b
- c
eu-central-1:
- a
- b
- c
eu-north-1:
- a
- b
- c
eu-west-1:
- a
- b
- c
eu-west-2:
- a
- b
- c
eu-west-3:
- a
- b
- c
sa-east-1:
- a
- b
- c
us-east-1:
- a
- b
- c
- d
- e
- f
us-east-2:
- a
- b
- c
us-west-1:
- a
- b
us-west-2:
- a
- b
- c
- d
19 changes: 13 additions & 6 deletions helm/cluster-aws/templates/_aws_cluster.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
{{- if and (regexMatch "\\.internal$" (required "global.connectivity.baseDomain is required" .Values.global.connectivity.baseDomain)) (eq (required "global.connectivity.dns.mode required" .Values.global.connectivity.dns.mode) "public") }}
{{- fail "global.connectivity.dns.mode=public cannot be combined with a '*.internal' baseDomain since reserved-as-private TLDs are not propagated to public DNS servers and therefore crucial DNS records such as api.<baseDomain> cannot be looked up" }}
{{- end }}
{{- $region := include "aws-region" . }}
{{/* $azs is a list of availability zones that are available for the region. Used for defaulting. */}}
{{- $azs := include "azs-in-region" (dict "region" $region "Files" .Files ) | fromYamlArray -}}
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSCluster
metadata:
Expand Down Expand Up @@ -81,14 +84,18 @@ spec:
{{- end }}
{{- else }}
{{- range $i, $cidr := $subnet.cidrBlocks -}}
{{- /*
Use customer-specified availability zone for this subnet, default to picking one of the available zones from $azs variable
We use the 'mod' function as an index because it might be that the number of subnets and the number of availability zones differ in a region
*/}}
{{- $az := $cidr.availabilityZone | default (index $azs (mod $i (len $azs))) -}}
{{- if (eq (len $az) 1) -}}
{{- $az = printf "%s%s" (include "aws-region" $) $az -}}
{{- end -}}
{{/* CAPA v2.3.0 defaults to using the `id` field as subnet name unless it's an unmanaged one (`id` starts with `subnet-`), so use CAPA's previous standard subnet naming scheme */}}
- id: "{{ include "resource.default.name" $ }}-subnet-{{ $subnet.isPublic | default false | ternary "public" "private" }}-{{ if eq (len $cidr.availabilityZone) 1 }}{{ include "aws-region" $ }}{{ end }}{{ $cidr.availabilityZone }}"
- id: "{{ include "resource.default.name" $ }}-subnet-{{ $subnet.isPublic | default false | ternary "public" "private" }}-{{ $az }}"
cidrBlock: "{{ $cidr.cidr }}"
{{- if eq (len $cidr.availabilityZone) 1 }}
availabilityZone: "{{ include "aws-region" $ }}{{ $cidr.availabilityZone }}"
{{- else }}
availabilityZone: "{{ $cidr.availabilityZone }}"
{{- end }}
availabilityZone: "{{ $az }}"
isPublic: {{ $subnet.isPublic | default false }}
{{- if or $subnet.tags $cidr.tags }}
tags:
Expand Down
Loading
Loading