nginx+php-fpm導入

移転してから自分でサーバーの中身をいじるようになったためか、ちょっとでも重くなるとすぐにWebminで負荷を見るようになった。まぁ、それ以前に設定が面倒だからとPHPをApacheモジュールで使っていたので重いのも当然である。で、CGIモードでPHPを動かす方法を調べていたのだが、そもそもApacheに拘る理由とはなんだろうか?と考えた。HTTPデーモンと聞くと無意識にApacheと考えてしまいそれ以外、見落としているプログラムがあるかもしれない。そういえば、昔Windowsでサーバーひらいている人はAN-HTTPD使ってた気がするし、MacはMacでOS標準のWeb共有というプログラムでWebサーバー開いていたような。

というわけで、他のHTTPDサーバーについて調べてみた。すると、lighttpdというのとnginxというのが浮かび上がった。どちらも共通しているのが、PHPを別プロセスで動かしているところである。どっちにするか迷ったが、開発背景と、コンフィグファイルの構文から後者のnginxを選ぶことにした。なんと、nginxはロシア製である。どうやら、ロシアの開発者が自社のポータルサイト向けに作ったHTTPDサーバーらしい。最初から設計方針がはっきりしている点がポイント高い。ちsourceForgeや最近話題のGitHubなどで使われているらしい。まぁ、弱小個人サイトに導入するほどのものではないが、どうせやるならってことで。

まずは、Ubuntuにインストール。例によってapt-getで。

apt-get install nginx

すると、/etc/nginx以下に設定ファイルが置かれる。

まずは、nginx.confの設定。PukiWiki Adv.の開発でお世話になっているHTML5 ★ Boilerplate推奨nginx.confを参考に設定を組む。この推奨設定は、Yslowのガイドラインに基づいた設定になっている。

nginxの設定

nginx.conf

user				www-data;
worker_processes	1;

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

events {
	# When you need > 8000 * cpu_cores connections, you start optimizing
	# your OS, and this is probably the point at where you hire people
	# who are smarter than you, this is *a lot* of requests.
	worker_connections 8000;

	# This sets up some smart queueing for accept(2)'ing requests
	# Set it to "on" if you have > worker_processes
	accept_mutex off;

	# These settings are OS specific, by defualt Nginx uses select(2),
	# however, for a large number of requests epoll(2) and kqueue(2)
	# are generally faster than the default (select(2))
	# use epoll; # enable for Linux 2.6+
	# use kqueue; # enable for *BSD (FreeBSD, OS X, ..)
}

http {
	# Set the mime-types
	include	  	/etc/nginx/mime.types;

	# And the fallback mime-type
	# default_type application/octet-stream;

	# Format for our log files
	log_format main '$remote_addr - $remote_user [$time_local] $status '
		'"$request" $body_bytes_sent "$http_referer" '
		'"$http_user_agent" "$http_x_forwarded_for"';

	# Click tracking!
	access_log	/var/log/nginx/access.log;

	# ~2 seconds is often enough for HTML/CSS, but connections in
	# Nginx are cheap, so generally it's safe to increase it
	keepalive_timeout 5;

	# You usually want to serve static files with Nginx
	sendfile on;

	tcp_nopush on; # off may be better for Comet/long-poll stuff
	tcp_nodelay off; # on may be better for Comet/long-poll stuff

	# Enable Gzip
	gzip on;
	gzip_http_version 1.0;
	gzip_comp_level 2;
	gzip_min_length 1100;
	gzip_buffers 4 8k;
	gzip_proxied any;
	gzip_types text/plain text/xml application/xml application/xml+rss text/css text/javascript application/javascript application/json;

	gzip_static on;

	gzip_proxied expired no-cache no-store private auth;
	gzip_disable "MSIE [1-6]\.";
	gzip_vary on;

	upstream backend {
#		server	127.0.0.1:9000;
		server	unix:/var/run/php-fpm.socket;
	}

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

次にデフォルトのサイト設定を行う。サイトごとの設定は、./sites-enabled/ディレクトリに設置する。とりあえず、テストなのでポートは8080を使用しよう。

server {
	## listen for ipv4; this line is default and implied
	listen		8080;
	## listen for ipv6
	#listen		[::]:80 default ipv6only=on;

	# e.g. "localhost" to accept all connections, or "www.example.com"
	# to handle the requests for "example.com" (and www.example.com)
	server_name _;
	# server_name  localhost;

	# Path for static files
	root   /var/www;

	index  index.html index.htm index.php index.cgi;

	expires 1M;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to index.html
		try_files $uri $uri/ /index.html;
	}

	#error_page  404  /404.html;

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

	# proxy the PHP scripts to Apache listening on 127.0.0.1:80
	#
	#location ~ \.php$ {
	#	proxy_pass   http://127.0.0.1;
	#}

	# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
	#
	location ~ \.php$ {
		fastcgi_pass   backend;
		fastcgi_index  index.php;
		fastcgi_param  SCRIPT_FILENAME  /var/www$fastcgi_script_name;
#		fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
		include fastcgi_params;
	}

	# deny access to .htaccess files, if Apache's document root
	# concurs with nginx's one
	#
	location ~ /\.ht {
		deny  all;
	}
}

次にサービスに登録する。http://code.google.com/p/nginx-init-ubuntu/で配布されているシェルスクリプトを使おう。ただし、DAEMONと、NGINX_CONF_FILEの値が違うためそのままでは動かない。以下のように変更すること。

#------------------------------------------------------------------------------
#                               Consts
#------------------------------------------------------------------------------
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/local/sbin/nginx
+DAEMON=/usr/sbin/nginx
 
PS="nginx"
PIDNAME="nginx"				#lets you do $PS-slave
PIDFILE=$PIDNAME.pid                    #pid file
PIDSPATH=/var/run
 
DESCRIPTION="Nginx Server..."
 
RUNAS=root                              #user to run as
 
SCRIPT_OK=0                             #ala error codes
SCRIPT_ERROR=1                          #ala error codes
TRUE=1                                  #boolean
FALSE=0                                 #boolean
 
lockfile=/var/lock/subsys/nginx
NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
+NGINX_CONF_FILE="/etc/nginx/nginx.conf"

設定が完了したら、/etc/init.d/nginxにこのファイルを保存し、chmod 755 、/etc/init.d/nginxで実行権限を与え、/usr/sbin/update-rc.d -f nginx defaultsを実行。

この例では、/var/wwwをサイトのトップにしてある。nginxをシェルから起動してhttp://localhost:8080/で確認して、ちゃんとアクセス出来るか試してみよう。 こんな感じでいいだろう。

php-fpmの設定

次にphp5-fpmのインストールと設定である。

apt-get install php5-fpm

で、/etc/php5/fpmに設定ファイルが置かれる。

自分は以下のように設定した。

;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;

; All relative paths in this configuration file are relative to PHP's install
; prefix.

; Include one or more files. If glob(3) exists, it is used to include a bunch of
; files from a glob(3) pattern. This directive can be used everywhere in the
; file.
;include=/usr/local/php/etc/fpm.d/*.conf

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;

[global]
; Pid file
; Default Value: none
pid = /var/run/php-fpm.pid

; Error log file
; Default Value: /usr/local/php/var/log/php-fpm.log
error_log = /var/log/php-fpm/php-fpm.log

; Log level
; Possible Values: alert, error, warning, notice, debug
; Default Value: notice
log_level = error

; If this number of child processes exit with SIGSEGV or SIGBUS within the time
; interval set by emergency_restart_interval then FPM will restart. A value
; of '0' means 'Off'.
; Default Value: 0
;emergency_restart_threshold = 0

; Interval of time used by emergency_restart_interval to determine when
; a graceful restart will be initiated.  This can be useful to work around
; accidental corruptions in an accelerator's shared memory.
; Available Units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
;emergency_restart_interval = 0

; Time limit for child processes to wait for a reaction on signals from master.
; Available units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
;process_control_timeout = 0

; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging.
; Default Value: yes
daemonize = yes

;;;;;;;;;;;;;;;;;;;;
; Pool Definitions ;
;;;;;;;;;;;;;;;;;;;;

; Multiple pools of child processes may be started with different listening
; ports and different management options.  The name of the pool will be
; used in logs and stats. There is no limitation on the number of pools which
; FPM can handle. Your system will tell you anyway :)

; Start a new pool named 'www'.
[www]

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
;listen = 127.0.0.1:9000
listen = /var/run/php-fpm.socket

; Set listen(2) backlog. A value of '-1' means unlimited.
; Default Value: -1
listen.backlog = 1
 
; List of ipv4 addresses of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
;listen.allowed_clients = 127.0.0.1

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
;                 mode is set to 0666
listen.owner = www-data
listen.group = www-data
listen.mode = 0666

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data

; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives:
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes to be created when pm is set to 'dynamic'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either 'static' or 'dynamic'
; Note: This value is mandatory.
;pm.max_children = 50
pm.max_children = 25
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
;pm.start_servers = 20
pm.start_servers = 10
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
;pm.min_spare_servers = 5
pm.min_spare_servers = 3
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
;pm.max_spare_servers = 35
pm.max_spare_servers = 17
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500
pm.max_requests = 250
; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. By default, the status page shows the following
; information:
;   accepted conn    - the number of request accepted by the pool;
;   pool             - the name of the pool;
;   process manager  - static or dynamic;
;   idle processes   - the number of idle processes;
;   active processes - the number of active processes;
;   total processes  - the number of idle + active processes.
; The values of 'idle processes', 'active processes' and 'total processes' are
; updated each second. The value of 'accepted conn' is updated in real time.
; Example output:
;   accepted conn:   12073
;   pool:             www
;   process manager:  static
;   idle processes:   35
;   active processes: 65
;   total processes:  100
; By default the status page output is formatted as text/plain. Passing either
; 'html' or 'json' as a query string will return the corresponding output
; syntax. Example:
;   http://www.foo.bar/status
;   http://www.foo.bar/status?json
;   http://www.foo.bar/status?html
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status
 
; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; - create a graph of FPM availability (rrd or such);
; - remove a server from a group if it is not responding (load balancing);
; - trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping

; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong
 
; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0
 
; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0
 
; The log file for slow requests
; Default Value: /usr/local/php/var/log/php-fpm.log.slow
;slowlog = /usr/local/php/var/log/php-fpm.log.slow
 
; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024
 
; Set max core size rlimit.
; Possible Values: 'unlimited' or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0
 
; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: chrooting is a great security feature and should be used whenever
;       possible. However, all PHP paths will be relative to the chroot
;       (error_log, sessions.save_path, ...).
; Default Value: not set
;chroot =
 
; Chdir to this directory at the start. This value must be an absolute path.
; Default Value: current directory or / when chroot
;chdir = /var/www
 
; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Default Value: no
;catch_workers_output = yes
 
; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
;   php_value/php_flag             - you can set classic ini defines which can
;                                    be overwritten from PHP call 'ini_set'.
;   php_admin_value/php_admin_flag - these directives won't be overwritten by
;                                     PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.

; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.

; Default Value: nothing is defined by default except the values in php.ini and
;                specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

キモとなるのはlistenの部分で、多くのサイトでは127.0.0.1:9000にしているが、自分の場合これをやったら502 Bad Gatewayエラーが多発したので、Socketによる通信でphp-fpmを動かすことにした。

ポイントとなるのは以下の箇所。

/etc/nginx/nginx.conf
	upstream backend {
#		server	127.0.0.1:9000;
		server	unix:/var/run/php-fpm.socket;
	}
/etc/php5/fpm/main.conf
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
;listen = 127.0.0.1:9000
listen = /var/run/php-fpm.socket

お気づきだろうか?nginx.confのserverの部分と、php5-fpmのmain.confのlistenの部分の設定は揃えなければならない。一般的な例では、このパスが、/var/run/php-fpm/php-fpm.sockになっていたが、自分の環境の場合、php-fpmディレクトリが消えてしまってちゃんと動かなかったことがあったので、/var/run/php-fpm.socketというファイル名にしてある。注意したいのは、nginx側でsocketファイルを指定する場合、unix:/var/run/php-fpm.socket; というように、unix:が必要になるところ。

テスト

念のため正常に動作するかチェック。以下のコマンドで確認できる。

nginx -t

エラーがでなければ動作開始しよう。ただし、phpが動作するかどうかまではわからない。

service nginx start

あと、phpの動作確認に、/var/wwwに内容が<?php phpinfo(); ?>のphpinfo.phpを作成しよう。

service php5-fpm start