Install Kubernetes cluster
Last updated
Last updated
https://habr.com/ru/companies/domclick/articles/682364/
В предыдущей статье я рассказывал, как построить простой кластер Kubernetes с одним мастер-узлом. Прошло время, опали листья... и мне захотелось большего, поэтому решил позариться на высокодоступные кластеры. В интернете много статей о том, как построить подобное решение, и давайте даже опустим тот факт, что многие из них уже устарели. Одно дело — установить кластер, а как же обслуживание: удаление, добавление, замена узлов? Про это и не вспоминают! В итоге оказалось, что не всё так просто, и вот, спустя больше ста установок, удалений и замен, у меня получилось собрать подробнейшее руководство по установке и, главное, обслуживанию highly available кластера с помощью Kubespray.
Высокодоступный кластер (aka high-availability) - это еще один шажок к production-ready кластеру. Как написано в официальной документации, построить высокодоступный кластер значит:
Отделить плоскость управления (мастер) от рабочих узлов
Реплицировать компоненты плоскости управления на несколько узлов
Добавить балансировщик нагрузки на API Kubernetes
Иметь достаточное количество рабочих узлов, чтобы выдержать нештатные ситуации и высокие нагрузки
Если подходить серьезно к вопросу, то наш будущий кластер будет квази high-available (в основном из-за внешнего балансировщика). Кластер будет состоять из четырёх узлов — двух мастеров и двух рабочих. Машины с характеристиками получше можно использовать как рабочие, а машины послабее — как мастера. На последних не будет запускаться рабочая нагрузка.
Сетап кластера:
Kubenetes version - 1.23.7
Сontainer runtime - Сontainerd 1.6.4
Network plugin - Calico 3.22.3
Доступные виртуалки и их роли я распределил так:
IP 185.186.142.53 - Master #1
IP 185.186.142.5 - Master #2
IP 46.8.19.144 - Worker #1
IP 46.8.19.244 - Worker #2
У всех машин характеристики: 2 CPU 3,0 ГГц, 4 Гб RAM, 100 Гб HDD, ОС Ubuntu 20.04. На рабочих машинах заявлена частота процессора 3,6 ГГц, хотя команда grep MHz /proc/cpuinfo
говорит, что фактически 3 ГГц (на мастер-узлах и того чуть меньше). По стоимости четыре машины в месяц обходятся в 2700 руб. (с учётом скидки за оплату на три месяца вперёд). Хочу опять отметить, насколько такой handmade дешевле Kubernetes-as-a-Service решений, аж на 75%!
Важно (но не обязательно) чтобы ваш VPS провайдер поддерживал снимки виртуальных машин, это облегчит обслуживание и тренировку по установке кластера.
Устанавливать кластер будем с домашней машины (далее — Ansible-машина). На ней должны быть Python, Ansible версии 2.4 или выше, и Jinja (это всё мы установим немного позже).
Большая головная боль при установки Kubernetes на "голое" железо - это отсутствие внешнего балансировщика. Для пользователя облачного сервиса GKE или AWS балансировщик прилагается в комплекте.
В предыдущей своей статье проблему решал с помощью MetalLb, в этот раз попробую другой подход. Все 4 узла смогут принимать трафик извне, так как поды ingress-nginx будут развернуты на каждом узле (с помощью ресурса Daemonset) и использовать порт 80/443 этой машины.
И конечно нужно привести цитату с предостережением по безопасности данного решения:
Enabling this option exposes every system daemon to the NGINX Ingress controller on any network interface, including the host's loopback. Please evaluate the impact this may have on the security of your system carefully.
Заключительный штрих - я заказал доменное имя awesomeservice.pro на которое будут разрешаться все 4 IP адреса, таким образом задействуем DNS балансировку трафика. Балансировка осуществляется с помощью алгоритма Round Robin — алгоритма кругового обслуживания. Первый запрос передается одному серверу, затем следующий запрос передается другому, и так далее до достижения последнего сервера. Затем направление запросов начинается сначала. Это самое бюджетное и простое решение которое я нашел на данный момент. Именно из-за DNS балансировки будущий кластер выйдет почти высокодоступным. Данное решение имеет ряд неприятных минусов:
Сложно управлять пулом адресов. Хорошо если DNS провайдер предоставляет API для программного изменения адресов. Но это лишний софт, плюс надо учитывать TTL на стороне клиента, его в таком случае советуют ставить на минимальное значение.
И самая большая проблема - нет отслеживания состояния серверов. Если одна машина из пула выйдет из строя, DNS сервер все равно будет отдавать этот адрес. Эту проблему и её последствия я наглядно продемонстрирую после установки кластера.
альтернативное решение
Если ваш VPS провайдер позволяет создавать Virtual IP, то можно его привязать ко всем 4 удаленным машинам и тогда не нужна будет никакая DNS балансировка. Мой провайдер, к сожалению, не предоставляет такой функционал, поэтому протестировать такое решение не могу. А там, где есть данная фича VPS стоят неоправданно дорого.
Приступим к делу.
В основе главы лежит эта статья, но на текущий момент она уже устарела, поэтому некоторые разделы будут продублированы и дополнены либо изменены.↑
Этот раздел необходимо выполнить для каждой виртуальной машины будущего кластера. Прежде всего настроим SSH-доступ без пароля. Для этого выполните команду ssh-copy-id
на своей локальной машине. Например, у меня она выглядит так:
Тут необходимо будет ввести пароль от удалённой машины. После успешного выполнения команды подключаемся к удалённой машине без пароля:
Установим Python:
Включим переадресацию IPv4:
Отключим подкачку памяти:
Пример из жизни №1
Раньше я полагал, что поставщики виртуалок предоставляют чистые образы ОС. Как оказалось, это не совсем так. Я пробовал установить Kubernetes на VPS двух провайдеров. У первого всё работало замечательно, а у второго (который был предпочтителен из-за низкой цены) установка кластера прерывалась на середине из-за внезапного пропадания интернета, а точнее, невозможности скачать определённый файл.
Поиск в интернете упорно ничего не давал, подсказали только в техподдержке: предложили заглянуть в файл resolv.conf и проверить наличие в конфигурации серверов адресов 1.1.1.1 или 8.8.8.8. Как оказалось, у этого провайдера интересная преднастройка ОС, и файл /etc/resolv.conf представляет собой simlink. О чём недвусмысленно сообщал объёмный комментарий в самом файле:
И в самом файле, конечно, этих резолверов обнаружено не было. Проблему удалось решить только удалением simlink и пересозданием файла с добавлением двух DNS-резолверов (в принципе, будет достаточно одного 1.1.1.1 или 8.8.8.8).
В итоге файл выглядит следующим образом:
Удаленная машина успешно настроена и готовка к установке Kubernetes. Инструкции из этого подраздела повторите на всех оставшихся машинах будущего кластера.
Также крайне рекомендую на этом этапе сделать снимок системы, чтобы при переустановке кластера можно было откатить систему до текущего состояния. Причём даже не понадобится перенастраивать SSH-доступ без пароля. И тогда в будущем об этом подразделе можно забыть. Функциональность снимков предоставляется провайдером VPS (у меня это vmmanager).
Эти инструкции нужно выполнить только на своей локальной машине, с которой будет устанавливаться Kubernetes. Перейдите в подготовленную директорию и клонируйте репозиторий проекта Kubespray:
Помните, что подобные инструкции по установке любого софта без указания версии могут в будущем привести к проблемам. Либо статья устареет, либо новые версии могут быть обратно несовместимыми. Если вы уверены в себе и готовы решать проблемы и несостыковки, клонируйте мастер-ветку. У меня на данный момент tag v2.19.0. Команда для переключения:
Перейдём к установке Kubernetes-кластера. Подготовим наш инвентарь, шаблон находится в папке sample, мы его скопируем под новым именем и будем использовать при установке:
Должна появиться новая директория k8s*.* Теперь объявим переменную IPS
массивом с перечислением наших виртуальных машин, это нужно для последующего генерирования конфигурации:
Сгенерируем конфигурацию в новый инвентарь:
Был создан файл, посмотрим его содержимое:
У меня конфиг выглядит следующих образом
Получилась такая конфигурация кластера:
Мастер-узлы перечисляются в разделе kube_control_plane
, в моём случае это node1 и node2.
Всего четыре Kubernetes-узла: node1, node2, node3 и node4.
Etcd-кластер будет установлен на три хоста: node1, node2 и node3. Три потому, что для etcd-кластера жизненно необходим кворум большинства, то есть всегда должно быть нечётное количество работающих узлов. Если попробуете удалить один из хостов в etcd:hosts
, то Ansible-роль завершится с ошибкой о необходимости нечётного количества узлов
Настройки будущего кластера находятся в папке group_vars. Чтобы не устанавливать Helm вручную на каждом мастер-узле, установим в файле addons.yaml параметр helm_enable
равным true
:
В том же файле addons.yaml раскомментируем и активируем nginx-ingress, а также параметр ingress_nginx_host_network, благодаря которому, к каждому узлу будет привязана пода nginx-ingress:
Итоговый файл выглядит следующим образом
Добавим в конфигурацию Ansible пользователя, под которым логинимся по SSH:
В группе ssh_connection
добавьте параметр remote_user
, равный root
:
Полностью файл выглядит так:
И наконец, команда запуска установки кластера. Выполните её в корне директории kubespray:
Установка кластера может занять от получаса до полутора часов, в зависимости от скорости интернета. После установки вывод в консоли должен быть примерно таким:
Теперь нужно проверить состояние узлов. Выполните команду:
Затем важно проверить, работают ли системные поды (coredns, kube-controller-manager, kube-scheduler и тд.):
Если появилась ошибка CrashLoopBackOff (пример из жизни №2)
Возможно, после установки возникнет ошибка CrashLoopBackOff у coredns и nodelocaldns:
Проблема легко гуглится, и решение подробно описано тут. Отредактируем конфигурацию kubelet:
В файле необходимо подправить поле resolvConf
на:
И перезагрузить машину:
Выполните это на всех узлах, рабочих и управляющих. После этого поды перезапустятся и ошибка должна исчезнуть:
Готово, установка Kubernetes-кластера завершена. Теперь проверим на практике. насколько кластер высокодоступный, то бишь high-availability.
На любом из мастер-узлов развернём простое приложение из предыдущей статьи, которое по GET-ручке возвращает случайно сгенерированный UUID. Создадим три ресурса, первый — deployment:
Далее — Service с типом LoadBalancer:
И наконец, создадим манифест ingress с хостом uuid.awesomeservice.pro, который принимает и обрабатывает запросы:
Применим его:
Проверим созданный ingress:
У ресурса появились выделенные адреса - все 4 узла могут принимать трафик. Но по факту трафик будет поступать только на две машины 185.186.142.53 и 185.186.142.5. Всё потому что только их я завязал на домен uuid.awesomeservice.pro у своего DNS провайдера. Теперь проверим, что сервер отвечает:
В инструментах разработчика можно посмотреть, на какой узел поступил и обработан запрос:
Ответила node1 (185.186.142.5). Ради интереса выполнил запрос в другом браузере (Safari):
В Safari запрос был обработан узлом node2 (185.186.142.53). Таким образом заодно убедились, что DNS-балансировка работает. Теперь посмотрим, как будет отработана внештатная ситуация и для чего вообще нужен высокодоступный кластер. Допустим, что один из узлов выходит из строя и становится недоступным. Я руками выключаю удалённую машину и через второй узел смотрю состояние кластера:
Теперь проверим, как запрос обработается через браузер:
Как видите, раньше запрос в Chrome обрабатывался узлом node1 (185.186.142.5), а после того, как тот стал недоступен, запросы пошли на узел node2 (185.186.142.53). На самом деле мне повезло, видимо TTL истек и локальный кэш выполнил новый запрос к DNS серверу и получил второй адрес. Стоит чуть подольше потыкать запросы, и начинаются проблемы. В какой-то момент браузер всё таки получит адрес недоступного узла (185.186.142.5) и тогда запрос подвисает на 1 минуту, после чего браузер берет второй адрес из пула и наконец запрос успешно выполняется. На картинке видно, что обработка длилась чуть больше минуты.
Но последующие запросы будут сразу поступать на работающий узел:
В итоге мы имеем, что кластер не совсем ВЫСОКО доступный. Если в кластере из 4 узлов, один выходит из строя, то мы будем иметь 25% долгих запросов. Но при этом надо отметить, что они всё же будут успешными. Данное поведение я проверил в 2-х браузерах и ради интереса сделал запрос в Python скрипте - там, тоже самое, долгий запрос, но в итоге успешный.
На этом установка и тестирование кластера завершено. С учетом полученных знаний и подводных камней решайте, подходит ли вам данная сборка кластера.
Данный раздел был основан на этой статье и issue, а в последствии доработан.
Перед тем, как добавлять новый узел, удалённую машину нужно подготовить.
Теперь займемся конфигурациями: добавим в наш инвентарь новый хост. В файле hosts.yaml аналогично добавьте три строчки с новым адресом в раздел all.hosts
, и добавьте ссылку на этот хост в раздел с перечислением всех узлов в кластере — all.children.kube_node.hosts
. Я добавил пятый узел с адресом 46.8.19.76.
Полный файл hosts.yaml
Перед тем, как запускать плейбук присоединения нового рабочего узла к кластеру, нужно выполнить команду:
Это сбор информации о кластере, он не займёт много времени. Теперь можно пользоваться ключом --limit
, который нужен, чтобы во время добавления нового worker-узла не беспокоить уже существующие узлы. После успешного сбора информации запускаем основной плейбук присоединения worker-узла:
Пример из жизни №3
Возможно, будет ошибка при выполнении скрипта. В моём случае надо было посмотреть, что работает resolved service. Запустим резолвер:
Чтобы удалить рабочий узел, нужно всего лишь воспользоваться следующим плейбуком:
Удаление занимает немного времени, не больше 10 минут. По завершении работы плейбука будет примерно такой вывод:
Плейбук завершится успешно только в том случае, если worker-узел жив. Если он абсолютно нерабочий, то смотрите следующий раздел про замену master-узла, там будет подробно рассказано, как удалить из кластера нерабочий узел.
Удалить мастер-узел, который не отвечает и не на связи, с помощью kubespray не получится. Пробовал разные варианты: запускал плейбук remove-node.yaml, запускал facts.yml, чтобы запросить актуальные данные кластера, запускал upgrade-cluster.yaml — всегда зависает на опросе недоступной ноды. Придётся удалять вручную. Эту инструкцию я долго искал по разным issue github, в итоге после небольших экспериментов получилось успешно удалить как не отвечающий мастер-узел, так и рабочий.
Сейчас кластер состоит из четырёх узлов: двух мастеров и двух рабочих машин:
Допустим, первый мастер-узел выходит из строя и не отвечает. Для этого собственноручно выключаю удалённую машину, и примерно через минуту узел помечается как "Not Ready":
Воспроизвели нештатную ситуацию, теперь давайте заменять мастер-узел. Прежде всего объявим его нерабочим:
Ключ --ignore-daemonsets
обязателен для недоступных узлов. Возможно, консоль подзависнет на команде drain
, поэтому можно прервать ожидание (Ctl+C). После этих команд узел перейдёт в состояние NotReady, SchedulingDisabled.
Удалим узел:
Теперь нужно обновить cluset-info, а именно, указать в поле server
актуальный адрес мастер-узла:
Я заменил на 185.186.142.53 (node2), так как это единственный на данный момент живой мастер. Напомню, что на каждом мастер-узле ещё и развёрнут etcd-кластер, а это значит, что наша нештатная авария задела и etcd-узел вместе с node1 kubernetes. Поэтому нужно подчистить всю информацию, связанную с etcd-1. На каждом рабочем мастер-узле открываем конфигурацию:
И удалим нерабочий etcd-узел в строке --etcd-servers
. В нашем случае IP-адреса будут одинаковы:
Аналогично, на каждом мастер-узле нужно отредактировать etcd-конфигурацию:
И удалить неактивную etcd-ноду в поле ETCD_INITIAL_CLUSTER
:
После актуализации конфигураций последний штрих — это удаление ноды непосредственно из etcd-кластера. Воспользуемся консольной утилитой etcdctl для доступа к кластеру etcd. Эта утилита должна быть установлена на мастер-узлах. Прежде всего посмотрим существующие etcd-узлы:
Вывод будет примерно таким:
Обратите внимание, что ключи --cacert
, --cert
и --key
обязательны, и эти сертификаты уже сгенерированы kubespray и будут лежать в директории /etc/ssl/etcd/ssl/ на каждой машине, где развёрнут etcd-кластер. Теперь удалим из etcd-кластера неактивный узел:
Вывод будет такой:
Можете после этого проверить предыдущей командой, что etcd-узел удалён. И последнее: возвращаемся на локальную машину и удаляем неактивный host из файла hosts.yaml:
Достаточно будет удалить три строчки из children.kube_control_plane.hosts, children.kube_node.hosts и children.etcd.hosts (в all.hosts можно оставить, если потом захотите повторно добавить этот хост в кластер):
Полный hosts.yaml
Теперь нужно запустить Ansible-роль актуализации кластера (upgrade-cluster.yml). При вызове этой команды возникнет ошибка чётного количества etcd-серверов. Чтобы эту ошибку проигнорировать, нужно добавить специальный ключ ignore_assert_errors
. Команда для запуска роли:
После выполнения роли вывод консоли будет такой:
Теперь можно добавлять новый мастер-узел в кластер. Но перед эти подготовить удалённую машину. Я вместо подготовки на node1 (185.186.142.5) накатил снимок ОС с готовым сетапом для установки Kubernetes. Отредактируем hosts.yaml:
Добавим новый host (в моем случае это node1) в all.hosts в разделы children.kube_control_plane.hosts, children.kube_node.hosts и children.etcd.hosts*.* Надо отметить, что советуют всегда добавлять новый хост в конец списка.
Полный файл hosts.yaml
Запускаем плейбук cluster.yaml. В ключе --limit
указываем etcd и kube_control_plane, тогда при установке работоспособность worker-узлов затронута не будет.
Готово, мастер-узел успешно добавлен!
Решение возможных проблем
Если добавляется абсолютно новая виртуалка, то проблем возникнуть не должно. Но если добавляется машина с тем же IP, то даже если система только что переустановлена, возникнут, скорее всего, проблемы. У меня, например, эта ошибка повторяется постоянно:
Каждый раз проблема возникает при присоединении etcd-узла к etcd-кластеру. Посмотрим логи etcd на удалённой машине node1:
Будет следующая ошибка:
Прогуглив ошибку "member count is unequal", можно наткнуться на такое решение. В нём советуют указать машину с другим IP, отличным от прежнего. Это подтверждает тезис, что если добавлять новый хост с новым IP, то Ansible-роль отработает и мастер-узел успешно добавится к Kubernetes-кластеру. Допустим, что у нас нет другой машины. Методом научного тыка я нашёл другое решение: сначала добавим вручную etcd-узел к etcd-кластеру, а после перезапустим Ansible-роль cluster.yaml, и мастер-узел успешно добавится к Kubernetes-кластеру.
Дальнейшие шаги выполняем на мастер-узле (node1). Сначала заглянем в etcd-конфигурацию:
Новый etcd-узел подключается как etcd3, запомним это имя. Вот команда присоединения к etcd-кластеру:
Здесь в ключе --endpoints
указываются уже существующие узлы etcd-кластера, в --peer-urls
— адрес нового etcd-узла (обратите внимание на порты, в первом ключе 2379, во втором — 2380). Остальные ключи — это ссылка на сертификаты, которые уже сгенерированы kubespray, смотрите в соответствующую директорию etc/ssl/etcd/ssl/. Вывод консоли будет такой:
Etcdctl сообщит кластеру о новом участнике и распечатает переменные среды, необходимые для его успешного запуска. Так как служба уже запущена в фоне, то перезапускать ничего не придётся. Убедитесь, что сгенерированные в консоли переменные окружения такие же, как в /etc/etcd.env; если нет, то нужно подправить этот файл на всех удалённых машинах, где развернут etcd, и перезапустить службу.
Смотрим текущее состояние etcd-кластера:
Etcd-узел успешно добавлен в etcd-кластер. Можно ещё глянуть, как дела со здоровьем кластера:
Etcd-кластер чувствует себя отлично. Наконец, можно перезапустить Ansible-роль и добавить новый мастер-узел к Kubernetes-кластеру.
Вот список полезных команд, которые наверняка понадобятся.
Просмотр журнала логов etcd, kubelet:
Просмотр статуса и перезагрузка службы:
Возможно, понадобится сделать снимок с etcd- узла. Вот рабочий пример:
Мониторинг ресурсов виртуалки: