使用Travis-ci自动SSH部署代码

之前文章谈代码质量控制的时候说过,Travis-ci等持续集成工具可以帮助我们在代码提交之后进行单元测试之类的工作,那么有没有什么办法在单元测试之后让代码自动部署到我们需要的机器上呢?答案是完全可以。今天以我自己的博客为例和大家谈谈我的自动部署之路。

1. 从手动部署到自动部署

在整个代码部署的道路上,我经历了手动部署shell半自动部署再到现在的Travis自动部署。

1.1 石器时代 - 手动部署

很早很早之前,我采用完全手动的方式部署代码:

这个过程中我需要,先在本地编写并调试代码然后上传到Git服务器上,再手动SSH登录机器通过git pull下载最新的代码,最后在服务器上进行编译和重启服务。

这种原始的模式往往最傻瓜有效,但意味着需要手动做一些列的工作,每次部署都会变得极其麻烦。

1.2 青铜时代 - shell半自动部署

后来我想到了把登录机器、编译、部署的事情交给shell自动执行,于是我就写了个shell脚本,主要的内容是:本地编译完成之后shell脚本自动的把代码打包,然后上传到服务器上,部署并重启服务。

使用了shell之后大大的解放了双手,每次决定上线的时候只需要执行sh deploy.sh然后坐等结果就好了。但是使用shell也有他的弊端:每次上线都得手动执行部署命令,然后等待代码自动的打包上传以及部署,如果遇到网络抖动或者打包后的文件过大存在着失败的可能性,就有可能还需要再来一次。说是自动化,其实还是需要人工去观察和干预的,于是Mofei就想了,能不能有一种更高效的方式进行部署呢?于是就有了现在的部署方式。

1.3 现代 - Travis自动部署

通过配合Travis的使用,我们可以实现如下的效果:

  • 测试部署:每次push代码到dev分支,Travis会自动进行单元测试,然后自动的通过SSH将代码部署到对应的开发机器上并重启服务,以保持开发机上始终是最新的版本。
  • 正式部署:决定上线的时候可以将代码push代码到deploy分支上,Travis会自动将代码部署到正式的开发环境。

当然了这个过程可以根据自己的团队进行适当的调整以决定何时进行部署。

2.调教Travis

步入正题,要完成自动部署,首先Travis要能监听Git的变化,然后Travis还需要有权限登录到我们的SSH服务器进行部署:

  1. 配置Travis,让Travis能监听Git的某个分支。
  2. Git某个分支提交之后,Travis能自动发现提交并进行编译。
  3. Travis将编译后的产物通过SSH部署到给我们指定的机器。

2.1 初始化Travis

开始之前,如果你还没有Travis账号,请使用Github登录Traivs,并关联你的项目。

Travis是通过项目中的.travis.yml文件来配置任务的,首先我们需要在项目的根目录建立.travis.yml文件,我们以该配置为例。

新建.travis.yml文件,写上如下代码:

language: node_js
node_js:
- 8

其中language指得是项目运行语言,因为这里是node.js项目,所以写的是node_js,如果你使用的是其他的语言可以参考官方的文档。之后的-8指的是使用v8版本的node.js.当然你也可以指定多个版本来分别执行代码,如:

node_js:
- stable
- '6'
- '4'

添加好.travis.yml文件之后,每次上传到Git中的代码都会自动进行Travis的构建,如果通过了可以在对应的commit后面看到一个绿色的勾,如果失败了会是一个红色的叉。

因为我们此次不会介绍单元测试的内容,所以可以在package.json文件中将script中的test脚本改为exit 0以跳过单元测试,但是实际的项目中还是建议各位认真的对待Test。

"scripts": {
    "test": "exit 0"
}

Travis配置完成之后我们看一下如何授权Travis访问我们的服务器。

2.2 Travis添加SSH密钥

通常我们是通过ssh命令加上用户名和密码访问服务器的,虽然理论上我们也可以在travis的命令中写上诸如ssh mofei@zhuwenlong.com -p abc的脚本,但是这样的代码如果提交到了公开的仓库中会有很大的泄露服务器密码的风险,所以我们需要一个别人无法窃取密码或者密钥的方式让Travis登录我们的服务器。

通常的免密登录是基于SSH信任关系的,那么如果我们能把密钥以加密的形式保持在Travis的服务器中,Travis就能登录我们的服务器了。这里我们可以使用Travis的文件加密功能,把我们的密钥进行加密保存。

在这个过程中,我们的密钥首先会被被Travis加密,解密的密钥被存储在Travis中,就是说只有Travis可以进行解密。所以我们可以大胆的把这个加密后的文件上传到github中,不用担心其他人盗用我们的密钥。

既然我们想要使用Travis加密文件,第一件事情就是在本地安装Travis。

2.2.1 本地安装Travis

我们可以执行以下代码安装Travis

sudo gem install travis

由于Travis使用的是ruby,如果遇到众所周知的原因无发现下载的话,可以将ruby更换成国内的源,具体可以参考这里

gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

然后再次运行安装Travis的命令 sudo gem install travis

安装好Travis之后,我们需要在命令行中登录Travis

travis login --pro

一路输入你的github账号和密码很快就完成了。

2.2.2 生成并加密SSH密钥

万事俱备只欠东风,我们现在只要生成SSH密钥,然后添加信任关系,并用Travis加密保存即可。

在命令行中执行如下脚本:

# 在当前目录生成密钥
ssh-keygen -t rsa -b 4096 -C 'build@travis-ci.org' -f ./deploy_rsa
# 使用Travis加密
travis encrypt-file deploy_rsa --add
# 添加信任关系
ssh-copy-id -i deploy_rsa.pub <ssh-user>@<deploy-host>
# 删除敏感文件
rm -f deploy_rsa deploy_rsa.pub
# 将修改添加到git中
git add deploy_rsa.enc .travis.yml

我们详细来看一下代码(如果你理解所有的命令,可以略过这一小段):

  • 在当前目录生成密钥

ssh-keygen -t rsa -b 4096 -C 'build@travis-ci.org' -f ./deploy_rsa

首先,我们使用ssh的命令在当前目录中生成了一个ssh密钥,这段代码执行完成之后,会在目录中生成2个文件,私钥deploy_rsa和公钥deploy_rsa.pub,前者是用来免密登录服务器时候使用的,后者服务器用来鉴定私钥的有效性的。

  • 使用Travis加密

travis encrypt-file deploy_rsa --add

因为Travis只需要使用到私钥,所以我们这里讲私钥进行加密保存,这句话执行完成之后,你会看到在.travis.yml文件中被自动加了下面的代码(--add),同时文件夹中也发现一个加密后的文件deploy_rsa.enc

before_install:
- openssl aes-256-cbc -K $encrypted_137f45644142_key -iv $encrypted_137f45644142_iv
  -in deploy_rsa.enc -out deploy_rsa -d

这几行被自动添加的代码的意思是,在install之前执行解开deploy_rsa.enc文件的命令并放置到deploy_rsa以供使用,其中这里的$encrypted_137f45644142_key$encrypted_137f45644142_iv是解开这个文件的两个变量被存储在了Travis的服务器上。

  • 添加信任关系

ssh-copy-id -i deploy_rsa.pub <ssh-user>@<deploy-host>

这句话的意思是向目标服务器(<ssh-user>@<deploy-host>这里的user和host需要替换成自己服务器的用户名和地址)添加公钥,添加成功之后,所有用该公钥对应的私钥访问服务器都会直接被认证通过。也就是说如果Travis保持了私钥的话,就可以免密的通过ssh登录我们的服务器了。

  • 删除敏感文件并将修改添加到git中

rm -f deploy_rsa deploy_rsa.pubgit add deploy_rsa.enc .travis.yml

私钥deploy_rsa和公钥deploy_rsa.pub已经完成了他们的使命,我们可以把它删除以免被其他人恶意使用,并把生成的加密文件deploy_rsa.enc和修改后的.travis.yml添加到git中。

2.3 Travis部署脚本

所有的一切都准备好之后,我们就可以修改.travis.yml文件让travis来进行部署了。

首先,我们需要在部署之前解密私钥,并使其生效,所以我们添加如下代码:

before_deploy:
- openssl aes-256-cbc -K $encrypted_137f45644142_key -iv $encrypted_137f45644142_iv
  -in deploy_rsa.enc -out /tmp/deploy_rsa -d
- eval "$(ssh-agent -s)"
- chmod 600 /tmp/deploy_rsa
- ssh-add /tmp/deploy_rsa

before_deploy字段标明了Travis在部署之前需要执行的命令,其中第一行是解密ssh文件,后面的3行是使ssh密钥生效。

然后,我们添加deploy字段,这个字段是指代码在运行部署时执行的内容:

deploy:
  provider: script
  script: bash ./deploy.sh
  skip_cleanup: true
  on:
    branch: deploy

这里我们指定的运行方式是脚本script,运行的内容是bash ./deploy.sh,生效的分支是deploy分支,至于./deploy.sh就是我们具体的部署代码,比如我的博客的部署代码文件中就是一些shell脚本:先编译,然后把编译后的文件打包传到机器上,再登录到机器上把文件放到该放的位置中,最后重启服务,具体的代码就不贴出来了,可以点击上面的链接查看。

最后,我们需要添加我们部署机器的host信息,以让Travis可以正常访问服务器:

addons:
  ssh_known_hosts: 47.99.143.70

这里的IP指的是部署服务器的IP。

最终的.travis.yml如下:

language: node_js
node_js:
- 8
addons:
  ssh_known_hosts: 47.99.143.70
before_deploy:
- openssl aes-256-cbc -K $encrypted_137f45644142_key -iv $encrypted_137f45644142_iv
  -in deploy_rsa.enc -out /tmp/deploy_rsa -d
- eval "$(ssh-agent -s)"
- chmod 600 /tmp/deploy_rsa
- ssh-add /tmp/deploy_rsa
deploy:
  provider: script
  script: bash ./deploy.sh
  skip_cleanup: true
  on:
    branch: deploy

完成这些之后,每次在push代码到deploy分支上的时候,就可以愉快的去玩耍了,Travis会自动的进行编辑并部署,如果想要查看部署进度,我们可以打开commit点击后面的编译状态图标去查看具体的Travis进度。

Write a response...
Mofei Zhu
publish