在Docker中运行Node.js的Web应用


【编者的话】本文是十七蝉同学撰写的基础实战类博客,作者通过代码的形式Step by step介绍了如何在Docker中运行Node.js应用。初学的同学可以一读。

在Docker环境下搭建了Node.js的Web应用运行环境:
* Node.js
* MongoDB
* Redis
* winston和morgan,日志

以下介绍一下搭建环境的步骤和注意事项。

准备工作

需要安装Docker,我的环境是Ubuntu Serer 14.04虚拟机。如果直接用apt-get install docker.io无法获得比较新的Docker版本。我参照这里:Docker 1.2 on Ubuntu 14.04.1,安装了Docker 1.2版本。即使用Docker官方的第三方Ubuntu源。

加入Docker的GPG Key
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 

加入Docker的源:
sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" 

更新包列表:
sudo apt-get update

安装Docker
sudo apt-get install lxc-docker 

重启系统:
sudo reboot

如果执行下面命令并看到类似的结果就说明安装成功了:
$ docker version
Client version: 1.2.0

最简单的通过Docker执行Node.js

执行一个简单的Node.js命令:
node --version

不是使用本地的Node.js,而是使用Docker,只需执行:
$ sudo docker run -it --rm node node --version
v0.10.33

对于第一次运行上面命令,会出现类似:
Unable to find image 'node' locally
Pulling repository node
63d7e1e1d897: Pulling dependent layers 
511136ea3c5a: Download complete 
36fd425d7d8a: Download complete 
aaabd2b41e22: Download complete 
f99c114b8ec1: Downloading [==>  
...

Docker本地并没有node的镜像(image),需要到官网(https://hub.docker.com)上查询这个名字的镜像,并下载到本地。这个过程可能比较漫长,在我这里需要30分钟左右。总之,下载完镜像(700多MB)后,镜像会启动一个容器(container)。可以把镜像看做Java的类(class),容器看做对象(object)。

这个容器包含一个最小的可运行的轻量级的虚拟机,当然还有Node.js。

说下命令的参数:

docker run -it --rm node node --version

其中--it
  • i,容器的标准输入保持打开
  • t,Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入


--rm,运行结束后删除容器。再后面就是我们要执行的命令。

将Web Application跑起来

首先,要准备一个简单的Web Application。我这里写好了一个简单的应用ProtoWebApp。拿到项目文件后,先用宿主的node安装:
$ sudo npm install
然后跑起来测试一下,看是否能在浏览器上访问。
下面,是用Docker里的Node.js跑这个Web Application了(在项目的根目录下):
sudo docker run --rm -it -p 3000:3000 --name ProtoWebApp -v "$(pwd)":/webapp -w /webapp  node npm start

在这里:
* -v:分割的路径,前者表示宿主的路径(在这里也就是expressjs项目的主目录),后者表示映射到Docker容器的路径。
* -w,表示将-v映射的/webapp目录设置为work directory,也就是运行node命令的目录。这个设置将覆盖Dockfiie中的设置:/Data

如果需要让Docker容器跑在后台,可以加上-d
sudo docker run --rm -itd -p 3000:3000 --name ProtoWebApp -v "$(pwd)":/webapp -w /webapp  node npm start

另外,如想了解这个镜像都包含哪些内容,可以看这里:Dockerfile/Node.js

日志的处理

运维中需要记录几种日志:
  • HTTP请求日志,为了以后分析访问量等数据时使用
  • 应用日志,可能有错误或者其他调试信息,便于发现错误,排错


HTTP请求日志

很多情况下未必用到这个,因为在Node.js的Web Appp前,可能还有Nginx,用后者做端口代理。目前的Expressjs,是4.x版本,使用的HTTP日志,是morgan。可以在app.js中看到:
var logger = require('morgan');
默认的日志是对接到标准输出上的。我们希望在生产环境(production)下和开发环境(development)情况下不一样:
* 生产环境(production):HTTP日志记录到文件
* 开发环境(development):打印到标准输出

这需要做两件事:
1. 通过docker命令设置为production
1. app.jsproduction情况下记录日志到文件中

docker run命令中加入production变量设置:
sudo docker run --rm -it -p 3000:3000 --name ProtoWebApp -v "$(pwd)":/webapp -w /webapp -e NODE_ENV=production  node npm start

即,-e NODE_ENV=production
设置保存日志到文件。找到app.js的这行:
app.use(logger('dev'));
改为:
if (app.get('env') === 'development') {
app.use(logger('dev'));
}

if (app.get('env') === 'production') {
var fs = require('fs')
var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'})
app.use(logger('combined', {stream: accessLogStream}))
}

这样,当development模式打印到标准输出,production模式下输出到项目根目录下的access.log文件中。源代码见这里:https://github.com/MarshalW/ProtoWebApp/tree/m2

应用日志

这个日志是必须要有的,可帮助开发者发现和诊断问题。使用的是winston。需要将winston加入到package.json中:
"winston":""

然后引入库:
var winston = require('winston');
再设置文件路径(我这里是app.log):
if (app.get('env') === 'production') {
var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'});
app.use(logger('combined', {stream: accessLogStream}));

winston.add(winston.transports.File, { filename: 'app.log' });
}

Docker不需要设置什么,就可以在项目的根目录下看到app.log文件了,如果运行没有问题的话。

连接Redis

和Node.js镜像类似,可以通过如下命令将Redis跑起来:
$ sudo docker run -d --name redis -p 6379:6379 redis
当Docker本地没有redis镜像的时候,会自动先下载该镜像的最新版本。redis镜像内容见:Dockerfile/redis。然后,我们可以启动Web App:
$ sudo docker run --rm -it -p 3000:3000 --name ProtoWebApp --link redis:redis -v "$(pwd)":/webapp -w /webapp -e NODE_ENV=production  node npm start

> ProtoWebApp@0.0.0 start /webapp
> node ./bin/www

info: Hello again distributed logs
Reply: OK
Reply: 0
Reply: 0
2 replies:
0: hashtest 1
1: hashtest 2

比上面启动Node.js的方式,多了:--link redis:redis,冒号前的redis表示镜像名称,后面的redis表示这里使用的别名。

另外,创建Client的代码有点不同:
var redis = require("redis"),
    client = redis.createClient(6379, "redis");

其中redis是redis容器的别名。或者讲究点也可以这样:
var redisHost  = process.env.REDIS_PORT_6379_TCP_ADDR;
var redis = require("redis"),
    client = redis.createClient(6379, redisHost);

源代码见这里:https://github.com/MarshalW/ProtoWebApp/tree/m4

连接MongoDB

执行命令,启动mongoDB:
sudo docker run -d -p 27017:27017 -v "$(pwd)"/db:/data/db --name mongodb dockerfile/mongodb

数据库文件保存在当前目录下的db目录下,如果不存在目录的话会自动创建。

package.json中加入:
"mongoose":""

app.js代码中加入:
//测试mongoDB
var mongoose = require('mongoose');
mongoose.connect('mongodb://mongodb/test');

var Cat = mongoose.model('Cat', { name: String });

var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function (err) {
if (err) console.log(err);
console.log('meow');
});

执行Docker命令:
$ sudo docker run --rm -it -p 3000:3000 --name ProtoWebApp --link redis:redis --link mongodb:mongodb  -v "$(pwd)":/webapp -w /webapp -e NODE_ENV=production  node npm start

> ProtoWebApp@0.0.0 start /webapp
> node ./bin/www

info: Hello again distributed logs
js-bson: Failed to load c++ bson extension, using pure JS version
Reply: OK
Reply: 0
Reply: 0
2 replies:
0: hashtest 1
1: hashtest 2
meow


源代码见这里:https://github.com/MarshalW/ProtoWebApp/tree/m5

本文收发于我的个人博客http://blog.shiqichan.com/Depl ... cker/

4 个评论

谢谢您的分享,我有个问题想请教您, 看到您是把项目的代码放在docker之外的,您是放在宿主机上的,然后把主机上的代码目录映射到docker里面的,请问这样做的好处是什么呢,如果直接把代码放入docker 有什么好处吗 谢谢您
我本人是开发人员,会经常修改代码,放在外面,方便我直接修改,或者和git同步。
另外,我在本地开发,Mac OSX,也是类似这样操作的,跑VMWare,虚拟机里安装docker,docker跑node,node使用的源文件是我Mac OSX上共享给VMWare许积极的目录。
当然,我也不知道是不是就是最佳实践,目前使用还算顺手。
谢谢分享
嗯嗯,算是吧

要回复文章请先登录注册