You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by da...@apache.org on 2017/05/24 23:26:44 UTC

kudu git commit: KUDU-1988: add support for advertised host:port info.

Repository: kudu
Updated Branches:
  refs/heads/master e2097c8b9 -> 12683435f


KUDU-1988: add support for advertised host:port info.

Prior to this change, it was difficult to deploy a Kudu cluster in
containerized environments where each container has a unique, local
subnetwork with an externally facing translation layer. In these
scenarios the network address that the Kudu server binds to is not the
externally-routable IP address. To overcome this, a set of new flags are
introduced, '--rpc-advertised-addresses' and
'--webserver-advertised-addresses' which allow administrators to
override the set of addresses which the Kudu server gives to external
servers and clients. Similar configurations are present in other
distributed systems, for example, Kafka's advertised.host.name property.

Change-Id: I6735ca5630fc4c426bf72d0b21d6ef452173a890
Reviewed-on: http://gerrit.cloudera.org:8080/6827
Reviewed-by: Adar Dembo <ad...@cloudera.com>
Tested-by: Kudu Jenkins


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/12683435
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/12683435
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/12683435

Branch: refs/heads/master
Commit: 12683435f0e578957ee18c4e141e376532a8a927
Parents: e2097c8
Author: Patrik Sundberg <pa...@gmail.com>
Authored: Tue May 9 15:30:06 2017 +0100
Committer: Dan Burkert <da...@apache.org>
Committed: Wed May 24 23:26:27 2017 +0000

----------------------------------------------------------------------
 src/kudu/master/master.cc            |   4 +-
 src/kudu/server/CMakeLists.txt       |   1 +
 src/kudu/server/rpc_server-test.cc   | 129 ++++++++++++++++++++++++++++++
 src/kudu/server/rpc_server.cc        |  32 ++++++++
 src/kudu/server/rpc_server.h         |  17 +++-
 src/kudu/server/webserver-test.cc    | 110 ++++++++++++++++++++++++-
 src/kudu/server/webserver.cc         |  26 +++++-
 src/kudu/server/webserver.h          |  12 ++-
 src/kudu/server/webserver_options.cc |  10 +++
 src/kudu/server/webserver_options.h  |   1 +
 src/kudu/tserver/heartbeater.cc      |   4 +-
 11 files changed, 333 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/master/master.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/master.cc b/src/kudu/master/master.cc
index 1d1d78a..079bd2d 100644
--- a/src/kudu/master/master.cc
+++ b/src/kudu/master/master.cc
@@ -232,13 +232,13 @@ Status Master::InitMasterRegistration() {
 
   ServerRegistrationPB reg;
   vector<Sockaddr> rpc_addrs;
-  RETURN_NOT_OK_PREPEND(rpc_server()->GetBoundAddresses(&rpc_addrs),
+  RETURN_NOT_OK_PREPEND(rpc_server()->GetAdvertisedAddresses(&rpc_addrs),
                         "Couldn't get RPC addresses");
   RETURN_NOT_OK(AddHostPortPBs(rpc_addrs, reg.mutable_rpc_addresses()));
 
   if (web_server()) {
     vector<Sockaddr> http_addrs;
-    web_server()->GetBoundAddresses(&http_addrs);
+    RETURN_NOT_OK(web_server()->GetAdvertisedAddresses(&http_addrs));
     RETURN_NOT_OK(AddHostPortPBs(http_addrs, reg.mutable_http_addresses()));
     reg.set_https_enabled(web_server()->IsSecure());
   }

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/server/CMakeLists.txt b/src/kudu/server/CMakeLists.txt
index 4d7bcf2..39913f3 100644
--- a/src/kudu/server/CMakeLists.txt
+++ b/src/kudu/server/CMakeLists.txt
@@ -106,4 +106,5 @@ set(KUDU_TEST_LINK_LIBS  ${KUDU_MIN_TEST_LIBS}
   kudu_curl_util
   server_process
   security-test)
+ADD_KUDU_TEST(rpc_server-test)
 ADD_KUDU_TEST(webserver-test)

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/rpc_server-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/rpc_server-test.cc b/src/kudu/server/rpc_server-test.cc
new file mode 100644
index 0000000..bf97c72
--- /dev/null
+++ b/src/kudu/server/rpc_server-test.cc
@@ -0,0 +1,129 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <string>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "kudu/rpc/messenger.h"
+#include "kudu/server/rpc_server.h"
+#include "kudu/util/net/sockaddr.h"
+#include "kudu/util/test_util.h"
+
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+
+DECLARE_bool(rpc_server_allow_ephemeral_ports);
+
+namespace kudu {
+
+class RpcServerAdvertisedAddressesTest : public KuduTest {
+ public:
+
+  void SetUp() override {
+    KuduTest::SetUp();
+    FLAGS_rpc_server_allow_ephemeral_ports = true;
+
+    RpcServerOptions opts;
+    string bind = use_bind_addresses();
+    if (!bind.empty()) {
+      opts.rpc_bind_addresses = bind;
+    } else {
+      opts.rpc_bind_addresses = "127.0.0.1";
+    }
+    string advertised = use_advertised_addresses();
+    if (!advertised.empty()) {
+      opts.rpc_advertised_addresses = advertised;
+    }
+    server_.reset(new RpcServer(opts));
+    unique_ptr<MetricRegistry> metric_registry(new MetricRegistry());
+    scoped_refptr<MetricEntity> metric_entity =
+        METRIC_ENTITY_server.Instantiate(metric_registry.get(), "test");
+    rpc::MessengerBuilder builder("test");
+    shared_ptr<rpc::Messenger> messenger;
+    builder.set_metric_entity(metric_entity);
+    ASSERT_OK(builder.Build(&messenger));
+    ASSERT_OK(server_->Init(messenger));
+    ASSERT_OK(server_->Bind());
+  }
+
+ protected:
+  // Overridden by subclasses.
+  virtual string use_bind_addresses() const { return ""; }
+  virtual string use_advertised_addresses() const { return ""; }
+
+  void GetAddresses(vector<Sockaddr>* bound_addrs,
+                    vector<Sockaddr>* advertised_addrs) {
+    ASSERT_OK(server_->GetBoundAddresses(bound_addrs));
+    ASSERT_OK(server_->GetAdvertisedAddresses(advertised_addrs));
+  }
+
+  unique_ptr<RpcServer> server_;
+};
+
+class AdvertisedOnlyWebserverTest : public RpcServerAdvertisedAddressesTest {
+ protected:
+  string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
+};
+
+class BoundOnlyWebserverTest : public RpcServerAdvertisedAddressesTest {
+ protected:
+  string use_bind_addresses() const override { return "127.0.0.1"; }
+};
+
+class BothBoundAndAdvertisedWebserverTest : public RpcServerAdvertisedAddressesTest {
+ protected:
+  string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
+  string use_bind_addresses() const override { return "127.0.0.1"; }
+};
+
+TEST_F(AdvertisedOnlyWebserverTest, OnlyAdvertisedAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+  ASSERT_EQ(1234, advertised_addrs[0].port());
+}
+
+TEST_F(BoundOnlyWebserverTest, OnlyBoundAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("127.0.0.1", advertised_addrs[0].host());
+  ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+  ASSERT_EQ(advertised_addrs[0].port(), bound_addrs[0].port());
+}
+
+TEST_F(BothBoundAndAdvertisedWebserverTest, BothBoundAndAdvertisedAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+  ASSERT_EQ(1234, advertised_addrs[0].port());
+  ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+}
+
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/rpc_server.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/rpc_server.cc b/src/kudu/server/rpc_server.cc
index 25c497c..1f8c542 100644
--- a/src/kudu/server/rpc_server.cc
+++ b/src/kudu/server/rpc_server.cc
@@ -47,6 +47,14 @@ DEFINE_string(rpc_bind_addresses, "0.0.0.0",
               "Currently, ephemeral ports (i.e. port 0) are not allowed.");
 TAG_FLAG(rpc_bind_addresses, stable);
 
+DEFINE_string(rpc_advertised_addresses, "",
+              "Comma-separated list of addresses to advertise externally for RPC "
+              "connections. Ephemeral ports (i.e. port 0) are not allowed. This "
+              "should be configured when the locally bound RPC addresses "
+              "specified in --rpc_bind_addresses are not externally resolvable, "
+              "for example, if Kudu is deployed in a container.");
+TAG_FLAG(rpc_advertised_addresses, advanced);
+
 DEFINE_int32(rpc_num_acceptors_per_address, 1,
              "Number of RPC acceptor threads for each bound address");
 TAG_FLAG(rpc_num_acceptors_per_address, advanced);
@@ -68,6 +76,7 @@ namespace kudu {
 
 RpcServerOptions::RpcServerOptions()
   : rpc_bind_addresses(FLAGS_rpc_bind_addresses),
+    rpc_advertised_addresses(FLAGS_rpc_advertised_addresses),
     num_acceptors_per_address(FLAGS_rpc_num_acceptors_per_address),
     num_service_threads(FLAGS_rpc_num_service_threads),
     default_port(0),
@@ -108,6 +117,19 @@ Status RpcServer::Init(const shared_ptr<Messenger>& messenger) {
     }
   }
 
+  if (!options_.rpc_advertised_addresses.empty()) {
+    RETURN_NOT_OK(ParseAddressList(options_.rpc_advertised_addresses,
+                                   options_.default_port,
+                                   &rpc_advertised_addresses_));
+
+    for (const Sockaddr& addr : rpc_advertised_addresses_) {
+      if (addr.port() == 0) {
+        LOG(FATAL) << "Advertising an ephemeral port is not supported (RPC advertised address "
+                   << "configured to " << addr.ToString() << ")";
+      }
+    }
+  }
+
   server_state_ = INITIALIZED;
   return Status::OK();
 }
@@ -189,6 +211,16 @@ Status RpcServer::GetBoundAddresses(vector<Sockaddr>* addresses) const {
   return Status::OK();
 }
 
+Status RpcServer::GetAdvertisedAddresses(vector<Sockaddr>* addresses) const {
+  CHECK(server_state_ == BOUND ||
+        server_state_ == STARTED) << "bad state: " << server_state_;
+  if (rpc_advertised_addresses_.empty()) {
+    return GetBoundAddresses(addresses);
+  }
+  *addresses = rpc_advertised_addresses_;
+  return Status::OK();
+}
+
 const rpc::ServicePool* RpcServer::service_pool(const string& service_name) const {
   return down_cast<rpc::ServicePool*>(messenger_->rpc_service(service_name).get());
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/rpc_server.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/rpc_server.h b/src/kudu/server/rpc_server.h
index 1d201b1..8a65825 100644
--- a/src/kudu/server/rpc_server.h
+++ b/src/kudu/server/rpc_server.h
@@ -39,6 +39,7 @@ struct RpcServerOptions {
   RpcServerOptions();
 
   std::string rpc_bind_addresses;
+  std::string rpc_advertised_addresses;
   uint32_t num_acceptors_per_address;
   uint32_t num_service_threads;
   uint16_t default_port;
@@ -50,12 +51,12 @@ class RpcServer {
   explicit RpcServer(RpcServerOptions opts);
   ~RpcServer();
 
-  Status Init(const std::shared_ptr<rpc::Messenger>& messenger);
+  Status Init(const std::shared_ptr<rpc::Messenger>& messenger) WARN_UNUSED_RESULT;
   // Services need to be registered after Init'ing, but before Start'ing.
   // The service's ownership will be given to a ServicePool.
-  Status RegisterService(gscoped_ptr<rpc::ServiceIf> service);
-  Status Bind();
-  Status Start();
+  Status RegisterService(gscoped_ptr<rpc::ServiceIf> service) WARN_UNUSED_RESULT;
+  Status Bind() WARN_UNUSED_RESULT;
+  Status Start() WARN_UNUSED_RESULT;
   void Shutdown();
 
   std::string ToString() const;
@@ -64,6 +65,10 @@ class RpcServer {
   // bound to. Requires that the server has been Start()ed.
   Status GetBoundAddresses(std::vector<Sockaddr>* addresses) const WARN_UNUSED_RESULT;
 
+  // Return the addresses that this server is advertising externally
+  // to the world. Requires that the server has been Start()ed.
+  Status GetAdvertisedAddresses(std::vector<Sockaddr>* addresses) const WARN_UNUSED_RESULT;
+
   const rpc::ServicePool* service_pool(const std::string& service_name) const;
 
  private:
@@ -85,6 +90,10 @@ class RpcServer {
   // Parsed addresses to bind RPC to. Set by Init()
   std::vector<Sockaddr> rpc_bind_addresses_;
 
+  // Parsed addresses to advertise. Set by Init(). Empty if rpc_bind_addresses_
+  // should be advertised.
+  std::vector<Sockaddr> rpc_advertised_addresses_;
+
   std::vector<std::shared_ptr<rpc::AcceptorPool> > acceptor_pools_;
 
   DISALLOW_COPY_AND_ASSIGN(RpcServer);

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/webserver-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver-test.cc b/src/kudu/server/webserver-test.cc
index a93fe9c..8a2c541 100644
--- a/src/kudu/server/webserver-test.cc
+++ b/src/kudu/server/webserver-test.cc
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <memory>
 #include <string>
 
 #include <gflags/gflags.h>
@@ -34,6 +35,7 @@
 #include "kudu/util/test_util.h"
 
 using std::string;
+using std::unique_ptr;
 
 DECLARE_int32(webserver_max_post_length_bytes);
 
@@ -65,7 +67,7 @@ class WebserverTest : public KuduTest {
     CHECK_OK(env_->CreateDir(static_dir_));
   }
 
-  virtual void SetUp() OVERRIDE {
+  void SetUp() override {
     KuduTest::SetUp();
 
     WebserverOptions opts;
@@ -91,7 +93,7 @@ class WebserverTest : public KuduTest {
 
   EasyCurl curl_;
   faststring buf_;
-  gscoped_ptr<Webserver> server_;
+  unique_ptr<Webserver> server_;
   Sockaddr addr_;
 
   string static_dir_;
@@ -235,6 +237,98 @@ TEST_F(WebserverTest, TestStaticFiles) {
   ASSERT_EQ("Remote error: HTTP 403", s.ToString());
 }
 
+class WebserverAdvertisedAddressesTest : public KuduTest {
+ public:
+  void SetUp() override {
+    KuduTest::SetUp();
+
+    WebserverOptions opts;
+    opts.port = 0;
+    string iface = use_webserver_interface();
+    int32 port = use_webserver_port();
+    string advertised = use_advertised_addresses();
+    if (!iface.empty()) {
+      opts.bind_interface = iface;
+    }
+    if (port != 0) {
+      opts.port = port;
+    }
+    if (!advertised.empty()) {
+      opts.webserver_advertised_addresses = advertised;
+    }
+    server_.reset(new Webserver(opts));
+
+    ASSERT_OK(server_->Start());
+  }
+
+ protected:
+  // Overridden by subclasses.
+  virtual string use_webserver_interface() const { return ""; }
+  virtual int32 use_webserver_port() const { return 0; }
+  virtual string use_advertised_addresses() const { return ""; }
+
+  void GetAddresses(vector<Sockaddr>* bound_addrs,
+                    vector<Sockaddr>* advertised_addrs) {
+    ASSERT_OK(server_->GetBoundAddresses(bound_addrs));
+    ASSERT_OK(server_->GetAdvertisedAddresses(advertised_addrs));
+  }
+
+  unique_ptr<Webserver> server_;
+};
+
+class AdvertisedOnlyWebserverTest : public WebserverAdvertisedAddressesTest {
+ protected:
+  string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
+};
+
+class BoundOnlyWebserverTest : public WebserverAdvertisedAddressesTest {
+ protected:
+  string use_webserver_interface() const override { return "127.0.0.1"; }
+  int32 use_webserver_port() const override { return 9999; }
+};
+
+class BothBoundAndAdvertisedWebserverTest : public WebserverAdvertisedAddressesTest {
+ protected:
+  string use_advertised_addresses() const override { return "1.2.3.4:1234"; }
+  string use_webserver_interface() const override { return "127.0.0.1"; }
+  int32 use_webserver_port() const override { return 9999; }
+};
+
+TEST_F(AdvertisedOnlyWebserverTest, OnlyAdvertisedAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+  ASSERT_EQ(1234, advertised_addrs[0].port());
+  ASSERT_NE(9999, bound_addrs[0].port());
+}
+
+TEST_F(BoundOnlyWebserverTest, OnlyBoundAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("127.0.0.1", advertised_addrs[0].host());
+  ASSERT_EQ(9999, advertised_addrs[0].port());
+  ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+  ASSERT_EQ(9999, bound_addrs[0].port());
+}
+
+TEST_F(BothBoundAndAdvertisedWebserverTest, BothBoundAndAdvertisedAddresses) {
+  vector<Sockaddr> bound_addrs, advertised_addrs;
+  NO_FATALS(GetAddresses(&bound_addrs, &advertised_addrs));
+
+  ASSERT_EQ(1, advertised_addrs.size());
+  ASSERT_EQ(1, bound_addrs.size());
+  ASSERT_EQ("1.2.3.4", advertised_addrs[0].host());
+  ASSERT_EQ(1234, advertised_addrs[0].port());
+  ASSERT_EQ("127.0.0.1", bound_addrs[0].host());
+  ASSERT_EQ(9999, bound_addrs[0].port());
+}
+
 // Various tests for failed webserver startup cases.
 class WebserverNegativeTests : public KuduTest {
  protected:
@@ -280,4 +374,16 @@ TEST_F(WebserverNegativeTests, BadPasswordCommand) {
     });
 }
 
+TEST_F(WebserverNegativeTests, BadAdvertisedAddresses) {
+  ExpectFailedStartup([this](WebserverOptions* opts) {
+      opts->webserver_advertised_addresses = ";;;;;";
+    });
+}
+
+TEST_F(WebserverNegativeTests, BadAdvertisedAddressesZeroPort) {
+  ExpectFailedStartup([this](WebserverOptions* opts) {
+      opts->webserver_advertised_addresses = "localhost:0";
+    });
+}
+
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/webserver.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver.cc b/src/kudu/server/webserver.cc
index 7d7619c..67fe64e 100644
--- a/src/kudu/server/webserver.cc
+++ b/src/kudu/server/webserver.cc
@@ -210,6 +210,19 @@ Status Webserver::Start() {
   RETURN_NOT_OK(BuildListenSpec(&listening_str));
   options.push_back(listening_str);
 
+  // initialize the advertised addresses
+  if (!opts_.webserver_advertised_addresses.empty()) {
+    RETURN_NOT_OK(ParseAddressList(opts_.webserver_advertised_addresses,
+                                   opts_.port,
+                                   &webserver_advertised_addresses_));
+    for (const Sockaddr& addr : webserver_advertised_addresses_) {
+      if (addr.port() == 0) {
+        return Status::InvalidArgument("advertising an ephemeral webserver port is not supported",
+                                       addr.ToString());
+      }
+    }
+  }
+
   // Num threads
   options.push_back("num_threads");
   options.push_back(std::to_string(opts_.num_worker_threads));
@@ -301,7 +314,18 @@ Status Webserver::GetBoundAddresses(std::vector<Sockaddr>* addrs) const {
   return Status::OK();
 }
 
-int Webserver::LogMessageCallbackStatic(const struct sq_connection* connection,
+Status Webserver::GetAdvertisedAddresses(vector<Sockaddr>* addresses) const {
+  if (!context_) {
+    return Status::IllegalState("Not started");
+  }
+  if (webserver_advertised_addresses_.empty()) {
+    return GetBoundAddresses(addresses);
+  }
+  *addresses = webserver_advertised_addresses_;
+  return Status::OK();
+}
+
+int Webserver::LogMessageCallbackStatic(const struct sq_connection* /*connection*/,
                                         const char* message) {
   if (message != nullptr) {
     // Using the ERROR severity for squeasel messages: as per source code at

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/webserver.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver.h b/src/kudu/server/webserver.h
index aea89ad..ff5dd49 100644
--- a/src/kudu/server/webserver.h
+++ b/src/kudu/server/webserver.h
@@ -53,7 +53,11 @@ class Webserver : public WebCallbackRegistry {
 
   // Return the addresses that this server has successfully
   // bound to. Requires that the server has been Start()ed.
-  Status GetBoundAddresses(std::vector<Sockaddr>* addrs) const;
+  Status GetBoundAddresses(std::vector<Sockaddr>* addrs) const WARN_UNUSED_RESULT;
+
+  // Return the addresses that this server is advertising externally
+  // to the world. Requires that the server has been Start()ed.
+  Status GetAdvertisedAddresses(std::vector<Sockaddr>* addresses) const WARN_UNUSED_RESULT;
 
   virtual void RegisterPathHandler(const std::string& path, const std::string& alias,
                                    const PathHandlerCallback& callback,
@@ -99,7 +103,7 @@ class Webserver : public WebCallbackRegistry {
   bool static_pages_available() const;
 
   // Build the string to pass to mongoose specifying where to bind.
-  Status BuildListenSpec(std::string* spec) const;
+  Status BuildListenSpec(std::string* spec) const WARN_UNUSED_RESULT;
 
   // Renders a common Bootstrap-styled header
   void BootstrapPageHeader(std::ostringstream* output);
@@ -148,6 +152,10 @@ class Webserver : public WebCallbackRegistry {
   // The address of the interface on which to run this webserver.
   std::string http_address_;
 
+  // Parsed addresses to advertise. Set by Start(). Empty if the bind addresses
+  // should be advertised.
+  std::vector<Sockaddr> webserver_advertised_addresses_;
+
   // Handle to Mongoose context; owned and freed by Mongoose internally
   struct sq_context* context_;
 };

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/webserver_options.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver_options.cc b/src/kudu/server/webserver_options.cc
index 860ecd3..c136e53 100644
--- a/src/kudu/server/webserver_options.cc
+++ b/src/kudu/server/webserver_options.cc
@@ -43,6 +43,15 @@ DEFINE_string(webserver_interface, "",
     "Interface to start debug webserver on. If blank, webserver binds to 0.0.0.0");
 TAG_FLAG(webserver_interface, advanced);
 
+DEFINE_string(webserver_advertised_addresses, "",
+              "Comma-separated list of addresses to advertise externally for "
+              "HTTP(S) connections. Ephemeral ports (i.e. port 0) are not "
+              "allowed. This should be configured when the locally bound "
+              "webserver address specified in --webserver_interface and "
+              "--webserver_port are not externally resolvable, for example, if "
+              "Kudu is deployed in a container.");
+TAG_FLAG(webserver_advertised_addresses, advanced);
+
 DEFINE_string(webserver_doc_root, kudu::GetDefaultDocumentRoot(),
     "Files under <webserver_doc_root> are accessible via the debug webserver. "
     "Defaults to $KUDU_HOME/www, or if $KUDU_HOME is not set, disables the document "
@@ -116,6 +125,7 @@ static string GetDefaultDocumentRoot() {
 
 WebserverOptions::WebserverOptions()
   : bind_interface(FLAGS_webserver_interface),
+    webserver_advertised_addresses(FLAGS_webserver_advertised_addresses),
     port(FLAGS_webserver_port),
     doc_root(FLAGS_webserver_doc_root),
     enable_doc_root(FLAGS_webserver_enable_doc_root),

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/server/webserver_options.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver_options.h b/src/kudu/server/webserver_options.h
index d192637..1d1abb1 100644
--- a/src/kudu/server/webserver_options.h
+++ b/src/kudu/server/webserver_options.h
@@ -29,6 +29,7 @@ struct WebserverOptions {
   WebserverOptions();
 
   std::string bind_interface;
+  std::string webserver_advertised_addresses;
   uint16_t port;
   std::string doc_root;
   bool enable_doc_root;

http://git-wip-us.apache.org/repos/asf/kudu/blob/12683435/src/kudu/tserver/heartbeater.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/heartbeater.cc b/src/kudu/tserver/heartbeater.cc
index bfbd246..b64d053 100644
--- a/src/kudu/tserver/heartbeater.cc
+++ b/src/kudu/tserver/heartbeater.cc
@@ -301,13 +301,13 @@ Status Heartbeater::Thread::SetupRegistration(ServerRegistrationPB* reg) {
   reg->Clear();
 
   vector<Sockaddr> addrs;
-  RETURN_NOT_OK(CHECK_NOTNULL(server_->rpc_server())->GetBoundAddresses(&addrs));
+  RETURN_NOT_OK(CHECK_NOTNULL(server_->rpc_server())->GetAdvertisedAddresses(&addrs));
   RETURN_NOT_OK_PREPEND(AddHostPortPBs(addrs, reg->mutable_rpc_addresses()),
                         "Failed to add RPC addresses to registration");
 
   addrs.clear();
   if (server_->web_server()) {
-    RETURN_NOT_OK_PREPEND(server_->web_server()->GetBoundAddresses(&addrs),
+    RETURN_NOT_OK_PREPEND(server_->web_server()->GetAdvertisedAddresses(&addrs),
                           "Unable to get bound HTTP addresses");
     RETURN_NOT_OK_PREPEND(AddHostPortPBs(addrs, reg->mutable_http_addresses()),
                           "Failed to add HTTP addresses to registration");