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