Build Node.Js web server in Docker containers: nodejs+pm2+mongodb+redis

来源:互联网 发布:淘宝里怎么打开链接 编辑:程序博客网 时间:2024/06/05 17:18

Build Node.Js web server in Docker containers: nodejs+pm2+mongodb+redis

环境

  • 系统环境 centos 6.5 64bit
  • docker v1.7.1

下载使用镜像

[root@dss ~]# docker search node # 搜素镜像NAME                      DESCRIPTION                                     STARS     OFFICIAL   AUTOMATEDnode                      Node.js is a JavaScript-based platform for...   2462      [OK]       strongloop/node           StrongLoop, Node.js, and tools.                 30                   [OK]bitnami/node              Bitnami Node.js Docker Image                    16                   [OK]siomiz/node-opencv        _/node + node-opencv                            8                    [OK]dahlb/alpine-node         small node for gitlab ci runner                 7                    [OK]tatsushid/tinycore-node   A small but a fully functional Node.js run...   6                    [OK]cusspvz/node               Super small Node.js container (~20MB)...      4                    [OK]azukiapp/node             Docker image to run Node.js by Azuki - htt...   4                    [OK]tutum/node                Run a Tutum node inside a container             3                    [OK]anigeo/node-forever       Daily build node.js with forever                3                    [OK]redjack/node              Node + Nave                                     1                    [OK]xataz/node                very light node image                           1                    [OK]joxit/node                Slim node docker with some utils for dev        1                    [OK]centralping/node          Bare bones CentOS 7 NodeJS container.           1                    [OK]urbanmassage/node         Some handy (read, better) docker node images    1                    [OK]tectoro/node-compass      Node JS minimal version with compass and b...   1                    [OK]starefossen/ruby-node     Docker Image with Ruby and Node.js installed    1                    [OK]atomiq/node               Use as base image for new Node.js images.       1                    [OK]robbertkl/node            Docker container running Node.js                0                    [OK]lynxtp/node               https://github.com/lynxtp/docker-node           0                    [OK]codexsystems/node         Node.js for Development and Production          0                    [OK]instructure/node          Instructure node images                         0                    [OK]stylisted/node            Stylisted's base Docker node image with gr...   0                    [OK]maxird/node               Node                                            0                    [OK]c4tech/node               NodeJS images, aimed at generated single-p...   0                    [OK]

安装 Node.Js 官方镜像 [root@dss ~]# yum pull node

查看已安装镜像

[root@dss workspace]# docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZEnode                latest              47abb70784d6        8 days ago          660.6 MB

基于 Node.Js 镜像创建服务镜像

新建目录 src, 在src目录下新建 package.json:

{    "name": "docker-node-test",    "private": true,    "version": "0.0.1",    "description": "Node.js test app on node docker image",    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",    "dependencies": {        "express": "^4.13.4"                                    }}

在src目录下新建index.js:

 const express = require('express'); // Creates an Express application let app = express(); // Assigns settings app.set('port', 8080); // Routes HTTP GET requests  app.get('/', (req, res) => {     res.send('Hello world --- from nodejs docker image\n'); });                                                           // Binds and listens app.listen(app.get('port'), () => {     console.info("Listen port %s for app", app.get("port"));

在src同级目录下新建文件 Dockerfile:

From node:6.3ADD src/ /srcRUN cd /src; npm installEXPOSE  8080CMD ["node", "/src/index.js"]

创建镜像 docker build --tag="node/app-test" ./, docker build --help 查看详细参数

创建成功会提示 Successfully built 2199b2aa9cd1

查看创建的镜像

[root@dss test]# docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZEnode/app-test       latest              2199b2aa9cd1        7 seconds ago       663.6 MBnode                6.3                 47abb70784d6        8 days ago          660.6 MBnode                latest              47abb70784d6        8 days ago          660.6 MB

运行刚刚创建的镜像, docker run --help 查看详细参数:

[root@dss test]# docker run -d -p 8081:8080 node/app-test 873ac198368799f9bf56262e097f55e66241f47cedd336349236b43e9001539d

查看运行中的容器,是否存在:

[root@dss test]# docker psCONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES873ac1983687        node/app-test       "node /src/index.js"   53 seconds ago      Up 52 seconds       0.0.0.0:8081->8080/tcp   desperate_swartz 

测试 Node.Js 服务:

[root@dss ~]# curl localhost:8081 -iHTTP/1.1 200 OKX-Powered-By: ExpressContent-Type: text/html; charset=utf-8Content-Length: 41ETag: W/"29-onjHxbOtTbPH0+vlfe2YtA"Date: Fri, 15 Jul 2016 10:49:54 GMTConnection: keep-aliveHello world --- from nodejs docker image

基于 Node.Js 镜像使用 pm2

使用 pm2

创建 Dockerfile:

From node:6.3RUN npm install pm2 -g --registry=https://registry.npm.taobao.orgRUN mkdir -p /usr/src/node-appWORKDIR /usr/src/node-appCOPY src/. /usr/src/node-app/RUN npm install --registry=https://registry.npm.taobao.orgEXPOSE  8080CMD ["npm", "start"]                                             

先看看目录结构吧

[root@dss workspace]# tree test/test/├── Dockerfile└── src    ├── index.js    ├── package.json    └── pm2.json

package.json:

{                                                               "name": "docker-node-test",                                 "private": true,                                            "version": "0.0.1",                                         "description": "Node.js test app on node docker image",     "author": "Shaoshuai Dong <dongsoso@hotmail.com>",          "scripts": {                                                    "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"         },                                                          "dependencies": {                                               "express": "^4.13.4"                                    }                                                       }                                                           

index.js

const express = require('express');// Creates an Express applicationlet app = express();// Assigns settingsapp.set('port', 8080);// Routes HTTP GET requests                                       app.get('/', (req, res) => {    res.send('Hello world --- from nodejs docker image\n');});// Binds and listensapp.listen(app.get('port'), () => {    console.info("Listen port %s for app", app.get("port"));});

pm2.json:

{                                             "apps": [        {            "name": "app-test",            "script"      : "index.js",            "args"        : [],            "watch"       : false,            "merge_logs"  : true,            "max_memory_restart": "100M",            "env": {                "NODE_ENV": "local"            }        }    ]}

创建镜像 docker build --tag="node/app-test" ./

查看创建的镜像 docker images:

[root@dss test]# docker imagesREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZEnode/app-test       latest              1a3325c28c8c        14 minutes ago      678 MBnode                latest              47abb70784d6        10 days ago         660.6 MBnode                6.3                 47abb70784d6        10 days ago         660.6 MB

运行 docker run -d -p 8081:8080 node/app-test

查看运行中的容器

[root@dss test]# docker psCONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES9e49f7c5270a        node/app-test       "pm2 start index.js    7 minutes ago       Up 7 minutes        0.0.0.0:8081->8080/tcp   gloomy_sammet

测试

[root@dss test]# curl -i localhost:8081HTTP/1.1 200 OKX-Powered-By: ExpressContent-Type: text/html; charset=utf-8Content-Length: 41ETag: W/"29-onjHxbOtTbPH0+vlfe2YtA"Date: Sun, 17 Jul 2016 09:12:48 GMTConnection: keep-aliveHello world --- from nodejs docker image

在容器内运行命令(docker exec --help 查看更多用法)

[root@dss test]# docker exec -i 9e49f7c5270a pm2 list┌──────────┬────┬──────┬─────┬────────┬─────────┬────────┬─────────────┬──────────┐│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory      │ watching │├──────────┼────┼──────┼─────┼────────┼─────────┼────────┼─────────────┼──────────┤│ app-test │ 0fork17  │ online │ 09m23.730 MB   │ disabled │└──────────┴────┴──────┴─────┴────────┴─────────┴────────┴─────────────┴──────────┘

将 pm2、log4js 日志记录到宿主机文件

这里介绍将 pm2 日志和程序日志记录在宿主机文件之中, 目标是:

  • pm2 out logs => /data/logs/app-test-out.log
  • pm2 error logs => /data/logs/app-test-err.log
  • logs by log4js => /data/logs/hello.log
./├── Dockerfile└── src    ├── index.js    ├── package.json    └── pm2.json

Dockerfile

From node:6.3RUN npm install pm2 -g --registry=https://registry.npm.taobao.orgRUN mkdir -p /usr/src/node-appWORKDIR /usr/src/node-appCOPY src/. /usr/src/node-app/RUN npm install --registry=https://registry.npm.taobao.orgEXPOSE  8080CMD ["npm", "start"]

index.js

const express = require('express');const log4js = require('log4js');// Creates an Express applicationlet app = express();// Init loggerlog4js.configure({    appenders: [{        type: 'dateFile',        pattern: '-yyyyMMddhh.log',        filename: '/data/logs/hello.log',        category: 'hello'    }],    levels: {        action: 'INFO',                                                    alarm_api: 'INFO'    }});global.logger = log4js.getLogger('hello');// Assigns settingsapp.set('port', 8080);// Routes HTTP GET requests app.get('/', (req, res) => {    console.info('On request: hello world');    global.logger.info(req.method, req.path);    res.send('Hello world --- from nodejs docker image\n');});// Binds and listensapp.listen(app.get('port'), () => {    console.info('Listen port %s for app', app.get('port'));});

package.json

{    "name": "docker-node-test",    "private": true,    "version": "0.0.1",    "description": "Node.js test app on node docker image",    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",    "scripts": {        "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"    },    "dependencies": {        "express": "^4.13.4",        "log4js": "^0.6.33"                                            }}

pm2.json

{    "apps": [        {            "name": "app-test",            "script": "index.js",            "args": [],            "watch": false,            "merge_logs": true,            "error_file": "/data/logs/app-test-err.log",            "out_file": "/data/logs/app-test-out.log",               "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",            "max_memory_restart": "100M",            "env": {                "NODE_ENV": "local"                                      }        }    ]}

创建镜像 docker build --tag="node/app-test:0.1" ./

运行容器 docker run -p 8081:8080 -v /data/logs:/data/logs --name hello node/app-test:0.1

使用 -v 参数在容器上挂载宿主机上的指定目录

查看容器和测试:

[root@dss test]# docker psCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES63eb2a3aea8b        node/app-test:0.1   "npm start"         17 seconds ago      Up 16 seconds       0.0.0.0:8081->8080/tcp   hello[root@dss test]# curl localhost:8081Hello world --- from nodejs docker image[root@dss test]# 

在宿主机上查看日志

[root@dss test]# ls /data/logs/app-test-err.log app-test-out.log hello.log[root@dss test]# cat /data/logs/app-test-out.log 2016-07-18 05:56:56.406: Listen port 8080 for app2016-07-18 05:57:21.289: On request: hello world[root@dss test]# cat /data/logs/hello.log [2016-07-18 05:57:21.290] [INFO] hello - GET /

命令 docker inspect hello 可以看到容器的 volume 配置

... "Volumes": {     "/data/logs": "/data/logs" }, "VolumesRW": {     "/data/logs": true },...

Data volumes 有如下特性:

  • 当容器被创建时 volumes 会被初始化
  • Data volumes 可以在容器间共享复用
  • 对 data volume 的修改直接生效
  • 更新镜像时, 原来 data volume 的修改不会被影响
  • 即使容器被删除, 对应的 data volumes 依然会存在

使用 Volume 可以将容器与容器产生的数据分离,容器产生的数据可以持久化。

Docker volumes 使用 -v 指定和 在 Dockerfile 指定 VOLUME 的区别:

  • -v /data/logs:/data/logs: 将宿主机的 /data/logs 目录挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容器共享该目录,二者对该目录下的修改相互受影响。
  • Dockerfile 指定 VOLUME /data/logs: 在宿主机创建一个目录(默认是 /var/lib/docker/volumes/)并挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容> 器共享该目录,二者对该目录下的修改相互受影响。
  • -v /data/logs: 同上, 在宿主机创建一个目录(默认是 /var/lib/docker/volumes/)并挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容器共享该目录,二者对该目录下的修改相互受影响。

Docker volumes 默认是 read-write 模式,也可以设置为 read-only 模式。

应用实践 nodejs&mongodb&redis

目标

  • container: nodejs
    • pm2
    • logs on host
  • container: mongodb
  • container: redis

基于 centos 镜像创建 mongodb 镜像

新建 Dockerfile

# Mongodb image from centos:6# run -v /data/db:/data/db -p 27017:27017From centos:6MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com># install mongodb v3.2COPY mongodb.repo /etc/yum.repos.d/RUN yum install -y mongodb-org# volumeVOLUME ["/data/db"]# expose portEXPOSE 27017# commandCMD ["mongod"]                                      

mongodb.repo

[MongoDB]name=MongoDB Repositorybaseurl=http://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/gpgcheck=0enabled=1                                                                      

创建镜像 docker build --tag="centos/mongo:0.1" ./
运行容器 docker run -d -p 27017:27017 -v /data/db:/data/db --name mongo centos/mongo:0.1

查看容器运行状态

[root@dss mongodb]# docker psCONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                              NAMESc9ddd14992e6        centos/mongo:0.1    "mongod"               5 seconds ago       Up 4 seconds        0.0.0.0:27017->27017/tcp           mongo 

测试远程连接 mongodb

[dongshaoshuai~/test] ]$mongo --port 27017 --host x.x.x.x^[[AMongoDB shell version: 3.0.6connecting to: x.x.x.x:27017/test

也可以通过 Robomongo 远程连接(如果mongodb 版本 >= v3,Robomongo 的版本需 > v0.8.5)

基于 centos 镜像创建 redis 镜像

Dockerfile

From centos:6MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com># install redis v3.2.1RUN yum install -y gcc-c++RUN yum install -y tclRUN yum install -y wgetRUN wget http://download.redis.io/releases/redis-3.2.1.tar.gzRUN tar -xzvf redis-3.2.1.tar.gzRUN mv redis-3.2.1 /usr/local/redisRUN cd /usr/local/redis; make; make installCOPY redis.conf /etc/redis.confEXPOSE 6379CMD ["/usr/local/bin/redis-server", "/etc/redis.conf"]

redis.conf: wget http://download.redis.io/redis-stable/redis.confprotected-mode yes 改为 protected-mode no

创建镜像 docker build --tag="centos/redis:0.1" ./
运行容器 docker run -d -p 6379:6379 --name redis centos/redis:0.1

查看运行容器

[root@dss redis]# docker psCONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                      NAMES5aa2aa34764e        centos/redis:0.1    "/usr/local/bin/redi   6 seconds ago       Up 3 seconds        0.0.0.0:6379->6379/tcp     redisc9ddd14992e6        centos/mongo:0.1    "mongod"               2 hours ago         Up 2 hours          0.0.0.0:27017->27017/tcp   mongo

基于 centos 镜像创建 nodejs 服务镜像

目录结构

├── app│   ├── index.js│   ├── package.json│   └── pm2.json└── Dockerfile

Dockerfile

# run -v /data/logs:/data/logs --link mongo:mongo                          From centos:6MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com># install nodejs v6RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -RUN yum -y install nodejs# install pm2RUN npm install pm2 -g --registry=https://registry.npm.taobao.org# make dirRUN mkdir -p /usr/src/myapp# envsENV NODE_ENV=local# copy filesCOPY app/. /usr/src/myapp/# work dirWORKDIR /usr/src/myapp# install dependenciesRUN npm install --registry=https://registry.npm.taobao.org# expose portEXPOSE  8080# cmdCMD ["npm", "start"]

package.json

{    "name": "docker-node-test",    "private": true,    "version": "0.0.1",    "description": "Node.js test app on node docker image",    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",    "scripts": {        "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"    },    "dependencies": {        "express": "^4.13.4",        "log4js": "^0.6.33",                                                 "mongoose": "^4.0.8",        "express-session": "^1.13.0",        "connect-redis": "^3.1.0"    }}

pm2.json

{    "apps": [        {            "name": "app",            "script": "index.js",            "args": [],            "watch": false,            "merge_logs": true,            "error_file": "/data/logs/app-err.log",                  "out_file": "/data/logs/app-out.log",            "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",            "max_memory_restart": "100M",            "env": {                "NODE_ENV": "local"            }        }    ]}

一个简单的服务,仅作为演示 index.js

const express = require('express');                                                  const log4js = require('log4js');const mongoose = require('mongoose');const cookieParser = require("cookie-parser");const session = require('express-session');const redisStore = require('connect-redis')(session);// Creates an Express applicationlet app = express();// NODE_ENVconst ENV = app.get('env');// Init loggerlog4js.configure({    appenders: [{        type: 'dateFile',        pattern: '-yyyyMMddhh.log',        filename: '/data/logs/app.log',        category: 'app'    }],    levels: {        app: 'INFO',    }});global.logger = log4js.getLogger('app');// Assigns settingsapp.set('port', 8080);// Middlewaresapp.use(cookieParser('tadf@a213!sdfa6'));app.use(session({    secret: 'tadf@a213!sdfa6',    resave: false,    saveUninitialized: false,    store: new redisStore({        logErrors: true,        // host: 'redis', // Can be solved by docker-compose        host: process.env.REDIS_PORT_6379_TCP_ADDR,        port: 6379,        ttl: 60 // expiration in 60s    })}));// MongoDB connection: 'mongo' resolves to a docker containerlet mongoUrl = 'mongodb://mongo:27017/test';mongoose.connect(mongoUrl);let Monkey = mongoose.model('Monkey', { name: String });/** * Routes HTTP GET requests  */app.all('*', (req, res, next) => {    global.logger.info(req.method, req.path);    if (req.path === '/login') {        next();        return;    }    if (!req.session || !req.session.isLoggedIn) {        // TODO: redirect to login page        res.send('Please login first');        return;    }    next();});app.get('/login', (req, res) => {    let userName = 'admin';    let password = 'admin';    if (req.query.name === userName && req.query.password === password) {        req.session.isLoggedIn = true;        res.json({            RetCode: 0        });        return;    }    res.json({        RetCode: 5,        ErrMsg: 'Username or password wrong'    });});app.get('/hello', (req, res) => {    console.info('On request: hello world');    res.send('Hello world --- from nodejs docker image\n'); });app.get('/mongo/insert', (req, res) => {    let newData = { name: req.query.name };    let newMonkey = new Monkey(newData);    newMonkey.save(err => {        if (err) {            global.logger.error('Insert data failed: ', err, newData);             res.json({                RetCode: 100,                ErrMsg: 'Insert data failed'            });            return;        }        global.logger.info('Insert data success:', newData);        res.json({            RetCode: 0        });    });});app.get('/mongo/find', (req, res) => {    Monkey.find((err, monkeys) => {        if (err) {            global.logger.error('Find data failed: ', err);             res.json({                RetCode: 200,                ErrMsg: 'Find data failed'            });            return;        }        global.logger.info('Find data success:', monkeys);        res.json({            RetCode: 0,            Data: monkeys        });    });});// Binds and listensapp.listen(app.get('port'), () => {    console.info('Listen port %s for app', app.get('port'));    console.info('NODE_ENV', ENV);});                                                             

创建镜像 docker build --tag="centos/node-app:0.1" ./

运行容器 docker run -d -p 8181:8080 -v /data/logs:/data/logs --link mongo:mongo --link redis:redis --name app centos/node-app:0.1

查看服务状态

[root@dss app]# docker exec app pm2 list┌──────────┬────┬──────┬─────┬────────┬─────────┬────────┬─────────────┬──────────┐│ App nameid │ mode │ pid │ status │ restart │ uptime │ memory      │ watching │├──────────┼────┼──────┼─────┼────────┼─────────┼────────┼─────────────┼──────────┤│ app      │ 0  │ fork │ 25  │ online │ 021s    │ 47.879 MB   │ disabled │└──────────┴────┴──────┴─────┴────────┴─────────┴────────┴─────────────┴──────────┘ Use `pm2 show <id|name>` to get more details about an app

测试

未登录, 在浏览器访问 yourhost:8181/hello

Please log in first

登录, yourhost:8181/login?name=admin&password=admin

{    "RetCode": 0}

登陆后,测试 yourhost:8181/hello

Hello world --- from nodejs docker image

测试 yourhost:8181/mongo/find

{"RetCode":0,"Data":[{"_id":"578ded8321be5919003c0dd8","name":"mmmm","__v":0},{"_id":"578df6f638a6a11b008a9bd0","name":"mmmmmm","__v":0}]}

查看日志

[root@dss logs]# cat /data/logs/app.log [2016-07-20 02:31:40.097] [INFO] app - GET /hello[2016-07-20 02:31:45.088] [INFO] app - GET /login[2016-07-20 02:32:12.375] [INFO] app - GET /hello[2016-07-20 02:32:16.476] [INFO] app - GET /mongo/find[2016-07-20 02:32:16.477] [INFO] app - Find data success: [ { _id: 578ded8321be5919003c0dd8, name: 'mmmm', __v: 0 },  { _id: 578df6f638a6a11b008a9bd0, name: 'mmmmmm', __v: 0 } ]

其他

修改镜像、容器存储目录

docker 镜像、容器默认存储在 /var/lib/docker,对于云主机通常系统盘不大,最好存储在数据盘。

通过 --graph 参数可以指定存储位置。修改 /etc/sysconfig/docker 文件:

# 数据盘other_args="--graph=/data/docker"

停止 docker 服务 service docker stop

copy 数据至数据盘 cp -r /var/lib/docker /data/docker/

移除、备份原数据 mv /var/lib/docker /data/bak/docker.bak

重新启动 docker 服务 service docker start

测试镜像和容器是否正常 docker images, docker ps -a

原文

0 0
原创粉丝点击