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

PHP положил файл в /tmp, а соседний сервис его не видит: разбираем PrivateTmp в systemd

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~22 мин чтения
PHP положил файл в /tmp, а соседний сервис его не видит: разбираем PrivateTmp в systemd
Иллюстрация к статье «PHP положил файл в /tmp, а соседний сервис его не видит: разбираем PrivateTmp в systemd».

Обработчик не забирает файл, который PHP только что успешно записал. Права на каталоге 1777, SELinux выключен, путь в логах правильный — а файла по этому пути нет. Это не мистика и не гонка: у службы под systemd свой собственный /tmp. Ниже — как за две минуты убедиться в этом, как выглядит диагностика на живом сервере, чем это заканчивается в продакшене и какой способ починки я считаю единственно нормальным.

Один путь — два разных каталога

Сценарий, который я разбирал уже не десяток раз. Веб-часть на PHP принимает загруженный файл и кладёт его в /tmp/intake/upload-8842.mp4, соседний демон-обработчик должен подхватить его по inotify и перекодировать. PHP пишет успешно: file_put_contents вернул размер, в логе приложения есть строчка «saved». Обработчик молчит. Админ заходит по SSH, делает ls -l /tmp/intake/ — пусто. Дальше по накатанной: chmod 777, chown на пользователя службы, отключить AppArmor, поставить noexec обратно, перезагрузить сервер. Ничего не помогает, потому что права тут вообще ни при чём. Путь один и тот же, а каталога — два.

Виновник — директива PrivateTmp= в юните. Если она включена, systemd перед запуском процессов разворачивает для них отдельное пространство имён монтирования и подсовывает туда свои /tmp/ и /var/tmp/. Процессы службы видят по адресу /tmp своё личное хранилище; ваша SSH-сессия по тому же адресу видит хостовый /tmp. Оба каталога существуют одновременно, оба доступны на запись, и ни один инструмент, работающий с путями, вам об этом не скажет. Отсюда и характерная примета: приложение уверено, что файл записан, а глазами его никто не видит.

Самый быстрый признак, что вы попали именно в эту историю, — заглянуть в хостовый /tmp и посмотреть на служебные каталоги systemd. Они называются по шаблону systemd-private-<boot-id>-<имя юнита>-<случайный суффикс>, лежат с правами 0700 root:root, а внутри у каждого подкаталог tmp/ с режимом 1777. Вот листинг с тестовой виртуалки (Ubuntu 24.04, systemd 255; boot-id заменён на условный):

$ ls -ld /tmp/systemd-private-*
drwx------ 3 root root 4096 сен  2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-ModemManager.service-B9sZrr
drwx------ 3 root root 4096 сен  2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-apache2.service-4JPR4t
drwx------ 3 root root 4096 сен  2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-systemd-logind.service-NvBFv2
drwx------ 3 root root 4096 сен  2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-polkit.service-rdt4DM

$ ls -la /tmp/systemd-private-*-apache2.service-*/
drwx------  3 root root 4096 сен  2 06:32 .
drwxrwxrwt 14 root root 4096 сен  7 15:14 ..
drwxrwxrwt  2 root root 4096 сен  2 06:32 tmp

Обратите внимание: у /var/tmp ровно такие же каталоги-близнецы. Если ваш обмен идёт через /var/tmp, потому что «там файлы переживают перезагрузку», ситуация та же самая, только диагностируется ещё позже — вы неделю думаете, что данные копятся, а их нет.

Если вы уже поставили chmod 777 на каталог обмена и это не помогло — остановитесь и проверьте namespace. Права вы уже испортили, а причина не в них.
Памятка: Один путь — два разных каталога — схема
Памятка: Один путь — два разных каталога. Открыть схему в полном размере

Что на самом деле делает PrivateTmp=

Документация тут короткая и точная, я её перечитываю перед каждым спором. По systemd.exec(5) директива принимает булево значение либо специальное disconnected. При включении для процессов юнита создаётся новое пространство имён файловой системы, каталоги /tmp/ и /var/tmp/ внутри него не разделяются с процессами снаружи, и — это важнее всего для эксплуатации — все временные файлы, созданные службой в этих каталогах, удаляются после остановки службы. По умолчанию false. Если включён DynamicUser=, в актуальной редакции мануала подразумевается disconnected (на старых версиях systemd, где этого значения ещё нет, DynamicUser= включал обычную приватность /tmp).

Разница между значениями практическая, а не косметическая. При PrivateTmp=true (или yes) подложкой служат обычные хостовые /tmp/ и /var/tmp/ — те самые каталоги systemd-private-*, которые видно снаружи от root. Именно в этом режиме работает JoinsNamespaceOf=: два и более юнита можно запустить внутри одного приватного /tmp. Побочный эффект — юнит автоматически получает Wants= и After= на все mount-юниты, нужные для доступа к /tmp/ и /var/tmp/ на хосте, плюс неявный After= на systemd-tmpfiles-setup.service.

При PrivateTmp=disconnected каталоги подкладываются совершенно новым экземпляром tmpfs, то есть хранилище полностью отрезано от хостового пространства имён. Такой tmpfs не разделяется с другими юнитами даже через JoinsNamespaceOf=. Значение появилось в systemd 257; в systemd 260 добавили ещё одну тонкость — для юнитов с PrivateTmp=yes и DefaultDependencies=no без явного требования /tmp/ теперь используется disconnected-каталог, как если бы так и было написано; а если для /var/ нет явного упорядочивания, приватный /var/tmp/ вообще не создаётся — хостовый /var/tmp остаётся виден юниту. Так что «yes» на свежих системах не всегда означает то, что означало пять лет назад.

Ещё один момент, который стоит проговорить, потому что вокруг него много путаницы. PrivateTmp=true сам по себе не переносит хранение в оперативную память: данные остаются на хостовых /tmp и /var/tmp, со всеми их свойствами по месту, скорости и переживанию перезагрузки. Отдельный RAM-диск получается либо из-за режима disconnected, либо из-за того, что /tmp у вас и так смонтирован как tmpfs на уровне системы — это разные вещи, и их постоянно смешивают.

«Все временные файлы удаляются после остановки службы» — это не примечание мелким шрифтом, а главный эксплуатационный риск. Очередь необработанных файлов, лежащая в приватном /tmp, исчезает на ближайшем деплое.
PHP положил файл в /tmp, а соседний сервис его не видит: разбираем PrivateTmp в systemd — схема
Схема к статье. Открыть схему в полном размере

Диагностика за две минуты

Первое, что я делаю, — спрашиваю у systemd, что он вообще думает про этот юнит. Не читаю юнит-файл: там могут быть drop-in из /etc/systemd/system/<юнит>.service.d/, которые вы не заметите, а show покажет итоговое значение после всех наложений.

systemctl show apache2 -p PrivateTmp -p DynamicUser -p FragmentPath
# DynamicUser=no
# PrivateTmp=yes
# FragmentPath=/usr/lib/systemd/system/apache2.service

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

$ sudo systemd-run -p PrivateTmp=yes --unit=ptmp-demo --remain-after-exit \
    /bin/sh -c 'echo hello-from-service > /tmp/demo.txt; sleep 300'
Running as unit: ptmp-demo.service

$ ls -l /tmp/demo.txt
ls: cannot access '/tmp/demo.txt': No such file or directory

$ ls -l /tmp/systemd-private-*ptmp-demo*/tmp/
-rw-r--r-- 1 root root 19 сен  7 15:14 demo.txt

Третье — доказываю, что дело именно в namespace, а не в путях. Сравниваю инод пространства имён монтирования у процесса службы и у своей оболочки, потом захожу внутрь через nsenter и смотрю на /tmp глазами самой службы. Это же лучший способ вытащить застрявший файл, не трогая конфигурацию:

P=$(systemctl show -p MainPID --value ptmp-demo.service)
readlink /proc/$P/ns/mnt      # mnt:[4026532503]
readlink /proc/self/ns/mnt    # mnt:[4026531841]

sudo nsenter -t $P -m -- ls -l /tmp/
# -rw-r--r-- 1 root root 19 сен  7 15:14 demo.txt
sudo nsenter -t $P -m -- cat /tmp/demo.txt
# hello-from-service

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

$ sudo systemctl stop ptmp-demo.service
$ ls -d /tmp/systemd-private-*ptmp-demo*
ls: cannot access '/tmp/systemd-private-*ptmp-demo*': No such file or directory
nsenter -t PID -m — ваш аварийный доступ к приватному /tmp живой службы. Скопируйте оттуда файлы ДО того, как перезапустите юнит: рестарт их снесёт.

Как это выглядело в бою: школа актёрского мастерства на 34 рабочих места

Условно назову клиента школой актёрского мастерства «Роль и образ». 34 рабочих места: администраторы, педагоги, приёмная комиссия, монтажёр. Своя веб-форма приёма заявок: абитуриенты загружают анкету и видеовизитку, дальше файл подхватывает фоновый обработчик — перекодирует ролик в лёгкий формат для просмотра и прикладывает его к карточке в CRM школы. Стенд простой: Ubuntu 24.04 LTS, systemd 255, nginx, php8.3-fpm из репозитория дистрибутива и Python-служба media-intake-worker.service. Обмен был сделан через каталог /tmp/intake — не мной, но принимал в работу я, так что вина общая.

В конце квартала подрядчик по безопасности прислал отчёт systemd-analyze security и попросил «закрыть очевидное». Один из пунктов — php8.3-fpm без изоляции временных файлов. Замечание по сути верное: в Debian и Ubuntu штатный юнит php*-fpm.service идёт без PrivateTmp, на тестовой машине systemctl show php8.3-fpm -p PrivateTmp отдавал no. Коллега сделал ровно то, что напрашивалось: systemctl edit php8.3-fpm, drop-in с PrivateTmp=true, рестарт. В пятницу вечером, разумеется.

Дальше три дня тишины. Портал работал: форма принимала файл, показывала галочку, в логе PHP аккуратно писалось «saved /tmp/intake/...». Воркер по inotify не получал ни одного события — он смотрел на хостовый /tmp/intake, а PHP писал в /tmp/systemd-private-...-php8.3-fpm.service-XXXXXX/tmp/intake. Никто не жаловался, потому что перекодированный ролик и раньше появлялся в карточке не мгновенно. Разбор начался только в понедельник, когда приёмная комиссия не нашла 87 видеовизиток за пятницу, субботу и воскресенье — как раз шёл набор на осенний поток. К этому моменту их уже не существовало: в субботу ночью прошёл штатный деплой с рестартом php-fpm, и приватный каталог вместе с очередью систем удалил — ровно так, как написано в мануале.

Что мы сделали. Во-первых, ушли из /tmp совсем: обменный каталог переехал в /var/lib/media-intake, поднимается через StateDirectory=, владелец — группа media-intake, режим 2770 с setgid, чтобы файлы от php-fpm автоматически наследовали группу и воркер их читал. Во-вторых, PrivateTmp=true у php-fpm оставили — замечание безопасников было правильным, ломало не оно, а обмен через общий /tmp. В-третьих, добавили в воркер простейший сторож: если за 30 минут в каталоге не появилось ни одного файла в рабочее время — алерт в Telegram. За семь месяцев после переделки — ни одной потери; сторож сработал дважды, оба раза по делу (упал php-fpm после обновления PHP).

Классика жанра: правку внёс один человек, сломалось у другого, а тишина в мониторинге держалась трое суток. Любой каталог, через который два процесса обмениваются данными, обязан иметь сторож «давно ничего не приходило».
Цифры и версии: Как это выглядело в бою: школа актёрского мастерства на 34 рабочих места — схема
Цифры и версии: Как это выглядело в бою: школа актёрского мастерства на 34 рабочих места. Открыть схему в полном размере

Четыре способа починить и тот, который я выбираю

Вариант первый — общий приватный /tmp через JoinsNamespaceOf=. Директива описана в systemd.unit(5): юнит перечисляет другие юниты, чьё сетевое и/или временное пространство имён он хочет разделить, обратная зависимость подразумевается автоматически. Работает только если PrivateTmp= включён у обоих юнитов, и только в режиме true — отдельный tmpfs при disconnected не разделяется принципиально. Есть и неприятная тонкость из мануала: если запущено несколько перечисленных юнитов и они не разделяют namespace между собой, то не определено, к какому из них вы присоединитесь.

# /etc/systemd/system/media-intake-worker.service.d/override.conf
[Unit]
JoinsNamespaceOf=php8.3-fpm.service

[Service]
PrivateTmp=true

Способ рабочий, я держу его в арсенале для жёстко связанной пары «сервис — его личный помощник», которые всегда стартуют и падают вместе. Для обмена между веб-мордой и фоновым обработчиком — не советую: вы намертво связываете два юнита порядком запуска и получаете зависимость, которую через полгода никто не вспомнит. Плюс любой рестарт «хозяина» namespace всё так же вычищает файлы.

Вариант второй — просто выключить изоляцию drop-in-ом PrivateTmp=no. Честно скажу: иногда это правильный ответ. Если речь про внутренний сервер без внешнего доступа, а служба легаси и переписывать её никто не будет, — выключайте и живите дальше, риск тут сильно преувеличен. Только делайте это осознанно и с комментарием в drop-in, а не «чтобы заработало». И не выключайте у того, что смотрит наружу.

Вариант третий и мой основной — вообще не обмениваться через /tmp. Временный каталог на то и временный: он общий для всей системы, чистится системными правилами, а в пакетных юнитах всё чаще ещё и изолируется через PrivateTmp=. Заводим собственный каталог с внятным владельцем и правами, объявляем его через StateDirectory= (для данных, переживающих перезагрузку) или RuntimeDirectory= (для эфемерных, живущих в /run), обе стороны кладём в одну группу.

[Service]
User=www-data
Group=media-intake
StateDirectory=media-intake
StateDirectoryMode=2770
UMask=0002
PrivateTmp=true

Каталог создаётся сам в /var/lib/media-intake, путь приезжает в процесс переменной $STATE_DIRECTORY, PrivateTmp можно спокойно оставить включённым — он больше ни на что не влияет. Это и есть правильная развязка: изоляция временных файлов отдельно, канал обмена отдельно. Вариант четвёртый — BindPaths=, прокинуть конкретный хостовый каталог внутрь namespace службы. Полезен, когда путь /tmp/что-то намертво зашит в чужом коде и переписать его нельзя:

[Service]
PrivateTmp=true
BindPaths=/var/lib/media-intake:/tmp/intake

Служба продолжает писать в /tmp/intake, а физически данные лежат в /var/lib/media-intake и видны всем. Костыль, но честный и локализованный — по крайней мере он написан в юните, а не спрятан в чьей-то голове.

Правило, которое экономит часы: /tmp — это скрэтч одного процесса, а не шина обмена между сервисами. Как только файл нужен второму процессу — ему место в /var/lib или /run, с явным владельцем.

Грабли, о которые бьются регулярно

Первое и самое коварное — поведение зависит от дистрибутива, и ваш опыт с прошлого места работы тут не работает. Апстрим php-fpm.service.in из php-src содержит PrivateTmp=true с большим комментарием, зачем это надо; Fedora и RHEL везут этот юнит практически без изменений. А в Debian и Ubuntu, по моим проверкам на тестовых машинах, штатные php*-fpm.service идут без него. Зато apache2.service в Debian поставляется с PrivateTmp=true, а nginx.service в тех же проверках показывал no. То есть одна и та же связка «PHP пишет в /tmp» ломается на CentOS/RHEL и работает на Debian, или ломается под Apache и работает под nginx. Отсюда бесконечные форумные треды, где половина участников уверена, что у собеседника «что-то не то с системой».

Второе — Debian 13 trixie поменял правила игры вокруг самого /tmp, и это накладывается на PrivateTmp самым неприятным образом. Теперь /tmp по умолчанию хранится в tmpfs, то есть в памяти, с лимитом до 50 % ОЗУ (память занимается только реально созданными файлами). Плюс на новых установках systemd-tmpfiles регулярно удаляет старые файлы: в /tmp — через 10 дней с момента последнего использования и при перезагрузке, в /var/tmp — через 30 дней без удаления при перезагрузке. При апгрейде с bookworm старое поведение сохраняется файлом /etc/tmpfiles.d/tmp.conf, но на свежих машинах его нет. Если у вас в /tmp годами копилась «временная» база на 40 ГБ, вы узнаете об этом дважды: сначала по внезапному расходу памяти, потом по пропаже данных.

Третье — мусор от аварийных завершений. Каталоги /tmp/systemd-private-* удаляются при штатной остановке юнита; если систему жёстко выключили или systemd убили, они остаются. Сами по себе они безобидны, но при разборе полётов вводят в заблуждение: вы находите файл в каталоге давно перезапущенной службы и думаете, что смотрите на актуальные данные, а это слепок с прошлой загрузки — boot-id в имени каталога другой. Сверяйте boot-id: cat /proc/sys/kernel/random/boot_id даст текущий (в имени каталога он записан без дефисов). Убирать такие хвосты руками обычно не нужно: если /tmp смонтирован как tmpfs, они исчезают при перезагрузке, а на диске их со временем вычищает правило q /tmp 1777 root root 10d из штатного tmpfiles.d/tmp.conf.

Четвёртое — PHP-специфика, о которой забывают. Директивы upload_tmp_dir и sys_temp_dir по умолчанию пусты, то есть PHP берёт системный временный каталог, а он в изолированной службе приватный. Загруженные файлы, распакованные архивы, промежуточные результаты imagick — всё это уезжает в приватный /tmp. А вот session.save_path в Debian задан явно как /var/lib/php/sessions, поэтому сессии от изоляции обычно не страдают, и это сбивает диагностику: «сессии же работают, значит с путями всё в порядке». Не значит.

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

Что делать в первую очередь, а на что забить

В первую очередь — инвентаризация обменов. Пройдитесь по своим сервисам и выпишите все места, где два разных процесса общаются через файлы. Обычно их немного, три-пять на сервер, и половина из них живёт в /tmp по историческим причинам. Для каждого проверьте systemctl show -p PrivateTmp у обеих сторон. Это полчаса работы и лучшее вложение времени из всего, что написано в статье.

Во вторую — уберите из /tmp всё, что должно пережить рестарт службы. Очереди, «файлы на обработку», результаты выгрузок, которые кто-то потом заберёт. Не потому что PrivateTmp, а потому что /tmp по определению не место для данных, которые кому-то ещё нужны, и на новых Debian их теперь ещё и вычищают по расписанию. Перенос в /var/lib через StateDirectory= занимает пятнадцать минут на сервис.

А вот на что можно спокойно забить. Не надо ради красоты переводить все юниты на PrivateTmp=disconnected: режим свежий (systemd 257), на RHEL 9 и Debian 12 его просто нет, а выигрыш в безопасности над обычным true невелик, если у вас нет сценария с общим хостовым /tmp для недоверенных процессов. Не надо чистить руками осиротевшие каталоги systemd-private-* — они занимают килобайты, а новая остановка юнита их не тронет (boot-id другой) — их уберёт перезагрузка при /tmp на tmpfs или суточная очистка systemd-tmpfiles по сроку. И не надо переписывать легаси-скрипт, который двадцать лет пишет свой лог в /tmp/foo.log и никому не мешает: изоляция ему не вредит, просто знайте, где искать этот лог.

И последнее, самое практичное. Заведите привычку: любое ужесточение sandbox-настроек systemd — PrivateTmp=, ProtectSystem=, ProtectHome=, DynamicUser= — делается не рестартом «посмотрим что будет», а сначала прогоном через systemd-run с теми же -p параметрами. Тридцать секунд проверки против трёх суток тихой потери документов — размен, который окупается с первого раза.

Если после статьи вы сделаете ровно одно действие — проверьте systemctl show -p PrivateTmp у обеих сторон вашего файлового обмена. В моей практике это находит проблему примерно в каждом третьем стенде, который я принимаю на обслуживание.
Порядок действий: Что делать в первую очередь, а на что забить — схема
Порядок действий: Что делать в первую очередь, а на что забить. Открыть схему в полном размере

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

Как быстро понять, что у службы свой /tmp?

Выполните systemctl show <юнит> -p PrivateTmp -p DynamicUser. Если PrivateTmp=yes/true/disconnected или DynamicUser=yes — у службы отдельные /tmp и /var/tmp. Дополнительно посмотрите ls -d /tmp/systemd-private-*: там будут каталоги с именем вашего юнита. Окончательное доказательство — сравнить readlink /proc/<PID службы>/ns/mnt и readlink /proc/self/ns/mnt: разные иноды означают разные пространства имён монтирования.

Можно ли достать файл из приватного /tmp живой службы?

Да, двумя способами. От root каталог /tmp/systemd-private-<boot-id>-<юнит>-<суффикс>/tmp/ доступен напрямую — это работает при PrivateTmp=true. Универсальный вариант, работающий и для disconnected: войти в namespace процесса командой nsenter -t $(systemctl show -p MainPID --value <юнит>) -m -- ls -l /tmp. Забирайте файлы до перезапуска службы: при остановке приватный каталог удаляется вместе с содержимым.

Как разделить приватный /tmp между двумя службами?

Через JoinsNamespaceOf=<другой юнит> в секции [Unit]. Обязательные условия: PrivateTmp= включён у обоих юнитов и именно в режиме true — отдельный tmpfs при disconnected не разделяется даже с этой директивой. Учтите, что если перечислено несколько уже запущенных юнитов с разными namespace, мануал прямо говорит: к какому из них произойдёт присоединение, не определено. Для обмена данными между сервисами я предпочитаю не это, а отдельный каталог через StateDirectory=.

Отключать PrivateTmp — это дыра в безопасности?

Риск часто преувеличивают. PrivateTmp защищает от классических атак через предсказуемые имена файлов и симлинки в общем /tmp и от подглядывания за временными файлами соседних процессов. Для сервиса, смотрящего в интернет, включать стоит. Для внутренней легаси-службы на изолированном сервере выключение — приемлемый компромисс, если альтернатива в том, чтобы неделю переписывать чужой код. Правильный же путь — оставить изоляцию включённой и увести обмен из /tmp в собственный каталог.

PrivateTmp=true — это RAM-диск?

Нет. При true подложкой служат обычные хостовые /tmp и /var/tmp, то есть тот же носитель, что и у системы; сам по себе этот режим ничего в память не переносит. В памяти каталоги окажутся либо при PrivateTmp=disconnected (создаётся новый экземпляр tmpfs), либо если /tmp смонтирован как tmpfs на уровне системы — так, например, стало по умолчанию в Debian 13 с лимитом до 50 % ОЗУ.

Почему на RHEL ломается, а на Debian та же схема работает?

Из-за разных дистрибутивных юнитов. Апстримный php-fpm.service содержит PrivateTmp=true, и Fedora с RHEL везут его так же; в Debian и Ubuntu пакетные php*-fpm.service, по моим проверкам, идут без этой директивы. Похожая история с веб-серверами: в Debian apache2.service поставляется с PrivateTmp=true, а у nginx.service я её не встречал. Поэтому переносить вывод «у нас так работало» с одного дистрибутива на другой нельзя — проверяйте через systemctl show на конкретной машине.

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

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

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

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

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

Источники

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