From ea61fb8de0ddd91aa45f71b9f1e06fb8b4de1b70 Mon Sep 17 00:00:00 2001 From: Thomas Jackson Date: Fri, 21 Jun 2019 15:48:33 -0700 Subject: [PATCH] Replace behavior for aws hostnameOverride If the cluster's VPC includes DHCP options the local-hostname includes the DHCP zone instead of the private DNS name from AWS (which is what k8s uses regardless of flags). This patch simply makes the hostnameOverride implementation match by using the AWS api to get the private DNS name Related to #7172 --- nodeup/pkg/model/BUILD.bazel | 2 ++ nodeup/pkg/model/context.go | 45 +++++++++++++++++++++++++---------- upup/pkg/fi/nodeup/command.go | 41 +++++++++++++++++++++---------- 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/nodeup/pkg/model/BUILD.bazel b/nodeup/pkg/model/BUILD.bazel index 99e8088a0a..b47d2287f1 100644 --- a/nodeup/pkg/model/BUILD.bazel +++ b/nodeup/pkg/model/BUILD.bazel @@ -59,8 +59,10 @@ go_library( "//util/pkg/proxy:go_default_library", "//util/pkg/reflectutils:go_default_library", "//util/pkg/vfs:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/ec2metadata:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", "//vendor/github.com/blang/semver:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", diff --git a/nodeup/pkg/model/context.go b/nodeup/pkg/model/context.go index df400348db..e7de559c68 100644 --- a/nodeup/pkg/model/context.go +++ b/nodeup/pkg/model/context.go @@ -33,6 +33,10 @@ import ( "k8s.io/kops/util/pkg/vfs" "k8s.io/kubernetes/pkg/util/mount" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/blang/semver" "k8s.io/klog" ) @@ -534,25 +538,40 @@ func EvaluateHostnameOverride(hostnameOverride string) (string, error) { return hostnameOverride, nil } - // We recognize @aws as meaning "the local-hostname from the aws metadata service" - vBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/local-hostname") + // We recognize @aws as meaning "the private DNS name from AWS", to generate this we need to get a few pieces of information + azBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/placement/availability-zone") if err != nil { - return "", fmt.Errorf("error reading local hostname from AWS metadata: %v", err) + return "", fmt.Errorf("error reading availability zone from AWS metadata: %v", err) } - // The local-hostname gets it's hostname from the AWS DHCP Option Set, which - // may provide multiple hostnames separated by spaces. For now just choose - // the first one as the hostname. - domains := strings.Fields(string(vBytes)) - if len(domains) == 0 { - klog.Warningf("Local hostname from AWS metadata service was empty") - return "", nil + instanceIDBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/instance-id") + if err != nil { + return "", fmt.Errorf("error reading instance-id from AWS metadata: %v", err) } - domain := domains[0] + instanceID := string(instanceIDBytes) - klog.Infof("Using hostname from AWS metadata service: %s", domain) + config := aws.NewConfig() + config = config.WithCredentialsChainVerboseErrors(true) - return domain, nil + s, err := session.NewSession(config) + if err != nil { + return "", fmt.Errorf("error starting new AWS session: %v", err) + } + + svc := ec2.New(s, config.WithRegion(string(azBytes[:len(azBytes)-1]))) + + result, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{ + InstanceIds: []*string{&instanceID}, + }) + + if len(result.Reservations) != 1 { + return "", fmt.Errorf("Too many reservations returned for the single instance-id") + } + + if len(result.Reservations[0].Instances) != 1 { + return "", fmt.Errorf("Too many instances returned for the single instance-id") + } + return *(result.Reservations[0].Instances[0].PrivateDnsName), nil } // FindCert is a helper method to retrieving a certificate from the store diff --git a/upup/pkg/fi/nodeup/command.go b/upup/pkg/fi/nodeup/command.go index a5ebd516eb..e899750f20 100644 --- a/upup/pkg/fi/nodeup/command.go +++ b/upup/pkg/fi/nodeup/command.go @@ -441,25 +441,40 @@ func evaluateHostnameOverride(hostnameOverride string) (string, error) { k = strings.ToLower(k) if k == "@aws" { - // We recognize @aws as meaning "the local-hostname from the aws metadata service" - vBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/local-hostname") + // We recognize @aws as meaning "the private DNS name from AWS", to generate this we need to get a few pieces of information + azBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/placement/availability-zone") if err != nil { - return "", fmt.Errorf("error reading local hostname from AWS metadata: %v", err) + return "", fmt.Errorf("error reading availability zone from AWS metadata: %v", err) } - // The local-hostname gets it's hostname from the AWS DHCP Option Set, which - // may provide multiple hostnames separated by spaces. For now just choose - // the first one as the hostname. - domains := strings.Fields(string(vBytes)) - if len(domains) == 0 { - klog.Warningf("Local hostname from AWS metadata service was empty") - return "", nil + instanceIDBytes, err := vfs.Context.ReadFile("metadata://aws/meta-data/instance-id") + if err != nil { + return "", fmt.Errorf("error reading instance-id from AWS metadata: %v", err) + } + instanceID := string(instanceIDBytes) + + config := aws.NewConfig() + config = config.WithCredentialsChainVerboseErrors(true) + + s, err := session.NewSession(config) + if err != nil { + return "", fmt.Errorf("error starting new AWS session: %v", err) } - domain := domains[0] - klog.Infof("Using hostname from AWS metadata service: %s", domain) + svc := ec2.New(s, config.WithRegion(string(azBytes[:len(azBytes)-1]))) - return domain, nil + result, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{ + InstanceIds: []*string{&instanceID}, + }) + + if len(result.Reservations) != 1 { + return "", fmt.Errorf("Too many reservations returned for the single instance-id") + } + + if len(result.Reservations[0].Instances) != 1 { + return "", fmt.Errorf("Too many instances returned for the single instance-id") + } + return *(result.Reservations[0].Instances[0].PrivateDnsName), nil } if k == "@digitalocean" {