15%

Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код:

Skills
Начать
23.10.2024

Балансировка нагрузки с выделенными серверами: архитектура, алгоритмы и реальное внедрение

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

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

Как на самом деле работает балансировка нагрузки: технический процесс

Понимание балансировки нагрузки требует понимания полного жизненного цикла запроса, а не просто абстрактной концепции «распределения трафика».

Конвейер маршрутизации запросов

  1. DNS-разрешение направляет клиента на IP-адрес балансировщика нагрузки (или виртуальный IP в anycast-конфигурации), а не на отдельный сервер.
  2. Балансировщик нагрузки получает соединение на уровне Layer 4 (TCP/UDP) или Layer 7 (HTTP/HTTPS) модели OSI.
  3. Балансировщик оценивает свою таблицу маршрутизации, применяет настроенный алгоритм и проверяет текущее состояние работоспособности каждого серверного узла.
  4. Запрос перенаправляется на выбранный серверный сервер. В зависимости от режима (NAT, Direct Server Return или IP-туннелирование) путь ответа может проходить или не проходить через балансировщик.
  5. Демоны проверки работоспособности работают параллельно, непрерывно опрашивая каждый серверный узел через TCP ping, HTTP-коды состояния или пользовательские скрипты. Неработающий узел удаляется из пула в течение нескольких секунд.

Балансировка нагрузки Layer 4 и Layer 7

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

ФункцияLayer 4 (транспортный)Layer 7 (прикладной)
Работает наTCP/UDP-пакетахHTTP/HTTPS-запросах, заголовках, cookies
Логика маршрутизацииIP-адрес + портURL-путь, имя хоста, значение cookie, содержимое заголовка
Завершение SSLНет (сквозной режим)Да (разгружает TLS с серверных узлов)
Маршрутизация на основе содержимогоНевозможнаПолная поддержка (маршрутизация /api/ отдельно от /static/)
Накладные расходы на производительностьОчень низкиеУмеренные (требуется глубокая инспекция пакетов)
Типичные варианты использованияСырые TCP-сервисы, базы данных, игровые серверыВеб-приложения, REST API, микросервисы
Примеры программного обеспеченияHAProxy (TCP-режим), LVS/IPVSNGINX, HAProxy (HTTP-режим), Traefik, Envoy
Сохранение сессииХэш исходного IPВнедрение cookie, привязка на основе заголовков

Для большинства веб-приложений, размещённых на выделенных серверах, Layer 7 является правильным выбором, поскольку он обеспечивает интеллектуальную маршрутизацию, разгрузку SSL и детальные проверки работоспособности на основе HTTP-кодов ответа, а не сырого TCP-соединения.

Алгоритмы балансировки нагрузки: выбор правильной стратегии

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

Round Robin

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

Недостаток: Если один запрос занимает 10 секунд, а следующий — 10 миллисекунд, round robin не учитывает это несоответствие. Медленный серверный узел накапливает очередь, пока другие простаивают.

Взвешенный Round Robin

Каждому серверу присваивается числовой вес. Сервер с весом 3 получает в три раза больше запросов, чем сервер с весом 1. Используйте этот метод, когда ваш пул содержит разнородное оборудование — например, сочетание 32-ядерного узла с 16-ядерным.

Наименьшее количество соединений

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

Наименьшее время ответа

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

IP Hash (привязка по источнику)

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

Критический граничный случай: Если значительная часть вашего трафика поступает из-за корпоративного NAT или шлюза мобильного оператора, тысячи пользователей могут использовать один исходный IP, вызывая серьёзный дисбаланс. Всегда проверяйте распределение трафика перед использованием IP hash в производственной среде.

Случайный выбор из двух (Power of Two)

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

Сохранение сессии: когда без состояния не обойтись

Многие устаревшие приложения хранят состояние сессии локально на сервере (например, PHP $_SESSION, записанные на диск). В таких случаях перенаправление возвращающегося пользователя на другой серверный узел приводит к потере сессии, что проявляется в виде неожиданных выходов из системы или потери данных корзины покупок.

Балансировщики нагрузки решают эту проблему с помощью sticky sessions, реализованных через:

  • Внедрение cookie: Балансировщик внедряет cookie (например, SERVERID=node2) в HTTP-ответ. Последующие запросы от этого клиента содержат cookie, и балансировщик считывает его для маршрутизации обратно на тот же узел.
  • Привязка по исходному IP: Как описано выше, менее надёжный метод, но не требующий поддержки cookie со стороны приложения.

Правильное долгосрочное решение — вынести хранилище сессий во внешний общий бэкенд — Redis или Memcached — чтобы любой серверный узел мог обслуживать любого пользователя. Это полностью устраняет зависимость от sticky sessions и делает ваш пул полностью stateless, что значительно упрощает масштабирование и аварийное переключение. Если вы создаёте новое приложение, проектируйте stateless серверные узлы с самого начала.

Проверки работоспособности: механизм автоматического аварийного переключения

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

Типы проверок работоспособности

  • TCP-проверка: Открывает TCP-соединение с портом серверного узла. Подтверждает, что процесс прослушивает порт, но не проверяет корректность на уровне приложения.
  • HTTP/HTTPS-проверка: Отправляет HTTP-запрос на определённый эндпоинт (например, /health) и ожидает определённый код состояния (обычно 200 OK). Это минимально приемлемый стандарт для веб-приложений.
  • Проверка пользовательским скриптом: Выполняет произвольный скрипт, который может запрашивать базу данных, проверять дисковое пространство или валидировать состояние приложения. Возвращает 0 для работоспособного состояния, ненулевое значение — для неработоспособного.

Критические параметры конфигурации

  • interval: Как часто выполняется проверка (например, каждые 5 секунд).
  • timeout: Как долго ждать ответа перед тем, как пометить проверку как неудавшуюся.
  • rise: Количество последовательных успешных проверок, необходимых для пометки узла как работоспособного (предотвращает нестабильность).
  • fall: Количество последовательных неудавшихся проверок, необходимых для удаления узла из пула.

Типичная производственная конфигурация для HAProxy выглядит следующим образом:

backend web_servers
    balance leastconn
    option httpchk GET /health HTTP/1.1rnHost: example.com
    http-check expect status 200
    default-server inter 5s fall 3 rise 2 slowstart 60s
    server node1 192.168.1.10:80 check weight 10
    server node2 192.168.1.11:80 check weight 10
    server node3 192.168.1.12:80 check weight 5

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

Завершение SSL и разгрузка TLS

Обработка шифрования и дешифрования TLS требует значительных вычислительных ресурсов. В наивной конфигурации каждый серверный сервер выполняет эту работу независимо. Завершение SSL на балансировщике нагрузки означает, что балансировщик расшифровывает входящий HTTPS-трафик и перенаправляет обычный HTTP на серверные узлы через доверенную внутреннюю сеть.

Преимущества:

  • Снижает нагрузку на CPU серверных серверов, освобождая циклы для логики приложения.
  • Централизует управление сертификатами — обновляйте один сертификат на балансировщике, а не на каждом узле.
  • Обеспечивает инспекцию содержимого запросов на уровне Layer 7 (невозможно при зашифрованном сквозном режиме).

Соображения безопасности: Трафик между балансировщиком нагрузки и серверными узлами передаётся в незашифрованном виде. Это приемлемо, когда все узлы находятся в изолированной частной VLAN или выделенной сети управления. Если ваши требования соответствия (PCI-DSS, HIPAA) предписывают сквозное шифрование, используйте повторное шифрование SSL: балансировщик завершает клиентскую TLS-сессию и устанавливает новую TLS-сессию с каждым серверным узлом. Это обеспечивает полное шифрование при сохранении возможности маршрутизации на уровне Layer 7.

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

Высокая доступность самого балансировщика нагрузки

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

Активно-пассивный режим с VRRP/Keepalived

Два узла балансировщика нагрузки совместно используют виртуальный IP (VIP). Активный узел удерживает VIP и обрабатывает весь трафик. Пассивный узел отслеживает активный узел через heartbeat. В случае отказа активного узла keepalived инициирует аварийное переключение VRRP, и пассивный узел захватывает VIP в течение 1–3 секунд.

# Install keepalived on both load balancer nodes (Debian/Ubuntu)
apt-get install keepalived

# /etc/keepalived/keepalived.conf on the MASTER node
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass securepassword
    }
    virtual_ipaddress {
        203.0.113.10/24
    }
}

На резервном узле установите state BACKUP и priority 90. Узел с более высоким приоритетом выигрывает выборы VIP.

Активно-активный режим с DNS Round Robin или Anycast

Оба узла балансировщика нагрузки активно обрабатывают трафик одновременно. DNS возвращает несколько A-записей, распределяя клиентов между обоими балансировщиками. Это удваивает пропускную способность, но требует тщательной синхронизации состояния при использовании sticky sessions.

Для крупномасштабных развёртываний на выделенных серверах активно-активная конфигурация с маршрутизацией BGP anycast обеспечивает наибольшую пропускную способность и географическую избыточность.

Защита от DDoS на уровне балансировщика нагрузки

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

Ограничение скорости соединений (HAProxy)

frontend http_in
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/
    stick-table type ip size 100k expire 30s store conn_rate(3s),http_req_rate(10s)
    tcp-request connection track-sc0 src
    tcp-request connection reject if { sc_conn_rate(0) gt 100 }
    http-request deny if { sc_http_req_rate(0) gt 300 }

Эта конфигурация отслеживает скорость соединений по исходному IP в таблице stick и отклоняет клиентов, превышающих 100 новых TCP-соединений за 3 секунды или 300 HTTP-запросов за 10 секунд — пороговые значения, блокирующие большинство объёмных HTTP-флуд-атак при разрешении легитимного всплеска трафика.

Защита от SYN-флуда

Включите SYN cookies на уровне ядра на узлах балансировщика нагрузки для обработки SYN-флуд-атак без исчерпания таблицы соединений:

sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=4096
sysctl -w net.ipv4.tcp_synack_retries=2

Сделайте эти настройки постоянными, добавив их в /etc/sysctl.conf.

NGINX как балансировщик нагрузки Layer 7: производственная конфигурация

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

upstream backend_pool {
    least_conn;
    keepalive 32;

    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.1.13:8080 backup;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        proxy_pass         http://backend_pool;
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout    60s;
        proxy_next_upstream   error timeout http_502 http_503;
    }
}

Ключевые детали этой конфигурации:

  • keepalive 32 поддерживает постоянные соединения с серверными узлами, устраняя накладные расходы на TCP-рукопожатие для высокочастотных запросов.
  • proxy_next_upstream автоматически повторяет неудавшиеся запросы на следующем работоспособном серверном узле.
  • Директива backup назначает node4 резервным узлом, который получает трафик только тогда, когда все основные узлы недоступны.
  • X-Forwarded-For гарантирует, что серверные приложения видят реальный IP клиента, а не IP балансировщика.

Сравнение программных вариантов балансировщиков нагрузки

Программное обеспечениеУровеньПроизводительностьЗавершение SSLАктивные проверки работоспособностиПростота настройкиЛучше всего подходит для
HAProxyL4 + L7Чрезвычайно высокаяДаДа (расширенные)УмереннаяВысоконагруженный TCP/HTTP, детальные ACL
NGINXL7 (L4 в модуле stream)Очень высокаяДаБазовые (NGINX Plus для расширенных)ПростаяПроксирование веб/API, интегрированный веб-сервер
TraefikL7ВысокаяДа (автоматически Let’s Encrypt)ДаОчень простаяКонтейнерные среды, Kubernetes
EnvoyL7Очень высокаяДаДа (gRPC-проверки работоспособности)СложнаяService mesh, микросервисы
LVS/IPVSL4На уровне ядра, максимальнаяНетЧерез KeepalivedСложнаяМаксимальная пропускная способность, сценарии с обходом ядра
AWS ALB/NLBL7/L4УправляемаяДаДаПростая (управляемая)Cloud-native, без самостоятельного управления

Для самостоятельно управляемых выделенных серверов HAProxy и NGINX охватывают подавляющее большинство производственных случаев использования. Traefik является прагматичным выбором для нагрузок Docker Swarm или Kubernetes благодаря автоматическому обнаружению сервисов.

Реальная архитектура: платформа электронной коммерции при пиковой нагрузке

Рассмотрим конкретный сценарий: платформа электронной коммерции, ожидающая 50 000 одновременных пользователей во время рекламной акции.

Схема инфраструктуры:

  • 2 узла HAProxy в активно-пассивной конфигурации, совместно использующие VIP (через Keepalived)
  • 6 серверов приложений, работающих на веб-уровне
  • 2 выделенных сервера баз данных (не в пуле балансировщика нагрузки — они используют собственную репликацию)
  • 1 кластер Redis для общего хранилища сессий (устраняет зависимость от sticky sessions)
  • Общее NFS или объектное хранилище для загружаемых пользователями ресурсов

Поток трафика:

  1. DNS клиента разрешается в VIP, удерживаемый активным узлом HAProxy.
  2. HAProxy применяет алгоритм leastconn, распределяя запросы между 6 серверами приложений.
  3. Каждый сервер приложений читает/записывает данные сессии из Redis — привязка сессии не требуется.
  4. Статические ресурсы обслуживаются непосредственно из объектного хранилища через CDN, полностью минуя балансировщик нагрузки и снижая его нагрузку на 60–70%.
  5. Если проверка работоспособности одного сервера приложений трижды подряд завершается неудачей, HAProxy удаляет его из пула в течение 15 секунд. Оставшиеся 5 серверов принимают его трафик.
  6. В случае отказа активного узла HAProxy Keepalived передаёт VIP пассивному узлу в течение 2 секунд — прозрачно для всех клиентов.

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

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

Мониторинг инфраструктуры с балансировкой нагрузки

Развёртывание балансировщика нагрузки без наблюдаемости — это работа вслепую. Вот метрики, которые имеют значение:

  • Активные соединения на серверный узел: Выявляет дисбаланс в алгоритме распределения или концентрацию sticky sessions.
  • Скорость запросов (RPS) на серверный узел: Должна быть пропорциональна весам серверов.
  • Время ответа серверного узла (p50, p95, p99): Всплески задержки p99 на одном узле указывают на проблему до срабатывания проверок работоспособности.
  • Частота сбоев проверок работоспособности: Серверный узел, который колеблется между работоспособным и неработоспособным состоянием (нестабильность), указывает на основную нестабильность, требующую расследования.
  • Глубина очереди соединений: Если очередь балансировщика растёт, ваш пул серверных узлов недостаточен для текущего трафика.
  • Скорость SSL-рукопожатий: Высокие значения указывают на потенциальную атаку на истощение TLS или неправильно настроенный клиент, агрессивно повторяющий попытки.

HAProxy предоставляет страницу статистики (включается с помощью stats enable во фронтенде) и Unix-сокет для программных запросов. Передавайте эти метрики в Prometheus через haproxy_exporter и визуализируйте в Grafana для полного стека наблюдаемости.

Практический контрольный список решений

Используйте эту матрицу перед развёртыванием или изменением архитектуры с балансировкой нагрузки:

  • Приложение с состоянием? Перенесите хранилище сессий в Redis или Memcached перед включением балансировки нагрузки. Не полагайтесь на sticky sessions как на постоянное решение.
  • Требуется TLS? Завершайте SSL на балансировщике нагрузки. Убедитесь, что сеть серверных узлов изолирована. Получайте и управляйте сертификатами централизованно через SSL-сертификаты.
  • Переменная продолжительность запросов? Используйте leastconn, а не round robin.
  • Разнородное оборудование? Применяйте значения weight пропорционально мощности серверов.
  • Высокая доступность балансировщика нагрузки? Разверните два узла балансировщика с Keepalived/VRRP. Никогда не запускайте единственный балансировщик нагрузки в производственной среде.
  • Подверженность DDoS? Реализуйте ограничение скорости соединений и защиту SYN cookies на уровне ядра и балансировщика.
  • Глубина проверки работоспособности? Используйте HTTP-проверки против выделенного эндпоинта /health, который проверяет подключение к базе данных, а не только доступность TCP-порта.
  • План масштабирования? Добавление нового серверного узла в пул HAProxy или NGINX требует перезагрузки конфигурации (haproxy -sf $(cat /var/run/haproxy.pid) для перезагрузки без простоя) — планируйте процесс управления изменениями соответствующим образом.
  • Мониторинг? Настройте HAProxy или NGINX с экспортёрами Prometheus до запуска в производство, а не после инцидента.
  • Предпочтение панели управления? Если вы предпочитаете управление сервером через GUI наряду с ручной настройкой балансировщика нагрузки, оцените панели управления VPS для административных задач на отдельных узлах.

FAQ

В чём разница между балансировщиком нагрузки и обратным прокси?

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

Может ли балансировка нагрузки работать с одним выделенным сервером?

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

Как балансировщик нагрузки обрабатывает WebSocket-соединения?

WebSocket требует постоянных, долгоживущих TCP-соединений. Балансировщики нагрузки Layer 7 должны быть явно настроены для обработки HTTP Upgrade-рукопожатия, а затем поддерживать привязку соединения на протяжении всей WebSocket-сессии. В NGINX установите proxy_http_version 1.1 и proxy_set_header Upgrade $http_upgrade со значением proxy_set_header Connection "upgrade". В HAProxy используйте option http-server-close и настройте соответствующие значения таймаута (timeout tunnel 1h для долгоживущих соединений).

Что происходит с запросами в процессе выполнения при отказе серверного сервера?

С proxy_next_upstream в NGINX или retries в HAProxy балансировщик обнаруживает ошибку соединения или таймаут при первой попытке и немедленно повторяет запрос на следующем работоспособном серверном узле. Эта повторная попытка прозрачна для клиента. Идемпотентные запросы (GET, HEAD) безопасно повторять автоматически. Неидемпотентные запросы (POST, PUT) следует повторять с осторожностью — настройте proxy_next_upstream для исключения http_500 для POST-маршрутов, чтобы избежать двойной обработки платежа или отправки формы.

Сколько серверных серверов необходимо, прежде чем балансировка нагрузки даст ощутимый эффект?

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

15%

Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код:

Skills
Начать