docker是一种虚拟化技术,可以在内核层隔离资源。因此,对于上层应用,利用docker技术可以实现类似于虚拟机的沙箱环境。这大大简化了应用部署,使运维人员无需陷入无休止繁琐的依赖环境和系统配置;另一方面,容器技术还可以充分利用硬件资源,实现资源共享。本文将使用docker技术部署一个简单的nodejs应用,包括一个简单的前端网关nginx、redis服务器和业务服务器。同时使用dockerfile配置具体镜像,使用docker-compose进行容器编排,解决依赖、网络等问题。Docker基础本文中机器已经默认安装了docker环境,即可以使用docker和docker-compose服务。如果本地没有安装,请参考:安装docker和docker-compose,参考安装DockerCompose。关于dockercompose技术,可以查看官方文档。DockerComposedockersourcedefaultDocker使用官方镜像,国内用户下载镜像较慢。为了更好的体验,建议切换源。OSX系统可以添加~/.docker/daemon.json文件,{"registry-mirrors":["http://f1361db2.m.daocloud.io/"]},镜像源地址可以替换,然后重启码头工人只是服务。Linux系统也可以通过修改/etc/docker/daemon.josn文件来替换源。简单docker操作的源切换后,可以尝试简单的容器操作。首先运行一个简单的容器:dockerrun-itnode:8-slimnoderun命令,根据某个版本的node镜像运行容器,同时执行“node”命令,进入node命令行交互模式。dockerrun-dnode:8-slimnode执行-d选项,让容器作为守护进程运行,并返回容器的hash值。根据hash值,我们可以通过命令行进入正在运行的容器查看相关状态:dockerexec-ithashcodebashhashcode可以通过dockerps-l找到对应容器的hashcode。关于镜像的选择和版本的确定,可以访问官方https://hub.docker.com/搜索,根据结果找到官方镜像使用,当然也可以根据下载量和星数。对于镜像标签,根据业务需求判断是否需要完整版系统。比如nodejs镜像只需要node基础环境,不需要其他系统预装命令,所以选择node:-slim版本。Dockerfile从源头下载的镜像大多不符合实际使用需求,需要自定义镜像。镜像自定义可以通过运行容器安装环境,最后作为镜像提交:dockerrun-itnode:8-slimbashroot@ff05391b4cf8:/#echohelloworld>/home/textroot@ff05391b4cf8:/#exitdockercommitff05391b4cf8node-hello然后运行这个只是镜像。另一种镜像定制可以通过Dockerfile的形式来完成。Dockerfile是容器运行的配置文件。每次执行一个命令,都会生成一个镜像,直到所有环境搭建完成。Dockerfile可以执行自定义镜像的命令,如“FROM、COPY、ADD、ENV、EXPOSE、RUN、CMD”等,dockerfile的具体配置可以参考相关文档。Dockerfile完成后,构建镜像:dockerbuild-tnode:custom:v1。镜像构建成功后,就可以运行容器了。docker-compose关于docker-compose,会在后面的例子中进行说明。示例:搭建一个nodejs应用本文所有代码已经开源到githubdocker-compose.yml。在docker-compose.yml中配置相关服务节点,在每个服务节点中配置镜像、网络、环境、磁盘映射等相关元信息。你也可以指定一个特定的Dockerfile来构建镜像。版本:'3'服务:nginx:图像:nginx:最新端口:-80:80重新启动:总是卷:-./nginx/conf.d:/etc/nginx/conf.d-/tmp/logs:/var/log/nginxredis-server:image:redis:latestports:-6479:6379restart:alwaysapp:build:./volumes:-./:/usr/local/apprestart:alwaysworking_dir:/usr/local/appports:-8090:8090command:nodeserver/server.jsdepends_on:-redis-serverlinks:-redis-server:rdredisserver首先搭建一个单节点缓存服务,使用的是最新版本的redis镜像官方,无需构建。version:'3'services:redis-server:image:redis:latestports:-6479:6379restart:always具体版本信息,可以参考Compose和Docker兼容矩阵,找到与docker匹配的版本格式引擎。在services下创建一个名为redis-server的服务,该服务采用官方最新的redis镜像,通过宿主机的6479端口对外提供服务。并设置自动重启功能。此时就可以通过6479端口在宿主机上使用缓存服务了。web应用使用node.js的koa和koa-router快速搭建一个web服务器。本节创建一个8090端口的服务器,同时提供两个功能:1.单个key缓存的简单查询2.多个key缓存的管道查询docker-compose.ymlservices:app:build:./volumes:-./:/usr/local/apprestart:alwaysworking_dir:/usr/local/appports:-8090:8090command:nodeserver/server.jsdepends_on:-redis-serverlinks:-redis-server:rd这里创建一个app服务,使用当前目录下的Dockerfile构建的镜像,并通过volume配置磁盘映射,将当前目录下的所有文件映射到容器的/usr/local/app,并使其成为运行时目录;同时映射宿主机的8090端口,最后执行nodeserver/server.js命令运行服务器。通过depends_on设置app服务的依赖,等待redis-server服务启动后再启动app服务;通过链接建立容器之间的网络连接,在应用服务中,可以通过别名rd访问redis-server。DockerfileFROMnode:8-slimCOPY.//usr/local/appWORKDIR/usr/local/appRUNnpmi--registry=https://registry.npm.taobao.orgENVNODE_ENVdevEXPOSE8090指定的Dockerfile初始化npm。网络服务器源代码constKoa=require('koa');constRouter=require('koa-router');constredis=require('redis');const{promisify}=require('util');letapp=newKoa();letrouter=newRouter();letredisClient=createRedisClient({//ip是docker-compose.yml中配置的redis-server别名rd,可以查看dns所在容器的dns配置ip应用程序位于:'rd',端口:6379,前缀:'',db:1,密码:null});functioncreateRedisClient({port,ip,prefix,db}){letclient=redis.createClient(port,ip,{prefix,db,no_ready_check:true});client.on('reconnecting',(err)=>{console.warn(`redis客户端重新连接,延迟${err.delay}ms并尝试${err.attempt}`);});client.on('error',function(err){console.error('Rediserror!',err);});client.on('ready',function(){console.info(`redis初始化完成,准备就绪:${ip}:${port}/${db}`);});returnclient;}functionexecReturnPromise(cmd,args){返回新舞会ise((res,rej)=>{redisClient.send_command(cmd,args,(e,reply)=>{if(e){rej(e);}else{res(reply);}});});}functionbatchReturnPromise(){returnnewPromise((res,rej)=>{letb=redisClient.batch();b.exec=promisify(b.exec);res(b);});}路由器。get('/',async(ctx,next)=>{awaitexecReturnPromise('set',['testkey','helloworld']);让ret=awaitexecReturnPromise('get',['testkey']);ctx.body={status:'ok',result:ret,};});router.get('/batch',async(ctx,next)=>{awaitexecReturnPromise('set',['testkey','helloworld,batch!']);letbatch=awaitbatchReturnPromise();for(leti=0;i<10;i++){batch.get('testkey');}letret=awaitbatch.exec();ctx.body={status:'ok',result:ret,};});app.use(router.routes()).use(router.allowedMethods()).listen(8090);需要注意的是,在web服务所在的容器中,通过别名rd访问缓存服务,此时运行命令docker-composeup后,可以通过http://127.0.0.1:8090/http://127.0.0.1:8090/batch访问这两个缓存服务。Forwarding目前可以通过宿主机的8090端口访问服务。为了以后web服务的可扩展性,需要在前端增加一个转发层。实例中使用nginx转发:services:nginx:image:nginx:latestports:-80:80restart:alwaysvolumes:-./nginx/conf.d:/etc/nginx/conf.d-/tmp/logs:/var/log/nginx采用最新版本的nginx官方镜像,并向宿主机暴露80端口。通过在本地配置nginx的抓包规则文件,映射到容器的nginx配置目录,实现快速高效的测试。运行展开默认单节点下,直接运行docker-composeup-d运行服务。如果服务节点需要扩容,可以通过docker-composeup-d--scaleapp=3扩容到3台web服务器,需要修改nginx转发规则:upstreamapp_server{#搭建服务器集群,负载均衡关键指令serverdocker-web-examples_app_1:8090;#设置具体服务器,serverdocker-web-examples_app_2:8090;服务器docker-web-examples_app_3:8090;}server{listen80;字符集utf-8;位置/{proxy_passhttp://app_server;proxy_set_header主机$host:$server_port;proxy_set_headerX-Forwarded-Host$server_name;proxy_set_headerX-Real-IP$remote_addr;proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;}}app_server里面的每一个服务器名都是docker-web-examples_app_1,格式是"${path}_${service}_${number}",即第一部分是docker-compose所在的目录名.yml所在,如果在根目录下,就是应用名;第二部分是扩展服务名称;这三部分是扩展序列号。通过在nginx配置的log_format中设置upstream_addr变量,可以观察到负载均衡已经生效。http{log_formatmain'$remote_addr:$upstream_addr-$remote_user[$time_local]"$request"''$status$body_bytes_sent"$http_referer"''"$http_user_agent""$http_x_forwarded_for"';}参考docker官方文档docker-compose.yml配置文件编写Dockerfile详解实践