一步步揭开CICD的神秘面纱

2020-04-25 loading

自从知道了CI/CD开始,就一直充满好奇和疑问,它的“自动、智能”化让人不禁浮想联翩,但又雀雀欲试。同时,不想再使用石器时代的方式——手动通过FTP上传代码到服务器,一直驱动着笔者去揭开这层神秘的面纱。之后做过一系列的尝试——Jenkins、Travis、GitHub Actions。期间为了更好的使用那3种方式,还简单了解了Linix命令、shell命令和docker。

当完成了一些CI/CD的部署后,却发现只是简单地学会了使用这3种工具而已。没有任何值得分享的东西。为了加深印象,强化学习成果,还是要记录下这段“心酸的血泪史”。

如果你也是CICD的初学者,想要找到一个CICD的参考例子,那就继续向下看吧~

# CICD概念

CI全称持续集成(Continuous Integration),指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码"集成"到主干。

好处:

  • 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
  • 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

目的:

  • 让产品可以快速迭代,同时还能保持高质量。

核心措施:

  • 代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。

CD全称持续交付(Continuous Delivery),指的是频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。

CD全称持续部署(Continuous Deployment),指的是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。

目标:

  • 代码在任何时刻都是可部署的,可以进入生产阶段。

前提:

  • 能自动化完成测试、构建、部署等步骤

两个CD的区别:

在这篇文章里面的CD我们指的是持续部署。通常开发过程中,至少有dev/master两个分支,dev触发生产环境CD,部署成功后,QA就可以开始一系列的测试。QA测试通过后,master触发生产环境的CD,即可供所有真实用户使用。

参考链接:持续集成是什么?(opens new window)

# CICD工具实现的主要步骤

经过对于Jenkins、Travis、GitHub Actions Actions的使用,发现他们大概的主要步骤是:

  • 工具配置与代码仓库(GitHub、GitLab)关联,以便监听仓库的push、commit、tag等事件时
  • 工具提供代运行环境,以便监听到上述事件时,可以进行编译、测试等操作
  • 工具配置与待部署服务器的关联,以便将上面运行成功的代码推送到服务器,并在服务器进行操作——将项目启动起来

Jenkins、Travis、GitHub Actions在使用起来,简易程度依次提升。

  • Travis、GitHub Actions有个天然的优势——它们已经实现了跟代码库的关联
  • GitHub Actions有个MarketPlace(opens new window) ,提供很多现成的action,可以实现拿来主义

接下来,主要给大家介绍Tarvis 和 GitHub Actions如何实现CI/CD

# Travis进行CI/CD

# Travis简介

Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。

# Travis生命周期

在install阶段:

install:
  - command1
  - command2

如果command1失败了,整个构建就会停下来,不再往下进行。

如果不需要安装,即跳过安装阶段,就直接设为true。

install: true

在script阶段:

script:
  - command1
  - command2

如果command1失败,command2会继续执行。但是,整个构建阶段的状态是失败。

如果command2只有在command1成功后才能执行,就要写成下面这样。

script: command1 && command2

# 运行状态

  • passed:运行成功,所有步骤的退出码都是0
  • canceled:用户取消执行
  • errored:before_install、install、before_script有非零退出码,运行会立即停止
  • failed :script有非零状态码 ,会继续运行

# 常用操作

# travis客户端安装
//更改gem源
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
sudo gem install travis
//macOS
brew install travis
# travis环境变量
  1. 在.travis.yml定义
env:
  - DB=postgres
  - SH=bash
  - PACKAGE_VERSION="1.0.*"
  1. 在travis.com平台设置
    • 进入Travis(opens new window) 首页,选择当前的项目,点击右侧more options按钮选择Settings
    • 在 Enviroment Variables中,配置这个变量。
# travis加密

前提,在客户端登录travis

如果正在使用 travis-ci.com, 而不是travis-ci.org,需要添加 --pro,--pro可以添加在travis的所有命令后面

//
travis login --pro
  1. 加密变量
travis encrypt SOMEVAR="secretvalue"
//--add,可以
travis encrypt SOMEVAR="secretvalue" --add
// 下面的代码会自动添加变量到.tarvis.yml
env:
  global:
    secure: WVnueaUvOoou0/K+ZfSIgJCrL4jku5vWIJKJ/Bipba1442FHKlBx90/9+sjt3IzhiGjHABpX6xJRku4FVbPJIrpzcYbmCcXaSDU0VJq8X2Ea23Ie2SQB6KgQdjauaqN1JOTmvjBy2yeJ/BqgK1fr9xSfvKhAcsJq3TvrhGBPhMSoL6mA3KB/EQRlVXojQ4e2TGMf+7Hmy/9ORARQSId+8wU5QrHSkdUUcGi+yPF6Q6Q4OVVJM44eIYHm5YxlyXChk1mbVqjh/NOOmBU4h0nK2AiW/2O8XTKlPUldK/RHRTGhJVyLfLFz2sy9f9WoJhROhwKXvHovahXE91WnolpAN7xUoAkGqE+EJ2wiL560lLpGYZvqk40+Xdc6yxv5ojvBD3uPc59jAhC/i/h8+u4fcYCWMQ4ZUYpdP82fl5d7Vwiu+s/dP+p71YiwQCArZ+1mOra0zrkrGvHr1remQp2FSZLCk34fAazZwBezjl0ivlz7eT05xeH2DzvB/cLDm2p8zsvOSSJToi7rnSVWygyjgYIn8XzCWGA+Jk9ro2wQu+IPhfW8C+Ys6nymUAGIWdfvVJq7AYd15WAk2DYlGuCi5nV7uIFwGUKqHy51FMn1FegxxmY/hcWCmwyKlgiO3rE0b8OmcJfjU8iawkMEwEXb3o2qH/oX7vNScnaBsHvNA1c=
  1. 加密文件
travis encrypt-file bacon.txt --add

# 开通Travis

  1. 进入Travis官网(opens new window) ,使用GitHub账号登录
  2. 点击头像-Settings进入个人设置页面,点击Activate按钮进入GitHub配置Travis页面,在Repository access板块,选择Only select repositories后,选择要配置Travis的项目,点击Approve and install按钮重新回到Travis官网。此时,已经打开了这个GitHub仓库与Travis的关联
  3. 在这个仓库的根目录新建.travis.yml,初始化内容如下
language: node_js
node_js:
- 12.10.0
  1. 将上面的更新,进行 push,将会自动触发Travis流水线的运行

# Travis部署GitHub Pages

下面以https://github.com/Heyff12/vuepress-theme-yaya(opens new window) 仓库为例,配置.travis.yml

language: node_js
node_js:
- 12.10.0
install:
- npm install
script:
- npm run build
deploy:
  - provider: pages
    github_token: "$GITHUB_TOKEN"
    keep_history: true
    local-dir: "./docs/.vuepress/dist/"
    target-branch: gh-pages
    skip_cleanup: true
    verbose: true
    on:
      branch: master
  1. github_token生成
  2. github_token使用
    • 进入Travi是首页,选择当前的项目,点击右侧more options按钮选择Settings
    • 在 Enviroment Variables中,配置这个变量。变量名为GITHUB_TOKEN,变量值就是上个步骤生成的token
    • 参考配置(opens new window)
  3. local-dir:指定GitHubPages需要的文件,在这个项目里面,只需要build之后的静态文件
  4. target-branch:用来配置GitHubPages指定的分支,默认就是gh-pages,可以自定义分支
  5. 在GitHub网站,进入当前项目,点击Settings,在GitHub Pages板块,选择分支 gh-pages
  6. GitHub Pages访问地址https://heyff12.github.io/vuepress-theme-yaya/(opens new window)

备注:GitHub Pages的部署服务器是GitHub本身,github_token就是实现Travis与部署服务器关联的核心

pages部署官方文档参考(opens new window)

# Travis部署npm

在上面项目的基础之上,继续配置.travis.yml

language: node_js
node_js:
- 12.10.0
install:
- npm install
script:
- npm run build
deploy:
  - provider: pages
    github_token: "$GITHUB_TOKEN"
    keep_history: true
    local-dir: "./docs/.vuepress/dist/"
    target-branch: gh-pages 
    skip_cleanup: true
    verbose: true
    on:
      branch: master
      
  - provider: npm
    email: 645068564@qq.com
    skip_cleanup: true
    api_key:
      secure: WXswpjk-----------------=  #防止信息泄露,这里的字符串是随机数据
    on:
      tags: true
      branch: master
      repo: Heyff12/vuepress-theme-yaya
    tag: latest
  1. tag: latest——npm的新版本发布只在有新tag时触发
  2. email是npm官网(opens new window) 的账号邮箱
  3. api_key的生产
    • 打开电脑的终端工具
    • 执行npm addUser,输入npm账号相关信息
    • 执行cat ~/.npmrc,查看该文件的详情,找到authToken对应的值
    • 执行 travis encrypt YOUR_AUTH_TOKEN --add deploy.api_key ,对这个值进行加密,YOUR_AUTH_TOKEN是上个步骤中找到的authToken的值
    • 上个步骤完成后,.travis.yml文件会自动填充api_key.secure数据

npm部署官方文档参考(opens new window)

备注:npm package的部署终端在npm平台,api_key就是实现Travis与部署服务器关联的核心

# 如何打tag

//查看所有tag
git tag
//添加tag
git tag v1.0.0
//伴随着代码提交,同时提交tag
git push origin --follow-tags v1.0.0
//删除本地tag
git tag -d v1.0.0
//删除远端tag
// git push origin --delete v1.0.0

# Travis部署阿里云服务器——前端项目

自定义服务器部署的核心——实现对该服务器的免密登录。这是因为在Travis执行过程中,不能手动输入用户名和密码。

# ssh免密登录

登录服务器后,执行

cd ~/.ssh
//生成秘钥对,-f指定秘钥文件名称
ssh-keygen -t rsa -f id_rsa_remote_login  -C "RemoteLoginCIDeployKey"
//将公钥 放入 authorized_keys
cat id_rsa_remote_login.pub >> authorized_keys
//拷贝 私钥,到自己的电脑
scp root@000.00.00.00:/root/.ssh/id_rsa_remote_login ./id_rsa_remote_login
//测试免密登录
ssh -i id_rsa_travis root@000.00.00.00

切换到当前项目,实现当前项目与服务器之间的免密登录

//切换到 当前项目根目录,
//登录 travis
travis login --pro
//加密私钥
travis encrypt-file ~/.ssh/id_rsa_remote_login --add
// 会自动生成id_rsa_remote_login.enc ,同时.travis.yml 自动生成before_install openssl 代码,在travis.com平台的该项目下,会自动生产两个加密变量

注意事项:要把 out 后的文件名中的转义字符 '' 删掉,否则Travis CI运行过程中会报错

# .travis.yml配置

language: node_js
node_js:
- 12.10.0
git:
  submodules: false
branches:
  only:
  - master
addons:
  ssh_known_hosts:
  - 000.00.00.00
before_install:
- openssl aes-256-cbc -K $encrypted_f8f2226d74ef_key -iv $encrypted_f8f2226d74ef_iv
  -in id_rsa_remote_login.enc -out ~/.ssh/id_rsa_remote_login -d
- chmod 600 ~/.ssh/id_rsa_remote_login
- eval "$(ssh-agent -s)"
- echo -e "Host 000.00.00.00\n\tUser root\n\tHostName 000.00.00.00\n\tPort 22\n\tStrictHostKeyChecking
  no\n\tIdentityFile ~/.ssh/id_rsa_remote_login\n\tIdentitiesOnly yes\n\tGSSAPIAuthentication
  no\n" >> ~/.ssh/config
- ssh-add ~/.ssh/id_rsa_remote_login
install:
- npm install
script:
- npm run build
after_success:
- travis_wait scp -i ~/.ssh/id_rsa_remote_login -o GSSAPIAuthentication=no -r dist/ root@000.00.00.00:/project/nodeweb/vuepressYaya/src
- ssh -i ~/.ssh/id_rsa_remote_login root@000.00.00.00 "cd /project/nodeweb/vuepressYaya && rm -rf
  public && mv src public && exit"

  1. ssh_known_hosts,将这个ip写入文件,是ssh免密登录的前提
  2. openssl 那行代码,就是自动生成的
  3. chmod 600 给解密后的私钥添加读写权限
  4. eval、echo、ssh-add3行代码,是辅助信息,如果前面两行代码在免密登录过程中报错,可以逐步添加这3行解决bug
  5. 超时时间
    • Travis 超时时间默认10m,使用travis_await后,超时时间提升到20m,如果不够,可以自定义设置超时时间 travis_await 30m
  6. 在after_success中有两步
    • 将build后的静态文件拷贝到服务器指定目录
    • 登录服务器,进行新代码的替换

参考链接:

# GitHub Actions进行CI/CD

# GitHub Actions简介

大家知道,持续集成由很多操作组成,比如抓取代码、运行测试、登录远程服务器,发布到第三方服务等等。GitHub 把这些操作就称为 actions。

github actions必须放置在项目根目录.github/workflows文件夹下。在 .github/workflows中,可以添加多个工作流程,后缀必须是 .yml 或 .yaml 文件

# 基本概念

  • workflow (工作流程):持续集成一次运行的过程,就是一个 workflow。
  • job (任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。
    • job可以并发执行
  • step(步骤):每个 job 由多个 step 构成,一步步完成。
    • step只能顺序执行
  • action (动作):每个 step 可以依次执行一个或多个命令(action)。

# workflow常用配置属性

  • name字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名
  • on字段指定触发 workflow 的条件,通常是某些事件
on: [push, pull_request]
  • on.<push|pull_request>.<tags|branches>指定触发事件时,可以限定分支或标签。
on:
  push:
    branches:    
      - master
  • jobs.<job_id>.name,jobs字段里面,需要写出每一项任务的job_id,具体名称自定义。job_id里面的name字段是任务的说明。
jobs:
  my_first_job:
    name: My first job
  my_second_job:
    name: My second job
  • jobs.<job_id>.needs字段指定当前任务的依赖关系,即运行顺序。
jobs:
  job1:
  job2:
    needs: job1
  job3:
    needs: [job1, job2]
  • jobs.<job_id>.runs-on指定运行所需要的虚拟机环境。它是必填字段。目前可用的虚拟机如下。
    • windows-latest or windows-2019
    • ubuntu-latest or ubuntu-18.04
    • ubuntu-16.04
    • macos-latest or macos-10.15
  • jobs.<job_id>.steps指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。
    • jobs.<job_id>.steps.name:步骤名称。
    • jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
    • jobs.<job_id>.steps.env:该步骤所需的环境变量。

查看更多属性(opens new window)

# 通过仓库操作构建简易GitHub Actions工作流

GitHub平台(opens new window) ,进入一个项目界面

  1. 点击Actions按钮
  2. 选择Node.js,点击Set up this workflow按钮
  3. 完成上面操作后,在该项目中,会自从生产.github/workflows/nodejs.yml
  4. push一次代码,可以看到该流水线正常执行

# GitHub Actions部署阿里云服务器——后端服务

在这个CI/CD实现过程中,需要使用docker。大致思路:

  • 将代码push到GitHub仓库,触发actions运行
  • pull最新代码并进行build、test
  • 登录docker,生产并推送镜像
  • 登录服务器,pull镜像并启动该镜像的容器

# 搭建一个简单的nodejs的后端服务

mkdir node-test
cd node-test
npm init -y
npm install koa
mkdir src
cd src

在src目录下新建app.js文件

const Koa = require('koa');
const app = new Koa();

const PORT = 3002;
const HOST = '0.0.0.0';

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(PORT);
console.log(`Running on http://${HOST}:${PORT}`);

在package.json文件中添加

"scripts": {
    "start": "node app.js"
},

启动该服务

npm run start

# 配置docker镜像

在根目录新建Dockerfile文件

FROM node:12
RUN mkdir -p /home/Service
WORKDIR /home/Service    
COPY . /home/Service
RUN npm install
EXPOSE 3002
CMD ["npm","start"]  
  1. FROM指定环境,node v12的相关版本
  2. 新建工作目录
  3. 指定工作目录
  4. 将该项目下的所有内容复制到工作目录
  5. 重新安装npm依赖包
  6. 暴露容器内部端口3002
  7. 执行 npm start,启动服务

备注:

  • CMD 语句中必须使用双引号""
# 在本地测试该镜像
  1. 生成镜像
docker build -t node-test .
  1. 构建该镜像的容器
docker run --name my-node-test -d --rm -it -p 3002:3002 node-test
  1. 正常访问http://localhost:3002

# 启用docker前提准备

  1. 在服务器安装docker
//清空之前docker痕迹,卸载旧版本
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine
//执行以下命令安装依赖包
sudo yum install -y yum-utils \
           device-mapper-persistent-data \
           lvm2
//添加 yum 软件源
sudo yum-config-manager \
    --add-repo \
    https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo
sudo sed -i 's/download.docker.com/mirrors.ustc.edu.cn\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
//如果需要测试版本的 Docker CE 请使用以下命令:
sudo yum-config-manager --enable docker-ce-test

//更新 yum 软件源缓存,并安装 docker-ce
sudo yum makecache fast
sudo yum install docker-ce
//启动 Docker CE
sudo systemctl enable docker
sudo systemctl start docker

#测试 Docker 是否安装正确
docker run hello-world
  1. 启动docker

# 配置GitHub Actions的CI过程

在项目根目录新建.github文件夹,在.github文件夹中新建workflows文件夹,在workflows文件夹下新建ali.yml

# 指定该action的名称
name: Ali deploy CI
# 指定该action触发的事件情形,当master分子在push、pull_request时触发
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
# 定义全局变量
env:
  IMAGE_NAME: heyff12/node-test  # 镜像名称
  VERSION: latest  # 镜像版本
  DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} #docker用户名
  DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} #docker密码

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.10.0]
    steps:
      - uses: actions/checkout@v2  #使用这个共有action拉取最新代码
      - name: Use Node.js ${{ matrix.node-version }} #设置node版本
        uses: actions/setup-node@v1 
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci #执行npm install
      - run: npm run build --if-present
      - run: npm test
        env:
          CI: true

      - name: store file #构建缓存数据,以便下一个step使用
        uses: actions/upload-artifact@v2
        with:
          name: store
          path: ./

  docker-push-image:  #登录docker,构建并push镜像
    needs: build #这个job需要build job完成后才能执行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2  #下载上个step存储的代码
        with:
          name: store
      - name: Build the Docker image
        run: |
          echo `pwd`
          echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
          docker build -t $IMAGE_NAME:$VERSION -f ./Dockerfile .
          docker push $IMAGE_NAME:$VERSION
  
  docker-run: #登录服务器,拉取最新镜像,并启动容器
    needs: docker-push-image
    runs-on: ubuntu-latest
    env:
      CONTAINER_NAME: heyff12-node-test
      DOCKER_REG: heyff12/node-test
    steps:
      - name: deploy on remote server 
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.ACCESS_TOKEN }}
          port: ${{ secrets.REMOTE_PORT }}
          command_timeout: 30m
          envs: CONTAINER_NAME, DOCKER_REG
          script: |
            docker pull $DOCKER_REG
            docker image prune -f
            if [ $(docker ps -a | grep -c $CONTAINER_NAME) -gt 0 ]; then docker stop $CONTAINER_NAME;docker rm $CONTAINER_NAME;fi
            docker run --name $CONTAINER_NAME -d --rm -p 3002:3002 $DOCKER_REG

备注:

  • secrets. 引入的是配置在该项目的环境变量
  • 配置方法,点击该项目的 Settings-Secrets,点击Add a new secret按钮添加环境变量

参考链接:

# 用Github Actions 改写 .travis.yml

# 部署阿里云服务器——前端项目

新建.github/workflows/ali.yml

name: Ali deploy CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
      env:
        CI: true
    - name: ssh Deploy
      uses: easingthemes/ssh-deploy@v2.1.2
      env:
        SSH_PRIVATE_KEY: ${{ secrets.ACCESS_TOKEN }}
        REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
        REMOTE_USER: ${{ secrets.REMOTE_USER }}
        REMOTE_PORT: ${{ secrets.REMOTE_PORT }}
        ARGS: "-avz --delete"
        SOURCE: "dist/"
        TARGET: "/project/nodeweb/vuepressYaya/public"

# 备注

000.00.00.00 需要替换成自己的服务器IP地址

支付宝打赏
支付宝打赏
微信打赏
微信打赏