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

 

Постановка задачи

Ситуация самая жизненная. Интернет-магазин, размещенный на шаред-хостинге, после запуска начал получать клиентов, но появились пожелания к функциональности, и разработчики активно занялись доработкой сайта. Выяснилось, что, когда в этом участвует несколько человек, постоянно копировать файлы через 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, который есть в репозитории.

Скрипт MySQLTuner позволяет оптимизировать MySQL
Скрипт MySQLTuner позволяет оптимизировать MySQL
 

Переносим базу

Архивируем на старом хосте базу данных через 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
Настройки Postfix во время установки
Настройки Postfix во время установки
 

Мониторинг и бэкап

Две важные вещи — мониторинг и бэкап. После установки сайт может падать из-за неоптимальных настроек. Поэтому лучше сразу установить хотя бы простое решение, позволяющее перезапускать сервисы. В репозиториях есть отличные утилиты 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 это могут быть основные файлы и каталоги.

Проверяем работу с Bitbucket вручную
Проверяем работу с Bitbucket вручную
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 и обращение к различным сервисам.

Добавляем Webhooks в настройках Bitbucket
Добавляем Webhooks в настройках Bitbucket

Нам для нашей схемы достаточно, чтобы 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% случаев. В идеале затем каждый пункт требует дополнительного внимания, после тестового прогона следует заняться оптимизацией и попробовать выжать из сервера максимум.

3 комментария

  1. Аватар

    Rage

    16.09.2016 в 21:17

    не раскрыт вопрос нескольких сайтов и запуск nginx с разных пользователей 🙁

    • Аватар

      Igor Saevets

      18.09.2016 в 15:19

      Возьмите хостинг со встроенной админ панелью типа ispmanager и установите несколько сайтов и сразу же поймете как заполняются конфиги для нескольких сайтов.

  2. Аватар

    tutifruti

    17.09.2016 в 11:08

    Согласен, очень бы помогла настройка нескольких сайтов для разных пользователей и желательно связку nginx и apache.
    Конечно сейчас начнут говорить, что полно сайтов с описанием и роликов в youtube, но большая часть из них под старые версии веб-серверов. Новичок запутается в момент.

Оставить мнение