Русский
Русский
English
Статистика
Реклама

Fault-tolerant

Перевод Prometheus и VictoriaMetrics отказоустойчивая инфраструктура для хранения метрик

08.12.2020 14:22:23 | Автор: admin

В статье мой коллега Luca Carboni, DevOps Engineer из амстердамского офиса Miro, рассказывает, как выглядит наша инфраструктура для хранения метрик. Все компоненты в ней соответствуют принципам высокой доступности (High Availability) и отказоустойчивости (Fault Tolerance), имеют чёткую специализацию, могут хранить данные долгое время и оптимальны с точки зрения затрат.

Стек, о котором пойдёт речь: Prometheus, Alertmanager, Pushgateway, Blackbox exporter, Grafana и VictoriaMetrics.

Настройка High Availability и Fault Tolerance для Prometheus

Сервер Prometheus может использовать механизм federation, чтобы собирать метрики с других серверов Prometheus. Он хорошо работает, если вам нужно открыть часть метрик инструментам вроде Grafana или нужно собрать в одном месте метрики разного типа: например, бизнес-метрики и сервисные метрики с разных серверов.

Такой подход широко применяется, но не соответствует принципам высокой доступности и отказоустойчивости. Мы работаем лишь с частью метрик, а если один из серверов Prometheus перестанет отвечать, то данные за этот период собраны не будут.

Готового встроенного решения этой проблемы не существует, но для её решения не обязательно настраивать сложные кластеры и придумывать сложные стратегии взаимодействия серверов. Достаточно продублировать конфигурационный файл (prometheus.yml) на двух серверах, чтобы они собирали одни и те же метрики одинаковым способом. При этом сервер A будет дополнительно мониторить сервер B и наоборот.

Старый добрый принцип избыточности прост в реализации и надежён. Если мы добавим к нему инструмент IaC (инфраструктура как код) вроде Terraform и систему управления конфигурациями (CM) вроде Ansible, то этой избыточностью будет легко управлять и легко её поддерживать. При этом можно не дублировать большой и дорогой сервер, проще дублировать маленькие серверы и хранить на них только краткосрочные метрики. К тому же, небольшие серверы проще воссоздавать.Alertmanager, Pushgateway, Blackbox, экспортёры

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

Alertmanager может работать в кластерной конфигурации, умеет дедуплицировать данные с разных серверов Prometheus и может связываться с другими копиями Alertmanager, чтобы не отправлять несколько одинаковых оповещений. Поэтому можно установить по одной копии Alertmanager на оба сервера, которые мы продублировали: Prometheus A и Prometheus B. И не забываем про инструменты IaC и CM, чтобы управлять конфигурацией Alertmanager при помощи кода.

Экспортёры устанавливаются на конкретные системы-источники метрик, их дублировать не нужно. Единственное, что нужно сделать разрешить серверам Prometheus A и Prometheus B подключаться к ним.

С Pushgateway простым дублированием сервером не обойтись, потому что мы получим дуплицирование данных. В этом случае нам нужно иметь единую точку для приёма метрик. Для достижения высокой доступности и отказоустойчивости можно продублировать Pushgateway и настроить DNS Failover или балансировщик, чтобы при отказе одного сервера все запросы шли на другой (конфигурация active/passive). Таким образом у нас будет единая точка доступа для всех процессов, несмотря на наличие нескольких серверов.

Blackbox мы также можем продублировать для серверов Prometheus A и Prometheus B.

Итого, у нас есть два сервера Prometheus, две копии Alertmanager, связанные друг с другом, два Pushgateway в конфигурации active/passive и два Blackbox. Высокая доступность и отказоустойчивость достигнуты.

Нет особого смысла использовать только эти копии для сбора всех метрик сервиса. Сервис может быть расположен на нескольких VPC (Virtual Private Cloud), которые могут находиться в разных регионах, принадлежать разным аккаунтам и провайдерам. У вас даже могут быть собственные серверы. В этих случаях копии станут очень большими, а значит их станет сложнее чинить. Распространённая практика достижения высокой доступности и отказоустойчивости здесь иметь отдельный набор приложений для каждой части инфраструктуры. Принципы разделения инфраструктуры на части зависят от ваших потребностей, настроек сети и безопасности, доверия между командами и так далее.

В итоге мы имеем относительно небольшие копии Prometheus, продублированные вместе со всеми компонентами, упомянутыми выше. У нас есть код, который может их быстро воссоздать. И нам не страшен выход из строя одного компонента в каждой группе. Это определенно лучше плана "скрестить пальцы и надеяться, что ничего не упадёт".

VictoriaMetrics для долгосрочного хранения данных

Мы настроили Prometheus и его экосистему для достижения высокой доступности и отказоустойчивости. У нас есть несколько небольших групп Prometheus со связанными компонентами, каждая из которых решает задачи в своей части инфраструктуры. Это отлично работает для хранения данных в краткосрочном периоде. Для решения большинства задач нам достаточно хранения метрик в течение 10 дней. Что делать, если нужно хранить данные дольше? Например, когда требуется найти связь между разными периодами неделями или месяцами. Prometheus может работать с долгосрочными данными, но стоимость этого будет очень высокой из-за того, что инструменту требуется иметь к ним быстрый доступ.

Тут на помощь приходят Cortex, Thanos, M3DB, VictoriaMetrics и многие другие инструменты. Все они умеют собирать данные с нескольких серверов Prometheus, дедуплицировать их у нас точно будут дубликаты, так как каждый наш сервер существует в двух экземплярах, и предоставлять единое хранилище для собираемых метрик.

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

Настройка кластерной версии

VictoriaMetrics доступен в двух версиях: обычная всё-в-одном (single-node version) и кластерная (cluster version). В обычной версии все компоненты объединены в одно приложение, поэтому инструмент проще настраивать, но масштабировать можно только вертикально. Кластерная версия разбита на отдельные компоненты, каждый из которых можно масштабировать вертикально и горизонтально.

Обычная версия хорошее и стабильное решение. Но мы любим всё усложнять (хех), поэтому выбрали кластерную версию.

Кластерная версия VictoriaMetrics состоит из трёх основных компонентов: vmstorage (хранение данных), vminsert (запись данных в хранилище) и vmselect (выборка данных из хранилища). В таком виде инструмент получается очень гибким, vminsert и vmselect выступают как своего рода прокси.

У vminsert есть множество полезных настраиваемых параметров. Для целей этой статьи важно то, что его можно продублировать любое количество раз и поставить перед этими копиями балансировщик нагрузки, чтобы иметь единую точку приёма данных. У vminsert нет состояния (stateless), поэтому с ним легко работать, легко дублировать, его удобно использовать в неизменяемых инфраструктурах и при автоматическом масштабировании.

Самые важные параметры, которые нужно указать для vminsert это адреса хранилищ (storageNode) и количество хранилищ, на которые нужно реплицировать данные (replicationFactor=N, где N количество копий vmstorage). Но кто будет слать данные на балансировщик перед vminsert? Это будет делать Prometheus, если мы укажем адрес балансировщика в настройках remote_write.

vmstorage пожалуй, самый важный компонент VictoriaMetrics. В отличие от vminsert и vmselect, vmstorage имеет состояние (stateful), и каждая его копия ничего не знает о других копиях. Каждый запущенный vmstorage считает себя изолированным компонентом, он оптимизирован для облачных хранилищ с большим временем отклика (IO latency) и небольшим количеством операций в секунду (IOPS), что делает его существенно дешевле того способа хранения данных, который использует Prometheus.

Самые важные настройки vmstorage:

  • storageDataPath путь на диске, по которому будут храниться данные;

  • retentionPeriod срок хранения данных;

  • dedup.minScrapeInterval настройка дедупликации (считать дубликатами те записи, разница между временными метками которых меньше указанного значения).

У каждой копии vmstorage свои данные, но благодаря параметру replicationFactor, который мы указали для vminsert, одни и те же данные будут отсылаться в несколько (N) хранилищ.

vmstorage можно масштабировать вертикально, можно использовать более вместительные облачные хранилища, и даже для долговременного хранения метрик это будет недорого, так как vmstorage оптимизирован под этот тип хранилищ.

vmselect отвечает за выборку данных из хранилищ. Его легко дублировать, перед созданными копиями тоже можно поставить балансировщик нагрузки, чтобы иметь один адрес для приёма запросов. Через этот балансировщик можно получить доступ ко всем данным, которые были собраны с нескольких групп Prometheus, и эти данные будут доступны столько времени, сколько вам нужно. Основным потребителем этих данных, скорее всего, будет Grafana. Как и vminsert, vmselect можно использовать при автоматическом масштабировании.

Настройка высокой доступности и отказоустойчивости для Grafana

Grafana умеет работать как с метриками, которые собирает Prometheus, так и с метриками, которые хранятся в VictoriaMetrics. Это возможно благодаря тому, что VictoriaMetrics поддерживает кроме собственного языка запросов (MetricsQL) ещё и PromQL, используемый Prometheus. Попробуем достичь высокой доступности и отказоустойчивости для Grafana.

По умолчанию Grafana использует SQLite для хранения состояния. SQLite удобен для разработки, отлично подходит для мобильных приложений, но не очень хорош для отказоустойчивости и высокой доступности. Для этих целей лучше использовать обычную СУБД. Например, мы можем развернуть PostgreSQL на Amazon RDS, который использует технологию Multi-AZ для обеспечения доступности, и это решит нашу главную проблему.

Для создания единой точки доступа мы можем запустить какое угодно количество копий Grafana и настроить их на использование одного и того же облачного PostgreSQL. Количество копий зависит от ваших потребностей, вы можете масштабировать Grafana горизонтально и вертикально. PostgreSQL можно установить и на серверы с Grafana, но нам лень это делать и больше нравится пользоваться услугами облачных провайдеров, когда они отлично справляются с задачей и не используют vendor lock. Это отличный пример того, как можно сделать жизнь проще.

Теперь нам нужен балансировщик нагрузки, который будет распределять трафик между копиями Grafana. Этот балансировщик мы дополнительно можем привязать к красивому домену.

Дальше остаётся соединить Grafana с VictoriaMetrics а точнее, с балансировщиком перед vmselect, указав Prometheus в качестве источника данных. На этом нашу инфраструктуру для мониторинга можно считать завершённой.

***

Теперь все компоненты инфраструктуры соответствуют принципам высокой доступности и отказоустойчивости, имеют чёткую специализацию, могут хранить данные долгое время и оптимальны с точки зрения затрат. Если мы захотим хранить данные ещё дольше, мы можем по расписанию автоматически делать снимки vmstorage и отправлять их в хранилище, совместимое с Amazon S3.

Это всё, что касается метрик. Нам ещё нужна система работы с логами, но это уже совсем другая история.

Список инструментов:

Оригинал статьи в англоязычном блоге Miro.

Подробнее..

Безотказный Zabbix миграция с асинхронной на групповую репликацию

16.05.2021 22:09:51 | Автор: admin

Введение

Zabbix поддерживает несколько баз данных, но под рассмотрение попали только MySQL и PostgreSQL, как наиболее подходящие под мою установку. PostgreSQL с его repomgr и pgbouncer или каким-нибудь stolon с одной стороны и MySQL Group Replication с другой. Из-за использования MySQL в текущей конфигурации и тяге к стандартной комплектации, выбор пал на второй вариант.

Так что же такое MySQL Group Replication. Как видно из названия, это группа серверов, хранящая одинаковый набор данных. Максимальное количество узлов в группе ограничивается 9-ю. Может работать в режиме single-primary или multi-primary. Но самое интересное всё работает автоматически, будь то выборы нового ведущего сервера, определение поломанного узла, Split-brain или восстановление БД. Поставляется данный функционал в качестве плагинов group_replication и mysql_clone, связь происходит по Group Communication System протоколу в основе которого лежит алгоритм Паксос. Поддерживается данный тип репликации с версий 5.7.17 и 8.0.1.

Моя текущая установка работает на Zabbix 5.0 LTS и MySQL 5.7, миграцию будем проводить с повышением версии MySQL на 8.0, так интереснее ).

Мониторинг репликации

Да да. Это как TDD, только в администрировании, сначала нужно подготовить мониторинг, чтобы новый кластер сразу попал на радары нашей системы мониторинга и не одна проблема не ускользнула от её зоркого взгляда. Так как у вас еще нет групповой репликации (ГР), то вывод команд указанных ниже будет пустым, поэтому я привожу пример выводов с работающего кластера.

Основным источником информации о статусе узлов служит команда:

SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+| CHANNEL_NAME              | MEMBER_ID                           | MEMBER_HOST  | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+| group_replication_applier | 500049c2-99b7-11e9-8d36-e4434b5f9d0c | example1.com |      3306   | ONLINE       | SECONDARY   | 8.0.13         || group_replication_applier | 50024be2-9889-11eb-83da-e4434ba03de0 | example2.com |      3306   | ONLINE       | PRIMARY     | 8.0.13         || group_replication_applier | 500b2035-986e-11eb-a9f8-564d00018ad1 | example3.com |      3306   | ONLINE       | SECONDARY   | 8.0.13         |+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+

Значение колонки MEMBER_STATE может быть разное. Статусы можно посмотреть на странице официальной документации https://dev.mysql.com/doc/refman/8.0/en/group-replication-server-states.html. Если сервер к примеру корректно перезагружен или выключен, он исчезнет из этой таблицы, поэтому желательно знать общее количество узлов в вашей схеме и следить за их количеством.

Дополнительно нужно следить за производительностью каждого узла:

SELECT * FROM performance_schema.replication_group_member_stats\G
*************************** 1. row ***************************                              CHANNEL_NAME: group_replication_applier                                   VIEW_ID: 16178860996821458:41                                 MEMBER_ID: 500049c2-99b7-11e9-8d36-e4434b5f9d0c               COUNT_TRANSACTIONS_IN_QUEUE: 0                COUNT_TRANSACTIONS_CHECKED: 75715997                  COUNT_CONFLICTS_DETECTED: 0        COUNT_TRANSACTIONS_ROWS_VALIDATING: 1957048        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 500049c2-99b7-11e9-8d36-e4434b5f9d0c:1-1821470279,500293cf-594c-11ea-aafd-e4434ba03de0:1-622868371,5000d25c-059e-11e8-822b-564d00018ad1:1-140221041,c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:1-125382195            LAST_CONFLICT_FREE_TRANSACTION: c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:125471159COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0         COUNT_TRANSACTIONS_REMOTE_APPLIED: 5664         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 75710337         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0*************************** 2. row ***************************                              CHANNEL_NAME: group_replication_applier                                   VIEW_ID: 16178860996821458:41                                 MEMBER_ID: 50024be2-9889-11eb-83da-e4434ba03de0               COUNT_TRANSACTIONS_IN_QUEUE: 0                COUNT_TRANSACTIONS_CHECKED: 75720452                  COUNT_CONFLICTS_DETECTED: 0        COUNT_TRANSACTIONS_ROWS_VALIDATING: 1955202        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 500049c2-99b7-11e9-8d36-e4434b5f9d0c:1-1821470279,500293cf-594c-11ea-aafd-e4434ba03de0:1-622868371,5000d25c-059e-11e8-822b-564d00018ad1:1-140221041,c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:1-125377993            LAST_CONFLICT_FREE_TRANSACTION: c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:125470919COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 0         COUNT_TRANSACTIONS_REMOTE_APPLIED: 75711354         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 9105         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0*************************** 3. row ***************************                              CHANNEL_NAME: group_replication_applier                                   VIEW_ID: 16178860996821458:41                                 MEMBER_ID: 500b2035-986e-11eb-a9f8-564d00018ad1               COUNT_TRANSACTIONS_IN_QUEUE: 38727                COUNT_TRANSACTIONS_CHECKED: 49955241                  COUNT_CONFLICTS_DETECTED: 0        COUNT_TRANSACTIONS_ROWS_VALIDATING: 1250063        TRANSACTIONS_COMMITTED_ALL_MEMBERS: 500049c2-99b7-11e9-8d36-e4434b5f9d0c:1-1821470279,500293cf-594c-11ea-aafd-e4434ba03de0:1-622868371,5000d25c-059e-11e8-822b-564d00018ad1:1-140221041,c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:1-125382195            LAST_CONFLICT_FREE_TRANSACTION: c9aae4fb-97a6-11eb-89d1-e4434b5f9d0c:125430975COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 47096         COUNT_TRANSACTIONS_REMOTE_APPLIED: 49908155         COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0         COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 03 rows in set (0.00 sec)

Тут нас интересуют в первую очередь COUNT_TRANSACTIONS_IN_QUEUE, похож на Seconds_Behind_Master в асинхронной репликации. Как видно на третьем сервере количество транзакций в очереди слишком большое, а это повод начать разбираться что же здесь не так.

Резервное копирование

Я надеюсь, у вас делается регулярное резервное копирование базы данных (БД) и конечно на специально выделенный сервер. Так же желательно убедиться перед миграцией, что на всех узлах с БД есть запас по месту, превышающий в два с половиной раза размер каталога с базой. Это нам понадобится для создания локальной резервной копии, на случай если что-то пойдёт не так, и позволит нам вернуть всё как было. Если у вас файловая система с поддержкой моментальных снимков, не в чём себе не отказывайте, но свободного места должно быть много, так как в самые тяжёлые таблицы будет добавлен дополнительный столбец, а это повлечёт за собой их пересоздание.

План миграции

В идеальном варианте, нужно провести тестовую миграцию на каком-нибудь тестовом кластере. Но если всё делать правильно и не пренебрегать созданием резервных копий, то всегда можно будет откатиться назад. План действий на случай непредвиденных обстоятельств, тоже желательно написать.

Если всё идёт гладко:

  1. Пропиливаем необходимые дырки в файрволле (для общения узлов между собой нужно открыть TCP 33061 порт). Выписываем необходимые сертификаты;

  2. Собираем репозиторий с MySQL 8.0 (FreeBSD, Poudriere - у каждого свои причуды);

  3. В системе мониторинга переводим серверы в режим обслуживания, уведомляем всех пользователей Zabbix (чтобы никто не удивлялся);

  4. Выключаем репликацию на всех узлах, которые были Secondary для этого сервера (мы же не хотим, чтобы какие-нибудь наши изменения улетели на действующие сервера). Выключаем репликацию на текущем узле;

  5. Выключаем MySQL 5.7 сервер на первом подопытном узле;

  6. Делаем резервное копирование БД с сохранением атрибутов файлов (сохраняем рядом, чтобы быстро восстановить базу);

  7. Обновляем пакеты на новую версию с новыми зависимостями;

  8. Запускаем MySQL 8.0 сервер (mysql_upgrade не нужен, с 8 версии это действо происходит автоматически);

  9. Добавляем первичные ключи в таблицы, в которых их нет (требования групповой репликации, иначе операции добавления, удаления и т. д. работать не будут). Операция длительная, зависит от размера таблиц и производительности сервера;

  10. Включаем репликацию на этом сервере, отреплецируем все изменения, выключаем репликацию (подтянем изменённые данные с действующего сервера, накопившееся за время наших манипуляций);

  11. Сбрасываем все упоминания об асинхронной репликации на данном сервере (команда RESET SLAVE ALL;);

  12. Настраиваем групповую репликацию и проверяем всё ли работает;

  13. Переключаем Zabbix сервер и Zabbix фронтенд на БД с ГР;

  14. Настраиваем групповую репликацию на других узлах (делаем шаги с 4 по 8, только с удалением каталога с БД перед 8 шагом, т. к. нам нужна чистая установка);

  15. Перенастраиваем мониторинг;

  16. Переделываем Ansible Playbook'и и конфигурационные файлы;

  17. Меняем скрипты и задачи по переключению мастера;

  18. Настраиваем HADNS;

  19. Обновляем документацию;

На непредвиденный случай:

  1. Останавливаем MySQL сервер;

  2. Возвращаем предыдущие версии пакетов;

  3. Удаляем каталог с БД и восстанавливаем из локальной резервной копии, запускаем MySQL сервер;

  4. Настраиваем асинхронную репликацию;

Откатываемся бесконечное количество раз, пока всё не пройдёт гладко.

Далее подробно рассмотрим 9, 12 и 14 шаги.

Шаг 9: Добавление первичных ключей

Наличие первичных ключей является одним из основных моментов для правильного функционирования групповой репликации. Все требования можно посмотреть здесь.

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

SELECT tables.table_schema , tables.table_name , tables.engine FROM information_schema.tables LEFT JOIN ( SELECT table_schema , table_name FROM information_schema.statistics GROUP BY table_schema, table_name, index_name HAVING SUM( case when non_unique = 0 and nullable != 'YES' then 1 else 0 end ) = count(*) ) puksON tables.table_schema = puks.table_schema and tables.table_name = puks.table_nameWHERE puks.table_name is null AND tables.table_type = 'BASE TABLE' AND Engine="InnoDB";

Какой тип ключей и для каких колонок его создать я подсмотрел на форуме Zabbix, вот тут. Чтобы не поломалось обновление Zabbix, в таблице dbversion добавляем первичный ключ по существующему столбцу. Ниже необходимые запросы.

ALTER TABLE history ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;ALTER TABLE history_uint ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;ALTER TABLE history_text ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;ALTER TABLE history_str ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;ALTER TABLE history_log ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;ALTER TABLE dbversion ADD PRIMARY KEY (mandatory);

Напоминаю действие занимает большое количество времени на больших таблицах. Надеюсь, когда-нибудь это внесут в схему БД для Zabbix.

Шаг 12: Запуск групповой репликации

Все настройки, относящиеся к групповой репликации, я собрал в одном месте в конфигурационном файле.

server-id=[номер сервера в кластере по порядку]gtid_mode=ONenforce_gtid_consistency=ONlog_bin=binloglog_slave_updates=ONbinlog_format=ROWmaster_info_repository=TABLErelay_log_info_repository=TABLEtransaction_write_set_extraction=XXHASH64disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"plugin_load_add='group_replication.so;mysql_clone.so'ssl-ca=/usr/local/etc/ssl/mysql/ca.crtssl-cert=/usr/local/etc/ssl/mysql/server.crtssl-key=/usr/local/etc/ssl/mysql/server.keygroup_replication_ssl_mode=VERIFY_IDENTITYgroup_replication_group_name="[одинаковое на всех узлах, генерируем один раз командой SELECT UUID();]"group_replication_start_on_boot=off # включаем после добавления всех узлов в группуgroup_replication_local_address="[полное имя текущего сервера].com:33061"group_replication_group_seeds="example1.com:33061,example2.com:33061,example3.com:33061"group_replication_ip_allowlist="2.2.2.2/32,3.3.3.3/32,4.4.4.4/32"group_replication_member_weight=50group_replication_recovery_use_ssl=ONgroup_replication_recovery_ssl_verify_server_cert=ONgroup_replication_recovery_ssl_ca=/usr/local/etc/ssl/mysql/ca.crtgroup_replication_recovery_ssl_cert=/usr/local/etc/ssl/mysql/server.crtgroup_replication_recovery_ssl_key=/usr/local/etc/ssl/mysql/server.key

Добавляем это всё в my.cnf, дубликаты переменных, которые встречаются в файле до этого, необходимо удалить. Теперь можно перезапустить сервер, либо проверить значение переменных как написано ниже и поменять их вручную. Обратите внимание на переменную group_replication_start_on_boot, она выключена, поэтому при рестарте репликация не запустится.

Проверяем значение переменных командой SHOW VARIABLES LIKE 'binlog_format'; меняем с помощью команды SET GLOBAL binlog_format = RAW; это относится к переменным в верхней части конфига, остальные настройки подтянутся при активации групповой репликации.

Переменные group_replication_ssl_mode и group_replication_recovery_ssl_verify_server_cert установлены в максимально безопасный режим с проверкой сертификата сервера, так что при выписывании сертификата укажите в Subject Alternative Name (SAN) полные имена всех улов кластера, которые есть в group_replication_group_seeds.

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

Создаём пользователя для работы репликации:

SET SQL_LOG_BIN=0;CREATE USER 'replication'@'%' IDENTIFIED BY '[придумайте пароль]' REQUIRE SSL;GRANT replication slave ON *.* TO 'replication'@'%';GRANT BACKUP_ADMIN ON *.* TO 'replication'@'%';FLUSH PRIVILEGES;SET SQL_LOG_BIN=1;

Устанавливаем плагины и проверяем статус:

INSTALL PLUGIN group_replication SONAME 'group_replication.so';INSTALL PLUGIN clone SONAME 'mysql_clone.so';SHOW PLUGINS;

Настраиваем пользователя, используемого при восстановлении БД с работающего сервера:

CHANGE REPLICATION SOURCE TO SOURCE_USER='replication', SOURCE_PASSWORD='[придуманный пароль]' \\  FOR CHANNEL 'group_replication_recovery';

Первый запуск группы. Переменную group_replication_bootstrap_group включаем только на первом сервере, на остальных, просто запускаем групповую репликацию:

SET GLOBAL group_replication_bootstrap_group=ON; # выполняем только на первом сервереSTART GROUP_REPLICATION;SET GLOBAL group_replication_bootstrap_group=OFF; # выполняем только на первом сервере

Если никаких ошибок команда не вернула, то можно посмотреть информацию о вновь созданной группе:

mysql> SELECT * FROM performance_schema.replication_group_members;+---------------------------+--------------------------------------+-------------+-------------+---------------+| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE  |+---------------------------+--------------------------------------+-------------+-------------+---------------+| group_replication_applier | ce9be252-2b71-11e6-b8f4-00212844f856 |example1.com |       3306  | ONLINE        |+---------------------------+--------------------------------------+-------------+-------------+---------------+

Проверьте дополнительно логи MySQL сервера на содержание в них ошибок.

Шаг 14: Добавление узла в группу

После того как вы переключили Zabbix сервер и фронтенд на сервер с ГР, можно добавить оставшиеся узлы в кластер. Для этого выключаем MySQL сервер, делаем локальную копию, обновляем пакеты и удаляем текущий каталог с БД.

Запускаем чистую базу данных и проделываем всё тоже самое как в 12-ом шаге, с добавлением специфичных для этого сервера настроек (server-id, group_replication_local_address). Так как группа уже запущена, использовать переменную group_replication_bootstrap_group не нужно.

На этом этапе и будет использоваться так называемый Distributed Recovery механизм в который входит mysql_clone плагин. При подключении к узлу донору в первую очередь он попытается использовать бинарный лог, если информации в нём будет недостаточно, он полностью скопирует базу, включая созданного для репликации пользователя.

Проверьте статус группы и что сервер добавился в неё, а так же его производительность, успевает ли узел применять все транзакции.

После добавления оставшихся серверов, поменяйте в конфиге my.cnf значения переменной group_replication_start_on_boot с off на on и перезагрузите MySQL сервер на любом ведомом сервере и проверьте что он остался в группе.

Полезные команды

SELECT * FROM performance_schema.replication_group_members; - показывает статус всех узлов в группе.

SELECT * FROM performance_schema.replication_group_member_stats\G - показывает производительность каждого отдельного узла.

SELECT group_replication_set_as_primary('[uuid узла]'); - переключение ведущего узла.

Безотказный Zabbix сервер

А что же с Zabbix сервером спросите вы, если дочитаете до этого момента, а всё просто. Я сделал так чтобы он постоянно следовал за ведущим сервером групповой репликации. В кроне на каждом сервере запускается скрипт, который проверяет что узел сейчас Primary в ГП, если да, то запускает Zabbix сервер, если нет, то останавливает его. Дальше включается в работу HADNS, он проверяет на каком сервере запущен Zabbix и отдает нужный IP адрес для DNS записи.

Заключение

Возможно, сделано не всё так элегантно как хотелось бы. Вы наверно захотите использовать mysql-shell, mysqlrouter и преобразовать Group Replication в InnoDB Cluster, а может добавить HAProxy, особенно это полезно, когда разворачиваешь Zabbix с нуля. Надеюсь, этот рассказ послужит неплохой отправной точкой и будет полезен. Спасибо за внимание!

Дополнительная литература

https://dev.mysql.com/doc/refman/8.0/en/group-replication.html

https://blog.zabbix.com/scaling-zabbix-with-mysql-innodb-cluster/8472/

https://en.wikipedia.org/wiki/Paxos_(computer_science)

Подробнее..

Настройка отказоустойчивого кластера Kubernetes на серверах с публичной и приватной сетью с помощью Kubeadm

01.02.2021 14:09:27 | Автор: admin

Эта статья написана потому, что я бы хотел иметь такую статью перед глазами, когда развертывал кластер по документации. Сразу хочу сказать, что не являюсь экспертом в K8S, однако имел опыт с развертыванием продуктовых установок DC/OS (экосистемы, основанной на Apache Mesos). Долгое время K8S меня отпугивал тем, что, при попытке его изучения, тебя закидывают кучей концепций и терминов, отчего мозг взрывается.

Тем не менее, у меня возникла задача настроить отказоустойчивый Bare Metal кластер для комплексного приложения, в связи с чем и возникла данная статья. В процессе руководства я затрону следующие аспекты:

  • корректная установка с помощью kubeadm на узлах с несколькими NIC;

  • реализация отказоустойчивого Control Plane с доступом по общему IP и DNS-имени;

  • реализация Ingress контроллера на базе Nginx на выделенных узлах с доступом из публичной сети;

  • проброс K8S API в публичную сеть;

  • проброс K8S Dashboard UI в публичную сеть.

Я выполнял установку в среде Ubuntu 18.04, в связи с чем часть из шагов может не работать в вашем дистрибутиве. В тексте могут встречаться фразы "как я понял..." и "я не до сих пор не вполне понял...".

Сначала рассмотрим узловую топологию кластера, в котором мы будем развертывать K8S. Это упрощенная принципиальная топология, чтобы не загромождать публикацию лишними деталями.

Отличительной особенностью в моем кластере является то, что у всех узлов имеется два сетевых интерфейся - в моем случае, на eth0 всегда находится публичный адрес, а на eth1 - адрес из сети 10.120.0.0/16.

Стоит отметить, что K8S исключительно проще настраивать в случае, если ваши машины имеют по одному NIC. Если ваша инфраструктура позволяет вам использовать машины с одним сетевым устройством и иметь внешний балансировщик нагрузки - однозначно так необходимо делать.

В случае моей же инфраструктуры такая топология хотя и возможна, но я понял насколько K8S становится проще при использовании машин с одной NIC уже после того, как начал развертывание, поэтому решил "победить" проблемы, в рамках данной топологии, сохранив преимущества доступа к каждой машине напрямую.

Еще, kubeadm при развертывании считает, что что правильный IP-адрес - это тот, который находится в сети, где шлюз "по-умолчанию", если не переопределить, ничего хорошего не выйдет.

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

Хочу отметить, что я использую Ansible, но в качестве упрощения не буду в статье демонстрировать playbook-и, ориентируясь на настройку руками. Итак, приступим.

Замена DNS-рекурсора

Я хочу обеспечить доступность всех узлов кластера через DNS-имена по внутренним IP-адресам, при этом сохранив доступность разрешения обычных имен узлов в интернете. Для этого, на серверах gw-1, gw-2 я разверну pdns-recursor и укажу его в качестве рекурсора на всех узлах кластера.

Базовая настройка pdns-recursor на gw-1, gw-2 включает указание следующих директив:

allow-from=10.120.0.0/8, 127.0.0.0/8etc-hosts-file=/etc/hosts.resolvexport-etc-hosts=onexport-etc-hosts-search-suffix=cluster

Сам файл /etc/hosts.resolv генерируется с помощью ansible и выглядит следующим образом:

# Ansible managed10.120.29.231  gw-1 gw-110.120.28.23  gw-2 gw-210.120.29.32  video-accessors-1 video-accessors-110.120.29.226  video-accessors-2 video-accessors-210.120.29.153  mongo-1 mongo-110.120.29.210  mongo-2 mongo-210.120.29.220  mongo-3 mongo-310.120.28.172  compute-1 compute-110.120.28.26  compute-2 compute-210.120.29.70  compute-3 compute-310.120.28.127  zk-1 zk-110.120.29.110  zk-2 zk-210.120.29.245  zk-3 zk-310.120.28.21  minio-1 minio-110.120.28.25  minio-2 minio-210.120.28.158  minio-3 minio-310.120.28.122  minio-4 minio-410.120.29.187  k8s-1 k8s-110.120.28.37  k8s-2 k8s-210.120.29.204  k8s-3 k8s-310.120.29.135  kafka-1 kafka-110.120.29.144  kafka-2 kafka-210.120.28.130  kafka-3 kafka-310.120.29.194  clickhouse-1 clickhouse-110.120.28.66  clickhouse-2 clickhouse-210.120.28.61  clickhouse-3 clickhouse-310.120.29.244  app-1 app-110.120.29.228  app-2 app-210.120.29.33  prometeus prometeus10.120.29.222  manager manager10.120.29.187 k8s-cp
Шаблон Ansible для генерации конфига
# {{ ansible_managed }}{% for item in groups['all'] %}{% set short_name = item.split('.') %}{{ hostvars[item]['host'] }}  {{ item }} {{ short_name[0] }}{% endfor %}10.120.0.1 k8s-cp

Теперь, необходимо сделать так, чтобы все узлы вместо DNS-рекурсоров, получаемых из настроек DHCP, использовали данные DNS-ы. В Ubuntu 18.04 используется systemd-resolved, поэтому необходимо ему указать требуемые серверы gw-1, gw-2. Для этого определим манифест, переопределяющий поведение systemd-resolved с помощью файла /etc/systemd/network/0-eth0.network на каждом хосте кластера:

[Match]Name=eth0[Network]DHCP=ipv4DNS=10.120.28.23 10.120.29.231Domains=cluster[DHCP]UseDNS=falseUseDomains=false

Делает он следующее, для DHCP-записи, полученной через eth0 будут игнорироваться DNS-серверы и поисковые домены. Вместо этого будут использоваться серверы 10.120.28.23, 10.120.29.231 и использоваться поисковый домен *.cluster. После создания данного файла требуется перезагрузить узел или сеть узла, поскольку простой перезапуск systemd-resolved не инициирует повторное получение данных по DHCP. Я перезагружаю для того, чтобы убедиться в корректном поведении при старте узла.

При успешной инициализации systemd-resolve --status выдаст следующий листинг:

Global          DNSSEC NTA: 10.in-addr.arpa                      16.172.in-addr.arpa                      168.192.in-addr.arpa                      17.172.in-addr.arpa                      18.172.in-addr.arpa                      19.172.in-addr.arpa                      20.172.in-addr.arpa                      21.172.in-addr.arpa                      22.172.in-addr.arpa                      23.172.in-addr.arpa                      24.172.in-addr.arpa                      25.172.in-addr.arpa                      26.172.in-addr.arpa                      27.172.in-addr.arpa                      28.172.in-addr.arpa                      29.172.in-addr.arpa                      30.172.in-addr.arpa                      31.172.in-addr.arpa                      corp                      d.f.ip6.arpa                      home                      internal                      intranet                      lan                      local                      private                      testLink 3 (eth1)      Current Scopes: none       LLMNR setting: yesMulticastDNS setting: no      DNSSEC setting: no    DNSSEC supported: noLink 2 (eth0)      Current Scopes: DNS       LLMNR setting: yesMulticastDNS setting: no      DNSSEC setting: no    DNSSEC supported: no         DNS Servers: 10.120.28.23                      10.120.29.231          DNS Domain: cluster

Это действие необходимо выполнить на всех узлах кластера. При корректном выполнении каждый узел сможет выполнить ping gw-1.cluster, ping gw-2.cluster и получить ответ от данных узлов по внутренним ip-адресам.

Отключение раздела подкачки

Выполняется на всех узлах. Kubernetes не хочет работать при наличии разделов подкачки на узлах. Для их отключения вы можете воспользоваться следующим скриптом:

sudo -- sh -c "swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab"

Для пущей уверенности удалите swap-раздел с помощью fdisk.

Внесение изменений в сетевые настройки ядра

Выполняется на всех узлах. Я буду использовать Flannel - простейший оверлейный сетевой провайдер для K8S. Обратите внимание, что довольно много провайдеров используют VXLAN. Это накладывает определенные особенности для сетевой инфраструктуры.

В простейшем случае, вам требуется обеспечить работоспособность multicast, поскольку VXLAN использует multicast-группы, для своей работы. Если multicast - не ваш вариант, но вы хотите использовать провайдер, основанный на VXLAN, можно настроить работу VXLAN через BGP или другими способами. Однако, сможет ли жить с этим выбранный вами провайдер сетевой инфраструктуры Kubernetes - это большой вопрос. В общем, Flannel поддерживает VXLAN через multicast. В моем случае это VXLAN+multicast over VXLAN+multicast over Ethernet, поскольку в моей сети виртуальные машины имеют VXLAN-бэкбон, работающий поверх Ethernet с использованием multicast - так тоже работает.

В /etc/modules добавьте br_netfilter, overlay.

Выполните modprobe br_netfilter && modprobe overlay, чтобы загрузить модули.

В /etc/sysctl.conf добавьте:

net.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1

Выполните sysctl -p для применения изменений.

Установка containerd

Выполняется на всех узлах. Kubernetes рекомендует использовать containerd (впрочем, новые версии docker тоже используют containerd), поэтому установим его:

sudo apt-get updatesudo apt install containerdsudo sh -- -c "containerd config default | tee /etc/containerd/config.toml"sudo service containerd restart

Установим kubeadm, kubelet, kubectl

Выполняется на всех узлах. Здесь прям из руководства по установке K8S:

sudo apt-get update && sudo apt-get install -y apt-transport-https curlcurl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.listdeb https://apt.kubernetes.io/ kubernetes-xenial mainEOFsudo apt-get updatesudo apt-get install -y kubelet kubeadm kubectlsudo apt-mark hold kubelet kubeadm kubectl

Инициализация первого узла K8S

Выполняется на узлах, которые будут обслуживать Control Plane K8S - в моем случае k8s-{1,2,3}. Здесь уже есть нюансы, специфичные для моего развертывания:

kubeadm init --pod-network-cidr=10.244.0.0/16 \      --control-plane-endpoint=k8s-cp \      --apiserver-advertise-address=10.120.29.187

Для Flannel категорически важно использовать --pod-network-cidr=10.244.0.0/16. С другим адресным пространством для POD-ов K8S он не запустится.

--control-plane-endpoint string Specify a stable IP address or DNS name for the control plane.

Здесь Вы должны указать тот DNS или IP, который будет использоваться для связи всех шурушков K8S-а с Control Plane. Я решил использовать доменное имя k8s-cp, привязанное к отказоустойчивому ip-адресу 10.120.0.1 (см. далее, на текущий момент, k8s-cp указывает на один из серверов Control Plane: 10.120.29.187 k8s-cp).

Важный аргумент --api-server-advertise-address. Важно, что он влияет не только на api-server, но и на Etcd, что нигде не сказано, но очень важно для отказоустойчивой топологии. Если ничего не указать, то kubeadm возьмет адрес с той сети, в которой шлюз по-умолчанию, что не всегда верно. В моем случае это приводит к тому, что Etcd стартует на публичном интерфейсе, а кластер Etcd хочет работать по публичной сети, что меня не устраивает. Если этот адрес не указать правильно, то Flannel тоже не сможет корректно инициализироваться, будет падать с ошибками, что не может связаться с Control Plane (будет использовать тот же IP-адрес из сети со шлюзом по умолчанию для связи).

В общем, этот параметр привносит много геморроя и ведет к некорректной работе всего, что только может некорректно работать.

Теперь, если все хорошо, то Kubeadm развернет K8S на данном узле. Я рекомендую выполнить перезагрузку узла для того, чтобы убедиться что Control Plane стартует как надо. Убедитесь в этом, запросив список выполняемых задач:

ps xa  | grep -E '(kube-apiserver|etcd|kube-proxy|kube-controller-manager|kube-scheduler)'

Теперь можно скопmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/configировать настройки конфигурации для доступа администратора к кластеру в домашний каталог:

mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config

Для проверки запросите задачи, выполняемые на данном настроенном контроллере посредством команды kubectl get pods --all-namespaces. Вы должны получить вывод, примерно соответствующий следующему:

NAMESPACE              NAME                                         READY   STATUS      RESTARTS   AGEkube-system            etcd-k8s-1                                   1/1     Running     0          2d23hkube-system            kube-apiserver-k8s-1                         1/1     Running     0          2d23hkube-system            kube-controller-manager-k8s-1                1/1     Running     1          2d23hkube-system            kube-scheduler-k8s-1                         1/1     Running     1          2d23h

Я рекомендую посмотреть вывод этой команды еще пару раз, с перерывом 1 минуту, чтобы убедиться, что RESTARTS не растут, а статус Running.

Сам Kubernetes никакой сети не предоставляет, делегируя это плагинам CNI. Мы будем использовать простой CNI - Flannel. Его установка производится элементарно следующей командой:

kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml

Опять же, выполните kubectl get pods --all-namespaces несколько раз, чтобы убедиться, что Flannel выполняется без ошибок и RESTARTS не растут. Если что-то пошло не так, посмотрите журнал событий flannel следующим способом (только используйте настоящее имя POD-а Flannel:

kubectl logs -n kube-system kube-flannel-ds-xn2j9

Теперь вы можете подключить два других узла Control Plane с помощью команды, выполненной на каждом из них:

# ssh k8s-2sudo kubeadm join k8s-cp:6443 --apiserver-advertise-address=10.120.28.37 --token tfqsms.kiek2vk129tpf0b7 --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXXX6055b4bd --control-planemkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config# ssh k8s-3sudo kubeadm join k8s-cp:6443 --apiserver-advertise-address=10.120.29.204 --token tfqsms.kiek2vk129tpf0b7 --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXXXec6055b4bd --control-planemkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config

Если вдруг --token "протух", используйте команду kubeadm token create, чтобы сгенерировать новый.

После подключения узла смотрите внимательно за состоянием Etcd:

kubectl get pods --all-namespaces | grep etcdkube-system            etcd-k8s-1                                   1/1     Running     0          2d23hkube-system            etcd-k8s-2                                   1/1     Running     1          2d22hkube-system            etcd-k8s-3                                   1/1     Running     1          2d22h

Состояние должно быть Running, увеличение счетчика перезагрузок не должно происходить. Команда kubectl get pods --all-namespaces должна отображать трехкратный набор процессов на всех узлах Control Plane:

NAME                            READY   STATUS    RESTARTS   AGEcoredns-74ff55c5b-h2zjq         1/1     Running   0          2d23hcoredns-74ff55c5b-n6b49         1/1     Running   0          2d23hetcd-k8s-1                      1/1     Running   0          2d23hetcd-k8s-2                      1/1     Running   1          2d22hetcd-k8s-3                      1/1     Running   1          2d22hkube-apiserver-k8s-1            1/1     Running   0          2d23hkube-apiserver-k8s-2            1/1     Running   1          2d22hkube-apiserver-k8s-3            1/1     Running   1          2d22hkube-controller-manager-k8s-1   1/1     Running   1          2d23hkube-controller-manager-k8s-2   1/1     Running   1          2d22hkube-controller-manager-k8s-3   1/1     Running   1          2d22hkube-flannel-ds-2f6d5           1/1     Running   0          2d3hkube-flannel-ds-2p5vx           1/1     Running   0          2d3hkube-flannel-ds-4ng99           1/1     Running   3          2d22hkube-proxy-22jpt                1/1     Running   0          2d3hkube-proxy-25rxn                1/1     Running   0          2d23hkube-proxy-2qp8r                1/1     Running   0          2d3hkube-scheduler-k8s-1            1/1     Running   1          2d23hkube-scheduler-k8s-2            1/1     Running   1          2d22hkube-scheduler-k8s-3            1/1     Running   1          2d22h

Следующим шагом настроим отказоустойчивый IP-адрес, который будет использоваться для доступа к Control Plane. В моем случае было три варианта:

  1. Keepalived;

  2. Pacemaker;

  3. kube-vip.

Здесь я потратил достаточно много времени, чтобы все завести. Начал я с Keepalived, который прекрасно работает в других моих серверных системах. В общем, здесь он у меня не завелся, не знаю в чем проблема - в Ubuntu 18.04 или в VXLAN-сети, которую я использую в качестве Underlay. Tcpdump показывал веселые VRRP-пакетики, летящие между k8s-{1,2,3}, но IP-адрес вешался всеми 3мя узлами, все считали себя MASTER-ами. Поскольку debug ничего полезного не дал, я отказался от Keepalived и выполнил настройку на Pacemaker. Тут меня ждала очередная неприятность - corosync и pacemaker не взлетали самостоятельно при старте, несмотря на:

sudo systemctl enable corosyncsudo systemctl enable pacemaker

Я придерживаюсь такого мнения, что корневые компоненты или работают или не надо их использовать, в /etc/rc.local прописывать эти службы не хотелось. В итоге, я познакомился со специализированным решением по предоставлению отказоустойчивого IP-адреса kube-vip, разработанным специально для Kubernetes.

Нам понадобится создать три манифеста, каждый для запуска kube-vip на одном из узлов Control Plane:

apiVersion: v1kind: Podmetadata:  creationTimestamp: null  name: kube-vip-cp-k8s-1         # поменять имя, соответственно узлу  namespace: kube-systemspec:  nodeName: k8s-1                 # будет запускаться именно на этом узле  containers:  - args:    - start    env:    - name: vip_arp      value: "true"    - name: vip_interface      value: eth1    - name: vip_leaderelection      value: "true"    - name: vip_leaseduration      value: "5"    - name: vip_renewdeadline      value: "3"    - name: vip_retryperiod      value: "1"    - name: vip_address      value: 10.120.0.1          # указать реальный IP, который будет использоваться    image: plndr/kube-vip:0.3.1  # проверить актуальную версию    imagePullPolicy: Always    name: kube-vip-cp    resources: {}    securityContext:      capabilities:        add:        - NET_ADMIN        - SYS_TIME    volumeMounts:    - mountPath: /etc/kubernetes/admin.conf      name: kubeconfig    - mountPath: /etc/ssl/certs      name: ca-certs      readOnly: true  hostNetwork: true  volumes:  - hostPath:      path: /etc/kubernetes/admin.conf    name: kubeconfig  - hostPath:      path: /etc/ssl/certs    name: ca-certsstatus: {}

Данный манифест необходимо сформировать для каждого из серверов Control Plane и выполнить их:

kubectl apply -f cluster_config/vip-1.ymlkubectl apply -f cluster_config/vip-2.ymlkubectl apply -f cluster_config/vip-3.yml

В результате вы должны получить три нормально выполняющихся POD-а, каждый на своем узле, при этом адрес 10.120.0.1 должен нормально пинговаться. Проверьте, что только один из kube-vip владеет IP:

sudo arping 10.120.0.1ARPING 10.120.0.142 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=0 time=319.476 usec42 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=1 time=306.360 msec42 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=2 time=349.666 usec

Чем хорош kube-vip? Он не только предоставляет отказоустойчивый IP, но и определяет когда сервер на хосте, где он выполняется становится недоступен, переставая балансировать на него трафик.

Теперь, когда kube-vip предоставляет отказоустойчивый доступ к Kubernetes необходимо на хостах-рекурсорах gw-1, gw-2 в /etc/hosts.resolv обновить записи для k8s-cp:

10.120.0.1 k8s-cp

Выполните перезагрузку pdns-recursor командой sudo service pdns-recursor restart и проверьте, что k8s-cp отвечает со всех узлов IP адресом 10.120.0.1. Проверьте, что kubectl все еще корректно работает с узла k8s-1, он будет соединяться по k8s-cp, но использовать уже другой IP-адрес.

На данном этапе у нас есть реализация отказоустойчивого Control Plane K8S. Я рекомендую несколько раз поочередно перезагружать k8s-{1,2,3}, чтобы проверить, что кластер остается в работоспособном состоянии.

Добавление узла Worker-а

В нашей топологии предполагается использование двух узлов gw-1, gw-2, на которых будет размещен Nginx Ingress и один узел общего назначения (compute-1).

Все эти узлы в кластер можно добавить следующим образом:

kubeadm token create --print-join-command kubeadm join k8s-cp:6443 --token rn0s5p.y6waq1t6y2y6z9vw     --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXe22ec6055b4bd# ssh gw-1sudo kubeadm join k8s-cp:6443 --token rn0s5p.y6waq1t6y2y6z9vw     --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXe22ec6055b4bd# ssh gw-2...# ssh compute-1

После добавления kubectl get pds --all-namespaces должен показать расширенный набор выполняющихся POD-ов, а kubectl get nodes должен вывести все узлы кластера:

kubectl get nodesNAME                STATUS   ROLES                  AGE     VERSIONcompute-1           Ready    compute                2d23h   v1.20.2gw-1                Ready    gateway                2d4h    v1.20.2gw-2                Ready    gateway                2d4h    v1.20.2k8s-1               Ready    control-plane,master   2d23h   v1.20.2k8s-2               Ready    control-plane,master   2d23h   v1.20.2k8s-3               Ready    control-plane,master   2d23h   v1.20.2

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

Назначение ролей узлам

Роль можно присвоить просто:

kubectl label node gw-1 node-role.kubernetes.io/gateway=truekubectl label node gw-2 node-role.kubernetes.io/gateway=truekubectl label node compute-1 node-role.kubernetes.io/compute=true# если надо удалить рольkubectl label node compute-1 node-role.kubernetes.io/compute-

Настройка Ingress

Сейчас все готово для того, чтобы можно было выполнить развертывание Nginx Ingress на узлах gw-1, gw-2. Воспользуемся манифестом Nginx Ingress, но внесем в него ряд изменений:

  • запускать будем с сетью hostNetwork;

  • запускать будем в виде Deployment с фактором масштабирования "2";

  • запускать будем на узлах с ролью gateway.

Разберем подробнее про hostNetwork. Дело в том, что при использовании K8S в рамках какого-то облачного провайдера, последний через API назначает каждому узлу External IP, который может быть использован приложениями для связи с внешним миром. Так вот, у нас в bare metal кластере никаких External IP нет:

kubectl get nodes --output wideNAME                STATUS   ROLES                  AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIMEcompute-1           Ready    compute                3d      v1.20.2   10.120.28.172   <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3gw-1                Ready    gateway                2d4h    v1.20.2   10.120.29.231   <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3gw-2                Ready    gateway                2d4h    v1.20.2   10.120.28.23    <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3k8s-1               Ready    control-plane,master   3d      v1.20.2   10.120.29.187   <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3k8s-2               Ready    control-plane,master   2d23h   v1.20.2   10.120.28.37    <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3k8s-3               Ready    control-plane,master   2d23h   v1.20.2   10.120.29.204   <none>        Ubuntu 18.04.5 LTS   4.15.0-135-generic   containerd://1.3.3

Собственно, назначить этот External IP можно только через API, при этом он сбрасывается самим Kubernetes время от времени и требует постоянной установки. В общем, это неудобно и использоваться нормально не может. Я читал длинную переписку на GitHub, которая закончилась ничем вразумительным, еще советуют использовать metallb, который тоже непонятно в каком состоянии. В общем, я решил просто завести Nginx Ingress, используя hostNetworking, поскольку это обеспечивает привязку данного Ingress с адресам 0.0.0.0:443, 0.0.0.0:80 и решает мою задачу.

Собственно, манифест для запуска Nginx Ingress выглядит так:

Очень большой фрагмент YAML
apiVersion: v1kind: Namespacemetadata:  name: ingress-nginx  labels:    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx---# Source: ingress-nginx/templates/controller-serviceaccount.yamlapiVersion: v1kind: ServiceAccountmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx  namespace: ingress-nginx---# Source: ingress-nginx/templates/controller-configmap.yamlapiVersion: v1kind: ConfigMapmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx-controller  namespace: ingress-nginxdata:---# Source: ingress-nginx/templates/clusterrole.yamlapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm  name: ingress-nginxrules:  - apiGroups:      - ''    resources:      - configmaps      - endpoints      - nodes      - pods      - secrets    verbs:      - list      - watch  - apiGroups:      - ''    resources:      - nodes    verbs:      - get  - apiGroups:      - ''    resources:      - services    verbs:      - get      - list      - watch  - apiGroups:      - extensions      - networking.k8s.io   # k8s 1.14+    resources:      - ingresses    verbs:      - get      - list      - watch  - apiGroups:      - ''    resources:      - events    verbs:      - create      - patch  - apiGroups:      - extensions      - networking.k8s.io   # k8s 1.14+    resources:      - ingresses/status    verbs:      - update  - apiGroups:      - networking.k8s.io   # k8s 1.14+    resources:      - ingressclasses    verbs:      - get      - list      - watch---# Source: ingress-nginx/templates/clusterrolebinding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm  name: ingress-nginxroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: ingress-nginxsubjects:  - kind: ServiceAccount    name: ingress-nginx    namespace: ingress-nginx---# Source: ingress-nginx/templates/controller-role.yamlapiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx  namespace: ingress-nginxrules:  - apiGroups:      - ''    resources:      - namespaces    verbs:      - get  - apiGroups:      - ''    resources:      - configmaps      - pods      - secrets      - endpoints    verbs:      - get      - list      - watch  - apiGroups:      - ''    resources:      - services    verbs:      - get      - list      - watch  - apiGroups:      - extensions      - networking.k8s.io   # k8s 1.14+    resources:      - ingresses    verbs:      - get      - list      - watch  - apiGroups:      - extensions      - networking.k8s.io   # k8s 1.14+    resources:      - ingresses/status    verbs:      - update  - apiGroups:      - networking.k8s.io   # k8s 1.14+    resources:      - ingressclasses    verbs:      - get      - list      - watch  - apiGroups:      - ''    resources:      - configmaps    resourceNames:      - ingress-controller-leader-nginx    verbs:      - get      - update  - apiGroups:      - ''    resources:      - configmaps    verbs:      - create  - apiGroups:      - ''    resources:      - events    verbs:      - create      - patch---# Source: ingress-nginx/templates/controller-rolebinding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx  namespace: ingress-nginxroleRef:  apiGroup: rbac.authorization.k8s.io  kind: Role  name: ingress-nginxsubjects:  - kind: ServiceAccount    name: ingress-nginx    namespace: ingress-nginx---# Source: ingress-nginx/templates/controller-service-webhook.yamlapiVersion: v1kind: Servicemetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx-controller-admission  namespace: ingress-nginxspec:  type: ClusterIP  ports:    - name: https-webhook      port: 443      targetPort: webhook  selector:    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/component: controller---# Source: ingress-nginx/templates/controller-deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: controller  name: ingress-nginx-controller  namespace: ingress-nginxspec:  replicas: 2  selector:    matchLabels:      app.kubernetes.io/name: ingress-nginx      app.kubernetes.io/instance: ingress-nginx      app.kubernetes.io/component: controller  revisionHistoryLimit: 10  minReadySeconds: 0  template:    metadata:      labels:        app.kubernetes.io/name: ingress-nginx        app.kubernetes.io/instance: ingress-nginx        app.kubernetes.io/component: controller    spec:      hostNetwork: true      dnsPolicy: ClusterFirst      containers:        - name: controller          image: k8s.gcr.io/ingress-nginx/controller:v0.43.0@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713          imagePullPolicy: IfNotPresent          lifecycle:            preStop:              exec:                command:                  - /wait-shutdown          args:            - /nginx-ingress-controller            - --election-id=ingress-controller-leader            - --ingress-class=nginx            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller            - --validating-webhook=:8443            - --validating-webhook-certificate=/usr/local/certificates/cert            - --validating-webhook-key=/usr/local/certificates/key          securityContext:            capabilities:              drop:                - ALL              add:                - NET_BIND_SERVICE            runAsUser: 101            allowPrivilegeEscalation: true          env:            - name: POD_NAME              valueFrom:                fieldRef:                  fieldPath: metadata.name            - name: POD_NAMESPACE              valueFrom:                fieldRef:                  fieldPath: metadata.namespace            - name: LD_PRELOAD              value: /usr/local/lib/libmimalloc.so          livenessProbe:            httpGet:              path: /healthz              port: 10254              scheme: HTTP            initialDelaySeconds: 10            periodSeconds: 10            timeoutSeconds: 1            successThreshold: 1            failureThreshold: 5          readinessProbe:            httpGet:              path: /healthz              port: 10254              scheme: HTTP            initialDelaySeconds: 10            periodSeconds: 10            timeoutSeconds: 1            successThreshold: 1            failureThreshold: 3          ports:            - name: http              containerPort: 80              protocol: TCP            - name: https              containerPort: 443              protocol: TCP            - name: webhook              containerPort: 8443              protocol: TCP          volumeMounts:            - name: webhook-cert              mountPath: /usr/local/certificates/              readOnly: true          resources:            requests:              cpu: 100m              memory: 90Mi      nodeSelector:        node-role.kubernetes.io/gateway: "true"      serviceAccountName: ingress-nginx      terminationGracePeriodSeconds: 300      volumes:        - name: webhook-cert          secret:            secretName: ingress-nginx-admission---# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml# before changing this value, check the required kubernetes version# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisitesapiVersion: admissionregistration.k8s.io/v1kind: ValidatingWebhookConfigurationmetadata:  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  name: ingress-nginx-admissionwebhooks:  - name: validate.nginx.ingress.kubernetes.io    matchPolicy: Equivalent    rules:      - apiGroups:          - networking.k8s.io        apiVersions:          - v1beta1        operations:          - CREATE          - UPDATE        resources:          - ingresses    failurePolicy: Fail    sideEffects: None    admissionReviewVersions:      - v1      - v1beta1    clientConfig:      service:        namespace: ingress-nginx        name: ingress-nginx-controller-admission        path: /networking/v1beta1/ingresses---# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yamlapiVersion: v1kind: ServiceAccountmetadata:  name: ingress-nginx-admission  annotations:    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  namespace: ingress-nginx---# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yamlapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:  name: ingress-nginx-admission  annotations:    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhookrules:  - apiGroups:      - admissionregistration.k8s.io    resources:      - validatingwebhookconfigurations    verbs:      - get      - update---# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:  name: ingress-nginx-admission  annotations:    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhookroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: ingress-nginx-admissionsubjects:  - kind: ServiceAccount    name: ingress-nginx-admission    namespace: ingress-nginx---# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yamlapiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:  name: ingress-nginx-admission  annotations:    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  namespace: ingress-nginxrules:  - apiGroups:      - ''    resources:      - secrets    verbs:      - get      - create---# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:  name: ingress-nginx-admission  annotations:    helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  namespace: ingress-nginxroleRef:  apiGroup: rbac.authorization.k8s.io  kind: Role  name: ingress-nginx-admissionsubjects:  - kind: ServiceAccount    name: ingress-nginx-admission    namespace: ingress-nginx---# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yamlapiVersion: batch/v1kind: Jobmetadata:  name: ingress-nginx-admission-create  annotations:    helm.sh/hook: pre-install,pre-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  namespace: ingress-nginxspec:  template:    metadata:      name: ingress-nginx-admission-create      labels:        helm.sh/chart: ingress-nginx-3.21.0        app.kubernetes.io/name: ingress-nginx        app.kubernetes.io/instance: ingress-nginx        app.kubernetes.io/version: 0.43.0        app.kubernetes.io/managed-by: Helm        app.kubernetes.io/component: admission-webhook    spec:      containers:        - name: create          image: docker.io/jettech/kube-webhook-certgen:v1.5.1          imagePullPolicy: IfNotPresent          args:            - create            - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc            - --namespace=$(POD_NAMESPACE)            - --secret-name=ingress-nginx-admission          env:            - name: POD_NAMESPACE              valueFrom:                fieldRef:                  fieldPath: metadata.namespace      restartPolicy: OnFailure      serviceAccountName: ingress-nginx-admission      securityContext:        runAsNonRoot: true        runAsUser: 2000---# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yamlapiVersion: batch/v1kind: Jobmetadata:  name: ingress-nginx-admission-patch  annotations:    helm.sh/hook: post-install,post-upgrade    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded  labels:    helm.sh/chart: ingress-nginx-3.21.0    app.kubernetes.io/name: ingress-nginx    app.kubernetes.io/instance: ingress-nginx    app.kubernetes.io/version: 0.43.0    app.kubernetes.io/managed-by: Helm    app.kubernetes.io/component: admission-webhook  namespace: ingress-nginxspec:  template:    metadata:      name: ingress-nginx-admission-patch      labels:        helm.sh/chart: ingress-nginx-3.21.0        app.kubernetes.io/name: ingress-nginx        app.kubernetes.io/instance: ingress-nginx        app.kubernetes.io/version: 0.43.0        app.kubernetes.io/managed-by: Helm        app.kubernetes.io/component: admission-webhook    spec:      containers:        - name: patch          image: docker.io/jettech/kube-webhook-certgen:v1.5.1          imagePullPolicy: IfNotPresent          args:            - patch            - --webhook-name=ingress-nginx-admission            - --namespace=$(POD_NAMESPACE)            - --patch-mutating=false            - --secret-name=ingress-nginx-admission            - --patch-failure-policy=Fail          env:            - name: POD_NAMESPACE              valueFrom:                fieldRef:                  fieldPath: metadata.namespace      restartPolicy: OnFailure      serviceAccountName: ingress-nginx-admission      securityContext:        runAsNonRoot: true        runAsUser: 2000

Запустив его с помощью kubectl apply -f nginx-ingress.yaml, мы получим два POD-а Nginx, выполняющихся на узлах gw-1, gw-2 и слушающих 443-й и 80-й порты:

kubectl get pods --all-namespaces | grep nginxingress-nginx          ingress-nginx-admission-create-4mm9m         0/1     Completed   0          46hingress-nginx          ingress-nginx-admission-patch-7jkwg          0/1     Completed   2          46hingress-nginx          ingress-nginx-controller-b966cf6cd-7kpzm     1/1     Running     1          46hingress-nginx          ingress-nginx-controller-b966cf6cd-ckl97     1/1     Running     0          46h

На узлах gw-1, gw-2:

sudo netstat -tnlp | grep -E ':(443|80)'tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      2661/nginx: master  tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      2661/nginx: master  tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      2661/nginx: master  tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      2661/nginx: master  tcp6       0      0 :::443                  :::*                    LISTEN      2661/nginx: master  tcp6       0      0 :::443                  :::*                    LISTEN      2661/nginx: master  tcp6       0      0 :::80                   :::*                    LISTEN      2661/nginx: master  tcp6       0      0 :::80                   :::*                    LISTEN      2661/nginx: master  

Можно постучаться на публичные адреса gw-1, gw-2 по 80-му порту и получить приветственную страницу Nginx. Далее, Вы можете использовать публичные адреса gw-1, gw-2 для создания записей DNS, использования в CDN и т.п.

Протестировать работу inress можно, создав сервис, на который Ingress будет проксировать трафик (взято отсюда) - echo1.yaml:

apiVersion: v1kind: Servicemetadata:  name: echo1spec:  ports:  - port: 80    targetPort: 5678  selector:    app: echo1---apiVersion: apps/v1kind: Deploymentmetadata:  name: echo1spec:  selector:    matchLabels:      app: echo1  replicas: 2  template:    metadata:      labels:        app: echo1    spec:      containers:      - name: echo1        image: hashicorp/http-echo        args:        - "-text=echo1"        ports:        - containerPort: 5678

Выполните данный манифест с помощью kubectl apply -f echo1.yaml. Теперь создадим правило Ingress (ingress-echo1.yaml):

apiVersion: networking.k8s.io/v1beta1kind: Ingressmetadata:  name: echo-ingressspec:  rules:  - host: echo1.example.com    http:      paths:      - backend:          serviceName: echo1          servicePort: 80

Выполним данный манифест kubectl apply -f ingress-echo1.yaml. Теперь, если на локальном компьютере в /etchosts внести запись для echo1.example.com:

127.0.0.1localhost127.0.1.1manager# The following lines are desirable for IPv6 capable hosts::1     localhost ip6-localhost ip6-loopbackff02::1 ip6-allnodesff02::2 ip6-allroutersX.Y.Z.C echo1.example.com

То можно получить проксирование трафика через Nginx Ingress. Проверим с помощью curl:

curl echo1.example.comecho1

Установка K8S Dashboard

Для установки Dashboard необходимо выполнить следующую команду:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

Dashboard имеет ряд ограничений, например, он не работает через http, если обращение осуществляется не с localhost. В целом, не рекомендуется как-либо предоставлять доступ к Dashboard извне кластера через Ingress. Кроме того, Dashboard использует встроенную систему RBAC Kubernetes, поэтому требуется создать пользователя и дать ему права на Dashboard. Инструкция взята с этой страницы, здесь приводится для простоты восприятия.

Создадим аккаунт:

cat <<EOF | kubectl apply -f -apiVersion: v1kind: ServiceAccountmetadata:  name: admin-user  namespace: kubernetes-dashboardEOF

Определим роль пользователя:

cat <<EOF | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:  name: admin-userroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: cluster-adminsubjects:- kind: ServiceAccount  name: admin-user  namespace: kubernetes-dashboardEOF

Получим токен, с помощью которого пользователь сможет войти в Dashboard:

kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"

Если на вашей машине уже настроен kubect и есть $HOME/.kube/config, а сама машина "видит" k8s-cp, то вы можете запустить kubectl proxy и получить доступ к Dashboard по ссылке: http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/.

Если же ваша машина с браузером находится вне кластера, то переходим к следующему шагу, где мы настроим проброс API Kubernetes наружу через HAProxy.

Доступ к API K8S из внешней сети

На узлах gw-1, gw-2 необходимо установить haproxy. После установки измените конфигурационный файл /etc/haproxy/haproxy.cfg так, чтобы далее секции defaults он выглядел следующим образом:

defaults    # mode is inherited by sections that follow    mode tcpfrontend k8s    # receives traffic from clients    bind :6443    default_backend kubernetesbackend kubernetes    # relays the client messages to servers    server k8s k8s-cp:6443

Теперь вы можете обратиться к API K8S извне, указав в /etc/hosts своей локальной машины адрес gw-1 или gw-2 в качестве k8s-cp. Вам должны быть доступны с локальной машины все команды kubectl, включая kubectl proxy:

kubectl proxyStarting to serve on 127.0.0.1:8001

Можно открыть в браузере http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ и насладиться видом приглашения авторизации Dashboard K8S:

Вводим токен, полученный с помощью:

kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"eyJhbGciOiJSUzI1NiIsImtpZCI6IlFkcGxwMTN2YlpyNS1TOTYtUnVYdsfadfsdjfksdlfjm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWd6anprIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjM2RiOWFkMS0yYjdmLTQ3YTYtOTM3My1hZWI2ZjJkZjk0NTAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.ht-RyqLXY4UzBnxIzJcnQn8Kk2iHWZzKVeAjUhKkJ-vOAtyqBu50rExgiRWsEHWfJp1p9xN8sXFKg62FFFbHWftPf6PmwfcBK2lXPy6OL9OaooP17WJVn75KKXIZHPWLO9VmRXPB1S-v2xFuG_J8jHB8pZHJlFjp4zIXfB--QwhrxeoTt5zv3CfXCl1_VYgCoqaxRsa7e892-vtMijBo7EZfJiyuAUQr_TsAIDY3zOWFJeHbwPFWzL_1fF9Y2o3r0pw7hYZHUoI8Z-3hbfOi10QBRyQlNZTMFSh7Z38RRbV1tw2ZmMvgSQyHa9TZFy2kYik6VnugNB2cilamo_b7hg

и переходим на главный экран Dashboard:

Вместо заключения

Как я писал в начале статьи, у меня уже есть опыт развертывания оркестраций на базе Docker, Apache Mesos (DC/OS), тем не менее, документация Kubernetes мне показалась сложной, запутанной и фрагментарной. Чтобы получить текущий результат я прошерстил довольном много сторонних руководств, Issue GitHub и, конечно, документацию Kubernetes. Надеюсь, что данное руководство поможет вам сэкономить свое время.

Подробнее..

Почему хабражители предпочитают велосипеды, вместо готовых решений? Или о systemd, part 0

01.01.2021 18:11:05 | Автор: admin

С Новым Годом Хабр!
Первого января все нормальные люди занимаются прокрастинацией, уничтожением стратегического запаса оливье и прочими, не связанными с физической или интеллектуальной нагрузкой вещами. Вот и я планировал присоединиться к этой новогодней армии, но не тут-то было!

Во всём виноват Хабр!

. Точнее хабражители! А, если ещё точнее, всему виной предновогодние статьи, в которых два никак не связанных между собой юзера изобрели два почти одинаковых велосипеда. Итак, встречайте моих сегодняшних героев!

Как создавался новогодний Хабрачат в этом году

Скачивается бинарник под нужную платформу изрелизов на github. Можно положить его, например, в/usr/bin. Далее пишем простой скрипт, который будет перезапускать сервер, в случае падения.

(пропущен башизм с бесконечным циклом и прочими sleep-ами)

оригинал

Безумный дом

В процессе эксплуатации я заметил, что Domoticz иногда падает с ошибкой. Чтобы поднимать его автоматически, напишу watchdog с помощью cron.

(пропущены башизм, на пару с кронтабом)

Теперь каждые 5 минут будет запускаться скрипт, который проверит, работает ли Domoticz и перезапустит его, если это необходимо

оригинал

Что с этим делать и как дальше жить?

Я совершенно не планировал писать статью освещающую самые основы systemd, у меня в планах, цикл статей из разряда systemd для продолжающих, но жизнь, как видно, вносит свои коррективы, в результате пусть моя сегодняшняя, коротенькая статья будет своеобразным прологом к планируемуму циклу. Но так как про написание сервисных юнитов systemd написано 100500 хаутушек, то мы осветим только параметры относящиеся к автоматическому перезапуску сервисов, на конкретных примерах (и на затравку кое что ещё ;-), в применении к статьям двух уважаемых хабровчан.

Делаем всё по фен-шую

Ну я надеюсь вы тут все достаточно технически грамотные, поэтому я сразу приведу готовые, самодокументированные юниты и конфиги, а так-же команды запуска всего этого безобразия.

Как создавался Хабрачат в этом году

Юнит(/etc/systemd/system/ssh-chat.service):

[Unit]Description=SSH Chat ServiceAfter=network.target network-online.target[Service]# Пользователь и группа с правами которых будет запускаться сервисUser=ssh-chatGroup=ssh-chatType=SimpleExecStart=/usr/local/bin/ssh-chat --admin=/etc/ssh-chat/admins --bind=0.0.0.0:22 --log /var/log/ssh-chat.log --motd=/etc/ssh-chat/motd# В каких случаях сервис будет автоматически перезагружаться.# on-failure  в случае выхода с ненулевым кодом возврата.Restart=on-failure# Таймаут перед загрузкой сервиса, после падения.RestartSec=1# Capablities для сервиса. В данном случае - разрешение сервису# биндиться на привилегированные порты (< 1000)AmbientCapablities=CAP_NET_BIND_SERVICE[Install]WantedBy=multiuser.target

Конфиг для systemd-sysusers.service(/etc/sysusers.d/ssh-chat.conf):

u ssh-chat - "SSH Chat user" /etc/ssh-chat# Поля записи:# u : создаём пользователя# ssh-chat : username# - : или UID[:GID] в данном случае автоматически занять свободные UID/GID < 1000# "SSH Chat user" : Описание, или "-", если не нужно.# /etc/ssh-chat : Home Directory# Может быть ещё одно поле -- login shell. По умолчанию /usr/bin/nologin

Инсталляция и запуск:

sudo systemctl restart systemd-sysusers.service && sudo systemctl enable --now ssh-chat

Безумный дом

Юнит(/etc/systemd/system/domoticz.service):

[Unit]Description=Domoticz DaemonAfter=network.target[Service]User=httpGroup=http# Эта директива позволяет выполнять подготовительные действия перед# запуском сервиса. Модификатор "+" указывает выполнять их от рута.ExecStartPre=+/usr/bin/install -d -m 0700 -o http -g http /var/run/domoticzExecStart=/opt/domoticz/domoticz -www 8080 -pidfile /var/run/domoticz/domoticz.pidPIDFile=/var/run/domoticz/domoticz.pidWorkingDirectory=/opt/domoticz# Всё то же самое, что и в случае "хабрачата", только таймаут 5 секунд.RestartSec=5Restart=on-failure[Install]WantedBy=multi-user.target

Инсталляция и запуск:

sudo systemctl enable --now domoticz

Что дальше?

Возможности systemd, кратко освещённые в этой статье, а так-же многие другие, более подробно будут разобраны в следующих статьях цикла. Триггеры, поддержка бинарных форматов, прозрачные(transient) юниты, встроенная контейнеризация and more, more... Но нетерпеливые могут уже вот прям щаз заняться чтением одной из лучших документаций в мире линукс. Маны которые можно почитать по сегодняшней теме:

man systemd.unitman systemd.serviceman systemctlman sysusers.dman systemd-sysusers

И на закуску маленький секрет. Один из моих любимых манов: man systemd.directives - путеводитель по всем директивам конфигурации которые могут встретиться вам в процессе изучения systemd.

Ещё раз С новым Годом Хабр! И используйте правильные инструменты! ;-)

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru