howtolnmp (linux + nginx + mysql + php-fpm)

Если у вас есть сайт php+mysql и VPS OpenVZ с небольшим количеством оперативки (192), и хочется выжать перфоманс (для CentOS, минимальная инсталляция, если есть апач — выключить):

Подключаем репозитории:

  # rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm
  # wget http://rpms.varien.com/varien-release-1-1.centos.varien.noarch.rpm
  # rpm -Uvh varien-release-1-1.centos.varien.noarch.rpm

Если подключен репозиторий rpmforge — смотрите, чтоб не было конфликтов (лучше его выключить)
Устанавливем ПО:

  # yum install nginx
  # yum install php-fpm php-mysql php-gd php-mcrypt php-pear php-dom
  # yum install php-pecl-apc
  # yum install mysql-server

Включаем автостарт сервисов:

  # /sbin/chkconfig nginx on
  # /sbin/chkconfig mysqld on
  # /sbin/chkconfig php-fpm on

Конфигурируем nginx (файл /etc/nginx.conf), привожу свой, взял на скорую с тестового сервера с малым количеством ресурсов (поэтому worker_processes 1 и т.д.), для одиночного wordpress, работает rewrite (конфиг для старого nginx из epel!!!, в > 0.7.x немного другой синтаксис), что-то лишнее вырезанно, не настроены кеши и т.д. (всё равно быстро), если что sysoev.ru/nginx/:

user              nginx;
worker_processes  1;
error_log         /var/log/nginx/error.log;
pid               /var/run/nginx.pid;

events {
    worker_connections  1024;
    use epoll; # конфиг для linux CentOS
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] $request '
                      '"$status" $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    ###
    server_tokens off;

    ### fastcgi nodes
    # у меня работает и через tcp, и unix сокет, через tcp незначительно быстрее (TIME_WAIT?)
    #
    upstream  backend  {
        # server unix:/tmp/fcgi.sock;
        server 127.0.0.1:9000;
    }

    #
    # yourdomain.com server
    #
    server {
        listen       80;
        server_name  yourdomain.com www.yourdomain.com;

        autoindex off;
        ###
        gzip on; # use gzip compression
        gzip_proxied any; # enable proxy for the fcgi requests
        gzip_types text/plain text/html text/css text/javascript;

        index index.php;

        location / {
            log_not_found off;
            error_page 404 = @wordpress;
        }

        location @wordpress {
            fastcgi_pass backend;
            fastcgi_param SCRIPT_FILENAME /var/www/html/yourdomain.com/index.php;
            include /etc/nginx/fastcgi_params;
            fastcgi_param SCRIPT_NAME /index.php;
        }

        location ~ \.php$ {
            if (!-e $request_filename) {
                rewrite ^(.+)$ /index.php break;
                break;
            }
            fastcgi_index index.php;
            fastcgi_pass backend;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include /etc/nginx/fastcgi_params;
        }

        #charset koi8-r;

        access_log  /var/log/nginx/yourdomain.com.access.log  main;

        error_page  404              /404.html;
        location = /404.html {
            root   /usr/share/nginx/html;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }

    # Load config files from the /etc/nginx/conf.d directory
    include /etc/nginx/conf.d/*.conf;

}

Конфигурируем PHP-FPM (файл /etc/php-fpm.conf). Используется локальный socket. Наиболее важный параметр тут – max_children, его значение должно быть подобрано с учетом конфигурации (память, CPU) в процессе тестов нагрузки. Обязательны синтетические тесты, у меня max_children = 5 был медленней max_children = 1, max_children = 2.

<?xml version="1.0" ?>
<configuration>

        All relative paths in this config are relative to php's install prefix

        <section name="global_options">

                Pid file
                <value name="pid_file">/var/run/php-fpm.pid</value>

                Error log file
                <value name="error_log">/var/log/php-fpm.log</value>

                Log level
                <value name="log_level">notice</value>

                When this amount of php processes exited with SIGSEGV or SIGBUS ...
                <value name="emergency_restart_threshold">10</value>

                ... in a less than this interval of time, a graceful restart will be initiated.
                Useful to work around accidental curruptions in accelerator's shared memory.
                <value name="emergency_restart_interval">1m</value>

                Time limit on waiting child's reaction on signals from master
                <value name="process_control_timeout">5s</value>

                Set to 'no' to debug fpm
                <value name="daemonize">yes</value>

        </section>

        <workers>

                <section name="pool">

                        Name of pool. Used in logs and stats.
                        <value name="name">default</value>

                        Address to accept fastcgi requests on.
                        Valid syntax is 'ip.ad.re.ss:port' or just 'port' or '/path/to/unix/socket'
                        <!-- <value name="listen_address">/tmp/fcgi.sock</value> -->
                        <value name="listen_address">127.0.0.1:9000</value>

                        <value name="listen_options">

                                Set listen(2) backlog
                                <value name="backlog">-1</value>

                                Set permissions for unix socket, if one used.
                                In Linux read/write permissions must be set in order to allow connections from web server.
                                Many BSD-derrived systems allow connections regardless of permissions.
                                <value name="owner"></value>
                                <value name="group"></value>
                                <value name="mode">0666</value>
                        </value>

                        Additional php.ini defines, specific to this pool of workers.
                        <value name="php_defines">
                <!--            <value name="sendmail_path">/usr/sbin/sendmail -t -i</value> -->
                <!--            <value name="display_errors">0</value> -->
                        </value>

                        Unix user of processes
                        <value name="user">nginx</value>

                        Unix group of processes
                        <value name="group">nginx</value>

                        Process manager settings
                        <value name="pm">

                                Sets style of controling worker process count.
                                Valid values are 'static' and 'apache-like'
                                <value name="style">static</value>

                                Sets the limit on the number of simultaneous requests that will be served.
                                Equivalent to Apache MaxClients directive.
                                Equivalent to PHP_FCGI_CHILDREN environment in original php.fcgi
                                Used with any pm_style.
                                <value name="max_children">1</value>

                                Settings group for 'apache-like' pm style
                                <value name="apache_like">

                                        Sets the number of server processes created on startup.
                                        Used only when 'apache-like' pm_style is selected
                                        <value name="StartServers">20</value>

                                        Sets the desired minimum number of idle server processes.
                                        Used only when 'apache-like' pm_style is selected
                                        <value name="MinSpareServers">5</value>

                                        Sets the desired maximum number of idle server processes.
                                        Used only when 'apache-like' pm_style is selected
                                        <value name="MaxSpareServers">35</value>

                                </value>

                        </value>

                        The timeout (in seconds) for serving a single request after which the worker process will be terminated
                        Should be used when 'max_execution_time' ini option does not stop script execution for some reason
                        '0s' means 'off'
                        <value name="request_terminate_timeout">0s</value>

                        The timeout (in seconds) for serving of single request after which a php backtrace will be dumped to slow.log file
                        '0s' means 'off'
                        <value name="request_slowlog_timeout">0s</value>

                        The log file for slow requests
                        <value name="slowlog">logs/slow.log</value>

                        Set open file desc rlimit
                        <value name="rlimit_files">1024</value>

                        Set max core size rlimit
                        <value name="rlimit_core">0</value>

                        Chroot to this directory at the start, absolute path
                        <value name="chroot"></value>

                        Chdir to this directory at the start, absolute path
                        <value name="chdir"></value>

                        Redirect workers' stdout and stderr into main error log.
                        If not set, they will be redirected to /dev/null, according to FastCGI specs
                        <value name="catch_workers_output">yes</value>

                        How much requests each process should execute before respawn.
                        Useful to work around memory leaks in 3rd party libraries.
                        For endless request processing please specify 0
                        Equivalent to PHP_FCGI_MAX_REQUESTS
                        <value name="max_requests">500</value>

                        Comma separated list of ipv4 addresses of FastCGI clients that allowed to connect.
                        Equivalent to FCGI_WEB_SERVER_ADDRS environment in original php.fcgi (5.2.2+)
                        Makes sense only with AF_INET listening socket.
                        <value name="allowed_clients">127.0.0.1</value>

                        Pass environment variables like LD_LIBRARY_PATH
                        All $VARIABLEs are taken from current environment
                        <value name="environment">
                                <value name="HOSTNAME">$HOSTNAME</value>
                                <value name="PATH">/usr/local/bin:/usr/bin:/bin</value>
                                <value name="TMP">/tmp</value>
                                <value name="TMPDIR">/tmp</value>
                                <value name="TEMP">/tmp</value>
                                <value name="OSTYPE">$OSTYPE</value>
                                <value name="MACHTYPE">$MACHTYPE</value>
                                <value name="MALLOC_CHECK_">2</value>
                        </value>

                </section>

        </workers>

</configuration>

Запускаем nginx и php-fpm:

# /etc/init.d/php-fpm start
# /etc/init.d/nginx start

Конфиг mysql, /etc/my.cnf, для условий ограничений по RAM

[mysqld]
datadir=/var/lib/mysql
socket          = /var/lib/mysql/mysql.sock
user=mysql
skip-locking
key_buffer = 16M
max_allowed_packet = 1M
table_cache = 64
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M

skip-federated

log-bin=mysql-bin

server-id       = 1

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash

[isamchk]
key_buffer = 20M
sort_buffer_size = 20M
read_buffer = 2M
write_buffer = 2M

[myisamchk]
key_buffer = 20M
sort_buffer_size = 20M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid


Запускаем mysqld, и назначаем пароль для mysql рута

  # /etc/init.d/mysqld start
  # /usr/bin/mysql_secure_installation

Теперь добавляем пользователя и нужную базу данных, если надо (можно конечно поставить phpmyadmin, я не ставил, был не нужен и не секьюрно):

  # mysql --user=root -p
  mysql>  CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'my_password';
  mysql>  CREATE DATABASE mywordpress;
  mysql>  GRANT ALL PRIVILEGES ON `mywordpress`.* TO 'wordpressuser'@'%' WITH GRANT OPTION;

И дальше ставим движок php.

Тестировал apache benchmark'ом (с сервером ping 50-100mc, мой канал 1Мбит) ab -n 100 -c 5, wordpress 3.0 с одним средним постом, какой-то темой и seo плагином.

Document Length:        9774 bytes

Concurrency Level:      5
Time taken for tests:   49.761 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      997800 bytes
HTML transferred:       977400 bytes
Requests per second:    2.01 [#/sec] (mean)
Time per request:       2488.066 [ms] (mean)


При выключенном apc — в 2,5 раза всё медленней. Интересно, что при подключенном ZendOptimizer (с apc не работает) — медленней в 2,2-2,3 раза. optimization_level почти не влияет на результат (104 против 111 секунд на время теста). Так что не знаю откуда zend берёт 40% ускорения в своей рекламе. С eaccelerator к сожалению не сравнивал (ходит инфа, что быстрее apc, но пока сам не увидишь..), может быть руки дойдут.

PS Нюансы — старый nginx из epel, минимальный max_children (для меня было лучшее значение), можно ещё тюнить mysql, я привёл стандартный конфиг «mysql small». Ещё репозиторий varien похоже забросили, но исходники со спеками там есть.
  • +8
  • kernelx
  • 21 августа 2010, 00:02

Комментарии (12)

если есть апач — выключить

А кто будет генерировать динамический контент?
php-fpm конечно. Это рабочая конфигурация, без апача, с rewrite. Апач не нужен для генерации динамического контента, он может понадобится из-за каких-то своих специфических модулей или заморочек с .htaccess (решаемо в nginx). Вообще на хостингах где стоит nginx+apache, apache оставляют не для «генерации», так как там всё равно генерит динамику fcgi(fcgid), он нужен из-за модулей, rewrite, htaccess etc.
Php-fpm, как известно, глючит при работе с ним через unix-сокет, об этом было неоднократно сказано в рассылке разрабов, советую поменять его работу на tcp-сокет, если не хотите периодически ловить 503 и 504.
Можно пруф? Дело в том, что у меня работает не один месяц, постоянный мониторинг zabbix раз в 10 минут, всё время 200. 50*-ых нет в помине.
Сам конкретного по 50* ничего не нашёл, что-то есть, но не то. Реализовать 50* у себя синтетическими тестами не получилось. Но через tcp работает немного быстрее (у меня 48-49 против 50 секунд, что в принципе в рамках погрешности). Поправил статью с учётом работы черех tcp, оставив сокет в комментах.
groups.google.com/group/highload-php-en/browse_thread/thread/72987134573199ec
groups.google.com/group/highload-php-en/browse_thread/thread/1e0a48f80d19cf9d
Вот тут можете почитать, в целом все работает, но иногда возникают проблемы.
Как я понял, проблемы только в Linux, т.к. народ с БСД пишет что разницы вообще нет.
Нгинксу-бы аналог .htaccess (под другим именем) с реврайтом, сразу бы сняло необходимость использовать апач, но увы.
Так можно же. Проблема как раз, что он
под другим именем
. На коммерческом массовом хостинге под каждого не угодишь.
стопстопстоп… Я никогда не использовал нгинкс… Расскажи, что в нем заменяет хтакцесс? Я всегда считал, что нгинксом рулит только егойный конфиг и более ничего.
Вы правильно понимаете работу nginx, меня неправильно поняли :). Я имел ввиду, что можно переписать правила .htaccess для конфига nginx, ну и ложить их например в conf.d/. Вот например _http://bit.ly/aPweyH. То-есть для одиночных сайтов можно отказаться от апач, на хостинге нет (замучаться можно под каждого конфиг nginx делать).
Ну так вот когда нгинкс научится дополнять свой конфиг по пути следования до целевого файла и соответственно несколько менять свое поведение, тогда наступит рай на земле. Можно будет переписать хтакцессы в его формат и накласть новые конфиги рядом с хтакцесссами, так что не важно на чем работает сайт, работать он будет одинаково.
Можно скриптами реализовать некоторые варианты :). Но это конечно не айс, хотя мы да и многие, немного для другого (динамической генерации конфига nginx), делали.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.