From 417913f62b486a9a5069318f402f46c01e4e1534 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Thu, 19 Dec 2024 17:15:32 +0700 Subject: [PATCH 01/13] [bitnami/etcd] Major refactor: - Remove prestop logic (no longer removing member when container stops) - Remove members not included in ETCD_INITIAL_CLUSTERS during startup - Stop storing member id on a separate file, member id is checked from etcd data dir instead - Stop reading member removal state off of disk, probe the cluster instead - Remove old member (with the same name) if exist before adding new member - If data dir is not empty, check if the member still belongs to the cluster. If not, remove data dir, remove member with the same name, and add new member - Remove env var ETCD_DISABLE_STORE_MEMBER_ID - Remove env var ETCD_DISABLE_PRESTOP Signed-off-by: Khoi Pham --- .../rootfs/opt/bitnami/scripts/etcd-env.sh | 4 - .../opt/bitnami/scripts/etcd/prestop.sh | 23 +- .../rootfs/opt/bitnami/scripts/libetcd.sh | 241 ++++++++++-------- bitnami/etcd/README.md | 2 - 4 files changed, 136 insertions(+), 134 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh index 723c73a9f1da1..478ac919ed0ec 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh @@ -35,8 +35,6 @@ etcd_env_vars=( ETCD_ON_K8S ETCD_INIT_SNAPSHOT_FILENAME ETCDCTL_API - ETCD_DISABLE_STORE_MEMBER_ID - ETCD_DISABLE_PRESTOP ETCD_NAME ETCD_LOG_LEVEL ETCD_LISTEN_CLIENT_URLS @@ -94,8 +92,6 @@ export ETCD_DISASTER_RECOVERY="${ETCD_DISASTER_RECOVERY:-no}" export ETCD_ON_K8S="${ETCD_ON_K8S:-no}" export ETCD_INIT_SNAPSHOT_FILENAME="${ETCD_INIT_SNAPSHOT_FILENAME:-}" export ETCDCTL_API="${ETCDCTL_API:-3}" -export ETCD_DISABLE_STORE_MEMBER_ID="${ETCD_DISABLE_STORE_MEMBER_ID:-no}" -export ETCD_DISABLE_PRESTOP="${ETCD_DISABLE_PRESTOP:-no}" # etcd native environment variables (see https://etcd.io/docs/current/op-guide/configuration) export ETCD_NAME="${ETCD_NAME:-}" diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh index 9807c6937697f..f9c077296b907 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh @@ -8,24 +8,5 @@ set -o pipefail set -o nounset # set -o xtrace # Uncomment this line for debugging purposes -# Load libraries -. /opt/bitnami/scripts/libetcd.sh - -# Load etcd environment settings -. /opt/bitnami/scripts/etcd-env.sh - -if is_boolean_yes "$ETCD_DISABLE_PRESTOP"; then - return 0 -fi - -endpoints="$(etcdctl_get_endpoints true)" -if is_empty_value "${endpoints}"; then - exit 0 -fi -read -r -a extra_flags <<<"$(etcdctl_auth_flags)" -extra_flags+=("--endpoints=${endpoints}" "--debug=true") -# We use 'sync' to ensure memory buffers are flushed to disk -# so we reduce the chances that the "member_removal.log" file is empty. -# ref: https://man7.org/linux/man-pages/man1/sync.1.html -etcdctl member remove "$(get_member_id)" "${extra_flags[@]}" >"$(dirname "$ETCD_DATA_DIR")/member_removal.log" -sync -d "$(dirname "$ETCD_DATA_DIR")/member_removal.log" +# This script should be removed when the chart no longer use it +return 0 diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index b75c4c95a5959..b026266cd7285 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -312,40 +312,6 @@ etcdctl_auth_norbac_flags() { echo "${authFlags[*]}" } -######################## -# Stores etcd member ID in the data directory -# Globals: -# ETCD_* -# Arguments: -# None -# Returns: -# None -######################## -etcd_store_member_id() { - if is_boolean_yes "$ETCD_DISABLE_STORE_MEMBER_ID"; then - return 0 - fi - local -a extra_flags - local member_id="" - info "Obtaining cluster member ID" - etcd_start_bg - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - if retry_while "etcdctl ${extra_flags[*]:-} member list" >/dev/null 2>&1; then - while is_empty_value "$member_id"; do - read -r -a advertised_array <<<"$(tr ',;' ' ' <<<"$ETCD_ADVERTISE_CLIENT_URLS")" - member_id="$(etcdctl "${extra_flags[@]}" member list | grep -w "${advertised_array[0]}" | awk -F "," '{ print $1}' || true)" - done - # We use 'sync' to ensure memory buffers are flushed to disk - # so we reduce the chances that the "member_id" file is empty. - # ref: https://man7.org/linux/man-pages/man1/sync.1.html - echo "$member_id" >"${ETCD_DATA_DIR}/member_id" - sync -d "${ETCD_DATA_DIR}/member_id" - info "Stored member ID: $(cat "${ETCD_DATA_DIR}/member_id")" - fi - etcd_stop -} - ######################## # Configure etcd RBAC (do not confuse with K8s RBAC) # Globals: @@ -376,31 +342,6 @@ etcd_configure_rbac() { etcd_stop } -######################## -# Checks if the member was successfully removed from the cluster -# Globals: -# ETCD_* -# Arguments: -# None -# Returns: -# None -######################## -was_etcd_member_removed() { - local return_value=0 - - if grep -sqE "^Member[[:space:]]+[a-z0-9]+\s+removed\s+from\s+cluster\s+[a-z0-9]+$" "${ETCD_VOLUME_DIR}/member_removal.log"; then - debug "Removal was properly recorded in member_removal.log" - rm -rf "${ETCD_DATA_DIR:?}/"* - elif [[ ! -d "${ETCD_DATA_DIR}/member/snap" ]] && is_empty_value "$(get_member_id)"; then - debug "Missing member data" - rm -rf "${ETCD_DATA_DIR:?}/"* - else - return_value=1 - fi - rm -f "${ETCD_VOLUME_DIR}/member_removal.log" - return $return_value -} - ######################## # Checks if etcd needs to bootstrap a new cluster # Globals: @@ -572,6 +513,127 @@ recalculate_initial_cluster() { fi } +######################## +# Remove the old member from the cluster if it exists +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# None +######################### +remove_old_member_if_exist() { + local old_member_id + old_member_id=$(get_member_id) + if ! is_empty_value "$old_member_id"; then + info "Removing old member $old_member_id" + local -a extra_flags + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") + etcdctl member remove "$old_member_id" "${extra_flags[@]}" + fi +} + +######################## +# Add this member as a new member to the cluster +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# None +######################### +add_new_member() { + local -a extra_flags + info "Adding new member to existing cluster" + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") + extra_flags+=("--peer-urls=$ETCD_INITIAL_ADVERTISE_PEER_URLS") + mkdir -p "$(dirname $ETCD_NEW_MEMBERS_ENV_FILE)" || true + etcdctl member add "$ETCD_NAME" "${extra_flags[@]}" | grep "^ETCD_" >"$ETCD_NEW_MEMBERS_ENV_FILE" + replace_in_file "$ETCD_NEW_MEMBERS_ENV_FILE" "^" "export " + sync -d "$ETCD_NEW_MEMBERS_ENV_FILE" +} + +######################## +# Check that this node is still a member of the cluster +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# None +######################### +is_membership_intact() { + local tmp_file + local start_command=("etcd") + local pid + local ret=0 + + tmp_file=$(mktemp) + + am_i_root && start_command=("run_as_user" "$ETCD_DAEMON_USER" "${start_command[@]}") + [[ -f "$ETCD_CONF_FILE" ]] && start_command+=("--config-file" "$ETCD_CONF_FILE") + $start_command > >(tee -a "$tmp_file") 2>&1 & + pid=$! + debug "Started etcd in background with PID $pid" + + while read -r line; do + echo "$line" # Stream the output + if [[ "$line" =~ (established TCP streaming connection with remote peer|the member has been permanently removed from the cluster|ignored streaming request; ID mismatch) ]]; then + kill "$pid" + wait "$pid" 2>/dev/null + debug "Stopped etcd" + break + fi + done < <(tail -f "$tmp_file") + + if grep -q "the member has been permanently removed from the cluster\|ignored streaming request; ID mismatch" "$tmp_file"; then + info "The member has been permanently removed from the cluster" + ret=1 + else + info "The member is still part of the cluster" + fi + rm -f "$tmp_file" + return $ret +} + +######################## +# Remove members that are not named in ETCD_INITIAL_CLUSTER +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# None +######################### +remove_obsolete_members() { + local -r current=$(mktemp) + local -r expected=$(mktemp) + local -a differences + + local -a extra_flags + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") + etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current + info "Current cluster members are: $(cat $current | awk -F, '{print $2}' | tr -s '\n' ', ' | sed 's/,$//g')" + + echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}' > $expected + info "Expected cluster members are: $(cat $expected | tr -s '\n' ', ' | sed 's/,$//g')" + + read -r -a differences <<<"$(comm -23 <(cat $current | awk -F, '{print $2}' | sort) <(sort $expected))" + if [ ! -z ${differences+x} ]; then + for member in "$differences"; do + info "Removing obsolete member $member" + etcdctl member remove ${extra_flags[@]} $(cat $current | grep "$member" | awk -F, '{print $1}') + done + else + info "No obsolete member detected" + fi + + rm -f $current $expected +} + ######################## # Ensure etcd is initialized # Globals: @@ -594,6 +656,9 @@ etcd_initialize() { export ETCD_INITIAL_CLUSTER [[ -f "$ETCD_CONF_FILE" ]] && etcd_conf_write "initial-cluster" "$ETCD_INITIAL_CLUSTER" + # Remove members not included in ETCD_INITIAL_CLUSTER + remove_obsolete_members + read -r -a initial_members <<<"$(tr ',;' ' ' <<<"$ETCD_INITIAL_CLUSTER")" if is_mounted_dir_empty "$ETCD_DATA_DIR"; then info "There is no data from previous deployments" @@ -630,6 +695,8 @@ etcd_initialize() { fi fi else + # if an old member with the same name is already registered, we want to remove it first + remove_old_member_if_exist info "Adding new member to existing cluster" ensure_dir_exists "$ETCD_DATA_DIR" add_self_to_cluster @@ -659,13 +726,10 @@ etcd_initialize() { ) fi debug_execute etcdctl snapshot restore "${ETCD_INIT_SNAPSHOTS_DIR}/${ETCD_INIT_SNAPSHOT_FILENAME}" "${restore_args[@]}" - etcd_store_member_id else error "There was no snapshot to restore!" exit 1 fi - else - etcd_store_member_id fi else info "Detected data from previous deployments" @@ -675,10 +739,7 @@ etcd_initialize() { fi if [[ ${#initial_members[@]} -gt 1 ]]; then member_id="$(get_member_id)" - if is_boolean_yes "$ETCD_DISABLE_PRESTOP"; then - info "The member will try to join the cluster by it's own" - export ETCD_INITIAL_CLUSTER_STATE=existing - elif ! is_healthy_etcd_cluster; then + if ! is_healthy_etcd_cluster; then warn "Cluster not responding!" if is_boolean_yes "$ETCD_DISASTER_RECOVERY"; then latest_snapshot_file="$(find /snapshots/ -maxdepth 1 -type f -name 'db-*' | sort | tail -n 1)" @@ -700,7 +761,6 @@ etcd_initialize() { --initial-cluster "$ETCD_INITIAL_CLUSTER" \ --initial-cluster-token "$ETCD_INITIAL_CLUSTER_TOKEN" \ --initial-advertise-peer-urls "$ETCD_INITIAL_ADVERTISE_PEER_URLS" - etcd_store_member_id else error "There was no snapshot to restore!" exit 1 @@ -708,33 +768,13 @@ etcd_initialize() { else warn "Disaster recovery is disabled, the cluster will try to recover on it's own" fi - elif was_etcd_member_removed; then - info "Adding new member to existing cluster" - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - extra_flags+=("--peer-urls=$ETCD_INITIAL_ADVERTISE_PEER_URLS") - etcdctl member add "$ETCD_NAME" "${extra_flags[@]}" | grep "^ETCD_" >"$ETCD_NEW_MEMBERS_ENV_FILE" - replace_in_file "$ETCD_NEW_MEMBERS_ENV_FILE" "^" "export " - # The value of ETCD_INITIAL_CLUSTER_STATE must be changed for it to be correctly added to the existing cluster - # https://etcd.io/docs/v3.5/op-guide/configuration/#--initial-cluster-state - export ETCD_INITIAL_CLUSTER_STATE=existing - etcd_store_member_id - elif ! is_empty_value "$member_id"; then - info "Updating member in existing cluster" - export ETCD_INITIAL_CLUSTER_STATE=existing - [[ -f "$ETCD_CONF_FILE" ]] && etcd_conf_write "initial-cluster-state" "$ETCD_INITIAL_CLUSTER_STATE" - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - extra_flags+=("--peer-urls=$ETCD_INITIAL_ADVERTISE_PEER_URLS") - if is_boolean_yes "$ETCD_ON_K8S"; then - extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - etcdctl member update "$member_id" "${extra_flags[@]}" - else - etcd_start_bg - etcdctl member update "$member_id" "${extra_flags[@]}" - etcd_stop - fi else - info "Member ID wasn't properly stored, the member will try to join the cluster by it's own" + info "Cluster is healthy" + if ! is_membership_intact; then + rm -rf "$ETCD_DATA_DIR" + remove_old_member_if_exist + add_new_member + fi export ETCD_INITIAL_CLUSTER_STATE=existing [[ -f "$ETCD_CONF_FILE" ]] && etcd_conf_write "initial-cluster-state" "$ETCD_INITIAL_CLUSTER_STATE" fi @@ -802,24 +842,11 @@ add_self_to_cluster() { # String ######################### get_member_id() { - if ! is_boolean_yes "$ETCD_DISABLE_STORE_MEMBER_ID"; then - if [[ ! -s "${ETCD_DATA_DIR}/member_id" ]]; then - echo "" - return 0 - fi - cat "${ETCD_DATA_DIR}/member_id" - return 0 - fi local ret local -a extra_flags - local etcd_active_endpoints=${ETCD_ACTIVE_ENDPOINTS:-} - if is_empty_value "${etcd_active_endpoints}"; then - setup_etcd_active_endpoints >/dev/null 2>&1 - fi - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - extra_flags+=("--endpoints=${ETCD_ACTIVE_ENDPOINTS}") + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") ret=$(etcdctl "${extra_flags[@]}" member list | grep -w "$ETCD_INITIAL_ADVERTISE_PEER_URLS" | awk -F "," '{ print $1 }') # if not return zero if is_empty_value "$ret"; then diff --git a/bitnami/etcd/README.md b/bitnami/etcd/README.md index 5d27ba09dac88..0775371c5e9c0 100644 --- a/bitnami/etcd/README.md +++ b/bitnami/etcd/README.md @@ -199,8 +199,6 @@ Apart from providing your custom configuration file, you can also modify the ser | `ETCD_ON_K8S` | Whether etcd is running on a K8s environment or not. | `no` | | `ETCD_INIT_SNAPSHOT_FILENAME` | Existing snapshot filename to start the etcd cluster from. | `nil` | | `ETCDCTL_API` | etcdctl API version. | `3` | -| `ETCD_DISABLE_STORE_MEMBER_ID` | Disable writing the member id in a file. | `no` | -| `ETCD_DISABLE_PRESTOP` | Disable running the pre-stop hook. | `no` | | `ETCD_NAME` | etcd member name. | `nil` | | `ETCD_LOG_LEVEL` | etcd log level. | `info` | | `ETCD_LISTEN_CLIENT_URLS` | List of URLs to listen on for client traffic. | `http://0.0.0.0:2379` | From 95811a3a944e49372b17e6a8c646a6437e8d76f9 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Fri, 20 Dec 2024 09:53:40 +0700 Subject: [PATCH 02/13] [bitnami/etcd] Fix remove_obsolete_members function Signed-off-by: Khoi Pham --- .../3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index b026266cd7285..e5d4ef0b0c0bf 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -615,7 +615,11 @@ remove_obsolete_members() { local -a extra_flags read -r -a extra_flags <<<"$(etcdctl_auth_flags)" is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current + debug "Listing members" + if ! etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current; then + debug "Error listing members, is this a new cluster?" + return 0 + fi info "Current cluster members are: $(cat $current | awk -F, '{print $2}' | tr -s '\n' ', ' | sed 's/,$//g')" echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}' > $expected From 96e784ce725a5e039d5447ad359c938b93a77b93 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Fri, 20 Dec 2024 11:50:59 +0700 Subject: [PATCH 03/13] [bitnami/etcd] is_new_etcd_cluster queries current cluster Signed-off-by: Khoi Pham --- .../rootfs/opt/bitnami/scripts/libetcd.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index e5d4ef0b0c0bf..688b811cd6c34 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -352,7 +352,14 @@ etcd_configure_rbac() { # Boolean ######################## is_new_etcd_cluster() { - [[ "$ETCD_INITIAL_CLUSTER_STATE" = "new" ]] && [[ "$ETCD_INITIAL_CLUSTER" = *"$ETCD_INITIAL_ADVERTISE_PEER_URLS"* ]] + local -a extra_flags + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") + if ! etcdctl member list ${extra_flags[@]}; then + return 0 + else + return 1 + fi } ######################## @@ -580,7 +587,7 @@ is_membership_intact() { while read -r line; do echo "$line" # Stream the output - if [[ "$line" =~ (established TCP streaming connection with remote peer|the member has been permanently removed from the cluster|ignored streaming request; ID mismatch) ]]; then + if [[ "$line" =~ (established TCP streaming connection with remote peer|the member has been permanently removed from the cluster|ignored streaming request; ID mismatch|\"error\":\"cluster ID mismatch\") ]]; then kill "$pid" wait "$pid" 2>/dev/null debug "Stopped etcd" @@ -589,7 +596,10 @@ is_membership_intact() { done < <(tail -f "$tmp_file") if grep -q "the member has been permanently removed from the cluster\|ignored streaming request; ID mismatch" "$tmp_file"; then - info "The member has been permanently removed from the cluster" + info "The remote member ID is different from the local member ID" + ret=1 + elif grep -q "\"error\":\"cluster ID mismatch\"" "$tmp_file"; then + info "The remote cluster ID is different from the local cluster ID" ret=1 else info "The member is still part of the cluster" From c3a825a7ee79da81ca22de035e600e65ef7c42e7 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Tue, 24 Dec 2024 10:55:48 +0700 Subject: [PATCH 04/13] [bitnami/etcd] Added preupgrade.sh Signed-off-by: Khoi Pham --- .../opt/bitnami/scripts/etcd/preupgrade.sh | 89 +++++++++++++++++++ .../rootfs/opt/bitnami/scripts/libetcd.sh | 43 --------- 2 files changed, 89 insertions(+), 43 deletions(-) create mode 100755 bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh new file mode 100755 index 0000000000000..b2b90ed106e62 --- /dev/null +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# Copyright Broadcom, Inc. All Rights Reserved. +# SPDX-License-Identifier: APACHE-2.0 + +# shellcheck disable=SC1091 + +set -o errexit +set -o pipefail +set -o nounset + +# Load libraries +. /opt/bitnami/scripts/libfs.sh +. /opt/bitnami/scripts/libos.sh +. /opt/bitnami/scripts/libetcd.sh + +# Load etcd environment settings +. /opt/bitnami/scripts/etcd-env.sh + +######################## +# Obtain endpoints to connect when running 'ectdctl' in a hook job +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# String +######################## +etcdctl_job_endpoints() { + local -a endpoints=() + local host domain port count + + # get number of endpoints from initial cluster endpoints + count="$(echo $ETCD_INITIAL_CLUSTER | awk -F, '{print NF}')" + + # This piece of code assumes this code is executed on a K8s environment + # where etcd members are part of a statefulset that uses a headless service + # to create a unique FQDN per member. Under these circumstances, the + # ETCD_ADVERTISE_CLIENT_URLS env. variable is created as follows: + # SCHEME://POD_NAME.HEADLESS_SVC_DOMAIN:CLIENT_PORT,SCHEME://SVC_DOMAIN:SVC_CLIENT_PORT + # + # Assuming this, we can extract the HEADLESS_SVC_DOMAIN and obtain + # every available endpoint + read -r -a advertised_array <<<"$(tr ',;' ' ' <<<"$ETCD_ADVERTISE_CLIENT_URLS")" + port="$(parse_uri "${advertised_array[0]}" "port")" + + for i in $(seq 0 $(($count - 1))); do + pod_name="${MY_STS_NAME}-${i}" + endpoints+=("${pod_name}.${ETCD_CLUSTER_DOMAIN}:${port:-2380}") + done + + debug "etcdctl endpoints are ${endpoints[*]}" + echo "${endpoints[*]}" | tr ' ' ',' +} + +######################## +# Remove members that are not named in ETCD_INITIAL_CLUSTER +# Globals: +# ETCD_* +# Arguments: +# None +# Returns: +# None +######################### +remove_members() { + local -r current=$(mktemp) + local -r expected=$(mktemp) + + local -a extra_flags + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_job_endpoints)") + debug "Listing members" + if ! etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current; then + debug "Error listing members, is this a new cluster?" + return 0 + fi + info "Current cluster members are: $(cat $current | awk -F, '{print $2}' | tr -s '\n' ', ' | sed 's/,$//g')" + + echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}' > $expected + info "Expected cluster members are: $(cat $expected | tr -s '\n' ', ' | sed 's/,$//g')" + + for member in $(comm -23 <(cat $current | awk -F, '{print $2}' | sort) <(sort $expected)); do + info "Removing obsolete member $member" + etcdctl member remove ${extra_flags[@]} $(cat $current | grep "$member" | awk -F, '{print $1}') + done + + rm -f $current $expected +} + +remove_members \ No newline at end of file diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index 688b811cd6c34..e1a850cd39c6f 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -608,46 +608,6 @@ is_membership_intact() { return $ret } -######################## -# Remove members that are not named in ETCD_INITIAL_CLUSTER -# Globals: -# ETCD_* -# Arguments: -# None -# Returns: -# None -######################### -remove_obsolete_members() { - local -r current=$(mktemp) - local -r expected=$(mktemp) - local -a differences - - local -a extra_flags - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - debug "Listing members" - if ! etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current; then - debug "Error listing members, is this a new cluster?" - return 0 - fi - info "Current cluster members are: $(cat $current | awk -F, '{print $2}' | tr -s '\n' ', ' | sed 's/,$//g')" - - echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}' > $expected - info "Expected cluster members are: $(cat $expected | tr -s '\n' ', ' | sed 's/,$//g')" - - read -r -a differences <<<"$(comm -23 <(cat $current | awk -F, '{print $2}' | sort) <(sort $expected))" - if [ ! -z ${differences+x} ]; then - for member in "$differences"; do - info "Removing obsolete member $member" - etcdctl member remove ${extra_flags[@]} $(cat $current | grep "$member" | awk -F, '{print $1}') - done - else - info "No obsolete member detected" - fi - - rm -f $current $expected -} - ######################## # Ensure etcd is initialized # Globals: @@ -670,9 +630,6 @@ etcd_initialize() { export ETCD_INITIAL_CLUSTER [[ -f "$ETCD_CONF_FILE" ]] && etcd_conf_write "initial-cluster" "$ETCD_INITIAL_CLUSTER" - # Remove members not included in ETCD_INITIAL_CLUSTER - remove_obsolete_members - read -r -a initial_members <<<"$(tr ',;' ' ' <<<"$ETCD_INITIAL_CLUSTER")" if is_mounted_dir_empty "$ETCD_DATA_DIR"; then info "There is no data from previous deployments" From a18d711f32f916d0c1e261a824ec86227b986f44 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Wed, 25 Dec 2024 11:58:39 +0700 Subject: [PATCH 05/13] [bitnami/etcd] Document changes to ETCD_INITIAL_CLUSTER_STATE Signed-off-by: Khoi Pham --- .../rootfs/opt/bitnami/scripts/etcd-env.sh | 2 -- bitnami/etcd/README.md | 28 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh index 478ac919ed0ec..89b08b5303c12 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd-env.sh @@ -40,7 +40,6 @@ etcd_env_vars=( ETCD_LISTEN_CLIENT_URLS ETCD_ADVERTISE_CLIENT_URLS ETCD_INITIAL_CLUSTER - ETCD_INITIAL_CLUSTER_STATE ETCD_LISTEN_PEER_URLS ETCD_INITIAL_ADVERTISE_PEER_URLS ETCD_INITIAL_CLUSTER_TOKEN @@ -99,7 +98,6 @@ export ETCD_LOG_LEVEL="${ETCD_LOG_LEVEL:-info}" export ETCD_LISTEN_CLIENT_URLS="${ETCD_LISTEN_CLIENT_URLS:-http://0.0.0.0:2379}" export ETCD_ADVERTISE_CLIENT_URLS="${ETCD_ADVERTISE_CLIENT_URLS:-http://127.0.0.1:2379}" export ETCD_INITIAL_CLUSTER="${ETCD_INITIAL_CLUSTER:-}" -export ETCD_INITIAL_CLUSTER_STATE="${ETCD_INITIAL_CLUSTER_STATE:-}" export ETCD_LISTEN_PEER_URLS="${ETCD_LISTEN_PEER_URLS:-}" export ETCD_INITIAL_ADVERTISE_PEER_URLS="${ETCD_INITIAL_ADVERTISE_PEER_URLS:-}" export ETCD_INITIAL_CLUSTER_TOKEN="${ETCD_INITIAL_CLUSTER_TOKEN:-}" diff --git a/bitnami/etcd/README.md b/bitnami/etcd/README.md index 0775371c5e9c0..13c9fbc412ad8 100644 --- a/bitnami/etcd/README.md +++ b/bitnami/etcd/README.md @@ -204,7 +204,6 @@ Apart from providing your custom configuration file, you can also modify the ser | `ETCD_LISTEN_CLIENT_URLS` | List of URLs to listen on for client traffic. | `http://0.0.0.0:2379` | | `ETCD_ADVERTISE_CLIENT_URLS` | List of this member client URLs to advertise to the rest of the cluster. | `http://127.0.0.1:2379` | | `ETCD_INITIAL_CLUSTER` | Initial list of members to bootstrap a cluster. | `nil` | -| `ETCD_INITIAL_CLUSTER_STATE` | Initial cluster state. Allowed values: "new" or "existing". | `nil` | | `ETCD_LISTEN_PEER_URLS` | List of URLs to listen on for peers traffic. | `nil` | | `ETCD_INITIAL_ADVERTISE_PEER_URLS` | List of this member peer URLs to advertise to the rest of the cluster while bootstrapping. | `nil` | | `ETCD_INITIAL_CLUSTER_TOKEN` | Unique initial cluster token used for bootstrapping. | `nil` | @@ -217,19 +216,20 @@ Apart from providing your custom configuration file, you can also modify the ser #### Read-only environment variables -| Name | Description | Value | -|-----------------------------|----------------------------------------------------------------------|------------------------------------| -| `ETCD_BASE_DIR` | etcd installation directory. | `/opt/bitnami/etcd` | -| `ETCD_VOLUME_DIR` | Persistence base directory. | `/bitnami/etcd` | -| `ETCD_BIN_DIR` | etcd executables directory. | `${ETCD_BASE_DIR}/bin` | -| `ETCD_DATA_DIR` | etcd data directory. | `${ETCD_VOLUME_DIR}/data` | -| `ETCD_CONF_DIR` | etcd configuration directory. | `${ETCD_BASE_DIR}/conf` | -| `ETCD_DEFAULT_CONF_DIR` | etcd default configuration directory. | `${ETCD_BASE_DIR}/conf.default` | -| `ETCD_TMP_DIR` | Directory where ETCD temporary files are stored. | `${ETCD_BASE_DIR}/tmp` | -| `ETCD_CONF_FILE` | ETCD configuration file. | `${ETCD_CONF_DIR}/etcd.yaml` | -| `ETCD_NEW_MEMBERS_ENV_FILE` | File containining the etcd environment to use after adding a member. | `${ETCD_DATA_DIR}/new_member_envs` | -| `ETCD_DAEMON_USER` | etcd system user name. | `etcd` | -| `ETCD_DAEMON_GROUP` | etcd system user group. | `etcd` | +| Name | Description | Value | +|------------------------------|----------------------------------------------------------------------|------------------------------------| +| `ETCD_BASE_DIR` | etcd installation directory. | `/opt/bitnami/etcd` | +| `ETCD_VOLUME_DIR` | Persistence base directory. | `/bitnami/etcd` | +| `ETCD_BIN_DIR` | etcd executables directory. | `${ETCD_BASE_DIR}/bin` | +| `ETCD_DATA_DIR` | etcd data directory. | `${ETCD_VOLUME_DIR}/data` | +| `ETCD_CONF_DIR` | etcd configuration directory. | `${ETCD_BASE_DIR}/conf` | +| `ETCD_DEFAULT_CONF_DIR` | etcd default configuration directory. | `${ETCD_BASE_DIR}/conf.default` | +| `ETCD_TMP_DIR` | Directory where ETCD temporary files are stored. | `${ETCD_BASE_DIR}/tmp` | +| `ETCD_CONF_FILE` | ETCD configuration file. | `${ETCD_CONF_DIR}/etcd.yaml` | +| `ETCD_NEW_MEMBERS_ENV_FILE` | File containining the etcd environment to use after adding a member. | `${ETCD_DATA_DIR}/new_member_envs` | +| `ETCD_DAEMON_USER` | etcd system user name. | `etcd` | +| `ETCD_DAEMON_GROUP` | etcd system user group. | `etcd` | +| `ETCD_INITIAL_CLUSTER_STATE` | Initial cluster state. Either "new" or "existing". | `nil` | Additionally, you can configure etcd using the upstream env variables [here](https://etcd.io/docs/v3.4/op-guide/configuration/) From 6e4d2a6ebbf7006a2f3c91c264a0e3b5eebe6d83 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Wed, 25 Dec 2024 16:11:01 +0700 Subject: [PATCH 06/13] [bitnami/etcd] Use etcdctl endpoint status to check whether cluster is new Signed-off-by: Khoi Pham --- .../etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index e1a850cd39c6f..038d1b235c940 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -355,7 +355,7 @@ is_new_etcd_cluster() { local -a extra_flags read -r -a extra_flags <<<"$(etcdctl_auth_flags)" is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - if ! etcdctl member list ${extra_flags[@]}; then + if ! etcdctl endpoint status --cluster ${extra_flags[@]}; then return 0 else return 1 From 71f24ce5dd1751a7e545e86e01419f5246f3a0b3 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Fri, 3 Jan 2025 16:49:19 +0700 Subject: [PATCH 07/13] [bitnami/etcd] Refactor remove_members function Signed-off-by: Khoi Pham --- .../opt/bitnami/scripts/etcd/preupgrade.sh | 23 ++++++++----------- .../rootfs/opt/bitnami/scripts/libetcd.sh | 12 +++++----- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh index b2b90ed106e62..a877f0b7a99f3 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -62,28 +62,25 @@ etcdctl_job_endpoints() { # None ######################### remove_members() { - local -r current=$(mktemp) - local -r expected=$(mktemp) - - local -a extra_flags + local -a extra_flags current expected + read -r -a extra_flags <<<"$(etcdctl_auth_flags)" is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_job_endpoints)") debug "Listing members" - if ! etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}' > $current; then + current="$(etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}')" + if [ $? -ne 0 ]; then debug "Error listing members, is this a new cluster?" return 0 fi - info "Current cluster members are: $(cat $current | awk -F, '{print $2}' | tr -s '\n' ', ' | sed 's/,$//g')" - - echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}' > $expected - info "Expected cluster members are: $(cat $expected | tr -s '\n' ', ' | sed 's/,$//g')" + info "Current cluster members are: $(echo "${current[@]}" | awk -F, '{print $2}' | tr -s '\n' ',' | sed 's/,$//g')" - for member in $(comm -23 <(cat $current | awk -F, '{print $2}' | sort) <(sort $expected)); do + expected="$(echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}')" + info "Expected cluster members are: $(IFS= echo "${expected[@]}" | tr -s '\n' ', ' | sed 's/,$//g')" + + for member in $(comm -23 <(echo "${current[@]}" | awk -F, '{print $2}' | sort) <(echo "${expected[@]}" | sort)); do info "Removing obsolete member $member" - etcdctl member remove ${extra_flags[@]} $(cat $current | grep "$member" | awk -F, '{print $1}') + etcdctl member remove ${extra_flags[@]} $(echo "${current[@]}" | grep "$member" | awk -F, '{print $1}') done - - rm -f $current $expected } remove_members \ No newline at end of file diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index 038d1b235c940..3f6b099b39b67 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -578,6 +578,7 @@ is_membership_intact() { local ret=0 tmp_file=$(mktemp) + trap "rm -f $tmp_file" RETURN am_i_root && start_command=("run_as_user" "$ETCD_DAEMON_USER" "${start_command[@]}") [[ -f "$ETCD_CONF_FILE" ]] && start_command+=("--config-file" "$ETCD_CONF_FILE") @@ -597,15 +598,14 @@ is_membership_intact() { if grep -q "the member has been permanently removed from the cluster\|ignored streaming request; ID mismatch" "$tmp_file"; then info "The remote member ID is different from the local member ID" - ret=1 + return 1 elif grep -q "\"error\":\"cluster ID mismatch\"" "$tmp_file"; then info "The remote cluster ID is different from the local cluster ID" - ret=1 - else - info "The member is still part of the cluster" + return 1 fi - rm -f "$tmp_file" - return $ret + + info "The member is still part of the cluster" + return 0 } ######################## From ff52e842f01254c2194ba8402418cfb9b9abeb3c Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Sat, 4 Jan 2025 18:28:42 +0700 Subject: [PATCH 08/13] [bitnami/etcd] Remove get_initial_cluster Signed-off-by: Khoi Pham --- .../opt/bitnami/scripts/etcd/preupgrade.sh | 31 ++----------- .../rootfs/opt/bitnami/scripts/libetcd.sh | 43 ------------------- 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh index a877f0b7a99f3..5190b6700d5d1 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -17,7 +17,7 @@ set -o nounset . /opt/bitnami/scripts/etcd-env.sh ######################## -# Obtain endpoints to connect when running 'ectdctl' in a hook job +# Return a comma separated list of : for each endpoint # Globals: # ETCD_* # Arguments: @@ -25,31 +25,8 @@ set -o nounset # Returns: # String ######################## -etcdctl_job_endpoints() { - local -a endpoints=() - local host domain port count - - # get number of endpoints from initial cluster endpoints - count="$(echo $ETCD_INITIAL_CLUSTER | awk -F, '{print NF}')" - - # This piece of code assumes this code is executed on a K8s environment - # where etcd members are part of a statefulset that uses a headless service - # to create a unique FQDN per member. Under these circumstances, the - # ETCD_ADVERTISE_CLIENT_URLS env. variable is created as follows: - # SCHEME://POD_NAME.HEADLESS_SVC_DOMAIN:CLIENT_PORT,SCHEME://SVC_DOMAIN:SVC_CLIENT_PORT - # - # Assuming this, we can extract the HEADLESS_SVC_DOMAIN and obtain - # every available endpoint - read -r -a advertised_array <<<"$(tr ',;' ' ' <<<"$ETCD_ADVERTISE_CLIENT_URLS")" - port="$(parse_uri "${advertised_array[0]}" "port")" - - for i in $(seq 0 $(($count - 1))); do - pod_name="${MY_STS_NAME}-${i}" - endpoints+=("${pod_name}.${ETCD_CLUSTER_DOMAIN}:${port:-2380}") - done - - debug "etcdctl endpoints are ${endpoints[*]}" - echo "${endpoints[*]}" | tr ' ' ',' +endpoints_as_host_port() { + echo $ETCD_INITIAL_CLUSTER | tr -s ',' '\n' | awk -F '//' '{print $2}' | tr -s '\n' ',' | sed 's/,$//' } ######################## @@ -65,7 +42,7 @@ remove_members() { local -a extra_flags current expected read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_job_endpoints)") + is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(endpoints_as_host_port)") debug "Listing members" current="$(etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}')" if [ $? -ne 0 ]; then diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index 3f6b099b39b67..ba25f98d5a555 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -227,18 +227,6 @@ etcdctl_get_endpoints() { local -a endpoints=() local host domain port - ip_has_valid_hostname() { - local ip="${1:?ip is required}" - local parent_domain="${1:?parent_domain is required}" - - # 'getent hosts $ip' can return hostnames in 2 different formats: - # POD_NAME.HEADLESS_SVC_DOMAIN.NAMESPACE.svc.cluster.local (using headless service domain) - # 10-237-136-79.SVC_DOMAIN.NAMESPACE.svc.cluster.local (using POD's IP and service domain) - # We need to discard the latter to avoid issues when TLS verification is enabled. - [[ "$(getent hosts "$ip")" = *"$parent_domain"* ]] && return 0 - return 1 - } - hostname_has_ips() { local hostname="${1:?hostname is required}" [[ "$(getent ahosts "$hostname")" != "" ]] && return 0 @@ -448,36 +436,6 @@ is_healthy_etcd_cluster() { return $return_value } -######################## -# Prints initial cluster nodes -# Globals: -# ETCD_* -# Arguments: -# None -# Returns: -# String -######################## -get_initial_cluster() { - local -a endpoints_array=() - local scheme port initial_members - read -r -a endpoints_array <<<"$(tr ',;' ' ' <<<"$ETCD_INITIAL_CLUSTER")" - if [[ ${#endpoints_array[@]} -gt 0 ]] && ! grep -sqE "://" <<<"$ETCD_INITIAL_CLUSTER"; then - # This piece of code assumes this container is used on a VM environment - # where ETCD_INITIAL_CLUSTER contains a comma-separated list of hostnames, - # and recreates it as follows: - # SCHEME://NODE_NAME:PEER_PORT - scheme="$(parse_uri "$ETCD_INITIAL_ADVERTISE_PEER_URLS" "scheme")" - port="$(parse_uri "$ETCD_INITIAL_ADVERTISE_PEER_URLS" "port")" - for nodePeer in "${endpoints_array[@]}"; do - initial_members+=("${nodePeer}=${scheme}://${nodePeer}:$port") - done - echo "${initial_members[*]}" | tr ' ' ',' - else - # Nothing to do - echo "$ETCD_INITIAL_CLUSTER" - fi -} - ######################## # Recalculate initial cluster # Globals: @@ -626,7 +584,6 @@ etcd_initialize() { # Generate user configuration if ETCD_CFG_* variables are provided etcd_setup_from_environment_variables - ETCD_INITIAL_CLUSTER="$(get_initial_cluster)" export ETCD_INITIAL_CLUSTER [[ -f "$ETCD_CONF_FILE" ]] && etcd_conf_write "initial-cluster" "$ETCD_INITIAL_CLUSTER" From 55b57e623e468403955b92f29f8375e8fe08adfa Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Sat, 4 Jan 2025 18:54:08 +0700 Subject: [PATCH 09/13] [bitnami/etcd] Remove prestop.sh Signed-off-by: Khoi Pham --- .../rootfs/opt/bitnami/scripts/etcd/prestop.sh | 12 ------------ bitnami/etcd/README.md | 8 ++++++++ 2 files changed, 8 insertions(+), 12 deletions(-) delete mode 100755 bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh deleted file mode 100755 index f9c077296b907..0000000000000 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/prestop.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# Copyright Broadcom, Inc. All Rights Reserved. -# SPDX-License-Identifier: APACHE-2.0 - -# shellcheck disable=SC1091 -set -o errexit -set -o pipefail -set -o nounset -# set -o xtrace # Uncomment this line for debugging purposes - -# This script should be removed when the chart no longer use it -return 0 diff --git a/bitnami/etcd/README.md b/bitnami/etcd/README.md index 43360a695cc9a..29f5a0f7217a0 100644 --- a/bitnami/etcd/README.md +++ b/bitnami/etcd/README.md @@ -235,6 +235,14 @@ Additionally, you can configure etcd using the upstream env variables [here](htt ## Notable Changes +### 3.5.17-debian-12-r3 + +* Drop support for non-Helm deployment. Upgrading of any kind including increasing replica count must also be done with `helm upgrade` exclusively. CD automation tools that respect Helm hooks such as ArgoCD can also be used. +* Remove `prestop.sh` script. Hence, container should no longer define lifecycle prestop hook. +* Add `preupgrade.sh` script which should be run as a pre-upgrade Helm hook. This replaces the prestop hook as a more reliable mechanism to remove stale members when replica count is decreased. +* Stop storing member ID in a local file which is unreliable. The container now check the member ID from the data dir instead. +* Stop storing/checking for member removal from a local file. The container now check with other members in the cluster instead. + ### 3.4.15-debian-10-r7 * The container now contains the needed logic to deploy the Etcd container on Kubernetes using the [Bitnami Etcd Chart](https://github.com/bitnami/charts/tree/master/bitnami/etcd). From 83d2a529846cf23074fa7ed41753f76e1fab9056 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Sat, 11 Jan 2025 14:53:40 +0700 Subject: [PATCH 10/13] [bitnami/etcd] Refactor preupgrade.sh Signed-off-by: Khoi Pham --- .../opt/bitnami/scripts/etcd/preupgrade.sh | 54 ++++++++----------- .../rootfs/opt/bitnami/scripts/libetcd.sh | 6 +-- bitnami/etcd/README.md | 2 +- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh index 5190b6700d5d1..1616767e037bc 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -18,8 +18,10 @@ set -o nounset ######################## # Return a comma separated list of : for each endpoint +# based on "initial-cluster" flag value +# ref: https://etcd.io/docs/latest/op-guide/clustering/#static # Globals: -# ETCD_* +# ETCD_INITIAL_CLUSTER # Arguments: # None # Returns: @@ -29,35 +31,21 @@ endpoints_as_host_port() { echo $ETCD_INITIAL_CLUSTER | tr -s ',' '\n' | awk -F '//' '{print $2}' | tr -s '\n' ',' | sed 's/,$//' } -######################## -# Remove members that are not named in ETCD_INITIAL_CLUSTER -# Globals: -# ETCD_* -# Arguments: -# None -# Returns: -# None -######################### -remove_members() { - local -a extra_flags current expected - - read -r -a extra_flags <<<"$(etcdctl_auth_flags)" - is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(endpoints_as_host_port)") - debug "Listing members" - current="$(etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $1 "," $3}')" - if [ $? -ne 0 ]; then - debug "Error listing members, is this a new cluster?" - return 0 - fi - info "Current cluster members are: $(echo "${current[@]}" | awk -F, '{print $2}' | tr -s '\n' ',' | sed 's/,$//g')" - - expected="$(echo $ETCD_INITIAL_CLUSTER | sed 's/,/\n/g' | awk -F= '{print $1}')" - info "Expected cluster members are: $(IFS= echo "${expected[@]}" | tr -s '\n' ', ' | sed 's/,$//g')" - - for member in $(comm -23 <(echo "${current[@]}" | awk -F, '{print $2}' | sort) <(echo "${expected[@]}" | sort)); do - info "Removing obsolete member $member" - etcdctl member remove ${extra_flags[@]} $(echo "${current[@]}" | grep "$member" | awk -F, '{print $1}') - done -} - -remove_members \ No newline at end of file +# Remove members that are not listed in ETCD_INITIAL_CLUSTER +# from the cluster before running Helm upgrades that potentially scale +# down the etcd cluster + +read -r -a extra_flags <<<"$(etcdctl_auth_flags)" +is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(endpoints_as_host_port)") +debug "Listing members" +if ! current="$(etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $3 ":" $1}')"; then + debug "Error listing members, is this a new cluster?" + exit 0 +fi +info "Current cluster members are: $(echo "$current" | awk -F: '{print $1}' | tr -s '\n' ',' | sed 's/,$//g')" +expected="$(echo $ETCD_INITIAL_CLUSTER | tr -s ',' '\n' | awk -F= '{print $1}')" +info "Expected cluster members are: $(echo "$expected" | tr -s '\n' ',' | sed 's/,$//g')" +for member in $(comm -23 <(echo "$current" | awk -F: '{print $1}' | sort) <(echo "$expected" | sort)); do + info "Removing obsolete member $member" + etcdctl member remove ${extra_flags[@]} "$(echo "$current" | grep "$member" | awk -F: '{print $2}')" +done \ No newline at end of file diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index ba25f98d5a555..35eb6de6dbd50 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -343,11 +343,7 @@ is_new_etcd_cluster() { local -a extra_flags read -r -a extra_flags <<<"$(etcdctl_auth_flags)" is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(etcdctl_get_endpoints)") - if ! etcdctl endpoint status --cluster ${extra_flags[@]}; then - return 0 - else - return 1 - fi + ! debug_execute etcdctl endpoint status --cluster ${extra_flags[@]} } ######################## diff --git a/bitnami/etcd/README.md b/bitnami/etcd/README.md index 29f5a0f7217a0..92ed0c6714a28 100644 --- a/bitnami/etcd/README.md +++ b/bitnami/etcd/README.md @@ -237,7 +237,7 @@ Additionally, you can configure etcd using the upstream env variables [here](htt ### 3.5.17-debian-12-r3 -* Drop support for non-Helm deployment. Upgrading of any kind including increasing replica count must also be done with `helm upgrade` exclusively. CD automation tools that respect Helm hooks such as ArgoCD can also be used. +* Drop support for non-Helm cluster deployment. Upgrading of any kind including increasing replica count must also be done with `helm upgrade` exclusively. CD automation tools that respect Helm hooks such as ArgoCD can also be used. * Remove `prestop.sh` script. Hence, container should no longer define lifecycle prestop hook. * Add `preupgrade.sh` script which should be run as a pre-upgrade Helm hook. This replaces the prestop hook as a more reliable mechanism to remove stale members when replica count is decreased. * Stop storing member ID in a local file which is unreliable. The container now check the member ID from the data dir instead. From 9cd12beb942303e330c62546f38726a4a583d261 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Tue, 14 Jan 2025 17:36:23 +0700 Subject: [PATCH 11/13] [bitnami/etcd] Remove mention of ETCD_INITIAL_CLUSTER_STATE from README Signed-off-by: Khoi Pham --- bitnami/etcd/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/bitnami/etcd/README.md b/bitnami/etcd/README.md index 92ed0c6714a28..c096387dfd3b7 100644 --- a/bitnami/etcd/README.md +++ b/bitnami/etcd/README.md @@ -229,7 +229,6 @@ Apart from providing your custom configuration file, you can also modify the ser | `ETCD_NEW_MEMBERS_ENV_FILE` | File containining the etcd environment to use after adding a member. | `${ETCD_DATA_DIR}/new_member_envs` | | `ETCD_DAEMON_USER` | etcd system user name. | `etcd` | | `ETCD_DAEMON_GROUP` | etcd system user group. | `etcd` | -| `ETCD_INITIAL_CLUSTER_STATE` | Initial cluster state. Either "new" or "existing". | `nil` | Additionally, you can configure etcd using the upstream env variables [here](https://etcd.io/docs/v3.4/op-guide/configuration/) From 9d199a809805e3a863ec07de3d8f3934f11e393b Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Thu, 16 Jan 2025 09:24:03 +0700 Subject: [PATCH 12/13] [bitnami/etcd] Fail preupgrade hook if members cannot be listed Signed-off-by: Khoi Pham --- .../opt/bitnami/scripts/etcd/preupgrade.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh index 1616767e037bc..c0722d667a6ae 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -39,13 +39,20 @@ read -r -a extra_flags <<<"$(etcdctl_auth_flags)" is_boolean_yes "$ETCD_ON_K8S" && extra_flags+=("--endpoints=$(endpoints_as_host_port)") debug "Listing members" if ! current="$(etcdctl member list ${extra_flags[@]} --write-out simple | awk -F ", " '{print $3 ":" $1}')"; then - debug "Error listing members, is this a new cluster?" - exit 0 + error "Unable to list members, are all members healthy?" + exit 1 fi info "Current cluster members are: $(echo "$current" | awk -F: '{print $1}' | tr -s '\n' ',' | sed 's/,$//g')" + expected="$(echo $ETCD_INITIAL_CLUSTER | tr -s ',' '\n' | awk -F= '{print $1}')" info "Expected cluster members are: $(echo "$expected" | tr -s '\n' ',' | sed 's/,$//g')" -for member in $(comm -23 <(echo "$current" | awk -F: '{print $1}' | sort) <(echo "$expected" | sort)); do - info "Removing obsolete member $member" - etcdctl member remove ${extra_flags[@]} "$(echo "$current" | grep "$member" | awk -F: '{print $2}')" -done \ No newline at end of file +read -r -a obsolete_members <<<"$(comm -23 <(echo "$current" | awk -F: '{print $1}' | sort) <(echo "$expected" | sort))" +if [ ${#obsolete_members[@]} -eq 0 ]; then + info "No obsolete members to remove." +else + for member in $obsolete_members; do + info "Removing obsolete member $member" + etcdctl member remove ${extra_flags[@]} "$(echo "$current" | grep "$member" | awk -F: '{print $2}')" + done +fi +info "Pre-upgrade checks completed!" \ No newline at end of file From 735bf0a158e71f100b4f9a588cc3c4052a1492a0 Mon Sep 17 00:00:00 2001 From: Khoi Pham Date: Sat, 18 Jan 2025 13:29:54 +0700 Subject: [PATCH 13/13] [bitnami/etcd] Fix preupgrade obsolete members loop Signed-off-by: Khoi Pham --- .../rootfs/opt/bitnami/scripts/etcd/preupgrade.sh | 5 +++-- .../3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh | 9 +++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh index c0722d667a6ae..9bd3f5f2c3780 100755 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/etcd/preupgrade.sh @@ -46,11 +46,12 @@ info "Current cluster members are: $(echo "$current" | awk -F: '{print $1}' | tr expected="$(echo $ETCD_INITIAL_CLUSTER | tr -s ',' '\n' | awk -F= '{print $1}')" info "Expected cluster members are: $(echo "$expected" | tr -s '\n' ',' | sed 's/,$//g')" -read -r -a obsolete_members <<<"$(comm -23 <(echo "$current" | awk -F: '{print $1}' | sort) <(echo "$expected" | sort))" + +read -r -a obsolete_members <<<"$(comm -23 <(echo "$current" | awk -F: '{print $1}' | sort) <(echo "$expected" | sort) | tr -s '\n' ' ')" if [ ${#obsolete_members[@]} -eq 0 ]; then info "No obsolete members to remove." else - for member in $obsolete_members; do + for member in "${obsolete_members[@]}"; do info "Removing obsolete member $member" etcdctl member remove ${extra_flags[@]} "$(echo "$current" | grep "$member" | awk -F: '{print $2}')" done diff --git a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh index 35eb6de6dbd50..edb6ef3819572 100644 --- a/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh +++ b/bitnami/etcd/3.5/debian-12/rootfs/opt/bitnami/scripts/libetcd.sh @@ -536,15 +536,12 @@ is_membership_intact() { am_i_root && start_command=("run_as_user" "$ETCD_DAEMON_USER" "${start_command[@]}") [[ -f "$ETCD_CONF_FILE" ]] && start_command+=("--config-file" "$ETCD_CONF_FILE") - $start_command > >(tee -a "$tmp_file") 2>&1 & - pid=$! - debug "Started etcd in background with PID $pid" + "${start_command[@]}" > "$tmp_file" 2>&1 & while read -r line; do - echo "$line" # Stream the output + debug_execute echo "$line" if [[ "$line" =~ (established TCP streaming connection with remote peer|the member has been permanently removed from the cluster|ignored streaming request; ID mismatch|\"error\":\"cluster ID mismatch\") ]]; then - kill "$pid" - wait "$pid" 2>/dev/null + etcd_stop debug "Stopped etcd" break fi