С арендованного тестового сервера на домашний переехали 14 сайтов: WordPress, два проекта на Laravel, крупный сайт на Bitrix и статика, около 29 ГБ на старте. Технически это rsync файлов и дампы баз. На практике почти каждая ошибка за эти выходные не выдавала сообщения, а тихо давала неверный результат: сайт открывается, но не тот; база импортируется, но в двадцать раз медленнее; текст на месте, но кракозябрами. Ниже — все ловушки по порядку.
Это шестая часть цикла «Домашняя инфраструктура».
Главный урок: копия должна быть один к одному
Первым заходом я «сэкономил»: не стал копировать node_modules, wp-content/cache и прочие кэши — «их можно пересобрать». Пересобрать их можно, но это стоило часа пересборки, которого можно было не тратить. Правило после этого:
- копируем всё; исключаем только файлы с реквизитами окружения:
wp-config.php,.env, конфиги подключения к базе; - никакого
--delete:rsync -a --update, повторный прогон дописывает недостающее и ничего не стирает; - проверка «забрано всё» — по числу файлов, а не по объёму:
rsync -an --update --itemize-changesдолжен показать ноль к переносу.
Ловушка 1: переменные одного сайта утекли в другой
Сайты поднимались в цикле, и для каждого читался его .env через set -a; . .env. Переменная COMPOSE_PROJECT_NAME оставалась в окружении и перебивала .env следующего сайта. Итог: контейнер одного сайта оказался смонтирован на каталог другого. Ошибок — ноль, просто открывается не тот сайт.
# вместо source всего файла — точечно и явно
name=$(grep -E '^COMPOSE_PROJECT_NAME=' "$dir/docker/.env" | cut -d= -f2)
docker compose -p "$name" --project-directory "$dir/docker" up -d
# и проверка, что смонтировано именно своё
docker inspect -f '{{range .Mounts}}{{.Source}} {{end}}' "${name}-php-1"
Ловушка 2: chown сломал базу
Чтобы поправить права, я сделал chown -R на весь каталог сайта, включая каталог данных MariaDB. База перестала читать свои файлы (OS error 13), и дальше пошли обманчивые сообщения вроде Table … is read only, хотя дело было совсем не в режиме таблицы. Права меняем только в docker/ и src/, данные базы не трогаем.
Рядом — ещё одна мелочь: MariaDB читает MARIADB_USER и MARIADB_PASSWORD из окружения только при инициализации пустого каталога данных. Поменяли пароль в .env у уже работающей базы — ничего не изменится.
Ловушка 3: импорт базы шёл со скоростью 45 КБ/с
Дамп крупного сайта — таблица на 6,6 миллиона строк — заливался так медленно, что я подумал на диск. Виноват был буферный пул InnoDB: 192 МБ на такой объём. Увеличил на ходу, без перезапуска:
SET GLOBAL innodb_buffer_pool_size = 640 * 1024 * 1024;
Попадания в буфер стали 999 из 1000, скорость — около 5000 строк в секунду, остальные 859 таблиц легли за пару минут. Прогресс, кстати, смотрел по числу таблиц в information_schema и по номеру записи в SHOW PROCESSLIST: счётчик Innodb_rows_inserted всё это время показывал ноль, и верить ему было нельзя.
Ловушка 4: кракозябры из-за прав, а не кодировки
На сайте с Bitrix после переноса вместо русского текста пошла мешанина. Первое желание — перекодировать дамп. Но данные в базе были целы: шестнадцатеричное представление строк совпадало с дампом байт в байт. Причина оказалась в правах. Каталог с дополнительным конфигом MariaDB приехал с правами 700, база (uid 999) не смогла прочитать свой my.cnf и молча запустилась с кодировкой по умолчанию latin1. init_connect с нужной кодировкой не применился, и на выходе получилась двойная перекодировка.
-- проверка, с какими настройками на самом деле работает база
SELECT @@character_set_server, @@collation_server, @@init_connect;
Лечение — chmod a+rX на каталог конфига и перезапуск контейнера. Ни одного байта в базе менять не пришлось.
Ловушка 5: кэш миниатюр пересобирался на каждом запросе
PHP в новых контейнерах работает от пользователя с uid 1000, а файлы приехали от uid 33 старого сервера. 401 каталог и 51 257 файлов оказались недоступны для записи, и кэш миниатюр пытался создаваться заново на каждом запросе — 468 предупреждений в сутки. После chown 1000:1000 по деревьям сайтов (72 966 объектов) за полчаса создалось 1158 файлов кэша, предупреждений — ноль.
Мелочи, каждая на полчаса
docker restartне применяет правки compose: он перезапускает контейнер с той командой, с которой его создали. Поменялиcommand:— нуженup -d;- Laravel ждал корень проекта, а не
public/: приложение строило пути отDOCUMENT_ROOT/public. Пришлосьtry_files $uri /public$uri /public/index.phpи явный запрет наapp,config,storage/logs,vendor,artisan,composer.*; - абсолютный симлинк
public/storageуказывал на путь старого сервера и вернулся при повторном rsync. Пересоздан относительным; sshвнутриwhile readсъедает стандартный ввод цикла, и цикл обрабатывает только первую строку. Нуженssh -n;- «55 425 изменённых файлов» в git — это только бит исполнения (в репозитории 755, на диске 664). Лечится
git config core.filemode false; - сайт отдавал 500, потому что в дампе приехала опция режима обслуживания. Не дефект переноса — состояние источника;
- Let’s Encrypt упёрся в лимит — пять неудачных проверок в час на имя, — пока DNS ещё указывал на старый сервер. Сертификаты выпускать после переключения DNS, а пробовать — на тестовом сервере ACME.
Перед переносом — проверка на заражение
Сайты переезжали вскоре после массового взлома WordPress через уязвимость ядра, поэтому перед переносом каждого сайта прогоняли поиск по известным сигнатурам того взлома. Искать надо по содержимому файлов, а не по факту наличия mu-plugins или дропинов — у многих сайтов они легальны. И отдельная находка на старом сервере: приватный SSH-ключ лежал с правами 644, читаемый всеми. Такой ключ считаем скомпрометированным и заменяем.
Переносите сайты на новый сервер?
Напишите, сколько их и на чём они работают, — составлю порядок переноса и проверки, чтобы ничего не потерялось молча.
// загружаю комментарии…