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

jopen 10年前

本文是十七蝉同学撰写的基础实战类博客,作者通过代码的形式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/