Docker + jenkins自动化部署Node.js应用

上一篇大概介绍了JWT的用法,实现了一个简单的登录注册以及邮箱验证。而这一篇呢就负责把我们的项目部署到自己的服务器上去。本文需要有一定Docker基础。理解Image,Container及其一些基础用法。

准备工作

首先我们得有一台服务器。我这里用的是阿里云ECS,华北节点。具体的购买操作的话百度会有教程。购买完成以后你会有一个公网的ip,以及一个通过ssh登录服务器的密码。

关于Docker的话,如果不了解的可以先去看文档。其实引用知乎大神的话来说,Docker的Container(容器)就像轮船上的集装箱。集装箱各自装着各自的货物,互不影响。比如一个Redis服务,一个Mongodb服务,都可以放到一个单独的Container(集装箱)里面。而这些容器,又依赖一个执行环境。这个执行环境就是Docker所说的Image(镜像)。每一个Container管理着自己的生命周期。

下面来说说Jenkins。Jenkins是一款由Java开发的开源软件项目,主要是用来持续集成的。相当于就是预先写好脚本,调试成功之后,下一次如果再需要部署的时候就会自动执行上一次存储的脚本,无需再修改。简单来说,我们用Jenkins持续集成Node.js项目之后,就不需要每一次都登录到服务器,把本地的文件传到服务器,在执行pm2 restart xxx或者node xxx等工作。只需要在你的Jenkins项目中点击立即构建就可以完成,非常方便。

安装docker

首先我们需要先安装docker,因为Jenkins是依赖docker的。linux平台CentOS 7的用户可以直接运行如下命令即可安装最新版的Docker。

$ sudo yum install docker

对于CentOS 6.5的用户需要先获取epel源并导入。

$ wget -c http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

$ rpm -ivh epel-release-6-8.noarch.rpm

$ rpm -import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

然后通过yum安装Docker

$ yum install docker-io --enablerepo=epel

下面启动Docker,并设置Docker为开机启动

$ sudo service docker start

$ sudo chkconfig docker on

以上是linux系统的安装步骤,如果你的系统是mac,需要从Docker官网下载。下载完成后执行安装。

安装Jenkins

Docker安装完成之后,我们需要通过docker来安装和启动Jenkins,执行如下命令安装Jenkins

docker pull jenkins:latest

首先我们需要下载jenkins镜像。很多人会想,既然jenkins是java开发的,那一定需要配置java的环境。答案肯定是否定的,因为jenkins镜像里面已经配置好java的环境,我们无需关心。镜像下载完毕后,我们需要把Jenkins的文件存储地址挂载到我们的主机上面。为了方便以后项目配置的迁移等操作。

注意:我们目前所做的一系列操作都是在自己内网机器上进行的操作,不要放到外网服务器中。

下面我们来配置Jenkins的工作目录,用于挂载Docker Container里面Jenkins工作目录。

$ mkdir /var/jenkins_home

$ sudo docker run -d --name myjenkins -p 49001:8080 -v /var/jenkins_home:/var/jenkins_home jenkins

首先我们在本机的var目录下创建jenkins_home目录,然后我们启动一个name为myjenkins的Docker。-d 的意思是程序在后台执行。-p 是把本机的49001端口映射到Container的8080端口,8080端口是Jenkins启动后默认监听的端口。-v 表示把Container中/var/jenkins_home(后者)挂载到本机/var/jenkins_home目录。最后的jenkins是该Docker启动依赖的镜像。

如果一切顺利的话我们就可以看到jenkins的欢迎页面了

Jenkins首页

当然我们这里是已经登录以后的。由于版本不同,新版本可能在进入到主页之前需要你预先安装插件。可以安装完毕后再进入。

开始部署

接下来我们就要对Jenkins进行一些简单的配置。我们需要实现一个这样的需求:把我们写好的代码传到github,然后Jenkins从github上拉取代码传到服务器,再启动一个包含pm2和node环境的Container将代码跑起来。

首先我们来安装几个插件。进入 系统管理>管理插件>可选插件,右上角过滤里面输入Git,如果报403的话在列表中直接找Git Plugin(The Plugin integrates GIT with Jenkins)插件;然后安装Publish Over SSH(Send build artifacts over SSH)。

Jenkins可选插件列表

安装完毕之后我们需要重启Jenkins。如果自动重启失败可以在浏览器键入下面地址。

http://192.168.1.116:49001/restart

重启完成之后,我们就需要对插件进行设置了。进入 系统管理>系统设置。首先为了连接到我们的远程服务器,我们需要添加服务器配置。找到你得Publish over SSH插件栏

系统设置

如图所示,我们需要填入远端服务器的IP地址、SSH登录密码、用户名以及我们所连接的远程目录。如果你是通过SSH key连接远程服务器的,在Path to key一栏需要填入key在你本机的路径。可以点击下面的Test Configuration按钮来测试服务器是否可以连接成功。

然后我们来创建一个项目,这里我们已经构建好了一个名为node_access_count的账户。

创建项目

这里选择构建一个自由风格的软件项目。

项目主页

点击ok进入到项目主页,点击左边菜单栏的配置按钮,在配置页我们找到"源码管理"选项,在这里我们要来配置github源码。


源码管理add github帐号

首先我们在Repostiories一栏填入代码的地址,在Credentials一栏点击add添加github帐号,如第二张图所示。最后在添加需要拉取的分支,这样拉取代码这一块就配置完毕。

然后把页面下拉,拉到构建一栏。这就是自动化部署的核心所在。构建表示我们要如何向生产服务器发布一个应用。简单来说就是配置一系列自动化脚本。我们完成构建需要做以下事情:

1.从git或者svn拉取最新代码。

2.本地打包,排除.git文件。

3.把代码包通过SSH发布到远程服务器。

4.停止原先远程服务器正在运行的服务,删除原来的代码,解压缩心代码。

5.通过新package.json安装依赖包,然后启动服务

首先我们点击增加构建步骤,选择Execute shell。由于之前我们已经配置好从github自动拉取源码,现在我们无需配置,只需要对拉取的文件进行打包。

首先我们从github拉取的代码会存放在/var/jenkins_home/workspace/node_access_count目录下,node_access_count是你项目的名字。所以我们要对这个路径下的文件进行压缩。这里我们用到的是tar命令,对tar不了解的先看这篇文章

tar -czvf /tmp/node_access_count.tar.gz -C /var/jenkins_home/workspace/node_access_count . --exclude="*.git"

mv /tmp/node_access_count.tar.gz /var/jenkins_home/workspace/node_access_count/

-C的意思是把tar的工作目录更改到/var/jenkins_home/workspace/node_access_count下,也就是对/var/jenkins_home/workspace/node_access_count的文件进行压缩;--exclude就是忽略所有以.git结尾的文件。然后我们把压缩完成的文件解压到/var/jenkins_home/workspace/node_access_count/目录下。

上述步骤完毕以后,我们需要把打包完成的代码发送到远程服务器上。这时继续点击添加构建步骤,选择Send files or execute commands over SSH。然后选择我们一开始添加的SSH服务器。

配置SSH文件发送

在Source files中填入文件名称,也就是刚才我们压缩的文件。这里的默认文件路径为/var/jenkins_home/workspace/node_access_count/,上一步我们已经将文件放到该目录下。

Remote directory意思是文件存放在远程服务器上的什么位置,这里我们填入var/,文件将会被保存在服务端/var/目录下。

接下来就要写发布的脚本了,要注意这里的脚本都是运行在服务端的。由于是用Docker,所以我们每次发布的时候都需要先删除一次之前的Container。

docker rm -f nodeCountAccess

然后我们创建目录

mkdir /var/node

mkdir /var/node/docker_node

mkdir /var/log/pm2

我们会把解压后的源码放到mkdir /var/node/docker_node,然后我们会挂载Container中的pm2 log输出文件到服务器的/var/log/pm2。首先解压。

tar -xvf /var/node_access_count.tar.gz -C /var/node/docker_node

然后我们起一个docker来安装node程序需要的依赖包

docker run --rm -v /var/node/docker_node:/var/node/docker_node -w /var/node/docker_node/ wangsidi/node_pm2 npm install

首先我们将服务器的/var/node/docker_node挂载到Container的/var/node/docker_node。-w的意思是指定命令的执行目录为Container的/var/node/docker_node/。我们用wangsidi/node_pm2(如果没有会自动下载,这是一个包含node和pm2的镜像)镜像来执行npm install命令。

因为我们的程序涉及mongodb服务,所以我们需要起一个mongodb的Container来link到最终的服务器Container。

sudo docker run --name some-mongo -d mongo

这里mongo镜像如果不存在系统会自动pull。mongodb的Container起起来之后,会默认监听Container的27017端口。我们可以先起一个Container连接some-mongo,查看里面的环境变量。打开终端执行下面命令。(这条命令不需要写在Jenkins里面)

ssh root@112.126.78.86

sudo docker run --rm=true -it --link some-mongo:mongo mongo /bin/bash

先用ssh命令进入到你得远程服务器。root是用户名,@后面是IP。进入之后我们启动一个用完即删的docker连接some-mongo并进入Container内部的bash。输入"env"。

Container内部的环境变量

我们可以看到,MONGO_PORT_27017_TCP_ADDR和MONGO_PORT_27017_TCP_PORT分别表示mongodb服务的IP和端口,这是我们之后连接数据库需要用到的。

配置mongodb的url

回到Jenkins,现在mongodb的Container已经存在,所以接下来我们只需要启动一个docker运行代码并连接到some-mongo这个Container,我们的服务就启动成功。下面来操作。

docker run -d --name "nodeCountAccess" -p 80:3000 -v /var/node/docker_node:/var/node/docker_node -v /var/log/pm2:/root/.pm2/logs/ --link some-mongo:mongo -w /var/node/docker_node/ wangsidi/node_pm2 pm2 start --no-daemon app.js

这里docker的名字叫nodeCountAccess(和一开始删除的名字相同)。-p 把主机80端口映射到Container的3000端口(80是访问ip地址或域名默认访问的端口;3000端口是我的node程序监听的端口,大家可以根据自己的程序进行修改)。要注意pm2启动的时候会在/root/.pm2/logs/下生成日志文件,我们把它挂载到主机的/var/log/pm2,也就是我们一开始创建的那个目录。start --no-daemon表明pm2不以守护进程的方式启动,否则docker将不会被docker ps -a命令看到。

最后我们删除一开是传上来的/var/node_access_count.tar.gz(其实应该是可以被覆盖的)。

rm -rf /var/node_access_count.tar.gz

把涉及到的命令粘贴到Exec command里面。至此项目配置完毕。

构建脚本

点击保存回到项目主页,然后点击立即构建

构建过程

点击你正在构建的项目,可以进入到详情,点击Console Output可以查看log输出。如果有报错什么的,可以在这里查看原因。

构建console output

构建中小球的颜色会有三种。蓝色表示成功;红色表示出错;黄色表示虽然构建成功,但中间出现错误。红色和黄色都表明构建存在问题,我们需要重新检查和修改脚本。

如果一切顺利,你的应用已经部署成功,打开浏览器访问你的ip+端口试试。童鞋们可以试着访问我构建的项目http://112.126.78.86

查错

如果你构建成功,却没有访问到也别急。这很有可能说明是你程序内部出了错误。由于我们把Container内部的pm2 log挂载到了服务器,我们可以进入的服务器的/var/log/pm2中进行查看。

app-error-0.log表示抛出异常的错误输出,app-out-0.log表示你程序中的一些console。你可以打开app-error-0.log看看里面有没有错误日志。绝大多数的错误都会被记录在里面。

end

以上就是我Docker+Jenkins实现自动化部署的全部过程,如果你构建成功的话,Docekr就算基本入门了。如果想要深入学习的话可以研究Docker的源码和官方文档。另外此次我没有使用Dockerfile来配置,相对来说Dockerfile更加简洁明了。关于Dockerfile的内容我会在后面的文章中给出。

这是我们的公众号,喜欢的可以关注交流。

如果你也喜欢全栈,请关注React全栈开发吧!

推荐阅读更多精彩内容