2013年12月9日

Ubuntu+Nginx搭建全功能服务器

作者 非鱼

最终效果:nginx作为统一的前端服务器,处理所有的站点的静态页面,同时,将不同站点的脚本发送到不同的后端引擎,包括Django(Python),PHP,NodeJs,当然,同理可以继续增加Mono+.Net的站点,和ROR的站点配置。

以前一直在使用lighttpd,不过这服务器貌似不怎么更新了,传说中的2.0已经说了好几年了也不出来,只是过几个月偶尔放一个补丁版本出来修改一两个重要Bug,远不像nginx这么活跃。而且原来的lighttpd的PHP采用了fastcgi模式配置以后,进程里面5个PHP站点启动了十几个PHP的fastcgi进程,相当浪费内存。于是决定统一换到nginx。不过过程还是挺痛苦的。

安装nginx,直接apt-get install nginx搞定。配置文件也自动安装好了,自己编译安装的话当配置文件就得累个半死。/etc/nginx/目录下好多配置文件,其中的nginx.conf里面的连接数、压缩、缓存之类的参数自己可以改一改,其它的不用动。

在site-available目录下已经有一个默认的default站点配置,在site-enabled目录下是指向它的一个软链接。同样,把所有的可用的站点配置文件放在site-available目录下,把需要生效的站点链接到site-enabled下即可。可以删掉default。

第一步,先是PHP。mysql和PHP我比较喜欢自己编译安装。mysql现在安装的是MariaDB 5.5.34,编译参数都跟mysql完全一样。下载tar.gz的源代码文件,tar zxvf mariadb-5.5.34.tar.gz解压,然后进入这个目录,使用这个配置:

cmake . \
-DCMAKE_BUILD_TYPE:STRING=Release \
-DCMAKE_INSTALL_PREFIX:PATH=/opt/mysql \
-DINSTALL_DATADIR=/docs/mysqldb \
-DCOMMUNITY_BUILD:BOOL=ON \
-DENABLED_PROFILING:BOOL=ON \
-DENABLE_DEBUG_SYNC:BOOL=OFF \
-DINSTALL_LAYOUT:STRING=STANDALONE \
-DMYSQL_MAINTAINER_MODE:BOOL=OFF \
-DWITH_EMBEDDED_SERVER:BOOL=ON \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS:STRING=all \
-DWITH_SSL:STRING=bundled \
-DWITH_UNIT_TESTS:BOOL=OFF \
-DWITH_ZLIB:STRING=bundled \
-LH

然后make && make install就安装好了。安装后把my.cnf放到/opt/mysql目录下,用这个目录下的mysql.server start就可以启动。

然后下载PHP的5.5.6版本,同样解压后进入目录,执行

./configure –prefix=/opt/php –disable-debug –with-config-file-path=/opt/php/etc/php.ini –enable-shmop –with-gd –with-jpeg-dir=/usr/lib64 –with-png-dir –with-libxml-dir –with-zlib-dir –with-mysqli=mysqlnd –with-mysql=mysqlnd –with-pdo-mysql=mysqlnd –enable-sockets –with-iconv –enable-mbstring –enable-mbregex –enable-ftp –enable-gd-native-ttf –with-freetype-dir –enable-fpm –with-fpm-user=nobody –with-fpm-group=nogroup –enable-opcache

中间提示缺什么组件就搜一下,然后用apt-get install安装对应的包就可以。注意-enable-fpm,这是fastcgi模式下的PHP的进程管理工具,在高版本的PHP里已经是内嵌的功能,启用后会生成一个独立的php-fpm的可执行文件。另外最后的-enable-opcache,就是以前版本中的zend optimizer,加速器,5.5里面也变成了内置的部分。但是这个东西编译安装后并不会自动生效,需要自己加到php.ini里去。

配置完成后make && make install,就安装好了。然后把php.ini-production复制到/opt/php/etc/php.ini,里面需要修改的东西不多,可以在最后面加上这么一段:

zend_extension=opcache.so
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.enable=1

用来启用加速功能。另外/opt/php/conf/目录下有个php-fpm.conf.default,改名成php-fpm.conf,然后修改里面listen参数为listen = /tmp/php-fpm.socket,下面的pm相关的几个值可以自己根据情况调一下。编译后的php源码目录里,sapi/fpm目录下有个init.d.php-fpm,可以把它复制为/etc/init.d/php-fpm文件,并添加可执行属性,这样就可以通过它来启动fpm了,/etc/init.d/php-fpm start。不过这个脚本启动以后不会加载php.ini,不知道为什么,可以打开这个文件修改一下,在第四行的参数定义的地方加上-c /opt/php/etc/php.ini。

启动以后应该就有了/tmp/php-fpm.socket文件。这时候在nginx里添加一个站点,转发给PHP进程就可以了。在/etc/nginx/site-available目录下添加一个自己站点名字的配置文件,内容是:

server {
    listen 80;
    server_name www.unfish.net;
    location / {
    root /docs/htdocs/public/unfish/wordpress/;
    index index.php index.html index.htm;

    if (-f $request_filename) {
      expires 30d;
      break;
    }

    if (!-e $request_filename) {
      rewrite ^(.+)$ /index.php?q=$1 last;
    }
  }

  location ~ .php$ {
    fastcgi_pass   unix:/tmp/php-fpm.socket;  # port where FastCGI processes were spawned
    include fastcgi_params;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME    /docs/htdocs/public/unfish/wordpress/$fastcgi_script_name;  # same path as above
    fastcgi_param PATH_INFO               $fastcgi_script_name;
  }
}

可以看的出来这是一个wordpress程序的配置文件,这个配置即包含了静态文件的托管,也包含了URL静态化以后的处理逻辑。fastcgi_pass unix:/tmp/php-fpm.socket;这个配置就是转发请求到php的socket接口。网上很多教程会讲ip:port的方式,其实在本地来说,socket方式效率更高,也不占用端口。对于其它的PHP站点,内容基本完全一样,只需要改server_name和root的路径参数。试试看PHP站点是不是可以打开了?

然后我们添加一个Django站点的配置文件。Django 1.4以前的版本是推荐fastcgi方式启动的,需要flup组件。后面的版本推荐的方式改成了wsgi方式,而且在项目生成模板的时候,就会自动生成一个wsgi.py文件,用来启动wsgi模式的服务。我现有的站点生成的版本比较老,没有这个文件,这也没关系,我们使用ini文件的方式来启动。在生产环境中运行django项目,就不能靠这个wsgi.py和manage.py了,需要更完整的wsgi服务器,uwsgi。下载最新版本的源代码,解压后在它的目录里执行python uwsgiconfig.py –build,就会执行编译命令,然后得到一个独立的可执行文件,uwsgi,简单起见,把这个文件复制到/usr/sbin/目录里就可以用了。

然后在Django项目的目录下,在settings.py的同级目录里,建一个wsgi.ini,内容是:

[uwsgi]
socket = /docs/htdocs/licaie/web.sock
chdir = /docs/htdocs/licaie/web/
pythonpath = ..
env = DJANGO_SETTINGS_MODULE=web.settings_fastcgi
module = django.core.handlers.wsgi:WSGIHandler()
processes = 4
master = true
threads = 5
daemonize = /var/log/uwsgi-licaie.log

第一行同样是生成后的socket文件用于连接nginx,后面的chdir是项目目录(我的项目名是web),settings_fastcgi.py是在这个项目目录下的用于生产环境的配置文件的名字,其它的都可以自己调。然后执行uwsgi wsgi.ini,这个站点就算启动了。然后在nginx下面增加一个站点的配置文件,内容是:

upstream django-licaie {
        server unix:/docs/htdocs/licaie/web.sock;
}
server {
    listen 80;
    server_name www.licaie.com;
    location ~ ^/site_media/(.*) { # STATIC_URL
        alias /docs/htdocs/licaie/web/templates/$1;
        autoindex off;
        expires 30d;
    }
    location /uploads { # STATIC_URL
        alias /docs/htdocs/licaie/web/uploads;
        expires 30d;
    }
    location ~ /(.*) {
        uwsgi_pass  django-licaie;
        include     uwsgi_params;
    }
}

site_media和uploads是两个静态目录,alias可以指向不同文件名的目录。最下面的代码指定转发方式。重启nginx,应该就可以看到这个站点的成功运行了。

最后是nodejs,这个不能直接apt-get install nodejs,因为主源里面的nodejs版本太老了,新的组件都不支持这个版本,需要添加一个服务器。

sudo apt-get install -y python-software-properties python g++ make
sudo add-apt-repository -y ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs

这样就可以安装上最新版本的nodejs了。然后用npm install express -g安装express,然后就可以创建基于express的站点了。对于express生成的站点模板,理论上只要在这个站点目录下执行node app.js,这个服务器就启动了。但是,同样的,这个方式也不适合生产环境。因为它启动的只是个单任务单线程的站点。我们需要用到PM2这个项目。只要运行

npm install -g pm2

就可以了。然后在这个站点目录下执行pm2 start app.js -i max就可以执行起这个站点。然后需要到nginx里添加一个对应的站点配置文件,内容是:

root@li315-123:/opt/php# ls var/
upstream nodejsapp1_upstream {
      server 127.0.0.1:9000;
      keepalive 8;
    }
server {
    listen 80;
    server_name nodeapp1.unfish.net;
        location ~ ^/(images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) {
        root /docs/htdocs/nodejsApp/nodeapp1/;
          access_log off;
          expires max;
        }
        location / {
          proxy_redirect off;
          proxy_set_header   X-Real-IP            $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_set_header   X-Forwarded-Proto $scheme;
          proxy_set_header   Host                   $http_host;
          proxy_set_header   X-NginX-Proxy    true;
          proxy_set_header   Connection "";
          proxy_http_version 1.1;
          proxy_pass         http://nodejsapp1_upstream;
        }
}

内容跟上面的差不多,只是这里目前我还没有找到用socket方式连接的方法,只有用IP+端口的方式。这个站点的端口号就在站点的app.js文件里,打开就看到了。重启nginx以后远程访问这个站点的域名,应该就可以看到这个站点输出的信息了。(默认是welcome to Express)

至此,一个支持PHP+Django+Nodejs的服务器就搭建好了。基本同样的代理方式,还可以再为nginx添加对Mono和ROR的支持。