# Book

<figure><img src="https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2a434339cdd61ef61e287c129ac93ea0670586cd%2Fimage.png?alt=media" alt="" width="188"><figcaption></figcaption></figure>

## Tech Recipe Book: Architectures, Security, and Beyond

In today's rapidly changing digital landscape, having a reliable source of guidance on developing and maintaining secure digital services is paramount. "Tech Recipe Book: Architectures, Security, and Beyond" is not just a traditional book; it's a curated compilation of articles, insights, and recipes from various authors, including my own, along with bookmarks of valuable resources and services. It's your comprehensive guide to mastering the multifaceted world of Information Technology.

This collection brings together the wisdom and expertise of different authors and aggregates a wealth of knowledge from various sources. Most of the content in this compilation is in the Russian language. Don't hesitate to utilize in-browser translation tools like those found in Google Chrome to fully access the rich content within these pages. Leveraging translation tools, we bridge language barriers and open the door to a world of IT insights and expertise.

Here's a glimpse of what awaits you within these pages:

* **Architect:** Explore the world of IT architectures through a variety of perspectives and approaches.
* **Pentest:** Gain insights into penetration testing techniques and methodologies from multiple experts.
* **Algorithms:** Dive into the intricacies of algorithmic solutions through a range of perspectives.
* **Zero Trust:** Discover cutting-edge security with a zero-trust model.
* **Compliance:** Navigate the complex landscape of IT compliance with a multitude of insights.

It's not just a compilation of knowledge; it's a hands-on guide that empowers you to tackle real-world challenges, using the expertise of a variety of authors.

So, as you delve into these articles, broaden your skill set, and together, let's contribute to a more stable and secure digital world through the power of professional IT services.

## Техническая Книга Рецептов: Архитектура, Безопасность и Не Только

В нынешнем быстро меняющемся цифровом мире важно иметь надежный источник рекомендаций по разработке и поддержке безопасных цифровых сервисов. "Техническая Книга Рецептов: Архитектура, Безопасность и Не Только" – это не просто обычная книга, а кураторская подборка статей, идей и рецептов различных авторов, включая мои собственные, а также ссылок с полезными ресурсами и услугами. Это ваше всестороннее руководство по овладению многогранным миром информационных технологий.

Эта коллекция объединяет мудрость и опыт различных авторов и собирает богатство знаний из разных источников. Большая часть материалов в этой книге на английском языке. Не стесняйтесь использовать встроенные средства перевода в браузере, подобные тем, которые предоставляются в Google Chrome, чтобы получить доступ к контенту на этих страницах. С помощью средств перевода мы расширяем горизонты и обеспечиваем доступ к миру информационных технологий и экспертизы, преодолевая языковые преграды.

Вот что вас ожидает на страницах этой книги:

* **Архитектура:** Исследуйте мир информационных технологий и архитектур с разнообразными точками зрения и методами.
* **Пентест:** Используйте ценные знания и навыки в области тестирования на проникновение от множества экспертов.
* **Алгоритмы:** Погрузитесь в тонкости алгоритмических решений с разнообразными точками зрения.
* **Безопасность "Zero Trust":** Откройте для себя передовые стратегии безопасности с моделью "Zero Trust".
* **Соблюдение стандартов:** Ориентируйтесь в сложном мире информационных технологий и соблюдения стандартов с множеством идей и практических рекомендаций.

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

Так что, углубитесь в статьи, расширьте свой кругозор и вместе давайте способствовать более стабильному и безопасному цифровому миру силой профессиональных информационных технологий.

### Content:

[Architect](/readme/architect)

[Big Data](/readme/big-data)

[Machine Learning](/readme/machine-learning)

[Malware](/readme/malware)

[Pentest](/readme/pentest)

[Compliance](/readme/compliance)

[Asset management](/readme/asset-management)

[Project management](/readme/project-management)

[Incident management SRE](/readme/incident-management-sre)

[Risk management](/readme/risk-management)

[Web Dev](/readme/web-dev)

[Art](/readme/art)

[Cryptocurrency](/readme/cryptocurrency)

[IT magazines](/readme/it-magazines)

[Languages](/readme/languages)

[Learning](/readme/learning)

[Relocation](/readme/relocation)

[Freenet](/readme/freenet)

[Services](/readme/services)


# About the author

\
**Greetings,**

I am **Konstantin Degtiarev**, the author of this book. I would like to clarify that while this book incorporates information from various sources, I do not claim sole ownership of its content, and proper attribution has been provided on the respective pages. I am deeply grateful to the individuals and communities that generously share architecture and cyber-security-related knowledge on the internet, as their contributions have enriched my understanding of architectural methodologies and hacking techniques, which I have incorporated into the **Tech Recipe Book**.

In addition to curating external insights, I contribute my own research findings to this publication. Thus, within the pages of the Tech Recipe Book, you will discover a wealth of IT-related information and something extra. If, by any chance, you come across any omissions or have suggestions for improvement, I welcome your feedback and encourage you to contact me.

**Здравствуйте**,

Я **Константин Дегтярев**, автор этой книги. Я хотел бы уточнить, что хотя эта книга содержит информацию из различных источников, я не утверждаю исключительное право на ее содержание. Я глубоко признателен людям и сообществам, которые щедро делятся знаниями в области архитектуры и кибербезопасности в интернете. Их вклад обогатил мое понимание архитектурных методологий и техник взлома, которые я интегрировал в книгу "**Tech Recipe Book**".

Помимо агрегирования внешних знаний, я вношу свои собственные исследования в эту публикацию. Таким образом, на страницах книги "Tech Recipe Book" вы найдете обширное собрание информации, связанной с информационными технологиями, и, возможно, что-то дополнительное. Если, случайно, вы обнаружите какие-либо пробелы или у вас есть предложения по улучшению, я приглашаю вас связаться со мной.

**BIO**

LinkedIn: <https://www.linkedin.com/in/konstantin-degtiarev-65b55bb0/>

**CONTACT**

Website: <https://konstantinsecurity.com/>

Email: <info@konstantinsecurity.com>

{% hint style="warning" %}
If you find that Tech Recipe Book is very useful for you, please consider **supporting it!**
{% endhint %}

{% hint style="warning" %}
Если вы считаете, что книга "Tech Recipe Book" полезна для вас, пожалуйста, поддержите её!
{% endhint %}


# Architect

[Algorithms](/readme/architect/algorithms)

[Architecture Frameworks](/readme/architect/architecture-frameworks)

[Zero Trust](/readme/architect/zero-trust)

[Billing](/readme/architect/billing)

[Bots](/readme/architect/bots)

[Business intelligence](/readme/architect/business-intelligence)

[Cloud Storage](/readme/architect/cloud-storage)

[Cryptography](/readme/architect/cryptography)

[Message broker](/readme/architect/message-broker)

[DB](/readme/architect/db)

[Identity and Access Management (IDM)](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Architect/Identity%20and%20Access%20Management%20\(IDM\).md)

[Firewall](/readme/architect/firewall)

[Infrastructure As a Code](/readme/architect/infrastructure-as-a-code)

[Kubernetes](/readme/architect/kubernetes)

[Load Balance](/readme/architect/load-balance)

[Monitoring](/readme/architect/monitoring)

[Windows](/readme/architect/windows)

[Linux](/readme/architect/linux)

[NGFW](/readme/architect/ngfw)

[CI/CD](/readme/architect/ci-cd)

[SIEM / SOC](/readme/architect/siem-soc)

[VPN](/readme/architect/vpn)

[OS hardening](/readme/architect/os-hardening)

[Cloud Providers](/readme/architect/cloud-providers)

[OpenNebula](/readme/architect/opennebula)

[OpenStack](/readme/architect/openstack)

[VM](/readme/architect/vm)

[Docker](/readme/architect/docker)

[LXC](/readme/architect/lxc)


# Algorithms

[DB index algorithms](/readme/architect/algorithms/db-index-algorithms)

[Neural network optimization](/readme/architect/algorithms/neural-network-optimization)

[Route search](/readme/architect/algorithms/route-search)


# DB index algorithms

[How does database indexing work](/readme/architect/algorithms/db-index-algorithms/how-does-database-indexing-work)


# How does database indexing work

<https://habr.com/ru/companies/ruvds/articles/724066/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b01726dc5a6648dc72b78c9a7408f6d8cfe88e72%2Fd0mk7rwr1ayin6ab0gregxx74r4.png?alt=media)

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

## Как базы данных создают индексы

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6414085c5f4ed0dcf5336dfa627653431de4770a%2Fwfdbf4jcvef9qfc4zer_aht4xyc.jpeg?alt=media)

Неиндексированная и индексированная базы данных

Индексирование базы данных обычно выполняется при помощи алгоритма, определяющего, как должен создаваться и храниться индекс. Конкретный процесс создания индекса может варьироваться в зависимости от типа используемой системы базы данных, однако в целом общие этапы выглядят так:

1. Определение столбца или столбцов в таблице базы данных, которые нужно индексировать. Обычно они определяются по тому, какие столбцы чаще всего используются в запросах или поисках.
2. Выбор алгоритма индексирования, подходящего для типа индексируемых данных. Например, индексы в виде B-деревьев обычно используются для индексирования строковых или числовых данных, а полнотекстовые индексы — для индексирования текстовых данных.
3. Применение алгоритма индексирования к выбранным столбцам, что создаёт структуру данных, сопоставляющую значения в столбцах с местоположениями соответствующих записей таблицы.
4. Сохранение индекса в отдельной структуре данных, обычно в другой части диска или в памяти, чтобы доступ к ней был более эффективным, чем к соответствующим табличным данным.
5. Обновление индекса в случае добавления новых записей, удаления или изменения записей в таблице.

Создание индекса может существенно улучшить производительность запросов к базе данных и операций поиска, поскольку оно позволяет системе базы данных находить соответствующие записи быстрее и эффективнее. Однако индексирование также может обладать и недостатками, например, увеличение требований к объёму хранилища и замедление выполнения операций вставки и обновления, поэтому перед созданием индекса следует взвесить плюсы и минусы.

## Алгоритмы индексирования

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

* **B-дерево**
* **Bitmap-индекс**
* **Хэш-индекс**
* **GiST** (Generalized Search Tree, обобщённое поисковое дерево)
* **Полнотекстовый индекс**

Каждый алгоритм индексирования имеет свои сильные и слабые стороны; давайте рассмотрим их по порядку.

## B-дерево

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-91d36926ced648d72d0aba840f223223f3105bd6%2Fwl6pdfvyrwe2sjxpbsht1s86ciu.jpeg?alt=media)

### ▍ Определение

B-дерево — это структура данных самобалансирующегося дерева, которая часто используется в качестве алгоритма индексирования в базах данных. Каждый узел дерева состоит из набора ключей и указателей на дочерние узлы; хранение данных осуществляется в иерархической структуре. Деревья B-узлов упорядочены таким образом, что позволяют быстро выполнять поиск, вставку и удаление данных.

Самое большое преимущество алгоритма B-дерева заключается в минимизации количества дисковых операций ввода-вывода, необходимых для доступа к данным, потому что в B-дереве все узлы-листья находятся на одном уровне, а каждый узел может хранить множество ключей и указателей. Количество ключей и указателей, которое может храниться в узле, определяется параметром, называемым «порядок» дерева.

### ▍ Как это работает

Алгоритм B-дерева работает следующим образом:

1. **Инициализация**: при создании B-дерева создаётся пустой корневой узел. Параметр, задающий максимальное количество ключей («порядок»), которые могут храниться в каждом узле, управляет B-порядком дерева.
2. **Вставка**: при добавлении нового узла в B-дерево алгоритм сначала подыскивает подходящий узел-лист, в который нужно вставить ключ. B-дерево разделяет заполненный узел-лист на два новых узла и перемещает медианный ключ в родительский узел. Пока не достигнут корневой узел, процесс разделения может распространяться по дереву. Благодаря этой процедуре дерево остаётся сбалансированным, а узлы-листья находятся на одинаковой высоте.
3. **Удаление**: когда ключ удаляется из B-дерева, алгоритм ищет узел, который изначально хранил ключ. Если узел-лист хранил ключ, то ключ извлекается и узел может нуждаться в перебалансировке. Алгоритм удаляет предшествующий или последующий лист после листа-узла, удалив ключ с ним, если ключ обнаружен не в узле-листе.
4. **Поиск**: в процессе поиска ключа в B-дереве алгоритм начинает с корневого узла и рекурсивно движется к веткам, пока не найдёт нужный узел-лист. Метод поиска сравнивает искомый ключ с ключами, содержащимися в каждом узле, а затем использует соответствующий указатель для перехода к дочернему узлу, в котором может находиться ключ. Этот процесс продолжается, пока не будет найден искомый ключ или пока не будет определено, что он отсутствует в дереве.

*Однако B-деревья обладают некоторыми недостатками:*

* **Излишняя трата ресурсов**: B-деревья задействуют большой объём излишнего пространства, поскольку каждый узел в B-дереве содержит указатель на родительский и дочерний узлы.
* **Сложность**: алгоритмы, используемые для вставки, удаления и поиска данных в B-дереве, сложнее по сравнению с другими структурами данных. Это усложняет реализацию и поддержку B-деревьев.
* **Медленные обновления**: обновление данных в B-дереве может быть относительно медленным по сравнению с другими структурами данных. Каждая операция обновления требует множества операций доступа к диску, и этот процесс может быть медленным для больших B-деревьев.

### ▍ Bitmap-индексирование

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/DB%20index%20algorithms/How%20does%20database%20indexing%20work/Untitled)

### ▍ Определение

Bitmap-индексирование — это методика индексирования данных, использующая битовые карты (bitmap) для обозначения наличия или отсутствия значения в таблице. Это успешная техника индексирования для таблиц с низкой кардинальностью, где количество уникальных значений в столбце довольно мало по сравнению с общим количеством строк.

Bitmap-индексирование может быть очень эффективным для столбцов с низкой кардинальностью, поскольку битовые карты крайне компактны и их можно быстро сканировать для извлечения данных. Bitmap-индексы очень удобны для применения в хранилищах данных, где необходимо быстро сканировать огромные объёмы данных. Кроме того, они полезны для баз данных, в которых много операций чтения, но мало обновлений или вставок.

### ▍ Как это работает

* Для создания bitmap-индекса столбца для каждого уникального значения столбца создаётся отдельный bitmap. Каждый bitmap имеет длину, равную количеству строк в таблице.
* Если значение присутствует в строке, соответствующему биту в bitmap присваивается значение 1, а если оно отсутствует, то присваивается значение 0. (Представьте таблицу, где столбец «Gender» имеет два уникальных значения, например, «Male» и «Female». Если этот столбец имеет bitmap-индекс, можно создать два bitmap, длина каждого из которых равна количеству строк в таблице. Когда в строке встречается «Male» или «Female», соответствующий бит в bitmap «Male» или «Female» получает значение 1, и наоборот. В случае отсутствия значения «Male» или «Female» соответствующему биту присваивается значение 0.)
* Чтобы выполнить запрос при помощи bitmap-индекса, соответствующие в запросе значения bitmap комбинируются при помощи побитовых операторов AND, OR и NOT. (например, если мы хотим найти все строки, где «Gender» равно «Male» И «Age» больше 30, нам сначала нужно получить bitmap «Male» и bitmap «Age > 30» из соответствующих индексов. Затем мы комбинируем эти два bitmap при помощи побитового оператора AND и получаем окончательный bitmap только с единицами в тех позициях, где оба условия истинны. Затем окончательный bitmap используется для получения из таблицы строк, удовлетворяющих запросу.)

*Bitmap-индексы имеют множество недостатков, и в том числе:*

* **Большой размер**: Bitmap-индексы могут быть большими, особенно при работе с крупными датасетами. Из-за этого они могут оказаться менее эффективными, чем другие методики индексирования.
* **Столбцы с высокой кардинальностью**: Bitmap-индексы неэффективны для столбцов с высокой кардинальностью, где количество уникальных значений очень высоко. В таких случаях bitmap-индексы могут становиться очень большими и не помещаться в памяти.
* **Смещённое распределение данных**: если данные смещены, у нескольких значений может быть гораздо более высокая частота, чем у других, и bitmap-индексы окажутся неэффективными. Это вызвано тем, что bitmap для наиболее частых значений становятся очень большими и могут доминировать в индексе.

## Хэш-индекс

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/DB%20index%20algorithms/How%20does%20database%20indexing%20work/Untitled)

### ▍ Определение

Хэш-индекс — это разновидность методики индексирования баз данных, использующая хэш-функцию для сопоставления ключей индекса с местоположениями соответствующих записей данных. Это быстрый метод индексирования для запросов точного соответствия в одном столбце.

Сопоставление ключей индекса с местоположениями соответствующих записей данных позволяет выполнять быстрый поиск и вставки за постоянное время O(1). Однако этот метод плохо работает с запросами диапазонов или частичными совпадениями и может страдать от коллизий, с которыми можно справляться при помощи различных техник разрешения коллизий.

### ▍ Как это работает

Чтобы объяснить, как работает хэш-индекс, давайте рассмотрим пример. Допустим, у нас есть таблица базы данных, содержащая информацию о сотрудниках, в том числе, номера их пользовательских ID. Мы хотим создать хэш-индекс столбца пользовательских ID, чтобы получить возможность быстрого поиска данных пользователей на основании номера их ID.

1. Мы создадим хэш-функцию, получающую на входе пользовательский ID и генерирующую на выходе уникальный хэш-код. Хэш-функция должна быть спроектирована таким образом, чтобы генерировать равномерно распределённое множество хэш-кодов для равномерного распределения записей по корзинам в файле индекса. На практике хэш-функция может использовать для генерации хэш-кода различные методики, например, модульную арифметику или побитовые операции.
2. Мы создаём файл хэш-индекса, содержащий набор корзин (bucket), каждая из которых соответствует уникальному хэш-коду сгенерированному хэш-функцией. Каждая корзина содержит указатель на файл базы данных, содержащий записи для этого хэш-кода.
3. При выполнении запроса к значению запроса применяется хэш-функция для генерации хэш-кода. Затем хэш-код используется для нахождения соответствующей корзины в файле хэш-индекса. Записи с одинаковым хэш-кодом хранятся в одной корзине, поэтому мы можем просто просканировать записи в этой корзине и найти совпадающую запись/записи. Если присутствуют коллизии (то есть несколько записей с одинаковым хэш-кодом), то для их разрешения можно использовать техники наподобие создания цепочек или открытой адресации.
4. Чтобы вставить новую запись в хэш-индекс, мы применяем к значению ключа записи хэш-функцию, чтобы сгенерировать его хэш-код, а затем вставляем запись в соответствующую корзину в файле хэш-индекса. Если коллизии отсутствуют, вставку можно выполнить за постоянное время O(1), так как нам нужно всего лишь вычислить хэш-код и вставить запись в корзину. Если коллизии есть, нам может потребоваться проделать дополнительные операции, например, вставку записи в связанный список в корзине или проверку других корзин, пока не будет найден свободный слот.

*Хэш-индексы также имеют множество недостатков, в том числе:*

* **Ограниченные возможности поиска**: хэш-индексы предназначены для обработки только поисков равенства (например, «найти все записи, где столбец A равен значению»). Они плохо подходят для запросов диапазонов или сортировки.
* **Коллизии**: хэш-индексы могут иметь коллизии, при которых несколько ключей соответствуют одному хэш-значению. Это может привести к снижению производительности, поскольку базе данных нужно будет выполнять дополнительные операции для разрешения коллизий.
* **Непредсказуемые требования к размеру хранилища**: размер хэш-индекса невозможно предугадать, так как он зависит от количества уникальных значений в индексируемом столбце. Это усложняет планирование требований к размеру хранилища.

### ▍ GiST

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/DB%20index%20algorithms/How%20does%20database%20indexing%20work/Untitled)

### ▍ Определение

GiST (Generalized Search Tree, обобщённое поисковое дерево) — это техника индексирования баз данных, которая может использоваться для индексирования сложных типов данных, например, геометрических объектов, текста или массивов. Это сбалансированная древовидная структура, состоящая из узлов с множественными дочерними узлами. Каждый узел описывает диапазон или множество значений и связан с предикативной функцией, проверяющей, принадлежит ли значение диапазону или множеству. Предикативная функция зависит от типа индексируемых данных и может быть подстроена под разные типы данных.

### ▍ Как это работает

Чтобы проиллюстрировать принцип работы индекса GiST, рассмотрим пример индексирования пространственных данных. Допустим, у нас есть таблица базы данных, содержащая информацию о городах, в том числе их названия и координаты в формате широты и долготы.

1. Зададим множество предикатов и функций преобразования, специфичных для индексируемого типа пространственных данных. В данном случае мы должны задать предикат, проверяющий, находится ли заданная точка в ограничивающем прямоугольнике, описанном узлом в индексе, и функцию преобразования, преобразующую точку в набор ключей на основании её позиции в ограничивающем прямоугольнике.
2. Создаём файл индекса GiST, состоящий из множества узлов, каждый из которых описывает ограничивающий прямоугольник, охватывающий диапазон координат. Корневой узел описывает весь диапазон координат в таблице базы данных, а каждый дочерний узел описывает подмножество этого диапазона. Каждый узел связывается с предикативной функцией и функцией преобразования, специфичными для индексируемого типа пространственных данных.
3. При выполнении запроса значение в запросе преобразуется при помощи функции преобразования в набор ключей. Затем ключи сравниваются с предикатами, связанными с каждым узлом индекса, начиная с корневого узла. Поиск продолжается вниз по дереву и выбирает дочерний узел, содержащий значение из запроса. Процесс повторяется, пока не будет достигнут узел-лист, содержащий элементы индекса, соответствующие значению в запросе.
4. Для вставки в индекс нового города координаты города сначала при помощи функции преобразования преобразуются в набор ключей. Затем ключи вставляются в соответствующие узлы индекса, начиная с корневого узла. Если узел заполнен, выполняется операция разделения для создания двух новых узлов и ключи распределяются между узлами.

*GiST имеет несколько недостатков, которые нужно учитывать:*

1. **Сниженная скорость вставок и обновлений**: структуры индексирования GiST могут быть сложнее, чем традиционные структуры индексирования, что может привести к снижению скорости операций вставки и обновления.
2. **Больше дискового пространства**: структуры индексирования GiST могут требовать больше дискового пространства, чем другие методики индексирования, поскольку хранят дополнительную информацию для поддержки различных типов поиска.
3. **Подходит не для всех типов данных**: GiST оптимизирован под индексирование сложных типов данных, например, пространственных данных, однако может быть не лучшим выбором для индексирования более простых типов данных, например, целочисленных значений или строк.
4. **Повышенные затраты на поддержку**: из-за сложности реализации индексы GiST требуют больше обслуживания по сравнению с традиционными индексами.

### ▍ Полнотекстовый индекс

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/DB%20index%20algorithms/How%20does%20database%20indexing%20work/Untitled)

### ▍ Определение

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

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

### ▍ Как это работает

Процесс полнотекстового индексирования состоит из нескольких этапов:

1. **Токенизация**: текстовое содержимое индексируемого столбца разбивается на отдельные слова или токены, которые затем сохраняются в индекс. При создании полнотекстового индекса система базы данных сначала анализирует текстовое содержимое индексируемых столбцов, а затем разбивает его на отдельные слова или токены. Этот процесс называется токенизацией, он может включать в себя фильтрацию игнорируемых слов (например, «the», «and», «or») и выделение корней (редуцирование слов до их базовой формы).
2. **Индексирование**: затем токены индексируются при помощи специальной структуры данных, например, B-дерева или инвертированного индекса. Структура индекса обеспечивает возможность эффективного поиска и извлечения строк, содержащих указанные токены.
3. **Построение и выполнение запросов**: система базы данных использует полнотекстовый индекс для поиска строк, содержащих релевантные токены. В процессе поиска токены запроса сопоставляются с индексированными токенами и извлекаются строки, соответствующие запросу. Результаты поиска можно ранжировать на основании их релевантности запросу, который вычисляется при помощи алгоритмов наподобие TF-IDF (term frequency-inverse document frequency).

*Полнотекстовое индексирование имеет некоторые недостатки:*

1. **Сниженная скорость индексирования и поиска**: полнотекстовое индексирование может быть более сложным, чем другие техники индексирования, что может приводить к снижению скорости индексирования и поиска, особенно в больших базах данных со множеством текстовых полей.
2. **Подходит не для всех типов данных**: полнотекстовое индексирование лучше всего подходит для баз данных, содержащих большие объёмы текстовых данных. Оно может и не быть наиболее эффективной техникой для баз данных, по большей мере, для содержащих числовую или другую нетекстовую информацию.
3. **Зависимость от языка**: полнотекстовое индексирование может быть не очень эффективно для многоязычных баз данных, поскольку требует отдельных индексов для каждого языка и может оказаться неспособным справиться с нюансами различных языков и систем письменности.

## Заключение

Индексирование баз данных — критически важная технология, повышающая эффективность запросов к базам данных. Оно заключается в создании специальных структур данных, обеспечивающих эффективный поиск и извлечение данных на основании одного или нескольких столбцов таблицы. Для оптимизации запросов под различные типы данных и сценарии использования применяются разные типы алгоритмов индексирования, например, B-деревья, bitmap-индексы, хэш-индексы и GiST-индексы.

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

1. “[Use The Index, Luke](https://use-the-index-luke.com/)!”, Markus Winand — это подробное руководство по индексированию баз данных SQL, в котором освещаются как основы, так и расширенные возможности.
2. “[Database Indexing Explained](https://www.digitalocean.com/community/tutorials/how-to-use-indexes-in-mysql)”, DigitalOcean — в этом туториале представлено понятное для новичков введение в концепции и методики индексирования с примерами на PostgreSQL.
3. “[Indexing Strategies for MySQL and MariaDB](https://severalnines.com/blog/guide-mysql-indexes/)”, Severalnines — в этом посте представлены практические советы по проектированию и оптимизации индексов в MySQL и MariaDB.


# Neural network optimization

[Neural Network Optimization](/readme/architect/algorithms/neural-network-optimization/neural-network-optimization)


# Neural Network Optimization

<https://habr.com/ru/companies/doubletapp/articles/722798/>

Всех приветствую, меня зовут Антон Рябых, работаю в [Doubletapp](https://doubletapp.ai/?utm_source=habr\&utm_medium=organic\&utm_campaign=722798). Вместе с коллегой [Данилом Гальпериным](https://habr.com/ru/users/mrrendal/) мы написали статью про важный этап в процессе обучения нейронных сетей и получения необходимых нам результатов — оптимизацию модели. Зачем нужно оптимизировать модель, если и так все работает? Но как только вы начнете разворачивать модель на устройстве, которое будет ее обрабатывать, перед вами встанет множество проблем.

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

Оптимизация моделей направлена на уменьшение размера моделей при минимизации потерь в точности и производительности.

## Сценарии использования

* Увеличение пропускной способности (снижение задержки), полезно как для облачных сервисов, так и для edge-девайсов в виде мобильных устройств, интернета вещей.
* Развертывание моделей на edge-устройствах с ограничениями по обработке, памяти и / или энергопотреблению.
* Уменьшение размера модели для ускоренного обновления модели и снижения затрат на хранение моделей.
* Оптимизация модели полезна для оборудования с ограничениями или оптимизацией для операций с фиксированной точкой.
* А также для аппаратных ускорителей специального назначения.

## Методы оптимизации

В статье обзорно рассмотрим следующие методы оптимизации:

* [Pruning](https://habr.com/ru/company/doubletapp/blog/722798/#1) — устранение части параметров нейронной сети.
* [Quantization](https://habr.com/ru/company/doubletapp/blog/722798/#2) — уменьшение точности обрабатываемых типов данных.
* [Knowledge distillation](https://habr.com/ru/company/doubletapp/blog/722798/#3) — обновление топологии исходной модели до более эффективной, с уменьшенным количеством параметров и более быстрым выполнением.
* [Weight clustering](https://habr.com/ru/company/doubletapp/blog/722798/#4) — сокращение количества уникальных параметров в весах модели.
* [OpenVino, TensorRT](https://habr.com/ru/company/doubletapp/blog/722798/#5) — фреймворки, с помощью которых можно оптимизировать модели.

## Pruning

Устранение части параметров нейронной сети — это метод сжатия, при котором происходит удаление весов из обученной модели. Удаление может производиться как на целых нейронах, так и на отдельных весах. Мы рассмотрим основные методы прунинга нейронной сети.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dce606844fe0533cac5091a5613fbd70354b91f4%2F011148ca026ad34005ddd1376c5f9329.png?alt=media)

Одним из способом является **прунинг весов**, когда некоторым **параметрам** устанавливается значение в ноль, тем самым создается разреженная сеть. Это уменьшает количество параметров модели, при этом сохраняется целостность архитектуры.Мы получаем сеть с меньшим количеством параметров, но для ее эффективности требуются разреженные вычисления, для которых **необходима поддержка оборудования**.

Вторым способом является **удаление из сети целых узлов** (нейронов). Такой способ уменьшает архитектуру сети и позволяет выполнять плотные, более оптимизированные вычисления. Можно работать без разреженных вычислений, и такие вычисления лучше поддерживаются на оборудовании. Однако такой прунинг способен навредить нейронной сети — удалить важные нейроны.

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

Интуитивно понятно, что можно обрезать те веса модели (свести их к нулю), которые и так имеют достаточно низкое значение по модулю. Для преднамеренного обучения модели заглушать незначимые веса используют регуляризацию L1 или L2.

Подобную логику имеет и удаление нейронов из сети. При запуске набора данных мы можем собрать некоторую статистику активаций. Те нейроны, которые не выдают высокие значения, — редко используются сетью и следовательно могут быть удалены. Помимо величины весов проверяется схожесть с другими выходами текущего слоя, если значения двух выходов статистически повторяются, то можно предположить, что они делают одно и то же. Следовательно, можно удалить один из них, и функциональность при этом не изменится.

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

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ffac4a64ca11cc9b7d66da75ea8008404f254e58%2Fb587b3dfcd5810c8488052a66ad3bb78.png?alt=media)

У нас имеется 3 слоя. 1-й слой имеет 6 узлов, 2-й слой — 4 узла и 3-й слой — 2 выходных узла. Для возможности вычисления активации скрытого слоя необходимо произвести 6 операций умножения-накопления (Multiply accumulate — MLA) для каждого узла. Всего необходимо произвести \[6\_4] + \[4\_2] = 24 + 8 = 32 MLA операции и держать в памяти 32 параметра.

Предположим, что по каким-то критериям решили удалить красный нейрон. Тогда количество операций изменится на \[6\_3] + \[3\_2] = 18 + 6 = 24 MLA операции и также нужно хранить в памяти 24 параметра. Удаление одного нейрона в такой простой сети способствовало снижению вычислительной мощности и объема потребляемой памяти на 25%.

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

Теперь вы спросите: «А зачем создавать избыточную архитектуру, которую впоследствии нужно как-то сокращать? Почему бы сразу не построить заведомо меньшую архитектуру без дальнейших оптимизаций?»

Но на самом деле очень сложно обучить меньшую модель с той же точностью, что и более крупную. У крупной модели имеется большее количество пространств поиска оптимального решения, и многое упирается в начальную инициализацию весов.

## Quantization

Квантование модели — это популярный метод оптимизации глубокого обучения, при котором данные модели — как параметры сети, так и активации — преобразуются из представления с плавающей запятой в представление с более низкой точностью, например, с использованием 8-битных целых чисел

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-15f07fb842e054fe5917c06b4025ccb534f1aa30%2Fa7048125e28bc93a853d4e0de3961258.png?alt=media)

Это дает несколько преимуществ:

* При обработке 8-битных целочисленных данных графические процессоры NVIDIA используют более быстрые и дешевые 8-битные тензорные ядра для вычисления операций свертки и умножения матриц. Это дает большую пропускную способность вычислений.
* Перемещение данных из памяти в вычислительные элементы (потоковые мультипроцессоры в графических процессорах NVIDIA) требует времени и энергии, а также выделяет тепло. Снижение точности данных активации и параметров с 32-битных чисел с плавающей запятой до 8-битных целых чисел приводит к 4-кратному сокращению данных, что экономит электроэнергию и снижает выделяемое тепло.
* Уменьшение объема памяти означает, что модель требует меньше места для хранения, меньше параметров для обновления, использование кэша выше и т.д.

## Методы квантования

Квантование имеет много преимуществ, но снижение точности параметров может легко навредить точности модели. 32-битный тип с плавающей точкой может представлять примерно 4 миллиарда чисел в интервале \[-3.4e38, 3.40e38]. Этот интервал представимых чисел также известен как динамический диапазон. Расстояние между двумя соседними представляемыми числами — это точность представления.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5f34c17005d2ba2775d560615c5e5136490fc658%2F060ab2d248b602583b7db9412ec1e9c4.png?alt=media)

В моделях глубокого обучения параметры и данные имеют высокую массу распределения в диапазоне \[-1, 1], вероятность, что значение входит в этот диапазон, достаточно высока.

Используя 8-битное целочисленное представление, вы можете представить только 256 различных значений. Эти 256 значений могут быть распределены равномерно или неравномерно, например, для более высокой точности около нуля.

Чтобы преобразовать представление тензора с плавающей запятой **x** в 8-битное представление **xq**, необходимо вычислить коэффициент масштабирования (s) и смещения (z). В итоге квантованное значение будет иметь следующий вид:

Теперь поймем, как получить эти коэффициенты. Для этого обозначим, какой диапазон значений имеет изначальный тип x и диапазон квантованных значений xq. Нам нужно решить систему линейных уравнений:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-014e74a5252c4b8b7f96653cfe22b06fcec4b238%2Fc57aab52205813c33f98613e52ca5b61.png?alt=media)

Где β — это верхняя граница диапазона х, βq — верхняя граница диапазона xq, α — нижняя граница диапазона x, αq — нижняя граница диапазона xq.

Отсюда,

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dde8fd1509656b6c6d8db657f92255d2ba40cf3b%2F98604db21cdd570defdb511586e02a0b.png?alt=media)

Посмотрим на примере: допустим входные значения лежат в диапазоне x \[-500, 2050] с типом данных fp32. Нам необходимо преобразовать в signed int8, который лежит в диапазоне xq \[-128, 127]. Отсюда s = (2050 + 500)/(127 + 128) = 10,

а z = (-500\_127-2050\_(-128))/(2050+500)=78.

Когда мы имеем коэффициенты преобразования, мы можем рассчитать любое входное значение:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-040a091667beed46d2b3f66cdb98bc41690b5d7f%2F83f435da8b43992fb6c06a7764256960.png?alt=media)

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

## Использование разных типов для квантования

Тип квантования в основном зависит от операции. Переход от float32 к int8 — не единственный вариант, есть и другие, например, от float32 к float16. Их также можно комбинировать. Например, вы можете квантовать умножения матриц до int8, а активации — до float16.

Квантование — это приближение. В целом, чем ближе приближение, тем меньшее снижение точности можно ожидать. Если вы все квантуете до float16, вы сократите память вдвое и, вероятно, не потеряете точность, но и не получите очень сильного ускорения. С другой стороны, квантование с помощью int8 может привести к гораздо более быстрой обработке, но точность результатов, вероятно, будет хуже. В крайних случаях это даже не сработает и может потребоваться обучение с учетом квантования.

## Квантование на практике

Чтобы устранить влияние квантования на качество модели, были разработаны различные методы квантования. Эти методы можно классифицировать как принадлежащие к одной из двух категорий: квантование после обучения (PTQ) или обучение с учетом квантования (QAT).

Как следует из названия, PTQ выполняется после обучения высокоточной модели. С помощью PTQ квантовать веса очень просто — у вас есть доступ к тензорам весов, и вы можете измерить их распределения.

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

Иногда PTQ не может достичь приемлемой точности на задаче. Тогда вы можете подумать об использовании QAT. Идея QAT проста: вы можете повысить точность квантованных моделей, если включите ошибку квантования в фазу обучения. Это позволяет сети адаптироваться к квантованным весам и активациям.

Существуют различные подходы выполнения QAT — от начала с необученной модели до начала с предварительно обученной модели. Все подходы изменяют режим обучения, чтобы включить ошибку квантования в потери при обучении, вставляя операции ложного квантования в обучающий граф для имитации квантования данных и параметров. Эти операции называются «фальшивыми», потому что они квантуют данные, но затем немедленно деквантовывают данные, чтобы вычисление операции оставалось с точностью до числа с плавающей запятой. Этот трюк добавляет квантование без особых изменений в структуре глубокого обучения.

PTQ — более популярный метод из двух, потому что он прост и является более быстрым методом. Однако QAT почти всегда дает лучшую точность, а иногда это единственный приемлемый метод.

## Knowledge distillation

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

Поэтому был разработан метод извлечения знаний из большой модели с большим количеством параметров в более легковесную модель. Такая модель учится повторять поведение крупной модели, ее выходные результаты на каждом слое. Обычно такую комбинацию называют «ученик — учитель».

Посмотрим на примере задачи классификации. При передаче знаний от учителя к ученику минимизируется функция потерь распределения классов, предсказанных моделью учителя. Обычно, в случае точных моделей, когда предсказание вероятности одного из классов (верного) близко к 1, а всех остальных — приближены к 0, такие данные мало помогут сети ученика, так как они практически не отличаются от исходной разметки. Поэтому был придуман softmax temperature, который помогает сети ученика повторять не разметку классификации, а вероятностное распределение, что позволяет модели ученика лучше перенять поведение учителя.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e26c5d176501425f9e9d44ef8e8987e0afd8c5ea%2F350d6c6b74d0ddc4013fbbe5afc85752.png?alt=media)

## Отличия от обучения с нуля

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

К сожалению, это само по себе не гарантирует сходимость сети ученика. Сеть-ученик может иметь сходимость, которая может сильно отличаться от сходимости сети учителя. Однако если сеть-ученик направлена ​​​​на то, чтобы воспроизвести поведение сети учителя (которая уже провела поиск в большем пространстве решений), ожидается, что ее пространство сходимости перекроется с исходным пространством сходимости учительской сети.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a3826596ae3fad0d8d2827416b16aee1630b932d%2F4a5280170982fb3514471200ed7497ee.png?alt=media)

## Сети учителей и учеников — как это реализовать?

1. *Обучите сеть-учитель*. Очень сложная сеть-учитель сначала обучается отдельно с использованием полного набора данных. Это может быть очень сложная и глубокая сеть, которую можно использовать в качестве сети учителя.
2. *Установите соответствие.* При проектировании сети-ученика необходимо установить соответствие между промежуточными выходами сети-ученика и учительской сети. Это соответствие может включать в себя непосредственную передачу результата слоя в сети учителя в сеть ученика или выполнение некоторого преобразования данных перед их передачей в сеть ученика.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7dceb2bebc9e998a98d3a4352ea1823368aa4e56%2F023cfee6caf43f2bbd904c89c9e25916.png?alt=media)

Пример установления соответствия

1. *Forward pass через сеть учителя.* Пропустите данные через сеть учителя, чтобы получить все промежуточные результаты.
2. *Back propogation через ученическую сеть.* Теперь используйте выходные данные из учительской сети и отношение соответствия для обратного распространения ошибки в ученической сети, чтобы она могла научиться воспроизводить поведение учительской сети.

## Weight clustering

Кластеризация весов — это метод уменьшения объема хранения вашей модели путем замены многих уникальных значений параметров меньшим количеством уникальных значений. Наряду с поддержкой платформы и аппаратного обеспечения, кластеризация весов может дополнительно уменьшить требуемые объем памяти и увеличить скорость обработки.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b78f20878673e0fe1a60ba80b8ca1716d2308019%2Fa3a14889aca6a0d31d621b1e825b17ff.png?alt=media)

Вот объяснение схемы. Представьте, например, что слой в вашей модели содержит матрицу весов 4×4. Каждый вес сохраняется с использованием значения float32. Когда вы сохраняете модель, вы сохраняете на диск 16 уникальных значений float32.

Кластеризация весов уменьшает размер вашей модели, заменяя аналогичные веса в слое с тем же значением. Эти значения находятся путем запуска алгоритма кластеризации по обученным весам модели. Пользователь может указать количество кластеров (в данном случае 4). Этот шаг показан в разделе «Get centroids» на диаграмме выше, а 4 значения центроидов показаны в таблице «Центроиды». Каждое значение центроида имеет индекс (0–3).

Затем каждый вес в весовой матрице заменяется индексом его центроида. Этот шаг показан в разделе «Assign indices». Теперь вместо сохранения исходной матрицы весов алгоритм кластеризации весов может сохранять модифицированную матрицу, показанную в «Pull indices» (содержащие индекс значений центроидов), и сами значения центроидов.

В этом случае мы уменьшили размер с 16 уникальных чисел с плавающей запятой до 4 чисел с плавающей запятой и 16 2-битных индексов. Экономия увеличивается с увеличением размера матрицы.

Обратите внимание, что даже если мы все еще сохранили 16 чисел с плавающей запятой, теперь у них есть только 4 различных значения. Общие инструменты сжатия (например, zip) теперь могут использовать преимущества избыточности данных для достижения более высокого сжатия.

## Преимущества кластеризации весов

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

## Результаты сжатия и точности

Эксперименты проводились на нескольких популярных моделях, демонстрирующих преимущества сжатия при кластеризации веса. Могут применяться более агрессивные оптимизации, но они уменьшат точность. Хотя в таблице ниже приведены измерения для моделей TensorFlow Lite, аналогичные преимущества наблюдаются и для других форматов сериализации.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-735e588f1be62e8092a4142c728af9749cca05fc%2F90e3318922d471c92dd1a9b88b0d7b82.png?alt=media)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9f7551d45dc95462ee8c88b18d7f8575c5541ac5%2F625e4a4144045eaf806252bc16aa80c2.png?alt=media)

## Фреймворки для оптимизации под конечное устройство

### OpenVINO

OpenVINO toolkit (или Intel Distribution of OpenVINO Toolkit) — это открытый бесплатный набор инструментов, который помогает ускорить разработку высокопроизводительных решений для использования в различных видеосистемах.

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

OpenVINO решает самые разнообразные задачи, включая детектирование лица, автоматическое распознавание объектов, текста и речи, обработку изображений и многое другое.

Производительность OpenVINO при вычислении сетей на платформах Intel в разы выше по сравнению с популярными фреймворками. Также значительно ниже требования по используемой памяти, что актуально для ряда приложений: на некоторых платформах невозможно запустить сеть с использованием фреймворков по причине нехватки памяти.

### Какие есть инструменты в OpenVINO

* ***Deep Learning Model Optimizer (Оптимизатор моделей глубокого обучения)*** — кроссплатформенный инструмент для импорта моделей и подготовки их к оптимизированному выполнению. Оптимизатор моделей конвертирует и оптимизирует модели популярных фреймворков (таких как Caffe, TensorFlow, MXNet, Kaldi и ONNX) во внутренний формат IR, который используется для представления модели внутри OpenVINO.

  Оптимизатор моделей глубокого обучения включает два компонента:

  * Model Optimizer — компонент для конвертации предварительно обученных моделей из формата какого-либо обучающего фреймворка в промежуточный формат (Intermediate Representation, IR) OpenVINO. Поддерживаемые форматы моделей: ONNX, TensorFlow, Caffe, MXNet, Kaldi
  * Inference Engine — компонент для эффективного инференса (запуска) моделей.
* ***Open Model Zoo —*** открытый репозиторий обученных моделей для решения различных задач. Содержит набор широко известных публичных моделей (более 20) и моделей, решающих различные задачи компьютерного зрения и обученных сотрудниками компании Intel (более 100). В составе можно обнаружить множество примеров и демоприложений, демонстрирующих использование доступных моделей.
* ***Предсобранный OpenCV —*** версия OpenCV, скомпилированная для оборудования Intel.
* ***Post-training Optimization tool —*** инструмент для калибровки модели и последующего ее инференса с точностью INT8.
* ***Deep Learning Workbench —*** веб-графическая среда, позволяющая легко использовать различные сложные компоненты набора инструментов OpenVINO toolkit.
* **Demo applications —** набор примеров.

### TensorRT

TensorRT — специальный фреймворк, который максимально утилизирует мощь видеокарты для нейронных сетей.

Приложения на основе TensorRT работают до 40 раз быстрее, чем платформы, использующие только CPU. С помощью TensorRT вы можете оптимизировать модели нейронных сетей, обученные во всех основных средах, провести квантование и развернуть решение в гипермасштабируемых центрах обработки данных, edge-девайсах или автомобильных платформах.

TensorRT построен на CUDA, модели параллельного программирования NVIDIA, и позволяет оптимизировать операции, используя библиотеки, инструменты разработки и технологии CUDA-X для искусственного интеллекта, автономных машин, высокопроизводительных вычислений и графики. С новыми графическим процессорами с архитектурой NVIDIA Ampere TensorRT также использует разреженные тензорные ядра, обеспечивая дополнительный прирост производительности.

TensorRT предоставляет INT8 вычисления с использованием Quantization Aware Training и Post Training Quantization, а также оптимизацию FP16 для производственных развертываний приложений для глубокого обучения, таких как потоковое видео, распознавание речи, рекомендации, обнаружение фрода, генерация текста и обработка естественного языка. Квантование значительно снижает время обработки, что является требованием для многих сервисов, работающих в реальном времени, а также для встроенных приложений.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e83ad3b0721c7896dfee933800585b23bea1faae%2Fd89eb6cc227da55b98abec581035e675.png?alt=media)

TensorRT интегрирован с PyTorch и TensorFlow, поэтому вы можете добиться ускорения работы сетей в кратчайшие сроки.

## Заключение

В заключение можно сказать, что область оптимизации нейронных сетей значительно продвинулась за последние годы благодаря развитию усовершенствованных методов, таких как прунинг, квантование, дистилляция знаний и кластеризация весов. Эти методы позволяют улучшить производительность и эффективность нейронных сетей, а также уменьшить их размер и вычислительные требования. Сочетая эти методы, мы можем создавать модели, которые одновременно точные и легкие, что делает их идеальными для развертывания на edge-устройствах и других ресурсно-ограниченных средах.

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

## Источники:

1. [Knowledge Distillation : Simplified](https://towardsdatascience.com/knowledge-distillation-simplified-dd4973dbc764)
2. [Quantization for Neural Networks](https://leimao.github.io/article/Neural-Networks-Quantization/)
3. [Achieving FP32 Accuracy for INT8 Inference Using Quantization Aware Training with NVIDIA TensorRT](https://developer.nvidia.com/blog/achieving-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/)
4. [Quantization in Deep Learning](https://medium.com/@joel_34050/quantization-in-deep-learning-478417eab72b)
5. [How to accelerate and compress neural networks with quantization](https://towardsdatascience.com/how-to-accelerate-and-compress-neural-networks-with-quantization-edfbbabb6af7)
6. [An Overview of Model Compression Techniques for Deep Learning in Space](https://medium.com/gsi-technology/an-overview-of-model-compression-techniques-for-deep-learning-in-space-3fd8d4ce84e5)
7. [Pruning Convolutional Neural Networks](https://towardsdatascience.com/pruning-convolutional-neural-networks-cae7986cbba8)
8. [Pruning Neural Networks](https://towardsdatascience.com/pruning-neural-networks-1bb3ab5791f9)
9. [Neural Architecture Search](https://lilianweng.github.io/lil-log/2020/08/06/neural-architecture-search.html)
10. [Что такое OpenVINO?](https://habr.com/ru/company/intel/blog/546438/)
11. [Как запихать нейронку в кофеварку](https://habr.com/ru/company/recognitor/blog/524980/)


# Route search

[Road network in a database to build a route](/readme/architect/algorithms/route-search/road-network-in-a-database-to-build-a-route)

[Traveling Salesman Problem (TSP)](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\).md)


# Road network in a database to build a route

<https://habr.com/ru/articles/688556/>

## Как хранить сеть дорог в БД для построения маршрута?

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2e6bd9a274a55b8ea2b8d3727034a42477d0c604%2F32c5b4c14170f37103d8da28249a7057.png?alt=media)

Построение маршрутов - задача распространенная, и, как для каждой распространённой задачи, для неё давно существуют реализации. Мне нравится [GraphHopper](https://www.graphhopper.com/). Да так нравится, что моя первая статья на Хабре была про то, [как создать для него собственные правила построения графа дорог](https://habr.com/ru/post/545782/).

Кроме того, для построения маршрута по дорогам из PostgreSQL, можете ознакомиться с [pgRouting](https://pgrouting.org/), на Хабре есть несколько статей по этой теме, вот хотя бы: “[делаем маршрутизацию (роутинг) на OpenStreetMap](https://habr.com/ru/post/511144/)”. Но это альтернативный способ реализации, я его затрагивать не буду.

Эта статья будет про то, как использовать свой источник данных, и как этот источник данных редактировать так, чтобы GraphHopper вас понял.

## Оглавление

* [GraphHopperPostgis](https://habr.com/ru/articles/688556/#GraphHopperPostgis)[Метод processJunctions - поиск перекрёстков](https://habr.com/ru/articles/688556/#ProcessJunctions)[Метод processRoads - создание рёбер графа дорог](https://habr.com/ru/articles/688556/#ProcessRoads)[Метод processRestrictions - запрет поворота](https://habr.com/ru/articles/688556/#ProcessRestrictions)[Результат](https://habr.com/ru/articles/688556/#Result)
* [Подготовка данных](https://habr.com/ru/articles/688556/#DataPreparation)[Индексирование и построение маршрута](https://habr.com/ru/articles/688556/#IndexingAndRoute)
* [Поиск пересечений](https://habr.com/ru/articles/688556/#FindIntersect)[Обработка пересечений](https://habr.com/ru/articles/688556/#HandlingIntersect)[Модификация линий](https://habr.com/ru/articles/688556/#ModificationLines)[Пересекающиеся линии](https://habr.com/ru/articles/688556/#IntersectingLines)[Соприкасающиеся линии](https://habr.com/ru/articles/688556/#TouchingLines)[Соединяющиеся линии](https://habr.com/ru/articles/688556/#ConnectingLines)[Многократное пересечение линий](https://habr.com/ru/articles/688556/#MultipleLineCrossings)[Накладывающиеся линии](https://habr.com/ru/articles/688556/#OverlappingLines)[Полное наложение линий](https://habr.com/ru/articles/688556/#FullLineOverlay)[Поглощение линий](https://habr.com/ru/articles/688556/#AbsorptionLines)[Наложение + пересечение линий](https://habr.com/ru/articles/688556/#OverlayIntersection)
* [Итог](https://habr.com/ru/articles/688556/#Itog)

## Как это работает?

GraphHopper использует данные OSM для построения графа дорог, однако есть возможность для расширения. Если нам понадобится альтернативный от OSM источник, необходимо предоставить свою реализацию класса [DataReader](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/reader/DataReader.java), который будет читать данные из нашего хранилища.

К счастью для нас, мы не первые кому такое решение понадобилось, Японская компания Georepublic, [уже всё сделала](https://georepublic.info/blog/2018/10/graphhopper-with-postgis-data-reader/). Репозиторий их проекта есть на GitHub: [mbasa/graphhopper-reader-postgis](https://github.com/mbasa/graphhopper-reader-postgis). Используем его. Я сделал форк репозитория [Tkachenko-Ivan/graphhopper-reader-postgis](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis), он немного отличается от первоисточника, все примеры кода ниже, взяты из форка.

В качестве хранилища данных используется PostgreSQL, с расширением PostGIS. Давайте разберём как это работает, и с чем это едят. Пример PostgreSQL поможет нам понять принцип создания произвольных источников данных.

Судя по всему, японцы основывали свои наработки на версии 2.x, а потом, с выходом новых версий GraphHopper, их актуализировали. Однако я решил вернуться к корням, все ссылки, которые я буду приводить на репозиторий GraphHopper, будут на [ветку 2.x](https://github.com/graphhopper/graphhopper/tree/2.x).

GraphHopper написан на Java, поэтому, если планируете делать какие-то доработки, или просто использовать его библиотеки, создайте проект Java. Прежде чем приступить к написанию классов для использования произвольных источников данных, необходимо [подключить к проекту](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/pom.xml#LL44C10-L44C10) основную библиотеку, классы которой мы и будем дополнять и расширять:

```
<dependency>
  <groupId>com.graphhopper</groupId>
  <artifactId>graphhopper-web</artifactId>
  <version>2.4</version>
</dependency>
```

### GraphHopperPostgis - фасад для работы с графом дорог

В качестве точки доступа к API маршрутизации, используется класс [GraphHopper](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/GraphHopper.java). Он является по сути [фасадом](https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D1%81%D0%B0%D0%B4_\(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F\)) для индексирования дорог и построения маршрутов. Как я уже говорил, в качестве источника используется OSM, и для работы с этим источником есть соответствующий класс [GraphHopperOSM](https://github.com/graphhopper/graphhopper/blob/2.x/reader-osm/src/main/java/com/graphhopper/reader/osm/GraphHopperOSM.java), - унаследованный от класса `GraphHopper`. На основе таких данных из OSM как, тип дороги, направление движения и максимальная скорость, он и строит граф дорог. Нас устраивает всё, кроме источника, поэтому мы можем не изобретать велосипед, а [унаследовать класс GraphHopperOSM](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/reader/postgis/GraphHopperPostgis.java#L12), и переопределить метод createReader:

```
public class GraphHopperPostgis extends GraphHopperOSM {

    private final Map<String, String> postgisParams = new HashMap<>();

    ...

    @Override
    protected DataReader createReader(GraphHopperStorage ghStorage) {
        OSMPostgisReader reader = new OSMPostgisReader(ghStorage, postgisParams);
        for (OSMPostgisReader.EdgeAddedListener l : edgeAddedListeners) {
            reader.addListener(l);
        }
        return initDataReader(reader);
    }

    ...

}
```

Здесь же мы должны дополнить метод `init`, создав `HashMap` для хранения параметров подключения к источнику данных, в нашем случае PostgreSQL.

```
public class GraphHopperPostgis extends GraphHopperOSM {

    ...

    private final Map<String, String> postgisParams = new HashMap<>();

    @Override
    public GraphHopper init(GraphHopperConfig ghConfig) {
        postgisParams.put("dbtype", "postgis");
        postgisParams.put("host", ghConfig.getString("db.host", host));
        postgisParams.put("port", ghConfig.getString("db.port", port));
        ...

        return super.init(ghConfig);
    }

    ...
}
```

Эти параметры были нами использованы при создании объекта класса OSMPostgisReader.

Приступим к разбору классов, которые непосредственно будут выполнять чтение данных из источника: [OSMPostgisReader](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/reader/postgis/OSMPostgisReader.java#L37), который наследует абстрактный [PostgisReader](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/reader/postgis/PostgisReader.java#L27), который, в свою очередь, реализует интерфейс [DataReader](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/reader/DataReader.java):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-decb8948fa00e43c0686bd20f3e3bcb05edb6da7%2F5e97d1734d64ffc07db71c7ae6d872c0.png?alt=media)

Диаграмма классов для чтения данных дорог (в пакетах показаны классы GraphHopper, вне пакетов - созданные "нами")

Нам нужно реализовать метод `readGraph` в `PostgisReader`:

```
public abstract class PostgisReader implements DataReader {

    ...

    @Override
    public void readGraph() {
        graphStorage.create(1000);
        processJunctions();
        processRoads();
        finishReading();
    }

    ...

}
```

Самое интересное начинается с метода `processJunctions()`. Вначале создаётся подключение к БД в методе [openPostGisStore](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/reader/postgis/PostgisReader.java#L112):

```
protected DataStore openPostGisStore() {

    ...

    DataStore ds = DataStoreFinder.getDataStore(this.postgisParams);

    ...

}
```

Затем получаем список дорог - [FeatureIterator](https://docs.geotools.org/stable/javadocs/org/geotools/feature/FeatureIterator.html), и обходим его:

```
void processJunctions() {
    DataStore dataStore = null;
    FeatureIterator<SimpleFeature> roads = null;

    try {
        dataStore = openPostGisStore();
        roads = getFeatureIterator(dataStore, roadsFile.getName());

        ...

        while (roads.hasNext()) {
            SimpleFeature road = roads.next();
            ...
        }
    }

    ...

}
```

Метод [getFeatureIterator](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/reader/postgis/PostgisReader.java#L69) реализован в классе `PostgisReader`.

Для соединения с БД и чтения геоданных, используется [GeoTools](https://geotools.org/), подключаем библиотеки:

```
<dependency>
  <groupId>org.geotools</groupId>
  <artifactId>gt-main</artifactId>
  <version>19.4</version>
</dependency>
<dependency>
  <groupId>org.geotools.jdbc</groupId>
  <artifactId>gt-jdbc-postgis</artifactId>
  <version>19.4</version>
</dependency>
```

Получаем объект соответствующий строке в БД:

```
SimpleFeature road = roads.next();
```

Нам необходимо получить координаты геометрии дороги (массив точек), для этого используем метод `getCoords(SimpleFeature feature)`:

```
public abstract class PostgisReader implements DataReader {

    ...

    protected List<Coordinate[]> getCoords(SimpleFeature feature) {
        ArrayList<Coordinate[]> ret = new ArrayList<>();

        ...

        Object coords = feature.getDefaultGeometry();

        ...

        if (coords instanceof LineString) {
            ret.add(((LineString) coords).getCoordinates());
        } else if (coords instanceof MultiLineString) {
            MultiLineString mls = (MultiLineString) coords;
            int n = mls.getNumGeometries();
            for (int i = 0; i < n; i++) {
                ret.add(mls.getGeometryN(i).getCoordinates());
            }
        }
        return ret;
    }

    ...

}
```

И в цикле обрабатываем все точки линии. Все обработанные точки хранятся в специальном массиве (для карты дорог России понадобится сохранить порядка 60 миллионов объектов):

```
public class OSMPostgisReader extends PostgisReader implements TurnCostParser.ExternalInternalMap {

    private GHObjectIntHashMap<Coordinate> coordState = new GHObjectIntHashMap<>(10_000_000, 0.7f);

    ...
}
```

Если точка попадает в него впервые, у неё статус `COORD_STATE_UNKNOWN`, а если она уже встречалась ранее, то у неё статус `COORD_STATE_PILLAR`, что означает, что это узловая точка, - она встречается в нескольких разных линия, а значит в этой точке можно повернуть на другую дорогу, ей присваивается некоторый порядковый номер, начиная с 1, по количеству найденных узлов (повторно узлы не обрабатываются, т.е. точка на перекрёстке семи дорог будет помечена как узловая один раз, и этого достаточно):

```
while (roads.hasNext()) {

    ...

    for (Coordinate[] points : getCoords(road)) {

        ...

        for (int i = 0; i < points.length; i++) {
            Coordinate c = points[i];

            ...

            int state = coordState.get(c);
            if (state >= FIRST_NODE_ID) {
                continue;
            }

            if (i == 0 || i == points.length - 1 || state == COORD_STATE_PILLAR) {
                int nodeId = nextNodeId++;
                coordState.put(c, nodeId);
                saveTowerPosition(nodeId, c);
            } else if (state == COORD_STATE_UNKNOWN) {
                coordState.put(c, COORD_STATE_PILLAR);
            }

            ...

        }
    }
}
```

Иными словами в этом методе - мы получаем список перекрёстков.

Запускается после `processJunctions`, и начинается аналогично: с подключения к БД, получения списка дорог и обхода их в цикле, разложения геометрии дороги на координаты, и перебор этих координат.

Его задача создать рёбра графа дорог и вычислить их длину. Рёбрами графа дорог будут не сами дороги, а те их участки, которые зажаты между найденными ранее точками перекрёстков.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3c6372417053e0a546d68734ed366d1b81be247f%2Fbe415c635eb413605ed6872539dd400d.png?alt=media)

Преобразование дорог в рёбра графа

Помимо перекрёстков существует множество других точек, из которых состоит линия, они позволяют вычислить её длину, поэтому все точки между перекрёстками, сохраняются в массиве `pillars`:

```
void processRoads() {

    ...

    while (roads.hasNext()) {

        ...

        for (Coordinate[] points : getCoords(road)) {

            ...

            List<Coordinate> pillars = new ArrayList<>();
            for (Coordinate point : points) {

                ...

                if (state >= FIRST_NODE_ID) {
                    // Получить расстояние и приблизитльный центр

                    ...

                    double distance = getWayLength(startTowerPnt, pillars, point);
                    addEdge(fromTowerNodeId, toTowerNodeId, road, distance, estmCentre, pillarNodes);

                    ...

                } else {
                    pillars.add(point);
                }

                ...

            }
        }
    }

    ...
}
```

А когда дойдём до перекрёстка (условие `if (state >= FIRST_NODE_ID)`), - [вычисляется длина ребра](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/util/DistanceCalc.java):

```
protected double getWayLength(Coordinate start, List<Coordinate> pillars, Coordinate end) {
    double distance = 0;

    Coordinate previous = start;
    for (Coordinate point : pillars) {
        distance += distCalc.calcDist(lat(previous), lng(previous), lat(point), lng(point));
        previous = point;
    }
    distance += distCalc.calcDist(lat(previous), lng(previous), lat(end), lng(end));

    ...

    return distance;
}
```

Для найденного ребра затем создаётся объект класса [ReaderWay](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/reader/ReaderWay.java), в который передаются параметры дороги:

```
private void addEdge(int fromTower, int toTower, SimpleFeature road, double distance,
                         GHPoint estmCentre, PointList pillarNodes) {
    ...

    ReaderWay way = new ReaderWay(id);
    way.setTag("estimated_distance", distance);
    way.setTag("estimated_center", estmCentre);

    ...

    // Тип дороги
    Object type = road.getAttribute("fclass");
    if (type != null) {
        way.setTag("highway", type.toString());
    }

    // Максимальная скорость
    Object maxSpeed = road.getAttribute("maxspeed");
    if (maxSpeed != null && !maxSpeed.toString().trim().equals("0")) {
        way.setTag("maxspeed", maxSpeed.toString());
    }

    ...

}
```

В конце настройки регистрируем созданную дорогу в [encodingManager](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java):

```
encodingManager.handleWayTags(way, acceptWay, tempRelFlags);
...
encodingManager.applyWayTags(way, edge);
```

Здесь и обрабатываются свойства дороги для определения скорости и направления в ребре графа.

На всю Россию получилось примерно 14.5 миллионов рёбер.

Он присутствует в реализации, и накладывает ограничение на проезд, например запрет поворота налево (см. [OSMTurnRelation](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/reader/OSMTurnRelation.java)), однако не используется из-за отсутствия в скачанных мною данных, полей:

* `restriction` - тип ограничения;
* `restriction_to` - по отношению к какой дороге это ограничение накладывается.

Если хотите чтобы заработал, надо искать другой источник данных или задавать самостоятельно, (я скачал shape c [geofabrik.de](https://www.geofabrik.de/), но об этом ниже). И не забыть также изменить SQL представление (о представлении тоже ниже).

### Результат

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

На построение графа дорог всей России потребовалось примерно 10 ГБ оперативной памяти, поэтому не забывайте про `-Xms`.

## Как это использовать?

В первую очередь читайте [README автора](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Road%20network%20in%20a%20database%20to%20build%20a%20route/README/README.md), но свои пять копеек так же вставлю.

### Подготовка данных

Для начала нам нужно где-то взять данные, удобно скачать [shape c geofabrik](http://download.geofabrik.de/russia.html), и загрузить файл `gis_osm_roads_free_1.shp` (название может немного отличаться) в свою БД, предварительно создав в ней таблицу для дорог (поля соответствуют тем, что представлены в shape файле):

```
CREATE TABLE public.gis_osm_roads (
    osm_id varchar(10) NULL,
    code int2 NULL,
    fclass varchar(28) NULL,
    "name" varchar(100) NULL,
    ref varchar(100) NULL,
    oneway varchar(1) NULL,
    maxspeed int2 NULL,
    layer float8 NULL,
    bridge varchar(1) NULL,
    tunnel varchar(1) NULL,
    geom geometry(MULTILINESTRING, 4326) NULL,
    CONSTRAINT gis_osm_roads_pkey PRIMARY KEY (osm_id)
);

CREATE INDEX gis_osm_roads_geom_idx ON public.gis_osm_roads USING gist (geom);
```

В `fclass` содержится значение тега [highway](https://wiki.openstreetmap.org/wiki/RU:Key:highway), можете выбрать только те типы дорог, которые вас интересуют, а лишние удалить чтобы не занимали место и не тратили время.

Скачанные геоданные имеют тип `MULTILINESTRING`, мне было удобнее работать с `LINESTRING`, поэтому я сначала гружу во временный слой `gis_osm_roads`, а потом осуществляю конвертацию с помощью [ST\_LineMerge](https://postgis.net/docs/ST_LineMerge.html) в другой слой - `gis_roads` (`gis_osm_roads` можно после этого удалять):

```
INSERT INTO gis_roads (fclass,"name",oneway,maxspeed,bridge,tunnel,geom)
(SELECT fclass,"name",oneway,maxspeed,bridge,tunnel,ST_LineMerge(geom) FROM gis_osm_roads)
```

Вы можете не конвертировать из `MULTILINESTRING` в `LINESTRING`, построитель графа и маршрутизатор умеют работать с обоими типами. Я произвожу конвертацию потому что, при [добавлении новых данных в слой](https://habr.com/ru/articles/688556/#NewData), мне удобнее работать с `LINESTRING .`

Таблица не обязательно должна однозначно соответствовать полям в скачанном файле, я добавил в неё несколько дополнительных полей, например дата загрузки `date_load`, и удалил то, что посчитал лишним, например `ref`. Ключ создал свой - `gid` (т.к. вставка данных предполагается не только за счёт импорта с OSM, но и из своих источников), `osm_id` по сути уже и не нужен. Вы можете аналогично создавать и удалять поля. Для построителя в первую очередь важны `fclass`, `geom`, `maxspeed`, `oneway`, на основе которых создадим представление:

```
CREATE OR REPLACE VIEW public.roads_view
AS SELECT gis_roads.gid AS osm_id,
    gis_roads.maxspeed,
    gis_roads.oneway,
    gis_roads.fclass,
    gis_roads.name,
    gis_roads.geom
   FROM gis_roads;
```

Поля `bridge` и `tunnel` не нужны для прокладки маршрута, т.к. для GraphHopper важно наличие общей точки, а не тип, однако эти поля важны для поиска пересечений при вставке новых линий, - логично, мост или эстакада не должны иметь точек пересечения с дорогой над которой они проложены.

Я загрузил дороги Калининградской области - она занимает меньше места среди других shape файлов. Если не хочется мучаться с загрузкой, то контейнер с загруженными данными можно взять с Docker Hub: [tkachenkoivan/road-data](https://hub.docker.com/r/tkachenkoivan/road-data).

Итак, данные получены. Переходим к построению графа.

### Индексирование и построение маршрута

Разобранный далее код, можно увидеть на GitHub: [пример построения маршрута](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis/blob/f3e3a69d180669583b490c2ca1154a159a685ef7/src/main/java/com/graphhopper/Worker.java#L14).

Создаём объект, для индексирования и построения маршрута:

```
GraphHopper hopper = new GraphHopperPostgis().forServer();
```

Выполняем настройку:

* параметры подключения к БД,
* указываем имя представления, в котором присутствуют данные - `roads_view`,
* директорию, где будут храниться построенные индексы,
* [encoder](https://github.com/graphhopper/graphhopper/tree/2.x/core/src/main/java/com/graphhopper/routing/util).

Поскольку мы решили строить автомобильные маршруты, то указываем имя для класса [CarFlagEncoder](https://github.com/graphhopper/graphhopper/blob/2.x/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java):

```
GraphHopperConfig graphHopperConfig = new GraphHopperConfig();
graphHopperConfig.putObject("db.host", host);
graphHopperConfig.putObject("db.port", port);
...
graphHopperConfig.putObject("datareader.file", "roads_view");
graphHopperConfig.putObject("graph.location", dir);
graphHopperConfig.putObject("graph.flag_encoders", "car");
```

Используем параметры для индексирования графа дорог, или загрузки из файлов, если он уже был ранее проиндексирован:

```
hopper.init(graphHopperConfig);
hopper.importOrLoad();
```

Вызывается метод `init`, класса `GraphHopperPostgis`.

Теперь можем построить маршрут, указывая в запросе точки, которые хотим посетить:

```
GHRequest request = new GHRequest();
for (int i = 0; i < points.size(); i++) {
    request.addPoint(new GHPoint(points.get(i)[0], points.get(i)[1]));
}
request.setProfile("my_car");

GHResponse response = hopper.route(request);
```

Маршрут построен!

## Как обрабатывать появление новых данных?

Чтение из БД для маршрутизации мы подсмотрели у Georepublic, спасибо им, однако там ничего нет о появлении новых данных. В целом это не проблема, если в качестве источника используется OSM, можно:

1. раз в какой-то период, например месяц, брать новую выгрузку OSM;
2. удалять из своей БД все данные;
3. заменять их новой выгрузкой;
4. перестраивать граф дорог.

Давайте подумаем что делать, если нам такой сценарий не подходит, например по той причине, что у нас свой анонимный источник данных о дорогах.

Саму вставку данных разбирать не будем, оставим без ответа вопрос о том, как эти данные в таблицу попали: может их загрузили из shape-файла, может этот слой можно редактировать через GeoServer, может геометрии были созданы через SQL выражения… здесь пусть каждый извращается как умеет.

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

Вначале подключите net.postgis:

```
<dependency>
  <groupId>net.postgis</groupId>
  <artifactId>postgis-jdbc</artifactId>
  <version>2.3.0</version>
  <type>jar</type>
</dependency>
```

### Поиск пересечений

Создадим функцию поиска пересечений, воспользуемся [ST\_Intersection](https://postgis.net/docs/ST_Intersection.html):

```
private List<Map<String, Object>> findIntersect(List<Long> tempIds) {
    if (tempIds.isEmpty()) {
        return new ArrayList<>();
    }
    String ids = StringUtils.join(tempIds, ',');
    String sql = String.format(
                """
                SELECT
                        ST_Intersection(temps.geom, mains.geom) intersects,
                        temps.gid temps_id,
                        mains.gid mains_id,
                        temps.geom temps_way,
                        mains.geom mains_way
                FROM
                        road_temp AS temps,
                        road_temp AS mains
                WHERE temps.gid IN (%s)
                  AND temps.gid <> mains.gid
                  AND ST_Intersects(temps.geom, mains.geom) IS TRUE
                """, ids);
    return jdbcTemplate.queryForList(sql);
}
```

В `WHERE temps.gid IN (...)` передаются идентификаторы добавленных линий, добавлены они сразу в основной слой, поэтому необходима проверка `temps.gid <> mains.gid`, чтобы не обрабатывать пересечения с самим собой. Чтобы исключить всякие мосты и тоннели, добавляйте подобные условия:

```
AND (mains.bridge IS null OR mains.bridge = 'F')
```

Пересечения нашли с помощью SQL.

### Обработка пересечений

Перебираем в цикле найденные пересечения:

```
List<Map<String, Object>> intersects = findIntersect(tempIds);
for (Map<String, Object> intersect : intersects) {
    // Идентификатор рассматриваемых линии
    Long tempId = ((Number) intersect.get("temps_id")).longValue();
    Long mainId = ((Number) intersect.get("mains_id")).longValue();

    ...

    // Геометрия пересечения
    PGgeometry pgGeom = (PGgeometry) intersect.get("intersects");
    Geometry geom = pgGeom.getGeometry();

    switch (geom.getTypeString()) {
        ...
    }
}

```

Здесь всё зависит от того, какого типа геометрия пересечения, подробнее они будут разобраны ниже, [в примерах](https://habr.com/ru/articles/688556/#Example). Но всё сводится к одной цели - иметь точки с одинаковыми координатами на месте пересечения.

### Модификация линий

Найдя факт пересечения линий, все эти линии необходимо модифицировать создав в них точки на месте пересечения.

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

Зная координаты точки пересечения, мы можем воспользоваться функцией [ST\_LineLocatePoint](https://postgis.net/docs/ST_LineLocatePoint.html), которая возвращает значение с плавающей точкой от 0 до 1, представляющее местоположение точки на линии:

```
String sql = String.format("SELECT ST_LineLocatePoint(geom, ?) FROM %s WHERE gid = %d", layerName, lineId);
double part = jdbcTemplate.queryForObject(sql, Double.class, new PGgeometry(intersectPoint));
```

Теперь нужно понять сколько точек в массиве располагается на этом участке, для этого воспользуемся [ST\_NumPoints](https://postgis.net/docs/ST_NumPoints.html):

```
int pointCount = 1;
if (part != 0) {
    // Количество точек в этой части линии
    sql = String.format("SELECT ST_NumPoints(ST_LineSubstring(geom, 0, %s)) FROM %s WHERE gid = %d", part + "", layerName, lineId);
    pointCount = jdbcTemplate.queryForObject(sql, Integer.class);
}
```

Теперь мы знает точку, после которой необходимо вставить найденную точку пересечения, при условии что координаты этих точек не совпадают, Для вставки воспользуемся функцией [ST\_AddPoint](https://postgis.net/docs/ST_AddPoint.html):

```
Point existPoint = tempWay.getPoints()[pointCount - 1];
if (existPoint.x != intersectPoint.x || existPoint.y != intersectPoint.y) {
    // Если отсутствует, - то создать
    sql = String.format("UPDATE %s SET geom = ST_AddPoint(geom, ?, %d) WHERE gid = %d", layerName, pointCount - 1, lineId);
    jdbcTemplate.update(sql, new PGgeometry(intersectPoint));
}
```

Здесь `tempWay` - это полученная из БД линия, чтобы ещё раз убедиться что координаты точки в указанной позиции не совпадают с добавляемой точкой пересечения.

Получение `tempWay`:

```
sql = String.format("SELECT geom FROM %s WHERE gid = %d", layerName, lineId);
LineString tempWay = (LineString) jdbcTemplate.queryForObject(sql, PGgeometry.class).getGeometry();
```

Теперь рассмотрим несколько примеров.

### Примеры

Данные, для моделируемых ситуаций я выложил в репозиторий GitHub [Tkachenko-Ivan/shape-example-graphhopper](https://github.com/Tkachenko-Ivan/shape-example-graphhopper), здесь они сохранены в shape-файлы, в README представлены схематичные иллюстрации содержимого, и дано текстовое представление данных. Текстовое представление можно использовать чтобы не грузить shape, а создать линии сразу в БД, с помощью [ST\_LineFromText](https://postgis.net/docs/ST_LineFromText.html), например:

```
INSERT INTO road_temp (gid,geom) VALUES (63,ST_LineFromText('LINESTRING (66.08940600324757 57.261858384708496, 66.09133689803114 57.26600914483714)',4326))
```

### Пересекающиеся линии

Сначала добавим две линии, которые пересекают одну из существующих линий, и пересекаются между собой. Точки, на местах пересечения не созданы - необходимо их создать. Например добавление 1 и 3 к линии 2.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b49016e6426831363d0e7d39b0c838f63a8e1271%2F71e5fbbeae604c5aae67c8ac976c5bdf.png?alt=media)

Пересекающиеся линии

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

Если линии пересекаются, то точки, на местах пересечений, должны быть добавлены в обе линии:

```
case "POINT" -> {
    // Модифицируем временную линию
    lineModificate(tempId, (Point) geom);
    // Модифицируем постоянную линию
    lineModificate(mainId, (Point) geom);
}
```

Было:

```
LINESTRING (66.06819599931458 57.25031281486611, 66.07962892895415 57.24961189436822)

LINESTRING (66.07467465944366 57.24771511084118, 66.07660555422723 57.25186587096982)

LINESTRING (66.07091449591776 57.24793501267562, 66.07284539070133 57.252085772804264)
```

Стало:

```
LINESTRING (66.06819599931458 57.25031281486611, 66.0719145752549 57.25008483951931, 66.0756699340296 57.249854609121236, 66.07962892895415 57.24961189436822)

LINESTRING (66.07467465944366 57.24771511084118, 66.0756699340296 57.249854609121236, 66.07660555422723 57.25186587096982)

LINESTRING (66.07091449591776 57.24793501267562, 66.0719145752549 57.25008483951931, 66.07284539070133 57.252085772804264)
```

Можно запустить ещё раз обработку этих линий, для того чтобы убедиться что повторно точки пересечения не создаются, - у ранее созданных точек дубли не появляются.

Если передать линии 1 и 3, для обработки, то будет найдено как пересечение 1 с 3, так и 3 с 1, важно повторно линии не обрабатывать, поэтому для хранения факта обработки необходимо создать список, и проверять в нём факт обработки.

```
public void putIntersectionPoint(List<Long> tempIds, List<Long> deleteList) {
    List<Bidi> map = new ArrayList<>();
    // Поиск пересечений
    List<Map<String, Object>> intersects = findIntersect(tempIds);
    for (Map<String, Object> intersect : intersects) {

        ...

        if (map.contains(new Bidi(mainId, tempId))) {
            // Это пересечение уже было обработано в другой комбинации
            continue;
        }

        // Сохранить признак того что эта пара линий уже обработана
        Bidi bidi = new Bidi(tempId, mainId);
        if (!map.contains(bidi)) {
            map.add(bidi);
        }

        ...
    }
}
```

### Соприкасающиеся линии

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d5d8f8d57bf05384e7c9771c035b77c3d1d4dc70%2F00fe00d4c0131d66046ebfa06fcdef8f.png?alt=media)

Соприкасающиеся линии

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

Ситуацию смоделировать легко, а вот написать обработчик наоборот - сложно. Основная сложность заключается в том, что примыкание может осуществляться с некоторой погрешностью, разумной конечно же. Если мы используем функцию [ST\_Intersects](https://postgis.net/docs/ST_Intersects.html) для определения статуса пересечения (пересеклась или нет), то допустимая погрешность 0.00001 (sic!). Это очень мало, невозможно гарантировать что все линии будут создаваться с таким допуском, поэтому первое что нужно сделать - [перегрузить функцию ST\_Intersects](https://gis.stackexchange.com/questions/236712/change-st-intersects-default-tolerance):

```
CREATE FUNCTION ST_Intersects(geography, geography, float8)
RETURNS boolean
AS 'SELECT $1 OPERATOR(&&) $2 AND _ST_Distance($1, $2, 0.0, false) < $3'
LANGUAGE 'sql' IMMUTABLE;
```

Теперь мы можем находить факт пересечения с той погрешностью, с которой посчитаем необходимым.

`ST_Intersection` находит геометрию пересечения - точку пересечения, которую мы ожидаем найти, однако, из-за тех же допусков и погрешностей может возвращать пустую геометрию. В этом случае можно воспользоваться функцией [ST\_ClosestPoint](https://postgis.net/docs/ST_ClosestPoint.html), которая вернёт из первой геометрии самую ближайшую точку ко второй геометрии:

```
 CASE
     WHEN ST_IsEmpty(ST_Intersection(temps.geom, mains.geom))
         THEN ST_ClosestPoint(temps.geom, mains.geom)
         ELSE ST_Intersection(temps.geom, mains.geom)
 END intersects
```

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

Кроме того, введение погрешности значительно замедляет работу SQL-запроса при поиске пересечений, если данных много, например все дороги России, - замедление становится чувствительным.

### Соединяющиеся линии

Тут всё просто, линии примыкают друг к другу.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-4b1213c035c34f9d34801aa2c4f7788fb19af6b6%2F5fcde83fc9135350b2f1731d1ef15c7f.png?alt=media)

Соединяющиеся линии

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

### Многократное пересечение линий

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-22c0b0a7bc5131ef5fa87347542b198732e8b5c4%2F01d9b655881fe59c7653653100105f6e.png?alt=media)

Многократно пересекающиеся линии

Особенность этого случая в том, что пересечением будет `MULTIPOINT`:

```
MULTIPOINT ((66.07382786284145 57.25453467704222), (66.07476362252248 57.25654623873221))
```

Во всех предыдущих случаях был `POINT`.

Мы можем разложить MultiPoint на Point, и обработать все точки последовательно, независимо друг от друга:

```
case "MULTIPOINT" -> {
    MultiPoint multiPoint = (MultiPoint) geom;
    for (Point point : multiPoint.getPoints()) {
          // Обработка как при обработке POINT
          ...
    }
}
```

### Накладывающиеся линии

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f103cc52e00c6d3df089cb631761cd73ad21634a%2F52d971a693d5319c99feaa306cb8c0e8.png?alt=media)

Накладывающиеся линии

Если одна из линий существует давно, а вторая загружена и в процессе обработки, то логично старую линия не трогать, а новую разбить на участки. Если обе линии новые, то “жертвой” может стать любая из них.

Результатом пересечения в этом случае будет LINESTRING, или MULTILINESTRING (если линии совпали на разных участках), например:

```
LINESTRING (66.08938990291034 57.24927918041815, 66.0888363036685 57.24808912999145)
```

Именно этот участок необходимо убрать. Воспользуемся [ST\_Difference](https://postgis.net/docs/ST_Difference.html) и посмотрим что осталось от линии:

```
/**
* Сохраняем различия линий (в виде новых линий) имеющих повторяющиеся куски
*
* @param tempId идентификатор временной линии
* @param mainId идентификатор уже существующей линии
* @return список идентифкаторов созданных линий
*/
private List<Long> lineSpliter(long tempId, long mainId) {
    List<Long> result = new ArrayList<>();
    // Получаем разницу
    String sql = String.format(
                """
                SELECT diff FROM
                (
                    SELECT ST_Difference(temps.geom, mains.geom) AS diff
                    FROM
                        %s AS temps,
                        %s AS mains
                    WHERE temps.gid = %d AND mains.gid = %d
                ) dif
                WHERE ST_IsEmpty(diff) = false
                """, layerName, layerName, tempId, mainId);
    List<Map<String, Object>> differences = jdbcTemplate.queryForList(sql);
    for (Map<String, Object> difference : differences) {
        PGgeometry pgGeom = (PGgeometry) difference.get("diff");
        Geometry geom = pgGeom.getGeometry();
        // Вставить в ту же самую таблицу
        result.addAll(insertTempLines(geom, tempId));
    }
    return result;
}
```

В нашем примере, от линии останется два отдельных куска, их надо вставить в слой:

```
public List<Long> insertTempLines(Geometry geom) {
    List<Long> result = new ArrayList<>();
    if (geom.numPoints() != 0) {
        String sql = "INSERT INTO road_temp (geom, date_load) VALUES (?, current_date)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        MultiLineString multiLine = (org.postgis.MultiLineString) geom;
        for (org.postgis.LineString line : multiLine.getLines()) {
            jdbcTemplate.update(
            (Connection connection) -> {
                PreparedStatement ps = connection.prepareStatement(sql, new String[]{"gid"});
                ps.setObject(1, new PGgeometry(line));
                return ps;
            },
            keyHolder);
            result.add(keyHolder.getKey().longValue());
        }
    }
    return result;
}
```

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

Это самая сложно воспроизводимая из ситуаций, всё дело, как всегда, в погрешности. Если при редактировании карты не включён режим “прилипания”, или он сам рассчитал с некоторым допуском (например в QGIS он может работать с точностью до 0,00001 градуса) - идеального совпадения не будет, а значит участок не будет обнаружен. Конечно факт пересечения в виде точке будет найден, благодаря `ST_ClosestPoint`, но именно точки, а не линии. И это меньшее из зол. Можно попытаться нивелировать погрешность с помощью [ST\_Buffer](https://postgis.net/docs/ST_Buffer.html), однако считаю это плохой идеей, т.к. `ST_Buffer` скажется даже на тех запросах, где результатом пересечения должна быть точка, - вместо неё будет линия длинной с размер буфера.

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

Полное наложение линий

Необходимо убедиться что останется только одна линия: новая линия не будет создана, если в БД уже такая существует, а если дубль присутствует в самом файле загрузки, то будет выбрана и создана только одна из двух.

Воспользуемся методом `ST_Difference`, например таким образом:

```
SELECT ST_Difference(temps.geom, mains.geom) AS diff
FROM
  road_temp AS temps,
  road_temp AS mains
WHERE temps.gid = 1 AND mains.gid = 2
```

Если линии полностью совпадают, то ответом будет `LINESTRING EMPTY`. Для удобства можем в запрос добавить `ST_IsEmpty(diff)`, и условие проверяющее на пустоту.

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

Похоже на пример с “наложением линий”.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d51fd241c07ff401173519e47c54d7c128649b39%2F1183d033903c7051cd512b14e7e07b69.png?alt=media)

Поглощение линий

Важно обратить внимание на последовательность действий: когда линия разделяется на кусочки (путём удаления совпадающей с другими линиями части), происходит операция вставки этих линий, и их тоже надо обработать как добавленные линии и найти точки пересечения с уже существующими. Необходимо чтобы линия, из которой они получились не участвовала в запросе поиска пересечений, иначе изначальная линия их полностью поглотит. Исходную линию надо как-то исключить, или даже удалить - `deleteFromTemp`, и только потом выполнять поиск пересечений рекурсивно вызвав `putIntersectionPoint`.

```
case "MULTILINESTRING", "LINESTRING" -> {
    // Если геометрией пересечения является линия значит временная линия будет поделена на несколько других
    List<Long> newTempIds = lineSpliter(tempId, mainId);

    // Удалить лишнюю линию
    deleteList.add(tempId);
    deleteFromTemp(Arrays.asList(tempId));

    putIntersectionPoint(newTempIds, deleteList);
}
```

```
private void deleteFromTemp(List<Long> deletesIds) {
    if (!deletesIds.isEmpty()) {
        String sql = String.format("DELETE FROM %s WHERE gid IN (%s)", layerName, StringUtils.join(deletesIds, ','));
        jdbcTemplate.update(sql);
    }
}
```

Идея та же: одна из наложившихся линий будет удалена, вместо неё будет создано две новые линии. Однако помимо наложения, эта линия ещё и имеет точки пересечения с некоторыми линиями (например при добавлении линии 4, она частично совпадает с линией 3, линия 4 будет удалена, и вместо неё появятся две новые).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-bd4c48b2adf355c7cf2725b7aa5614cfafffb7ee%2Fb3f185b7b213a7a92c700f0e4eb1bdeb.png?alt=media)

Наложение и пересечение линий

Необходимо убедиться, что в местах пересечений будет создано только по одной точке в каждой линии, и удаление не приведёт к ошибкам, в случае, если в списке найденных пересечений, имеется факт пересечения с удалённой ранее линией. Поэтому можно сохранять список удалённых линий, и при обработке найденных пересечений, проверять не была ли эта линия удалена:

```
if (deleteList.contains(tempId) || deleteList.contains(mainId)) {
    // Одна из этих линий была удалена на предыдущем шаге
    // найденное пересечение уже не актуально
    continue;
}
```

Это был последний из рассматриваемых примеров.

## Итог

Статья получилась объёмной, давайте ещё раз соберём всё вместе.

* И так, мы разобрали решение от Georepublic, оригинал их репозитория есть на GitHub: [mbasa/graphhopper-reader-postgis](https://github.com/mbasa/graphhopper-reader-postgis).
* Я сделал форк их решения и немного подшаманил его: [Tkachenko-Ivan/graphhopper-reader-postgis](https://github.com/Tkachenko-Ivan/graphhopper-reader-postgis).
* Данные в виде spahe файлов можно взять с [geofabrik](http://download.geofabrik.de/russia.html).
* Для тех, кому данные не принципиальные и хочется перейти к экспериментам с построителем, я уже загрузил данные по Калининградской области и выложил в докер контейнере на Docker Hub: [tkachenkoivan/road-data](https://hub.docker.com/r/tkachenkoivan/road-data).
* Затем разобрали как обрабатывать появление новых данных в таблице, получившийся код есть на GitHub Gist [Tkachenko-Ivan/PostGisGeometry.java](https://gist.github.com/Tkachenko-Ivan/c2418a09c887e0baa0a823944d76e343)
* Примеры загружаемых данных, на которых можно протестировать загрузку есть на GitHub: [Tkachenko-Ivan/shape-example-graphhopper](https://github.com/Tkachenko-Ivan/shape-example-graphhopper)

Мы рассмотрели, как реализовать в GraphHopper чтение данных из источника отличного от OSM, на примере PostgreSQL, но точно такой же подход можно использовать для чего угодно, из чего можно получить список дорог и их координат.

Удачи!


# Traveling Salesman Problem (TSP)

<https://habr.com/ru/articles/708072/>

## Задача коммивояжера (TSP) точное решение — метод ветвей и границ

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/592454ca03b8381d59084d2e72685653.png)/592454ca03b8381d59084d2e72685653.png)

> Путешествие в тысячу ли начинается с одного шага. (Лао-Цзы)

Что делает код хорошим? Большинство программистов ответят: хороший код должен быть структурирован, легко читаем и понятен. Но так ли важно качество кода, если он медленный? В большинстве задач производительность кода не критична, хотя и желательна. Но есть задачи, время выполнения которых столь огромно, что выигрыш в производительности доминирует над всем остальным.

Я говорю про NP-трудные задачи (NP-трудность - недетерминированная полиномиальная трудность по времени) и на одной из данного класса хочу акцентировать ваше внимание. Задаче коммивояжера.

Мы не будем рассматривать эвристические алгоритмы, нам нужно точное решение.

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

В [прошлой работе](https://habr.com/ru/post/701458/) мы ознакомились с решением задачи коммивояжера на минимум методом динамического программирования. Метод конечно замечательный, но он ограничен объёмом памяти системы. Уже при n = 28 вершин, 16 Гб оперативной памяти недостаточно, а использовать память жёсткого диска очень неэффективно. Возможно, используя хитроумные подходы структурирования и сжатия памяти можно несколько улучшить алгоритм, но ненамного. Нужен иной подход, если мы хотим рассчитывать матрицы большего размера.

Метод ветвей и границ, пожалуй, самый известный и эффективный метод нахождения точного решения задачи коммивояжёра. Не будем тут останавливаться на его описании, в интернете существует множество подробных мануалов с примерами. Для новичков рекомендую [статью](http://galyautdinov.ru/post/zadacha-kommivoyazhera) где, на мой взгляд, всё описано понятным языком.

Так же в своё время разобраться с тонкостями алгоритма мне помогла [работа](https://habr.com/ru/post/246437/). Как и её автор, я прошёл тот же путь, и совершил ровно те же ошибки, главная из которых заключалась в том, что при невозможности получить приемлемые результаты, начал использовать эвристики. Ещё один любопытный [подход](https://habr.com/ru/post/332208/).

Хочу обозначить одну тонкость метода ветвей и границ, которую мало кто освещает:

На этапе выбора элемента ветвления и разделения решения на два подмножества M1 (содержащие ребро с максимальным штрафом) и M2 (не содержит ребро с максимальным штрафом), при рассмотрении множества M1 вычеркиваем относящиеся к выбранной клетке строку и столбец, а также заменяем значение ячейки соответствующей обратному пути на бесконечность.

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/7fa7aeed65e29fd94eff360455c91d34.png)/7fa7aeed65e29fd94eff360455c91d34.png)

Есть некий граф, для которого на предыдущих этапах мы уже нашли рёбра \[0, 3], \[3, 5], \[6, 7], \[7, 1] и на текущем шаге рассматриваем ребро \[5, 6] множества M1.

Так вот обратным элементом для ребра \[5, 6], будет не ребро \[6, 5] (как можно было подумать), а ребро \[1, 0]. И вычёркиваем данное ребро затем, чтобы избежать в графе подцикл. Естественно, мы так же вычёркиваем все дуги, исходящие из \[5] (вычеркивание строки) и входящие в \[6] (вычеркивание столбца).

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

Игнорирование сего факта очень часто заводит полный перебор в тупик, про точное решение промолчу.

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

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

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

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

Алгоритм состоит в применении трёх мета шагов:

1. Производим приведение матрицы (редукция строк, столбцов), выбираем нулевой элемент с максимальной оценкой для разбиения.
2. На каждом ветвлении между выбором множества M1 или M2, **всегда двигаться в M1**, пока мы не достигнем дна. В матрице останется только один элемент, не предусматривающий ветвления, так мы получаем опорное решение. Сравниваем лучшее с текущим решением и запоминаем его, если оно короче.
3. Далее мы возвращаемся на уровень выше, проверяем для него множество M2 и вычёркиваем клетку. Переходим к шагу 1.

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

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/0ae6231387f7330b104eb5e4a4dd208a.png)/0ae6231387f7330b104eb5e4a4dd208a.png)

Пример расчёта множества M1

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

Вы можете спросить, к чему эти сложности? Именно так мы избавляемся от хвостовой рекурсии при расчёте множества М2 и гарантируем, что глубина рекурсии для множества M1 никогда не превысит n.

А если применить ещё небольшой фокус с выделением памяти только на стеке, не используя динамическое выделение памяти в куче (просить память у ОС относительно дорого), то алгоритм не будет покидать сверхбыструю память L1 процессора. То есть при таком подходе мы не будем использовать ужасную медленную оперативную память, ну разве что только при переключении потоков в ОС.

Например, при n = 33 программе требуется не более 63КБ стека, при любых входных данных.

Из прочих неочевидных оптимизаций:

* Предлагаю хранить индексы матрицы одним куском с данными, это здорово сокращает количество операций при копировании в матрицу меньшего размера, а также повышает локальность данных;
* Использовать только 32 битные переменные для всего, нужно для выравнивания обращений в памяти (выровненные обращения происходят быстрее);
* Использовать максимальное число для расчётов inf = 2147483647 (0x7fffffff), для того чтобы при сложении двух бесконечностей не получать переполнение регистра (не нужно проверять);
* Умышленный отказ от операций деления (очень дорогая инструкция);
* Переменные по возможности высвобождаются сразу, как перестают быть нужными;
* Объединить этап редукции с выбором нулевого элемента для разбиения, для уменьшения количества обращений к матрице;

  Последняя оптимизация интересна в том, что за один проход можно получить сразу два параметра: минимальное значение для редукции по строке и минимум по той же строке для анализа нулей. Для этого для каждого элемента матрицы применяем функцию two\_lows, которая находит два минимума за один проход, что сокращает почти вдвое число обращений к матрице на данном этапе. Аналогично и для столбцов.

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

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

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/b6dcb70c2319753e6708e38e25bd7ce4.png)/b6dcb70c2319753e6708e38e25bd7ce4.png)

Зависимость времени выполнения (сек) от размера матрицы - логарифмическая шкала

Графики были построены по 99, 98, 95 и 90 перцентилям от размера графа и времени в секундах. 99 перцентиль говорит о том, что мы найдём решение задачи в среднем в 99 случаях из ста, за указанное время для похожего набора входных данных.

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

Как бонус алгоритм неплохо себя чувствует на не полно связанных матрицах (отсутствующие связи задаются как отрицательные числа или inf), а также на не симметричных матрицах. Последнее особо важно для задач транспортной логистики, где расстояние в точку доставки не равно обратному пути, из-за того, что дороги могут быть односторонними.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/6a2bb4f37ac9653e7afcfb6d3cbb1b0f.png)/6a2bb4f37ac9653e7afcfb6d3cbb1b0f.png)

Задача коммивояжёра на минимум и максимум

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Algorithms/Route%20search/Traveling%20Salesman%20Problem%20\(TSP\)/Traveling%20Salesman%20Problem%20\(TSP\)/9a9eb6e6ef50791e30e8b335268405ac.png)/9a9eb6e6ef50791e30e8b335268405ac.png)

Задача коммивояжёра на максимум

Хоть я и обожаю Python, но для ресурсоёмких задач он не подходит совершенно, даже с numpy. Привожу пример ранней наивной реализации с использование pandas чисто в целях ознакомления для новичков, но возможно кого-то подвигнет на изучение.

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

Буду рад откликам, замечаниям, предложениям, возражениям.

Код, приведённый к статье, разработан вашим покорным слугой и может быть использован вами под лицензией GNU GPL.

P.S. C Наступающим Новым годом!


# Architecture Frameworks

[TOGAF](/readme/architect/architecture-frameworks/togaf)

[DODAF](/readme/architect/architecture-frameworks/dodaf)

[Enterprise Architecture (EA) Tools Reviews 2023 | Gartner](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Architecture%20Frameworks/Architecture%20Frameworks/Enterprise%20Architecture%20\(EA\)%20Tools%20Reviews%202023%20Ga.md)

Some of the most popular architecture frameworks:

1. **TOGAF (The Open Group Architecture Framework)**:
   * TOGAF is one of the most widely adopted enterprise architecture frameworks. It provides a comprehensive approach to designing, planning, implementing, and managing enterprise architectures. TOGAF is organized into different phases and includes guidelines and templates for each phase.
2. **Zachman Framework**:
   * The Zachman Framework is a well-known framework that classifies architectural artifacts into a matrix, which helps in organizing and understanding the different perspectives and elements of an enterprise architecture.
3. **FEAF (Federal Enterprise Architecture Framework)**:
   * FEAF is a framework used by the U.S. federal government for enterprise architecture development. It provides a structured approach to align business processes with IT solutions.
4. **DoDAF (Department of Defense Architecture Framework)**:
   * DoDAF is a framework used by the U.S. Department of Defense to create, manage, and analyze architecture models. It is tailored for defense and military applications.
5. **MODAF (Ministry of Defence Architecture Framework)**:
   * MODAF is similar to DoDAF but is used by the UK Ministry of Defence for modeling and managing defense architectures.
6. **The Three-Tier Architecture Model**:
   * This is a common software architecture framework that divides an application into three layers: presentation, business logic, and data storage. It's widely used in web development and other software systems.
7. **Microservices Architecture**:
   * Microservices is an architectural style that structures an application as a collection of small, independent services that communicate through APIs. It's popular for building scalable and maintainable systems.
8. **Serverless Architecture**:
   * Serverless architecture is a cloud computing model where the cloud provider manages the infrastructure, and developers focus on writing code in the form of functions. It's popular for building highly scalable and cost-effective applications.
9. **Event-Driven Architecture**:
   * Event-driven architecture is an approach where systems communicate and react to events. It's commonly used in scenarios where real-time processing and responsiveness are crucial.
10. **Model-Driven Architecture (MDA)**:
    * MDA is an approach to software design and development that focuses on creating platform-independent models that can be transformed into executable code. It emphasizes model-driven development.
11. **Big Data Architectural Frameworks**:
    * Various frameworks, like the Lambda Architecture and Kappa Architecture, are used for designing big data solutions that can handle large volumes of data and provide real-time analytics.

When choosing an architecture framework, it's important to consider the specific needs and context of your project or organization. Different frameworks are better suited to different situations, and some may be more appropriate for specific industries or domains. You should also consider the scale and complexity of your architecture and the skills and resources available within your organization.


# DODAF

## DODAF

<https://dodcio.defense.gov/library/dod-architecture-framework/>

## **The DoDAF Architecture Framework Version 2.02**

Welcome to DoDAF Version 2.02! This is the official and current version for the Department of Defense Architecture Framework.

For a description of changes made to DoDAF/DM2 2.01 to create DoDAF/DM2 2.02, download the [Version Description Document here.](https://dodcio.defense.gov/LinkClick.aspx?fileticket=EtUTwKVtKqM%3d\&tabid=1150\&portalid=0\&mid=34002)

***


# TOGAF

<https://www.opengroup.org/togaf>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c7e55dd3cb06f6f062b64e2765a833d6fe16a8d6%2FTOGAF10-banner.png?alt=media)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-27503ac8e99c0f36565dafa1e9781902c0b9aa4a%2Fadm.png?alt=media)

[The TOGAF® Standard, 10th Edition](https://www.opengroup.org/togaf/10thedition) makes adoption of best practices easier. It will show you where to find enduring and universal concepts and proven best practice and it will also underscore where to look for new emerging ideas.

Together universal concepts, best practice guidance, and emerging ideas are how you adapt the TOGAF Standard for your configured Enterprise Architecture practice.

* The TOGAF Standard is used by small, medium, and large commercial businesses, as well as government departments, non-government public organizations, and defense agencies
* With greatly expanded guidance and how-to material, it enables organizations to operate in an efficient and effective way across a broad range of use-cases, including agile enterprises and Digital Transformation
* The TOGAF Standard is designed for the dichotomy of common universal concepts and variable detailed configuration
* The structure focuses on what most architects want – more, better, and topical guidance on how to deliver the best Enterprise Architecture that supports their stakeholders and their organization
* It is divided into the TOGAF Fundamental Content and the TOGAF Series Guides; the TOGAF Fundamental Content provides the core concepts and practices, and the TOGAF Series Guides advise on configuration of the Fundamental Content

[Download now](https://publications.opengroup.org/c220)

[Go to Digital Edition](https://www.opengroup.org/togaf/10thedition)

## [The TOGAF® Standard, Version 9.2 Overview](https://publications.opengroup.org/c182)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-be5f66a5dfbb16b31a1841f0c4cab45c75fc67c4%2Ftogaf92-logo.png?alt=media)

The TOGAF® Standard, a standard of The Open Group, is a proven Enterprise Architecture methodology and framework used by the world’s leading organizations to improve business efficiency.

It is the most prominent and reliable Enterprise Architecture standard, ensuring consistent standards, methods, and communication among Enterprise Architecture professionals. Those professionals who are fluent in the TOGAF approach enjoy greater industry credibility, job effectiveness, and career opportunities. This approach helps practitioners avoid being locked into proprietary methods, utilize resources more efficiently and effectively, and realize a greater return on investment.

Further details can be found at

* [W182 White Paper: An Introduction to the TOGAF Standard, Version 9.2](http://www.opengroup.org/library/w182)
* [N180 Reference Cards: The TOGAF Standard, Version 9.2 Overview](https://publications.opengroup.org/n180)

[Download Here](https://publications.opengroup.org/c182)

[Read Online](https://pubs.opengroup.org/architecture/togaf92-doc/arch/)

## Certification

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-af9d78e5c0b4413eea99591f2c11f2dfed871fb0%2FTOGAF-Certification-Portfolio-full_3.png?alt=media)

The TOGAF certification portfolio includes certifications and certification credentials built upon the TOGAF Standard, Version 9.2, and the TOGAF Standard, 10th Edition. It includes a set of complementary learning paths centered around the TOGAF Standard, and the TOGAF Library. The paths include different amounts of learning, ranging from short certification credentials (3 hours study or more) to multi-day certifications.

* [The TOGAF Certification Portfolio](https://www.opengroup.org/certifications/togaf-certification-portfolio)
* [Knowledge-Based Certification](https://www.opengroup.org/certifications/knowledge-based-certifications)
* [How to Get Started](https://www.opengroup.org/certifications/getting-started)
* [Directory of Certified People](https://www.opengroup.org/certifications/registers)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c2e4172c3e17deedde61d27a2e5f02da6dce4a65%2FTOGAF20Banner.png?alt=media)

## [ARCHIMATE® 3.1 SPECIFICATION](http://publications.opengroup.org/c197)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f550a6a558dadca9b7855fa6d87a2c5aa2c06c98%2FArchiMate3.jpg?alt=media)

The ArchiMate® Specification, a standard of The Open Group, defines an open and independent modeling language for Enterprise Architecture that is supported by different tool vendors and consulting firms. The ArchiMate language enables Enterprise Architects to describe, analyze, and visualize the relationships among business domains in an unambiguous way.


# Enterprise Architecture (EA) Tools Reviews 2023 | Gartner

<https://www.gartner.com/reviews/market/enterprise-architecture-tools>

## Frequently Asked Questions

\_help\_What is Enterprise Architecture (EA) software?

Enterprise Architecture (EA) software provides a centralized, consolidated source of truth about the enterprise and captures the limitations of the IT assets, processes, value streams, change programs, and projects. While helping organizations mature and improve their business operations and operating model, they also support the overall business strategy and the enterprise’s ecosystem of relationships up and down the value chain.

\_help\_What features do EA tools need to include?

EA tools help to create a better-aligned enterprise that uses a cohesive and comprehensive set of models to shape and drive its future, and may include some examples of core features / capabilities which are:

* **Innovation Management:** Supporting the creation and tracking of innovation and change initiatives.
* **Automation:** Industrializing activities to deliver value more quickly and reliably while keeping information current.
* **Integration:** Exposing and importing data to and from other products.
* **Publication:** Enabling wide consumption of the data contained within the EA tool, across the enterprise and beyond.
* **Repository:** Providing a single source of truth for the organization with storage, categorization, and versioning of objects and model primitives of various sorts.
* **Modeling:** Structuring relationships across entities, such as business strategies, objectives, goals, constraints, capabilities, personas, or customer journeys.
* **Analysis:** Identify, assess, prioritize and track gaps, challenges, opportunities, and risks.
* **Presentation:** Display and illustrate information in the form of dashboards, heat maps, models, and scenarios that contribute to the presentation capability of the tool.
* **Usability:** Ease-of-use features and functions that enable support for various classes of users.
* **Configuration and management:** Set up and administer the support and security of the EA tooling platform.
* **Frameworks:** Apply EA, industry, or other frameworks as a starting point for structuring the repository and the relationships among artifacts.
* **Extensibility:** Extend the metamodel of the EA tool through the definition of new modeling primitives (concepts) and relationship types.

\_help\_Who uses Enterprise Architecture tools?

EA tools are often used by enterprise architecture and technology innovation leaders. As they serve across a broad range of architectural and IT disciplines (information, solution, security, applications, and infrastructure), many stakeholders from the boardroom and the C-suite across all strategic and operational roles can benefit from EA tools.

\_help\_In what areas can EA tools help the organizations?

EA tools operate at many levels and across a broad spectrum to enable insights and support informed decision-making. This spectrum includes but is not limited to:

* Business strategies, objectives, capabilities, competitors, ecosystem partners, and products/services, as well as the KPIs, metrics, risks, and costs related to them.
* Supporting technologies and applications, the services they offer, and interfaces between them, as well as infrastructure providers and vendors that provide these things.
* Customer segments and stakeholder personas, customer journey maps, and the processes, value streams, and activities that the organization depends upon to deliver value.
* Business scenarios, managing innovation, change and transformation programs/initiatives, and including the individual projects and development sprints in IT.

\_help\_What are the challenges for Enterprise Architecture tool deployments?

Organizations are likely to experience three major challenges while deploying an EA tool:

* **Initially structuring and continually restructuring the repository:** This involves looking into the future to predict the value desired, how to get there within the specific tool and who should contribute to and consume that value.
* **Populating the repository with consistent and usable information:** While evaluating the objectives, programs, products, projects, capabilities, applications, and technologies, it is important to define the right relationships between the individual elements in the repository.
* **Maintaining the repository and evolving its use over time:** Heavy maintenance is required to maintain the repositories and the complex models built within them so revisiting the processes and models in the repository is required regularly.

## Gartner Research

This research requires a log in to determine access

[Magic Quadrant for Enterprise Architecture Tools](https://www.gartner.com/doc/4022077)

[Critical Capabilities for Enterprise Architecture Tools](https://www.gartner.com/doc/4022170)

[Gartner Peer Insights 'Voice of the Customer': Enterprise Architecture Tools](https://www.gartner.com/doc/4410899)

## Trending Products

* [Bizzdesign Enterprise Studio](https://www.gartner.com/reviews/market/enterprise-architecture-tools/vendor/bizzdesign/product/bizzdesign-enterprise-studio)
* [HOPEX](https://www.gartner.com/reviews/market/enterprise-architecture-tools/vendor/mega-international/product/hopex)
* [OrbusInfinity](https://www.gartner.com/reviews/market/enterprise-architecture-tools/vendor/orbus-software/product/orbusinfinity)
* [Bizzdesign Horizzon](https://www.gartner.com/reviews/market/enterprise-architecture-tools/vendor/bizzdesign/product/bizzdesign-horizzon)

## Popular Comparisons

* [HOPEX vs Sparx Systems Enterprise Architect](https://www.gartner.com/reviews/market/enterprise-architecture-tools/compare/product/hopex-vs-sparx-systems-enterprise-architect)
* [Ardoq vs LeanIX Enterprise Architecture Management](https://www.gartner.com/reviews/market/enterprise-architecture-tools/compare/product/ardoq-vs-leanix-enterprise-architecture-management)
* [LeanIX Enterprise Architecture Management vs Sparx Systems Enterprise Architect](https://www.gartner.com/reviews/market/enterprise-architecture-tools/compare/product/leanix-enterprise-architecture-management-vs-sparx-systems-enterprise-architect)
* [HOPEX vs LeanIX Enterprise Architecture Management](https://www.gartner.com/reviews/market/enterprise-architecture-tools/compare/product/hopex-vs-leanix-enterprise-architecture-management)
* [OrbusInfinity vs Sparx Systems Enterprise Architect](https://www.gartner.com/reviews/market/enterprise-architecture-tools/compare/product/orbusinfinity-vs-sparx-systems-enterprise-architect)


# Zero Trust

<https://learn.microsoft.com/en-us/training/modules/azure-ad-privileged-identity-management/2-microsofts-zero-trust-model>

Cloud-based services and mobile computing have changed the technology landscape for the modern enterprise. Today’s workforce often requires access to applications and resources outside traditional corporate network boundaries, rendering security architectures that rely on firewalls and virtual private networks (VPNs) insufficient. Changes brought about by cloud migration and a more mobile workforce has led to the development of an access architecture called Zero Trust.

## The Zero Trust model

Based on the principle of “never trust, always verify,” Zero Trust helps secure corporate resources by eliminating unknown and unmanaged devices and limiting lateral movement. Implementing a true Zero Trust model requires that all components—user identity, device, network, and applications—be validated and proven trustworthy. Zero Trust verifies identity and device health prior to granting access to corporate resources. When access is granted, applying the principle of least privilege limits user access to only those resources that are explicitly authorized for each user, thus reducing the risk of lateral movement within the environment. In an ideal Zero Trust environment, the following four elements are necessary:

* Strong identity authentication everywhere (user verification via authentication)
* Devices are enrolled in device management, and their health is validated
* Least-privilege user rights (access is limited to only what is needed)
* The health of services is verified (future goal)

For Microsoft, Zero Trust establishes a strict boundary around corporate and customer data. For end users, Zero Trust delivers a simplified user experience that allows them to easily manage and find their content. And for customers, Zero Trust creates a unified access platform that they can use to enhance the overall security of their entire ecosystem.

## Zero Trust architecture

A Zero Trust approach extends throughout the entire digital estate and serves as an integrated security philosophy and end-to-end strategy.

The illustration below provides a representation of the primary elements that contribute to Zero Trust.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-faf1d564efa97fff70e91c2ba324b7b324e0dbe8%2Fzero-architecture-example-1a-4d3e0059.png?alt=media)

In the illustration above:

Security policy enforcement is at the center of a Zero Trust architecture. This includes Multi-Factor authentication with conditional access that takes into account user account risk, device status, and other criteria and policies that you set.

Identities, devices (also called endpoints), data, applications, network, and other infrastructure components are all configured with appropriate security. Policies that are configured for each of these components are coordinated with your overall Zero Trust strategy. For example, device policies determine the criteria for healthy devices and conditional access policies require healthy devices for access to specific apps and data.

Threat protection and intelligence monitors the environment, surfaces current risks, and takes automated action to remediate attacks.

## Guiding principles of Zero Trust

Today, organizations need a new security model that effectively adapts to the complexity of the modern environment, embraces the mobile workforce, and protects people, devices, applications, and data wherever they are located.

To address this new world of computing, Microsoft highly recommends the Zero Trust security model, which is based on these guiding principles:

* **Verify explicitly** - Always authenticate and authorize based on all available data points.
* **Use least privilege access** - Limit user access with Just-In-Time and Just-Enough-Access (JIT/JEA), risk-based adaptive policies, and data protection.
* **Assume breach** - Minimize blast radius and segment access. Verify end-to-end encryption and use analytics to get visibility, drive threat detection, and improve defenses.

## Microsoft's Zero Trust architecture

Below is a simplified reference architecture for our approach to implementing Zero Trust. The primary components of this process are Intune for device management and device security policy configuration, Azure AD conditional access for device health validation, and Azure AD for user and device inventory.

The system works with Intune, pushing device configuration requirements to the managed devices. The device then generates a statement of health, which is stored in Azure AD. When the device user requests access to a resource, the device health state is verified as part of the authentication exchange with Azure AD.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-98a6499b83d9051c46d6dca523f1dba9efdc3e90%2Faz500-zero-trust-architecture-d7277787.png?alt=media)

Important

The National Institute of Standards and Technology has a Zero Trust Architecture, NIST 800-207, publication.

**Microsoft Identity Manager** or MIM helps organizations manage the users, credentials, policies, and access within their organizations and hybrid environments. With MIM, organizations can simplify identity lifecycle management with automated workflows, business rules, and easy integration with heterogenous platforms across the datacenter. MIM enables Active Directory Domain Services to have the right users and access rights for on-premises apps. Azure AD Connect can then make those users and permissions available in Azure AD for Microsoft 365 and cloud-hosted apps.

On-premises Active Directory Domain Services, Azure Active Directory (Azure AD), or a hybrid combination of the two all offer services for user and device authentication, identity and role management, and provisioning.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-835345f3f834117d75350a3d7504eaad440535d3%2Faz500-hybrid-identities-e604f6fb.png?alt=media)

Identity has become the common factor among many services, like Microsoft 365 and Xbox Live, where the person is the center of the services. Identity is now the security boundary, the new firewall, the control plane—whichever comparison you prefer. Your digital identity is the combination of who you are and what you’re allowed to do. That is:

**Credentials + privileges = digital identity**

First step, you need to help protect your privileged accounts.

These identities have more than the normal user rights and, if compromised, allow a malicious hacker to access sensitive corporate assets. Helping secure these privileged identities is a critical step to establishing security assurances for business assets in a modern organization. Cybercriminals target these accounts and other privileged services in their kill chain to carry out their objectives.

## Evolution of identities

Identity management approaches have evolved from traditional, to advanced, to optimal.

**Traditional identity approaches**

* On-premises identity providers.
* No single sign-on is present between on-premises and cloud apps.
* Visibility into identity risk is very limited.

**Advanced identity approaches**

* Conditional access policies gate access and provide remediation actions.
* Analytics improve visibility into identity risk.

**Optimal identity approaches**

* Passwordless authentication is enabled.
* User, location, devices, and behavior are analyzed in real time.
* Continuous protection to identity risk.

## Steps for a passwordless world

* **Enforce MFA** — Conform to the fast identity online (FIDO) 2.0 standard, so you can require a PIN and a biometric for authentication rather than a password. Windows Hello is one good example, but choose the MFA method that works for your organization.
* **Reduce legacy authentication workflows** — Place apps that require passwords into a separate user access portal and migrate users to modern authentication flows most of the time. At Microsoft only 10 percent of our users enter a password on a given day.
* **Remove passwords** — Create consistency across Active Directory Domain Services and Azure Active Directory (Azure AD) to enable administrators to remove passwords from identity directory.

Important

We recommend **Azure AD Privileged Identity Management** as the service to help protect your privileged accounts.


# Billing

[SHM billing system](/readme/architect/billing/shm-billing-system)


# SHM billing system

<https://habr.com/ru/articles/437328/>

## SHM — безопасный, открытый, бесплатный, событийный универсальный биллинг

Представляю Вашему вниманию универсальную биллинговую систему, которая позволяет легко и просто автоматизировать оказание IT сервисов.

SHM хорошо подходит для оказания разовых и периодических услуг, таких как:

* Услуги хостинга
* Услуги по продаже сервисов, таких как VPN
* Интернет услуги и услуги связи с безлимитными (пакетными) тарифами

## Компоненты системы:

* Ядро (API)
* Web интерфейс для администраторов системы
* Web интерфейс для клиентов

Для оказания услуг необходимо:

* Подготовить Ваш сервер, на котором планируете оказывать услуги
* Установить SHM на Ваш сервер (можно на любой виртуальный сервер)
* Настроить SHM с помощью Web интерфейса администратора либо через API
* Подключить платежную систему для приема платежей от ваших клиентов

[Запустить](https://docs.myshm.ru/docs/install/docker/) SHM на своём сервере очень просто. Поддерживается Docker и Kubernetes.

## Биллинг

Биллинговая система позволяет [принимать платежи](https://docs.myshm.ru/docs/setup/pay/payments/), списывать средства за оказанные услуги, возвращать средства за преждевременно завершенные услуг, прогноз оплаты услуг и многое другое. Каждое такое действие называется: «[Событие](https://docs.myshm.ru/docs/setup/services/events/)». К событиям можно привязывать команды, которые могут быть выполнены на ваших серверах. Способ доставки команд на сервера называется: «[Транспорт](https://docs.myshm.ru/docs/setup/servers/transport/)» (SSH, HTTP, MAIL…).

Биллинг SHM поддерживает различные [системы расчетов](https://docs.myshm.ru/docs/setup/billing/).

## Команды и шаблоны

Когда клиент заказал и оплатил услугу, биллинг SHM инициирует событие «CREATE». Мы можем привязать к нему следующую команду:

```
vpn_create.sh —login="vpn_{{ us.id }}" —password="{{ us.gen_store_pass }}"
```

Где:

* «vpn\_create.sh» — скрипт, заранее написанный, для создания услуг на сервере и принимающий в качестве аргумента идентификатор услуги.
* {{ us.id }} — шаблон. SHM заменит это выражение на реальный и уникальный идентификатор услуги пользователя.
* {{ us.gen\_store\_pass }} — шаблон со специальной функцией, которая вернет сгенерированный пароль и сохранит его в БД, в настройки услуги пользователя, для возможности предоставления его клиенту.

SHM выберет доступный сервер из списка и выполнит на нём эту команду, например с помощью транспорта SSH. В случае, если команда будет успешна, услуга будет считаться оказанной, а её статус будет установлен в значение: ACTIVE. А в случае ошибки (например если сервер не доступен), SHM предпримет дальнейшие попытки выполнения этой команды через некоторое время.

Аналогичным образом, мы можем привязать команды и к другим событиям: («BLOCK», «REMOVE» и т.п.)

С помощью [шаблонов](https://docs.myshm.ru/docs/setup/templates/) SHM умеет формировать и email уведомления, и целые скрипты.

## SHM поддерживает вложенные, дочерние услуги

Если мы хотим оказывать услуги Виртуального хостинга, то под «Тарифом» мы подразумеваем сразу несколько услуг, такие как: «Хостинг сайтов», «Хостинг почты» и «Хостинг БД». В этом случае, мы создаем сразу 4 услуги, где родительская услуга это «Тариф». Привязываем соответствующие отдельные команды для дочерних услуг. Это можно представить в виде дерева:

> «Тариф» (100р./мес.)

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

Такой подход позволяет оказать несколько услуг с единым платежом и привязать к родительской услуге команду для email уведомления об успешно созданном (или удаленном) тарифе.

## SHM позволяет хранить произвольные данные

Практически во всех таблицах БД есть поле `settings`, куда можно сохранять произвольные данные в формате JSON. Для некоторых услуг, таких как «Виртуальный сервер (VPS)», обычно сохраняют такие параметры как `CPU` и `RAM`. Каталог услуг хранится в таблице `services`. Следующий пример позволит создать услугу VPS с дополнительными параметрами, сохраненными в услуге:

```
vps_create.sh —login="vps_{{ us.id }}" —password="{{ us.gen_store_pass }}" \
   —cpu="{{ us.service.settings.cpu }}" —ram="{{ us.service.settings.ram }}"
```

Дополнительно, есть возможность сохранять произвольные, многострочные данные, причем в момент выполнения скриптов на серверах. Например, когда мы оказываем услугу VPN, нам необходимо куда-то сохранить сгенерированный ключ VPN, для последующей передачи его клиенту. Это можно сделать с помощью следующего шаблона скрипта:

```
#!/bin/bash

USER_ID="{{ user.id }}"
KEY="my_vpn_key"
SESSION_ID="{{ user.gen_session.id }}"

# Создаем пользователя и генерируем VPN ключ
./vpn-create-config.sh -c -u ${USER}

# Сохраняем VPN ключ пользователя в хранилище SHM
curl -sX PUT \
    -H "session-id: ${SESSION_ID}" \
    -H "Content-Type: text/plain" \
    https://mybilling.local/shm/v1/storage/${KEY} \
    --data-binary @- < <(cat out/${USER}.ovpn)
```

Получить сохраненный ключ клиент сможет с помощью ссылки вида:

```
https://mybilling.local/shm/v1/storage/my_vpn_key
```

Больше информации можно получить на [сайте](https://docs.myshm.ru/) документации и в группе [Telegram](https://t.me/shm_billing).

## Заключение

Биллинг SHM представляет из себя готовую систему, с Web интерфейсом администратора, клиента и API (backend).

## Основные особенности системы:

* OpenSource (<https://github.com/danuk/shm>), в разработке с 2016 года.
* Различные режимы [биллинга](https://docs.myshm.ru/docs/setup/billing/)
* Гибкое построение команд ([шаблоны](https://docs.myshm.ru/docs/setup/templates/)) и привязка их к [событиям](https://docs.myshm.ru/docs/setup/services/events/)
* Отслеживает статус выполнения команд на серверах, с поддержкой Retry и логированием вывода команд (pipeline), по аналогии с GitLab CI.
* Поддерживает неограниченное кол-во серверов. Позволяет объединять сервера в группы.
* Располагается на отдельном, независимом сервере. Общается с серверами безопасно, посредством "[Транспорта](https://docs.myshm.ru/docs/setup/servers/transport/)"
* Работает в контейнерах (Docker). Легко инсталлировать, поддерживать и обновлять. Поддержка Kubernetes.
* API
* Код SHM обширно покрыт unit тестами, что позволяет избегать ошибок при разработке
* Работа с БД осуществляется в транзакционном режиме, что позволяет оставаться системе атомарной.
* Легко делать резервные копии системы (бэкапы)
* … и многое другое

SHM находится в активной разработке. Множество идей ещё предстоит реализовать.

Документация: [https://docs.myshm.ru](https://docs.myshm.ru/)

Поддержка в группе Telegram: <https://t.me/shm_billing>

p.s. в самое ближайшее время, в качестве примера, я планирую написать подробную статью/инструкцию по запуску сервиса продажи VPN на основе SHM.


# Bots

[Discord](/readme/architect/bots/discord)

[Telegram](/readme/architect/bots/telegram)


# Discord

<https://habr.com/ru/articles/715552/>

## Руководство по созданию экономического бота для Discord, а также установка необходимых компонентов и ресурсов

Начнём с самого начала. Если у вас всё ещё не установлен Discord, то вы можете это сделать на официальном сайте discord.com, тут же вы можете открыть Discord в браузере если вы не хотите его устанавливать. После входа в Discord вам потребуется создать свой сервер, или же использовать любой уже существующий сервер, где вы имеете права администрации.

Когда вы определитесь с сервером вам понадобится подготовиться к созданию самого бота. Для этого вам нужно установить язык программирования Python. Переходим на официальный сайт python.org, далее в вкладке Downloads в соответствии с вашей операционной системой переходим на нужную страницу и устанавливаем Python версии 3.9.x, так как модуль discord который потребуется нам для написания бота не поддерживает более новые версии.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9817125d29a2f820b4fcaa9b5155096a52408648%2F6a4dc2e06d301ffa76455c8c71e7ed1c.png?alt=media)

Как только вы откроете установочный файл Python, вы должны отметить галочку, подписанную как: “Add python.exe to PATH”. После этого можете нажимать install now. Спустя некоторое время вы увидите надпись Setup was successful, это означает что Python был успешно установлен.

Далее нам следует установить среду разработки. Вы можете программировать на Python как в стандартном IDLE, который устанавливается вместе с Python, так и в среде разработки собственного выбора. Но я бы порекомендовал PyCharm, ведь он наиболее распространённый и удобный, и создан специально Python. Для его установки переходим на официальный сайт jetbrains.com на страницу с PyCharm, и нажимаем на кнопку Download.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8ae7d715d2683f905b17ee4cbf42a7513ec55680%2F217304d0ac7881612d50ecd7b88bfecf.png?alt=media)

Далее выбираем в соответствие с вашей операционной системой, вы можете заплатить и использовать версию Professional, или же установить бесплатную версию Community.

Во время установки вам предложат изменить путь установки программы, чтобы избежать неожиданных проблем изменять путь не стоить. На следующей странице установки вы можете нажать на первый пункт что бы создать ярлык на рабочем столе, более ни чего трогать не стоит. После установки запускаем PyCharm и нажимаем New Project. Далее пере называем папку проекта как хотим, расположение проекта трогать не стоит. Выбираем пункт Previously configured interpreter и нажимаем Add Interpreter, заходим в System Interpreter, и видим путь к Python, или же вводим его вручную, нажимаем OK.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2200d010ff69326f1d235bd4786a433236fa9336%2F9734219f32a8df72ee5bba40207159c6.png?alt=media)

Для удобства ставим галочку в нижнем пункте, который создаст main.py, в котором мы и будем писать главный код бота.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2a07af357462448603423b96e314c2ca98bfb5c0%2Fcf78ccfd6541526561cb626d197620e5.png?alt=media)

Нажимаем кнопку Create, после создания проекта мы видим рабочею среду PyCharm, и открытый файл main.py. Ждём, когда PyCharm всё настроит и стираем содержимое main.py. Теперь нам осталось установить нужные модули для Python и приступить к созданию бота. Для установки библиотек вы можете воспользоваться настройками в PyCharm, или же ввести в терминал PyCharm команды, описанные далее. Для начала на всякий случай обновим pip, для этого нужно ввести в терминал:

`python -m pip install --upgrade pip`

Теперь установим библиотеку discord введя в терминал:

`pip install discord`

Следующие модули установим тогда, когда они понадобятся.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8bfe0900bf32ebf6702f8a4ba5a37c082ab71170%2F61525111cebb3073d37674e6f5d0137f.png?alt=media)

Теперь приступим к созданию бота. Для этого сперва перейдём сайт Discord Developer Portal, и удостоверившись что это официальный сайт, входим в учётную запись Discord. В свободное время вы можете почитать документацию, а для создания бота перейдите во вкладку Applications, здесь вы можете увидеть список своих приложений.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7937c082ff38aa4464375d5179e68f9f494d88a0%2F3af705ccb8b51ab4e27907a2d35ab619.png?alt=media)

Нажимаем New Application, даём желаемое название нашему приложению, и жмём Create. После вы появитесь во вкладке General Information, где вы можете добавить описание, изменить название, добавить аватар и т.п. для вашего приложения. Теперь нам понадобиться перейти во вкладку Bot, где мы и создадим самого бота нажав на Add Bot. В этой же вкладке можно изменить имя и аватар бота. Всё что нам понадобиться на этой странице это токен бота. Сразу стоит оговорить что его не стоит никому присылать и показывать токен, а также публиковать где либо, так как с его помощью люди смогут делать что захотят от лица бота, если всё-таки такое произошло, сразу же нажимайте Reset Token на странице настройки вашего бота, это пересоздаст токен, и вы сможете заменить старый на новый, не переживая за вашего бота. Токен вашего бота находиться под именем бота всё на той же странице во вкладке Bot, если токен не отображается просто нажмите Reset Token, а после можете его скопировать кнопкой Copy.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-16ffbfd504463e71e7e7d7d425c8fabc45058b90%2F5e520b708e2b9e1c6382bbcca5c910a3.png?alt=media)

Пришло время добавить бота на сервер, для этого разверните вкладку OAuth2, и перейдите в подвкладку URL Generator, в блоке scopes прожмите галочку Bot, после в появившемся блоке bot permissions для полного функционала бота на сервере поставьте галочку в пункте Administrator, это нужно для того, чтобы ваш бот мог делать всё что вы пожелаете на сервере, где он находиться. Попрошу не обманывать людей предлагая им добавить вашего бота, и при этом затевая что-то плохое, и также не попадайтесь сами, не добавляйте незнакомых и не верифицированных ботов, имеющих права администратора на свой сервер. После выбора этих пунктов в самом низу в блоке generated url вы можете увидеть ссылку для добавления бота на сервер. Перейдя по ссылке, выберете сервер куда вы хотите добавить бота и оставьте галочку Администратор. После нажатия кнопки Авторизовать, ваш бот присоединится к выбранному серверу и будет иметь права администратора.

Теперь всё что нам осталось это написать код. Переходим в PyCharm и открываем файл main.py. Если же вы не создали main.py при создании проекта, то создайте его сейчас с помощью нажатия правой кнопки мыши на папку проекта в левой панели PyCharm с директорией проекта, наведитесь на New, выберете пункт Python File и введите название файла. Таким образом мы будем создавать и другие файлы проекта, если же вы называете файлы по-своему, то не забудьте, как назван аналогичный файл в этом руководстве что бы не было путаницы. Для начала я предлагаю создать файл config.py, куда мы будем записывать все константы и т.п. В главном файле main.py импортируем модуль discord, пару его частей и наш файл конфига:

```
import discord
from discord.ext import commands
from discord.utils import get
from config import *
```

Далее добавим в конфиг константы TOKEN и PREFIX:

```
TOKEN = "ВАШ ТОКЕН"
PREFIX = "!"
```

В константе TOKEN будет храниться токен вашего бота, где найти токен описано выше. В константе PREFIX будет храниться префикс ваших команд, например “!”.

Теперь в главном файле после импортов, создаём переменную client, с помощью которой будут работать наши команды и не только:

```
client = commands.Bot(command_prefix=PREFIX, intents=discord.Intents.all())
```

При создании переменной мы создаём класс commands.Bot в который мы должны указать префикс наших команд, используя константу, которую мы импортируем из нашего файла config.py. Вы можете не задавать переменную intents, если после создания бота при его запуске вас не потребуют это сделать, если же всё-таки у вас потребовали добавить intents в переменную client, вам понадобиться включить все пункты блока Privileged Gateway Intents, который можно найти на той же странице, где и токен вашего бота.

Что бы узнать работу команд посмотрим на форму префикс команды:

```
@client.command()
async def <название команды>(ctx, <прочие переменные если они должны быть>):
    <код который исполняется при вызове команды>
```

Как вы заметили, команда, это просто асинхронная функция, а асинхронная она для того, чтобы различные команды можно было использовать не зависимо от друг друга. Разберём каждую строчку. В первой строчке мы добавляем нашей функции декоратор, который делает из этой функции полноценную команду, далее в название команды (функции) вы должны записать то, что нужно будет ввести в самом Discord после префикса для вызова команды, название может быть на любом языке, т.е. вы можете ввести название этой функции даже на русском. В полученных переменных мы обязательно должны получить ctx, вкратце с его помощью мы получаем информацию о вызове команды: само сообщение в Discord, автора сообщение, канал и так далее. Если же вам нужно получить ещё какую-либо информацию от того, кто вызвал команду, то вы можете создать дополнительные переменные, чтобы при вызове команды заполнить их, после команды через пробел нужно ввести столько информации сколько нужно. Каждый пробел разделяет то, что вы написали на разные переменные, то есть у вас не выйдет написать предложение в одну дополнительную переменную, например если ваша команда получает две переменные, а вы ввели: “<команда> <слово> <слово> <слово>”, то это будет ошибка. Так же стоит уточнить что если вам нужно получить число, то переменную придётся перевести из строки в число в коде команды. И последнее, как вы, наверное, поняли, это сам код команды, который будет работать при вызове команды.

Рассмотрим пример стандартной тестовой команды “ping”, с небольшими дополнениями:

```
@client.command()
async def ping(ctx):
    await ctx.send("pong!")
    await ctx.send(ctx.author.name)
```

Первое что делает наша команда, с помощью функции await ctx.send() отправляет сообщение “pong!”, в чат в котором была вызвана команда. Далее с помощью ctx.author мы получаем класс пользователя, который вызвал команду, а потом его имя также отправляем в чат.

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

```
client.run(token=TOKEN)
```

После всех действий код должен выглядеть так:

```
import discord
from discord.ext import commands
from discord.utils import get
from config import *

client = commands.Bot(command_prefix=PREFIX, intents=discord.Intents.all())

@client.command()
async def ping(ctx):
    await ctx.send("pong!")
    await ctx.send(ctx.author.name)

client.run(token=TOKEN)

```

Запускаем код, и переходим в Discord. После отправки команды вы должны будете увидеть следующее: см. картинку ниже.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-04de049ce441201f6dca7df89df631286ec8f405%2F1503287abed30748f725a70f8977487d.png?alt=media)

Готово! Какой никакой бот уже есть, и он работает.

Продолжим, и приступим к написанию команд для экономики, а также созданию json файла, где будут храниться балансы пользователей сервера в Discord. Перед работой с json, импортируем стандартный модуль для работы с ним:

```
import json
```

Создаём базу данных в виде файла wallets.json, который будет хранить данные о кошельках пользователей, а также в него нужно написать {} что бы создать пустые данные. Далее создадим функцию, которая будет извлекать всю информацию о кошельке нужного пользователя из базы данных, или записывать если его там нет. Но перед этим добавим новую константу с параметрами кошелька по умолчанию в наш config.py:

```
WALLET_DEFAULT = {"balance": 0}
```

Теперь и сама функция:

```
async def get_user_wallet(user_id):
    user_id = str(user_id)

    with open("wallets.json", "r") as file:
        users_wallets = json.load(file)

    if user_id not in users_wallets.keys():
        users_wallets[user_id] = WALLET_DEFAULT

    with open("wallets.json", "w") as file:
        json.dump(users_wallets, file)

    return users_wallets[user_id]
```

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

```
async def set_user_wallet(user_id, parameter, new_value):
    user_id = str(user_id)

    with open("wallets.json", "r") as file:
        users_wallets = json.load(file)

    if user_id not in users_wallets.keys():
        users_wallets[user_id] = WALLET_DEFAULT

    users_wallets[user_id][parameter] = new_value

    with open("wallets.json", "w") as file:
        json.dump(users_wallets, file)
```

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

Систему записи и получения информации из базы данных мы реализовали, теперь её нужно задействовать. Создадим команду для просмотра баланса:

```
@client.command()
async def balance(ctx):
    user_wallet = await get_user_wallet(ctx.author.id)
    await ctx.send(f"**Ваш баланс**: {user_wallet['balance']}")
```

Тут всё просто, мы создаём стандартную префикс команду с названием “balance”, далее при вызове команды с помощью функции, описанной выше, мы получаем кошелёк пользователя, который вызвал команду, и пишем в чат его баланс.

Пора протестировать работу бота. Но перед этим, добавим ещё одну, на этот раз не обязательную, функцию, а именно, событие, которое будет вызываться при включении бота:

```
@client.event
async def on_ready():
    print("Бот запустился!")
```

Нам она понадобится чтобы чётко видеть, когда бот включился. Замете что декоратор немного изменился, так как это не команда, а событие, и в зависимости от названия функции будет меняться событие, при котором функция будет срабатывать, чтобы узнать какие ещё события существуют ознакомьтесь с документацией модуля discord.py.

Теперь запускаем нашего бота и при тестирование его работы, мы должны увидеть следующее: см. картинку ниже.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c2552bc7a5984fc89d31fb33093e871f3b474f8f%2F8c12c8bfda0401ff598d2e7225d591ec.png?alt=media)

А также если посмотреть в наш json файл, то увидим, что бот записал нас туда.

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

```
@client.command()
async def change_balance(ctx, user_mention=None, amount=None):
    if not ctx.author.guild_permissions.administrator:
    	  await ctx.send("**Недостаточно прав!**")
    	  return

    if user_mention is None or amount is None:
        await ctx.send(f"**Вы не указали тот или иной параметр!**")

    user = get(ctx.guild.members, id=int(user_mention[2:-1]))
    user_wallet = await get_user_wallet(user.id)
    user_wallet["balance"] += int(amount)
    await set_user_wallet(user.id, "balance", user_wallet['balance'])

    await ctx.send(f"Баланс {user.name} был успешно изменён!\n" \
                   f"**Баланс {user.name}**: {user_wallet['balance']}")
```

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

Как обычно протестируем работу:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8ae7118c40de211f1f39833ca2417860f8d495bc%2F57b506774c4d05e3cb2cb970e0d421d7.png?alt=media)

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

```
@client.command()
async def kill(ctx):
    if not ctx.author.guild_permissions.administrator:
        await ctx.send("**Недостаточно прав!**")
        return
    sys.exit()
```

Для этой команды ещё понадобиться импортировать модуль sys:

```
import sys
```

Теперь посмотрим на такую интересную вещь в оформлении сообщений как “embed”. Вкратце это табличка, с помощью которой можно оформить текст. Рассмотрим создание embed на примере команды баланса:

```
embed = discord.Embed(title=f"💰 БАЛАНС {ctx.author.name}: {user_wallet['balance']}",
                      color="#FFD700")
```

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

```
embed.add_field(
    name=<заголовок поля>,
    value=<содержание текста>,
    inline=<если указано False то каждое поле будет с новой строки>
)
```

В нашем случае это не понадобиться. По итогу, чтобы отправить в сообщении embed, нужно использовать всё тот же await ctx.send(), только на этот раз в следующем виде:

```
await ctx.send(embed=embed)
```

И вновь протестируем всё выше созданное:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0a8f17d50e75fad5947d27a1515da3ac3beeb6c6%2Facfebcf6e754fa82999d6c469a2e6fb6.png?alt=media)

Как видим после команды kill, бот перестаёт работать.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5e5a407777ddf1f4216f2fecbe8ee98a088c1a5f%2F73119c12aee6beaea1c5dd1da1b8978c.png?alt=media)

Теперь баланс выглядит намного лучше.

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


# Telegram

[Chat GPT Telegram bot](/readme/architect/bots/telegram/chat-gpt-telegram-bot)

[Получаем статистику Telegram-канала при помощи api и python или свой tgstat с регистрацией и смс](/readme/architect/bots/telegram/poluchaem-statistiku-telegram-kanala-pri-pomoshi-api)

[Как хостить телеграм-бота (и другие скрипты на Python) на Repl.it бесплатно 24/7](/readme/architect/bots/telegram/kak-khostit-telegram-bota-i-drugie-skripty-na-pyt)

[Создание Telegram бота на PHP #1: основные понятия для работы с API](/readme/architect/bots/telegram/sozdanie-telegram-bota-na-php-1-osnovnye-ponyatiya-dlya-raboty-s-api)

[Создание Telegram бота на PHP #2: создание первого бота для Telegram](/readme/architect/bots/telegram/sozdanie-telegram-bota-na-php-2-sozdanie-pervogo-bota-dlya-telegram)

[Создание Telegram бота на PHP #3: примеры отправки сообщений с кнопками в Telegram](/readme/architect/bots/telegram/sozdanie-telegram-bota-na-php-3-primery-otpravki-soobshenii-s-knopkami-v-telegram)

[Создание Telegram бота на PHP #4: отправка файлов и изображений в Telegram](/readme/architect/bots/telegram/sozdanie-telegram-bota-na-php-4-otpravka-failov-i-izobrazhenii-v-telegram)

[Создание Telegram бота на PHP #5: работа с хуками](/readme/architect/bots/telegram/sozdanie-telegram-bota-na-php-5-rabota-s-khukami)


# Chat GPT Telegram bot

<https://habr.com/ru/articles/719912/>

## Телеграм-бот на языке Python, использующий API OpenAI для получения ответов на запросы

В этой статье хочу поделиться интересным опытом работы с ChatGPT, в рамках которого

был написан телеграм-бот на языке Python, использующий API OpenAI для

получения ответов на запросы.

Началось все с того, что я захотел зарегистрировать себе аккаунт на OpenAI и понять, кто такой этот ваш ChatGPT. Немного танцев с бубном, и желаемая цель была достигнута. Бот понравился, но возникали некоторые неудобства из-за доступа по VPN. Тогда у меня появилась идея создать телеграм-бота, который будет отправлять запросы в API OpenAI и возвращать ответы.

Я потратил следующий час на чтение [документации](https://platform.openai.com/docs/introduction) по API OpenAI, но моя лень оказалась более находчивой, и я решил дать ChatGPT задание написать бота самостоятельно.

Что из этого вышло:

## Начало киберпанка

Текст ТЗ для ChatGPT

Приветствую, мой дорогой! У меня для тебя задание: написать

телеграм-бота на Python, который будет отправлять запросы в API OpenAI и

возвращать ответы. Для работы с API телеграм давай использовать

библиотеку telebot

1. Телеграм-бот должен иметь два обработчика команд - /start и /help. Текст ответа на команды может быть любым.
2. Бот должен немедленно отправлять ответ о том, что запрос принят в работу, при получении запроса от клиента.
3. Когда бот получает запрос от пользователя, необходимо вызвать метод create у класса Completion с использованием библиотеки OpenAI. В качестве параметров задаем модель text-davinci-003 и max\_tokens = 3500.
4. Затем мы получаем ответ от API OpenAI и возвращаем его пользователю.

ChatGPT получил от нас задание и принялся за работу. Стоит отметить, что предоставленный код, будет использован без изменений и рефакторинга.

Вот сгенерированный им код:

```
import telebot
import openai
import os

# устанавливаем ключ API OpenAI из переменной окружения
openai.api_key = os.environ.get("OPENAI_API_KEY")

# создаем экземпляр телеграм бота
bot = telebot.TeleBot('YOUR_TOKEN')

# создаем обработчик команд
@bot.message_handler(commands=['start'])
def start(message):
    bot.reply_to(message, "Привет! Я бот, который может помочь вам с OpenAI API.")

@bot.message_handler(commands=['help'])
def help(message):
    bot.reply_to(message,
                 "Вы можете отправлять запросы в OpenAI API через меня. Просто напишите мне свой запрос и я отправлю его на обработку.")

# создаем обработчик сообщений
@bot.message_handler(func=lambda message: True)
def echo(message):
    bot.reply_to(message, "Запрос принят в работу.")
    try:
        response = openai.Completion.create(
            engine="text-davinci-003",
            prompt=message.text,
            max_tokens=3500
        )
        bot.reply_to(message, response.choices[0].text)
    except:
        bot.reply_to(message, "Произошла ошибка при обработке вашего запроса.")

# запускаем телеграм бота
bot.polling()

```

Немного поправляем этот участок кода `bot = telebot.TeleBot('YOUR_TOKEN')`, чтобы значение тянулось из переменных окружения.

Первое сообщение боту

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ab06bc5695290dc670bb13616c2f27396f5c2239%2F99800589ff74f09c08a9193c386ebd3c.jpg?alt=media)

Вуаля! Буквально одна правка и все работает. Но мы хотим продолжить беседу про дождь и уточнить еще некоторых моментов, и получаем что-то странное.

Продолжаем беседу про дождь, но не упомянем о нем явно

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fd875240055932bf7781e3846034e8f0e0272642%2Fce80e3cecc54d219e4fed9170574ff96.jpg?alt=media)

Проблема здесь заключается в том, что бот не способен учитывать контекст разговора, поскольку каждое новое сообщение отправляется в API отдельно. Просмотрев документацию, я понял, что здесь нет явного решения этой проблемы. (Если я не прав, поправьте меня, пожалуйста).

Поэтому я принял решение делать костыль. Нам нужно завести какой-то кеш и хранить информацию в нем. Мы можем условно разбить кеш на два уровня: один будет храниться в базе данных, а другой - в рантайме.

С учетом этой информации, мы готовы внести некоторые изменения в исходное ТЗ.

Текст ТЗ №2 для бота

Задача требует внести изменения в код бота, чтобы он мог сохранять

контекст предыдущих сообщений пользователей и использовать его при

отправке запросов в API OpenAI.

1. Для хранения контекста необходимо использовать как массив, так и баз данных SQLite. Бот должен сначала пытаться получить информацию о контексте из массива. При этом массив должен быть очищен через 10 минут. Если контекст не найден в массиве или время его хранения истекло, бот должен обратиться к базе данных.
2. Важно предусмотреть возможность оповещения пользователя в случае возникновения ошибок.

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

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

Получаем от бота вот такой результат:

```
import telebot
import openai
import os
import sqlite3
from datetime import datetime, timedelta

# устанавливаем ключ API OpenAI из переменной окружения
openai.api_key = os.environ.get("OPENAI_API_KEY")

# создаем экземпляр телеграм бота
bot = telebot.TeleBot(os.environ.get("TG_API_KEY"))

# создаем подключение к базе данных
conn = sqlite3.connect("example.db", check_same_thread=False)

# создаем таблицу в базе данных для хранения контекста
with conn:
    cur = conn.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTS context (user_id TEXT, message TEXT, timestamp TEXT)")

# задаем интервал, через который массив с контекстом будет очищаться
CONTEXT_CACHE_INTERVAL = timedelta(minutes=10)

# словарь, в котором будут храниться последние запросы пользователя
context_cache = {}

# создаем обработчик команд
@bot.message_handler(commands=['start'])
def start(message):
    bot.reply_to(message, "Привет! Я бот, который может помочь вам с OpenAI API.")

@bot.message_handler(commands=['help'])
def help(message):
    bot.reply_to(message,
                 "Вы можете отправлять запросы в OpenAI API через меня. Просто напишите мне свой запрос и я отправлю его на обработку.")

# создаем обработчик сообщений
@bot.message_handler(func=lambda message: True)
def echo(message):
    # смотрим, есть ли контекст в кэше
    if message.chat.id in context_cache and datetime.now() - context_cache[message.chat.id]['timestamp'] <= CONTEXT_CACHE_INTERVAL:
        context = context_cache[message.chat.id]['message']
    else:
        # если контекста в кэше нет, ищем его в базе данных
        with conn:
            cur = conn.cursor()
            cur.execute("SELECT message FROM context WHERE user_id = ? ORDER BY timestamp DESC LIMIT 1", (str(message.chat.id),))
            row = cur.fetchone()
            context = row[0] if row else ""

    bot.reply_to(message, "Запрос принят в работу.")
    try:
        response = openai.Completion.create(
            engine="text-davinci-003",
            prompt=context + message.text,
            max_tokens=3500
        )
        bot.reply_to(message, response.choices[0].text)

        # сохраняем контекст в кэше и базе данных
        with conn:
            cur = conn.cursor()
            cur.execute("INSERT INTO context (user_id, message, timestamp) VALUES (?, ?, ?)", (str(message.chat.id), context + message.text, datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
            conn.commit()
        context_cache[message.chat.id] = {'message': context + message.text, 'timestamp': datetime.now()}

    except Exception as e:
        bot.reply_to(message, f"Произошла ошибка при обработке вашего запроса: {str(e)}")

# запускаем телеграм бота
bot.polling()
```

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

Вопрос про дождь с контекстом

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-bf776d8a7f1321534957d346de91dacb1d3d3486%2Ff93a040084954cc78abfaffe9100008b.jpg?alt=media)

К сожалению, такое решение может иметь побочный эффект. Если вы ведете продолжительную беседу на разные темы, то есть риск, что бот может ответить на все ваши предыдущие вопросы, даже если последнее сообщение не имело к ним никакого отношения. Хотя этот эффект не всегда наблюдается.

Побочный эффект от костылей

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5adca56f0e19cc0d31e77d301e9368e1de9dd268%2F1593f416c5acb28d110393396bea33d3.jpg?alt=media)

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

```
response = openai.Completion.create(
            engine="text-davinci-003",
            prompt=context + message.text,
            max_tokens=3500
        )

```

Мы продолжаем вести непринужденную беседу с ботом, как вдруг получаем ошибку: `Произошла ошибка при обработке вашего запроса: This model's maximum context length is 4097 tokens, however you requested 4654 tokens (1154 in your prompt; 3500 for the completion). Please reduce your prompt; or completion length.`

Это происходит из-за ограничения на количество символов в запросе, которое установлено в API OpenAI. А мы еще тут контекст копим в кэше. Поэтому все также можно попросить бота исправить ошибку, и он дает нам такой код:

```
@bot.message_handler(commands=['drop_cache'])
@restricted_access
def drop_cache(message):
    user_id = message.from_user.id

    conn = get_conn()
    cursor = conn.cursor()

    cursor.execute('DELETE FROM context WHERE user_id=?', (user_id,))

    hot_cache.clear()

    conn.commit()
    bot.send_message(user_id, "Cache dropped.")
```

Здесь мы очищаем кеш для конкретного user\_id, чтобы уложиться в ограничения api.

В итоге мы получаем рабочее решение. Его можно дальше улучшать и настраивать. [Исходный код бота доступен здесь](https://github.com/sulsoltanoff/gpt-integrate-telegram).


# Получаем статистику Telegram-канала при помощи api и python или свой tgstat с регистрацией и смс

<https://habr.com/ru/articles/702148/>

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

## Нам понадобится

Пройти небольшой и увлекательный путь с TDLib.

* [Залогиниться](https://core.telegram.org/api/obtaining_api_id) на <https://my.telegram.org>. Перейти в "API development tools" и заполнить форму (три поля: название приложение, платформа и описание)
* Получить **api\_id** and **api\_hash**, они нужны для авторизации.
* Почитать страшные предупреждения, что за использование api для флуда и прочих накруток ваш номер забанят навсегда.

Итак, TDLib, кроссплатформенная, [работает со всеми языками](https://core.telegram.org/tdlib/docs/) (питон тоже), написана на Си. С установкой библиотеки любезно помогает сам Телеграм [по ссылке](https://tdlib.github.io/td/build.html), можно выбрать язык, систему, и все команды вам напишут.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-60539efe2203f956c7d55cc1fb5ff31be3a4229d%2F33ec2ad6deeb119b0c3d794f5dd07fbe.png?alt=media)

Тут же предлагают рассмотреть решения от третьих лиц и стоит ссылка на конкретно [alexander-akhmetov/python-telegram](https://github.com/alexander-akhmetov/python-telegram). Он, так он.

## Сразу получаем статистику (ну, почти)

Импортируем всё нужное и логинемся по инструкции

```
import json
from telegram.client import Telegram
import plotly.graph_objects as go

tg = Telegram(
    api_id='api_id',
    api_hash='api_hash',
    phone='+31611111111',  # you can pass 'bot_token' instead
    database_encryption_key='changekey123',
)
tg.login()
# if this is the first run, library needs to preload all chats
# otherwise the message will not be sent
result = tg.get_chats()
result.wait()
```

После выполнения 11 строчки, Tелеграм пришлёт код, который надо ввести. Потом нужно получить все чаты (14-15), а то чуда не произойдёт.

Дальше всё очень просто, библиотека располагает прекрасной функцией call\_method, которая вызывает всё что нужно из TDLib, а нужно нам удостовериться, что группе доступна статистика.

```
params = {
    'supergroup_id': 12324890 #id группы (без -100)
}

result = tg.call_method('getSupergroupFullInfo', params, block=True)
if result.update['can_get_statistics']:
    print('Можно продолжать')
else:
    print("что-то пошло не так")
```

В библиотеке есть собственная функция по получению информации о группе, tg.get\_supergroup\_full\_info(-100231243245), но если что-то идёт не так, возвращается None и сложно понять в чём дело, при вызове tg.call\_method('getSupergroupFullInfo', params, block=True), можно указать block=True, и ошибка будет показываться.

```
params = {
    'supergroup_id': -10012324890
}

result = tg.call_method('getSupergroupFullInfo', params, block=True)
>>Telegram error: {'@type': 'error', 'code': 400, 'message': 'Supergroup not found', '@extra': {'request_id': 'fd88892cac814b4c834973d80004d09a'}}

```

В этом случае пишет, что нет такой группы.

В общем, если есть заветный флажок can\_get\_statistics==True, можем наконец, переходить к главному, [вызову метода getChatStatistics](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1get_chat_statistics.html). Всего два параметра, айди чата, и темная или светлая тема.

```
params = {
    'chat_id': -10012324890, #тут надо -100
    'is_dark': True
}
stat_resp = tg.call_method('getChatStatistics', params, block=True)
stat = stat_resp.update
```

В ответ получаем json [со всей статистикой](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1chat_statistics_supergroup.html) и наслаждаемся результатом.

* Количество подписчиков в динамике
* Подписалось/отписалось
* Включены уведомления
* Просмотры по часам
* Источники просмотров
* Активность
* Новые посты
* [и тд](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1chat_statistics_supergroup.html)

## Немного визуализации

Например, можно результат визуализировать при помощи plotly

```
# загружаем данные диаграммы в json
member_count_graph=json.loads(stat['member_count_graph']['json_data'])

# переводим unix timestamp в обычное время
data_x = [
    datetime.fromtimestamp(x / 1000).strftime("%m.%d")
    for x in graph["columns"][0][1:]
]

#создаём визуализацию пользователей
fig = go.Figure()
fig.add_trace(go.Scatter(x=data_x, y=member_count_graph['columns'][1]))

fig.show()
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-4ea25715ddfe570c644532b22abedfff7a453159%2F186d694c14b431774b4658db7b7c9507.png?alt=media)

Всем спасибо, надеюсь, будет полезно. В производство пока это всё не запускалось, поэтому насколько стабильно и уверенно всё работает, сказать не могу.

[Ноутбук с примером всего](https://github.com/Sstoryteller2/getting-statistic-of-Telegram-Channals).

* [Список классов TDLib Class Index](https://core.telegram.org/tdlib/docs/classes.html)
* [Telegram Database Library](https://core.telegram.org/tdlib)
* [Установка TDLib](https://tdlib.github.io/td/build.html)
* [python-telegram](https://python-telegram.readthedocs.io/en/0.16.0/index.html)


# Как хостить телеграм-бота (и другие скрипты на Python) на Repl.it бесплатно 24/7

<https://habr.com/ru/articles/709314/>

Очень часто возникающий вопрос: где можно разместить скрипты на Python, Flask-приложение, телеграм или дискорд ботов?

Один из вариантов — на своем компьютере при наличии внешнего IP-адреса и опыта в настройке проброса портов на роутере. Или другие сервисы, как правило, требующие платной подписки.

Цель этот статьи - подробная инструкция, как сделать хостинг Python-скриптов бесплатно и доступным 24/7 на примере телеграм-бота

## Шаг 0 - регистрация бота

Существует огромное количество туториалов, как получить токен, поэтому все по-простому. Находим в телеграм BotFather, регистрируем нового бота, выбираем ему имя, получаем токен вида: 127466748171:HJfwijfw88jf32lc9FHjwpfkfgwerhjf

Он нам понадобится в дальнейшем

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5251c462f52e9ee3ccfe416d0dafd7451ecb0b9a%2F42485cb2aad09bfd1a723b95169028e1.png?alt=media)

## Шаг 1 - регистрируемся на Repl.it

Создаем новый проект на Python

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-00b576478b67e563499de4af52c3b06edda0a999%2Fec489e9dd25e3bc86b657198b83c2f8a.png?alt=media)

## Шаг 2 - Пишем код бота

В проекте будет создан файл main.py. В нем размещаем код бота:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-62ac4a8c2e1ffa99fc3389decd0209363dc73031%2F7e0eab89c308243bae7e7e56be2d7054.png?alt=media)

Тут стоит обратить на установку модуля pytelegrambotapi: импортируем сначала `pip` и потом выполняем его через: `pip.main(['install', 'pytelegrambotapi']).`

В этом случае при запуске никаких дополнительных действий для установки не потребуется

```
import os
from background import keep_alive #импорт функции для поддержки работоспособности
import pip
pip.main(['install', 'pytelegrambotapi'])
import telebot
import time

bot = telebot.TeleBot('СЮДА ВСТАВЬТЕ ВАШ ТОКЕН')

@bot.message_handler(content_types=['text'])
def get_text_message(message):
  bot.send_message(message.from_user.id,message.text)
# echo-функция, которая отвечает на любое текстовое сообщение таким же текстом

keep_alive()#запускаем flask-сервер в отдельном потоке. Подробнее ниже...
bot.polling(non_stop=True, interval=0) #запуск бота
```

## Шаг 3 - Создаем Flask-сервер

Создаем в проекте еще один файл `background.py` В нем будет запущен Flask-сервер, который будет принимать запросы от сервиса мониторинга и использоваться для поддержания работоспособности скрипта на ReplIt.

Flask - модуль на python для разработки веб-приложений. Мы создадим "шаблон" сервера, в котором только одна страница, необходимая для нашей задачи.

Все дело в том, что в бесплатном режиме запущенный скрипт на Replit будет остановлен спустя некоторое время (10-30 мин) после закрытия вкладки браузера.

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

```
from flask import Flask
from flask import request
from threading import Thread
import time
import requests

app = Flask('')

@app.route('/')
def home():
  return "I'm alive"

def run():
  app.run(host='0.0.0.0', port=80)

def keep_alive():
  t = Thread(target=run)
  t.start()

```

Важно, что сервер запускается в файле не напрямую, а в отдельном потоке `t = Thread(target=run).` Это обеспечит возможность одновременной работы Flask-сервера и телеграм-бота.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3c89dabb48ddee66eadf4c98b2f9c7ed479798df%2Fd15b2e96e74332235079b7383bf90a04.png?alt=media)

Запуск Flask-сервера

После запуска в верхнем правом углу появилась ссылка **(она потребуется чуть позже)** по которой можно увидеть результат работы Flask-сервера (в нашем случае сообщение I'm alive).

На этом этапе у нас работает эхо-телеграм-бот и веб-сервер, доступный из вне по адресу вида: \_**YOUR\_REPL.your\_nickname.repl.co**\_Однако, спустя 10-30 минут после закрытия вкладки браузера скрипт будет остановлен. Вся хитрость в том, что если "кто-то" будет периодически открывать ссылку, ведущую на страницу нашего веб сервера скрипты будут продолжать работать бесконечно долго.

## Шаг 4 - настраиваем службу мониторинга

Для того, чтобы скрипт работал постоянно, воспользуемся сервисом [UpTimerRobot](https://uptimerobot.com/). Он будет раз в 5 минут создавать запрос к нашему web-серверу и продлевать время его работы. Регистрация не представляет трудности, поэтому перейдем к следующему этапу.

После входа в личный кабинет, создаем новый монитор

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6e3c827a1c92b8276b00848708ea04399b141c90%2F88cc6eb8ed9dd8fe318d40a3a008f748.png?alt=media)

Создание монитора в UpTimerRobot

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-13beab120161cb3d025db0543afaa8f47810ded8%2F6541ff05fb5f79e4abb37bf564d2aa45.png?alt=media)

Сохраняем монитор и возвращаемся в ReplIt. В консоле сервера видим входящие обращения от службы мониторинга

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b529b8e4730072647147b3ce8614a114da21cb59%2F5444eccbd6ff777cd012e97939405847.png?alt=media)

Это значит, что все получилось и наш скрипт будет работать 24/7. Можно работать над ним и развивать проект!

Такие дела! Успехов!


# Создание Telegram бота на PHP #1: основные понятия для работы с API

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

В первом уроке мы с вами рассмотрим основные понятия связанные с API. Я вам расскажу что такое API методы, хуки, покажу на примере Telegram построение URL для создания запросов и расскажу о том как создаются простые API запросы на PHP.

Полный список всех записей курса находится [на сайте](https://prog-time.ru/course_cat/telegram-bot-basic/) или [в публикациях на Хабр](https://habr.com/ru/users/Prog-Time/posts/).

Для отправки и получения запросов через API, вам лучше использовать виртуальный хостинг, так как локальный хостинг не сможет получать данные через хуки.

#### Основные понятия

Давайте рассмотрим основные понятия для работы с API.

**API (Application Programming Interface)** — это набор способов и правил, по которым различные программы общаются между собой и обмениваются данными.

**Метод API** — это определённое действие, которое должно выполнить приложение основываясь на полученных данных (отправить сообщение, вернуть список чатов, отправить картинку и т.д.)

**Token (токен)** — это уникальный ключ бота, необходимый для отправки запросов.

#### Как отправлять HTTP запросы на PHP

Для отправки HTTP запросов можно использовать функцию **file\_get\_contents()**, где в качестве первого главного параметра указывается ссылка. Данная функция отлично подходит для отправки GET запросов, но к сожалению с помощью функции **file\_get\_contents()** нельзя отправлять POST запросы и поэтому для отправки POST запросов мы будем использовать библиотеку Curl.

**Curl** — это библиотека предназначенная для получения и передачи данных через такие протоколы, как HTTP, FTP, HTTPS.

Подробнее о Curl вы можете почитать [на моём сайте](https://prog-time.ru/parsing-php-ottachivaem-curl/).

#### Виды взаимодействия с приложением через API

Существует 2 вида взаимодействия с приложением через API. Первое это от **клиента к серверу**, а второе от **сервера к клиенту**. **Клиентом** в данном случае является ваше приложение (сайт), а в качестве **сервера** выступает сайт на который вы отправляете запросы (в нашем случае, это Telegram).

**API запрос** — это способ общения с программой, по средствам отправки данных от **клиента** — **серверу**.

**Hooks (Хуки)** — это способ общения с программой, по средствам отправки данных от **сервера** — **клиенту**. То есть при определённых изменениях в программе, сервер (приложение) будет отправлять данные на указанный скрипта клиента.

#### Документация для работы с API Telegram

Все методы и параметры для запросов вы можете найти в официальной документации Telegram.

Telegram Bot API — <https://core.tlgr.org/bots/api>

К данному сайту мы будем ссылаться на протяжение всего курса.

#### Работа с документацией для Telegram

Документация для создания Telegram ботов разделена на несколько разделов.

В разделе **Recent changes** вы можете найти информацию об обновлениях Telegram. Здесь описаны версии и нововведения которые были внесены в функционал мессенджера.

Разделы **Authorizing your bot** и **Making requests** описывают способы авторизации ботов и способы создания запросов для работы с ботами.

Раздел **Getting updates** описывает способы получения обновлений взаимодействия с ботами. При взаимодействие пользователя с ботов, все его действия, по стандарту, записываются на сервера Telegram, и для того чтобы получить к ним доступ, необходимо отправить запрос **getUpdates**.

Отправив запрос **getUpdates** вы можете получить id последнего пользователя который написал боту, узнать его ник, текст сообщения и дату отправки. Если бот добавлен в сообщество, то вы можете получить id сообщества.

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

Следующий раздел, который нас интересует называется — **Available types**. Данный раздел описывает все типы данных которые возвращает нам Telegram. Когда ваш скрипт отправляет запрос, то обработав его, Telegram вернёт вам ответ в формате JSON строки, в котором описаны специальные параметры.

Например если вы отправляете сообщение, то Telegram вернёт вам массив в котором указаны id созданного сообщения, id пользователя, дата создания сообщения и много другое. Все эти данные вы можете разобрать и записать в базу данных.

Далее описан раздел, с которым нам придётся работать больше всего — это **Available methods**, методы для взаимодействия с ботом. Советую вам пройтись по всем методам и изучить все возможности работы с ботами.

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

Ну и в конце у нас описаны методы для работы со стикерами, играми в Telegram, методы для работы с оплатой в Telegram.

#### Структура URL для отправки запросов в Telegram

API Telegram имеет простую и понятную структуру урлов для отправки запросов.

Вот пример URL для создания запросов к боту:

```
https://api.telegram.org/bot{token}/{method}
```

**{token}** — это уникальный ключ, который выдаётся при создание бота;

**{method}** — это метод запроса по которому мы будем получать или отправлять определённые данные. В зависимости от названия метода, мы будем выполнять разные действия.

#### Примеры URL для запросов

Данные примеры используются только для наглядности построения URL, токен указанный в URL не привязан ни к одному боту!

Вот так выглядит отправка сообщений методом GET. Первая часть URL содержит домен **api.telegram.org**, далее прописываем строку bot с токеном который нам даётся при создание бота, после чего указываем метод **sendMessage** и перечисляем GET параметры.

```
https://api.telegram.org/bot546445612928:AAHjk6643OYgWHim_TICgsaF9NDDVXYnKzA/sendMessage?chat_id=<ID чата>&text=<text>
```

Отправка файлов в чат выглядит аналогично, только метод **sendMessage** заменяется на **sendDocument**. И здесь не перечисляются GET параметры, после указания метода, так как мы отправляем данные методом POST.

```
https://api.telegram.org/bot543264456928:AAHjk6643OYgWHim_TICgsaF9NDDVXYnKzA/sendDocument
```

Отправка изображений в чат:

```
https://api.telegram.org/bot546413456928:AAHjk6643OYgWHim_TICgsaF9NDDVXYnKzA/sendPhoto
```

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

Второй урок уже на Хабре - <https://habr.com/ru/post/697000/>


# Создание Telegram бота на PHP #2: создание первого бота для Telegram

Во втором уроке я вам покажу как создать бота для Telegram и мы попробуем отправить несколько сообщений в чат.

Полный список всех записей курса находится на сайте <https://prog-time.ru/course_cat/telegram-bot-basic/> или в публикациях на Хабр <https://habr.com/ru/users/Prog-Time/posts/>

Для того чтобы создать бота, нам необходимо сделать несколько последовательных действий.

1\) Вам нужно авторизоваться в Telegram аккаунте

2\) В поиске найти пользователя @BotFather

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/3d3/461/b61/3d3461b61ed65cda1303619f332cf0bf.jpeg" alt="" height="371" width="1024"><figcaption></figcaption></figure>

3\) Отправить сообщение боту — `/newbot`

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/f09/549/9e3/f095499e3368c739dbe6d88eeb5e5031.png" alt="" height="107" width="1024"><figcaption></figcaption></figure>

4\) После отправки запроса , нужно указать имя бота

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/987/f51/8f4/987f518f451dd20abdf8879abd7fc783.png" alt="" height="100" width="584"><figcaption></figcaption></figure>

5\) После этого дублировать название бота, но только суффиксом `_bot`

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/aaf/d34/e23/aafd34e23e7270c08f08fe1f9ce1c231.png" alt="" height="319" width="796"><figcaption></figcaption></figure>

6\) После успешной регистрации бота, [@BotFather](https://t.me/BotFather) пришлёт вам сообщение с токеном, который вам нужно сохранить, в дальнейшем он нам понадобится.

7\) Теперь нам нужно создать чат в который мы добавим нашего бота

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/367/b70/187/367b701871c1821c17be06182556c062.jpeg" alt="" height="672" width="1024"><figcaption></figcaption></figure>

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/20f/53c/229/20f53c2291d4de4c297b7b86f4bf2cb4.png" alt="" height="366" width="1024"><figcaption></figcaption></figure>

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/9eb/5fe/19e/9eb5fe19e00fe133f972ca8a81faea3a.png" alt="" height="404" width="850"><figcaption></figcaption></figure>

8\) Далее нам нужно получить `id` нашего бота. Для этого нужно перейти по следующей ссылке, где вместо символов X нужно подставить ваш токен:\
<https://api.telegram.org/botXXXXXXXXXXXXXXXXXX/getUpdates\\>
Не закрывайте эту страницу, после 9 пункта, её нужно будет обновить.

9\) Теперь вам необходимо отправить команду `/join` в чат для активации бота. После отправки команды, вам нужно обновить страницу, чтобы сделать повторный запрос.

Здесь вам нужно записать следующий фрагмент кода — id вашего бота.

Вам нужен id со знаком минус.

```
"my_chat_member":{"chat":{"id":-594377170, ...
```

#### Пример отправки сообщения боту в Telegram

Теперь давайте попробуем отправить сообщение нашему боту методом GET запроса.

Здесь мы создаём переменные в которые заносим токен, id чата и сообщение. Сообщение мы прогоняем через функцию **urlencode()** для формирования специальный кодировки, для создания запросов.

А в конце мы используем функцию **file\_get\_contents()** для отправки запроса.

```
$token = "5340791844:AAEXXD786InvQrlWHRXykV91USOQSevrPVU";
$chat_id = -594377170;

$textMessage = "Тестовое сообщение";
$textMessage = urlencode($textMessage);

$urlQuery = "https://api.telegram.org/bot". $token ."/sendMessage?chat_id=". $chat_id ."&text=" . $textMessage;

$result = file_get_contents($urlQuery);
```

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

Информацию по использованию данной библиотеке, вы можете получить в следующей записи — <https://prog-time.ru/parsing-php-biblioteka-curl/>

Давайте посмотрим код для запросов, с использованием Curl.

```
$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
     "chat_id" 	=> 1424625511,
     "text"  	=> "Новое сообщение из формы",
     "parse_mode" => "html",
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/sendMessage?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;
```

Теперь мы получили более удобочитаемый код, благодаря записи параметров в массив **$getQuery**. При такой структуре, вам не нужно переписывать URL запроса, изменения вносятся только в массив **$getQuery**, а функция **http\_build\_query()** сама добавит строку параметров в URL запроса.

В дальнейших уроках, мы будем пользоваться библиотекой Curl, но вы должны понимать что многие запросы можно отправлять через функцию file\_get\_contents. Нужно просто составить правильный URL.

У нас получилось отправить сообщение в Telegram с помощью нашего бота. Теперь давайте посмотрим на ответы которые отправляет нам Telegram.

#### Разбор ответа от Telegram.

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

Если вы допустили ошибку, то вам придёт сообщение с параметрами, в которых указан код ошибки с описанием.

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

Сейчас давайте попробуем сделать запрос на отправку сообщения и разобрать полученный ответ.

```
{
  "ok": true,
  "result": {
    "message_id": 12,
    "from": {
      "id": 5340791844,
      "is_bot": true,
      "first_name": "test_prog_time",
      "username": "test_prog_time_bot"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1658907913,
    "text": "Новое сообщение из формы"
  }
}
```

В ответе мы видим следующее:

* Параметр «ok» — описывает успешность отправки запроса
* «result» — возвращает массив с данными ответа, в которых:
  * «message\_id» — id созданного сообщения
  * «from» — кто отправил сообщение
  * «chat» — данные о чате в который попало сообщение
  * «date» — дата создания сообщения
  * «text» — текст сообщения

Подведём итог.

* Все боты для Telegram создаются через **BotFather**
* Для отправки запросов вы можете использовать функцию file\_get\_contents или воспользоваться библиотекой Curl
* Каждый запрос в Telegram возвращает ответ с описание результата запроса.

Третий урок уже на Хабр - <https://habr.com/ru/post/697002/>


# Создание Telegram бота на PHP #3: примеры отправки сообщений с кнопками в Telegram

В новом уроке мы с вами рассмотрим отправку базовых запросов в Telegram. Я покажу вам как отправлять простые текстовые сообщения в Telegram, как отправлять кнопки и дополнительные клавиатуры.

Всю информацию по параметрам запросов мы будем брать из [официальной документации Telegram](https://core.telegram.org/bots/api/).

Полный список всех записей курса находится [на сайте](https://prog-time.ru/course_cat/telegram-bot-basic/) или [в публикациях на Хабр](https://habr.com/ru/users/Prog-Time/posts/).

Все ответы от Telegram приходят в виде JSON строки. Для удобного отображения массива ответа в браузере, советую вам установить специальное расширение для браузера, которое называется **JSON Viewer**.

#### Отправка простых сообщений

Для отправки простых текстовых сообщений, нам необходимо воспользоваться методом **sendMessage**.

Ранее я показывал вам, как отправлять запросы с передачей параметров в URL, теперь для удобства я буду использовать запись параметров в массиве и с помощью функции **http\_build\_query** мы будем формировать строку с GET параметрами.

```
$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
    "chat_id" 	=> 1424625511,
    "text"  	=> "Новое сообщение из формы",
    "parse_mode" => "html"
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/sendMessage?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;
```

Каждый запрос будет иметь переменную с записанным токеном, массив с параметрами запроса, код для создания запроса через Curl и вывод или запись полученной информации.

#### Отправка ответа на сообщение

Для отправки ответа на ранее созданное сообщения, вам необходимо в новом запросе **sendMessage** отправить дополнительный параметр **reply\_to\_message\_id**, передав в него id сообщения, которое вы хотите прикрепить.

Полный запрос будет выглядеть так…

```
$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
     "chat_id" 	=> 1424625511,
     "text"  	=> "Новое сообщение из формы",
     "parse_mode" => "html",
     "reply_to_message_id" => 7
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/sendMessage?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;
```

#### Удаление сообщений из чата

Для удаления сообщений, вам нужно воспользоваться методом **deleteMessage** и знать id сообщения которое вы хотите удалить.

Пример кода для удаления сообщений выглядит так:

```
$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
    "chat_id" 	=> 1424625511,
    "message_id"  => 32456,
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/deleteMessage?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;
```

#### Отправка кнопок в чат

На данный момент, существует 3 вида кнопок в чате, в Telegram.

1. Кнопки которые прикреплены к сообщению (**inline\_keyboard**).
2. Кнопки которые располагаются под строкой ввода сообщения, они называются клавиатурой (**keyboard**).
3. Кнопки меню команд, которые чаще всего располагаются слева от строки ввода сообщения.

Для начала давайте рассмотрим как нам добавить кнопки которые будут прикреплены к сообщению.

Для отправки таких кнопок, нам нужно воспользоваться методом **sendMessage** и передать ему в качестве параметра **reply\_markup** — массив со свойствами клавиатуры.

Данный массив выглядит следующим образом…

```
...
'reply_markup' => json_encode(array(
    'inline_keyboard' => array(
	    array(
	        array(
		        'text' => 'Button 1',
		        'callback_data' => 'test_2',
	        ),

            array(
		        'text' => 'Button 2',
		        'callback_data' => 'test_2',
	        ),
	    )
    ),
)),
...
```

Разберём всё по порядку.

Первое важное правило - **reply\_markup** принимает json, поэтому для создания кнопок, вам нужно конвертировать массив в JSON с помощью функции **json\_encode**.

В массиве с параметрами кнопок, есть особые параметры. Эти параметры, так же, указаны в документации.

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/d21/345/cd4/d21345cd4a2d77886b8ca780e45701c0.png" alt="" height="303" width="832"><figcaption></figcaption></figure>

* С помощью параметра **text** вы можете передать текст кнопки.
* параметр **url** указывает ссылку, если вам нужно сделать кнопку для перехода на внешний ресурс.
* параметр **callback\_data** указывает строку которая будет возвращена после нажатия на кнопку. Данную строку используют как команду.

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

Таким образом, для создания 2 кнопок в одном ряду, мы будем использовать следующий код

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/484/26c/79c/48426c79cb0199b89f278fe9316e0249.png" alt="" height="82" width="262"><figcaption></figcaption></figure>

```
...
'reply_markup' => json_encode(array(
    'inline_keyboard' => array(
	    array(
	        array(
		        'text' => 'Button 1',
		        'callback_data' => 'test_2',
	        ),

            array(
		        'text' => 'Button 2',
		        'callback_data' => 'test_2',
	        ),
	    )
    ),
)),
...
```

Для создания 2 рядов по 2 кнопки используйте код ...

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/efa/b73/de3/efab73de34369a28763e286486ea33d8.png" alt="" height="123" width="274"><figcaption></figcaption></figure>

```
...
'reply_markup' => json_encode(array(
    'inline_keyboard' => array(
	     array(
	        array(
  		        'text' => 'Button 1',
		        'callback_data' => 'test_2',
	        ),

            array(
		        'text' => 'Button 2',
		        'callback_data' => 'test_2',
	        ),
	    ),
        array(
	        array(
		        'text' => 'Button 3',
		        'callback_data' => 'test_3',
	        ),

            array(
		        'text' => 'Button 4',
		        'callback_data' => 'test_4',
	        ),
	    )
    ),
)),
...
```

И для создания одной кнопки в первом ряду и 2 — во втором, используйте следующий код.

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/06c/dc0/f19/06cdc0f19716054dc3d913ebb8f0cd73.png" alt="" height="130" width="318"><figcaption></figcaption></figure>

```
...
'reply_markup' => json_encode(array(
    'inline_keyboard' => array(
	    array(
            array(
		        'text' => 'Button 2',
		        'callback_data' => 'test_2',
	        ),
	    ),
        array(
	        array(
		        'text' => 'Button 3',
		        'callback_data' => 'test_3',
	        ),

            array(
		        'text' => 'Button 4',
		        'callback_data' => 'test_4',
	        ),
	    )
    ),
)),
...
```

Надеюсь, я смог объяснить данную тему доступно, если у вас будут вопросы, пишите их в нашем Telegram канале.

#### Отправка клавиатуры в чат

Аналогичные параметры имеет и массив для отправки клавиатуры в чат. Для создания клавиатуры пропишем следующий код.

```
...
'reply_markup' => json_encode(array(
    'keyboard' => array(
        array(
	        array(
		        'text' => 'Тестовая кнопка 1',
		        'url' => 'YOUR BUTTON URL',
	        ),
	        array(
		        'text' => 'Тестовая кнопка 2',
		        'url' => 'YOUR BUTTON URL',
	        ),
        )
    ),
    'one_time_keyboard' => TRUE,
    'resize_keyboard' => TRUE,
)),
...
```

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

Ключ inline\_keyboard заменяется на keyboard.

А так же для клавиатуры добавляются 2 дополнительных параметра:

* **one\_time\_keyboard** — скрыть клавиатуру, как только она была использована. Клавиатура по-прежнему будет доступна, но клиенты будут автоматически отображать обычную, буквенную клавиатуру в чате — пользователь может нажать специальную кнопку в поле ввода, чтобы снова увидеть пользовательскую клавиатуру. Значение по умолчанию равно false.
* **resize\_keyboard** — изменяет размер клавиатуры по вертикали для оптимальной подгонки (например, уменьшить клавиатуру, если есть только два ряда кнопок). По умолчанию установлено значение false, и в этом случае пользовательская клавиатура всегда имеет ту же высоту, что и стандартная клавиатура приложения.

Подведём итоги!

* В новом уроке мы с вами разобрали самый популярный метод для работы с Телеграм ботами — **sendMessage**. Данный метод позволяет отправлять текстовые сообщения с привязанными кнопками и клавиатурами.
* Научились удалять сообщения
* Разобрали какие бывают типы кнопок и научились создавать массивы для гибкой структуры вывода дополнительных клавиатур и кнопок.

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

Оригинал статьи [на сайте Prog-Time](https://prog-time.ru/course/bot-v-telegram-3/).

Новый урок уже на Habr - <https://habr.com/ru/post/697010/>


# Создание Telegram бота на PHP #4: отправка файлов и изображений в Telegram

В новом уроке мы с вами научимся отправлять файлы и изображения в Telegram сообщениях. Мы с вами изучим 2 новых метода: **sendPhoto()** и **sendDocument()**.

Для отправки файлов в Телеграм, нам необходимо воспользоваться функцией **curl\_file\_create()**, которая формирует специальный объект файла, для того чтобы его можно было передавать через HTTP запросы.

Полный список всех записей курса находится на сайте <https://prog-time.ru/course_cat/telegram-bot-basic/> или в публикациях на Хабр <https://habr.com/ru/users/Prog-Time/posts/>

#### Отправка изображений в Telegram чат

Пример отправки изображения выглядит так:

```
/*токен который выдаётся при регистрации бота */
$token = "5340791844:AAEXXDdu324vInvQrlWHyk8V91USOQSevrPVU";

$arrayQuery = array(
    'chat_id' => 1424646511,
    'caption' => 'Проверка работы',
    'photo' => curl_file_create(__DIR__ . '/cat.jpg', 'image/jpg' , 'cat.jpg')
);		
$ch = curl_init('https://api.telegram.org/bot'. $token .'/sendPhoto');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$res = curl_exec($ch);
curl_close($ch);
```

Здесь мы как и в прошлый раз собираем в массив **$arrayQuery** параметры для отправки запросов. Для отправки изображения, нам необходимо передать id чата, текст сообщения (для изображений он передается в параметре **caption**), и новый параметр **photo** в который мы передаём сформированный, с помощью функции **curl\_file\_create()**, объект изображения.

Ниже мы указываем что все данные должны передаваться методом POST и не забываем передавать токен в URL запроса.

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

<figure><img src="https://habrastorage.org/r/w780q1/getpro/habr/upload_files/5fc/a4a/5ca/5fca4a5caa6030b27300d479b25bc002.jpeg" alt="" height="485" width="594"><figcaption></figcaption></figure>

Давайте рассмотрим дополнительные параметры, которые предлагает нам документация Telegram.

**protect\_content** — данный параметр запрещает сохранение и пересылку изображения.

<figure><img src="https://habrastorage.org/r/w780q1/getpro/habr/upload_files/db7/8f0/2ff/db78f02ff6bbd77db8604e72cfb1ffcb.jpeg" alt="" height="468" width="627"><figcaption></figcaption></figure>

**reply\_markup** — позволяет добавить кнопки под изображение

#### Отправка файлов в Telegram чат

Отправка документов производится аналогичным образом, меняется только метод отправки и параметр **photo** заменяется на **document**.

```
/*токен который выдаётся при регистрации бота */
$token = "5340791844:AAEXXDdu324vInvQrlWHyk8V91USOQSevrPVU";

$arrayQuery = array(
    'chat_id' => 1424646511,
    'caption' => 'Проверка работы',
    'document' => curl_file_create(__DIR__ . '/cat.jpg', 'image/jpg' , 'cat.jpg')
);		
$ch = curl_init('https://api.telegram.org/bot'. $token .'/sendDocument');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$res = curl_exec($ch);
curl_close($ch);
```

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/643/356/8c6/6433568c687a003fe26a793a29c6f8b4.png" alt="" height="126" width="525"><figcaption></figcaption></figure>

#### Разбор ответа при отправке файла

Давайте теперь разберём ответ получаемый от сервера при отправке файла в чат.

В данном примере я получаю следующий ответ:

```
{
  "ok": true,
  "result": {
    "message_id": 20,
    "from": {
      "id": 5340791844,
      "is_bot": true,
      "first_name": "test_prog_time",
      "username": "test_prog_time_bot"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1658991191,
    "document": {
      "file_name": "cat.jpg",
      "mime_type": "image/jpeg",
      "thumb": {
        "file_id": "AAMCAgADGQMAAxRi4jJXqhVVPzULdQ1xw_LeYcZGRwACGhkAAmCwEEuw8OvQNNsHDQEAB20AAykE",
        "file_unique_id": "AQADGhkAAmCwEEty",
        "file_size": 24268,
        "width": 320,
        "height": 320
      },
      "file_id": "BQACAgIAAxkDAAMUYuIyV6oVVT81C3UNccPy3mHGRkcAAhoZAAJgsBBLsPDr0DTbBw0pBA",
      "file_unique_id": "AgADGhkAAmCwEEs",
      "file_size": 132208
    },
    "caption": "Проверка работы"
  }
}
```

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

Новым параметром для нас, в данном случае является — **document**, в котором указываются данные об отправленном файле.

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

ID отправленного файла хранится в массиве ответа, в параметре **document -> file\_id.**

Выглядит это следующим образом

```
$arrayQuery = array(
    'chat_id' => 1424646511,
    'caption' => 'Проверка работы',
    'document' => "BQACAgIAAxkDAAMUYuIyV6oVVT81C3UNccPy3mHGRkcAAhoZAAJgsBBLsPDr0DTbBw0pBA",
);		
$ch = curl_init('https://api.telegram.org/bot'. $token .'/sendDocument');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$res = curl_exec($ch);
curl_close($ch);
```

#### Групповая отправка изображений и файлов

Для групповой отправки изображений в чат, нам необходимо воспользоваться методом **sendMediaGroup()** и немного переделать наш массив с параметрами запроса.

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

```
/*токен который выдаётся при регистрации бота */
$token = "5340791844:AAEXXDduvInvQrlWHRXykV91USOQSevrPVU";

$arrayQuery = [
    'chat_id' => 1424646511,
    'media' => json_encode([
	    ['type' => 'photo', 'media' => 'attach://cat.jpg' ],
	    ['type' => 'photo', 'media' => 'attach://cat_2.jpg' ],
	    ['type' => 'photo', 'media' => 'attach://cat_3.jpg' ],
    ]),
    'cat.jpg' => new CURLFile(__DIR__ . '/cat.jpg'),
    'cat_2.jpg' => new CURLFile(__DIR__ . '/cat_2.jpg'),
    'cat_3.jpg' => new CURLFile(__DIR__ . '/cat_3.jpg'),
];


$ch = curl_init('https://api.telegram.org/bot'. $token .'/sendMediaGroup');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$res = curl_exec($ch);
curl_close($ch);

echo $res;
```

Для передачи группы файлов, нам необходимо передать в качестве параметра **media** массив с параметрами изображений которые необходимо сгруппировать.

Каждый массив вложенный в параметр media имеет следующие параметры:

* **type** — тип файла который необходимо передать (в нашем случае это photo)
* **media** — строка указывающая вложенный файл. Добавление подстроки **attach://** является обязательным правилом.

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

Для формирования объекта изображений мы будем использовать аналог функции **curl\_file\_create()** — класс **CURLFile()**, который просто принимает путь до изображения.

После отправки запроса, мы получаем следующий результат.

<figure><img src="https://habrastorage.org/r/w780q1/getpro/habr/upload_files/f05/2e6/00c/f052e600ccc8cba2ccc9163fd34a206b.jpeg" alt="" height="453" width="576"><figcaption></figcaption></figure>

Подведём итоги. В новом уроке мы с вами научились:

* работать с функцией **curl\_file\_create()** и классом **CURLFile()**
* отправлять документы в Telegram чат
* отправлять сжатые изображения в Telegram
* отправлять сгруппированные изображения в одном сообщение

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

Оригинал на сайте Prog-Time - <https://prog-time.ru/course/bot-v-telegram-4/>


# Создание Telegram бота на PHP #5: работа с хуками

В новом уроке мы с вами поговорим о настройке хуков и напишем свой первый обработчик команд.

В первом уроке я вам рассказывал что такое хуки, давайте повторим:

**Hooks (Хуки)** — это способ общения с программой, по средствам отправки данных от **сервера** — **клиенту**. То есть при определённых изменениях в программе, сервер (приложение) будет отправлять данные на указанный URL скрипта клиента.

Например. Каждый раз когда пользователи будут писать сообщения боту, данные о сообщениях будут отправляться на указанный скрипт, где вы сможете записать сообщения в БД или отправить ответ.

Полный список всех записей курса находится на сайте <https://prog-time.ru/course_cat/telegram-bot-basic/> или в публикациях на Хабр <https://habr.com/ru/users/Prog-Time/posts/>

Для регистрации хука нужно выполнить 2 правила:

* разместить скрипт на виртуальный сервер (хостинг)
* домен на который будут отправляться запросы, должен иметь SSL сертификат и работать через HTTPS соединение

Если ваш хостинг соответствует данным требованиям, то давайте займёмся регистрацией хука для Telegram бота.

#### Регистрация хука для Telegram бота

Для регистрации хука нам нужно отправить запрос с методом **setWebhook()**, которому в качестве параметра **url** мы должны передать ссылку на скрипт обработчик. В моём случае это просто php скрипт.

Вот пример запроса:

```
$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
     "url" => "https://prog-time.ru/tg_script/index.php",
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/setWebhook?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);

$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;
```

Если запрос прошёл успешно, то вы получите следующий ответ:

```
{
  "ok": true,
  "result": true,
  "description": "Webhook was set"
}
```

Теперь давайте проверим работу нашего обработчика. Сообщения приходят POST-запросом, с типом `application/json`. Получить его в PHP можно следующим образом:

```
$data = file_get_contents('php://input');
$data = json_decode($data, true);
```

#### Разбор параметров передаваемых через Hooks

Давайте теперь посмотрим что приходит на наш скрипт при отправке простого текстового сообщения боту.

Здесь есть небольшая проблема! Скрипты будут выполняться в рандомный момент и если мы не запишем данные, то они пропадут в пустоту. Для записи ответа вы можете использовать БД или как я, просто записать массив в файл **txt**.

Для записи строки я буду использовать дополнительную, самописную функцию **writeLogFile()**

Моя функция принимает 2 параметра:

* первый параметр, строка для записи. В нашем случае это JSON строка.
* второй параметр используется для очистки файла и перезаписи. Если данный параметр имеет значение **false**, то в файл дописывается информация.

```
function writeLogFile($string, $clear = false){
    $log_file_name = __DIR__."/message.txt";
    if($clear == false) {
	$now = date("Y-m-d H:i:s");
	file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
    else {
	file_put_contents($log_file_name, '');
        file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
}
```

Полный код для записи информации в файл будет выглядеть следующим образом.

```
function writeLogFile($string, $clear = false){
    $log_file_name = __DIR__."/message.txt";
    if($clear == false) {
		$now = date("Y-m-d H:i:s");
		file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
    else {
		file_put_contents($log_file_name, '');
        file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
}

$data = file_get_contents('php://input');
writeLogFile($data, true);
```

После отправки сообщения боту, данные были отправлены на наш скрипт и мы записали их в лог файл.

<figure><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/c7c/8e5/413/c7c8e541331f4ba1948600abd92eef49.png" alt="" height="46" width="503"><figcaption></figcaption></figure>

Теперь выведем полученную информацию на страницу

```
echo file_get_contents(__DIR__."/message.txt");
```

Вот что мы получаем. Это объект в котором записана информация о созданном сообщение, мы видим данные о пользователе, данные о чате, дата отправления и текст сообщения.

```
{
  "update_id": 803290892,
  "message": {
    "message_id": 41,
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1659098034,
    "text": "Новое тестовое сообщение"
  }
}
```

#### Данные при нажатие на кнопку в чате

Если пользователь нажал на кнопку, то на скрипт также будет отправлен запрос с данными о пользователе и о кнопке.

Отличительной особенностью таких запросов является то что главный ключ **message** заменяется на **callback\_query**, а сам массив message будет находиться внутри.

Получить код кнопки на которую было произведено нажатие, можно из **callback\_query** -> **data**.

```
{
  "update_id": 803290921,
  "callback_query": {
    "id": "6118810175780540321",
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "message": {
      "message_id": 113,
      "from": {
        "id": 5340791844,
        "is_bot": true,
        "first_name": "test_prog_time",
        "username": "test_prog_time_bot"
      },
      "chat": {
        "id": 1424646511,
        "first_name": "Илья",
        "last_name": "Лящук",
        "username": "iliyalyachuk",
        "type": "private"
      },
      "date": 1659335238,
      "text": "Тестовое сообщение",
      "reply_markup": {
        "inline_keyboard": [
          [
            {
              "text": "YOUR BUTTON LABEL TEXT",
              "callback_data": "test_123"
            }
          ]
        ]
      }
    },
    "chat_instance": "4661722712167232747",
    "data": "test_123"
  }
}
```

#### Данные при отправке изображения

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

```
{
  "update_id": 803290893,
  "message": {
    "message_id": 42,
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1659099213,
    "photo": [
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAANzAAMpBA",
        "file_unique_id": "AQADub0xG53kIUt4",
        "file_size": 1863,
        "width": 90,
        "height": 90
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAANtAAMpBA",
        "file_unique_id": "AQADub0xG53kIUty",
        "file_size": 30064,
        "width": 320,
        "height": 320
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN5AAMpBA",
        "file_unique_id": "AQADub0xG53kIUt-",
        "file_size": 133230,
        "width": 880,
        "height": 880
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN4AAMpBA",
        "file_unique_id": "AQADub0xG53kIUt9",
        "file_size": 138716,
        "width": 800,
        "height": 800
      }
    ]
  }
}
```

После получения данного массива мы можем сохранить отправленное изображение на своём сервере. Для этого нам нужно с помощью метода **getFile** получить полный путь к изображению, передав ему в качестве параметра **file\_id**.

Полный код для сохранения будет выглядеть так:

```
/* токен */
$token = "5340791844:AAEXXDduvInvQrlWHRXykV91USOQSevrPVU";

/* массив с параметрами запроса */
$getQuery = array(
    "file_id" => "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN5AAMpBA",
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/getFile?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$resultQuery = curl_exec($ch);
curl_close($ch);

/* записываем ответ в формате PHP массива */
$arrDataResult = json_decode($resultQuery, true);

/* записываем URL необходимого изображения */
$fileUrl = $arrDataResult["result"]["file_path"];

/* формируем полный URL до файла */
$photoPathTG = "https://api.telegram.org/file/bot". $token ."/" . $fileUrl;

/* забираем название файла */
$arrFilePath = explode("/", $fileUrl);
$newFilerPath = __DIR__ . "/img/" . $arrFilePath[1];

/* сохраняем файл на сервер */
file_put_contents($newFilerPath , file_get_contents($photoPathTG));
```

#### Скрипт для ответа на запросы через Хук

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

Поехали…

Токен бота запишем в константу **TG\_TOKEN**

```
define("TG_TOKEN", "5340791844:AAEXXDdu324vInvQrlWHyk8V91USOQSevrPVU");
```

Для удобства я создал специальные функции для отправки типовых запросов на сервер Telegram. Созданные функции принимают в качестве первого аргумента массив с параметрами запроса.

```
/* для отправки текстовых сообщений */
function TG_sendMessage($getQuery) {
    $ch = curl_init("https://api.telegram.org/bot". TG_TOKEN ."/sendMessage?" . http_build_query($getQuery));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}

/* для отправки изображений */
function TG_sendPhoto($arrayQuery) {
    $ch = curl_init('https://api.telegram.org/bot'. TG_TOKEN .'/sendPhoto');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}

/* для получения данных о файле */
function TG_getFile($arrayQuery) {
    $ch = curl_init("https://api.telegram.org/bot". TG_TOKEN ."/getFile?" . http_build_query($arrayQuery));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}
```

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

```
function list_files($path) {
    if ($path[mb_strlen($path) - 1] != '/') {
	$path .= '/';
    }
 
    $files = array();
    $dh = opendir($path);
    while (false !== ($file = readdir($dh))) {
	if ($file != '.' && $file != '..' && !is_dir($path.$file) && $file[0] != '.') {
	    $files[] = $file;
	}
    }
 
    closedir($dh);
    return $files;
}
```

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

В переменные **$textMessage** записывает текст сообщения, а в переменную **$chatId** записываем id чата.

```
$data = file_get_contents('php://input');

$arrDataAnswer = json_decode($data, true);
$textMessage = mb_strtolower($arrDataAnswer["message"]["text"]);
$chatId = $arrDataAnswer["message"]["chat"]["id"];
```

Ниже мы проверяем наличие файла в сообщение. Если пользователь отправил файл, то мы его сохраняем в папку с картинками.

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

```
if(!empty($arrDataAnswer["message"]["photo"])) {
    $documentData = array_pop($arrDataAnswer["message"]["photo"]);
}
else if(!empty($arrDataAnswer["message"]["document"])) {
    $documentData = array_pop($arrDataAnswer["message"]["document"]);
}
```

Далее мы прописываем проверку на текст сообщения и в случае нужного текста отправляем ответное сообщение.

Если пользователь отправил «Привет», то мы в ответ отправляем сообщение «Привет! Есть фото для меня?». Данное сообщение отправляется с помощью ранее созданной функции **TG\_sendMessage()**.

```
if($textMessage == 'привет') {
    $textMessage_bot = "Привет! Есть фото для меня";

    $arrayQuery = array(
	'chat_id' 		=> 1424646511,
	'text'			=> $textMessage_bot,
	'parse_mode'	=> "html",
    );
    TG_sendMessage($arrayQuery);
}
```

Ниже пропишем подобный код для запроса изображения. Если пользователь отправил «хочу фото», то мы выбираем рандомное изображение и отправляем его пользователю с помощью функции **TG\_sendPhoto()**.

```
else if($textMessage == 'хочу фото') {
    $textMessage_bot = "Вот, держи!";

    $listFile = list_files(__DIR__ . "/img/");

    $max = count($listFile) - 1;
    $randIdFile = rand(0, $max);

    $filePath = __DIR__ . "/img/" . $listFile[$randIdFile];

    $arrayQuery = array(
         'chat_id' => $chatId,
	  "photo" => new CURLFile($filePath),
	  "caption" => "Вот твоё фото!"
    );
    TG_sendPhoto($arrayQuery);

} 
```

Далее пропишем код для сохранения любых, отправленных в чат, изображений.

```
if(!empty($documentData)) {

    $arrayQuery = array(
	"file_id" => $documentData["file_id"],
    );
    $resultQuery = TG_getFile($arrayQuery);

    /* записываем ответ в формате PHP массива */
    $arrDataResult = json_decode($resultQuery, true);

    /* записываем URL необходимого изображения */
    $fileUrl = $arrDataResult["result"]["file_path"];

    /* формируем полный URL до файла */
    $photoPathTG = "https://api.telegram.org/file/bot". TG_TOKEN ."/" . $fileUrl;

    /* забираем название файла */
    $arrFilePath = explode("/", $fileUrl);
    $newFilerPath = __DIR__ . "/img/" . $arrFilePath[1];

    /* сохраняем файл на сервер */
    file_put_contents($newFilerPath , file_get_contents($photoPathTG));

    $arrayQuery = array(
	'chat_id' => 1424646511,
	'text' => "Отличное фото! Я его, пожалуй, сохраню",
	'parse_mode' => "html",
    );
    TG_sendMessage($arrayQuery);

}
```

Ну и на последок, давайте пропишем ещё 2 условия. Первое условие будет отправлять кнопку в чат, а второе условие будет проверять нажатие на кнопку и отправлять дополнительное сообщение.

Запрос для отправки кнопок создаём аналогично запросу со словом «Привет». По запросу мы будем отправлять 2 кнопки с callback\_data — **but\_1** и **but\_2**.

```
if($textMessage == 'отправь кнопки') {
    $textMessage_bot = "Вот твои кнопки! Нажимай";

    $arrayQuery = array(
	'chat_id' => 1424646511,
	'text'	=> $textMessage_bot,
	'parse_mode'	=> "html",
	'reply_markup' => json_encode(array(
	    'inline_keyboard' => array(
		array(
		    array(
			'text' => 'Кнопка 1',
			'callback_data' => 'but_1',
		    ),

		    array(
			'text' => 'Кнопка 2',
			'callback_data' => 'but_2',
		    )
		),
	    ),
	)),
    );  
    TG_sendMessage($arrayQuery);
}
```

Теперь давайте пропишем проверку нажатия на кнопки. Здесь нам нужно записать в переменную **$dataBut** код нашей кнопки, чтобы по нему в дальнейшем делать проверку. В переменную **$textMessage** и **$chatId** мы так же записываем текст сообщения и id пользователя, только в этот раз достаём эти данные из массива с ключом **callback\_query**.

Ниже проверяем код нажатой кнопки и отправляем простое текстовое сообщение в ответ.

```
if($arrDataAnswer["callback_query"]) {
    $dataBut = $arrDataAnswer["callback_query"]["data"];
    $textMessage = mb_strtolower($arrDataAnswer["callback_query"]["message"]["text"]);
    $chatId = $arrDataAnswer["callback_query"]["message"]["chat"]["id"];

    if($dataBut == "but_1") {
	$arrayQuery = array(
	    'chat_id' => 1424646511,
	    'text' => "Ты нажал на 'КНОПКА 1'",
	    'parse_mode' => "html",
	);
	TG_sendMessage($arrayQuery);
    }
    else if($dataBut == "but_2") {
	$arrayQuery = array(
	    'chat_id' => 1424646511,
	    'text' => "Ты нажал на 'КНОПКА 2'",
	    'parse_mode' => "html",
	);
	TG_sendMessage($arrayQuery);
    }
}
```

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

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


# Business intelligence

## Apache Superset. Первый взгляд на BI инструмент

<https://habr.com/ru/articles/681228/>

В последнее время изучая вакансии на сайтах по поиску работы, все чаще стал отмечать, что помимо платных инструментов BI от кандидатов требуется знание еще бесплатных платформ. Мой предыдущий опыт работы по построению графической отчетности был связан исключительно с коммерческими продуктами, поэтому я решил выделить время на ознакомление с альтернативными решениями. Выбор Superset был случайным, так как я обратил внимание на него лишь потому, что он входит в экосистему Apache. Сразу хочу оговориться, что в данной заметке не будет сравнения Superset с платными инструментами. Такое сопоставление функционала просто некорректно из-за разных “весовых категорий”. Также я не буду выделять плюсы и минусы решения по сравнению с бесплатными аналогами, так как это очень дискуссионный вопрос. Неизбежно найдутся адепты того или иного продукта, которые будут доказывать ошибочность моих суждений. Поэтому я построил публикацию в форме простого описания “нюансов”, которые я выделил для себя, начав знакомство с Superset. Читатели же сами смогут сделать свои выводы.

Тестирование Superset решил начать с полноценной установки программы на Linux (Debian). Несмотря на то, что я полностью выполнил список действий, описанный в [документации](https://superset.apache.org/docs/installation/installing-superset-from-scratch), данный эксперимент завершился ошибкой. Попытка с запуском docker образа удалась с первого раза, список команд на [Docker Hub](https://registry.hub.docker.com/r/apache/superset). Как и в случае с Apache Airflow на этапе развертывания системы разработчики предлагают загрузить демонстрационные примеры. Я решил пропустить этот шаг (`docker exec -it superset superset load_examples`), чтобы в дальнейшем не удалять вручную предустановленные элементы. Вариант с разворачиваем сервиса из файла docker-compose.yml также попробовал. Список команд вы можете найти в [официальном руководстве](https://superset.apache.org/docs/installation/installing-superset-using-docker-compose/). Единственное замечание, я указал не последний релиз, а 1.5.0.

Далее нужно было настроить коннект к базе данных. Superset поддерживает возможность подключения к нескольким десяткам БД, но я выбрал PostgreSQL, как наиболее понятное для себя хранилище. На Хабр уже есть публикация ([“Поднимаем Apache Superset — необходимый и достаточный гайд”](https://habr.com/ru/post/661159/)), в которой описан пошаговый алгоритм, но там приводился пример, где PostgreSQL запускается в docker контейнере. Мне же захотелось реализовать случай, когда БД установлена локально. Разумеется, когда на этапе настройки соединения я указал стандартные **127.0.0.1** и **5432** меня постигла неудача: порт был закрыт. Первая причина указана в документации ([последние два абзаца](https://superset.apache.org/docs/installation/installing-superset-using-docker-compose/)), которую традиционно никто не читает. Вторая помеха кроется в первоначальных настройках самой PostgreSQL.

По умолчанию, PostgreSQL в целях безопасности принимает только локальные подключения. Чтобы разрешить подключения извне, нужно в файле **postgresql.conf** раскомментировать параметр и заменить localhost на звездочку: **listen\_addresses = '\*'**. Сам файл расположен по адресу ***/etc/postgresql/14/main/postgresql.conf***. Отредактировать его напрямую не получиться, поэтому нужно прибегнуть к услугам терминала (root, плюс редактор nano или vim). Второй файл, в который необходимо внести изменения это **pg\_hba.conf** (***/etc/postgresql/14/main/pg\_hba.conf***). Добавляем в самый конец страницы строку: **host all all 172.17.0.0/16 trust**. Вместо trust нужно использовать scram-sha-256, если доступ требуется по паролю. Данный момент я также вычитал в Хабр публикации [“Настройка PostgreSQL под Linux”](https://habr.com/ru/post/590599/). Работаем из терминала, с количеством пробелов между словами не ошибетесь, так как в файле будут образцы для заполнения. На финальном шаге перезагружаем БД командой в терминале: `systemctl restart postgresql`. В настройках сервера PostgreSQL через pgAdmin4 ничего менять не нужно. Теперь можно перейти в веб-интерфейс Superset и указать верные значения для хоста и порта: **172.17.0.1** и **5432**. Название базы данных, логин и пароль указываете исходя из ваших настроек.

Так как адреса хостов отличаются в рекомендациях из Интернета, советую проверить значения для вашего конкретного случая до начала правки файлов. Для этого в терминале последовательно введите две команды: `docker network ls` (для получения списка запущенных сетей, ищем **id bridge**), далее `docker network inspect id`. Нас интересуют пункты: **Subnet: 172.17.0.0/16** и **Gateway: 172.17.0.1**. Так как я не devops и не администратор БД, я не могу утверждать, что приведенные настройки адекватны с точки зрения безопасности. Поэтому не рекомендую использовать их без дополнительной консультации со специалистом на боевой БД! Все эксперименты только в тестовой среде и на демо БД.

Базовый алгоритм работы с Superset можно описать четырьмя шагами.

Шаг 1. Настроить коннект к БД (**Databases**).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1076f44657d9f7d5e322ae9337b1c0a5db7ad367%2F944a9b9e26803ea2a0a490b035bed299.png?alt=media)

Настройка подключения к БД

Шаг 2. Подключить физические таблицы / представления к “витринам” платформы (**Datasets**). Если требуется "вытащить" агрегированные данные, то можно написать запрос в разделе SQL Lab и сохранить результат как датасет.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-74b7e6618ccf8461c65e882dbc33017fcd6e27b3%2F44f9eb75ac0566140455d59d7df714e1.png?alt=media)

Запрос к БД, который будет сохранен как датасет

Для созданных датасетов можно рассчитывать базовые метрики.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0e3997a91306cac0de8ca83b2ceea1caf7e2ba61%2F6faf7952fc580193a13f44dfc75503dd.png?alt=media)

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

Шаг 3. Сформировать в режиме виртуального конструктора на основе датасетов отдельные графики и диаграммы (**Charts**). Superset из коробки содержит большой набор типовых визуализаций. Возможно создавать кастомные решения. Насколько целесообразна данная затея – большой вопрос, так как я еще ни разу не видел, чтобы замысловатая диаграмма приводила к инсайту менеджера. А вот когда все было наоборот, такие случаи мне известны.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a2e6900094644d6e42c930e87d7bd70a1896f110%2Feb5cc9370361c133bb7f9f537b8e7e51.png?alt=media)

Создание графика в режиме визуального конструктора

Шаг 4. Создать новую управленческую панель путем простого перетаскивания созданных элементов (**Dashboards**). По данному шагу у меня будут два замечания. Во-первых, на рисунке видно, что в отчете применены два типа фильтров. Вариант в левом углу, создается на этапе моделирования дашборда, он более современный и рекомендуется к использованию. Элементы для фильтрации, включенные в тело самого дашборда, как отдельные элементы - устаревший подход, о чем вам будет сигнализировать всплывающее окно. Во-вторых, на части визуальных элементов присутствуют надписи на языке Шекспира. От части из них можно избавиться с помощью имеющихся настроек, вот удастся ли добиться 100%-ого перевода я не уверен. Лично мне в результате беглой рекогносцировки этого не удалось, но перфекционистам с хорошим знанием программирования эта задача будет по плечу.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-027314346dd0ad68cdf690c3c472a8f58d8899a0%2Ffd3070df7e8afd976f47bf520fbdaee2.png?alt=media)

Прототипирование дашборда

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

В программе реализована возможность сохранения дашборда в формате JPEG

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Business%20intelligence/Untitled)

Работать напрямую с файлами txt, csv, xlsx нельзя. Нужно предварительно загружать информацию в БД и только потом писать SQL запросы. Заливка информации возможна прямо из интерфейса, но нужно разрешить данную операцию в настройках БД. Инструментов для предварительной обработки сырых данных нет. Поэтому быстрая ad hoc аналитика со сбором данных из разрозненных плохо структурированных файлов с помощью Superset крайне затруднена. Так как SQL, по сути, основной язык платформы, то и реализация сложных расчетов на стыке данных из разных датасетов будет также проблематична. Но функциональность языка можно расширить путем использования шаблонов Jinja в запросах.

Для старта работы с Superset от специалистов компании могут потребоваться следующие вещи.

Если Superset еще не установлен – нужны знания бэкенд-разработчика: умение работать с Docker; базовые команды терминала Linux; настройка Flask, Redis, Celery; выбор веб-сервера для платформы и т.д. Важно понимать, что данный BI инструмент это продукт с открытым исходным кодом. Это дает плацдарм для доработки под нужды бизнеса, но, с другой стороны, требует затрат времени на грамотную настройку компонентов системы и последующую утилизацию возможностей (как пример, возможность взаимодействия с артефактами Superset посредством Rest API).

Если продукт уже развернут, но подходящее DWH отсутствует - навыки дата инженера данных: создание Data Lake для сырых логов; ETL/ELT; умение выбрать, установить и настроить DWH (возможно колоночную базу данных, чтобы ускорить обработку запросов).

Если в DWH уже есть подготовленные витрины с актуальными данными - знание SQL хотя бы на среднем уровне, плюс экспресс-курс по возможностям BI решения.

Вместо выводов. Apache Superset – интересный продукт со своим характером. BI инструмент плохо подходит для срочной разработки дашбордов на основе разрозненных источников данных. Из-за нюансов платформы на этапе внедрения нетехническим компаниям обязательно потребуется помощь в установке и настройке. В организациях, где хорошо развита культура дата инжиниринга, Superset вполне может использоваться для создания несложной регламентированной отчетности.

На этом все. Всем здоровья, удачи и профессиональных успехов!

P.S. Поступил интересный вопрос, косвенно связанный с основной темой: “Если настраивать коннект между локальной БД PostgreSQL и **Redash** (контейнер Docker), то применим ли приведенный в публикации алгоритм действий?” Ответ: “Последовательность действий при настройке БД будет аналогичной, за исключением двух параметров. В файле **/etc/postgresql/14/main/pg\_hba.conf** указываем **172.18.0.0/16**, а в окне настройки подключения к PostgreSQL в среде Redash - **172.18.0.1**. Объясняется это тем, что при развертывании сервиса BI из файла docker-compose.yml ([официальный репозиторий для загрузки всех необходимых компонентов](https://github.com/getredash/setup)) создается отдельный bridge.”

P.S.S. Еще один вопрос по теме: "Как настроить подключение локальной **ClickHouse** и Apache Superset (контейнер Docker)?". После установки ClickHouse согласно инструкции ([официальная документация](https://clickhouse.com/docs/ru/getting-started/install/)), необходимо создать новую БД, провести настройку прав доступа для нового пользователя, а также настройку сетевого доступа. Данные шаги описаны в публикации "[Установка и настройка ClickHouse на Ubuntu](https://mcs.mail.ru/docs/additionals/cases/cases-db-config/case-ch-create?kb_language=ru_RU)". Здесь же я приведу два ключевых момента.

Для настройки прав доступа на созданную базу данных test\_db в папке **/etc/clickhouse-server/users.d** создаем файл **new\_user.xml** c описанием прав доступа.

```
<yandex>
    <users>
    <new_user>
        <password>nopswd</password>
        <networks>
            <ip>::/0</ip>
    </networks>
        <profile>default</profile>
        <quota>default</quota>
        <allow_databases>
            <database>test_db</database>
        </allow_databases>
    </new_user>
    </users>
</yandex>
```

По умолчанию ClickHouse слушает только 127.0.0.1. Чтобы настроить сетевой доступ к серверу, в папке **/etc/clickhouse-server/config.d** создаем конфигурационный файл **listen.xml**.

```
<yandex>
    <listen_host>::</listen_host>
</yandex>
```

Далее перезапускаем сервер командой `sudo systemctl restart clickhouse-server` и проверяем порты `sudo ss -tulpn | grep clickhouse.` Повторю момент, на котором уже заострял внимание в начале данной статьи, все манипуляции с портами БД нужно проверять на адекватность информационной безопасности!

Для создания коннекта необходимо дополнительно установить библиотеки ([официальная документация Superset](https://superset.apache.org/docs/databases/clickhouse/)). Так как я лишь тестировал данный вариант, то инсталляцию проводил прямо в работающий контейнер: `docker exec -it superset bash, pip install clickhouse-driver, pip install clickhouse-sqlalchemy, docker restart superset`. Если же обратиться к [документации ClickHouse](https://clickhouse.com/docs/en/connect-a-ui/superset-and-clickhouse/), то там рекомендована к установке другая библиотека `pip install clickhouse-connect`. Финальная строка для коннекта: **clickhouse://new\_user:nopswd\@172.17.0.1:8123/test\_db**


# Cloud Storage

[Ceph](/readme/architect/cloud-storage/ceph)

[Virtual Distributed File System](/readme/architect/cloud-storage/virtual-distributed-file-system)


# Ceph

<https://ceph.io/en/discover/technology/>

## Ceph delivers object, block, and file storage in a single, unified system.

Ceph is highly reliable, easy to manage, and free. Ceph delivers extraordinary scalability: thousands of clients accessing exabytes of data.

* [Object storage](https://ceph.io/en/discover/technology/#object)
* [Block storage](https://ceph.io/en/discover/technology/#block)
* [File system](https://ceph.io/en/discover/technology/#file)
* [CRUSH Algorithm](https://ceph.io/en/discover/technology/#crush)
* [RADOS](https://ceph.io/en/discover/technology/#rados)

## The Ceph stack: architectural overview

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-18a4876cc607c37b87411e28ccaa4184b927556f%2Finformation-stack.png?alt=media)

The RADOS-based Ceph Stack

Whatever delivery framework you require, Ceph can be adapted and applied accordingly. Ceph provides a flexible, scalable, reliable and intelligently distributed solution for data storage, built on the unifying foundation of RADOS (Reliable Autonomic Distributed Object Store). By manipulating all storage as objects within RADOS, Ceph is able to easily distribute data throughout a cluster, even for block and file storage types.

Ceph's core architecture achieves this by layering RGW (RADOS Gateway), RBD (RADOS Block Device) and CephFS (a POSIX-compliant file system) atop RADOS, along with a set of application libraries in the form of LIBRADOS for direct application connectivity.

## Object storage

The Ceph RGW object storage service provides industry-leading S3 API compatibility with a robust set of security, tiering, and interoperability features. Applications which use S3 or Swift object storage can take advantage of Ceph's scalability and performance within a single data center, or federate multiple Ceph clusters across the globe to create a global storage namespace with an extensive set of replication, migration, and other data services.

[Swift API documentation](https://docs.ceph.com/en/latest/radosgw/swift/)

### Rich metadata attributes

Make use of object storage's rich metadata attributes in Ceph for super-efficient unstructured data storage and retrieval:

* Create large scale data repositories that are easy to search and curate.
* Use cloud tiering to keep frequently accessed data in your cluster, and shift less frequently used data elsewhere.

### Transparent cache tiering

Use your ultra-fast solid state drives as your cache tier, and economical hard disk drives as your storage tier, achievable natively in Ceph.

* Set up a backing storage pool, a cache pool, then set up your failure domains via CRUSH rules.
* Combine cache tiering with erasure coding for even more economical data storage.

[Cache tiering documentation](https://docs.ceph.com/en/latest/rados/operations/cache-tiering/)

## Block storage

Ceph RBD (RADOS Block Device) block storage stripes virtual disks over objects within a Ceph storage cluster, distributing data and workload across all available devices for extreme scalability and performance. RBD disk images are thinly provisioned, support both read-only snapshots and writable clones, and can be asynchronously mirrored to remote Ceph clusters in other data centers for disaster recovery or backup, making Ceph RBD the leading choice for block storage in public/private cloud and virtualization environments.

### Provision a fully integrated block storage infrastructure

Enjoy all the features and benefits of a conventional Storage Area Network using Ceph's iSCSI Gateway, which presents a highly available iSCSI target which exports RBD images as SCSI disks.

[iSCSI overview](https://docs.ceph.com/en/latest/rbd/iscsi-overview/)

### Integrate with Kubernetes

Dynamically provision RBD images to back Kubernetes volumes, mapping the RBD images as block devices. Because Ceph ultimately stores block devices as objects striped across its cluster, you'll get better performance out of them than with a standalone server!

[Kubernetes and RBD documentation](https://docs.ceph.com/en/latest/rbd/rbd-kubernetes/)

### Create cluster snapshots with RBD

Take a look at the links below for more details on working with snapshots using Ceph RBD.

* [Taking snapshots](https://docs.ceph.com/en/latest/rbd/rbd-snapshot/)
* [RBD mirroring](https://docs.ceph.com/en/latest/rbd/rbd-mirroring/)

## File system

The Ceph File System (CephFS) is a robust, fully-featured POSIX-compliant distributed filesystem as a service with snapshots, quotas, and multi-cluster mirroring capabilities. CephFS files are striped across objects stored by Ceph for extreme scale and performance. Linux systems can mount CephFS filesystems natively, via a FUSE-based client, or via an NFSv4 gateway.

### File storage that scales

* File metadata is stored in a separate RADOS pool from file data and served via a resizable cluster of Metadata Servers (MDS), which can scale as needed to support metadata workloads.
* Because file system clients access RADOS directly for reading and writing file data blocks, workloads can scale linearly with the size of the underlying RADOS object store, avoiding the need for a gateway or broker mediating data I/O for clients.

### Export to NFS

CephFS namespaces can be exported over NFS protocol using the NFS-Ganesha NFS server. It's possible to run multiple NFS with RGW, exporting the same or different resources from the cluster.

* [More on CephFS and NFS.](https://docs.ceph.com/en/latest/cephfs/nfs/)
* [NFS and RGW](https://docs.ceph.com/en/latest/radosgw/nfs/)

## CRUSH Algorithm

The CRUSH (Controlled Replication Under Scalable Hashing) algorithm keeps organizations’ data safe and storage scalable through automatic replication. Using the CRUSH algorithm, Ceph clients and Ceph OSD daemons are able to track the location of storage objects, avoiding the problems inherent to architectures dependent upon central lookup tables.

[Introduction to the CRUSH algorithm](https://docs.ceph.com/en/latest/architecture/#crush-introduction)

## Reliable Autonomic Distributed Object Store (RADOS)

RADOS (Reliable Autonomic Distributed Object Store) seeks to leverage device intelligence to distribute the complexity surrounding consistent data access, redundant storage, failure detection, and failure recovery in clusters consisting of many thousands of storage devices. RADOS is designed for storage systems at the petabyte scale: such systems are necessarily dynamic as they incrementally grow and contract with the deployment of new storage and the decommissioning of old devices. RADOS ensures that there is a consistent view of the data distribution while maintaining consistent read and write access to data objects.

* [RADOS documentation (docs.ceph.com)](https://docs.ceph.com/en/latest/rados/)
* [RADOS: A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters (academic paper)](https://ceph.com/assets/pdfs/weil-rados-pdsw07.pdf)

## Discover more

* Benefits[Learn more about the benefits of Ceph](https://ceph.io/en/discover/benefits/)

  Ceph can be relied upon for reliable data backups, flexible storage options and rapid scalability. With Ceph, your organization can boost its data-driven decision making, minimize storage costs, and build durable, resilient clusters.
* Use cases[See how Ceph can be used](https://ceph.io/en/discover/use-cases/)

  Businesses, academic institutions, global organizations and more can streamline their data storage, achieve reliability and scale to the exabyte level with Ceph.
* Case studies[Examples of Ceph in action](https://ceph.io/en/discover/case-studies/)

  Ceph can run on a vast range of commodity hardware, and can be tailored to provide highly specific, highly efficient unified storage, fine-tuned to your exact needs.


# Virtual Distributed File System

<https://habr.com/ru/companies/first/articles/678818/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8df8a683f0df35b77a0430ab24c7d1bb7e5b2b46%2Fxnqhjup167y6klleamdu3wye1zu.png?alt=media)

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

Создаваемые нами данные — это наше наследие, которое надолго переживёт нас. По идее, личная информация не должна быть никак привязана ни к какому конкретному облаку, провайдеру или компании. Хорошо бы иметь возможность свободной замены облачных сервисов в своём личном наборе. В идеале — составить общую «файловую систему», куда можно в любой момент добавить/удалить Google Drive, Яндекс.Диск, [YouTube Drive](https://habr.com/ru/company/first/blog/676282/) или другие бесплатные файлохостинги. Главное, чтобы данные были размазаны по всему пространству и оставались независимы от конкретного провайдера.

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

Давайте рассмотрим ниже некоторые полезные программы, которые помогают управлять архивом данных, распределённому по множеству устройств и облаков:

## Файл-менеджер на распределённой файловой системе

Файл-менеджер [Spacedrive](https://github.com/spacedriveapp/spacedrive) — это опенсорсный кросс-платформенный файл-менеджер на файловой системе VDFS, который ставит задачей объединить в едином интерфейсе файлы из разных сервисов и разных файловых систем, в том числе из разных облаков. Грубо говоря, объединить в одном окошке облачные сервисы, которые официально не умеют друг с другом взаимодействовать, не имеют общих API и др.

Разработка программы ещё не закончена, но обещают выпустить клиенты под Windows, Linux, MacOS, iOS, watchOS и Android. Можно записаться в [список ожидания](https://www.spacedrive.com/), чтобы вас первым оповестили о релизе.

Файл-менеджер будет выглядеть примерно таким образом:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-528baf515bd6781e0c424d0c7046be161a6dc653%2Fqs1dikaf6cs9ebzhgcmh3xrmjfm.png?alt=media)

## Что такое VDFS

Отдельно нужно сказать пару слов о VDFS (virtual distributed filesystem) — виртуальной распределённой файловой системе, написанной на Rust. Это фундамент, на котором базируется Spacedrive.

VDFS предоставляет единый API для доступа к файлам на всех ваших устройствах (смартфоны, персональные компьютеры, серверы) и облачных дисках. То есть это единый интерфейс, который ведёт виртуальный индекс всех мест хранения файлов, а также синхронизирует БД между клиентами в режиме реального времени. Данная реализация использует архитектуру [CAS](https://en.wikipedia.org/wiki/Content-addressable_storage) (Content-addressable storage, контентно-адресуемое хранилище данных) для уникальной идентификации файлов, сохраняя логические пути файлов относительно мест хранения.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7a3ab58114c2c6eca4852f2f5c277eb9ff29f67f%2Fuuhqevwrbozc3izy31unmsem4ho.png?alt=media)

Первую реализацию VDFS можно найти в [статье Хаоюана Ли](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2018/EECS-2018-29.pdf) из Калифорнийского университета в Беркли. Там предполагается использовать VDFS в облачных хранилищах, но ничто не помешает перенести концепцию в клиентский софт, что и делается в Spacedrive.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-639294e4950781e57f0d1d82ff1918e1b132beff%2Frk4t6rmljnauycz-hpdxam-u9ai.png?alt=media)

Spacedrive находится в активной разработке, а большинство функций или в статусе «экспериментальная», или ещё не реализованы, а только запланированы.

На данный момент реализовано следующее (в стадии тестирования):

* обнаружение файлов (сканирование всех устройств, дисков и облачных аккаунтов для создания каталога всех файлов с метаданными);
* генерация превью (автоматическое создание маленьких превью для изображений и видео);
* статистика (общий объём, размер индекса, свободное пространство и другое).

В планах на ближайшее время:

* файл-менеджер — просмотр онлайн- и офлайн-хранилищ, файлов с метаданными, базовые функции CRUD (файл-менеджер разрабатывается прямо сейчас, к моменту публикации статьи может быть готов);
* синхронизация в реальном времени (тоже в разработке прямо сейчас);
* фото- и видеоальбомы;
* поиск по файловой системе;
* теги для автоматизации рабочих процессов, массовых операций с группами файлов, организации фотоколлекций;
* расширения (интеграция сторонних сервисов и расширение функциональности Spacedrive).

В более отдалённых планах:

* интеграция облаков — Apple Photos, Google Drive, Dropbox, OneDrive, создание API для добавления других облаков, таких как Яндекс.Диск;
* зашифрованные хранилища, модуль поверх VeraCrypt;
* менеджер ключей;
* установка коэффициента избыточности для файлов, мониторинг состояния устройств и накопителей;
* таймлайн/версионность (просмотр файловой системы за любой момент времени в прошлом);
* кодер аудио- и видеофайлов на базе FFMPEG в разные форматы с поддержкой тегов;
* воркеры (распределение вычислений по нескольким своим устройствам во время кодирования или других ресурсоёмких вычислений);
* бесплатный хостинг Spacedrive Cloud на своём сервере (или платная подписка).

В общем, задача Spacedrive понятна: объединить все облака в едином интерфейсе, удобном для пользователя. Идея красивая.

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

## Шифрование файлов в своём облаке

[Cryptomator](https://cryptomator.org/) — удобная программа для шифрования файлов, которые хранятся на облачном хостинге. В то время как Spacedrive только обещает реализовать модуль шифрования в своём файл-менеджере, здесь всё уже готово и работает.

Можно создать зашифрованное хранилище файлов всего в несколько щелчков мыши:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0a5ac865487ed80b588ca265de43ebe967949fab%2Fdzas6evossfeo9cy81k1piepslm.png?alt=media)

Хранилище открывается в файл-менеджере после введения пароля, его можно просматривать и добавлять файлы. А само хранилище легко скопировать на любое облако — это просто папка с `vault.cryptomator` и зашифрованными файлами в формате `*.c9r`.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-880cda5bac329bff30299627f2be43e33e5f5cdb%2Fugtnkg1fzq49ta-aw9ishptsi4a.png?alt=media)

Под Windows для более удобной работы рекомендуется скачать и установить сторонний драйвер [WinFsp](https://winfsp.dev/rel/) (Windows File System Proxy). Это своеобразный аналог FUSE для Unix, который упрощает работу сторонних файловых систем под Windows.

В качестве более простой альтернативы, которая работает из командной строки, можно рекомендовать [gocryptfs](https://github.com/rfjakob/gocryptfs) (Linux), [cppcryptfs](https://github.com/bailey27/cppcryptfs) (Windows) или [DroidFS](https://github.com/hardcore-sushi/DroidFS) (Android). Всё это оверлейные зашифрованные файловые системы, которые прозрачно работают поверх основной ФС, что очень удобно — со стороны они выглядят как обычные папки и обычные файлы, только со странными названиями и нечитаемым содержимым.

В целом, это более простая альтернатива команде [crypt](https://rclone.org/crypt/), которая поддерживается в `rclone`.

## Копия облака в другом облаке

Как дома, так и в других местах, файлы с одного сервера дублируются на другом для надёжности, так и в интернете на каждое облако нужно второе, резервное. Это элементарная безопасность.

Некоторые даже бизнес-модель построили на этой идее. Например, сервис [rsync.net](https://rsync.net/) предлагает облачное хранилище и *удобный бэкап других облаков* с помощью стандартных linux-инструментов типа [borg](https://www.borgbackup.org/), [restic](https://restic.net/), [rclone](https://rclone.org/), [git-annex](https://git-annex.branchable.com/) и др.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7b0b6776bc0d725b117f86cbf58c40ce9055cd69%2Fhzqdru8wx6ydbpcagequ5jprm7w.png?alt=media)

По сути, *rsync.net* предоставляет клиенту пустую файловую систему UNIX и доступ к ней по SSH. Никаких обвесистых клиентов GUI или API, всё работает настолько просто, насколько просто выглядит. Это удалённая файловая система, доступная из локальной консоли. Дата-центр даже не использует [ни файрволов, ни маршрутизаторов](https://console.dev/qa/rsync-john-kozubik/), потому что в них «нет особой необходимости». Просто стоят серверы FreeBSD, набитые накопителями с файловой системой ZFS — одно огромное файлохранилище.

Вообще, серверы у них сконфигурированы довольно интересно: это в основном корпуса 4U типа JBOD (just a bunch of disks), куда втиснуто от 45 до 60 накопителей SSD в каждый. Массивы накопителей подключаются к управляющим хед-юнитам 2U, в которых установлено 16 SSD, в том числе два загрузочных и 14 для кэшей на чтение (L2ARC) и запись (SLOG). Специфика файловой системы ZFS такова, что требуется много оперативной памяти, поэтому хед-юниты поддерживают до 2 ТБ.

Такое удалённое файлохранилище легко интегрировать в любую файловую систему, примонтировав его как накопитель под Windows, macOS или Linux. Например, назначить [букву диска под Windows](https://www.rsync.net/resources/howto/windows_map.html).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-aadfb74780f0b99e228c2c958228605d674e977c%2Fzit8d3v0gfo9khmwk7hziffuw-k.png?alt=media)

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

В свою очередь, [rclone](https://rclone.org/) — утилита командной строки, которая позволяет управлять файлами практически на любом облачном хостинге. Сейчас поддерживается [более 40 облачных провайдеров](https://rclone.org/#providers), включая хранилище объектов S3, хранилища Yandex Disk, Mail.ru Cloud, Microsoft OneDrive, Dropbox, Google Drive и другие.

В общем, `rclone` в облаке — это эквивалент локальным unix-командам rsync, cp, mv, mount, ls, ncdu, tree, rm и cat. Утилита также позволяет примонтировать облачное хранилище в виде локального диска под Windows, macOS, Linux или FreeBSD.

## Децентрализованная архитектура на основе ячеек

Итак, мы сформулировали парадигму «персонального хранилища», которое состоит из разных облаков, множества личных устройств и накопителей. Все файлы распределяются по носителям/облакам с указанной степенью избыточности, но доступны из единого «окна».

Как видим, постепенно появляются инструменты, которые поддерживают эту парадигму. В неё вписывается модель независимых «персональных подов» с личной информацией — концепция [SOLID](https://solid.mit.edu/) от Тима Бернерса-Ли.

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

Такая система напоминает ещё одну интересную концепцию из области бизнеса — [децентрализованную архитектуру организации на основе ячеек](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cloud%20Storage/Virtual%20Distributed%20File%20System/reference-architecture-cell-based/README.md), Cell-Based Architecture.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-90a93a056c4c811a6fb58d23288b92035e0f51a8%2Fme5onvdk0tkddhawugpufaezfww.png?alt=media)

Это облачная инфраструктура для современных цифровых компаний, созданная по образцу Agile, микросервисов и многоклеточных организмов в биологии.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-52535d18b69083455ec7b2287dc2c84adc018dee%2Fs3betgj8jgrqzwv8m29oylork9o.png?alt=media)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ee2ca2f54b1e300600a88ba07fb0c0ed6c264b6a%2Fx35yhb9rgjbyzhrll4rvkdakamo.png?alt=media)

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


# Cryptography

[Open Source PKI Software](/readme/architect/cryptography/open-source-pki-software)

[OpenPGP](/readme/architect/cryptography/openpgp)


# Open Source PKI Software

<https://www.keyfactor.com/blog/the-4-best-open-source-pki-software-solutions-and-choosing-the-right-one/>

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/Open%20Source%20PKI%20Software/iStock-1354205009.jpg)

There are many reasons why you may be looking for open-source public key infrastructure (PKI) software. Maybe you need to enable authentication and encryption for IoT products you deliver to the market. Or maybe you’re issuing certificates into a microservices environment to secure machine-to-machine connections. In any case, you’ve got options.

This blog will discuss the best open-source PKI software tools available today and provide tips on choosing the right tool for your needs.

## What is open source PKI?

First off, let’s begin with a few definitions. PKI is used to issue certificates that enable authentication, encryption, and digital signatures for multiple use cases.

**Authentication**: proving your identity to a website or other entity

**Encryption**: protecting data from unauthorized access

**Digital signatures**: verifying the authenticity of a message or document

Open-source PKI solutions are a type of CA software that is available for anyone to use, modify and distribute. Open source software could be used for publicly trusted SSL/TLS certificates or, more commonly, as a private certificate authority (CA) for internal trust within an enterprise.

The code for these tools is typically published under an open-source license, allowing anyone to view, edit and redistribute the software.

Developers and engineers increasingly leverage PKI to embed security into their products or application development and delivery pipelines. Open source certificate authority (CA) software is a great way to get started with PKI.

## The 4 best open source PKI software tools

There are many different open-source PKI software tools available today. Here we’ve broken down the four most common open source PKI solutions, including key considerations and recommendations when choosing the right fit for your use case.

### (1) EJBCA CE

[EJBCA](https://www.ejbca.org/) is a Java-based PKI solution that offers both enterprise and community editions. EJBCA Community Edition (CE) is free to download and has all the core features needed for certificate issuance and management. It includes multiple certificate enrollment methods, as well as a REST API. EJBCA was developed by PrimeKey, now a part of Keyfactor, and it is the most widely trusted and adopted solution for open-source PKI CA today.

**Core capabilities include:**

* X.509 and SSH certificate issuance and lifecycle management
* Certificate authority (CA), registration authority (RA), and OCSP functionality
* Extensibility via CMP, SCEP, and REST API
* Audit logging to file or database
* Basic HSM support using Java PKCS#11

[EJBCA Enterprise Edition (EE)](https://www.keyfactor.com/platform/keyfactor-ejbca-enterprise/) includes features for production-ready environments, including high availability, clustering, authentication, advanced protocol and HSM support, professional support and services, and deployment flexibility. EJBCA Enterprise can be deployed as a turnkey hardware appliance, software appliance, cloud-based, or SaaS-delivered PKI.

### (2) Dogtag Certificate System

[Dogtag Certificate System](https://www.dogtagpki.org/wiki/PKI_Main_Page) (also known as Dogtag PKI) is an open-source certificate authority (CA) that supports many common PKI use cases. It offers a web-based management interface that allows you control over your certificates while also supporting multiple formats so that they can easily fit different use cases.

**Core capabilities include:**

* X.509 certificate issuance and certificate management
* CRL generation and publishing
* Local registration authority (LRA) for authentication and policies
* Extensibility via ACME, SCEP, and REST API
* Does not support relational databases – requires LDAP

### (3) OpenXPKI

[The OpenXPKI](https://www.openxpki.org/) is a toolkit based on OpenSSL and Perl that can create, manage, and deploy digital certificates. It includes support for multiple certificate formats and an online interface to help you oversee your PKI workloads.

**Core capabilities include:**

* X.509 certificate issuance and certificate management
* Web-based GUI compatible with all major browsers
* Extensibility via SCEP and EST

### (4) Step-ca

[Step-ca](https://smallstep.com/docs/step-ca) is a simple yet flexible CLI-based open-source PKI tool that can create and manage digital certificates. It similarly includes support for multiple certificate formats and integrates with tools like Kubernetes, Nebula, and Envoy.

**Core capabilities include:**

* X.509 and SSH certificate issuance and management
* CLI-based interface for certificate
* Extensibility via ACME and SCEP protocol
* Requires technical expertise in PKI concepts and JSON

## 5 key considerations for choosing open source PKI solutions

When choosing an open source PKI management tool, there are several factors you will want to consider based on your specific use case and requirements.

### **Ease of use:**

Setting up and running a PKI isn’t for the faint of heart. Even the best tools can create vulnerabilities if they are not properly configured and deployed. Open-source PKI solutions should be easy to deploy, with published containers offering the simplest method. They should also provide an easy-to-use interface for configuration, reporting, and management.

### **Flexibility and extensibility:**

Once you have your PKI up and running, you’ll need to integrate certificate issuance and management workflows with your tools and applications. Industry-standard protocols such as ACME, SCEP, EST, and CMP provide certificate lifecycle management and enrollment capabilities. A REST API is also important to offer additional extensibility and functionality specific to the tool you choose.

### **Documentation and user community:**

[Good documentation](https://doc.primekey.com/ejbca) is essential for any PKI solution. Be sure to check that the documentation is up-to-date and easy to understand. Support typically isn’t available with open-source projects, so you’ll need to ensure that you can set up and deploy the solution independently.

You should also ensure that there’s a solid community to provide support and guidance when you need it. A good indicator of an active community is to check the number of downloads, discussions, and online forums where end users can discuss features and assist one another.

### **Maintenance and support:**

Security isn’t static, and your PKI shouldn’t be either. Ensure that your open source PKI solution is actively developed and maintained by the community and project owner. This ensures that vulnerabilities are addressed swiftly, and new features and functionality are continuously available as the PKI landscape evolves.

If something goes wrong with your PKI implementation, you’ll need access to troubleshooting documentation. Make sure the supplier you choose offers thorough documentation and a commercial/premium support agreement available from the vendor with an enterprise version, should the need arise to upgrade.

### **Enterprise upgrade:**

If you need enterprise-grade features, be sure to choose a tool that offers a simple path to upgrade. A full-featured enterprise PKI should be able to handle the increased load of large-scale production environments without compromising performance or security. To support these requirements, you’ll need capabilities like high availability, multi-node clustering, compliance certifications, advanced protocols, and hardware security module (HSM).integrations.

## Why choose EJBCA over open source PKI alternatives?

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/Open%20Source%20PKI%20Software/The-best-open-source-PKI-software-solutions-comparison.png)

EJBCA CE is a powerful, flexible, and easy-to-use PKI solution used by everyone from developers and engineers to IAM and security teams to issue trusted identities for all of their devices and workloads. Here are just a few of the key reasons why teams choose EJBCA CE over open source PKI alternatives:

### **Complete PKI solution:**

EJBCA provides a complete PKI solution that includes everything you need to get started. It supports CA, RA, and OCSP functionality out of the box and can easily scale to meet even the most demanding transaction workloads for certificate issuance and validation.

### **Extensibility:**

EJBCA is extremely flexible and can be easily extended to meet your specific needs. It supports pre-built plugins with other open-source tools such as HashiCorp Vault and Kubernetes, and it also supports SCEP, CMP, and REST API protocols. Advanced protocols such as ACME and EST are available with EJBCA Enterprise.

### **Easy to deploy and use:**

EJBCA is readily available for download from GitHub and Sourceforge. It’s also available as a published container via Docker Hub, making it easy to deploy quickly and securely. It also offers a web-based GUI for centralized administration of CAs, audit logs, templates and policies, and more.

### **Proven and trusted:**

EJBCA is one of the longest-running CA software projects, with millions of downloads and time-proven robustness and reliability. It’s built on open standards and a Common-Criteria certificate open-source platform.

### **Robust documentation:**

EJBCA is supported by comprehensive documentation, including how-to guides, tutorial videos, troubleshooting guides, and use cases. This makes it incredibly easy for end-users to get up and running quickly and to get the most out of their PKI.

### **Path to enterprise:**

If you need an enterprise-grade PKI solution, EJBCA offers an easy path to upgrade from the community edition to the enterprise edition. EJBCA Enterprise is available in many different forms and flavors to meet your specific requirements for simplicity, availability, and compliance.

## Don't take our word for it. Try it out.

If you’re looking for an open source PKI management tool, be sure to [explore EJBCA Community with Keyfactor](https://www.ejbca.org/). Ready to try EJBCA Enterprise? No problem. You can get started with a free 30-day trial of EJBCA Cloud in [Microsoft Azure](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/primekey.ejbca_enterprise_cloud_2?tab=Overview) or [AWS](https://aws.amazon.com/marketplace/pp/prodview-u2xdo5mkuilke) in minutes.


# OpenPGP

<https://www.openpgp.org/about/>

OpenPGP is a non-proprietary format for authenticating or encrypting data, using public key cryptography.

It is based on the original PGP (Pretty Good Privacy) software.

Beginning in 1997, the OpenPGP Working Group was formed in the Internet Engineering Task Force (IETF) to define this standard that had formerly been a proprietary product since 1991.

Over the past decade, PGP, and later OpenPGP, has become the standard for nearly all of the world’s signed or encrypted email.

OpenPGP also defines a standard format for certificates which, unlike most other certificate formats, enables [webs of trust](https://en.wikipedia.org/wiki/Web_of_trust).

OpenPGP formats and uses are specified in many [IETF RFCs and drafts](https://www.ietf.org/standards/rfcs/)[1](https://www.openpgp.org/about/#fn:rfcs), so these standards can be implemented by any company without paying any licensing fees to anyone.

1.

```
[RFC 3156](https://tools.ietf.org/html/rfc3156) MIME Security with OpenPGP, [RFC 4880](https://tools.ietf.org/html/rfc4880) OpenPGP Message Format (the main one), [RFC 5581](https://tools.ietf.org/html/rfc5581) The Camellia Cipher in OpenPGP, [RFC 6091](https://tools.ietf.org/html/rfc6091) Using OpenPGP Keys for Transport Layer Security (TLS) Authentication, [RFC 6637](https://tools.ietf.org/html/rfc6637) Elliptic Curve Cryptography (ECC) in OpenPGP, and [more](https://www.openpgp.org/about/standard/). [↩](https://www.openpgp.org/about/#fnref:rfcs)
```

[Email Encryption](/readme/architect/cryptography/openpgp/email-encryption)

[Kleopatra](/readme/architect/cryptography/openpgp/kleopatra)

[Miscellaneous Tools](/readme/architect/cryptography/openpgp/miscellaneous-tools)

[Server side applications](/readme/architect/cryptography/openpgp/server-side-applications)


# Email Encryption

<https://www.openpgp.org/software/>

All email applications on this page support the OpenPGP standard either directly or with additional software. The authors of this webpage are not actively participating in the development of each of these third-party apps. No security audits have been done by us and, thus, we cannot provide any security guarantees.

## Windows

* [Claws Mail](https://www.openpgp.org/software/claws/)
* [eM Client](https://www.openpgp.org/software/emclient/)
* [EverDesk](https://www.openpgp.org/software/everdesk/)
* [The Bat!](https://www.openpgp.org/software/thebat/)
* Outlook:
  * [gpg4o](https://www.openpgp.org/software/gpg4o/)
  * [Gpg4win](https://www.openpgp.org/software/gpg4win/)
  * [p≡p](https://www.openpgp.org/software/pep/)
* [Postbox](https://www.openpgp.org/software/postbox/) using [Enigmail](https://www.openpgp.org/software/enigmail/)
* [Thunderbird](https://www.openpgp.org/software/thunderbird):
  * [Autocrypt](https://www.openpgp.org/software/autocrypt/) for versions <78
  * [Enigmail](https://www.openpgp.org/software/enigmail/) for versions <78

## Mac OS

* [Apple Mail: GPGTools](https://www.openpgp.org/software/gpgtools/)
* [Canary Mail](https://www.openpgp.org/software/canary-mail/)
* [Mutt](https://www.openpgp.org/software/mutt/)
* [Postbox](https://www.openpgp.org/software/postbox/) using [Enigmail](https://www.openpgp.org/software/enigmail/)
* [Thunderbird](https://www.openpgp.org/software/thunderbird):
  * [Autocrypt](https://www.openpgp.org/software/autocrypt/) for versions <78
  * [Enigmail](https://www.openpgp.org/software/enigmail/) for versions <78

## Android

* [FairEmail](https://www.openpgp.org/software/fairemail/)
* [FlowCrypt](https://www.openpgp.org/software/flowcrypt/)
* [K-9 Mail: OpenKeychain](https://www.openpgp.org/software/openkeychain/)
* [p≡p](https://www.openpgp.org/software/pep/)
* [R2Mail2](https://www.openpgp.org/software/r2mail2/)

## iOS

* [Canary Mail](https://www.openpgp.org/software/canary-mail/)
* [FlowCrypt](https://www.openpgp.org/software/flowcrypt/)
* [iPGMail](https://www.openpgp.org/software/ipgmail/)
* [PGPro](https://www.openpgp.org/software/pgpro/)
* [Safe Easy Privacy](https://www.openpgp.org/software/safe/)

## Linux

* [Claws Mail](https://www.openpgp.org/software/claws/)
* [Evolution: Seahorse](https://www.openpgp.org/software/seahorse/)
* [KMail: Kleopatra](https://www.openpgp.org/software/kleopatra/)
* [Mutt](https://www.openpgp.org/software/mutt/)
* [Thunderbird](https://www.openpgp.org/software/thunderbird):
  * [Autocrypt](https://www.openpgp.org/software/autocrypt/) for versions <78
  * [Enigmail](https://www.openpgp.org/software/enigmail/) for versions <78

## Browser Plugins

* [FlowCrypt (Gmail)](https://www.openpgp.org/software/flowcrypt/)
* [Mailvelope](https://www.openpgp.org/software/mailvelope/)
* [Psono](https://www.openpgp.org/software/psono/)

## Webmail Provider with Browser Plugins

A lot of webmail providers support email encryption via the OpenPGP standard using [Mailvelope](https://www.openpgp.org/software/mailvelope/). The Mailvelope website provides a list of [supported webmail providers](https://www.mailvelope.com/en/faq#mailer_list).

Providers with help pages:

* [GMX](https://hilfe.gmx.net/sicherheit/pgp/mailvelope-installieren.html)
* [Posteo](https://posteo.de/hilfe/wie-installiere-ich-eine-ende-zu-ende-verschluesselung-pgp-im-browser)
* [WEB.DE](https://hilfe.web.de/sicherheit/pgp/index.html)

Pre-configured (authorized) providers:

* [Gmail](https://mail.google.com/)
* [mail.ru](https://mail.ru/)
* [Outlook.com](https://outlook.live.com/owa/)
* [volny.cz](https://volny.cz/)
* [Yahoo](https://login.yahoo.com/)
* [Zoho Mail](https://www.zoho.eu/mail/)

Other authorized providers with API support:

* [mailbox.org](https://mailbox.org/)
* [riseup.net](https://mail.riseup.net/)
* [Roundcube](https://roundcube.net/)

## Webmail Provider with In-Browser Cryptography

In contrast to the previous section, the following webmail providers do not require the installation of additional browser plugins, instead OpenPGP is implemented in JavaScript provided directly by the website. While these are easier to set up and provide basic security guarantees with OpenPGP, [some people don’t consider these “end-to-end secure”](https://tonyarcieri.com/whats-wrong-with-webcrypto).

* [Hushmail](https://www.hushmail.com/) (limited OpenPGP support)
* [Mailfence](https://www.mailfence.com/)
* [postale.io](https://postale.io/)
* [ProtonMail](https://protonmail.com/)

## Project Missing?

If a project is missing and you would like it included, please open a pull request at [github.com/OpenPGP/openpgp.org](https://github.com/OpenPGP/openpgp.org). Please note that we only include published, working software, which implements the standard. The software is ordered alphabetically within the sections.


# Kleopatra

<https://habr.com/ru/articles/551138/>

Программы семейства [GPG](https://www.gnupg.org/) *(GNU Privacy Guard)* / [PGP](https://ru.wikipedia.org/wiki/PGP) *(Pretty Good Privacy)* позволяют "прозрачно" подписывать и зашифровывать все типы цифровой информации. По своей сути, названные инструменты являются лишь удобной обёрткой, упрощающей практическое использование открытых алгоритмов асимметричной криптографии.

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

В этой статье рассмотрим приложение с открытым исходным кодом для работы с инструментарием GPG в графической оболочке — находка для новичков и тех, кто просто избегает загадочного черного окна командной строки. Благодаря кроссплатформенности Клеопатры, статья одинаково полезна для пользователей Windows, Linux и FreeBSD.

## **Установка**

Во многих unix-like операционных системах Клеопатра имеется в репозиториях по умолчанию. В Debian установка выглядит так: `sudo apt-get install kleopatra`.

Для Windows программа распространяется в пакете [GPG4Win](https://www.gpg4win.org/download.html), объединяющем в себе несколько полезных инструментов: непосредственно **Kleopatra**, **GpgEX** - удобный плагин для проводника Windows, который добавляет в контекстное меню пункты "Зашифровать", "Подписать", "Расшифровать", "Проверить контрольные суммы" и некоторые другие, **GPA** — еще один более простой на вид и менее функциональный менеджер ключей, **GpgOL** — плагин для почтового клиента Outlook).

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

## **Создание пары ключей**

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/74ab1e649dc27b80565b3f3c1e7df611.jpg)

На выбор предлагаются типы ключей [X.509](https://habr.com/ru/post/194664/) (практически применяется в корпоративной среде) и OpenPGP. Выбираем OpenPGP. Вводим контактые данные, которые будут отображаться у всех владельцев нашего открытого ключа. Вместо настоящего имени можно указать никнейм. В дальнейшем некоторую информацию ключа будет возможно изменить.

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/e72da1f34db1ee0be32d858b2d9161e0.jpg)

Чтобы создать пару ключей на эллиптических кривых, переходим в дополнительные параметры. Пункт ECDSA/EdDSA — то, что нам надо. Дополнительный чекбокс (галочка) *"*+ECDH" \*\*даст ключу возможность шифровать, без нее сертификат можно будет использовать только для подписи и идентификации, так как ECDSA/EdDSA — алгоритмы подписи, а не шифрования. В выпадающих списках предлагается выбрать один из алгоритмов: ed25519, brainpool и NIST.

1. ed25519 (Curve25519) — эталонная и непатентованной реализация криптографии на эллиптической кривой, имеет 128-битную длину. Является ключом EdDSA — самым актуальным алгоритмом цифровой подписи (считается, что без закладок от силовых структур каких-либо стран).
2. brainpool — алгоритм, разработанный немецким сообществом криптографоф, в число которых входят университеты, государственные ведомства и коммерческие организации, например, компания Bosch. Поддерживает длины в 256, 384 и 512 бит. При подписи использует несколько устаревший алгоритм ECDSA.
3. NIST — американский алгоритм, разработанный [Национальным Институтом Стандартов и Технологий](https://www.nist.gov/). Рекомендован для использования государственными органами США. Поддерживает длины в 256, 384 и 521 бит. По оценке некоторых специалистов, [NIST лучше brainpool по производительности.](https://tls.mbed.org/kb/cryptography/elliptic-curve-performance-nist-vs-brainpool) При подписи использует несколько устаревший алгоритм ECDSA.

Для примера в обоих случаях используется алгоритм brainpool с максимальной длиной ключа. По умолчанию ключ создается со сроком годности в два года. Этот параметр можно изменить, либо вовсе отключить, тогда ключ будет бессрочным.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/10b02ba2fd9c118973ddbc325d2c88a7.jpg)

На следующем шаге задается пароль, который является последним рубежом защиты секретного ключа. Не следует передавать кому-то секретный ключ, но если так все-таки вышло, будет лучше, когда вы задали очень надежный пароль. Рекомендуется использовать специальные знаки (символы пунктуации и прочее) для надежной защиты от брутфорса. Лучшим вариантом будет длинный пароль, полученный из генератора случайных символов, однако не забывайте про золотую середину между использованием и безопасностью. Например, вводить на телефоне очень длинный и сложный пароль с символами из расширенной таблицы ASCII, не имея возможности его скопировать из менеджера паролей, будет весьма проблематично.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/678448e69c5033056515b5c481d9f47c.jpg)

Итак, пара ключей создана! Обратите внимание на отпечаток, это уникальный идентификатор вашего ключа. Даже если кто-то захочет представиться вами, создав ключ с такими же именем и электронной почтой, его отпечаток будет отличаться. Именно поэтому важно сверять отпечатки получаемых ключей.

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/98dd76370ee7a6c6f374e63f10cde77b.jpg)

Открыв экспортированный ключ в текстовом редакторе, мы увидим специфичный фрагмент текста, начинающийся словами "BEGIN PGP **PRIVATE** KEY BLOCK". Будьте внимательны, не отправьте его кому-то по ошибке! Открытые ключи, предназначенные для передачи вторым лицам, начинаются со слов "BEGIN PGP **PUBLIC** KEY BLOCK".

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

## **Экспорт и импорт**

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/63f93e18d9a455638110890b09c845a2.jpg)

Для операций с ключом, щелкните по нему правой кнопкой мыши.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/6ce32c46fdab8dead79d28b408d873d2.jpg)

Для импорта ключей (нашего уже существующего на новом устройстве или полученного публичного), воспользуемся кнопкой "Импорт". Также можно использовать двойной клик по файлу ключа, это автоматически откроет Клеопатру и импортирует выбранный ключ. GPG-файлы встречаются с расширениями \*.asc, \*.pgp и \*.gpg. Это не имеет большого значения, так как расширение нужно больше для удобства пользователя и лишь немного — приложений. Файл будет корректно прочитан и в случае, когда специальное расширение изменено или удалено.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/e24c415e6251e751cccb38a4819d5c02.jpg)

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/cb331f4f9cf40e4808fcfeb5bdba82fc.jpg)

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/a098dc2a154381bbb1c083b41259bf51.jpg)

Теперь мы можем проверять подпись владельца нового ключа и шифровать для него информацию.

## **Шифрование и подпись**

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/5da774f8833630e2adee17a6ff70740a.jpg)

После выбора файла, предлагается выбрать нужные операции. Подпись позволяет получателю убедиться в авторстве файла. Эта функция очень полезна: расшифровав архив, мы точно знаем, что архив был зашифрован владельцем обозначенного ключа, а не кем-то другим, кто просто располагает нашим публичным ключом. Использовать подпись — полезная привычка в большинстве случаев.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/a43956eced57ca0984ba540b2e0de030.jpg)

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

Также возможна подпись без шифрования. Чаще всего применимо к текстовой информации. Механизм подписания строится на хеш-сумме: позволяет сравнить актуальное состояние информации с тем, какой она была, когда ее подписывал отправитель. Для примера откроем "блокнот".

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/5543ca73c71c781cac051ad588cfbc48.jpg)

Назначив отсутствие шифрования для кого-либо, оставляем только подпись и нажимаем кнопку "Подписать".

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/1e14238548abf042ae455b98a14f3b4b.jpg)

После ввода пароля от ключа, видим, что к фразе "Отличная работа" добавился дополнительный текстовый блок с хеш-суммой SHA512.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/2a994d5b6341d9047fd5ce683663227b.jpg)

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

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/acbac4b3c816c9d5f706703b018a6fbb.jpg)

В случае подписи файла без шифрования, в директории файла создается сигнатура с расширением \*.sig, которую следует передавать вместе с исходным файлом. Если в файле изменится хотя бы один бит, проверка подписи выдаст ошибку.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Cryptography/OpenPGP/Kleopatra/94c4a8f94502045ac340631320ad1858.jpg)

## **Постскриптум**

Все локальные ключи централизованно хранятся на устройстве в специальной папке. Все программы, взаимодействующие с GPG, будут их видеть. Для общения по протоколу XMPP (Jabber), защищенного GPG-шифрованием, можно использовать [Gajim](https://gajim.org/), который также является кроссплатформенным. Для ведения защищенной почтовой переписки удобно использовать этичный клиент [Thunderbird](https://www.thunderbird.net/ru/), в который необходимо будет импортировать секретный ключ, так как он имеет свое изолированное хранилище ключей. Об использовании Thunderbird написано [тут](https://habr.com/ru/post/565212/).

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


# Miscellaneous Tools

<https://www.openpgp.org/software/misc/>

All applications on this page implement the OpenPGP standard. The authors of this webpage are not actively participating in the development of each of these third-party apps. No security audits have been done by us and, thus, we cannot provide any security guarantees.

## PC Applications

* [GpgFrontend](https://www.openpgp.org/software/misc/gpgfrontend/) (Windows, macOS, Linux)

## Web-Based Tools

* [Pipefile](https://pipefile.com/) (Secure File Sharing)
* [Cryptonomica](https://cryptonomica.net/#!/openPGPOnline) (Identity Verification)

## Apps

* [Pignus](https://www.frobese.de/pignus) (iOS)
* [neutriNote](https://play.google.com/store/apps/details?id=com.appmindlab.nano) (Android)

## Project Missing?

If a project is missing and you would like it included, please open a pull request at [github.com/OpenPGP/openpgp.org](https://github.com/OpenPGP/openpgp.org). Please note that we only include published, working software, which implements the standard. The software is ordered alphabetically within the sections.


# Server side applications

<https://www.openpgp.org/software/server/>

All applications on this page implement the OpenPGP standard. The authors of this webpage are not actively participating in the development of each of these third-party apps. No security audits have been done by us and, thus, we cannot provide any security guarantees.

## Webmail Clients

* [Mailpile](https://mailpile.is/)
* [Pixelated](https://pixelated-project.org/)
* [Roundcube](https://roundcube.net/)

## Keyservers

* [FlowCrypt Attester](https://flowcrypt.com/attester/)
* [Hockeypuck Keyserver](https://hockeypuck.github.io/) (in Go)
* [keys.openpgp.org](https://keys.openpgp.org/) (in Rust)
* [Mailvelope Keyserver](https://keys.mailvelope.com/) (in JS)
* [Nicknym](https://leap.se/en/docs/design/nicknym), from the [LEAP](https://leap.se/) project
* [Nyms](http://nyms.io/)
* [SKS Keyserver](https://sks-keyservers.net/) (in OCaml)

## Mailing List Software

* [Mailman 3 PGP plugin](https://pypi.python.org/pypi/mailman-pgp)
* [Schleuder encrypted mailinglist](https://schleuder.org/)

## Password Managers

* [Passbolt](https://www.passbolt.com/)

## Project Missing?

If a project is missing and you would like it included, please open a pull request at [github.com/OpenPGP/openpgp.org](https://github.com/OpenPGP/openpgp.org). Please note that we only include published, working software, which implements the standard. The software is ordered alphabetically within the sections.


# Message broker

[Kafka](/readme/architect/message-broker/kafka)

[RabbitMQ](/readme/architect/message-broker/rabbitmq)


# Kafka

Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.

### **CORE CAPABILITIES**

* **HIGH THROUGHPUT**

  ![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Message%20broker/Kafka/icon-high-throughput.svg)

  Deliver messages at network limited throughput using a cluster of machines with latencies as low as 2ms.
* **SCALABLE**

  ![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Message%20broker/Kafka/icon-scalable.svg)

  Scale production clusters up to a thousand brokers, trillions of messages per day, petabytes of data, hundreds of thousands of partitions. Elastically expand and contract storage and processing.
* **PERMANENT STORAGE**

  ![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Message%20broker/Kafka/icon-database.svg)

  Store streams of data safely in a distributed, durable, fault-tolerant cluster.
* **HIGH AVAILABILITY**

  ![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Message%20broker/Kafka/icon-high-availability.svg)

  Stretch clusters efficiently over availability zones or connect separate clusters across geographic regions.

[Kafka UI-tools](/readme/architect/message-broker/kafka/kafka-ui-tools)

[Kafka streams ksqlDb](/readme/architect/message-broker/kafka/kafka-streams-ksqldb)


# Kafka UI-tools

<https://habr.com/ru/companies/flant/articles/688190/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c833e72946c97de8cac460ecf87f5bc5bcb9738d%2F60c61c846be57a5670c3a2be65118007.png?alt=media)

***Прим. перев.**: автор этого материала — Герман Осин, старший архитектор решений в Provectus. Осин сравнивает функциональность восьми UI-инструментов, которые помогают решить проблемы наблюдаемости и мониторинга Apache Kafka. Стоит отметить, что обзор скорее вводный. Он будет полезен для первоначального знакомства с возможностями решений.*

Какие инструменты лучше всего подходят для наблюдения за потоками данных, отслеживания ключевых метрик и устранения неполадок в Apache Kafka?

Apache Kafka — это Open Source-платформа для распределенной потоковой передачи событий. Ее задача — организация высокопроизводительных пайплайнов данных, потоковая аналитика, интеграция данных. Кроме того, она незаменима при работе с критически важными приложениями. Тысячи компаний по всему миру используют Apache Kafka, в том числе 80% компаний из списка Fortune 100.

Apache Kafka — незаменимый инструмент для обработки данных в реальном времени и отслеживания активности приложений. К сожалению, мониторинг кластеров Apache Kafka и управление ими — непростая задача. Решить ее помогают сторонние коммерческие или Open Source-инструменты с графическим интерфейсом и дополнительными функциями в области администрирования и мониторинга.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-295899964024b309a5cf7762050a16ec90efe6a3%2F7fa36e35ce73aec4efd3fbdd110c9b22.png?alt=media)

В этой статье представлен краткий обзор таких инструментов:

1. AKHQ
2. Kowl
3. Kafdrop
4. UI for Apache Kafka
5. Lenses
6. CMAK
7. Confluent CC
8. Conduktor

Но сначала давайте немного углубимся в проблемы наблюдаемости и мониторинга в Apache Kafka.

## Наблюдаемость и мониторинг в Apache Kafka

Apache Kafka — ключевой сервис для любой организации, работающей с данными. Но работа с ванильными кластерами Apache Kafka — вещь довольно мучительная. Кластеры Kafka сложны в настройке, масштабировании и поддержке. Кроме того, они не защищены от ошибок и трудны для наблюдения, что может приводить к критическим проблемам с потоками данных, мешать инженерам отслеживать и решать эти проблемы.

Снизить число ошибок и обезопасить кластеры Apache Kafka можно с помощью эффективного observability-компонента. Повышение наблюдаемости:

* помогает гораздо быстрее устранять проблемы с потоками данных;
* повышает эффективность совместной работы инженеров благодаря единому пониманию потоков данных, управляемых с помощью метаданных;
* облегчает поиск конфиденциальных данных в потоках и помогает соблюдать требования безопасности;
* позволяет быстрее и эффективнее очищать данные. Меньше некорректных dashboard'ов — больше счастливых клиентов.

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

## 8 лучших UI-инструментов мониторинга для кластеров Apache Kafka

Сперва проведем краткое сравнение инструментов для мониторинга кластеров Apache Kafka.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c9754feb32bf67dd7962955451bd847f8e46dbe6%2F42624d330ab5090e600eceb07ecb9e8a.png?alt=media)

Таблица от автора статьи

### AKHQ

* GitHub: [github.com/tchiotludo/akhq](https://github.com/tchiotludo/akhq)
* Лицензия: Apache 2.
* Доступность: бесплатно.
* Плюсы: множество полезных функций.
* Минусы: плохой интерфейс; отсутствует интеграция с KSQL; частичная поддержка реестра схем данных Protobuf.

[AKHQ](https://akhq.io/) (ранее известный как KafkaHQ) — это графический интерфейс Kafka для Apache Kafka, позволяющий инженерам искать и исследовать данные в унифицированной консоли. С помощью AKHQ разработчики и DevOps-инженеры могут управлять топиками, данными топиков, группами подписчиков (consumers), реестрами схем, подключениями и т. д.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e81fec15aed1fd9984efed63ffea47643e022344%2F46d7a54e58700c0034d94bfb54ec1e90.png?alt=media)

Скриншот автора статьи

AKHQ предлагает множество полезных функций, включая управление несколькими кластерами, просмотр сообщений, tailing в реальном времени, аутентификацию, авторизацию, режим только для чтения, реестры схем и управление Kafka Connect. Он поддерживает Avro и совместим с LDAP и RBAC.

Увы, пользовательский интерфейс AKHQ нельзя назвать удобным. Пользователю определенно потребуется время, чтобы привыкнуть к нему.

Кроме того, AKHQ не интегрируется с KSQL и обеспечивает лишь частичную поддержку реестра схем Protobuf. Он также не работает с динамической конфигурацией топиков, увеличением разделов (partition), сменой реплик, топологиями Kafka Streams или визуализацией метрик JMX.

Желающие встроить AKHQ в стек для потоковой передачи данных на AWS должны обратить внимание на то, что он поддерживает [AWS Identity and Access Management (IAM) для Amazon MSK](https://docs.aws.amazon.com/msk/latest/developerguide/iam-access-control.html).

### Kowl (с недавнего времени — Redpanda Console\*)

* Примечание

*Оригинальная статья вышла в сентябре 2021 года. В апреле 2022-го CloudHut, создатели Kowl, стали частью Redpanda.*

* GitHub: [github.com/redpanda-data/console](https://github.com/redpanda-data/console)
* Лицензия: BSL.
* Доступность: частично платный.
* Плюсы: хороший интерфейс.
* Минусы: не хватает многих функций.

Kowl помогает разработчикам проанализировать сообщения в кластерах Apache Kafka и разобраться в том, что на самом деле происходит в этих кластерах.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-4d238b803022b812aed2240908b37aec1f825882%2Fabd568af5c626521bc200ccd6a1cc7f7.png?alt=media)

Скриншот автора статьи

Самое большое преимущество Kowl — потрясающий интерфейс. Он удобен, интуитивен и довольно прост в использовании. Однако у него не так много функций.

Например, Kowl предлагает просмотр сообщений, tailing в реальном времени, поддерживает Protobuf, Avro и Amazon MSK IAM. Однако различные методы входа в систему (Google, GitHub, Okta) и RBAC с групповой синхронизацией доступны только для платного плана.

Кроме того, в Kowl отсутствуют такие функции, как управление несколькими кластерами, динамическая конфигурация топиков, увеличение разделов, смена реплик, управление Kafka Connect, реестр схем, интеграция с KSQL, топологии Kafka Streams, режим только для чтения, а также графики для метрик JMX. С подобной функциональностью Kowl стал бы явным лидером нашего сравнения.

### Kafdrop

* GitHub: [github.com/obsidiandynamics/kafdrop](https://github.com/obsidiandynamics/kafdrop)
* Лицензия: Apache 2.
* Доступность: бесплатно.
* Плюсы: активное сообщество.
* Минусы: средний интерфейс; не хватает множества функций.

[Kafdrop](https://github.com/obsidiandynamics/kafdrop) — веб-интерфейс для просмотра топиков Apache Kafka и групп подписчиков. Инструмент облегчает отображение и обработку информации о брокерах, топиках, разделах и подписчиках. Он также позволяет просматривать сообщения.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c43dea1c761c22022fe4f71394e425ddbe842f1a%2F49cac04e80c824d46416982480740ada.png?alt=media)

Скриншот автора статьи

По большому счету, Kafdrop — инструмент среднего уровня. Его пользовательский интерфейс не впечатляет, и ему не хватает многих функций. Да, он позволяет просматривать брокеры Kafka и группы подписчиков, работать с топиками, смотреть сообщения и отслеживать списки ACL. Также Kafdrop поддерживает Azure Event Hubs. Но как насчет других полезных функций, таких как tailing, реестры схем или read-only режим?

У Kafdrop высокая оценка на GitHub, и он вполне может пригодиться тем, кто ищет толковое и отзывчивое сообщество.

### UI for Apache Kafka

* GitHub: [github.com/provectus/kafka-ui](https://github.com/provectus/kafka-ui)
* Лицензия: Apache 2.
* Доступность: бесплатно.
* Плюсы: хороший интерфейс; гибкость; множество функций.
* Минусы: пока ещё в разработке.

[UI for Apache Kafka](https://github.com/provectus/kafka-ui) — это веб-сервис с открытым кодом и простым и понятным пользовательским интерфейсом для работы с кластерами Apache Kafka. Он позволяет разработчикам отслеживать потоки, находить и устранять проблемы с данными. При этом у UI for Apache Kafka оптимальная производительность.

Лаконичная панель инструментов упрощает отслеживание ключевых показателей кластеров Apache Kafka, в том числе для брокеров, топиков, разделов, производителей (producers) и потребителей (consumers).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-aa7036035fc28e9bbcbdeaa8daf49b76c4d2dadc%2F1d19cb5f6b28cd96ee5bee54fca08fdd.png?alt=media)

Скриншот автора статьи

UI for Apache Kafka выделяется своим удобным интерфейсом, бесплатностью и огромным количеством функций. Он может похвастаться такими возможностями:

* **Просмотр сообщений** в формате Avro, Protobuf, JSON и plain text.
* **Просмотр групп подписчиков** — просмотр фиксированных смещений по разделам, а также совокупного лага и лага по разделам.
* **Настраиваемая аутентификация** — защита системы с помощью функций Github/Gitlab/Google OAuth 2.0.
* **Просмотр брокеров Kafka** — просмотр распределения топиков и разделов, а также статуса контроллера.
* **Просмотр топиков Kafka** — просмотр числа разделов, статуса репликации и кастомной конфигурации.
* **Управление несколькими кластерами** — централизованный мониторинг и управление всеми кластерами.
* **Динамическая конфигурация топиков** — создание и конфигурирование новых топиков.

[Provectus](https://provectus.com/), консалтинговая компания в области ИИ, разрабатывающая UI for Apache Kafka, в скором времени намеревается добавить дополнительные функции, в том числе tailing в реальном времени, интеграцию с KSQL, топологии Kafka Streams, а также визуализацию метрик и диаграмм JMX.

### Lenses

* GitHub: [github.com/lensesio](https://github.com/lensesio)
* Лицензия: BSL.
* Доступность: бесплатно.
* Плюсы: отлично подойдёт для fast-kafka-dev и локальной разработки.
* Минусы: не хватает многих функций.

[Lenses](https://lenses.io/) позиционирует себя как DataOps-платформу для работы в реальном времени и операций с данными для Apache Kafka и Kubernetes. Он делает данные более функциональными и защищенными и устраняет их разрозненность (т. н. data silos). Lenses отлично подходит для потоковой аналитики в реальном времени.

При всем при этом его можно назвать довольно средним инструментом. Имеет смысл использовать Lenses с fast-kafka-dev и для локальной разработки, при этом в нем отсутствуют некоторые функции. Управления несколькими кластерами, просмотра сообщений и поддержки Avro просто недостаточно для многих задач. Управление Kafka Connect в качестве отдельной услуги также не способно повысить привлекательность этого инструмента в глазах пользователей.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-506a69ebf36e714b2651918d4192d7eb2b9a25c2%2F2132843343bf52b8fabf43fc77820525.png?alt=media)

Скриншот автора статьи

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

### CMAK

* GitHub: [github.com/yahoo/CMAK](https://github.com/yahoo/CMAK)
* Лицензия: Apache 2.
* Доступность: бесплатно.
* Плюсы: отлично подходит для переназначения разделов; Ops-инструмент.
* Минусы: ограничен эксплуатационными задачами.

[CMAK](https://github.com/yahoo/CMAK) (ранее известный как Kafka Manager) — комплексный инструмент для управления кластерами Apache Kafka в рамках различных эксплуатационных задач.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c986f49ba59bd9daa702c870a36bcf1f8756ff07%2Fcf1656cfa829e688130b7c7f364e2a18.png?alt=media)

Скриншот автора статьи

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

По большей части CMAK — это прежде всего Ops-инструмент. Кроме того, он отлично зарекомендовал себя в деле перераспределения разделов (partition reassignment).

### Confluent CC

* GitHub: [github.com/confluentinc](https://github.com/confluentinc)
* Лицензия: платная.
* Доступность: платная.
* Плюсы: входит в Confluent Enterprise.
* Минусы: входит в Confluent Enterprise.

Веб-интерфейс [Confluent Control Center](https://www.confluent.io/product/confluent-platform/gui-driven-management-and-monitoring/) позволяет разработчикам и операторам управлять кластерами Apache Kafka, проверять их работоспособность, управлять сообщениями, топиками и реестрами схем. Его также можно использовать для разработки и выполнения запросов ksqlDB.

Важно, что Confluent CC входит в состав Confluent Enterprise и доступен только по подписке. Он предлагает множество функций и хороший пользовательский интерфейс. Confluent CC отлично подходит для тех, кого устраивает зависимость от экосистемы Confluent.

В целом Confluent CC — это больше, чем просто инструмент для работы с топиками. Его возможности обширны и работают отлично, без каких-либо сбоев.

### Conduktor

* GitHub: [github.com/conduktor](https://github.com/conduktor)
* Лицензия: платная (но есть бесплатный вариант для локальной работы).
* Плюсы: множество возможностей.
* Минусы: десктопный инструмент.

[Conduktor](https://www.conduktor.io/) — десктопный клиент для Apache Kafka с удобным интерфейсом для работы в экосистеме Kafka. Есть версии для Windows, Linux и Mac. Conduktor поддерживает все типы кластеров Apache Kafka и может похвастаться большим разнообразием функций.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ef2ee12e39d04d29738682de9ff4bf1dc68deafe%2F8d8bd137fd2f70f45c116a4815ecc05c.png?alt=media)

Скриншот автора статьи

При этом он, пожалуй, проигрывает другим UI-инструментам из списка из-за своей «десктопности». Если это вас не смущает, Conduktor может стать реальной альтернативой Confluent CC.

## Выводы

Использование правильных UI-инструментов для мониторинга и управления кластерами Apache Kafka — ключ к их «здоровью». Простой и удобный пользовательский интерфейс помогает эффективно наблюдать за потоками данных, отслеживать метрики и устранять неполадки, не прибегая к помощи десятков других CLI-инструментов. В результате устраняется часть тонких мест и повышается эффективность.

Эта статья отражает субъективный взгляд автора на основные UI-инструменты для мониторинга кластерами в Apache Kafka и управления ими. Как обычно бывает в таких случаях, в сообществе наверняка найдутся желающие что-то добавить к этом списку.

## P.S.

Читайте также в нашем блоге:

* [«Знакомство с Debezium — CDC для Apache Kafka»](https://habr.com/ru/company/flant/blog/523510/);
* [«Практические истории из наших SRE-будней. Часть 2 — Kafka и переменные от Docker’a в K8s»](https://habr.com/ru/company/flant/blog/510486/);
* [«Определяем подходящий размер для кластера Kafka в Kubernetes»](https://habr.com/ru/company/flant/blog/488920/) (перевод).


# Kafka streams ksqlDb

<https://habr.com/ru/companies/piter/articles/722852/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-488b57ebe8178f91fe13a45f19a7430ab5932e3b%2Ftpqbiahkevbp4edc3f6swdgoi9s.jpeg?alt=media)

Привет, Хаброжители!

Работа с неограниченными и быстрыми потоками данных всегда была сложной задачей. Но Kafka Streams и ksqlDB позволяют легко и просто создавать приложения потоковой обработки. Из книги специалисты по обработке данных узнают, как с помощью этих инструментов создавать масштабируемые приложения потоковой обработки, перемещающие, обогащающие и преобразующие большие объемы данных в режиме реального времени.

Митч Сеймур, инженер службы обработки данных в Mailchimp, объясняет важные понятия потоковой обработки на примере нескольких любопытных бизнес-задач. Он рассказывает о достоинствах Kafka Streams и ksqlDB, чтобы помочь вам выбрать наиболее подходящий инструмент для каждого уникального проекта потоковой обработки. Для разработчиков, не пишущих код на Java, особенно ценным будет материал, посвященный ksqlDB.

## Обзор Kafka Connect

Kafka Connect — это один из пяти API в экосистеме Kafka, он используется для подключения к Kafka внешних хранилищ данных, API и файловых систем. Когда данные находятся в Kafka, их можно обрабатывать, преобразовывать и обогащать с помощью ksqlDB. Перечислю основные компоненты Kafka Connect.

### Коннекторы

Коннекторы — это упакованные фрагменты кода, которые можно внедрить в рабочие процессы (обсудим их чуть ниже). Они способствуют перемещению данных между Kafka и другими системами и делятся на две категории:

* коннекторы-источники читают данные из внешних систем в Kafka;
* коннекторы-приемники записывают данные во внешние системы из Kafka.

### Задачи

Задачи — это единицы работы внутри коннектора. Количество задач может быть разным, что позволяет контролировать объем работы, выполняемой одним рабочим процессом.

### Рабочие процессы

Рабочие процессы (workers) — это процессы JVM, которые выполняют коннекторы. Можно развернуть несколько рабочих процессов, чтобы распараллелить/распределить работу и добиться отказоустойчивости в случае частичного сбоя (например, если один рабочий процесс неожиданно завершится).

### Конвертеры

Конвертеры — это код, осуществляющий сериализацию/десериализацию данных в Connect. Конвертер по умолчанию (например, AvroConverter) должен указываться на уровне рабочего процесса, но также есть возможность задавать конвертеры на уровне коннекторов.

### Кластер Connect

Кластер Connect объединяет один или несколько рабочих процессов Kafka Connect, действующих вместе как группа и перемещающих данные в Kafka и из нее.

На рис. 9.1 показана схема работы всех этих компонентов.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3fbc714f5907c6632c0e2e0467774a5365cce46c%2Fawtisev8cyhhurlr5g4zq_octs0.png?alt=media)

Может показаться, что все это будет трудно усвоить, но по мере чтения главы вы увидите, что ksqlDB значительно упрощает ментальную модель Kafka Connect. А теперь посмотрим на варианты развертывания Kafka Connect для использования с ksqlDB.

## Внешняя и встроенная интеграция с Connect

Интеграция с Kafka Connect в ksqlDB может работать в двух разных режимах. В этом разделе описываются оба режима и рассказывается, когда их использовать. Начнем с внешней интеграции.

## Внешняя интеграция

Если у вас уже есть готовый кластер Kafka Connect или вы хотите развернуть Kafka Connect отдельно от ksqlDB, то существует возможность использовать внешнюю интеграцию с Kafka Connect. Для этого необходимо в ksqlDB настроить URL кластера Kafka Connect, определив свойство ksql.connect.url. После этого ksqlDB сможет обращаться к внешнему кластеру Kafka Connect напрямую, создавать коннекторы и управлять ими. Пример конфигурации внешнего режима показан ниже (он будет сохранен в файле свойств сервера ksqlDB):

```
ksql.connect.url=http://localhost:8083
```

При работе в режиме внешней интеграции любые коннекторы (источники и приемники), необходимые приложению, должны действовать во внешних рабочих процессах. Обратите внимание, что при работе в режиме внешней интеграции рабочие процессы, как правило, размещаются отдельно от сервера ksqlDB, потому что одно из основных преимуществ этого режима заключается в отсутствии необходимости использования ресурсов компьютера совместно с ksqlDB. На рис. 9.2 показана схема работы Kafka Connect в режиме внешней интеграции.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e025c63467e96db1e273d9c2bbadf69ff80e7ab1%2Fd8iezzdyikc3cirswtjkdbzjiiu.png?alt=media)

Вот некоторые ситуации, когда может появиться желание использовать режим внешней интеграции с Kafka Connect:

* требуется независимо масштабировать рабочие нагрузки и ввод/вывод данных и/или изолировать ресурсы для этих различных видов рабочих нагрузок;
* ожидается большой трафик через темы источников/приемников;
* уже есть действующий кластер Kafka Connect.

Далее рассмотрим режим встроенной интеграции, который используем в учебных проектах в этой книге.

## Встроенная интеграция

В режиме встроенной интеграции рабочий процесс Kafka Connect выполняется под управлением той же JVM, что и сервер ksqlDB, в распределенном режиме Kafka Connect. Это означает возможность распределения работы между несколькими взаимодействующими экземплярами рабочего процесса. Количество рабочих процессов Kafka Connect совпадает с количеством серверов ksqlDB в кластере ksqlDB. Режим встроенной интеграции предпочтительнее использовать, когда:

* требуется одновременно масштабировать рабочие нагрузки потоковой обработки и ввода/вывода;
* ожидается небольшой или средний трафик через темы источников/приемников;
* желательны простота поддержки интеграции данных, отсутствие необходимости управлять отдельным развертыванием Kafka Connect и независимо масштабировать рабочие нагрузки интеграции/преобразования данных;
* допускается перезапуск рабочих процессов Kafka Connect с перезапуском серверов ksqlDB;
* допускается совместное использование вычислительных ресурсов/памяти ksqlDB и [Kafka Connect](https://oreil.ly/fK6WQ).

Поскольку в режиме встроенной интеграции серверы ksqlDB сосуществуют вместе с рабочими процессами Kafka Connect, любые коннекторы источников/приемников, необходимые приложению, должны устанавливаться на том же узле, где работают серверы ksqlDB. На рис. 9.3 показана схема работы Kafka Connect в режиме встроенной интеграции.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9a738b80084dfd03bcc137045931639607aba5d5%2Fhqs3pyko19l5rc2hsp-lzbersc0.png?alt=media)

Для запуска в режиме встроенной интеграции необходимо установить конфигурационное свойство ksql.connect.worker.config сервера ksqlDB, указав путь к конфигурациям рабочих процессов Kafka Connect. Не забывайте, что рабочие процессы — это процессы Kafka Connect, в рамках которых фактически действуют коннекторы источников и приемников. Вот пример настройки этого свойства в файле свойств сервера ksqlDB:

```
ksql.connect.worker.config=/etc/ksqldb-server/connect.properties
```

Но какая информация должна быть определена в конфигурационном файле рабочего процесса, на который ссылается свойство ksql.connect.worker.config? Мы поговорим об этом в следующем разделе.

## Настройка рабочих процессов Connect

Kafka Connect имеет множество параметров настройки, подробно описанных в официальной документации Apache Kafka (<https://oreil.ly/UWnW3>). В этом разделе будут представлены только наиболее важные из них на примере настройки рабочего процесса Kafka Connect. При запуске в режиме встроенной интеграции настройки следует определить в файле (например, connect.properties) и сослаться на него в свойстве ksql.connect.worker.config в конфигурации сервера ksqlDB. При запуске в режиме внешней интеграции настройки рабочего процесса передаются в аргументах запуска Kafka Connect. Пример конфигурации показан в следующем листинге:

```
bootstrap.servers=localhost:9092 (1)
group.id=ksql-connect-cluster (2)

key.converter=org.apache.kafka.connect.storage.StringConverter (3)
value.converter=org.apache.kafka.connect.storage.StringConverter (4)

config.storage.topic=ksql-connect-configs (5)
offset.storage.topic=ksql-connect-offsets
status.storage.topic=ksql-connect-statuses

errors.tolerance=all (6)

plugin.path=/opt/confluent/share/java/ (7)
```

(1) Список пар хост/порт брокеров Kafka, которые следует использовать для подключения к кластеру Kafka.

(2) Строковый идентификатор кластера Connect, которому принадлежит этот рабочий процесс. Рабочие процессы, настроенные с одним и тем же идентификатором group.id, принадлежат одному кластеру и могут совместно использовать рабочую нагрузку для выполнения коннекторов.

(3) «Класс конвертера для преобразования между форматом Kafka Connect и сериализованной формой. Управляет форматом ключей в сообщениях, записываемых в Kafka или извлекаемых из него, а поскольку класс не зависит от коннекторов, это позволяет любому коннектору работать с любым форматом сериализации. Примерами распространенных форматов могут служить JSON и Avro». (Документация Connect; <https://oreil.ly/08AW5>.)

(4) «Класс конвертера для преобразования между форматом Kafka Connect и сериализованной формой. Управляет форматом значений в сообщениях, записываемых в Kafka или извлекаемых из него, а поскольку класс не зависит от коннекторов, это позволяет любому коннектору работать с любым форматом сериализации. Примерами распространенных форматов могут служить JSON и Avro». (Документация Connect.)

(5) Kafka Connect использует несколько дополнительных тем для хранения информации с настройками коннекторов и задач. Здесь мы просто используем стандартные имена этих тем с префиксом ksql-, потому что будем работать в режиме встроенной интеграции (то есть рабочие процессы будут выполняться под управлением той же JVM, что и экземпляры серверов ksqlDB).

(6) Свойство errors.tolerance позволяет настроить политику обработки ошибок по умолчанию в Kafka Connect. Допустимые значения: none (немедленный отказ при возникновении ошибки) и all (полное игнорирование ошибок или, при использовании со свойством errors.deadletterqueue.topic.name, пересылка всех ошибок в тему Kafka по вашему выбору).

(7) Список путей в файловой системе, перечисленных через запятую, где находятся плагины (коннекторов, конвертеров, преобразователей). Как устанавливать коннекторы, вы увидите далее в этой главе.

Как видите, основная масса конфигурационных параметров рабочих процессов довольно проста. Тем не менее некоторые настройки стоит изучить подробнее, потому что они связаны с решением важной задачи сериализации данных — это свойства конвертеров (key.converter и value.converter). В следующем разделе мы детально рассмотрим конвертеры и форматы сериализации.

## Конвертеры и форматы сериализации

Классы конвертеров, используемых в Kafka Connect, играют важную роль в сериализации и десериализации данных. В нашем учебном проекте Hello, world, представленном в предыдущей главе (см. раздел «Учебный проект» главы 8), мы использовали инструкцию из примера 9.1, чтобы создать поток в ksqlDB.

**Пример 9.1. Создание потока, читающего данные из темы users**

```
CREATE STREAM users (
    ROWKEY INT KEY,
    USERNAME VARCHAR
) WITH (
    KAFKA_TOPIC='users',
    VALUE_FORMAT='JSON'
);
```

Эта инструкция сообщает ksqlDB, что тема users (KAFKA\_TOPIC='users') содержит записи со значениями, сериализованными в формат JSON (VALUE\_FORMAT='JSON'). Если есть свой производитель, записывающий в тему данные в формате JSON, то довольно легко рассуждать о формате. Но что, если Kafka Connect используется, например, для потоковой передачи в Kafka данных из PostgreSQL? В какой формат сериализуются данные из PostgreSQL, когда они записываются в Kafka?

Здесь в игру вступают настройки конвертеров. Для управления форматами сериализации ключей и значений записей, которые обрабатывает Kafka Connect, можно настроить свойства key.converter и value.converter, определив в них соответствующие классы конвертеров. В табл. 9.1 перечислены наиболее часто используемые классы конвертеров и соответствующие им форматы сериализации ksqlDB (то есть значение, которое указывается в свойстве VALUE\_FORMAT при создании потока или таблицы, как было показано в примере 9.1).

В табл. 9.1 также отмечено, какие конвертеры опираются на Confluent Schema Registry для хранения схем записей, что может пригодиться, если потребуется более компактный формат сообщений. Schema Registry позволяет хранить схемы записей, то есть имена и типы полей, вне самих сообщений.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ebaa65297727e037bd73241f0c505ef98d17f726%2Fwel1fqwa4oqzzz7kemwbom7sjog.png?alt=media)

Для каждого конвертера в табл. 9.1, требующего реестра схем, нужно добавить дополнительное конфигурационное свойство: { key | value }.converter.schema.registry.url. Например, в этой книге мы будем работать в основном с данными Avro, поэтому, чтобы коннекторы записывали значения в этом формате, можно обновить конфигурацию рабочего процесса, как показано в примере 9.2.

**Пример 9.2. Конфигурация рабочего процесса, использующего AvroConverter для преобразования значений записей**

```
bootstrap.servers=localhost:9092
group.id=ksql-connect-cluster

key.converter=org.apache.kafka.connect.storage.StringConverter
value.converter=io.confluent.connect.avro.AvroConverter (1)
value.converter.schema.registry.url=http://localhost:8081 (2)

config.storage.topic=ksql-connect-configs
offset.storage.topic=ksql-connect-offsets

status.storage.topic=ksql-connect-statuses

plugin.path=/opt/confluent/share/java/
```

(1) Использовать AvroConverter для сериализации значений в формат Avro.

(2) Конвертеру Avro требуется Confluent Schema Registry для хранения схем записей, поэтому нужно указать URL этого реестра схем, определив свойство value.converter.schema.registry.url.

Сейчас, узнав, как задать формат сериализации данных в Kafka Connect, и подготовив конфигурацию для рабочих процессов в Kafka Connect (см. пример 9.2), перейдем к учебному проекту и на практике установим и используем некоторые коннекторы.

## Учебный проект

В этом учебном проекте мы используем коннектор-источник JDBC для потоковой передачи данных из PostgreSQL в Kafka. Затем создадим коннектор-приемник Elasticsearch для записи данных из Kafka в Elasticsearch. Полный код этого проекта и инструкции по настройке окружения (включая экземпляр PostgreSQL и Elasticsearch) можно найти в репозитории на GitHub (<https://oreil.ly/7ImWJ>).

Начнем с установки коннекторов.

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

Существует два основных способа установки коннекторов источников и приемников:

* вручную;
* автоматически, через Confluent Hub.

Ручная установка может отличаться для разных реализаций коннекторов и зависит от того, как разработчики коннектора решат распространять артефакт (артефакт коннектора обычно включает один или несколько файлов JAR). Однако обычно процедура установки предполагает загрузку артефакта непосредственно с веб-сайта или из репозитория артефактов, такого как Maven Central или Artifactory. После загрузки файлы JAR помещаются в место, указанное в конфигурационном свойстве plugin.path.

Более простой метод загрузки коннекторов, который будет использоваться в этой книге, позволяет устанавливать коннекторы с помощью инструмента командной строки, разработанного в Confluent. Этот инструмент с названием confluent-hub можно установить, следуя инструкциям в документации Confluent (<https://oreil.ly/31Sd9>). После установки Confluent Hub установка самих коннекторов не вызывает никаких сложностей. Вот синтаксис команды установки коннектора:

```
confluent-hub install <владелец>/<компонент>:<версия> [параметры]
```

Например, следующая команда установит коннектор-приемник Elasticsearch:

```
confluent-hub install confluentinc/kafka-connect-elasticsearch:10.0.2 \
    --component-dir /home/appuser \ (1)
    --worker-configs /etc/ksqldb-server/connect.properties \ (2)
    --no-prompt (3)
```

(1) Каталог, куда должен быть установлен коннектор.

(2) Местоположение конфигурационных файлов рабочих процессов. Место установки (определяется параметром --component-dir) будет добавлено в plugin.path, если это еще не было сделано.

(3) Чтобы обойти стороной интерактивные шаги (например, подтверждение установки, принятие лицензионного соглашения и т. д.), можно разрешить интерфейсу командной строки работать с рекомендуемыми значениями/значениями по умолчанию. Это полезно для установки из сценария.

Точно так же можно установить коннектор-источник PostgreSQL:

```
confluent-hub install confluentinc/kafka-connect-jdbc:10.0.0 \
    --component-dir /home/appuser/ \
    --worker-configs /etc/ksqldb-server/connect.properties \
    --no-prompt
```

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

## Создание экземпляров коннекторов в ksqlDB

Вот как выглядит синтаксис создания коннектора:

```
CREATE { SOURCE | SINK } CONNECTOR [ IF NOT EXISTS ] <identifier> WITH(
    property_name = expression [, ...]);
```

Предположим, что у нас уже есть экземпляр PostgreSQL, доступный по адресу postgres:5432, в этом случае можно установить коннектор-источник для чтения из таблицы titles, выполнив следующую команду в ksqlDB:

```
CREATE SOURCE CONNECTOR `postgres-source` WITH( (1)
    "connector.class"='io.confluent.connect.jdbc.JdbcSourceConnector', (2)
    "connection.url"=
        'jdbc:postgresql://postgres:5432/root?user=root&password=secret', (3)
    "mode"='incrementing', (4)
    "incrementing.column.name"='id', (5)
    "topic.prefix"='', (6)
    "table.whitelist"='titles', (7)
    "key"='id'); (8)
```

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

(2) Класс Java коннектора.

(3) Коннектору-источнику JDBC требуется URL для подключения к хранилищу данных (в данном случае к базе данных PostgreSQL).

(4) Коннектор-источник ОВИС поддерживает несколько режимов запуска. Поскольку мы предполагаем передавать любые новые записи, добавляемые в таблицу titles и имеющие столбец с автоматическим приращением значения, можно установить режим incrementing. Этот и другие режимы, поддерживаемые данным коннектором, подробно описаны в документации (<https://oreil.ly/w8Grb>).

(5) Имя столбца с автоматическим приращением, который коннектор-источник будет использовать для определения новых строк.

(6) Каждая таблица передается в отдельную тему (например, таблица titles будет передаваться в тему titles). При желании можно задать префикс для имени темы (например, если настроить префикс ksql-, данные будут передаваться в тему ksql-titles). В этом проекте мы не будем использовать префикс.

(7) Список таблиц для потоковой передачи в Kafka.

(8) Значение, используемое в роли ключа записи.

После выполнения инструкции CREATE SOURCE CONNECTOR в консоли должно появиться сообщение, подобное следующему:

```
Message
-----------------------------------
Created connector postgres-source
-----------------------------------
```

Теперь создадим коннектор-приемник для вывода записей из приложения в Elasticsearch. Эта инструкция очень похожа на инструкцию создания коннектора-источника:

```
CREATE SINK CONNECTOR `elasticsearch-sink` WITH(
    "connector.class"=
        'io.confluent.connect.elasticsearch.ElasticsearchSinkConnector',
    "connection.url"='http://elasticsearch:9200',
    "connection.username"='',
    "connection.password"='',
    "batch.size"='1',
    "write.method"='insert',
    "topics"='titles',
    "type.name"='changes',
    "key"='title_id');
```

Как видите, конфигурации разных коннекторов различаются. Большинство имен конфигурационных параметров говорят сами за себя, а определение назначения остальных я оставляю вам в качестве самостоятельного упражнения. Соответствующие описания конфигурационных параметров ElasticsearchSinkConnector можно найти в справочнике по настройке Elasticsearch Sink Connector (<https://oreil.ly/o8h7j>). И снова после выполнения инструкции CREATE SINK CONNECTOR в консоли должно появиться сообщение:

```
Message
--------------------------------------
Created connector elasticsearch-sink
--------------------------------------
```

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

## Вывод списка коннекторов

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

```
{ LIST | SHOW } [ { SOURCE | SINK } ] CONNECTORS
```

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

```
SHOW CONNECTORS;
```

В консоли должен появиться такой вывод:

```
Connector Name     | Type   | Class    | Status
---------------------------------------------------------------------
postgres-source    | SOURCE | ...      | RUNNING (1/1 tasks RUNNING)
elasticsearch-sink | SINK   | ...      | RUNNING (1/1 tasks RUNNING)
```

Команда SHOW CONNECTORS выводит некоторую полезную информацию об активных коннекторах, включая их состояние. В данном случае оба коннектора имеют по одной задаче в состоянии RUNNING. Другие состояния, которые можно увидеть, включают: UNASSIGNED, PAUSED, FAILED и DESTROYED. Увидев такое состояние, как FAILED, вы наверняка захотите выяснить причину. Например, если коннектор postgres-source потеряет соединение с базой данных PostgreSQL (это можно сымитировать, просто остановив экземпляр PostgreSQL), то появится такой вывод:

```
Connector Name     | Type   | Class    | Status
---------------------------------------------------------------------
postgres-source    | SOURCE | ...      | FAILED
--------------------------------------------------------------
```

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

## Получение описаний коннекторов

ksqlDB упрощает получение состояния коннекторов, предлагая инструкцию DESCRIBE CONNECTOR. Например, если коннектор postgres-source потеряет соединение с хранилищем данных, как обсуждалось в предыдущем разделе, можно попробовать запросить его описание, чтобы получить дополнительную информацию. Например:

```
DESCRIBE CONNECTOR `postgres-source`;
```

Если имеет место ошибка, то в консоли появится вывод с трассировкой этой ошибки, как показано ниже:

```
Name                 : postgres-source
Class                : io.confluent.connect.jdbc.JdbcSourceConnector
Type                 : source
State                : FAILED
WorkerId             : 192.168.65.3:8083
Trace                : org.apache.kafka.connect.errors.ConnectException (1)

Task ID | State  | Error Trace
---------------------------------------------------------------------
0       | FAILED | org.apache.kafka.connect.errors.ConnectException (2)
```

(1) Трассировка стека в этом примере приводится неполностью, но в случае фактического сбоя вы должны увидеть полную трассировку стека исключения.

(2) Разбивка по задачам. Задачи могут находиться в разных состояниях (например, одни могут находиться в состоянии RUNNING, а другие — в состоянии UNASSIGNED, FAILED и т. д.).

Однако чаще вы будете видеть задачи в работоспособном состоянии. Вот пример вывода инструкции DESCRIBE CONNECTOR:

```
Name                 : postgres-source
Class                : io.confluent.connect.jdbc.JdbcSourceConnector
Type                 : source
State                : RUNNING
WorkerId             : 192.168.65.3:8083

Task ID | State   | Error Trace
---------------------------------
0       | RUNNING |
--------------------------------
```

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

## Удаление коннекторов

Удаление коннекторов может понадобиться для их перенастройки или безвозвратного удаления. Синтаксис удаления коннектора:

```
DROP CONNECTOR [ IF EXISTS ] <идентификатор>
```

Например, чтобы удалить коннектор PostgreSQL, можно выполнить следующую инструкцию:

```
DROP CONNECTOR `postgres-source` ;
```

После удаления коннектора в консоли должно появиться подтверждение, что коннектор действительно удален. Например:

```
Message
-------------------------------------
Dropped connector "postgres-source"
-------------------------------------
```

## Проверка коннектора-источника

Один из быстрых способов проверить работоспособность коннектора-источника PostgreSQL — записать некоторые данные в базу данных, а затем вывести содержимое темы. Например, создадим таблицу titles в экземпляре Postgres и заполним ее некоторыми данными:

```
CREATE TABLE titles (
    id           SERIAL PRIMARY KEY,
    title        VARCHAR(120)
);

INSERT INTO titles (title) values ('Stranger Things');
INSERT INTO titles (title) values ('Black Mirror');
INSERT INTO titles (title) values ('The Office');
```

> Это инструкция PostgreSQL, а не ksqlDB.

Наш коннектор-источник PostgreSQL должен автоматически извлечь данные из этой таблицы в тему titles. Чтобы убедиться в этом, воспользуемся инструкцией PRINT:

```
PRINT `titles` FROM BEGINNING ;
```

ksqlDB должен вывести:

```
Key format: JSON or KAFKA_STRING
Value format: AVRO or KAFKA_STRING
rowtime: 2020/10/28 ..., key: 1, value: {"id": 1, "title": "Stranger Things"}
rowtime: 2020/10/28 ..., key: 2, value: {"id": 2, "title": "Black Mirror"}
rowtime: 2020/10/28 ..., key: 3, value: {"id": 3, "title": "The Office"
```

Обратите внимание, что ksqlDB, как сообщается в первых двух строках вывода, пытается определить формат ключей и значений записей в теме titles. Поскольку для ключей у нас используется StringConverter, а для значений — AvroConverter (см. пример 9.2), этот результат вполне ожидаем.

Точно так же для проверки коннектора-приемника нужно создать принимающую тему, а затем запросить данные из нижестоящего хранилища. Мы оставим это читателю в качестве самостоятельного упражнения (можете заглянуть в репозиторий \[<https://oreil.ly/gs18X>], и вы увидите, как это сделать).

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

## Взаимодействие с кластером Kafka Connect напрямую

Иногда может потребоваться взаимодействовать с кластером Kafka Connect напрямую, без участия ksqlDB. Например, некоторые конечные точки Kafka Connect предоставляют информацию, недоступную в ksqlDB, и позволяют выполнять важные действия, такие как повторный запуск задач, потерпевших неудачу. Я не собираюсь давать здесь исчерпывающие инструкции по работе с Connect API, а просто приведу несколько примеров запросов, которые вы можете выполнить в своем кластере Connect. Они перечислены в следующей таблице.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-814e970ab1cea5bcf7acc895c74accf04808e58d%2F7lcgpv2fxwkftg3zci8aewnyitw.png?alt=media)

Наконец, посмотрим, как проверить схемы при использовании форматов сериализации, применяющих Confluent Schema Registry.

## Анализ управляемых схем

Некоторые форматы сериализации из перечисленных в табл. 9.1 требуют Confluent Schema Registry для хранения схем записей. При их использовании Kafka Connect будет автоматически сохранять схемы в реестре Confluent Schema Registry. В табл. 9.2 показаны примеры запросов к конечной точке Schema Registry, которые помогут проанализировать управляемые схемы.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-38765694784f8021bf45f0acfadbf6f3c493d63b%2Fs4v2smndmn8vtphc6-bvhwh1tkc.png?alt=media)

Полную справку по API можно найти в справочнике по Schema Registry API (<https://oreil.ly/Q26Si>).

Более подробно с книгой можно ознакомиться на [сайте издательства](https://www.piter.com/product/kafka-streams-i-ksqldb-dannye-v-realnom-vremeni):

» [Оглавление](https://www.piter.com/product/kafka-streams-i-ksqldb-dannye-v-realnom-vremeni#Oglavlenie-1)

» [Отрывок](https://www.piter.com/product/kafka-streams-i-ksqldb-dannye-v-realnom-vremeni#Otryvok-1)

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.

Для Хаброжителей скидка 25% по купону — **Kafka Streams**


# RabbitMQ

## RabbitMQ

<https://www.rabbitmq.com/>

<figure><img src="https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e709a1f937514fc428fc9a3969d65a43cf6b63d6%2Fimage.png?alt=media" alt="" width="375"><figcaption></figcaption></figure>

RabbitMQ is lightweight and easy to deploy on premises and in the cloud. It supports multiple messaging protocols and [streaming](https://www.rabbitmq.com/streams.html). RabbitMQ can be deployed in distributed and federated configurations to meet high-scale, high-availability requirements.

RabbitMQ runs on many operating systems and cloud environments, and provides a [wide range of developer tools for most popular languages](https://www.rabbitmq.com/devtools.html).

See how other people are using RabbitMQ:

## **OSS RabbitMQ Features**

\*\***Asynchronous Messaging Supports** [**multiple messaging protocols**](https://www.rabbitmq.com/protocols.html)**,** [**message queuing**](https://www.rabbitmq.com/tutorials/tutorial-two-python.html)**,** [**delivery acknowledgement**](https://www.rabbitmq.com/reliability.html)**,** [**flexible routing to queues**](https://www.rabbitmq.com/tutorials/tutorial-four-python.html)**,** [**multiple exchange type**](https://www.rabbitmq.com/tutorials/amqp-concepts.html)**. Developer Experience Deploy with** [**Kubernetes, BOSH, Chef, Docker and Puppet**](https://www.rabbitmq.com/download.html)**. Develop cross-language messaging with favorite programming languages such as: Java, .NET, PHP, Python, JavaScript, Ruby, Go,** [**and many others**](https://www.rabbitmq.com/devtools.html)**. Distributed Deployment Deploy as** [**clusters**](https://www.rabbitmq.com/clustering.html) **for high availability and throughput;** [**federate**](https://www.rabbitmq.com/federation.html) **across multiple availability zones and regions. Enterprise & Cloud Ready Pluggable** [**authentication**](https://www.rabbitmq.com/authentication.html)**,** [**authorisation**](https://www.rabbitmq.com/access-control.html)**, supports** [**TLS**](https://www.rabbitmq.com/ssl.html) **and** [**LDAP**](https://www.rabbitmq.com/ldap.html)**. Lightweight and easy to deploy in public and private clouds. Tools & Plugins Diverse array of** [**tools and plugins**](https://www.rabbitmq.com/devtools.html) **supporting continuous integration, operational metrics, and integration to other enterprise systems. Flexible** [**plug-in approach**](https://www.rabbitmq.com/plugins.html) **for extending RabbitMQ functionality. Management & Monitoring HTTP-API, command line tool, and UI for** [**managing and monitoring**](https://www.rabbitmq.com/management.html) **RabbitMQ.**


# DB

[MySQL](/readme/architect/db/mysql)

[Postgres](/readme/architect/db/postgres)

[Vitess - Scalable. Reliable. MySQL-compatible. Cloud-native. Database.](/readme/architect/db/vitess-scalable-reliable-mysql-compatible-cloud)


# MySQL

[Auto sharding](/readme/architect/db/mysql/auto-sharding)

[MariaDB Zabbix monitoring](/readme/architect/db/mysql/mariadb-zabbix-monitoring)

[MySQL and MariaDB replication with Zabbix monitoring](/readme/architect/db/mysql/mysql-and-mariadb-replication-with-zabbix-monitori)


# Auto sharding

<https://habr.com/ru/companies/otus/articles/703790/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6d9245d5eba55a756d33c985f6a6e955d63db885%2F89e6350727eb8fb8fe5eae56b32a7158.png?alt=media)

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

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

SQL — это предметно-ориентированный язык, используемый для управления данными в реляционной системе управления базами данных (РСУБД - RDBMS). Он стабилен, существует с 1970-х годов, и работает. Есть причина, по которой он повсеместно распространен и сегодня.

Одним из преимуществ баз данных NOSQL, таких как MongoDB, например, традиционно является возможность горизонтального масштабирования по сравнению с SQL. При бессерверном подходе вертикальное масштабирование легко и практически доступно с любым вариантом SQL или NOSQL, находящимся в сфере возможностей вашего облачного провайдера.

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

Горизонтальное масштабирование, в свою очередь, с помощью SQL традиционно не реализуется. В отличие от него, MongoDB использует шардинг, что дает ей возможность создавать наборы реплик и, таким образом, осуществлять горизонтальное масштабирование.

Но что, если нам все-таки нужна база данных SQL для управления и доступа к структурированным данным и при этом мы хотим получить превосходную производительность и надежность, на которые мы рассчитывали? Неужели мы навсегда застряли с FOMO (синдром упущенной выгоды) горизонтального шардинга?

Существует технология, позволяющая устранить этот недостаток баз данных MySQL. Я говорю о [Vitess](https://vitess.io/), на которой работают многие крупнейшие и наиболее посещаемые сайты в Интернете, такие как YouTube, Pinterest, Slack и другие. Vitess — это ранний проект Go, который реализует горизонтальный шардинг и управляет им, повышает производительность запросов и устраняет накладные расходы памяти на соединения для баз данных MySQL.

Звучит здорово, правда? Я лично согласен! Но есть одна загвоздка: Vitess может быть достаточно сложным при внедрении в продакшн. Для успеха вам, скорее всего, понадобится команда инженеров с опытом работы в этой области.

[PlanetScale](https://planetscale.com/) функционирует на базе Vitess. Это наполовину обеспечивает его потрясающую эффективность, с моей точки зрения, поскольку они упростили для вас этот сложный процесс.

PlanetScale — это совместимая с MySQL бессерверная платформа баз данных с горизонтальным шардингом и неограниченным количеством соединений. Вертикальное и горизонтальное масштабирование больше не является проблемой, и это позволит вам обеспечить задел на будущее без каких-либо неудобств в этом плане.

Но есть еще одно преимущество PlanetScale, и я считаю его инновацией, которая устраняет еще один недостаток баз данных SQL: неблокируемые миграции схемы с нулевым временем простоя.

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

Такое происходит.

Но как становится осуществима эта неблокируемая миграция схемы с нулевым временем простоя? С помощью ветвления (бранчинг — branching). Ветвление, вероятно, напомнит вам, как и мне, ту же концепцию, что и в случае с git branches (команда для управления ветвями в репозитории Git).

У вас есть ветвь, назовем ее main (главная), вы добавляете данные и переводите ее в продакшн. Схема теперь заблокирована, и будущие изменения схемы потребуют от вас создания development-ветви (разработки) main-ветви. Когда будет установлено, что в development-ветви нет конфликтов схем, вы сможете создать запрос на деплой, чтобы перевести ее в продакшн без каких-либо сбоев в работе вашего сервиса.

Мое впечатление от PlanetScale на данный момент таково: это высококачественный продукт, который может помочь вам с легкостью начать работу. Вы получаете самую передовую производительность и масштабирование. Мне также приятно, что он так хорошо работает с Prisma, внося лишь некоторые изменения в мой обычный рабочий процесс.

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

Всех желающих приглашаем на открытое занятие «Алгоритмы распределенного консенсуса (RAFT, PAXOS)». На занятии разберем, для чего используются алгоритмы распределенного консенсуса и какие они бывают. Посмотрим, как работают алгоритмы RAFT, PAXOS, а также византийский консенсус. Регистрация открыта [по ссылке.](https://otus.pw/SCVA/)


# MariaDB Zabbix monitoring

<https://habr.com/ru/companies/first/articles/689138/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fae3519aae76e179b5ea9be47e0868b45055ea52%2Faceaa5a9b8721b9c61b270e4913759a8.png?alt=media)

От правильной настройки и надежной работы сервера СУБД зависит работоспособность и быстродействие интернет-магазинов, работающих на сервисе SAAS. То же самое относится к любым сайтам, если им нужна база данных.

Очень часто в качестве СУБД используется MySQL или MariaDB.

Из нашей статьи вы узнаете, как установить сервер MariaDB в ОС Debian 11, как оптимизировать его параметры сразу после установки и как контролировать работу MariaDB вручную и с помощью Zabbix.

Cразу после установки конфигурация сервера MySQL или MariaDB обычно сильно отличается от оптимальной и требует обязательной настройки. В процессе работы могут появляться новые базы данных, новые таблицы и запросы. При этом может потребоваться дополнительная настройка параметров работы СУБД.

Отличия MariaDB от MySQL [рассмотрены здесь](https://mariadb.com/kb/ru/mariadb-vs-mysql-features/). А в [этой статье](https://mariadb.com/kb/en/mariadb-vs-mysql-compatibility/) рассказано о совместимости. В плане настройки конфигурации и мониторинга для MySQL и MariaDB используются схожие процедуры.

## Установка сервера MariaDB в ОС Debian 11

Если на сервере есть панель управления, такая как ISPmanager или Hestia Control Panel, то сервер MariaDB или MySQL у вас, скорее всего, уже установлен. Обычно MariaDB устанавливается вместе с панелью, если это задано при установке. Некоторые панели, например, ISPmanager, позволяют добавить MySQL или MariaDB уже после установки панели.

Если панель не используется, установите MariaDB вручную следующими командами:

```
# apt update
# apt upgrade
# apt install mariadb-server
```

После установки сервиса проверьте, что он запустился и находится в состоянии enabled, то есть будет запущен автоматически после перезагрузки ОС:

```
~# systemctl status mariadb
● mariadb.service - MariaDB 10.5.15 database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2022-08-29 09:44:00 MSK; 16s ago
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
    Process: 814172 ExecStartPre=/usr/bin/install -m 755 -o mysql -g root -d /var/run/mysqld (code=exited, status=0/SUCCESS)
    Process: 814173 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
    Process: 814175 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= ||   VAR=`cd /usr/bin/..; /usr/bin/galera_recovery`; [ $? -eq 0 ]   && systemctl set-environment _WS>
    Process: 814241 ExecStartPost=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS)
    Process: 814243 ExecStartPost=/etc/mysql/debian-start (code=exited, status=0/SUCCESS)
   Main PID: 814222 (mariadbd)
     Status: "Taking your SQL requests now..."
      Tasks: 19 (limit: 4676)
     Memory: 68.9M
        CPU: 746ms
     CGroup: /system.slice/mariadb.service
             └─814222 /usr/sbin/mariadbd
```

## Проверка конфигурации MariaDB

Для оценочной проверки параметров MariaDB или MySQL можно использовать утилиту MySQLTuner, [опубликованную на Github](https://github.com/major/MySQLTuner-perl).

После того как вы добавили на сервер базы данных пользователей и сервер проработал хотя бы сутки, загрузите утилиту MySQLTuner и запустите ее следующим образом:

```
# wget http://mysqltuner.pl/ -O mysqltuner.pl
# perl mysqltuner.pl
```

Если вы запускаете эту утилиту в Debian 11 от пользователя root, то вам не нужно указывать пароль root сервера MariaDB.

Через некоторое время утилита после запуска выведет на консоль подробный отчет. Внимательно просмотрите его, особенно уделите внимание строкам, отмеченным восклицательными знаками:

```
[!!] InnoDB is enabled but isn't being used
…
[!!] There is no basic password file list!
…
[--] InnoDB is enabled.
[!!] No tables are Innodb
…
[!!] Ratio InnoDB log file size / InnoDB Buffer pool size (75%): 96.0M * 1 / 128.0M should be equal to 25%
```

В конце отчета вы найдете раздел рекомендаций:

```
-------- Recommendations ------------------------------------
General recommendations:
    Add skip-innodb to MySQL configuration to disable InnoDB
    MySQL was started within the last 24 hours - recommendations may be inaccurate
    Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=1
    Performance schema should be activated for better diagnostics
    Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL
    Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU
Variables to adjust:
    skip-name-resolve=1
    performance_schema=ON
    innodb_log_file_size should be (=32M) if possible, so InnoDB total log files size equals 25% of buffer pool size.
```

Здесь указаны изменения, которые нужно внести в файл конфигурации MariaDB. Обратите внимание, что если сервер СУБД проработал менее 24 часов, то рекомендации могут быть неточными.

## Настройка конфигурации MariaDB

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

Для повышения производительности в первую очередь необходимо правильно задать размер буферов. При этом нужно сделать так, чтобы общий объем памяти, потребляемый MariaDB, не превысил разумных значений. Иначе памяти может не хватить, и сервис MariaDB завершит свою работу аварийно, либо вообще не сможет стартовать.

Ни в коем случае не изменяйте значения параметров, не разобравшись предварительно, на что они влияют. Даже если вы получили такие рекомендации от утилиты MySQLTuner.

Конфигурация сервиса MariaDB находится в файле /etc/mysql/mariadb.conf.d/50-server.cnf. Перед изменениями сделайте копию файла конфигурации, например, в своем рабочем каталоге. Тогда, если с измененной конфигурацией сервер не запустится, вы всегда сможете восстановить старый вариант.

После редактирования файла конфигурации перезапустите MariaDB и убедитесь, что сервис запустился:

```
# systemctl restart mariadb
# systemctl -l status mariadb
```

Если сервис mariadb не запустился, посмотрите журнал ошибок и найдите там возможную причину.

Чтобы понять, где находится файл журнала ошибок, откройте файл /etc/mysql/mariadb.conf.d/50-server.cnf и проверьте параметр log\_error:

```
#log_error = /var/log/mysql/error.log
```

В новых версиях MariaDB этот параметр закрыт символом комментария. Это означает, что для журнала используется сервис journald.

В этом случае содержимое журнала MariaDB можно посмотреть так:

```
# journalctl -u mariadb
```

Хорошую статью по использованию journalctl [можно найти здесь](https://habr.com/ru/company/ruvds/blog/533918/).

Теперь, когда вы знаете, как редактировать конфигурацию MariaDB и где смотреть журнал ошибок, перейдем к оптимизации параметров.

### Отключите обратный поиск по DNS

Для того чтобы сервис не тратил время на поиск адреса IP клиента в DNS, добавьте в файл конфигурации следующую строку:

```
skip-name-resolve=1
```

Это позволит увеличить производительность при большом количестве запросов со стороны клиентов с разных адресов IP.

### Настройте key\_buffer\_size

Если в ваших базах данных используются таблицы типа MyISAM, нужно настроить размер буфера для индексных блоков. Этот размер задается параметром key\_buffer\_size и по умолчанию составляет всего 128 Мбайт.

Оптимальный размер этого буфера позволяет исключить обращения к диску для чтения блоков индекса.

Чтобы узнать текущий размер буфера, введите в консольном приглашении MariaDB такую команду:

```
MariaDB [(none)]> show variables like 'key_buffer_size';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| key_buffer_size | 134217728 |
+-----------------+-----------+
```

В документации MariaDB рекомендуется установить размер key\_buffer\_size равной примерно четверти объема памяти, доступной на сервере: <https://mariadb.com/kb/en/optimizing-key_buffer_size/>.

Для более точной установки размера буфера нужно сравнить значения переменных key\_read\_requests и key\_reads. Первая из них содержит общее количество запросов на чтение индекса, а вторая — количество запросов, для выполнения которых пришлось читать данные с диска.

Значение переменных можно посмотреть так:

```
MariaDB [(none)]> SHOW STATUS LIKE "key%";
+------------------------+-------------+
| Variable_name          | Value       |
+------------------------+-------------+
| Key_blocks_not_flushed | 0           |
| Key_blocks_unused      | 1469932     |
| Key_blocks_used        | 250318      |
| Key_blocks_warm        | 55690       |
| Key_read_requests      | 24417275945 |
| Key_reads              | 13131669    |
| Key_write_requests     | 38609729    |
| Key_writes             | 17155447    |
+------------------------+-------------+
8 rows in set (0.001 sec)
```

Чем меньше отношение значения key\_reads к значению key\_read\_requests, тем лучше. Отношение 1:100 еще приемлемо, а вот отношение 1:10 уже очень плохое — нужна оптимизация размера key\_buffer\_size.

При выделении памяти учтите, что на сервере работают и другие сервисы, помимо СУБД, которым также требуется память.

Если вы проверяете настройки при помощи утилиты [mysqltuner.pl](http://mysqltuner.pl/), то при недостатке свободной памяти на сервере для реализации текущих настроек MariaDB вы получите такое предупреждение:

```
*** MySQL's maximum memory usage is dangerously high ***
*** Add RAM before increasing MySQL buffer variables ***
```

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

Например, если вы не используете таблицы MyISAM, то для key\_buffer\_size можно установить минимальное значение 64 Кбайт или использовать значение по умолчанию.

### Настройте innodb\_buffer\_pool\_size

Если ваши базы данных содержат таблицы InnoDB, нужно установить буферный пул для кэширования и индексирования данных. Этот размер задается при помощи параметра innodb\_buffer\_pool\_size.

По умолчанию для MariaDB размер буферного пула составляет всего 128 Мбайт, поэтому его нужно увеличить, например:

```
innodb_buffer_pool_size = 3G
```

Используйте здесь от четверти до половины общего объема памяти, установленной на сервере, но с учетом требований к памяти других сервисов.

Также установите размер файла журнала, равный четверти от размера innodb\_buffer\_pool\_size:

```
innodb_log_file_size = 384M
```

Еще нужно установить значение параметра innodb\_buffer\_pool\_instances, равное количеству гигабайт памяти , выделенных для буферного пула:

```
innodb_buffer_pool_instances = 3
```

Чтобы определить необходимый размер буферного пула innodb\_buffer\_pool\_size, сравните количество запросов из буферного пула Innodb\_buffer\_pool\_read\_requests с количеством операций чтения с диска Innodb\_buffer\_pool\_reads:

```
MariaDB [(none)]> SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
+----------------------------------+-------+
| Variable_name                    | Value |
+----------------------------------+-------+
| Innodb_buffer_pool_read_requests | 9560  |
+----------------------------------+-------+
1 row in set (0.00 sec)

MariaDB [(none)]> SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| Innodb_buffer_pool_reads | 497   |
+--------------------------+-------+
1 row in set (0.00 sec)
```

В идеале количество чтений Innodb\_buffer\_pool\_reads должно составлять не более 1% от общего количества запросов Innodb\_buffer\_pool\_read\_requests.

Также убедитесь, что в параметре Innodb\_buffer\_pool\_wait\_free находится нулевое значение.

```
MariaDB [(none)]> SHOW STATUS LIKE 'Innodb_buffer_pool_wait_free';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| Innodb_buffer_pool_wait_free | 0     |
+------------------------------+-------+
```

В противном случае размер буферного пула нужно увеличить.

### Не увлекайтесь увеличением размеров буферов

Очень внимательно стоит отнестись к изменению размеров следующий буферов:

* read\_buffer\_size
* read\_rnd\_buffer\_size
* join\_buffer\_size

Параметры read\_buffer\_size и read\_rnd\_buffer\_size задают размеры буферов чтения и размер буфера случайного чтения, соответственно.

Параметр join\_buffer\_size задает размер буфера для операций объединения таблиц без использования индексов.

Следует учитывать, что буферы, задаваемые этими параметрами, создаются для каждого соединения с MariaDB. Максимальное количество соединений задается так:

```
max_connections = 300
```

Когда создается много соединений, для указанных буферов может потребоваться очень много памяти. Подробнее об этом [можно прочитать здесь](https://netpoint-dc.com/blog/mysql-chastie-oshibki-nastroiki/).

### Калькулятор для вычисления необходимого объема памяти

Для приблизительной оценки влияния настроек MySQL и MariaDB на потребление памяти можно использовать калькулятор [MySQL Calculator](http://www.mysqlcalculator.com/) (рис. 1).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-090c94ef202f76da9dab2dfe90484be9e7c0bdf4%2Fd0cce8dac7bdc7cc02c7f04db0d8b1c8.png?alt=media)

Рис. 1. Калькулятор MySQL Calculator

Задавая значения настроек, вы можете наблюдать в поле **Totals** изменение объема памяти, необходимого для работы СУБД.

### Настройте максимальное количество открытых файлов

Если на сервере работает очень много сайтов, то в журнале MariaDB могут появиться сообщения о невозможности открыть файл базы данных.

По умолчанию MariaDB может открыть 16384 файла. Чтобы увеличить это количество, например, вдвое, отредактируйте файл /etc/systemd/system/mariadb.service.d/nofile.conf, добавив в него строки:

```
[Service]
LimitNOFILE=32768
```

Далее перезапустите сервис и проверьте результат:

```
# systemctl restart mariadb
# mysql -u root
MariaDB [(none)]> show variables like 'open_files_limit';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| open_files_limit | 32768 |
+------------------+-------+
1 row in set (0.001 sec)
```

### Полезные статьи про оптимизацию

В интернете есть немало статей, посвященных оптимизации параметров MySQL и MariaDB, вот, например, несколько ссылок, которые могут быть вам полезны:

* <https://g-soft.info/articles/2717/nastroyka-i-optimizatsiya-mysql-i-mariadb/>
* <https://netpoint-dc.com/blog/mysql-chastie-oshibki-nastroiki/>

И, конечно, читайте [документацию MariaDB](https://mariadb.com/kb/ru/5306/).

Учтите, что нет универсальной инструкции или конфигурации, подходящей для любого случая. Настройка параметров MariaDB (как и любой другой СУБД) должна выполняться индивидуально для каждой конкретной ситуации.

## Мониторинг с помощью MySQL by Zabbix agent 2

Если на сервере, где установлена СУБД MySQL или MariaDB, есть Zabbix agent 2, то вы сможете очень просто организовать мониторинг СУБД. Используйте для этого плагин MySQL by Zabbix agent, [описанный здесь](https://www.zabbix.com/integrations/mysql#mysql_agent2).

Откройте страницу **Host** в Web-интерфейсе Zabbix для узла, на котором нужно контролировать работу MariaDB. Затем добавьте к хосту шаблон **MySQL by Zabbix agent 2**.

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

### Добавление пользователя zbx\_monitor

```
# mysql -u root
```

Так как утилита mysql запущена от имени root в Debian, пароль root для подключения к MariaDB указывать не нужно.

Создайте пользователя zbx\_monitor и укажите необходимые права доступа:

```
MariaDB [(none)]> CREATE USER 'zbx_monitor'@localhost IDENTIFIED BY '********';
MariaDB [(none)]> GRANT REPLICATION CLIENT,PROCESS,SHOW DATABASES,SHOW VIEW ON *.* TO 'zbx_monitor'@localhost;
```

Здесь вместо символов ‘\*\*\*\*\*\*\*\*’ укажите пароль.

### Настройка макросов

Создав пользователя, добавьте для хоста, где работает сервис MariaDB, три макроса шаблона MySQL by Zabbix agent 2.

В макросе **{$MYSQL.DSN}** задайте адрес и порт для подключения к MariaDB как tcp\://localhost:3306, а в макросах **{$MYSQL.USER}** и **{$MYSQL.PASSWORD}** задайте, соответственно, имя пользователя zbx\_monitor и его пароль (рис. 2).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-25549390a5cfc3c28abed8560dfe9c793ade5377%2F4715d96604873387798a49d6733c6582.png?alt=media)

Рис. 2. Настройка макросов для шаблона MySQL by Zabbix agent 2

Такие настройки нужно сделать для всех хостов с контролируемым сервисом MariaDB, при этом имеет смысл использовать на разных хостах для пользователя zbx\_monitor разные пароли.

На рис. 3 приведены все макросы шаблона MySQL by Zabbix agent 2.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d54ce642164ee2817fcf236e91c3709e38552fd7%2F08bfe41ea66bbbc5b01311f1da5ec2b3.png?alt=media)

Рис. 3. Макросы шаблона MySQL by Zabbix agent 2

Многие из этих макросов используются в условиях триггеров. Вы можете их изменять, если требуется настроить какие-либо из условий.

### Метрики шаблона MySQL by Zabbix agent 2

В шаблоне MySQL by Zabbix agent 2 вы найдете очень большое количество метрик, с помощью которых можно оценить состояние СУБД, узнать размеры баз данных и получить другую важную информацию. Небольшая часть метрик (часть одной из трех страниц) представлена на рис. 4.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-95a8c8026301ebef5dab5219b0fd5a929a0a80ef%2Fdd6480eae85a6600455008f8ce65dcea.png?alt=media)

Рис. 4. Метрики шаблона MySQL by Zabbix agent 2

Для каждой метрики строится график, с помощью которого можно отслеживать изменения соответствующей метрики. Например, на рис. 5 показано количество команд SELECT, выполненных сервером за одну секунду.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-27b4c11d6b2cd882ba490302426a642e5e2d42b0%2Ffeb84084cbebaae2a2c56e02b3037112.png?alt=media)

Рис. 5. Количество выполненных команды SELECT за одну секунду

### Триггеры шаблона MySQL by Zabbix agent 2

На рис. 6 показаны триггеры, определенные в шаблоне MySQL by Zabbix agent 2.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2c0c7be609d7fb2a5dc852ca1165b2f3cd02b211%2F413751e9a4593dc396877241f97d0291.png?alt=media)

Рис. 6. Триггеры шаблона MySQL by Zabbix agent 2

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

Самый большой уровень серьезности **High** назначен триггеру MySQL: Service is down. Он срабатывает, когда сервис СУБД не работает, и нужно срочно разбираться, в чем проблема.

Средний уровень серьезности **Average** у триггеров MySQL: Refused connections и MySQL: Server has aborted connections. Если сработали эти триггеры, сервис не успевает обрабатывать все соединения от клиентов.

Что касается триггеров с уровнем серьезности **Warning**, то они предупредят системного администратора о недостаточном использовании буферного пула, о слишком высокой скорости создания временных таблиц в памяти и на диске, о слишком высокой скорости создания временных файлов, а также о наличии медленных запросов. В этом случае имеет смысл заняться оптимизацией настройки MariaDB или приложений.

Триггеры с низким уровнем серьезности **Information** предупредят об изменении версии и перезапуске сервиса СУБД, а также о проблемах с получением данных от агента.

Мониторинг состояния MariaDB с помощью Zabbix поможет выявить ряд проблем, связанных с работоспособностью и производительностью сервиса СУБД. Однако для более детального анализа ситуации и для оптимизации параметров работы потребуется анализ метрик и текущих значений параметров работы MariaDB.

*Автор: Александр Фролов*


# MySQL and MariaDB replication with Zabbix monitoring

<https://habr.com/ru/companies/first/articles/690318/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c2799262b5d36b75c1688a8052881f35ac5e8219%2F8923510ceec24077d13c2378f9d2a905.png?alt=media)

Когда к отказоустойчивости интернет-магазина или другого сервиса с базами данных предъявляются повышенные требования, не обойтись без репликации серверов СУБД и файлов. Репликация совместно с другими технологиями отказоустойчивости помогает полностью защититься от сбоя оборудования, например, от выхода из строя отдельных серверов.

Из этой статьи вы узнаете, как настроить и проверить репликацию Master-Slave для MySQL и MariaDB, а также как контролировать ее работу с помощью Zabbix.

## Настройка репликации Master-Slave

Мы будем настраивать репликацию базы данных myshop\_db по схеме Master-Slave. При этом в роли мастера будет выступать сервер r01master, а в роли реплики — сервер r01slave.

### Настройка конфигурации мастер-сервера

Отредактируйте файл конфигурации MariaDB /etc/mysql/mariadb.conf.d/50-server.cnf на мастер сервере, добавив в него следующие строки:

```
#bind-address = 127.0.0.1
server-id = 42442171
log_bin   = /var/log/mysql/mysql-bin.log
expire_logs_days = 5
max_binlog_size  = 50M
sync-binlog = 0
binlog_format = mixed
binlog-do-db = myshop_db
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DIRECT
```

Прежде всего, закройте символом комментария параметр **bind-address** со значением 127.0.0.1. Если этого не сделать, сервер реплики не получит доступ к мастер-серверу MariaDB, так как сервис СУБД будет принимать соединения только на локальном интерфейсе 127.0.0.1.

Удалите следующую настройку, если она есть:

```
skip-networking=1
```

Если этого не сделать, к серверу MariaDB нельзя будет подключиться по сети.

Открыв доступ к MariaDB по сети, не забудьте ограничить его по адресу IP файерволом.

Далее задайте уникальный идентификатор **server-id** (в примере показано произвольное число, задайте здесь другое значение). На сервере реплики необходимо будет задать другой уникальный идентификатор.

Для работы репликации необходим бинарный журнал, путь к файлу которого задается в параметре **log\_bin**. При этом в параметрах **expire\_logs\_days** и **max\_binlog\_size** нужно задать количество дней, в течение которых будет храниться журнал, и максимальный размер журнала, соответственно. Выберите эти параметры исходя из наличия свободного пространства на дисках сервера, а также интенсивности обращений к СУБД.

Параметр **sync-binlog** управляет синхронизацией бинарного лога на диск. По умолчанию значение этого параметра равно 0, в результате чего синхронизацией управляет операционная система. С точки зрения потери данных безопаснее задать для этого параметра значение 1. При этом данные будут записываться на диск после каждой операции записи в журнал. Однако это может снизить производительность сервера СУБД.

Что касается параметра **binlog\_format**, то мы использовали наиболее безопасный смешанный формат двоичного журнала. Подробнее об этом [можно прочитать здесь](https://mariadb.com/kb/en/binary-log-formats/). Так же доступен [перевод на русский язык](https://runebook.dev/ru/docs/mariadb/binary-log-formats/index).

Очень важно задать с помощью параметра **binlog-do-db** название реплицируемой базы данных. В нашем случае мы указали базу данных myshop\_db.

Параметр **innodb\_flush\_log\_at\_trx\_commit** управляет сбросом данных на диск. Значение этого параметра, равное 0, дает максимальную производительность, но журнал транзакций будет сбрасываться на диск через какое-то время после выполнения транзакции.

Безопаснее будет указать значение, равное 1 (используется по умолчанию). При этом запись журнала на диск выполняется сразу после каждой транзакции. Но такой вариант будет работать медленнее. И, наконец, задав значение этого параметра, равное 2, данные будут сбрасываться не на диск, а в кэш операционной системы.

Подробнее о параметре **innodb\_flush\_log\_at\_trx\_commit** [можно прочитать здесь](https://mariadb.com/docs/reference/mdb/system-variables/innodb_flush_log_at_trx_commit/).

Параметр **innodb\_flush\_method** влияет на кэширование. В нашем случае база данных находится на локальном диске сервера, при этом значение этого параметра, равное O\_DIRECT, отключает кэширование на уровне ОС.

Подробнее о параметре **innodb\_flush\_method** [можно прочитать здесь](https://mariadb.com/kb/en/innodb-system-variables/#innodb_flush_method).

После внесения всех изменений в конфигурацию MariaDB перезапустите сервис и проверьте его состояние:

```
# service mysql restart
# service mysql status
```

Если появились ошибки или предупреждения, внесите исправления в файл конфигурации и попробуйте запустить сервис еще раз.

### Настройка конфигурации сервера реплики

После того как вы настроили MariaDB на мастер-сервере, отредактируйте конфигурацию MariaDB, расположенную в файле /etc/mysql/mariadb.conf.d/50-server.cnf на сервере реплики:

```
#bind-address = 127.0.0.1
server-id = 11213181
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 5
max_binlog_size = 50M
sync-binlog = 0
binlog_format = mixed
relay-log = /var/log/mysql/mysql-relay-bin.log
replicate-do-db = myshop_db
report-host=r01slave.domain.ru
slave_sql_verify_checksum = 0
#skip_slave_start = 1 # prevent restart slave after failure
```

Здесь обратите внимание на параметры **replicate-do-db**, **report-host**, **slave\_sql\_verify\_checksum** и **skip\_slave\_start**.

Параметр **replicate-do-db** задает базу, которая будет реплицироваться с мастер-сервера. Если ее не указать, будут реплицированы все базы.

При помощи параметра **report-host** можно задать имя хоста реплики, как оно будет отображаться при просмотре на мастер-хосте списка хостов реплик.

Параметр **slave\_sql\_verify\_checksum** управляет вычислением контрольной суммы при работе с журналом репликации на сервере реплики (релея): <https://mariadb.com/kb/en/relay-log/>. Если задать нулевое значение, проверка контрольной суммы будет отключена.

Параметр **skip\_slave\_start** используется для отключения репликации при перезагрузке сервера реплики. Он нужен, если требуется отменить репликацию, поэтому в нормальном режиме он закрыт символом комментария.

Также не забудьте удалить следующую настройку, если она есть:

```
skip-networking=1
```

Вы можете почитать описания параметров, имеющих отношение к репликации, в [документации MariaDB](https://mariadb.com/kb/ru/replication-and-binary-log-server-system-variables).

После редактирования файла конфигурации перезапустите сервис и убедитесь в отсутствии ошибок:

```
# service mysql restart
# service mysql status
```

### Создание пользователя репликации

Теперь, когда вы подготовили мастер-сервер и сервер реплики, создайте на мастер-сервере и сервере реплики пользователя репликации, например, с именем repl\_user:

```
# mysql -u root
> CREATE USER repl_user;
> GRANT REPLICATION SLAVE ON *.* TO repl_user IDENTIFIED BY '*******';
> FLUSH PRIVILEGES;
```

### Копирование базы данных с мастер-сервера на сервер реплики

Для переноса базы данных нужно открыть **два** консольных окна на мастер-сервере.

В **первом** консольном окне блокируем таблицы реплицируемой базы данных на запись:

```
mysql> USE myshop_db
mysql> FLUSH TABLES WITH READ LOCK;
```

В этом же окне проверяем статус мастера:

```
MariaDB [myshop_db]> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000007 |     342  | myshop_db    |                  |
+------------------+----------+--------------+------------------+
1 row in set (0.000 sec)
```

Значения `mysql-bin.000007` и `342` будут нужны для запуска реплики.

**Внимание!** Если выйти из окна консоли, где вы ввели команду `FLUSH TABLES`, то сервер разблокирует таблицы и они снова будут доступны на запись. Дамп базы нужно делать **во втором, отдельном окне**, не закрывая окно консоли, в котором была выдана команда `FLUSH TABLES WITH READ LOCK`.

Во **втором** консольном окне делаем дамп базы данных от имени пользователя myshop\_db:

```
# mysqldump -umyshop_db -p -hlocalhost --opt --quote-names myshop_db > myshop_db.sql
```

Разблокируем таблицы на мастере в **первом** консольном окне, чтобы пользователи могли работать дальше:

```
mysql> UNLOCK TABLES;
```

На сервере реплики обычным пользователем, например, admdb создаем каталог `/home/admdb/repl`:

```
$ mkdir /home/admdb/repl
```

Копируем файл дампа базы с мастера на сервер реплики:

```
$ scp -v myshop_db.sql frolov@xxx.xxx.xxx.xxx:/home/admdb/repl/
```

Здесь `xxx.xxx.xxx.xxx` — адрес IP сервера реплики.

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

```
$ mysql -umyshop_db -p -hlocalhost myshop_db < myshop_db.sql
```

После загрузки базы на сервере реплики надо почистить журналы в `/var/log/mysql`:

```
RESET MASTER;
```

### Подключение к мастер-серверу

Перед подключением убедитесь, что сервер реплики SLAVE остановлен, а список SLAVE пуст:

```
MariaDB [(none)]> SHOW SLAVE STATUS\G
Empty set (0.000 sec)
```

Если это не так, удалите данные репликации:

```
MariaDB [(none)]> RESET SLAVE ALL;
MariaDB [(none)]> SHOW SLAVE STATUS\G
Empty set (0.000 sec)
```

Укажите на сервере реплики параметры подключения к мастеру:

```
MariaDB [(none)]> CHANGE MASTER TO MASTER_HOST='xxx.xxx.xxx.xxx', MASTER_USER='repl_user', MASTER_PASSWORD='*******', MASTER_LOG_FILE = 'mysql-bin.000007', MASTER_LOG_POS = 342;
```

Здесь `xxx.xxx.xxx.xxx` — адрес IP сервера мастера. Параметры **MASTER\_LOG\_FILE** и **MASTER\_LOG\_POS** нужно взять из состояния мастера (в **первом** консольном окне).

### Запуск реплики

Запускаем реплику при помощи следующей команды:

```
MariaDB [(none)]> START SLAVE;
```

Далее проверяем статус реплики на узле реплики:

```
MariaDB [(none)]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
                Slave_IO_State: Waiting for master to send event
                   Master_Host: xxx.xxx.xxx.xxx
                   Master_User: repl_user
                   Master_Port: 3306
                 Connect_Retry: 60
               Master_Log_File: mysql-bin.000102
           Read_Master_Log_Pos: 2195
                Relay_Log_File: mysql-relay-bin.000007
                 Relay_Log_Pos: 1573
         Relay_Master_Log_File: mysql-bin.000007
              Slave_IO_Running: Yes
             Slave_SQL_Running: Yes
               Replicate_Do_DB: myshop_db
           Replicate_Ignore_DB:
            Replicate_Do_DB:
        Replicate_Ignore_DB:
       Replicate_Wild_Do_Table:
   Replicate_Wild_Ignore_Table:
                    Last_Errno: 0
                    Last_Error:
                  Skip_Counter: 0
           Exec_Master_Log_Pos: 2195
               Relay_Log_Pos: 1878
               Until Condition: None
                Until_Log_File:
                 Antilogous: 0
            Master_SSL_Allowed: No
            Master_SSL_CA_File:
            Master_SSL_CA_Path:
               Master_SSL_Cert:
             Master_SSL_Cipher:
                Master_SSL_Cipher:
         Seconds_Behind_Master: 0
 Master_SSL_Verify_Server_Cert: No
                 Last_IO_Errno: 0
                 Last_IO_Error:
                Last_SQL_Errno: 0
                Last_SQL_Error:
   Replicate_Ignore_Server_Ids:
              Master_Server_Id: 4244209171
                Master_SSL_Crl:
            Master_SSL_Crlpath:
                    Using_Gtid: No
                   Gtid_IO_Pos:
       Replicate_Do_Domain_Ids:
   Replicate_Ignore_Domain_Ids:
                 Parallel_Mode: conservative
                     SQL_Delay: 0
           SQL_Remaining_Delay: NULL
       Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
              Slave_DDL_Groups: 1
Slave_Non_Transactional_Groups: 0
    Slave_Transactional_Groups: 3
1 row in set (0.000 sec)
```

Убедитесь, что в этой выдаче значения параметров **Slave\_IO\_Running** и **Slave\_SQL\_Running** равно `Yes`, а значение параметра **Seconds\_Behind\_Master** равно `0`.

Теперь откройте консоль мастер-сервера и посмотрите там узлы реплики:

```
MariaDB [myshop_db]> show slave hosts;
+------------+----------------------+------+------------+
| Server_id  | Host                 | Port | Master_id  |
+------------+----------------------+------+------------+
| 11213181   | r01slave.domain.ru   | 3306 | 42442171   |
+------------+----------------------+------+------------+
1 row in set (0.000 sec)
```

Если все так, то значит, репликация работает.

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

## Отключение репликации

Если вам нужно превратить сервер реплики в мастер-сервер, то следует отключить репликацию. Сначала выдайте команду `STOP SLAVE IO_THREAD`:

```
MariaDB [(none)]> STOP SLAVE IO_THREAD;
```

Далее выдавайте команду `SHOW PROCESSLIST`:

```
MariaDB [(none)]> SHOW PROCESSLIST;
+----+-------------+-----------+------+-----------+------+-----------------------------------------------------------------------------+------------------+----------+
| Id | User | Host | db | Command | Time | State | Info | Progress |
+----+-------------+-----------+------+-----------+------+-----------------------------------------------------------------------------+------------------+----------+
| 1 | system user | | NULL | Daemon | NULL | InnoDB purge coordinator | NULL | 0.000 |
…
| 41 | root | localhost | NULL | Query | 0 | Init | SHOW PROCESSLIST | 0.000 |
| 43 | system user | | NULL | Slave_SQL | 346 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL | 0.000 |
+----+-------------+-----------+------+-----------+------+-----------------------------------------------------------------------------+------------------+----------+
7 rows in set (0.000 sec)
```

Дождитесь появления сообщения:

```
Slave has read all relay log; waiting for the slave I/O thread to update it
```

Это сообщение говорит о том, что сервер реплики выполнил все команды из relay-лога в своей базе.

Теперь останавливаем реплику и очищаем bin-log:

```
MariaDB [(none)]> STOP SLAVE;
MariaDB [(none)]> RESET MASTER;
```

Если не дождаться выполнения всех команд из relay-лога, то при переключении сервера реплики на новый мастер-сервер может потеряться часть команд, которые не были выполнены на реплике.

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

```
MariaDB [(none)]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
                Slave_IO_State:
                   Master_Host: xxx.xxx.xxx.xxx
                   Master_User: repl_user
                   Master_Port: 3306
                 Connect_Retry: 60
               Master_Log_File: mysql-bin.000100
           Read_Master_Log_Pos: 3342
                Relay_Log_File: mysql-relay-bin.00007
                 Relay_Log_Pos: 555
         Relay_Master_Log_File: mysql-bin.000100
              Slave_IO_Running: No
             Slave_SQL_Running: No
               Replicate_Do_DB: myshop_db
           Replicate_Ignore_DB:
            Replicate_Do_Table:
        Replicate_Ignore_Table:
       Replicate_Wild_Do_Table:
   Replicate_Wild_Ignore_Table:
                    Last_Errno: 0
                    Last_Error:
                  Skip_Counter: 0
           Exec_Master_Log_Pos: 342
               Relay_Log_Space: 864
               Until_Condition: None
                Until_Log_File:
                 Until_Log_Pos: 0
            Master_SSL_Allowed: No
            Master_SSL_CA_File:
            Master_SSL_CA_Path:
               Master_SSL_Cert:
             Master_SSL_Cipher:
                Master_SSL_Key:
         Seconds_Behind_Master: NULL
 Master_SSL_Verify_Server_Cert: No
                 Last_IO_Errno: 0
                 Last_IO_Error:
                Last_SQL_Errno: 0
                Last_SQL_Error:
   Replicate_Ignore_Server_Ids:
              Master_Server_Id: 4244209171
                Master_SSL_Crl:
            Master_SSL_Crlpath:
                    Using_Gtid: No
                   Gtid_IO_Pos:
       Replicate_Do_Domain_Ids:
   Replicate_Ignore_Domain_Ids:
                 Parallel_Mode: conservative
                     SQL_Delay: 0
           SQL_Remaining_Delay: NULL
       Slave_SQL_Running_State:
              Slave_DDL_Groups: 0
Slave_Non_Transactional_Groups: 0
    Slave_Transactional_Groups: 0
1 row in set (0.000 sec)
```

Убедитесь, что значение параметров **Slave\_IO\_Running** и **Slave\_SQL\_Running** указано как **No**.

Далее, чтобы при перезагрузке ОС на сервере репликации или при перезапуске сервиса MariaDB репликация не возобновилась, уберите символ комментария со строки параметра `skip_slave_start` в файле конфигурации сервера репликации `/etc/mysql/mariadb.conf.d/50-server.cnf`:

```
skip_slave_start = 1
```

Возможно вам пригодится [следующая статья про настройку репликации](https://habr.com/ru/post/56702/), а также [раздел документации MariaDB](https://mariadb.com/kb/ru/setting-up-replication/), посвященный настройке репликации.

Если вы настраиваете репликацию MariaDB версии 10.5 или новее, можете настроить репликацию на базе глобального идентификатора транзакции global transaction ID. Эта процедура [описана здесь](https://mariadb.com/kb/en/gtid/).

## Мониторинг репликации с помощью Zabbix

Для мониторинга репликации MySQL или MariaDB используйте [плагин MySQL by Zabbix agent](https://www.zabbix.com/integrations/mysql#mysql_agent2). Про установку и настройку этого плагина мы уже рассказывали в статье «[MariaDB: настройка и мониторинг с помощью Zabbix](https://habr.com/ru/company/first/blog/689138/)».

Здесь мы расскажем только об особенностях, имеющих отношение к мониторингу репликации.

### Создание пользователя zbx\_monitor

Вам необходимо создать пользователя zbx\_monitor:

```
MariaDB [(none)]> CREATE USER 'zbx_monitor'@'%' IDENTIFIED BY '<password>';
```

Для мониторинга репликации необходимо указать этому пользователю следующие права:

```
MariaDB [(none)]> GRANT REPLICATION CLIENT, REPLICATION SLAVE,BINLOG MONITOR,SLAVE MONITOR,PROCESS,SHOW DATABASES,SHOW VIEW ON *.* TO 'zbx_monitor'@'localhost';
```

Такие права нужны для того, чтобы плагин MySQL by Zabbix agent от имени этого пользователя мог выполнять в MariaDB версии 10.5 команду, на базе которой и построен мониторинг:

```
show slave status\G
```

Для мониторинга сервера MySQL или MariaDB версии до 10.5 (например, MariaDB 10.3) у пользователя zbx\_monitor должны быть права REPLICATION CLIENT. Однако у новых версий MariaDB этих прав для мониторинга репликации недостаточно.

Подробнее от этом можно почитать [здесь](https://mariadb.com/docs/reference/mdb/privileges/REPLICATION_CLIENT/) и [здесь](https://mariadb.com/kb/en/show-replica-status/). Установите минимально необходимые права в соответствии с версией вашей СУБД.

Для проверки достаточности прав вы можете подключиться к консоли MariaDB как пользователь zbx\_monitor, а затем выдать в консоли команду «`show slave status\G`». Если прав недостаточно, вы увидите соответствующее сообщение об ошибке.

Когда плагин MySQL by Zabbix пытается получить доступ к серверу с адреса 127.0.0.1, а не localhost, и этот доступ не настроен, то при выдаче команды «`system mysql status`» на консоли появится такое сообщение:

```
Sep 01 15:59:56 xxx1slave.domain.ru mariadbd[2543745]: 2022-09-01 15:59:56 1624 [Warning] Access denied for user 'zbx_monitor'@'127.0.0.1' (using password: YES)
```

Домен `xxx1slave.domain.ru` указан только для примера.

В этом случае нужно добавить пользователя:

```
MariaDB [(none)]> CREATE USER 'zbx_monitor'@'127.0.0.1' IDENTIFIED BY '<password>';
MariaDB [(none)]> GRANT REPLICATION CLIENT, REPLICATION SLAVE,BINLOG MONITOR,SLAVE MONITOR,PROCESS,SHOW DATABASES,SHOW VIEW ON *.* TO 'zbx_monitor'@'127.0.0.1';
```

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

Если сервер MySQL или MariaDB участвует в репликации (как сервер реплики), средства Zabbix для обнаружения LLD автоматически создают необходимые метрики (рис. 1).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6d30e36ffeab5739da2c800f100c8a3e564b0ac4%2Fdc2c051650321ad4a3a1dd7128db8b21.png?alt=media)

Рис. 1. Метрики мониторинга репликации

На рис. 1 мы закрасили адрес IP мастер-сервера.

Также автоматически создаются необходимые триггеры (рис. 2).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3b6b660ff2ef98ea3afd36b5e0f078f95e746140%2F906a6d7ddd0c243c29a097bc3567355f.png?alt=media)

Рис. 2. Триггеры мониторинга репликации

На рис. 2 закрашены имена хоста реплики, а также адрес IP мастер-сервера.

Как видите, эти триггеры будут установлены, если репликация не выполняется или работает с большим отставанием.

Самый серьезный уровень **Average** по умолчанию назначен триггеру **The slave I/O thread is not running**. Если он установился, то информация из бинарного лога мастера не попадает в журнал релея (relay log) на сервере реплики. Такое бывает, например, в результате ошибки в сети.

Срабатывание триггера **Replication lag is too high** может означать, что сервер реплики не справляется со своей работой из-за недостаточной производительности или проблем с аппаратным обеспечением. [Этот вопрос обсуждается здесь](https://www.percona.com/blog/2011/07/29/reasons-for-mysql-replication-lag/).

Уровень серьезности триггеров вы можете изменить в соответствии с требованиями вашего бизнеса.

Автор статьи: Александр Фролов.


# Postgres

[HA PostgreSQL with Patroni, Haproxy, Keepalived](/readme/architect/db/postgres/ha-postgresql-with-patroni-haproxy-keepalived)

[Mass parallel requests - Greenplum](/readme/architect/db/postgres/mass-parallel-requests-greenplum)

[PostgreSQL cluster for development and testing](/readme/architect/db/postgres/postgresql-cluster-for-development-and-testing)


# HA PostgreSQL with Patroni, Haproxy, Keepalived

<https://habr.com/ru/articles/322036/>

Привет, Хабр! Встала передо мной недавно задача: настроить максимально надежный кластер серверов PostgreSQL версии 9.6.

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

Планируя кластер я проштудировал много статей, как из основной документации к PostgreSQL, так и различных howto, в том числе с Хабра, и пробовал настроить стандартный кластер с RepMgr, эксперементировал с pgpool.

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

В итоге где-то (уже не вспомню точно где) нашел ссылку на прекрасный проект [Zalando Patroni](https://github.com/zalando/patroni), и все заверте…

### Введение

Patroni — это демон на python, позволяющий автоматически обслуживать кластеры PostgreSQL с различными типами репликации, и автоматическим переключением ролей.

Его особенная красота, на мой взгляд в том, что для поддержания актуальности кластера и выборов мастера используются распределенные [DCS](https://en.wikipedia.org/wiki/Distributed_control_system) хранилища (поддерживаются Zookeeper, etcd, Consul).

Таким образом кластер легко интегрируется практически в любую систему, всегда можно выяснить кто в данный момент мастер, и статус всех серверов запросами в DCS, или напрямую к Patroni через http.

Ну и просто это красиво :)

Я потестировал работу Patroni, пробовал ронять мастера и другие сервера, пробовал наливать разные базы (\~25 Гб база автоматически поднимается с нуля на 10Гб сети за несколько минут), и в целом мне проект Patroni очень понравился. После полной реализации описанной ниже схемы я проводил тестирование простым бенчером, который ходил в базу по единому адресу, и переживал падения всех элементов кластера (мастер сервера, haproxy, keepalived).

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

Для автоматизации разворачивания кластера и добавления новых серверов, решено было использовать привычный Ansible (я дам ссылки на получившиеся роли в конце статьи). В качестве DCS выступает уже применяемый у нас Consul.

У статьи две основные цели: показать пользователям PostgreSQL что есть такая прекрасная штука как Patroni (упоминаний в рунете вообще и на Хабре в частности, практически нет), и заодно немного поделиться опытом использования Ansible на простом примере, тем кто только начинает с ним работать.

Я постараюсь разъяснить все действо сразу на примере разбора Ansible ролей и плейбуков. Те, кто не использует Ansible, смогут перенести все действия в любимое средство автоматизированного управления серверами, либо выполнить их же вручную.

Поскольку большая часть yaml скриптов будет длинной, я буду заворачивать их в спойлер.

Рассказ будет разделен на две части — подготовка серверов и разворачивание непосредственно кластера.

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

### Часть I

Для этого примера я использую виртуальные машины на базе Centos 7. Виртуалки разворачиваются из шаблона который периодически обновляется (ядро, системные пакеты), но эта тема выходит за рамки данной статьи.

Отмечу только, что никакого прикладного или серверного софта на виртуалках заранее не установлено. Также вполне подойдут любые облачные ресурсы, например с AWS, DO, vScale, и т.п. Для них есть скрипты динамического инвентаря и интеграции с Ansible, либо можно прикрутить Terraform, так что весь процесс создания и удаления серверов c нуля может быть автоматизирован.

Для начала нужно создать инвентарь используемых ресурсов для Ansible. Ansible у меня (и по умолчанию) расположен в /etc/ansible. Создаем инвентарь в файле /etc/ansible/hosts:

```
[pgsql]
cluster-pgsql-01.local
cluster-pgsql-02.local
cluster-pgsql-03.local

```

У нас используется внутренняя доменная зона .local, поэтому у серверов такие имена.

Далее нужно подготовить каждый сервер к установке всех необходимых компонентов, и рабочих инструментов.

Для этой цели создаем плейбук в /etc/ansible/tasks:

**/etc/ansible/tasks/essentialsoftware.yml**

```jsx
---

- name: Install essential software
  yum: name={{ item }} state=latest
  tags: software
  with_items:
   - ntpdate
   - bzip2
   - zip
   - unzip
   - openssl-devel
   - mc
   - vim
   - atop
   - wget
   - mytop
   - screen
   - net-tools
   - rsync
   - psmisc
   - gdb
   - subversion
   - htop
   - bind-utils
   - sysstat
   - nano
   - iptraf
   - nethogs
   - ngrep
   - tcpdump
   - lm_sensors
   - mtr
   - s3cmd
   - psmisc
   - gcc
   - git
   - python2-pip
   - python-devel

- name: install the 'Development tools' package group
  yum:
    name: "@Development tools"
    state: present
```

Набор пакетов Essential служит для создания на любом сервере привычного рабочего окружения.

Группа пакетов Development tools, некоторые библиотеки -devel и python нужны pip-у для сборки Python модулей к PostgreSQL.

Мы используем виртуальные машины на базе VmWare ESXi, и для удобства администрирования в них нужно запускать агент vmware.

Для этого мы запустим открытый агент vmtoolsd, и опишем его установку в отдельном плейбуке (поскольку не все сервера у нас виртуальные, и возможно для каких-то из них этот таск не понадобится):

**/etc/ansible/tasks/open-vm-tools.yml**

```jsx
---

- name: Install open VM tools for VMWARE
  yum: name={{ item }} state=latest
  tags: open-vm-tools
  with_items:
   - open-vm-tools

- name: VmWare service start and enabling
  service: name=vmtoolsd.service state=started enabled=yes
  tags: open-vm-tools
```

Для того чтобы завершить подготовку сервера к установке основной части софта, в нашем случае, понадобятся следующие шаги:

1. настроить синхронизацию времени с помощью ntp
2. установить и запустить zabbix агент для мониторинга
3. накатить требуемые ssh ключи и authorized\_keys.

Чтобы не слишком раздувать статью деталям не относящимися к собственно кластеру, я кратко процитирую ansible плейбуки, выполняющие эти задачи:

NTP:

**/etc/ansible/tasks/ntpd.yml**

```jsx
---
    - name: setting default timezone
      set_fact:
        timezone: name=Europe/Moscow
      when: timezone is not defined

    - name: setting TZ
      timezone: name={{ timezone }}
      when: timezone is defined
      tags:
      - tz
      - tweaks
      - ntp
      - ntpd

    - name: Configurating cron for ntpdate
      cron: name="ntpdate" minute="*/5" job="/usr/sbin/ntpdate pool.ntp.org"
      tags:
      - tz
      - tweaks
      - ntp
      - ntpd

    - name: ntpd stop and disable
      service: name=ntpd state=stopped enabled=no
      tags:
      - tz
      - tweaks
      - ntp
      - ntpd
      ignore_errors: yes

    - name: crond restart and enabled
      service: name=crond state=restarted enabled=yes
      tags:
      - tz
      - tweaks
      - ntp
      - ntpd
```

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

Мы не используем ntpd из-за проблем с уплыванием времени на виртуалках ESXi, после которого ntpd отказывается синхронизировать время. (И tinker panic 0 не помогает). Поэтому просто запускаем кроном ntp клиент раз 5 минут.

Zabbix-agent:

**/etc/ansible/tasks/zabbix.yml**

```jsx
---

    - name: set zabbix ip external
      set_fact:
        zabbix_ip: 132.xx.xx.98
      tags: zabbix

    - name: set zabbix ip internal
      set_fact:
        zabbix_ip: 192.168.xx.98
      when: ansible_all_ipv4_addresses | ipaddr('192.168.0.0/16')
      tags: zabbix

    - name: Import Zabbix3 repo
      yum: name=http://repo.zabbix.com/zabbix/3.0/rhel/7/x86_64/zabbix-release-3.0-1.el7.noarch.rpm state=present
      tags: zabbix

    - name: Remove old zabbix
      yum: name=zabbix2* state=absent
      tags: zabbix

    - name: Install zabbix-agent software
      yum: name={{ item }} state=latest
      tags: zabbix
      with_items:
        - zabbix-agent
        - zabbix-release

    - name: Creates directories
      file: path={{ item }}  state=directory
      tags:
      - zabbix
      - zabbix-mysql
      with_items:
        - /etc/zabbix/externalscripts
        - /etc/zabbix/zabbix_agentd.d
        - /var/lib/zabbix

    - name: Copy scripts
      copy: src=/etc/ansible/templates/zabbix/{{ item }} dest=/etc/zabbix/externalscripts/{{ item }} owner=zabbix group=zabbix  mode=0755
      tags: zabbix
      with_items:
        - netstat.sh
        - iostat.sh
        - iostat2.sh
        - iostat_collect.sh
        - iostat_parse.sh
        - php_workers_discovery.sh

    - name: Copy .my.cnf
      copy: src=/etc/ansible/files/mysql/.my.cnf dest=/var/lib/zabbix/.my.cnf owner=zabbix group=zabbix  mode=0700
      tags:
      - zabbix
      - zabbix-mysql

    - name: remove default configs
      file: path={{ item }} state=absent
      tags: zabbix
      with_items:
        - /etc/zabbix_agentd.conf
        - /etc/zabbix/zabbix_agentd.conf

    - name: put zabbix-agentd.conf to default place
      template: src=/etc/ansible/templates/zabbix/zabbix_agentd.tpl dest=/etc/zabbix_agentd.conf owner=zabbix group=zabbix force=yes
      tags: zabbix

    - name: link zabbix-agentd.conf to /etc/zabbix
      file: src=/etc/zabbix_agentd.conf dest=/etc/zabbix/zabbix_agentd.conf state=link
      tags: zabbix

    - name: zabbix-agent start and enable
      service: name=zabbix-agent state=restarted enabled=yes
      tags: zabbix
```

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

Сервера расположенные в пределах нашей сети ходят на 192.168.х.98, а сервера не имеющие в нее доступа, на реальный адрес этого же сервера.

Перенос ssh ключей и настройка ssh вынесена в отдельную роль, которую можно найти, например, на ansible-galaxy.

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

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

Создаем плейбук для группы серверов:

**/etc/ansible/cluster-pgsql.yml**

```jsx
---
- hosts: pgsql

  pre_tasks:
    - name: Setting system hostname
      hostname: name="{{ ansible_host }}"

    - include: tasks/essentialsoftware.yml
    - include: tasks/open-vm-tools.yml
    - include: tasks/ntpd.yml

  post_tasks:
    - include: tasks/zabbix.yml

  roles:
     - ssh.role
     - ansible-role-patroni
```

Запускаем обработку всех серверов:

```jsx
~# ansible-playbook cluster-pgsql.yml --skip-tags patroni
```

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

Аргумент --skip-tags заставляет Ansible пропустить шаги помеченные этим тегом, поэтому роль ansible-role-patroni выполняться сейчас не будет.

Если же ее на диске нет, то ничего страшного и не произойдет, Anisble просто проигнорирует этот ключ.

Ansible у меня заходит на сервера сразу пользователем root, а если вам потребуется пускать ansible под непревилегированного пользователя, стоит дополнительно добавить в шаги требующие рутовых прав специальный флаг «become: true», который побудит ansible использовать вызовы sudo для этих шагов.

Подготовка закончена.

### Часть II

Приступаем к разворачиванию непосредственно кластера.

Поскольку для настройки кластера требуется много работы (установить PostgreSQL и все компоненты, залить для них индивидуальные конфиги), я выделил весь этот процесс в отдельную роль.

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

Шаблон роли для установки Patroni я взял тут: <https://github.com/gitinsky/ansible-role-patroni>, за что спасибо его автору.

Для своих целей я переработал имеющийся и добавил свои плейбуки haproxy и keepalived.

Роли у меня лежат в каталоге /etc/ansible/roles. Создаем каталог для новой роли, и подкаталоги для ее компонентов:

```
~# mkdir /etc/ansible/roles/ansible-role-patroni/tasks
~# mkdir /etc/ansible/roles/ansible-role-patroni/templates
```

Помимо PostgreSQL наш кластер будет состоять из следующих компонентов:

1. haproxy для отслеживания состояния серверов и перенаправления запросов на мастер сервер.
2. keepalived для обеспечения наличия единой точки входа в кластер — виртуального IP.

Все плейбуки выполняемые данной ролью перечисляем в файле, запускаемом ansible по умолчанию:

**/etc/ansible/roles/ansible-role-patroni/tasks/main.yml**

```jsx
- include: postgres.yml
- include: haproxy.yml
- include: keepalived.yml
```

Далее начинаем описывать отдельные таски.

Первый плейбук устанавливает PostgreSQL 9.6 из родного репозитория, и дополнительные пакеты требуемые Patroni, а затем скачивает с GitHub саму Patroni:

**/etc/ansible/roles/ansible-role-patroni/tasks/postgres.yml**

```jsx
---

- name: Import Postgresql96 repo
  yum: name=https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm state=present
  tags: patroni
  when: install is defined

- name: Install PGsql96
  yum: name={{ item }} state=latest
  tags: patroni
  with_items:
    - postgresql96
    - postgresql96-contrib
    - postgresql96-server
    - python-psycopg2
    - repmgr96
  when: install is defined

- name: checkout patroni
  git: repo=https://github.com/zalando/patroni.git dest=/opt/patroni
  tags: patroni
  when: install is defined

- name: create /etc/patroni
  file: state=directory dest=/etc/patroni
  tags: patroni
  when: install is defined

- name: put postgres.yml
  template: src=postgres0.yml dest=/etc/patroni/postgres.yml backup=yes
  tags: patroni
  when: install is defined

- name: install python packages
  pip: name={{ item }}
  tags: patroni
  with_items:
    - python-etcd
    - python-consul
    - dnspython
    - boto
    - mock
    - requests
    - six
    - kazoo
    - click
    - tzlocal
    - prettytable
    - PyYAML
  when: install is defined

- name: put patroni.service systemd unit
  template: src=patroni.service dest=/etc/systemd/system/patroni.service backup=yes
  tags: patroni
  when: install is defined

- name: Reload daemon definitions
  command: /usr/bin/systemctl daemon-reload
  tags: patroni

- name: restart
  service: name=patroni state=restarted enabled=yes
  tags: patroni
```

Кроме установки ПО данный плейбук также заливает конфигурацию для текущего сервера Patroni, и systemd юнит для запуска демона в системе, после чего запускает демон Patroni. Шаблоны конфигов и systemd юнит должны лежать в каталоге templates внутри роли.

Шаблон конфига Patroni:

**/etc/ansible/roles/ansible-role-patroni/templates/postgres.yml.j2**

```jsx
name: {{ patroni_node_name }}
scope: &scope {{ patroni_scope }}

consul:
  host: consul.services.local:8500

restapi:
  listen: 0.0.0.0:8008
  connect_address: {{ ansible_default_ipv4.address }}:8008
  auth: 'username:{{ patroni_rest_password }}'

bootstrap:
  dcs:
    ttl: &ttl 30
    loop_wait: &loop_wait 10
    maximum_lag_on_failover: 1048576 # 1 megabyte in bytes
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        archive_mode: "on"
        wal_level: hot_standby
        archive_command: mkdir -p ../wal_archive && cp %p ../wal_archive/%f
        max_wal_senders: 10
        wal_keep_segments: 8
        archive_timeout: 1800s
        max_replication_slots: 5
        hot_standby: "on"
        wal_log_hints: "on"

pg_hba:  # Add following lines to pg_hba.conf after running 'initdb'
  - host replication replicator 192.168.0.0/16 md5
  - host all all 0.0.0.0/0 md5

postgresql:
  listen: 0.0.0.0:5432
  connect_address: {{ ansible_default_ipv4.address }}:5432
  data_dir: /var/lib/pgsql/9.6/data
  pg_rewind:
    username: superuser
    password: {{ patroni_postgres_password }}
  pg_hba:
  - host all all 0.0.0.0/0 md5
  - hostssl all all 0.0.0.0/0 md5
  replication:
    username: replicator
    password: {{ patroni_replicator_password }}
    network:  192.168.0.0/16
  superuser:
    username: superuser
    password: {{ patroni_postgres_password }}
  admin:
    username: admin
    password: {{ patroni_postgres_password }}
  restore: /opt/patroni/patroni/scripts/restore.py
```

Поскольку для каждого сервера кластера требуется индивидуальная конфигурация Patroni, его конфиг лежит в виде шаблона jinja2 (файл postgres0.yml.j2), и шаг template заставляет ansible транслировать этот шаблон с заменой переменных, значения из которых берутся из отдельного описания для каждого сервера.

Переменные, общие для всего кластера укажем в прямо в инвентаре, который примет теперь следующий вид:

**/etc/ansible/hosts**

```jsx
[pgsql]
cluster-pgsql-01.local
cluster-pgsql-02.local
cluster-pgsql-03.local

[pgsql:vars]
patroni_scope: "cluster-pgsql"
patroni_rest_password: flsdjkfasdjhfsd
patroni_postgres_password: flsdjkfasdjhfsd
patroni_replicator_password: flsdjkfasdjhfsd
cluster_virtual_ip: 192.xx.xx.125
</spoiler>
А отдельную для каждого сервера - в каталоге host_vars/имя_сервера:

<spoiler title="/etc/ansible/host_vars/pgsql-cluster-01.local/main.yml">
<source lang="yaml">
patroni_node_name: cluster_pgsql_01
keepalived_priority: 99
```

Расшифрую для чего нужны некоторые переменные:

patroni\_scope — название кластера при регистрации в Consul

patroni\_node\_name — название сервера при регистрации в Consul

patroni\_rest\_password — пароль для http интерфейса Patroni (требуется для отправки команд на изменение кластера)

patroni\_postgres\_password: пароль для юзера postgres. Он устанавливается в случае создания patroni новой базы.

patroni\_replicator\_password — пароль для юзера replicator. От его имени осуществляется репликация на слейвы.

Также в этом файле перечислены некоторые другие переменные, используемые в других плейбуках или ролях, в частности тот может быть настройка ssh (ключи, пользователи), таймзона для сервера, приоритет сервера в кластере keepalived, и.т.п.

Конфигурация для остальных серверов аналогична, соответственно меняется имя сервер и приоритет (например 99-100-101 для трех серверов).

Установка и настройка haproxy:

**/etc/ansible/roles/ansible-role-patroni/tasks/haproxy.yml**

```jsx
---

- name: Install haproxy
  yum: name={{ item }} state=latest
  tags:
    - patroni
    - haproxy
  with_items:
    - haproxy
  when: install is defined

- name: put config
  template: src=haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg backup=yes
  tags:
    - patroni
    - haproxy

- name: restart and enable
  service: name=haproxy state=restarted enabled=yes
  tags:
    - patroni
    - haproxy
```

Haproxy устаналивается на каждом хосте, и содержит в своем конфиге ссылки на все сервера PostgreSQL, проверяет какой сервер сейчас является мастером, и отправляет запросы на него.

Для этой проверки используется прекрасная фича Patroni — REST интерфейс.

При обращении на урл [server](http://server/):8008 (8008 это порт по умолчанию) Patroni возвращает отчет по состоянию кластера в json, а также отражает кодом ответа http является ли данный сервер мастером. Если является — будет ответ с кодом 200. Если же нет, ответ с кодом 503.

Очень советую обратится в документацию на Patroni, http интерфейс там достаточно интересный, позволяется также принудительно переключать роли, и управлять кластером.

Аналогично, это можно делать при помощи консольной утилиты patronyctl.py, из поставки Patroni.

Конфигурация haproxy достаточно простая:

**/etc/ansible/roles/ansible-role-patroni/templates/haproxy.cfg**

```jsx
global
maxconn 800

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

frontend ft_postgresql
bind *:5000
default_backend postgres-patroni

backend postgres-patroni
  option httpchk

  http-check expect status 200
  default-server inter 3s fall 3 rise 2

  server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008
  server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008
  server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008
```

В соответствии с этой конфигурацией haproxy слушает порт 5000, и отправляет трафик с него на мастер сервер.

Проверка статуса происходит с интервалом в 1 секунду, для перевода сервера в даун требуется 3 неудачных ответа (код 500), для переключения сервера назад — 2 удачных ответа (с кодом 200).

В любой момент времени можно обратиться непосредственно на любой haproxy, и он корректно запроксирует трафик на мастер сервер.

Также в комплекте с Patroni есть шаблон для настройки демона confd, и пример его интеграции с etcd, что позволяет динамически менять конфиг haproxy при удалении или добавлении новых серверов.

Я же пока делаю достаточно статичный кластер, лишняя автоматизация в данной ситуации, имхо, может привести к непредвиденным проблемам.

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

Демон keepalived работает по протоколу vrrp со своими соседями, и в результате выборов одного из демонов как главного (приоритет указан в конфиге, и шаблонизирован в переменную keepalived\_priority в host\_vars для каждого сервера), он поднимает у себя виртуальный ip адрес.

Остальные демоны терпеливо ждут. Если текущий основной сервер keepalived по какой-то причине умрет либо просигналит соседям аварию, произойдут перевыборы, и следуюший по приоритету сервер заберет себе виртуальный ip адрес.

Для защиты от падения haproxy демоны keepalived выполняют проверку, запуская раз в секунду команду «killall -0 haproxy». Она возвращает код 0 если процесс haproxy есть, и 1 если его нет.

Если haproxy исчезнет, демон keepalived просигналит аварию по vrrp, и снимет виртуальный ip.

Виртуальный IP сразу же подхватит следующий по приоритету сервер, с живым haproxy.

Установка и настройка keepalived:

**/etc/ansible/roles/ansible-role-patroni/tasks/keepalived.yml**

```jsx
---

- name: Install keepalived
  yum: name={{ item }} state=latest
  tags:
    - patroni
    - keepalived
  with_items:
    - keepalived
  when: install is defined

- name: put alert script
  template: src=alert.sh.j2 dest=/usr/local/sbin/alert.sh backup=yes mode=755
  tags:
    - patroni
    - keepalived
  when: install is defined

- name: put config
  template: src=keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf backup=yes
  tags:
    - patroni
    - keepalived

- name: restart and enable
  service: name=keepalived state=restarted enabled=yes
  tags:
    - patroni
    - keepalived
```

Кроме установки keepalived, этот плейбук также копирует простой скрипт для отправки алертов через телеграм. Скрипт принимает сообщение в виде переменной, и просто дергает curl-ом API телеграма.

В этом скрипте только нужно указать свои токен и ID группы telegram для отсылки оповещений.

Конфигурация keepalived описана в виде jinja2 шаблона:

**/etc/ansible/roles/ansible-role-patroni/templates/keepalived.conf.j2**

```jsx
global_defs {
   router_id {{ patroni_node_name }}
}

vrrp_script chk_haproxy {
        script "killall -0 haproxy"
        interval 1
        weight -20
        debug
        fall 2
        rise 2
}

vrrp_instance {{ patroni_node_name }} {
        interface ens160
        state BACKUP
        virtual_router_id 150
        priority {{ keepalived_priority }}
        authentication {
            auth_type PASS
            auth_pass secret_for_vrrp_auth
        }
        track_script {
                chk_haproxy weight 20
        }
        virtual_ipaddress {
                {{ cluster_virtual_ip }}/32 dev ens160
        }
        notify_master "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became MASTER'"
        notify_backup "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became BACKUP'"
        notify_fault "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became FAULT'"

}
```

В переменные patroni\_node\_name, cluster\_virtual\_ip и keepalived\_priority транслируются соответствующие данные из host\_vars.

Также в конфиге keepalived указан скрипт для отправки сообщений о смене статуса в telegram канал.

Накатываем полную конфигурацию кластера на сервера:

```
~# ansible-playbook cluster-pgsql.yml
```

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

Если же не хочется дольше ждать, или вы уверены что сервера полностью готовы, можно запустить ansible-playbook с ключом -t patroni.

Тогда будут выполнены только шаги из роли Patroni.

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

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

В случае запуска слейва отставшего на какое-то время от мастера, Patroni автоматически вольет изменения при помощи pg\_rewind.

Убеждаемся что все сервера запустились и выбрали себе роли:

```
~# journalctl -f -u patroni
```

Сообщения со слейва (сервер cluster-pgsql-01):

```jsx
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,254 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_01
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_01
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: does not have lock
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: no action. i am a secondary and i am following a leader
```

Сообщения с мастера (в данном случае это сервер cluster-pgsql-02):

```jsx
Feb 17 23:52:23 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:23,457 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:23 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:23,874 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:24 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:24,082 INFO: no action. i am the leader with the lock
Feb 17 23:52:33 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:33,458 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:33 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:33,884 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:34 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:34,094 INFO: no action. i am the leader with the lock
```

По логам ясно видно что каждый сервер постоянно мониторит свой статус и статус мастера.

Попробуем остановить мастер:

```
~# systemctl stop patroni
```

```jsx
Feb 17 23:54:03 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:03,457 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:54:03 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:03,880 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:54:04 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:04,092 INFO: no action. i am the leader with the lock
Feb 17 23:54:11 cluster-pgsql-02.local systemd[1]: Stopping Runners to orchestrate a high-availability PostgreSQL...
Feb 17 23:54:13 cluster-pgsql-02.local patroni.py[4913]: waiting for server to shut down.... done
Feb 17 23:54:13 cluster-pgsql-02.local patroni.py[4913]: server stopped
```

А вот что в этот момент произошло на слейве:

```jsx
Feb 17 19:54:12 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:12,353 INFO: does not have lock
Feb 17 19:54:12 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:12,776 INFO: no action. i am a secondary and i am following a leader
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,440 WARNING: request failed: GET http://192.xx.xx.121:8008/patroni (HTTPConnectionPool(host='192.xx.xx.121', port=8008
): Max retries exceeded with url: /patroni (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x1f12750>: Failed to establish a new connection: [Er
rno 111] Connection refused',)))
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,444 INFO: Got response from cluster_pgsql_03 http://192.xx.xx.122:8008/patroni: {"database_system_identifier": "63847
30077944883705", "postmaster_start_time": "2017-02-17 05:36:52.388 MSK", "xlog": {"received_location": 34997272728, "replayed_timestamp": null, "paused": false, "replayed_location": 34997272
728}, "patroni": {"scope": "clusters-pgsql", "version": "1.2.3"}, "state": "running", "role": "replica", "server_version": 90601}
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: server promoting
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,961 INFO: cleared rewind flag after becoming the leader
Feb 17 19:54:14 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:14,179 INFO: promoted self to leader by acquiring session lock
Feb 17 19:54:23 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:23,436 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_01
Feb 17 19:54:23 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:23,857 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_01
Feb 17 19:54:24 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:24,485 INFO: no action. i am the leader with the lock
```

Этот сервер перехватил роль мастера на себя.

А теперь вернем сервер 2 обратно в кластер:

```
~# systemctl start patroni
```

```jsx
Feb 18 00:02:11 cluster-pgsql-02.local systemd[1]: Started Runners to orchestrate a high-availability PostgreSQL.
Feb 18 00:02:11 cluster-pgsql-02.local systemd[1]: Starting Runners to orchestrate a high-availability PostgreSQL...
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,186 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,190 WARNING: Postgresql is not running.
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,190 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,398 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,400 INFO: starting as a secondary
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,412 INFO: rewind flag is set
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: changing primary_conninfo and restarting in progress
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,631 INFO: running pg_rewind from user=superuser host=192.xx.xx.120 port=5432 dbname=postgres sslmode=prefer sslcompression=1
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: servers diverged at WAL position 8/26000098 on timeline 25
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: rewinding from last common checkpoint at 8/26000028 on timeline 25
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: Done!
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:14,535 INFO: postmaster pid=56893
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: < 2017-02-18 00:02:14.554 MSK > LOG: redirecting log output to logging collector process
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: < 2017-02-18 00:02:14.554 MSK > HINT: Future log output will appear in directory "pg_log".
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: localhost:5432 - accepting connections
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: localhost:5432 - accepting connections
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,790 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: does not have lock
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: establishing a new patroni connection to the postgres cluster
Feb 18 00:02:16 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:16,014 INFO: no action. i am a secondary and i am following a leader
```

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

Попробуем создать ошибку на другом слое кластера, остановив haproxy на основном сервере keepalived.

По приоритету, эту роль у меня принимает второй сервер:

> \[root\@cluster-pgsql-02 \~]# ip a 2: ens160: \<BROADCAST,MULTICAST,UP,LOWER\_UP> mtu 1500 qdisc mq state UP qlen 1000 link/ether 00:50:56:a9:b8:7b brd ff:ff:ff:ff:ff:ff inet 192.xx.xx.121/24 brd 192.168.142.255 scope global ens160 valid\_lft forever preferred\_lft forever inet 192.xx.xx.125/32 scope global ens160 <---- виртуальный адрес кластера valid\_lft forever preferred\_lft forever inet6 fe80::xxx::4895:6d90/64 scope link valid\_lft forever preferred\_lft forever

Остановим haproxy:

```
~# systemctl stop haproxy ; journalctl -fl
```

> Feb 18 00:18:54 cluster-pgsql-02.local Keepalived\_vrrp\[25018]: VRRP\_Script(chk\_haproxy) failed Feb 18 00:18:56 cluster-pgsql-02.local Keepalived\_vrrp\[25018]: VRRP\_Instance(cluster\_pgsql\_02) Received higher prio advert Feb 18 00:18:56 cluster-pgsql-02.local Keepalived\_vrrp\[25018]: VRRP\_Instance(cluster\_pgsql\_02) Entering BACKUP STATE Feb 18 00:18:56 cluster-pgsql-02.local Keepalived\_vrrp\[25018]: VRRP\_Instance(cluster\_pgsql\_02) removing protocol VIPs. Feb 18 00:18:56 cluster-pgsql-02.local Keepalived\_vrrp\[25018]: Opening script file /usr/bin/sh Feb 18 00:18:56 cluster-pgsql-02.local Keepalived\_healthcheckers\[25017]: Netlink reflector reports IP 192.xx.xx.125 removed

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

Смотрим что произошло на втором сервере:

> Feb 18 00:18:56 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) forcing a new MASTER election Feb 18 00:18:56 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) forcing a new MASTER election Feb 18 00:18:56 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) forcing a new MASTER election Feb 18 00:18:56 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) forcing a new MASTER election Feb 18 00:18:57 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Transition to MASTER STATE Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Entering MASTER STATE Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) setting protocol VIPs. Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125 Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: Opening script file /usr/bin/sh Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Received lower prio advert, forcing new election Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125 Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_healthcheckers\[41189]: Netlink reflector reports IP 192.xx.xx.125 added Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Received lower prio advert, forcing new election Feb 18 00:18:58 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125 Feb 18 00:19:03 cluster-pgsql-01.local Keepalived\_vrrp\[41190]: VRRP\_Instance(cluster\_pgsql\_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125

Дважды произошли перевыборы (потому что третий сервер кластера успел отправить свой анонс до первых выборов), сервер 1 принял на себя роль ведущего, и выставил виртуальный IP.

Убеждаемся в этом:

> \[root\@cluster-pgsql-01 log]# ip a 2: ens160: \<BROADCAST,MULTICAST,UP,LOWER\_UP> mtu 1500 qdisc mq state UP qlen 1000 link/ether 00:50:56:a9:f0:90 brd ff:ff:ff:ff:ff:ff inet 192.xx.xx.120/24 brd 192.xx.xx.255 scope global ens160 valid\_lft forever preferred\_lft forever inet 192.xx.xx.125/32 scope global ens160 <---- виртуальный адрес кластера присутствует! valid\_lft forever preferred\_lft forever inet6 fe80::1d75:40f6:a14e:5e27/64 scope link valid\_lft forever preferred\_lft forever

Теперь виртуальный IP присутствует на сервере, не являющимся мастером репликации. Однако это не имеет значения, поскольку в базу мы обращаемся через haproxy, а она мониторит состояние кластера независимо, и отправляет запросы всегда на мастер.

При возврате в строй haproxy на втором сервере снова происходят перевыборы (keepalived с бОльшим приоритетом встает в строй), и виртуальный IP возвращается на свое место.

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

~~«rm -rf /var/lib/pgsql/9.6/data», и перезапустить Patroni. Она сольет базу с мастера целиком.*(Осторожно с очисткой «ненужных» баз, внимательно смотрите на каком сервере вы выполняете команду!!!)*~~

В таком случае нужно воспользоваться утилитой patronictl. Команда reinit позволяет безопасно очистить конкретный узел кластера, на мастере она выполняться не будет.

Спасибо за дополнение [CyberDemon](https://habrahabr.ru/users/cyberdemon/).

Сама утилита patronictl позволяет увидеть текущую ситуацию с кластером через командную строку, без обращений в DCS, и управлять кластером.

Пример отчета о состоянии кластера:

/opt/patroni/patronictl.py -c /etc/patroni/postgres.yml list cluster-pgsql:

> +---------------+------------------+-----------------+--------------+------------------+-----------+ | Cluster | Member | Host | Role | State | Lag in MB | +---------------+------------------+-----------------+--------------+------------------+-----------+ | cluster-pgsql | cluster\_pgsql\_01 | 192.xxx.xxx.120 | Leader | running | 0.0 | | cluster-pgsql | cluster\_pgsql\_02 | 192.xxx.xxx.121 | Sync standby | running | 0.0 | | cluster-pgsql | cluster\_pgsql\_03 | 192.xxx.xxx.122 | | creating replica | 33712.0 | +---------------+------------------+-----------------+--------------+------------------+-----------+

В данном случае наливается третья нода, ее отставание от мастера составляет 33 Гб.

После завершения этого процесса она также переходит в состояние Running с нулевым лагом.

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

### Послесловие

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

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

Стриминговая (асинхронная) репликация не обеспечивает консистентности кластера в любой момент времени, и для этого нужна синхронная репликация.

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

Patroni поддерживает все варианты, и если синхронная репликация вам подойдет больше, вам всего лишь понадобится изменить значение нескольких полей в конфигах Patroni.

Вопросы разных методов репликации прекрасно разобраны в документации к Patroni.

Кто-то наверное предложит использовать pgpool который сам, по сути, покрывает весь функционал этой системы. Он может и мониторить базы, и проксировать запросы, и выставлять виртуальный IP, а также осуществляет пулинг коннектов клиентов.

Да, он все это может. Но на мой взгляд схема с Patroni гораздо прозрачнее (конечно это только мое мнение), и во время экспериментов с pgpool я ловил странное поведение с его вочдогом и виртуальными адресами, которое не стал пока слишком глубоко дебажить, решив поискать другое решение.

Конечно возможно, что проблема тут только моих в руках, и позже я к тестированию pgpool планирую вернуться.

Однако, в любом случае, pgpool не сможет полностью автоматически управлять кластером, вводом новых и (особенно) возвратом сбойных серверов, работать с DCS. На мой взгляд это самый интересный функционал Patroni.

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

Огромное спасибо Zalando за Patroni, и авторам исходного проекта [Governor](https://github.com/compose/governor), который послужил основой для Patroni, а также [Алексу Чистякову](https://github.com/alexclear) за шаблон роли для Ansible.

Полный код плейбуков и шаблонов Ansible, описанных в статье [лежит тут](https://github.com/imcitius/ansible-pgsql_patroni_cluster). Буду благодарен за доработки от гуру Ansible и PostgreSQL. :)

Основные использованные статьи и источники:

Несколько вариантов кластеров PgSQL:

→ <https://habrahabr.ru/post/301370/>

→ <https://habrahabr.ru/post/213409/>

→ <https://habrahabr.ru/company/etagi/blog/314000/>

→ [Пост о Patroni в блоге Zalando](https://tech.zalando.com/blog/zalandos-patroni-a-template-for-high-availability-postgresql/)

→ [Проект Patroni](https://github.com/zalando/patroni)

→ [ansible-role-patroni Алекса Чистякова](https://github.com/gitinsky/ansible-role-patroni)

→ [Governor](https://github.com/compose/governor) — к сожалению разработка давно заморожена.

→ [Книга Ansble for Devops](https://www.ansiblefordevops.com/) — прекрасный учебник с кучей примеров применения Ansible.


# Mass parallel requests - Greenplum

<https://habr.com/ru/companies/tinkoff/articles/694652/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fab9f659b16f979da53d0b85680116ef98c29aaa%2Fb617260fdbf8f7f157607340e36be427.png?alt=media)

Меня зовут Дмитрий Немчин, я руковожу отделом, который отвечает за движки хранения и обработки данных в платформе данных Тинькофф. Несколько лет назад мы поняли, что продукты, на которых работало хранилище, перестали нас устраивать. Объемы росли, понадобилось масштабируемое решение. В этом тексте я расскажу, как мы пришли к Greenplum в качестве ядра хранилища данных и как используем его.

Вообще, эта статья должна была стать расшифровкой моего доклада, с которым я выступал на HighLoad++ в 2018 году. Но с тех пор у нас произошло много изменений, которые очень хочется показать сообществу. Поэтому статья — микс из ретроспективы и текущих реалий нашей большой платформы данных. Кроме того, в процессе у статьи появился второй автор — написать ее помог [@koskar](https://habr.com/users/koskar)

## Как мы пришли к использованию Greenplum

В начале было слово, и этим словом был SAS. Все наше хранилище когда-то строилось на продуктах SAS. Для обработки данных мы использовали один сервер, хранили их на СХД, а процессы строились только на использовании паттерна ETL. Если говорить просто, то: открыли базу-источник, что-то оттуда забрали, преобразовали и положили в КХД.

Все работало отлично, пока не выросли объемы (удивительно, не правда ли?). Конечно же, мы уперлись в процессор с памятью на сервере обработки и производительность СХД.

Нужно было что-то менять, причем радикально. Изучив разные решения, мы выбрали Greenplum за высокую производительность и простоту (слегка обманчивую, но все же). Как выяснилось потом, мы получили неплохой бонус: через несколько лет Greenplum вышел в Open Source. В текущих обстоятельствах это очень сильное конкурентное преимущество!

Коротко о том, как Greenplum обрабатывает пользовательские запросы:

1. Запрос приходит на мастер.
2. Запрос отправляется на сегменты (небольшие инстансы Postgres).
3. Каждый сегмент обрабатывает свою часть данных.
4. Данные со всех сегментов собираются на мастере, сортируются, агрегируются и возвращаются клиенту.

Подробнее о Greenplum можно узнать [из этого текста](https://habr.com/ru/company/tinkoff/blog/267733/), а как с ним жить с точки зрения DBA — [из этого.](https://habr.com/ru/company/southbridge/news/t/676648/)

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

Greenplum в нашей компании используется с 2013 года. Я же пришел в компанию в 2015 году, когда вокруг Greenplum уже было построено заметное число процессов и даже один внутренний продукт. Но обо всем по порядку.

В те далекие времена заметную часть данных мы читали напрямую из операционных баз данных, а часть реплицировали непосредственно в КХД, используя Attunity Replicate. Данных в DWH у нас было порядка 20 ТБ, каждую ночь запускалось около 2 тысяч ETL\ELT-джобов, которые строили хранилище. Уже тогда у нас работали два боевых кластера из 16 машин каждый (для катастрофоустойчивости) и тестовый кластер, тоже из 16 машин. Во всех серверах Greenplum были только HDD, и все это жило и даже процветало, то есть вполне справлялось с нагрузками.

Но объемы данных вместе с количеством пользователей платформы постоянно росли, и к 2018 году объем данных в нашем Greenplum превысил 70 ТБ. На тот момент мы сделали свое решение для DR и свою систему репликации, а число ETL/ELT-джобов на каждую ночь выросло до примерно 4000. Причем теперь это были по большей части ELT. Объем каждого из двух боевых кластеров вырос до 36 машин, столько же машин теперь было в тестовом кластере.

Часть данных мы стали хранить на очень быстрых NMVe PCIe картах, часть — на SAS SSD, остальное — на RAID-массивах из HDD. Это дало заметный буст скорости работы с данными. Когда мы это затеяли, даже вендор Greenplum (Pivotal, сейчас — часть VMWare) высказывал сомнения в целесообразности такого решения. А сейчас такие решения стали стандартом построения кластеров Greenplum и других [MPP (massive parallel processing)](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%81%D1%81%D0%BE%D0%B2%D0%BE-%D0%BF%D0%B0%D1%80%D0%B0%D0%BB%D0%BB%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0) БД.

Поговорим подробнее о пользователях нашей платформы. Тинькофф — data-driven компания, и без данных и цифр у нас решения не принимаются. При этом мы стараемся не увеличивать количество бюрократии для получения доступа к данным. Нет, информационная безопасность у нас есть и не дремлет, но это совсем не такая информационная безопасность, какой ее можно представить. Несмотря на то, что у нас есть банковская лицензия, они всеми силами стараются сделать работу компании безопасной, не создавая лишних препятствий.

Потребители данных у нас работают по двум направлениям: отчетность и аналитика. В 2018 году отчетность строилась в основном в SAP BusinessObjects и Tableau, а ad hoc аналитика была сосредоточена в SAS и Apache Zeppelin. MAU платформы в те времена составляло около 3 тысяч пользователей.

Позже на замену Apache Zeppelin пришел Helicopter, а от SAS и SAP мы постепенно уходим, но это уже выходит за рамки темы статьи. При этом MAU выросло до примерно 6 тысяч пользователей. Да, это MAU платформы данных.

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b80eba1c89d377a702ec2afa7f6fad6c48164219%2F23dd80e8c44a965e92fafbe0405793f3.png?alt=media)

Верхнеуровневая структура хранилища данных на 2018 год

## Как и почему мы отказались от Attunity Replicate в пользу своей CDC-системы

Начнем с репликации данных. ETL — это замечательно. Но с ростом объемов все приходит к тому, что постоянно лезть в операционные базы не нужно. Хотя бы потому, что это создает бешеную нагрузку и можно получить по шапке. Ну и да, можно своей нагрузкой сломать боевой процессинг денег или положить сайт/приложение. Вариантов получить критические нагрузки на боевых системах много.

Поэтому все хранилища любят CDC — change data capture. Это захват изменений в данных и метаданных источников, обычно выполняющийся более-менее в реальном времени. Но для доставки таких изменений в MPP систему, например в Greenplum, данные собираются в батчи, чтобы не убивать MPP движок построчными изменениями. Далее эти батчи изменений применяются в Greenplum (в нашем случае). А потом запускаем свой страшный ELT, обрабатываем данные уже в отдельной от боевых баз системе и спокойно строим аналитическое хранилище, отчеты и все, что придет нам в голову. Например, обучение ML.

Вначале, еще до моего прихода в компанию, Тинькофф для этих целей использовал Attunity Replicate. На каждый Greenplum у нас был свой сервер Attunity. У системы было несколько плюсов, но минусы в итоге перевесили.

Disclaimer: все, относящееся к Attunity Replicate верно для ее версий 2015-2016 годов. Примерно в это время мы уже начали строить свою систему репликации данных.

Плюсы:

— удобная система с функциональным веб-интерфейсом;

— хорошо работает на небольших объемах и при стабильной схеме данных.

Минусы:

— row-by-row. Если апдейт не прошел, система начинает построчно проводить апдейты. Greenplum этого крайне не любит;

— отстаивание репликации при нагрузке на источник;

— изменение DDL требует полной перезагрузки таблицы;

— убивает диски Greenplum, создавая слишком большую нагрузку;

— работает на Windows, а для нас был привычнее Linux.

Пожив какое-то время с Attunity, мы пришли к решению создать собственную CDC-систему. Что мы хотели от нее получить?

Во-первых, снизить нагрузку на приемники. Сделать батчи с регулировкой размера и получить возможность произвольное количество времени ничего не грузить (например, выделить окно для работы ETL/ELT без паразитных нагрузок на кластер).

Во-вторых, автоматически применять DDL. Бывало, что из-за ночных релизов у нас падало что-то важное в репликации и приходилось это чинить, а мы хотели спать по ночам.

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

В-четвертых, мы хотели Linux, поскольку фактически все другие сервисы у нас жили и живут на Linux.

Нам удалось всего этого достичь. Текущее решение — не полностью наш продукт: в начале потока загрузки данных мы используем Oracle Golden Gate. С его помощью мы из нескольких десятков баз Oracle сливаем одну большую базу ODS. Из нее выгружаем данные нашими самописными процессами во временные файлы и применяем их аккуратно во все Greenplum с удобными для нас размерами батчей и частотой их применения. Unload, то есть выгрузка из ODS, делается с помощью нашей самописной библиотеки, а apply сделан на механизмах Greenplum.

Сейчас CDC-система переваривает довольно большие объемы. Только в Greenplum хранится более 150 ТБ (на 2018 год было порядка 20 ТБ) данных с учетом сжатия. Теперь мы загружаем данные не только из Oracle, но и из Postgres. Лаг репликации — до двух часов от попадания строки в источник до попадания в DWH для самых критичных данных. Можно сделать еще меньше, но мы не хотим грузить Greenplum. Также есть возможность отгружать данные в Kafka.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f63e14a6d6e978f88a969a38b7afb11f71905747%2F6f3c0f381b6b9beb0750c9a42dbf98da.png?alt=media)

Архитектура нашей CDC-системы

## Что будет, если в ЦОД попадет метеорит

Теперь поговорим о disaster recovery для Greenplum. В 2015 году DR-решений для GP, каким его видели мы, просто не было. Больше того: в таком виде его, наверное, нет и сейчас. Есть много других решений, но нам они не подходят по разным причинам.

Сам по себе Greenplum — отказоустойчивая система. В ней есть primary-сегмент и зеркала. Если primary-сегмент упал, зеркало с ним синхронится. Оно поднимается, клиент переподключается к базе, и все работает. Но у нас на Greenplum завязана вся отчетность и аналитика, а количество пользователей и важность платформы данных для компании в целом очень велики. И если в ЦОД попадет метеорит или случится что-то еще, нам будет очень грустно без данных.

Так как наше хранилище строится на 90% SAS, у нас есть свой планировщик, который запускает SAS-джобы. Нам нужен был пообъектный перенос из этого планировщика. Представим простую ситуацию: на дворе ночь и мы строим Хранилище. У нас отработали 2000 джобов и осталось еще 2000 джобов впереди. И тут оно: в ЦОДе что-то пошло не так и мы его потеряли. И вместо того, чтобы перезапустить все, хочется перезапустить только половину, то есть продолжить построение с момента аварии.

А еще очень хочется иметь резервные копии всего, что мы уже построили: мало ли что еще может пойти не так. Обе эти проблемы отлично решает наш DUET. Да, сначала его называли Dual ETL, хотя по факту никакого двойного ETL/ELT не происходит.

## Первое пришествие DUET

В целом наши требования к disaster recovery для Greenplum были такими:

— запуск переноса из SAS-планировщика;

— создание и проверка бэкапов в ходе переноса;

— минимальная задержка доставки данных на резервный кластер;

— возможность регулировать нагрузку (количество потоков) на каждой стадии: бэкап, перенос, рестор, сброс на СХД;

— возможность использовать бэкапы для доставки данных на контуры тестирования и разработки.

Поначалу мы пробовали очевидные подходы: gp\_transfer, gpfdist, gpfdist + pipes. Все работало замечательно, но бэкапов не было. Поэтому мы просто взяли большую NFS-шару и сделали на нее dump, а с нее — restore. Бэкапы появились, но мы быстро уперлись в производительность NFS и поняли, что с ростом нагрузки использовать ее будет все тяжелее и дороже.

В итоге мы пришли к следующей схеме:

— делаем dump на локальные диски (независимые от дисков БД, это были небольшие SAS SSD);

— на каждом сервере мы запускаем SCP, которая просто переносит это на соответствующие машины в другой кластер;

— на втором кластере запускаем рестор фактически из локальных файлов;

— если рестор прошел корректно — складываем все на хранилку (опять же NFS).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9a8fa240ab1cdd80daa0dc9fdcb6ff982acb17c3%2F51197469298dd93a627f8aeb098500b2.png?alt=media)

DUET первой версии

Стоит отдельно отметить, что хранилок было две (на двух площадках) и синхронизировались они самописным python-приложением. В какой-то момент это самописное приложение перестало вывозить наши потоки данных, и в итоге мы перешли на SDS LizardFS.

Показатели DUET на 2018 год:

— перегон 30 ТБ данных в сутки;

— задержка от построения объекта на боевой базе до доступности данных объекта на резервной базе данных — до двух часов;

— поддержка базы данных, частично работающей на зеркалах;

— постановка объектов в очередь SAS-планировщика;

— управление через веб-приложение.

При этом у первой версии DUET было несколько проблем:

— перенос управлялся python-скриптами, не было нормального API;

— локальные диски для бэкапов — фактически две записи одних и тех же данных (сначала бэкап, потом вынос в LizardFS отдельно);

— нет инкремента, что при нашем (иногда взрывном) росте — больно;

— и да, тогда мы переносили объекты полностью, даже не по партициям. А это снова боль от объемов;

— использование python-утилит из поставки Greenplum для бэкапа/рестора данных. Они создавали транзакции уровня serializable, из-за которых во всей БД не работал vacuum. Так и хомячка на куски разорвать недолго.

## DUET2

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5a03b870233cdbc4e1c00ac3bec25f20f306a782%2Fda89022c483926d7017a5662e90f3861.jpg?alt=media)

DUET2

Вторая версия нашей DR-системы привнесла следующие изменения:

— появление полноценного RestAPI;

— при этом для каждого кластера Greenplum мы сделали N очередей под разные задачи как на бэкап, так и на рестор;

— бэкапы мы стали сразу писать на LizardFS;

— вместо python-утилит мы перешли на использование COPY ON SEGMENT, что позволило уйти от serializable-транзакций;

— часть объектов переносилась по партициям.

Эта версия DUET переваривала уже до 70 ТБ в сутки. А кластеров Greenplum у нас на тот момент стало не три, а четыре.

## DUET3

Где-то во времена перехода к DUET3 кластеров Greenplum у нас уже стало восемь. Соответственно, реализация инкрементного переноса стала мегакритичной.

Вот как она работает сейчас:

1. Сперва ETL-планировщик при внесении изменений в таблицу использует insert-only diff-таблицу. Он записывает в нее строки, которые должны измениться в таргет-таблице, а потом из таргет-таблицы делает delete по ключам и insert данных. При этом он заполняет поле processed\_dttm в основной и diff-таблице для новых/изменяемых записей одним значением. Эти diff-таблицы партицированы по дням и хранят партиции только за последнюю неделю. Поэтому они маленькие и запросы к ним работают быстро.
2. После отработки джоба ETL-планировщик откидывает в специальный сервис событие, в котором есть информация о произведенных изменениях: имя таблицы, processed\_dttm, параметры diff (например, имя diff-таблицы, признак ее валидности, ключ для применения изменений, дополнительный фильтр для построения более оптимального запроса для delete-данных).
3. Событие обрабатывается и создается таск для DUET.
4. DUET по таску определяет, необходим ли полный перенос (full-таск) или достаточно перенести и накатить инкремент (diff-таск).
5. Если достаточно инкремента, делается дамп таблицы (select \* from diff\_table where processed\_dttm = <>). Причем дамп только нужных записей для обновления таргет-таблицы.
6. Этот дамп сохраняется в LizardFS, а затем восстанавливается в соответствующие diff-таблицы на кластерах приемниках. Затем воспроизводится логика delete+insert нужных данных в основную таблицу из ETL-джоба.
7. Если в какой-то момент оказывается, что применение diff-таблицы невозможно, например на таргет-контуре у основной таблицы некорректный DDL или он вообще отсутствует, DUET сам себе ставит дополнительные таски на дамп и рестор полной таблицы.

DUET хранит историю дампов в виде цепочек. Цепочка имеет вид full \[ + diff, diff, ...] — содержит полный дамп и некоторое количество diff-дампов. Соответственно, при необходимости можно восстановить любую версию таблицы из этой цепочки путем восстановления full и нужного количества diff.

DUET следит за тем, чтобы такие цепочки не были слишком большими. Периодически он сам себе ставит себе таски на снятие полного дампа в период минимальной загрузки, чтобы начать новую цепочку. Сейчас DUET успешно переваривает около 200 ТБ данных каждый день. При этом он доставляет данные построенной модели на семь кластеров Greenplum и обеспечивает задержку от построения на ETL-кластере до доступности данных пользователям до 30 минут.

## Советы по применению Greenplum

Мы используем Greenplum почти десять лет. За это время мы хорошо изучили инструмент и поняли, как работать с ним эффективно. Вот, что мы делаем, чтобы упростить жизнь себе и пользователям.

**Помним про shared-nothing.** Greenplum — это MPP shared-nothing система. Она хорошо параллелится и масштабируется. Но MPP shared-nothing означает, что каждый маленький кусочек базы обрабатывает свой маленький кусочек данных. Следовательно, нужно стараться равномерно раскладывать данные по кластеру.

В Greenplum есть distribution key — ключ к распределению. Таблица может быть распределена рандомно по хэшу от всей строки или по одному или нескольким полям. Старайтесь раскладывать так, чтобы у вас все лежало ровно.

Архитектурно у всех подобных систем есть общая проблема: они работают со скоростью самого медленного сегмента. Если рейд просел по производительности на одном сервере, мы будем работать со скоростью этого рейда. Если мы положили данные криво и обращаемся к таблице, в которой они лежат, мы будем работать со скоростью этого запроса.

Кроме того, нужно учитывать нагрузку. Крайне желательно равномерно распределять временные данные в джойнах. Если запрос не вошел в память, он начинает писать диск. Соответственно, мы пишем кучу временных файлов на одну машину, а она тянет за собой кластер. Он не упадет, но будет работать медленнее.

**Избегаем большого количества null-ов в ключах джойна.** Об этом нюансе мы узнали, когда стали позволять большому количеству пользователей писать произвольный SQL в базе.

График ниже — это load average на кластере. Вы можете видеть, что один сервер выбивается. Больше того: с точки зрения базы выбивается один сегмент, на котором скапливаются эти null-ы. Сделать с этим в общем случае нельзя ничего. Можно только посоветовать пользователям отделять null-ы, чтобы потом их приджойнить.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a7b8924f8911f6d31ac488ef28721c98be6871b5%2F89d8c2fdee03a38abb24f56eb7505433.png?alt=media)

Перекос нагрузки из-за null в ключах джойна

**Режем количество и объем spill-файлов.** Spill-файлы (спиллы) — это временные файлы, которые Greenplum складывает на диске, если запрос не вошел в память. Есть несколько параметров базы, которые влияют на спиллы. Это ограничение объема спиллов на запрос, ограничение количества спиллов на запрос и ограничение объема спиллов на сегмент.

Все началось с того, что база внезапно начала падать, когда в нее приходили запросы со множеством временных данных. Мы решили ограничить объем. Падать стали в два раза реже, но все равно падали, а почему — непонятно.

Поразмыслив, мы почитали документацию и сделали XFS под Greenplum с размером блока 16 МБ. Все заработало, но база писала очень много мелких файлов. Мы писали N сотен файлов по 4 КБ, и база видела их как файлы по 4 КБ, а файловая система и операционка с нами не соглашались, ведь у них размер блока — 16 МБ. Написали тысячу файлов — все забили на ровном месте. Тогда мы начали резать количество спиллов — и все стало хорошо.

Дальше мы поняли, что объем спилл-файлов — один из параметров, по которым можно и нужно убивать любые запросы к Greenplum вне зависимости от их важности. Да, «мне надо срочно выгрузить данные для ЦБ» — недостаточно хороший аргумент для того, чтобы писать плохие запросы.

**Не злоупотребляем синхронизацией метаданных.** У нас довольно большие кластеры: несколько десятков тысяч объектов и больше двух миллионов атрибутов. А клиенты, которые изначально не точились под Greenplum, любят синхронизировать метаданные маленькими кусочками, по чуть-чуть. При этом пользователей у наших кластеров много.

Это приводило к тому, что на аналитической базе было по 300—500 тысяч запросов к каталожным таблицам. Маленьких, но идущих постоянно. Greenplum это не любит из-за лишней нагрузки. Обнаружив это, мы попросили пользователей отключить автосинхронизацию меты. Казалось бы — мелочь, но вместо 300 тысяч запросов в сутки у нас теперь 0,5 тысячи, а это существенное сокращение.

**Обучаем пользователей.** Многие очевидные для нас вещи могут не быть очевидными для пользователей. Для них все выглядит как очень большой Postgres. И эта простота очень обманчива!

Однажды мы заметили, что у нас две ночи подряд висит сессия от пользователя в состоянии . Мы стараемся такого избегать: это либо очень большая загрузка, либо очень большая выгрузка, либо незакрытая транзакция, что в любом Postgres плохо. В нашем случае это приводило к замедлению базы, поэтому сессию пришлось оба раза убить.

Мы нашли пользователя и попытались выяснить у него, что он делал. Оказалось, он забирал табличку весом 60 ГБ в один поток через мастер. А мог в 140+ потоков через gpfdist, но он не умел этим пользоваться. Мы все ему объяснили, и выгрузка начала занимать у него 40 минут вместо 4 часов, не замедляя базу. И волки сыты, и овцы целы.

Greenplum имеет свою специфику. У нас на wiki есть учебник с лучшими и худшими практиками, который мы дополняем. Раньше мы рассылали худшие запросы в виде дайджеста. Берем самые долго висящие запросы от пользователей в базе, оптимизируем и рассылаем на всех пользователей хранилища. Объясняем, что было не так и как мы это исправили. Работает очень классно, всем советую.

Но сейчас мы все же перешли к хорошему, как нам кажется, онбордингу коллег. Сделали им несколько крутейших учебных курсов на внутреннем портале и даже команды Data Partners внутри нашего управления, которые помогают пользователям получать крутые результаты в работе с данными, никому при этом не мешая.

## Заключение

Greenplum, как и любая другая технология, — не серебряная пуля. Но он очень хорошо может решать определенные задачи на достаточно больших (up to petabyte scale) объемах данных и не самых маленьких нагрузках. Его близость к Postgres и открытый код дают простор для реализации различного рода идей и исправления недостатков системы. Ну и сама по себе открытость кода — очень большой бонус в нынешних условиях.

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

Наша платформа данных — одна из самых больших в России по количеству пользователей и нагрузкам, и Greenplum остается ее ядром. За его долгую жизнь мы вместе прошли через множество вызовов, многому научились сами и обучили наших пользователей, построили большое количество автоматизаций и продолжаем развивать нашу платформу. Если у вас остались вопросы, пишите комментарии.


# PostgreSQL cluster for development and testing

<https://habr.com/ru/companies/first/articles/699644/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-136d63bcb568d7a1ddeb80c41dbe3219c754bc08%2F21e01fb7fd7d64efcdd6b4a24e3d9cfa.png?alt=media)

Сегодня СУБД PostgreSQL является одной из самых известных и популярных систем управления баз данными в мире. Открытый исходный код, отсутствие платы за использование, контроль целостности, репликация – это далеко не все преимущества данной СУБД. В современных реалиях, когда тема импортозамещения особенно актуальна, PostgreSQL может оказаться подходящим вариантом.

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

Как правило, при развертывании кластеров PostgreSQL используют сторонние инструменты такие как Patroni, stolon, repmgr.

В статье будет описана установка кластера PostgreSQL с помощью **Ansible** – инструмента, предназначенного для автоматизации настройки и развертывания программного обеспечения, а также инструмента **repmgr**, предназначенного для управления репликами и отказоустойчивостью в кластерах PostgreSQL.

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

## Подготовка к установке

В качестве примера будет использоваться виртуальная машина с установленной операционной системой **Ubuntu 20.04.3 LTS**. Узлы кластера будут представлены в виде 3 виртуальных машин под управлением ОС **Ubuntu 18.04 Bionic Beaver**, которые будут запущены на гипервизоре **VirtualBox**. Сами ВМ будут развернуты при помощи **Vagrant** – утилиты, предназначенной для создания и конфигурирования виртуальных окружений (под виртуальным окружением понимается более стандартное понятие – виртуальная машина). Ниже описаны хосты, которые будут использоваться в качестве кластера PostgreSQL:

**node1 192.168.56.11** Роль **primary**, она же мастер-нода;

**node2 192.168.56.12** Роль **standby**. Обычная рабочая нода;

**node3 192.168.56.13** Роль **witness**. В терминологии repmgr **witness** это нода, которая не является частью кластера и предназначена для выбора новой мастер-ноды в случае возникновения проблем с кластером.

Ниже перечислено ПО, которое будет использоваться в статье:

**Ansible**;

**Vagrant**;

**VirtualBox**;

**PostgreSQL**;

**repmgr**.

Сначала на управляющий хост (основной хост, с которого будет вестись управление Vagrant и Ansible) необходимо установить **Ansible**, **Vagrant** и **VirtualBox**.

Произвести установку Ansible можно разными способами. В данном примере установка будет произведена при помощи официального репозитория ansible. Для этого необходимо выполнить следующие шаги:

1. Обновить списки пакетов:

```
sudo apt update
```

2. Установить пакет software-properties-common:

```
sudo apt -y install software-properties-common
```

3. Добавить официальный репозиторий Ansible:

```
sudo add-apt-repository --yes --update ppa:ansible/ansible
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3832ae14709de80e4ef3eabc1bcaa2777d57cc77%2F1d9d27c7a38b538af20ab9a5f94be5ce.png?alt=media)

4. Установить Ansible:

```
sudo apt -y install ansible
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-55e2805f0693d250ffbc5063f6092f07ff00f885%2F04eab3fe3d2dd48913623a11e5cd5481.png?alt=media)

После того как установка будет завершена, можно проверить, что Ansible установился корректно, путем вывода его версии. Для этого достаточно выполнить команду:

```
ansible --version
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-134ebb9d5fc13a7441fab97bbe851a6615730101%2F620281e100b94cae3daf6d02b97d8db0.png?alt=media)

Если команда отобразила версию (первая строка с названием **ansible \[core <версия>**]), значит, пакет успешно и без ошибок установлен в системе.

Далее необходимо установить Vagrant. Установка производится из официального репозитория. Шаги по установке Vagrant:

1. Добавить gpg ключ от официального репозитория Vagrant:

```
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
```

2. Добавить официальный репозиторий hashicorp:

```
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8150a5c90546b18cf9be76a647381f43d0f6d139%2Fc77d153580e4ea917b51e2f39268c3ed.png?alt=media)

3. Обновить список репозиториев и установить пакет vagrant:

```
sudo apt update && sudo apt -y install vagrant
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e22306dd629f0e10db928dfc516d5ad8b51f303d%2Fe16e90abade925092c0dbdb37ce198da.png?alt=media)

После установки необходимо убедиться, что установка прошла успешно. Для этого в терминале необходимо ввести команду:

```
vagrant
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b380fcc87e261abae7282514c51fd7b9ca294f5a%2F128074d7aece8d6048ba8ed5ffdb56d2.png?alt=media)

Если команда вернула список команд и их описание, значит установка vagrant прошла успешно.

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

```
sudo apt -y install virtualbox virtualbox-dkms
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1059e90d56cbc922be78939cada312f7b219fdb4%2F0ab94fe4ccf4ed2d820acc28a699ae65.png?alt=media)

## Создание и подготовка Vagrantfile

Для создания виртуальных машин в vagrant используется специальный файл – **vagranfile**. Для его создания необходимо выполнить команду:

```
vagrant init
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6850fc44f77369523b6637c036823f15b39fa929%2F8556f97a24303b3c359009f2ee66949f.png?alt=media)

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

Содержимое файла будет следующим

```
Vagrant.configure("2") do |config|
  (1..3).each do |n|
	config.vm.define "node#{n}" do |define|
  	define.ssh.insert_key = false
  	define.vm.box = "ubuntu/bionic64"
  	define.vm.hostname = "node#{n}"
  	define.vm.network :private_network, ip: "192.168.56.1#{n}"
  	# if you would like to use port forwarding, uncomment the line below
  	# define.vm.network :forwarded_port, guest: 5432, host: "543#{n}"

  	define.vm.provider :virtualbox do |v|
    	v.cpus = 1
    	v.memory = 1024
    	v.name = "node#{n}"
  	end

  	if n == 3
    	define.vm.provision :ansible do |ansible|
      	ansible.limit = "all"
      	ansible.playbook = "playbook.yaml"

      	ansible.host_vars = {
        	"node1" => {:connection_host => "192.168.56.11",
                    	:node_id => 1,
                    	:role => "primary" },

        	"node2" => {:connection_host => "192.168.56.12",
                    	:node_id => 2,
  	                  :role => "standby" },

        	"node3" => {:connection_host => "192.168.56.13",
                    	:node_id => 3,
                    	:role => "witness" }
      	}
      	# to enable ansible playbook verbose mode, uncomment the line below
      	# ansible.verbose = "v"
    	end
  	end

	end
  end
end
```

Где:

**define.ssh.insert\_key** – если выставлен в **false,** то vagrant не будет автоматически создавать и использовать собственные SSH ключи;

***define.vm.box*** – задает имя образа для ВМ. Образы хранятся на сайте Vagrant Cloud;

**define.vm.hostname** – задает hostname виртуальным машинам;

**define.vm.network** – задает тип сети и диапазон IP адресов;

**v.cpus** – задает количество ядер, которое будет выделено для ВМ;

**v.memory** – задает количество оперативной памяти, которое будет выделено для ВМ;

**ansible.playbook** – прописывается полный путь до playbook Ansible. Vagrant имеет полную поддержку и интеграцию с Ansible;

**ansible.host\_vars** – в данном блоке прописываются хосты, на которых будет запущен playbook Ansible. Эквивалентен файлу инвентаризации в Ansible.

### Создание ролей в Ansible

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

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

Для удобства создадим директорию с именем **postgres-cluster:**

```
mkdir postgres-cluster
```

Далее необходимо перейти в созданный каталог и создать следующие директории:

```
mkdir group_vars
mkdir roles
```

В директории **roles** будут храниться все необходимые роли. В ней необходимо создать:

```
mkdir roles/postgres_12/tasks
mkdir roles/postgres_12/templates
mkdir roles/registration/tasks
mkdir roles/registration/templates
mkdir roles/repmgr/tasks
mkdir roles/repmgr/templates
mkdir roles/ssh/files/keys
mkdir roles/ssh/tasks
```

Начнем заполнять директории файлами с описанием необходимых действий (в терминологии Ansible каждая задача называется task). Но сначала необходимо заполнить файл с переменными. Они будут храниться в директории **group\_vars** в файле с именем **all.yaml**.

Содержимое файла представлено ниже:

```
group_vars/all.yaml
node1_ip: "192.168.56.11"
node2_ip: "192.168.56.12"
node3_ip: "192.168.56.13"
pg_version: "12"
```

В переменных с именем node прописаны IP-адреса, которые будут присвоены виртуальным машинам. Переменная ***pg\_version*** содержит версию PostgreSQL, которая будет установлена на хосты. В данном примере будет использоваться 12 версия.

Далее описываются роли. Для каждой роли в своей директории будет создана еще одна директория с именем **roles**, в которой будет находиться файл с именем **main.yaml.**

Первая роль предназначена для установки PostgreSQL

```
roles/postgres_12/tasks/main.yaml
- name: Add PostgreSQL apt key
  apt_key:
	url: https://www.postgresql.org/media/keys/ACCC4CF8.asc

- name: Add PostgreSQL repository
  apt_repository:
	# ansible_distribution_release = xenial, bionic, focal
	repo: deb http://apt.postgresql.org/pub/repos/apt/ {{ ansible_distribution_release }}-pgdg main

- name: Install PostgreSQL 12
  apt:
	name: postgresql-12
	update_cache: yes

- name: Copy database configuration
  template:
	src: full_postgresql.conf.j2
	dest: /etc/postgresql/12/main/postgresql.conf
	group: postgres
	mode: '0644'
	owner: postgres

- name: Copy user access configuration
  template:
	src: pg_hba.conf.j2
	dest: /etc/postgresql/12/main/pg_hba.conf
	group: postgres
	mode: '0640'
	owner: postgres

```

Порядок действий, описанный в роли, следующий:

1. Добавление ключа от официального репозитория postgres;
2. Добавление официального репозитория postgres;
3. Установка PostgreSQL 12;
4. Копирование и использование конфигурационного файла **full\_postgresql.conf.j2**,который заменит стандартный конфигурационный файл postgresql.conf;
5. Копирование и использование конфигурационного файла **pg\_hba.conf.j2**\*,\*который заменит стандартный конфигурационный файл pg\_hba.conf.

Конфигурационные файлы **full\_postgresql.conf.j2**и **pg\_hba.conf.j2**будут находиться по следующему пути: **roles/postgres\_12/templates**.

Содержимое файлов описано ниже

```
roles/postgres_12/templates/full_postgresql.conf.j2
data_directory = '/var/lib/postgresql/12/main'
hba_file = '/etc/postgresql/12/main/pg_hba.conf'
ident_file = '/etc/postgresql/12/main/pg_ident.conf'
external_pid_file = '/var/run/postgresql/12-main.pid'
port = 5432
max_connections = 100
unix_socket_directories = '/var/run/postgresql'
shared_buffers = 128MB
dynamic_shared_memory_type = posix
# repmgr
listen_addresses = '*'
shared_preload_libraries = 'repmgr'
wal_level = replica
max_wal_senders = 5
wal_keep_segments = 64
max_replication_slots = 5
hot_standby = on
wal_log_hints = on
```

Строки под комментарием **# repmgr** относятся к настройкам утилиты repmgr и предназначены для настройки репликации.

В конфигурационном файле **pg\_hba.conf.j2** прописаны сетевые доступы до всех нод кластера.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0dd4607d7faf6a093ba2eb4c261ac5f83af34ff1%2F30889bb0fda1450ac486ff3af4413adc.png?alt=media)

Следующая задача – создание SSH ключей для подключения к виртуальным машинам. Сначала на хостовой ОС необходимо сгенерировать SSH ключи. Команда ниже эквивалента команде ssh-keygen с той лишь разницей, что команда ниже сгенерирует ключи без интерактивного режима:

```
ssh-keygen -q -t rsa -f ~/.ssh/id_rsa <<<y >/dev/null 2>&1
```

Закрытый (**id\_rsa**) и открытый (**id\_rsa.pub**) ключи будут сохранены по умолчанию — в домашней директории пользователя в скрытой директории **.ssh**

Далее необходимо скопировать файл с открытым и закрытым ключом в директорию **/roles/ssh/files/keys** Итого в поддиректории keys будет два файла — **id\_rsa** и **id\_rsa.pub**.

Роль по использованию SSH ключей описана ниже

```
roles/ssh/tasks/main.yaml
- name: Install OpenSSH
  apt:
	name: openssh-server
	update_cache: yes
	state: present

- name: Create postgres SSH directory
  file:
	mode: '0755'
	owner: postgres
	group: postgres
	path: /var/lib/postgresql/.ssh/
	state: directory

- name: Copy SSH private key
  copy:
	src: "keys/id_rsa"
	dest: /var/lib/postgresql/.ssh/id_rsa
	owner: postgres
	group: postgres
	mode: '0600'

- name: Copy SSH public key
  copy:
	src: "keys/id_rsa.pub"
	dest: /var/lib/postgresql/.ssh/id_rsa.pub
	owner: postgres
	group: postgres
	mode: '0644'

- name: Add key to authorized keys file
  authorized_key:
	user: postgres
	state: present
	key: "{{ lookup('file', 'keys/id_rsa.pub') }}"

- name: Restart SSH service
  service:
	name: sshd
	enabled: yes
	state: restarted
```

Порядок действий, описанный в роли, следующий:

1. Установка пакета OpenSSH.
2. Создание директории, где будут храниться SSH ключи - /var/lib/postgresql/.ssh/;
3. Копирование закрытого ключа в директорию /var/lib/postgresql/.ssh/;
4. Копирование открытого ключа в директорию /var/lib/postgresql/.ssh/;
5. Добавление открытого ключа в файл authorized\_key;
6. Перезапуск демона sshd.

Следующая задача – установка и настройка repmgr.

Посмотреть

```
roles/repmgr/tasks/main.yaml

- name: Download repmgr repository installer

  get_url:

	dest: /tmp/repmgr-installer.sh

	mode: 0700

	url: https://dl.2ndquadrant.com/default/release/get/deb

- name: Execute repmgr repository installer

  shell: /tmp/repmgr-installer.sh

- name: Install repmgr for PostgreSQL {{ pg_version }}

  apt:

	name: postgresql-{{ pg_version }}-repmgr

	update_cache: yes

- name: Setup repmgr user and database

  become_user: postgres

  ignore_errors: yes

  shell: |

	createuser --replication --createdb --createrole --superuser repmgr &&

	psql -c 'ALTER USER repmgr SET search_path TO repmgr_test, "$user", public;' &&

	createdb repmgr --owner=repmgr

- name: Copy repmgr configuration

  template:

	src: repmgr.conf.j2

	dest: /etc/repmgr.conf

- name: Restart PostgreSQL

  systemd:

	name: postgresql

	enabled: yes

    state: restarted
```

Порядок действий, описанный в роли, следующий:

1. Скачивание установщика, содержащего официальный репозиторий repmgr;
2. Запуск скачанного установщика;
3. Установка пакета repmgr для 12 версии PostgreSQL;
4. Инициализация и создание репликационного кластера;
5. Копирование и использование конфигурационного файла **repmgr.conf.j2**, который заменит стандартный конфигурационный файл **repmgr.conf**;
6. Перезапуск демона PostgreSQL.

Конфигурационный файл **repmgr.conf.j2** будет находиться по следующему пути **roles/repmgr/templates**

Содержимое файла описано ниже

```
roles/repmgr/templates/repmgr.conf.j2
node_id = {{ node_id }}
node_name = 'node{{ node_id }}'
conninfo = 'host={{ connection_host }} user=repmgr dbname=repmgr'
data_directory = '/var/lib/postgresql/{{ pg_version }}/main'
use_replication_slots = yes
reconnect_attempts = 5
reconnect_interval = 1
failover = automatic
pg_bindir = '/usr/lib/postgresql/{{ pg_version }}/bin'
promote_command = 'repmgr standby promote -f /etc/repmgr.conf'
follow_command = 'repmgr standby follow -f /etc/repmgr.conf'
log_level = INFO
log_file = '/var/log/postgresql/repmgr.log'

#monitoring_history=yes
#monitor_interval_secs=5
#log_status_interval=5
#promote_check_timeout=5
#promote_check_interval=1
#master_response_timeout=5
```

Последняя роль – это присвоение ролей нодам кластера.

Посмотреть

```
roles/repmgr/registration/main.yaml
- name: Register primary node
  become_user: postgres
  shell: repmgr primary register
  ignore_errors: yes
  when: role == "primary"

- name: Stop PostgreSQL
  systemd:
	name: postgresql
	state: stopped
  when: role == "standby"

- name: Clean up PostgreSQL data directory
  become_user: postgres
  file:
	path: /var/lib/postgresql/{{ pg_version }}/main
	force: yes
	state: absent
  when: role == "standby"

- name: Clone primary node data
  become_user: postgres
  shell: repmgr -h {{ node1_ip }} -U repmgr -d repmgr standby clone
  ignore_errors: yes
  when: role == "standby"

- name: Start PostgreSQL
  systemd:
	name: postgresql
	state: started
  when: role == "standby"

- name: Register {{ role }} node
  become_user: postgres
  shell: repmgr -h {{ node1_ip }} {{ role }} register -F
  ignore_errors: yes
  when: role != "primary"

- name: Start repmgrd
  become_user: postgres
  shell: repmgrd
  ignore_errors: yes
```

Порядок действий, описанный в роли, следующий:

1. Регистрация primary ноды (она же мастер-нода).
2. Остановка демона PostgreSQL.
3. Удаление всех данных из директории /var/lib/postgresql/12/main.
4. Регистрация stan-by ноды.
5. Запуск демона PostgreSQL.
6. Запуск демона repmrg.

## Создание и запуск playbook

Чтобы собрать все задачи воедино, необходимо создать один общий playbook, в который будут включены все задачи и файлы, что были созданы ранее. Для этого в корневой директории (в данном примере это директория с именем **postgres-cluster**) необходимо создать файл с именем **playbook.yaml** со следующим содержанием:

```
postgres-cluster/playbook.yaml
---
- hosts: all
  gather_facts: yes
  become: yes
  roles:
	- postgres_12
	- ssh
	- repmgr
	- registration
```

В параметре **roles** перечислены все роли, которые будут запущены на хостах. Обратите внимание на порядок ролей.

В итоге получится следующая структура файлов:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6331cc8f3802c511c49f7dad4db0c652870676fd%2F3bfb94f4db7352678e494f5eff24bfd9.png?alt=media)

Также в корневой директории присутствует ранее созданный **Vagrantfile**.

Когда все файлы будут созданы, можно запускать установку виртуальных машин и playbook Ansible. Для этого достаточно выполнить одну команду:

```
vagrant up
```

Начнется процесс установки (см. скриншот ниже). Сначала будут созданы 3 виртуальные машины, далее будет запущен playbook, который установит СУБД PostgreSQL, утилиту repmgr и настроит репликацию.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5b43146b1e1ae4d3a4d0068f466be778b2239893%2Fb3e29c8a6530112d06a8a7335d0f5002.png?alt=media)

Ниже показан процесс запуска ролей Ansible:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fdaeab985d88c5b8408dbfbb14264c3f72a510f1%2Fe6597ff9bd4684ac8690cf88b36feebd.png?alt=media)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-78775cc33c883131c6d5799f06c3e8b664b2cfc4%2Ffe42793fa7ee69a8b5ca7c7f53bd2f68.png?alt=media)

После того как установка будет завершена, можно подключиться к любой из 3 созданных ВМ для проверки статуса репликации. Для этого необходимо ввести команду **vagrant ssh node1**, где node1 — это имя хоста одной из ВМ:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-08413ea487df6a212778e9c870f99844e28c1f4b%2F8ee73a1927f7d9d2d742cf0a048806ea.png?alt=media)

При подключении по SSH пароль вводить не нужно, так как был настроен вход по SSH ключам.

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

```
repmgr service status
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-55bcc4739945e060d0ad7b9a4676df6fd54e2650%2F63ddc69bbc7f63d96c0bafc0b8c1a2ff.png?alt=media)

Как видно из вывода команды, у нас создался кластер PostgreSQL с 3 нодами. У каждой ноды своя роль (столбец Role).

## Итог

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


# Vitess - Scalable. Reliable. MySQL-compatible. Cloud-native. Database.

<https://vitess.io/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7978c28eaa4af31fead53af1a3da3debaba83f2d%2Fvitess.png?alt=media)

## Scalability

Vitess combines many important MySQL features with the scalability of a NoSQL database. Its built-in sharding features let you grow your database without adding sharding logic to your application.

## Performance

Vitess automatically rewrites queries that hurt database performance. It also uses caching mechanisms to mediate queries and prevent duplicate queries from simultaneously reaching your database. Performance is monitored through nightly [benchmarks](https://benchmark.vitess.io/).

## Manageability

Vitess automatically handles functions like failovers and backups. It uses a topology server to track and administer servers, letting your application be blissfully ignorant of database topology.

## Connection pooling

Vitess eliminates the high-memory overhead of MySQL connections. Vitess servers easily handle thousands of connections at once.

## Shard management

MySQL doesn't natively support sharding, but you will likely need it as your database grows. Vitess saves you from having to add sharding logic to your application. It also enables live resharding with minimal read-only downtime.

## Workflow

Vitess keeps track of all of the metadata about your cluster configuration so that the cluster view is always up-to-date and consistent for different clients.

Vitess is a [Cloud Native Computing Foundation](https://cncf.io/) graduated project


# Identity and Access Management (IDM)

[FreeIPA - Identity, Policy, Audit](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/broken-reference/README.md)

[FreeIPA as an Enterprise solution](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/broken-reference/README.md)

[Keycloak](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/broken-reference/README.md)

[Open Identity Platform](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/broken-reference/README.md)

[SSO](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/broken-reference/README.md)


# FreeIPA - Identity, Policy, Audit

<https://www.freeipa.org/page/Main_Page>

## Identity

Manage Linux users and [client hosts](https://www.freeipa.org/page/Client) in your realm from [one central location](https://www.freeipa.org/page/Directory_Server) with CLI, [Web UI](https://www.freeipa.org/page/Web_UI) or RPC access. Enable [Single Sign On](https://www.freeipa.org/page/Kerberos) authentication for all your systems, services and applications.

## Policy

Define [Kerberos](https://www.freeipa.org/page/Kerberos) authentication and authorization policies for your identities. Control services like [DNS](https://www.freeipa.org/page/DNS), SUDO, SELinux or autofs.

## Trusts

Create mutual [trust](https://www.freeipa.org/page/Trusts) with other Identity Management systems like [Microsoft Active Directory](https://www.freeipa.org/page/Active_Directory_trust_setup).

[About FreeIPA](https://www.freeipa.org/page/About) •[Roadmap](https://www.freeipa.org/page/Roadmap) • [FreeIPA Leaflet](https://www.freeipa.org/page/Leaflet) • [FreeIPA public demo](https://www.freeipa.org/page/Demo) • [Blogs/RSS](http://planet.freeipa.org/)

## Main features

* Integrated security information management solution combining Linux (Fedora), [389 Directory Server](http://directory.fedoraproject.org/), [MIT Kerberos](http://k5wiki.kerberos.org/wiki/Main_Page), NTP, [DNS](https://pagure.io/bind-dyndb-ldap), [Dogtag certificate system](http://pki.fedoraproject.org/), [SSSD](https://pagure.io/SSSD/sssd) and others.
* Built on top of well known Open Source components and standard protocols
* Strong focus on ease of management and automation of installation and configuration tasks.
* Full multi master replication for higher redundancy and scalability
* Extensible management interfaces (CLI, Web UI, XMLRPC and JSONRPC API) and Python SDK

## Releases

* [FreeIPA 4.11.0-beta](https://www.freeipa.org/release-notes/4-11-0-beta.html)
* [FreeIPA 4.10.2](https://www.freeipa.org/release-notes/4-10-2.html)
* [FreeIPA 4.9.12](https://www.freeipa.org/release-notes/4-9-12.html)

## Getting involved

Whether you’d like to contribute to discussion, to code, or simply test it out, FreeIPA needs your help!

* To contribute to the development of FreeIPA go to [Contribute](https://www.freeipa.org/page/Contribute) and subscribe to [freeipa-devel](https://lists.fedoraproject.org/archives/list/freeipa-devel@lists.fedorahosted.org/)
* To share deployment experience with FreeIPA and ask “how to” questions subscribe to [freeipa-users](https://lists.fedoraproject.org/archives/list/freeipa-users@lists.fedorahosted.org/)
* To file a bug, RFE or to see where you can help, please see <https://www.freeipa.org/page/Contribute#Reporting_bugs_or_Features>
* For security-related communication, please use <https://www.freeipa.org/page/Contribute#Security_Bugs_and_Flaws>
* Contributions are always welcome!

[Learn more](https://www.freeipa.org/page/Contribute)

## Public Demo

People eager to try the looks and feel of the most recent FreeIPA, can visit our [public FreeIPA instance](https://www.freeipa.org/page/Demo)! It is great for

* Testing changes in the most recent CLI/Web UI/API
* Testing [client](https://www.freeipa.org/page/Client) enrollment
* Testing [web applications](https://www.freeipa.org/page/Web_App_Authentication) with [LDAP](https://www.freeipa.org/page/Directory_Server) / [Kerberos](https://www.freeipa.org/page/Kerberos) authentication

Read more on the page [Demo](https://www.freeipa.org/page/Demo).


# FreeIPA as an Enterprise solution

<https://habr.com/ru/companies/sberbank/articles/677900/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dad75cb3bbb9babae066b60e5c4d24fbd30dd058%2F98ec7c38f77f1e1218096088ab63c2e2.png?alt=media)

Привет, Хабр! Меня зовут Александр Копылов. Я руководитель направления аутентификации bigdata, участник профсообщества Сбера DWH/BigData.

Сегодня предлагаю обсудить интересное решение из сферы инфобеза для высоконагруженных проектов. Огромное их количество, помимо технических возможностей и разнообразных фич, требует правильного подхода к безопасности. Одно из оптимальных решений ― FreeIPA, о нём и поговорим под катом.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-824b6c0389f63c1891677494537c8e01eb844029%2F7cc4a3c6e5c48aa87abfbde37ca9d581.png?alt=media)

## В чём вообще проблема?

На данный момент существует очень ограниченное количество полноценных решений для обеспечения полного комплекса задач безопасности, необходимого крупным компаниям. Сложность в том, что большинство таких решений выполняет только часть нужных функций, при этом у них множество ограничений. Ещё одна проблема ― неполная совместимость с различными разработками, актуальными для энтерпрайза. Кроме того, поддержка таких решений зачастую стоит неоправданно дорого.

Наиболее популярным является ряд проприетарных решений, содержащих множество различных компонентов. Их проблема в том, что они «заточены» под собственную экосистему продуктов компании-разработчика. Крайне редко какое-то из таких решений полностью совместимо с продуктами других вендоров.

Мировая практика показывает, что в сфере высоконагруженных сервисов основной операционной системой является Linux/Unix. Интеграция в подобные ОС имеет ряд сложностей из-за отличий архитектуры по сравнению с наиболее популярной проприетарной ОС. Некоторые вещи через неё невозможно настроить, например политики доступа SUDO и HBAC правила. Возможно, разработчик ОС просто не заинтересован в развитии этого направления из-за сложности разработки.

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

* Open-source как гарант отсутствия скрытых элементов.
* Возможность разработки собственного форка.
* Управление Linux ОС и полная интеграция с Linux.
* Компактный, отказоустойчивый и функциональный дистрибутив.
* Обилие различных компонентов безопасности.
* Поддержка всех возможных протоколов.
* Аналог проприетарного решения и поддержка RPC.
* Наличие как интерфейса управления, так и консольной реализации.

## А вот и решение

По совокупности показателей выбор пал на FreeIPA ― это open-source-проект, который является полным аналогом RH IDM, выполняет те же самые задачи и может работать на небольшом аппаратном ресурсе с огромным количеством транзакций.

Плюс FreeIPA в том, что с его помощью мы получаем возможность управления политиками, доступами к Linux-серверам, возможность ведения собственного LDAP-каталога учётных записей для аутентификации по протоколу Kerberos (на данный момент он один из самых защищённых), собственный DNS-сервер и удостоверяющий сервер для подписи сертификатов. Также инструмент позволяет создавать защищённые доверенные соединения с доменами наиболее популярных проприетарных ОС для объединения гетерогенных систем в большое множество.

Ниже расскажем о существенном объёме работ по «допиливанию» продукта под нужды такой крупной компании, как Сбер, то есть по обеспечению использования FreeIPA в Enterprise-инфраструктуре со всеми критично важными компонентами: мониторинг, нагрузочное тестирование и вспомогательные средства отслеживания аномальной активности.

## FreeIPA ― что за зверь?

FreeIPA или IPA ― open-source-набор компонентов для централизованного управления пользователями, их группами, хостами ― (389 LDAP), аутентификацией ― (MIT KDC) и авторизацией в Linux-системах (SSSD). Плюс это ещё и собственный сервер доменных имён (BIND), а также удостоверяющий центр (DOGTAG).

Он разработан для ОС Linux/Unix и сейчас успешно развивается.

FreeIPA ― upstream, который представляет собой community-версию в основном для тестирования и отладки. Тем не менее он способен работать на проектах без требований High Availability и SLA. Серверная реализация может разворачиваться на CentOS, Debian/Ubuntu и некоторых других Linux-дистрибутивах, на которые портирован продукт.

IPA ― клиент-серверное решение, требующее наличия выделенных Linux-серверов с развёрнутым ПО для работы с субъектами безопасности, которые хранятся в реплицируемом службе каталогов LDAP.

Соответственно, любые потребители на любой операционной системе (которые называются IPA-клиентами) могут беспрепятственно взаимодействовать с серверами, получая данные о пользователях, политиках доступа, а также любую другую необходимую информацию для обеспечения безопасной работы.

Полная поддержка разных функциональных модулей безопасности присутствует только в CentOS. В частности, управление доступами к службам внутри Linux или Host-Based Access Control, подсистема OTP, sudo-привилегии.

FreeIPA можно назвать главным аналогом проприетарного решения RH IDM. Несмотря на наличие технической возможности интеграции с Linux-клиентами, обеспечение политик доступа из-за отличий ОС невозможно и сводится к проприетарному стороннему ПО, представляющему скорее небольшую надстройку, чем родственный Linux интерфейс.

IPA разработана исключительно под Linux-системы и поддерживает полностью все необходимые ОС возможности. В то же время, как было сказано выше, можно подключать и клиентов других систем, но с ограничениями.

Благодаря гибкости, универсальности, функциональности и эксклюзивности для Linux данный инструмент был включён в дистрибутив ОС Astra Linux. Дополнив внутренние технологии переработанным сервером FreeIPA, компании удалось получить надёжное и безопасное решение отечественной операционной системы.

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

Выбор FreeIPA с точки зрения коммерческой нагрузки является наиболее оптимальным и подходящим большинству энтерпрайз-инфраструктур.

Стоит отметить, что далеко не всегда потребители используют все возможности FreeIPA. Чаще всего работают с такими возможностями: аутентификация пользователей и служб, подпись сертификатов для серверов и авторизация (PAM, LDAP и т. п.)

В связи с развитием отрасли больших данных и сопутствующих технологий неоднократно поднимался вопрос о безопасности работы с Big Data. Сообщество разработчиков, которое подарило миру Hadoop-экосистему, остановилось на наиболее надёжном с точки зрения защиты протоколе ― Kerberos. Таким образом определена поддержка проприетарного решения и FreeIPA.

**Почему Kerberos?**

1. Это действительно очень надёжное, испытанное десятками лет решение, которое просто работает.
2. Версии протокола постоянно модернизируются, вносятся новые криптографические алгоритмы и тестируются все кейсы, которые появляются у желающих скомпрометировать хранилища.
3. Kerberos ― не самое простое в имплементации решение. Но оно является гибким и оптимизированным под высоконагруженные системы, что при наличии других инструментов делает кластеры практически неуязвимыми.
4. Kerberos поддерживается в проприетарных решениях.
5. Для обеспечения массовости внедрения для любого языка программирования и в любой open-source- или коммерческий продукт у Kerberos существует общепринятый интерфейс ― GSSAPI.

Помимо Kerberos-аутентификации, IPA-серверы могут управлять жизненным циклом сертификатов: выпускать их автоматизированным и прозрачным для серверов-потребителей способом, но при этом максимально безопасно. Многофункциональность IPA позволила быстро занять нишу популярных BigData-решений.

Но есть и определённый нюанс: из-за особенности работы встроенного модуля аутентификации Java, который первоначально разрабатывался для экосистемы вендорских продуктов и не мог учитывать изменения RFC, он не совместим с MIT Kerberos для большинства задач. А разработчики средних и крупных корпораций в силу незнания и/или непонимания всех стандартов оставляют «коробочный» код, не заменяя на GSSAPI-модуль. К сожалению, это коснулось и ряда крупных продуктов BigData.

К слову, приложения, написанные на языках C++, Python, Go, используют «под капотом» родную библиотеку MIT, поэтому указанная проблема для них неактуальна.

## Наша реализация

В Сбере пошли чуть дальше и разработали свой дистрибутив Hadoop, который поддерживает все стандарты и обеспечивает отказоустойчивую и быструю работу с большими данными. Производительность всей системы выросла в 4 раза.

Удалось полноценно интегрировать SDP и с плагинами IPA, многократно упростив процесс разворачивания огромных кластеров для аналитиков и разработчиков.

Изменения полностью легитимные и достаточно подробно описаны. Всё это позволило оптимизировать и сторонние системы, написанные на Java, так как проблема общая.

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

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

Для анализа нарушителей мы разработали отдельный инструмент поиска аномалий и сервис статистики запросов, по которым видна общая карта движения принципалов ― субъектов Kerberos, а также частота их взаимодействия.

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

Увеличение количества IPA-серверов в таком случае является неэффективным, так как это попытка устранить симптомы, а не причину. И вовсе не решает проблему влияния со стороны нарушителей.

Использование двух подходов поможет разделить критичность задач:

1. Приведение к требованиям всех автоматизированных систем без исключения и предоставление экспертизы.
2. Обеспечение бизнес-кластеров собственными керберизованными структурами.

Первый пункт удалось решить Java-методом с полной поддержкой стандарта протокола Kerberos.

Второй пункт базируется на экспериментальных доверительных отношениях между разными областями Kerberos (лесами ― в терминологии Directory Server). Каждый блок по приоритету живёт в собственном ограниченном пространстве, но при этом имеет возможность обмениваться информацией по безопасному каналу с соседями, если данная необходимость присутствует.

## Нагрузочное тестирование + мониторинг Prometheus + модель аномалий

Стоит отметить, что в рамках Enterprise-решения FreeIPA и в community не проводилось нагрузочное тестирование кластера в условиях горизонтального и вертикального масштабирования.

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

В первую очередь необходимо было определить, какую нагрузку эмулировать для максимальной эмуляции реальных условий. Как основное приложение для генерации нагрузки был выбран Jmeter. Дополнительно был проведён анализ необходимых действий и служб, которые будут максимально нагружаться. Сложность заключалась в том, что у IPA 3 наиболее активных сервиса, которые находятся под постоянной нагрузкой ― KDC, LDAP и HTTPD. Получить пороговые значения требуется как под точечной нагрузкой на сервис, так и при фоновой инфраструктурной и прикладной нагрузках. Тем самым был создан план нагрузки, где идут постоянные обращения к HTTPD, эмулирующие работу с web-администратора, чтение LDAP со случайным фильтром, эмулирующие запросы на поиск объекта LDAP при аутентификации, и стандартные запросы Kerberos. Важно было также учесть влияние операций модификации и удаления сущностей LDAP на весь кластер.

Впоследствии были определены 3 основных сценария тестирования:

1. Операции активного чтения с предварительной аутентификацией.
2. Операции активной записи/модификации с предварительной аутентификацией.
3. Операции удаления с предварительной аутентификацией.

Для первичного тестирования был выбран кластер FreeIPA в составе 3 инстансов, при вертикальном масштабировании ресурсы были увеличены вдвое, а для горизонтального был собран кластер из 6 инстансов. Самым сложным оказалось обеспечить активный мониторинг FreeIPA так, чтобы он мог забирать данные и агрегировать их быстрее, чем Zabbix.

Проанализировав несколько систем и способов мониторинга, мы выбрали стек Prometheus+Grafana. Здесь для мониторинга системы используется расширенный набор метрик с помощью node exporter и самописный LDAP exporter для мониторинга LDAP и активности прикладного программного обеспечения. Дополнительно были отрисованы 9 дашбордов в графане для проведения дополнительного анализа результатов в процессе выполнения нагрузочного тестирования.

По результатам тестирования было выявлено, что наиболее успешный способ масштабирования кластера FreeIPA ― вертикальный. Дело в том, что из-за расширения количества инстансов в кластере в случае изменений в структуре LDAP репликация проходит крайне долго, и горизонтальное масштабирование становится неэффективным. Дополнительно к этому на каждую структуру и топологию были рассчитаны пороговые значения по запросам аутентификации и взаимодействию с LDAP и HTTPD.

В процессе развития нагрузочного тестирования и систем мониторинга на базе prometheus был доработан экспортёр и дополнительные дашборды мониторинга для оперативного обнаружения дефектов, связанных с повышением нагрузки на серверы IDM. Дашборды и экспортёр после активных тестов во всех средах были выведены в промышленную эксплуатацию и переданы на сопровождение. Такой механизм мониторинга показал себя как более быстрый и детализированный по сравнению с Zabbix.

Дополнительно к средствам мониторинга мы разработали модель аномалий событий FreeIPA для отслеживания тех событий, которые не может отследить ни одна система мониторинга. На основе данных логов Kerberos и Access модель отслеживает все аномальные проявления, вызванные нагрузкой компоненты FreeIPA. Модель читает весь набор логов за период 15 минут и предоставляет ответ в формате json с результатами всех действий внутри логов Kerberos и Access за этот период.

В результатах на основе Kerberos-логов учитывается количество запросов к kdc, с указанием upn/spn. Так, указывается, кто их вызывает и какое влияние они имеют на каждый сервер FreeIPA, какой динамический и общий рост обнаружен.

В результатах на основе Access-логов учитывается весь объём операций за указанный период с LDAP, их тип, количество и влияние на каталог, среднее время выполнения запроса к LDAP, мера аномальности, которая высчитывается по совокупности всех этих данных.

В конечном счёте FreeIPA стала полноценным Enterprise-решением для Сбера с огромным каталогом пользовательских, хостовых и сервисных клиентов. С имеющимися в составе полноценной обвязкой мониторинга и наборами специнструментов обслуживания и сопровождения. Отметим также, что, как и у любого другого продукта, у FreeIPA есть «точки роста», но о них мы расскажем в следующих статьях.

На этом всё. Традиционно ― если у вас есть вопросы, добавляйте их в комментарии, а мы ответим. Если в вашей компании есть собственные кейсы, связанные с описанным в статье решением, расскажите о них, пожалуйста.


# Keycloak

<https://www.keycloak.org/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-27fba53778994875e50fcafa3e0af5601df201ed%2Fkeycloak_logo_200px.svg?alt=media)

Add authentication to applications and secure services with minimum effort.

No need to deal with storing users or authenticating users.

Keycloak provides user federation, strong authentication, user management, fine-grained authorization, and more.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-345e2384c221b0a60d314a7ad3bcf62d0a0c943b%2Fkeycloak_icon_512px.svg?alt=media)

## Single-Sign On

Users authenticate with Keycloak rather than individual applications. This means that your applications don't have to deal with login forms, authenticating users, and storing users. Once logged-in to Keycloak, users don't have to login again to access a different application.

This also applies to logout. Keycloak provides single-sign out, which means users only have to logout once to be logged-out of all applications that use Keycloak.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f599160601e1d0c6dd023db03bfaa515bd0f1217%2Fscreen-login.png?alt=media)

Screenshot showing a user's login screen as presented by Keycloak

## Identity Brokering and Social Login

Enabling login with social networks is easy to add through the admin console. It's just a matter of selecting the social network you want to add. No code or changes to your application is required.

Keycloak can also authenticate users with existing OpenID Connect or SAML 2.0 Identity Providers. Again, this is just a matter of configuring the Identity Provider through the admin console.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1a6a56f9b4d8845dd9a5a1f26f2e9d3ea5cb945d%2Fdia-identity-brokering.png?alt=media)

Diagram illustrating brokering

## User Federation

Keycloak has built-in support to connect to existing LDAP or Active Directory servers. You can also implement your own provider if you have users in other stores, such as a relational database.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dc9482f3d96da385ee4696fc4407fe2c2ad62272%2Fdia-user-fed.png?alt=media)

Diagram illustrating user federation

## Admin Console

Through the admin console administrators can centrally manage all aspects of the Keycloak server.

They can enable and disable various features. They can configure identity brokering and user federation.

They can create and manage applications and services, and define fine-grained authorization policies.

They can also manage users, including permissions and sessions.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2a4773b3661a22efa7bcca7da9f6933526c5b3ca%2Fscreen-admin.png?alt=media)

Screenshot of the admin console

## Account Management Console

Through the account management console users can manage their own accounts. They can update the profile, change passwords, and setup two-factor authentication.

Users can also manage sessions as well as view history for the account.

If you've enabled social login or identity brokering users can also link their accounts with additional providers to allow them to authenticate to the same account with different identity providers.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e58349c04e40c0d6968843a49d706ffbb4c4df5c%2Fscreen-account.png?alt=media)

Screenshot of the account management console

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-054e178ab6a519732b7b07e57616b56ba71875a5%2Fdia-protocols.png?alt=media)

Logos of OpenID certification, SAML and OAuth 2.0

## Authorization Services

If role based authorization doesn't cover your needs, Keycloak provides fine-grained authorization services as well. This allows you to manage permissions for all your services from the Keycloak admin console and gives you the power to define exactly the policies you need.

[Keycloak HA cluster](/readme/architect/identity-and-access-management-idm/keycloak/keycloak-ha-cluster)


# Keycloak HA cluster

<https://habr.com/ru/companies/tuturu/articles/766284/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-64d159bf732621a52feed9790ac1b0dc64cdfbb7%2F8713a6f2bc7464b7d411493b6e01519f.png?alt=media)

Разворачивая у нас в tutu Keycloak, мы столкнулись с необходимостью создания отказоустойчивого кластера. И если с БД всё более-менее понятно, то вот реализовать корректный обмен кешами между Keycloak оказалось довольно непростой для настройки задачей.

Мы упёрлись в то, что в документации Keycloak описано, как создать кластер, используя UDP-мультикаст. И это работает, если у вас все ноды будут находиться в пределах одного сегмента сети (например, ЦОДа). Если с этим сегментом что-то случится, то мы лишимся Keycloak. Нас это не устраивало.

Необходимо сделать так, чтобы ноды приложения были географически распределены между ЦОДами, находясь в разных сегментах сети.

В этом случае в документации Keycloak довольно неочевидно предлагается создать свой собственный кастомный JGroups транспортный стек, чтобы указать все необходимые вам параметры.

Бонусом приложу shell-скрипт, написанный для Consul, который предназначен для снятия анонсов путём выключения bird и попытки восстановления приложения.

### Особенности

Нами была выбрана инсталляция без контейнеризации, приложение завёрнуто в systemd-сервис.

Keycloak может принять конфигурацию из четырёх разных источников:

* CLI: kc.sh --key=value.
* Переменная окружения: KC\_KEY=value.
* Файл конфигурации: key=value.
* Файл Java KeyStore: kc.key=value.

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

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

### Дано

* Нода keycloak1.
* Keycloak версии 20, завёрнутая в systemd-сервис.
* Интерфейс eth0 с локальным IP-адресом виртуалки. Каждой ноде этот адрес должен быть доступен.
* Интерфейс eth1, в котором через bgp анонсируется anycast IP-адрес.
* Отказоустойчивая база данных за пределами Keycloak, к которой мы подключаем приложение.

### Задача

Сделать Keycloak отказоустойчивым и геораспределённым.

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

Для этого надо создать custom transport stack.

**TCPPING**

Остановим Keycloak.

Скопируем файл **conf/cache-ispn.xml** в новый файл **conf/custom-cache-ispn.xml.**

Добавим в секцию infinispan следующее:

```
  <jgroups>
        <stack name="add_tcpping" extends="tcp">
            <TCPPING initial_hosts="<eth0_ip_keycloak1>[7800],<eth0_ip_keyclaok2>[7800],<eth0_ip_keycloak3>[7800]"
                     port_range="0"
                     stack.combine="REPLACE"
                     stack.position="MPING"
            />
        </stack>
    </jgroups>
    <cache-container name="keycloak">
        <transport lock-timeout="60000" stack="add_tcpping"/>
```

stack name ― имя стека, который мы потом используем в секции transport. Можно указать что угодно. Имя стека будет писаться в логах.

initial\_hosts ― перечисляем IP-адреса с портами всех наших Keycloak-нод.

port\_range ― TCPPING будет пытаться связать с каждой из нод кластера, начиная с указанного порта + port\_range. В нашем случае будет использоваться только порт 7800.

stack.combine ― способ изменения параметров протокола. REPLACE заменяет протокол.

stack.position ― протокол, который мы меняем.

Теперь надо в конфигурации задать с помощью переменной **cache-config-file** наш .xml-файл, а также переменной **http-host** указать anycast-адрес (cache=ispn ― это дефолтное значение):

```
cache=ispn
cache-config-file=cache-ispn-tcpping.xml

http-host=<anycast_eth1_ip>
```

Из-за того, что мы используем anycast-адрес, надо указать IP-адрес хоста, по которому infinispan будет слушать порт 7800. Для этого при запуске сервера нам надо явно задать основной IP-адрес ноды:

```
bin/kc.sh start -Djgroups.bind.address=<eth0_ip>
```

После этого мы должны увидеть в логах, что JGroups запускается со стеком add\_tcpping:

```
2023-04-21 10:40:54,586 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN` with stack `add_tcpping`
```

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

```
2023-04-21 10:41:02,197 INFO  [org.infinispan.CLUSTER] (jgroups-12,keycloak1-57393) ISPN100000: Node keycloak2-60977 joined the cluster
2023-04-21 10:41:02,643 INFO  [org.infinispan.CLUSTER] (jgroups-5,keycloak1-57393) [Context=authenticationSessions] ISPN100002: Starting rebalance with members [keycloak1-57393, keycloak2-60977], phase READ_OLD_WRITE_ALL, topology id 7
2023-04-21 10:41:06,963 INFO  [org.infinispan.CLUSTER] (jgroups-12,keycloak1-57393) ISPN100000: Node keycloak3-7710 joined the cluster
2023-04-21 10:41:07,242 INFO  [org.infinispan.CLUSTER] (jgroups-12,keycloak1-57393) [Context=authenticationSessions] ISPN100002: Starting rebalance with members [keycloak1-57393, keycloak2-60977, keycloak3-7710], phase READ_OLD_WRITE_ALL, topology id 11
```

Готово!

### Объяснение

Понять, что мы сейчас настроили в .xml-файле, нам помог дефолтный конфиг стека TCP, находящегося по пути:

```
lib/lib/main/org.infinispan.infinispan-core-<version>.jar/default-configs/default-jgroups-tcp.xml
```

Там мы можем увидеть, что в качестве протокола обнаружения используется MPING. В **conf/custom-cache-ispn.xml** c помощью stack.position мы выбираем MPING, а с помощью stack.combine заменяем его на TCPPING.

### HashiCorp Consul

Вы настроили anycast (у нас анонсируется адрес с помощью bird), кластеризацию, но вам надо как-то снимать анонсы, если с приложением что-то случится. Вариантов много, я рассмотрю используемый нами.

В этом туториале я не буду разбирать, как настраивать консул, рассмотрим лишь shell-скрипт, который запускается с его помощью раз в 15 секунд.

Keycloak имеет встроенный healthcheck, на его основе и построим проверку.

Чтобы включить его, надо в конфигурации задать переменную:

```
health-enabled=true
```

После этого у приложения становятся доступны следующие эндпоинты:

```
/health
/health/live
/health/ready
```

Будем отслеживать последний эндпоинт, так как там есть проверка подключения к базе данных. Её тоже будем отслеживать:

```
function keycloak_healthcheck {
  app_status=$(curl -sk https://127.0.0.1/health/ready | python -c "import sys, json; print(json.load(sys.stdin)['status'])" 2>/dev/null)
  db_status=$(curl -sk https://127.0.0.1/health/ready | python -c "import sys, json; print(json.load(sys.stdin)['checks'][0]['status'])" 2>/dev/null)

  if [ "$app_status" != "UP" ] || [ "$db_status" != "UP" ]
    then
      healthcheck=1
    else
      healthcheck=0
  fi
}
```

Также попытаемся один раз восстановить работу Keycloak ребилдом приложения:

```
function keycloak_recover {
  echo $(date +%s) > $tmp_recover
  cmd="systemctl stop keycloak && <keycloak_dir>/bin/kc.sh build >/dev/null && systemctl start keycloak"
  timeout 50s bash -c "$cmd" & disown
}
```

Запуск ребилда в фоне позволяет нам запускать скрипт сколько угодно часто, чтобы как можно быстрее реагировать на упавшее приложение и выключать bird.service.

Ну и для управления всем этим безобразием создаём tmp-файл для отслеживания времени запуска восстановления:

```
tmp_recover="/tmp/keycloak_recover_try"
touch $tmp_recover
recover_try=$(cat $tmp_recover)
```

Подробная настройка консула выходит за рамки данного туторила.

Собираем это всё вместе в скрипт:

Целиком скрипт

```
#!/bin/bash

keyclaok_dir="<keycloak_dir>"
tmp_recover="/tmp/keycloak_recover_try"
touch $tmp_recover

function disable_bird {
  pgrep bird > /dev/null 2>&1
  bird_status=$?
  if [[ "$bird_status"  == "1" ]]
    then
      echo "Bird disabled"
    else
      /bin/systemctl stop bird
      echo "Bird disabled"
  fi
}

function enable_bird {
  pgrep bird > /dev/null 2>&1
  bird_status=$?
  if [[ "$bird_status"  == "1" ]]
    then
      echo "Bird enabled"
    then
      /bin/systemctl start bird
      echo "Bird enabled"
  fi
}

function keycloak_healthcheck {
  app_status=$(curl -sk https://127.0.0.1/health/ready | python -c "import sys, json; print(json.load(sys.stdin)['status'])" 2>/dev/null)
  db_status=$(curl -sk https://127.0.0.1/health/ready | python -c "import sys, json; print(json.load(sys.stdin)['checks'][0]['status'])" 2>/dev/null)

  if [ "$app_status" != "UP" ] || [ "$db_status" != "UP" ]
    then
      healthcheck=1
    else
      healthcheck=0
  fi
}

# Попытка восстановления, запущенная в background с таймаутом
function keycloak_recover {
  echo $(date +%s) > $tmp_recover
  cmd="systemctl stop keycloak && <keycloak_dir>/bin/kc.sh build >/dev/null && systemctl start keycloak"
  timeout 50s bash -c "$cmd" & disown
}

keycloak_healthcheck
# Когда запускалось восстановление
recover_try=$(cat $tmp_recover)
# Если восстановление запускалось, то подсчитываем сколько секунд с тех пор прошло
if [[ ! -z "$recover_try" ]]
  then
    let "try_s = $(date +%s) - $recover_try"
fi

if [[ "$healthcheck" == 0 ]]
  then
    enable_bird
    echo "Keycloak is ok"
    echo "" > $tmp_recover
    exit 0

elif [[ "$healthcheck" != 0 ]]
  then
    disable_bird

    if [[ -z "$recover_try" ]]
      then
        keycloak_recover
        exit 2

    elif [[ ! -z "$recover_try" && "$try_s" -ge 60 ]]
      then

        if [[ "$app_status" != "UP" ]]
          then
            echo "Keycloak service is down, bird disabled"
        elif [[ "$db_status" != "UP" ]]
          then
            echo "Keycloak database problem, bird disabled"
        fi
        exit 2
    fi
fi
```

И настраиваем конфиг консула:

```
{
  "check": {
  "id": "Keycloak",
  "name": "Keycloak healthcheck",
  "args": ["/opt/consul/check/script-check-keycloak.sh"],
  "interval": "15s",
  "timeout": "15s"
  }
}
```

### Заключение

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

Мы живём в такой конфигурации уже более полугода, и за это время она ни разу не давала сбой. За исключением не зависящих от кластера ситуаций.

### Источники информации

<https://dantheengineer.com/keycloak-on-distributed-sql-cockroach-part-2-2/>

<https://infinispan.org/docs/stable/titles/server/server.html>

<https://www.keycloak.org/server/caching>


# Open Identity Platform

<https://www.openidentityplatform.org/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-62d5e7a84a9896c588c9632af8ed5c551975f7bc%2Foip-star.png?alt=media)

Open Access Management (OpenAM) is an access management solution that includes Authentication, SSO, Authorization, Federation, Entitlements, and Web Services Security.

OpenDJ is an LDAPv3 compliant directory service, which has been developed for the Java platform and provides a high performance, highly available, and secure store for identities, that are managed by your organization. Its easy installation process, combined with the power of the Java platform makes OpenDJ the simplest, fastest directory to deploy and manage.

Open Identity Gateway (OpenIG) is a high-performance reverse proxy server with specialized session management and credential replay functionality.

Open Identity Management (OpenIDM) is an open standards-based Identity Management, Provisioning, and Compliance solution. Provisioning users, devices, and things is a repetitive and potentially time-consuming task that has a significant impact on security and user access.

Open Identity Connector Framework (OpenICF) provides interoperability between identity, compliance and risk management solutions. An OpenICF Connector enables provisioning software, such as OpenIDM, to manage the identities that are maintained by a specific identity provider.


# SSO

[Keycloak](/readme/architect/identity-and-access-management-idm/keycloak)

[Keycloak for Java app](/readme/architect/identity-and-access-management-idm/sso/keycloak-for-java-app)

[OpenAM](/readme/architect/identity-and-access-management-idm/sso/openam)

[OpenIG](/readme/architect/identity-and-access-management-idm/sso/openig)


# Keycloak for Java app

## Keycloak for Java app

<https://habr.com/ru/articles/716232/>

### Содержание

1. Содержание
2. О чем речь?
3. Подготовка стенда для разработки
4. Настройка realm
5. Подготовка приложения к внедрению
6. Настройка приложения для взаимодействия с Keycloak
7. Реализация Attribute based access control в Keycloak

### О чем речь?

Это первая часть серии статей о переходе на Keycloak в качестве SSO в условиях кровавого enterprise.

Целью данной серии постов является демонстрация одного из возможных вариантов внедрения Keycloak в большой enterprise проект в качестве SSO. Еще в ней будут приведены мои размышления на этот счет, а также описаны сложности, с которыми пришлось столкнуться и решения, которые при этом были найдены.

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

Проект написан на spring-framework и как следствие использует компоненты spring-security для реализации системы аутентификации / авторизации пользователей.

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

* Basic (по логину паролю)
* По логину паролю с ассеss и refresh-token
* OAUTH
* SAML
* LDAP
* JWT (где наш бэкенд выступает просто в качестве resource-server, а аутентификация происходит на стороне заказчика)

На сегодняшний день реализованные нами компоненты аутентификации и авторизации уже очень сильно устарели и техническое руководство приняло, на мой взгляд, очень верное решение избавиться от них полностью и внедрить SSO (Single sign-on). Выбор пал на Keycloak, как на явного фаворита в среде систем данного рода с открытым исходным кодом. (В данной последующих постах не будут подниматься вопросы delivery нашего продукта заказчику вкупе с Keycloak и специфики комбинации проприетарного и open-source кода).

Когда я только начал заниматься этой задачей, я весьма оптимистично оценил ее в 100 чч по трудозатратам, потому что буквально недавно внедрил Keycloak на другом проекте за неделю, однако я не учел, что мой нынешний проект в десятки раз больше и запутаннее чем предыдущий, который только-только стартовал.

### 1. Подготовка стенда для разработки

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

Ниже я приведу compose файл, который у меня получился и дам ряд комментариев:

```
version: "3.9"
services:
  keycloak-postgres:
    image: library/postgres:${KC_POSTGRES_IMAGE_TAG:-14}
    container_name: ${POSTGRES_CONTAINER_NAME:-postgres}
    restart: on-failure
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    healthcheck:
      test: pg_isready -d postgres
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
    ports:
      - ${KC_POSTGRES_PORT_MAPPING:-5435}:5432
    deploy:
      resources:
        limits:
          memory: 256M

  keycloak:
    image: quay.io/keycloak/keycloak:20.0.2
    container_name: keycloak
    command:
      - start --auto-build --db postgres --hostname-strict-https false --hostname-strict false --proxy edge --http-enabled true --import-realm --spi-user-profile-legacy-user-profile-read-only-attributes *_RES_ACCESS_MODE
    environment:
      KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/postgres
      KC_DB_USERNAME: postgres
      KC_DB_PASSWORD: postgres
      KC_DB_SCHEMA: public
      KC_FEATURES: preview
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - 8282:8080
    depends_on:
      keycloak-postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://0.0.0.0:8080/realms/master"]
      start_period: 10s
      interval: 30s
      retries: 3
      timeout: 5s
```

Данный compose-файл позволяет запустить keycloak в продуктивном режиме с реляционной базой данных.

Так как SSO это как правило отдельный микросервис, то под него имеет смысл выделять собственную БД.

Данный файл содержит ряд place-holder’ов для более гибкой конфигурации через .env файлы (в дальнейшем нам это пригодится для CI).

Пройдемся подробнее по конфигурации сервиса keycloak:

Все начинается с команды запуска:

```
start --auto-build --db postgres --hostname-strict-https false --hostname-strict false --proxy edge --http-enabled true --import-realm
```

Если мы посмотрим DockerFile \[<https://www.keycloak.org/server/containers> ] из которого собирается наш образ Keycloak, то увидим, что entrypoint там стоит ENTRYPOINT \["/opt/keycloak/bin/kc.sh"] (скрипт запуска Keycloak в standalone режиме). Команда start запускает приложение в production режиме.

Теперь пройдемся по опциям:

* **auto-build** – собирает наш экземпляр со всеми зависимостями и кастомизациями
* **db** – указывает, какую БД мы будем использовать, чтобы выбрать соответствующий драйвер
* **hostname-strict-https** разрешаем/запрещаем фронту и бэку keycloak общаться по HTTP
* **proxy** устанавливает режим reverse-proxy
* **hostname-strict** вкл/выкл динамического имени хоста из заголовков запросов
* **http-enabled** разрешаем взаимодействие по http
* **import-realm** включаем импортирование realms из файлов конфигураций

После запуска данной конфигурации командой `docker-compose up -d` у вас будет готовый стенд Keycloak, который можно использовать для разработки и прогона тестов, Главная страница будет доступна по адресу [http://localhost:8282](http://localhost:8282/).

Войдя по admin:admin мы окажемся на странице master realm:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2c4ea6497855fcc8a8e5dcf800a3f213c28103ec%2Febf2934026c0a9b1d497c7d45a3d9e59.png?alt=media)

### 2. Настройка realm

Темой данного раздела является базовая настройка realm Keycloak для разработки.

В прошлом разделе мы создали docker-compose файл, который позволяет нам развернуть стенд Keycloak, теперь нам необходимо его настроить, для этого:

Создадим собственный realm:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c0a9a10a25d482b8c89e51e1efba0d5178557da9%2Fdae71044655faa551e38832518fec24c.png?alt=media)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1aef6fe3197ea41a9579f5b6760e85b1edac0a03%2Fd83fb2af990c3fae15b0d3dc95b4447e.png?alt=media)

Теперь в рамках созданного realm создадим клиента:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-53077471ab1ee5d70b701f94d367ee155fdf390d%2Fb8bfd068a64267396dd843b8cfcdac5b.png?alt=media)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5952672c27d3ef5e1151f2fc4a15f40854ad4fb4%2Fdb7169959e1182823d26e6dcf8c9115b.png?alt=media)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d371f2bc8c2fb98103805fc648348d0b36bafeab%2Fa70532178363667e65633c89debfa1e4.png?alt=media)

Теперь у нас есть клиент, с возможностью логиниться из под него в Keycloak, давайте попробуем получить access-token с помощью сurl, для этого нам понадобится client\_secret, который был сгенерирован в момент создания клиента, найти его можно в разделе credentials вашего клиента, тут важно подчеркнуть, что в продуктивном контуре этот секрет всегда должен генерироваться keycloak, однако в тестовой среде и среде для разработки допускается его определять заранее, ниже будет описано как именно:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-500c4db5ba992279c849f8ac79a35b2b516f3554%2F78c934a7b2da3242f393210c670ca9ea.png?alt=media)

```
curl --location --request POST 'http://localhost:8282/realms/demo/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=backend' \
--data-urlencode 'client_secret=v2iL3uK1uZxQqpYfw634SOEdAvT0UFSb' \
--data-urlencode 'grant_type=client_credentials'
```

```
{
  "access_token":"eyJhbGciOiJSUzI1NiIsInR5c...",
  "expires_in":300,
  "refresh_expires_in":0,
  "token_type":"Bearer",
  "not-before-policy":0,
  "scope":"profile email"
}

```

Очевидно, что производить ручную настройку клиента каждый раз неудобно и долго, к тому же в этом примере мы почти ничего не настраивали, в следующих постах мы настроим ресурсы, политики и прочее. Для того чтобы иметь возможность содержать эти настройки в коде, и автоматизировать развертывание стенда, keycloak предоставляет механизмы импорта/экспорта нашего realm.

Есть один интересный баг, который пока никто не исправил - при создании клиента создается политика по умолчанию, которая имеет тип JS (подробнее о политиках и разрешениях можно прочитать здесь <https://www.keycloak.org/docs/latest/authorization_services/index.html>). В более ранних версиях сложные кастомные политики можно было загружать прямо через интерфейс в виде JS кода, позже эту возможность убрали (потому что не секьюрно). Однако эта политика по умолчанию осталась, и при экспорте, она тоже выгрузится, но когда вы захотите потом импортировать ваш realm, вы получите ошибку, так как в актуальной версии JS политики можно загружать только через специальный JAR файл, однако об этом позже. Сейчас, чтобы магия случилась нам нужно удалить эту политику и связанное с ней разрешение:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-118a131fc984fc917ec5891d6edc495a63316c08%2F0ee75b0bb4113a4583b1568dba632c66.png?alt=media)

Для того чтобы экспортировать realm, переходим на страницу настроек и выбираем соответствующее действие:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7a7e4775807133a878a397e0aca6d8ee4d6aa3f6%2F9842870204505623645fd869b3819f25.png?alt=media)

В итоге мы получим большой JSON файл, содержащий конфигурацию нашего realm.

Теперь мы можем усовершенствовать наш docker‑compose файл из прошлого поста, мы прокинем наш файл внутрь контейнера keycloak, чтобы наш realm импортировался автоматически при старте приложения:

```
version: "3.9"
services:
  keycloak-postgres:
    image: library/postgres:${KC_POSTGRES_IMAGE_TAG:-14}
    container_name: ${POSTGRES_CONTAINER_NAME:-postgres}
    restart: on-failure
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    healthcheck:
      test: pg_isready -d postgres
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
    ports:
      - ${KC_POSTGRES_PORT_MAPPING:-5435}:5432
    deploy:
      resources:
        limits:
          memory: 256M

  keycloak:
    image: quay.io/keycloak/keycloak:20.0.2
    container_name: keycloak
    command:
      - start --auto-build --db postgres --hostname-strict-https false --hostname-strict false --proxy edge --http-enabled true --import-realm --spi-user-profile-legacy-user-profile-read-only-attributes *_RES_ACCESS_MODE
    environment:
      KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/postgres
      KC_DB_USERNAME: postgres
      KC_DB_PASSWORD: postgres
      KC_DB_SCHEMA: public
      KC_FEATURES: preview
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    volumes:
      - type: bind
        source: ./src/main/resources/keycloak/import/realm-export.json
        target: /opt/keycloak/data/import/realm-export.json
        read_only: true
    ports:
      - 8282:8080
    depends_on:
      keycloak-postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://0.0.0.0:8080/realms/master"]
      start_period: 10s
      interval: 30s
      retries: 3
      timeout: 5s
```

Теперь при запуске наших контейнеров, в Keycloak сразу будет realm demo и client backend.

Для удобства локальной разработки и тестирования (ЭТО ВАЖНО, В ПРОДЕ НИКОГДА ТАК НЕ ДЕЛАЙТЕ !!!), мы можем прописать secret нашего клиента в файл импорта, и тогда он не будет генерироваться заново каждый раз. Для этого в нашем JSON файле находим следующее поле: clients\[YOUR CLIENT INDEX.secret и пишем туда что хотим.

### 3. Подготовка приложения к внедрению

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

Когда мы используем Spring очень многое настраивается за нас, в частности, простое добавление зависимости spring-security уже тянет за собой пачку авто-конфигураций, которые добавляют бин SecurityFilterChain, и много других разностей, которые и отвечают за то, чтобы наше приложение было защищено от неправомерного доступа.

Различные конфигурации иногда вполне могут сосуществовать совместно, а иногда нет. В моем случае я столкнулся с тем, что один из фильтров, которые тянет конфигурация, использующая устаревшую зависимость, конфликтует с фильтрами, которые нужны мне для внедрения Keycloak.

В таком случае нам на помощь приходят механизмы Spring из группы аннотаций [@ConditionalOn](https://habr.com/users/ConditionalOn)..., которые выключают те или иные конфигурации в зависимости от различных условий.

Например, мы внедряем SSO, следовательно если мы ходим в приложение через SSO остальные варианты входа нам не нужны, но и выпилить их из проекта мы тоже не можем, потому что их используют другие заказчики, что нам остается? - правильно - исключить их из контекста приложения завязавшись, например, на настройку приложения.

`keykloak.enabled=true`

Тогда старая конфигурация, которая нам мешает, примет следующий вид:

```
@Configuration
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "false")
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// Some cofiguration code
}

```

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

Если ваш проект достаточно большой, то вам придется потратить довольно много времени на то, чтобы найти и "выключить" все, что вам мешает. Тут могу посоветовать, вооружившись отладчиком, при старте приложения смотреть содержание бина SecurityFilterChain, чтобы удостовериться, что оно соответствует вашим потребностям.

### 4. Настройка приложения для взаимодействия с Keycloak

Темой данного раздела является самая простая часть внедрения Keycloak, а именно настройка spring-security для проверки токенов через Keycloak.

Небольшое пояснение, в выбранной архитектуре схема взаимодействия фронта и бэка выглядит следующим образом:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-4538b76978db29d9940d66dc8fe1a396c6005568%2F283ed2934acc5cda114409931e259041.png?alt=media)

Поэтому на стороне бэкенда от нас требуется только валидировать JWT-токены, которые присылаются в запросах.

Для того чтобы подружить наше приложение с Keycloak нужно не так много.

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

```
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    implementation 'org.springframework.security:spring-security-oauth2-jose'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    // Your other dependecies
    // ...
}
```

В application.yml надо прописать URL откуда наше приложение будет брать информацию о том, как именно ему коммуницировать с Keycloak:

```
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: {YOUR_KEYCLOAK_URL}/realms/{YOUR_REALM_NAME}/protocol/openid-connect/certs

```

Теперь надо добавить конфигурацию spring-security, которая будет проверять JWT из запроса через Keycloak:

```
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }
}
```

Voi la, наше приложение подружилось с keycloak и теперь пропускает запросы, которые содержат токены авторизации, полученные в Keycloak. Давайте добавим контроллер и убедимся в этом:

```
@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/me")
    public Authentication whoAmI() {
        SecurityContext context = SecurityContextHolder.getContext();
        return context.getAuthentication();
    }

}
```

В данном примере мы просто возвращаем информацию из JWT-токена, которую наше приложение распарсило с помощью данных полученных от Keycloak через URL, который мы указали в настройках:

```
curl --location --request GET 'http://localhost:8080/api/me' \
--header 'Authorization: Bearer {TOKEN}'
```

```
{
   "authorities": [
       {
           "authority": "SCOPE_profile"
       },
       {
           "authority": "SCOPE_email"
       }
   ],
   "details": {
       "remoteAddress": "0:0:0:0:0:0:0:1",
       "sessionId": "DBB88DFABD56C8E057F0B5D15D501841"
   },
   "authenticated": true,
   "principal": {
       "tokenValue": "{TOKEN}",
       "issuedAt": "2023-01-06T12:11:07Z",
       "expiresAt": "2023-01-06T12:16:07Z",
       "headers": {
           "kid": "3aBPuufejG63gx8kZLvc57dfz48zwro2HAkU1yLsgj4",
           "typ": "JWT",
           "alg": "RS256"
       },
       "claims": {
           "sub": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
           "resource_access": {
               "backend": {
                   "roles": [
                       "uma_protection"
                   ]
               },
               "account": {
                   "roles": [
                       "manage-account",
                       "manage-account-links",
                       "view-profile"
                   ]
               }
           },
           "email_verified": false,
           "clientHost": "172.25.0.1",
           "clientId": "backend",
           "iss": "http://localhost:8282/realms/demo",
           "typ": "Bearer",
           "preferred_username": "service-account-backend",
           "clientAddress": "172.25.0.1",
           "aud": [
               "account"
           ],
           "acr": "1",
           "realm_access": {
               "roles": [
                   "offline_access",
                   "uma_authorization",
                   "default-roles-demo"
               ]
           },
           "azp": "backend",
           "scope": "profile email",
           "exp": "2023-01-06T12:16:07Z",
           "iat": "2023-01-06T12:11:07Z",
           "jti": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7"
       },
       "subject": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
       "notBefore": null,
       "id": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7",
       "issuer": "http://localhost:8282/realms/demo",
       "audience": [
           "account"
       ]
   },
   "credentials": {
       "tokenValue": "{TOKEN}",
       "issuedAt": "2023-01-06T12:11:07Z",
       "expiresAt": "2023-01-06T12:16:07Z",
       "headers": {
           "kid": "3aBPuufejG63gx8kZLvc57dfz48zwro2HAkU1yLsgj4",
           "typ": "JWT",
           "alg": "RS256"
       },
       "claims": {
           "sub": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
           "resource_access": {
               "backend": {
                   "roles": [
                       "uma_protection"
                   ]
               },
               "account": {
                   "roles": [
                       "manage-account",
                       "manage-account-links",
                       "view-profile"
                   ]
               }
           },
           "email_verified": false,
           "clientHost": "172.25.0.1",
           "clientId": "backend",
           "iss": "http://localhost:8282/realms/demo",
           "typ": "Bearer",
           "preferred_username": "service-account-backend",
           "clientAddress": "172.25.0.1",
           "aud": [
               "account"
           ],
           "acr": "1",
           "realm_access": {
               "roles": [
                   "offline_access",
                   "uma_authorization",
                   "default-roles-demo"
               ]
           },
           "azp": "backend",
           "scope": "profile email",
           "exp": "2023-01-06T12:16:07Z",
           "iat": "2023-01-06T12:11:07Z",
           "jti": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7"
       },
       "subject": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
       "notBefore": null,
       "id": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7",
       "issuer": "http://localhost:8282/realms/demo",
       "audience": [
           "account"
       ]
   },
   "token": {
       "tokenValue": "{TOKEN}",
       "issuedAt": "2023-01-06T12:11:07Z",
       "expiresAt": "2023-01-06T12:16:07Z",
       "headers": {
           "kid": "3aBPuufejG63gx8kZLvc57dfz48zwro2HAkU1yLsgj4",
           "typ": "JWT",
           "alg": "RS256"
       },
       "claims": {
           "sub": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
           "resource_access": {
               "backend": {
                   "roles": [
                       "uma_protection"
                   ]
               },
               "account": {
                   "roles": [
                       "manage-account",
                       "manage-account-links",
                       "view-profile"
                   ]
               }
           },
           "email_verified": false,
           "clientHost": "172.25.0.1",
           "clientId": "backend",
           "iss": "http://localhost:8282/realms/demo",
           "typ": "Bearer",
           "preferred_username": "service-account-backend",
           "clientAddress": "172.25.0.1",
           "aud": [
               "account"
           ],
           "acr": "1",
           "realm_access": {
               "roles": [
                   "offline_access",
                   "uma_authorization",
                   "default-roles-demo"
               ]
           },
           "azp": "backend",
           "scope": "profile email",
           "exp": "2023-01-06T12:16:07Z",
           "iat": "2023-01-06T12:11:07Z",
           "jti": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7"
       },
       "subject": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
       "notBefore": null,
       "id": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7",
       "issuer": "http://localhost:8282/realms/demo",
       "audience": [
           "account"
       ]
   },
   "name": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
   "tokenAttributes": {
       "sub": "6e70f6cf-e1cb-4c78-8b81-082fd5c18d36",
       "resource_access": {
           "backend": {
               "roles": [
                   "uma_protection"
               ]
           },
           "account": {
               "roles": [
                   "manage-account",
                   "manage-account-links",
                   "view-profile"
               ]
           }
       },
       "email_verified": false,
       "clientHost": "172.25.0.1",
       "clientId": "backend",
       "iss": "http://localhost:8282/realms/demo",
       "typ": "Bearer",
       "preferred_username": "service-account-backend",
       "clientAddress": "172.25.0.1",
       "aud": [
           "account"
       ],
       "acr": "1",
       "realm_access": {
           "roles": [
               "offline_access",
               "uma_authorization",
               "default-roles-demo"
           ]
       },
       "azp": "backend",
       "scope": "profile email",
       "exp": "2023-01-06T12:16:07Z",
       "iat": "2023-01-06T12:11:07Z",
       "jti": "c4bf73ae-ab84-484f-afb1-0d14b4f7efe7"
   }
}

```

### 5. Реализация Attribute based access control в Keycloak

Темой данного раздела является реализация ABAC-политик безопасности в Keycloak.

ABAC или Attribute based access control - весьма популярный способ разграничения прав в крупных системах с большим числом пользователей и множеством ролей. ABAC позволяет разграничивать доступ между пользователями одной роли или группы на основе атрибутов этих пользователей. И так уж вышло, что и в нашей системе присутствует ABAC, и следовательно его логику нужно было переносить в Keycloak, и все было бы здорово, но Keycloak не поддерживает его из коробки.

Для реализации кастомных политик безопасности в Keycloak есть возможность писать их на JavaScript и упаковав в JAR со специальным дескриптором подложить в Keycloak.

Рассмотрим это на примере.

В парадигме Keycloak есть основные сущности, когда речь идет о разграничении доступа:

* Ресурс - например оконечная точка API
* Политика - правило на основе которого предоставляется доступ
* Разрешение - связующий элемент, который объединяет ресурс и политику

Таким образом если мы имеем ресурс RESOURCE\_1, политику, которая гласит, что разрешение выдается только пользователям с атрибутом RESOURCE\_1\_ACCESS\_ATTRIBUTE, то чтобы это все объединить, нам нужно еще разрешение PERMISSION\_1, что доступ к ресурсу RESOURCE\_1 могут получить только пользователи с атрибутом RESOURCE\_1\_ACCESS\_ATTRIBUTE.

P.S. с точки зрения Keycloak разрешение - тоже политика, но об этом позже.

Про то как писать политики на JS и какие данные при этом в вашем распоряжении можно почитать тут ([JavaScript-based policy](https://www.keycloak.org/docs/latest/authorization_services/index.html#_policy_js)).

Для того чтобы использовать атрибут как средство разграничения доступа у рядового пользователя не должно быть возможности его самостоятельно редактировать, потому что иначе любой джуниор пропишет себе, что он CTO и пойдет крушить-ломать. Тут на помощь приходит механизм Keycloak, который позволяет задать шаблон, соответствие которому автоматически защищает атрибут от редактирования. Вспомним наш compose файл, в команде запуска сервера у нас есть следующая опция:

* -spi-user-profile-legacy-user-profile-read-only-attributes \*\_RES\_ACCESS\_MODE

Данная опция означает, что все пользовательские атрибуты с постфиксом \_RES\_ACCESS\_MODE доступны для редактирования только администратору realm.

Давайте добавим в наше приложение новый ресурс:

```
@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/me")
    public Authentication whoAmI() {
        SecurityContext context = SecurityContextHolder.getContext();
        return context.getAuthentication();
    }

    @GetMapping("/resource")
    public ResponseEntity<String> administratorReadRes(){
        return ResponseEntity.of(Optional.of("Resource for users with fancy access attribute"));
    }

}

```

Мы хотим, чтобы доступ к этому ресурсу был только у пользователя с атрибутом FANCY\_RES\_ACCESS\_ATTRIBUTE в значении PRETTY\_FANCY, каков наш алгоритм действий?

Нам нужно создать ресурс в Keycloak, мы это можем сделать руками, для этого переходим во вкладку authorization -> resources и создаем его:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0a8db3a5ef0fc1191144fe4b60eb21cb96771862%2F58472834dc67833a3ce04084ea4779e8.png?alt=media)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-45a7c3df8425178fdd698870083dce96b506efc5%2F20c7c2bbea13dfb17f46807546641408.png?alt=media)

либо это можно сделать через код, для этого в наш JSON, в список ресурсов нашего клиента (clients\[YOUR CLIENT INDEX].authorizationSettings.resources) добавляем следующее:

```
{
   "name": "fancy resource",
   "ownerManagedAccess": false,
   "attributes": {},
   "_id": "55a4bc23-ae83-4f0c-a7c0-c26118f18b10",
   "uris": [
     "/api/resource"
   ],
   "icon_uri": ""
}

```

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

Создаем политику

Тут все будет несколько сложнее) Для начала где-нибудь в ресурсах создадим структуру каталогов вида:

custom-scripts

## - META-INF

### - keycloak-scripts.json

## - policy.js

## - …

из этого каталога мы впоследствии будем собирать JAR-файл и деплоить его в Keycloak.

keycloak-scripts.json это дескриптор следующего вида:

```
{
   "authenticators": [],
   "policies": [],
   "mappers": [],
   "saml-mappers": []
}

```

как его составлять, можно прочитать здесь ([JavaScript providers](https://www.keycloak.org/docs/latest/server_development/#_script_providers))

теперь давайте создадим политику, как уже говорилось ранее, мы хотим, чтобы доступ к ресурсу был только у пользователя с атрибутом FANCY\_RES\_ACCESS\_ATTRIBUTE в значении PRETTY\_FANCY

для этого мы реализуем следующую логику:

```
var context = $evaluation.getContext().getIdentity().getAttributes();
var identity = context.getIdentity();
var attributes = identity.getAttributes();

if (attributes.containsValue(' FANCY_RES_ACCESS_ATTRIBUTE', 'PRETTY_FANCY')) {
   $evaluation.grant();
} else {
   $evaluation.deny();
}

```

сохраним это в файле pretty-fancy-policy.js в подкаталоге policies и опишем в дескрипторе в соответствии с документацией:

```
{
 "authenticators": [],
 "policies": [
   {
     "name": "Pretty fancy policy",
     "fileName": "policies/pretty-fancy-policy.js",
     "description": "Gives access only to pretty fancy users."
   }
 ],
 "mappers": [],
 "saml-mappers": []
}

```

Когда все готово нам нужно собрать JAR файл, для этого вызываем следующую команду:

```
jar --create --file "/path/to/your/custom-scripts.jar" --no-manifest -C "/path/to/your/custom-scripts" .
```

Теперь нам надо прокинуть этот JAR в контейнер Keycloak, это можно сделать командой докера, потом залезть в контейнер и перезагрузить сервер, но так как мы вынесли нашу конфигурацию в код, мы смело можем просто пересоздать контейнер дописав volume в compose файле:

```
version: "3.9"
services:
  keycloak-postgres:
    image: library/postgres:${KC_POSTGRES_IMAGE_TAG:-14}
    container_name: ${POSTGRES_CONTAINER_NAME:-postgres}
    restart: on-failure
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    healthcheck:
      test: pg_isready -d postgres
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
    ports:
      - ${KC_POSTGRES_PORT_MAPPING:-5435}:5432
    deploy:
      resources:
        limits:
          memory: 256M

  keycloak:
    image: quay.io/keycloak/keycloak:20.0.2
    container_name: keycloak
    command:
      - start --auto-build --db postgres --hostname-strict-https false --hostname-strict false --proxy edge --http-enabled true --import-realm --spi-user-profile-legacy-user-profile-read-only-attributes *_RES_ACCESS_MODE
    environment:
      KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/postgres
      KC_DB_USERNAME: postgres
      KC_DB_PASSWORD: postgres
      KC_DB_SCHEMA: public
      KC_FEATURES: preview
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    volumes:
      - type: bind
        source: ./src/main/resources/keycloak/import/realm-export.json
        target: /opt/keycloak/data/import/realm-export.json
        read_only: true
      - type: bind
        source: ./src/main/resources/keycloak/scripts/custom-scripts.jar
        target: /opt/keycloak/providers/custom-scripts.jar
        read_only: true
    ports:
      - 8282:8080
    depends_on:
      keycloak-postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://0.0.0.0:8080/realms/master"]
      start_period: 10s
      interval: 30s
      retries: 3
      timeout: 5s
```

после запуска, наша политика станет доступна как тип в разделе политик нашего клиента your realm -> your client -> authorization -> policies -> create new:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-610a59201a0753ec5bf35964e29ef587b7caf53f%2F361296fe9f81b8b739d9f3599e850602.png?alt=media)

теперь мы можем создать политику с нашим кастомным типом, это можно сделать через интерфейс, но я не буду отдельно останавливаться на этом, так как наша основная цель - содержать конфигурацию keycloak в коде. Поэтому мы добавим следующий код в секцию политик нашего клиента (clients\[YOUR CLIENT INDEX].authorizationSettings.policies):

```
// Политика
{
 "name": "Pretty fancy policy",
 "description": "Gives access only to pretty fancy users",
 "type": "script-policies/pretty-fancy-policy.js",
 "logic": "POSITIVE",
 "decisionStrategy": "UNANIMOUS",
 "config": {}
},
// Разрешение
{
 "name": "Pretty fancy permission",
 "description": "Fancy permission for fancy resource",
 "type": "resource",
 "logic": "POSITIVE",
 "decisionStrategy": "UNANIMOUS",
 "config": {
   "resources": "[\"fancy resource\"]",
   "applyPolicies": "[\"Pretty fancy policy\"]"
 }
}

```

Политика:

* В описании политики поле type формируется следующим образом: script-\[Путь до JS файла от корня Jar]
* logic в значении POSITIVE означает, что логика работы нашей политики - прямая, т. е. если политика принимает положительное решение оно таковым и остается, с помощью значения NEGATIVE можно сделать обратную логику
* decisionStrategy в значении UNANIMOUS условно означает логическое И, если бы наша политика состояла из комбинации нескольких других, то все они должны были бы прийти к положительному решению, чтобы наша также дала положительный ответ, помимо UNANIMOUS есть еще AFFIRMATIVE - логическое ИЛИ, а также CONSENSUS, в последнем случае решение принимается в зависимости от того каких решений было больше.

Разрешение:

* Типом данного разрешения является resource, это означает, что данное разрешение выдается на конкретный ресурс, безотносительно к Scope и другим атрибутам ресурса.
* Поля logic и decisionStrategy имеют такую же семантику, как и в обычной политике
* Основным отличием разрешения от обычной политики является наличие атрибута config, в котором перечислены ресурсы, относящиеся к данному разрешению, и комбинация политик, которые должны принять решение о предоставлении доступа.

Подробнее про ресурсы, политики и разрешения можно прочитать здесь (<https://www.keycloak.org/docs/latest/authorization_services/index.html>)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1a63b802cb776a9997951c8ef79ef4d5e8bc0abc%2F85855be417088d845f9b00f0ba5f83ac.png?alt=media)

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

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

Для начала создадим пользователя:

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/SSO/Keycloak%20for%20Java%20app/Untitled)

Теперь идем во вкладку evaluate секции authorization, там выбираем нашего пользователя и ресурс и жмем evaluate:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-77d67921da5ff212d03715dad46face73246785e%2F91b08c6c461cdca183189151e8fa904f.png?alt=media)

Иииии….

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-add7102dcaf85576437efbd3d1bde62b90415a43%2F5ff10e7dafdf2f186eb3e538d0d787e9.png?alt=media)

Получаем вполне закономерный результат, потому что у нашего пользователя нет требуемого атрибута. Добавим его:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5c121969e285c8e57e40357b9ea0f5fbcdbc8e25%2Ff7e6b5f244f2d228b0dcb48627446e90.png?alt=media)

И проверим еще раз:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-add7102dcaf85576437efbd3d1bde62b90415a43%2F5ff10e7dafdf2f186eb3e538d0d787e9.png?alt=media)

Хмммм… мы что-то явно упустили.

А именно, то что заботливые разработчики клоаки решили, что просто добавить пользовательский атрибут это слишком просто, и чтобы он был доступен во время проверки прав, нужно еще добавить маппер, поэтому идем в секцию clients\[YOUR CLIENT INDEX].protocolMappers и добавляем туда маппер для нашего атрибута:

```
{
 "name": "FANCY_RES_ACCESS_ATTRIBUTE_MAPPER",
 "protocol": "openid-connect",
 "protocolMapper": "oidc-usermodel-attribute-mapper",
 "consentRequired": false,
 "config": {
   "aggregate.attrs": "false",
   "userinfo.token.claim": "true",
   "multivalued": "false",
   "user.attribute": "FANCY_RES_ACCESS_ATTRIBUTE",
   "id.token.claim": "true",
   "access.token.claim": "true",
   "claim.name": "FANCY_RES_ACCESS_ATTRIBUTE"
 }
}

```

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e3e6d106158c2230857e47d8a3ae987ccb7ee477%2F5894afbdbb8bfde99eae20d58a200725.png?alt=media)

Наконец разрешение получено.

У нас получилось реализовать полноценный ABAC в Keycloak, более того, вся конфигурация у нас хранится в коде, тут небольшое лирическое отступление, когда сами будете это все разрабатывать - проще всего набить все настройки в интерфейсе, а потом экспортировать в файл средствами Keycloak ;)

И так у нас есть контроль доступа, который работает на основе пользовательских атрибутов и вроде все прекрасно …, НО наше spring-приложение про это ничего не знает и все также пропускает любого авторизованного в keycloak пользователя к своим ресурсам, как это поправить поговорим в следующих постах.


# OpenAM

## OpenAM

<https://www.openidentityplatform.org/openam>

[30845478](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/SSO/OpenAM/OpenAM/30845478/README.md)

## [OpenAM](https://github.com/OpenIdentityPlatform/OpenAM)

If you have multiple sites and applications in your company, probably you need to provide seamless authentication to all of them. So when user logged in at one of your sites once, he does not need to enter his credentials on other sites. So, OpenAM can help you to solve all this issues. Key features of OpenAM are:

* **Authentication** - OpenAM ships with more than 20 authentication modules, which you can use to customize your authentication process. Also, you can customize sequence of authentication modules, to provide multi-factor or adaptive authentication.
* **Authorization** - OpenAM can also manage authorization, so you can restrict access to desired resources according to different authorization policies.
* **Identity Provider** - OpenAM can act as an Identity Provider, using SAML, OAuth 2.0 or OpenID Connect 1. So, your clients can develop their own applications or websites and authenticate via OpenAM like they authenticate via Facebook or Google.
* **Single Sign On** - after single authentication, user gets access to all resources protected by OpenAM. So, there is no need to authenticate at other services.
* **High Performance and Clusterization** - To enable high availability for large-scale and mission-critical deployments, OpenAM provides both system failover and session failover. These two key features help to ensure that no single point of failure exists in the deployment, and that the OpenAM service is always available to end-users. Redundant OpenAM servers, policy agents, and load balancers prevent a single point of failure. Session failover ensures the user’s session continues uninterrupted, and no user data is lost.
* **Extensibility** - OpenAM allows to extend just any functionality, from authentication modules to user data source. Besides, it supports UI customization to create separate end-user pages with personal branding.
* **Developer SDK** - OpenAM ships with Java SDK, which allows to interact with authorization API, authentication API, manage accounts and so on…
* **Security** - As OpenAM is open source, it allows community and clients test it for possible vulnerabilities, and do PEN tests.


# OpenIG

## OpenIG

<https://www.openidentityplatform.org/openig>

[30845478](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Identity%20and%20Access%20Management%20\(IDM\)/SSO/OpenIG/OpenIG/30845478/README.md)

## [OpenIG](https://github.com/OpenIdentityPlatform/OpenIG)

The Open Identity Gateway (OpenIG) is a high-performance reverse proxy server with specialized session management and credential replay functionality.

OpenIG is an independent policy enforcement point that reduces the proliferation of passwords and ensures consistent, secure access across multiple web apps and APIs. OpenIG can leverage any standards-compliant identity provider to integrate into your current architecture. Single sign-on and sign-off improves the user experience and will vastly improve adoption rates and consumption of services provided.

* Extend SSO to any Application
* Federate Enabling Applications
* Implement Standards Based Policy Enforcement

#### How it Works

OpenIG is essentially a Java-based reverse proxy which runs as a web application. All HTTP traffic to each protected application is routed through OpenIG, enabling close inspection, transformation and filtering of each request. You can create new filters and handlers to modify the HTTP requests on their way through OpenIG, providing the ability to recognize login pages, submit login forms, transform or filter content, and even function as a Federation endpoint for the application. All these features are possible without making any changes to the application’s deployment container or the application itself.

OpenIG works together with [OpenAM](https://www.openidentityplatform.org/openam) to integrate Web applications without the need to modify the target application or the container that it runs in.

* Support for identity standards ([OAuth 2.0](https://tools.ietf.org/html/rfc6749), [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html), [SAML 2.0](http://saml.xml.org/saml-specifications))
* Application and API gateway concept
* Prepackaged SAML 2.0-based federation
* Password capture and replay
* Works with any identity provider, including OpenAM
* Single Sign-On and Single Log-Out

Useful links:


# Firewall

[nftables](/readme/architect/firewall/nftables)


# nftables

<https://habr.com/ru/articles/684524/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-434d30599be7d1e595aaac2f0becf7d6b5cfac18%2F22557e6e23b34add885f56568d8ad7e6.jpg?alt=media)

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

В качестве межсетевого экрана будем использовать nftables, функционирующий под управлением ОС Debian GNU Linux.

## Требования к предварительным знаниям

Здесь мы не будем рассматривать принципы работы nftables, синтаксис его командной строки или формат конфигурационных файлов. Об этом есть множество других статей. Мы же сосредоточимся на его практическом использовании. По большому счету все необходимые настройки nftables будут в статье подробно описаны, так что проблем с их воспроизведением, по идее, быть не должно. Тем не менее для лучшего понимания процесса рекомендуется ознакомиться и держать под рукой следующие ресурсы:

* [Man по nftables](https://www.netfilter.org/projects/nftables/manpage.html)
* [Официальный nftables wiki](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page)
* [Статья на Хабре: «Используем nftables в Red Hat Enterprise Linux 8»](https://habr.com/ru/company/otus/blog/511122/)

## Технические требования

Для построения лабораторных стендов можно использовать любую удобную для вас систему виртуализации: VMware, Hyper-V, VirtualBox, QEMU-KVM или другую. Главным требованием к этой системе будет наличие возможности управления виртуальными сетями. Система должна позволять создавать виртуальные сети, а также назначать их на различные адаптеры виртуальных машин.

### Создание шаблона виртуальной машины

В большинстве лабораторных стендов нам потребуется виртуальная машина-шаблон, которую мы будем тиражировать и донастраивать. Для создания этого шаблона необходимо:

1. Скачать [Debian 11.4 в формате netinstall](https://www.debian.org/CD/netinst/) .
2. Установить ОС в минимальной конфигурации. Для этого во время инсталляции выбираем все параметры по умолчанию (Next, Next, Next, ...), а на шаге с выбора пакетов «Software selection» отказываемся от всего предлагаемого (Рисунок 1). *Рисунок 1*

   ![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-267a0060832b4b8c2b0470c4843031a3e1aaecdd%2Fx4azky3xrn-wtqtovxdnp1dknlg.jpeg?alt=media)
3. (Опционально). Рекомендуется установить пакет расширений средств виртуализации для гостевой операционной системы. Например, для VMware – это будет VMware Tools, для VirtualBox – Virtualbox extension pack и т.д. После этого рекомендуется настроить разделяемый каталог (Shared Folder) для связи между файловыми системами виртуальной машины и гипервизора.
4. (Опционально). Для повышения удобства выполнения практических работ рекомендуется поставить дополнительные пакеты:
   * OpenSSH сервер (пакет ssh).
   * Файловый менеджер Midnight Commander (пакет mc).
   * Утилиту conntrack, демонстрирующую внутреннее состояние брандмауэра и помогающую в отладке правил фильтрации (пакет conntrack). Все эти пакеты можно легко поставить. Для этого достаточно зайти на машину под root’ом (при конфигурировании Linux нам всегда потребуется root) и выполнить в консоли заклинание (команду):

     ```
     apt install mc ssh conntrack
     ```

## Типовой сценарий: защита сервера / рабочей станции с помощью локального брандмауэра

Задача настроить локальный брандмауэр — пожалуй, самая распространённая задача межсетевого экранирования. Для ее решения мы должны знать ответы на два вопроса: чего мы хотим добиться, и как это реализовать.

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

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

Стандартный алгоритм решения задач межсетевого экранирования:

1. Формулирование модели угроз.
2. Анализ угроз и выработка мер по их нейтрализации. На данном этапе необходимо сформулировать схему разграничения трафика: то есть какой трафик пропускаем, а какой блокируем.
3. На основании схемы разграничения трафика производится настройка межсетевого экрана: формируются правила фильтрации, настраиваются сервисы, пишутся скрипты и так далее.

Моделирование угроз – процесс нетривиальный и требующий значительных компетенций в области информационной безопасности. Фактически необходимо знать то, как вас будут пытаться взломать. Множество способов атак уже описано, но постоянно появляются все новые и новые. Специалисты, профессионально занимающиеся моделированием угроз, должны постоянно держать руку на пульсе и быть в курсе всех модных новинок. Для всех остальных существенным подспорьем будут готовые модели и банки данных угроз, например, [MITRE ATT\&CK Matrix](https://attack.mitre.org/), [Банк данных угроз ФСТЭК России](https://bdu.fstec.ru/) или публичные исследования, например, [это](https://habr.com/ru/post/351326/), [это](https://habr.com/ru/post/421161/) и [это](https://habr.com/ru/post/422329/).

### Модель угроз

Применительно к нашей задаче мы будем рассматривать следующие угрозы:

| Угроза                                                                                                                                    | Комментарий                                                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| У1. Угроза удаленной эксплуатации уязвимостей (как программных, так и конфигурационных) в сетевых сервисах узла.                          | Примеры реализации угрозы: эпидемия червя <https://en.wikipedia.org/wiki/Blaster\\_(computer\\_worm)> (эксплуатация программной уязвимости) или <https://www.bigdataschool.ru/blog/elasticsearch-security-problems-data-leaks.html>, происходящие из-за отсутствия авторизации (эксплуатация конфигурационной уязвимости).                                                      |
| У2. Угроза удаленной атаки на отказ в обслуживании (DoS) сетевых сервисов узла.                                                           |                                                                                                                                                                                                                                                                                                                                                                                 |
| У2.1. DoS атака за счет превышения критического числа сетевых запросов, которые может обработать сетевой сервис с приемлемым качеством    | Подобные атаки наиболее актуальны для серверов баз данных, которые могут генерировать существенную вычислительную нагрузку на обработку входящего запроса.                                                                                                                                                                                                                      |
| У2.2. DoS атака за счет подделки злоумышленником IP-пакета и установки в нем обратного адреса равному адресу петли 127.0.0.0/8            | Сетевой сервис, получая подобный запрос, при отправке ответа может получить его себе обратно, что может привести к зацикливанию или другим негативным последствиям.                                                                                                                                                                                                             |
| У3. Угроза раскрытия аутентификационных данных за счет удаленных атак прямого перебора, осуществляемых в отношении сетевых сервисов узла. | Тривиальный подбор паролей, но осуществляемый удаленно через сеть.                                                                                                                                                                                                                                                                                                              |
| У4. Угроза установки исходящих вредоносных сетевых соединений локальным программным обеспечением узла.                                    | Вредоносное сетевое соединение может установить как троян, связывающийся со своим сервером управления (C\&C), так и легитимная программа, передающая избыточные данные (например, телеметрию) на сервера разработчиков. Кроме того, к данной угрозе можно отнести и действия сотрудников, использующих рабочие компьютеры для личных нужд (например, для майнинга криптовалют). |
| У5. Угроза несанкционированного открытия сетевого порта вредоносным кодом.                                                                | На заре вирусостроения первые шпионские вредоносные программы открывали на машине жертвы сетевые порты, к которым затем подключались лица, занимающиеся шпионажем.                                                                                                                                                                                                              |

Теперь проанализируем угрозы и сформулируем идеи по их нейтрализации.

Для парирования угрозы **У1** и **У5** необходимо лишить злоумышленников возможности осуществлять подключения к сетевым сервисам узла. Это достигается путем ограничения входящего трафика по портам и адресам источника (белые или черные списки).

Нейтрализация **У2.1** базируется на ограничении скорости получения сервисом сетевых пакетов, а как более сложный вариант — обнаружение атакующих узлов и блокировка их по IP. Следует отметить, что ограничение максимальной частоты подключений не всегда допустимо, особенно для публичных сервисов.

Угроза **У2.2.** нейтрализуется путем отбрасывания всех сетевых пакетов, имеющих адрес источника из подсети 127.0.0.0/8 и пришедших не с петлевого (loopback) интерфейса.

Для нейтрализации угрозы **У3** в общем случае требуется применение дополнительных программ, которые бы определяли факты неудачных попыток авторизации, определяли бы IP адреса атакующих, а затем с помощью брандмауэра блокировали бы их по IP. Реализации защиты от угрозы **У2.1.** частично усложнит злоумышленникам возможность реализации угрозы **У3**.

Борьба с **У4** проводится путем ограничения исходящего сетевого трафика, что может производиться на основании анализа:

* адресов назначения (белые или черные списки);
* протоколов транспортного уровня (TCP/UDP) и номеров их портов (белые или черные списки);
* приложений, пытающихся установить соединение.

Переходя от идей по защите к их реализации, следует помнить базовую аксиому: чем более надежная защита, тем дороже она стоит. Причем эта стоимость выражается не только в затратах на покупку средств защиты, но и трудозатратах на их эксплуатацию особенно со стороны рядовых пользователей. Если защита терроризирует пользователя регулярными запросами или другим образом отвлекает от работы, то он всеми правдами и неправдами будет саботировать ее применение (user resistance). Поэтому всегда нужно находить баланс между защищенностью и удобством. Для этого в некоторых случаях следует принимать (игнорировать) риски маловероятных или незначительных по ущербу угроз информационной безопасности.

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

### Схемы разграничения трафика

**Схема № 1. Минимальная защита рабочей станций.**

* Запрещен весь входящий трафик, кроме того, что относится к уже установленным соединениям.
* Весь исходящий трафик разрешен.

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

Ограничение исходящего трафика по адресам назначения на рабочих станциях, где активно пользуются Интернетом, крайне трудоемко, а ограничивать трафик по приложениям брандмауэр nftables не умеет. Соответственно, весь исходящий сетевой трафик разрешается без ограничений. Угроза **У3** полностью игнорируется. Как слабенький вариант борьбы с **У3** можно рекомендовать применение встроенной системы мандатного разграничения доступа (MAC) [AppArmor](https://manpages.debian.org/unstable/apparmor/apparmor.d.5.en.html#Network), которая позволяет выбранным приложениям отключить сетевые возможности. Проблема в том, что AppArmor работает только по черным спискам, белый список – замкнутую программную среду — с его помощью сделать не получится.

Рассмотренная схема разграничения трафика позволяет полностью нейтрализовать угрозы **У1, У2, У3, У5**, угроза **У4** игнорируется.

По умолчанию встроенный брандмауэр ОС Windows работает по этой схеме.

**Схема № 2. Минимальная защита сервера.**

Схема практически полностью повторяет предыдущую, за исключением того, что:

* разрешается входящий трафик по выбранным сетевым портам, требуемым для работы сетевым сервисам;
* блокируется входящий трафик для IP-пакетов, адрес источника которых относится к сети 127.0.0.0/8, и которые пришли не с петлевого (loopback) интерфейса.

Ограничение доступа к сетевым сервисам по IP-адресам не производится.

Реализация данной схемы частично защищает от **У1** и полностью от **У2.2** и **У5**, остальные же угрозы игнорируются.

**Схема № 3. Стандартная защита серверов.**

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

Исходящий же трафик полностью запрещается, за исключением трафика:

* текущего по уже установленным соединениям;
* отправляемого по перечню явно разрешенных портов протоколов транспортного уровня (белый список).

Подобная схема фильтрации полностью защищает от **У2.2** и **У5**, частично от других угроз, кроме **У2.1**, которая полностью игнорируются.

**Схема № 4. Усиленная защита серверов.**

Схема основана на предыдущей, но усилена следующими мерами:

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

Подобная схема фильтрации дает максимальную защиту от всех рассмотренных угроз.

### Практическая работа: реализация межсетевого экранирования сервера по схеме усиленной защищенности

### Описание стенда

Проведем нашу первую практическую работу. Начнем с того, что организуем лабораторный стенд по следующей схеме (Рисунок 2):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3333f8dca1c2d759ecfd5bcc93a525e968014220%2F0v_axia4mpc6machrrsk1ik_ya8.png?alt=media)

*Рисунок 2*

Здесь все просто. Есть одна виртуальная машина «Сервер». Она подключена своим единственным сетевым адаптером к виртуальной сети «NAT». В данной сети присутствует виртуальный маршрутизатор, реализующий NAT и выполняющий роль DNS-сервера. Сетевой адаптер гипервизора также подключен к сети «NAT». В качестве защищаемого сервиса на виртуальной машине «Сервер» будет выступать SSH. Для его тестирования на гипервизоре устанавливается SSH-клиент.

### Задача

На виртуальной машине «Сервер» настроить межсетевой экран по схеме 4 «Усиленная защита сервера».

### Подготовка стенда

1. На виртуальную машину «Сервер» установим OpenSSH сервер и службу автоматической синхронизации системных часов systemd-timesyncd:

   ```
   аpt install ssh systemd-timesyncd
   ```
2. В файле настроек службы синхронизации времени /etc/systemd/timesyncd.conf раскомментируем строки NTP и FallbackNTP. Строку NTP запишем в следующем виде:

   ```
   NTP=0.europe.pool.ntp.org 1.europe.pool.ntp.org 2.europe.pool.ntp.org 3.europe.pool.ntp.org
   ```
3. Активируем автоматическую синхронизацию времени, выполнив заклинание:

   ```
   timedatectl set-ntp true
   systemctl enable --now systemd-timesyncd.service
   systemctl restart systemd-timesyncd.service
   ```
4. Для проверки синхронизации времени нужно перезагрузиться, а затем выполнить заклинание:

   ```
   timedatectl timesync-status
   ```
5. Настроим статический IP адрес на виртуальной машине «Сервер». Для этого содержимое файла /etc/network/interfaces заменим на следующее: *Примечание.* В Debian по умолчанию при описании сетевых интерфейсов используется ключевое слово «allow-hotplug». Мы же поменяем его на «auto». Это сделано для того, чтобы была возможность менять сетевые настройки с помощью заклинания:

   ```
   auto lo
   iface lo inet loopback

   # The primary network interface
   auto ens33
   iface ens33 inet static
   address 172.30.0.40
   mask 255.255.255.0
   gateway 172.30.0.2
   nameserver 172.30.0.2
   ```

   ```
   service networking restart
   ```
6. Проверим работоспособность стенда. С гипервизора должна быть возможность установить SSH сессию до «Сервера», а с «Сервера» — возможность скачивать обновления с официальных репозиториев и обновлять время по сети.

### Ход выполнения работы

1. Логика организации дистрибутива Debian такова, что межсетевой экран nftables установлен в нем по умолчанию. В этом можно убедиться, выполнив заклинание:

   ```
   nft -v
   ```
2. Для автоматической загрузки правил межсетевого экранирования после старта системы создана служба nftables (по сути являющаяся systemd юнитом), но по умолчанию она деактивирована. В этом можно убедиться, выполнив заклинание: Служба организована таким образом, что при запуске она считывает правила, записанные в файле /etc/nftables.conf. Соответственно, цель нашей работы — внести в данный файл необходимые правила и активировать службу.

   ```
   service nftables status
   ```
3. Уточним схему разграничения сетевого трафика. Как часто бывает, те схемы, которые мы описывали ранее, не могут учитывать особенности эксплуатации конкретных серверов, поэтому и требуют уточнения. В частности, сделаем следующее:
   * Разрешим «пинговать» (использовать команду проверки сетевой связности ping) защищаемый сервер и разрешим серверу «пинговать» другие узлы. Несмотря на то, что каждый открытый поток сетевого трафика снижает защищенность, открытие «пингов» существенно улучшает эксплуатационные свойства сервера. Для этого разрешим входящие и исходящие ICMP echo-request.
   * Разрешим отправлять запросы и получать ответы по протоколу DNS. Для этого разрешим исходящие соединения по портам: TCP 53 и UDP 53 в адрес явно указанного DNS сервера: 172.30.0.2.
   * Разрешим автоматически обновлять системные часы по протоколу NTP. Для этого разрешим исходящие соединения по портам: TCP 123 и UDP 123 в адрес серверов, указанных в файле /etc/systemd/timesyncd.conf.
   * Разрешим скачивать обновления с репозиториев, указанных в файлах настройки менеджеров пакетов. Для этого разрешим исходящие http соединения (TCP 80) в адрес репозиториев, указанных в файле /etc/apt/sources.list.
   * Будем явно отбрасывать входящий трафик, превышающий скоростные ограничение в:
     * 5 пакетов в секунду для ICMP;
     * 10 попыток установки соединений в минуту для SSH.
   * Узлы, занимающиеся подборкой паролей к SSH, будем определять с помощью утилиты fail2ban. Утилита сама будет конфигурировать nftables для блокирования и разблокирования атакующих узлов.
4. В файл /etc/nftables.conf запишем следующее содержимое: *Примечание.* Опытные администраторы наверно заметили, что в цепочке *fw\_input* мы явно отбрасываем icmp пакеты, превышающие установленный скоростной лимит (*ip protocol icmp limit rate over 5/second drop*), а затем пишем правило, разрешающее получать icmp эхо-запросы (*icmp type echo-request accept*). Резонный вопрос: зачем мы это делаем? Ведь политика цепочки — отбрасывать все, что явно не разрешено, и, по идее, кажется, что эти два правила можно заменить одним *icmp type echo-request limit rate 5/second accept*, но в данном случае это не так. Дело в том, что в цепочке присутствует правило *ct state established,related accept*, и из-за него *icmp type echo-request limit rate 5/second accept* не будет ограничивать скорость. Это происходит потому, что nftables считает «ping» как сетевое соединение. Это можно увидеть с помощью заклинания Соответственно, пакеты, не попавшие в правило *icmp type echo-request limit rate 5/second accept*, будут пропущены правилом *ct state established,related accept*. Вот поэтому и нужно явно отбрасывать пакеты, превышающие скорость, и делать это перед правилом *ct state established,related accept*.

   ```
   #!/usr/sbin/nft -f

   flush ruleset

   table ip firewall {
   # Список разрешенных DNS серверов
           set allowed-dns-servers {
                   type ipv4_addr
                   elements = { 172.30.0.2 }
           }

   # Список разрешенных узлов, с которых можно подключаться по SSH
           set allowed-ssh-clients {
                   type ipv4_addr
                   elements = { 172.30.0.1 }
           }

   # Список разрешённых NTP-серверов из файла /etc/systemd/timesyncd.conf
           set allowed-ntp-servers {
                   type ipv4_addr
           }

   # Список разрешённых серверов репозиториев из файла /etc/apt/sources.list
           set allowed-repos {
                   type ipv4_addr
           }

   # Цепочка правил фильтрации входящего трафика. Запрещено все, кроме того, что
   # явно разрешено (policy drop)
           chain fw_input {
                   type filter hook input priority filter; policy drop;

   # Разрешен трафик с петлевого (loopback) интерфейса
                   iifname "lo" accept

   # Явно отбрасываются IP-пакеты с обратным адресом локальной петли,
   # но не относящиеся к петлевому интерфейсу
                   ip saddr 127.0.0.0/8 drop

   # Явно отбрасываем ICMP трафик, превышающий скоростной лимит
                   ip protocol icmp limit rate over 5/second drop

   # Явно отбрасываем запросы на соединение по SSH, превышающие скоростной лимит
   		tcp dport 22 ct state new limit rate over 10/minute drop

   # Разрешаем трафик по уже установленным соединениям
                   ct state established,related accept

   # Разрешаем получение ICMP-эхо запросов (чтобы узел можно было пингануть)
                   icmp type echo-request accept

   # Разрешаем подключение по SSH избранным клиентам
                   ip saddr @allowed-ssh-clients tcp dport 22 accept
           }

   # Цепочка правил фильтрации исходящего трафика. Запрещено все, кроме того, что
   # явно разрешено (policy drop)
           chain fw_output {
                   type filter hook output priority filter; policy drop;

   # Разрешаем трафик на петлевой интерфейс
                   oifname "lo" accept

   # Разрешаем трафик по уже установленным соединениям
                   ct state established,related accept

   # Разрешаем отправку ICMP эхо-запросов (чтобы узел мог пингануть).
                   icmp type echo-request accept

   # Разрешаем отправку DNS-запросов по UDP
                   ip daddr @allowed-dns-servers udp dport 53 accept

   # Разрешаем отправку DNS-запросов по TCP
                   ip daddr @allowed-dns-servers tcp dport 53 accept

   # Разрешаем отправку запросов по протоколу NTP c помощью TCP
                   ip daddr @allowed-ntp-servers tcp dport 123 accept

   # Разрешаем отправку запросов по протоколу NTP c помощью UDP
                   ip daddr @allowed-ntp-servers udp dport 123 accept

   # Разрешаем установкe HTTP-соединений с серверами репозиториев, указанными в файле
   # /etc/apt/sources.list
                   ip daddr @allowed-repos tcp dport 80 accept
           }
   }

   ```

   ```
   conntrack -L
   ```
5. Активируем автоматический запуск nftables после загрузки системы. Для этого выполним заклинания:

   ```
   systemctl daemon-reload
   systemctl enable nftables
   ```
6. Для дальнейших работ нам потребуется утилита dig, входящая в пакет dnsutils, и утилита fail2ban, входящая в одноименный пакет. Установим их, выполнив заклинание:

   ```
   apt install dnsutils fail2ban
   ```
7. Рассмотрим процесс формирования динамических списков allowed-ntp-servers и allowed-repos. Данные списки должны содержать в себе IP-адреса: Если вы просмотрите эти файлы, то никаких IP-адресов там не заметите. Вместо них указаны лишь FQDN-имена требуемых серверов. Проблема в том, что nftables не умеет фильтровать по FQDN-именам, он работает только по IP. Соответственно, необходимо, чтобы кто-то ему перевел из FQDN в IP. Также следует помнить, что FQDN-имена — вещь не постоянная, и процесс перевода их в IP нужно делать периодически. Для извлечения и перевода из данных файлов FQDN в IP-адреса можно воспользоваться скриптом: Алгоритм работы данного скрипта заключается в том, что вначале очищаются соответствующие списки nftables, затем с помощью регулярных выражений из файлов извлекаются FQDN-имена серверов, которые с помощью утилиты dig преобразуются в IP-адреса, а затем добавляются к требуемым спискам.

   * репозиториев, указанных в файле /etc/apt/sources.list;
   * NTP-серверов, указанных в файле /etc/systemd/timesyncd.conf.

   ```
   nft flush set ip firewall allowed-ntp-servers
   nft flush set ip firewall allowed-repos

   grep -oP '(?<=^deb http://)[^ /]*' /etc/apt/sources.list | uniq  | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-repos { IP }

   grep -oP '(?<=^NTP=).+$' /etc/systemd/timesyncd.conf | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-ntp-servers { IP }

   grep -oP '(?<=^FallbackNTP=).+$' /etc/systemd/timesyncd.conf | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-ntp-servers { IP }
   ```
8. Проблема в том, что приведенный выше скрипт нужно выполнять периодически. Для ее решения преобразуем скрипт в systemd юнит /etc/systemd/system/nft-dns.service:

   ```
   # /etc/systemd/system/nft-dns.service
   [Unit]
   Description=nftables DNS resolve service
   Requires=nftables.service
   Wants=nft-dns.timer
   After=network.target

   [Service]
   Type=oneshot
   ExecStart=bash -c "nft flush set ip firewall allowed-ntp-servers ; nft flush set ip firewall allowed-repos ; grep -oP '(?<=^deb http://)[^ /]*' /etc/apt/sources.list | uniq  | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-repos { IP } ; grep -oP '(?<=^NTP=).+$' /etc/systemd/timesyncd.conf | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-ntp-servers { IP } ; grep -oP '(?<=^FallbackNTP=).+$' /etc/systemd/timesyncd.conf | xargs dig +short | xargs -r -I IP nft add element ip firewall allowed-ntp-servers { IP } "

   StandardOutput=journal

   [Install]
   WantedBy=multi-user.target

   ```
9. Затем для ежечасного запуска этого юнита создадим systemd таймер /etc/systemd/system/nft-dns.timer:

   ```
   # /etc/systemd/system/nft-dns.timer
   [Unit]
   Description=nftables DNS resolve timer
   Requires=nftables.service

   [Timer]
   Unit=nft-dns.service
   OnCalendar=hourly

   [Install]
   WantedBy=timers.target
   ```
10. После создания юнита и таймера активируем их, выполнив заклинания:

    ```
    systemctl daemon-reload
    systemctl enable nft-dns.service
    systemctl enable nft-dns.timer
    ```
11. Последним этапом данной задачи будет настройка fail2ban на анализ журналов работы OpenSSH сервера и блокирование всех тех, кто совершил большое количество неудачных попыток авторизации по ssh. На самом деле fail2ban по умолчанию защищает SSH-сервер сразу после установки. Единственное, что необходимо сделать, так это поменять действия, выполняемые при блокировке. По умолчанию они рассчитаны на iptables (предшественника nftables), нам же требуется их поменять для nftables. Сделать это очень просто. В конфигурационном файле /etc/fail2ban/jail.conf в секции \[DEFAULT] значение параметров *banaction* и *banaction\_allports* необходимо поменять на *nftables-multiport* и *nftables-allports* соответственно. Затем перезапустить службу заклинанием Для блокировки нарушителей fail2ban добавляет к правилам фильтрации nftables новую таблицу f2b-table. Эта таблица содержит единственную цепочку f2b-chain, имеющую более низкий приоритет и соответственно срабатывающую раньше, чем тем цепочки, что мы создавали в файле /etc/nftables.com. Единственным правило цепочки f2b-chain является блокировка доступа к порту ssh (tcp 22) для IP-адресов, включенных в список addr-set-sshd. Пример рассмотренных списков и правил фильтрации, при добавлении туда нарушителя, выглядит следующим образом (Рисунок 3): *Рисунок 3* Текущее состояние блокировок можно посмотреть, выполнив заклинание:

    ```
    service fail2ban restart
    ```

    ![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2c6d438866f9f3b44bceebdf0eeadcc1d2f8565c%2Fvejd8al578rzbof9o1pdrw4jcte.jpeg?alt=media)

    ```
    fail2ban-client status sshd
    ```

## Типовая задача: проверка работы брандмауэра

Очень часто на практике возникает задача проверить, работает ли межсетевой экран. Сделать это можно тривиальным образом: попробовать «пингануть» защищаемый узел или попробовать обратится к какому-либо защищаемому сетевому сервису.

Иногда специалисты по информационной безопасности могут потребовать от системных администраторов провести полную гарантированную проверку (аттестацию) всех правил фильтрации. Например, проверить на возможность открытия UDP и TCP порты из всего возможного диапазона (1-65535).

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

Не самым удобным, но очень доступным вариантом решения этой задачи будет следующий: с помощью утилиты netcat на проверяемом сервере при включенном брандмауэре открываются UDP или TCP порты, а затем сервер с помощью утилиты nmap сканируется с другой машины. Если обнаруживаются порты, которые не должны быть открытыми, то с межсетевым экраном есть проблемы.

### Практическая работа: проверка работы брандмауэра

### Описание стенда

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

### Задача

Проверить на виртуальной машине «Сервер» работу локального брандмауэра в части блокировки входящего трафика в отношении всего диапазона TCP и UDP портов.

### Подготовка стенда

1. На виртуальную машину «Сервер» поставим две дополнительные утилиты: «netcat» и «netstat». Для этого выполним заклинание:

   ```
   apt install netcat net-tools
   ```
2. На гипервизор, а именно с него мы и будем сканировать, установим [nmap](https://nmap.org/).

### Ход выполнения работы

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

   ```
   netstat -lu
   ```

   ```
   netstat -lt
   ```
2. Удаленную проверку доступности открытых портов будем проводить с помощью nmap. Например, для проверки порта UDP 100: или для проверки порта TCP 100:

   ```
   nmap.exe -v 172.30.0.40 -T5 -sU -p100
   ```

   ```
   nmap.exe -v 172.30.0.40 -T5 -sT -p100
   ```
3. Для того чтобы проверить порты, которые никто не слушает, воспользуемся утилитой netcat и откроем их. В частности, для открытия порта UDP 100 выполним следующее заклинание: Здесь netcat ожидает подключение по порту UDP 100, а после подключения выдает в сеть строку «PORT UDP 100 opened» и завершает свою работу, закрывая порт. Если нужно открыть TCP порт 300, то заклинание будет чуть другим:

   ```
   echo "PORT UDP 100 opened" |  nc -lu 100
   ```

   ```
   echo "PORT TCP 300 opened" |  nc -lt 300
   ```
4. Для удобства открытия диапазона портов воспользуемся shell-скриптом openport.sh: *Примечание.* Во время работы скрипт запускает в фоне множество процессов netcat, которые автоматически завершаются после обращения к ним по сети. Рекомендуется за один раз открывать не больше 1000 портов, иначе сервер может потерять стабильность.

   ```
   #!/bin/bash
   # openport.sh

   PROTOCOL=$1
   START_PORT=$2
   END_PORT=$3

   if [[ $# != 3 ]]
   then
       echo -e "Script usage:\nopenport.sh <UDP| TCP> <start port> <end port>"
       exit 1
   fi

   x=$START_PORT

   while [ $x -le $END_PORT ]
   do

   if [ $PROTOCOL == "UDP" ]
   then
       echo "PORT UDP $x opened" |  nc -lu $x &
   elif [ $PROTOCOL == "TCP" ]
   then
       echo "PORT UDP $x opened" |  nc -lt $x &
   else
       echo "ERROR: invalid protocol"
       exit 1
   fi

   echo "Open port $PROTOCOL: $x"

   x=$(( $x + 1 ))
   done
   ```
5. Пример 1. Проведем проверку TCP портов 1-30. На сервере выполняем заклинание: На сканирующей машине запускаем nmap, выполнив заклинание:

   ```
   ./openport.sh TCP 1 30
   ```

   ```
   nmap.exe -v 172.30.0.40 -T5 -sT -p1-30
   ```
6. Пример 2. Проведем проверку TCP портов 20001-20900. На сервере выполняем заклинание: На сканирующей машине запускаем nmap, выполнив заклинание:

   ```
   ./openport.sh UDP 20001 20900
   ```

   ```
   nmap.exe -v 172.30.0.40 -T5 -sU -p20001-20900
   ```
7. Для решения поставленной задачи необходимо последовательно открывать и сканировать все порты из диапазона 1-65535.

## Типовой сценарий: сегментирование локальной сети маршрутизатором с функцией брандмауэра

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

В корпоративных сетях, как правило, выделяют следующие сегменты: сегмент пользовательских рабочих станций, сегмент серверов, сегмент технологического оборудования (например, системы видеонаблюдения), сегмент серверов, имеющих доступ из Интернет (DMZ) и другие сегменты. Каждый сегмент может в свою очередь быть разделен на полсегмента и так далее.

Существует несколько способов сегментации, но наиболее распространенной является сегментация на сетевом уровне (L3), когда каждому сегменту присваивается своя IP-подсеть, а разграничение доступа осуществляется маршрутизаторами с функцией брандмауэра.

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

Важно отметить, что в корпоративных сетях доступы пользователей, как правило, бывают типовыми. Например, все сотрудники отдела продаж должны иметь доступ к Web-интерфейсу системы учета клиентов (customer relation management, CRM). С точки зрения учета и управления доступами можно сказать, что таким пользователям назначается роль «Доступ к Web-интерфейсу CRM». Поэтому очень важно сделать правила фильтрации таким образом, чтобы можно было просто и единообразно добавлять доступы пользователям. Требование единообразия важно еще и потому, что любую систему разграничения доступа нужно периодически проверять, а зоопарк вариантов предоставления доступов серьезно осложнит эту задачу.

Базовая схема разграничения трафика между пользовательским и серверным сегментами будет следующая:

* Из серверного сегмента в пользовательский запрещен весь трафик, кроме:
  * трафика, текущего по уже установленным соединениям.
* Из пользовательского сегмента в серверный запрещен весь трафик, кроме:
  * трафика, текущего по уже установленным соединениям;
  * трафика к явно разрешенным сетевым службам явно разрешенных серверов.

*Примечание 1.* Тут может возникнуть вопрос: почему по умолчанию запрещается весь трафик из серверного сегмента в пользовательский? Дело в том, что в модели «Клиент-Сервер» последний выполняет строго пассивную функцию. Он ждет обращения клиента, обслуживает его, после чего разрывает сетевое соединение. Сам сервер соединяться ни с кем не должен, поэтому ему и блокируется возможность самостоятельной установки соединений. Важно отметить, что в серверном сегменте очень часто размещают технологические рабочие станции, которые периодически опрашивают узлы сети (например, сетевые принтеры на предмет проверки уровня чернил). В таких случаях их либо выносят в отдельный сегмент, либо делают для них исключения в правилах фильтрации.

*Примечание 2.* Большинство серверов enterprise уровня имеют несколько сетевых адаптеров. Например, один рабочий, с помощью которого он обслуживает целевые запросы клиентов, а второй служебный, используемый, например, для систем удаленного управления типа iLO, IPMI, iDrac и др. Поэтому корректнее говорить не «помещение сервера в отдельный сегмент», а «помещение сетевого интерфейса сервера в отдельный сегмент». Нормальной является ситуация, когда все сетевые адаптеры сервера находятся в различных сетевых сегментах.

### Практическая работа: разграничение трафика между пользовательским и серверным сегментами

### Описание стенда

Дан макет корпоративной сети (Рисунок 4), в которой присутствуют две подсети: 192.168.0.0/24 – для пользовательского сегмента и 172.30.0.0/24 — для серверного сегмента. В макете эти две сети представлены виртуальными сетями: «Custom11» и «NAT» соответственно. Виртуальная машина FW снабжена двумя сетевыми интерфейсами, каждый из которых «смотрит» в свою виртуальную сеть. Данная машина выполняет функции маршрутизатора и брандмауэра.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9dea49197756a280faca58df04e182f80f76aed7%2Fije2nuyry0lfpoazhmrt_iphfds.png?alt=media)

*Рисунок 4*

### Задача

Настроить разграничение трафика между пользовательским и серверным сегментами в соответствии со следующей матрицей доступа:

|       | Server1 | Server2 | Виртуальный маршрутизатор |
| ----- | ------- | ------- | ------------------------- |
| User1 | TCP:80  | TCP:80  | UDP:53                    |
| User2 | TCP:22  | TCP:22  | UDP:53                    |

### Подготовка стенда

1. Средствами системы виртуализации создадим отдельную виртуальную сеть (VMnet), которую назовем «Custom11».
2. Ранее созданный шаблон виртуальной машины растиражируем в 5 отдельных экземпляров: FW, User1, User2, Server1, Server2.
3. К виртуальной машине FW добавим второй виртуальный сетевой адаптер, который соединим с сетью «Custom11». Ее первый сетевой адаптер должен быть подключен к виртуальной сети «NAT».
4. Сетевые адаптеры машин User1 и User2 подключим к «Custom11», а сетевые адаптеры Server1, Server2 подключим к «NAT».
5. Редактируя файлы /etc/network/interfaces, назначим всем виртуальным машинам статические IP-адреса, а также шлюзы (GW) и DNS-сервера в соответствии со схемой лабораторного стенда. В качестве шлюзов по умолчанию для Host1 и Host2 будет 192.168.0.40, для Server1 и Server2 172.30.0.40, а для FW 172.30.0.2
6. На виртуальной машине FW активируем функции маршрутизации IPv4 трафика. Для этого в файле /etc/sysctl.conf раскомментируем строку net.ipv4.ip\_forward=1. Сделать это можно с помощью заклинания:

   ```
   sed '/net.ipv4.ip_forward=1/s/^#//' -i /etc/sysctl.conf
   ```
7. Проверим стенд. Host1 и Host2 должны иметь возможность «пингануть» Server1, Server2 и FW и наоборот. FW должен иметь возможность «пингануть» любой из узлов сети.

### Ход выполнения работы

1. Полную настройку брандмауэра мы уже рассматривали в предыдущей задаче. Поэтому здесь мы направим внимание на конфигурационный файл /etc/nftables.conf. Причем мы также опустим вопросы защиты самого FW, поскольку они будут точно такими же, как и в предыдущей задаче, и сосредоточимся лишь на фильтрации транзитного трафика.
2. Анализируя матрицу доступа можно предположить, что User1 является рядовым работником, и ему требуется доступ по http ко всем серверам. Назовем такой доступ ролью «all\_web». Для User2 требуется предоставить доступ ко всем серверам по SSH, вероятно он системный администратор. Обозначим такой доступ ролью «all\_ssh». Обоим пользователям требуется доступ к DNS-серверу — это будет роль «DNS».
3. Наиболее простым способом создать в nftables ролевую систему разграничения доступа будет применение правил, где в качестве аргументов используются списки (sets), содержащие конкретные параметры предоставления доступа. В нашем случае для каждой роли нужно создать два списка: *наименование роли\_users* и *наименование роли\_servers*. В первый список будут добавляться IP-адреса пользователей, во второй — IP-адреса серверов, а также протоколы и доступные порты.
4. После создания списков для каждой роли создадим правило, разрешающее установку соединений *ip saddr @название роли\_users ip daddr. meta l4proto. th dport @название роли\_servers accept*. Трафик по инициированным соединения, как обычно, будет проходить с помощью правила *ct state established,related accept*.
5. Итоговый конфигурационный файл (/etc/nftables.conf), решающий поставленную задачу будет выглядеть следующим образом:

```

#!/usr/sbin/nft -f

flush ruleset

table ip firewall {

        set all_web_users {
                type ipv4_addr
                flags interval
                elements = { 192.168.0.101 }
        }

        set all_web_servers {
                type ipv4_addr . inet_proto . inet_service
                flags interval
                elements = { 172.30.0.101-172.30.0.102 . tcp . 80 }
        }

        set all_ssh_users {
                type ipv4_addr
                flags interval
                elements = { 192.168.0.102 }
        }

        set all_ssh_servers {
                type ipv4_addr . inet_proto . inet_service
                flags interval
                elements = { 172.30.0.101-172.30.0.102 . tcp . 22 }
        }

        set DNS_users {
                type ipv4_addr
                flags interval
                elements = { 192.168.0.101, 192.168.0.102 }
        }

        set DNS_servers {
                type ipv4_addr . inet_proto . inet_service
                flags interval
                elements = { 172.30.0.2 . udp . 53 }
        }

        chain fw_forward {
                 type filter hook forward priority filter; policy drop;
                 ct state established,related accept

                 ip saddr @all_web_users ip daddr . meta l4proto . th dport @all_web_servers accept
                 ip saddr @all_ssh_users ip daddr . meta l4proto . th dport @all_ssh_servers accept
                 ip saddr @DNS_users ip daddr . meta l4proto . th dport @DNS_servers accept
        }
}
```

*Примечание.* После настройки всех правил вы столкнетесь с одной проблемой: DNS на User1 и User2 работать не будет. Это не баг, это методическая фича. Проблема в том, что DNS-сервер работает на виртуальном маршрутизаторе, который ничего не знает о пользовательской сети 192.168.0.0/24, из-за этого ответы на запросы не доходят до адресатов. Этой проблемой хотелось показать реальные сложности, возникающие при сегментации уже работающих сетей с помощью разделения их на IP-подсети. На предприятиях со сложившейся инфраструктурой, но низким уровнем зрелости IT внедрить подобные меры защиты крайне сложно, а учитывая user resistance, практически невозможно. Но проблема все же имеет решение, и мы поговорим о нем прямо сейчас.

## Типовой сценарий: сегментирование локальной сети коммутатором с функцией брандмауэра

Данный подход к межсетевому экранированию имеет множество названий: коммутатор с функцией брандмауэра, «stealth firewall», «transparent firewall» и др. Суть, однако, заключается в том, что сегментируемая сеть сохраняет свою IP-адресацию, а разделение происходит путем установки коммутатора в разрыв между будущими сегментами. Причем коммутатор фильтрует трафик как обычный межсетевой экран — на основании данных со всех инкапсулированных протоколов (L2, L3, L4, L7), а не только данных с протоколов канального уровня (L2), как в случае с обычным коммутатором.

Сетевые интерфейсы коммутатора не имеют IP-адресов (за исключением тех, что используются для управления). Соответственно, данное устройство не видимо для других сетевых узлов (за исключением случаев, когда используются специфические протоколы, например OSPF). Поэтому коммутатор с функцией брандмауэра часто называют прозрачный межсетевой экран — stealth firewall.

Применение фильтрующих коммутаторов имеет несколько неоспоримых преимуществ:

1. Внедрение устройства не требует выделения IP-подсетей и, как следствие, перенастройки таблиц маршрутизации роутеров и сетевых стеков узлов.
2. Устройство очень просто внедрить и очень просто изъять из сети в случае его поломки.

Основной недостаток данной технологии, как ни странно, является прямым следствием ее основного достоинства: сегментирование сети без разделения ее на IP-подсети не позволяет ограничивать широковещательный трафик, что негативным образом сказывается на производительности сетей и усиливает негативные последствия от атак типа широковещательный шторм (broadcast flood), отравления кэша ARP (ARP-poisoning), подмены DHCP (Rogue DHCP Server) и других.

### Практическая работа: провести сегментирование сети без разделения ее на IP-подсети

### Описание стенда

Используемый лабораторный стенд (Рисунок 5) очень похож на предыдущий, за исключением того, что все машины здесь находятся в одной IP-подсети. Для того, чтобы машины UserX и ServerX не могли связаться между собой напрямую, а использовали для связи FW, их разделили по виртуальным сетям «Custom11» и «NAT».

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-44ff2746f4cf004fa530e0ae5b01795ae6e1dc3b%2F8fraoj45smfbnxri-dtlnkcrckg.png?alt=media)

*Рисунок 5*

### Задача

Настроить разграничение трафика в соответствии с матрицей доступа из предыдущей задачи.

### Подготовка стенда

1. Внося изменения в файлы /etc/network/interfaces, назначим статические адреса всем узлам сети.
2. Для организации работы FW в режиме коммутатора установим на него пакет bridge-utils:

   ```
   pt install bridge-utils
   ```
3. Затем на узле FW сконфигурируем коммутатор, объединив в мост (bridge) все сетевые порты. Для этого файл /etc/network/interfaces заполним следующим образом:

   ```
   auto lo
   iface lo inet loopback

   iface ens33 inet static

   iface ens36 inet static

   auto br0
   iface br0 inet manual
   bridge_ports ens33 ens36
   ```
4. Проверим стенд. Все узлы, кроме FW, должны «пинговаться» между собой. DNS, кстати, тоже должен работать.

### Ход выполнения работы

1. Как и в предыдущей задаче рассмотрим только файл с правилами межсетевого экранирования /etc/nftables.conf. Скорее всего при беглом осмотре вы даже не заметите в нем различий по сравнению с таким же файлом из предыдущей задачи. Основные отличия в том, что тип таблицы firewall изменился с *ip* на *bridge*, поменялись адреса в списках \**\_users* (так как изменились соответствующие адреса машин), и к правилам фильтрации мы добавили разрешение прохождения ARP трафика.

   ```
   #!/usr/sbin/nft -f

   flush ruleset

   table bridge firewall {

           set all_web_users {
                   type ipv4_addr
                   flags interval
                   elements = { 172.30.0.11 }
           }

           set all_web_servers {
                   type ipv4_addr . inet_proto . inet_service
                   flags interval
                   elements = { 172.30.0.101-172.30.0.102 . tcp . 80 }
           }

           set all_ssh_users {
                   type ipv4_addr
                   flags interval
                   elements = { 172.30.0.12 }
           }

           set all_ssh_servers {
                   type ipv4_addr . inet_proto . inet_service
                   flags interval
                   elements = { 172.30.0.101-172.30.0.102 . tcp . 22 }
           }

           set DNS_users {
                   type ipv4_addr
                   flags interval
                   elements = { 172.30.0.11, 172.30.0.12 }
           }

           set DNS_servers {
                   type ipv4_addr . inet_proto . inet_service
                   flags interval
                   elements = { 172.30.0.2 . udp . 53 }
           }

           chain fw_forward {
                    type filter hook forward priority filter; policy drop

                    ether type arp accept
                    ct state established,related accept

                    ip saddr @all_web_users ip daddr . meta l4proto . th dport @all_web_servers accept
                    ip saddr @all_ssh_users ip daddr . meta l4proto . th dport @all_ssh_servers accept
                    ip saddr @DNS_users ip daddr . meta l4proto . th dport @DNS_servers accept

           }
   }
   ```

## Проект OneButtonFirewall

Можно ли сделать межсетевой экран, не требующий конфигурирования? Если можно, то что он будет уметь? Давайте разбираться.

Мы с вами рассмотрели несколько схем разграничения трафика. Есть ли среди них та, что не требует конфигурирования, то есть указания IP-адресов или портов, на основании которых производится фильтрация трафика? Конечно, есть, и эта схема первая в списке.

Теперь возникает второй вопрос: рассмотренная схема относится к защите рабочих станций, как с ее помощью построить межсетевой экран для защиты сегмента сети?

Тут тоже нет ничего сложного. Один сегмент сети, подключенный к брандмауэру, будем считать внутренним (защищаемым), а второй внешним (от которого защищаем). Тогда правила фильтрации преобразуются в следующие:

* Запрещен транзит трафика из внешнего сегмента во внутренний, кроме того, что относится к уже установленным соединениям.
* Разрешен транзит любого трафика из внутреннего сегмента во внешний сегмент.

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

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

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

Осталась одна проблема – широковещательный трафик. Раньше, когда мы рассматривали фильтрацию с помощью коммутатора, мы не обращали на него внимание, и по факту он был запрещен, кроме разве что ARP. Это, конечно, безопасно, но для универсального межсетевого экрана не подходит, поскольку ломает работу множества протоколов, базирующихся на широковещательных посылках, и первыми такими протоколами будут протоколы ОС Windows, отвечающие за отрисовку сетевого окружения. В результате тетенька бухгалтерша, сидящая за подобным брандмауэром, издаст злобный писк, что пропали все ее сетевые папки, и что вы вообще бесполезный вредитель. Нам такого не надо, поэтому, скрипя сердцем, разрешим транзит всего широковещательного трафика.

Ну, вроде бы все учли… ан нет. В ходе эксплуатации OneButtonFirewall всплыла проблема, откуда не ждали – получение IP-адреса от DHCP-сервера, находящегося во внешнем сегменте. ОС Windows получает IP-адреса по DHCP сугубо на широковещательных рассылках, и текущих правил фильтрации ей полностью хватает. Linux же идет своим путем. При получении адреса на конечном этапе DHCP-сервер посылает клиенту адресный (unicast) пакет по UDP 68, и, чтобы тот достиг адресата, в правилах фильтрации нужно сделать соответствующее исключение.

### Практическая работа: защитить сегмент сети с помощью межсетевого экрана, построенного по технологии OneButtonFirewall

### Описание стенда

Давайте теперь соберем лабораторный стенд (Рисунок 6) и отработаем на нем наши идеи. Тут мы даже немного усложним задачу. У FW будет не два интерфейса: один внутренний и один внешний, а четыре: один внешний и три внутренних. Все узлы, подключенные к внутренним интерфейсам, будем считать внутренним сегментом и фильтровать трафик между этими узлами не будем. Как вы увидите дальше, количество внутренних интерфейсов не имеет значения, но внешний может быть только один.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-15ff9e079998a8b28f34c9e019f389f9029f7357%2F77qiroisnd6xkeqpzqmxe8ff7vs.png?alt=media)

*Рисунок 6*

В этом примере для наглядности мы моделируем классическую корпоративную сеть на базе ОС Windows. Host1 – это Windows 10, подключенный к домену Active Directory, контроллер которого (ADDC) расположен во внешней сети. Host2 – Windows 10, не подключенная к домену, а Host3 – наша шаблонная машина на базе Debian. Все HostX получают IP-адреса по DHCP от виртуального маршрутизатора.

Для моделирования наших идей с помощью средств виртуализации каждый узел внутренней сети подключим через отдельную виртуальную сеть к FW. Как и прошлый раз мы делаем это для того, чтобы весь трафик между узлами HostX и узлами внешней сети шел через FW.

### Задача

Защитить узлы HosX от несанкционированных подключений.

### Подготовка стенда

1. На узле FW установим 4 сетевых интерфейса. Для наглядности с помощью средств виртуализации изменим на них MAC-адреса (Рисунок 7): *Рисунок 7*

   ![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-502bca2cbbd04e8f22b9ee447c332428c3c7ba34%2F002dunnsiqfvlajpmhnop3cynfs.png?alt=media)
2. Один из интерфейсов с MAC-адресом 00:11:11:11:11:00, подключенный к «NAT», переименуем в «external» (Рисунок 7). Для этого создадим файл /etc/systemd/network/10-set-external-name.link и заполним его следующим содержанием:

   ```
   # /etc/systemd/network/10-set-internal1-name.link
   [Match]
   MACAddress=00:11:11:11:11:00

   [Link]
   Name=external
   ```
3. Как и при решении прошлой задачи установим на узел FW пакет bridge-utils.
4. Сделаем из FW коммутатор. Для этого в файл /etc/network/interfaces запишем следующее содержание:

   ```

   auto lo
   iface lo inet loopback

   iface external  inet manual

   iface ens36 inet manual

   iface ens37 inet manual

   iface ens38 inet manual

   auto br0
   iface br0 inet manual
   bridge_ports external ens36 ens37 ens38
   ```
5. Проверим работоспособность сети, «пингуя» все узлы. Для проведения тестирования не забудьте отключить встроенный межсетевой экран на Windows машинах. Все узлы должны «пинговаться».

### Ход выполнения работы

1. Для решения поставленной задачи заполним файл /etc/nftables.conf следующим образом:

```
#!/usr/sbin/nft -f

flush ruleset

table bridge firewall {

	chain fw_forward {
		type filter hook forward priority filter; policy drop;

# Разрешаем весь трафик между внутренними портами и трафик
# от внутренних портов к внешнему
		iifname != "external" accept

# Разрешаем IPv4 трафик по уже установленным соединениям
		ether type ip ct state established,related accept

# Разрешаем весь IPv4 трафик с широковещательными MAC адресами
		ether type ip ether daddr ff:ff:ff:ff:ff:ff accept

# Разрешаем трафик из внешней сети во внутреннюю по UDP 68
# Костыль для того, чтобы внутренние узлы могли получить адрес по DHCP из внешней сети
               udp dport 68 accept
	}
}
```

### Реализация OneButtonFirewall в виде аппаратного устройства

Идея OneButtonFirewall наиболее полным образом раскрывает себя в виде аппаратного устройства. Реализовать ее в виде виртуальных машин или контейнеров, конечно, тоже можно, но особого профита это не даст. Аппаратное же устройство дает эффект своеобразного кирпичика, который можно подключить к сети, и он ее защищает. Отсюда, кстати, и название пошло OneButtonFirewall, так как подобному устройству нужна только одна кнопка: включение питания. Несомненным плюсом устройства является его неконфигурируемость. Оно реализует жесткий алгоритм, не требующий дополнительных настроек. Как следствие, не нужно заботиться о целостности конфигурации или продумывать интерфейсы администрирования и обеспечивать их защиту.

Построить OneButtonFirewall очень просто. Достаточно найти подходящую аппаратную платформу установить туда дистрибутив Linux, в котором есть поддержка nftables, и провести настройку из примера выше. Например, OneButtonFirewall, построенный на базе аппаратной платформы MikroTik, выглядит следующим образом (Рисунок 8):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b8dbc0c01c9e738e4b49d5d830a34aa58dcbbf7a%2Fphzbdkoc2lowq1dbrmysexmcxwy.jpeg?alt=media)

*Рисунок 8*

### Преимущества и недостатки OneButtonFirewall

Преимуществами аппаратных устройств,, реализующих технологию OneButtonFirewall, по сравнению с другими межсетевыми экранами являются:

1. Простота и скорость развертывания и изъятия системы защиты.
2. Защищенность конфигурации от изменения.
3. Минимальные требования к квалификации персонала.

Недостатки технологии:

1. Устройство реализует жесткий алгоритм фильтрации, который решает далеко не все задачи межсетевого экранирования.
2. Устройство не фильтрует широковещательный трафик и трафик по порту UDP 68 из внешней сети во внутреннюю.

### Сценарии применения

Рассмотренные преимущества и недостатки определяют основную нишу применения OneButtoFirewall сценариями, в которых нужно просто и быстро реализовать межсетевое экранирование, а применение классических межсетевых экранов натыкается на недостаточную квалификацию / саботаж / лень / перегруженность работников IT.

Рассмотрим теперь типовые сценарии применения OneButtonFirewall:

1. Защита сети отдела компании, например, бухгалтерии или службы безопасности. Узлы защищаемого отдела подключаются к внутренним портам межсетевого экрана, а остальная сеть к внешним. Внутренние узлы работают «как обычно», а из внешней сети подключиться к ним нельзя.
2. Защита сети компании от компьютеров подрядчиков (например, аудиторов). Тут обратная схема. Узлы компьютеров подрядчиков через коммутатор подключаются к внешнему порту межсетевого экрана, корпоративная сеть подключается к внутреннему порту. Из корпоративной сети можно подключится к компьютерам подрядчиков, а вот в обратную сторону нет.
3. Элемент дежурной аптечки системных администраторов. OneButtonFirewall наряду с антивирусным LiveUSB может существенно помочь в случае массового заражения сети компьютерными вирусами. С его помощью можно безопасно переустановить и обновить операционную систему компьютеров, в случаях когда заражение происходит еще до того, как стартует штатный межсетевой экран операционной системы.

## Заключение

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

##


# Infrastructure As a Code

[Ansible](/readme/architect/infrastructure-as-a-code/ansible)

[IaC Packer Ansible Teraform](/readme/architect/infrastructure-as-a-code/iac-packer-ansible-teraform)

[Installing Jenkins using terraform in Kubernetes in Yandex Cloud with letsencypt](/readme/architect/infrastructure-as-a-code/installing-jenkins-using-terraform-in-kubernetes-i)

[Teraform Crosplan Pulumi](/readme/architect/infrastructure-as-a-code/teraform-crosplan-pulumi)

[Yandex IaC solutions](/readme/architect/infrastructure-as-a-code/yandex-iac-solutions)


# Ansible

## Ansible

<https://github.com/ansible-community/awesome-ansible>

[awesome-ansible](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Infrastructure%20As%20a%20Code/Ansible/Ansible/awesome-ansible/README.md)

## Awesome Ansible

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2f25933529c35064b2d70384773f0b36b3cb4ac4%2Fansible_logo.svg?alt=media)

> A collaborative curated list of awesome Ansible resources, tools, Roles, tutorials and other related stuff.

[Ansible](https://www.ansible.com/) is an open source toolkit, written in Python, it is used for configuration management, application deployment, continuous delivery, IT infrastructure automation and automation in general.

### Contents

* [Official resources](https://github.com/ansible-community/awesome-ansible#official-resources)
* [Community](https://github.com/ansible-community/awesome-ansible#community)
* [Tutorials](https://github.com/ansible-community/awesome-ansible#tutorials)
* [Books](https://github.com/ansible-community/awesome-ansible#books)
* [Videos](https://github.com/ansible-community/awesome-ansible#videos)
* [Tools](https://github.com/ansible-community/awesome-ansible#tools)
* [Blog posts and opinions](https://github.com/ansible-community/awesome-ansible#blog-posts-and-opinions)
* [Playbooks, Roles and Collections](https://github.com/ansible-community/awesome-ansible#playbooks-roles-and-collections)
* [Editor and IDE Integrations](https://github.com/ansible-community/awesome-ansible#editor-and-ide-integrations)

### Official resources

> Official resources by and for Ansible.

* [Latest Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/index.html) - Latest user guide and documentation for Ansible.
* [Ansible Galaxy Website](https://galaxy.ansible.com/) - Official repository and community site for Ansible Roles.
* [Ansible Blog](https://www.ansible.com/blog) - Official Ansible blog.

### Community

> Places where to chat with the Ansible community

* About code - [GitHub.com/ansible](https://github.com/ansible), [GitHub.com/ansible-collections](https://github.com/ansible-collections) and [GitHub.com/ansible-community](https://github.com/ansible-community).
* [reddit.com/r/ansible](https://old.reddit.com/r/ansible/) - The Ansible subreddit.
* [Discord](https://old.reddit.com/r/ansible/comments/jv5shj/ansible_discord_server_come_get_ansible_help_in/) - The Ansible discord.
* [ansible.com/community](https://ansible.com/community) - Twitter, mailing lists, meetups and more.

There are also many Ansible IRC channels on [libera.chat](https://libera.chat/) that are bridged to [Matrix](https://matrix.org/). You can find the full list and how to connect in the official documentation [documentation](https://docs.ansible.com/ansible/latest/community/communication.html) but here's a few:

| IRC                | Matrix                 | Topic                                                                            |
| ------------------ | ---------------------- | -------------------------------------------------------------------------------- |
| #ansible           | #users:ansible.com     | General Ansible user support and discussion                                      |
| #ansible-devel     | #devel:ansible.com     | Developer discussions around code, bugs and features                             |
| #ansible-community | #community:ansible.com | Community working group, wide range of topics including weekly meetings          |
| #ansible-docs      | #docs:ansible.com      | Documentation working group, discuss docs and participate in weekly meetings     |
| #ansible-devtools  | #devtools:ansible.com  | For devtools such as ansible-lint, molecule and the vscode plugin                |
| #ansible-awx       | #awx:ansible.com       | For the AWX open source project, upstream of Ansible Tower/Automation controller |
| #ansible-network   | #network:ansible.com   | For general support and discussion around network automation with Ansible        |
| #ansible-fr        | #francais:ansible.com  | For discussion about Ansible in french                                           |

### Tutorials

> Tutorials and courses to learn Ansible.

* [How To Manage Remote Servers with Ansible](https://www.digitalocean.com/community/tutorial_series/how-to-manage-remote-servers-with-ansible) - This Tutorial goes over how to use Ansible to manage remote servers.
* [Ansible Tutorial by leucos](https://github.com/leucos/ansible-tuto) - 12 Step Tutorial for Ansible.
* [Programming Community Curated Resources for learning Ansible](https://hackr.io/tutorials/learn-ansible) - A list of recommended resources.
* [Ansible TopTechSkills.com Tutorial Series on Ansible](https://www.toptechskills.com/ansible-tutorials-courses/) - Tutorials on how to Install and use Ansible.
* [Official Ansible labs by Red Hat](https://ansible.github.io/workshops/exercises/ansible_rhel/) - Training Course for Ansible Automation Platform.
* [Ansible Tutorials on DigitalOcean](https://www.digitalocean.com/community/tags/ansible?subtype=tutorial) - Basic tutorials on DigitalOcean.com.
* [Ansible Tutorial by BlueBanquise team](http://bluebanquise.com/documentation/releases/1.5.0/training_ansible.html) - Basic Ansible tutorial.
* [Ansible Tutorial for Beginners: Playbook & Examples](https://spacelift.io/blog/ansible-tutorial) - Introduction to Ansible for beginners.
* [Ansible Tutorials for Beginners and Advanced](https://ansible.puzzle.ch/) - Workshop on multiple topics with different levels of difficulty.

### Books

> Books about Ansible.

* [Ansible for DevOps](https://www.ansiblefordevops.com/) - This book helps to start using Ansible to provision and manage anywhere from one to thousands of servers. Free sample can be read [here](https://leanpub.com/ansible-for-devops/read_sample).
* [Ansible for Kubernetes](https://www.ansibleforkubernetes.com/) - Deploy and maintain real-world massively-scalable and high-available applications with Ansible.
* [How To Manage Remote Servers with Ansible eBook](https://www.digitalocean.com/community/books/how-to-manage-remote-servers-with-ansible-ebook) - This book is based on the "How To Manage Remote Servers with Ansible" tutorial series.

### Videos

> Video tutorials and Ansible training.

* [Ansible YouTube Channel](https://www.youtube.com/channel/UCPJo5UY1KsP7J1BuHmiWNzQ) - Official Ansible YouTube channel.
* [Introduction to Ansible](https://youtu.be/iVWmbStE1MM) - Introduction to Ansible by Cloud Academy.
* [Ansible 101 by Jeff Geerling](https://www.jeffgeerling.com/blog/2020/ansible-101-jeff-geerling-youtube-streaming-series) - Great video series on Ansible, by Jeff Geerling.
* [Ansible TopTechSkills.com Tutorial Series on YouTube](https://www.youtube.com/playlist?list=PLMyOob-UkeytIleCbMlFfCzaunOh27hm6) - Video tutorials on Ansible.
* [Ansible Essentials - Course](https://www.redhat.com/en/services/training/do007-ansible-essentials-simplicity-automation-technical-overview) - Free Video Classroom on Ansible essentials by Red Hat.
* [Complete Ansible Course 2020 by DevOps Journey](https://www.youtube.com/watch?v=KuiAiUyuDY4\&list=PLnFWJCugpwfzTlIJ-JtuATD2MBBD7_m3u\&index=1) - Free Video Course on Ansible including labs to follow along.
* [Getting started with Ansible](https://youtube.com/playlist?list=PLT98CRl2KxKEUHie1m24-wkyHpEsa4Y70) - YouTube tutorial series by LearnLinuxTV.

### Tools

> Tools for and using Ansible.

* [Automation Controller](https://www.ansible.com/products/controller) - Automation Controller (formerly Ansible Tower) by Red Hat helps you scale IT automation, manage complex deployments and speed productivity. Extend the power of Ansible to your entire team.
* [AWX](https://github.com/ansible/awx) - AWX provides a web-based user interface, REST API, and task engine built on top of Ansible. It is the upstream project for Automation Controller, a commercial derivative of AWX.
* [Ansible Lint](https://github.com/ansible/ansible-lint) - Checks Playbooks for best practices and behavior that could potentially be improved.
* [Ansible Later](https://github.com/thegeeklab/ansible-later) - Another best practice scanner. Checks Playbooks and Roles for best practices and behavior that could potentially be improved.
* [Ansible Doctor](https://github.com/thegeeklab/ansible-doctor) - Simple annotation like documentation generator for Ansible roles based on Jinja2 templates.
* [Ansible cmdb](https://github.com/fboender/ansible-cmdb) - Takes the output of Ansible's fact gathering and converts it into a static HTML page.
* [ARA](https://github.com/ansible-community/ara) - ARA Records Ansible playbooks and makes them easier to understand and troubleshoot with a reporting API, UI and CLI.
* [Mitogen for Ansible](https://mitogen.networkgenomics.com/ansible_detailed.html) - Speed up Ansible substantially with Mitogen.
* [Molecule](https://molecule.readthedocs.io/en/latest/) - Molecule aids in the development and testing of Ansible roles.
* [Packer Ansible Provisioner](https://www.packer.io/plugins/provisioners/ansible/ansible-local) - This Provisioner can be used to automate VM Image creation via Packer with Ansible.
* [Excel Ansible Inventory](https://github.com/KeyboardInterrupt/ansible_xlsx_inventory) - Turn any Excel Spreadsheet into an Ansible Inventory.
* [terraform.py](https://github.com/mantl/terraform.py) - Ansible dynamic inventory script for parsing Terraform state files.
* [ansible-navigator](https://github.com/ansible/ansible-navigator) - A text-based user interface (TUI) for Ansible.
* [squest](https://hewlettpackard.github.io/squest/) - Self-service portal for Automation Controller job templates.
* [ansible-bender](https://ansible-community.github.io/ansible-bender/build/html/index.html) - Tool which bends containers using Ansible playbooks and turns them into container images.
* [ansible-runner](https://github.com/ansible/ansible-runner) - A tool and python library that helps when interfacing with Ansible directly or as part of another system whether that be through a container image interface, as a standalone tool, or as a Python module that can be imported.
* [ansible-builder](https://ansible-builder.readthedocs.io/en/latest/) - Using Ansible content that depends on non-default dependencies can be tricky. Packages must be installed on each node, play nicely with other software installed on the host system, and be kept in sync.
* [kics](https://github.com/Checkmarx/kics) - SAST Tool that scans your ansible infrastructure as code playbooks for security vulnverables, compliance issues and misconfigurations.
* [php-ansible Library](https://github.com/maschmann/php-ansible) - OOP-Wrapper for Ansible, making Ansible available in PHP.
* [TD4A](https://github.com/cidrblock/td4a) - Design aid for building and testing jinja2 templates, combines data in yaml format with a jinja2 template and render the output.
* [Ansible Playbook Grapher](https://github.com/haidaraM/ansible-playbook-grapher) - Command line tool to create a graph representing your Ansible playbook plays, tasks and roles.
* [ansible-doc-extractor](https://github.com/xlab-steampunk/ansible-doc-extractor) - A tool that extracts documentation from Ansible modules in the HTML form.
* [Ansible Semaphore](https://github.com/ansible-semaphore/semaphore) - Ansible Semaphore is a modern UI for Ansible.
* [Steampunk Spotter](https://steampunk.si/spotter/) - Provides an Assisted Automation Writing tool that analyzes and offers recommendations for your Ansible Playbooks.
* [ansible-roster](https://gitlab.com/jlecomte/ansible/ansible-roster) - Ansible Roster inventory plugin to generate inventory from a host oriented yaml file. Supports ranges, regex hostnames, file inclusions, and variable merging.
* [Monkeyble](https://hewlettpackard.github.io/monkeyble/) - A callback plugin that allow to execute end-to-end tests on playbooks with a Pythonic testing and CI/CD approach to detect regressions.

### Blog posts and opinions

> Best practices and other opinions on Ansible.

* [Ansible (Real Life) Good Practices](https://reinteractive.com/posts/167-ansible-real-life-good-practices) - Best practice guidelines.
* [Testing Ansible Roles Against Windows with Test-Kitchen](https://hodgkins.io/testing-ansible-roles-windows-test-kitchen) - Using Test-Kitchen with Ansible to apply playbooks to Windows machines and test them with [Pester](https://github.com/pester/Pester/).
* [Ansible Best Practices by AndiDog](https://andidog.de/blog/2017-04-24-ansible-best-practices) - Practices covering many aspects of an Ansible setup, including hints to support different environments (testing, staging, production).
* [Getting started with Ansible](https://steampunk.si/blog/getting-started-with-ansible/) - Introduces Ansible, provides installation instructions and gives an interactive walkthrough of Ansible's basic functionalities, like running Ansible playbooks and installing Ansible content.
* [Taking Ansible apart](https://steampunk.si/blog/taking-ansible-apart/) - Describes and shows how most commonly used Ansible components work.

#### German

* [Ansible – Was ich am Ad-hoc-Modus schätze](https://www.my-it-brain.de/wordpress/ansible-was-ich-am-ad-hoc-modus-schaetze/) - Opinion what the author likes about the Ansible Ad-Hoc mode.

#### French

* [Apprendre et Maitriser Ansible l'outil de gestion de configuration](https://blog.stephane-robert.info/post/introduction-ansible/) - A large of courses on Ansible in French.

### Playbooks, Roles and Collections

> Awesome production ready Playbooks, Roles and Collections to get you up and running.

* [Ansible Vagrant Examples by geerlingguy](https://github.com/geerlingguy/ansible-vagrant-examples) - Ansible examples using Vagrant to deploy to local VMs.
* [Ansible playbook for Linux machine setup](https://github.com/olivomarco/my-ansible-linux-setup) - Ansible playbook for setting up a self-updating, hardened Debian/Ubuntu machine with Docker daemon.
* [DevSec Hardening Framework](https://dev-sec.io/) - The DevSec collection helps you harden your Linux Based OS as well as MySQL, NGINX and SSH Server/Services.
* [T.A.D.S. boilerplate](https://github.com/Thomvaill/tads-boilerplate) - Provision and deploy a Docker Swarm cluster to development environment and to production. Infrastructure as Code and DevOps best practices.
* [Openstack Ansible](https://github.com/openstack/openstack-ansible) - Ansible Playbooks for deploying [OpenStack](https://www.openstack.org/).
* [Robert de Bock](https://robertdebock.nl/) - A extensive collection of Ansible roles.
* [DebOps](https://docs.debops.org/en/master/) - A extensive collection of Debian based Ansible Playbooks.
* [ansible-ssm](https://github.com/HQarroum/ansible-ssm) - An ansible role to provision physical and virtual hosts with the AWS SSM agent.
* [BlueBanquise](https://github.com/bluebanquise/bluebanquise) - An ansible coherent roles collection to deploy clusters.
* [redhat-cop](https://github.com/search?q=topic%3Aansible+org%3Aredhat-cop\&type=Repositories\&s=updated\&o=desc) - Repositories with ansible topic of the Red Hat Communities of Practice project.

### Editor and IDE Integrations

> Awesome Integrations into Text Editors and IDE's to make development with/for Ansible easier.

* [Ansible Language Server](https://github.com/ansible/ansible-language-server) - Language Server that adds support for Ansible, to compatible Editors.
* [Emacs - Ansible client for Language Server Protocol](https://emacs-lsp.github.io/lsp-mode/page/lsp-ansible/) - Emacs support for Ansible Language Server Protocol.
* [VS Code - official Ansible Extension](https://marketplace.visualstudio.com/items?itemName=redhat.ansible) - Adds language support for Ansible to Visual Studio Code and OpenVSX compatible editors by leveraging ansible-language-server.


# IaC Packer Ansible Teraform

<https://habr.com/ru/companies/vk/articles/674842/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-afcda81004a18b30b6ec19a29b9a63ab84d7baf0%2Fstj3cdteidmqyacirekvqzuvfxq.png?alt=media)

Привет, Хабр! Я Алексей Волков, менеджер продукта компании [VK Cloud Solutions](https://mcs.mail.ru/). Хочу рассказать о подходе IaC (Infrastructure as Code, инфраструктура как код), который позволяет управлять сетями, виртуальными машинами, подсистемами балансировки нагрузки и другими элементами инфраструктуры как кодом с помощью описательной модели. Поговорим о современных принципах управления инфраструктурой, инструментах IaC в облаке и вариантах построения CI/CD-пайплайна.

## Традиционный процесс администрирования vs IaC

Чтобы лучше понять суть IaC, удобнее всего сравнить его с традиционным подходом к администрированию инфраструктуры.

**При традиционном подходе:**

1. Устанавливают операционную систему (ОС).
2. Добавляют пользователей.
3. Ставят нужные пакеты.
4. Вписывают конфигурации.
5. Запускают приложение.

Если нужно расширить функциональность образа или перенастроить его, повторяют пункты 3–5. Такой подход еще называют Configuration Drain. Недостаток в том, что при неоднократном обновлении инстанса практически невозможно понять, какая версия пакетов установлена и какие настройки активны. Это нередко приводит к тому, что последующее обновление может не запуститься или сломать всю инфраструктуру.

**При IaC-подходе алгоритм отличается:**

1. Готовят шаблон сервера со всеми настройками и пакетами.
2. Описывают в тексте инфраструктуру с указанием сетей, серверов, прав доступа и других параметров.
3. Применяют изменения.

В итоге получается «золотой образ», готовый к использованию. Как правило, перед запуском в продакшене его проверяют на стейдже — если он корректно работает во время теста, то и в реальных условиях проблем не будет.

«Золотой образ» можно использовать и в качестве шаблона — например, если нужно внести дополнительные изменения поверх уже активных настроек.

IaC-подход к администрированию повышает предсказуемость системы, ее воспроизводимость и контролируемость. Кроме того, он позволяет быстро исправлять возникающие ошибки: в любой момент можно перезапустить инстанс с корректными настройками из чистого образа. Одновременно с этим можно управлять инфраструктурой как кодом: версионировать, использовать возможности Git для контроля изменений, указывать этапы выкатки и другие нюансы.

На практике для реализации IaC-подхода к администрированию используют разные инструменты. Рассмотрим Packer и Terraform от компании HashiCorp.

## Packer

Packer — инструмент для создания одинаковых образов ОС для различных платформ из одного описания. Образ, создаваемый Packer, включает в себя настроенную операционную систему (ОС) и набор программного обеспечения (ПО). Packer умеет создавать образы AMI для EC2, VMDK/VMX-файлы для VMware, OVF для VirtualBox и другие.

Packer не зависит от облачной инфраструктуры — он поддерживает несколько бэкендов и может работать с разными провайдерами «из коробки».

**Алгоритм работы с Packer следующий:**

1. Создаем и описываем файл конфигурации базовой виртуальной машины.
2. Запускаем Packer, который создает виртуальную машину.
3. Обращаемся в провижинер, например Ansible, для провижинга виртуальной машины.
4. После провижинга приводим виртуальную машину к нужному состоянию, со всеми настройками и наборами ролей.
5. Packer идет в облачное хранилище в бэкенде и делает снапшот виртуальной машины, который потом загружает в облако в виде образа.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c77abd47bfd76deac7c97558fa00e2b1ad0e2d52%2F9__qjqyiucbc_l9usq--lhvkaii.jpeg?alt=media)

*Схема работы Packer*

В результате получается базовый образ, из которого можно развернуть любое количество виртуальных машин. Логика работы Packer похожа на логику Docker, когда из Docker-файла с описанием создают образ виртуальной машины.

### Пример использования Packer

Покажу на примере. Возьмем директорию Nginx, она вложена в директорию Packer на [GitHub](https://github.com/vk-cs/terraform-webinars/tree/master/packer_terraform):

```
# ls
README.md

packer
terraform
# cd packer/nginx
```

Далее соберем базовый образ с Nginx. Для этого:

1. Берем стандартный образ из облака.
2. С помощью Ansible ставим в образ Nginx.
3. Изменяем конфигурацию заменой конфигурационного файла.
4. Запускаем и включаем Nginx.

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

Приступаем к сборке образа:

1. Для примера я создал на [GitHub](https://github.com/vk-cs/terraform-webinars/tree/master/packer_terraform) директорию Packer. В ней лежит директория Nginx и файл nginx.pkr.hcl.

```
# ls
nginx
nginx.pkr.hcl
playbook.yml
# vim nginx.pkr.hcl

```

2. Файл nginx.pkr.hcl содержит конфигурацию для Packer, эта информация нужна, чтобы Packer понимал, в каком бэкенде и как нужно запустить виртуальную машину.

```
variable "image_tag" {
  type = string
}

source "openstack" "nginx" {
  source_image_filter {
    filters {
      name = "Centos-7.9-202107"
    }
    most_recent = true
  }

  flavor                  = "Basic-1-1-10"
  ssh_username            = "centos"
  security_groups         = ["all"]
  volume_size             = 10
  config_drive            = "true"
  use_blockstorage_volume = "true"
  networks                = ["298117ae-3fa4-4109-9e08-8be5602be5a2"]

  image_name = "nginx-${var.image_tag}"
}

build {
  sources = ["source.openstack.nginx"]

  provisioner "ansible" {
    playbook_file = "playbook.yml"
  }

}

```

3. В конфиге через `source "openstack" и source_image_filter` указываем, какой базовый образ использовать для создания нового. В данном случае — Centos-7.9-202107, который находится в публичном доступе в облаке VK Cloud Solutions.
4. Через `flavor` указываем размер виртуальной машины, например: 1 ядро, 1 ГБ оперативной памяти, 10 ГБ диска. Эти параметры касаются только базовой виртуальной машины, с которой снимается образ. Из нового образа можно будет запускать ВМ любого размера.
5. Здесь же указываем параметры подключения к ВМ по SSH и другие настройки. Доступ по SSH нужен, чтобы Packer мог запустить в облаке ВМ и получить доступ к Ansible, который подключается к ВМ и выполняет нужные действия. Только после этого создается снапшот виртуальной машины.
6. В переменных указываем название создаваемых образов. Здесь `nginx`

— постоянный префикс, `$` — переменная версии. Например, `nginx 0.0.1`. Для этого в начале описываем, что есть переменная `image_tag` типа `string`. При старте сборки образа мы запускаем Packer и указываем `image_tag`. Алгоритм такой же, как при сборке Docker-образов, то есть у нас будет образ nginx 0.0.1, 0.0.2, 0.0.3, 1.0.0 и так далее — сразу версионируем образы.

7. В блоке `build` описываем, как запускать провижинер и какой. В данном случае `Ansible`. Тут же указываем файл, который нужен для запуска, — `playbook.yml`.
8. В файле playbook.yml указываем, что на всех хостах нужно запустить роль `Nginx`.

```
---

- hosts: all
  become: true

  roles:
    - nginx

```

9. В файле роли `Nginx` все довольно просто: указываем минимальную установку Nginx, прикрепляем набор конфигураций, добавляем репозиторий с исходным Nginx и устанавливаем его. После этого копируем конфигурацию в виртуальную машину, стартуем и энейблим сервис с Nginx.

```
---
- name: Add nginx repo | Centos
  yum_repository:
    baseurl: http://nginx.org/packages/mainline/rhel/7/$basearch/
    enabled: true
    gpgcheck: false
    description: Nginx repo
    name: nginx
  when: ansible_facts['os_family'] == "RedHat"

- name: Install Nginx | Centos
  yum:
    name: nginx
    state: present
  when: ansible_facts['os_family'] == "RedHat"

- name: Add Nginx config
  template:
    src: default.conf.j2
    dest: /etc/nginx/conf.d/default.conf
    mode: 0644

- name: Start and enable Nginx
  service:
    name: nginx
    enabled: true
    state: started
```

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

10. В файле конфига default.conf просим Nginx при обращении к корню возвращать «200» и свой hostname. Это поможет понять, как работают инстансы и выполняется балансировка.

```
server {
  listen       80 default_server;
  server_name  _;

  default_type text/plain;

  location / {
    return 200 '$hostname\n';
  }
}
```

Это вся конфигурация, которая нужна для запуска Packer. Перед этим важно соблюсти два условия:

1. Передать в Packer ключи для взаимодействия с API облака, то есть логин и пароль учетной записи.
2. Локально установить и настроить Ansible.

Запустим Packer.

```
# packer build -var ‘image_tag=1.0.1' nginx.pkr.hcl
openstack: output will be in this color
==> openstack: Loading flavor: Basic-1-1-10
       openstack: Verified flavor. ID: df3c499a-044f-41d2-8612-d303adc613cc
==> openstack: Creating temporary keypair: packer_621795d4-d237-1627-ef62-57d0952dl304 ...
==> openstack: Created temporary keypair: packer_621795d4-d237-1627-ef62-57d0952dl304
       openstack: Found Image ID: 44709803-5ec2-496b-88b7-85a5250e51c4
==> openstack: Creating volume...
==> openstack: Waiting for volume packer_621795d4-d40a-0146-b30c-92f00elae3b4 (volume id: 9102e41e-15b7-4ef6-8c27-7a0de 49ce922) to become available...
      openstack: Volume ID: 9102e41e-15b7-4ef6-8c27-7a0de49ce922
=> openstack: launching server...
==> openstack: Launching server... openstack: Server ID: f44e40eb-a907-49e3-8d7e-89b20eaa8df1
==> openstack: Waiting for server to become ready...

```

1. Выполняем команду `packer build - var`, указываем переменную `image_tag` с индексом 1.0.1 и передаем конфигурационный файл nginx.pkr.hcl, который мы использовали раньше.
2. После выполнения команды Packer идет в интерфейс облака и запускает виртуальную машину.
3. При этом в личном кабинете облака начинает создаваться виртуальная машина с указанным типом. На этом этапе Packer ждет запуск ВМ и последующий запуск SSH. После этого на ВМ запускается Ansible, который снимает с ВМ образ.
4. После завершения обработки в интерфейсе облака будет создан образ, из которого можно запустить любое количество виртуальных машин.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0c82648e5993e290143550542431f639255b0629%2Ftzopfig67md6eferso81_0islw4.png?alt=media)

Вручную добавлять инстансы, конфигурировать и настраивать каждую ВМ в облаке опасно: даже из-за незначительной ошибки могут возникать глобальные сбои в инфраструктуре. Автоматизировать эти процессы и исключить ошибки позволяет Terraform.

## Terraform

Terraform — инструмент от компании Hashicorp, который позволяет декларативно управлять инстансами, сетями, группами безопасности и другими компонентами инфраструктуры с помощью файлов конфигураций. Благодаря Terraform можно привести инфраструктуру к нужному состоянию декларативно, сразу указав нужные параметры системы.

Еще в Terraform предусмотрена функция «План». Благодаря ей инструмент сравнивает текущее состояние системы с будущим и отображает пользователю, что именно будет удалено, запущено или изменено в соответствии с новым конфигурационным файлом. Такая проверка помогает исключить ошибки при создании рабочей инфраструктуры.

### Конфигурация Terraform

Конфигурация Terraform интереснее, чем у Packer. Для его работы файлы конфигурации должны находиться в директории, из который запускают инструмент. При этом оформление не имеет значения: конфигурацию можно описать как в одном файле, так и в нескольких, разделив на смысловые блоки.

```
# ls
nginx		nginx.pkr.hcl 	playbook.yml
# cd ../../nginx/terraform
# ls
keys.tf			network.tf		terraform.tfstate.backup
loadbalancer.tf		providers.tf		variables.tf
main.tf			terraform.tfstate	vars.tfvars

```

Как правило, описание конфигурации содержит:

1. **Файл с описанием провайдеров.** Terraform может работать с разными облаками, поэтому в файле описываем параметры провайдера — название и версию. Подробнее про настройку Terraform-провайдера для VK Cloud Solutions можно посмотреть [здесь](https://mcs.mail.ru/docs/ru/additionals/terraform/terraform-provider-config).

```
terraform {
    required_providers {
        vkcs = {
            source = "vk-cs/vkcs"
        }
    }
}

```

2. **Файл с данными для подключения к облаку.** Инструменту надо передать API endpoints, ключи и другие персональные идентификаторы.
3. **Файл с описанием конфигурации виртуальных машин.** Terraform работает с двумя типами объектов: Data и Resource. Data — то, что можно получить из облака. Например, конфигурация дефолтной сети облака. А Resource — то, что создаем сами.

```
resource "vkcs_compute_instance" "instance" {
  count = var.node_count
  name = "node-${count.index}"
  image_name = "${var.image_name}-${var.image_tag}"
  flavor_name = var.flavor_name

  key_pair = vkcs_compute_keypair.ssh.name
  config_drive = true

  security_groups = [
    vkcs_networking_secgroup.secgroup.name
  ]

  network {
    name = vkcs_networking_network.example_routed_private_network.name
  }

  lifecycle {
    create_before_destroy = true
  }
}
```

В описании Resource указываем тип ресурса: `vkcs_compute_instance`. Также указываем внутреннее имя ресурса, которое Terraform будет использовать для автоматического построения зависимостей. С помощью таких имен можно обращаться к свойствам других объектов.

В этом файле конфигурации также указываем переменную с названием ВМ — с префиксом node и переменной `count.index`. Прописываем еще две переменные: `image_name` и `image_tag`, которые будут указывать на название и тег образа, созданного с помощью Packer.

Также описываем и другие параметры.

4. **Файл с описанием сетей.** В файле с сетями используется другой тип объектов — Data. Он нужен для запроса данных из облака в переменную `ext-net`. Также в файле прописываем приватную сеть и подсеть, создаем роутер, конфигурируем Security-группы и настраиваем остальные сетевые параметры.

```
data "vkcs_networking_network" "extnet" {
  name = "ext-net"
}

resource "vkcs_networking_network" "example_routed_private_network" {
  name = "example_routed_private_network"
}

resource "vkcs_networking_subnet" "example_routed_private_subnet" {
  name        = "example_routed_private_subnet"
  network_id  = vkcs_networking_network.example_routed_private_network.id
  cidr        = "10.0.2.0/24"
  ip_version  = 4
  enable_dhcp = true
}

resource "vkcs_networking_router" "example_router" {
  name                = "example_router"
  external_network_id = data.vkcs_networking_network.extnet.id
}

resource "vkcs_networking_router_interface" "example_router_interface" {
  router_id = vkcs_networking_router.example_router.id
  subnet_id = vkcs_networking_subnet.example_routed_private_subnet.id
}

resource "vkcs_networking_secgroup" "secgroup" {
  name        = "terraform__security_group"
  description = "security group for terraform instance"
}

resource "vkcs_networking_secgroup_rule" "secgroup_rule22" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}

resource "vkcs_networking_secgroup_rule" "secgroup_rule80" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}

resource "vkcs_networking_secgroup_rule" "secgroup_rule-1" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "icmp"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}

```

5. **Файл с ключами доступа.** В нем прописываем, что Terraform сам автоматически генерирует SSH-ключ: приватную часть после запуска сохранит локально, а публичную часть положит в облако. Впоследствии публичный ключ будет доставлен во все новые ВМ.

```
resource "tls_private_key" "ssh" {
  algorithm = "RSA"
}

resource "vkcs_compute_keypair" "ssh" {
  name       = "terraform_ssh_key"
  public_key = tls_private_key.ssh.public_key_openssh
}

output "ssh" {
  value = tls_private_key.ssh.private_key_pem
  sensitive = true
}
```

Ресурс `tls_private_key.ssh.private_key_pem` содержит приватный ключ. В выводе он помечен как `sensitive = true` и будет маскирован. Используем следующую команду для сохранения приватного ключа:

```
terraform.exe output ssh

```

6. **Файл с конфигурацией load balancer.** Балансировщик нагрузки нужен, если планируется запускать несколько виртуальных машин. Для подготовки в файле создаем Load Balancer и внешний Listener, добавляем в балансировщик виртуальные машины и определяем правила проверки. При правильной настройке балансировщика Terraform обеспечивает Zero Downtime Deployment с плавной для пользователей сменой версии приложения.

```
resource "vkcs_networking_floatingip" "example_floating_ip" {
  pool    = "ext-net"
  port_id = vkcs_lb_loadbalancer.example_http_balancer.vip_port_id
}

resource "vkcs_lb_loadbalancer" "example_http_balancer" {
  name          = "example_http_balancer"
  description   = "An HTTP load balancer in a private network with 2 backends"
  vip_subnet_id = vkcs_networking_subnet.example_routed_private_subnet.id
}

resource "vkcs_lb_listener" "example_http_listener" {
  name            = "example_http_listener"
  description     = "A load balancer frontend that listens on 80 prot for client traffic"
  protocol        = "HTTP"
  protocol_port   = 80
  loadbalancer_id = vkcs_lb_loadbalancer.example_http_balancer.id
}

resource "vkcs_lb_pool" "example_http_pool" {
  name        = "example_http_pool"
  description = "A load balancer pool of backends with Round-Robin algorithm to distribute traffic to pool's members"
  protocol    = "HTTP"
  lb_method   = "ROUND_ROBIN"
  listener_id = vkcs_lb_listener.example_http_listener.id
}

resource "vkcs_lb_monitor" example_http_monitor {
  name           = "example_http_monitor"
  delay          = 5
  max_retries    = 3
  timeout        = 5
  type           = "HTTP"
  url_path       = "/"
  http_method    = "GET"
  expected_codes = "200"
  pool_id        = vkcs_lb_pool.example_http_pool.id
}

resource "vkcs_lb_member" "example_http_member" {
  count         = var.node_count
  name          = "example_http_member-${count.index}"
  address       = vkcs_compute_instance.instance.*.access_ip_v4[count.index]
  protocol_port = 80
  weight        = 10
  pool_id       = vkcs_lb_pool.example_http_pool.id
  subnet_id     = vkcs_networking_subnet.example_routed_private_subnet.id

  lifecycle {
    create_before_destroy = true
  }
}

output "example_http_balancer_vip_address" {
  value = vkcs_networking_floatingip.example_floating_ip.address
}
```

Для запуска Terraform и начала создания указанной инфраструктуры нужно выполнить `terraform apply`. В ответ на это Terraform выводит весь план создаваемой конфигурации, запрашивает подтверждение и начинает создавать в облаке всю заданную инфраструктуру с сетями, подсетями, роутерами, балансировщиками и другими компонентами.

В итоге IaC-инструменты сводят всю подготовку инфраструктуры к простым действиям:

1. Описание образа.
2. Описание состояний виртуальных машин с помощью Ansible.
3. Установка и настройка Nginx.
4. Запуск виртуальных машин с помощью Terraform.

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

## Обновленный CI/CD-пайплайн

C помощью Packer и Terraform можно реализовать все принципы Docker с неизменяемой инфраструктурой и Kubernetes с выкатыванием новых версий через перезапуск.

Связка инструментов отлично работает с инфраструктурой на виртуальных машинах в облаке и сохраняет привычный алгоритм:

1. Разработчик выпускает новую версию приложения.
2. С помощью Packer делаем новый образ ВМ на основе новой версии приложения.
3. С помощью Terraform автоматизировано повторно разворачиваем всю инфраструктуру без даунтайма с Health Check от балансировщиков.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Infrastructure%20As%20a%20Code/IaC%20Packer%20Ansible%20Teraform/Untitled)

> На нашей
>
> [платформе VK Cloud Solutions](https://mcs.mail.ru/?)


# Installing Jenkins using terraform in Kubernetes in Yandex Cloud with letsencypt

<https://habr.com/ru/articles/683844/>

В этой статье будет следующее:

* Заведение DNS домена на reg.ru.
* Управление DNS зоной в Yandex DNS c помощью terraform.
* Создание Kubernetes в Yandex Cloud.
* Резервируем внешний статический IP адрес.
* Установка Jenkins c помощью terraform модуля helm\_release.
* Создание ClusterIssue(Issue) для создания letsencypt сертификата.

**Быстрая установка Jenkins с помощью terraform в Kubernetes в Yandex Cloud с letsencypt.**

Скачать репозиторий:

```
git clone https://github.com/patsevanton/infrastructure-as-a-code-example.git
```

Перейти в каталог terraform-helm-release-jenkins:

```
cd terraform-helm-release-jenkins
```

Заполнить private.auto.tfvars на базе шаблона private.auto.tfvars.example.

Запустить установку:

```
k8s_install.sh
```

Разберем подробнее.

Заводим DNS домен на reg.ru — `mycompany.ru` или `mycompany.org.ru`. В настройках домена на вкладке "DNS-серверы и управление зоной" указываем DNS Yandex Cloud:

* ns1.yandexcloud.net
* ns2.yandexcloud.net

DNS зона управляется с помощью terraform кода. Документация — <https://cloud.yandex.ru/docs/dns/>.

```
resource "yandex_dns_zone" "dns_domain" {
  name   = replace(var.dns_domain, ".", "-")
  zone   = join("", [var.dns_domain, "."])
  public = true
}
```

Так как у zone на конце должна быть точка, то используем метод join для соединения нашего домена и точки.

**Создание DNS записи.**

В имени DNS записи на конце необходима точка, поэтому используем метод join для соединения DNS записи и точки. Код:

```
resource "yandex_dns_recordset" "jenkins_dns_domain" {
  zone_id = yandex_dns_zone.dns_domain.id
  name    = join("", [var.jenkins_dns_name, "."])
  type    = "A"
  ttl     = 200
  data    = [yandex_vpc_address.addr.external_ipv4_address[0].address]
}
```

Поле data — список IP адресов, на которые должны резолвиться эта DNS запись.

* *Создание сервисного аккаунта.*

Перед созданием Kubernetes кластера необходимо рассказать о создание сервисного аккаунта. Название сервисного аккаунта — `sa-k8s-admin`.

```
resource "yandex_iam_service_account" "sa-k8s-admin" {
  folder_id = var.yc_folder_id
  name      = "sa-k8s-admin"
}

resource "yandex_resourcemanager_folder_iam_member" "sa-k8s-admin-permissions" {
  folder_id = var.yc_folder_id
  role      = "admin"
  member    = "serviceAccount:${yandex_iam_service_account.sa-k8s-admin.id}"
}
```

Лучше использовать `yandex_resourcemanager_folder_iam_member` вместо `yandex_resourcemanager_folder_iam_binding`, так как не всегда работает корректно. У меня на другом коде вызывает ошибку. Issue — <https://github.com/yandex-cloud/terraform-provider-yandex/issues/267> и <https://github.com/yandex-cloud/terraform-provider-yandex/issues/283>.

```
"Binding for role "mdb.dataproc.agent" not found in policy"
```

В `role` указываем какие права имеет сервисный аккаунт. Более подробно в документации <https://cloud.yandex.ru/docs/iam/concepts/access-control/roles>.

**Создание kubernetes кластера в Yandex Cloud.**

Для создание kubernetes кластера в Yandex Cloud используется уже обычный terraform код:

```
resource "yandex_kubernetes_cluster" "zonal_k8s_cluster" {
  name        = "my-cluster"
  description = "my-cluster description"
  network_id  = yandex_vpc_network.k8s-network.id

  master {
    version = "1.21"
    zonal {
      zone      = yandex_vpc_subnet.k8s-subnet.zone
      subnet_id = yandex_vpc_subnet.k8s-subnet.id
    }
    public_ip = true
  }

  service_account_id      = yandex_iam_service_account.sa-k8s-admin.id
  node_service_account_id = yandex_iam_service_account.sa-k8s-admin.id
  release_channel         = "STABLE"
  // to keep permissions of service account on destroy
  // until cluster will be destroyed
  depends_on = [yandex_resourcemanager_folder_iam_member.sa-k8s-admin-permissions]
}

# yandex_kubernetes_node_group

resource "yandex_kubernetes_node_group" "k8s_node_group" {
  cluster_id  = yandex_kubernetes_cluster.zonal_k8s_cluster.id
  name        = "name"
  description = "description"
  version     = "1.21"

  labels = {
    "key" = "value"
  }

  instance_template {
    platform_id = "standard-v3"

    network_interface {
      nat        = true
      subnet_ids = [yandex_vpc_subnet.k8s-subnet.id]
    }

    resources {
      cores         = 2
      memory        = 4
      core_fraction = 50
    }

    boot_disk {
      type = "network-hdd"
      size = 32
    }

    scheduling_policy {
      preemptible = true
    }

    metadata = {
      ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
    }

  }

  scale_policy {
    fixed_scale {
      size = 1
    }
  }

  allocation_policy {
    location {
      zone = "ru-central1-b"
    }
  }

  maintenance_policy {
    auto_upgrade = true
    auto_repair  = true

    maintenance_window {
      day        = "monday"
      start_time = "15:00"
      duration   = "3h"
    }

    maintenance_window {
      day        = "friday"
      start_time = "10:00"
      duration   = "4h30m"
    }
  }
}

locals {
  kubeconfig = <<KUBECONFIG
apiVersion: v1
clusters:
- cluster:
    server: ${yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].external_v4_endpoint}
    certificate-authority-data: ${base64encode(yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].cluster_ca_certificate)}
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: yc
  name: ycmk8s
current-context: ycmk8s
users:
- name: yc
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      command: yc
      args:
      - k8s
      - create-token
KUBECONFIG
}

output "kubeconfig" {
  value = local.kubeconfig
}
```

Если кластер тестовый, то можно снизить стоимость используя 50% ядра и [использовать прерываемые виртуальные машины.](https://cloud.yandex.ru/docs/compute/pricing#prices-instance-resources)

```
    resources {
      ...
      core_fraction = 50
    }

    scheduling_policy {
      preemptible = true
    }
```

**Сеть и внешний статический IP адрес.**

Указываем сеть:

```
resource "yandex_vpc_network" "k8s-network" {
  name = "k8s-network"
}
```

Указываем подсеть:

```
resource "yandex_vpc_subnet" "k8s-subnet" {
  zone           = "ru-central1-b"
  network_id     = yandex_vpc_network.k8s-network.id
  v4_cidr_blocks = ["10.5.0.0/24"]
  depends_on = [
    yandex_vpc_network.k8s-network,
  ]
}
```

Указываем внешний статический IP адрес:

```
resource "yandex_vpc_address" "addr" {
  name = "static-ip"
  external_ipv4_address {
    zone_id = "ru-central1-b"
  }
}
```

Неважно какой будет IP. В DNS запись будет добавлен этот IP.

**Jenkins установливаем c помощью terraform модуля helm\_release.**

Рассмотрим `helm_release.tf`:

`provider "helm"` описывает настройки подключения к kubernetes для helm.

```
provider "helm" {
  kubernetes {
    host                   = yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].external_v4_endpoint
    cluster_ca_certificate = yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].cluster_ca_certificate
    exec {
      api_version = "client.authentication.k8s.io/v1beta1"
      args        = ["k8s", "create-token"]
      command     = "yc"
    }
  }
}
```

**Создаем ingress-nginx.**

```
resource "helm_release" "ingress_nginx" {
  name       = "ingress-nginx"
  repository = "https://kubernetes.github.io/ingress-nginx"
  chart      = "ingress-nginx"
  version    = "4.2.1"
  wait       = true
  depends_on = [
    yandex_kubernetes_node_group.k8s_node_group
  ]
  set {
    name  = "controller.service.loadBalancerIP"
    value = yandex_vpc_address.addr.external_ipv4_address[0].address
  }
}
```

В terraform опции `--set key=value` передаются так:

```
  set {
    name  = "controller.service.loadBalancerIP"
    value = yandex_vpc_address.addr.external_ipv4_address[0].address
  }
```

В данном случае для `controller.service.loadBalancerIP` указываем внешний статический IP адрес.

**Устанавливаем cert-manager по аналогии с ingress-nginx.**

```
resource "helm_release" "cert-manager" {
  namespace        = "cert-manager"
  create_namespace = true
  name             = "jetstack"
  repository       = "https://charts.jetstack.io"
  chart            = "cert-manager"
  version          = "v1.9.1"
  wait             = true
  depends_on = [
    yandex_kubernetes_node_group.k8s_node_group
  ]
  set {
    name  = "installCRDs"
    value = true
  }
}
```

**Устанавливаем jenkins.**

```
resource "helm_release" "jenkins" {
  namespace        = "jenkins"
  create_namespace = true
  name             = "jenkins"
  repository       = "https://charts.jenkins.io"
  chart            = "jenkins"
  wait             = true
  version          = "4.1.17"
  depends_on = [
    yandex_kubernetes_node_group.k8s_node_group
  ]
  values = [
    #file("${path.module}/jenkins-values-google-login.yaml")
    yamlencode(local.jenkins_values_google_login)
  ]
}
```

Jenkins helm чарту на вход необходимо указать value.yaml с нашими настройками.

Есть 2 способа указать value.yaml.

Первый способ использовать готовый и настроеный value.yaml в текущей директории:

```
  values = [
    file("${path.module}/jenkins-values-google-login.yaml")
  ]
```

Второй способ использовать yamlencode для перевода terraform кода в yaml код.

```
  values = [
    yamlencode(local.jenkins_values_google_login)
  ]
```

**Как получить terraform код из yaml кода?**

Подготавливаете и настраиваете value.yaml, а затем используя вот такую команду переводите в terraform код:

```
echo 'yamldecode(file("value.yaml"))' | terraform console
```

В моем случае value.yaml имеет другое название:

```
echo 'yamldecode(file("jenkins-values-google-login.yaml"))' | terraform console
```

Я использую yamlencode, потому что внутри yamlencode кода ссылаюсь на terraform переменные, а так же потому что job лежат отдельно.

Полученный код вставляете в `local.jenkins_values_google_login`

```
locals {
  jenkins_values_google_login = {
  сюда
  }
}
```

Затем вместо ваших данных:

```
"hostName"         = jenkins.mycompany.ru
```

используйте terraform переменные

```
"hostName"         = "${var.jenkins_dns_name}"
```

**Рассмотрим `local.jenkins_values_google_login`.**

В корне стоит `controller` от которого идут все настройки.

Для настройка jenkins из кода используются [jcasc](https://www.jenkins.io/projects/jcasc/), [job-dsl](https://github.com/jenkinsci/job-dsl-plugin). Примеры jcasc <https://github.com/jenkinsci/configuration-as-code-plugin>

```
      "JCasC" = {
        "authorizationStrategy" = <<-EOT
        loggedInUsersCanDoAnything:
          allowAnonymousRead: false
        EOT
```

Более подробно — [https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos/global-matrix-auth/README.md](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Infrastructure%20As%20a%20Code/Installing%20Jenkins%20using%20terraform%20in%20Kubernetes%20i/README/README.md)

В configScripts каждый блок EOT это отдельный файл в контейнере jenkins. `systemMessage` системное сообщение.

```
        "configScripts" = {
          "jenkins-configuration" = <<-EOT
          jenkins:
            systemMessage: This Jenkins is configured and managed 'as code' by Managed Cloud team.
          EOT
```

Указываем файл job:

```
          "job-config" = yamlencode({
            jobs = [
              { script = file("${path.module}/job1.groovy") },
              { script = file("${path.module}/job2.groovy") }
            ]
          })
```

Указываем список view:

```
          jenkins:
            views:
              - all:
                  name: "all"
              - list:
                  columns:
                  - "status"
                  - "weather"
                  - "jobName"
                  - "lastSuccess"
                  - "lastFailure"
                  - "lastDuration"
                  - "buildButton"
                  jobNames:
                  - "job1"
                  name: "stage"
              - list:
                  columns:
                  - "status"
                  - "weather"
                  - "jobName"
                  - "lastSuccess"
                  - "lastFailure"
                  - "lastDuration"
                  - "buildButton"
                  jobNames:
                  - "job2"
                  name: "test"
            viewsTabBar: "standard"
```

Указываем как мы будет входить в jenkins:

```
        "securityRealm" = <<-EOT
        googleOAuth2:
          clientId: "${var.clientId}"
          clientSecret: "${var.clientSecret}"
          domain: "${var.google_domain}"
        EOT
```

Я использую googleOAuth2. Можно еще использовать local, ldap и другие.

Дополнительные плагины jenkins:

```
      "additionalPlugins" = [
        "google-login:1.6",
        "job-dsl:1.81",
        "allure-jenkins-plugin:2.30.2",
        "ws-cleanup:0.42",
        "build-timeout:1.21",
        "timestamper:1.18",
        "google-storage-plugin:1.5.6",
        "permissive-script-security:0.7",
        "ansicolor:1.0.2",
        "google-oauth-plugin:1.0.6",
      ]
```

Настройка Ingress:

```
      "ingress" = {
        "annotations" = {
          "cert-manager.io/cluster-issuer" = "letsencrypt-prod"
        }
        "apiVersion"       = "networking.k8s.io/v1"
        "enabled"          = true
        "hostName"         = "${var.jenkins_dns_name}"
        "ingressClassName" = "nginx"
        "tls" = [
          {
            "hosts" = [
              "${var.jenkins_dns_name}",
            ]
            "secretName" = "jenkins-tls"
          },
        ]
      }
```

Если у вас нет letsencrypt, то вы удаляете cert-manager.io/cluster-issuer.

Указываем javaOpts чтобы запускать Job-DSL скрипты:

```
javaOpts: '-Dpermissive-script-security.enabled=true'
```

**Сравнение настроенный yaml и yamlencode из terraform кода.**

Чем плох настроенный yaml в качестве value.yaml для helm чарта относительно yamlencode ?

* Он длинный.
* В yaml коде нельзя вынести кусок кода (например job) в отдельный файл.
* Для формирования заполненого настроенного value.yaml необходимо использовать templatefile. Мне кажется это лишнее.

В JCasC.configScripts каждый блок до вертикальной черты (|) будет сохранен как отдельный файл в контейнере Jenkins.

В Yaml формате все job нужно расписывать. Если сравнить:

```
      job-config: |
        jobs:
          - script: >
              pipelineJob('job1') {
                logRotator(120, -1, 1, -1)
                authenticationToken('secret')
                definition {
                  cps {
                    script("""\
                      pipeline {
                        agent any
                        parameters {
                            string(name: 'Variable', defaultValue: '', description: 'Variable', trim: true)
                        }
                        options {
                          timestamps()
                          ansiColor('xterm')
                          timeout(time: 10, unit: 'MINUTES')
                        }
                        stages {
                          stage ('build') {
                            steps {
                              cleanWs()
                              echo "hello job1"
                            }
                          }
                        }
                      }""".stripIndent())
                    sandbox()
                  }
                }
              }
```

и чтение файла job, то вынос файла лучшее красивее и читабельнее:

```
          "job-config" = yamlencode({
            jobs = [
              { script = file("${path.module}/job1.groovy") },
              { script = file("${path.module}/job2.groovy") }
            ]
          })
```

Добавление `kind: ClusterIssuer` в Kubernetes.

Если мы будем добавлять `kind: ClusterIssuer` как `resource "kubernetes_manifest"` и добавим как подключатся к Kubernetes используя `provider "kubernetes"`, то получим ошибку:

```
cannot create REST client: no client config
```

Мы не можем развернуть `resource "kubernetes_manifest"` в котором ссылаемся на другой ресрус — <https://github.com/hashicorp/terraform-provider-kubernetes/issues/1380>

Поэтому создадим файл `ClusterIssuer.yaml.tpl` и будем формировать его через `templatefile` передав всего 1 переменную email\_letsencrypt.

Вот мы с вами и прошли то, что напланировали в самом начале.


# Teraform Crosplan Pulumi

<https://habr.com/ru/companies/slurm/articles/713026/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f8c3e7667960fd971d86af6d40a0c604fe527b58%2F1737a44cc46f63d99242f8c4d2046c01.png?alt=media)

*Перевод* [*оригинальной*](https://medium.com/@argonaut.dev/top-infrastructure-as-code-iac-tools-2022-7ce1205c4d0d) *статьи, где автор пишет о подходе **Infrastructure as Code** с его основными концепциями, оценивает преимущества такого подхода и сравнивает главные инструменты для работы с IaC на сегодняшний день: **Terraform**, **Pulumi** и **Crossplane**.*

**Инфраструктура как код** или **Infrastructure as Code** (IaC) — метод, который по умолчанию стали применять современные cloud-native компании для управления облачными ресурсами.

Не удивительно, что мировой рынок инструментов для работы с IaC стабильно растет: его прогнозируемая стоимость к 2028 году [оценивается в 2,8 млрд долларов](https://finance.yahoo.com/news/global-infrastructure-code-iac-market-115300736.html?guccounter=1\&guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8\&guce_referrer_sig=AQAAAIrD7GviDoxyYae8LmPDTaoku-sTLR8fFCHkK84_M5FG35TAuvwBXr7RaKfPY-0B01H2nJFKmp88adsP_jM28eFsWimtHdIhCnnJNz21W9atI1TsjMOYYvqmEvL--iljgq0Yk5tPzSffvb3DcohYHTw0CnJGfAcMFMB7GFls44bk). Популярность этих инструментов обусловлена двумя факторами:

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-723246237dccf9770ab7dd26af6d1e1c4e56b1e1%2Fd9bea063e7571afdffb2826f88b1e224.png?alt=media)

## Что такое Infrastructure as Code

IaC — это декларативный метод предоставления и управления ресурсами (инфраструктурой) в рамках системы контроля версий. Метод схож с тем, как обычно обрабатывают код. Некоторые из инструментов позволяют определить ресурсы, выходящие за рамки облачной инфраструктуры: приложения, конфигурации и т. д.

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

## Несколько ключевых концепций IaC

### Декларативные определения

IaC декларативна по своей природе: она гарантирует, что конечное состояние системы точно соответствует заданному во входной конфигурации. Пользователи могут просто указать желаемое конечное состояние вместо императивного описания процедуры для его достижения. Это необходимо для управления несколькими ресурсами заданного масштаба в различных средах.

Есть два типичных подхода к IaC: декларативный и процедурный. Декларативное определение дает инструкции, как должна выглядеть система после завершения процесса настройки. Процедурное/императивное определение представляет собой пошаговые инструкции о том, что система должна сделать для достижения желаемой конфигурации.

### Модели push и pull

Определение IaC необходимо запустить, чтобы повлиять на целевые системы и перевести их в желаемое состояние. Это достигается либо пулингом конфигураций уровнем управления (pull), либо запушиванием изменений в репозиторий всякий раз, когда в конфигурации происходят изменения (push).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-42a8d57be72ec03b60faf5b4f0c05d0587e6dab2%2F46fcc9d5afe49b28b8f6bd3f59071921.jpg?alt=media)

### Изменяемый и неизменяемый подход

В зависимости от варианта использования можно внедрить изменяемую или неизменяемую инфраструктуру.

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

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3adf4beb9b3e168191912cda195624bbd9172b42%2Fde74c1214147ed4284df3cda0921b10e.jpg?alt=media)

## Преимущества IaC

Основными факторами внедрения IaC являются: увеличение количества развертываний, растущая сложность облачных сервисов и архитектуры, а также необходимость масштабирования облачных систем в зависимости от нагрузки.

*Пройдемся по основным преимуществам от внедрения Infrastructure as Code.*

### 1. Повторяемость развертываний

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

### 2. Сложность облака

Основные облачные провайдеры предлагают от 150 до 200 инструментов и сервисов. Для всего этого (в дополнение к сложной гибридной облачной архитектуре) требуется глобальное и безопасное решение, которое можно быстро развернуть.

### 3. Требования масштабирования

После того, как требования к инфраструктуре определены в виде кода, масштабирование становится проще. Это означает снижение затрат времени и средств.

### 4. Контроль версий

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

### 5. Декларативная парадигма

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

### 6. Возможность совместной работы

Поскольку IaC обрабатывается так же, как код, она предоставляет расширенные возможности для совместной работы. Например, через системы контроля версий или платформы облачного инжиниринга, такие как Pulumi.

### 7. Автоматизация процессов

Технические навыки и бюджет, необходимые для управления сложными облачными средами, могут быть неподъемными для компании. Благодаря IaC несколько инженеров могут легко управлять всей облачной инфраструктурой.

### 8. Лучшие практики

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

## Зачем нужны инструменты для работы с IaC

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

## Топ-3 vendor-neutral инструмента для работы с IaC

**Terraform**

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-723246237dccf9770ab7dd26af6d1e1c4e56b1e1%2F9955f12e3fa3a1c00ade2c87732a51f2.png?alt=media)

**Terraform** от HashiCorp — популярнейший инструмент для работы с IaC. Он поставляется в варианте с открытым исходным кодом для самостоятельного управления либо как Terraform Cloud (управляемый). Полезен своей способностью определять облачные и локальные ресурсы в удобочитаемом формате. Этот инструмент не зависит от облачной платформы, а создаваемые модули также могут иметь версии, повторно или совместно использоваться.

Плагины Terraform напрямую взаимодействуют с облачным провайдером, поставщиком SaaS и другими API. Список общедоступных поставщиков Terraform можно найти в [Terraform Registry](https://registry.terraform.io/browse/providers). Можно использовать один из общедоступных модулей из реестра напрямую или написать свой собственный.

Terraform не просто управляет конфигурацией инфраструктуры — он позволяет создавать инфраструктуру с нуля, определять сетевые ресурсы и комбинировать ресурсы от разных поставщиков.

Язык конфигурации декларативный. Поставщики автоматически вычисляют зависимости между ресурсами, чтобы они создавались и удалялись в правильном порядке. Terraform легко интегрируется с системами контроля версий, такими как GitHub, так что внести изменения в инфраструктуру можно простым Git Merge.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6bd7266b728e9897dd7f8b34590f72bfb4236d13%2F36bc335b6a3484b0805d0914c6f36562.jpg?alt=media)

### Crossplane

**Crossplane** — фреймворк для создания cloud-native уровней управления без необходимости написания кода. Создан специально для Kubernetes с поддержкой мультиоблачной, serverless и контейнерной разработки.

Crossplane предоставляет стандартные блоки, которые позволяют внедрять, формировать и использовать инфраструктуру с помощью Kubernetes API. Эти компоненты вместе обеспечивают мощное разделение, чтобы каждый член команды взаимодействовал с Crossplane на соответствующем уровне абстракции.

Upbound также предлагает [Universal Crossplane (UXP)](https://www.upbound.io/products/universal-crossplane) тем компаниям, которым нужно больше стабильности, лучшая поддержка и сниженные риски.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-be5ab343f86f5f58a4ce8e46699b223360f35ab5%2F95b645be4d8221067186695369c829df.jpg?alt=media)

### Pulumi

**Pulumi** основан в 2017 году ветеранами Microsoft и Google как бесплатный опенсорсный инструмент для работы с IaC, который делает управление инфраструктурой простым, надежным и безопасным. Доступен как в виде self-hosted решения, так и в виде SaaS. Поставку IaC можно осуществлять через существующие CI/CD пайплайны. Также существует возможность усиливать ограничения безопасности и следить за соответствием установленным требованиям.

Платформа облачного инжиниринга Pulumi помогает на этапах разработки, развертывания и управления инфраструктурой. Этот инструмент дает возможность использовать любимые языки (например, TypeScript, JavaScript, Go, .NET, YAML) для создания облачной инфраструктуры. Он также предоставляет доступ ко всему массиву услуг от AWS, GCP, Azure и более 60 других поставщиков. Код облачной инфраструктуры можно повторно использовать в виде [Pulumi packages](https://www.pulumi.com/product/packages/).

Главная причина успеха Pulumi — способность поддерживать команды и помогать им легко мигрировать на современные контейнерные среды и Kubernetes. [Здесь](https://github.com/orgs/pulumi/projects/44/views/1) можно посмотреть дорожную карту Pulumi и внести свой вклад в развитие инструмента.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a49d7e1f14a5c040713440d477bd2931d23071b3%2Fecfbca2affd35c823e00e3d05e526d45.jpg?alt=media)

*Ниже представлена таблица сравнения 3 описанных инструментов для работы с IaC, которые предлагают бесплатные версии, высокую универсальность и дополнительные функции.*

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e8517598d612b200d1b01abd0d3c745972458960%2Fe30d5f122e297556cc3318e3805e823d.jpg?alt=media)

## Автор советует

1. *Ускорьте DevOps.* Воспринимайте IaC в качестве средства автоматизации процесса предоставления ценности конечному пользователю. Используйте инфраструктуру, чтобы быстрее выполнять итерации, своевременно масштабировать и создавать более надежные системы.
2. *IaC и GitOps.* Внедряйте IaC с учетом совместной работы. Работа с инфраструктурой как с кодом позволяет быстрее планировать, запускать тесты и копировать среды. Эффективное управление доступом и установка ожиданий от новых конфигураций могут помочь контролировать расходы.

Каждый инструмент работы с IaC хорош для своих целей

А мы в Слёрм сконцетрируемся на Terraform! 4-5 февраля пройдет интенсив Terraform Мега для инженеров и разработчиков, которые уже работают с этим инструментом и хотят начать работать с ним на глубоком уровне. Ведут интенсив архитектор Yandex Cloud Павел Селиванов и Lead DevOps в Naviteq Александр Довнар.

Посмотреть программу и записаться здесь: <https://slurm.club/3j8fZ1G>


# Yandex IaC solutions

## Yandex IaC solutions

<https://github.com/patsevanton/yandex-iac-ansible-example>

[yandex-iac-ansible-example](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Infrastructure%20As%20a%20Code/Yandex%20IaC%20solutions/Yandex%20IaC%20solutions/yandex-iac-ansible-example/README.md)

## infrastructure-as-a-code-example

Examples of infrastructure as code tools include Yandex Cloud, Terraform and Ansible. Terraform code don\`t use managed service in Yandex Cloud.

### Install YC cli

```
curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash

```

### Init YC cli

```
yc init

```

### Get token, cloud-id, folder-id

```
yc config list

```

Output:

```
token: xxx
cloud-id: xxx
folder-id: xxxx
compute-default-zone: ru-central1-b

```

## Terraform

### Set up terraform

#### Download terraform <https://hashicorp-releases.yandexcloud.net/terraform/>

```
unzip terraform_1.2.4_linux_amd64.zip
sudo mv terraform /usr/local/bin/

```

#### Set terraform mirror

```
nano ~/.terraformrc

```

with code

```
provider_installation {
  network_mirror {
    url = "https://terraform-mirror.yandexcloud.net/"
    include = ["registry.terraform.io/*/*"]
  }
  direct {
    exclude = ["registry.terraform.io/*/*"]
  }
}

```

#### terraform fmt for private.auto.tfvars.example

```
find . -iname 'private.auto.tfvars.example' -execdir mv -i '{}' private.auto1.tfvars \;
terraform fmt -recursive
find . -iname 'private.auto1.tfvars' -execdir mv -i '{}' private.auto.tfvars.example \;

```

## Ansible

### Install Ansible

```
sudo add-apt-repository ppa:ansible/ansible
sudo apt update
sudo apt install ansible

```

### Setting for ubuntu 22.04

```
sudo apt install python3-resolvelib=0.5.4-1ppa~jammy
sudo apt-mark hold python3-resolvelib

```

## SSH

```
cat .ssh/config
Host *
    StrictHostKeyChecking no
    ServerAliveInterval 5
    HashKnownHosts no

```


# Kubernetes

[Installation](/readme/architect/kubernetes/installation)

[Frameworks](/readme/architect/kubernetes/frameworks)

[Auth](/readme/architect/kubernetes/auth)

[GUI management Lens](/readme/architect/kubernetes/gui-management-lens)

[Monitoring](/readme/architect/kubernetes/monitoring)

[Exposing services](/readme/architect/kubernetes/exposing-services)

[CNCF](/readme/architect/kubernetes/cncf)

[Helm](/readme/architect/kubernetes/helm)

[Isolation](/readme/architect/kubernetes/isolation)

[Security Center](/readme/architect/kubernetes/security-center)

[Terraform CI security](/readme/architect/kubernetes/terraform-ci-security)

[Vulnerability management](/readme/architect/kubernetes/vulnerability-management)

[Image scanning](/readme/architect/kubernetes/image-scanning)

[Signature verification](/readme/architect/kubernetes/signature-verification)

[Control plane security](/readme/architect/kubernetes/control-plane-security)

[Runtime Security](/readme/architect/kubernetes/runtime-security)

[Network security](/readme/architect/kubernetes/network-security)

[Honeypot](/readme/architect/kubernetes/honeypot)

[Backup](/readme/architect/kubernetes/backup)

[Secrets](/readme/architect/kubernetes/secrets)


# Installation

[Install Kubernetes cluster](/readme/architect/kubernetes/installation/install-kubernetes-cluster)

[Deploying a Kubespray cluster to OpenStack using Terraform](/readme/architect/kubernetes/installation/deploying-a-kubespray-cluster-to-openstack-using-t)

[Kube deploy in Yandex cloud](/readme/architect/kubernetes/installation/kube-deploy-in-yandex-cloud)


# Install Kubernetes cluster

<https://habr.com/ru/companies/domclick/articles/682364/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-30d1416c721725e924a0b0f52585f6165cde4eea%2F4e54f8f6300fecbf8b89c5dcd432095a.gif?alt=media)

В предыдущей [статье](https://habr.com/ru/company/domclick/blog/577964) я рассказывал, как построить простой кластер Kubernetes с одним мастер-узлом. Прошло время, опали листья... и мне захотелось большего, поэтому решил позариться на высокодоступные кластеры. В интернете много статей о том, как построить подобное решение, и давайте даже опустим тот факт, что многие из них уже устарели. Одно дело — установить кластер, а как же обслуживание: удаление, добавление, замена узлов? Про это и не вспоминают! В итоге оказалось, что не всё так просто, и вот, спустя больше ста установок, удалений и замен, у меня получилось собрать подробнейшее руководство по установке и, главное, обслуживанию *highly available* кластера с помощью Kubespray.

## Оглавление

1. [Введение](https://habr.com/ru/companies/domclick/articles/682364/#1)
2. [Установка высокодоступного Kubernetes-кластера](https://habr.com/ru/companies/domclick/articles/682364/#2)
   * [Настройка виртуальных машин](https://habr.com/ru/companies/domclick/articles/682364/#3)
   * [Настройка Ansible-машины](https://habr.com/ru/companies/domclick/articles/682364/#4)
   * [Установка кластера](https://habr.com/ru/companies/domclick/articles/682364/#5)
3. [Обслуживание кластера](https://habr.com/ru/companies/domclick/articles/682364/#6)
   * [Добавление worker node](https://habr.com/ru/companies/domclick/articles/682364/#7)
   * [Удаление worker node](https://habr.com/ru/companies/domclick/articles/682364/#8)
   * [Замена master node](https://habr.com/ru/companies/domclick/articles/682364/#9)
4. [Полезные команды](https://habr.com/ru/companies/domclick/articles/682364/#10)

## Введение

Высокодоступный кластер (aka high-availability) - это еще один шажок к production-ready кластеру. Как написано в [официальной](https://kubernetes.io/docs/setup/production-environment/#production-considerations) документации, построить высокодоступный кластер значит:

1. Отделить плоскость управления (мастер) от рабочих узлов
2. Реплицировать компоненты плоскости управления на несколько узлов
3. Добавить балансировщик нагрузки на API Kubernetes
4. Иметь достаточное количество рабочих узлов, чтобы выдержать нештатные ситуации и высокие нагрузки

Если подходить серьезно к вопросу, то наш будущий кластер будет *квази* high-available (в основном из-за внешнего балансировщика). Кластер будет состоять из четырёх узлов — двух мастеров и двух рабочих. Машины с характеристиками получше можно использовать как рабочие, а машины послабее — как мастера. На последних не будет запускаться рабочая нагрузка.

Сетап кластера:

1. Kubenetes version - 1.23.7
2. Сontainer runtime - Сontainerd 1.6.4
3. Network plugin - Calico 3.22.3

Доступные виртуалки и их роли я распределил так:

1. IP 185.186.142.53 - Master #1
2. IP 185.186.142.5 - Master #2
3. IP 46.8.19.144 - Worker #1
4. 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** балансировщик прилагается в комплекте.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3af7707852562552e3f38bd626eef26780caa8a9%2Fce0da16ed7023c2d59bd42fb82f0a98e.jpeg?alt=media)

В предыдущей [своей](https://habr.com/ru/company/domclick/blog/577964) статье проблему [решал](https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#a-pure-software-solution-metallb) с помощью MetalLb, в этот раз попробую [другой](https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#via-the-host-network) подход. Все 4 узла смогут принимать трафик извне, так как поды ingress-nginx будут развернуты на каждом узле (с помощью ресурса Daemonset) и использовать порт 80/443 этой машины.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-455cb2038ab872c427bd35a04bd929597da52d10%2Fc1c0effe5a87ed38a1bdcedf4ad322e5.jpeg?alt=media)

И конечно нужно привести [цитату](https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#via-the-host-network) с предостережением по безопасности данного решения:

> 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 [балансировку](https://kb.selectel.ru/docs/networks-services/dns/manage-records/dns-balancing/) трафика. Балансировка осуществляется с помощью алгоритма Round Robin — алгоритма кругового обслуживания. Первый запрос передается одному серверу, затем следующий запрос передается другому, и так далее до достижения последнего сервера. Затем направление запросов начинается сначала. Это самое бюджетное и простое решение которое я нашел на данный момент. Именно из-за DNS балансировки будущий кластер выйдет *почти* высокодоступным. Данное решение имеет ряд неприятных минусов:

1. Сложно управлять пулом адресов. Хорошо если DNS провайдер предоставляет API для программного изменения адресов. Но это лишний софт, плюс надо учитывать TTL на стороне клиента, его в таком случае советуют ставить на минимальное значение.
2. И самая большая проблема - нет отслеживания состояния серверов. Если одна машина из пула выйдет из строя, DNS сервер все равно будет отдавать этот адрес. Эту проблему и её последствия я наглядно [продемонстрирую](https://habr.com/ru/companies/domclick/articles/682364/#%D1%82%D0%B5%D1%81%D1%82) после установки кластера.

альтернативное решение

Если ваш VPS провайдер позволяет создавать [Virtual IP](https://gcore.com/support/articles/4405866963345/), то можно его привязать ко всем 4 удаленным машинам и тогда не нужна будет никакая DNS балансировка. Мой провайдер, к сожалению, не предоставляет такой функционал, поэтому протестировать такое решение не могу. А там, где есть данная фича VPS стоят неоправданно дорого.

Приступим к делу.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d754675f7c4ae94ce279bc053650c1c29e7f8f16%2F1ca78f4feaa0490a5c4d7ebb57a60901.jpeg?alt=media)

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

## Установка высокодоступного Kubernetes-кластера

В основе главы лежит эта [статья](https://schoolofdevops.github.io/ultimate-kubernetes-bootcamp/cluster_setup_kubespray/), но на текущий момент она уже устарела, поэтому некоторые разделы будут продублированы и дополнены либо изменены.[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

### Настройка виртуальных машин

Этот раздел необходимо выполнить для каждой виртуальной машины будущего кластера. Прежде всего настроим SSH-доступ без пароля. Для этого выполните команду `ssh-copy-id` на своей локальной машине. Например, у меня она выглядит так:

```
ssh-copy-id root@185.186.142.53
```

Тут необходимо будет ввести пароль от удалённой машины. После успешного выполнения команды подключаемся к удалённой машине без пароля:

```
ssh root@185.186.142.53
```

Установим Python:

```
sudo apt update
sudo apt install python
```

Включим переадресацию IPv4:

```
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
```

Отключим подкачку памяти:

```
swapoff -a
```

Пример из жизни №1

Раньше я полагал, что поставщики виртуалок предоставляют чистые образы ОС. Как оказалось, это не совсем так. Я пробовал установить Kubernetes на VPS двух провайдеров. У первого всё работало замечательно, а у второго (который был предпочтителен из-за низкой цены) установка кластера прерывалась на середине из-за внезапного пропадания интернета, а точнее, невозможности скачать определённый файл.

Поиск в интернете упорно ничего не давал, подсказали только в техподдержке: предложили заглянуть в файл resolv.conf и проверить наличие в конфигурации серверов адресов 1.1.1.1 или 8.8.8.8. Как оказалось, у этого провайдера интересная преднастройка ОС, и файл /etc/resolv.conf представляет собой simlink. О чём недвусмысленно сообщал объёмный комментарий в самом файле:

```
# This file is managed by man:systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0 trust-ad
search template

```

И в самом файле, конечно, этих резолверов обнаружено не было. Проблему удалось решить только удалением simlink и пересозданием файла с добавлением двух DNS-резолверов (в принципе, будет достаточно одного 1.1.1.1 или 8.8.8.8).

В итоге файл выглядит следующим образом:

```
nameserver 127.0.0.53
options edns0 trust-ad
search template

nameserver 8.8.8.8
nameserver 1.1.1.1
```

Удаленная машина успешно настроена и готовка к установке Kubernetes. Инструкции из этого подраздела повторите на всех оставшихся машинах будущего кластера.

Также крайне рекомендую на этом этапе сделать снимок системы, чтобы при переустановке кластера можно было откатить систему до текущего состояния. Причём даже не понадобится перенастраивать SSH-доступ без пароля. И тогда в будущем об этом подразделе можно забыть. Функциональность снимков предоставляется провайдером VPS (у меня это vmmanager).

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

### Настройка Ansible-машины

Эти инструкции нужно выполнить только на своей локальной машине, с которой будет устанавливаться Kubernetes. Перейдите в подготовленную директорию и клонируйте репозиторий проекта Kubespray:

```
git clone git@github.com:kubernetes-sigs/kubespray.git
cd kubespray
```

Помните, что подобные инструкции по установке любого софта без указания версии могут в будущем привести к проблемам. Либо статья устареет, либо новые версии могут быть обратно несовместимыми. Если вы уверены в себе и готовы решать проблемы и несостыковки, клонируйте мастер-ветку. У меня на данный момент tag v2.19.0. Команда для переключения:

```
git checkout tags/v2.19.0
```

Перейдём к установке Kubernetes-кластера. Подготовим наш инвентарь, шаблон находится в папке sample, мы его скопируем под новым именем и будем использовать при установке:

```
cp -rfp inventory/sample inventory/k8s
```

Должна появиться новая директория k8s\*.\* Теперь объявим переменную `IPS` массивом с перечислением наших виртуальных машин, это нужно для последующего генерирования конфигурации:

```
declare -a IPS=(185.186.142.5 185.186.142.53 46.8.19.144 46.8.19.244)
```

Сгенерируем конфигурацию в новый инвентарь:

```
CONFIG_FILE=inventory/k8s/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-759ea6842eed3bc8ed776fa9b073fd523a26087f%2Fe629b2a1ab6faeb4e8c16a3c9f9130d0.png?alt=media)

Был создан файл, посмотрим его содержимое:

```
nano inventory/k8s/hosts.yaml
```

У меня конфиг выглядит следующих образом

```
all:
  hosts:
    node1:
      ansible_host: 185.186.142.5
      ip: 185.186.142.5
      access_ip: 185.186.142.5
    node2:
      ansible_host: 185.186.142.53
      ip: 185.186.142.53
      access_ip: 185.186.142.53
    node3:
      ansible_host: 46.8.19.144
      ip: 46.8.19.144
      access_ip: 46.8.19.144
    node4:
      ansible_host: 46.8.19.244
      ip: 46.8.19.244
      access_ip: 46.8.19.244
  children:
    kube_control_plane:
      hosts:
        node1:
        node2:
    kube_node:
      hosts:
        node1:
        node2:
        node3:
        node4:
    etcd:
      hosts:
        node1:
        node2:
        node3:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}
```

Получилась такая конфигурация кластера:

* Мастер-узлы перечисляются в разделе `kube_control_plane`, в моём случае это node1 и node2.
* Всего четыре Kubernetes-узла: node1, node2, node3 и node4.
* Etcd-кластер будет установлен на три хоста: node1, node2 и node3. Три потому, что для etcd-кластера жизненно необходим кворум большинства, то есть всегда должно быть нечётное количество работающих узлов. Если попробуете удалить один из хостов в `etcd:hosts`, то Ansible-роль завершится с ошибкой о необходимости нечётного количества узлов

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

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

Настройки будущего кластера находятся в папке group\_vars. Чтобы не устанавливать Helm вручную на каждом мастер-узле, установим в файле addons.yaml параметр `helm_enable` равным `true`:

```
# Helm deployment
helm_enabled: true
```

В том же файле addons.yaml раскомментируем и активируем nginx-ingress, а также параметр *ingress\_nginx\_host\_network, благодаря которому, к каждому узлу будет привязана пода* nginx-ingress:

```
# Nginx ingress controller deployment
ingress_nginx_enabled: true
ingress_nginx_host_network: true
```

Итоговый файл выглядит следующим образом

```
---
# Kubernetes dashboard
# RBAC required. see docs/getting-started.md for access details.
# dashboard_enabled: false

# Helm deployment
helm_enabled: true

# Registry deployment
registry_enabled: false
# registry_namespace: kube-system
# registry_storage_class: ""
# registry_disk_size: "10Gi"

# Metrics Server deployment
metrics_server_enabled: false
# metrics_server_container_port: 4443
# metrics_server_kubelet_insecure_tls: true
# metrics_server_metric_resolution: 15s
# metrics_server_kubelet_preferred_address_types: "InternalIP,ExternalIP,Hostname"

# Rancher Local Path Provisioner
local_path_provisioner_enabled: false
# local_path_provisioner_namespace: "local-path-storage"
# local_path_provisioner_storage_class: "local-path"
# local_path_provisioner_reclaim_policy: Delete
# local_path_provisioner_claim_root: /opt/local-path-provisioner/
# local_path_provisioner_debug: false
# local_path_provisioner_image_repo: "rancher/local-path-provisioner"
# local_path_provisioner_image_tag: "v0.0.21"
# local_path_provisioner_helper_image_repo: "busybox"
# local_path_provisioner_helper_image_tag: "latest"

# Local volume provisioner deployment
local_volume_provisioner_enabled: false
# local_volume_provisioner_namespace: kube-system
# local_volume_provisioner_nodelabels:
#   - kubernetes.io/hostname
#   - topology.kubernetes.io/region
#   - topology.kubernetes.io/zone
# local_volume_provisioner_storage_classes:
#   local-storage:
#     host_dir: /mnt/disks
#     mount_dir: /mnt/disks
#     volume_mode: Filesystem
#     fs_type: ext4
#   fast-disks:
#     host_dir: /mnt/fast-disks
#     mount_dir: /mnt/fast-disks
#     block_cleaner_command:
#       - "/scripts/shred.sh"
#       - "2"
#     volume_mode: Filesystem
#     fs_type: ext4
# local_volume_provisioner_tolerations:
#   - effect: NoSchedule
#     operator: Exists

# CSI Volume Snapshot Controller deployment, set this to true if your CSI is able to manage snapshots
# currently, setting cinder_csi_enabled=true would automatically enable the snapshot controller
# Longhorn is an extenal CSI that would also require setting this to true but it is not included in kubespray
# csi_snapshot_controller_enabled: false
# csi snapshot namespace
# snapshot_controller_namespace: kube-system

# CephFS provisioner deployment
cephfs_provisioner_enabled: false
# cephfs_provisioner_namespace: "cephfs-provisioner"
# cephfs_provisioner_cluster: ceph
# cephfs_provisioner_monitors: "172.24.0.1:6789,172.24.0.2:6789,172.24.0.3:6789"
# cephfs_provisioner_admin_id: admin
# cephfs_provisioner_secret: secret
# cephfs_provisioner_storage_class: cephfs
# cephfs_provisioner_reclaim_policy: Delete
# cephfs_provisioner_claim_root: /volumes
# cephfs_provisioner_deterministic_names: true

# RBD provisioner deployment
rbd_provisioner_enabled: false
# rbd_provisioner_namespace: rbd-provisioner
# rbd_provisioner_replicas: 2
# rbd_provisioner_monitors: "172.24.0.1:6789,172.24.0.2:6789,172.24.0.3:6789"
# rbd_provisioner_pool: kube
# rbd_provisioner_admin_id: admin
# rbd_provisioner_secret_name: ceph-secret-admin
# rbd_provisioner_secret: ceph-key-admin
# rbd_provisioner_user_id: kube
# rbd_provisioner_user_secret_name: ceph-secret-user
# rbd_provisioner_user_secret: ceph-key-user
# rbd_provisioner_user_secret_namespace: rbd-provisioner
# rbd_provisioner_fs_type: ext4
# rbd_provisioner_image_format: "2"
# rbd_provisioner_image_features: layering
# rbd_provisioner_storage_class: rbd
# rbd_provisioner_reclaim_policy: Delete

# Nginx ingress controller deployment
ingress_nginx_enabled: true
ingress_nginx_host_network: true
ingress_publish_status_address: ""
# ingress_nginx_nodeselector:
#   kubernetes.io/os: "linux"
# ingress_nginx_tolerations:
#   - key: "node-role.kubernetes.io/master"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
#   - key: "node-role.kubernetes.io/control-plane"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
# ingress_nginx_namespace: "ingress-nginx"
# ingress_nginx_insecure_port: 80
# ingress_nginx_secure_port: 443
# ingress_nginx_configmap:
#   map-hash-bucket-size: "128"
#   ssl-protocols: "TLSv1.2 TLSv1.3"
# ingress_nginx_configmap_tcp_services:
#   9000: "default/example-go:8080"
# ingress_nginx_configmap_udp_services:
#   53: "kube-system/coredns:53"
# ingress_nginx_extra_args:
#   - --default-ssl-certificate=default/foo-tls
# ingress_nginx_termination_grace_period_seconds: 300
# ingress_nginx_class: nginx

# ALB ingress controller deployment
ingress_alb_enabled: false
# alb_ingress_aws_region: "us-east-1"
# alb_ingress_restrict_scheme: "false"
# Enables logging on all outbound requests sent to the AWS API.
# If logging is desired, set to true.
# alb_ingress_aws_debug: "false"

# Cert manager deployment
cert_manager_enabled: false
# cert_manager_namespace: "cert-manager"
# cert_manager_tolerations:
#   - key: node-role.kubernetes.io/master
#     effect: NoSchedule
#   - key: node-role.kubernetes.io/control-plane
#     effect: NoSchedule
# cert_manager_affinity:
#  nodeAffinity:
#    preferredDuringSchedulingIgnoredDuringExecution:
#    - weight: 100
#      preference:
#        matchExpressions:
#        - key: node-role.kubernetes.io/control-plane
#          operator: In
#          values:
#          - ""
# cert_manager_nodeselector:
#   kubernetes.io/os: "linux"

# cert_manager_trusted_internal_ca: |
#   -----BEGIN CERTIFICATE-----
#   [REPLACE with your CA certificate]
#   -----END CERTIFICATE-----
# cert_manager_leader_election_namespace: kube-system

# MetalLB deployment
metallb_enabled: false
metallb_speaker_enabled: true
# metallb_ip_range:
#   - "10.5.0.50-10.5.0.99"
# metallb_pool_name: "loadbalanced"
# matallb_auto_assign: true
# metallb_speaker_nodeselector:
#   kubernetes.io/os: "linux"
# metallb_controller_nodeselector:
#   kubernetes.io/os: "linux"
# metallb_speaker_tolerations:
#   - key: "node-role.kubernetes.io/master"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
#   - key: "node-role.kubernetes.io/control-plane"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
# metallb_controller_tolerations:
#   - key: "node-role.kubernetes.io/master"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
#   - key: "node-role.kubernetes.io/control-plane"
#     operator: "Equal"
#     value: ""
#     effect: "NoSchedule"
# metallb_version: v0.12.1
# metallb_protocol: "layer2"
# metallb_port: "7472"
# metallb_memberlist_port: "7946"
# metallb_additional_address_pools:
#   kube_service_pool:
#     ip_range:
#       - "10.5.1.50-10.5.1.99"
#     protocol: "layer2"
#     auto_assign: false
# metallb_protocol: "bgp"
# metallb_peers:
#   - peer_address: 192.0.2.1
#     peer_asn: 64512
#     my_asn: 4200000000
#   - peer_address: 192.0.2.2
#     peer_asn: 64513
#     my_asn: 4200000000

argocd_enabled: false
# argocd_version: v2.1.6
# argocd_namespace: argocd
# Default password:
#   - https://argoproj.github.io/argo-cd/getting_started/#4-login-using-the-cli
#   ---
#   The initial password is autogenerated to be the pod name of the Argo CD API server. This can be retrieved with the command:
#   kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
#   ---
# Use the following var to set admin password
# argocd_admin_password: "password"

# The plugin manager for kubectl
krew_enabled: false
krew_root_dir: "/usr/local/krew"
```

Добавим в конфигурацию Ansible пользователя, под которым логинимся по SSH:

```
nano ansible.cfg
```

В группе `ssh_connection` добавьте параметр `remote_user`, равный `root`:

```
remote_user=root
```

Полностью файл выглядит так:

```
[ssh_connection]
pipelining=True
ssh_args = -o ControlMaster=auto -o ControlPersist=30m -o ConnectionAttempts=100 -o UserKnownHostsFile=/dev/null
#control_path = ~/.ssh/ansible-%%r@%%h:%%p
[defaults]
# https://github.com/ansible/ansible/issues/56930 (to ignore group names with - and .)
force_valid_group_names = ignore

host_key_checking=False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp
fact_caching_timeout = 7200
stdout_callback = default
display_skipped_hosts = no
library = ./library
callback_whitelist = profile_tasks,ara_default
roles_path = roles:$VIRTUAL_ENV/usr/local/share/kubespray/roles:$VIRTUAL_ENV/usr/local/share/ansible/roles:/usr/share/kubespray/roles
deprecation_warnings=False
inventory_ignore_extensions = ~, .orig, .bak, .ini, .cfg, .retry, .pyc, .pyo, .creds, .gpg
remote_user=root
[inventory]
ignore_patterns = artifacts, credentials
```

И наконец, команда запуска установки кластера. Выполните её в корне директории kubespray:

```
ansible-playbook -i inventory/k8s/hosts.yaml cluster.yml
```

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-99bb123ba79f947591292ac5eb1c27b9997ac487%2F2ca7ae1a9126b949545eca51d2e773d8.png?alt=media)

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

```
kubectl get nodes
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-46e265a74ddac1792393e0ef29dd1c4e16ec3c74%2F00960a11a383a2db5a12ba92cfd05ae5.png?alt=media)

Затем важно проверить, работают ли системные поды (coredns, kube-controller-manager, kube-scheduler и тд.):

```
kubectl get pods -n kube-system
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e803be082c47e35db1f095e52b67dd96a2ac0e0c%2Fa851c8344245ae8ea647583974c80b10.png?alt=media)

Если появилась ошибка CrashLoopBackOff (пример из жизни №2)

Возможно, после установки возникнет ошибка CrashLoopBackOff у coredns и nodelocaldns:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d633ca6677da88878acb0badf114d720df413f48%2Fcefcb10c78f7a827ffe9562cb3eba716.png?alt=media)

Проблема легко гуглится, и решение подробно описано [тут](https://coredns.io/plugins/loop/#troubleshooting-loops-in-kubernetes-clusters). Отредактируем конфигурацию kubelet:

```
nano /etc/kubernetes/kubelet-config.yaml
```

В файле необходимо подправить поле `resolvConf` на:

```
resolvConf: "/etc/resolv.conf"
```

И перезагрузить машину:

```
reboot
```

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e803be082c47e35db1f095e52b67dd96a2ac0e0c%2Fa851c8344245ae8ea647583974c80b10.png?alt=media)

Готово, установка Kubernetes-кластера завершена. Теперь проверим на практике. насколько кластер высокодоступный, то бишь high-availability.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0c93478e0d6a8831384bb455c1df08648046a6d2%2F2ea188958b86ec30e3f1772a646b73fd.png?alt=media)

На любом из мастер-узлов развернём простое приложение из предыдущей [статьи](https://habr.com/ru/company/domclick/blog/551332/), которое по GET-ручке возвращает случайно сгенерированный UUID. Создадим три ресурса, первый — deployment:

```
kubectl create deployment uuid-server --image=mopckou/sticky-session:0.0.5
```

Далее — Service с типом LoadBalancer:

```
kubectl expose deployment uuid-server --type=LoadBalancer --port=8080
```

И наконец, создадим манифест ingress с хостом uuid.awesomeservice.pro, который принимает и обрабатывает запросы:

```
cat <<EOF> ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uuid-server-ingress
spec:
  rules:
    - host: uuid.awesomeservice.pro
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: uuid-server
                port:
                  number: 8080
EOF
```

Применим его:

```
kubectl apply -f ingress.yaml
```

Проверим созданный ingress:

```
kubectl get ingress
```

У ресурса появились выделенные адреса - все 4 узла могут принимать трафик. Но по факту трафик будет поступать только на две машины 185.186.142.53 и 185.186.142.5. Всё потому что только их я завязал на домен *uuid.awesomeservice.pro* у своего DNS провайдера. Теперь проверим, что сервер отвечает:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d39ccb6fa55bbf0662ebbdd25a0dabd248f0089c%2F1866e4cb47e271e01e9aaa61882aa410.png?alt=media)

В инструментах разработчика можно посмотреть, на какой узел поступил и обработан запрос:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a6c791241be3847eb2199c0a54cfd3890b688c58%2Fcec2a833d91f83c749ade52a9c9fe268.png?alt=media)

Ответила node1 (185.186.142.5). Ради интереса выполнил запрос в другом браузере (Safari):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fa0daa6dbf8c350694fa48b223cf366c715113d2%2F0dcfbd218cb968c43b38bc93b7973c48.png?alt=media)

В Safari запрос был обработан узлом node2 (185.186.142.53). Таким образом заодно убедились, что DNS-балансировка работает. Теперь посмотрим, как будет отработана внештатная ситуация и для чего вообще нужен высокодоступный кластер. Допустим, что один из узлов выходит из строя и становится недоступным. Я руками выключаю удалённую машину и через второй узел смотрю состояние кластера:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-70edf56b5dcab75d72af586f14c47c36996675cd%2Fb1e34db4e847dc18234e7c2d46a5bef5.png?alt=media)

Теперь проверим, как запрос обработается через браузер:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f9f6b5385aee485b438f6d114069d8ed8e6f410f%2F8a395f22277a208242be852734ce7d5e.png?alt=media)

Как видите, раньше запрос в Chrome обрабатывался узлом node1 (185.186.142.5), а после того, как тот стал недоступен, запросы пошли на узел node2 (185.186.142.53). На самом деле мне повезло, видимо [TTL](https://www.cloudns.net/wiki/article/188/) истек и локальный кэш выполнил новый запрос к DNS серверу и получил второй адрес. Стоит чуть подольше потыкать запросы, и начинаются проблемы. В какой-то момент браузер всё таки получит адрес недоступного узла (185.186.142.5) и тогда запрос подвисает на 1 минуту, после чего браузер берет второй адрес из пула и наконец запрос успешно выполняется. На картинке видно, что обработка длилась чуть больше минуты.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5032ea2b3aa9f1b44732eec18edde5b1ac83a4d7%2Fbd7eaec4e1d1a4a59abbf5cb56a87066.png?alt=media)

Но последующие запросы будут сразу поступать на работающий узел:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f13f089d25a37b1f83c5bb4e2320a0e6c85ec3c8%2Ffee202433466097f6f4213a63be1b44d.png?alt=media)

В итоге мы имеем, что кластер не совсем ВЫСОКО доступный. Если в кластере из 4 узлов, один выходит из строя, то мы будем иметь 25% долгих запросов. Но при этом надо отметить, что они всё же будут успешными. Данное поведение я проверил в 2-х браузерах и ради интереса сделал запрос в Python скрипте - там, тоже самое, долгий запрос, но в итоге успешный.

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

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

## Обслуживание кластера

Данный раздел был основан на этой [статье](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/nodes.md#1-change-order-of-current-control-planes) и [issue](https://github.com/kubernetes-sigs/kubespray/issues/3471), а в последствии доработан.

### Добавление worker node

Перед тем, как добавлять новый узел, удалённую машину нужно [подготовить](https://habr.com/ru/companies/domclick/articles/682364/#3).

Теперь займемся конфигурациями: добавим в наш инвентарь новый хост. В файле hosts.yaml аналогично добавьте три строчки с новым адресом в раздел `all.hosts`, и добавьте ссылку на этот хост в раздел с перечислением всех узлов в кластере — `all.children.kube_node.hosts`*.* Я добавил пятый узел с адресом 46.8.19.76.

Полный файл hosts.yaml

```
all:
  hosts:
    node1:
      ansible_host: 185.186.142.5
      ip: 185.186.142.5
      access_ip: 185.186.142.5
    node2:
      ansible_host: 185.186.142.53
      ip: 185.186.142.53
      access_ip: 185.186.142.53
    node3:
      ansible_host: 46.8.19.144
      ip: 46.8.19.144
      access_ip: 46.8.19.144
    node4:
      ansible_host: 46.8.19.244
      ip: 46.8.19.244
      access_ip: 46.8.19.244
    node5:
      ansible_host: 46.8.19.76
      ip: 46.8.19.76
      access_ip: 46.8.19.76
  children:
    kube_control_plane:
      hosts:
        node1:
        node2:
    kube_node:
      hosts:
        node1:
        node2:
        node3:
        node4:
        node5:
    etcd:
      hosts:
        node1:
        node2:
        node3:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}
```

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

```
ansible-playbook -i inventory/k8s/hosts.yaml facts.yml
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c93583fb11b959ac131c1e4cf9df96ee5d319ec8%2F418d5c596ea7f9a7259f0e824957779c.png?alt=media)

Это сбор информации о кластере, он не займёт много времени. Теперь можно пользоваться ключом `--limit`, который нужен, чтобы во время добавления нового worker-узла не беспокоить уже существующие узлы. После успешного сбора информации запускаем основной плейбук присоединения worker-узла:

```
ansible-playbook -i inventory/k8s/hosts.yaml --limit=node5 scale.yml
```

Пример из жизни №3

Возможно, будет ошибка при выполнении скрипта. В моём случае надо было посмотреть, что работает resolved service. Запустим резолвер:

```
 systemctl enable systemd-resolved.service
```

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

## Удаление worker node

Чтобы удалить рабочий узел, нужно всего лишь воспользоваться следующим плейбуком:

```
ansible-playbook -i inventory/k8s/hosts.yaml -e node=node5 remove-node.yml
```

Удаление занимает немного времени, не больше 10 минут. По завершении работы плейбука будет примерно такой вывод:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-38e2c5a33eadc61abe15a9c48c64dbfb9fd1571f%2F922098022936bbd4aaad897fb5617804.png?alt=media)

Плейбук завершится успешно только в том случае, если worker-узел жив. Если он абсолютно нерабочий, то смотрите следующий раздел про замену master-узла, там будет подробно рассказано, как удалить из кластера нерабочий узел.

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

### Замена master node

Удалить мастер-узел, который не отвечает и не на связи, с помощью kubespray не получится. Пробовал разные варианты: запускал плейбук remove-node.yaml, запускал facts.yml, чтобы запросить актуальные данные кластера, запускал upgrade-cluster.yaml — всегда зависает на опросе недоступной ноды. Придётся удалять вручную. Эту инструкцию я долго искал по разным issue github, в итоге после небольших экспериментов получилось успешно удалить как не отвечающий мастер-узел, так и рабочий.

Сейчас кластер состоит из четырёх узлов: двух мастеров и двух рабочих машин:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-cf4b716a99a22d506fb05b3f1c9879061cef58b1%2Fc64516c8e0ec640072a5cc92724f67f0.png?alt=media)

Допустим, первый мастер-узел выходит из строя и не отвечает. Для этого собственноручно выключаю удалённую машину, и примерно через минуту узел помечается как "Not Ready":

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b752b287294b3f6cca713a7e5c18b0b4c2d352cb%2Ffb235391ad9f6478a43e0ee4f4bf6e08.png?alt=media)

Воспроизвели нештатную ситуацию, теперь давайте заменять мастер-узел. Прежде всего объявим его нерабочим:

```
kubectl cordon node1
kubectl drain node1 --ignore-daemonsets
```

Ключ `--ignore-daemonsets` обязателен для недоступных узлов. Возможно, консоль подзависнет на команде `drain`, поэтому можно прервать ожидание (Ctl+C). После этих команд узел перейдёт в состояние NotReady, SchedulingDisabled.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6bf40ae6c0137b47dc9a381a25fa413eb8b56d9a%2Fea1df97f0cdadf54c32f0f5fa0a967a4.png?alt=media)

Удалим узел:

```
kubectl delete node node1
```

Теперь нужно обновить cluset-info, а именно, указать в поле `server` актуальный адрес мастер-узла:

```
kubectl edit cm -n kube-public cluster-info
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-56d6c6d36d3dc6479f166874b75256bb362c2c4f%2F71ada57636f262309b2fee7a80579377.png?alt=media)

Я заменил на 185.186.142.53 (node2), так как это единственный на данный момент живой мастер. Напомню, что на каждом мастер-узле ещё и развёрнут etcd-кластер, а это значит, что наша нештатная авария задела и etcd-узел вместе с node1 kubernetes. Поэтому нужно подчистить всю информацию, связанную с etcd-1. На каждом рабочем мастер-узле открываем конфигурацию:

```
nano /etc/kubernetes/manifests/kube-apiserver.yaml
```

И удалим нерабочий etcd-узел в строке `--etcd-servers`. В нашем случае IP-адреса будут одинаковы:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-736e3739c832d0e5c8bd8ecf6bd50a21c632d57c%2Fe4fee1984f0551c8e2e06d5183e00f37.png?alt=media)

Аналогично, на каждом мастер-узле нужно отредактировать etcd-конфигурацию:

```
nano /etc/etcd.env
```

И удалить неактивную etcd-ноду в поле `ETCD_INITIAL_CLUSTER`:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-499e0609590bb158b3713ffbc110c5a476d19e7f%2F4c048dbb8b2d81a4f959847393233317.png?alt=media)

После актуализации конфигураций последний штрих — это удаление ноды непосредственно из etcd-кластера. Воспользуемся консольной утилитой etcdctl для доступа к кластеру etcd. Эта утилита должна быть установлена на мастер-узлах. Прежде всего посмотрим существующие etcd-узлы:

```
etcdctl member list --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node2.pem  --key=/etc/ssl/etcd/ssl/admin-node2-key.pem
```

Вывод будет примерно таким:

Обратите внимание, что ключи `--cacert`, `--cert` и `--key` обязательны, и эти сертификаты уже сгенерированы kubespray и будут лежать в директории /etc/ssl/etcd/ssl/ на каждой машине, где развёрнут etcd-кластер. Теперь удалим из etcd-кластера неактивный узел:

```
etcdctl member remove 4f211fee4e79e7e7 --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node2.pem  --key=/etc/ssl/etcd/ssl/admin-node2-key.pem
```

Вывод будет такой:

Можете после этого проверить предыдущей командой, что etcd-узел удалён. И последнее: возвращаемся на локальную машину и удаляем неактивный host из файла hosts.yaml:

```
nano inventory/k8s/hosts.yaml
```

Достаточно будет удалить три строчки из children.kube\_control\_plane.hosts, children.kube\_node.hosts и children.etcd.hosts (в all.hosts можно оставить, если потом захотите повторно добавить этот хост в кластер):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-56aa5bd822c41f5dbbc8c3f12b2d26c44ee4f9b8%2F73281afb768d03f74b339476da432e99.png?alt=media)

Полный hosts.yaml

```
all:
  hosts:
    node1:
      ansible_host: 185.186.142.5
      ip: 185.186.142.5
      access_ip: 185.186.142.5
    node2:
      ansible_host: 185.186.142.53
      ip: 185.186.142.53
      access_ip: 185.186.142.53
    node3:
      ansible_host: 46.8.19.144
      ip: 46.8.19.144
      access_ip: 46.8.19.144
    node4:
      ansible_host: 46.8.19.244
      ip: 46.8.19.244
      access_ip: 46.8.19.244
    node5:
      ansible_host: 46.8.19.76
      ip: 46.8.19.76
      access_ip: 46.8.19.76
  children:
    kube_control_plane:
      hosts:
        node2:
    kube_node:
      hosts:
        node2:
        node3:
        node4:
    etcd:
      hosts:
        node2:
        node3:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}
```

Теперь нужно запустить Ansible-роль актуализации кластера (upgrade-cluster.yml). При вызове этой команды возникнет ошибка чётного количества etcd-серверов. Чтобы эту ошибку проигнорировать, нужно добавить специальный ключ `ignore_assert_errors`. Команда для запуска роли:

```
ansible-playbook -i inventory/k8s/hosts.yaml -e ignore_assert_errors=yes upgrade-cluster.yml
```

После выполнения роли вывод консоли будет такой:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-14d8a4fe56b2ef53cce22d67dc4c51aa168ce0c8%2Fb536acc6fb63a82788040938c84a046d.png?alt=media)

Теперь можно добавлять новый мастер-узел в кластер. Но перед эти [подготовить](https://habr.com/ru/companies/domclick/articles/682364/#3) удалённую машину. Я вместо подготовки на node1 (185.186.142.5) накатил снимок ОС с готовым сетапом для установки Kubernetes. Отредактируем hosts.yaml:

```
nano inventory/k8s/hosts.yaml
```

Добавим новый host (в моем случае это node1) в all.hosts в разделы children.kube\_control\_plane.hosts, children.kube\_node.hosts и children.etcd.hosts\*.\* Надо отметить, что [советуют](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/nodes.md#1-change-order-of-current-control-planes) всегда добавлять новый хост в конец списка.

Полный файл hosts.yaml

```
all:
  hosts:
    node1:
      ansible_host: 185.186.142.5
      ip: 185.186.142.5
      access_ip: 185.186.142.5
    node2:
      ansible_host: 185.186.142.53
      ip: 185.186.142.53
      access_ip: 185.186.142.53
    node3:
      ansible_host: 46.8.19.144
      ip: 46.8.19.144
      access_ip: 46.8.19.144
    node4:
      ansible_host: 46.8.19.244
      ip: 46.8.19.244
      access_ip: 46.8.19.244
    node5:
      ansible_host: 46.8.19.76
      ip: 46.8.19.76
      access_ip: 46.8.19.76
  children:
    kube_control_plane:
      hosts:
        node2:
        node1:
    kube_node:
      hosts:
        node2:
        node3:
        node4:
        node1:
    etcd:
      hosts:
        node2:
        node3:
        node1:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}
```

Запускаем плейбук cluster.yaml. В ключе `--limit` указываем etcd и kube\_control\_plane, тогда при установке работоспособность worker-узлов затронута не будет.

```
ansible-playbook -i inventory/k8s/hosts.yaml --limit=etcd,kube_control_plane cluster.yml
```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3775ec72d49913a75419eb74322a539225e4ff37%2F4278528062f93e0230c3dd62465799e7.png?alt=media)

Готово, мастер-узел успешно добавлен!

Решение возможных проблем

Если добавляется абсолютно новая виртуалка, то проблем возникнуть не должно. Но если добавляется машина с тем же IP, то даже если система только что переустановлена, возникнут, скорее всего, проблемы. У меня, например, эта ошибка повторяется постоянно:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f0f020974a9e2060947abe61d1b1290aa304e779%2F6bf85823bb6e66968523bea6e42b0634.png?alt=media)

Каждый раз проблема возникает при присоединении etcd-узла к etcd-кластеру. Посмотрим логи etcd на удалённой машине node1:

```
journalctl -n 100 --unit=etcd
```

Будет следующая ошибка:

```
"error":"error validating peerURLs {ClusterID:95417c89898c861a Members:[&{ID:5baef32decc40060 RaftAttributes:{PeerURLs:[https://185.186.142.53:2380] IsLearner:false} Attributes:{Name:etcd1 ClientURLs:[https://185.186.142.53:2379]}} &{ID:80fbe9a8863e9bfe RaftAttributes:{PeerURLs:[https://46.8.19.144:2380] IsLearner:false} Attributes:{Name:etcd2 ClientURLs:[https://46.8.19.144:2379]}}] RemovedMemberIDs:[]}: member count is unequal"
```

Прогуглив ошибку "member count is unequal", можно наткнуться на такое [решение](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Installation/Install%20Kubernetes%20cluster/https:/member%20count%20is%20unequal/README.md). В нём советуют указать машину с другим IP, отличным от прежнего. Это подтверждает тезис, что если добавлять новый хост с новым IP, то Ansible-роль отработает и мастер-узел успешно добавится к Kubernetes-кластеру. Допустим, что у нас нет другой машины. Методом научного тыка я нашёл другое решение: сначала добавим вручную etcd-узел к etcd-кластеру, а после перезапустим Ansible-роль cluster.yaml, и мастер-узел успешно добавится к Kubernetes-кластеру.

Дальнейшие шаги выполняем на мастер-узле (node1). Сначала заглянем в etcd-конфигурацию:

```
nano /etc/etcd.env
```

Новый etcd-узел подключается как etcd3, запомним это имя. Вот команда присоединения к etcd-кластеру:

```
etcdctl member add etcd3 --endpoints=185.186.142.53:2379,46.8.19.144:2379 --peer-urls=https://185.186.142.5:2380 --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node3.pem  --key=/etc/ssl/etcd/ssl/admin-node3-key.pem
```

Здесь в ключе `--endpoints` указываются уже существующие узлы etcd-кластера, в `--peer-urls` — адрес нового etcd-узла (обратите внимание на порты, в первом ключе 2379, во втором — 2380). Остальные ключи — это ссылка на сертификаты, которые уже сгенерированы kubespray, смотрите в соответствующую директорию etc/ssl/etcd/ssl/. Вывод консоли будет такой:

Etcdctl сообщит кластеру о новом участнике и распечатает переменные среды, необходимые для его успешного запуска. Так как служба уже запущена в фоне, то перезапускать ничего не придётся. Убедитесь, что сгенерированные в консоли переменные окружения такие же, как в /etc/etcd.env; если нет, то нужно подправить этот файл на **всех** удалённых машинах, где развернут etcd, и перезапустить службу.

Смотрим текущее состояние etcd-кластера:

```
etcdctl member list --endpoints=185.186.142.53:2379,46.8.19.144:2379  --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node3.pem  --key=/etc/ssl/etcd/ssl/admin-node3-key.pem
```

Etcd-узел успешно добавлен в etcd-кластер. Можно ещё глянуть, как дела со здоровьем кластера:

```
etcdctl endpoint health --endpoints=https://185.186.142.53:2379,https://46.8.19.144:2379,https://185.186.142.5:2379  --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node2.pem  --key=/etc/ssl/etcd/ssl/admin-node2-key.pem
```

Etcd-кластер чувствует себя отлично. Наконец, можно перезапустить Ansible-роль и добавить новый мастер-узел к Kubernetes-кластеру.

```
ansible-playbook -i inventory/k8s/hosts.yaml --limit=etcd,kube_control_plane cluster.yml
```

[↑](https://habr.com/ru/companies/domclick/articles/682364/#0)

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

Вот список полезных команд, которые наверняка понадобятся.

Просмотр журнала логов etcd, kubelet:

```
journalctl --unit=etcd -n 100
journalctl --unit=kubelet -n 100
```

Просмотр статуса и перезагрузка службы:

```
systemctl status kubelet
systemctl restart kubelet
```

Возможно, понадобится сделать снимок с etcd- узла. Вот рабочий пример:

```
ETCDCTL_API=3 etcdctl --endpoints https://185.186.142.53:2379 snapshot save snapshot.db --cacert=/etc/ssl/etcd/ssl/ca.pem  --cert=/etc/ssl/etcd/ssl/admin-node2.pem  --key=/etc/ssl/etcd/ssl/admin-node2-key.pem
```

Мониторинг ресурсов виртуалки:

```
wget -qO- bench.sh | bash
```


# Deploying a Kubespray cluster to OpenStack using Terraform

<https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform>

Kubespray is a community-driven project that provides a set of Ansible playbooks to deploy a production-ready Kubernetes cluster. It is a great tool to deploy a Kubernetes cluster on OpenStack! This guide will detail using Terraform to automate creation of your OpenStack infrastructure and Ansible to deploy a Kubespray cluster on it.

We'll be using the following official Kubespray documentation as a reference:

* Support for most popular network plugins (Calico, Cilium, Contiv, Flannel, Multus, Weave, Kube-router, Romana, Amazon VPC CNI, etc.)
* Support for most popular Linux distributions
* Upgrade support from a previous Kubernetes version
* Composable attributes
* Declarative way to customize cluster configuration through a configuration file
* Network load balancer (MetalLB) for services of type LoadBalancer
* Configurable bootstrap tools for the Kubernetes cluster
* Multi-purpose bootstrap node used as a bastion (optional)
* GPU node support
* An OpenStack instance. If you don't have OpenStack, you can sign up for a free trial today with OpenMetal [OpenMetal Central](https://central.openmetal.io/sign-up).

We'll be performing this deployment from a VM running Ubuntu 20.04. You can also use one of your OpenMetal cloud core nodes or your work station. Our guide will have you install Terraform and Ansible in your installation environment.

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform supports existing, popular service providers as well as custom in-house solutions. Configuration files describe to Terraform the components needed to run a single application or your entire data center.

Terraform generates an execution plan describing what is needed to reach the desired state, then executes it to build the described infrastructure. As the configuration changes, Terraform is able to determine what changed and create incremental execution plans which can be applied. This allows for high fidelity plans and helps reduce out-of-band changes, which can lead to drift and conflicts.

For non Debian based systems, please see the [Terraform Installation Instructions](https://www.terraform.io/downloads).

```
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

```

Ensure the required Python modules are installed.

```
sudo apt-get install python3-virtualenv

```

Create your virtual environment.

```
virtualenv .kubespray

```

Activate the environment.

```
source .kubespray/bin/activate

```

Update pip

```
pip install -U pip

```

We'll be using the CLI to help populate our Terraform variables. If you don't have access to the OpenStack CLI, please follow the steps in this guide: [How to Install and Use OpenStack's CLI](https://openmetal.io/docs/manuals/operators-manual/day-1/command-line/openstackclient#how-to-install-and-use-openstacks-cli)

We'll be creating a project to deploy our infrastructure into. You can use an existing project if you have one.

```
openstack project create --domain default \
--description "Kubespray Cluster" \
kubespray-demo

```

```
openstack role add --project kubespray-demo --user admin admin

```

> Note: You can substitute the admin user if you already have your own user.

## Deploy the infrastructure with Terraform[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#deploy-the-infrastructure-with-terraform)

If you have not already done so, download your `openrc.sh` file from your projects "API Access" menu. Save the OpenStack RC file to your workspace and source it.

> This is an important step as it sets the environment variables Terraform uses to authenticate with OpenStack. Double check that these values are correct.

`openrc.sh`

```
export OS_AUTH_URL=https://openstack:5000
export OS_PROJECT_ID=projectid
export OS_PROJECT_NAME="kubespray-demo"
export OS_PROJECT_DOMAIN_ID=default
export OS_USERNAME=username
export OS_PASSWORD=password
export OS_REGION_NAME=RegionOne
export OS_INTERFACE=public
export OS_IDENTITY_API_VERSION=3
export OS_USER_DOMAIN_ID=default

```

```
source openrc.sh

```

```
ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_rsa.kubespray

```

```
# Start an SSH Agent for the current shell
eval $(ssh-agent -s)

# Add the generated key
ssh-add ~/.ssh/id_rsa.kubespray

```

The Kubespray repository contains the Ansible playbooks and Terraform templates we'll be using. Pull them down now with `git`:

```
git clone --depth 1 --branch v2.20.0  https://github.com/kubernetes-sigs/kubespray

```

Install Ansible and other requirements with `pip`.

```
cd kubespray
pip install -r requirements.txt

```

```
cp -LRp contrib/terraform/openstack/sample-inventory inventory/test-cluster
cd inventory/test-cluster
ln -s ../../contrib/terraform/openstack/hosts
ln -s ../../contrib

```

The previous commands generated a few files including one named `cluster.tfvars`. This file will be used to configure the nodes and networks for your cluster. Refer to [Cluster Variables](https://github.com/kubernetes-sigs/kubespray/blob/v2.20.0/contrib/terraform/openstack/README.md#cluster-variables) documentation for a full list of variables.

For this example, we'll be using the following variables:

> Note: We've added comments to help you fetch the values you want to replace from OpenStack.

```
cluster_name = "test-cluster"

public_key_path = "~/.ssh/id_rsa.kubespray.pub"

image = "Ubuntu 20.04 (focal-amd64)"

ssh_user = "ubuntu"

## Path to your cluster group vars directory
group_vars_path="<group_vars_path>/kubespray/inventory/test-cluster/group_vars"

number_of_bastions = 1

# List available flavors command: openstack flavor list
flavor_bastion = "gp1.small"

number_of_etcd = 0
number_of_k8s_masters = 0
number_of_k8s_masters_no_etcd = 0
number_of_k8s_masters_no_floating_ip = 1
number_of_k8s_masters_no_floating_ip_no_etcd = 0

# List available flavors command: openstack flavor list
flavor_k8s_master = "gp1.large"

number_of_k8s_nodes = 0
number_of_k8s_nodes_no_floating_ip = 2

# List available flavors command: openstack flavor list
flavor_k8s_node = "gp1.large"

network_name = "test-cluster-network"

# Fetch this value with this command: openstack network list --external
external_net = "<external_network_id>"

subnet_cidr = "172.29.0.0/25"

floatingip_pool = "External"

bastion_allowed_remote_ips = ["0.0.0.0/0"]

```

```
terraform -chdir="contrib/terraform/openstack" init

```

> Note: Run these commands from the kubespray/inventory/test-cluster directory.

```
 terraform -chdir="contrib/terraform/openstack" apply -var-file=$PWD/cluster.tfvars

```

You'll be prompted to confirm your changes to OpenStack, type `yes` to continue. Once the process completes, the infrastructure required to deploy Kubernetes will be available in your OpenStack project.

> Note: If you want to destroy your resources, you can run the following command:
>
> ```
> terraform -chdir="contrib/terraform/openstack" destroy -var-file=$PWD/cluster.tfvars
>
> ```

## Deploy Kubernetes with Ansible[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#deploy-kubernetes-with-ansible)

The Terraform run created your nodes and an Ansible inventory file. Next prepare the Ansible variables.

We provide here a simplified example configuration, it is likely you will want to configure more options than we've provided when setting up your cluster. For a full list of options, refer to the [Kubespray Documentation](https://kubespray.io/#/).

These are the options we updated to deploy the cluster with the OpenStack Cloud Provider, Cinder CSI, and support for Octavia load balancers.

```
cloud_provider: external
external_cloud_provider: openstack

```

### Update `group_vars/all/openstack.yml`[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#update-group_varsallopenstackyml)

```
cinder_csi_enabled: true
cinder_csi_ignore_volume_az: true

```

You are ready to deploy Kubernetes. The following command needs to be run from the `kubespray` directory. The process will take some time to complete and depends on the number of resources you wish to deploy. In our example, it took about 12 minutes.

```
cd ../..

```

```
ansible-playbook --become -i inventory/test-cluster/hosts cluster.yml

```

## Verify Kubernetes Installation[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#verify-kubernetes-installation)

If you followed along with the guide, you have a bastion node you can use to access your cluster. If you don't have a bastion node, you can skip this step.

```
openstack server list

```

```
ssh -A ubuntu@<bastion_ip>

```

```
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

```

```
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

```

To create the configuration file used to authenticate with the cluster, several certificates must be copied from the master node. Replace `<master_ip>` with the IP address of your master node:

```
ssh ubuntu@[master-ip] sudo cat /etc/kubernetes/ssl/apiserver-kubelet-client.key > client.key
ssh ubuntu@[master-ip] sudo cat /etc/kubernetes/ssl/apiserver-kubelet-client.crt > client.crt
ssh ubuntu@[master-ip] sudo cat /etc/kubernetes/ssl/ca.crt > ca.crt

```

```
# Set cluster
kubectl config set-cluster default-cluster \
  --server=https://[master-ip]:6443 \
  --certificate-authority=ca.crt \
  --embed-certs=true

# Set credentials
kubectl config set-credentials default-admin \
  --certificate-authority=ca.crt \
  --client-key=client.key \
  --client-certificate=client.crt \
  --embed-certs=true

# Create context
kubectl config set-context default-context \
 --cluster=default-cluster \
 --user=default-admin

# Set active context
kubectl config use-context default-context

```

```
 kubectl get pods -A

```

Output:

```
NAMESPACE     NAME                                                   READY   STATUS    RESTARTS   AGE
kube-system   coredns-74d6c5659f-b9lrn                               1/1     Running   0          13h
kube-system   coredns-74d6c5659f-t9q6q                               1/1     Running   0          13h
kube-system   csi-cinder-controllerplugin-9b75f6cc7-hpwn5            6/6     Running   0          11h
kube-system   csi-cinder-nodeplugin-75vt5                            3/3     Running   0          11h
kube-system   csi-cinder-nodeplugin-jhdng                            3/3     Running   0          11h
kube-system   dns-autoscaler-59b8867c86-cv5nm                        1/1     Running   0          13h
kube-system   kube-apiserver-test-cluster-k8s-master-nf-1            1/1     Running   1          13h
kube-system   kube-flannel-dstxm                                     1/1     Running   0          13h
...

```

You should now have a working configuration file. Save this in a safe place to access your cluster from a machine that can reach your master node.

```
cat ~/.kube/config

```

## Verify OpenStack Cloud Provider[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#verify-openstack-cloud-provider)

By enabling the OpenStack Cloud Provider, Kubespray configured a few pods that should now be in the running state.

```
$ kubectl get pods -A | grep 'csi\|openstack'

kube-system   csi-cinder-controllerplugin-9b75f6cc7-hpwn5            6/6     Running   0          11h
kube-system   csi-cinder-nodeplugin-75vt5                            3/3     Running   0          11h
kube-system   csi-cinder-nodeplugin-jhdng                            3/3     Running   0          11h
kube-system   openstack-cloud-controller-manager-d7wbb               1/1     Running   0          11h

```

The OpenStack Cloud Provider supports Octavia load balancers. Verify the load balancer is working by creating a service of type `LoadBalancer`. Once you create the service, you should see a new load balancer created in the OpenStack dashboard.

Create a service with the following command:

```
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: fake-service
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: fake-service
EOF

```

You can verify that the load balancer was created by running the following command:

```
openstack loadbalancer list

```

You should also see a floating IP associated with the load balancer service in Kubernetes. This may take a couple of minutes to complete:

```
kubectl get svc -A -w

```

Output:

```
NAMESPACE     NAME              TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                  AGE
default       hostname-server   LoadBalancer   10.233.32.201   127.0.0.1        80:32709/TCP             12h

```

Next we'll verify that Cinder volumes are working. First, create a storage class:

```
kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cinder-csi
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: cinder.csi.openstack.org
parameters:
  availability: nova
allowVolumeExpansion: true
volumeBindingMode: Immediate
EOF

```

Now create a PersistentVolumeClaim by running the following command:

```
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-volume
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: cinder-csi
  resources:
    requests:
      storage: 1Gi
EOF

```

## Deploy a pod that uses the volume[​](https://openmetal.io/docs/manuals/kubernetes-guides/deploying-a-kubespray-cluster-to-openstack-using-terraform#deploy-a-pod-that-uses-the-volume)

We'll deploy a Redis instance configured to use the volume we created in the previous step.

> Warning: This is just an example. Do not use this in production.

```
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - image: redis
        name: redis
        volumeMounts:
        - mountPath: /var/lib/redis
          name: redis-data
      volumes:
      - name: redis-data
        persistentVolumeClaim:
          claimName: test-volume
EOF

```

```
kubectl get pvc -A

```

Output:

```
NAMESPACE   NAME            STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
default     test-volume     Bound     pvc-f7ceeaae-86aa-4ab3-9512-bb65f7d6c5f0   1Gi        RWO            cinder-csi      12h

```

```
openstack volume list

```

You should now have a working Kubernetes cluster with the OpenStack Cloud Provider enabled. You can now deploy your applications to the cluster.


# Kube deploy in Yandex cloud

<https://habr.com/ru/articles/727820/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-67edaeb621239f150577a2338dffd5ac7326cb79%2F2edee2be5212c5e896b6ac2a69c37dbd.png?alt=media)

Всем привет. Меня зовут Путилин Дмитрий (Добрый Кот) [Telegram](https://t.me/Dobry_kot).

От коллектива [FR-Solutions](https://t.me/fraima_ru) и при поддержке [@irbgeo](https://habr.com/users/irbgeo) [Telegram](https://t.me/irbgeo) : Продолжаем серию статей о K8S.

В этой статье мы поделимся своим опытом разработки Managed K8S под Yandex Cloud и расскажем, как мы создали конфигурацию, которую можно легко адаптировать для запуска в любом облаке или on-premises решении, изменяя только некоторые настройки. Если вы заинтересованы в построении гибких и масштабируемых Kubernetes-кластеров, то этот материал обязательно для вас.

## В предыдущих статьях

Базовая организации сертификатов в kubeadm — [Сертификаты K8S или как распутать вермишель Часть 1](https://habr.com/ru/articles/673730/).

Как начать использовать внешний PKI сторедж Vault для хранения и выписывания сертификатов для k8s control‑plane — [Сертификаты K8S или как распутать вермишель Часть 2](https://habr.com/ru/articles/695344/).

Как развернуть Kubernetes кластер по принципу Hard Way — [Kubernetes the hard way](https://habr.com/ru/articles/699074/).

## Проблема

Из моего личного опыта могу сказать, что Managed решения в облаках или в онпрем‑серверах — это отличный инструмент для создания своего продукта, и зачастую этого достаточно. Однако, бывают ситуации, когда нужно больше гибкости и возможностей настройки, а Managed решение предоставляет ограниченный набор функций.

Для нас было критично использовать сетевой плагин Cilium с нашими настройками, также нам требовались флаги feature‑gates, которых по дефолту нет в Yandex K8S API.

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

## Выбор инструментов

Для данной задачи однозначно требуются cloud native инструменты, поэтому выбор пал на Terraform. Остались вопросы: как настраивать узлы, нужен ли нам Ansible, Puppet, SaltStack? После 3 месяцев поиска золотой пилюли мы поняли, что для создания кластера нам потребуется только Terraform и cloud‑init.

## Архитектура

Так как в основе нашего продукта лежит Terraform, то одно из условий работы с ним — Сервисно‑ресурсная модель (СРМ).

Ресурсами выступают все его компоненты, от балансировщика нагрузки до конфигураци cloud‑init для нашего кластера.

Также CPM позволяет менять одинаковые типы ресурсов без потребности в смене процесса деплоя кластера, таким образом, описав модули создания инфраструктуры под Yandex Cloud, VK Cloud и т. п., и, поменяв намеример модуль Yandex cloud на модуль VK Cloud, получим тот же результат, но в другом окружении.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8e428106c6448053800613ac58cb261c556cbec3%2F5f6365654bef90b37098c7b84eea2afb.png?alt=media)

[**Сертификаты**](https://github.com/fraima/terraform-modules/blob/main/modules/k8s-config-vars/locals.certs.tf)

Наиболее значимым и сложным этапом было разработать подход работы с сертификатами, проблема была упомянута в предыдущих статьях. Мы определили основные спецификации для сертификатов и описали ресурсы Vault, которые создаются на основе содержимого [**спецификации**](https://github.com/fraima/terraform-modules/blob/main/modules/k8s-config-vars/locals.certs.tf). Однако возник вопрос доставки ключей/токенов на мастер-узлы, чтобы клиент на узле мог запросить сертификаты, указанные в спецификации. Было рассмотрено несколько вариантов решения этой проблемы:

* Для получения secret\_id и role\_id от Approle можно использовать временный токен, который имеет ограниченный доступ. Для этого токен должен иметь достаточно длительный срок жизни, чтобы виртуальная машина успела запустить клиента, или можно указать, что использование токена допустимо только один раз.
* Использование сервиса IAM от облачного провайдера для сохранения secret\_id/role\_id для каждой машины в облаке. Затем, можно использовать cloud-cli для получения необходимых секретов прямо с хоста.

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

\*\*[Переменные окружения](https://github.com/fraima/terraform-modules/tree/main/modules/k8s-config-vars)\*\*При написании кода мы поняли, что описывать каждый модуль с его входными и выходными переменными - это трудоемкий процесс, особенно когда возникают повторы. Через некоторое время мы решили, что имеет смысл выделить отдельный модуль, содержащий переменные, которые используются в нескольких модулях. Таким образом, мы смогли уменьшить объем входных аргументов каждого модуля и привести их к более компактному формату: [**каталог**](https://github.com/fraima/terraform-modules/blob/main/modules/k8s-config-vars/outputs.tf)

```
variable "k8s_global_vars" {
  description = "module:K8S-GLOBAL VARS"
  type        = any
  default     = {}
}

```

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

```
locals {

  k8s-addresses = {
    local_api_address           = format("%s.1",  join(".", slice(split(".",local.k8s_network.service_cidr), 0, 3)) )
    dns_address                 = format("%s.10", join(".", slice(split(".",local.k8s_network.service_cidr), 0, 3)) )

    idp_provider_fqdn           = format("auth.%s"          , local.cluster_metadata.base_domain)
    base_cluster_fqdn           = format("%s.%s"            , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
    wildcard_base_cluster_fqdn  = format("%s.%s.%s", "*"    , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
    etcd_server_lb_fqdn         = format("%s.%s.%s", "etcd" , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
  }
}

output "k8s-addresses" {
  value = local.k8s-addresses
}
```

[**Cloud init**](https://github.com/fraima/terraform-modules/blob/main/modules/k8s-templates/cloud-init-master/templates/cloud-init-kubeadm-master-fraima.tftpl#L130)

Генерация cloud-init конфигурации является не менее важным аспектом, поскольку эта конфигурация передается виртуальной машине при ее создании.

В первых версиях мы были вынуждены описывать каждый файл, создавать шаблоны для них и выносить их в отдельные модули по логическому смыслу, например, модуль containerd" включал в себя конфигурационные файлы и шаблоны для systemd сервисов. Однако, такой подход был слишком трудоемким в поддержке из-за большого количества модулей.

Мы решили использовать подход, подобный kubeadm. Сначала мы попытались развернуть кластер с помощью kubeadm, но выяснилось, что он не может выполнить первоначальную настройку системы, такую как установка пакетов, добавление конфигурационных файлов и запуск сервисов. Поэтому мы начали разработку инструмента, который бы мог настроить систему до требуемого состояния. Результатом этой работы стал fraimctl - инструмент, который заменил множество шаблонов одной командой `fraimctl init`.

Таким образом, нам оставалось описать:

* базовый [**конфиг**](https://github.com/fraima/terraform-modules/blob/a2a11072deabdbe2b71907cc6cd4874734bced65/modules/k8s-templates/cloud-init-master/templates/cloud-init-kubeadm-master-fraima.tftpl#L187) fraimctl (устанавливает все компоненты и готовит конфиги к ним);
* базовый [**конфиг**](https://github.com/fraima/terraform-modules/blob/a2a11072deabdbe2b71907cc6cd4874734bced65/modules/k8s-templates/cloud-init-master/templates/cloud-init-kubeadm-master-fraima.tftpl#L130) kubeadm (генерит статик под манифесты и чекает, что кластер поднят);
* базовый [**конфиг**](https://github.com/fraima/terraform-modules/blob/a2a11072deabdbe2b71907cc6cd4874734bced65/modules/k8s-templates/cloud-init-master/templates/cloud-init-kubeadm-master-fraima.tftpl#L97) key-keeper (клиент который запрашивает сертификаты).

У нас есть несколько задач, которые мы должны выполнить, чтобы полностью отказаться от kubeadm. Мы планируем перенести этап создания конфигурационных файлов key-keeper, kubeconfig и static pod manifests в fraimctl. Кроме того, мы добавим функционал для проверки готовности сертификатов и кластера, а также этап маркировки узлов. Это позволит нам полностью отказаться от использования kubeadm и не зависеть от этого инструмента.

[**Fraimctl**](https://github.com/fraima/fraima)

Как уже упоминалось ранее, этот инструмент создан для возможности полного отказа от использования kubeadm и настройки кластеров без его использования.

Пример конфигурациооного файла:

```jsx
fraimctl.conf
- apiVersion: fraima.io/v1alpha
  kind: Containerd
  spec:

    service:
      extraArgs:
        # This document provides the description of the CRI plugin configuration. 
        # The CRI plugin config is part of the containerd config
        # Default: /etc/containerd/config.toml
        config: /etc/kubernetes/containerd/config.toml

    configuration:
      extraArgs:
        version: 2
        plugins:
          io.containerd.grpc.v1.cri:
            containerd:
              runtimes:
                runc:
                  # Runtime v2 introduces a first class shim API for runtime authors to integrate with containerd. 
                  # The shim API is minimal and scoped to the execution lifecycle of a container.
                  runtime_type: "io.containerd.runc.v2"
                  options:
                    # While containerd and Kubernetes use the legacy cgroupfs driver for managing cgroups by default, 
                    # it is recommended to use the systemd driver on systemd-based hosts for compliance of the "single-writer" rule of cgroups. 
                    # To configure containerd to use the systemd driver, set the following option:
                    SystemdCgroup: true

    downloading:
      - name: containerd
        src: https://github.com/containerd/containerd/releases/download/v1.6.6/containerd-1.6.6-linux-amd64.tar.gz
        checkSum:
          src: https://github.com/containerd/containerd/releases/download/v1.6.6/containerd-1.6.6-linux-amd64.tar.gz.sha256sum
          type: "sha256"
        path: /usr/bin/
        owner: root:root
        permission: 0645
        unzip:
          status: true
          files: 
            - bin/containerd
            - bin/containerd-shim
            - bin/containerd-shim-runc-v1
            - bin/containerd-shim-runc-v2
            - bin/containerd-stress
            - bin/ctr

      - name: runc
        src: https://github.com/opencontainers/runc/releases/download/v1.1.3/runc.amd64
        path: /usr/bin/
        owner: root:root
        permission: 0645

    starting:
      - systemctl enable containerd
      - systemctl start containerd
```

Каждый компонент имеет четыре стадии:

* **downloading** (загружает бинарные файлы, проверяет контрольные суммы, распаковывает необходимые компоненты и размещает их в соответствующих папках.)
* **service** (генерирует службу systemd, и с помощью параметра extraArgs можно настроить ее поведение под свои нужды.)
* **configuration** (генерирует конфигурацию для службы systemd, и с помощью параметра extraArgs можно настроить ее поведение под свои нужды.)
* **starting** (выполняет необходимые команды после первых трех этапов.)

Одной из ключевых особенностей этого инструмента является этап загрузки (Downloading), который загружает бинарные файлы компонентов. Это позволяет не зависеть от производителя операционной системы и разворачивать единым подходом на любом хосте, не нужно думать о множестве условий (if else) и о том какая операционная система в основе.

Также предусмотрены отдельные конфигурационные файлы для настройки sysctl и modprobe.

```jsx
fraimctl.conf
- apiVersion: fraima.io/v1alpha
  kind: Sysctl
  spec:
    configuration:
      extraArgs:
        net.ipv4.ip_forward: 1
    starting:
      - sudo sysctl --system

- apiVersion: fraima.io/v1alpha
  kind: Modprob
  spec:
    configuration:
      extraArgs:
      - br_netfilter
      - overlay
    starting:
      - sudo modprobe overlay
      - sudo modprobe br_netfilter
      - sudo sysctl --system
```

### Инфраструктура

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c1bb52d7d5453cf64c4a5ce66a26d658df94c7d4%2F038591d7a140c7d3a4bc0d1b82654cb5.png?alt=media)

Каждый кубик в Terraform представляет собой ресурс и логически определяется как класс в языке программирования. Мы можем определить класс, например, loadBalancer, который принимает определенный набор аргументов и возвращает структуру, которая также заранее определена. Это означает, что мы можем изменять кубики по нашему усмотрению, а при смене облака все компоненты будут взаимодействовать друг с другом благодаря структуре входных и выходных параметров.

Благодаря этой архитектуре мы можем обновлять операционные системы без проблем и даже менять производителя операционной системы на лету.

```jsx
kubectl get no -o wide

root@master-2-cluster-2:/home/dkot# kubectl get nodes -o wide
NAME                 STATUS   ROLES                  AGE     VERSION    INTERNAL-IP   EXTERNAL-IP     OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master-1-cluster-2   Ready    control-plane,master   2m50s   v1.23.12   10.1.0.11     51.250.66.122   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.8
master-2-cluster-2   Ready    control-plane,master   2m53s   v1.23.12   10.2.0.33     84.201.139.95   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.8
master-3-cluster-2   Ready    control-plane,master   2m55s   v1.23.12   10.3.0.21     51.250.40.244   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.8

root@master-2-cluster-2:/home/dkot# kubectl get nodes -o wide
NAME                 STATUS   ROLES                  AGE   VERSION    INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                       KERNEL-VERSION    CONTAINER-RUNTIME
master-1-cluster-2   Ready    control-plane,master   37s   v1.23.12   10.1.0.12     62.84.119.244   Debian GNU/Linux 10 (buster)   4.19.0-18-amd64   containerd://1.6.8
master-2-cluster-2   Ready    control-plane,master   12m   v1.23.12   10.2.0.16     51.250.27.187   Debian GNU/Linux 10 (buster)   4.19.0-18-amd64   containerd://1.6.8
master-3-cluster-2   Ready    control-plane,master   12m   v1.23.12   10.3.0.13     51.250.45.49    Debian GNU/Linux 10 (buster)   4.19.0-18-amd64   containerd://1.6.8
```

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

## Реализация

Давайте рассмотрим базовый проект и то, как можно начать использовать этот инструмент.

1. Скачиваем репозиторий <https://github.com/fraima/kubernetes>
2. В этом репозитории есть несколько разделов
   1. [infrastructure-vault](https://github.com/fraima/kubernetes/tree/main/infrastructure-vault) (создает рут PKI в Vault)
   2. [infrastructure-yandex](https://github.com/fraima/kubernetes/tree/main/infrastructure-yandex) (создает базовую конфигурацию в YC, которая включает в себя создание VPC, таблицы маршрутизации и создание сервисных аккаунтов по умолчанию)
   3. [infrastructure-keycloak](https://github.com/fraima/kubernetes/tree/main/infrastructure-keycloak) (устанавливает базовую конфигурацию для Keycloak, которая позволяет авторизоваться в кластере через этот инструмент)
   4. [k8s-yandex-cluster](https://github.com/fraima/kubernetes/tree/main/k8s-yandex-cluster) (проект-шаблон, который используется для создания кластера.)
3. Заходим в каждый раздел по очереди и применяем, что прописано в Readme.

### Подготовка

Для начала работы вам понадобятся переменные для подключения к Vault, YC и Keycloak.

```jsx
environments
export TF_VAR_YC_CLOUD_ID=""
export TF_VAR_YC_FOLDER_ID=""
export TF_VAR_YC_TOKEN=""
export TF_VAR_YC_ZONE=""
export TF_VAR_VAULT_TOKEN=""
export TF_VAR_VAULT_ADDR=""
export TF_VAR_KEYCLOAK_REALM=""
export TF_VAR_KEYCLOAK_CLIENT_ID=""
export TF_VAR_KEYCLOAK_USER=""
export TF_VAR_KEYCLOAK_PASSWORD=""
export TF_VAR_KEYCLOAK_URL=""
Этот подход позволяет использовать Terraform в контейнере через инструмент CI/CD, не указывая реальные значения переменных в провайдерах.
```

Если вы работаете с чистым Terraform, не забывайте выделять каждый кластер в отдельный workspace.

```
terraform workspace new example

terraform plan    -var-file vars/example.tfvars
terraform apply   -var-file vars/example.tfvars
terraform destroy -var-file vars/example.tfvars
```

### Инит конфиг

Основная конфигурация зависит от двух файлов в проекте.

* locals.defaults.tf - базовые значения, которые определены для всех наших кластеров.
* vars/${cluster\_name}.tf - переменные, которые специально указаны для конкретного кластера.

```jsx
vars/${cluster_name}.tf
global_vars = {
    cluster_name    = "example"
    pod_cidr        = "10.102.0.0/16"

    serviceaccount_k8s_controllers_name = "yandex-k8s-controllers"

    kube_apiserver_flags = {
        oidc-issuer-url         = "https://auth.dobry-kot.ru/auth/realms/master"
        oidc-client-id          = "kubernetes-clusters"
        oidc-username-claim     = "sub"
        oidc-groups-claim       = "groups"
        oidc-username-prefix    = "-"
    }

    kube_controller_manager_flags = {
        cluster-name = "kubernetes"
    }

    kube_scheduler_flags = {
        
    }

    addons = {
        cilium = {
            enabled = true
            extra_values = {
                cluster = {
                    name = "example"
                    id = 12
                }
            }
        }

        vault-issuer = {
            enabled = true
            extra_values = {}
        }

        coredns = {
            enabled = true
            extra_values = {}
        }

        gatekeeper = {
            enabled = true
            extra_values = {}
        }

        certmanager = {
            enabled = true
            extra_values = {}
        }

        machine-controller-manager = {
            enabled = true
            extra_values = {}
        }

        yandex-cloud-controller = {
            enabled = true
            extra_values = {}
        }

        yandex-csi-controller = {
            enabled = true
            extra_values = {}
        }

        compute-instance = {
            enabled = true
            custom_values = {
                subnet_id   = "e9bndv0b3c5asheadg09"
                zone        = "ru-central1-a"
                image_id    = "fd8ingbofbh3j5h7i8ll"
                replicas    = 1
            }
            extra_values = {
                metadata = {
                    nodeLabels = {
                        "node-role.kubernetes.io/worker" = ""
                        "provider" = "yandex"    
                    }
                    cloudLabels = {
                        tair = "critical"
                    }
                }
            }
        }
    }

}

cloud_metadata = {
    cloud_name  = "cloud-uid-vf465ie7"
    folder_name = "example"
}

master_group = {
    name                = "master"
    count               = 3

    default_subnet      = "10.0.0.0/24"
    default_zone        = "ru-central1-a"

    metadata = {
        # user_data_template = "fraima-hbf"
        user_data_template = "fraima"
    }
}
```

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

Например, мы можем изменить или добавить флаги Kube-apiserver с помощью переменной "kube\_apiserver\_flags".

В файле с переменными на данный момент определены три группы.

1. "master\_group" определяет, какие мастера следует заказать, в какой подсети они будут находиться, в какой зоне, будут ли они в разных зонах или нет, а также количество мастер-нод (это значение можно определить только один раз, изменить его с 1 на 3 в настоящее время невозможно).
2. "global\_vars" определяет будущую конфигурацию кластера, включая его имя, подсети для подов, флаги для компонент, которые будут использоваться, а также какие аддоны будут добавлены.
3. "cloud\_metadata" содержатся указатели на облачный провайдер, такие как cloud\_name" и "folder\_name".

### Запускаем

```
time terraform apply -var-file vars/example.tfvars  -auto-approve
```

По умолчанию будет развернут кластер с тремя мастер-нодами, каждая из которых имеет 6 CPU, 12 ГБ оперативной памяти и 100 ГБ дискового пространства, а также 10 ГБ для ETCD.

Для каждого кластера будет создан внешний балансер, к которому вы сможете подключиться. Также будут созданы аддоны, которые настроят сеть, базовые интеграции с YC, такие как CSI driver, Cloud Controller и Machine Controller Manager для заказа воркер-нод в облаке.

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

```
Apply complete! Resources: 86 added, 0 changed, 0 destroyed.

Outputs:

LB-IP = "kubectl config set-cluster  cluster --server=https://158.160.63.64:443 --insecure-skip-tls-verify"

real    6m4,698s
user    0m24,182s
sys     0m1,582s

dk@dobry-kot-system:~/workspace/fraima/kubernetes/k8s-yandex-cluster-naked$ kubectl get nodes -o wide
NAME                STATUS   ROLES                  AGE     VERSION    INTERNAL-IP   EXTERNAL-IP      OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master-50d858e0-1   Ready    control-plane,master   9m34s   v1.23.12   10.0.0.12     158.160.51.95    Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
master-50d858e0-2   Ready    control-plane,master   9m33s   v1.23.12   10.0.0.6      158.160.38.139   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
master-50d858e0-3   Ready    control-plane,master   9m34s   v1.23.12   10.0.0.19     158.160.42.200   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
```

Вы можете заметить, что кластер успешно запущен и функционирует. Кроме того, у узлов теперь есть внешние IP-адреса и свидетельствует о том, что интеграция с YC работает.

Если вы используете keycloak для подключения, не забудьте установить плагин "kubectl login" и воспользоваться универсальным kubeconfig.

```jsx
kubeconfig
apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://158.160.63.64:443
  name: cluster
contexts:
- context:
    cluster: cluster
    namespace: kube-fraima-machine-controller-manager
    user: cluster
  name: cluster
current-context: cluster
kind: Config
preferences: {}
users:
- name: cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - oidc-login
      - get-token
      - --oidc-issuer-url=https://$KEYCLOAK-SERVER/auth/realms/master
      - --oidc-client-id=kubernetes-clusters
      - --oidc-client-secret=kube-client-secret
      - --certificate-authority=/usr/local/share/ca-certificates/oidc-ca.pem
      - --skip-open-browser
      - --grant-type=password
      - --username=$USERNAME
      - --password=$PASSWORD
      command: kubectl
      env:
      - name: context
        value: $(kubectl config current-context)
      interactiveMode: IfAvailable
      provideClusterInfo: false
```

## Наполнение

```
NAME                                     STATUS   AGE
default                                  Active   11m
kube-fraima-certmanager                  Active   8m48s # CERTMANAGER
kube-fraima-dns                          Active   9m57s # COREDNS
kube-fraima-machine-controller-manager   Active   8m55s
kube-fraima-opa                          Active   9m44s # GATEKEEPER
kube-fraima-sdn                          Active   10m   # CILIUM
kube-fraima-yandex-cloud-controller      Active   11m
kube-fraima-yandex-csi-controller        Active   9m54s
kube-node-lease                          Active   11m
kube-public                              Active   11m
kube-system                              Active   11m
```

## Внимание

Одной из важных особенностей этих кластеров является отсутствие приватных ключей от СА на мастерах, так как они хранятся в VAULT. Однако, такой подход приводит к определенным проблемам.

Вы можете добавить любую ноду в кластер через csr bootstraping, где нода генерирует запрос на сертификат и отправляет его в API, а затем вы подтверждаете этот запрос и нода получает свои сертификаты и добавляется в кластер. Однако, в данной инсталляции это нельзя сделать стандартными средствами.

Поскольку kube-controller-manager занимается выдачей сертификатов для узлов, то без доступа к приватному ключу CA этот функционал теряется. Однако, мы нашли способ получить сертификаты, установив Certmanager и Gatekeeper, а затем настроив ClusterIssuer в Certmanager для интеграции с VAULT. С помощью этого ClusterIssuer можно будет выписывать сертификаты только для worker/master узлов. Затем в Gatekeeper настраиваем мутацию ресурса CSR, который изменит базовый SIGNERNAME с "[kubernetes.io/kubelet-serving](http://kubernetes.io/kubelet-serving)" на "[clusterissuers.cert-manager.io/vault-issuer](http://clusterissuers.cert-manager.io/vault-issuer)". Таким образом, мы сможем получить необходимые сертификаты.

```
dk@dobry-kot-system:~/Downloads$ kubectl get csr
NAME                                                   AGE     SIGNERNAME                                    REQUESTOR                       REQUESTEDDURATION   CONDITION
csr-52cx7                                              12m     kubernetes.io/kubelet-serving                 system:node:master-50d858e0-3   <none>              Pending
csr-lbhqf                                              12m     kubernetes.io/kubelet-serving                 system:node:master-50d858e0-1   <none>              Pending
csr-n27p4                                              12m     kubernetes.io/kubelet-serving                 system:node:master-50d858e0-2   <none>              Pending
node-csr-3l5VT-i7YinQWaTvbCY467d27GQLnSqnT_BYgk_PFII   8m17s   clusterissuers.cert-manager.io/vault-issuer   system:bootstrap:663273         <none>              Pending

```

Как вы можете заметить, новый узел запросил сертификат через CSR, но SIGNERNAME у него установлен как "[clusterissuers.cert-manager.io/vault-issuer](http://clusterissuers.cert-manager.io/vault-issuer)". После подтверждения этого

запроса Certmanager выдаст сертификат, который будет храниться во внешнем хранилище Vault.

```
kubectl certificate approve node-csr-3l5VT-i7YinQWaTvbCY467d27GQLnSqnT_BYgk_PFII
```

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

```
dk@dobry-kot-system:~/Downloads$ kubectl get no -o wide
NAME                                         STATUS   ROLES                  AGE   VERSION    INTERNAL-IP   EXTERNAL-IP      OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master-50d858e0-1                            Ready    control-plane,master   24m   v1.23.12   10.0.0.12     158.160.51.95    Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
master-50d858e0-2                            Ready    control-plane,master   24m   v1.23.12   10.0.0.6      158.160.38.139   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
master-50d858e0-3                            Ready    control-plane,master   24m   v1.23.12   10.0.0.19     158.160.42.200   Ubuntu 20.04.4 LTS   5.4.0-124-generic   containerd://1.6.6
worker-yandex-compute-instance-68ffc-f2sd2   Ready    worker                 11m   v1.23.12   10.154.0.11   51.250.72.216    Ubuntu 22.04.1 LTS   5.15.0-46-generic   containerd://1.6.6

```

## Планы

1. Расширить функционал Fraimctl, чтобы отказаться от использования Kubeadm.
2. Написать инфраструктурные модули для AWS и VK-Cloud.
3. Покрыть Terraform тестами.
4. Организовать модули более четко и удалить ненужное.
5. Перейти с использования Terraform + Helm на Terraform + Flux.
6. Написать расширение для K8S API для добавления нашего кастомного функционала.
7. Добавить инструмент для настройки узлов как Day2 операций.

У нашего коллектива амбициозные планы и мы нацелены на получение статуса CNCF.

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

## Контакты

terraform modules: [**https://github.com/fraima/terraform-modules**](https://github.com/fraima/terraform-modules)

terraform cluster: [**https://github.com/fraima/kubernetes**](https://github.com/fraima/kubernetes)

telegram community: [**https://t.me/fraima\_ru**](https://t.me/fraima_ru)**telegram me:** [**https://t.me/Dobry\_kot**](https://t.me/Dobry_kot)


# Frameworks

[Deckhouse](/readme/architect/kubernetes/frameworks/deckhouse)

[K3S](/readme/architect/kubernetes/frameworks/k3s)

[OpenShift OKD](/readme/architect/kubernetes/frameworks/openshift-okd)

[RKE2](/readme/architect/kubernetes/frameworks/rke2)

[Rancher](/readme/architect/kubernetes/frameworks/rancher)


# Deckhouse

<https://deckhouse.io/>

Deckhouse is a Kubernetes platform that allows you to create homogeneous K8s clusters on any infrastructure. It manages clusters comprehensively and “automagically” and provides all necessary modules and add-ons for autoscaling, observability, security, and service mesh implementation. Deckhouse has vanilla Kubernetes under the hood and integrates a balanced set of Open Source tools that have become the industry standard.

Deckhouse is [CNCF certified](https://landscape.cncf.io/card-mode?category=certified-kubernetes-distribution,certified-kubernetes-hosted,certified-kubernetes-installer\&grouping=category\&selected=flant-deckhouse).

[Yandex Cloud Install](/readme/architect/kubernetes/frameworks/deckhouse/yandex-cloud-install)

[On premise Install](/readme/architect/kubernetes/frameworks/deckhouse/on-premise-install)

[LDAP authentification](/readme/architect/kubernetes/frameworks/deckhouse/ldap-authentification)


# LDAP authentification

<https://habr.com/ru/companies/flant/articles/710010/>

[Deckhouse](https://deckhouse.ru/) — Kubernetes-платформа с открытым кодом, с помощью которой можно создавать идентичные Kubernetes-кластеры в любой инфраструктуре и автоматически управлять ими. Для проверки подлинности в Deckhouse используется модуль [user-authn](https://deckhouse.ru/documentation/v1/modules/150-user-authn/). Он настраивает единую систему аутентификации, интегрированную с Kubernetes и веб-интерфейсами других модулей — например, с Grafana.

user-authn поддерживает несколько внешних провайдеров и протоколов аутентификации: GitHub, GitLab, Bitbucket Cloud, Crowd, LDAP и OIDC. В статье расскажу, как развернуть сервер LDAP и настроить через него доступ к приложению.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e4591b0cb469c4aa371b786b311a876b57338b73%2F3eebe98df5289896b4dad1571e50c67a.png?alt=media)

## Подготовка

Нам потребуется кластер Kubernetes с установленной платформой Deckhouse. Кластер [можно развернуть](https://deckhouse.ru/gs/) в облаке или локально (на kind). Установка занимает от 15 до 30 минут.

[В конфигурации](https://deckhouse.ru/documentation/v1/deckhouse-configure-global.html) Deckhouse в параметре [publicDomainTemplate](https://deckhouse.ru/documentation/v1/deckhouse-configure-global.html#parameters-modules-publicdomaintemplate) должен быть корректный шаблон DNS-имен, который указывает на IP-адреса Ingress-контроллеров кластера. Также можно воспользоваться сервисом типа [sslip.io](https://sslip.io/); в этом случае в publicDomainTemplate достаточно будет указать шаблон `%s.x.x.x.x.sslip.io`, где `x.x.x.x` — ваш IP-адрес.

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

## Настройка LDAP-сервера

Развернем LDAP-сервер:

```
kubectl create -f https://raw.githubusercontent.com/flant/examples/master/2022/11-d8-user-authn/ldap.yaml
```

Прежде чем продолжить, убедимся, что Pod сервера запустился, то есть в статусе `Running`:

```
# kubectl -n openldap-demo get pod
NAME                                            READY   STATUS    RESTARTS   AGE
ldap-6c949b6c6d-4zxg7                    1/1      Running     0                   1m
```

В конфигурации развернутого LDAP-сервера есть два пользователя:

* `johndoe@example.com` (пароль `bar`) — входит в группу `admins`;
* `janedoe@example.com` (пароль `foo`) — входит в группы `admins` и `developers`.

Далее, для примера, предоставим доступ пользователю `janedoe@example.com` из группы `developers`.

Создадим в кластере ресурс DexProvider, который «подключит» созданный LDAP-сервер и будет использоваться при аутентификации:

```
kubectl create -f https://raw.githubusercontent.com/flant/examples/master/2022/11-d8-user-authn/dex-provider.yaml
```

## Настройка веб-приложения

Развернем в качестве приложения простой echo-server, который выводит на страницу информацию об HTTP-запросе.

Выполним команду, указав в переменной `DOMAINNAME` используемый вами домен:

```
DOMAINNAME=<ВАШ_ДОМЕН>
curl https://raw.githubusercontent.com/flant/examples/master/2022/11-d8-user-authn/echo-service.yaml | sed "s/{{ __cluster__domain__ }}/${DOMAINNAME}/" | kubectl create -f -
```

Приложение будет развернуто в пространстве имен `openldap-demo`. Ingress-ресурс приложения будет настроен на поддомен `echo`.

Проверим, что Pod `echoserver` запустился:

```
# kubectl -n openldap-demo get pod
NAME                                            READY   STATUS    RESTARTS   AGE
echoserver-6944fb9c86-9flgh            1/1      Running     0                   1m
ldap-6c949b6c6d-4zxg7                    1/1      Running     0                   3m
```

Откроем браузер и убедимся, что приложение доступно по адресу `echo.<ВАШ_ДОМЕН>` без авторизации.

## Настройка аутентификации

Приступим к самому интересному: закроем доступ к приложению.

Чтобы включить аутентификацию, нужно:

* включить аутентификацию через развернутый экземпляр OAuth2 Proxy в Ingress-ресурсе приложения.

OAuth2 Proxy будет принимать запросы на аутентификацию от nginx и выполнять аутентификацию в LDAP.

Создадим ресурс DexAuthenticator, указав в переменной `DOMAINNAME` используемый вами домен:

```
DOMAINNAME=<ВАШ_ДОМЕН>
curl https://raw.githubusercontent.com/flant/examples/master/2022/11-d8-user-authn/dex-authenticator.yaml | sed "s/{{ __cluster__domain__ }}/${DOMAINNAME}/" | kubectl create -f -
```

Убедимся, что в пространстве имен `openldap-demo` появился и запустился Pod `echoserver-dex-authenticator`:

```
# kubectl -n openldap-demo get pod
NAME                                                         READY   STATUS    RESTARTS   AGE
echoserver-6944fb9c86-9flgh                     1/1         Running   0                    5m
echoserver-dex-authenticator-6bdd57cc95-wvbgk   2/2     Running   1          2m
ldap-6c949b6c6d-4zxg7                             1/1          Running   0                    8m
```

Посмотрим на ресурс DexAuthenticator. Обратите внимание на параметр [spec.allowedGroups](https://deckhouse.ru/documentation/v1/modules/150-user-authn/cr.html#dexauthenticator-v1-spec-allowedgroups): он содержит список групп, которым будет разрешен доступ к приложению. В нашем случае это группа `developers`, в которую входит пользователь `janedoe@example.com`:

```
# kubectl -n openldap-demo get dexauthenticator echoserver -o yaml
apiVersion: deckhouse.io/v1
kind: DexAuthenticator
...
spec:
  allowedGroups:
  - developers
...
```

Теперь настроим Ingess-контроллер так, чтобы он использовал OAuth2 Proxy.

Укажем две аннотации на Ingress-ресурсе приложения, выполнив команды:

```
kubectl -n openldap-demo annotate ingress echoserver 'nginx.ingress.kubernetes.io/auth-signin=https://$host/dex-authenticator/sign_in'
kubectl -n openldap-demo annotate ingress echoserver 'nginx.ingress.kubernetes.io/auth-url=https://echoserver-dex-authenticator.openldap-demo.svc.cluster.local/dex-authenticator/auth'
```

Обновим страницу приложения в браузере — сработает переадресация на страницу входа:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2cab5e6b48f469ad2a673cd4b4c53f0956d52524%2Fbe5e3c4635b930e6987bfa508b542ed8.png?alt=media)

Выберем способ входа через LDAP-сервер: *Log in with OpenLDAP Demo*. Войдем под пользователем `janedoe@example.com` (пароль в LDAP: `foo`).

Проверим, что все работает как нужно. Откроем браузер в безопасном режиме и попробуем войти под другим пользователем — `johndoe@example.com` (пароль в LDAP: `bar`). Получим ошибку `User not in allowed groups`, так как пользователь `johndoe` не состоит в группе `developers`.

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

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

## Добавление статического пользователя

Для аутентификации статического пользователя внешние провайдеры аутентификации не используются. Нужно создать custom resource [User](https://deckhouse.ru/documentation/v1/modules/150-user-authn/cr.html#user).

Создадим пользователя `openldap-demo@example.com`, который состоит в группе `developers`, с паролем `bar` и временем жизни учетной записи — 24 часа:

```
kubectl create -f - <<"EOF"
apiVersion: deckhouse.io/v1
kind: User
metadata:
 name: openldap-demo
spec:
 email: openldap-demo@example.com
 # echo "bar" | htpasswd -BinC 10 "" | cut -d: -f2
 password: '$2a$10$spCnoGzDIRicDfiTmtImwu7sn2Csjj6oWRoLjNs6N/bV3WDsxioui'
 groups:
   - developers
 ttl: 24h
EOF
```

> Обратите внимание: в комментарии к полю password приведены команды генерации пароля. Они пригодятся, если вы хотите использовать другой пароль.

После того, как пользователь создан, в приложение можно войти, выбрав *Log in with Email*.

Более подробную информацию о настройке аутентификации в кластере можно найти в описании модуля [user-authn](https://deckhouse.ru/documentation/v1/modules/150-user-authn/). Также в документации есть [готовые примеры](https://deckhouse.ru/documentation/v1/modules/150-user-authn/usage.html) использования модуля.

## Убираем за собой

Для удаления созданных выше ресурсов выполним:

```
kubectl delete dexprovider openldap-demo
kubectl delete user openldap-demo
kubectl delete ns openldap-demo
```


# On premise Install

<https://habr.com/ru/companies/flant/articles/717484/>

Продолжаем серию статей про установку Deckhouse в разные окружения. Мы уже рассказывали [про развертывание в Yandex Cloud](https://habr.com/ru/company/flant/blog/707422/). Эта статья посвящена установке платформы в закрытое окружение, когда у машин, на которых разворачивается кластер, нет доступа в Интернет.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-29cfb7e34545a45cc31dbf7b531c582a24852a7a%2F0c94f2344041a8f6f3b8ef24468d1cf7.png?alt=media)

Установка Deckhouse в закрытое окружение почти не отличается от установки на bare metal. Главные особенности:

* Чтобы предоставить приложениям доступ в Интернет в закрытом контуре, нужно явно указать параметры прокси-сервера [в конфигурации кластера](https://deckhouse.ru/documentation/v1/installing/configuration.html#parameters-proxy).
* Для обновлений или подключения дополнительных компонентов кластера необходимо [указать адрес](https://deckhouse.ru/documentation/latest/installing/configuration.html#initconfiguration-deckhouse-imagesrepo) развернутого хранилища с образами контейнеров Deckhouse, прописав в случае необходимости [параметры прав доступа](https://deckhouse.ru/documentation/latest/installing/configuration.html#initconfiguration-deckhouse-registrydockercfg).

Рассмотрим все необходимые этапы по порядку.

## Исходные данные и требования к установке

Пример схемы развертывания Deckhouse в закрытом контуре с прокси-сервером:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-74e0ac925831495459650db61fce9a9fcca9c7f1%2F72ad24a03767ae5228070d6a556d859d.png?alt=media)

Здесь между сетью Интернет и будущим кластером поднят прокси-сервер, через который предоставляется доступ к репозиториям пакетов ОС. Через этот же прокси-сервер можно открыть доступ в Интернет для приложений, настроив соответствующие параметры в конфигурации кластера. Однако использование прокси-сервера — не обязательное условие: кластер может работать и в полностью изолированном контуре.

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

## Требования

Для установки Deckhouse понадобятся персональный компьютер, а также два сервера (или ВМ).

Требования к ПК:

* ОС Ubuntu 18.04+, Fedora 35+, Windows 10+ или macOS 10.15+;
* Docker для запуска инсталлятора Deckhouse (см. инструкции по установке для [Ubuntu](https://docs.docker.com/engine/install/ubuntu/), [macOS](https://docs.docker.com/desktop/mac/install/), [Windows](https://docs.docker.com/desktop/windows/install/));
* SSH-доступ по ключу к master-узлу будущего кластера;
* доступ к развернутому хранилищу с образами контейнеров Deckhouse;
* [crane](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/Deckhouse/On%20premise%20Install/README/README.md), jq.

Требования к серверу или ВМ для master-узла:

* 4 ядра CPU;
* 8 Гб RAM;
* не менее 40 Гб на диске;
* установленная ОС ([на выбор](https://deckhouse.ru/documentation/v1/supported_versions.html));
* доступ к развернутому хранилищу с образами контейнеров Deckhouse;
* доступ к прокси-серверу для скачивания deb/rpm-пакетов ОС (при необходимости);
* SSH-доступ от персонального компьютера (см. п.1) по ключу;
* на узле не должно быть установлено пакетов container runtime, например containerd или Docker.

Также нужен сервер или ВМ для развертывания хранилища образов Deckhouse и образов приложений.

## Установка container registry

В качестве container registry будем использовать [Harbor](https://goharbor.io/) — популярный Open Source-инструмент, с помощью которого можно развернуть self-hosted хранилище Docker-образов.

### Подготовка машины

[В официальной документации](https://goharbor.io/docs/2.7.0/install-config/) разработчики Harbor рекомендуют следующие минимальные характеристики машины для хранилища:

* 2 ядра CPU;
* 4 Гб RAM;
* 40 Гб на жестком диске.

*Рекомендуемые требования: 4 ядра, 8 Гб оперативной памяти и 160 Гб на жестком диске.*

Для тестов возьмем машину с минимальными требованиями, установленной Ubuntu 22.04 и без прямого доступа к Интернету.

Помимо требований к «железу» в документации указаны также и требования к установленному ПО:

* Docker Engine 17.06.0+;
* Docker Compose;
* OpenSSL (желательно последней доступной версии).

Для установки софта требуется доступ к репозиториям пакетов дистрибутива. Временно предоставим его через поднятый прокси-сервер.

*Настройка прокси или NAT в этой статье не рассматривается, потому что выходит за ее рамки. К тому же это процесс зависит от инфраструктуры, на которой разворачивается Deckhouse*.

### Установка Docker Engine

Подключимся по SSH к машине и добавим новый репозиторий в `/etc/sources.list`:

```
sudo apt update
sudo apt install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
```

Добавим GPG-ключи репозитория:

```
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
```

Подключим новый репозиторий:

```
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

Установим последнюю версию Docker Engine:

```
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

Убедимся, что Docker Engine работает:

```
sudo docker run hello-world
```

Если все прошло успешно, будет запущен тестовый контейнер, который выведет сообщение `Hello from Docker!`.

### Установка Docker Compose

Установим docker-compose командой:

```
sudo apt install docker-compose
```

### Установка OpenSSL

Скорее всего, последняя версия OpenSSL уже установлена в системе. Если нет, выполним команду:

```
sudo apt install openssl
```

### Установка Harbor

Harbor поддерживает установку двумя способами — онлайн и офлайн. У обоих схожий принцип. Но поскольку мы уже настроили доступ в Интернет на время подготовки машины к установке, воспользуемся первым вариантом.

В соответствии [с официальной документацией](https://goharbor.io/docs/2.7.0/install-config/download-installer/) скачиваем с GitHub [последний актуальный релиз](https://github.com/goharbor/harbor/releases) (на момент написания статьи это v2.5.5):

```
$ curl -OL https://github.com/goharbor/harbor/releases/download/v2.5.5/harbor-online-installer-v2.5.5.tgz
```

*Ключ **L** нужен для того, чтобы curl прошел по всем редиректам, которые будет предлагать ему GitHub. Если попытаться просто скачать файл (только ключ **-O**), велика вероятность, что он окажется пустым.*

Распаковываем установщик:

```
$ tar -xzvf ./harbor-online-installer-v2.5.5.tgz
harbor/prepare
harbor/LICENSE
harbor/install.sh
harbor/common.sh
harbor/harbor.yml.tmpl
```

### Настройка перед установкой

Harbor настраивается в файле `harbor.yml`. В распакованном архиве есть его шаблон с расширением `*.tmpl`, в котором уже заданы рекомендуемые параметры.

Переименуем шаблон в `harbor.yml` и отредактируем нужные параметры\*\*:\*\*

```
$ mv ./harbor.yml.tmpl ./harbor.yml
$ vim ./harbor.yml
```

На что следует обратить внимание:

* `HTTPS` — важен, если хранилище используется в production. Для настройки поддержки HTTPS необходимо [добавить соответствующие сертификаты](https://goharbor.io/docs/2.7.0/install-config/configure-https/). В нашем случае можно обойтись без него, поэтому закомментируем эти строки.
* `hostname` — имя хоста хранилища образов. Это может быть как доменное имя, так и IP-адрес.
* `harbor_admin_password` — пароль администратора для входа в систему.

### Установка

Запускаем установку командой:

```
sudo ./install.sh
```

Установщик скачает все необходимые для работы Harbor образы и запустит сервис. Если все прошло успешно, в конце лога будет сообщение `✔ ----Harbor has been installed and started successfully.----`.

Откроем браузер на машине, с которой будет разворачиваться Deckhouse, и перейдем по адресу машины, на которой развернут Harbor.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0da00ff182d6ab903ea8e9f04a974a14b67eaf3e%2F24b220568a9ebe7e9f3c801815f22dd3.png?alt=media)

Страница входа в Harbor

Теперь переходим к установке платформы.

## Установка Deckhouse

### Получение образов Deckhouse

Для работы с Deckhouse необходим доступ к образам контейнеров, которые нужны для его работы. Получить доступ можно двумя способами:

* Настроить Proxy Cache в Harbor. В этом режиме он будет работать как прокси-сервер для всех запросов к хранилищу `https://registry.deckhouse.io`**,** кэшируя получаемые образы и раздавая их в закрытое окружение.
* Перенести в Harbor образы вручную. Актуально, если при установке использовался офлайн-способ, и доступа наружу из окружения нет.

Рассмотрим первый вариант. (О ручном переносе образов можно прочитать [в документации](https://deckhouse.ru/documentation/v1/deckhouse-faq.html#%D1%80%D1%83%D1%87%D0%BD%D0%B0%D1%8F-%D0%B7%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B0-%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2-%D0%B2-%D0%B8%D0%B7%D0%BE%D0%BB%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9-%D0%BF%D1%80%D0%B8%D0%B2%D0%B0%D1%82%D0%BD%D1%8B%D0%B9-registry).)

### Настройка Proxy Cache

Войдем в систему: имя пользователя по умолчанию `admin`, пароль — тот, что указан в конфигурационном файле.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b7a7afd67449ffcb292e6e450a0906a57adec285%2Fb74f9893bb25cb05401f267b21b25b0a.png?alt=media)

Главная страница интерфейса Harbor

*При необходимости имя пользователя можно изменить в настройках профиля.*

Перейдем на страницу *Administration* → *Registries* → *New Endpoint*:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-eb4390c36c3e07f47599eccd82d1782c8b70a4d3%2Fa33525a655840ad0bc9dc10bceaa3297.png?alt=media)

В открывшемся окне настроим следующие параметры:

* *Provider*: Docker Registry.
* *Name* — имя, может быть любым.
* *Description* — краткое описание, можно оставить пустым.
* *Endpoint URL*: `https://registry.deckhouse.io`.
* *Access ID* и *Access Secret* — если используется Deckhouse Enterprise Edition; в нашем случае оставляем пустым.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-99b2b1e297b1dfd2e8c3a947eb01f92e4f6a200b%2Fdcc82de30550ad8e89eeda3e2019163a.png?alt=media)

Кнопкой *Test Connection* можно проверить, что Harbor получил доступ к указанному хранилищу и готов к работе.

Теперь вернемся на главную вкладку *Projects* и создадим новый проект:

* `Project Name` — станет частью URL. Используйте любой, например, `d8s`.
* `Access Level` — Public.
* `Proxy Cache` — включаем и выбираем в списке Registry, созданный на предыдущем шаге.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8ca7407335b946093a2e48f61f34b1586d23ea24%2Fc386ff1835d893c07efc1be8c840b158.png?alt=media)

Теперь все образы Deckhouse будут доступны по адресу `https://your-harbor.com/d8s/deckhouse/{d8s-edition}:{d8s-version}`.

### Настройка будущего кластера

Переходим [на страницу конфигурации](https://deckhouse.ru/gs/bm-private/step3.html) в Getting Started. Здесь нужно ввести параметры, которые в дальнейшем будут указаны в конфигурационных файлах будущего кластера:

* Шаблон DNS-имён кластера в формате `%s.domain.my` — по нему будут доступны веб-интерфейсы, предоставляемые Deckhouse. Например, Grafana — по адресу `grafana.domain.my`.
* Адрес прокси-сервера для HTTP-трафика (если необходимо), через который будет предоставляться доступ в Интернет изнутри кластера.
* Адрес прокси-сервера для HTTPS-трафика.
* Список IP-адресов, для которых проксирование трафика не будет включено.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a0a8a49ef71bf0e8da17bab7ea37e3f6889b1318%2F3914d5ca20cbd6cc33ccefcfacf90519.png?alt=media)

В следующей части страницы настраиваем доступ к созданному ранее container registry:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-29f7d53d6b1d8e83283d6b0ffb26ca016b60ec65%2F186a9f5eac179a3c65a7af7f148c09db.png?alt=media)

В поле префикса имени образов указываем созданный ранее endpoint в хранилище: `your-harbor.com/d8s/deckhouse/ce`.

Теперь нужно авторизоваться в container registry. Так как мы используем HTTP-протокол, необходимо указать Docker-server'у, к каким хранилищам допустимо присоединяться без шифрования. Для этого откроем файл `/etc/docker/daemon.json` (если его нет — создадим) и добавим туда хранилище:

```
{
  "insecure-registries" : ["http://myregistrydomain.com"]
}
```

Вместо доменного имени можно использовать IP-адрес хранилища во внутренней сети.

Перезапускаем Docker-server, чтобы параметры подхватились, и логинимся в хранилище:

```
$ docker login http://your-harbor.com
```

Теперь закодируем параметры доступа в Base64:

```
$ base64 ~/.docker/config.json
```

Полученную в ответ строку копируем в поле с правами доступа.

Так как мы не стали ранее настраивать HTTPS-доступ к хранилищу, последний пунктом нужно включить использование только HTTP-трафика.

Нажимаем кнопку «*Далее: Установка*» внизу страницы.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a117116f724c0dc520ed30725279a8cf35dd96c9%2F6daf466ed597848a8f5d1ae7fe9ca323.png?alt=media)

На следующей странице отобразится содержимое файла `config.yml`, сгенерированного на основе введенных ранее данных:

```
# Секция с общими параметрами кластера.
# https://deckhouse.ru/documentation/v1/installing/configuration.html#clusterconfiguration
apiVersion: deckhouse.io/v1
kind: ClusterConfiguration
clusterType: Static
# Адресное пространство Pod'ов кластера.
podSubnetCIDR: 10.111.0.0/16
# Адресное пространство для service'ов кластера.
serviceSubnetCIDR: 10.222.0.0/16
kubernetesVersion: "1.23"
clusterDomain: "cluster.local"
---
# Секция первичной инициализации кластера Deckhouse.
# https://deckhouse.ru/documentation/v1/installing/configuration.html#initconfiguration
apiVersion: deckhouse.io/v1
kind: InitConfiguration
deckhouse:
  releaseChannel: Stable
  configOverrides:
    global:
      modules:
        # Шаблон, который будет использоваться для составления адресов системных приложений в кластере.
        # Например, Grafana для %s.example.com будет доступна на домене grafana.example.com.
        publicDomainTemplate: "%s.example.com"
    # Включить модуль cni-flannel.
    # Возможно, захотите изменить.
    cniFlannelEnabled: true
    # Настройки модуля cni-flannel.
    # Возможно, захотите изменить.
    cniFlannel:
      # Режим работы flannel, допустимые значения VXLAN (если ваши сервера имеют связность L3) или HostGW (для L2-сетей).
      podNetworkMode: VXLAN
  # Адрес Docker registry с образами Deckhouse.
  imagesRepo: your-harbor.com/d8s/deckhouse/ce
  # Строка с ключом для доступа к Docker registry.
  registryDockerCfg: ewoJImF1LjAuMzMiOiB7CgkJCSJhdXRoIjogIllXUnRhVzQ2U0dGeVlt OXlNVEl6TkRVPSIKCQl9Cgl9Cn0=
  # Протокол доступа к registry (HTTP или HTTPS).
  registryScheme: HTTP
```

В этом примере мы создаем простой кластер из одного узла с одним адресом, поэтому секцию `StaticClusterConfiguration` сгенерированного конфигурационного файла можно удалить.

Сохраним содержимое в файл и разместим его в отдельном каталоге с произвольным именем.

### Развертывание кластера

Установщик Deckhouse запускается в отдельном контейнере командой:

```
docker run --pull=always -it -v "$PWD/config.yml:/config.yml" -v "$HOME/.ssh/:/tmp/.ssh/" your-harbor.com/d8s/deckhouse/ce/install:stable bash
```

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

По окончании загрузки появится приглашение командной строки внутри контейнера:

```
[deckhouse] root@8e5bd71f05b4 / #
```

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

```
dhctl bootstrap --ssh-user=<username> --ssh-host=<master_ip> --ssh-agent-private-keys=/tmp/.ssh/id_rsa \
  --config=/config.yml \
  --ask-become-pass
```

Если на сервере для работы с *sudo* требуется пароль, нужно его ввести в ответ на соответствующий запрос.

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

## Получение доступа к кластеру

Установленный кластер состоит из одного узла. Добавить в него статичные узлы можно по инструкции [из официальной документации](https://deckhouse.ru/documentation/latest/modules/040-node-manager/faq.html#%D0%BA%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D1%8B%D0%B9-%D1%83%D0%B7%D0%B5%D0%BB-%D0%B2-%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80).

Если же кластер развернут в ознакомительных целях либо для какой-то специфической задачи, и дополнительные узлы не требуются — нужно разрешить компонентам Deckhouse работать на master-узле. Для этого снимем с master-узла taint, выполнив на нем команду:

```
kubectl patch nodegroup master --type json -p '[{"op": "remove", "path": "/spec/nodeTemplate/taints"}]'
```

Если в ответ выводится ошибка:

```
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

…нужно настроить kubectl командой:

```
sudo cat /etc/kubernetes/admin.conf >> ~/.kube/config
```

### Установка Ingress-контроллера

Создадим на master-узле файл `ingress-nginx-controller.yml` со следующим содержимым:

```
# Секция, описывающая параметры Nginx Ingress controller.
# https://deckhouse.ru/documentation/v1/modules/402-ingress-nginx/cr.html
apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: nginx
spec:
  # Имя Ingress-класса для обслуживания Ingress NGINX controller.
  ingressClass: nginx

  # Способ поступления трафика из внешнего мира.
  inlet: HostPort
  hostPort:
    httpPort: 80
    httpsPort: 443
  # Описывает, на каких узлах будет находиться компонент.
  # Возможно, захотите изменить.
  nodeSelector:
    node-role.kubernetes.io/master: ""
  tolerations:
  - operator: Exists
```

Применим его:

```
kubectl create -f ingress-nginx-controller.yml
```

### Создание пользователя для доступа в веб-интерфейсы

Создадим на master-узле файл `user.yml` со следующим содержимым:

```
# Настройки RBAC и авторизации.
# https://deckhouse.ru/documentation/v1/modules/140-user-authz/cr.html#clusterauthorizationrule
apiVersion: deckhouse.io/v1
kind: ClusterAuthorizationRule
metadata:
  name: admin
spec:
  # Список учётных записей Kubernetes RBAC.
  subjects:
  - kind: User
    name: admin@deckhouse.io
  # Предустановленный шаблон уровня доступа.
  accessLevel: SuperAdmin
  # Разрешить пользователю делать kubectl port-forward.
  portForwarding: true
---
# Данные статического пользователя.
# https://deckhouse.ru/documentation/v1/modules/150-user-authn/cr.html#user
apiVersion: deckhouse.io/v1
kind: User
metadata:
  name: admin
spec:
  # E-mail пользователя.
  email: admin@deckhouse.io
  # Это хэш пароля tk6776lyo2, сгенерированного сейчас.
  # Сгенерируйте свой или используйте этот, но только для тестирования:
  # echo "tk6776lyo2" | htpasswd -BinC 10 "" | cut -d: -f2
  # Возможно, захотите изменить.
  password: '$2a$10$/8aOtxwur79/lAUawVQYkOcb5Z55ooIRdJf5PH45oqVcoeD3ebtR.'
```

*Обратите внимание, что в секции с паролем есть подсказка, как его сгенерировать.*

Применим файл:

```
kubectl create -f user.yml
```

### Настройка DNS-записей

Для доступа к веб-интерфейсам кластера нужно настроить resolve соответствующих адресов. Это можно сделать несколькими способами: настроить полноценный DNS-сервер, прописать их в файл `/etc/hosts` или воспользоваться сторонними сервисами, предоставляющими такие услуги.

Веб-интерфейсы расположены по следующим адресам:

* api.example.com
* argocd.example.com
* dashboard.example.com
* deckhouse.example.com
* dex.example.com
* grafana.example.com
* hubble.example.com
* istio.example.com
* istio-api-proxy.example.com
* kubeconfig.example.com
* openvpn-admin.example.com
* prometheus.example.com
* status.example.com
* upmeter.example.com

Для доступа к ним необходимо настроить адресацию на IP-адрес Ingress-контроллера.

Кластер развернут и готов к работе.

## Удаление кластера (обновлено)

Изначально был неверно описан способ удаления кластера с помощью команды `dhctl destroy`. В случае с self-hosted-кластером команда завершится с ошибками, а элементы кластера не будут удалены.

Для удаления кластера, развернутого на ВМ или bare-metal сервере, необходимо **выполнить шаги с 1 по 7** [инструкции по зачистке узла](https://deckhouse.ru/documentation/v1/modules/040-node-manager/faq.html#%D0%BA%D0%B0%D0%BA-%D0%B7%D0%B0%D1%87%D0%B8%D1%81%D1%82%D0%B8%D1%82%D1%8C-%D1%83%D0%B7%D0%B5%D0%BB-%D0%B4%D0%BB%D1%8F-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%B3%D0%BE-%D0%B2%D0%B2%D0%BE%D0%B4%D0%B0-%D0%B2-%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80).

*Обратите внимание, что если выполнить последующие шаги с 8 по 10, на узел можно снова установить необходимые компоненты, и такой узел станет готовым для последующего введения в другой кластер.*

## P.S.

Статья основана на материалах раздела сайта [«Getting Started»](https://deckhouse.ru/gs/bm-private/step2.html). Подробную информацию о дальнейшей настройке платформы и ее модулей можно найти [в официальной документации](https://deckhouse.ru/documentation/v1/deckhouse-overview.html).

С любыми вопросами и предложениями ждем вас в комментариях к статье, а также в Telegram-чате [deckhouse\_ru](https://t.me/deckhouse_ru), где всегда готовы помочь. Будем рады issues (и, конечно, звёздам) [в GitHub-репозитории Deckhouse](https://github.com/deckhouse/deckhouse).

Читайте также в нашем блоге:

* [«Разворачиваем Kubernetes-платформу Deckhouse в Yandex Cloud»](https://habr.com/ru/company/flant/blog/707422/);
* [«Настройка LDAP-аутентификации в кластере Kubernetes под управлением Deckhouse»](https://q.flant.com/?class=other\&query=Deckhouse\&sources%5B%5D=loghouse\&sources%5B%5D=flantblog#:~:text=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0%20LDAP%2D%D0%B0%D1%83%D1%82%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B2%20%D0%BA%D0%BB%D0%B0%D1%81%D1%82%D0%B5%D1%80%D0%B5%20Kubernetes%20%D0%BF%D0%BE%D0%B4%20%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5%D0%BC%20Deckhouse);
* [«Deckhouse соответствует большинству рекомендаций PCI Security Standards Council»](https://habr.com/ru/company/flant/news/t/711730/).


# Yandex Cloud Install

<https://habr.com/ru/companies/flant/articles/707422/>

Платформу Deckhouse можно устанавливать на виртуальные машины облачных провайдеров, на bare metal-серверы, в закрытый контур и не только. В статье рассмотрим вариант установки Deckhouse в Yandex Cloud. А чтобы убедиться, что все внутренние ресурсы и компоненты работают как надо, заглянем в веб-интерфейсы платформы, в том числе Grafana и Kubernetes Dashboard.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-14b7e2b1cdb04f12966354c4778cf7f7ea81dc85%2F9c14ec85b251953cb2e3938877a85735.png?alt=media)

## О платформе

[Deckhouse](https://deckhouse.ru/) — это Open Source-решение для автоматизации обслуживания кластеров Kubernetes. В основе платформы лежат upstream-версия Kubernetes и компоненты с открытым кодом, которые признаны стандартами в cloud native-экосистеме. Платформа связывает их между собой и предоставляет всё необходимое для production-окружений, сводя к минимуму ручные манипуляции.

В Deckhouse включены такие инструменты, как CoreDNS, cert-manager, Ingress NGINX Controller, Prometheus + Grafana, dex, Istio, Cilium. С подробным списком можно ознакомиться [в документации](https://deckhouse.io/ru/documentation/v1/oss_info.html). На основе этих проектов подготовлены соответствующие модули платформы, с помощью которых пользователь может подключить и настроить необходимый инструмент. Конфигурации всех модулей интегрированы друг с другом, что позволяет отдавать на откуп Deckhouse многие рутинные задачи по администрированию кластера. Модули обновляются регулярно и автоматически.

## Требования к установке

Для установки Deckhouse вам потребуются:

* ПК с Linux (Ubuntu 18.04+, Fedora 35+), macOS 10.15+ или Windows 10+.
* Docker (инструкции по установке: [Ubuntu](https://docs.docker.com/engine/install/ubuntu/), [macOS](https://docs.docker.com/desktop/mac/install/), [Windows](https://docs.docker.com/desktop/windows/install/)).
* HTTPS-доступ к хранилищу контейнеров [registry.deckhouse.io](http://registry.deckhouse.io/).
* Доступ к API Yandex Cloud.
* Учетная запись с правами на создание ресурсов.
* Установленная и настроенная утилита Yandex Cloud (CLI).

Инсталлятор Deckhouse, запущенный на вашей машине, подключится к API Yandex Cloud и создаст один master-узел и один worker-узел. Рекомендованные минимальные характеристики узлов:

* 4 ядра CPU;
* 8 Гб RAM;
* 40 Гб дискового пространства.

Поддерживаемые версии ОС для узлов:

* РЕД ОС 7.3;
* AlterOS 7;
* Astra Linux Special Edition 1.7;
* CentOS 7, 8, 9;
* Debian 9, 10, 11;
* Ubuntu 16.04, 18.04, 20.04, 22.04.

## Подготовка Yandex Cloud и настройка Yandex Cloud CLI

Перед тем, как устанавливать Deckhouse в облако, нужно подготовить ваш аккаунт.

> Если вы уже работали с облаком, и у вас есть все необходимое, этот раздел можно пропустить.

### Создаем свое облако

Переходим [на сайт](https://cloud.yandex.ru/) Yandex Cloud и нажимаем на кнопку *Подключиться* в правом верхнем углу страницы:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c964c74711385fc1e46c8bc37315169fbcb9c951%2F87edfd4869dc49d6e28fea1531c7429c.png?alt=media)

Для этого нужно залогиниться в Yandex Cloud либо создать новый аккаунт, если у вас его нет.

Далее необходимо настроить вашу организацию и выбрать название для будущего облака:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fe8dbd627666352c3039c0b2540cff68d72338a6%2Fa12a33b33b705f9cadb4f52d97bb353b.png?alt=media)

Указываем необходимые данные и нажимаем *Создать*.

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ea56483b988134b8ffaa7e8e0b01bfe38a04addd%2F46efcb5a6fb44ec439e53c62f1025ca8.png?alt=media)

У одного аккаунта может быть несколько облаков, их список отображен в левой части главного экрана. Сейчас там только созданное облако.

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

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5d55377cb773a2e11e4c2683baa07af32e93b700%2Fd43f84ef9d58ca4dcd32b8ddbd2bc7a2.png?alt=media)

Создать любой их них можно прямо из интерфейса дашборда, нажав *Создать ресурс* в правом верхнем углу экрана, а также с помощью консольной утилиты Yandex Cloud (CLI):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dfb0564c311c9d630682c97aaa6e1f24c830c2c2%2F63f106374ca8ab220438eb6bf3cc6cfe.png?alt=media)

Все необходимые действия по созданию виртуальных машин Deckhouse выполнит за вас, для этого он использует CLI Yandex Cloud. Для нее необходимо настроить доступ в новое облако.

### Установка и настройка Yandex Cloud (CLI)

Установите утилиту [по официальной инструкции](https://cloud.yandex.ru/docs/cli/quickstart).

> Все описываемые команды актуальны для UNIX-подобных операционных систем, в частности для macOS Monterey. Для Windows команды могут немного отличаться (они упомянуты в Getting Started Deckhouse).

Далее нужно получить токен авторизации в вашем аккаунте. Для этого перейдите [по ссылке](https://oauth.yandex.ru/authorize?response_type=token\&client_id=1a6990aa636648e9b2ef855fa7bec2fb) (при этом нужно быть авторизованным в браузере в вашем облаке). На открывшейся странице будет только одна строка — ваш токен:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a4a5f126dcab6cae4f24caad21411a2542389cbc%2F5389fd96141cb096a3a88dd4b0ee68e6.png?alt=media)

Токен для доступа в облако

Запишите его или оставьте страницу открытой.

Выполните команду `yc init` и введите полученный ранее токен:

```
Please go to https://oauth.yandex.ru/authorize?response_type=token&client_id=1a6990aa636648e9b2ef855fa7bec2fb
 in order to obtain OAuth token.

Please enter OAuth token: AaAaBbBbCcCcDdDdEeEeFfFfGgGg
```

Теперь нужно выбрать облако, с которым будете работать:

```
Please select cloud to use:
 [1] cloud1 (id = aoe2bmdcvatao4frg22b)
 [2] cloud2 (id = dcvatao4faoe2bmrg22b)
Please enter your numeric choice: 2
```

И затем — каталог:

```
Please choose a folder to use:
 [1] folder1 (id = cvatao4faoe2bmdrg22b)
 [2] folder2 (id = tao4faoe2cvabmdrg22b)
 [3] Create a new folder
Please enter your numeric choice: 1
```

Далее нужно указать зону доступности; здесь можно выбрать вариант `4`:

```
Do you want to configure a default Yandex Compute Cloud availability zone? [Y/n] Y
Which zone do you want to use as a profile default?
 [1] ru-central1-a
 [2] ru-central1-b
 [3] ru-central1-c
 [4] Don't set default zone
Please enter your numeric choice: 4
```

Проверьте, что все настроено как нужно, с помощью команды:

```
$ yc config list
token: AaAaBbBbCcCcDdDdEeEeFfFfGgGg
cloud-id: b1g159pa15cddlv5mvcr
folder-id: b1g8o9jbt587mbadu25k
```

Утилита установлена и готова к работе с вашим облаком.

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

## Установка Deckhouse

Для развертывания кластера будет использоваться схема размещения узлов в облаке без использования NAT. В этой схеме IP-адреса всех машин в кластере публичные и свободно доступны из интернета.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ae8a49b40896a8dd179c37dba16393b6aceaae48%2F25b6c0124af2e78830618cea53006be0.png?alt=media)

Схема размещения будущего кластера в облаке без использования NAT

*Подробную информацию о возможностях работы с Yandex Cloud можно получить в документации к модулю* [*cloud-provider-yandex*](https://deckhouse.ru/documentation/v1/modules/030-cloud-provider-yandex/).

### Подготовка окружения

Для того, чтобы Deckhouse смог работать с облаком, необходимо [создать сервисный аккаунт](https://deckhouse.ru/documentation/v1/modules/030-cloud-provider-yandex/environment.html) с правами на редактирование.

Сделать это можно как средствами админки Yandex Cloud, так и из консоли. Мы пойдем вторым путем:

```
$ yc iam service-account create --name habr
id: <userID>
folder_id: <folderID>
created_at: "YYYY-MM-DDTHH:MM:SSZ"
name: habr
```

Здесь мы создаем нового пользователя с именем `habr`. В ответ нам возвращаются его параметры: ID пользователя, ID доступного каталога, дата и время создания, а также имя.

Убедиться, что все прошло как нужно, можно в панели управления, перейдя во вкладку *Сервисные аккаунты*. В таблице должен появиться новый пользователь:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ce942a3dc7a6ec9ff02237e6c99b9fae90cc7ba6%2F5fcd4623717fe030eb2abcfa9d2c0e2e.png?alt=media)

Далее выдадим новому пользователю права с ролью `editor`:

```
$ yc resource-manager folder add-access-binding <folderID> --role editor --subject serviceAccount:<userID>
done (1s)
```

Новая роль также появится в панели:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-68a8faad44cfea8ce1551e9c3439ef0f8efe3e4a%2F78e965b56fc95a8e9c28c115f3e8d557.png?alt=media)

Новая роль пользователя

Теперь создадим JSON-файл с параметрами авторизации, который в дальнейшем будет использоваться для входа в облако:

```
$ yc iam key create --service-account-name habr --output deckhouse-sa-key.json
```

Файл создан. Теперь можно устанавливать Deckhouse!

### Установка

Переходим [на следующую страницу](https://deckhouse.ru/gs/yandex/step4.html) «Быстрого старта».

### Выбор версии платформы

Здесь нужно выбрать Community-(CE) или Enterprise-версию (EE) Deckhouse. Их отличия:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-867a7d56e75ba6d75787de0c50407591363a91a9%2Fe56b2978033ca373e38ff1f8b50d0b66.png?alt=media)

Enterprise-версия доступна только при наличии лицензионного ключа\*. Мы рассмотрим пример с бесплатной CE-редакцией. Поэтому оставляем выбор по умолчанию:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3b6ec5017e28953668137c8aad0fcf6c45799d46%2F0b09c7367de4e79fccf82396b376a809.png?alt=media)

* Примечание

Чтобы оценить всю функциональность Deckhouse, можно протестировать Enterprise-версию [в триальном режиме](https://deckhouse.ru/products/enterprise_edition.html) в течение 30 дней.

### Генерация конфигурационных файлов

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

* `config.yml` — файл первичной конфигурации кластера. Содержит параметры инсталлятора, параметры доступа облачного провайдера и начальные параметры кластера.
* `resources.yml` — настройки узлов и Ingress-контроллера.

Рассмотрим их подробнее. Файл `config.yml`:

```
# Секция с общими параметрами кластера.
# https://deckhouse.ru/documentation/v1/installing/configuration.html#clusterconfiguration
apiVersion: deckhouse.io/v1
kind: ClusterConfiguration
clusterType: Cloud
cloud:
  provider: Yandex
  # Префикс объектов, создаваемых в облаке при установке.
  prefix: cloud-demo
# Адресное пространство Pod'ов кластера.
podSubnetCIDR: 10.111.0.0/16
# Адресное пространство для service'ов кластера.
serviceSubnetCIDR: 10.222.0.0/16
kubernetesVersion: "1.23"
clusterDomain: "cluster.local"
---
# Секция первичной инициализации кластера Deckhouse.
# https://deckhouse.ru/documentation/v1/installing/configuration.html#initconfiguration
apiVersion: deckhouse.io/v1
kind: InitConfiguration
deckhouse:
  releaseChannel: Stable
  configOverrides:
    global:
      modules:
        # Шаблон, который будет использоваться для составления адресов системных приложений в кластере.
        # Например, Grafana для %s.example.com будет доступна на домене 'grafana.example.com'.
        # Можете изменить на свой сразу, либо следовать шагам руководства и сменить его после установки.
        publicDomainTemplate: "%s.example.com"
    userAuthn:
      controlPlaneConfigurator:
        dexCAMode: DoNotNeed
      publishAPI:
        enable: true
        https:
          mode: Global
---
# Секция, описывающая параметры облачного провайдера.
# https://deckhouse.io/documentation/v1/modules/030-cloud-provider-yandex/cluster_configuration.html
apiVersion: deckhouse.io/v1
kind: YandexClusterConfiguration
layout: WithoutNAT
# Параметры доступа к облаку Yandex Cloud.
provider:
  # ID облака.
  cloudID: *!CHANGE_CloudID*
  # ID каталога.
  folderID: *!CHANGE_FolderID*
  # JSON-ключ, сгенерированный с помощью `yc iam key create` на предыдущем шаге.
  # Пример заполнения serviceAccountJSON:
  # serviceAccountJSON: |
  #    {
  #      "id": "...",
  #      "service_account_id": "...",
  #      "created_at": "2022-08-04T05:38:34.756137618Z",
  #      "key_algorithm": "RSA_2048",
  #      "public_key": "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----\n",
  #      "private_key": "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\n"
  #    }
  serviceAccountJSON: *!CHANGE_ServiceAccountJSON*
masterNodeGroup:
  replicas: 1
  instanceClass:
    cores: 4
    memory: 8192
    # ID образа в Yandex Cloud. Рекомендуется использовать актуальную сборку Ubuntu 22.04 LTS.
    # Для получения можете воспользоваться командой:
    # yc compute image list --folder-id standard-images --format json | jq -r '[.[] | select(.family == "ubuntu-2204-lts")] | sort_by(.created_at)[-1].id'
    imageID: fd864gbboths76r8gm5f
    externalIPAddresses:
    - "Auto"
# Данная подсеть будет разделена на три равных части и использована для создания подсетей в трёх зонах Yandex Cloud.
nodeNetworkCIDR: "10.241.32.0/20"
# Публичная часть SSH-ключа для доступа к узлам облака.
# Этот ключ будет добавлен пользователю на созданных узлах (имя пользователя зависит от используемого образа).
sshPublicKey: *!CHANGE_SSH_KEY*
```

Здесь нужно обратить внимание на поля `cloudID`, `folderID`, `serviceAccountJSON` и `sshPublicKey`. Введите туда нужные данные и сохраните файл вместе с уже сгенерированным JSON-файлом с конфигурацией доступа в Yandex Cloud (удобнее всего это сделать в отдельном каталоге).

> Получить значения cloudID и folderID можно командой yc config list, а содержимое публичной части вашего SSH-ключа — командой cat \~/.ssh/id\_rsa.pub.

Файл `resources.yml`:

```
apiVersion: deckhouse.io/v1
kind: NodeGroup
metadata:
  name: worker
spec:
  cloudInstances:
    classReference:
      kind: YandexInstanceClass
      name: worker
    maxPerZone: 1
    minPerZone: 1
    # Можно изменить
    zones:
    - ru-central1-a
  disruptions:
    approvalMode: Automatic
  nodeTemplate:
    labels:
      node.deckhouse.io/group: worker
  nodeType: CloudEphemeral
---
apiVersion: deckhouse.io/v1
kind: YandexInstanceClass
metadata:
  name: worker
spec:
  # Можно изменить
  cores: 4
  # Можно изменить
  memory: 8192
  # Можно изменить
  diskSizeGB: 30
---
apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: nginx
spec:
  # Имя Ingress-класса для использования Ingress Nginx controller
  ingressClass: nginx
  # способ поступления трафика из внешнего мира
  inlet: LoadBalancer
  # Описывает, на каких узлах будет находиться компонент. Лейбл node.deckhouse.io/group: <NODE_GROUP_NAME> устанавливается автоматически.
  nodeSelector:
    node.deckhouse.io/group: worker
---
apiVersion: deckhouse.io/v1
kind: ClusterAuthorizationRule
metadata:
  name: admin
spec:
  # Список учётных записей Kubernetes RBAC
  subjects:
  - kind: User
    name: admin@deckhouse.io
  # Предустановленный шаблон уровня доступа
  accessLevel: SuperAdmin
  # Разрешить пользователю делать kubectl port-forward
  portForwarding: true
---
apiVersion: deckhouse.io/v1
kind: User
metadata:
  name: admin
spec:
  email: admin@deckhouse.io
  # Это хэш сгенерированного пароля vwhnw62bio
  # Сгенерируйте свой или используйте этот, но только для тестирования
  # echo "vwhnw62bio" | htpasswd -BinC 10 "" | cut -d: -f2
  # Можно изменить
  password: '$2a$10$QZ/5WOgdoiqr9f0GG4q3oOcODjwqcCxvWdJnJX.I/L3LhJUeLDBU.'
```

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

Сгенерировать новый пароль можно любым удобным способом — например, так:

```
$ openssl rand 14 -base64
QIAbnGxzzDZDGxbxdAc=
```

Теперь посчитаем его хэш-сумму:

```
$ echo "QIAbnGxzzDZDGxbxdAc=" | htpasswd -BinC 10 "" | cut -d: -f2
$2y$10$VPpfbWGk5L34NkXy7wFtbO9YIbQt0UifkYPowkx0VOeJp23qlmFga
```

Вставляем хэш созданного пароля в поле `password`, затем сохраняем файл с нужным именем и помещаем рядом с предыдущими двумя файлами:

```
$ tree .
.
├── config.yml
├── deckhouse-sa-key.json
└── resources.yml
```

### Развертывание в Yandex Cloud

Для установки Deckhouse Platform используется Docker-образ, в который необходимо передать конфигурационные файлы и SSH-ключи доступа на master-узел.

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

```
docker run --pull=always -it -v "$PWD/config.yml:/config.yml" -v "$HOME/.ssh/:/tmp/.ssh/" \
  -v "$PWD/resources.yml:/resources.yml" -v "$PWD/dhctl-tmp:/tmp/dhctl" registry.deckhouse.io/deckhouse/ce/install:stable bash
```

После того, как образ будет выкачан, откроется приглашение для работы внутри контейнера:

```
213ec9aee27d: Already exists
7699c8f5da30: Pull complete
d408eafd71b1: Pull complete
e79ec47abd43: Pull complete
dbe384732cbc: Pull complete
5d67ae3b7d74: Pull complete
a1bbf50e3ae0: Pull complete
Digest: sha256:014279df55f931f519d642eba4eed3ed02f3bdf4f81e6dd7a571bee353566042
Status: Downloaded newer image for registry.deckhouse.io/deckhouse/ce/install:stable
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
[deckhouse] root@ee5907591d42 / #
```

Выполните команду:

```
dhctl bootstrap --ssh-user=ubuntu --ssh-agent-private-keys=/tmp/.ssh/id_rsa --config=/config.yml --resources=/resources.yml
```

После этого запустится процесс развертывания кластера в облаке, который займет около 15 минут.

В команде выше в параметре `--ssh-user` указывается имя пользователя для входа по SSH на создаваемые узлы. Оно должно соответствовать образу, который указан в `config.yaml` (ID образа). Для выбранного нами образа это пользователь `ubuntu`.

В процессе установки в терминале будет отображаться полный лог всего, что происходит. В нем могут встречаться сообщения о warning'ах и error'ах, которые возникают из-за ожидания развертывания ресурсов на машинах. Это нормально, и не означает, что все сломалось.

Завершится процесс установки новым приглашением ввести команду внутри контейнера. Также будет показан адрес master-узла, куда можно постучаться по SSH, чтобы попасть на созданную ВМ:

```
┌ 🎈 ~ Common: Kubernetes Master Node addresses for SSH
│ habr-demo-master-0 | ssh ubuntu@xx.xxx.xxx.x
└ 🎈 ~ Common: Kubernetes Master Node addresses for SSH (0.00 seconds)
```

Обратите внимание, что в каталоге, в котором хранятся созданные файлы конфигурации, появился каталог `dhctl-tmp`**.** Это временный каталог, в котором инсталлятор будет хранить состояние данных Terraform. Если вы по какой-то причине установку прервалась (проблемы с сетью, нехватка квот), просто перезапустите команду установки. Инсталлятор не создаст «дублирующих» объектов и продолжит процесс с прерванного места\*\*.

* * Если что-то пошло не так

Если вы не можете продолжить установку и хотите очистить облако от созданных объектов, выполните команду в контейнере инсталлятора:

```
dhctl bootstrap-phase abort --ssh-user=ubuntu --ssh-agent-private-keys=/tmp/.ssh/id_rsa --config=/config.yml
```

Две созданные ВМ можно увидеть в дашборде Yandex Cloud. Для этого перейдите в раздел *Compute Cloud / Виртуальные машины*:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c0681e33fa34a05b25855f5aaed69e7cdeff0c39%2F24589addaa7f694adc08ce8d2903fa5e.png?alt=media)

Итак, Deckhouse развернут. Осталось получить доступ к кластеру.

## Получение доступа к кластеру

В завершении установки инсталлятор показал IP-адрес master-узла. Можно подключиться к нему по SSH:

```
ssh ubuntu@<MASTER_IP>
```

Подключение должно выполниться сразу, так как публичная часть вашего SSH-ключа уже добавлена на все созданные узлы. Теперь на master-узле можно выполнять различные действия с кластером:

```
$ sudo -i
root@habr-demo-master-0:~# kubectl get nodes
NAME                                    STATUS   ROLES                  AGE    VERSION
habr-demo-master-0                      Ready    control-plane,master   117m   v1.23.9
habr-demo-worker-d321e7c6-74bb7-hvfwh   Ready    worker                 105m   v1.23.9
```

### Настройка доступа через Ingress

Во время установки кластера был создан и настроен Ingress-контроллер. Он позволяет получать доступ к веб-интерфейсам Deckhouse: Grafana, Prometheus, Dashboard и так далее. LoadBalancer тоже уже готов, поэтому остается только направить на него доменные имена. Для этого необходимо настроить работу DNS и указать в параметрах Deckhouse шаблон DNS-имен.

Шаблон DNS-имен используется для настройки Ingress-ресурсов системных приложений. Например, имя `grafana` соответствует сервису Grafana. Поэтому для шаблона `%s.kube.company.my` Grafana будет доступна по адресу `grafana.kube.company.my`, и т. д.

Для настройки доменных имен будет использован сервис [sslip.io](https://sslip.io/). Чтобы получить IP-адрес балансировщика и автоматически настроить доступ через sslip.io, выполните команду на master-узле:

```
BALANCER_IP=$(sudo kubectl -n d8-ingress-nginx get svc nginx-load-balancer -o json | jq -r '.status.loadBalancer.ingress[0].ip') && \
echo "Balancer IP is '${BALANCER_IP}'." && sudo kubectl patch mc global --type merge \
  -p "{\"spec\": {\"settings\":{\"modules\":{\"publicDomainTemplate\":\"%s.${BALANCER_IP}.sslip.io\"}}}}" && echo && \
echo "Domain template is '$(sudo kubectl get mc global -o=jsonpath='{.spec.settings.modules.publicDomainTemplate}')'."
```

Полученный шаблон DNS-имен будет отображен после выполнения команды:

```
Balancer IP is '1.2.3.4'.
moduleconfig.deckhouse.io/global patched

Domain template is '%s.1.2.3.4.sslip.io'.
```

Также можно настроить доступ с использованием существующего доменного имени, к регистрации поддоменов которого у вас есть доступ, или с использованием файла `/etc/hosts`. Подробнее об этом можно прочитать [в Getting Started](https://deckhouse.ru/gs/yandex/step5.html).

### Настройка доступа kubectl с локальной машины

Работать с кластером напрямую с master-узла из-под root-пользователя небезопасно. Поэтому рекомендуется настроить внешний доступ к нему с помощью kubectl на локальной машине.

Для этого войдите в веб-интерфейс Kube Configurator; его адрес соответствует шаблону `https://kubeconfig.1.2.3.4.sslip.io`. Логин — `admin@deckhouse.io`, пароль — `w1ekkzbccr` (если вы не меняли его в конфигурационных файлах).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-5225ebc53c1010998b1ee953b4fdbeff31f052ad%2F5deebf9510c1f320b9e1807dfe9a6d87.png?alt=media)

Интерфейс настройки доступа kubectl

Выберите нужную ОС и следуйте дальнейшим инструкциям.

Чтобы убедиться, что все получилось, выполните команду на вашей машине:

```
$ kubectl get no
NAME                                    STATUS   ROLES                  AGE   VERSION
habr-demo-master-0                      Ready    control-plane,master   38m   v1.23.9
habr-demo-worker-a32e16ee-5dcbd-sttgd   Ready    worker                 25m   v1.23.9
```

Если вы видите список узлов развернутого кластера — доступ настроен. Кластер развернут.

## Обзор веб-интерфейсов Deckhouse

Deckhouse предоставляет несколько полезных веб-интерфейсов в составе кластера — для доступа к документации, мониторингу, Kubernetes Dashboard и контроля состояния кластера. По умолчанию доступ ко всем компонентам организован с помощью [Dex](https://dexidp.io/), через статического пользователя, созданного в кластере во время установки.

### Встроенная документация

В кластер добавлен сервис, который позволяет получить доступ ко всей актуальной документации Deckhouse. Находится она по адресу `https://deckhouse.1.2.3.4.sslip.io`. Логин — `admin@deckhouse.io`, пароль — `w1ekkzbccr` (если вы не меняли его в конфигурационных файлах).

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-33b3fb02ced257d8becbe85fe4ee75384078f979%2Fbfeff6e25cf98435e98b0ea7f1e5b48a.png?alt=media)

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

### Мониторинг с помощью Grafana

Дашборды Grafana доступны по адресу `grafana`. На главной странице можно увидеть общую информацию о развернутом кластере, а также ссылки на другие встроенные веб-интерфейсы:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-79867dbc4c7a87ff603b4567b0ae2ef1f9ffaf68%2Fed92f568cc6dc7ebd9085a38608f6bed.png?alt=media)

Для мониторинга ресурсов можно перейти по соответствующим ссылкам. Например, вот так выглядит мониторинг Ingress Nginx Controller'а:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9761a07791f44af9b26b3583ad56d485289c0295%2F644c8ab3d94d1d0636f03ca624c350cb.png?alt=media)

Для доступа к Prometheus напрямую можно перейти по ссылке `/prometheus` (либо прямо из интерфейса главной страницы Grafana):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ce186d62a150df96f89e5052ecf0536160fef7ad%2Fc2386e9cecbeb4f2a0ca65e0211235d2.png?alt=media)

### Kubernetes Dashboard

Kubernetes Dashboard доступен по адресу `dashboard`. Здесь собрана информация обо всех сущностях кластера и их состоянии:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9cfe3459f4885e503be8927551d6c2f449785292%2Ff760370904372e41bec9cea247c0f314.png?alt=media)

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

Состояние кластера и его компонентов можно посмотреть на странице, доступной по адресу `status`:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-04f50ba0ec8cf978a134c47c7a76da3a546871c1%2Fa9bc702fed63c3286f1c5315538e6471.png?alt=media)

## Удаление развернутого кластера

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

Запустите установочный образ Deckhouse, пробросив ему свой ключ SSH:

```
docker run --pull=always -it -v "$HOME/.ssh/:/root/.ssh/" registry.deckhouse.io/deckhouse/ce/install:stable bash
```

Внутри образа выполните команду:

```
dhctl destroy --ssh-host <MASTER-IP> --ssh-user <USER>
```

Удаление может занять от 5 до 10 минут. В результате будут удалены все компоненты Deckhouse и сами виртуальные машины, развернутые в облаке на момент установки.

## Заключение

Мы пошагово рассмотрели, как создать новое облако в Yandex Cloud и развернуть в нем Kubernetes-кластер под управлением платформы Deckhouse.

Эта статья основана на материалах раздела [«Getting Started»](https://deckhouse.ru/gs/) на сайте Deckhouse. Более подробную информацию о настройке развернутого кластера можно получить [в официальной документации](https://deckhouse.ru/documentation/v1/deckhouse-overview.html) (как на сайте, так и внутри развернутого кластера), где в соответствующих разделах собраны все необходимые инструкции по настройке компонентов кластера.

С любыми вопросами и предложениями ждем вас в комментариях к статье, а также в Telegram-чате [deckhouse\_ru](https://t.me/deckhouse_ru), где всегда готовы помочь. Будем рады issues (и, конечно, звёздам) [в GitHub-репозитории Deckhouse](https://github.com/deckhouse/deckhouse).

## P.S.

Читайте также в нашем блоге:

* [«Kubernetes-платформа Deckhouse сертифицирована для работы с «Ред ОС», Astra Linux и AlterOS»](https://habr.com/ru/company/flant/news/t/691574/);
* [«Тернистый путь к eBPF, или Как мы Cilium в Deckhouse внедряли»](https://habr.com/ru/company/flant/blog/682520/);
* [«Kubernetes-платформа Deckhouse зарегистрирована в Едином реестре российского ПО»](https://habr.com/ru/company/flant/news/t/597623/).


# K3S

<https://habr.com/ru/articles/711440/>

Может ли kubernetes сделать жизнь админов небольших и средних компаний проще или же это шайтан-машина для кровавого enterprise и оголтелых стартапов?

Сейчас Kubernetes раскатывают все, кому не лень, для всего, что только может прийти в голову. Чаще всего там, где ему не место, но он развёрнут и используется, причина довольно прозаичная: обучение за счёт ~~работодателя~~ проектов. Все хотят получать больше, а для этого нужно соответствовать критериям из вакансий, где практически везде написаны эти 3 волшебных символа: K-8-S.

Основная проблема кубера - громоздкость. В целом это не проблема, когда он managed в каком-то облаке с автоскейлом и нодагруппами. Когда же всё это разворачиваешь руками и тем боле bare-metal, то начинаешь задумываться над своей адекватностью (и не безосновательно). Даже если развернёшь, то поддерживать такое прям совсем не хочется. Если бы не такая лютая сложность, то можно было бы столько всего развернуть в отказоустойчивом режиме с масштабированием и мониторингом. Столько статей пестрят helm'ами, операторами и разными "вкусностями".

Мало-помалу все приходят в итоге к какому-то инструменту для автоматизированного деплоя и снова впадают в отчаяние, потому что он зачастую не проще, а иногда даже сложнее. Собственно, ребята с Rancher Labs видимо задолбались в поддержке и решили сделать свой кубер, который будет проще, быстрее, легче, а главное его будет проще доставить. Так появился k3s - неприхотливый, простой как булыжник младший брат k8s. А с учётом того, что далеко не для всего нужен весь комбайн, то это неплохой вариант для тех, кому нужны некоторые возможности kubernetes, но без лютого хайлоада и катастрофоустойчивости.

## Что это за зверь?

K3s - это полностью (по заверению разработчиков) совместимый дистрибутив кубера, с некоторыми улучшениями:

* Это всего один бинарь, который можно легко положить куда угодно и просто запустить как сервис.
* Они переписали хранилище на Sqlite (недавно перешли на [Dqlite](https://dqlite.io/)) и добавили ещё несколько драйверов для SQL баз и etcd3.
* Добавили "батарейки": балансировщики там всякие, сети и т. п.

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

Чтобы его установить, достаточно просто запустить скрипт на целевой машине: `curl -sfL https://get.k3s.io | sh -` и можно начинать работу. Скрипт автоматически выкачает бинарь по нужному пути и поднимет все необходимые сервисы (systemd). Скрипт также подтянет все необходимые инструменты для работы, такие как `kubectl`, `crictl` и скрипты для удаления ноды или кластера. Вес самого бинаря на данный момент около 63мб. При желании можно доработать скрипт и разместить все необходимые файлы в локальной сети. Если очень сильно хочется, то можно самостоятельно реализовать установку, выгрузив нужные бинарники на хост, создав сервисы и запустив их. На момент написания статьи, поддерживается версия кубера 1.26.

Особенно хочу отметить возможность настройки локального зеркала для образов. [Тут](https://docs.k3s.io/installation/private-registry) написано более подробно, но я бы отметил, что такой подход может помочь сэкономить время на запуске сервисов при добавлении новых хостов. Особенно актуально для нагруженных каналов небольшой ширины.

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

Но мы же хотим отказоустойчивость. Такой вариант подразумевает несколько master-нод. В этом плане k3s выигрывает у старших собратьев, потому как может работать с двумя мастерами, вместо трёх. Этот вариант крайне не рекомендован, но возможен. При этом есть три пути:

1. Развернуть несколько master'ов со встроенной БД и отдельно подключить некоторое количество worker'ов, на которых будут работать непосредственно развёрнутые сервисы. Такой вариант лучше всего подходит для случаев, когда "железок" очень много и они мелкие (например, кластер на базе Raspberry Pi). [Тут](https://docs.k3s.io/installation/ha-embedded) подробная документация как это делать правильно.
2. Развернуть кластер БД отдельно, а к нему подключать некоторое количество нод, которые могут быть как мастерами, так и worker'ами. Такой вариант подходит для более консервативного набора серверов и, особенно хорошо, если уже присутствует кластер БД в сети (совместимость только с MySQL/MariaDB и Postgres). База данных при нечастых манипуляциях довольно скромно нагружается, поэтому вполне может ужиться с 1С базой параллельно. В некоторых организациях практикуют подход "единой СУБД", когда есть большой кластер БД и все сервисы подключаются к нему, все бэкапы завязаны на нём и т. п. Если такое уже есть, то это отличный вариант. В противном случае, можно сделать Gallera/Percona SQL на трёх нодах и на этих же нодах развернуть кластер. Но в этом случае нужно заранее планировать нагрузки, чтобы не навредить БД другими сервисами (и наоборот). Для тех, кому нужна отказоустойчивость (всякие там перезагрузки "на горячую", сгоревший сервер и т.п.), - это наиболее оптимальный вариант, работа продолжится даже при отключении части серверов. [Здесь](https://docs.k3s.io/installation/ha) можно прочитать, как создать кластер с внешней базой данных.
3. Поднять только один master и подключать к нему дополнительные ноды только как worker'ы по мере увеличения нагрузки. В 90% это подходящий вариант. Если сервисы не хранят в себе данных, которые не имеют резервных копий, то такой вариант вполне себе может работать годами в небольших компаниях. Порой split brain приносит больше боли, чем такая архитектура. Если кластер будет стоять на окошке в кабинете директора (или CTO в отделе одного-двух сотрудников), то этот вариант более чем подходящий, а порой и предпочтительный.

При любом из этих вариантов архитектуры добавление новых нод для увеличения ресурсов кластера под рабочие нагрузки будет практически одинаковым (для нескольких мастеров, нужно будет создать общий Virtual IP, чтобы ноды подключались по нему). С учётом того, что в данной статье рассматривается не highload, то этого будет вполне достаточно. Если от простоя в 1 час компания не понесёт многомиллионные убытки (в реальном, а не "упущенном" варианте), то это 100% ваш вариант.

## Установка ещё проще

Казалось бы, куда уже проще? Но одному человеку оказалось и этого много, поэтому он написал бинарь, который раскатывает кластер в одну строку. Проект называется ["Кетчуп"](https://github.com/alexellis/k3sup) и пока что показывает себя довольно неплохо. Всё, что ему нужно, - доступ к хосту по SSH.

k3sup был создан не для того, чтобы один раз развернуть кластер, но вполне себе для этих целей пригоден. Это бинарный исполняемый файл, который содержит в себе модуль ssh. Им он подключается к удалённой машине (виртуалке, "малинке" или bare-metal хосту) и выполняет все те же самые действия, что пришлось бы проделать нам вручную. По сути это просто удобная обёртка для установки новых кластеров.

Вот пример, как можно быстро раскатать кластер на bare-metal из мастера и двух дополнительных воркеров:

* Сеть `172.16.0.0/16`.
* Физические серверы располагаются на `172.16.100.202` (master+worker) и `172.16.100.203-204` (workers).
* Для того, чтобы сервисы можно было выставить во внутреннюю сеть, выбран диапазон адресов с `172.16.100.221` по `172.16.100.229`.

```
# Генерим ключ
ssh-keygen -t ecdsa -f ~/.ssh/id_rsa
# Раскидываем его по хостам
for i in 20{2..4}; do ssh-copy-id -i ~/.ssh/id_rsa.pub -f ubuntu@172.16.100.$i; done
# Поднимаем мастер ноду (без некоторых сервисов, которые можно потом руками более гибко поднять)
k3sup install --ip 172.16.100.202 --user ubuntu --k3s-extra-args '--disable servicelb,traefik,local-storage --flannel-backend=none --cluster-cidr=10.10.0.0/16 --disable-network-policy'
# Подключаем полученный конфиг
export KUBECONFIG=$(pwd)/kubeconfig && kubectl config set-context default
# Катим canal (у него очень удобное межхостовое взаимодействие и куча всяких других плюшек)
kubectl create -f kubectl replace -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml
# Добавляем ноды
for i in 20{3..4}; do k3sup join --ip 172.16.100.$i --user ubuntu --server-ip 172.16.100.202 --server-user ubuntu; done
# Катим metallb, без которого bare-metal нормально не завести
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
cat <<EOF > metallb_values.yaml
configInline:
  address-pools:
   - name: default
     protocol: layer2
     addresses:
     - 172.16.100.221-172.16.100.229
EOF
helm upgrade metallb metallb/metallb -f metallb_values.yaml --install --wait --timeout 600s --namespace metallb-system
# Катим ингресс на базе nginx
helm upgrade --install ingress-nginx ingress-nginx   --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace
# Можно сразу сделать его дефолтным
kubectl annotate ingressclasses.networking.k8s.io nginx ingressclass.kubernetes.io/is-default-class="true"
```

На такую установку, в зависимости от канала в интернет, уйдёт от 5 до 15 минут. Технически сервера могут располагаться и во внешней сети. Но тогда нужно внимательно прочитать документацию на предмет открытых портов и заморочиться фаэрволом. Metallb также поддерживает BGP, что могло бы помочь держать серверы внутри, а балансировщик сразу выставить наружу, но это выходит за рамки данной статьи. Также я бы рекомендовал развернуть какое-то хранилище для Persistent Volume Storage. Для этих целей очень хорошо подойдёт [Longhorn](https://longhorn.io/docs/latest) (может помочь очень гибко управлять хранилищем, есть даже GUI).

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

## Удобный не-highload

Проект изначально был рассчитан на то, чтобы развернуть в нём сам Rancher, а уже потом плодить новые кластеры как кроликов. Т. е. он не был рассчитан на миллионы запросов в секунду, с десятками тысяч контейнеров, SLA с 5 девятками после запятой и т.п. Если к этому относиться спокойно и без иллюзий, то получается вполне себе удобный инструмент для администратора. Что же может быть полезного в таком кластере?

### Внутренние сервисы

Какие сервисы можно было бы установить и сделать жизнь относительно небольшой организации проще:

* Локальное S3 хранилище на базе Minio ([чарт](https://artifacthub.io/packages/helm/bitnami/minio)). Довольно приличное количество сервисов умеют хранить данные в S3, удобно управлять доступом, шарить какую-то статику (вплоть до образов дисков). Может выступать как gateway для облачных S3-сервисов. Довольно полезный инструмент для многих задач. При грамотном деплое сможет масштабироваться дальше вместе с кластером. Здесь можно хранить бэкапы, конфиги, технически можно хранить образы для PXE и многое другое. Зачастую этот сервис самый нужный и удобный. Его легко можно заменить простым Nginx, но не будет такого гибкого управления доступом + хранилище будет ограничено либо одной нодой, либо должно лежать где-то в отказоустойчивом хранилище. Ещё S3 хранилища довольно удобно и быстро синхронизируются между собой с помощью различных утилит. Бэкап такого хранилища можно организовать с помощью CronJob в любое другое и быть спокойным за свои данные.
* Pi-Hole ([чарт](https://artifacthub.io/packages/helm/mojo2600/pihole)) - небольшой инструмент для блокировки рекламного трафика. Пусть вас не смущает "PI" в названии, потому что инструмент заточен для домашних экспериментов на "малинке". Но вполне себе может послужить и в корпоративной среде.
* Gitea ([чарт](https://artifacthub.io/packages/helm/gitea/gitea)) может стать локальным хранилищем для исходников и наработок внутри компании. Более того, он вполне может стать коллектором инцидентов и трекером задач. Технически можно развернуть целый комбайн в виде Gitlab ([чарт](https://artifacthub.io/packages/helm/gitlab/gitlab)), но очень часто для небольших организаций достаточно и Gitea.
* В зависимости от размеров любви к метрикам, можно развернуть Prometheus Operator ([чарт](https://artifacthub.io/packages/olm/community-operators/prometheus)) или VictoriaMetrics Operator ([чарт](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-operator)). Если ко всему этому аккуратно прикрутить ещё и AlertManager, то в принципе можно сделать свою работу тихой и безмятежной на долгие месяцы, а иногда и годы. Более того, можно собирать данные с различных устройств вроде Mikrotik или Cisco, что позволит ещё и анализировать качество канала и трафика.
* Мне лень было подбирать чарты, но к предыдущему сервису логично так же прибавить и ELK-стек, чтобы собирать логи отовсюду, а затем внимательно их изучать. Это может очень понравиться не только разработчикам и админам, но и заскорузлым безопасникам, которые захотят отслеживать какие-то действия на рабочих станциях сотрудников (взгляд неодобрения).
* Postfix релей ([чарт](https://artifacthub.io/packages/helm/docker-postfix/mail)) я бы не стал разворачивать (больше за использование Proxmox Mail Gateway, который сильно больше вещей может предложить), но некоторым может пригодиться.

### Сервисы для сотрудников

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

* Nextcloud ([чарт](https://artifacthub.io/packages/helm/nextcloud/nextcloud)) - удобный комбайн для управления файлами внутри компании. По сути это "облако" в привычном понимании пользователей. Можно поставить клиент на телефон, на PC и синхронизировать их между собой. Практически "из коробки" идёт плагин Talk для корпоративной связи (чат + звонилка). Так же есть куча плагинов, которые изрядно облегчат обслуживание файлов и документов. Хорошо интегрируется с S3 хранилищами, а значит можно установить в качестве хранилища внутренний S3.
* Collabora Online (вариант [чарта](https://artifacthub.io/packages/helm/truecharts/collabora-online)) - не сказать что прям замена MS Office, но хороший помощник для редактирования документов онлайн. В сочетании с Nextcloud, можно развернуть буквально "облачный офис" (если вся работа сотрудников ведётся в документах). Как альтернативу можно посмотреть OnlyOffice ([чарт](https://github.com/ONLYOFFICE/Kubernetes-Docs)), несмотря на довольно урезанную версию Community.
* Wordpress ([чарт](https://artifacthub.io/packages/helm/bitnami/wordpress)) - при достаточно хорошем канале, вполне можно разместить корпоративный сайт или интернет-магазин. Почти вся работа с движком осуществляется через плагины и базу с контентом, поэтому довольно удобно будет управлять масштабированием. Разработчиков под этот движок очень много, а переезд куда-то за пределы офиса осуществить будет в дальнейшем не очень сложно. В качестве альтернативы или дополнения есть форум phpBB ([чарт](https://bitnami.com/stack/phpbb/helm)) и магазинчик от OpenCart ([чарт](https://bitnami.com/stack/opencart/helm)). Тут уж на вкус, цвет и задачи.
* Кому-то может понадобиться CRM от SuiteCRM ([чарт](https://bitnami.com/stack/suitecrm/helm)). Хотя есть (частенько печально) известный Bitrix24 ([магия](https://habr.com/ru/company/southbridge/blog/456608/)).
* Корпоративную базу знаний вполне можно разместить на MediaWiki ([чарт](https://artifacthub.io/packages/helm/bitnami/mediawiki)). Даже если компания небольшая, онбординг новых сотрудников может заметно ускориться с помощью подобных сервисов.
* Если вдруг Nexcloud Talk будет недостаточно, то на помощь может прийти Rocket.Chat ([чарт](https://artifacthub.io/packages/helm/rocketchat-server/rocketchat)) или Mattermost ([чарт](https://artifacthub.io/packages/helm/mattermost/mattermost-team-edition)). Такие вот хорошие альтернативы Slack в локальной среде. Оба поддерживают внутренние звонки.
* Есть [случаи](https://forum.mista.ru/topic.php?id=865478), когда люди поднимают даже 1С Web-сервис в кластере. Не одобряю, но и не осуждаю.

## Вместо итога

В целом всё это можно поместить вне Kubernetes, организовать на виртуалках нужные сервисы и поддерживать их. Всё можно сделать иначе и это тоже будет правильно. Лично мне проще сделать свою работу так, чтобы я мог за довольно ограниченный промежуток времени полностью повторить инфраструктуру с нуля. Возможности helm позволяют мне это сделать и сохранить описание инфраструктуры в git (например, стороннем). При грамотном подходе, какая-то сервисная или аутсорс компания могла бы развернуть на своих мощностях несколько похожих окружений для своих клиентов, предлагая это как услугу. С учётом минимального оверхеда и более удобного управления лимитами ресурсов, это может быть сильным инструментом в руках Ops'ов.

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

Поэтому не бойтесь кубернетиса в своих инфраструктурах. Пусть лучше он боится вас!


# OpenShift OKD

<https://www.okd.io/>

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/OpenShift%20OKD/Untitled)

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/OpenShift%20OKD/Untitled)

Built around a core of OCI container packaging and Kubernetes container cluster management, OKD is also augmented by application lifecycle management functionality and DevOps tooling. OKD provides a complete open source container application platform.

## OKD 4

`$ openshift-install create cluster`

Tons of amazing new features

Automatic updates not only for OKD but also for the host OS, k8s Operators are first class citizens, a fancy UI, and much much more

CodeReady Containers for OKD: local OKD 4 cluster for development

CodeReady Containers brings a minimal OpenShift 4 cluster to your local laptop or desktop computer! Download it here: CodeReady Containers for OKD Images

## What is OKD?

OKD is a **distribution of Kubernetes** optimized for continuous application development and multi-tenant deployment

OKD embeds Kubernetes and extends it with security and other integrated concepts

OKD adds **developer and operations-centric** tools on top of Kubernetes to enable rapid application development, easy deployment and scaling, and long-term lifecycle maintenance for small and large teams

OKD is also referred to as Origin in GitHub and in the documentation

OKD is a **sibling** Kubernetes distribution to **Red Hat OpenShift** | If you are looking for enterprise-level support, or information on partner certification, Red Hat also offers [Red Hat OpenShift Container Platform](https://www.openshift.com/)

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8b3db65ec6e865dd4af3e455f98e58c92d5b98be%2Ftopology.png?alt=media)

## OKD Community

We know you've got great ideas for improving OKD and its network of open source projects. So roll up your sleeves and come join us in the community!

### Get Started

All contributions are welcome! OKD uses the Apache 2 license and does not require any contributor agreement to submit patches. Please open issues for any bugs or problems you encounter, ask questions in the **#openshift-users** on Kubernetes Slack Channel, or get involved in the OKD-WG by joining the OKD-WG google group.

* Get started with the [Contributors Guide](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/OpenShift%20OKD/CONTRIBUTING/README.md)
* Read [the documentation](https://docs.okd.io/latest/welcome/index.html)
* Read [our charter](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/OpenShift%20OKD/CHARTER/README.md)
* Help Resolve an [Open Issue](https://github.com/okd-project/okd/issues)

### Connect to the community

Join the [OKD Working Group](https://www.okd.io/contributor/)

### Talk to Us

* Follow the [public mailing lists](https://groups.google.com/g/okd-wg)
* Chat with us on [Matrix](https://matrix.to/#/#okd:fedoraproject.org)
* Chat with us on the [#openshift-users channel on Slack](https://kubernetes.slack.com/messages/openshift-users/)
* See more options on the [OKD Working Group Communications page](https://www.okd.io/communications/)

## Standardization through Containerization

Standards are powerful forces in the software industry. They can drive technology forward by bringing together the combined efforts of multiple developers, different communities, and even competing vendors.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c4eacf0a61e9d4097702f4ac573e1a274578f2b9%2Flogo-kubernetes-horizontal-color.png?alt=media)

Open source container orchestration and cluster management at scale

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-135624a42eba04e11c19f717d00d750e12d26b34%2Fpodman.svg?alt=media)

Standardized Linux container packaging for applications and their dependencies

A container-focused OS that's designed for painless management in large clusters

An open source project that provides developer and runtime Kubernetes tools, enabling you to accelerate the development of an Operator

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0392519efdf4c9631720a7938ebafa738fa7e61d%2Flogo-cri-o.png?alt=media)

A lightweight container runtime for Kubernetes

Prometheus is a systems and service monitoring toolkit that collects metrics from configured targets at given intervals, evaluates rule expressions, displays the results, and can trigger alerts if some condition is observed to be true

## OKD End User Community

There is a large, vibrant [end user community](https://commons.openshift.org/participants.html)

### Become a part of something bigger

OpenShift Commons is open to all community participants: users, operators, enterprises, non-profits, educational institutions, partners, and service providers as well as other open source technology initiatives utilized under the hood or to extend the OpenShift platform

* If you are an OpenShift Online or an OpenShift Container Platform customer or have deployed OKD on premise or on a public cloud
* If you have contributed to the OKD project and want to connect with your peers and end users
* If you simply want to stay up-to-date on the roadmap and best practices for using, deploying and operating OpenShift

... then OpenShift Commons is the right place for you


# RKE2

## RKE2

<https://docs.rke2.io/>

## Introduction

data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyNC4xLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAzNDUuOTk3NjUwMSAxMTEuMzYyMzgxIgogICBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAzNDUuOTk3NjUwMSAxMTEuMzYyMzgxOyIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgc29kaXBvZGk6ZG9jbmFtZT0ibG9nby1ob3Jpem9udGFsLXJrZTItZGFyay5zdmciCiAgIGlua3NjYXBlOnZlcnNpb249IjEuMi4yICg3MzJhMDFkYTYzLCAyMDIyLTEyLTA5LCBjdXN0b20pIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzCiAgIGlkPSJkZWZzNDA4Ij4KCQkKCTwvZGVmcz48c29kaXBvZGk6bmFtZWR2aWV3CiAgIGlkPSJuYW1lZHZpZXc0MDYiCiAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgYm9yZGVyY29sb3I9IiMwMDAwMDAiCiAgIGJvcmRlcm9wYWNpdHk9IjAuMjUiCiAgIGlua3NjYXBlOnNob3dwYWdlc2hhZG93PSIyIgogICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICBpbmtzY2FwZTpkZXNrY29sb3I9IiNkMWQxZDEiCiAgIHNob3dncmlkPSJmYWxzZSIKICAgaW5rc2NhcGU6em9vbT0iMy44MzgxNzY0IgogICBpbmtzY2FwZTpjeD0iMTcyLjk5ODgzIgogICBpbmtzY2FwZTpjeT0iNTUuODg1OTE2IgogICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjMzNzQiCiAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEzNzYiCiAgIGlua3NjYXBlOndpbmRvdy14PSI2NiIKICAgaW5rc2NhcGU6d2luZG93LXk9IjI3IgogICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJnMzgzIiAvPgo8c3R5bGUKICAgdHlwZT0idGV4dC9jc3MiCiAgIGlkPSJzdHlsZTM3NSI+Cgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cgkuc3Qxe2ZpbGw6IzJFNjhFOTt9Cjwvc3R5bGU+CjxnCiAgIGlkPSJnNDAzIj4KCTxnCiAgIGlkPSJnMzk3IgogICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPgoJCTxnCiAgIGlkPSJnMzgzIj4KCQkJPHBhdGgKICAgY2xhc3M9InN0MCIKICAgZD0ibSAxNjMuODE5ODUsMzAuMzEzOTA4IGggMjAuODAyOTUgYyA5Ljg3NzczLDAgMTYuNTM3Niw0Ljc4OTA1OCAxNi41Mzc2LDE0LjA2NzU1OCAwLDcuNTU4NzI3IC01LjE2Mjk4LDEyLjEyMzY4IC0xMC4xNzczNywxMy42OTQ4NjIgMS40OTY4OSwxLjI3MTU2MSAyLjYxOTg2LDIuOTkyNTU0IDMuNTE3NSw0Ljc4OTA1OSAyLjA5NDkxLDQuMjY1MzM1IDMuNTE2MjksOC45Nzg4ODIgNy45MzE0Myw4Ljk3ODg4MiAxLjEyMjk3LDAgMi4wMjA2MiwtMC4zNzM5MTcgMi4wMjA2MiwtMC4zNzM5MTcgbCAtMC45NzMxNiw4LjkwNDU3OSBjIDAsMCAtMi42OTI5NCwwLjY3MzUzOSAtNS4wMTMxNywwLjY3MzUzOSAtNS45ODYzMywwIC05LjQyODMyLC0yLjMyMDIzNyAtMTIuOTQ1OCwtMTAuMzI3MTcyIC0xLjQ5NTY5LC0zLjU5MDU3NiAtMy41OTE4LC05Ljg3Nzc0MyAtNi4zNjAyNSwtOS44Nzc3NDMgaCAtMi44NDM5NiB2IDE5Ljk3OTU4OCBoIC0xMi40OTYzOSB6IG0gMTIuNDk2MzksOS4wNTQzOTMgdiAxMi40MjA4NjQgaCA0LjQ5MDY2IGMgMy41OTE3OCwwIDcuNzgxNiwtMS4xMjE3NTMgNy43ODE2LC02LjUwODgzNCAwLC00LjQxNTE0NiAtMi44NDI3NCwtNS45MTIwMyAtNi4yODU5MywtNS45MTIwMyB6IgogICBpZD0icGF0aDM3NyIgLz4KCQkJPHBhdGgKICAgY2xhc3M9InN0MCIKICAgZD0ibSAyMDkuNjkxMDcsMzAuMzEzOTA4IGggMTIuNDk2MzcgdiAxNC4xNDMwNzQgYyAwLDEuNTcxMTgyIC0wLjIyNDExLDMuODkwMTk3IC0wLjM3MzkyLDUuNjEyNDA3IGggMC4yOTk2MiBjIDAuODIzMzUsLTEuMjcxNTYgMS44NzA4MSwtMy4yMTc4NzYgMy4xNDIzNywtNC43MTQ3NjMgTCAyMzcuNjAzMywzMC4zMTM5MDggaCAxMy40NjgzMSBsIC0xNi40NjIwOCwxOS44Mjk3NzYgMTcuMzYwOTQsMzAuNjc5NDU5IEggMjM3LjYwMzMgbCAtMTEuNjc0MjUsLTIxLjE3NTYzMiAtMy43NDE2MSw0LjU2NDk1MiB2IDE2LjYxMDY4IGggLTEyLjQ5NjM3IHoiCiAgIGlkPSJwYXRoMzc5IiAvPgoJCQk8cGF0aAogICBjbGFzcz0ic3QwIgogICBkPSJtIDI1NS45Mzc0MSwzMC4zMTM5MjcgaCAzMy40NDc5IHYgOS4yNzg0OTIgSCAyNjguNDMzNzggViA1MC40NDMzMSBoIDE3LjU4NTA1IHYgOS4yNzg0OTIgaCAtMTcuNTg1MDUgdiAxMS44MjI4MyBoIDIxLjcwMDU5IHYgOS4yNzg0OTYgaCAtMzQuMTk2OTYgeiIKICAgaWQ9InBhdGgzODEiCiAgIHN0eWxlPSJkaXNwbGF5OmlubGluZSIgLz4KCQk8cGF0aAogICBjbGFzcz0ic3QwIgogICBkPSJtIDMzMy45OTc2NSw4MC42NDg4MTEgaCAtMzcuMzAwNzggdiAtOC44NTUyOTMgbCAxMi41NTA3OCwtMTIuMjk1MjIzIGMgMy41ODU5NCwtMy42MzI5NDIgNS45Mjk2OSwtNi4xMTM1NTYgNy4wMzEyNSwtNy40NDE4NDkgMS4xMDE1NiwtMS4zMjgyOTMgMS44NjkxNCwtMi40NjkyNjUgMi4zMDI3MywtMy40MjI5MDkgMC40MzM2LC0wLjk1MzY0NyAwLjY1MDQsLTEuOTUyNzA1IDAuNjUwNCwtMi45OTcxNzcgMCwtMS4yOTQyMzEgLTAuNDMzNiwtMi4zMTU5OTggLTEuMzAwNzksLTMuMDY1MjkyIC0wLjg2NzE4LC0wLjc0OTI5MSAtMi4wODU5MywtMS4xMjM5NCAtMy42NTYyNSwtMS4xMjM5NCAtMS42MTcxOCwwIC0zLjI1MTk1LDAuNDQ4NDQxIC00LjkwNDI5LDEuMzQ1MzI2IC0xLjY1MjM1LDAuODk2ODgxIC0zLjUyMTQ5LDIuMjE5NDk3IC01LjYwNzQyLDMuOTY3ODQ5IGwgLTcuNjI4OTEsLTguNjUwOTM2IGMgMi42NDg0NCwtMi4yOTMyOTMgNC44NzUsLTMuOTMzNzkyIDYuNjc5NjksLTQuOTIxNDk3IDEuODA0NjgsLTAuOTg3NzA1IDMuNzY3NTcsLTEuNzQyNjc2IDUuODg4NjcsLTIuMjY0OTEgMi4xMjEwOSwtMC41MjIyMzYgNC41MDU4NiwtMC43ODMzNTIgNy4xNTQzLC0wLjc4MzM1MiAzLjMyODEyLDAgNi4yOTg4MiwwLjU2NzY0NiA4LjkxMjEsMS43MDI5NCAyLjYxMzI5LDEuMTM1MjkzIDQuNjQwNjMsMi43NTMwODcgNi4wODIwNCw0Ljg1MzM3OSAxLjQ0MTQsMi4xMDAyOTIgMi4xNjIxMSw0LjQ1NjAyOCAyLjE2MjExLDcuMDY3MTk5IDAsMS45NTI3MDYgLTAuMjUxOTYsMy43NTc4MjQgLTAuNzU1ODYsNS40MTUzNTIgLTAuNTAzOTEsMS42NTc1MjggLTEuMjgzMjEsMy4yODY2NzUgLTIuMzM3ODksNC44ODc0MzYgLTEuMDU0NjksMS42MDA3NjUgLTIuNDU1MDgsMy4yODY2NzUgLTQuMjAxMTgsNS4wNTc3MzIgLTEuNzQ2MDksMS43NzEwNTcgLTUuNDY2NzksNS4xMzE1MjcgLTExLjE2MjExLDEwLjA4MTQwMiB2IDAuMzQwNTkxIGggMTkuNDQxNDEgeiIKICAgaWQ9InBhdGgzOTkiIC8+PC9nPgoJCTxnCiAgIGlkPSJnMzk1IgogICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPgoJCQk8Y2lyY2xlCiAgIGNsYXNzPSJzdDEiCiAgIGN4PSIzNC45MDgwNTgiCiAgIGN5PSI3Ni4zOTQyMTEiCiAgIHI9IjciCiAgIGlkPSJjaXJjbGUzODUiIC8+CgkJCTxwYXRoCiAgIGNsYXNzPSJzdDEiCiAgIGQ9Im0gNzEuNDU4NjE4LDU4LjY4MTgyNCBjIC0wLjIwOTE4MiwwIC0wLjQxNjk5MiwtMC4wMTA4MSAtMC42MjMwOTIsLTAuMDI5NCAxLjgwMzQ4OSwzLjg5OTE3NyAyLjk1ODkzMSw4LjE1Njg5OCAzLjMwNjE5OCwxMi42Mzk5OTEgSCA4OS4xMDczIGMgMi4xMzM2NjcsLTMuNzYzMDU0IDUuNjE4OTczLC02LjY2MDY4MiA5Ljc5MzUwMywtOC4wMjkwMjIgViA1OC42ODAxMTUgSCA3MS40ODczMjggYyAtMC4wMDk0LDQuMmUtNSAtMC4wMTkxNCwwLjAwMTcgLTAuMDI4NzEsMC4wMDE3IHoiCiAgIGlkPSJwYXRoMzg3IiAvPgoJCQk8cGF0aAogICBjbGFzcz0ic3QxIgogICBkPSJtIDQyLjEwNzE1NSwyNy43NDQwOCB2IDkuNjQ2NDA0IGMgOC4yNDQzMDgsMS4xMTI0MjMgMTUuNjM2ODY0LDQuOTM4MTEgMjEuMjQ5MTY4LDEwLjU0OTMwNSBMIDU4LjQ2ODI4NSwyNy43NDQwOCBaIgogICBpZD0icGF0aDM4OSIgLz4KCQkJPGNpcmNsZQogICBjbGFzcz0ic3QxIgogICBjeD0iMTA0LjMzODI4IgogICBjeT0iNzkuODk0MjExIgogICByPSIzLjUiCiAgIGlkPSJjaXJjbGUzOTEiIC8+CgkJCTxwYXRoCiAgIGNsYXNzPSJzdDEiCiAgIGQ9Ik0gMTIxLjU4Njg5LDAgSCAxNC4zOTIzODMgQyA2LjQ3NjUzODIsMCAwLDYuNDc2NjIzNSAwLDE0LjM5MjQ2OSB2IDgyLjU3NzQzOCBjIDAsNy45MTU4NDMgNi40NzY1MzgyLDE0LjM5MjQ3MyAxNC4zOTIzODMsMTQuMzkyNDczIEggMTIxLjU4Njg5IGMgNy45MTU4NCwwIDE0LjM5MjM5LC02LjQ3NjYzIDE0LjM5MjM5LC0xNC4zOTI0NzMgViAxNC4zOTI0NjkgQyAxMzUuOTc5MjgsNi40NzY2MjM1IDEyOS41MDI3MywwIDEyMS41ODY4OSwwIFogTSAzNC45MDgwNTgsOTcuMzk0MjExIGMgLTExLjU3OTM5NSwwIC0yMS4wMDAwMDIsLTkuNDIwNTYzIC0yMS4wMDAwMDIsLTIxIDAsLTExLjU3OTQzOCA5LjQyMDYwNywtMjEuMDAwMDA0IDIxLjAwMDAwMiwtMjEuMDAwMDA0IDExLjU3OTM5NSwwIDIxLDkuNDIwNTY3IDIxLDIxLjAwMDAwNCAwLDExLjU3OTQzNyAtOS40MjA2MDgsMjEgLTIxLDIxIHogbSA2OS40MzAyMjIsMCBjIC03Ljc2NjEzNywwIC0xNC4zNjMxNTksLTUuMDg2NTc5IC0xNi42NDQxNDMsLTEyLjEwMTc5MiBIIDY3LjI1NzkzNSBjIC0zLjg2Mjk4OCwwIC02Ljk5NTIxNywtMy4xMjkxNSAtNywtNi45OTE3OTggbCAtMC4wMDQ4LC00LjEwODA1NSBjIDAsLTEyLjc2NTA4NyAtMTAuMzc4MzIzLC0yMy4xNDM0MSAtMjMuMTM0ODY1LC0yMy4xNDM0MSBIIDI1LjYwODYxOCBjIC0zLjg2NTcyMywwIC03LC0zLjEzMzkzNCAtNywtNyAwLC0zLjg2NjA2NiAzLjEzNDI3NywtNyA3LC03IGggMi40OTg1MzUgViAyNy43NDQwOCBoIC0yLjQ5ODUzNSBjIC0zLjg2NTcyMywwIC03LC0zLjEzMzkzNiAtNywtNyAwLC0zLjg2NjA2NCAzLjEzNDI3NywtNyA3LC03IGggMzguMzY3Mzg2IGMgMy4yMzEzNSwwIDYuMDQyOTY1LDIuMjEyMTA5IDYuODAzODA2LDUuMzUzMjIyIGwgNi4xOTIzMzcsMjUuNTgyODEzIGggMjYuNDE1MDgzIGMgNS4yNDU5LDAgOS41MTM1Nyw0LjI2NzY3NyA5LjUxMzU3LDkuNTEzNTczIHYgMTAuNDQ2ODkxIGMgNS4zMjg2MiwzLjAwMzExMyA4LjkzNzQ4LDguNzEzMzAzIDguOTM3NDgsMTUuMjUzNjMyIDAsOS42NDk1MjEgLTcuODUwMzksMTcuNSAtMTcuNSwxNy41IHoiCiAgIGlkPSJwYXRoMzkzIiAvPgoJCTwvZz4KCTwvZz4KCQo8L2c+Cjwvc3ZnPgo=#gh-dark-mode-only

RKE2, also known as RKE Government, is Rancher's next-generation Kubernetes distribution.

It is a fully [conformant Kubernetes distribution](https://landscape.cncf.io/card-mode?selected=rke-government) that focuses on security and compliance within the U.S. Federal Government sector.

To meet these goals, RKE2 does the following:

* Provides [defaults and configuration options](https://docs.rke2.io/security/hardening_guide) that allow clusters to pass the CIS Kubernetes Benchmark [v1.6](https://docs.rke2.io/security/cis_self_assessment16) or [v1.23](https://docs.rke2.io/security/cis_self_assessment123) with minimal operator intervention
* Enables [FIPS 140-2 compliance](https://docs.rke2.io/security/fips_support)
* Regularly scans components for CVEs using [trivy](https://github.com/aquasecurity/trivy) in our build pipeline

### How is this different from RKE or K3s?[​](https://docs.rke2.io/#how-is-this-different-from-rke-or-k3s)

RKE2 combines the best-of-both-worlds from the 1.x version of RKE (hereafter referred to as RKE1) and K3s.

From K3s, it inherits the usability, ease-of-operations, and deployment model.

From RKE1, it inherits close alignment with upstream Kubernetes. In places K3s has diverged from upstream Kubernetes in order to optimize for edge deployments, but RKE1 and RKE2 can stay closely aligned with upstream.

Importantly, RKE2 does not rely on Docker as RKE1 does. RKE1 leveraged Docker for deploying and managing the control plane components as well as the container runtime for Kubernetes. RKE2 launches control plane components as static pods, managed by the kubelet. The embedded container runtime is containerd.

### Why two names?[​](https://docs.rke2.io/#why-two-names)

It is known as RKE Government in order to convey the primary use cases and sector it currently targets.

It is also known as RKE2 as it is the next iteration of the Rancher Kubernetes Engine for datacenter use cases. The distribution runs standalone and integration work into Rancher is underway. We intend to make RKE2 an option in Rancher once it achieves feature parity with RKE. An upgrade path from RKE to RKE2 is also under development for those that want to migrate.

Rancher Labs supports responsible disclosure and endeavors to resolve security issues in a reasonable timeframe. To report a security vulnerability, email [\[email protected\]](https://docs.rke2.io/cdn-cgi/l/email-protection#9be8fef8eee9f2efe2dbe9faf5f8f3fee9b5f8f4f6).


# Rancher

Rancher is a Kubernetes management tool to deploy and run clusters anywhere and on any provider.

Rancher can provision Kubernetes from a hosted provider, provision compute nodes and then install Kubernetes onto them, or import existing Kubernetes clusters running anywhere.

Rancher adds significant value on top of Kubernetes, first by centralizing authentication and role-based access control (RBAC) for all of the clusters, giving global admins the ability to control cluster access from one location.

It then enables detailed monitoring and alerting for clusters and their resources, ships logs to external providers, and integrates directly with Helm via the Application Catalog. If you have an external CI/CD system, you can plug it into Rancher, but if you don't, Rancher even includes Fleet to help you automatically deploy and upgrade workloads.

Rancher is a complete container management platform for Kubernetes, giving you the tools to successfully run Kubernetes anywhere.

<https://ranchermanager.docs.rancher.com/>

• The Rancher server manages and provisions Kubernetes clusters. You can interact with downstream Kubernetes clusters through the Rancher server's user interface. The Rancher management server can be installed on any Kubernetes cluster, including hosted clusters, such as Amazon EKS clusters. • RKE (Rancher Kubernetes Engine) is a certified Kubernetes distribution and CLI/library which creates and manages a Kubernetes cluster. • K3s (Lightweight Kubernetes) is also a fully compliant Kubernetes distribution. It is newer than RKE, easier to use, and more lightweight, with a binary size of less than 100 MB. • RKE2 is a fully conformant Kubernetes distribution that focuses on security and compliance within the U.S. Federal Government sector.

[Rancher Install](/readme/architect/kubernetes/frameworks/rancher/rancher-install)


# Rancher Install

<https://habr.com/ru/articles/720256/>

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Frameworks/Rancher/Rancher%20Install/Untitled)

Если вы активно используете kubernetes в своей инфраструктуре, при этому у вас небольшая команда, или она состоит в основном из разработчиков, то у меня к вам вопрос: ну как вам — стала жизнь легче? Наверное те, кто используют managed‑решения в некотором роде покивают головой. Продавцы этих решений скажут «да!», с особенно довольным лицом, а бизнес, пуская скупую слезу, просто согласятся с большинством (ну бизнес же растёт).

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

## Анализ рынка

На рынке сейчас есть несколько наиболее популярных (по слухам) платформ, которые решают примерно один и тот же набор задач:

* OpenShift — обладает особой популярностью благодаря поддержке от RedHat. Не возьмусь про него подробно писать, потому что с ним не работал (видел, даже тыкал всякое, но не использовал). Предоставляет как и другие всё необходимое, чтобы развернуть кластер и управлять контейнерами. Говорят, удобный во многом. Есть целые секты.
* Deckhouse — по сути платформа, которая позволяет пользователям развернуть ванильный куб с обвязкой в виде стандартного набора приложений и некоторого гуя. Кластеры можно развернуть в публичных облаках (AWS, Azure, GCP, Yandex Cloud, который считается киллер‑фичей), частных (VMWare и OpenStack) и, конечно же, на голом железе. На сайте они позиционируют продукт как NoOps.
* Rancher — то же самое, что и Deckhouse, только вышел раньше, с некоторого времени принадлежит Suse, имеет шире поддержку по облакам (использует для управления виртуалками механизм docker‑machine‑driver) и умеет управлять managed‑кластерами (EKS, GKS и т. п.). Одна из особенностей — все плагины доступны во Free версии, включая Istio, безопасность и т. п. Дословный перевод — владелец ранчо (большой сельскохозяйственный участок, где очень часто надо разгребать г.... бардак).

Нельзя сказать, что какой‑то продукт лучше или хуже. На самом деле, если приглядеться, то можно увидеть, что у каждого есть своя сильная черта. Я продвигаю Rancher по двум причинам. Во‑первых, я с ним работал, относительно хорошо знаю его сильные и слабые стороны. Когда я искал инструмент, который бы решал мои задачи, то выбора особо и не было. А во‑вторых, я наивно верю в силу Open Source и уверен, что, приложив немного усилий, я смогу адаптировать этот инструмент под любой облачный вендор (мощная система плагинов решает).

Забавный факт

Free Software и Open Source многим и мне в том числе напоминают коммунизм: трудишься на благо общества, не взирая на трудности, ничего не ожидая взамен.

Что интересно, но в бывшем СССР, который был оплот коммунизма, сейчас процветает кровавый капитализм: именно отечественные продукты стараются максимально всё выжать за каждую фичу, а Free‑версии тщательно урезаются в функционале. Можно по пальцам пересчитать продукты, которые полностью бесплатны.

Недавно общался со знакомым из штатов. Кажется, что там коммунизм победил. Он мне рассказал про «фудбанки» (можно прийти и набрать продукты абсолютно бесплатно), и я задумался — а ведь в направлении свободного и открытого ПО у нас то же самое. Сколько всего мы используем в жизни, что подарили нам энтузиасты: Линус (Финляндия), Гвидо (Нидерланды), Google (ведь это они нам подарили k8s) и т. д.

Единицы людей делают что‑то по‑настоящему полезное. Ещё меньшее количество выкладывают это под свободными лицензиями. Соотечественники, давайте делать настоящее добро без корысти! Меньше болденос и больше чего‑то нового!

## Под капотом

Чтобы понять — откуда мощь, нужно заглянуть «под капот» платформы, и там обнаружится, что и этот GUI обёртка над CLI. По классике жанра, хороший интерфейс делает более привлекательным и более понятным мощный набор инструментов для деплоя и управления кластерами kubernetes.

Rancher Kubernetes Engine — первое поколение инструментария для решения проблемы установки кластера в различных средах. Для тех, кому это важно, rke использует docker практически везде. С помощью докера на сервер или вируталку доставляются все компоненты, а так же сами контейнеры подов. Возможно, именно поэтому для старта кластера требуются слегка большие мощности (может не взлететь на вирутуалке «2 ядра, 2 гига»). Но, преимуществ так же хватает: знакомый инструментарий, возможности docker‑machine и т. п.

Этот инструмент отлично подходит для GitOps, потому что он берёт конфиг, на основании него формирует state‑файл, в котором описывает что, где и как развернул, и конфиг для kubectl. Так же легко интегрируется в CI, как и любая другая утилита, не требующая отдельного сервиса. Жирный минус в том, что версии кластера сильно отстают от upstream, но это не всех волнует.

Примеры конфигураций

Простой AIO кластер на bare-metal:

```
cluster_name: "rke-test"
ssh_agent_auth: true
ssh_key_path: "~/.ssh/rke-test.pem"
# kubernetes_version: v1.10.3-rancher2

nodes:
 - address: 172.16.160.165
   internal_address: 172.16.160.165
   user: rke
   ssh_key_path: "~/.ssh/default.pem"
   role:
    - controlplane
    - worker
    - etcd

ingress:
   provider: nginx
   network_mode: hostNetwork

```

Bare-metal кластер с распределением ролей:

```
cluster_name: "rke-test"
ssh_agent_auth: true
ssh_key_path: "~/.ssh/rke-test.pem"
# kubernetes_version: v1.10.3-rancher2

nodes:
 - address: 172.16.160.165
   internal_address: 172.16.160.165
   user: rke
   ssh_key_path: "~/.ssh/rke-test-master.pem"
   role:
    - controlplane
    - etcd

- address: 172.16.160.167
  internal_address: 172.16.160.167
  user: rke
  ssh_key_path: "~/.ssh/rke-test-worker.pem"
  role:
   - worker

- address: 172.16.160.168
  internal_address: 172.16.160.168
  user: rke
  ssh_key_path: "~/.ssh/rke-test-worker.pem"
  role:
   - worker

ingress:
   provider: nginx
   network_mode: hostNetwork
```

Больше примеров [здесь](https://rancher.com/docs/rke/latest/en/example-yamls/).

### K3S

Облегчённая версия кластера, где в качестве хранилища состояния кластера может выступать некоторый набор SQL баз. Это всего один бинарь, который содержит в себе весь необходимый набор компонентов, чтобы проинициализировать кластер. Настолько легковесный, что очень часто используется DIY‑решениях на базе «малинки» и прочих одноплатников. Более подробно можно почитать в моём [предыдущем посте](https://habr.com/ru/post/711440/).

### RKE2

Rancher Kubernetes Engine второго поколения взял лучшее из предыдущих двух миров. Но в большей степени это полноценный «сын маминой подруги» для k3s. Процесс установки и развёртывания практически идентичен. Сами разработчики позиционируют его как решение для государственных учреждений, с особым отношением к безопасности и уязвимостям.

Это три наиболее значимых инструмента, которые используются или подразумевают использование внутри самого Rancher'а. Первый используется непосредственно внутри для деплоя и масштабирования кластеров. Второй подразумевает использование для решения проблемы отказоустойчивости самой платформы. Третий предназначен для стабильных production‑кластеров. Все вместе они позволяют строить гибридные и не очень кластера.

## Rancher

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-05a09fa6703f850542f1b4028cf944d98afd528c%2F3bae1ba66f299e7cf5080f31abe03113.png?alt=media)

Взято с официальной документации

Сам сервер Rancher это самостоятельный сервис, который может быть развёрнут как простой контейнер в docker (или аналоге), так и в уже готовом кластере. Ресурсов для одного контейнера он потребляет «будь здоров», но вместе с этим помимо создания и масштабирования кластеров, сервис предоставляет такие возможности как:

* Аутентификация (я пользовался только встроенной и Active Directory).
* Управление политиками безопасности.
* Контроль доступа к кластерам.
* Набор инструментов для управления приложениями и проектами.

А чтобы прям совсем было удобно, они добавили возможность развернуть по кнопке сервисы и управлять Service mesh (Istio), мониторингом (Grafana+Prometheus), алертами, логами и даже Continuous Delivery (Fleet) запихнули. Эти компоненты не являются обязательными и можно настроить свои сервисы. Однако именно с этими интегрирован сам интерфейс. Это не исчерпывающий список сервисов (есть решение по безопасности NeuVector+CIS, хранилищам Longhorn, и т. п.), но важно то, что можно заточить практически любое расширение с помощью системы плагинов.

### Установка

Вариантов установки всего два: одна машина или кластер kubernetes. В документации на сайте описаны примеры для некоторых облачных провайдеров, но всё сводится к одному — helm chart. Самое главное, чтобы к кластеру и от сервера Rancher был доступ к агенту.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9fbe40049fe8cf92b4a1d4d86ac024f3e66a11b4%2F91e9b696011714a822024826988864b5.png?alt=media)

Скриншот из официальной документации

Такая архитектура позволяет размещать сам управляющий кластер в закрытом и защищённом контуре (спокойно работает за NAT) и ограничить доступ к кластеру.

```
docker run -d --restart=unless-stopped \
  -p 80:80 -p 443:443 \
  -v /opt/rancher:/var/lib/rancher \
  --privileged \
  rancher/rancher:stable
```

В таком варианте сервер поднимется на стандартных HTTP/HTTPS портах и для TLS будет использоваться сгенерированный ранчером самоподписанный сертификат. Для тестов более чем достаточно. Однако, если есть возможность, то лучше подкинуть свой проверенный сертификат:

```
# <CERT_DIRECTORY> - путь на текущей машине, где лежат сертификаты.
# <FULL_CHAIN.pem> - имя файла с полной цепочкой сертификатов.
# <PRIVATE_KEY.pem> - имя файла ключа, которым зашифрован данный сертификат.
docker run -d --restart=unless-stopped \
  -p 80:80 -p 443:443 \
  -v /opt/rancher:/var/lib/rancher \
  -v /<CERT_DIRECTORY>/<FULL_CHAIN.pem>:/etc/rancher/ssl/cert.pem \
  -v /<CERT_DIRECTORY>/<PRIVATE_KEY.pem>:/etc/rancher/ssl/key.pem \
  --privileged \
  rancher/rancher:stable \
  --no-cacerts
```

или для тех, у кого сервер будет торчать наружу ~~голым задом~~ открытыми портами:

```
docker run -d --restart=unless-stopped \
  -p 80:80 -p 443:443 \
  -v /opt/rancher:/var/lib/rancher \
  --privileged \
  rancher/rancher:stable \
  --acme-domain мой.потрясающий.домен
```

Есть и другие варианты, например, использовать [терминирующий прокси](https://ranchermanager.docs.rancher.com/how-to-guides/advanced-user-guides/configure-layer-7-nginx-load-balancer), который решит проблему сертификатов на своей стороне.

В моей интерпретации установки, по сравнению с официальной документацией, можно заметить пару нюансов — я всегда монтирую директорию с данными и использую канал stable. Без этого вы просто не сможете обновить образ, потому что все данные о кластерах исчезнут, а на нестабильный канал слишком часто обновляется. А так всё сводится к тому, чтобы спулить свежий образ, остановить контейнер, удалить его и запустить команду по новой. Если сервер, на котором вы всё это проделываете достаточно надёженый и мощный, активно бэкапится и имеет резервное питание, то для относительно небольшого количества кластеров такого варианта будет достаточно.

Насколько мощный сервер/виртуалка?

В требованиях можно найти такую табличку:

| Deployment Size | Clusters   | Nodes        | vCPUs | RAM    |
| --------------- | ---------- | ------------ | ----- | ------ |
| Small           | Up to 150  | Up to 1500   | 2     | 8 GB   |
| Medium          | Up to 300  | Up to 3000   | 4     | 16 GB  |
| Large           | Up to 500  | Up to 5000   | 8     | 32 GB  |
| X-Large         | Up to 1000 | Up to 10,000 | 16    | 64 GB  |
| XX-Large        | Up to 2000 | Up to 20,000 | 32    | 128 GB |

Как видите, в целом основным ресурсом, на который распространяется аппетит сервера, является память. Сервис можно поднять и на 6ГБ, но работать будет просто отвратительно. Диск сложно оценивать по размеру, потому что всё зависит от настроек логгирования (самый прожорливый сервис). Для эксперимента хватит 10Гб.

Если же мы уверены, что хотим использовать большое количество кластеров или хотим так же задействовать текущие мощности для каких‑то иных задач (например, дополнительно развернуть внутренние сервисы git, управление проектами, чат и т. п.), то можно использовать существующий кластер kubernetes и развернуть Rancher на нём, с помощью [helm chart'а](https://ranchermanager.docs.rancher.com/pages-for-subheaders/install-upgrade-on-a-kubernetes-cluster). Кластер, в который будет установлен сервис автоматически появится в списке как `local` , и им так же можно будет управлять. Если этот кластер работает на базе k3s или rke/rke2, то вам тоже будет доступно обновление самого кластера.

Интересный факт

На самом деле внутри образа в докере будет работать тот самый k3s. Одна из причин, почему контейнер так долго поднимается, это процесс подъёма k3s кластера с перезапуском контейнера и раскаткой там нужных сервисов. Можно сказать, что самым нативным способом поднять Rancher является создание кластера на базе k3s и установка там helm чарта.

## Управление

После того как мы развернули сервис, на этом интересное не заканчивается. Что можно делать с кластерами:

* Разворачивать новые с помощью RKE и различных Node Driver'ов или с помощью запуска агента в докере (команда генерируется в интерфейсе). Так же доступны плагины для запуска managed-решений (т.е. GKS, который сам по себе кластер, ECS, который даже не kubernetes как таковой).
* Обновлять версию kubernetes кластера и прочее обслуживание.
* Подключать репозитории helm и устанавливать оттуда приложения в кластеры.
* Управлять всеми сущностями контейнеров (Workaload=Deployment/DaemonSet, Service, Ingress и т.п.).
* Подключить необходимые плагины и расширения.
* Управлять доступом и пользователями.

### Драйверы

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

Доступные драйверы

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9dcfde106554425492ca413513d0123d40270541%2F90a62dc4baec85f4c3b6a7967d713bb5.png?alt=media)

Драйверы управляемых кластеров.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-4882209bff50457c4fd2307674a41551e772c81c%2Fa9e7a6d48af3df89e78a6d822c2fd615.png?alt=media)

Кусочек списка доступных драйверов для вируталок (там ниже есть VMWare)

Так же можно добавить свой через интерфейс, указав путь до бинарника docker-machine-driver.

Очень часто люди спрашивают за [Proxmox VE driver](https://github.com/lnxbil/docker-machine-driver-proxmox-ve). Мы с командой в своё время допилили до ума его так, что он проверенно работает. По аналогии можно написать свой под любые нужды. Главный принцип в том, чтобы драйвер предоставил нужную информацию Rancher'у о хосте и параметрах подключения. Дальнейшая работа осуществляется в докере.

Для особо искушённых, можно реализовать собственный драйвер управляемого кластера (т. е. не самих виртуалок, а уже управляемого кластера). Например, очень актуально написать драйвер для местных Yandex, VK, MTS, Selectel и т. п. Пишется всё на Go, и есть [хороший пример](https://github.com/rancher-plugins/kontainer-engine-driver-example) для Google облака. Собранный бинарь просто загружается через UI на сервис и предоставляет интерфейс создания/управления.

### Обслуживание и обновление

Базовые инструменты обслуживания, которые даёт Rancher:

* Получение всех необходимых конфигурационных файлов.
* Интерактивная оболочка (да, можно прям из интерфейса хоть с телефона).
* Управление нодами (всякие метки, параметры, drain и прочее).
* Снэпшоты etcd (можно в S3).
* Бесшовное обновление (можно настроить стратегию обновления).
* Шаблоны RKE (какая версия кластера, параметры и т. п.)

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

### Приложения и расширения

Насколько всё просто

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-56aebdfe261a188bda36b85b2301775e98b96cb8%2F5f01eb25d5e51cf5466537786649291f.png?alt=media)

Один из чартов

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f335bcb4bd49a941b4687c35a1c8ee6a0465e7a9%2F60f1edc797c2bb95ae257cf227510ab9.png?alt=media)

Установка приложений идёт и так же можно посмотреть лог

Чтобы установить какое‑то приложение, доступное в репозиториях, достаточно «жмякнуть» на Install и следовать инструкциям. Это крайне просто, в стандартных репозиториях есть всё необходимое. Но, так же можно подключать репозитории с чартами как на уровне всей платформы, так и на уровне конкретного кластера.

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

Ручной труд

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f33f2decc09edb2e2b8c7933afa0b9da1a2a4fa6%2Fabf3b72a38bcb0eb3043644d3050569e.png?alt=media)

Даже с объяснением для "особенных"

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-cbf09d4040cc3a7487d26986189076f744545747%2Fda6bb041dcc76d1f408e90269c85b92c.png?alt=media)

Все параметры подписаны и можно посмотреть что получится в итоге

После создания деплоймента нужно создать сервис

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d89709b27b3fe9048738780a3d6f25a78d009dc2%2F481a7c7ea5b07db9f05aef6c1bfb060b.png?alt=media)

Тут тоже создаётся осмысленный объект

И так далее...

Стоит отметить, что расширения находятся в тех же самых репозиториях, что и приложения. Чтобы написать своё расширение, достаточно заглянуть в репозитории Rancher'а.

### Управление доступом и пользователями

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

Создание пользователя в платформе

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-a85aad0360919d4f7a2d62c0092100a1e49fc69c%2F87fa9f7f345c46a248432bb4dd323ba8.png?alt=media)

Уже видно, что можно настраивать доступ ко всем частям платформы по отдельности

Добавление пользователя в кластер

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7d34a1ebc8472929ea38c590f867583d5d2a13ab%2F7788890f8f24e58aca12b14ba2fc5ac2.png?alt=media)

## Итог

Когда мы управляем одним или двумя кластерами выделенной командой эксплуатации, то в целом инструмент не имеет значения. Во многих случаях, кластер может быть managed от вендора, и это покроет все необходимые задачи. Но когда мы хотим дать возможность разработчикам и командам самим управлять своими кластерами, чтобы распределить нагрузку, то нужен такой инструмент, с которым справится даже джун. У ранчера хорошие шансы заменить как таковые managed‑кластеры со множеством разных интерфейсов у разных провайдеров на единый удобный UI. За счёт того, что у него очень гибкая система плагинов и полностью открытая лицензия, можно небольшой командой поддерживать настоящий зоопарк разных вендоров незаметно для пользователей продукта (команды каждого продукта).

Моё личное мнение, которое никому не нужно, в том, что несмотря на многообразие разных платформ, на данный момент самым гибким и простым пока что является Rancher. У них ещё много классных продуктов, но я постарался передать информацию в общем виде о тех, которые я использовал в своей работе.


# Auth

[Keycloak in k8s](/readme/architect/kubernetes/auth/keycloak-in-k8s)

[LDAP](/readme/architect/kubernetes/auth/ldap)


# Keycloak in k8s

<https://habr.com/ru/companies/kaspersky/articles/763790/>

Привет, Хаброжители! Продолжаем делиться с вами экспертизой отдела Security services infrastructure ([департамент Security Services компании «Лаборатории Касперского»](https://kas.pr/habr-sandzhiev-keycloak2-sec-serv)).

Предыдущую статью нашей команды вы можете прочесть вот здесь: [Keycloak. Админский фактор и запрет аутентификации](https://habr.com/ru/companies/kaspersky/articles/756812/)

В этой части продолжим настраивать IAM с упором на отказоустойчивость и безопасность. Статья рассчитана на людей, которые ранее были знакомы с IAM и, в частности, с keycloak-ом. Поэтому в этой части не будет «базы» по SAML2, OAuth2/OIDC и в целом по IAM (на Хабре есть хорошие статьи на эту тему). Также для понимания данной статьи необходимы знания базовых абстракций kubernetes и умение читать его манифесты.

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Auth/Keycloak%20in%20k8s/Untitled)

Рассмотрим два кейса:

1. Как в свежей версии keycloak (v.22.0.3) настроить отказоустойчивость при развертывании в k8s в режиме standalone-ha.
2. Как закрыть ненужные векторы атаки, ограничив пользователям доступ только до нужных путей, но оставив возможность админам заходить на консоль админки keycloak.

## Первый кейс. Keycloak standalone-HA в k8s (v.22.0.3)

На данную тему уже были хорошие статьи на Хабре, вот примеры:

Плагиатить данные статьи не имею желания и не вижу смысла. Теорию по HA для keycloak вы можете взять из них.

В своей статье постараюсь описать, что же изменилось в настройке на период Q3–Q4 2023 года и как сейчас можно без «головной боли» настроить standalone-ha в k8s.

Изменения:

1. Keycloak перешел с выделенного сервера приложений WildFly на Quarkus (на момент написания статьи версия 3.2.5.Final).
2. Cменилось registry c jboss/keycloak на quay.io/keycloak/keycloak.
3. Старые версии образов используют устаревшие переменные среды, которые в современных версиях не поддерживаются (работу с тегом -legacy не проверял).
4. И самое главное: в статьях прошлых лет нет манифестов для развертывания keycloak в режиме standalone-ha в k8s, хотя многим компаниям/проектам этого режима достаточно для базовой отказоустойчивости инструмента аутентификации/авторизации (а при необходимости и идентификации) для производственной среды.

Напомню, что keycloak может быть развернут в следующих режимах: standalone, standalone-ha, domain cluster, DC replication.

Режим standalone-ha, у keycloak развернутого в k8s, включает в себя следующие элементы или наборы подов в нашем случае:

1. Поды с выделенным сервером приложений Quarkus и встроенным модулем Infinispam, собранным в кластер (Key-value database, используется для хранения кэша, аналог redis-a).
2. Поды распределенной СУБД, собранной в кластер, либо отдельно стоящий кластер СУБД.
3. Reverse-proxy (в нашем случае ingress-controller), чтобы балансировать нагрузку.

Как собрать кластер СУБД внутри k8s, в данной статье описывать не будем, для базового примера с Postgresql можете развернуть Хельм-чарт от Bitnami: PostgreSQL или PostgreSQL-ha. Либо посмотреть в сторону k8s-операторов СУБД (у Фланта есть хорошие статьи на эту тему на Хабре). Как развертывать ingress-controller в k8s, в данной статье опустим (это популярный кейс и легко гуглится).

Берем за исходные данные то, что у нас перед развертыванием keycloak задеплоены PosgreSQL и ingress-controller (Nginx).

Что касаемо самого keycloak, для удобства развертывания вы можете использовать чарт от Bitnami: keycloak, но для понимания мы развернем standalone-ha keycloak, используя манифесты куба, + Bitnami любят менять название переменных в своих образах, которые отличаются от официальной документации keycloak-a, а это может внести путаницу.

Необходимые нам манифесты:

1. Service. Нужен для балансировки внешних запросов (запросов аутентификации) от пользователей. То есть чтобы пользователя забрасывало на разные вебки keycloak при аутентификации.

```
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak-http
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 8080
      protocol: TCP
  selector:
    app: keycloak-ha

```

2. Headless Service. Нужен для определения количества подов кластера Infinispan. Так как headless-service не имеет собственного ip-адреса, то при использовании протокола обнаружения узлов кластера JGroups, такого как DNS\_PING, он в ответе получит ip-адреса всех эндпоинтов keycloak+infinispan. Протоколы обнаружения JDBC\_PING и KUBE\_PING в режиме standalone-ha [не используются](https://www.keycloak.org/server/caching).

```
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak-headless
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: keycloak-ha

```

3. StatefulSet. Нужен для развертывания реплик сервера приложений Quarkus со встроенным модулем Infinispam. Используется StatefulSet, а не Deployment, так как StatefulSet может использовать Headless Service для управления доменом своих подов. Поле `serviceName` в StatefulSet как раз для этого и необходимо.

```
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: keycloak
  labels:
    app: keycloak-ha
spec:
  selector:
    matchLabels:
      app: keycloak-ha
  replicas: 2
  serviceName: keycloak-headless
  podManagementPolicy: Parallel
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: keycloak-ha
    spec:
      restartPolicy: Always
      securityContext:
        fsGroup: 1000
#      priorityClassName: high
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:22.0.3
          imagePullPolicy: Always
          resources:
            limits:
              memory: 1500Mi
            requests:
              memory: 500Mi
              cpu: 100m
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            capabilities:
              drop:
                - ALL
                - CAP_NET_RAW
            readOnlyRootFilesystem: false # Quarkus не запускается если данное поле securityContext-a выставить в "true"
            allowPrivilegeEscalation: false
          args:
            - start
          env:
            - name: KC_METRICS_ENABLED
              value: "true"
            - name: KC_LOG_LEVEL
              value: "info"
            - name: KC_CACHE # тут мы указываем что кеш будем хранить в infinispan
              value: "ispn"
            - name: KC_CACHE_STACK # тут мы указываем, какую конфигурацию нужно выбрать infinispan-у что б он работал в кубе с протоколом обнаружения DNS_PING
              value: "kubernetes"
            - name: KC_PROXY
              value: "edge"
            - name: KEYCLOAK_ADMIN
              valueFrom:
                secretKeyRef:
                  name: ...
                  key: "..."
            - name: KEYCLOAK_ADMIN_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: ...
                  key: "..."
            - name: KC_DB
              value: "postgres"
            - name: KC_DB_URL_HOST
              value: "postgresql-keycloak"
            - name: KC_DB_URL_PORT
              value: "5432"
            - name: KC_DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: ...
                  key: "..."
            - name: KC_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: ...
                  key: "..."
            - name: KC_DB_URL_DATABASE
              valueFrom:
                secretKeyRef:
                  name: ...
                  key: "..."
            - name: KC_FEATURES
              value: "docker"
            - name: KC_HOSTNAME
              value: "keycloak.example.ru"
            - name: JAVA_OPTS_APPEND # обязательное поле необходимое для работы DNS_PING, указываем наш Headless Service
              value: "-Djgroups.dns.query=keycloak-headless.keycloak.svc.cluster.local"
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 120
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /realms/master
              port: http
            initialDelaySeconds: 60
            timeoutSeconds: 1
      terminationGracePeriodSeconds: 60

```

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

Также на нем выставляем привязку сессий (README/README.md)), чтобы все запросы пользователя в рамках одной сессии передавались на один под. Иначе мы усложним жизнь infinispan-y, которому придется передавать данные о сессиях пользователей между подами.

```
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak
  annotations:
    # следующие 4 строки аннотаций настраивают Sticky Sessions на ingress-e
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-expires: "86400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "86400"
    nginx.ingress.kubernetes.io/session-cookie-name: "keycloak-cookie"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
      - keycloak.examlple.ru
      secretName: web-tls
  rules:
  - host: keycloak.examlple.ru
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: keycloak-http
            port:
              name: http
      #путь "/" слишком избыточен для пользователей и создает дополнительные векторы атаки,
      #тут он представлен только для примера, во 2-м кейсе этой статьи пофиксим =)

```

Деплоим это все в k8s и смотрим, собрался ли наш кластер infinispan:

```
kubectl apply -f <folder>

```

Если в логах контейнеров такого вида строки (отображается два элемента keycloak: keycloak-1-XXXXX, keycloak-0-XXXXX), то значит, кластер infinispam собрался

```
2023-09-08 13:48:20,514 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN` with stack `kubernetes`
2023-09-08 13:48:20,518 INFO  [org.jgroups.JChannel] (keycloak-cache-init) local_addr: b4d6190d-e9cb-4a3c-8d01-c611129f2a3b, name: keycloak-0-11230
2023-09-08 13:48:20,530 INFO  [org.jgroups.protocols.FD_SOCK2] (keycloak-cache-init) server listening on *.57800
2023-09-08 13:48:20,672 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: [keycloak-1-40291|15] (2) [keycloak-1-40291, keycloak-0-11230]
2023-09-08 13:48:20,783 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `keycloak-0-11230`
2023-09-08 13:48:21,223 INFO  [org.infinispan.LIFECYCLE] (jgroups-6,keycloak-0-11230) [Context=org.infinispan.CONFIG] ISPN100002: Starting rebalance with members [keycloak-1-40291, keycloak-0-11230], phase READ_OLD_WRITE_ALL, topology id 42
2023-09-08 13:48:21,260 INFO  [org.infinispan.LIFECYCLE] (non-blocking-thread--p2-t5) [Context=org.infinispan.CONFIG] ISPN100010: Finished rebalance with members [keycloak-1-40291, keycloak-0-11230], topology id 42

```

Первый кейс решен, keycloak развернут в режиме standalone-ha в k8s!

P. S. Если не уверены, потянет ли развернутое вами количество подов keycloak-HA всех ваших пользователей, то можете использовать проект самого keycloak-a под названием [keycloak-benchmark](https://github.com/keycloak/keycloak-benchmark) для проведения тестов производительности.

## Второй кейс. Админка на localhost и закрытие всех излишних путей для пользователей

Если мы развернем ingress keycloak-a, как в первом кейсе, то пользователям будут доступны все пути, в том числе и админка, а этого делать не рекомендуется, так как создаются дополнительные векторы атаки на систему аутентификации. [В официальной документации](https://www.keycloak.org/server/reverseproxy) keycloak можно посмотреть, какие пути достаточны для потока аутентификации пользователей.

Обрезать пути будем на reverse-proxy. Для работы стандартного потока аутентификации достаточно пути /realms/, но для примера указаны все пути, которые могут пригодиться и которые рекомендует keycloak. Измененный ingress будет выглядеть так:

```
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak
  annotations:
    # следующие 4 строки аннотаций настраивают Sticky Sessions на ingress-e
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-expires: "86400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "86400"
    nginx.ingress.kubernetes.io/session-cookie-name: "keycloak-cookie"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
      - keycloak.examlple.ru
      secretName: web-tls
  rules:
  - host: keycloak.examlple.ru
    http:
      paths:
      - path: /realms/
        pathType: Prefix
        backend:
          service:
            name: keycloak-http
            port:
              name: http
      - path: /resources/
        pathType: Prefix
        backend:
          service:
            name: keycloak-http
            port:
              name: http
      - path: /robots.txt
        pathType: ImplementationSpecific
        backend:
          service:
            name: keycloak-http
            port:
              name: http
      - path: /js/
        pathType: Prefix
        backend:
          service:
            name: keycloak-http
            port:
              name: http

```

После данных изменений доступа извне к админке не будет ни у кого. Но администраторам же надо конфигурировать сам IAM, а постоянно возвращать путь "/" для этих целей в ingress-e не хотелось бы. Можно использовать k8s port-forwarding, но админка все равно будет редиректить на внешний адрес keycloak-a, и в итоге мы получим страницу с «вечной» загрузкой административной консоли. Мы решили данный кейс, добавив в StatefulSet keycloak-a переменную KC\_HOSTNAME\_ADMIN\_URL (поменяв редирект админки на [localhost](http://localhost/):9999/):

```
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: keycloak
  labels:
    app: keycloak-ha
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
          ...
          args:
            - start
          env:
            - name: KC_HOSTNAME_ADMIN_URL
              value: "http://localhost:9999/"
            ...
      ...

```

После этого выполним k8s port-forwarding на 9999/tcp:

```
kubectl port-forward -n keycloak services/keycloak-http 9999:8080

```

Далее заходим в браузере на [localhost](http://localhost/):9999 и с welcome-страницы переходим в админку.

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

P.S. Я понимаю, что у читателей могут возникнуть замечания типа: администратор k8s и администратор IAM могут быть разные люди и администратору IAM придется давать права на port-forward. Но на практике во многих компаниях эту роль выполняют одни и те же люди, + настраивайте правильно RBAC в k8s и используйте impersonate для повышения привилегий.


# LDAP

<https://habr.com/ru/articles/441112/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dde92941d31a9dbd2bd7a92ac959fcf4b84f31a7%2Fkj60vi6ugv7bunlqdts2nlr2vja.png?alt=media)

Небольшая инструкция о том, как используя Keycloak можно связать Kubernetes с вашим LDAP-сервером и настроить импорт пользователей и групп. Это позволит настраивать RBAC для ваших пользователей и использовать auth-proxy чтобы защитить Kubernetes Dashboard и другие приложения, которые не умеют производить авторизацию самостоятельно.

## Установка Keycloak

Предположим что у вас уже есть LDAP-сервер. Это может быть Active Directory, FreeIPA, OpenLDAP или что-либо еще. Если LDAP-сервера у вас нет, то в принципе вы можете создавать пользователей прямо в интерфейсе Keycloak, либо использовать публичные oidc-провайдеры (Google, Github, Gitlab), результат получится почти такой же.

Первым делом установим сам Keycloak, установка может выполняться отдельно, так и сразу в Kubernetes-кластер, как правило если у вас имеется несколько Kubernetes-кластеров, было бы проще установить его отдельно. С другой стороны вы всегда можете использовать [официальный helm-чарт](https://github.com/helm/charts/tree/master/stable/keycloak) и установить его прямо в ваш кластер.

Для хранения данных Keycloak вам понадобится база данных. По умолчанию используется `h2` (все данные хранятся локально), но возможно также использовать `postgres`, `mysql` или `mariadb`.

Если вы все же надумали установить Keycloak отдельно, более подробные инструкции вы найдете в [официальной документации](https://www.keycloak.org/docs/latest/getting_started/index.html).

## Настройка федерации

Первым делом создадим новый realm. Realm — это пространство нашего приложения. Каждое приложение может иметь свой realm с разными пользователями и настройками авторизации. Master realm используется самим Keycloak и использовать его для чего-нибудь еще неправильно.

Нажимаем **Add realm**

| Option            | Value                                                                   |
| ----------------- | ----------------------------------------------------------------------- |
| Name              | kubernetes                                                              |
| Display Name      | Kubernetes                                                              |
| HTML Display Name | \<img src="<https://kubernetes.io/images/nav\\_logo.svg>" width="400" > |

Kubernetes по умолчанию проверяет подтвержден у пользователя email или нет. Так как мы используем собственный LDAP-сервер, то тут эта проверка почти всегда будет возвращать `false`. Давайте отключим представление этого параметра в Kubernetes:

**Client scopes** --> **Email** --> **Mappers** --> **Email verified** (Delete)

Теперь настроим федерацию, для этого перейдем в:

**User federation** --> **Add provider...** --> **ldap**

Приведу пример настройки для FreeIPA:

| Option                         | Value                                                   |
| ------------------------------ | ------------------------------------------------------- |
| Console Display Name           | freeipa.example.org                                     |
| Vendor                         | Red Hat Directory Server                                |
| UUID LDAP attribute            | ipauniqueid                                             |
| Connection URL                 | ldaps\://freeipa.example.org                            |
| Users DN                       | cn=users,cn=accounts,dc=example,dc=org                  |
| Bind DN                        | uid=keycloak-svc,cn=users,cn=accounts,dc=example,dc=org |
| Bind Credential                |                                                         |
| Allow Kerberos authentication: | on                                                      |
| Kerberos Realm:                | EXAMPLE.ORG                                             |
| Server Principal:              | HTTP/freeipa.<example.org@EXAMPLE.ORG>                  |
| KeyTab:                        | /etc/krb5.keytab                                        |

Пользователя `keycloak-svc` нужно создать заранее на нашем LDAP-сервере.

В случае с Active Directory, достаточно просто выбрать **Vendor: Active Directory** и необходимые настройки подставятся в форму автоматически.

Нажимаем **Save**

Теперь перейдем:

**User federation** --> **freeipa.example.org** --> **Mappers** --> **First Name**

| Option         | Value     |
| -------------- | --------- |
| Ldap attribure | givenName |

Теперь включим маппинг групп:

**User federation** --> **freeipa.example.org** --> **Mappers** --> **Create**

| Option                        | Value                                        |
| ----------------------------- | -------------------------------------------- |
| Name                          | groups                                       |
| Mapper type                   | group-ldap-mapper                            |
| LDAP Groups DN                | cn=groups,cn=accounts,dc=example,dc=org      |
| User Groups Retrieve Strategy | GET\_GROUPS\_FROM\_USER\_MEMBEROF\_ATTRIBUTE |

На этом настройка федерации закончена, перейдем к настройке клиента.

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

Создадим нового клиента (приложение которое будет получать пользователей из Keycloak). Переходим:

**Clients** --> **Create**

| Option              | Value                                |
| ------------------- | ------------------------------------ |
| Client ID           | kubernetes                           |
| Access Type         | confidential                         |
| Root URL            | <http://kubernetes.example.org/>     |
| Valid Redirect URIs | <http://kubernetes.example.org/\\>\* |
| Admin URL           | <http://kubernetes.example.org/>     |

Так же создадим scope для групп:

**Client Scopes** --> **Create**

| Option          | Value       |
| --------------- | ----------- |
| Template        | No template |
| Name            | groups      |
| Full group path | false       |

И настроим mapper для них:

**Client Scopes** --> **groups** --> **Mappers** --> **Create**

| Option           | Value            |
| ---------------- | ---------------- |
| Name             | groups           |
| Mapper Type      | Group membership |
| Token Claim Name | groups           |

Теперь нам нужно включить маппинг груп в нашем client scope:

**Clients** --> **kubernetes** --> **Client Scopes** --> **Default Client Scopes**

Выбираем **groups** в **Available Client Scopes**, нажимаем **Add selected**

Теперь настроим аутентификацию нашего приложения, переходим:

**Clients** --> **kubernetes**

| Option                | Value |
| --------------------- | ----- |
| Authorization Enabled | ON    |

Нажимем **save** и на этом настройка клиента завершена, теперь на вкладке

**Clients** --> **kubernetes** --> **Credentials**

вы сможете получить **Secret** который мы будем использовать в дальнейшем.

## Настройка Kubernetes

Настройка Kubernetes для OIDC-авторизации достаточно тривиальна и не является чем-то очень сложным. Все что вам нужно это положить CA-сертификат вашего OIDC-сервера в `/etc/kubernetes/pki/oidc-ca.pem` и добавить необходимые опции для kube-apiserver.

Для этого обновите `/etc/kubernetes/manifests/kube-apiserver.yaml` на всех ваших мастерах:

```
...
spec:
  containers:
  - command:
    - kube-apiserver
...
    - --oidc-ca-file=/etc/kubernetes/pki/oidc-ca.pem
    - --oidc-client-id=kubernetes
    - --oidc-groups-claim=groups
    - --oidc-issuer-url=https://keycloak.example.org/auth/realms/kubernetes
    - --oidc-username-claim=email
...
```

А так-же обновите kubeadm конфиг в кластере, что бы не потерять эти настройки при обновлении:

```
kubectl edit -n kube-system configmaps kubeadm-config
```

```
...
data:
  ClusterConfiguration: |
    apiServer:
      extraArgs:
        oidc-ca-file: /etc/kubernetes/pki/oidc-ca.pem
        oidc-client-id: kubernetes
        oidc-groups-claim: groups
        oidc-issuer-url: https://keycloak.example.org/auth/realms/kubernetes
        oidc-username-claim: email
...
```

На этом настройка Kubernetes завершена. Вы можете повторить данные действия во всех ваших Kubernetes-кластерах.

## Начальная авторизация

После данных действий вы уже будете иметь Kubernetes-кластер с настроенной OIDC-авторизацией. Единственный момент, что ваши пользователи пока что не имеют настроенного клиента как и собственного kubeconfig. Что бы эту проблему решить нужно настроить автоматическую выдачу kubeconfig пользователям после успешной авторизации.

Для этого можно использовать специальные web-приложения, которые позволяют провести аутентификацию пользователя а затем скачать готовый kubeconfig. Одно из самых удобных — это [Kuberos](https://github.com/negz/kuberos), он позволяет в одном конфиге описать все Kubernetes-кластеры и легко переключаться между ними.

Для настройки Kuberos достаточно описать template для kubeconfig и запустить со следующими параметрами:

```
kuberos https://keycloak.example.org/auth/realms/kubernetes kubernetes /cfg/secret /cfg/template
```

Для более детальной информации смотрите [Usage](https://github.com/negz/kuberos#usage) на Github.

Так же возможно использовать [kubelogin](https://github.com/int128/kubelogin) если вы хотите производить авторизацию непосредственно на компьютере пользователя. В этом случае пользователю откроется браузер с формой авторизации на localhost.

Полученный kubeconfig можно проверить на сайте [jwt.io](https://jwt.io/#debugger-io). Просто скопируейте значение `users[].user.auth-provider.config.id-token` из вашего kubeconfig в форму на сайте и сразу получите расшифровку.

## Настройка RBAC

При настройке RBAC можно ссылаться как на имя пользователя (поле `name` в jwt-токене), так и на группу пользователей (поле `groups` в jwt-токене). Вот пример настройки прав для группы `kubernetes-default-namespace-admins`:

**kubernetes-default-namespace-admins.yaml**

```jsx
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: default-admins
  namespace: default
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-default-namespace-admins
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: default-admins
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: kubernetes-default-namespace-admins
```

Больше примеров для RBAC можно найти в [официальной документации Kubernetes](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)

## Настройка auth-proxy

Есть замечательный проект [keycloak-gatekeeper](https://github.com/keycloak/keycloak-gatekeeper), который позволяет защитить любое приложение, предоставляя пользователю возможность аутентифицироваться на OIDC-сервере. Я покажу как можно настроить его на примере Kubernetes Dashboard:

**dashboard-proxy.yaml**

```jsx
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kubernetes-dashboard-proxy
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: kubernetes-dashboard-proxy
    spec:
      containers:
      - args:
        - --listen=0.0.0.0:80
        - --discovery-url=https://keycloak.example.org/auth/realms/kubernetes
        - --client-id=kubernetes
        - --client-secret=<your-client-secret-here>
        - --redirection-url=https://kubernetes-dashboard.example.org
        - --enable-refresh-tokens=true
        - --encryption-key=ooTh6Chei1eefooyovai5ohwienuquoh
        - --upstream-url=https://kubernetes-dashboard.kube-system
        - --resources=uri=/*
        image: keycloak/keycloak-gatekeeper
        name: kubernetes-dashboard-proxy
        ports:
        - containerPort: 80
          livenessProbe:
            httpGet:
              path: /oauth/health
              port: 80
            initialDelaySeconds: 3
            timeoutSeconds: 2
          readinessProbe:
            httpGet:
              path: /oauth/health
              port: 80
            initialDelaySeconds: 3
            timeoutSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard-proxy
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: kubernetes-dashboard-proxy
  type: ClusterIP
```


# GUI management Lens

<https://habr.com/ru/companies/first/articles/677420/>

<https://github.com/MuhammedKalkan/OpenLens>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-18e9b23b9f6fe443087878c980b9c5a325ff1088%2F749a2bcd7869138bcfaac95a55d573f5.jpg?alt=media)

Управление кластером Kubernetes чаще всего осуществляется при помощи командной строки и утилиты kubectl. Однако, кроме этого распространенного способа, есть и другие. Например, с помощью программы Lens.

Lens — программное обеспечение, которое позволяет полноценно управлять кластером Kubernetes через графический интерфейс пользователя — GUI (graphical user interface).

В качестве плюсов Lens можно выделить следующие особенности:

1. Бесплатный продукт с открытым исходным кодом.
2. Поддержка всех основных типов кластеров на bare metal, on-premise, cloud computing (облачные решения), public clouds.
3. Доступен весь набор функций для управления кластером и всеми его объектами — управление подами, namespace, deployment, service и т. д.

Разработчиком программы является американская компания Mirantis, специализирующаяся на на разработке программного обеспечения для облачных вычислений с открытым исходным кодом. Исходный код программы выложен на [GitHub](https://github.com/lensapp/lens).

Те, кому требуется работать в команде, могут воспользоваться платными тарифами. Главная особенность платного тарифа — наличие функции Team Management, которая представляет собой пространство для команд с возможностью организации совместной работы и централизованного доступа к кластеру. При этом набор функций одинаков как для платной версии, так и для бесплатной.

[Установить Lens](https://k8slens.dev/) можно на любую операционную систему – Windows, macOS, Linux. Пользоваться программой могут только зарегистрированные пользователи. При первом запуске необходимо создать учетную запись на официальном сайте Lens или авторизоваться при помощи GitHub/Google.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dcb8a373819ffdd003a840b79fda6b6319b1f9d4%2Fb5fc87c49beac0e3fb54b437acb2be72.png?alt=media)

Подключение к кластеру Kubernetes осуществляется при помощи конфигурационного файла кластера, который по умолчанию находится в директории **/home/<имя\_пользователя>/.kube** и называется **config**.

Чтобы добавить файл с конфигом, необходимо нажать на синюю кнопку со знаком «плюс» которая находиться справа снизу. Далее следует выбрать директорию, где хранится файл, и нажать на кнопку **sync**. После этого кластер отобразится в главном меню в самом конце списка.

При подключении к кластеру Kubernetes, Lens использует собственный прокси-сервер - **Lens K8S Proxy**.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-f6f57942bdcde53fa59fa967433500fc76a9078c%2Fdf8ac44b0637defa90ea5408d76bd8b3.png?alt=media)

После перехода на страницу кластера отображается главная страница с метриками **Prometheus**. Если метрики Prometheus у вас не используются или не настроены, то будет отображаться надпись **Metrics are not available**.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-30f04c32eeb9cd3b6cd0b3e721976674fe996313%2Fbfc470050a50018c258b947b9a453de0.png?alt=media)

Слева находится меню в котором перечислены все компоненты кластера – Nodes, Workloads, Config, Networks, Storage, Namespaces:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ab2fed8c7fb2cdeda4840b9621dd25a6bc62bbdd%2Fa8529f8df05905eb2d2341c7f850da13.png?alt=media)

Также предусмотрены отдельные разделы для просмотров событий (Events), для работы с Helm чартами (Helm) и работы с политиками доступа (Access Control).

Для просмотра информации о нодах кластера необходимо перейти в раздел **Nodes**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dc9e07474175d32aca775f929b5d536f26994103%2Faae649ce078dfc749467cb6ea2272637.png?alt=media)

Для отображения секретов (Secrets) необходимо перейти в раздел **Config** и выбрать параметр **Secrets**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-04b10daeb3a218767bb024fb0af63d901693142e%2F7d6469c6dd4ae4c3dddbd7d80f3f2448.png?alt=media)

Чтобы просмотреть все доступные типы сетей (Service), необходимо перейти в раздел **Network** и выбрать параметр **Services**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e61950ebe9455cab01abea46d98e9d279aab3aa0%2F1b967b5f640c17dc1c8100543e495e51.png?alt=media)

Для отображения всех доступных namespace в кластере необходимо перейти в раздел **Namespaces**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2802b2ba7206ce60247dfe94e711a2515fd5b982%2F0d571456969df4355c2d8bb22b1e449f.png?alt=media)

Все перечисленные выше объекты можно редактировать путем нажатия на них. В появившемся окне справа сверху будут доступны кнопки для редактирования и удаления объекта:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-cbd7023d4fad900a97f4d0acc2d7a4af5a702093%2F4aba2387278c513ec46bd1a14ff6391c.png?alt=media)

Чтобы найти информацию о подах а также отобразить все доступные Deployments, Daemon Sets, Stateful Sets, Replica Sets, Jobs и Cron Jobs необходимо раскрыть раздел **Workloads** и перейти в пункт **Overview**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-3a15c81d8ee2c822c7444d712a5a8694145e869d%2Ff48ae99bf070fe6633b9d5b87d91b161.png?alt=media)

Отобразим список всех подов в системе, перейдя в пункт **Pods**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ff2791cd7022fca25dbec5025187391b220af905%2F24fd5dc5cf78cc8a5d56bd4d853cb75b.png?alt=media)

Чтобы выбрать необходимый namespace, необходимо найти его в выпадающем списке, который находится справа сверху. По умолчанию отображаются все поды, которые находятся в namespace с именем default.

Чтобы отобразить информацию о поде (эквивалент команды kubectl describe pod), достаточно щелкнуть по имени пода. Откроется дополнительное окно, в котором будет отображена вся информация о поде, включая его полное имя, namespace, labels, статус пода и т. д.:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-916c1e9f680a5123b5c4a02852cada950e5ba1c0%2Fae1a214c8ffaefc92477e78a6ba47b89.png?alt=media)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-da92f1eb45f6eb5ac3309faed22376a3ff703e17%2F7c8112445ab25684b0159bb7637f5fb8.png?alt=media)

Для того что попасть внутрь контейнера, необходимо выбрать опцию **Pod Shell**. После этого внизу отобразится интерфейс командной строки, а также запустится сама оболочка внутри контейнера (команда kubectl exec -i -t):

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-779f1c6413c56158556da58773dcb5b2e96985ea%2F16fd6123aae47154438e9c99c382831d.png?alt=media)

Для просмотра логов пода выберите опцию **Pod Logs** (эквивалент команды kubectl logs). Логи также будут отображены в терминале, который появится снизу:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2e300180bcd3dc77b96abe37a66fb8f6a29b2ef7%2Fa3ab9a991893b81c1a339de3aa6f67ee.png?alt=media)

А чтобы отредактировать конфигурационный файл пода, воспользуйтесь опцией Edit (эквивалент команды kubectl edit), которая отобразит терминал — в нем можно вносить необходимые правки:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d2c00dcce14af2c63b0ac28ca2f0e9067e674064%2F64b450fedaf8d37b5ca7e8ee31eec487.png?alt=media)

Для сохранения изменений необходимо нажать на кнопку **Save & Close**. Изменения не будут применены до перезагрузки пода. Чтобы выполнить перезагрузку, перейдите в меню **Workloads**, далее выберите раздел **Deployments**. Найдите нужный deployment и кликните по нему. В появившемся окне, справа сверху нажмите на кнопку **Restart**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-1c1fbef8d67279263b3ec224e071a99d120f8ba1%2F64a352276ad99dbaa5d4dbb9cde26515.png?alt=media)

Для просмотра всех доступных событий в кластере следует перейти в раздел **Events**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8f0f9cbc6b4f953376eceed08e99e0a3a4c9f1fa%2F728ff6a18762d152ccdd354ea91c5ed9.png?alt=media)

Для просмотра более подробной информации о событии необходимо просто по нему щелкнуть:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-60e096a483d67328b2ec79be1899810a0fe83524%2F0f5d7ad041917014c82bfc36f70599fd.png?alt=media)

Также в Lens предусмотрена работа с чартами Helm. Для этого существует отдельная вкладка с именем **Helm**:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-edcba5731c7cdedfcd501a5d06cc457ecb946d60%2F5f468dabab5339529e883c2ff6e69628.png?alt=media)

В списке представлены только самые популярные чарты. Для установки необходимо кликнуть по нужному чарту и в открывшемся меню нажать на кнопку **Install**. Также в этом окне будет представлена вся основная информация о чарте:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-06c852b1cfb680db7f8dd7facf61800f78cbb030%2F2490da4fcc4b0b5c451bb6e06763fe55.png?alt=media)

Подводя итоги, можно сказать, что программа Lens идеально подходит для управления кластером Kubernetes. Весь процесс управления происходит из графического интерфейса. При этом доступны все функции управления – от просмотра объектов до перезапуска подов, создания объектов и редактирования конфигурационных файлов.


# Monitoring

[Monitoring with Falco](/readme/architect/kubernetes/monitoring/monitoring-with-falco)

[Network monitoring](/readme/architect/kubernetes/monitoring/network-monitoring)

[Nginx ingress](/readme/architect/kubernetes/monitoring/nginx-ingress)

[Prometheus Graphana for sample Nodejs app](/readme/architect/kubernetes/monitoring/prometheus-graphana-for-sample-nodejs-app)

[Rsource monitoring Avito](/readme/architect/kubernetes/monitoring/rsource-monitoring-avito)


# Monitoring with Falco

<https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/kubernetes-hardening/monitoring-with-falco>

> `Falco`, the cloud-native runtime security project, is the de facto Kubernetes threat detection engine. Falco was created by Sysdig in 2016 and is the first runtime security project to join CNCF as an incubation-level project. Falco detects unexpected application behavior and alerts on threats at runtime.

Falco uses system calls to secure and monitor a system, by:

* Parsing the Linux system calls from the kernel at runtime
* Asserting the stream against a powerful rules engine
* Alerting when a rule is violated

Falco ships with a default set of rules that check the kernel for unusual behavior such as:

* Privilege escalation using privileged containers
* Namespace changes using tools like setns
* Read/Writes to well-known directories such as /etc, /usr/bin, /usr/sbin, etc
* Creating symlinks
* Ownership and Mode changes
* Unexpected network connections or socket mutations
* Spawned processes using execve
* Executing shell binaries such as sh, bash, csh, zsh, etc
* Executing SSH binaries such as ssh, scp, sftp, etc
* Mutating Linux coreutils executables
* Mutating login binaries
* Mutating shadowutil or passwd executables such as shadowconfig, pwck, chpasswd, getpasswd, change, useradd, etc, and others.

Get more details about the falco deployment

```jsx
kubectl get pods --selector app=falco
```

Manually obtaining the logs from the falco systems

```jsx
kubectl logs -f -l app=falco
```


# Network monitoring

<https://habr.com/ru/companies/ruvds/articles/712536/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0532d5e18f2e9606859fb987d8b3bf8858be07cf%2Fhaslu1ba8fzcakeupsmpcia1aek.jpeg?alt=media)

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

Хотя [Kubernetes](https://dzone.com/articles/how-kubernetes-changed-container-management) при выборе системы управления контейнерами является стандартом «де факто», многим организациям не удаётся её реализовать. Растущие организации в процессе увеличения масштабов своих сервисов ненамеренно вносят в систему усложнения. Критически важно понимать, как настраивать инфраструктуру и как кластеры могут работать и взаимодействовать между собой.

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

## Введение в визуальное картографирование сетей

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

Для картографирования сетей и генерации визуальных карт сетей можно использовать опенсорсные инструменты безопасности наподобие OpenVAS, Nmap и Nessus. Эти инструменты бесплатны, что делает их экономичным решением для организаций, стремящихся повысить безопасность сетей. Кроме того, многие опенсорсные инструменты безопасности имеют активную поддержку сообщества, позволяя пользователям делиться знаниями, советами и рекомендациями по реализации полного потенциала инструмента.

## Преимущества использования визуальных карт сетей

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

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

## Знакомство с Caretta и Grafana

[Caretta](https://github.com/groundcover-com/caretta) — это опенсорсный инструмент визуализации и мониторинга сетей, позволяющий просматривать и мониторить сети в реальном времени.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-53365b69d09cb9938508c997e74d3cbca4f19b04%2Fwhi8leuubshjhxbcsfzjf9-6mto.png?alt=media)

[Grafana](https://grafana.com/) — это опенсорсная платформа визуализации и мониторинга данных, позволяющая создавать собственные дэшборды и алерты, а также исследовать и анализировать данные.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0648ad5444bf51f75a3a76736cf9f3e5f94b0939%2Fubly7l6yh5w5vmwvqtgduuvngt8.png?alt=media)

На основе Caretta и Grafana можно создать эффективное решение для понимания сети и управления ею.

### ▍ Как Caretta использует eBPF и Grafana

Предназначение Caretta заключается в том, чтобы помочь в понимании топологии и взаимосвязи между устройствами в распределённых средах. Она предлагает различные возможности, например, выявление устройств, мониторинг в реальном времени, алерты, уведомления и отчётность. Для сбора и публикации своих метрик она использует Victoria Metrics, а результаты её работы можно использовать в любом совместимом с Prometheus дэшборде.

Благодаря обеспечению допусков (toleration) Carreta позволяет принимать типичные аннотации узлов уровня управления. Она собирает информацию сети, например, сведения об устройствах и соединениях, при помощи функциональности ядра [eBPF](https://ebpf.io/) (extended Berkeley Packet Filter), а затем использует платформу Grafana для отображения информации на визуальной карте.

### ▍ Роль Grafana в визуализации карт сетей Caretta

[Grafana](https://dzone.com/articles/-grafana-the-open-observability-platform) спроектирована так, чтобы быть модульным и гибким инструментом, с лёгкостью интегрирующим и принимающим широкий диапазон источников данных и приложений.

Благодаря возможности настройки функций вы можете изменять способ отображения карты сети при помощи дэшборда Grafana. Кроме того, у вас есть широкий выбор из множества опций визуализации для отображения данных в понятном и полезном виде. Grafana критически важна и для демонстрации данных сетей, собранных Caretta, и для предоставления пользователям полной картины сети.

## Использование Caretta и Grafana для создания визуальной карты сети

Чтобы использовать Caretta и Grafana в создании визуальной карты сети, их необходимо установить, внедрить и конфигурировать. Основной объект конфигурирования — это Caretta daemonset.

Для просмотра карты сети необходимо развернуть Caretta daemonset в выбранном кластере, который будет собирать метрики сети в базу данных, а также настроить источник данных Grafana так, чтобы указывал на базу данных Caretta.

### ▍ Обязательные требования для использования Caretta и Grafana

Caretta — современный инструмент, интегрированный с расширенными функциями. Он использует версию ядра [Linux](https://dzone.com/articles/a-little-linux-goes-a-long-way) от 4.16 и выше и [helm chart](https://helm.sh/docs/chart_template_guide/values_files/) для 64-битной системы.

Давайте разберёмся, как установить и сконфигурировать это сочетание инструментов.

### ▍ Установка и конфигурирование Caretta и Grafana

При наличии заранее сконфигурированный helm chart для установки Caretta достаточно всего нескольких вызовов.

Рекомендуется устанавливать Caretta в новое уникальное пространство имён.

```
helm repo add groundcover https://helm.groundcover.com/
helm repo update
helm install caretta --namespace caretta --create-namespace groundcover/caretta
```

То же самое относится и к установке Grafana.

```
helm install --name my-grafana --set "adminPassword=secret" \n
--namespace monitoring -f custom-values.yaml stable/grafana
```

Наш файл `custom-values.yaml` будет выглядеть примерно так:

```
## Grafana configuration
grafana.ini:
 ## server
 server:
   protocol: http
   http_addr: 0.0.0.0
   http_port: 3000
   domain: grafana.local
 ## security
 security:
   admin_user: admin
   admin_password: password
   login_remember_days: 1
   cookie_username: grafana_admin
   cookie_remember_name: grafana_admin
   secret_key: hidden
 ## database
 database:
   type: rds
   host: mydb.us-west-2.rds.amazonaws.com
 ## session
 session:
   provider: memory
   provider_config: ""
   cookie_name: grafana_session
   cookie_secure: true
   session_life_time: 600

## Grafana data
persistence:
 enabled: true
 storageClass: "-"
 accessModes:
 - ReadWriteOnce
 size: 1Gi
```

### ▍ Конфигурирование

Сконфигурировать Caretta можно при помощи значений helm. Значения в Helm — это позиции настройки chart. После установки chart можно изменить значения, перечисленные в файле `values.yaml`, являющемся частью пакета chart, и настроить конфигурации на основании своих требований.

Ниже представлен пример конфигурации с переписыванием стандартных значений:

```
pollIntervalSeconds: 15  # set metrics polling interval
tolerations:             # set any desired tolerations
 - key: node-role.kubernetes.io/control-plane
   operator: Exists
   effect: NoSchedule

config:
 customSetting1: custom-value1
 customSetting2: custom-value2

victoria-metrics-single:
 server:
   persistentVolume:
     enabled: true   # set to true to use persistent volume

ebpf:
 enabled: true  # set to true to enable eBPF
 config:
   someOption: ebpf_options
```

`pollIntervalSeconds` задаёт интервал опроса метрик. В нашем случае метрики будут запрашиваться каждые 15 секунд.

В разделе tolerations можно указать допуски для подов. В показанном выше примере поды могут работать только в узлах, имеющих метку `node-role.kubernetes.io/control-plane` и с эффектом `NoSchedule`.

Раздел config позволяет указать собственные опции конфигурации для приложения.

Раздел victoria-metrics-single позволяет сконфигурировать сервер Victoria-metrics-single. В примере мы включаем использование постоянного хранилища (persistent volume).

Раздел `eBPF` позволяет включить eBPF и настроить его опции.

### ▍ Создаём визуальную карту сети с помощью Caretta и Grafana

Caretta состоит из двух частей: Caretta Agent и Caretta Server. Каждый узел в кластере выполняет Caretta Agent Kubernetes DaemonSet, собирающий информацию о состоянии кластера.

Для просмотра данных в виде карты сети и генерации карты сети необходимо добавить в Grafana данные, собранные Caretta.

```
apiVersion: apps/v1
kind: DaemonSet
metadata:
 name: caretta-depoy-test
 namespace: caretta-depoy-test
spec:
 selector:
   matchLabels:
     app: caretta-depoy-test
 template:
   metadata:
     labels:
       app: caretta-depoy-test
   spec:
     containers:
     - name: caretta-depoy-test
       image: groundcover/caretta:latest
       command: ["/caretta"]
       args: ["-c", "/caretta/caretta.yaml"]
       volumeMounts:
       - name: config-volume
         mountPath: /caretta
     volumes:
     - name: config-volume
       configMap:
         name: caretta-config
```

Данные из Caretta Agent получаются Caretta Server (который является [Kubernetes StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)), после чего он сохраняет их в базу данных.

```
apiVersion: apps/v1
kind: StatefulSet
metadata:
 name: caretta-depoy-test
 labels:
   app: caretta-depoy-test
spec:
 serviceName: caretta-depoy-test
 replicas: 1
 selector:
   matchLabels:
     app: caretta-depoy-test
 template:
   metadata:
     labels:
       app: caretta-depoy-test
   spec:
     containers:
       - name: caretta-depoy-test
         image: groundcover/caretta:latest
         env:
           - name: DATABASE_URL
             value: mydb.us-west-2.rds.amazonaws.com
         ports:
           - containerPort: 80
             name: http
```

Чтобы сделать это, нам нужно создать собственный плагин источника данных в Grafana для подключения к данным Caretta, а затем разработать визуализации в Grafana для отображения этих данных.

```
[datasources]
[datasources.caretta]
 name = caretta-deploy-test
 type = rds
 url = mydb.us-west-2.rds.amazonaws.com
 access = proxy
 isDefault = true
```

### ▍ Опции настройки для карты сети и как получить к ним доступ

Карту сети, созданную Caretta и Grafana, можно настраивать множеством разных способов. Мы можем изменять следующие параметры:

* **Опции отображения**: при помощи опций настройки отображения вы можете управлять структурой карты, толщиной и цветом соединений и устройств.
* **Опции данных**: при помощи опций данных можно выбирать, какая информация будет отображаться на карте, в том числе уведомления, метрики производительности, сведения об устройстве и подключении.
* **Опции алертов**: при помощи опций алертов можно получать уведомления о любых изменениях и проблемах в сети, например, о большом трафике, низкой производительности или трудностях со связью.
* **Опции визуализации**: при помощи опций визуализации можно представлять собранные данные понятным и полезным образом.

Обычно для доступа к этим и другим опциям настройки требуется дэшборд Grafana. Доступ к опциям и параметрам зависит от версии Caretta и Grafana, а также от настроек и потребностей вашей системы.

## Интерпретация и использование визуальной карты сети

Основные задачи визуальной карты сети, созданной Caretta и Grafana — помощь в понимании сетевой топологии, выявлении возможных узких мест или проблем и планировании и устранении сетевых проблем. Чтобы интерпретировать и использовать визуальную карту сети, нужно разобраться в компонентах карты и их значении.

Вот некоторые из типов информации, которые могут отображаться на карте:

* **Устройства**: присутствующие на карте конечные точки сети, в том числе серверы, коммутаторы и роутеры.
* **Соединения**: соединения между устройствами, например, сетевые кабели, беспроводные или виртуальные соединения; иногда тип соединения отображается на карте.
* **Данные**: на карте отображаются показатели производительности, уведомления и информация о конфигурации.

### ▍ Советы по использованию карты сети для оценки производительности кластера K8s

Создание курируемой, информативной и масштабируемой карты сети — довольно сложная задача. Однако при наличии подходящего набора инструментов её вполне можно решить.

Мы уже увидели, чего можно добиться при использовании Caretta и Grafana. Теперь давайте посмотрим, что нужно учитывать при использовании карт сетей, отображающих метрики производительности кластеров Kubernetes.

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

## Заключение

Разбиение крупных систем на микросервисы, превращение систем в распределённые и управление ими — самый популярный подход для повышения производительности и аптайма. В этом лидерами рынка стали Kubernetes и Docker.

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

Grafana — лидирующий инструмент на рынке мониторинга. Соединив опенсорсный инструмент сетевой визуализации и мониторинга Caretta с Grafana, можно полностью раскрыть возможности своей инфраструктуры.


# Nginx ingress

<https://habr.com/ru/companies/vk/articles/729796/>

![](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Monitoring/Nginx%20ingress/Untitled)

Команда [VK Cloud](https://mcs.mail.ru/?utm_source=habr\&utm_medium=media\&utm_campaign=nginx-ingress-kubernetes) перевела пошаговую инструкцию о том, как установить и сконфигурировать ingress-nginx, Prometheus и Grafana, а также настроить оповещения для ключевых метрик Ingress. Для работы понадобится кластер Kubernetes и Helm v3.

## Устанавливаем Prometheus и Grafana

Первым делом установим Prometheus для сбора метрик и Grafana для визуализации и создания оповещений на их основе.

Установим Helm chart [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack), скопировав следующие команды в свой терминал. Так мы установим Grafana, Prometheus и другие компоненты для мониторинга.

```
# Add and update the prometheus-community helm repository.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
cat <<EOF | helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--create-namespace -n monitoring -f -

grafana:
  enabled: true

adminPassword: "admin"
  persistence:
    enabled: true
    accessModes: ["ReadWriteOnce"]
    size: 1Gi
  ingress:
    enabled: true
    ingressClassName: nginx
    hosts:
      - grafana.localdev.me
EOF

```

Давайте убедимся, что установленные компоненты работают:

```
kubectl get pods -n monitoring

NAME                                                        READY   STATUS    RESTARTS        AGE
kube-prometheus-stack-grafana-7bb55544c9-qwkrg              3/3     Running   0               3m38s
prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   0               3m14s
...

```

Переходим к следующему этапу.

## Установка и настройка Ingress Nginx

На этом этапе устанавливаем и настраиваем контроллер Nginx ingress и включаем метрику, которую собирает Prometheus.

1. С помощью следующей команды устанавливаем **ingress Nginx** в кластер:

```
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.metrics.enabled=true \
  --set controller.metrics.serviceMonitor.enabled=true \
  --set controller.metrics.serviceMonitor.additionalLabels.release="kube-prometheus-stack"

```

Чтобы Prometheus мог обнаружить монитор служб и автоматически подтягивать из него метрики, в качестве `release: kube-prometheus-stack` указываем `serviceMonitor.additionalLabels`.

2. Установив чарт, давайте для примера выполним деплоймент приложения [podinfo](https://github.com/stefanprodan/podinfo) в пространстве имен по умолчанию.

```
helm install --wait podinfo --namespace default \
oci://ghcr.io/stefanprodan/charts/podinfo

```

3. Теперь создаем ingress для выполненного деплоймента **podinfo**:

```
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: podinfo-ingress
spec:
  ingressClassName: nginx
  rules :
  - host: podinfo.localdev.me

 defaultBackend:
    service:
      name: podinfo
      port:
        number: 9898
EOF

```

Давайте немного углубимся в эту конфигурацию ingress:

* В качестве ingress-контроллера мы используем ingress-nginx, поэтому класс `ingress` определяется как `nginx`.
* В этой конфигурации я использовал `podinfo.localdev.me` как адрес хоста для Ingress.
* DNS \*.localdev.me трансформируется в 127.0.0.1, так что этот DNS можно использовать для любого локального тестирования, не добавляя запись в файл /etc/hosts.
* Приложение Podinfo обслуживает HTTP API через порт 9898, и поэтому мы указываем его для backend-порта. То есть трафик, поступающий в домен <http://podinfo.localdev.me>, направляется на порт 9898 службы podinfo.

4. Далее с терминала трафик необходимо перенаправить на порт службы ingress-nginx, чтобы можно было направлять трафик с локального терминала.

```
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 8080:80  > /dev/null &
```

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

> Если вы запускаете службу в облаке, в перенаправлении портов нет необходимости, так как LoadBalancer службы ingress-nginx создается автоматически — служба определяется как LoadBalancer по умолчанию.

5. Теперь выполняем следующий запрос curl к конечной точке podinfo и получаем ответ:

```
> curl http://podinfo.localdev.me:8080

"hostname": "podinfo-59cd496d88-8dcsx"
"message": "greetings from podinfo v6.2.2"

```

6. URL в браузере будет выглядеть симпатичнее: <http://podinfo.localdev.me:8080/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8c559785ead7e22bf72d3400c5af76783b7b323a%2Fbkebvxdhr3il44_e1zj3jczrgi0.png?alt=media)

## Настройка дашбордов Grafana для мониторинга Ingress Nginx

Чтобы запустить Grafana, нужно открыть в браузере URL с учетными данными admin:admin : <http://grafana.localdev.me:8080/>.

Чтобы импортировать дашборд, скопируйте [отсюда](https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/grafana/dashboards/nginx.json) thenginx.json и вставьте в <http://grafana.localdev.me:8080/dashboard/import>. Вот так должен выглядеть импортированный дашборд:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-ae7749fbb871c226a3b7aeefb18d3c2e47094088%2Fvyyz9loqd_eeloexmb5gb7z2vfq.png?alt=media)

## Генерируем нагрузки для примера

Чтобы направить трафик в приложение podinfo, воспользуемся инструментом нагрузочного тестирования vegeta. Его можно взять [отсюда](https://github.com/tsenart/vegeta). Для примера давайте создадим трафик HTTP 4xx. Для этого выполните следующую команду, которая запускается с частотой запросов 10 RPS на 10 минут:

```
echo "GET http://podinfo.localdev.me:8080/status/400" | vegeta attack -duration=10m -rate=10/s

```

Можно изменить код состояния с 400 на 500 и выполнять команду и для тестового трафика 5xx.

Для проверки задержки я использовал команду `GET /delay/{seconds} waits` за указанный период:

```
echo "GET http://podinfo.localdev.me:8080/delay/3" | vegeta attack -duration=10m -rate=100/s

```

Примечание: [здесь](https://github.com/stefanprodan/podinfo) можно дополнительно почитать о конечных точках в приложении podinfo.

## Оповещения о метриках SLI в Grafana

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

### Частота ошибок 4xx

1. Чтобы создать оповещение, давайте перейдем в <http://grafana.localdev.me:8080/alerting/new>.
2. Для получения частоты ошибок 4xx в процентах можно использовать следующую формулу: (общее количество запросов 4xx / общее количество запросов) \* 100.
3. Добавьте в запрос следующее выражение:

```
(sum(rate(nginx_ingress_controller_requests{status=~'4..'}[1m])) by (ingress) / sum(rate(nginx_ingress_controller_requests[1m])) by (ingress)) * 100 > 5

```

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-43d8b223aa373a69fdcfcb51e15ff0ddac5adbfe%2F7qpkqilxd2ksqngzwbqjtqc4ajq.png?alt=media)

4. В выражении B используйте операцию редукции с функцией Mean для вводных A.
5. В Alert Details назовите оповещение так, как вам нравится. Я свое назвал Ingress\_Nginx\_4xx.
6. Summary можно сделать максимально коротким: просто показать имя Ingress с меткой {{ $labels.ingress }}.

```
Ingress High Error Rate : 4xx on *{{ $labels.ingress }}*

```

7. В Description я использовал `printf "%0.2f"`, чтобы проценты отображались с точностью до двух знаков после запятой.

```
4xx : High Error rate : `{{ printf "%0.2f" $values.B.Value }}%` on *{{ $labels.ingress }}*.

```

8. В целом оповещение должно быть похоже на снапшот ниже:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8b00ea28a19edc4f213aef57b5b318458e364d47%2F4n5wda55lzglel8vwjbyp4xbqsg.png?alt=media)

9. В конце можно добавить пользовательскую метку, например `severity : critical.`

### Частота ошибок 5xx

Как и в случае с настройкой оповещений 4xx, для частоты ошибок 5xx можно использовать следующий запрос:

```
sum(rate(nginx_ingress_controller_requests{status=~'5..'}[1m])) by (ingress,cluster) / sum(rate(nginx_ingress_controller_requests[1m]))by (ingress) * 100 > 5

```

> В соответствии с настройками оповещение отправляется, когда процент 5xx/4xx превышает 5 %. Настраиваем таким образом, чтобы это соответствовало нашим требованиям, а именно Error budget — времени, в течение которого система может испытывать проблемы без нарушений SLA.

### Большая задержка (p95)

Чтобы рассчитать 95-й процентиль продолжительности запросов за последние 15 минут, можно использовать метрику `nginx_ingress_controller_request_duration_seconds_bucket`. Так вы получите **The request processing time in milliseconds**. Поскольку это бакет, мы можем использовать функцию `histogram_quantile`. Создайте оповещение, похожее на пример выше, и используйте следующий запрос:

```
histogram_quantile(0.95,sum(rate(nginx_ingress_controller_request_duration_seconds_bucket[15m])) by (le,ingress)) > 1.5

```

Я установил пороговое значение на уровне 1,5 секунды, но его можно изменить в соответствии с вашим SLO.

### Высокая частота запросов

Чтобы получить частоту запросов в секунду (RPS), можно использовать следующий запрос:

```
sum(rate(nginx_ingress_controller_requests[5m])) by (ingress) > 2000

```

В таком случае оповещение отправляется, когда частота запросов превышает 2000 RPS.

### Другие SLI

**Скорость подключения.** Измеряет количество активных подключений к Nginx ingress и может использоваться для выявления потенциальных проблем с подключениями.

```
rate(nginx_ingress_controller_nginx_process_connections{ingress="ingress-name"}[5m])

```

**Upstream response time.** Время на ответ исходной службы на запрос; помогает выявлять проблемы не только с ingress, но и со службой.

```
histogram_quantile(0.95,sum(rate(nginx_ingress_controller_response_duration_seconds_bucket[15m])) by (le,ingress))

```

## Шаблон оповещений в Slack

Чтобы сообщения с оповещениями были удобочитаемыми, можно использовать [шаблоны оповещений в Grafana.](https://grafana.com/docs/grafana/latest/alerting/contact-points/message-templating/)

1. Чтобы их настроить, перейдем в <http://grafana.localdev.me:8080/alerting/notifications> и создадим новый шаблон. Назовем его slack и скопируем следующий блок кода:

```
{{ define "alert_severity_prefix_emoji" -}}
    {{- if ne .Status "firing" -}}
        :white_check_mark:
    {{- else if eq .CommonLabels.severity "critical" -}}
        :fire:
    {{- else if eq .CommonLabels.severity "warning" -}}
        :warning:
    {{- end -}}
{{- end -}}

{{ define "slack.title" -}}
    {{ template "alert_severity_prefix_emoji" . }}  {{- .Status | toUpper -}}{{- if eq .Status "firing" }} x {{ .Alerts.Firing | len -}}{{- end }}  |  {{ .CommonLabels.alertname -}}
{{- end -}}

{{- define "slack.text" -}}
{{- range .Alerts -}}
{{ if gt (len .Annotations) 0 }}
*Summary*: {{ .Annotations.summary}}
*Description*: {{ .Annotations.description }}
Labels:
{{ range .Labels.SortedPairs }}{{ if or (eq .Name "ingress") (eq .Name "cluster") }}• {{ .Name }}: `{{ .Value }}`
{{ end }}{{ end }}
{{ end }}
{{ end }}
{{ end }}

```

2. Настраиваем новую точку контакта типа Slack. Для этого нужно создать входящий вебхук из Slack. Все подробно расписано в [этом документе](https://api.slack.com/messaging/webhooks#create_a_webhook).
3. Редактируем точку контакта **slack**, прокручиваем вниз и выбираем параметр **Optional Slack settings**.
4. В **Title** ниже укажем, какой шаблон использовать:

```
{{ template "slack.title" . }}

```

5. **В Text Body** введем приведенный ниже код и сохраним его:

```
{{ template "slack.text" . }}

```

6. Перейдем в <http://grafana.localdev.me:8080/alerting/routes> и укажем **Slack** в параметре **Default contact point**.

**Вот, наконец, и сообщение с оповещением!**

Все шаги выполнены, мы получили результат: вот так выглядит оповещение в Slack.

Частота ошибок 4xx:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-c3a73d07a3cd4611e2293c2a118a7f1af6c6a166%2Faethucu7qekmzyemzaa42dbltu0.png?alt=media)

Частота ошибок 5xx:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-be4cf4c87cde0be8fd834e467f852e95ea4350f7%2Fodxj0c9ifil7mpn-peqaovw31au.png?alt=media)

Задержка p95:

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6c7b2293e00e3433e6cb48efe6cc1dde7dd86b6f%2Fy7jzllnogbqraxy8upanq_u9qdi.png?alt=media)

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

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-94b19d1ee5e8620ae6d32e4b8919dabfbc00fde1%2Fj_vgz2zjrcnl4c_efx0xryfbjck.png?alt=media)

Aviator автоматизирует тяжелые рабочие процессы для разработчиков, управляя запросами Pull (Pr) в Git; благодаря тестированию в ходе непрерывной интеграции (CI) это помогает избежать сломанных сборок, оптимизировать утомительные процессы объединения, управлять cross-PR-зависимостями и справляться с нестабильными тестами, соблюдая при этом требования безопасности.

Aviator состоит из четырех основных компонентов:

1. **MergeQueue** — автоматизированная очередь, которая управляет рабочим процессом Merging для репозитория GitHub, защищая важные ветви от неисправных сборок. Бот Aviator использует GitHub Labels для идентификации готовых к объединению запросов Pull (PR), подтверждает проверки CI, обрабатывает семантические конфликты и автоматически объединяет PR.
2. **ChangeSets** — рабочие процессы, призванные синхронизировать валидацию и объединение нескольких PR в одном репозитории или в нескольких. Может пригодиться, если у вашей команды часто появляются группы связанных между собой PR, которые нужно объединить или иным образом обработать как единый, более крупный блок изменений.
3. **FlakyBot** — инструмент, который умеет автоматически определять и обрабатывать результаты нестабильных тестов в инфраструктуре CI.
4. **Stacked PRs CLI** — инструмент командной строки для работы с cross-PR-зависимостями. Этот инструмент также автоматизирует синхронизацию и объединение PR в стеке. Помогает развивать культуру небольших инкрементальных PR вместо больших изменений и подходит для ситуаций, когда ваши рабочие процессы завязаны на синхронизацию нескольких зависимых PR.

> Вы можете опробовать мониторинг
>
> [Kubernetes в облаке VK Cloud](https://mcs.mail.ru/containers/?utm_source=habr\&utm_medium=media\&utm_campaign=nginx-ingress-kubernetes)
>
> [«Вокруг Kubernetes»](http://t.me/+cWY7eMrhzNVmMmQy)


# Prometheus Graphana for sample Nodejs app

<https://habr.com/ru/companies/slurm/articles/704502/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-e79995e4b5b654e029dfc7beb86e08efde4c61f9%2F59934cf182a3b1b1624d3c4e0ec5ddd9.png?alt=media)

Что такое Argo Rollouts? Это контроллер Kubernetes и набор CRD для дополнительных возможностей развёртывания — сине-зелёное, канареечное, прогрессивное, анализ канареечного развёртывания и экспериментирование.

В этой статье поговорим о продвинутых возможностях развёртывания с кастомными ресурсами Kubernetes.

## Argo Rollouts

Как мы увидим, Argo Rollouts предоставляет ресурс rollout в Kubernetes API, на который можно заменить встроенный ресурс deployment. Использование расширенных возможностей в виде ресурса Kubernetes API даёт несколько преимуществ:

* Знакомые методы работы — управляйте развёртываниями с помощью манифестов Kubernetes и *kubectl* CLI.
* Простота понимания — используйте знакомые возможности развёртывания.
* Аутентификация/авторизация — используйте имеющиеся в Kubernetes механизмы для аутентификации и авторизации.
* Привычная программируемость — используйте знакомый API для Argo Rollouts.
* Совместимость с любым решением для непрерывной поставки (CD) — Argo Rollouts развёртывается с помощью манифеста Kubernetes, поэтому может использоваться с любым решением CD.

Другие популярные решения с расширенными возможностями развёртывания не дают этих преимуществ:

* [Spinnaker](https://spinnaker.io/): опенсорс-решение для непрерывной поставки.
* SASS для непрерывной интеграции: [GitLab](https://docs.gitlab.com/ee/user/project/clusters/), [GitHub Actions](https://github.com/marketplace/actions/deploy-to-kubernetes-cluster),[CodeFresh](https://codefresh.io/kubernetes-tutorial/fully-automated-canary-deployments-kubernetes/) и т. д.

Все примеры кода и конфигураций, которые мы будем использовать в этой статье, можно [скачать здесь](https://github.com/larkintuckerllc/hello-argo-rollouts).

## Кластер

Примеры из этой статьи выполнялись в кластере Google Kubernetes Engine (GKE) версии *1.17.14-gke.1600*, но должны работать в любом другом кластере Kubernetes. Лучше если это будет Kubernetes версии 1.15.x и выше.

Итак, вам понадобится:

* Кластер Kubernetes версии 1.15.x и выше.
* kubectl CLI, совместимый с кластером.
* [Установка](https://argoproj.github.io/argo-rollouts/installation/#controller-installation) контроллера Argo Rollouts в кластер.
* [Установка](https://argoproj.github.io/argo-rollouts/installation/#kubectl-plugin-installation) плагина Argo Rollouts kubectl.

## Код рабочей нагрузки

[Рабочая нагрузка](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/server.js) в этом примере представляет собой Express [*Hello World*](https://expressjs.com/en/starter/hello-world.html) и [клиент Prometheus для Node.js](https://github.com/siimon/prom-client). У рабочей нагрузки есть две конечные точки: */* возвращает Hello World!, а */metrics* возвращает метрики в формате Prometheus. Помимо стандартных метрик Node.js конечная точка */metrics* предоставляет две метрики, которые мы будем использовать в примерах:

* *app\_requests\_total*: общее число запросов, исключая конечную точку */metrics*, обработанных рабочей нагрузкой.
* *app\_not\_found\_total*: общее число запросов, которые не соответствовали двум конечным точкам; возвращается ошибка *404*.

Рабочая нагрузка [встроена](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/Dockerfile) в образ контейнера и доступна в [репозитории](https://hub.docker.com/repository/docker/sckmkny/app-1) Docker Hub. В примере мы будем использовать три тега:

* *0.2.0 и 0.3.0*: образы, которые работают ожидаемо.
* *0.3.1*: повреждённый образ, у которого запросы к конечной точке */* входят в метрику *app\_not\_found\_total*.

## Манифесты Kubernetes для рабочей нагрузки

В этой статье мы будем развёртывать разные вариации рабочей нагрузки для иллюстрации разных концепций. Развёртывать рабочие нагрузки мы будем с помощью манифестов Kubernetes в папках проекта. Их имена будут начинаться на *k8s*.

Здесь мы её использовать не будем, но в проекте есть папка [*k8s*](https://github.com/larkintuckerllc/hello-argo-rollouts/tree/master/k8s) с финальной работающей рабочей нагрузкой. Она используется в конфигурации Travis CI для иллюстрации простейшего процесса непрерывной поставки (в этой статье мы не будем его рассматривать).

## Загрузка манифестов Kubernetes

В целях иллюстрации нам понадобится HTTP-трафик для рабочих нагрузок. В папке [*load*](https://github.com/larkintuckerllc/hello-argo-rollouts/tree/master/load) проекта есть нужные манифесты Kubernetes для создания равномерного распределения запросов к конечной точке */* (каждое задание создаёт один запрос в секунду) по всем pod’ам рабочей нагрузки.

## Prometheus и Grafana

Чтобы использовать расширенные возможности анализа в Argo Rollouts, нам понадобится рабочая нагрузка Prometheus в кластере, которая скрейпит конечные точки сервисов, предоставляющие метрики в формате Prometheus. Для визуализации метрик, которые мы будем анализировать, мы также запустим в кластере Grafana.

Для удобства в папке [monitoring](https://github.com/larkintuckerllc/hello-argo-rollouts/tree/master/monitoring) есть нужные манифесты Kubernetes для создания подходящих рабочих нагрузок Prometheus и Grafana. Больше об этих рабочих нагрузках см. в статьях [*с примерами Prometheus*](https://codeburst.io/prometheus-by-example-4804ab86e741) и [*с примерами Grafana*](https://codeburst.io/grafana-by-example-58726443e317)*.*

## k8s-deployment-working

Прежде чем приступить к использованию возможностей Argo Rollouts, давайте вспомним, как мы создаём канареечное развёртывание с помощью deployment. В первой вариации рабочей нагрузки, в изначально стабильном состоянии, у нас есть следующие ресурсы:

* [*Сервис app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-deployment-working/app-1-00-service.yaml): предоставляет внутреннюю балансировку нагрузки для pod’ов в deployment’ах *app-1* и *app-1-canary*.
* [*Deployment app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-deployment-working/app-1-02-deployment.yaml): содержит пять pod’ов с рабочим образом, *0.3.0*.
* [*Deployment app-1-canary*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-deployment-working/app-1-01-canary-deployment.yaml): содержит один pod с рабочим образом, *0.3.0*.

Проверим все эти ресурсы следующей командой:

```
$ kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/app-1-6fbf6fb56f-4dshr          1/1     Running   0          16s
pod/app-1-6fbf6fb56f-f8zxr          1/1     Running   0          16s
pod/app-1-6fbf6fb56f-hclt9          1/1     Running   0          15s
pod/app-1-6fbf6fb56f-jlc29          1/1     Running   0          16s
pod/app-1-6fbf6fb56f-qq848          1/1     Running   0          16s
pod/app-1-canary-6fbf6fb56f-9n9sg   1/1     Running   0          118s
NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/app-1        ClusterIP   10.8.12.240   <none>        80/TCP    18m
service/kubernetes   ClusterIP   10.8.0.1      <none>        443/TCP   2d8h
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/app-1          5/5     5            5           16s
deployment.apps/app-1-canary   1/1     1            1           118s
NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/app-1-6fbf6fb56f          5         5         5       17s
replicaset.apps/app-1-canary-6fbf6fb56f   1         1         1       119s
```

Итак, всё на месте. Теперь мы подаём нагрузку и видим, что средний процент запросов с ошибкой *404* равен *0*.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d4278047929f1afc06e142c6c6c8007f1ef131f9%2F0c5f5ed9af9b96c46e173ba539bf56ff.png?alt=media)

Напоминаю, что мы отслеживаем эти две метрики:

* *app\_requests\_total*: общее число запросов, исключая конечную точку */metrics*, обработанных рабочей нагрузкой.
* *app\_not\_found\_total*: общее число запросов, которые не соответствовали двум конечным точкам; возвращается ошибка *404*.

Вот метрика, которая визуализирована на схеме:

```
avg(rate(app_not_founds_total{kubernetes_namespace="default",
kubernetes_name="app-1"}[$__interval])) /
(avg(rate(app_requests_total{kubernetes_namespace="default",
kubernetes_name="app-1"}[$__interval])) > 0) or
avg(rate(app_requests_total{kubernetes_namespace="default",
kubernetes_name="app-1"}[$__interval]))
```

**Примечание**: оператор *or* усложняет выражение, но зато мы видим значение *0*, если средняя частота запросов равняется *0* (деление на ноль нам не мешает)

## k8s-deployment-broken

Здесь мы добавим в \*[deployment app-1-canary](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-deployment-broken/app-1-01-canary-deployment.yaml)\*поломанный образ *0.3.1*. На практике сначала всегда нужно обновлять канареечный deployment, чтобы заметить проблемы, пока они затрагивают только часть рабочей нагрузки.

Пускаем трафик и видим, что примерно *1/6* (чуть больше *16%*) запросов завершаются ошибкой 404, и все эти запросы от сломанного канареечного pod’а.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9decf489d9107186ef6a3d02fff251e2961d9113%2F75014aaa60f46d53eed5c41f8611548f.png?alt=media)

Увидев эту проблему с канареечным deployment’ом, мы решили откатить *app-1-canary*.

```
$ kubectl rollout undo deployment.v1.apps/app-1-canary
deployment.apps/app-1-canary rolled back
```

Давайте посмотрим на наши ресурсы после отката.

```
$ kubectl get all
...
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/app-1          5/5     5            5           103s
deployment.apps/app-1-canary   1/1     1            1           104s
NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/app-1-6fbf6fb56f          5         5         5       103s
replicaset.apps/app-1-canary-597ff54bb6   0         0         0       70s
replicaset.apps/app-1-canary-6fbf6fb56f   1         1         1       104s
```

Обратите внимание:

* Тут почти всё то же самое, что было до того, как мы добавили в *app-1-canary* поломанный образ, только теперь у нас есть дополнительный набор реплик с *0* реплик — это он управлял проблемным pod’ом.

Смотрим историю deployment’а:

```
$ kubectl rollout history deployment.v1.apps/app-1-canary
deployment.apps/app-1-canary
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
```

Обратите внимание:

* Версия *1* (уже не отображается) обозначала изначальное состояние с рабочим образом.
* Версия *2* соответствует deployment’у с поломанным образом.
* Версия *3* отражает текущее состояния после отката. Версии неизменяемы, так что откат создал новую версию.
* В выходных данных мало информации. Например, непонятно, как сопоставить версии с наборами реплик.

## k8s-rollout-manual-working

Здесь мы реплицируем канареечную функцию с помощью Argo Rollouts. В этой вариации рабочей нагрузки мы начинаем с изначально стабильного состояния со следующими ресурсами:

* [*Сервис app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-manual-working/app-1-00-service.yaml): предоставляет внутреннюю балансировку нагрузки для pod’ов в rollout’е *app-1*.
* [*Rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-manual-working/app-1-01-rollout.yaml): как и deployment до этого, он предоставляет пять pod’ов с рабочим образом, *0.3.0*.

Проверим ресурсы следующей командой:

```
$ kubectl get all
NAME                         READY   STATUS    RESTARTS   AGE
pod/app-1-55c599b68f-fbwzd   1/1     Running   0          33s
pod/app-1-55c599b68f-mlxxj   1/1     Running   0          33s
pod/app-1-55c599b68f-n4lvg   1/1     Running   0          33s
pod/app-1-55c599b68f-nntnq   1/1     Running   0          33s
pod/app-1-55c599b68f-qg44l   1/1     Running   0          33s
NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/app-1        ClusterIP   10.8.15.149   <none>        80/TCP    35s
service/kubernetes   ClusterIP   10.8.0.1      <none>        443/TCP   3d9h
NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/app-1-55c599b68f   5         5         5       34s
```

Проверяем rollout:

```
$ kubectl get rollout app-1
NAME    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
app-1   5         5         5            5
```

Обратите внимание:

* rollout — это не deployment, так что в выходных данных команды *kubectl get all* мы его не видим.
* Rollout, как и deployment, управляет наборами реплик (которые, в свою очередь, управляют pod’ами).
* Выходные данные у rollout’а такие же, как у deployment’а, потому что у них один интерфейс API.

Мы можем узнать больше о rollout’е следующей командой:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          sckmkny/app-1:0.3.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5
NAME                               KIND        STATUS     AGE  INFO
⟳ app-1                            Rollout     ✔ Healthy  16m
└──# revision:1
   └──⧉ app-1-55c599b68f           ReplicaSet  ✔ Healthy  16m  stable
      ├──□ app-1-55c599b68f-fbwzd  Pod         ✔ Running  16m  ready:1/1
      ├──□ app-1-55c599b68f-mlxxj  Pod         ✔ Running  16m  ready:1/1
      ├──□ app-1-55c599b68f-n4lvg  Pod         ✔ Running  16m  ready:1/1
      ├──□ app-1-55c599b68f-nntnq  Pod         ✔ Running  16m  ready:1/1
      └──□ app-1-55c599b68f-qg44l  Pod         ✔ Running  16m  ready:1/1
```

**Примечание.** Дальше мы будем использовать только подробный вывод для rollout’а, потому что он гораздо интереснее. Пока мы говорили о сходствах rollout’а и deployment’а. Теперь поговорим о различиях.

В примере с *рабочим deployment’ом* у нас было *0*% запросов с ошибкой.

## k8s-rollout-manual-broken

Теперь добавим в \*[rollout app-1](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-manual-broken/app-1-01-rollout.yaml)\*поломанный образ *0.3.1*. Здесь, в отличие от deployment’а, rollout обновил одну реплику и остановился.

Давайте посмотрим поближе:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ॥ Paused
Message:         CanaryPauseStep
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          sckmkny/app-1:0.3.0 (stable)
                 sckmkny/app-1:0.3.1 (canary)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5
NAME                               KIND        STATUS     AGE   INFO
⟳ app-1                            Rollout     ॥ Paused   23h
├──# revision:2
│  └──⧉ app-1-57c5db7ccd           ReplicaSet  ✔ Healthy  113s  canary
│     └──□ app-1-57c5db7ccd-9w7rz  Pod         ✔ Running  113s  ready:1/1
└──# revision:1
   └──⧉ app-1-55c599b68f           ReplicaSet  ✔ Healthy  23h   stable
      ├──□ app-1-55c599b68f-fbwzd  Pod         ✔ Running  23h   ready:1/1
      ├──□ app-1-55c599b68f-mlxxj  Pod         ✔ Running  23h   ready:1/1
      ├──□ app-1-55c599b68f-n4lvg  Pod         ✔ Running  23h   ready:1/1
      └──□ app-1-55c599b68f-nntnq  Pod         ✔ Running  23h   ready:1/1
```

Обратите внимание:

* В отличие от deployment’а, rollout остановился, пока никто из pod’ов ещё не сообщил о проблеме. Deployment останавливается, только если кто-то из pod’ов не готов.
* Как видим, rollout тоже создал один канареечный pod.

Давайте посмотрим на главное различие между настройкой deployment’а и rollout’а — блок *strategy*. Вот блок strategy для rollout’а [*app-1–01-rollout.yaml*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-manual-broken/app-1-01-rollout.yaml)*:*

```
strategy:
  canary:
    steps:
    - setWeight: 20
    - pause: {duration: 5m}
    - analysis:
        templates:
        - templateName: not-found-percentage
        args:
        - name: service-name
          value: app-1
```

Обратите внимание:

* Эти восемь шагов (steps) соответствуют восьми шагам, указанным в подробных выходных данных rollout’а, причём там мы видим, что последним был выполнен шаг *1*
* Weight — это процент от количества реплик, которые мы хотели обновить. На *20*% процесс остановился, обновив одну реплику (*5 \* 0,2 = 1*).
* Для паузы не указана длительность, а значит мы должны вручную разрешить или запретить продолжение операции.
* Этот пример немного надуманный, потому что шаги после первой паузы нам не нужны (приводятся здесь для иллюстрации).

Если бы мы пустили трафик, то увидели бы, что примерно *1/5* (чуть больше 20%) запросов завершаются ошибкой 404, и все эти запросы поступают от сломанного канареечного pod’а.

Увидев эту проблему, мы решили отменить rollout *app-1*.

```
$ kubectl argo rollouts abort app-1
rollout 'app-1' aborted
```

В подробных выходных данных видно, что произошло:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✖ Degraded
Message:         RolloutAborted: Rollout is aborted
Strategy:        Canary
  Step:          0/8
  SetWeight:     0
  ActualWeight:  0
Images:          sckmkny/app-1:0.3.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         5
  Available:     5
NAME                               KIND        STATUS        AGE  INFO
⟳ app-1                            Rollout     ✖ Degraded    23h
├──# revision:2
│  └──⧉ app-1-57c5db7ccd           ReplicaSet  • ScaledDown  18m  canary
└──# revision:1
   └──⧉ app-1-55c599b68f           ReplicaSet  ✔ Healthy     23h  stable
      ├──□ app-1-55c599b68f-fbwzd  Pod         ✔ Running     23h  ready:1/1
      ├──□ app-1-55c599b68f-mlxxj  Pod         ✔ Running     23h  ready:1/1
      ├──□ app-1-55c599b68f-n4lvg  Pod         ✔ Running     23h  ready:1/1
      ├──□ app-1-55c599b68f-nntnq  Pod         ✔ Running     23h  ready:1/1
      └──□ app-1-55c599b68f-rz92r  Pod         ✔ Running     92s  ready:1/1
```

Обратите внимание:

* Здесь мы видим, что rollout находится в состоянии Degraded, потому что у последней версии (revision) нет реплик.

Чтобы вернуть rollout в состояние Healthy, мы обновляем [*rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-manual-working/app-1-01-rollout.yaml), взяв изначальный образ *0.3.0*.

Вот что у нас получится:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          sckmkny/app-1:0.3.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5
NAME                               KIND        STATUS        AGE    INFO
⟳ app-1                            Rollout     ✔ Healthy     23h
├──# revision:3
│  └──⧉ app-1-55c599b68f           ReplicaSet  ✔ Healthy     23h    stable
│     ├──□ app-1-55c599b68f-fbwzd  Pod         ✔ Running     23h    ready:1/1
│     ├──□ app-1-55c599b68f-mlxxj  Pod         ✔ Running     23h    ready:1/1
│     ├──□ app-1-55c599b68f-n4lvg  Pod         ✔ Running     23h    ready:1/1
│     ├──□ app-1-55c599b68f-nntnq  Pod         ✔ Running     23h    ready:1/1
│     └──□ app-1-55c599b68f-rz92r  Pod         ✔ Running     4m18s  ready:1/1
└──# revision:2
   └──⧉ app-1-57c5db7ccd           ReplicaSet  • ScaledDown  21m
```

Обратите внимание:

* В отличие от deployment’а, мы можем легко связать версию с набором реплик, например здесь у revision *3* тот же Replicaset, что и у revision *1* (из предыдущих выходных данных).

## k8s-rollout-analysis-initial

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

* [*Сервис app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-initial/app-1-00-service.yaml): предоставляет внутреннюю балансировку нагрузки для pod’ов в rollout’е *app-1* (тот же, что и раньше).
* [*Rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-initial/app-1-01-rollout.yaml): rollout, который автоматизирует анализ канареечного pod’а, то есть на основе метрики выбирает — продолжить или отменить rollout.
* [*Шаблон анализа app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-initial/00-not-found-percentage-analysistemplate.yaml): метрика и логика, которую использует rollout. В этом шаблоне мы видим те же шаги, которые делали вручную, когда смотрели на панель Grafana, чтобы узнать, всё ли в порядке с pod’ом.

Давайте посмотрим на блок strategy для этого rollout’а.

```
strategy:
  canary:
    steps:
    - setWeight: 20
    - pause: {duration: 5m}
    - analysis:
        templates:
        - templateName: not-found-percentage
        args:
        - name: service-name
          value: app-1
```

Обратите внимание:

* На первом шаге мы выполняем один канаречный pod в течение пяти минут. Этого достаточно, чтобы Prometheus успел насобирать метрики.
* Здесь мы проиллюстрируем использование параметризованного шаблона и передадим в AnalysisTemplate *service-name: app-1*.

Как видите, ничего особо не поменялось:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          3/3
  SetWeight:     100
  ActualWeight:  100
Images:          sckmkny/app-1:0.2.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                              KIND        STATUS     AGE  INFO
⟳ app-1                           Rollout     ✔ Healthy  13s
└──# revision:1
   └──⧉ app-1-58dcdc8db           ReplicaSet  ✔ Healthy  13s  stable
      ├──□ app-1-58dcdc8db-5tcsd  Pod         ✔ Running  13s  ready:1/1
      ├──□ app-1-58dcdc8db-7vk29  Pod         ✔ Running  13s  ready:1/1
      ├──□ app-1-58dcdc8db-gf4x4  Pod         ✔ Running  13s  ready:1/1
      ├──□ app-1-58dcdc8db-hp9sl  Pod         ✔ Running  13s  ready:1/1
      └──□ app-1-58dcdc8db-m4bz5  Pod         ✔ Running  13s  ready:1/1
```

## k8s-rollout-analysis-working

Теперь добавим в [*rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-working/app-1-01-rollout.yaml) ещё один рабочий образ *0.3.0*, чтобы показать автоматическое продолжение rollout’а. Через *5* минут проверяем, как дела:

Обратите внимание:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          3/3
  SetWeight:     100
  ActualWeight:  100
Images:          sckmkny/app-1:0.3.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                               KIND         STATUS        AGE   INFO
⟳ app-1                            Rollout      ✔ Healthy     22m
├──# revision:2
│  ├──⧉ app-1-55c599b68f           ReplicaSet   ✔ Healthy     14m   stable
│  │  ├──□ app-1-55c599b68f-6fx2z  Pod          ✔ Running     14m   ready:1/1
│  │  ├──□ app-1-55c599b68f-lcj7r  Pod          ✔ Running     9m9s  ready:1/1
│  │  ├──□ app-1-55c599b68f-qdl7k  Pod          ✔ Running     9m9s  ready:1/1
│  │  ├──□ app-1-55c599b68f-fkvr6  Pod          ✔ Running     9m7s  ready:1/1
│  │  └──□ app-1-55c599b68f-h2jmq  Pod          ✔ Running     9m7s  ready:1/1
│  └──α app-1-55c599b68f-2-2       AnalysisRun  ✔ Successful  9m9s  ✔ 1
└──# revision:1
   └──⧉ app-1-58dcdc8db            ReplicaSet   • ScaledDown  22m
```

* Как видим, rollout автоматически переведён в версию *2*
* Появился новый ресурс: успешный AnalysisRun.

Давайте изучим его самую важную часть:

```
$ kubectl describe analysisrun app-1-55c599b68f-2-2
...
Status:
  Metric Results:
    Count:  1
    Measurements:
      Finished At:  2021-02-13T15:21:50Z
      Phase:        Successful
      Started At:   2021-02-13T15:21:50Z
      Value:        [0]
    Name:           not-found-percentage
    Phase:          Successful
    Successful:     1
  Phase:            Successful
  Started At:       2021-02-13T15:21:50Z
...
```

Обратите внимание:

Мы видим не только состояние Succesful, но и фактическое значение (здесь это *0*), возвращённое запросом Prometheus.

## k8s-rollout-analysis-broken

Теперь добавим в [*rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-broken/app-1-01-rollout.yaml) поломанный образ *0.3.1.*, чтобы показать автоматическую отмену rollout’а. Через *5* минут проверяем, как дела:

```
$ kubectl argo rollouts get rollout app-1
Name:            app-1
Namespace:       default
Status:          ✖ Degraded
Message:         RolloutAborted: metric "not-found-percentage" assessed Failed due to failed (1) > failureLimit (0)
Strategy:        Canary
  Step:          0/3
  SetWeight:     0
  ActualWeight:  0
Images:          sckmkny/app-1:0.3.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         5
  Available:     5

NAME                               KIND         STATUS        AGE  INFO
⟳ app-1                            Rollout      ✖ Degraded    45m
├──# revision:3
│  ├──⧉ app-1-57c5db7ccd           ReplicaSet   • ScaledDown  16m  canary
│  └──α app-1-57c5db7ccd-3-2       AnalysisRun  ✖ Failed      11m  ✖ 1
├──# revision:2
│  ├──⧉ app-1-55c599b68f           ReplicaSet   ✔ Healthy     37m  stable
│  │  ├──□ app-1-55c599b68f-6fx2z  Pod          ✔ Running     37m  ready:1/1
│  │  ├──□ app-1-55c599b68f-lcj7r  Pod          ✔ Running     32m  ready:1/1
│  │  ├──□ app-1-55c599b68f-qdl7k  Pod          ✔ Running     32m  ready:1/1
│  │  ├──□ app-1-55c599b68f-h2jmq  Pod          ✔ Running     32m  ready:1/1
│  │  └──□ app-1-55c599b68f-pw8kv  Pod          ✔ Running     11m  ready:1/1
│  └──α app-1-55c599b68f-2-2       AnalysisRun  ✔ Successful  32m  ✔ 1
└──# revision:1
   └──⧉ app-1-58dcdc8db            ReplicaSet   • ScaledDown  45m
```

Часть AnalysisRun:

```
kubectl describe analysisrun app-1-57c5db7ccd-3-2
...
Status:
  Message:  metric "not-found-percentage" assessed Failed due to failed (1) > failureLimit (0)
  Metric Results:
    Count:   1
    Failed:  1
    Measurements:
      Finished At:  2021-02-13T15:42:34Z
      Phase:        Failed
      Started At:   2021-02-13T15:42:34Z
      Value:        [0.22925031610593755]
    Name:           not-found-percentage
    Phase:          Failed
  Phase:            Failed
  Started At:       2021-02-13T15:42:34Z
...
```

Обратите внимание:

* Значение превышает 0,1, то есть проверка не пройдена.
* Здесь rollout автоматически отменяется — как мы бы это сделали вручную.
* Чтобы вернуть rollout в состояние Healthy, мы обновляем [*rollout app-1*](https://github.com/larkintuckerllc/hello-argo-rollouts/blob/master/k8s-rollout-analysis-working/app-1-01-rollout.yaml), взяв рабочий образ *0.3.0*.

##


# Rsource monitoring Avito

<https://habr.com/ru/companies/avito/articles/694232/>

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-efc8ff48d28899d3f130821f8c5829bf5567cf78%2Fb25c21b0cbc0844e892412540a28307e.png?alt=media)

Привет! Меня зовут Антон Губарев, я инженер PaaS (Platform-as-a-Service) в Авито. Платформа как сервис позволяет продуктовым командам разработки не тратить время на рутинные и инфраструктурные задачи, например, определение оптимальных значений request/limit CPU и RAM для контейнеров в кластерах Kubernetes. Вместо этого они могут сосредоточиться на качестве сервиса, над которым работают. PaaS умеет автоматически рассчитывать ограничения и выделять ресурсы для каждого сервиса.

Рассказываю, как мы в Авито собираем метрики потребления ресурсов серверного оборудования, храним и используем их, чтобы спланировать потребление в будущем.

### Решения для сбора метрик и анализа потребления ресурсов

Все сервисы Авито выкатываются в 3–4 независимых кластера Kubernetes и весь влетающий трафик балансируется между ними. Сервис в продакшене существует в каждом кластере. Взаимодействие осуществляется через наш Service Mesh, в том числе и между кластерами если это необходимо. Всего мы используем несколько десятков кластеров под разные нужды, и они периодически сменяют друг друга: один выводится из эксплуатации и вместо него вводится другой.

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

* Потребление CPU и RAM глобально на все сервисы суммарно.
* Потребление CPU и RAM на каждый контейнер/под, чтобы можно было выявить аномалии.
* Суммарное потребление по деплойментам.
* Индекс потребления (Resource Volume) как единая метрика, понятная для руководства.

Раньше для сбора метрик и мониторинга мы использовали Prometheus, но со временем его возможностей перестало хватать. Поэтому перешли на VictoriaMetrics. Это база данных для хранения временных рядов, которая поддерживает протокол PromQL.

VictoriaMetrics отлично кластеризуется и меньше тормозит на тех же объёмах данных. Переход на новое решение позволил снизить потребление ресурсов CPU примерно в семь раз, а RAM — в 12 раз.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-0894d1bcaccfa425219157e59b0bce83e7463092%2F81a0e49c092fa4a6f14fd599e60bc325.png?alt=media)

Потребление CPU: жёлтая полоса — Prometheus, зелёная — VictoriaMetrics

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-625b7c9bf7294055ba9a64a3d6c0ebf9eddf393e%2Fac43b9341c61874ce21a790188e23493.png?alt=media)

Потребление RAM: жёлтая полоса — Prometheus, зелёная — VictoriaMetrics

При этом осталась задача, которую VictoriaMetrics решить не может в наших условиях: долгосрочная аналитика и планирование потребления. Недостаточно данных за семь дней, нужна информация за месяцы и кварталы. А хранить такие объемы без серьезной потери производительности не позволяли возможности ни VM, ни Prometheus.

В качестве решения для хранения данных за длительный период мы выбрали ClickHouse. Это OLAP-система, которая способна переваривать большие объемы. Предварительные эксперименты показали, что ClickHouse может не только хранить, но и быстро отдавать данные за нужные нам периоды..

Ещё у ClickHouse есть интересные фичи:

* Материализованные представления — аналог View в РСУБД.
* Словари — хранилище внешних данных в виде пар «ключ-значение». Доступ к ним происходит быстрее, чем с помощью JOIN-ов.

### Какие данные о потреблении мы храним

Главные метрики, которые нас интересуют: глобальный расход CPU и RAM, расход на каждый контейнер или под и общий индекс потребления Resource Volume.

Сначала разберёмся с метриками для CPU/RAM. С точки зрения аналитики нас интересует фактическое потребление — usage, и запрошенные ресурсы — request, для сервиса за минуту, день, неделю и месяцы. Основная таблица хранения в ClickHouse:

```
CREATE TABLE resources.consumption
(
    time         DateTime,
    namespace    String,
    env          LowCardinality(FixedString(10)),
    cluster      LowCardinality(FixedString(20)),
    deployment   String,
    pod          String,
    node         String,
    unit         Nullable(String),
    cpu_usage    Nullable(Float64),
    cpu_request  Nullable(Float64),
    mem_usage    Nullable(UInt64),
    mem_request  Nullable(UInt64),
    net_tx_usage Nullable(UInt64),
    net_tx_usage Nullable(UInt64)
)
    engine = ReplicatedMergeTree()
        PARTITION BY toYYYYMM(time)
        ORDER BY (time, namespace, env, cluster, deployment, pod, node)
        SETTINGS index_granularity = 8192;
```

Мы написали сборщик данных, который аккумулирует данные из всех экземпляров VictoriaMetrics и переносит их в ClickHouse, контролирует доступность источников. С его помощью сделали первую выгрузку данных за полгода. Получили больше 15 миллиардов записей, при этом аналитические запросы длились от 10 секунд — это долго.

Чтобы улучшить производительность, решили использовать материализованные представления и просуммировать данные по подам до уровня микросервиса. Этого достаточно для аналитических запросов и планирования потребления. Для суммирования использовали движок SummingMergeTree, который присутствует в ClickHouse.

```
CREATE TABLE resources.consumption
(
    time         DateTime,
    namespace    String,
    env          LowCardinality(FixedString(10)),
    cluster      LowCardinality(FixedString(20)),
    deployment   String,
    pod          String,
    node         String,
    unit         Nullable(String),
    cpu_usage    Nullable(Float64),
    cpu_request  Nullable(Float64),
    mem_usage    Nullable(UInt64),
    mem_request  Nullable(UInt64),
    net_tx_usage Nullable(UInt64),
    net_tx_usage Nullable(UInt64)
)
    engine =  SummingMergeTree(
      pods, cpu_usage, cpu_request, mem_usage, mem_request, net_tx_usage, net_rx_ usage)
        PARTITION BY toYear(time)
        ORDER BY (env, cluster, namespace, time)
        SETTING ingex_granularity = 8192
```

Запрос, с помощью которого данные из таблицы-источника автоматически переносятся в материализованное представление:

```
SELECT
     time,
     namespace,
     env,
     cluster,
     count() AS pods,
     sum(cpu_usage) AS cpu_usage,
     sum(cpu_request) AS cpu_request,
     sum(mem_usage) AS mem_usage,
     sum(mem_request) AS mem_request,
     sum(net_tx_usage) AS net_tx_usage,
     sum(net_rx_usage) AS net_rx_usage
 FROM resources.consumption
 GROUP BY
     env,
     cluster,
     namespace,
     time
```

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

Ещё одна важная метрика — Resource Volume (RV). Она показывает общую картину потребления ресурсов в компании и нужна больше для менеджмента. 1 RV — это эквивалент 1 CPU или 3 ГБ RAM. Допустим, у сервиса есть 10 реплик, каждая из которых потребляет 1 CPU и 2 ГБ RAM. Значит, всего сервис использует 10 CPU *и 20*/3 RAM, или 16,6 RV.

### Как отображаются аналитические данные о потреблении ресурсов

Аналитика потребления отображается в формате графиков и диаграмм в Grafana.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9e6306149752a77a23269dc69eb4a1bb25f287ac%2Fa65b178b7ccbeb29cc294ca737d0ddee.png?alt=media)

Дашборд в Grafana

Отдельный дашборд с данными за несколько месяцев есть в PaaS. Внутри него можно посмотреть детализацию потребления до пода. Можно быстро проверить, сколько ресурсов потребляет каждый сервис, и сразу увидеть аномалию.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8e151bcb8816f2b3d26337f2458db7b4a5e6a197%2F493e24820a482975bdd301656a848588.png?alt=media)

Дашборд потребления ресурсов в PaaS

На основе аналитики в PaaS мы построили систему бюджетирования ресурсов. Команды, которые используют общие ресурсы, объединены в юниты. Каждый юнит раз в квартал подаёт заявку на выделение для него серверных мощностей. Одобренные заявки и использованная часть ресурса отображаются в дашборде.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-dc60e30a27d02046e00309aaab63ab7390089524%2F59aed69decc8bfb8dd2b10ec248c62e1.png?alt=media)

Использованный ресурс рассчитывается в единицах Resource Volume

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

### Запрос ресурсов и ограничения

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

В Kubernetes можно установить значения request и limit для CPU и RAM для каждого контейнера. Причём разработчики Авито делают это не вручную, а только указывают с помощью PaaS, сколько реплик сервиса им нужно. Система деплоя распределяет их по кластерам, в том числе рассчитывает request и limit.

Расчёт проходит в четыре шага, на каждом из которых значения request/limit могут измениться.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-52b003a3f3be11890696a12b639e7c38a9e1e90b%2Feb05de8c1563e5d75a45251a85d9a3c7.png?alt=media)

Этапы расчёта request/limit для сервиса

**Первый шаг / Box.** Для каждого языка программирования, которые используются в Авито, и для каждого размера сервиса есть предустановленные — «коробочные» — значения. Размер может быть большой, средний или маленький, его указывает в конфигурации разработчик, когда создаёт сервис (может быть изменено в любой момент). Языки, для которых есть предустановленные значения, — Go, PHP, Python, JavaScript, Kotlin и Swift. Например, для маленького сервиса на Go можно запросить максимум 100 CPU.

**Второй шаг / Usage.** Для расчёта значений request/limit на этом шаге используется 75 перцентиль за три дня и постоянный коэффициент Ratio. Данные по потреблению хранятся в VictoriaMetrics, среднее значение берётся по всем кластерам, где запущен сервис.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-9af6fdf919372c456b2520bfc1187f7c3193c6fe%2F4cc8684391267b401215dd972e5da4dd.png?alt=media)

Коэффициент Ratio для CPU и RAM выведен эмпирически и помогает пересчитать реальное потребление в значение request/limit

Например, если сервис за прошлые 3 дня потреблял в среднем 500 RAM, то значение request для него будет равно 1 000 (Ratio=2), а limit — 5 000 (Ratio=10).

**Третий шаг / Range.** Для исключения вероятности бесконтрольного роста значений request/limit и возможности появления аномалий мы установили некоторые пороговые значения, больше или меньше которых значения выставиться не могут. —

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7c69160da41567dd9f6aee2acffb316f47f8260c%2F095973fdfb2f43d45e60149119b6b3c6.png?alt=media)

Значения Range заданы в PaaS вручную и зависят от языка программирования и размера сервиса

На этом шаге платформа проверяет каждый контейнер и корректирует request/limit, если они вышли за максимальное или минимальное значения. Даже если один из микросервисов ведёт себя аномально и потребляет слишком много ресурсов по историческим данным (Usage), это не повлияет на выделение ресурсов в новом деплое. При возникновении таких ситуаций разработчики разбираются в проблеме и исправляют ее, чтобы потребление пришло в норму.

**Четвёртый шаг / Manual.** В случае, когда автоматические расчёты не подходят, разработчик может выставить значения request/limit вручную. Например, если запускается высоконагруженный и критически важный микросервис, для которого нужны особые условия. Для этого есть специальный конфигурационный файл, в котором описывается, сколько реплик должно быть, какие переменные окружения, кроны и воркеры. Ещё в нём можно указать значения request/limit для крона, воркера или самого сервиса. Этот файл использует PaaS, когда разворачивает сервис. В конфигурационном файле разработчик вручную может прописать необходимые значения request и limit

```
[[envs.prod.crons]]
name = "import services consumption"
enabled = true
schedule = "*/5 * * * *"
command = 'etl -type=consumption -duration=1h'
resources/requests.cpu = 1000
resources.requests.memory = 10000
resources.limits.cpu = 5000
resources.limits.memory = 30000
```

На этом заканчивается работа автоматики, и микросервис раскатывается в нужное количество кластеров с нужными значениями ресурсов.

### Что даёт мониторинг потребления ресурсов

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

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

Разработчики не должны думать об устройстве инфраструктуры и запросе ресурсов. PaaS автоматически выставляет объективные значения request и limit для CPU и RAM. При этом в особых случаях можно прописать их вручную в конфигурационном файле.


# Exposing services

[Exposing Kubernetes Services](/readme/architect/kubernetes/exposing-services/exposing-kubernetes-services)

[Cilium BGP](/readme/architect/kubernetes/exposing-services/cilium-bgp)


# Exposing Kubernetes Services

<https://earthly.dev/blog/kubernetes-services/>

Exposing Services in Kubernetes short version <https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security/exposing-services-in-kubernetes>

Kubernetes is a tool for managing containerized applications, designed to make it easy to deploy and scale applications. It is designed to work with a variety of container technologies like [Docker and containerd](https://earthly.dev/blog/containerd-vs-docker/). In a Kubernetes [cluster](https://earthly.dev/blog/kube-bench), your application runs in a **Pod**. In Kubernetes, Pods are *ephemeral*; they are temporary resources which are created and destroyed as needed .

When pods need to interact with other resources in a Kubernetes cluster, they can use the IP addresses provided by the cluster. However, this approach has the drawback of requiring developers to manually configure the IP addresses for each pod. Because Pods are temporary resources in a cluster, it is practically impossible to configure IP tables whenever a new Pod is created or destroyed. As a result, it is challenging for Pods to communicate with one another using IP addresses.

To solve this problem, Kubernetes has a resource called **Service**, which gives the Pod a stable IP address to solve this communication issue—making interaction with other Pods considerably more reliable. Services provide a way to expose applications running on a Kubernetes cluster to the outside world. They also allow for load balancing and for routing traffic to the correct application instance. Services can be exposed using a variety of methods, such as a load balancer or an [Ingress](https://earthly.dev/blog/k8s-networking) resource.

In this guide, you’ll learn about Services and its types in Kubernetes, and how to define them using YAML files. By the end of the article, you’ll have a good understanding of Services in Kubernetes.

## An Overview of ReplicaSets in Kubernetes

We’ll use [ReplicaSets](https://earthly.dev/blog/use-replicasets-in-k8s/) in this tutorial. ReplicaSets are Pod Controllers in Kubernetes, they are used to make the pods fault tolerant by making it easy for them to easily scale up and down. Replicaset ensures that a specific number of Pods(replicas) keep running in the cluster. To make a ReplicaSet, create a new file `my-replicaset.yml` and populate it with the following configuration:

```
my-replicaset.ymlapiVersion: apps/v1
kind: ReplicaSet
metadata:
name: my-replicaset
spec:
replicas: 3
selector:
matchLabels:
app: my-pod
template:
metadata:
labels:
app: my-pod
spec:
containers:
- name: my-server
image: nginx
ports:
- containerPort: 80
```

Let’s go through the contents of the above YAML file:

* The first line, `apiVersion: apps/v1`, specifies the version of the Kubernetes API that should be used to interpret this YAML file.
* The second line, `kind: ReplicaSet`, specifies the type of object that this YAML file describes. In this case, it specifies that the file describes a ReplicaSet object.
* The `metadata` section provides metadata about the ReplicaSet object. Here, it specifies that the ReplicaSet should be named “my-replicaset”.
* The `spec` section specifies the details of the ReplicaSet. Here, the ReplicaSet should manage three replicas of the pod, and that the pod should be selected based on the “app” label being set to “my-pod”.
* The `template` section specifies the details of the pod that the ReplicaSet will manage. The pod should have the `app: my-pod` label and it should contain a container named `my-server` based on the `nginx` image.
* The `containerPort` field in the `spec` section specifies the port from which the pod can be reached from inside the cluster.

To create the ReplicaSet, open your terminal and run `kubectl create -f my-replicaset.yml`. Running this command should produce the following output.

```
>_replicaset.apps/my-replicaset created
```

## Types of Services in Kubernetes

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-d0e73f2cce40c18df185e8d24423f7faf321907e%2FIkyAMKi.png?alt=media)

Kubernetes offers four primary service types that are each beneficial for a certain task. The following services are covered in more detail below:

* ClusterIP Service
* Headless Service
* NodePort Service
* Load Balancer Service

### ClusterIP Services

A ClusterIP service is a type of service that can be used to expose instances of pods running on the Kubernetes cluster. Each pod has a single IP address that is used by the ClusterIP service to route traffic to and from that pod. A ClusterIP Service is the *default* service; if you don’t specify the `type` attribute in the YAML file, then Kubernetes will create a ClusterIP service automatically.

To create a ClusterIP service, create a YAML file and add the following configuration:

```
apiVersion: v1
kind: Service
metadata:
name: rs-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80

selector:
app: my-pod
```

Let’s parse the contents of the YAML file:

* `apiVersion` defines the version to be used. The API version must support this kind of resource.
* `kind` specifies the resource type you’re creating in [k8s](https://earthly.dev/blog/k8s-autoscaling).
* `metadata` is another mandatory field which provides the resource’s fundamental details. In this example, you only enter the resource’s name, but you may also include labels and annotations.
* The final mandatory part, `spec` , describes the requirements for the resources. Each resource has unique specifications.
* `spec.ports.port` specifies the port which should be made an endpoint in the cluster; port can take any arbitrary value.
* `spec.ports.targetPort` identifies the port that the Pod opens.
* `spec.selector` helps the Service to identify the Pods to which the request should be forwarded to. The Service will send requests to only those Pods which have a label of `app: my-pod`.

To create a new service resource, type `kubectl create -f rs-svc.yml` into your console with the `rs-svc.yml` file containing the above configuration. Kubernetes creates an endpoint resource when the service is created that lists all the endpoints to which requests should be directed.

```
>_$ kubectl get endpoints
```

```
Output
NAME             ENDPOINTS                                      AGE
kubernetes       192.168.49.2:8443                              105d
rs-service       172.17.0.5:80,172.17.0.6:80,172.17.0.7:80      111s
```

In the above example, you have three endpoints which correspond to the number of Pods in the ReplicaSet. Note that these endpoints are internal to the cluster. This means only the service can access the Pod using these endpoints but the end user cannot.

You can check detailed information about the Service by running `kubectl describe -f rs-svc.yml`, which produces the following output:

```
OutputName:            rs-service
Namespace:       default
Labels:          <none>
Annotations:     <none>
Selector:         app=my-pod
Type:             ClusterIP
IP Family Policy: SingleStack
IP Families:      IPv4
IP:               10.103.78.229
IPs:              10.103.78.229
Port:             <unset>  80/TCP
TargetPort:       80/TCP
Endpoints:        172.17.0.5:80,172.17.0.6:80,172.17.0.7:80
Session Affinity: None
Events:           <none>
```

In the above output, you can see all the Events, IPs, Type, and Selector at one place. This is helpful when you have to examine your Service thoroughly.

To check all the current services running in your cluster type `kubectl get svc`:

```
>_$ kubectl get svc
```

```
Output
NAME         CLUSTER-IP         EXTERNAL-IP     PORT(S)     AGE
kubernetes   10.111.240.1         <none>        443/TCP     30d
rs-service   10.96.206.29         <none>        80/TCP      6m
```

In the above output, you can see that External IP is set to `none` which means the current service can only be used from inside the cluster. The ClusterIP service is only accessible to other Pods and other resources inside the cluster. You’ll soon learn how to expose a service externally.

You can delete the newly created service using `kubectl delete -f rs-svc.yml` command which returns the confirmation that the service has been deleted.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-6cb7f98a6ff6eb6118ccd73430011c25178b2231%2FC9LF2jd.png?alt=media)

In the above illustration, every request at `IP:<PORT>` from inside the cluster is directed to the service resource which then redirects to the `<TARGET_PORT>` (in this case 80) of the Pods with matching labels.

### When Do We Use ClusterIP Services?

Here are some common use cases for ClusterIP services in Kubernetes:

* Load balancing traffic to a group of identical pods
* Exposing a service to other services within the cluster
* Providing a stable IP address and DNS name for a set of pods that can be used by other services within the cluster
* Providing a single entry point for accessing multiple services within the cluster

### Multiport Services

ClusterIP services can also be used to make a Pod listen to more than one port. This configuration is known as **multiport services**. In Kubernetes, a multiport service is a type of service that exposes multiple ports for external access. This is useful when a **single application or container exposes multiple services or APIs on different ports**.

By creating a multiport service, you can map these different ports to a single service and make them accessible using a single IP address and DNS name. This simplifies the process of accessing the services and allows you to easily scale them up or down as needed.

To define a multiport service create a YAML file, `mul-svc.yml` and add the following configuration:

```
mul-svc.ymlapiVersion: v1
kind: Service
metadata:
name: rs-service
spec:
ports:
- name: server-main
protocol: TCP
port: 80
targetPort: 80

- name: server-logs
protocol: TCP
port: 25
targetPort: 25

selector:
app: my-pod
```

In the above configuration, you can see that multiple ports are defined in the `spec.ports` field. This field contains an array of objects, each of which defines the port number, protocol, and target port for a specific service. Note that defining the `name` field is *required* for each port you define in multiport services. To create the Service from the above file, run the following command in your terminal:

```
>_kubectl create -f mul-svc.yml
```

After running the above command, your application can be communicated to both port 80 and 25 using multiport service. Multiport services provide a convenient way to access multiple services from a single application or container, while cluster IP services require you to create a separate service for each port you want to expose. This is useful when a single application or container exposes multiple services or APIs on different ports.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-fc8d8f5dbd6c82b01a62e5f94c4538a864155c5c%2F1CzxmLr.png?alt=media)

In the above illustration, you can see that the requests for both Port 80 and Port 25 are routed through the same service to the Pods.

### Headless Services

You’ve learned that the ClusterIP Service *randomly routes requests* to any one of the Pods inside the Cluster. The Headless Service operates differently; rather than issuing requests at random, it facilitates direct communication between a specific Pod and other Pods. Headless service accomplishes this by setting the ClusterIP of the Pod to `None` and then performing a DNS lookup which provides the Pod’s IP instead of Service IP. This means that the service does not have a stable IP address that can be used to access it from outside the cluster.

When you need to access stateful applications such as databases, a headless service is employed. Accessing random Pods in a stateful application could lead to data inconsistencies, which is undesirable in a system.

These services are typically used when you want to access the individual pods within a service directly, rather than accessing the service as a whole through a load balancer. Headless Service is created by setting the `ClusterIP` field to `None` in the Service YAML file (here, a new file, `rs-svc-headless.yml`, is created).

```
rs-svc-headless.ymlCopyapiVersion: v1
kind: Service
metadata:
name: rs-service-headless
spec:
clusterIP: None
ports:
- protocol: TCP
port: 80
targetPort: 80

selector:
app: my-pod
```

Note that we’ve set the newly added `spec.clusterIP` field to `None`. After creating this service using `kubectl create -f rs-svc-headless.yml`, run the following command in your terminal.

```
>_$ kubectl get svc
```

Running the above command gives the following output:

```
Output
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP    PORT(S)       AGE
kubernetes                ClusterIP   10.96.0.1         <none>       443/TCP       117d
rs-service                ClusterIP   10.103.78.229     <none>       80/TCP        3m51s
rs-service-headless       ClusterIP   None              <none>       80/TCP        82s
```

As seen in the output, Kubernetes did not allocate the `CLUSTER-IP` as specified in the YAML file, hence it is None. In most cases, this type of configuration is used in conjunction with ClusterIP Service to handle load balancing and normal communication between the Pods.

Headless services are often used with pods which maintain a consistent state and store data across multiple pods. In order to maintain this state of a service and its associated data, Stateful Pods need to be accessed directly and consistently. By using a headless service, you can access the individual pods within the service directly, without going through a load balancer. This allows you to maintain the state of the service and ensure that the data is consistent across all the pods.

You can also get detailed information about the service by running the following command:

```
>_$ kubectl describe -f rs-svc-headless.yml
```

Here’s the output:

```
OutputName:                  rs-service-headless
Namespace:             default
Labels:                <none>
Annotations:           <none>
Selector:              app=my-pod
Type:                  ClusterIP
IP Family Policy:      SingleStack
IP Families:           IPv4
IP:                    None
IPs:                   None
Port:                  <unset>  80/TCP
TargetPort:            80/TCP
Endpoints:             172.17.0.5:80,172.17.0.6:80,172.17.0.7:80
Session Affinity:      None
Events:                <none>
```

In the above output, you can see that IP and IPs field is set to `None` since Headless Service does not provide any IP to the pod, but does a DNS lookup on the endpoints.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-b6e7258cf4cb8577dd3d4d9ec21f44eee0d618f0%2FOAOylfV.png?alt=media)

The above image shows how the request to write data to the database (stateful pod) goes through a DNS lookup and then to the database. This is done to maintain consistency between the replicas of the databases.

### NodePort Services

A NodePort service in Kubernetes is an extension of the ClusterIP service type. A NodePort service is similar to a ClusterIP service in that it allows the pods within a cluster to communicate with each other. However, a NodePort service also exposes the service on a specific port on each node in the cluster. Each pod has one or more ports (for example, 8080) which are used by this type of service as the external IP addresses used by clients connecting to it.

Note that exposing ports using NodePort Service type is not considered secure. This is because it allows anyone with access to the IP address of a node in the cluster to access the resources that are running on that node. This can be a security risk because it allows unauthorized users to potentially access and manipulate the services and their associated data.

To create a NodePort service, create a new file, `rs-np-svc.yml`, and populate it with the following configuration.

```
rs-np-svc.ymlCopyapiVersion: v1
kind: Service
metadata:
name: rs-service-nodeport
spec:
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30256
selector:
app: my-pod
```

In the above file you can see that the `type` field specifies the service type as NodePort, the `nodePort` field exposes port 30256 for the external use. Note that `nodePort` can take on any value in the range between 30000 and 32767. The `port` field is used for the ClusterIP service as the Service automatically creates a ClusterIP service to load balance requests among the Pods.

Now create the above service by running `kubectl create -f`rs-np-svc.yml\`. To check if the Service has been created, run the following command in your terminal:

```
>_$ kubectl get svc
```

```
Output
NAME                   TYPE            CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes             ClusterIP       10.96.0.1        <none>        443/TCP        117d
rs-service             ClusterIP       10.103.78.229    <none>        80/TCP         7m25s
rs-service-headless    ClusterIP       None             <none>        80/TCP         4m56s
rs-service-nodeport    NodePort        10.105.4.133     <none>        80:30256/TCP   28s
```

In the above output you can see that the new Nodeport Service is created by the Kubernetes. You can also run the following command to get detailed information about the Service:

```
>_$ kubectl describe -f rs-np-svc.yml
```

```
OutputName:                    rs-service-nodeport
Namespace:               default
Labels:                  <none>
Annotations:             <none>
Selector:                app=my-pod
Type:                    NodePort
IP Family Policy:        SingleStack
IP Families:             IPv4
IP:                      10.105.4.133
IPs:                     10.105.4.133
Port:                    <unset>  80/TCP
TargetPort:              80/TCP
NodePort:                <unset>  30256/TCP
Endpoints:               172.17.0.5:80,172.17.0.6:80,172.17.0.7:80
Session Affinity:        None
External Traffic Policy: Cluster
Events:                  <none>
```

The working of NodePort service is summarized below. You can see that the Node in the cluster opens the desired port (as specified in spec.ports.nodePort) which redirects all the requests to the internal service which then redirects them to the Pods.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-2485200233dd2d97af4f390c72d131c3db9307df%2Fq2dibr0.png?alt=media)

### LoadBalancer Services

Load balancer services in Kubernetes are a type of service that exposes a service to external traffic using a load balancer. A load balancer is a network appliance that distributes incoming traffic among multiple servers or nodes in a cluster. This can improve the performance, reliability, and scalability of your services by distributing the workload among multiple instances, and allowing you to easily add or remove nodes as needed.

Additionally, LoadBalancer services automatically detect and route traffic away from unhealthy pods. This helps ensure that the service remains available and responsive, even in times of failures and disruptions.

To create a LoadBalancer Service, create a new file `svc-load.yml` and add the following to the manifest file:

```
svc-load.ymlCopyapiVersion: v1
kind: Service
metadata:
name: rs-service-loadbalancer
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30056
selector:
app: my-pod
```

In the above file, you can see that `spec.type` is set as LoadBalancer, and every other configuration is the same as the Nodeport service you defined earlier.

This is because the LoadBalancer service is an *extension* of NodePort Service. Like a NodePort service, a LoadBalancer service exposes a resource on a specific port on each node in the cluster. However, a LoadBalancer service also creates a load balancer with a stable IP address and DNS name that can be used to access the service from outside the cluster. When LoadBalancer services are created Kubernetes automatically creates a NodePort service to work in conjunction with the LoadBalancer service.

Now create the above service by running the below command:

```
>_kubectl create -f svc-load.yml
```

After running the above command you can see that the LoadBalancer service was created using the command `kubectl get svc`, which produces the following output:

```
Output
NAME                     TYPE            CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
kubernetes               ClusterIP       10.96.0.1        <none>          443/TCP        126d
rs-service               ClusterIP       10.103.78.229    <none>          80/TCP         8d
rs-service-headless      ClusterIP       None             <none>          80/TCP         8d
rs-service-loadbalancer  LoadBalancer    10.110.210.241   145.168.25.58   80:30056/TCP   60s
```

In the above output you can see that the LoadBalancer Service also exposed your application using EXTERNAL-IP. Note that using LoadBalancer Service requires cloud providers like AWS or GCP. Also, minikube does not support LoadBalancer so using this service in [minikube](https://earthly.dev/blog/k8s-dev-solutions) will show `<pending>` in the EXTERNAL-IP section.

Overall, load balancer services are helpful when exposing services to external traffic.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-7f6a0190ae1f26d41db45d8624e4a2aaadb372a2%2FILmSY1C.png?alt=media)

In the above image, you can see that the external request to the cluster goes through the LoadBalancer service which then redirects it to the `nodePort`—and then chooses a random Pod—after taking into account the current load on each Pod that listens to `targetPort`.

## Configuring Services - What You Should Know

If services in Kubernetes are not configured properly, we may run into problems such as:

* The containers within the pod may not be able to communicate with each other or with other pods in the cluster.
* Pods may not be able to access the resources they need, such as memory, CPU, or storage.
* Your application may not be able to reach the outside world, which can prevent it from accessing external services or being accessed by users.
* The overall performance of the Kubernetes cluster may be degraded, which can affect the availability and reliability of the applications running on it.

It’s important to be careful when setting up services in Kubernetes because they act as the gateway to your application. If they are not configured properly, they could disrupt communication between pods and harm your system.

## Conclusion

Services in Kubernetes provide a steady network endpoint for a specific set of pods, simplifying inter-application communication. This article gave insights into creating and using such services, focusing on the most common types; ClusterIP, Headless, NodePort, and Load Balancer Services. Understanding and correctly configuring these services is crucial in Kubernetes, ensuring effective cluster communication.




---

[Next Page](/llms-full.txt/1)

