Содержание статьи
В интернете сегодня можно не только развлекаться, но и учиться, работать и зарабатывать. Количество сайтов растет ежесекундно, услуги хостинга также становятся привлекательными и множатся как грибы после дождя. Бывает, что хостер оправдывает все ожидания, но иногда приходится и переезжать. Можно нанять фрилансера, но лучше научиться делать это самому. Сегодня тебя ждет небольшая инструкция именно на этот случай.
Постановка задачи
Ситуация самая жизненная. Интернет-магазин, размещенный на шаред-хостинге, после запуска начал получать клиентов, но появились пожелания к функциональности, и разработчики активно занялись доработкой сайта. Выяснилось, что, когда в этом участвует несколько человек, постоянно копировать файлы через FTP для теста, да и еще на рабочий сайт, очень проблемно. Терялся контроль, кто когда что сделал, нужно было беспокоиться о сохранении оригинальных файлов, чтобы было легко откатиться. Владельцу приходилось или согласовывать правки, или копировать все самому. Разработчик не мог сразу посмотреть результат и ждал. Процесс сильно тормозился. В итоге пришли к тому, что нужно использовать возможности Git и создать новый сайт-зеркало, где можно было бы все обкатывать. При такой схеме разработчик мог сразу тестировать код, а в случае одобрения код переносили в master и выкладывали уже на рабочий сайт. Также можно легко отслеживать коммиты.
Вторая проблема: хостинг постоянно падал. Причину в итоге нашли: Entry processes limit — параметр, который определяет количество CGI/PHP-процессов, входящих внутрь виртуального контейнера, и о котором не сильно любят говорить маркетологи хостера. На графиках его тоже не видно, только маленькая графа в таблице. В итоге при небольших нагрузках CPU и RAM (не более 20%) сервер вообще не работал даже при минимальном количестве посетителей. В итоге было принято решение переезжать.
Первоначальные настройки сервера
OC в VDS устанавливается автоматически. Достаточно выбрать версию и вариант с веб-панелью или без и чуть подождать, пока не придет письмо с данными для входа. На хостингах предлагаются и разные веб-панели. Когда этот материал создавался, Vesta не поддерживала Ubuntu 16.04 и необходимости в ней не было, поэтому выбрали чистую систему. Все дальнейшие действия ведутся от имени root. Первым делом проверяем локаль, часовой пояс и время. Вообще, веб-приложения обычно не обращают внимания на некоторые системные настройки, но иногда попадается именно тот случай, поэтому лучше сразу сделать все правильно.
# locale
Если в ответ получаем отличное от ru_RU.UTF — перенастраиваем.
# locale-gen ru_RU ru_RU.UTF-8 ru_RU ru_RU.UTF-8
# localedef -c -i ru_RU -f UTF-8 ru_RU.UTF-8
# dpkg-reconfigure locales
# update-locale LANG=ru_RU.UTF-8
Проверяем время:
# date
Если часовой пояс не соответствует — переконфигурируем.
# dpkg-reconfigure tzdata
Обновляем сервер:
# apt update && apt upgrade
Теперь можем ставить сервисы.
Ставим веб-сервер
Несмотря на их разнообразие, выбор установки обычно сводится к трем вариантам: Apache, nginx или nginx как реверс Apache. Apache очень гибок в настройках и использует модули для обработки динамических запросов, поэтому хорошо справляется с динамикой. Nginx хорош в отдаче статики и потребляет меньше ресурсов, но для обработки динамики использует сторонний модуль, что снижает скорость и чуть усложняет настройки. В зависимости от конкретного приложения каждый из них может иметь свои плюсы и минусы и показывать разную скорость. Поэтому окончательный выбор веб-сервера всегда приходится подтверждать практикой, подбирая оптимальный вариант. Проблема nginx — то, что в некоторых специфических движках следует вручную возиться с редиректами, когда на Apache все будет работать буквально из коробки, достаточно просто включить mod_rewrite.
Нагрузочное тестирование можно произвести при помощи ab (Apache Benchmark, входит в apache2-utils) или siege. Причем лучше проверить с localhost и удаленного узла, чтобы видеть, как работает сеть.
# ab -c 10 -n 6000 http://example.org/
Хотя ab — это скорее для себя, чтобы оценить эффективность установок. Человека со стороны обычно интересует только то, что показывает Google PageSpeed, поэтому ориентироваться следует и на него.
В последнем случае сайт на старом хостинге давал 60, после переноса на VDS (с такими же параметрами) он в Apache в установке по умолчанию показывал 72, nginx с голым конфигом — 62, после добавления сжатия — 78. На этом и остановились, выбрали nginx. В репозитории несколько пакетов, для большинства ситуаций достаточно базового core, содержащего все основные модули, для PHP нам понадобится FPM.
# apt nginx install nginx php7.0-fpm
Файл в общем стандартный, но для скорости добавим кеширование и сжатие. Точные параметры в каждом случае необходимо подбирать опытным путем, но для небольших и средних проектов таких установок обычно бывает достаточно. В nginx.conf добавляем или, если повезло, снимаем комментарии в секции http:
# nano /etc/nginx/nginx.conf
http {
....
open_file_cache max=200000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server_tokens off;
server_names_hash_bucket_size 64;
reset_timedout_connection on;
client_body_timeout 10;
gzip on;
gzip_disable "msie6";
gzip_static on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
}
Создаем настройки для домена:
# nano /etc/nginx/sites-available/example.org
server {
listen 80;
server_name example.org default;
root /var/www/example.org;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
rewrite_log on; # Полезная настройка для отладки
index index.php;
try_files $uri $uri/ /index.php?$query_string;
location ~ \.php$ {
include /etc/nginx/fastcgi_params;
# fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
# Кешируем картинки и txt/XML/JS/CSS. Можно убрать ненужное или что-то добавить
location ~* ^.+\.(jpg|jpeg|gif|png|js|css|txt|xml)$ {
access_log off;
expires 30d;
}
# Блокируем доступ к каталогу .git (о нем дальше), по аналогии добавляем свои правила
location ~ /\.git {
deny all;
}
}
Это общий пример для стандартного движка. Некоторые движки вроде OpenCart или WebAsyst требуют специфических настроек, и даже не всегда работает то, что предлагается в Сети.
Проверяем, работает ли сжатие. Это можно сделать, просмотрев заголовок Content-Encoding в Firebug (он должен показывать gzip), или при помощи специального сервиса.
Включаем сайт:
# ln -s /etc/nginx/sites-available/example.org /etc/nginx/sites-enabled/example.org
Перезапускаем nginx:
# service nginx restart
Но работать еще не будет. Нужно настроить PHP. Для FPM все установки находятся в /etc/php/7.0/fpm. Проверяем, что в pool.d/www.conf учетная запись совпадает с используемой nginx и включен сокет.
# nano /etc/php/7.0/fpm/pool.d/www.conf
user = www-data
group = www-data
listen = /run/php/php7.0-fpm.sock
Кроме этого, можно обратить внимание на параметры, определяющие количество процессов, которые будут обслуживать PHP-запросы.
pm = dynamic
pm.max_children = 15
pm.start_servers = 6
pm.min_spare_servers = 2
pm.max_spare_servers = 6
На чуть загруженных серверах может не хватать количества процессов. В логах об этом сразу скажут.
# cat /var/log/php7.0-fpm.log
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Еще важный файл php.ini. Параметров там много, и можно рассказывать долго. Но изначально следует включить сжатие, установить максимальный размер файла на аплоад, подключить mail(), сессии и очень желательно включить акселератор OPcache.
# nano /etc/php/7.0/fpm/php.ini
zlib.output_compression = On
upload_max_filesize = 2M
[mail function]
sendmail_path = sendmail -t -i
[Session]
session.save_path = "/var/lib/php/sessions"
[opcache]
opcache.enable=1
opcache.memory_consumption=128
pcache.max_accelerated_files=2000
Обязательно проверяем права доступа на /var/lib/php/sessions, чтобы туда мог писать nginx, иначе сессии не будут образовываться. Перезапускаем.
# service php7.0-fpm restart
Теперь перенос сайта. Если переносим с другого хостинга, то там создаем бэкап. Если есть хостинговая веб-панель, то можно использовать ее возможности. Или вручную:
# tar -zcvf backup.tar.gz /var/www
И на новом месте распаковываем:
# tar -zxvf backup.tar.gz /var/www
Но для сайта нам нужна СУБД.
Ставим MySQL
C MySQL все очень просто. Вводим
# apt install mysql-server
На запрос указываем пароль root, и уже можно работать. Если не требуется доступ к нему извне, то следует разрешить использовать только локалхост или сокет.
# nano /etc/mysql/my.cnf
socket = /var/run/mysqld/mysqld.sock
skip-networking
# bind-address = 127.0.0.1
После изменений перезапускаем:
# service mysql restart
Остальные параметры обычно настроены оптимально для большинства ненагруженных узлов. В процессе работы следует смотреть за журналами и значениями текущих переменных.
# mysqladmin -uroot -p extended-status
Вероятно, что-то придется подкрутить. Для быстрой оптимизации лучше воспользоваться советами, выдаваемыми скриптом MySQLTuner, который есть в репозитории.
Переносим базу
Архивируем на старом хосте базу данных через phpMyAdmin или вручную:
# mysqldump -uroot -p workbase > base.sql
Если нужны все базы, то используем ключ -A. Копируем на новый сервер. Создаем базу workbase, импортируем старые данные и создаем учетную запись baseadmin для работы с этой базой:
# mysql -uroot -p
mysql> CREATE DATABASE workbase;
mysql> use workbase;
mysql> source base.sql;
mysql> GRANT ALL PRIVILEGES ON workbase.* to 'baseadmin'@'localhost' IDENTIFIED BY 'password';
Заодно добавим учетку с меньшими правами для бэкапа.
mysql> GRANT SELECT, LOCK TABLES ON *.* to 'backup'@'localhost' IDENTIFIED BY 'backup_pass';
mysql> FLUSH PRIVILEGES;
Настраиваем подключение к БД в параметрах движка, и можно работать.
Почтовый сервер
Хотя некоторые приложения могут напрямую подключаться к внешнему SMTP (что очень даже хорошо: в случае взлома провайдер не забанит аккаунт из-за рассылки спама), но в большинстве приложений для отправки почты используют функцию mail()
, а поэтому нам потребуется локальный SMTP-сервер. Здесь опять два варианта: настроить полноценный сервер или установить прокси, который будет подменять SMTP, переправляя запросы на внешний сервер (потребуется почтовый ящик). В качестве последнего отлично подходит ssmtp, который есть в репозитории. Хотя установить «большой» сервер в минимальной конфигурации — дело пяти минут.
# apt install postfix
В процессе выбираем «Интернет-сайт» и указываем домен.
# nano /etc/postfix/main.cf
myhostname = example.org
mydestination = $myhostname, localhost.localdomain, localhost
# Чтобы подключались только с локальных адресов
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
Перезапускаем:
# service postfix restart
И почта должна уже работать. Единственный момент — если почтовый ящик домена привязан к Gmail, то, когда в него идет письмо с этого же домена, технология DMARC Gmail может его отбросить как спам. Хотя если отправитель будет другой, то все будет работать. В этом случае следует убедиться, что SMTP-сервер не отправляет hostname, которое дал серверу хостер. Строку mydestination следует изменить на
mydestination = $mydomain, localhost.$mydomain, localhost
Мониторинг и бэкап
Две важные вещи — мониторинг и бэкап. После установки сайт может падать из-за неоптимальных настроек. Поэтому лучше сразу установить хотя бы простое решение, позволяющее перезапускать сервисы. В репозиториях есть отличные утилиты healt-check или monit, проверяющие не только сервисы, но и общее состояние системы. Настроек там много, и на первых порах или на легких сайтах можно обойтись простеньким скриптом. Для nginx он будет выглядеть примерно так:
# nano monitor.sh
#!/bin/bash
RESTART="/etc/init.d/nginx restart"
PGREP="/usr/bin/pgrep"
HTTPD="nginx"
$PGREP ${HTTPD}
if [ $? -ne 0 ]; then
$RESTART
fi
По аналогии можно добавить контроль MySQL, PHP-FPM и SMTP-сервера.
Решений для бэкапа в репозитории больше чем достаточно, в зависимости от ситуации и наличия ресурсов можно подобрать себе любой по вкусу. В самом простом случае можно использовать самописный скрипт, который будет собирать папки /etc, веб-серверы и SQL-базы и отправлять их на FTP. Файлы будем хранить неделю. Чтобы файлы удалялись автоматически, в имени будем использовать остаток от деления, тогда новый файл с таким же именем будет перезатираться. В нашем примере будем делить на 7.
# nano backup.sh
#!/bin/sh
# Данные FTP
FTPD="/"
FTPU="user"
FTPP="password"
FTPS="ftp.server.name"
# Системные файлы и каталог для архивов
BACKUP=/var/archives
TAR="/bin/tar"
GZIP="/bin/gzip"
FTP="/usr/bin/ftp"
# Переменные MySQL
MUSER="backup"
MPASS="backup_pass"
MHOST="localhost"
MYSQLDUMP="/usr/bin/mysqldump"
SQLFILE=$BACKUP/$DOY/sql.$DOY.sql.gz
# Чистим старые файлы
[ ! -d $BACKUP ] && mkdir -p $BACKUP || /bin/rm -f $BACKUP
# Создаем каталог
DOY1=`date +%j`
DOY=`expr $DOY1 % 7`
mkdir $BACKUP/$DOY
# Собираем архивы /etc, сайтов и SQL
$TAR -cf $BACKUP/$DOY/etc.tar /etc
$TAR -cf $BACKUP/$DOY/www.tar /var/www/
$MYSQLDUMP -u $MUSER -h $MHOST -p$MPASS --all-databases | $GZIP -9 > $SQLFILE
# Создаем единый архив
ARCHIVE=$BACKUP/backup-$DOY.tar.gz
ARCHIVED=$BACKUP/$DOY
$TAR -zcvf $ARCHIVE $ARCHIVED
# Отправляем на FTP
cd $BACKUP
DUMPFILE=backup-$DOY.tar.gz
$FTP -in $FTPS <<END_SCRIPT
quote user $FTPU
quote pass $FTPP
cd $FTPD
mput $DUMPFILE
bye
END_SCRIPT
# Убираем временные файлы и оставляем последнюю копию на локальном сервере
rm -rf $ARCHIVED
rm -rf backup-last.gz
mv $DUMPFILE backup-last.gz
exit
Прогоняем первый раз оба файла вручную, чтобы убедиться в их работоспособности. И добавляем задачи в /etc/crontab. Мониторить будем каждые десять минут, резервную копию будем создавать ежедневно в 20:00.
*/10 * * * * root /bin/sh /root/dbmonitor.sh 2>&1 /var/log/monitor.log
00 20 * * * root /bin/sh /root/backup.sh 2>&1 /var/log/backup.log
Перезапускаем cron:
# service cron restart
На данный момент мы имеем полностью настроенный веб-сервер.
Подключаемся к Bitbucket
Вся изюминка переноса состояла в использовании при разработке веб-сайта Git. Выглядело интересно, осталось только это все реализовать. Здесь можно пойти несколькими путями. Самый, наверное, простой — инициализировать локальный репозиторий и позволить разработчику при коммите выкладывать файлы прямо на сервер. Минус здесь — мы фактически даем ему доступ на сервер. Поэтому лучше перестраховаться, и самым правильным вариантом будет использовать посредника с возможностью автоматического pull файлов после коммита. Так мы получаем еще один источник бэкапа. В качестве промежуточного сервиса был выбран сервис «ведро битов» Bitbucket, предлагающий всякие вкусности вроде бесплатных «private»-репозиториев и удобного интерфейса. Хотя, в принципе, это может быть любой другой подобный сервис — GitHub или Google Cloud Source Repositories.
Механизм взаимодействия будет простым. Создаем репозиторий (можно в отдельной теме), инициализируем Git прямо в корне сайта (как вариант, можно переносить с другого каталога, но это не так интересно), добавляем удаленный репозиторий Bitbucket и подключаем сервер к аккаунту Bitbucket. Чтобы коммит на Bitbucket сразу попадал на веб-сайт, будем использовать механизм хуков. Сам Git предоставляет такую возможность, а в Bitbucket есть даже два варианта.
Для пула можно использовать протокол HTTPS или Git — ставить эту схему в уже рабочий сайт или разворачивать с нуля. В случае HTTPS меньше настроек, просто после инициализации подключаем удаленный репозиторий и в последующем тянем из него изменения.
# git init
# git clone https://аккаунт@bitbucket.org/тема/репозиторий.git .
Но если придется экстренно вносить правки в файлы вручную, то возможен конфликт при будущих pull. Если же используем SSH, то настроек чуть больше, но зато, поправив файл, можем сразу сделать commit, избежав возможных проблем.
# git commit -a -m "wp-config correction"
Для подключения через Git/SSH нужно на Bitbucket загрузить публичный ключ. Генерируем:
# ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
В качестве имени вводим bitbucket, чтобы не путаться. На запрос пароля жмем ввод. Меняем сразу права, иначе будет ругаться.
# chmod 0600 ~/.ssh/bitbucket
Проверяем, работает ли ssh-agent:
# eval "$(ssh-agent -s)"
Agent pid 7782
Добавляем ключ:
# ssh-add ~/.ssh/bitbucket
Enter passphrase for /root/.ssh/bitbucket:
# ssh-add -l
Смотрим, чтобы в ~/.ssh/config была информация для идентификации хоста Bitbucket:
Host bitbucket.org
IdentityFile ~/.ssh/bitbucket
Добавляем публичный ключ bitbucket.pub на Bitbucket в настройках учетной записи «Безопасность -> SSH-ключи». После этого должны заходить ssh -Tvv git@bitbucket.org без пароля. Теперь у нас два варианта: пустой или рабочий сайт. Если сайт пустой, а репозиторий содержит данные, то просто делаем
# git clone git@bitbucket.org:аккаунт/тема/репозиторий.git
Это вариант самый беспроблемный, так как сайт фактически ставим с нуля и не будет конфликтов между локальными файлами и теми, что уже есть в репозитории. В других случаях следует инициализировать репозиторий и добавить удаленный.
# git init
# git remote add origin git@bitbucket.org:аккаунт/тема/репозиторий.git
После чего тянуть изменения git pull origin master. Главная проблема в том, что Git не хочет инициализировать репозиторий в каталоге, в котором уже есть файлы. Выкрутиться можно несколькими способами. Самый простой — проделать это все в отдельном каталоге, а затем скопировать в рабочий и проверить работу git pull. Но файлы в Git и локальные не должны различаться, иначе придется использовать git checkout, который набросает лишние строки в файле, в результате можем получить нерабочий сайт. Причем нет необходимости переносить весь сайт, достаточно перенести только каталог .git.
Не забываем про права доступа. Так как имя начинается с точки, то шаблон * не сработает, нужно указать явно.
# chown -R www-data:www-data /var/www/site/.*
Для большего контроля следует в .gitignore
внести все файлы, которых не должны касаться изменения. Например, для WP это могут быть основные файлы и каталоги.
wp-config.php
wp-includes/
wp-admin/
wp-content/uploads/
Теперь разработчик может выкладывать код в Bitbucket, а мы забирать на сайт. Осталось только автоматизировать процесс. В Git это позволяет система хуков — фактически скриптов, выполняющихся в зависимости от наступления определенного события. Реализованы хуки и в Bitbucket. Причем доступно сразу два варианта: веб-хук (Webhooks) и службы. В логах они выглядят так:
"POST /post.php HTTP/1.0" 200 236 "-" "Bitbucket-Webhooks/2.0"
"POST /post.php HTTP/1.0" 200 703 "-" "Bitbucket.org"
Настраиваются они через API или веб-интерфейс (меню «Настройки»). На проект можно создать несколько хуков. Для настройки веб-хука нужно указать URL и событие (всего 21 событие). В Webhooks на указанный в установках URL отправляется POST-запрос с данными в JSON-формате (в интерфейсе есть возможность просмотра View requests), при необходимости можно их отобрать и обработать запрос в зависимости от параметров.
В «Службах» можно выбрать несколько вариантов, включая и POST-запрос, Twitter и обращение к различным сервисам.
Нам для нашей схемы достаточно, чтобы Bitbucket при пуше (repo:push) просто «дернул» URL в веб-хуке, а мы по этому событию вытянем коммит из репозитория. Создаем простой скрипт:
# nano bitbucket.php
<?php
shell_exec("/usr/bin/git pull origin master 2>&1");
?>
В целях безопасности можно его назвать как-нибудь случайно типа 12ghrt78.php и ограничить доступ к скрипту из сетей Bitbucket: 131.103.20.160/27, 165.254.145.0/26, 104.192.143.0/24. Хотя иногда приходится его вызывать из браузера. Указываем файл в настройках веб-хука на событие Repository push. Теперь при пуше разработчиком веб-сервер вытянет коммит из Bitbucket. В зависимости от настройки хостинга может не хватить прав доступа. В этом случае ничего не остается, как разрешить выполнять команду через sudo:
shell_exec("sudo /usr/bin/git pull origin master 2>&1");
Набираем команду visudo и в /etc/sudoers записываем:
www-data ALL=(root) NOPASSWD:/usr/bin/git
Теперь должно работать.
Вывод
В статье описана самая простая ситуация, которая встречается в 80% случаев. В идеале затем каждый пункт требует дополнительного внимания, после тестового прогона следует заняться оптимизацией и попробовать выжать из сервера максимум.