#!/bin/bash # Create & manage k8s clusters set -o nounset set -o errexit #set -o xtrace function usage() { echo -e "\033[33mUsage:\033[0m ezctl COMMAND [args]" cat < to switch default kubeconfig of the cluster new to start a new k8s deploy with name 'cluster' setup to setup a cluster, also supporting a step-by-step way start to start all of the k8s services stopped by 'ezctl stop' stop to stop all of the k8s services temporarily upgrade to upgrade the k8s cluster destroy to destroy the k8s cluster backup to backup the cluster state (etcd snapshot) restore to restore the cluster state from backups start-aio to quickly setup an all-in-one cluster with 'default' settings Cluster ops: add-etcd to add a etcd-node to the etcd cluster add-master to add a master node to the k8s cluster add-node to add a work node to the k8s cluster del-etcd to delete a etcd-node from the etcd cluster del-master to delete a master node from the k8s cluster del-node to delete a work node from the k8s cluster Use "ezctl help " for more information about a given command. EOF } function usage-setup(){ echo -e "\033[33mUsage:\033[0m ezctl setup " cat < 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-etcd.md'" ;; (add-master) echo -e "read more > 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-master.md'" ;; (add-node) echo -e "read more > 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-node.md'" ;; (del-etcd) echo -e "read more > 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-etcd.md'" ;; (del-master) echo -e "read more > 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-master.md'" ;; (del-node) echo -e "read more > 'https://github.com/easzlab/kubeasz/blob/master/docs/op/op-node.md'" ;; (*) echo -e "todo: help info $1" ;; esac } ### Cluster setups functions ############################## function new() { # check if already existed [[ -d "clusters/$1" ]] && { logger error "cluster: $1 already existed"; exit 1; } logger debug "generate custom cluster files in clusters/$1" mkdir -p "clusters/$1" cp example/hosts.multi-node "clusters/$1/hosts" sed -i "s/_cluster_name_/$1/g" "clusters/$1/hosts" cp example/config.yml "clusters/$1/config.yml" logger debug "set version of common plugins" calicoVer=$(grep 'calicoVer=' ezdown|cut -d'=' -f2) ciliumVer=$(grep 'ciliumVer=' ezdown|cut -d'=' -f2) flannelVer=$(grep 'flannelVer=' ezdown|cut -d'=' -f2) kubeRouterVer=$(grep 'kubeRouterVer=' ezdown|cut -d'=' -f2) kubeOvnVer=$(grep 'kubeOvnVer=' ezdown|cut -d'=' -f2) corednsVer=$(grep 'corednsVer=' ezdown|cut -d'=' -f2) dashboardVer=$(grep 'dashboardVer=' ezdown|cut -d'=' -f2) dashboardMetricsScraperVer=$(grep 'dashboardMetricsScraperVer=' ezdown|cut -d'=' -f2) metricsVer=$(grep 'metricsVer=' ezdown|cut -d'=' -f2) promChartVer=$(grep 'promChartVer=' ezdown|cut -d'=' -f2) sed -i -e "s/__flannel__/$flannelVer/g" \ -e "s/__calico__/$calicoVer/g" \ -e "s/__cilium__/$ciliumVer/g" \ -e "s/__kube_ovn__/$kubeOvnVer/g" \ -e "s/__kube_router__/$kubeRouterVer/g" \ -e "s/__coredns__/$corednsVer/g" \ -e "s/__dashboard__/$dashboardVer/g" \ -e "s/__dash_metrics__/$dashboardMetricsScraperVer/g" \ -e "s/__prom_chart__/$promChartVer/g" \ -e "s/__metrics__/$metricsVer/g" "clusters/$1/config.yml" logger debug "cluster $1: files successfully created." logger info "next steps 1: to config 'clusters/$1/hosts'" logger info "next steps 2: to config 'clusters/$1/config.yml'" } function setup() { [[ -d "clusters/$1" ]] || { logger error "invalid config, run 'ezctl new $1' first"; return 1; } [[ -f "bin/kube-apiserver" ]] || { logger error "no binaries founded, run 'ezdown -D' fist"; return 1; } PLAY_BOOK="dummy.yml" case "$2" in (01) PLAY_BOOK="01.prepare.yml" ;; (02) PLAY_BOOK="02.etcd.yml" ;; (03) PLAY_BOOK="03.runtime.yml" ;; (04) PLAY_BOOK="04.kube-master.yml" ;; (05) PLAY_BOOK="05.kube-node.yml" ;; (06) PLAY_BOOK="06.network.yml" ;; (07) PLAY_BOOK="07.cluster-addon.yml" ;; (90) PLAY_BOOK="90.setup.yml" ;; (all) PLAY_BOOK="90.setup.yml" ;; (*) usage-setup exit 1 ;; esac logger info "cluster:$1 setup step:$2 begins in 5s, press any key to abort:\n" ! (read -r -t5 -n1) || { logger warn "setup abort"; return 1; } ansible-playbook -i "clusters/$1/hosts" -e "@clusters/$1/config.yml" "playbooks/$PLAY_BOOK" || return 1 } function cmd() { [[ -d "clusters/$1" ]] || { logger error "invalid config, run 'ezctl new $1' first"; return 1; } PLAY_BOOK="dummy.yml" case "$2" in (start) PLAY_BOOK="91.start.yml" ;; (stop) PLAY_BOOK="92.stop.yml" ;; (upgrade) PLAY_BOOK="93.upgrade.yml" ;; (backup) PLAY_BOOK="94.backup.yml" ;; (restore) PLAY_BOOK="95.restore.yml" ;; (destroy) PLAY_BOOK="99.clean.yml" ;; (*) usage exit 1 ;; esac logger info "cluster:$1 $2 begins in 5s, press any key to abort:\n" ! (read -r -t5 -n1) || { logger warn "$2 abort"; return 1; } ansible-playbook -i "clusters/$1/hosts" -e "@clusters/$1/config.yml" "playbooks/$PLAY_BOOK" || return 1 } function list() { [[ -d ./clusters ]] || { logger error "cluster not found, run 'ezctl new' first"; return 1; } [[ -f ~/.kube/config ]] || { logger error "kubeconfig not found, run 'ezctl setup' first"; return 1; } which md5sum > /dev/null 2>&1 || { logger error "md5sum not found"; return 1; } CLUSTERS=$(cd clusters && echo -- *) CFG_MD5=$(md5sum -t ~/.kube/config |cut -d' ' -f1) cd "$BASE" logger info "list of managed clusters:" i=1; for c in $CLUSTERS; do if [[ -f "clusters/$c/kubectl.kubeconfig" ]];then c_md5=$(md5sum -t "clusters/$c/kubectl.kubeconfig" |cut -d' ' -f1) if [[ "$c_md5" = "$CFG_MD5" ]];then echo -e "==> cluster $i:\t$c (\033[32mcurrent\033[0m)" else echo -e "==> cluster $i:\t$c" fi let "i++" fi done } function checkout() { [[ -d "clusters/$1" ]] || { logger error "invalid config, run 'ezctl new $1' first"; return 1; } [[ -f "clusters/$1/kubectl.kubeconfig" ]] || { logger error "invalid kubeconfig, run 'ezctl setup $1' first"; return 1; } logger info "set default kubeconfig: cluster $1 (\033[32mcurrent\033[0m)" /bin/cp -f "clusters/$1/kubectl.kubeconfig" ~/.kube/config } ### in-cluster operation functions ############################## function add-node() { # check new node's address regexp [[ $2 =~ ^(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}$ ]] || { logger error "Invalid ip add:$2"; return 1; } # check if the new node already exsited sed -n '/^\[kube-master/,/^\[harbor/p' "$BASE/clusters/$1/hosts"|grep "^$2[^0-9]*$" && { logger error "node $2 already existed in $BASE/clusters/$1/hosts"; return 2; } logger info "add $2 into 'kube-node' group" sed -i "/\[kube-node/a $2 NEW_NODE=yes ${@:3}" "$BASE/clusters/$1/hosts" logger info "start to add a work node:$2 into cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/22.addnode.yml" -e "NODE_TO_ADD=$2" -e "@clusters/$1/config.yml" } function add-master() { # check new master's address regexp [[ $2 =~ ^(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}$ ]] || { logger error "Invalid ip add:$2"; return 1; } # check if the new master already exsited sed -n '/^\[kube-master/,/^\[kube-node/p' "$BASE/clusters/$1/hosts"|grep "^$2[^0-9]*$" && { logger error "master $2 already existed!"; return 2; } logger info "add $2 into 'kube-master' group" sed -i "/\[kube-master/a $2 NEW_MASTER=yes ${@:3}" "$BASE/clusters/$1/hosts" logger info "start to add a master node:$2 into cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/23.addmaster.yml" -e "NODE_TO_ADD=$2" -e "@clusters/$1/config.yml" logger info "reconfigure and restart the haproxy service on 'kube-node' nodes" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/05.kube-node.yml" -t restart_lb -e MASTER_CHG=yes -e "@clusters/$1/config.yml" } function add-etcd() { # check new node's address regexp [[ $2 =~ ^(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}$ ]] || { logger error "Invalid ip add:$2"; return 1; } # check if the new node already exsited sed -n '/^\[etcd/,/^\[kube-master/p' "$BASE/clusters/$1/hosts"|grep "^$2[^0-9]*$" && { logger error "etcd $2 already existed!"; return 2; } logger info "add $2 into 'etcd' group" sed -i "/\[etcd/a $2 NEW_ETCD=yes ${@:3}" "$BASE/clusters/$1/hosts" logger info "start to add a etcd node:$2 into cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/21.addetcd.yml" -e "NODE_TO_ADD=$2" -e "@clusters/$1/config.yml" logger info "reconfig &restart the etcd cluster" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/02.etcd.yml" -t restart_etcd -e "@clusters/$1/config.yml" logger info "restart apiservers to use the new etcd cluster" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/04.kube-master.yml" -t restart_master -e "@clusters/$1/config.yml" } function del-etcd() { # check node's address regexp [[ $2 =~ ^(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}$ ]] || { logger error "Invalid ip add:$2"; return 1; } logger warn "start to delete the etcd node:$2 from cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/31.deletcd.yml" -e "ETCD_TO_DEL=$2" -e "CLUSTER=$1" -e "@clusters/$1/config.yml" logger info "reconfig &restart the etcd cluster" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/02.etcd.yml" -t restart_etcd -e "@clusters/$1/config.yml" logger info "restart apiservers to use the new etcd cluster" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/04.kube-master.yml" -t restart_master -e "@clusters/$1/config.yml" } function del-node() { # check node's address regexp [[ $2 =~ ^(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}$ ]] || { logger "Invalid ip add:$2"; return 2; } logger warn "start to delete the node:$2 from cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/32.delnode.yml" -e "NODE_TO_DEL=$2" -e "CLUSTER=$1" -e "@clusters/$1/config.yml" } function del-master() { # check node's address regexp [[ $2 =~ ^(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}$ ]] || { logger error "Invalid ip add:$2"; return 2; } logger warn "start to delete the master:$2 from cluster:$1" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/33.delmaster.yml" -e "NODE_TO_DEL=$2" -e "CLUSTER=$1" -e "@clusters/$1/config.yml" logger info "reconfig kubeconfig in ansible manage node" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/roles/deploy/deploy.yml" -t create_kctl_cfg -e "@clusters/$1/config.yml" logger info "reconfigure and restart the haproxy service on 'kube-node' nodes" ansible-playbook -i "$BASE/clusters/$1/hosts" "$BASE/playbooks/05.kube-node.yml" -t restart_lb -e MASTER_CHG=yes -e "@clusters/$1/config.yml" } function start-aio(){ set +u # Check ENV 'HOST_IP', exists if the CMD 'ezctl' running in a docker container if [[ -z $HOST_IP ]];then # ezctl 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$"|head -n1|awk '{print $2}'|cut -d'/' -f1) fi set -u logger info "get local host ipadd: $HOST_IP" new default /bin/cp -f example/hosts.allinone "clusters/default/hosts" sed -i "s/_cluster_name_/default/g" "clusters/default/hosts" sed -i "s/192.168.1.1/$HOST_IP/g" "clusters/default/hosts" setup default all } ### Main Lines ################################################## function main() { BASE="/etc/kubeasz" [[ -d "$BASE" ]] || { logger error "invalid dir:$BASE, try: 'ezdown -D'"; exit 1; } cd "$BASE" # check bash shell readlink /proc/$$/exe|grep -q "dash" && { logger error "you should use bash shell only"; exit 1; } # check 'ansible' executable which ansible > /dev/null 2>&1 || { logger error "need 'ansible', try: 'pip install ansible==2.6.18'"; exit 1; } [ "$#" -gt 0 ] || { usage >&2; exit 2; } case "$1" in ### in-cluster operations ##################### (add-etcd) [ "$#" -gt 2 ] || { usage >&2; exit 2; } add-etcd "${@:2}" ;; (add-master) [ "$#" -gt 2 ] || { usage >&2; exit 2; } add-master "${@:2}" ;; (add-node) [ "$#" -gt 2 ] || { usage >&2; exit 2; } add-node "${@:2}" ;; (del-etcd) [ "$#" -eq 3 ] || { usage >&2; exit 2; } del-etcd "$2" "$3" ;; (del-master) [ "$#" -eq 3 ] || { usage >&2; exit 2; } del-master "$2" "$3" ;; (del-node) [ "$#" -eq 3 ] || { usage >&2; exit 2; } del-node "$2" "$3" ;; ### cluster-wide operations ####################### (checkout) [ "$#" -eq 2 ] || { usage >&2; exit 2; } checkout "$2" ;; (list) [ "$#" -eq 1 ] || { usage >&2; exit 2; } list ;; (new) [ "$#" -eq 2 ] || { usage >&2; exit 2; } new "$2" ;; (setup) [ "$#" -eq 3 ] || { usage-setup >&2; exit 2; } setup "${@:2}" ;; (start) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" start ;; (stop) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" stop ;; (upgrade) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" upgrade ;; (backup) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" backup ;; (restore) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" restore ;; (destroy) [ "$#" -eq 2 ] || { usage >&2; exit 2; } cmd "$2" destroy ;; (start-aio) [ "$#" -eq 1 ] || { usage >&2; exit 2; } start-aio ;; (help) [ "$#" -gt 1 ] || { usage >&2; exit 2; } help-info "$2" exit 0 ;; (*) usage exit 0 ;; esac } main "$@"