Перейти к основному содержимому

Kubernetes The Hard Way: Workers

· 7 мин. чтения

Kubernetes THW: Workers #

Продолжаем Kubernetes The Hard Way: подключаем рабочие узлы.

В предыдущей статье мы собрали control plane вручную: выпустили сертификаты, подготовили конфигурации и запустили управляющие компоненты. API-сервер уже отвечает, но кластер пока остается без рабочих узлов.

Пока в кластере нет worker-нод, запускать прикладные поды просто негде. В этой статье добавим Worker-ноду и разберем весь путь от чистой VM до зарегистрированного узла в Kubernetes.

Формат тот же, что и в первой части: подготовим ОС, поставим containerd и kubelet, настроим подключение к кластеру и проверим регистрацию узла. Покажу два варианта: ручной сценарий через bootstrap token и CSR API или стандартный путь через kubeadm join.

Kubernetes The Hard Way

1. Введение

Control Plane принимает решения: где запускать поды, как поддерживать желаемое состояние и что делать при сбоях. Но сами приложения живут на worker-нодах, то есть в data plane кластера.

Worker-нода обычно сводится к двум главным частям: kubelet и контейнерный рантайм, в нашем случае containerd. kubelet общается с API-сервером, получает спецификации подов и следит за тем, чтобы контейнеры действительно работали.

В отличие от мастер-нод, здесь нет etcd, kube-apiserver, kube-controller-manager и kube-scheduler. Нет и static pod-ов control plane. Приватный ключ CA на worker-ноду тоже не попадает.

Из-за этого сама машина настраивается проще, но появляется отдельный bootstrap-вопрос: как новой ноде войти в кластер, если у нее еще нет ни клиентского сертификата, ни доверия к API?


2. Инфраструктура

Ниже минимальный набор параметров, с которым будем добавлять worker-ноду: имя, адрес, DNS и состав ПО. Этого достаточно, чтобы повторить шаги из статьи в своей среде.

Рабочие узлы

ИмяIP-адресОперационная системаРесурсы

worker-1.my-first-cluster.example.com

NODE-IP-4ubuntu-24-04-lts2CPU / 4RAM / 40GB

DNS-записи

A-записьIP-адресTTL

worker-1.my-first-cluster.example.com

NODE-IP-460s

Компоненты

На worker-ноде оставляем только то, что нужно для запуска рабочих нагрузок. Компоненты control plane и etcd здесь не нужны.

КомпонентВерсияНазначение
containerd1.7.19Контейнерный рантайм, управляющий жизненным циклом контейнеров.
runcv1.1.12Низкоуровневый инструмент для запуска контейнеров с использованием средств ядра Linux.
crictlv1.30.0Утилита для отладки CRI-сред с поддержкой взаимодействия с containerd.
kubeletv1.30.4Агент, запускаемый на каждом узле, обеспечивающий выполнение и контроль состояния подов.
kubectlv1.30.4Клиент для взаимодействия с Kubernetes API (опционально).
kubeadmv1.30.4Инструмент для автоматизации присоединения узла к кластеру (опционально).

3. Базовая настройка узлов

Сначала приводим ОС в предсказуемое состояние: задаем переменные окружения, меняем hostname и ставим базовые утилиты. Шаг почти такой же, как на master-нодах, меняются только значения для worker-узла.

Сначала приводим worker-узел в предсказуемое состояние: задаем переменные окружения, меняем hostname и ставим базовые утилиты. Это короткий шаг, но дальше почти все команды будут опираться именно на эти значения.

Базовая настройка узлов

● Обязателен к применению

Базовые настройки узлов

  • Переменные окружения узла.
  • Изменение имени узла.
  • Установка зависимостей.

Переменные окружения узла

export HOST_NAME=worker-1
export CLUSTER_NAME="my-first-cluster"
export BASE_DOMAIN="example.com"
export CLUSTER_DOMAIN="cluster.local"
export FULL_HOST_NAME="${HOST_NAME}.${CLUSTER_NAME}.${BASE_DOMAIN}"

Изменение имени узла

hostnamectl set-hostname ${FULL_HOST_NAME}

Установка зависимостей

sudo apt update
sudo apt install -y conntrack socat jq tree

4. Загрузка модулей ядра

Загружаем модули ядра, которые нужны containerd и сетевой части Kubernetes. Набор тот же, что и на master-нодах.

Этот раздел посвящен загрузке модулей ядра, необходимых для корректной работы Kubernetes. Настройка включает конфигурацию modprobe и активацию модулей overlay и br_netfilter, обеспечивающих поддержку контейнерной файловой системы и сетевых функций. Эти действия обязательны для функционирования сетевых политик, iptables и контейнерных рантаймов.

Загрузка модулей ядра

● Обязателен к применению

Этапы установки компонента:

  • Конфигурация modprobe.
  • Загрузка модулей.

Конфигурация modprobe

cat <<EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

Загрузка модулей

sudo -i
modprobe overlay
modprobe br_netfilter
примечание

Модуль overlay используется файловой системой OverlayFS для управления слоями контейнеров. Он позволяет объединять несколько директорий в единую виртуальную файловую систему. Применяется такими рантаймами, как Docker и containerd.

Модуль br_netfilter обеспечивает обработку трафика сетевых мостов через подсистему netfilter. Это необходимо для корректной работы iptables в Kubernetes.


5. Настройка параметров sysctl

Дальше настраиваем sysctl: включаем IP forwarding и параметры для bridge-трафика. Значения здесь тоже совпадают с control plane.

Этот раздел посвящен настройке параметров ядра с помощью sysctl, необходимых для сетевой работы Kubernetes. Вносятся изменения, обеспечивающие маршрутизацию трафика между подами и корректную работу iptables для мостов. Эти параметры обязательны для включения пересылки IP-пакетов и фильтрации сетевых потоков в кластере.

Настройка параметров sysctl

● Обязателен к применению

Этапы установки компонента:

  • Конфигурация sysctl.
  • Применение конфигурации.
Обратите внимание

Сетевые параметры

Для корректной маршрутизации и фильтрации трафика необходимо задать параметры ядра.

Конфигурация sysctl

cat <<EOF > /etc/sysctl.d/99-br-netfilter.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
EOF

Применение конфигурации

sysctl --system

Если параметр net.ipv4.ip_forward не активирован, система не будет пересылать IP-пакеты между интерфейсами. Это может привести к сетевым сбоям внутри кластера, недоступности сервисов и потере связи между подами.

Конфигурация sysctl

cat <<EOF > /etc/sysctl.d/99-network.conf
net.ipv4.ip_forward=1
EOF
sysctl --system

6. Установка компонентов

На worker-ноду ставим только то, что нужно для запуска контейнеров и регистрации узла в кластере: containerd, kubelet и вспомогательные утилиты. Компоненты control plane здесь не устанавливаются.

Здесь ставим минимальный набор бинарников для worker-ноды. Этого достаточно, чтобы запустить контейнеры, поднять kubelet и присоединить узел к кластеру.

Установка runc

● Обязателен к применению

Этапы установки компонента

  • Создание рабочих директорий.
  • Переменные окружения.
  • Инструкция загрузки.
  • Настройка прав.
  • Сервис загрузки.
  • Запуск сервиса загрузки.

Создание рабочих директорий

mkdir -p /etc/default/runc

Переменные окружения

cat <<EOF > /etc/default/runc/download.env
COMPONENT_VERSION="v1.1.12"
REPOSITORY="https://github.com/opencontainers/runc/releases/download"
EOF

Инструкция загрузки

cat <<"EOF" > /etc/default/runc/download-script.sh
#!/bin/bash
set -Eeuo pipefail


COMPONENT_VERSION="${COMPONENT_VERSION:-v1.1.12}"
REPOSITORY="${REPOSITORY:-https://github.com/opencontainers/runc/releases/download}"
PATH_BIN="${REPOSITORY}/${COMPONENT_VERSION}/runc.amd64"
PATH_SHA256="${REPOSITORY}/${COMPONENT_VERSION}/runc.sha256sum"
INSTALL_PATH="/usr/local/bin/runc"

LOG_TAG="runc-installer"
TMP_DIR="$(mktemp -d)"

logger -t "$LOG_TAG" "[INFO] Checking current runc version..."

CURRENT_VERSION=$($INSTALL_PATH --version 2>/dev/null | head -n1 | awk '{print $NF}') || CURRENT_VERSION="none"
COMPONENT_VERSION_CLEAN=$(echo "$COMPONENT_VERSION" | sed 's/^v//')

logger -t "$LOG_TAG" "[INFO] Current: $CURRENT_VERSION, Target: $COMPONENT_VERSION_CLEAN"

if [[ "$CURRENT_VERSION" != "$COMPONENT_VERSION_CLEAN" ]]; then
logger -t "$LOG_TAG" "[INFO] Download URL: $PATH_BIN"
logger -t "$LOG_TAG" "[INFO] Updating runc to version $COMPONENT_VERSION..."

cd "$TMP_DIR"
logger -t "$LOG_TAG" "[INFO] Working directory: $PWD"

logger -t "$LOG_TAG" "[INFO] Downloading runc..."
curl -fsSL -o runc.amd64 "$PATH_BIN" || { logger -t "$LOG_TAG" "[ERROR] Failed to download runc"; exit 1; }

logger -t "$LOG_TAG" "[INFO] Downloading checksum file..."
curl -fsSL -o runc.sha256sum "$PATH_SHA256" || { logger -t "$LOG_TAG" "[ERROR] Failed to download checksum file"; exit 1; }

logger -t "$LOG_TAG" "[INFO] Verifying checksum..."
grep "runc.amd64" runc.sha256sum | sha256sum -c - || { logger -t "$LOG_TAG" "[ERROR] Checksum verification failed!"; exit 1; }

logger -t "$LOG_TAG" "[INFO] Installing runc..."
install -m 755 runc.amd64 "$INSTALL_PATH"

logger -t "$LOG_TAG" "[INFO] runc successfully updated to $COMPONENT_VERSION."
rm -rf "$TMP_DIR"

else
logger -t "$LOG_TAG" "[INFO] runc is already up to date. Skipping installation."
fi
EOF

Настройка прав

chmod +x /etc/default/runc/download-script.sh

Сервис загрузки

cat <<EOF > /usr/lib/systemd/system/runc-install.service
[Unit]
Description=Install and update in-cloud component runc
After=network.target
Wants=network-online.target

[Service]
Type=oneshot
EnvironmentFile=-/etc/default/runc/download.env
ExecStart=/bin/bash -c "/etc/default/runc/download-script.sh"
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

Загрузки

systemctl enable runc-install.service
systemctl start runc-install.service

Проверка установки

Проверка установки

Исполняемые файлы

journalctl -t runc-installer
Вывод команды
***** [INFO] Checking current runc version...
***** [INFO] Current: none, Target: v1.1.12
***** [INFO] Download URL: https://*******
***** [INFO] Updating runc to version v1.1.12...
***** [INFO] Working directory: /tmp/tmp.*****
***** [INFO] Downloading runc...
***** [INFO] Downloading checksum file...
***** [INFO] Verifying checksum...
***** [INFO] Installing runc...
***** [INFO] runc successfully updated to v1.1.12.
ls -la /usr/local/bin/ | grep 'runc$'
Вывод команды
-rwxr-xr-x  1 root root  10709696 Jan 23  2024 runc

Версия исполняемого файла

runc --version
Вывод команды
runc version 1.1.12
commit: v1.1.12-0-g51d5e946
spec: 1.0.2-dev
go: go1.20.13
libseccomp: 2.5.4

7. Настройка компонентов

После установки собираем конфиги для containerd, kubelet, crictl и при необходимости kubeadm. Для worker-ноды в kubeadm нужен только блок JoinConfiguration, без секции controlPlane.

После установки собираем конфиги для containerd, kubelet, crictl и при необходимости kubeadm. Если делаете join вручную, вкладку с kubeadm можно просто пропустить.

Настройка containerd

● Обязателен к применению

Этапы настройки компонента

  • Конфигурация компонента
  • Настройка Systemd Unit компонента
  • Старт Systemd Unit
Обратите внимание

Данный раздел зависит от следующих документов:

Конфигурация компонента

Создание рабочих директорий

mkdir -p /etc/containerd/
mkdir -p /etc/containerd/conf.d
mkdir -p /etc/containerd/certs.d

Базовый конфигурационный файл

cat <<"EOF" > /etc/containerd/config.toml
version = 2
imports = ["/etc/containerd/conf.d/*.toml"]
EOF

Шаблон кастомного конфигурационного файла

cat <<"EOF" > /etc/containerd/conf.d/in-cloud.toml
version = 2
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d/"
EOF

Настройка Systemd Unit компонента

cat <<EOF > /usr/lib/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target containerd-install.service runc-install.service
Wants=containerd-install.service runc-install.service

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
EOF
systemctl enable containerd
systemctl start containerd

Проверка настройки

Проверка настройки
tree /etc/containerd/
Вывод команды
/etc/containerd/
├── certs.d
├── conf.d
│   └── cloud.toml
└── config.toml
systemctl status containerd
Вывод команды
● containerd.service - containerd container runtime
Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; preset: enabled)
Active: active (running) since Tue 2024-12-31 17:26:21 UTC; 2min 30s ago
Docs: https://containerd.io
Main PID: 839 (containerd)
Tasks: 7 (limit: 2274)
Memory: 62.0M (peak: 62.5M)
CPU: 375ms
CGroup: /system.slice/containerd.service
└─839 /usr/local/bin/containerd

***** level=info msg="Start subscribing containerd event"
***** level=info msg="Start recovering state"
***** level=info msg="Start event monitor"
***** level=info msg="Start snapshots syncer"
***** level=info msg="Start cni network conf syncer for default"
***** level=info msg="Start streaming server"
***** level=info msg=serving... address=/run/containerd/containerd.sock.ttrpc
***** level=info msg=serving... address=/run/containerd/containerd.sock
***** level=info msg="containerd successfully booted in 0.065807s"
***** Started containerd.service - containerd container runtime.

8. Аутентификация

Это самый важный шаг во всей статье. У worker-ноды нет доступа к приватному ключу CA, поэтому ей нужно безопасно получить доверие к кластеру одним из двух способов:

  • Bootstrap Token + CSR API — ручной путь с полным контролем над TLS Bootstrap
  • Kubeadm — стандартное подключение через kubeadm join

Аутентификация worker-ноды

● Обязателен к применению

Внутри ручного сценария получим ca.crt из cluster-info, соберем bootstrap-kubelet.conf и при желании вручную пройдем поток CSR для клиентского и серверного сертификатов kubelet.

Ниже два рабочих сценария подключения worker-ноды к кластеру. Первый дает полный контроль над bootstrap-процессом, второй повторяет привычный путь через kubeadm join.

Это ручной сценарий: создаем bootstrap-kubelet.conf, запускаем kubelet и при необходимости сами проводим CSR через Kubernetes API. Подходит, если хотите видеть весь путь TLS Bootstrap без автоматики kubeadm.

Внимание

В примере ниже используется статичный bootstrap-токен для всех worker-узлов. Для production-среды лучше выпускать отдельный токен на каждую ноду и задавать ему ограниченный срок жизни.

Создание bootstrap-токена

Bootstrap-токен хранится в Secret в namespace kube-system и дает новой ноде право начать bootstrap-процесс. Ниже два способа его создать.

🖥️ Мастер-нода

Команды ниже выполняются на мастер-ноде или на хосте с kubeconfig, имеющим права на создание Secret в namespace kube-system.

Переменные окружения

export AUTH_EXTRA_GROUPS="system:bootstrappers:kubeadm:default-node-token"
export DESCRIPTION="kubeadm bootstrap token"
export EXPIRATION=$(date -d '24 hours' "+%Y-%m-%dT%H:%M:%SZ")
export TOKEN_ID="fjt9ex"
export TOKEN_SECRET="lwzqgdlvoxtqk4yw"
export USAGE_BOOTSTRAP_AUTHENTIFICATION="true"
export USAGE_BOOTSTRAP_SIGNING="true"

Создание Secret

kubectl \
--kubeconfig=/etc/kubernetes/super-admin.conf \
apply -f - <<EOF
---
apiVersion: v1
kind: Secret
metadata:
name: bootstrap-token-${TOKEN_ID}
namespace: kube-system
data:
auth-extra-groups: $(echo -n "$AUTH_EXTRA_GROUPS" | base64)
description: $(echo -n "$DESCRIPTION" | base64)
expiration: $(echo -n "$EXPIRATION" | base64)
token-id: $(echo -n "$TOKEN_ID" | base64)
token-secret: $(echo -n "$TOKEN_SECRET" | base64)
usage-bootstrap-authentication: $(echo -n "$USAGE_BOOTSTRAP_AUTHENTIFICATION" | base64)
usage-bootstrap-signing: $(echo -n "$USAGE_BOOTSTRAP_SIGNING" | base64)
type: bootstrap.kubernetes.io/token
EOF

Создание bootstrap-kubelet.conf

🖥️ Worker-нода

Все команды данного этапа выполняются на worker-ноде. На worker-ноде файл ca.crt еще отсутствует. CA-данные получаем из публичного ConfigMap cluster-info в namespace kube-public, доступного анонимно через kube-apiserver.

Переменные окружения

export BOOTSTRAP_TOKEN=fjt9ex.lwzqgdlvoxtqk4yw
export API_SERVER="https://api.my-first-cluster.example.com:6443"

Рабочая директория

mkdir -p /etc/kubernetes

Получение CA из cluster-info

export CA_DATA=$(curl -sk "${API_SERVER}/api/v1/namespaces/kube-public/configmaps/cluster-info" | \
jq -r '.data.kubeconfig' | \
grep 'certificate-authority-data' | \
awk '{print $2}')

Сохранение CA-сертификата

mkdir -p /etc/kubernetes/pki
echo "${CA_DATA}" | base64 -d > /etc/kubernetes/pki/ca.crt

Генерация kubeconfig

cat <<EOF > /etc/kubernetes/bootstrap-kubelet.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ${CA_DATA}
server: ${API_SERVER}
name: my-first-cluster
contexts:
- context:
cluster: my-first-cluster
user: tls-bootstrap-token-user
name: tls-bootstrap-token-user@kubernetes
current-context: tls-bootstrap-token-user@kubernetes
kind: Config
preferences: {}
users:
- name: tls-bootstrap-token-user
user:
token: ${BOOTSTRAP_TOKEN}
EOF

Kubernetes CSR (имитация TLS Bootstrap)

Ниже показан тот же поток, который kubelet обычно проходит сам во время TLS Bootstrap: ключи генерируются на worker-ноде, CSR отправляются через Kubernetes API, а подпись подтверждает администратор на мастер-ноде. Приватный ключ CA на worker-ноде для этого не нужен.

Kubelet Client Certificate (CSR)

● Обязателен к применению

Назначение: Клиентский сертификат kubelet для подключения к kube-apiserver.

1. Генерация ключа и CSR

🖥️ Worker-нода

Все команды данного этапа выполняются на worker-ноде.

export HOST_NAME=worker-1
export CLUSTER_NAME="my-first-cluster"
export BASE_DOMAIN="example.com"
export FULL_HOST_NAME="${HOST_NAME}.${CLUSTER_NAME}.${BASE_DOMAIN}"
mkdir -p /var/lib/kubelet/pki
mkdir -p /etc/kubernetes/openssl/csr
cat <<EOF > /etc/kubernetes/openssl/kubelet-client.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
CN = system:node:${FULL_HOST_NAME}
O = system:nodes

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=clientAuth
EOF
openssl genrsa \
-out /var/lib/kubelet/pki/kubelet-client-key.pem 2048
openssl req -new \
-key /var/lib/kubelet/pki/kubelet-client-key.pem \
-out /etc/kubernetes/openssl/csr/kubelet-client.csr \
-config /etc/kubernetes/openssl/kubelet-client.conf

2. Отправка CSR в Kubernetes API

🖥️ Worker-нода

Worker-нода аутентифицируется bootstrap-токеном через bootstrap-kubelet.conf.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-client"
export CSR_CONTENT=$(cat /etc/kubernetes/openssl/csr/kubelet-client.csr | base64 | tr -d '\n')
kubectl \
--kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: ${CSR_NAME}
spec:
request: ${CSR_CONTENT}
signerName: kubernetes.io/kube-apiserver-client-kubelet
usages:
- digital signature
- key encipherment
- client auth
EOF

3. Утверждение CSR

🖥️ Мастер-нода

Утверждение CSR выполняется на мастер-ноде. Укажите имя worker-ноды, для которой утверждается CSR.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-client"
kubectl \
--kubeconfig=/etc/kubernetes/super-admin.conf \
certificate approve ${CSR_NAME}

4. Получение подписанного сертификата

🖥️ Worker-нода

Сертификат получаем на worker-ноде с использованием bootstrap-kubelet.conf.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-client"
kubectl \
--kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
get csr ${CSR_NAME} \
-o jsonpath='{.status.certificate}' | base64 -d > /var/lib/kubelet/pki/kubelet-client.pem
cat /var/lib/kubelet/pki/kubelet-client.pem /var/lib/kubelet/pki/kubelet-client-key.pem > /var/lib/kubelet/pki/kubelet-client-$(date '+%Y-%m-%d-%H-%M-%S').pem
ln -sf /var/lib/kubelet/pki/kubelet-client-$(date '+%Y-%m-%d-%H-%M-%S').pem /var/lib/kubelet/pki/kubelet-client-current.pem

Kubelet Server Certificate (CSR)

● Обязателен к применению

Назначение: Серверный сертификат kubelet для TLS на порту 10250.

1. Генерация ключа и CSR

🖥️ Worker-нода

Все команды данного этапа выполняются на worker-ноде.

export HOST_NAME=worker-1
export CLUSTER_NAME="my-first-cluster"
export BASE_DOMAIN="example.com"
export FULL_HOST_NAME="${HOST_NAME}.${CLUSTER_NAME}.${BASE_DOMAIN}"
export MACHINE_LOCAL_ADDRESS="$(ip -4 addr show scope global | awk '/inet/ {print $2; exit}' | cut -d/ -f1)"
mkdir -p /var/lib/kubelet/pki
mkdir -p /etc/kubernetes/openssl/csr
cat <<EOF > /etc/kubernetes/openssl/kubelet-server.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost
DNS.2 = ${HOST_NAME}
DNS.3 = ${FULL_HOST_NAME}
IP.1 = 127.0.0.1
IP.2 = 0:0:0:0:0:0:0:1
IP.3 = ${MACHINE_LOCAL_ADDRESS}

[ dn ]
CN = system:node:${FULL_HOST_NAME}
O = system:nodes

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt_names
EOF
openssl genrsa \
-out /var/lib/kubelet/pki/kubelet-server-key.pem 2048
openssl req -new \
-key /var/lib/kubelet/pki/kubelet-server-key.pem \
-out /etc/kubernetes/openssl/csr/kubelet-server.csr \
-config /etc/kubernetes/openssl/kubelet-server.conf

2. Отправка CSR в Kubernetes API

🖥️ Worker-нода

Worker-нода аутентифицируется bootstrap-токеном через bootstrap-kubelet.conf.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-server"
export CSR_CONTENT=$(cat /etc/kubernetes/openssl/csr/kubelet-server.csr | base64 | tr -d '\n')
kubectl \
--kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: ${CSR_NAME}
spec:
request: ${CSR_CONTENT}
signerName: kubernetes.io/kubelet-serving
usages:
- digital signature
- key encipherment
- server auth
EOF

3. Утверждение CSR

🖥️ Мастер-нода

Утверждение CSR выполняется на мастер-ноде. Укажите имя worker-ноды, для которой утверждается CSR.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-server"
kubectl \
--kubeconfig=/etc/kubernetes/super-admin.conf \
certificate approve ${CSR_NAME}

4. Получение подписанного сертификата

🖥️ Worker-нода

Сертификат получаем на worker-ноде с использованием bootstrap-kubelet.conf.

export HOST_NAME=worker-1
export CSR_NAME="${HOST_NAME}-kubelet-server"
kubectl \
--kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
get csr ${CSR_NAME} \
-o jsonpath='{.status.certificate}' | base64 -d > /var/lib/kubelet/pki/kubelet-server.pem
cat /var/lib/kubelet/pki/kubelet-server.pem /var/lib/kubelet/pki/kubelet-server-key.pem > /var/lib/kubelet/pki/kubelet-server-$(date '+%Y-%m-%d-%H-%M-%S').pem
ln -sf /var/lib/kubelet/pki/kubelet-server-$(date '+%Y-%m-%d-%H-%M-%S').pem /var/lib/kubelet/pki/kubelet-server-current.pem

9. Запуск Kubelet

Когда аутентификация готова, остается запустить kubelet. На этом шаге создаем его конфиг, поднимаем systemd-сервис и даем узлу зарегистрироваться в кластере.

На этом шаге нода получает все, что нужно для старта kubelet, и регистрируется в кластере. В ручном сценарии собираем конфиг и поднимаем сервис сами, а в варианте с kubeadm достаточно одной команды.

Запуск/Настройка kubelet

● Обязателен к применению

Ниже базовый конфиг kubelet, от которого будем стартовать на worker-ноде.

Kubelet default config

Базовый конфигурационный файл kubelet

cat <<EOF > /var/lib/kubelet/config.yaml
---
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 0s
cacheUnauthorizedTTL: 0s
cgroupDriver: systemd
clusterDNS:
- 29.64.0.10
clusterDomain: cluster.local
containerRuntimeEndpoint: ""
cpuManagerReconcilePeriod: 0s
evictionPressureTransitionPeriod: 0s
fileCheckFrequency: 0s
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 0s
imageMaximumGCAge: 0s
imageMinimumGCAge: 0s
kind: KubeletConfiguration
logging:
flushFrequency: 0
options:
json:
infoBufferSize: "0"
text:
infoBufferSize: "0"
verbosity: 0
memorySwap: {}
nodeStatusReportFrequency: 0s
nodeStatusUpdateFrequency: 0s
resolvConf: /run/systemd/resolve/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 0s
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 0s
syncFrequency: 0s
volumeStatsAggPeriod: 0s
EOF
Предварительные требования

Перед стартом kubelet убедитесь, что вы уже прошли раздел 5.3.2. Аутентификация и получили:

  • Получение CA-сертификата (ca.crt)
  • Создание bootstrap-kubelet.conf (или сертификатов вручную)

Переменные окружения

Обратите внимание

Данный блок конфигурации применим только при установке Kubernetes вручную (методом «Kubernetes the Hard Way»). При использовании утилиты kubeadm конфигурационный файл будет создан автоматически на основе спецификации, указанной в файле kubeadm-config.

cat <<EOF > /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --pod-infra-container-image=registry.k8s.io/pause:3.9 --config=/var/lib/kubelet/config-custom.yaml --cluster-domain=cluster.local --cluster-dns=29.64.0.10
"
EOF

Данная команда запускает сервис Kubelet, который отвечает за развертывание всех контейнеров на основе манифестов Static Pods.

systemctl start kubelet

Статус Systemd Unit

Проверка готовности systemd unit
Обратите внимание

Обратите внимание, что при создании кластера с помощью Kubeadm без выполнения команд kubeadm init или kubeadm join, Systemd Unit будет добавлен в автозапуск, но будет выключен.

systemctl status kubelet
Вывод команды
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: enabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since Sat 2025-02-22 10:33:54 UTC; 17min ago
Docs: https://kubernetes.io/docs/
Main PID: 13779 (kubelet)
Tasks: 14 (limit: 7069)
Memory: 34.0M (peak: 35.3M)
CPU: 27.131s
CGroup: /system.slice/kubelet.service
└─13779 /usr/local/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml

10. Проверка

После старта kubelet проверьте, что нода появилась в кластере. До установки сетевого плагина она может оставаться в состоянии NotReady — это нормально.

Выполните на мастер-ноде:

kubectl --kubeconfig=/etc/kubernetes/super-admin.conf get nodes -o wide
к сведению
NAME                                          STATUS     ROLES    AGE   VERSION
master-1.my-first-cluster.example.com NotReady master 1d v1.32.0
master-2.my-first-cluster.example.com NotReady master 1d v1.32.0
master-3.my-first-cluster.example.com NotReady master 1d v1.32.0
worker-1.my-first-cluster.example.com NotReady <none> 30s v1.32.0
Статус NotReady

Статус NotReady — нормальное поведение до установки сетевого плагина (CNI). После развертывания CNI (Calico, Cilium, Flannel и т.д.) статус узлов сменится на Ready.


Вывод

Worker-нода добавлена в кластер. Теперь control plane можно использовать по назначению: он может принимать реальные нагрузки.

По пути мы:

  • Подготовили ОС и сетевой стек
  • Установили containerd, kubelet и сопутствующие утилиты
  • Подключили ноду вручную через bootstrap token и CSR API или через kubeadm
  • Запустили kubelet и проверили регистрацию узла

Следующий логичный шаг — поставить CNI, поднять DNS внутри кластера и только потом переходить к первым прикладным подам.

примечание

Если вы дошли до этого места, у вас уже есть вручную собранный control plane и первый worker-узел. Дальше можно переходить к сетевому плагину и превращать заготовку кластера в рабочую среду.