如何利用Docker快速发布一个Nginx+Express+Redis项目,然后使用Jenkins进行简单的持续集成发布工作。
Docker在2013年首次进入业界的视线。官网定义:
“Docker是一个为开发者和运维管理员搭建的开放平台软件,可以在这个平台上创建、管理和运行生产应用。Docker Hub是一个云端服务,可以用它共享应用或自动化工作流。Docker能够从组件快速开发应用,并且可以轻松地创建开发环境、测试环境和生产环境。”
Docker容器完全使用沙箱机制,独立于硬件、语言、框架、打包系统,相互之间不会有任何接口,几乎没有任何性能开销,便可以很容易地在机器和数据中心中运行。最重要的是,它不依赖于任何语言、框架或系统。
由于容器体积小,可以快速部署,所以有助于开发者进行超大规模的部署。相对于虚拟机,开发者可以使用更少的存储空间、内存和CPU,因为其在性能方面基本上不需要系统开销。
Nginx是轻量级网页服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。
正向代理:比如我们想访问一些国外的网站,可是由于某些原因无法正常打开该网站或者打开缓慢,这时我们通过香港的HTTP代理就可以正常访问一些国外网站了,在此,香港的这个HTTP代理就是正向代理。
反向代理,正好相反,比如我们有一个对外的API服务api.nodeInAction.com,初期我们启动一台服务器、一个Node.js进程就可以完成负载,但是随着后期访问量的加大,可以在Nginx后端添加多个服务器或启动多个进程来分担访问压力。
把它放在Node.js的应用,有几个好处:
1、静态文件性能:Node.js的静态文件处理性能受限于它的单线程异步I/O模型。使用Nginx处理静态文件的性能基本上是纯Node.js的2倍以上。
2、反向代理规则:使用Nginx的配置文件就可以简单实现session的问题。
3、扩展性:最典型的就是加入Lua语言的扩展。
4、稳定性和转发性能:
5、安全性:
6、运维管理:
一个好习惯就是,在生产环境中,永远把Nginx放置在Node.js的前端,对性能、安全性和将来的扩展都有益处。
$ sudo docker pull centos:7
○ → sudo docker images centos
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7 ff426288ea90 2 weeks ago 207MB
1、获取镜像: $ sudo docker pull NAME[:TAG]
2、启动Container盒子: $ sudo docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
$ sudo docker run -t -i centos /bin/bash
3、查看镜像列表,列出本地的所有images: $ sudo docker images [OPTIONS] [NAME]
4、查看容器列表,可看到我们创建过的所有Container
$ sudo docker ps [OPTIONS] ==> sudo docker ps -a
5、删除镜像,从本地删除一个已经下载的镜像
$ sudo docker rmi IMAGE [ IMAGE ... ]
6、移除一个或多个容器实例
$ sudo docker rm [OPTIONS] CONTAINER [CONTAINER... ]
7、停止一个正在运行的容器
$ sudo docker kill [OPTIONS] CONTAINER [CONTAINER...]
$ sudo docker kill 026e
8、重启一个正在运行的容器
$ sudo docker restart [OPTIONS] CONTAINER [CONTAINER ... ]
9、启动一个已经停止的容器
$ sudo docker start [OPTIONS] CONTAINER [CONTAINER ... ]
$ sudo docker start 026e
Image和Container的关系,Docker会根据当前的Image创建一个新的Container,Container是一个程序运行的沙箱,它们互相独立,但都运行有Image创建的执行环境之上。
把本机的/etc目录挂载到Container里的/opt/etc下面,并且打印Container的/opt/et目录。
$ sudo docker run --rm=true -i -t --name=ls-volume -v /etc/:/opt/etc/ centos ls /opt/etc
下载一个Redis数据库的镜像:
$ sudo docker pull redis:latest
启动Redis镜像的Container,开启redis-server持久化服务。
$ sudo docker run --name redis-server -d redis redis-server --appendonly yes
启动一个Redis镜像的Container作为客户端,连接刚才启动的redis-server
$ sudo docker run --rm=true -it --link redis-server:redis redis /bin/bash
root@819902062c6b:/data# env
root@819902062c6b:/data# redis-cli -h "$REDIS_PORT_6379_TCP_ADDR" -p "$REDIS_PORT_6379_TCP_PORT" #连接redis
172.17.0.2:6379> set a 1
OK
172.17.0.2:6379> get a
"1"
在Container里安装一个SSH Server是非常诱人的,因为这样我们就可以直接连接到Container,并且进入它的内部,我们可以让本地的SSH客户端连Container。
但在这么做之前需要考虑以下几个问题:
若不使用SSH,我们该如何做以下事情呢?
(1)备份数据。数据必须是一个volumn,这样可以启动另外一个Container,并且通过--volumes-from来共享应用的Container数据,数据备份就不会影响到我们的应用Container.。
(2)检查日志。重新启动一个日志分析的Container来处理日志和检查日志。
(3)重启应用服务。只需重启Container即可。
(4)修改配置文件。如果需要在应用存活期间改变自己的配置,例如增加一个新的虚拟站点,那么还是需要使用volumn来处理,这样,所有的应用Container都可以快速地临时变更配置。
(5)调试应用。这可能是唯一需要进入Container的场景了,这样你就需要用到nsenter软件。用来进入现有的命名空间。尽管这个Container没有安装SSH Server或者其他类似软件。
项目地址:https://github.com/jpetazzo/nsenter
命令安装nsenter。sudo docker run -v /usr/local/bin:/target jpetazzo/nsenter
需要进入的Container的pid。获取pid号
$ sudo docker inspect --format {{.State.Pid}} 258c96d111f3
15926
进入Container
$ sudo nsenter --target 15926 --mount --uts --ipc --net --pid
开始简单制作一个Node.js包含Express.js环境的镜像,通过pm2来启动Web应用,然后发布到Docker云上;使用Redis数据库来暂存用户的访问次数;Nginx作为反向代理。
$ sudo docker pull redis
$ sudo docker pull node
$ sudo docker images
在本地创建一个部署Node.js应用的目录,然后写上package.json
$ sudo mkdir /var/node/docker_node -p
制作node镜像,并安装pm2软件(Node.js进程管理软件,可以方便地重启进程和查看Node.js日志)
$ sudo docker run -i -t node /bin/bash
$ npm install pm2 -g
$ pm2 -v
2.9.3
#考虑国内的网络,再装下cnmp更靠谱些
$ npm install cnpm -g --registry=https://registry.npm.taobao.org
$ exit
把镜像push到云上,必须以<用户名>/<镜像名>这样的方式提交,比如doublespout/node_pm2
$ sudo docker login
$ sudo docker commit 7a3e doublespout/node_pm2
# 查看Images列表
$ sudo docker images <镜像名>
# 把镜像提交到云上
$ sudo docker push doublespout/node_pm2
# 删除本地doublespout/node_pm2,试着从云上下载这个镜像
$ sudo docker rmi doublespout/node_pm2
$ sudo docker images doublespout/node_pm2
$ sudo docker pull doublespout/node_pm2
通过Redis镜像启动一个Redis的Container
$ sudo docker run --name redis-server -d redis redis-server --appendonly yes
准备编写Node.js代码来实现这个计数访问应用的功能。
/var/node/docker_node/package.json
{
"name" : "docker_node",
"version" : "0.0.1",
"main" : "app.js",
"dependencies" : {
"express" : "4.10.2",
"redis": "0.12.1"
},
"engines" : {
"node" : ">=0.10.0"
}
}
/var/node/docker_node/app.js,通过Redis记录访问次数
var express = require('express');
var redis = require('redis');
var app = express();
// 从环境变量里读取Redis服务器的ip地址
var redisHost = process.env['REDIS_PORT_6379_TCP_ADDR'];
var redisPort = process.env['REDIS_PORT_6379_TCP_PORT'];
var redisClient = redis.createClient(redisPort, redisHost);
app.get('/', function(req, res) {
console.log('get request');
redisClient.get('access_count', function(err, countNum) {
if (err) {
return res.send('get access count error');
}
if (!countNum) {
countNum = 1;
} else {
countNum = parseInt(countNum) + 1;
}
redisClient.set('access_count', countNum, function(err) {
if (err) {
return res.send('set access count error');
}
res.send(countNum.toString());
});
});
});
app.listen(8000);
启动一个Container把依赖包装一下
$ sudo docker run --rm -i -t -v /var/node/docker_node:/var/node/docker_node -w /var/node/docker_node/ doublespout/node_pm2 cnpm install
-w参数表示命令执行的当前工作目录。
如果出现EACCESS的权限错误。su -c "setenforce 0"
启动一个运行这个程序的Container,要求这个Container有端口映射、文件挂载,并同时加载Redis的那个Container,命令如下:
挂载pm2的日志输出
$ mkdir /var/log/pm2
使用pm2启动app应用
$ sudo docker run -d --name "nodeCountAccess" -p 8000:8000 -v /var/node/docker_node:/var/node/docker_node -v /var/log/pm2:/root/.pm2/logs/ --link redis-server:redis -w /var/node/docker_node/ doublespout/node_pm2 pm2 --no-daemon start app.js
打开 http://127.0.0.1:8000/
Nginx反向代理:由于使用Docker的Container,所以它的IP地址是动态变化的,那么配置写起来会比较困难,这里暂不使用Docker容器来管理Nginx,而是直接编译安装Nginx。
使用Niginx的分支版本openresty来做反向代理,比Nginx内置了ngx-lua模块,让Nginx具有逻辑处理能力。
$ wget https://openresty.org/download/openresty-1.11.2.2.tar.gz
$ ./configure --prefix=/opt/openresty --with-pcre-jit --with-ipv6 --without-http_redis2_module --with-http_iconv_module -j2
$ make && make install
$ ln -s /opt/openresty/nginx/sbin/nginx /usr/sbin/
修改配置文件在/opt/openresty/nginx/conf/nginx.conf,此配置文件是精简版,不要用于生产环境
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server_names_hash_bucket_size 64;
access_log off;
sendfile on;
keepalive_timeout 65;
server {
listen 3001;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_redirect default;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
}
}
$ sudo nginx 启动openrestry
打开http://127.0.0.1:3001/
如果遇到在Container里无法解析域名的情况,就需要手动增加DNS服务器:
DOCKER_OPTS=" --dns 8.8.8.8"
service docker restart
Jeknins是一款有Java开发的开源软件项目,旨在提供一个开放易用的软件平台,使持续集成变成可能,它的前身是大名鼎鼎的Hundson。是收费的商用版本,Jenkins是它的一个免费开源的分支。
持续集成:(IBM团队的定义)“随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题,持续集成正是针对这类问题的一种软件开发实践。它倡导团队开发成员必须经常集成他们的工作,甚至每天都可能发生多次集成。而每次的集成都是通过自动化的构建来验证的,包括自动编译、发布和测试,从而尽快发现集成错误,让团队能够更快地开发内聚的软件。”
它的核心价值:
简而言之,利用Jenkins持续集成Node.js项目之后,就不用每次都登录到服务器,执行pm2 restart xxx 等。只需单击“立即构建”按钮,就可以自动从Git仓库获取代码,然后远程部署到目标服务器,执行一些安装依赖包和测试的命令,最后启动应用。
$ sudo docker pull jenkins
把Jenkins的文件存储地址挂载到主机上,万一Jenkins的服务器重装或者迁移,我们都可以很方便地把之前的项目配置保留,否则就只能进入Container的文件系统里去复制了。另外,Jenkins会搭建在内网的服务器上,而非生产服务器,如果外网能直接访问,那么可能会造成一定的风险。
# 创建配置文件目录
$ sudo mkdir /var/jenkins_home
$ sudo docker run -d --name myjenkins -p 49001:8080 -v /var/jenkins_home:/var/jenkins_home jenkins
建议去系统管理--->管理用户栏目中创建几个用户和权限,方便多人协同操作。