#!/bin/bash # # A helper script to wait for ZooKeeper # # This script waits for a ZooKeeper master to appear. # It repeatedly looks up the name passed as argument # in the DNS using getent, and then connects to the # ZooKeeper admin port and uses the 'srvr' command to # obtain the server's status. # You can use this in a Kubernetes init container to # delay Solr pods starting until the ZooKeeper service # has settled down. Or you could explicitly run this in # the Solr container before exec'ing Solr. # # Inspired by https://github.com/helm/charts/blob/9eba7b1c80990233a68dce48f4a8fe0baf9b7fa5/incubator/solr/templates/statefulset.yaml#L60 # # Usage: wait-for-zookeeper.sh [--max-attempts count] [--wait-seconds seconds] zookeeper-service-name # # If no argument is provided, but a Solr-style ZK_HOST is set, # that will be used. If neither is provided, the default # name is 'solr-zookeeper-headless', to match the helm chart. set -euo pipefail SCRIPT="$0" if [[ "${VERBOSE:-}" == "yes" ]]; then set -x fi function usage { echo "$1" echo "Usage: $SCRIPT [--max-attempts count] [--wait-seconds seconds ] zookeeper-service-name" exit 1 } TMP_HOSTS="/tmp/hosts.$$" TMP_STATUS="/tmp/status.$$" function cleanup { rm -f $TMP_HOSTS $TMP_STATUS } trap cleanup EXIT function check_zookeeper { local host=$1 local port="${2:-2181}" if ! echo srvr | nc "$host" "$port" > $TMP_STATUS; then echo "Failed to get status from $host" return fi if [ ! -s $TMP_STATUS ]; then echo "No data from $ip" return fi if grep -q 'not currently serving requests' $TMP_STATUS; then echo "Node $ip is not currently serving requests" return fi mode=$(grep "Mode: " $TMP_STATUS | sed 's/Mode: //'); if [ -z "$mode" ]; then echo "Cannot determine mode from:" cat $TMP_STATUS return fi echo "Node $ip is a $mode" if [ "$mode" = "leader" ] || [ "$mode" = "standalone" ]; then echo "Done" exit 0 fi } max_attempts=120 wait_seconds=2 while (( $# > 0 )); do case "$1" in --help) cat <0" exit 1 fi grep -q -E '^[0-9]+$' <<<"$wait_seconds" || usage "--wait-seconds $wait_seconds: not a number" if [ -z "${lookup_arg:-}" ]; then if [ -n "$ZK_HOST" ]; then lookup_arg="$ZK_HOST" else lookup_arg=solr-zookeeper-headless fi fi echo "Looking up '$lookup_arg'" # split on commas, for when a ZK_HOST string like zoo1:2181,zoo2:2181 is used IFS=',' read -ra lookups <<< "$lookup_arg" ((attempts_left=max_attempts)) while (( attempts_left > 0 )); do for lookup in "${lookups[@]}"; do if grep -q -E "^\[[0-9].*\]" <<<"$lookup"; then # looks like an IPv6 address, eg [2001:DB8::1] or [2001:DB8::1]:2181 # getent does not support the bracket notation, but does support IPv6 addresses host=$(sed -E 's/\[(.*)\].*/\1/' <<<"$lookup") port=$(sed -E 's/^\[(.*)\]:?//' <<<"$lookup") else # IPv4, just split on : IFS=: read -ra split <<<"$lookup" host="${split[0]}" port="${split[1]:-}" fi if [[ "${VERBOSE:-}" == "yes" ]]; then echo "Parsed host=$host port=${port:-}" fi if getent hosts "$host" > $TMP_HOSTS; then while read -r ip hostname ; do echo "${hostname:-}">/dev/null # consume for shellcheck check_zookeeper "$ip" "$port" done <$TMP_HOSTS else echo "Cannot find $lookup yet" fi done (( attempts_left-- )) if (( attempts_left == 0 )); then echo "Still no master found; giving up" exit 1 fi sleep "$wait_seconds" done # To test the parsing: # bash scripts/wait-for-zookeeper.sh foo # bash scripts/wait-for-zookeeper.sh 'ZK_HOST=[2001:DB8::1]:2181,[2001:DB8::1],127.0.0.1:2181,127.0.0.2' # ZK_HOST=[2001:DB8::1]:2181,[2001:DB8::1],127.0.0.1:2181,127.0.0.2 bash scripts/wait-for-zookeeper.sh