Это программные системы, которые позволяют обмениваться данными между разными компонентами приложения или между различными приложениями. Они действуют как посредники, принимая сообщения от отправителей (producers) и доставляя их получателям (consumers).
Сообщения отправляются от одного компонента и доставляются нужному получателю. Брокер определяет, куда отправить сообщение, используя темы (topics), очереди (queues) или маршруты (routing keys).
Отправитель может передать сообщение, не дожидаясь его обработки, что повышает производительность системы.
Если получатель временно недоступен, сообщение сохраняется в очереди до тех пор, пока оно не будет доставлено.
Некоторые брокеры предоставляют механизмы подтверждения получения сообщений (acknowledgment), чтобы избежать их потери.
Сообщения могут быть обработаны несколькими получателями, что позволяет распределить нагрузку между ними.
Сообщения доставляются только тем потребителям, которые их ожидают, используя фильтры или ключи маршрутизации.
Компоненты приложения обмениваются данными через брокер, что позволяет им оставаться изолированными и независимыми.
Сбор логов и метрик от множества источников с их дальнейшей обработкой.
Постановка задач в очередь для выполнения одним или несколькими воркерами.
Связывание разнородных систем, которые обмениваются данными.
Протокол: AMQP (Advanced Message Queuing Protocol). Поддерживает очереди, маршрутизацию, подтверждения доставки. Хорошо подходит для сложных сценариев с разными типами маршрутизации.
Протокол: Проприетарный. Отличается высокой производительностью и надежностью. Используется для потоковой обработки данных, аналитики в реальном времени.
Протокол: Redis. Простая и быстрая модель pub/sub. Хорошо подходит для временных сообщений без сохранения состояния.
Протокол: AMQP, STOMP, MQTT. Гибкий и совместимый с различными сценариями.
Легковесный и быстрый брокер для приложений, требующих низкой задержки.
Producers (отправители) отправляют сообщение в брокер.
Брокер размещает сообщение в соответствующей очереди или теме.
Consumers (получатели) получают сообщение: Либо сразу, если они активны. Либо позже, если оно сохраняется в очереди.
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
connection.close()
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(f"Received {body}")
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
channel.start_consuming()
Компоненты системы сосредотачиваются на своих задачах, а не на доставке данных.
Легко добавлять новых потребителей или отправителей.
Брокеры обеспечивают сохранность сообщений, даже если один из компонентов временно недоступен.
Возможность использовать различные стратегии маршрутизации и обработки данных.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Это значит, что контейнер падает и перезапускается:
- Ошибка внутри приложения (не хватает переменной, зависимостей, крэш при запуске).
- Неправильный command или entrypoint.
- Отсутствие подключения к БД или внешнему сервису.
- Недостаточно ресурсов (kill по памяти).
- Проблемы с томами (доступ, монтирование).
Стоит смотреть logs и describe pod для диагностики.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Это понятия из Kubernetes, которые используются для управления хранилищем данных (storage). Они решают задачу обеспечения долгоживущего хранилища для приложений, которое сохраняется независимо от жизненного цикла подов (Pods).
Это объект Kubernetes, который представляет физическое хранилище, доступное в кластере. Это может быть локальное хранилище, сетевой диск (например, NFS), или облачный сервис хранения (например, Amazon EBS, Google Persistent Disk, Azure Disk).
PV создается администратором или автоматически через StorageClass. Это ресурс хранилища, выделенный для использования в кластере. Независим от подов, что позволяет хранить данные, даже если под был удален или перезапущен.
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: "/mnt/data"
Это запрос на использование хранилища (PV) со стороны пользователя или приложения. Это как "заявка" на нужное количество места и определенный тип хранилища.
PVC создается разработчиком или приложением. PVC связывается с подходящим PV, который соответствует запросу. Если подходящий PV найден, он "привязывается" к PVC.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Администратор или динамическое создание выделяет хранилище (например, сетевой диск).
Приложение запрашивает хранилище с помощью PVC.
Kubernetes автоматически находит подходящий PV, который соответствует запросу PVC (по размерам, режимам доступа и другим параметрам). После этого PVC "связывается" с PV.
PVC указывается в поде как volume, что позволяет контейнерам работать с данным хранилищем.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
volumeMounts:
- mountPath: "/data"
name: my-storage
volumes:
- name: my-storage
persistentVolumeClaim:
claimName: my-pvc
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
2. Настроить ulimit в системных настройках.
3. Обновить параметры ядра через sysctl (например, fs.file-max, net.core.somaxconn).
4. Перезапустить сервис, чтобы изменения вступили в силу.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Динамическая маршрутизация – это механизм автоматического выбора маршрута на основе изменяющихся сетевых условий. В отличие от статической маршрутизации, она автоматически адаптируется к изменениям в сети.
Протоколы динамической маршрутизации делятся на:
Протоколы внутреннего шлюза (IGP – Interior Gateway Protocol)
RIP (Routing Information Protocol) – устаревший, работает на алгоритме дистанционно-векторного маршрутизации.
OSPF (Open Shortest Path First) – основан на SPF (Dijkstra) и работает по принципу линк-стейт.
IS-IS (Intermediate System to Intermediate System) – аналог OSPF, но чаще используется в провайдерских сетях.
Протоколы внешнего шлюза (EGP – Exterior Gateway Protocol)
BGP (Border Gateway Protocol) – основной протокол маршрутизации в интернете.
eBGP vs iBGP
BGP бывает двух типов:
eBGP (External BGP) – маршрутизация между автономными системами (AS). Используется для обмена маршрутами между разными организациями или провайдерами.
iBGP (Internal BGP) – работает внутри одной AS, передавая BGP-маршруты между роутерами этой сети.
BGP использует атрибуты маршрута для выбора наилучшего пути. Основные атрибуты:
приоритетный маршрут внутри AS (чем выше, тем лучше).
список AS, через которые проходит маршрут (короче – лучше).
предпочтение одного из выходов из AS (меньше – лучше).
источник маршрута (
IGP < EGP < Incomplete
). метки маршрутов для упрощения политики маршрутизации.
локальный параметр (чем больше, тем лучше).
BGP можно развернуть на разных платформах, включая аппаратные и программные решения:
Cisco (IOS, IOS-XE, IOS-XR, NX-OS) – классика для крупных сетей.
Juniper (Junos OS) – мощное решение для дата-центров и операторов.
Arista (EOS) – высокопроизводительные маршрутизаторы для ЦОДов.
Huawei (VRP) – популярно в крупных корпоративных сетях.
FRRouting (FRR) – open-source, поддерживает BGP, OSPF, IS-IS.
BIRD – лёгкий BGP-демон для Linux, используется в IX (Internet Exchange).
GoBGP – BGP-демон, написанный на Go.
VyOS – Linux-маршрутизатор с поддержкой BGP.
MikroTik (RouterOS) – бюджетное, но мощное решение.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
ELK (Elasticsearch + Logstash + Kibana) и EFK (Elasticsearch + Fluentd + Kibana) — это стэки для сбора, хранения и визуализации логов. Logstash или Fluentd обрабатывают и отправляют логи в Elasticsearch, где они индексируются и хранятся. Kibana предоставляет UI для поиска, анализа и дашбордов.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
После создания нового репозитория, будь то локальный или удаленный, необходимо выполнить несколько шагов для начала полноценной работы с ним. Эти шаги включают инициализацию репозитория, настройку удаленного репозитория, добавление файлов, коммит и настройку синхронизации с удаленным репозиторием.
Перейдите на GitHub и войдите в свой аккаунт. Нажмите на кнопку "New" для создания нового репозитория. Введите имя репозитория, описание (опционально), выберите публичный или приватный доступ, инициализируйте с README (если нужно). Нажмите "Create repository".
Если репозиторий уже существует на GitHub
git clone https://github.com/yourusername/your-repository.git
cd your-repository
Если вы создаете новый локальный репозиторий
mkdir your-repository
cd your-repository
git init
Создайте или добавьте файлы
echo "# Your Repository" > README.md
Добавьте файлы в индекс
git add README.md
Сделайте первый коммит
git commit -m "Initial commit"
Если вы инициализировали локальный репозиторий, вам нужно настроить удаленный репозиторий.
git remote add origin https://github.com/yourusername/your-repository.git
Отправьте ваши изменения в удаленный репозиторий
git push -u origin master
Для разработки новых функций или исправлений багов рекомендуется создавать отдельные ветки
git checkout -b feature-branch
После завершения работы в ветке создайте Pull Request на GitHub для обзора и слияния.
Добавьте файл
.gitignore
для исключения ненужных файлов из коммитов. echo "node_modules/" > .gitignore
git add .gitignore
git commit -m "Add .gitignore"
Подключите сервисы Continuous Integration/Continuous Deployment, такие как GitHub Actions, Travis CI или Jenkins, для автоматизации тестирования и развертывания.
# Пример файла .github/workflows/ci.yml для GitHub Actions
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- run: npm test
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2👍1
Redis — это in-memory хранилище данных типа ключ-значение. Используется как:
- кэш,
- брокер сообщений (Pub/Sub),
- счётчики,
- очереди,
- сессионное хранилище,
- блокировки и семафоры.
Он хранит данные в памяти, с возможностью сброса на диск (RDB, AOF), что делает его молниеносным.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
AWS предоставляет облачные сервисы для автоматизации CI/CD, управления инфраструктурой, мониторинга и безопасности.
AWS предлагает инструменты для автоматической сборки, тестирования и деплоя.
AWS CodePipeline – автоматизация CI/CD-процессов
AWS CodeBuild – сборка и тестирование кода
AWS CodeDeploy – автоматический деплой в EC2, ECS, Lambda
AWS CodeCommit – репозиторий Git в AWS
Пример CI/CD-пайплайна в AWS CodePipeline
1. CodeCommit получает новый коммит
2. CodeBuild собирает и тестирует код
3. CodeDeploy разворачивает приложение на EC2
В DevOps важно автоматически создавать и управлять ресурсами AWS.
Terraform – создает инфраструктуру по коду
AWS CloudFormation – аналог Terraform от AWS
AWS CDK (Cloud Development Kit) – IaC на Python/TypeScript
hcl
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
}
AWS поддерживает управление контейнерами и Kubernetes.
Amazon ECS (Elastic Container Service) – контейнеры без Kubernetes
Amazon EKS (Elastic Kubernetes Service) – управляемый Kubernetes
AWS Fargate – запуск контейнеров без управления серверами
Пример развертывания контейнера в AWS ECS:
1. Собираем Docker-образ
2. Загружаем его в Amazon ECR (Elastic Container Registry)
3. ECS автоматически масштабирует и управляет контейнерами
Amazon CloudWatch – сбор метрик и логов
AWS X-Ray – трассировка запросов в микросервисах
AWS CloudTrail – аудит действий в AWS
Пример мониторинга EC2
1. CloudWatch собирает метрики CPU, RAM
2. Настраиваем авто-масштабирование на основе этих метрик
3. CloudTrail записывает все изменения инфраструктуры
AWS IAM (Identity and Access Management) – контроль прав
AWS Secrets Manager – управление паролями и API-ключами
AWS KMS (Key Management Service) – шифрование данных
hcl
resource "aws_iam_role" "s3_read" {
name = "s3-read-only"
assume_role_policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::my-bucket/*"
}]
})
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
- Control Plane включает:
- kube-apiserver — входная точка API.
- etcd — хранилище состояния кластера.
- controller-manager — следит за desired state.
- scheduler — определяет, где запускать поды.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
Нет, нельзя изменить DNS у уже запущенного контейнера напрямую через CLI. Но есть обходные способы!
Самый надежный способ — перезапустить контейнер с нужными DNS-серверами
docker run --dns 8.8.8.8 --dns 8.8.4.4 -d my_container
Или задать DNS через
docker network
docker network create mynet --dns=1.1.1.1
docker run --net=mynet -d my_container
Можно изменить DNS вручную внутри работающего контейнера
docker exec -it my_container sh
echo "nameserver 1.1.1.1" > /etc/resolv.conf
Можно подключить контейнер к другой сети с нужными DNS:
docker network create mynewnet --dns=8.8.8.8
docker network connect mynewnet my_container
Если контейнер использует сеть хоста (
--network host
), можно поменять DNS на хосте: resolvectl dns eth0 8.8.8.8
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Основные:
- A — IP-адрес (IPv4);
- AAAA — IP-адрес (IPv6);
- CNAME — псевдоним (редирект на другой домен);
- MX — почтовый сервер;
- TXT — текстовая информация (часто SPF, DKIM);
- NS — сервера имен;
- SRV — служебные записи для сервисов.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2
Нормализация баз данных - это процесс организации данных в базе данных для минимизации избыточности данных и обеспечения их целостности. Цель нормализации - структурировать таблицы таким образом, чтобы устранить аномалии обновления, вставки и удаления данных. Нормализация достигается путем разбиения больших таблиц на более мелкие, связанных между собой отношениями, что упрощает управление данными и делает их более логически связанными.
Нормализация позволяет минимизировать дублирование данных, что экономит пространство и упрощает управление базой данных.
Нормализация помогает поддерживать целостность данных, обеспечивая правильное и единообразное хранение данных.
Разделение данных на логические таблицы делает структуру базы данных более понятной и удобной для использования.
Нормализация предотвращает возникновение аномалий при обновлении, вставке и удалении данных.
Удаление повторяющихся групп в таблице. Каждый столбец должен содержать только атомарные (неделимые) значения. Все записи в таблице должны быть уникальными.
Таблица должна быть в 1NF. Удаление частичной функциональной зависимости: каждый неключевой атрибут должен быть полностью зависим от первичного ключа.
Таблица должна быть в 2NF. Удаление транзитивной зависимости: все неключевые атрибуты должны быть напрямую зависимы от первичного ключа, а не от других неключевых атрибутов.
Усиление 3NF: каждая детерминанта должна быть кандидатом на ключ, что означает, что в любой нетривиальной функциональной зависимости X -> Y, X должно быть суперключом.
Таблица должна быть в BCNF. Устранение многозначных зависимостей: таблица не должна содержать многозначных зависимостей, когда один атрибут зависит от нескольких значений другого атрибута.
Таблица должна быть в 4NF. Устранение соединительных зависимостей: данные должны быть разбиты так, чтобы каждая зависимость сохранялась.
Если у вас есть таблица с повторяющимися группами, такими как несколько телефонных номеров для одного клиента, вы создаете отдельную таблицу для телефонов и связываете ее с таблицей клиентов.
Если у вас есть таблица заказов с колонками "номер заказа", "название товара" и "цена товара", вы можете создать отдельные таблицы для заказов и товаров, чтобы цена товара зависела только от товара, а не от комбинации заказа и товара.
Если у вас есть таблица сотрудников с колонками "идентификатор сотрудника", "название отдела" и "имя начальника отдела", вы можете создать отдельные таблицы для сотрудников и отделов, чтобы имя начальника отдела зависело только от отдела, а не от сотрудника.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
- Самостоятельность в задачах;
- Знание инструментов и шаблонов;
- Участие в код-ревью;
- Способность анализировать и предлагать улучшения;
- Командная работа и ответственность за результат.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊11
Оба инструмента управляют контейнерами, но предназначены для разных сценариев.
Docker Compose удобен для локальной разработки и небольших проектов.
Разворачиваете приложение на одном сервере.
Нужно быстро поднять несколько сервисов (БД, кэш, бекенд, фронт).
Разработка ведется в команде, и среда должна быть одинаковой.
Нет необходимости в сложном оркестрационном механизме (авто-масштабирование, балансировка нагрузки).
Файл
docker-compose.yml
для запуска бэкенда и базы данных локально: version: '3'
services:
app:
image: my-backend:latest
ports:
- "5000:5000"
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
Команда запуска:
docker-compose up -d
Kubernetes удобен для продакшн-окружений, облаков и сложных приложений.
Нужно масштабирование (автоматическое увеличение/уменьшение количества контейнеров).
Требуется автоисправление (если контейнер падает, он перезапускается).
Используется балансировка нагрузки (Kubernetes распределяет трафик между подами).
Нужна развертка в кластере (несколько машин, облако).
Требуется обновление без простоя (rolling updates).
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-backend:latest
ports:
- containerPort: 5000
Разворачиваем в Kubernetes:
kubectl apply -f my-app.yaml
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Применяется, когда:
- процесс завис и не реагирует на другие сигналы;
- требуется немедленное завершение;
- нет возможности дождаться корректного завершения (например, при утечке ресурсов).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В реляционных базах данных, операции объединения (JOIN) позволяют объединить строки из двух или более таблиц на основе связанных между собой столбцов. Существует несколько типов JOIN, каждый из которых имеет свои особенности и применим для разных ситуаций. Рассмотрим основные типы JOIN:
Объединяет строки из обеих таблиц, если они удовлетворяют условию объединения. Когда необходимо выбрать только те строки, которые имеют соответствующие значения в обеих таблицах.
SELECT *
FROM таблица1
INNER JOIN таблица2
ON таблица1.ключ = таблица2.ключ;
Возвращает все строки из левой таблицы и соответствующие строки из правой таблицы. Если соответствующей строки в правой таблице нет, в результирующем наборе данных для столбцов правой таблицы будут значения NULL. Когда необходимо выбрать все строки из одной таблицы и соответствующие данные из другой таблицы, если они существуют.
SELECT *
FROM таблица1
LEFT JOIN таблица2
ON таблица1.ключ = таблица2.ключ;
Возвращает все строки из правой таблицы и соответствующие строки из левой таблицы. Если соответствующей строки в левой таблице нет, в результирующем наборе данных для столбцов левой таблицы будут значения NULL. Когда необходимо выбрать все строки из одной таблицы (правой) и соответствующие данные из другой таблицы (левой), если они существуют.
SELECT *
FROM таблица1
RIGHT JOIN таблица2
ON таблица1.ключ = таблица2.ключ;
Возвращает все строки, когда есть совпадения либо в левой, либо в правой таблице. Если строки не соответствуют в одной из таблиц, для этой таблицы будут значения NULL. Когда необходимо выбрать все строки из обеих таблиц, независимо от того, есть ли соответствующие строки в другой таблице.
SELECT *
FROM таблица1
FULL JOIN таблица2
ON таблица1.ключ = таблица2.ключ;
Возвращает декартово произведение двух таблиц, то есть все возможные комбинации строк из обеих таблиц. Когда необходимо создать комбинации всех строк из обеих таблиц. Используется редко и с осторожностью, так как может привести к очень большому количеству строк.
SELECT *
FROM таблица1
CROSS JOIN таблица2;
Применяется для объединения таблицы самой с собой. Обычно используется для сравнения строк внутри одной и той же таблицы. Когда необходимо сопоставить строки одной таблицы друг с другом, например, для анализа иерархий или поиска парных записей.
SELECT A.*
FROM таблица A, таблица B
WHERE A.ключ = B.ключ;
Автоматически объединяет таблицы по всем столбцам с одинаковыми именами и типами данных. Когда у таблиц есть столбцы с одинаковыми именами, и нужно объединить их без явного указания условий объединения.
SELECT *
FROM таблица1
NATURAL JOIN таблица2;
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Контекст в Kubernetes — это комбинация пользователя, кластера и пространства имён (namespace) в kubeconfig. Он определяет, с каким кластером и от имени какого пользователя вы работаете в текущий момент.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
В Linux права доступа к файлам и папкам регулируются с помощью команд
chmod
и chown
. Если вам нужно дать полный доступ ко всем файлам и папкам, используйте
chmod -R 777 /путь/к/папке
-R
(recursive) — применяет права ко всем вложенным файлам и каталогам. 777
означает: 7
(чтение, запись, выполнение) для владельца 7
(чтение, запись, выполнение) для группы 7
(чтение, запись, выполнение) для всех остальных Если текущий пользователь не владеет файлами, назначьте нового владельца
chown -R username:group /путь/к/папке
username
— новый владелец group
— новая группа Вместо
777
лучше дать только нужные права: Для исполняемых файлов:
chmod 755
Для конфигураций и данных:
chmod 644
Для приватных ключей и чувствительных файлов:
chmod 600
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Если liveness-проба не проходит:
- Kubernetes считает pod "неживым" (неспособным восстановиться).
- Pod будет перезапущен (удаляется и создается заново в зависимости от политики restartPolicy).
- Используется для восстановления приложения в случае зависания или критической ошибки.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2👍1