16561: Add ListenAddress, explain InternalURLs/ExternalURL better.
authorTom Clegg <tom@curii.com>
Tue, 13 Jul 2021 20:16:36 +0000 (16:16 -0400)
committerTom Clegg <tom@curii.com>
Tue, 21 Jun 2022 13:50:59 +0000 (09:50 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

lib/config/config.default.yml
lib/config/export.go
lib/service/cmd.go
sdk/go/arvados/config.go

index a9bbf4eee9b5002e733cb46df5ffe9be995ffdcf..c321434cb137761153942a745c7260c61b7c6fa5 100644 (file)
@@ -22,34 +22,67 @@ Clusters:
 
     Services:
 
-      # In each of the service sections below, the keys under
-      # InternalURLs are the endpoints where the service should be
-      # listening, and reachable from other hosts in the
-      # cluster. Example:
+      # Each of the service sections below specifies ListenAddress,
+      # InternalURLs, and ExternalURL.
+      #
+      # InternalURLs specify how other Arvados service processes will
+      # connect to the service. Typically these use internal hostnames
+      # and high port numbers. Example:
       #
       # InternalURLs:
-      #   "http://host1.example:12345": {}
-      #   "http://host2.example:12345": {}
+      #   "http://host1.internal.example:12345": {}
+      #   "http://host2.internal.example:12345": {}
+      #
+      # ListenAddress specifies the address and port the service
+      # process's HTTP server should listen on. Example:
+      #
+      # ListenAddress: "0.0.0.0:12345"
+      #
+      # If ListenAddress is blank, the service will try listening on
+      # the host:port part of each InternalURLs entry until one
+      # works. This approach only works if the host names resolve (via
+      # /etc/hosts, DNS, etc) to the IP addresses of the host's
+      # network interfaces.
+      #
+      # ExternalURL specifies how applications/clients will connect to
+      # the service, regardless of whether they are inside or outside
+      # the cluster. Example:
+      #
+      # ExternalURL: "https://keep.zzzzz.example.com/"
+      #
+      # To avoid routing internal traffic through external networks,
+      # use split-horizon DNS for ExternalURL host names: inside the
+      # cluster's private network "host.zzzzz.example.com" resolves to
+      # the host's private IP address, while outside the cluster
+      # "host.zzzzz.example.com" resolves to the host's public IP
+      # address (or its external gateway or load balancer).
 
       RailsAPI:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Controller:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Websocket:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Keepbalance:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       GitHTTP:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       GitSSH:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       DispatchCloud:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       DispatchLSF:
@@ -59,9 +92,11 @@ Clusters:
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Keepproxy:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       WebDAV:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         # Base URL for Workbench inline preview.  If blank, use
         # WebDAVDownload instead, and disable inline preview.
@@ -101,6 +136,7 @@ Clusters:
         ExternalURL: ""
 
       WebDAVDownload:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         # Base URL for download links. If blank, serve links to WebDAV
         # with disposition=attachment query param.  Unlike preview links,
@@ -115,6 +151,7 @@ Clusters:
         ExternalURL: ""
 
       Keepstore:
+        ListenAddress: ""
         InternalURLs:
           SAMPLE:
             # Rendezvous is normally empty/omitted. When changing the
@@ -124,9 +161,11 @@ Clusters:
             Rendezvous: ""
         ExternalURL: ""
       Composer:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       WebShell:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         # ShellInABox service endpoint URL for a given VM.  If empty, do not
         # offer web shell logins.
@@ -138,12 +177,15 @@ Clusters:
         # https://*.webshell.uuid_prefix.arvadosapi.com
         ExternalURL: ""
       Workbench1:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Workbench2:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
       Health:
+        ListenAddress: ""
         InternalURLs: {SAMPLE: {}}
         ExternalURL: ""
 
index a55295d1268b7cc0829ec2fe5073eb276c5b4564..56090afec9fa06a659d703481241c227d2f2b3cc 100644 (file)
@@ -214,6 +214,7 @@ var whitelist = map[string]bool{
        "Services.*":                                          true,
        "Services.*.ExternalURL":                              true,
        "Services.*.InternalURLs":                             false,
+       "Services.*.ListenAddress":                            false,
        "StorageClasses":                                      true,
        "StorageClasses.*":                                    true,
        "StorageClasses.*.Default":                            true,
index 679cbede13bc8cf141a34ec40373a458c5195451..a9d5731eacb149f7d9a19d5d5bb9c9354d7b26de 100644 (file)
@@ -121,11 +121,11 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        })
        ctx := ctxlog.Context(c.ctx, logger)
 
-       listenURL, err := getListenAddr(cluster.Services, c.svcName, log)
+       listenURL, internalURL, err := getListenAddr(cluster.Services, c.svcName, log)
        if err != nil {
                return 1
        }
-       ctx = context.WithValue(ctx, contextKeyURL{}, listenURL)
+       ctx = context.WithValue(ctx, contextKeyURL{}, internalURL)
 
        reg := prometheus.NewRegistry()
        loader.RegisterMetrics(reg)
@@ -157,7 +157,7 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
                },
                Addr: listenURL.Host,
        }
-       if listenURL.Scheme == "https" {
+       if listenURL.Scheme == "https" || listenURL.Scheme == "wss" {
                tlsconfig, err := tlsConfigWithCertUpdater(cluster, logger)
                if err != nil {
                        logger.WithError(err).Errorf("cannot start %s service on %s", c.svcName, listenURL.String())
@@ -223,28 +223,41 @@ func interceptHealthReqs(mgtToken string, checkHealth func() error, next http.Ha
        return ifCollectionInHost(next, mux)
 }
 
-func getListenAddr(svcs arvados.Services, prog arvados.ServiceName, log logrus.FieldLogger) (arvados.URL, error) {
+func getListenAddr(svcs arvados.Services, prog arvados.ServiceName, log logrus.FieldLogger) (arvados.URL, arvados.URL, error) {
        svc, ok := svcs.Map()[prog]
        if !ok {
-               return arvados.URL{}, fmt.Errorf("unknown service name %q", prog)
+               return arvados.URL{}, arvados.URL{}, fmt.Errorf("unknown service name %q", prog)
        }
 
        if want := os.Getenv("ARVADOS_SERVICE_INTERNAL_URL"); want == "" {
        } else if url, err := url.Parse(want); err != nil {
-               return arvados.URL{}, fmt.Errorf("$ARVADOS_SERVICE_INTERNAL_URL (%q): %s", want, err)
+               return arvados.URL{}, arvados.URL{}, fmt.Errorf("$ARVADOS_SERVICE_INTERNAL_URL (%q): %s", want, err)
        } else {
                if url.Path == "" {
                        url.Path = "/"
                }
-               return arvados.URL(*url), nil
+               internalURL := arvados.URL(*url)
+               listenURL := arvados.URL(*url)
+               if svc.ListenAddress != "" {
+                       listenURL.Host = svc.ListenAddress
+               }
+               return listenURL, internalURL, nil
+       }
+
+       if svc.ListenAddress != "" {
+               for internalURL := range svc.InternalURLs {
+                       listenURL := internalURL
+                       listenURL.Host = svc.ListenAddress
+                       return listenURL, internalURL, nil
+               }
        }
 
        errors := []string{}
-       for url := range svc.InternalURLs {
-               listener, err := net.Listen("tcp", url.Host)
+       for internalURL := range svc.InternalURLs {
+               listener, err := net.Listen("tcp", internalURL.Host)
                if err == nil {
                        listener.Close()
-                       return url, nil
+                       return internalURL, internalURL, nil
                } else if strings.Contains(err.Error(), "cannot assign requested address") {
                        // If 'Host' specifies a different server than
                        // the current one, it'll resolve the hostname
@@ -252,13 +265,13 @@ func getListenAddr(svcs arvados.Services, prog arvados.ServiceName, log logrus.F
                        // can't bind an IP address it doesn't own.
                        continue
                } else {
-                       errors = append(errors, fmt.Sprintf("tried %v, got %v", url, err))
+                       errors = append(errors, fmt.Sprintf("tried %v, got %v", internalURL, err))
                }
        }
        if len(errors) > 0 {
-               return arvados.URL{}, fmt.Errorf("could not enable the %q service on this host: %s", prog, strings.Join(errors, "; "))
+               return arvados.URL{}, arvados.URL{}, fmt.Errorf("could not enable the %q service on this host: %s", prog, strings.Join(errors, "; "))
        }
-       return arvados.URL{}, fmt.Errorf("configuration does not enable the %q service on this host", prog)
+       return arvados.URL{}, arvados.URL{}, fmt.Errorf("configuration does not enable the %q service on this host", prog)
 }
 
 type contextKeyURL struct{}
index 0d8f293124976cb42f9d009da1b16c5057ba1071..ad663b23ef0cc8e1c83a994c2646b3df6116833a 100644 (file)
@@ -366,8 +366,9 @@ type Services struct {
 }
 
 type Service struct {
-       InternalURLs map[URL]ServiceInstance
-       ExternalURL  URL
+       ListenAddress string
+       InternalURLs  map[URL]ServiceInstance
+       ExternalURL   URL
 }
 
 type TestUser struct {