АйТи Фреш
Главная / Статьи / DevOps и автоматизация
DevOps и автоматизация

PreferSameNode против internalTrafficPolicy: Local — как оставить трафик на своём узле и не потерять сервис

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~22 мин чтения
PreferSameNode против internalTrafficPolicy: Local — как оставить трафик на своём узле и не потерять сервис
Иллюстрация к статье «PreferSameNode против internalTrafficPolicy: Local — как оставить трафик на своём узле и не потерять сервис».

Статья для тех, чей сервис живёт в Kubernetes — в собственном кластере или у подрядчика, — и кто не хочет разбираться с «иногда зависает» по ночам. Разбираю, что на самом деле означают значения поля trafficDistribution (PreferClose, PreferSameZone, PreferSameNode), как они шли по версиям от 1.30 до 1.35, почему internalTrafficPolicy: Local — это не «предпочтение своего узла», а жёсткое требование с отбрасыванием пакетов, и как мы перевели сервис онлайн-записи небольшой консультации с одного на другое, чтобы rolling update DaemonSet перестал на полминуты обрывать форму записи.

PreferClose никогда не означал «свой узел»

Типичная сцена. На каждом узле крутится вспомогательный под — кэширующий прокси, агент сбора логов, локальный DNS. Логика администратора железная: раз реплика есть на каждой ноде, пусть клиент ходит в свою, а не гоняет пакеты по коммутатору. Человек открывает документацию по Service, видит поле trafficDistribution со значением PreferClose, читает слово «close» как «ближайший, то есть на этой же машине» — и ставит его. А потом удивляется, что в метриках межузловой трафик как был, так и есть.

PreferClose никогда не значил «тот же узел». Он всегда значил ровно «та же зона» — зона в смысле метки topology.kubernetes.io/zone на объекте Node. Внутри зоны все эндпоинты для kube-proxy равны: хоть на этой же ноде, хоть на соседней стойке. В облаке зона — это availability zone провайдера, и там от PreferClose есть польза: он режет межзональный трафик, за который в AWS и GCP выставляют отдельный счёт. А вот в кластере на своём железе в одном ЦОД зона обычно одна на весь кластер (или метки нет вообще), и PreferClose там не делает буквально ничего. Ноль эффекта, ноль ошибок, ноль сообщений в логах — просто тишина.

Второй путь в ту же яму — internalTrafficPolicy со значением Local. Вот это действительно «свой узел», и работает именно так, как хотелось. Ровно до первой ситуации, когда локального пода на узле не оказалось. Тогда kube-proxy не ищет запасной маршрут — он выбрасывает пакет. Не 503, не переадресация на соседа, а тишина в сокете до клиентского таймаута. Rolling update этого самого DaemonSet, cordon узла перед обновлением ядра, OOM-kill одной реплики — и всё, для клиентов на этой ноде сервис умер.

Закрывали эту дыру в два захода. Само поле trafficDistribution с единственным значением PreferClose пришло по KEP-4444: alpha в 1.30 (гейт ServiceTrafficDistribution), beta с включением по умолчанию в 1.31, stable в 1.33. А по KEP-3015 в 1.33 появились PreferSameNode — предпочитаем локальный эндпоинт, но при его отсутствии спокойно уходим на удалённый — и PreferSameZone, новое имя для PreferClose, чтобы оно перестало врать о семантике. В 1.35 эта вторая часть стала stable. Старое имя PreferClose оставили работающим: удалять его из API в KEP прямо записано как «не цель».

PreferClose = PreferSameZone. Это не «примерно то же самое», а буквально устаревший алиас того же поведения: так он описан в KEP-3015 с 1.33 и в документации Kubernetes по Virtual IPs and Service Proxies.

Что именно приехало в 1.35 и как оно устроено внутри

Работа лежит в KEP-3015 «PreferSameZone and PreferSameNode Traffic Distribution». Календарь такой: alpha в 1.33, beta с включением по умолчанию в 1.34, stable в 1.35 (релиз от 17 декабря 2025 года). Фича-гейт называется PreferSameTrafficDistribution и заявлен для трёх компонентов — kube-apiserver, kube-controller-manager и kube-proxy. На сентябрь 2026 в поддержке находятся 1.35, 1.36 и 1.37 (1.37.0 вышел 26 августа 2026), то есть на любой живой версии функция уже stable и гейт трогать не нужно вообще. Важная деталь про alpha: в 1.33 гейт выключен по умолчанию, и без явного включения на kube-apiserver новое значение будет отклонено валидацией. Если у вас 1.32 или ниже — сначала обновляйтесь, там этого нет вообще; впрочем, 1.34 и более ранние версии уже вне поддержки проекта.

Механика простая и в ней стоит разобраться, потому что диагностика идёт именно по ней. kube-proxy сам поле trafficDistribution не читает — вообще никогда. Читает его контроллер EndpointSlice внутри kube-controller-manager и по результату проставляет в объекты EndpointSlice подсказки в секции hints. Для зоны это forZones, для узла — новое поле forNodes со списком объектов вида {name: <имя-узла>}. Причём для PreferSameNode контроллер проставляет ОБА хинта: forNodes для новых прокси и forZones для старых, чтобы при рассинхроне версий сервис деградировал до «той же зоны», а не до полного отсутствия локальности.

Выглядит это так. Сам Service:

apiVersion: v1
kind: Service
metadata:
  name: node-cache
  namespace: prod
spec:
  selector:
    app: node-cache
  ports:
    - port: 80
      targetPort: 8080
  trafficDistribution: PreferSameNode

И то, что после этого появляется в EndpointSlice:

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
endpoints:
  - addresses: ["10.244.3.17"]
    nodeName: worker-03
    zone: zone-a
    conditions:
      ready: true
    hints:
      forNodes:
        - name: worker-03
      forZones:
        - name: zone-a

Дальше решение принимает kube-proxy в функции CategorizeEndpoints, и алгоритм там трёхступенчатый. Первое: если у КАЖДОГО эндпоинта проставлен forNodes и хотя бы один из них указывает на локальный узел — берём только эти эндпоинты. Второе: иначе, если у каждого эндпоинта есть forZones и хотя бы один указывает на зону локального узла — берём зональные. Третье: иначе доступны все эндпоинты. Обратите внимание на слово «каждый» — это не придирка, это ключевой предохранитель. Если хотя бы у одного эндпоинта в слайсе подсказки нет, вся топология игнорируется целиком, и трафик растекается по кластеру. Сделано, чтобы во время раскатки не получилось полусостояния, когда часть подов невидима.

Диагностировать надо не поле в Service, а хинты в EndpointSlice. Поле может стоять корректно, а хинтов не быть — например, потому что контроллер старой версии или сервис попал под конфликтующую аннотацию.
PreferSameNode против internalTrafficPolicy: Local — как оставить трафик на своём узле и не потерять сервис — схема
Схема к статье. Открыть схему в полном размере

internalTrafficPolicy: Local — это требование, а не пожелание

Поля .spec.internalTrafficPolicy и .spec.externalTrafficPolicy принимают два значения: Cluster и Local. Внутренний вариант стабилен с 1.26, так что это давно не новинка. Документация формулирует поведение предельно прямо: при Local трафик уходит только на готовые эндпоинты локального узла, а если локальных эндпоинтов нет — трафик отбрасывается kube-proxy. Не отклоняется с ошибкой, не перенаправляется. Отбрасывается.

Для клиента это выглядит как зависание. Соединение не устанавливается, ответа нет, приложение сидит до своего таймаута — пять секунд, тридцать, сколько прописано. В графиках это даёт не всплеск пятисоток, а всплеск p99 и рост числа открытых соединений. Люди часто ищут причину в сети, в MTU, в CNI — а причина в одной строчке манифеста и в том, что реплика уехала с узла.

Теперь про приоритеты, потому что это ровно тот вопрос, из-за которого путаница и возникает. Правило одно и оно жёсткое: если для соответствующего типа трафика политика выставлена в Local, она перебивает trafficDistribution. Внутренний трафик смотрит на internalTrafficPolicy, внешний — на externalTrafficPolicy. Если политика стоит в Cluster (значение по умолчанию) или не задана вовсе — тогда работает trafficDistribution. Документация прямо называет это разницей между «строгими гарантиями» и «предпочтениями».

Практический вывод: комбинация из internalTrafficPolicy со значением Local и trafficDistribution со значением PreferSameNode на одном сервисе — это не «двойная защита», а мёртвая строчка. Для внутреннего трафика выиграет Local, и вся идея с откатом на удалённый эндпоинт не сработает. Я такие манифесты вижу регулярно: человек добавил новое поле, старое убрать забыл, поведение не изменилось, и он делает вывод, что «PreferSameNode не работает».

Ещё один тихий перебиватель: аннотация service.kubernetes.io/topology-mode со значением Auto. По KEP-4444, если заданы и аннотация, и поле trafficDistribution, приоритет у аннотации. Это наследие старого Topology Aware Routing; в KEP такой приоритет назван временным, аннотацию планируют признать устаревшей в пользу нового поля.
Памятка: internalTrafficPolicy: Local — это требование, а не пожелание — схема
Памятка: internalTrafficPolicy: Local — это требование, а не пожелание. Открыть схему в полном размере

Разбор из практики: «Кадровый навигатор», онлайн-запись у подрядчика и четыре зависания формы

Клиент — карьерная консультация «Кадровый навигатор»: 10 рабочих мест, консультанты по поиску работы и составлению резюме, приём по записи. Своего кластера у них нет и быть не должно: сервис онлайн-записи на сайте написал и сопровождает веб-подрядчик, и живёт он у подрядчика в Kubernetes — три воркера, Kubernetes 1.36, kube-proxy в режиме iptables, CNI Calico. Одна зона, метки topology.kubernetes.io/zone нет — обычная история для небольшого кластера. Мы у консультации отвечаем за офисную IT-часть и как её представитель получили от подрядчика read-only доступ к пространству имён сервиса записи.

Перед API календаря консультантов стоял кэширующий nginx, развёрнутый DaemonSet-ом: по одному поду на воркер, задача — не дёргать календарь на каждый просмотр свободных слотов. Подрядчик поставил на его Service internalTrafficPolicy со значением Local. Логика понятна: кэш локальный, ходить в чужой кэш смысла нет. И несколько месяцев всё работало.

Меня позвали, когда администратор консультации переслала два письма клиентов: «нажимаю «Записаться», крутится, потом ошибка». Для компании на 10 человек это не абстрактный SLA: каждая сорванная запись — это клиент, который уходит к конкурентам, а одна консультация стоит заметных денег. Мы с подрядчиком собрали окна из метрик Prometheus за две недели: четыре эпизода, суммарно около семи минут, и все совпадают по времени либо с rolling update DaemonSet-а nginx (смена конфига кэша, 20–40 секунд без готового пода на узле), либо с drain узла под обновление ядра. Поды бэкенда записи на «пострадавшем» узле в эти окна упирались в свой пятисекундный таймаут: 214 записей context deadline exceeded, в каждом окне все с одного узла. По журналу заявок — минимум три незавершённые записи. Сеть ни при чём: kube-proxy честно отбрасывал пакеты, как и написано в документации.

Правка — одна строчка, выполнял её подрядчик по нашей рекомендации: убрать internalTrafficPolicy и поставить trafficDistribution со значением PreferSameNode.

kubectl patch svc nginx-cache -n booking --type=merge \
  -p '{"spec":{"internalTrafficPolicy":"Cluster","trafficDistribution":"PreferSameNode"}}'

kubectl get endpointslice -n booking \
  -l kubernetes.io/service-name=nginx-cache \
  -o jsonpath='{range .items[*].endpoints[*]}{.nodeName}{"\t"}{.hints.forNodes[*].name}{"\n"}{end}'

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

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

И честная вторая половина истории. По инерции я предложил PreferSameNode и для сервиса генерации PDF-резюме, а у него всего две реплики на три узла. Формально ничего не сломалось: клиенты на узле без реплики откатились на общее распределение. Но на одном из узлов с репликой жила примерно половина клиентских подов, ещё около 30 % — на узле без реплики. В итоге одна реплика стала принимать порядка 65 % запросов (свои 50 % плюс половина от 30 %), вторая — 35 %, и в часы пиковой записи это было видно по времени генерации. Через два дня сервис вернули на дефолт. Вывод: PreferSameNode — для сервисов, у которых реплика есть на каждом узле, где живут клиенты. Для сервисов с горсткой реплик это перекос нагрузки, а не оптимизация.

Что из этого стоит вынести владельцу маленькой компании, у которой сайт или запись крутятся у подрядчика. Разбираться в хинтах EndpointSlice вам не нужно. Нужно уметь задать подрядчику три вопроса: какие сервисы у вас стоят на internalTrafficPolicy: Local и что с ними происходит при обновлении; проверяли ли вы сценарий «на узле нет реплики»; и какие метрики покажут, что форма записи зависала. Если на второй вопрос ответ «не проверяли» — это повод попросить приёмочный тест с drain узла, а не повод менять подрядчика.

Если ставите PreferSameNode на сервис, который развёрнут не DaemonSet-ом, — сначала посчитайте, сколько клиентских подов сидит на узлах с локальной репликой. Неравномерность клиентов превращается в неравномерность нагрузки на бэкенды один в один.

Порядок внедрения: шесть шагов, которые я прохожу всегда

Первое — версия. Значение PreferSameNode валидируется API-сервером, и на кластере ниже 1.33 (и на 1.33 без включённого гейта) патч просто не пройдёт с ошибкой Unsupported value. Это, кстати, самый быстрый способ проверки: попробовать поставить и посмотреть на реакцию.

kubectl version -o json | jq -r '.serverVersion.gitVersion'

kubectl patch svc node-cache -n booking --type=merge \
  -p '{"spec":{"trafficDistribution":"PreferSameNode"}}'

Второе — снять конфликтующее. Аннотацию topology-mode со значением Auto убрать, internalTrafficPolicy привести к Cluster. Пока они на месте, новое поле ни на что не влияет, и вы будете час искать несуществующую проблему.

kubectl annotate svc node-cache -n booking service.kubernetes.io/topology-mode-
kubectl get svc node-cache -n booking \
  -o custom-columns='ITP:.spec.internalTrafficPolicy,TD:.spec.trafficDistribution'

Третье — убедиться, что хинты реально проставились у ВСЕХ эндпоинтов, а не у части. Напоминаю: одного эндпоинта без подсказки достаточно, чтобы kube-proxy отключил топологию для всего сервиса. Четвёртое — проверить, что ваш service proxy умеет forNodes. Штатный kube-proxy умеет с 1.33 при включённом гейте, по умолчанию — с 1.34. Со сторонними реализациями нужна проверка на месте, об этом ниже отдельно. Пятое — снять базовые метрики до правки, иначе потом нечем будет доказать эффект. Шестое — раскатывать по одному сервису, а не пачкой: поведение при отсутствии локальной реплики у разных сервисов разное, и разбирать регресс проще, когда изменение одно.

Обязательный тест приёмки — искусственно убрать локальную реплику (kubectl drain или scale до нуля на одном узле) и убедиться, что клиенты на этом узле продолжают получать ответы. Именно этот сценарий и отличает PreferSameNode от Local, и именно его почти никто не проверяет.
Порядок действий: Порядок внедрения: шесть шагов, которые я прохожу всегда — схема
Порядок действий: Порядок внедрения: шесть шагов, которые я прохожу всегда. Открыть схему в полном размере

Где ломается и на что можно спокойно забить

Главный подводный камень — сторонние service proxy. kube-proxy читает forNodes начиная с 1.33 (с гейтом) и по умолчанию с 1.34, тут вопросов нет. А вот если у вас kube-proxy заменён на Cilium в режиме kube-proxy-free, картина другая: Traffic Distribution там реализован и включается опцией loadBalancer.serviceTopology=true, но в документации механика описана как маршрутизация к эндпоинтам в той же зоне, про forNodes там ничего нет. KEP-3015 именно такой сценарий и предусматривает: прокси, не знающий forNodes, увидит проставленный рядом forZones и отработает как PreferSameZone. То есть сервис не сломается — он просто тихо не будет делать того, что вы задумали. Проверять надо на своём кластере трассировкой реального запроса, а не по релиз-нотам.

Второй камень — перегрузка эндпоинтов, и это спорное место по дизайну. Старый Topology Aware Routing через аннотацию Auto пытался быть умным: раскидывал трафик пропорционально выделяемому CPU по зонам и имел предохранитель на случай малого числа эндпоинтов. Новое поле сознательно от этого отказалось в пользу предсказуемости: есть локальные эндпоинты — они забирают весь трафик, нет — уходим дальше. Документация честно перекладывает балансировку на вас и предлагает Pod Topology Spread Constraints, отдельные Deployment на зону и горизонтальное автомасштабирование. Мне такой размен нравится больше — предсказуемое поведение отлаживается, эвристика нет, — но признаю, что это именно размен.

Третий — деградация при откате версии. Если кластер откатили на релиз, не знающий новое значение, контроллер EndpointSlice воспримет его как пустое и снимет хинты. Сервис не упадёт, он просто вернётся к равномерному распределению. Безопасно, но тихо: в событиях ничего, в статусе Service ничего. Поэтому мониторить надо наличие forNodes в EndpointSlice, а не значение поля в Service. Достаточно простой проверки в любом мониторинге — раз в пять минут kubectl-запрос и сравнение количества эндпоинтов с количеством хинтов:

kubectl get endpointslice -n booking -l kubernetes.io/service-name=nginx-cache -o json \
  | jq '[.items[].endpoints[]] | {endpoints: length, withNodeHints: map(select(.hints.forNodes != null)) | length}'

А теперь про то, на что можно забить. На фича-гейт — он stable, включён, отключать его вручную не надо и не стоит. На размер EndpointSlice из-за дублирования forZones рядом с forNodes — прирост исчисляется десятками байт на эндпоинт, это шум. На метрики самой фичи — их нет, и KEP прямо это признаёт: проект не знает, какая латентность считается у вас хорошей, поэтому меряйте прикладными метриками. И на PreferSameZone в кластере с одной зоной — он там просто лишняя строчка в манифесте, вреда никакого, пользы тоже.

Если у вас Cilium, Calico eBPF или другая замена kube-proxy — не считайте PreferSameNode работающим, пока не увидели своими глазами, что запрос с узла попал в под на этом же узле. Молчаливый откат до зональной семантики выглядит абсолютно нормально в любом дашборде.

Когда Local — по-прежнему правильный выбор

Я не призываю выкорчёвывать Local отовсюду. Есть сценарии, где именно строгая гарантия и нужна, а падение при отсутствии локальной реплики — корректное поведение, а не авария. Самый частый — externalTrafficPolicy со значением Local на сервисах типа LoadBalancer и NodePort. Он сохраняет реальный source IP клиента (критично для геофильтрации, whitelist по адресам и вообще любой аналитики) и убирает лишний хоп между узлами.

Там же завязана и балансировщиковая проверка здоровья. kube-proxy отдаёт health-check на ${NODE_IP}:10256/healthz, и для сервиса с Local он возвращает 200 только тогда, когда kube-proxy жив и на узле есть локальный эндпоинт. То есть внешний балансировщик сам уберёт узел без реплики из пула — деградации по сути не будет. Это принципиально иная ситуация, чем с внутренним трафиком, где никакого балансировщика между подом и Service нет и некому принять решение об исключении узла. Отдельно отмечу: для livenessProbe самого kube-proxy используйте путь /livez, а не /healthz — второй учитывает состояние удаления узла и при drain загонит kube-proxy в бесконечный рестарт.

Внутренний Local я оставляю там, где удалённый вызов семантически невозможен. Агент, который пишет в сокет или устройство конкретного узла. Сервис, читающий данные с локального hostPath. Компонента, лицензированная по узлу. В этих случаях уход на соседнюю ноду — это не «деградация с сохранением работоспособности», а тихо неверный результат, и явный отказ лучше. Но такие случаи надо уметь назвать вслух: если на вопрос «что сломается, если запрос уйдёт на соседний узел» ответа нет — значит, вам нужен PreferSameNode, а не Local.

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

Правило одной фразы: Local — если чужой узел ответит неверно; PreferSameNode — если чужой узел ответит верно, но медленнее.
Памятка: Когда Local — по-прежнему правильный выбор — схема
Памятка: Когда Local — по-прежнему правильный выбор. Открыть схему в полном размере

Частые вопросы

С какой версии Kubernetes можно ставить PreferSameNode в продакшене?

Значение принимается с 1.33 (alpha, гейт PreferSameTrafficDistribution выключен по умолчанию), включено по умолчанию с 1.34 (beta), stable — с 1.35. В продакшене я ставлю с 1.35 и выше: там гейт уже не нужно контролировать, а поведение зафиксировано. На сентябрь 2026 поддерживаются 1.35, 1.36 и 1.37, так что на любом живом кластере вопрос не стоит.

Нужно ли переписывать манифесты с PreferClose на PreferSameZone?

Не обязательно и не срочно. PreferClose объявлен устаревшим алиасом PreferSameZone, но удалять его из API никто не планирует — это прямо записано в непринятых целях KEP-3015. Меняйте при очередной правке манифеста, чтобы имя не вводило в заблуждение следующего дежурного. Поведение при этом не изменится ни на йоту.

Что произойдёт, если поставить internalTrafficPolicy: Local и trafficDistribution: PreferSameNode одновременно?

Для внутреннего трафика выиграет Local: строгие политики имеют приоритет над предпочтениями. То есть при отсутствии локального эндпоинта пакет будет отброшен, никакого отката на удалённый под не случится. Такая комбинация — типичная причина жалобы «поставил PreferSameNode, а ничего не изменилось». Уберите Local или явно поставьте Cluster.

Как убедиться, что PreferSameNode реально работает, а не откатился до зональной семантики?

Посмотрите EndpointSlice: у каждого эндпоинта должен быть hints.forNodes с именем его узла. Если хинтов нет или они есть не у всех — kube-proxy отключит топологию для всего сервиса. Потом проверьте на трафике: запустите curl из пода на конкретном узле и посмотрите в логах бэкенда, какой под ответил. И обязательно проверьте сторонний service proxy — при замене kube-proxy на Cilium или аналог поддержка forNodes не гарантирована, и вы молча получите поведение PreferSameZone.

Имеет ли смысл PreferSameZone в кластере на своём железе в одном ЦОД?

Практически нет. Если метка topology.kubernetes.io/zone на узлах не проставлена или одинакова у всех, PreferSameZone (он же PreferClose) не изменит ничего. Ощутимую пользу он даёт в облаке, где межзональный трафик тарифицируется отдельно и добавляет заметную задержку. Для on-prem кластера в одной стойке правильный инструмент — именно PreferSameNode.

Не перегрузит ли PreferSameNode отдельные поды?

Может, и это задокументированный риск. В отличие от старой аннотации topology-mode: Auto, у нового поля нет эвристики и предохранителя на малое число эндпоинтов: есть локальный под — он забирает весь трафик узла. Поэтому PreferSameNode хорош для DaemonSet-подобных сервисов, где реплика есть на каждом узле с клиентами, и опасен для сервисов с двумя-тремя репликами на большой кластер. Балансировку проект перекладывает на Pod Topology Spread Constraints и автомасштабирование.

Нашей компании 10 человек, сервис записи у подрядчика в Kubernetes. Нам вообще нужно об этом думать?

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

Столкнулись с похожей задачей? Обращайтесь — решим

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

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи