Загрузка данных



cat <<EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
nf_conntrack
EOF
modprobe overlay
modprobe br_netfilter
modprobe nf_conntrack
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe ip_set

lsmod | grep -E 'overlay|br_netfilter|ip_vs|ip_set'
cat <<EOF > /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 --system



apt-get update && apt-get install -y containerd
containerd config default | tee /etc/containerd/config.toml > /dev/null
grep SystemdCgroup /etc/containerd/config.toml
            SystemdCgroup = true
systemctl enable --now containerd

apt-get install -y cri-tools1.35

cat <<EOF > /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
EOF
    Проверяем:
[root@k8s-srv1 ~]# crictl image ls



sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

apt-get update && apt-get install -y kubernetes1.35-{kubeadm,kubelet} kubectl-node-shell

systemctl enable --now kubelet


Все действия ниже выполняются на нодах k8s-srv1, k8s-srv2, k8s-srv3

    Устанавливаем необходимые пакеты:

apt-get update && apt-get install -y keepalived haproxy

    Разрешаем привязку к IP-адресу, который не назначен на интерфейс (необходимо для работы с floating IP):

echo 'net.ipv4.ip_nonlocal_bind = 1' > /etc/sysctl.d/ha.conf

sysctl -p /etc/sysctl.d/ha.conf

k8s-srv1:

    Реализуем конфигурацию в соответствии с требованиями задания:
        MASTER, priority 110

cat <<EOF > /etc/keepalived/keepalived.conf
global_defs {
    enable_script_security
    max_auto_priority
}

vrrp_script chk_haproxy {
  script "killall -0 haproxy"
  interval 2
  weight 2
}

vrrp_instance VI_1 {
  interface eth0
  state MASTER

  virtual_router_id 110
  priority 110

  virtual_ipaddress {
    192.168.0.253/24
  }

  track_script {
    chk_haproxy
  }
}
EOF

    Включаем службу keepalived:

systemctl enable --now keepalived

    Проверяем наличие VIP-адреса:

[root@k8s-srv1 ~]# ip -c -br -4 a
lo               UNKNOWN        127.0.0.1/8
eth0             UP             192.168.0.201/24 192.168.0.253/24
[root@k8s-srv1 ~]#

k8s-srv2:

    Реализуем конфигурацию в соответствии с требованиями задания:
        BACKUP, priority 105

cat <<EOF > /etc/keepalived/keepalived.conf
global_defs {
    enable_script_security
    max_auto_priority
}

vrrp_script chk_haproxy {
  script "killall -0 haproxy"
  interval 2
  weight 2
}

vrrp_instance VI_1 {
  interface eth0
  state BACKUP

  virtual_router_id 110
  priority 105

  virtual_ipaddress {
    192.168.0.253/24
  }

  track_script {
    chk_haproxy
  }
}
EOF

    Включаем службу keepalived:

systemctl enable --now keepalived

k8s-srv3:

    Реализуем конфигурацию в соответствии с требованиями задания:
        BACKUP, priority 100

cat <<EOF > /etc/keepalived/keepalived.conf
global_defs {
    enable_script_security
    max_auto_priority
}

vrrp_script chk_haproxy {
  script "killall -0 haproxy"
  interval 2
  weight 2
}

vrrp_instance VI_1 {
  interface eth0
  state BACKUP

  virtual_router_id 110
  priority 100

  virtual_ipaddress {
    192.168.0.253/24
  }

  track_script {
    chk_haproxy
  }
}
EOF

    Включаем службу keepalived:

systemctl enable --now keepalived
Конфигурация haproxy

Конфигурация одинаковая на всех трёх нодах.
cat <<EOF > /etc/haproxy/haproxy.cfg
global
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    daemon

defaults
    log     global
    mode    tcp
    retries 2
    timeout client 30m
    timeout connect 4s
    timeout server 30m
    timeout check 5s

frontend main
    bind 0.0.0.0:7443
    default_backend             app

backend app
    balance     roundrobin
    server k8s-srv1 192.168.0.201:6443 check
    server k8s-srv2 192.168.0.202:6443 check
    server k8s-srv3 192.168.0.203:6443 check

listen stats
    bind *:9000
    mode http
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats uri /haproxy_stats
EOF

    Включаем службу haproxy:

systemctl enable --now haproxy

Вариант реализации:
k8s-srv1:

sudo su -

    Для инициализации кластера подготовим конфигурационный файл kubeadm-config.yaml:

cat <<EOF > /etc/kubernetes/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "1.35.0"
controlPlaneEndpoint: "192.168.0.253:7443"
networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.96.0.0/12"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  strictARP: true
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDNS:
  - 169.254.25.10
systemReserved:
  cpu: "500m"
  memory: "512Mi"
EOF


Разбор параметров

Используйте последнюю версию API для вашей текущей версии kubernetes. Посмотреть все варианты можно в документации.

InitConfiguration — настройки процесса инициализации:

    criSocket — путь к сокету CRI (containerd).

ClusterConfiguration — настройки кластера:

    kubernetesVersion — версия Kubernetes.
    controlPlaneEndpoint — адрес и порт для подключения к API серверу. Указываем floating IP и порт haproxy (192.168.0.252:7443). Это обеспечивает высокую доступность — клиенты подключаются через балансировщик, а не напрямую к конкретной ноде.
    podSubnet — подсеть для подов. Должна совпадать с CIDR в настройках CNI (Calico).
    serviceSubnet — подсеть для сервисов.

KubeProxyConfiguration — настройки kube-proxy:

    mode: "ipvs" — режим работы. IPVS обеспечивает лучшую производительность по сравнению с iptables при большом количестве сервисов.
    strictARP: true — необходим для корректной работы некоторых балансировщиков (например, MetalLB).

KubeletConfiguration — настройки kubelet:

    clusterDNS — адрес DNS сервера кластера. Указываем 169.254.25.10 — это адрес NodeLocalDNS, который будет установлен позже. NodeLocalDNS кэширует DNS запросы на каждой ноде, снижая нагрузку на CoreDNS и уменьшая задержки.
    systemReserved — ресурсы, зарезервированные для системных процессов ОС. Kubelet не будет отдавать их подам.


Инициализация кластера

    Выполняем инициализацию на ноде k8s-srv1:
        флаг --upload-certs загружает сертификаты в Secret кластера, что позволяет другим control нодам присоединиться без ручного копирования сертификатов

kubeadm init --config /etc/kubernetes/kubeadm-config.yaml --upload-certs

        ожидаемый результат (процесс не быстрый, так как происходит загрузка необходимых образов):

...
Your Kubernetes control-plane has initialized successfully!
...

    Для удобства настроем kubectl на текущей ноде, а на ADM-PC в соответствующем разделе задания:

mkdir -p $HOME/.kube

ln -s /etc/kubernetes/admin.conf $HOME/.kube/config

    Проверяем:

        нода в статусе NotReady — это нормально, пока не установлена сетевая подсистема (CNI)

[root@k8s-srv1 ~]# kubectl get nodes
NAME                     STATUS     ROLES           AGE   VERSION
k8s-srv1.au-team.cloud   NotReady   control-plane   41s   v1.35.0
[root@k8s-srv1 ~]#

Вариант реализации:
k8s-srv1:

sudo su -

    Устанавливаем требуемый оператор Tigera:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/tigera-operator.yaml

    Создаём манифест для оператора Tigera, файл calico-install.yaml:

cat <<EOF > /etc/kubernetes/manifests/calico-install.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
      - name: default-ipv4-ippool
        blockSize: 26
        cidr: 10.244.0.0/16
        encapsulation: IPIPCrossSubnet
        natOutgoing: Enabled
        nodeSelector: all()
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}
EOF

Параметры:

    cidr — подсеть для подов. Должна совпадать с podSubnet в конфигурации kubeadm.
    blockSize — размер блока IP адресов, выделяемых каждой ноде (26 = 64 адреса).
    encapsulation: IPIPCrossSubnet — IPIP инкапсуляция используется только для трафика между подсетями. Внутри одной подсети трафик идёт напрямую.
    natOutgoing — включает NAT для исходящего трафика подов.



    Применяем манифест:

kubectl create -f /etc/kubernetes/manifests/calico-install.yaml

    Дождёмся готовности всех подов:
        процесс не быстрый

watch kubectl get pods -A

    Ожидаемый результат:

NAMESPACE          NAME                                             READY   STATUS    RESTARTS   AGE
calico-apiserver   calico-apiserver-5cc557c79d-jn756                1/1     Running   0          3m11s
calico-apiserver   calico-apiserver-5cc557c79d-rlknf                1/1     Running   0          3m11s
calico-system      calico-kube-controllers-74d6b68f67-5f5lp         1/1     Running   0          3m10s
calico-system      calico-node-cvgg7                                1/1     Running   0          3m11s
calico-system      calico-typha-5578d8d9c7-lq7sc                    1/1     Running   0          3m11s
calico-system      csi-node-driver-g5vfj                            2/2     Running   0          3m11s
kube-system        coredns-7d764666f9-5jjj7                         1/1     Running   0          13m
kube-system        coredns-7d764666f9-sp55x                         1/1     Running   0          13m
kube-system        etcd-k8s-srv1.au-team.cloud                      1/1     Running   1          13m
kube-system        kube-apiserver-k8s-srv1.au-team.cloud            1/1     Running   1          13m
kube-system        kube-controller-manager-k8s-srv1.au-team.cloud   1/1     Running   1          13m
kube-system        kube-proxy-lp5jk                                 1/1     Running   0          13m
kube-system        kube-scheduler-k8s-srv1.au-team.cloud            1/1     Running   1          13m
tigera-operator    tigera-operator-75ddfd89b-pcqrw                  1/1     Running   0          5m50s

    После старта приложений calico, нода перейдёт в статус Ready:

[root@k8s-srv1 ~]# kubectl get nodes
NAME                     STATUS   ROLES           AGE   VERSION
k8s-srv1.au-team.cloud   Ready    control-plane   14m   v1.35.0

Вариант реализации:
k8s-srv1:

sudo su -

Зачем нужен NodeLocalDNS

В стандартной конфигурации все DNS запросы от подов проходят через conntrack и NAT к ClusterIP сервиса kube-dns. При высокой нагрузке это может приводить к race condition в conntrack таблице и потере DNS пакетов.

NodeLocalDNS запускает кэширующий DNS сервер на каждой ноде по адресу 169.254.25.10 (link-local) и это:

    Исключает DNAT и conntrack для DNS запросов.
    Кэширует ответы, снижая нагрузку на CoreDNS.
    Уменьшает задержки DNS.



    Манифест nodelocaldns-daemonset.yaml на ноде k8s-srv1:

cat <<EOF > /etc/kubernetes/manifests/nodelocaldns-daemonset.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: node-local-dns
  namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
                success 9984 30
                denial 9984 5
        }
        reload
        loop
        bind 169.254.25.10
        forward . 10.96.0.10 {
                force_tcp
        }
        prometheus :9253
        health 169.254.25.10:8080
    }
    in-addr.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . 10.96.0.10 {
                force_tcp
        }
        prometheus :9253
    }
    ip6.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . 10.96.0.10 {
                force_tcp
        }
        prometheus :9253
    }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . /etc/resolv.conf
        prometheus :9253
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    k8s-app: node-local-dns
spec:
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 10%
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
    spec:
      priorityClassName: system-node-critical
      serviceAccountName: node-local-dns
      hostNetwork: true
      dnsPolicy: Default
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
        - effect: "NoExecute"
          operator: "Exists"
        - effect: "NoSchedule"
          operator: "Exists"
      containers:
        - name: node-cache
          image: registry.k8s.io/dns/k8s-dns-node-cache:1.24.0
          resources:
            requests:
              cpu: 25m
              memory: 5Mi
          args:
            - "-localip"
            - "169.254.25.10"
            - "-conf"
            - "/etc/Corefile"
            - "-upstreamsvc"
            - "kube-dns"
            - "-skipteardown=true"
            - "-setupinterface=true"
            - "-setupiptables=true"
          securityContext:
            capabilities:
              add:
                - NET_ADMIN
          ports:
            - containerPort: 53
              name: dns
              protocol: UDP
            - containerPort: 53
              name: dns-tcp
              protocol: TCP
            - containerPort: 9253
              name: metrics
              protocol: TCP
          livenessProbe:
            httpGet:
              host: 169.254.25.10
              path: /health
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
          volumeMounts:
            - mountPath: /run/xtables.lock
              name: xtables-lock
              readOnly: false
            - name: config-volume
              mountPath: /etc/coredns
            - name: kube-dns-config
              mountPath: /etc/Corefile
              subPath: Corefile
      volumes:
        - name: xtables-lock
          hostPath:
            path: /run/xtables.lock
            type: FileOrCreate
        - name: kube-dns-config
          configMap:
            name: node-local-dns
        - name: config-volume
          emptyDir: {}
EOF

Приведенная ниже часть конфигурационного файла, говорит DNS серверу что бы он пересылал запросы к основному DNS серверу кластера на указанный ip по протоколу TCP. Т.е. мы избавляемся от UDP трафика между нодами кластера в DNS запросах:

  forward . 10.96.0.10 {
                force_tcp
        }

10.96.0.10 - это IP адрес сервиса core-dns кластера. Он выбирается автоматически в зависимости от того, какую сеть вы указали в параметре serviceSubnet при создании кластера в kind: ClusterConfiguration.

Так же обратите внимание на строку: hostNetwork: true. Это значит что контейнер будет открывать порт на прослушивание на сетевых интерфейсах хоста.

Поскольку мы использовали DaemonSet, кеширующий DNS сервер будет автоматически запускаться на всех нодах кластера.

    Применяем:

kubectl apply -f /etc/kubernetes/manifests/nodelocaldns-daemonset.yaml

    Проверяем:

[root@k8s-srv1 ~]# kubectl get pods -n kube-system -l k8s-app=node-local-dns
NAME                   READY   STATUS    RESTARTS   AGE
node-local-dns-mc9np   1/1     Running   0          20s