Загрузка данных
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