Migracja z Apache do Nginx

by Mateusz Tymek — on

Head's up! This post was written back in 2010 and is very likely to contain outdated information.

Serwer na którym postawiony jest ten blog to "słaby" dedyk z oferty OVH, z zainstalowanym Debianem. Aktualnie obsługuje kilkanaście stron z dość małym natężeniem ruchu (głównie proste, statyczne wizytówki). Całość śmiga na serwerze Apache i mod_php. Ostatnio, spodziewając się zwiększonego ruchu na jednej z tych stron, postanowiłem zwiększyć wydajność poprzez instalację serwera WWW Nginx.

Migracja nie była taka prosta gdyż niektóre z wspomnianych serwisów korzystały z rozszerzeń Apache. Nie chcąc tracić czasu na przerabianie ich, podszedłem do problemu w następujący sposób:

  • umieściłem Nginx "ponad" apache tak aby ten pierwszy kierował cały ruch do drugiego;
  • dodając kolejne wirtualne hosty decydowałem która strona ma być serwowana wyłącznie przez Nginx.

Instalacja oprogramowania

Jedynym komponentem którego brakowało na serwerze był sam Nginx. Znajduje się on w repozytorium Debiana, a więc instalacja to jedno polecenie:

# apt-get install nginx

Nginx jako Reverse Proxy

Pierwszym etapem będzie postawienie Nginxa "ponad" Apachem i kierowanie całego ruchu do tego drugiego.

Najpierw zmieniamy port na którym nasłuchuje Apache z 80 na 8080. W Debianie edytujemy plik /etc/apache2/ports.conf:

NameVirtualHost *:8080
Listen *:8080

Główna konfiguracja znajduje się w pliku /etc/nginx/nginx.conf:

user www-data;
worker_processes  1;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    access_log    /var/log/nginx/access.log;
    sendfile           on;     
    keepalive_timeout  65;  # Nad tym można się zastanowić
    tcp_nodelay        on;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Jak widać dołączone będą wszystkie pliki z katalogu /etc/nginx/sites-enabled. Jest to mechanizm znany z Apache: wszystkie hosty zdefiniowane są w osobnych plikach w /etc/nignx/sites-available, a włączamy je poprzez utworzenie linków w /etc/nginx/sites-enabled.

Pierwszy wirtualny host będzie nasłuchiwał na porcie 80, a następnie kierował cały ruch do Apacza:
Plik /etc/nginx/sites-available/default:

server {
    listen   80;
    server_name  localhost;

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

    location / {
        proxy_pass http://127.0.0.1:8080/;
        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;        
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/nginx-default;
    }

}

Można już zrestartować Apache i uruchomoć nginx:

# /etc/init.d/apache2 restart
# /etc/init.d/nginx start

Od tej pory Nginx będzie działał ponad Apaczem, kierując do niego wszystkie żądania.
Tutaj zrobiłem mały przystanek aby sprawdzić czy wszystkie strony które obsługuje serwer dalej działają poprawnie. Jak się okazało - działały :-)

Konfiguracja hostów

Załóżmy że mamy wykupioną domenę "mojastrona.pl" i chcemy podpiąć ją do katalogu /var/www/mojastrona.

Tworzymy plik /etc/nginx/sites-available/mojastrona:

server {
    listen 80;
    server_name mojastrona.pl;
    root /var/www/mojastrona.pl
    location / {
    }
    location ~ \.php$ {
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      include        /etc/nginx/fastcgi_params;
    }
}

Tworzymy dowiązanie, aby włączyć stronę:

ln -s /etc/nginx/sites-available/mojastrona /etc/nginx/sites-enabled/mojastrona

Sekcja "location ~ \.php$" mówi serwerowi w jaki sposób ma traktować pliki PHP.

PHP w trybie fastcgi

Plik /etc/nginx/fastcgi_params odpowiada za parametry przekazywane interpreterowi PHP przez serwer. Warto wymienić przede wszystkim jeden z nich: SCRIPT_FILENAME. Jeśli podana tutaj wartość będzie nieprawidłowa to PHP zwróci komunikat "no input file specified".

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

fastcgi_param  REDIRECT_STATUS    200;

Ostatnia sprawa to skonfigurowanie intepretera PHP aby cały czas był uruchomiony i nasłuchiwał na porcie 9000. Posłużę się tutaj gotowym skryptem który znalazłem gdzieś w sieci.

Skrypt startowy - /etc/init.d/php-fastcgi:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          php-fastcgi
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start and stop php-cgi in external FASTCGI mode
# Description:       Start and stop php-cgi in external FASTCGI mode
### END INIT INFO

# Author: Kurt Zankl <[EMAIL PROTECTED]>

# Do NOT "set -e"

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="php-cgi in external FASTCGI mode"
NAME=php-fastcgi
DAEMON=/usr/bin/php-cgi
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
PHP_CONFIG_FILE=/etc/php5/cgi/php.ini

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

# If the daemon is not enabled, give the user a warning and then exit,
# unless we are stopping the daemon
if [ "$START" != "yes" -a "$1" != "stop" ]; then
        log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
        exit 0
fi

# Process configuration
export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT -c $PHP_CONFIG_FILE"

do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
                --background --make-pidfile --chuid $EXEC_AS_USER:$EXEC_AS_GROUP --startas $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
}

do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}
case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
        exit 3
        ;;
esac

Plik konfiguracyjny: /etc/default/php-fastcgi:

START=yes

# Which user runs PHP? (default: www-data)
EXEC_AS_USER=www-data
EXEC_AS_GROUP=www-data

# Host and TCP port for FASTCGI-Listener (default: localhost:9000)
FCGI_HOST=localhost
FCGI_PORT=9000

# Environment variables, which are processed by PHP
PHP_FCGI_CHILDREN=4
PHP_FCGI_MAX_REQUESTS=1000

Uruchamiamy PHP:

# /etc/init.d/php-fastcgi start

Zakończenie

Restartujemy nginxa:

# /etc/init.d/nginx restart

Uwaga: przedstawione pliki konfiguracyjne będą działy tylko na Debianie i debiano-podobnych odmianach Linuksa. Uruchomienie tego na innym systemie będzie wymagało kilku (niezbyt skomplikowanych) modyfikacji.

Zend Framework

Nginx zawiera mechanizm przepisywania adresów, jednak jego konfiguracja jest zupełnie inna od tej z Apache. Jeżeli chcemy przepisywać urle w stylu Zenda, a więc kierować żądania nieistniejących plików do skryptu index.php, możemy posłużyć się następującą regułą (umieszczoną w sekcji server w pliku /etc/nginx/sites-available/mojastrona):

if (!-e $request_filename) {
    rewrite ^.*$ /index.php last;
}

comments powered by Disqus