Skip to content

Commit 11d25b6

Browse files
committed
Discard kubeconfig changes when proxy is present for Private Installs
Check if the API server is reachable during Private installs. If so, there is probably a proxy/bastion configured in the same network as the cluster. Dialer context can be removed from kubeconfig in that case.
1 parent bee547d commit 11d25b6

File tree

1 file changed

+113
-17
lines changed

1 file changed

+113
-17
lines changed

cmd/openshift-install/create.go

Lines changed: 113 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ type target struct {
5151
assets []asset.WritableAsset
5252
}
5353

54+
type customDNSInfo struct {
55+
platform string
56+
privateInstall bool
57+
lbIPAddr string
58+
}
59+
60+
var savedConfig *rest.Config
61+
5462
// each target is a variable to preserve the order when creating subcommands and still
5563
// allow other functions to directly access each target individually.
5664
var (
@@ -152,8 +160,15 @@ func clusterCreatePostRun(ctx context.Context) (int, error) {
152160
return 0, errors.Wrap(err, "loading kubeconfig")
153161
}
154162

163+
// Kubeconfig could get modified with the Dialer context. Save a copy of the original config
164+
// in case we need to revert back to it (e.g., when a proxy is configured for private installs).
165+
savedConfig = rest.CopyConfig(config)
166+
customDNSData := customDNSInfo{}
167+
if err := getCustomDNSInfo(ctx, &customDNSData); err != nil {
168+
logrus.Fatal(fmt.Errorf("unable to retrieve configuration for UserProvisionedDNS: %w", err))
169+
}
155170
// Handle the case when the API server is not reachable.
156-
if err := handleUnreachableAPIServer(ctx, config); err != nil {
171+
if err := handleUnreachableAPIServer(config, &customDNSData); err != nil {
157172
logrus.Fatal(fmt.Errorf("unable to handle api server override: %w", err))
158173
}
159174

@@ -222,6 +237,8 @@ func clusterCreatePostRun(ctx context.Context) (int, error) {
222237
}
223238
timer.StopTimer(timer.TotalTimeElapsed)
224239
timer.LogSummary()
240+
241+
postInstallSteps(&customDNSData)
225242
return 0, nil
226243
}
227244

@@ -336,13 +353,11 @@ func runTargetCmd(ctx context.Context, targets ...asset.WritableAsset) func(cmd
336353
}
337354

338355
func waitForBootstrapComplete(ctx context.Context, config *rest.Config) *clusterCreateError {
339-
client, err := kubernetes.NewForConfig(config)
356+
customDNSData := customDNSInfo{}
357+
err := getCustomDNSInfo(ctx, &customDNSData)
340358
if err != nil {
341-
return newClientError(errors.Wrap(err, "creating a Kubernetes client"))
359+
return newClientError(errors.Wrap(err, "unable to retrieve configuration for UserProvisionedDNS"))
342360
}
343-
344-
discovery := client.Discovery()
345-
346361
apiTimeout := 20 * time.Minute
347362

348363
untilTime := time.Now().Add(apiTimeout)
@@ -366,6 +381,16 @@ func waitForBootstrapComplete(ctx context.Context, config *rest.Config) *cluster
366381

367382
var lastErr error
368383
err = wait.PollUntilContextCancel(apiContext, 2*time.Second, true, func(_ context.Context) (done bool, err error) {
384+
385+
// Determine which config to use: the modified one (with custom dialer) or the original.
386+
// When UserProvisionedDNS is enabled and this is a private install, a proxy server
387+
// could be configured. In that case, kubeconfig does not need the custom dialer.
388+
activeConfig := getKubeconfig(&customDNSData, config)
389+
client, err := kubernetes.NewForConfig(activeConfig)
390+
if err != nil {
391+
return false, err
392+
}
393+
discovery := client.Discovery()
369394
version, err := discovery.ServerVersion()
370395
if err == nil {
371396
logrus.Infof("API %s up", version)
@@ -425,11 +450,16 @@ func waitForBootstrapComplete(ctx context.Context, config *rest.Config) *cluster
425450
logrus.Infof(" Baremetal control plane finished provisioning.")
426451
}
427452

453+
activeConfig := getKubeconfig(&customDNSData, config)
454+
client, err := kubernetes.NewForConfig(activeConfig)
455+
if err != nil {
456+
return newClientError(errors.Wrap(err, "creating a Kubernetes client"))
457+
}
428458
if err := waitForBootstrapConfigMap(waitCtx, client); err != nil {
429459
return err
430460
}
431461

432-
if err := waitForEtcdBootstrapMemberRemoval(ctx, config); err != nil {
462+
if err := waitForEtcdBootstrapMemberRemoval(ctx, activeConfig); err != nil {
433463
return newBootstrapError(err)
434464
}
435465

@@ -501,12 +531,15 @@ func waitForEtcdBootstrapMemberRemoval(ctx context.Context, config *rest.Config)
501531
return nil
502532
}
503533

504-
func handleUnreachableAPIServer(ctx context.Context, config *rest.Config) error {
534+
// getCustomDNSInfo gets LB IP address and other values needed specifically when UserProvisionedDNS
535+
// is configured from various assets in the asset store and populates customDNSInfo. That information
536+
// is useful to make updates to kubeconfig before and after install.
537+
func getCustomDNSInfo(ctx context.Context, customDNSData *customDNSInfo) error {
505538
assetStore, err := assetstore.NewStore(command.RootOpts.Dir)
506539
if err != nil {
507540
return fmt.Errorf("failed to create asset store: %w", err)
508541
}
509-
542+
customDNSData.platform = ""
510543
// Ensure that the install is expecting the user to provision their own DNS solution.
511544
installConfig := &installconfig.InstallConfig{}
512545
if err := assetStore.Fetch(ctx, installConfig); err != nil {
@@ -528,43 +561,106 @@ func handleUnreachableAPIServer(ctx context.Context, config *rest.Config) error
528561
default:
529562
return nil
530563
}
564+
customDNSData.platform = installConfig.Config.Platform.Name()
531565

532566
lbConfig := &lbconfig.Config{}
533567
if err := assetStore.Fetch(ctx, lbConfig); err != nil {
534568
return fmt.Errorf("failed to fetch %s: %w", lbConfig.Name(), err)
535569
}
536570

537571
lbType := lbconfig.PublicLoadBalancer
572+
customDNSData.privateInstall = false
538573
if !installConfig.Config.PublicAPI() {
539574
lbType = lbconfig.PrivateLoadBalancer
575+
customDNSData.privateInstall = true
540576
}
541577

542578
_, ipAddrs, err := lbConfig.ParseDNSDataFromConfig(lbType)
543579
if err != nil {
544580
return fmt.Errorf("failed to parse lbconfig: %w", err)
545581
}
546-
547-
// The kubeconfig handles one ip address
582+
// Grabbing just the 1st load balancer IP address
548583
ipAddr := ""
549584
if len(ipAddrs) > 0 {
550585
ipAddr = ipAddrs[0].String()
551586
}
552587
if ipAddr == "" {
553588
return fmt.Errorf("no ip address found in lbconfig")
554589
}
555-
556-
dialer := &net.Dialer{
557-
Timeout: 1 * time.Minute,
558-
KeepAlive: 1 * time.Minute,
559-
}
560-
config.Dial = kubeconfig.CreateDialContext(dialer, ipAddr)
590+
customDNSData.lbIPAddr = ipAddr
561591

562592
// The asset is currently saved in <install-dir>/openshift. This directory
563593
// was consumed during install but this file is generated after that action. This
564594
// artifact will hang around unless it is purged here.
565595
if err := asset.DeleteAssetFromDisk(lbConfig, command.RootOpts.Dir); err != nil {
566596
return fmt.Errorf("failed to delete %s from disk", lbConfig.Name())
567597
}
598+
return nil
599+
}
600+
601+
// handleUnreachableAPIServer updates kubeconfig with Dial context if needed.
602+
func handleUnreachableAPIServer(config *rest.Config, customDNSData *customDNSInfo) error {
603+
if customDNSData.platform == "" {
604+
// Platform doesn't support custom-dns or UserProvisioedDNS was not enabled in a
605+
// supported platform. Nothing to do.
606+
return nil
607+
}
608+
if customDNSData.lbIPAddr == "" {
609+
return fmt.Errorf("no load balancer ip address found to update kubeconfig")
610+
}
568611

612+
dialer := &net.Dialer{
613+
Timeout: 1 * time.Minute,
614+
KeepAlive: 1 * time.Minute,
615+
}
616+
config.Dial = kubeconfig.CreateDialContext(dialer, customDNSData.lbIPAddr)
569617
return nil
570618
}
619+
620+
// checkPrivateInstallReachability checks if install host is within the same network
621+
// as the private cluster.
622+
func checkPrivateInstallReachability(customDNSData *customDNSInfo) (bool, error) {
623+
reachableAPIServer := true
624+
if customDNSData.platform == "" {
625+
// Platform doesn't support custom-dns or UserProvisionedDNS was not enabled in a
626+
// supported platform. Nothing to do.
627+
return true, nil
628+
}
629+
if customDNSData.privateInstall {
630+
if customDNSData.lbIPAddr == "" {
631+
return true, fmt.Errorf("no load balancer ip address found to check connectivity")
632+
}
633+
logrus.Debugf("checking if API server reachable with updated kubeconfig.")
634+
// Check if private LB IP and port are reachable from the Install host.
635+
address := net.JoinHostPort(customDNSData.lbIPAddr, "6443")
636+
conn, err := net.DialTimeout("tcp", address, time.Second*10)
637+
if err != nil {
638+
// Not reachable. So, the install host is not in the same network.
639+
// Use kubeconfig without dialer context because some reachability
640+
// mechanism should be put in place at this time.
641+
logrus.Debugf("unable to reach API server with kubeconfig. Reverting to using original kubeconfig.")
642+
reachableAPIServer = false
643+
} else {
644+
conn.Close()
645+
}
646+
}
647+
return reachableAPIServer, nil
648+
}
649+
650+
func postInstallSteps(customDNSData *customDNSInfo) {
651+
if customDNSData.platform != "" {
652+
// Platform with ProvisionedDNS enabled. Display post-install message.
653+
logrus.Infof("This cluster is configured with UserProvisionedDNS. To access the cluster, please configure your External DNS solution with entries for the cluster's API and *.apps service endpoints. Please refer to documentation for further details")
654+
}
655+
}
656+
657+
func getKubeconfig(customDNSData *customDNSInfo, config *rest.Config) *rest.Config {
658+
activeConfig := config
659+
reachable, reachErr := checkPrivateInstallReachability(customDNSData)
660+
if reachErr == nil && !reachable && savedConfig != nil {
661+
// Revert to saved kubeconfig without Dialer context.
662+
logrus.Debug("Private install not directly reachable, using original kubeconfig configuration")
663+
activeConfig = savedConfig
664+
}
665+
return activeConfig
666+
}

0 commit comments

Comments
 (0)