VPS上部署 nodejs 应用并和现有应用共存

最近,nodejs 很火,我也尝试用 nodejs 写了一个小应用,主要用到了 express framework 和 socket.io ,数据库使用的 mongodb.

我有一台Linode的VPS ,自然是想把应用部署在自己的VPS上面,为什么没有选择云服务呢?一方面是省钱,我已经为Linode付过钱了,一方面是更加自由,可以随心配置。这台Linode上已经部署了标准的 LAMP 环境,跑了几个 wordpress 。那这次部署自然是不能影响到现有应用。

我的部署方案主要要达到的目的有:

  1. nodejs 应用可以像一个守护进程(daemon)一样运行
  2. 提供一对简单的命令来 start / stop nodejs 应用
  3. 提供监控机制,当应用崩溃,可以及时重启它
  4. 不影响现有 PHP 应用,但是也要用 80 端口访问
  5. socket.io 提供的 websocket 功能正常使用

下面分享下我的部署经验,不一定好,我也是第一次部署,欢迎交流。

1,在 VPS 上安装 nodejs ,我 VPS 跑的是 Ubuntu 12.04 , 最简单的方式当然是通过 apt-get 来安装,但是直接 apt-get install 的话,安装的版本比较旧,建议采用 https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager这个页面提供的方法,先添加源,再来安装,就可以安装到最新的稳定版了。这里还有其他主要 Linux 发行版的安装方式可以参考。下面的步骤默认使用的 OS 是 Ubuntu 12.04。

2,安装 mongodb (如果使用的不是 mongodb ,请忽略此步) ,http://www.mongodb.org/downloads#packages 这里同样有包管理器的安装方式,照着做就好了。

3,把你的代码放到 VPS 上来,然后以开发模式运行一次试试,不出意外的话,应该是可以运行了,如果有其他依赖需要解决,请自行解决。

4,把 nodejs 应用变成一个 daemon 。 我使用 upstart 来完成此任务 ,高版本的 ubuntu 已经自带,如果没有,直接 apt-get install upstart 来安装。然后新建一个文件在 /etc/init/yourapp.conf, 文件内容如下

#!upstart
description "node.js server"
author      "joe"

start on startup
stop on shutdown

script
    # We found $HOME is needed. Without it, we ran into problems
    export HOME="/var/www"

    echo $$ > /var/run/yourapp.pid
    exec sudo -u username NODE_ENV=production /usr/bin/node /where/yourapp.js >> /var/log/yourprogram.sys.log 2>&1
end script

pre-start script
    # Date format same as (new Date()).toISOString() for consistency
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/yourapp.sys.log
end script

pre-stop script
    rm /var/run/yourprogram.pid
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/yourapp.sys.log
end script

你需要修改上述文件的 username 为你想要运行应用的用户名,推荐 www-data ,不要使用 root ,比较危险。如果你不使用 www-data 来运行你的应用,记得把export HOME=""改成对应的 HOME 目录。/where/yourapp.js 是你应用的路径。/var/run/yourapp.pid是你应用的pid文件,/var/log/yourapp.sys.log是你应用的日志文件。这些内容都需要根据你的情况修改。NODE_ENV=production 是让你的 nodejs 应用运行在生产环境的环境变量,你可以在代码中通过 process.env.NODE_ENV来获取。

配置文件创建好后,你就可以使用下面命令来开启/关闭你的应用了。

#!sh
start yourapp
stop yourapp

5,监控你的应用。我们不能保证我们的代码总是运行的很好,总会有崩溃的可能,所以我们需要监控它的运行,当它挂掉后,重启它。这里使用 monit 来解决这个问题,安装方法自己去这个网站找吧。

安装好后,新建一个文件在/etc/init/monit/conf.d/yourapp.conf,文件内容如下:

#!monit
set logfile /var/log/monit.log

check process nodejs with pidfile "/var/run/yourapp.pid"
    start program = "/sbin/start yourapp"
    stop program  = "/sbin/stop yourapp"
    if failed port 3000 protocol HTTP
        request /
        with timeout 10 seconds
        then restart

需要替换 /var/run/yourapp.pid 为你刚才设置的 pid 文件,yourapp 替换成你刚才在 upstart 设置的。port 3000 设置成你的 nodejs 应用实际监听的端口号,timeout 时间,可以根据你的实际情况调整。这几行配置文件很容易看懂,就不解释了。

然后确保 /etc/monit/monitrc 文件存在 include /etc/monit/conf.d/* 这行, 同时,你也可以在这个文件里修改监控频率,根据你的实际情况调整。然后重启 monit: service restart monit让刚才的配置文件生效。

upstart 和 monit 的配置我是参考 http://howtonode.org/deploying-node-upstart-monit 这篇博客的,你也可以来这里看看。

6,配置 apache 。我们需要在 80 端口来访问 nodejs 应用,现在又存在 apache ,那最简单的方式就使用反向代理来解决这个问题了。首先,需要安装 mod_proxy 和 mod_proxy_http ,你可以现在 /etc/apache2/mods-enabled/ 这个目录查看是否已经安装了,如果没有安装,你可以通过 a2enmod 命令来快速安装。
然后,新建一个 virtualhost ,按照你的习惯,新建一个文件 include 进去,或者一起写在某个文件。virtualhost 配置如下:

    
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName yourhost.com

    ProxyRequests off

    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>

    <Location />
        ProxyPass http://localhost:3000/
        ProxyPassReverse http://localhost:3000/
    </Location>
</VirtualHost>

如果你的应用不是跑在 3000 端口上,记得修改配置, ServeName 记得改成你的域名,然后把你的域名解析到你 VPS 的 IP 上来。 ProxyPass 的作用是用 yourhost.com 来代理 localhost:3000 。ProxyPassReverse 的作用是处理 30X 跳转的情况,会把 Location:中的 http://localhost:3000 替换成 http://yourhost.com 。现在开启你的应用,重启 apache , 访问试试看。

如果都配置对了,到这里就应该能正常访问了。

使用反向代理时,要特别注意一些事情。页面中 a 标签的 url 尽量使用省略域名的形式,例如 href="/login",因为如果你处理不好,写上了 http://localhost:3000 这个后端的域名加端口,点击后就打不开了。对于 30* 跳转,Location 的值如果是 “//localhost:3000/login” 这种省略了协议的形式,apache 的反向代理也是无能为力的,我在 express 某个非正式版发现了这个问题。建议 Location 也使用省略域名的形式,这样虽然不符合 HTTP 1.1 的规范,但是各个浏览器都能兼容,可以省去这样的麻烦。

等等,如果你和我一样,也使用了 socket.io 来提供 websocket 服务,你会发现,现在长链接虽然可以用,但是却不是走 websocekt 模式了,这是因为 websocket 是无法通过 mod_proxy 来反向代理的,那就要另外想办法了。我使用的办法是:直接让 websocket 的请求去连接应用监听的后端真实端口。

7, 如果你使用了 websocket ,可以在页面中埋点,把带有你应用真实监听端口的 URI 埋入页面,然后前端 js 获取此 URI ,再来 io.connect( server ) 就可以让 websocket 正常运行了。需要注意的是,这个带端口的 URI 的域名需要和你访问页面的域名保持一致,否则取不到 cookie ,就没法获取 session 了。

如果你有更好的方式,请在下面留言交流,或者新浪微博 @allenm 来交流