You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2023/08/14 15:17:21 UTC

[solr] branch main updated: SOLR-16926: Give override for EmbeddedZK bind host (#1836)

This is an automated email from the ASF dual-hosted git repository.

houston pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 3b2b1b7f790 SOLR-16926: Give override for EmbeddedZK bind host (#1836)
3b2b1b7f790 is described below

commit 3b2b1b7f790986c84671c505e8055afb4dc59c76
Author: Houston Putman <ho...@apache.org>
AuthorDate: Mon Aug 14 11:17:13 2023 -0400

    SOLR-16926: Give override for EmbeddedZK bind host (#1836)
---
 solr/CHANGES.txt                                   |  4 ++
 solr/bin/solr                                      |  4 ++
 solr/bin/solr.cmd                                  |  4 ++
 solr/bin/solr.in.cmd                               |  2 +
 solr/bin/solr.in.sh                                |  2 +
 .../java/org/apache/solr/cloud/SolrZkServer.java   | 41 +++++++------
 .../java/org/apache/solr/cloud/ZkController.java   | 34 +----------
 .../java/org/apache/solr/util/AddressUtils.java    | 64 ++++++++++++++++++++
 solr/docker/templates/Dockerfile.body.template     |  3 +-
 .../cases/cloud_multi_node_embedded_zk/test.sh     | 70 ++++++++++++++++++++++
 .../test.sh                                        | 11 ++--
 .../test.sh                                        |  0
 solr/server/solr/zoo.cfg                           |  3 -
 .../deployment-guide/pages/securing-solr.adoc      | 13 ++++
 .../pages/major-changes-in-solr-9.adoc             |  4 ++
 15 files changed, 195 insertions(+), 64 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9951772eecc..a0ff03bc0bc 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -91,6 +91,10 @@ Improvements
 
 * SOLR-14667: Make zkClientTimeout consistent and based on a system property. The defauls values are stored in a single place referenced everywhere and they are based on system properties (Alex Deparvu)
 
+* SOLR-16926: The embedded Zookeeper's bind host can now be overridden, but still defaults to "127.0.0.1".
+  This is useful when using the ZkCli on a remote Solr using the embedded ZK, or Solr running in a Docker container.
+  The SOLR_ZK_EMBEDDED_HOST envVar or -Dsolr.zk.embedded.host sysProp control this bind address. (Houston Putman)
+
 Optimizations
 ---------------------
 
diff --git a/solr/bin/solr b/solr/bin/solr
index 3ff7832037a..bae4b722432 100644
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -1494,6 +1494,10 @@ if [ -n "${SOLR_JETTY_HOST:-}" ]; then
   SOLR_OPTS+=("-Dsolr.jetty.host=$SOLR_JETTY_HOST")
 fi
 
+if [ -n "${SOLR_ZK_EMBEDDED_HOST:-}" ]; then
+  SOLR_OPTS+=("-Dsolr.zk.embedded.host=$SOLR_ZK_EMBEDDED_HOST")
+fi
+
 : "${STOP_PORT:=$((SOLR_PORT - 1000))}"
 
 if [ "$SCRIPT_CMD" == "start" ] || [ "$SCRIPT_CMD" == "restart" ] ; then
diff --git a/solr/bin/solr.cmd b/solr/bin/solr.cmd
index 61414f807da..4055bad6d00 100755
--- a/solr/bin/solr.cmd
+++ b/solr/bin/solr.cmd
@@ -955,6 +955,10 @@ IF DEFINED SOLR_JETTY_HOST (
   set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.jetty.host=%SOLR_JETTY_HOST%"
 )
 
+IF DEFINED SOLR_ZK_EMBEDDED_HOST (
+  set "SOLR_OPTS=%SOLR_OPTS% -Dsolr.zk.embedded.host=%SOLR_ZK_EMBEDDED_HOST%"
+)
+
 IF "%SCRIPT_CMD%"=="start" (
   REM see if Solr is already running using netstat
   For /f "tokens=2,5" %%j in ('netstat -aon ^| find "TCP " ^| find ":0 " ^| find ":%SOLR_PORT% "') do (
diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd
index 0ca0c12c3d5..5f982cb16b5 100755
--- a/solr/bin/solr.in.cmd
+++ b/solr/bin/solr.in.cmd
@@ -130,6 +130,8 @@ REM set this value as narrowly as required before going to production. In
 REM environments where security is not a concern, 0.0.0.0 can be used to allow
 REM Solr to accept connections on all network interfaces.
 REM set SOLR_JETTY_HOST=127.0.0.1
+REM Sets the network interface the Embedded ZK binds to.
+REM set SOLR_ZK_EMBEDDED_HOST=127.0.0.1
 
 REM Restrict access to solr by IP address.
 REM Specify a comma-separated list of addresses or networks, for example:
diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh
index b1dc6ab5ab4..117ef1761a9 100644
--- a/solr/bin/solr.in.sh
+++ b/solr/bin/solr.in.sh
@@ -154,6 +154,8 @@
 # environments where security is not a concern, 0.0.0.0 can be used to allow
 # Solr to accept connections on all network interfaces.
 #SOLR_JETTY_HOST="127.0.0.1"
+# Sets the network interface the Embedded ZK binds to.
+#SOLR_ZK_EMBEDDED_HOST="127.0.0.1"
 
 # Enables HTTPS. It is implictly true if you set SOLR_SSL_KEY_STORE. Use this config
 # to enable https module with custom jetty configuration.
diff --git a/solr/core/src/java/org/apache/solr/cloud/SolrZkServer.java b/solr/core/src/java/org/apache/solr/cloud/SolrZkServer.java
index f340f4d6c4d..ee7a14733ed 100644
--- a/solr/core/src/java/org/apache/solr/cloud/SolrZkServer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/SolrZkServer.java
@@ -20,9 +20,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.Reader;
 import java.lang.invoke.MethodHandles;
-import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
@@ -30,6 +28,7 @@ import java.util.Properties;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.servlet.SolrDispatchFilter;
+import org.apache.solr.util.AddressUtils;
 import org.apache.zookeeper.server.ServerConfig;
 import org.apache.zookeeper.server.ZooKeeperServerMain;
 import org.apache.zookeeper.server.quorum.QuorumPeer;
@@ -72,7 +71,15 @@ public class SolrZkServer {
     if (zkRun == null) return null;
 
     InetSocketAddress addr = zkProps.getClientPortAddress();
-    return addr.getHostString() + ":" + addr.getPort();
+    String hostName;
+    // We cannot advertise 0.0.0.0, so choose the best host to advertise
+    // (the same that the Solr Node defaults to)
+    if (addr.getAddress().isAnyLocalAddress()) {
+      hostName = AddressUtils.getHostToAdvertise();
+    } else {
+      hostName = addr.getAddress().getHostAddress();
+    }
+    return hostName + ":" + addr.getPort();
   }
 
   public void parseConfig() {
@@ -101,6 +108,10 @@ public class SolrZkServer {
     try {
       props = SolrZkServerProps.getProperties(zooCfgPath);
       SolrZkServerProps.injectServers(props, zkRun, zkHost);
+      // This is the address that the embedded Zookeeper will bind to. Like Solr, it defaults to
+      // "127.0.0.1".
+      props.setProperty(
+          "clientPortAddress", System.getProperty("solr.zk.embedded.host", "127.0.0.1"));
       if (props.getProperty("clientPort") == null) {
         props.setProperty("clientPort", Integer.toString(solrPort + 1000));
       }
@@ -145,14 +156,16 @@ public class SolrZkServer {
     if (zkProps.getServers().size() > 1) {
       if (log.isInfoEnabled()) {
         log.info(
-            "STARTING EMBEDDED ENSEMBLE ZOOKEEPER SERVER at port {}",
-            zkProps.getClientPortAddress().getPort());
+            "STARTING EMBEDDED ENSEMBLE ZOOKEEPER SERVER at port {}, listening on host {}",
+            zkProps.getClientPortAddress().getPort(),
+            zkProps.getClientPortAddress().getAddress().getHostAddress());
       }
     } else {
       if (log.isInfoEnabled()) {
         log.info(
-            "STARTING EMBEDDED STANDALONE ZOOKEEPER SERVER at port {}",
-            zkProps.getClientPortAddress().getPort());
+            "STARTING EMBEDDED ENSEMBLE ZOOKEEPER SERVER at port {}, listening on host {}",
+            zkProps.getClientPortAddress().getPort(),
+            zkProps.getClientPortAddress().getAddress().getHostAddress());
       }
     }
 
@@ -268,20 +281,6 @@ class SolrZkServerProps extends QuorumPeerConfig {
     this.dataDir = dataDir;
   }
 
-  public void setClientPort(int clientPort) {
-    if (clientPortAddress != null) {
-      try {
-        this.clientPortAddress =
-            new InetSocketAddress(
-                InetAddress.getByName(clientPortAddress.getHostName()), clientPort);
-      } catch (UnknownHostException e) {
-        throw new RuntimeException(e);
-      }
-    } else {
-      this.clientPortAddress = new InetSocketAddress(clientPort);
-    }
-  }
-
   /**
    * Parse config from a Properties.
    *
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index c93a7dfef99..25f097cc8c8 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -31,15 +31,11 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -121,6 +117,7 @@ import org.apache.solr.handler.component.HttpShardHandlerFactory;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.update.UpdateLog;
+import org.apache.solr.util.AddressUtils;
 import org.apache.solr.util.RTimer;
 import org.apache.solr.util.RefCounted;
 import org.apache.zookeeper.CreateMode;
@@ -875,35 +872,8 @@ public class ZkController implements Closeable {
   // normalize host removing any url scheme.
   // input can be null, host, or url_prefix://host
   private String normalizeHostName(String host) {
-
     if (host == null || host.length() == 0) {
-      String hostaddress;
-      try {
-        hostaddress = InetAddress.getLocalHost().getHostAddress();
-      } catch (UnknownHostException e) {
-        hostaddress = "127.0.0.1"; // cannot resolve system hostname, fall through
-      }
-      // Re-get the IP again for "127.0.0.1", the other case we trust the hosts
-      // file is right.
-      if ("127.0.0.1".equals(hostaddress)) {
-        Enumeration<NetworkInterface> netInterfaces = null;
-        try {
-          netInterfaces = NetworkInterface.getNetworkInterfaces();
-          while (netInterfaces.hasMoreElements()) {
-            NetworkInterface ni = netInterfaces.nextElement();
-            Enumeration<InetAddress> ips = ni.getInetAddresses();
-            while (ips.hasMoreElements()) {
-              InetAddress ip = ips.nextElement();
-              if (ip.isSiteLocalAddress()) {
-                hostaddress = ip.getHostAddress();
-              }
-            }
-          }
-        } catch (Exception e) {
-          log.error("Error while looking for a better host name than 127.0.0.1", e);
-        }
-      }
-      host = hostaddress;
+      host = AddressUtils.getHostToAdvertise();
     } else {
       if (URLUtil.hasScheme(host)) {
         host = URLUtil.removeScheme(host);
diff --git a/solr/core/src/java/org/apache/solr/util/AddressUtils.java b/solr/core/src/java/org/apache/solr/util/AddressUtils.java
new file mode 100644
index 00000000000..ca03e26cf85
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/AddressUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package org.apache.solr.util;
+
+import java.lang.invoke.MethodHandles;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Simple utilities for working Hostname/IP Addresses */
+public final class AddressUtils {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  // normalize host removing any url scheme.
+  // input can be null, host, or url_prefix://host
+  public static String getHostToAdvertise() {
+    String hostaddress;
+    try {
+      hostaddress = InetAddress.getLocalHost().getHostAddress();
+    } catch (UnknownHostException e) {
+      hostaddress =
+          InetAddress.getLoopbackAddress()
+              .getHostAddress(); // cannot resolve system hostname, fall through
+    }
+    // Re-get the IP again for "127.0.0.1", the other case we trust the hosts
+    // file is right.
+    if ("127.0.0.1".equals(hostaddress)) {
+      try {
+        var netInterfaces = NetworkInterface.getNetworkInterfaces();
+        while (netInterfaces.hasMoreElements()) {
+          NetworkInterface ni = netInterfaces.nextElement();
+          Enumeration<InetAddress> ips = ni.getInetAddresses();
+          while (ips.hasMoreElements()) {
+            InetAddress ip = ips.nextElement();
+            if (ip.isSiteLocalAddress()) {
+              hostaddress = ip.getHostAddress();
+            }
+          }
+        }
+      } catch (Exception e) {
+        log.error("Error while looking for a better host name than 127.0.0.1", e);
+      }
+    }
+    return hostaddress;
+  }
+}
diff --git a/solr/docker/templates/Dockerfile.body.template b/solr/docker/templates/Dockerfile.body.template
index 7282642a73f..d754b9d7e42 100644
--- a/solr/docker/templates/Dockerfile.body.template
+++ b/solr/docker/templates/Dockerfile.body.template
@@ -45,7 +45,8 @@ ENV SOLR_USER="solr" \
     SOLR_PID_DIR=/var/solr \
     SOLR_LOGS_DIR=/var/solr/logs \
     LOG4J_PROPS=/var/solr/log4j2.xml \
-    SOLR_JETTY_HOST="0.0.0.0"
+    SOLR_JETTY_HOST="0.0.0.0" \
+    SOLR_ZK_EMBEDDED_HOST="0.0.0.0"
 
 RUN set -ex; \
   groupadd -r --gid "$SOLR_GID" "$SOLR_GROUP"; \
diff --git a/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh b/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh
new file mode 100755
index 00000000000..2ee47103264
--- /dev/null
+++ b/solr/docker/tests/cases/cloud_multi_node_embedded_zk/test.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+# 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.
+
+set -euo pipefail
+
+TEST_DIR="${TEST_DIR:-$(dirname -- "${BASH_SOURCE[0]}")}"
+source "${TEST_DIR}/../../shared.sh"
+
+echo "Running base solr node w/embeddedZk - $container_name"
+docker run --name "${container_name}" -d "$tag" solr-fg -c
+
+wait_for_container_and_solr "${container_name}"
+
+solr_ip=$(docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" "${container_name}")
+
+
+container_cleanup "${container_name}-2"
+
+echo "Running additional solr node - $container_name-2"
+docker run --name "$container_name-2" -d \
+  --env "ZK_HOST=${solr_ip}:9983" \
+  "$tag" solr-fg -c
+
+wait_for_container_and_solr "${container_name}-2"
+
+echo "Check live nodes"
+data=$(docker exec --user=solr "${container_name}-2" wget -q -O - 'http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS')
+
+if ! grep -q "${solr_ip}:8983" <<<"$data"; then
+  echo "Test $TEST_NAME $tag failed; could not find first solr node in cluster state of second node"
+  echo "$data"
+  exit 1
+fi
+
+echo "Creating distributed collection"
+data=$(docker exec --user=solr "$container_name" solr create -c test -rf 1 -s 2)
+
+if ! grep -q "Created collection 'test'" <<<"$data"; then
+  echo "Test $TEST_NAME $tag failed; could not create distributed collection"
+  echo "$data"
+  exit 1
+fi
+
+echo "Submitting Solr query"
+data=$(docker exec --user=solr "${container_name}-2" wget -q -O - 'http://localhost:8983/solr/test/select?q=*:*')
+
+if ! grep -q '"numFound":0' <<<"$data"; then
+  echo "Test $TEST_NAME $tag failed; could not query distributed collection"
+  echo "$data"
+  exit 1
+fi
+
+
+container_cleanup "${container_name}-2"
+container_cleanup "$container_name"
+
+echo "Test $TEST_NAME $tag succeeded"
diff --git a/solr/docker/tests/cases/prometheus-exporter/test.sh b/solr/docker/tests/cases/prometheus-exporter-cloud/test.sh
similarity index 74%
copy from solr/docker/tests/cases/prometheus-exporter/test.sh
copy to solr/docker/tests/cases/prometheus-exporter-cloud/test.sh
index d964453170b..6407dfeb272 100755
--- a/solr/docker/tests/cases/prometheus-exporter/test.sh
+++ b/solr/docker/tests/cases/prometheus-exporter-cloud/test.sh
@@ -22,27 +22,24 @@ source "${TEST_DIR}/../../shared.sh"
 container_cleanup "${container_name}-solr"
 
 echo "Running $container_name"
-docker run --name "${container_name}-solr" -d "$tag" "solr-demo"
+docker run --name "${container_name}-solr" -d "$tag" solr-fg -c
 
 wait_for_container_and_solr "${container_name}-solr"
 
 solr_ip=$(docker inspect --format="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" "${container_name}-solr")
 
-docker run --name "$container_name" --add-host "solr-host:${solr_ip}" -d \
-  --env "SOLR_URL=http://solr-host:8983/solr" \
+docker run --name "$container_name" -d \
+  --env "ZK_HOST=${solr_ip}:9983" \
   --env "SCRAPE_INTERVAL=1" \
   --env "CLUSTER_ID=myCluster" \
   "$tag" "solr-exporter"
 
 wait_for_container_and_solr_exporter "${container_name}"
 
-echo "Submitting Solr query"
-docker exec --user=solr "${container_name}-solr" wget -q -O - 'http://localhost:8983/solr/demo/select?q=id%3Adell' > /dev/null
-
 echo "Checking prometheus data"
 data=$(docker exec --user=solr "$container_name" wget -q -O - 'http://localhost:8989/metrics')
 
-if ! grep -E -q 'solr_metrics_core_query_requests_total{category="QUERY",searchHandler="/select",internal="false",core="demo",base_url="http://solr-host:8983/solr",cluster_id="myCluster",} [0-9]+.0' <<<"$data"; then
+if ! grep -E -q "solr_collections_live_nodes{zk_host=\"${solr_ip}:9983\",cluster_id=\"myCluster\",} 1.0" <<<"$data"; then
   echo "Test $TEST_NAME $tag failed; did not find correct data"
   echo "$data"
   exit 1
diff --git a/solr/docker/tests/cases/prometheus-exporter/test.sh b/solr/docker/tests/cases/prometheus-exporter-standalone/test.sh
similarity index 100%
rename from solr/docker/tests/cases/prometheus-exporter/test.sh
rename to solr/docker/tests/cases/prometheus-exporter-standalone/test.sh
diff --git a/solr/server/solr/zoo.cfg b/solr/server/solr/zoo.cfg
index 4ef8dcea2ae..7c32425e718 100644
--- a/solr/server/solr/zoo.cfg
+++ b/solr/server/solr/zoo.cfg
@@ -11,9 +11,6 @@ syncLimit=5
 # dataDir=/opt/zookeeper/data
 # NOTE: Solr defaults the dataDir to <solrHome>/zoo_data
 
-# the address that embedded zookeeper will bind to
-clientPortAddress=127.0.0.1
-
 # the port at which the clients will connect
 # clientPort=2181
 # NOTE: Solr sets this based on zkRun / zkHost params
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/securing-solr.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/securing-solr.adoc
index 3732cea17cb..9a4fdea3898 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/securing-solr.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/securing-solr.adoc
@@ -115,4 +115,17 @@ This can be done by setting a `SOLR_JETTY_HOST` value in your environment's "inc
  ----
  SOLR_JETTY_HOST="0.0.0.0"
  ----
+
+The same setting is also available as the `-Dsolr.jetty.host` System Property.
+
+The same is true for the embedded Zookeeper, if it is run with Solr.
+By default, the embedded Zookeeper only listens on the loopback interface ("127.0.0.1")
+The bind host is controlled via the `SOLR_ZK_EMBEDDED_HOST` value in your environment's "include script" (`solr.in.sh` or `solr.in.cmd`):
+
+[source,bash]
+ ----
+ SOLR_ZK_EMBEDDED_HOST="0.0.0.0"
+ ----
+
+The same setting is also available as the `-Dsolr.zk.embedded.host` System Property.
 // end::security-network-binding-1[]
diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
index 750919eabef..2e670ca9f06 100644
--- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
+++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
@@ -76,6 +76,10 @@ Upgrading existing clouds and use-cases that have custom configSets will not be
 * The default `minimalFreeDiskGB` value has been lowered from `20GB` to `5GB` when using the xref:configuration-guide:replica-placement-plugins.adoc#affinityplacementfactory[AffinityPlacementPlugin].
 Therefore, when using the default settings, nodes that were previously excluded from Replica placements due to low available disk space may be selected after upgrading.
 
+=== Embedded Zookeeper
+* The Embedded Zookeeper can now be configured to listen to (or bind to) more hosts than just `localhost`,
+see the  xref:deployment-guide:securing-solr.adoc#network-configuration[Network Configuration documentation] for more information.
+
 
 == Solr 9.3
 === Binary Releases