Вопрос или проблема
Я настроил кластер Kubernetes с 1 узлом управляющей плоскости и 2 рабочими узлами в облаке Hetzner. Все узлы работают на Ubuntu-24.04 в Hetzner, ниже приведены скрипты, которые я использовал во время настройки.
Ниже представлен скрипт, который пытается настроить управляющую плоскость:
#!/bin/bash
# Получить текущего пользователя
USER="root"
# Удалить существующий репозиторий Kubernetes, если он есть
sudo rm -f /etc/apt/sources.list.d/kubernetes.list
# Отключить своп
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
# Создать директорию конфигурации kubelet, если она не существует
sudo mkdir -p /etc/systemd/system/kubelet.service.d
# Создать файл 20-hetzner-cloud.conf с необходимой конфигурацией
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/20-hcloud.conf
[Service]
Environment="KUBELET_EXTRA_ARGS=--cloud-provider=external"
EOF
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# Применить параметры sysctl без перезагрузки
sudo sysctl --system
# Установить containerd и другие требуемые компоненты
sudo apt-get update && sudo apt-get install -y \
apt-transport-https \
ca-certificates \
gpg \
curl \
software-properties-common
# Добавить официальный GPG-ключ Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Настроить стабильный репозиторий Docker
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
# Обновить базу данных пакетов с пакетов Docker из недавно добавленного репозитория
sudo apt-get update
sudo apt-get install -y containerd.io curl socat
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
sudo sed -i '/\[plugins."io.containerd.grpc.v1.cri"\]/,/disable_apparmor/ s/disable_apparmor = .*/disable_apparmor = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | 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.29/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
systemctl enable --now kubelet
Следующий скрипт выполняется после завершения предыдущего скрипта для завершения настройки управляющей плоскости:
#!/bin/bash
USER="root"
LOAD_BALANCER_IP=$(cat /home/$USER/control_plane_load_balancer_ip.txt)
echo "IP балансировщика нагрузки=$LOAD_BALANCER_IP"
if [ -z "$LOAD_BALANCER_IP" ]; then
echo "Ошибка: IP балансировщика нагрузки не найден в /home/$USER/control_plane_load_balancer_ip.txt. Выход..."
exit 1
fi
# Проверить, инициализирован ли Kubernetes, проверяя наличие файла kubeconfig
if [ -f /etc/kubernetes/admin.conf ]; then
echo "Kubernetes уже инициализирован. Пропускаю kubeadm init..."
else
echo "Kubernetes не инициализирован. Инициализация Kubernetes..."
# Инициализировать Kubernetes с использованием IP балансировщика нагрузки
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--control-plane-endpoint "$LOAD_BALANCER_IP" \
--cri-socket=unix:///run/containerd/containerd.sock \
--upload-certs
if [ $? -eq 0 ]; then
echo "Kubernetes успешно инициализирован."
# Установить kubeconfig
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
# Применить конфигурацию сети CNI Flannel
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# Сохранить KUBECONFIG между сессиями
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >>/home/$CURRENT_USER/.bashrc
source /home/$CURRENT_USER/.bashrc
echo "CNI Flannel применен и KUBECONFIG успешно настроен."
# Создать /run/flannel/subnet.env, если он не существует
if [ ! -f /run/flannel/subnet.env ]; then
sudo mkdir -p /run/flannel
{
echo "FLANNEL_NETWORK=10.244.0.0/16"
echo "FLANNEL_SUBNET=10.244.0.1/24"
echo "FLANNEL_MTU=1450"
echo "FLANNEL_IPMASQ=true"
} | sudo tee /run/flannel/subnet.env >/dev/null
echo "Файл конфигурации подсети Flannel успешно создан."
else
echo "Файл конфигурации подсети Flannel уже существует. Пропускаю создание."
fi
else
echo "Ошибка: kubeadm init завершился неудачей."
exit 1
fi
fi
А управляющая плоскость все время инициализируется успешно. Теперь вот код, который настраивает рабочие узлы:
#!/bin/bash
# Получить текущего пользователя
USER="root"
SSH_KEY="..."
CONTROL_PLANE_NODE_IPS_FILE="/home/${USER}/control_plane_node_ips.txt"
KUBECONFIG_PATH="/home/${USER}/kubeconfig"
# Удалить существующий репозиторий Kubernetes, если он есть
sudo rm -f /etc/apt/sources.list.d/kubernetes.list
# Отключить своп
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
# Создать директории конфигурации kubelet и сети, если они не существуют
sudo mkdir -p /etc/systemd/system/kubelet.service.d
# Создать файл 20-hetzner-cloud.conf с необходимой конфигурацией
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/20-hcloud.conf
[Service]
Environment="KUBELET_EXTRA_ARGS=--cloud-provider=external"
EOF
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# Установить containerd и другие требуемые компоненты
sudo apt-get update && sudo apt-get install -y \
apt-transport-https \
ca-certificates \
gpg \
curl \
software-properties-common
# Добавить официальный GPG-ключ Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Настроить стабильный репозиторий Docker
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
# Обновить базу данных пакетов с пакетов Docker из недавно добавленного репозитория
sudo apt-get update
sudo apt-get update && sudo apt-get install -y containerd.io socat
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
sudo sed -i '/\[plugins."io.containerd.grpc.v1.cri"\]/,/disable_apparmor/ s/disable_apparmor = .*/disable_apparmor = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd
mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | 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.29/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
systemctl enable --now kubelet
CONTROL_PLANE_NODE_IPS=$(cat "$CONTROL_PLANE_NODE_IPS_FILE")
echo "IP узлов управляющей плоскости = $CONTROL_PLANE_NODE_IPS"
IFS=',' read -r -a CONTROL_PLANE_NODE_IPS <<<"$CONTROL_PLANE_NODE_IPS"
if [ ${#CONTROL_PLANE_NODE_IPS[@]} -gt 0 ]; then
SHUFFLED_IPS=($(shuf -e "${CONTROL_PLANE_NODE_IPS[@]}"))
else
echo "Ошибка: Нет доступных IP узлов управляющей плоскости."
exit 1
fi
mkdir -p "$(dirname $KUBECONFIG_PATH)"
for ip in "${SHUFFLED_IPS[@]}"; do
echo "Пытаюсь подключиться к узлу управляющей плоскости по IP $ip..."
# Получить команду присоединения от узла управляющей плоскости
JOIN_COMMAND=$(ssh -o StrictHostKeyChecking=no -i $SSH_KEY $USER@$ip 'kubeadm token create --print-join-command' 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Команда присоединения получена от узла управляющей плоскости по IP $ip: $JOIN_COMMAND"
# Получить kubeconfig от узла управляющей плоскости
scp -o StrictHostKeyChecking=no -i $SSH_KEY $USER@$ip:/etc/kubernetes/admin.conf $KUBECONFIG_PATH
if [ $? -eq 0 ]; then
echo "Успешно получен kubeconfig от узла управляющей плоскости по IP $ip."
else
echo "Не удалось получить kubeconfig от узла управляющей плоскости по IP $ip."
exit 1
fi
# Попытаться присоединить рабочий узел, используя полученную команду присоединения
if eval "$JOIN_COMMAND"; then
echo "Успешно присоединился к кластеру с использованием команды от узла управляющей плоскости по IP $ip."
break
else
echo "Не удалось присоединиться к кластеру с использованием полученной команды присоединения от узла управляющей плоскости по IP $ip."
fi
else
echo "Не удалось получить команду присоединения от узла управляющей плоскости по IP $ip. Пытаюсь следующий..."
fi
done
# Проверяем, была ли получена команда присоединения и успешно выполнена
if [ -z "$JOIN_COMMAND" ]; then
echo "Ошибка: Не удалось получить команду присоединения ни от одного узла управляющей плоскости."
exit 1
fi
# Пометить узел как рабочий после присоединения к кластеру
NODE_NAME=$(hostname)
kubectl --kubeconfig="$KUBECONFIG_PATH" label node $NODE_NAME node-role.kubernetes.io/worker=worker --overwrite
kubectl --kubeconfig="$KUBECONFIG_PATH" label node $NODE_NAME role=worker --overwrite
echo "Настройка рабочего узла Kubernetes завершена!"
И скрипт для рабочих узлов также выполняется успешно, и все рабочие узлы теперь подключены к управляющей плоскости.
Однако, похоже, есть проблемы с подключением.
Как выяснилось, связь между подами работает через частные IP, но внешняя связь пода, похоже, не работает, о чем свидетельствуют журналы от Traefik, который является контроллером шлюза, который я использую для кластера:
2024-10-08T12:27:27Z INF версия Traefik 3.1.5 собрана на 2024-10-02T12:49:07Z версия=3.1.5
2024-10-08T12:27:27Z INF Сбор статистики включен.
2024-10-08T12:27:27Z INF Большое спасибо за вклад в улучшение Traefik, позволяя нам получать анонимную информацию из вашей конфигурации.
2024-10-08T12:27:27Z INF Помогите нам улучшить Traefik, оставив эту функцию включенной :)
2024-10-08T12:27:27Z INF Подробности на: https://doc.traefik.io/traefik/contributing/data-collection/
2024-10-08T12:27:27Z INF селектор меток: "" providerName=kubernetesgateway
2024-10-08T12:27:27Z INF Создание клиентского конечного точки в кластере endpoint= providerName=kubernetesgateway
2024-10-08T12:27:27Z INF Запуск агрегатора пользователей aggregator.ProviderAggregator
2024-10-08T12:27:27Z INF Запуск провайдера *traefik.Provider
2024-10-08T12:27:27Z INF Запуск провайдера *crd.Provider
2024-10-08T12:27:27Z INF селектор меток: "" providerName=kubernetescrd
2024-10-08T12:27:27Z INF Создание клиентского конечного точки в кластере providerName=kubernetescrd
2024-10-08T12:27:27Z INF Запуск провайдера *gateway.Provider
2024-10-08T12:27:27Z INF Запуск провайдера *acme.ChallengeTLSALPN
2024-10-08T12:37:57Z WRN Ошибка проверки новой версии error="Get \"https://update.traefik.io/repos/traefik/traefik/releases\": dial tcp: lookup update.traefik.io: i/o timeout"
Кроме того, один из подов постоянно аварийно завершает работу с следующей ошибкой, полученной из журналов:
Defaulted container "csi-attacher" out of: csi-attacher, csi-resizer, csi-provisioner, liveness-probe, hcloud-csi-driver
I1008 13:31:30.822915 1 main.go:109] "Версия" version="v4.7.0"
I1008 13:31:40.824824 1 connection.go:253] "Все еще подключение" address="unix:///run/csi/socket"
I1008 13:31:50.825437 1 connection.go:253] "Все еще подключение" address="unix:///run/csi/socket"
I1008 13:32:00.824918 1 connection.go:253] "Все еще подключение" address="unix:///run/csi/socket"
E1008 13:32:00.825053 1 main.go:149] "Не удалось подключиться к драйверу CSI" err="context deadline exceeded" csiAddress="/run/csi/socket
В результате я думаю, что в кластере есть проблема с подключением, которую необходимо устранить.
Обратите внимание, что я настроил инфраструктуру с использованием Terraform в облаке Hetzner, и вот файрволы, которые я применил к каждому узлу, я не знаю, могут ли они быть частью проблемы:
resource "hcloud_firewall" "k8s_firewall_control_plane" {
name = "k8s-firewall-control-plane"
# Правила входящего трафика
rule {
description = "Разрешить входящий HTTPS-трафик для управляющих плоскостей"
direction = "in"
protocol = "tcp"
port = "443"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящие запросы TCP - Kube API Server"
direction = "in"
protocol = "tcp"
port = "6443"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий HTTP-трафик для управляющей плоскости"
direction = "in"
protocol = "tcp"
port = "80"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий SSH-трафик для управляющей плоскости"
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий трафик для Kubelet"
direction = "in"
protocol = "tcp"
port = "10250"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить трафик проверки жизнеспособности для CSI Controller"
direction = "in"
protocol = "tcp"
port = "9808"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить трафик клиента etcd"
direction = "in"
protocol = "tcp"
port = "2379-2380"
source_ips = [
hcloud_network.private_network.ip_range
]
}
rule {
description = "Разрешить трафик kube-controller-manager"
direction = "in"
protocol = "tcp"
port = "10252"
source_ips = [
hcloud_network.private_network.ip_range
]
}
rule {
description = "Разрешить трафик kube-scheduler"
direction = "in"
protocol = "tcp"
port = "10251"
source_ips = [
hcloud_network.private_network.ip_range
]
}
# Правила исходящего трафика
rule {
description = "Разрешить исходящий HTTPS-трафик"
direction = "out"
protocol = "tcp"
port = "443"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий HTTP-трафик"
direction = "out"
protocol = "tcp"
port = "80"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий DNS-трафик"
direction = "out"
protocol = "udp"
port = "53"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий TCP-трафик (любой)"
direction = "out"
protocol = "tcp"
port = "1-65535"
destination_ips = ["0.0.0.0/0"]
}
}
resource "hcloud_firewall" "k8s_firewall_worker" {
name = "k8s-firewall-worker"
# Правила входящего трафика
rule {
description = "Разрешить входящий SSH-трафик для рабочих узлов"
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить входящий HTTP-трафик для рабочих узлов"
direction = "in"
protocol = "tcp"
port = "80"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий HTTP-трафик для рабочих узлов"
direction = "in"
protocol = "tcp"
port = "8000"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий HTTPS-трафик для рабочих узлов"
direction = "in"
protocol = "tcp"
port = "443"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить входящий трафик для Kubelet"
direction = "in"
protocol = "tcp"
port = "10250"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить трафик проверки жизнеспособности для CSI Controller"
direction = "in"
protocol = "tcp"
port = "9808"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
description = "Разрешить трафик Flannel VXLAN"
direction = "in"
protocol = "udp"
port = "8472"
source_ips = [
hcloud_network.private_network.ip_range
]
}
rule {
description = "Разрешить трафик Flannel IP-in-IP"
direction = "in"
protocol = "udp"
port = "8285"
source_ips = [
hcloud_network.private_network.ip_range
]
}
rule {
description = "Разрешить сервисы NodePort"
direction = "in"
protocol = "tcp"
port = "30000-32767"
source_ips = [
"0.0.0.0/0"
]
}
rule {
description = "Разрешить сервисы NodePort UDP"
direction = "in"
protocol = "udp"
port = "30000-32767"
source_ips = [
"0.0.0.0/0"
]
}
# Правила исходящего трафика
rule {
description = "Разрешить исходящий HTTPS-трафик"
direction = "out"
protocol = "tcp"
port = "443"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий HTTP-трафик"
direction = "out"
protocol = "tcp"
port = "80"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий DNS-трафик"
direction = "out"
protocol = "udp"
port = "53"
destination_ips = ["0.0.0.0/0"]
}
rule {
description = "Разрешить исходящий TCP-трафик (любой)"
direction = "out"
protocol = "tcp"
port = "1-65535"
destination_ips = ["0.0.0.0/0"]
}
}
И, что удивительно, все поды CoreDNS работают отлично и без проблем.
Я пытался сделать все возможное, чтобы диагностировать, откуда возникает проблема с подключением.
Буду признателен за любые идеи о том, что может быть не так с моей настройкой.
Заранее спасибо!
Ответ или решение
Проблема с коммуникацией между подами и внешним интернетом в вашей конфигурации Kubernetes может быть вызвана несколькими факторами. Давайте рассмотрим возможные причины и шаги, которые могут помочь вам решить данную проблему.
1. Проверка сетевой конфигурации
-
Сетевой плагин: Вы используете Flannel в качестве CNI. Убедитесь, что Flannel правильно установлен и запущен. Проверьте, нет ли ошибок в подах Flannel:
kubectl get pods -n kube-system
Если поды Flannel имеют статус
CrashLoopBackOff
илиError
, необходимо просмотреть логи для понимания проблемы:kubectl logs <имя-пода> -n kube-system
-
Конфигурация Flannel: Убедитесь, что Flannel правильно конфигурирует сетевые интерфейсы. Проверьте, есть ли файлы конфигурации во всех нодах, связанных с Flannel, и убедитесь, что они совпадают.
2. Проверка firewall (брандмауэра)
Ваши правила брандмауэра выглядят достаточно открыто, но все же убедитесь, что никакие другие правила или конфигурации безопасности не блокируют трафик.
-
Правила для Flannel: Убедитесь, что UDP порты 8472 и 8285 по-прежнему разрешены и могут использоваться для VXLAN и IP-in-IP трафика:
rule { description = "Allow Flannel VXLAN traffic" direction = "in" protocol = "udp" port = "8472" # порты для VXLAN source_ips = [ hcloud_network.private_network.ip_range ] } rule { description = "Allow Flannel IP-in-IP traffic" direction = "in" protocol = "udp" port = "8285" # порты для IP-in-IP source_ips = [ hcloud_network.private_network.ip_range ] }
3. Проверка маршрутизации
-
Маршруты: Проверьте, что маршруты правильно созданы на узлах. Выполните команду для проверки маршрутов:
ip route
Убедитесь, что маршруты для CIDR подов и внешних IP настроены правильно.
-
Ping и traceroute: Проверьте доступность внешних ресурсов из подов. Для этого вы можете запустить тестовый под и пытаться пинговать, например, www.google.com:
kubectl run -it --rm --restart=Never alpine --image=alpine -- sh
Внутри пода выполните:
ping 8.8.8.8
4. Тестирование DNS
Поскольку вы отметили, что CoreDNS работает без проблем, проверьте, корректно ли разрешаются DNS-запросы из подов. Используйте тот же тестовый под:
nslookup update.traefik.io
Если DNS не работает, посмотрите, могут ли поды обращаться к сервису CoreDNS.
5. Настройки Traefik
Логи Traefik показывают ошибки с разрешением DNS. Это может указывать на проблемы с доступом к интернету. Убедитесь, что у вас правильная конфигурация для подключения Traefik к внешним ресурсам.
6. Анализ подов
Под с ошибкой Unable to connect to the CSI driver
может также указывать на сетевую проблему. Проверьте конфигурацию CSI и обеспечьте его доступность, включая возможные зависимости.
Заключение
- Проверьте наличие ошибок в подах Flannel и Traefik.
- Убедитесь, что порты для Flannel и Traefik открыты в брандмауере.
- Проверьте и протестируйте маршрутизацию.
- Убедитесь, что DNS запросы работают из подов.
Если описанные шаги не решат вашу проблему, следует изучить конфигурацию самого кластера и сетевые настройки окружающей инфраструктуры для дальнейшего понимания.
Если у вас есть дополнительные вопросы или необходима более детальная помощь, пожалуйста, не стесняйтесь обращаться!