DevOps | Вопросы собесов
5.57K subscribers
35 photos
1.53K links
Сайт: https://easyoffer.ru/
Все каналы: t.me/+xGeAw6ckJ4liYzQy

Контакт для рекламы: @sendme_ads
Download Telegram
🤔 Какие минусы Stateful-приложения?

- Сложность масштабирования (не масштабируются горизонтально так же просто, как Stateless);
- Требуют постоянного хранения состояния (например, базы данных);
- Требуют использования PersistentVolume, что усложняет деплой в Kubernetes;
- Трудности с отказоустойчивостью и миграциями данных.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍1
🤔 В чем разница Deployment и DaemonSet?

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

🚩Deployment

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

🟠Масштабирование
Вы можете увеличивать или уменьшать количество подов в зависимости от нужд.
🟠Обновления
Поддерживает стратегии развертывания, такие как Rolling Update (постепенное обновление), которое помогает минимизировать простои при обновлении приложения.
🟠Самовосстановление
Автоматически перезапускает поды, которые перестали работать, находятся в ошибочном состоянии или не отвечают.

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80


🚩DaemonSet


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

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

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: fluent/fluentd:v1.0
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true


Ставь 👍 и забирай 📚 Базу знаний
👍5
🤔 Как установить msodbcsql на Astra?

Пакет msodbcsql — это драйвер ODBC от Microsoft для SQL Server. Его нет в стандартных репах Astra.
1. Добавь репозиторий Microsoft:
- Установи зависимости: unixODBC, curl, gnupg2.
- Импортируй ключ и добавь репо:
- curl
https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
- echo "deb [arch=amd64]
https://packages.microsoft.com/debian/10/prod buster main" | sudo tee /etc/apt/sources.list.d/msprod.list
2. Установи:
3. sudo apt update
4. sudo apt install msodbcsql17
Примечание: требуется совместимость с Debian-базой, так что могут понадобиться обходные пути.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🤔 Чем отличается образ от контейнера?

Образ (Image) – это шаблон, из которого создаются контейнеры.
Контейнер (Container) – это запущенный экземпляр образа.

🚩Что такое образ (Image)?

Образ – это неизменяемый шаблон с установленным ПО
Содержит все файлы, библиотеки и зависимости
Может храниться в реестре (Docker Hub, GitHub Container Registry, ECR)
docker pull nginx:latest



Пример списка образов
docker images


Пример создания образа через Dockerfile
FROM ubuntu:latest
RUN apt update && apt install -y nginx
CMD ["nginx", "-g", "daemon off;"]

docker build -t my-nginx .



🚩Что такое контейнер (Container)?

Контейнер – это запущенный экземпляр образа
Может изменять файлы и состояние, но это не сохраняется в образе
Изолирован, но использует ядро ОС хоста
docker run -d --name web -p 80:80 nginx


Пример списка контейнеров
docker ps


Пример остановки контейнера
docker stop web


Ставь 👍 и забирай 📚 Базу знаний
👍2
🤔 Что помимо bash sh можно в шебанг написать без проблем?

Можно использовать другие интерпретаторы, такие как zsh, python, perl, env, и т.д., в зависимости от того, на каком языке написан скрипт. Это делает скрипты переносимыми или привязывает их к конкретной среде.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍1💊1
🤔 Как бы построить выделение ресурсов, балансировку, нагрузку и всю масштабированность?

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

🚩Основные компоненты

Выделение ресурсов - CPU, RAM, диски, сеть
Балансировка нагрузки равномерное распределение трафика
Горизонтальное и вертикальное масштабирование
Авто-масштабировани – динамическое добавление/удаление мощностей

🟠Выделение ресурсов (CPU, RAM, Диск, Сеть)
В виртуализированных средах (Kubernetes, Docker, AWS, KVM, ESXi) выделение ресурсов настраивается через лимиты и квоты.
yaml  
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: "500m" # Минимально 0.5 CPU
memory: "256Mi" # Минимально 256MB RAM
limits:
cpu: "1000m" # Максимально 1 CPU
memory: "512Mi" # Максимально 512MB RAM


🟠Балансировка нагрузки
Балансировка уменьшает нагрузку на один сервер и равномерно распределяет запросы.
nginx  
upstream backend {
server app1:5000;
server app2:5000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}


Пример терраформа для AWS ALB
hcl  
resource "aws_lb" "example" {
name = "my-load-balancer"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.lb_sg.id]
}


🟠Масштабирование (Горизонтальное и Вертикальное)
Горизонтальное масштабирование (добавление новых инстансов)
Kubernetes Horizontal Pod Autoscaler (HPA)
yaml  
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70


🟠Авто-масштабирование (AWS/GCP/Kubernetes)
AWS Auto Scaling Group
hcl  
resource "aws_autoscaling_group" "example" {
min_size = 2
max_size = 10
desired_capacity = 2
launch_configuration = aws_launch_configuration.example.name
}


Ставь 👍 и забирай 📚 Базу знаний
🔥3
🤔 Почему если поставить Docker и создать образ, он может не запускаться, в чём может быть причина?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🔥2
🤔 По какой причине inode могут закончиться?

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

🚩Причины исчерпания

🟠Большое количество мелких файлов
Одной из наиболее частых причин исчерпания inode является наличие очень большого количества маленьких файлов на файловой системе. Поскольку каждый файл использует как минимум один inode, системы с большим количеством мелких файлов могут исчерпать доступные inode, даже если дисковое пространство по-прежнему доступно.

🟠Недостаточное количество выделенных inode
При создании файловой системы, если количество выделенных inode было рассчитано неправильно (слишком мало для предполагаемого использования), это может привести к раннему их исчерпанию. Это особенно актуально для серверов или систем, где ожидается большое количество файлов.

🟠Особенности файловой системы
Некоторые файловые системы, такие как Ext3 или Ext4, имеют фиксированное соотношение inode к объёму файловой системы, которое задаётся при их создании. Если создать файловую систему с недостаточным количеством inode для конкретного случая использования, то в дальнейшем это может стать проблемой.

🚩Решения проблемы исчерпания

🟠Проверка использования
С помощью команды df -i можно проверить, сколько inode используется и сколько ещё доступно в вашей файловой системе.

🟠Очистка файловой системы
Удаление ненужных или временных файлов может освободить inode.

🟠Изменение файловой системы
Если возможно, можно увеличить количество inode путём изменения файловой системы или пересоздания файловой системы с более высоким количеством inode. Для файловых систем, таких как XFS или некоторые конфигурации Btrfs, можно динамически добавлять inode.

🟠Использование других файловых систем
Переход на другую файловую систему, которая не имеет строгих ограничений на количество inode (например, Btrfs или ZFS), может быть решением для систем с большим количеством маленьких файлов.

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

Ставь 👍 и забирай 📚 Базу знаний
👍1
🤔 Как прикрутить логирование к любому питон-скрипту?

Добавить в начало
Можно заменить print на logging.debug(), info(), warning() и т.д.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🔥1💊1
🤔 Что такое debezium?

Она позволяет отслеживать изменения в базах данных (INSERT, UPDATE, DELETE) в реальном времени и передавать их в Kafka, Elasticsearch, MongoDB и другие системы.

🚩Как работает Debezium?

Подключается к базе данных
(PostgreSQL, MySQL, MongoDB, Oracle и др.).
Слушает лог изменений (binlog, WAL, oplog и т. д.)
Формирует события в формате JSON
Передаёт их в Kafka или другую шину данных.

🚩Где используется?

Синхронизация данных между базами
Репликация данных в реальном времени
Отправка изменений в аналитические системы (Elasticsearch, ClickHouse)
Аудит и логирование изменений

🟠Пример работы с Kafka
Запускаем Debezium Connector для PostgreSQL*
{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "localhost",
"database.port": "5432",
"database.user": "debezium",
"database.password": "dbz",
"database.dbname": "inventory",
"database.server.name": "dbserver1"
}
}


При изменении данных в таблице, Kafka получит событие:
{
"schema": { ... },
"payload": {
"before": { "id": 1, "name": "Old Name" },
"after": { "id": 1, "name": "New Name" },
"op": "u" // Update
}
}


Ставь 👍 и забирай 📚 Базу знаний
👍2
🤔 Можно ли откатить деплоймент?

Да. Kubernetes поддерживает откат на предыдущую ревизию:
- С помощью команды kubectl rollout undo deployment <имя>.
- Можно указать конкретную ревизию, если нужно вернуться дальше.
Это позволяет безопасно вернуться к предыдущей стабильной версии.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🤔 Какие виды метрик в Prometheus знаешь?

В Prometheus есть 4 типа метрик, которые используются для мониторинга и сбора данных.

🚩Counter (Счетчик) – только увеличивается

Используется для подсчета событий, ошибок, запросов
Значение только увеличивается (нельзя уменьшить)
promql  
http_requests_total


Как вывести количество запросов за последние 5 минут
promql  
rate(http_requests_total[5m])


Пример экспозиции в приложении
go  
httpRequests := prometheus.NewCounter(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
})



🚩Gauge (Измеритель) – может расти и уменьшаться

Используется для измерения текущего состояния (температура, RAM, CPU)
Значение может расти и уменьшаться
promql  
node_memory_usage_bytes


Как вывести среднюю загрузку CPU за 5 минут
promql  
avg(node_cpu_seconds_total)



Пример экспозиции в коде
go  
cpuUsage := prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "cpu_usage_percentage",
Help: "Current CPU usage",
})



🚩Histogram (Гистограмма) – распределение значений

Используется для измерения длительности, размера запросов
Делит данные по диапазонам (buckets)
promql  
http_request_duration_seconds_bucket


Как вычислить среднее время отклика
promql  
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))


Пример в коде
go  
requestDuration := prometheus.NewHistogram(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Histogram of response time for HTTP requests",
Buckets: prometheus.DefBuckets,
})



🚩Summary (Сводка) – квантильные метрики

Показывает среднее, медиану, квантильное распределение
Используется для измерения задержек, времени отклика
promql  
http_request_duration_seconds{quantile="0.99"}


Пример в коде:
go  
requestSummary := prometheus.NewSummary(
prometheus.SummaryOpts{
Name: "http_request_duration_seconds",
Help: "Summary of response time for HTTP requests",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})


Ставь 👍 и забирай 📚 Базу знаний
🤔1💊1
🤔 Как отличается работа с PersistentVolume у Deployment и StatefulSet?

Deployment может использовать общий volume или один PVC для всех подов. В StatefulSet у каждого пода есть свой отдельный PVC, созданный на основе шаблона. Эти PVC жёстко связаны с именами подов и сохраняются даже после удаления подов. Это позволяет сохранять состояние у каждого инстанса.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🤔 Что такое cherry pick?

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

🚩Когда используется

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

🚩Примеры использования

🟠Простого `cherry-pick`
Перенос одного коммита из другой ветки.
# Переключитесь на ветку, куда вы хотите применить изменения
git checkout target-branch

# Выполните cherry-pick коммита
git cherry-pick <commit-hash>


🟠Применения нескольких коммитов
Перенос нескольких коммитов из другой ветки.
# Переключитесь на ветку, куда вы хотите применить изменения
git checkout target-branch

# Выполните cherry-pick нескольких коммитов
git cherry-pick <commit-hash-1> <commit-hash-2> <commit-hash-3>


🟠Применения диапазона коммитов
Перенос диапазона коммитов из другой ветки.
# Переключитесь на ветку, куда вы хотите применить изменения
git checkout target-branch

# Выполните cherry-pick диапазона коммитов
git cherry-pick <start-commit-hash>..<end-commit-hash>


🚩Разрешение конфликтов

1⃣Разрешите конфликты в файлах.

2⃣Добавьте изменения в индекс
git add <filename>   


3⃣Завершите процесс cherry-pick
git cherry-pick --continue   


🚩Прерывание


Если вы хотите прервать процесс cherry-pick, можно использовать команду
git cherry-pick --abort


Ставь 👍 и забирай 📚 Базу знаний
👍2
🤔 Как доставлять билд в окружение?

Билд доставляется в окружение через CI/CD пайплайн. Этапы:
1. Сборка (build) — компиляция и упаковка артефакта.
2. Тестирование — юнит, интеграционные, e2e.
3. Артефакт хранится в реестре (например, Docker Registry, Nexus, Artifactory).
4. Деплой — с помощью:
- kubectl, Helm (в Kubernetes);
- Ansible, Terraform;
- скриптов или GitLab/GitHub Actions.
Процесс автоматизируется, чтобы исключить ошибки и ускорить доставку.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🤔 Что такое basic auth?

Basic Auth (Basic Authentication) – это простой метод аутентификации HTTP-запросов.
Он используется для ограничения доступа к ресурсам, отправляя логин и пароль в заголовке запроса.

🟠Как работает Basic Auth?
Клиент делает запрос к серверу.
Сервер отвечает 401 Unauthorized и требует аутентификацию:

   HTTP/1.1 401 Unauthorized  
WWW-Authenticate: Basic realm="Restricted Area"


Клиент кодирует логин и пароль в Base64 и отправляет их в заголовке:

   Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=


🟠Пример запроса с Basic Auth
В curl
curl -u username:password http://example.com/protected


В Python (requests):
import requests
from requests.auth import HTTPBasicAuth

response = requests.get("http://example.com/protected", auth=HTTPBasicAuth("username", "password"))
print(response.text)


В JavaScript (fetch):
fetch("http://example.com/protected", {
headers: {
"Authorization": "Basic " + btoa("username:password")
}
})
.then(response => response.text())
.then(data => console.log(data));


🚩Минусы

➖Не безопасен без HTTPS
данные передаются в открытом виде (Base64 — это не шифрование!).
➖Логин и пароль передаются в каждом запросе
что делает систему уязвимой.
➖Нет механизма разлогинивания
браузеры кэшируют креды, и их сложно сбросить.

Ставь 👍 и забирай 📚 Базу знаний
👍1
🤔 Чем отличаются Secrets и ConfigMaps?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍2
🔥 Скрытые вакансии с удаленной работой для DevOps, которые нигде больше не публикуются. Напрямую от компаний, с контактами рекрутеров.

🔹 DevOps Jobs | Вакансии

🔸 Все каналы с вакансиями
🤔 Как заставить процесс отпустить файловые дескрипторы?

Если процесс удерживает файловые дескрипторы и вам нужно их освободить, то есть несколько способов это сделать.

🟠Завершение процесса
Самый простой способ — завершить процесс, который удерживает файловые дескрипторы.
kill <PID>


или принудительно
kill -9 <PID>


Чтобы найти процессы, удерживающие файлы
lsof | grep <filename>


или
fuser <filename>


🟠Закрытие дескриптора вручную
Если вы хотите закрыть файловый дескриптор, не завершая процесс, можно использовать lsof и /proc

1⃣Найдите открытые дескрипторы процесса:
ls -l /proc/<PID>/fd/


2⃣Закройте конкретный дескриптор:
exec 3>&-


🟠Использование `gdb` для вмешательства в процесс
Если процесс нельзя перезапустить, но вы имеете к нему доступ, можно закрыть дескриптор через gdb:
gdb -p <PID>
(gdb) call close(<FD>)
(gdb) detach
(gdb) quit


🟠Перемонтирование файловой системы
Если файл удален, но все еще удерживается процессом, можно принудительно перемонтировать файловую систему:
mount -o remount /


Или использовать lsof для очистки удаленных файлов
lsof | grep deleted


🟠Использование `systemctl restart` для сервисов
Если файлы удерживаются службой (например, Nginx, Apache, MySQL), можно перезапустить службу
systemctl restart <service>


или
service <service> restart


Ставь 👍 и забирай 📚 Базу знаний
🤔 Динамическая маршрутизация и разные протоколы динамической маршрутизации, eBGP, iBGP, константы BGP. На чём сейчас принято строить BGP-пиринги?

Это основной протокол для маршрутизации в интернете.
- eBGP (external BGP) – используется для связи между автономными системами (AS).
- iBGP (internal BGP) – применяется внутри одной автономной системы.
Константы BGP:
- AS_PATH – список пройденных автономных систем.
- MED (Multi-Exit Discriminator) – предпочтение пути.
- LOCAL_PREF – приоритет маршрута внутри AS.
Сейчас BGP-пиринги строятся на базе FRRouting (FRR), Bird, ExaBGP, Cisco IOS, Juniper JunOS.
Для балансировки используются Route Reflectors и ECMP (Equal-Cost Multi-Path Routing).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
🔥2💊1
🤔 Почему плохо запускать контейнер от рута?

Запуск контейнеров от имени пользователя root (рута) в Docker является обычной практикой, но это может привести к серьезным проблемам безопасности. Вот основные причины, почему это считается плохой практикой:

🟠Повышение рисков безопасности:
Эксплуатация уязвимостей: Если злоумышленник получает доступ к контейнеру, запущенному от имени root, он может легко использовать уязвимости контейнера для атаки на хост-систему.
Злоумышленники: Контейнеры могут содержать уязвимые или злонамеренные коды, которые при запуске с привилегиями root могут получить доступ к чувствительной информации или вызвать сбои.

🟠Отсутствие изоляции:
Гостевая изоляция: Контейнеры должны быть изолированы от хост-системы. Запуск контейнера от имени root нарушает эту изоляцию, так как root внутри контейнера — это также root на хосте.
Повышенные привилегии: Контейнеры, запущенные от имени root, могут иметь доступ к системным ресурсам, что увеличивает риск нарушения безопасности.

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

🚩Примеры проблем

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

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

🚩Как избежать запуска контейнеров от рута

Использование непривилегированных пользователей:
В Dockerfile можно создать пользователя с ограниченными привилегиями и переключиться на него.
FROM ubuntu:20.04
RUN useradd -m myuser
USER myuser
CMD ["myapp"]


Использование флага --user:
При запуске контейнера можно использовать флаг --user, чтобы указать непривилегированного пользователя.
docker run --user 1000:1000 myapp


Использование механизмов безопасности Docker:
Использование профилей seccomp для ограничения системных вызовов.
Использование AppArmor или SELinux для ограничения доступа контейнеров к системным ресурсам.

Ставь 👍 и забирай 📚 Базу знаний
👍1