Last active
August 13, 2020 08:48
-
-
Save gildas/a06b800f300d4c176d70f73bf8edbc02 to your computer and use it in GitHub Desktop.
install-k8s.sh
This file contains hidden or 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
#!/usr/bin/env bash | |
# This script is supposed to run right after having installed the OS | |
# With only one NIC configured | |
shopt -s extglob | |
set -o errtrace | |
set -o errexit | |
set +o noclobber | |
# Defaults {{{ | |
ASSUMEYES=0 | |
NOOP= | |
FORCE=0 | |
VERBOSE=1 | |
SHOW_TRACE=0 | |
LOG_ROOT=. | |
LOG_BASENAME="ubuntu-prep-k8s" | |
LOG="${LOG_ROOT}/${LOG_BASENAME}.log" | |
MGT_NIC= | |
K8S_CERT_HASH=$K8S_CERT_HASH | |
K8S_TOKEN=$K8S_TOKEN | |
K8S_JOIN=$K8S_JOIN | |
WITH_METALLB=0 | |
METALLB_IP_RANGE= | |
# }}} | |
# DO NOT MODIFY ANYTHING AFTER THIS LINE ################################################# | |
# General variables {{{ | |
DOCKER_REPO_FINGERPRINT="0EBFCD88" | |
DOCKER_EDITION=stable # or edge | |
KUBERNETES_REPO_FINGERPRINT="BA07F4FB" | |
KUBERNETES_EDITION=main | |
userid=$(whoami) | |
userhome=$( getent passwd "$userid" | cut -d: -f6 ) | |
GROUP=wheel | |
SUDO_PASSWORD="" | |
NGINX_user=nginx | |
NGINX_group=nginx | |
# Various Unix tools | |
CURL="/usr/bin/curl --location --progress-bar " | |
SUDO="/usr/bin/sudo " | |
JQ="jq --raw-output " | |
RM="/usr/bin/rm -rf " | |
MV="/usr/bin/mv " | |
CP="/usr/bin/cp " | |
YUM="/usr/bin/yum --quiet" | |
APT_GET="UCF_CONFNEW=yes DEBIAN_FRONTEND=noninteractive apt-get --quiet" | |
ZYPPER="/usr/bin/zypper --non-interactive" | |
CHECKSUM="/usr/bin/md5sum " | |
NETCAT="nc" | |
# Unix versioning | |
OS="Linux" | |
OS_FLAVOR="" | |
OS_DISTRO="" | |
OS_CODENAME="" | |
OS_VERSION="" | |
export PATH="/usr/local/bin${PATH+:$PATH}" | |
# }}} | |
# tracing {{{ | |
trap trace_end EXIT | |
function trace() # {{{2 | |
{ | |
local caller_index=1 | |
while :; do # Parse arguments {{{3 | |
case $1 in | |
--trace-member) | |
caller_index=2 | |
;; | |
--noop|-n) | |
return | |
;; | |
*) # End of options | |
break | |
;; | |
esac | |
shift | |
done # }}}3 | |
echo -e "[$(date +'%Y%m%dT%H%M%S')]${BASH_SOURCE[$caller_index]}::${FUNCNAME[$caller_index]}@${BASH_LINENO[(($caller_index - 1))]}: $@" >> "$LOG" | |
} # 2}}} | |
function trace_init() # {{{2 | |
{ | |
local log_file=$(basename $LOG) | |
local log_group="wheel" | |
local result | |
if [ -w $LOG_ROOT ]; then | |
export LOG="${LOG_ROOT}/${LOG_BASENAME}-$(date +'%Y%m%d%H%M%S').log" | |
else | |
export LOG="${HOME}/${LOG_BASENAME}-$(date +'%Y%m%d%H%M%S').log" | |
fi | |
while :; do # Parse arguments {{{3 | |
case $1 in | |
--logdest) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die -n "Argument for option $1 is missing" | |
LOG="$2/$log_file" | |
shift 2 | |
continue | |
;; | |
--logdest=*?) | |
LOG="${1#*=}/$log_file" | |
;; | |
--logdest=) | |
die -n "Argument for option $1 is missing" | |
;; | |
--loggroup) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die -n "Argument for option $1 is missing" | |
log_group="$2" | |
shift 2 | |
continue | |
;; | |
--loggroup=*?) | |
log_group=${1#*=} | |
;; | |
--loggroup=) | |
die -n "Argument for option $1 is missing" | |
;; | |
-?*) # Invalid options | |
;; | |
--) # Force end of options | |
shift | |
break | |
;; | |
*) # End of options | |
break | |
;; | |
esac | |
shift | |
done # }}}3 | |
if [[ ! -w $LOG ]]; then | |
if [[ ! -w $(dirname $LOG) ]]; then | |
echo "NOTE: You might have to enter your password to allow the script to modify your system!" | |
if [[ ! -d $(dirname $LOG) ]]; then | |
sudo mkdir -p $(dirname $LOG) 2>&1 | tee /dev/null > /dev/null | |
result=$? | |
[[ $result ]] && die -n "Could not create folder $(dirname $LOG)" $result | |
fi | |
sudo touch $LOG 2>&1 | tee /dev/null > /dev/null | |
[[ $result ]] && die -n "Could not create $LOG" $result | |
sudo chown $(whoami):${log_group} $LOG | |
[[ $result ]] && die -n "Could not change owner for $LOG" $result | |
sudo chmod 640 $LOG 2>&1 | tee /dev/null > /dev/null | |
[[ $result ]] && die -n "Could not change permissions for $LOG" $result | |
else | |
touch $LOG 2>&1 | tee /dev/null > /dev/null | |
[[ $result ]] && die -n "Could not create $LOG" $result | |
chgrp ${log_group} $LOG 2>&1 | tee /dev/null > /dev/null | |
[[ $result ]] && die -n "Could not change group for $LOG" $result | |
chmod 640 $LOG 2>&1 | tee /dev/null > /dev/null | |
[[ $result ]] && die -n "Could not change permissions for $LOG" $result | |
fi | |
fi | |
trace --trace-member "[BEGIN] --8<----------------------8<------------------------8<------------ [BEGIN]" | |
} # 2}}} | |
function trace_end() # {{{2 | |
{ | |
for cache_mount in ${CACHE_MOUNTS[@]}; do | |
trace --trace-member "Removing CIFS mount point: $cache_mount" | |
umount $cache_mount 2>&1 > /dev/null | |
done | |
for vpn_id in ${CONNECTED_VPNS[@]}; do | |
vpn_stop --id=$vpn_id | |
done | |
trace --trace-member "[END] --8<----------------------8<------------------------8<------------ [END]" | |
} # 2}}} | |
function trace_output() ## {{{2 | |
{ | |
tee -a "$LOG" | |
} # 2}}} | |
function verbose() { # {{{2 | |
trace --trace-member "$@" | |
[[ $VERBOSE > 0 ]] && echo -e "$@" | |
} # 2}}} | |
function warn() # {{{2 | |
{ | |
trace --trace-member "[WARNING] $@" | |
echo -e "Warning: $@" | |
} # 2}}} | |
function error() # {{{2 | |
{ | |
trace --trace-member "[ERROR] $@" | |
echo -e "\e[0;31mError: $@\e[0m" >&2 | |
} # 2}}} | |
function die() { # {{{2 | |
local trace_noop= | |
if [[ $1 == '-n' ]]; then | |
trace_noop=: | |
shift | |
fi | |
local message=$1 | |
local errorlevel=$2 | |
[[ -z $message ]] && message='Died' | |
[[ -z $errorlevel ]] && errorlevel=1 | |
$trace_noop trace --trace-member "[FATALERROR] $errorlevel $message" | |
echo -e "\e[0;31m$message\e[0m" >&2 | |
echo Installation logs: $LOG | |
exit $errorlevel | |
} # 2}}} | |
function on_error_die() { # {{{2 | |
status=$? ; [[ $status == 0 ]] || die "$@. Error: $status" $status | |
return 0 | |
} # 2}}} | |
# }}} | |
function check_os() { # {{{2 | |
if [[ $OSTYPE == "linux-gnu" ]]; then | |
OS="linux" | |
if [ -f /etc/redhat-release ]; then | |
# RedHat or Centos | |
export OS_FLAVOR="redhat" | |
export OS_DISTRO="$(sed -e 's/ release.*//' /etc/redhat-release)" | |
export OS_CODENAME="$(sed -e 's/.*(//' /etc/redhat-release | sed -e 's/ *)//')" | |
export OS_VERSION="$(sed -e 's/.*release\ //' /etc/redhat-release | sed -e 's/\ .*//')" | |
elif [ -f /etc/debian_version ]; then | |
export OS_FLAVOR="debian" | |
export OS_DISTRO="$(grep DISTRIB_ID= /etc/lsb-release | cut -d '=' -f 2)" | |
export OS_CODENAME="$(grep DISTRIB_CODENAME= /etc/lsb-release | cut -d '=' -f 2)" | |
export OS_VERSION="$(grep DISTRIB_RELEASE= /etc/lsb-release | cut -d '=' -f 2)" | |
export JQ="/usr/bin/jq --raw-output" | |
export NGINX_user=www-data | |
export NGINX_group=www-data | |
else | |
die "Unsupported OS: $OSTYPE" | |
fi | |
elif [[ $OSTYPE == "linux" ]]; then | |
OS="linux" | |
if [ -f /etc/os-release ]; then | |
# OpenSuSE or SuSE Linux Enterprise Server | |
export OS_FLAVOR="suse" | |
export OS_DISTRO="$(grep '^NAME=' /etc/os-release | cut -d '=' -f 2 | tr -d '"')" | |
export OS_CODENAME="$(grep VERSION= /etc/os-release | cut -d '=' -f 2 | tr -d '"')" | |
export OS_VERSION="$(grep VERSION_ID= /etc/os-release | cut -d '=' -f 2 | tr -d '"')" | |
else | |
die "Unsupported OS: $OSTYPE" | |
fi | |
else | |
die "Unsupported OS: $OSTYPE" | |
fi | |
verbose "OS: $OS, Flavor: $OS_FLAVOR, Distro: $OS_DISTRO, Version: $OS_VERSION ($OS_CODENAME)" | |
return 0 | |
} # 2}}} | |
function check_oslicense() { # {{{2 | |
trace "Checking OS License" | |
if [[ $OS_DISTRO == 'Red Hat Enterprise Linux Server' ]]; then | |
# --proxy=hostname:port --proxyuser=username --proxypassword=password | |
if ! $SUDO subscription-manager status &>/dev/null; then | |
status=$? | |
die "This system is not registered. Please Register it with your Red Hat Subscription" $status | |
else | |
verbose "This system is registered with Red Hat." | |
fi | |
elif [[ $OS_DISTRO == "SLES" ]]; then | |
if command -v SUSEConnect >& /dev/null; then # SLES 12+ | |
#if $SUDO SUSEConnect --status &>/dev/null; then | |
if ! $SUDO SUSEConnect --status >&/dev/null ; then | |
status=$? | |
die "This system is not registered. Please register it with your Suse Linux Enterprise Subscription" $status | |
else | |
verbose "This system is registered with SUSE." | |
fi | |
elif [[ ! -f /var/cache/SuseRegister/lastzmdconfig.cache ]]; then # SLES 11 | |
die "This system is not registered. Please register it with your Suse Linux Enterprise Subscription" 1 | |
fi | |
fi | |
return 0 | |
} # 2}}} | |
function sudo_init() { # {{{2 | |
local SUDO_GROUP='wheel' | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
SUDO_GROUP='wheel' | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
SUDO_GROUP='sudo' | |
fi | |
# Check if we are either root or a sudoer, die otherwise | |
if [[ $(whoami) == 'root' ]]; then | |
verbose "Executing the script as root" | |
SUDO="" | |
else | |
verbose "Executing the script as a sudoer" | |
if sudo -n true 2>&1 > /dev/null ; then | |
trace "Can execute script as a sudoer without providing password. Cool" | |
else | |
if [[ -n $SUDO_PASSWORD ]]; then | |
echo "$SUDO_PASSWORD" | /usr/bin/sudo -S -p "." -v ; status=$? && [[ $status != 0 ]] && die "Invalid sudo passsword" $status | |
else | |
echo "Please enter your password to verify you can install software" | |
$SUDO -v | |
fi | |
# Keep-alive: update existing sudo time stamp if set, otherwise do nothing. | |
# Thanks to: https://gist.github.com/cowboy/3118588 | |
while true; do sudo -n true; echo "$(date)" >> /tmp/keep.log.$$ ; sleep 60; kill -0 "$$" || exit; done 2>>/tmp/keep.log.$$ & | |
# This line forks a process that will run as long as this script runs. | |
# When this script is done, kill -0 "$$" will return 0 allowing "exit" to stop the loop | |
fi | |
fi | |
return 0 | |
} # 2}}} | |
function configure_network() { # {{{2 | |
if [[ -n $(sudo sysctl net.ipv4.ip_forward | grep 0) ]]; then | |
verbose "Turning on IPV4 Forwarding" | |
$NOOP $SUDO tee /etc/sysctl.d/90-bridge.conf >/dev/null <<-EOF | |
net.ipv4.ip_forward=1 | |
net.bridge.bridge-nf-call-arptables=1 | |
net.bridge.bridge-nf-call-iptables=1 | |
net.bridge.bridge-nf-call-ip6tables=1 | |
EOF | |
$NOOP $SUDO sysctl -p | |
fi | |
} # 2}}} | |
function add_repo_ansible() { # {{{2 | |
local status | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
# The EPEL repository contains ansible | |
return 0 | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
if [[ $(lsb_release -cs) == "bionic" ]]; then | |
trace "Bionic Beaver already contains Ansible > 2.3" | |
return 0 | |
fi | |
if [[ -z $(grep "ansible" /etc/apt/sources.list) ]]; then | |
verbose "Adding repository for Ansible" | |
sudo add-apt-repository ppa:ansible/ansible | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository" ; return $status) | |
fi | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function add_repo_docker() { # {{{2 | |
local status | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
$NOOP $SUDO yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository" ; return $status) | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
if [[ -z $(apt-key fingerprint $DOCKER_REPO_FINGERPRINT) ]]; then | |
verbose "Adding repository key for Docker" | |
curl -sSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - > /dev/null | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository key" ; return $status) | |
fi | |
if [[ -z $(grep "docker.com" /etc/apt/sources.list) ]]; then | |
verbose "Adding repository for Docker" | |
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) $DOCKER_EDITION" | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository" ; return $status) | |
# TODO: remove conditionally. Since 18.04, this is done by the add-aot-repository... | |
sudo apt update --quiet --quiet | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while updating" ; return $status) | |
fi | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function add_repo_kubernetes() { # {{{2 | |
local status | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
if [[ ! -f /etc/yum.repos.d/kubernetes.repo ]]; then | |
$NOOP $SUDO tee /etc/yum.repos.d/kubernetes.repo > /dev/null <<EOM | |
[kubernetes] | |
name=Kubernetes | |
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 | |
enabled=1 | |
gpgcheck=1 | |
repo_gpgcheck=1 | |
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg | |
EOM | |
fi | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
if [[ -z $(apt-key fingerprint $KUBERNETES_REPO_FINGERPRINT) ]]; then | |
verbose "Adding repository key for Kubernetes" | |
curl -sSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - > /dev/null | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository key" ; return $status) | |
fi | |
if [[ -z $(grep "kubernetes.io" /etc/apt/sources.list) ]]; then | |
verbose "Adding repository for Kubernetes (Note: Kubernetes for Bionic Beaver does not exist yet)" | |
sudo add-apt-repository "deb http://apt.kubernetes.io/ kubernetes-xenial $KUBERNETES_EDITION" | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while adding repository" ; return $status) | |
# TODO: remove conditionally. Since 18.04, this is done by the add-aot-repository... | |
sudo apt update --quiet --quiet | |
status=$? ; [[ $status == 0 ]] || ( trace "Error: $status, while updating" ; return $status) | |
fi | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function update_packages() { # {{{2 | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
verbose "Updating packages..." | |
$NOOP $SUDO $YUM update --assumeyes ; status=$? && [[ $status != 0 ]] && return $status | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
verbose "Updating packages..." | |
$NOOP $SUDO UCF_CONFNEW=yes DEBIAN_FRONTEND=noninteractive apt upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" | |
status=$? && [[ $status != 0 ]] && return $status | |
elif [[ $OS_FLAVOR == 'suse' ]]; then | |
verbose "Updating packages..." | |
$NOOP $SUDO $ZYPPER update --no-confirm ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function install() { # {{{2 | |
local package=$1 | |
local version=$2 | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
if ! $YUM --cacheonly list installed $package &>/dev/null; then | |
$NOOP $SUDO $YUM install --assumeyes $package | |
status=$? && [[ $status != 0 ]] && error "Failed to install $package, error: $status" && return $status | |
else | |
verbose "$package is already installed" | |
fi | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
if ! dpkg -s $package &>/dev/null; then | |
verbose "Installing package $package ${version:+(version $version)}" | |
$SUDO -H UCF_CONFNEW=yes DEBIAN_FRONTEND=noninteractive apt install --yes --quiet $package${version:+=$version} | |
status=$? && [[ $status != 0 ]] && error "Failed to install $package, error: $status" && return $status | |
[[ -n $version ]] && sudo apt-mark hold $package | |
else | |
verbose "$package is already installed" | |
fi | |
elif [[ $OS_DISTRO == 'SLES' ]]; then | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function install_prereq() { # {{{2 | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
# Set SELinux in permissive mode | |
$NOOP $SUDO setenforce 0 | |
$NOOP $SUDO sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config | |
install deltarpm | |
$NOOP $SUDO yum makecache fast | |
if ! $YUM --cacheonly list installed epel-release &>/dev/null; then | |
verbose "Installing EPEL Repository" | |
if [[ $OS_DISTRO == 'CentOS Linux' ]]; then | |
$NOOP $SUDO $YUM install --assumeyes epel-release ; status=$? && [[ $status != 0 ]] && return $status | |
$NOOP $SUDO rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 | |
else | |
$NOOP $SUDO rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm ; status=$? && [[ $status != 0 ]] && return $status | |
fi | |
else | |
verbose "EPEL repository is already installed" | |
fi | |
install curl | |
install jq | |
install yum-utils | |
install moreutils | |
install device-mapper-persistent-data | |
install lvm2 | |
install ansible | |
install ansible-lint | |
install bash-completion | |
# TODO: Maybe we should configure firewalld instead of turning it off | |
$NOOP systemctl is-enabled firewalld &> /dev/null && (verbose " Disabling Firewalld" ; $SUDO systemctl disable firewalld) | |
$NOOP systemctl is-active firewalld &> /dev/null || (verbose " Stopping Firewalld" ; $SUDO systemctl stop firewalld) | |
elif [[ $OS_FLAVOR == 'debian' ]]; then | |
# On Debian we really have to update the packages first, it is too dumb without | |
update_packages | |
verbose "Installing common pre-requisite stuff" | |
install apt-transport-https | |
install ca-certificates | |
install file | |
install curl | |
install jq | |
install software-properties-common | |
install moreutils | |
install libcap2-bin | |
#install firewalld | |
install ansible | |
install ansible-lint | |
# TODO: Maybe we should configure firewalld instead of turning it off | |
$NOOP systemctl is-enabled firewalld &> /dev/null && (verbose " Disabling Firewalld" ; $SUDO systemctl disable firewalld) | |
$NOOP systemctl is-active firewalld &> /dev/null && (verbose " Stopping Firewalld" ; $SUDO systemctl stop firewalld) | |
RM="/bin/rm -rf " | |
MV="/bin/mv " | |
CP="/bin/cp " | |
GROUP="adm" # no wheel on Ubuntu | |
elif [[ $OS_DISTRO == 'SLES' ]]; then | |
if command -v SUSEConnect >& /dev/null ; then | |
if [[ -z $($SUDO SUSEConnect --list-extensions | grep "SUSE Package Hub.*Installed") ]]; then | |
verbose "Installing SUSE Package Hub Extension" | |
$NOOP $SUDO SUSEConnect -p PackageHub/${OS_VERSION}/x86_64 | |
status=$? | |
if [[ $status == 106 ]]; then # There is an issue with SUSE metadata | |
$NOOP $SUDO SUSEConnect -p PackageHub/${OS_VERSION}/x86_64 | |
status=$? && [[ $status != 0 ]] && return $status | |
else | |
[[ $status != 0 ]] && return $status | |
fi | |
fi | |
if [[ -z $($SUDO SUSEConnect --list-extensions | grep "Web and Scripting Module.*Installed") ]]; then | |
verbose "Installing Web and Scripting Module" | |
# We need only the major version | |
$NOOP $SUDO SUSEConnect -p sle-module-web-scripting/${OS_VERSION%.*}/x86_64 | |
status=$? | |
if [[ $status == 106 ]]; then # There is an issue with SUSE metadata | |
$NOOP $SUDO SUSEConnect -p sle-module-web-scripting/${OS_VERSION%.*}/x86_64 | |
status=$? && [[ $status != 0 ]] && return $status | |
else | |
[[ $status != 0 ]] && return $status | |
fi | |
fi | |
fi | |
if [[ -z $($ZYPPER repos | grep MediaArea) ]]; then | |
verbose "Adding MediaArea Software Repository" | |
$NOOP $SUDO rpm -Uvh https://mediaarea.net/repo/rpm/releases/repo-MediaArea-1.0-6.noarch.rpm | |
status=$? && [[ $status != 0 ]] && return $status | |
fi | |
verbose "Refreshing repositories" | |
$NOOP $SUDO $ZYPPER --gpg-auto-import-keys refresh ; status=$? && [[ $status != 0 ]] && return $status | |
if ! rpm -q file &>/dev/null; then | |
verbose "Installing package file" | |
$NOOP $SUDO $ZYPPER install -y file ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "file is already installed" | |
fi | |
if ! rpm -q curl &>/dev/null; then | |
verbose "Installing package curl" | |
$NOOP $SUDO $ZYPPER install -y curl ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "curl is already installed" | |
fi | |
if ! rpm -q libcap2 &>/dev/null; then | |
verbose "Installing package libcap2" | |
$NOOP $SUDO $ZYPPER install -y libcap2 ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "libcap2 is already installed" | |
fi | |
if ! rpm -q libcap-progs &>/dev/null; then | |
verbose "Installing package libcap-progs" | |
$NOOP $SUDO $ZYPPER install -y libcap-progs ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "libcap-progs is already installed" | |
fi | |
if ! rpm -q jq &>/dev/null; then | |
verbose "Installing package jq" | |
$NOOP $SUDO $ZYPPER install -y jq ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "jq is already installed" | |
fi | |
if ! rpm -q moreutils &>/dev/null; then | |
verbose "Installing package moreutils" | |
$NOOP $SUDO $ZYPPER install -y moreutils ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "moreutils is already installed" | |
fi | |
if ! rpm -q systemd &>/dev/null; then | |
verbose "Installing package systemd" | |
$NOOP $SUDO $ZYPPER install -y systemd ; status=$? && [[ $status != 0 ]] && return $status | |
else | |
verbose "systemd is already installed" | |
fi | |
else | |
die "Unsupported $OS $OS_FLAVOR ($OS_DISTRO $OS_VERSION)" | |
fi | |
return 0 | |
} # 2}}} | |
function add_docker_partition() { # {{{2 | |
verbose "Checking disks for Docker dedicated disk" | |
if [[ -z $(grep docker /etc/fstab) ]]; then | |
used_partitions=( $(blkid | cut -f1 -d:) ) | |
trace "Used partitions: ${used_partitions[@]}" | |
used_disks=( $(blkid | cut -f1 -d: | sed -e 's/[0-9]\+$//' -e 's;^/dev/;;' | sort | uniq) ) | |
trace "Used disks: ${used_disks[@]}" | |
used_disks=$(printf "\|%s" "${used_disks[@]}") | |
used_disks=${used_disks:2} | |
trace "Used disks: ${used_disks[@]}" | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
docker_disk=$(sudo fdisk -l | grep '^Disk \/dev' | grep -v "$used_disks" | grep -v loop | grep -v home | sort -k 5n | tail -1 | cut -f1 -d: | cut -f2 -d' ') | |
else | |
docker_disk=$(sudo fdisk --list --bytes | grep '^Disk \/dev' | grep -v "$used_disks" | grep -v loop | sort -k 5n | tail -1 | cut -f1 -d: | cut -f2 -d' ') | |
fi | |
trace "Docker disk: ${docker_disk}" | |
if [[ -n $docker_disk ]]; then | |
verbose "Preparing disk $docker_disk for Docker storage" | |
verbose " partitioning..." | |
sudo parted --script --align=optimal $docker_disk -- \ | |
mklabel msdos \ | |
mkpart primary ext4 0% 100% \ | |
set 1 lvm on | |
sudo parted --script $docker_disk print | |
verbose " creating Physical Volume..." | |
sudo pvcreate ${docker_disk}1 | |
trace "Physical volumes: $(sudo pvs)" | |
verbose " creating Volume Group docker-pool..." | |
sudo vgcreate docker-pool ${docker_disk}1 | |
trace "Physical groups: $(sudo vgs)" | |
verbose " creating Logical Volume docker-data..." | |
sudo lvcreate --extents 100%FREE --name docker-data docker-pool | |
trace "Logical groups: $(sudo lvs)" | |
verbose " formating Logical Volume docker-data..." | |
sudo mke2fs -t ext4 /dev/docker-pool/docker-data | tee -a $LOG | |
verbose " binding docker-data to /var/lib/docker..." | |
sudo tee -a /etc/fstab > /dev/null <<EOF | |
/dev/docker-pool/docker-data /var/lib/docker ext4 defaults 0 0 | |
EOF | |
trace "fstab: $(cat /etc/fstab)" | |
verbose " mounting /var/lib/docker..." | |
sudo mkdir -p /var/lib/docker | |
sudo mount /var/lib/docker | |
fi | |
else | |
verbose "Docker partition already exists" | |
fi | |
} # 2}}} | |
function install_docker() { # {{{2 | |
verbose "Installing Docker Community Edition" | |
if [[ -n $(grep swap /etc/fstab) ]]; then | |
verbose "Turning off swap partitions as Docker does not support them" | |
sudo swapoff -a | |
sudo sed -i '/[ \t]swap[ \t]/s/^/#/' /etc/fstab | |
fi | |
add_docker_partition | |
add_repo_docker ; on_error_die "Failed to add Docker repository" | |
#apt-cache madison docker-ce | |
# grep 18.09 | head -1 | | |
#install docker-ce=5:18.09.8~3-0~ubuntu-bionic | |
install docker-ce 5:18.09.8~3-0~ubuntu-bionic | |
if [[ $OS_FLAVOR == 'redhat' ]]; then | |
install docker-ce-cli | |
install containerd.io | |
fi | |
if [[ ! -f /etc/docker/daemon.json ]]; then | |
verbose "Configuring Docker daemon" | |
[[ ! -d /etc/docker ]] && $SNOOP $SUDO mkdir -p /etc/docker | |
sudo tee /etc/docker/daemon.json > /dev/null <<EOF | |
{ | |
"exec-opts": ["native.cgroupdriver=systemd"], | |
"log-driver": "json-file", | |
"log-opts": { | |
"max-size": "100m" | |
}, | |
"storage-driver": "overlay2" | |
} | |
EOF | |
[[ ! -d /etc/systemd/system/docker.service.d ]] && $SNOOP $SUDO mkdir -p /etc/systemd/system/docker.service.d | |
$NOOP $SUDO systemctl daemon-reload | |
$NOOP systemctl is-active docker &> /dev/null || (verbose " Restarting Docker" ; $SUDO systemctl restart docker) | |
fi | |
$NOOP systemctl is-enabled docker &> /dev/null || (verbose " Enabling Docker" ; $SUDO systemctl enable docker) | |
$NOOP systemctl is-active docker &> /dev/null || (verbose " Starting Docker" ; $SUDO systemctl start docker) | |
sudo usermod -aG docker ${USER} | |
# TODO: we should test Docker with something like docker run hello-world | |
} # 2}}} | |
function usage() { | |
echo "$(basename $0) [options]" | |
echo " Installs a Kubernetes cluster with kubeadm" | |
echo " Options are:" | |
echo "" | |
echo " --join ip-address:port" | |
echo " Joins a Kubernetes Cluster as a Worker node." | |
echo " When used, --discovery-token-ca-cert-hash and --token are also mandatory" | |
echo " --discovery-token-ca-cert-hash hash-value" | |
echo " The hash of the CA Certificate to allow traffic with the Kubernetes master" | |
echo " --token token-value" | |
echo " The token used to join the Kubernetes cluster" | |
echo " --metallb addr1-addr2" | |
echo " Installs the MetalLB LoadBalancer and reserve addresses from addr1 to addr2" | |
echo " The IP range should be in the same range at the main NIC of the server" | |
echo " CANNOT be used when --join is in effect" | |
echo " --help " | |
echo " Prints some help on the output." | |
echo " --noop, --dry-run " | |
echo " Do not execute instructions that would make changes to the system (write files, install software, etc)." | |
echo " --quiet " | |
echo " Runs the script as silently as possible." | |
echo " --verbose " | |
echo " Runs the script verbosely, that's by default." | |
echo " --show-trace, --show_trace" | |
echo " Displays the content of the install log" | |
echo " --yes, --assumeyes, -y " | |
echo " Answers yes to any questions automatiquely." | |
} | |
function parse_args() { # {{{2 | |
while (( "$#" )); do | |
# Replace --parm=arg with --parm arg | |
[[ $1 == --*=* ]] && set -- "${1%%=*}" "${1#*=}" "${@:2}" | |
trace "Analyzing option \"$1\"" | |
case $1 in | |
--discovery-token-ca-cert-hash|--hash) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die "Argument for option $1 is missing" | |
K8S_CERT_HASH=$2 | |
shift 2 | |
continue | |
;; | |
--join) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die "Argument for option $1 is missing" | |
K8S_JOIN=$2 | |
shift 2 | |
continue | |
;; | |
--metallb) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die "Argument for option $1 is missing" | |
WITH_METALLB=1 | |
METALLB_IP_RANGE=$2 | |
shift 2 | |
continue | |
;; | |
--token) | |
[[ -z $2 || ${2:0:1} == '-' ]] && die "Argument for option $1 is missing" | |
K8S_TOKEN=$2 | |
shift 2 | |
continue | |
;; | |
--force) | |
warn "This program will overwrite the current configuration" | |
FORCE=1 | |
;; | |
-h|-\?|--help) | |
trace "Showing usage" | |
usage | |
exit 0 | |
;; | |
--noop|--dry_run|--dry-run) | |
warn "This program will execute in dry mode, your system will not be modified" | |
NOOP=: | |
;; | |
--quiet) | |
VERBOSE=0 | |
trace "Verbose level: $VERBOSE" | |
;; | |
--show_trace|--show-trace) | |
SHOW_TRACE=1 | |
;; | |
-v|--verbose) | |
VERBOSE=$((VERBOSE + 1)) | |
trace "Verbose level: $VERBOSE" | |
;; | |
-y|--yes|--assumeyes|--assume_yes|--assume-yes) # All questions will get a "yes" answer automatically | |
ASSUMEYES=1 | |
trace "All prompts will be answered \"yes\" automatically" | |
;; | |
-?*) # Invalid options | |
warn "Unknown option $1 will be ignored" | |
;; | |
--) # Force end of options | |
shift | |
break | |
;; | |
*) # End of options | |
break | |
;; | |
esac | |
shift | |
done | |
# Set all positional arguments back in the proper order | |
eval set -- "${ARGS[@]}" | |
# Validation | |
if [[ -n $K8S_JOIN ]]; then | |
[[ -z $K8S_TOKEN ]] && die "Missing token to join $K8S_JOIN" | |
[[ -z $K8S_CERT_HASH ]] && die "Missing Certificate Hash to join $K8S_JOIN" | |
[[ -n $WITH_METALLB ]] && warn "Option --metallb will be ignored!" | |
trace "Join Cluster with Master: $K8S_JOIN" | |
trace "Token: $K8S_TOKEN" | |
trace "Cert Hash: $K8S_CERT_HASH" | |
fi | |
return 0 | |
} # 2}}} | |
function main() { # {{{2 | |
trace_init ; on_error_die "Failed to initialize tracing" | |
check_os ; on_error_die "Failed to check the OS" | |
check_oslicense ; on_error_die "Failed to Check the OS License" | |
parse_args "$@" ; on_error_die "Failed to parse command line" | |
sudo_init ; on_error_die "Failed to initialize sudo" | |
add_repo_ansible ; on_error_die "Failed to add Ansible repository" | |
install_prereq ; on_error_die "Failed to install pre-requisites" | |
update_packages ; on_error_die "Failed to update packages" | |
MGT_NIC=$(ip -o link show | awk '{print $2,$9}' | grep -v docker | grep UP | cut -f1 -d:) | |
configure_network ; on_error_die "Failed to configure network" | |
install_docker ; on_error_die "Failed to install Docker" | |
verbose "Installing Kubernetes" | |
add_repo_kubernetes ; on_error_die "Failed to add Kubernetes repository" | |
install kubeadm | |
install kubectl | |
install kubernetes-cni | |
$NOOP systemctl is-enabled kubelet &> /dev/null || (verbose " Enabling Kubelet" ; $SUDO systemctl enable kubelet) | |
$NOOP systemctl is-active kubelet &> /dev/null || (verbose " Starting Kubelet" ; $SUDO systemctl start kubelet) | |
if [[ -n $K8S_JOIN ]]; then | |
install kubelet | |
if systemctl is-active firewalld.service >& /dev/null; then | |
verbose "Configuring the OS firewall" | |
sudo firewall-cmd --permanent --add-port=10250/tcp | |
sudo firewall-cmd --permanent --add-port=10251/tcp | |
sudo firewall-cmd --permanent --add-port=10255/tcp | |
sudo firewall-cmd --reload | |
# In the end, let's stop it... :( | |
sudo systemctl disable firewalld.service | |
sudo systemctl stop firewalld.service | |
fi | |
# Let's configure an extremely simple storage: hostpath | |
# Later, I would like to use rook.io | |
sudo mkdir -p /var/lib/kubernetes/default-storage | |
verbose "Kubernetes Worker will join its cluster at $K8S_JOIN" | |
sudo kubeadm join ${K8S_JOIN} --token $K8S_TOKEN --discovery-token-ca-cert-hash $K8S_CERT_HASH | |
on_error_die "Failed to join ${K8S_JOIN}" | |
else | |
if systemctl is-active firewalld.service >& /dev/null; then | |
verbose "Configuring the OS firewall" | |
sudo firewall-cmd --permanent --add-port=6443/tcp | |
sudo firewall-cmd --permanent --add-port=2379-2380/tcp | |
sudo firewall-cmd --permanent --add-port=10250/tcp | |
sudo firewall-cmd --permanent --add-port=10251/tcp | |
sudo firewall-cmd --permanent --add-port=10252/tcp | |
sudo firewall-cmd --permanent --add-port=10255/tcp | |
sudo firewall-cmd --reload | |
# In the end, let's stop it... :( | |
sudo systemctl disable firewalld.service | |
sudo systemctl stop firewalld.service | |
fi | |
#if [[ -z $(docker ps | grep k8s) ]]; then | |
verbose "Creating Master Node" | |
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 | tee -a kubeadm-init.log | |
#fi | |
[[ -d $HOME/.kube ]] || mkdir -p $HOME/.kube | |
[[ -r $HOME/.kube/config ]] || \ | |
(sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && \ | |
sudo chown $(id -u):$(id -g) $HOME/.kube/config) | |
[[ -r /etc/bash_completion.d/kubectl ]] || kubectl completion bash | $SUDO tee /etc/bash_completion.d/kubectl >/dev/null | |
if ! grep 'alias kb' ~/.bashrc; then | |
tee -a .bashrc <<EOM | |
alias kb=kubectl | |
complete -F __start_kubectl kb | |
EOM | |
fi | |
# Let's configure an extremely simple storage: hostpath | |
# Later, I would like to use rook.io | |
STORAGE_LOCATION=/var/lib/kubernetes/default-storage | |
sudo mkdir -p $STORAGE_LOCATION | |
kubectl create serviceaccount --namespace kube-system hostpath | |
kubectl create clusterrole hostpath \ | |
--verb list,get,watch,update --resource persistentvolumeclaims \ | |
--verb create,list,get,watch,update,delete --resource persistentvolumes \ | |
--verb create,list,patch --resource events \ | |
--verb list,watch --resource storageclasses | |
kubectl create clusterrolebinding hostpath \ | |
--clusterrole hostpath \ | |
--serviceaccount kube-system:hostpath | |
kubectl apply -f-<<EOM | |
kind: Deployment | |
apiVersion: apps/v1 | |
metadata: | |
namespace: kube-system | |
name: hostpath-provisioner | |
labels: | |
k8s-app: hostpath-provisioner | |
spec: | |
replicas: 1 | |
revisionHistoryLimit: 0 | |
selector: | |
matchLabels: | |
k8s-app: hostpath-provisioner | |
template: | |
metadata: | |
labels: | |
k8s-app: hostpath-provisioner | |
spec: | |
serviceAccountName: hostpath | |
containers: | |
- name: hostpath-provisioner | |
image: cdkbot/hostpath-provisioner-amd64:1.0.0 | |
env: | |
- name: NODE_NAME | |
valueFrom: | |
fieldRef: | |
fieldPath: spec.nodeName | |
- name: PV_DIR | |
value: $STORAGE_LOCATION | |
# - name: PV_RECLAIM_POLICY | |
# value: Retain | |
volumeMounts: | |
- name: pv-volume | |
mountPath: $STORAGE_LOCATION | |
volumes: | |
- name: pv-volume | |
hostPath: | |
path: $STORAGE_LOCATION | |
--- | |
kind: StorageClass | |
apiVersion: storage.k8s.io/v1 | |
metadata: | |
name: hostpath | |
annotations: | |
storageclass.kubernetes.io/is-default-class: "true" | |
provisioner: hostpath | |
EOM | |
if ! kubectl get daemonset --namespace kube-system kube-flannel-ds-amd64 >& /dev/null; then | |
verbose "Installing Flannel" | |
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml |& trace_output | |
fi | |
verbose "Installing the Ingress Controller 0.34.1" | |
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml |& trace_output | |
if [[ -n $WITH_METALLB ]]; then | |
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml | |
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml | |
# On first install only | |
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" | |
kubectl apply -f - << EOF | |
apiVersion: v1 | |
kind: ConfigMap | |
metadata: | |
namespace: metallb-system | |
name: config | |
data: | |
config: | | |
address-pools: | |
- name: default | |
protocol: layer2 | |
addresses: | |
- $METALLB_IP_RANGE | |
EOF | |
fi | |
kubectl get nodes | |
if [[ -z $(grep 'tinyurl' ./kubeadm-init.log) ]]; then | |
K8S_JOIN=$(awk '/^kubeadm join/ { print $3 }' ./kubeadm-init.log) | |
K8S_TOKEN=$(awk '/^kubeadm join/ { print $5 }' ./kubeadm-init.log) | |
K8S_CERT_HASH=$(awk '/^[ ]+--discovery-token-ca-cert-hash/ { print $2 }' ./kubeadm-init.log) | |
echo "To join a worker node with this script, run:" | tee -a ./kubeadm-init.log | |
echo "curl -sSL https://tinyurl.com/install-k8s | bash -s -- --join $K8S_JOIN --token $K8S_TOKEN --discovery-token-ca-cert-hash $K8S_CERT_HASH" | tee -a ./kubeadm-init.log | |
else | |
echo "To join a worker node with this script, run:" | |
grep 'tinyurl' kubeadm-init.log | |
fi | |
fi | |
verbose "For the completion to work with kubectl (and kb), you might need to logoff and log back in" | |
verbose "Installation Logs: ${LOG}" | |
} # 2}}} | |
main "$@" |
Well, now that the script works on CentOS and Ubuntu, the new curl is like:
$ curl -sSL https://tinyurl.com/install-k8s | bash -s
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run it, on the (future) master:
$ curl -sSL https://tinyurl.com/ubuntu-prep-k8s | bash -s
Then follow the instructions to install the worker nodes.