В этой заметке развернем кластер куба используя инструмент - kubespray. Если вы уже читали про методы установки кластера, то косвенно знакомы с kubespray. Вкратце это сборник ansible-плэйбуков и ролей, дающий нам возможности:

  • Основное это установка кластера k8s, установка как актуальной версии, так у более старой версии кластера,
  • Установка контейнерного runtime (Docker, Cri-o, Containerd),
  • Установка сетевых решений (Calico, Flannel),
  • Установка и настройка кластера etcd,
  • Возможность установки дополнительных компонентов - это DNS, Ingress-контроллеры, Cert-менеджеры, Storage-драйверы и прочее.
  • Добавление/Удаление нод кластера.
  • Обновеление кластера.

Стоит сделать отступление относительно установки дополнительных компонентов, все же рекомендуют накатывать компоненты независимо от kubespray. Так как обновление или обычная замена версии компонента, может привести к проблемкам.

Подготовка серверов

Если же говорить про продакшен сетап кластера, то рекомендуется использовать некое количество нод:

  • 3/5 - нод кластера под Control Plane,
  • 3/5 - экземпляров etcd,
  • 2 - ноды под Ingress-контроллер,
  • 2 - ноды под рабочие нагрузки, worker-ноды.

Для запуска тестового кластера я выделил сервера. Три ноды непосредственно под сам кластер и одна маленькая тачка под менеджмент и установку кластеров:

Server name Server IP Short comment
k8s-node1 192.168.122.51 K8s node master, etcd
k8s-node2 192.168.122.52 K8s worker node
k8s-node3 1192.168.122.53 K8s worker node
k8s-bootstrap 192.168.122.5 K8s, bootstrap server

Итак на всех серверах нужно провести ряд подготовительных работ. Во первых это отключить firewalld, сам кубернетес за счет iptables будет разруливать правилами. Далее отключаем swap-раздел, так как kubelet не умеет работать со свапом.

# Отключаем firewalld
[All Cluster Servers]# systemctl disable --now firewalld

# Отключаем swap, также не забываем закоментировать в /etc/fstab
[All Cluster Servers]# swapoff -a

Что касаемо используемого дистрибутива, я этом сетапе я использую - Almalinux 8.6 (К сожалению, только такой заготовленный шаблон был =). Ну одно из важных требований к установки - это версия ядра, в рекомендациях допускается к использованию ядро выше версии 4.20. У меня к сожалению текущая версия ядра 4.18:

Linux k8s-node3.domain.local 4.18.0-372.32.1.el8_6.x86_64 #1 SMP Tue Oct 25 05:53:57 EDT 2022 x86_64 x86_64 x86_64 GNU/Linux

Поэтому будем обновлять ядро.

Более-менее актуальное ядро будем ставить из репозитория elrepo, импортируем gpg-ключи и устанавливаем репозиторий.

[All Cluster Servers]# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
[All Cluster Servers]# yum install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm

Ну и запускаем установку нового ядра из этой репы. В данной инсталяции я использую long term релиз ядра.

[All Cluster Servers]# yum --enablerepo=elrepo-kernel install kernel-lt

Перезапускаем сервак, и смотрим текущую версию ядра:

[All Cluster Servers]# uname -r
5.4.242-1.el8.elrepo.x86_64

Далее разрешаем форвард межсетевого трафика между интерфейсами:

[All Cluster Servers]# echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf

Запускать kubespray будем из под пользователя - root, поэтому я разрешил логин из под рута. Далее сгенерил пару ключей и публичную часть ключа раскидал на сервера:

# Генерим новый ключ
[root@k8s-bootstrap ~]# ssh-keygen -t rsa -b 2048 -C "K8s, bootstrap srv"

# Раскидываем ключи на сервера
[root@k8s-bootstrap ~]# ssh-copy-id root@192.168.122.51
[root@k8s-bootstrap ~]# ssh-copy-id root@192.168.122.52
[root@k8s-bootstrap ~]# ssh-copy-id root@192.168.122.53

Установка кластера

В первую очередь нам нужно заполучить репозиторий, по этому идем на гибхаб страничку kubespray, и клонируем репозиторий:

[root@k8s-bootstrap ~]# yum install git
[root@k8s-bootstrap ~]# git clone https://github.com/kubernetes-sigs/kubespray

Обратите внимание, что все делаем на bootstrap-сервере. Запуск кубеспрея, логично, тоже будет запускать с этого сервака.

Теперь проваливаемся внутрь репозитория, и начинаем ставить зависимости python.

# Ставим сам питон
[root@k8s-bootstrap kubespray]# yum install python39

# Запускаем установку зависимостей и ожидаем окончание процесса..
[root@k8s-bootstrap kubespray]# pip3 install -r requirements.txt

Настройка inventory

Внутри репозитория будет содержаться каталог - inventory, в котором мы и будем описывать наши сервера и настройки кластера.

Авторы kubespray во внутрь инвертаря заложили семпл каталог для примера. Копируем семпл, имя для нового каталога указываем аналогично имени кластера:

[root@k8s-bootstrap kubespray]# cp -rp inventory/sample/ inventory/k8s-dev-cluster

Смотрим, что у нас есть:

[root@k8s-bootstrap kubespray]# tree inventory/k8s-dev-cluster/ -L 1
inventory/k8s-dev-cluster/
├── group_vars
├── inventory.ini
└── patches

Внутри нашего инвенторя есть основные компоненты содержимого на сегодня:

  • inventory.ini - файл с инвентарем, содержащий настройки подключения к серверам и список самих серверов.
  • group_vars - в этом каталоге содержать переменные для установок различных характеристик нашего кластера.

На данном этапе нам требуется заполнить файл с инвентарем. Сделать это мы можем двумя способами.:

  1. Первый способ за счет ручного редактирования файла inventory.ini.
  2. Второй способ через питоновский скрипт, который входит в состав этой репы. Этот скрипт якобы упрощает нам заполнение инвенторя от части это так, но мне все равно по факту приходилось редактировать полученный файл с хостами.

Мы же пойдем первым способом, и просто ручками отредактируем inventory.ini. Открываем инвентарь, и прописываем наши сервера. Далее указываем роли для них.

[root@k8s-bootstrap kubespray]# vim inventory/k8s-dev-cluster/inventory.ini
---
[all]
k8s-node1 ansible_host=192.168.122.51  ip=192.168.122.51
k8s-node2 ansible_host=192.168.122.52  ip=192.168.122.52
k8s-node3 ansible_host=192.168.122.53  ip=192.168.122.53

[kube_control_plane]
k8s-node1

[etcd]
k8s-node1

[kube_node]
k8s-node2
k8s-node3

[k8s_cluster:children]
kube_control_plane
kube_node

По итогу инвентарь должен выглядить таким образом. В контексте all, идет перечесление всех нод кластера. Далее уже в отдельных контекстах группируем ноды. Здесь kubespray засетапит control plane и etcd на первую ноду. Остальные сервера настроит как обычные ноды куба.

Настройка group_vars

Теперь перейдем к настройке переменных в каталоге group_vars:

[root@k8s-bootstrap kubespray]# tree inventory/k8s-dev-cluster/group_vars/ -L 1
inventory/k8s-dev-cluster/group_vars/
├── all
├── etcd.yml
└── k8s_cluster

В ansible переменные для групп хостов могут быть обозначаны в обычном yml-файле, или же в отдельном каталоге с названием группы. Внутри группы содержаться множество файлов с переменными. Так например, каталог - all внутри его определяются переменные которые применяются всех хостов инвенторя. Переменые вынесены по отдельным атомарным файлам, и влияют на конфигурацию того или иного сервиса. Если же взглянуть во внутрь docker.yml, файл содержит переменные влиящие на настройку докера.

inventory/k8s-dev-cluster/group_vars/all
├── all.yml
├── aws.yml
├── azure.yml
├── containerd.yml
├── coreos.yml
├── cri-o.yml
├── docker.yml
├── etcd.yml
└── vsphere.yml

Далее файл etcd.yml, содержит набор переменных для настройки etcd кластера. И самое интересное это группа k8s_cluster, здесь лежат все переменные для настройки серверов, на которых будет запущен k8s кластер. В этой группе и будем вносить все последующие изменения.

[root@k8s-bootstrap kubespray]# vim inventory/k8s-dev-cluster/group_vars/k8s_cluster/k8s-cluster.yml
---
cluster_name: k8s-dev-cluster
kube_version: v1.26.3
kube_network_plugin: flannel
kube_proxy_mode: iptables
kube_service_addresses: 10.233.0.0/18
kube_pods_subnet: 10.233.64.0/18
dns_mode: coredns
kubeconfig_localhost: true

Большинство переменных уже определено по умолчанию, но все же нужные мне переменные я стараюсь перепроведить и подправить походу.

  • cluster_name - тут в значении указывается имя кластера,
  • kube_version - версия кластера, по умолчанию стоит самая последняя версия
  • kube_network_plugin - используемый сетевой плагин (cni). В этом сетапе я буду использовать flannel. Этот плагин намного проще, и идеально вписывается в нашу конценцию где все ноды кластера работают из одной сети.
  • kube_proxy_mode - режим проксирования, по умолчанию стоит ipvs. Я всеже предпочитаю iptables.
  • kube_service_addresses - здесь прописываем адресный пул, ip-адреса из которой будут выдаваться нашим сервисом с режимом работы ClusterIP.
  • kube_pods_subnet - и здесь указывается пул, из которого будут выдаваться адреса для подов.
  • dns_mode - здесь указываем dns сервер, который будет обслуживать наш кластер
  • kubeconfig_localhost - а включение этой опции, сгенерит kube-конфиг для подключения к кластеру.

Теперь нужно внести изменения в файл с описанием переменных, которые настраивают Flannel. Как я ранее говорил, все настройки сгруппированные по отдельным файлам. Открываем файл с переменнымы для flannel, и раскомментируем:

[tony@i3Arch kubespray]$ vim inventory/k8s-dev-cluster/group_vars/k8s_cluster/k8s-net-flannel.yml
---
flannel_interface_regexp: '192\\.168\\.122\\.\\d{1,3}'
flannel_backend_type: "host-gw"
  • flannel_backend_type - здесь мы переопределяем режим работы плагина, на режим host gateway.
  • flannel_interface_regexp - эта регулярка описывающая, в какой сети у меня будут подняты сервера

В процессе установки кластера столкнулся с проблемой настройки flannel. По этой проблеме есть issue на гитхабе kubespray - github. Что бы исправить это нужно в файле - roles/download/defaults/main.yml, подправить путь к образу flannel:

Найти: flannel_image_repo: "{{ docker_image_repo }}/flannelcni/flannel"
Заменить на: flannel_image_repo: "{{ docker_image_repo }}/flannel/flannel"

И еще одна опциональная настройка. Если ваши сервера также как и у меня спрятаны за семью прокси, то дополнительно в group_vars нужно указать параметры подключения к прокси:

[root@k8s-bootstrap kubespray]# vim inventory/k8s-dev-cluster/group_vars/all/all.yml
---
https_proxy: "srv-proxy.local:3357"

Запуск kubespray и первичный осмотр

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

Я запускаю ssh агента, и добавляю ключ для него.

[root@k8s-bootstrap kubespray]# eval `ssh-agent`
[root@k8s-bootstrap kubespray]# ssh-add ../.ssh/id_rsa

Ну и запускаю сценарий на ansible:

[root@k8s-bootstrap kubespray]# ansible-playbook -i inventory/k8s-dev-cluster/inventory.ini -u root cluster.yml

Установка будет займет от 10-20 минут времени. Поэтому идем пить кофе, в надежде что нечего не сфейлится. По окончанию установки нас будет ждать вот такой recap ансибла:

PLAY RECAP **********************************************************************************
k8s-node1      : ok=741  changed=135  unreachable=0    failed=0    skipped=1248 rescued=0    ignored=8
k8s-node2      : ok=512  changed=79   unreachable=0    failed=0    skipped=763  rescued=0    ignored=1
k8s-node3      : ok=512  changed=79   unreachable=0    failed=0    skipped=762  rescued=0    ignored=1
localhost      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

В каталоге inventory/k8s-dev-cluster/artifacts/ лежит конфиг для подключения к кластеру, который нам любезно сгенерил kubespray. Используя этот конфиг мы можем обратится на api сервер нашего кластера прямо с ансибл сервера, через kubectl утилиту:

[root@k8s-bootstrap ~]# KUBECONFIG=kubespray/inventory/k8s-dev-cluster/artifacts/admin.conf kubectl get nodes -o wide
NAME        STATUS   ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                    KERNEL-VERSION                CONTAINER-RUNTIME
k8s-node1   Ready    control-plane   1h   v1.26.3   192.168.122.51   <none>        AlmaLinux 8.6 (Sky Tiger)   5.4.242-1.el8.elrepo.x86_64   containerd://1.7.0
k8s-node2   Ready    <none>          1h   v1.26.3   192.168.122.52   <none>        AlmaLinux 8.6 (Sky Tiger)   5.4.242-1.el8.elrepo.x86_64   containerd://1.7.0
k8s-node3   Ready    <none>          1h   v1.26.3   192.168.122.53   <none>        AlmaLinux 8.6 (Sky Tiger)   5.4.242-1.el8.elrepo.x86_64   containerd://1.7.0

Как можно заметить из вывода, у наших worker нодах неопределена роль текущий статус - <none>. Честно говоря, я не нашел причину такого поведения, но поправить этот момент можно путем релэйблинга ноды:

[root@k8s-bootstrap ~]# export KUBECONFIG=kubespray/inventory/k8s-dev-cluster/artifacts/admin.conf
[root@k8s-bootstrap ~]# kubectl label nodes k8s-node2 kubernetes.io/role=worker
[root@k8s-bootstrap ~]# kubectl label nodes k8s-node3 kubernetes.io/role=worker

Теперь давайте просмотрим на компоненты и текущее состояние нашего кластера. Выведим список всех namespaces:

[root@k8s-bootstrap ~]# kubectl get ns
NAME              STATUS   AGE
default           Active   14h
kube-node-lease   Active   14h
kube-public       Active   14h
kube-system       Active   14h
  • default - это namespace, который используется по умолчанию. Если при запуске приложения мы не указываем нужный namespace, то поды будут размещены в этом пространстве.
  • kube-node-lease - namespace, который содержит lease-объекты для каждой ноды.
  • kube-public - данный namespace создается автоматически, и доступен для всех пользователей (даже неаутентифицированных). Этот неймспэйс может быть использован, когда создаваемые объекты должны быть доступны для всего кластера.
  • kube-system - это системный неймспейс, в котором обитают все компоненты control plane.

Посмотрим, какие поды запущены в kube-system:

[root@k8s-bootstrap ~]# kubectl get po -n kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
coredns-645b46f4b6-9b56x                  1/1     Running   0          3h43m
coredns-645b46f4b6-lf469                  1/1     Running   0          3h43m
dns-autoscaler-659b8c48cb-zhp8t           1/1     Running   0          3h43m
kube-apiserver-k8s-node1.local            1/1     Running   2          3h44m
kube-controller-manager-k8s-node1.local   1/1     Running   2          3h44m
kube-flannel-7l2t8                        1/1     Running   0          3h43m
kube-flannel-9qbvz                        1/1     Running   0          3h43m
kube-flannel-z7zmz                        1/1     Running   0          3h43m
kube-proxy-2z88x                          1/1     Running   0          3h44m
kube-proxy-pztwt                          1/1     Running   0          3h44m
kube-proxy-v6g5l                          1/1     Running   0          3h44m
kube-scheduler-k8s-node1.local            1/1     Running   1          3h44m
nginx-proxy-k8s-node2.local               1/1     Running   0          3h43m
nginx-proxy-k8s-node3.local               1/1     Running   0          3h43m
nodelocaldns-bwsgc                        1/1     Running   0          3h43m
nodelocaldns-sqlfj                        1/1     Running   0          3h43m
nodelocaldns-zcf29                        1/1     Running   0          3h43m

Вывод этой команды вернет нам список запущенных статик подов, то есть подов которые были запущены самим kubelet из манифеста.

Здесь же можем увидеть запущенные по одному экземпляру - apiserver, controller-manager, scheduler. Coredns - запущенный dns отвечающий за service discovery, dns-autoscaler компонент который мониторит количество узлов в кластере и если мы добаляем новую ноду в кластер, то он увеличивает поды coredns. nginx-proxy - специальный прокси, который запускается на каждом узле и обеспечивает постоянную связь kubelet с api-сервером. Ощутимая польза от использования этой штуки, когда в кластере используется 3 api-сервер, и с помошью этого компоненты kubelet всегда сможет кинуть запрос на api.

Отлично, мы подняли кластер за 30 минут для дальнейших экспериментов.