前端工程化之travis ci

1,564 阅读6分钟

大家在平时的工作中,应该或多或少都听说过 CI/CD 吧,CI 全称是 Continuous Integration(持续集成),CD 全称则是 Continuous Delivery(持续交付)和 Continuous Deployment(持续部署)。

名词解释

持续集成

持续集成强调开发人员提交了新代码之后,立刻进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。

简单来说,就是每次提交代码的时候,都进行构建、测试等

持续交付

持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」(production-like environments)中。比如,我们完成单元测试后,可以把代码部署到连接数据库的 Staging 环境中更多的测试。如果代码没有问题,可以继续手动部署到生产环境中。

简单来说,就是代码通过测试后,将其部署到测试环境

持续部署

持续部署则是在持续交付的基础上,把部署到生产环境的过程自动化。

简单来说,就是代码通过测试后,将其部署到生产环境

总结:严格来说,持续部署依赖于持续交付,持续交付依赖于持续集成。但是这毕竟只是理论,在实际运用中,可以不用纠结于规范,只实现其中一部分。

参考自: www.zhihu.com/question/23…

travis ci 教程

官方文档: docs.travis-ci.com/user/tutori…

1. 开始

  1. 进入travis-ci.com/,使用 GitHub 登录。(目前支持 GitHub 、 Bitbucket、 GitLab 及 Assembla. 除GitHub 外,其余三个都还是 beta 版本)
  2. 点击右上角的 头像-> Setttings ,再点击左侧的 sync account 按钮,即可将 GitHub 的仓库同步至 travis。 image.png
  3. 在你的仓库下新建 .travis.yml文件,书写为以下内容,提交后即可触发。
language: node_js
node_js:
  - 14
install:
  - npm install
script: npm run test
  1. 提交代码后,即可在 travis-ci 上面看到当前的构建状态。

2. yaml1.1语法

yaml.org/spec/1.1/ travis ci 使用的是 Ruby libYAML 库,所以 .travis.yml 必须采用 YAML 1.1

以下内容摘抄自cocos 文档

  1. 所有的引号和逗号都可以省略,注意:冒号后的空格不可省略
key1: 213
key2: dsas string
  1. 行首的空格缩进数量代表数据的层级
obj1:
  key1: sad
obj2:
  key2: 1.23
  nestedObject:
    key3: 'quoted string'
  1. 以 连字符 + 空格 开头,表示数组元素
- 42
- "double-quoted string"
- arrayElement3:
    key1: punctuations? sure.
  1. 引用
object1: &o1
  key1: value1
object2:
  key2: value2
  key3: *o1
  
# 相当于json
{
  "object1": {
    "key1": "value1"
  },
  "object2": {
    "key2": "value2",
    "key3": {
      "key1": "value1"
    }
  }
}
  1. 继承
object1: &o1
  key1: value1
  key2: value2
object2:
  <<: *o1
  key3: value3
  
# 相当于json
{
  "object1": {
    "key1": "value1",
    "key2": "value2"
  },
  "object2": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }
}

3. 自定义travis ci build

1. 定义构建分支

except定义黑名单,only定义白名单。通常只使用其中一个,容易引起误解。因为使用 only 时,即使不符合 except ,如果在 only 中也不存在,也不会进行构建。

# 黑名单
branches:
  except:
  - dev
  - /^no-.*/

# 白名单
branches:
  only:
  - master
  - /^ci-.*/

2. 跳过构建

只要提交信息包含 [<KEYWORD> skip] 或者 [skip <KEYWORD>]等信息,即可跳过构建。

KEYWORD 包括 ci, travis, travis ci, travis-ci, or travisci。 只要提交信息包含 [<KEYWORD> skip] 或者 [skip <KEYWORD>]等信息,即可跳过构建。

3. 构建矩阵

如下就形成了一个 3*2的矩阵,npm test默认将会运行 6 次,可通过下文所示的方法进行排除或包含指定环境

language: node_js
node_js:
    - 14
    - 12
    - 10
env:
    - DB=1
    - DB=0

script: npm test

4. job 作业

job的生命周期有如下几种:

  1. apt addons
  2. cache components
  3. before_install
  4. install
  5. before_script
  6. script
  7. before_cache(当且仅当缓存有效)
  8. after_success or after_failure
  9. before_deploy(当且仅当部署处于活动状态时)
  10. deploy
  11. after_deploy(当且仅当部署处于活动状态时)
  12. after_script

script举例来说

# 可以字符串
script: npm test

# 可以用数组
script:
    - npm run test
    - npm run build

# 命令复杂时可用 shell 脚本
script: ./install.sh

# 值为skip 表示 跳过
script: skip

5. stage 阶段

以下配置表示:先进行 test 阶段的 2个作业,然后在 deploy 阶段开始第3个作业。

jobs:
  include:
    - stage: test
      script: ./test 1
    - # 阶段名不是必要的,将沿用之前的 test
      script: ./test 2
    - stage: deploy
      script: ./deploy

stage 的顺序也可以定义:

stages:
  - compile
  - test
  - name: deploy
    if: branch = master

6. 条件判断

  1. 有条件的构建
# 只有 master分支会触发 (注意对 PRs 来说这是 base branch 的 name)
if: branch = master
  1. 有条件的 Stages

下面的 stage 只会在 master 分支触发。

stages:
  - name: deploy
    if: branch = master
  1. 有条件的 Jobs
jobs:
  include:
      if: branch = master
      env: FOO=foo
  1. 有条件的排除 Jobs 下面的例子会创建 2 个 job,但是只有 master 分支,env ONE=one生效。
env:
  - ONE=one
  - TWO=two
jobs:
  exclude:
    - if: branch = master
      env: TWO=two
  1. 此处条件判断可使用的 keyword
  • type (the current event type, known event types are: push, pull_request, api, cron)
  • repo (the current repository slug owner_name/name)
  • branch (the current branch name; for pull requests: the base branch name)
  • tag (the current tag name)
  • commit_message (the current commit message)
  • sender (the event sender’s login name)
  • fork (true or false depending if the repository is a fork)
  • head_repo (for pull requests: the head repository slug owner_name/name)
  • head_branch (for pull requests: the head repository branch name)
  • os (the operating system)
  • language (the build language)
  • sudo (sudo access)
  • dist (the distribution)
  • group (the image group)
# 等式
if: 1 = 1

# 不等式
if: true != false

# 环境变量与 type 做比较
env(FOO) = type

# 正则表达式
if: branch =~ /^(one|two)-three$/

# 枚举
if: branch IN (one, other)

# 使用变量枚举
if: repo IN (env(ONE), env(OTHER))

# 布尔操作
if: branch = master AND env(FOO) = foo
branch = master OR env(FOO) = foo
branch = master AND env(FOO) = foo OR tag = bar
branch = master AND (env(FOO) = foo OR tag = bar)
NOT branch = master

# 方法调用,仅支持 env 和 concat
if: concat()

7. 脚本中支持的变量

这个大家自己看官方文档吧,不想CV了---

docs.travis-ci.com/user/enviro…

实战

1. 发布到npm

官方已集成该部分 docs.travis-ci.com/user/deploy…

该 yml 实现了:

  1. 仅在 masterci-开头的分支触发构建
  2. 定义 stage 的顺序,先test,然后 publish(仅commit中存在release才会触发publish)
  3. job 排除 dev 分支及 commit 中存在 no-ci
  4. 名为 test 的 stage,运行 npm run test
  5. 名为 publish 的stage,运行 npm run bd,然后执行 deploy
  6. deploy需要设置provider为npm,api_key为NPM TOKEN,以及 email

NPM TOKEN生成可参考www.axihe.com/edu/npm/wor…

  • 如何查看您帐户上的token: npm token list.
  • 创建新token: npm token create

注意: 为了避免隐私泄露,像NPM TOKEN 和 EMAIL这些信息,建议大家在 travis ci 网站的面板上进行设置,如下所示。点击 ADD 按钮后,环境变量就添加成功了,并且默认是不可见的。

image.png

如果大家觉得 travis ci 网站也不可信的话,可以使用 travis ci提供的客户端进行加密。travis 使用 ruby 制作,并用gem发布。(windows有坑,如果是windows的话,推荐大家用子系统)

  1. 安装 ruby,然后就有 gem 了
  2. gem install travis
  3. travis login --github-token 你的GITHUB_TOKEN --com
  4. travis encrypt SOMEVAR="secretvalue" --add --com;需要在当前仓库的目录下执行
  5. 该命令执行后,就会在 yml文件 中添加secure: ".... encrypted data ...."至 env

GITHUB TOKEN 生成步骤:

点击右上角的 头像 -> settings -> Developer settings -> Personal access tokens,然后点击生成 token 即可,权限通常选择 repo 即可。

image.png

image.png

完整的 .travis.yml 如下所示:

language: node_js
node_js:
  - 12
env:
  - BUILD_NAME=bd

install:
  - npm install

branches:
  only:
    - main
    - /^ci-.*$/

stages:
  - test
  - name: publish
    if: commit_message =~ /release/

jobs:
  exclude:
    - if: branch = dev OR commit_message =~ /(no-ci)/
  include:
    - stage: test
      script: npm run test
    - stage: publish
      script: npm run $BUILD_NAME
      deploy:
        provider: npm
        api_key: $NPM_API_KEY
        email: $EMAIL
        on:
          branch: main

2. 集成 github page

与上面大体类似,需要注意的是:运行构建脚本后,生成的文件在位于当前仓库的 dist文件夹,部署到 github page 的时候,我们可能只想要这个文件夹,那么可以使用 mv dist/ /tmp/demo 命令将 dist 移动到 其他目录,否则可能直接使用 local_dir 设置目录名会报错。

1616252226(1).jpg 执行 deploy 时,会在其他目录新建一个文件夹,然后拉取指定分支名的代码,之前的构建和当前目录不在同一个目录,所以直接使用 dist文件夹,那自然是不存在了。

以下实现的配置会在 main 分支,并且提交信息包含 pub page 时,才会触发。

language: node_js
node_js:
  - 14

env:
- BUILD_NAME=bd

install:
- npm install

branches:
  only:
  - main
  - "/^ci-.*$/"

notifications:
  email:
    recipients:
      - $EMAIL_SELF
    on_success: change # default: change
    on_failure: always # default: always

stages:
  - test
  - name: page
    if: commit_message =~ /pub\s+page/

jobs:
  exclude:
    - if: branch = dev OR commit_message =~ /(no-ci)/
  include:
    - stage: test
      script: npm run test
    - stage: page
      script: npm run $BUILD_NAME && mv dist/ /tmp/demo
      deploy:
        provider: pages
        cleanup: true
        local_dir: /tmp/demo
        token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
        on:
          branch: main

3. 发送文件到远程服务器

这里与上面的不同在于,需要发送文件到远程服务器,那就需要用ssh协议连接。

travis ci构建过程不能输入或者中断,所以使用 ssh 登录服务器的时候,不能采用 用户名 + 密码,只能用 公钥和私钥 进行免密登录了。

1.实现免密登录

  1. 需要在本地生成公钥和私钥,使用ssh-keygen -t rsa,会在用户主目录下的 .ssh 文件夹内生成 id_rsa.pub(公钥) 和 id_rsa(私钥) 。
  2. 将 id_rsa.pub 公钥 写入到服务器对应用户主目录的 .ssh/authorized_keys远程机器的.ssh目录需要700权限,authorized_keys文件需要600权限
  3. 将 id_rsa 私钥通过 travis encrypt-file ~/.ssh/id_rsa --add添加到当前目录的 .travis.yml中。
  4. 执行 CI 时,解密后的 id_rsa文件要修改为 600 权限,chmod 600 ~/.ssh/id_rsa,同时echo -e "Host $HOST_IP\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config,不校验目标IP,否则 ssh 第一次连接时,会出现选择输入,阻碍流程。

2. linux相关知识

  1. ssh 用户名@主机IP -p SSH端口 "rm -rf ~/$DEST_DIR && mkdir ~/$DEST_DIR" 连接服务器执行相应命令
  2. scp -P 主机端口 -r dist/. 用户名@主机IP:~/$DEST_DIR使用scp命令将本地的 dist 目录发送到服务器
  3. export DEST_DIR=`echo $TRAVIS_BRANCH | cut -d "-" -f 1`截取分支名,qa-ci 获得 qa,并设置为 DEST_DIR 变量

以下配置文件实现了 在 main 分支推送代码时,如果 commit 包含了 release,就会将打包文件发送至远程服务器。

language: node_js
node_js:
  - 14

env:
- BUILD_NAME=bd

install:
- npm install

branches:
  only:
  - main
  - "/^.*-ci$/"

notifications:
  email:
    recipients:
      - $EMAIL_SELF
    on_success: always # default: change
    on_failure: always # default: always

stages:
  - test
  - name: page
    if: commit_message =~ /pub\s+page/
  - name: publish
    if: commit_message =~ /release/

jobs:
  exclude:
    - if: branch = dev OR commit_message =~ /(no-ci)/
  include:
    - stage: test
      script: npm run test
    - stage: page
      script: npm run $BUILD_NAME && mv dist/ /tmp/demo
      deploy:
        provider: pages
        cleanup: true
        local_dir: /tmp/demo
        token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
        on:
          branch: main

    - stage: publish
      before_install:
        # 解密
        - openssl aes-256-cbc -K $encrypted_db599200e721_key -iv $encrypted_db599200e721_iv -in id_rsa.enc -out ~/.ssh/id_rsa -d
        # 设置正确的权限
        - chmod 600 ~/.ssh/id_rsa
        # 目标IP不进行校验,防止阻碍流程
        - echo -e "Host $HOST_IP\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
      script:
        # 构建脚本
        - npm run $BUILD_NAME
        # 截取分支名,ci-qa 获得 qa,并设置为 DEST_DIR 变量
        - export DEST_DIR=`echo $TRAVIS_BRANCH | cut -d "-" -f 1`
        # 删除远程目标文件夹,并新建目录(这一步需要自己衡量是否需要,一般个人开发建议删除)
        - ssh travis@$HOST_IP -p $HOST_PORT "rm -rf ~/$DEST_DIR && mkdir ~/$DEST_DIR"
        # 将本地文件上传到服务器
        # dist/ 将会在目标文件夹下 存在一个 dist 文件夹
        # dist/. 将直接存储dist目录下的所有文件,而不包括目录名
        - scp  -P $HOST_PORT -r dist/. travis@$HOST_IP:~/$DEST_DIR
        # 上传文件完毕后,可能需要 启动服务 等,自行替换
        - ssh travis@$HOST_IP -p $HOST_PORT "echo 'replace your exec';"

疑难总结

1. gem 安装慢

gem sources修改

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

2. ssh连接时 known_hosts 问题

# 目标IP不进行校验,防止阻碍流程
        - echo -e "Host $HOST_IP\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config

1616252226.jpg

3. before_deploy脚本只能为 单个值,不支持数组

1616252226(2).jpg

github: github.com/ma125120/vi…

gitee: gitee.com/ma125120/vi…

travis ci这次差不多就这样了,下一篇讲一讲 docker + jenkins 实现自动化部署。