From c7ef856f9274defda72c8d32a460f143a8d16f39 Mon Sep 17 00:00:00 2001 From: chrislovecnm Date: Fri, 27 Oct 2017 12:09:17 -0600 Subject: [PATCH] Improving UX for kops validate cluster Before the `kops validate cluster` attempts to connect to the K8s API endpoint, the code now checks to see if the API DNS Entry is the kops placeholder IP Address 203.0.113.123. It prints a message to the user and err's. There is a new init func in validate cluster that disables CGO based DNS for Darwin OS. Darwin does two things with kops validates; it caches the IP address, and it does not return the placeholder IP address. We cannot use CGO base DNS with kops validate. --- cmd/kops/validate_cluster.go | 28 ++++++++++++++++++++++++++++ pkg/validation/BUILD.bazel | 1 + pkg/validation/validate_cluster.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/cmd/kops/validate_cluster.go b/cmd/kops/validate_cluster.go index a5e07d1d44715..f2d9b1414b6dd 100644 --- a/cmd/kops/validate_cluster.go +++ b/cmd/kops/validate_cluster.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "os" + "runtime" "strings" "github.com/golang/glog" @@ -31,10 +32,18 @@ import ( "k8s.io/kops/cmd/kops/util" api "k8s.io/kops/pkg/apis/kops" apiutil "k8s.io/kops/pkg/apis/kops/util" + "k8s.io/kops/pkg/dns" "k8s.io/kops/pkg/validation" "k8s.io/kops/util/pkg/tables" ) +func init() { + if runtime.GOOS == "darwin" { + // In order for net.LookupHost(apiAddr.Host) to lookup our placeholder address on darwin, we have to + os.Setenv("GODEBUG", "netdns=go") + } +} + type ValidateClusterOptions struct { // No options yet } @@ -105,6 +114,25 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out return fmt.Errorf("Cannot build kube api client for %q: %v\n", contextName, err) } + // Do not use if we are running gossip + if !dns.IsGossipHostname(cluster.ObjectMeta.Name) { + hasPlaceHolderIPAddress, err := validation.HasPlaceHolderIP(contextName) + if err != nil { + return err + } + + if hasPlaceHolderIPAddress { + fmt.Println( + "Validation Failed\n\n" + + "The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." + + " The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." + + " Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." + + " The protokube container and dns-controller deployment logs may contain more diagnostic information." + + " Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start.") + return fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name) + } + } + validationCluster, validationFailed := validation.ValidateCluster(cluster.ObjectMeta.Name, list, k8sClient) if validationCluster == nil || validationCluster.NodeList == nil || validationCluster.NodeList.Items == nil { diff --git a/pkg/validation/BUILD.bazel b/pkg/validation/BUILD.bazel index ab02e85f9d1c0..6d87b70731743 100644 --- a/pkg/validation/BUILD.bazel +++ b/pkg/validation/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", ], ) diff --git a/pkg/validation/validate_cluster.go b/pkg/validation/validate_cluster.go index 1760d4f895b00..536fc3be76374 100644 --- a/pkg/validation/validate_cluster.go +++ b/pkg/validation/validate_cluster.go @@ -18,11 +18,15 @@ package validation import ( "fmt" + "net/url" "time" + "net" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/util" "k8s.io/kops/upup/pkg/fi" @@ -54,6 +58,32 @@ type ValidationNode struct { Status v1.ConditionStatus `json:"status,omitempty"` } +// HasPlaceHolderIP checks if the API DNS has been updated +func HasPlaceHolderIP(clusterName string) (bool, error) { + + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &clientcmd.ConfigOverrides{CurrentContext: clusterName}).ClientConfig() + + apiAddr, err := url.Parse(config.Host) + if err != nil { + return true, fmt.Errorf("unable to parse Kubernetes cluster API URL: %v", err) + } + + hostAddrs, err := net.LookupHost(apiAddr.Host) + if err != nil { + return true, fmt.Errorf("unable to resolve Kubernetes cluster API URL dns: %v", err) + } + + for _, h := range hostAddrs { + if h == "203.0.113.123" { + return true, nil + } + } + + return false, nil +} + // ValidateCluster validate a k8s cluster with a provided instance group list func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupList, clusterKubernetesClient kubernetes.Interface) (*ValidationCluster, error) { var instanceGroups []*kops.InstanceGroup