Привет, меня зовут Руслан, я энтузиаст одного отдела искусственного интеллекта, занимаюсь автоматизацией процесса разработки и контролем за инфраструктурой внутри 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<<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

Установка 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 <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart kubelet

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

На машине, которая будет являться 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 <nodename> 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.yaml
values.yaml
values.yaml
values.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 <<EOF | sudo tee testpvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 500Mi
EOF
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 Configuration
DaemonSet Configuration

Итоги

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

Комментарии (10)


  1. DeeZ
    29.01.2023 20:42
    +4

    Учитывая что поддержка docker удалена из 1.24, тк в doker не реализовали CRI, то строить кластер на докере наверно не лучшая идея. Хотя, добавить мастеров, и воркеров на других контейнер-рантаймах будет не сложно (как я понимаю).

    Второй совет, видимо, добавить еще контрол-плейн нодов, тк документация советует минимум 3, особенно если кластер большой и распределенный.

    Ну и с т.з. безопасности снимать NoSchedule на контрол-плейне в книгах не рекомендуют. А вот взлетит ли ваш конфиг без этого? Я сам только начинаю, и строю кластер на calico+clonghorn как в книге.


    1. haoshand Автор
      29.01.2023 21:33

      Огромное спасибо за фидбек, учту описанные моменты. А насчёт работоспособности без NoSchedule, всё будет работать и не снимая его, т.к. все использованные в статье сервисы имеют Tolerations и будут развёрнуты. Метка была убрана, чтобы в дальнейшем можно было разместить сбор метрик для мониторинга, т.е., чтобы поды могли размещаться и на мастер ноде. Ещё одна причина это вероятная нехватка ресурсов.


    1. beckonlisp
      29.01.2023 22:44
      +1

      Зарегистрировался, чтобы спросить — в какой книге?
      Пытался найти в поисковиках, но на нашел книгу где описывается настройка кластера c Calico и longhorn


      1. DeeZ
        30.01.2023 06:14
        +3

        The Book of Kubernetes

        Репа с плейбуками для "подготовки" стендов к каждому уроку (в тч там есть устровка ванильного кубера, с Calico и longhorn. Урок 6).

        Но я дела не в AWS и не в Vargant, и в добавок на Debian, а не Ubuntu, по этому плйбук не работает для меня. Приходится все делать вручную. зато лучше разбираешься :)


    1. Odnokletochnoe
      30.01.2023 09:30

      Здравствуйте, поделисьтесь пожалуйста названием книги.


  1. r0binak
    29.01.2023 22:19
    +4

    Лучше конечно вместо Flannel использовать другой CNI плагин – Calico или Cilium например. Дело в том, что Flannel не поддерживает NetwokPolicy, что с точки зрения безопасности не есть хорошо.


    1. dimkus
      30.01.2023 00:22

      Поддерживаю. Calico и Cilium на сегодняшний день самые топовые на сколько мне известно.


  1. S1M
    30.01.2023 08:52
    +1

    Аннотация ingress.class в 1.22 была объявлена как deprecated. https://kubernetes.io/blog/2021/07/14/upcoming-changes-in-kubernetes-1-22/ Честно говоря не понимаю практический смысл ручного поднятия куба. Если чисто для себя, есть уже замыленная статья "Kubernetes hard way" где всё описано. Есть kubespray который позволяет штамповать кластера под копирку и при этом можно легко добавлять/удалять ноды.


    1. haoshand Автор
      30.01.2023 10:11

      Спасибо! Проресерчил и обновил момент с ingress.class.


  1. k3NGuru
    31.01.2023 10:44
    +11

    2023 год на дворе, а кластера все так развертывают kubeadm+flannel для петпроектов.

    Ну возьмите вы коробочные решения от Флант Deckhouse, или talos от Sidero Labs. Да на крайний случай даже обычный Docker Desktop уже умеет кластер.
    Зачем нужны эти лишние движения по разворачиванию вручном режиме? Профита никакого.