在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):打印到標準輸出
這需要做兩件事: - 通過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}))
}</pre>
這樣,當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' }); }</pre>
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</pre>
比上面啟動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'); });</pre>
執行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</pre>
源代碼見這里:https://github.com/MarshalW/ProtoWebApp/tree/m5
本文收發于我的個人博客:http://blog.shiqichan.com/Depl ... cker/