Skip to content

Instantly share code, notes, and snippets.

@mechamogeo
Created July 8, 2020 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mechamogeo/e80fd0e1e689063dcae32139612de6a1 to your computer and use it in GitHub Desktop.
Save mechamogeo/e80fd0e1e689063dcae32139612de6a1 to your computer and use it in GitHub Desktop.
Simple CLI to manage soft scaling in Kubernetes using HPA Methods
#!/bin/bash
NAME="HPA Soft Scaling CLI"
DESCRIPTION="Simple to use HPA (Horizontal Pods Autoscaling) manager to Kubernetes, Creation of Geovani Perez França"
VERSION=0.0.2
# UTILS FUNCTIONS #################################################################################
function get_hpa_description() {
unset hpa_describe pods_min pods_max pods_actual
# Of the output filter only Namespace, Min replicas, Max replicas and Deployment pods and transform to arguments
hpa_describe=`kubectl describe hpa $namespace $_service --kubeconfig=${config} | egrep "Namespace:|Min replicas:|Max replicas:|Deployment pods:" | xargs`
# Some verifications if the kubectl has response of describe
if [[ -z "$hpa_describe" ]]; then
echo -e "\033[0;31mThe service <$service> is not reachable with config file <$config>\033[0m"
return
fi
hpa_describe=`echo $hpa_describe | sed 's/Namespace://g' | sed 's/Min\ replicas://g' | sed 's/Max\ replicas://g' | sed 's/Deployment\ pods://g' | sed 's/current \///g' | sed 's/desired//g'`
# If namespace is default -A redefines to real namespace of application in cluster reached
if [[ "$namespace" == "-A" ]]; then
namespace=`echo $hpa_describe | awk '{ print $1 }'`
fi
# Set variable to actual values in service details
pods_min=`echo $hpa_describe | awk '{ print $2 }'`
pods_max=`echo $hpa_describe | awk '{ print $3 }'`
pods_actual=`echo $hpa_describe | awk '{ print $4 }'`
}
function mark_done_line() {
# Escape backslash, forward slash and ampersand for use as a sed replacement
replacement=$( echo "$service,$min,$max,$config,true" | sed -e 's/[\/&]/\\&/g' )
sed -i "${line}s/.*/$replacement/" "$file"
}
# COMMAND PARSING #################################################################################
cmd="${1}" ; shift
# SUBCOMMAND PARSING ##############################################################################
case "${cmd}${1}" in
status|up|down|scale)
sub=${1} ; shift
;;
esac
# ARGUMENT AND OPTION PARSING #####################################################################
while (( "$#" )); do
case "${1}" in
--trace|-x) trace='set -x' ; shift ;;
--service=*) service=${1/--service=/''} ; shift ;;
--service*|-s*) service=${2} ; shift ; shift ;;
--namespace=*) namespace="-n ${1/--namespace=/''}" ; shift ;;
--namespace*|-n*) namespace="-n ${2}" ; shift ; shift ;;
--config=*) config="${1/--config=/''}" ; shift ;;
--config*|-c*) config="${2}" ; shift ; shift ;;
--min=*) min=${1/--min=/''} ; shift ;;
--min*) min=${2} ; shift ; shift ;;
--max=*) max=${1/--max=/''} ; shift ;;
--max*) max=${2} ; shift ; shift ;;
--csv=*) file=${1/--csv=/''} ; shift ;;
--csv*) file=${2} ; shift ; shift ;;
esac
done
# TRACING #########################################################################################
${trace}
# COMMAND DEFINITIONS #############################################################################
function status() {
if [[ "$pods_min" ]]; then
echo "Status of ${service}:"
echo "- Current: ${pods_actual}";
echo "- Min: ${pods_min}";
echo "- Max: ${pods_max}";
fi
}
function scale() {
local from_min=$pods_min
local from_max=$pods_max
has_finished=false
while [ "$has_finished" = false ]; do
# Verify if the actual min is greatter then new min
if [[ "$pods_min" -gt "$min" ]]; then
# Subtract 1 of actual min and apply in the service removing this way one once at time to get the same value of new min
((pods_min=pods_min-1))
`kubectl -n $namespace patch hpa $service --kubeconfig=$config --patch '{"spec":{"minReplicas":'$pods_min', "maxReplicas":'$pods_max'}}'`
# If the min is the same of the actual min in pod make the change of max
else
`kubectl -n $namespace patch hpa $service --kubeconfig=$config --patch '{"spec":{"minReplicas":'$min', "maxReplicas":'$max'}}'`
has_finished=true
echo -e "\033[92mThe service $service was updated to Min ( From: $from_min / To: $min ) and Max ( From: $from_max / To: $max )\033[0m"
# Verify if the process is running on csv and mark column done with true
if [ "$line" ]; then
mark_done_line
fi
fi
sleep 5
done
unset has_finished
}
function up() {
local continue_process=1
local want_continue
# Verify if the new max is < actual max ;; the process is to upscale the pods not downscale
if [ "$max" -lt "$pods_max" ]; then
echo -e "\033[33mThe new max ($max) is greater than actual max ($pods_max)\033[0m"
continue_process=0
fi
# Verify if the new min is < actual min ;; the process is to upscale the pods not downscale
if [ "$min" -lt "$pods_min" ]; then
echo -e "\033[33mThe new min ($min) is greater than actual min ($pods_min)\033[0m"
continue_process=0
fi
if [ "$continue_process" -eq 0 ]; then
[[ -z "${want_continue}" ]] && read -e -p 'You ran the upscalling process but the min/max to change is lower than actual, do you want continue? [Y/n]: ' want_continue
case ${want_continue,,} in
y|s) continue_process=1;;
n) continue_process=0;;
*) continue_process=0;;
esac
# Show the line skipped in the csv
if [[ "$line" ]]; then
echo -e "\033[33mSkipping the process of line $line in CSV\033[0m"
fi
fi
# Continue the process verification
if [ "$continue_process" -eq 1 ]; then
scale
fi
}
function down() {
local continue_process=1
local want_continue
# Verify if the new min is > actual min ;; the process is to downscale the pods not upscale
if [ "$min" -gt "$pods_min" ]; then
echo -e "\033[33mThe new min ($min) is greater than actual min ($pods_min)\033[0m"
continue_process=0
fi
if [ "$continue_process" -eq 0 ]; then
[[ -z "${want_continue}" ]] && read -e -p 'You ran the downscaling process but the min/max to change is upper than actual, do you want continue? [Y/n]: ' want_continue
case ${want_continue,,} in
y|s) continue_process=1;;
n) continue_process=0;;
*) continue_process=0;;
esac
# Show the line skipped in the csv
if [[ "$line" ]]; then
echo -e "\033[33mSkipping the process of line $line in CSV\033[0m"
fi
fi
# Continue the process verification
if [ "$continue_process" -eq 1 ]; then
scale
fi
}
function prepare_csv() {
line=$1; shift; # Get the line of CSV
service=$1; shift; #Get the service to scaling
min=$1; shift; # Get the minimum of pods in scaling
max=$1; shift; # Get the maximum of pods in scaling
config=$1; shift; # Get the Configuration File in Scaling
done=$1; shift; # Get if this process is already done before or not
[ -z "$config" ] && undefined_vars+=('Config')
[ -z "$service" ] && undefined_vars+=('Service') || namespace="-A" && _service="-l name=$service" || _service="$service"
[ -z "$min" ] && undefined_vars+=('Min')
[ -z "$max" ] && undefined_vars+=('Max')
[ -z "$done" ] && undefined_vars+=('Done')
if [[ "$undefined_vars" ]]; then
echo -e "\033[0;31m[${undefined_vars[*]}] is not defined\033[0m at line $line of CSV"
echo
unset undefined_vars
else
# Switch min and max if the min is > than max
if [ "$min" -gt "$max" ]; then
echo "Min ($min) / Max ($max)"
echo -e "\033[0;34mThe min was > than max, in this case we switch the min and max\033[0m"
aux=$max
max=$min
min=$aux
unset aux
fi
# Get details of HPA for the line of CSV
get_hpa_description
# If the service can be reached in cluster, run command
if [[ "$hpa_describe" ]]; then
${cmd}${sub}
fi
echo
fi
}
function read_csv() {
local i=0
while IFS=, read -ra line
do
# Increment line
((i=i+1))
# Verify if is head line and jump
test $i -eq 1 && continue
# Test if done is true if is jump
local is_done=`echo ${line[@]} | awk '{ print $5 }'` || false
test $is_done == "true" && continue
# call the method and save variables from csv
prepare_csv ${i} ${line[@]};
done < "$file"
}
function version() {
echo "${VERSION}"
}
# DEFAULT ARGUMENTS DEFINITION ####################################################################
case "${cmd}${sub}" in
status|up|down|scale)
# Verify if the --csv has in use
if [[ "$file" ]]; then
# Verify if file exists
if [[ ! -e "$file" ]]; then
echo -e "\033[0;31mFile $(basename -- $file) in $(dirname -- $file) not exists\033[0m";
echo -e "See how to define using \033[1m--help\033[0m or \033[1m-h\033[0m"
exit 1
# Verify if extension file is csv
elif [ ${file##*.} != "csv" ]; then
echo -e "\033[0;31mFile $(basename -- $file) is not a csv file\033[0m";
echo -e "See how to define using \033[1m--help\033[0m or \033[1m-h\033[0m"
exit 1
fi
# Read CSV and apply the command in each line of csv
read_csv
else
[ -z "$config" ] && echo -e "\033[0;31mConfig File is not defined [-c, --config]\033[0m" && undefined_vars=1; # Exit when config file is undefined
[ -z "$service" ] && echo -e "\033[0;31mService is not defined [-s, --service]\033[0m" && undefined_vars=1; # Exit when service is undefined
if [[ "$cmd" != "status" ]]; then
[ -z "$min" ] && echo -e "\033[0;31mMin is not defined [--min]\033[0m" && undefined_vars=1; # Exit when min is undefined
[ -z "$max" ] && echo -e "\033[0;31mMax is not defined [--max]\033[0m" && undefined_vars=1; # Exit when max is undefined
fi
if [[ -z "$undefined_vars" ]]; then
# Switch min and max if the min is > than max
if [ "$min" -gt "$max" ]; then
echo -e "\033[0;34mThe min was > than max, in this case we switch the min and max\033[0m"
aux=$max
max=$min
min=$aux
unset aux
fi
[ -z "$namespace" ] && namespace="-A" && _service="-l name=$service" || _service="$service" # Case the namespace is not defined the namespace is all "-A" and the _service is the label name definition "-l name=<service>" otherwise the _service receives the service variable
[ -z "$hpa_describe"] && get_hpa_description # Call to get HPA at start of cli executation to get Min/Max/Current of service
fi
if [[ "$undefined_vars" ]]; then
echo
echo -e "See how to define using \033[1m--help\033[0m or \033[1m-h\033[0m"
exit 1;
fi
fi
;;
esac
# HELP DECLARATION ################################################################################
function help() {
local a=(${0//\// })
local bin=${a[${#a[@]}-1]}
echo "${NAME}"
echo "${DESCRIPTION}"
echo
echo "Usage:"
echo " \$ ${bin} status [-s|--service <s>]"
echo " \$ ${bin} up [-s|--service <s>] [--min <n>] [--max <n>]"
echo " \$ ${bin} down [-s|--service <s>] [--min <n>] [--max <n>]"
echo " \$ ${bin} scale [-s|--service <s>] [--min <n>] [--max <n>]"
echo " \$ ${bin} status --csv <file.csv>"
echo
echo "Commands:"
echo " status Get the actual status of the service, how many is the maximum/minimum/actual scalling at service"
echo " up Set to upscaling the pods, if the min/max is bellow of actual do nothing"
echo " down Set to downscaling the pods, if the min/max is above of actual do nothing"
echo " scale Do down/up depending of the actual state of pods"
echo
echo "Options:"
echo " --csv Do process running defined configuration into csv file"
echo " -c, --config Set the config file to access rancher (required if is used status/up/down/go commands)"
echo " -s, --service The application name of service to scalling (required if is used status/up/down/go commands)"
echo
echo " -n, --namespace Set the namespace in rancher (default is all namespaces)"
echo " --min Set the minimum of pods scaling (required if is used up/down/go commands)"
echo " --max Set the maximum of pods scaling (required if is used up/down/go commands)"
echo
echo " -x, --trace Debug the application to trace failures"
echo " -h, --help Show this help summary"
echo " -v, --version Show the actual version of ${bin} program"
echo
echo "CSV Columns:"
echo " 0. service Service to scalling"
echo " 1. min Minimum of pods scaling"
echo " 2. max Maximum of pods scaling"
echo " 3. config Config file to access rancher"
echo " 4. done Set if the process is done (if done is true the process will do nothing)"
echo
}
# COMMAND ROUTING #################################################################################
case "${cmd}${sub}" in
--help|-h) help ; exit 0 ;;
--version|-v) version ; exit 0 ;;
status|up|down|scale)
# Verify if is not csv input call function
if [[ -z "$file" ]]; then
${cmd}${sub} ; exit $?
fi
;;
*) help ; exit 1 ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment