Для этой лаборатории я создам 4 ВМ. Инсталяция будет небольшая, один мастер, одна ингресс нода и 2 воркер ноды. Kubespray предлагает также различные addons, которые можно поставить вместе с кластером. Поэтому мы включим metric server, ingress контроллер и metallb. Некоторое из этого я описывал в предыдущих статьях на сайте. Сейчас мы понимаем как это работает и можем опустить работы с каждым компонентом по отдельности. Вообще в addons есть много всего еще, но для стартового setup-а хватит и этого.
Создадим Vagrantfile следующего содержания.
servers = [
{ hostname: "k8s-master", ip: "192.168.10.10", ssh_port: 2228 },
{ hostname: "k8s-worker1", ip: "192.168.10.11", ssh_port: 2223 },
{ hostname: "k8s-worker2", ip: "192.168.10.12", ssh_port: 2224 },
{ hostname: "k8s-ingress1", ip: "192.168.10.13", ssh_port: 2226 },
]
Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-22.04"
config.vm.box_check_update = false
config.vm.provider "virtualbox" do |vb|
vb.gui = false
vb.memory = 3096
vb.cpus = 2
vb.check_guest_additions = false
end
servers.each do |machine|
config.vm.define machine[:hostname] do |node|
node.vm.hostname = machine[:hostname]
node.vm.network "public_network", ip: machine[:ip], bridge: "wlo1"
node.vm.network "forwarded_port", id: "ssh", host: machine[:ssh_port], guest: 22
if machine[:hostname] == "k8s-master"
node.vm.provider "virtualbox" do |vb|
vb.memory = 2048
end
end
node.vm.provider "virtualbox" do |vb|
disk_name = "#{machine[:hostname]}_disk.vdi"
disk_path = "/vm_data/k8s/#{disk_name}"
unless File.exist?(disk_path)
system("VBoxManage", "createhd", "--filename", disk_path, "--size", "40720")
end
controller_name = "SATA Controller"
vb.customize [
"storageattach", :id,
"--storagectl", controller_name,
"--port", "1",
"--device", "0",
"--type", "hdd",
"--medium", disk_path
]
end
end
end
end
Тут я выделяю отдельный диск на 40 гигов под всякие нужны k8s. Например туда будет смонтирован /var/lib как дефолт где kubelet будет запускать контейнеры и тома для longhorn, о котором я уже писал ранее.
Запускаем и заходим на первый воркер:
vagrant up
vagrant ssh k8s-worker1
У меня картина по дискам выглядит вот так:
sdb наш диск, который мы создали из Vagrantfile. Также уже есть VG том. Для начала разметим диск:
fdisk /dev/sdb
n
p
w
После разметки будет доступен раздел sdb1. Создадим там файловую систему:
mkfs.ext4 /dev/sdb1
Для LVM я буду использовать уже существующую volume группу, которую vagrant любезно создал автоматически. Для этого нужно ее расширить, а именно добавить наш раздел, сказать чтобы текущая группа томов его использовала и потом создать новый логический том.
Узнать имя текущей группы можно командой vgs.
Добавляем наш физический диск в абстракцию LVM и расширяем текущую группу томов на этот диск, также создаем новый логический том.
pvcreate /dev/sdb1
vgextend ubuntu-vg /dev/sdb1
lvcreate -L50G -n var_lib ubuntu-vg
В итоге получаем что-то вроде:
Далее создадим также файловую систему и смонтируем том. Но перед этим, у меня в /var/lib естественно были уже данные, их нужно скопировать куда-то, например в отдельную директорию в /tmp и после монтирования, скопировать обратно. При установке не на Vagrant образ такого не будет, если изначально диски на машине будут разбиты так, как нужно.
mkfs.ext4 /dev/ubuntu-vg/var_lib
mount /dev/ubuntu-vg/var_lib /var/lib/
cat /etc/mtab | tail -n1 >> /etc/fstab
Эту же операцию нужно проделать на оставшихся машинах.
Тут уже можно накидать скрипт для автоматизации или написать ansible роль.
#!/bin/bash
set -e
if [[ $EUID -ne 0 ]]; then
echo "Ошибка: Этот скрипт должен быть выполнен от имени root."
exit 1
fi
DEVICE="/dev/sdb"
PARTITION="${DEVICE}1"
VG="ubuntu-vg"
LV="var_lib"
MOUNT_POINT="/var/lib"
TMP_DIR="/tmp/var"
log() {
echo "[$(date +'%Y-%m-%d\ %H:%M:%S')] $1"
}
log "Создание нового раздела на ${DEVICE}..."
(
echo n # Создать новый раздел
echo p # Тип: первичный
echo 1 # Номер раздела
echo # Первый сектор по умолчанию
echo # Последний сектор по умолчанию
echo w # Записать изменения и выйти
) | fdisk "$DEVICE"
partprobe "$DEVICE"
log "Форматирование ${PARTITION} в файловую систему ext4..."
mkfs.ext4 "$PARTITION"
log "Создание физического тома на ${PARTITION}..."
pvcreate "$PARTITION" -y
log "Расширение группы томов ${VG} с использованием ${PARTITION}..."
vgextend "$VG" "$PARTITION"
log "Создание логического тома ${LV} размером 50G в группе томов ${VG}..."
lvcreate -L50G -n "$LV" "$VG"
LV_PATH="/dev/${VG}/${LV}"
log "Форматирование логического тома ${LV_PATH} в файловую систему ext4..."
mkfs.ext4 "$LV_PATH"
log "Копирование содержимого ${MOUNT_POINT} в ${TMP_DIR} с сохранением прав доступа..."
mkdir -p "$TMP_DIR"
cp -a "${MOUNT_POINT}/." "$TMP_DIR/"
log "Монтирование ${LV_PATH} в ${MOUNT_POINT}..."
mount "$LV_PATH" "$MOUNT_POINT"
log "Добавление записи о монтировании ${LV_PATH} в /etc/fstab..."
cat /etc/mtab | tail -n1 >> /etc/fstab
log "Восстановление содержимого из ${TMP_DIR} обратно в ${MOUNT_POINT}..."
cp -a "${TMP_DIR}/." "$MOUNT_POINT/"
log "Очистка временной директории ${TMP_DIR}..."
rm -rf "$TMP_DIR"
log "Автоматизация завершена успешно."
И можно просто запустить данный скрипт на всех машинах.
Далее я положу свой открытый ssh ключ на эти машины в /root/.ssh/authorized_keys.
На этом подготовительный этап для самих ВМ закончен.
Далее нам нужно скачать kubespray и подготовить его к установке kubernetes.
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
VENVDIR=kubespray-venv
KUBESPRAYDIR=kubespray
python3 -m venv $VENVDIR
source $VENVDIR/bin/activate
pip install -U -r requirements.txt
pip install ruamel.yaml ### Без этого модуля не будет работать
Далее нужно создать директорию с inventory под наш будущий кластер.
cp -rfp inventory/sample inventory/kubernetes.dev.local
Можно воспользоваться билдером inventory файла, но я просто сразу же приложу готовый hosts.yml
all:
hosts:
master:
ansible_host: 192.168.10.10
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/home_virt
ip: 192.168.10.10
access_ip: 192.168.10.10
worker1:
ansible_host: 192.168.10.11
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/home_virt
ip: 192.168.10.11
access_ip: 192.168.10.11
node_labels:
node-role.kubernetes.io/worker: ""
worker2:
ansible_host: 192.168.10.12
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/home_virt
ip: 192.168.10.12
access_ip: 192.168.10.12
node_labels:
node-role.kubernetes.io/worker: ""
ingress1:
ansible_host: 192.168.10.13
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/home_virt
ip: 192.168.10.13
access_ip: 192.168.10.13
node_labels:
node-role.kubernetes.io/ingress: ""
node_taints:
- "node-role.kubernetes.io/ingress=:NoSchedule"
children:
kube_control_plane:
hosts:
master:
kube_node:
hosts:
worker1:
worker2:
ingress1:
etcd:
hosts:
master:
k8s_cluster:
children:
kube_control_plane:
kube_node:
calico_rr:
hosts: {}
Я добавил пользователя и ssh ключ для авторизации на серверах, а также добавил кастомный лейбл на ingress ноду и taint для нее же, чтобы только определенные поды, это будет ingress controller и speaker metallb, могли быть размещены на этой ноде.
Далее нунжно отредактировать inventory/kubernetes.dev.local/group_vars/all/all.yml. Ниже я буду у всех измененных файлов приводить собственно только сами изменения.
upstream_dns_servers:
- 8.8.8.8
- 192.168.10.100
ntp_enabled: true
ntp_manage_config: true
ntp_servers:
- "0.pool.ntp.org iburst"
- "1.pool.ntp.org iburst"
- "2.pool.ntp.org iburst"
- "3.pool.ntp.org iburst"
192.168.10.100 — это мой локальный DNS сервер.
Далее редактируем inventory/kubernetes.dev.local/group_vars/k8s_cluster/addons.yml
helm_enabled: true
metrics_server_enabled: true
ingress_nginx_enabled: true
ingress_nginx_host_network: true
ingress_nginx_service_type: LoadBalancer
ingress_publish_status_address: ""
ingress_nginx_nodeselector:
node-role.kubernetes.io/ingress: ""
ingress_nginx_tolerations:
- key: "node-role.kubernetes.io/ingress"
operator: "Exists"
effect: "NoSchedule"
ingress_nginx_namespace: "ingress-nginx"
ingress_nginx_insecure_port: 80
ingress_nginx_secure_port: 443
ingress_nginx_class: nginx
metallb_enabled: true
metallb_speaker_enabled: "{{ metallb_enabled }}"
metallb_namespace: "metallb-system"
metallb_version: v0.13.9
metallb_protocol: "layer2"
metallb_port: "7472"
metallb_memberlist_port: "7946"
metallb_config:
speaker:
nodeselector:
kubernetes.io/os: "linux"
tolerations:
- key: "node-role.kubernetes.io/ingress"
operator: "Exists"
effect: "NoSchedule"
controller:
nodeselector:
kubernetes.io/os: "linux"
address_pools:
primary:
ip_range:
- 192.168.10.101/32
auto_assign: true
layer2:
- primary
На этом с аддонами можно закончить. Я ставлю metric server, ingress controller на базе nginx и metallb. Последний выдаст адрес только для ingress svc и этого будет достаточно, поэтому 32 маска (один адрес).
Редактируем inventory/kubernetes.dev.local/group_vars/k8s_cluster/k8s-cluster.yml
kube_network_plugin: flannel
kube_proxy_mode: ipvs
kube_proxy_strict_arp: true
dns_mode: coredns
enable_nodelocaldns: true
enable_nodelocaldns_secondary: false
nodelocaldns_ip: 169.254.25.10
nodelocaldns_health_port: 9254
nodelocaldns_second_health_port: 9256
nodelocaldns_bind_metrics_host_ip: false
nodelocaldns_secondary_skew_seconds: 5
Так как я использую vagrant, то flannel по умолчанию возьмет NAT интерфейс для сети, это не подойдет и сеть в кластере толком не будет работать при таком варианте, поэтому изменим это в roles/network_plugin/flannel/defaults/main.yml
flannel_interface: eth1
На этом можно закончить с настройкой и приступить к установке.
ansible-playbook -i inventory/kubernetes.dev.local/hosts.yml --become --become-user=root cluster.yml
И я сразу поймал ошибку:
Это из-за того, что kubespray использует также и свои модули для ansible, а сам ansible я использую тот, что установлен на хосте, а не из requirements.txt, приложенного в репе. В документации советуют переопределить переменные модулей, давайте посмотрим куда смотрит ansible установленный через pip внутри venv.
pip show ansible
Нужно взять путь из Location:
И выполнить экспорт нужных переменных:
export ANSIBLE_LIBRARY=/home/andrei/kubespray/kubespray-venv/lib/python3.10/site-packages/ansible/modules
export ANSIBLE_MODULE_UTILS=/home/andrei/kubespray/kubespray-venv/lib/python3.10/site-packages/ansible/module_utils
После перезапуска, лично у меня ошибка сохранилась, поэтому я также переопределил PYTHONPATH:
export PYTHONPATH=/home/andrei/kubespray/kubespray-venv/lib/python3.10/site-packages:$PYTHONPATH
После чего запускаем заново:
ansible-playbook -i inventory/kubernetes.dev.local/hosts.yml --become --become-user=root cluster.yml
Установка должна пройти успешно. На master ноде можно забрать админский kubeconfig, он будет лежать в /etc/kubernetes/admin.conf. Для использования его не с мастер ноды, нужно поменять внутри файла адрес до api сервера с 127.0.0.1 на тот, который соответствует мастер ноде.
Установим kubernetes dashboard:
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
helm --kubeconfig ~/local_k8s.conf upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
Создадим ингресс:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: kubernetes-dashboard
name: kubernetes-dashboard-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- k8s-dashboard.dev.local
rules:
- host: k8s-dashboard.dev.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubernetes-dashboard-web
port:
number: 8000
kubectl apply -f dash.yaml
Ингресс работает.
Можно установить и другие компоненты, например longhorn и keycloak, о них есть статьи на сайте. Только в этой инсталяции у longhorn defaultDataPath лучше оставить по умолчанию, он как раз будет в нашей точке монтирования — /var/lib.
Проверить работу metric server:
kubectl top node
kubectl top pod -n kube-system