Создание Kubernetes-кластера на пальцах или почему это не сложно

Привет, меня зовут Руслан, я энтузиаст одного отдела искусственного интеллекта, занимаюсь автоматизацией процесса разработки и контролем за инфраструктурой внутри Kubernetes. Хочу детально рассмотреть развёртку Kubernetes-кластера, показать решения на возможные ошибки, ответы на которые пришлось довольно долго поискать. После окончания статьи вы будете знать, как создать кластер, который подойдет почти под любые задачи.

Используемый стек

  • 3x VM Ubuntu 20.04 (cloud).

  • Kube* == 1.23.3.

  • Docker, containerd.

  • Flannel — интерфейс сети контейнеров, назначает Pod-ам IP-адреса для их взаимодействия между друг другом.

  • MetalLB — LoadBalancer, который будет использоваться для выдачи внешних IP-адресов из заданного нами пула.

  • Ingress NGINX Controller — контроллер для Ingress записей, используемый NGINX в качестве обратного прокси (reverse proxy) и балансировщика нагрузки.

  • Helm — средство для установки/обновления даже самого сложного приложения в Kubernetes в один клик.

  • NFS Subdir External Provisioner — средство устанавливаемое в Kubernetes, как обычный Deployment, которое использует существующий и уже настроенный NFS сервер для динамического создания и централизованного хранения PersistentVolume.

Первоначальная настройка

Для начала подготовим систему к установке Kubernetes, отключим swap, чтобы избежать неконтролируемых последствий. Большинство интерфейсов сети контейнеров (Container Network Interface), в том числе Flannel, работают напрямую с iptables, поэтому включим параметры, отвечающие за отправку пакетов из моста прямо в iptables с целью обработки.

sudo su;
ufw disable;
swapoff -a; sed -i '/swap/d' /etc/fstab;
cat >>/etc/sysctl.d/kubernetes.conf<

Установка Docker и Kubernetes

{
  apt install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
  add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  apt update
  apt install -y docker-ce containerd.io
}

{
  curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
  echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
  apt update && apt install -y kubeadm=1.23.3-00 kubelet=1.23.3-00 kubectl=1.23.3-00
}

Важно знать

Перед тем, как начнём создавать кластер, хочу предостеречь от возможных проблем, держим в голове, что Flannel использует сеть для назначения Pod’ам 10.244.0.0/16, поэтому при создании будет добавлен параметр --pod-network-cidr=10.244.0.0/16.

Если по какой-то причине необходимо изменить сеть для Pod’ов, то используйте свою, но не забудьте изменить сеть и в самой конфигурации Flannel, решение будет в «Нюансы в Flannel».

Чтобы избежать ошибки curl -sSL http://localhost:10248/healthz' failed with error: Get http://localhost:10248/healthz: dial tcp [::1]:10248: connect: connection refused., связанной из-за разных cgroupdriver используемых Kubelet и Docker.

sudo mkdir -p /etc/docker
cat <

Создание кластера

На машине, которая будет являться master-нодой прописываем команду на создание кластера.

kubeadm init --pod-network-cidr=10.244.0.0/16

Для доступа к команде kubectl прописываем команды по перемещению конфига в домашнюю директорию.

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

Для добавление других VM, прописываем команду на создание токена. Вывод с команды вводим на остальных машинах.

kubeadm token create --print-join-command

#Примерный вывод - kubeadm join --discovery-token abcdef.1234567890abcdef --discovery-token-ca-cert-hash sha256:1234..cdef 1.2.3.4:6443

Так как master-нода имеет по умолчанию метку NoSchedule, которая не позволяет запускать Pod’ы без этой метки, что помешает нам в развёртке дальнейших DaemonSet’ов, поэтому уберём метку с ноды.

kubectl get nodes # Узнаем название master ноды
kubectl taint nodes , node-role.kubernetes.io/master:NoSchedule-

Установка Flannel и MetalLB

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml

Далее необходимо задать пул IP-адресов, MetalLB будет использовать их для сервисов, которым необходим External-IP. Копируем код снизу, заменяем адрес и применяем командой kubectl apply -f <название файла>.yaml.

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.119.0.15/32 # Локальный адрес одной из нод

P.S. Я указываю локальный адрес одной из своих worker-нод, интерфейс на котором назначен этот адрес является и выходом в интернет, после можно создать DNS-запись и подключаться по домену.

Нюансы в Flannel

Вернёмся к тому, как изменить пул адресов у Flannel. Для этого нужно скачать конфиг Flannel, зайти в него, найти net-conf.json, заменить на свой адрес, далее можно применять.

wget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

Конфиг FlannelКонфиг Flannel

Если вы решили сделать это уже после установки, то даже после ресета кластера Flannel не позволит поменять адрес интерфейсов, вероятно вы столкнулись с ошибкой NetworkPlugin cni failed to set up pod "xxxxx" network: failed to set bridge addr: "cni0" already has an IP address different from10.x.x.x, произошло это, потому что старые интерфейсы всё ещё остались, чтобы исправить это, удаляем интерфейсы на всех нодах.

sudo su
ip link set cni0 down && ip link set flannel.1 down 
ip link delete cni0 && ip link delete flannel.1
systemctl restart docker && systemctl restart kubelet

Установка Helm

Самая простая установка из всей статьи.
P.S. Всегда проверяйте скрипты.

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

Установка Ingress NGINX Controller

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm show values ingress-nginx/ingress-nginx > values.yaml
kubectl create ns ingress-nginx

В values.yaml меняем параметры hostNetwork, hostPort на true, kind на DaemonSet и применяем.

values.yamlvalues.yamlvalues.yamlvalues.yaml

helm install ingress ingress-nginx/ingress-nginx -n ingress-nginx --values values.yaml

Установка NFS Subdir External Provisioner

Для установки понадобится развёрнутый NFS-сервер, в моём случае он находится на одной из worker-нод. На данный сервер будут сохраняться данные из PersistentVolume, советую задуматься о бэкапах.
Входные данные: 10.119.0.17 — IP-адрес NFS-сервера, /opt/kube/data — директория сетевого хранилища. На остальных машинах (не NFS-сервере) необходимо скачать пакет nfs-common для возможности доступа к хранилищу.

helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
    --set nfs.server=10.119.0.17 \
    --set nfs.path=/opt/kube/data

Делаем StorageClass NFS Provisioner’а, как класс по умолчанию, для удобного создания PersistentVolumeClaim без указания StorageClassName.

kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Проверяем работоспособность NFS Provisioner создав базовый PersistentVolumeClaim, применяем.

cat <
kubectl apply -f testpvc.yaml
kubectl get pv

Если в поле Status написано Bound, и на NFS-сервере в директории хранилища появилась новая папка, то всё прошло успешно.

Проброс TCP/UDP сервисов с помощью Ingress NGINX Controller

Обычный Ingress не поддерживает TCP или UDP для проброса сервисов наружу. По этой причине в Ingress NGINX Controller есть флаги --tcp-services-configmap и --udp-services-configmap, которые помогут пробросить целый сервис с помощью описанного ConfigMap. Пример снизу показывает как пробросить TCP сервис, где 1111 — проброшенный порт; prod — название namespace; lhello — название сервиса; 8080 — порт сервиса.

apiVersion: v1
kind: ConfigMap
metadata:
  name: tcp-services
  namespace: ingress-nginx
data:
  1111: "prod/lhello:8080"

Если используется проброс TCP/UDP, то эти порты должны быть открыты и в службе ingress-ingress-nginx-controller, для этого прописываем команду на редактирование сервиса.

kubectl edit service/ingress-ingress-nginx-controller -n ingress-nginx

Добавляем свой новый порт, который хотим открыть и сохраняем.

###...значения опущены...
spec:
  type: LoadBalancer
  ports:

    - name: proxied-tcp-1111
      port: 1111
      targetPort: 1111
      protocol: TCP

И последнее, что нужно для проброса, так это указать ConfigMap, который будет использоваться, для этого добавим флаг в DaemonSet контроллера.

kubectl edit daemonset.apps/ingress-ingress-nginx-controller -n ingress-nginx
--tcp-services-configmap=$(POD_NAMESPACE)/tcp-services

DaemonSet ConfigurationDaemonSet Configuration

Итоги

На этом кластер готов к работе, вы можете развернуть, что угодно, не хватает только сертификатов для сайтов, но решение уже есть. Не забудьте только поставить в Ingress записи аннотациюkubernetes.io/ingress.class: "nginx". Буду рад любому фидбеку и советам, как улучшить инфраструктуру. Всем пока!

© Habrahabr.ru