#!/bin/bash # # This script aims to manage k8s clusters created by 'kubeasz'. (developing) set -o nounset set -o errexit #set -o xtrace function usage() { cat < context, or create it if not existed destroy To destroy the current cluster, with '--purge' option to also delete the context list To list all of clusters managed setup To setup a cluster using the current context start-aio To quickly setup an all-in-one cluster for testing (like minikube) In-cluster operation: add-etcd To add a etcd-node to the etcd cluster add-master To add a kube-master(master node) to the k8s cluster add-node To add a kube-node(work node) to the k8s cluster clean-node To clean a node, whatever role the node plays del-etcd To delete a etcd-node from the etcd cluster upgrade To upgrade the k8s cluster Extra operation: basic-auth To enable/disable basic-auth for apiserver Use "easzctl help " for more information about a given command. EOF } function help-info() { case "$1" in (add-node) echo -e "Usage: easzctl add-node \n\nread 'https://github.com/easzlab/kubeasz/blob/master/docs/op/AddNode.md'" ;; (add-master) echo -e "Usage: easzctl add-master \n\nread 'https://github.com/easzlab/kubeasz/blob/master/docs/op/AddMaster.md'" ;; (add-etcd) echo -e "Usage: easzctl add-etcd \n\nread 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-etcd.md'" ;; (del-etcd) echo -e "Usage: easzctl del-etcd \n\nread 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-etcd.md'" ;; (clean-node) echo -e "Usage: easzctl clean-node \n\nread 'https://github.com/easzlab/kubeasz/blob/master/docs/op/clean_one_node.md'" ;; (basic-auth) echo -e "Usage: easzctl basic-auth \nOption:\t -s enable basic-auth\n\t -S disable basic-auth\n\t -u set username\n\t -p set password" ;; (*) usage return 0 ;; esac } function process_cmd() { echo -e "[INFO] \033[33m$ACTION\033[0m : $CMD" $CMD || { echo -e "[ERROR] \033[31mAction failed\033[0m : $CMD"; return 1; } echo -e "[INFO] \033[32mAction successed\033[0m : $CMD" } ### in-cluster operation functions ############################## function add-node() { # check new node's address regexp [[ $1 =~ ^(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})(\.(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})){3}$ ]] || { echo "[ERROR] Invalid ip address!"; return 1; } # check if the new node already exsited sed -n '/^\[kube-master/,/^\[harbor/p' $BASEPATH/hosts|grep "^$1" && { echo "[ERROR] node $1 already existed!"; return 2; } # add a node into 'kube-node' group sed -i "/\[kube-node/a $1 NEW_NODE=yes" $BASEPATH/hosts # check if playbook runs successfully ansible-playbook $BASEPATH/tools/20.addnode.yml -e NODE_TO_ADD=$1 || { sed -i "/$1 NEW_NODE=yes/d" $BASEPATH/hosts; return 2; } # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } function add-master() { # check new master's address regexp [[ $1 =~ ^(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})(\.(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})){3}$ ]] || { echo "[ERROR] Invalid ip address!"; return 2; } # check if k8s with DPLOY_MODE='multi-master' grep '^DEPLOY_MODE=multi-master' $BASEPATH/hosts || { echo "[ERROR] only k8s with DPLOY_MODE='multi-master' can have master node added!"; return 2; } # check if the new master already exsited sed -n '/^\[kube-master/,/^\[kube-node/p' $BASEPATH/hosts|grep "^$1" && { echo "[ERROR] master $1 already existed!"; return 2; } # add a node into 'kube-master' group sed -i "/\[kube-master/a $1 NEW_MASTER=yes" $BASEPATH/hosts # check if playbook runs successfully ansible-playbook $BASEPATH/tools/21.addmaster.yml -e NODE_TO_ADD=$1 || { sed -i "/$1 NEW_MASTER=yes/d" $BASEPATH/hosts; return 2; } # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } function add-etcd() { # check new node's address regexp [[ $1 =~ ^(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})(\.(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})){3}$ ]] || { echo "[ERROR] Invalid ip address!"; return 2; } # check if the new node already exsited sed -n '/^\[etcd/,/^\[kube-master/p' $BASEPATH/hosts|grep "^$1" && { echo "[ERROR] node $1 already existed!"; return 2; } # input an unique NODE_NAME of the node in etcd cluster echo "Please input an UNIQUE name(string) for the new node: " read -t15 NAME sed -n '/^\[etcd/,/^\[kube-master/p' $BASEPATH/hosts|grep "$NAME" && { echo "[ERROR] name [$NAME] already existed!"; return 2; } # add a node into 'kube-node' group sed -i "/\[etcd/a $1 NODE_NAME=$NAME" $BASEPATH/hosts # check if playbook runs successfully ansible-playbook $BASEPATH/tools/19.addetcd.yml -e NODE_TO_ADD=$1 || { sed -i "/$1 NODE_NAME=$NAME/d" $BASEPATH/hosts; return 2; } # restart apiservers to use the new etcd cluster ansible-playbook $BASEPATH/04.kube-master.yml -t restart_master || { echo "[ERROR] Unexpected failures in master nodes!"; return 2; } # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } function del-etcd() { # check node's address regexp [[ $1 =~ ^(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})(\.(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})){3}$ ]] || { echo "[ERROR] Invalid ip address!"; return 2; } # ansible-playbook $BASEPATH/tools/remove_etcd_node.yml -e ETCD_TO_DEL=$1 # restart apiservers to use the new etcd cluster ansible-playbook $BASEPATH/04.kube-master.yml -t restart_master || { echo "[ERROR] Unexpected failures in master nodes!"; return 2; } # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } function clean-node() { # check node's address regexp [[ $1 =~ ^(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})(\.(2(5[0-5]{1}|[0-4][0-9]{1})|[0-1]?[0-9]{1,2})){3}$ ]] || { echo "[ERROR] Invalid ip address!"; return 2; } # ansible-playbook $BASEPATH/tools/clean_one_node.yml -e NODE_TO_DEL=$1 # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } function upgrade() { echo -e "[INFO] prepare the new binaries in advance" echo -e "[INFO] upgrade begin in 5s, press any key to abort\n:" ! (read -t5 -n1 ANS) || { echo "[WARN] upgrade aborted"; return 1; } ansible-playbook -t upgrade_k8s $BASEPATH/22.upgrade.yml || return 1 [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } ### cluster-wide operation functions ############################ function save_context() { [ -f "$BASEPATH/.cluster/current_cluster" ] || { echo "[WARN] Invalid Context"; return 0; } CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) echo "[INFO] save context: $CLUSTER" echo "[INFO] save $CLUSTER roles' configration" for ROLE in $(ls $BASEPATH/roles); do if [ -d "$BASEPATH/roles/$ROLE/defaults" ]; then mkdir -p $BASEPATH/.cluster/$CLUSTER/roles/$ROLE/defaults/ cp -fpr $BASEPATH/roles/$ROLE/defaults/* $BASEPATH/.cluster/$CLUSTER/roles/$ROLE/defaults/ fi done if [ -f "$BASEPATH/hosts" ];then echo "[INFO] save $CLUSTER ansible hosts" cp -fp $BASEPATH/hosts $BASEPATH/.cluster/$CLUSTER/ fi if [ -f /root/.kube/config ];then echo "[INFO] save $CLUSTER kubeconfig" cp -fp /root/.kube/config $BASEPATH/.cluster/$CLUSTER/ fi } function install_context() { [ -f "$BASEPATH/.cluster/current_cluster" ] || { echo "[ERROR] Invalid Context"; return 1; } CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) echo "[INFO] install context: $CLUSTER" echo "[INFO] install $CLUSTER roles' configration" for ROLE in $(ls $BASEPATH/.cluster/$CLUSTER/roles); do cp -fp $BASEPATH/.cluster/$CLUSTER/roles/$ROLE/defaults/* $BASEPATH/roles/$ROLE/defaults/ done if [ -f "$BASEPATH/.cluster/$CLUSTER/hosts" ];then echo "[INFO] install $CLUSTER ansible hosts" cp -fp $BASEPATH/.cluster/$CLUSTER/hosts $BASEPATH/ fi if [ -f "$BASEPATH/.cluster/$CLUSTER/config" ];then echo "[INFO] install $CLUSTER kubeconfig" cp -fp $BASEPATH/.cluster/$CLUSTER/config /root/.kube/ fi } function checkout() { # check directory '.cluster', initialize it if not existed if [ ! -d "$BASEPATH/.cluster" ]; then echo "[INFO] initialize directory $BASEPATH/.cluster" mkdir -p $BASEPATH/.cluster/default echo default > $BASEPATH/.cluster/current_cluster fi # check if $1 is already the current context CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) [ "$1" != "$CLUSTER" ] || { echo "[WARN] $1 is already the current context"; return 0; } # save context of the current cluster echo "[INFO] save current context: $CLUSTER" save_context echo "[INFO] clean context: $CLUSTER" rm -rf $BASEPATH/hosts /root/.kube/* # check context $1, install it if existed, otherwise initialize it using default context if [ ! -d "$BASEPATH/.cluster/$1" ];then echo "[INFO] context $1 not existed, initialize it using default context" cp -rp $BASEPATH/.cluster/default $BASEPATH/.cluster/$1 rm -f $BASEPATH/.cluster/$1/hosts $BASEPATH/.cluster/$1/config fi echo "[INFO] change current context to $1" echo $1 > $BASEPATH/.cluster/current_cluster install_context; } function setup() { [ -d "$BASEPATH/.cluster" ] || { echo "[ERROR] invalid context, run 'easzctl checkout ' first"; return 1; } [ -f "$BASEPATH/bin/kube-apiserver" ] || { echo "[ERROR] no binaries found, download then fist"; return 1; } [ -f "$BASEPATH/hosts" ] || { echo "[ERROR] no ansible hosts found, read 'docs/setup/00-planning_and_overall_intro.md'"; return 1; } CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) echo -e "\n[INFO] setup cluster with context: $CLUSTER" echo -e "[INFO] setup begin in 5s, press any key to abort\n:" ! (read -t5 -n1 ANS) || { echo "[WARN] setup aborted"; return 1; } ansible-playbook $BASEPATH/90.setup.yml || return 1 save_context } function list() { [ -d "$BASEPATH/.cluster" ] || { echo "[ERROR] invalid context, run 'easzctl checkout ' first"; return 1; } CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) echo -e "\nlist of managed contexts (current: $CLUSTER)" i=1; for Cluster in $(ls $BASEPATH/.cluster/ |grep -v current_cluster); do echo -e "==> context $i:\t$Cluster" let "i++" done echo -e "\nlist of installed clusters (current: $CLUSTER)" i=1; for Cluster in $(ls $BASEPATH/.cluster/ |grep -v current_cluster); do KUBECONF=$BASEPATH/.cluster/$Cluster/config if [ -f "$KUBECONF" ]; then echo -e "==> cluster $i:\t$Cluster" $BASEPATH/bin/kubectl --kubeconfig=$KUBECONF get node fi let "i++" done } function destroy() { [ -d "$BASEPATH/.cluster" ] || { echo "[ERROR] invalid context, run 'easzctl checkout ' first"; return 1; } CLUSTER=$(cat $BASEPATH/.cluster/current_cluster) echo -n "[WARN] DELETE cluster: $CLUSTER, Continue? (y/n): " read -t10 -n1 ANS || { echo -e "\n[WARN] timeout, destroy aborted"; return 1; } if [[ -n $ANS && $ANS == y ]];then echo -e "\n[INFO] clean all nodes of cluster in 5s" sleep 5 ansible-playbook $BASEPATH/99.clean.yml rm -f $BASEPATH/.cluster/$CLUSTER/config [ "$#" -gt 0 ] || { return 0; } if [[ -n $1 && $1 == --purge ]];then echo "[INFO] delete current context" rm -rf $BASEPATH/.cluster/$CLUSTER rm -rf $BASEPATH/hosts /root/.kube/* echo "[INFO] change current context to default" echo default > $BASEPATH/.cluster/current_cluster install_context fi else echo -e "\n[WARN] destroy aborted"; return 1; fi } function start-aio(){ checkout aio set +u # Check ENV 'HOST_IP', if exist indecates running in a docker container, otherwise running in a host machine if [[ -z $HOST_IP ]];then # easzctl runs in a host machine, get host's ip HOST_IF=$(ip route|grep default|cut -d' ' -f5) HOST_IP=$(ip a|grep "$HOST_IF$"|awk '{print $2}'|cut -d'/' -f1) fi set -u cp -f $BASEPATH/example/hosts.allinone.example.en $BASEPATH/hosts sed -i "s/192.168.1.1/$HOST_IP/g" $BASEPATH/hosts setup } ### extra operation functions ################################### function basic-auth(){ OPTIND=2 CONFIG=$BASEPATH/roles/kube-master/defaults/main.yml EX_VARS="" while getopts "sSu:p:" OPTION; do case $OPTION in s) EX_VARS="BASIC_AUTH_ENABLE=yes $EX_VARS" ENABLED=yes ;; S) grep BASIC_AUTH_ENABLE $CONFIG|grep no > /dev/null && \ { echo -e "\n[WARN]basic-auth already disabled!\n"; return 1; } EX_VARS="BASIC_AUTH_ENABLE=no $EX_VARS" ENABLED=no ;; u) EX_VARS="BASIC_AUTH_USER=$OPTARG $EX_VARS" sed -i "s/BASIC_AUTH_USER.*$/BASIC_AUTH_USER: '$OPTARG'/g" $CONFIG ;; p) EX_VARS="BASIC_AUTH_PASS=$OPTARG $EX_VARS" sed -i "s/BASIC_AUTH_PASS.*$/BASIC_AUTH_PASS: '$OPTARG'/g" $CONFIG ;; ?) help-info basic-auth return 1 ;; esac done ansible-playbook $BASEPATH/04.kube-master.yml -t restart_master -e "$EX_VARS" || { return 1; } sed -i "s/BASIC_AUTH_ENABLE.*$/BASIC_AUTH_ENABLE: '$ENABLED'/g" $CONFIG if [[ $ENABLED == yes ]];then echo -e "\n[INFO]basic-auth for apiserver is enabled!" sed -n '/BASIC_AUTH_USER/p' $CONFIG sed -n '/BASIC_AUTH_PASS/p' $CONFIG elif [[ $ENABLED == no ]];then echo -e "\n[INFO]basic-auth for apiserver is disabled!\n" fi # save current cluster context if needed [ -f "$BASEPATH/.cluster/current_cluster" ] && save_context return 0 } ### Main Lines ################################################## BASEPATH=/etc/ansible [ "$#" -gt 0 ] || { usage >&2; exit 2; } case "$1" in ### in-cluster operations ##################### (add-node) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: add a k8s work node" CMD="add-node $2" ;; (add-master) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: add a k8s master node" CMD="add-master $2" ;; (add-etcd) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: add a etcd node" CMD="add-etcd $2" ;; (del-etcd) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: delete a etcd node" CMD="del-etcd $2" ;; (clean-node) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: clean a node" CMD="clean-node $2" ;; (upgrade) ACTION="Action: upgrade the cluster" CMD="upgrade" ;; ### cluster-wide operations ####################### (checkout) [ "$#" -gt 1 ] || { usage >&2; exit 2; } ACTION="Action: checkout cluster context" CMD="checkout $2" ;; (destroy) ACTION="Action: destroy current cluster" if [ "$#" -gt 1 ];then CMD="destroy $2" else CMD="destroy" fi ;; (list) ACTION="Action: list all of clusters managed" CMD="list" ;; (setup) ACTION="Action: setup cluster with current context" CMD="setup" ;; (start-aio) ACTION="Action: start an AllInOne cluster" CMD="start-aio" ;; (help) [ "$#" -gt 1 ] || { usage >&2; exit 2; } help-info $2 exit 0 ;; ### extra operations ############################## (basic-auth) [ "$#" -gt 1 ] || { help-info $1; exit 2; } ACTION="Action: enable/disable apiserver's basic-auth" CMD="basic-auth $*" ;; (*) usage exit 0 ;; esac process_cmd