Привет, меня зовут Руслан, я энтузиаст одного отдела искусственного интеллекта, занимаюсь автоматизацией процесса разработки и контролем за инфраструктурой внутри 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 записи аннотациюkubernetes.io/ingress.class: "nginx"
для работы контроллера в Ingress. Буду рад любому фидбеку и советам, как улучшить инфраструктуру. Всем пока!
Автор:
haoshand