Всем привет сегодня разберемся в установке kubeadm в данном примере будет 3 master ноды и 3 worker ноды. Данный разбор будет производиться на ubuntu 24.04, все 6 серверов будут в одной сети. Ну чтож начнем P.s все этапы выполняем на всех серверах если не сказанно обратного.
1. Подготовка системы на сервера.
1.1 обновление системы здесь долго не задерживаемся

sudo apt update
sudo apt upgrade -y

1.2 Устанавливаем пакеты

sudo apt-get install -y \
  curl \
  wget \
  vim \
  net-tools \
  htop \
  git

1.3 Отключаем swap(хоть это уже не обезательно кубер уже вроде как работает с ним но делаем по инерции :) )

sudo swapoff -a
#это  Удали swap из fstab чтобы он не включался при перезагрузке
sudo sed -i '/ swap / s/^/#/' /etc/fstab

1.3.1 при ошибках или проверки используйте это, а именно вручную отредактируйте файл:

sudo nano /etc/fstab

Найти строки типа:

UUID=xxxx-xxxx none swap sw 0 0

И добавить # в начало.

1.4 Настройка сети

Включи модули ядра для Kubernetes:

# Эти модули нужны для контейнеризации и сетевых возможностей
sudo modprobe overlay
sudo modprobe br_netfilter

# Сделай это постоянным
sudo tee /etc/modules-load.d/kubernetes.conf > /dev/null <<EOF
overlay
br_netfilter
EOF

Настрой параметры ядра:

sudo tee /etc/sysctl.d/99-kubernetes.conf > /dev/null <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.ipv4.ip_nonlocal_bind = 1
EOF

sudo sysctl --system

Проверь эти значения (все должны быть 1):

sysctl net.bridge.bridge-nf-call-iptables
sysctl net.bridge.bridge-nf-call-ip6tables
sysctl net.ipv4.ip_forward

1.5 Установить Docker

Добавь репозиторий Docker:

Curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

Установи Docker:

sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Добавь своего пользователя в группу docker (опционально, но удобно):

sudo usermod -aG docker $USER
newgrp docker

Включи Docker:

sudo systemctl enable docker
sudo systemctl start docker

Проверь что Docker работает:

docker run hello-world

1.5 Установи kubeadm, kubelet, kubectl

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update

Включи kubelet (но НЕ запускай его пока!):

sudo systemctl enable kubelet

2.Настройка Keepalived(этот процесс отвечает за отказоустойчевость мастер нод на сетевом уровне если в вашем примере 1 master нода и вы не планируете ставить ещё тогда этот момент смело пропускайте.) всё будет дальше прописанно использовать на всех 3 master нодах если не сказанно обратного.
2.1 установка

sudo apt-get install -y keepalived psmisc

2.2 Включение forwarding для Keepalived

# Это нужно чтобы Keepalived мог управлять VIP
sudo sysctl -w net.ipv4.ip_nonlocal_bind=1

Сделай это постоянным:

sudo tee -a /etc/sysctl.conf > /dev/null <<EOF
net.ipv4.ip_nonlocal_bind=1
EOF

sudo sysctl -p

2.3 Делаем конфигурацию.

Создаём

sudo vim /etc/keepalived/keepalived.conf

и вставляем/заменяем на

global_defs {
    router_id KUBE_MASTER1   # меняется на каждой ноде
    script_user root
    enable_script_security
}

vrrp_script check_apiserver {
    script "/etc/keepalived/check_apiserver.sh"
    interval 2
    weight -30
    fall 3
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens160
    virtual_router_id 51
    priority 100
    advert_int 1
    nopreempt

    authentication {
        auth_type PASS
        auth_pass keepalived123
    }

    virtual_ipaddress {
        x.x.x.x/24 dev ens160
    }

    track_script {
        check_apiserver
    }
}

2.4 создаём скрипт создаем в /etc/keepalived/check_apiserver.sh

#!/bin/bash

curl -k --silent --max-time 2 https://127.0.0.1:6443/healthz | grep -q ok

if [ $? -eq 0 ]; then
    exit 0
else
    exit 1
fi

Делаем его исполняемым

chmod +x /etc/keepalived/check_apiserver.sh

2.5 теперь внимательно делаем конфигурацию на 2 и 3 worker ноде. Месторасположение тот же шаблон тот же единственное изменение это "router_id KUBE_MASTER1" меняем по нумерации ваших мастер нод.

upd. Все control-plane ноды настроены с одинаковым priority и отключённым preempt для предотвращения повторного захвата VIP и исключения двойного failover.

2.6 запускаем keeplived

sudo systemctl enable keepalived
sudo systemctl start keepalived

Проверь статус:

sudo systemctl status keepalived
# Должно быть: active (running)

2.7 Проверяем как работает на 1 master ноде

ip addr show ens160
# если вы указывали другой интерфейс то поменяйте на свой

ну и пустите пинг с другой ноды до вашего vIP

3.Инициализация первой Master ноды (на этом этапе делаем все на master нрде 1 если не сказоно другого)

создай файл:

sudo vim /tmp/kubeadm-config.yaml

Содержимое (замени IP адреса):

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.35.0
controlPlaneEndpoint: "x.x.x.x:6443"
# ↑ Это VIP - точка входа для всего кластера

certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes

networking:
  dnsDomain: cluster.local
  podSubnet: "192.168.0.0/16"
  # ↑ Pod CIDR - диапазон IP для контейнеров в подах
  # Calico будет использовать этот диапазон
  serviceSubnet: "10.96.0.0/12"
  # ↑ Service CIDR - для Kubernetes сервисов

controllerManager:
  extraArgs:
    bind-address: "0.0.0.0"

scheduler:
  extraArgs:
    bind-address: "0.0.0.0"

etcd:
  local:
    dataDir: /var/lib/etcd
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd

Объяснение важных параметров:

Параметр

Значение

Зачем

controlPlaneEndpoint

x.x.x.x:6443

VIP для доступа к Kubernetes API

podSubnet

192.168.0.0/16

IP адреса для подов (контейнеров)

serviceSubnet

10.96.0.0/12

IP адреса для Kubernetes сервисов

cgroupDriver

systemd

Как контролировать ресурсы контейнеров

3.1  Инициализируй кластер (если у вас ошибка то смотри 3.1.2)

sudo kubeadm init --config=/tmp/kubeadm-config.yaml --upload-certs

Что происходит:

  • 📝 Создаются сертификаты для безопасности

  • 🔑 Генерируются ключи для authentication

  • 📦 Распаковываются Docker образы Kubernetes

  • ⚙️ Запускаются компоненты: API Server, etcd, Controller Manager, Scheduler

  • 🔓 --upload-certs - загружает сертификаты для других master нод

ВАЖНО: Дождись конца! Это может занять 2-5 минут.

Вывод будет примерно такой:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run (as a regular user):

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-setup/addons-available/

You can now join any number of control-plane nodes by running the following command on each as root:
  kubeadm join 10.202.4.111:6443--token xxxxx.xxxxxxxxxxxxx \
        --discovery-token-ca-cert-hash sha256:xxxxx.... \
        --control-plane --certificate-key yyyyy....

Please save these tokens! You'll need them for other nodes.

3.1.2 этот раздел если у вас как и у меня возникла ошибка вида

W0207 15:44:07.467481   30644 common.go:100] your configuration file uses a deprecated API spec: "kubeadm.k8s.io/v1beta3" (kind: "ClusterConfiguration"). Please use 'kubeadm config migrate --old-config old-config-file --new-config new-config-file', which will write the new, similar spec using a newer API version.
[init] Using Kubernetes version: v1.35.0
[preflight] Running pre-flight checks
[preflight] Some fatal errors occurred:
	[ERROR CRI]: could not connect to the container runtime: failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
	[ERROR ContainerRuntimeVersion]: could not connect to the container runtime: failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
error: error execution phase preflight: preflight checks failed
To see the stack trace of this error execute with --v=5 or higher

я предположил что это из-за проблемы в том, что containerd версии 2.2.1 имеет проблемы с Kubernetes 1.35. Решаем это путем Использование Docker вместо containerd (сделать всё это надо на всех Master нодах)

# Скачай последнюю версию
VER=$(curl -s https://api.github.com/repos/Mirantis/cri-dockerd/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//g')

# Скачай архив
wget https://github.com/Mirantis/cri-dockerd/releases/download/v${VER}/cri-dockerd-${VER}.amd64.tgz

# Распакуй
tar xzf cri-dockerd-${VER}.amd64.tgz
sudo mv cri-dockerd/cri-dockerd /usr/local/bin/
rm -rf cri-dockerd cri-dockerd-${VER}.amd64.tgz

# Проверь установку
cri-dockerd --version

Создай systemd сервис для cri-dockerd

sudo vim /etc/systemd/system/cri-docker.service

Содержимое:

[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd --container-runtime-endpoint fd://
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutStopSec=120
RestartSec=5
Restart=always

[Install]
WantedBy=multi-user.target

Создай socket файл

sudo vim /etc/systemd/system/cri-docker.socket

Содержимое:

[Unit]
Description=CRI Docker Socket
PartOf=cri-docker.service

[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target

Запусти cri-dockerd

sudo systemctl daemon-reload
sudo systemctl enable cri-docker.service cri-docker.socket
sudo systemctl startcri-docker.service cri-docker.socket

Проверь:

sudo systemctl status cri-docker.service

Должно быть active (running)

после этого меняем конфигурацию кубера,

sudo vim /tmp/kubeadm-config.yaml
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.35.0
controlPlaneEndpoint: "x.x.x.x:6443"
# ↑ Это VIP - точка входа для всего кластера

certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes

networking:
  dnsDomain: cluster.local
  podSubnet: "192.168.0.0/16"
  # ↑ Pod CIDR - диапазон IP для контейнеров в подах
  # Calico будет использовать этот диапазон
  serviceSubnet: "10.96.0.0/12"
  # ↑ Service CIDR - для Kubernetes сервисов

controllerManager:
  extraArgs:
    bind-address: "0.0.0.0"

scheduler:
  extraArgs:
    bind-address: "0.0.0.0"

etcd:
  local:
    dataDir: /var/lib/etcd
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
# ↓ ДОБАВЬ ЭТО:
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
  criSocket: unix:///var/run/cri-dockerd.sock
  # ↑ Указываем что использовать cri-dockerd вместо containerd

и делаемин ициализацию снова

sudo kubeadm init --config=/tmp/kubeadm-config.yaml --upload-certs

3.2 Настрой kubectl 

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Проверь что kubectl работает:

kubectl get nodes

Должно быть:

NAME      STATUS   ROLES           AGE   VERSION
kubem1    NotReady control-plane   1m    v1.35.0

❌ NotReady - это НОРМАЛЬНО! Потому что сетевой плагин не установлен ещё.

3.3 Проверь компоненты

kubectl get pods -n kube-system

Должно быть примерно так:

NAME          READY   STATUS    
coredns-768b8dd64f-xxxxx         0/1     Pending   ← Ждёт сетевого плагина
coredns-768b8dd64f-xxxxx         0/1     Pending   
etcd-kubem1        1/1     Running   ✅
kube-apiserver-kubem1            1/1     Running   ✅
kube-controller-manager-kubem1   1/1     Running   ✅
kube-scheduler-kubem1    1/1     Running   ✅

Что это:

  • etcd - база данных (хранит конфиг всего кластера)

  • kube-apiserver - API сервер (управляет кластером)

  • kube-controller-manager- контролер (следит за состоянием)

  • kube-scheduler - планировщик (выбирает где запустить поды)

  • coredns - DNS (будет работать когда установим Calico)

3.4 Установи Calico (сетевой плагин)

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/tigera-operator.yaml

ждём 5 минут и приступаем

kubectl wait --for=condition=available --timeout=300s deployment/tigera-operator -n tigera-operator

Создай конфиг Installation для Calico

cat <<EOF | kubectl apply -f -
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
    - blockSize: 26
      cidr: 192.168.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()
EOF

Подожди пока Calico запустится

# Проверяй статус
kubectl get pods -n calico-system -w

Когда все поды будут в статусе Running - нажми Ctrl+C

# Должны быть примерно такие поды:
# calico-kube-controllers-xxxx - Running
# calico-node-xxxx - Running
# calico-typha-xxxx - Running (опционально)

Проверь что всё работает

# Проверь статус ноды
kubectl get nodes

Должно быть:

NAME      STATUS   ROLES           AGE   VERSION
kubem1    Ready    control-plane   20m   v1.35.0

✅ Ready - отлично!

Поздравляю вас мы смогли сделать если вы дошли до данного этапа значит сегодня точно доделаете )

4.добовляем остальные мастер ноды в кластер
Перед началом проверяем что бы всё было так как тут:

  •  kubectl get nodes показывает kubem1 как Ready

  •  kubectl get pods -n kube-system - все поды Running

  •  kubectl get pods -n calico-system - все поды Running

4.1 Получи команды для присоединения. Всё делаем на master ноде 1(Важно все это делаем для каждой уникальной ноде для добовление токены должны быть уникальными)

# Получи token для worker нод (НЕ нужны сертификаты)
kubeadm token create --print-join-command --ttl=30m

Вывод будет примерно:

kubeadm join 10.202.4.111:6443 --token abc123.def456 \
        --discovery-token-ca-cert-hash sha256:xxxxx....

СОХРАНИ эту команду - она нужна для worker нод позже!

Теперь получи команду для MASTER нод:

На kubem1:

bash# Загрузи сертификаты для других master нод
sudo kubeadm init phase upload-certs --upload-certs

Вывод будет:

I0207 16:30:15.xxx etcdserver: Serving insecure client requests on 127.0.0.1:2379, this is STRONGLY discouraged!
W0207 16:30:15.xxx Loads of warnings...

Certificates uploaded successfully.

[upload-certs] Storing the apiserver certificate in Secret in the cluster
[upload-certs] Uploading the apiserver CA certificate  in the cluster
...

Upload certificate key: abc123def456ghi789jkl000mnopqr

СКОПИРУЙ и СОХРАНИ Upload certificate key!

Создай полную команду для master нод Это команда для kubem2 и kubem3 (важно помнить что мы используем --cri-socket unix:///var/run/cri-dockerd.sock для запуска):

bash# Используй данные из предыдущих команд
# ЗАМЕНИ на свои значения!

sudo kubeadm join <API_SERVER_ENDPOINT> \
  --token <BOOTSTRAP_TOKEN> \
  --discovery-token-ca-cert-hash <CA_CERT_HASH> \
  --control-plane \
  --certificate-key <CERTIFICATE_KEY> \
  --cri-socket <CRI_SOCKET_PATH>
  --cri-socket unix:///var/run/cri-dockerd.sock

Ожидай вывод:

[preflight] Running pre-flight checks
[preflight] Found systemd as the boot cri driver
[download-certs] Downloading the certificates in Secret in the cluster
[certs]Using certificateDir at /etc/kubernetes/pki
[certs] Generating "front-proxy-ca" certificate and key
[certs] Valid certificates and keys now exist in /etc/kubernetes/pki
[certs] Using the existing "sa" key
[kubeconfig] Generating kubeconfig files
[kubeconfig] kubeconfig file for kubelet kubem2
[kubeconfig] kubeconfig file for admin kubem2
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubelets.conf"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest files from "/etc/kubernetes/manifests"
[upload-certs] Skipping phase. [certs] Not requested a certificate rotation
[mark-control-plane] Marking the node kubem2 as a control-plane by adding labels and taints.
[kubeadm-join] 1.35.0
[bootstrap-token] Using token: abc123...
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long lived certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrs approver controller automatically approve CSRs from a 'system:bootstrappers' group
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Thedefault bootstrap token generated by 'kubeadm init' has been deleted
[bootstrap-token] Bootstrap tokens have been deleted after cluster bootstrap. It's safe to reuse the bootstrap token for new nodes.

This node has joined the cluster and a new control plane instance was created successfully.

Before completing cluster setup, you need to ensure that your cluster deploys a pod network.
It is now safe to move forward without the drain/uncordon steps.

✅ Успешно присоединился!

Копируй конфиг kubectl правильно на 2 и 3 master ноду

# На kubem1
scp /home/kube_m1/.kube/config kube_m2@ip/tmp/config

# На kubem2 и на 3 мастер ноде всё тоже самое
mkdir -p $HOME/.kube
cp /tmp/config $HOME/.kube/config
chmod 600 $HOME/.kube/config

 Проверь на kubem2 и kubem3

# На kubem2
kubectl get nodes

# Должно быть:
# NAME      STATUS   ROLES AGE     VERSION
# kubem1 Ready  control-plane   40m  v1.35.0
# kubem2    Ready    control-plane   15m     v1.35.0
# kubem3    Ready    control-plane   10m     v1.35.0

5.Добавление Worker нод (kubew1, kubew2, kubew3)

на них(воркер нодах) должно быть установленно всё тоже самое что и на мастер нодах 2 и 3 (отключенный swap,  Включи модули ядра и настроенны, cri-dockerd и kubeadm, kubelet, kubectl, Docker установлены )
конфиги тоже должны быть они одинаковые

5.1

Получи токен для Worker нод(Получим токен присоединения (без флага --control-plane))

**На мастер 1

# Создай новый токен для worker нод
kubeadm token create --print-join-command --ttl=30m

и точно так же присоединяем

p.s как выглядит ещё раз команда присоединение

Пояснение параметров:

sudo kubeadm join <API_SERVER_ENDPOINT> \
  --token <BOOTSTRAP_TOKEN> \
  --discovery-token-ca-cert-hash <CA_CERT_HASH> \
  --control-plane \
  --certificate-key <CERTIFICATE_KEY> \
  --cri-socket <CRI_SOCKET_PATH>
  1. <API_SERVER_ENDPOINT> - адрес и порт control-plane ноды

    Как получить: kubectl cluster-info на control-plane

  2. <BOOTSTRAP_TOKEN> - временный токен для аутентификации

    Как получить на control-plane: kubeadm token create --print-join-command

  3. <CA_CERT_HASH> - хэш CA сертификата кластера

    Как получить на control-plane: openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex

  4. <CERTIFICATE_KEY> - ключ для расшифровки сертификатов

    Как получить на control-plane: kubeadm init phase upload-certs --upload-certs

  5. <CRI_SOCKET_PATH> - путь к сокету container runtime

    • Варианты:

      • Для containerd: unix:///var/run/containerd/containerd.sock

      • Для Docker с cri-dockerd: unix:///var/run/cri-dockerd.sock

    • Как определить: sudo systemctl status containerd docker cri-docker

ну вот и всё по итогу должно получиться вот так