Автор: Джон Л. Уотсон (John L. Watson)

Оригинал

Apache Kafka вполне законно ассоциируется у нас с событийно-ориентированной архитектурой, большими данными, а еще с микросервисами, от которых сейчас все без ума. Это гибкая платформа с широкими возможностями, поэтому она популярна как для коммерческих, так и для опенсорс-решений. По факту она стала стандартом отрасли и любимым словом рекрутеров, наряду с «фулстеком», «микрофронтендами» и «полиглотом». По некоторым оценкам, чуть ли не 40% компаний из Fortune 500 используют Kafka. И это, возможно, ещё очень скромные цифры.

В этой статье я расскажу, как изучал Apache Kafka и с какими трудностями столкнулся. Я очень надеюсь, что мои наблюдения помогут тем, кто ещё только приступает к знакомству с платформой. Здесь мы обсудим полезные ресурсы, в основном, книги, которые я прочитал, и мой опыт с платформой — до чтения книг и после.

Я не претендую на звание эксперта по Kafka. (Было бы здорово, если бы люди чаще себе в таком признавались.) Я много лет использую Kafka и довольно неплохо с ней управляюсь, понимаю её экосистему и инструменты, работал с ней как дев и как опс. Иногда я выступал с презентациями на местных митапах и даже вошёл в топ-10 авторов по Apache Kafka на Quora.

Я примостился на скромном пятом месте среди истинных легенд в мире Kafka: Emil Koutanov, Stéphane Maarek, Hans Jespersen, Jay Kreps, Kai Wähner и Gwen Shapira. До них я, конечно, не дотягиваю, но все равно немножко собой горжусь.

Effective Kafka: A Hands-on Guide to Building Robust and Scalable Event-Driven Applications

Автор: Эмиль Кутанов (Emil Koutanov) 

Я начал использовать Kafka в 2017 году, когда это была уже зрелая технология с подробной документацией и кучей статей по теме. Я пошел по проторенному пути — почитал официальную документацию и полагался на Stack Overflow в остальном. Мы делали приложения, которые прекрасно работали... до поры до времени. Иногда мы теряли пару сообщений. Иногда консьюмеры зависали без видимых причин, и это приводило к перебалансировке. А иногда сообщения поступали не по порядку. Так или иначе все было терпимо — как у любого другого опенсорс-проекта. Я думал, у нас всё более или менее под контролем, а если что-то случалось, я винил в этом баг в Kafka или действия ребят из DevOps, обслуживавших Kafka, Spark, Hadoop, Cassandra и другие опенсорс-платформы, которые мы тогда использовали. Все изменилось, когда в 2020 году на Leanpub я увидел неприметную книжицу с хорошей скидкой по акции. В аннотации было сказано, что это книга про архитектуру, распределённые вычисления, модели согласованности, безопасность и т. д. То есть, вроде как, не очередной мануал по Kafka. И стоила она как пара чашек кофе.

Представляете? Как оказалось, у автора превосходный слог. Как он владеет языком, как строит предложения, какие слова подбирает! Настоящий литературный шедевр. Не сравнится с тем чтивом, что нам обычно предлагают сегодня специализированные издательства. Почему это так важно? Потому что автору удаётся разъяснять сложные идеи простыми словами. Хороший автор использует язык для выражения своих мыслей, для самовыражения, а не ради славы и денег. Я ценю хорошую литературу и хороший слог, тем более что сам им не владею. Конечно, это субъективное мнение, и вряд ли это хорошая реклама, если вам просто срочно нужно решить какую-то проблему в Kafka.

Effective Kafka: A Hands-on Guide to Building Robust and Scalable Event-Driven Applications — это во всех смыслах превосходная книга. В первых главах я ничего особо нового не нашёл, потому что уже был знаком с платформой и это была не первая моя книга про Kafka (об этом позже). Зато с третьей главы стало интересно. Я усомнился в своих знаниях, но и на слово автору верить не собирался. Тогда это была новая книга, без отзывов, и я не знал, насколько автор в теме, так что я закопался в официальные документы и исходный код Kafka. Так-то я и узнал, почему у нас сообщения пропадают. Батчи записей в пуле потоков потреблялись при непрерывном поллинге консьюмера, чтобы поддерживать насыщенность пула. Клиент консьюмера делал для записей офсет, пока они еще были in flight. И все это было написано в книге, чёрным по белому. А мы это пропустили. Мои инвестиции в книгу сразу же многократно окупились.

Я до чтения «Effective Kafka»
Я до чтения «Effective Kafka»

В десятой главе я сделал ещё одно открытие. Я узнал, почему сообщения публикуются не в том порядке, в каком отправляет их продюсер. Тут проблема была в настройке по умолчанию для свойства max.in.flight.requests.per.connection, которая разрешала продюсерам менять порядок записей при определённых условиях. Я снова перепроверил всё в официальной документации и нашёл подтверждение — среди описания тысяч других свойств. Если бы не книга, я бы ни за что не нашёл эту иголку в стоге сена. Это один из главных недостатков Kafka лично для меня. Гибкость — это хорошо, конечно, но нужно же разобраться в куче разных параметров и правильно их настроить, иначе они могут серьёзно нарушить работу системы. Если рядом нет эксперта, придётся несколько дней сидеть над свойствами конфигурации клиента и разбираться, какие из них относятся к конкретной проблеме.

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

В книге очень много примеров на Java и небольших фрагментов конфигурации. Примеры поддержаны репозиторием GitHub, поэтому писать код не придётся. Все примеры рабочие, я проверял! Если вы не работаете с Java, наверное, примеры можно как-то адаптировать на другие языки, но я не пробовал.
Книга подойдёт не только для разработчиков, но и для команды эксплуатации. В ней подробно описана конфигурация и управление кластером, а ещё контейнеризация и безопасность. Часть про безопасность, пожалуй, здесь лучше, чем в любой другой книге.

Effective Kafka — это с большим отрывом лучшая книга про Apache Kafka. Она подходит всем — от самых зелёных новичков до тех, кто мнит себя экспертом по теме. (Просто поверьте.) Думаю, лучшее в книге, — это не технические детали, а способ передачи мыслей. Эмиль хорошо разбирается в нескольких областях, и эта книга — кладезь знаний, а не просто источник информации. Мне очень понравились разделы про liveness и safety, хоть я и не сразу понял, причём тут Kafka. Очень даже причём. Иногда мы сами не понимаем, какие знания нам нужны, и они приходят с неожиданной стороны. Погуглите «free ebook on kafka pdf» и обязательно что-нибудь найдёте. Но не факт, что это будет что-то полезное.

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

Kafka: The Definitive Guide

Авторы: Неха Наркеде (Neha Narkhede)Гвен Шапира (Gwen Shapira) и Тодд Палино (Todd Palino).

Книга есть на русском — прим. пер.

The Definitive Guide — первая книга, которую я прочитал о Kafka. Когда мы переходили с ActiveMQ на Kafka, было много неразберихи, и компания купила несколько экземпляров The Definitive Guide, чтобы помочь нам понять, что к чему. (Но больше для того, чтобы успокоить разработчиков, которые так любят жаловаться на новые технологии, навязанные им сверху.) Хорошо, что мы начали именно с этой книги.

В то время большинство разработчиков использовало брокер MQ на JMS, и само понятие стриминга событий было нам чуждо. Нам казалось, что это слишком мудрёная система. Партицииофсетыгруппы консьюмеров, строгая последовательность, at least once delivery… Зачем нужна вся эта ерунда, когда мы годами обходились топиками и очередями? Тогда никто не понимал, чем события отличаются от команд, и хотя событийно-ориентированная архитектура не была новинкой, её вряд ли можно было назвать мейнстримом. Тогда большинство микросервисов использовало REST, а крутые ребята баловались с gRPC, Kafka, Kubernetes и монорепозиториями.

Kafka не просто платформа. Это конкретное проявление идеологии использования событий. Мне кажется, документация не разъясняет этот момент. Там максимально точно описаны технические детали, но ты недоумеваешь, зачем все это нужно. Это только моё мнение, но мне кажется, что типичный разработчик заглядывает в документацию по Kafka, просто чтобы узнать, как заставить её работать так же, как ActiveMQ. Это совершенно неправильный подход, но так происходит чаще, чем думают мейнтейнеры Kafka. Мне кажется, именно поэтому Kafka разделила всех на два полюса: ее сторонники действительно понимали, зачем нужна строгая последовательность и at least once delivery, а остальные просто искали улучшенную версию RabbitMQ. Если честно, я тоже сначала примкнул к последним.

А потом прочёл Kafka: The Definitive Guide. Авторы очень доступно объясняют сложные концепции, показывая не только как, но и почему. В книге описывается контекст и проводится разделение между вариантами, где нужна платформа потоковой обработки событий, и сценариями, где лучше подходят очереди сообщений.

Помню, сначала подумал, что это будет скорее маркетинг, тем более что два из трёх авторов работали в Confluent. К счастью, я ошибся. Там упоминаются некоторые опенсорс-проекты Confluent, и это объяснимо. Так же было с Kafdrop в Effective Kafka. В целом, написано объективно и беспристрастно. Здесь хорошо объясняются базовые темы, можно читать без подготовки.

Сейчас я полагаюсь на Effective Kafka и давно не заглядываю в The Definitive Guide, но для объективности этого обзора перечитал её заново. Мне по-прежнему понравилось. Отличная книга адекватного размера и по адекватной цене. Кстати, The Definitive Guide вышла раньше, чем Effective Kafka.

В первых главах The Definitive Guide разъясняются ключевые понятия, а не конкретные детали и примеры. Такой же подход используется в Effective Kafka. У вас есть шанс сначала разобраться, с чем вы имеете дело. Эта книга дает знания, а не инструкции!

Сравнение The Definitive Guide и Effective Kafka

  • The Definitive Guide описывает технические детали платформы, а Effective Kafka ещё рассказывает об архитектуре и лучших практиках, а также разъясняет некоторые концепции распределённых вычислений.

  • В Effective Kafka описаны основные опенсорс-компоненты платформы — Kafka и ZooKeeper. The Definitive Guide также рассказывает о продуктах Confluent, например, Replicator и Schema Registry.

  • The Definitive Guide гораздо короче, чем Effective Kafka — 280 страниц против 460. Я не знал, что она такая короткая, пока не перечитал. Если это важно, The Definitive Guide вы прочтёте быстрее.

  • The Definitive Guide подойдёт, скорее, для начального и среднего уровня. Effective Kafka хороша как для новичков, так и для настоящих спецов.

  • В Effective Kafka гораздо подробнее описаны топологии развёртывания и сети, включая даже Docker и Kubernetes. The Definitive Guide вышла на три года раньше, чем Effective Kafka. Возможно, поэтому там ничего не сказано о Docker и Kubernetes.

  • В обеих книгах достаточно примеров на Java. Effective Kafka описывает больше API продюсеров и консьюмеров и ссылается на репозиторий GitHub.

  • Effective Kafka описывает больше инструментов — не только CLI, которые поставляются с Kafka, но и сторонние веб-интерфейсы, например, Kafdrop. Что неудивительно, ведь автор — мейнтейнер Kafdrop.

The Definitive Kafka короче, поэтому в ней меньше информации. Там совсем или почти не описаны некоторые темы, которые рассматриваются в Effective Kafka, например:

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

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

  • Транзакции — книга написана до распространения транзакций Kafka. В Effective Kafka этой теме посвящена целая глава.

  • Exactly-once delivery  — упоминается в контексте идемпотентных операций записи, но не раскрыта полностью без обсуждения транзакций.

  • Устойчивость к потере данных, например, повторные попытки, недоставленные сообщения, таймауты и т. д., рассматривается недостаточно подробно. В Effective Kafka, например, сбоям посвящена целая глава. По опыту могу сказать, что в реальным системах с Kafka об этом аспекте часто забывают.

  • Про advertised.listeners ни слова. Это плохо, ведь о них следует знать каждому уважающему себя разработчику и администратору. Думаю, отчасти это связано с тем, как Kafka обычно развёртывали в 2017 году — на виртуальных машинах или bare metal. Может быть, тогда было не так актуально настраивать несколько прослушивателей, но сейчас всё поменялось. Сегодня большинство разработчиков используют Kafka и ZooKeeper локально с Docker Compose.

  • Квоты упоминаются только в контексте мониторинга. В общей инфраструктуре Kafka в средних и крупных организациях без квот не обойтись. В Effective Kafka квотам посвящена целая глава, и не зря.

  • Безопасность в The Definitive Guide встречается в трех абзацах. Там едва упоминается SASL, главный протокол аутентификации брокера. Сертификаты и списки контроля доступа (ACL) не упоминаются вовсе. В Effective Kafka этой теме уделяется — внимание! – 80 страниц. Представляете? Прямо книга внутри книги. И там только полезная информация.

  • Стратегия назначения партиций в The Definitive Guide рассматривается кратко — только различия между RoundRobinAssignor и RangeAssignor. В Effective Kafka подробно описаны все четыре метода назначения, с примерами и сравнениями.

В The Definitive Guide тоже есть несколько тем, которые не затрагиваются в Effective Kafka:

  • Сборка мусора: удивительно, но в The Definitive Guide рассказывается о настройке производительности сборки мусора, а в Effective Kafka — нет. Правда, вместо G1 в больших кучах теперь используется ZGC, но кое-что полезное можно узнать.

  • О мониторинге JMX в Effective Kafka ничего не говорится. В The Definitive Guide этой теме уделена пара абзацев.

Есть незначительные отличия в терминологии. Для консьюмеров, которые не связаны с группой консьюмеров, в The Definitive Guide используется термин standalone, а в Effective Kafka — free. Главный цикл, который делает опрос для записей на консьюмере и обрабатывает получившийся батч, называется poll-process loop в Effective Kafka и просто poll loop — в The Definitive Guide.

В целом, Effective Kafka может решить почти любые вопросы — не придется обращаться к сторонней документации, блогам, KIP или другим книгам. Официальная документация пригодится для справки, но она не особо нужна. В The Definitive Guide, с другой стороны, даётся общий обзор среднего уровня, а недостающие детали можно найти в официальной документации. Единственное, чего мне не хватает в Effective Kafka, — оглавления.

Если сравнивать две книги, видно, сколько усилий было вложено в Effective KafkaThe Definitive Guide хорошая книга, но понемногу устаревает. Было бы здорово, если бы вышло второе издание. В Effective Kafka гораздо больше очень важных деталей. Она подробнее, чем The Definitive Guide и даже официальная документация. Автор говорит, что узнал все это из анализа исходного кода Kafka, истории коммитов и изучения Kafka Improvement Proposals (KIP). Даже не представляю, сколько работы пришлось проделать. Описание архитектуры в Effective Kafka никогда не потеряет актуальности, потому что относится к любой технологии. Effective Kafka станет классикой.

Если бы я писал эту статью в 2019 году, то назвал бы The Definitive Guide лучшей книгой о Kafka. Но Effective Kafka и еще пара книг полностью изменили мой взгляд на то, какой должна быть книга. Это невероятный источник мудрости и знаний. При этом The Definitive Guide очень хорошо читается и занимает достойное место на моей книжной полке. Я купил себе домой бумажный экземпляр, хотя у нас их было несколько на работе. Выражаю огромную благодарность авторам за то, что помогли мне познакомиться с этой потрясающей платформой и вложили столько энергии в её создание. У меня есть бумажный экземпляр Effective Kafka, и я надеюсь, что однажды автор его подпишет.

Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems

Автор: Мартин Клеппман (Martin Kleppmann)

Не удивляйтесь, но третья книга в моем списке вообще не о Kafka. Я уже представил две отличные книги на эту тему, третья была бы лишней. Может быть, на это стоило бы закончить список? Как я уже говорил, иногда мы сами не понимаем, какие знания нам нужны, и они приходят с неожиданной стороны.

Зачем проектировать приложения в рамках событийно-ориентированной архитектуры? Какие еще паттерны и принципы стоит рассмотреть? Как добиться согласованности в распределённой системе? Как упорядочивать и хранить данные? Какие сбои нужно предусмотреть? Kafka — это всего один компонент распределённой архитектуры. Недостаточно быть экспертом по Kafka, чтобы ориентироваться в современном сложном мире разработки.

Designing Data-Intensive Applications The big ideas behind reliable, scalable & maintainable systems (Проектирование высоконагруженных приложений. Что помогает обеспечивать надежность, масштабируемость и поддержку систем)

Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems («Высоконагруженные приложения. Программирование, масштабирование, поддержка») — это ещё один литературный шедевр, который украсит любую книжную полку и долго не потеряет актуальности. В книге рассматриваются три главных принципа проектирования приложений: надежность, масштабируемость и простота поддержки.

По названию можно подумать, что это очередная книга о биг дате. Это не так. Designing Data-Intensive Applications — книга об архитектуре распределённых систем. Её единственный недостаток — название, которое вводит в заблуждение. Здесь бы лучше смотрелось что-то вроде «Проектирование надежных и масштабируемых распределённых приложений». Правда, слово data в названии упоминается не зря — зачем нам распределённые системы без данных?

Маленькое примечание: книга не о Kafka, но она там упоминается. Автор не новичок в Kafka — Мартин был senior-разработчиком в LinkedIn (в инкубаторе которого зародилась Kafka), и он один из мейнтейнеров Apache Samza — фреймворка в стиле MapReduce для распределённой обработки больших потоков событий, где Kafka служит для передачи сообщений.

К чему я клоню? Некоторые книги делят нашу жизнь на «до» и «после». Мы отказываемся от прошлых убеждений и обретаем новые. У меня так и было, когда я прочел Designing Data-Intensive Applications в 2018. А потом в 2020, уже с Effective Kafka. У авторов схожий стиль изложения, вплоть до грамматических конструкций. Они используют сноски и примечания, чтобы добавить что-то интересное и забавное, и от этого книги читаются ещё лучше. Они подбирают превосходные аналогии. Я смеялся, когда Эмиль проводил параллель между криминологией XVIII века и презумпцией невиновности с одной стороны и свойствами safety и liveness в распределённых системах — с другой. А миграции схем баз данных он сравнивает с изменением ширины колеи в XIX веке. Мне особенно понравилось, как Мартин вспомнил о мейнфреймах 60-х и 70-х годов, показывая, что история повторяется. Обе книги очень хорошо написаны, нигде не заметно, что глава писалась второпях, в погоне за дедлайнами издателя. Подсознательно я стараюсь копировать этот стиль. И даже не стесняюсь этого.

В Designing Data-Intensive Applications Мартину удалось кратко (хоть книга и получилась объемной) изложить свои превосходные исследования. Основные темы книги:

  • Модели данных (реляционные, ключ-значение, граф и т. д.), структуры данных и индексы.

  • Модели сохраняемости (B-деревья, LSM).

  • Форматы данных (JSON, XML, Avro, Thrift, Protobuf и т. д.). Кстати, Мартин — один из мейнтейнеров Avro.

  • Репликация — лидер-фоловер, отставание, репликация с несколькими лидерами, конфликты записи, кворумная репликация.

  • Транзакции — уровни изоляции, перекос при чтении и записи, механизмы контроля параллелизма (последовательный, пессимистичный и оптимистичный).

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

  • Распределённые алгоритмы, модели согласованности, порядок, распределённые транзакции и консенсус.

  • Обработка батчей, MapReduce и обработка потоков.

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

Эта книга не научит вас обращаться с Kafka — для этого есть Effective Kafka. И её определённо нельзя прочитать за один присест. Читайте вдумчиво, не торопясь. Эта книга изменит ваш взгляд на распределённые системы и подход к работе. Сколько заблуждений у меня было, пока я не прочёл книгу Мартина! Вот некоторые из не самых постыдных:

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

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

  • Я всегда связывал распределённые системы с микросервисами и считал их шагом вперед по сравнению с монолитами. Я не думал о том, как сложно устранять сбои, если упала только часть системы. Просто подожди и попробуй снова. Что тут сложного?

Знания, которые дает Designing Data-Intensive Applications, никогда не устареют. Это шедевр технической литературы, который станет классикой.

Что еще почитать про Apache Kafka?

Я, конечно, читал и другие книги о Kafka. Могу ли я ещё что-то порекомендовать. Извините, но нет.

Не хочу говорить ничего плохого об авторах, ведь написание книги требует огромных усилий, даже если книга потом не оправдала ожидания читателей. Я прочёл много книг о популярных опенсорс-технологиях, в основном, издательств Safari, Manning и Packt, и выделил для себя два типа авторов. Первые прекрасно разбираются в теме, очень увлечены ей и жаждут поделиться знаниями. Они уже признанные эксперты, им не нужно писать книгу, чтобы это доказывать. Второй тип — «карьеристы». Они неплохо разбираются в теме, но книга им нужна для статуса и строчки в резюме.

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

Полностью согласен! Я почувствовал то же самое. Сложно поверить, что кто-то пишет такое всерьез. Вот реальный дословный отрывок:

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

Для Hadoop нужен Hadoop? Да ладно? В общем, прежде чем купить книгу, убедитесь, что автор разбирается в теме и реально работает с проектами. Кроме книг, о которых я рассказал, я ничего достойного больше не находил. Если вам что-то попадётся, пишите!

В целом, можно начать работу с Kafka, изучив официальную документацию и почитав популярные блоги. Но учтите: Kafka — комплексная платформа со множеством вариантов использования и тысячами вариантов конфигурации. Разобраться во всем этом настолько сложно, что даже опытные специалисты по Kafka, бывает, ошибаются. Мне ли не знать. Блоги обычно пишут люди, которые разбираются в теме чуть больше, чем обычный человек. Они не всегда правы. Говорю по своему опыту. Официальная документация полезна для справки, но точно не как источник мудрости.

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

Комментарии (3)


  1. Color
    27.01.2022 16:06
    +4

    Designing Data-Intensive Applications — книга об архитектуре распределённых систем. Её единственный недостаток — название, которое вводит в заблуждение

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

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


    1. Myclass
      27.01.2022 16:44
      +2

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


    1. GraDea
      27.01.2022 18:29
      +5

      А правильное название: книга с кабанчиком)