前端工程化之持续集成

Posted by Eric Blog on September 27, 2020

前端工程化之 CI/CD

背景

前段时间在开发wad组件迭代的过程中,wad组件的sdkgais组件的sdk强依赖,需要经常执行开发—调试—发布的过程,命令行需要敲很多重复命令,感觉浪费了很多时间在这种机械重复工作上,是否可以通过自动化解放双手呢。之前有在文章里看到过DevOps,知道其是一种思想,实现开发到部署整个环境的自动化,刚好趁这个机会 研究一下Devops

DevOps 是一个完整的面向 IT 运维的工作流,以 IT 自动化以及持续集成(CI)、持续部署(CD)为基础,来优化程式开发、测试、系统运维等所有环节

DevOps一词的来自于 Development 和 Operations 的组合,突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠。

img

研究发现DevOps 包含的东西太多,涉及到 CI/CD,监控平台、日志、自动化测试等,本文仅仅针对 CI/CD 进行探索和阐述,也方便记录一些配置中踩过的坑。

Gitlab-CI

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。而GitLab-CI就是一套配合GitLab使用的持续集成系统。

环境配置

目前公司的 iris 仓库是支持持续集成的,官方也建议主要放一些公司开源的项目,而公司内部项目还是需要放到 sys 仓库,sys 仓库暂时不支持持续集成,配管有基于 Jenkins 完成了对应的自动打包工作。

Gitlab-CI的持续集成需要依赖于runner,runner有共享和私有两种,目前 iris 仓库的官方没有提供共享runner,所以需要我们自己搭建私有的runner。那就需要一台服务器。下面的流程都是基于 Windows10 server 服务器进行配置。

需要安装 git,node,gitlab-ci,git 和 node 都跟平常安装一样,这里就不讲了。

下载gitlab-runner[1],在 E 盘新建一个GitLab-Runner文件夹(随便哪个盘),把安装文件放在里面。

img

然后通过管理员权限进行安装

img

注册gitlab-runner,在注册之前需要取我们仓库的地址和项目 token(如果项目在 group 里面,直接注册 group 更加方便,这样其他组里面的项目就不用注册了)。

img

在 gitlab-runner 目录下,进行注册,在命令行输入.\gitlab-runner.exe register(当然可以配置环境变量,就可以直接使用 gitlab-runner register)

img

启动 gitlab-runner 服务,运行.\gitlab-runner.exe start

img

这样整个环境配置就完成了,我们去项目仓库里面查看,如果看到下图,就说明已经配置成功。

img

可以在页面中直接暂停和删除 runner。

Tips: 可以注册多个 runner,同样的步骤,config.toml文件里面记录了所有 runner,只需要重启 runner 即可运行所有 runner

配置文件

Gitlab-CI是通过.gitlab-ci.yml 文件来进行解析的,所以项目的根目录需要新增一个.gitlab-ci.yml 文件。

下面给一个示例,具体配置项查看配置项[2]

1
image: node:lateststages:  - install_deps  - build  - publish-master  - dev-masterinstall_deps:  stage: install_deps  only:    - master  script:  # script 就是你要执行的脚本了    - npm installbuild:  stage: build  only:    - master    - dev  script:    - npm run builddev-master:  stage: dev-master  only:    - dev  script: # 这里建议也是把 deploy.js 放到远端,好更新,我这里简单写下    - node deploy.js devpublish-master:  stage: publish-master  only:    - master  script: # 这里建议也是把 deploy.js 放到远端,好更新,我这里简单写下    - node deploy.js master

npm 包发布

在平常开发中,如果要发布npm包的话,就需要运行npm login 输入用户名、密码才能进行npm publish。但是持续集成是全部自动化,没有任何交互,所以要自动化发布必须解决这个登录问题。

npm login的本质就是 往当前用户的 .npmrc 写入你的登录信息,我们可以执行下 cat ~/.npmrc,看下内容

img

所以只需要把这串文本存储在项目根目录的.npmrc 文件就可以了。

Travis-CI

github 的项目要进行持续集成,有工具可以供选择,比如 Travis-CI、CircleCI、Jenkins 等等,选择 Travis-CI 是因为网上很多人推荐,下面就阐述一下步骤和踩过的坑。

注册 Travis

进入Travis 官网[3],用 GitHub 账号登录,这样就可以同步所有的项目,选择项目进行持续集成。

img

配置文件

Gitlab-CI一致都需要一个配置文件,方便 Travis 进行读取运行脚本命令,在项目根目录添加.travis.yml文件,下面给出一个示例,具体配置项参考配置项[4]

1
language: node_js #设置语言node_js: "10.16.3" #设置语言版本cache:  directories:    - node_modules #缓存依赖# S: Build Lifecycleinstall:  - npm iscript:  - npm run build#命令里面的变量都是在Travis CI里配置过的。after_script:  - git add .  - git commit -m ":construction_worker:- Build & Deploy by Travis CI"  - git push --force --quiet "https://${Travis_Token}@${GH_REF}" gh-pages:${D_BRANCH}# E: Build LifeCycle# 只有指定的分支提交时才会运行脚本branches:  only:    - master

部署服务器

这个地方跟Gitlab-CI不大一样的是,Gitlab-CI是不提供服务器,是通过gitlab-runner直接连接到我们自己的服务器,脚本直接在服务器中执行的,而Travis-CI自身就提供了服务器,脚本都是在其服务器上执行的,但我们要部署到我们自己的服务器。使用过 Linux 服务器的同学应该知道,服务器可以通过 ssh 进行登录,但得输入用户名和密码,这跟之前 npm 发布一样,我们需要规避掉命令行交互,才能实现持续集成。

下面有两种方式可以实现,分布是 ssh 免密登陆服务器和通过 sshpass 将密码提前设置。

ssh 免密登陆

root用户下创建新用户 travis

1
#新建用户useradd travis#修改密码(应该不是必要,但是万一以后需要用密码登陆呢),按照提示设置密码。passwd travis#为用户添加添加权限vim /etc/sudoers

找到#Allow root to run any commands anywhere 这一段注释,在下面新增一行:

1
travis  ALL=(ALL)   ALL

生成密钥对

  • 一定要切到 travis 用户
  • passphase 一定要为空
1
#切换至用户travis,注意后面的操作都在该用户下操作,不然从git上面拉下来的代码或者生成的文件拥有着将不是travis,会造成一些麻烦[root@VM_156_69_centos ~]# su travis[travis@VM_156_69_centos root]$ cd ~# 生成RSA密钥对,后面所有的直接以默认就行,passphase一定要为空[travis@VM_156_69_centos ~]$ ssh-keygen -t rsaGenerating public/private rsa key pair.Enter file in which to save the key (/home/travis/.ssh/id_rsa):Created directory '/home/travis/.ssh'.Enter passphrase (empty for no passphrase):Enter same passphrase again:Your identification has been saved in /home/travis/.ssh/id_rsa.Your public key has been saved in /home/travis/.ssh/id_rsa.pub.The key fingerprint is:8b:1f:5b:c5:b8:e2:09:21:0a:f8:6d:ef:5f:25:84:24 travis@VM_156_69_centosThe key's randomart image is:+--[ RSA 2048]----+|      E .        ||       o .       ||        . .      ||.        . o     ||o   . . S o +    || o o . o . =     ||  o o o + +      ||   . . + B       ||     .o.*        |+-----------------+

可以看到生成密钥对在用户家目录的.ssh 文件夹(/home/travis/.ssh)下面。 由于 Linux 权限的控制规则,文件的权限不是越大越好,所有需要设置合适的权限。这里需要把.ssh 目录设置为 700 权限,给.sshm=目录下面的文件设置为 600 权限

1
#设置.ssh目录为700[travis@VM_156_69_centos ~]$ chmod 700 ~/.ssh/#设置.ssh目录下的文件为600[travis@VM_156_69_centos ~]$ chmod 600 ~/.ssh/*#可以看到下面的所有目录和文件所用者都是travis这个用户[travis@VM_156_69_centos ~]$ ls -altotal 28drwx------  3 travis travis 4096 Mar  6 20:12 .drwxr-xr-x. 5 root   root   4096 Mar  6 20:03 ..drwx------  2 travis travis 4096 Mar  6 20:12 .ssh[travis@VM_156_69_centos ~]$ ls ~/.ssh/ -altotal 16drwx------ 2 travis travis 4096 Mar  6 20:12 .drwx------ 3 travis travis 4096 Mar  6 20:12 ..-rw------- 1 travis travis 1675 Mar  6 20:12 id_rsa-rw------- 1 travis travis  405 Mar  6 20:12 id_rsa.pub

将生成的公钥添加为受信列表(重点)

1
[travis@VM_156_69_centos ~]$ cd .ssh/#将公钥内容输出到authorized_keys中[travis@VM_156_69_centos .ssh]$ cat id_rsa.pub >> authorized_keys[travis@VM_156_69_centos .ssh]$ cat authorized_keys# authorized_keys文件内容类似这样ssh-rsa  *************centos

测试 SSH 登陆

1
#在.ssh目录下新增配置文件config[travis@VM_156_69_centos .ssh]$ vim config#添加下面代码段中的内容并保存#测试连接[travis@VM_156_69_centos .ssh]$ ssh testBad owner or permissions on /home/travis/.ssh/config#注意此时的测试是失败的,因为authorized_keys和config是我们后面添加的文件,文件权限并不是600[travis@VM_156_69_centos .ssh]$ ls -altotal 28drwx------ 2 travis travis 4096 Mar  6 20:40 .drwx------ 3 travis travis 4096 Mar  6 20:38 ..-rw-rw-r-- 1 travis travis  405 Mar  6 20:40 authorized_keys-rw-rw-r-- 1 travis travis   91 Mar  6 20:38 config-rw------- 1 travis travis 1675 Mar  6 20:12 id_rsa-rw------- 1 travis travis  405 Mar  6 20:12 id_rsa.pub#修改文件权限[travis@VM_156_69_centos .ssh]$ chmod 600 config[travis@VM_156_69_centos .ssh]$ chmod 600 authorized_keys#查看修改后的权限[travis@VM_156_69_centos .ssh]$ ls -altotal 28drwx------ 2 travis travis 4096 Mar  6 20:40 .drwx------ 3 travis travis 4096 Mar  6 20:38 ..-rw------- 1 travis travis  405 Mar  6 20:40 authorized_keys-rw------- 1 travis travis   91 Mar  6 20:38 config-rw------- 1 travis travis 1675 Mar  6 20:12 id_rsa-rw------- 1 travis travis  405 Mar  6 20:12 id_rsa.pub#重新执行测试[travis@VM_156_69_centos .ssh]$ ssh testThe authenticity of host '139.199.90.74 (139.199.90.74)' can't be established.ECDSA key fingerprint is 41:39:50:e1:e7:c2:f5:19:86:dc:70:e5:91:42:bb:56.Are you sure you want to continue connecting (yes/no)? yesWarning: Permanently added '139.199.90.74' (ECDSA) to the list of known hosts.Last login: Tue Mar  6 20:43:32 2018 from 139.199.90.74#测试成功,生成了一个known_hosts文件,以后再登陆时就不需要在输入yes确认了,你可以再做一次测试[travis@VM_156_69_centos ~]$ ls .ssh/authorized_keys  config  id_rsa  id_rsa.pub  known_hosts

config 文件内容 :

1
Host testHostName 99.99.99.99(你的服务器ip)#登陆的用户名User travisIdentitiesOnly yes#登陆使用的密钥IdentityFile ~/.ssh/id_rsa

安装 travis 命令行工具

需要通过 gem 来安装 travis,所以在这之前需要安装一下 ruby,这个在这边就不啰嗦了,自行百度。

1
#在travis下面提示没有权限,我切到root用户去安装[travis@VM_156_69_centos ~]$ gem install travisFetching: multipart-post-2.0.0.gem (100%)ERROR:  While executing gem ... (Gem::FilePermissionError)    You dont have write permissions for the /usr/local/rvm/gems/ruby-2.4.1 directory.#安装travis[root@VM_156_69_centos ~]# gem install travisSuccessfully installed travis-1.8.8Parsing documentation for travis-1.8.8Done installing documentation for travis after 1 seconds1 gem installed#切回travis用户,执行travis命令有以下输出说明安装成功[travis@VM_156_69_centos root]$ travisShell completion not installed. Would you like to install it now? |y| yUsage: travis COMMAND ...

添加加密的私钥至代码仓库

切换至 travis 用户,在家目录下拉取代码,进入代码目录。一定要切换至 travis 用户,要不然将没有权限操作这些文件,导致代码都不能提交

执行下面命令生成加密的私钥文件

1
#首先用GitHub账户登陆travis[travis@VM_156_69_centos blog-front]$ travis loginWe need your GitHub login to identify you.This information will not be sent to Travis CI, only to api.github.com.The password will not be displayed.Try running with --github-token or --auto if you dont want to enter your password anyway.Username: lzq4047Password for lzq4047: ******Successfully logged in as lzq4047!#登陆成功后解密私钥,--add参数会把加密的私钥解密命令插入到.travis.yml,Travis解密时要用到的[travis@VM_156_69_centos blog-front]$ travis encrypt-file ~/.ssh/id_rsa --addDetected repository as lzq4047/blog-front, is this correct? |yes| yesencrypting /home/travis/.ssh/id_rsa for lzq4047/blog-frontstoring result as id_rsa.enc#由于我之前生成过,所有这里提示是否覆盖DANGER ZONE: Override existing id_rsa.enc? |no| yesstoring secure env variables for decryptionMake sure to add id_rsa.enc to the git repository.Make sure not to add /home/travis/.ssh/id_rsa to the git repository.Commit all changes to your .travis.yml.#可以看到已经生成了加密后的私钥id_rsa.enc[travis@VM_156_69_centos blog-front]$ ls -altotal 464drwxrwxr-x 7 travis travis   4096 Mar  6 21:24 .drwx------ 7 travis travis   4096 Mar  6 21:24 .....-rw-rw-r-- 1 travis travis   1680 Mar  6 21:27 id_rsa.enc...-rw-rw-r-- 1 travis travis   1286 Mar  6 21:27 .travis.yml#.travis.yml中也自动添加了解密命令[travis@VM_156_69_centos blog-front]$ cat .travis.ymllanguage: node_jsnode_js:- '8'branchs:  only:  - masterbefore_install:- openssl aes-256-cbc -K $encrypted_****_key -iv $encrypted_****_iv  -in id_rsa.enc -out ~/.ssh/id_rsa -d

解释下解密命令中 -in-out 参数:

  • -in 参数指定待解密的文件,位于仓库的根目录(Travis 执行任务时会先把代码拉到 Travis 自己的服务器上,并进入仓库更目录)
  • -out 参数指定解密后的密钥存放在 Travis 服务器的~/.ssh/id_rsa,如果你的后面需要的话可以取这个路径,我看到网上有的 SSH 登陆方式用到了这个文件

配置 after_success 钩子

前面的工作都是为这一步做准备,SSH 免密登陆服务器执行脚本。

在.travis.yml 中添加一些配置,主要是 after_success 钩子配置。修改之后的配置如下:

1
language: node_jsnode_js:- '8'branchs:  only:  - masterinstall:- npm installscript:- npm run buildenv:  global:    secure: *********addons:  ssh_known_hosts:  - 99.99.99.99 #受信主机,你的Linux服务器ipbefore_install:- openssl aes-256-cbc -K $encrypted_****_key -iv $encrypted_****_iv  -in id_rsa.enc -out ~/.ssh/id_rsa -dafter_success:- chmod 600 ~/.ssh/id_rsa   #还是Linux文件权限问题- ssh blog@139.199.90.74 -o StrictHostKeyChecking=no 'cd ~/blog-front && git pull && npm install && npm run build'   #使用ssh连接服务器

-o stricthostkeychecking=no 这个参数是必须的,禁用 SSH 远程主机的公钥检查。当我们用 ssh 第一次登陆服务器,通常会提示是否需要导入服务器公钥,看起来会是这样

The authenticity of host ‘192.168.0.110 (192.168.0.110)’ can’t be established. RSA key fingerprint is a3:ca:ad:95:a1:45:d2:57:3a:e9:e7:75:a8:4c:1f:9f. Are you sure you want to continue connecting (yes/no)?

我们说了,是没有交互界面的,如果遇到这样的选择,我们的自动化脚本将会不能正常执行。

sshpass

另外一种是直接使用 sshpass,这是一个简单的命令行工具, ssh 的时候会提示输入密码,这个工具的作用是可以将密码预先设置,从而使脚本可以自动化。

sshpass -p woshimima -p参数是指定密码为woshimima 。但是 travis 项目是公开的(除非你花钱买私有的),任何人可以看到公开项目的日志,这样的脚本会暴露服务器 ssh 密码。好在 travis 提供了解决方案,我们可以将 ssh 密码,用户名,端口等加密。 我们能够告诉 travis 在使用 sshpass 时从环境变量读取变量而非命令行。利用travis command line tool[5]增加加密变量。假设你已经在本机安装了travis command line tool

1
travis encrypt DEPLOY_USER=<csun-username>travis encrypt DEPLOY_PASS=<csun-password>

在有travis.yml 文件的项目中运行以上命令时,travis 会自动注入global节点到travis.yml 文件中。如果不是,你也可以从命令行得到值后 copy 加密后的值进travis.yml文件

这样我们修改一下 after_success 钩子脚本即可。

1
language: node_jsnode_js:- '8'branchs:  only:  - masterinstall:- npm installscript:- npm run buildenv:  global:    secure: *********addons:  ssh_known_hosts:  - 99.99.99.99 #受信主机,你的Linux服务器ipafter_success:- export SSHPASS=$DEPLOY_PASS- sshpass -e ssh blog@139.199.90.74 -o StrictHostKeyChecking=no 'cd ~/blog-front && git pull && npm install && npm run build'

总结

devops 是一种思想,实现开发、运维、测试等整个环境的闭环,实现全部自动化,解放人力,同时可以及时反馈现场问题,可以结合敏捷开发加快开发效率同时保持比较高的质量。而 CI/CD 是 devops 其中比较重要的一环,之前没有实践过 CI/CD,在实践过程中还是踩了一些坑的,特别是在 ssh 免密登陆过程的实现。

参考资料

[1] gitlab-runner: https://docs.gitlab.com/runner/install/windows.html [2] 配置项: https://docs.gitlab.com/ee/ci/yaml/ [3] Travis 官网: www.travis-ci.org/ [4] 配置项: https://docs.travis-ci.com/user/job-lifecycle/ [5] travis command line tool: https://github.com/travis-ci/travis.rb