Skip to content

Instantly share code, notes, and snippets.

@gildas
Last active August 13, 2020 08:48
Show Gist options
  • Save gildas/a06b800f300d4c176d70f73bf8edbc02 to your computer and use it in GitHub Desktop.
Save gildas/a06b800f300d4c176d70f73bf8edbc02 to your computer and use it in GitHub Desktop.
install-k8s.sh
#!/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 "$@"
@gildas
Copy link
Author

gildas commented Mar 26, 2018

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.

@gildas
Copy link
Author

gildas commented Sep 3, 2019

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