Created
July 8, 2020 02:10
-
-
Save mechamogeo/e80fd0e1e689063dcae32139612de6a1 to your computer and use it in GitHub Desktop.
Simple CLI to manage soft scaling in Kubernetes using HPA Methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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