Задача — на минимальных ресурсах VPS развернуть хостинг нескольких не нагруженных сайтов. Сделать это быстро и удобно с минимальными проблемами в будущем и не падать на пиковых нагрузках.
Основные принципы LAMP +Nginx на VPS:
- ОС — Centos-6 86_x64 потому что стабильно, удобно и легко обновляется.
- Никакого самосборного софта. А то, как говорится, «командой make && make install любой дистрибутив превращается в Slackware».
На данный момент я использую тарифный план 512 у хостинг провайдера www.digitalocean.com (512Мб оперативки) и не рассчитываю на большую нагрузку так что большая часть относится именно к такому количеству оперативной памяти, но в целом решения легко переносимы на тарифные планы хостинг-провайдеров.
Хостинг делается «для себя». Поэтому недостаточно описаны моменты, которые стоит учитывать, если вы даете доступ к администрированию сайтов посторонним людям.
Приступим.
1. Проверяем обновления
Установочный образ у хостинг провайдера может оказаться не особо свежим.
[root@test ~]# yum update
Есть что обновлять — обновляем. Нет — радуемся.
2. Подключаем репозитарий EPEL (http://fedoraproject.org/wiki/EPEL) из которого будем ставить недостающий софт
[root@test ~]# rpm -ihv download.fedora.redhat.com/pub/epel/6/x86_64/epel-release-6-5.noarch.rpm
3. Ставим нужный нам софт
[root@test ~]# yum install httpd mysql-server php vsftpd mc phpMyAdmin php-eaccelerator sysstat crontabs tmpwatch
Кратко о софте:
httpd — Apache штатная версия для Centos-6 — 2.2.15
mysql-server — Mysql 5.1.52
php — PHP 5.3.2
vsftpd — довольно удобный FTP сервер vsftpd 2.2.2
mc — некоторые вещи все-таки удобнее делать в mc чем в командной строке.
phpMyAdmin — управлять mysql базами в phpMyAdmin удобнее.
php-eaccelerator — акселератор для PHP. Заметно увеличивает скорость выполнения скриптов и снижает нагрузку на процессор. Да и на память.
sysstat — на случай если нам захочется посмотреть как поживает система.
crontabs — для выполнения заданий по расписанию.
tmpwatch — утилита для удаления устаревших файлов.
На самом деле установится несколько больше пакетов, к тем пакетам что мы попросили установить добавится все необходимое для их функционирования.
В результате получается:
Install 44 Package(s) Upgrade 0 Package(s) Total download size: 37 M Installed size: 118 M
4. Командой free смотрим, есть ли у нас своп и если нет, то создаем его и подключаем. Если есть – радуемся и пропускаем этот пункт.
Активное использование свопа — очень плохо. Если идет активный свопинг — значит нужно что-то оптимизировать или урезать. Если оптимизировать и урезать не получается — придется переходить на более дорогой тарифный план. Нужно учитывать, что хостинг-провайдер может быть не рад слишком активному использованию свопа.
Совсем без свопа — не очень хорошо — oom killer — штука страшная. Может убить mysqld и вместо того чтобы просто тормозить ваши сайты будут совсем лежать.
Замечание — делать своп больше имеющейся оперативной памяти не нужно. Пользы от него не будет, а место он скушает.
Создаем своп следующим образом:
[root@test /]# dd if=/dev/zero of=/swap bs=1M count=256 [root@test /]# mkswap /swap
подключаем
[root@test /]# swapon /swap
Для подключения свопа автоматически записываем эту команду в /etc/rc.local
Проверить наличие и занятость свопа можно командами top или free
5. Включаем и запускаем демонов
[root@test /]# chkconfig httpd on [root@test /]# chkconfig mysqld on [root@test /]# chkconfig crond on
[root@test /]# service httpd restart [root@test /]# service mysqld restart [root@test /]# service crond restart
6. Создаем пользователей для сайтов. Я предпочитаю, чтобы имя пользователя было аналогично домену сайта.
[root@test /]# adduser testsite.ru [root@test /]# adduser mysite.ru [root@test /]# adduser cfg.testsite.ru
Далее создаем дополнительные каталоги в каталогах пользователей:
каталог html (в котором будет основное содержимое сайтов) и каталог log в которую будут писаться логи для этого сайта и выставляем права. Права ставим: пользователю – полный доступ, группе apache чтение и листинг директорий, остальным – фикус.
Права можно выставить и руками, а можно и воспользоваться небольшим скриптиком:
cd /home for dir in `ls -1 `; do mkdir /home/$dir/log mkdir /home/$dir/html chown -R $dir:apache $dir chmod ug+rX $dir done;
7. Настраиваем веб сервер. Правим /etc/httpd/conf/httpd.conf
Из действительно нуждающегося в изменении — настраиваем модуль prefork так, чтобы он изначально кушал поменьше памяти и ограничивал свои аппетиты.
Дело в том что Apache изначально настроен на запуск до 256 своих рабочих процессов, при том что один рабочий процесс запросто занимает 20-40Мб (256*20=5Гб) это запросто может привести к проблемам, особенно на скромных VPS где есть всего 512Мб оперативки.
Поэтому мы ограничиваем их количество разумными цифрами исходя из доступной нам оперативной памяти. Например 5 процессов апача при среднем размере 30Мб займут около 150Мб — что уже терпимо.
Было:
<IfModule prefork.c> StartServers 8 MinSpareServers 5 MaxSpareServers 20 ServerLimit 256 MaxClients 256 MaxRequestsPerChild 4000
Стало:
<IfModule prefork.c> StartServers 2 MinSpareServers 2 MaxSpareServers 3 ServerLimit 5 MaxClients 5 MaxRequestsPerChild 1000
Такая настройка не даст апачу расплодиться сверх меры и скушать всю оперативку. В зависимости от реальной нагрузки параметры, возможно, стоит пересмотреть.
Чтобы на одном ip адресе иметь много сайтов раскомментируем строку
NameVirtualHost *:80
Далее переходим в директорию /etc/httpd/conf.d/ и настраиваем сайты.
Там можно удалить welcome.conf, который выключает индексы и выдает вместо нее страничку «Apache 2 Test Page».
Следует учесть, что конфиги виртуальных хостов в этой директории применяются по очереди в алфавитном порядке.
Для того чтобы пользователь зайдя по IP адресу на какой либо из наших сайтов не попал на совершенно другой (который будет первым по списку) в директорию conf.d стоит положить файл с именем например 000-default.conf и таким содержимым:
<VirtualHost *:80> ServerName localhost.local DocumentRoot "/var/www/html"
и положить в директорию /var/www/html/ файлик index.html с пожеланиями.
Далее для каждого из наших виртуальных хостов создаем конфиг файл по примерно такому шаблону:
<VirtualHost *:80> ServerName testsite.ru ServerAlias www.testsite.ru ServerAdmin [email protected] ErrorLog /home/testsite.ru/log/error.log CustomLog /home/testsite.ru/log/access.log combined DocumentRoot /home/testsite.ru/html/ <Directory "/home/testsite.ru/html"> Order allow,deny Allow from all
В эти же файлы, по вкусу можно добавить индивидуальные настройки каких либо модулей.
Перезапускаем apache и смотрим все ли работает.
[root@test /]# service httpd restart
apache должен запуститься нормально. В директориях log сайтов должно создаться по 2 файла логов.
При обращении к серверу по IP адресу должен выводиться файл который вы положили в /var/www/html/, а при обращениях по именам сайтов вы должны видеть содержимое директории html (пустое скорее всего) и записи в файле access.log соответствующего сайта.
8. Настраиваем mysql. Первым делом удаляем базу test и задаем пароль пользователя root на mysql
[root@test /]# mysql mysql> DROP DATABASE test; mysql> USE mysql; mysql> UPDATE user SET Password=PASSWORD('MyMysqlPassword') WHERE user='root'; mysql> FLUSH PRIVILEGES; mysql> quit
С MySql проблема примерно такая же как с Apache — требовательность к оперативной памяти, которая на VPS весьма дорога.
Для уменьшения объема используемой памяти sql сервером правим /etc/my.cnf следующим образом:
в секцию [mysqld] добавляем следующее:
key_buffer = 16M max_allowed_packet = 10M table_cache = 400 sort_buffer_size = 1M read_buffer_size = 4M read_rnd_buffer_size = 2M net_buffer_length = 20K thread_stack = 640K tmp_table_size = 10M query_cache_limit = 1M query_cache_size = 32M skip-locking skip-innodb skip-networking
и в конец файла добавляем эти строки:
[mysqldump] quick max_allowed_packet = 16M [mysql] no-auto-rehash [isamchk] key_buffer = 8M sort_buffer_size = 8M [myisamchk] key_buffer = 8M sort_buffer_size = 8M [mysqlhotcopy] interactive-timeout
перезапускаем mysqld чтобы убедиться что все нормально:
[root@test ]# service mysqld restart
Так же нужно заменить, что опция «skip-networking» делает возможным обращение к серверу только с локальной машины через сокет. Если требуется сетевой доступ — эту опцию включать не нужно.
Такие настройки позволят минимизировать память используемую процессом mysql и нормально работать на незагруженном сайте. Но конечно же нужно смотреть на статистику работы mysql и в зависимости от потребностей увеличивать данные здесь лимиты.
Дальнейшее администрирование mysql удобнее производить через phpMyAdmin.
Теперь один нюанс — по умолчанию phpMyAdmin доступен по пути /phpMyAdmin на всех наших сайтах.
Чтобы этого не было создаем специализированный сайт для управления (например cfg.testsite.ru) и настраиваем его аналогично остальным.
Потом переносим все содержимое файла /etc/httpd/conf.d/phpMyAdmin.conf в конфиг этого сайта, а сам файл phpMyAdmin.conf удаляем или переносим куда-нибудь из дириктории conf.d.
После таких действий phpMyAdmin будет доступен по пути /phpMyAdmin/ только на специально выделенном сайте.
Ну и для того, чтобы в него можно было войти в файле конфигурации сайта меняем
<Directory /usr/share/phpMyAdmin/> Order Deny,Allow Deny from All Allow from 127.0.0.1 Allow from ::1 <Directory /usr/share/phpMyAdmin/setup/> Order Deny,Allow Deny from All Allow from 127.0.0.1 Allow from ::1
на
<Directory /usr/share/phpMyAdmin/> Order Deny,Allow Deny from All Allow from 127.0.0.1 Allow from ваш.ип.адрес. Allow from ::1 <Directory /usr/share/phpMyAdmin/setup/> Order Deny,Allow Deny from All Allow from 127.0.0.1 Allow from ваш.ип.адрес. Allow from ::1
После этого phpMyAdmin будет доступен ТОЛЬКО с вашего ip адреса.
Авторизуемся в нем как пользователь root с тем паролем что установили.
Чтобы создать пользователя идем в «Привелегии» — «Добавить нового пользователя»
имя пользователя — произвольное, я предпочитаю использовать имя сайта чтобы уменьшить путаницу.
Хост — локальный (мы же делаем его для сайта который будет крутиться тут же?)
Пароль — генерировать (не забываем копировать пароль).
Ставим галочку — «Создать базу данных с именем пользователя в названии и предоставить на нее полные привилегии»
Применяем.
В итоге получаем пользователя с выбранными вами именем, паролем и базой данных с аналогичным названием.
9. Часто заливать файлы на хостинг удобнее через фтп. Для этого мы и установили vsftpd
редактируем его конфиг /etc/vsftpd/vsftpd.conf
выключаем анонимный логин, меняем
anonymous_enable=YES
на
anonymous_enable=NO
и раскоментируем
chroot_local_user=YES
Теперь чтобы можно было зайти на фтп определенного сайта соответствующему пользователю нужно задать пароль
[root@test /]# passwd testsite.ru
И не забываем, что по умолчанию этот пользователь с установленным паролем может зайти по SSH. Чтобы отключить эту возможность проще всего сменить шелл пользователя
[root@test etc]# chsh -s /sbin/nologin testsite.ru
Включаем и запускаем vsftpd
[root@test /]# chkconfig vsftpd on [root@test /]# service vsftpd start
Проверяем, все ли работает.
Ну и на последок совсем простенький «оперативный бекап». По принципу «бекапов много не бывает».
Лучше бы использовать что-то более правильное, но плохой бекап все-таки лучше полного отсутствия.
Такой бекап может служить неплохим дополнением полному бекапу виртуальной машины у хостинг-провайдера. Но, ни в коем случае не его заменой.
Бекапим содержимое сайтов и баз данных, а так же настройки в каталоге /etc/.
Создаем директорию /backup/ и ставим на нее права «700»
[root@test /]# mkdir /backup/ [root@test /]# chmod 700 /backup/
В директории /etc/cron.daily/ создаем файл backup.sh и так же ставим на него права «700».
[root@test /]# touch /etc/cron.daily/backup.sh [root@test /]# chmod 700 /etc/cron.daily/backup.sh
Файл имеет следующее содержание:
#!/bin/sh #Бекапим все директории html наших сайтов tar -cf - /home/*/html/ | gzip > /backup/sites-`date +%Y-%m-%d`.tar.gz #Бекапим все базы даных в один файл mysqldump -u root --password=MyMysqlPassword --all-databases | gzip > /backup/mysql-`date +%Y-%m-%d`.dump.gz #Бекапим конфигурационные файлы tar -cf - /etc/ | gzip > /backup/etc-`date +%Y-%m-%d`.tar.gz #Удаляем файлы бекапов старше 7 дней tmpwatch -t -m 7d /backup/
В принципе, вместо бекапа всего в одну кучу может оказаться лучше бекапить все по отдельности, но тогда возникает возможность забыть настроить бекап чего-либо и пожалеть об этом, когда он понадобится.
Ну или вариант бекапа «по раздельности» требующий чтобы имя пользователя сайта и имя базы данных совпадали:
#!/bin/sh for dir in `ls -1 /home/ `; do tar -cf - /home/$dir/html/ | gzip > /backup/sites-$dir-`date +%Y-%m-%d`.tar.gz mysqldump -u root --password=MyMysqlPassword $dir | gzip > /backup/mysql-$dir-`date +%Y-%m-%d`.dump.gz done; #Бекапим конфигурационные файлы tar -cf - /etc/ | gzip > /backup/etc-`date +%Y-%m-%d`.tar.gz #Удаляем файлы бекапов старше 7 дней tmpwatch -t -m 7d /backup/
10. Обновления
Не забываем время от времени обновлять систему.
[root@test ~]# yum update
Благодаря политике RHEL/Centos в отношении к софту версии софта после обновления останутся теми же и нечаяно положить сервер из-за того, что в конфиге что-то поменялось шансов очень мало.
Правда в этом подходе есть и минус — через три года в Centos-6 будут теже версии софта, что и сейчас. Но если наша цель стабильность — это нам подходит.
11. Тестирование
Очень рекомендую провести после настройки тестирование сайта.
Первый пункт тестирования — перезагрузка сервера и проверка того что все нужные демоны стартанули и все работает как ожидалось. Я бы вообще порекомендовал не гнаться за циферками аптайма, а перезагружаться после установки или изменения версий любого серверного софта стартующего автоматом.
Лучше узнать, что Apache не стартует в автозапуске после собственноручного планового ребута, чем узнать, что у хостера были проблемы и в следствии ребута вашей виртуалки сайты на ней уже полдня как не работают.
Далее — нагрузочное тестирование при помощи утилиты ab (Apache HTTP server benchmarking tool).
В данном тестировании нас интересует не столько количество попугаев сколько поведение сервера под нагрузкой. На нем не должно быть умирающих процессов и активного свопинга.
Для тестирования нам потребуется сайт размещенный на этом сервере в рабочем состоянии. И «типичная» страница с этого сайта. Ну или можно использовать не типичную, а наиболее тяжелую.
Я для примера провожу тестирование на свежеустановленном Drupal 7.9
Из всего многообразия командной строки ab нам потребуются всего 2 параметра -n — количество http запросов -c — количество одновременных запросов (потоков).
Во время выполнения теста во второй ssh сессии с помощью top наблюдаем за тем как поживает сервер.
100 запросов в 2 потока.
[root@test ~]# ab -n 100 -c 2 testsite.ru/
Из вывода ab мне особо интересны «Requests per second», «Time per request» и «Failed requests» которые дают общее представление о производительности сервера.
Failed requests: 0 Requests per second: 6.20 [#/sec] (mean) Time per request: 322.788 [ms] (mean)
Видно, что сервер обрабатывает 6 с копейками запросов в секунду и тратит 322 миллисекунды на генерацию одной страницы.
Из вывода top интересно распределение памяти и загрузка процессора.
Tasks: 62 total, 3 running, 59 sleeping, 0 stopped, 0 zombie Cpu(s): 19.9%us, 5.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.4%si, 74.5%st Mem: 244856k total, 151624k used, 93232k free, 3752k buffers Swap: 262136k total, 0k used, 262136k free, 76604k cached
Swap: 0k used — ооочень хорошо.
93232k free + 76604k cached — фактически 170 мегабайт свободной памяти.
100 запросов 5 потоков.
[root@test ~]# ab -n 100 -c 5 testsite.ru/ Failed requests: 0 Requests per second: 6.21 [#/sec] (mean) Time per request: 804.513 [ms] (mean)
Tasks: 63 total, 5 running, 58 sleeping, 0 stopped, 0 zombie Cpu(s): 17.5%us, 6.2%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 76.3%st Mem: 244856k total, 159756k used, 85100k free, 3812k buffers Swap: 262136k total, 0k used, 262136k free, 76660k cached
Количество запросов в секунду осталось тем же а вот время генерации выросло больше чем в 2 раза — уперлись в процессор.
Ну и наконец, хабраэффект или что-то близкое :-)
[root@test ~]# ab -n 500 -c 50 testsite.ru/ Failed requests: 0 Requests per second: 6.45 [#/sec] (mean) Time per request: 7749.972 [ms] (mean)
Tasks: 63 total, 6 running, 57 sleeping, 0 stopped, 0 zombie Cpu(s): 19.1%us, 5.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 75.6%st Mem: 244856k total, 162740k used, 82116k free, 3884k buffers Swap: 262136k total, 0k used, 262136k free, 76672k cached
Опять-таки количество запросов в секунду относительно стабильно, а вот время генерации стало уже совсем грустным. Но в тоже время Failed requests — нулевое. Что значит что хоть и медленно, но все работает.
Ну и по поводу памяти — на данный момент Swap: 0k used, 82116k free, 76672k cached — потребление практически не выросло и, в принципе, можно увеличить некоторые лимиты, но учитывая отсутствие у меня наполнения сайта на данный момент, думаю, этого делать не стоит. А вот позже стоит прогнать тесты на заполненом сайте и в зависимости от результатов уже откорректировать настройки.
12. Установка nginx в качестве фронтенда
Почему это нужно?
Основная проблема кроется в том, как apache обрабатывает входящие соединение. На каждое входящее соединение создается новый процесс или берется один из запущенных и соединение передается ему на обслуживание. До тех пор пока соединение не закрыто этот процесс занимается только им.
Все выглядит хорошо пока у нас много оперативки и/или очень быстрые клиенты (ab запущенный с локалхоста один из таких вариантов), но все становится куда грустнее если клиент сидит на медленном канале или просто никуда не спешит. В таком случае он на время забора реквеста фактически блокирует один из процессов который на это время выключается из работы сервера.
Таким образом в теории имея сервер на 100мбит канале и одного настойчивого клиента на диалапе с регетом мы можем получить нечто-вроде DOS — клиент в несколько потоков заблокирует фактически все наши процессы apache которых у нас в виду малого количества оперативной памяти весьма не много.
Решается данная проблема установкой легкого http сервера в виде фронтенда. При наличии фронтенда все входящие соединения принимаются им, затем запрос передается apache и быстро получается ответ. Тем самым освобождая процесс apache для новых запросов. Фронтенд же не спеша и не растрачивая лишних ресурсов отдает полученный ответ уже запросившему ему клиенту.
Дополнительным бонусом фронтенд может сам отдавать статическое содержимое — например картинки, css и т.п., снимая нагрузку с тяжелого апача.
[root@test ~]# rpm -ihv centos.alt.ru/pub/repository/centos/6/x86_64/centalt-release-6-1.noarch.rpm [root@test ~]# yum install mod_realip2 nginx-stable
Для того чтобы apache и наши скрипты в запросах видели реальный ip адрес клиента, а не адрес фронтенда у нас будет установлен mod_realip2.
редактируем /etc/httpd/conf.d/mod_realip2.conf, раскоментируем
RealIP On RealIPProxy 127.0.0.1 RealIPHeader X-Real-IP
редактируем httpd.conf и файлы в /etc/httpd/conf.d/
меняем все упоминания 80-го порта на порт 8080
Всего менять нужно три директивы:
Listen 127.0.0.1:8080 NameVirtualHost *:8080 <VirtualHost *:8080>
редактируем /etc/nginx/nginx.conf
user apache; worker_processes 2;
Я использую запуск nginx из-под пользователя apache, поскольку изначально мы давали все права с расчетом именно на него.
Так же не лишним будет закомментировать директиву access_log в nginx.conf чтобы избежать двойного ведения лога.
error_log лучше не трогать — ошибки у апача и nginx все-таки разные.
В секции server правим директиву listen и ставим:
listen 80 default
меняем:
location / { root /usr/share/nginx/html; index index.html index.htm; }
на
location / { proxy_pass 127.0.0.1:8080/; }
В директории /etc/nginx/conf.d/ создаем файл proxy.conf со следующим содержанием
proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 10m; client_body_buffer_size 128k; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k;
Перезапускаем apache и nginx
service httpd restart service nginx restart
Проверяем все ли работает.
В целом все. Теперь nginx стоит фронтендом, принимает все входящие соединения и проксирует их апачу который их обрабатывает и быстро передает ответ обратно в nginx освобождая процесс для новых запросов.
Следующим шагом по увеличению быстродействия и снижения потребляемых ресурсов будет отдача статического содержимого напрямую через nginx.
Для этого придется в дополнение к виртуальным хостам apache завести виртуальные хосты nginx и указать что раздавать.
Для этого в каталоге /etc/nginx/conf.d/ создаем файл с именем нашего сайта и расширением .conf со следующим содержимым:
server { listen 80; server_name testsite.ru www.testsite.ru; location / { proxy_pass 127.0.0.1:8080/; } location ~ /\.ht { deny all; } location /sites/default/files { root /home/testsite.ru/html; access_log /home/testsite.ru/log/access_static.log combined; } }
В этом примере для сайта на CMS Drupal статическое содержимое каталога /sites/default/files раздается через nginx, а за всем остальным мы уже идем к апачу.
Еще один вариант — заменить директиву location на:
location ~ \.(jpg|gif|png|css|js|ico)$ { root /home/testsite.ru/html; access_log /home/testsite.ru/log/access_static.log combined; }
В таком случае все файлы с соответствующими расширениями будут отдаваться nginx’ом. Но в данном варианте есть маленький минус — nginx не умеет работать с файлами .htaccess поэтому если у вас там есть какое либо содержимое, закрытое от просмотра .htaccess’ом — от использования такого варианта стоит воздержаться.
Еще стоит заметить, что в данной ситуации мы получаем два лога на один сайт. Отдельно лог запросов, по которым отработал апач и отдельно лог содержимого отданного nginx.
Как вариант — перенести директиву access_log из секции location в секцию server и отключить access_log в виртуальном хосте апача. В таком случае лог будет вести только nginx.
Но для посмотреть «как же это работает» двойной лог может оказаться интересным — по ним сразу видно какая часть нагрузки на кого приходится.
Для проведения дальнейшей оптимизации стоит читать уже мануалы по оптимизации конкретных компонентов и делать ее с оглядкой на сложившуюся ситуацию.
Вариант бекап скрипта:
#!/bin/bash USER="root" PASSWORD="myRootPWD" mkdir /var/backup/database/`date +%F`; for DB in `mysql -u$USER -p$PASSWORD -N -e 'show databases' | awk '{print $1}'`; do mysqldump --user=$USER --host=$HOST --password=$PASSWORD ${DB} | gzip > /var/backup/database/`date +%F`/${DB}.sql.gz; echo "${DB} Backup"; done