在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.js
在production
情况下记录日志到文件中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许积极的目录。
当然,我也不知道是不是就是最佳实践,目前使用还算顺手。
另外,我在本地开发,Mac OSX,也是类似这样操作的,跑VMWare,虚拟机里安装docker,docker跑node,node使用的源文件是我Mac OSX上共享给VMWare许积极的目录。
当然,我也不知道是不是就是最佳实践,目前使用还算顺手。
谢谢分享